lisa-marie mueller

lisa-marie mueller

bullet pont

format selector - container to save properties link

September 25, 2020

colored pencils writing title on paper and clock - in orange 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:

summary

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.

resources

If you want to learn to code and don’t know where to start check out my posts about Steps to Learn to Code [for architects and designers] Part 1 and Part 2.

Revit API Docs

bullet pont

format selector - text properties link

September 18, 2020

colored pencils writing title on paper and clock - in red 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.

summary

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.

resources

If you want to learn to code and don’t know where to start check out my posts about Steps to Learn to Code [for architects and designers] Part 1 and Part 2.

Revit API Docs

bullet pont

change text case link

September 11, 2020

colored pencils writing title on paper and clock - in blue 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.

summary

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.

resources

If you want to learn to code and don’t know where to start check out my posts about Steps to Learn to Code [for architects and designers] Part 1 and Part 2.

Revit API Docs

bullet pont

WPF sliders [part 2] link

August 28, 2020

typewriter, Cuckoo Clock, marbles in orange 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.

UI storyboarding

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.

slider lock

WPF show sliders lock and unlock

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”.

summary

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.

resources

If you want to learn to code and don’t know where to start check out my posts about Steps to Learn to Code [for architects and designers] Part 1 and Part 2.

Revit API Docs

bullet pont

WPF sliders [part 1] link

August 21, 2020

typewriter, Cuckoo Clock, marbles in blue Happy Friday! I hope August is treating you well. Our family has lots of celebrations in August including my wedding anniversary and my mom’s Birthday within just a few days of each other. I needed to take a quick break last week but have regrouped and prepared a post on sliders in Windows Presentation Form (WPF). When creating a user interface, the interactions can have a lot of pieces. When you incorporate user selection, you need to consider all of the different scenarios.

For the curtain wall randomizer, we have the material selection which involves a handful ofdifferent parts that work together. We use the material selection combobox, slider, textbox, and lock option as components to allow users to enter information. All these elements need event handles and this is what I will discuss this week and next week. For user interfaces, it’s important to keep in mind that intricate interfaces take time to storyboard and implement. For the Randomizer plug-in, I spent almost as much time on the UI as I did on the plug-in. The methods used by the UI, which includes event handlers and update methods, are located in the code-behind file for the form, which is the .xaml.cs file.

First we need to store a few values. A list of tuples helps store the various WPF form elements we need so we can easily access them in all of our methods. We also need a list to store the integers for the slider values. We’re going to read and write to this list throughout the process.

set slider value

The first method I am going to discuss is setSliderValues. This method takes the slideValues list and assigns them to the correct slider. This method, along with a few others reference the equalizeSliders method, which is a longer, more intricate method that ensures all of the sliders add up to 100. I’m covering a handful of different methods this week on a higher level, so I won’t be discussing the equalizeSliders method in detail. You can however take a look at how it works on GitHub.

value change via slider

WPF slider changes update all other sliders to keep a 100% total

In addition to having a method to update the sliders, we need to consider what happens when you move the slider back and forth. This change is handled through an event handler on the slider itself. The event handler updates all of the slider and the sliderValues list as you move the slider back and forth.

value change via text input

WPF number input changes update all other sliders to keep a 100% total

Additionally, it would be nice to have a text input that allows users to enter the percentage instead of using the slider. This way, you can be more precise in the percentages or enter them quickly if you know how the different materials should be allocated. The sltext_LostFocus method takes the value entered in the textbook and updates all the sliders.

summary

And that covers a few of the considerations for using sliders in WPF. Next week, I will discuss two additional features I integrated into the sliders: the lock feature and resetting sliders.

resources

If you want to learn to code and don’t know where to start check out my posts about Steps to Learn to Code [for architects and designers] Part 1 and Part 2.

Revit API Docs

bullet pont

input validation for WPF user interfaces link

August 7, 2020

typewriter, Cuckoo Clock, marbles in blue User interfaces allow you to customize your plug-ins and provide a better experience. Your UI can be quite simple or more complex to allow users to control a variety of inputs. I’ve posted a few times about the use of Windows Presentation Form (WPF), which is one common way to create user interfaces. There are a lot of options that allow you to customize the interface. You can check out Microsoft’s documentation if you are looking for a place to get started.

goals and considerations

  • create the grids by entering width and height information
  • randomly delete curtain wall grids but allow for user configurations
  • randomize curtain wall panel materials
  • option to remove mullions

WPF form to allow for wall selection

Today, I am going to discuss data validation, specifically for user selected elements. If you are allowing a user to select a Revit element in the project, it is important to confirm the element is of the correct type. This way your application will not throw an error. We can confirm the element type by first assigning our user selected element to a variable or putting multiple elements in a list. For our Curtain Wall Randomizer, the user only selects one element so we can assign it to the variable userObjectSelection. Then we can use the GetElement method to select it and cast it to a Wall because we are expecting a Wall. Since we are specifically looking for a curtain wall, we also need to check that the wall has a CurtainGrid which is a defining factor of a curtain wall. If this check returns null, we know the user did not select a wall and they need to try again. If they did select a wall, then we can move forward and run the rest of our application.

summary

The steps to verify this information will take place in your main method. Similarly, you can check for other element types or iterate over a list to review multiple elements. Next week we will look at one more UI feature, sliders.

resources

If you want to learn to code and don’t know where to start check out my posts about Steps to Learn to Code [for architects and designers] Part 1 and Part 2.

Revit API Docs

bullet pont

delete mullions link

July 31, 2020

typewriter, Cuckoo Clock, marbles in green This week, we complete the final step before reviewing a few things in the user interface. In addition to modeling curtain walls with randomized panel sizes and panel types, users may want to model panelized rain screen systems. Our ‘randomizer’ makes it easier to model both systems. One key difference is that the curtain wall system will have mullions but a panelized rain screen system, will not need mullions. Therefore, we will want to allow for the option to delete the mullions.

goals and considerations

  • create the grids by entering width and height information
  • randomly delete curtain wall grids but allow for user configurations
  • randomize curtain wall panel materials
  • option to remove mullions

In order to delete the mullions on the curtain wall, we will create a new method that does not return anything. The parameters we need are our Document, UIDocument, and the curtain wall we are operating on. First we get all the mullions’ Ids and add this to a list. Then we iterate on each mullion. Before we can make any changes, we need to unpin the mullion and then we can simply delete it.

summary

And those few steps allow us to delete the mullions. Next week, we’ll take a look at input validation for data entered through a WPF user interface.

resources

If you want to learn to code and don’t know where to start check out my posts about Steps to Learn to Code [for architects and designers] Part 1 and Part 2.

Revit API Docs

bullet pont

randomize curtain wall panel types link

July 24, 2020

typewriter, Cuckoo Clock, marbles in red Now that we have all the panel types that we gathered last week, we’ll want to randomize the panel types. For this plug-in, I limited the input to a maximum of five different panel types which will each be a different material. The five panel types are a user input in our UI and the user will also be able determine the mix of each material.

goals and considerations

  • create the grids by entering width and height information
  • randomly delete curtain wall grids but allow for user configurations
  • randomize curtain wall panel materials
  • option to remove mullions

gather and unpin panels

We are going to create a new method called RandomizePanelMaterial that does not return anything. The parameters we need for our method are the Document, UIDocument, and the Curtainwall we are randomizing. We also need the percentage of each material (mtl#Max) and the curtain wall panel type (mtl#) both of which the user selected. To start, we need to get all the ElementIds of the panels on our curtain wall and add them to a list. Then we can use the GetElement method with the ElementIds as a parameter to return the element object. This way, we can iterate on each panel. Before we can change the type, we will need to unpin each panel.

randomize types

In addition to getting our curtain wall panel at the beginning of our foreach loop, we will also need to get a random number. Based on the user selected material mix, each panel has a percentage represented by an integer between 1 and 100. We have a maximum of five panel types but if the user has fewer than five panel types, the values for the unused types will simply be zero. In our execute method, we calculate our mtlMax variable. All we do is add the previous materials’ percentages to the current material. For example if we have material A, B, and C with the mix 10, 30, 60, mtlMax1 would be 10, mtlMax2 would be 40, and mtlMax3 would be 100. Our random number will be between 0 and 99. In order to assign the material, we will see in what range the random number if. If it is between 0 and 10, we will assign material A. If it is between 11 and 30, we will assign material B. If it is between 31 and 100, we will assign material C.

summary

Now that we have randomized the panel materials, we have one last step before looking at a few things in the UI. Next week, we’ll incorporate the option to delete the mullions.

resources

If you want to learn to code and don’t know where to start check out my posts about Steps to Learn to Code [for architects and designers] Part 1 and Part 2.

Revit API Docs

bullet pont

get valid curtain wall panel types link

July 17, 2020

typewriter, Cuckoo Clock, marbles in orange A few weeks ago, I took a pause from discussing our plug-in on randomizing curtain walls because Andreas and I entered a hackathon and we wanted to share some information on our project. This week, we’re back to curtain walls and switching gears from the curtain wall grid lines to the curtain wall panels. In addition to randomizing curtain wall panel sizes, we also want to be able to randomize the material.

goals and considerations

  • create the grids by entering width and height information
  • randomly delete curtain wall grids but allow for user configurations
  • randomize curtain wall panel materials
  • option to remove mullions

GetValidTypes

The first step to be able to change the material of the panels is to get a list of all the options. This is where data type validation comes into play. Generally when you want to collect all the elements of a certain type, you use a filtered element collector. For curtain walls, this can return panel types that, if used, throw an error that they are invalid. In order to avoid this, we want to first get the ElementId of the first panel of our selected curtain wall since we know this is a valid type. Then we can use the GetElement method to retrieve the panel Element and assign it to a variable.

To put together our list of valid curtain wall panels, we use the GetValidTypes method and add these valid types to a list. In our valid panel types, it is possible to have some that aren’t panels for curtain walls. To filter out those, we need to check if the name of the Category is “Curtain Panels.” Once we’ve checked for this, we have a list of curtain wall panel types that can be used to switch out the panels of our curtain wall system.

conclusion

Now that we have all the types of panels we can use, our next step will be randomizing the panels. Next week, we will walk through how to randomly assign a panel type based on a user entered ratio.

resources

If you want to learn to code and don’t know where to start check out my posts about Steps to Learn to Code [for architects and designers] Part 1 and Part 2.

Revit API Docs

bullet pont

Streaming information between the RevitAPI and a VR application (StreamVR) link

With guest writer: Andreas Brake

July 10, 2020

computer with StreamVR logo, oculus quest and controllers, clock, falling abstract objects blue Happy Friday! This week I am excited to introduce 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 dive into the technical details of our VR application and how we set up communication between the RevitAPI and StreamVR.

communication

To start with, I will discuss how we communicated information between the Revit plug-in and the VR Application. There were many options to weight when making this decision. We first looked at the option of running an HTTP server from the Revit plug-in that would act primarily as a proxy for the RevitAPI. This setup could allow us to perhaps even go so far as to expose the RevitAPI via GraphQL or other information retrieval frameworks popular in the web world. We ran quickly into a few problems with this. Firstly, due to corporate computer policies, it was not guaranteed (in fact very unlikely) that the Revit application would have sufficient permissions to expose HTTP ports on the machine and allow incoming traffic. Furthermore, we wanted to maintain the possible use case of having an architect or designer drive a meeting with clients which would require multiple applications connected to the model and staying in sync if any changes occurred. This scenario became dramatically more complex using HTTP calls.

Therefore, we decided to communicate our data over a message bus. Simply put, this is a program that allows for applications to push bits of information and have other connected applications listen to and process these messages. There are many options for messaging buses from MSMQ to RabbitMQ but we ended up using NATS as, among other reasons, it is a very light-weight solution that is deployable on most operating systems.

Using a messaging bus allows us to communicate between Revit and our Application as follows. We first stand up a nats-server on a machine that allows it. This means that a Revit user who has some admin privileges can run this locally. Alternatively, in a corporate setting, the organization’s IT team can deploy this to the local network and ensure a fixed IP or DNS to this server. Next, both the Revit plug-in and the VR Application connect to the server’s address. The plug-in subscribes to the TO_SERVER channel and the Application pushes requests and commands to this channel which the plug-in is now able to process and reply to.

revit plug-in

VR is a powerful tool to convey design intent. Architects, clients, designers, contractors, and engineers can utilize VR to simulate use cases and to make decisions while experiencing the space. Although there are tools to view BIM (building information models) in VR, so far, the process has been linear, exporting a model from Revit into a rendering software. There is a disconnect between VR and BIM. By allowing users to make changes in VR and synchronize their changes back to Revit, design teams and owners can have more fluid conversations allowing design and documentation to exist in the same space.

The reason for the application hanging is because the plug-in is constantly looping and listening for new additions to the message queue. Every loop, it will attempt to pop a command from the queue and process it based on the message contents. These requests will generally return one or more RevitAPI objects that have been converted to a data transfer object (DTO) which can then be serialized and understood by our VR Application. These DTO schemas will be further discussed shortly. There are six currently supported request types and seven transfer objects.

data transfer objects

Data Transfer Objects (DTOs) are the intermediate link between Revit and the VR application. Our Revit plug-in takes the features of a Revit object and saves these as parameters of a DTO and then can translate those parameters to the VR application and vice versa. Most of our DTOs inherit a simple Element class similar to the RevitAPI. Our base Element however only has 2 fields: Id and Name. Some of our objects, such as Family and Material, do not currently have additional data beyond these fields. For these cases, the Name field is being used to resolve an existing locally defined Prefab or Material in the VR application. The Id is then used to link these Unity GameObjects to FamilyInstances and material-defining Faces.

More complex objects include Wall, Floor, and Ceiling which are similar to each other as, beyond their inheritedId and Name fields, they primarily define a list of Faces that represent their geometry. This was the simplest way of telling our VR Application how to render these objects. The Application reconstructs the meshes based on the provided vertices and indexes.

The final DTO we define is the FamilyInstance. This object has the additional fields of a HostId, FamilyId, and a Transform object, defining the object’s position and rotation. Our focus for this hackathon was to provide the option to move, rotate, and place this DTO through the VR application.

revit plug-in supported requests

GET_ALL

Param: Type
Return: All objects of given type
Description: This request first takes the passed "Type" and uses Reflection to resolve the c# Type from the RevitAPI assembly. If the Type correctly resolves, we then use a FilteredElementCollector to get all objects of this Type from the Revit model which we then attempt to convert to the corresponding DTO.

Request

Response

GET

Param: ElementId
Return: Object with corresponding ElementId
Description: This request differs from the GET_ALL request as it only takes the ElementId for the RevitAPI object. This ElementId is used with the Document.GetElement method to retrieve the corresponding object which is then converted to its DTO and returned.

Request

Response

SET

Param: DTO
Return: Updated DTO
Description: The set request takes in the entire DTO as a parameter. First, this process takes the "Id" field and resolves the corresponding RevitAPI object. Then, it creates a transaction and passes both of these objects through the defined reverse mapper for the object’s Type. The fields that are able to be updated depend on what we support through the reverse mapper. Some fields are strictly read-only (e.g. Id) and therefore will not be updated even if the corresponding DTO field has a new value. In order to ensure that the VR Application has the correct values after this operation, the updated RevitAPI object is converted to its DTO and returned.

Request

Response

PAINT

Param: DTO
Return: Updated DTO
Description: This request is similar to SET as it also takes in a DTO to be updated. The primary difference comes in that the only DTO type supported is a "Face" which corresponds to the paintable Autodesk.Revit.DB.Face object. The process here is very straight-forward. The MaterialId and ElementId fields are extracted from the DTO and used to resolve the RevitAPI objects corresponding to the new material and parent object for the face. Then, the exact face of the parent is identified using the provided FaceIndex. The new material is then applied using the existing Document.Paint method. As usual, the updated face DTO is returned.

Request

Response

CREATE

Param: Base object details
Return: DTO for created object
Description: Create operates differently than our previous requests as it needs to create a new RevitAPI object based on base fields. Which fields are required for input depends on the Type to be created. For this hackathon we focused on support for creating new FamilyInstances. The fields required for FamilyInstances are the FamilyId and the Transform.Origin location where the object should be placed. The object is then placed using the Document.Create.NewFamilyInstance method.

Request

Response

EXIT

Param: none
Return: none
Description: This final parameter is very simple. It tells the plug-in to stop processing requests and return control to the Revit Application.

conclusion

And that sums up the work we did for the hackathon. Another huge thank you to Andreas Brake for guest writing this post. I hope you learned a few things to consider for your future projects. Check out our demo video and github to view the work we did! Next week, I will dive back in to the Revit plug-in for randomizing curtain walls.