Skip to content

Atrium allocation and green path finding

More about atrium allocation and green path finding

In this notebook, we will first try to allocate the center of the building which will eventually represent the atrium of the building. Secondly we want to compute the shortest path from the three public green spaces in the nearby neighborhood of the plot. We will this with the astar algorithm. Finally we will edt the voxelized envelope with the help of the atrium and the green path by broadening both, and set every voxel above the path as unavailable.

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
import pandas as pd
from sklearn.cluster import KMeans
np.random.seed(0)
# extra import function
def lattice_from_csv(file_path):
    # read metadata
    meta_df = pd.read_csv(file_path, nrows=3)

    shape = np.array(meta_df['shape'])
    unit = np.array(meta_df['unit'])
    minbound = np.array(meta_df['minbound'])

    # read lattice
    lattice_df = pd.read_csv(file_path, skiprows=5)

    # create the buffer
    buffer = np.array(lattice_df['value']).reshape(shape)

    # create the lattice
    l = tg.to_lattice(buffer, minbound=minbound, unit=unit)

    return l

0.2. Define the Neighborhood (Stencil)

# creating neighborhood definition - center with 0 neighbours
stencil = tg.create_stencil("moore", 0, 1)
# setting the center to zero
stencil.set_index([0, 0, 0], 0)
stencil.set_index([1, 0, 0], 1)
stencil.set_index([-1, 0, 0], 1)
stencil.set_index([1, 1, 0], 1)
stencil.set_index([-1, 1, 0], 1)
stencil.set_index([1, -1, 0], 1)
stencil.set_index([-1, -1, 0], 1)
stencil.set_index([-1, 1, 0], 1)
stencil.set_index([0, 1, 0], 1)
stencil.set_index([0, -1, 0], 1)

0.3. Load the envelope lattice as the avialbility lattice

# loading the lattice from csv
lattice_path = os.path.relpath('../data/envelope_highres_new.csv')
avail_lattice = 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/program_atrium.csv')
agn_info = np.genfromtxt(prgm_path, delimiter=',')[1:, 1:]
# extract agent ids
agn_ids = agn_info[:, 0]
# extract agent preferences
agn_prefs = agn_info[:, 1:]

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)

# adding the origins to the agents locations
agn_locs = []
agn_manual_input = {
    0: [[20,10,1]],
    1: [[32,19,1]],
    2: [[1,1,2]],
    3: [[39,0,1]],
}

# retrieving the entrance access value of the free neighbours
for a_id in agn_ids:    
    voxel_vals = []
    pot_voxels = []
    # retrieve agent preferences
    a_pref = agn_prefs[int(a_id)]

    if a_id in agn_manual_input:

        # add the newly selected neighbour location to agent locations
        agn_locs.append(agn_manual_input[a_id])

        for loc in agn_manual_input[a_id]:
            # set the newly selected neighbour as UNavailable (0) in the availability lattice
            avail_lattice[tuple(loc)] = 0
            # set the newly selected neighbour as OCCUPIED by current agent 
            # (-1 means not-occupied so a_id)
            occ_lattice[tuple(loc)] = a_id
    else:
        # Voxel Evaluation Loop
        for pot_vox in avail_index:
            if avail_lattice[tuple(pot_vox)]:

                global_vox_value = 1.0
                # for every lattice in the environment informations
                for i, info_lattice in enumerate(env_info):
                    vox_val = info_lattice[tuple(pot_vox)]
                    agn_vox_val = np.power(vox_val, a_pref[i])
                    global_vox_value *= agn_vox_val
                # add the neighbour value to the list of values
                voxel_vals.append(global_vox_value)
                pot_voxels.append(pot_vox)

        # convert to numpy array
        voxel_vals = np.array(voxel_vals)
        # convert to numpy array
        pot_voxels = np.array(pot_voxels)
        # select the neighbour with highest value 
        selected_int = np.argmax(voxel_vals) 
        # find 3D intiger index of selected neighbour
        selected_neigh_3d_id = tuple(pot_voxels[selected_int].T)
        # find the location of the newly selected neighbour
        selected_neigh_loc = np.array(selected_neigh_3d_id).flatten()

        # add the newly selected neighbour location to agent locations
        agn_locs.append([selected_neigh_loc])
        # set the newly selected neighbour as UNavailable (0) in the availability lattice
        avail_lattice[selected_neigh_3d_id] = 0
        # set the newly selected neighbour as OCCUPIED by current agent 
        # (-1 means not-occupied so a_id)
        occ_lattice[selected_neigh_3d_id] = a_id
# extract the address of all occupied voxels
occ_ind = np.array(np.where(occ_lattice > -1)).T

1.4. Set the atrium agent as vertical shaft

# find the shaft location
shaft_loc = agn_locs[0]
# init shaft lattice
shft_lattice = occ_lattice * 0
# set the shafts
for sh_loc in shaft_loc:
    shft_lattice[sh_loc[0],sh_loc[1],2:] = 1

shft_voxels = np.argwhere(shft_lattice==1)

for vox in shft_voxels:
    VX, VY, VZ = tuple(vox)
    shft_lattice[VX, VY, :VZ] = 1
for vox in shft_voxels:
    VX, VY, VZ = tuple(vox)
    shft_lattice[VX, VY, VZ-1:VZ-1] = 1

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()
all_cors = []
# 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
        try:
            path = nx.algorithms.shortest_paths.astar.astar_path(g, src_ind, dst_ind)
            paths.append(path)
            path_lens.append(len(path))
        except:
            pass


    # find the shortest path
    shortest_path = paths[np.array(path_lens).argmin()]
    all_cors.append(shortest_path)
    # set the shortest path occupied in the 
    cor_flat[shortest_path] = 1

# reshape the flat lattice
cor_lattice = cor_flat.reshape(cor_lattice.shape)

3.Editing Voxels

3.1 Corridor

for path in all_cors:
    # for each voxel in the corridor ...
    for vox_1d_ind in path:
        # find the 3-dimensional index of the voxel
        vox_3d_ind = np.unravel_index(vox_1d_ind, cor_lattice.shape)
        VX, VY, VZ = vox_3d_ind
        # mark the voxel as unavailable
        #init_avail_lattice[VX, VY, VZ] = 0
        # mark two voxels horizontal the voxel avilable
        cor_lattice[VX, VY, VZ] = 1
        cor_lattice[VX:VX+3, VY:VY+3, VZ] = 1
        init_avail_lattice[VX:VX+3, VY:VY+3, VZ:] = 0        

3.2 Shaft

shft_voxels = np.argwhere(shft_lattice==1)

for vox in shft_voxels:
    VX, VY, VZ = tuple(vox)
    shft_lattice[VX-1:VX+2, VY-1:VY+2, :VZ] = 1

3.3. Visualize the corridor lattice

context_path = os.path.relpath("../data/extended_context.obj")
# load the mesh from file
context_mesh = tm.load(context_path)

p = pv.Plotter(notebook=True)

# 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

base_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 meshes
p.add_mesh(tri_to_pv(context_mesh), opacity=1.0, style='surface')


# 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")


# 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)
[(1437.5557019624885, 1436.3451154624884, 1448.4847311363303),
 (18.23971549999999, 17.02912900000001, 29.168744673841857),
 (0.0, 0.0, 1.0)]

4. Saving

# save the sun access latice to csv

csv_path = os.path.relpath('../data/atrium_3.6.csv')
shft_lattice.to_csv(csv_path)

Credits

__author__ = "Shervin Azadi and Pirouz Nourian"
__changes_made_by__: "Frank Vahstal"
__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"