What is the correct process for getting a transform between Forge coordinates and Revit's shared coordinates? I know there is globalOffset, but does it reference the Revit project internal coordinate system or shared coordinates?
Update Jun 11th, 2021
Now my MultipleModelUtil.js supports the alignments I shared below. Also, we can easily tell Forge Viewer to use By shared coordinates to aggregate models. Here is the code snippet, and you can check out here to know supported alignments
const util = new MultipleModelUtil( viewer );
util.options = {
alignment: MultipleModelAlignmentType.ShareCoordinates
};
const models = [
{ name: '1.rvt', urn: 'urn:dXJuOmFkc2sud2lwcHJvZDpmcy5maWxlOnZmLlNpaHgxOTVuUVJDMHIyWXZUSVRuZFE_dmVyc2lvbj0x' },
{ name: '2.rvt', urn: 'urn:dXJuOmFkc2sud2lwcHJvZDpmcy5maWxlOnZmLldVRHJ4ajZ6UTBPLTRrbWZrZ3ZoLUE_dmVyc2lvbj0x' },
{ name: '3.rvt', urn: 'urn:dXJuOmFkc2sud2lwcHJvZDpmcy5maWxlOnZmLjRyZW5HRTNUU25xNHhYaW5xdWtyaWc_dmVyc2lvbj0x' }
];
util.processModels( models );
==================
First, Forge Viewer supports 3 kinds of Revit link methods as the below, and you can take a look at the 3rd one (By shared coordinates).
Origin to origin: Apply the globalOffset of the 1st model to others. Check MultipleModelUtil/MultipleModelUtil.js for the demo
Center to center: the default way of the viewer.
By shared coordinates: set up applyRefpoint: true and make the globalOffset to the refPoint. This method is the one you are looking for.
The refPoint is the Revit survey point location inside Revit internal coordinate system. It's accessible with the AecModelData. Meanwhile, you can take advantage of the AggregatedView to use this aligning option. Here is an example of telling how to use AggregatedView:
https://gist.github.com/yiskang/c404af571ba4d631b5929c777503891e
If you want to use this logic with the Viewer class directly, here is a code snippet for you:
let globalOffset = null;
const aecModelData = bubbleNode.getAecModelData();
const tf = aecModelData && aecModelData.refPointTransformation; // Matrix4x3 as array[12]
const refPoint = tf ? { x: tf[9], y: tf[10], z: 0.0 } : { x: 0, y: 0, z: 0 };
// Check if the current globalOffset is sufficiently close to the refPoint to avoid inaccuracies.
const MaxDistSqr = 4.0e6;
const distSqr = globalOffset && THREE.Vector3.prototype.distanceToSquared.call(refPoint, globalOffset);
if (!globalOffset || distSqr > MaxDistSqr) {
globalOffset = new THREE.Vector3().copy(refPoint);
}
viewer.loadDocumentNode(doc, bubbleNode, { applyRefpoint: true, globalOffset: globalOffset, keepCurrentModels: true });
The bubbleNode can be either of the following:
bubbleNode = doc.getRoot().getDefaultGeometry()
//Or
const viewables = viewerDocument.getRoot().search({'type':'geometry'});
bubbleNode = viewables[0];
To get AecModelData, please refer to my gist: https://gist.github.com/yiskang/c404af571ba4d631b5929c777503891e#file-index-html-L67
// Call this line before using AecModelData
await doc.downloadAecModelData();
// doc.downloadAecModelData(() => resolve(doc));
See here for details of the AecModelData: https://forge.autodesk.com/blog/consume-aec-data-which-are-model-derivative-api
I've also found success feeding the refPointTransformation into a matrix4.
This way, the orientation of the model is also taken into account. (This is based off Eason's Answer).
const bubbleNode = doc.getRoot().getDefaultGeometry();
await doc.downloadAecModelData();
const aecModelData = bubbleNode.getAecModelData();
const tf = aecModelData && aecModelData.refPointTransformation;
const matrix4 = new THREE.Matrix4()
.makeBasis(
new THREE.Vector3(tf[0], tf[1], tf[2]),
new THREE.Vector3(tf[3], tf[4], tf[5]),
new THREE.Vector3(tf[6], tf[7], tf[8])
)
.setPosition(new THREE.Vector3(tf[9], tf[10], tf[11]))
viewer.loadDocumentNode(doc, viewables, {
placementTransform: matrix4,
keepCurrentModels: true,
globalOffset: {
"x": 0,
"y": 0,
"z": 0
},
applyRefpoint: true,
applyScaling: 'ft',
})
Related
I have a simple forge app to view 3d models. At first, I initiated the forge viewer with GuiViewer3D class but then wanted to implement AggregatedView instead.
My problem is that AggregatedView shows the model correctly but it shows it as being "stitched" together. Whereas, if I use GuiViewer3D or Viewer3D, the model looks smooth and clean.
I have looked into the globalOffset but in any solution, the globalOffset is the same, and hence should not be the cause here.
This is how the model should look like (GuiViewer3D)
But this is how it looks like instea using AggregatedView
I am not quite sure what the issue here. I am using an .fbx file as the source of 3d model.
This the code of AggregatedView()
var view = new Autodesk.Viewing.AggregatedView();
function launchViewer(urn) {
var options = {
env: 'AutodeskProduction',
getAccessToken: getForgeToken
};
Autodesk.Viewing.Initializer(options, () => {
var htmlDiv = document.getElementById('forgeViewer');
view.init(htmlDiv, options);
var documentId = 'urn:' + urn;
view.unloadAll();
Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
});
}
function onDocumentLoadSuccess(doc) {
var nodes = doc.getRoot().search({ role:'3d', type: 'geometry' });
console.log(nodes);
view.setNodes(nodes[0]);
}
function onDocumentLoadFailure(viewErrorCode, viewErrorMsg) {
console.error('onDocumentLoadFailure() - errorCode:' + viewErrorCode + '\n- errorMessage:' + viewErrorMsg);
}
function getForgeToken(callback) {
fetch('/api/forge/oauth/token').then(res => {
res.json().then(data => {
callback(data.access_token, data.expires_in);
});
});
}
Many thanks in advance!
UPDATE:
After setting the global offset to (0,0,0), the geometry still looks "Stitched" together rather than smooth.
The pivot point is not the global offset. Please use viewer.getAllModels().map( model => model.getGlobalOffset() ) to check that instead. For AggreagateView, you can get the viewer instance via view.viewer;
In addition, AggreagateView loads models in the shared coordinate (applyRefPoint: true), so your model might be far away from the viewer's origin. Could you try this to see if it helps?
const options3d = {
getCustomLoadOptions: (bubble, data) => {
console.log(bubble, data);
return {
applyRefPoint: false //!<<< Disable Share Coordinate
// globalOffset: new THREE.Vector3( 543.0811920166016, 9.154923574611564, -1442.591747316564 ) //!<<< uncomment this to specify your globalOffset, but you need to include `applyRefPoint: false` above together.
// createWireframe: false
};
}
};
view.init(viewerDiv, options3d);
I have two separate extensions visualizing different "datapoints" from different domains (measurements like temperature, humidity, ... and image locations). These extensions should remain separated (and should not know each other).
There is no problem when using the extensions independent of each other. But problems occur, if both extensions are used at the same time (for the same model) in the Forge Viewer.
Both extensions try to get an existing instance of the DataVis-extension and load the extension if it is not available (call to viewer.loadExtension in each custom extension no difference).
The extensions create ViewableData and add viewables to it (sharing a single instance of ViewableData makes no difference).
After adding viewables await viewableData.finish() is called and the addViewables-method of the DataVis-extension is called.
One of the main problems is, that method changeOcclusion changes the occlusion only of the viewables which were added last. The other viewables remain visible at any time.
Probably because the pointMaterial in the DataVis-extension is overwritten any time the addViewables extension is called.
Is there a way to instantiate the extension multiple times, so that it is guaranteed that there are no side-effects when using it from within different custom extensions? Or maybe other mechanisms?
After consulting our engineering team, we submitted an issue report, LMV-6574, for tracking this issue. Please take note of the LMV prefixed id for tracking. We're welcome to ask for updates in the future by sending the issue id to forge (DOT) help (AT) autodesk (DOT) com.
However, we don't want to stop your development, so here is a workaround.
As I mentioned in the above comments area, SpriteViewable's constructor accepts an argument, ViewableStyles, that is used to set up the sprite icon. So, you don't need to call DataVisualization.addViewables(data) twice. Before finishing the ViewableData, you can add viewables with different ViewableStyles without a doubt.
Back to your use case, you want to reuse the DataVisualization extension for different data sources. To do so, I would advise you to store your device (sensor) data separately (e.g. device container). When you need to add/remove devices, just modify the device container, clear viewables, and then add new viewalbe data among your device container.
Here are some code snippets demonstrating this idea:
Example code for initializing:
let sensorStyleDefinitions = {
co2: {
url: "http://localhost:3000/assets-1/images/co2.svg",
color: 0xffffff,
},
temperature: {
url: "http://localhost:3000/assets-1/images/thermometer.svg",
color: 0xffffff,
},
default: {
url: "http://localhost:3000/assets-1/images/circle.svg",
color: 0xffffff,
},
};
// Create model-to-style map from style definitions.
let styleMap = {};
Object.entries(sensorStyleDefinitions).forEach(([type, styleDef]) => {
styleMap[type] = new Autodesk.DataVisualization.Core.ViewableStyle(
Autodesk.DataVisualization.Core.ViewableType.SPRITE,
new THREE.Color(styleDef.color),
styleDef.url
);
});
let devices = [
{
id: "Hall I",
position: {
x: -14.297511041164398,
y: -77.6432056427002,
z: 11.31889820098877,
},
type: "temperature",
sensorTypes: ["temperature"],
},
{
id: "Hall IV",
position: {
x: 60.53697395324707,
y: -74.6432056427002,
z: 11.31889820098877,
},
type: "co2",
sensorTypes: ["co2"],
},
];
const viewableData = new Autodesk.DataVisualization.Core.ViewableData();
viewableData.spriteSize = 16;
// Add viewables
devices.forEach((device, index) => {
const style = styleMap[device.type] || styleMap["default"];
const viewable = new Autodesk.DataVisualization.Core.SpriteViewable(
device.position,
style,
index + 1
);
viewableData.addViewable(viewable);
});
await viewableData.finish();
dataVizExt.addViewables(viewableData);
Example code for adding a device(sensor)
devices.push({
id: "Hall XII",
position: {
x: -15,
y: -70,
z: 50,
},
type: "temperature",
sensorTypes: ["temperature"],
});
// Remove existing sprites
dataVizExt.removeAllViewables();
const viewableData = new Autodesk.DataVisualization.Core.ViewableData();
viewableData.spriteSize = 16;
// re-add viewables
devices.forEach((device, index) => {
const style = styleMap[device.type] || styleMap["default"];
const viewable = new Autodesk.DataVisualization.Core.SpriteViewable(
device.position,
style,
index + 1
);
viewableData.addViewable(viewable);
});
await viewableData.finish();
dataVizExt.addViewables(viewableData);
Example code for removing a device(sensor)
devices = devices.splice(1, 1);
// Remove existing sprites
dataVizExt.removeAllViewables();
const viewableData = new Autodesk.DataVisualization.Core.ViewableData();
viewableData.spriteSize = 16;
// re-add viewables
devices.forEach((device, index) => {
const style = styleMap[device.type] || styleMap["default"];
const viewable = new Autodesk.DataVisualization.Core.SpriteViewable(
device.position,
style,
index + 1
);
viewableData.addViewable(viewable);
});
await viewableData.finish();
dataVizExt.addViewables(viewableData);
I am trying to find x,y,z coordinates of an object inside nwc model, and I am using below code.
Despite that this code was working for rvt files, it is not working for nwc model.
Is there a way to get x,y,z coordinates from a nwc model?
getFragmentWorldMatrixByNodeId(nodeId) {
let result = {
fragId: [],
matrix: [],
};
let viewer = this.viewer;
this.tree.enumNodeFragments(nodeId, function (frag) {
let fragProxy = viewer.impl.getFragmentProxy(viewer.model, frag);
let matrix = new THREE.Matrix4();
fragProxy.getWorldMatrix(matrix);
result.fragId.push(frag);
result.matrix.push(matrix);
});
return result;
}
You mentioned you are looking for the "x,y,z coordinates of an object". What exactly do you mean by that? I'm going to assume that you want the coordinates of the center point of the object's bounding box, as that is what people ask for usually. In your code snippet, however, you're retrieving the entire transformation matrix, not a position.
If the center point of a bounding box works for you, you could obtain it like so:
function getObjectBoundingBox(model, dbid) {
const tree = model.getInstanceTree();
const frags = model.getFragmentList();
let totalBounds = new THREE.Box3();
tree.enumNodeFragments(dbid, function (fragid) {
let fragBounds = new THREE.Box3();
frags.getWorldBounds(fragid, fragBounds);
totalBounds.union(fragBounds);
}, true);
return totalBounds;
}
getObjectBoundingBox(viewer.model, 123).center();
I'm trying to switch from paperspace view to model space view in a dwg loaded in the Forge platform on v7. I think it's supposed to use the BubbleNode, but I can't find any code samples showing. Any ideas how to get the BubbleNode from a loaded document?
I've reviewed: https://forge.autodesk.com/en/docs/viewer/v7/change_history/changelog_v7/migration_guide_v6_to_v7/
and
https://forge.autodesk.com/en/docs/viewer/v7/developers_guide/viewer_basics/load-a-model/
Trying to piece together some sample code that will do the same thing as step 3 here from v6:https://forge.autodesk.com/en/docs/viewer/v6/tutorials/basic-viewer/
You can get geometry BubbleNode Array by search method of root BubbleNode with specifying { 'type': 'geometry' } as parameter.
Below is code example for switching viewable.
var viewer;
var viewables;
var indexGeom;
var viewdoc;
//Call back for viewer DocumentLoadSuccess
function onDocumentLoadSuccess(doc) {
viewdoc = doc;
indexGeom = 0;
viewables = doc.getRoot().search({ 'type': 'geometry' });
viewer.loadDocumentNode(doc, viewables[indexGeom]).then(i => {
activateUI();
});
}
//Call back for switch to next view button
function loadNextModel() {
// Next geometry index. Loop back to 0 when overflown.
indexGeom = (indexGeom + 1) % viewables.length;
viewer.tearDown();
viewer.loadDocumentNode(viewdoc, viewables[indexGeom]).then(i => {
activateUI();
});
}
pls. see developer guide '3.Load a Model' chapter.
https://forge.autodesk.com/en/docs/viewer/v7/developers_guide/viewer_basics/load-a-model/
I created below function to get worldCoordinates back, but it gives different values on two occasions.
While clicking a dbId, I get dbid cordinates and I pass it to below function which give me world coordinates, but you can see while I save that dbId selection to DB and reloading page next time to see it back, it gives me different coordinates.
Why it happen so?
saving dbid phase
dbid coordinates
x: -26.277027130126953
y: 18.102033615112305
z: -7.173819303512573
getWorldCoordinates
x: 256.76347287180107
y: 306.8180434914181
z: 0
relaoding page phase
dbid coordinates
x: -26.277027130126953
y: 18.102033615112305
z: -7.173819303512573
getWorldCoordinates
x: 422.50000131979897
y: 249.49997927733767
z: 0
function getWorldCoordinates(position){
var screenpoint = viewer.worldToClient(
new THREE.Vector3(position.x,
position.y,
position.z,));
return screenpoint
}
function getObjPosition(dbId) {
function getObjPosition(dbId) {
const model = viewer.model;
const instanceTree = model.getData().instanceTree;
const fragList = model.getFragmentList();
let bounds = new THREE.Box3();
instanceTree.enumNodeFragments( dbId, ( fragId ) => {
let box = new THREE.Box3();
fragList.getWorldBounds( fragId, box );
bounds.union( box );
}, true );
const position = bounds.center();
return position;
}
Unfortunately I was unable to reproduce the issue...
Try live demo here - refresh the page and see the world coords prompted when the model completes loading ...
Be sure to convert the coords after the model finishes loading (e.g. after the TEXTURES_LOADED_EVENT) otherwise you may get erratic results:
NOP_VIEWER.addEventListener(Autodesk.Viewing.TEXTURES_LOADED_EVENT,()=>{
alert(JSON.stringify(NOP_VIEWER.worldToClient(
new THREE.Vector3(-26.277027130126953,
18.102033615112305,
-7.173819303512573,))))
})