Comment on page
Python and Civil 3D
- 1.Write DesignScript using a Code Block
- 2.Write Python using a Python node
This section will focus on how to leverage Python in the Civil 3D environment to take advantage of the AutoCAD and Civil 3D .NET APIs.
Both AutoCAD and Civil 3D have several APIs available that enable developers like you to extend the core product with custom functionality. In the context of Dynamo, it is the Managed .NET APIs that are relevant. The following links are essential to understanding the structure of the APIs and how they work.
As you move through this section, there may be some concepts that you are unfamiliar with, such as databases, transactions, methods, properties, etc. Many of these concepts are core to working with the .NET APIs and are not specific to Dynamo or Python. It is beyond the scope of this section of the Primer to discuss these items in detail, so we recommend frequently referring to the above links for more information.
When you first edit a new Python node, it will be pre-populated with template code to get you started. Here's a breakdown of the template with explanations about each block.
The default Python template in Civil 3D
- 1.Imports the
clrmodules, both of which are necessary for the Python interpreter to function properly. In particular, the
clrmodule enables .NET namespaces to be treated essentially as Python packages.
- 2.Loads the standard assemblies (i.e., DLLs) for working with the managed .NET APIs for AutoCAD and Civil 3D.
- 3.Adds references to standard AutoCAD and Civil 3D namespaces. These are equivalent to the
Importsdirectives in C# or VB.NET (respectively).
- 4.The node's input ports are accessible using a pre-defined list called
IN. You can access the data in a specific port using its index number, for example
dataInFirstPort = IN.
- 5.Gets the active Document and Editor.
- 6.Locks the Document and initiates a Database transaction.
- 7.This where you should place the bulk of your script's logic.
- 8.Uncomment this line to commit the transaction after your main work is done.
- 9.If you want to output any data from the node, assign it to the
OUTvariable at the end of your script.
Want to customize? You can modify the default Python template by editing the
PythonTemplate.pyfile located in
Let's work through an example to demonstrate some of the essential concepts of writing Python scripts in Dynamo for Civil 3D.
Get the boundary geometry of all Catchments in a drawing.🎯
Here are examples files that you can reference for this exercise.
Here's an overview of the logic in this graph.
- 1.Review the Civil 3D API documentation
- 2.Select all of the Catchments in the document by layer name
- 3."Unwrap" the Dynamo Objects to access the internal Civil 3D API members
- 4.Create Dynamo points from AutoCAD points
- 5.Create PolyCurves from the points
Before we start building our graph and writing code, it's a good idea to take a look at the Civil 3D API documentation and get a sense of what the API makes available to us. In this case, there's a property in the Catchment class that will return the boundary points of the Catchment. Note that this property returns a
Point3dCollectionobject, which is not something that Dynamo will know what to do with. In other words, we won't be able to create a PolyCurve from a
Point3dCollection, so eventually we'll need to convert everything to Dynamo points. More on that later.
Now we can start building our graph logic. The first thing to do is get a list of all the Catchments in the Document. There are nodes available for this, so we don't need to include it in the Python script. Using nodes provides better visibility for someone else that might read the graph (versus burying lots of code in a Python script), and it also keeps the Python script focused on one thing: returning the boundary points of the Catchments.
Getting all of the Catchments in the Document by layer
Note here that the output from the All Objects on Layer node is a list of CivilObjects. This is because Dynamo for Civil 3D doesn't currently have any nodes for working with Catchments, which is the whole reason why we need to access the API through Python.
Before we go further, we need to briefly touch on an important concept. In the Node Library section, we discussed how Objects and CivilObjects are related. To add a little more detail to this, a Dynamo Object is a wrapper around an AutoCAD Entity. Similarly, a Dynamo CivilObject is a wrapper around a Civil 3D Entity. You can "unwrap" an Object by accessing its
As a rule of thumb, it is generally safer to get the Object ID using the
InternalObjectIdproperty and then access the wrapped object in a transaction. This is because the
InternalDBObjectproperty will return an AutoCAD DBObject that is not in a writable state.
Here's the complete Python script that does the work of accessing the internal Catchment objects are getting their boundary points. The highlighted lines represent those that are modified/added from the default template code.
Click on the underlined text in the script for an explanation of each line.
# Load the Python Standard and DesignScript Libraries
# Add Assemblies for AutoCAD and Civil3D
# Import references from AutoCAD
from Autodesk.AutoCAD.Runtime import *
from Autodesk.AutoCAD.ApplicationServices import *
from Autodesk.AutoCAD.EditorInput import *
from Autodesk.AutoCAD.DatabaseServices import *
from Autodesk.AutoCAD.Geometry import *
# Import references from Civil3D
from Autodesk.Civil.ApplicationServices import *
from Autodesk.Civil.DatabaseServices import *
from Autodesk.DesignScript.Geometry import Point as DynPoint
# The inputs to this node will be stored as a list in the IN variables.
objs = IN
output = 
if objs is None:
sys.exit("The input is null or empty.")
if not isinstance(objs, list):
objs = [objs]
adoc = Application.DocumentManager.MdiActiveDocument
editor = adoc.Editor
with adoc.Database as db:
with db.TransactionManager.StartTransaction() as t:
for obj in objs:
id = obj.InternalObjectId
aeccObj = t.GetObject(id, OpenMode.ForRead)
if isinstance(aeccObj, Catchment):
catchment = aeccObj
acPnts = catchment.BoundaryPolyline3d
dynPnts = 
for acPnt in acPnts:
pnt = DynPoint.ByCoordinates(acPnt.X, acPnt.Y, acPnt.Z)
# Commit before end transaction
# Assign your output to the OUT variable.
OUT = output
As a rule of thumb, it is a best practice to include the bulk of your script logic inside a transaction. This ensures safe access to the objects that your script is reading/writing. In many cases, omitting a transaction can cause a fatal error.
At this stage, the Python script should output a list of Dynamo points that you can see in the background preview. The last step is to simply create PolyCurves from the points. Note that this could also be accomplished directly in the Python script, but we've intentionally put it outside the script in a node so that it is more visible. Here's what the final graph looks like.
The final graph
And here's the final Dynamo geometry.
The resulting Dynamo PolyCurves for the Catchment boundaries
Just a quick note here before we wrap up. Depending on which version of Civil 3D you are using, the Python node may be configured differently. In Civil 3D 2020 and 2021, Dynamo used a tool called IronPython to move data between .NET objects and Python scripts. In Civil 3D 2022, however, Dynamo transitioned to use the standard native Python interpreter (aka CPython) instead that uses Python 3. Benefits of this transition include access to popular modern libraries and new platform features, essential maintenance, and security patches.
You can read more about this transition and how to upgrade legacy scripts on the Dynamo Blog. If you want to keep using IronPython, then you will simply need to install the DynamoIronPython2.7 package using the Dynamo Package Manager.