custom dropdown menu (part 2) link
January 15, 2021
Happy Friday! Last week, we set up our project to write a Zero Touch Node for Dynamo so that this week, we can start creating some custom dropdown nodes. I did update >last week's post to include two additional DLL files you will need to reference: ProtoCore.dll and ewtonSoft.Json.dll. For this example, I will walk through two different dropdown menus. They both use FilteredElementCollectors to get the elements we need for our menu. You can of course completely customize the nodes to fit your needs.
The first node we want to put together is a dropdown that only displays the different area plans in the project. We can create our class and call it AreaPlans. We want to add the attributes we discussed last week so that it is easier to control how everything is labeled in Dynamo.
First, we need to write our constructor. Then, we can use the PopulateItemsCore method, however we want to override it’s functionality so that we can populate it with our own items. The method we are overriding is protected, so our method will also be protected, it will return a SelectionState and the method name stays the same.
To use a filtered element collector, we need the active document, which we can assign to a variable called doc. Using Revit LookUp we can see that Area Plans are ViewPlans where the ViewType is AreaPlan. This allows us to easily set up a filtered element collector for our AreaPlans.
After we collect all of our area plans, if no elements were collected, we want to return null with the message “No area plans” available. If elements were collected, we want to sort them based on the Title property. This allows them to appear in alphabetical order by Title in our drop down. We also need to return our SelectionState.
Then we need to build our output. We are going to override the BuildOutputAst method. If it does not have an Area Plan to output, we want to return null. Otherwise, it will select the Area Plan from the drop down and return the ElementId as an integer.
To create a dropdown just for title blocks works almost exactly the same way. We want to create a new class and can update the attributes. Since we want to specifically get all the types of title blocks in the project, not a list of the placed title blocks, we want to look for FamilySymbol not FamilyInstance. Title block families have “Title Blocks” as the name of their Category parameter so we can use this to filter for all the required families. This time, we want to sort by the element’s name and also use the name to display in the dropdown list, but return the title block family element.
And in just a few simple steps, we can create a large variety of custom dropdown nodes to use in Dynamo. This can help to simplify selections in your scripts and make them faster and easier to use. Don’t forget, the DLL for your custom nodes will need to be located on the computer of everyone who needs to use the script, just like packages. Happy customizing!
custom dropdown menu (part 1) link
January 08, 2021
Happy Friday and Happy New Year! I hope you all enjoyed the holidays and found some time to relax. We filled our days with puzzles, boardgames, and walks which was a great way to unwind. I knew there would come a time when the packages for Dynamo wouldn’t have exactly the tools I needed and I would dive into the work of custom dynamo nodes. Currently, I have a few scripts that I would like to make more user-friendly by filtering the dropdown menu to very specific items. As I was trying to put together a custom dropdown menu, I spent a lot of time looking through existing resources and although all the information is out there, I thought I would compile everything in one place to make it easier to reference.
zero touch nodes
Zero Touch Nodes (ZTN) are custom Dynamo nodes that are written in C#, compiled, and then used inside Dynamo by importing the DLL. If you have never created a Zero Touch Node, I would recommend Thomas Mahon’s excellent course “Become a Dynamo Zero Touch C# Node Developer in 75 Minutes”. It clearly walks through how to set up your project in Visual Studio and how to create your first node as well as covering some C# basics, dynamo node development basics, and more. This walkthrough did not have information on all the references we need to import into our project because they weren’t required for the tutorial, but we do need some for the node we are making so I will review these later in this post.
node naming - using attributes
As Thomas explains, public methods appear as Dynamo nodes. Node naming is taken from the DLL, namespace, and class names which can be inconvenient. One helpful piece of information I learned through this process is that you can use attributes, some people refer to them as class decorators, which allow you to override the node name, category, description, and other information so that Dynamo doesn’t pull the naming from the DLL, namespace and class names Thomas describes. Based on the information I found, you can only use the attributes on a class so each node you create will need to be a new class. You will need some of the references mentioned below to use them.
Konrad Sobon posted excellent information in response to a question on the Dynamo forum that got me on the right track for creating a custom dropdown node. We will be using the RevitDropDownBase class that Konrad mentioned to create our menu. First though, we need to track down and import the following references into our C# project. I was working with Revit 2019 but these should also be available in the later Dynamo and Revit folders.
- C:\Program Files\Autodesk\Revit 2021\AddIns\DynamoForRevit\nodes\CoreNodeModules.dll
- C:\Program Files\Autodesk\Revit 2021\AddIns\DynamoForRevit\Revit\nodes\DSRevitNodesUI.dll
- C:\Program Files\Autodesk\Revit 2021\AddIns\DynamoForRevit\DynamoCore.dll
- C:\Program Files\Autodesk\Revit 2021\RevitAPI.dll
- C:\Program Files\Autodesk\Revit 2021\AddIns\DynamoForRevit\Revit\RevitServices.dll
- C:\Program Files\Autodesk\Revit 2021\AddIns\DynamoForRevit\ProtoCore.dll
- C:\Program Files\Autodesk\Revit 2021\NewtonSoft.Json.dll
These are all the references we need in order to use the RevitDropDownBase class and others along with the attributes. Now that we have set up our project and imported the required references, we can create our custom node, which I will walk through next week.
managing data types (python for Dynamo) link
December 18, 2020
Happy Friday! I hope you all enjoyed AU this year and had a great time during the recent holidays. I know for my husband and I, it was nice to have a very small gathering with just our immediate family for Thanksgiving. We also got to do a lot of hiking, which we loved! I can’t believe it’s all gone by so fast and we are now past mid-December.
If you want to start learning how to code, python is a popular choice with a wide variety of applications. For example, you can use it for custom nodes in Dynamo. A great stepping stone is to start turning pieces of your graph into python nodes. I frequently do this with filtering as mentioned in a previous post, especially for parameters, because it speeds up the process. It can also be used to efficiently get the data type you need.
I noticed there were a couple tasks that I always use Python nodes for and I decided to share another one. For this graph, I am taking the phase created and phase demolished parameters and copying the name of the phase as a string into a custom parameter. I need to write this phase as text to a text parameter so I can use it for a filter in a view template.
I find that I frequently need to change and manage the data type of outputs from various Dynamo nodes. For example, when you get the phase of an element using the Element.Phases node, it returns an object representing the phase element. In order to copy this to a string, we need the name, but we also need to handle the cases for when the phase is none. To accomplish this, we can add a couple lines of code in a python node.
Our IN will be a list of the phases. In this case, phase demolished. We will also need a new empty list to store the text in. Then we look at each phase in the phaseDemolished list. If it is equal to none, we add the text “none” to our phaseDemolishedText list. Otherwise, we get the name of the phase as a string and put that into our list. Then we can output our phaseDemolishedText list and the node will return a list of strings noting either the phase name or “none” based on the conditions.
Then we can use that in our graph to copy both the Phase Created and Phase Demolished parameters to a text parameter. And this is what the graph looks like in use:
Thank you for all the support this year. I can say it has certainly been strange. There are so many things that have not gone right, but there are also things I am grateful for, like getting to spend more time with my family. I will not be posting over the next two weeks, but will be back to a more regular schedule in January. I wish you happy holidays and all the best for the new year.
Export OBJ Using C# link
With guest writer: Andreas Brake
December 11, 2020
Happy Friday! This week I am excited to welcome back Andreas Brake as the guest writer for this post. He is a software engineer at Topgolf Media doing server development. In his free time, he enjoys hiking, gaming, and gardening. This week, he will discuss how to export Revit geometry as OBJ files using C#, a feature we used when working on StreamVR, a Hackathon project we did in August.
One exciting usage of Revit plugins is to add interoperability between Revit and external programs. This can take many forms, but sometimes you may want to transfer geometry from Revit including Families in the model. This presents some difficulty, as Revit allows exporting Families as FBX files or other proprietary formats but lacks a built-in export to a widely used open format such as OBJ. This post will be describing what an OBJ model looks like and how to create one in C# using the Revit API.
what is an .obj file
Simply put, an OBJ file is a plain-text file that contains structured information that describes some arbitrary geometry. In its most basic form, this file will define a series of vertices and faces that can be read and reconstructed into a 3D model. The example below defines a Cube.
Click Here to download this example OBJ. This download is generated in-browser so no data will be sent to any server.
In addition to these simple parameters, an OBJ file can be extended with information for applying correct normal vectors and texture mapping among many other features. This post will consider the more basic features for brevity, but further examples of OBJ files can be found here.
Beyond geometry information, an OBJ file can also contain organizational lines that allow for sub-geometry to be defined. These organizational groups can also be given names that can be read by external model editors.
Because an OBJ file only contains plain-text information, it becomes a good option for interoperability as it is then easier to serialize and transmit as a file, over HTTP, or really any other channel for communication between programs.
Our code starts by assuming that the program has access to the current Document and an ElementId for a FamilySymbol. To review: a "Family" that is placed in a Revit Model is represented in code as a FamilyInstance object which contains positional, rotational, and other instance data. This object is a child of a FamilySymbol object. A FamilySymbol defines the Geometry for a "Family" and is a shared parent for all FamilyInstances of the same type. Beyond this, there exists a true Family object that is the parent of one or more FamilySymbols and can act as a container for variations of similar FamilySymbols. This post will only focus on and interact with FamilySymbols.
The high-level structure of our function looks like this:
Here, we get the FamilySymbol and iterate over each GeometryObject associated with it. GeometryObjects can represents various types of 1D, 2D, and 3D forms, but we will consider only Solids. We start our OBJ file data by creating a StringBuilder which will help us build out longer strings than would be reasonable using simple string concatenation. This StringBuilder will then have appended to it our first line of the file. This is an "o <name>" line that tells any reading program that the following lines will contain geometry for the <name> object.
For each Solid, we will write a "g <name>" line that will tell the reader to consider each face as a separate grouping. This is technically optional, but can help for model organization.
This block is where we actually write the bulk of our file’s contents. First, we iterate over the Vertices member of the Face's Mesh and write each of these to the file as a "v <x> <y> <z>" line. Note that sometimes, you may have to swap the Y and Z coordinates to orient the output model correctly.
After we have the vertices, we generate the triangles that will define our geometry. Luckily for us, Revit exposes some nice methods to help with getting these triangles. We can first iterate over the total number of triangles in the mesh and process each one individually. In order to create a "f <v1> <v2> <v3>" line, we need the indexes of the vertices as they appear in the OBJ file. This means that we have to keep track of the global vertex index using an "indexOffset" variable. Between this and the index, as retrieved from the MeshTriangle, we can compute a global index value for the desired triangle vertex and write these to the file.
Finally, we increment our various counters to track offsets and face numbers across face and part iterations.
After we have our full StringBuilder object, we can write this OBJ to a file like so:
We are writing to the system’s Temp path for ease of access, but an actual destination is up to you as the coder. Because the OBJ is simply a string, there are other options than writing to a file. It could also just as easily be sent over HTTP to a remote server or used elsewhere in the program.
place material tag link
November 20, 2020
Happy Friday! I took a break last week because my husband and I took a road trip for a few days. It was great to get a break and relax. We had the chance to do some hiking in Zion which was beautiful! We even got some snow. Today, we’re back with the last post of the series. This will be the last plug-in for the year but I have some great content for the rest of the year including another guest post about exporting OBJ from Revit and some Dynamo scripts.
material tags and limitations
Our final step is placing our material tag. To do this, we want to place an IndependentTag
using the Create method. When we test our plug-in, though, there is one issue. The tags are
hosted correctly but show a ‘?’ in place of the correct tag text. It isn’t until you manually
nudge them that they show the correct tag information. I have done extensive research on the
issue and it seems to be a bug that people are aware of. Some people have posted about various
work-arounds but none of them worked for me. The most promising seems to be the one posted by
Leo on the
in August he writes:
“I'm not sure if this issue has been officially solved but I also encountered this problem a few days ago and finally found a workaround.
I tried to create a material tag with leader with Revit API but the tagtext was always a question mark and none of the solutions that I could find from the Internet ever worked. You have to manually click on the tag or move it a little bit to activate it to show the right tagtext but you couldn't activate it with codes which is quite frustrating. But after some more experiment I happen to find a workaround which is quite simple:
Always set bool addLeader to false when you use IndependentTag.Create() and then set tag.HasLeader to true and LeaderEndCondition to Free, then you can place the LeaderEnd, LeaderElbow and TagHeadPosition wherever you want and the tagtext still shows correctly.”
He had also tested some of the previously discussed work-arounds and had no luck with those either. I tried the steps Leo outlined but could not get them to work for me in Revit 2019. I will try again when I upgrade to 2021, but maybe it will work for you. Having to nudge the tags is unfortunate, but not a deal breaker as this would still save significant time versus having to manually place all the tags so I will continue to keep an eye out for options in the future.
And that wraps up our material tagging plug-in. You can take a look at all the code on GitHub as well as download the compiled DLL to try. Next week, Andreas Brake will guest author a post about exporting families from Revit to an OBJ file.
tag location part 2 link
November 6, 2020
Happy Friday! Last week we looked at the simple way to get our tag location, this week we will look at adjusting the location to ensure it is in our view.
get elevation endpoints
The first thing we will need to do in order to ensure our tag is within our view, is to get the endpoints of the interior elevation view. We set up a new method which will return a Tuple to store our two endpoints. The only parameter that we need is the ViewSection. We can get the min and max values of the CropBox and assign them to variables. This will give us the opposite corners of the cube, but we want the opposite corners of the plane that forms the boundary of our view. This means the smaller endpoint will be the X and Y coordinates of the CropBox min and the Z coordinate of the CropBox max. The larger endpoint will be the Y coordinate of the CropBox min and the X and Z coordinates of the CropBox max.
tag location in view
We can then use the endpoints we just found to adjust our GetTagLocation method from last week. We still get the endpoints of the wall, but we then need to transform these coordinates into the view’s coordinate system because they are located in the project coordinate system.
After we transform the endpoints, we need to confirm that endpoint 1 is still the min and endpoint 2 is still the max, if not, we need to swap those values. Then we can compare the endpoint of the wall to the endpoints of the view. If the minimum value of the wall’s endpoint is smaller than the view’s, we know that the end of that wall falls outside of the current view. In that case, we want to take the view’s endpoint instead. We also check the same for the maximum. Then we use the updated endpoint values to get the center of the part of the wall that is visible in the view.
We transform this coordinate back into the project’s coordinate space and then can offset by two feet again so the tag sits below the view.
And that is how we can adjust the location to be within our view and centered on the visible portion of the wall. Next week, we’ll cover how to place the tag now that we have the required parameters and we will look at some of the challenges with placing material tags through the API.
tag location part 1 link
October 30, 2020
Happy Friday and Happy (almost) Halloween! I never thought there would come a year where I was more excited about spending halloween on our animal crossing island, but somehow this year, that’s where all the decorations and costumes are. I hope you all enjoyed the last week of October and still get to have some halloween fun.
A straight forward way to find a location for the material tags is to find the center of the wall. We can start our new method which will return the wall center. We just need the active document and the wall as parameters. Then we can take the wall and get the endpoints of the location curve. We add the endpoints and divide by two to get our wall tag location. This centers the tag on the wall. We also want to consider at what height we want the tag to be located at. In this case, I want the tag to be located under the wall so it sits at the bottom of the interior elevation view. This means we need to adjust the Z value. Since the Z we have is from the wall’s location curve, it is located at the bottom of the wall. We can move it down by another two feet to offset it from the bottom. You can adjust this offset to fit your needs. Then we return our updated location.
That is the simple way to get a location for our wall tag, but there may be a few additional considerations we want to take into account. Next week, we will look at adjusting the location to be centered on the wall within the elevation view so that our tag falls where expected. We want to consider this since the same wall can run along multiple rooms, thus the center could end up outside of our view.
collect elements in view link
October 23, 2020
Happy Friday! This week I am starting a new series about a plug-in for tagging materials in views. I’m going to focus on tagging walls in the interior elevation views, but you can also adjust it for other views. There’s a few challenges I ran into after placing the tags with the API, but I still want to share the process.
We need two methods to collect the views we want. The first is the filtered element collector to get the interior elevation ViewTypeId. We look for all view of Type elevation and that contain the string “int”. If you want to collect other types of views, like floor plans, you can adjust the filter accordingly.
The second method then collects all the views that match the TypeId we just found. In our case, it filters down to the interior elevation views in the project.
Then we can use another filtered element collector to get all the walls for each of the views we collected. We want to tag walls since we are looking at the interior elevations views. If you are applying this to plan views, for example, you can collect the floors instead.
And in a few simple steps, we have a list of all the elements that we want to tag. Next week, we’ll take a look at determining the tag location.
Async Operations for Revit Plug-ins link
With guest writer: Andreas Brake
October 16, 2020
Happy Friday! This week I am excited to welcome back Andreas Brake as the guest writer for this post. Andreas was my partner in crime for our project StreamVR which won the Best Overall Project Award at the ENGworks Hackathon. He is a software engineer at Topgolf Media doing server development. In his free time, he enjoys hiking, gaming, and gardening. This week, he will discuss how to perform async operations within Revit, a feature we used when working on StreamVR.
When developing Revit plug-ins, one will eventually run into the issue where the structure of the standard "tutorial" plug-in has a specific shortcoming: this is the inability to run in parallel or in the background with Revit or other plug-ins. This blog post will be going over ways to structure your plug-in to allow for asynchronous and multi-threaded activity.
For a standard add-in with a ribbon, your application has one main class that implements "IExternalApplication" this class is registered in your ".addin" file and acts as your entry point. This class defines two methods named "OnStartup" and "OnShutdown" that are called when Revit is started and shut down. These hooks are frequently used to create ribbon buttons that trigger additional commands.
Commands are classes that implement "IExternalCommand". They implement a method called Execute which, as the name would imply, is the method that is called when your command is executed. In simpler plug-ins, you can also just have a command which is registered in your ".addin" file.
When a command is executed, it blocks other activity within Revit. This means that you cannot run other plug-ins at the same time or run asynchronous operations within the plug-in that still operate on the Revit DB.
summary of changes
The major change we are doing to allow for background operations is to have our main application contain a "static" reference to itself. This static reference is assigned "OnStartup" and will act as a singleton that can be used by your other commands. It will persist throughout the Revit application lifecycle. This singleton can be used to create listeners for asynchronous events, for example a form that takes user input. We ensure that events from this form trigger a class that implements "IExternalEventHandler" which will perform additional background operations that can interact with the Revit DB.
The code below shows the application code that registers itself as a singleton.
Later a command is triggered by a button on the Revit ribbon which then calls "ShowFormInBackground" on this singleton.
"ShowFormInBackground" creates instances of all "ExternalEvent"s that will be triggered by our async operation, in this case a button press in a WPF form. The method also creates and shows the form, passing through the event objects for later usage.
Because the "ExternalEvent" objects are passed to the WPF form, we can call the "Raise" method on these events when a user clicks a button in the form. When "Raise" is called on a class that implements "IExternalEventHandler", the "Execute" method is triggered with a reference to the UIApplication. This means that we can now run additional operations against the RevitAPI including updating elements all without relying on a command that blocks all other operations within Revit.
because "ExternalEvent"s are intended to be called by external sources, we can even go so far as to trigger this event within a Task which will run elsewhere within the ThreadPool!
format selector - read and re-assign saved properties link
October 2, 2020
Happy Friday! Today is the last post in our brief series about text in Revit. We will put together our TextPainter tool. As we did in our previous tools, we have a method that allows users to select the text boxes that they want to paint the saved formatting to, but since we have gone over selection previously, I will jump right in to the next step: reading the properties saved in our container file. You can check out the full code on my GitHub repo.
In order to know what properties we need to assign, we first need to read our container file. We can create a new method called TextFormattingContainer and get the file. We can then read the bytes and assign the container to a variable which we will return to use in our SetTextProperties method.
In the SetTextProperties method, we want to make sure we have the document, selected TextNote, and the formatting container as parameters. For each formatting item, we can then go through and assign it to our TextNote after getting the formatted text. Our text Type can be reassigned easily by getting the element that has the ElementId that we had saved. Then we can just assign this TextNoteType to the selected TextNote. For the all uppercase status, we want to go through and change the plain text string to all upper case like we did in the first post of the series. The bold, italic, and underline statuses can also be easily set using their respective Set methods. Finally, we need to make sure we assign the revised FormattedText back to the object.
And that wraps up our text tools. We walked through a few quick steps to learn more about text in Revit. We built a fun little tool to save text formatting from a TextNote and another to re-assign the formatting to a different TextNote in the project. You can download the compiled DLL on my GitHub repo.