Resources
Summary
Practice makes perfect!
Let's continue practicing Selection and Parameters once more! We will create another add-in together, applying the concepts we learned in Modules 3-4 to ensure a better understanding.
We are going to solve an issue of changing wall levels. It's really annoying attempt to modify wall levels, only to encounter a warning because the offset hasn't been adjusted correctly.
Let's take control back into our coding hands 🙌!
Tool Brainstorming
Firstly, let's break down the tool creation process into smaller steps:
1️⃣ Get Selected Walls
2️⃣ Get User Input (New Levels)
3️⃣ Calculate New Offset to keep geometry
4️⃣ Modify Wall Levels
These are the main steps we need to solve. Additionally, we will make sure that tool is prone to errors and has simple error handling.
So get ready and Happy Coding!
1️⃣ Get Selected Walls
Firstly, we need to select some walls. And there are many different options to do that. I want to make something very universal that has multiple selection options.
👇To get our selection, we will create a function that does the following:
Get Currently Selected Elements (GetElementIds)
Filter selection to only Walls.
If none selected - prompt PickObjects
Improve user experience with ISelectionFilter to filter walls.
Lastly, check if walls were selected.
If we still have no walls, create a warning message with pyrevit.forms.alert
💡 It might sound like a lot of steps to select elements, but it's nothing new for you! And also you will be able to reuse same logic for your future scripts as well 😉
So let's create a function that does all that!
And since we don't even have any arguments, we just call this function to get selected walls.
Let's preview our selection and print WallType Names.
🤔 Also before going to the next step, try to test your selection from all angles!
Select before click the button.
Click without anything selected.
Select Nothing and click Finish.
Cancel selection all together.
Just think of all possible cases that your users might interact with the tool.
💀 Keep in mind that non-coders get terrified seeing red wall of text when they get an error. Even though it's harmless, they will try to avoid seeing that at all cost!
2️⃣ Get User Input
Once user has selected walls, we need to ask what is the desired level for Base/Top constraints. And for that we need to create a custom UI.
There are different ways to create a UI with python in Revit
Custom WPF - Highly customizable, but steep-learning curve!
pyrevit.forms - Simple to use but Limited forms.
rpw.FlexFox - 👈 That's what we will use
💡Also rpw is included in pyRevit by default!
📃rpw.FlexForm
rpw module has a bunch of prewritten functions that you might find useful, but FlexForm stands out the most. It allows you to create custom UI forms by combining different components in any order you want.
For Example:
You will be able to create as many Text/ComboBox/CheckBox inputs in a single form as you need. They will be stacked one after another.
And while this form is not the most customizable, it's very simple to use!
In our case, we will need to create 2 CheckBoxes and 2 ComboBoxes, so user can select desired levels for Base/Top constraints and also have an option to modify only Base, only Top or Both of them together!
💡Keep in mind that this form returns dictionary.
When you will be creating your components, it takes first argument as the key name in the dictionary. This will allow you to read the values in the end by specifying this key name!
We just need to call our function name to create this form. And once the user finished selection, we will get a dictionary of all the user inputs.
Make sure you get your values using the same keys you provided in your components!
👇 Here is how I will get all user inputs from the form
3️⃣ Calculate New Offsets
By this point we have selected walls, and we know what are the new Base/Top Levels.
So we can start calculating new offsets, while practicing working with parameters.
For this we will need to get the following parameters:
Base Level (WALL_BASE_CONSTRAINT)
Base Offset (WALL_BASE_OFFSET)
Top Level (WALL_HEIGHT_TYPE)
Top Offset (WALL_TOP_OFFSET)
Wall Height (WALL_USER_HEIGHT_PARAM)
💡 Inspect your elements with RevitLookup to find correct Built-In Parameter Names
Once we got these parameters, we can calculate Wall's Elevations.
For that we need to get Elevation of Wall's Levels and then include offsets. Then by subtracting these elevations, we will get a value that will be our new offset!
💡 Also notice that we haven't used Top Level / Top Offset parameter.
We used Wall Height instead, because it already provides us the necessary value. However, these parameters will be used later on when we will change Top Level and Offset.
4️⃣ Modify Base/Top Levels + Offsets
Now that we have everything we need, the last step is to modify the parameters with new values.
Since we've given the user an option to modify only Base/Top or Both, we need to make a few if statements.
We have modify_base and modify_top from our FlexForm, so let's check if they return True or False.
Then we can use Set method to change parameter value.
💡Make sure you set the parameter value with the correct StorageType.
If parameter is a Double, you need to provide you values as Double and so on… but in this case it's quite simple!
🔓Add Transaction - How Allow Changes
As you remember, we have to use a Transaction every time we make any changes with the Revit API. This is great as it gives us a sense of security, knowing we can't mess up our projects.
So, create your transaction and make sure you make changes between Start and Commit statements.
💡Also make sure you keep your Transaction outside of the loops if possible. This will be a huge difference in the execution speed.
✨Bonus Feature:
Also, as a bonus feature, we can think about work-sharing projects.
While automating something in Revit, you will often encounter warnings about elements being used by other users in the Work-Sharing project. Isn't it annoying?
So we can at least make a check to see if our elements are not OwnedByOtherUser. And for that we need to use the GetCheckoutStatus method from the WorksharingUtils Class.
💻Final Code:
Let's put all these pieces together in a single code snippet:
HomeWork
💪Practice Working with Selection and Parameters!
Create a simple tool for another category of elements, to modify some parameters. We can already create a ton of tools by using the same logic as I showed you.
👇 Don't forget to share your ideas in the community. Together you will encourage each other to think about new tools, and how this can be used.
💬 Also, Feel Free to Ask for Help in the Discord to Create Your Tool!
⌨️ Happy Coding!