Summary
Filtering collectors
Let me show you how we can filter our elements in Revit API using list comprehensions. You can also use regular for-loops, but the point is that we can make simple filters by just going through the list of elements, and check their values.
Let's go through a few examples:
1️⃣ Filter out Unbounded Rooms
Let's start with a simple examples.
We are going to get all rooms and then we will sort them by bounded and unbounded rooms. That's useful to avoid calculation errors when you expect to use room.Area property, but your room is not placed or bounded…
all_rooms = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Rooms).ToElements()
all_rooms_bounded = [r for r in all_rooms if r.Area]
all_rooms_unbounded = [r for r in all_rooms if not r.Area]
print('Total Rooms: {}'.format(len(all_rooms)))
print('Total Bounded Rooms: {}'.format(len(all_rooms_bounded)))
print('Total Unbounded Rooms: {}'.format(len(all_rooms_unbounded)))
🤔List Comprehension Breakdown
If you are new to List Comprehensions in python, then don't worry.
They are a compact way of creating a new list with a for loop and a few if statements in a single line.
They might look confusing first time you see them, but I find them much more readable once you know the rules. And the rules are not that complicated.
Here is the breakdown:
As you see there are 3 parts:
Output - What is added to new list?
Collection - What do we use for iteration
Condition - Are there any conditions to match?
So when you look at this line:
bounded_rooms = [r for r in all_rooms if r.Area]
We want to get rooms, for room in all_rooms if these rooms have a valid Area value.
Here is the same logic but using for-loop and if statement separately.
bounded_rooms = []
for r in all_rooms:
if r.Area:
bounded_rooms .append(r)
These statements will return exactly the same results. But once needed 1 very simple line, while the other took 4 lines.
What's better?
2️⃣ Get 'Beton' Walls that are shorter than 1 meter
Let's try it again but with a more complex example. Here we will want to check 2 parameter values.
Our goal is to get only walls that have:
'Beton' in Type Name
Height less than 1 meter
We could make it a one lines, but it would look awful, trust me!
So instead we are going to create a function to check these parameters and it will return us True/False, and we will use that function in a list comprehension in the condition part.
2️⃣ Get 'Beton' Walls that are shorter than 1 meter
Let's try it again but with a more complex example. Here we will want to check 2 parameter values.
Our goal is to get only walls that have:
'Beton' in Type Name
Height less than 1 meter
We could make it a one lines, but it would look awful, trust me!
So instead we are going to create a function to check these parameters and it will return us True/False, and we will use that function in a list comprehension in the condition part.
2️⃣ Get 'Beton' Walls that are shorter than 1 meter
Let's try it again but with a more complex example. Here we will want to check 2 parameter values.
Our goal is to get only walls that have:
'Beton' in Type Name
Height less than 1 meter
We could make it a one lines, but it would look awful, trust me!
So instead we are going to create a function to check these parameters and it will return us True/False, and we will use that function in a list comprehension in the condition part.
So first of all let's create a function to test our walls.
from Snippets._convert import convert_internal_units
keyword = 'Beton'
max_height_m = 1
max_height_ft = convert_internal_units(max_height_m, get_internal=True, units='m')
def check_wall(wall, keyword, max_height_ft):
"""Check if wall's Type name contains keyword and it's lower than max_height_ft."""
wall_type_name = wall.get_Parameter(BuiltInParameter.ELEM_TYPE_PARAM).AsValueString()
wall_height_ft = wall.get_Parameter(BuiltInParameter.WALL_USER_HEIGHT_PARAM).AsDouble()
if keyword.lower() in wall_type_name.lower() and wall_height_ft < max_height_ft:
return True
Now we can get our walls and create a list comprehension where we will check our walls.
So here is the snippet of how we would use that function in this case:
all_walls = FilteredElementCollector(doc).OfClass(Wall).ToElements()
filtered_walls_ = [wall for wall in all_walls if check_wall(wall, keyword, max_height_ft)]
print('Total Walls: {}'.format(len(all_walls)))
print('There are {} Walls with "{}" keyword and height lower than {}m'.format(len(filtered_walls_),keyword,max_height_m))
3️⃣ Check Material in Type Structure
Now let's take it another step further. This time we will try to get elements that have certain material used in their structure. And we will do that for multiple categories:
- Walls
- Floors
- Ceilings
- Roofs
So again, we will create a function and use it inside list comprehension to keep it more readable in our code.
I would recommend to follow along the video to better understand how to read materials, but in a nutshell we need to:
That's not such a complicated process, but it takes multiple steps to reach the material.
So therefore, it might look more complicated that it actually is.
Here is how we would create this function:
mat_name = 'Holz'
def check_mat(el, mat_name):
"""Function to check if given element has a Material in Structure that matches given material name."""
el_type_id = el.GetTypeId()
el_type = doc.GetElement(el_type_id)
if type(el_type) not in [WallType, FloorType, CeilingType, RoofType]:
return False
structure = el_type.GetCompoundStructure()
if structure:
for layer in structure.GetLayers():
mat_id = layer.MaterialId
if mat_id != ElementId(-1):
mat = doc.GetElement(mat_id)
if mat_name.lower() in mat.Name.lower():
return True
Once we have our function we can start making List Comprehensions.
First get your elements with FilteredElementCollector, and then we can start using this function in list comprehension.
💡 Keep in mind that I decided to keep my elements separate to make separate print statements to know how many walls are made with 'Holz' material.
mat_name = 'Holz'
all_walls = FilteredElementCollector(doc).OfClass(Wall).ToElements()
all_floors = FilteredElementCollector(doc).OfClass(Floor).ToElements()
all_ceilings = FilteredElementCollector(doc).OfClass(Ceiling).ToElements()
all_roofs = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Roofs)\
.WhereElementIsNotElementType().ToElements()
all_roofs = [roof for roof in all_roofs if type(roof) != FamilyInstance]
filtered_walls = [el for el in all_walls if check_mat(el, mat_name)]
filtered_floors = [el for el in all_floors if check_mat(el, mat_name)]
filtered_ceilings = [el for el in all_ceilings if check_mat(el, mat_name)]
filtered_roofs = [el for el in all_roofs if check_mat(el, mat_name)]
print('Total {}/{} Walls found with material Name "{}" in CompoundStructure'.format(len(filtered_walls), len(all_walls), mat_name))
print('Total {}/{} Floors found with material Name "{}" in CompoundStructure'.format(len(filtered_floors), len(all_floors), mat_name))
print('Total {}/{} Ceilings found with material Name "{}" in CompoundStructure'.format(len(filtered_ceilings), len(all_ceilings), mat_name))
print('Total {}/{} Roofs found with material Name "{}" in CompoundStructure'.format(len(filtered_roofs), len(all_roofs), mat_name))
Do I need to use List Comprehension?
Do I need to use List Comprehension?
Is list comprehension more efficient?
Is list comprehension more efficient?