2010
06.25

Maya GUI excepthook

Autodesk passes all exceptions thrown in the command engine when in GUI mode to maya.utils._guiExceptHook, rather than doing it the way that Python does it natively through sys.excepthook. Here’s a simple workaround if you want to modify that behavior at run-time.

You can just overload the function of the imported module like so:

import sys
import maya.cmds as cmds

utils = sys.modules['maya.utils']
def excepthook(tb_type, exc_object, tb, detail=2):
	print '='*40
	print utils.formatGuiException(tb_type, exc_object, tb, detail)
	cmds.ScriptEditor()
	print '='*40
	
	import pdb
	pdb.post_mortem(tb)
	return utils.formatGuiException(tb_type, exc_object, tb, detail)

utils._guiExceptHook = excepthook

Or, if you want to get the normal behavior back, you can do this:

import sys
import maya.cmds as cmds

utils = sys.modules['maya.utils']
def excepthook(tb_type, exc_object, tb, detail=2):
	if sys.excepthook != sys.__excepthook__:
		sys.excepthook(tb_type, exc_object, tb)
	return utils.formatGuiException(tb_type, exc_object, tb, detail)

utils._guiExceptHook = excepthook

Now, when an exception is fired, it will first run your custom excepthook (if modified, if not it will just skip it).

Note: your function must return a string, and that string will be passed by the Maya command engine to MGlobal.displayError()

2010
06.14

Maya python selectFromScreen

Something I whipped up to get the selection from the screen using the Maya API in python, just returns the selection as a list of dag path’s.

Maya’s API selecFromScreen is great, except for the fact that for what I needed, I only wanted a list of object from the screen, not to actually select them.

import maya.OpenMaya as api

def selectFromScreenApi(x, y, x_rect=None, y_rect=None):
	#get current selection
	sel = api.MSelectionList()
	api.MGlobal.getActiveSelectionList(sel)
	
	#select from screen
	args = [x, y]
	if x_rect!=None and y_rect!=None:
		api.MGlobal.selectFromScreen(x, y, x_rect, y_rect, api.MGlobal.kReplaceList)
	else:
		api.MGlobal.selectFromScreen(x, y, api.MGlobal.kReplaceList)
	objects = api.MSelectionList()
	api.MGlobal.getActiveSelectionList(objects)
	
	#restore selection
	api.MGlobal.setActiveSelectionList(sel, api.MGlobal.kReplaceList)
	
	#return the objects as strings
	fromScreen = []
	objects.getSelectionStrings(fromScreen)
	return fromScreen

print selectFromScreenApi(0, 0)
print selectFromScreenApi(0, 0, 4096, 4096)
2010
06.13

Compiled against Qt 4.5.3 (2009.04) and python 2.6.4 (Both the same as Maya 2011):

PyQt-Py2.6-gpl-4.7.3-1_QT_MAYA_x86.exe

sip.pyd

Edit: A few people were unable to get this working, not sure why but here’s an alternate version without the installer that seems to fix any issues with Qt failing to import

PyQt 4.7.3 Qt x86 Maya.zip

2010
05.27

Compiled Versions of PyQt4 x64

After getting fed up with using someone else’s out of date versions, I decided to compile my own Qt, SIP, and PyQt for x64. I managed to get it all working and figured I could share it up, so here’s a link to the installers, as well as the latest version of sip.pyd which you will need.

PyQt-Py2.6-gpl-4.7.3-1_QT_MAYA.exe
PyQt-Py2.6-gpl-4.7.3-1.exe
sip.pyd

Both are the latest PyQt4 4.7.3-1, however they are compiled against different versions of QT.
PyQt-Py2.6-gpl-4.7.3-1_QT_MAYA.exe is compiled against Qt 4.5.3 (2009.04) which Maya 2011 uses, the other is compiled against the latest Qt 4.6.2 (2010.02.1).

Also, please note that I did not include any components that do not come standard with the Qt SDK such as QScintilla.

 

Edit: A few people were unable to get this working, not sure why but here’s an alternate version without the installer that seems to fix any issues with Qt failing to import

PyQt 4.7.3 Qt x64 Maya.zip

2010
05.05

Python Rivet script

I’ve had to use this at work recently, andIi decided to revamp an old mel script I had been using before.  I figured I would share it up for others to use.

Right click, save-as.

Public SVN repository web view

2010
04.21

PyQt and Maya 2011

Re-posting some information here that I had posted on TD club:

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

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

class MayaSubWindow(QtGui.QMainWindow):
    'My custom window, which i want to parent to the maya main window'
    def __init__(self, parent=getMayaWindow()):
        #Init my main window, and pass in the maya main window as it's parent
        QtGui.QMainWindow.__init__(self, parent)

#Show my window
myWindow = MayaSubWindow()
myWindow.show()

Here’s a quick little bit of code showing how to create a custom GUI class using pyqt (Almost all the pyqt examples use this over using some form of .ui file, because it allows for much more control). You can also use it in combination with ui files thanks to pyqt’s uic module.

QT does not require objects to have “names”, but if you ever want to find your pyqt objects using MQtUil.findControl then you need to assign it a name using OBJECT.setObjectName(“AwesomeWindow”). In the above example, in the MayaSubWindow.__init__ function you would call self.setObjectName(“AwesomeWindow”).

Also, since qt does not require names, it also does not require that control names be unique, so it’s completely possible to use findControl and get the “wrong” object. So you may want to ensure that the name you are using is unique if you want to use it to find that object again later. (Also, you can skip the whole findControl step by just using instance variables to keep track of all your controls, like what the pyqt examples use)

Here’s a pyqt example using uic to load in a ui file (Rather than maya’s new loadUi command, which wont give you all your pyqt objects.

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()

Designer .UI file

The python code will look for the ui file in maya2011\prefs\ui\demo.ui. It will print a message with the absolute path when running.
2009
08.18

Timer threads

Timers have all sorts of uses, and unfortunately up until 8.5 Maya had no good way of checking a timer, and running a background event.

Previously you could check system time at certain events with a scriptJob, like selectionChanged, but it tends to slow Maya down right when the user was trying to interact.. so in a production environment that wasn’t a viable solution (usually scriptJobs aren’t, as nifty as they are).

Now, with the flexibility of python in Maya, and python’s ability to create threads, it’s quite simple to create a basic Timer class.

Note: I updated it to use threading.Event().wait() instead of a time.sleep() loop, and now I directly subclass threading.Thread.

'''
Usage:
def timerTest():
	print 'Hello World!'

#create and start a timer
timer = Timer(30, timerTest, repeat=True)
timer.start()

#To stop the timer
timer.stop()
'''

import threading

try:
	from maya.utils import executeInMainThreadWithResult
except:
	executeInMainThreadWithResult = None

class Timer(threading.Thread):
	def __init__(self, interval, function, args=[], kwargs={}, repeat=True):
		self.interval = interval
		self.function = function
		self.repeat = repeat
		self.args = args
		self.kwargs = kwargs
		self.event = threading.Event()
		threading.Thread.__init__(self)

	def run(self):
		def _mainLoop():
			self.event.wait(self.interval)
			if not self.event.isSet():
				if executeInMainThreadWithResult:
					executeInMainThreadWithResult(self.function, *self.args, **self.kwargs)
				else:
					self.function(*self.args, **self.kwargs)

		if self.repeat:
			while not self.event.isSet():
				_mainLoop()
		else:
			_mainLoop()
			self.stop()

	def start(self):
		self.event.clear()
		threading.Thread.start(self)

	def stop(self):
		self.event.set()
		threading.Thread.__init__(self)
”’
Usage:
def timerTest():
print ‘Hello World!’
#create and start a timer
timer = Timer(30, timerTest, repeat=True)
timer.start()
#To stop the timer
timer.stop()
”’
import threading
try:
from maya.utils import executeInMainThreadWithResult
except:
executeInMainThreadWithResult = None
class Timer(threading.Thread):
def __init__(self, interval, function, args=[], kwargs={}, repeat=True):
self.interval = interval
self.function = function
self.repeat = repeat
self.args = args
self.kwargs = kwargs
self.event = threading.Event()
threading.Thread.__init__(self)
def run(self):
def _mainLoop():
self.event.wait(self.interval)
if not self.event.isSet():
if executeInMainThreadWithResult:
executeInMainThreadWithResult(self.function, *self.args, **self.kwargs)
else:
self.function(*self.args, **self.kwargs)
if self.repeat:
while not self.event.isSet():
_mainLoop()
else:
_mainLoop()
self.stop()
def start(self):
self.event.clear()
threading.Thread.start(self)
def stop(self):
self.event.set()
threading.Thread.__init__(self)
2009
08.15

SQL logging from Maya

I was inspired by a post on tech-artists by Adam Pletcher over at Volition, he talked about using SQL to track usage stats for tools, and startup time for 3dsmax.

Getting the data:

I started with a simple database,  just tracking the tool name, the username, and the date/time. But there is room to expand that information later (I want to track arguments, and time taken per tool).

There’s a problem right away: accessing a remote database on every function call (that I want to track) is SLOW, to work around this I dump the data to a local temp file (I used csv), and use a timer in a separate thread to upload every N seconds (on my machine I do every 10 seconds, for the artist’s I’m still tweaking, but somewhere around 2-5 minutes seems to work well). The actual database access usually takes .1 to .5 of a second, so it’s not terrible. I also have it set to run with utils.executeDeferred(), so it goes unnoticed for me, even on my 10 second interval.

Sooo… I have a bunch of cool data in a table, now what?

CHARTS! 🙂

It even has alpha! :)

It even has alpha! 🙂

I used pycha and pycairo to create charts from the database, had to track down some missing dll’s to get it working, but not too bad. So far the most useful chart is function usage by user (given a  function, % that each person uses it), it helps me to spot tools that aren’t being used by certain users, which will allow me to train them on the new tool. This is good because if they never use it, they probably don’t know it exists.. 🙂

Where to go from here:

Break functions down by how long they take to execute, it should show me where most of our processor time is going as far as tools are concerned, so once I turn on function timing, that’s an immediate goal.

2009
08.11

A cool feature of Maya’s python is that it runs as an interactive session, and that lets you do this neat trick from any imported module

you can add objects to the __main__ namespace (The script editor’s interactive prompt), this is also where python will search when you use the MEL command python();

from some python file:

#import the interactive main module
import __main__

def someCommand():
'''This is a pretty basic function...'''
print 'someCommand called!'

#now assign it to an attribute in __main__
__main__.someCommand = someCommand
python("someCommand()");
// someCommand called!

This can also be used as a way to create global variables across multiple modules, as it is always present so long as Maya is open (although any attributes you define may not be, so check first :))