Lesson 7.1Lesson 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
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:
# Inputs are provided in the IN list; the result goes to OUT
dataEnteringNode = IN
OUT = 0Everything 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:
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 = largeDynamo 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.
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:
# engine: CPython3
print("hello from Python 3")
OUT = 1 / 2 # -> 0.5, not 0The 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.
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.
values = IN[0]
print("got", len(values), "values") # shows up in the node output
total = sum(values)
print("total is", total)
OUT = totalWhen 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.
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.
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.
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
- 1In Dynamo, create a
Number Range(0 to 10) and add aPython Scriptnode. Wire the range into its first input; you now have data atIN[0]. - 2Drag the node's
+to add a second input and wire a singleNumber(say 2) into it - that isIN[1]. Set the engine dropdown to CPython3. - 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. - 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.
- 5Break it on purpose: assign a value to
OUT2instead ofOUTand 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.
Three altitudes on the same idea
Read the band that fits you — or all three.
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.
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.
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.
“Dynamo is visual, so if I use the Python node I am doing it wrong - I should be able to wire everything with boxes.”
Do it yourself
Reason about the node boundary.
- 1You wired three ports into a Python node. How do you read the value on the second port?
- 2What single name do you assign your result to so it leaves the node?
- 3Why might an element from the graph need
UnwrapElementbefore you call a Revit API method on it? - 4Name one difference in behaviour between the IronPython2 and CPython3 engines.
- 5Give one task better done with plain Dynamo nodes and one better done inside a Python node.
The one line to carry out
Peer-reviewed journals & authoritative standards
- 01Dynamo (software) — Wikipedia, 2026.
- 02Autodesk Revit — Wikipedia, 2026.
- 03Python (programming language) — Wikipedia, 2026.
- 04Building information modeling — Wikipedia, 2026.
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.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.
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 →