PyQT in Maya – Custom Class inheritance

Categories: Maya, PyQT, Python
Tags: No Tags
Comments: 4 Comments
Published on: January 15, 2013

PyQT in Maya

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!

Post to Twitter

4 Comments
  1. ryutaro says:

    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

    • jason says:

      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.

      • ryutaro says:

        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

  2. lala says:

    my friend. you helped me again and save my day. thanks again

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Welcome , today is Wednesday, May 31, 2023