In Revit API 2011, a new FilteredElementCollector mechanism is introduced, all previous filters are replaced and some brand new ones are added. In addition, the filters are splitted into logical filters (ElementLogicalFilter), quick filters (ElementQuickFilter) and slow filters (ElementSlowFilter). There are two logical filters, a dozen of quick filters and a bunch of slow filters.
The logical filters (AND and OR) are not really new and they seem to be renamed from the old versions. In fact, in the new FilteredElementCollector mechanism, they become redundant as the collector has two shortcut methods as well, the UnionWith() and the IntersectWith(), and they can do exactly the same work (more nicely and meaningfully) as the two logical filters are supposed to do. Because of this, we will skip them in this article; instead, we will talk about the UnitWith() and the IntersectWith() a bit and use them in some example code. The split of quick and slow filters is said primarily for performance considerations.
Quick filters include:
• BoundingBoxContainsPointFilter
• BoundingBoxIntersectsFilter
• BoundingBoxIsInsideFilter
• ElementCategoryFilter
• ElementClassFilter
• ElementDesignOptionFilter
• ElementIsCurveDrivenFilter
• ElementIsElementTypeFilter
• ElementOwnerViewFilter
• ElementStructuralTypeFilter
• ExclusionFilter
• FamilySymbolFilter
They are not exclusive to each other. In some cases, for example, we can use the ElementCategoryFilter or the ElementClassFilter to achieve the same goal.
Assuming we need to fufill a task like filtering out all roofs, ceilings, floors and stairs from a Revit model, which filters should we use and what filter arguments are good and necessary?
It is not arguable that the ElementCategoryFilter or the ElementClassFilter or both should be applied, but the question is how. Here are some more clues that can help us make such a decision: there are Floor, CeilingAndFloor, and RoofBase classes in the API, but there is no Ceiling or Stair class; there are OST_Ceilings, OST_Roofs, OST_Floors, and OST_Stairs values and a lot of more specific ones in the BuiltInCategory enumerator.
If we prefer the ElementClassFilter, we can filter out the roofs, ceilings and floors using the filter with the CeilingAndFloor and the RoofBase classes as arguments respectively and unit them together, then we filter out stairs using the ElementCategoryFilter with the OST_Stairs BuiltInCategory argument and again unit them together. One thing needs to bear in mind is do not forget to exclude all element types (symbols) from the final FilteredElementCollector.
Here is the example code to demonstrate this approach:
Public Shared Function FindAllRoofsCeilingsFloorsAndStairs_Approach1(ByVal doc As RvtDocument) As List(Of Autodesk.Revit.DB.Element)
Dim list As New List(Of Autodesk.Revit.DB.Element)()
Dim finalCollector As New Autodesk.Revit.DB.FilteredElementCollector(doc)
Dim filter1 As New Autodesk.Revit.DB.ElementClassFilter(GetType(Autodesk.Revit.DB.CeilingAndFloor), False)
finalCollector.WherePasses(filter1)
Dim filter2 As New Autodesk.Revit.DB.ElementClassFilter(GetType(Autodesk.Revit.DB.RoofBase), False)
finalCollector.UnionWith((New Autodesk.Revit.DB.FilteredElementCollector(doc)).WherePasses(filter2))
Dim filter3 As New Autodesk.Revit.DB.ElementCategoryFilter(BuiltInCategory.OST_Stairs, False)
finalCollector.UnionWith((New Autodesk.Revit.DB.FilteredElementCollector(doc)).WherePasses(filter3))
Dim filter4 As New Autodesk.Revit.DB.ElementIsElementTypeFilter(True)
finalCollector.IntersectWith((New Autodesk.Revit.DB.FilteredElementCollector(doc)).WherePasses(filter4))
list = finalCollector.ToList()
Return list
End Function
If we prefer the ElementCategoryFilter instead we can write some other code to do the same thing:
Public Shared Function FindAllRoofsCeilingsFloorsAndStairs_Approach2(ByVal doc As RvtDocument) As List(Of Autodesk.Revit.DB.Element)
Dim list As New List(Of Autodesk.Revit.DB.Element)()
Dim finalCollector As New Autodesk.Revit.DB.FilteredElementCollector(doc)
Dim filter1 As New Autodesk.Revit.DB.ElementCategoryFilter(BuiltInCategory.OST_Ceilings, False)
finalCollector.WherePasses(filter1)
Dim filter2 As New Autodesk.Revit.DB.ElementCategoryFilter(BuiltInCategory.OST_Roofs, False)
finalCollector.UnionWith((New Autodesk.Revit.DB.FilteredElementCollector(doc)).WherePasses(filter2))
Dim filter3 As New Autodesk.Revit.DB.ElementCategoryFilter(BuiltInCategory.OST_Floors, False)
finalCollector.UnionWith((New Autodesk.Revit.DB.FilteredElementCollector(doc)).WherePasses(filter3))
Dim filter4 As New Autodesk.Revit.DB.ElementCategoryFilter(BuiltInCategory.OST_Stairs, False)
finalCollector.UnionWith((New Autodesk.Revit.DB.FilteredElementCollector(doc)).WherePasses(filter4))
Dim filter5 As New Autodesk.Revit.DB.ElementIsElementTypeFilter(True)
finalCollector.IntersectWith((New Autodesk.Revit.DB.FilteredElementCollector(doc)).WherePasses(filter5))
list = finalCollector.ToList()
Return list
End Function
Personally, I like this approach as the code is more readable and its logic clearer though one more filter is needed in this particular case. And please do not forget, more importantly, the Revit API does not provide classes for all categories!
This leads to another question: does it indicate that the ElementClassFilter is also redundant if it does not have significant performance gain than the ElementCategoryFilter? It is not something we will or can answer in this post.
Supposing we have another task, retrieving all elements from a document, what should we do?
It needs two filters, one ElementIsElementTypeFilter and one inverted
ElementIsElementTypeFilter, as follows:
Public Shared Function FindAllElements(ByVal doc As RvtDocument) As List(Of Autodesk.Revit.DB.Element)
Dim list As New List(Of Autodesk.Revit.DB.Element)()
Dim finalCollector As New Autodesk.Revit.DB.FilteredElementCollector(doc)
Dim filter1 As New Autodesk.Revit.DB.ElementIsElementTypeFilter(False)
finalCollector.WherePasses(filter1)
Dim filter2 As New Autodesk.Revit.DB.ElementIsElementTypeFilter(True)
finalCollector.UnionWith((New Autodesk.Revit.DB.FilteredElementCollector(doc)).WherePasses(filter2))
list = finalCollector.ToList()
Return list
End Function
It is apparently cumbersome to create so much code to do the simplest and most fundamental task but that is something we have to sacrifice for the performance gain, I guess.
Here is some code to call and test these element finders:
Public Shared Sub Test()
Dim elements As List(Of Autodesk.Revit.DB.Element) = ElementFinder.FindAllRoofsCeilingsFloorsAndStairs_Approach1(CachedDoc)
MessageBox.Show(String.Format("There are {0} roofs, ceilings, floors and stairs in the model.", elements.Count.ToString()))
elements = ElementFinder.FindAllRoofsCeilingsFloorsAndStairs_Approach2(CachedDoc)
MessageBox.Show(String.Format("There are {0} roofs, ceilings, floors and stairs in the model.", elements.Count.ToString()))
elements = ElementFinder.FindAllElements(CachedDoc)
MessageBox.Show(String.Format("There are totally {0} elements in the model.", elements.Count.ToString()))
End Sub
The whole ElementFinder has been appended below:
#Region "Namespaces"
Imports System
Imports System.Text
Imports System.Linq
Imports System.Xml
Imports System.Reflection
Imports System.ComponentModel
Imports System.Collections
Imports System.Collections.Generic
Imports System.Windows
Imports System.Windows.Media.Imaging
Imports System.Windows.Forms
Imports System.IO
Imports Autodesk.Revit.ApplicationServices
Imports Autodesk.Revit.Attributes
Imports Autodesk.Revit.DB
Imports Autodesk.Revit.DB.Events
Imports Autodesk.Revit.DB.Architecture
Imports Autodesk.Revit.DB.Structure
Imports Autodesk.Revit.DB.Mechanical
Imports Autodesk.Revit.DB.Electrical
Imports Autodesk.Revit.DB.Plumbing
Imports Autodesk.Revit.UI
Imports Autodesk.Revit.UI.Selection
Imports Autodesk.Revit.UI.Events
Imports Autodesk.Revit.Collections
Imports Autodesk.Revit.Exceptions
Imports Autodesk.Revit.Utility
Imports RvtApplication = Autodesk.Revit.ApplicationServices.Application
Imports RvtDocument = Autodesk.Revit.DB.Document
#End Region
Public Class ElementFinder
Public Shared Function FindAllRoofsCeilingsFloorsAndStairs_Approach1(ByVal doc As RvtDocument) As List(Of Autodesk.Revit.DB.Element)
Dim list As New List(Of Autodesk.Revit.DB.Element)()
Dim finalCollector As New Autodesk.Revit.DB.FilteredElementCollector(doc)
Dim filter1 As New Autodesk.Revit.DB.ElementClassFilter(GetType(Autodesk.Revit.DB.CeilingAndFloor), False)
finalCollector.WherePasses(filter1)
Dim filter2 As New Autodesk.Revit.DB.ElementClassFilter(GetType(Autodesk.Revit.DB.RoofBase), False)
finalCollector.UnionWith((New Autodesk.Revit.DB.FilteredElementCollector(doc)).WherePasses(filter2))
Dim filter3 As New Autodesk.Revit.DB.ElementCategoryFilter(BuiltInCategory.OST_Stairs, False)
finalCollector.UnionWith((New Autodesk.Revit.DB.FilteredElementCollector(doc)).WherePasses(filter3))
Dim filter4 As New Autodesk.Revit.DB.ElementIsElementTypeFilter(True)
finalCollector.IntersectWith((New Autodesk.Revit.DB.FilteredElementCollector(doc)).WherePasses(filter4))
list = finalCollector.ToList()
Return list
End Function
Public Shared Function FindAllRoofsCeilingsFloorsAndStairs_Approach2(ByVal doc As RvtDocument) As List(Of Autodesk.Revit.DB.Element)
Dim list As New List(Of Autodesk.Revit.DB.Element)()
Dim finalCollector As New Autodesk.Revit.DB.FilteredElementCollector(doc)
Dim filter1 As New Autodesk.Revit.DB.ElementCategoryFilter(BuiltInCategory.OST_Ceilings, False)
finalCollector.WherePasses(filter1)
Dim filter2 As New Autodesk.Revit.DB.ElementCategoryFilter(BuiltInCategory.OST_Roofs, False)
finalCollector.UnionWith((New Autodesk.Revit.DB.FilteredElementCollector(doc)).WherePasses(filter2))
Dim filter3 As New Autodesk.Revit.DB.ElementCategoryFilter(BuiltInCategory.OST_Floors, False)
finalCollector.UnionWith((New Autodesk.Revit.DB.FilteredElementCollector(doc)).WherePasses(filter3))
Dim filter4 As New Autodesk.Revit.DB.ElementCategoryFilter(BuiltInCategory.OST_Stairs, False)
finalCollector.UnionWith((New Autodesk.Revit.DB.FilteredElementCollector(doc)).WherePasses(filter4))
Dim filter5 As New Autodesk.Revit.DB.ElementIsElementTypeFilter(True)
finalCollector.IntersectWith((New Autodesk.Revit.DB.FilteredElementCollector(doc)).WherePasses(filter5))
list = finalCollector.ToList()
Return list
End Function
Public Shared Function FindAllElements(ByVal doc As RvtDocument) As List(Of Autodesk.Revit.DB.Element)
Dim list As New List(Of Autodesk.Revit.DB.Element)()
Dim finalCollector As New Autodesk.Revit.DB.FilteredElementCollector(doc)
Dim filter1 As New Autodesk.Revit.DB.ElementIsElementTypeFilter(False)
finalCollector.WherePasses(filter1)
Dim filter2 As New Autodesk.Revit.DB.ElementIsElementTypeFilter(True)
finalCollector.UnionWith((New Autodesk.Revit.DB.FilteredElementCollector(doc)).WherePasses(filter2))
list = finalCollector.ToList()
Return list
End Function
End Class
People may wonder why the code has to use fully qualified names for many types in spite of the fact that their namespaces have already been imported by the Imports statements. It’s due to a weird behavior of the VB.NET IDE and compiler and we have discussed it in more detail in another article.
Revit API & VB.NET – ‘Friend’ Is Not Accessible
So most of times we have to make guesses on which filters should be used and what filter arguments should be applied, and do a lot of experiments to figure those out.
Fortunately, the Element Finder of the RevitAddinWizard can help generate all these code and much more complex one in VB.NET for any combinations of the filters and their arguments in a moment.
Recent Comments