Default material for model in Forge Viewer - autodesk-forge

I'd need to have the complete model use a default material (e.g. grey color) and then use externally defined materials for each node.
So I'm looking for some advice on two points:
1) Setting a default material on all nodes.
2) Setting the material / color for given nodes after they're fetched from an external source.
Could this be done at some point before the model is loaded into the viewer? (i.e. server-side)? If not, can it be done in the viewer?

All geometry coming from Forge will always have some material defined for it, but you can iterate over dbIDs of all objects on the model and set a custom THREE.js material for them using something along these lines:
function setCustomMaterial(viewer, dbids) {
const material = new THREE.MeshPhongMaterial({
color: 0xAB00EE,
specular: 0xEEABEE
});
viewer.impl.matman().addMaterial('CustomMaterial', material, true);
const fragList = viewer.model.getFragmentList();
const instanceTree = viewer.model.getData().instanceTree;
for (let dbid of dbids) {
instanceTree.enumNodeFragments(dbid, function(frag) {
fragList.setMaterial(frag, material);
});
}
}

Related

Forge viewer - fitToView when running locally and loading local sourced model

I am trying to ensure that when a 3d model is loaded into the viewer it should always orient the model in isometric view and then fit to view.
I have tried the viewer.fitToView(null, null, true) method as well as viewer.fitToView(model) but no success.
This is what I currently have:
var options = {
env : 'Local',
};
var viewer = new Autodesk.Viewing.GuiViewer3D(document.getElementById('ADViewer'));
Autodesk.Viewing.Initializer(options,function() {
if (showDocumentBrowser) {
//file is 2D so load document browser extension
viewer.loadExtension('Autodesk.DocumentBrowser');
// for sheet metal pdf drawings display page 2 first
if(sDisplayFlag == "sm") {
viewer.loadExtension('Autodesk.PDF').then(function() {
// URL parameter `page` will override value passed to loadModel
viewer.loadModel(sFileName, { page: 2 });
});
}
else {
viewer.loadModel(sFileName);
}
}else
{
//file is 3D model. Need to add code here to orient model in isometric view and then fit to view
viewer.loadModel(sFileName);
}
viewer.setTheme('light-theme');
viewer.start(options);
});
There is no difference between local or official mode while using fitToview.
The function declaration of the Viewer3D#fitToview is fitToView(objectIds, model, immediate), so the ways you're using are incorrect.
// Make the camera focus on one object
const selSet = viewer.getSelection();
viewer.fitToView(selSet[0]);
// Make the camera zoom out
viewer.fitToView();

Forge Viewer, raster images

Is possible to directly load a raster image (PNG, JPG, TIFF) to Forge Viewer?
I see the Autodesk.PDF add-in that can load PDF, I cant find any Autodesk.IMAGE add-in...
Otherwise I need to prior convert Image into PDF and than load it through Autodesk.PDF.
The Autodesk Forge Viewer is based on Three.js - therefore you can use the Three.js API to load an image/texture, there is no need of a Viewer extension for that.
However it depends what you want to do. In case you just want to load an image in the scene, that code is enough.
const texture = THREE.ImageUtils.loadTexture( "thumbnail256.png" );
const material = new THREE.MeshBasicMaterial({ map : texture });
const geometry = new THREE.PlaneGeometry(5, 20, 32);
const planeMesh = new THREE.Mesh(geometry, material);
const planeMesh.position.set(1, 2, 3);
NOP_VIEWER.overlays.addScene('custom-scene');
NOP_VIEWER.overlays.addMesh(planeMesh, 'custom-scene');
But if you want to apply the texture on an existing element in the loaded scene, you need to proceed like this:
const texture = THREE.ImageUtils.loadTexture( "thumbnail256.png" );
const material = new THREE.MeshBasicMaterial({ map : texture, side: THREE.DoubleSide });
NOP_VIEWER.impl.matman().addMaterial('custom-material', material, true);
const model = NOP_VIEWER.model;
model.unconsolidate(); // If the model is consolidated, material changes won't have any effect
const tree = model.getInstanceTree();
const frags = model.getFragmentList();
const dbids = NOP_VIEWER.getSelection();
for (const dbid of dbids) {
tree.enumNodeFragments(dbid, (fragid) => {
frags.setMaterial(fragid, material);
});
}
NOP_VIEWER.impl.invalidate(true, true, false);
Note you may need to work out the texture uv, depending on the geometry.

Is it possible to dimm a model except the objects NOT affected by setThemingColor?

We use the viewer (version 7) to show the issues on 3d model colouring the affected objects using a user defined palette.
The remainig objects of the model are colored with a gray tone.
To achieve this I'm using the setThemingColor technique: I set the theming color grey for the rootid recursive and then I set the correct theme color to the specific issued object.
All the colors used are THREE.Vector4 with the opacity set to 1. In this way the themingColor is non blended with the "natural" color of the object but it "covers" object.
To improve the user experience we'd like to allow the user to dimm the objects not affected by issue instead of set the gray color using setThemingColor.
And now the question: is it possible to change the color (material?) of a group of objects by specifying a fade level up to the ghost of the viewer hide method while preserving the selection functionality?
I have tried the following approach with no success:
const mat = new THREE.MeshBasicMaterial({ color: 0x00ff00, opacity: 0.925, transparent: false });
(this.viewer as any).impl.getMaterials().addMaterial('ghost-material', mat, true);
const model = (this.viewer as any).model;
const tree = model.getInstanceTree();
const frags = model.getFragmentList();
[19240, 20373, 34364, 58813].map(dbid => {
tree.enumNodeFragments(dbid, (fragid) => {
console.log(fragid);
frags.setMaterial(fragid, mat);
(this.viewer as any).impl.invalidate(true);
}, true);
});
[19240, 20373, 34364, 58813] are valid dbids.
The opacity is almost 1 and the material is not transparent hoping to see something.
I tried to invalidate the viewer for each fragment.
After running this script, the 3d model remains the same.
The 'ghost-material' is regularly registered on matman but does not 'replace' the native one.
Where I am doing wrong?
The most important thing to do is to call the method:
model.unconsolidate(); // If the model is consolidated, material changes won't have any effect
before apply the new material to the fragments as explained in the following post
https://forge.autodesk.com/blog/custom-shader-materials-forge-viewer
The right answer is
const mat = new THREE.MeshBasicMaterial({ color: 0x00ff00, opacity: 0.925, transparent: false });
(this.viewer as any).impl.getMaterials().addMaterial('ghost-material', mat, true);
const model = (this.viewer as any).model;
const tree = model.getInstanceTree();
const frags = model.getFragmentList();
// without this it is all useless
model.unconsolidate();
[19240, 20373, 34364, 58813].map(dbid => {
tree.enumNodeFragments(dbid, (fragid) => {
console.log(fragid);
frags.setMaterial(fragid, mat);
(this.viewer as any).impl.invalidate(true);
}, true);
});
Yes, it's a bit of an advanced topic but you can customize materials assigned to individual objects (fragments). The process would be as follows:
Create a custom, semi-transparent material, e.g., a simple THREE.MeshBasicMaterial, and add it to the viewer's material manager
Use the instance tree to enumerate fragments of all objects in your scene
Use the fragment list to retrieve the original material of each fragment, store its reference somewhere, and set its material to your custom one
The code could look roughly like so:
const mat = new THREE.MeshBasicMaterial({ color: 0x00ff00, opacity: 0.25, transparent: true });
viewer.impl.getMaterials().addMaterial('my-material', mat, true);
const tree = viewer.model.getInstanceTree();
const frags = viewer.model.getFragmentList();
tree.enumNodeFragments(tree.getRootId(), (fragid) => { frags.setMaterial(fragid, mat) }, true);
When needed, repeat the process, and reset each fragment to its original material.

forge viewer - how to show object tree / components tree along with viewer?

How we can show object tree / components tree along with viewer? So that user can click on the tree node and then he can see the object/dbid selected in viewer?
Any thoughts on this?
So, you wanted to build a similar model tree aside by Forge Viewer. The demo you shared is using JStree library to list the files in BIM 360. I believe you are familiar with JsTree.
To dump the model tree nodes of Forge Viewer, the code below could be a reference. It enumerates the hierarchy and gets the nodes name and dbId one by one.
function getAllLeafComponents(viewer, callback) {
var cbCount = 0;
var tree;
var jsData = []
function getLeafComponentsRec(current,parent) {
cbCount++;
if (tree.getChildCount(current) != 0) {
tree.enumNodeChildren(current, function (children) {
getLeafComponentsRec(children,current);
}, false);
}
var nodeName = viewer.model.getInstanceTree().getNodeName(current)
jsData.push({id:current,parent:parent,text:nodeName})
if (--cbCount == 0) callback(jsData);
}
viewer.getObjectTree(function (objectTree) {
tree = objectTree;
var rootId = tree.getRootId()
var nodeName = viewer.model.getInstanceTree().getNodeName(rootId)
jsData.push({id:rootId,parent:'#',text:nodeName})
var allLeafComponents = getLeafComponentsRec(rootId,'#');
});
}
To use the function,
getAllLeafComponents(viewer, function (jsonData) {
console.log(jsonData);
})
It dumps the tree, which can be used with JSTree. Since the data tells DbId, when the JStree node is clicked, get out dbId, and call
viewer.fitToView([dbId])
It will zoom to the object.

Get THREE.Mesh elements in Autodesk Forge Viewer

I would like to get the THREE.Mesh object of an element in Autodesk Forge Viewer. Here is the code:
var dbId; // geometry node Id of an element
var viewer; // GuiViewer3D
var mesh = viewer.impl.getRenderProxy(viewer.model, dbId);
The return mesh object is a THREE.Mesh object but with null Geometry and Material, so it is useless. How can I get the real THREE.Mesh object?
Thank you.
It depends what you want to do with the mesh: if you want to change the render style, you need to get the renderProxy, if you want to transform the component position or rotation, you need to get the fragmentProxy.
Those methods take as input the fragment ids not the dbId.
Find examples for both at:
Viewing.Extension.Material
Viewing.Extension.Transform
You get the fragment Ids for a given dbId either from the selection event, as illustrated in the above samples, or by using enumNodeFragments:
var instanceTree = model.getData().instanceTree
var fragIds = []
instanceTree.enumNodeFragments(dbId, function(fragId){
fragIds.push(fragId)
})
// to change material or transform, need to iterate all
// fragments of a given dbId and apply same material/transform
fragIds.forEach(function(fragId) {
var renderProxy = viewer.impl.getRenderProxy(viewer.model, fragId)
var fragmentproxy = viewer.impl.getFragmentProxy(viewer.model, fragId)
})