I have really large models where I do some ElementIntersectsElementFilter that can take a long time to process, and sometimes I want to abort them 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.
Thanks!
Dear Mathias,
Thank you for the interesting question.
I am not aware of any way to do this, I'm afraid.
However, I'm checking wth the development team for you to see what they may come up with.
Is there any way you can further optimise the filter beforehand?
I can hardly imagine that you have completely exhausted all filtering optimisation possibilities and it is still slow, however large the model is.
Cheers,
Jeremy
Thank you Jeremy. I would be very interested in any findings from the dev team.
I've been thinking in the same line as you but can't really see how I can filter more efficiently at the moment. I thought maybe a bounding box filter would make it quicker, but I suspect, after trying, that the ElementIntersectsElementFilter works somewhat similarly under the hood since I got close to same as before (a bit slower even).
But this is how it 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?
private 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... } }
Thanks again!
The bounding box filter is a quick filter, whereas the element intersection one is slow.
This can make a huge difference, especially in large models:
http://thebuildingcoder.typepad.com/blog/2015/12/quick-slow-and-linq-element-filtering.html
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 );
Cheers,
Jeremy
Thanks! That is what I tried, as I stated in my previous response. The result was a slight decrease in performance by a few percent. And for some reason I got fewer collisions as a result which felt a bit odd =S
Wow! Well, in that case, all your elements have already been completely loaded when the collector is executed. I guess you have a lot of memory. I cannot suggest anything further yet. Let's see what the devteam come up with.
So you mean that my approach is bad from a memory perspective? I haven't really noticed or measured so I didn't think about it. Should I use the BoundingBoxIntersectsFilter to lower the memory footprint?
Thanks!
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.
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.
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 to X 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 if there are many.
Iterating over them one by one does not, costs no time, and can be interrupted any time you like.
Please confirm that this solves your problem entirely.
Thank you!
Cheers,
Jeremy
Devteam adds: In one of the samples, they loop over walls and call the same filter for each. So there's an opportunity to stop at each wall before calling it again. But even within the same iteration, it should be possible to break early if it's reached some limit.
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 😃
Thank you for your confirmation.
I edited and published our conversation for posterity:
https://thebuildingcoder.typepad.com/blog/2019/02/cancelling-filtered-element-collection.html
Thank you for raising this.
Cheers,
Jeremy
Can't find what you're looking for? Ask the community or share your knowledge.