Wednesday, February 25, 2009

Maya and Python lambdas, part 3

A thought occurred to me this morning before coming into work. I was still thinking that there had to be some way of further improving my use of lambdas in Maya GUI creation. Then it hit me: assign the data from the loop as a default value for one of the lambda's arguments!

import maya.cmds as mc

def showStuff (stuff):
print "You've given me %s!" % stuff

stuffList = ["an apple", "a pear", "a pickle"]
mc.window("Stuff to Give", w=300, h=200)
cl = columnLayout()
for item in stuffList:
mc.button(l=item, c=lambda x, i=item:showStuff(i))

Look, ma...no lambda factory! :) Here's my guess as to why this works when it doesn't work to put the variable "inside" the lambda...

By stuffing a variable into the function/method call inside the lambda function, it appears that the value of the variable isn't retrieved until the lambda function is executed. For a variable defined by a loop, this means that the variable value is the same as it was during the last iteration through the loop.

However, by taking a variable and assigning it as the default for one of the lambda's arguments, the value assigned to that variable is retrieved and stored in the lambda function definition. When called by Maya when the appropriate GUI element is used, the lambda function already has that value, and knows to pass it as the default for the appropriate argument. Because no other data is passed to replace it, it can be used reliably inside the function as part of the real function call we want to make.

The only thing to keep in mind when using this technique is the extra data that Maya passes on its own, which is caught (and promptly ignored) in the example above by the x argument.

Clean and simple. Me likey.

5 comments:

Bruno Andrade said...

Hi Justin, sorry for commenting offtopic. Tween machine is not working in maya 2009, any ideas?
I got the xml_lib in the script folder as well. cheers

Justin said...

No worries, Bruno. I discovered that bug a little bit ago, but haven't yet had the time to publish a fix. I'll get it up in the next day or two, and post an update when it's ready.

Josh said...

Hi Justin-

First off, thanks very much for these lambda tutorials with Maya UIs. This has been very helpful in my switch from MEL to Python. I'm still trying to wrap my head around these lambdas, and came across one odd issue. I have these two lines of UI code:

cmds.shelfButton(('iconButton' + str(incName)), style = 'iconOnly', parent = 'iconsLayout', label = pose, image1 = bmpImage, command = lambda applyCopy = pose: igApplyPose(applyCopy))

cmds.menuItem(('iconPopupMenu' + str(incName) + '_Delete'), label = 'Delete', command = lambda x, deleteCopy = pose: igDeletePose(deleteCopy))

My question is that besides the difference in calling a menuItem or a shelfButton, these calls seem pretty much the same. However, when I do "lambda x, y = pose:" in the shelfButton call, like I have in the menuItem call, instead of using the "lambda y = pose" call I get an error. Could you explain why these two lines of more or less the same code require two different ways to call lambda?

Hopefully that made sense enough for you to elaborate. Thanks again for your big help!

Justin said...

Josh,

Thanks for the comment! I'm glad this stuff helps. It's been a while since I tossed anything up here, and your comment reminded me that I should probably get on the ball.

To answer your question, it has to do with the data that Maya sends on its own when certain UI commands are executed. You might think that it sends the same stuff for everything, but it doesn't. To check what Maya is passing on its own for a given UI element, point its command to a function like this (and I doubt that the indentation will work, so forgive me)...

def blah(*args):
print args

The "*" in front of the "args" argument means that Python will capture all non-keyword arguments and place them in "args" as a tuple. For a shelfButton, the tuple is empty, but for a menuItem it contains a single Unicode string: u'0' (don't ask me why...I haven't figured out that part yet).

Because nothing is passed for a shelfButton command, pointing that command to a function (lambda or otherwise) that expects at least one argument will raise an exception, which is why "lambda x, y=foo: bar" fails. Keyword arguments are optional by definition, though, which is why you can drop "x" (the required argument) and "lambda y=foo: bar" will work when shelfButton isn't passing anything.

Josh said...

Awesome! That definitely makes things much, much clearer. Thanks again for all the help.