ReciprocalGen

Virginia
Design Intelligence Course
8 min readDec 18, 2023

Python-Enhanced Reciprocal Structure Tool for Grasshopper

Python-Enhanced Reciprocal Structure Tool for Grasshopper - Recursive function for tessellation

Overview

ReciprocalGen is a tool designed to facilitate structural design development based on reciprocal structures, adaptable to any modeled geometry in Rhino and optimised by using Grasshopper Plug-in Kangaroo.

Introduction

This tool is based on the principle to use reclaimed modular timber elements and modular design to reduce environmental impact and promote circular building practices.

ReciprocalGen is developed as a Python script within the Grasshopper environment, this tool simplifies the complex process of designing reciprocal patterns, offering a user-friendly interface and integration with Rhino’s 3D modeling capabilities and Rhinoscypt library.

Sketches of grillage assemblies by Leonardo da Vinci. (Sketches by A. E. Piroozfar.)

Reciprocal structure

A reciprocal frame is based on a reciprocal structure in engineering and architecture, and refers to a spatial structure where each element supports and is supported by another, creating a self-stabilising system. This design concept is characterised by a repetitive, interlocking arrangement of beams or other structural elements, where each piece rests on the next, forming a continuous loop or network.

A possible definition of a reciprocal frame is a grid of linear members where each member simultaneously supports and is supported by its neighboring members. Therefore, the members are structurally interdependent and in a structural hierarchy of equal importance. Although the basic RF configurations are typically used to enclose a regular rectangular/polygonal plan form, current built cases have also presented reciprocal frames with irregular shapes. Up-to-date digital design tools enable a broad spectrum of opportunities for RFs, facilitating the design and fabrication of complex RF geometries and building typologies for diverse purposes. Source: https://www.structuremag.org/?p=20653

These structures are often used for their aesthetic appeal and structural efficiency. They can span large spaces without the need for internal supports or columns, creating open, unobstructed interiors. Reciprocal frames are found in various applications, from small-scale installations to large architectural projects, and are valued for their combination of strength, simplicity, and beauty.

Image Credit: https://sutd-cgl.github.io/supp/Publication/papers/2013-SIGGRAPH-RFStructure.pdf

Features of the Design Tool

  1. Input — Base Geometry with Structural Logic Application
  • Incorporation of any geometric shapes as the foundation of design.
  • Applying structural logic to these geometries to create stable forms.
  • A parametric toolkit for flexible design adjustments.

2. Rationalizer of Structure

  • Our system is developed to manage reciprocal frames, known for their interlocking and self-supporting composition.

3. Modeling with Lines for Kangaroo Testing

  • Simplified modeling capabilities, sufficient for testing in Kangaroo Physics (a physics engine plugin for Rhino/Grasshopper).
  • Functionality to allow the tool to analyze the structure and facilitate testing in Kangaroo.
  • Testing the structure against various forces to assess stability and integrity.
  • Utilizing analytical tools to predict performance under different load conditions.
Features of the ReciprocalGen Design Tool
Process Sketch of ReciprocalGen

Process for ReciprocalGen Development

1. Define the Unit

  • Size Specification: We initiate by determining the dimensions of each infill unit in Rhino. This preliminary sizing is flexible, allowing for subsequent adjustments. (using Kangaroo, a dynamic simulation tool)
  • Structural Composition: The next step involves deciding the quantity of structural members within each unit.

2. Grammar Rules for Recursive Algorithm

  • Recursive Algorithm Development: The cornerstone of our process is the creation of a recursive algorithm. This algorithm is responsible for defining how the units connect and arrange themselves in relation to one another.
  • Automation through Grammar Rules: To streamline the structure generation process, we integrate grammar rules into our algorithm. These rules function as the blueprint, guiding the automated assembly of the structure.
  • Connection Mapping: Each unit is designed with specific connection points — inputs and outputs. These are meticulously defined to ensure seamless assembly. The units are then strategically arranged to populate a grid, the dimensions of which are predefined in terms of width and height.
Code Structure of ReciprocalGen
ReciprocalGen Design Tool in Rhino — Grasshopper

Code Development

💡
Defining Inputs and Outputs

Inputs: x: The x script variable = Shapes y: The y script variable = Slots
Output: a: The a output variable = reciprocal pattern

2. Classes and Their Relationships
class Slots is based on input line-geometry which defines the begin + end point and direction </aside>
Definition of the Slot class

# Store the line associated with the slot
def __init__(self, line):
"""
The Slot class contains:
.line object assosciated with the slot,
.direction: calculates the direction of the line at its start, and
.connected: indicates whether the slot is connected to another slot
"""
self.line = line
self.direction = line.TangentAt(0)
self.connected = False

# Return True if the slot is not connected
def is_empty(self):
return not self.connected

# Mark the slot as connected
def remove_slot(self):
self.connected = True
  • class Shapes contains input shape-geometry and contains Slot objects
Definition of the Shape class

# Store the members (components) of the shape
def __init__(self, members, lines):
self.members = members

# Create Slot objects for each line provided
self.slots = [Slot(l) for l in lines]
self.bounding_box = self.calculate_global_bounding_box()

def calculate_global_bounding_box(self):
"""
Calculate the global bounding box that encompasses all members of the shape.
"""
bbox = rg.BoundingBox()
for member in self.members:
bbox.Union(member.GetBoundingBox(True))
return bbox

# Return a list of slots that are not connected
def available_slots(self):
return [s for s in self.slots if s.is_empty()]
Function to map slots to members of a shape
def map_slots_to_members(shape):

# Dictionary to hold pairs of slots
slot_pairs = {}
for member in shape.members:
associated_slots = []
for slot in shape.slots:
if does_slot_intersect_member(slot, member):
associated_slots.append(slot)
if len(associated_slots) == 2:
# Assuming each member has exactly two associated slots
slot_pairs[associated_slots[0]] = associated_slots[1]
slot_pairs[associated_slots[1]] = associated_slots[0]
return slot_pairs
Function to check if a slot intersects with a member

def does_slot_intersect_member(slot, member):
intersection_events = rg.Intersect.Intersection.CurveCurve(slot.line, member, 0.01, 0.01)
# Return True if there is an intersection
return len(intersection_events) > 0
Function to clone a shape and transform it to a new position

def clone_and_transform_shape(base_shape, target_slot, target_point, slot_pairs):

#Clone the base shape and transform it to attach at the target slot.
#Position it at the starting point of the target slot line, oriented according to the line.

# Clone the members and slots of the base shape
cloned_members = [member.Duplicate() for member in base_shape.members]
cloned_slots = [Slot(slot.line.Duplicate()) for slot in base_shape.slots]

# Create the new shape
new_shape = Shape(cloned_members, [slot.line for slot in cloned_slots])

base_paired_slot = get_slot_pair(target_slot, slot_pairs)
# Calculate the translation to move the base paired slot to the target slot
move_vector = target_point - base_paired_slot.line.PointAtStart
move_to_target = rg.Transform.Translation(move_vector)

# Calculate the rotation
## ... (rotation logic as before, using the target_slot and base_paired_slot for alignment)

# Apply the transformations to each member
for member in new_shape.members:
member.Transform(move_to_target)
# Apply rotation if necessary

# Apply the transformations to each slot line
for slot in new_shape.slots:
slot.line.Transform(move_to_target)
# Apply rotation if necessary

return new_shape
Function to get the slot paired with a given slot

def get_slot_pair(slot, slot_pairs):
"""
Returns the paired slot for the given slot.
"""
return slot_pairs.get(slot)
Recursive function for tessellation

def tessellate_recursive(current_shape, placed_shapes, base_shape, counter):
"""
Recursively tessellate by attaching new shapes at the available slots of the current shape.
"""
if counter <= 0:
return(placed_shapes) # Stop recursion when the counter reaches zero

new_shapes = [] # To store all new shapes created from the current shape
used_directions = set() # Track used slot directions in the current shape

# Generate slot pairs for the current shape
current_slot_pairs = map_slots_to_members(current_shape)

for slot in current_shape.available_slots():
# Skip if the slot direction has been used already
if slot.direction in used_directions:
continue

# Determine the target point for the new shape
target_point = slot.line.PointAtStart # Assuming we attach at the start of the slot line

# Clone and transform the shape to the target point
new_shape = clone_and_transform_shape(current_shape, slot, target_point, current_slot_pairs)

# Add the new shape to the list of new shapes if it's not overlapping
## Check if the new shape fits within the boundary
if is_shape_within_boundary(new_shape.bounding_box, boundary_curve) and new_shape not in placed_shapes:
placed_shapes.add(new_shape)
new_shapes.append(new_shape)

# Add the new shape to the list of new shapes if it's not overlapping
if new_shape not in placed_shapes:
placed_shapes.add(new_shape)
new_shapes.append(new_shape)

# Mark the slot as used in the current shape and track the direction
slot.remove_slot()
used_directions.add(slot.direction)

for shape in new_shapes:
tessellate_recursive(shape, placed_shapes, base_shape, counter - 1)
# Creating the grid boundary

def create_boundary():
# Define the corner points of the rectangle
size = 25 # example boundary size
corner1 = rg.Point3d(-size, -size, 0)
corner2 = rg.Point3d(size, size, 0)

# Create a rectangle using the corner points
rectangle = rg.Rectangle3d(rg.Plane.WorldXY, corner1, corner2)
return rectangle.ToNurbsCurve()

def is_shape_within_boundary(bounding_box, boundary_curve):
"""
Check if the bounding box of a shape is entirely within a rectangular boundary.
"""
# Get the corners of the boundary rectangle
boundary_corners = get_rectangle_corners(boundary_curve)
min_x = min(corner.X for corner in boundary_corners)
max_x = max(corner.X for corner in boundary_corners)
min_y = min(corner.Y for corner in boundary_corners)
max_y = max(corner.Y for corner in boundary_corners)

# Check each corner of the bounding box
for i in range(4):
corner_point = bounding_box.Corner(i, i % 2, 0) # Get the 4 corners on the XY plane
if not (min_x <= corner_point.X <= max_x and min_y <= corner_point.Y <= max_y):
return False

return True

def get_rectangle_corners(rectangle_curve):
"""
Extract corner points from a rectangle curve.
"""
if rectangle_curve.IsClosed and rectangle_curve.IsPlanar():
polyline = rectangle_curve.TryGetPolyline()[1]
return list(polyline)
return None
# Creating the base shape and grid

# Define the boundary (e.g., a 100x100 rectangle)
boundary_curve = create_boundary()
base_shape = Shape(shape, lines)
placed_shapes = set([base_shape]) #Initializing a set to store placed shapes
output = []
tessellate_recursive(base_shape, placed_shapes, base_shape, 6) # Begin the tessellation process
# Flattening the list of members from all shapes in the output

for shape in placed_shapes:
output.append(shape.members)
output = [item for sublist in output for item in sublist]
output.append(boundary_curve)
Map Grid to Surface

Next Steps : Map to Surface + Optimization

  1. Map Grid to Surface
  • Mapping the generated structural grid to various surfaces of a base geometry, allowing for diverse applications.

2. Optimization with Kangaroo

  • Utilizing Kangaroo for optimization, ensuring structural stability and efficiency.
Tesselation of a triangular Input — shape

Additional Considerations

  • User Interface: Developing a user-friendly interface that allows architects and designers to easily input parameters, visualize structures, and receive feedback.
  • Integration with Existing Tools: Ensuring compatibility with popular architectural software like Rhino and Grasshopper.
  • Documentation and Tutorials: Providing clear instructions and examples to help users understand and effectively use the tool.
  • Feedback Loop: Implementing a system where the tool provides immediate feedback on structural viability as changes are made.

This design tool was developed by Kevin Liberman and Virginia Zangs, within the Design Intelligence Course taught by Danil Nagy at GSAPP Columbia.

Plug-In Kangaroo-Physics

https://www.food4rhino.com/en/app/kangaroo-physics

References

https://discourse.mcneel.com/t/basic-l-system-pattern-modification-anemone/69399

https://www.grasshopper3d.com/group/kangaroo/forum/topics/reciprocal-structures-example-definition

https://parametrichouse.com/architecture-design-2/

https://www.tsg.ne.jp/TT/cg/FreeformTensegrityTachiAAG2012.pdf

https://casaeco.files.wordpress.com/2012/03/reciprocal-frame-architecture.pdf

https://www.northernarchitecture.us/structural-behaviour/background-the.html

--

--