MayaCmds/Pymel functions that take a string instead of a callable?

My coworker (Styler) was recently trying to use the ‘attributeMenu’ command and pass in the 'regPulldownMenuCommand ’ argument. This command requires that argument to be a string, NOT a callable, which is unlike most pymel/maya cmds (in fact, passing in a callable will probably give you a SyntaxError when it tries to cast it to a string and evaluate it…). Even better, it must be the name of a MEL Proc, NOT a Python callable.

We ended up doing an awesome (not) workaround where we use mel.eval to create a MEL proc, which invokes your Python callable (which must unfortunately be accessible from a string, ie it cannot be an instance method or inner function), the return value of which is returned from the synthesized MEL proc.

This actually isn’t TOO bad a hack since it’s localized and doesn’t change design much, but I was wondering if there’s a better way to handle this? Trying to write a generalized, testable function to wrap an arbitrary python callable to be invoked from MEL, while possible in theory, ends up with an absurd signature and limitations (may as well write one-offs like for above).


'''
Exposes the MelFunction decorator, which is basically a way of saying "here's some mel I couldn't get out of using....

@author: Steve-Dev
'''
import os
import maya.mel
class MelFunction( object ):
    '''
    Wraps mel functionality (I'm looking at you, bakeSimulation!) that doesn't
    work correcly in python while allowing us to call the function from python
    and eventually to replace it with real python when the python bug is fixed
    by adsk.
    
    The decorator points at a mel file in 	ools\maya\data with the file name as
    an argument. It read it in at decoration time. When the python wrapper
    function is called, the mel file is evaluated , string arguments are passed,
    and the mel proc is called.
    
    Please remember to add a line to the end of the mel file that includes the
    name of the mel proc as string, eg:
    
        proc string GetString() { return "string"; } 
        string $name = "GetString"
    
    When that is evaluated it will return "GetString" as a string -- but GetString is defined locally and can be executed by the decorate python call
    '''



    def __init__( self, melfile ):
        self.mel_file = melfile
        self.mel_args = None
        datapath = os.path.join( os.environ[os.environ["UL_project"]], "tools", "maya", "data", melfile )
        with open( datapath, "r" ) as fh:
            melstring = fh.read()
        self.mel_string = melstring

    def __call__( self, f ):

        def wrapped( *args, **kwargs ):
            theProc = maya.mel.eval( self.mel_string ) #@UndefinedVariable
            flags = self.make_mel_flags( **kwargs )
            items = self.make_mel_args( *args )
            return maya.mel.eval( theProc + "(" + flags + "  " + items + ")" ) #@UndefinedVariable

        return wrapped

    def make_mel_args( self, *args ):
        if not args: return ""
        return " ".join( [str( i ) for i in args] )

    def make_mel_flags( self, **kwargs ):
        if not kwargs: return ""
        fmt = lambda kv: "-" + kv[0] + " " + str( kv[1] )
        return " ".join( map( fmt, kwargs.items() ) )



I store the unavoidable mel in a data directory so it’s data rather than code.

Yeah but the problem is generating that MEL file (or string, in our case). Creating that procedurally was a route I didn’t want to start down. In your case you have it in a file, in our’s it is in code. Pick your poison.

The real design goal here is to be able to replace this mel crap with real code later - when they finally get their act together i’ll be able to delete or nullop the decorator and keep the ripples into the rest of the codebase small. It’s sucky that we have to do mel at all anymore, this is just a way of containing the damage. You probably also go the other way and create a generated bit of python that wrapped an unavoiable bit of mel. You could create the signature to some degree by parsing the help entry for the mel script, and maybe add some decorationt to enforce something like the correct meaning of arguments.

For the ‘must pass in string mel name’ case you coudl define a mel proc that just called ‘python (your_func_here())’ and pass that in – or is that what you’re already doing?

Yup, exactly. The eval’ed MEL proc just calls a python function in the same module. Of course, it required assigning the function to builtin so it was accessible in MEL’s app global namespace… It turned out straightforward enough and the hacks are localized to a single function.