Skip to content

Polygonization

In this notebook, the voxel output from the MCDA growth is turned into a facade from several tile sets.

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

0.1. Generate Symmetry Stencils

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)

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.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 for either set
subtile_meshes = []
for c in range(len(corner_loc_lattices)):
#     corner_mesh_path = os.path.relpath('../data/bmc/bmc_subtiles_Set1/t_' + f'{c:02}' + '.obj')
    corner_mesh_path = os.path.relpath('../data/bmc/bmc_subtiles_Set1/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.4. Save the tile-set

tiles_path = os.path.relpath('../data/bmc/bmc_tiles_Set1')
bmc.save_tile_meshes(tiles_meshes, l_bis, tiles_path)

2.5 Visualize the created tile meshes (with fixed ID's)

tiles_set = []
for c in range(256):
    tiles_meshes_set_path = os.path.relpath('../data/bmc/bmc_tiles_Set1/t_' + f'{c:03}' + '.obj')
    corner_mesh_set = tm.load(tiles_meshes_set_path)
    tiles_set.append(corner_mesh_set)
# 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_set[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, 256], 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.931851869400357, 1.931851719400357, 1.931851884400357),
 (-1.5000000019860238e-08, -1.64999999996418e-07, 0.0),
 (0.0, 0.0, 1.0)]

3. Boolean Marching Cube

3.1. Load envelope lattice

# loading the lattice from csv
solar_envelope_path = ('../data/abm_final.csv')
envelope_lattice = tg.lattice_from_csv(solar_envelope_path)

#Pad to make sure all voxels are tiled
envelope_lattice_expanded = np.pad(envelope_lattice, ((1,1),(1,1),(1,1)),'constant',constant_values= 0)
envelope_lattice_expanded = tg.to_lattice(envelope_lattice_expanded, envelope_lattice.minbound-envelope_lattice.unit, envelope_lattice.unit)
envelope_lattice = envelope_lattice_expanded

3.1.1 Remove voxels that are not on the boundry

#Remove voxels that are not on the boundry

# 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 the voxel if less than 6 neighbours
new_envelope_lattice = envelope_lattice * (neighbor_sum <= 5)

3.2. Extract the cube lattice from the envelope lattice

cube_lattice = new_envelope_lattice.boolean_marching_cubes()
np.set_printoptions(threshold=100000)
# print(cube_lattice)

3.2.1 Select which voxels to tile

#Select which column of voxels will be tiled.
#Set all other voxels to 0, to isolate the tiling
cube_lattice[:,5:,:] = 0
cube_lattice[:,:4,:] = 0

3.3. Tile the cube lattice with a selected tileset

#Choosing which tileset to tile with and un-commenting it

#The first set
tileset_path = os.path.relpath('../data/bmc/bmc_tiles_Set1')
#The second set
# tileset_path = os.path.relpath('../data/bmc/bmc_tiles_Set2')

bmc_mesh = bmc.marching_cube_mesh(cube_lattice, tileset_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

pv.set_plot_theme("document")
# 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 = new_envelope_lattice.fast_vis(p)

# plotting
# p.show(use_ipyvtk=True)
# plotting
cpos = [(785.8704805788776, 708.4540755788776, 741.8613927288776),
 (65.08283250000001, -12.333572500000002, 21.07374465),
 (0.0, 0.0, 1.0)]
p.camera_position = cpos
p.window_size = 2000, 2000
p.show(use_ipyvtk=True)
[(785.8704805788776, 708.4540755788776, 741.8613927288776),
 (65.08283250000001, -12.333572500000002, 21.07374465),
 (0.0, 0.0, 1.0)]

3.5. Save the final mesh

#Export the tiled facade part to merged with other parts in a Rhino file.
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))

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"