Polygonization
In this workshop we will learn how to convert our topological model of the building into a geometric model.
0. Initialization
0.0. Importing libraries
import topogenesis as tg
import numpy as np
import pyvista as pv
import os
import trimesh as tm
import boolean_marching_cubes as bmc
0.1. Generate Symmetry Stencils
# example symmetry strings
##########################
# sym_str = [["OO"], ["XP"], ["XN"], ["YP"], ["YN"], ["ZP"], ["ZN"]]
# sym_str = [["OO"], ["XX"], ["YP"], ["YN"], ["ZP"], ["ZN"]]
# sym_str = [["OO"], ["XX"], ["YY"], ["ZP"], ["ZN"]]
# sym_str = [["OO"], ["XX", "YY"], ["ZZ"]]
# sym_str = [["OO"], ["XX", "YY", "ZZ"]]
# sym_str = [["OO"]]
sym_str = [["OO"], ["XX"], ["YY"], ["ZP"], ["ZN"]]
stencils = bmc.create_symmetry_stencils(sym_str)
0.2. Generate lattices for all possible cubes
# generate binary representation of all the possible cubes
l_bis = bmc.bi_cube_lattices()
1. Profiling
1.1. Catalogue the profile of all corners
cube =/= voxel corner profile geeft alle coordinaten (die in sym_str zijn gedefinieerd) van de hoeken van de cubes weer
# find all unique corner arrangements based on stencils
# Iterate over all the corners to find out what kind they are: isolated, edge, L, 3d L
# would be 2^11 cases with 256 cubes
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 unique arrangements of corners
uniq_corner_arang = np.unique(cp_stacked, axis=0)
print(len(uniq_corner_arang))
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
bounding box=The Cube (?) white box=origin corner we're looking at purple box=filled
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/selfdesigned_subtiles_3/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_old(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')
bmc.save_tile_meshes(tiles_meshes, l_bis, tiles_path)
3. Boolean Marching Cube
3.1. Load envelope lattice
BMC
1) Load lattice
2) run the bmc
3) load a tileset (making our own is the hard part)
4)
5)
# loading the lattice from csv
lattice_path = os.path.relpath('../data/meshes/useable_lattice.csv')
envelope_lattice = tg.lattice_from_csv(lattice_path)
envelope_lattice.shape
padded_env_arr = np.pad(envelope_lattice, 1, mode="constant", constant_values=False)
padded_minbound = envelope_lattice.minbound - envelope_lattice.unit
padded_env_lat = tg.to_lattice(padded_env_arr, minbound=padded_minbound, unit=envelope_lattice.unit)
3.2. Extract the cube lattice from the envelope lattice
cube_lattice = padded_env_lat.boolean_marching_cubes()
3.3. tile the cube lattice with a tileset
custom_tiles_path = os.path.relpath('../data/bmc/bmc_tiles')
bmc_mesh = bmc.marching_cube_mesh(cube_lattice, custom_tiles_path)
3.4. Visualize the final mesh
# 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
# initiating the plotter
p = pv.Plotter(notebook=True)
# adding the meshes
p.add_mesh(tri_to_pv(bmc_mesh), color='#abd8ff', name="sphere")
# fast visualization of the lattice
p = envelope_lattice.fast_vis(p)
# plotting
p.show(use_ipyvtk=True)
# plotting
cpos = [(314.348192231551, 283.381630231551, 296.74455709155103),
(65.08283250000001, -12.333572500000002, 21.07374465),
(0.0, 0.0, 1.0)]
p.camera_position = cpos
p.window_size = 2000, 1000
p.show(use_ipyvtk=True)
p.screenshot("Street_1")
print(p.camera_position)
3.5. Save the final mesh
# final_mesh_path = os.path.relpath('../data/meshes/final_mesh.obj')
#
# with open(final_mesh_path, 'w') as file:
# file.write(tm.exchange.obj.export_obj(bmc_mesh))
Credits
__author__ = "Shervin Azadi"
__license__ = "MIT"
__version__ = "1.0"
__url__ = "https://github.com/shervinazadi/spatial_computing_workshops"
__summary__ = "Spatial Computing Design Studio Workshop on Polygonization"