We've Moved

The blog has been retired - it's up for legacy reasons, but these days I'm blogging at blog.theodox.com. All of the content from this site has been replicated there, and that's where all of the new content will be posted. The new feed is here

Saturday, March 26, 2016

mGui updates in the offing...

mGui updates in the offing... Changes on the way for mGui, the maya Gui framework

For those of you who’ve been using mGui to speed up and simplify your Maya gui coding, there are some interesting changes on the horizon. Although I’m not entirely ready to release the changes I have in mind they are mostly sitting in their own branch in the Github repo.

The upcoming version introduces some new idioms - in particular, it gets rid of the need for explicitly setting keys on new controls to get access to nested properties. In the first version of mGui you’d write something like this:

    with gui.Window('window', title = 'fred') as example_window:
        with VerticalForm('main') as main:
            Text(None, label = "Items without vertex colors")
            lists.VerticalList('lister' ).Collection < bind() < bound  
            with HorizontalStretchForm('buttons'):
                Button('refresh', l='Refresh')
                Button('close', l='Close')

With the new refactor that looks like this:

    with gui.Window('window', title = 'fred') as example_window:
        with VerticalForm() as main:
            Text(label = "Items without vertex colors")
            lister = lists.VerticalList()
            lister.collection < bind() < bound  
            with HorizontalStretchForm() as button_row:
                refresh = Button( label='Refresh')
                close = Button(label='Close')

The big advantage here is that those local variables are not scoped exclusively to the layout context managers where they live, which makes it easy to control when and where you hook up your event handlers: In the above example you could defer all the bindings and event handlers to the end of the script like this:

    with gui.Window('window', title = 'fred') as example_window:
        with VerticalForm() as main:
            Text(label = "Items without vertex colors")
            lister = lists.VerticalList()
            with HorizontalStretchForm() as button_row:
                refresh = Button( label='Refresh')
                close = Button(label='Close')

    lister.collection < bind() < bound
    refresh.command += refresh_def
    close.command += close_def

So far I’m really liking the new idiom, particularly eliminating the extra quotes and redundant None keys. However this is a minorly breaking change: in some cases, old code which relied on the key value to name and also label a control at the same time will when the keys become redundant. Moreover I bit the bullet and started to refactor the entire mGui module to use correct pep-8 naming conventions – in particular, member variables are no longer capitalized. So if you have code outside of mGui this will introduce some issues. When I converted my own code, most of the changes could be done with a regular expression but there were a few danglers.

I think the changes are worth the effort, but I’d be really interested in hearing from users before trying to bring the new mGui branch back into the main line. It should actually be possible to write a script that fixes most existing code automatically, that’s something we could refine collaboratively.Please let me know in the comments or by opening an issue on the GitHub site if you have comments or plans. As always, bug fixes and pull requests always entertained!

Sunday, March 20, 2016

Another Year, Another GDC...

Another year, another GDC GDC 2016 roundup It doesn’t take much super-secret industry-insider special knowledge to know that the big story this year was VR, VR and more VR – if the you had somehow managed to miss out on the pre-show hype the giant black Oculus booth looming like the Death Star right over the entrance to the expo floor told pretty much the whole story in one glance.

It is, however, interesting to note how cynical we’ve become as an industry about tech bubbles: from MMOs to Facebook games to In-app purchases and 3-D TVs, we’ve all lived through so many Next Big Things that we habitually reserve judgement – even on things like the latest crop of VR gear which sets our little nerdly hearts a-fluttering. I had a lot of conversations with people on the general theme of “wow, that’s cool. In three years most of them will be out of business, though.” – even with people in the VR business itself.
Personally, I think VR is going to survive but I don’t think it’ll be the kind of world-changing, ubiquitous tech that the broadband internet and handhelds have turned out to be. It’s an awesome geek toy and the gateway to many interesting and novel experiences. I see it sort of like that kayak in the garage: a cool thing that gets dusted off a few times a year but not a regular part of daily life, and never going to buffed up to the high consumerist gloss of an iPhone. Maybe in another decade, but for now it’s a really cool niche product. I hope all that crazy money sloshing around in VR land fuels some general purpose innovation : in particular, I hope that VR’s need for screaming framerates in two renders at once may make hardware that is more performant overall and also for split-screen friendly deferred rendering.
In the actual, as opposed to the virtual, world the highlight of the show for me is always the TA roundtables. It’s such a great resource for the community, and a chance for folks who often live a bit outside the main stream of their development teams to get together with people who share their unique and esoteric pain. Those three roundtables are the only room full of people who will chuckle at jokes about MaxScript and who truly appreciate the hopeless pathos of sending emails about proper check-in procedures to a team of artists. As always, hats off to +Jeff Hanna  for running the roundtables and also the TA Bootcamp, which I had to miss this year but sounded really good. I’m anxiously awaiting the release of the talks I missed on the GDC Vault.
In these annual roundups I usually try to note the size and intensity of the job market. This year was kind of difficult to gauge. The big mega-booths from mega-teams were largely absent this year: I didn’t see many of the long lines of students queuing up to show their portfolios to Blizzard, Bungie, or 343. On the other hand the general commercial tempo seemed pretty up beat. I think this reflects the general trend away from big, highly institutionalized teams and toward smaller, more agile (but also more fragile) groupings: the job market is more fragmented but not necessarily smaller than in years gone by. I could be wrong about that one, though – this might just be a mid-console-cycle lull.
To all the folks I saw down at the show – it was awesome! To those who didn’t make it this year – don’t miss it next time!

PS - one important thing that came out of the round table is that more and more people are signing up to the Tech artists slack channel.  If you're not already a member, follow that link to sign up.

Sunday, March 6, 2016

A touch of minq

A touch of minq If you’re a long-time reader, you may recall that i’m very ambivalent about wrapper code. I’m just as prone to adding my own little spoonful of syntax sugar on top of my daily tasks, but I’ve also been around long enough to be a bit cynical about my own various faddisms and dubious style choices over the years. Sure, extra typing is annoying – but nowadays I tend to set a pretty high bar for actually writing wrapper code instead of just, ya know, doing my actual job.
So, it’s with a little bit of trepidation that I’m sharing my latest library. Minq bills itself as ‘a query language for Maya scenes.’ The goal is to simplify a very common task for Maya coders: finding things in a scene.
Now, that isn’t a particularly interesting job most of the time, but it’s one we do a lot: a quick grep of my own codebase shows over 600 calls to cmds.ls(), cmds.listRelatives(), cmds.listHistory and cmds.nodeType() in various combinations: as far as I can tell, ls() is actually the single most common call I make.
Moreover, I’m reasonably certain (though I didn’t do the grepping to bear this out) that those hundreds of ls() calls are accompanied by hundreds of little snippets of code to deal with Maya’s quirks. How often have you run into little gems like this?
    stuff = ['top', 'something_thats_not_transform']
    print cmds.ls(*stuff, type='transform')
    # [u'top']

    stuff = []
    print cmds.ls(*stuff, type='transform')
    # [u'front', u'persp', u'side', u'top']
or this?
    for item in cmds.ls(my_meshes, type='mesh'):
        print item
    # Error: 'NoneType' object is not iterable
    # Traceback (most recent call last):
    #   File "<maya console>", line 1, in <module>
    # TypeError: 'NoneType' object is not iterable # 
There are of course ways around these little gotchas - but given the number of times you have to interact with them it’s hard to be sure you’ve really nailed them all. In my case a 99% correct handlong of my ls() calls alone will produce at least 5 bugs.
More importantly – and, frankly, the whole reason for this project – dealing with these little gotchas is not an interesting job. Finding, filter and sorting stuff in your Maya scene is not am opportunity for you to display your brilliant algorithms or clever strategies for bending Maya to your will: it’s just a bunch of stuff you have to on your way to fixing the problems your users really want fixed.

Minq in action

Hence, minq.
The goal of minq is to provide a more concise and more readable way to find things in your maya scenes. Here’s an example to give you the idea of how the project is supposed to work.
Suppose you need to find all of your character skeletons and distinguish them from other things lying around in the scene. The easy way to do that is usually to look for assemblies (top level nodes) which have children who drive skinClusters. Here’s an example of how you could find all the root nodes in the scene which drive skins using conventional means:
def find_assemblies_that_drive_skins():
    skinned = []
    for asm in cmds.ls(assemblies=True) or []:
        children = cmds.listRelatives(asm, ad=True) or []
        history = cmds.listHistory(children, future=True)
        if history and cmds.ls(history, type='skinCluster'):
    return skinned
You’ll notice the littering of or [] to make sure we don’t get errors for failed queries. We have to create two temporary variables (childen and history) in order to store the intermediate results. And, obviously, we’re 3 layers deep when we get to the actual work item.
Above all, though, you need to remember two little bits of Maya trivia to make sense of this code: that cmds.ls(asm=True) means ‘give me the assemblies’ and that listRelatives(ad=True) gives you the children of an object. These are, of course, very clear to Maya vets – but there are over 50 flags in ls() and more than a dozen in listRelatives() . I’ve been working in Maya for 20 years and I still need to look up most of them. You pass those flags to Maya as strings which won’t get evaluated until runtime – and it’s possible to mistype them and not even know because ls(), in particular, makes wierd tweaky decisions about how to interpret conflicting flags.
Here’s the minq equivalent to the previous function:
def drives_skin(some_object):
    children = using(some_object).get(AllChildren)
    skin_clusters = children.get(Future).only(SkinClusters)
    return any(skin_clusters)

unskinned_assemblies = Assemblies().where(drives_skin)
It’s shorter, but the real goal is to make it more readable. Here’s what happens, which should be pretty clear from the names:
1. drives_skin() takes a maya object
2. It gets all of that object’s children
3. It gets all of the future history of those children
4. It it filters down to only the skin clusters in that future history
5. it returns true if any skin clusters are present
The rest of it pretty self evident: unskinned_assemblies just collects all of the assemblies which pass drives_skin(). The algorithm is exactly the same as the first version – but, at least to me, that algorithm is actually expressed much more clearly in the minq version. As for concision, I deliberately broke the query into two lines to make it easier to read -- otherwise it could all be done in a single expression.
A purist will probably point out that there are important under-the-hood details in the first one that are hidden in the second, and s/he’d be right. However after doing a lot of this kind of code down the years I’m fairly certain that those important details have almost always been important because screwing them up causes problems – not because they provide an opportunity for a wizardly optimization or better approach to the problem. I’m interested in finding unskinned meshes, not in remembering to pass the correct flags to ls and listRelatives.
Here’s a couple of other examples to give you the flavor of what a minq query looks like:
# get all mesh transforms in a scene
mesh_transforms =  Meshes().get(Parents)

# find stub joints
def is_stub(obj):
    return not any (using(obj).get(Children).only(Transforms))
stubs = Joints().where(is_stub)

# filtering by type, by name, and with functions
cube_creator_nodes = PolyCreators().only('polyCube')
used_to_be_cubes = cube_creator_nodes.get(Future).only(Meshes)
has_8_verts = lambda p: cmds.polyEvaluate(p, v=True) == 8
still_are_cubes = used_to_be_cubes.where(has_8_verts)

# adding, subtracting or intersecting queries

too_high = Transforms().where(item.ty > 100)
too_low = Transforms().where(item.ty < -100)

middle_xforms = Transforms() - (too_high + too_low)
So, that’s the basic idea: to replace a lot of tedious boilerplate with something a little cleaner, more predictable and easier to maintain. The code for the whole thing is up on up on Github under the usual MIT, ‘use it as you like but keep the copyright header’ license. It’s still very much a work-in-progress and I’d love feedback, particularly on issues of syntax and style.

Saturday, March 5, 2016

Technical Art for Art Directors

I noticed that my 2015 GDC talk, Technical Art For Art Directors,  is currently up on the GDC Vault and is not behind the paywall.  If you've been struggling to explain what you do, you might find this one useful: