Explanation PickElementsByRectangle (ISelectionFilter)

I need a little help my friends. I have following python code:

from Autodesk.Revit.UI.Selection import *
class MySelectionFilter(ISelectionFilter):
	def __init__(self):
		pass
	def AllowElement(self, element):
		if element.Category.Name == "Walls":
			return True
		else:
			return False
	def AllowReference(self, element):
		return False
selection_filter = MySelectionFilter()
walls = uidoc.Selection.PickElementsByRectangle(selection_filter)
OUT = walls

How does this work? I don’t get how the funtions of the class are being called, as they are not directly called by selection_filter.AllowElement(element). Are they automatically called at creation of the class? I would be very glad if someone could explain it to me.

Here is a link that may help explain it more:

Here is what is written in the reference from Autodesk:

2 Likes

The ISelectionFilter is an Interface which has the methods AllowElement and AllowReference.Interfaces on their own do nothing and you cannot instantiate them. They are a contract, or template if you will, to create objects derived from it with inherited methods/properties. When you inherit from a template in your class, you MUST implement all methods/properties derived from that interface and (unless you are creating abstract classes/methods which is another topic) you must include your own logic within those methods.

Why is this important? Well, it allows you to create and pass in ANY derived type that inherits from that interface into a method/constructor in a fashion where the method/constructor doesn’t really care about each concrete type specifically, but rather only needs to know that it implements the interface, and therefore has access to all members of that interface (say like calling a method). This is a fundamental principle in .net.

How is this relevant? Well, the Selection.PickElement() methods need flexibility as it doesn’t know what needs to be selected and you can’t write a PickElement method for all possible cases and even if you did, this might change in the future. So using an interface means you can write just one PickElement method that knows exactly how to handle that interface, and by extension, the concrete type given.

Here is a super crude .net example of how the Selection.PickElement works under the hood. It is only enough to show how the interfaces are used and how the PickElement method uses the interface to call the AllowElements(e) method…

public static class Selection
{
	private static Document m_document { get; }
    //Takes concrete class as input and can only handle that one case...
	public static IEnumerable<Element> PickWallElement(MyWallFilter selectionFilter)
	{
		// This would probably be a function like GetAllElementsWithinRect(rect) which gets all the elements within a rectangle in screen coords...
		// But we will use the FilteredElementCollector as we all know this...
		var elems = new FilteredElementCollector(m_document).WhereElementIsNotElementType().ToElements();

		var filteredElems = new List<Element>();

		foreach (var e in elems)
		{
			// See how I can use the AllowElement method here...
			if (selectionFilter.AllowElement(e))
			{
				// This Element Passes, so we add it to filtered element list...
				filteredElems.Add(e);
			}
		}

		// return the filtered elements...
		return filteredElems;
	}
    // Takes intereface as an input and can handle ANY concrete type derived from that interface...
	public static IEnumerable<Element> PickElement(ISelectionFilter selectionFilter)
    {
		// This would probably be a function like GetAllElementsWithinRect(rect) which gets all the elements within a rectangle in screen coords...
		// But we will use the FilteredElementCollector as we all know this...
		var elems = new FilteredElementCollector(m_document).WhereElementIsNotElementType().ToElements(); 

		var filteredElems = new List<Element>();

		foreach (var e in elems)
        {
			// See how I can use the AllowElement method here...
			if (selectionFilter.AllowElement(e))
            {
				// This Element Passes, so we add it to filtered element list...
				filteredElems.Add(e);
            }
        }

		// return the filtered elements...
		return filteredElems;
    }
}

public class MyWallFilter : ISelectionFilter
{
    public bool AllowElement(Element elem) => elem.Category == Category.GetCategory(elem.Document, BuiltInCategory.OST_Walls);
	public bool AllowReference(Reference reference, XYZ position) => false;
}

public class MyFloorSurfaceFilter : ISelectionFilter
{
	public bool AllowElement(Element elem) => elem.Category == Category.GetCategory(elem.Document, BuiltInCategory.OST_Floors);
	public bool AllowReference(Reference reference, XYZ position) => reference.ElementReferenceType == ElementReferenceType.REFERENCE_TYPE_SURFACE;
}

public class MySelectionTests
{
	public void Test_1()
    {
		var walls = Selection.PickElement(new MyWallFilter());
		var floor = Selection.PickElement(new MyFloorSurfaceFilter());

		// Both pass as we are using interface and can handle derived types...
    }

	public void Test_2()
	{
		var walls = Selection.PickWallElement(new MyWallFilter()); // passes as it is the same type as what the method requires.
		var floor = Selection.PickWallElement(new MyFloorSurfaceFilter()); // Fails as it is not the same type as what the method requires.
	}
}

You can read more about Interfaces and this type of behaviour by looking at design patterns in .net. Here is a good example of the Composite Design Pattern which uses interfaces and inheritance…

Hope that all makes sense! :slight_smile:

5 Likes

An example for you, set flag to reset selection.

import clr
clr.AddReference("RevitAPI")
clr.AddReference("RevitAPIUI")
from Autodesk.Revit.DB import *
from Autodesk.Revit.UI import *
from Autodesk.Revit.UI.Selection import *
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
uidoc=DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument
class MySelectionFilter(ISelectionFilter):
	def __init__(self):
		pass
	def AllowElement(self, element):
		if element.Category.Name == "Walls":
			return True
		else:
			return False
	def AllowReference(self, element):
		return False
flag = IN[0]
elements = []
if flag:
	selection_filter = MySelectionFilter()
	walls = uidoc.Selection.PickElementsByRectangle(selection_filter)
	elements.append(walls)
OUT = elements

2 Likes