2011
06.11

Just posting a new project I’ve started working on: a Python/PyQt based Subsonic desktop client! Let me know if you’d like to contribute. I’m just making this in my spare time because I’m sick of keeping a web browser open, and I think PyQt and python are pretty neat ­čÖé

http://code.google.com/p/subsonic-client/

Feel free to grab a build and give it a try.  Note: This hooks into a subsonic server, you will need one that you have access to in order to try it out. You can check out subsonic here: http://www.subsonic.org

2011
04.11

Maya 2012 PyQt x64

Here’s a compiled x64 version of PyQt 4.8.3 (Compiled against Qt 4.7.1 which Maya 2012 uses) – Download

I don’t know if I am going to get around to building the x86 version, since I don’t actually use it. E. Ozgur Yilmaz from my links section has a great tutorial if you wanted to build it yourself however ­čÖé

2011
02.24

Woof Woof

2011
02.07

Maya Clip tool

I’ve been working on a new clip tool for the animators at work, here’s what it looks like so far:

Current features:

  • Full support for animation layers
  • One to One curve reconstruction, exactly what you save comes back (Tangents, broken tangents, free’d weights, etc..)
  • Fast import/export
    • Export of my test animation takes about 2 seconds for a 2,200 frame animation with 70 controls (1 key per control per frame)
      • *Not including time for the playblast to capture for the preview. There is an option to only store the first frame preview if the animator chooses.
    • Import of the same animation takes 3 to 4 seconds
  • Small file size: <7 megabytes for 2,200 frame test animation including the preview avi (Animation is ~2.3mb of that after compression)
    • Typical size for a 24-30 frame animation with hand keyframing (Not solid mocap keys) typically runs at 100-400kb per animation including preview avi
  • Preview window
    • loads animation previews on the fly – Can load ~4.5mb avi from the compressed clip in about .1-.5 seconds
    • Play, pause, scrub, and manually enter time to preview from avi
    • Completely internal to Maya. Doesn’t even use PyQt or any sort of plug-ins
  • Clip capture dialog with options, and a custom preview window that allows you to edit the render camera
  • Additional metadata stored inside the file, can hold any arbitrary data, and is easy to read and manipulate while leaving the clip data intact
    • By default stores information like: Who exported the clip, when was it exported, what controls were exported with/without namespaces, what time does it start and end, total frame count, total keyframe count, etc…
2010
11.08

Some PyQt for the day

Here’s a way to add popup tool tips to the 3d view ports in Maya (Based on inspiration from this post)

import sip
import maya.cmds as cmds
import maya.OpenMayaUI as apiUI
from PyQt4 import QtGui, QtCore

class ToolTipFilter(QtCore.QObject):
	'''A simple event filter to catch tooltip events'''
	def eventFilter(self, obj, event):
		if event.type() == QtCore.QEvent.ToolTip:
			QtGui.QToolTip.hideText() #Hide the old tooltip, so that it can move
			QtGui.QToolTip.showText(event.globalPos(), '%04f, %04f'%(event.globalX(), event.globalY()), obj)
			return False
		return True

#Install the event filter into all the model panels
global filter #Have to make the filter object global so it doesnt get garbage collected
filter = ToolTipFilter()

for editor in cmds.lsUI(panels=True): #For soem reason type='modelEditor' won't work...
	if cmds.objectTypeUI(editor)=='modelEditor':
		ptr = apiUI.MQtUtil.findControl(editor)
		viewWidget = sip.wrapinstance(long(ptr), QtCore.QObject)
		viewWidget.installEventFilter(filter)

Here’s some simple code to get the widget with focus, under the cursor, and the main Maya window:

import sip
import maya.cmds as cmds
import maya.OpenMayaUI as apiUI
from PyQt4 import QtGui, QtCore

def getMayaWindow():
	'Get the maya main window as a QMainWindow instance'
	ptr = apiUI.MQtUtil.mainWindow()
	return sip.wrapinstance(long(ptr), QtCore.QObject)
	
def getFocusWidget():
	'Get the currently focused widget'
	return QtGui.qApp.focusWidget()

def getWidgetAtMouse():
	'Get the widget under the mouse'
	currentPos = QtGui.QCursor().pos()
	widget = QtGui.qApp.widgetAt(currentPos)
	return widget

Here’s some handy debug code, this changes the tooltips for all the QWidgets in Maya to display their path, and QObject instance:

import sip
import maya.cmds as cmds
import maya.OpenMayaUI as apiUI
from PyQt4 import QtGui, QtCore

def toQtObject(mayaName):
	'''
    Given the name of a Maya UI element of any type, return the corresponding QWidget or QAction. 
    If the object does not exist, returns None
    '''
	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)

#Set the tooltip for all widgets (This takes a little bit)
for widget in cmds.lsUI(dumpWidgets=True):
	try:
		qWidget = toQtObject(widget)
		qWidget.setToolTip('(%s) %s'%(widget, qWidget))
	except:
		pass

Convert a wrapped pyqt object back to a Maya UI path:

apiUI.MQtUtil.fullName( sip.unwrapinstance(widget) )
2010
10.22

Modifying Maya menus

I’ve gotten sick of it being such a pain to work with Maya’s built in right click menu’s, so I’ve been playing today with finding a good way to modify them. I wanted to do this without copying over the existing scripts included with Maya. I also wanted to enable some features that Maya’s menus are lacking: Icons in menus, and Inserting items at positions other than the end.

Here’s my progress so far:

I am just doing a few simple steps to get to this point:

  1. Overwrite Maya’s default popup menu with my own, which calls a python function instead
  2. From there, call Maya’s built-in function to create the menu
  3. Then, grab the list of QActions that make up the menu
  4. Copy them into a new QMenu
  5. Delete all the items off of the original Maya menu, causing it not to show (This is before it ever displays in the UI)
  6. Insert items, add icons to existing ones, etc.. (Also hook up signals/slots if need be)
  7. Show my new QMenu at the cursor
  8. Once it’s all done, run action.deleteLater() on all the actions (Otherwise Maya complains about non unique names later)

The trickiest menus to work with are going to be the 3d viewport ones, because they seem to hook into some functions that are not meant to be used. I had┬ápreviously┬ácopied over the dagMenu script, and a few others to implement these tools, and I really don’t like doing it that way. I am thinking of using my selectFromScreen code to completely re-implement background and object right click menus. I am excited to see where all this goes, and what new tools I can come up with to take advantage of it.

Edit: Looks like Autodesk’s implementation of radial menu’s is not meant to be messed with, all my attempts so far to add or insert new widgets into a menu with radial menu set to True have failed miserably…

2010
10.15

Force processEvents for Maya UI

Sometimes when UI events are posted right before entering a long section of a script, the Maya UI event wont occur until AFTER the section of code is complete. This problem is most obvious with the progressBar command, and the MFeedbackLine when used from python.

Here’s how to force Maya to go ahead and process UI events before continuing on with your script:

With processEvents:

import time
import maya.OpenMayaUI as apiUI
import maya.cmds as cmds

from PyQt4 import QtGui

apiUI.MFeedbackLine.setTitle('Waiting for 5 seconds')

#Get the QApplication instance, and process events
QtGui.qApp.processEvents()
time.sleep(5)
apiUI.MFeedbackLine.clear()

Without processEvents:

import time
import maya.OpenMayaUI as apiUI
import maya.cmds as cmds

apiUI.MFeedbackLine.setTitle('Waiting for 5 seconds')
time.sleep(5)
apiUI.MFeedbackLine.clear()
2010
10.08

I was working on our pose library a bit today, and ran into some issues with generating icons for use with the pose file:

  • You can render an image, but then you are subject to render issues like bad lights and missing textures. You also have to worry about setting and restoring the users render settings
  • You can playblast a single frame to an image file, but then you have to deal with the pose changing if the rig has animation on it (Playblast evaluates the current frames animation first)
  • You can screengrab, but then you have to crop out the parts of Maya you don’t want to see, and have occlusion issues with other floating windows/offscreen

This code will grab the frame buffer from the active viewport (You could also change it to be a specified viewport with little work) and write it to any format that MImage supports:

#Import api modules
import maya.OpenMaya as api
import maya.OpenMayaUI as apiUI

#Grab the last active 3d viewport
view = apiUI.M3dView.active3dView()

#read the color buffer from the view, and save the MImage to disk
image = api.MImage()
view.readColorBuffer(image, True)
image.writeToFile('C:/test.jpg', 'jpg')

Something to note: Since the hud, and all tools/manipulators are also part of the openGL framebuffer, they also show up in the image. This could be good or bad depending on your needs. In my case I’m already hiding all that stuff before I render my pose anyways, so it’s not an issue.

Our old pose image code was doing render() and then writing the icon file using renderWindowEditor(). This was pretty unreliable since the renderWindowPanel tends to get renamed, or not exist at all. Also of note: sometimes the call to renderWindowEditor(writeImage=path) seems to be non-blocking, causing the image to be written after the script tries to work with it, and finds it missing.

2010
09.17

QComboBox mouse wheel, and P4Python x64

When working with QComboBox in a tree widget/view in QT I ran into an issue where the user scrolling up and down in the list would accidentally change the QComboBox value instead. Here’s a really simple inherited class that gets rid of the mouse wheel for QComboBox

class ComboBoxNoWheel(QtGui.QComboBox):
	def wheelEvent (self, event):
		event.ignore()

Perforce for python 2.6 doesn’t come pre-compiled for x64 for some strange reason. So here’s the compiled .pyd file, and it’s companion .py file.

If your studio uses perforce, integrating it into your DCC apps is a must! Your artists will thank you! ­čÖé

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