Passing a matrix attribute to a Maya Python API 2 command

Hello,

I’m continuing my journey with the API and have created a command that does some calculation with vectors, I would like to supply a matrix as an argument to transform the vectors into a different space (if desired).

I hoped that in the MSyntax class there would be a kMatrix or k4x4Matrix property, unfortunately not. Then I remembered how to pass a vector

syntax.addFlag(short_name_flag, long_name_flag,
(om.MSyntax.kDouble, om.MSyntax.kDouble, om.MSyntax.kDouble))

So I thought I could pass a matrix as a list of 16 kDouble but that doesn’t seem to work. Does anyone have experience with using a matrix as flag argument and could point me in the right direction?

Thanks,
-Harry

This might help How to send python lists to a custom command - Autodesk Community

Unfortunately not. I know how to pass a list as an argument but Maya crashes when I try to pass a matrix as a list of 16 number. I read in the documentation that MSyntax.addFlag returns an error if more than 6 argument types are provided, maybe that’s the reason but I’m not sure

  • Wherever I’ve written list in this text below, you could swap it with tuple, just a clarification. Same goes for code - you could pass list instead of tuple, and vice versa.

I think you might have missed the point of that topic @xian has linked - it talks about list of lists in the end. So, you can create flag with 4 kDoubles in a list, and enable multi use of that flag; and then, when you’re calling command, you could pass to that flag a list of several 4 elements “sub-lists” (for 4x4 matrix, you would pass list consisting of 4 sub-lists, each containing 4 elements).

For example (not entire code here, just relevant parts):

class MatrixCommand(om.MPxCommand):
    """ ."""

    COMMAND_NAME = 'matrixCommand'
    MATRIX_FLAG = ['-mtx', '-matrix']

    ...

    @classmethod
    def syntax_creator(cls):
        """ ."""

        syntax = om.MSyntax()
        syntax.addFlag(MatrixCommand.MATRIX_FLAG[0], MatrixCommand.MATRIX_FLAG[1], (om.MSyntax.kDouble, om.MSyntax.kDouble, om.MSyntax.kDouble, om.MSyntax.kDouble))  # 4 elements tuple (or list)
        syntax.makeFlagMultiUse(MatrixCommand.MATRIX_FLAG[0])

        return syntax

    def parse_arguments(self, args):
        """ ."""

        argDatabase = om.MArgDatabase(self.syntax(), args)
        
        if argDatabase.isFlagSet(MatrixCommand.MATRIX_FLAG[0]):
            # how many times flag has been used, i.e. how many 4 elements lists we have passed
            num_of_flag_uses = argDatabase.numberOfFlagUses(MatrixCommand.MATRIX_FLAG[0])

            print(f"Command called with flag '{MatrixCommand.MATRIX_FLAG[1]}' set to: ")
            
            # then just iterate over each sub-list
            for i in range(num_of_flag_uses):
                flag_value = argDatabase.getFlagArgumentList(MatrixCommand.MATRIX_FLAG[0], i)

                # iterate over each element in current sub-list/tuple
                for j in range(len(flag_value)):
                    curent_element_value = flag_value.asDouble(j)
                    print(f"Row {i} - Column {j}: {curent_element_value }")

    ...

# call command - all of these would work
cmds.matrixCommand(mtx=[(1., 0., 0., 0.), (0., 1., 0., 0.), (0., 0., 1., 0.), (0., 0., 0., 1.)])  # 4x4 matrix
cmds.matrixCommand(mtx=[(1., 0., 0., 0.), (0., 1., 0., 0.)])  # 2x4 matrix
cmds.matrixCommand(mtx=(1., 0., 0., 0.))  # vector4

Yep, sorry about the lack of clarification, but @bjelDark is right. I’m thinking of the matrix as a multidimensional array and the how to do it is explained by the end of that link I shared.

Thanks for taking the time to write the example code. Although this works, I don’t think it’s convenient to use, if we use getAttr on a matrix attribute then it’s returned as a list of 16 numbers, so that’s the format I would like to use

Actually, the 2-dimensional array gives you the dimensions of the Matrix, so you can operate with rows and columns easier than just having a single array of values. Not all matrices are 4x4. A 16 values matrix could be anything, though I assume you’re just talking about a 4x4 transformation matrix and that’s why you don’t find it convenient.

On another note, if you’re trying to interface with getAttr, maybe you should just try to replicate what they have in setAttr.

        ...

        # syntax.addFlag(MatrixCommand.MATRIX_FLAG[0], MatrixCommand.MATRIX_FLAG[1], (om.MSyntax.kDouble, om.MSyntax.kDouble, om.MSyntax.kDouble, om.MSyntax.kDouble))  # 4 elements tuple (or list)
        syntax.addFlag(MatrixCommand.MATRIX_FLAG[0], MatrixCommand.MATRIX_FLAG[1], om.MSyntax.kDouble)

Matrix is not flattened list (technically, it would be 1x16 in this case, but we would call it vector16, rather than matrix, I’d say), but if that’s what you want (I won’t go into that), you can change that line of code above, and call command with:

cmds.matrixCommand(mtx=[1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.])

or even (16 is not the limit):

cmds.matrixCommand(mtx=[1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.])

And of course, change logic in code (now it will print rows incremented, and all columns as 0…), but that’s irrelevant…

I tried to implment the your code but I’m still getting an error, I’ve changed the line in the syntax_creator to use om.MSyntax.kDouble and updated the parse_arguments to print the

from maya.api import OpenMaya as om
import sys


maya_useNewAPI = None


class MatrixCommand(om.MPxCommand):
    """ ."""

    COMMAND_NAME = 'matrixCommand'
    MATRIX_FLAG = ['-mtx', '-matrix']

    ...

    @classmethod
    def syntax_creator(cls):
        """ ."""

        syntax = om.MSyntax()
        # syntax.addFlag(MatrixCommand.MATRIX_FLAG[0], MatrixCommand.MATRIX_FLAG[1], (om.MSyntax.kDouble, om.MSyntax.kDouble, om.MSyntax.kDouble, om.MSyntax.kDouble))  # 4 elements tuple (or list)
        syntax.addFlag(cls.MATRIX_FLAG[0], cls.MATRIX_FLAG[1], om.MSyntax.kDouble)
        syntax.makeFlagMultiUse(cls.MATRIX_FLAG[0])

        return syntax

    def parse_arguments(self, args):
        """ ."""

        argDatabase = om.MArgDatabase(self.syntax(), args)

        if argDatabase.isFlagSet(MatrixCommand.MATRIX_FLAG[0]):
            # how many times flag has been used, i.e. how many 4 elements lists we have passed
            num_of_flag_uses = argDatabase.numberOfFlagUses(MatrixCommand.MATRIX_FLAG[0])

            print(f"Command called with flag '{MatrixCommand.MATRIX_FLAG[1]}' set to: ")

            # then just iterate over each sub-list
            for i in range(num_of_flag_uses):
                flag_value = argDatabase.getFlagArgumentList(MatrixCommand.MATRIX_FLAG[0], i)
                print(f'current element {i} value: {flag_value}')

                # # iterate over each element in current sub-list/tuple
                # for j in range(len(flag_value)):
                #     curent_element_value = flag_value.asDouble(j)
                #     print(f"Row {i} - Column {j}: {curent_element_value}")
                    
    @staticmethod
    def cmdCreator():
        return MatrixCommand()


def initializePlugin(plugin):
    pluginFn = om.MFnPlugin(plugin)
    try:
        pluginFn.registerCommand(
            MatrixCommand.COMMAND_NAME, MatrixCommand.cmdCreator, MatrixCommand.syntax_creator
        )
    except:
        sys.stderr.write(
            "Failed to register command: %s\n" % MatrixCommand.COMMAND_NAME
        )

def uninitializePlugin(plugin):
    pluginFn = om.MFnPlugin(plugin)
    try:
        pluginFn.deregisterCommand(MatrixCommand.COMMAND_NAME)
    except:
        sys.stderr.write(
            "Failed to unregister command: %s\n" % MatrixCommand.COMMAND_NAME
        )

Any help would be appreciated!

If this is your whole class, then it’s probably because you’re missing (just) doIt() method, and __init__(); though it actually works even if you omit initializer (don’t recommend it), but you must have doIt() method, (in which you, among other things, call for arguments parsing), or you’ll get kFailure, when executing command…

I did point out that in my example, some code is left out, and there are 3 dots at the beginning, and at the end of class example, meaning there’s more code to it.

Try this one:

import maya.api.OpenMaya as om
import sys


maya_useNewAPI = None


class MatrixCommand(om.MPxCommand):
    """ ."""

    COMMAND_NAME = 'matrixCommand'
    MATRIX_FLAG = ['-mtx', '-matrix']

    def __init__(self):
        """ ."""

        super().__init__()

    @classmethod
    def syntax_creator(cls):
        """ ."""

        syntax = om.MSyntax()
        syntax.addFlag(cls.MATRIX_FLAG[0], cls.MATRIX_FLAG[1], om.MSyntax.kDouble)
        syntax.makeFlagMultiUse(cls.MATRIX_FLAG[0])

        return syntax

    def parse_arguments(self, args):
        """ ."""

        argDatabase = om.MArgDatabase(self.syntax(), args)

        if argDatabase.isFlagSet(MatrixCommand.MATRIX_FLAG[0]):
            # how many times flag has been used, i.e. how many 4 elements lists we have passed
            num_of_flag_uses = argDatabase.numberOfFlagUses(MatrixCommand.MATRIX_FLAG[0])

            print(f"Command called with flag '{MatrixCommand.MATRIX_FLAG[1]}' set to: ")

            # then just iterate
            for i in range(num_of_flag_uses):
                flag_value = argDatabase.getFlagArgumentList(MatrixCommand.MATRIX_FLAG[0], i)

                print(f'Current element {i} value: {flag_value.asDouble(0)}')

    @staticmethod
    def cmdCreator():
        """ ."""
        
        return MatrixCommand()

    def doIt(self, args):
        """ ."""

        # parse arguments when doIt() is invoked
        self.parse_arguments(args)


def initializePlugin(plugin):
    pluginFn = om.MFnPlugin(plugin)

    try:
        pluginFn.registerCommand(MatrixCommand.COMMAND_NAME, MatrixCommand.cmdCreator, MatrixCommand.syntax_creator)
    except:
        sys.stderr.write(f"Failed to register command: {MatrixCommand.COMMAND_NAME}\n")

def uninitializePlugin(plugin):
    pluginFn = om.MFnPlugin(plugin)

    try:
        pluginFn.deregisterCommand(MatrixCommand.COMMAND_NAME)
    except:
        sys.stderr.write(f"Failed to deregister command: {MatrixCommand.COMMAND_NAME}\n")

If you still get errors, it would be helpful telling what sort of error did you receive, and when (while loading plugin, trying to run command, etc.).

1 Like

I’m embarrsed that I forgot the doIt method…

Thanks for your help, I wasn’t using the syntax.makeFlagMultiUse, argDatabase.numberOfFlagUses or argDatabase.getFlagArgumentList methods previously

Don’t beat yourself up about it - when stuck, it’s quite easy to overlook some bits, however obvious or not they may be.