Reputation: 71
I was following example shown in this youtube video (https://www.youtube.com/watch?v=WU_D2qNnuGg&index=7&list=PLc_1PNcpnV5742XyF8z7xyL9OF8XJNYnv) which illustrates superiority of filtering methods in Revit API over usual iteration. But my code is significantly slower than the the iteration method :
filter method-0.16 secs
iteration method-0.06 secs
My code using filter method is :
import Autodesk.Revit.DB as DB
doc=__revit__.ActiveUIDocument.Document
uidoc=__revit__.ActiveUIDocument
height_param_id=DB.ElementId(DB.BuiltInParameter.WALL_USER_HEIGHT_PARAM)
height_param_prov=DB.ParameterValueProvider(height_param_id)
param_equality=DB.FilterNumericEquals() # equality class
height_value_rule=DB.FilterDoubleRule(height_param_prov,param_equality,10,1e-02)
param_filter=DB.ElementParameterFilter(height_value_rule)
# This program significantly slows down for the next line
walls=DB.FilteredElementCollector(doc)\
.WherePasses(param_filter)\
.ToElementIds()
uidoc.Selection.SetElementIds(walls)
For iteration following code was used.
from System.Collections.Generic import List
import Autodesk.Revit.DB as DB
doc=__revit__.ActiveUIDocument.Document
uidoc=__revit__.ActiveUIDocument
sheet_collector=DB.FilteredElementCollector(doc)\
.OfCategory(DB.BuiltInCategory\
.OST_Sheets)\
.WhereElementIsNotElementType()\
.ToElements()
walls=DB.FilteredElementCollector(doc)\
.OfCategory(DB.BuiltInCategory.OST_Walls)\
.WhereElementIsNotElementType()\
.ToElements()
tallwallsids=[]
for wall in walls:
heightp=wall.LookupParameter('Unconnected Height')
if heightp and heightp.AsDouble()==10:
tallwallsids.append(wall.Id)
uidoc.Selection.SetElementIds(List[DB.ElementId](tallwallsids))
Upvotes: 2
Views: 5934
Reputation: 11
So the original post doesn't exactly have a question in it, so I interpret it as "WHY is my code slower?". I don't mean to be overly critical, because Konrad is correct, but there is more to it than his answer includes. He is right to point out the use of quick filters and slow filters.
DO use quick filters whenever possible. These operate on ElementRecord (i.e. similar to a file header of the Element data type internal to Revit DB) BEFORE expanding Elements into memory (which is why they are so fast). If you can't use Quick, Slow are OK.
Specifically, category and class filters are some of my favorite QuickFilters.
However, when it comes to slow filters - and Parameter filtering logic in particular - I prefer to use Lambda expressions with the LINQ library over the (IMHO overly cumbersome syntax of) ElementParameterFilter.
My query would look something like this: (In C# syntaxt)
List<Wall> filteredWalls = new FilteredElementCollector(doc)
.OfCategory(BuiltInCategory.OST_Walls)
.OfClass(typeof(Wall))
//At this point, Elements are Marshalled into memory
.Cast<Wall>()
//At this point, elements are cast as Wall instead of Element
//for Revit Versions that utilize BuiltInParameter -> see also newer ForgeTypeId
.Where(w => w.get_Parameter(BuiltInParameter.WALL_USER_HEIGHT_PARAM)?.AsDouble() == 10.0)
.ToList();
In Python, it looks like the category and class filtering syntax are similar, but instead of the LINQ library (unique to C#), you can use list comprehension: (In Python syntax - separated to highlight post-filtering)
collector_results = DB.FilteredElementCollector(doc)
.OfCategory(BuiltInCategory.OST_Walls)
.OfClass(typeof(Wall))
.ToList();
paramFilteredWalls = [w for w in collector_results if w.get_Parameter(BuiltInParameter.WALL_USER_HEIGHT_PARAM) != None and w.get_Parameter(BuiltInParameter.WALL_USER_HEIGHT_PARAM).AsDouble() == 10.0]
you can further abstract the final filtering logic by using a python method like:
def MeetsHeightRequirement(wall, required_height = 10.0) -> bool:
height_param = wall.get_Parameter(BuiltInParameter.WALL_USER_HEIGHT_PARAM)
return height_param != None and height_param.AsDouble() == required_height
This would make your final comprehension look like this:
paramFilteredWalls = [w for w in collector_results if MeetsHeightRequirement(w)]
Good luck!
Upvotes: 1
Reputation: 3706
This makes sense if you consider the amount of elements that the two methods have to consider. First method:
walls=DB.FilteredElementCollector(doc)\
.WherePasses(param_filter)\
.ToElementIds()
In this method you are asking the filter to consider ALL elements in the model. That's potentially a lot of elements to pass through the filter. That's opposed to:
walls=DB.FilteredElementCollector(doc)\
.OfCategory(DB.BuiltInCategory.OST_Walls)\
.WhereElementIsNotElementType()\
.ToElements()
In this method you use the QUICK filter OfCategory()
and another WhereElementIsNotElementType()
to narrow down the selection to only Wall
instances. Even though you follow that through with a simple for
loop which is the slow component here, its still FASTER than passing ALL elements in the model through the first filter.
You can optimize it by creating a filter like so:
walls=DB.FilteredElementCollector(doc)\
.OfCategory(DB.BuiltInCategory.OST_Walls)\
.WhereElementIsNotElementType()\
.WherePasses(param_filter)
.ToElements()
This would actually combine the quick category filter, element type filter, and slow parameter filter to potentially be an overall faster and easier to read solution.
Give it a go, and let me know if this makes sense.
Cheers!
Upvotes: 2
Reputation: 8294
What iteration method?
Nowadays, filtered element collectors are normally the only way to retrieve and iterate over Revit database elements.
The filtered element collector in itself is probably fast.
If you have a huge number of walls and your memory is limited, the call to ToElementIds
may consume significant resources.
SetElementIds
may also cost time.
Check out the extensive Revit API forum discussion on filtered element collector by pipe system types for more on this.
I suggest you provide a complete minimal reproducible sample case equipped with benchmarking code for each of those method calls to prove the performance degradation.
Upvotes: 1