worldMatrix to Translate/Rotate

worldMatrix to Translate/Rotate

negow
Advocate Advocate
3,590 Views
5 Replies
Message 1 of 6

worldMatrix to Translate/Rotate

negow
Advocate
Advocate

Hi all,

 

I've got a custom node that..

 

1. Takes a worldMatrix

2. Performs some transformations on it

3. Spits out another worldMatrix

 

The default Maya worldMatrix is the sum of translate, rotate, rotate/scale pivot, rotate axis and in the case of joints a jointOrient too. The problem I'm having is in extracting only the translate/rotate components out of the resulting worldMatrix.

 

The transformations in step (2) are guaranteed to only involve translation and rotation, no scale nor shear. And my goal is finding updated translate/rotate of the output worldMatrix, taking into account any pre-existing pivots and axes and joint orients.

 

Here's an example in psuedo-code.

 

import cmdx

cmds.file(new=True, force=True)

# Two identical cubes
cube1, _ = map(cmdx.encode, cmds.polyCube(width=3))
cube2, _ = map(cmdx.encode, cmds.polyCube(width=3))

cube1["translate"]             = (-3, 2, -1)
cube1["rotate", cmdx.Degrees]  = (30, 20, 10)

cube2["translate"]             = (1, 2, 3)
cube2["rotate", cmdx.Degrees]  = (10, -60, 0)
cube2["rotatePivot"]           = (1.5, 0, 0)
cube2["rotatePivotTranslate"]  = (0.5, 1, 0)

# Apply matrix from cube1 to cube2, preserving pivot
wmat = cube1["worldMatrix"][0].asMatrix()
tm = cmdx.TransformationMatrix(wmat)

cube2["translate"] = tm.translation()
cube2["rotate"] = tm.rotation()

 

This results in cube2 being offset to cube1 by the amount of rotatePivot and rotatePivotTranslate is had, as they would be applied on-top of whatever the worldMatrix was.

 

negow_0-1599805825553.png

 

I've formulated a more elaborate, working example using Maya API 2.0 on the Python-Inside-Maya mailing list a few days ago in search of a solution, without avail.

 

- https://groups.google.com/g/python_inside_maya/c/x8F417cpato

 

The Maya docs has a breakdown of how the MTransformationMatrix works, which I expect is how the Transform *node* works as well, given that the MFnTransform documentation is identical.

 

- https://help.autodesk.com/view/MAYAUL/2020/ENU/?guid=Maya_SDK_MERGED_cpp_ref_class_m_transformation_...

 

Along with this excellent write-up about the jointOrient.

 

- https://forums.autodesk.com/t5/maya-programming/mtransformationmatrix-and-joint-orientation-python/m...

 

But my eyes glaze over, my mind turns to dust and I get nowhere. I expect I'm overlooking a series of inverse matrix multiplications somewhere, but I can't for the life of me figure out where. Help!

0 Likes
3,591 Views
5 Replies
Replies (5)
Message 2 of 6

olarn
Advocate
Advocate

Looks like the rotate pivot position were composed separately from translation so that have to be accounted for.

 

Fortunately MTransformationMatrix automatically deal with translation offset for us so all that is needed to be done was simply assign the rotate pivot value back to the transformation matrix.

https://help.autodesk.com/cloudhelp/2020/CHS/Maya-SDK-MERGED/py_ref/class_open_maya_1_1_m_transforma...

 

 

 

The transformation matrix is then constructed as follows:

   -1                       -1
[Sp]x[S]x[Sh]x[Sp]x[St]x[Rp]x[Ro]x[R]x[Rp]x[Rt]x[T]

 

 

>... x[Ro]x[R]x[Rp]x[Rt]x[T]

 

 

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

def setup():
    cmds.file(new=True, force=True)

    cube1, _ = cmds.polyCube(width=5)
    cube2, _ = cmds.polyCube(width=5)
    cmds.parent(cube2, cube1)

    # Here's our rotate pivot
    cmds.setAttr(cube2 + ".rotatePivot", -2.5, 0, 0, type="double3")

    cmds.move(0, 2, 0, cube1)
    cmds.move(5.1, 0, 0, cube2, relative=True)
    cmds.rotate(0, 0, 30, cube1)
    cmds.rotate(0, 0, -60, cube2)

    cube3, _ = cmds.polyCube(width=6, height=0.5, depth=0.5)
    cmds.move(4, 2, -2, cube3, absolute=True)
    cmds.rotate(25, 15, 50, cube3)
    
def copyMatrix(mobj1, mobj2):
    # Parent inverse
    obj2fn = om.MFnDagNode(mobj2)
    parentInverseMatrixPlug = obj2fn.findPlug("parentInverseMatrix", True).elementByLogicalIndex(0)
    parentInverseMatrix = om.MFnMatrixData(parentInverseMatrixPlug.asMObject()).matrix()
    

    # Get target worldmatrix we'd like cube2 to receive
    obj1fn = om.MFnDagNode(mobj1)
    worldMatrixPlug = obj1fn.findPlug("worldMatrix", False).elementByLogicalIndex(0)
    worldMatrix = om.MFnMatrixData(worldMatrixPlug.asMObject()).matrix()

    obj2TrnMat = om.MTransformationMatrix(om.MFnMatrixData(obj2fn.findPlug("matrix", False).asMObject()).matrix())
    obj2_rot_pivot_t = obj2TrnMat.rotatePivotTranslation(om.MSpace.kTransform)
   
    # Cancel out the parent matrix
    outMatrix = worldMatrix * parentInverseMatrix

    # Apply the thing and profit
        
    obj2_trn = om.MFnTransform(mobj2)
    
    final_trnmat = om.MTransformationMatrix(outMatrix)
    
    obj2_trnmat = obj2_trn.transformation()
    
    obj2_rotatePivot = obj2_trnmat.rotatePivot(om.MSpace.kTransform)

    final_trnmat.setRotatePivot(om.MPoint(obj2_rotatePivot), om.MSpace.kTransform, True)
    
    obj2_trn.setTransformation(final_trnmat)

setup()

selectionList = om.MSelectionList()
selectionList.add("pCube2")
selectionList.add("pCube3")

cube2obj = selectionList.getDependNode(0)
cube3obj = selectionList.getDependNode(1)

# Copy from pCube3 -> pCube2
copyMatrix(cube3obj, cube2obj)

 

 

0 Likes
Message 3 of 6

negow
Advocate
Advocate

EDIT: I spoke too soon, this does work! Thank so you much! Experimenting with this next.

 

Ooo, thought we had a winner, so close!

 

But, as it happens, no matter what you do to the rotate pivot, it is this line which performs the magic.

 

> obj2_trn.setTransformation(final_trnmat)

 

Which *overwrites* the rotate pivot, zeroing it out. So while it does work, it moves the goal posts a little. The matrix being edited it only having its rotation and translation edited, and what I'm looking for is to preserve everything about the transform apart from those two channels.

 

Here's a reduced version of your example without the changes made to the rotate pivot. Notice how it also affects the rotate pivot of the destination.

 

 

def copyMatrix(mobj1, mobj2):
    # Parent inverse
    obj2fn = om.MFnDagNode(mobj2)
    parentInverseMatrixPlug = obj2fn.findPlug("parentInverseMatrix", True).elementByLogicalIndex(0)
    parentInverseMatrix = om.MFnMatrixData(parentInverseMatrixPlug.asMObject()).matrix()

    # Get target worldmatrix we'd like cube2 to receive
    obj1fn = om.MFnDagNode(mobj1)
    worldMatrixPlug = obj1fn.findPlug("worldMatrix", False).elementByLogicalIndex(0)
    worldMatrix = om.MFnMatrixData(worldMatrixPlug.asMObject()).matrix()

    # Cancel out the parent matrix
    outMatrix = worldMatrix * parentInverseMatrix

    # Apply the thing and profit
    obj2_trn = om.MFnTransform(mobj2)
    final_trnmat = om.MTransformationMatrix(outMatrix)
    obj2_trn.setTransformation(final_trnmat)

 

0 Likes
Message 4 of 6

negow
Advocate
Advocate

Sorry, got a little carried away. 🙂 That call to `setTransformation` does alter the `.rotatePivotTranslate` attribute, which isn't allowed. Is there a way of avoiding editing of any attribute other than the translate/rotate channels?

0 Likes
Message 5 of 6

olarn
Advocate
Advocate

I think mtransformationmatrix also supports "rotatePivotTranslate". Now the assignment should also "preserve" that attribute.

If only modification to translate and rotate attributes were allowed, perhaps the simplest way to do this would be to emulate conditions exactly on transformation matrix first and then dump only those attributes back.

 

    import math

    def copyMatrix(mobj1, mobj2):
        # Parent inverse
        obj2fn = om.MFnDagNode(mobj2)
        parentInverseMatrixPlug = obj2fn.findPlug("parentInverseMatrix",
                                                  True).elementByLogicalIndex(0)
        parentInverseMatrix = om.MFnMatrixData(parentInverseMatrixPlug.asMObject()).matrix()

        # Get target worldmatrix we'd like cube2 to receive
        obj1fn = om.MFnDagNode(mobj1)
        worldMatrixPlug = obj1fn.findPlug("worldMatrix", False).elementByLogicalIndex(0)
        obj1_worldMatrix = om.MFnMatrixData(worldMatrixPlug.asMObject()).matrix()

        # Cancel out the parent matrix

        outMatrix = obj1_worldMatrix * parentInverseMatrix

        # Apply the thing and profit

        obj2_trn = om.MFnTransform(mobj2)

        final_trnmat = om.MTransformationMatrix(outMatrix)

        obj2_trnmat = obj2_trn.transformation()
        obj2_rot_pivot = obj2_trnmat.rotatePivot(om.MSpace.kTransform)
        obj2_rot_pivot_v = om.MVector(obj2_rot_pivot)
        obj2_rot_pivot_t = obj2_trnmat.rotatePivotTranslation(om.MSpace.kTransform)

        # This have to be called before setRotatePivot
        final_trnmat.setRotatePivotTranslation(
            obj2_rot_pivot_t, om.MSpace.kPostTransform)
        final_trnmat.setRotatePivot(obj2_rot_pivot, om.MSpace.kTransform, False)

        total_offset = obj2_rot_pivot_v + obj2_rot_pivot_t

        # Offset in parent's space, move pivot point to origin
        final_trnmat.translateBy(-total_offset, om.MSpace.kTransform)
        # Displace again in our space
        final_trnmat.translateBy(obj2_rot_pivot_v, om.MSpace.kPreTransform)

        # Both of this should give the same result

        USE_ASSIGN_TRANSFORMATION = True
        # USE_ASSIGN_TRANSFORMATION = False

        if USE_ASSIGN_TRANSFORMATION:
            obj2_trn.setTransformation(final_trnmat)
        else:
            pos = final_trnmat.translation(om.MSpace.kPostTransform)
            cmds.setAttr(obj2fn.name() + ".translate", *pos)
            cmds.setAttr(obj2fn.name() + ".rotate", *[
                math.degrees(n) for n in
                final_trnmat.rotation(False)
            ])

 

0 Likes
Message 6 of 6

negow
Advocate
Advocate

Finally back at this, and this works! This is amazing! I'm still a little uncertain of the various MSpace's, but I can work with this.

 

..or at least so I thought!

 

I am unable to replicate this in C++, so I've put together a minimal reproducible of the problem, with what I *thought* was an identical series of multiplications leading up to what works in your Python version.

 

- https://gist.github.com/mottosso/d637cfdc3cb0ad4f9e41cc0da8894bba

 

The example is straightforward enough; a matrix goes in, a matrix goes out. The outgoing matrix should account for the additional parentInverseMatrix, rotatePivot and rotatePivotTranslate. Just like before. The grey locator should exactly match the yellow, whilst keeping its pivots intact.

 

negow_0-1600588269315.png

 

 

Why oh why isn't this working? :S

 

Thanks again, getting real close I think!