Studio Matrx Monthly · Volume 1 · Issue 3 · August 2026
Amogh N P
 In loving memory of Amogh N P — Architect · Designer · Visionary 
Python in DynamoLesson 7.1
PSD for Architecture, Planning & Urban Design/Module 7 · Scripting Revit & Dynamo

Lesson 7.1 · Scripting Revit & Dynamo

Python in Dynamo

Drop real code into a visual graph - the Python Script node, its IN and OUT ports, and the two engines that run it

13 min Interactive lessonFree · open lessonByAmogh N P· Architect & interior designer
The hook

Dynamo turns Revit scripting into wiring boxes together - and when the boxes run out, the Python Script node lets you write the missing box yourself.

Dynamo is visual programming for the building model. Instead of typing code, you drag nodes onto a canvas and wire them together - one node reads all the walls, another gets a parameter, another sets a value. For a huge range of BIM tasks that graph is enough, and it is wonderfully legible: you can see the data flow.

But some logic is clumsy as a tangle of wires - a loop with a condition inside it, a bit of string manipulation, an algorithm with no ready-made node. That is where the Python Script node comes in. It is a single node on the canvas that runs real Python 3, taking inputs from the wires on its left and pushing a result out on its right. This lesson is about that node: how data crosses into and out of your code, and the one setting - the engine - that quietly decides how your script behaves.

Dynamo = boxes and wires. Python node = the box you draw yourself. IN in, OUT out.

The Python Script node: code inside a visual graph

Dynamo is an add-in that most designers open inside Revit, though it also runs standalone as Dynamo Sandbox and inside Civil 3D. Its canvas is a directed graph: data enters nodes on the left, flows through them, and leaves on the right. A typical Revit graph might be Categories -> All Elements of Category -> Element.GetParameterValueByName - three boxes, no code, and it reads every wall in the model and pulls a parameter. For a great deal of BIM work you never write a line of Python; you compose nodes.

The Python Script node is the escape hatch for when nodes are not enough. You add it by searching the library for Python Script, or right-clicking the canvas. Double-click it and an editor opens with a starter template already filled in. In a Revit session the template wires up the API references for you; the important lines are the two that connect code to canvas:

python
# Inputs are provided in the IN list; the result goes to OUT
dataEnteringNode = IN
OUT = 0

Everything you type between reading IN and assigning OUT is ordinary Python. You can loop, branch, build lists and dictionaries, import from the standard library, and call the Revit API. The node itself is just a container: Dynamo hands your code the inputs, runs it top to bottom, and takes whatever you put in OUT back onto the wire. Think of it as writing one custom node whose behaviour is exactly the code you want - the missing box you draw yourself when the library does not have it.

IN and OUT: how data crosses the boundary

The whole contract of the node is two names. `IN` is a list of the node's inputs, and `OUT` is the single value the node returns. When you add a Python Script node it starts with one input port; drag the little + at its bottom-left to add more. The first wired port is IN[0], the second IN[1], and so on - a plain Python list, in port order.

So if you wire a list of room areas into the first port and a threshold number into the second, your code reads them like this:

python
areas = IN[0]         # a list of numbers from the wire
threshold = IN[1]     # a single number

large = []
for a in areas:
    if a > threshold:
        large.append(a)

OUT = large

Dynamo converts between its own data and Python transparently: a Dynamo list arrives as a Python list, a number as a float or int, text as a str. Whatever you assign to OUT travels back out - a list becomes a Dynamo list, a dictionary becomes a dictionary, a single value a single value. One catch matters constantly in Revit work: elements coming from the graph arrive wrapped in Dynamo's own type, not as raw Revit API objects. Before you can call API methods on them you unwrap them with UnwrapElement(IN[0]), and if you build new Dynamo geometry to send back out you may need to wrap it. Get IN, OUT and UnwrapElement straight and the boundary stops being mysterious - it is just a list in and one value out.

THE PYTHON SCRIPT NODEareas listthresholdfactorPython Scripta = IN[0]t = IN[1]f = IN[2]OUT = [x*f for x in a if x>t]IN[0]IN[1]IN[2]OUTresultWires fill the IN list in port order; the value you assign to OUT leaves on the right.
Zoom
The Python Script node as a boundary: wires deliver values to the IN list in port order - IN[0], IN[1], IN[2] - your code does the work, and the single value you assign to OUT leaves on the right onto the next wire. The node is just a custom box you write yourself.

IN[0], IN[1]... = inputs in port order. OUT = the one thing you send back.

IronPython2 vs CPython3: which engine runs your code

A Python Script node does not run Python by magic - an engine interprets it, and Dynamo ships with two. For years the only option was IronPython 2.7, an implementation of Python 2 that runs on Microsoft's .NET framework. Because Revit itself is a .NET application, IronPython could talk to the Revit API very directly, which is why almost every older Dynamo script and forum answer is written for it. But Python 2 reached end of life in 2020, and its print statement, integer division and text handling all differ from modern Python.

Today Dynamo defaults to CPython3 - the standard, current Python 3 you learn everywhere else in this course, bridged to .NET through a library called Python.NET. This is the engine you should choose for new work: print() is a function, division of two integers gives a float, f-strings work, and the language matches the rest of the world. You pick the engine from a small dropdown at the bottom of the node, or pin it with a comment on the first line:

python
# engine: CPython3
print("hello from Python 3")
OUT = 1 / 2   # -> 0.5, not 0

The difference is not only syntax. The two engines expose the Revit API slightly differently - handling of output parameters, .NET enums and some collections varies - so a script copied from an old blog may fail under CPython3 and need small fixes, or vice versa. The practical rule: write new scripts in CPython3, and when you paste in old IronPython code, expect to adjust prints, division and the odd API call rather than assume it just runs.

WHICH ENGINE RUNS YOUR CODEIronPython 2.7legacy - Python 2 on .NETprint "hi"1 / 2 -> 0very direct .NET accessPython 2 ended 2020use only for old scriptsCPython3default - standard Python 3print("hi")1 / 2 -> 0.5.NET via Python.NETf-strings, modern syntaxchoose this for new workSame node, different interpreter. Set it in the engine dropdown or a # engine: CPython3 comment.
Zoom
Two engines run the same node differently. IronPython 2.7 is legacy Python 2 on .NET - what older scripts target. CPython3 is modern, standard Python 3 and the current default. Write new code for CPython3; expect small fixes when porting an old IronPython script across.

When to reach for code, and when to stay visual

The Python node is powerful, but reaching for it too early makes graphs worse, not better. A well-built Dynamo graph is self-documenting: a reviewer sees the flow of boxes and understands it. A Python node is opaque by comparison - its logic hides until someone opens the editor. So the honest guidance is to stay visual for as long as the nodes read clearly, and drop into Python only where the graph would otherwise become a knot of wires.

Good reasons to write code: a loop with a branch inside it (much cleaner as five lines of Python than as List.Map plus If plus List.FilterByBoolMask); string work like building sheet names from parts; a bit of maths or an algorithm with no matching node; or calling a Revit API method that Dynamo simply has not wrapped as a node. Good reasons to stay with nodes: reading and setting parameters on a category, filtering lists, and simple geometry - all of which have clear, readable nodes already.

A common and very healthy pattern is to mix the two: let the graph gather inputs and select elements with legible nodes, feed those into a compact Python node that does the awkward logic, and let the graph take the result onward to write it back. Your Python stays small and focused - the interesting decision, nothing more - and the surrounding graph keeps the whole thing readable. Scripting inside Dynamo is not a rejection of visual programming; it is visual programming with a sharper tool available for the parts that need one. Keep the code node lean, comment what it does, and you get the best of both worlds.

Nodes for the readable parts, Python for the knot. Keep the code node small.

Seeing what your code does: printing and errors

Code that runs cleanly in a plain file can still misbehave inside a node, so knowing how to see what your script is doing is part of using the Python node well. Your first tool is print. Anything you print from inside the node is captured by Dynamo, so scattering a few prints to check the value of a variable, the length of a list, or which branch a condition took is the quickest way to understand a script that is not behaving.

python
values = IN[0]
print("got", len(values), "values")   # shows up in the node output
total = sum(values)
print("total is", total)
OUT = total

When a script genuinely fails, the node turns yellow or the graph reports an error, and the message usually names the line and the problem - a TypeError, an AttributeError from calling a method on the wrong kind of object, or the classic NoneType error when LookupParameter returned nothing and you used it anyway. Read the last line of the message first; it is almost always the real cause. Because Dynamo runs the node top to bottom, an error stops at that line, so a print just above the failing line tells you the state of things right before it broke.

A second habit that saves grief is to build up a script in stages rather than all at once. Wire the inputs, print them, and run - confirm the data arriving is what you think. Then add the loop, printing inside it, and run again. Only once the middle behaves do you assign the final result to OUT. This is the same incremental style the debugging lesson in Module 10 preaches, and it matters even more here because a node hides its internals: the smaller the step between runs, the less you have to search when something breaks.

One last practical point: the node re-runs whenever its inputs change or you edit the code and click Run, so you get a fast feedback loop. Change a value upstream, watch the output update; tweak the code, run, read the print. That tight loop - edit, run, read, adjust - is how you actually develop a script inside Dynamo, and it turns the Python node from an intimidating black box into an ordinary, inspectable piece of your graph.

print() shows in the node output. Read the last line of an error first. Build up in stages.

Tools & terms you'll meet in this lesson

Python Script node

The Dynamo node that runs real Python inside a graph

One node on the canvas that executes your code; add it, double-click to edit, and it becomes a custom box you define yourself.

IN / OUT

The node's inputs list and single return value

IN[0], IN[1] are the wired inputs in port order; assign your result to OUT to send it back onto the wire.

UnwrapElement

Converts a Dynamo-wrapped element to a raw Revit API element

Call it before using Revit API methods on elements that arrive from the graph, or the API calls fail.

CPython3 engine

The modern Python 3 engine, now the Dynamo default

Prefer it for new work; IronPython 2.7 is the legacy .NET engine older scripts target and often needs small fixes to port.

Hands-on workshop

Workshop - a doubling node

You do not need Revit for this: Dynamo Sandbox (free) or Dynamo inside Revit both run a Python node against plain numbers. The goal is to feel the IN/OUT boundary before any BIM is involved.

Dynamo (Sandbox is free) or Dynamo inside Revit. No Revit model required for this exercise - plain numbers are enough.

Given & goal
Goal: read a list and a factor, return a transformed list
Inputs: a Dynamo number-range wired to a Python Script node
Time: ~25 minutes
  1. 1In Dynamo, create a Number Range (0 to 10) and add a Python Script node. Wire the range into its first input; you now have data at IN[0].
  2. 2Drag the node's + to add a second input and wire a single Number (say 2) into it - that is IN[1]. Set the engine dropdown to CPython3.
  3. 3In the editor, read both inputs, loop over the list multiplying each value by the factor, and assign the new list to OUT. Run the graph and watch the output preview change.
  4. 4Add a condition inside the loop - only keep values whose result is above 8 - so the node both transforms and filters. Confirm the output list shrank.
  5. 5Break it on purpose: assign a value to OUT2 instead of OUT and read the error. Fix it. Then change the factor node and watch the whole graph recompute - the leverage of a live script.

You’ll walk away with
A saved Dynamo graph with one Python Script node that reads a list and a factor from its IN ports, transforms and filters the values in a loop, and returns the result through OUT.

The worked example

Three altitudes on the same idea

Read the band that fits you — or all three.

For the architectAutomate busywork & build custom tools

Dynamo is where scripting meets your live Revit model. The recurring studio chores - renumbering sheets, batch-setting a fire rating across a category, checking that every door has a mark - are graphs with a small Python node doing the decision. Because Dynamo runs inside Revit, you script the model you already have open, and a graph saved once becomes a button your whole team can run.

For the interior designerScripts for data, schedules & layouts

Much of your Revit and BIM work is parameters and lists, which is exactly Dynamo's comfort zone. Pulling a room finish schedule, pushing FF&E codes onto families, tagging every fixture - these are short graphs, and the Python node handles the fiddly text and rules (build a code from three fields, skip rooms already filled). You get automation without leaving the model environment your consultants share.

For the studentA hireable computational skill

Dynamo is the friendliest on-ramp from Python to real BIM. You see data flow as boxes, so the abstract becomes visible, and the Python node lets you apply the loops and conditionals from Modules 1 and 2 to an actual building model. Employers list Dynamo alongside Revit constantly; a portfolio graph that automates a genuine task is concrete proof you can script BIM, not just talk about it.

Misconception check

Dynamo is visual, so if I use the Python node I am doing it wrong - I should be able to wire everything with boxes.

Neither extreme is right. Wiring everything is the ideal for simple, readable flows, and forcing a genuinely awkward loop into a chain of List.Map and If nodes produces a graph nobody can follow. The Python node exists precisely because some logic is clearer as a few lines of code. The skilled Dynamo user mixes both: legible nodes for gathering and writing data, a compact Python node for the knotty decision in the middle. Reaching for Python is not a failure of visual programming - it is knowing when a small piece of code is the more readable choice. The mistake is only at the extremes: a graph that is all wires and unfollowable, or a single giant Python node that ignores the graph entirely.
Try it

Do it yourself

Reason about the node boundary.

  1. 1You wired three ports into a Python node. How do you read the value on the second port?
  2. 2What single name do you assign your result to so it leaves the node?
  3. 3Why might an element from the graph need UnwrapElement before you call a Revit API method on it?
  4. 4Name one difference in behaviour between the IronPython2 and CPython3 engines.
  5. 5Give one task better done with plain Dynamo nodes and one better done inside a Python node.
Take this with you

The one line to carry out

The Python Script node is a box you write yourself: read the wires from `IN`, do the awkward logic in real Python, hand the result back through `OUT` - and choose CPython3. It lets code and the visual graph collaborate, each doing what it is best at.
Take it further
References & further reading

Peer-reviewed journals & authoritative standards

  1. 01Dynamo (software)Wikipedia, 2026.
  2. 02Autodesk RevitWikipedia, 2026.
  3. 03Python (programming language)Wikipedia, 2026.
  4. 04Building information modelingWikipedia, 2026.
Related lessons
Recap
Dynamo scripts Revit as a visual graph of nodes; the Python Script node runs real Python inside that graph. Inputs arrive as the IN list in port order and your return value goes to OUT, with UnwrapElement bridging Dynamo-wrapped elements to the raw Revit API. Two engines exist - legacy IronPython 2.7 and the modern default CPython3 - and you should write new code for CPython3. Reach for the node only where the graph would otherwise knot up.
Carry forward →

The node let us run code against the model, but we leaned on Dynamo to hand us the elements. Next we go under the hood: what the Revit API actually is - elements, parameters and the transactions that make changes stick.

A

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 →