Transform an Element

Question:
How can I apply a transformation to a Revit element or family instance?

I used to work in the AutoCAD API, where I could apply a transformation using the transformBy() method.
It takes an AcGeMatrix3d parameter, which can represent a composed transformation including rotation, scaling, translation and mirroring.

In the Revit API, I see the use of the 4×3 matrix represented by the Transform class, which I suppose serves the same purpose.
However, I am unable to find a method like transformBy() to apply this transformation to a FamilyInstance or any other graphic element.

Therefore, my questions are:

  1. Is there a way to apply a transformation matrix to a Revit element?
  2. Can this matrix be applied containing simultaneous rotation, translation and mirroring transformations?
  3. If there is no such function, how can I decompose a Transform to apply the transformation through the Move, Rotate and Mirror document commands?

Answer:
As much as possible I would recommend to avoid comparing the AutoCAD and Revit APIs.
In AutoCAD, everything is disconnected and anything can be done.
In Revit, everything is totally parameterised and connected, and almost no independent manipulation of objects is possible, because almost everything affects almost everything else.

For instance, imagine the effect of a transformation including a non-unit scale factor on a Revit element.
In the case of a wall, the wall type includes information on the wall thickness.
Blindly applying such a transformation to the wall would change its thickness, which would require it to change its type.
This is a rather far-reaching modification, more than you might have expected, coming from an environment like AutoCAD.
Therefore, the Revit API does not provide any method for scaling elements, or more generally applying general transformations to them.

The FamilyInstance class provides a property Location, which can be used to find the physical location of an instance within project. It overrides the base class Element.Location, which provides the same functionality for generic elements. As the description says, it is a read-only property.

The location object provides the ability to translate and rotate elements. More detailed location information and control can be found by using the derivatives of this object, such as LocationPoint and LocationCurve.

You might be able to make use of the Location.Move and Rotate methods for your purposes:

  • Move: move the element within the project by a specified vector.
  • Rotate: rotate the element within the project by a specified number of degrees around a given axis.

The document class provides similar methods to manipulate several elements simultaneously:

  • Move: overloaded, moves one or more elements within the document by a specified vector.
  • Rotate: overloaded, rotates one or more elements within the document by a specified number of degrees around a given axis.

The overloads are provided so you can specify the affected elements by individual element or element id, or by a whole set of each respectively.

The Document and Element classes also provide analogous methods to mirror one or more elements.

In exact details of what you can and cannot do will obviously depend on the circumstances, such as what kind of elements you are manipulating and what transformations you wish to apply.

The Revit API does not provide methods to decompose an arbitrary Transform instance, beyond the ones listed in the help file. For instance, you can check whether a transformation is a translation using IsTranslation, in which case the translation vector can be obtained from the Origin property.

Decomposing a matrix in the manner you describe is not a trivial task. Here is some old Silicon Graphics C++ code I found which achieves this and also includes some documentation and references for further reading and research. More references and background information is provided by the Wikipedia article on

matrix decomposition
:


//
// Decompose a rotation into translation etc, based on scale
//
// Decomposes the matrix into a translation, rotation, scale,
// and scale orientation.  Any projection information is discarded.
// The decomposition depends upon choice of center point for
// rotation and scaling, which is optional as the last parameter.
// Note that if the center is 0, decompose() is the same as
// factor() where "t" is translation, "u" is rotation, "s" is scaleFactor,
// and "r" is ScaleOrientattion.
//
void
SbMatrix::getTransform(
  SbVec3f & translation,
  SbRotation & rotation,
  SbVec3f & scaleFactor,
  SbRotation & scaleOrientation,
  const SbVec3f &center ) const
{
  SbMatrix so, rot, proj;
  if (center != SbVec3f(0,0,0)) {
    // to get fields for a non-0 center, we
    // need to decompose a new matrix "m" such
    // that [-center][m][center] = [this]
    // i.e., [m] = [center][this][-center]
    // (this trick stolen from Showcase code)
    SbMatrix m,c;
    m.setTranslate(-center);
    m.multLeft(*this);
    c.setTranslate(center);
    m.multLeft(c);
    m.factor(so,scaleFactor,rot,translation,proj);
  }
  else {
    this->factor(so,scaleFactor,rot,translation,proj);
  }
  scaleOrientation = so.transpose();  // have to transpose because factor gives us transpose of correct answer.
  rotation = rot;
}
//
// Factors a matrix m into 5 pieces: m = r s r^ u t, where r^
// means transpose of r, and r and u are rotations, s is a scale,
// and t is a translation. Any projection information is returned
// in proj.
//
// routines for matrix factorization taken from BAGS code, written by
// John Hughes (?).  Original comment follows:
//
/* Copyright 1988, Brown Computer Graphics Group.  All Rights Reserved. */
/* --------------------------------------------------------------------------
 * This file contains routines to do the MAT3factor operation, which
 * factors a matrix m:
 *    m = r s r^ u t, where r^ means transpose of r, and r and u are
 * rotations, s is a scale, and t is a translation.
 *
 * It is based on the Jacobi method for diagonalizing real symmetric
 * matrices, taken from Linear Algebra, Wilkenson and Reinsch, Springer-Verlag
 * math series, Volume II, 1971, page 204.  Call number QA251W623.
 * In ALGOL!
 * -------------------------------------------------------------------------*/
/*
 * Variable declarations from the original source:
 *
 * n  : order of matrix A
 * eivec: true if eigenvectors are desired, false otherwise.
 * a  : Array [1:n, 1:n] of numbers, assumed symmetric!
 *
 * a  : Superdiagonal elements of the original array a are destroyed.
 *    Diagonal and subdiagonal elements are untouched.
 * d  : Array [1:n] of eigenvalues of a.
 * v  : Array [1:n, 1:n] containing (if eivec = TRUE), the eigenvectors of
 *    a, with the kth column being the normalized eigenvector with
 *    eigenvalue d[k].
 * rot  : The number of jacobi rotations required to perform the operation.
 */
SbBool
SbMatrix::factor(
  SbMatrix & r,
  SbVec3f & s,
  SbMatrix & u,
  SbVec3f & t,
  SbMatrix & proj ) const
{
  double    det;        /* Determinant of matrix A    */
  double    det_sign;    /* -1 if det < 0, 1 if det > 0    */
  double    scratch;
  int        i, j;
  int        junk;
  SbMatrix    a, b, si;
  float    evalues[3];
  SbVec3f    evectors[3];
 
  a = *this;
  proj.makeIdentity();
  scratch = 1.0;
 
  for (i = 0; i < 3; i++) {
    for (j = 0; j < 3; j++) {
      a.matrix[i][j] *= scratch;
    }
    t[i] = matrix[3][i] * scratch;
    a.matrix[3][i] = a.matrix[i][3] = 0.0;
  }
  a.matrix[3][3] = 1.0;
 
  /* (3) Compute det A. If negative, set sign = -1, else sign = 1 */
  det = a.det3();
  det_sign = (det < 0.0 ? -1.0 : 1.0);
  if (det_sign * det < 1e-12)
    return(FALSE);        // singular
 
  /* (4) B = A * A^  (here A^ means A transpose) */
  b = a * a.transpose();
 
  b.jacobi3(evalues, evectors, junk);
 
  // find min / max eigenvalues and do ratio test to determine singularity
 
  r = SbMatrix(evectors[0][0], evectors[0][1], evectors[0][2], 0.0,
         evectors[1][0], evectors[1][1], evectors[1][2], 0.0,
         evectors[2][0], evectors[2][1], evectors[2][2], 0.0,
         0.0, 0.0, 0.0, 1.0);
 
  /* Compute s = sqrt(evalues), with sign. Set si = s-inverse */
  si.makeIdentity();
  for (i = 0; i < 3; i++) {
    s[i] = det_sign * sqrt(evalues[i]);
    si.matrix[i][i] = 1.0 / s[i];
  }
 
  /* (5) Compute U = R^ S! R A. */
  u = r * si * r.transpose() * a;
 
  return(TRUE);
}

I believe that if the matrix is mirroring, then the scaling vector produced by the decomposition above will have one or two negative and one or two positive components. If all three are either positive or negative, then it is not a mirroring.

An easy way to determine whether a matrix is a mirroring transformation or not is to check its

determinant
.
If it is negative, the transformation is a mirroring, I believe. Happily, the determinant is provided directly by the Revit API as a property of the Transform class.

In Revit 2010, we have some updated API functionality affecting this area, specifically the Instance.Transformed method and geometry of Instances. There is a paragraph in the What’s New section of the help file on this topic:

Instance.Transformed[Transform] and geometry of Instances

This property has been removed. Obtain the transformed geometry of the instance using Instance.GetInstanceGeometry(), Instance.GetSymbolGeometry(Transform) and Instance.GetInstanceGeometry(Transform).
The two overloads compute the geometric representation of the instance and a transformation it, respectively.


Comments

22 responses to “Transform an Element”

  1. Hi Jeremy,
    I am trying to use move on a new family instance. Since I haven’t found a get point command, I’ve gone and used PickOne to grab an existing object in the drawing so that I know where the new family instance is going to get moved to. I first load a family, then use newfamilyinstance to insert it into the drawing and then move it to the location point of the object/family I picked earlier. The problem is that I won’t always have an item in the location that I need the new family instance to go to. So, what I would like to do,is to use the move command where I can specify the start point of the newfamilyinstance and then have the item on my cursor to place wherever I need to in the drawing. Is there a way to do this in the API?
    Thanks for your time.
    Matt

  2. Dear Matt,
    Unfortunately, the Revit 2010 API does not provide any method for interactively picking a point on the graphics screen, cf.
    http://thebuildingcoder.typepad.com/blog/2008/10/picking-a-point.html
    http://thebuildingcoder.typepad.com/blog/2009/02/selection-questions.html
    Neither is there currently a way attach a family instance to be inserted to the cursor and then drag it around on the graphics screen to select the desired location.
    But, I still have good news for you, anyway, if you can just hang on for a little while longer … so, please, just a little bit of patience for a short while more…
    Cheers, Jeremy.

  3. Vasco Granadeiro Avatar
    Vasco Granadeiro

    Dear Jeremy,
    I am using the VSTA IDE to generate a grid to be the starting point for the generation of parametric houses but I am having a problem with the ‘Item’ property of the ‘XYZArray’ Class. I need to retrieve XYZ objects out of an arrays, to draw lines, but I am getting the following error message: ‘Autodesk.Revit.Geometry.XYZArray’ does not contain a definition for ‘Item’. However, I can find this property for this class in the RevitAPI Help file. Can you help me, please?
    Best regards,
    Vasco Granadeiro

  4. Dear Vasco,
    I don’t use VSTA at all myself, so I cannot test it for you. The only thing that comes to mind is that you might have to use get_Item instead of Item. Can’t you use Intellisense or the object browser to see what the methods are called? I hope this helps.
    Cheers, Jeremy.

  5. Vasco Granadeiro Avatar
    Vasco Granadeiro

    Dear Jeremy,
    In the object browser this property does not appear, although it does on the RevitAPI Help file… Let me try to put the question in another way: if you create a XYZArray with three XYZ objects, how do you retrieve (call) the first and the second objects, for example to create a line? Thanks.
    Best,
    Vasco

  6. Dear Vasco,
    I have used code like this in C# to access points in an XYZArray:
    XYZArray points = e.Tessellate();
    XYZ p = points.get_Item( 0 );
    int n = points.Size;
    XYZ q = points.get_Item( n – 1 );
    As said, I have no experience with VSTA, this is in standard .NET.
    Also, it may possibly just be ‘Item’ without the ‘get_’ prefix in VB.NET.
    Please also note that the XYZArray class has been replaced by standard .NET collections in the Revit 2011 API.
    Cheers, Jeremy.

  7. Hi,Jeremy,
    I am trying to create a section view in revit project using API and use BoundBoxXYZ in Revit Structure 2011 while I get exception. Would you please help me?
    BoundingBoxXYZ bbox = new BoundingBoxXYZ();
    but I cannot change bbox.Transform. Its default is An identity Matrix.
    Transform trans = new Transform(Transform.Identity);
    trans.BasisX= …;
    trans.BasisY= …;
    trans.BasisZ= …;
    But When bbox.Transform = trans, An argumentexception is trown.
    And I also try to
    bbox.Transform.set_Basis(0, trans.BasisX);
    bbox.Transform.set_Basis(1, trans.BasisY);
    bbox.Transform.set_Basis(2, trans.BasisZ);
    bbox.Transform.Origin = trans.Origin;
    There is no use. bbox.Transform keeps its identity format.

  8. Dear Jane,
    Probably the problem is that your transform is not uniform, and therefore being rejected.
    You should probably have a look at the Revit SDK CreateViewSection sample.
    It includes a method named GenerateBoundingBoxXYZ which calls a helper method GenerateTransform to generate a valid transformation, and then assign that to the bounding box transform property just as you attempted to do using
    m_box = new BoundingBoxXYZ();
    Transform transform = GenerateTransform();
    m_box.Transform = transform;
    Cheers, Jeremy

  9. Hi Jeremy
    Is it possible to rotate Grid objects? The code below, adapted from the API documentation, does’t seem to do anything for grids.
    Public Function RotateElement(ByVal element As Autodesk.Revit.DB.Element, ByVal AxisStart As Autodesk.Revit.DB.XYZ, ByVal AxisEnd As Autodesk.Revit.DB.XYZ, ByVal Rotation As Double) As Boolean
    ‘ The axis should be a bound line.
    ‘Dim axis As Line = IntUIApp.Application.Create.NewLineBound(AxisEnd, AxisStart)
    Dim x As Autodesk.Revit.DB.Line = Autodesk.Revit.DB.Line.Bound(AxisStart, AxisEnd)
    Dim successful As Boolean = IntUIApp.ActiveUIDocument.Document.Rotate(element, x, Rotation)
    Return successful
    End Function
    Regards
    Urly

  10. Dear Urly,
    Sorry, as far as I know there is currently no way at all to rotate a grid object programmatically. Sorry for the bad news.
    Cheers, Jeremy.

  11. Dear Urly,
    Correction: the following code works fine for grids just as it does for walls and lines:
    ElementId id = uidoc.Selection.PickObject(ObjectType.Element).ElementId;
    Line axis = app.Create.NewLine(XYZ.Zero, new XYZ(0, 0, 10), true);
    ElementTransformUtils.RotateElement(doc, id, axis, Math.PI / 4);
    Cheers, Jeremy.

  12. Hello Jeremy,
    This is the closest snippet of a conversation I could find related to modifying existing Grids.
    I am trying to modify a Grid line by changing the underlying curve (line or arc) or setting the grid to a newly defined curve. Is this even possible?
    Thank you,
    -Nate

  13. Dear Nate,
    Sorry, currently that does not seem possible. I added a note of your request to the existing wish list item SPR #151715 [API functionality request: to change Grid’s start/end point coordinates via API].
    Cheers, Jeremy.

  14. Hi Jeremy
    How can I scale the Annotation Elements?
    Thank you

  15. Dear Andy,
    You should set up the required situation manually through the user interface first and then explore it using RevitLookup, the BipChecker, the interactive Python or Ruby shell, and any other tools that you find useful to detect the differences.
    That will tell you how to achieve your manual adjustment programmatically as well, if you are lucky and it is possible at all.
    Please let us know what you find out. Thank you.
    Cheers, Jeremy.

  16. Moustafa Khalil Avatar
    Moustafa Khalil

    Hi Jeremy
    how can i make a copy of existing inclined line. something like offset by a known distance in user interface. i have tried using this
    ElementTransformUtils.CopyElement(doc, line.Id,new XYZ(0,2,0));
    but not working… may you help me in this regard?

  17. Moustafa Khalil Avatar
    Moustafa Khalil

    correction!! it is working but not giving me the expected results, it offsets the line but not perpendicular to inclination. any suggestions ??!!!!

  18. Dear Moustafa,
    You simply need to specify a vector that is indeed perpendicular to the inclination, whatever you may mean by that.
    Here are some instructions on how to determine a perpendicular vector:
    http://en.wikipedia.org/wiki/Normal_%28geometry%29
    For 2D, it is very simple indeed:
    http://www.wikihow.com/Find-Perpendicular-Vectors-in-2-Dimensions
    In other words, given v=(a,b), its perpendicular vector equals w=(-b,a).
    Cheers, Jeremy.

  19. Hi Jeremy Tammik,
    i am a very new in Revit API, I have some questions want to your help.
    1. Why the all objects’s LocationPoint imported from the IFC file has value (0,0,0)?
    2. How to define LocationPoint of a Family?
    3. LocationPoint same centroid?
    Help me please!

  20. Dear Bcb,
    When an object is imported from IFC, it is impossible to know exactly what insertion point it is supposed to have.
    The software could possible make a more intelligent guess than (0,0,0), of course.
    For instance, it could pick the centroid of the union of all the object’s solids, assuming that they all have the same specific weight.
    However, whatever point it picks, it would still be a guess, and therefore (0,0,0) is considered as good as any.
    Do you understand?
    The location point of a family is simply the origin of the defining family project world coordinate system, which turns into the local coordinate system when an instance of the family is placed into the project document.
    It could be defined as the centroid, but it could also be located somewhere completely different.
    I would suggest playing around a bit in the family editor, manually, through the user interface, and exploring the behaviour of the resulting family instances when you place them in a project.
    Then, very quickly, all will become clear.
    I hope this helps.
    Cheers, Jeremy.

  21. Hi Jeremy Tammik,
    Thank you very much for all your support.
    It is helpful for me.

  22. Dear Bcb,
    :-)
    Thank you for your appreciation.
    My pleasure entirely.
    Cheers, Jeremy.

Leave a Reply to Jeremy TammikCancel reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading