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.

Aerial render of a perimeter block of coloured row houses around a green courtyard, with a copper-roofed corner tower.
Default block, textured and rendered in V-Ray. The download produces the untextured geometry; materials and the render are not included.

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

  1. Extract the ZIP and open town-block.gh in Grasshopper. Set Rhino's units to meters first.
  2. Wait about a second for the solution. The model appears as a Grasshopper preview.
  3. 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.

Block at seed 7.
Seed 7: 27 houses, 2,051 objects.
Block at seed 23.
Seed 23: 25 houses, 2,090 objects.
Block with a 5 metre mean plot width and many narrow houses.
Mean plot 5 m: 39 houses.
Block with a 12 metre mean plot width and fewer, wider houses.
Mean plot 12 m: 19 houses.
Block with three storeys.
3 storeys: 1,207 objects.
Block with seven storeys.
7 storeys: 2,895 objects.

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 defaultHousesObjectsSolve (s)
Default (seed 7)272,0511.05
Seed 23252,0901.44
Seed 41252,1590.94
Block length 50 m201,7360.82
Block length 100 m352,6370.94
3 storeys271,2070.51
7 storeys272,8951.01
Mean plot 5 m391,9900.73
Mean plot 12 m192,0921.12
Pitched-roof share 0272,1420.76
Pitched-roof share 1272,2010.79
Corner tower off272,0231.05
Tree density 0271,9730.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.

DateStudyResult
28 OctPattern library26 tutorial definitions, mostly by TheAdam (@AdamArchh), re-implemented as 25 Python generators
29 OctPattern campus7 sliders; towers skinned from the pattern library, linked by bridges
31 OctTown block9 sliders; 27 houses; 2,051 objects
1 NovCanal town10 sliders; 100 houses; 5,974 objects; block code reused, adding a church, square, canal and bridges
3 NovFluid strata13 parameters; the method moved to Houdini 22 for a flowing form in the idiom of Zaha Hadid's work
Towers with patterned skins on a podium, joined by a ring walkway and a bridge.
Pattern campus, V-Ray render.
Aerial view of a small town of perimeter blocks around a church and market square, beside a canal.
Canal town, V-Ray render.
A smooth curved building wrapped in white floor bands, in a rippled landscape of textured ground and round trees.
Fluid strata, an original form and not a copy of a real building. This is a draft Karma viewport capture. Plaster, concrete, terrazzo and walnut come from my texture library through triplanar projection, as colour and roughness only; the glass stays an untextured material.

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.