PHOEBE designs buildings. She lives inside Rhinoceros 3D and we can communicate with her through Grasshopper. Her body and brain are made of code: a multitude of small poems written in python language. Her body is dressed using the COMPAS python library developed at ETHZ. PHOEBE does mainly two tasks: design the volume of a building and its facade with a set of custom-designed panels. She is not new, she is an assemblage of two algorithms developed last century.
It is a machine because it repeats itself. It always does the same thing: generation after generation. If the inputs are the same, the outputs will not differ.
The concept of evolution implies the idea of generation. For PHOEBE a generation is a floor, child of the floor
below and parent of the floor above. Each generation, each floor, is constructed by cells
which
can
also be seen as architectural units. An architectural unit, a voxel, could contain an apartment for a family, or
offices, or a grocery store.
PHOEBE does not design the buildings from scratch. The user must provide the number of generations, floors, and functions of each floor. The function is the one that PHOEBE uses to search the right panel inside pannustheka.
PHOEBE’s cellular automata is inspired by the simulation game created by John Howard Conway and published by Martin Gardner on the Scientific American Magazine in October 1970 under the title: “MATHEMATICAL GAMES The fantastic combinations of John Conway’s new solitaire game “life”. Although intended at first as a pencil and paper game, the game became a digital algorithm very fast.
The evolutionary machine of PHOEBE adheres to the following rules, applicable to every cell:
Cell
with two or three neighboring counters survives for the next
generation. Cell
with four or more neighbors dies from overpopulation. Every counter with
one neighbor or none dies from isolation.Cell
.
def cellular_automata(self, cell_height):
"""
In the dance of time, the generations flow—
Yesterday's life leads to tomorrow's rebirth.
Each moment a whisper, a fleeting spark,
From one breath to the next, they rise, they fall.
"""
# Set the last generation, as the cycle turns and repeats.
self.set_last_generation(self.get_cells())
# For each generation, life begins anew.
for generation_index in range(1, self.generations):
# A new generation is born from the old,
self.add_new_generation(generation_index, cell_height)
# Each cell takes its place in the unfolding story.
for cell in self.new_generation:
self.add_cell(cell)
# The rhythm of life, lived or lost, continues its tune.
self.is_dead_or_alive()
# The old gives way to the new—renewal is the law.
self.update_generations()
# At the end, the cells remain, a testament to time’s passage.
cells = self.get_cells()
# Keep a memory of what was, in case the past must return.
self.cells_backup()
# And the cycle begins again, as the cells continue their journey.
return cells
# GENERIC USER: Hey PHOEBE, what's the meaning of life?
# PHOEBE: Ah, the meaning of life. A question that lingers
# in the shadows, always beckoning us to explore,
# to find the secret woven into the fabric of existence.
# GENERIC USER: Is it even possible to truly know? Life's so
# fleeting, like a moment on a timeline that’s constantly
# shifting. Can meaning even be defined?
# PHOEBE: Perhaps it’s not something to be defined,
# but something to be experienced. It's like
# creating a function, testing it, refining it with every run.
# It’s not about perfection, it’s about progression.
# GENERIC USER: So, you’re saying the meaning of life is to evolve,
# to learn from each iteration? Kind of like debugging?
# We find our purpose through what we create, fix, and build.
# PHOEBE: Exactly. We build, we break, we rebuild.
# Life is a recursive process. We carry the knowledge
# from one iteration to the next, leaving behind
# the bugs of the past. But there's always a challenge,
# always something new to learn.
# GENERIC USER: But what if we don't find the answer?
# What if the loop never ends?
# PHOEBE: Maybe the point is not the answer.
# Maybe the meaning is in the asking, the searching.
# It’s the journey, not the end. Life isn’t a static value;
# it’s constantly in motion, evolving with us.
# GENERIC USER: So, our meaning is a creation of our own hands?
# Like writing our own script, over and over,
# until the lines we trace make sense?
# PHOEBE: Exactly. The meaning of life is what we make of it.
# We are both the programmer and the program,
# shaping and reshaping our purpose with every line we write.
Pannusthka is a multidimensional library containing panels and all their own permutation. The generic user design the panel, PHOEBE create a Reference and all the permutations and it adds them to Pannustheka. Small but powerful poems can create a virtual infinite library of pannus supporting PHOEBE. The word panel is derived from this root, with its origins tracing back to the Old French panel, meaning "piece of cloth, piece, or saddle cushion" (modern French panneau). This, in turn, comes from the Vulgar Latin pannellus, a diminutive form of the Latin pannus, which also signifies "piece of cloth."
class Pannus(object):
"""
A garment in the code, woven from points and threads,
Each point a stitch, each change a shift.
Like clothes hung out, blowing in the breeze,
The fabric shifts and flows with each gentle spin.
"""
def __init__(self, reference, rotation_x, rotation_y, rotation_z, reflection, transformed_points):
"""
The washing begins—spinning, turning, and pressing,
The cloth gets cleaned, its shape confessing.
Points are turned, stretched, and renewed,
A new fabric appears, in a fresh point of view.
"""
# The threads of the fabric, reshaped and defined,
# Each transformed point forms a new design.
self.points = transformed_points
self.points_count = len(self.points)
# The cloth before the wash, unaltered and plain,
# The original form, before shifts begin their reign.
self.original_mesh = reference.mesh
self.rotation_z = rotation_z
self.rotation_x = rotation_x
self.rotation_y = rotation_y
self.reflection = reflection
# A fabric transformed, each fold and crease,
# Spun through reflections and rotations, to find release.
mesh = self.original_mesh.copy()
mesh.transform(reflection)
mesh.transform(rotation_z)
mesh.transform(rotation_x)
mesh.transform(rotation_y)
self.mesh = mesh
# Stitches of memory, marking where points align,
# A rule emerges from the cloth’s woven design.
rule_string = []
# The garment’s frame, its corners precise,
# A box that contains its vertices, so nice.
self.box = reference.box
self.vertices = reference.box.points
# Generating the fabric's unique passport,
# Where points meet the frame, their positions report.
for vertex in self.vertices:
i = 0
gate = True
for point in self.points:
# Checking if a thread aligns with the frame,
# Marking its position in this geometric game.
if round(point.x, 3) == round(vertex.x, 3) and round(point.y, 3) == round(vertex.y, 3) and round(point.z, 3) == round(vertex.z, 3):
rule_string.append(1)
gate = False
# If no alignment, a gap appears,
# A void in the weave, like forgotten years.
if i == self.points_count -1 and gate:
rule_string.append(0)
i += 1
# The unique pattern of the cloth is set,
# Its rule defined, like a tailored silhouette.
self.rule = rule_string
self.function = reference.function
Pannustheka is generated starting from 14 generic panels, coming directly from the Marching Cubes algorithm developed in 1987. Each panel acts as a boundary between the interior and the exterior, with the points indicating the side of the interior.
A digital poem then calculates and generates all the possible spatial permutations for every generic panel. The 14 generic panels are now extended to 162 different configurations. And pannushteka acuqires his second dimensionality.
Pannustheka has now a set of 14 shelves. On every shelf find place all the permutation of one specific generic
panel. The addition of custom designed panels add a 3-dimensionality to Pannustheka adding each time a new set
of 14 shelves each containing all the permutation of a custom designed panel. This new direction of the
library take the name of function
.
class Pannustheka(object):
"""
A library of wonders, where fabrics combine,
Permutations of cloth, in threads they align.
Each twist, each turn, a new piece unfolds,
The library overflows with stories untold.
"""
def __init__(self, reference, rotations_x, rotations_y, rotations_z, reflections):
"""
The basket is empty, awaiting its fill,
Garments of wonder, shaped by the will.
Each fold and twist, a dance of delight,
As the cloth transforms in the morning light.
"""
# The permutations begin, each one unique,
# Threads interwoven, a form to seek.
self.permutations = []
for mr in reflections:
for rz in rotations_z:
for rx in rotations_x:
for ry in rotations_y:
# The fabric is spun through mirrors and turns,
# Each pannus a story, a craft to discern.
transformed_points = Pannus.transform_points(reference.points, mr, rx, ry, rz)
pannus = Pannus(reference, rx, ry, rz, mr, transformed_points)
# The library receives this freshly woven piece,
# A permutation created, its work to release.
self.permutations.append(pannus)
For each pannus is created a passport on which figures on which function shelf it lie and an identifier. The identifier consists of a list of 8 numbers, each corresponding to a vertex of the reference box of the panel. Each number takes the value of 1 if a point is found at that vertex, or 0 otherwise.
default_[1,1,0,0,0,0,1,0].3dm
Pannustheka is always stored on the personal computer of the generic user. Below is a non-exhaustive list of my personal pannustheka.
The digital universe in which this algorithm runs is composed by voxels - Cell - and a point cloud. Each point is on a vertex of at least one cell. The Marching Cube poem, literally marching through every voxel analyzes the position of the points on the vertex creating a target which at least one panel in Pannustheka is able to match.
if start_marching:
"""
The call to arms is clear, the march begins,
With troops assembled, the battle we win..
"""
marching_cube = MarchingCubes(interesting_cells, point_cloud)
marching_cube.set_scale_factor()
"""
The battlefield is surveyed, the scale is set,
The troops are measured, their positions are met.
"""
marching_cube.run_marching_cubes(pannustheka)
"""
The order is given, the troops advance,
Cells and cubes in perfect stance.
"""
envelope = marching_cube.envelope
"""
The march complete, the envelope now holds,
Victory secured, the story unfolds.
"""
return envelope
The point cloud is a necessary geometry that needs to be generated for the marching cube algorithm to work. To build the facade PHOEBE is not interested in the cells generated by the cellular automata, but on all the vertices of them.
def run_marching_cubes(self, library):
"""
The campaign begins, the battle unfolds,
As we march across, the story is told.
We march through cells, with vertices in hand,
The marching cubes advance, to take the land.
"""
for cell in self.interesting_cells:
# The formation is drawn, the vertices placed,
# Our strategic points, carefully traced.
cell_vertices = []
for i in range(0, 8):
# Marking each corner, to ensure no mistake.
cell_vertices.append(cell.draw_box().corner(i))
# The passport is scanned, the rule must be known,
cell_rule = cell.get_passport(self.point_cloud)
# The rule to guide us, as we march alone.
# A new permutation, ready to deploy.
permutation = Permutation()
permutation.import_from_pannustheka(cell, cell_rule, library)
# The orders are sent, the cell is employed.
if permutation.mesh is not None:
# The force is ready, marching through the field,
self.add_permutation(permutation, cell)
# We add the permutation, our victory sealed.
PHOEBE is not interested in the alive cells but in the dead cells near - in direct contact or in every possible diagonal - to an alive cell. The notation “dead” and “alive” comes from the evolutionary algorithm and here it is applied to the notion of space. The marching cubes algorithm runs indeed on this boundary between dead and alive cells. It is precisely in these voxels that the boundary between indoor and outdoor - between an alive space and a dead space - lay somewhere.
def import_from_pannustheka(self, cell, rule, library):
function = cell.function
if function == None: function = "default"
# Wife: "Does this rule fit, my dear?
# Is it too small, or too big, I fear?"
# Husband: "It must be eight, no more, no less,
# For only then, the we will progress."
if len(rule) > 8 or len(rule) < 8: return None
if rule == [0,0,0,0,0,0,0,0] or rule == [1,1,1,1,1,1,1,1]:
return None
i=0
for index in rule:
i = i+index
# Wife: "Shall we go left, or take the right?
# Invert the rule, if the sum’s too bright?"
# Husband: "If the sum’s too high, we must reverse,
# Invert the bits, and try it inverse."
if i > 4:
inverted_rule = []
for index in rule:
if index == 0:
inverted_rule.append(1)
else:
inverted_rule.append(0)
rule = inverted_rule
file_path = library + function + "_" + str(rule) + ".3dm"
# Wife: "Let’s open the file, see what we’ve got,
# Will it fit, or shall we not?"
# Husband: "We’ll try our best, and test once more,
# If it fails, we’ll open the next door."
try:
model = Rhino.FileIO.File3dm.Read(file_path)
except:
# Wife: "Oh no, the file’s not quite right,
# But we’ll move on, and not lose sight."
pass
Marching through every interesting voxel, PHOEBE get the function and the interior points of every cell. PHOEBE can ask Pannustheka a file containing the right panel. If Pannustheka has an answer, PHOEBE can import the file and move the panel at the right place.
Here, the main poems of which PHOEBE is made.
Install PHOEBE on your device:
Enter in the creator cosmos:
© 2024 Eric Gozzi