Highlight an array of nodes in Autodesk Viewer - autodesk-forge

Problem:
I have an array of nodes that I would like to highlight when an action happens.
My Attempted Solution
I have tried using code from the model browser, but it seems to only accept one dbId at a time. I have tried to iterate over my array and call it, but the highlighting doesn't work when that is done.
for (var i = 0; i < dbIdsArray.length; i++) {
viewerApp.getCurrentViewer().impl.rolloverObjectNode(dbIdsArray[i]);
}
Any advice on how to implement this correctly would be a great help.
Thanks

If you want to highlight a couple of dbids, there are some different ways depending on your requirement.
Maybe you can use the API Viewer3D.isolate() to highlight the
selected objects by isolating them, you can just input dbId array as
follow, also, you can zoom the selected items to the viewer window
use the API Viewer3D.fitToView() to focus on them:
viewer.isolate(dbIdArray);
viewer.fitToView(dbIdArray);
If you want to highlight the selected objects with different color,
maybe you can try the new API Viewer3D.setThemingColor(), here is the
simple code sample. Remember you need to clear the color using
Viewer3D.clearThemingColors(). The simple code sample should be like:

I'm able to highlight components using following code:
viewer.addEventListener(
Autodesk.Viewing.SELECTION_CHANGED_EVENT,
function (e) {
if(e.dbIdArray.length) {
var dbId = e.dbIdArray[0];
viewer.impl.highlightObjectNode(
viewer.model, dbId, true, false)
viewer.select([])
viewer.impl.sceneUpdated(true)
}
})
This is using function:
viewer.impl.highlightObjectNode = function(model, dbId, value, simpleHighlight)

Related

Set transparent for specific element

I want set transparent for specific element, i follow this code:
var instanceTree = this.viewer.model.getInstanceTree();
var fragList = this.viewer.model.getFragmentList();
this.listElement.forEach(element => {
instanceTree.enumNodeFragments(element, (fragId) => {
console.log(element.material)
var material = fragList.getMaterial(fragId)
if (material) {
material.opacity = value;
material.transparent = true;
material.needsUpdate = true;
}
});
});
this.viewer.impl.invalidate(true, true, true);
but it overide for all elements have that material. How can i set for selected element?
Appreciate any comments.
UPDATE 1:
i found go around way is clone main material than register it with different name:
var newMaterial = material.clone();
const materials = this.viewer.impl.matman();
materials.addMaterial('mymaterial',newMaterial,true);
fragList.setMaterial(fragId,newMaterial);
newMaterial.opacity = value;
newMaterial.transparent = true;
newMaterial.needsUpdate = true;
but effect is not what i want, it has different color and when set transparent i can only see a couple object behind it
You can create your own, custom THREE.js material and assign it to the fragment using fragList.setMaterial(fragId, material).
For more information on using custom materials or shaders, see https://forge.autodesk.com/blog/custom-shader-materials-forge-viewer.
EDIT:
Regarding the visual anomalies (for example, when you only see some of the objects behind something semi-transparent), this is a known problem, unfortunately with no clear solution. When the Forge Model Derivative service creates an SVF file to be viewed in Forge Viewer, the individual scene elements are stored in a data structure optimized for fast traversal, depending on whether they are semi-transparent or fully opaque. This data structure is fixed, and so unfortunately, when you take an object that was originally fully opaque, and you make it semi-transparent, it will most likely be rendered in a wrong order...

Autodesk Forge Viewer, Search

I am using Autodesk Forge Viewer.
viewer.search('"' + keyword +'"', function(e)
{
viewer.select(e);
viewer.fitToView(e);
}
I am searching like this.
The problem is that it searches for both "SG-100" and "SSG-100".
I only want to search for SG-100.
How can I do this?
Help!
My suggestion would be to do a second filter inside the search:
viewer.search(keyword, (dbIds) => {
// success
viewer.getBulkProperties(dbIds, ['AttributeName'], (elements) => {
let dbIdsToSelect = [];
for(var i=0; i<elements.length; i++){
if (elements[i].properties[0].displayValue===keyword)
dbIdsToSelect.push(elements[i].dbId;
}
viewer.select(dbIdsToSelect);
viewer.fitToView(dbIdsToSelect);
}
}, (e) => {
// error, handle here...
}, ['AttributeName']);
I agree with Augusto's suggestion that you'd need to limit the search scope to specific properties only to avoid partial matches. According to the search function description it's supposed to do just that if you provide the list of property names in the 4th argument that is called 'attributeNames'. Unfortunately, from my experience, that does not work, so you need a second level filtering by using a getBulkProperties function that will reduce the list of dbIds from the search to only those that have specific properties defined. Pay attention, that the search method belong to the viewer object but the getBulkProperties method belongs to the viewer.model object.

Instance of ModelStructurePanel in div outside the viewer GUI

I am building an html page with model query functionality starting from the viewer.
I have implemented some methods including viewer.getProperties () and viewer.getBulkProperties (). Working on these themes I realized that it would be very useful to create instances of the panels (modelstructure, properties etc ...) in elements external to the viewer (not in the docking panels), maintaining the functionality and if possible customizing them (for example showing the elements not -collapsed).
First question: is it possible to do this? Second question: suggestions on a general method or tutorial for this theme?
For the Q1, it's possible to do that, appending the container of the ModelStructurePanel to a div outside the viewer container, but it's not recommended. It would take extra effort to fix CSS of the ModelStructurePanel after moving it out from viewer container.
For the Q2, you could take advantage of the jstree.js to make a three like UI, and for rebuilding model hierarchy data as the native ModelStructurePanel does, here is a code snippet for you. You would have to modify it to match jstree's data requirement.
function buildModelTree( model ) {
//builds model tree recursively
function _buildModelTreeRec( node ) {
it.enumNodeChildren( node.dbId, function(childId) {
node.children = node.children || [];
var childNode = {
dbId: childId,
name: it.getNodeName( childId )
};
node.children.push( childNode );
_buildModelTreeRec( childNode );
});
}
//get model instance tree and root component
var it = model.getData().instanceTree;
var rootId = it.getRootId();
var rootNode = {
dbId: rootId,
name: it.getNodeName( rootId )
};
_buildModelTreeRec( rootNode );
return rootNode;
}
var root = buildModelTree( viewer.model );
Then you will need to bind those events to change tree UI's look in some particular situations:
Autodesk.Viewing.SELECTION_CHANGED_EVENT: To make tree nodes be selected.
Autodesk.Viewing.ISOLATE_EVENT:
Autodesk.Viewing.HIDE_EVENT: To change tree node look, make tree nodes' text color gray if this node is invisible.
Autodesk.Viewing.SHOW_EVENT: To change selected three node's text color from gray to black
Autodesk.Viewing.ISOLATE_EVENT: a combination of the HIDE_EVENT and SHOW_EVENT.
And bind select_node.jstree event to isolate, fit to view the viewer objects according to the selected three nodes.
Isolate: Viewer3D#isolate( dbIds )
Fit to view: Viewer3D#fitToView( dbIds )

How can I remove or hide an object on the model tree panel in Forge Viewer?

I need to hide (make it go away completely) from the model tree panel in Viewer.
I already tried overriding methods from the Viewer (some other stuff is done that way), but the Tree-related methods and objects are not accessible for extending. It also seems too dangerous to mess with instanceTree data, like removing the dbId from the nodes list.
I'm running on the latest Viewer code (6.5.3), and writing pure javascript extensions.
For example, I tried overriding this function, which is used internally to determine if a node should or not be displayed. It doesn't work, neither does overriding the same function on the ModelStructureTreeDelegate:
Autodesk.Viewing.UI.TreeDelegate.prototype.shouldCreateTreeNode = function (dbId)
{
// original code on the viewer.js is:
// return true;
let itGo = true;
// _objectsHiddenInTree is populated with dbIds of objects to be hidden right after initializing the viewer
_objectsHiddenInTree.forEach(x => {
if (x == dbId){
itGo = false;
}
});
// return false; doesn't work either
return itGo;
};
Is there a way to do this from the Viewer side? I mean, to remove an item from the model tree?
If it's more viable, removing the object from the scene altogether is also a valid option. But I can't remove it from the model before sending to model derivative, it has to be done when opening the Viewer, or before opening the Tree Model panel.
Personally the easiest way would be to access node element via viewer.modelstructure and use styling to hide the node:
<style>
.yourHiddenNodeClass{display:none!important}
</style>
...
<script>
let modelStructureControl = viewer.modelstructure;
modelStructureControl.createUI(); //initialize the panel if it hasn't
let treeViewControl = modelStructureControl.tree;
let modelDelegate = treeViewControl.getDelegate(model.id);
treeViewControl.addClass(modelDelegate, dbid, "yourHiddenNodeClass", false) //hide a node - last boolean to toggle recursiveness
...
treeViewControl.removeClass(modelDeleagate, dbid, "yourHiddenNodeClass", false) //remove your custom class
</script>
And to hide a node completely:
model.visibilityManager.setNodeOff(dbid, true) // true=hide, false=show
Bryan's answer gave me an idea that seems to work for now:
Every element on the tree panel has an atribute 'lmv-nodeid', with the dbId of the object. So I looked for it, and added the 'hidden' attribute to the div:
document.querySelectorAll('[lmv-nodeid="' + objectDbId + '"]')[0].hidden = true;
His answer is still better, though, because there is no guarantee that the attribute will remain on newer versions of the Viewer, whereas the Viewer classes and methods are more stable and future-proof.

Set marker visible with knockout JS ko.utils.arrayFilter

Hello guys I am trying to create an app that sets the appropriate markers visible when a knockout search is being done.
Basically the app is.
When someone does a search the list that is bellow it, filters the list and makes only the markers that are associated with the filter list visible on the map.
I have created a ko.utils.arrayFilter and I am trying to set only the item.marker.setVisible(true)
My Github link is https://github.com/Aimpotis/map3
Thank you again and much respect to the community it is helping me learn a lot
All you need is to set the visibility of the marker to match whether it is found:
if (!filter) {
// this is new
ko.utils.arrayForEach(self.listLoc(), function (item) {
item.marker.setVisible(true);
});
return self.listLoc();
} else {
return ko.utils.arrayFilter(self.listLoc(), function(item) {
var result = (item.title.toLowerCase().search(filter) >= 0)
item.marker.setVisible(result); // this is a new line
return result;
});
}
Working fiddle.
Note: unless you're supporting particularly old browsers, you can use the Array filter method rather than Knockout's arrayFilter util, and .foreach instead of arrayForEach.