Reputation: 47
In Autodesk Maya I'm dynamically creating UI based on the number of floors a user wants to make (the variable numFloors is fetched from an intField). For each floor, I want to make a button that changes the height of the camera to look at the floor (this is for buildings). The problem I have is that I can't have my viewFloor() function know which button was pressed in order to activate it. Each time, the button passes the most recent floor rather than the specific floor.
I know why this occurs, the command is not dynamically stored when a button is being created so when a button is clicked, the command uses the most recent instance of the variable, which should be the last floor which is equal to the variable numFloors.
for i in range(0, numFloors):
floor = i
btnName = 'floor'+str(floor)+'ViewBtn'
btnLabel = "view Floor " + str(floor)
btnCmdVar = str(floor)
cmds.button(btnName, label= btnLabel, w=20, h=20, command=lambda
arg:self.viewFloor(btnCmdVar))
def viewFloor(self, arg):
print "arg = " + str(arg)
So when numFloors = 4, viewFloor will always have 4 passed to it regardless of which floor button was pressed. Like I said, I'm aware of why this happens, this code snippet was simply my best shot at creating a snippet of code that would communicate what I'm trying to do. Unfortunately, I have no idea how to actually accomplish this.
Some ideas I have are:
Some complicated setattr and getattr setup (but I have no idea how that would work)
or a scriptJob that detects when the button is pressed and passes that specific button's name to the viewFloor function (I could then just pull the digits from the string to get the floor). However, I cannot find any indication of the event or condition that occurs when a button is pressed in the Maya Python documentation. Because of this, I can't create the scriptJob.
If anyone could point me in the right direction (such as the scriptJob flags I might need or an example of a setattr getattr setup) or any other solution I am too uneducated to even conceive it would be very appreciated. Thanks in advance for any assistance!
Upvotes: 1
Views: 733
Reputation: 12218
This is a well known issue with the way lambdas work: it's documented here.
You can work around it a couple of ways:
functools.partial
instead of lambdafunctools.partial
is an object from the functools module that binds a callable and arguments together. You can use it almost the way you are using lambda:
from functools import partial
for i in range(0, numFloors):
floor = i
btnName = 'floor'+str(floor)+'ViewBtn'
btnLabel = "view Floor " + str(floor)
cmd = partial(self.viewFloor, i)
cmds.button(btnName, label= btnLabel, w=20, h=20, command=cmd)
def make_button(n):
def callback (_):
self.showFloor(n)
return cmds.button( label = 'floor '+ str(n), command = callback)
for i in range(6):
make_button(i)
Here the nested function def will capture the value the same way you want the lambda to -- but the nesting prevents the problem you're seeing
Incidentally in either strategy self.showFloor
will need to take an extra argument to accomodate the extra boolean that buttons always fire.
Upvotes: 2
Reputation: 47
OP here, thought I would post how I solved the problem in case it helps anyone else. Partial wasn't a viable option because the variable cmd (as found in the example) still would be updated with the loop and therefore each button still returned the last floor. The issue with creating dynamically generated UI in Maya is that its UI commands are functions rather than classes, meaning you can't actually store any data in a command only pass it. Fortunately, someone has gone in ahead of me and rewritten the entirety of Maya's UI commands as classes. You can find that library here: mGui
The functional code when written for this library is as follows:
for i in range(0, numFloors):
floor = i
BTN= mGui.Button()
BTN.data.[`floor`] = floor
BTN.command = self.viewFloor
def viewFloor(self, defaultArg, **kw):
floor = str(kw[`floor`])
Upvotes: 0