Apply transform to family instance

Apply transform to family instance

peter_hirn
Enthusiast Enthusiast
2,057 Views
3 Replies
Message 1 of 4

Apply transform to family instance

peter_hirn
Enthusiast
Enthusiast

Hello,

 

I've been trying to figure this out for several days now and can't find a good solution.

 

Scenario:

  • Given a document with family instances
  • Run a custom command which exports some family instance data (eg. file name, symbol name, transform)
  • Create a new document and run custom import command
  • Expectation: all family instances are identical (wrt. exported data)
  • Actual: it seems impossible to consistently apply a transform to a family instance

JSON sample:

{
  "Family": "test.rfa",
  "Symbol": "test",
  "Transform": {
    "Origin": {
      "X": 1.0,
      "Y": 2.0,
      "Z": 3.0.
    },
    "BasisX": {
      "X": 1.0,
      "Y": 0.0,
      "Z": 0.0
    },
    "BasisY": {
      "X": 0.0,
      "Y": 1.0,
      "Z": 0.0
    },
    "BasisZ": {
      "X": 0.0,
      "Y": 0.0,
      "Z": 1.0
    }
  }
}

 

Approach 1

create the family instance on a correctly rotated/mirrored sketch plane

let private frame origin basisX basisY basisZ =
    new Frame(origin, basisX, basisY, basisZ)

let private plane origin basisX basisY basisZ =
    frame origin basisX basisY basisZ |> Plane.Create

let private sketchPlane (doc : Document) origin basisX basisY basisZ =
    SketchPlane.Create(doc, plane origin basisX basisY basisZ)

let private creationData doc transform symbol =
    let origin = transform.Origin.ToXYZ()
    let basisX = transform.XAxis.Normalize() |> unitVectorToXYZ
    let basisY = transform.YAxis.Normalize() |> unitVectorToXYZ
    let basisZ = transform.ZAxis.Normalize() |> unitVectorToXYZ

    let sketchPlane = sketchPlane doc origin basisX basisY basisZ
    let creationData = new Creation.FamilyInstanceCreationData(origin, symbol, sketchPlane, Structure.StructuralType.NonStructural)

    creationData

This seems to work for most rotations, but fails when mirroring along z-axis (and probably other cases I didn't test yet):

  Expected: <CoordinateSystem 4x4-Double
1  0   0  1
0  1   0  2
0  0  -1  3
0  0   0  1
>
  But was:  <CoordinateSystem 4x4-Double
1   0   0  1
0  -1   0  2
0   0  -1  3
0   0   0  1

Revit decides that this instance should also be mirrored along the y-axis.

 

Approach 2

create all family instances in XYZ.Zero and use ElementTransformUtils.CopyElements(...) to apply the transform:

let private applyTransform (doc : Document) (transform, id) =
    let transformedId = ElementTransformUtils.CopyElements(doc, [| id |], doc, transform, null) |> Seq.exactlyOne
    doc.Delete id |> ignore
    transformedId

let private creationData symbol =
    new Creation.FamilyInstanceCreationData(XYZ.Zero, symbol, Structure.StructuralType.NonStructural)

This doubles the runtime of the import and also fails when mirroring along z-axis:

  Expected: <CoordinateSystem 4x4-Double
1  0   0  1
0  1   0  2
0  0  -1  3
0  0   0  1
>
  But was:  <CoordinateSystem 4x4-Double
1   0  0  1
0  -1  0  2
0   0  1  3
0   0  0  1

This time only the y-axis is mirrored.

 

Dynamo implementation

https://github.com/DynamoDS/DynamoRevit/blob/master/src/Libraries/RevitNodes/Elements/FamilyInstance...

public static FamilyInstance ByCoordinateSystem(FamilyType familyType, CoordinateSystem coordinateSystem)

 

They extract the euler angles from the transform, then throw rotations around X- and Y-axis away and apply rotation around Z-axis. Mirroring instances is not supported.

 

I played around with this idea and the gimbal-lock problem but eventually gave up, since I couldn't find a way to extract mirror properties (plane or direction) from the transform. It also seems very inefficient to decompose the transform, apply all rotations and mirror operations just to get the same transform in Revit.

 

Other idea: maybe it's possible to get the native Trf* pointer and override it. Probably breaks internal Revit invariants.

 

If you know how to simply apply a (non-scaling) transform to a family instance, please help.

 

Best regards

0 Likes
2,058 Views
3 Replies
Replies (3)
Message 2 of 4

jeremy_tammik
Alumni
Alumni

Yes, Unfortunately, the Revit API and Revit itself does not support applying a simple non-scaling transform to a family instance. Have you checked the flipped and mirrored properties of the instances before exporting? Maybe you need to unflip and unmirror them in the transform that you export, and then re-flip and re-mirror the instances after importing. I know this is tricky stuff, and I have not seen a solution to it yet, so I will be very interested indeed to hear how you end up solving this.

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 3 of 4

peter_hirn
Enthusiast
Enthusiast

Hey Jeremy,

 

thank you for the response. After more testing I'm now thinking that my initial approach (Transform |> Frame |> Plane |> Sketchplane |> CreationData) already works for my case.

 

test.png

I thought the bottom right should be the negated Z-axis transform (which can't be applied, always negates Y-axis)

 

 

1  0   0 
0  1   0
0  0  -1

 

 

here it is produced by negating X- and Z-axis

 

-1   0   0
 0   1   0
 0   0  -1

 

- equivalent to negating Y- and Z-axis (bottom left) and rotating 180° around Z-axis

- equivalent to rotating 180° around Y-axis

 

Maybe the test family is a bad, because it's symmetric along one axis? I'll have to do more tests and read a lot more about transformations.

 

About the hand- and facing-flipped instance fields. I had some troubles with that about a year ago when I was working on something very similar. Back then I was only concerned with X- and Y-axis (a undetected bug in hindsight).

 

let fromInstance (instance : FamilyInstance) =
    use transform = instance.GetTotalTransform()

    let origin = transform.Origin.ToPoint3D()

    let basisX =
        let vector = transform.BasisX.ToUnitVector3D()
        if instance.HandFlipped then
            vector.Negate()
        else
            vector

    let basisY =
        let vector = transform.BasisY.ToUnitVector3D()
        if instance.FacingFlipped then
            vector.Negate()
        else
            vector

    let basisZ = transform.BasisZ.ToUnitVector3D()

    CoordinateSystem(origin, basisX, basisY, basisZ)

 

I think this still works correctly, but needs more testing. Maybe Revit accepts single negated X- or Y-axis transforms when the family can be hand- or facing-flipped, but negates a second axis otherwise?

 

Will post an update when I understand this better.

Message 4 of 4

ankofl
Advocate
Advocate

Perhaps there will come a bright day in the history of Autodesk when they will add to their flagship product such an elementary and at the same time basic function for any design system as applying transformation to objects, but that day has not yet arrived. Therefore, plus or minus, every developer working with RevitAPI has to create similar crutches from scratch.:
ankofl_0-1756723737374.png
By the way, I may have overlooked some cases, I would appreciate it if someone would report them here.

public static FamilyInstance Create(this Document doc, FamilySymbol symbol, XYZ pos, XYZ targetY, XYZ targetX)
{
	if (doc == null) throw new ArgumentNullException(nameof(doc));
	if (symbol == null) throw new ArgumentNullException(nameof(symbol));
	if (pos == null) throw new ArgumentNullException(nameof(pos));
	if (targetY == null) throw new ArgumentNullException(nameof(targetY));
	if (targetX == null) throw new ArgumentNullException(nameof(targetX));

	// Проверка нормализации векторов
	if (!targetY.IsUnitLength() || !targetX.IsUnitLength())
	{
		throw new ArgumentException("Векторы targetY и targetX должны быть нормализованными.");
	}

	// Проверка ортогональности targetY и targetX
	if (Math.Abs(targetY.DotProduct(targetX)) > 1e-6)
	{
		throw new ArgumentException("Векторы targetY и targetX должны быть ортогональны.");
	}

	// Вычисление targetZ как векторного произведения
	XYZ targetZ = targetY.CrossProduct(targetX);
	if (!targetZ.IsUnitLength())
	{
		targetZ = targetZ.Normalize();
	}

	try
	{
		// Активация символа
		if (!symbol.IsActive)
		{
			symbol.Activate();
		}

		// Создание плоскости с нормалью targetZ
		Plane plane = Plane.CreateByNormalAndOrigin(targetZ, pos);
		SketchPlane sketchPlane = SketchPlane.Create(doc, plane);

		// Создание экземпляра семейства
		var newInstance = doc.Create.NewFamilyInstance(pos, symbol, sketchPlane, StructuralType.NonStructural);

		// Первый поворот: выравнивание FacingOrientation с targetY
		XYZ currentFacing = newInstance.FacingOrientation;
		double facingAngle = currentFacing.AngleTo(targetY);
		if (facingAngle > 1e-6)
		{
			XYZ axis1 = currentFacing.CrossProduct(targetY);
			if (axis1.IsZeroLength())
			{
				// Если вектора противоположны, используем перпендикулярную ось
				axis1 = targetZ;
				facingAngle = Math.PI;
			}
			axis1 = axis1.Normalize();
			Line rotationAxis1 = Line.CreateBound(pos, pos + axis1);
			ElementTransformUtils.RotateElement(doc, newInstance.Id, rotationAxis1, facingAngle);
		}

		// Обновляем текущие вектора после первого поворота
		newInstance = doc.GetElement(newInstance.Id) as FamilyInstance; // Обновляем объект
		currentFacing = newInstance.FacingOrientation;

		// Второй поворот: выравнивание HandOrientation с targetX
		XYZ currentHand = newInstance.HandOrientation;
		double handAngle = currentHand.AngleTo(targetX);
		if (handAngle > 1e-6)
		{
			XYZ axis2 = currentFacing; // Ось - FacingOrientation
			XYZ crossCheck = currentHand.CrossProduct(targetX);
			if (crossCheck.DotProduct(axis2) < 0)
			{
				handAngle = -handAngle; // Корректируем направление
			}
			axis2 = axis2.Normalize();
			Line rotationAxis2 = Line.CreateBound(pos, pos + axis2);
			ElementTransformUtils.RotateElement(doc, newInstance.Id, rotationAxis2, handAngle);
		}

		// Дополнительная проверка и коррекция, если HandOrientation противоположен
		currentHand = newInstance.HandOrientation;
		if (currentHand.IsAlmostEqualTo(targetX.Negate(), 1e-6))
		{
			XYZ correctionAxis = currentFacing.Normalize();
			Line correctionAxisLine = Line.CreateBound(pos, pos + correctionAxis);
			ElementTransformUtils.RotateElement(doc, newInstance.Id, correctionAxisLine, Math.PI);
		}

		return newInstance;
	}
	catch (Exception e)
	{
		throw new InvalidOperationException("Ошибка при создании FamilyInstance", e);
	}
}