Dynamo Components: Part 2a – Zero Touch

I started documenting my process for developing Dynamo nodes a couple of weeks ago, and this post continues that by covering Zero Touch nodes. The addition of Zero Touch to Dynamo sparked a major push for people who had developed Grasshopper plugins to start porting them over to Dynamo, at least as far as it was appropriate. I came to the game a little later, but it did lead me to finally porting over my mapping plugin, Elk, as well as transitioning some other tools that LINE’s developed for projects in Grasshopper over to Dynamo.

Developing Zero Touch Nodes

The concept behind Zero Touch is that almost any library (DLL) could plausibly import into Dynamo, and Dynamo will parse through the library and look for public classes, methods, and properties which it transforms into nodes. This allows Dynamo to utilize a lot of existing libraries that were not necessarily made specifically for Dynamo, but can also be used to make Dynamo specific libraries. Because they’re compiled libraries, Zero Touch components can give you access to different set of programming tools and libraries than you would get with a CustomNode, even using python. However, most, if not all, things that I would want to do with a Zero Touch node could be done in Dynamo’s Python node, I just find this way a little easier. Beyond personal preferences, these nodes also benefit from being compiled, which while not absolutely secure it can at least mitigate unwanted modifications being made to the components.

For exploring Zero Touch nodes, I’m going to cover two problems, one dealing purely with Dynamo geometry, and one deal with Revit elements. This is to show the different requirements that you need if you’re doing something purely with Dynamo elements versus doing something with Revit. So this particular post will be covering a native Dynamo component, and I will follow up with another post on a Zero Touch Revit node.

Defining the Problem

As I mentioned in the previous post, the first step is having a problem you want to solve. While most of the geometry tools in Dynamo are quite good, I’ve found its Mesh tools to be a little lacking, so for the Dynamo-centric node I’m going to create a Mesh Join node to combine a list of Dynamo meshes into a single mesh.

Initial Hypothesis

I know that native Dynamo does not have a node to join meshes, so I thought I would at least try the MeshToolkit package as I’d read it added a lot of mesh capabilities to Dynamo. While MeshToolkit has considerably more capability than native Dynamo, it doesn’t appear have anything to Join meshes. It also has a serious caveat of being a completely different mesh object than the Dynamo mesh and doesn’t have a built-in converter to go between the two. Something that can be overcome, but considering it doesn’t have a mesh join function to begin with, I’ll just make one to be compatible with a typical Dynamo mesh.

MeshToolkit

Research

Before we can work on joining meshes, we need to know a little bit about what a mesh is and how the geometry is structured. The Mesh Elements subsection in the Dynamo Primer shows that meshes within Dynamo are formatted as Face-Vertex meshes. As these sources point out, a face-vertex mesh is just as a list of vertices (3d points), and a list of faces (pointers to specific vertices).

The Dynamo Primer also shows a useful example of creating a list of Vertices and Faces (replicated below) and generating a mesh from that. While the language used is DesignScript, the language native to Code Blocks, we can assume we’ll need to follow a similar function for creating a new mesh in C# as a Zero Touch node.

Dynamo Primer - Mesh Example

Knowing all of the above doesn’t necessarily tell me how to join meshes together, but I could certainly begin making some guesses. However, before I get too carried away with guesswork, I’m going to look at another program that I know has Mesh Join capability and is easy to view the object data, Grasshopper for Rhino. For just a quick glance at how mesh joining works, at least superficially, I created two primitives and plugged them both into the Mesh Join component. What this shows is that there’s probably not any major reworking of the meshes being done to join them. The combined vertex count and face count for the joined mesh is a simple addition of what the initial meshes contain.

Grasshopper_MeshJoin

Prototyping

To further verify that joining meshes is just a matter of combining the vertex and face lists, I set up another quick test in Grasshopper before I get to work developing the node. Knowing that the quantity of vertices for the joined mesh is identical to the quantities of the two primitive meshes added together, I made a quick comparison by deconstructing the primitive meshes and the joined mesh created from them. I then merged the list of vertices from the primitives to create a single list and measured the distance from each point in the merged mesh to each point in the two meshes. Calculating a total distance between pairs of vertices in the joined mesh versus my manually joined vertex list shows that there is no deviation between the points.

CompareMeshes

Knowing this it should be easy to go through our input list of meshes and append the vertex lists from those meshes into a single list, but what we should probably be careful of is the faces. Since each face is really just a set of indices that correspond to the position of a vertex in the list, we’ll need to modify all faces except for those in the first meshes faces. And to put a final nail in this exploration, here’s a quick comparison doing just that and comparing the joined mesh faces to the manually joined mesh faces.

Grasshopper_FaceComparison

I couldn’t find a native Grasshopper method to modify a MeshFace’s indices or build a new MeshFace to construct a new mesh, so I tested this by using the C# component from within Grasshopper. I won’t go into how these work, but the code is replicated below and the GH file should be in the Github repo.

private void RunScript(Mesh joinedMesh, Mesh boxPrimitive, Mesh spherePrimitive, 
    ref object joined, ref object merged)
{
    // Get a list of the MeshFace's from the joined mesh
    List<MeshFace> joinedFaces = joinedMesh.Faces.ToList();

    // Get the list of MeshFace's from the primitives
    List<MeshFace> boxFaces = boxPrimitive.Faces.ToList();
    List<MeshFace> sphereFaces = spherePrimitive.Faces.ToList();

    // Create a new list of MeshFace's and add the boxFaces to the list
    List<MeshFace> mergedFaces = new List<MeshFace>();
    mergedFaces.AddRange(boxFaces);

    // Iterate through the sphereFaces list and add the quantity of vertices from
    // the boxPrimitve mesh to each index in the MeshFace. Then add the modified 
    // MeshFace to the mergedFaces lsit.
    for(int i = 0; i < sphereFaces.Count; i++)
    {
        MeshFace face = sphereFaces[i];
        face.A = face.A + boxPrimitive.Vertices.Count;
        face.B = face.B + boxPrimitive.Vertices.Count;
        face.C = face.C + boxPrimitive.Vertices.Count;

        // If the face is a quad, add the vertex count, otherwise leave as is.
        if(face.IsQuad)
            face.A = face.A + boxPrimitive.Vertices.Count;

        // Add the modified face to the mergedFaces list.
        mergedFaces.Add(face);
    }

    // output the mesh faces.
    joined = joinedFaces;
    merged = mergedFaces;
}

Project Setup

With all of this, I should have a good indication of how to join meshes that follow the Face-Vertex mesh typology. I’ve seen a snippet of code thanks to the Dynamo Primer on how to generate the necessary mesh information in code (as DesignScript) and run some tests using a different geometry engine that allowed me to make comparisons and a judgement on how to proceed with a Mesh Join operation in Dynamo.

It is worth noting at this point that I am using Visual Studio Professional, which does have a cost for use. There is a Community Edition that is free and may be an option for some, but HKS is large enough that it doesn’t qualify. I could also just as easily use something like SharpDevelop, the same IDE that is used for Revit Macros and is open source and free. Whichever IDE you use should work, though some of the particulars may differ between programs.

Fire up Visual Studio and create a new project. Since I already have a project folder set up from the previous post, I’m just going to create the new C# project as a Class Library within there and give it a name. I’ll Uncheck the ‘Create new Git repository’ option as the folder it’s in is already set up as a Git repo.

ZeroTouchDS_CreateProj

This will create a very simple project with a single CS file named Class1.cs with just a little bit of code already in it.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ZeroTouchDS
{
    public class Class1
    {

    }
}

Before I go about trying to create a Dynamo node, I need to import some references to Dynamo. This can be done manually by adding references and navigating to the files, but there are several needed and its much easier to use the NuGet Package Manager from Tools > NuGet Package Manager > Manage NuGet Packages for Solution…

NuGet_Run

NuGet is similar to the Dynamo Package Manager, but instead of Dynamo node libraries it gives you access to .Net libraries and tools. Autodesk has put most of the necessary Dynamo libraries on NuGet to make it easier to get up and running with Zero Touch and even CustomUI nodes. Launching the NuGet package manager will probably pop up as a new tab in Visual Studio and will by default be on the Installed sub-section. Since I just started and have no packages installed, I’m going to switch to Browse and search for ‘DynamoVisualProgramming’ to find half a dozen packages for Dynamo. Each has their place, but the relevant one here is the ‘DynamoVisualProgramming.ZeroTouchLibrary. Make sure your project is checked on the right side and click Install to add the package to your project.

NuGet_Packages

There is also note at the bottom of the description for the ZeroTouchLibrary package indicating that it has a dependency of the DynamoServices package. This will automatically be added as well, as indicated by the Preview pop-up after you click Install. Click OK at this pop-up to finish the install process. This can also be verified by going back to the Installed section of the NuGet Package Manager and you’ll see those two packages.

NuGet_Packages_Review

Now that we’ve added the references via NuGet, you can look at the References section of the Solution Explorer. It will show four new references that were not there when the project was created, all of which came from the NuGet package installation. Select all four of them and change their Copy Local property to False. This can be done individually or by selecting the all four references (DynamoServices, DynamoUnits, NodeServices2, and ProtoGeometry) and changing the Copy Local property for all of them at once.

References_NoCopy

Developing the Node

Now that the references have been added, I can proceed to develop the node. The prototype made in Grasshopper is only appropriate for joining two meshes and can’t handle any more or less, so the structure of the actual Dynamo node will be a little different. Based on the research above, I know that I’ll need to loop through each of the meshes, adding the current instance’s vertices to a combined list, and then updating the mesh faces (IndexGroup) and adding them to a combined list of mesh faces for each iteration of the loop. Once that’s done I’ll be able to build a new mesh that joins all of the input meshes.

To start with our node, I’ll go to the lone Class1.cs file in this project and rename it to Meshes.cs. If it’s not already opened, open the CS file and start editing the using statements at the top. The actual statements needed will depend on the type of project being made, and for this simple project I really just need two references, one to the Dynamo geometry library (Autodesk.DesignScript.Geometry), and one for generic collections (using System.Collections.Generic) so that I have access to Lists. With more recent versions of Visual Studio, it will actually indicate which statements are not being used within the file by fading the text slightly, making it easier to identify which to remove. On the other hand, knowing what needs to be added is usually a matter of experience. Many of the standard Dynamo nodes seem to be loaded as Zero Touch, and there are examples in the DynamoDS github page that show how to set up a Zero Touch project. I typically start there and then modify as needed.

// The unnecessary 'using' statements have been removed and Dynamo specific
// ones have been added. What remains is System.Collections.Generic to have
// access to Lists, and Autodesk.DesignScript.Geometry which gives access to
// Dynamo geometry.
using System.Collections.Generic;
using Autodesk.DesignScript.Geometry;

While not an absolute, C# projects are generally organized so that you have one class per CS file. For ZeroTouch nodes it is usually a method within the class that represents a node, so one class may hold many nodes. The class is typically used to organize similar nodes together. In this example I have only one node that I’ll be making, but if you are planning several nodes it is worth thinking about how you wish to group them together. As we’ll see later, the class name is also representative of how the nodes are organized in the node library within Dynamo.

The next two important parts are the namespace, which should be somewhat unique to the project, and the name of the class. When I’m developing a new project I will typically start the namespace with my studio name, LINE, and then add one or two more categorizations separated by a decimal. Here I use the format <STUDIO>.<SOFTWARE>.<GROUP>. For the class name, since I’m making a node that works with Meshes I’m going to call this class Meshes. That way if I want to add additional mesh nodes I will do it within this class. When Dynamo is evaluating a DLL file to generate the necessary nodes, it only looks at classes and methods with the modifiers public and static, so this Meshes class needs to be declared as such.

namespace LINE.Dynamo.ZeroTouchDS
{
    public static class  Meshes
    {
    }
}

The class will be the last grouping member for the nodes and doesn’t itself represent a node. Nodes are typically created from Methods, chunks of code that are very much like Dynamo Nodes in that they accept inputs, process something, and then output something. Since I’m making a single node for now, I’ll just create a single method to add to my Meshes class. A method will typically include an accessibility modifier, a return type or output, and any inputs. Like the class that contains it, this method needs to be defined as public in order for Dynamo to see it, and should likewise be defined as static so that a node can be generated from it. Since we know our intention is to input a list of meshes and output a single mesh, that will dictate the method inputs and return type. A method in C# follows the format <MODIFIERS> <RETURN TYPE> <NAME>(<INPUTS>), and since we know the modifiers need to be ‘public static‘, that just leaves the return type which will be a Mesh (shortened from Autodesk.DesignScript.Geometry.Mesh), the node’s name ‘MeshJoin‘, and it will have an input of a list of Mesh objects (List<Mesh>, also shortened from Autodesk.DesignScript.Geometry.Mesh).

What is actually contained in the class is fairly similar to the prototype that we made in Grasshopper. It’s been fixed to manage a list of meshes rather than two specific meshes, and is using the appropriate Dynamo geometry instead of Rhino’s geometry, but generally it follows a similar layout. Some notable differences are that an index group stores each of it’s indices as uint instead of int datatypes, and since you cannot modify an IndexGroup’s indices once it’s been instantiated, we have to build new ones to use for our new joined mesh rather than passing along modifications of the existing ones.

/// <summary>
/// Join a list of meshes into a single mesh
/// </summary>
/// <param name="meshes">Meshes to Join</param>
/// <returns name="Mesh">Returns a single joined mesh</returns>
public static Mesh MeshJoin(List<Mesh> meshes)
{
    // Create lists to store the vertex points and index groups (faces)
    List<Point> vertices = new List<Point>();
    List<IndexGroup> faces = new List<IndexGroup>();

    foreach (Mesh m in meshes)
    {
        // Get the number of vertices before adding the new set to them
        // This count will be what we add to the mesh face indices to shift
        // it to the growing list of vertices.
        uint currentVertCount = (uint)vertices.Count;

        // Add the current mesh's vertices to the list
        vertices.AddRange(m.VertexPositions);

        // Iterate through the mesh faces, creating new ones with shifted indices
        foreach (IndexGroup f in m.FaceIndicies)
        {
            // Create a new IndexGroup (face) by adding the vertex count to each index
            IndexGroup updatedFace = null;
            if (f.Count == 3)
            {
                updatedFace = IndexGroup.ByIndicies(
                    f.A + currentVertCount, f.B + currentVertCount, f.C + currentVertCount);
            }
            else
            {
                updatedFace = IndexGroup.ByIndicies(
                    f.A + currentVertCount, f.B + currentVertCount, 
                    f.C + currentVertCount, f.D + currentVertCount);
            }

            // Add the mesh face to the growing list of mesh faces
            faces.Add(updatedFace);
        {
    }

    // Make sure we retrieved data before trying to build a new mesh
    if (vertices.Count > 0 && faces.Count > 0)
    {
        // Build a new mesh with the vertices and faces and return it
        Mesh mesh = Mesh.ByPointsFaceIndicies(vertices, faces);
        return mesh;
    }
    return null;
}
Testing the Nodes

Once that’s done the project can be compiled and loaded into Dynamo to verify that it functions as we expect. In most cases there’s a little back and forth as bugs are worked out, but considering I made this node ahead of time it should work as is. To load it into Dynamo, at least for testing, you can use the Add button at the bottom of the Node Library to import a library.

Dynamo_ImportLibrary

Selecting the compiled DLL file from the project will load it into Dynamo. This is an in-session load, so the next time you start up Dynamo the library won’t be loaded. However, if you open a file that uses a node from the library it will automatically load the zero touch library for you when the DYN file is loaded. When you load a Zero Touch library that doesn’t include a customization file, it will generate the structure of the node library according to the assembly name (DLL file name) and the namespace of the project. Every point along the way that you have a period in the namespace or assembly name will add another level to the nodes. So for my project, with an assembly named ‘LINE.Dynamo.ZeroTouchDS.dll’ and a namespace of ‘LINE.Dynamo.ZeroTouchDS’ that matches it, a ridiculous amount of branching is required to find the node.

Dynamo_NoCustom

Before I get around to resolving that, first it’s important to just make sure the node is functioning properly. To test it I’ve set up a Dynamo file that is similar to the Grasshopper prototype file I made. It uses the MeshToolkit package to create a couple of primitive meshes (a cube and sphere), converts those MeshToolkit meshes into Dynamo meshes, and then uses the MeshJoin component I’ve just developed.

Dynamo_MeshJoinTest

As I’d hoped, the MeshJoin node works exactly as it’s supposed to. Now that it’s confirmed to be working I can work on the customization side of things to clean up the node library organization and add an icon for the node.

Adding Icons

On the icon side of things, the process of adding icons to Zero Touch nodes is pretty well documented in Dynamo’s GitHub Wiki. Still, I found the Icons more troublesome than anything else related to the Zero Touch nodes. The documentation states that you should name the resource file as <Assembly Name>Images.resx, but that didn’t work for me. What I found out is that you cannot have any special characters in the name of the resource file. If you have periods or underscores it seems to throw it off. So instead of naming my resource file ‘LINE.Dynamo.ZeroTouchDSImages.resx’, I name it ‘LINEDynamoZeroTouchDSImages.resx’. Other than that, in the AfterBuild code that you’re supposed to add to your CSPROJ file, I also had to make changes to the the paths defined within the GerateResource and AL tags. Since the resource file I created was in my main project directory, and not in a Resources folder, I had to remove that part of the path so it was now ‘$(ProjectDir)LINEDynamoZeroTouchDSImages.resx’ (and .resources when appropriate). The final modification was in the OutputAssembly variable in the AL tag, I added the periods back into the file name that it would generate. So my output would be ‘LINE.Dynamo.ZeroTouchDS.customization.dll’.

<!-- Generate customization dll -->
<GenerateResource UseSourcePath="true" Sources="$(ProjectDir)LINEDynamoZeroTouchDSImages.resx" OutputResources="$(ProjectDir)LINEDynamoZeroTouchDSImages.resources" References="$(FrameworkAssembliesPath)System.Drawing.dll" />
<AL TargetType="library" EmbedResources="$(ProjectDir)LINEDynamoZeroTouchDSImages.resources" OutputAssembly="$(OutDir)LINE.Dynamo.ZeroTouchDS.customization.dll" Version="%(AssemblyInfo.Version)" />

The icons used in Zero Touch libraries don’t appear to have an option to use a single icon for every node in the library, something you can do when creating a Package.customization.dll file for Custom Nodes. So even if I want to use a single icon for everything, I will need to make copies of the image and name them appropriately for each node in the library. And a final note about Icons, the documentation says a small icon should be 32×32 pixels and a large icon should be 128×128 pixels. While by no means exhaustively studied, the icons in the default Dynamo nodes all appear to be 24×24 pixels in size. They may be a 24×24 icon saved into a 32×32 bitmap with lots of empty space, but I often just save mine as 24×24 and leave it at that. It appears to work fine with the smaller image and is more appropriately sized when than if you have a 32×32 icon that fills out the bitmap space.

Adding a Customization File

The last part I wanted to mention for the development is the ‘…_DynamoCustomization.xml’ file that you can add to your projeect. I didn’t find a lot of documentation on the customization file, but there were a couple of references to them in the Dynamo forum and Dynamo’s Issues page on GitHub. Even though it’s a little light on explanation, there’s not a whole lot going on with the file. There’s a section specify the assembly name, ‘LINE.Dynamo.ZeroTouchDS’ in my case, and the namespace, also ‘LINE.Dynamo.ZeroTouchDS’ for this project. And below the namespace, is a tag to specify the Category. This category is similar to what is seen in the Category section of the New Custom Node properties dialog. It lets you specify where in the library you want the node to appear by specifying the path, separated by periods. So following my previous Custom Node example, I’m going to place this in ‘LINE.Examples – ZeroTouchDS’.

<?xml version="1.0"?>
<doc>
    <assembly>
        <name>LINE.Dynamo.ZeroTouchDS</name>
    </assembly>
    <namespaces>
        <namespace name="LINE.Dynamo.ZeroTouchDS">
            <category>LINE.Examples - ZeroTouchDS</category>
        </namespace>
    </namespaces>
</doc>

I’ve just created this XML file by copying it from the example’s pointed to in the links above. Then I changed the relevant parts to match my assembly, namespaces, and category preference. The file was added to my zero touch project and I set the file to be copied to the output directory when the project is built. This way all of the files relevant to this Zero Touch library will all be in the same place. The compiler will output the assembly (LINE.Dynamo.ZeroTouchDS.dll) to the directory, there’s an AfterBuild command added to the CSPROJ file per the icon instructions to build the LINE.Dynamo.ZeroTouchDS.customization.dll for the icons, and then it will copy the LINE.Dynamo.ZeroTouchDS_DynamoCustomization.xml file to the output directory as well. Now when I load the LINE.Dynamo.ZeroTouchDS.dll file into Dynamo, I get a more pleasant node organization and I get the icons loaded.

Dynamo_Customized

Final Comments

Aside from just my preferences in using C# for development, another argument that could be made is that if you want to use icons for your nodes you will have to do it in a .net project anyways. Just the convenience of keeping all of the project files in one place is compelling enough for me to go with Zero Touch.

You can find the project files here:

https://github.com/logant/DynamoExperiments/tree/master/ZeroTouchDS

It includes an Example folder with the Dynamo file that shows the MeshToolkit primitive meshes being converted to Dynamo meshes and joined, as well as the Grasshopper prototype.