I'm using the Forge Viewer to display simple geometry extractions from buildings.
However, when loading them the orientation of the model/view cube is not matching the expected use case (see image).
Basically I would need to swap the "Front View" with the "Top View".
Is this possible to achieve such a thing through e.g. default settings on the viewer object?
My set up is basically identical to the one in this 3rd-party react wrapper of the Forge Viewer: https://github.com/outer-labs/react-forge-viewer
Thank you already very much.
Daniel
EDIT: The model is in STP format
Basically, you can archive this with following steps via Viewer APIs after the model is loaded completely and can be separated into two parts.
(Preproccess) Get the Front view state of the Viewer that your want to make it as the Top:
a. Orient the current view to Front view: viewer.setViewCube( 'front' ).
b. Obtain current view statue of the viewport: var viewState = .getState( { viewport: true } ).
c. Save this viewState to somewhere, your js file or database.
Restore view state and set it as the Top view:
a. Obtain the viewState from somewhere (e.g. js file or database) that you got from the step1.
b. Restore view state via viewer.restoreState( viewState ).
c. Set the current view as Top view: viewer.autocam.setCurrentViewAsTop().
d. Set the current view as Home to avoid the state of the viewcube to be reset: viewer.autocam.setCurrentViewAsHome().
The code snippet for step2:
viewer.addEventListener(
Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
function( event ) {
console.log( '%cGEOMETRY_LOADED_EVENT: !!!Geometries loaded!!!', 'color: green;' );
setTimeout(() => {
const onOrientTopViewCompleted = function() {
viewer.removeEventListener(
Autodesk.Viewing.CAMERA_TRANSITION_COMPLETED,
onOrientTopViewCompleted
);
viewer.autocam.setCurrentViewAsTop();
viewer.autocam.setCurrentViewAsHome();
console.log( 'CAMERA_TRANSITION_COMPLETED' );
};
viewer.addEventListener(
Autodesk.Viewing.CAMERA_TRANSITION_COMPLETED,
onOrientTopViewCompleted
);
var viewState = '....'; //!<< the view state of the original `Front` view.
viewer.restoreState( viewState )
}, 1000);
});
Hope it helps!
Related
I am trying to load multiple models(aggregation) on to the forge viewer(v.6.x) initially while loading, but I want to show only one model and other models as hidden by default(can be with ghost view). Later when user clicks on eye icon from model browser, then that model should be visible/hidden.
I tried calling viewer.hideModel(modelId) after loading the model. But even though the model is showing in model browser, when I click on it, it says error model is not loaded.
var showModel = false;
this.viewer.loadModel(url, {globalOffset: { x:0, y:0, z:0 }, modelNameOverride: modelName}, () =>
{
this.isModelLoaded = true;
this.viewer.caller = this;
this.addEventListenersToViewer();
if(!showModel){
this.viewer.hideModel(modelId); // This is to hide the model by default after loading.
}
},
errorMsg => {
this.isModelLoaded = false;
this.viewer.container.style.opacity = 0;
this.modelLoadError(this.fetchTranslationByKey('getModelError'));
}
);
Expected behavior is to allow user to select from model browser, which models to show/hide on the viewer among all the models loaded initially(linked models should be hidden by default).
Current result I am getting is the linked models are showing in the model browser but when I click on that it says - Error Model is not loaded
A few issues here:
When using arrow function the context that this points to is different so be careful with that
how did you obtain the modelId? Instead of using arbitrary counter better to iterate the model array via Viewer.impl.modelQueue()
I am unable to replicate the model not loaded error with model browser. Can you provide more details or a live sample (jsfiddle/jsbin)?
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.
I want to be able to show a model in the forge viewer but before the model shows I need to change the materials from its defaults. Currently I register the Autodesk.Viewing.GEOMETRY_LOADED_EVENT and when I receive the event I hide the model, change the materials and then show the model. This works except that the default model displays in the viewer for a second or two before I can hide it. How can I prevent that from happening?
After initializing the viewer here is code that loads the document. If I try and hide the model as indicated in the commented code below the viewer returns this error. I have to wait for the geometry loaded event before I can hide it.
wgs.js?v=v3.3:17876 Uncaught TypeError: Cannot read property 'getGeometryList' of undefined
at RenderScene.getGeometryList (wgs.js?v=v3.3:17876)
at Viewer3DImpl.onLoadComplete (viewer3D.js?v=v3.3:32002)
Here is the code I'm, running:
Autodesk.Viewing.Document.load(
documentId, (doc) => {
var geometryItems = Autodesk.Viewing.Document.getSubItemsWithProperties(doc.getRootItem(), { 'type': 'geometry' }, true);
if (geometryItems.length > 0) {
viewer.load(doc.getViewablePath(geometryItems[0]), null, (model) => {
// Document loaded
// Can't hide model here, viewer returns an error because geometry
// is not loaded
//viewer.hideModel(model.id);
resolve(model.id);
}); // show 1st view on this document...
}
},
function (errorMsg) { // onErrorCallback
console.log('Load Document returned error message: ' + errorMsg);
}
)
I would simply show a pre-canned jpg image preview in place of the forge canvas.
ie.
1. Hide the forge canvas with style 'display:none'
2. Show your preview-jpg in place of the forge canvas
3. wait for TEXTURES_LOADED_EVENT event, like this...
https://github.com/wallabyway/forge-pdf-report/blob/6babb6d7332b6cdb983f57e7d140ff59a5136705/docs/index.html#L44
Hide the forge canvas with style 'display:block'
Hide your preview-jpg.
I`m trying to insert a custom link to a special page in VisualEditor toolbar. See the image below.
See Image
I googled a lot but without success. Someone please give a path...
My answer is based on the following resources:
MediaWiki core JS doc (ooui-js)
VisualEditor JS doc (+ reading code of both repositories used for VE, mediawiki/extension/VisualEditor and VisualEditor)
Also, I'm pretty sure, that there is no documented way of adding a tool to the toolbar in VE, as far as I know. Although it's possible to add a tool to a group, which is already added, mostly used for the "Insert" tool group, like in Syntaxhighlight_GeSHi). There is, probably, a much easier or "better" way of doing this :)
First, VisualEditor provides a way to load additional modules (called plugins) when the main part of VE loads (mostly, when you click the "Edit" button). The modules needs to be registered via the global variable $wgVisualEditorPluginModules (or the equivalent in extension.json, if you're using the new extension registration). In your extension registration file, you should initialize a module (with your required script files to add the tool) and add it as a VE plugin.
Example PHP (old extension registration via PHP files):
// other setup...
$wgResourceModules['ext.extName.visualeditor'] = array(
'localBasePath' => __DIR__,
'remoteExtPath' => 'extName'
'dependencies' => array(
'ext.visualEditor.mwcore',
),
'scripts' => array(
'javascripts/ve.ui.ExtNameTool.js',
),
'messages' => array(
'extname-ve-toolname',
),
);
$wgVisualEditorPluginModules[] = 'ext.extName.visualeditor';
// other setup...
extension.json (new JSON-based extension registration):
// other setup...
"ResourceModules": {
"ext.geshi.visualEditor": {
"scripts": [
"javascripts/ve.ui.ExtNameTool.js"
],
"dependencies": [
"ext.visualEditor.mwcore"
],
"messages": [
"extname-ve-toolname"
]
}
},
"VisualEditorPluginModules": [
"ext.extName.visualeditor"
],
// other setup...
Now, if VE starts, it will load your module, named ext.extName.visualeditor in this example, with the script ve.ui.ExtNameTool.js. In this script, you can now do, what ever you want. As an example, this is a way to add a new module to the end of the toolgroup list in the toolbar:
Example of ve.ui.ExtNameTool.js:
( function () {
// create a new class, which will inherit ve.ui.Tool,
// which represents one tool
ve.ui.extNameTool = function extNameTool( toolGroup, config ) {
// parent constructor
ve.ui.extNameTool.super.apply( this, arguments );
// the tool should be enabled by default, enable it
this.setDisabled( false );
}
// inherit ve.ui.Tool
OO.inheritClass( ve.ui.extNameTool, ve.ui.Tool );
// every tool needs at least a name, or an icon
// (with the static property icon)
ve.ui.extNameTool.static.name = 'extname';
// don't add the tool to a named group automatically
ve.ui.extNameTool.static.autoAddToGroup = false;
// prevent this tool to be added to a catch-all group (*),
although this tool isn't added to a group
ve.ui.extNameTool.static.autoAddToCatchall = false;
// the title of the group (it's a message key,
// which should be added to the extensions i18n
// en.json file to be translateable)
// can be a string, too
ve.ui.extNameTool.static.title =
OO.ui.deferMsg( 'extname-ve-toolname' );
// onSelect is the handler for a click on the tool
ve.ui.extNameTool.prototype.onSelect = function () {
// show an alert box only, but you can do anything
alert( 'Hello' );
this.setActive( false );
}
// needs to be overwritten, but does nothing so far
ve.ui.extNameTool.prototype.onUpdateState = function () {
ve.ui.extNameTool.super.prototype.onUpdateState.apply( this, arguments );
}
// the tool needs to be registered to the toolFactory
// of the toolbar to be reachable with the given name
ve.ui.toolFactory.register( ve.ui.extNameTool );
// add this tool to the toolbar
ve.init.mw.Target.static.toolbarGroups.push( {
// this will create a new toolgroup with the tools
// named in this include directive. The naem is the name given
// in the static property of the tool
include: [ 'extname' ]
} );
} )();
After installing the extension in your LocalSettings.php and starting VE, you should see a new tool in the toolbar with the given name. Clicking it will show an alert box with content "Hello". Like written in the inline comments: In the click handler (onSelect) you can do whatever you want, e.g. open a link in a new tab, e.g. to a Special page. To get the link to a special page I would suggest to use mw.Title to get a localized namespace. For example:
var relativeUrl = mw.Title.newFromText( 'RecentChanges', -1 ).getUrl();
The first parameter of mw.Title.newFromText() is the name of the page, the second parameter is the ID of the namespace (-1 is the default for special pages and should always work).
I hope that helps!
I am not sure I understand your question entirely. It is as simple as selecting some text, clicking the chain icon, then clicking the External Link tab and pasting your link there.
I'm using a kendo UI tree with a remote data source from a JSON file.
I have a button on the tree page which gets the current data of the tree,sends it through a POST to a server and the server saves the current data to the JSON file so as the next time I reload the page,the changes I made will be kept.That's what I want to happen.
So I know the current data of the tree is in:
$("#treeview").data("kendoTreeView").dataSource.data()
Which means the data changes real time in there for example when someone drag and drops a node of the tree.
My problem starts when this data doesn't seem to change when I drag and drop nodes inside the tree,and only changes when I drag and drop a node on the root level of the tree and even then it doesn't do it correcly as the node should be moved in there as well but instead the node gets copied,leaving the past node in the tree as well...
For Example I have this tree:
If I make a drag and drop change like this:
And I send the data,save it and reload the change isn't made at all!
PS:Even when I view the current data after the change before sending it,I see that there is no change on the data at all even though I did a change visualy with a drag and drop.So it doesn't have to do with the sending,saving and the server.
On the other hand,if I make a change like this:
I can see in the current data that the moved node is added in the end of the data indeed but it is not deleted from it's initial position within the data!So if i send the current data to the server,save it and then refresh I get the result:
The code for viewing and sending the data is:
function sendData() {
var req = createRequest();
var putUrl = "rest/hello/treeData";
req.open("post", putUrl, true);
req.setRequestHeader("Content-type","application/json");
var dsdata = $("#treeview").data("kendoTreeView").dataSource.data();
alert(JSON.stringify(dsdata));
req.send(JSON.stringify(dsdata));
req.onreadystatechange = function() {
if (req.readyState != 4) {
return;
}
if (req.status != 200) {
alert("Error: " + req.status);
return;
}
alert("Sent Data Status: " + req.responseText);
}
}
Is this a Bug or am I doing something wrong?Has anyone been able to see the current data changing correctly on every drag and drop?
First and most important you have to use the latest version of KendoUI (Kendo UI Beta v2012.3.1024) still in beta but is where they have solved many problems.
Then, when you create the kendoTreeView you have to say something like:
tree = $("#treeview").kendoTreeView({
dataSource :kendo.observableHierarchy(data),
dragAndDrop:true
}).data("kendoTreeView");
Here the important is not using directly data array but wrapping it with kendo.observableHierarchy.
Then you will have the data updated with the result of drag & drops.
For me in addition to OnaBai answer I had to use the sync function on the save method. I am using Type Script.
this.treeData = new kendo.data.HierarchicalDataSource({
data: kendo.observableHierarchy([]),//Thanks OnaBai
schema: {
model: {
id: "MenuItemId",
children: "MenuItemChildren",
hasChildren: (e) => {
//this removes arrow next to items that do not have children.
return e.MenuItemChildren && e.MenuItemChildren.length > 0;
}
}
}
});
public save() {
this.treeData.sync().done(() => {
console.log("sync data");
var myType = this.treeData.view();
this.$http.post("/api/TreeViewPicker", myType)
.then((response) => {
});
});
}