Generative Relations: Corridor Generation
In this workshop, we will learn about creation of vertical shafts, path-finding between agents, and constructition of coridor system within the building.
0. Initialization
0.1. Load required libraries
#!pip install scikit-learn
import os
import topogenesis as tg
import pyvista as pv
import numpy as np
import networkx as nx
import pandas as pd
import functions
from sklearn.cluster import KMeans
np.random.seed(0)
0.2. Define the Neighborhood (Stencil)
# creating neighbourhood definition
stencil = tg.create_stencil("von_neumann", 1, 1)
# setting the center to zero
stencil.set_index([0,0,0], 0)
stencil.set_index([0,0,1], 0)
stencil.set_index([0,0,-1], 0)
print(stencil)
0.3. Load the envelope lattice as the avialbility lattice
# loading the lattice from csv
lattice_path = os.path.relpath('../data/meshes/useable_lattice.csv')
avail_lattice = tg.lattice_from_csv(lattice_path)
init_avail_lattice = tg.to_lattice(np.copy(avail_lattice), avail_lattice)
0.4. Load Agents Information
# loading program (agents information) from CSV
relative_prefs = pd.read_excel('../data/relationships/relative_preferences.xlsx')
functions.normalize_excel(relative_prefs)
program_prefs = relative_prefs.drop(["space_name", "street_sight"], 1)
program_prefs
1. Creation of Vertical Shaft
1.1. Agent initialization
# loading the lattice from csv
fields = {}
for f in program_prefs.columns:
lattice_path = os.path.relpath('../data/fields/' + f + '.csv')
fields[f] = tg.lattice_from_csv(lattice_path) * avail_lattice
agent_pref_fields=[]
for i in range(0,len(program_prefs)):
int_d=1
for f in program_prefs:
t=fields[f]**program_prefs[f][i]
int_d=int_d*t
agent_pref_fields.append(int_d)
# initialize the occupation lattice
occ_lattice = avail_lattice * 0 - 1
# Finding the index of the available voxels in avail_lattice
avail_flat = avail_lattice.flatten()
avail_index = np.array(np.where(avail_lattice == 1)).T
# Randomly choosing three available voxels
# agn_num = len(program_complete) # this is now based on the number of rows in our table
# select_id = np.random.choice(len(avail_index), agn_num)
agn_num = len(program_prefs)
agn_origins=[]
for i in range(0,agn_num):
best_index = np.array(np.where(agent_pref_fields[i] == agent_pref_fields[i].max())).T
best_index=best_index[0]
agn_origins.append(best_index)
agn_origins=np.stack(agn_origins)
#program_prefs[["OX", "OY", "OZ"]] = agn_origins
# adding the origins to the agents locations
agn_locs = []
# for each agent origin ...
for a_id, a_origin in enumerate(agn_origins):
#a_origin = a_info[["OX", "OY", "OZ"]].to_list()
#print(a_origin)
# add the origin to the list of agent locations
agn_locs.append(a_origin)
# set the origin in availability lattice as 0 (UNavailable)
avail_lattice[tuple(a_origin)] = 0
# set the origin in occupation lattice as the agent id (a_id)
occ_lattice[tuple(a_origin)] = a_id
1.2. Visualizing the agents seeds
p = pv.Plotter(notebook=True)
base_lattice = occ_lattice
# 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 the availability lattice
init_avail_lattice.fast_vis(p)
# adding axes
p.add_axes()
p.show_bounds(grid="back", location="back", color="#aaaaaa")
# Add the data values to the cell data
grid.cell_arrays["Agents"] = base_lattice.flatten(order="F").astype(int) # Flatten the array!
# filtering the voxels
threshed = grid.threshold([-0.1, agn_num - 0.9])
# adding the voxels
p.add_mesh(threshed, name='sphere', show_edges=True, opacity=1.0, show_scalar_bar=False)
# p.add_slider_widget(create_mesh, [0, n_frames], title='Time', value=0, event_type="always", style="classic", pointa=(0.1, 0.1), pointb=(0.9, 0.1))
p.show(use_ipyvtk=True)
1.3. Cluster the existing voxels
# extract the address of all occupied voxels
occ_ind = np.array(np.where(occ_lattice > -1)).T
# construct kmeans model and fit it to find the clustering
kmeans_model = KMeans(n_clusters=3, random_state=0).fit(occ_ind)
1.4. Set the vertical column of cluster centers as vertical shafts
# extract cluster centers
cluster_centers = np.round(kmeans_model.cluster_centers_).astype(np.int8)
# init shaft lattice
shft_lattice = occ_lattice * 0
# set the shafts
for cl_cen in cluster_centers:
shft_lattice[cl_cen[0], cl_cen[1],:] = 1
shft_lattice = shft_lattice * avail_lattice
1.5. Visualize Vertical Shafts
p = pv.Plotter(notebook=True)
base_lattice = shft_lattice
# 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 the availability lattice
init_avail_lattice.fast_vis(p)
# adding axes
p.add_axes()
p.show_bounds(grid="back", location="back", color="#aaaaaa")
# Add the data values to the cell data
grid.cell_arrays["Agents"] = base_lattice.flatten(order="F").astype(int) # Flatten the array!
# filtering the voxels
threshed = grid.threshold([0.9, 1.1])
# adding the voxels
p.add_mesh(threshed, name='sphere', show_edges=True, opacity=1.0, show_scalar_bar=False)
# p.add_slider_widget(create_mesh, [0, n_frames], title='Time', value=0, event_type="always", style="classic", pointa=(0.1, 0.1), pointb=(0.9, 0.1))
p.show(use_ipyvtk=True)
2. Creation of Horizontal Corridors
2.1. Extract the connectivity graph from the lattice based on the horizontal stencil
# find the number of all voxels
vox_count = avail_lattice.size
# initialize the adjacency matrix
adj_mtrx = np.zeros((vox_count,vox_count))
# Finding the index of the available voxels in avail_lattice
avail_index = np.array(np.where(avail_lattice == 1)).T
# fill the adjacency matrix using the list of all neighbours
for vox_loc in avail_index:
# find the 1D id
vox_id = np.ravel_multi_index(vox_loc, avail_lattice.shape)
# retrieve the list of neighbours of the voxel based on the stencil
vox_neighs = avail_lattice.find_neighbours_masked(stencil, loc = vox_loc)
# iterating over the neighbours
for neigh in vox_neighs:
# setting the entry to one
adj_mtrx[vox_id, neigh] = 1.0
# construct the graph
g = nx.from_numpy_array(adj_mtrx)
2.2. Find the shortest path and construct the corridor
# initialize corridor lattice
cor_lattice = shft_lattice * 0
cor_flat = cor_lattice.flatten()
# for each voxel that needs to have access to shafts
for a_vox in occ_ind:
# slice the corridor lattice horizontally
cor_floor = shft_lattice[:,:,a_vox[2]]
# find the vertical shaft voxel indices
shaft_vox_inds = np.array(np.where(cor_floor > 0)).T
paths = []
path_lens = []
for shft_ind in shaft_vox_inds:
# construct the destination address
dst_vox = np.array([shft_ind[0],shft_ind[1],a_vox[2]])
# construct 1-dimensional indices
src_ind = np.ravel_multi_index(a_vox, shft_lattice.shape)
dst_ind = np.ravel_multi_index(dst_vox, shft_lattice.shape)
# find the shortest path
path = nx.algorithms.shortest_paths.astar.astar_path(g, src_ind, dst_ind)
paths.append(path)
path_lens.append(len(path))
if(len(path_lens) == 0):
continue
# find the shortest path
shortest_path = paths[np.array(path_lens).argmin()]
# set the shortest path occupied in the
cor_flat[shortest_path] = 1
# reshape the flat lattice
cor_lattice = cor_flat.reshape(cor_lattice.shape)
2.3. Visualize the accessability lattice
p = pv.Plotter(notebook=True)
base_lattice = shft_lattice + cor_lattice
# 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 the availability lattice
init_avail_lattice.fast_vis(p)
# adding axes
p.add_axes()
p.show_bounds(grid="back", location="back", color="#aaaaaa")
# Add the data values to the cell data
grid.cell_arrays["Agents"] = base_lattice.flatten(order="F").astype(int) # Flatten the array!
# filtering the voxels
threshed = grid.threshold([0.9, 2.1])
# adding the voxels
p.add_mesh(threshed, name='sphere', show_edges=True, opacity=1.0, show_scalar_bar=False)
# p.add_slider_widget(create_mesh, [0, n_frames], title='Time', value=0, event_type="always", style="classic", pointa=(0.1, 0.1), pointb=(0.9, 0.1))
p.show(use_ipyvtk=True)
Credits
__author__ = "Shervin Azadi and Pirouz Nourian"
__license__ = "MIT"
__version__ = "1.0"
__url__ = "https://github.com/shervinazadi/spatial_computing_workshops"
__summary__ = "Spatial Computing Design Studio Workshop on Path Finding and Corridorfor Generative Spatial Relations"