What language are we using?

Please forgive for my complete lack of education and the lost coordinates of my existence in time and space.

@jacob.small has told me more than once to read Dynamo Python Primer git book. So, I took time to check it out, and saw a page talking about Revit API. The page takes me to API documentation where I can search for methods. I looked for Join Geometry method, ApiDocs.co
and here I see that the syntax is C#. What are we coding? Python or C#? I’m confused…

You can use different languages like Python and C# to communicate with the API. Most examples you find is with C#.

2 Likes

Before starting something, as long as we think about the idea and the way we will solve them, then think of the tool. And the language is one of those things. Choose something that makes you happy and started with it. Good luck.

2 Likes

@Anton_Huizinga @chuongmep Thank you very much!

I’m mostly confused because I can only find “Python Script” node. I don’t see “C# Node”… Where can I write C# ? Can “Python Script” node be used to write C# as well?

00

If you really interesting with some behind that, you can access this repo Dynamo opensource to see : GitHub - DynamoDS/Dynamo: Open Source Graphical Programming for Design

Just Warning : Just some part of Dynamo is Open Source, not all part of Dynamo.

2 Likes

This is a big question, which I’ve tried to outline as best as I can before but can’t recall exactly where, so bear with me as I try to lay it all out.

The ‘too long, didn’t read’ of it is this: we write Python in a way to call the same methods which are documented in C#, Visual Basic, and Visual C++.

Revit, Dynamo, Civil 3D, and pretty much every application we use in AEC runs in Microsoft’s .net environment. That environment utilizes C# to execute code in the common language runtime (clr), which allows otherwise unrelated applications to speak a common language during execution which allows content to flow between applications.

Under that there is C++ and under that is C and under that is machine code. You can look those all up on your own, but they are mostly irrelevant until we start speaking about Python.

Python is native to C, and so it doesn’t execute natively in the clr (hence why you can use it on iOS, OS X, android os, etc.). However there are special flavors of Python which are implemented in .net, such as IronPython or PythonNet which allow Python to execute a little bit closer to the clr. To do so they utilize ‘import clr’ and then they can start to leverage those libraries.

However Python is still Python, not c#, Visual Basic, or C++ (the languages which Revit’s API documentation is in). And so we have to convert the text we write to perform calls from that C# to Python, why PythonNet’s or IronPython’s CLR interpreter will convert the actual code for us.

This means you need some basic understanding of converting the calls, and apply those into your usual Python coding syntax.

Constructors are mostly the same in both c# and Python, Class() will usually do the trick.

Methods can be the same, Class.Method(parameter1, parameter2) will usually do the trick. However in Python you often will see objectInstance.Method(parameter2, parameter3) where parameter1 is the object instance.

I find that properties will differ the most, as CPython’s CLR interpreter doesn’t quite deal with them the same. In some cases you can get them with the same C# call objectInstance.Property. However in other instances you need to use the call objectInstance.get_Property() which ensures the conversion for you.

Similarly setting properties will sometimes work as objectInstance.Property = value. However when it fails you may have to fall back on objectInstance.set_Property(value) to ensure clr conversion.

Lastly among the basics we have enumerators, which fortunately are the same again. Class.EnumeratorKey. The one hang up to this method is that sometimes the enumeration will return the index instead of the method. There are ways to solve that throughout the forum and other Revit Python scripting communities, but there is not a ‘standard’ among the community that I am aware of.

Hopefully this sheds some light for you in the Python bit. I’ll post something for the ‘what if we want to use C#’ but shortly.

8 Likes

Python is compiled at runtime, and as such you can script it directly in Dynamo and keep things stable.

C# is a compiled language, that is there is not an easy and stable way to convert the text you might write over to executable code without preemptively converting it to a dll which Dynamo can call. As a result there aren’t any official ways to utilize c# code directly in Dynamo, but you need an external application referred to as an IDE or ‘integrated development t environment’. There are many IDEs out there. Personally I use both Visual Studio (paid) and Visual Studio Code (free) depending on the project (VS Code I mostly use from ‘introduction’ workshops as everyone can have the same common environment as we learn together, without any issues with access or paying for a license).

The next thing to know is that because you are compiling code from the stuff we can understand into something only your CPU can work with means that Dynamo is calling the compiled library instead of the text. As a result you will have to package or otherwise distribute the content into the environment to utilize the code. Instead of sending along a dyn and calling it a day, you’ll need to publish the package to package manager or put the content on the end user’s systems directly.

To get started with writing nodes for Dynamo with C#, check out the Developer primer here: https://developer.dynamobim.org/

4 Likes

My book Dynamo for Civil 3D contains a few chapters about Python and C#:

https://books.huiz.net/book/dynamo-for-civil-3d/

If you want to learn more about C#, I recommend my book about programming plug-ins for AutoCAD:

https://books.huiz.net/book/start-programming-in-net-for-autocad/

5 Likes

@jacob.small thank you for your detailed explanation. I will bookmark this topic, just in case I will have any further questions in the future.
@Anton_Huizinga thanks

Hi,
you can look at how the package designers used the classes (not to copy but to understand) it’s a good learning method with combined reading of the API classes
That’s when you realize that there are really smart guys.

a few days ago not in a package in a subject looked at, use the property of the element to retrieve the document (I found this very brilliant)

Sincerely
christian.stan

1 Like

@christian.stan how can I see the code behind a node made by package author?

you open the custom node name.dyf
(with a double click on the node)

cordially
christian.stan

For C# authored nodes you often cannot. Those which are open sourced (meaning the source code has been made open to the larger public) are usually available on the package author’s repository, often on GitHub but bitbucket and other alternatives exist. :slight_smile:

1 Like

I’d recommend these packages which use exposed Python in their nodes to learn from:

  • Genius Loci
  • Clockwork
  • MEPover (if doing MEP)
  • Springnodes
  • Data shapes (if playing with winforms)
  • Crumple (which I built after learning from above)

C# is a big hurdle for most to take on unless they have some developer/coding experience prior, I highly recommend spending time with Python first unless you’re comfortable learning Visual Studio also. There’s generally a lot less beginner friendly content out there in C#/VS.

3 Likes

@GavinCrump Thanks. Well, I guess even looking at other people code is over ambitious for me. I see some IDs that I can’t even assume what they are, and I cannot detect which statement was used to do the actual join geometry action. This is JoinGeometry node from Clockwork (unless I opened the wrong thing):

{
  "Uuid": "38d2b003-a570-4fc4-9836-4e10befcd4fc",
  "IsCustomNode": true,
  "Category": "Clockwork.Revit.Elements.Actions",
  "Description": "Joins two model elements (if possible). Only works inside the project environment.",
  "Name": "Element.JoinGeometry",
  "ElementResolver": {
    "ResolutionMap": {}
  },
  "Inputs": [],
  "Outputs": [],
  "Nodes": [
    {
      "ConcreteType": "PythonNodeModels.PythonNode, PythonNodeModels",
      "NodeType": "PythonScriptNode",
      "Code": "import clr\r\nclr.AddReference('RevitAPI')\r\nfrom Autodesk.Revit.DB import *\r\nfrom System.Collections.Generic import *\r\nclr.AddReference(\"RevitServices\")\r\nimport RevitServices\r\nfrom RevitServices.Persistence import DocumentManager\r\nfrom RevitServices.Transactions import TransactionManager\r\n\r\ndoc = DocumentManager.Instance.CurrentDBDocument\r\nitems1 = UnwrapElement(IN[0])\r\nitems2 = UnwrapElement(IN[1])\r\n\r\ndef JoinGeometry(doc, item1, item2):\r\n\ttry:\r\n\t\tJoinGeometryUtils.JoinGeometry(doc,item1,item2)\r\n\t\treturn True\r\n\texcept: return False\r\n\r\nTransactionManager.Instance.EnsureInTransaction(doc)\r\nif isinstance(IN[0], list):\r\n\tif isinstance(IN[1], list): OUT = [JoinGeometry(doc, x, y) for x, y in zip(items1, items2)]\r\n\telse: OUT = [JoinGeometry(doc, x, items2) for x in items1]\r\nelse:\r\n\tif isinstance(IN[1], list): OUT = [JoinGeometry(doc, items1, x) for x in items2]\r\n\telse: OUT = JoinGeometry(doc, items1, items2)\r\nTransactionManager.Instance.TransactionTaskDone()",
      "VariableInputPorts": true,
      "Id": "fa79ddac0e2348cdaa6a9e28ed1fedc0",
      "Inputs": [
        {
          "Id": "9fb68f5bc0b647e9a70a60f6a1e0cbf8",
          "Name": "IN[0]",
          "Description": "Input #0",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        },
        {
          "Id": "1a3af1a679764327be7ed42a03689449",
          "Name": "IN[1]",
          "Description": "Input #1",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        }
      ],
      "Outputs": [
        {
          "Id": "9ab16ffe497349dda04d6f4a5dea68f1",
          "Name": "OUT",
          "Description": "Result of the python script",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        }
      ],
      "Replication": "Disabled",
      "Description": "Runs an embedded IronPython script."
    },
    {
      "ConcreteType": "Dynamo.Graph.Nodes.CustomNodes.Symbol, DynamoCore",
      "NodeType": "InputNode",
      "Parameter": {
        "Name": "element1",
        "TypeName": "var",
        "TypeRank": -1,
        "DefaultValue": null,
        "Description": ""
      },
      "Id": "c21c0b76d41242c880d087c1b897d41b",
      "Inputs": [],
      "Outputs": [
        {
          "Id": "442dabe6a363440db6f402f6da5f8d5f",
          "Name": "",
          "Description": "Symbol",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        }
      ],
      "Replication": "Disabled",
      "Description": "A function parameter, use with custom nodes.\r\n\r\nYou can specify the type and default value for parameter. E.g.,\r\n\r\ninput : var[]..[]\r\nvalue : bool = false"
    },
    {
      "ConcreteType": "Dynamo.Graph.Nodes.CustomNodes.Symbol, DynamoCore",
      "NodeType": "InputNode",
      "Parameter": {
        "Name": "element2",
        "TypeName": "var",
        "TypeRank": -1,
        "DefaultValue": null,
        "Description": ""
      },
      "Id": "033818c90bdc43459592605945495201",
      "Inputs": [],
      "Outputs": [
        {
          "Id": "7aae147942944c1dbf6b73b0888d3887",
          "Name": "",
          "Description": "Symbol",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        }
      ],
      "Replication": "Disabled",
      "Description": "A function parameter, use with custom nodes.\r\n\r\nYou can specify the type and default value for parameter. E.g.,\r\n\r\ninput : var[]..[]\r\nvalue : bool = false"
    },
    {
      "ConcreteType": "Dynamo.Graph.Nodes.CustomNodes.Output, DynamoCore",
      "NodeType": "OutputNode",
      "ElementResolver": null,
      "Symbol": "success",
      "Id": "1cd28e9e5f47490d8d9cffa1dea7a2c6",
      "Inputs": [
        {
          "Id": "4c9707518c54456c9d9ed5404b0313a7",
          "Name": "",
          "Description": "",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        }
      ],
      "Outputs": [],
      "Replication": "Disabled",
      "Description": "A function output, use with custom nodes"
    },
    {
      "ConcreteType": "Dynamo.Graph.Nodes.CustomNodes.Output, DynamoCore",
      "NodeType": "OutputNode",
      "ElementResolver": null,
      "Symbol": "element1",
      "Id": "e46afdd5286e4afa8bfc6c5899fb9791",
      "Inputs": [
        {
          "Id": "aa4b666c8de54568aeeafb51e1696f66",
          "Name": "",
          "Description": "",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        }
      ],
      "Outputs": [],
      "Replication": "Disabled",
      "Description": "A function output, use with custom nodes"
    },
    {
      "ConcreteType": "Dynamo.Graph.Nodes.CustomNodes.Output, DynamoCore",
      "NodeType": "OutputNode",
      "ElementResolver": null,
      "Symbol": "element2",
      "Id": "8b1a47da1c184209ab5c73e34d7c60ca",
      "Inputs": [
        {
          "Id": "f0253e02c79c48bfaf9bdd85a9fd2081",
          "Name": "",
          "Description": "",
          "UsingDefaultValue": false,
          "Level": 2,
          "UseLevels": false,
          "KeepListStructure": false
        }
      ],
      "Outputs": [],
      "Replication": "Disabled",
      "Description": "A function output, use with custom nodes"
    }
  ],
  "Connectors": [
    {
      "Start": "9ab16ffe497349dda04d6f4a5dea68f1",
      "End": "4c9707518c54456c9d9ed5404b0313a7",
      "Id": "c875ed12e125482abf1b0e973e1e2f54"
    },
    {
      "Start": "442dabe6a363440db6f402f6da5f8d5f",
      "End": "9fb68f5bc0b647e9a70a60f6a1e0cbf8",
      "Id": "5242352e0d0f485c972d971c3d0c56ef"
    },
    {
      "Start": "442dabe6a363440db6f402f6da5f8d5f",
      "End": "aa4b666c8de54568aeeafb51e1696f66",
      "Id": "989421041d62441d8abc9cda2584ba7c"
    },
    {
      "Start": "7aae147942944c1dbf6b73b0888d3887",
      "End": "1a3af1a679764327be7ed42a03689449",
      "Id": "e4b440c60089477e9717c21fc030c5fe"
    },
    {
      "Start": "7aae147942944c1dbf6b73b0888d3887",
      "End": "f0253e02c79c48bfaf9bdd85a9fd2081",
      "Id": "33478adab9784905b9ce5437a7aa9c35"
    }
  ],
  "Dependencies": [],
  "Bindings": [],
  "View": {
    "Dynamo": {
      "ScaleFactor": 1.0,
      "HasRunWithoutCrash": false,
      "IsVisibleInDynamoLibrary": true,
      "Version": "2.0.1.5055",
      "RunType": "Manual",
      "RunPeriod": "1000"
    },
    "Camera": {
      "Name": "Background Preview",
      "EyeX": -17.0,
      "EyeY": 24.0,
      "EyeZ": 50.0,
      "LookX": 12.0,
      "LookY": -13.0,
      "LookZ": -58.0,
      "UpX": 0.0,
      "UpY": 1.0,
      "UpZ": 0.0
    },
    "NodeViews": [
      {
        "ShowGeometry": true,
        "Name": "Python Script",
        "Id": "fa79ddac0e2348cdaa6a9e28ed1fedc0",
        "IsSetAsInput": false,
        "IsSetAsOutput": false,
        "Excluded": false,
        "X": 498.81277664478057,
        "Y": 120.98284513131719
      },
      {
        "ShowGeometry": true,
        "Name": "Input",
        "Id": "c21c0b76d41242c880d087c1b897d41b",
        "IsSetAsInput": false,
        "IsSetAsOutput": false,
        "Excluded": false,
        "X": 211.81277664478057,
        "Y": 147.48284513131716
      },
      {
        "ShowGeometry": true,
        "Name": "Input",
        "Id": "033818c90bdc43459592605945495201",
        "IsSetAsInput": false,
        "IsSetAsOutput": false,
        "Excluded": false,
        "X": 211.81277664478057,
        "Y": 231.98284513131716
      },
      {
        "ShowGeometry": true,
        "Name": "Output",
        "Id": "1cd28e9e5f47490d8d9cffa1dea7a2c6",
        "IsSetAsInput": false,
        "IsSetAsOutput": false,
        "Excluded": false,
        "X": 722.81277664478057,
        "Y": 120.98284513131719
      },
      {
        "ShowGeometry": true,
        "Name": "Output",
        "Id": "e46afdd5286e4afa8bfc6c5899fb9791",
        "IsSetAsInput": false,
        "IsSetAsOutput": false,
        "Excluded": false,
        "X": 722.81277664478057,
        "Y": 203.98284513131719
      },
      {
        "ShowGeometry": true,
        "Name": "Output",
        "Id": "8b1a47da1c184209ab5c73e34d7c60ca",
        "IsSetAsInput": false,
        "IsSetAsOutput": false,
        "Excluded": false,
        "X": 722.81277664478057,
        "Y": 286.98284513131716
      }
    ],
    "Annotations": [],
    "X": 46.084607938236559,
    "Y": 75.672304135145623,
    "Zoom": 1.0606077223602091
  }
}

So that’s the raw dyf which is more akin to json data structure.

Actually go into Dynamo, place the node to inspect, right click > edit and you should be able to access the python nodes within.

2 Likes

Nice, I will try that out now.

1 Like

I also have a quick crash course in Python fundamentals here that I made after struggling my way into the language initially. Most content out there is too broad or simple for my liking so I broke down the most useful aspects into a mini series for others.

Definitively will do!

1 Like

Actually go into Dynamo, place the node to inspect, right click > edit and you should be able to access the python nodes within.

Interesting thing happened. When I use JoinGeometry node from Clockwork, and run the script, nothing is being joined.

But when I edit the Clockwork node, copy the Python that is inside, close the node, paste the code into a OOTB Python node, and run the script, it works. Everything is joined :joy: