Search code examples
c#revit-api

Given two family instances with the same LocationPoint how can I get instance #1 and #2 to have the same orientation


In the Revit API I'm trying to replace a family instance of the "Specialty Equipment" category with with an almost identical version of the category "Mechanical Equipment". So far I've been able to insert the new instance in the same location as the old but I've not been able to match the same orientation. A FamilyInstance has a method GetTransform that I can use to get the Transform of the old FamilyInstance but there is no method SetTransform to set the transform for the new FamilyInstance. The only tools available for moving the new FamilyInstance are the ElementTransformUtils with functions like MoveElement and RotateElement but I'm unsure what to pass into these using the old Transform.

How can I make sure that the orientation of the new FamilyInstance matches that of the old FamilyInstance?


Solution

  • I found a better solution that account for 0 or 180 degree rotation. My first solution will not work in this case. I found the solution and the java version of the code at euclideanspace.com.

    Here is my code getting the matrix from the Transform and then calling the function to get the axis and angle.

    double[][] matrix = new double[][]
    {
        new double[]{ oldTransform.BasisX.X, oldTransform.BasisY.X, oldTransform.BasisZ.X },
        new double[]{ oldTransform.BasisX.Y, oldTransform.BasisY.Y, oldTransform.BasisZ.Y },
        new double[]{ oldTransform.BasisX.Z, oldTransform.BasisY.Z, oldTransform.BasisZ.Z }
    };
    
    
    GetAxisAngleFromMatrix(matrix, out double angleOfRotation, out XYZ axisOfRotation);
    
    Line rotationLine = Line.CreateUnbound(oldTransform.Origin, axisOfRotation);
    

    Here is the the mathmatical function

    public void GetAxisAngleFromMatrix(double[][] m, out double angleOfRotation, out XYZ axisOfRotation)
    {
      double angle, x, y, z; // variables for result
      double epsilon = 0.01; // margin to allow for rounding errors
      double epsilon2 = 0.1; // margin to distinguish between 0 and 180 degrees
                             // optional check that input is pure rotation, 'isRotationMatrix' is defined at:
                             // https://www.euclideanspace.com/maths/algebra/matrix/orthogonal/rotation/
    
      if ((Math.Abs(m[0][1] - m[1][0]) < epsilon)
        && (Math.Abs(m[0][2] - m[2][0]) < epsilon)
        && (Math.Abs(m[1][2] - m[2][1]) < epsilon))
      {
        // singularity found
        // first check for identity matrix which must have +1 for all terms
        //  in leading diagonaland zero in other terms
        if ((Math.Abs(m[0][1] + m[1][0]) < epsilon2)
          && (Math.Abs(m[0][2] + m[2][0]) < epsilon2)
          && (Math.Abs(m[1][2] + m[2][1]) < epsilon2)
          && (Math.Abs(m[0][0] + m[1][1] + m[2][2] - 3) < epsilon2))
        {
          // this singularity is identity matrix so angle = 0
          angleOfRotation = 0;
          axisOfRotation = new XYZ(1, 0, 0);
    
          return;
        }
    
        // otherwise this singularity is angle = 180
        angle = Math.PI;
        double xx = (m[0][0] + 1) / 2;
        double yy = (m[1][1] + 1) / 2;
        double zz = (m[2][2] + 1) / 2;
        double xy = (m[0][1] + m[1][0]) / 4;
        double xz = (m[0][2] + m[2][0]) / 4;
        double yz = (m[1][2] + m[2][1]) / 4;
        if ((xx > yy) && (xx > zz))
        { // m[0][0] is the largest diagonal term
          if (xx < epsilon)
          {
            x = 0;
            y = 0.7071;
            z = 0.7071;
          }
          else
          {
            x = Math.Sqrt(xx);
            y = xy / x;
            z = xz / x;
          }
        }
        else if (yy > zz)
        { // m[1][1] is the largest diagonal term
          if (yy < epsilon)
          {
            x = 0.7071;
            y = 0;
            z = 0.7071;
          }
          else
          {
            y = Math.Sqrt(yy);
            x = xy / y;
            z = yz / y;
          }
        }
        else
        { // m[2][2] is the largest diagonal term so base result on this
          if (zz < epsilon)
          {
            x = 0.7071;
            y = 0.7071;
            z = 0;
          }
          else
          {
            z = Math.Sqrt(zz);
            x = xz / z;
            y = yz / z;
          }
        }
    
        angleOfRotation = angle;
        axisOfRotation = new XYZ(x, y, z); // return 180 deg rotation
    
        return;
      }
      // as we have reached here there are no singularities so we can handle normally
      double s = Math.Sqrt((m[2][1] - m[1][2]) * (m[2][1] - m[1][2])
        + (m[0][2] - m[2][0]) * (m[0][2] - m[2][0])
        + (m[1][0] - m[0][1]) * (m[1][0] - m[0][1])); // used to normalise
      if (Math.Abs(s) < 0.001) s = 1;
      // prevent divide by zero, should not happen if matrix is orthogonal and should be
      // caught by singularity test above, but I've left it in just in case
      angle = Math.Acos((m[0][0] + m[1][1] + m[2][2] - 1) / 2);
      x = (m[2][1] - m[1][2]) / s;
      y = (m[0][2] - m[2][0]) / s;
      z = (m[1][0] - m[0][1]) / s;
    
      angleOfRotation = angle;
      axisOfRotation = new XYZ(x, y, z);
    }