16 Oct 2018
Load XML of Navisworks Saved Viewpoints to Forge Viewer Cameras
Forge Model Derivative API can extract Navisworks saved viewpoints and build camera objects (NOP_VIEWER.model.getData().cameras) in Forge Viewer. The other blog tells more. However, in some cases, the workflows need to load the XML of Navisworks saved viewpoints and convert the data to Forge Viewer objects, because XML contains more info of viewpoints, and also contains clip plans/box data. The below is one example:
<exchange xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://download.autodesk.com/us/navisworks/schemas/nw-exchange-12.0.xsd" units="m" filename="ClashTest.nwd" filepath="C:\Program Files\Autodesk\Navisworks Manage 2019\api\COM\examples">
<viewpoints>
<view name="View1" guid="0ab1e9b4-cb96-41ce-aa71-7c4b83a80978">
<viewpoint tool="navigator_zoom" render="shaded" lighting="user" focal="1550.2882399154" linear="49.2732722032" angular="0.7853981634">
<camera projection="persp" near="1.0000000000" far="10.0000000000" aspect="0.8460000000" height="0.7853981634">
<position>
<pos3f x="854.0685942394" y="243.8875607304" z="967.6407589236"/>
</position>
<rotation>
<quaternion a="0.4594456548" b="-0.1425955159" c="-0.2598635311" d="0.8372855871"/>
</rotation>
</camera>
<viewer radius="0.2500000000" height="1.7000000000" actual_height="1.7000000000" eye_height="0.1500000000" avatar="dummy" camera_mode="first" first_to_third_angle="0.2617993878" first_to_third_distance="4.0000000000" first_to_third_param="1.0000000000" first_to_third_correction="1" collision_detection="0" auto_crouch="0" gravity="0" gravity_value="9.8000000000" terminal_velocity="50.0000000000"/>
<up>
<vec3f x="0.0000000000" y="0.0000000000" z="1.0000000000"/>
</up>
</viewpoint>
<clipplaneset linked="0" current="0" mode="planes" enabled="0">
<range>
<box3f>
<min>
<pos3f x="1.0000000000" y="1.0000000000" z="1.0000000000"/>
</min>
<max>
<pos3f x="0.0000000000" y="0.0000000000" z="0.0000000000"/>
</max>
</box3f>
</range>
<clipplanes>
<clipplane state="default" distance="0.0000000000" alignment="top">
<plane distance="0.0000000000">
<vec3f x="0.0000000000" y="1.0000000000" z="0.0000000000"/>
</plane>
</clipplane>
<clipplane state="default" distance="0.0000000000" alignment="bottom">
<plane distance="0.0000000000">
<vec3f x="0.0000000000" y="1.0000000000" z="0.0000000000"/>
</plane>
</clipplane>
<clipplane state="default" distance="0.0000000000" alignment="front">
<plane distance="0.0000000000">
<vec3f x="0.0000000000" y="1.0000000000" z="0.0000000000"/>
</plane>
</clipplane>
<clipplane state="default" distance="0.0000000000" alignment="back">
<plane distance="0.0000000000">
<vec3f x="0.0000000000" y="1.0000000000" z="0.0000000000"/>
</plane>
</clipplane>
<clipplane state="default" distance="0.0000000000" alignment="left">
<plane distance="0.0000000000">
<vec3f x="0.0000000000" y="1.0000000000" z="0.0000000000"/>
</plane>
</clipplane>
<clipplane state="default" distance="0.0000000000" alignment="right">
<plane distance="0.0000000000">
<vec3f x="0.0000000000" y="1.0000000000" z="0.0000000000"/>
</plane>
</clipplane>
</clipplanes>
<box>
<box3f>
<min>
<pos3f x="1.0000000000" y="1.0000000000" z="1.0000000000"/>
</min>
<max>
<pos3f x="0.0000000000" y="0.0000000000" z="0.0000000000"/>
</max>
</box3f>
</box>
<box-rotation>
<rotation>
<quaternion a="0.0000000000" b="0.0000000000" c="0.0000000000" d="1.0000000000"/>
</rotation>
</box-rotation>
</clipplaneset>
</view>
In this blog, we will talk about how to convert the data to cameras of Forge Viewer. Forge Viewer's camera system is configured by the same graphics logic as Navisworks. The only tricky is Navisworks XML describes camera by position and quaternion, while Forge Viewer cameras mainly takes position and targets (and up vector). Fortunately, there are a lot of materials on internet. I love one which also demos the equations on extracting view direction, up vector from quaternion. With view direction, we can easily calculate target.
However, the position and target are different to those by NOP_VIEWER.model.getData().cameras. After debugging Viewer3D.js, I found the source position and target from Navisworks will need to be applied with global offset matrix.
The code below assumes the XML file is available on server side:
var NavisworksVPList = [];
function readNavisVPXML()
{
//assume the XML file is available on the server
var offsetMatrix = viewer.model.myData.placementWithOffset;
$.get("ClashTest", {}, function (xml){
NavisworksVPList = [];
//get XML content
var NavisworksVPXML =xml;
var rootNode = NavisworksVPXML.documentElement;
var vpNodes = NavisworksVPXML.documentElement.children[0];
for(var index=0;index<vpNodes.children.length;index++){
//each viewpoint node
var eachVPNode = vpNodes.children[index];
//each viewpoint
var vp= eachVPNode.getElementsByTagName('viewpoint')[0]
//attributes
var focal = Number(vp.attributes.focal.nodeValue);
//FOV
var fov = Number(vp.attributes.angular.nodeValue);
//camera node
var cameraNode = eachVPNode.getElementsByTagName('camera')[0];
//aspect
var aspect = Number(cameraNode.attributes.aspect.nodeValue);
//isperspective
var projection = cameraNode.attributes.projection.nodeValue;
var isPerspective = projection=='persp'?true:false;
//position
var position = cameraNode.getElementsByTagName('pos3f')[0];
var threePos = new THREE.Vector3(Number(position.attributes.x.nodeValue),
Number(position.attributes.y.nodeValue),
Number(position.attributes.z.nodeValue));
//quaternion
var quaternion = cameraNode.getElementsByTagName('quaternion')[0];
var threeQN = new THREE.Quaternion(Number(quaternion.attributes.a.nodeValue),
Number(quaternion.attributes.b.nodeValue),
Number(quaternion.attributes.c.nodeValue),
Number(quaternion.attributes.d.nodeValue));
//calculate view direction
var v1 = -2 * (threeQN.x * threeQN.z + threeQN.w * threeQN.y);
var v2 = -2 * (threeQN.y * threeQN.z - threeQN.w * threeQN.x);
var v3 = -1+ 2 * (threeQN.x * threeQN.x + threeQN.y * threeQN.y);
//calculate target
var threeTarget = new THREE.Vector3(
threePos.x + focal*v1,
threePos.y + focal*v2,
threePos.z + focal*v3
);
//apply with global offset
var offsetPos = threePos.applyMatrix4(offsetMatrix);
var offsetTarget = threeTarget.applyMatrix4(offsetMatrix);
fov = fov * 180 / Math.PI;
//calculate up vector
v1 = 2 * (threeQN.x * threeQN.y - threeQN.w * threeQN.z);
v2 = 1 - 2 * (threeQN.x*threeQN.x + threeQN.z*threeQN.z);
v3 = 2 * (threeQN.y*threeQN.z + threeQN.w*threeQN.x);
var up = new THREE.Vector3(v1,v2,v3);
//build one camera
NavisworksVPList.push(
{
aspect:aspect,
isPerspective:isPerspective,
fov:fov,
position:offsetPos,
target:offsetTarget,
up:up,
orthoScale: 1
}
);
}
});
}
After running the function readNavisVPXML, you can switch the viewpoint by
NOP_VIEWER.impl.setViewFromCamera(NavisworksVPList[0]);