I just discovered an interesting and not completely obvious aspect of using a filtered element collector in
the Revit API discussion forum thread
on aborting a long running ElementIntersectsElementFilter
:
Question: I have really large models where I use an ElementIntersectsElementFilter
that can take a long time to process, and sometimes I want to abort it in a graceful way. Is this possible?
I've read about task cancellation tokens, but all the examples I've seen using this approach have been loops where I can check if cancellation has been requested. But the ElementIntersectsElementFilter is out of my control and I can't really see how I can cancel it.
I tried to implement the filter as efficiently as possible.
I thought maybe a bounding box filter would make it quicker.
However, after trying, the ElementIntersectsElementFilter
seemed to be a bit faster than that.
This is how it currently looks. I have a loop that I can cancel via a cancellation token. But I'm still interested in a more efficient filter if possible. Do you see anything obvious?
List<Element> WallMepClashDetection( Document doc, List<Element> walls ) { var filterCategories = new List<BuiltInCategory> { BuiltInCategory.OST_DuctCurves, BuiltInCategory.OST_PipeCurves }; foreach( var wall in walls ) { var clashingElements = new FilteredElementCollector( doc ) .WhereElementIsNotElementType() .WherePasses( new ElementMulticategoryFilter( filterCategories ) ) .WherePasses( new ElementIntersectsElementFilter( wall ) ); // Do stuff... } }
Answer: I can hardly imagine that it remains slow if you optimise it as much as possible, however large the model is.
The bounding box filter is a quick filter, whereas the element intersection one is slow, cf., quick, slow and LINQ element filtering
This can make a huge difference, especially in large models.
Can you try this for starters?
var cats = new List<BuiltInCategory> { BuiltInCategory.OST_DuctCurves, BuiltInCategory.OST_PipeCurves }; var mepfilter = new ElementMulticategoryFilter( cats ); BoundingBoxXYZ bb = wall.get_BoundingBox( null ); Outline o = new Outline( bb.Min, bb.Max ); var bbfilter = new BoundingBoxIntersectsFilter( o ); var clashingElements = new FilteredElementCollector( doc ) .WhereElementIsNotElementType() .WherePasses( mepfilter ) .WherePasses( bbfilter );
If the element intersection filter is equally fast or faster, that means that all your elements have already been completely loaded when the collector is executed. I guess you have a lot of memory.
Response: Should I use the BoundingBoxIntersectsFilter
to lower the memory footprint?
Answer: If your elements are not already all loaded in memory, the element intersection filter will force them to load. The bounding box filter will not.
Another question: Do you want to cancel because you found what you were looking for, or because you reached some time limit?
Generally, the evaluation is on demand, using iterators, so as long as you don't try to convert the results to a collection, you can write the code to stop whenever you want.
Response: Oh, that's interesting. I will have to check memory usage when comparing the two cases, just out of curiosity.
When I started out I didn't have any means of seeing progress when running heavy intersection filters. And my main motivation was to be able to cancel if the command felt unresponsive or hung. But I've since refactored to use the loop approach which makes it possible to do that and also show a progress bar. I think this applies to any long running command that is out of the control of the developer.
Answer: Yes, please check memory usage.
Also understand the concept of iterators.
You can do the following:
filtered element collector X = ... foreach element in X: do something with element break out of the loop if you wish at any time
You can interrupt your processing of the results returned by the collector at any time.
You should NOT convert the collector to a list or any other collection.
That would force it to return all the elements, which would cost time and memory, with a performance hit if there are many of them.
Iterating over them one by one does not, costs no time, and can be interrupted any time you like.
You loop over walls and call the same filter for each. So, there's an opportunity to stop at each wall before calling it again. In addition, even within the same iteration, you can break early, any time you like, e.g., if you reach some limit.
Response: Thank you for the information.
I use loops now so I will be able to cancel the command as suggested.
Thanks for taking the time to help, I've learned some things =)