Custom Transform Node & xform

Custom Transform Node & xform

chirieacam
Enthusiast Enthusiast
5,180 Views
46 Replies
Message 1 of 47

Custom Transform Node & xform

chirieacam
Enthusiast
Enthusiast

Is it possible to add a rotation offset to a custom transform node while making the rest of maya be aware of it? I get the following issues with it:

  • Getting the transform's world rotation using xform doesn't work, it always return without the offset. I couldn't find what else I need to override in my node to make it work. The default joint or hikEffector both have extra pre-rotation/offset, and they do return the correct results, so there must be a way to do it. 
    maya.cmds.xform(obj, query=True, rotation=True, worldSpace=True)
     
  • Using Modify->Match Transformations->Match Rotation command also fails to take the offset into account. It might be related to issue #1 using xform.

Is there some documentation somewhere what those commands expects to make them aware of the new rotation offset?

0 Likes
Accepted solutions (1)
5,181 Views
46 Replies
Replies (46)
Message 21 of 47

brentmc
Autodesk
Autodesk

Hi,

 

It is better to do the entire calculation yourself in asRotateMatrix from the components in the transformation matrix.


In your code sample below you are converting from MMatrix to an MTransformationMatrix. That operation involves matrix decomposition (recovering transformation components from a 4 x 4 matrix) and that is a lossy operation.

 

For example, a Rotation of (180, 0, 0) produces the same matrix as a Scaling (1,-1,-1) so when you decompose a matrix there is no way to know whether that matrix was generated by a rotation or scaling. Same thing with pivots, rotation spins etc. That information is lost when transformations are converted into a single 4 x 4 matrix so you will never be able to recover the exact transformation that generated that matrix.

Brent McPherson
Principle Engineer
Message 22 of 47

brentmc
Autodesk
Autodesk

Hi,

 

In a transform heirarchy points are transformed from object-space to world-space by multiplying it by all the matrices in the transform heirarchy until you reach the top-most transform. (e.g. MDagPath::inclusiveMatrix)

So when you ask the xform command for a world-space value it retrieves the local value and then transforms it to world-space using the appropriate matrices. (e.g. it also has to account for the intermediate transforms inside the transformation matrix)

Brent McPherson
Principle Engineer
0 Likes
Message 23 of 47

chirieacam
Enthusiast
Enthusiast

If that's the case, is it possible to know when a world-space value is requested? How does the joint knows to return the rotation included with jointOrient? It's something accessible to us?

0 Likes
Message 24 of 47

brentmc
Autodesk
Autodesk

Hi,

 

The underlying code is specifically dealing with joint orientation.

 

When you ask for the rotation in world-space it is 1) multiplying the jointOrient * exclusiveMatrix 2) extracting the rotation from that matrix and 3) multiplying the local rotation by that to get the world-rotation.

 

It does not look like preRotation is being handled but you can calculate it yourself. (as described above and similar to the code you had previously in asRotateMatrix)

Brent McPherson
Principle Engineer
Message 25 of 47

chirieacam
Enthusiast
Enthusiast

Hi Brent,

 

Thanks for letting me know. I was overriding various methods hoping there is one that work specifically for world space retrieval. Is it good to know there's no such thing.

 

I'll go ahead and wrap xform and do my own calculations to retrieve and set the world space values for my extra pre-attributes.

0 Likes
Message 26 of 47

chirieacam
Enthusiast
Enthusiast

I'm starting to think that adding pre-translate/rotate/scale values to a custom transform is not possible, or not an easy task, at least not without doing many workarounds when working with the custom transform. Many issues starts showing up.

 

For example, when retrieving the world matrix and setting it back on the node, the transform keeps getting extra translation added every time.

 

worldMatrix = cmds.xform('customTransform', query=True, matrix=True, worldSpace=True)
cmds.xform('customTransform', matrix=worldMatrix, worldSpace=True)

 

The asMatrix looks something like this:

 

MMatrix CustomTransformationMatrix::asMatrix() const
{
    MTransformationMatrix tm;

    tm.setTranslation(preTranslation + translationValue, MSpace::kTransform);

    return tm.asMatrix();
}

 

There's no "setMatrix", so not sure what we can do.

0 Likes
Message 27 of 47

brentmc
Autodesk
Autodesk

I think your best chance of success is to stick to overriding asScaleMatrix and asRotateMatrix.

You could apply pre-translation in asRotateMatrix since translations are commutative. e.g. order in which they are applied doesn't matter.

Brent McPherson
Principle Engineer
0 Likes
Message 28 of 47

chirieacam
Enthusiast
Enthusiast

Is there anything particular I need to do in asRotateMatrix? I'm tried to add pre-translation there too, but it has the same effect as in asMatrix: setting the worldMatrix using xform adds extra translation because the pre-translation keeps getting added to translate value.

 

It doesn't seems to be a way to remove the pre-translation when setting the matrix from the custom transform, so I guess I just need to take extra care and remove the pre-translation before applying the matrix.

 

 

0 Likes
Message 29 of 47

brentmc
Autodesk
Autodesk

Hi,

 

Is resetTransormation getting called when you assign to a matrix? Note: there are two overloads for this method.

 

That would be the place where you might be able to 1) grab your current pre-translation 2) call the parent class and 3) subtract pre-translation value from the translate channel.

Maya transforms do something similar for pivots/rotation order to preserve them. 

Brent McPherson
Principle Engineer
0 Likes
Message 30 of 47

chirieacam
Enthusiast
Enthusiast

Hi,

 

Thanks for the suggestion, but the resetTransformation is not called when setting the matrix, neither one of them. But, that's not a big issue, I can remove the pre-translation before setting the matrix. 

0 Likes
Message 31 of 47

chirieacam
Enthusiast
Enthusiast

I have another question not related to custom transforms, but to xform and transforms in general which is something useful to know when writing a custom transform node.

 

Why xform returns a different rotation than just retrieving the rotation from the world matrix when non-uniform scale is involved? I assume I should trust xform, but how would I do that using the api, what extra stuff xform does?

 

I have a child controlled by a non-uniform scaled parent using the offsetParentMatrix. Here's an example:

 

 

import maya.cmds as cmds
import maya.api.OpenMaya as om
from math import degrees

cmds.polyTorus(name='target')[0]
cmds.polyTorus(name='parent')[0]
cmds.polyCube(name='child')[0]

cmds.setAttr(f'target.translate', 4, 0, 0)
cmds.setAttr(f'target.rotate', 3, 11, -20)

cmds.setAttr(f'parent.translate', -4, 0, 0)
cmds.setAttr(f'parent.rotate', 20, 5, -32)
cmds.setAttr(f'parent.scale', 1.7, 1, 1)

cmds.connectAttr('parent.worldMatrix', 'child.offsetParentMatrix')
cmds.matchTransform('child', 'target')

worldRotation = cmds.xform('child', query=True, rotation=True, worldSpace=True)

print(worldRotation)
# result: [1.440870900254673, 7.9009174626179455, -5.730235420083044]

mobject = om.MGlobal.getSelectionListByName('child').getDependNode(0)
worldMatrix = om.MFnDagNode(mobject).getPath().inclusiveMatrix()

worldRotation = om.MTransformationMatrix(worldMatrix).rotation()

worldRotation.x = degrees(worldRotation.x)
worldRotation.y = degrees(worldRotation.y)
worldRotation.z = degrees(worldRotation.z)

print(worldRotation)
# result: (-0.275715, 6.95309, -15.8468, kXYZ)

# ---

 

 

0 Likes
Message 32 of 47

brentmc
Autodesk
Autodesk

When you retrieve world-space rotation using xform it takes the rotation channel and multiplies it by the rotation extracted from the (decomposed) exclusiveMatrix.

In the other case you are extracting the rotation from the (decomposed) inclusiveMatrix. They are different calculations so saying one is more correct than the other is a bit like comparing apples to oranges. 🙂

 

There are infinitely many ways to decompose a 4x4 matix. Especially when you start introducing non-uniform scaling which can introduce skew which gets tangled up with rotation in the 4x4 matrix.

A good reference for all the various ways that you can permute common transformations is "Transformation Identities" by Ned Greene in Graphics Gems I.
e.g.
"a rotation can be expressed as 3 skews"
"a skew can be expressed as two rotations and a scale"
"anisotropic scaling following rotation introduces skew"

Brent McPherson
Principle Engineer
Message 33 of 47

chirieacam
Enthusiast
Enthusiast

What is a better way to retrieve the world rotation that has the pre-rotation included? The solution I came up with returns the correct results from the user standpoint, but the values are not the same as when using the joint orient for the pre-rotation.

 

I use quaternion multiplication to add the pre-rotation. If I change the order of elements when multiplied I get the expected rotation in some cases, but it breaks in other ones.

 

 

import math 
import maya.api.OpenMaya as om
import maya.cmds as cmds

def worldRotation(obj):
    if cmds.nodeType(obj) == 'customTransform':
        rotation = om.MEulerRotation(cmds.xform(obj, query=True, rotation=True, worldSpace=True))
        rotation.x = math.radians(rotation.x)
        rotation.y = math.radians(rotation.y)
        rotation.z = math.radians(rotation.z)

        preRotation = om.MEulerRotation(cmds.getAttr(f'{obj}.preRotation')[0])
        preRotation.x = math.radians(preRotation.x)
        preRotation.y = math.radians(preRotation.y)
        preRotation.z = math.radians(preRotation.z)

        fullRotation = rotation.asQuaternion() * preRotation.asQuaternion()
        fullRotation = fullRotation.asEulerRotation()

        return om.MVector(
            math.degrees(fullRotation.x),
            math.degrees(fullRotation.y),
            math.degrees(fullRotation.z),
        )

    return om.MVector(cmds.xform(obj, query=True, rotation=True, worldSpace=True))

 

 

0 Likes
Message 34 of 47

brentmc
Autodesk
Autodesk

Hi,

 

When dealing with transforms you always have to be aware of what space you are doing the calculations in. In your case you are mutliplying a world-space rotation value by the preRotation which is not in world-space.
 

As I mentioned before xform is doing something like this when you ask for the world rotation.

 

1) grabbing the localRot and the exclusiveMatrix. (everything above your transform)

2) multiply jointOrientRotMatrix * exclusiveMatrix
3) extract exclusiveRot from matrix in previous step (by converting to a transform matrix)

4) multiply localRot * exclusiveRot to get world rotation

Brent McPherson
Principle Engineer
0 Likes
Message 35 of 47

chirieacam
Enthusiast
Enthusiast

Hi @brentmc,

 

Thank you for your help. Previously I was using joints to make use of the pre-rotation, but now I changed to custom transforms and, thanks to your help, all the functionality works the same and all my automated tests pass.

 

But, now I'm experiencing a new issue.

 

I'm doing two-way connections between the custom transforms, but each one is either modifying the pre-rotation or the pre-translation. Somewhere in the middle there are other custom transforms that affects the transforms by their world matrix. See the next image:

 

puppeteer_custom_transform.jpg

Everything works nice, but if I save and reopen the file the custom transforms are not in the position I left them (mainly the top transforms in the image above). As soon as I rotate the transform it snaps into place. Or, if I call cmds.dgdirty it also  moves the transforms in the right position.

 

(One thing though about cmds.dgdirty: it gives a cycle warning only if the red transforms controls a joint chain using worldMatrixe and offsetParentMatrix, even though everything works correctly and there's no real cycle.)

 

I was thinking that maybe the issue is because my pre-transform attributes affects the worldMatrix, but if I don't do that it can't update any objects that are connected to them.

 

Curiously, the normal rotate/translate/scale attributes do not affect the world matrix, but only the matrix, inverseMatrix and xformmatrix (used cmds.affects). I don't understand why the worldMatrix gets updated for them even if they are not marked as affecting the worldMatrix.

 

Is my issue fixable? Is anything else I'm missing in my code regarding the attributes?

0 Likes
Message 36 of 47

brentmc
Autodesk
Autodesk

Hi,

 

Unfortunately we are still have some issues with custom transforms and your issue sounds like this one:

 

MAYA-122884 Custom transform with incoming connection doesn't evaluate properly 

 

The symptom is the custom transform is not evaluated when loading the file.

 

Sorry, no workarounds at this time.

Brent McPherson
Principle Engineer
0 Likes
Message 37 of 47

chirieacam
Enthusiast
Enthusiast

Hey @brentmc ,

 

That's very unfortunate. Do you have more details about the issue you mentioned that you can share with me? I want to know how similar it is, and if indeed is only happening on loading a file or in some other cases as well.

 

Regarding workarounds, if the issue is happening only when loading a file, isn't this a decent one? It seems to work.

 

void CustomTransform::postConstructor()
{
    ParentClass::postConstructor();

    if (MFileIO::isReadingFile())
    {
        MGlobal::executeCommandOnIdle("dgdirty -allPlugs");
    }
}

 

 (Of course, better would be to dirty only the custom transforms, not all node types, and only once)

0 Likes
Message 38 of 47

brentmc
Autodesk
Autodesk

Hi,

 

It is an issue with the custom attributes on the custom transform not being properly evaluated.

In the issue previously mentioned the attribute would only be evaluated when the channel box is displayed.


There was some significant under-the-hood changes made to custom transform to fix other issues and this bug is a result of those changes and something we are investigating.

Brent McPherson
Principle Engineer
0 Likes
Message 39 of 47

chirieacam
Enthusiast
Enthusiast

Hi @Anonymous,

 

Can you please give an update on the custom transform issues? Is it possible please to prioritize this and get a fix in Maya 2023.2?

 

I recently tested Maya 2023.1 and the bug with the the custom attributes is still there; the attributes are not being evaluated if the channel box or attribute editor is not displayed.

 

This bug stops my plugin from working in 2023. In 2022 I only had issues with loading the file, and at least there was a workaround by marking all nodes as dirty on open.  But the new issues in 2023 stops me from upgrading to it.

0 Likes
Message 40 of 47

brentmc
Autodesk
Autodesk

Hi,

 

Thanks again for calling out these issues.

Unfortunately we weren't able to get these into 2023.1 because of timing but we are indeed prioritizing them.

Brent McPherson
Principle Engineer