2011
11.22

Why use PyQt/PySide at all in Maya?

  • A larger array of available GUI elements to work with.
  • A visual editor(Qt Designer).
  • Object Oriented design.
  • Signal/Slot mechanism, combined with an event driven system.
  • Access to low level components such as the base QWidget, QObject, QAction, and QPainter.
  • Much more! We are going to go over many things that are simply impossible within the context of Maya’s default toolkit.

Some examples of cool things!

image

A proper tree widget, and full control over it.

pyqt_maya

Mounds of random GUI’s as you can see, icon support in PyQt is just about everywhere versus Maya UI where only a few specific controls support images (And never dynamic resizing of them).

PyQt/PySide, lets get this out of the way…

  • PyQt and PySide are nearly identical as of right now. PySide is newer and is being sponsored and partially developed by the people who make Qt itself, Nokia.
  • PyQt website (Made by Riverbank software)
  • PySide website (Made by Nokia)
  • For this article I will be covering primarily PyQt, but PySide is pretty much the same so just swap PyQt for PySide and you should be fine.
  • PySide is available as LGPL as well as GPL. PyQt is GPL only.
  • I will make a note if something is specifically different between the two.

Setting up PyQt

Hopefully by now you are saying “Gee, that sounds great, but how do I get it installed and working!”, fear no more! I am also going to go over some basic deployment information, in case you want to roll out PyQt to your team.

Setting up PyQt – Getting it!

Here are the versions of Maya, and their corresponding installers for PyQt. All have been tested in production env. and are verified working. Download the correct installer for your version of Maya below. (If you are on OS-X/Linux you will have to acquire these yourself sadly. If anyone wants to do a write up and provide links let me know.)  Credit goes to Robert Kist (http://blarg.robertkist.com/) for the 2012 X86 build. Thanks Robert!

Setting up PyQt – Installing it

I have always found that it’s easiest to install new packages into a clean python install before extracting them out into their own folder, or moving them into Maya’s site-packages directory. Here’s the installers for the versions of Python that Maya uses (And that the above PyQt installers will be looking for).
Maya2011/2012:
http://www.python.org/ftp/python/2.6.4/python-2.6.4.msi
Maya2011/2012 x64:
http://www.python.org/ftp/python/2.6.4/python-2.6.4.amd64.msi

Now install PyQt into that clean Python install.

Setting up PyQt – Making Maya aware of the PyQt and sip packages

There are multiple ways of doing this, and which works best depends on how your pipeline/deployment works. There are probably more ways as well, but these are the ones that I know of.

The last two of these, the .pth method and the site.addsitedir method are both great for deploying PyQt via a network location. With these you can just drop a single copy somewhere everyone has access to, and then during Maya startup (Or python in general with site-customize) add the correct path and voila PyQt is working.

Copying to Maya’s site packages
  • Probably the easiest method for a single user who only wants to use PyQt from inside Maya.
  • Has to be done per-user and involves having access to a copy of the files to copy over.
  • Updates are somewhat difficult only because they need to be done per user (Unless you have a versioned Maya install)
  • Copy the PyQt4 folder, and the sip.pyd file from your python26/lib/site-packages directory into the Maya install /python/lib/site-packages directory.
Creating a .pth file
  • Still requires a file per-user, but not a full copy of the packages
  • Updates are easy, just update the network/Perforced location
  • Extremely simple file, and can be created after python startup but before pyqt import if need be.
  • Create an empty text file in your Maya install /python/lib/site-packages directory and rename it to something like pyqt.pth
  • Open that file in a text editor and paste in the full path to your PyQt4/sip site-packages folder ( python26/lib/site-packages)
site.addsitedir
  • No files in the users Maya directory
  • Requires code to be run before importing PyQt (Can easily be done in sitecustomize.py, or userSetup.py, or at head of script)
  • Before trying to import PyQt or sip, use something like the following code
  • import site
  • site.addsitedir(r’PATH/TO/PYQT-SITE’)
Setting up PyQt – Test it!

In Maya, try the following code

import sip
sip.setapi('QString', 2)
sip.setapi('QVariant', 2)
from PyQt4 import QtGui, QtCore
btn = QtGui.QPushButton(QtGui.__file__)
btn.show()

If you don’t get any errors and a button is created (Not parented to Maya though) then it worked! Otherwise, leave me a comment with the error message from Maya, and I will try to help you out.

A quick note on sip

PySide uses the equivalent of the PyQt version 2 API, while PyQt has two versions that can be picked via sip.setapi(). To maintain code compatibility between the two and for the benefit of the improved API you will see “sip.setapi(‘blah’, 2) on occasion. sip only permits the API to be set before PyQt is loaded, so be aware that you cannot change API’s mid-run. Maya MUST be restarted for code using the alternate API to be run, and only if the api version is set properly before PyQt is imported. For this reason, try to pick an API version and stick with it (I highly recommend version 2…)

Diving in..

#Source code for some common Maya/PyQt functions we will be using
import sip
sip.setapi('QString', 2)
sip.setapi('QVariant', 2)
from PyQt4 import QtGui, QtCore
import maya.OpenMayaUI as apiUI

def getMayaWindow():
	"""
	Get the main Maya window as a QtGui.QMainWindow instance
	@return: QtGui.QMainWindow instance of the top level Maya windows
	"""
	ptr = apiUI.MQtUtil.mainWindow()
	if ptr is not None:
		return sip.wrapinstance(long(ptr), QtCore.QObject)

def toQtObject(mayaName):
	"""
	Convert a Maya ui path to a Qt object
	@param mayaName: Maya UI Path to convert (Ex: "scriptEditorPanel1Window|TearOffPane|scriptEditorPanel1|testButton" )
	@return: PyQt representation of that object
	"""
	ptr = apiUI.MQtUtil.findControl(mayaName)
	if ptr is None:
		ptr = apiUI.MQtUtil.findLayout(mayaName)
	if ptr is None:
		ptr = apiUI.MQtUtil.findMenuItem(mayaName)
	if ptr is not None:
		return sip.wrapinstance(long(ptr), QtCore.QObject)
PyQt and Maya – Parenting to the Maya Window

The process for parenting your window to the main Maya window is fairly simple. First you get the main Maya window as a QMainWindow instance using the getMayaWindow() function and then pass that instance in as the parent object for your new top level PyQt object.

PyQt and Maya – Parenting to a specific Maya Widget

Similar to the main Maya window, here the path for our Maya UI control is passed to the toQtObject() function to get a PyQt object. Next just parent the new object to the Maya PyQt one. The main issue here is going to be that Maya uses “invisible” layouts to control how objects are placed. I would generally recommend avoiding this use scenario unless absolutely necessary, navigating the Maya widget hierarchy can be a pain. Look for info on these invisible widgets in the Maya API docs for MScriptUtil, it shows how to query the internal Qt structure for the Maya widgets.

PyQt and Maya – Parenting a Maya widget to PyQt

First convert the Maya UI control name to a PyQt object then call .setParent() on that object and provide the new parent widget. You will probably need to assign it a layout in PyQt as well, depending on the structure of your UI.

PyQt and Maya – Customizing a Maya widget with PyQt

Convert your Maya UI control to a PyQt object. Customize as you normally would for that object type (Ex: assign a Qt style sheet, or add an image to a QPushButton)

PyQt general – Garbage collection

Python will garbage collect any locally scoped objects when a function has finished executing if their reference count drops to zero. The effect on us is that a window declared in a function will be immediately destroyed when that function ends, if that window is not stored elsewhere. The two most common ways to store it are either by declaring the window variable global, or by assigning it into the __main__ module (import __main__).

Qt Designer and you!
Why use Qt Designer?
  • Esablishing layouts and getting everything properly positioned is more easily achieved visually (IMO)
  • The designer files offload what is normally the largest portion of your code to a seperate file.
  • XML is nice and easy to parse, and modify at run-time.
  • It’s possible for non-programmers to tweak UI’s to some degree. (Customize icon sets, change tooltips, etc..)
  • Utilize the Qt resource system (Although some modifications are necesarry at run-time or as a “build” step depending.
Pre-compiled designer files versus run-time
  • Both PyQt and PySide have a utility for generating a python file based on the designer .ui file.
  • Both PyQt and PySide have the ability to parse and convert the designer file to python objects on the fly.
  • Unless you are having a huge hit startup time wise for your UI (Due to the parse step of the UI xml data) I would highly recommend converting on the fly.
  • On the fly is much more dynamic, and you wont have to constantly maintain the intermediate .py file.
  • Also there’s still an import step involved for the .py file, so it’s not going to save a substantial amount of time in most cases.
  • If the UI file is load on the fly, it’s easy to modify the incoming xml file before loading it. This is most useful for finding resource paths and either compiling them, or handling them in a custom way.
How to load a designer file
  • DO NOT USE cmds.loadUi!!!
    • seriously, don’t.. forget it exists, now.
    • Who ever wrote it needs to be punched in the mouth.
  • Use uic/pysideuic (PySide has an alternate setup here), it’s awesome!
  • Note: pysideuic is lacking a loadUiType function, I have a workaround that I will try to mention in the example code. (Edit: here’s some PySide loadUiType code)
  • The uic.loadUiType function is the one that should be used for loading in a PyQt UI from a designer file.
  • Depending on how your resources are set up you may wish to alter the UI file before passing it to the loadUiType command. (See resources section below)
from PyQt4 import uic
form_class, base_class = uic.loadUiType('path/to/uifile.ui')

The  form_class returned is your customized python class (Based on object) and base_class is the super class of the window (Ex: QMainWindow, QDialog, etc..). Now to begin using them simply subclass from both (multiple inheritance). In your __init__ method you need to use super() to call the proper parent __init__ functions, and then setupUi() on self to instantiate the designer widgets onto self. One important thing to note here, is that the form_class and base_class attributes need to be unique variables for each UI at the top level, or you will have issues when you go to create your window instances.

class Window(form_class, base_class):
    def __init__(self, parent=None):
        super(Window, self).__init__(parent)
        self.setupUi(self)

Here’s a more specific example showing how to use a designer file as a base, and extend it in code. We are going to create a simple list, where items can be added, and removed via a button. The layout portion of this is defined in Qt designer, which I’ve provided the file for (example1.ui file). Now let’s dig into some code! (Full source code)

#This relies on the earlier getMayaWindow() function, so make sure that this code has access to that.
from PyQt4 import uic

#If you put the .ui file for this example elsewhere, just change this path.
listExample_form, listExample_base = uic.loadUiType('c:/example1.ui')
class ListExample(listExample_form, listExample_base):
	def __init__(self, parent=getMayaWindow()):
		super(ListExample, self).__init__(parent)
		self.setupUi(self)

		#The names "addItemBtn" and "removeItemBtn"
		#come from the "objectName" attribute in Qt Designer
		#the attributes to access them are automatically created
		#for us when we call setupUi()
		#Designer ensures that the names are unique for us.
		self.addItemBtn.clicked.connect(self.addItem)
		self.removeItemBtn.clicked.connect(self.removeItem)

	def addItem(self):
		"""
		Add a new item to the end of the listWidget
		"""
		item = QtGui.QListWidgetItem(self.listWidget)
		item.setText('Item #%s!'%self.listWidget.count())

	def removeItem(self):
		"""
		Remove the last item from the listWidget
		"""
		count = self.listWidget.count()
		if count:
			self.listWidget.takeItem(count-1)

I have allot more planned, but Thanksgiving is coming up so I have decided to split this up into parts (probably at least 3, judging by how much of my overview I covered in this intro)

  1. Actually, PyQt also has two licensing options. It is available as a commercial license which, though pricey, may be an option for those who are GPL averse. You really shouldn’t need to worry about it unless you plan on distributing your code to others and money is involved somehow.

  2. This is a great article, Thanks! between this and the new Maya Python book I have no reason to make ugly GUI’s.

  3. Awesome! Thanks.

  4. Wow, GREAT step-by-step presentation, thanks for sharing!

  5. Awesome write up Nathan. Justin Israel has some pre-compiled PyQt stuff here: http://www.justinfx.com/2011/11/09/installing-pyqt4-for-maya-2012-osx/

    He’s got a version for Maya 2012 for OSX, Windows (32/64 <- 64 bit is yours) and a Linux build.

  6. […] by mattanimation. Posted in Uncategorized Well I was reading this post by Nathan Horne of Naughty Dog and read this […]

  7. Heya Nathan!

    I’m trying to wrap my head around pyQT and python. I’m wondering if all the computers using the GUI needs pyQT installed.

    I’m thinking that if i build a tool using a pyQT GUI and i want to use it on a computer where i haven’t installed pyQT, does it use python instead or doesn’t it work at all ?

    I’m sorry if this is a rudementary question but as i said i’m new to all this and i’m just trying to form a basic understanding of pyQT.

  8. PyQt is required to be available for each computer that wants to be able to run that GUI. If it’s not there, then the “from PyQt4 import X” lines will fail.

  9. Thank you for the quick response Nathan.

    unfortunately I think I have to stick to clean python then. To bad I was really liking the workflow of creating GUI’s.

    It seems likely that Autodesk will incorporate pyQT in the future, hopefully it’s not that far away.

  10. Can you confirm whether your PyQt4 builds include all modules? For example, I believe that QMultimedia has not been included.

  11. Actually, that doesn’t guarantee that your UIs won’t be ugly. You should also be studying interaction design – that’s a whole skill unto its own.

  12. These builds do not include everything, just the standard Qt libraries. Extra’s (As of the time of 4.5) like QtMultimedia aren’t included in 4.5 (Although I am fairly sure they are in the 2012 builds). If you choose to go with PySide, that has pretty much all of the modules included.

  13. that was a great post Nathan, looking forward for the Pt.2!

  14. […] up, so you can load the .ui file in a python app with out having to compile the file. Nathan Horne wrote a nice wrapper: […]

  15. I’m trying to build some stuff via the dynamic loading with the UIC module with PyQt4 and I’m running into some trouble when dealing with event handling. The conventional way seems to be sub-classing your widget and then overriding the particular event handlers to do what you want.

    I’m not sure how to do that when my UI classes are being dynamically loaded. I can override them by plugging new functions to their __class__. but that seems clunky and ends up overriding all instances of that class, not just a particular control.

    Do you have any thoughts on this? I’m just trying to handle some more advanced drag/drop and key-presses in my UI.

  16. In Qt designer you can Right click the widget and do “Promote to..” then set the promoted class name to match your python class name, and set the header file to be the name of the python module to import it from. The only issue I’ve had with it is that you must have your promoted class in a different file from the one that the UI is loaded in (Because that module can’t import itself without causing a NameError)

  17. […] IMPORTANT: The PyQt libraries do not come w/ a standard Maya install so you’ll have to handle that part yourself. If you need help, Nathan Horne has a great post about how to do that: Guide to PyQt in Maya. […]

  18. […] IMPORTANT: The PyQt libraries do not come w/ a standard Maya install so you’ll have to handle that part yourself. If you need help, Nathan Horne has a great post about how to do that: Guide to PyQt in Maya. […]

  19. […] of Maya. Nathan Horne is one of the pioneers in this area and has shared a ton of useful info: Nathan’s Mega-post and some […]

You must be logged in to post a comment.