Python and Civil 3D

While Dynamo is extremely powerful as a visual programming tool, it is also possible to go beyond nodes and wires and write code in textual form. There are two ways that you can do this:

  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.

Take a look at the Python section for more general information about using Python in Dynamo.

API Documentation

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.

AutoCAD .NET API Developer's Guide

AutoCAD .NET API Reference Guide

Civil 3D .NET API Developer's Guide

Civil 3D .NET API Reference Guide

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.

Code Template

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.

  1. Imports the sys and clr modules, both of which are necessary for the Python interpreter to function properly. In particular, the clr module 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 using or Imports directives 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[0].

  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 OUT variable at the end of your script.

Want to customize? You can modify the default Python template by editing the file located in C:\ProgramData\Autodesk\C3D <version>\Dynamo.


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.

Solution Overview

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

Let's go!

Review API Documentation

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 Point3dCollection object, 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.

Get All Catchments

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.

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.

Unwrapping Objects

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 InternalDBObject or InternalObjectId properties.

Dynamo TypeWraps

Object Autodesk.AutoCAD.DynamoNodes.Object

Entity Autodesk.AutoCAD.DatabaseServices.Entity

CivilObject Autodesk.Civil.DynamoNodes.CivilObject

Entity Autodesk.Civil.DatabaseServices.Entity

As a rule of thumb, it is generally safer to get the Object ID using the InternalObjectId property and then access the wrapped object in a transaction. This is because the InternalDBObject property will return an AutoCAD DBObject that is not in a writable state.

Python Script

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
import sys
import clr

# 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 *

# The inputs to this node will be stored as a list in the IN variables.



adoc = Application.DocumentManager.MdiActiveDocument
editor = adoc.Editor

with adoc.LockDocument():
    with adoc.Database as db:
        with db.TransactionManager.StartTransaction() as t:
            # Commit before end transaction
# Assign your output to the OUT variable.

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.

Create PolyCurves

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.


And here's the final Dynamo geometry.

🎉 Mission accomplished!

IronPython vs. CPython

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.

Last updated