Going Further With Zero-Touch
With an understanding of how to create a Zero-Touch project, we can go deeper into the specifics of creating a node by walking through the ZeroTouchEssentials example on the Dynamo Github.
Many of Dynamo's standard nodes are essentially Zero-Touch nodes, like most of the Math, Color, and DateTime nodes above.
To start, download the ZeroTouchEssentials project from here: https://github.com/DynamoDS/ZeroTouchEssentials
In Visual Studio, open the ZeroTouchEssentials.sln
solution file and build the solution.
The
ZeroTouchEssentials.cs
file contains all the methods we will be importing into Dynamo.
Open Dynamo and import the ZeroTouchEssentials.dll
to get the nodes we will be referencing in the following examples.
The code examples are pulled from and generally match ZeroTouchEssentials.cs. The XML documentation has been removed to keep them concise, and each code example will create the node in the image above it.
Default Input Values
Dynamo supports the definition of default values for input ports on a node. These default values will be supplied to the node if the ports have no connections. Defaults are expressed using the C# mechanism of specifying optional arguments in the C# Programming Guide. Default are specified in the following way:
- Set method parameters to a default value:
inputNumber = 2.0
namespace ZeroTouchEssentials
{
public class ZeroTouchEssentials
{
// Set the method parameter to a default value
public static double MultiplyByTwo(double inputNumber = 2.0)
{
return inputNumber * 2.0;
}
}
}
- The default value will show up when hovering over the node input port
Returning Multiple Values
Returning multiple values is a bit more complex than creating multiple inputs and will need to be returned using a dictionary. The dictionary's entries become ports on the output side of the node. Multiple return ports are created in the following way:
- Add
using System.Collections.Generic;
to useDictionary<>
. - Add
using Autodesk.DesignScript.Runtime;
to use theMultiReturn
attribute. This references "DynamoServices.dll" from the DynamoServices NuGet package. - Add the
[MultiReturn(new[] { "string1", "string2", ... more strings here })]
attribute to the method. The strings refer to keys in the dictionary and will become the output port names. - Return a
Dictionary<>
from the function with keys that match the parameter names in the attribute:return new Dictionary<string, object>
using System.Collections.Generic;
using Autodesk.DesignScript.Runtime;
namespace ZeroTouchEssentials
{
public class ZeroTouchEssentials
{
[MultiReturn(new[] { "add", "mult" })]
public static Dictionary<string, object> ReturnMultiExample(double a, double b)
{
return new Dictionary<string, object>
{ "add", (a + b) },
{ "mult", (a * b) }
};
}
}
}
Refer to this code example in ZeroTouchEssentials.cs
A node that returns multiple outputs.
Notice that there are now two output ports named according to the strings we entered for the dictionary's keys.
Documentation, Tooltips, and Search
It is best practice to add documentation to Dynamo nodes that describe the node's function, inputs, outputs, search tags, etc. This is done through XML documentation tags. XML documentation is created in the following way:
- Any comment text that is preceded by three forward slashes is considered to be documentation
- For example:
/// Documentation text and XML goes here
- For example:
- After the three slashes, create XML tags above methods that Dynamo will read when importing the .dll
- For example:
/// <summary>...</summary>
- For example:
- Enable XML documentation in Visual Studio by selecting
Project > Project Properties > Build
and checkingXML documentation file
Visual Studio will generate an XML file at the specified location
The types of tags are as follows:
/// <summary>...</summary>
is the main documentation for your node and will appear as a tooltip over your node in the left search side bar/// <param name="inputName">...</param>
will create documentation for specific input parameters/// <returns>...</returns>
will create documentation for an output parameter/// <returns name = "outputName">...</returns>
will create documentation for multiple output parameters/// <search>...</search>
will match your node with search results based on a comma separated list. For example, if we create a node that subdivides a mesh we may want to add tags such as "mesh", "subdivision", and "catmull-clark".
The following is an example node with input and output descriptions, as well as a summary that will display in the Library.
using Autodesk.DesignScript.Geometry;
namespace ZeroTouchEssentials
{
public class ZeroTouchEssentials
{
/// <summary>
/// This method demonstrates how to use a native geometry object from Dynamo
/// in a custom method
/// </summary>
/// <param name="curve">Input Curve. This can be of any type deriving from Curve, such as NurbsCurve, Arc, Circle, etc</param>
/// <returns>The twice the length of the Curve </returns>
/// <search>example,curve</search>
public static double DoubleLength(Curve curve)
{
return curve.Length * 2.0;
}
}
}
Refer to this code example in ZeroTouchEssentials.cs
Note that the code for this example node contains:
- A node summary
- An input description
- An output description
Objects
Dynamo doesn't have a new
keyword, so objects will need to be constructed using static construction methods. Objects are constructed in the following way:
- Make the constructor internal
internal ZeroTouchEssentials()
unless otherwise required - Construct the object with a static method such as
public static ZeroTouchEssentials ByTwoDoubles(a, b)
Note: Dynamo uses the "By" prefix to indicate a static method is a constructor, and while this is optional, using "By" will help your library better fit into the existing Dynamo style.
namespace ZeroTouchEssentials
{
public class ZeroTouchEssentials
{
private double _a;
private double _b;
// Make the constructor internal
internal ZeroTouchEssentials(double a, double b)
{
_a = a;
_b = b;
}
// The static method that Dynamo will convert into a Create node
public static ZeroTouchEssentials ByTwoDoubles(double a, double b)
{
return new ZeroTouchEssentials(a, b);
}
}
}
Refer to this code example in ZeroTouchEssentials.cs
After the ZeroTouchEssentials dll has been imported there will be a ZeroTouchEssentials node in the library. This object can be created by using the ByTwoDoubles
node.
Using Dynamo Geometry Types
Dynamo libraries can use native Dynamo geometry types as inputs and create new geometry as outputs. Geometry types are created in the following way:
- Reference "ProtoGeometry.dll" in the project by including
using Autodesk.DesignScript.Geometry;
at the top of your C# file and adding the ZeroTouchLibrary NuGet package to the project. - Important: Manage the geometry resources that are not returned out of your functions, see the Dispose/using Statements section below.
Note: Dynamo geometry objects are used like any other passed object to functions.
using Autodesk.DesignScript.Geometry;
namespace ZeroTouchEssentials
{
public class ZeroTouchEssentials
{
// "Autodesk.DesignScript.Geometry.Curve" is specifying the type of geometry input,
// just as you would specify a double, string, or integer
public static double DoubleLength(Autodesk.DesignScript.Geometry.Curve curve)
{
return curve.Length * 2.0;
}
}
}
Refer to this code example in ZeroTouchEssentials.cs
A node that gets a curve's length and doubles it.
This node accepts a Curve geometry type as an input.
Dispose/using Statements
Geometry resources that are not returned out of functions will need to be manually managed unless you are using Dynamo version 2.5 or later. In Dynamo 2.5 and later versions, geometry resources are handled by the system internally, however, you may still have to dispose geometry manually if you have a complex use case or you have to cut down on memory at a deterministic time. The Dynamo engine will handle any geometry resources that are returned out of functions. Geometry resources that are not returned can be handled manually in the following ways:
With a using statement:
using (Point p1 = Point.ByCoordinates(0, 0, 0)) { using (Point p2 = Point.ByCoordinates(10, 10, 0)) { return Line.ByStartPointEndPoint(p1, p2); } }
The using statement is documented here
See Dynamo Geometry Stability Improvements to read more about the new stability features introduced in Dynamo 2.5
With manual Dispose calls:
Point p1 = Point.ByCoordinates(0, 0, 0); Point p2 = Point.ByCoordinates(10, 10, 0); Line l = Line.ByStartPointEndPoint(p1, p2); p1.Dispose(); p2.Dispose(); return l;
Migrations
When publishing a newer version of a library, node names may change. Name changes can be specified in a migrations file so that graphs built on previous versions of a library continue to work properly when an update is made. Migrations are implemented in the following way:
- Create a
.xml
file in the same folder as the.dll
with the following format: "BaseDLLName".Migrations.xml - In the
.xml
, create a single<migrations>...</migrations>
element - Inside the migrations element, create
<priorNameHint>...</priorNameHint>
elements for each name change - For each name change, provide an
<oldName>...</oldName>
and<newName>...</newName>
element
- Right-click and select
Add > New Item
- Choose
XML File
- For this project, we would name the migrations file
ZeroTouchEssentials.Migrations.xml
This example code is telling Dynamo that any node named GetClosestPoint
is now named ClosestPointTo
.
<?xml version="1.0"?>
<migrations>
<priorNameHint>
<oldName>Autodesk.DesignScript.Geometry.Geometry.GetClosestPoint</oldName>
<newName>Autodesk.DesignScript.Geometry.Geometry.ClosestPointTo</newName>
</priorNameHint>
</migrations>
Refer to this code example in ProtoGeometry.Migrations.xml
Generics
Zero-Touch currently does not support the use of generics. They can be used, but not in the code that is directly imported where the type is not set. Methods, properties, or classes that are generic and don’t have the type set cannot be exposed.
In the example below, a Zero-Touch node of type T
will not be imported. If the rest of the library imports into Dynamo there will be missing type exceptions.
public class SomeGenericClass<T>
{
public SomeGenericClass()
{
Console.WriteLine(typeof(T).ToString());
}
}
Using a generic type with the type set in this example will import into Dynamo.
public class SomeWrapper
{
public object wrapped;
public SomeWrapper(SomeGenericClass<double> someConstrainedType)
{
Console.WriteLine(this.wrapped.GetType().ToString());
}
}