So after a few hours of headache on axis systems and rotations (the axis I mention above are wrong), here is what I got:
The main sub is ReframeOnObjects3D() it calls the Rotate() function to switch between axis systems.
You can play with the ratio and 2 angles parameters to get edges, corners or face views. Note that the example is limited to 0 <= teta, phi <= Pi/2 and so only covers Front, Top and Right faces, but you can adapt it for the 3 other faces if needed.
The sub Reframes() demonstrate some standard views, I used a 0.99 ratio for debuging to make sure the box was not slighly cropped, but a ratio of 2/3 usually gives a good view.
Make sure objects are selected in 3D before you run the code.
Public Sub ReframeOnObjects3D(FitRatio As Double, teta As Double, phi As Double)
'Remarks about the setup:
'1) Navisworks axis system (X,Y,Z) is right handed with the Front view in the plane (YZ), Z Up and X going toward the screen
'2) Corners of the superior face of the bounding box are named A, B, C and D
' - D is the origin (0,0,0)
' - DA aligned with X, DC aligned with Y
' Corners of the inferior face are named Ai, Bi, Ci and Di, after the corresponding top corner
'3) The axis system of the screen (x,y,z) has x horizontally, z vertically and y toward the object
'4) Rteta is the rotation around Z and Rphi the rotation around X so that Rteta * Rphi * (X,Y,Z) = (x,y,z)
'This gives
'1) Projection p of a point P on the screen:
' pi = Pi*Fd/Py for all P(x,y,z); iE(x,z); Fd = Focal distance
'2) Projection lengths centered:
' |ax| = |cx| horizontally with a and c extremum points
' |bix| = |dx| vertically with bi and d extremum points
'3) Projection inferior to given ratio of screen length:
' cx-ax <= RHe; He = Horizontal extent at focal distance
' dz-bix <= RVe; Ve = Vertical extent at focal distance
Dim debug As New System.Text.StringBuilder
Dim Items As ModelItemCollection = Autodesk.Navisworks.Api.Application.ActiveDocument.CurrentSelection.SelectedItems
'Get the items bounding box dimensions
Dim boundingBox As BoundingBox3D = Items.BoundingBox(False)
Dim G As Point3D = boundingBox.Center
Dim W As Double = boundingBox.Size.X
Dim L As Double = boundingBox.Size.Y
Dim H As Double = boundingBox.Size.Z
'Set the rotation angles around z and x
'Warning: vector x for second rotation must be expressed
'in the axis system already rotated around z (inverse rotation)
Dim Rteta As Double = -Math.PI / 2 - teta
Dim URteta As New UnitVector3D(0, 0, 1) 'around z
Dim Rphi As Double = phi
Dim URphi As New UnitVector3D(Rotate(New UnitVector3D(1, 0, 0), -Rteta, URteta)) 'around x (in Rz axis system)
debug.Append("URphi:")
debug.AppendLine(URphi.ToString)
'Create a modifiable copy of the current viewpoint
Dim nvp As Viewpoint = Autodesk.Navisworks.Api.Application.ActiveDocument.CurrentViewpoint.Value.CreateCopy()
'Get the minimum distance By to respect the ratio horizontally
'the dimensionning vectors will be AC with small FOV*FitRatio, AB or BC with high FOV*FitRatio
Dim Th As Double = FitRatio * nvp.HorizontalExtentAtFocalDistance / (2 * nvp.FocalDistance)
Dim rAC As Vector3D = Rotate(Rotate(New Vector3D(-W, L, 0), Rphi, URphi), Rteta, URteta)
Dim rAB As Vector3D = Rotate(Rotate(New Vector3D(0, L, 0), Rphi, URphi), Rteta, URteta)
Dim rBC As Vector3D = Rotate(Rotate(New Vector3D(-W, 0, 0), Rphi, URphi), Rteta, URteta)
debug.AppendLine()
debug.AppendLine("Vectors:")
debug.AppendLine("AC: " & rAC.ToString)
debug.AppendLine("AB: " & rAB.ToString)
debug.AppendLine("BC: " & rBC.ToString)
Dim MinByAC As Double = (rAC.X / Th - rAC.Y) / 2 + rAB.Y
Dim MinByAB As Double = (rAB.X / Th + rAB.Y) / 2
Dim MinByBC As Double = (rBC.X / Th - rBC.Y) / 2
Dim MinByH As Double = Math.Max(Math.Max(MinByAC, MinByAB), MinByBC)
'Get the minimum distance By to respect the ratio vertically
'the dimensionning vectors will be DBi with small FOV*FitRatio, BBi with high FOV*FitRatio
Dim Tv As Double = FitRatio * nvp.VerticalExtentAtFocalDistance / (2 * nvp.FocalDistance)
Dim rBiD As Vector3D = Rotate(Rotate(New Vector3D(-W, -L, H), Rphi, URphi), Rteta, URteta)
Dim rBiB As Vector3D = Rotate(Rotate(New Vector3D(0, 0, H), Rphi, URphi), Rteta, URteta)
debug.AppendLine("BiD: " & rBiD.ToString)
debug.AppendLine("BiB: " & rBiB.ToString)
Dim MinByBiD As Double = (rBiD.Z / Tv - rBiD.Y) / 2 + rBiB.Y
Dim MinByBiB As Double = (rBiB.Z / Tv + rBiB.Y) / 2
Dim MinByV As Double = Math.Max(MinByBiD, MinByBiB)
debug.AppendLine()
debug.AppendLine("Dimensionning:")
'Get the overall minimum distance
Dim Bx, By, Bz As Double
If MinByH >= MinByV Then
'the horizontal is dimensionning
By = MinByH
'Get Bx depending on the longest H vector
Select Case MinByH
Case MinByAC
Bx = (Th * rAC.Y - rAC.X) / 2 + rAB.X
debug.Append("H-AC")
Case MinByAB
Bx = (Th * rAB.Y + rAB.X) / 2
debug.Append("H-AB")
Case MinByBC
Bx = (Th * rBC.Y - rBC.X) / 2
debug.Append("H-BC")
End Select
'since the horizontal is dimensionning, the vertical ratio
'is inferior to the target ratio so we calculate the actual
'ratio on the vertical and the resulting Bz
Dim VFitRatio As Double = 0.0
If MinByBiD >= MinByBiB Then
'VFitRatio = (rBiD.Z * nvp.FocalDistance) / ((By - rBiB.Y + rBiD.Y / 2) * nvp.VerticalExtentAtFocalDistance)
'Tv = VFitRatio * nvp.VerticalExtentAtFocalDistance / (2 * nvp.FocalDistance)
Tv = rBiD.Z / ((By - rBiB.Y + rBiD.Y / 2) * 2)
Bz = (Tv * rBiD.Y - rBiD.Z) / 2 + rBiB.Z
debug.AppendLine("; V-BiD")
Else
Tv = rBiB.Z / ((By - rBiB.Y / 2) * 2)
Bz = (Tv * rBiB.Y + rBiB.Z) / 2
debug.AppendLine("; V-BiB")
End If
Else
'the vertical is dimensionning
By = MinByV
'Get Bz depending on the longest V vector
If MinByBiD >= MinByBiB Then
Bz = (Tv * rBiD.Y - rBiD.Z) / 2 + rBiB.Z
debug.Append("V-BiD")
Else
Bz = (Tv * rBiB.Y + rBiB.Z) / 2
debug.Append("V-BiB")
End If
'since the vertical is dimensionning, the horizontal ratio
'is inferior to the target ratio so we calculate the actual
'ratio on the horizontal and the resulting Bx
Dim HFitRatio As Double = 0.0
Select Case MinByH
Case MinByAC
'HFitRatio = (rAC.X * nvp.FocalDistance) / ((By - rAB.Y + rAC.Y / 2) * nvp.HorizontalExtentAtFocalDistance)
'Th = HFitRatio * nvp.HorizontalExtentAtFocalDistance / (2 * nvp.FocalDistance)
Th = rAC.X / ((By - rAB.Y + rAC.Y / 2) * 2)
Bx = (Th * rAC.Y - rAC.X) / 2 + rAB.X
debug.AppendLine("; H-AC")
Case MinByAB
Th = rAB.X / ((By - rAB.Y / 2) * 2)
Bx = (Th * rAB.Y + rAB.X) / 2
debug.AppendLine("; H-AB")
Case MinByBC
Th = rBC.X / ((By + rBC.Y / 2) * 2)
Bx = (Th * rBC.Y - rBC.X) / 2
debug.AppendLine("; H-BC")
End Select
End If
'Apply inverse rotation to BO(x,y,z) to get BO(X,Y,Z) and
'where O is the camera position in 3D
Dim BO As Vector3D = Rotate(Rotate(New Vector3D(-Bx, -By, -Bz), -Rteta, URteta), -Rphi, URphi)
Dim GB As New Vector3D(W / 2, L / 2, H / 2)
nvp.Position = G.Add(GB).Add(BO)
'By definition the view alignment is perpendicular to the screen y=(0,1,0)
'we applied the inverse rotation to get y=(X,Y,Z) coordinates
Dim ViewDirection As Vector3D = Rotate(Rotate(New UnitVector3D(0, 1, 0), -Rteta, URteta), -Rphi, URphi)
nvp.AlignDirection(ViewDirection)
'AlignUp aligned to Z for (any teta ; phi <= PI/4)
nvp.AlignUp(New UnitVector3D(0, 0, 1))
debug.AppendLine()
debug.AppendLine("View Alignment:")
debug.AppendLine(ViewDirection.ToString)
'Replace current viewpoint
Autodesk.Navisworks.Api.Application.ActiveDocument.CurrentViewpoint.CopyFrom(nvp)
MessageBox.Show(debug.ToString)
End Sub
Private Function Rotate(v As Vector3D, angle As Double, u As UnitVector3D) As Vector3D
Dim C As Double = Math.Cos(angle)
Dim S As Double = Math.Sin(angle)
Return New Vector3D((C + u.X ^ 2 * (1 - C)) * v.X + (u.X * u.Y * (1 - C) - u.Z * S) * v.Y + (u.X * u.Z * (1 - C) + u.Y * S) * v.Z,
(u.Y * u.X * (1 - C) + u.Z * S) * v.X + (C + u.Y ^ 2 * (1 - C)) * v.Y + (u.Y * u.Z * (1 - C) - u.X * S) * v.Z,
(u.Z * u.X * (1 - C) - u.Y * S) * v.X + (u.Z * u.Y * (1 - C) + u.X * S) * v.Y + (C + u.Z ^ 2 * (1 - C)) * v.Z)
End Function
Private Sub Reframes()
ReframeOnObjects3D(0.99, 0, 0) 'Front
ReframeOnObjects3D(0.99, Math.PI / 2, 0) 'Right
ReframeOnObjects3D(0.99, 0, Math.PI / 2) 'Top
ReframeOnObjects3D(0.99, Math.PI / 4, 0) 'FrontRight
ReframeOnObjects3D(0.99, 0, Math.PI / 4) 'FrontTop
ReframeOnObjects3D(0.99, Math.PI / 2, Math.PI / 4) 'RightTop
ReframeOnObjects3D(0.99, Math.PI / 4, Math.PI / 4) 'TopFrontRight / Awesome
ReframeOnObjects3D(0.99, Math.PI / (1 + Math.Sqrt(5)), Math.PI / 8.12) 'Useless / Architect
End Sub