How can I filter only on elements in the Autodesk Forge Viewer API that are relevant for quantity take off? - autodesk-forge

For BIM model quantity take off I need to filter on all elements that are relevant for quantity take off. Below are some visual examples of the element I need (green circled) and which elements I don't need (red cross)
Example 1
Example 2
How can I filter only on elements in the Autodesk Forge API that are relevant for quantity take off?
When I use the API I get all levels and I didn't manage to get the level I need.

First of all, you'll need to define the logic to filter these elements.
From the images you've shared, the hierarchy tree of your model seems to be organized in a way that you're interested in the nodes in the 5th level from the root node.
So, if that's the case, you could use something like the snippet below to find the nodes' paths and then filter it to retrieve the 5th-level nodes' dbids:
var findfifthNodes = function (model) {
return new Promise(function (resolve, reject) {
model.getObjectTree(function (tree) {
let nodes = {};
tree.enumNodeChildren(tree.getRootId(), function (dbid) {
let nodepath = [dbid];
if(!!nodes[tree.getNodeParentId(dbid)]){
nodepath = nodepath.concat(nodes[tree.getNodeParentId(dbid)]);
}
nodes[dbid] = nodepath;
}, true /* recursively enumerate children's children as well */);
let fifthNodes = Object.values(nodes).filter(p => p.length == 5).map(a => a[0]);
resolve(fifthNodes);
}, reject);
});
}
You can also add your own custom tree view that might be more suitable to your workflow.
To achieve that, please refer to https://aps.autodesk.com/blog/custom-tree-views

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 )

Set instanceTree to a custom node in Forge 3D viewer

Lets say I'm working with a 3D file which is the combination of one Architectural model and one Structural model.
The instance tree or Model Browser looks like this
root/
Arch/
Level 01/
Level 02/
...
Str/
Level 01/
Level 02/
...
I want to display only the Level 01.
So I:
Followed the steps in the Viewer tutorial
Add an event listener to both Autodesk.Viewing.GEOMETRY_LOADED_EVENT & Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT
When the 2 are fired, I use the code in this article to display only the Level 01 without ghosting.
I have 2 problem with this approach
I have to wait until the entire model is loaded before I can filter the level
After filtering the level, if I click on Model Browser, I can still see the entire model structure (but with everything as hidden except Level 01). How can I set the instance tree to only have what's below?
root/
Arch/
Level 01/
Str/
Level 01/
EDIT
At what point am I supposed to override the shouldInclude() function?
I've tried this and put a breakpoint but it seems it never gets called... I also tried to move it around but in vain.
const start = Date.now();
Autodesk.Viewing.UI.ModelStructurePanel.shouldInclude = (node) => {
Logger.log(node);
return true;
};
Autodesk.Viewing.Initializer(options, () => {
Logger.log(`Viewer initialized in ${Date.now() - start}ms`);
const config = {};
// prettier-ignore
Autodesk.Viewing.theExtensionManager.registerExtension('MyAwesomeExtension', MyAwesomeExtension);
viewerApp = new Autodesk.Viewing.ViewingApplication('MyViewerDiv');
viewerApp.registerViewer(viewerApp.k3D, Autodesk.Viewing.Private.GuiViewer3D, config);
loadDocumentStart = Date.now();
// prettier-ignore
viewerApp.loadDocument(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
});
Regarding #1: the object tree is stored in the file's internal database which - for performance reasons - is only loaded after the actual geometry.
Regarding #2: you can subclass the ModelStructurePanel class and add your own behavior, for example, by overriding the ModelStructurePanel#shouldInclude method.
Since I wasn't able to understand how to use ModelStructurePanel, I overrode Autodesk.Viewing.ViewingApplication.selectItem to only modify the options which are either passed to loadDocumentNode or startWithDocumentNode as below:
const options = {
ids: leafIDs.length > 0 ? leafIDs : null, // changed this line
acmSessionId: this.myDocument.acmSessionId,
loadOptions,
useConsolidation: this.options.useConsolidation,
consolidationMemoryLimit: this.options.consolidationMemoryLimit || 100 * 1024 * 1024, // 100 MB
};
With leafIDs being an array of objectIDs to display. I was able to build it by:
querying the ModelDerivativeAPI using GET :urn/metadata/:guid
going through the tree to find the ids which I am interested in.
There's probably a more elegant way to do this but that's the best I could do so far.

Immutable.js without redux, how do I create new lists without explicitly naming them?

---Background---
I'm trying to learn current best data management practices and as far as I can tell that means stateless / stateful components and immutable data structure. I'm having problems with implementing latter (immutables). I'm trying to incorporate it into angular 2 without redux. Redux is on my list of things to learn but for now I want to use immutable.js without redux.
---The problem---
How do I create a copy of an array in a service and return it on demand? I have this example code (just for illustration purposes, I haven't tested it!):
import { Product } from './product';
import { Immutable } from './immutable';
export class ProductListService {
let id = 0;
const cheese = new Product(id++, 'cheese');
const ham = new Product(id++, 'ham');
const milk = new Product(id++, 'milk');
// I fill the list with some sample data
let oldProductList = Immutable.List.of(cheese, ham, milk);
let newProductList = [];
let returnProductList = oldProductList;
getProductList() {
return returnProductList;
}
addProduct() {
// As far as I know, this creates a deep immutable copy
newProductList = oldProductList.withMutations(function (list) {
list.push(new Product(id++, 'name'););
});
returnProductList = newProductList;
oldProductList = newProductList;
}
}
The above code is based on the example from the official docs where they just add a number to the variable each time they create a copy (I understand that is only for example purposes?). How do I go about creating new lists without using numbers? Do I use oldList / newList? Do I dynamically create new numbers for new variables so that I have a history of objects?
I feel I'm doing something wrong on a architectural level here. What is the correct approach? All immutable.js examples are using redux or show no real-life example, does someone know of a good material to learn about immutalbe.js (+ possible ng2?)
Thanks
Not sure i fully understand what you want to do here,
but consider this:if you just want to push one element to the list you should not use withMutations.
let list1 = Immutable.List(['one'])
let list2 = list1.push('two')
console.log(list1.toJS()) // ['one']
console.log(list2.toJS()) // ['one', 'two']
Applying a mutation to create a new immutable object results in some overhead, which can add up to a minor performance penalty. use withMutations only If you need to apply a series of mutations locally before returning
let list1 = Immutable.List(['one'])
var list2 = list1.withMutations(function (list) {
list.push('two').push('three').push('four').push('five');
});
console.log(list1.toJS()) //["one"]
console.log(list2.toJS()) //["one", "two", "three", "four", "five"]
here we create a temporary mutable (transient) copy of list 1 and apply a batch of mutations in a performant manner by using withMutations
I hope that answers your question