PyQt4 and Maya Help

I am in the process of learning how to create GUI’s using PyQt.

I decided to try and create a GUI for a simple tool I made, it works great until I try to close it, it closes but then maya freezes and I have no idea why, it runs fine when I run it outside of maya.

I am using maya 2011.

Here is the code I am executing in maya:


from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
import Steps.ui_stepsdlg

class StepsDlg(QDialog, Steps.ui_stepsdlg.Ui_TileWalk):
    def __init__(self,parent=None):
        super(StepsDlg, self).__init__(parent)
        self.setupUi(self)
    
    @pyqtSignature("") 
    def on_createButton_clicked(self):
        print "It Works"

app = QApplication(sys.argv)
form = StepsDlg()
form.show()
app.exec_()  

And here is the UI file:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'D:\Maya\scripts\Steps\stepsdlg.ui'
#
# Created: Tue Jun 08 15:35:06 2010
#      by: PyQt4 UI code generator 4.5.4
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

class Ui_TileWalk(object):
    def setupUi(self, TileWalk):
        TileWalk.setObjectName("TileWalk")
        TileWalk.resize(302, 210)
        self.gridLayout_3 = QtGui.QGridLayout(TileWalk)
        self.gridLayout_3.setObjectName("gridLayout_3")
        self.gridLayout = QtGui.QGridLayout()
        self.gridLayout.setObjectName("gridLayout")
        self.label = QtGui.QLabel(TileWalk)
        self.label.setObjectName("label")
        self.gridLayout.addWidget(self.label, 0, 0, 1, 2)
        self.label_2 = QtGui.QLabel(TileWalk)
        self.label_2.setObjectName("label_2")
        self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
        self.stepWSpinBox = QtGui.QDoubleSpinBox(TileWalk)
        self.stepWSpinBox.setMinimum(0.01)
        self.stepWSpinBox.setProperty("value", QtCore.QVariant(1.0))
        self.stepWSpinBox.setObjectName("stepWSpinBox")
        self.gridLayout.addWidget(self.stepWSpinBox, 1, 1, 1, 1)
        self.label_3 = QtGui.QLabel(TileWalk)
        self.label_3.setObjectName("label_3")
        self.gridLayout.addWidget(self.label_3, 1, 2, 1, 1)
        self.stepDSpinBox = QtGui.QDoubleSpinBox(TileWalk)
        self.stepDSpinBox.setMinimum(0.01)
        self.stepDSpinBox.setProperty("value", QtCore.QVariant(2.0))
        self.stepDSpinBox.setObjectName("stepDSpinBox")
        self.gridLayout.addWidget(self.stepDSpinBox, 1, 3, 1, 1)
        self.label_4 = QtGui.QLabel(TileWalk)
        self.label_4.setObjectName("label_4")
        self.gridLayout.addWidget(self.label_4, 1, 4, 1, 1)
        self.stepHSpinBox = QtGui.QDoubleSpinBox(TileWalk)
        self.stepHSpinBox.setMinimum(0.1)
        self.stepHSpinBox.setProperty("value", QtCore.QVariant(0.2))
        self.stepHSpinBox.setObjectName("stepHSpinBox")
        self.gridLayout.addWidget(self.stepHSpinBox, 1, 5, 1, 1)
        self.gridLayout_3.addLayout(self.gridLayout, 0, 0, 1, 2)
        self.gridLayout_2 = QtGui.QGridLayout()
        self.gridLayout_2.setObjectName("gridLayout_2")
        self.label_5 = QtGui.QLabel(TileWalk)
        self.label_5.setObjectName("label_5")
        self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1)
        spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
        self.gridLayout_2.addItem(spacerItem, 0, 1, 1, 1)
        self.numStepsSpinBox = QtGui.QSpinBox(TileWalk)
        self.numStepsSpinBox.setMinimum(1)
        self.numStepsSpinBox.setObjectName("numStepsSpinBox")
        self.gridLayout_2.addWidget(self.numStepsSpinBox, 0, 2, 1, 1)
        spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
        self.gridLayout_2.addItem(spacerItem1, 0, 4, 1, 1)
        self.label_6 = QtGui.QLabel(TileWalk)
        self.label_6.setObjectName("label_6")
        self.gridLayout_2.addWidget(self.label_6, 1, 0, 1, 1)
        spacerItem2 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
        self.gridLayout_2.addItem(spacerItem2, 1, 1, 1, 1)
        self.gapSpinBox = QtGui.QDoubleSpinBox(TileWalk)
        self.gapSpinBox.setObjectName("gapSpinBox")
        self.gridLayout_2.addWidget(self.gapSpinBox, 1, 2, 1, 1)
        self.label_7 = QtGui.QLabel(TileWalk)
        self.label_7.setObjectName("label_7")
        self.gridLayout_2.addWidget(self.label_7, 2, 0, 1, 1)
        self.durationSpinBox = QtGui.QSpinBox(TileWalk)
        self.durationSpinBox.setMinimum(1)
        self.durationSpinBox.setMaximum(1000)
        self.durationSpinBox.setProperty("value", QtCore.QVariant(10))
        self.durationSpinBox.setObjectName("durationSpinBox")
        self.gridLayout_2.addWidget(self.durationSpinBox, 2, 2, 1, 2)
        self.gridLayout_3.addLayout(self.gridLayout_2, 1, 0, 1, 2)
        spacerItem3 = QtGui.QSpacerItem(20, 28, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        self.gridLayout_3.addItem(spacerItem3, 2, 1, 1, 1)
        spacerItem4 = QtGui.QSpacerItem(113, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
        self.gridLayout_3.addItem(spacerItem4, 3, 0, 1, 1)
        self.horizontalLayout = QtGui.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.createButton = QtGui.QPushButton(TileWalk)
        self.createButton.setObjectName("createButton")
        self.horizontalLayout.addWidget(self.createButton)
        self.cancelButton = QtGui.QPushButton(TileWalk)
        self.cancelButton.setObjectName("cancelButton")
        self.horizontalLayout.addWidget(self.cancelButton)
        self.gridLayout_3.addLayout(self.horizontalLayout, 3, 1, 1, 1)

        self.retranslateUi(TileWalk)
        QtCore.QObject.connect(self.cancelButton, QtCore.SIGNAL("clicked()"), TileWalk.reject)
        QtCore.QMetaObject.connectSlotsByName(TileWalk)

    def retranslateUi(self, TileWalk):
        TileWalk.setWindowTitle(QtGui.QApplication.translate("TileWalk", "Steps/Tile Walk", None, QtGui.QApplication.UnicodeUTF8))
        self.label.setText(QtGui.QApplication.translate("TileWalk", "Step Dimensions:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_2.setText(QtGui.QApplication.translate("TileWalk", "Width:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_3.setText(QtGui.QApplication.translate("TileWalk", "Depth:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_4.setText(QtGui.QApplication.translate("TileWalk", "Height:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_5.setText(QtGui.QApplication.translate("TileWalk", "Number of Steps:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_6.setText(QtGui.QApplication.translate("TileWalk", "Gap Between Steps:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_7.setText(QtGui.QApplication.translate("TileWalk", "Duration of Each Flip:", None, QtGui.QApplication.UnicodeUTF8))
        self.durationSpinBox.setSuffix(QtGui.QApplication.translate("TileWalk", " Frames", None, QtGui.QApplication.UnicodeUTF8))
        self.createButton.setText(QtGui.QApplication.translate("TileWalk", "Create", None, QtGui.QApplication.UnicodeUTF8))
        self.cancelButton.setText(QtGui.QApplication.translate("TileWalk", "Cancel", None, QtGui.QApplication.UnicodeUTF8))

Any ideas why its killing maya.

Also another quick question, how would I get it to close the dialog once I click create?

Cheers.

Oooohhh goody, my favorite type of question! I love Maya and PyQt!

Assuming this is Maya 2011 we are talking about: you are hanging Maya because you are creating a new Qt Application, and running a second exec loop. Maya already has a QApplication instance running, as well as an exec loop, so doubling them up is causing it to hang. If you do ever need to access the QApplication instance use the value from QtGui.qApp.
(In the case of pre-Maya 2011, look into the pyqt pumpThread example included in the sdk)

If you need it to work outside of Maya you would need to detect that case, and create a QApplication and app exec loop there.

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
import ui_stepsdlg #chagned your namespace, I don't have your same package.

class StepsDlg(QDialog, ui_stepsdlg.Ui_TileWalk):
    def __init__(self,parent=None):
        super(StepsDlg, self).__init__(parent)
        self.setupUi(self)
    
    @pyqtSignature("") 
    def on_createButton_clicked(self):
        print "It Works"

#Must declare your form instance as global, or it will get garbage collected on occasion.
global form
form = StepsDlg()
form.show()

To get it to close, in your on_createButton_clicked(self) just call self.close() or self.hide() at the end.

I would recommend using the uic module to dynamically load in your ui file. The startup time difference is barely noticeable, and it makes it much easier to work with.
Also, you probably want to parent your window to Maya’s, with the above code it’s going to become a second window under the same application group, but the main Maya window will be able to raise above this window (Usually not the desired behavior for tools sub-windows)

Try this code out to fetch the main Maya window:


import sip
import maya.OpenMayaUI as mui
def getMayaWindow():
	'Get the maya main window as a QMainWindow instance'
	ptr = mui.MQtUtil.mainWindow()
	return sip.wrapinstance(long(ptr), QtCore.QObject)

And then make your __init__default to this:

def __init__(self,parent=getMayaWindow()):

Many thanks for your reply, you’ve helped me out loads.

Im hainvg a little bit of trouble trying to use the uic module.

The way I was doing it was to convert the ui file to a python file, and then use the setupUi() and the on_button_clicked to make the connections.

With the uic module, Im guessing you just use uic.loadUi and make connections using the connect method rather than the setupUi, is this correct?

Thanks again.

You will still have to run setupUi(), but it will be there on a class you inherit from (With all you ui elements). PyQt will still try to connect signals and slots by name, but generally I would recommend using the new style connect method.

Here’s how I use uic, it has a really cool function called loadUiType, which returns the base class of the ui, as well as the specific form class. it works great for setting up and customizing your ui.


#where uiFile is the path to our designer .ui file
form_class, base_class = uic.loadUiType(uiFile)

class Window(base_class, form_class):
	def __init__(self, parent=getMayaWindow()):
		'''A custom window with a demo set of ui widgets'''
		#init our ui using the MayaWindow as parent
		super(base_class, self).__init__(parent)
		#uic adds a function to our class called setupUi, calling this creates all the widgets from the .ui file
		self.setupUi(self)
                
		#Now all the widgets are under self, for example
		self.createButton.clicked.connect(self.createButtonClicked)

	def createButtonClicked(self):
		print 'Clicked!'
		self.close()


Also, if you want to get icons working using a relative path instead of having to work with qrc files you can point your Qt’s current working directory to the same as the .ui file before loading the ui:


dir = QtCore.QDir()
ui_path = os.path.dirname(uiFile)
dir.setCurrent(ui_path)

Many thanks, really top notch reply.

Ill have a play around later using the uic module, cheers for showing me the new connect method to.

Thanks again.

Tom.

Nathan,

Saw your post on running PyQt in Maya 2011 for 64bit:
http://nathanhorne.com/

But for 32bit, the instruction page link you noted:
http://tinytools.sebr.fr/index.php/topic,11.0.html

Needs PyQt-Py2.6-gpl-4.6-1.exe for Maya2011 32bit.

But the download link now points to Riverbank’s 4.7.3.-2.exe which does not seem to import into Maya.

I wonder if you have or know how to get ahold of PyQt-Py2.6-gpl-4.6-1.exe? Don’t have a copy do ya?

Thanks,

I had a working copy of it at some point, but I ca’t find it anymore… The autodesk guide, andthis one both work well for building Qt, Sip, and PyQt. I can build it for you, but probably not until next week unless I have free time over the weekend. Its fairly easy to build, the only annoying thing is the amount of compile time. probably close to 5 or 6 hours total.
You probably just want the same thing I did for x64, the latest version of PyQt against Qt 4.5.3.

[QUOTE=Temujin;6610]You will still have to run setupUi(), but it will be there on a class you inherit from (With all you ui elements). PyQt will still try to connect signals and slots by name, but generally I would recommend using the new style connect method.

Here’s how I use uic, it has a really cool function called loadUiType, which returns the base class of the ui, as well as the specific form class. it works great for setting up and customizing your ui.


#where uiFile is the path to our designer .ui file
form_class, base_class = uic.loadUiType(uiFile)

class Window(base_class, form_class):
	def __init__(self, parent=getMayaWindow()):
		'''A custom window with a demo set of ui widgets'''
		#init our ui using the MayaWindow as parent
		super(base_class, self).__init__(parent)
		#uic adds a function to our class called setupUi, calling this creates all the widgets from the .ui file
		self.setupUi(self)
                
		#Now all the widgets are under self, for example
		self.createButton.clicked.connect(self.createButtonClicked)

	def createButtonClicked(self):
		print 'Clicked!'
		self.close()


Also, if you want to get icons working using a relative path instead of having to work with qrc files you can point your Qt’s current working directory to the same as the .ui file before loading the ui:


dir = QtCore.QDir()
ui_path = os.path.dirname(uiFile)
dir.setCurrent(ui_path)

[/QUOTE]

I am having a hard time passing anything to a method when called like above.


self.createButton.clicked.connect(self.createButtonClicked('passed'))
def createButtonClicked(self, passedVar):
	print passedVar
	self.close()

Anyone know of a good way to handle that?
Thanks,
/Christian

First off, hats off to Nathan, your post helped me get on track when dealing with pyqt4 in Maya. I have a question when it comes to docking and I am using Nathan’s example below:


import os
import sip

import maya.cmds as cmds
import maya.OpenMayaUI as mui

from PyQt4 import QtGui, QtCore, uic

def getMayaWindow():
	'Get the maya main window as a QMainWindow instance'
	ptr = mui.MQtUtil.mainWindow()
	return sip.wrapinstance(long(ptr), QtCore.QObject)

#Get the absolute path to my ui file
uiFile = os.path.join(cmds.internalVar(upd=True), 'ui', 'demo.ui')
print 'Loading ui file:', os.path.normpath(uiFile)

#Load the ui file, and create my class
form_class, base_class = uic.loadUiType(uiFile)
class Window(base_class, form_class):
	def __init__(self, parent=getMayaWindow()):
		'''A custom window with a demo set of ui widgets'''
		#init our ui using the MayaWindow as parent
		super(base_class, self).__init__(parent)
		#uic adds a function to our class called setupUi, calling this creates all the widgets from the .ui file
		self.setupUi(self)
		self.setObjectName('myWindow')
		self.setWindowTitle("My Qt Demo Window")

def main():
	global myWindow
	myWindow = Window()
	#myWindow.show()
	if (cmds.dockControl('myDock', q=1, ex=1)):
		cmds.deleteUI('myDock')
	allowedAreas = ['right', 'left']
	myDock = cmds.dockControl('myDock',aa=allowedAreas, a='right', content='myWindow', label='My Qt Demo Window', w=350)

That will dock the created ui in Maya 2011.
For the questions:

  1. If I have a pyqt tabWidget in the UI, how can I attach it to the boundaries of the window itself? (attribute editor frames strech when pulled out)
  2. How can I prevent the window from going below a certain size (look at the way the attribute editor behaves).
  3. Can you create collapsable frames in pyqt? I was a heavy user of that on the mel side, was just wondering if something similar exists in (py)qt land.

Thanks,
/Christian

P.S Love how you can handcode ontop of .ui files with the setup you posted above…

For you question on passing arguments:

There’s two scenarios for this, so ill show how I would do each. Anyone who knows a more pythonic/simpler way to do this please let me know.

You can bake it in when you create the button, just like you would using a mel/python button with the -command flag. This data is set in stone, when the button is actually created.

class Window(QtCore.QObject):
    def __init__(self):
        super(QtCore.QObject, self).__init__()
        self.myData = {'a python dict':1234, 'Hello world':['my', 'python', 'list']}
        
        self.button = QtGui.QPushButton()
        
        #you can bake in the argument into the connect using functools.partial
        import functools
        self.button.clicked.connect(functools.partial(self.buttonClicked, self.myData))
        
        self.button.show()
    
    def buttonClicked(self, *args):
        print 'Button clicked, args passed:', args

win = Window()

This way the arguments/data are generated at click time, not at the time the button was created

class MyButton(QtGui.QPushButton):
    #create the custom signal, use PyQt_PyObject
    customClicked = QtCore.pyqtSignal('PyQt_PyObject')
    
    def __init__(self, parent=None):
        super(QtGui.QPushButton, self).__init__(parent)
        self.myData = ['some python data', 'in a list']
        
        #make the clicked signal emit mySignal
        self.clicked.connect(self.emitCustomClicked)
    
    def emitCustomClicked(self):
        #Make a custom function, which gets our data at click time and emits it
        self.customClicked.emit(self.myData)

#test function to connect to
def buttonClicked(myData):
    print 'button clicked', myData

btn = MyButton()
btn.customClicked.connect(buttonClicked)
btn.myData = ['Actual data that will be sent', 'even though the signals are already created']
btn.show()

[QUOTE=cakesson;6668]First off, hats off to Nathan, your post helped me get on track when dealing with pyqt4 in Maya. I have a question when it comes to docking and I am using Nathan’s example below:


import os
import sip

import maya.cmds as cmds
import maya.OpenMayaUI as mui

from PyQt4 import QtGui, QtCore, uic

def getMayaWindow():
	'Get the maya main window as a QMainWindow instance'
	ptr = mui.MQtUtil.mainWindow()
	return sip.wrapinstance(long(ptr), QtCore.QObject)

#Get the absolute path to my ui file
uiFile = os.path.join(cmds.internalVar(upd=True), 'ui', 'demo.ui')
print 'Loading ui file:', os.path.normpath(uiFile)

#Load the ui file, and create my class
form_class, base_class = uic.loadUiType(uiFile)
class Window(base_class, form_class):
	def __init__(self, parent=getMayaWindow()):
		'''A custom window with a demo set of ui widgets'''
		#init our ui using the MayaWindow as parent
		super(base_class, self).__init__(parent)
		#uic adds a function to our class called setupUi, calling this creates all the widgets from the .ui file
		self.setupUi(self)
		self.setObjectName('myWindow')
		self.setWindowTitle("My Qt Demo Window")

def main():
	global myWindow
	myWindow = Window()
	#myWindow.show()
	if (cmds.dockControl('myDock', q=1, ex=1)):
		cmds.deleteUI('myDock')
	allowedAreas = ['right', 'left']
	myDock = cmds.dockControl('myDock',aa=allowedAreas, a='right', content='myWindow', label='My Qt Demo Window', w=350)

That will dock the created ui in Maya 2011.
For the questions:

  1. If I have a pyqt tabWidget in the UI, how can I attach it to the boundaries of the window itself? (attribute editor frames strech when pulled out)
  2. How can I prevent the window from going below a certain size (look at the way the attribute editor behaves).
  3. Can you create collapsable frames in pyqt? I was a heavy user of that on the mel side, was just wondering if something similar exists in (py)qt land.

Thanks,
/Christian

P.S Love how you can handcode ontop of .ui files with the setup you posted above…[/QUOTE]

Glad I can be of help! For connecting your widgets to the boundaries, if you are loading in from QTDesigner, just make sure your layouts are properly setup to stretch in there, and it should work when you load it into maya. Otherwise, you will need to manually add your widgets to a layout to get them to stretch with the UI. Qt Layout’s in designer doc Qt layout Docs

To make your layout stay a certain size, once again, set it up in designer to behave correctly, or use the functions on your qt objects directly, look for the minimum size function set QWidget minimum size functions

For collapsible frames, not by default no, they dont exist, you could very easily create your own widget for it using several existing widgets like QGroupBox. Or you could create one using Maya cmds, and re-parent it into your UI.
I’ve also heard of using QTreeWidget (Which has a ± button to expand an item) and putting a QGroupBox inside it. if you style it right, it would just be your groupbox with a ± next to it.

A really easy way to learn to use PyQt is to set it up the way you want in designer, and then convert the designer .ui form to python code using pyuic4.bat. It can spit out a .py file with your form’s code in it, and you can see exactly how it’s creating that layout.

That will work, very nice!
Thanks for those tips and links.

/Christian

Hey Nathon, ran into another issue I was hoping you could help me with. Its more to do with general pyqt than maya.

I created a ui and I am using the load uic, the issue is I used qtdesigner to set up my icons in a resource file, how do I get them to work within pyqt.

I read something about having to convert them using pyrcc4 and then assign them in your code, is there a way that I can do it without having to set them up again, I have my icons linked to buttons using the designer.

Cheers.

Never mind I sorted it, just ran pyrcc4 on the resource file and pyqt did the rest lol.

Hi, I am trying to create a tab next to the renderlayers tab and steal some of the icons from that tab.
I have have it all working except I can’t seem to get the icons.

I couldn’t find the icons on disk so assume they are wrapped up in the qt resources some how.

I have been trying this approach:

from PyQt4 import QtGui, QtCore, uic, Qt
import maya.OpenMayaUI as mui
import sip


def getControl(controlName):
    ptr = mui.MQtUtil.findControl(controlName)
    return sip.wrapinstance(long(ptr), QtCore.QObject)

btn = getControl("symbolButton1")
# symbolButton1 is the name of the move layer up button on the render
# layer tab

pixmap = btn.icon().pixmap(16)

wigdet = QtGui.QWidget()

btn2 = QtGui.QPushButton()
btn2.setIcon(QtGui.QIcon(pixmap))

lay = QtGui.QHBoxLayout()
wigdet.setLayout(lay)
lay.addWidget(btn2)

wigdet.show()

Any help would be appreciated.

That seems like it should work, but here’s an easy way to just rip the textures to a file:

cmds.resourceManager(saveAs=["addWire.png", "Save/Image/Path.png"]);

You can view and grab the names of all the resources using this:

import maya.app.general.resourceBrowser as resourceBrowser;
resourceBrowser.resourceBrowser().run()

The resource Browser actually has a “Save As” button as well, so it just depends on if you want to rip them in advance, or in code at runtime.

Thanks, for the quick reply. Awesome! I’ve never even heard of the resourceBrowser. Worked a treat.

Nathan G

Hi, so I am now trying work out how some of maya’s widgets are created.


from PyQt4 import QtGui, QtCore
import maya.OpenMayaUI as mui
import sip


def getControl(controlName):
    ptr = mui.MQtUtil.findControl(controlName)
    return sip.wrapinstance(long(ptr), QtCore.QObject)


RenderLayerTabRenderLayerEditor = getControl("RenderLayerTabRenderLayerEditor")

children = RenderLayerTabRenderLayerEditor.children()

print children

The example above obviously just gives you the QWidget but I’m assuming “RenderLayerTabRenderLayerEditor” is some kind of item view.

Is there a way to get more information about the implementation?

Thanks in advance!

Guess I’m asking, Is there a way to get the derived class from the base class (QWidget)?

So I have been doing something similar to what you suggest, but I am having issues where my toolwindows (if open) do not seem to be properly closing when maya shuts down - and I am not sure how to fix it:
I have some code to get the main maya window, I implemented the tool based on a class template which allows me to load a .ui file, etc.:

class Toolwindow(base_class, form_class):
    def __init__(self, parent=getMayaMainWindow()):

The class tracks call backs, and has some clean-up methods I can call:

    #----------------------------------------------------------------------
    def cleanUp(self):
        #need to clean up the call backs
        for c in self.callbackList:
            LOG.log.debug('Removing this callback :'+str(c))
            try:
                OM.MSceneMessage.removeCallback(c)
                LOG.log.debug('Callback: {0} was removed!'.format(c))
            except:
                LOG.log.debug('Could not remove the callback: {0}'.format(c))        
        pass

    #----------------------------------------------------------------------
    def closeEvent(self, event):
        """Event which is run when window closes"""

        # stop the tracker just in case
        self.tracker.stop()
        
        # cleanup()
        self.cleanUp()
        self.writeSettings()

        #self.removeCallBacks(self.callbackList)
        LOG.log.debug( 'The toolwindow was closed.' )
        pass

I’ve got an exit callback code block:

#----------------------------------------------------------------------  
def mayaExitCallback( *args):
    print 'The mayaExitCallback ARGS are: '+str(args)
    LOG.log.debug( 'mayaExitCallback' )
    toolWindow.close()  
    return

in my main loop, where I create the toolwindow, set up callbacks and such:

def main():
# Setting up the window
    global toolWindow
    toolWindow = ToolWindow()

    global exitSceneCB
    mayaExit = OM.MSceneMessage.kMayaExiting
    exitSceneCB = OM.MSceneMessage.addCallback(mayaExit, mayaExitCallback)
    toolWindow.addToCBlist(exitSceneCB)

    toolWindow.show()

So, I figured that when Maya exits it would trigger the callback, I tell my window to close, which fires off the .closeEvent which also cleans up.

But Maya crashes on exit and I see my window and the script editor hanging there during the crash routine, the last thing I see as feedback is ‘# leaked’ - am I doing something incorrectly?