Last week, iker j. de los mozos posted a Qt tutorial on his blog. The post was retweeted a number of times, so I figure people liked it.
The post exemplifies what is wrong with the Qt Designer, and also how a little more investment in learning can pay dividends for your career.
I know it’s unfair to give people code reviews on things they just put out for free, but I consider it even worse to allow people to continue to use the Qt Designer with a clear conscience. I thank Ike for his post, and for syndicating his feed on Planet Tech Art, and hope that no one takes my writeup below personally. It’s not an attack on a person, it’s trying to point out there there is a much better way to do things.
There are 117 lines of Python code in Ike’s tool for locking and unlocking transformation attributes. This sounds like a small amount, but is for an experienced Pythonista it indicates huge waste. For comparison, the entire ZMQ-based request/reply client and server I built for Practical Maya Programming with Python is the same size or smaller (depending on the version). If we take a closer look at his code (see link above), we can see a ton of copy and pasted functionality. This is technically a separate concern from the use of the Designer, but in my experience the two go hand-in-hand. The duplication inherent in GUI tools carries over to the way you program.
Let’s look at some refactored code where we build the GUI in code (warning, I haven’t tried this code since I don’t have Maya on this machine):
from functools import partial from PySide import QtGui import pymel.core as pmc import qthelpers OPTIONS = [ dict(label='Position', btn='POS', attr='translate'), dict(label='Rotation', btn='ROT', attr='rotate'), dict(label='Scale', btn='SCALE', attr='scale') ] def setLock(obj, attr, value): obj.setAttr(attr, lock=value, keyable=not value) def isAttrLocked(obj, attr): return obj.getAttr(attr, q=True, lock=True) def toggleRowCallback(attr): obj = pmc.ls() value = isAttrLocked(obj, attr + 'X') for axis in 'XYZ': setLock(obj, attr + axis, value) def toggleCellCallback(attr, state): obj = pmc.ls() setLock(obj, attr, state) def makeRow(options): return qthelpers.row( [QtGui.QLabel(options['label'])] + map(lambda axis: qhelpers.checkbox(onCheck=partial(toggleCellCallback, options['attr'] + axis)), 'XYZ') + qhelpers.button(label='lock ' + options['btn'], onClick=partial(toggleRowCallback, options['attr'])) ) def main(): window = qthelpers.table(map(makeRow, OPTIONS), title='lockAndHide UI', base=QtGui.QMainWindow) window.show()
Why is this code better? Well, for starters, it’s less than a third of the size (37 lines) and there’s less duplication. These are very good things. When we want to change behavior- such as auto-updating the checkboxes when our selection changes- we can put it in one place, not nine or more.
So the code is better, but what other benefits are there to not using the Designer?
– We pull common primitives, like a “row” (QWidget with HBoxLayout) and “table” into a
qthelpers module, so we can use this across all GUIs. This saves huge amounts of boilerplate over the long run, especially since we can customize what parameters we pass to it (like
onClick being a callback).
– The GUI is clear from the code because the UI is built declaratively. I do not even need to load the UI into the Designer or run the code to understand it. I can just read the bottom few lines of the file and know what this looks like.
– You learn new things. We use
functools.partial for currying, instead of explicit callbacks. This is more complicated to someone that only knows simple tools, but becomes an indispensable tool as you get more advanced. We are not programming in Turtle Graphics. We are using the wonderful language of Python. We should take advantage of that.
Again, I thank Ike for his blog post, and hope I haven’t upset anyone. Ike’s code is pretty consistent with the type of code I’ve seen from Technical Artists. It’s time to do better. Start by ditching the Designer and focusing on clean code.