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

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');

Related

is there is any solution for openlayer pop up not working?

var container = document.getElementById('popup');
var content = document.getElementById('popup-content');
var closer = document.getElementById('popup-closer');
var overlay = new Overlay({
element: container,
autoPan: true,
autoPanAnimation: {
duration: 250,
},
});
closer.onclick = function () {
overlay.setPosition(undefined);
closer.blur();
return false;
};
this.draw.on('drawend', (e: any) => {
const coordinate = e.coordinate;
const hdms = toStringHDMS(toLonLat(coordinate));
content.innerHTML =
'<p> Current Position are :</p><code>' + hdms + '</code>';
overlay.setPosition(coordinate);
});
this.map.addOverlay(overlay);
}
pop up is not coming after the draw end. but the pop up is working on map single click events .i could not find anything related on internet

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}`

How to get coordinates i.e vertices from an object

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!

Autodesk Forge - Isolating element: amount of transparency of others

When we isolate an element in a 3d view, is there anyway to control the amount of transparency of all the other elements? Say, change to 50% translucent?
Have I missed something obvious?
And can you do the same for 2d views?
I dug out the following code for you, it shows how to set all leaf nodes to 50% opacity by changing their material properties:
AutodeskNamespace("Autodesk.ADN.Viewing.Extension");
function getLeafNodes(model, nodeId) {
return new Promise((resolve, reject)=>{
try{
var leafIds = [];
var instanceTree = model.getData().instanceTree
nodeId = nodeId || instanceTree.getRootId()
function _getLeafNodesRec(id){
var childCount = 0;
instanceTree.enumNodeChildren(id,
function(childId) {
_getLeafNodesRec(childId)
++childCount
})
if(childCount == 0){
leafIds.push(id)
}
}
_getLeafNodesRec(nodeId)
return resolve(leafIds)
} catch(ex){
return reject(ex)
}
})
}
function nodeIdToFragIds(model, nodeId) {
var instanceTree = model.getData().instanceTree
var fragIds = []
instanceTree.enumNodeFragments(
nodeId, (fragId) => {
fragIds.push(fragId)
});
return fragIds
}
Autodesk.ADN.Viewing.Extension.Basic = function (viewer, options) {
Autodesk.Viewing.Extension.call(this, viewer, options);
var _this = this;
_this.load = function () {
var fragList = viewer.model.getFragmentList()
getLeafNodes(viewer.model).then((dbIds) => {
dbIds.forEach((dbId) => {
const fragIds = nodeIdToFragIds(
viewer.model, dbId)
fragIds.forEach((fragId) => {
var material = fragList.getMaterial(fragId)
if(material) {
material.opacity = 0.5
material.transparent = true
material.needsUpdate = true
}
})
})
viewer.impl.invalidate(true, true, true)
})
return true;
};
_this.unload = function () {
Autodesk.Viewing.theExtensionManager.unregisterExtension(
"Autodesk.ADN.Viewing.Extension.Basic");
return true;
};
};
Autodesk.ADN.Viewing.Extension.Basic.prototype =
Object.create(Autodesk.Viewing.Extension.prototype);
Autodesk.ADN.Viewing.Extension.Basic.prototype.constructor =
Autodesk.ADN.Viewing.Extension.Basic;
Autodesk.Viewing.theExtensionManager.registerExtension(
"Autodesk.ADN.Viewing.Extension.Basic",
Autodesk.ADN.Viewing.Extension.Basic);
Some syntax requires ES6 transpiling. You can quickly paste the code there to test it: http://viewer.autodesk.io/node/gallery/#/extension-editor?id=560c6c57611ca14810e1b2bf
This works only for 3D, I'll see what we can do for 2D and update that topic.