Seeing 'root space' python instances from another class instance

So I think half my problem is I don’t know how to properly search for this topic. I’ve tried multiple variations on google and various forums to no avail. My python knowledge and coding knowledge is spotty so this may well be a simple thing.

So the classes I have are:
[ul]
[li] a gui class utilizing Hamish Mckenzie’s baseMelUI. [/li][li] a puppet class for a modular rigger I’ve been working on[/li][/ul]

Currently I’m instancing the puppet class to the gui. What I want to do is to instance to maya ‘root’ python space so that I can in the script editor call them from a bridge class like - bridgeClassInstance.PuppetInstance.yadayada. And that I’d be able to call that class within my gui class to link to an existing instance if one is there, or instance to that bridge class if not and link it.

Trying to find an easy way to only instance a puppet once to make everything faster. The only current way I know to do that is to run a mel.eval("python… which seem sloppy in my limited expertise and doesn’t provide all the functionality I’m looking for.

Sorry for the meandering post, if I knew what it was I was looking for, I’m sure I’d find it faster:)

Thanks to any insight you can provide!
Josh @ cgmonks

It sounds like you just want to get a handle to your instance into the python root namespace so you can get to it in callbacks – Is that correct?

You should look at the discussion from a few weeks ago about closures, pymell.Callback objects and functools.partial (not sure how to link to the right posts here…) It’s cleaner to create your callbacks as callable objects, which gets you off the hook for much of the namespace manipulation:


def cb_test(*args):
    print 'callback!', args

button('test', c= cb_test) # note - no quotes!

is a lot nicer than


def cb_test(*args):
    print 'callback!', args

globals()['cb_test'] = cb_test
button('test', c= "cb_test()") 

If you really, really need to go the callback string route, you push stuff into the global namespace like this:

globs = globals()
globs['objectname'] = object

‘object’ could even be a module in this case.

You don’t want to clutter up the globals namespace, it raises the possibility of dangerous behaviors if you promote things to the same global name from different sources. A common pattern is to have a callbacks module that gets promoted, and inside that a registry of callback objects keyed by name.

Here’s an example that uses class methods so it’s a pseudo-singleton:

class CallbackRegistry( object ):
    '''
    Holds commands associated with a menu item
    '''
    CALLBACKS = {}


    @classmethod
    def register( cls, command, key ):
        cls.CALLBACKS[key] = command
        return key

    @classmethod
    def call( cls, key, *args, **kwargs ):
        cls.CALLBACKS[key]( *args, **kwargs )

    @classmethod
    def unregister ( cls, key ):
        try:
            del cls.CALLBACKS[key]
        except KeyError:
            pass

    @classmethod
    def defined ( cls, key ):
        return key in cls.CALLBACKS.keys

# this line promotes the class into the global namespace
globals()['CallbackRegistry'] = CallbackRegistry

You add callbacks with CallbackRegistry.register() and call them with CallbackRegistry.call() – this way you only have to promote the CallbackRegistry name to globals instead of a zillion individual names. Your button scripts would look like


button('test', c= "CallbackRegistry.call('<callback name>')");

All that said, it’s nicer to use callables as callbacks and sidestep the globals management where you can. This does, however, make it a little easier to get to your python callbacks from Mel if for some awful reason you must. It would also mean you could call your stuff from the script editor as needed, since the registry object would be in the global namespace.

If you want to go all metaphysical, you might also want to read up on the Singleton Pattern In Python :


which is a topic of intense theological controversy – but several of the methods in the discussion would make it ‘easy’ to get an existing instance or create a new one as needed.

This might not be what you’re asking and I apologize if this is obvious:

This ties into the ‘singleton’ debate, but really if you just want one instance of something you can store it as a class variable wrapped in a class (or module) that makes sense. If you want that to be your GUI class, that’s fine. Or you could have some sort of PuppetFactory() class (or the ‘bridge’ class you describe) which has the concrete implementation of the Puppet() object as one of its class variables. For example, something like this makes the GUI class the owner of the PUPPET instance, so if you wanted to reference the PUPPET instance you would just make use of a ‘get’ function which would do the lazy initialization (create the PUPPET instance if it doesn’t exist). Alternately you can just rename GUI to BRIDGE:


class PUPPET(object):
    def __init__(self):
        self.counter = 0
    def yada(self):
        print 'yada yada:', self.counter

        
class GUI(object):
    PUPPET_CLASS = None
    def __init__(self):
        pass

    def getPuppetClass(self):
        if not GUI.PUPPET_CLASS:
            GUI.PUPPET_CLASS = PUPPET()

        return GUI.PUPPET_CLASS            

    def guiLogic(self):
        x = self.getPuppetClass()
        x.yada()

guiInstance = GUI()        
#Internal GUI logic makes use of the PUPPET instance
guiInstance.guiLogic()
yada yada: 0

#Script editor version - Get the PUPPET instance if you want to do something in the script editor
puppetInstance = guiInstance.getPuppetClass()
puppetInstance.counter
# Result: 0 # 

#Now just show that the GUI code and script editor changes are referencing the same object
guiInstance.getPuppetClass().counter += 1
puppetInstance.counter
# Result: 1 # 
puppetInstance.counter += 1
puppetInstance.counter
# Result: 2 # 
guiInstance.getPuppetClass().counter
# Result: 2 # 

Also, I’m not entirely clear what you are trying to do, but I would caution against making system designs based on wanting to do things in the script editor. You have full access to all the modules/instances while you’re manually doing things in the script editor, you don’t really need to code special bridge modules.

If you’re wanting to abstract the PUPPET implementation, rather than simply returning the PUPPET instance you may want to consider an abstraction layer (a manager or interface), such that function calls might flow like:

GUI provides a button to rig a limb, which calls PuppetManager interface -> PuppetManager.rigLimb() creates the appropriate PuppetInstance if necessary and calls the correct function -> PuppetInstance.rigLimb() does the work

For the script editor you’d just bypass the GUI code and start at calling PuppetManager.rigLimb(), which would handle the instantiation if necessary and call PuppetInstance to do the actual work.

Phil

Thanks for the insights guys.

Phil, Your layout is pretty much exactly what I have now:)
Yes…the singleton threads were one that I came across several times in my searches but wasn’t sure that’s what I was lookin for. It doesn’t have to go the script editor I just thought it would be handy to be able to call commands from a module from the commandline without needing a gui all the time but for most users they’d never do that anyway so maybe that’s not the way to go. I was envisioning being able to do something like a bridge.Character.module.snapToFK() type formatting.

Theodox, I use callbacks currently in the gui but I’ll look more into what you have here when I get some time this weekend or next week.
I need to read it a bit closer. If it makes it clearer, I’m not proposing reinstancing the same thing over and over but a check that would look for a instance already in the bridge and only create it if it’s not there. For example, currently in my gui I instance the puppet.andModules to the gui. If you change characters, it reinstances that asset again which wastes time (to me). And I’d like other gui’s to potentially be able to see that instance if it exists though I definitely see at least an idea of how this could cause issues.

Maybe what I need a master class above both of them to call things to that all of my various modules could call to.

Thanks guys for the stuff to chew on:)

Thanks again for the help guys. I spent yesterday lookin at this again and not getting very far so I’m gonna let it stew in my brain a while and come back and look to implementing it later.

I did glean a lot from the resources and info you provided and as think I have better understanding of what is I’m lookin for which isn’t a singleton after all.

Regardless, I appreciate your time.

Cheers,
-Josh

[QUOTE=jjburton;17073]Thanks again for the help guys. I spent yesterday lookin at this again and not getting very far so I’m gonna let it stew in my brain a while and come back and look to implementing it later.

I did glean a lot from the resources and info you provided and as think I have better understanding of what is I’m lookin for which isn’t a singleton after all.

Regardless, I appreciate your time.

Cheers,
-Josh[/QUOTE]

This sounds a lot simpler than what I was thinking :slight_smile:

Try something like wrapping the GUI itself in a class, and poke the active instance into a class field. Then you only need to check to see if that field is not null.


class UnitDialog( object ):

    def __init__( self ):
        self.Unit = None  # instance variable goes here

    def _create( self, window ):

        self._Window = window  # note that this is an instance variable too, you could get the window handle
                                            # from the class instance if you wanted to, although that's unwise
                                            # which is why it's a "polite private" name with an _

        form = cmds.formLayout()
        rbs = cmds.columnLayout() #@UndefinedVariable
        cmds.formLayout( form, e=True, attachForm=( rbs, 'top', 10 ) ) #@UndefinedVariable
        cmds.formLayout( form, e=True, attachForm=( rbs, 'left', 10 ) ) #@UndefinedVariable
        cmds.formLayout( form, e=True, attachForm=( rbs, 'bottom', 10 ) ) #@UndefinedVariable
        cmds.formLayout( form, e=True, attachForm=( rbs, 'right', 10 ) ) #@UndefinedVariable
        cmds.formLayout( form, e=True, width=320 )
        cmds.text( label="Set this file to:" )
        cmds.text( " " )
        cmds.rowColumnLayout( nc=2, cw=[( 1, 120 ), ( 2, 120 )], cal=[( 1, "both" ), ( 2, "both" ) ], cs=[( 1, 15 ), ( 2, 15 )] )
        cmds.button( "meters", c=self._meters )
        cmds.button( "centimeters", c=self._centimeters )
        cmds.setParent( rbs )
        cmds.text( " " )
        cmds.text( "Scene data will be scaled to the same real-world size.", align="left", ww=True, width=320 )

    def _meters( self, *args ):

        cmds.currentUnit ( l="m" );
        cmds.warning( "units set to meters" )
        cmds.deleteUI( self._Window )
        self.Unit = 'm'


    def _centimeters( self, *args ):
        cmds.currentUnit ( l="cm" );
        cmds.warning( "units set to centimeters" )
        cmds.deleteUI( self._Window )
        self.Unit = 'cm'


    def open( self ):
        window = cmds.window( title="Fix unit settings", widthHeight=( 320, 144 ) )
        self._create( window )
        cmds.showWindow( window )

test_dlg = UnitDialog()
test_dlg.open()

# from other code you could check the instance variable.  Not this example is not modal,
# so you might get None since the user has not yet made a choice. 
# It's just the shortest bit of relevant code I had lying around.

if test_dlg.Unit == 'm': print 'unit is set to meters'


In this example, obviously, you could always check the instance variable from inside the class’s method by checking self.Unit – so the ‘create if needed’ idiom would be a method in the class like


def annoy_puppet(self, *args):
      if not self.Puppet: # ie, it's None
         self.Puppet = Puppet()
      self.Puppet.State = "annoyed"

… and of course this also means all you can pass the class methods to button commands and bypass the global namespace and it’s annoyances.

Thanks Theodox, as I follow what you said, that’s pretty much what I have.

Gui Class 1
–Instanced Puppet
----Instanced Modules
–Various functions to create,actiate, modify puppet and modules

What I want to get to is something like this…

Bridge class - initialized on tool launch if it doesn’t exist
–Instanced Puppet
----Instanced Modules
–Instanced Puppet2
----Instnaced Modules

Gui Class 1
–Link to bridge class Puppet instances which can be initialized from this class to the bridge

Gui Class 2
–Link to bridge class Puppet Instances which can be initialized from this class to the bridge

The reason I want to do it is that the puppet only needs to initialized,verified and what not once per puppet to make the gui’s more responsive rather than waiting a few seconds per puppet to initialize when activated the first time. Hope that makes more sense. I recognize that I’d need to probably have some sort of ‘checkOut’ fucntion on the bridge class so two gui classes couldn’t be modifying it at the same time. Again, not coming from a coders background my terminology is lousy.

-j

Sharing the instances, however you get them, is a probably bad idea. If the state stored in your puppet is meaningful – it has settings that the user cares about, or processes that store persistent information using variables in the instance – then you’re asking for problems when you let two unrelated GUIs poke at it independently. If the puppet is just a collection of functions with no stored data inside it’s less problematic.

If you think that shared puppets has to be a common case, then spend your energy on optimizing the startup time for new instances, not trying to invent and manage a quasi-multitasking environment. Shared-state bugs are evil and subtle and hard to debug. Don’t over think it!

If you think that shared puppets has to be a common case, then spend your energy on optimizing the startup time for new instances, not trying to invent and manage a quasi-multitasking environment. Shared-state bugs are evil and subtle and hard to debug. Don’t over think it!

That’s exactly what I did:) Yup, I see what you mean. However only one potential gui would be able to change settings, the other would be to access functions.

Overthinking? It’s what I do:)