Lesson 6.3Lesson 6.3 · Scripting Rhino & Grasshopper
Generating Geometry with Code
Points, lines, curves and surfaces built from numbers - a column grid and a twisting tower, driven by parameters you can turn
Once geometry comes from numbers, the numbers become your design controls - turn one and the whole building responds.
Everything so far has been plumbing: getting data in and out, choosing a library. Now we do the thing that makes all of it worthwhile - make geometry from code. A point is three numbers. A line is two points. A curve is a list of points and a rule. A surface is a grid of them. Build these from code and the numbers behind them become dials you can turn.
That is the whole promise of parametric scripting. Instead of drawing a column grid and redrawing it when the bay changes, you describe it once - counts and spacing - and change the description. This lesson builds two real examples from scratch so the promise stops being abstract.
Numbers at the top = the dials. Loops build from them. Twist = accumulating rotation. Code builds; you judge.
The building blocks: points, vectors, lines
All geometry starts with the point. In Rhino, a 3D point is three numbers - x, y, z - wrapped in a Point3d:
import Rhino.Geometry as rg
a = rg.Point3d(0, 0, 0)
b = rg.Point3d(10, 0, 0)
line = rg.Line(a, b)A vector (Vector3d) looks the same - three numbers - but means a direction and distance rather than a location; lesson 5.2 drew that distinction, and it matters here because you move and copy geometry by adding vectors. A line is just two points. Once you have points and lines, curves follow: a smooth NURBS curve is built from a list of points it passes through or is shaped by.
pts = [rg.Point3d(0, 0, 0),
rg.Point3d(5, 8, 0),
rg.Point3d(12, 4, 0),
rg.Point3d(18, 10, 0)]
curve = rg.Curve.CreateInterpolatedCurve(pts, 3)CreateInterpolatedCurve threads a degree-3 (smooth) curve through those points. Notice the pattern already forming: make a list of points, hand it to a constructor, get geometry back. That single move - points into a builder - underlies almost everything in this lesson. Assign the result to an output like a and it appears on the Grasshopper canvas.
It is worth internalising the vector-as-a-move idea, because it is how geometry travels. To copy a point ten units up, you do not compute new coordinates by hand - you add a vector:
up = rg.Vector3d(0, 0, 10)
top = a + up # a new Point3d, ten units higherThat single move - point plus vector gives a new point - is the basis of extruding, arraying and offsetting everything later in the lesson. Surfaces extend the same logic one dimension further: where a curve is shaped by a list of points, a NURBS surface is shaped by a grid of them, which is precisely why the nested-loop grid you are about to build is so central - it is the raw material a surface is made from. Points beget lines and curves; grids of points beget surfaces; and every one of them starts as a few numbers you chose.
This is also the moment to connect the geometry to Module 5, which built the mathematical intuition - coordinates, vectors, transformations - underneath everything here. You do not need to recompute any of that maths yourself; Rhino.Geometry provides ready objects and methods for it. But understanding what a Vector3d means, or why a curve needs a degree, makes the difference between copying code you do not trust and writing code you can reason about and fix. The library supplies the machinery; Module 5 supplied the mental model; this lesson puts them together to actually make things. That layering - concepts, then geometry, then generation - is deliberate, and it is why the earlier modules pay off precisely here.
Point3d = a place. Vector3d = a move. Line = two points. Curve = points + a rule.
Worked example: a parametric column grid
Time for the classic. A column grid is a point at every intersection of a set of bays in two directions - exactly what two nested loops produce. The outer loop walks across, the inner loop walks up, and the same instruction places a point each time:
import Rhino.Geometry as rg
cols, rows = 5, 4 # inputs from sliders
step_x, step_y = 6.0, 5.0
points = []
for i in range(cols):
for j in range(rows):
x = i * step_x
y = j * step_y
points.append(rg.Point3d(x, y, 0))
a = points # 20 column base points onto the canvasWire cols, rows, step_x and step_y to sliders and you have a live, adjustable grid: 5 by 4 becomes 8 by 6 by dragging, and the spacing follows the numbers. To turn each base point into an actual column, extrude a vertical line at every point:
height = 3.2
columns = []
for p in points:
top = rg.Point3d(p.X, p.Y, height)
columns.append(rg.Line(p, top))
b = columnsThat is a complete, honest parametric model in a dozen lines: numbers in, a grid of columns out, everything responsive. The double loop you just wrote is the same engine behind facade panels, floor plates and seating layouts - learn it once, reuse it forever.
Notice a small but important detail in how the points were collected. The points list was created empty, before the loops, and each pass append-ed to it; if you create the list inside the inner loop it resets every time and you keep only the last point - a classic slip worth watching for. This gather-into-a-list pattern is everywhere in geometry code: start with [], build up inside the loop, use the full collection after. A neater, more Pythonic form of the same grid uses a nested list comprehension, which Module 2 introduced:
points = [rg.Point3d(i * step_x, j * step_y, 0)
for i in range(cols)
for j in range(rows)]Both produce the identical 20 points; the comprehension is terser, the explicit loops are easier to extend when the logic grows. Choose whichever reads more clearly to you - both are correct, and clarity beats cleverness.
Grid = outer loop across x inner loop across = a point each pass. i*step, j*step.
Worked example: a twisting tower
Now something that looks impressive but is really the same idea plus a rotation. A twisting tower is a stack of identical floor plates, each lifted higher and rotated a little more than the one below. One loop, an accumulating angle:
import Rhino.Geometry as rg
import math
floors = 20
floor_h = 3.5
twist = 4.0 # degrees added per floor
size = 12.0
plates = []
for k in range(floors):
z = k * floor_h
centre = rg.Point3d(0, 0, z)
plane = rg.Plane(centre, rg.Vector3d.ZAxis)
rect = rg.Rectangle3d(plane, rg.Interval(-size/2, size/2),
rg.Interval(-size/2, size/2)).ToNurbsCurve()
angle = math.radians(k * twist)
rect.Rotate(angle, rg.Vector3d.ZAxis, centre)
plates.append(rect)
a = platesEach floor is a square, raised by k * floor_h and rotated by k * twist degrees around its own centre - so the twist accumulates up the building. The move that makes it parametric is that the character of the tower now lives in four numbers: more floors, taller floor_h, sharper twist, wider size. Set twist to zero and you have a plain extrusion; push it to eight and you have a dramatic corkscrew. You could Loft these plates into a skin inside Rhino (remember: lofting needs the kernel, lesson 6.2), but as a stack of profiles the design intent is already fully there, controlled entirely by numbers you can turn.
Two things in that snippet are worth pausing on. First, the math.radians(...) call: Rhino, like most geometry libraries, measures rotation angles in radians, not degrees, so you almost always convert - it is one of the most common small bugs in geometry scripting, a tower that barely twists because someone passed 4 (radians, a huge angle wrapped around) or one that spins wildly because they forgot to convert. Second, notice that the same handful of moves from the column grid returns: a loop, a running index k, and geometry placed from numbers. The tower is not a new skill - it is the grid skill plus a rotation. That is the quiet lesson of parametric scripting: impressive-looking form is usually a simple pattern with one extra idea layered on, and once you have the patterns, ambitious geometry stops being intimidating. The same recipe - a loop, an index, an accumulating transform - scales from a twisting tower to a spiral ramp, a fanned louvre array, or a helical stair, each just a different transform applied per step.
Tower = one loop of floors. z = k*height. angle = k*twist (accumulates). Rotate each plate.
Parameters as design controls - and honest limits
Step back and notice what changed. In both examples, the design lives in a few named numbers at the top. That is the heart of parametric design: separate the parameters (the dials) from the construction (the loops that build from them), and exploring options becomes turning dials rather than redrawing. A good habit is to gather every meaningful number at the very top of your script, clearly named, so the controls are obvious - to you next month and to anyone reading it.
# --- controls ---
floors = 20
floor_h = 3.5
twist = 4.0
size = 12.0
# --- construction below; do not hand-edit ---But keep two honest limits in view. First, code does not supply judgement. A script will happily generate a 90-degree twist that is unbuildable or a column grid that ignores the site; deciding which numbers are good is still design, and still yours. Second, not everything should be scripted. A one-off shape you will never vary is faster to draw by hand than to parameterise. Scripting geometry pays off when a form has real variations worth exploring, or a pattern too repetitive to draw - which is most of the interesting cases, and exactly what Module 9 pushes further into generative territory.
One more habit pays off enormously as scripts grow: keep the construction free of magic numbers. Every meaningful quantity should trace back to a named control at the top, so there is one place to change the design and no stray 3.5 buried deep in a loop that silently fights the slider above it. When you find yourself typing a raw number inside the construction, that is usually a signal it wants to become a parameter. This discipline is what lets a script survive contact with a real project - a colleague, or you in six months, can open it, read the controls block, and understand the whole design without deciphering the loops. Parametric thinking is as much about naming your intentions as about the geometry itself.
rg.Point3d(x, y, z)
A location in 3D space from three numbers
The atom of all geometry. Access components as p.X, p.Y, p.Z. Everything else is built from points.
rg.Line(a, b)
A straight segment between two Point3d
Two points make a line. Extrude a vertical line at each grid point and you have columns.
Curve.CreateInterpolatedCurve
A smooth NURBS curve through a list of points
Threads a degree-3 curve through points you supply. The make-a-list-hand-it-to-a-constructor pattern.
nested for loops
Two loops, one inside the other, over i and j
The engine of grids: outer across, inner up, a point placed each pass. Reused everywhere in geometry code.
rect.Rotate(angle, axis, centre)
Rotate geometry in place about an axis
An accumulating angle per iteration builds a twist. angle in radians - convert with math.radians.
Workshop - your own parametric tower
Build a twisting tower from scratch in a Grasshopper Python 3 component, then drive it entirely from sliders. You will practise a loop, an accumulating transform, and the discipline of putting controls at the top - the core of parametric scripting.
Rhino 8 with Grasshopper (for the CPython 3 component and Rhino.Geometry). No external libraries needed.
Goal: a stack of rotated floor plates whose form is controlled by four sliders Inputs: sliders for floors, floor_h, twist, size wired into a script component Time: ~40 minutes
- 1Drop a Python 3 script component and add four inputs; rename them
floors,floor_h,twist,size. Setfloorsto Item access (a single count) and wire an integer slider; wire number sliders to the other three. - 2At the top of your code,
import Rhino.Geometry as rgandimport math. Do not hard-code the four values - read them from the inputs so the sliders are live controls. - 3Write one
for k in range(floors):loop. Inside, computez = k * floor_h, build a square profile centred at that height (a Rectangle3d on a Plane), and rotate it bymath.radians(k * twist)about the vertical axis through its centre. - 4Collect every plate into a list and assign it to the output
a. Confirm the plates appear stacked and rotating as you drag thetwistslider from 0 upward. - 5Bonus: add a fifth slider
taperand multiplysizeby(1 - taper * k / floors)so the tower narrows as it rises - proof that new design behaviour is often just one more number and one more line.
You’ll walk away with
A Grasshopper Python 3 component that outputs a stack of rotated (and optionally tapering) floor plates, fully driven by named sliders - a working parametric tower you can reshape by dragging.
Three altitudes on the same idea
Read the band that fits you — or all three.
Parametric geometry turns option-testing from days into minutes. A massing you can twist, a bay grid you can re-space, a facade whose panel density follows a number - each lets you present three studied variants where you once had time for one. The discipline of gathering controls at the top of a script mirrors good design thinking: name the moves that matter, and let the construction follow. Judgement about which numbers are right stays firmly with you.
The same loops that stack a tower lay out a room. A grid of pendant lights over an island, a run of identical joinery modules, a repeating ceiling baffle, seating spaced evenly along a curve - all are points-from-numbers and a loop. You rarely need the twisting-tower drama; you need the reliable column-grid move, applied to furniture, fittings and finishes, so a spacing change ripples through the whole layout instantly.
Generating geometry from code is the portfolio skill that reads as computational design. A parametric tower or an adaptive facade, driven by clearly named parameters, demonstrates exactly the thinking studios hire for. Master the two moves here - the nested-loop grid and the accumulating-transform stack - and you can build a surprising range of forms. They are the literal foundation of the Computational Design and Generative AI courses in this Academy.
“Generating geometry with code means the computer designs the building for me.”
Do it yourself
Trace the numbers into the form.
- 1What three numbers define a Point3d, and what does a Line need to be built?
- 2In the column-grid script, what do the outer and inner loops each control, and what does
i * step_xcompute? - 3In the twisting tower, why is the rotation angle
k * twistrather than justtwist? - 4What single change to the tower script turns the corkscrew back into a plain vertical extrusion?
- 5Why is gathering all the parameters at the top of a script a good habit, beyond tidiness?
The one line to carry out
Peer-reviewed journals & authoritative standards
- 01Parametric design — Wikipedia, 2026.
- 02Non-uniform rational B-spline — Wikipedia, 2026.
- 03Grasshopper 3D — Wikipedia, 2026.
- 04Computational geometry — Wikipedia, 2026.
Our scripts have been returning flat Python lists of geometry. But Grasshopper organises data into branching trees, and that structure carries real meaning - which floor a plate belongs to, which bay a column sits in. Next we learn to think in trees, and when code beats components for handling them.
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 →