jacobmlovelace
jacobmlovelace

Reputation: 55

Revit shared coordinates to Forge viewer

What is the correct process for getting a transform between Forge coordinates and Revit's shared coordinates? I know there is globalOffset, but does it reference the Revit project internal coordinate system or shared coordinates?

Upvotes: 0

Views: 1291

Answers (3)

wagner
wagner

Reputation: 1

Jonathan Chang's answer is a good apointment to use revit's shared coordinates, but if have coordinates far from zero i recommend convert coordinates more close to zero to better rendering and avoid problems with then.

Promise.all(
    docs.map(({ urn, name }) => {
      return new Promise((resolve, reject) => {
        Autodesk.Viewing.Document.load(
          `urn:${urn}`,
          async (Document) => {
            Document.downloadAecModelData(() => {
              resolve({
                doc: Document,
                name,
              });
            });
          },
          () => console.error("Failed fetching Forge manifest")
        );
      });
    })).then((docs) => {
    Promise.all(
      docs.map(async ({ doc }) => {
        const bubbleNode = doc.getRoot().getDefaultGeometry();
        const aecModelData = bubbleNode.getAecModelData();
        if (aecModelData) {
          const tf = aecModelData.refPointTransformation;
          return { x: tf[9], y: tf[10], z: tf[11] };
        }
      })
    ).then((refs) => {
      const points = refs.filter((el) => el !== undefined);
      const refPosition = points.reduce(
        (p, c, i, arr) => {
          const div = i + 1 === arr.length ? arr.length : 1;
          return {
            x: (p.x + c.x) / div,
            y: (p.y + c.y) / div,
            z: (p.z + c.z) / div,
          };
        },
        { x: 0, y: 0, z: 0 }
      );
      docs.forEach(async ({ doc, name }) => {
        const bubbleNode = doc.getRoot().getDefaultGeometry();
        const aecModelData = bubbleNode.getAecModelData();
        const tf = aecModelData && aecModelData.refPointTransformation;
        const matrix4 = new THREE.Matrix4()
          .makeBasis(
            new THREE.Vector3(tf[0], tf[1], tf[2]),
            new THREE.Vector3(tf[3], tf[4], tf[5]),
            new THREE.Vector3(tf[6], tf[7], tf[8])
          )
          .setPosition(
            new THREE.Vector3(
              tf[9] - refPosition.x,
              tf[10] - refPosition.y,
              tf[11] - refPosition.z
            )
          );

        viewer.loadDocumentNode(doc, bubbleNode, {
          placementTransform: matrix4,
          keepCurrentModels: true,
          globalOffset: {
            x: 0,
            y: 0,
            z: 0,
          },
          applyRefpoint: true,
          applyScaling: "ft",
        });
      });
    });});

With this example based on Jonathan's code I used multiple models and load averyone refPointTransformation to use these points like base to drag the models closer to zero, there are some discussions apointing to use globalOffset with position but in cases with big numbers this can cause poor rendering in viewer.

Upvotes: 0

Jonathan Chang
Jonathan Chang

Reputation: 21

I've also found success feeding the refPointTransformation into a matrix4.

This way, the orientation of the model is also taken into account. (This is based off Eason's Answer).

const bubbleNode = doc.getRoot().getDefaultGeometry();
await doc.downloadAecModelData();
const aecModelData = bubbleNode.getAecModelData();
const tf = aecModelData && aecModelData.refPointTransformation;
const matrix4 = new THREE.Matrix4()
  .makeBasis(
    new THREE.Vector3(tf[0], tf[1], tf[2]),
    new THREE.Vector3(tf[3], tf[4], tf[5]),
    new THREE.Vector3(tf[6], tf[7], tf[8])
  )
  .setPosition(new THREE.Vector3(tf[9], tf[10], tf[11]))

viewer.loadDocumentNode(doc, viewables, {
  placementTransform: matrix4,
  keepCurrentModels: true,
  globalOffset: {
    "x": 0,
    "y": 0,
    "z": 0
  },
  applyRefpoint: true,
  applyScaling: 'ft',
})

Upvotes: 2

Eason Kang
Eason Kang

Reputation: 7070

Update Jun 11th, 2021

Now my MultipleModelUtil.js supports the alignments I shared below. Also, we can easily tell Forge Viewer to use By shared coordinates to aggregate models. Here is the code snippet, and you can check out here to know supported alignments

const util = new MultipleModelUtil( viewer );

util.options = {
  alignment: MultipleModelAlignmentType.ShareCoordinates
};

const models = [
  { name: '1.rvt', urn: 'urn:dXJuOmFkc2sud2lwcHJvZDpmcy5maWxlOnZmLlNpaHgxOTVuUVJDMHIyWXZUSVRuZFE_dmVyc2lvbj0x' },
  { name: '2.rvt', urn: 'urn:dXJuOmFkc2sud2lwcHJvZDpmcy5maWxlOnZmLldVRHJ4ajZ6UTBPLTRrbWZrZ3ZoLUE_dmVyc2lvbj0x' },
  { name: '3.rvt', urn: 'urn:dXJuOmFkc2sud2lwcHJvZDpmcy5maWxlOnZmLjRyZW5HRTNUU25xNHhYaW5xdWtyaWc_dmVyc2lvbj0x' }
];

util.processModels( models );

==================

First, Forge Viewer supports 3 kinds of Revit link methods as the below, and you can take a look at the 3rd one (By shared coordinates).

  1. Origin to origin: Apply the globalOffset of the 1st model to others. Check MultipleModelUtil/MultipleModelUtil.js for the demo
  2. Center to center: the default way of the viewer.
  3. By shared coordinates: set up applyRefpoint: true and make the globalOffset to the refPoint. This method is the one you are looking for.

The refPoint is the Revit survey point location inside Revit internal coordinate system. It's accessible with the AecModelData. Meanwhile, you can take advantage of the AggregatedView to use this aligning option. Here is an example of telling how to use AggregatedView: https://gist.github.com/yiskang/c404af571ba4d631b5929c777503891e

If you want to use this logic with the Viewer class directly, here is a code snippet for you:

let globalOffset = null;

const aecModelData = bubbleNode.getAecModelData();
const tf = aecModelData && aecModelData.refPointTransformation; // Matrix4x3 as array[12]
const refPoint = tf ? { x: tf[9], y: tf[10], z: 0.0 } : { x: 0, y: 0, z: 0 };

// Check if the current globalOffset is sufficiently close to the refPoint to avoid inaccuracies.
const MaxDistSqr = 4.0e6;
const distSqr    = globalOffset && THREE.Vector3.prototype.distanceToSquared.call(refPoint, globalOffset);
if (!globalOffset || distSqr > MaxDistSqr) {
    globalOffset = new THREE.Vector3().copy(refPoint);
}

viewer.loadDocumentNode(doc, bubbleNode, { applyRefpoint: true, globalOffset: globalOffset, keepCurrentModels: true });

The bubbleNode can be either of the following:

bubbleNode = doc.getRoot().getDefaultGeometry()

//Or

const viewables = viewerDocument.getRoot().search({'type':'geometry'});
bubbleNode = viewables[0];

To get AecModelData, please refer to my gist: https://gist.github.com/yiskang/c404af571ba4d631b5929c777503891e#file-index-html-L67

// Call this line before using AecModelData
await doc.downloadAecModelData();

// doc.downloadAecModelData(() => resolve(doc));

See here for details of the AecModelData: https://forge.autodesk.com/blog/consume-aec-data-which-are-model-derivative-api

Upvotes: 5

Related Questions