Pity for the outcast

Posted on Sun 16 February 2014 in blog

I don’t think it’s too far over the top to say that everybody hates Maya’s internal GUI system. It combines a very 1990’s selection of widgets with a very 1970’s coding style: it’s a 1970’s/1990’s Frankenstein monster as horrifying as Ashton Kutcher in That 70’s Show.

Not surprisingly a lot of TA’s feel like they have to turn to PyQT if they want to deliver polished tools with a modern UI. Unfortunately this is also less than ideal - while PyQT is a very powerful framework, it’s got a very distinctive idiom of its own to learn, and moreover its hard to distribute since it depends on C++ dlls. If you want to share a tool across studios or with outsourcers on a variety of boxes, OS’es and Maya versions it can become a Dantesque journey into DLL hell.

Because we do a lot of work with outsourcers, I’ve been looking into ways to render native Maya GUI a little less irritating. A quick review of my own pain-points in Maya GUI development showed me three main problems with the existing system

Icky syntax

Default Maya GUI is purely imperative; while Maya creates an in-memory object for every widget you create, you can only interact through it with via commands: In the typical idiom you create a button:

cmds.button("big button");

but your can only interact with it by calling more commands:

mybutton = cmds.button("big button")  
cmds.button(mybutton, e=True, w=128)  
cmds.button(mybutton, e=True, label = 'Red button'))  
cmds.button(mybutton, e=True, bgc = (1,.5, .5))

This gets old pretty fast. It’s particularly bad for GUI components like formLayout, which can easily require whopping big arguments like this:

cmds.formLayout( form, edit=True, attachForm=[(b1, 'top', 5), (b1, 'left', 5), (b2, 'left', 5), (b2, 'bottom', 5), (b2, 'right', 5), (column, 'top', 5), (column, 'right', 5) ], attachControl=[(b1, 'bottom', 5, b2), (column, 'bottom', 5, b2)], attachPosition=[(b1, 'right', 5, 75), (column, 'left', 0, 75)], attachNone=(b2, 'top') )

Which is, frankly, stupid.

Lousy event handling

Another big knock on Maya’s native gui is it’s lousy callback system. The python version is bolted on to the original, string based MEL version and confuses a lot of people with uncertain scoping and unclear syntax (Check out these threads on Tech Artists.Org and StackOverflow for some examples of how people get lost) .

Even when default Maya GUI callbacks do fire, they don’t usually indicated who fired them off. This means you need to capture any relevant information ahead of time and encode it into the callback. In lots of GUI systems, you could handle a raft of similar functions like this pseudocode:

button_codes = ['red', 'green', 'blue']  
for code in button_codes:  
    button(code, tag = code, handler = apply_color)

def apply_color(button):  
   target.color = constants[button.tag]

In Maya, on the other hand, you need to compose the callbacks with the right context when you create them using a [functools.partial](http://docs.python.org/2/library/functools.html) or a closure. This makes coding up anything like dynamic model-view-controller UI into a real chore, and forces you to interleave your layout architecture and your data model in clumsy ways.

Moreover, Maya GUI callbacks are single-function calls. Its common in other frameworks — for example in C# — to have multicast delegates which can trigger multiple actions from a single callback. This makes for cleaner, more general code since you can split UI-only functionality (‘highlight this button’) from important stuff (‘delete this model’).

No Hierarchy

It might not be strictly fair to say that Maya GUI has ‘no hierarchy’; anybody who has ever mucked around with cmds.setParent or cmds.lsUI knows that the actual widgets are composed in a strict hierarchical tree; hence UI objects with names like
window1|formLayout57|formLayout58|frameLayout38|columnLayout5|button5
The problem is that Maya doesn’t manage this for you - you are responsible for capturing the pathname of every UI widget you create if you ever want to access it again, which imposes a useless maintenance tax on otherwise simple code.

Moreover, the names aren’t determninistic, thanks to Maya’s rule against identical paths: that means you may want a button called ‘Button’, but it may decide to call itself ‘Button1’ or ‘Button99’ and there’s nothing you can do about it. As if that weren’t bad enough, the strongly imperative style of the Maya GUI code also requires manual management. If you declare a layout and start filling it up with widgets, you’re also responsible deciding when a particular container is full. A missed cmds.setParent can easily screw up your visual layout or the ordering of a menus and it’s possible to shoot yourself in the foot without any corresponding gain in power, productivity or even prettiness. This limitation is why Maya has to offer all those redundant command sets for multiple-column rows and grids. When a monstrosity like cmds.rowLayout(nc=5, cw5=(100,100,50,50,80), ct5 =("left", "left", "both","both","right")) is a win for code cleanliness and legibility you know something has gone horribly wrong.

So What?

All of that amounts to a long-winded way of restating the obvious: nobody likes coding in standard Maya GUI. The question is, can something be done about it?

Actually, quite a bit.. Next time out I’ll talk about some practical ways to make Maya GUI coding… well, let’s not say “fun”, lets run with another 90’s retread:

For ‘Windows 95’ substitute ‘Maya GUI’ and you’ve got the idea (For the origins of this authentic 2400-baud era meme, click here.)