How to get coordinates i.e vertices from an object - autodesk-forge

I am trying to get geometry data one element at a time such as vertices.
What I tried so far:
fragproxy = viewer.gui.impl.getFragmentProxy(model,fragid) gives me some kind of geometry, but not any vertices, which I can access by dbId
frags = viewer.gui.model.getFragmentList() gives me a fragId to dbId map but no no connection from dbId to geometry
Does anyone know a method to get the geometry with the vertices ?

The positions value of the following code snippets is the vertices what you want.
function getLeafFragIds( model, leafId ) {
const instanceTree = model.getData().instanceTree;
const fragIds = [];
instanceTree.enumNodeFragments( leafId, function( fragId ) {
fragIds.push( fragId );
});
return fragIds;
}
function getComponentGeometry( viewer, dbId ) {
const fragIds = getLeafFragIds( viewer.model, dbId );
let matrixWorld = null;
const meshes = fragIds.map( function( fragId ) {
const renderProxy = viewer.impl.getRenderProxy( viewer.model, fragId );
const geometry = renderProxy.geometry;
const attributes = geometry.attributes;
const positions = geometry.vb ? geometry.vb : attributes.position.array;
const indices = attributes.index.array || geometry.ib;
const stride = geometry.vb ? geometry.vbstride : 3;
const offsets = geometry.offsets;
matrixWorld = matrixWorld || renderProxy.matrixWorld.elements;
return {
positions,
indices,
offsets,
stride
};
});
return {
matrixWorld,
meshes
};
}
var meshInfo = getComponentGeometry( viewer, 1234 );
Since this information of the Forge fragment is stored in a flatten storage, please check the demo extension Autodesk.ADN.Viewing.Extension.MeshData.js if you want to rebuild the meshing relationship.
Hope it helps!

Related

How to check if the model out of the viewer container?

I made an info card and this card will disappear if the viewer is rotated until the model is not visible. I use isNodevisible but it always returns true.
updateInfoCard() {
if (this.infoCard && this.posModel) {
const pos = this.viewer.worldToClient(this.posModel);
console.log(pos);
this.infoCard.style.left = `${Math.floor(
50 + pos.x - this.infoCard.offsetWidth / 2
)}px`;
this.infoCard.style.top = `${Math.floor(
50 + pos.y - this.infoCard.offsetWidth / 2
)}px`;
const id = this.infoCard.dataset.id;
console.log(this.viewer.isNodeVisible(id));
this.infoCard.style.display = this.viewer.isNodeVisible(id)
? "block"
: "none";
}
}
If I understand your question correctly, you'll probably want to do an intersection test between the camera's frustum and the models's bounding box. That can be done like so:
viewer.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, function () {
if (!viewer.model) {
return;
}
const camera = viewer.getCamera();
const matrix = new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
const frustum = new THREE.Frustum().setFromMatrix(matrix);
const bbox = viewer.model.getBoundingBox();
console.log('Model in the view?', frustum.intersectsBox(bbox));
});
And if you only want to check the visibility of a specific element (based on its dbID) of your model, you can compute its bounding box like so:
function objectBounds(model, dbId) {
const tree = model.getInstanceTree();
const frags = model.getFragmentList();
const objectBounds = new THREE.Box3();
tree.enumNodeFragments(dbId, function (fragId) {
const fragBounds = new THREE.Box3();
frags.getWorldBounds(fragId, fragBounds);
objectBounds.union(fragBounds);
}, true);
return objectBounds;
}
The function isNodeVisible returns the visibility status of your node in the scene. If you do something like this.viewer.hide(id, model) your function will return false.
If I well understood what you want to achieve, you want to hide an info card when the associated object is occluded by others objects, so we can't see it from our point of view ?
So I think what you need is to check for occlusion. You can take a look at the checkOcclusion function of this point cloud markup extension made by Philippe Leefsma.
To check for node occlusion, you basically need to raycast from your point of view to the node that you want to check. If you hit something and it's your node, there is no occlusion. If it's not the same node, it's mean that something occlude your node.
checkOcclusion (markup) {
const clientPoint = this.viewer.worldToClient(
markup.point)
const offset = $(this.viewer.container).offset()
const rayCaster = this.pointToRaycaster(
this.viewer.impl.canvas,
this.viewer.impl.camera, {
x: clientPoint.x + offset.left,
y: clientPoint.y + offset.top
})
const hitTest = this.viewer.model.rayIntersect(
rayCaster, true, this.dbIds)
if (hitTest) {
if (hitTest.fragId === markup.fragId) {
const offset = {
x: hitTest.point.x - markup.point.x,
y: hitTest.point.y - markup.point.y,
z: hitTest.point.z - markup.point.z
}
const dist = Math.sqrt(
offset.x * offset.x +
offset.y * offset.y +
offset.z * offset.z)
if (this.options.logOcclusionDist) {
console.log(dist)
}
if (dist < this.options.occlusionDist) {
return false
}
}
return true
}
}

How can I show 2 different label sets as markup in the forge viewer

I'm trying to show some labels in the forge viewer, based on the example markup code. My code works fine for one dataset, but when I add another one, I get: "BuildingData.js:113 Uncaught TypeError: this.frags[("dbId" + dbId)] is not iterable". The 2 datasets are extensions that extend the BuildingData class & they just create a button & call super.init(). I don't get why it fails when I activate the second label set.
I'm using v7 of the viewer.
class BuildingData extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
}
init(){
//Callback to update labels
const updateLabelsCallback = () => {
if(this.button.getState() === 0) {
this.updateLabels();
}
};
//Events when to update the labels
OnEvent(this.viewer, Autodesk.Viewing.CAMERA_CHANGE_EVENT, updateLabelsCallback);
OnEvent(this.viewer, Autodesk.Viewing.ISOLATE_EVENT, updateLabelsCallback);
OnEvent(this.viewer, Autodesk.Viewing.HIDE_EVENT, updateLabelsCallback);
OnEvent(this.viewer, Autodesk.Viewing.SHOW_EVENT, updateLabelsCallback);
//Add the button to the data bar
this.button.onClick = (ev) => {
this.enabled = !this.enabled;
this.button.setState(this.enabled ? 0 : 1);
this.showLabels();
};
this.viewer.dataBar.getControl("dataGrp").addControl(this.button);
}
cleanup(){
//remove button from data bar
this.viewer.dataBar.getControl("dataGroup").removeControl(this.button);
//remove labels
let labels = FindAll(`#${this.viewer.clientContainer.id} div.adsk-viewing-viewer label.data.${this.dataClass}`);
for(let label of labels) label.remove();
}
showLabels() {
let viewerContainer = Find(`#${this.viewer.clientContainer.id} div.adsk-viewing-viewer`);
//remove old labels
let labels = FindAll(`#${this.viewer.clientContainer.id} div.adsk-viewing-viewer label.data.${this.dataClass}`);
for(let label of labels) label.remove();
//show new labels?
if(!this.enabled) return;
//check if the model tree is available
let tree = this.viewer.model.getInstanceTree();
if(tree === undefined){
console.log("Model tree is not loaded yet");
return;
}
//select sessor & fit to view
const onClick = (e) => {
this.viewer.select(e.currentTarget.dataset.dbId);
this.viewer.utilities.fitToView();
};
this.frags = [];
for(let i = 0; i < this.labels.length; i++){
const label = this.labels[i];
this.frags["dbId" + label.dbId] = [];
// create the label for the dbId
let lbl = document.createElement("label");
lbl.classList.add("data");
lbl.classList.add("update");
lbl.classList.add(this.dataClass);
lbl.dataset.dbId = label.dbId;
lbl.style.cssText = `display: ${this.viewer.isNodeVisible(label.dbId) ? "block" : "none"}`;
//add click event
OnEvent(lbl, "click", onClick);
let span = document.createElement("span");
lbl.appendChild(span);
span.innerText = label.name;
viewerContainer.appendChild(lbl);
//Collect fragment ids of dbId
tree.enumNodeFragments(label.dbId, (fragId) => {
this.frags["dbId" + label.dbId].push(fragId);
this.updateLabels();
});
}
}
updateLabels() {
for(const label of FindAll(`#${this.viewer.clientContainer.id} div.adsk-viewing-viewer .update`)){
const dbId = label.dataset.dbId;
//get center of the dbId based on the bounding box of the fragments
const pos = this.viewer.worldToClient(this.getBoundingBox(dbId).center());
//position label in the center of the box
label.style.cssText = `left: ${Math.floor(pos.x - label.offsetWidth / 2)}px`;
label.style.cssText +=`top: ${Math.floor(pos.y - label.offsetHeight / 2)}px`;
label.style.cssText +=`display: ${this.viewer.isNodeVisible(dbId) ? "block" : "none"})`;
}
}
getBoundingBox(dbId) {
var fragList = this.viewer.model.getFragmentList();
const nodebBox = new THREE.Box3()
//get bounding box for each fragment
for(const fragId of this.frags["dbId" + dbId]){ //<----- ERROR is here
const fragBBox = new THREE.Box3();
fragList.getWorldBounds(fragId, fragBBox);
nodebBox.union(fragBBox);
}
return nodebBox
}
}
I found the solution, I was trying to update all labels (of both datasets) in the viewer & not only the ones corresponding to the active dataset. Changing 1 line in updateLabels() fixed it:
const label of FindAll(`#${this.viewer.clientContainer.id} div.adsk-viewing-viewer .data.${this.dataClass}`

Please tell me how to appear markup near a specific 2D object in Forge Viewer

I am developing HTML + JS using the forge viewer of Autodesk-forge.
I am looking for a way to display text near a specific object in 2D on the viewer.
Please let me know if there is a fix for the code below, or any other way to do it.
What I tried and result
I practiced the contents of the following article
https://adndevblog.typepad.com/technology_perspective/2020/12/forge-viewer-markup-along-dbid.html
The markup could be displayed on the 3D model as expected.
However, it was not possible to set the markup position on the 2D model.
Code (excerpt)
Calling part
viewer.addEventListener(Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, () => {
createMarkUp(viewer, {
icons: [
{ dbId: 11146, label: 'コメントあり', css: 'iconWarning fas fa-exclamation-triangle fa-2x
faa-flash animated' },
],
onClick: (id) => {
viewer.select(id);
}
});
});
  Logic part
  
function createMarkUp(viewer, options) {
let _group = null;
let _button = null;
let _icons = options.icons || [];
let _frags = null;
viewer._enabled = true;
load();
showIcons(true);
function load() {
const updateIconsCallback = () => {
if (viewer._enabled) {
updateIcons();
}
};
viewer.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, updateIconsCallback);
viewer.addEventListener(Autodesk.Viewing.ISOLATE_EVENT, updateIconsCallback);
viewer.addEventListener(Autodesk.Viewing.HIDE_EVENT, updateIconsCallback);
viewer.addEventListener(Autodesk.Viewing.SHOW_EVENT, updateIconsCallback);
return true;
}
function showIcons(show) {
const $viewer = $('#' + viewer.clientContainer.id + ' div.adsk-viewing-viewer');
// remove previous...
$('#' + viewer.clientContainer.id + ' div.adsk-viewing-viewer label.markup').remove();
if (!show) return;
// do we have anything to show?
if (_icons === undefined || _icons === null) return;
// do we have access to the instance tree?
const tree = viewer.model.getInstanceTree();
if (tree === undefined) { console.log('Loading tree...'); return; }
const onClick = (e) => {
if (options.onClick)
options.onClick($(e.currentTarget).data('id'));
};
_frags = {}
for (var i = 0; i < _icons.length; i++) {
// we need to collect all the fragIds for a given dbId
const icon = _icons[i];
_frags['dbId' + icon.dbId] = []
// create the label for the dbId
const $label = $(`
<label class="markup update" data-id="${icon.dbId}">
<span class="${icon.css}"> ${icon.label || ''}</span>
</label>
`);
$label.css('display', viewer.isNodeVisible(icon.dbId) ? 'block' : 'none');
$label.on('click', onClick);
$viewer.append($label);
// now collect the fragIds
tree.enumNodeFragments(icon.dbId, function (fragId) {
_frags['dbId' + icon.dbId].push(fragId);
updateIcons(); // re-position of each fragId found
});
}
}
function getModifiedWorldBoundingBox(dbId) {
const fragList = viewer.model.getFragmentList();
const nodebBox = new THREE.Box3();
// for each fragId on the list, get the bounding box
for (const fragId of _frags['dbId' + dbId]) {
const fragbBox = new THREE.Box3();
fragList.getWorldBounds(fragId, fragbBox);
nodebBox.union(fragbBox); // create a unifed bounding box
}
return nodebBox
}
function updateIcons() {
for (const label of $('#' + viewer.clientContainer.id + ' div.adsk-viewing-viewer .update'))
{
const $label = $(label);
const id = $label.data('id');
// get the center of the dbId (based on its fragIds bounding boxes)
const pos = viewer.worldToClient(getModifiedWorldBoundingBox(id).center());
// position the label center to it
$label.css('left', Math.floor(pos.x - $label[0].offsetWidth / 2) + 'px');
$label.css('top', Math.floor(pos.y - $label[0].offsetHeight / 2) + 'px');
$label.css('display', viewer.isNodeVisible(id) ? 'block' : 'none');
}
}
 }
Constitution
  HTML+JS
Unfortunately, the code above cannot be applied to 2D drawings as those use a different internal data structure.
With that said, you should still be able to achieve what you want because the Viewer APIs provide some utilities for 2D designs as well. For example, based on this blog post, you can obtain the bounding box of a specific element in your 2D drawing like so:
function getBounds2D(viewer, model, dbId) {
const frags = model.getFragmentList();
let bounds = new THREE.Box3();
let boundsCallback = new Autodesk.Viewing.Private.BoundsCallback(bounds);
let fragIds = frags.fragments.dbId2fragId[dbId]; // Find all fragments including this object's primitives
if (!Array.isArray(fragIds)) {
fragIds = [fragIds];
}
for (const fragId of fragIds) {
// Get the actual mesh with all geometry data for given fragment
const mesh = frags.getVizmesh(fragId);
const vbr = new Autodesk.Viewing.Private.VertexBufferReader(mesh.geometry, viewer.impl.use2dInstancing);
vbr.enumGeomsForObject(dbId, boundsCallback); // Update bounds based on all primitives linked to our dbId
}
return bounds;
}
This will return a THREE.Box3 structure defining the extent of the 2D primitive. From there, you could grab the center of the bounding box, project it to screen coordinates, and use the screen coords to position your HTML overlay elements, for example:
const bounds = getBounds2D(viewer, viewer.model, someDbId);
const coords = viewer.worldToClient(bounds.center());
$label.css('left', Math.floor(coords.x + 'px');
$label.css('top', Math.floor(coords.y + 'px');

How do you render a line markup with the Markups Extension?

I previously asked question about if there was a programatic way to render markups with the markups extension. This worked, or at least, for text markups! Now I am trying to do the same thing with line markups; however, I am stuck with one issue. How do you add the locations to the markup? I have an array of locations that I am trying to assign to it, but there doesn't seem to be a function, and when I try to directly assign locations with markup.location = [etc], for some reason it changes all the numbers to infinity.
So, how can I assign the location array to the markup?
This is how I am loading them:
let MarkupsCore = Autodesk.Viewing.Extensions.Markups.Core;
let line = new MarkupsCore.MarkupFreehand(25, markupTool); //eslint-disable-line
line.locations = [{x: 2, y: 3}]; //something like this
markupTool.addMarkup(line);
line.setSize({ x: markup.x, y: markup.y}, markup.width, markup.height);
line.updateStyle(true);
The MarkupFreehand cannot be used directly, it should be replaced by EditModeFreehand. It's also not easy to archive your request, to create a line markup, in a few codes. Here is the code snippet I used to create a line markup with the MarkupCore extension:
function createLineStartPt( mousePosX, mousePosY, editor ) {
const editMode = markup.editMode;
editor.snapper && editor.snapper.clearSnapped();
editMode.lastX = editMode.initialX = mousePosX;
editMode.lastY = editMode.initialY = mousePosY;
//set the starting point
const position = editMode.position = editor.clientToMarkups( mousePosX, mousePosY );
editMode.movements = [position];
const size = editMode.size = editor.sizeFromClientToMarkups( 1, 1 );
// Create pen.
editor.beginActionGroup();
const markupId = editor.getId();
const createPen = editMode.createPen( markupId, position, size, 0, [{x: 0, y: 0 }] );
createPen.execute();
editMode.createAbsolutePath( position );
editMode.selectedMarkup = editor.getMarkup( markupId );
editMode.creationBegin();
}
function createLineEndPt( mousePosX, mousePosY, editor ) {
const editMode = markup.editMode;
const selectedMarkup = editMode.selectedMarkup;
if( !selectedMarkup || !editMode.creating )
return;
const movements = editMode.movements;
const location = editor.clientToMarkups( mousePosX, mousePosY );
const dx = editMode.lastX - mousePosX;
const dy = editMode.lastY - mousePosY;
const moveTol = 25; // 5^2, compare to square to avoid using square root of distance
if( movements.length > 1 && (dx*dx + dy*dy) < moveTol ) {
movements[movements.length - 1] = location;
editMode.removeFromAbsolutePath( 1 );
} else {
movements.push( location );
editMode.lastX = mousePosX;
editMode.lastY = mousePosY;
}
editMode.addToAbsolutePath([location]);
const appendPen = editMode.setPen( editMode.position, editMode.size, editMode.absolutePath, true );
appendPen.execute();
}
function endLineDrawing( editor ) {
const editMode = markup.editMode;
if( !editMode.creating )
return editMode.creationEnd();
let movements = editMode.movements;
const cameraWidth = editMode.viewer.impl.camera.right - editMode.viewer.impl.camera.left;
const cameraHeight = editMode.viewer.impl.camera.top - editMode.viewer.impl.camera.bottom;
const cameraDiagSq = cameraWidth * cameraWidth + cameraHeight * cameraHeight;
movements = Autodesk.Viewing.Extensions.Markups.Core.Utils.simplify( movements, cameraDiagSq * 0.00000001, true );
const xs = movements.map(function(item) { return item.x });
const ys = movements.map(function(item) { return item.y });
const l = Math.min.apply(null, xs);
const t = Math.min.apply(null, ys);
const r = Math.max.apply(null, xs);
const b = Math.max.apply(null, ys);
const width = r - l; // Already in markup coords space
const height = b - t; // Already in markup coords space
const position = {
x: l + width * 0.5,
y: t + height * 0.5
};
const size = editMode.size = {x: width, y: height};
// Adjust points to relate from the shape's center
const locations = movements.map(function(point){
return {
x: point.x - position.x,
y: point.y - position.y
};
});
const endPen = editMode.setPen( position, size, locations, false );
endPen.execute();
editMode.creationEnd();
}
Then call them in this way:
// Load the extionsion
let markup;
viewer.loadExtension( 'Autodesk.Viewing.MarkupsCore' )
.then(function( markupsExt ) {
markup = markupsExt;
});
// Enter markup editer mode and change it to freehand mode
markup.enterEditMode();
const freehand = new Autodesk.Viewing.Extensions.Markups.Core.EditModeFreehand( markup );
markup.changeEditMode( freehand );
// Create start point of the line
createLineStartPt( 360.03125, 191.3125, markup );
// Create end point of the line
createLineEndPt( 460.03125, 191.3125, markup );
// Tell the markup tool to finish drawing
endLineDrawing( markup );
Here is the result of the above codes:
Note. All mousePos prefixed variables are coordinates in the client coordinate system in the browser viewport, see below link for details. After getting the mosue's clientX or clientY, you have to minus markup.svg.getBoundingClientRect() to adjust their values.
https://developer.mozilla.org/en-US/docs/Web/CSS/CSSOM_View/Coordinate_systems#Client
Hope it helps!

Autodesk Forge Viewer How to get coordinates of line start/stop

I am trying to do room highlighting in forge viewer.
In revit I have created lines that represent the borders of a room. After conversion to svf I know the dbids of those lines. Now I want to know the start and stop points (vertices) of those lines so that I can create a Three.Shape() of the room borders.
[EDIT] I get the fragId from dbId
function getFragIdFromDbId(viewer, dbid){
var returnValue;
var it = viewer.model.getData().instanceTree;
it.enumNodeFragments(dbid, function(fragId) {
console.log("dbId: " + dbid + " FragId : " + fragId);
returnValue = fragId;
}, false);
return returnValue;
}
Question:
Once I know the fragId is there a way to see its start and stop points(vertices)? Also will those vertices be world space or local space?
This is what I ended up doing. Note make sure the model is finished loading before calling instanceTree. Also in my case the dbid and fragid where one to one, not sure if this will always be the case in the instance tree.
function getFragIdFromDbId(viewer, dbid) {
var returnValue;
var it = viewer.model.getData().instanceTree;
it.enumNodeFragments(dbid, function (fragId) {
console.log("dbId: " + dbid + " FragId : " + fragId);
returnValue = fragId;
}, false);
return returnValue;
}
...
// only need the start vertex
var floatArray = [];
for (var i = 0; i < dbidArray.length; i++) {
var fragId = getFragIdFromDbId(viewer, dbidArray[i]);
var mesh = viewer.impl.getRenderProxy(viewer.model, fragId);
var matrixWorld = mesh.matrixWorld;
var lmvBufferGeometry = mesh.geometry;
var lmvFloatArray = lmvBufferGeometry.vb; //this will have an array of 6 values 0,1,2 are start vertext , 3,4,5 are end vertex
floatArray.push(lmvFloatArray[0]);
floatArray.push(lmvFloatArray[1]);
floatArray.push(lmvFloatArray[2]);
}
//use matrixWorld to convert array to worldSpace