FilteredElementCollector ... whereelementsis ... to elements

Hey guys,

Can any of you help me understand the difference between some of the methods used in FilteredElementCollector?

I’m reviewing some of my code and I notice that I use some methods but without being sure why I used them. So with undertstanding some differences I can alter my code where needed.
(and no revitapidocs doesn’t help me with understanding the differences)

So let me show you an example: what is the difference between:

# 1. no where's, no toelements.... I use that most of the times.
selections = DB.FilteredElementCollector(revit.doc, revit.active_view.Id).OfCategory(DB.BuiltInCategory.OST_Ceilings)

# 2. only where.
selections = DB.FilteredElementCollector(revit.doc, revit.active_view.Id).OfCategory(DB.BuiltInCategory.OST_Ceilings).WhereElementIsNotElementType()

# 3. where + elements
selections = DB.FilteredElementCollector(revit.doc, revit.active_view.Id).OfCategory(DB.BuiltInCategory.OST_Ceilings).WhereElementIsNotElementType().ToElements()

Good place to start to understand the Revit API is by going through its documentation :wink: :
https://apidocs.co/apps/revit/2022/263cf06b-98be-6f91-c4da-fb47d01688f3.htm

Developers can assign a variety of conditions to filter the elements that are returned. This class requires that at least one condition be set before making the attempt to access the elements.

Revit will attempt to organize the filters in order to minimize expansion of elements regardless of the order in which conditions and filters are applied.

There are three groups of methods that you can use on a given collector once you have applied filter(s) to it. One group provides collections of all passing elements, a second finds the first match of the given filter(s), and a third provides an iterator that is evaluated lazily (each element is tested by the filter only when the iterator reaches it). You should only use one of the methods from these group at a time; the collector will reset if you call another method to extract elements. Thus, if you have previously obtained an iterator, it will be stopped and traverse no more elements if you call another method to extract elements.

In .NET, this class supports the IEnumerable interface for Elements. You can use this class with LINQ queries and operations to process lists of elements. Note that because the ElementFilters and the shortcut methods offered by this class process elements in native code before their managed wrappers are generated, better performance will be obtained by using as many native filters as possible on the collector before attempting to process the results using LINQ queries.

One special consideration when using this class in .NET: the debugger will attempt to traverse the members of the collector because of its implementation of IEnumerable. You may see strange results if you also attempt to extract the first element or all elements from the collector while the debugger is also looking at the contents of the collector.

1. Collects all elements of the category, regardless it is a type or an instance and it does not cast it in an element set

I was about to write the answer and just got lazy, here is a chatGPTED answer. Pretty good

Here’s a breakdown of the differences between the methods used in your examples and their effects on the FilteredElementCollector:


1. No Where or ToElements:

selections = DB.FilteredElementCollector(revit.doc, revit.active_view.Id).OfCategory(DB.BuiltInCategory.OST_Ceilings)
  • This returns a filtered collector object.
  • The collector represents a queryable collection of elements matching the OfCategory filter (in this case, ceiling elements in the active view).
  • Key points:
    • The elements aren’t retrieved yet—this is a lazy evaluation.
    • You can continue chaining additional filters or methods.
    • This is useful when you want flexibility to further refine the query later.

2. Using WhereElementIsNotElementType:

selections = DB.FilteredElementCollector(revit.doc, revit.active_view.Id).OfCategory(DB.BuiltInCategory.OST_Ceilings).WhereElementIsNotElementType()
  • Adds a filter to exclude element types (family types or system types).
  • This ensures you only get instances of the elements (e.g., placed ceilings in the model) rather than element types (e.g., ceiling family types like “Basic Ceiling”).
  • Key points:
    • Without WhereElementIsNotElementType, the collector might include both types and instances.
    • Use this when you only need actual instances present in the project.

3. Using ToElements:

selections = DB.FilteredElementCollector(revit.doc, revit.active_view.Id).OfCategory(DB.BuiltInCategory.OST_Ceilings).WhereElementIsNotElementType().ToElements()
  • The ToElements() method resolves the collector into a list of elements immediately.
  • This means:
    • All elements matching the applied filters are collected and returned as a Python list (or a .NET IList in Revit’s API).
    • The query is executed, and the elements are materialized in memory.
  • Key points:
    • Use ToElements() when you need to work with a static list of elements.
    • Avoid using this unless necessary because it impacts performance (eager evaluation vs lazy).

When to Use Each Approach:

  1. No Where or ToElements:
    Use this when you want to construct a flexible query or apply additional filters later. This is efficient since the collection remains lazy until accessed.

  2. WhereElementIsNotElementType:
    Use this when you’re certain you don’t need element types in your results. This narrows the scope of your query to only instances.

  3. ToElements:
    Use this when you need a resolved, iterable list of elements immediately (e.g., if you’re looping over them or need random access). Be cautious of performance implications, especially for large queries.


Example Summary in Practice:

  • Lazy Query: Use without ToElements() for better performance and flexibility:

    collector = DB.FilteredElementCollector(revit.doc, revit.active_view.Id).OfCategory(DB.BuiltInCategory.OST_Ceilings)
    for elem in collector:  # Efficient, elements are retrieved one at a time
        print(elem.Id)
    
  • Immediate Results: Use ToElements() when you need a static list:

    elements = DB.FilteredElementCollector(revit.doc, revit.active_view.Id).OfCategory(DB.BuiltInCategory.OST_Ceilings).ToElements()
    print([e.Id for e in elements])  # Full list materialized in memory
    

By choosing the right combination based on your needs, you can optimize both performance and clarity in your code.

plus this

and you should have a look at the building coder articles around quick and slow filters

https://thebuildingcoder.typepad.com/blog/2019/04/slow-slower-still-and-faster-filtering.html

k. now that I could have chatgpt’d that myself … ughh. Now I’m the one feeling lazy.

I found the description in apidocs (or revitapidocs for that matter) not descriptive enough for me. But didn’t think of consulting chatGPT for that

Lazy Query feels like yield in python.

Agree with you on choosing the right combination is key for efficiency. I’m going to grab another cup of coffee and dive head first into the fine print of this!

Thanks @Jean-Marc

It does

Enjoy your coffee ;p

1 Like

and this is a good question to ask oneself!

btw, have you tried timing sections of your script with decorators (getting you down another rabbit hole)

1 Like

Already in that rabbit hole.

Since I’m reviewing my code, and I learned a lot in the meanwhile I have to recode a lot.

Guard clauses.
Make more but smaller functions to improve readability / simplicity.
Wrapping ‘everything’ in a function if possible and run ‘main()’
Apply decorators

So I’m already there. It will add sooooo much to my scripts.

image

2 Likes

Welcome to the Autodesk API world! :rofl:

And consider yourself lucky you’re looking at Revit API and not AutoCADs :sweat_smile:

1 Like

AutoCAD? Don’t you just use lisp…?:wink:

2 Likes