-
Notifications
You must be signed in to change notification settings - Fork 635
Zero Touch Plugin Development
This page outlines the process of developing a custom Dynamo node in C# using the "Zero Touch" interface
An example Visual Studio 2012 project can be found here: https://github.com/DynamoDS/ZeroTouchEssentials
Note 1: Make sure your Visual Studio project's "Platform target" is set to "Any CPU" or x64, and "Prefer 32-bit" is un-checked.
Note 2: If you are creating Geometry objects in your ZeroTouch nodes, see a VERY IMPORTANT note at the bottom.
In most cases, C# static methods and Classes can be imported without modification. If your library only needs to call functions, and not construct new objects, this can be achieved very easily with static methods. When Dynamo loads your DLL, it will strip off the namespace of your classes, and expose all static methods as nodes. For instance, we can create a node that takes a single number and multiplies it by 2 as follows:
namespace ZeroTouchExample
{
public class ZeroTouchExample
{
public static double MultByTwo(double inputNumber)
{
return inputNumber * 2.0;
}
}
}
A node will appear in the Dynamo menu called "MultByTwo" in the "ZeroTouchExample" category. It will have one input, called "inputNumber".
Dynamo can support having default values for input ports on the node. These are the values that will be supplied to the node if the ports aren't wired up.
They can be expressed with the C# way of specifying optional arguments (https://msdn.microsoft.com/en-us/library/dd264739.aspx).
public static double Pow(double inputNumber, double power = 2.0)
{
return Math.Pow(inputNumber, power);
}
Currently the default types that are supported are numbers, bools and Strings, like in C#. We're working on expanding the set of support items soon.
Accepting multiple inputs into a node is easy: just specify multiple parameters to your C# function. Returning multiple values from a node requires slightly more work. The first step is to reference "DynamoServices.dll" in your project, found in the Dynamo install location, and add using Autodesk.DesignScript.Runtime;
to the top of your C# script. The second step is to add the MultiReturn
attribute to your function. It should contain an array of strings that map 1-1 with the output ports. The names of the output ports match the attribute string names only if the node does not contain XML return tags with descriptions. Your function should then return a Dictionary<string, object> containing the returned values. The keys to your dictionary should exactly match the strings in your attribute and occur in the same order. The returned Dictionary also dictates how the node preview is displayed.
Example 1: This creates a new node that outputs two doubles, "add" and "mult" containing the addition and multiplication of the two input numbers.
using Autodesk.DesignScript.Runtime;
namespace ZeroTouchExample
{
public class ZeroTouchExample
{
[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) }
};
}
}
}
If the node has XML return tags for each returned value including descriptions, the output ports will match the XML returns tags instead.
Example 2: This node will display "thing one" and "thing two" in its output ports but it will show "thing 1" and "thing 2" in the node preview.
/// <summary>
/// The names of the output ports
/// match the XML returns tag only if they include descriptions.
/// Otherwise the output ports will match the attribute names.
/// The returned dictionary displayed in the node preview is displayed
/// in the order of its keys as specified in the MultiReturn attribute.
/// </summary>
/// <returns name="thing one">first thing</returns>
/// <returns name="thing two">second thing</returns>
[MultiReturn(new[] { "thing 1", "thing 2" })]
public static Dictionary<string, List<string>> MultiReturnExample2()
{
return new Dictionary<string, List<string>>()
{
{ "thing 1", new List<string>{"apple", "banana", "cat"} },
{ "thing 2", new List<string>{"Tywin", "Cersei", "Hodor"} }
};
}
Dynamo doesn't have a "new" keyword, so objects need to be constructed via static constructors. 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 ZeroTouchExample
{
public class MyLine
{
private double _x1;
private double _y1;
private double _x2;
private double _y2;
public static MyLine ByStartPointEndPoint(double x1, double y1, double x2, double y2)
{
_x1 = x1;
_y1 = y1;
_x2 = x2;
_y2 = y2;
}
public double Length()
{
return Math.sqrt(Math.Pow(x2 - x1, 2) + Math.Pow(y2 - y1, 2));
}
}
}
Dynamo libraries can also use native Dynamo geometry types as inputs, and create new geometry as outputs. The first step is to reference "ProtoGeometry.dll" in your project, and include "using Autodesk.DesignScript.Geometry;" at the top of your C# file. Dynamo geometry objects are then used like any other passed object to your functions. For example, we can clean up our MyLine example above by using Dynamo's native geometry types:
using Autodesk.DesignScript.Geometry;
namespace ZeroTouchExample
{
public class MyLine
{
private Autodesk.DesignScript.Geometry.Point _p1;
private Autodesk.DesignScript.Geometry.Point _p2;
public static MyLine ByStartPointEndPoint(Autodesk.DesignScript.Geometry.Point p1, Autodesk.DesignScript.Geometry.Point p2)
{
_p1 = p1;
_p2 = p2;
}
public double Length()
{
return Math.sqrt(Math.Pow(_p2.X- _p1.X, 2) + Math.Pow(_p2.Y - _p1.Y, 2));
}
}
}
It's best practice to add documentation to your Dynamo nodes. This is done through XML documentation tags. The main documentation for your node should be in the <summary>...</summary>
XML tags. This will appear as a tooltip over your node in the left search side bar. Documentation on specific input parameters should appear in the <param name="inputName">...</param>
XML tag.
You can specify the name of the output parameter with the <returns>...</returns>
XML tag. Add a "name" attribute with the output name to the tag (as shown belo). The tag should contain the tooltip of the output port. For multiple output zero touch methods, simply add more "returns" fields:
/// <returns name="foo">The first output</param>
/// <returns name="bar">The second output</param>
Control over how your node appears in the Dynamo search results is similarly controlled through the <search>...</search>
XML documentation tag. This tag should contain a comma-separated list of search terms that, if matched, will cause your node to appear in search results.
To get Dynamo to pickup the tags you must enable XML documentation generation through visual studio, this setting is in the Build tab of the project settings.
We can add documentation an search to our original example as follows:
namespace ZeroTouchExample
{
public class ZeroTouchExample
{
/// <summary>
/// This is an example node demonstrating how to use the Zero Touch import mechanism.
/// It returns the input number multiplied by 2.
/// </summary>
/// <param name="inputNumber">Number that will get multiplied by 2</param>
/// <returns name="outputNumber">The result of the input number multiplied by 2</param>
/// <search>
/// example, multiply, math
/// </search>
public static double MultByTwo(double inputNumber)
{
return inputNumber * 2.0;
}
}
}
As you publish newer versions of your library, you may want to change the names of ZeroTouch nodes. It is possible to specify these name changes in a migrations file so that graphs built on previous versions of your library don't break when you create an update.
Migration files should be named in the following format: BaseDLLName.Migrations.xml. For instance ProtoGeometry.dll, Dynamo's core geometry library, has a migration file named ProtoGeometry.Migrations.xml. The file needs to be located in the same directory as the base dll.
The migrations file should be valid XML. The file should contain one "migrations" element, containing several "priorNameHint" elements. Each prior name element should have one "oldName" element, and one "newName" element. Old name should contain the complete name, including namespace, of the previous method name. newName should contain the new name, also including complete namespace. See ProtoGeometry.Migrations.xml for a template migrations file.
Note that in its current state, the migrations file gives hints to Dynamo about up how to handle missing nodes in a DLL, but doesn't specify in absolute terms a mapping between versions. So, for instance, if you "depreciate" a node name, then begin re-using the name in a subsequent version, the migration will likely fail.
If you are using the Dynamo geometry library in your C# plugins older than the Dynamo 2.5 version, you'll need to manually manage the geometry resources created in your functions which are not returned out of your functions. Any resources returned out of your functions will be managed by the Dynamo engine. There are two ways to do this, a "using" statement, or manual Dispose() calls. The using statement is documented here.
IF YOU DO NOT IMPLEMENT THIS YOU WILL PUT BOTH DYNAMO AND REVIT INTO AN UNDEFINED STATE CAUSING BOTH TO MYSTERIOUSLY CRASH!!!
Here are two examples:
With using
using (Point p1 = Point.ByCoordinates(0, 0, 0))
{
using (Point p2 = Point.ByCoordinates(10, 10, 0))
{
return Line.ByStartPointEndPoint(p1, p2);
}
}
With Dispose
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;
Note: You don't need to manually manage the resources if you are using Dynamo version 2.5 or later, unless you have a complex use case, which may still require manual disposal. You can read more about this feature here in extensive detail. See Dynamo Geometry Stability Improvements.
So short answer is no, not currently in the zero touch importer, but you can of course use generics in your code, just not in the code that is directly imported where the type is not set.
so if you try to write a zero touch node of type T
, like
public class SomeGenericClass<T>
{
public SomeGenericClass()
{
Console.WriteLine(typeof(T).ToString());
}
}
this will NOT be imported - but the rest of the library should be, so you will get missing type exceptions
If you use a generic type with the type set like:
public class SomeWrapper
{
public object wrapped;
public SomeWrapper(SomeGenericClass<double> someContrainedType)
{
Console.WriteLine(this.wrapped.GetType().ToString());
}
}
this second class should import fine. You wouldn’t be able to expose any methods, properties, or classes, that are generic and don’t have the type set.
This constructor would be kind of useless though since you can’t construct the SomeGenericClass
object - you would need another method that outputs that type.
Looking for help with using the Dynamo application? Try dynamobim.org.
- Dynamo 2.0 Language Changes Explained
- How Replication and Replication Guide work: Part 1
- How Replication and Replication Guide work: Part 2
- How Replication and Replication Guide work: Part 3