Getting the Overall Size of Parts

The question of getting the size of a part frequently comes up and I wanted to spend some time discussing the existing functionality and other options. The discussion here applies to both Fusion 360 and Inventor (and probably most other CAD systems).

What is a Bounding Box?

The easiest way to get a size of a part is to get the bounding box (also called the range box) of the object. Let’s look at what a bounding box is by looking at some simple 2D examples first. A 2D bounding box is a rectangle that encompasses the object. Below is an example of a part (the shaded area) and it’s bounding rectangle (dashed lines).

Range1

A typical bounding box is defined using just two points; the minimum and maximum points. The picture below shows the two 2D points used to define the bounding box. A limitation with defining a bounding box in this way is that the rectangle is oriented such that it’s edges are always parallel to the x and y model axes.

Range2

Below is the same part that has been rotated and its bounding box is displayed. In this case the size of the bounding box probably isn’t what you wanted and in most cases is not particularly useful.range3

What you probably really wanted was this bounding box but that box can’t be defined by a simple two points and needs additional information to define the orientation.

Range4

Bounding boxes aren’t something specific to the Inventor and Fusion 360 API’s but are a basic concept in computer graphics. The usual intent of a bounding box is not to provide a tight fitting, orientated box around a part but instead to provide a very fast box that fits around a certain piece of geometry. The only thing guaranteed is that the object is completely contained within the box and there is no guarantee about how tight the box fits around the object. In many cases, like in the first example, the size of the range box is the same as the physical size of the part but there are cases when this is not the case. Below, on the left, is an example from Inventor where the bounding box is quite a bit larger than the visible part. At first glance this would seem to be a bug in the calculation but the bounding box is including the control polygon of the spline surface, as shown on the right. So it may not be what you want but it is arguably correct and is the simplest for Inventor to calculate.

range5

Bounding boxes are intended to be used in more complex operations where you need to first determine if a particular object should be considered in a calculation. To determine if an object should be included you can often use it’s bounding box to see if it’s within the area to be calculated. This is a very simple and fast calculation that can be performed to determine if the object can be eliminated from the more complicated and costly computations. Operations with bounding boxes like this are also very simple. For example you can combine bounding boxes to create a new box that contains the originals.

The Inventor API has the Box and Box2d objects that are used to pass back the bounding box information and these objects also provide properties and methods to let you work with the box information. The Fusion 360 API has the BoundingBox3D and BoundingBox2D objects that provide the equivalent functionality. The most important feature of these objects is that they return the coordinates of the min and max points.

Getting Accurate Bounding Boxes

As discussed above, the bounding box returned by the Inventor RangeBox property and returned by the Fusion360 boundingBox property may not be what you need. Unfortunately, a bounding box that is always tight fitting to the visible graphics is not currently provided by either Inventor or Fusion 360. However, it is possible to calculate a very close approximation on your own using other API functionality. What I’m going to show you here will create a tight range box but it will still be oriented so it’s parallel to the world XYZ planes. The ability to automatically calculate a bounding box that is oriented to get the tightest fitting box possible is a very difficult problem that I haven’t seen a solution to. It’s something you could probably write a PhD paper on. The easiest approach to the orientation problem is to ask the user for help by letting them define the coordinate system the bounding box should be calculated in, essentially defining the directions for the length, width and height. That’s possible but a little more than I wanted to go into in this post so I’ll limit this discussion to calculating a tight fitting bounding box that’s oriented parallel to the world XYZ planes.

To do this you can use a triangular mesh representation of the model and calculate your own bounding box by including all of the vertices of the mesh. The result will be as accurate as the mesh. In both Inventor and Fusion 360 you can calculate a mesh representation to any tolerance you want or you can get the existing mesh that Inventor or Fusion 360 is using for the display of the model. The actual model is made up of accurate smooth surfaces, but a mesh representation is created and used internally to display the model. Using the existing mesh is faster that calculating a new one but if accuracy is critical you might want to calculate a new mesh. The shape of the model will also affect the accuracy. A model made up entirely of planes will result in an exact mesh representation regardless of the tolerance of the mesh because the triangles exactly represent those faces. If a model has any curved surfaces, even as simple as a cylinder, then the mesh is an approximation. I’ve tried to illustrate this in the picture below. On the left are the original models where one model is made entirely of planes and the other has planes, cylinders, and a sphere. On the right is the triangular mesh representations of the two parts. The first model is an exact representation because the shape can be exactly represented by the triangles. However, the second model has curved surfaces and has to be approximated by the triangles. The accuracy of the bounding box you build will depend on their being a triangle vertex at the minimum and maximum X, Y, and Z sizes of the model. The more triangles, the more likely you’ll have vertices at those locations but it also means more processing for Inventor or Fusion to calculate the mesh and more processing for you to analyze the mesh to build the range box.

MeshExample

I’ve used the code similar to that below in a few programs I’ve posted previously. The calculateTightBoundingBox function takes in a body and optional tolerance and returns the tight fitting bounding box. If no tolerance is provided, the existing display mesh is used. There can be more than one display mesh because Inventor and Fusion will generate different meshes depending on how close you zoom into the model so the display always appears smooth. When using the display mesh, this function will use the highest quality display mesh that exists. If a tolerance is provided, then a new mesh is calculated using that tolerance. There is a test function for each that is used to test the calculateTightBoundingBox function. Samples below are provided in Python for Fusion 360 and VBA and Visual Basic for Inventor. Below is the result on the previous model where the bounding box returned by Inventor was much larger.

NewRange

Fusion 360 Python

# Function to test the calculateTightBoundingBox function.
def run(context):
    ui = None 
    try:
        app = adsk.core.Application.get()
        ui = app.userInterface
        des = adsk.fusion.Design.cast(app.activeProduct)
        
        bodySelect = ui.selectEntity('Select the body.', 'Bodies')
        body = adsk.fusion.BRepBody.cast(bodySelect.entity)
        
        # Call the function to get the tight bounding box.
        bndBox = calculateTightBoundingBox(body)
        
        # Draw the bounding box using a sketch.
        sk = des.rootComponent.sketches.add(
            des.rootComponent.xYConstructionPlane)
        
        lines = sk.sketchCurves.sketchLines
        
        minXYZ = bndBox.minPoint
        minXYmaxZ = adsk.core.Point3D.create(
            bndBox.minPoint.x, bndBox.minPoint.y, bndBox.maxPoint.z)
        minXmaxYZ = adsk.core.Point3D.create(
            bndBox.minPoint.x, bndBox.maxPoint.y, bndBox.maxPoint.z)
        minXZmaxY = adsk.core.Point3D.create(
            bndBox.minPoint.x, bndBox.maxPoint.y, bndBox.minPoint.z)
        
        maxXYZ = bndBox.maxPoint
        maxXYminZ = adsk.core.Point3D.create(
            bndBox.maxPoint.x, bndBox.maxPoint.y, bndBox.minPoint.z)
        maxXZminY = adsk.core.Point3D.create(
            bndBox.maxPoint.x, bndBox.minPoint.y, bndBox.maxPoint.z)
        maxXminYZ = adsk.core.Point3D.create(
            bndBox.maxPoint.x, bndBox.minPoint.y, bndBox.minPoint.z)
        
        lines.addByTwoPoints(minXYZ, maxXminYZ)
        lines.addByTwoPoints(minXYZ, minXZmaxY)
        lines.addByTwoPoints(minXZmaxY, minXmaxYZ)
        lines.addByTwoPoints(minXYmaxZ, minXmaxYZ)
        
        lines.addByTwoPoints(maxXYZ, maxXYminZ)
        lines.addByTwoPoints(maxXYZ, maxXZminY)
        lines.addByTwoPoints(maxXYminZ, maxXminYZ)
        lines.addByTwoPoints(maxXZminY, maxXminYZ)
        
        lines.addByTwoPoints(minXYZ, maxXminYZ)
        lines.addByTwoPoints(minXYmaxZ, maxXZminY)
        lines.addByTwoPoints(minXmaxYZ, maxXYZ)
        lines.addByTwoPoints(minXZmaxY, maxXYminZ) 
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

# Calculates a tight bounding box around the input body.  An optional
# tolerance argument is available.  This specificies the tolerance in 
# centimeters.  If not provided the best existing display mesh is used.
def calculateTightBoundingBox(body, tolerance = 0):
    try:
        # If the tolerance is zero, use the best display mesh available.
        if tolerance <= 0:
            # Get the best display mesh available.
            triMesh = body.meshManager.displayMeshes.bestMesh
        else:
            # Calculate a new mesh based on the input tolerance.
            meshMgr = adsk.fusion.MeshManager.cast(body.meshManager)
            meshCalc = meshMgr.createMeshCalculator()
            meshCalc.surfaceTolerance = tolerance
            triMesh = meshCalc.calculate()
    
        # Calculate the range of the mesh.
        smallPnt = adsk.core.Point3D.cast(triMesh.nodeCoordinates[0])
        largePnt = adsk.core.Point3D.cast(triMesh.nodeCoordinates[0])
        for vertex in triMesh.nodeCoordinates:
            if vertex.x < smallPnt.x:
                smallPnt.x = vertex.x
                
            if vertex.y < smallPnt.y:
                smallPnt.y = vertex.y
                
            if vertex.z < smallPnt.z:
                smallPnt.z = vertex.z
            
            if vertex.x > largePnt.x:
                largePnt.x = vertex.x
                
            if vertex.y > largePnt.y:
                largePnt.y = vertex.y
                
            if vertex.z > largePnt.z:
                largePnt.z = vertex.z 
                
        # Create and return a BoundingBox3D as the result.
        return adsk.core.BoundingBox3D.create(smallPnt, largePnt)
    except:
        # An error occurred so return None.
        return None

Inventor VBA

Public Sub TestTightBoundingBox()
    ' Have a body selected.
    Dim body As SurfaceBody
    Set body = ThisApplication.CommandManager.Pick(kPartBodyFilter, "Select the body.")
    
    ' Call the function to get the tight bounding box.
    Dim bndBox As Box
    Set bndBox = calculateTightBoundingBox(body)
        
    ' Draw the bounding box using a 3D sketch.
    Dim partDoc As PartDocument
    Set partDoc = ThisApplication.ActiveDocument
    Dim sk As Sketch3D
    Set sk = partDoc.ComponentDefinition.Sketches3D.Add()
    Dim lines As SketchLines3D
    Set lines = sk.SketchLines3D
    Dim tg As TransientGeometry
    Set tg = ThisApplication.TransientGeometry
    
    Dim minXYZ As Point
    Dim minXYmaxZ As Point
    Dim minXmaxYZ As Point
    Dim minXZmaxY As Point
    Set minXYZ = bndBox.MinPoint
    Set minXYmaxZ = tg.CreatePoint( _
        bndBox.MinPoint.X, bndBox.MinPoint.Y, bndBox.MaxPoint.Z)
    Set minXmaxYZ = tg.CreatePoint( _
        bndBox.MinPoint.X, bndBox.MaxPoint.Y, bndBox.MaxPoint.Z)
    Set minXZmaxY = tg.CreatePoint( _
        bndBox.MinPoint.X, bndBox.MaxPoint.Y, bndBox.MinPoint.Z)
    
    Dim maxXYZ As Point
    Dim maxXYminZ As Point
    Dim maxXZminY As Point
    Dim maxXminYZ As Point
    Set maxXYZ = bndBox.MaxPoint
    Set maxXYminZ = tg.CreatePoint( _
        bndBox.MaxPoint.X, bndBox.MaxPoint.Y, bndBox.MinPoint.Z)
    Set maxXZminY = tg.CreatePoint( _
        bndBox.MaxPoint.X, bndBox.MinPoint.Y, bndBox.MaxPoint.Z)
    Set maxXminYZ = tg.CreatePoint( _
        bndBox.MaxPoint.X, bndBox.MinPoint.Y, bndBox.MinPoint.Z)
    
    Call lines.AddByTwoPoints(minXYZ, minXYmaxZ)
    Call lines.AddByTwoPoints(minXYZ, minXZmaxY)
    Call lines.AddByTwoPoints(minXZmaxY, minXmaxYZ)
    Call lines.AddByTwoPoints(minXYmaxZ, minXmaxYZ)
    
    Call lines.AddByTwoPoints(maxXYZ, maxXYminZ)
    Call lines.AddByTwoPoints(maxXYZ, maxXZminY)
    Call lines.AddByTwoPoints(maxXYminZ, maxXminYZ)
    Call lines.AddByTwoPoints(maxXZminY, maxXminYZ)
    
    Call lines.AddByTwoPoints(minXYZ, maxXminYZ)
    Call lines.AddByTwoPoints(minXYmaxZ, maxXZminY)
    Call lines.AddByTwoPoints(minXmaxYZ, maxXYZ)
    Call lines.AddByTwoPoints(minXZmaxY, maxXYminZ)
End Sub

' Calculates a tight bounding box around the input body.  An optional
' tolerance argument is available.  This specificies the tolerance in
' centimeters.  If not provided the best existing display mesh is used.
Public Function calculateTightBoundingBox( _
    body As SurfaceBody, Optional Tolerance As Double = 0) As Box
    On Error GoTo ErrorFound
        
    Dim vertCount As Long
    Dim facetCount As Long
    Dim vertCoords() As Double
    Dim normVectors() As Double
    Dim vertInds() As Long
        
    ' If the tolerance is zero, use the best display mesh available.
    If Tolerance <= 0 Then
        ' Get the best display mesh available.
        Dim tolCount As Long
        Dim tols() As Double
        Call body.GetExistingFacetTolerances(tolCount, tols)
        Dim i As Integer
        Dim bestTol As Double
        bestTol = tols(0)
        For i = 1 To tolCount - 1
            If tols(i) < bestTol Then
                bestTol = tols(i)
            End If
        Next
        
        Call body.GetExistingFacets( _
            bestTol, vertCount, facetCount, vertCoords, normVectors, vertInds)
    Else
        ' Calculate a new mesh based on the input tolerance.
        Call body.CalculateFacets( _
            Tolerance, vertCount, facetCount, vertCoords, normVectors, vertInds)
    End If

    Dim tg As TransientGeometry
    Set tg = ThisApplication.TransientGeometry
    
    ' Calculate the range of the mesh.
    Dim smallPnt As Point
    Dim largePnt As Point
    Set smallPnt = tg.CreatePoint(vertCoords(0), vertCoords(1), vertCoords(2))
    Set largePnt = tg.CreatePoint(vertCoords(0), vertCoords(1), vertCoords(2))
    
    For i = 1 To vertCount - 1
        Dim vertX As Double
        Dim vertY As Double
        Dim vertZ As Double
        vertX = vertCoords(i * 3)
        vertY = vertCoords(i * 3 + 1)
        vertZ = vertCoords(i * 3 + 2)
        
        If vertX < smallPnt.X Then
            smallPnt.X = vertX
        End If
            
        If vertY < smallPnt.Y Then
            smallPnt.Y = vertY
        End If
            
        If vertZ < smallPnt.Z Then
            smallPnt.Z = vertZ
        End If
        
        If vertX > largePnt.X Then
            largePnt.X = vertX
        End If
            
        If vertY > largePnt.Y Then
            largePnt.Y = vertY
        End If
            
        If vertZ > largePnt.Z Then
            largePnt.Z = vertZ
        End If
    Next
    
    ' Create and return a Box as the result.
    Set calculateTightBoundingBox = tg.CreateBox()
    calculateTightBoundingBox.MinPoint = smallPnt
    calculateTightBoundingBox.MaxPoint = largePnt
    Exit Function
ErrorFound:
    Set calculateTightBoundingBox = Nothing
    Exit Function
End Function

Inventor Visual Basic (iLogic)

Public Sub TestTightBoundingBox()
    Dim invApp As Inventor.Application = GetObject(, "Inventor.Application")
    ' Have a body selected.
    Dim body As SurfaceBody
    body = invApp.CommandManager.Pick(SelectionFilterEnum.kPartBodyFilter, "Select the body.")
    ' Call the function to get the tight bounding box.
    Dim bndBox As Box = calculateTightBoundingBox(body)
    ' Draw the bounding box using a 3D sketch.
    Dim partDoc As PartDocument = invApp.ActiveDocument
    Dim sk As Sketch3D = partDoc.ComponentDefinition.Sketches3D.Add()
    Dim lines As SketchLines3D = sk.SketchLines3D
    Dim tg As TransientGeometry = invApp.TransientGeometry
    Dim minXYZ As Point = bndBox.MinPoint
    Dim minXYmaxZ As Point = tg.CreatePoint( _
        bndBox.MinPoint.X, bndBox.MinPoint.Y, bndBox.MaxPoint.Z)
    Dim minXmaxYZ As Point = tg.CreatePoint( _
        bndBox.MinPoint.X, bndBox.MaxPoint.Y, bndBox.MaxPoint.Z)
    Dim minXZmaxY As Point = tg.CreatePoint( _
        bndBox.MinPoint.X, bndBox.MaxPoint.Y, bndBox.MinPoint.Z)
    Dim maxXYZ As Point = bndBox.MaxPoint
    Dim maxXYminZ As Point = tg.CreatePoint( _
        bndBox.MaxPoint.X, bndBox.MaxPoint.Y, bndBox.MinPoint.Z)
    Dim maxXZminY As Point = tg.CreatePoint( _
        bndBox.MaxPoint.X, bndBox.MinPoint.Y, bndBox.MaxPoint.Z)
    Dim maxXminYZ As Point = tg.CreatePoint( _
        bndBox.MaxPoint.X, bndBox.MinPoint.Y, bndBox.MinPoint.Z)
    lines.AddByTwoPoints(minXYZ, minXYmaxZ)
    lines.AddByTwoPoints(minXYZ, minXZmaxY)
    lines.AddByTwoPoints(minXZmaxY, minXmaxYZ)
    lines.AddByTwoPoints(minXYmaxZ, minXmaxYZ)
    lines.AddByTwoPoints(maxXYZ, maxXYminZ)
    lines.AddByTwoPoints(maxXYZ, maxXZminY)
    lines.AddByTwoPoints(maxXYminZ, maxXminYZ)
    lines.AddByTwoPoints(maxXZminY, maxXminYZ)
    lines.AddByTwoPoints(minXYZ, maxXminYZ)
    lines.AddByTwoPoints(minXYmaxZ, maxXZminY)
    lines.AddByTwoPoints(minXmaxYZ, maxXYZ)
    lines.AddByTwoPoints(minXZmaxY, maxXYminZ)
End Sub

' Calculates a tight bounding box around the input body.  An optional
' tolerance argument is available.  This specificies the tolerance in
' centimeters.  If not provided the best existing display mesh is used.
Public Function calculateTightBoundingBox( _
    body As SurfaceBody, Optional Tolerance As Double = 0) As Box
    Try
        Dim vertCount As Integer
        Dim facetCount As Integer
        Dim vertCoords() As Double = {}
        Dim normVectors() As Double = {}
        Dim vertInds() As Integer = {}
        ' If the tolerance is zero, use the best display mesh available.
        If Tolerance <= 0 Then
            ' Get the best display mesh available.
            Dim tolCount As Long
            Dim tols() As Double = {}
            Call body.GetExistingFacetTolerances(tolCount, tols)
            Dim bestTol As Double
            bestTol = tols(0)
            For i As Integer = 1 To tolCount - 1
                If tols(i) < bestTol Then
                    bestTol = tols(i)
                End If
            Next
            body.GetExistingFacets( _
                bestTol, vertCount, facetCount, vertCoords, normVectors, vertInds)
        Else
            ' Calculate a new mesh based on the input tolerance.
            body.CalculateFacets( _
                Tolerance, vertCount, facetCount, vertCoords, normVectors, vertInds)
        End If
        Dim tg As TransientGeometry = body.Application.TransientGeometry
        ' Calculate the range of the mesh.
        Dim smallPnt As Point = tg.CreatePoint(vertCoords(0), vertCoords(1), vertCoords(2))
        Dim largePnt As Point = tg.CreatePoint(vertCoords(0), vertCoords(1), vertCoords(2))
        For i As Integer = 1 To vertCount - 1
            Dim vertX As Double = vertCoords(i * 3)
            Dim vertY As Double = vertCoords(i * 3 + 1)
            Dim vertZ As Double = vertCoords(i * 3 + 2)
            If vertX < smallPnt.X Then
                smallPnt.X = vertX
            End If
            If vertY < smallPnt.Y Then
                smallPnt.Y = vertY
            End If
            If vertZ < smallPnt.Z Then
                smallPnt.Z = vertZ
            End If
            If vertX > largePnt.X Then
                largePnt.X = vertX
            End If
            If vertY > largePnt.Y Then
                largePnt.Y = vertY
            End If
            If vertZ > largePnt.Z Then
                largePnt.Z = vertZ
            End If
        Next
        ' Create and return a Box as the result.
        Dim newBox As Box = tg.CreateBox()
        newBox.MinPoint = smallPnt
        newBox.MaxPoint = largePnt
        Return newBox
    Catch ex As Exception
        Return Nothing
    End Try
End Function

-Brian


Comments

4 responses to “Getting the Overall Size of Parts”

  1. Stig Kristiansen Avatar
    Stig Kristiansen

    Had to change counter i from integer to long because of inherent limitations.
    Otherwise worked well. I only tested vba.

  2. Can you do something similar for assemblies?
    If you have a part with splines and use that in an assembly, then the RangeBox for the assembly will (potentially) be wrong.

  3. Yes, it can be done for an assembly. It means doing this process on each occurrence in the assembly and combining the bounding boxes to get the overall bounding box.

  4. Thanks…
    I kinda figured that was the way to do, but wanted to see if there was another way.

Leave a Reply to LGCancel reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading