0

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?

3 Answers 3

5

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

Sign up to request clarification or add additional context in comments.

3 Comments

One clarifying question: why is the refPoint Z value 0.0? Should that not match the elevation of the survey point?
I think the options object should be { applyRefpoint: true, globalOffset: globalOffset , keepCurrentModels: true } otherwise the variable globalOffset would be unused
Hi @Yasser Thanks for pointing it out. Updated the code snippet. My apologies for the typo.
2

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',
})

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.