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 . I'm experimenting with crossposting from the live site, but if you want to keep up to date use blog.theodox.com or just theodox.com
Showing posts with label techart. Show all posts
Showing posts with label techart. Show all posts

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.

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:


Saturday, February 6, 2016

The Memory Hole

We tend to be the kind of people who throw themselves into things – we live for the joy of problem solving. So when we’re really grappling with the intricacies of todays disaster, we immerse ourselves in it. We tear it apart and inspect all the little moving pieces till we understand it well enough to duct-tape it back together again.

Along the way, that attention to detail and mastery of nuance tends to make us think we know it all. But – a shock, I know – we don’t. More to the point, we might now it all for the moment. But we’ll dump that knowledge to make sure we have room for our encyclopedic knowledge of tomorrow’s problem. And next week’s. And next months.
In short, we’re constantly flushing our caches. Unless you’re stuck in a rut, doing the same thing every day, you’re constantly learning new little things for your current problem and silently shelving the knowledge you aquired for your last.
This is one reason why good code comments are so important. Sure, comments rot just like code. But a couple of well-placed notes about how and why the code looks the way it does can save future you a lot of time when many layers of memory recycling have left you completeley oblivious about what the hell past you was up to. I can easily think of a couple of embarrassing occasions where I’ve literally chased my own tail – done something non-obvious because of a wierd maya bug, then come back six month later to ‘clean up’ my ‘ugly code’ and of course hit the exact same bug again.
Of course, good comments don’t have to have high literary quality, they don’t need to cover every variable and for loop, and they certainly don’t need to be overwhelming: what they should be is notes to future-self that will help him or her revive all the fading memories which seem so obvious today but which will be utterly erased before the next season of Silicon Valley is released.
Which brings me, by a very roundabout route, to what I actually set out to talk about: a perfect case in point. I was noodling around with a system that needed to fire events for maya attribute changes: basically, a way to make attributeChanged scriptJobs that were easy to start, stop and restart. So I did a little googling and…
Yep. I’d already written it. I’d even put it up on Github.
In my defense, I realized in retrospect that I had cancelled the project at work that made it necessary the first time: I did the work on the system, got it ready to go, and then decided that there was a simpler way to solve the problem without all those attribute-change scripts anyway. Nonetheless it’s a perfect illustration of how thoroughly one’s short-term memory cache gets flushed – and of the importance of leaving good comments. At least when I found the damn thing the readme that Github makes you put up reminded me how it was supposed to work (as an aside, it’s a great reason for putting your stuff up on GitHub or similar forums: knowing that other people will be looking at it forces you to clean up and document more than you would if you just decided to shelve a project).
So there you have it: an object lesson in the importance of clarity in tools development and a free module for messing around with AttributeChange scriptJobs!

Wednesday, September 16, 2015

I don't endorse this...

.. But I could not resist. The original title was Automation comes from the Latin word meaning 'self', and 'mating', which means 'screwing'


Sunday, July 12, 2015

Wraptastic!


The wrap up

The beauty of working with code, even really simple code, is that you can build your own little universe out of bits and pieces contributed by thousands of other people – all without paying a dime or even asking them for help. From sharing a script off of CreativeCrash to downloading a huge open-source behemoth like Apache, any reasonably plucky individual can today make stuff that actually involves the work of thousands of anonymous others. It’s really quite a remarkable evolution in human history that so many people voluntarily give away their work for nothing, and (whatever else you can say about the internet era) it’s something to be proud of participatng in.
On the other hand…
Well, Say you are an Amish farmer and all your neighbors showed up to help you raise your barn, you’d certainly be grateful. But you might still be pretty annoyed if Hans from next door hung your barn doors so they stuck in the summer heat. Maybe old Hans worries more about keeping the barn warm than you do, so he prefers a tight seal: but that’s small comfort when you’re heaving on that handle in a muggy Pennsylvania morning.
barn raising

The internet abounds in excellent – and, amazingly, free – tools to help make your life easier. But they all started life as tools to make somebody else’s life easier. If your needs don’t line up perfectly with the needs of the original author, you’re likely to get a little gereizt.

The fact is that nobody writes all their own stuff: we all use other people’s code all the time (and, as sharing becomes more and more ingrained in coding, that’s only going to increase). All that sharing means that we constantly have to work with libraries and APIs that are useful and free and for which we know we should be grateful… but – like that sticky barn door – they drive us absolutely bonkers.

Wrap up

Not surprisingly, almost everybody ends up writing wrappers: code to help ease those nice-but-imperferct tools and API into a something that feels a little more natural. If you spend a lot of time on TAO or coding forums where people swap tips and advertise their wares you’ll see a huge variety of wrappers for all sorts of tasks: indeed, the wrappers often seem to outnumber the actual functional bits. Whether you call the job making things ‘more pythonic’ or ‘more functional’ or ‘cleaner’, its something we all feel compelled to do (and to share) from time to time.
It’s also easy to get cynical about wrappers. You see so many – and so many of them just taste-driven syntactic variations on each other – that veteran coders often reflexively shrug and ignore them. This is particularly true in Python land, where the malleability of the language encourages a certain degree of experimentation and re-casting. Because you can adapt Python to suit your tastes, the temptation to do so even when it’s not actually getting you much beyond style points is hard to resist.
The net result of all this customization and adaptation is messier than Christmas morning: wrappers everywhere. Whatever simplifications each individual wrapper gives you, the aggregate effect of so many different extra layers is overwhelming. At several times in the last decade I’ve sworn off wrappers and vowed to stick with vanilla python, straight-up maya.cmds and simple, linear code. A good code archaeologist could troll through my history and find several repeated periods of growth and die-offs in the wrapper ecosystem, like fossils trapped in shale.
where's pymel?
Where's pymel in there?

Wraptors

Wrappers, though, never really die off like the dinosaurs: they, in fact, more persistent as the cockroaches. And there’s a lesson in that.
Consider a classic case of wrapper-iteis: a system for making maya GUI less of a pain. Everybody writes that one at some point in their TA career (I’ve done it 4 times to my certain knowledge, not counting one-offs and half-assed, abandonware). When somebody feels compelled to spruce something up that much it’s a sign.
Sure, most gui wrappers are just a reaction to the clunky, wordy way that Maya expects us to pop up a window or make a button. And sure, most of those wrappers (some of my own, I hasten to add) really aren’t much better: they’re just shortcuts that cut down on the carpal-tunnel of cmds.textField(fieldname, q=True, text=True).
Sure, saving keystrokes is nice, but over the life of a piece of code the time spent typing is a tiny fraction of that spent reading, debugging and refactoring: that you could (and probably should) just bit the bullet on. But so many persistent, repeated efforts to fix a problemare a symptom that something worse than wordiness is the problem. Wrapper-itis really runs rampant when the toolkit that is simply not adequate to the job at hand. If you have to spend a lot of time thinking about the implementation details instead of the problem you really want to solve you’re not just wasting keystrokes: you’re wasting precious thought and time.
So I’ve been trying to soften my anti-wrapper stance. Sometimes it’s better to actually solve a recurring problem instead of papering it over; sometimes it’s worth taking the time to be in a position to write the code you need to write instead of the code you’re forced to write. Sometimes.
Which of course raises the question of how you can identify those situations and distinguish between a real need for better abstractions and a plain old peevish desire to avoid boilerplate.

Wraptitude

The prime way to distinguish between a ‘wrappable’ problem and a purely syntactic one is to consider the needs of the person who’ll be picking through your code after you’be been run over by a bus.

When your replacement comes to look at your code, will they see something that seems to clearly express the problems you were trying to solve? Or just code that clearly expresses your preferences for a particular set of formatting options and code idioms?
Here’s a little bit of code that reads some information from a database in order to add some ‘credits’ to a time account:
def replenish(user):
    if user is None:
        return False

    with connect_db() as db:
        repl = db.execute("SELECT replenished FROM users WHERE name LIKE ? AND DATE (replenished) <  DATE ('now')", (user,))
        recent = repl.fetchone()

        if recent:
            daynum = db.execute("SELECT strftime ('%w', 'now')").fetchone()[0]
            daynum = int(daynum)
            repl_amount = db.execute(
                "SELECT sun, mon, tues, weds, thurs, fri, sat FROM replenish WHERE users_name LIKE ?", ( user,))
            refresh = repl_amount.fetchone()[daynum]
            cap_amount = db.execute("SELECT cap, balance FROM users WHERE name LIKE ?", (user,))
            cap, balance = cap_amount.fetchone()
            new_balance = min(cap, refresh + balance)

            db.execute("UPDATE users SET balance = ? , replenished = DATE('now') WHERE name LIKE ?", (new_balance, user))
        log(db, user, "replenished with %i credits" % new_balance)
the basic logic is pretty simple. Stripped all the fluff, you merely need to:
  • connect to the database
  • ask the database the last time the user was topped off
  • if the user hasn’t been replenished today, get the amount due
  • add the amount to the user’s account

That’s just four basic ideas. but it takes more than 20 lines to express them.
Far worse, the key logical linkages of the operation are implied, not stated.For the code to make real sense you need to know or deduce that the users table has a field called replenished which stores the last day when the user was topped off; that the ‘replenish’ table has seven fields containing the top-off numbers, arranged Sunday throguh Saturday; and that the user table stores both the maximum number of credits to store and the current balance of credits. The implementation of our simple, 4-step idea only makes sense with all of that special knowledge. It’s further obscured by time saving shortcuts, like using the actual column index in a database table to check today’s value. That may save a couple of lines but it renders the code even harder to parse. And, of course, there are syntax quirks big and small, particularly relating to the creation and formatting of the SQL.
This code works fine; it’s even fairly economical and readable for what it does (for a given value ‘economical’) But it’s not the kind of thing you’d ever want to inherit; it makes sense to me, because I wrote it and I remember (at least today) what I was thinking about when I did. But some future inheritor (heck, even me a year from now) will have to think long and hard about what really ought to be a simple process. The whole thing is bogged down in implementation details that obscure the intent of what’s going on. Really good code often reads almost like pseudo-code. This does not.
To illustrate what a good wrapper can do, here’s the same code using an ‘object relational mapper‘ called peewee: it’s a wrapper around the SQL backend that map database operations onto classes and allows you to focus on the logic instead of the mechanics:
def replenish(user):
    if user is None:
        return
    with connect_db().atomic():
        today = datetime.now()
        today_name = now.strftime("%A")

        updatable_user = User.get(name=user, replenished  < today)
        today_update = Replenish.get(name = user, today_name > 0)
        if updatable_user and  today_update:
            refresh = getattr(today_update, today_name)
            new_balance = min(updatable_user.cap, refresh + updatable_user.balance)
            updatable_user.balance = new_balance
            Log.create(user= user,  message = "replenished with %i credits" % new_balance)
That’s a significantly cleaner bit of code to read. It still requires some outside knowledge but the intention is much more clearly expressed and the message isn’t drowned out in quotes and parens. An ‘offscreen’ benefit, given the way peewee is structured, is that backtracking to the User and Replenish classes would tell the rest of the story pretty straightforwardly without a ton of comments. Only a handful of lines are needed to munge data into the right forms, and the code almost reads like the summary.
That’s a good example of how wrappers can help: saving keystrokes is nice but clarifying the real meaning of the code is priceless.

Wrapola

Well, maybe not exactly price-less. All wrapper code comes with a cost: there are new rules to learn and, probably, new bugs to encounter. If the wrapper uses odd conventions, unusual data formats or is simply slower than hand rolled code it may still be a bad bargain. Nonetheless, this example shows wrappers can be more than just a protest against awkward syntax and API’s that don’t match your taste. Ultimately wrappers are a perfect microcosm of what all coding is about: the search for a clearer understanding of the problem you’re trying to solve.
So if you’re thinking about writing a wrapper, ask yourself this: does the code you want to write teach you something about the problem your solving? Or does it just save you a few keystrokes? Typing is a pain, but you’ll spend a lot more tine looking at your code than you ever will typing it. So don’t focus on just counting lines or syntax: focus on whether the wrapper helps you understand the problem better. If the wrapped code reads like a description of your thought process, you’re on the right track. If it’s just getting you back to that TwitchTV stream on your second monitor a few minutes earlier it might not be worth your time.

PS

I used an ORM for my example because it provides such a powerful example of code that’s not bogged down in syntactic complexities. There is, however, a classic internet flame war about ORMs that I’m glossing over, with nerd rage aplenty for friends and foes of ORMs. Background here if you care.

Saturday, May 9, 2015

Tech art for art directors

I see that the slides from my 2015 GDC talk, Technical Art For Art Directors is now up on the GDC Vault. paywall warning Enjoy – and feedback in the comments is very welcome. I can post the slides if there is interest, but if you watch the video you’ll see that they don’t really work without the verbal context. I defy anybody who wasn't there to make sense of this image, for example....
I’ll say in passing that – despite the lack of text in the finished presentation – I had great luck putting this talk together in Markdown using DeckSet, which let me do the first 90% of the presentation without ever opening PowerPoint or Keynote. The slides were just one big plain text file, which let me put them up on github so I could version them and track changes without getting bogged down in the finicky formatting nonsense that ultimately consumes all slide decks. I particularly liked this workflow because I could work as if it was text but still run it as a slide show very early on, which gave me a chance to get my patter down and listen to the words instead of getting hung up on formatting text. Plain text FTW!

Wednesday, May 6, 2015

Book Review

Computer books are a very hit-or-miss affair. I go through them like comic books but – like comic books – they rarely have much of an impact on me: I read along, nod, and ten minutes after I close the covers I’ve either compressed the whole thing into a crunched-down, dried-up mental summary or filed it away as something to look at for a very specific kind of reference.
It’s hard for a technical book to really engage a reader. Clarity, clean exposition and simply not peddling misinformation by accident or oversight are hard enough and many tech books fail to even hit those mares. Engaging style and memorable insights are even tougher to conjure up.
My personal favorite in this regard is The Art Of Readable Code by Dustin Boswell and Trevor Foucher. Unlike most tech books it’s not about technology, it’s about you. By which I mean it’s about how to approach programming problems with a pragmatic, long term view that’s about making your life more productive and less stressful in the long run by adopting smart practices. It’s not about whiz-bang tech - in fact, the book hops around between languages and problem sets and doesn’t really teach anything about programming per se; rather, it’s about writing: about making your code clear, expressive and humane. Programmer machismo often encourages us to write super-efficient alien artifacts which can be extremely impressive but which nobody wants to touch for fear of getting the Space Odyssey treatment. I’ve started forcing this book on all my new people: it’s better than a style guide because it teaches you things to think about, rather than giving you a list of things to memorize and follow.
(hat tip to @Pat Corwin, by the way, who turned me on to this one)
What got me thinking about tech books just now is the fact that I’ve recently found one that’s almost but not quite in that elite class of game changers. Effective Python, by Brett Slatkin, is more technical and a bit less fluent than The Art Of Readable Code. Still, it caught my eye because it seems like a great mile-marker on the road from “pretty good python user” to “real Pythonista.”
Effective Python does a great job of explaining and illustrating several features of the language which can be overlooked by folks coming from a scripting background rather than computer science. Many of the habits which separate the Python zen masters from the mere acolytes are explained neatly and clearly: the mysteries of the yield keyword, the evils of the mutable default argument, and the unfathomable allure of metaclasses are all dealt with clearly and in ways that don’t make you feel like a chump. Even better, the information is presented with a usable, practical slant: it’s neither an abstract set of rules to memorize nor a comp-sci nerdout about language features. Instead the book presents enough information for you to make good strategic choices: Perennial python puyzzlers like “list comprehension or loop?”, “Is it okay to use Try…except…else?” and “When to use propertites vs descriptors vs plain old fields” are all addressed in a reasonable, non-dogmatic way.
So, I’ll definitely be adding that one to the new-hire reading list in the future. Recommended!
PS if you haven’t checked out the Tech Art Bookstore in a while, I’ve added a bunch of new items over the last few months

Saturday, March 7, 2015

Back from GDC

Another year, and another horde of game developers have washed over the Mosconce Center like a tidal wave leaving devastation, half-eaten Peruvian chicken sandwiches, and flyers for UE4 in their wake.
hot times on the hiring floor this year!
This was a pretty upbeat year at the show. It was big -- numbers aren't final yet but it looks like the biggest one yet -- and buzzing: the show floor and (perhaps more interesting to the crowd here) the hiring floor were pretty busy. For an old GDC vet it was still a bit of an odd scene on the jobs floor -- the titans of old (EA, Blizzard, Sony, and Microsoft) no longer dominate the space with huge booths and long lines of art school kids -- the biggest booth was from wargaming.net and the busiest ones were hiring for things like VR or even SpaceX (by the way, that rumor I've heard about SpaceX hiring tech artists is not true, at least not according to the folks at the booth this year).  The job market for TA's looks to be pretty hot too: there were a lot of people in the TA roundtable looking to hire. Of course, everybody want to hire "senior" TAs, which is a tough thing for the less-senior to contemplate. Much discussion of how to get people from the entry level to the big leagues, I think there's a future post in there somewhere.
A different kind of change is the vastly expanded demographics of the show -- it's a much bigger, more diverse and more interesting set of faces than you used to see back in the day. The tech art round tables (thanks as always to +Jeff Hanna  for that pillar of the community) had a great mix of people and good questions from all over. Even the dinner at Bucca's had lost a bit of it's pallor, at least until the end when the bottle of chianti and 6 pound calamari plate had circled the table one too many times.
Here's a few things that stuck out to me at the show:

  • the TA Bootcamp was bigger and more badass than ever. We're not afraid to tackle the controversial subjects, from a rigging system in Max to processing budget reports! By all means check out the bootcamp sessions on the GDC vault(link) when they go up in a month or so.
  • the animation bootcamp has also grown into a big treasure trove of great information. If your job involves supporting animation, you should check it out and make sure you know what the animators care about when they aren't whining about where you put the sliders or the shape of your nurbs handles.
  • PBR Rendering has definitely become ubiquitious -- now that Unity, UE, Crytek and Stingray are all offering PBR it's going to be the AAA standard in a year or two if it's not already. I suspect that mobile will stay with conventional forward-rendered shaders for a while yet -- Marmoset's Skyshop(link) is beautiful and all but it labors to render a single model at speed on my iPad -- but I would not be suprised to see cheap-ass versions of the PBR workflow being ported back to mobile either. Start getting ready to pronounce 'metallicity' a lot, and while you're at it check out the talks from Certain Affinity at the TA bootcamp on their PBR pipeline, as well as last year's Order 1886 rendering talk.
  • Almost as can't-swing-a-dead-cat common as PBR was Substance from Allegorithmic. Lots of sightings in talks and in various booths around the show floor. One thing I picked up from their sponsored talk: the batch tools in Substance are python-scriptable, and include notification callbacks for things like texture changes: this could be a very useful trick for the perennially unkempt fringes of the pipeline. I expect I'll have a blog post on this one in the near future once I learn what's going on (or, more characteristically, before I learn what's going on).
  • I think a lot of TA's will find some value in this book from David Lightbown:
     His talk was great, with insights into many of the things we often get backwards in tool development.
  • VR, VR, VR.  Yadda yadda. I'm not jaded, just a little motion sick.  Although that will all be better once I get a virtual nose.  Bootcamp FTW!
About the only disappointing thing about the show was the lack of really stunning tech talks (fair disclaimer: between meetings and checking out talks I helped out with, I didn't see as many talks as I'd have liked to). The only graphics talk I found really compelling was from Q-Games on their bizarro Stalinist platformer The Tomorrow Children.


The renderer does some really interesting non-standard tricks. It's got a real-time 3-bounce GI system, using a hardware voxel representation of the world -- I think even the Enlighten middleware (used in Battlefield, for example) only does a single bounce. They also use what seems like a volumetric method for doing sub-surface scattering that does an amazing job for things like icebergs and waxy surfaces: unlike all the conventional screen space tricks it renders the effects of light sources (including indirect sources) behind a surface. The techniques looked hellaciously complicated, but the results were pretty impressive and visually unique.

I'm sure I'll come up with some more stuff as I sort through my notes, but there's a quick set of expressions from a great show. Hope to see you all there next year!

Thursday, January 22, 2015

The Trust Fund

A recent discussion on TAO reminded me of this old GDMag column from 2010. Tech artists always have to fight to stay focused on the relationship at the heart of our business: no amount of technical wizardry matters if your artists aren't actually benefitting from what you do.

While much has changed in the last five years, a lot of this still seems like good advice (to me, anyway, but I'm biased).  I might add a couple of sentences about 'more unit tests' and 'continuous integration' and similar buzzwords but that's just fluff: taking care of your users is all that really counts.

Friday, May 2, 2014

Saturday, April 5, 2014

Earth calling maya.standalone!

Somebody on Tech-artists.org was asking about how to control a maya.standalone instance remotely.  In ordinary Maya you could use the commandPort, but the commandPort doesn't exist when running under standalone - apparently it's part of the GUI layer which is not present in batch mode.

So, I whipped up an uber-simple JSON-RPC-like server to run in a maya standalone and accept remote commands. In response to some queries I've polished it up and put it onto GitHub.

It's an ultra-simple setup. Running the module as a script form mayapy.exe starts a server:
    mayapy.exe   path/to/standaloneRPC.py


To connect to it from another environment, you import the module, format the command you want to send, and shoot it across to the server. Commands return a JSON-encoded dictionary. When you make a successful command, the return object will include a field called 'results' containg a json-encoded version of the results:
           
        cmd = CMD('cmds.ls', type='transform')
        print send_command(cmd)
        >>> {success:True, result:[u'persp', u'top', u'side', u'front'}
 

For failed queries, the result includes the exception and a traceback string:
           
        cmd = CMD('cmds.fred')  # nonexistent command
        print send_command(cmd)
        >>> {"exception": "", 
             "traceback": "Traceback (most recent call last)... #SNIP#",
             "success": false, 
             "args": "[]", 
             "kwargs": "{}", 
             "cmd_name": "cmds.fred"}
 

It's a single file for easy drop. Please, please read the notes - the module includes no effort at authentication or security, so it exposes any machine running it to anyone who knows its there. Don't let a machine running this be visible to the internet!

Sunday, March 16, 2014

Sliders!

Although GDMag is no more, I still occasionally get interesting press releases from PR flacks who haven't gotten the bad news. This morning,  one from MakeHuman caught my eye.

Now with realistic back flab!

MakeHuman is a new open sourced parametric modeling / body morphing program written in Python.  The overall use case is similar to Poser or Daz3d.  It seems like it also incorporates some of the underlying ideas from the venerable FaceGen - particularly having a few high level sliders that correlate changes across many different aspects of a model.


Tuesday, March 11, 2014

Descriptors and pythonic Maya properties

Updated 4/11/2015: fixing the embedded code that was busted by Blogger.
I’m still working on the followup to Rescuing Maya GUI From Itself, but while I was at it this StackOverflow question made me realize that the same trick works for pyMel-style property access to things like position or rotation. If you’re a member of the anti-pyMel brigade you might find this a useful trick for things like pCube1.translation = (0,10,0). Personally I use pyMel most of the time, but this is a good supplement or alternative for haterz or for special circumstance where pymel is too heavy.

The goal is to be able to write something like

from xform import Xform
example = Xform('pCube1')
print example.translation
# [0,0,0]
example.rotation = (0,40, 0)

The process s about as simple as it can get thanks to the magic of descriptors. This example spotlights one advantage of descriptors over getter/setter property functions: by inheriting the two classes (BBoxProperty and WorldXformProperty) I can get 4 distinct behaviors (world and local, read-write and read-only) with very little code and no if-checks.

'''
xform.py

Exposes the xform class: a simple way to set maya position, rotation and similar properties with point notation.

(c) 2014 Steve Theodore.  Distributed under the MIT License (http://opensource.org/licenses/MIT)
TLDR: Use, change and share, please retain this copyright notice.
'''

import maya.cmds as cmds

class XformProperty( object ):
    CMD = cmds.xform
    '''
    Descriptor that allows for get-set access of transform properties
    '''
    def __init__( self, flag ):
        self.Flag = flag
        self._q_args = {'q':True, flag:True}
        self._e_args = {flag: 0}


    def __get__( self, obj, objtype ):
        return self.CMD( obj, **self._q_args )

    def __set__( self, obj, value ):
        self._e_args[self.Flag] = value
        self.CMD( obj, **self._e_args )


class WorldXformProperty( XformProperty ):
    '''
    Get-set property in world space
    '''
    def __init__( self, flag ):
        self.Flag = flag
        self._q_args = {'q':True, flag:True, 'ws':True}
        self._e_args = {flag: 0, 'ws':True}

class BBoxProperty ( XformProperty ):
    '''
    Read only property for bounding boxes
    '''
    def __set__( self, obj, value ):
        raise RuntimeError ( "bounding box is a read-only property!" )

class WorldBBoxProperty ( WorldXformProperty, BBoxProperty ):
    '''
    Read only property for bounding boxes
    '''
    pass


class Xform( object ):
    '''
    Thin wrapper providing point-notation access to transform attributes

       example = Xform('pCube1')
       # |pCube1
       example.translation 
       # [0,0,0]
       example.translation = [0,10,0]

    For most purposes Xforms are just Maya unicode object names.  Note this does
    NOT track name changes automatically. You can, however, use 'rename':
       example = Xform('pCube1')
       example.rename('fred')
       print example.Object
       # |fred

    '''

    def __init__( self, obj ):
        self.Object = cmds.ls( obj, l=True )[0]

    def __repr__( self ):
        return unicode( self.Object )  # so that the command will work on the string name of the object

    # property descriptors  These are descriptors so they live at the class level,
    # not inside __init__!

    translation = XformProperty( 'translation' )
    rotation = XformProperty( 'rotation' )
    scale = XformProperty( 'scale' )
    pivots = XformProperty( 'pivots' )

    world_translation = WorldXformProperty( 'translation' )
    world_rotation = WorldXformProperty( 'rotation' )
    world_pivots = WorldXformProperty( 'pivots' )
    # maya does not allow 'world scale' - it's dependent on the parent scale

    # always local
    scaleTranslation = XformProperty( 'scaleTranslation' )
    rotateTranslation = XformProperty( 'rotateTranslation' )

    boundingBox = BBoxProperty( 'boundingBox' )
    world_boundingBox = WorldBBoxProperty( 'boundingBox' )


    def rename( self, new_name ):
        self.Object = cmds.ls( cmds.rename( self.Object, new_name ), l=True )[0]

    @classmethod
    def ls( cls, *args, **kwargs ):
        '''
        Returns a list of Xforms, using the same arguments and flags as the default ls command
        '''
        try:
            nodes = cmds.ls( *cmds.ls( *args, **kwargs ), type='transform' )
            return map ( Xform, nodes )
        except:
            return []

You may note the absence of a __metaclass__. In this case, with only a single class, a meta would be an unnecessary complication. Meanwhile the code for MayaGUI itself is up on GitHub. Comments and/or contributions welcome!

Thursday, February 20, 2014

Some new research in the academic page

Still cleaning up after the long blog silence (teaching that Unity class was fun -- but six games in five weeks plus a day job is murder!). So I should point out a couple of additions to the Interesting Research page.

Sunday, February 16, 2014

Pity for the outcast

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

Shameless plug time

The book I worked on is finally available for ordering on Amazon!



I'm pleased and of course also nervous.  If you like it, please be sure to leave a positive review on Amazon - I'm told it makes a big difference.  I don't actually make any money from this one (such is modern publishing, alas) but I do have another book under way and it'll be much easier to get that published if this one does well and is well reviewed.

Wednesday, January 29, 2014

Talking to Photoshop via TCP

A recent thread on TAO got me thinking about communicating with Photoshop from Python. In the past I've done this Adam Plechter-style using COM ( from both C# and Python) and it's worked for me, but I know some people have problems with COM for a variety of reasons relating to windows versions and DLL hell.
Using COM always makes me feel like Mr. Yuk.


Since I had to learn a few JavaScript tricks back when I started looking into Python web development, I thought it might be worth investigating what could be done to cut out COM.  It turns out that ExtendScript - the JavaScript flavor that comes with Adobe products (at least since CS 5, and I think earlier) includes a socket object which allows for TCP communication.  That's not kosher in 'real' browser based JS - but ExtendScript cheats a little (that's also why it allows you to do things like hit the local file system, another JS no-no).

The Adobe docs have an example (around page 196) which shows how you can implement a chat server or a web server running inside Photoshop. That might not be practical but it provides a simple framework you can hijack to turn Photoshop into a remote procedure call server.  Here's an example of a super-simple server that can be run inside Photoshop:
Note: You'll need to save the file as a .JSX (not .JS!) for Photoshop to allow the ExtendScript functionality which makes the socket objects work.

This example is very bare bones, but it is easy to extend - just create function objects and add them to known_commands dictionary and then send them over the socket. The way it's written here the commands and arguments are split on spaces (similar to the way a shell command works) -- if you need to get at them in your JS functions you can get at them using the arguments() keyword:.  For serious work I'd probably use the ExtendScript XML object and send the commands and responses as xml since that gets you out of having to worry about stuff like 'what if I want to send an argument with a space in it' -- however this purpose of this excersize is just to demonstrate what's possible.

I should note that while the server is running, Photoshop is locked into the wait loop so it will not be accessible interactively -- like a Maya running a long script, the main thread is just waiting for the script to continue. For the typical 'remote control' application that's what you'd expect, but it may not answer for all purposes - so be warned.

If you're trying to talk to Photoshop from Python, it's incredibly simple:


That's all there is to it - of course, the real problem is getting useful work done in the clunky Photoshop API -- but that's going to be the same no matter whether you talk to PS via COM or TCP/IP.  Anecdotally, I've heard the PS scripts are faster in JavaScript than when using COM or AppleTalk or VB, so perhaps this method will be competitive on speed as well.  For small tasks it's certainly a simpler and less irritating way to send a squirt to and from PS 

Thursday, January 16, 2014

Mighty Morphin Maya Module Manager

For folks who are interested in fiddling with Maya modules as per the last post, I've tossed a quickie class to manage Maya modules onto my Gist account.

This is a bare bones bit of code. The main class is the ModuleManager which can find any .mod files on the MAYA_MODULE_PATH of the current Maya environment. It's primary use is to find and list all the modules; secondarily it can be used to toggle them on and off (by changing the leading + which Maya uses to id a module to a -, or vice-versa). It's pretty dumb (no accounting for file permissions, incorrectly formatted .mod files, etc) but it's handy for quickly testing out configs.

 Also included is a GUI class, ModuleManagerDialog, which finds provides a simple GUI for listing, enabling, and disabling .mod files. Again, pretty simple stuff, but people may find it useful.


As will all code I put up here, it's BSD licensed. Use away with attribution - and if you have any bug fixes, let me know and we'll fix the Gist version.

PS, hat tip to the gang at TAO  for the idea of using a context manager to get out of all those stupid setParent("..") calls in Maya GUI work. I could not find the original post where somebody mentioned it - but whoever you are, sir or madam, thank you ever so much.