Creating custom geometry on Forge Viewer - autodesk-forge

I am trying to include the snapshot for each issue which includes the highlighted issue with the complete plan, which I intend to use in a custom report. The files are in PDF format for the plan.
Now, the optimal workflow which I feel would be the following:
1) Create a custom geometry (circle) at the issue location.
2) Take a snapshot of the viewer and save it at a location
I have been trying the below code for step 1:
var pushpinAttributes = issue.attributes.pushpin_attributes;
var viewer = viewerApp.getCurrentViewer();
var geom = new THREE.SphereGeometry(0.05, 32, 32);
var material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
var sphereMesh = new THREE.Mesh(geom, material);
sphereMesh.position.set(pushpinAttributes.location.x, pushpinAttributes.location.y, pushpinAttributes.location.z);
viewer.impl.createOverlayScene ('overlay-scene', material);
viewer.impl.addOverlay('overlay-scene', sphereMesh);
viewer.impl.invalidate (true);
For step 2, I am using viewer.getScreenShot();
However, the geometry is created at a different location than that of the pushpin.
Where could I be going wrong?
Is there be a better way of doing the same?

Related

Transform point from DWG model coordinates to autodesk forge viewer coordinates (new WGS.LmvMatrix4 undefined)

I am trying to display a point on model displayed in the Autodesk forge viewer. However I am unable to figure out how to transform the point. I found the next question that seems to resolve this question:
Transform point from DWG model coordinates to autodesk forge viewer coordinates
When I try to use the function of this question:
var vpXform = viewer.model.getPageToModelTransform(viewportId).clone();
var invVpXform = new WGS.LmvMatrix4(true);
invVpXform.getInverse(vpXform, true);
var ptInCadX = ...;
var ptInCadY = ...;
var verticesInViewer = new THREE.Vector3().set(ptInCadX, ptInCadY, 0).applyMatrix4(invVpXform);
It shows the next error: Uncaught ReferenceError: WGS is not defined
And if I try it without the WGS.LmvMatrix4, it shows the following error: Uncaught ReferenceError: new LmvMatrix4 is not defined
I'm using the latest version of the Forge Viewer, v7.Can someone help me finding what am I doing wrong?
Thank you very much.
Here's an updated version of the snippet:
// Start by getting a list of all viewports in your model
let viewports = viewer.model.getData().viewports;
// Choose a viewport you want to use for the design-to-viewer coordinate mapping (it must include a `transform` property);
// in my case I'll just pick the first one
let viewport = viewports[0];
// Load the viewport transform into a three.js matrix
let xform = new THREE.Matrix4().fromArray(viewport.transform);
// Compute the inverse transxform
let inverse = new THREE.Matrix4().getInverse(xform, true);
// Use the inverse transform to convert design coordinates to paper coordinates
let someX = 0, someY = 0;
let viewerCoords = new THREE.Vector3().set(someX, someY, 0).applyMatrix4(inverse);

Embedding MS-Office Documents Into AutoCAD Drawings Using Design Automation

I have a need to embed MS-Office documents (Excel, Word) into AutoCAD using Design Automation. Searching around the web, it seems that this is not possible because the MS-Office applications, which would act as an OLE Client, would need to be running on the Forge Server. Could someone confirm that this is the case?
If I am correct in my above statement, my next best alternative would be to embed .EMF files created from each page of the document I want to embed; alternatively using raster images would also be acceptable. Creating the .EMF or raster files is not a problem. I just can't find a solution for embedding the file that does not involve copying them to the clipboard and using the PASTECLIP command. This approach has worked for me in the AutoCAD application using a C# AutoCAD.NET plugin, an OLE2Frame object is created, but it fails in accoreconsole (because PASTECLIP uses a UI class which is not available). This leads me to think that the same would occur while running the bundle in Design Automation.
The best I have been able achieve so far is to write a raster image files to the working directory and linking the raster images to the AutoCAD document using RasterImageDef and RasterImage (code below). Is this the only way I can do this? Can I do something similar using an EMF image, which is vector based, instead of a raster image? Or is there a way to actually embed an EMF (preferred) or raster image instead of just linking the images?
The code below fails if I use .EMF files, because RasterImageDef and RasterImage do not support the the EMF file; the EMF file being a vector format, not a raster format?
[CommandMethod("TEST")]
public void Test()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
// Get the file name of the image using the editor to prompt for the file name
// Create the prompt
PromptOpenFileOptions options = new PromptOpenFileOptions("Enter Sequence file path");
options.PreferCommandLine = true;
// Get the file name, use no quotes
PromptFileNameResult result = null;
try { result = ed.GetFileNameForOpen(options); }
catch (System.Exception ex)
{
DisplayLogMessage($"Could not get sequence file location. Exception: {ex.Message}.", ed);
return;
}
// Get the rtf filename from the results
string filename = result.StringResult;
DisplayLogMessage($"Got sequence filename: {filename}", ed);
// Load the Sequence.rtf document
Aspose.Words.Document seq;
using (FileStream st = new FileStream(filename, FileMode.Open))
{
seq = new Aspose.Words.Document(st);
st.Close();
}
DisplayLogMessage($"Aspose.Words Loaded: {filename}", ed);
Transaction trans = db.TransactionManager.StartTransaction();
// Get or create the image dictionary
ObjectId imageDictId = RasterImageDef.GetImageDictionary(db);
if (imageDictId != null)
imageDictId = RasterImageDef.CreateImageDictionary(db);
// Open the Image Dictonary
DBDictionary imageDict = (DBDictionary)trans.GetObject(imageDictId, OpenMode.ForRead);
double x = 0.0;
double y = 0.0;
try
{
// For each page in the Sequence.
for (int i = 0; i < seq.PageCount; i++)
{
DisplayLogMessage($"Starting page {i + 1}", ed);
// extract the page.
Aspose.Words.Document newSeq = seq.ExtractPages(i, 1);
Aspose.Words.Saving.ImageSaveOptions imgOptions = new Aspose.Words.Saving.ImageSaveOptions(Aspose.Words.SaveFormat.Emf);
imgOptions.Resolution = 300;
DisplayLogMessage($"Extracted page {i + 1}", ed);
string dictName = Guid.NewGuid().ToString();
filename = Path.Combine(Path.GetDirectoryName(doc.Name), dictName + ".Emf");
// Save the image
SaveOutputParameters sp = newSeq.Save(filename, imgOptions);
DisplayLogMessage($"Saved {dictName}.Emf", ed);
RasterImageDef imageDef = null;
ObjectId imageDefId;
// see if my guid is in there
if (imageDict.Contains(dictName))
imageDefId = (ObjectId)imageDict.GetAt(dictName);
else
{
// Create an image def
imageDef = new RasterImageDef();
imageDef.SourceFileName = $"./{dictName}.Emf";
// load the image
imageDef.Load();
imageDict.UpgradeOpen();
imageDefId = imageDict.SetAt(dictName, imageDef);
trans.AddNewlyCreatedDBObject(imageDef, true);
}
// create raster image to reference the definition
RasterImage image = new RasterImage();
image.ImageDefId = imageDefId;
// Prepare orientation
Vector3d uCorner = new Vector3d(8.5, 0, 0);
Vector3d vOnPlane = new Vector3d(0, 11, 0);
Point3d ptInsert = new Point3d(x, y, 0);
x += 8.5;
CoordinateSystem3d coordinateSystem = new CoordinateSystem3d(ptInsert, uCorner, vOnPlane);
image.Orientation = coordinateSystem;
// some other stuff
image.ImageTransparency = true;
image.ShowImage = true;
// Add the image to ModelSpace
BlockTable bt = (BlockTable)trans.GetObject(db.BlockTableId, OpenMode.ForRead);
BlockTableRecord btr = (BlockTableRecord)trans.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
btr.AppendEntity(image);
trans.AddNewlyCreatedDBObject(image, true);
// Create a reactor between the RasterImage
// and the RasterImageDef to avoid the "Unreferenced"
// warning the XRef palette
RasterImage.EnableReactors(true); // in the original was true
image.AssociateRasterDef(imageDef);
}
trans.Commit();
}
catch (System.Exception ex)
{
DisplayLogMessage("ERROR: " + ex.Message,ed);
trans.Abort();
}
}
Raster images are always linked. There's no way to embed them. The only way to embed an image is to use AcDbOle2Frame (C++) or Autodesk.AutoCAD.DatabaseServices.Ole2Frame (C#). In theory, it is possible to create these objects without the "OLE server" being present but I haven't tried so I don't know if enough APIs are exposed to make it happen.
You should try it and see how far you can get.
Albert
There is way to embed raster image, it is not straightforeward, you need to use C++\ObjectARX API, please refer this https://github.com/MadhukarMoogala/EmbedRasterImage/tree/EmbedRasterImageUsingDBX

Save and retrive in forge viewer

I am using forge viewer for displaying AutoCAD files.
Also using the drawing tool over viewer based on the sample source.
I will draw the area by using box or sphere draw tools.
I need to save the current viewer including box or sphere area which I was marked over viewer and when again loading same file the area which has been marked that should be bind default.
How it is possible please help me
Suggest any way to implement this scenario.
Thanks in advance.
You can do that with 2 steps.
First, taking advantage of Object3D.toJSON() method.
Let's summarize in a sample where we generate a JSON object from our mesh:
//here we create a BoxGeometry
let geom = new THREE.BufferGeometry().fromGeometry(new THREE.BoxGeometry(100,100,100));
let phongMaterial = new THREE.MeshPhongMaterial({
color: new THREE.Color(1, 0, 0)
});
let mesh = new THREE.Mesh(geom, phongMaterial);
if (!viewer.overlays.hasScene("CustomScene")) {
viewer.overlays.addScene("CustomScene");
}
viewer.overlays.addMesh(mesh, "CustomScene");
viewer.impl.sceneUpdated(true);
//here we generate the JSON from the mesh and download it
let jsonObject = JSON.stringify(mesh.toJSON())
download(jsonObject, 'Box.json', 'text/plain');
download function can be found here.
The next step is about generating the box from the saved JSON.
For that, we'll use ObjectLoader.parse method.
And again, we can summarize in the code below:
//here we read the JSON object from our generated file
var request = new XMLHttpRequest();
request.open("GET", "js/Box.json", false);
request.send(null)
var my_JSON_object = JSON.parse(request.responseText);
//here we generate the mesh
let mesh = new THREE.ObjectLoader().parse(my_JSON_object);
if (!viewer.overlays.hasScene("CustomScene")) {
viewer.overlays.addScene("CustomScene");
}
viewer.overlays.addMesh(mesh, "CustomScene");
viewer.impl.sceneUpdated(true);
Refer here for the function to read objects from JSON file.

Forge Viewer Select in a multi-model context

We have extensions that currently leverage viewer.select() with a list of dbIds from the model.
Our customers would like to see secondary models in the same viewer, and we’re giving them the ability to load reference models after the first model has been loaded.
We’re running into a problem with multiple models, however, where the viewer is selecting from one of the models other than the first model loaded when we call viewer.select().
It seems like we may want to stop using viewer.select() but instead start using model.selector.select() after keeping a reference to the first model loaded. This would mean changing quite a bit of code.
Is there a way to set the context of viewer.select() so that it always uses the first model we load?
Before Forge Viewer v3.3, Viewer3D#select( dbIds, selectionType) didn't be exposed for the multi-model use case unfortunately. The 2nd argument of Viewer3D#select has been changed to Viewer3D#select( dbIds, model ). So, the below code snippets will changed to:
var scene = viewer.impl.modelQueue();
var models = scene.getModels();
var targetIndex = ...;
var targetModel = models[targetIndex];
var selectionType = ...;
// Method 1:
viewer.impl.selector.setSelection( dbIds, targetModel, selectionType );
// Method 2:
model.selector.select( dbIds, selectionType );
// Method 3: (After Forge Viewer v4)
viewer.select( dbIds, targetModel );
// Method 4: (After Forge Viewer v4)
var selections = [
{
model: targetModel,
ids: dbIds
}
];
viewer.impl.selector.setAggregateSelection( selections );
==== Update End ====
Unfortunately, Viewer3D#select didn't be exposed for the multi-model use case. However, there are few ways to select items via the API in multi-model environment:
var scene = viewer.impl.modelQueue();
var models = scene.getModels();
var targetIndex = ...;
var targetModel = models[targetIndex];
var selectionType = ...;
// Method 1:
viewer.impl.selector.setSelection( dbIds, targetModel, selectionType );
// Method 2:
model.selector.select( dbIds, selectionType );
// Method 3: (After Forge Viewer v4)
var selections = [
{
model: targetModel,
ids: dbIds
}
];
viewer.impl.selector.setAggregateSelection( selections );
Or, you can write your own Viewer class which extends Autodesk.Viewing.Viewer3D or Autodesk.Viewing.Private.GuiViewer3D to private a select function that supports passing model argument.

How to Retrieve Forge Viewer objectTree?

My goal is to highlight a room by adding new geometry to the viewer based on lines I have created in revit like they do here Link
but i can not figure out how to access those lines ids.
I know what they are in revit (element_id) but not how they are mapped as dbid.
Following this Blog Post
I want to access the objectTree in my extension to find out, but it always comes back as undefined.
var tree;
//old way - viewer is your viewer object - undefined
viewer.getObjectTree(function (objTree) {
tree = objTree;
});
//2.5 - undefined
var instanceTree = viewer.model.getData().instanceTree;
var rootId = this.rootId = instanceTree.getRootId();
//- undefined
var objectTree = viewer.getObjectTree();
Can anyone tell me if its still works for them I am using the v2 of the API for the rvt conversion to svf and 2.9 of the viewer3D.js
note I can see a list of dbid if I call this
var model = viewer.impl.model;
var data = model.getData();
var fragId2dbIdArray = data.fragments.fragId2dbId ;
but have no way of mapping back to the Revit element_id
As of version 2.9 this is still working. Here's my console:
Here's a couple of things you can try:
Is viewer undefined? Are you in the correct scope when grabbing the viewer?
The document have to be loaded before you can grab the instance tree. When the document is loaded, an event called Autodesk.Viewing.GEOMETRY_LOADED_EVENT will be fired, then you can start manipulating the instance tree.
Simply do this:
viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, function () {
var instanceTree = viewer.model.getData().instanceTree;
});
For more structured code, follow this guide to add an extension.
There's a more detailed blog post on which event to listen for. It's still using the old way to get instance tree, though.
Shiya Luo was correct the viewer had not yet finished loading the geometry
in my extentions Load function I added two event listeners and made sure they both fired before trying to access the instanceTree
viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, function () {
finishedGEOMETRY_LOADED_EVENT = true;
if(finishedGEOMETRY_LOADED_EVENT && finishedOBJECT_TREE_CREATED_EVENT ){
afterModelLoadEvents(viewer);
}
});
viewer.addEventListener(Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, function () {
finishedOBJECT_TREE_CREATED_EVENT = true;
if(finishedGEOMETRY_LOADED_EVENT && finishedOBJECT_TREE_CREATED_EVENT ){
afterModelLoadEvents(viewer);
}
});