lisa-marie mueller

lisa-marie mueller

bullet pont

lessons learned developing a VR app in Unity in 7 days (StreamVR) link

July 3, 2020

computer with StreamVR logo, oculus quest and controllers, clock, falling abstract objects blue Happy Friday! I ended up taking a break last week because Andreas and I decided last-minute to join the ENGworks hackathon. I’m so glad we did because it was so much fun! It was my first hackathon and I’ve been wanting to participate in one for a while now. ENGworks offered a great opportunity with their online event. If you missed it but you want to participate in a hackathon, AEC Hackathon is hosting an online event coming up and they have some excellent presentations scheduled. I will continue the series on randomizing curtain walls later this month, right now I wanted to take some time to provide more information on our hackathon project.

Although I did not post last week, I still kept my pledge to donate to a different organization each week for the month of June. Staying informed, sharing stories and perspectives, and even just taking some time to laugh is so important right now. This is why last week we donated to our local NPR station.

the hackathon

It was June 18th that I found out about the ENGworks hackathon and the registration deadline was June 19th. I spoke with Andreas about the hackathon and asked if he wanted to participate with me. He was on board and we immediately started brainstorming. I feel like I have new thoughts weekly about how I want to change the AEC industry with technology. For the hackathon, we had to find a problem we could solve in less than 4 days with 3 additional days of brainstorming and prep. Ultimately, we landed on our idea: StreamVR. We are excited to say we received the award for Best Overall Project for our application.

the problem

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 process

Our plan for setting up the project was two fold. One piece involved setting up the VR application that runs on the headset, the other was the Revit plug-in. For the Revit plug-in we set up a data bus to stream information from the Revit API to our VR application. Next week, Andreas will be joining as a guest writer and we will share more in depth information on the Revit plug-in. This week, I will discuss a few of the lessons we learned going from downloading Unity to developing a VR application for the Oculus Quest in 7 days. You can watch our demo video on YouTube.

lessons learned

VR development is changing fast

We learned this the hard way on day 3. After going through a number of tutorials for VR in Unity, I kept hitting road blocks. We found that most of the information was already out of date. We were originally referencing tutorials from late 2019 and even those released in November were already missing critical information. Thankfully we stumbled upon a youtube channel by Valem, the channel is also called Valem, that recently released a series of videos that introduces the Unity XR tool kit. If you are interested in developing for VR with Unity, I recommend you check out his Introduction to VR in Unity - UNITY XR TOOLKIT playlist. And we were on track again after going through his tutorials and able to move forward with our VR application.

understanding SDKs

Software Development Kits are a collection of development tools which include compilers, debuggers, and frameworks. For VR, there are SDKs that target specific headset devices. Unity has also developed an SDK that allows for functionality across different XR headsets. We started by using Oculus specific SDKs but our attempts were not working. We ultimately did not have time to fix all the bugs but thankfully found excellent resources for using the general Unity XR libraries which work across headsets. Ultimately, it’s a better path because, in theory, it allows for interoperability. If you are planning to develop in Unity, I would recommend reviewing and understanding the SDK options and how this fits in with your application.

prioritize and organize

When you have an idea for a project, it’s easy to want to complete everything and even easier to want to get the features you are working on to be perfect. I always try to remind myself: KISS - keep it simple stupid. And sometimes my husband has to remind me when I go down a rabbit hole. As with anything it’s important to prioritize. Identify your minimum viable product and work towards that goal. As new ideas come to mind, keep a list and add these features later. For the hackathon, we determined early on that our goal was to extract the model information from Revit and render it in the VR application. We focused on structural surfaces and FF&E families to limit the scope to something we could accomplish in 4 days. We also wanted to allow users to move families, create families, and paint surfaces in VR and have those changes synchronize back to Revit in real time. For the families, we started with only non-hosted families and we manually exported the FBX model files for testing. This helped to limit our scope and allow us to have a presentable project in just a few days.

conclusion

These are a few items that I hope will be helpful as you jump into your next project or hackathon. Next week, Andreas will guest write a post that dives into how we extracted data from the Revt model and communicated with the VR application.

Check out our demo video and github to view the work we did!

bullet pont

randomly delete curtain wall grid line segments link

June 19, 2020

typewriter, Cuckoo Clock, marbles in green My weekly donations through the month of June continue and this week, my love of theatre has influenced my selection. Musicals are my favorite but I love it all! Plays, operas, ballets, and concerts inspire me. I love when performances challenge your view of the world or allow you to see things from someone else’s point of view. Because of this, I have chosen to donate to the Fund for Black Theatre in the U.S. this week. The fund is locally organized on behalf of Sew Productions Inc and proceeds will be distributed to support Black artists and Black Theatre. If you remember, last week, we created our curtain wall grid lines. This week we will want to randomly delete grid line segments to get the effect of varying panel sizes.

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

randomly delete horizontal grids

We create a new method and call it RandDeleteHorizGrids. The parameters we need are the document, the user selected curtain wall, a user entered integer that we will use for the probability of deleting the grid, and the user entered information on the maximum number of grids that should be deleted in a row. It’s also important to note that in the execute method, we need to create a random number generator. The seed will be the date and time. It is important to only have one random number generator for the entire application to ensure sequences don’t repeat. If you use the same seed in a random number generator, you will get the same series of random numbers.

In our RandDeleteHorizGrids, we first need to declare our variables. Then we can take each horizontal grid and divide it into the individual curves. Each curve is a curtain grid line segment which we will delete or keep.

We are also using a counter to track how many gird line segment have been deleted in a “column” of the curtain wall. This allows the user to set a maximum size to each panel. For example, if you want a maximum 8 foot panel and your grid is 2 feet on center then you can remove a maximum of 3 consecutive gird line segments in any column. Our counter keeps track of this so if we reach the maximum, the grid line segment is not removed and the counter is reset to 0. Before we can start deleting curves, we need to confirm our removal counter list is initialized. If our removal counter list is null, we create a new list with zero at each index. The number of indexes is equal to the number of columns that our curtain wall has.

We are also tracking the index (i) of the curve in the list of curves. “i” starts at zero and while it is less than the total number of curves, we add 1 for each time we loop through. In our for loop, we take the curve at that index to perform our actions. We use our random number generator to get a random number between 0 and 99. Then we check if this random number is less than the user entered probability. For example, if the user chose the probability of grids being removed to be 30 then the grid is only deleted if the random number is less than 30. We also need to check if your counter is less than the maximum number of horizontal grid line segments that should be removed in a column. If both are true, we remove the grid line segment. If both are false, we reset the counter to 0.

randomly delete vertical grids

In this case, we do not need to make any adjustments to accommodate the vertical grid lines. We do just rename our variables for clarity. The reason I have the horizontal and vertical grids in different methods is that you may want to delete only horizontal grid line segments or only vertical grid line segments. Randomly deleting both means you will have non-rectangular shapes for your panels. Separate methods allow us to easily call each method.

conclusion

And that sums up how to randomly delete curtain wall grid line segments to have random panel sizes. These methods still allow for user control by allowing for input on the maximum panel size and on the probability of a gird line segment being deleted. Next week we will start to look at the curtain wall panels.

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

create curtain wall grid lines link

June 12, 2020

typewriter, Cuckoo Clock, marbles in pink As mentioned last week, for June I have pledged to donate to a different organization each week. This week, I have donated to my local food bank: the Alameda County Community Food Bank. If you are able, this is an excellent way to support others. Food banks are doing even more for our communities right now when other support may not currently be available like school lunch programs.

Last week, we found the minimum and maximum coordinate of the panel in our curtain wall so this week we can use it to create the grid lines. We covered briefly that to create a grid there are three parameters we need. These are a boolean to determine if it is a U (true) or a V (false) gridline, a position for placing the grid line, and a boolean to determine if it is adding the entire line or just one segment of it. We also know that the position will change because we need to step along the curtain wall to create multiple grid lines until we reach the end.

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

position - horizontal grid lines

We can create a new method to create the horizontal grid lines and call it MakeHorizGrids. We are not returning anything but we do have a number of parameters: the document, the curtain wall that the user had selected, the minimum height of the panels, and the minimum and maximum coordinates of the panel that we found last week. The minimum height of the panels will be one of the user inputs we need in order to create the grid lines.

We can use a while loop to check if the new position we are proposing is still located on the curtain wall by comparing the Z coordinate of the position to the Z coordinate of the curtain wall’s maximum. If it is less, then we are still locating the grid on the wall. For horizontal grid lines, we are locating the next grid up the curtain wall in the Z direction which means we can just take the minimum height and add it to the Z coordinate.

position - vertical grid lines

For the vertical grid lines we start with the same method but instead of moving up the curtain wall, we are moving along the curtain wall. In addition to the curtain wall min (the bottom left corner of the curtain wall), we also need the bottom right corner of the curtain wall - the other endpoint. We can create that from the X and Y coordinates of the maximum and the Z coordinate of the minimum. Since we are starting in the bottom left corner, to create vertical grid lines we are moving the position by some factor in the X and Y direction.

Starting with our curtain wall minimum, we know the length of the curtain wall is √((X2-X1)2 + (Y2-Y1)2). The vector for the direction we need to move is the bottom right corner (btmCorner) minus the bottom left corner (cwMin) divided by the length of the curtain wall (distance).

We can again use a while loop to check if the distance we have moved is less than the total distance minus the minimum width of a panel to ensure the next grid line is still located on the curtain wall. If it is less, then we can add our minimum width multiplied by our direction vector to the X and Y of the coordinate to move along the curtain wall to create the next grid line.

conclusion

Now that we have the correct grid lines created for the curtain wall, we can tackle randomly deleting grid line segments next week.

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

curtain wall panel min and max link

June 5, 2020

typewriter, Cuckoo Clock, marbles in blue This past week, I have taken a lot of time to think. I thought about the impacts we have on the lives of others. What can we all do to better the future? I encourage everyone to take a minute and do the same. I went in circles about posting a topic this week. In such challenging times, when so many people are hurting, it’s easy to feel like there is nothing we can do to help. I believe that what I can do is to better myself and educate myself and help others grow as well. This week, I have donated to the NAACP Legal Defense and Educational Fund and I pledge to donate every week through the end of June to a different organization that helps bring about the change we need. USA Today wrote about 100 Ways You Can Take Action Against Racism Right Now. Not all of the actions require a monetary commitment, so I encourage you to review the resources they have collected.

For this week’s content, I would like to continue looking at the curtain wall grid. Last week, we deleted all the grid lines, so this week we can create new ones.

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

First we will want to take a look at the method we are using: AddGridLine. There are three parameters we need. These are a boolean to determine if the grid line is a U (true) or a V (false) grid line, a position for placing the grid line, and lastly a boolean to determine if it is adding the entire line (false) or just one segment of it (true). Determining the boolean values are straight forward, but the position can be a bit tricky.

position

To determine a position for grid lines, I need a coordinate that is in a corner of the curtain wall. This way, I can then add the width or height of the panels to get to the next curtain grid line until I reach the edge of the wall. My first thought was using the bounding box of the curtain wall. When I printed out the minimum coordinate of the bounding box and the coordinates of some of the curtain wall panels, they were not in the same plane. I started thinking that since all the grids are removed, we have one large curtain wall panel. If I can locate the minimum coordinate of the curtain wall panel, I can use this as the starting point for creating the grids. A curtain wall panel does not have any properties that are minimum and maximum values. However, we know that it is made of CurveLoops, which are just lines, which all have endpoints. So finding the minimum coordinate had two steps: collect all the endpoints and find which endpoint is the minimum coordinate. I should also mention that I am targeting to create the CurtainGrid on a flat curtain walls so there are some assumptions we can make.

curtain wall panel coordinates

I start my method and call it GetCWMinMax. The easiest way to store these coordinates is in a tuple so this is what we will return. The parameter taken in by the method is the curtain wall that the user selects. First we declare the variables we will need: the tuple (which we will return with the min and max coordinates) and a list of all the coordinates.

To get the list of coordinates, we first need to retrieve the CurveLoops that construct the curtain wall panel. Since we know there is only one panel, we can safely use the first CurtainCell and get the first set of CurveLoops that defines it. A CurveLoop contains a list of curves, in this case we know that they are all lines because we are working with a single panel, which in Revit cannot be curved. If we add it to our list as a Line we can get the origin of the line so that we only add each endpoint once. Now we have a list of endpoints and we need to find the minimum endpoint. The method we use to find the min and max coordinates from the list can be used for many different situations. I added it to my Utility file so it is easier to use in the future and I called it GetMinMaxFromList.

min and max coordinates from list

The next task is finding the minimum and maximum values in a list of coordinates. The challenge here is that these points can be anywhere within the coordinate system. It’s much easier if all the coordinates are in the positive side of the coordinate system. Therefore, we want to find the smallest X and independently the smallest Y coordinates and shift all coordinates by these amounts. We can put this into a new list called xyzListAdjusted.

Now that all the coordinates are in the positive quadrant, we can use Euclidian distance, √(a² + b²), to find the coordinates closest and furthest from (0,0). Then we check which index these coordinates are located at so we can go back to our original coordinate list and find the corresponding coordinates. We then return these original coordinates as our minimum and maximum.

conclusion

Now we have the starting coordinate at which we can place our new CurtainGridLines. Next week, we will go over placing those lines along the entire curtain wall.

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 curtain wall grid lines link

May 29, 2020

typewriter, Cuckoo Clock, marbles in purple For my next series, I wanted to explore the Revit API a bit further and dive into Curtain Walls. I’ve found that a common Dynamo script is a script that randomizes curtain wall mullions. Its mentioned across many forum posts and resources. I wanted to make sure to say thanks to my colleague Payam Rad (who also posts helpful Revit tips and tricks you can check out) who requested this script and gave us the idea to put it together for our firm a few years ago. Panelized rain screens are still a trendy design feature and having a script to randomize these panels saves time. Since it is still in use, I wanted to explore the topic in more detail and also look at customization options that can more easily be incorporated through the Revit API. I think the biggest benefit to using a plug-in is the additional control a user has when presented with a well planned user interface.

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

overview

I would say that this plug-in is all about options. The goal is to create the most flexibility. A lot of this flexibility is accomplished through the user interface but it is also accomplished in how we write our methods. One of the options I wanted to incorporate was the option to create the CurtainGrid. This way you could even use the tool just to create a CurtainGrid. I incorporated a selection in the dialog box (more on this later) that allows the user to select if they would like to use the grid they already created or if they would like the plug-in to create this grid based on width and height information. I allowed this to be a user selected option because I understand you may also just want to randomly delete CurtainGrid Lines and use your own grid.

So the first overarching goal is to create a CurtainGrid for the CurtainWall using width and height information. There are a few steps we need to take to get there. One item to consider is what the input will be. In this case, the user will select a curtain wall in Revit. We need to assume they forgot to delete the grid. So if we are creating a grid, the first thing we actually need to do is delete the existing grid lines. This is what we will walk through today.

overview

We can start our first public method and call it DeleteAllGrids. We will not return anything. In Revit, CurtainWalls have CurtainGrids and these grids are separated into the U (horizontal) and V (vertical) grids lines. We can use the GetUGridLineIds and GetVGridLineIds methods to collect these elements. Revit also has a handy Delete method we can use. It looks like we just solved the problem in four lines of code, however this is not the case. It’s not that simple when it comes to CurtainWalls.

type association

In Revit, you can create CurtainWall Types that automatically create CurtainGrids when a user places that Type of CurtainWall. If a CurtainGrid is created through this way, it has a Revit parameter called Type Association which is set to “dependent”. If a CurtainGrid is dependent, you cannot delete it. When you manually set this Revit parameter to be “independent” through Revit, the parameter is hidden.

I was playing around a bit with Revit LookUp and logging some additional information and found out that the Type Association is stored as an integer. When the CurtainGrid is “dependent”, the Type Association is 1 and if it is “independent” it is 0. So we want get the Type Association parameter using the GetParameters method. If the Type Association is equal to 1 (dependent) then we want to make sure we set it to 0 (independent).

locked grids

Then we also have to keep in mind that a user can lock elements in Revit. This includes CurtainGrids, and you cannot delete locked elements. This is a very simple check. For each grid, we just want to check if the Lock property is true and if it is, set it to false.

delete grids

Finally, once we’ve done all the checks to make sure it is possible to delete the grid, then we can delete it. We can repeat the same for the vertical grids.

conclusion

And that wraps up the considerations for deleting curtain grids. Next week, we will start to process of creating a new CurtainGrid and discuss some of the challenges with finding the coordinates to use.

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

filtering with Revit parameters (python for Dynamo) link

May 22, 2020

laptop with title, cloud shaped balloons, rocket shaped balloon, orange and green When I first started my blog, my original intent was to cover equal parts Dynamo content and Revit API content. Due to numerous circumstances, I dove headfirst into developing plug-ins for Revit and haven’t spent much time in Dynamo since August of last year. Every time I did go in to write a script, I also encountered some of the same frustrations. Don’t get me wrong, I think Dynamo is invaluable, but if you don’t utilize the Revit API, there are limitations. Recently, I’ve been spending more time in Dynamo and am going to start interjecting some Dynamo posts on my blog. Of course anything you can do in Dynamo you can due purely in Python or C#, but I see value in creating graphs including ease of use and speed. I think there is a benefit to using Dynamo nodes but also to utilizing opportunities to make your graphs more efficient. Currently, I’m trying to collect Python snippets that you can use in a Python node in Dynamo to make your workflow faster and easier.

A minor note on terminology, when I am discussing parameters for elements in Revit I will do my best to always refer to them as Revit parameters to avoid confusion with method parameters.

collecting elements that have a specific Revit parameter:

Personally, one of my least favorite things to do in Dynamo is filter lists. I find filtering with nodes unnecessarily complicated and often I need so many nodes to filter out what I want. One of these examples is filtering for Revit parameters. Last week, for example, I wanted a Dynamo script to turn off the visibility setting of a certain instance Revit parameter in all the title blocks in a project. I wrote two helpful nodes that are super simple but save you so much time.

The first challenge I ran into is we had planning packages and construction document sheets set up and not all of them contained the Revit visibility parameter I wanted to toggle. So the first Python node finds all elements that have a certain Revit parameter.

The node has two inputs. The input at index 0 is a list of elements and the input at index 1 is a list of the element’s Revit parameters. You can get these inputs with Dynamo nodes (see graph at the end of this post). After creating a new empty list, the node then looks at each element and that element’s Revit parameters. If the element has a Revit parameter with the name “Consultant Stamp” it adds it to our new list. You can copy and paste the code below into a Python node in your own Dynamo script and change the p.Name from “Consultant Stamp” to the Revit parameter relevant to you. Please note when comparing strings, the name needs to be an exact match including spaces and capitalizations. The block then returns a list of elements that have a Revit parameter equal to the p.Name you specified.

filter list based on Revit parameter value

After I found all the title blocks that had the Consultant Stamp Revit parameter, I wanted to hide it if it was visible so I had to filter the list one more time. This time, I needed to filter by the Revit parameter value. This node will only have one input and that will be the list of Revit parameters of the elements we filtered with the previous Python node.

Because the Consultant Stamp Revit parameter is a visibility Revit parameter, a value of 0 means it is hidden and a value of 1 means it is visible. I want to check if p.Name is equal to “Consultant Stamp” and p.Value is equal to 1. In other words, I want to select all the elements with the Revit parameter “Consultant Stamp” equal to 1 which means it is visible. Then we can add the element to our new list. The output will be a list of the Revit parameters that we need to hide. We can do this using the Revit Parameter.SetValue node and simply set the value to 0. Remember to also update the p.Name in this node.

And this is what the graph looks like in use:

dynamo graph with collect elements with parameter node and filter list based on parameter value node

summary

And now when we connect our Python nodes with the other nodes, we can easily change the value of a Revit parameter. These nodes can be reused to filter for a number of Revit parameters for different types of elements, not just title blocks. I hope this gives you a few tools that you can use and modify to make your Dynamo workflow more efficient. Happy scripting!

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

tracking ROI link

May 15, 2020

ipad with title, large neon star, movie reel, marker, purple Happy Friday! Today I wanted to walk through an option for tracking ROI. It’s much easier to make the business case for the work you do if you understand how it’s impacting people’s every day process. Thankfully, you can log just a few pieces of information and receive a lot of insight!

platform

There are three main parts to tracking ROI data: the platform to track the data, the configuration file with the link, and the data you are tracking. There are multiple options for platforms to track this data. One option is Slack. You will want to create a web hook with Slack or your chosen platform. As always, before implementing processes with 3rd party applications, read and follow the license agreements.

config file

You may want to create a configuration file for your project so you don’t hard-code API keys or credentials. I would recommend building projects without the keys entered into the configuration files and then copy and pasting this information when you deploy it. This is more secure. Of course, it is important to know that the configuration file with this information will need to be on the computers of everyone who is using your plug-in and sending telemetry. Anyone with the credentials can send information to that channel. If your project is exclusively internal, this is an acceptable set-up, just know that this is how it works.

Once you have the configuration file you will need to add a method to read it. Our method is a public method and returns the Configuration. We need the location of the file as well as the tag we used. In my case, I called the URL “telemetryURL”. We can simply search through each of the elements in our config file and if the element has a tag “telemetryURL” we want to assign this value to the Configuration.

data

Then you can consider what data you would like to collect. Some options include the file name, date and time, and the name of the plug-in that was run. These are general pieces of information that can be included in our SendTelemetryData method. The ROI calculation is plug-in specific and can be added in the Execute method within the plug-in. Because I want to add plug-in specific information I have a parameter called “message” that allows me to also send any additional information that I want.

We can then format the information so it is easier to read and add any required description. I collect the username, date and time, and plug-in and format it into an easy to read string. Then I place the message on the next line. This is a utility method that I added to my Utility file so I can use it for all plug-ins.

ROI

The ROI calculation is entered as the message parameter in the above described method. This allows me to customize the information for each plug-in and still send the data I need. There may be slight differences in calculating ROI depending on the plug-in. Generally, I actually take the stop watch feature on my phone and time myself completing the tasks that the plug-in automates. I record it at least a few times to make sure there aren’t any outliers. Then, I find the appropriate relatable metric and multiply to get the total time saved.

For example, for the Automate Interior Elevations Plug-In, I timed myself creating elevations, renaming them, adjusting the crop, adding the view template, and adding the masking region for one room. In my ROI calculation, I take this time and multiply it by the number of rooms that the program goes through to get my total time saved.

I send data both when the plug-in started running and then ROI data when it has completed. This way I also know if people are canceling after opening it. When the plug-in is started, I read the config file and then simply send the message “Started”. This code lives in the Execute method of the plug-in.

When a plug-in finishes running successfully, I record the ROI information. For interior elevations, I calculated an approximate time savings of 8 minutes per room. I multiply this by the number of rooms in the project and return the time saved in hours and minutes.

summary

And these are the steps to start tracking the ROI of the plug-ins you make. I hope this will be helpful in understanding how your work impacts the time spent on projects.


Disclaimer: This post is intended to explain how to collect data relevant to tracking and understanding ROI. Please review any relevant privacy and protection policies before collecting data.

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

user wall selection link

May 1, 2020

ipad with title, large neon star, movie reel, marker, purple Happy Friday! As you know, I am tackling placing exterior elevations based on selected walls. This week we can add in the final piece: allowing users to select the wall.

goals and considerations

  • create an elevation marker for exterior walls by having the user select the walls
  • place the marker and elevation offset from the wall so it is legible
  • correctly phase the elevation markers
  • accommodate curved and angled walls

if you missed it:

Part 1: elevation by wall

Part 2: phases

Part 3: rotating elevation markers

Part 4: align ViewSection CropBox to Wall CropBox

Part 5: finding the wall normal for curved walls

Part 6: align elevation markers to curved walls

windows presentation foundation (WPF)

I’m starting to learn WPF so for this plug-in I made a very simple user interface with the instructions for using the plug-in. I’m just beginning to learn so it’s nothing complicated. I won’t walk through this portion because I have done a couple posts on user interfaces and there are a lot of WPF resources out there. In our execute method, we need to first create a new instance of our form. Our form needs the active document which we had previously assigned to a variable called UIdoc. In the WPF code, depending on if the user clicked cancel or accept, it will return false or true for the dialog result. If the user pressed the cancel button, we want to stop our application.

WPF form with instructions for exterior elevations

user wall selection

If the user did not cancel, then they are going to select the walls. We can use the PickObjects method for this selection. One thing to note, is that the user will be able to select things other than walls with this method. One way to filter the user’s selection is to use the method that allows you to enter an ISelectionFilter as an input parameter. You can also filter for it in your code. Since we know all of our elements need to be walls specifically, we can just take our list of user elements and see if they are walls and if they are not, we don’t add them to our list. Finally, we just assign our list of user selected walls to our variable for the list of walls that we use for our program.

summary

And those few steps are all it takes to add user selection to our plug-in. This wraps up the create exterior elevations plug-in. You can access the code on my GitHub account and complied DLL is there to download as well. I’m taking a pause next week and will return the week after with a new plug-in. See you May 15th!

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

align elevation markers for curved walls link

April 24, 2020

ipad with title, large neon star, movie reel, marker, green Happy Friday! For me, this marks the end of week 7 of quarantine here in the Bay Area. We’ve been trying to keep ourselves busy. One of the things my husband and I started doing is having Skype game nights with friends. We found some board games that work pretty well and we also purchased some computer games that are good for groups. I hope you are all staying healthy and sane.

As you know, I am tackling placing exterior elevations based on selected walls. Last week, we found the normal of curved walls so we could properly locate our elevation marker. This week we are going to offset our elevation marker so it correctly aligns with the curved wall.

I would like to add one note about a challenge that I found with the wall's normal. When I was testing this plug-in I had always created new curved walls to try it and had placed them where I wanted to test them. It wasn’t until I accidentally copied and pasted walls and then rotated them when I realized an interesting feature. The endpoints of a wall’s LocationCurve stay the same when you rotate walls. But the x and y direction parameter changes. This means that the walls are first created in the model coordinate space but when you rotate them they will be in a transformed space. The methods we used to find the wall's normal won't work if a curved wall was rotated after it was placed. I believe that this will apply to a small number of cases so I won’t be addressing it in this series. I did want to point it out so you are aware and can adjust your code to accommodate. I do have it on my list to address in the future.

goals and considerations

  • create an elevation marker for exterior walls by having the user select the walls
  • place the marker and elevation offset from the wall so it is legible
  • correctly phase the elevation markers
  • accommodate curved and angled walls

if you missed it:

Part 1: elevation by wall

Part 2: phases

Part 3: rotating elevation markers

Part 4: align ViewSection CropBox to Wall CropBox

Part 5: finding the wall normal for curved walls

orientation of curved walls

We will be adding a GetElevationMarkerPosition method which we will call within out PlaceMarker method that we wrote in Part 1. This will find the offset center for both curved walls and straight walls. Within our method, we add an if statement to separate our curved walls. We can do the same test for our if statement that we have done in the past. We try to cast the wall’s LocationCurve as an Arc and if that works then we know our wall is curved. After the last post Part 5, I have pullout out the code for finding the normal of a wall into its own method. This way we can use it again. I called it NormalofCurvedWall and output not only the angle of the normal, I also output a boolean parameter so we know if the wall is concave or convex. 

Within our if statement, we declare a few of our variables, call the NormalofCurvedWall method, and get the endpoints for our curve. This then allows us to get the angle that the curved wall’s orientation is at. This is the angle of the line that connects the two endpoints of the wall’s location curve. When we find the normal angle of the wall, that angle is relative to the model’s coordinate space, however, we need to find the angle between the wall’s normal and the wall’s orientation. So we update the wNormalAngle to be the wNormalAngle minus the angleLine EP1EP2. We add a negative because the axis of rotation for the marker is inverted. 

concave walls

Adjusting the normal is necessary for all curved walls. If a wall is concave, we want the wall center to be in between the two endpoints of the curve. That way, we can offset the marker’s location from this center by 5 feet and it will have the same offset as our straight walls. Our wall's normal is in radians and we need to find the vector for that angle so we can offset our elevation marker in the correct direction. To do that, we can use a rotation matrix and rotate our wall’s Orientation, which is the vector between the wall’s endpoints, and rotate it by the angle of our wall’s normal to get the wall’s normal as a vector. We also need to find the center which, for concave walls, we want to have as the center between the two endpoints.

convex walls

If a wall is convex, we still want to find the wall’s normal as a vector just as we did for concave walls. However, we need to use the negative of the wall’s normal as an angle. This is because a rotation matrix rotates the coordinate counter-clockwise when it is rotated about the Z-axis, but when rotating elevation markers around the Z-axis, it rotates clockwise. To accommodate for that, we use the negative of the angle. Our wall center for convex walls will be the center, plus the offset of the radius, times the wall’s normal. This will place it at the center of the rounded part of the curve, or the peak.

straight walls

This covers when our wall is curved, we also need our else statement for finding the center and the normal for a straight line.

offset

And finally, regardless of what type of wall it is, we can offset the center by 5 feet in the direction of the normal of the wall. This offset makes sure that the marker does not overlap with the wall. Since it is the same offset for curved and straight walls, we can do this at the end of our method and then return this offset center so we can use it in our PlaceElevations method.

summary

And that wraps up the adjustments we have to make to correctly place the marker for curved walls. The last item we want to update is adding the user selection for the walls so you can select the walls through Revit by clicking on them.

For reference, the NormalofCurveWall method. We put together the steps in Part 5, but I pulled it out into a separate method so we can use it again.

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

finding the wall normal for curved walls link

April 17, 2020

ipad with title, large neon star, movie reel, marker, light blue Happy Friday! I hope you had an egg-cellent easter if you celebrate or otherwise a good week.

As you know, I am tackling placing exterior elevations based on selected walls. One of our goals was to accommodate curved and angled walls. Our plug-in already accommodates walls at any angle. Now, we need to make a few adjustments to accommodate curved walls. This involves finding the normal for curved walls.

goals and considerations

  • create an elevation marker for exterior walls by having the user select the walls
  • place the marker and elevation offset from the wall so it is legible
  • correctly phase the elevation markers
  • accommodate curved and angled walls

if you missed it:

Part 1: elevation by wall

Part 2: phases

Part 3: rotating elevation markers

Part 4: align ViewSection CropBox to Wall CropBox

orientation of curved walls

When we test our plug-in with curved walls, we immediately notice that the elevation markers do not face the correct direction. Using RevitLookUp we can see that the Orientation of the wall does not face away or towards the curve, but instead is the vector between the wall’s endpoints.

Curved walls fall into two categories:
category A: concave when looking at the wall from the outside
category B: convex when looking at the wall from the outside

Additionally, you can draw walls two different ways, from left to right and from right to left. Wherever you click first, that is the endpoint that is stored in the first index when you retrieve the endpoints of the location curve. We will call these type 1 and type 2. I diagramed these four instances in the image below.

When looking at the Orientation property for the wall, it is possible to notice a pattern. For walls A1 and A2, the Orientation of the wall is the unit vector from endpoint(0) to endpoint(1). The Orientation flips if you draw the curve from right to left instead of from left to right. We will come back to this in a moment.

diagram of curved wall category A and category B with both variations

LocationCurve

For now, we are going to make the adjustment to accommodate curved walls in our GetAngleViewtoWall method which we had created in Part 3 rotating elevation markers. Originally, we had used the wall’s Orientation property which is the wall’s normal for a straight wall. As we realized above, the Orientation of curved walls is not equal to the wall normal. When you have a curved wall, the LocationCurve is an Arc instead of a Curve. We can use this to filter our if statement so the part of the code we are about to write only executes if the wall is curved. If the wall is not an arc, the variable wallCurveAsArc will return null.

Since the variable wallCurveAsArc tells us if the wall is curved, we can use an if statement that checks if the variable wallCurveAsArc is not equal to null. From there we can first get the curve’s endpoints. We also want to get the angle between the endpoints using ArcTan and declare our curveNormal variable which we will use later.

category A or B

We also need to figure out if a wall falls into category A: concave when looking at it from the outside or category B: convex when looking at it from the outside. We know that the Orientation of the walls that fall into category A is the unit vector from endpoint(0) to endpoint(1). This means that if the unit vector from endpoint(0) to endpoint(1) is equal to the Orientation, then it falls into category A. If they are not equal, it falls into category B. To simplify this comparison, we compare the signs for the X and Y value of the vectors to the signs of the X and Y values of the Orientation. This prevents any rounding errors that may happen if we were to compare the actual values.

First, we set up our Boolean variables.

Then we can use an if statement to find the correct curveNormal based on if the wall is in category A or category B. If the wall is in category A, the X and Y values of the unit vector from endpoint(0) to endpoint(1) is equal to the X and Y values of the Orientation. We simplified this comparison by only comparing the signs as described above. If the wall is in Category A1 then the curveNormal will equal the angle between the endpoints plus 90 degrees. If the wall is in Category A2 the curveNormal will equal the angle between the endpoints minus 90 degrees. To adjust if you add or subtract, we are multiplying it by the YDirection property of the Wall’s LocationCurve. This YDirection tells us if we flipped endpoints 0 and 1.

All walls that do not fall into category A, fall into category B. This is our else condition and all we have to do is change from addition to subtraction to accommodate the flipped condition. And that is how you find the normal of a curved wall!

rotating elevation marker for curved walls

Once we find the normal of the curved wall, we need to rotate the elevation marker that we placed for the wall by the wall's normal minus 180 degrees. During testing, I noticed that rotating the marker by a negative angle did not work. So if angleViewtoWall is less than zero, we want to add 360 degrees.

summary

Since we are modifying the GetAngleViewtoWall method that we previously wrote, we add the else statement at the end with the code needed for walls that follow a straight line. And that wraps up finding the normal and rotating the marker for curved walls. Next, we will look at how to place the elevation marker correctly for curved walls so it is not located in the middle of the curve.

UPDATE: 4/23/2020: I would like to add one note about a challenge that I found with the wall's normal. When I was testing this plug-in I had always created new curved walls to try it and had placed them where I wanted to test them. It wasn’t until I accidentally copied and pasted walls and then rotated them when I realized an interesting feature. The endpoints of a wall’s LocationCurve stay the same when you rotate walls. But the x and y direction parameter changes. This means that the walls are first created in the model coordinate space but when you rotate them they will be in a transformed space. The methods we used to find the wall's normal won't work if a curved wall was rotated after it was placed. I believe that this will apply to a small number of cases so I won’t be addressing it in this series. I did want to point it out so you are aware and can adjust your code to accommodate. I do have it on my list to address in the future.

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