skeletank
skeletank

Reputation: 2888

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?

Upvotes: 0

Views: 696

Answers (3)

Basomis
Basomis

Reputation: 101

In case a family instance is a line based, one can transform a location curve of a source family instance.

if (sourceInstance.Location is LocationCurve locationCurve) 
{
    var transformInverse = sourceInstance.GetTransform().Inverse;
    var transformToApply = targetInstance.GetTransform().Multiply(transformInverse);
    var newLocationCurve = locationCurve.Curve.CreateTransformed(transformToApply);
    locationCurve.Curve = newLocationCurve;
}

Upvotes: 0

skeletank
skeletank

Reputation: 2888

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);
}

Upvotes: 1

skeletank
skeletank

Reputation: 2888

I was able to write the code for this after checking Wikipedia for how to determine the axis and the angle of rotation. I also had to look up how to calculate the trace in order to determine the angle.

private static Line GetRotationAxisFromTransform(Transform transform)
{
  double x = transform.BasisY.Z - transform.BasisZ.Y;
  double y = transform.BasisZ.X - transform.BasisX.Z;
  double z = transform.BasisX.Y - transform.BasisY.X;

  return Line.CreateUnbound(transform.Origin, new XYZ(x, y, z));
}

private static double GetRotationAngleFromTransform(Transform transform)
{
  double x = transform.BasisX.X;
  double y = transform.BasisY.Y;
  double z = transform.BasisZ.Z;

  double trace = x + y + z;

  return Math.Acos((trace - 1) / 2.0);
}

I then passed these into the ElementTransformUtils.RotateElement method in order to rotate the FamilyInstance #2 into the same position as FamilyInstance #1.

Upvotes: 1

Related Questions