Hi,
When clicking on the faces, edges or corners of the view cube, navisworks window reframes to fit selected object on the screen.
I am trying to trigger that behavior using the API when the selection changes.
I couldn't find any API reference that seemed able to call the view cube functions directly, besides I'd like to let the user specify the reframing ratio (by default the view cube fit the selected objects to the entire window which often makes the context hard to see, 1/2 or 1/3 window would be better). I did find the TopLeftFront function of the View object but it seem to only reset the scene.
So I started writting my own function, using the viewpoint object and the modelitemcollection.boundingbox function to get the selected objects dimension.
After some intensive trigonometric geomery, I am now able to replicate the "edge click" and "face click" functions of the cube, with nice parameter for fit ratio and view angle.
The "corner click" functions however has prooven to be a challenge of another level entirely.
I welcome all suggestions on how to create a "TopLeftFront" view of selected objects that would fit the screen on a given ratio (vertical or horizontal, whichever is the largest).
attached is an example of the view cube fiting, the ratio being always 1.
cheers,
Nic
Solved! Go to Solution.
Solved by ngombault. Go to Solution.
Hi,
probably there is other solutions, this is what I got. Hope it helps.
Hi Xiaodong,
Thank you for the suggestion.
When I tried it though, it the results were a bit approximative, both in terms of centering the object on the screen and for the size ratio, not reacting well to variation of the FOV.
I know I won't cover extreme cases with FOV close to 180deg for instance, but there are some consideration I think I cannot avoid even in the range 20-160, for instance the viewpoint target is only the bounding box center when the FOV is null (orthographic view); in perspective mode it moves toward the edges depending on the FOV.
I have major OCD, and so I am in search of the exact solution.
You used Boundingbox.Max and Min, can you please tell me which points these refer too exactly?
Thanks,
Nic
Hi,
I thought my codes in message 2 has solved your original question. I was struggling to understand your further comment in the message 3. So I asked our expert to take a look. He is also not entirely sure what you want.
He said it should be pretty easy to replicate ViewCube style code. You can already determine the orientation of the model using the Front and Up vectors. From those two vectors, you can compute all size directions (Front, Back, Left, Right, Top, Bottom). Then, when the user changes the selection, you can create a camera that is centered on the selection, with the appropriate view direction, then zoom until the bounding box is in view. This is exactly what my code does.
So, could you elaborate: if my codes solves your orignal question and what your further questions exactly is?
As to the viewcube-like-reframing, we have had the wish to expose ViewCube in API, but this is just a wish at this moment. I cannot guarantee when it would be implemented.
Hi,
Sorry if it wasn't clear enough.
basically I am trying to fit the bounding box of the selection on the screen so that:
- the dimensions of its projection on the screen divided by the screen dimensions are inferior or equal to a given ratio (Hb/Hs <= R AND Vb/Vs <=R), and
- the bounding box projection is centered both horizontally and vertically on the screen.
when looking at the bounding box from the FrontTopRight corner, the longest dimension of the bounding box projection horizontally is generally given by the projection of the vector AC, but it can be AB or BC with very high FOV. Vertically it is generally the vector BiD but it can be BiB with high FOV (try it out in Navis with the vew cube, a dummy rectangular box (see attached nwd) and a high FOV like 120-140).
These conditions will give you a minimum distance to be away from the object AND the position both vertically and horizontally, which does not conincide with the center of the box (again try the view cube in Navis and see where the pivot point of the rotation is once the object is reframed, it is not the center of the box).
Since we know the coordinates of all points in the 3D space (X,Y,Z) we can calculate the coordinates of these points in the screen axis system, by using 2 rotation matrices, one around Z (angle teta) and one around X (angle phi). It gets a little tricky after the first rotation, since the rotation axis of the second rotation is X (1,0,0) BUT expressed in the rotated axis system (cos Teta, Sin Teta, 0), and that gives a nasty second rotation matrix. Wikipedia being allknowing, it also knows the coefficient of the second matrix.
I am sure having a lot of fun doing all this, but a more straigh forward solution would be appreciated. So I definitly vote in favor of the view cube API.
Regards,
Nic
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