Set a nodes transform
Hey!
I am trying to align the transform of one node to another, but have got to a point where I can't see a way forward. Here is my function:
def align_transforms_x(node1, node2):
"""
Align the transform of node2 to node1
Args:
- node1: The target node.
- node2: The node to align.
Returns:
None
"""
node1 = cmdx.encode(node1)
node2 = cmdx.encode(node2)
transform = node1.transform()
# this method does not exist:
node2.set_transform(transform)
Is there something I am missing?
Hm, yes you will need to set translate/rotate/scale explicitly, as well as account for any parent.
Try this.
def align_transforms_x(node1, node2):
node1 = cmdx.encode(node1)
node2 = cmdx.encode(node2)
try:
parent = node1.parent()
parent_inverse_matrix = parent["worldInverseMatrix"][0].as_matrix()
except AttributeError:
parent_inverse_matrix = cmdx.Matrix4()
matrix2 = node2["worldMatrix"][0].as_matrix()
matrix2 = matrix2 * parent_inverse_matrix
tm = cmdx.TransformationMatrix(matrix2)
node1["translate"] = tm.translation()
node1["rotate"] = tm.rotation()
node1["scale"] = tm.scale()
Then if your source and target nodes have pivots and custom axes you'll need to take those into account as well, and for undo you should use cmdx.DagModifier() instead of setting things directly. There might be a function in the API, e.g. MFnTransform that can handle this I think? If you're up for it, it would be good to expose that to cmdx.
ah thanks for the super detailed reply - my initial intention was to get/set it in world space, but parent space will likely be useful at some point too. Once I get things in working order I will submit a PR.
Hey @mottosso - I have the basic logic in place, but I am struggling with getting undo to be recorded.
Was your suggestion to expose MFnTransform to the DagModifier, effectively setting a nodes transform in a similar way to the createNode and parent methods?
Yes, I think that's what has to happen. Setting the transform of a kTransform node implies setting a whole bunch of things at once..
- Translate
- Rotation
- Scale
- Shear
- Scale pivot
- Scale pivot translation
- Rotate pivot
- Rotate pivot translation
- Rotation orientation
- Reference
And if the input is a MMatrix as in your initial example, rather than a MTransformationMatrix that actually carries all of this information, then we'll also have to assume the user means to zero out everything but translate, rotate, scale and shear since a MMatrix doesn't carry anymore than that.
And, if the node isn't a kTransform but a e.g. kJoint then we'll also have to zero out the jointOrient as neither the MMatrix nor MTransformationMatrix carries that.
With that that in mind, manual use might look something like..
tm = cmdx.TransformationMatrix()
with cmdx.DagModifier() as mod:
mod.setAttr(node["translate"], tm.translation())
mod.setAttr(node["rotatePivot"], tm.rotatePivot())
mod.setAttr(node["rotatePivotTranslate"], tm.rotatePivotTranslate())
# etc...
Whereas some kind of wrapper function would do those things under the hoodl.
mod.setTransform(node, tm)
# Or..
node.setTransform(tm)
Undo would then be handled as any other call to setAttr.
ah I see - I had completely overlooked setAttr for some reason - that works perfectly in my prototype function Would it make sense to create JointNode as a child class of DagNode, with access to joint specific attributes and methods such as jointOrient or do class specific checks on DagNode methods?
Would it make sense to create JointNode
Yes, except not inheriting from DagNode as joints don't have things like rotatePivot. It would need to inherit from Node. Would welcome a PR with a JointNode implementation.
Although that said, you can node.isA(cmdx.kJoint) to find the type (along with node.type() == "joint") and node["jointOrient"] to get the attribute. So there's not much benefit having a dedicated class for it.