Maya Drop Window

I am trying to implement a Drop Window using the MASH api
http://help.autodesk.com/cloudhelp/2018/ENU/Maya-Tech-Docs/MASH/DropWindow.html

You can see in the example (copied below) that it works as a python generator, which I’m quite new to. I am really confused about the seemingly cyclical reference to “smartPreset” - it’s defined outside the function but referenced inside the function.

It all seems to work fine when run directly, but I want to import my script as a module and call the function to build the window. That’s where it all falls apart and I get a NameError. How can I create this generator window after I import it as a module? I hope that makes sense, I’m fumbling my way through this.

import flux.core as fx
def runPreset():
    '''
    ... Some cool code ...
    '''
    fx.DropWindow.getDrop(label='Put a window title here:', callback=lambda data: smartPreset.send(data))
    node = yield
    '''
    'node' will be a string of all the nodes you dropped onto the Drop Window
    Multiple nodes will be newline seperated.
    One way to split the string is like so:
    '''
    nodeList = node.split('\n')
    print nodeList # print it out so that this example actually does something
    '''
    Finish the Generator with a Yield so that it knows where to stop.
    '''
    yield

'''
Now run your Smart Preset like so
'''
smartPreset = runPreset()
smartPreset.next()

yeah, that code seems weird.

What does your calling code look like when you attempt to use it though?

So in my actual case, my module is called NKTR_hair_ctrl_helper and it includes:

def reset_hair_rig_drop_window():
    ...
    fx.DropWindow.getDrop(steps, title='Reset ctrl rig', callback=lambda data: reset_rig_drop.send(data))
    dropped_nodes = yield
    ...
    yield

if __name__ == '__main__':
    reset_rig_drop = reset_hair_rig_drop_window()
    reset_rig_drop.next()

And that works fine, but I want to have a shelf button that has:

import NKTR_hair_ctrl_helper
reset_rig_drop = NKTR_hair_ctrl_helper.reset_hair_rig_drop_window()
reset_rig_drop.next()

And here I get the NameError

If anyone wants to take a deeper look, the DropWindow code is in C:\Program Files\Autodesk\Maya2018\plug-ins\MASH\scripts\flux\ui\core.py

But I can’t really get much from it

Yeah, so that name error is because the lambda is looking in the module namespace for reset_rig_drop, but the generator actually exists in the namespace that you’ve imported it into.

This should work, unless my guess is wrong. But it’s ugly, and I personally hate it.

import NKTR_hair_ctrl_helper
NKTR_hair_ctrl_helper.reset_rig_drop = NKTR_hair_ctrl_helper.reset_hair_rig_drop_window()
NKTR_hair_ctrl_helper.reset_rig_drop.next()

I can’t actually test it, but this should work in a still ugly/weird kind of way, but less awful.
Basically the lambda should check the namespace of the reset_hair_rig_drop_window function, before stepping out and checking the module namespace.


def reset_hair_rig_drop_window():
	def do_drop():
		...
		fx.DropWindow.getDrop(steps, title='Reset ctrl rig', callback=lambda data: drop.send(data))
		dropped_nodes = yield
		...
		yield

	drop = do_drop()
	drop.next()

import NKTR_hair_ctrl_helper
NKTR_hair_ctrl_helper.reset_hair_rig_drop_window()
1 Like

Wonderful, thank you! I tried the second method and it works. I agree the second option is cleaner from the user-perspective.
Out of curiosity, I tried the first method too, and it worked as well, so thank you again. Namespaces seem to be the thing I get most confused and hung up on, but I’m glad I’m not the only one who things the original problem was kind of weird, and it wasn’t just a dumb error. If it makes you feel better, this script is just for a specific project, so the “ugliness” won’t be that long-lived.

To help me understand a bit more, can you clarify what is happening in line 2 of the first solution? Is it possible to just create any arbitrary variable in the namespace of a module by defining my_module.new_variable? Or does this only work because there’s kind of a reference to NKTR_hair_ctrl_helper.reset_rig_drop in the module itself?

Edit: Just did a little play around, and it seems like you can indeed define a new variable in the module’s namespace from outside the module. I can only assume that’s typically not done… but I guess it could be useful in some circumstances?

So just about everything in python is mutable by default, this includes module objects.

For example, you can do this (but you shouldn’t)

import sys
sys.foo = 'WHY OH WHY DID I DO THIS!?!?'

Now any other module in your program, if they are imported after you set the value.

print(sys.foo)

But really, don’t ever do this with the sys module.

So this next part involves how names are looked up in python it is a bit more complex, the short simple version though, is that when you are inside a function, first the name is checked in the local function, in this case that would be inside the lambda, if it can’t find it there, it checks the next outer scope, which would be inside runPreset, it then keeps checking outer scopes, until it hits the module global space. If it can’t find it there, a NameError exception gets raised.

Now if you combine those two facts, the first example is basically inserting the name smartPreset into the NKTR_hair_ctrl_helper module’s namespace, so that when the drop happens, the lambda will actually find the name when the drop occurs.

This is actually also why the second option works, it just relies on placing everything into a function’s scope, which is a bit more portable than relying on module’s global space.

1 Like

Gotcha… thanks so much for taking the time to explain.