Getting Started With Dynamo Development

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 8

4/23/2017 Getting Started with Dynamo Development · DynamoDS/Dynamo Wiki

This repository Search Pull requests Issues Gist

DynamoDS / Dynamo Watch 148 Star 443 Fork 287

Code Issues 724 Pull requests 17 Projects 3 Wiki Pulse Graphs

Getting Started with Dynamo Development Edit New Page

Fabian Ritter edited this page on Aug 2, 2013 · 15 revisions

Development Guide Pages 60

Find a Page…

Home
This page is outdated! It will be refreshed once some new
Add Icons for a Zero Touch
changes to the node API are in place. Assembly

API Changes

Building a Package for Dynamo


##The Spirit of Open‐Source Development
in Visual Studio
Because Dynamo is open‐source on GitHub, anyone can make a copy of the code and modify it Choosing a Pull Request
however they like. Since Dynamo is an entire programming language and is very much a Work in Template
Progress, contributions to the source code from the community is strongly encouraged and is
Coding Standards
almost entirely necessary for it's success.
Concepts, Definitions and
Users can contribute by simply writing nodes which they feel should be included out of the box. At Terminology
this time, a very small subset of the Revit API has been exposed in Dynamo, so we hope that users
Create An Icon for A Node
familiar with the Revit API can contribute in order to expand that domain as well.
Creating Icons for Custom
##Pre‐requisites Nodes

Daylight Analysis for Dynamo


###Revit 2013
Developing with Dynamo FAQ
In order to work on the parts of Dynamo that use Revit, you will need to have some full‐flavored
variant of Revit 2013. It's expensive, but it you're a student or educator you can download it for Documentation on
CBNLanguageComparisonTests
free here.
Dynamo 2.0
###IronPython
Dynamo Command Line
In order to work with the parts of Dynamo that use IronPython, you will need to install IronPython, Interface
which you can download here.
Dynamo FAQ

##The Dynamo Visual Studio Solution Show 45 more pages…

Dynamo is written in a combination of C# and F#, two .NET languages. While you can use any
Visual Studio version ﴾2008 and onwards﴿ to work with the Dynamo Source, Visual Studio will need Releases
to be configured to be able to load and build C# and F# projects.
Release Notes
###DragCanvas The DragCanvas project contains a WPF component based on the Canvas class,
which allows UIElements to be dragged around a canvas using a mouse. Contributing

###DynamoElements DynamoElements is the main Dynamo project, and is ﴾probably﴿ the one you Choosing A Pull Request
will be spending most of your time in. DynamoElements contains the entire Dynamo UI, all Node Template
Major
defintions, and the program logic. ####Notable Files:
Feature

dynBench.xaml/dynBench.xaml.cs ‐ Contains the UI and logic behind it. Incremental


Coding standards
dynNode.xaml/dynNode.xaml.cs ‐ Contains the UI and logic for Nodes.
Naming standards
dynBaseTypes.cs ‐ Contains most of the basic nodes. Testing expectations

###DynamoRevit DynamoRevit contains the plugin code for Revit and Vasari. It is where the
How To
Ribbon button is setup and where initialization takes place for the UI and the Dynamic Model
Updater. Create your own library for
Dynamo

https://github.com/DynamoDS/Dynamo/wiki/Getting­Started­with­Dynamo­Development Zero Touch 1/8


4/23/2017 Getting Started with Dynamo Development · DynamoDS/Dynamo Wiki

###FScheme The FScheme project serves as the foundation for running Dynamo scripts. It's what Zero Touch
handles the order of execution and passing arguments to nodes as inputs. In reality, FScheme is a Write a Library with
programming language based on an existing language called Scheme. FScheme is written in F# due ZeroTouch
to a close relationship with the language ﴾both FSharp and FScheme are functional programming Write unit tests for your
languages﴿, and because F# has Tail Call Optimizations built into the compiler, which allows for the Dynamo library

construction of recursive calls of unlimited depth without worrying about stack overflows. Add Icons for a Zero Touch
Assembly

###FSchemeInterop FSchemeInterop contains wrappers which are used to quickly convert C# code Migration of
ProtoInterface to
to code blocks executable from FScheme. Odds are, you will be using some utility functions
DynamoServices
contained in this project when writing new nodes.
Integrate Dynamo with another
##Writing your first new Node host

###Getting Setup First thing's first: make sure that Dynamo is building properly before you start to Integrate localized resource
files
modify it. You can start a build by clicking Build > Build Solution ﴾or by pressing F6 by default﴿. If it
says "Build Successful" at the bottom of the screen, then you're good to go! If not, then you will Write usable error messages
see some errors which need to be fixed.
Build Dynamo on Linux, Mac

Assuming you're now building correctly, we're ready to write a node! Make a new file called Efficiently Working With Large
MyFirstNode.cs in the DynamoElements project ﴾since that's where Nodes go﴿. Data Sets In Dynamo

First, you'll want to tweak the auto‐generated code. Your class MyFirstNode has to extend dynNode Run Dynamo from the
command line
in order to be a Node. You will also want to add a few using statements:
Build A Package for Dynamo
with Visual Studio
using System;
using System.Collections.Generic;
using System.Linq; Dynamo Internals
using System.Text;
How Replication and
using Microsoft.FSharp.Collections; Replication Guide work: Part 1
using Dynamo.Connectors; How Replication and
using Value = Dynamo.FScheme.Value; Replication Guide work: Part 2
How Replication and
namespace Dynamo.Elements
Replication Guide work: Part 3
{
class MyFirstNode : dynNode
{ Libraries
}
} Dynamo Revit.
Dynamo Mesh Toolkit.

The two extra using statements are necessary for proper code‐style in the Dynamo project. They
Installers
are also fairly convenient and will save you a lot of unnecessary typing.
Dynamo Installers
###Defining the Node

In this example, we will be creating a node that takes the maximum of two given numbers. So this FAQs
node will take in two numbers as inputs, and return one number as a result. In Dynamo, it's not
Dynamo FAQ
necessary for any node to take in input, but ALL nodes must have one and only one output.
Python FAQ

First, we need to add some Attributes to the class definition:


API and Dynamo Nodes
[ElementName("Max")] Dynamo Versions
[ElementCategory(BuiltinElementCategories.MATH)]
API Changes
[ElementDescription("Returns the maximum of two given numbers.")]
Dynamo Node Changes
[RequiresTransaction(false)]
class MyFirstNode : dynNode
{ Clone this wiki locally
}
https://github.com/DynamoDS/Dynamo.wiki.git

While these aren't the only attributes you can use for a node, these are the ones you should use for Clone in Desktop
every node you define.

ElementName﴾string﴿ ‐ The name of your node, this must be unique. It will also be what the
user sees in Dynamo.
ElementCategory﴾string﴿ ‐ The category this node belongs to in the side‐menu. You can put
whatever string you like here, and if the category doesn't already exist, it will be created for the
node. If you want to reference any of the built‐in categories, however, it is recommended you
reference it via the BuiltInElementCategories class. This is important if, in the future, the names
https://github.com/DynamoDS/Dynamo/wiki/Getting­Started­with­Dynamo­Development 2/8
4/23/2017 Getting Started with Dynamo Development · DynamoDS/Dynamo Wiki

of the categories change. If you reference these instead of hard‐coding the string, you won't
have to update them in the future if they change.
ElementDescription﴾string﴿ ‐ Description of the node, displayed as a ToolTip to the user when
they mouse over the node.
RequiresTransaction﴾bool﴿ ‐ True or false value determining whether or not the Node's logic
should be wrapped in a Revit Transaction. If your node calls any API that requires a
Transaction, then set this to true. Otherwise set it to false for performance purposes.

We define our inputs and output in the constructor like so:

public MyFirstNode()
{
InPortData.Add(new PortData("num1", "A number to compare", typeof(double)));
InPortData.Add(new PortData("num2", "A number to compare", typeof(double)));
OutPortData = new PortData("max", "The greater of the two inputs", typeof(double));

NodeUI.RegisterAllPorts();
}

Let's take an in‐depth look at this. InPortData is a field inherited from dynElement that is a list of
PortData object, used to define the inputs of the Node. OutPortData is a field inherited from
dynElement that is a single PortData object, used to define the output of the node. PortData is a
class used to define the characteristics of any Port ﴾either Input or Output﴿.

The arguments to PortData in order are:

string nickName ‐ The name of the port. This will be displayed as text visually on the node.
string top ‐ Description of the port. This will appear as a ToolTip when the user overs over the
port.
Type portType ‐ Type of object the port produces/accepts. Right now this functionality is not
implemented into Dynamo, but eventually this will be used to enforce Type‐checking
automatically, so that the user cannot connect two nodes without matching types.

Finally, NodeUI.RegisterAllPorts(); tells the UI to update it's layout so that it can display all of the
Ports that have just been defined. This should always be called in the constructor after all of the
ports have been defined.

###Writing the logic

The Node's logic is the code that will be called when the node is ready to be evaluated. You will
have access to the inputs given to the node, and can use them to calculate the desired output.

The logic is contained in an overridden method inherited from dynNode called Evaluate. Add the
following to your node definition underneath the constructor:

public override Value Evaluate(FSharpList<Value> args)


{
//logic goes here
}

From the method signature, you can see that the logic takes in a List of Expression objects, and
returns an Expression object.

####About Values

In Dynamo, all arguments and return values are objects of type Value. A Value is a container that is
used to differentiate between datatypes under the hood in Dynamo's FScheme internals. Values
come in a few flavors, here are the ones you need to know about:

Value.Number
Value.String
Value.List
Value.Container ‐ Contains any other object.

https://github.com/DynamoDS/Dynamo/wiki/Getting­Started­with­Dynamo­Development 3/8
4/23/2017 Getting Started with Dynamo Development · DynamoDS/Dynamo Wiki

To access the contents of an Value, you must first cast the Value to the appropriate Value type, and
then get it's contents by using the Item field.

For example, if I want to get a number or a string out of a Value:

double a = ((Value.Number)numberValue).Item;
string s = ((Value.String)stringValue).Item;

Objects work a little differently: Item for Value.Container will return an object, so that must be cast
to the specific type you want.

XYZ location = (XYZ)((Value.Container)objValue).Item;

Lists require a bit more explanation. Item for Value.List will return a FSharpList. Each element in the
list is another expression, which then must be converted using the same rules as above.

//A quick way to convert a list using LINQ


var xyzSequence = ((Value.List)listValue).Item.Select(
xyzExpr =>
(XYZ)((Value.Container)xyzValue).Item
);
foreach (XYZ loc in xyzSequence)
{
//process each XYZ
}

#####How do I know what the type of a Value is?

In the Evaluate method, any exceptions that are thrown are automatically caught by Dynamo and
notified to the user. So it's usually common practice to simply cast to what you need and assume it
works. Any failures will be announced to the user, and that means they haven't followed the input
requirements for your node and will have to adjust their inputs.

However, there are situations where you may be expecting inputs of one type or another. In this
case, you can use the Value.Is[Type] fields. For example:

if (value.IsNumber)
{
//Treat value as a number
}
else if (value.IsString)
{
//Treat value as a string
}
//...

####Convert the arguments

We first have to get our desired inputs from the argument list, like so:

double a = ((Value.Number)args[0]).Item;
double b = ((Value.Number)args[1]).Item;

Since we are expecting two inputs, bother numbers, we simply fetch the first two elements from
the input list ﴾args﴿, cast them to Value.Number, and then pull the number using .Item.

####Generate the output

Now that we have the numbers, we can calculate the max:

double result = Math.Max(a, b);

And finally, we return the result in a new Value.Number:

https://github.com/DynamoDS/Dynamo/wiki/Getting­Started­with­Dynamo­Development 4/8
4/23/2017 Getting Started with Dynamo Development · DynamoDS/Dynamo Wiki

return Value.NewNumber(result);

Note that we don't use the new keyword to construct a new Expression. All Expressions are
constructed using the Value.New[Type] factory methods.

###And we're done

Here is a look at our new, finished node:

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

using Microsoft.FSharp.Collections;
using Dynamo.Connectors;
using Value = Dynamo.FScheme.Value;

namespace Dynamo.Elements
{
[ElementName("Max")]
[ElementCategory(BuiltinElementCategories.MATH)]
[ElementDescription("Returns the maximum of two given numbers.")]
[RequiresTransaction(false)]
class MyFirstNode : dynElement
{
public MyFirstNode()
{
InPortData.Add(new PortData("num1", "A number to compare", typeof(double)));
InPortData.Add(new PortData("num2", "A number to compare", typeof(double)));
OutPortData = new PortData("max", "The greater of the two inputs", typeof(double));

NodeUI.RegisterAllPorts();
}
}

public override Value Evaluate(FSharpList<Value> args)


{
double a = ((Value.Number)args[0]).Item;
double b = ((Value.Number)args[1]).Item;

double result = Math.Max(a, b);

return Value.NewNumber(result);
}
}

So now we build DynamoElements, copy the new DynamoElements.dll file over to your Dynamo
installation directory, and run Dynamo. You should see your new Max node under the Math section
of the side menu. Test it out!

##Dynamo and Revit

To demonstrate the proper way to write Revit nodes in Dynamo, we will look at a simple example.
This node will take in an XYZ and return a ReferencePoint located at that XYZ.

Here is how one might design it naively:

[ElementName("My Reference Point")]


[ElementCategory(BuiltinElementCategories.REVIT)]
[ElementDescription("An element which creates a reference point.")]
[RequiresTransaction(true)]
public class MyReferencePoint : dynNode
{
public MyReferencePoint()
{
InPortData.Add(new PortData("xyz", "The point(s) from which to create reference points.", typeof
OutPortData = new PortData("pt", "The Reference Point(s) created from this operation.", typeof

NodeUI.RegisterAllPorts();
}

https://github.com/DynamoDS/Dynamo/wiki/Getting­Started­with­Dynamo­Development 5/8
4/23/2017 Getting Started with Dynamo Development · DynamoDS/Dynamo Wiki
public override Value Evaluate(FSharpList<Value> args)
{
XYZ loc = (XYZ)((Value.Container)args[0]).Item;

return Value.NewContainer(
this.UIDocument.Document.FamilyCreate.NewReferencePoint(loc)
);
}
}

Now, if you were to run this once, it would work fine; when executed, this node will blindly make a
new ReferencePoint at the location of the given XYZ.

However, you will notice that on subsequent runs, it creates new ReferencePoints and keeps the
old ones where they were. With Run Automatically checked in Dynamo, this is problematic.

What we need to do is store a reference to the created ReferencePoint in our code, and then
update that ReferencePoint's position on subsequent Evaluate calls.

All nodes inherit a special list from dynElement called Elements. When an ElementId is placed in the
Elements list, Dynamo will automatically watch those ElementIds to make sure they remain valid on
later runs. This saves the user a lot of effort, since they won't have to keep track of changes in the
document and whether or not the Dynamo transaction was successful or rolled back.

Using Elements, we re‐write the Evaluate method to look like this:

public override Value Evaluate(FSharpList<Value> args)


{
XYZ loc = (XYZ)((Value.Container)args[0]).Item;

ReferencePoint pt;

//If we've made any elements previously...


if (this.Elements.Any())
{
//Move existing...
}
//...otherwise...
else
{
//...just make a point and store it.
pt = this.UIDocument.Document.FamilyCreate.NewReferencePoint(loc);
this.Elements.Add(pt.Id);
}

//Fin
return Value.NewContainer(pt);
}

So now, we check to see if we've made anything by checking if we've put anything in Elements. If
Elements is empty, we make the Element like normal, and then add it to Elements.

Now let's fill in the code for when we've already stored one.

...
//If we've made any elements previously...
if (this.Elements.Any())
{
Element e;
//...try to get the first one...
if (dynUtils.TryGetElement(this.Elements[0], out e))
{
//..and if we do, update it's position.
pt = e as ReferencePoint;
pt.Position = loc;
}
else
{
//...otherwise, just make a new one and replace it in the list.
pt = this.UIDocument.Document.FamilyCreate.NewReferencePoint(loc);
this.Elements[0] = pt.Id;

https://github.com/DynamoDS/Dynamo/wiki/Getting­Started­with­Dynamo­Development 6/8
4/23/2017 Getting Started with Dynamo Development · DynamoDS/Dynamo Wiki
}
}
...

Elements stores ElementIds, so we need to fetch the Element from the document. dynUtils contains
a utility method for doing this, called TryGetElement. We pass in the ElementId to fetch ﴾taken
directly from Elements﴿, and we pass in the variable we want to assign the fetched element to.
TryGetElement will return true if it was successful, and false if the ElementId is no longer valid.

So if it's successful, we just cast the element to a ReferencePoint, and then set the position to the
new xyz. If it's unsuccessful, we create a new element like normal, and then update the stored Id in
Elements.

Here is the completed Node:

[ElementName("My Reference Point")]


[ElementCategory(BuiltinElementCategories.REVIT)]
[ElementDescription("An element which creates a reference point.")]
[RequiresTransaction(true)]
public class MyReferencePoint : dynNode
{
public MyReferencePoint()
{
InPortData.Add(new PortData("xyz", "The point(s) from which to create reference points.", typeof
OutPortData = new PortData("pt", "The Reference Point(s) created from this operation.", typeof

NodeUI.RegisterAllPorts();
}

public override Value Evaluate(FSharpList<Value> args)


{
XYZ loc = (XYZ)((Expression.Container)args[0]).Item;

ReferencePoint pt;

//If we've made any elements previously...


if (this.Elements.Any())
{
Element e;
//...try to get the first one...
if (dynUtils.TryGetElement(this.Elements[0], out e))
{
//..and if we do, update it's position.
pt = e as ReferencePoint;
pt.Position = loc;
}
else
{
//...otherwise, just make a new one and replace it in the list.
pt = this.UIDocument.Document.FamilyCreate.NewReferencePoint(loc);
this.Elements[0] = pt.Id;
}
}
//...otherwise...
else
{
//...just make a point and store it.
pt = this.UIDocument.Document.FamilyCreate.NewReferencePoint(loc);
this.Elements.Add(pt.Id);
}

//Fin
return Value.NewContainer(pt);
}
}

Looking for help with using the Dynamo application? Try dynamobim.org.

https://github.com/DynamoDS/Dynamo/wiki/Getting­Started­with­Dynamo­Development 7/8
4/23/2017 Getting Started with Dynamo Development · DynamoDS/Dynamo Wiki
© 2017 GitHub, Inc. Terms Privacy Security Status Help Contact GitHub API Training Shop Blog About

https://github.com/DynamoDS/Dynamo/wiki/Getting­Started­with­Dynamo­Development 8/8

You might also like