I have been developing a POC application to load a PDF into the Autodesk GUIViewer3D
, and using a custom extension, draw PolygonPath
s and PolylinePath
s over the PDF
. My extension is simple, it adds a toolbar and manages the active tools, so that only one Edit2d
tool is active at a time. The toolbar buttons I have added essentially toggle or switch between them.
I have the measurement extension loaded and can set the calibration, this carries over to the Edit2d Extension
and allows me to have the correct values when getting one of the drawn shapes, and doing getArea()
with the measureTransform
etc.
Todo: add the code to get calibrated area of 2d shape via measure transform
I want to add a new toolbar button to my extension that enables the following functionality:
When the viewer selection event is fired, giving me the dbid
of a my selected line in the PDF
, I retrieve the raw coords / vector data of that line and draw a Polyline
shape programmatically using those coords.
Note that this is a rough idea of the setup. In reality I have the viewer bound in a class called ApsViewerManager
and have an instance of that assigned to the window object. The ApsViewerManager
supports loading from a PDF
that is downloaded from another api, or loading from the APS OSS API
. I will try strip things down to the bare minimum of what is needed for the “local” PDF
loading.
My setup for loading a pdf into the viewer:
const url = "some-url" // in reality this is pointing to a get endpoint on an external api that returns the pdf content
const page = 1 // you can only load 1 pdf page at a time
const localManager = this; // this is an instance of the APSViewerManager
const options = {
accessToken: "",
useCredentials: false
env: 'Local'
}
await Autodesk.Viewing.Initializer(options, async function () {
const htmlDiv = document.querySelector(localManager.containerSelector);
localManager.viewer = new Autodesk.Viewing.GuiViewer3D(htmlDiv);
attachViewerEvents.call(localManager); // I have my events handling setup in a separate module called as if part of the APSViewerManager
const startedCode = localManager.viewer.start(null, options);
if (startedCode > 0) {
localManager.report_error('Failed to create Viewer', {startCode: startedCode});
return;
}
// setup the edit2d extension from 'Autodesk.Edit2d'
localManager.edit2d = await localManager.viewer.loadExtension('Autodesk.Edit2D', {enableArcs: true});
// setup the inhouse extension to add edit2d controls as toolbar buttons + inhouse functionality consuming edit2d
localManager.rlb2dTakeOff = await localManager.viewer.loadExtension(
'RLB2DTakeOffExtension',
{
manager: localManager,
filename: localManager.shapesFilename,
}
);
// setup for loading the pdf extension from 'Autodesk.PDF'
await localManager.viewer.loadExtension('Autodesk.PDF')
// URL parameter page will override value passed to loadModel
localManager.viewer.loadModel(url, {page: page});
});
The challenge is, While I have the dbId
from the selection event, I am unable to query the property database using this Id.
const dbid = 2900 //the dbid for a wall I selected on the pdf - obtained from viewer selection event
const db = apsViewerManager.viewer.model.getPropertyDb();
db.getProperties2(
dbid,
function(x){
console.log("Promise resolved")
console.dir(x)
},
function(x){
console.log("Promise rejected")
console.dir(x)
}
)
The promise rejects, and I am left with the following object:
{
"err": undefined,
"instanceTree": null,
"maxTreeDepth": 0
}
I figured maybe my db was fragmented – I am not entirely sure what this means, but I do see properties in the db object full of “fragments”.
const frags = apsViewerManager.viewer.model.getFragmentList();
console.dir(frags);
result:

I wanted to try the following script to retrieve the props from there:
I am not sure the exact source of this script combination but I see it was derived from this APS blog: https://aps.autodesk.com/blog/working-2d-and-3d-scenes-and-geometry-forge-viewer
viewer = apsViewerManager.viewer;
viewer.addEventListener(
Autodesk.Viewing.SELECTION_CHANGED_EVENT,
function () {
const tree = viewer.model.getInstanceTree();
const frags = viewer.model.getFragmentList();
function listFragmentProperties(fragId) {
console.log('Fragment ID:', fragId);
// Get IDs of all objects linked to this fragment
const objectIds = frags.getDbIds(fragId);
console.log('Linked object IDs:', objectIds);
// Get the fragment's world matrix
let matrix = new THREE.Matrix4();
frags.getWorldMatrix(fragId, matrix);
console.log('World matrix:', JSON.stringify(matrix));
// Get the fragment's world bounds
let bbox = new THREE.Box3();
frags.getWorldBounds(fragId, bbox);
console.log('World bounds:', JSON.stringify(bbox));
}
if (tree) { // Could be null if the tree hasn't been loaded yet
const selectedIds = viewer.getSelection();
for (const dbId of selectedIds) {
const fragIds = [];
tree.enumNodeFragments(
dbId,
listFragmentProperties,
false
);
}
}
else {
console.log("No tree!");
}
}
);
Unfortunately, the tree is always null, so my browser spits out “No tree!”
What approaches / hidden documentation secrets can I look at to progress further?
I had seen a thread somewhere stating that PDFs are always converted to Vectors, hence my ambition of getting some coords of the selected wall (line) in the PDF.