Maya - Reloading a shelf object

Hey there,
I created a shelf in Maya 2019, using this good ol’ Super Class that I found here years ago:
Everything works well, except that I can’t reload the shelf using a button from the shelf: this instantly makes maya crash, without any error prompt/window.

To reload the shelf when developping or debugging, I create an instance of my subclass, then call
inst._cleanOldShelf() and inst.build() . This works perfectly when used from the script editor.
If I create a button in the shelf with those methods, clicking it will crash maya.

Do you have an idea how I can let my users reload my shelf, without the need to give them scripts to launch in the script editor?

Thanks

import sgtk

import maya.cmds as cmds
import maya.mel as mel

def _null(*args):
    pass

class CreateMayaShelf(object):
    '''
    A simple class to build shelves in maya. Since the build method is empty,
    it should be extended by the derived class to build the necessary shelf elements.
    By default it creates an empty shelf called 'WIP'.
    BEWARE Do not use any space character ' ' in the name as Maya will freack out.
    '''

    def __init__(self, name='WIP'):
        self.engine = sgtk.platform.current_engine()
        self.name = name
        self.iconPath = 'L:/_tech/media/icons/maya_shelfs/'
        self.labelBackground = (0, 0, 0, 0)
        self.labelColour = (.9, .9, .9)
        self._cleanOldShelf()
        cmds.setParent(self.name)
        # self.build()


    def setName(self, name):
        '''
        BEWARE shelf name must be unique as it will replace any shelf with same name.
        BEWARE Do not use any space character ' ' in the name as maya will freack out
        :param: str name
        '''
        self.name = name


    def setName(self, name):
        self.name = name


    def build(self):
        '''This method should be overwritten in derived classes to actually build the shelf
        elements. Otherwise, nothing is added to the shelf.'''
        pass


    def addButon(self, label, icon='wip_32.png', command=_null, doubleCommand=_null):
        '''Adds a shelf button with the specified label, command, double click command and image.'''
        cmds.setParent(self.name)
        if icon:
            icon = self.iconPath + icon
        cmds.shelfButton(width=37, height=37, image=icon, l=label, command=command, dcc=doubleCommand, imageOverlayLabel=label, olb=self.labelBackground, olc=self.labelColour)


    def addSeparator(self):
        # cmds.addShelfSeparator()
        # cmds command seems broken
        mel.eval("addShelfSeparator()")


    def addMenuItem(self, parent, label, command=_null, icon=''):
        '''Adds a shelf button with the specified label, command, double click command and image.'''
        if icon:
            icon = self.iconPath + icon
        return cmds.menuItem(p=parent, l=label, c=command, i='')


    def addSubMenu(self, parent, label, icon=None):
        '''Adds a sub menu item with the specified label and icon to the specified parent popup menu.'''
        if icon:
            icon = self.iconPath + icon
        return cmds.menuItem(p=parent, l=label, i=icon, subMenu=1)


    def _cleanOldShelf(self):
        '''Checks if the shelf exists and empties it if it does or creates it if it does not.'''
        if cmds.shelfLayout(self.name, ex=1):
            if cmds.shelfLayout(self.name, q=1, ca=1):
                for each in cmds.shelfLayout(self.name, q=1, ca=1):
                    cmds.deleteUI(each)
        else:
            cmds.shelfLayout(self.name, p='ShelfLayout')
    
    
    def _deleteOldShelf(self):
            if cmds.shelfLayout(self.name, q=1, ex=1):
                cmds.deleteUI(self.name, layout=1)


you could try something like:

from maya.utils import executeDeferred

def _shelf_reload():
    # code to build instance, kill and reload shelf

executeDeferred(_shelf_reload)

That might be less crash-tastic?

1 Like

Hey Bob,
Nop, already tried that, it crashes maya if launched from the shelf while working from anywhere else.

Would there be a way in maya to create a script node which would launch a python script, with a button?

For insta-crashes (in my experience) that’s your code accessing deleted/garbage collected memory, right? So figure out where that’s happening precisely. I wonder if it’s happening in python, or maya? You can try connecting a c++ debugger to maya to take a look a the call stack at crash time to figure that out. You can sometimes gain other insight into the crash that way.

What’s the exact line that causes the crash? As deep as the call stack as you can go. Is it the deleteUI call? I’m going to assume “yes” for this one, but its definitely something to test… Or maybe CreateMayaShelf is getting defined and deleted strangely, so calling a method from the superclass is where the crash happens.

Where are you defining CreateMayaShelf? Are you doing it in the shelf button? Or maybe in the scripts directory? You’re sub-classing it to do something. Where are those subclasses defined? Try putting the definitions in the other place (shelf vs script).

1 Like

Hey there, sorry for the very long delay.
The exact line that causes the crash seems to be line 141: cmds.evalDeferred( self.build() ).

I’m defining CreateMayaShelf in a different module which I import. Im sub classing to reuse the base Shelf construction if I want ti create a different Shelf later.

Here are the two classes if you want to try it out. The subclass has been cleaned from tools buttons:

from maya import cmds, mel

def _null(*args):
    pass

class CreateMayaShelf(object):
    '''
    A simple class to build shelves in maya. Since the build method is empty,
    it should be extended by the derived class to build the necessary shelf elements.
    By default it creates an empty shelf called 'WIP'.
    BEWARE Do not use any space character ' ' in the name as Maya will freack out.
    '''

    def __init__(self, name='WIP'):
        self.name = name
        self.iconPath = 'L:/_tech/media/icons/maya_shelfs/'
        self.labelBackground = (0, 0, 0, 0)
        self.labelColour = (.9, .9, .9)

        self._cleanOldShelf()
        cmds.setParent(self.name)


    def setName(self, name):
        '''
        BEWARE shelf name must be unique as it will replace any shelf with same name.
        BEWARE Do not use any space character ' ' in the name as maya will freack out
        :param: str name
        '''
        self.name = name


    def build(self):
        '''This method should be overwritten in derived classes to actually build the shelf
        elements. Otherwise, nothing is added to the shelf.'''
        pass


    def addButon(self, label, icon='wip_32.png', command=_null, doubleCommand=_null):
        '''Adds a shelf button with the specified label, command, double click command and image.'''
        cmds.setParent(self.name)
        if icon:
            icon = self.iconPath + icon
        cmds.shelfButton(width=37, height=37, image=icon, l=label, command=command, dcc=doubleCommand, imageOverlayLabel=label, olb=self.labelBackground, olc=self.labelColour)


    def addSeparator(self):
        # cmds.addShelfSeparator()
        # cmds command seems broken
        mel.eval("addShelfSeparator()")


    def addMenuItem(self, parent, label, command=_null, icon=''):
        '''Adds a shelf button with the specified label, command, double click command and image.'''
        if icon:
            icon = self.iconPath + icon
        return cmds.menuItem(p=parent, l=label, c=command, i='')


    def addSubMenu(self, parent, label, icon=None):
        '''Adds a sub menu item with the specified label and icon to the specified parent popup menu.'''
        if icon:
            icon = self.iconPath + icon
        return cmds.menuItem(p=parent, l=label, i=icon, subMenu=1)


    def _cleanOldShelf(self):
        '''Checks if the shelf exists and empties it if it does or creates it if it does not.'''
        if cmds.shelfLayout(self.name, ex=1):
            if cmds.shelfLayout(self.name, q=1, ca=1):
                for each in cmds.shelfLayout(self.name, q=1, ca=1):
                    cmds.deleteUI(each)
        else:
            cmds.shelfLayout(self.name, p='ShelfLayout')
    
    
    def _deleteOldShelf(self):
            if cmds.shelfLayout(self.name, q=1, ex=1):
                cmds.deleteUI(self.name, layout=1)



class Shelf_Exemple(CreateMayaShelf):
    def __init__(self):

        # init class ####################################################
        self.name = 'Exemple'
        self.iconPath = 'L:/_tech/media/icons/maya_shelfs/'

        super(Shelf_Exemple, self).__init__(name = self.name )

        # build shelf ####################################################
        self.build()

    def build(self):

            # delete shelf if already exsist 
            self._cleanOldShelf()

            # create buttons
            self.button_RELOAD()
                    
            return 



    ############################################# BUTTONS
    def button_RELOAD(self):
            self.addButon(label='RELOAD',command='', icon='reset_32.png')
            popup_RELOAD = cmds.popupMenu(b=1)
            
            if popup_RELOAD:
                # Shelf
                buttonName = 'Reset Shelf'
                buttonCommand = lambda x: self.resetShelf_confirmDialog()
                self.addMenuItem(parent=popup_RELOAD, label=buttonName, command=buttonCommand)

            return 


    ############################################# METHODS + TOOLS
    def resetShelf_confirmDialog(self):
            """
            Reloading a shelf from the shelf crashes maya:
            we create a confirmWindow to trick import maya
            Recreate an instance of the shelf object.
            """
            title = 'Shelf Exemple'
            choiceMessage = 'Reset Shelf ?'
            userChoice = cmds.confirmDialog(t= title, m=choiceMessage , b=['Yes', 'No'])

            if userChoice:
                cmds.warning('Reloading Shelf')
                
                try:
                    cmds.evalDeferred( self._cleanOldShelf() )
                    cmds.evalDeferred( self.build() )
                    return 
                
                except:
                    return 

So these aren’t doing what you think they are. Basically they aren’t deferring anything, instead you’re calling those methods, and then deferring the results.

So 1) I’d use maya.utils.executeDeferred instead of evalDeferred it takes a callable object (not a string) and you’d spell those lines like so:

maya.utils.executeDeferred(self._cleanOldShelf)
maya.utils.executeDeferred(self.build)

Notice that I’m not calling the methods directly, instead I’m handing them to the executeDeferred function so that the system will call them at a later time.

1 Like

It seems Maya won’t crash as fast when using this, but if I try to rebuild the shelf after deleteing one of the buttons, maya will crash anyway.

It seems the crash appends after the build, as I can see the new uttons pop in the shelf before maya exits.

Might be worth trying

def _rebuild():
    self._cleanOldShelf()
    self.build()
maya.utils.executeDeferred(_rebuild)

As there is a weird chance that the order isn’t super deterministic when just pushing both methods separately to be deferred.

Tried doing that by moving the maya.utils.executeDeferred in the lambda function: it still works when I call the method from the script editor, but using the button will crash maya.

I’ll try to add a menu button outside of the shelf to reload it.