David Alin
David Alin

Reputation: 21

How to decompose a matrix in Python for Maya?

I'm trying to write a script that can transfer translate and rotate from child to parent and vice versa without having to resort on parent constraints. I've spent the last few days investigating this using matrices information but I'm a beginner in Python and matrices.

So far, I've been able to find the matrices I want and apply the correct calculations on it but I can't convert those matrices back to regular Maya values. Essentially I'm trying to replicate whatever it is the "decomposeMatrix" node does in Maya.

After a lot of research and testing, I'm pretty sure the MTransformationMatrix function from OpenMaya is what I need but I can't make it work, I don't know how to write/use it, what parameters it's using, etc.

If I am on the right track, what do I need to finish the work? If I'm tackling something bigger that requires a lot more coding than this, I'd be interested to understand more on that too.

Here's the code:

import maya.cmds as mc

import maya.OpenMaya as OpenMaya

def myM():

    tlm = MMatrix(cmds.getAttr('TRAJ.matrix'))

    pwm = MMatrix(cmds.getAttr('TRAJ.worldMatrix'))

    pim = MMatrix(cmds.getAttr('TRAJ.parentInverseMatrix'))
    
    prod = tlm * pwm * pim

    return prod
    
    tMat = OpenMaya.MTransformationMatrix(prod)
    print tMat
    
    
myM()

Edit

I misused return on the code above. Re-testing the code today (and also when implementing Klaudikus' suggestions) I get the following error:

Error: TypeError: file /home/Maya_2020_DI/build/RelWithDebInfo/runTime/lib/python2.7/site-packages/maya/OpenMaya.py line 7945: in method 'new_MMatrix', argument 1 of type 'float const [4][4]' #

Upvotes: 0

Views: 4018

Answers (2)

David Alin
David Alin

Reputation: 21

Thanks to Klaudikus and a bunch of perseverance, I managed to make it work. It may look bit wanky but that's good enough for me! It does needs user friendly optimisation of course (that's my next step) but the core idea works. It potentially makes exchanging objects location between child and parent way faster than snapping or parent constraining.

import maya.cmds as cmds
from maya.api.OpenMaya import MMatrix
import pymel.core as pm

def myM():
    tlmList = cmds.getAttr('TRAJ.matrix')
    tlm = MMatrix(tlmList)

    pwmList = cmds.getAttr('PARENT1.worldMatrix')
    pwm = MMatrix(pwmList)

    pimList = cmds.getAttr('PARENT0.worldInverseMatrix')
    pim = MMatrix(pimList)



    prod = tlm * pwm * pim



    pm.xform('PARENT1', m=([prod[0],prod[1],prod[2],prod[3],prod[4],prod[5],prod[6],prod[7],prod[8],prod[9],prod[10],prod[11],prod[12],prod[13],prod[14],prod[15]]))



myM()

Upvotes: 0

Klaudikus
Klaudikus

Reputation: 392

I'm assuming you understand what the script is doing, but I'll recap just in case:

  • Getting the local matrix values of TRAJ and creating a new MMatrix object from those values.
  • Getting the world matrix values of TRAJ and creating a new MMatrix object from those values.
  • Getting the parent inverse matrix of TRAJ and creating a new MMatrix object from those values.
  • Multiplying all three of the above matrices and storing the resulting MMatrix in prod.
  • Returning prod.

As stated by halfer, the rest of the function is ignored because return will exit the function. Here's something that should work.

# here you typed:
# import maya.cmds as mc
# but further below used the cmds.getAttr...
# I decided to change the import statement instead
import maya.cmds as cmds

# MMatrix is a class within OpenMaya
# when using it, you must reference the module first then the class/object as so:
#     my_matrix = OpenMaya.MMatrix()
import maya.OpenMaya as OpenMaya


def myM():
    tlm = OpenMaya.MMatrix(cmds.getAttr('TRAJ.matrix'))
    pwm = OpenMaya.MMatrix(cmds.getAttr('TRAJ.worldMatrix'))
    pim = OpenMaya.MMatrix(cmds.getAttr('TRAJ.parentInverseMatrix'))
    prod = tlm * pwm * pim

    # check the documentation at:
    # https://help.autodesk.com/view/MAYAUL/2020/ENU/?guid=__py_ref_class_open_maya_1_1_m_transformation_matrix_html
    # here I'm "creating" an MTransformationMatrix with the MMatrix stored in prod
    tMat = OpenMaya.MTransformationMatrix(prod)

    # this is how you get the data from this object
    # translation is an MVector object
    translation = tMat.translation(OpenMaya.MSpace.kObject)
    
    # eulerRotation is a MEulerRotation object
    eulerRotation = tMat.rotation(asQuaternion=False)
    

    # typically you'll return these values
    return translation, eulerRotation

TRAJ_trans, TRAJ_rot = myM()

But this is not really useful. First of all the function myM should really take arguments so you can reuse it and get the translation and rotation from any object in the scene, not just TRANJ. Second, the math you are performing doesn't make sense to me. I'm not a mathematician, but here you're multiplying the local matrix, by the world matrix, by the parent's inverse local matrix. I honestly wouldn't be able to tell you what this does, but as far as I know, it's most likely useless. I won't go into detail on matrices, as it's a beast of a subject and you seem to be on that path anyway, but in a nutshell:

obj_local_matrix = obj_world_matrix * inverse_parent_world_matrix
obj_world_matrix = obj_local_matrix * parent_world_matrix

Another way of writing this is:

obj_relative_to_other_matrix = obj_world_matrix * inverse_other_world_matrix
obj_world_matrix = obj_relative_to_other_matrix * other_world_matrix

This is essentially the math that dictates the relationship between parented nodes. However, you can get the transform values of one node relative to any other node, irrespective of it's parenting, as long as you have their world matrices. Note that order is important here, as matrix multiplication is not commutative A * B != B * A.

What I gather from your question is that you're trying to get the relative transforms of an object, to that of another object in a scene. The following function returns the position, rotation, and scale of an object relative to that of another object.

def relative_trs(obj_name, other_obj_name):
    """
    Returns the position, rotation and scale of one object relative to another.

    :param obj_name:
    :param other_obj_name:
    :return:
    """
    obj_world_matrix = OpenMaya.MMatrix(cmds.getAttr('{}.worldMatrix'.format(obj_name)))
    other_obj_world_matrix = OpenMaya.MMatrix(cmds.getAttr('{}.worldMatrix'.format(other_obj_name)))
    
    # multiplying the world matrix of one object by the inverse of the world matrix of another, gives you it's relative matrix of the object to the other
    matrix_product = obj_world_matrix * other_obj_world_matrix.inverse()
    trans_matrix = OpenMaya.MTransformationMatrix(matrix_product)
    translation = trans_matrix.translation(OpenMaya.MSpace.kObject)
    euler_rotation = trans_matrix.rotation(asQuaternion=False)
    scale = trans_matrix.scale(OpenMaya.MSpace.kObject)
    return list(translation), list(euler_rotation), list(scale)

You can test the function by creating two hierarchies, and run the function between a leaf node of one hierarchy and any node in the other hierarchy. Store the results. Then re-parent the leaf node object to the one in the other hierarchy and check its local coordinates. They should match your stored results. This is essentially how a parent constraint works.

Upvotes: 3

Related Questions