Python 0.6.3 to 0.7.x Migration

Thomas Mahon edited this page Nov 7, 2016 · 21 revisions

Input Variable(s)

In the 0.6.3 environment, inputs to the Python Script node are accessed via the IN variable from inside of the Python script. In the case of the Python Node With Variable Inputs, each IN varable had the port index appended to the end, so the first input is accessed thru IN0, the second from IN1, and so on.

In 0.7.x, the Python Script node accepts a variable number of inputs. Unlike in 0.6.3, each input is no longer given it's own variable. Instead, they are all packed into a list and assigned to the single IN variable. You can access individual inputs by indexing: the first input is IN[0], the second is IN[1], and so on.

This allows you to do things like loop over all of the inputs, for example:

count = 0
for number in IN:
    count += number
OUT = count

If you are migrating from a single input Python Script node, you can simply add the following line to the beginning of your old script:

IN = IN[0]

This will allow the rest of your code to continue using the IN variable.

RevitAPI

To facilitate easier interaction between the RevitAPI and Dynamo, we have written a comprehensive library for interacting with Revit. This means that all of our Revit data types passed between Dynamo nodes are actually wrappers around the core data types; these wrappers help Dynamo remain in sync with Revit. These changes will affect old Python scripts written for 0.6.3.

Document and Application

The Revit Document/Application can be accessed via the Dynamo DocumentManager.

import clr

# Import DocumentManager
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager

doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
app = uiapp.Application

Elements

All Elements coming out of Dynamo Nodes are actually wrappers around core Revit Elements. Inside of Python, you can operate on these types directly by calling our nodes from inside of Python, which are all located in the Revit.Elements namespace:

import clr

# Import RevitNodes
clr.AddReference("RevitNodes")
import Revit
# Import types we need. Instead of listing individual types,
# you can do 'from Revit.Elements import *'
from Revit.Elements import CurveByPoints, ReferencePoint

import System

startRefPt = IN[0]
endRefPt = IN[1]
refPtArray = System.Array[ReferencePoint]([startRefPt, endRefPt])
OUT = CurveByPoints.ByReferencePoints(refPtArray)

If you would prefer to use the RevitAPI directly, you will need to unwrap the Element before operating on it, use our TransactionManager to ensure that you're operating inside of a RevitAPI Transaction, and wrap any Element you wish to return.

import clr

# Import RevitAPI
clr.AddReference("RevitAPI")
import Autodesk
from Autodesk.Revit.DB import ReferencePointArray

# Import DocumentManager and TransactionManager
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

# Import ToDSType(bool) extension method
clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)

# Unwrap
startRefPt = UnwrapElement( IN[0] )
endRefPt = UnwrapElement( IN[1] )

# Start Transaction
doc = DocumentManager.Instance.CurrentDBDocument
TransactionManager.Instance.EnsureInTransaction(doc)

# Make the CurveByPoints
arr = ReferencePointArray()
arr.Append(startRefPt)
arr.Append(endRefPt)
cbp = doc.FamilyCreate.NewCurveByPoints(arr)

# End Transaction
TransactionManager.Instance.TransactionTaskDone()

# Wrap
OUT = cbp.ToDSType(false)

Unwrapping

Wrapped Elements are located in the Revit.Elements namespace. All wrapped Elements extend the Revit.Elements.Element abstract class. This class provides a public property InternalElement which contains a reference to the underlying RevitAPI Element, of type Autodesk.Revit.DB.Element. Alternatively, Dynamo provides a convenience function UnwrapElement(element) function that accepts wrapped Elements or arbitrarily nested lists of wrapped Elements. If passed a non-Element it will simply return the object unmodified.

wrappedElement = IN[0]
unwrappedElement = UnwrapElement( wrappedElement )
# Now I can use 'unwrappedElement' with the RevitAPI

Wrapping

In order to interoperate with our Revit nodes, any raw Autodesk.Revit.DB.Element being returned from a Python script must be wrapped in a Revit.Elements.Element. This can be done by using the ToDSType(bool) extension method. The bool argument determines whether or not the Element is "Revit-owned." This distinction is important: Revit-owned Elements are not controlled by Dynamo, non-Revit-owned Elements are. Basically, if you are creating a new Element in your Python script, then you should not mark the wrapped Element as Revit-owned. If you are selecting an Element from the Document, then you should mark the wrapped Element as Revit-owned.

import clr

# Import ToDSType(bool) extension method
clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)

docPt = FetchRefPtFromDoc() #Fetch an existing ref pt (not a real method)
newPt = CreateNewRefPt()    #Create a new ref pt (not a real method)
OUT = [ 
    docPt.ToDSType(True), #Not created in script, mark as Revit-owned
    newPt.ToDSType(False) #Created in script, mark as non-Revit-owned
]

Units

Dynamo uses meters for length units and the Revit API uses feet for length units. This is true regardless of what the "user-facing" units are in either application. Dynamo's Geometry Conversion capabilities make it so you don't often have to think about this when converting Geometry. However, you will need to manually do a unit conversion when:

  • passing a length value to a Python node and then invoking the Revit API using those lengths
  • extracting a length from the RevitAPI and then returning that value from a Python node
metersToFeet = 0.3048
feetToMeters = 1 / metersToFeet 

# Convert a length from Dynamo to Revit API units
dynamoUnitsLength = someDynamoLengthFunction() 
revitUnitsAfterConvert = dynamoUnitsLength * metersToFeet 

# Convert a length from the Revit API to Dynamo units
revitUnitsLength = someRevitLengthFunction()
dynamoUnitsLengthAfterConvert = revitUnitsLength * feetToMeters

The latest Dynamo Build (1.+) for Revit no longer uses meters for length units. Instead, Dynamo units are derived from the active Revit document and this is problematic if units other than m are in use since the above example shows a hard coded conversion multiplier. A simple method for dynamically assigning the correct units conversion can be achieved by using the Revit API UnitUtils.ConvertFromInternalUnits() method:

getDocUnits = doc.GetUnits()
getDisplayUnits = getDocUnits.GetFormatOptions(UnitType.UT_Length).DisplayUnits
unitConversion = UnitUtils.ConvertFromInternalUnits(1, getDisplayUnits )

GeometryObjects

Revit Geometry (solids, points, curves, etc) are all kinds of GeometryObject's. All Geometry coming out of Dynamo Nodes are NOT Revit GeometryObject's, so they need to be converted when used with the Revit API. To make matters worse, Dynamo represents all Geometry in meters, while Revit uses feet. Dynamo's GeometryConversion facilities make this relatively painless.

To import the GeometryConversion tools, do this:

import clr

clr.AddReference("RevitNodes")
import Revit

# Import ToProtoType, ToRevitType geometry conversion extension methods
clr.ImportExtensions(Revit.GeometryConversion)

To convert a Revit GeometryObject to Dynamo's geometry system, use:

dynamoGeometry = revitGeometryObject.ToProtoType()

To convert a piece of Dynamo Geometry to a Revit GeometryObject, use:

revitGeometryObject = dynamoGeometry.ToRevitType()

A Revit XYZ is not a GeometryObject (unfortunately, there is such a thing as a Revit Point, which is handled correctly by ToProtoType()) and there is more than one way to convert an XYZ (i.e. convert it to a Vector or Point):

point = xyz.ToPoint()
vector = xyz.ToVector()
xyz = pointOrVector.ToXyz()

You can omit the unit conversion, by passing false as the argument to ToRevitType(), ToProtoType() ToXyz(), ToPoint(), or ToVector().

Here's an example:

import clr

# Import RevitAPI
clr.AddReference("RevitAPI")
import Autodesk

clr.AddReference("RevitNodes")
import Revit

# Import DocumentManager and TransactionManager
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

# Import geometry conversion extension methods
clr.ImportExtensions(Revit.GeometryConversion)

# Import Element wrapper extension methods
clr.ImportExtensions(Revit.Elements)

# Unwrap the Point, yielding a Revit XYZ in Revit unit system
xyz = IN[0].ToXyz() 

# Start Transaction
doc = DocumentManager.Instance.CurrentDBDocument
TransactionManager.Instance.EnsureInTransaction(doc)

# Create a Reference Point
refPt = doc.FamilyCreate.NewReferencePoint(xyz)

# End Transaction
TransactionManager.Instance.TransactionTaskDone()

# Wrap ReferencePoint Element
OUT = refPt.ToDSType(false)

Transactions

Dynamo provides its own Transaction framework for working with the RevitAPI. This means that your Python script will be executing in the context of an overall Dynamo Transaction.

If you are writing RevitAPI code that requires a Transaction, then you may use the Dynamo TransactionManager.

  • TransactionManager.EnsureInTransaction(): Initializes the Dynamo Transaction
  • TransactionManager.TransactionTaskDone(): Tells Dynamo that we are finished using the Transaction
  • TransactionManager.ForceCloseTransaction(): Tells Dynamo to commit the active Transaction. This is slower than TransactionTaskDone(), so only use it when you actually need to close the Transaction for your script to work.

For your scripting, any place where you would normally create a new RevitAPI Transaction and then call Transaction.Start(), you will instead use TransactionManager.EnsureInTransaction(). Any place where you would normally call Transaction.Commit(), you will instead use TransactionManager.TransactionTaskDone(). Any place where you actually definitely need the Transaction to end (sitations where you want to operate on the model after regeneration occurs), then you may use TransactionManager.ForceCloseTransaction().

import clr

# Import DocumentManager and TransactionManager
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

# Get the document
doc = DocumentManager.Instance.CurrentDBDocument

# "Start" the transaction
TransactionManager.Instance.EnsureInTransaction(doc)

# Create a reference point (requires a transaction)
refPt = doc.FamilyCreate.NewReferencePoint(XYZ(0, 0, 0))

# "End" the transaction
TransactionManager.Instance.TransactionTaskDone()

You may also use RevitAPI Sub-Transactions alongside (but not as a substitute for) the TransactionManager. Sub-Transactions will give you the ability to rollback your changes. There is no way to rollback the main Dynamo Transaction.

Passing Functions to Python

Currently, passing functions to Python scripts through Dynamo is not supported in 0.7.x. This capability will be returning some time in the future.

Passing Python Nodes as Functions

Currently, passing Python nodes to other nodes as functions is not supported in 0.7.x. This capability will be returning some time in the future.

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.