ViewingApplication with multiple viewers - autodesk-forge

This is more of a "best practices" question. It seems to me that a ViewingApplication as outline in the docs here is directly tied to a div on the page. If that is true and I want to show both a 3d viewer and a 2d viewer, does that mean I need a seperate instance of the ViewingApplication for each div?

Yes you will need as many separate containers for your Viewer instances, just so you can initialise them separately and Viewer won’t mind if these containers share the same parent - that’s so long as a Viewer can operate exclusively in a distinct, direct parent.
See here for sample code accommodating multiple Viewers.
Alternatively if you are only after a quick fix to render several models in one div/canvas then the SplitScreen Extension probably fits the bill already:
var options = {
viewports: [
function(id) { return id === 1; },
function(id) { return id !== 1; }
]
};
viewer.loadExtension('Autodesk.SplitScreen', options);

Related

Check if Model has any default hidden elements in Forge's 3D Viewer

Some background:
I'm using Forge to visualize IFC models. Some of my uploaded models have IfcOpeningElements which seems to be hidden in Forge by default, at least when setting ghosting to false via viewer.setGhosting(false). I'm also having functionality to hide normal elements in the viewer (viewer.hide(dbIds, model)), and to show/hide all elements (model.setAllVisibility(show)).
The problem I'm having is that I want to be able to show/hide the IfcOpeningElements regardless of showing and hiding "normal" elements.
The approach I tried, which is not working very well is to call model.visibilityManager.getHiddenNodes(). The problem with this approach is that, even for models with IfcOpeningElements, getHiddenNodes only returns a non-empty array after ~15 seconds (probably varies with the size of the model). In the meantime, if the user does anything that makes the app call model.setAllVisibility(true), I'm no longer able to detect the original hidden IfcOpeningElements.
Furthermore, when calling getHiddenNodes after an arbitrary waiting period after the model has loaded, I'm not sure if it returns an empty array because the model is not "ready" to detect hidden elements from IfcOpeningElements or if the model simply does not have any IfcOpeningElements.
So, Is there any good way to detect if a model has any "default" hidden elements without having to wait long after the model is loaded? Or perhaps there is a way to call change the visibility of the entire model without changing the visibility of the IfcOpeningElements?
I'm aware of the possibility to listen to the HIDE_EVENT event, but since I'm not sure if it will fire at all (since I'm not sure if the model has any IfcOpeningElements), I cannot block the application from calling model.setAllVisibility(true) which in turn would make getHiddenNodes() return an empty array even if there was hidden elements to begin with.
How about just skipping loading IfcOpeningElements' geometries? To do so, pass skipHiddenFragments: true to the 3rd argument of Viewer3D#loadDocumentNode
viewer.loadDocumentNode(
doc,
viewable,
{
skipHiddenFragments: true
}
);
This approach will skip loading the IfcOpeningElements' meshes, but you can still see their properties while selecting IfcOpeningElements on the model structure panel. On the other hand, you cannot access their bounding boxes and geometries with this approach.
Regarding how to check default hidden elements, try to call this code after all geometries are loaded. The hiddenDbIds are elements hidden by default.
let model = viewer.getAllModels()[0]; //!<< Check the first model just for demo
let fragList = model.getFragmentList();
let hiddenDbIds = Object.keys( fragList.vizflags ).filter(fragId => !fragList.isFragVisible( fragId )).map(fragId => fragList.getDbIds( fragId ) );
// hiddenDbIds.forEach(dbId => viewer.getProperties(dbId, console.log))
Note. The visible flag will also be changed after changing the objects' visibility. So, ensure that run the above before changing the visibilities.

load 2D & 3D forge viewers in single web page

I would like to link between elements from the 2D sheet and 3D model, so when I select the element from 2D it should reflect and select (isolate) in the 3D also if I change the color it does the same on both e.g. and the other way around.
so I can use the document browser extensions to open the 2d sheet on 1st viewer and the 3d model on the 2nd viewer:
const firstModel = new Autodesk.Viewing.Private.GuiViewer3D(document.getElementById('MyViewerDiv1'));
const secondModel = new Autodesk.Viewing.Private.GuiViewer3D(document.getElementById('MyViewerDiv2'));
Autodesk.Viewing.Initializer(options1, function() {
viewer1.start();
viewer1.load(...);
});
Autodesk.Viewing.Initializer(options2, function() {
viewer2.start();
viewer2.load(...);
});
if the example above is correct I am still missing how to links both viewers.
I hope someone could help me with this issue
Note that we have a viewer extension that might already give you what you're looking for: https://github.com/Autodesk-Forge/forge-extensions/blob/master/public/extensions/NestedViewerExtension/README.md.
If you want to implement the cross-selection between two viewer instances yourself, you can. Just subscribe to the SELECTION_CHANGED event in one of the viewers, get the selected IDs, and select the same IDs in the other viewer using the usual viewer.select([...]); method.
Btw. regarding your code snippet:
the Autodesk.Viewing.Initializer only needs to be called once per the entire webpage
the Autodesk.Viewing.Private.GuiViewer3D instances should be created after the initializer has done its work

How to load two forge viewers in single web page

I am trying to add two forge viewers in a single web page. I am using "react-forge-viewer" npm package to do that, but for some reason, only one viewer gets loaded and another one stays at "starting viewer..." state. It would be really great if anyone could help me resolve this problem.
I think this might be because of the dependency on class names or ID's.
Also, I want to synch their events in such a way that if I change camera view by dragging on on viewer same thing should reflect on another viewer as well.
You can easily fit multiple Viewers together, just be sure to initialize them separately with distinct container divs:
var viewer1 = new Autodesk.Viewing.Private.GuiViewer3D(document.getElementById('MyViewerDiv1'));
var viewer2 = new Autodesk.Viewing.Private.GuiViewer3D(document.getElementById('MyViewerDiv2'));
Autodesk.Viewing.Initializer(options1, function() {
viewer1.start();
viewer1.load(...);
});
Autodesk.Viewing.Initializer(options2, function() {
viewer2.start();
viewer2.load(...);
});
And you can sync the states of two viewers by subscribing to the CAMERA_CHANGE_EVENT, but beware pitfalls like endless loops - you can overcome this by implementing critical blocks to ensure only one viewer is exclusively requesting the other viewer to sync at any given time:
viewer1.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, function()
{
//critical block to prevent endless chained loop of events
viewer2.restoreState(viewer1.getState())
});
viewer2.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, function()
{
//critical block to prevent endless chained loop of events
viewer1.restoreState(viewer2.getState())
});
See a working sample here.
P.S:react-forge-viewer is a third party package not authored by Autodesk as is noted in its own README so you might need to contact its author if you have issues with it.

HTML5 Canvas: Calculate the Mouseposition after Zooming and Translating

I try to develope an interactive viewer for vector drawings and want to have the feature of zooming.
The function for zooming works pretty good but now I have the problem to calculate the mouseposition for picking objects.
The event gives back the screen coordinates. The canvas doesn't have a methode to use the transformation matrix in the inverse way.
Does anyone have a solution to this problem?
I made a very small a simple class for keeping track of the transformation matrix.
I added an invert() function for reasons like this. I also made an invertPoint() function but didn't put it in the final version. It's not hard to deduce though, just invert and transform point together.
I often just calculate the appropraite transform with this class and then use setTransform, depending on the application.
I wish I could give you a more specific solution but without a code sample of what you want that'd be hard to do.
Here's the transformation class code. And here's a blog post with a bit of an explanation.
Here are some valuable functions for your library that preserve the matrix state and needed to build up a scene graph:
Transform.prototype.reset = function() {
this.m = [1,0,0,1,0,0];
this.stack = [];
};
Transform.prototype.push = function() {
this.stack.push(this.m.slice());
};
Transform.prototype.pop = function() {
this.m = this.stack.pop();
};

HTML5 Geolocation - Detect location within a specific area

I haven't had much of an opportunity to look at HTML5 Geolocation yet, but I'm curious: Is it possible to build a web app that can detect when a user enters a certain area (perimeter) and then return a message or something like that?
You can use watchPosition to get periodic updates of the browser's location, and then in your callback, test to see if the new position is within your area of interest. So if you've defined a function isInArea that checks a position to see if it's in your area of interest, you could do something like:
function positionCallback(position) {
if (isInArea(position)) {
alert("Honey, I'm home!");
}
}
function handleError(error) {
alert("Error!")
}
// Request repeated updates.
var watchId = navigator.geolocation.watchPosition(positionCallback, handleError);
Based on Example of requesting repeated position updates from w3c.