Skip to content

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 resources.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))
24

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)
<vtkmodules.vtkInteractionWidgets.vtkSliderWidget(0x0000023C4E1E1CC0) at 0x0000023C4E83E580>

1.5. Save unique arrangement profiles

# save all design templates into lattice CSVs
templates_path = os.path.relpath('../data/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/subtiles_structure/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)
[(1.9318516525781368, 1.9318516525781368, 1.9318516525781368),
 (0.0, 0.0, 0.0),
 (0.0, 0.0, 1.0)]

2.4. Save the tile-set

tiles_path = os.path.relpath('../data/bmc/tiles_structure')
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/dynamic output/abm_animation/abm_f_1500.csv')
envelope_lattice = tg.lattice_from_csv(lattice_path)
envelope_lattice = envelope_lattice != -1
envelope_lattice.shape
(43, 40, 11)
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()
# cube_lattice *= 0
cube_lattice[0,0,0] = 1

3.3. tile the cube lattice with a tileset

custom_tiles_path = os.path.relpath('../data/bmc/tiles_structure')
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)
[(275.910778145043, 194.91077814504303, 257.91077778741516),
 (36.0, -45.0, 17.99999964237213),
 (0.0, 0.0, 1.0)]

3.5. Save the final mesh

final_mesh_path = os.path.relpath('../data/final_mesh_structure.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"