11 Aug 2023

Mimicking Viewer Fit to view without properties database

fit to view

Introduction

In specific cases where we need to handle large models, we can use different strategies such as splitting the models through their disciplines or levels and loading only what's necessary.

This last option has been addressed previously in the Minimizing Viewer Workloads blog. In this blog, one of the options shared is skipping the properties database (using skipPropertyDb: true in load options). The problem when we do that is that we can't rely anymore on element properties or the Instance Tree and functions that take advantage of these data, such as fit to view and element selection.

So what can we do if we need to skip the properties database but still need to use fit to view?

That's what we'll cover in this blog post.

The approach

To take advantage of a design element geometry, we need to understand the correlation between objects and fragments:

  • fragments - individual meshes with properties like transform matrices, bounding boxes, or materials
  • objects - logical, selectable entities that can have properties attached to them

I encourage you to go through this blog post that covers this in details.

Now that we know the principles, we can cover the solution workflow:

We'll have to "recreate" the selection tool, find the fragments of the selected element, and adjust the view.

Handling selection

To handle the selection, we can basically listen to the mouse click by the user, and based on the clicked position, use the clientToWorld method to retrieve the selected element, just like in the snippet below:

let result = this.viewer.clientToWorld(event.canvasX, event.canvasY, true);

In the result, we can find the dbId of the element selected, and with that, it is time to find this element's fragments.

Finding the fragments

To find the fragments associated with the selected element, we can first filter all the fragments ids available based on the dbId of the selected element, just like in the snippet below:

async getFragsIdsFromdbId(dbId) {
  const fragsIds = this.fragList.fragments.fragId2dbId.map((id, fragId) => id == dbId ? fragId : -1).filter(i => i > -1);
  return fragsIds;
}

And with these fragments ids, we can obtain their geometries:

async getBoxFromFrag(fragId) {
  const boxCoordinates = this.fragList.fragments.boxes.slice(fragId * 6, (fragId * 6) + 6);
  const boxMin = new THREE.Vector3(boxCoordinates[0], boxCoordinates[1], boxCoordinates[2]);
  const boxMax = new THREE.Vector3(boxCoordinates[3], boxCoordinates[4], boxCoordinates[5]);
  const fragBox = new THREE.Box3(boxMin, boxMax);
  return fragBox;
}

Putting the steps together, we can obtain individual boxes for each dbId:

async getBoxFromDbid(dbId) {
  const fragsIds = await this.getFragsIdsFromdbId(dbId);
  let fragsBoxes = [];
  for (const fragId of fragsIds) {
    let newBox = await this.getBoxFromFrag(fragId);
    fragsBoxes.push(newBox);
  }
  var box3 = new THREE.Box3();
  fragsBoxes.map(fbox => box3.union(fbox));
  return box3;
}

And once we have the bounding box, it is time to adjust the view.

Adjusting the view

To adjust the view, we can use the fitBounds method that's based on a box, just like in the snippet below:

let fits = await viewer.navigation.fitBounds(false, box);

And passing all of that in an extension we can mimic fit to view feature based on a mouse single click (that could also be triggered by a hotkey or an option in the context menu).

Feel free to try our live demo and check its source code:

DEMO

SOURCE

Related Article