Moving Speckle Objects with API

Hi community and speckel developpers!
My development environment
I’m using codesandbox.io and API Viewer dependency : @speckle/viewer 22.18.16
Revit for publish my model with Speckle Connector to send my model
My question
I’d like to move a clicked object to new coordinates. Once the object has been moved, I’d like to move it again.
My problem: The object moves, but the coordinates are not correct. I wonder why?

Hre is my sandbox

In the example shown here, after zooming in on the Manu object, I set the coordinates to 1,-1,0 and then click on Move object. The character moves, but we can see that he doesn’t move to the center of the small cube, which is at coordinates 1,-1,0…

Speckle Interface with codesandbox.io

Revit interface

Many thanks for your Help

Looking at the original objects in the data view on the Speckle web app, the transform for the figure is -1, -1, 0, not what is shown in your Revit screenshot.

It seems that entering a transform in your sandbox with a relative move of 2, -2, 0 consequently moves the figure to the cube.

I can only speculate that the insertion point of the figure is not centred or something else is going on.

Hi @pierrenavarra

I’ve made a small change in your MoveExtended extension, namely

/** Calculer la valeur actuelle de position basée sur le temps d'animation */
const value = new Vector3()
  .copy(anim.start)
  .lerp(anim.end, this.easeInOutQuint(anim.time));

/** Appliquer la translation */
/** I commented this out, since it's not needed. At least not for this particular use case*/
// const translation = value.sub(anim.current); // Calculer la différence par rapport à la position actuelle
anim.target.transformTRS(value, undefined, undefined, undefined);

transformTRS simply transforms, so the transformed position from one animation frame to the next is not cumulative. Now the person object thing moves to the cube when you input (1, -1, 0)

Here’s the forked sandbox

Cheers

1 Like

Hi @alex !
Fabulous! Thanks a lot…
I’ve got an other pb now, after moving on 1,-1,0, if I move to ne next position 1,-2,0, the person restart on 0,0,0…mmm…
Anyway, thanks a lot!

Like I said in my previous post, transformTRS only transforms. Any new transformation state is not being saved anywhere. Also, you are doing

this._animations.push({
  target: obj,
  start: new Vector3(), // Your anims will always start at (0,0,0)
  end: targetVector,
  current: new Vector3(),
  time: 0,
});

You have access to the BatchObject’s current transformation via it’s transform property or it’s translation property which is just the translation component of the transform. Do note that it’s the current translation, and not the current position.

1 Like

Oh, I understand

is it possible to get the current position after the translation? or do I have to save it? the object did move, didn’t it?

You will need to keep track of any current positions yourself, as BatchObject does not store such state explicitly. However if you know your starting position, say in your person object’s case (0,0,0), then to get the new position, you just need to apply the BatchObject’s transform matrix property to the vector and you will get it’s new position

Hi…

I still have a problem. I’ve temporarily abandoned the MoveExtended extension to concentrate on moving.
On loading, I set up a list containing TreeNodes, a Vecto3 to store the current position after each move and a list of your batchObjects.

type TypeRevitSpeckle = {
  node: TreeNode;
  currentCenter: Vector3;
  batchObjects: BatchObject[]; // Liste des objets batch associés
};

The first time the person moves from 0,0,0, I translate to 1,1,0. It works.
Next, I store my object’s new coordinates in my typeRevitSpeckles list.
The console.log shows me 1,1,0.
The next move is to 1,-2,0, but there’s a problem: the person moves to 0,1,0.
Now I don’t get it!

First translation
image
Second translation
image

Voici mon code…

//Function to Move current TreeNode
  function MoveTreeNode() {
    if (!_treeNodeSelected) return;
    const currentElementId = _treeNodeSelected.model.raw.elementId;
    if (!currentElementId) return;
    if (!typeRevitSpeckles) return;

    // Affichage des centres de chaque boîte englobante avec window.alert
    // Vérifier si SpecialityEquipmentNodes est vide ou null
    if (typeRevitSpeckles) {
      // Trouver le TypeRevitSpeckle correspondant à l'élément sélectionné
      const foundTypeRevitSpeckle = findTypeRevitSpeckleByElementId(
        typeRevitSpeckles,
        currentElementId
      );

      // Affichage des TreeNodes trouvés
      //console.log(`Number of filteredNode with elementId ${elementIdToSearch} is ${filteredNodes.length}`);
      if (foundTypeRevitSpeckle) {
        /*      
          node: TreeNode;
          currentCenter: Vector3;
        */
        const tn: TreeNode = foundTypeRevitSpeckle.node;
        const currentCenter = foundTypeRevitSpeckle.currentCenter;
        /*calcul du vecteur de déplacement par rapport à la position speckle
        de  l'objet sélectionné
        */

        const newPosition = new Vector3(
          REVIT_COORDINATES.x,
          REVIT_COORDINATES.y,
          REVIT_COORDINATES.z
        );

        const translation = new Vector3(
          REVIT_COORDINATES.x - currentCenter.x,
          REVIT_COORDINATES.y - currentCenter.y,
          REVIT_COORDINATES.z - currentCenter.z
        );

        if (isNaN(translation.x)) {
          translation.x = 0;
        }
        if (isNaN(translation.y)) {
          translation.y = 0;
        }
        if (isNaN(translation.z)) {
          translation.z = 0;
        }

        console.log("Translation:", translation);
        console.log("NewPosition:", newPosition);

        /*
        node est le TreeNode Speckle correspondant
        currentCenter: Vector3; position actuelle du TreeNode
        batchObjects: BatchObject[]; // Liste des objets batch associés
        */
        foundTypeRevitSpeckle.batchObjects.forEach(
          (batchObject: BatchObject) => {
            batchObject.transformTRS(
              translation,
              undefined,
              undefined,
              undefined
            );
          }
        );

        /*
        moveExtension.MoveTreeNode(
          _treeNodeSelected,
          translation,
          currentCenter
        );
*/

        viewer.requestRender();

        /*
        type TypeRevitSpeckle = {
          node: TreeNode;
          currentCenter: Vector3;
        };
        */
        const foundIndex = typeRevitSpeckles.findIndex(
          (item) => item.node.model.raw.elementId === currentElementId
        );
        if (foundIndex !== -1) {
          //MAJ du foundTypeRevitSpeckle
          typeRevitSpeckles[foundIndex].currentCenter = newPosition;
        }

        typeRevitSpeckles.forEach((item, index) => {
          if (item.node.model.raw.elementId != currentElementId) return;
          console.group("After moving :");
          console.log("Node elementid :", item.node.model.raw.elementId);
          console.log(
            "Current Center after translation is :",
            item.currentCenter
          );
          console.groupEnd();
        });

        /*
        https://codesandbox.io/p/sandbox/categorize-forked-s6p6z6?file=%2Fsrc%2FBoxSelection.ts%3A122%2C60

        */
      } else {
        console.log(
          `No TypeRevitSpeckle found with elementId ${currentElementId}`
        );
      }
    }
  }

my sandbox.io

maybe the coordinates aren’t stored…in which case, the SelectionExtension may be used for that, no? because when you move an object, then move it again, it takes into account the previous location…

The first time the person moves from 0,0,0, I translate to 1,1,0. It works.
Next, I store my object’s new coordinates in my typeRevitSpeckles list.
The console.log shows me 1,1,0.
The next move is to 1,-2,0, but there’s a problem: the person moves to 0,1,0.
Now I don’t get it!

I see that you’ve figure it out in here sandbox.io. :slight_smile:

Yes, done it!!!
I need to store all transformations

// Définir le type de l'objet contenant TreeNode et Box3
type TypeRevitSpeckle = {
  node: TreeNode;
  currentCenter: Vector3;
  transformations: Vector3;
  batchObjects: BatchObject[]; // Liste des objets batch associés
};

Here is my definitive code :slightly_smiling_face:

//Function to Move current TreeNode
  function MoveTreeNode() {
    if (!_treeNodeSelected) return;
    const currentElementId = _treeNodeSelected.model.raw.elementId;
    if (!currentElementId) return;
    if (!typeRevitSpeckles) return;

    // Affichage des centres de chaque boîte englobante avec window.alert
    // Vérifier si SpecialityEquipmentNodes est vide ou null
    if (typeRevitSpeckles) {
      // Trouver le TypeRevitSpeckle correspondant à l'élément sélectionné
      const foundTypeRevitSpeckle = findTypeRevitSpeckleByElementId(
        typeRevitSpeckles,
        currentElementId
      );

      // Affichage des TreeNodes trouvés
      //console.log(`Number of filteredNode with elementId ${elementIdToSearch} is ${filteredNodes.length}`);
      if (foundTypeRevitSpeckle) {
        /*      
          node: TreeNode;
          currentCenter: Vector3;
          transformations: Vector3;
          batchObjects: BatchObject[]; // Liste des objets batch associés
        */
        const tn: TreeNode = foundTypeRevitSpeckle.node;
        const currentCenter = foundTypeRevitSpeckle.currentCenter;
        const lastTransformation = foundTypeRevitSpeckle.transformations;

        /*calcul du vecteur de déplacement par rapport à la position speckle
        de  l'objet sélectionné
        */
        const newPosition = new Vector3(
          REVIT_COORDINATES.x,
          REVIT_COORDINATES.y,
          REVIT_COORDINATES.z
        );

        const translation = new Vector3(
          REVIT_COORDINATES.x - currentCenter.x,
          REVIT_COORDINATES.y - currentCenter.y,
          REVIT_COORDINATES.z - currentCenter.z
        );

        if (isNaN(translation.x)) {
          translation.x = 0;
        }
        if (isNaN(translation.y)) {
          translation.y = 0;
        }
        if (isNaN(translation.z)) {
          translation.z = 0;
        }
        
        lastTransformation.add(translation);
        /*
          node: TreeNode;
          currentCenter: Vector3;
          transformations: Vector3;
          batchObjects: BatchObject[]; // Liste des objets batch associés
        */

        foundTypeRevitSpeckle.batchObjects.forEach(
          (batchObject: BatchObject) => {
            batchObject.transformTRS(
              lastTransformation,
              undefined,
              undefined,
              undefined
            );
          }
        );

        /*
        moveExtension.MoveTreeNode(
          _treeNodeSelected,
          translation,
          currentCenter
        );
        */

        viewer.requestRender();

        /*
        type TypeRevitSpeckle = {
          node: TreeNode;
          currentCenter: Vector3;
          transformations: Vector3;
          batchObjects: BatchObject[]; // Liste des objets batch associés
        };
        */
        const foundIndex = typeRevitSpeckles.findIndex(
          (item) => item.node.model.raw.elementId === currentElementId
        );
        if (foundIndex !== -1) {
          //MAJ du foundTypeRevitSpeckle
          typeRevitSpeckles[foundIndex].currentCenter = lastTransformation;
        }

        typeRevitSpeckles.forEach((item, index) => {
          if (item.node.model.raw.elementId != currentElementId) return;
          console.group("After moving :");
          console.log("Node elementid :", item.node.model.raw.elementId);
          console.log(
            "Current Center after translation is :",
            item.currentCenter
          );
          console.groupEnd();
        });
        
      } else {
        console.log(
          `No TypeRevitSpeckle found with elementId ${currentElementId}`
        );
      }
    }
  }
2 Likes