This week we continue on the path to automating interior elevations. As a reminder, I have been building a plug-in that automates various parts of creating interior elevations, and I will continue to develop it. Since this project will evolve as I continue, I have determined to set my primary goal for each stage. With this goal in mind, I will develop a functioning piece of software to meet the goal. Then I will discuss topics that cover key aspects for each step for building the plug-in. At the end of each stage, I will move the plug-in to a public repository on GitHub so you can use it too.
Part 1: filtered element collector [c#]
Create interior elevations for all placed rooms, place the tag at the center of the rooms, place the tag on the correct level when multiple levels exist, ensure the tag is visible on the interior elevation key plans (already in the project), and ensure the interior elevation tags have the proper phase settings
Today, we will write the method that finds the center of all of our rooms. We need to find the center because to place an ElevationMarker using the CreateElevationMarker method, we need an XYZ parameter (known to most of us as a coordinate). I have chosen the center of the room for this purpose.
The new public method needs to return an XYZ parameter, and we can call it GetCenter. The new method accepts one parameter, the list of rooms that we found in Part 1. Let’s find the center! Finding the center of the rooms would be perfectly fine if every room in all of our projects were rectangular. The truth is, they are probably not. If we think back to math class, what we are actually finding is the centroid. Thankfully, finding the centroid has a very simple formula, average the coordinates of the corners for a 2D shape.
From reviewing our math skills, we have determined that we need to get the corners of the room. First, we need to collect the BoundarySegments of each room and then we can get the start and end points of each BoundarySegment. When using the GetBoundarySegments method, the default value of the SpatialElementBoundaryLocation Enum is the finish surface boundary of the wall, for our case this is fine because we are just placing a tag. I specifically defined this parameter in the code so others looking at it would remember that the method is using the BoundarySegments at the finish surface location, especially if they aren’t as familiar with the Revit API.
Another item we need to remember from using Revit is that rooms can exist in your project even if they are not placed or are not bound. This is why we want to incorporate an “if statement” so that we only find the end points of the segments of the rooms that are bound, ei when the segments are not equal to null. If our program tries to find the endpoints of segments that are null, it will crash. Once we collect our endpoints, we can find the average for the X-, Y-, and Z-axis and create a new XYZ variable to store this coordinate. Thankfully, LINQ has a method for averages that we can use. You may have realized that we are double counting all the corners of the room since each segment overlaps endpoints with the adjacent segments. Because we are finding the average and each end point is counted twice, it does not affect our calculation.
If you think about a few possible common shapes for rooms, one that comes to mind is an “L” shaped room. Depending on the proportions, it is possible for the centroid to end up on the edge of the room or very close to the edge. If the coordinates we use to place our ElevationMarker are outside of the room, our program will crash so we need to write in a few “if statements” to confirm the centroid is in the room, and if not we need to throw an exception. For our case, I choose to first check if the center we found is in the room using the IsPointInRoom method. If it is, perfect. If not, we move the coordinate one unit in the positive direction of the X-axis, and then check if it is now in the room. We repeat for the negative direction of the X-axis as well as the positive and negative directions of the Y-axis. This covers all our bases because a centroid will never fall outside of our room boundary, regardless of the shape. However, it is possible for the centroid to fall on or near the boundary line which Revit may not be happy with. If something went wrong and the centroid is still outside of the room, it will throw an exception so the user knows what happened.
When discussing “L” shaped rooms, we should also consider that they fall into a special category. We need to place two elevation markers to show all walls. For our Primary Goal #1, this aspect is too detailed. Software development is an iterative process and it is better to finish a functioning plug-in that covers 75-80% of your use cases, then to get caught up in every “what if” situation and never finish. For now, we will put this on the wishlist of features to add in the future.
And there we have it. We just found the centroid of all of our rooms. This is one method that can be used for many different applications, so I will likely pull it out into a utility class. [edited 10/16] Next week, we will take a look at the ViewFamilyTypeId which is the last parameter we need to place the ElevationMarker.