Add a script to Shelf as a module to be imported instead of pasted code, how to?

1,775 views
Skip to first unread message

Gabriele Bartoli

unread,
Jul 15, 2014, 11:54:27 AM7/15/14
to python_in...@googlegroups.com
Hello there.

This is what I'd like to achieve: let's say I have a script that I saved as testScript.py, and it is located in the scripts folder, so that Maya can find it. I want to add an icon to my custom shelf which, instead of running the code of script, "loads" the script file and runs it. This would allow me to edit the script and avoid substituting the code inside the custom shelf icon.
Here is a code example:
# Load maya commands module
import maya.cmds as mc

# This prevents multiple windows to pop up
if mc.window("myWindow", ex=True):
    mc
.deleteUI("myWindow", window=True)

# Window set-up
mc
.window("myWindow",title="Test Window", s=False, wh=(300,100))
mc
.columnLayout(adj=True)
toggle_test
= mc.checkBox(label = 'Toggle me!', value = False)
mc
.button(l="Press to print the value of the checkbox below", w=300, h=100,
          command
= 'printIt()'
         
)
mc
.showWindow("myWindow")    
   

# Print the value
def printIt():    
   
   
# Query toggle status
   
var = mc.checkBox(toggle_test, query = True, value = True )
   
   
# Print the status
   
print "Checkbox's status is:", var

The code runs fine if selected and executed in the script editor. I created a shelf button with the following code:
import testScript

reload (testScript)


After restarting maya, or deleting the global instance of the printIt function, if I click on the shelf button the script runs and the UI is shown. If I then click on the UI button, I get the following error:

# Error: NameError: file <maya console> line 1: name 'printIt' is not defined #

Ok, I get what is happening. The button press is running the code 'printIt()', but the function has not been imported in global space (I might be throwing wrong terms around a bit, please correct me if that happens) and it can't be found.

I am willing to edit the script, but I want it to work both by importing it or running it from the script editor. First option is to edit the shelf button code to:
from testScript_01 import *

import testScript

reload (testScript)


This seems to work fine, but as I understand is not the suggested way of solving the problem.

I could also edit
command = 'printIt()'
to
command = 'testScript.printIt()'

but this would have to side effects: first, if the script file is renamed or imported as something else, the function would not work anymore. Second, running the code inside the script editor would not work anymore.

So, any idea on how I could fix this issue? Is there a way I could feed the proper filename to the code?

Something like:

import testScript

reload (testScript)
# Add a line of code that feeds the string 'testScript' to the imported module

that then could be used inside the script as:
# Declare var for fed string

input_string = #fed string

...

command = input_string + '.printIt()'

Hopefully I managed to explain the issue properly :P

Thanks in advance!

Marcus Ottosson

unread,
Jul 15, 2014, 12:10:20 PM7/15/14
to python_in...@googlegroups.com

Hi Gabriele,

All you need to do is import your script in the shelf button, it will take care of the “loading” for you.

testScript.py

def test_function():
   print "Test complete"

Your shelf button can then consist of something like this:

import testScript
testScript.test_function()

As a side note, you shouldn’t have anything outside of functions or classes that executes anything; so imports and attribute definitions are okay, but showing and deleting windows are not. Reload is useful if you change your script on disk; so really only for dev, not during production, you can do something like what you did:

import testScript
reload(testScript)
testScript.test_function()

Also, have a look at PEP08 for naming conventions; mainly how to stick with snake_case:

test_script.py

def test_function():
   print "Test complete"

Best,
Marcus



--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/python_inside_maya/be3c13c1-48f0-41d3-8184-e36fda61073e%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Marcus Ottosson
konstr...@gmail.com

Leonardo Bruni

unread,
Jul 15, 2014, 4:03:14 PM7/15/14
to python_in...@googlegroups.com
Hi, try to put def printIt() before the interface creation.
I'ts the first and simplest way if you don't want put all in a class

# Load maya commands module
import maya.cmds as mc

# Print the value
def printIt():    
    
    
# Query toggle status
    
var = mc.checkBox(toggle_test, query = True, value = True )
    
    
# Print the status
    
print "Checkbox's status is:", var

# This prevents multiple windows to pop up
if mc.window("myWindow", ex=True):
    mc
.deleteUI("myWindow", window=True)

# Window set-up
mc
.window("myWindow",title="Test Window", s=False, wh=(300,100))
mc
.columnLayout(adj=True)
toggle_test 
= mc.checkBox(label = 'Toggle me!', value = False)
mc
.button(l="Press to print the value of the checkbox below", w=300, h=100,
          command 
= 'printIt()'
          
)
mc
.showWindow("myWindow")    

--

Gabriele Bartoli

unread,
Jul 15, 2014, 6:49:15 PM7/15/14
to python_in...@googlegroups.com
Hi Marcus,

thanks for the reply. I already tested a version of the script with the UI enclosed in a function. The script looked like this:

import maya.cmds as mc

def load_UI():
   
# UI mumbo jumbo

def printIt():
   
# same as before

# call the function
load_UI
()

I'm almost sure it worked both inside the script editor AND by importing it from the shelf, without calling the function from the button. I'm not using a computer with Maya installed right now, so I'll try again. What I am sure of is that it still gave me problems when the UI button calls for the printIt() function. I'll give it a shot and update the post ASAP.

Thanks a lot, especially for the PEP08 info.


Gabriele Bartoli

unread,
Jul 15, 2014, 6:53:29 PM7/15/14
to python_in...@googlegroups.com
Hi Reyan.

Thanks, I'll give it a shot and see what happens. I must say that I don't understand how using a Class might help me. If you'd like to explain further, I would really appreciate it!

Cheers!

Justin Israel

unread,
Jul 15, 2014, 7:54:23 PM7/15/14
to
I think the primary suggestion about ensuring your code is all within functions and not executing at the module level is the real focus. And then your shelf button does the import and calls the entry point function.

But I did just notice something that may help correct your issue. Instead of using a string reference to the name of your button callback, you should switch it to use the actual callable:

From this:
mc.button(l="Press to print the value of the checkbox below", w=300, h=100, command = 'printIt()' )
To this:
mc.button(l="Press to print the value of the checkbox below", w=300, h=100, command=printIt )
That way your button doesn't have to try and look up your printIt() function in a particular scope (or global scope). It just uses the callable object directly.

- Justin




--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_maya+unsub...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/python_inside_maya/bd96c3b7-9fe1-4f38-b7fb-8e4506792af2%40googlegroups.com.

Gabriele Bartoli

unread,
Jul 16, 2014, 11:49:21 AM7/16/14
to python_in...@googlegroups.com
Following everybody's hints, I got to this point:

# Load maya commands module
import maya.cmds as mc

# Build the UI
def launch_UI():  
   
   
# This prevents multiple windows to pop up

   
if mc.window("myWindow", ex=True):
        mc
.deleteUI("myWindow", window=True)
   
   
# Window set-up
    mc
.window("myWindow",title="Test Window", s=False, wh=(300,100))
    mc
.columnLayout(adj=True)
    toggle_test
= mc.checkBox(label = 'Toggle me!', value = False)    
    mc
.button(l="Press to print the value of the checkbox below", w=300, h=100,

              command
= printIt)

    mc
.showWindow("myWindow")    
       
# Print the value
def printIt():
   
   
# Query toggle status
   
var = mc.checkBox(toggle_test, query = True, value = True )
   
   
# Print the status
   
print "Checkbox's status is:", var

   
launch_UI
()

And the shelf button still looks like this:

import testScript
reload
(testScript)

I'm sticking with having the UI launched from within the script simply because it allows me to run the code from both the script editor and the shelf. I seem to understand it shouldn't be like that, so I'll fix it when the debugging is done :)

When I run it, the UI pops up and at button press I get this error:

# Error: printIt() takes no arguments (1 given)
# TypeError: printIt() takes no arguments (1 given) #

So I edited the printIt function like this:

# Print the value
def printIt(test):
   
   
print test
   
   
# Query toggle status

   
var = mc.checkBox(toggle_test, query = True, value = True )
   
   
# Print the status
   
print "Checkbox's status is:", var

I get a printout of test as "False", but I am at a loss to what that value refers to and from where it comes from. 

Plus, I get a new error :P

# Error: NameError: file D:/GoogleDrive/Maya/shared_folder/scripts\testScript_01.py line 25: global name 'toggle_test' is not defined #

I was thinking of feeding "toggle_test" to print it using the call back, like this:

mc.button(l="Press to print the value of the checkbox below", w=300, h=100, command=printIt(toggle_test) )

This doesn't seem to work though. This is what I get:

# Error: TypeError: file D:/GoogleDrive/Maya/shared_folder/scripts\testScript_01.py line 16: Invalid arguments for flag 'command'.  Expected string or function, got NoneType #

I guess I could simply define it as a global variable and be done with it, but I don't think is the best option. Unless it is the ONLY option, which would make it the best option too >P

Thanks a lot guys, and I apologize if I am being a N00b

Cheers

Yi Liang Siew

unread,
Jul 16, 2014, 12:09:39 PM7/16/14
to python_in...@googlegroups.com
Hi Gabriele:

You might want to look at some sample code for creating windows. (e.g. http://stackoverflow.com/questions/3492106/creating-a-maya-ui)

However, more to the point, the reason you cannot access toggle_test is because it is out of scope, and only launchUI() knows about it. If you want printIt to have access to toggle_test, I would place these two methods in a class and have that variable be a instance variable.
--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/python_inside_maya/3075c5c8-5215-461f-86d3-39adde494131%40googlegroups.com.

Justin Israel

unread,
Jul 16, 2014, 5:08:01 PM7/16/14
to python_in...@googlegroups.com
You could either structure it as a class, as suggested, or you could use my most favorite member of the stdlib, and wrap your callback:

import maya.cmds as mc
from functools import partial 

def launch_UI():   
    
if mc.window("myWindow", ex=True):
        mc.deleteUI("myWindow", window=True
)

    mc.window("myWindow",title="Test Window", s=False, wh=(300,100))
    mc.columnLayout(adj=True)
    toggle_test = mc.checkBox(label = 'Toggle me!', value = False)    
    mc.button(l="Press to print the value of the checkbox below", 
                w=300, h=100, command=partial(printIt, toggle_test))

    mc.showWindow("myWindow")    

def printIt(checkBox, *args):
    var = mc.checkBox(checkBox, query = True, value = True )
    
print "Checkbox's status is:", var

launch_UI()

partial() lets you pass a callable and a number of args and kwargs to it to bind it up as a new callable. So in this example we just set the callback to already know the checkbox name, and to pass it to printIt(). 

The bit about that extra arg is the stuff that the Maya button callback sends along. If it were a checkbox callback, it would tell you if it were checked or not. You just have to add *args to swallow up that extra arg since we don't care about it right now. 



Gabriele Bartoli

unread,
Jul 17, 2014, 10:48:10 AM7/17/14
to python_in...@googlegroups.com
Hi Yi,

thanks for the link. It seems hard to find proper examples and guides regarding the Maya UI. Any other link you might have would be appreciated!

Gabriele Bartoli

unread,
Jul 17, 2014, 10:54:26 AM7/17/14
to python_in...@googlegroups.com
Hello again. I came up with three versions of the script, all of them working properly. I'd like your opinion regarding which one you guys prefer!

Version one, which works because I changed the scope of toggle_test to 'global'

# Build the UI
def launch_UI():

   
   
global toggle_test  
   
   
# This prevents multiple windows to pop up

   
if mc.window("myWindow", ex=True):
        mc
.deleteUI("myWindow", window=True)
   
   
# Window set-up
    mc
.window("myWindow",title="Test Window", s=False, wh=(300,100))
    mc
.columnLayout(adj=True)
    toggle_test
= mc.checkBox(label = 'Toggle me!', value = False)    
    mc
.button(l="Press to print the value of the checkbox below", w=300, h=100,

              command
= printIt)              
    mc
.showWindow("myWindow")    
       
# Print the value
def printIt(*args):

   
# Query toggle status
   
var = mc.checkBox(toggle_test, query = True, value = True )
   
   
# Print the status
   
print "Checkbox's status is:", var

   
launch_UI
()

Version two, which uses the partial command (which is a wrapper, isn't it? I still have to 'wrap' may head around wrappers)

# Build the UI
def launch_UI():
 
   
   
# This prevents multiple windows to pop up

   
if mc.window("myWindow", ex=True):
        mc
.deleteUI("myWindow", window=True)
   
   
# Window set-up
    mc
.window("myWindow",title="Test Window", s=False, wh=(300,100))
    mc
.columnLayout(adj=True)
    toggle_test
= mc.checkBox(label = 'Toggle me!', value = False)    
    mc
.button(l="Press to print the value of the checkbox below", w=300, h=100,

              command
= partial(printIt, toggle_test))            
    mc
.showWindow("myWindow")    
       
# Print the value
def printIt(check_box, *args):
   
# Query toggle status
   
var = mc.checkBox(check_box, query = True, value = True )

   
   
# Print the status
   
print "Checkbox's status is:", var

   
launch_UI
()

Version three, which works with a Class:

class Custom_tool:
   
def __init__(self):  
       
       
# This prevents multiple windows to pop up

       
if mc.window("myWindow", ex=True):
            mc
.deleteUI("myWindow", window=True)
       
       
# Window set-up
        mc
.window("myWindow",title="Test Window", s=False, wh=(300,100))
        mc
.columnLayout(adj=True)

       
self.toggle_test = mc.checkBox(label = 'Toggle me!', value = False)            
        mc
.button(l="Press to print the value of the checkbox below", w=300, h=100,
                  command
= self.printIt)            
        mc
.showWindow("myWindow")          
           
   
# Print the value
   
def printIt(self, *args):
       
       
# Query toggle status
       
var = mc.checkBox(self.toggle_test, query = True, value = True )

       
       
# Print the status
       
print "Checkbox's status is:", var

   
new_tool
= Custom_tool()

I like the second better, because the global variable in v1 kinda throws me off and because using a custom class to create only one object feels like overshooting. But hey, I might be wrong! Any feedback would be welcome!

Thanks a lot!

Marcus Ottosson

unread,
Jul 17, 2014, 11:02:26 AM7/17/14
to python_in...@googlegroups.com
Sharing state amongst functions is one reason to use classes. I'll have to go with option 3, which looks pretty darn good too. You could move one step ahead and have a look at the Singleton pattern to keep windows from co-existing.



--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Marcus Ottosson
konstr...@gmail.com

Justin Israel

unread,
Jul 17, 2014, 3:57:17 PM7/17/14
to python_in...@googlegroups.com

Definitely not the first one. Avoid relying on globals when possible.
The second and third are equally fine right now. But if you start needing more state such as storing more widgets or values, then the class will end up becoming the better choice.

--

Gabriele Bartoli

unread,
Jul 18, 2014, 5:05:46 AM7/18/14
to python_in...@googlegroups.com
Thanks man, I'll give it a shot!

Hopefully I'm going to be able to build a decent enough skill set to land a job in a studio by the end of the year :P
Message has been deleted

Justin Israel

unread,
Jul 18, 2014, 5:49:46 AM7/18/14
to python_in...@googlegroups.com

For sure. Please share what you have when you are ready. We can all give you some help if you get stuck again.

On 18/07/2014 9:07 PM, "Gabriele Bartoli" <prodegu...@gmail.com> wrote:
Thanks man, I appreciate the feedback.

Needless to say that I'm not working only on a test script, but I have something else up my sleeve. Would you like to give it a try when I am done with it?

Cheers


On Thursday, 17 July 2014 21:57:17 UTC+2, Justin Israel wrote:

Definitely not the first one. Avoid relying on globals when possible.
The second and third are equally fine right now. But if you start needing more state such as storing more widgets or values, then the class will end up becoming the better choice.


--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.

Gabriele Bartoli

unread,
Jul 18, 2014, 5:51:07 AM7/18/14
to python_in...@googlegroups.com
Thanks man, I appreciate the feedback.

Needless to say that I'm not working only on a test script, but I have something else up my sleeve. Would you like to give it a try when I am done with it?

Cheers

P.S. I'm still trying to understand why i have to remove the empty parentheses when calling the callable function. I've been looking around the internet but I can't find an answer. :(

To make it clearer, why

command = my_function


instead of

command = my_function()

I think I am missing something syntax-wise.


On Thursday, 17 July 2014 21:57:17 UTC+2, Justin Israel wrote:

Justin Israel

unread,
Jul 18, 2014, 6:06:37 AM7/18/14
to python_in...@googlegroups.com
It is the difference between calling a callable object to get its return value, vs passing a reference to the object.
You call a function by doing:    aFunc()
But aFunc is an object which can be passed around. So if you pass aFunc to something else, then that target has a reference to the same callable. This is how "callbacks" work. You don't want to execute the function at that moment in time. You just want to pass a reference to it, so that at a later point in time the function can be called by a different owner. 

For more detailed information, you can look at the __call__() special method of objects:

This is what defines the behavior for when you do aFunc() on the object aFunc

Consider this:
def f():
    print "hello"

f()
# hello
f.__call__()
# hello



--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.

Gabriele Bartoli

unread,
Jul 18, 2014, 7:14:32 AM7/18/14
to python_in...@googlegroups.com
Ok, let me see if I'm getting this straight:

if I want to assign the returned value of a function to a variable, I do

a = f()

which means that the function is executed, does what it has to do and returns a value which is assigned to the defined a variable.

On the other hand, when working with the command flag like this

command = f

I'm not asking for the function to be executed when the code is compiled, but to assign the target of the variable 'f' to the variable 'command'. When the button is clicked, that is when the function is executed.

So using partial like this

command = partial(f, val)

I'm wrapping two values together, telling command to target the wrapper and, when the button is clicked, the wrapper is unwrapped and the function executed with 'val' as its argument.

Did I get this right? :P


To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_maya+unsub...@googlegroups.com.

Marcus Ottosson

unread,
Jul 18, 2014, 7:31:15 AM7/18/14
to python_in...@googlegroups.com
Justin, I honestly wouldn't introduce Python to someone just starting out by going through functools. In his case, classes is the more straightforward approach and it encourages better design; partial is in my opinion more a workaround than a solution, probably best used as a last resort. Using lambda would save him from importing an additional package and give him less of a learning curve, but again, both of those are workarounds imo.


To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/python_inside_maya/9fcb19aa-8bc9-44b4-bec3-bfedb4694469%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Marcus Ottosson
konstr...@gmail.com

Justin Israel

unread,
Jul 18, 2014, 7:47:19 AM7/18/14
to
Marcus, 

partial() is a formal version of a closure (lambda) so if you recommend a lambda, then a partial() is a safer version of this. It does a proper closure (lambda has the potential to have scoping issues if not careful).

I am not here to judge whether Gabriele is more capable of comprehending classes vs closures. I only presented options, and am answering questions to the best of my abilities when asked.

Gabriele, please disregard Marcus's interjections. functools.partial() is not a 'workaround'. It is a tool for performing a closure. It is like you said, a 'wrapper' that says "I will give you back a new callable that will automatically use these default arguments and keywords that you have given me". You can have a function called  foo(x). foo() requires a single argument 'x'. If I write:   wrapped = functools.partial(foo, x)    , then 'wrapped' is a new callable that is a 'closure' of foo() over x. When I call  wrapped(), it is really calling   foo(x)  for me with the baked in x value. Honestly though, I can't see classes being any more or less complex than closures so they might as well be met on the same level. A class has methods, and methods are functions that are 'baked' with an implicit first argument that is the current instance at that moment. 

Yes, you seem to have explained your understanding of the callable just fine. The only correction I would make is where you are describing what "command" is in this context. "command" is a parameter to a function. Namely, the button function that accepts a callable as a parameter. Within this function it will receive the callable, passed through the parameter "command". Within the scope of that function (which is completely transparent to you), it would be working with a local variable "command" to save your callback, and use it later when the button is clicked.




To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_maya+unsubscribe@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_maya+unsub...@googlegroups.com.



--
Marcus Ottosson
konstr...@gmail.com

--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_maya+unsub...@googlegroups.com.

Marcus Ottosson

unread,
Jul 18, 2014, 7:52:43 AM7/18/14
to python_in...@googlegroups.com
Let the battle begin! Haha, just kidding. Not sure why you took it personally, Justin, but I'll leave you to it then. No harm intended. :)


On 18 July 2014 12:46, Justin Israel <justin...@gmail.com> wrote:
Marcus, 

partial() is a formal version of a closure (lambda) so if you recommend a lambda, then a partial() is a safer version of this. It does a proper closure (lambda has the potential to have scoping issues if not careful).

I am not here to judge whether Gabriele is more capable of comprehending classes vs closures. I only presented options, and am answering questions to the best of my abilities when asked.

Gabriele, please disregard Marcus's interjections. functools.partial() is not a 'workaround'. It is a tool for performing a closure. It is like you said, a 'wrapper' that says "I will give you back a new callable that will automatically use these default arguments and keywords that you have given me. You can have a function called  foo(x). foo() requires a single argument 'x'. If I write:   wrapped = functools.partial(foo, x)    , then 'wrapped' is a new callable that is a 'closure' of foo() over x. When I call  wrapped(), it is really calling   foo(x)  for me with the baked in x value. Honestly though, I can't see classes being any more or less complex than closures so they might as well be met on the same level. A class has methods, and methods are functions that are 'baked' with an implicit first argument that is the current instance at that moment. 

Yes, you seem to have explained your understanding of the callable just fine. The only correction I would make is where you are describing what "command" is in this context. "command" is a parameter to a function. Namely, the button function that accepts a callable as a parameter. Within this function it will receive the callable, passed through the parameter "command". Within the scope of that function (which is completely transparent to you), it would be working with a local variable "command" to save your callback, and use it later when the button is clicked.


On Fri, Jul 18, 2014 at 11:31 PM, Marcus Ottosson <konstr...@gmail.com> wrote:

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Marcus Ottosson
konstr...@gmail.com

Gabriele Bartoli

unread,
Jul 18, 2014, 8:44:38 AM7/18/14
to python_in...@googlegroups.com
Thanks to the both of you, any help is well accepted. I only took a python class on coursera and I am using the internet as a guide, so I am sure there is much more to learn, especially given that my only programming background is a little bit of C, Mel and Renderman.

As of now, I'm fixing my "real" script adapting it to classes (I already have a class in there, I'm building a second for the UI) and I am also using partial() because it appears to be a necessity in a couple of situations. I'll post the final script as soon as I am done with fixing it. It already worked fine when ran from the script editor, I ended up making my life harder by wanting to import the script file through the shelf button >P

Thanks again!!!!!

Gabriele Bartoli

unread,
Jul 18, 2014, 11:39:08 AM7/18/14
to python_in...@googlegroups.com
Here I am!

I adjusted my script, and I'd like for you to give it a go. It is a set of tools for node renaming purposes. It has a Help section which should cover all the information needed to use it. The short version of it is that it works on the assumption of having a parent_child naming convention and aims to rename all the nodes in the hierarchy using such rule.

If you have the time, I'd like for you to test it out and give me some feedback regarding the UI, the tools and the overall usefulness. It would be really appreciated! It is kind of long, so don't waste too much time on the coding part.

You'll find it here: http://pastebin.ubuntu.com/7814931/

Cheers!





On Tuesday, 15 July 2014 17:54:27 UTC+2, Gabriele Bartoli wrote:

Justin Israel

unread,
Jul 18, 2014, 4:45:02 PM7/18/14
to python_in...@googlegroups.com
On Fri, Jul 18, 2014 at 11:52 PM, Marcus Ottosson <konstr...@gmail.com> wrote:
Let the battle begin! Haha, just kidding. Not sure why you took it personally, Justin, but I'll leave you to it then. No harm intended. :)

Not sure why my reply was so unexpected. Feel free to contribute to any conversation, however you want. You don't have to leave me to it. I just didn't appreciate the implication that the information I am offering is a workaround. Especially after Gabriele clearly understood the concepts. Classes are useful. Closures are useful. And avoiding "extra imports" don't really make sense to me as an argument when its the standard library, if the reason is avoiding dependencies.
 
Because I use functools.partial() so often in my own UI development, and find it indispensable, I thought it best to offer it as an option for solving the problem. Hopefully I do my best to avoid teaching hacks or workarounds, when better solutions are available  :-)
 

Justin Israel

unread,
Jul 18, 2014, 5:36:52 PM7/18/14
to python_in...@googlegroups.com
I can offer a couple points of feedback so far, but I didn't get too deep into the too since it has some compatibility issues with python2.6 (Maya 2013).
If you use the str.format() approach for formatting text, python2.6 doesn't support the anonymous fields. That was introduced in python2.7

So in python2.7, this is valid:     "My string says {} {}!".format("Hello", "World")
But in python2.6 you have to name the fields:   "My string says {0} {1}!".format("Hello", "World")

Also, there are a couple button callbacks where you use "mm.eval()", but if someone doesn't have maya.mel imported as mm in their global scope (either having done it manually in the script editor, or in their userSetup.py) then it will produce an error. That could be another candidate for partial, doing something like partial(mm.eval, "my mel command"). Otherwise you have to do the more verbose string approach that has to first import maya.mel: "import maya.mel as mm; mm.eval('my mel command')". I kind of prefer the former, if you have to call mel as a python callback. Or it can call a python method you have set up already that will cleanly call the mel command, like:   self.__prefixHierarchy()

Those two blocking issues aside, a couple of other notes:

* "New-style" python classes always at least subclass object:   class MyClass(object):

* When testing if a list is empty, instead of allocating a new empty list object each time, just to test if it is equal to the other list, you can simply check if your list is empty using:     if not myList

* It is usually best not to have your class constructor have side effects, such as showing the window. It should have a separate method like show(). That way someone can import and create an instance of the window and show it when they want to. Constructors are for setting up the state of the class.

* The approach where you are using the tool_launcher() method with a string tool name might be cleaner if you made those tools formal functions or methods. There are a couple of places where you test for the literal string names in order to do different logic, which could be bug prone because changing the name in one spot means you have to find and replace all of the literal string references. Maybe you might want to define those once in your class as class constants:

class MyClass(object):
    ACTION_NAME_1 = "action1"
    ACTION_NAME_2 = "action2"

Or if you want to make the association between an action and a displayable name, you can register a dictionary in your constructor that maps them:

self._actions = {
    "Action 1": self.__action1,
    "Action 2": self.__action2,
}

Then you won't have to do the 'if actionName == "literal name"' test in multiple places to figure out the action to perform. You can just look up the associated action in the dict:  self._actions[actionName]

* rsplit() is a method of a string
so this:    str.rsplit(str(temp_name), split_char, 1)
can be written as:    temp_name.rsplit(split_char, 1)








--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.

Gabriele Bartoli

unread,
Jul 21, 2014, 7:08:07 AM7/21/14
to python_in...@googlegroups.com
Hi, sorry for the late reply. Busy weekend!


On Friday, 18 July 2014 23:36:52 UTC+2, Justin Israel wrote:
I can offer a couple points of feedback so far, but I didn't get too deep into the too since it has some compatibility issues with python2.6 (Maya 2013).
If you use the str.format() approach for formatting text, python2.6 doesn't support the anonymous fields. That was introduced in python2.7

So in python2.7, this is valid:     "My string says {} {}!".format("Hello", "World")
But in python2.6 you have to name the fields:   "My string says {0} {1}!".format("Hello", "World")

Good to know! I'll fix it so that it works with older versions too. :)
 
Also, there are a couple button callbacks where you use "mm.eval()", but if someone doesn't have maya.mel imported as mm in their global scope (either having done it manually in the script editor, or in their userSetup.py) then it will produce an error. That could be another candidate for partial, doing something like partial(mm.eval, "my mel command"). Otherwise you have to do the more verbose string approach that has to first import maya.mel: "import maya.mel as mm; mm.eval('my mel command')". I kind of prefer the former, if you have to call mel as a python callback. Or it can call a python method you have set up already that will cleanly call the mel command, like:   self.__prefixHierarchy()

Thanks, I never noticed that issue, probably because I have always launched the script at least once from the script editor, importing maya.mel in the global scope, before trying the shelf button. I fixed it creating three methods looking like this:

def launch_prefixHierarchy(self, *args):
       
"""Launches Maya's built-in command"""
       
       
from maya.mel import eval as eval
       
       
eval('prefixHierarchy')

Out of curiosity, why did you add the double underscore in the method example? I know it is supposed to tell python that the method is private, but I don't see why would someone do that (it is more of a general inquiry, not a specific one).

* "New-style" python classes always at least subclass object:   class MyClass(object):

Ok, not really sure what is going on here, but I'll do some research! To be on the safe side, I'll do as you say and take it from there.

* When testing if a list is empty, instead of allocating a new empty list object each time, just to test if it is equal to the other list, you can simply check if your list is empty using:     if not myList

Do you mean that instead of doing

if my_list != []:

I should do

if not my_list

? I guess I stick with the first method as it reminds me of the few things I remember about C :P
 
* It is usually best not to have your class constructor have side effects, such as showing the window. It should have a separate method like show(). That way someone can import and create an instance of the window and show it when they want to. Constructors are for setting up the state of the class.

Ok, to put in other words, I can leave the __init__ empty, given that there isn't much to initialize, and create a new method that does what the __init__ was doing. It seems to work, so I'll do it :)

* The approach where you are using the tool_launcher() method with a string tool name might be cleaner if you made those tools formal functions or methods. There are a couple of places where you test for the literal string names in order to do different logic, which could be bug prone because changing the name in one spot means you have to find and replace all of the literal string references. Maybe you might want to define those once in your class as class constants:

class MyClass(object):
    ACTION_NAME_1 = "action1"
    ACTION_NAME_2 = "action2"

Or if you want to make the association between an action and a displayable name, you can register a dictionary in your constructor that maps them:

self._actions = {
    "Action 1": self.__action1,
    "Action 2": self.__action2,
}

Then you won't have to do the 'if actionName == "literal name"' test in multiple places to figure out the action to perform. You can just look up the associated action in the dict:  self._actions[actionName]

You're very much right. I think I'll live it as it is for now. If happen to change that part of the code I'll fix it properly. 
 
* rsplit() is a method of a string
so this:    str.rsplit(str(temp_name), split_char, 1)
can be written as:    temp_name.rsplit(split_char, 1)

Ok, now this is interesting. It was definitely stupid of me to use that syntax. I do have a question though. I started with:

str.rsplit(temp_name, split_char, 1)

which gave me this:

TypeError: descriptor 'rsplit' requires a 'str' object but received a 'unicode'

This is why I managed to make the ugly code even uglier with the str(temp_name) type conversion.

Now with the question. Following the error stated above, I assumed that if I wanted to avoid the same TypeError, your suggestion

temp_name.rsplit(split_char, 1)

needed to be converted to

str(temp_name).rsplit(split_char, 1)

Needles to say, you were right and it works perfectly without conversion. Any idea why it needed the conversion in the first case and not in the second?


Well, thanks a lot. You've been really helpful. Do you have anything to say about the actual functionality of the whole thing? UI design and stuff like that?

Thank you very much!

Justin Israel

unread,
Jul 21, 2014, 7:47:26 AM7/21/14
to python_in...@googlegroups.com
On Mon, Jul 21, 2014 at 11:08 PM, Gabriele Bartoli <prodegu...@gmail.com> wrote:
Hi, sorry for the late reply. Busy weekend!


On Friday, 18 July 2014 23:36:52 UTC+2, Justin Israel wrote:
I can offer a couple points of feedback so far, but I didn't get too deep into the too since it has some compatibility issues with python2.6 (Maya 2013).
If you use the str.format() approach for formatting text, python2.6 doesn't support the anonymous fields. That was introduced in python2.7

So in python2.7, this is valid:     "My string says {} {}!".format("Hello", "World")
But in python2.6 you have to name the fields:   "My string says {0} {1}!".format("Hello", "World")

Good to know! I'll fix it so that it works with older versions too. :)
 
Also, there are a couple button callbacks where you use "mm.eval()", but if someone doesn't have maya.mel imported as mm in their global scope (either having done it manually in the script editor, or in their userSetup.py) then it will produce an error. That could be another candidate for partial, doing something like partial(mm.eval, "my mel command"). Otherwise you have to do the more verbose string approach that has to first import maya.mel: "import maya.mel as mm; mm.eval('my mel command')". I kind of prefer the former, if you have to call mel as a python callback. Or it can call a python method you have set up already that will cleanly call the mel command, like:   self.__prefixHierarchy()

Thanks, I never noticed that issue, probably because I have always launched the script at least once from the script editor, importing maya.mel in the global scope, before trying the shelf button. I fixed it creating three methods looking like this:

def launch_prefixHierarchy(self, *args):
       
"""Launches Maya's built-in command"""
       
       
from maya.mel import eval as eval
       
       
eval('prefixHierarchy')

Out of curiosity, why did you add the double underscore in the method example? I know it is supposed to tell python that the method is private, but I don't see why would someone do that (it is more of a general inquiry, not a specific one).

The use of double underscore is for methods where the implementation is private and outside people shouldn't be calling it. For things like callbacks that may take private arguments or have functionality where you want control over them being called, it is good to hide them. Ideally you are only exposing functions you want the outside world to have access to. I just chose to make this a private method since it's sole purpose was to wrap a mel call to prefixHierarchy, and really serves no purpose as being a part of your public API. But it is possible you might have some reason to make it a public method.

Out of curiosity for me as well, why do you choose to use that specific form of an import statement? It seems redundant to import something as an alias of the original name (not to mention that technically in this case, "eval" would shadow the python eval, although it would have no effect if you weren't going to use the python eval() anywhere). But it could be written as:
from maya.mel import eval
 

* "New-style" python classes always at least subclass object:   class MyClass(object):

Ok, not really sure what is going on here, but I'll do some research! To be on the safe side, I'll do as you say and take it from there.

* When testing if a list is empty, instead of allocating a new empty list object each time, just to test if it is equal to the other list, you can simply check if your list is empty using:     if not myList

Do you mean that instead of doing

if my_list != []:

I should do

if not my_list

? I guess I stick with the first method as it reminds me of the few things I remember about C :P

Well my comment wasn't just about syntax. You are actually allocating a temporary list object in memory each time that just gets garbage collected. Seems like something you might want to avoid doing. Do you allocate temporary arrays in C to check if an existing array is empty? Or are you confusing this syntax with comparing an array to NULL in C (asking if it is a NULL pointer)?  
 
 
* It is usually best not to have your class constructor have side effects, such as showing the window. It should have a separate method like show(). That way someone can import and create an instance of the window and show it when they want to. Constructors are for setting up the state of the class.

Ok, to put in other words, I can leave the __init__ empty, given that there isn't much to initialize, and create a new method that does what the __init__ was doing. It seems to work, so I'll do it :)

You could leave all of your widget construction and layout in the constructor if you want (thats up to you). I only meant the last line where it shows the window as part of the constructor. 
 

* The approach where you are using the tool_launcher() method with a string tool name might be cleaner if you made those tools formal functions or methods. There are a couple of places where you test for the literal string names in order to do different logic, which could be bug prone because changing the name in one spot means you have to find and replace all of the literal string references. Maybe you might want to define those once in your class as class constants:

class MyClass(object):
    ACTION_NAME_1 = "action1"
    ACTION_NAME_2 = "action2"

Or if you want to make the association between an action and a displayable name, you can register a dictionary in your constructor that maps them:

self._actions = {
    "Action 1": self.__action1,
    "Action 2": self.__action2,
}

Then you won't have to do the 'if actionName == "literal name"' test in multiple places to figure out the action to perform. You can just look up the associated action in the dict:  self._actions[actionName]

You're very much right. I think I'll live it as it is for now. If happen to change that part of the code I'll fix it properly. 
 
* rsplit() is a method of a string
so this:    str.rsplit(str(temp_name), split_char, 1)
can be written as:    temp_name.rsplit(split_char, 1)

Ok, now this is interesting. It was definitely stupid of me to use that syntax. I do have a question though. I started with:

str.rsplit(temp_name, split_char, 1)

which gave me this:

TypeError: descriptor 'rsplit' requires a 'str' object but received a 'unicode'

This is why I managed to make the ugly code even uglier with the str(temp_name) type conversion.

Now with the question. Following the error stated above, I assumed that if I wanted to avoid the same TypeError, your suggestion

temp_name.rsplit(split_char, 1)

needed to be converted to

str(temp_name).rsplit(split_char, 1)

Needles to say, you were right and it works perfectly without conversion. Any idea why it needed the conversion in the first case and not in the second?


Ya, the reason it was giving you an error was because you were passing an instance of a unicode object to a method of the string class. If you really wanted to make it work that way, it would look like this:
s = u"foo bar"
unicode.rsplit(s, ' ', 1)
But obviously you have figured out that you can just call the rsplit() method on the object so it is shorter. 
 

Well, thanks a lot. You've been really helpful. Do you have anything to say about the actual functionality of the whole thing? UI design and stuff like that?

I didn't get to try the whole thing out, since I didn't have Maya2014+ handy at the time. But it looks nice! Also very kind of you to add so much help at the bottom. Leaves nothing to ambiguity! Sorry that I focused more on the code aspects, rather than the interface. It was easier for me to do that at the given moment. 
 

Thank you very much!

--
You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.

Gabriele Bartoli

unread,
Jul 21, 2014, 11:13:32 AM7/21/14
to python_in...@googlegroups.com
The use of double underscore is for methods where the implementation is private and outside people shouldn't be calling it. For things like callbacks that may take private arguments or have functionality where you want control over them being called, it is good to hide them. Ideally you are only exposing functions you want the outside world to have access to. I just chose to make this a private method since it's sole purpose was to wrap a mel call to prefixHierarchy, and really serves no purpose as being a part of your public API. But it is possible you might have some reason to make it a public method.

Ok, I understand why it makes sense to hide some of the methods to the end user if they are not going to call them. But for what I understand they are not really hidden, they are simply slightly harder to call. Example from stackOverflow:

>>> class MyClass:
...     def myPublicMethod(self):
...             print 'public method'
...     def __myPrivateMethod(self):
...             print 'this is private!!'
... 
>>> obj = MyClass()
 
I can call the private method using obj._MyClass__myPrivateMethod(), or self._MyClass__myPrivateMethod() from the inside, right?
It seems to link to the "new classes" deal. I'll look into it!

Out of curiosity for me as well, why do you choose to use that specific form of an import statement? It seems redundant to import something as an alias of the original name (not to mention that technically in this case, "eval" would shadow the python eval, although it would have no effect if you weren't going to use the python eval() anywhere). But it could be written as:
from maya.mel import eval
I don't really have an answer. I first thought of importing the whole maya.mel as mm in every method, but it seemed overkill, so I decided to try importing the single eval() method. I should call it differently, maybe mm_eval, to avoid it shadowing the python eval, shouldn't I? I tried to call it mm.eval, which was obviously wrong, and then I forgot to try another name. 

Well my comment wasn't just about syntax. You are actually allocating a temporary list object in memory each time that just gets garbage collected. Seems like something you might want to avoid doing. Do you allocate temporary arrays in C to check if an existing array is empty? Or are you confusing this syntax with comparing an array to NULL in C (asking if it is a NULL pointer)?

You have a point, rampage of confusion going on here. What I started doing was  declaring a variable with None (my_list = None), but then it wold give me issues when I tried to append stuff to it, because it is not considered a list yet. The fastest thing I thought of was to make it a list to begin with. At that point I didn't think using booleans or identity tests would work.
 
You could leave all of your widget construction and layout in the constructor if you want (thats up to you). I only meant the last line where it shows the window as part of the constructor. 

I tried to do that real quick, but it gave me an error of window not found. It was easier to rename the method and create an empty __init__

I didn't get to try the whole thing out, since I didn't have Maya2014+ handy at the time. But it looks nice! Also very kind of you to add so much help at the bottom. Leaves nothing to ambiguity! Sorry that I focused more on the code aspects, rather than the interface. It was easier for me to do that at the given moment.

No reason to apologize, you are being very helpful! Do you know where I could find more pointers regarding creating custom UIs? It seems very hard to find good reference guides.

I uploaded the edited version here, if you want to give it a go. I changed the text formatting, therefore it shouldn't give you any issues now.

Cheers!

Justin Israel

unread,
Jul 21, 2014, 3:54:36 PM7/21/14
to python_in...@googlegroups.com


On 22/07/2014 3:13 AM, "Gabriele Bartoli" <prodegu...@gmail.com> wrote:
>>
>> The use of double underscore is for methods where the implementation is private and outside people shouldn't be calling it. For things like callbacks that may take private arguments or have functionality where you want control over them being called, it is good to hide them. Ideally you are only exposing functions you want the outside world to have access to. I just chose to make this a private method since it's sole purpose was to wrap a mel call to prefixHierarchy, and really serves no purpose as being a part of your public API. But it is possible you might have some reason to make it a public method.
>
>
> Ok, I understand why it makes sense to hide some of the methods to the end user if they are not going to call them. But for what I understand they are not really hidden, they are simply slightly harder to call. Example from stackOverflow:
>
> >>> class MyClass:
> ...     def myPublicMethod(self):
> ...             print 'public method'
> ...     def __myPrivateMethod(self):
> ...             print 'this is private!!'
> ...
> >>> obj = MyClass()
>
>  
> I can call the private method using obj._MyClass__myPrivateMethod(), or self._MyClass__myPrivateMethod() from the inside, right?
> It seems to link to the "new classes" deal. I'll look into it!
>

Yea in Python there is no true sense of private members. Python just uses name mangling as a deterrent. One would still need to construct a name based on the class name and the member to access it (your example is doing it interactively where you know the object to call) . But like I said it is more of a deterrent. It implies to others which parts they should access and which could cause undocumented problems and side effects. In a private method you can do whatever you want and don't have to be clear about it in terms of public documentation. Although you should still write clear code and comment it if needed. It also helps dictate which parts of your code will get picked up for documentation generators such as Sphinx which by default will look for public methods.
It is more of a practice and Python idiom. It tells me as a consumer how to use the object without breaking it.

>> Out of curiosity for me as well, why do you choose to use that specific form of an import statement? It seems redundant to import something as an alias of the original name (not to mention that technically in this case, "eval" would shadow the python eval, although it would have no effect if you weren't going to use the python eval() anywhere). But it could be written as:
>>
>> from maya.mel import eval
>
> I don't really have an answer. I first thought of importing the whole maya.mel as mm in every method, but it seemed overkill, so I decided to try importing the single eval() method. I should call it differently, maybe mm_eval, to avoid it shadowing the python eval, shouldn't I? I tried to call it mm.eval, which was obviously wrong, and then I forgot to try another name. 
>

Ideally you would just import it once at the root of your module and then base your callbacks off that.

>> Well my comment wasn't just about syntax. You are actually allocating a temporary list object in memory each time that just gets garbage collected. Seems like something you might want to avoid doing. Do you allocate temporary arrays in C to check if an existing array is empty? Or are you confusing this syntax with comparing an array to NULL in C (asking if it is a NULL pointer)?
>
>
> You have a point, rampage of confusion going on here. What I started doing was  declaring a variable with None (my_list = None), but then it wold give me issues when I tried to append stuff to it, because it is not considered a list yet. The fastest thing I thought of was to make it a list to begin with. At that point I didn't think using booleans or identity tests would work.
>  
>>
>> You could leave all of your widget construction and layout in the constructor if you want (thats up to you). I only meant the last line where it shows the window as part of the constructor. 
>
>
> I tried to do that real quick, but it gave me an error of window not found. It was easier to rename the method and create an empty __init__
>
>> I didn't get to try the whole thing out, since I didn't have Maya2014+ handy at the time. But it looks nice! Also very kind of you to add so much help at the bottom. Leaves nothing to ambiguity! Sorry that I focused more on the code aspects, rather than the interface. It was easier for me to do that at the given moment.
>
>
> No reason to apologize, you are being very helpful! Do you know where I could find more pointers regarding creating custom UIs? It seems very hard to find good reference guides.
>

Do you mean more like design guides as opposed to technical code-based guides? Hmm I'm terrible with remembering references to suggest. Not quite sure in this area. Maybe others can chime in?

> I uploaded the edited version here, if you want to give it a go. I changed the text formatting, therefore it shouldn't give you any issues now.
>

Will give it a go when I have a moment.  Thanks!

> Cheers!


>
> --
> You received this message because you are subscribed to the Google Groups "Python Programming for Autodesk Maya" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to python_inside_m...@googlegroups.com.

> To view this discussion on the web visit https://groups.google.com/d/msgid/python_inside_maya/8b96abf3-8863-4738-9000-397f7eabf922%40googlegroups.com.

Gabriele Bartoli

unread,
Jul 22, 2014, 9:06:26 AM7/22/14
to python_in...@googlegroups.com

Yea in Python there is no true sense of private members. Python just uses name mangling as a deterrent. One would still need to construct a name based on the class name and the member to access it (your example is doing it interactively where you know the object to call) . But like I said it is more of a deterrent. It implies to others which parts they should access and which could cause undocumented problems and side effects. In a private method you can do whatever you want and don't have to be clear about it in terms of public documentation. Although you should still write clear code and comment it if needed. It also helps dictate which parts of your code will get picked up for documentation generators such as Sphinx which by default will look for public methods.
It is more of a practice and Python idiom. It tells me as a consumer how to use the object without breaking it.

Gotcha!
 

Ideally you would just import it once at the root of your module and then base your callbacks off that.

 Yeah, I think I'll do that :)

Do you mean more like design guides as opposed to technical code-based guides? Hmm I'm terrible with remembering references to suggest. Not quite sure in this area. Maybe others can chime in?

Both technical and design would be useful, it is really hard to find both. Or maybe I do not know how to look for them properly!

Cheers! 
Reply all
Reply to author
Forward
0 new messages