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
import os
import topogenesis as tg
import pyvista as pv
import trimesh as tm
import numpy as np
import networkx as nx
from sklearn.cluster import KMeans
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib import cm
import pandas as pd
np.random.seed(0)
import pickle
0.2. Define the Neighborhood (Stencil)
# creating neighborhood 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)
0.3. Load the envelope lattice as the avialbility lattice
# loading the lattice from csv
lattice_path = os.path.relpath('../data/voxelated_model_3_6.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
prgm_path = os.path.relpath('../data/Matrix10.csv')
agn_info = np.genfromtxt(prgm_path, delimiter=',')[1:, 1:]
agn_df = pd.read_csv(prgm_path)
# extract the initial location
agn_initial_loc = list(agn_df["initial_loc"])
# extract agent ids
agn_ids = list(agn_df["space_id"])
agn_df
1. Creation of Vertical Shaft
1.1. Agent initialization
# 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
# count the number of spaces (rows) and intiialize an agent for each space
agn_num = len(agn_info) # this is now based on the number of rows in our table
# Randomly choosing three available voxels
select_id = np.random.choice(len(avail_index), agn_num)
agn_origins = avail_index[select_id]
# adding the origins to the agents locations
agn_locs = []
# for each agent origin ...
for a_id, a_origin, a_init_loc in zip(agn_ids, agn_origins, agn_initial_loc):
# if a_init_loc == -1:
# final_a_origin = a_origin
# else:
final_a_origin = np.unravel_index(a_init_loc, avail_lattice.shape)
# add the origin to the list of agent locations
agn_locs.append([final_a_origin])
# set the origin in availablity lattice as 0 (UNavailable)
avail_lattice[tuple(final_a_origin)] = 0
# set the origin in occupation lattice as the agent id (a_id)
occ_lattice[tuple(final_a_origin)] = int(a_id) # this is now based on the id of the agent in the program
with open("../data/startpoint.txt", "wb") as fp: #Pickling
pickle.dump(agn_locs, fp)
np.argwhere(occ_lattice == 23)
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 avilability lattice
# init_avail_lattice.fast_vis(p)
# adding axes
p.add_axes()
p.show_bounds(grid="back", location="back", color="#aaaaaa")
#Make a dictonary for the annotations
space_list = agn_df["space_name"].to_dict()
#make a dictionary for
sargs = dict(
shadow = True,
n_labels = 0,
italic = False,
fmt ="%.0f",
font_family="arial",
height = 0.6,
vertical = True,
position_x = 1.05,
position_y = 1)
# 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
# visualize all agents
threshed = grid.threshold([-0.1, max(agn_ids) + 0.1])
# # visualize a selected agent
# a_id = 22
# threshed = grid.threshold([a_id-0.1, a_id+0.1])
# adding the voxels
p.add_mesh(threshed, name='sphere', show_edges=True, opacity=1.0, show_scalar_bar=True, annotations = space_list, scalar_bar_args=sargs, cmap="tab20b")
# 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 and set the vertical column of cluster centers as vertical shafts
# making the clusters and weights for the atrium and courtyard
cluster_1_agents = [ 0, 7, 8, 6, 20, 21]
cluster_1_weights = [7, 1, 1, 1, 1, 1]
cluster_2_agents = [ 0, 21, 9, 5, 12, 18, 19]
cluster_2_weights = [7, 1, 1, 1,1,1,1]
cluster_3_agents = [1, 15, 17, 4, 13, 10]
cluster_3_weights = [1,1,1,1,1,1]
cluster_4_agents = [1, 3, 2, 12, 15, 16]
cluster_4_weights = [8,1,1,1,1,1]
# put them in a list
cluster_agents = [cluster_1_agents, cluster_2_agents, cluster_3_agents, cluster_4_agents]
cluster_weights = [cluster_1_weights,cluster_2_weights,cluster_3_weights,cluster_4_weights]
# make centers out of the clusters and the extra weights
cluster_centers = []
for ca,cw in zip(cluster_agents,cluster_weights):
occ_ind_cluster = np.vstack([agn_locs[i]*w for i,w in zip(ca,cw)])
rough_cen_cluster = occ_ind_cluster.mean(axis=0)
cen_cluster = np.round(rough_cen_cluster).astype(np.int8)
cluster_centers.append(cen_cluster)
print(cluster_centers)
1.4. Add cluster centres so there are pairs
# init shaft lattice
shft_lattice = occ_lattice * 0
# set the shafts
# shaft_new = np.array([[5,5,1], [13,6,2], [8,9,2], [15,14,3]])
shaft_new = []
for coords in cluster_centers:
shaft_new.append([(coords[0]-1), coords[1], coords[2]])
# have all the cluster centers in 1 list
cluster_final = np.concatenate((cluster_centers, shaft_new), axis=0)
for cl_cen in cluster_final:
shft_lattice[cl_cen[0],cl_cen[1],:] = 1
#Assign maxium height for shafts, so that they are not taller than the voxelated model
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 avilability lattice
init_avail_lattice.fast_vis(p)
# adding axes
p.add_axes()
p.show_bounds(grid="back", location="back", color="#aaaaaa")
cluster_final
Function_list = {
0: "Public noisy traffic space",
1: "Public quiet traffic space",
2: "Private noisy traffic space",
3: "Private quiet traffic space",
4: "Public noisy traffic space",
5: "Public quiet traffic space",
6: "Private noisy traffic space",
7: "Private quiet traffic space",
#0 or 1: "Public noisy traffic space",
#2 or 3: "Public quiet traffic space",
#4 or 5: "Private noisy traffic space",
#6 or 7: "Private quiet traffic space",
}
#make a dictionary for
sargs = dict(
shadow = True,
n_labels = 0,
italic = False,
fmt ="%.0f",
font_family="arial",
height = 0.6,
vertical = True,
position_x = 1.05,
position_y = 1)
# 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=True, annotations = Function_list, scalar_bar_args=sargs, cmap="tab20b")
# 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 neighbours of a voxel
# take the stencil, lattice and adress of a voxel in that lattice
def find_neighbours_masked(lattice, stencil, loc):
neigh_locs = np.argwhere(stencil) - stencil.origin + loc
neigh_filter = np.all(neigh_locs > -1, axis=1) * np.all(neigh_locs < np.array(lattice.shape), axis=1)
neigh_3d = neigh_locs[neigh_filter]
# returns the index of the neigbhours of that voxel
neigh_1d = [np.ravel_multi_index(n_loc, avail_lattice.shape) for n_loc in neigh_3d]
return(neigh_1d)
find_neighbours_masked(init_avail_lattice, stencil, loc = [0,19,0])
# find the number of all voxels
vox_count = init_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(init_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, init_avail_lattice.shape)
# retrieve the list of neighbours of the voxel based on the stencil
vox_neighs = find_neighbours_masked(init_avail_lattice, 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 to the cluster centres seeds and construct the corridor
# define the clusters again
cluster_1_agents = [0, 7, 8, 6, 20, 21]
cluster_2_agents = [0, 21, 9, 5, 12, 18, 19]
cluster_3_agents = [1, 15, 17, 4, 13, 10]
cluster_4_agents = [1, 3, 2, 12, 15, 16]
cluster_agents = [cluster_1_agents, cluster_2_agents, cluster_3_agents, cluster_4_agents]
cluster_centers = []
for ca in cluster_agents:
occ_ind_cluster = np.vstack([agn_locs[i] for i in ca])
rough_cen_cluster = occ_ind_cluster.mean(axis=0)
cen_cluster = np.round(rough_cen_cluster).astype(np.int8)
cluster_centers.append(cen_cluster)
# initialize corridor lattice
cor_lattice = shft_lattice * 0
cor_flat = cor_lattice.flatten()
paths = []
# for each voxel that needs to have access to shafts
for i, ccen in enumerate(cluster_centers):
ca = cluster_agents[i]
occ_ind_cluster = np.vstack([agn_locs[i] for i in ca])
for a_vox in occ_ind_cluster:
# slice the corridor lattice horizontally
cor_floor = shft_lattice[:,:,a_vox[2]]
# find the vertical shaft voxel indices
# construct the destination address
dst_vox = np.array([ccen[0],ccen[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)
try:
# find the shortest path
path = nx.algorithms.shortest_paths.astar.astar_path(g, src_ind, dst_ind)
paths.append(path)
# set the shortest path occupied in the
cor_flat[path] = 1
except:
print("unreachable",src_ind, a_vox , init_avail_lattice[tuple(a_vox)],dst_ind, dst_vox, init_avail_lattice[tuple(dst_vox)])
# reshape the flat lattice
cor_lattice = cor_flat.reshape(cor_lattice.shape)
list(nx.neighbors(g, 387 ))
# g.nodes()
# iterates over the list of paths
path_3d = []
for p in paths:
p_3d = [np.unravel_index(p[0], cor_lattice.shape)]
# iterates over the list of voxels that are included in each path
for v in p:
# convert 1 dimensional index of that voxel to a 3 dimensional index
v_3d = np.unravel_index(v, cor_lattice.shape)
print(np.array(v_3d) - np.array(p_3d[-1]), v_3d, v)
p_3d.append(v_3d)
# append the 3dimensional indices in a list
path_3d.append(p_3d)
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 avilability lattice
# init_avail_lattice.fast_vis(p)
# adding axes
p.add_axes()
p.show_bounds(grid="back", location="back", color="#aaaaaa")
#Make a dictonary for the annotations
# 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=True, annotations = Function_list, scalar_bar_args=sargs, cmap="tab20b")
# 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
csv_path = os.path.relpath('../data/shaftandcorridors.csv')
base_lattice.to_csv(csv_path)
__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"
Last update: January 25, 2021