Funnily enough, a very similar question was raised shortly after by another developer:
Question: Is there a built-in method to get a bounding box of the entire model, similar to the method Element.get_BoundingBox?
The only way I see right now is to use IExportContext, go through all the visible elements and get the minimum and maximum coordinates among the all points. But on the large models this method may take a while.
Is there some faster method?
Answer: Yes, maybe.
As I already suggested above, there are a number of possibilities:
- Compute the convex hull of the entire design, and then determine the width and length of that:
http://thebuildingcoder.typepad.com/blog/2009/06/convex-hull-and-volume-computation.html
- Skip the convex hull part and just iterate over all building elements, enlarging a bounding box containing all their vertices as you go along:
bounding box B = (+infinity, -infinity)
for all elements
for all element geometry vertices P
if P < B.min: B.min = P
if P > B.max: B.max = P
- A faster approach: use the element bounding box vertices instead of all the element geometry vertices.
One aspect that I did not mention that makes a huge difference for large models:
The bounding box of all elements is immediately available from the element header information, and thus corresponds to a quick filtering method, whereas all the approaches that require the full geometry correspond to a slow filter.
Using a custom exporter, like you suggest, is also a good idea, and corresponds to a slow filter.
If you are interested in good performance in large models, I would aim for a quick filter method, e.g., like this:
public static class JtBoundingBoxXyzExtensionMethods
{
/// <summary>
/// Expand the given bounding box to include
/// and contain the given point.
/// </summary>
public static void ExpandToContain(
this BoundingBoxXYZ bb,
XYZ p )
{
bb.Min = new XYZ( Math.Min( bb.Min.X, p.X ),
Math.Min( bb.Min.Y, p.Y ),
Math.Min( bb.Min.Z, p.Z ) );
bb.Max = new XYZ( Math.Max( bb.Max.X, p.X ),
Math.Max( bb.Max.Y, p.Y ),
Math.Max( bb.Max.Z, p.Z ) );
}
/// <summary>
/// Expand the given bounding box to include
/// and contain the given other one.
/// </summary>
public static void ExpandToContain(
this BoundingBoxXYZ bb,
BoundingBoxXYZ other )
{
bb.ExpandToContain( other.Min );
bb.ExpandToContain( other.Max );
}
}
#region Get Model Extents
/// <summary>
/// Return a bounding box enclosing all model
/// elements using only quick filters.
/// </summary>
BoundingBoxXYZ GetModelExtents( Document doc )
{
FilteredElementCollector quick_model_elements
= new FilteredElementCollector( doc )
.WhereElementIsNotElementType()
.WhereElementIsViewIndependent();
IEnumerable<BoundingBoxXYZ> bbs = quick_model_elements
.Where<Element>( e => null != e.Category )
.Select<Element,BoundingBoxXYZ>( e
=> e.get_BoundingBox( null ) );
return bbs.Aggregate<BoundingBoxXYZ>( ( a, b )
=> { a.ExpandToContain( b ); return a; } );
}
#endregion // Get Model Extents
I implemented that for you and included it in The Building Coder samples release 2017.0.127.6:
https://github.com/jeremytammik/the_building_coder_samples/releases/tag/2017.0.127.6
Response: So, in fact I was right and the only method is to get this information from the geometry.
I’ll benchmark with CustomExport (as I suggested) and FilteredElementCollector (as you did) with large models and let you know.
I think your way should be faster, but not correct in some cases:
Case 1) Linked models. It doesn’t consider linked models if they are.
Case 2) Even if we extend this method and get the elements geometry from the linked models, we need to transform the coordinates from linked model to the hosted model, as BoundingBox returns the coordinates in a source model.
Case 3) Your method returns all the elements. In my case I need only the view specific Model BoundingBox. Yes, we can use FilteredElementCollector with ActiveView, but it can be a section view or 3d view bounded by section box.
Therefore, FilteredElementCollector should be faster, but needs to consider all the cases (maybe there are more than I mentioned). As CustomExporter iterate only the visible geometry, exactly like it presented on the view, this way is more reliable.
Anyway, the benchmark will show the results ☺
Cheers,
Jeremy