Done Before
There are dozens of useful sites and blog posts for how to incorporate PyQT into Maya (Tech-Artists.org alone has over 130 threads!)
Nathan has some of the best overviews, here: nathanhorne.com
I’ve found it necessary to add my own to the mix because there are a couple of tricks I’ve found to make PyQT windows work with docking and inheriting custom core library classes, which is something we’ve used at the past couple of companies I have worked with.
One of the reasons why there are so many posts about using PyQT in Maya is because there are so many different ways to get it done. You can use QT Designer to make .ui files and load them on the fly, like I’m demonstrating here. You can convert the designer files to code or you can code up the entire UI from scratch.
Compiling and getting the correct versions of PyQT installed for Maya is no easy task either. You’re supposed to build it on your own following Autodesk’s instructions: building-qt-pyqt-pyside-for-maya-2013
But that can be very complicated and most prefer to get a download from a helpful person who have shared their built code (hint: google it)
So here’s your standard code needed to create a simple PyQT window:
# Python std lib import os # PyQT import PyQt4.QtGui import PyQt4.QtCore import PyQt4.uic import sip # Maya sdk import pymel.core import maya # Maya Core lib import core.mayaCore # Common Core lib import common.core # function to get main Maya window def get_maya_main_window( ): ptr = maya.OpenMayaUI.MQtUtil.mainWindow( ) main_win = sip.wrapinstance( long( ptr ), PyQt4.QtCore.QObject ) return main_win UI_FILE_PATH = '{0}/my_tool.ui'.format( common.core.globalVariables.UI_DIR ) # get PyQt4.uic objects and classes for main class to inherit try: ui_object, base_class = PyQt4.uic.loadUiType( UI_FILE_PATH ) except IOError: core.mayaCore.mCore.logger.warning( "Could not find: '{0}'".format( UI_FILE_PATH ) ) core.mayaCore.mCore.logger.debug( 'Loading ui file: {0}'.format( os.path.normpath( UI_FILE_PATH ) ) ) class My_Tool( base_class, ui_object ): """ Main class to build pyQT UI and tool *Arguments:* * ``base_class``Base Class of widget from ui file. * ``ui_object`` uiObjects of widget from ui file. *Examples:* tool = My_Tool() tool.show() """ def __init__( self, parent = get_maya_main_window(), *args ): super( base_class, self ).__init__( parent ) self.setupUi( self ) self.setObjectName( 'myTool_window' ) # Add functionality here def run(): if pymel.core.window( 'myTool_window', q = 1, ex = 1 ): pymel.core.deleteUI( 'myTool_window' ) tool = My_Tool() tool.show() run()
Breakdown
To our breakdown, section by section. Imports are obvious. Next:
# function to get main Maya window def get_maya_main_window( ): ptr = maya.OpenMayaUI.MQtUtil.mainWindow( ) main_win = sip.wrapinstance( long( ptr ), PyQt4.QtCore.QObject ) return main_win
This is a simple utility function to find Maya main window. (Copied from many a site). Next:
UI_FILE_PATH = '{0}/my_tool.ui'.format( common.core.globalVariables.UI_DIR )
An enumerated UI path. It’s always good to use relative paths in case they change. (They always do, of course). You’ll need a .ui file created with Qt Designer.
# get PyQt4.uic objects and classes for main class to inherit try: ui_object, base_class = PyQt4.uic.loadUiType( UI_FILE_PATH ) except IOError: core.mayaCore.mCore.logger.warning( "Could not find: '{0}'".format( UI_FILE_PATH ) ) core.mayaCore.mCore.logger.debug( 'Loading ui file: {0}'.format( os.path.normpath( UI_FILE_PATH ) ) )
Our usual grabbing of the PyQt objects to inherit from in our class call. (Explained many places). I like to catch exceptions wherever I can. Remember to always catch specific exceptions, like ‘IOError’ in this case. It’s pretty poor form to do a general ‘except’. I used the logger.warning call here, I think this maybe a custom level I added to coincide with Maya’s warning printout. There’s also a logger.debug level output telling you which .ui file you loaded. This only triggers when the logger level has been set to debug, of course.
I always like to know about what kind of objects I’m dealing with, just type them into the ScriptEditor to get their types.
ui_object # Result: <class 'Ui_Skeleton_Builder_Dialog'> # base_class # Result: <class 'PyQt4.QtGui.QDialog'> #
Next is the main class call:
class My_Tool( base_class, ui_object ):
Simply inherit from both classes. I’m not super sure if the order matters here. I think it does if you are using the .show() method to bring up the UI, but only in Windows. I did some tests and if you do base_class first, the UI stays on top of Maya just fine, if you reverse the two, it hops behind Maya after clicking anywhere else in the application. On a Mac it always disappeared behind, I could not get it to stay out front. That is why I use the docking method described below because that ensures staying on top even when the dock is ‘floating’ (just like a window!)
def __init__( self, parent = get_maya_main_window(), *args ): super( base_class, self ).__init__( parent ) self.setupUi( self ) self.setObjectName( 'myTool_window' )
The initialization function must be passed the parent window, hence the ‘parent’ key word argument (kwarg) which calls our utility function mentioned earlier. We need to call ‘super’ to inherit from our base_class which was returned from the uic.loadUiType() earlier. You can call super on the name of your actual class, i.e- super( My Tool ), but it is really running the super inheritance on the base_class so this syntax is fine too.
Then you need to call the setupUi() method. Just standard PyQT necessity. Go ahead and use the .setObjectName() method to name your window so you can clear it upon reload.
Lastly we draw the UI using .show():
def run(): if pymel.core.window( 'myTool_window', q = 1, ex = 1 ): pymel.core.deleteUI( 'myTool_window' ) tool = My_Tool() tool.show() run()
That explains the “simple” example above. Now to add some docking.
Docking
To add docking, add this method to your class and just call the method on your class instance to draw the UI:
#[...] # Add functionality here ''' Launch UI ''' def dock_ui( self ): if pymel.core.dockControl( 'myToolDock', q = 1, ex = 1 ): pymel.core.deleteUI( 'myToolDock' ) allowedAreas = ['right', 'left'] try: floatingLayout = pymel.core.paneLayout( configuration = 'single', width = 300, height = 400 ) except RuntimeError: self.m_logger.warning( "Skipping docking. Restart to dock." ) self.show() return False pymel.core.dockControl( 'myToolDock', area = 'right', allowedArea = allowedAreas, content = floatingLayout, label = 'My Tool' ) pymel.core.control( 'myTool_window', e = True, p = floatingLayout ) return True def run(): My_Tool().dock_ui() run()
You’ll notice I catch the creation of the paneLayout. On occasion this will not work, so it’s nice to go ahead and .show() the window anyway.
Class inheritance
Lastly, let’s add some complexity. If you’re lucky enough to work with a large enough team and/or on a large enough project that has allowed you to spend some time building up core libraries for re-use, you’ll no doubt want to take advantage of your code base and inherit from your core classes so you can use that code in your UI/tool. This can get tricky as you are already inheriting 2 PyQt4.uic classes in order to launch the UI.
In this example, I’d like to inherit from MayaCore. When I do, I get a whole slew of helper methods on this class. One of the attributes of the MayaCore class is a logger instance. It is very handy to just call self.logger and have it setup to log in to all the right places and all the right people.
Here’s the sample inheriting from your own class as well. You need to add your class into the inheritance brackets:
class My_Tool( base_class, core.mayaCore.MayaCore, ui_object ): """ *Arguments:* * ``base_class`` Base Class of widget from ui file. Must be FIRST! Must be inherited via super to maintain parent connection to Maya sessions * ``MayaCore`` Maya core library class to inherit from """ def __init__( self, parent = get_maya_main_window(), *args ): super( base_class, self ).__init__( parent ) core.mayaCore.MayaCore.__init__( self, *args) self.setupUi( self ) self.setObjectName( 'myTool_window' ) # Did mayaCore inherit? self.logger.info('testing')
Make sure it is second in the order! Then it is important to instantiate this class but do not use the ‘super’ command! If you do, it will overwrite the ‘base_class’ inheritance and cause you UI to “un-parent” from the main Maya UI. It will then disappear behind Maya when you click anywhere else. It also leads to lots of crashing after you run your tool several times in a session.
You’ll know it worked if it prints out ‘testing’ in the ScriptEditor. This self.logger attribute is proof that the MayaCore class has been inherited.
So, finally, all together:
# Python std lib import os # PyQT import PyQt4.QtGui import PyQt4.QtCore import PyQt4.uic import sip # Maya sdk import pymel.core import maya # Maya Core lib import core.mayaCore # Common Core lib import common.core # function to get main Maya window def get_maya_main_window( ): ptr = maya.OpenMayaUI.MQtUtil.mainWindow( ) main_win = sip.wrapinstance( long( ptr ), PyQt4.QtCore.QObject ) return main_win UI_FILE_PATH = '{0}/my_tool.ui'.format( common.core.globalVariables.UI_DIR ) # get PyQt4.uic objects and classes for main class to inherit try: ui_object, base_class = PyQt4.uic.loadUiType( UI_FILE_PATH ) except IOError: core.mayaCore.mCore.logger.warning( "Could not find: '{0}'".format( UI_FILE_PATH ) ) core.mayaCore.mCore.logger.debug( 'Loading ui file: {0}'.format( os.path.normpath( UI_FILE_PATH ) ) ) class My_Tool( base_class, core.mayaCore.MayaCore, ui_object ): """ Main class to build pyQT UI and tool *Arguments:* * ``base_class`` Base Class of widget from ui file. Must be FIRST! Must be inherited via super to maintain parent connection to Maya sessions * ``MayaCore`` Maya core library class to inherit from * ``ui_object`` uiObjects of widget from ui file. *Examples:* tool = My_Tool() tool.show() """ def __init__( self, parent = get_maya_main_window(), *args ): super( base_class, self ).__init__( parent ) core.mayaCore.MayaCore.__init__( self, *args) self.setupUi( self ) self.setObjectName( 'myTool_window' ) # Did mayaCore inherit? self.logger.info('testing') # Add functionality here ''' Launch UI ''' def dock_ui( self ): if pymel.core.dockControl( 'myToolDock', q = 1, ex = 1 ): pymel.core.deleteUI( 'myToolDock' ) allowedAreas = ['right', 'left'] try: floatingLayout = pymel.core.paneLayout( configuration = 'single', width = 300, height = 400 ) except RuntimeError: self.m_logger.warning( "Skipping docking. Restart to dock." ) self.show() return False pymel.core.dockControl( 'myToolDock', area = 'right', allowedArea = allowedAreas, content = floatingLayout, label = 'My Tool' ) pymel.core.control( 'myTool_window', e = True, p = floatingLayout ) return True def run(): My_Tool().dock_ui() run()
Happy coding!
Hello Jason. Thank you for your information. The article is informative for me.
But I can’t find “common.mayaCore” and “common.core” module. Probably these modules seem to be your original class.
Can I download the modules? I would like you to my question.
Thanks in advance.
– Ryutaro
Ryutaro,
You’re correct. Those are my own core Maya library classes. They are not available for download yet. I’ve created an alpha of a pipeline core library at: https://github.com/CountZer0/PipelineConstructionSet
So far, I’ve only added some basic MotionBuilder core libraries to it and a Maya menu launcher but no Maya core libraries yet. Someday I’ll expand on that.
For now, you can write your own and use the method above to inherit them into your PyQT tool class.
Thank you for your detailed reply. And sorry for my late reply.
I understand that “maya.core” module is your original module and it isn’t included your github module yet. But you might expand the module in the future.
I’m going to download your module on github. Thanks again!
– Ryutaro
my friend. you helped me again and save my day. thanks again