The topic of how to determine all views in which a given element is visible has been discussed several times on the past, and a couple of viable solutions have been suggested.
However, for large projects, performance becomes an issue.
This question was raised again by Abba Lustgarten
of Abba CAD Abba Inc. and
discussed quite extensively with Erik Eriksson
of White in
the Revit API discussion forum thread
on finding views in which an element is visible (by geometry or actual visibility),
with Erik sharing a View
extension method IntersectsBoundingBox
that helps alleviate the performance impact.
Before getting to that, here is a picture from the early morning stroll from the hotel to the Munich Forge accelerator:
Here is what I would like to do:
For example, if I select a specific door, then I want to find all the floor plan views that include the door, plus section views that show the door (cut or projection), any elevation views that show the door, etc.
Without having given this too much work yet, it would seem that there are two general issues here, and I am guessing that Revit might deal with them differently:
In my situation, what I really would like to find at the moment is: If all visibility of all model elements is on, then what views would show the element.
Lucky you, asking the right question.
This one, we can answer, and already have, including a dedicated external command CmdViewsShowingElements
to prove it to The Building Coder samples:
Thanks for your reference to the sample code and the brief history of this.
I am just going to give your CmdViewsShowingElements
a try, and will let you know how it works for me.
As I do so, I just have this question:
You mentioned that your initial solution for this involved looping through views and using a filtered element collector (with a view_ID parameter) to check for object visibility. Colin pointed out the potential slowness of this, and your note implied that he had come up with what you considered to be a 'better way'.
From my first glance, it looks to me like this sample also loops through a series of views and uses a view-specific filtered element collector to check for element visibility in each view. I don't see your original code to compare, but I am wondering if Colin's code does in fact overcome the efficiency problem that he pointed out, or if he just found a better way to implement the approach.
I am only asking to make sure I am not missing something, and to make sure I don't go too far down the wrong path as I am bringing my Revit API skills up to speed.
I think the blog post is pretty clear on that: "Here is the CmdViewsShowingElements
implementation including Colin's two extension methods..."
What more can I do to clarify?
Please note that the GitHub repo is maintained and contains the most up-to-date code.
What I wanted to clarify was this:
In the 'original' code, (which I haven't seen), Colin raised the concern that when checking each view for element visibility, Revit would essentially have to open the view and this could take a long time, especially if a lot of views are being checked.
In the posted code, do you think this problem is overcome? Or would the implementation of the filtered element collector still be 'opening' the views, with the potential time problem.
In my case, I am likely to be looking at hundreds of views, so this could be significant.
Practically speaking, as you advise, I am working on the assumption that the GitHub posted sample is most recently 'recommended' approach. Thanks for putting it there and helping me find it.
Revit will open all the views and it is going to take A LOT of time if you have a lot of views within a complex project.
I've just run into that problem and ended up here.
Revit caches the results for a while so if you ask for the same thing one more time it's going to go a lot faster, but if you change your input, you are going to have to wait again.
I'm seeing times of 11 sec when I'm going through 560 views.
I don't think the filtered element collector is designed with this in mind...
So far, there isn't an alternative that I can see easily. It seems that the key for me will probably be to 'pre-filter' the list of views that I want to check.
For example, by the name of the view, or by the type of view (only Floor Plans, or Section Views, etc.).
I built an interface that does this and gives me a list of 'eligible' views and lets me select the ones I want to check for element visibility. This is mostly for testing the whole mechanism at the moment, since I don't totally trust the filter mechanism yet.
This is all a bit preliminary. I found that if I select something like a Stair, or a wall, it seems to reliably find it in views. However, I tried selecting some other things, like a mullion on a curtain wall, etc., and that doesn't seem to register as visible to the filtered element collector, even though it seems to be visible in the view. If and when I find a clearer pattern, I will post something about it, and if I don't, I will post something about it.
As for the performance, I also noticed that 'cache' effect, so that running the filter repeatedly on the same view is much faster.
I am actually seeing the Revit interface show me that it is regenerating the view (at the bottom of the screen) when it is big and takes a while.
I have a question for you:
You mentioned 560 views taking 11 seconds. Did that mean 11 seconds to scan all of them, or 11 seconds per view?
I.e., are you getting through 50 views per second?
I am looking at quite a large model with some fairly big and complex views, but it is averaging a few views per second on small ones and a few seconds per view on big ones.
For my purposes, 11 seconds for 560 views would be ok.
Again, thanks for your contributions. I would be eager to hear if you have something working well for you.
Ok, yes. Minimising the views is key, I also prefilter a lot.
Yes, the 11 sec are for all 560 views and that's the total time for prefiltering as well, so I might be getting little less than 10 secs for the views.
However, I might have found another way that looks really promising.
By taking and comparing bounding boxes of views and the target elements (intersections), I was able to get results in under a second for all views. However, in contrast to the filtered element collector way, this requires some testing and setup to get accurate results. You need to make sure that the view has cropping enabled, set the correct height of the view bounding box and some more. Furthermore, I noticed that section views need some special care.
I haven't finished my tests, but like I said, it looks promising.
Does sound promising. I was planning on using the bounding box idea, but I am somewhat new at the Revit API, and have not dealt with the bounding box of views yet. Somehow I suspect there is a level of complexity once you factor in different coordinate systems (e.g. shared coordinates), view clipping, floor level heights, etc.
If you have encountered any of these pitfalls, I would love to hear about them.
Thank you for the valuable discussion, especially Erik with his in-depth real-world experience.
The note on the huge speed-up using bounding boxes is of fundamental importance.
You absolutely must be aware of quick filters versus slow filters and the difference between them.
Quick filters can be executed without regenerating the views and without fully loading the nitty-gritty detailed data of a huge model.
The bounding box check is a quick filter.
Actually, I'm not using the quick filter. I tried to, but the bounding boxes of the views need altering because the Z values are not correct (for plan views at least). I'm prefiltering a lot with quick filters, but when it comes to the actual bounding box comparison I've expanded the views into memory. You can see this in the code in the bottom.
Anyway, I've perfected my bounding box filter into something that works for me. I tried to get it to work in section views, but that was just too big of an obstacle for me today, the client didn't need that in the end, so I just skipped that part. The problem was figuring out the bounding box since the coordinate system shifts in section views.
I tried to look at the transform of the bounding box, and shift it accordingly, but it didn't work.
Maybe you can shed some light on what is wrong or how to accomplish it, Jeremy?
The performance of the bounding box version is extreme in comparison, using a stopwatch I measured the following times:
Anyway, here is the extension method I use right now:
∫∫/// <summary> /// View extension predicate method: does /// this view intersect the given bounding box? /// </summary> public static bool IntersectsBoundingBox( this View view, BoundingBoxXYZ targetBoundingBox ) { Document doc = view.Document; var viewBoundingBox = view.CropBox; if( !view.CropBoxActive ) { using( Transaction tr = new Transaction( doc ) ) { //If the cropbox is not active we can't //extract the boundingbox (we rollback so we //don't change anything and also increase //performance) tr.Start( "Temp" ); view.CropBoxActive = true; viewBoundingBox = view.CropBox; tr.RollBack(); } } Outline viewOutline = null; if( view is ViewPlan ) { var viewRange = ( view as ViewPlan ).GetViewRange(); //We need to change the boundingbox Z-values because //they are not correct (for some reason). var bottomXYZ = ( doc.GetElement( viewRange .GetLevelId( PlanViewPlane.BottomClipPlane ) ) as Level ).Elevation + viewRange.GetOffset( PlanViewPlane.BottomClipPlane ); var topXYZ = ( doc.GetElement( viewRange .GetLevelId( PlanViewPlane.CutPlane ) ) as Level ).Elevation + viewRange.GetOffset( PlanViewPlane.CutPlane ); viewOutline = new Outline( new XYZ( viewBoundingBox.Min.X, viewBoundingBox.Min.Y, bottomXYZ ), new XYZ( viewBoundingBox.Max.X, viewBoundingBox.Max.Y, topXYZ ) ); } //this is where I try to handle section views. //But I can't get it to work!! if( !viewBoundingBox.Transform.BasisY.IsAlmostEqualTo( XYZ.BasisY ) ) { viewOutline = new Outline( new XYZ( viewBoundingBox.Min.X, viewBoundingBox.Min.Z, viewBoundingBox.Min.Y ), new XYZ( viewBoundingBox.Max.X, viewBoundingBox.Max.Z, viewBoundingBox.Max.Y ) ); } using( var boundingBoxAsOutline = new Outline( targetBoundingBox.Min, targetBoundingBox.Max ) ) { return boundingBoxAsOutline.Intersects( viewOutline, 0 ); } }
I hope someone finds it useful and good luck Abba!
Thank you Erik. I will hopefully soon have a chance to dig into this in full. Right away, I can see a few major tricks that I am sure would have taken me quite a while to uncover.
In particular, needing to make the crop box active to get the bounding box, and thus using a transaction and rolling it back in order to do this. Question: if the cropping box is NOT active on a particular view, is the bounding box really what reflects the visibility of an object in it?
My gut told me that levels, transforms, etc. would be necessary to filter views according to their geometrical extents. This seems to have been right, and I haven't gotten my hands very wet with those yet. Jeremy, can you give a brief tutorial (or point to a sample or previous overview) on:
It turns out that section views are probably the most important for me, so it would be great to have a 'universal' view extent calculator method.
It does make sense that filtering by the geometrical extents would be much faster, even with slow filters, since we are considering the extents of only a few objects (view, cropping box, element?), compared with many thousands of elements that must be processed in some way when a view is actually opened, as seems to happen when the filtered element collector goes at it. However, I think it will be critical to make it work reliably on all appropriate types of views.
Thanks especially for the snippet. If and when I get this working, I will post whatever extent-getter I come up with.
I'm glad you found some useful bits.
I don't think that Revit is using the bounding box to calculate of the element is visible in the view, it's far too slow for that (select element by id and press show, for instance...). It's just another way of doing it, maybe they don't do it because they too know they can't rely on the bounding box extents and the fact that it must be turned on or maybe we'll see a much faster way of showing elements in the UI in the coming version of Revit =P
I'm sure there is a way of using this method with section views and from what I've seen, Jeremy is a whole lot better at this type of math than I am =)
By looking at the bounding box of the section view, it's pretty clear that the Z values are messed up here to (or have some other meaning, that I can't comprehend).
God, while writing this I started investigating a little bit more.
I wrote a Room drawing generator last year and that had some heavy lifting when it came section views and figuring out and changing their cropboxes.
This is a theory: but if you skip the original cropbox and use the new GetCropRegionShape
using the CropManager
. You get the crop in lines in project coordinates and then you can create a Solid by extrusion (using the far clip offset) using the GeometryCreationUtils and then use the SolidUtils and calculate solid intersections. That would probably be a lot faster.
This has an obvious problem and that is that there could still be elements hiding your elements in the views. That is a problem I have in my bounding box version too.
This is just me thinking out loud, there might be other problems with this workflow, what do you think, Jeremy?
Sorry that I never answered your questions, guys, and left some open ends dangling.
I hope we can resolve them by and by.
Here are some pretty old and rudimentary introductions to the topics that you asked for, Abba:
I added Erik's View
extension method IntersectsBoundingBox
to The Building Coder samples
release 2017.0.131.3 in the
module CmdViewsShowingElements.cs,
in case anyone else would like to play with it.