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.
format selector - container to save properties link
September 25, 2020
Happy Friday! Now that we found all the text formatting properties that we want to save, we need to save them out to a file that we can read from whenever we want to paint our formatting. That is what we will review this week.
container to save formatting
First, we need to create a container where we can save our formatting. We can create a new class in the same namespace, and we want to explicitly state it is Serializable. Our class will be called TextFromattingContainer and will contain a member for each of the properties we want to save. We need to make sure the variable type is the same as the value we will store there: an integer for the textNoteTypeID, a Boolean for the textNoteAllCapsStatus and FormatStatus for the others. We also want to make sure we can get and set each member.
save text formatting properties in container
Then back in our FormatSelector class, we can assign the formatting statuses we gathered to the members in our TextFromattingContainer. We create a new container and assign each variable. Then we want to convert the container to bytes and write all the bytes to a new file located in the same folder that our plug-in is saved to. This means that even after closing a session of Revit, we can read from this file and whichever formatting was last saved can continue to be applied to any additional TextNotes. This can all be added to the SaveTextProperties method we wrote last week.
Here is the summary of last week's method with the updates we made:
And that is how we can save the formatting data to later apply to any TextNote in Revit. Next week, we will wrap up this series and cover how to apply the formatting to a TextNote.
format selector - text properties link
September 18, 2020
Happy Friday! This week we will look at some of the other properties we can save and change through the API and start working on a little tool to save and then re-assign text formatting from one TextNote to another.
select text with ISelectionFilter
First, I will briefly go over the ISelectionFilter I used to select text boxes. ISelectionFilters can be used to limit the type of element a user can select in the Revit model. Since we want to limit this to TextNote elements, we can create a new public class and call it TextNoteSelectionFilter. It extends ISelectionFilter and allows elements with the category name Text Notes. Then we can use this as an input when we use the PickObject method so users can only select the type of element we want.
In our SelectText method, like the one we used last week, we can then use this filter and users will only be able to select TextNote elements.
text properties to save
Since we have the TextNote input, we now want to save all the formatting parameters from that TextNote. For the format painter we are working on, we will want get the text Type property and also see if the string is all uppercase, bold, italic, or underlined. To get the type, we can just get the TypeId as an integer and this is the value we will then save. I was having issues when using the GetAllCapsStatus method, so I did my own check. I check all the characters of the plain text string against a string of lower case values. If there are any lowercase values in the string, we set isUpper to false. This lets us save a boolean value which represents if the text note is all caps. The reason we check against a lower case string instead of an upper case string is that we do not want the all caps value to be set to false if there are special characters. All we care about is if there are any lower case characters present and it is simpler to complete that check rather than have to also ignore special characters. The bold, italic, and underlined status are easy to find with their respective get methods. We save these values to variables so that we can later save these values to an external file.
That wraps up how to get the status of all the formatting from our text box. Next week we will take a look at how to save this information in a separate file so we can read it later.
change text case link
September 11, 2020
Happy Friday! The next few weeks I'm going to do a short series related to text. I hadn't work with the text in the Revit API before so I just choose two simple tasks to try out a few things and better familiarize myself with it. TextCase allows you to select a TextNote and changes all the text in the TextNote to uppercase. Format Selector and Painter is a simple text format painting tool that saves any text overrides from a selected TextNote and applies them to another TextNote. The goal was to understand how to manipulate text formatting through the API and have these tools to build upon.
select text box
This week we will start with the TextCase plug-in. There are two parts to it - selecting the text box and changing the case to upper case. To select the text box, we can create a new method and call it SelectText. It will return the list of TextNotes. We use a selection filter to select the text boxes and then we need to take each Reference, cast it to a TextNote, and if the cast succeeds, add it to our list.
text to uppercase
Then once we have our text notes, we want to change the case of our text. We can start a new method and call it TextToUPPERCASE. It will not return anything. Our method needs the UIdoc and the text we selected as parameters. To access the formatting, we need to use the GetFormattedText method and we also need the plain text by using the GetPlainText method. We assign both to variables to make them easier to reference. In order to change text to upper case in Revit, you need to take the plain text and make all the characters uppercase. We can do this using the standard C# ToUpper method. Then we simply set the TextNote’s PlainText to the upperCase text we just made and then use the SetFormattedText method to assign the updated FormattedText.
With only a few simple steps, we can see how we can manipulate text properties in Revit. Next week, we will look at some of the other properties we can adjust and start making a simple format selecting and painting tool.
WPF sliders [part 2] link
August 28, 2020
Happy Friday! Last week we looked at sliders and how to move and adjust a collection of sliders. This week, we will look at locking and resetting sliders when a material is deselected. First, I’m going to discuss how I start storyboarding and putting together a UI.
I like to connect the visual requirements with the code so I usually start by sketching out what I want the UI to look like. I think about the different features I want to incorporate and how to best control them in an interface. For example, for the randomizing curtain wall mullions plug-in I wanted to makes sure it was possible to randomly delete only the vertical mullions, only the horizontal mullions, or both. This lended itself well to a check box that activated each option and allowed users to customize their selection. Additionally, I knew I wanted to allow users to randomize the materials of the panels. This made me start thinking about sliders since you can easily see the ratio of each material you have selected. Then I added additional layers for more control like the lock option.
When selecting materials for your curtain wall, it is possible you may want to lock one material to a certain percentage and then adjust the rest of the sliders to adjust the ratio. This is why I have added a check box to lock a slider. When the lock is checked, we have the chk_sliderLock_Checked event handler that sets the slider and input box to be inactive. It then also ensures the value of the locked slider isn’t changed when our other sliders are updated. We have a second event handler that sets everything active again when we uncheck the box.
reset slider after material change
One additional item we need to consider is what happens when a slider was set to a material and the user changes it back to “none”. In this case, we want to make sure the slider amount is redistributed to any active sliders and that the slider with no material selected is set to be inactive. This is exactly what our cwPanelType_SelectionChanged method does when the material selection is set to “none”.
And that wraps up the Curtain Wall Randomizer plug-in. You can download the compiled DLL on my GitHub repo. Next week, I’ll start a new series where I start to explore some of API related to text.