Skip to content

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"