MFnMesh.create() and undo

Has anybody got a trick for running an MFnMesh.create() with proper support outside an MPXCommand? I have yet to figure out a way to do this that doesn’t involve creating a plugin command (with attendant hassles) - I’d really like to just do


def api_mesh(verts, faces):
    with SomeMagicalUndoContextGoesHere():   # < but how?
        vertpoints = [api.MFloatPoint(v) for v in verts]
        polyCounts = [len(f) for f in faces]
        polyConnects = [i for i in itertools.chain.from_iterable(faces)]
        mObj = api.MObject()
        mFn = api.MFnMesh(mObj)
        return mFn.create(vertpoints, polyCounts, polyConnects)

I’ve tried the obvious (undoInfo, wrapping in a script, etc) but the meshes created this way are not part of the undo queue so their creation cannot be undone (they can still be deleted - just not undone). If i put it all into an MPXCommand it can be made to work but I’m a whiny baby and I don’t want to do that.

Anybody have a magic trick?

I am certainly not a definitive source, but I have never heard of a way to get undo support for api commands outside of MPXCommand. Also a number of pymel commands that call API commands directly don’t support undo, and I would imagine they would have employed an undo method if there was one available.

From the docs:

Undoing the operations of such a script will not be correct if the Maya Python API calls modify the model and do not properly support undo using the MPxCommand class.

Hey Theodox,

There is wrapper around maya’s commands that was supposed to be faster than PyMel; it’s called MRV.

It has a wrapping functionality that allowed api commands to be undoable by a dynamic instantiation as an MPxCommand.
So basically it defines a temporary command that becomes undoable.
The dynamic instantiation only got a small speed hit on first use (for defining the ‘plug-in command’) but aside from that it is supposed to be tons of times faster than PyMel.

Though reading the documentation again I see: “Full undo is only implemented for the MRV methods which reside in the ‘m’ namespace.”
Also I can’t find the exact line of documentation/code that mentioned the dynamic instantiation of the MPxCommand, so I might be mistaking the actual package. (Still think it was this one though!)
Anyway, here’s some more info:

https://pythonhosted.org/MRV/compare/index.html
https://pythonhosted.org/MRV/compare/features.html?highlight=undo

The last time I had a look at this (just reading the manual) was a long, long time ago.
So not sure if this is even of any interest and still relevant to the Maya scene today (especially the comparison with Pymel).

Good catch, that one looks interesting. Will post if it turns out to be useful.

Had a thought on something related to this today. It may be possible to capture the initial MFN “get” methods used in a function, then pass those values to a MPXCommand plugin to dynamically build the undo MFN “set” methods and restore the values.

First thing needed would be a decorator to find initial variable assignments. Came up with this:


import sys

class returnUndoValues(object):
    '''
    Decorator to return any variable's initial value
    '''
    
    def __init__(self, function, *args, **kwargs):
        #Get the function and create a dictionary to hold the initial locals
        self.function = function
        self._initLocals = {}
        
    def __call__(self, *args, **kwargs):
        #When the function is called, begin tracing
        sys.settrace(self.globaltrace)
        result = self.function(*args, **kwargs)
        sys.settrace(None)

    def globaltrace(self, frame, why, arg):
        #If the tracer is called, run the local trace
        if why == 'call':
            return self.localtrace
        else:
            return None

    def localtrace(self, frame, why, arg):
        #Trace what happens on each line
        if why == 'line' or why == 'return':
            for key, value in frame.f_locals.iteritems():
                self._initLocals.setdefault(key, value)
        return self.localtrace

    @property
    def initLocals(self):
        #Make it returnable after the fact
        return self._initLocals 

And here’s the decorator in use:

@returnUndoValues
def exampleFunction():
    value = True
    value = False

test = exampleFunction()
print exampleFunction.initLocals

{'value': True}

I’m pretty busy with work these days, so it may be a while before I can see if this would actually work. Of course if someone else wants to give it a try, go for it.

I think what’ you’d really need to do there is to find the MDgModifier and MDagModifier instances which do all the real work in an MPXcommand. It’s similar to something I do in Plugger