Limit Selection with ISelectionFilter

There is a class in Revit API that can enchance our selection. It will limit elements allowed for selection during the selection itself. Let's learn how to use ISelectionFilter

Limit Selection with ISelectionFilter

There is a class in Revit API that can enchance our selection. It will limit elements allowed for selection during the selection itself. Let's learn how to use ISelectionFilter

Summary

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 not only in PickObject, but in all other element Selection methods as well.


ISelectionFilter - is an Interface.

If we want to use it, we need to create a class and inherit all functionality of the interface.

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.

In simple terms - We need to inherit Interface when we create our own class. And then we need to override methods that are made in the interface.

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 C# Example

Luckily for us, there is an example of how to use ISelectionFilter in the Class description.

You can see that there is a function GetManyRefByRectangle, that shows you how to use custom MassSelectionFIlter.

And the MassSelectionFIlter Class Itself.

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

💡 Don't worry, we are going to translate it to python and simplify!

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

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

💡 Try Asking ChatGPT to translate it for you! You might get surprised.

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

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

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

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

Simplify Class

Let's look at the python translation that we made and simplify it.

I will start with class.

class CustomFilter(ISelectionFilter):

    def AllowElement(self, element):
        if element.Category.BuiltInCategory == BuiltInCategory.OST_Walls:
            return True

You can see that I made a few changes as well:

  • Renamed Class to CustomFilter

  • Removed AllowReference method

  • Used Category.BuiltInCategory instead of Category.Name

  • Removed return False, because there are no conditions

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 override 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.

How to use it?

To use the filter, we need to create an instance of the class that we crated and provide it to methods that take ISelectionFilter as an argument.

💡Since we don't have def __init__(self) method, it doesn't have any arguments, so we can simply call CustomFilter() to create an instance of a class.

custom_filter     = CustomFilter()
selected_elements = selection.PickElementsByRectangle(custom_filter, "Select rooms ")
print(selected_elements)
Debugging

When you will be testing your CustomFilter, make sure you haven't misspelled anything!

As I showed in the video, when we have a code issue, you won't even get any messages, it will just return False, and therefore disallow you selecting any elements.

Create Custom ISelectionFilter Classes

Let's create something useful using 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.

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)

💡 You can create separate variable for a list of types/categories.

Add to lib

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 whenever you want to use them.

I will place them in lib/Snippets/_selection.py

Bonus Example

Lastly, Let me show you a more advanced example where we check multiple properties of our elements.

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

💡 I have read parameters in that example, and you will learn more about Parameters in the next module!

# 🔥[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 = convert_internal_units(height_ft, get_internal=False, units='m')

            #🔎 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)

💡 Revit 2022 or earlier Exception

Before Revit 2023, BuiltInCategory is not accessible property for some reason. We can see it in Revit Lookup, but we can't get it with this

# ❌
built_in_cat = category.BuiltInCategory

So as a workaround we can compare ElementIds of element's Category and our desired BuiltInCategory.

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

ISelectionFilter might look confusing until you start using it yourself. So I want you to practice it.

✅ Try out the Bonus Snippet and feel free to modify it.
✅ Create ISelectionFilter with your rules (Read Properties for checks)

e.g. Try to create ISelectionFilter for Rooms and make conditional statement using its Area property. By now you know how to do that!

😉 Don't forget to share your custom ISelectionFilter in Discord.

⌨️ Happy Coding!

Questions:

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?

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