A Parametric City Block in One Grasshopper Component
Tutorial · Rhino 8, Grasshopper, Python 3 · 4 November 2026
This tutorial builds a European perimeter block from nine sliders: row houses on seeded plots, shop bases, windows, balconies, mixed roofs, a corner tower, a courtyard and street trees. The default solution contains 27 houses and 2,051 valid closed objects and solves in about one second. All of it comes from one Python 3 component in Grasshopper, and the definition is a free download.
The block is the clearest example of a method developed across a series of parametric studies between 28 October and 3 November 2026. The sections below show how to run it, how the generator is organised, and how to change it.
Download the tutorial kit (ZIP)
How this was made
The geometry code was written by AI agents, mainly Claude, driving Rhino and Grasshopper through a Model Context Protocol (MCP) server. Theo Dimitrasopoulos set the brief, chose the references and judged the results. The human work was art direction and review; the agents wrote the code and ran the tests. That division is part of the method, because it is what makes the testing harness below necessary: generated code is only useful if every run can be checked automatically.
What you need
- Rhino 8 for Windows with Grasshopper. Tested on version 8.36. The Python 3 script component requires Rhino 8.
- Basic Grasshopper use: opening a definition and moving sliders. Reading the code needs basic Python.
- No plug-ins and no external data. The generator uses RhinoCommon and the Python standard library.
1. Run the definition
- Extract the ZIP and open
town-block.ghin Grasshopper. Set Rhino's units to meters first. - Wait about a second for the solution. The model appears as a Grasshopper preview.
- Check the panel named
MODEL / VALIDATION. It should read:
houses 27 | storeys 5 | plot 8.0 m | objects 2051 | dropped 0
Move the Layout seed slider. Every seed gives a different block, and the same seed always gives the same block. dropped counts any geometry that failed validation, so it should stay at 0.






V-Ray renders of the definition's output, dressed with materials from my texture library. Only the named slider differs from the defaults. The download itself carries preview colours only.
2. Why one component
A conventional definition would spread this model across hundreds of wired components. Here the canvas holds nine sliders, one script component and sixteen preview components, one for each material group. The full source sits in a panel wired into the script component, so the .gh file is self-contained, and the same text is supplied as geometry.py for reading.
This layout suits agent-written code. A text file can be read, edited and compared directly, whereas a wire graph cannot. Grasshopper keeps what it does well: live sliders, previews and baking.
3. Four ideas in the generator
Validate as geometry is created
Every object passes through one gate. Anything invalid or open is counted and discarded, so a failure shows up in the dropped figure and not as a hole in a later render.
def keep(target,g):
if g is not None and g.IsValid and (g.IsClosed if isinstance(g,G.Mesh) else g.IsSolid):target.append(g)
else:dropped[0]+=1
Design one street, then transform it
Houses are written in simple local coordinates: x runs along the street and y runs back from it. A single plane-to-plane transform places each side of the block, so no facade code needs to know which way it faces.
XF[0]=G.Transform.PlaneToPlane(G.Plane.WorldXY,G.Plane(G.Point3d(ax,ay,SW),t,G.Vector3d(-t.Y,t.X,0)))
Cut plots, then let each plot decide
Each side is cut into plots around the mean width, varying between 70% and 130% of it. The house function then chooses that plot's colour, setback, window width, balconies and roof.
while L-depth-x>plotwidth*1.25:x+=plotwidth*rnd.uniform(.7,1.3);cuts.append(x)
Give independent features their own random stream
All house decisions draw from one seeded generator. Trees use a second one, so tree density never disturbs the buildings:
rnd=random.Random(seed);trnd=random.Random(seed+1000)
The test results show why this matters. Setting tree density to 0 removes exactly 78 objects (39 canopies and 39 trunks) and leaves the houses untouched. The pitched-roof share has no such separation: a house draws extra numbers only when it gets a pitched roof, which shifts every later decision. That explains why both extremes in the table below have more objects than the default. The corner tower switch behaves the same way. These two sliders change the block as well as the feature they name.
4. Test results
verify_block.py opens the definition, solves it, repeats the default seed and sweeps twelve slider values. Every result below is from a run of the downloadable file in Rhino 8.36 on Windows. All twelve variants contained only valid closed objects, and repeating the default seed gave identical object counts and bounds.
| Change from default | Houses | Objects | Solve (s) |
|---|---|---|---|
| Default (seed 7) | 27 | 2,051 | 1.05 |
| Seed 23 | 25 | 2,090 | 1.44 |
| Seed 41 | 25 | 2,159 | 0.94 |
| Block length 50 m | 20 | 1,736 | 0.82 |
| Block length 100 m | 35 | 2,637 | 0.94 |
| 3 storeys | 27 | 1,207 | 0.51 |
| 7 storeys | 27 | 2,895 | 1.01 |
| Mean plot 5 m | 39 | 1,990 | 0.73 |
| Mean plot 12 m | 19 | 2,092 | 1.12 |
| Pitched-roof share 0 | 27 | 2,142 | 0.76 |
| Pitched-roof share 1 | 27 | 2,201 | 0.79 |
| Corner tower off | 27 | 2,023 | 1.05 |
| Tree density 0 | 27 | 1,973 | 0.95 |
To repeat the check, set the GH path at the top of verify_block.py, run -_Grasshopper _Window _Hide _Enter in a new Rhino document, then run the script from Rhino's script editor. It writes verification.json beside the definition. Solve times will vary by machine.
5. Exercise: add a chimney
Double-click the SOURCE | embedded Python 3 panel and find the ridge-roof line in house. Add the chimney line directly below it, keeping the eight-space indent:
add(roof,prism([(x0,f-.4,z),(x0,dep+.4,z),(x0,(f+dep)/2,z+(dep-f)*.42)],G.Vector3d(w,0,0)))
add(stone,box(x0+.8,x0+1.5,dep*.5,dep*.5+.7,z,z+3.4)) # chimney
The validation panel should now report objects 2069 | dropped 0: 18 new chimneys, one for each pitched roof apart from the tower's. The rest of the layout stays the same, because the new line draws no random numbers. This is the rule to follow when extending the generator: additions that use no random numbers cannot reshuffle the block.
From block to town
The block sits in a sequence of studies that used the same routine: a generator, sliders, an automated test run and a saved snapshot. Each stage reused what came before.
| Date | Study | Result |
|---|---|---|
| 28 Oct | Pattern library | 26 tutorial definitions, mostly by TheAdam (@AdamArchh), re-implemented as 25 Python generators |
| 29 Oct | Pattern campus | 7 sliders; towers skinned from the pattern library, linked by bridges |
| 31 Oct | Town block | 9 sliders; 27 houses; 2,051 objects |
| 1 Nov | Canal town | 10 sliders; 100 houses; 5,974 objects; block code reused, adding a church, square, canal and bridges |
| 3 Nov | Fluid strata | 13 parameters; the method moved to Houdini 22 for a flowing form in the idiom of Zaha Hadid's work |


Limits
- This is massing and facade detail at diorama level. Plans, structure, circulation and building codes are not modelled, and the dimensions are plausible defaults, not measured from a real city.
- The block is rectangular. Irregular sites would need a different way to cut plots.
- The tests prove that the geometry is valid and repeatable. They say nothing about architectural quality, which was judged by eye.
- The kit contains the block only. Textures, V-Ray setup, the pattern library, canal town and the Houdini file are not included.
- Testing covered Rhino 8.36 on Windows. Rhino for Mac was not tested.