1) that your function calls are not repeatable with the "G" hotkey the way they are with MEL commands and shelf buttons.
2) python functions are not by default undoable with a single undo. If you make 10 connections between 10 nodes, you will need to undo 10 times to get back to the state your scene was in before you ran the function.
One way you can get around this is to build a decorator function to wrap your regular functions and add that functionality to them. So that when you are done, it will look like:
@undoable
@repeatable
def myAwesomeFunction(valueA='totalyWickedMode'):
@repeatable
def myAwesomeFunction(valueA='totalyWickedMode'):
....doAwesomeStuff
The nice thing about the format of the code above is that it is easy to add or remove from any function, and we can edit the functionality of these two decorators in 1 place. So lets define our decorators starting with the easiest of the two, undoable.
def undoable(function):
'''A decorator that will make commands undoable in maya'''
def decoratorCode(*args, **kwargs):
cmds.undoInfo(openChunk=True)
functionReturn = None
try:
functionReturn = function(*args, **kwargs)
except:
print sys.exc_info()[1]
finally:
cmds.undoInfo(closeChunk=True)
return functionReturn
return decoratorCode
The above function will automaticly be called in place of the function its decorating (in this case myAwesomeFunction), passing as a variable the function being called (again, in this case it is myAwesomeFunction). Now the function inside the function 'undoable', can be named whatever you want, and it will be the function collecting our arguments and keyword arguments (if there are any). In this inner function is where we can openChunck in maya's undoInfo (which defines the start of our undo "chunk"). Then, we run the original function in a try statement, followed by a finally statement so that we can close the undo chunk regardless of whether or not our original command produces and error. Now if we run myAwesomeFunction() it should undo the whole function in a single undo.
Next is the somewhat tricky problem of making our functions repeatable. The reason this is harder is because we have to use a command "cmds.repeatLast()" that is undocumented. luckily if you do a help() on the mel version of the command, we can see the list of flags for this command. The two flags we will need are: "ac" and "acl".
"ac" being the command (which even tho we are using the python version of repeat last, we will need to feed a MEL command to it".
"acl" is just the label of the command, in this case we will label it with the name of our command.
def repeatable(function):
'''A decorator that will make commands repeatable in maya'''
def decoratorCode(*args, **kwargs):
functionReturn = None
argString = ''
if args:
for each in args:
argString += str(each)+', '
if kwargs:
for key, item in kwargs.iteritems():
argString += str(key)+'='+str(item)+', '
commandToRepeat = 'python("'+__name__+'.'+function.__name__+'('+argString+')")'
functionReturn = function(*args, **kwargs)
try:
cmds.repeatLast(ac=commandToRepeat, acl=function.__name__)
except:
pass
return functionReturn
return decoratorCode
The main difference here is that we need to construct our command into a mel command, understanding that our python command could be named anything (because many different commands will be using this decorator) . When we are done, our command string should look like (in this case):
python("awesomeTools.myAwesomeFunction(valueA=\"totalyWickedMode\"")
So the first thing we do is to turn our arguments and keyword arguments into a string in the same format as we would type to call it. Then we need to find the name of the module that contains this function (assuming we are not defining it in the script editor), the __name__ attribute in this case will give us that info, as it is inherited from the module. Now we just need the name of the function being decorated, again, we can use the __name__ attribute of the function (function.__name__) to get that info. Now we just stick them all together, and feed it as the mel command arg in cmds.repeatLast().
Hope this answers more questions than it creates for you,
Cheers,
Kris Andrews