C# For Revit Dynamo - Zero Touch
C# For Revit Dynamo - Zero Touch
Class Description
Have you ever thought about coding but never tried because you think you can’t do
it? Are you an experienced Dynamo user but find yourself at the limits of visual
programming? Or maybe you're an IronPython wiz who feels strong-typed languages
are out-of-reach.
If the above resonates then you should attend this lab, because in 75 minutes these
barriers will be broken. You will learn how to develop Zero Touch nodes for Dynamo in
C# and set yourself up on a path to develop relevant and highly-prized skills for 21
Century construction professionals. Hosted by Thomas Mahon, a computational
design/BIM expert and creator of BimorphNodes, one of Dynamo's most popular
packages - you will learn from one of the most experienced developers in the Dynamo
community. By the end of the lab, delegates will be able to apply this knowledge to
their existing workflows, and enter the top tier of developers capable of controlling
Dynamo for Revit without limitation!
Why learn Zero Touch? There are numerous reasons to create Zero Touch nodes in C#:
Fully integrated in an IDE (Visual Studio), providing IntelliSense and debugging
which make coding easier than any other option
Speed - Zero Touch nodes execute rapidly making them ideal for complex
problems on large projects
Full access to the Revit API, Dynamo API and ability to communicate with
external applications
Code is more secure and easier to protect if IP is a concern
Page 2 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Page 3 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
PREREQUISITES
Before beginning the lab, ensure the following software is installed:
1. Revit 2018.3
2. Dynamo 2.0 (http://dynamobim.org/download/)
3. Visual Studio Community 2017 – Use the Visual Studio Installer and select/install
the .NET Desktop Development Workload
(https://visualstudio.microsoft.com/vs/community/)
4. Revit Lookup (https://github.com/jeremytammik/RevitLookup)
Page 4 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Page 5 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Solution Explorer (top right) lets you view, navigate, and manage your code files.
Solution Explorer can help organize your code by grouping the files into solutions and
projects.
The editor window (center), where you'll likely spend a majority of your time, displays file
contents. This is where you can edit code or design a user interface such as a window
with buttons and text boxes.
Page 6 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
The Output window (bottom center) is where Visual Studio sends notifications such as
debugging and error messages, compiler warnings, publishing status messages, and
more. Each message source has its own tab.
Team Explorer (bottom right) lets you track work items and share code with others using
version control technologies such as Git and Team Foundation Version Control (TFVC).
To rename the class and ensure all references are updated, click on the Class1.cs file in
the Solution Explorer panel, and below, in the Properties panel, enter HelloWorld into
the File Name property. Ensure the class is marked public otherwise it won’t be visible in
the Dynamo when it is imported:
Page 7 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
When you rename a class in this fashion, the following warning message appears. Click
Yes to accept the change:
Page 8 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
2. ENVIRONMENT CONFIGURATION
Step 1 Build Configuration
Once the project is set-up, it needs to be configured to automate actions that aid with
the development of Zero Touch custom node packages. The first step is to associate an
external application to our project for the purposes of debugging and testing. Since
Dynamo is dependent on Revit to operate, the external application needs to be set to
Revit.exe. To do this, click on the Project tab > ZeroTouchNodes Properties, from the
menu. A new panel will open displaying the project properties.
Click on the Build tab on the left-hand side and select x64 from the drop-down next to
Platform Target. This step is required whenever referencing a library built on the x64
platform, such as the Revit API:
Next, click on the Debug tab and activate the ‘Start External Program’ radio button.
Click on the square browse button next to the input field, and browse to your Revit root
folder (typically this is C:\Program Files\Autodesk\Revit 2018) and locate/select the
Revit.exe file. Click OK to select it and assign it to the external program setting:
Page 9 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
To ensure Revit launches when we run the application, click on the Tools tab > Options.
In the Options dialog, expand the Debugging tab and click on General. Find ‘Use
Managed Combability Mode’ and ensure it is checked:
Page 10 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Click on Tools > Options > Project Solutions > Build and Run, ensure ‘Always build’ option
is selected from the drop-down under ‘On Run, when projects are out of date’:
Page 11 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Next, right-click the ZeroTouchNodes solution file in the Solution Explorer again, and click
on Edit ZeroTouchNodes.csproj from the drop-down menu. The csproj file will be
displayed. Scroll down to the bottom of the file and copy and paste the XML below
before the closing </project> tab:
<Target Name="AfterBuild">
<!--Copy the package to the Dynamo package root directory-->
<GetReferenceAssemblyPaths TargetFrameworkMoniker=".NETFramework, Version=v2.0">
<Output TaskParameter="FullFrameworkReferenceAssemblyPaths"
PropertyName="FrameworkAssembliesPath" />
</GetReferenceAssemblyPaths>
<GetAssemblyIdentity AssemblyFiles="$(OutDir)$(TargetName).dll">
<Output TaskParameter="Assemblies" ItemName="AssemblyInfo" />
</GetAssemblyIdentity>
<ItemGroup>
<SourceDlls Include="$(TargetDir)*.dll" />
<SourcePdbs Include="$(TargetDir)*.pdb" />
<SourcePdbs Include="$(TargetDir)*.pdb" />
<SourceXmls Include="$(TargetDir)*.xml" />
<SourcePkg Include="pkg.json" />
</ItemGroup>
<RemoveDir Directories="$(AppData)\Dynamo\Dynamo Revit\2.0\packages\$(ProjectName)\bin" />
<Copy SourceFiles="@(SourceDlls)" DestinationFolder="$(AppData)\Dynamo\Dynamo
Revit\2.0\packages\$(ProjectName)\bin\%(RecursiveDir)" />
<Copy SourceFiles="@(SourcePkg)" DestinationFolder="$(AppData)\Dynamo\Dynamo
Revit\2.0\packages\$(ProjectName)\" />
<Copy SourceFiles="@(SourcePdbs)" DestinationFolder="$(AppData)\Dynamo\Dynamo
Revit\2.0\packages\$(ProjectName)\bin\" />
Page 12 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
The code snippet automates the creation of the folders Dynamo requires all custom
node packages include (bin, dyf and extra), as well as the copying all dll, pdb, xml and
json files found in the bin folder of the ZeroTouchNodes project to the Dynamo root
package folder whenever we compile. It targets Dynamo v2.0, however any version
can be targeted by changing the version number in the directory addresses to the
version you have installed.
Save the change by clicking on the save icon and re-open the solution by right-clicking
on the project in the Solution Explorer, and select Reload Project:
Page 13 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
The JSON file can be created from a new text file (txt). Copy the JSON code below and
paste it into the text file using any text editor. It includes all the attributes Dynamo
requires for custom packages, including the reference to our new ZeroTouchNodes
library in the node_libraries in the root folder of the ZeroTouchNodes Visual Studio
project and copy/paste the code shown below. Rename the extension of the file to
json and ignore any Windows warning about changing file extensions.
The ZeroTouchNodes package will comprise a single dll named ZeroTouchNodes and
the node_libraries property of the json file must point to this file otherwise Dynamo will
not load the dll and no nodes will appear in the library:
Page 14 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
{
"license": "MIT",
"file_hash": null,
"name": "ZeroTouchNodes",
"version": "1.0.0",
"description": "A collection of Zero Touch Dynamo nodes.",
"group": "",
"keywords": [ "ZeroTouch", "dynamo" ],
"dependencies": [],
"contents": "Zero Touch Nodes - A collection of simple Zero Touch Nodes Dynamo nodes.",
"engine_version": "1.0.0",
"engine": "dynamo",
"engine_metadata": "",
"site_url": "",
"repository_url": "",
"contains_binaries": true,
"node_libraries": [
"ZeroTouchNodes, Version=1.0.0, Culture=neutral, PublicKeyToken=null"]
}
For convenience, add the json file to the Visual Studio project so edits can easily be
made. To do this, right-click on the ZeroTouchNodes solution file in the Solution Explorer,
and click Add > Existing Item, (ensure all file types is select from the browse window file
types drop-down) then selection and open the JSON file:
Page 15 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
The environment configuration of the project is complete. Now it is time to start creating
new custom nodes. In the next step, we look at creating new constructors, methods
and properties of the HelloWorld class.
Page 16 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
3. KEY C# CONCEPTS
C# Syntax Introduction
Before we begin writing our first custom Zero Touch node, it is important to understand
the basics of C# syntax, the architecture of a simple program (in this case, the
‘program’ is our custom nodes) and key C# concepts as this will make writing our nodes
in the next few exercises easy to follow and understand.
Note that the explanations and concepts introduced throughout this document are
orientated specifically for introductory-level custom node development; details which
are not relevant (but important to know) have therefore been omitted. Delegates are
therefore encouraged to continue developing their understanding of C# concepts
following the lab.
This differs from the assignment of reference type variables, which copies a
reference to the object but not the object itself (a reference to the object, and
the object itself, are both allocated memory addresses).
Further reading: Research the memory ‘heap’ and ‘stack’ to understand how
value and reference types are stored and accessed.
2. Keywords
Keywords are predefined, reserved identifiers that have special meanings to the
compiler. They cannot be used as identifiers in your program. Fortunately, Visual
Studio will highlight keywords to aid you as you write code to avoid any potential
Page 17 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
3. Modifiers
Modifiers are used to modify declarations of types and type members. Zero touch
node development typically requires the use of the static modifier if your class
returns objects different from its own type. In most cases, it’s the only modifier
(excluding Access Modifiers) you’ll need to use.
a. Public – Access is not restricted. You can call public members or types
anywhere in your program. So can any external application, i.e. Dynamo
b. Private – Access is limited to the containing type. You can only call
members or types within their scope (typically the class). External
applications cannot access nor call private members or types.
5. Statements
The actions that a program takes are expressed in statements. Common actions
include declaring variables, assigning values, calling methods, looping through
collections, and branching to one or another block of code, depending on a
given condition. The order in which statements are executed in a program is
Page 18 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
called the flow of control or flow of execution. The flow of control may vary every
time that a program is run, depending on how the program reacts to input that it
receives at run time.
static void Main()
{
// Declaration statement.
int counter;
// Assignment statement.
counter = 1;
// Error! This is an expression, not an expression statement.
// counter + 1;
// Declaration statements with initializers are functionally
// equivalent to declaration statement followed by assignment statement:
int[] radii = { 15, 32, 108, 74, 9 }; // Declare and initialize an array.
const double pi = 3.14159; // Declare and initialize constant.
// foreach statement block that contains multiple statements.
foreach (int radius in radii)
{
// Declaration statement with initializer.
double circumference = pi * (2 * radius);
// Expression statement (method invocation). A single‐line
// statement can span multiple text lines because line breaks
// are treated as white space, which is ignored by the compiler.
System.Console.WriteLine("Radius of circle #{0} is {1}. Circumference = {2:N2}",
counter, radius, circumference);
// Expression statement (postfix increment).
counter++;
} // End of foreach statement block
} // End of Main method body.
6. Using directive
The using directive has three uses:
Page 19 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
b. To allow you to access static members and nested types of a type without
having to qualify the access with the type name:
using static System.Math;
The scope of a using directive is limited to the file in which it appears and can
appear:
7. Dot operator
The dot operator (.) is used for member access. The dot operator specifies a
member of a type or namespace. For example, the dot operator is used to
access specific methods within the .NET Framework class libraries
Line dynamoLine = Line.ByStartPointEndPoint(startPoint, endPoint);
It is used use to access members of classes (and class instances), such as its
properties:
double lineLength = dynamoLine.Length;
And the dot is used to form qualified names, which are names that specify the
namespace or interface, for example, to which they belong:
Autodesk.Revit.DB.Wall wall = Autodesk.Revit.DB.Wall.Create(document, profile, false);
8. Comments
Always add comments throughout your code to document the purpose of your
program and provide more meaning. All good developers comment their code –
it makes it easier for you and others to comprehend the logic, flow and structure
of your program. The C# compiler ignores comments, so you can include them
anywhere in your program without any adverse effects.
Page 20 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
A multi-line comment can be achieved by containing the text within and forward
slash and asterisk /* and terminates with an asterisk and forward slash:
/*Get the total length of the ProtoGeometry Line
The length is measured in mm*/
double lineLength = dynamoLine.Length;
1. Namespace
A namespace is essentially a container with a meaningful name which the
developer can use to add more legibility and structure to their program by grouping
related classes together. For example, our project includes a namespace called
ZeroTouchNodes, and within this namespace, the HelloWorld class is declared.
namespace ZeroTouchNodes
{
}
2. Class
Classes are one of the basic constructs of the common type system in the .NET
Framework. It is essentially a data structure that encapsulates a set of data and
behaviours that belong together as a logical unit. The data and behaviours are the
members of the class, and they include its methods, properties, and events, and so
on.
Page 21 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
A class declaration is like a blueprint that is used to create instances or objects at run
time. If you define a class called HelloWorld, HelloWorld is the name of the type.
Multiple instances of the same HelloWorld type can be created, and each instance
can have different values in its properties and fields.
A class is a reference type. When an object of the class is created, the variable to
which the object is assigned holds only a reference to that memory. When the
object reference is assigned to a new variable, the new variable refers to the
original object. Changes made through one variable are reflected in the other
variable because they both refer to the same data.
At run time, when you declare a variable of a reference type, the variable contains
the value null until you explicitly create an instance of the class by using the new
operator, or assign it an object of a compatible type that may have been created
elsewhere.
a. Declaring a Class
Classes are declared by using the class keyword followed by a unique
identifier as shown in the following example.
Tip: your classes (i.e. your custom nodes) should use the public access
modifier if they need to appear in the Dynamo node library after Zero Touch
import.
public class HelloWorld
{
}
The class keyword is preceded by the access level. Because public is used in
this case, anyone can create instances of this class. The name of the class
follows the class keyword. The name of the class must be a valid C# identifier
name. The remainder of the definition is the class body between the open ‘{‘
and closed ‘}’ curly brackets, where the behaviour and data are defined.
Fields, properties, methods, and events on a class are collectively referred to
as class members.
Page 22 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
b. Creating Objects
Although they are sometimes used interchangeably, a class and an object
are different things. A class defines a type of object, but it is not an object
itself. An object is a concrete entity based on a class, and is sometimes
referred to as an instance of a class.
Objects can be created by using the new keyword followed by the name of
the class that the object will be based on, like this:
HelloWorld hello = new HelloWorld("Hello BILT Euro 2018");
3. Constructor
Whenever a class is created, its constructor is called. A class may have multiple
constructors that take different arguments. Constructors enable the programmer to
set default values, limit instantiation, and write code that is flexible and easy to read.
a. Default Constructors
If you don't provide a constructor for your class, C# creates one by default
that instantiates the object and sets member variables to the default values
as listed in the Default Values Table.
Tip: Zero Touch import will convert default constructors into a node visible in
the Dynamo library. To prevent this, you should always declare a default
constructor explicitly and change its access level to private:
namespace ZeroTouchNodes
{
public class HelloWorld
{
//Default constructor set to private
private HelloWorld() { }
}
}
b. Constructor syntax
A constructor is a method whose name is the same as the name of its type. Its
method signature includes only the method name and its parameter list ,
which is always encased in curved brackets; it does not include a return type.
The following example shows the constructor for our HelloWorld class:
namespace ZeroTouchNodes
{
public class HelloWorld
Page 23 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
{
private string message;
//HelloWorld constructor
public HelloWorld(string myMessage)
{
message = myMessage;
}
}
}
namespace ZeroTouchNodes
{
public class HelloWorld
{
//HelloWorld field
private string message;
private int length;
//Property which
public int Length
{
get
{
return length;
}
}
//A constructor ‐ 'creates' and instance of HelloWorld class
public HelloWorld(string myMessage)
{
message = myMessage;
length = myMessage.Length;
}
//A method (And member of) MyClass
public static string Concatenate(HelloWorld helloWorld, string otherMessage)
{
return helloWorld.message + " " + otherMessage;
}
}
}
Page 24 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Syntax of a Method
The following diagram summarizes the basic syntax structure of a method. Dynamo Zero
Touch import will convert public class members, which include methods, into nodes.
The modifiers are both optional; without any access modifier for example, the method is
set to private by default. In cases where the method doesn’t return anything, replace
the return type with the void keyword:
`
Access
Modifier
Modifier Return
type
Method
name
+ Input
parameters
= Method
signature
public static string Concatenate(HelloWorld helloWorld, string otherMessage)
{
return helloWorld.message + " " + otherMessage;
}
Statement
body
Page 25 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Create a Constructor
The first step involves creating a default constructor. Applying the concepts learned in
the previous section, we need to ensure our class is public so it displays in the Dynamo
library after Zero Touch import, and we need to declare a default constructor following
the syntax, so our program should look like this once these steps are complete:
namespace ZeroTouchNodes
{
public class HelloWorld
{
public HelloWorld()
{
}
}
}
Note that as we’ve already configured our environment to export the library files and
custom node JSON file to the Dynamo root package folder, there are no intermediate
steps involved; our nodes should automatically appear in Dynamo’s node library.
Page 26 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Launch Dynamo (v2.0 since this is our target platform) and our ZeroTouchNode and
place our new HelloWorld node on the graph:
Page 27 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
You will see that our HelloWorld class constructor node is correctly returning an
instance of the HelloWorld class. However, right now it is not doing much and serves no
purpose. In the next step we will look at developing the constructors method signature
and create fields and properties in our class to store the values.
namespace ZeroTouchNodes
{
public class HelloWorld
{
public HelloWorld(string message)
{
}
}
}
Now the input is declared, the next consideration is what should happen with the
message once its input. If we leave the constructor in its current state, the message
does nothing as we haven’t told the compiler what to do with it.
This is where we can add a field to our class to store the input message value. We can
then access the value stored in the field whenever required using the ‘.’ (dot) operator.
Declare a new field above the constructor called _message. As this field is storing our
message, the type of this field must match, so precede the name with string to define
its type.
We also want to control its access level; typically fields are always set to private
following C# encapsulation standards, and our program will also adhere to this
principle. Our code should look like this once complete:
namespace ZeroTouchNodes
{
public class HelloWorld
{
Page 28 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
private string _message;
public HelloWorld(string message)
{
}
}
}
Next, we need to assign the message that’s input into the constructor to the _message
field. To do this we have to specify it in the statement body of our constructor using the
‘=’ (assignment) operator as shown below:
namespace ZeroTouchNodes
{
public class HelloWorld
{
private string _message;
public HelloWorld(string message)
{
_message = message;
}
}
}
The final step involves creating a public property so the field value is accessible at
every level, and crucially, shows as a node in the Dynamo library. Properties use the get
and set keywords and adhere to the following syntax:
namespace ZeroTouchNodes
{
public class HelloWorld
{
private string _message;
public string Message
{
get { return _message; }
set { message = value; }
}
public HelloWorld(string message)
{
_message = message;
}
}
}
Tip: properties can implement one or the other to restrict modification (set) or access
(get) to the field; just omit the one you don’t need. E.g.
public string Message
{
get { return _message; }
}
Page 29 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Once these steps are complete, compile the solution again and test the nodes in
Dynamo. If successful, you should now see two nodes in the library and our HelloWorld
constructor will include a string input called message:
Methods require a return type and optional modifiers. Since our method is querying the
message value for a substring, the return type is either true or false which are values of
the bool type. Hence, the return type of our method is bool.
Now we have established the method signature, declare the method within our class
with the name Contains() and precede the name with the return type bool.
namespace ZeroTouchNodes
{
public class HelloWorld
{
private string _message;
Page 30 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
public string Message
{
get { return _message; }
set { message = value; }
}
public HelloWorld(string message)
{
_message = message;
}
bool Contains()
{
}
}
}
If the access level of the method (or any member of class, or a class itself) is not
specified, its set to private by default. Since we want the method to show as a node in
our library after Zero Touch import, we need to declare the access level as public.
The method also requires a string input – the query string – so create an input the
method called substring preceded by string as the type. You method should now look
like this:
namespace ZeroTouchNodes
{
public class HelloWorld
{
private string _message;
public string Message
{
get { return _message; }
set { message = value; }
}
public HelloWorld(string message)
{
_message = message;
}
public bool Contains(string subString)
{
}
}
}
Next, we need to write the instructions in our method to perform the query action we
require. These instructions are declared within the statement body of the method
(within the two curly brackets below the method signature).
Page 31 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Since we are returning a bool value, we can declare a new bool variable, perform the
query on the HelloWorld Message property, then return the result. Declare a new bool
value called contains adhering to the same syntax rules we’ve already used in the
previous steps.
We are going to assign the result of the query to this value. We use = operator for
assignment and it’s the Message property we are querying, so we need to write the
following line in our method:
namespace ZeroTouchNodes
{
public class HelloWorld
{
//HelloWorld field
private string _message;
//HelloWorld property
public string Message
{
get { return _message; }
set { _message = value; }
}
//HelloWorld constructor
public HelloWorld(string message)
{
_message = message;
}
public bool Contains(string subString)
{
bool contains = Message
}
}
}
To perform the query we can utilise the Contains method from the .NET Common
Runtime Language (which returns a bool value) using the ‘.’ (dot) operator on the
Message property. The dot operator will display the IntelliSense list where we can
browse all the methods which can be called on the object the dot is used on. Locate
the Contains method and double click it to add it into your method:
Page 32 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
To complete this step, we need to input a string into the .NET Contains method. This
string is the same string input into our method, so pass subString into the curved brackets
of the method. Remember, in C#, statements terminate with a semi-colon:
namespace ZeroTouchNodes
{
public class HelloWorld
{
//HelloWorld field
private string _message;
//HelloWorld property
public string Message
{
get { return _message; }
set { _message = value; }
}
//HelloWorld constructor
public HelloWorld(string message)
{
_message = message;
}
public bool Contains(string subString)
{
bool contains = Message.Contains(substring);
}
}
}
Page 33 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Finally, our method needs to return the result of the query, otherwise the query executes
with the result we need but will be disposed (removed from memory) after execution
which isn’t very useful, as we need the result! Instead we can use the return statement
which outputs/returns a value or reference type from a method. Therefore, we need to
insert the return statement. The syntax is always return followed by the name of value
or reference to output; in our case, it’s the contains value:
namespace ZeroTouchNodes
{
public class HelloWorld
{
//HelloWorld field
private string _message;
//HelloWorld property
public string Message
{
get { return _message; }
set { _message = value; }
}
//HelloWorld constructor
public HelloWorld(string message)
{
_message = message;
}
//HelloWorld method
public bool Contains(string subString)
{
bool contains = Message.Contains(subString);
return contains;
}
}
}
Recompile the solution and test the result in Dynamo. You should see three nodes in the
ZeroTouchNodes library. If the new Contains method has been developed correctly,
you will see two inputs; one for a HelloWorld instance to query, and a another for the
string with which to search. The output from the node should be either true or false:
Page 34 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Tip: Do not create custom Zero Touch nodes if the same functionality already exists with
a single, or combination of, out-of-the-box nodes. Dynamo already includes a OOTB
node called String.Contains for example, and the HellowWorld.Message property can
be used to provide the input for this node (i.e. a combination of nodes achieves the
same end-goal). The reason then for adding a method called Contains to our
HelloWorld class is purely for the educational purposes of the lab – otherwise, don’t do
it!
Page 35 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Namespace and class names appear as
sub-categories in the Dynamo node library
namespace ZeroTouchNodes
{
public class HelloWorld
{
private string _message;
Properties appear as ‘Query’
nodes in the Dynamo node library
public string Message
{
get { return _message; }
set { _message = value; }
}
Constructors appear as ‘Create’
nodes in the Dynamo node library
public HelloWorld(string message)
{
_message = message;
}
Methods appear as ‘Action’
nodes in the Dynamo node library
public bool Contains(string subString)
{
bool contains = Message.Contains(subString);
return contains;
}
}
}
Page 36 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
In the dialog box that opens, select Class from the menu and Name it RevitWall.cs.
Click the Add button to add the new class to the project:
Page 37 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Once the RevitWall class is added to the project, ensure its access level is declared as
public so it is visible in the Dynamo node library after Zero Touch import.
Page 38 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
The second involves declaring the using directive so we don’t need to fully qualify the
API calls (i.e. declare the namespace to access the class). Following the guidance from
Section 3, add the using directive at the top of the RevitWall class document followed
by Autodesk.Revit.DB to use the primary namespace in the Revit API:
Page 39 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
To avoid copying all the Revit API dll’s into your project when you compile, click the
RevitAPI listing from the References list in the Solution Explorer and set the ‘Copy Local’
property in the Properties dialog to False:
Page 40 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Start by manually creating a wall in Revit, select it, and click Add-Ins tab > Revit Lookup
> Snoop Current Selection:
Page 41 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
A dialog appears which enables detailed interrogation of the element. We simply want
to know its class, which is listed at the top of the hierarchy tree on the left side of the
dialog – in this case, it’s the Revit API Wall class:
Page 42 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Now the class – or rather, the return type of the method – is identified, we can
complete the method signature declaration. Our code should look like this:
namespace ZeroTouchNodes
{
public class RevitWall
{
private RevitWall() { }
public Wall Create()
{
}
}
}
Page 43 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Locate the Wall class from this namespace and click it to displays its members in the
central dialog box:
We can see that there are a number of overloaded wall constructors; for the purposes
of the lab, we are going to use the simplest method, which takes a document, curve,
level Id, and boolean:
Add the constructor to your method and assign it to a new variable named newWall
adhering to C# syntax grammar. A convenient way of implementing a method from
the API is to copy/paste its name and signature from the description box directly into
your code then update its inputs accordingly:
namespace ZeroTouchNodes
{
public class RevitWall
{
private RevitWall() { }
public Wall Create()
{
Wall newWall = Wall.Create(Autodesk.Revit.DB.Document document, Autodesk.Revit.DB.Curve
curve, Autodesk.Revit.DB.ElementId levelId, bool structural);
}
}
}
Page 44 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Tip: The Wall.Create() method is static so it’s called on its type, not an instance, which
means the use of the new keyword is unneeded. Visual Studio will tell you if the method
is static on the first line of the method signature in the description box, enabling you to
determine how to call the member:
Page 45 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Create a new variable in our method called doc and use the static DocumentManager
class from RevitServices.dll to instantiate a new Document object. We can update the
first input of the Create() constructor from the API by passing the doc variable into the
first of its inputs:
namespace ZeroTouchNodes
{
public class RevitWall
{
private RevitWall() { }
public Wall Create()
{
Document doc = DocumentManager.Instance.CurrentDBDocument;
Wall newWall = Wall.Create(doc, Autodesk.Revit.DB.Curve curve, Autodesk.Revit.DB.ElementId
levelId, bool structural);
}
}
}
For the next input we need to create a Curve object. For this we will use the Revit API
Line class and its CreateBound() constructor, which takes two XYZ’s (Revit API
points/vectors) as inputs:
Page 46 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
We want to be able to control the length of this line (and therefore the length of the
new Wall) as an input to our method. Therefore, we need to add a new input called
lengthInFt of type double.
namespace ZeroTouchNodes
{
public class RevitWall
{
private RevitWall(double lenghtInFt) { }
public Wall Create()
{
Document doc = DocumentManager.Instance.CurrentDBDocument;
Line lnLocationCurve = Line.CreateBound(Autodesk.Revit.DB.XYZ endpoint1,
Autodesk.Revit.DB.XYZ endpoint2);
Wall newWall = Wall.Create(doc, Autodesk.Revit.DB.Curve curve, Autodesk.Revit.DB.ElementId
levelId, bool structural);
}
}
}
Tip: The Revit API uses decimal feet as its internal units system. This cannot be changed,
so if you prefer working in other unit systems (i.e. metric) or if your inputs are not decimal
feet, you will have to perform the units conversion within your code. It is also worth
mentioning for angles, it uses radians, not degrees.
Page 47 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
To satisfy the inputs for the Line.CreateBound() constructor, create two new XYZ objects
called ptStart and ptEnd above lnLocationCurve. Instantiate the new XYZ’s using the
new keyword, and assign them to these variables. Use the lengthInFt variable as the X
dimension of ptEnd:
namespace ZeroTouchNodes
{
public class RevitWall
{
private RevitWall() { }
public Wall Create(double lenghtInFt)
{
Document doc = DocumentManager.Instance.CurrentDBDocument;
XYZ ptStart = new XYZ(); //no inputs creates an XYZ at 0,0,0
XYZ ptEnd = new XYZ(lenghtInFt, 0.0, 0.0);
Line lnLocationCurve = Line.CreateBound(Autodesk.Revit.DB.XYZ endpoint1,
Autodesk.Revit.DB.XYZ endpoint2);
Wall newWall = Wall.Create(doc, Autodesk.Revit.DB.Curve curve, Autodesk.Revit.DB.ElementId
levelId, bool structural);
}
}
}
Pass the new XYZ’s into the Line.CreateBound() inputs to complete the instantiation of
the new Line object and pass the lnLocationCurve into the Wall.Create() methods
second input:
namespace ZeroTouchNodes
{
public class RevitWall
{
private RevitWall() { }
public Wall Create(double lenghtInFt)
{
Document doc = DocumentManager.Instance.CurrentDBDocument;
XYZ ptStart = new XYZ(); //no inputs creates an XYZ at 0,0,0
XYZ ptEnd = new XYZ(lenghtInFt, 0.0, 0.0);
Line lnLocationCurve = Line.CreateBound(ptStart, ptEnd);
Wall newWall = Wall.Create(doc, lnLocationCurve, Autodesk.Revit.DB.ElementId levelId, bool
structural);
}
}
}
There are two more inputs that need to be satisfied. The final input is not structural, so
we can simply input false as the wall doesn’t need to be structural. For the remaining
Page 48 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
input ‘levelId’ we want this to be an input into our method so the user can provide the
level. However, the ElementId type cannot be used as the type as it is a Revit API type,
none of which are supported by Dynamo. Instead, we can provide the level Id as an
integer value using Dynamo’s Element.Id node, and instantiate the ElementId object
within our method using the integer. Therefore, create a new input called levelId of the
type int:
namespace ZeroTouchNodes
{
public class RevitWall
{
private RevitWall() { }
public Wall Create(double lenghtInFt, int levelId)
{
Document doc = DocumentManager.Instance.CurrentDBDocument;
XYZ ptStart = new XYZ(); //no inputs creates an XYZ at 0,0,0
XYZ ptEnd = new XYZ(lenghtInFt, 0.0, 0.0);
Line lnLocationCurve = Line.CreateBound(ptStart, ptEnd);
Wall newWall = Wall.Create(doc, lnLocationCurve, Autodesk.Revit.DB.ElementId levelId,
false);
}
}
}
To provide the ElementId object, create a new variable of this type called
levelElementId and instantiate it using the new keyword with the ElementId constructor
which takes an int as an input. Input the levelId to the constructor, then pass
levelElementId into the ElementId input of the Wall.Create() method, to satisfy its inputs:
namespace ZeroTouchNodes
{
public class RevitWall
{
private RevitWall() { }
public Wall Create(double lenghtInFt, int levelId)
{
Document doc = DocumentManager.Instance.CurrentDBDocument;
XYZ ptStart = new XYZ(); //no inputs creates an XYZ at 0,0,0
XYZ ptEnd = new XYZ(lenghtInFt, 0.0, 0.0);
Line lnLocationCurve = Line.CreateBound(ptStart, ptEnd);
ElementId levelElementId = new ElementId(levelId);
Wall newWall = Wall.Create(doc, lnLocationCurve, levelElementId, false);
}
}
}
Page 49 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Page 50 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Modifiers are added immediately after access modifiers, so our method should look like
this once complete:
namespace ZeroTouchNodes
{
public class RevitWall
{
private RevitWall() { }
public static Wall Create(double lenghtInFt, int levelId)
{
Document doc = DocumentManager.Instance.CurrentDBDocument;
XYZ ptStart = new XYZ(); //no inputs creates an XYZ at 0,0,0
XYZ ptEnd = new XYZ(lenghtInFt, 0.0, 0.0);
Line lnLocationCurve = Line.CreateBound(ptStart, ptEnd);
ElementId levelElementId = new ElementId(levelId);
Wall newWall = Wall.Create(doc, lnLocationCurve, levelElementId, false);
return newWall;
}
}
}
Add a Transaction
There is an additional step we need to take before we can begin testing our method;
adding a transaction to our method. A transaction is a context required in Revit in order
to make any modifications to a model – this includes the creation or deletion of
elements. Without a transaction in our method, an exception will be throw as we are
attempting to modify the document (i.e. create a new wall).
We can utilise Dynamo’s RevitServices library again to declare the transaction. We use
its static TransactionManager class and use its EnsureInTransaction() method to start
the transaction. The transaction must be closed using the TransactionTaskDone()
method. The modifications to the document must be declared between these two
statements, as shown below:
namespace ZeroTouchNodes
{
public class RevitWall
{
private RevitWall() { }
public static Wall Create(double lenghtInFt, int levelId)
{
Document doc = DocumentManager.Instance.CurrentDBDocument;
Page 51 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
XYZ ptStart = new XYZ(); //no inputs creates an XYZ at 0,0,0
XYZ ptEnd = new XYZ(lenghtInFt, 0.0, 0.0);
Line lnLocationCurve = Line.CreateBound(ptStart, ptEnd);
ElementId levelElementId = new ElementId(levelId);
TransactionManager.Instance.EnsureInTransaction(doc);
Wall newWall = Wall.Create(doc, lnLocationCurve, levelElementId, false);
TransactionManager.Instance.TransactionTaskDone();
return newWall;
}
}
}
To utilise this class, we also need to add a using directive to point to the namespace
containing the TransactionManager class. Visual Studio can assist us here; simply hover
your cursor over the unknown class and click on ‘Show potential fixes’:
It will suggest adding the required directive, which you can confirm by clicking on the
suggestion:
Page 52 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Tip: It is possible to use the Revit API’s Transaction class for the same purpose, however
it is recommended to use the TransactionManager class from Dynamo’s RevitServices
library as It saves you the effort of having to dispose your objects (aka garbage
collection), plus it helps to keep both Revit and Dynamo in more consistent states.
The method we call to ‘wrap’ Revit elements into the Dynamo wrapper class is
ToDSType(). It takes a bool input, where true is used if the element exists in Revit, or
false if the element is being instantiated by our code. In our case, we are creating a
new wall element so we need to input false. The wrapper method is available in
Dynamo’s RevitNodes.dll library. Add the library by right-clicking on References in the
Solution Explorer and locate the library here: C:\Program Files\Dyna\Dynamo
Revit\2\Revit_2018:
Page 53 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Add the following using directive followed by the target namespace Revit.Elements to
the top of the RevitWall document:
You will notice Visual Studio now displays a warning of an ambiguous reference
between the Revit API Wall class and the Wall class in Dynamo’s Revit.Elements library.
We can resolve this quickly using an alias in our using directives. Aliases provide
convenient shorthand’s for the developer to use in their code to avoid having to fully
Page 54 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
qualify classes and resolve issues such as ambiguous references. The simplest solution is
to add a new alias to our using directives and qualify the intended Wall class – in our
case, it’s the Wall class from the Revit API – with the following syntax:
using Wall = Autodesk.Revit.DB.Wall;
We can now call ToDSType() on our newWall object. For convenience we can call it as
the wall element is returned from the method, as shown below.
namespace ZeroTouchNodes
{
public class RevitWall
{
private RevitWall() { }
public static Wall Create(double lenghtInFt, int levelId)
{
Document doc = DocumentManager.Instance.CurrentDBDocument;
XYZ ptStart = new XYZ(); //no inputs creates an XYZ at 0,0,0
XYZ ptEnd = new XYZ(lenghtInFt, 0.0, 0.0);
Line lnLocationCurve = Line.CreateBound(ptStart, ptEnd);
ElementId levelElementId = new ElementId(levelId);
TransactionManager.Instance.EnsureInTransaction(doc);
Page 55 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Wall newWall = Wall.Create(doc, lnLocationCurve, levelElementId, false);
TransactionManager.Instance.TransactionTaskDone();
return newWall.ToDSType(false);
}
}
}
ToDSType() also has another purpose; it creates a binding (known as ‘Element binding’)
between the element created in our code, and its representation in Revit. It essentially
creates a dependency between the node and the Revit element(s) it outputs, to
prevent duplicates – similar to a synchronisation between the two applications. To
illustrate this behaviour; if our method inputs change (eg. length or level changes), the
change modifies the same Wall instance. Without element binding, we would get a
new wall every time the node’s inputs change, or if the Dynamo file is closed/re-
opened and run! It is therefore essential to call ToDSType() on any Revit Element
instantiated in your code to prevent unwanted duplicates from appearing.
Page 56 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Line lnLocationCurve = Line.CreateBound(ptStart, ptEnd);
ElementId levelElementId = new ElementId(levelId);
TransactionManager.Instance.EnsureInTransaction(doc);
Wall newWall = Wall.Create(doc, lnLocationCurve, levelElementId, false);
TransactionManager.Instance.TransactionTaskDone();
return newWall.ToDSType(false);
}
}
}
Page 57 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Page 58 of 59
1.5 and 2.3 - Become a Dynamo Zero Touch Node Developer
in 75 Minutes
Gain expert knowledge on Dynamo’s Revit wrapper class, element binding, wrapping
and unwrapping, inheritance, garbage collection, interoperability techniques, data
management, custom package creation, its ProtoGeometry, RevitNodes + geometry
conversion libraries, in-depth C# skill development, XML documentation and more.
Page 59 of 59