lisa-marie mueller

lisa-marie mueller

bullet pont

WinForms ComboBox link

February 7, 2019

Laptop with code on screen, film camera, dice, green As I discussed last week, user interfaces can be useful when creating plug-ins. They allow you to show information or allow selections. The post from last week goes into more specific details than I will cover this week.

ComboBox

As a reminder, once you have created your WinForm, you will have a .cs file for your WinFrom and you will have a .cs file for the plug-in within the project. Generally, best practices recommend to have the code for your program in the program’s .cs file and to only have the methods you need for the interface within the WinForms .cs file.

To use a ComboBox, there are two aspects. displaying the data to the user and the final user selection. In the WinForms .cs file we can add the methods that will complete these functions. I will use the user input selection of the View Family as an example.

Our constructor will take in the lists of all the different items we will want to display. In this case, the ViewFamilyType. After we initialize the dialog box, we can display data in the ComboBox. We had named the ComboBox “viewFamilyTypeBox” in its properties menu. Then we set what the ComboBox will display and what the value will be. The reason we need these to be different is that our program will need the ElementId, however, to a user, the ElementId would be meaningless. In order to display the choices in a legible way, we need to show the name as a string. This is why we set the DisplayMember as the ViewFamilyType Name and the ValueMember as the ViewFamilyTypeId.

Then we need a method that allows us to collect the user input. The viewFamilyTypeBox’s selected value is only exposed to the PlaceElevationsDialog class. In order for our program to read the input, we need to provide other classes access to the information. We do this through a public getter property.

We will also want accept and cancel buttons like last week.

execute method

We also have to add a few things to our execute method which is located in the plug-in’s .cs file. We only want this plug-in to run if the selections in the dialog were accepted by the user. We accomplish this by placing it inside an if statement that checks to see if the user pressed the “Okay” button. As you can see in lines 29, 34, 38, and 40 in the snipit below, we call the properties of the dialog box throughout the code and use them in our program’s methods.

Those are the minor adjustments we need to make to add user selections to our Create Elevations Plug-In.

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.

if you missed it:

Part 1: filtered element collector [c#]

Part 2: finding centroids and considering exceptions

Part 3: ViewFamilyTypeId

Part 4: ViewPlanId and Levels

Part 5: phases & goal #1 complete [includes GitHub link to the release]

Part 6: view tempaltes

Part 7: resizing CropBoxes

Part 8: creating FilledRegions & Goal #2 Complete [includes GitHub link to release]

Part 9: coordinate system utilities

Part 10: rename views & goal #3 complete [includes GitHub link to release]

Revit API Docs

bullet pont

WinForms DataGrid link

January 31, 2019

Laptop with code on screen, party hat, whistle, blue As you dive into creating plug-ins, you will most likely want to also utilize a user interface to show information or allow selections. WinForms allows you to provide pop-up interfaces to do just that. The benefit of using WinForms is that there are pre-defined interface elements - like buttons, drop-down menus, and more - that you can drag and drop into your form. You don’t have to code the visual features of your interface, you simply code the function.

DataGrid

I’m not going to cover how to get started with WinForms because it is well documented and there are lots of excellent resources. I’m going to jump into after you have created your WinForm with a DataGrid. Generally, you will also want at least two buttons as part of your interface - one to allow the user to accept and run your plug-in, the other to allow the user to cancel. Once you have created your WinForm, you will have a .cs file for your WinFrom and you will have a .cs file for the plug-in within the project. Generally, best practices recommend to have the code for your program in the program’s .cs file and to only have the methods you need for the interface within the WinForms .cs file.

I am going to walk through how I am using a DataGrid for the stand-alone Rename Interior Elevations plug-in I started. First, I will discuss the WinForms .cs file. When you create a WinForm, certain files are automatically created and updated. The class for the WinForms .cs file, which will contain the methods you write to add functionality to your interface, is a “partial” class. The other part of the "partial" class is the autogenerated designer file.

We want to think about what we are displaying and how to display it. The goal is to have a two-column chart that displays the old name of the interior elevation and the new name of the interior elevation. This way the user can review the proposed changes and either accept or cancel the rename action. To display the names for our Rename Interior Elevation tool, we are using a list of key-value pairs. These pairs are taken in as a parameter from our plug-in’s .cs file. We can write a method that takes these pairs of data and separates them into the OldName and the NewName properties. Then a second method converts each row to the displayable data type.

To have our dialog box display, we need a constructor for our interface. We will call this RenameElevationsDialog. We already discussed that we want to display our information in a table. For our DataGrid, we will want to create two columns. We defined this in the properties menu of the DataGrid so we do not need to define this in the code. We called one column “elevationName” and the other “renameTo”. In our code, we need to assign the appropriate field to bind to each column. To achieve this, we assign the property as a string. For example, we bind the OldName property by assigning the string “OldName” to the DataPropertyName of elevationName.

Then all we have left to add are cancel and accept buttons. To generate these methods, simply double click on the respective button, or you can assign these event handlers on the button’s properties. If you are using the methods, you can then fill in what each method does.

execute method

Now we have to add a few things to our execute method which is located in the plug-in’s .cs file. First, we need to create a list that will save the new names of the interior elevations to display. In the snippet below, we do this in line 1 where we create the List of KeyValuePairs and call it newNames. When we cycle through our interior elevation views, we add the new name and the old name to our list. In the snippet below, you can see this in line 11.

And those are the changes we had to make to add a dialog box with WinForms. All the code is available on my GitHub account and you can download the Rename Interior Elevations plug-in in the releases section. Next week I will take a look at using a ComboBox in WinForms (similar a SelectElement in HTML).

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.

if you missed it:

Part 1: filtered element collector [c#]

Part 2: finding centroids and considering exceptions

Part 3: ViewFamilyTypeId

Part 4: ViewPlanId and Levels

Part 5: phases & goal #1 complete [includes GitHub link to the release]

Part 6: view tempaltes

Part 7: resizing CropBoxes

Part 8: creating FilledRegions & Goal #2 Complete [includes GitHub link to release]

Part 9: coordinate system utilities

Part 10: rename views & goal #3 complete [includes GitHub link to release]

Revit API Docs

bullet pont

is point in room link

January 17, 2020

laptop with code on screen, party hat, whistle, red After automating the interior elevation process in my last series, I wanted to see what it would take to just rename interior elevations. I am using the same naming rules as I did in Part 10 of the previous series. When I automated interior elevations, the steps started with a room so the room information and elevation name was easy to pair. When looking at renaming elevations as a stand-alone tool, you need to determine which room each elevation is in.

room helper methods

The first two steps are very straight forward. I wrote two methods that I added to my Utility project because I found I was using them a lot. The first is a simple FilteredElementCollector to get all the rooms. The next item we need to consider is phases. We need to filter out the rooms that are the same phase as the interior elevation that we are looking at. This is because you may have rooms that overlap in different phases of the project and you want to make sure you are renaming the interior elevation according to the correct room number and name.

GetElevRoom

Then we can write the method that finds the room that our elevation is in. We create a new public method that will return our Room and call it GetElevRoom. It needs a ViewSection and the list of rooms as parameters. First, we take the BropBox of the interior elevation ViewSection and assign the min and max values to a variable. Then we find the center of the CropBox. The center is located in the coordinate system of the interior elevation ViewSection but we need to find that coordinate in the project coordinate system to compare it to the rooms. To do this, I use a number of the methods I had written for my Utility project which you can read more about in Part 9 of the Automating Interior Elevations series. These methods rotate the coordinate to orient it to the project coordinate system. We still need to translate the point which we do by adding the interior elevation’s Origin property. Lastly, we check each room in our list of rooms to see if the point is in that room. If it is, we need to return the room so we can use it to rename the elevation.

add to execute

In our execute method we now need to call the method we just wrote. We use a for-each loop to circle through each interior elevation. First, we get the list of rooms filtered by the correct phase, then we find which room the elevation is in, and finally, we rename the elevation with a similar process as I discussed in Part 10 of the Automating Interior Elevations series.

And that’s all it takes to rename the interior elevations. Next week, I will discuss how to add a WinForms user interface with a DataGrid to this plug-in.

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.

if you missed it:

Part 1: filtered element collector [c#]

Part 2: finding centroids and considering exceptions

Part 3: ViewFamilyTypeId

Part 4: ViewPlanId and Levels

Part 5: phases & goal #1 complete [includes GitHub link to the release]

Part 6: view tempaltes

Part 7: resizing CropBoxes

Part 8: creating FilledRegions & Goal #2 Complete [includes GitHub link to release]

Part 9: coordinate system utilities

Part 10: rename views & goal #3 complete [includes GitHub link to release]

Revit API Docs

bullet pont

strategic planning [part 1] link

January 10, 2019

Large light and wooden trees with quote and yellow background Happy New Year everyone! Wishing you a year filled with opportunities and success. I am taking a short break from discussing the Revit API to cover another topic that I’m sure many people are thinking about. In our personal lives, we set goals for the new year. In our professional lives, we participate in strategic planning. In December, DAHLIN had its first team-wide Design Technology group strategic planning session and I wanted to share some thoughts about the process.

At DAHLIN, we do not have a BIM manager. Instead, we have a group of very talented people across market sectors and offices that are on the Design Technology group. We meet every two weeks to discuss new projects, standards, template items, and more. For this upcoming year, we have divided the group down into small teams based on four key subject areas: Maintenance, Automation, Education, and Innovation. Each team has a Champion who is responsible for the planning and execution of the team’s responsibilities. The goal of the new organization structure is to help productivity and allow people to focus their efforts on topics they are most passionate about. In order to update our goals and align them with our new organization, we decided to get everyone involved with a series of strategic planning activities.

dream big

To start off, we all got together in a room with snacks and lots of post-it notes to complete a brainstorming session. We held this initial three-hour meeting while everyone was visiting our headquarter office because the engagement is higher in person than over video conferences. We currently have fourteen members so we decided to split in half to make the setting more conducive to conversation. One team was focused on Maintenance and Automation, the second on Education and Innovation. We started with an introduction to the process. Then, each group of seven took two topics for approximately 30 minutes and then the groups switched. This way everyone had input for all of the topics. 

Some of the key items we learned from the initial session:

write down every idea first, organize later

Focus on what you want to accomplish in the next year, the next three years, and the next five years. The group does not need to discuss how everyone will accomplish those items because that is not critical in the brainstorming step.

always “yes and” ideas

There are no ideas that are too big. There are no ideas that are too small. The point of the initial group workshop is to receive any and all ideas. Having a positive attitude and encouraging creative goals is key to hearing from everyone.

have a moderator for each group

Relating to the two mentioned above, it was immensely helpful to have a moderator to keep people focused on the task at hand. We were all tempted to discuss how we could get to the next step, but it was more important to think about the bigger picture. Additionally, the moderators were able to facilitate conversation and encourage everyone to contribute. We had informal moderator roles, but with larger groups, it may be helpful to have a more defined role.

get organized

After we filled the tables with sticky notes, each team presented the goals they wished to see, and the entire group provided feedback. Then, teams went back to their original topics and began organizing the goals based on the target completion date. We grouped goals into those that should be focused on in the next year, the next three years, and beyond. The teams then presented to the entire team for feedback.

Some of the key items we implemented for organizing goals:

stay positive

This is the step where you have to bring things back to reality a bit, however, it’s important to not think of all the negatives. Even though an idea can be challenging to achieve or require a shift in company standards, if you believe it is where the industry is headed and an important goal, keep it.

understand the constraints

It is a balance between encouraging ideas but also considering realistic challenges. Keep the "dream goals" and the "reach goals", but also keep in mind constraints like budget and time when setting target deadlines. Maybe the "reach goals" fall into the three-year time period so that you can take steps to make them easier to accomplish. 

be realistic

You do not need to get into the specifics of how each goal will be accomplished in time, but it is important to consider a realistic deadline for each goal. Think about things like the required approvals and the number of people involved in achieving each goal when setting a timeframe.

ownership and champions

After brainstorming, we all looked at the four topics and each person volunteered to be on the team for a minimum of one topic. Additionally, people volunteered to champion each effort: Maintenance, Automation, Education, and Innovation. Having the Champions helps to ensure that tasks are going to be completed because there is someone organizing and moving forward each topic. Once these roles were assigned, there was one critical task left. We had to determine the path to accomplishing all the goals that the group discussed. This plan includes measurable objectives and check-in points to determine objective progress and completion. Finalizing this plan was handed off to the Champions and their teams. This is what our Champions are working on this month, and I will cover a wrap-up of our strategic planning by the end of February. 

Some of the key strategies we are using for the smaller teams:

balance team size with tasks

Certain categories have a lot more to do. For example, the Maintenance group handles all of our standards, templates, and libraries. Because it takes the majority of our time, it is our largest group. 

allow Champions to manage their topic and team

When you have a group managing the Design Technology branch of your firm, it’s important to let the smaller groups handle their own responsibilities. Making decisions with fourteen people is not realistic. Instead, the tasks and oversight can be handled by the groups and they can present back to the larger team so knowledge is shared and distributed across teams.

let people choose

One concern we had when starting the process is that some topics may be viewed as boring and would not receive a Champion. If you have a diverse group of people, chances are people will naturally gravitate toward different things. We received a wide range of interest in all the topics and teams. If there is not enough interest in a topic, you can always pair popular and unpopular topics together so everyone is involved in at least one thing they are passionate about.

Setting goals for our group allows us to identify trends we see in the industry and prepare for them. It also allows us to spend time strategically improving the resources, training, and tools we provide for the firm. By going through strategic planning with our entire team, everyone has the opportunity to be a part of the process. Their ideas are valued, their ideas are heard, and they are shaping the path of the Design Technology group.

bullet pont

rename views & goal #3 complete link

December 13, 2019

Laptop with code on screen, film camera, floppydisk, yellow 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.

if you missed it:

Part 1: filtered element collector [c#]

Part 2: finding centroids and considering exceptions

Part 3: ViewFamilyTypeId

Part 4: ViewPlanId and Levels

Part 5: phases & goal #1 complete [includes GitHub link to the release]

Part 6: view tempaltes

Part 7: resizing CropBoxes

Part 8: creating FilledRegions & Goal #2 Complete [includes GitHub link to release]

Part 9: coordinate system utilities

primary goal #3

Rename the interior elevations based on the room name, add a suffix based on the cardinal directions to identify the elevations

RenameElevation

Since we set up our utility methods last week we now have a very easy time renaming our elevations. We create a new public method that does not return anything and call it RenameElevation. It uses the document, the interior elevation ViewSection, and the room as parameters. For this plug-in I have decided to rename the elevations starting with the room number, then the room name, and then a suffix based on the direction the interior elevation is looking (utilizing the utility methods GetVectorAngle and Angle2Cardinal discussed in Part 9). I decided to assign the suffix A to North, B to East, and so on, rotating counterclockwise. You can change this to be whatever you like. We do want to use a try-catch statement when concatenating our string because I found that when a room is not placed, sometimes assigning the new name fails. This way our program skips the name assignment so our program doesn’t crash if there are any problems.

add to execute

Then we just add this method to the Execute method of our plug-in and we are done!

We have created a plug-in that will automate your interior elevations for you, create the tag at the center of the room, consider multiple levels and phases, assign a view template to the interior elevations, reset the crop boundary, set up a masking region, and rename the interior elevation based on the room number and name. You can access the updated code on my GitHub account . There you can also download the DLL so you can use the updated plug-in too.

Next week we'll take a short break from plug-ins and discuss BIM strategic planning.

Please note that since we have now added even more filters and hard-coded selections, there are a couple of things you need to use this plug-in as written. Your Revit file needs:

  • to have all the rooms placed for which you want to create the interior elevations
  • an elevation ViewFamily type with a name that contains “interior”
  • a ViewPlan with a name that contains “interior elevations”
  • a ViewTemplate with a name that contains “interior” and “elevation”
  • a FilledRegionType with the BackgroundPatternColor set to white (RGB 255, 255, 255)
  • a LineStyle with a name that contains “05” and “solid”

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

coordinate system utilities link

December 06, 2019

Laptop with code on screen, film camera, floppydisk, pink I hope everyone had a wonderful Thanksgiving if you celebrate or just had a great end to November. 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.

if you missed it:

Part 1: filtered element collector [c#]

Part 2: finding centroids and considering exceptions

Part 3: ViewFamilyTypeId

Part 4: ViewPlanId and Levels

Part 5: phases & goal #1 complete [includes GitHub link to the release]

Part 6: view tempaltes

Part 7: resizing CropBoxes

Part 8: creating FilledRegions & Goal #2 Complete [includes GitHub link to release]

primary goal #3

Rename the interior elevations based on the room name, add a suffix based on the cardinal directions to identify the elevations

overview

To rename the interior elevations, we first need to find the cardinal direction that the elevation marker is facing. To make this easier, we have a handful of methods that I added to a Utility class so we can reference them.

radians and degrees

First, we want to set up a couple of simple conversions and write a method to convert radians to degrees and vice versa.

GetDocNorthOffset

We also need to find the offset of the project north from true north. To do this, we first need to convert the ProjectLocationSet type to an IEnumerable of ProjectLocation so that it is a more standard type. Then we can filter for the project location with the name “Internal”. To get the offset, we find the angle between true north and project north and utilize our Radians2Degrees method to ensure it is returned in degrees. We will use this method in the Angle2Cardinal method described below.

GetVectorOffset

This method is very generic and simply gets the angle of a vector and returns this angle in degrees.

Angle2Cardinal

Since multiple views cannot have the same name, a great way to distinguish interior elevations is to incorporate a suffix indicating the direction of the interior elevation. To do this, we need to find the document’s north offset by using the GetDocNorthOffset method described above. It is possible for this angle to come back as a number greater than 360. Because the directions rotate around a circle, we can keep adding or subtracting 360 until the angle falls between 0 and 360 degrees. This makes assigning the suffix easier. After correcting the angle, we can assign suffixes based on true north. For example, if the angle is between 0 and 45 degrees or 315 degrees and 360 degrees, the suffix is East. We repeat this for each option. Then we can utilize this method next week to assist in renaming our views.

And that sets up a number of Utility methods that we can then use to rename our interior elevations. Next week, we will tackle renaming our views and complete the final step in automating our interior elevations.

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

creating FilledRegions & Goal #2 Complete link

November 22, 2019

Laptop with code on screen, globe, game controller, green 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.

if you missed it:

Part 1: filtered element collector [c#]

Part 2: finding centroids and considering exceptions

Part 3: ViewFamilyTypeId

Part 4: ViewPlanId and Levels

Part 5: phases & goal #1 complete [includes GitHub link to the release]

Part 6: view tempaltes

Part 7: resizing CropBoxes

primary goal #2

Override the view’s parameters to set a view template, update the extents of the views, create a masking region, override the inside line weight of the masking region

overview

Since we resized the CropBox, we now want to place our masking region. The reason a masking region can be helpful is that when you want to have the border of the interior elevation follow along door openings and changes in ceilings and soffits, it can be easier to do it with a masking region. It also makes it easier to adjust the line weight around the elevation rather than having to worry about overriding the display graphics of the interior elevation crop every time.

FilledRegion boundaries

Because we are using the filled regions to mask the parts around our view that are beyond the room, we need to create a donut-shaped filled region. Anything in the middle is visible and anything beyond that will be masked. For this, we need inside and outside boundaries.

We create a new public method that returns a list of CurveLoops which we will use to create our FilledRegion. We call it FilledRegionBoundary and it needs a ViewSection parameter which will be our interior elevation view. If you remember from Part 7, when we set the interior elevation view CropBox, we set it so that it was 1’-0” beyond the room in width and height. So when we get the bounds of the interior elevation CropBox, they will be 1’-0” beyond the room. This is why, for the inside boundary, we need to add 1 to the minimum value and subtract 1 from the maximum value. This will put the boundary back to the edge of the room. Also similar to Part 7, we have to perform some more transformations. I have to give a shoutout to my husband who helped me with the inverse matrices. If you remember, when you get the coordinates of the CropBox of the elevations view it uses the coordinate system of the view. However, to create our filled region, we need to work with the project coordinates. To to do this, we actually have to use an inverse matrix to reverse the translations and rotations back to the project coordinate system. I added these methods to a utility file for matrices. You can get access to it through my GitHub account. After we find the correct coordinates, we simply draw lines between them and add those lines to a list.

Since we want the outside curve to be set back from the CropBox a bit, we can take the CropBox coordinates and set them back 6”. Then we transform the coordinates with the inverse matrix as we did for the interior boundary. After we find the correct coordinates, we simply draw lines between them and add those lines to a list just like above.

Finally, we create a CurveLoop for both the inside and outside curves. We add both to a list of CurveLoops which we return to use to create our FilledRegion.

create FilledRegions

Now that we have the curves for the boundaries of our filled region, we just have to create the filled region. We create a new public method that does not return anything. It takes in our document, the interior elevation ViewSection, and the room’s BoundingBoxXYZ as parameters. The first thing we do is call our filledRegionBoundaries method to get our boundaries. Then we need to get the element Id of the type of filled region we want to create. Since we are using it to mask our view, we want the fill pattern to be white (RGB 255, 255, 255). For Revit 2019 and above, filled regions that are opaque have the IsMasking property set to true. You can also change the filters below or make them more specific if you need to. This code block then returns the TypeId of the filled region we need. We can then use this along with our interior elevation view’s Id and our boundaries to create our filled region. Finally, we override the line thickness to provide us with a thicker outline. For the code below, I just filtered for a line that contained “05” and “solid” in the name. It is more complex to filter for line style properties like the thickness and pattern. You may need to adjust the filter for the name based on how you name your lines.

add to Execute

Our final step is to add the methods we’ve created in Goal #2 to the Execute method. We circle through each elevation, overwrite the CropBox, create the filled regions, and adjust the line weight of the filled regions. This means that we have completed Goal #2. You can access the updated code on my GitHub account. There you can also download the DLL so you can use the updated plug-in too. Have fun!

Please note that since we have now added even more filters and hard-coded selections, there are a couple of things you need to use this plug-in as written. Your Revit file needs:

  • to have all the rooms placed for which you want to create the interior elevations
  • an elevation ViewFamily type with a name that contains “interior”
  • a ViewPlan with a name that contains “interior elevations”
  • a ViewTemplate with a name that contains “interior” and “elevation”
  • a FilledRegionType with the BackgroundPatternColor set to white (RGB 255, 255, 255)
  • a LineStyle with a name that contains “05” and “solid”

I’m taking a break next week because of the holidays, but check back December 6 for the next post in the series. That week, we set our 3rd and final goal so that everyone can enjoy automated interior elevations by the end of the year. Have a great turkey day!

code summary



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

resizing CropBoxes link

November 15, 2019

Laptop with code on screen, globe, game controller, blue 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.

if you missed it:

Part 1: filtered element collector [c#]

Part 2: finding centroids and considering exceptions

Part 3: ViewFamilyTypeId

Part 4: ViewPlanId and Levels

Part 5: phases & goal #1 complete [includes GitHub link to the release]

Part 6: view tempaltes

primary goal #2

Override the view’s parameters to set a view template, update the extents of the views, create a masking region, override the inside line weight of the masking region

CropBox

I have to admit I had a lot of fun with this one. I was able to recollect linear algebra which I have not used in many years. This week, we will create a method that adjusts the CropBox of the interior elevation views. You can use this for two things. First, to get rid of that pesky problem of having a small crop height on interior elevation views depending on your floors and levels (also discussed on the forums). Second, if you use masking regions over your interior elevation views to add a thick crop boundary and to make it easier to adjust the boundary around doors, other openings, and angled ceilings, we can set the crop of the interior elevation one foot beyond the room so the masking region shows. 

coordinate systems

coodinate sytem of elevations facing opposite directions While working on this part, I came across the fact that the coordinate system used for the view and the coordinate system used for the rooms is not the same. When you get the CropBox from a view, it uses the coordinate system of that view. However, when you overwrite the CropBox of the view, it uses the room’s (which is also usually the same as the project’s) coordinates. Additionally, in Revit the positive Z direction is always towards the user. So for example in plan view, Z is the direction that would be “up” in the real world, the height of your building. However, in elevations “up” is the Y coordinate. This means that the Y of the elevation maps to the Z of the room. But the X of the elevation varies and will be either the positive or negative X or the positive or negative Y of the room based on which elevation it is. This is because as you rotate around the elevation marker, Z stays towards the viewer, but the viewer is changing directions. This is why we need to use linear algebra. Thankfully, by using a linear transform, we can easily solve the problem.

transform

The other background information I wanted to touch on was that the Transform member in the Revit API was not working as expected for me. There is a Transform property for all BoundingBoxXYZs, in this case the CropBox of our view. When I attempted to use it to adjust the coordinates, it did not perform a linear transformation. So, I just set up my own matrices and calculated it out manually as you can see below.

We start by making our public method which will return a BoundingBoxXYZ (we will use this in Part 8, next week) and we will call it SetCropBox. It needs a ViewSection parameter which is our interior elevations views, and a Room parameter. We start by getting the bounds of the interior elevation CropBox and the bounds of the room’s CropBox. Then we set up our transform matrix with the basis coordinates. After we have that set up, we can do all the math and get out our transformed minimum and maximum values. Teaching everyone linear algebra would be a bit more than we have time for right now, so I recommend you check out some youtube videos if you do not remember transformations. Then we can create extended minimum and maximum bounds from our transformed coordinates. If you do not want to extend the bounds beyond the room boundary, simply keep the transformed coordinates.

box is empty

When I tried to assign the CropBox back to the interior elevation view, the assignment threw an InvalidArgumentException. The message was “Box is empty” in Revit. Looking into it, the exception happened when I was trying to set the larger coordinate value as the minimum and the smaller coordinate value as the maximum. To make sure this doesn’t happen, we added the following code to check if the minimum X and minimum Y values are the smaller values. If not, we switch the minimum and maximum.

Now we can just set our minimum and maximum values, set the bounds, and overwrite our elevation view’s CropBox. Next week, we’ll play around with filled regions.

code summary

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

view templates link

November 8, 2019

Laptop with code on screen, 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.

if you missed it:

Part 1: filtered element collector [c#]

Part 2: finding centroids and considering exceptions

Part 3: ViewFamilyTypeId

Part 4: ViewPlanId and Levels

Part 5: phases & goal #1 complete [includes GitHub link to the release]

primary goal #2

Override the view’s parameters to set a view template, update the extents of the views, create a masking region, override the inside line weight of the masking region

view template

We now have a functioning plug-in that places our interior elevations, but it would be great to automate some additional tedious tasks like applying our masking region, double-checking the extents, and setting some standard Revit parameters. This week we will look at setting a view template for our interior elevation views.

Interior elevations have a ViewTemplateId that we can simply set after we find the view template we want to use. To find it we can use a FilteredElementCollector. One thing I discovered while putting this together is that although in Revit, the view templates are listed under categories (plan view templates are under a different category drop down then elevations) this has no relationship to anything that I could find in the Revit API, they all seem to be in one large bucket. This is why we filtered by name only. We set the filter to look for view templates that contain “interior” and “elevation” in the name. Depending on what you have named your view templates, you will need to adjust these filters to match the name you chose.

And that’s it. Pretty straight forward to set a view template. This same concept can also be applied to other Revit parameters of the view. If you categorize your views, for example, you can assign the correct category for interior elevations when they are created. Next week, we will update view extents to match the extents of the room and get rid of that pesky problem of having a small crop height on interior elevation views depending on your floors and levels (also discussed on the forums)

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

phases and goal 1 complete link

November 1, 2019

Laptop with code on screen, toy bird, toy rocket orange First of all, I hope everyone had a fun and safe halloween! 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.

if you missed it:

Part 1: filtered element collector [c#]

Part 2: finding centroids and considering exceptions

Part 3: ViewFamilyTypeId

Part 4: ViewPlanId and Levels

primary goal #1

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.

phases

As I was working on this plug-in, I realized that the interior elevation maker and the interior elevation views are not necessarily created in the same phase as the plan that you use to create them (Part 4). Because of this, I added a few simple steps to find the phase of the view, and set the interior elevation marker and views to this same phase. We can make this adjustment in the PlaceElevations method.

execution

This completes all the elements of our Goal #1, it’s time to put it together. We need to write our Execute method. This will be a public method that outputs a Result. It takes ExternalCommandData, a ref string, and an ElementSet as input parameters.

We set up our initial variables for use in the entire program. Next, we use a try-catch block so that our plug-in does not crash Revit if it fails and so that we can output proper error messages. We use a FilteredElementCollector to filter all of our rooms (Part 1) and then call our method that collects the document’s PlanViews (Part 4). With this information gathered, we can circle through each room in the project, get the center of the room (Part 2), find the PlanView that the room is visible in (Part 4), and place our elevations (Part 3 & Part 4).

We have now accomplished Primary Goal #1 and have a program that places our interior elevations in all of our rooms. It considers projects with multiple levels, and ensures the elevations are in the correct phase. You can take a look at all of the code on my GitHub account. There you can also download the DLL from GitHub so you can use the plug-in too. Enjoy!

Next week we set Goal #2 and jump in to updating some of the parameter properties of the interior elevations.

code summary

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