Monday, January 20, 2014

How to use pymel.core.other.repeatLast

In Maya, and especially with MEL, when you want to add commands to the last used commands menu (you know the one shows up when you hit space) you use the repeatLast command. It is pretty easy, just add the command as a string and add a label to it to show up in the UI etc. as follows:

repeatLast -ac "some MEL command here!" -acl "a descriptive label for the command"
It is easy. Now when you want to do it in Python especially in PyMel, you will punched in the face by seeing that it only accepts MEL commands. I can hear the pipeline engineer inside you saying "I'd use the python Mel command to run Python code inside MEL", not so easy boy, how would you pass all the data. So if what you want to repeat is a function that accepts arbitrary arguments and especially the arguments are Python objects it is impossible to use the repeatLast command as it is. You need some modifications.

So, what I came up with was to store the callable, the arguments and the keyword arguments in somewhere that I know, and then call some python functions that needs only simple arguments (like an integer) from MEL.

Here is the code that I've written for a UI script that repeats the function on UI buttons:
import pymel.core as pm

__last_commands__ = []  # list of dictionaries to store callables and arguments


def repeater(index):
    """repeats the last command with the given index
    """
    global __last_commands__
    try:
        call_data = __last_commands__[index]
        return call_data[0](*call_data[1], **call_data[2])
    except IndexError:
        return None


def repeat_last(call_data):
    """own implementation of pm.repeatLast
    """
    global __last_commands__
    index = len(__last_commands__)

    callable_ = call_data[0]
    args = call_data[1]
    kwargs = call_data[2]

    command = \
        'print \\"\\";python(\\\"from oyToolbox import repeater; repeater(%s);\\\");' % index

    repeat_last_command = 'repeatLast -ac "%(command)s" -acl "%(label)s";' % {
        'command': command,
        'label': callable_.__name__
    }

    pm.mel.eval(repeat_last_command)
    __last_commands__.append(call_data)

    # also call the callable
    call_data[0](*call_data[1], **call_data[2])


def RepeatedCallback(callable_, *args, **kwargs):
    """Adds the given callable to the last commands list and adds a caller to
    the pm.repeatLast
    """
    return pm.Callback(
        repeat_last, [callable_, args, kwargs]
    )
The only problem here is that the __last_commands__ variable will get fatter and fatter if you call a lot of commands, and Pythons garbage collector will not be able delete anything inside it. A good solution is to delete the list occasionally, limit the element count to the same number of elements in the last commands UI element. Oh one another note, do you see the empty print command in line 28, maya doesn't add anything to the list without it, weird isn't it.

No comments: