Revit API within Python Nodes

Hi All!

I want to use the Revit API within a Python Node in Dynamo. This is because Dynamo is already integrated within Revit, and I love that simplicity from a user-perspective. The problem is, I don’t believe there is a central place for the Revit API being used in python. Please share resources / good script examples that you have found of this!

  • https://www.revitapidocs.com/ has a ton of info on the Revit API, but no examples are in Python
  • Aussie BIM Guru (on youtube) will write some python nodes using the Revit API, but the information is scattered. It is difficult to understand what each portion of his code accomplishes. It is possible, though. And he must have learned from some other resource, which keeps me searching.
  • I have found a couple forum posts with code that actually works, but those are difficult to find

I have been using Dynamo and Python nodes for a year now and I am a big fan. I do have functioning Python code using the Revit API for a confirmation message once the script is complete (shown below). But now I would like to try using the Selection class and Duct class.

Welcome to the forum :smiley:

The examples in the Revit API aren’t in Python (which I agree is really annoying) but it’s only the syntax that changes.

When you say you want to use the selection class and duct class… What exactly are you trying to do and what have you tried so far?

Which examples do you refer to? It actually has a TON.

If it is the samples for a particular class, that code is not provided by Revit API docs, but by the Revit Development team. They do not provide a Python sample as Python is not a supported language. The samples provided by the Revit API docs site can be found in the Code section (which won’t load on my phone), or o. The github repository here: revitapidocs.code/0-python-code at master · gtalarico/revitapidocs.code · GitHub

There is also a great repository of Dynamo Python code managed by @solamour here: GitHub - Amoursol/dynamoPython: Python Modules for Dynamo

The bulk of the code on the Dynamo forum also works, but you need to reconfigure things for your environment. Any changes to Revit, Dynamo, Python, the Python engine, and .NET means your code has to change every time you update any of the items in that list. All of that means you need to revise things about 4x a year, which means there is about a 25% chance a random Python script will ‘just run’ without you haveing to either reconfigure your environment or altering the script.

3 Likes

Hi, thank you! Changing the syntax doesn’t seem so easy, so that’s why I’m generally asking if people have resources on this. But maybe you all will help me see that it isn’t so bad after all.

  • Selection Class: I would like the user to select ducts/pipes in the Revit model, hit “Run” on a Dynamo script, in which has a node that observes what is selected already and assigns the parameter “Phase Demolished” to “Phase 1”. I can assign that parameter with Dynamo/DesignScript nodes pretty easily, but observing the selection looks the most feasible with the API. In RevitAPIDocs, it looks like I can perform a “GetElementIDs” and here is the C# example:

And here is my current Python node:

Since “TaskDialog” works, I am following the same format it was written in. “Autodesk.Revit.UI.Selection” is the namespace, “Selection” is the class, and the “GetElementIDs” method is within the “Selection” methods of that class. Even though I have a few elements selected in Revit, I’m getting this error currently:

There may be too many levels deep of Selection.Selection. So instead, I can write my last line of code as such:
Screenshot 2024-11-01 122856

And I will get this error:

  • For the Duct Class, I would like to make a simple “Duct Create” node where IN[0] is the starting coordinate, IN[1] is the ending coordinate, and IN[2] is the diameter. Maybe I need more inputs, but the goal is to be able to generate a duct in Revit with a node. I have not tried to write this out since I got stuck with the syntax on selections.

Also my intention is not to make you all write code for me. I just need some guidance on Revit API friendly syntax. Also, I am currently trying to avoid using any packages.

Hey Jacob, thanks for your context on this. Maybe there are Python examples on RevitAPIDocs after all. I have looked at about 5 methods that look interesting to me, none of them had a sample Python code, and I moved on to google around for other resources. I just assumed there wasn’t any python on that website.

I shall dig into those repositories you linked! Thank you for those. I have not seen those before.

hi
The more messages we read, the more things we understand, everything comes with time depending on the person.
I always have to learn

to help

import sys
import clr
import System
from System.Collections.Generic import List
clr.AddReference("RevitAPIUI")
import Autodesk
from Autodesk.Revit.UI.Selection import Selection
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import ElementId
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
uiapp = DocumentManager.Instance.CurrentUIApplication
uidoc = uiapp.ActiveUIDocument
listElementId=List[ElementId]()
listElementId=uidoc.Selection.GetElementIds()

doc = DocumentManager.Instance.CurrentDBDocument
listElement=[doc.GetElement(Eid) for Eid in listElementId]
OUT = listElementId,type(listElementId),type(listElementId[0]),listElement

sincerely
christian.stan

1 Like

I just get an error with your code @christian.stan

EDIT… just realised you’re meant to select something in Revit first!!

I’d add a warning:

import clr
import System
from System.Collections.Generic import List

clr.AddReference("RevitAPIUI")
import Autodesk
from Autodesk.Revit.UI.Selection import Selection
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import ElementId
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager

uiapp = DocumentManager.Instance.CurrentUIApplication
uidoc = uiapp.ActiveUIDocument
doc = DocumentManager.Instance.CurrentDBDocument

# Get the element IDs
listElementId = List[ElementId](uidoc.Selection.GetElementIds())

# Check if anything is selected
if listElementId.Count > 0:
    # Get the elements corresponding to the selected IDs
    listElement = [doc.GetElement(Eid) for Eid in listElementId]
    OUT = (listElementId, type(listElementId), type(listElementId[0]), listElement)
else:
    # If no elements are selected, tell the user they're a moose 
    OUT = ("No elements selected", [], [])

1 Like

I prefer to tell the user to select something… This is a version of something I found on this forum aeons ago.

import clr
clr.AddReference("RevitAPIUI")
from Autodesk.Revit.UI import *
clr.AddReference("RevitServices")
from RevitServices.Persistence import DocumentManager
clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)

doc = DocumentManager.Instance.CurrentDBDocument
uidoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument
sel1 = uidoc.Selection

category_list = ["Ducts"]

message = "In your Revit window click on your lovely, lovely duct.\n\n    ***   Then press the ESC key to finish.   ***"

TaskDialog.Show("Select your elements!", message)

class CustomISelectionFilter(Selection.ISelectionFilter):
    def __init__(self, allowed_categories):
        self.allowed_categories = allowed_categories

    def AllowElement(self, e):
        return e.Category.Name in self.allowed_categories

    def AllowReference(self, ref, point):
        return True

out1 = []
flag = True

while flag:
    try:
        el1 = doc.GetElement(sel1.PickObject(Selection.ObjectType.Element, CustomISelectionFilter(category_list), "Select an element").ElementId)
        out1.append(el1.ToDSType(True))
    except:
        flag = False

OUT = out1[0] if len(out1) == 1 else out1

1 Like

hello, you are right the particular case to rule out :wink:
It’s worth a ruler on the fingers (Mr. Jacob you can.) :wink:Sincerely
christian.stan

2 Likes

Don’t forget to switch to IronPython the famous ISelectionFilter
Sincerely
Christian.stan

@christian.stan @Alien

Thank you both for your help. Have you noticed that the selection will update every Run if you use the Dynamo Player, but not the Dynamo node editor? That is how it works for me. Is that affected by a setting within the Dynamo node editor, or simply a behavior I should expect?

In other words, I typically test run my script with the Dynamo node editor open. But right now my selection only updates each run if I use the Dynamo Player. I’ve worked with the “Document.Current” node and it has the same behavior.

1 Like

That’s how it works. Annoying but also can be useful.

In the Dynamo context, nodes only re-execute if they are marked as ‘dirty’. That is the node either ‘watches’ for document changes which would trigger new data (I believe All Elements of Category and File From Path nodes both do this in some cases), or the node has a change in the input.

However in the Dynamo Player context all nodes are re-executed on each run - even if they ran to completion previously.

The former setup is preferable when authoring, as otherwise it means EVERYTHING has to run all the way through every time you run the graph, so if you’re building a graph that takes 40 seconds to run and it is the 3rd node you place on canvas but you need to place say 100 nodes after it… well you’re now waiting 4,000 seconds (or one hour six minutes) just to put those other nodes in canvas. No one wants that.

But the later is better if you want the full thing to execute and the result added to your file every time you push the button.

2 Likes

So I would like to connect the dots here for the usage of Revit API + Python. A couple questions below. Here is my desired method from RevitAPIDocs:

It looks like what I want to use is “Autodesk.Revit.UI.Selection.Selection” and to make that available, I need this in my script:

> import sys
> import clr
> clr.AddReference("RevitAPIUI")
> import Autodesk
> from Autodesk.Revit.UI.Selection import Selection

But also, Selection must include the “ElementId class” so the script also contains:

clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import ElementId

But to further understand how this is put together, two questions:
1. How do you know to import the List method? It is ultimately used to collect the ElementIds as shown:
image

image

2. How do you know to use the “doc.GetElement” method? Is this simply from seeing other examples of people using it, or does this follow a core principle of yours? This one seems a little more dense, needing to know to use “CurrentDBDocument” instead of “CurrentUIApplication”

You’re writing code based on a C# API. This means that anything Python that needs a C# object type has to do one of two things:

  1. Be provided with an object of the C# type.
  2. Convert the provided object to one of the required C# object type.

For some object types - say an Element - the Dynamo Python environment will manage the content via changes accordingly. But for others - say a list of element IDs - the Python environment can’t do this directly and as such you have to specify the object type.

In particular this is a must for Lists as Python is a dynamically typed language. By that I mean that any variable can be if any data type and Python just implies what type of object it is based on the context. It also means the object type for a variable can change. This is valid code in Python (you can run it in Dynamo and check the console to confirm).

x = 1
x = “one”

However in C# in order to define x you need to provide an object type, and x will always be of that type (ok always might be an exaggeration, but generally that’s the case). This code will error out in C# as you can’t assign the string “one” to the variable X as it’s already an integer.

int x = 1;
x = “one”;

This explicit typing in C# extends beyond individual objects, but to collections as well. But Python doesn’t have typing, and so if the C# method is expecting a list of element ids but you provide it with a Python list the C# method will fail. And so to get a list of element IDs we need to use a typed list, which means we need the .NET list class, so we import it into the Python environment and create a typed list - hence converting your genericly typed Python list into a typed list in line 20.

Well, you don’t have elements, you have Element IDs, right? And since you want to modify the element, not the element ID we need to get to that. To tell what you have (and what you can do with it), isolating one of the objects, inspecting it, and sending that to the Dynamo environment can help. Assuming you have one object as a variable named obj, this can help with that:
OUT = obj.__class__, dir(obj).
As far as ‘how to get an element from an element ID’… well knowing it is the easy way. Asking is likely the next best. Getting objects from the document is one of those ‘early lesson’ things.

1 Like

Thank you all for the time you’ve spent explaining these ideas! I’ll be processing this info and I’ll come back with any other related questions I may run into.

You can provide an input to the node, not use it and change it. That will tell the selection node and its downstream to re execute. A classic input for this is a boolean toggle.

For reference, check out my node revit.selection in Crumple package. I use a refresh input in any node that has no input to ensure users can refresh it for that reason.

1 Like

If you go to apidocs.co there is a code search tool that includes python examples
Just click the angle brackets
image
And look for files with the .py extension. Click on the file name and you will open the repository. (Some times the link may be broken if the repo has changed)

2 Likes