Polygonization
In this notebook we will import a subtile set, which is based on a certain symmetry stencil. With this subtile set we will create a tile set containing 256 tiles. With these tiles the boolean marching cube algorithm will create a facade for a few lattices.
0. Initialization
0.0. Importing libraries
import topogenesis as tg
import numpy as np
import pyvista as pv
import os
import copy
import trimesh as tm
import pandas as pd
import resources.boolean_marching_cubes as bmc
from scipy.spatial import KDTree
np.random.seed(0)
0.1. Generate Symmetry Stencils
# Symmetry sring
Sym_str = [["OO"], ["XX", "YY"], ["ZP"], ["ZN"]]
stencils = bmc.create_symmetry_stencils(sym_str)
0.2. Generate lattices for all possible cubes
# generate bianary representation of all the possible cubes
l_bis = bmc.bi_cube_lattices()
1. Profiling
1.1. Catalogue the profile of all corners
# find all unique corner arrangements based on stencils
corner_profiles = bmc.extract_corner_profiles(stencils, l_bis)
1.2. Find unique corner profiles
# stack corner_profiles vertically
cp_stacked = np.vstack(corner_profiles)
# find the uniqe arangements of corners
uniq_corner_arang = np.unique(cp_stacked, axis=0)
1.3. Construct unique profile latices
# construct lattices for all unique corner profiles
(corner_loc_lattices, corner_neigh_lattices) = bmc.profiles_to_lattices(uniq_corner_arang, stencils)
1.4. Visualize unique profiles
p = pv.Plotter(notebook=True)
base_lattice = corner_neigh_lattices[0]
# Set the grid dimensions: shape + 1 because we want to inject our values on the CELL data
grid = pv.UniformGrid()
grid.dimensions = np.array(base_lattice.shape) + 1
# The bottom left corner of the data set
grid.origin = base_lattice.minbound - base_lattice.unit * 0.5
# These are the cell sizes along each axis
grid.spacing = base_lattice.unit
# adding the boundingbox wireframe
p.add_mesh(grid.outline(), color="grey", label="Domain")
# adding axes
p.add_axes()
p.show_bounds(grid="back", location="back", color="#aaaaaa")
def create_mesh(value):
f = int(value)
lattice = corner_neigh_lattices[f]
loc = corner_loc_lattices[f]
# Add the data values to the cell data
grid.cell_arrays["filled"] = lattice.flatten(order="F").astype(int) # Flatten the array!
# filtering the voxels
threshed = grid.threshold([.9, 1.1], scalars="filled")
# adding the voxels
p.add_mesh(threshed, name='sphere', show_edges=True, opacity=0.7, show_scalar_bar=False)
# Add the data values to the cell data
grid.cell_arrays["corner"] = loc.flatten(order="F").astype(int)# Flatten the array!
# filtering the voxels
threshed = grid.threshold([.9, 1.1], scalars="corner")
# adding the voxels
p.add_mesh(threshed, name='sphere2', show_edges=True, opacity=1.0, show_scalar_bar=False, color="white")
return
p.add_slider_widget(create_mesh, [1, len(corner_neigh_lattices)], title='Arrangements', value=1, event_type="always", style="classic", pointa=(0.1, 0.1), pointb=(0.9, 0.1))
p.show(use_ipyvtk=True)
1.5. Save unique arrangement profiles
# save all design templates into lattice CSVs
templates_path = os.path.relpath('../data/bmc/bmc_templates')
bmc.save_design_templates(corner_loc_lattices, corner_neigh_lattices, templates_path)
2. Construct the tile-set
2.1. Load sub-tile meshes
# load subtile meshes
subtile_meshes = []
for c in range(len(corner_loc_lattices)):
corner_mesh_path = os.path.relpath('../data/bmc/bmc_subtiles/t_' + f'{c:02}' + '.obj')
corner_mesh = tm.load(corner_mesh_path)
subtile_meshes.append(corner_mesh)
2.2. Combine sub-tile meshes to create tile meshes
tiles_meshes = bmc.construct_tile_meshes(subtile_meshes, corner_profiles, uniq_corner_arang, corner_loc_lattices)
2.3. Visualize tile meshes
# convert mesh to pv_mesh
def tri_to_pv(tri_mesh):
faces = np.pad(tri_mesh.faces, ((0, 0),(1,0)), 'constant', constant_values=3)
pv_mesh = pv.PolyData(tri_mesh.vertices, faces)
return pv_mesh
p = pv.Plotter(notebook=True)
base_lattice = l_bis[0]
# Set the grid dimensions: shape + 1 because we want to inject our values on the CELL data
grid = pv.UniformGrid()
grid.dimensions = np.array(base_lattice.shape) + 1
# The bottom left corner of the data set
grid.origin = base_lattice.minbound - base_lattice.unit * 0.5
# These are the cell sizes along each axis
grid.spacing = base_lattice.unit *0.5
# adding the boundingbox wireframe
p.add_mesh(grid.outline(), color="grey", label="Domain")
# adding axes
p.add_axes()
p.show_bounds(grid="back", location="back", color="#aaaaaa")
def create_mesh(value):
i = int(value)
mesh = tiles_meshes[i]
lattice = l_bis[i]
# Add the data values to the cell data
grid.cell_arrays["cube"] = lattice.flatten(order="F").astype(int)# Flatten the array!
# filtering the voxels
threshed = grid.threshold([.9, 1.1], scalars="cube")
# adding the voxels
p.add_mesh(threshed, name='sphere2', show_edges=True, opacity=0.2, show_scalar_bar=False, color="white")
# adding the meshes
p.add_mesh(tri_to_pv(mesh), color='#abd8ff', name="sphere")
return
p.add_slider_widget(create_mesh, [0, len(tiles_meshes)], title='Tiles', value=1, event_type="always", style="classic", pointa=(0.1, 0.1), pointb=(0.9, 0.1))
p.show(use_ipyvtk=True)
2.4. Save the tile-set
tiles_path = os.path.relpath('../data/bmc/bmc_tiles_new')
bmc.save_tile_meshes(tiles_meshes, l_bis, tiles_path)
3. Boolean Marching Cube
3.1. Load lattices
# extra import function
def lattice_from_csv(file_path):
# read metadata
meta_df = pd.read_csv(file_path, nrows=3)
shape = np.array(meta_df['shape'])
unit = np.array(meta_df['unit'])
minbound = np.array(meta_df['minbound'])
# read lattice
lattice_df = pd.read_csv(file_path, skiprows=5)
# create the buffer
buffer = np.array(lattice_df['value']).reshape(shape)
# create the lattice
l = tg.to_lattice(buffer, minbound=minbound, unit=unit)
return l
# loading the lattice from csv
lattice_path = os.path.relpath('../data/final_envelope_new.csv')
envelope_lattice = tg.lattice_from_csv(lattice_path)
#loading the lattice from csv
inner_part_path = os.path.relpath('../data/negative_opening.csv')
inner_part_lattice = lattice_from_csv(inner_part_path)
3.2 Load environment
# loading the lattice from csv
sun_acc_path = os.path.relpath('../data/sun_access_highres.csv')
sun_acc_lattice = lattice_from_csv(sun_acc_path)
# list the environment information layers (lattices)
# the order should match the program matrix
env_info = [sun_acc_lattice]
3.3 Creating lattices
3.3.1 Facade lattice
# create the stencil
s = tg.create_stencil("von_neumann", 1, 1)
s.set_index([0,0,0], 0)
# add the sum function to the stencil
s.function = tg.sfunc.sum
# apply the stencil on the lattice
neighbor_sum = envelope_lattice.apply_stencil(s)
# remove if 6
facade_lattice = envelope_lattice * (neighbor_sum <= 5)
3.3.2 Ground Floor Lattice
# Finding the index of the available voxels in avail_lattice
avail_index = np.array(np.where(facade_lattice == 1)).T
for vox_1d_ind in avail_index:
vox_3d_ind = np.unravel_index(vox_1d_ind, envelope_lattice.shape)
VX, VY, VZ = vox_3d_ind
# Ground Floor
ground_floor_lattice = facade_lattice * 0
ground_floor_lattice[:,:,:1] = 1
ground_floor_lattice *= facade_lattice
3.3.3 Inner part lattice
# Inner part lattice
g_0 = np.roll(inner_part_lattice, [0,2,0], [0,1,1])
g_1 = np.roll(inner_part_lattice, [0,-2,0], [0,1,1])
g_2 = np.roll(inner_part_lattice, [2,0,0], [0,1,1])
g_3 = np.roll(inner_part_lattice, [-2,0,0], [0,1,1])
inner_part_lattice = facade_lattice * (g_0 + g_1 + g_2 + g_3)
inner_part_lattice[inner_part_lattice>1] = 1
3.3.4 Main outer part lattice
# Main outer lattice by substracting the previous lattices from the facade lattice
main_outer_lattice = facade_lattice - ground_floor_lattice - inner_part_lattice
3.3.5 Balcony lattice
main_outer_flat = main_outer_lattice.flatten()
main_outer_index = np.array(np.where(main_outer_lattice == 1)).T
balcony_lattice = main_outer_lattice * 0 - 1
# Randomly choosing three available voxels
balcony_num = 50
select_id = np.random.choice(len(main_outer_index), balcony_num)
balc_origins = []
for id in select_id:
balc_origins.append(main_outer_index[id])
# adding the origins to the agents locations
balc_locs = []
# for each agent origin ...
for a_origin in balc_origins:
# add the origin to the list of agent locations
balc_locs.append([a_origin])
# set the origin in availablity lattice as 0 (UNavailable)
main_outer_lattice[tuple(a_origin)] = False
# set the origin in occupation lattice as the agent id (a_id)
balcony_lattice[tuple(a_origin)] = 1
3.3.extract cube lattice
# Ground Floor
ground_floor_cube_lattice = ground_floor_lattice.boolean_marching_cubes()
# Opening
inner_part_cube_lattice = inner_part_lattice.boolean_marching_cubes()
# Main outer
main_outer_cube_lattice = main_outer_lattice.boolean_marching_cubes()
# Balcony
balcony_cube_lattice = balcony_lattice.boolean_marching_cubes()
3.4 Load the tile sets
Here we load all the tile sets. In the first part of this notebook we locally have saved, for every tile sets, two materials. Therefor we can now load all the materials seperately so it will be easier to model textures.
# Main outer part facade
tiles_path_main_outer = os.path.relpath('../data/bmc/bmc_tiles_main')
# Brick
tiles_path_main_outer_brick = os.path.relpath('../data/bmc/bmc_tiles_main_brick')
# Glass
tiles_path_main_outer_glass = os.path.relpath('../data/bmc/bmc_tiles_main_glass')
# Ground floor facade
tiles_path_ground_floor = os.path.relpath('../data/bmc/bmc_tiles_ground_floor')
# Brick
tiles_path_ground_floor_brick = os.path.relpath('../data/bmc/bmc_tiles_ground_floor_brick')
# Glass
tiles_path_ground_floor_glass = os.path.relpath('../data/bmc/bmc_tiles_ground_floor_glass')
# Balony
tiles_path_balcony = os.path.relpath('../data/bmc/bmc_tiles_balcony')
# Brick
tiles_path_balcony_brick = os.path.relpath('../data/bmc/bmc_tiles_balcony_brick')
# Glass
tiles_path_balcony_glass = os.path.relpath('../data/bmc/bmc_tiles_balcony_glass')
# Inner part facade
tiles_path_inner_part = os.path.relpath('../data/bmc/bmc_tiles_inner')
# Metal
tiles_path_inner_part_metal = os.path.relpath('../data/bmc/bmc_tiles_inner_metal')
# Glass
tiles_path_inner_part_glass = os.path.relpath('../data/bmc/bmc_tiles_inner_glass')
3.5 Boolean marching cubes
# Ground Floor
bmc_mesh_ground_floor = bmc.marching_cube_mesh(ground_floor_cube_lattice, tiles_path_ground_floor)
# Brick
bmc_mesh_ground_floor_brick = bmc.marching_cube_mesh(ground_floor_cube_lattice, tiles_path_ground_floor_brick)
# Glass
bmc_mesh_ground_floor_glass = bmc.marching_cube_mesh(ground_floor_cube_lattice, tiles_path_ground_floor_glass)
# Inner part
bmc_mesh_inner = bmc.marching_cube_mesh(inner_part_cube_lattice, tiles_path_inner_part)
# Metal
bmc_mesh_inner_metal = bmc.marching_cube_mesh(inner_part_cube_lattice, tiles_path_inner_part_metal)
# Glass
bmc_mesh_inner_glass = bmc.marching_cube_mesh(inner_part_cube_lattice, tiles_path_inner_part_glass)
# # Balcony
# bmc_mesh_balcony = bmc.marching_cube_mesh(balcony_cube_lattice, tiles_path_balcony)
# # Brick
# bmc_mesh_balcony_brick = bmc.marching_cube_mesh(balcony_cube_lattice, tiles_path_balcony_brick)
# # Glass
# bmc_mesh_balcony_glass = bmc.marching_cube_mesh(balcony_cube_lattice, tiles_path_balcony_glass)
# Main outer part
bmc_mesh_main_outer = bmc.marching_cube_mesh(main_outer_cube_lattice, tiles_path_main_outer)
# Brick
bmc_mesh_main_outer_brick = bmc.marching_cube_mesh(main_outer_cube_lattice, tiles_path_main_outer_brick)
# Glass
bmc_mesh_main_outer_glass = bmc.marching_cube_mesh(main_outer_cube_lattice, tiles_path_main_outer_glass)
# Final
bmc_mesh_final = bmc_mesh_ground_floor + bmc_mesh_main_outer + bmc_mesh_inner
3.6. Visualize the final mesh
# convert mesh to pv_mesh
def tri_to_pv(tri_mesh):
faces = np.pad(tri_mesh.faces, ((0, 0),(0,0)), 'constant', constant_values=3)
pv_mesh = pv.PolyData(tri_mesh.vertices, faces)
return pv_mesh
# initiating the plotter
p = pv.Plotter(notebook=True)
# adding the meshes
p.add_mesh(tri_to_pv(bmc_mesh_final), color='#abd8ff', name="sphere")
# fast visualization of the lattice
p = envelope_lattice.fast_vis(p)
# plotting
p.show(use_ipyvtk=True)
3.7. Save the final mesh
final_mesh_path = os.path.relpath('../data/final_mesh.obj')
with open(final_mesh_path, 'w') as file:
file.write(tm.exchange.obj.export_obj(bmc_mesh_final))
Credits
__author__ = "Shervin Azadi"
__changes_made_by__ = "Frank Vahstal"
__license__ = "MIT"
__version__ = "1.0"
__url__ = "https://github.com/frankvahstal/spatial_computing_workshops"
__summary__ = "Spatial Computing Design Studio Workshop on Polygonization"