Filter Elements using List Comprehensions

You know how to get elements with FilteredElementCollector. But often times that's not enough and we want to filter even more. We could use Revit API filters, but often times I find that we can use just list comprehensions to simply filter our elements.

Filter Elements using List Comprehensions

You know how to get elements with FilteredElementCollector. But often times that's not enough and we want to filter even more. We could use Revit API filters, but often times I find that we can use just list comprehensions to simply filter our elements.

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…

# 1️⃣Filter out Unbounded Rooms
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.

#🔎 List Comprehension Breakdown
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.

# 2️⃣ Get Walls with 'Beton' in Type Name and Height of less than 1 Meter
from Snippets._convert import convert_internal_units

#📦 Variables
keyword = 'Beton'
max_height_m  = 1
max_height_ft = convert_internal_units(max_height_m, get_internal=True, units='m')

#🧬 Functions
def check_wall(wall, keyword, max_height_ft):
    #type: (Wall, str, float) -> bool
    """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:

#🎯 Main
all_walls      = FilteredElementCollector(doc).OfClass(Wall).ToElements()
filtered_walls_ = [wall for wall in all_walls if check_wall(wall, keyword, max_height_ft)]

#📃 Report
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:

#3️⃣ Get Elements with certain Material used in CompoundStructure [Walls, Floors, Roofs, Ceilings]

#📦 Variables
mat_name = 'Holz'

#🧬 Function
def check_mat(el, mat_name):
    #type: (Element, str) -> bool
    """Function to check if given element has a Material in Structure that matches given material name."""
    # Get Type
    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
    structure = el_type.GetCompoundStructure()

    if structure:
        # Get &  Iterate through Layers
        for layer in structure.GetLayers():
            mat_id = layer.MaterialId

            #Ensure material is assigned! ElementId(-1) means No material
            if mat_id != ElementId(-1):
                mat = doc.GetElement(mat_id)

                # Check Material Name
                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.

#📦 Variables
mat_name = 'Holz'

#🎯 Main
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)]


#📃 Report
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))

Questions:

Do I need to use List Comprehension?

Do I need to use List Comprehension?

Is list comprehension more efficient?

Is list comprehension more efficient?

Discuss the lesson:

P.S. Sometimes this chat might experience connection issues.
Please be patient or join via Discord app so you can get the most out of this community and get access to even more chats.

P.S. Sometimes this chat might experience connection issues.
Please be patient or join via Discord app so you can get the most out of this community and get access to even more chats.

© 2023-2024 EF Learn Revit API

© 2023-2024 EF Learn Revit API

© 2023-2024 EF Learn Revit API