Need some help/guidelines on how to organize my PyMEL project

Right now Im working on a somewhat larger PyMEL project than I’ve done in the past. My idea is to make it modular, having a core module/script file that the user needs - and then several opional scripts that the user can chose or not chose to use. Every file has a class definition in it, and under that I’ve added the class methods/functions. Before you ask me why Im using classes everywhere: I’m not sure when it’s a good idea to use a class object or not. I know that code and functions can be written directly in the root of the script document!

So the structure is like this:

import random
import pymel.core as pm
import maya.mel as mm

class my_tool():

    # Class variables
    
    # UI names

    
    ## Initialize
    def __init__(self):
        
        # Draw UI
        self.createUI()

        # Some other stuff, like running self.loadOptVars() - a method that creates/loads option variables

        
    ########## Methods ##########
    
    def some_method(self):
        pass

    # More methods

    ########## UI-related ##########

    def createUI(self):
        pass
[B][/B]

My issue is that I can’t seem to properly import these other modules (import to my core module, or import the core module from another script if it’s being run stand-alone).
I’ve tried both from module import * as well as from module import function and import module as md - but no matter how I write the import, the methods doesn’t seem to import properly because Maya says they cant be run. My guess is that it’s a namespace issue.

The particular method I want to run from moduleX is defined as a static method with the @staticmethod descriptor right before the definition - so they are not tied to working only with a class instance. So yea, obviously I’ve not layed these script files out as they should - or the namespace is wrong or Im missing something else. All help is appreciated.

(also, class name is the same as the script name - is that an issue?)

Are the files somewhere in pythonpath?

And if they are in modules aka subdirectories do you got a init.py in them so python knows it is a module.

[QUOTE=passerby;24543]Are the files somewhere in pythonpath?

And if they are in modules aka subdirectories do you got a init.py in them so python knows it is a module.[/QUOTE]
No they are just different script files (*.py)

On the classes vs functions side: It’s fine to write functions instead of classes if what you’re doing involves no stored information. Classes are better if you need to bundle information and behavior together. If you have a long function with tons of local variables in it – especially lots of lists that need to be correlated to each other – that’s probably an indication you could be breaking up the problem better into classes; if you have a class which does nothing except expose static methods, you probably should just make it a module instead. When in doubt I tend to go with classes since they have more flexibility over the long haul, but it’s usually pretty clear what kind of problem a particular bit of code is addressing.


'''
Defines the ExampleTool class which makes wireframe geometry in user-supplied colors
Also exposes convenience methods for generating arrays of colored objects
'''
import maya.cmds as cmds

class ExampleTool(object):
    ''' a tool '''
    def __init__(self, name, cmd, color):
        self.name = name
        self.cmd = cmd
        self.color = color 

    def create(self, pos):
        result, shape = self.cmd(n=self.name)
        cmds.color(result, ud = self.color)
        cmds.xform(t=pos, a=True)
        return result
    
    @staticmethod 
    def red_cube_tool():
        return ExampleTool('red_cube', cmds.polyCube, 8)


    @staticmethod 
    def blue_sphere_tool():
        return ExampleTool('blue_sphere', cmds.polyCube, 6)
        
test = class ExampleTool(object):
    ''' create supplied geometry using the supplied name and color settings
    
    green_pyramid_tool = ExampleTool('green_pyramid', cmds.polyPyramid, 4)
    green_pyramid_tool.create( (x,y,z) )
    
     '''
    def __init__(self, name, cmd, color):
        self.name = name
        self.cmd = cmd
        self.color = color 

    def create(self, pos):
        result, shape = self.cmd(n=self.name)
        cmds.color(result, ud = self.color)
        cmds.xform(t=pos, a=True)
        return result
    
    @staticmethod 
    '''
    Makes an ExampleTool that creates red cubes
    
    use it when you know you want red cubes:
        from exampleTool import ExampleTool
        my_tool = ExampleTool.red_cube_tool()
        my_tool.create( (x,y,z) ) 
    '''
    def red_cube_tool():
        return ExampleTool('red_cube', cmds.polyCube, 8)


    @staticmethod 
    '''
    Makes an ExampleTool that creates blue spheres
    '''
    def blue_sphere_tool():
        return ExampleTool('blue_sphere', cmds.polyCube, 6)


# example using a function -- don't bother the user with class management
def red_cube_array():
    '''
    heres'a function that uses the class internally but other code would call it like
    
    from exampleTool import red_cube_array
    red_cube_array()
    
    '''
    tool = ExampleTool.red_cube_tool()
    for x in range(10):
        for y in range(10):
            tool.create( (x,y,0) )


# However, if you need to do lots of array creation, you could do it more generally:
class ArrayCreator(object):
     '''
     Fire the supplied tool in a N by N grid:
     '''
      def __init__(self, size, tool):
           self.tool = tool
           self.size = size

      def create(self):
        for x in range(self.size):
            for y in range(self.size):
                self.tool.create( (x,y,0) )

# again, don't bother the user with the creation details
# but leverage the abstraction of the tool and the array 
def blue_sphere_array(size = 10):
     tool = ExampleTool.blue_sphere_tool()
     arrayCreator = ArrayCreator(size, tool)
     arrayCreator.create()

 

In the example you posted the tool probably does have state (such as options) so it is a good fit for a class. Static creator functions are a totally fine alternative to constructors, particularly if you want to do things like use the tool from scripts and shelf buttons and menu items without doing too much cut and paste. You can use lots of classes under the hood to avoid cut-and-paste boilerplate while using functions to provide a simpler public face to your functionality so other modules can just import and use functions.

As for the rest: If’ you’re not able to import stuff, you probably don’t have your py files in the search path for maya python. If you do


import sys
for item in sys.path: print item

do you see the folder where your .py files live?

Also. Don’t forget the init.py file in the module folder.

Thanks guys
I ended up making everything into a package - placing the modules inside a folder together with a init.py module
Things work as they should right now, I just have to do some namespace cleaning.

How do I access a UI control (which is an attribute of a class) from another module?

My core module creates a UI with several controls - and each of those controls have a designated name that is defined as a class var. So accessing that UI control internally is done via self.myControl. But I fail to access that control externally, even when I assign the class to a var upon creation.
Example:

core.py

import pymel.core as pm

class myClass():

    myUI = None

    def __init__(self):
        # stuff

    def someMethod(self):
        pass

####

    def createUI(self):
        self.myUI = pm.iconTextButton()

extra.py:


import pymel.core as pm
# Why do I have to import this in every single module file?

class someClass():

    def __init__(self):
        # stuff

    def someMethod(self):
        pm.iconTextButton() # Here I want to do an edit to the myUI -iconTextButton created by createUI() in core.myClass - but how?

####

    def createUI(self):
        # stuff for making a UI when extra.py is executed stand-alone

Both core.py and extra.py are imported by the init.py file in the package folder
Even when assigning this class to a var I can’t reach it from the extra.py module. So:
someVar = package.module.class() and then trying to do an edit on someVar.myUI in extra.py doesn’t work. I get an error message from Maya saying that package.module.class is undefined or something…

You could do this two ways:

  1. the GUI lives in a class which provides public methods for other code to get-set UI values; the class is the only code that knows which widget is where, all outside code has to ask the class for info or tell it what to do.

  2. The class which owns the GUI stores the addresses of individual widgets and outside code accesses the stored widgets directly.


# option 1 ==================
class LimitedAccess(obj):
     def __init__(self):
          self._window = cmds. window()
          self._maincolumn = cmds.columnLayout()
          self._display = cmds.text(label='...')
     
     def set_display(self, value):
          cmds.text(self._display, e=True, label=value)

    def get_display(self, value):
          cmds.text(self._display, q=True, label = true)

    def show(self):
          cmds.showWindow(self._window)

example = LimitedAccess()
example.show()
example.set_display("hello world")

# option 2 =======================
class UnlimitedAccess(obj):
     def __init__(self):
          self.window = cmds. window()
          self.maincolumn = cmds.columnLayout()
          self.display = cmds.text(label='...')

    def show(self):
          cmds.showWindow(self.window)

example2 = UnlimitedAccess()
example2.show()
cmds.text(example2.display, e=True, label = '*sigh*')

In general option 1 is superior, since you can change the details of the implementation without other code caring (ie, in this example you could swap in an iconTextButton instead of a text and outside code would be the same using strategy 1 but would have to change using strategy 2. The goal is to reduce the amount that one piece of code needs to know about the inner workings of another.

And yes, you do need to import stuff all over. It’s actually a benefit - it lets you stay organized by using dotted names instead of having a bazillion commands competing for a finite number of meaningful names.

[QUOTE=Theodox;24560]You could do this two ways:

  1. the GUI lives in a class which provides public methods for other code to get-set UI values; the class is the only code that knows which widget is where, all outside code has to ask the class for info or tell it what to do.

  2. The class which owns the GUI stores the addresses of individual widgets and outside code accesses the stored widgets directly.


# option 1 ==================
class LimitedAccess(obj):
     def __init__(self):
          self._window = cmds. window()
          self._maincolumn = cmds.columnLayout()
          self._display = cmds.text(label='...')
     
     def set_display(self, value):
          cmds.text(self._display, e=True, label=value)

    def get_display(self, value):
          cmds.text(self._display, q=True, label = true)

    def show(self):
          cmds.showWindow(self._window)

example = LimitedAccess()
example.show()
example.set_display("hello world")

# option 2 =======================
class UnlimitedAccess(obj):
     def __init__(self):
          self.window = cmds. window()
          self.maincolumn = cmds.columnLayout()
          self.display = cmds.text(label='...')

    def show(self):
          cmds.showWindow(self.window)

example2 = UnlimitedAccess()
example2.show()
cmds.text(example2.display, e=True, label = '*sigh*')

In general option 1 is superior, since you can change the details of the implementation without other code caring (ie, in this example you could swap in an iconTextButton instead of a text and outside code would be the same using strategy 1 but would have to change using strategy 2. The goal is to reduce the amount that one piece of code needs to know about the inner workings of another.

And yes, you do need to import stuff all over. It’s actually a benefit - it lets you stay organized by using dotted names instead of having a bazillion commands competing for a finite number of meaningful names.[/QUOTE]
Yea option 1 is what I wanted to create but wasn’t sure how.
Writing names with a single underscore as a prefix - that’s new to me: it wasn’t covered in the Python/PyMEL book I read. If I’ve understood that functionality correctly, such names will not get imported when doing module imports such as from x import * ?
I have some concerns/questions regarding that though: Say that I organize everything like in your option 1 example, doesn’t this mean that I would have to declare the example variable as a global var in order to have another imported module being able to do stuff to it? Consider this flow of execution:

  1. The core module has a call to createUI() - a class method inside the init definition - and it creates and assigns the UI to a global variable: exampleVar
  2. A method inside another module, wants to do a change to this UI, so I hardcode it to do the change to this global var, like: exampleVar.show(0)

…is that the right way to do it? I’m wondering because ppl keep telling me to stay away from global variables.

underscores usually just mean ‘don’t mess with this from outside’ - its common for class fields that the writer does not want outside users to mess with. It doesn’t change behavior - it’s just a convention. Double underscores actually hides the names from ‘casual’ inspection — but it’s always possible to work around.

On your main question:

Anything defined in a .py file lives in the namespace of that file: thats why you commonly do things like


import maya.cmds as cmds
print cmds.ls(type='transform')

Here maya.cmds is a module (technically, a package - but the idea is the same) and ls is a function that is defined in the module. So if you make a class in one file and want to use it in another – assuming they are both in the python path— you just do:


#----------------------
# in file example.py

class Example(object):
     def __init__(self, name):
          self.name = name

    def get_name(self):
         return self.name

def somefunction (arg, arg2):
    return arg + "--" + arg2

#------------------
# in file otherFile.py
import example
# importing means the name 'example' is now here and we can get
# it's contents with dot notation:

def test_example_class():
      ex = example.Example('hello world')
      print ex.get_name()

def test_example_function():
      print example.somefunction("hello",, "world")


In general you avoid * imports for this reason, because it’s much easier to know what you’re dealing with when you get it from it’s parent module (ie “example.somefunction”) instead of out of the ether (“somefunction()”) . Plus multiple star imports can overwrite each other so you may not even know what you’re really calling.

Your examples makes sense and I appreciate your efforts, but things are not working for some reason.

init.py

# Import core part
import core

# Import another module I want to use
import myModule

core.py

import pymel.core as pm
def someMethod(self):
    # Row below doesn't work at all ^1
    pm.iconTextButton( command=lambda *args: myModule.myClass() )

myModule.py

class MyClass():   
    def someMethod(self):
        print("success!")

    @staticmethod
    def someOtherMethod():
        print("success v2!")

^1 - # NameError: name ‘myModule’ is not defined #
I get that nomatter what attribute (class, method or variable) I try and “get” from that module. Yes, everything is stored in a class MyClass but I’ve tried executing a static method inside that class (and those are not bound to class instances right?). I’ve also tried to create said class object, like:
myObject = myModule.MyClass()

NameError: name ‘myModule’ is not defined

Also tried import core as cr in init.py followed by lambda *args: cr.myModule.myMethod() in core.py and then I get this error:

AttributeError: class cr has no attribute ‘myModule’

I don’t get it. I know that the module has been imported successfully (I have a print in it’s def init(self) ) but I still can’t access any of the imported stuff via the dotted names.

EDIT: But if I write that stuff from the lambda function directly into the script editor, it works:

Result: <myModule.MyClass instance at 0x000000003DC6C5C8>

What is going on here?

It looks like you aren’t importing myModule into core. Currently any code in core has no idea what myModule is, and that is true the other way around as well.

Now it does look like you’ve imported both core, and myModule into init, this means that code written in init can make use of objects from both core and myModule, but their namespaces are still separate from each other.

You have to remember that an import statement lives in a particular scope. If I import XXX in module YYY, then YYY.XXX will contain the XXX module – but (unlike mel) that does not mean that another file can now use XXX. Any module that wants the functions from a given module has to import it (there’s no memory cost for this, btw: import does not reload the module, it just adds it to the current namespace. So it’s totally possible for code to work in the listener and not in a file and vice versa.

For code layout, the basic rules are:

  1. Do you imports at the top so they are clear

  2. import modules as a whole and use dot notation to get at them:


import x

def local_function(arg):
    return x.imported_function(arg)

  1. Be clear who is importing who!

It’s usually a good idea to divide your files between ‘library’ code that is intended to be imported elsewhere and ‘tool’ code that is NOT intended to be imported. If you accidentally create a circular chain of references you can get odd behavior or crashes. Library code can import other library code, but it’s best to keep the references few in number so you don’t create paradoxes.

On disk it looks like:



pytools    # root folder, this is in your PATH 
+-- lib
|     + __init__.py   # this makes the other files into sub-modules of 'lib'
|     + uvs.py
|     + anims.py
|     + modelling.py
+---tools
      + fast_uv_tool.py

# in fast_uv_tool:
import  lib.uv as uv
import  lib.modelling as modeling
# .. use as uv.xxx(), modelling.yyy() etc\

I see.
I was under the assumption that when importing things in init.py - the namespace would be shared across the imported scripts.