Lesson Objectives

Lesson Objectives

  • Learn about Advanced Selection technique

  • How to use ISelectionFilter class in API

  • How to create custom criteria for selection

Are you Ready?

Lesson Summary

Lesson Summary

Advanced Revit API Selection

Alright, you know how basic Selection works in Revit API.

Now it's time to go a step deeper and learn about advanced selection. There is a way to limit what can be selected when you prompt user to select something. This will greatly improve user experience and avoid a lot of unwanted bugs in the process.

And for that we need to use ISelectionFilter Interface.

Revit API Docs - ISelectionFilter

Let's start with Revit API Documentation.

As you remember, when we explored methods in Selection Class, we saw that some methods can take ISelectionFilter as an argument.

It's available for PickObject, PickObjects and PickElementsByRectangle.

ISelectionFilter - is an Interface.

So it menas we need to create a class based on that to inherit all necessary functionality.

Then we can override pre-written methods with our custom logic so it works correctly.

💡An Interface is like a blueprint for a class, specifying mandatory functionalities without any implementation. By implementing an interface, a class gains certain functionality, while still having the flexibility to customize these functions to suit its needs.

Pre-Written Methods

If you are going to have a look in ISelectionFilter methods, you will see that there are 2 methods, that we can override.

ISelectionFilter Example

Let's start with an example just to get an idea of how it works.
Here is the provided example in C#:

public static IList<Element> GetManyRefByRectangle(UIDocument doc)
{
    ReferenceArray ra = new ReferenceArray();
    ISelectionFilter selFilter = new MassSelectionFilter();
    IList<Element> eList = doc.Selection.PickElementsByRectangle(selFilter, 
        "Select multiple faces") as IList<Element>;
    return eList;
}

public class MassSelectionFilter : ISelectionFilter
{
    public bool AllowElement(Element element)
    {
        if (element.Category.Name == "Mass")
        {
            return true;
        }
        return false;
    }

    public bool AllowReference(Reference refer, XYZ point)
    {
        return false

Let's translate it to python so it's easier to explore. You can follow these simple steps to make it Python.

  • Remove curly braces {}

  • Remove semi-colons ;

  • Remove new keywords

  • Remove Type Hinting

  • Make sure True/False are capitalized.

  • Use def for defining function

  • Use python syntax for class + inheritance

💡 You can ask ChatGPT to translate it for you!

Translate C# Example to Python

🎦 Watch the video to see how I translate it, because it's not complicated!

In a nutshell:

  • Remove curly braces

  • Remove semi-colons

  • Remove new keywords

  • Remove Type Hinting

  • Make sure True/False are capitalized.

  • use def for defining function

  • use python syntax for class + inheritance

  • Remove AllowReference method

Translate C# Example to Python

🎦 Watch the video to see how I translate it, because it's not complicated!

In a nutshell:

  • Remove curly braces

  • Remove semi-colons

  • Remove new keywords

  • Remove Type Hinting

  • Make sure True/False are capitalized.

  • use def for defining function

  • use python syntax for class + inheritance

  • Remove AllowReference method

class MassSelectionFilter(ISelectionFilter):
    def AllowElement(self, element):
        if element.Category.Name == "Mass":
            return True
        return False

    def AllowReference(self, refer, point):
        return False


def GetManyRefByRectangle(doc):
    ra = ReferenceArray()
    selFilter = MassSelectionFilter()
    eList = doc.Selection.PickElementsByRectangle(selFilter, "Select multiple faces")
    return eList
How it works?

It's actually much simpler than you think. Focus on AllowElement method.

class MassSelectionFilter(ISelectionFilter):
    def AllowElement(self, element):
        if element.Category.Name == "Mass":
            return True
        return False

You can see that it has an element argument which refers to elements in Revit project. We can use it to create any checks we want and then we return True to allow selection or False to disallow it.

In the example they check if elem.Category.Name is 'Mass'. And to be honest that's a very bad example! You should not use string names to check Category, instead use BuiltInCategory as it's more reliable way to check it.

Let's create a new one from scratch.

Simplify Class

I'm going to create a new filter that will check if element has BuiltInCategory.OST_Walls

So here is my new class:

from Autodesk.Revit.UI.Selection import ObjectType, ISelectionFilter

class MyFilter(ISelectionFilter):
    def AllowElement(self, elem):
        if elem.Category.BuiltInCategory == BuiltInCategory.OST_Walls :
            return True

To use it, we need to select a method with ISelectionFilter as an argument and provide an instance of our class. And then it will apply the filter logic during selection.

Let's use PickObjects(objectType, iSelectionFilter) method.

sel_filter = MyFilter() # Create Instance of Class
refs       = uidoc.Selection.PickObjects(ObjectType.Element, sel_filter)
elems      = [doc.GetElement(ref) for ref in refs]
print(elems)

🔥And now you can see that we can only select Walls and nothing else!

Now you can change the logic and try selecting other elements like OST_Windows for example.

Why we override methods?

Interfaces are like blueprints for classes.

In reality they have a lot more functionality hidden from us, and to access this functionality we need to use pre-written methods.

In our case, we've overridden AllowElement method.

We have to use the same argument types as provided in the documentation. In our case elem argument represents an Element type.

This argument element, will represent all elements that might be allowed for selection. And the goal of the method is to make a check to return True/False.

💡Returning False is not necessary, since by default it will be None.

🪲Debugging

💡Important note:

If you have mistakes in your ISelectionFilter class, it will return False without any errors.

So if your filter doesn't work as expected and you can't select anything, you might have a typo or an error because it returns False to everything.

Some people spend hours trying to figure out why it's not working because they don't see any error messages. So go back and double check your code if you can't select anything.

Improve your Filter

Now, let's add even more logic to your filter.

We will keep the filter by category, but I also want to add a filter to ensure that elements are part of the group using elem.GroupId property.

This way it will become a really useful tool because of Revit limitation when selecting grouped elements. This way you will be able to easily drag and select elements of selected category inside groups.

Here is an updated MyFilter class with 2 if-statements.

class MyFilter(ISelectionFilter):
    def AllowElement(self, elem):
        if elem.GroupId != ElementId.InvalidElementId:
            if elem.Category.BuiltInCategory == BuiltInCategory.OST_Doors :
                return True

        return False

And here is how it looks in action:

💡 Finally you can select grouped elements without smashing Tab button hundred times…

Bonus: Reuse your Classes

You can also create a reusable class with ISelectionFilter.

I will create 2 classes that can take arguments.

  1. First one will take list of allowed Types, so we can specify what types of elements we want to allow selecting

  2. Second one will take a list of allowed BuiltInCategories, with the same logic.

Maybe this will make it simpler for you to use.

from Autodesk.Revit.UI.Selection import ISelectionFilter

class ISelectionFilter_Classes(ISelectionFilter):
    def __init__(self, allowed_types):
        """ ISelectionFilter made to filter with types
        :param allowed_types: list of allowed Types"""
        self.allowed_types = allowed_types

    def AllowElement(self, element):
        if type(element) in self.allowed_types:
            return True


class ISelectionFilter_Categories(ISelectionFilter):
    def __init__(self, allowed_categories):
        """ ISelectionFilter made to filter with categories
        :param allowed_types: list of allowed Categories"""
        self.allowed_categories = allowed_categories

    def AllowElement(self, element):
        #❌ Category.BuiltInCategory is not available in all Revit Versions
        #if element.Category.BuiltInCategory in self.allowed_categories:
        #    return True
        
        #✅ Let's use Category.Id instead
        self.allowed_categories = [ElementId(bic) for bic in self.allowed_categories]
        if element.Category.Id in self.allowed_categories:
            return True

To use these classes, we simply create instances of these classes.

Notice that we have create __init__ method, where we specified an argument allowed_type/allowed_categories. So make sure you provide the right types of elements in the list.

#1️⃣ ISelectionFilter - Classes
filter_types    = ISelectionFilter_Classes([Room, RoomTag])
selected_elements_ = selection.PickObjects(ObjectType.Element, 
                                           filter_types)


#2️⃣ ISelectionFilter - Categories
filter_cats       = ISelectionFilter_Categories([BuiltInCategory.OST_Walls])
selected_elements = selection.PickObjects(ObjectType.Element, 
                                          filter_cats)
Add to lib

Now you can put these classes to your lib of reusable snippets and import when you need them. This way you only need to create a filter with simpler syntax and provide it as an argument.

I am sure you will find a lot of use-cases for these classes.


💡 Make sure you add them to you lib folder so you can import these classes.
e.g. lib/Snippets/_selection.py

Bonus: Advanced Example

Want more advanced example?

This snippet will only allow Walls with a height between 1 and 2 meters, and there should be a keyword 'Beton' in the TypeName.


# 🔥[Advanced Example] 
# ONLY Pick Wall with Height between 1-2 meters and 'Beton' in Type Name
from Autodesk.Revit.UI.Selection import ObjectType, ISelectionFilter
from Snippets._selection.py import convert_internal_units

#🔻 Define ISelectionFilter
class WallSelectionFilter(ISelectionFilter):
    def AllowElement(self, element):
        if type(element) == Wall:
            #⏹️ Get Type Name
            type_name = element.get_Parameter(BuiltInParameter.ELEM_TYPE_PARAM).AsValueString()

            #⏹️ Get Height
            height_ft = element.get_Parameter(BuiltInParameter.WALL_USER_HEIGHT_PARAM).AsDouble()
            height_m  = UnitUtils.ConvertFromInternalUnits(height_ft, UnitTypeId.Meters)

            #🔎 Check height and type_name
            if 1 <= height_m <= 2 and 'Beton' in type_name:
                return True


# 🎯 Pick a Wall matching criteria
ref_wall = uidoc.Selection.PickObject(ObjectType.Element,
                                      WallSelectionFilter(),
                                      "Select a wall")
wall = doc.GetElement(ref_wall)

P.S.

We will cover parameters in another module in more depth, for now this is just an example for you to see how to check multiple criterias.

💡 Bug in Revit 2022 or earlier

BuiltInCategory property is not accessible before Revit 2023.
So, it might not work if you try to use it in older versions.

# ❌
built_in_cat = category.BuiltInCategory

As a workaround we can compare ElementIds of element's Category and our desired BuiltInCategory. It will have the same effect in the end.

Here is the simplified Snippet

class CustomFilter(ISelectionFilter):

    def AllowElement(self, element):
        if element.Category.Id == ElementId(BuiltInCategory.OST_Rooms):
            return True

🤐 Sorry for not mentioning this during the video.

Homework

Homework

Homework

ISelectionFilter might look confusing until you start using it yourself.
So I'd recommend to spend 5-15 minutes practicing.

  • Copy my example

  • Adjust to another category and test it

  • Maybe even try an Advanced example i provided

And while you are practicing I want you to write it from scratch at least once.
Because if you'll copy-paste all examples and not write much yourself, you won't remember it next time. So write it from scratch at least once.

⌨️ Happy Coding!

Legacy Tutorial

Legacy Tutorial

Legacy Tutorial

This is an older lesson that cover the same lesson materials. It might be useful if you'd like to understand this lesson better and see a bit different examples.

P.S.

I'll also show you how to create reusable class to place in your lib in this video.

⌨️ Happy Coding!

Questions

Questions

Questions

It's too confusing. What should I do?

It's too confusing. What should I do?

It's too confusing. What should I do?

Why should we use ISelectionFilter?

Why should we use ISelectionFilter?

Why should we use ISelectionFilter?

Discuss Lesson

P.S. Sometimes this chat might experience connection issues.

Use Discord App for the best experience.

Discuss Lesson

P.S. Sometimes this chat might experience connection issues.

Use Discord App for the best experience.

Discuss Lesson

P.S. Sometimes this chat might experience connection issues.

Use Discord App for the best experience.

© 2023-2025 EF Learn Revit API

© 2023-2025 EF Learn Revit API

© 2023-2025 EF Learn Revit API