Lesson 9.3Lesson 9.3 · Generative & Parametric Scripting
Algorithms for Form
Grids and tilings, attractors, cellular automata and packing - the generative approaches that turn rules into architecture
A grid, an attractor, a growth rule, a packing - four small ideas that generate an astonishing range of architectural form.
Once you can vary a pattern with randomness and grow one with recursion, the natural next question is: what other rules turn into form? The answer is a compact, powerful toolbox that recurs across every parametric project - and each tool is a rule you can write in a few lines.
This lesson tours four of the most useful: grids and tilings (the scaffold most generative work starts from), attractors (points of influence that reshape a field), cellular automata (patterns that grow from neighbour rules), and packing (filling space without overlap). Together they are the vocabulary of the Computational Design course, in plain Python.
Four engines: grid, attractor, cellular automaton, packing. Design the rule.
Grids and tilings - the scaffold of generative work
Almost every generative project starts from a grid: a regular arrangement of points or cells that gives you somewhere to put things. In Python a grid is just a nested loop or a list comprehension over rows and columns:
cols, rows, spacing = 5, 4, 3.0
points = [(c * spacing, r * spacing)
for r in range(rows) for c in range(cols)]
print(len(points), points[:3]) # 20 pointsA plain grid is only the beginning. A tiling (or tessellation) fills a surface with repeated shapes - squares, hexagons, triangles - and hexagons in particular show up constantly in facades and screens because they pack efficiently and read as organic. You get a hexagonal layout by offsetting every other row:
import math
size = 1.0
cells = []
for r in range(4):
for c in range(5):
x = c * size * 1.5
y = r * size * math.sqrt(3) + (c % 2) * size * math.sqrt(3) / 2
cells.append((round(x, 2), round(y, 2)))
print(cells[:4])The grid is rarely the finished design - it is the scaffold you then perturb, populate or deform. Every technique that follows takes a grid as its starting field and changes it according to a rule. Get comfortable generating grids and tilings and you have the substrate for everything else.
It helps to notice how much design already lives on grids you did not think of as generated: a curtain-wall mullion layout, a ceiling tile module, a paving pattern, a structural column bay. Writing them as code rather than drawing them by hand buys you two things immediately. First, they become parametric - change the spacing or count in one place and the whole field updates, so exploring 'what if the bay were 300 mm wider' is instant instead of an afternoon of redrawing. Second, they become addressable - because each cell has an index (c, r), you can ask questions of the grid ('every third column', 'the cells within two metres of the corner', 'alternate rows') and act on them selectively. That indexability is what turns a dead grid into a design surface: it is the hook every later rule grabs onto.
Grid = the scaffold. Everything else perturbs, populates or deforms it.
Attractors - a point of influence reshapes the field
An attractor is one of the most productive ideas in parametric design: a point (or curve, or set of points) whose influence varies across a field, so that every element responds to its distance from the attractor. Panels grow larger near it, apertures open wider, fins rotate more - all driven by one moving point. The mechanism is always the same: for each element, measure the distance to the attractor, and map that distance to a property.
import math
def attractor_scale(px, py, ax, ay, strength=6.0):
dist = math.hypot(ax - px, ay - py)
return strength / (dist + 1.0) # bigger when closer
grid = [(c, r) for r in range(5) for c in range(5)]
sizes = [round(attractor_scale(x, y, 2, 2), 2) for (x, y) in grid]
print(max(sizes), min(sizes))Each cell's size now depends on how close it sits to the attractor at (2, 2) - a smooth gradient of influence across the whole grid. The magic for a designer is that the attractor is a handle: move it, and the entire pattern reflows continuously. That is parametric design in miniature - one intuitive control driving hundreds of coordinated elements. Attractors are a signature move in Grasshopper (Module 6), where you drag a point and watch a facade breathe; the maths underneath is exactly this distance-to-property mapping.
The idea generalises well beyond a single point. The attractor can be a curve (map each element to its distance from a path - a circulation spine, a site boundary), several points at once (take the nearest, or sum their influences), or even a repelling force that pushes elements away rather than pulling them in. The falloff - how influence fades with distance - is itself a design choice: a sharp inverse-square falloff makes a tight, dramatic focus, while a gentle linear one spreads the effect broadly. Tuning the falloff curve is often where an attractor design goes from crude to elegant, and it is worth experimenting with deliberately rather than accepting the first formula you write.
Attractor = a handle. Measure distance, map to a property. Move it, all reflows.
Cellular automata - form that grows from neighbour rules
A cellular automaton is a grid of cells that update together according to a rule about each cell's neighbours. From a rule you could write on a napkin, startlingly organic patterns emerge. The simplest is a one-dimensional automaton, where each new cell depends on the three cells above it, and an 8-bit 'rule number' encodes the outcome for all eight possible neighbourhoods:
def step(row, rule):
n = len(row)
out = []
for i in range(n):
left, mid, right = row[(i-1) % n], row[i], row[(i+1) % n]
pattern = (left << 2) | (mid << 1) | right
out.append((rule >> pattern) & 1)
return out
row = [0, 0, 0, 1, 0, 0, 0]
for _ in range(3):
print(row)
row = step(row, 90)Rule 90 grows a Sierpinski triangle; rule 30 produces something that looks random. The two-dimensional cousin is Conway's Game of Life, whose rules about live neighbours generate gliders and oscillators. For designers, cellular automata are a way to grow porosity, shading patterns and screen densities that feel alive - each cell 'decides' whether to be solid or void based on its neighbourhood. You design the rule, not the pattern, and the pattern emerges from it - the defining move of generative design.
The word to sit with is emergence: complex, coherent, unplanned global behaviour arising from simple local rules that mention nothing about the big picture. No cell knows it is part of a triangle or a glider; each only looks at its neighbours and applies the rule, yet structure appears at the scale of the whole grid. This is genuinely the same principle behind a flock of birds, a termite mound, or a city's street pattern - and it is why generative design can feel almost alive. For a designer it is liberating and humbling at once: you cannot dictate the pattern directly, only shape the rule and the starting state and then discover what they produce. That is exactly why the seed and the rule number matter so much - they are your two handles on an outcome you do not draw but grow.
CA: cells update from neighbours. You design the rule; the pattern emerges.
Packing - filling space without overlap
The last staple is packing: arranging elements to fill a space without overlapping. Circle packing, Voronoi cells and box packing all answer the same design question - how do I distribute many objects efficiently across an area? The simplest honest algorithm is dart-throwing: propose a random position, accept it only if it does not collide with what is already placed, and repeat until you have enough or you give up.
import random, math
random.seed(3)
radius, placed, tries = 0.6, [], 0
while len(placed) < 8 and tries < 1000:
p = (random.uniform(0, 10), random.uniform(0, 10))
if all(math.hypot(p[0]-q[0], p[1]-q[1]) > 2 * radius for q in placed):
placed.append(p)
tries += 1
print(len(placed), "circles placed")Notice the honest limit: it might not fit all eight, so we cap the attempts - real generative code accepts that some goals are not fully reachable and stops gracefully. More sophisticated packings (relaxation, Voronoi, physics-based) produce denser, more even results, and Grasshopper plugins like Kangaroo do this with simulated forces. But the concept is the one above: propose, test against a constraint, keep or reject. That is a thread running through this whole module - and it is exactly the generate-and-test structure that the next lesson turns into optimization. Voronoi deserves a special mention, since it is the flip side of packing: given a set of points, it carves space into the region closest to each point, producing the cell patterns you see in leaf veins, cracked mud and a great deal of contemporary facade and structure work.
Packing: propose, test the constraint, keep or reject. Cap the attempts - accept it may not all fit.
Combining engines - and keeping judgement in charge
The real power of this toolbox appears when you combine the engines, because they are all just rules acting on a field. A single facade might start from a hexagonal tiling, have each cell's aperture scaled by an attractor near the entrance, and then have a cellular automaton decide which cells are solid for privacy - three rules layered into one coherent, parametric system. Because each layer is driven by a few parameters, the whole composition stays adjustable: move the attractor, change the automaton rule, retune the tiling, and the design regenerates as a family rather than a fixed drawing. This composability is why these particular techniques recur across the field - they stack cleanly.
Choosing which engine fits is a matter of matching the rule to the intent. Want a smooth gradient of a property across a surface - openings, depth, rotation? That is an attractor. Want an all-over pattern with an organic, grown quality from a local rule? That is a cellular automaton or an L-system. Want to distribute many discrete elements efficiently in a space? That is packing or Voronoi. Want a regular substrate to build any of the above on? Start with a grid or tiling. Naming the intent usually points straight at the tool.
And here is the honest caution that must accompany all of it: emergent does not mean good. These algorithms will happily generate thousands of striking patterns, most of which are not right for the building - too busy, structurally naive, or blind to program, orientation and cost. The techniques expand the space of forms you can explore and the speed of exploring it; they do not tell you which form to choose. That judgement, and the discipline of testing a generated form against real constraints, is what the final lesson formalises by turning the generate-and-test habit into optimization - and it is what keeps you, not the algorithm, the author of the design.
Stack the engines: tiling + attractor + automaton. Emergent is not the same as good.
grid / tiling
A regular field of points or cells to build on
A nested loop or comprehension. The scaffold nearly every generative technique starts from; hexagonal tilings are a facade favourite.
attractor
A point of influence that varies a property by distance
Measure distance, map it to size/rotation/spacing. A single intuitive handle driving hundreds of elements; a Grasshopper signature move.
cellular automaton
Cells that update from a neighbour rule
Rule 90, rule 30, Conway's Life. You design the rule; complex, organic patterns emerge. Great for porosity and shading screens.
packing
Filling space with non-overlapping elements
Dart-throwing, Voronoi, relaxation (Kangaroo). Propose-test-keep; accept that not everything always fits and cap the attempts.
Workshop - an attractor-driven facade grid
Build a grid of panels whose sizes respond to a movable attractor point, then move the attractor and watch the whole pattern reflow. This is the single most transferable generative pattern - a grid plus a distance-to-property mapping.
Python 3 (math is built in). Optional: matplotlib to visualise the grid of sized panels.
Goal: a parametric facade grid controlled by one attractor Inputs: Python 3, the math module (matplotlib optional to view) Time: ~40 minutes
- 1Generate a grid of (x, y) points, say 8 by 8 at unit spacing, with a list comprehension. Print how many points you have.
- 2Write
attractor_scale(px, py, ax, ay)that returns a size based on the inverse distance to the attractor (bigger when closer), as in the lesson. Compute a size for every grid point. - 3Move the attractor: run the whole thing with the attractor at (0, 0), then at (4, 4), and confirm the map of sizes changes - the field reflows around the new point.
- 4Add a second attractor and combine their influence (for instance, take the larger of the two scales at each point). Observe how two handles interact.
- 5Bonus: plot the grid with matplotlib, drawing each point as a circle whose radius is its computed size, so you actually see the facade breathe around the attractor.
You’ll walk away with
A script that prints (or plots) an 8x8 grid of panel sizes driven by an attractor, run for at least two attractor positions, plus a sentence on how adding a second attractor changed the field.
Three altitudes on the same idea
Read the band that fits you — or all three.
These four algorithms are the working vocabulary of parametric practice. Attractor-driven facades, cellular-automata shading screens, Voronoi structural patterns and packed skylights are now routine on real projects, and understanding the plain-Python core lets you go beyond the sliders - customising the rule when an off-the-shelf Grasshopper component does not do quite what the design needs. The grid-attractor pairing alone underlies a huge share of contemporary facade work.
Generative algorithms produce bespoke pattern and layout that would be impossibly tedious by hand. Attractors drive perforated screens that open toward a view, packing arranges an artful non-repeating tile or acoustic-panel layout, and cellular automata generate feature-wall textures with an organic, grown quality. Because each is rule-driven and parametric, the pattern adapts instantly when the panel size, the room or the focal point changes.
This lesson is the bridge from scripting into full computational design. Grids, attractors, cellular automata and packing are exactly the topics the Academy's Computational Design course develops in Grasshopper - seeing their plain-Python cores here means you will understand what those components actually do, not just wire them together. Build an attractor grid and a cellular automaton and you have two portfolio pieces and a real head start.
“Generative design means the computer creates the design for you.”
Do it yourself
Think in rules and fields.
- 1Why is a grid described as the 'scaffold' of generative work rather than the design itself?
- 2In an attractor, what is measured for each element, and what is it mapped to?
- 3In a cellular automaton, do you design the pattern or the rule? What is the difference?
- 4What does the dart-throwing packing algorithm do when a proposed position collides with an existing one?
- 5Give one real facade or screen example for each: attractor, cellular automaton, packing.
The one line to carry out
Peer-reviewed journals & authoritative standards
- 01Cellular automaton — Wikipedia, 2026.
- 02Parametric design — Wikipedia, 2026.
- 03Generative design — Wikipedia, 2026.
- 04Grasshopper 3D — Wikipedia, 2026.
So far the rules generate form directly. The final lesson closes the loop: instead of accepting whatever a rule produces, we score each result and let the computer search for better ones - the generate-evaluate-improve loop behind optimization and simulation.
The author
Amogh N P
Architect, interior designer, and creative polymath. Studio Matrx began in his notebooks — his vision of design made honest, useful, and open to everyone. Its Academy is written and taught in his memory, and free, forever.
More about Amogh →