4 November 2011

Our First Window - 01 -Creating the Window

First thing we are going to want to do now that we have PyQt installed on our machine is to create a bare bones window. But before we do, it is important to talk about qt designer and what it can do for you if you choose to use it. Qt designer is an application made by Nokia that greatly speeds up the process of designing a UI.

It basically turns much of the process of making and positioning button into drag an drop operations, and while you can not do everything you may want using this program, it does give you a very big head start. Aside from time saved in the initial layout of the UI, Qt Designer also makes it very easy to edit the layout of your UI down the road I do this quite often at work, as the tools we use evolve and grow over time. That said, if you do go the route of QT Designer (which I usually do), do keep a couple of things in mind.

1) you can't do everything in Qt Desiner, there will be certain things you would like to do, that just require you to script them, and for some tasks it is probably simpler than try to do it in designer.

2) you will likely have an extra .ui file that needs to travel with your python file. Although you can avoid this by converting it into python code, and then added to your python script, you would then need to reconvert it each time you wanted to make a change (no matter how small) in Qt Desiner. In general I prefer to keep it as a .ui file.

you can download Qt Designer from:
it comes bundled with QtCreator


ok, lets get started. So the first thing we are going to want to do to is to open QT Designer, and create a MainWindow.
Then feel free to populate it with some buttons (drag and drop them from the widget box column on the left, to your UI window in the middle of the work area)
Once you have a few buttons, save it in your mayaScripts directory (C:\Users\****\Documents\maya\scripts) as testWindow.ui

Next, we will create a python file in that same folder. To do this, create a text file, and rename it to testWindow.py
Now lets open the .py file and paste the following inside:

#!/usr/bin/env python

import sys
import os
import sip

import maya.cmds as cmds
import maya.OpenMayaUI as OpenMayaUI

from PyQt4 import QtGui, QtCore, uic
import PyQt4

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

selfDirectory = os.path.dirname(__file__)    #returns the folder THIS file is in
uiFile= selfDirectory+'/testWindow.ui'    #only works if the .ui file is in the same folder

#Load the ui file, and create my class
form_class, base_class = uic.loadUiType(uiFile)
    #form_class will be <class 'Ui_MainWindow'>
    #base_class will be <class 'PyQt4.QtGui.QMainWindow'>

class BaseClass():
    def __init__(self):
        ptr = long(long(OpenMayaUI.MQtUtil.findWindow(newMayaWindow)))
        newQtWin = sip.wrapinstance(ptr, base_class)

class UI(base_class, form_class):

    title = 'My wicked window'

    def __init__(self, parent=getMayaWindow()):
        #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


def open():
    '''will attempt to close the window (in case there is one already open)
    and then open a new one'''

    global testWindow
    testWindow = UI()

 And once we have that, just paste the next chunk of code into your maya script editor:

import testWindow

...And run it. This should pop up the window you created in Qt Designer.
Next post, I will go over how to add some functionality to the window.

28 August 2011

Python in Maya - How to make commands repeatable and undoable

Two of the problems you may have noticed using python in Maya are:

1) that your function calls are not repeatable with the "G" hotkey the way they are with MEL commands and shelf buttons.

2) python functions are not by default undoable with a single undo. If you make 10 connections between 10 nodes, you will need to undo 10 times to get back to the state your scene was in before you ran the function.

One way you can get around this is to build a decorator function to wrap your regular functions and add that functionality to them. So that when you are done, it will look like:

def myAwesomeFunction(valueA='totalyWickedMode'):

The nice thing about the format of the code above is that it is easy to add or remove from any function, and we can edit the functionality of these two decorators in 1 place. So lets define our decorators starting with the easiest of the two, undoable.

def undoable(function):
    '''A decorator that will make commands undoable in maya'''

    def decoratorCode(*args, **kwargs):
        functionReturn = None
            functionReturn = function(*args, **kwargs)
            print sys.exc_info()[1]

            return functionReturn
    return decoratorCode

The above function will automaticly be called in place of the function its decorating (in this case myAwesomeFunction), passing as a variable the function being called (again, in this case it is myAwesomeFunction). Now the function inside the function 'undoable', can be named whatever you want, and it will be the function collecting our arguments and keyword arguments (if there are any). In this inner function is where we can openChunck in maya's undoInfo (which defines the start of our undo "chunk"). Then, we run the original function in a try statement, followed by a finally statement so that we can close the undo chunk regardless of whether or not our original command produces and error. Now if we run myAwesomeFunction() it should undo the whole function in a single undo.

Next is the somewhat tricky problem of making our functions repeatable. The reason this is harder is because we have to use a command "cmds.repeatLast()" that is undocumented. luckily if you do a help() on the mel version of the command, we can see the list of flags for this command. The two flags we will need are: "ac" and "acl". 
"ac" being the command (which even tho we are using the python version of repeat last, we will need to feed a MEL command to it".
"acl" is just the label of the command, in this case we will label it with the name of our command.

def repeatable(function):
    '''A decorator that will make commands repeatable in maya'''
    def decoratorCode(*args, **kwargs):
        functionReturn = None
        argString = ''
        if args:
            for each in args:
                argString += str(each)+', '
        if kwargs:
            for key, item in kwargs.iteritems():
                argString += str(key)+'='+str(item)+', '

        commandToRepeat = 'python("'+__name__+'.'+function.__name__+'('+argString+')")'
        functionReturn = function(*args, **kwargs)
            cmds.repeatLast(ac=commandToRepeat, acl=function.__name__)
        return functionReturn
    return decoratorCode

The main difference here is that we need to construct our command into a mel command, understanding that our python command could be named anything (because many different commands will be using this decorator) . When we are done, our command string should look like (in this case):


So the first thing we do is to turn our arguments and keyword arguments into a string in the same format as we would type to call it. Then we need to find the name of the module that contains this function (assuming we are not defining it in the script editor), the __name__ attribute in this case will give us that info, as it is inherited from the module. Now we just need the name of the function being decorated, again, we can use the __name__ attribute of the function (function.__name__) to get that info. Now we just stick them all together, and feed it as the mel command arg in cmds.repeatLast().

Hope this answers more questions than it creates for you,
Kris Andrews

21 August 2011

pyQT in Maya - getting started

Ok, so you probably have heard that maya switched its interface to QT in 2011, which opens up a world of possibility's for those of us who want to make UI's that can go above and beyond what MEL used to offer us.

So how do we get started?

Well if your like me, you probably are not too keen about in programming UIs in C++ to run commands we are scripting with python, so our first step is going to be to go and get 'pyQT'. pyQT is developed by Riverbank Computing (where as QT itself is developed by Nokia). PyQT allows us to write QT UI's with python.

What if I am writing code in MEL?

Well then your shit out of luck. perhaps this is the push you need to make the switch to python (having programmed in MEL for 3-4 years, I can tell you, once you learn python, you will never want to look at MEL again... ever)

Does it come with maya?

Nope. This is largely due to licensing issues.
see:    http://images.autodesk.com/adsk/files/pyqtmaya2011.pdf

So we get it from Riverbank's website? Not really. First thing is that it needs to be compiled. Furthermore from what I understand the version shipped with maya, does not line up with the way riverbanks versions (and this list of complications goes on)... but lucky for us, other TDs have done this leg work for us. For this you can thank Nathan Horne.

Now we download pre compiled file from his website:
maya 2011:   http://nathanhorne.com/?p=229
maya 2012:   http://nathanhorne.com/?p=322

and  install to C:\Program Files\Autodesk\Maya2011\Python    (or where ever your maya is installed to).

This should get you up and running (it did for me anyways, I am running on windows 7, with maya 2011). Stay tuned for our first simple window

Kris Andrews

20 August 2011

First Post

First Post! Ok, so having picked up loads of code, tricks, and general knowledge from other forums, blogs and websites, its only fair that I contribute back in some way. Since there is plenty of info on things like general python scripting, I will try to post some information that is more specific to 3d animation (specifically Maya, since that is what I am currently working in).