I originally wrote this for Game Developer in 2006, and it was always one of my favorite columns.
A blog about technical art, particularly Maya, Python, and Unity. With lots of obscurantist references
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
Friday, December 26, 2014
The Conquest of Space
One of the hardest thing about working in our business is that the technology evolves faster than our means of talking about it. There are a lot of things which we learn in the course of our work that we have a hard time passing on, because the field is so small and so few of us have leisure to try to find ways to sum up all of the practical knowledge we pick up along the way.
I originally wrote this for Game Developer in 2006, and it was always one of my favorite columns.
I originally wrote this for Game Developer in 2006, and it was always one of my favorite columns.
Sunday, December 21, 2014
Comments requested
I'm working a talk for GDC , tentatively entitled "Technical Art Director? What the heck does that mean?"
I'd love to hear thoughts from all you industry types on what the role means. That could be what you think it means for your company, or what you think it means when other people hear it, or what you think it ought to mean.
Good anecdotes are particularly appreciated and may be shamelessly stolen :)
So please comment below and let me know what you think while I'm trying to figure out what I think...
PS for future readers coming here via Google or whatnot, this link goes to my perennial how to write a GDC talk post. Physician, heal thyself!
PS for future readers coming here via Google or whatnot, this link goes to my perennial how to write a GDC talk post. Physician, heal thyself!
Monday, December 15, 2014
Adventures in the 4th dimension
In our last discussion of 3d math, we started to plumb the mysteries of the matrix. Along the way we discovered two important facts: First, that it’s possible to write an article about matrices with only the merest smidge of a Keanu Reeves mention and second (almost as important), that matrices are just a convention for applying dot products in series. We walked through the derivation of matrices for a series of dot products and shows how hat simple operation allows you to do rotations in two and three dimensions.
Naturally, any TA reading this will be knows there's more. We all know that the matrices we’re most familiar with — the transform matrices that drive animation and modeling — do more than rotate. So this this time out we’re going talk about how translation — spatial offsets — can be packed into matrices. And we're going to do it in a truly brain bending way. Sort of.
Naturally, any TA reading this will be knows there's more. We all know that the matrices we’re most familiar with — the transform matrices that drive animation and modeling — do more than rotate. So this this time out we’re going talk about how translation — spatial offsets — can be packed into matrices. And we're going to do it in a truly brain bending way. Sort of.
If none of this sounds familiar, you may want to return to the previous post in the series before continuing.
Friday, December 12, 2014
All we are saying is give API 2.0 a chance
Doing all this math-related posting has reminded me of something I've been meaning to write up:
Maya's python API 2.0, first introduced in the 2013 version, got off to a rocky start. People complained about missing functions and missing modules. It uses (mostly) the same function and class names as the original OpenMaya Python, which is a recipe for confusion. The documentation is pretty confusing too, since it points at the original C++ docs and leaves it up to you to do much of the translation in your head. However....
Maya's python API 2.0, first introduced in the 2013 version, got off to a rocky start. People complained about missing functions and missing modules. It uses (mostly) the same function and class names as the original OpenMaya Python, which is a recipe for confusion. The documentation is pretty confusing too, since it points at the original C++ docs and leaves it up to you to do much of the translation in your head. However....
Saturday, December 6, 2014
Dot Matrix
We started our math review with a look at the dot product, and started out by showing how dots work in a minimalist way. This time out we’ll do the same thing the most basic component of 3d math - the matrix.
Saturday, November 29, 2014
Dot's all, folks
Last time out I went on (probably a bit too long) on the virtues of the dot product - the operation which takes two lists of numbers and multiplies them to create a single product. The highlight of the whole thing was the cosine dot product - the handy fact that the dot product of two normalized vectors is the cosine of the angle between them.
Now that the theory is out of the way, it’s time to highlight some of the zillions of applications for this handy little operation.
Now that the theory is out of the way, it’s time to highlight some of the zillions of applications for this handy little operation.
Saturday, November 22, 2014
Bagels and Coffee, or, the vector dot product and you
I’ve been boning up on my math lately.
Like most TA’s I’ve cobbled together a bag of tricks from different situations I’ve dealt with over the years, but I’ve never really gone back to shore up my shaky high school trigonometry and pre-calculus. It’s certainly possible (at least, I hope it is!) to be a good TA with only seat-of-the-pants math skills — after all, we have parenting and scaling and all the other cool tricks in our apps to do the heavy lifting for us. Still, I’ve been finding that paying more attention to the math fundamentals is helping me solve problems more efficiently and elegantly than my patented hack-and-slash techniques did.
So, I’m starting an occasional series on some basic math concepts that I hope will be useful to other TA’s. I know it’s been helpful to me - there’s nothing that concentrates the mind like putting something out there on the internet for public commentary - it’s really forces you to think things through… At least, as long as you’re not on Twitter.
Like most TA’s I’ve cobbled together a bag of tricks from different situations I’ve dealt with over the years, but I’ve never really gone back to shore up my shaky high school trigonometry and pre-calculus. It’s certainly possible (at least, I hope it is!) to be a good TA with only seat-of-the-pants math skills — after all, we have parenting and scaling and all the other cool tricks in our apps to do the heavy lifting for us. Still, I’ve been finding that paying more attention to the math fundamentals is helping me solve problems more efficiently and elegantly than my patented hack-and-slash techniques did.
So, I’m starting an occasional series on some basic math concepts that I hope will be useful to other TA’s. I know it’s been helpful to me - there’s nothing that concentrates the mind like putting something out there on the internet for public commentary - it’s really forces you to think things through… At least, as long as you’re not on Twitter.
Sunday, November 16, 2014
Big (?) Python bucks!
I've frequently commented in the past on the Game Developers Salary Survey. For you Pythonistas out there, there's some nice data (amateur grade, but still pretty good) to be found at the Python Developer Salary Survey. No mention of what you get for knowing Max / Maya, however, but by crunching the numbers I'd guess it subtracts about $30k per year.
Or maybe that's just because we're all lousy programmers.
EDIT: fixed bad link
Or maybe that's just because we're all lousy programmers.
EDIT: fixed bad link
Thursday, November 13, 2014
Dot Net is Open?
Courtesy of +Robert Butterworth comes the news that .Net is now open source. Future development will be done out of GitHub (!). Is the timing - on the anniversary of the fall of the Berlin Wall - just a coincidence?
In the short term this probably only means that some projects which have avoided .Net for being closed-source may consider using C# or other .Net languages. Over the medium term it probably presages a 3-6% decline in the use of the dollar sign in derisive online references to Micro$oft. What's most interesting, though, is what it might mean for Unity, which is hard to predict but tantalizing to speculate on.
In the short term this probably only means that some projects which have avoided .Net for being closed-source may consider using C# or other .Net languages. Over the medium term it probably presages a 3-6% decline in the use of the dollar sign in derisive online references to Micro$oft. What's most interesting, though, is what it might mean for Unity, which is hard to predict but tantalizing to speculate on.
Saturday, November 1, 2014
WYG > WYS
Update 4/4/2015: I've got a much improved pipeline for markdown blogging using Sublime Text, as detailed here. I'm also having a lot of luck with MDWiki for static sites on github.io
First off, a confession. I’ve become a plaintext nazi. 30 years since I first hit Ctrl+I to italicise a piece of text, I’ve pretty much abandoned fancy-pants text edtors for the hardcore geek chic of plain text and Markdown.
To be honest, this makes me uncomfortable, because plain-text-chauvinism is the techie equivalent of skinny jeans and ironical facial hair; it’s definitely a thing for the Cool Kids,tm a demographic which is not really my native habitat.
But I’m willing to be cool if that’s what it takes.
First off, a confession. I’ve become a plaintext nazi. 30 years since I first hit Ctrl+I to italicise a piece of text, I’ve pretty much abandoned fancy-pants text edtors for the hardcore geek chic of plain text and Markdown.
To be honest, this makes me uncomfortable, because plain-text-chauvinism is the techie equivalent of skinny jeans and ironical facial hair; it’s definitely a thing for the Cool Kids,tm a demographic which is not really my native habitat.
But I’m willing to be cool if that’s what it takes.
Thursday, October 30, 2014
The Dog Ate My Homework
I had an interesting issue at work the other day. While the details are unit-test specific, I learned a useful general idea that’s worth sharing.
Sunday, October 26, 2014
Laziness and cleanliness and MEL, Oh My.
The other day I was following a thread on Tech-Artists which reminded me of one of those little Maya things that doesn't really matter, but which drives me bonkers: busted front ends for Maya plugins.
Sunday, October 5, 2014
The Dismal Science : Technical Debt For Technical Artists
From the estimable +Paul Vosper an excellent discussion of technical debt: the long term costs you incur by prioritizing the here-and-now demands of everyday life over technical and architectural needs.
Tuesday, September 16, 2014
The Player's Handbook
In keeping with the other career articles I've been reposting, here's a favorite old piece from Game Developer about career arcs in games. The illustrations are from the absolutely priceless Old School FRP Tumblr, which is a treasure trove of 1980's era RPG artwork that any gamer ought to check out. Know your history!
Friday, September 5, 2014
The Game Artists Resume Guide (Revisited)
I noticed that my recent repost of the old job hunting guide, Read The Damn Ad, has quickly amassed a lot of views and links. In the interest of completeness, therefore, I thought I'd also repost the bookending article (also from Game Developer) on the nuts and bolts of applying for a job.
The whole thing is after the jump...
The whole thing is after the jump...
Thursday, September 4, 2014
2015 Bug watch: ls()
For people switching to Maya 2015 here's an irritating bug in the 2015 Maya python layer.
In all Mayas before 2015 (as far as I can check, anyway), calling cmds.ls() with a string that was not a valid Maya object name was allowed. You could for example, call
and you'd get back an empty array. In 2015, however, it looks like they have changed the way maya.cmds is converting the string into a dag node reference; it you call the same thing in 2015 you'll get this instead:
This is a bit more serious than it seems at first glance, because ls is such a common command. Any ls operation which includes a string that starts with anything other than a letter or a number with raise an exception, so there are a lot of places which used to just chug along silently that are going to start raising exceptions.
My workaround is to patch cmds.ls on startup so that it safely renames any bad string before passing them to Maya. I do this in my bootstrap routine so I don't have to chase down every occurrence of ls anywhere in my code (1,001 of them, or so PyCharm tells me...).
This makes sure that existing code works as it did before and I don't think it will break anything, since the invalid character strings were never going to be ls'ed into anything anyway. Ordinarily I'm not a big fan of magical behind the scenes fixes but this is a pretty serious change to the behavior of ls which doesn't seem like an intentional upgrade so much as an oversight on Autodesk's part. So, at least until the old behavior comes back I'm gonna try it.
Update: Hat tip to +Robert White for pointing out that the original regex I posted did not handle namespaces. Code above includes the fix. Never would have figured it out without Pythex!
Update 2: Updated the safe_ls procedure to handle more of the allowable syntax in older mayas
In all Mayas before 2015 (as far as I can check, anyway), calling cmds.ls() with a string that was not a valid Maya object name was allowed. You could for example, call
cmds.ls("@")
and you'd get back an empty array. In 2015, however, it looks like they have changed the way maya.cmds is converting the string into a dag node reference; it you call the same thing in 2015 you'll get this instead:
# Error: Syntax error: unexpected end @ at position 1 while parsing:
# ; ; @
# ; ; ^
# : @
# Traceback (most recent call last):
# ; File "", line 1, in
# RuntimeError: Syntax error: unexpected end @ at position 1 while parsing:
# ; ; @
# ; ; ^
# : @ #
This is a bit more serious than it seems at first glance, because ls is such a common command. Any ls operation which includes a string that starts with anything other than a letter or a number with raise an exception, so there are a lot of places which used to just chug along silently that are going to start raising exceptions.
My workaround is to patch cmds.ls on startup so that it safely renames any bad string before passing them to Maya. I do this in my bootstrap routine so I don't have to chase down every occurrence of ls anywhere in my code (1,001 of them, or so PyCharm tells me...).
import re
import maya.cmds as cmds
VALID_OBJECT = re.compile("""^[|]?([^a-zA-Z_\?\*\:\|])|([^a-zA-Z0-9_\?\*\:\|\.\[\]])""")
as_u = lambda p: p if not hasattr(p, 'addPrefix') else unicode(p)
def safe_ls(*args, **kwargs):
'''
Patches maya 2015 cmds.ls so that it does not except when passed illegal name characters.
'''
if not len(args):
return _BASE_LS(**kwargs)
if len(args) == 1 and hasattr(args[0], '__iter__'):
args = args[0]
test_args = [VALID_OBJECT.sub('_', as_u(i)) for i in args]
return _BASE_LS(test_args, **kwargs)gs)
cmds.ls = safe_ls
This makes sure that existing code works as it did before and I don't think it will break anything, since the invalid character strings were never going to be ls'ed into anything anyway. Ordinarily I'm not a big fan of magical behind the scenes fixes but this is a pretty serious change to the behavior of ls which doesn't seem like an intentional upgrade so much as an oversight on Autodesk's part. So, at least until the old behavior comes back I'm gonna try it.
Update: Hat tip to +Robert White for pointing out that the original regex I posted did not handle namespaces. Code above includes the fix. Never would have figured it out without Pythex!
Update 2: Updated the safe_ls procedure to handle more of the allowable syntax in older mayas
Wednesday, September 3, 2014
Sony's Open Source Toolset
I noticed on Gamasutra (hat tip +Jon Jones ) that Sony is open-sourcing its Authoring Tools Framework.
Monday, September 1, 2014
Size is the enemy: an oldie but a goodie
While googling my way around some strategy issues lately I rediscovered this 2007 post from Jeff Atwood which neatly sums up a few important things that have been factoring very large in my thinking lately: Plus, I've spent the last 4 days in the madness that is Pax (shout out to all the great folks who came by the booths, by the way!) so I'm a little grumpy.
The reasons why are enumerated after the jump.... (Update 9/3) see below
The reasons why are enumerated after the jump.... (Update 9/3) see below
Thursday, August 28, 2014
Pax!
Throat lozenges? Check.
Hand sanitizer? Check.
Let's the games begin!
I'll be working the Moonrise booth Friday, and manning the State of Decay booth Sunday and Monday. Stop by!
Monday, August 25, 2014
Pax Dev State of Decay Post-mortem
PS. You know we're looking for some serious senior tech-artists, right?
Wednesday, August 20, 2014
SIGGRAPH 2014 Short Review
It's been crazy times at Undead Labs as we get ready for Pax. I did sneak in a lightning visit to SIGGRAPH, since it drive-able in Vancouver, but I had to cut it pretty short.
The highlight of the show was the TA beer night at the Butcher and Bullock -- hats off to +Robert Butterworth for putting together -- but there was some other stuff going on as well. Here's a very partial and completely unscientific brain dump of what I saw. The important caveat here is that my limited schedule kept me on a very short leash: I spent all day Monday in the Advances in Real Time Graphics course, which I'm pleased to say has become a SIGGRAPH institution (go Natasha!) and then all day Tuesday talking to vendors, so I'm 100% certain to have missed a lot of cool and interesting stuff. This was an all business visit, so most of what I have to report is general impressions rather than new cutting edge research. My impressions are after the jump...
The highlight of the show was the TA beer night at the Butcher and Bullock -- hats off to +Robert Butterworth for putting together -- but there was some other stuff going on as well. Here's a very partial and completely unscientific brain dump of what I saw. The important caveat here is that my limited schedule kept me on a very short leash: I spent all day Monday in the Advances in Real Time Graphics course, which I'm pleased to say has become a SIGGRAPH institution (go Natasha!) and then all day Tuesday talking to vendors, so I'm 100% certain to have missed a lot of cool and interesting stuff. This was an all business visit, so most of what I have to report is general impressions rather than new cutting edge research. My impressions are after the jump...
Thursday, August 14, 2014
Submit!
For any readers who are thinking about doing a GDC talk in 2015, it's time to get your submissions in! Talk abstracts should be submitted by August 28th!
Tech artists are an important part of GDC: sharing knowledge, inspring people to work smarter, and teaching better ways to get things done are what we do all the time -- doing a talk is just taking your day job on the road. So hurry up and get those proposals together!
After the jump I've reposted an article I did for Game Developer back in 2008 which outlines some of the things I think make for a good GDC talk. Update: I added a longish list of stuff that has changed since the original 2008 article in the comments - in particular, don't forget to read the new submission guidelines and don't forget 25 minute talks if you're getting your feet wet
Tech artists are an important part of GDC: sharing knowledge, inspring people to work smarter, and teaching better ways to get things done are what we do all the time -- doing a talk is just taking your day job on the road. So hurry up and get those proposals together!
After the jump I've reposted an article I did for Game Developer back in 2008 which outlines some of the things I think make for a good GDC talk. Update: I added a longish list of stuff that has changed since the original 2008 article in the comments - in particular, don't forget to read the new submission guidelines and don't forget 25 minute talks if you're getting your feet wet
Wednesday, August 6, 2014
Moonrise announcement
So, we just took the wraps off our latest project:
We'll be showing the beta at Pax Prime (booth 6103). It's been a really fun project to work on (for a grizzled ancient like yrs. truly, working on mobile is kind of liberating. It's like the old days of limited budgets and tiny teams. Its so nice to get something from concept to execution in 18 months instead of 4 or 5 years, and with a team that can meet in a lunch room instead of needing a whole theater.
...the game looks a lot nicer than they did in the old days though. I used to sneer at mobile graphics, but nowadays it's pretty awesome what you can accomplish with a little care.
PS. In case you're wondering: It's not a zombie game. But we're not out of the zombie business, not by a long shot. Check out article linked above for more from HQ. But trust me, there's a lot of zombies in our future too.
We'll be showing the beta at Pax Prime (booth 6103). It's been a really fun project to work on (for a grizzled ancient like yrs. truly, working on mobile is kind of liberating. It's like the old days of limited budgets and tiny teams. Its so nice to get something from concept to execution in 18 months instead of 4 or 5 years, and with a team that can meet in a lunch room instead of needing a whole theater.
...the game looks a lot nicer than they did in the old days though. I used to sneer at mobile graphics, but nowadays it's pretty awesome what you can accomplish with a little care.
Friday, August 1, 2014
Read The Damn Ad!
This is an article I originally wrote for the late lamented Game Developer Magazine on the black art of job hunting. Hiring has been on my mind a lot lately and so I've had to revisit a lot of things I touched on here. Although a lot has changed in the intervening decade, the brutal realities of the job market haven 't changed much -- except, perhaps, to get a tad more brutal as the pool of qualified candidates has grown faster than the games biz as a whole.
So, without further ado, here's a disgruntled reader's guide to resumes and cover letters.
Ahhh! It's spring, and the air is full of new life. Everything old is new again -- particularly
the soundtracks (and the appetizers) at those GDC parties. Everywhere you look,
studios flush with greenlight funding are strutting their gorgeous plumage
before a new crop of art-school grads, hoping to entice them into an intricate
mating dance. Birds do, bees do it – even Hollywood TD's do – so let's do it:
let's talk about job hunting. But rather than the looking at if from the
perspective of the potential hire, let's stop and look at the hiring process
from the other side of things – the way it looks from the other side.
Rule #1: Hiring hurts
Rule #2 Companies are desperate and pathetic
So, without further ado, here's a disgruntled reader's guide to resumes and cover letters.
Dear Job Applicant,
Thank you for applying for
the opening we advertised. While we specifically requested only applications from
candidates having four or more years of industry experience, we understand that
you might not have realized which industry we intended. However we are
impressed by your entrepreneurial drive and are sure that the skills you
learned behind the cash register at McDonalds can contribute greatly to our
team environment.
We are somewhat concerned,
however, that you may have mistaken our ad for one from another company. Since
none of the four identical resumes and cover letters we received from you
mentioned our company or which position you were applying for, we just wanted
to contact you to be sure that you were, in fact, responding to one of our
ads. My assistant and I spent quite some
time puzzling over this and were unable to reach a conclusion, so we thought it
best to contact you and see if we could figure out what, exactly you intended.
After all, we receive dozens of applications a week – what excuse could we have
for giving each one less than our undivided attention?
In any case, we also have a more
personal motive for contacting you. We really wanted to thank you for
introducing us to the fascinating world of post-industrial Thrashtronica – a
musical style we had never even heard of around the office until we went out
and bought a VHS player to view your samples. The nightmarish soundscape
certainly taught us a few things about the meaningless void at the heart of
modern capitalism! It's a good thing the turntable animations on your tape were
15 minutes long, so we were able to hear the entire piece.
There's really only two things you need to remember when
approaching preparing job applications:
Rule #1: Hiring hurts
Hiring is a slow, expensive, and risky proposition.
Think about the employment process from the perspective of a company that has a slot to fill: In order to get one person, you need to spend a couple of weeks contacting various websites and magazines, writing up a set of ads that adequately describe the exact job you're trying to fill – assuming you can get management and production to agree on what that is! You'll also have to spend at least a few hundred bucks on each ad.
Once you've laid out all this money and time, you'll get hundreds of applications – and 95 percent of them will be from people who haven't got any of the qualifications you're seeking. Nevertheless, you'll need to have a dedicated person to spend weeks sifting through all the resumes and looking at all the portfolios of old school work, 3-d package tutorial images, and scanned-in charcoals from figure classes.
When you finally get to the really good candidates, half of them will already be taken. To top it all off, the remainders won't come to work for you unless you actually give them money. Just to show up! And there's probably a recruiter, a moving company, or an immigration laywer lurking in the background with a bill as well. You know, it's really not hard to see why companies hate hiring.
Think about the employment process from the perspective of a company that has a slot to fill: In order to get one person, you need to spend a couple of weeks contacting various websites and magazines, writing up a set of ads that adequately describe the exact job you're trying to fill – assuming you can get management and production to agree on what that is! You'll also have to spend at least a few hundred bucks on each ad.
Once you've laid out all this money and time, you'll get hundreds of applications – and 95 percent of them will be from people who haven't got any of the qualifications you're seeking. Nevertheless, you'll need to have a dedicated person to spend weeks sifting through all the resumes and looking at all the portfolios of old school work, 3-d package tutorial images, and scanned-in charcoals from figure classes.
When you finally get to the really good candidates, half of them will already be taken. To top it all off, the remainders won't come to work for you unless you actually give them money. Just to show up! And there's probably a recruiter, a moving company, or an immigration laywer lurking in the background with a bill as well. You know, it's really not hard to see why companies hate hiring.
Rule #2 Companies are desperate and pathetic
Of course, as horrible as hiring can be for companies, it's
also an unavoidable necessity. The power
of the the old-boys-and-girls network is a product of how much companies
dislike the ordinary “over the counter” hiring process -- bringing aboard that
old friend or former colleague means skipping the most laborious and risky
parts of the hiring process, so naturally it's popular with firms in a hurry.
We all know how this can stack the deck against folks without pre-existing
industry connections.
Luckily for outsiders – and unfortunately for the potential employers -- no company can fill every vacancy from the ranks of old friends and former co-workers. Companies with vacancies are constantly aware that every day an opening stays vacant is a day lost – possibly many days, if the empty seat is an important one. So any would-be employer will eventually turn to the public forums – the websites and recruiting firms (and, *ahem,* magazines – ed.) where job-seekers congregate.
It's tough for the companies, because it involves them in all the unpleasant stuff we sketched out just a moment ago. But it's good news for the hopeful job seeker -- by the time a job posting hits the boards, the company is absolutely committed to filling that empty chair.
Luckily for outsiders – and unfortunately for the potential employers -- no company can fill every vacancy from the ranks of old friends and former co-workers. Companies with vacancies are constantly aware that every day an opening stays vacant is a day lost – possibly many days, if the empty seat is an important one. So any would-be employer will eventually turn to the public forums – the websites and recruiting firms (and, *ahem,* magazines – ed.) where job-seekers congregate.
It's tough for the companies, because it involves them in all the unpleasant stuff we sketched out just a moment ago. But it's good news for the hopeful job seeker -- by the time a job posting hits the boards, the company is absolutely committed to filling that empty chair.
Living by the rules
Now, we all know that it's not just the companies that
suffer. Job-hunting (particularly when you're out of work) is a morass of
frustration and uncertainty. It can be easy to be overwhelmed by the injustice
of it all – you know how good you are, how passionate you are about games, and
what great work you can do. The nonsense generated by the process – the resumes
and cover letters, the formula interview questions, and of course the dreaded issue
of prior experience – eventually starts to seem like a vast shadowy
conspiracy directed right at you. “I know I can do the job!” you want to
scream, “Let's just cut to the chase!”
The sad truth, though, is that the miseries inflicted on job
seekers aren't just random cruelties. They are logical byproducts of the
dilemmas faced by potential employers. Companies hate hiring, and at the same
time companies are desperate for people.
The rituals of the job hunt have evolved out of these conflicting
demands. Figuring out why employers set you to jumping a particular set of
hoops is a very powerful tool to help you upgrade your own job application.
It's also a good way to keep yourself focused when the emotional drain of job
hunting gets you down.
Mysteries of the cover letter
Let's look at an example of how seeing the process from the
perspective of employers can make take some of the mystery – and misery – out
of job hunting. Take the tradition of the cover letter as an example. It's easy
for a job hunter to see the cover letter as a meaningless formality. After all,
a dab hand at writing cover letters is hardly a guarantee of artistic or
technical skill. The ability to search
for the name of the last company you applied to and replace it with the name of
the next hardly betokens artistic brilliance or technical savvy. Moreover
artists aren't famous for their verbal skills to begin with. Don't forget either that many talented folks
don't speak English as a first language.
So why do we bother with cover letters? Or, more to the point, why do the companies bother? After all if you think cover letters are a meaningless formality, just imagine what the HR person on the other end goes through, sorting through them by the dozen.
So why do we bother with cover letters? Or, more to the point, why do the companies bother? After all if you think cover letters are a meaningless formality, just imagine what the HR person on the other end goes through, sorting through them by the dozen.
In fact, the cover letter makes sense when seen against the
backdrop of the two basic rules of hiring. The screener or HR person or
producer who reads your application is trapped between two conflicting desires:
the desire to find a great candidate, and the desire to dispose of all the no-hope
candidates as efficiently as possible.
The cover letter helps the screener in both directions.
On the positive side, a good cover letter lets the screener
know that you've actually paid some attention to the requirements of the job at
hand.
It may sound strange, but proving that you have read the ad is suprisingly important matter. Until you've been tasked with screening resumes yourself, you'll literally cannot believe how few applicants pay attention to the carefully thought out, diplomatically worded prose that describe a job on offer. Place an ad for a concept artist, and 30-40% of the respondents will be modelers or level designer or animators. Post a job requiring two shipped titles, and half the respondents will be in their senior year at art school.
Against this backdrop, a decent cover letter which clearly indicates that you've read and understood the job requirements automatically tells the screener that you're in the top half of the incoming wave of resumes. Even if your cover letter simply explains why you'd be good for the job even though you don't meet the formal criteria you'll probably get a free pass on the first cut. The key thing is to tell the screener simply and clearly that you know what job is on offer and that you have the professional chops and personal drive to do that job.
It may sound strange, but proving that you have read the ad is suprisingly important matter. Until you've been tasked with screening resumes yourself, you'll literally cannot believe how few applicants pay attention to the carefully thought out, diplomatically worded prose that describe a job on offer. Place an ad for a concept artist, and 30-40% of the respondents will be modelers or level designer or animators. Post a job requiring two shipped titles, and half the respondents will be in their senior year at art school.
Against this backdrop, a decent cover letter which clearly indicates that you've read and understood the job requirements automatically tells the screener that you're in the top half of the incoming wave of resumes. Even if your cover letter simply explains why you'd be good for the job even though you don't meet the formal criteria you'll probably get a free pass on the first cut. The key thing is to tell the screener simply and clearly that you know what job is on offer and that you have the professional chops and personal drive to do that job.
Which brings us, of course, to second reason cover letters
exist.
Remember our rule #1 – companies hate hiring. No matter how badly a company wants to fill that slot, most of the applicants for the slot will be completely unqualified. The employer wants to get to to the handful of good candidates quickly -- and that means dispensing with the bad ones as fast as possible. In this context, a cover letter offers a chance to fail – in other words, a fast way for the screener to find weak candidates. If you can't put together two reasonable paragraphs that tell the screener (a) you've read the ad and (b) you'd make a good match for the job on offer, your application is headed for the circular file. It's not strictly true that a bad cover letter will sink you right away, because most screeners are conscientious enough (and desperate enough to find that mythical good candidate) that they'll still check out the resume and reel. But make no mistake, if you blow the cover letter stage of the process you're set up for elimination in the next round – anything else is a comeback.
Remember our rule #1 – companies hate hiring. No matter how badly a company wants to fill that slot, most of the applicants for the slot will be completely unqualified. The employer wants to get to to the handful of good candidates quickly -- and that means dispensing with the bad ones as fast as possible. In this context, a cover letter offers a chance to fail – in other words, a fast way for the screener to find weak candidates. If you can't put together two reasonable paragraphs that tell the screener (a) you've read the ad and (b) you'd make a good match for the job on offer, your application is headed for the circular file. It's not strictly true that a bad cover letter will sink you right away, because most screeners are conscientious enough (and desperate enough to find that mythical good candidate) that they'll still check out the resume and reel. But make no mistake, if you blow the cover letter stage of the process you're set up for elimination in the next round – anything else is a comeback.
The Awful Truth
“Wait a minute,” you're thinking, “did he really mean to say
that a stupid cover letter means more than my demos?”
Well, no.
No artist gets a job with a terrible reel and a great cover letter. But don't let that fact make you forget that rule #1 is lurking in the background at every stage of your job application. The person sorting and screening job applications – no matter how nice and open minded they are – spends most of their time looking for reasons not to spend time with a given resume or reel. To put it more succinctly, most of the screener's job is figuring out who not to hire.
Against this context the cover letter, and then the resume, and finally and most ultimately the demo, are all up against a pretty stark test – it's always sudden death overtime in the hiring business. It's not because any rational person thinks a cover letter, or even a well formatted resume in .doc format, is an indication of artistic talent. It's because there are a lot of resumes and not much time, so every niggling detail can become an excuse to move on to the next item in the in-box.
Well, no.
No artist gets a job with a terrible reel and a great cover letter. But don't let that fact make you forget that rule #1 is lurking in the background at every stage of your job application. The person sorting and screening job applications – no matter how nice and open minded they are – spends most of their time looking for reasons not to spend time with a given resume or reel. To put it more succinctly, most of the screener's job is figuring out who not to hire.
Against this context the cover letter, and then the resume, and finally and most ultimately the demo, are all up against a pretty stark test – it's always sudden death overtime in the hiring business. It's not because any rational person thinks a cover letter, or even a well formatted resume in .doc format, is an indication of artistic talent. It's because there are a lot of resumes and not much time, so every niggling detail can become an excuse to move on to the next item in the in-box.
If this sounds brutal, that's because it is. But it's purely
impersonal – it's a fact of life that can be managed if you understand it and
work with it. Above all, nobody is exempt, not even the greatest talents among
us. Next month we'll finish off our discussion of job hunting by looking at how
resumes and demo reels function in the hiring process. In the meantime, polish
up those cover letters. And please, read the damn ads!
Wednesday, July 30, 2014
Rob G's Maya Python book is finally out
I see Rob Galanakis's new book is finally available for realz on Amazon.
I've added a few new books to the Techart bookstore page too!
I've added a few new books to the Techart bookstore page too!
Sunday, July 27, 2014
Pythonistas need Pythonista!
If you consider yourself a Pythonista, you've probably been frustrated by the difficulty involved in getting to work in Python on iOS devices. I just stumbled upon a really cool answer to your prayers in the form of Pythonista. It's not brand new - it looks like it came out last year - but I just found out about it and flipped my proverbial wig.
Pythonista is a sandboxed Python 2.7 development environment for iOS. It borrows a page from the playbook of earlier sandboxes like Codea. and manages to skirt Apple's rules for what you can do on the device while still allowing plenty of power. It includes a script editor (a pretty slick one for iOS, by the way) ,an interactive environment, and a bunch of libraries to make development really useful. Among the 'batteries' included are heavy hitters like pil, numpy and matplotlib, along with a few cool little things like a text-to-speech module and tools for dealing with the iOS console.
The most impressive inclusions are the scene and ui modules: custom modules devoted to iOS drawing and UI. Ironically, it's easier to develop a GUI application on your iPad using Pythonista than it is to do it on a desktop machine - the app even comes with a UI builder tool similar to QT's interface builder (not nearly as deep or complex, of course, but iOS UI is less complex than desktop). You can read multiple touches. You can even do hardware accelerate drawing - nice for things like a finger-sketching program. Since Pythonista includes pil, you can even do stuff like image processing:
Pythonista's main limitation is that it's not possible to add external modules to the library in the usual ways: setuptools and pip aren't available. You can manually install pure-python modules by copy-paste-save, and there are few installation tools floating around on the web such as pipista and Pypi. (As an aside: here's a handy collection of Pythonista snippets and links). Modules with binary dependencies -- such as the perforce api -- are off-limits; I'm not sure it it would be possible to use .pyd's that were properly compiled for iOS or if the security sandbox won't allow arbitrary binary code at all.
All in all, it's pretty cool stuff for any Pythonerd. My big project right now is a touch based inteface on the iPad to control a BrickPi Mindstorms robot, but at some point I think an asset-database / issue tracker client on the iPad would be a handy tool for our production team . Pretty cool for $6.99!
Pythonista's main limitation is that it's not possible to add external modules to the library in the usual ways: setuptools and pip aren't available. You can manually install pure-python modules by copy-paste-save, and there are few installation tools floating around on the web such as pipista and Pypi. (As an aside: here's a handy collection of Pythonista snippets and links). Modules with binary dependencies -- such as the perforce api -- are off-limits; I'm not sure it it would be possible to use .pyd's that were properly compiled for iOS or if the security sandbox won't allow arbitrary binary code at all.
All in all, it's pretty cool stuff for any Pythonerd. My big project right now is a touch based inteface on the iPad to control a BrickPi Mindstorms robot, but at some point I think an asset-database / issue tracker client on the iPad would be a handy tool for our production team . Pretty cool for $6.99!
Friday, July 11, 2014
Pipeline book out in Japan (!)
I just heard that the Production Pipeline book is out in Japan as of today. Wowsers!
This is actually my second time being published in Japan. I also contributed a chapter to 97 Things Every Game Creator Should Know. But still. Japan. I mean...
PS: The books is still up for sale on Amazon: And it's also on the Tech Art Bookstore page.
This is actually my second time being published in Japan. I also contributed a chapter to 97 Things Every Game Creator Should Know. But still. Japan. I mean...
PS: The books is still up for sale on Amazon: And it's also on the Tech Art Bookstore page.
Wednesday, July 9, 2014
Handy link: Python string format cookbook
If you're like me and addicted to using the old-school percent-symbol based string format, the newer bracket-based formatting is probably a bit mysterious. The python docs certainly don't help, they seem to be written for C programmers on meth (a help document entitled 'string format specification mini language' does not scream 'usability' to me, at any rate).
So, many props to Marcus Kazmierczak for his handy Python String Formatting Cookbook page. It's already saved me a ton of profanity. Here's to elevating the discourse of the internet!
So, many props to Marcus Kazmierczak for his handy Python String Formatting Cookbook page. It's already saved me a ton of profanity. Here's to elevating the discourse of the internet!
Saturday, July 5, 2014
Save The Environment 2: I am the .Egg Man
In my last, bumper-sticker-laden post I offered to share a little bit about the way I pack up my tools for users. This time I'll try to actually describe the process.
After a all the buildup, I wish I could make this sound more high tech and impressive. Basically, I just pack up what I have and send it all out to my users in a big ol' zip file. The zip ends up on their Maya's PYTHONPATH, and gives them exactly the same stuff I had when I created the zip. That's kind of it. It's basically a simplified version of a python egg; however since I'm distributing an entire ecosystem in one shot I've opted to do the packaging myself instead of relying on setuptools and all of its complex dependency management arrangements.
Simple as it is, this system has saved me a huge amount of time and energy over the last few years. It's been a long time since I've had to worry about a mysterious import failure or the wrong version of a module. Simplicity and robustness are very important, especially in the foundation of a pipeline. Of course, they don't always make for the most engaging blog posts But I'll do what I can, even if it means resorting to some pretty lame egg puns.
After a all the buildup, I wish I could make this sound more high tech and impressive. Basically, I just pack up what I have and send it all out to my users in a big ol' zip file. The zip ends up on their Maya's PYTHONPATH, and gives them exactly the same stuff I had when I created the zip. That's kind of it. It's basically a simplified version of a python egg; however since I'm distributing an entire ecosystem in one shot I've opted to do the packaging myself instead of relying on setuptools and all of its complex dependency management arrangements.
Simple as it is, this system has saved me a huge amount of time and energy over the last few years. It's been a long time since I've had to worry about a mysterious import failure or the wrong version of a module. Simplicity and robustness are very important, especially in the foundation of a pipeline. Of course, they don't always make for the most engaging blog posts But I'll do what I can, even if it means resorting to some pretty lame egg puns.
Thursday, June 26, 2014
Binary bonanza!
Here's a very complete source for a variety of python package binary distributions, covering a pretty broad variety of platforms, python versions and bit depths. A good page to bookmark next time your scrounging for a rare extension module you can't compile.
Tuesday, June 24, 2014
mGui updates
For anybody whos been following the mGui Maya GUI construction kit posts, I've added a few fixes and tweaks to the GitHub project in the last couple of weeks:
There are two classes in the module; ProgressBar is the generic version and MainProgressBar always points at Maya's main progress bar. Both classes have start(), update() and end() methods instead of Maya's clunky cmds.progressBar(name, e=True, beginProgress=1) and so on. They also both have an iter method, which will loop over a generator expression and update the progress bar for each yield then pass along the value. This allows simple idioms like:
So you can update the progress bar without unduly intertwining the GUI update and the program logic.
As with all the mGui Event classes, you can add multiple handlers to single event:
The module also includes named subclasses to simplify setup. That way you can do things like:
which is a bit nicer and less typo prone if you use an autocompleting IDE.
mGui.progress
The progress module wraps Maya's progressBar command for mGui style coding of progress bars.There are two classes in the module; ProgressBar is the generic version and MainProgressBar always points at Maya's main progress bar. Both classes have start(), update() and end() methods instead of Maya's clunky cmds.progressBar(name, e=True, beginProgress=1) and so on. They also both have an iter method, which will loop over a generator expression and update the progress bar for each yield then pass along the value. This allows simple idioms like:
from mGui.progress import MainProgressBar
import os
def list_files(dir):
# pretend this is as long, slow function...
for item in os.listdir(dir):
yield item
pb = MainProgressBar()
for each_file in pb.iter(list_files()):
print each_file.upper()
# here's where you do something with the results
So you can update the progress bar without unduly intertwining the GUI update and the program logic.
mGui.menu_loader
The menu_loader module will create menus from a YAML data file. It does a little bit of introspection to figure out how to create the items and attach their handlers to functions. This makes it easy to set up a menu with several items from a single setup routine.
The menu data is a text-based YAML file that looks like this:
!MMenu
key: UndeadLabs
label: Undead Labs
items:
- !MMenuItem
key: About
label: About Undead Labs...
annotation: "About this UndeadLabs tool setup"
command: tools.common.aboutDialog.open
- !MMenuItem
key: RemoteDebugger
label: Remote Debugger
annotation: Start / Stop remote python debugger
command: tools.common.remoteDebug.remote_debugger_dialog
And loading the menu is as simple as:
import mGui.menu_loader as loader
loader.load_menu('path/to/undeadlabs.YAML')
mGui.scriptJobs
The scriptJobs module adapts the event model for use with scriptJobs. A ScriptJobEvent is a derivative of Event which allows you to hook multiple handlers to a single scriptjob (in the same way that the other Event classes allow for multicast delegates):from mGui.scriptJobs import * def print_selected (*args, **kwargs): print cmds.ls(sl=True) or [] sj = ScriptJobEvent("e", "SelectionChanged") sj += print_selected
As with all the mGui Event classes, you can add multiple handlers to single event:
sj += some_other_function()
The module also includes named subclasses to simplify setup. That way you can do things like:
closing_sj = RecentCommandChanged() closing_sj += close_handler
which is a bit nicer and less typo prone if you use an autocompleting IDE.
Friday, June 20, 2014
From the annals of bug subtlety
... comes an object lesson in why it's nice to have a debugger.
I'm porting a biggish python codebase to support multiple OSs and maya versions. As I move things around I try to use the opportunity to shore up test coverage. And it feels like the most boring chore imaginable, until something like this crops up.
I've got an exporter framework that I share between projects and files, and when I was moving from 2011- only to multiple version. It's important code, so it's tested - but the test is really dumb: it creates a test scene, imports the test framework, and calls one function. But running the tests in 2014 never works - even though I can manually execute the exact steps in a regular copy of maya and all is well.
So I threw it under the debugger -- PyCharm FTW! -- and started stepping through. No dice, everything seemed OK but still the test failed: it could not find my test objects. Finally, in desperation, I started stepping though the test and issuing an ls() after every step... and I found that the break wasn't caused by running code - it was caused by importing my module. I didn't call it - just imported it. WTF?
It turns out that importing PyMel was wiping my test scene in 2014! The tests all run under maya.standalone, and the bug only shows up there, which is why just doing it by hand in maya wasn't showing the same symptoms.
Here's my repro case:
import maya.cmds as cmds
cmds.polyCube()
# [u'pCube1', u'polyCube1']
cmds.ls(type='transform')
# [u'front', u'pCube1', u'persp', u'side', u'top']
import pymel.core as pm
cmds.ls(type='transform')
# [u'front', u'persp', u'side', u'top']
This is a 100% repro in maya.standalone - but not in GUI maya, where the bug does not occur.Is this true for everybody else ? The workaround is to import pymel earlier so that the destruction doesn't affect anything important.
But... ouch!
Saturday, June 14, 2014
Friday, June 6, 2014
Chromosaurus!
This 1985 short from Pacific Data Images (the X chromosome in the DNA of Dreamworks Animation) was extremely popular in the late 80's / early 90's CG compilation VHS market, frequently accompanied by trippy electronica.
Of special note: this is well before the the popularization of our mesh skinning techniques: all the moving parts are jointed, and the ripples in the tail are all damped sine waves instead of skeletal animation. I'm pretty sure these aren't raytraced, it looks to me like a tweaked reflection map.
One of the saddest generation gap experiences of my life is trying to explain to my 14 year old son, who's never known a world without casually photoreal CG, why this set a generation of nerdly hearts afire. At least he appreciated the soundtrack.
Of special note: this is well before the the popularization of our mesh skinning techniques: all the moving parts are jointed, and the ripples in the tail are all damped sine waves instead of skeletal animation. I'm pretty sure these aren't raytraced, it looks to me like a tweaked reflection map.
One of the saddest generation gap experiences of my life is trying to explain to my 14 year old son, who's never known a world without casually photoreal CG, why this set a generation of nerdly hearts afire. At least he appreciated the soundtrack.
Tuesday, June 3, 2014
Save the environment!
+Rob Galanakis posted this on Google+, and as I started to reply I realized this would be a useful thing to put out to a wider audience. Or maybe useful isn't the right word - it's just something that bugs me so I want to bloviate about it.
Hey +Cory Mogk , +Brad Clark, +Steve Theodore , and whoever else in #techart . Has anyone figured out a viable model for reusing Python libraries in Maya, like other Python applications with pip/virtualenv/requirements.txt do? Is there a conversation anywhere?The short answer to Rob's question is : I've tried to go this route, but I couldn't get a virtualenv-based approach to match for my needs. For the long answer, read on...
Friday, May 30, 2014
Channeling Gerry Anderson
Utterly off topic. And brilliant. This guy is totally channeling Gerry Anderson and Derek Meddings of Thunderbirds fame - not only is the home model animation work pretty cool, it's a pitch-perfect reproduction of the original aesthetic right down to the music choices and ponderous VO.
Mr. Greg Martin, I salute you!
Mr. Greg Martin, I salute you!
Tuesday, May 27, 2014
Your weekly moment of Python-is-awesome
I stumbled across a cool little idea while working on a refactor of my python tools build system, and although it is not really ready for prime-time it's fun enough I had to share. With a little bit of work you can load Python modules directly over the web via http! How cool is that?
Details & code after the jump
Details & code after the jump
Sunday, May 25, 2014
Maya binaries list
+Rob Galanakis just posted a handy list of Maya binaries for people who need the correct version of QT or Perforce to go with their Maya versions. Great one-stop shop for navigating this annoying maze....
Sunday, May 18, 2014
TA Bookstore page
I've started a page with links to books that TA's would find interesting or useful. Let me know if you've got recommendations - right now there's a glaring lack of Max related books. I've been skipping over "How to do X in Max 2011" type titles so far since they get dated so quickly (I've got probably 200 lbs of utterly useless reference on versions of Maya and Max that wouldn't even run on Windows 7 stuffed into my basement). But I'm always interested to hear about useful reference stuff.
The TA Bookstore page
The TA Bookstore page
Friday, May 16, 2014
Subjective truth
This video is proof that perceived reality is socially constructed.
It will delight and inspire all the artists and designers on your team...
Unified Particle Physics for Real-Time Applications - SIGGRAPH 2014 from Miles Macklin on Vimeo.
... and make the engineers very, very nervous...
Unified Particle Physics for Real-Time Applications - SIGGRAPH 2014 from Miles Macklin on Vimeo.
... and make the engineers very, very nervous...
Thursday, May 15, 2014
Rob G's book is up for preorder on amazon
I just noticed that Rob G's upcoming book about Maya Python programmingis up for pre-order on Amazon! Looking forward to seeing it in "print"! |
Tuesday, May 13, 2014
No soup for you, userSetup.py
When I start working on isolating maya environments, I came across a nice bit of trivia I didn't know about.
If you ever want to run a Maya without its userSetup.py and without having to move or rename files, it turns out you can suppress userSetups by setting an environment variable called MAYA_SKIP_USERSETUP_PY to any value that evaluates as True. This is handy for testing and isolating path management problems - if you've got a rogue path and you're not sure where it's coming from, this is an easy way to make sure it's not being added in by the userSetup.
PS: If you're using a MayaPyManager to run mayapy instances, you can set this variable like so:
If you ever want to run a Maya without its userSetup.py and without having to move or rename files, it turns out you can suppress userSetups by setting an environment variable called MAYA_SKIP_USERSETUP_PY to any value that evaluates as True. This is handy for testing and isolating path management problems - if you've got a rogue path and you're not sure where it's coming from, this is an easy way to make sure it's not being added in by the userSetup.
PS: If you're using a MayaPyManager to run mayapy instances, you can set this variable like so:
from mayaPyManager import MayaPyManager
import os
env = os.environ.copy()
env['MAYA_SKIP_USERSETUP_PY'] = '1'
mgr = MayaPyManager('path/to/mayapy.exe', env, 'path/to/maya/scripts')
# this manager will use only the user provided path
# and won't run the userSetup.py on startup
Sunday, May 11, 2014
Multiple MayaPy Management Mania
Lately I've been re-factoring the build system I use to distribute my python tools to users. The things which has been driving me crazy is the need to start supporting multiple versions of Maya at the same time.
Besides the general hassle involved, supporting multiple Maya versions and multiple projects at the same time is a nightmare for doing good testing and QA. With so many different configurations it becomes increasingly easy for something to slip through the cracks. You might have a bit of Python 2.7 syntax which you wrote in Maya 2014 sneaking into a tool used in Maya 2011. You might have tools that rely on an external dll that is correctly set up in your Maya 2011 tools but not in the outsourcer version of your 2012 setup.... The possibilities for shooting yourself in the foot are endless.
So, in an effort to clean this up, I've cooked up a simple module designed to create and run instances of MayaPy.exe with precise control over the paths and environment variables. You can use it to run tests or automatic processes in isolation, knowing that only the paths and settings you're using will be live.
The actual code is not super complex - it's up on gitHub, as usual free-to-use under the MIT license. Comments / questions/ feedback and especially bug fixes all welcome! Code also here after the jump
Besides the general hassle involved, supporting multiple Maya versions and multiple projects at the same time is a nightmare for doing good testing and QA. With so many different configurations it becomes increasingly easy for something to slip through the cracks. You might have a bit of Python 2.7 syntax which you wrote in Maya 2014 sneaking into a tool used in Maya 2011. You might have tools that rely on an external dll that is correctly set up in your Maya 2011 tools but not in the outsourcer version of your 2012 setup.... The possibilities for shooting yourself in the foot are endless.
So, in an effort to clean this up, I've cooked up a simple module designed to create and run instances of MayaPy.exe with precise control over the paths and environment variables. You can use it to run tests or automatic processes in isolation, knowing that only the paths and settings you're using will be live.
The actual code is not super complex - it's up on gitHub, as usual free-to-use under the MIT license. Comments / questions/ feedback and especially bug fixes all welcome! Code also here after the jump
Friday, May 2, 2014
Tuesday, April 29, 2014
The Main Event - event oriented programming in Maya
The Maya Callbacks Cheat Sheet post started out as an effort to explain the design the event system in mGui - but it quickly turned into it's own thing as I realized that the vanilla Maya system remains confusing to lots of people. With that background out of the way, I want to return to events proper, both to explain why they work the way the do in mGui and also how they can be useful for other projects as well (I use them all over the place in non-GUI contexts).
...and, because it's got the word 'event' in it, I'm going to throw in a lot of irrelevant references as I can manage to The Crushah!
...and, because it's got the word 'event' in it, I'm going to throw in a lot of irrelevant references as I can manage to The Crushah!
Wednesday, April 23, 2014
Maya callbacks cheat sheet
Update 5/7/14: Added a note on closures and lambdas
In All Your Base Classes, I suggested that we can do better than the standard callback mechanism for doing Maya event handling. The limitations of the default method are something I've complained about before, and if you follow these things on TAO or CGTalk or StackOverflow it seems pretty clear that a lot of other people have problems with the standard Maya code flow too.
I was planning on devoting the next big post to the event mechanism in mGui . However as I did the spadework for this post I decided it was better to split it up into two parts, since a lot of folks seem to be confused about the right way to manage basic Maya callbacks. Before moving fancy stuff, it's a good idea to make sure the basics are clear. Most vets will already know most of what I'm going over here, but I found the time spent laying it out for myself a useful exercise so I figured it would be worth sharing even if it's not revolutionary.
In All Your Base Classes, I suggested that we can do better than the standard callback mechanism for doing Maya event handling. The limitations of the default method are something I've complained about before, and if you follow these things on TAO or CGTalk or StackOverflow it seems pretty clear that a lot of other people have problems with the standard Maya code flow too.
I was planning on devoting the next big post to the event mechanism in mGui . However as I did the spadework for this post I decided it was better to split it up into two parts, since a lot of folks seem to be confused about the right way to manage basic Maya callbacks. Before moving fancy stuff, it's a good idea to make sure the basics are clear. Most vets will already know most of what I'm going over here, but I found the time spent laying it out for myself a useful exercise so I figured it would be worth sharing even if it's not revolutionary.
Saturday, April 19, 2014
Classic CG: La plus ca change
From SIGGRAPH 1992.
While I'd hesitate to call this a 'classic' it does prove one thing: tacky me-too crap is eternal Hands up if you remember palette animations! (For that matter, walkers and canes up if you remember the local-cable ads this thing was parodying!)
While I'd hesitate to call this a 'classic' it does prove one thing: tacky me-too crap is eternal Hands up if you remember palette animations! (For that matter, walkers and canes up if you remember the local-cable ads this thing was parodying!)
Thursday, April 17, 2014
Roger Roger
If you've been playing with the stansdaloneRPC server, I've added a new branch to the github project that includes a minimal level of password security. It's still not the kind of security you want if this is to be exposed to the wicked world, but it should suffice to keep you free from teammates who want to prank you.
Comments / bug reports and pull request welcome! If you use the github wiki to log an issue or ask a question, it's a good idea to put a comment here to make sure I see it.
Comments / bug reports and pull request welcome! If you use the github wiki to log an issue or ask a question, it's a good idea to put a comment here to make sure I see it.
Monday, April 14, 2014
Sweet Sumotori Dreams
I had no idea that the genius behind Sumotori Dreams is still making awesome procedural animation toys.
If you're not familiar with Sumotori Dreams, it's the funniest thing that ever happened to procedual animation. Proof here (loud cackling and some profanity in the audio track, could not find any that did not have lots of hilarity and shouting):
If you're at all interested in procedural animation - or have even a tiny sliver of a sense of humor - you should buy the iPhone app the android app, or the PC version. This guys deserves our support!
On a related note, if you like this you may find this talk from the developer of Overgrowth interesting as well.
If you're not familiar with Sumotori Dreams, it's the funniest thing that ever happened to procedual animation. Proof here (loud cackling and some profanity in the audio track, could not find any that did not have lots of hilarity and shouting):
If you're at all interested in procedural animation - or have even a tiny sliver of a sense of humor - you should buy the iPhone app the android app, or the PC version. This guys deserves our support!
On a related note, if you like this you may find this talk from the developer of Overgrowth interesting as well.
Saturday, April 12, 2014
Warning: Garish graphics ahead!
If you're tired of boring old light-grey-on-dark-grey text, you'l'l be pleased to know that the Maya text widget actually supports a surprising amount of HTML markup. Which means that instead of this:
You set peoples eyeballs on fire like this:
You set peoples eyeballs on fire like this:
This is a single cmds.text object with it's label property set to an HTML string. |
Tuesday, April 8, 2014
Mighty Morphin Module Manager Made Moreso
I've added a port of the Maya Module Manager I posted a while back to the examples included with the mGui maya GUI library. This was an exercise to see how much the neater and more concise I could make it using the library.
Here's some interesting stats:
The original version was 237 lines of code, not counting the header comments. The mGui version was 178 without the header, so about 25% shorter overall. There are about 80 lines of unchanged, purely behind-the-scenes code which didn't change between versions, so the real savings is more like 45%. Plus, the original sample included some functions for formLayout wrangling so real savings might be a little higher for more old-fashioned code.
Like I said last time, the mGui package is still evolving so it's still very much in a "use at your own risk" state right now... That said, I'd love to get comments, feedback and suggestions.
Here's some interesting stats:
The original version was 237 lines of code, not counting the header comments. The mGui version was 178 without the header, so about 25% shorter overall. There are about 80 lines of unchanged, purely behind-the-scenes code which didn't change between versions, so the real savings is more like 45%. Plus, the original sample included some functions for formLayout wrangling so real savings might be a little higher for more old-fashioned code.
Like I said last time, the mGui package is still evolving so it's still very much in a "use at your own risk" state right now... That said, I'd love to get comments, feedback and suggestions.
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:
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:
For failed queries, the result includes the exception and a traceback string:
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!
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!
Friday, April 4, 2014
Classic (?) CG: Bingo the Clown
From the Classic CG files comes Bingo the Clown. This was originally created to showcase the capabilities of Maya 1.0, back in 1998. It creeped me out then and it creeps me out now.
I've been told, I don't know how correctly, that Chris Landreth - the animator who did this film - was the driving force between Maya's decision to use Euler angles for everything. I hope that's not true. Having this video and those goddamn Euler angles on your conscience is a lot to answer for.
I've been told, I don't know how correctly, that Chris Landreth - the animator who did this film - was the driving force between Maya's decision to use Euler angles for everything. I hope that's not true. Having this video and those goddamn Euler angles on your conscience is a lot to answer for.
Friday, March 28, 2014
New(-ish) Tools Watch: 3D Coat 4.1
Version 4.1 of 3DCoat just came out - details in the video above or at 3D-Coat.com.
3DCoat is an interesting voxel-based alternative to Zbrush. It deserves a lot of credit for really pushing the retopology business forward, and also for early PTex support. The interface is less idiosyncratic than Zbrush's but less by-the-numbers than Mudbox.
Plus, it's made in Kiev - buy a copy and stand up for Ukrainian independence!
Some cool feature videos if you're curious:
UV tools
Curve-constrained sculpts
Ptex texturing
Maya GUI II: All Your Base Classes Are Belong To Us
Updated 4/11/2015: fixing code samples that had gone missing thanks to Blogger templates....
In Rescuing Maya GUI From Itself I talked in some detail about how to use descriptors and metaclasses to create a wrapper for the Maya GUI toolkit that, er, sucks less than the default implementation. I also strove mightily to include a lot of more or less irrelevant references to Thunderbirds. This time out I want to detail what a working implementation of the ideas I sketched out there looks like.
I think this time the irrelevant thematic gloss will come from All Your Base Are Belong To Us jokes. Because (a), we’re talking about base classes, (b) what could be more retro and 90’s than Maya’s GUI system, and (c) For Great Justice, Make All Zig!
I’ve put my current stab at a comprehensive implementation up on Github, in the form of the mGui project , where you can poke at it to your heart’s content. The whole project is there, and it’s all free to use under the MIT, ‘do-what-thou-wilt-but-keep-the-copyright-notice’ license. Enjoy! I should warn you, though, that this is still W.I.P code, and is evolving all the time! Use it at your own risk – things may change a lot before its really ‘ready’.
What we’re shooting for is a library that provides all of Maya;’s GUI widgets in a clean, pythonic way without making anybody learn too much new stuff. If all goes well, the result is a cleaned up and more efficient version of things most of us already know. You can also treat this an template for how you might want to to wrap other aspects of Maya – say, rendering or rigging – in cleaner code.
From last time, we know we can wrap a Maya GUI component in a class which uses descriptors to make conventional property access work. The main thing we’re going to be delivering in this installment is a slew of classes that have the right property descriptors to replicate the Maya GUI toolkit. We’ll be using the metaclass system we showed earlier to populate the classes (if none of this makes sense, you probably want to hop back to the previous blog entry before following along).
To keep things simple and minimize the boilerplate, we’ll want to derive all of our concrete classes – the widgets and layouts and so on – from a single base. This helps the code simple and ensure that the features we add work the same way for the whole library. We’ll add a second class later to handle some details specific to layouts, but that will derive from the base class.
Before we look at the details of the base class, we should think a little more about the properties. In the last installment, we treated all properties the same way - as generic wrappers around maya.cmds. In a production setting, though, we want to distinguish between 3 types of properties:
If you have experimented a little with last time’s code, you may already have seen that it works just as well for callbacks and commands as for other properties. So you may wonder why we should bother to treat callbacks differently. What’s the point?
There are two main reasons this is a useful complication.
First, and most usefully, event delegates make it easier to add your callbacks after you lay out your GUI, rather than forcing you to interleave your code logic with the process of building forms and layouts. De-coupling the functional code form the graphic display makes for more readable and more maintainable code. It also makes it possible for you to reuse fairly generic layouts with different back ends. In pseodo-code:
as opposed to
Keeping the functional bits separate makes it easy to, say, split the purely visual layout details into a separate file, but more importantly makes it clear whats an administrative detail and what’s actual functionality.
On a second, more tactical level the proxies also allow you to attach more than one function to a callback. It’s pretty common, for example, that you the act of want selecting an item in a list to select object in the Maya scene, but also to enable some relevant controls and maybe check with a database or talk to source control. Using an event proxy lets you handle those different tasks in three separate functions instead of one big monster that mixes up lots of UI feedback and other concerns.
If you’re familiar with QT you’ll rexognize that event delegates are basically QT “Signals”
So that’s why the extra complexity is worth it.
The actual workings of the proxy class are documented in the events.py file in the Github project; I’ll get back to how those work in a future post. Details aside, they key takeaway for right now is that this setup helps us move towards GUI code that’s more declarative. That’s the other reason why ‘button.label = “Reset”’ is better than cmds.Button(self.activeButton, e=True, l=’Reset’ – it’s not just less typing, it’s real value comes from treating the GUI layout as data rather than code,. That means you can concentrate on the actual work of your tools rather than the fiddly details of highlighting buttons or whatever.
Last but not least - by standardizing on the event mechanism we have an easy way to standardize the information that comes with the callback events for very little extra works. So, for example, all of the callbacks include a dictionary of keyword arguments when they fire - and the dictionary includes a reference to the widget that fired the event. That way it’s easy to write a generic event handler and not have to manually bind the firing control to a callback function.
While we’re on the topic of de-coupling: Wouldn’t it be nice to separate out the details of the visuals (“what color is that button?”) from the structure of the forms and layouts?. Spoiler alert! This is a topic for a future post – but the curious might want to check out styles.py in the GitHub
.
So, we’ve covered our improved property descriptors, and now it’s time to set up our base class.
This is a great opportunity to do some plumbing for more efficient coding. However it’s also a temptation – when the desire to sneak everything under the sun into your base classes is a recipe for monster code and untraceable bugs. This design should be as simple as we can make it.
Still, there are a couple of things that it would be nice to put into the base class - they are all very general (as befits base-class functions) and they are all common to any GUI tasks.
In conventional maya coding the names are critical, since they are your only way of contacting the GUI after it’s built. They are also unpredictable, because of Maya’s habit of renaming items to give them unique path names. Luckily for us we don’t need to rely on the widget names from Maya, since we’re managing the GUI items under the hood inside our wrappers. This gets us off the hook for creating and managing variables to capture the results of every GUI command under the sun.
That said, names are still useful in a big complex system. So, to make it really clear how to find one of our wrappers inside a GUI layout it makes sense to ask for an explicit name passed in as the first argument - that way it’s totally clear what the control is intended to be. There are, of course, plenty of control you don’t really care about once they’re made: help text, spaces, separators and so on. To avoid making users have to invent names for those guys, we should let users pass in 0 or False or None as a succinct way of saying “I don’t care about the name of this thing”.
One minor note: I used Key as the name of the property so my IDE did not bug me for using in the Python reserved word ‘id’. Little things matter :)
Speaking of little things: there are some great tools in the Python language to make classes more efficient to work with. The so called ‘magic methods’ allow you to customize the behavior of your classes, both to make them feel more Pythonic and to express your intentions more clearly. Here are a couple of the things we can do with the magic methods in our base class:
Speaking of that pass-in-zero-to-skip-names gimmick, one simple but super-useful thing we can do is to implement the
test. In our case, we know that all Maya GUI controls have the exist flag, and therefore all of our GUI classes will too. So, if our
As a minor tweak, the
The next magic method we want to add is
Now, a single GUI object obviously is not iterable. A layout like columnLayout, on the other hand, can be iterated since it has child controls. By implementing
So with all those methods added the base Control class looks like this:
You’ll notice that it is inheriting from two classes we have not touched on,
Despite my rather verbose way of describing it all, this is not a lot of code. Which is what exactly you want in a base class: simple, common functionality, not rocket science. Hopefully, though, adding those pythonic behaviors will save a lot of waste verbiage in production work.
That’s way of making sure that we can store references to our control wrappers in our layout wrappers - that is, when you create a wrapped button inside a wrapped columnLayout, the columnLayout has a handle to the wrapper class for the button. Which brings us around neatly to the wrapper class for layouts - called… wait for it… Layout.
To support nesting, we want our Layout wrapper class to be a context manager. The idea is that you when you start a Layout, it declares itself the active layer and all GUI controls that get created add themselves to it; when you’re done with it control is return to whatever Layout was active before. As Doctor Who says of bow ties, “Context Managers are cool.”
If you’ve done a lot of Maya GUI you know it’s also nice to have the same functionality for menus as well. So, to avoid repeating ourselves let’s start by creating a generic version of Control that works as a context manager so we can get identical functionality in windows, layouts and menus. Then we can inherit it into a wrapper class for layouts and another for windows and voila, they are all context managers without cutting and pasting. Here’s the abstract base class for all ‘nested’ classes: menus, windows, layouts etc:
All that really does is pop the current
Here’s the concrete implementation for actual Layout classes:
This is just a regular mGui class (it gets all of the metaclass behavior from
While we’re messing with contexts, this is also a great opportunity to do what PyMel already does and make all layouts automatically manage UI parenting. This gets rid of all those irritating calls to setParent(“..”), and lets us write GUI code that looks like real Python and not a plate of spaghetti. Compare this wordy cmds example:
To this version using context manager layouts:
That example also includes one other neat way to leverage contexts too. If you double check the
There’s one little extra bit of magic in there to let the add method discriminate between children you care about and those you don’t. If your child controls have no key set, they won’t be added to the
Here’s a snippet tacked on to the end of that last sample showing how you can use the iterability of the layouts to set properties in bulk. You can see how the work of turning command-style access into property style access, combined with the extra clarity we get from context managers, really pays off:
I don’t even want to think about the equivalent code in
One parting note about the naming scheme, It does have one, inevitable drawback: it means that the child wrappers have unique names inside a given context. Not much we can do about that. They can, however, have the same name under different parents - the example above has , gui.t_buttons.grid.mk_sphere, and gui.g_buttons.grid.mk_sphere Thats a useful thing to exploit if you want to, say, find all of the ‘Select’ buttons on a form and disable them or something off that sort.
In an ideal world we’d have a way of reflecting over some kind of assembly information and extracting all of the maya GUI commands with their flags and options. Of course, in an ideal world we would not have to do this in the first place, since the native GUI system would not be the unfortunate SNES-era mishmash that it is.
Mass production is a pain in the ass.
Luckily, the TA spirit cannot be kept down by adversity. In this case we don’t have a nice clean api but we do have MEL.... poor, neglected, wallflower MEL. Well, here’s a chance for the wallflower to save the party: MEL’s help command can list all of the commands and all of the flags in Maya. So, what we need to do is to run through all of the Mel commands in help, find the ones that look like GUI commands, and capture their command - flag combinations as raw material for our metaclass control factory.
See? This was getting all programmery, but now we’re back in familiar TA spit-and-bailing-wire territory. Comfier?
The actual code to build the wrappers isn’t particularly interesting (its here if you want to see it). In two sentences: Use the mel
We generate two files, one for controls and one for layouts (that’s an arbitrary design call on my part, you could of course have one file). Now they’re just sitting on disk as if we’d written them by hand. We can import our newly generated modules and away we go, with nice pythonic properties and our new functions.
There is one judgement call here that is worth mentioning in passing.
The logic in the helper modules which generate this is all deterministic, it doesn’t need human intervention so it could actually be run at module load time rather than being run and dumped out to a file. For what I want to do, I felt that physical files were a better choice, because they allow the option of hand tailoring the wrapper classes as the project evolves. Plus, the startup cost of trolling through every MEL command, while it’s not very big, is real and it seems good to avoid it. I’ve have heard enough grumbling over the years about PyMel’s startup speed that I thought it wisest to opt for speed and clarity over fewer files on disk.
One nice side effect of generating our wrappers this way: we’ve added some functionality through our base classes but fundamentally we’ve kept the same names and options we already know from plain old maya.cmds. The only changes are the mandatory names and the fact that I’ve capitalized the class names to make them more pep-8-friendly.
Hopefully, keeps the learning curve short for new user. Its hard enough to pick up a new style, making you memorize hundreds of new property names seem like a big tax on users.
In the version up on Github (and in this example) I opted to use only the long name for the properties. This is definitely a matter of taste; I’m sure that many TAs out there are sufficiently familiar with the old maya command flags that a howler like
If you are of the opposite opinion, though, you can call the
However… Simpler syntax is just scratching the surface of what we can get up to now that we have a proper class library for our widgets. Next time out we’ll look at the event mechanism in more detail and talk about how to cleanly separate your functional code, GUI layouts, and the display styles of your widgets.
Until next time....
In Rescuing Maya GUI From Itself I talked in some detail about how to use descriptors and metaclasses to create a wrapper for the Maya GUI toolkit that, er, sucks less than the default implementation. I also strove mightily to include a lot of more or less irrelevant references to Thunderbirds. This time out I want to detail what a working implementation of the ideas I sketched out there looks like.
I think this time the irrelevant thematic gloss will come from All Your Base Are Belong To Us jokes. Because (a), we’re talking about base classes, (b) what could be more retro and 90’s than Maya’s GUI system, and (c) For Great Justice, Make All Zig!
I’ve put my current stab at a comprehensive implementation up on Github, in the form of the mGui project , where you can poke at it to your heart’s content. The whole project is there, and it’s all free to use under the MIT, ‘do-what-thou-wilt-but-keep-the-copyright-notice’ license. Enjoy! I should warn you, though, that this is still W.I.P code, and is evolving all the time! Use it at your own risk – things may change a lot before its really ‘ready’.
All Your Properties Are Belong To Our Base Class
What we’re shooting for is a library that provides all of Maya;’s GUI widgets in a clean, pythonic way without making anybody learn too much new stuff. If all goes well, the result is a cleaned up and more efficient version of things most of us already know. You can also treat this an template for how you might want to to wrap other aspects of Maya – say, rendering or rigging – in cleaner code.
From last time, we know we can wrap a Maya GUI component in a class which uses descriptors to make conventional property access work. The main thing we’re going to be delivering in this installment is a slew of classes that have the right property descriptors to replicate the Maya GUI toolkit. We’ll be using the metaclass system we showed earlier to populate the classes (if none of this makes sense, you probably want to hop back to the previous blog entry before following along).
To keep things simple and minimize the boilerplate, we’ll want to derive all of our concrete classes – the widgets and layouts and so on – from a single base. This helps the code simple and ensure that the features we add work the same way for the whole library. We’ll add a second class later to handle some details specific to layouts, but that will derive from the base class.
Before we look at the details of the base class, we should think a little more about the properties. In the last installment, we treated all properties the same way - as generic wrappers around maya.cmds. In a production setting, though, we want to distinguish between 3 types of properties:
- Regular properties
- These are just wrapped accesses to commands, like we demonstrated last week. They use the same ControlProperty class as we used last time to call commands on our GUI widgets.
- Read-only properties
- A small number of Maya GUI commands are read-only. It would be nice and more pythonic to make sure that these behave appropriately. So, ControlProperty has been tweaked with a flag that allows it to operate as a read-only property; otherwise it’s just the same descriptor we talked about last time out. ]
- Callbacks
- This one is a bit more involved. I’ve already complained about the weaknesses of the event model in Maya GUI. Cleaning it up starts with knowing which properties are callback properties and treating them accordingly.
Somebody Set Us Up The Bomb!
Before getting into the nitty-gritty of our overall widget class, I want to make a side noted about the special properties used for the callbacks. These CallbackProperty descriptors are slightly different from the familiar ControlProperty. Their job is to de-couple the maya GUI widget from the commands it fires. They create special delegate objects which will intercept callbacks fired by our GUI objects.If you have experimented a little with last time’s code, you may already have seen that it works just as well for callbacks and commands as for other properties. So you may wonder why we should bother to treat callbacks differently. What’s the point?
There are two main reasons this is a useful complication.
First, and most usefully, event delegates make it easier to add your callbacks after you lay out your GUI, rather than forcing you to interleave your code logic with the process of building forms and layouts. De-coupling the functional code form the graphic display makes for more readable and more maintainable code. It also makes it possible for you to reuse fairly generic layouts with different back ends. In pseodo-code:
Layout Layout ButtonA ButtonB Layout ButtonC ListA ButtonA deletes selected item from ListA ButtonB renames selected item from ListA ButtonC adds new item to ListA
as opposed to
Layout Layout ButtonA. I'm going to delete something from the list when it gets made ButtonB I'm going to rename something in the list when it gets made Layout ButtonC I'm going to add something to the list when it gets made ListA
On a second, more tactical level the proxies also allow you to attach more than one function to a callback. It’s pretty common, for example, that you the act of want selecting an item in a list to select object in the Maya scene, but also to enable some relevant controls and maybe check with a database or talk to source control. Using an event proxy lets you handle those different tasks in three separate functions instead of one big monster that mixes up lots of UI feedback and other concerns.
If you’re familiar with QT you’ll rexognize that event delegates are basically QT “Signals”
So that’s why the extra complexity is worth it.
The actual workings of the proxy class are documented in the events.py file in the Github project; I’ll get back to how those work in a future post. Details aside, they key takeaway for right now is that this setup helps us move towards GUI code that’s more declarative. That’s the other reason why ‘button.label = “Reset”’ is better than cmds.Button(self.activeButton, e=True, l=’Reset’ – it’s not just less typing, it’s real value comes from treating the GUI layout as data rather than code,. That means you can concentrate on the actual work of your tools rather than the fiddly details of highlighting buttons or whatever.
Last but not least - by standardizing on the event mechanism we have an easy way to standardize the information that comes with the callback events for very little extra works. So, for example, all of the callbacks include a dictionary of keyword arguments when they fire - and the dictionary includes a reference to the widget that fired the event. That way it’s easy to write a generic event handler and not have to manually bind the firing control to a callback function.
While we’re on the topic of de-coupling: Wouldn’t it be nice to separate out the details of the visuals (“what color is that button?”) from the structure of the forms and layouts?. Spoiler alert! This is a topic for a future post – but the curious might want to check out styles.py in the GitHub
Think ahead
How the hell are you going to explain THAT to your grandchildren? The obvious lession is THINK AHEAD |
.
So, we’ve covered our improved property descriptors, and now it’s time to set up our base class.
This is a great opportunity to do some plumbing for more efficient coding. However it’s also a temptation – when the desire to sneak everything under the sun into your base classes is a recipe for monster code and untraceable bugs. This design should be as simple as we can make it.
Still, there are a couple of things that it would be nice to put into the base class - they are all very general (as befits base-class functions) and they are all common to any GUI tasks.
Tags
In most GUI systems, you can attach any arbitrary data you want to a widget. For example, you might want to have an array of buttons that all did the same thing with slightly different values, say moving an object by different amounts. In Maya you have to encapsulate the data into your command call:. With a tag attached to the buttons, on the other hand, you can write a script that just says ‘move the target by the amount in this button’s tag’, which is much easier to maintain and more flexible. And as we just pointed out, the event mechanism always sends a reference to the control which owns an event when it fires, so it’s easy to get to the right Tag when you want it.A real name
Having explicit names for your pieces is very handy, particularly in large, deeply nested systems like a GUI..In conventional maya coding the names are critical, since they are your only way of contacting the GUI after it’s built. They are also unpredictable, because of Maya’s habit of renaming items to give them unique path names. Luckily for us we don’t need to rely on the widget names from Maya, since we’re managing the GUI items under the hood inside our wrappers. This gets us off the hook for creating and managing variables to capture the results of every GUI command under the sun.
That said, names are still useful in a big complex system. So, to make it really clear how to find one of our wrappers inside a GUI layout it makes sense to ask for an explicit name passed in as the first argument - that way it’s totally clear what the control is intended to be. There are, of course, plenty of control you don’t really care about once they’re made: help text, spaces, separators and so on. To avoid making users have to invent names for those guys, we should let users pass in 0 or False or None as a succinct way of saying “I don’t care about the name of this thing”.
One minor note: I used Key as the name of the property so my IDE did not bug me for using in the Python reserved word ‘id’. Little things matter :)
Speaking of little things: there are some great tools in the Python language to make classes more efficient to work with. The so called ‘magic methods’ allow you to customize the behavior of your classes, both to make them feel more Pythonic and to express your intentions more clearly. Here are a couple of the things we can do with the magic methods in our base class:
__nonzero__
Speaking of that pass-in-zero-to-skip-names gimmick, one simple but super-useful thing we can do is to implement the __nonzero__
method. That’s what Python calls when you try the familiarif something: doSomething()
test. In our case, we know that all Maya GUI controls have the exist flag, and therefore all of our GUI classes will too. So, if our
__nonzero__
just returns the exist property of our class instances, we can elegantly check for things like dead controls with a simple, pythonic if test.
__repr__
__repr__
is what Python calls when you need a printable representation of an object. In our case, we can pass back our underlying Maya GUI object, which is just a GUI path string. This way, you can pass one of our wrapper classes to some other python code that works on GUI objects and it will ‘just work’ – This is more or less what PyMel does for nodes, and it’s a great help when integrating a new module into an existing codebase. Like PyMel’s version there will be some odd corner cases that don’t work but it’s a handy convenience most of the time.As a minor tweak, the
__repr__
is also tweaked to display differently when the GUI widget inside a wrapper class has been deleted. This won’t prevent errors if you try to use the widget, but it is a big help in parsing error messages or stack traces.
__iter__
The next magic method we want to add is __iter__
. It is the what python calls when you try to loop over a list or a tuple.Now, a single GUI object obviously is not iterable. A layout like columnLayout, on the other hand, can be iterated since it has child controls. By implementing
__iter__
here and then over-riding it when we tackle layouts, we can iterate over both layouts and their children in a single call. This makes it easy to look for layout children :for child in mainlayout: if child.Key == 'cancel': #.... etc
class Control(Styled, BindableObject): ''' Base class for all mGui controls. Provides the necessary frameworks for CtlProperty and CallbackProperty access to the underlying widget. NOTE this is not exactly identical to the code on github - more advanced stuff is removed to make the progression clearer ''' CMD = cmds.control _ATTRIBS = ['annotation', 'backgroundColor', 'defineTemplate', 'docTag', 'enable', 'enableBackground', 'exists', 'fullPathName', 'height', 'manage', 'noBackground', 'numberOfPopupMenus', 'parent', 'popupMenuArray', 'preventOverride', 'useTemplate', 'visible', 'visibleChangeCommand', 'width'] _CALLBACKS = ['dragCallback', 'dropCallback', 'visibleChangeCommand'] _READ_ONLY = ['isObscured', 'popupMenuArray', 'numberOfPopupMenus'] __metaclass__ = ControlMeta def __init__(self, key, *args, **kwargs): self.Key = key self.Widget = self.CMD(*args, **_style) ''' Widget is the gui element in the scene ''' self.Callbacks = {} ''' A dictionary of Event objects ''' Layout.add_current(self) def register_callback(self, callbackName, event): ''' when a callback property is first accessed this creates an Event for the specified callback and hooks it to the gui widget's callback function ''' kwargs = {'e':True, callbackName:event} self.CMD(self.Widget, **kwargs) def __nonzero__(self): return self.exists def __repr__(self): if self: return self.Widget else: return "<deleted UI element %s>" % self.__class__ def __str__(self): return self.Widget def __iter__(self): yield self
Styled
and BindableObject
. Those don’t interact with what we’re doing here - they’ll come up in a later post. You can pretend it just says ‘object’. If you’re reading the code carefully you’ll probably spot a little bit of code I haven’t described. register_callback
is there to support event proxies – we’ll talk about the details when we get to event proxies in the future.Despite my rather verbose way of describing it all, this is not a lot of code. Which is what exactly you want in a base class: simple, common functionality, not rocket science. Hopefully, though, adding those pythonic behaviors will save a lot of waste verbiage in production work.
Damn, the internet has a lot of time on its hands |
All Your Children Are Belong To Parent Layout
There’s one little bit of plumbing in Control that is worth calling out:Layout.add_current(self)
To support nesting, we want our Layout wrapper class to be a context manager. The idea is that you when you start a Layout, it declares itself the active layer and all GUI controls that get created add themselves to it; when you’re done with it control is return to whatever Layout was active before. As Doctor Who says of bow ties, “Context Managers are cool.”
If you’ve done a lot of Maya GUI you know it’s also nice to have the same functionality for menus as well. So, to avoid repeating ourselves let’s start by creating a generic version of Control that works as a context manager so we can get identical functionality in windows, layouts and menus. Then we can inherit it into a wrapper class for layouts and another for windows and voila, they are all context managers without cutting and pasting. Here’s the abstract base class for all ‘nested’ classes: menus, windows, layouts etc:
class Nested(Control): ''' Base class for all the nested context-manager classes which automatically parent themselves ''' ACTIVE_LAYOUT = None def __init__(self, key, *args, **kwargs): self.Controls = [] super(Nested, self).__init__(key, *args, **kwargs) def __enter__(self): self.__cache_layout = Nested.ACTIVE_LAYOUT Nested.ACTIVE_LAYOUT = self return self def __exit__(self, typ, value, traceback): self.layout() Nested.ACTIVE_LAYOUT = self.__cache_layout self.__cache_layout = None cmds.setParent("..") def layout(self): ''' this is called at the end of a context, it can be used to (for example) perform attachments in a formLayout. Override in derived classes for different behaviors. ''' return len(self.Controls) def add(self, control): path_difference = control.Widget[len(self.Widget):].count('|') - 1 if not path_difference: self.Controls.append(control) if control.Key and not control.Key[0] == "_": if control.Key in self.__dict__: raise RuntimeError('Children of a layout must have unique IDs') self.__dict__[control.Key] = control def remove(self, control): self.Controls.remove(control) k = [k for k, v in self.__dict__.items() if v == control] if k: del self.__dict__[k[0]] def __iter__(self): for item in self.Controls: for sub in item: yield sub yield self @classmethod def add_current(cls, control): if cls.ACTIVE_LAYOUT: Nested.ACTIVE_LAYOUT.add(control)
Nested
onto a stack and make it possible for other controls to add themselves to the instance on top of the stack.Here’s the concrete implementation for actual Layout classes:
class Layout(Nested): CMD = cmds.layout _ATTRIBS = ['annotation', 'backgroundColor', 'defineTemplate', 'docTag', 'dragCallback', 'dropCallback', 'enable', 'enableBackground', 'exists', 'fullPathName', 'height', 'manage', 'noBackground', 'numberOfPopupMenus', 'parent', 'popupMenuArray', 'preventOverride', 'useTemplate', 'visible', 'visibleChangeCommand', 'width'] _CALLBACKS = ['dragCallback', 'dropCallback', 'visibleChangeCommand'] _READ_ONLY = ['isObscured', 'popupMenuArray', 'numberOfPopupMenus', 'childArray', 'numberOfChildren']
Control
, via Nested
) with added properties for common layout properties like numberOfChildren
.While we’re messing with contexts, this is also a great opportunity to do what PyMel already does and make all layouts automatically manage UI parenting. This gets rid of all those irritating calls to setParent(“..”), and lets us write GUI code that looks like real Python and not a plate of spaghetti. Compare this wordy cmds example:
win = window('main window', title="Ugly version") columnLayout('gui', width = 256) frameLayout("t_buttons", label = "buttons column") columnLayout("col") sphere_1 = button('mkSphere', label = "Make Sphere", c = make_sphere) cone_1 = button('mkCone', label ="Make Cone", c = make_cone) cube_1 = button('mkCube', label ="Make Cube", c = make_cube) setParen("..") setParent("..") frameLayout("r_buttons", label = "buttons row") rowLayout ("row", numberOfColumns=3) sphere_2 = button('mkSphere', label = "Make Sphere", c = make_sphere) cone_2 = button('mkCone', label ="Make Cone", c = make_cone) cube_2 = utton('mkCube', label ="Make Cube", c = make_cube) setParen("..") setParent("..") frameLayout("g_buttons", label = "buttons grid") gridLayout("grid", numberOfColumns = 2): sphere_3 = button('mkSphere', label = "Make Sphere", c = make_sphere ) cone_3 = button('mkCone', label ="Make Cone", c = make_cone) cube_3 = button('mkCube', label ="Make Cube", c = make_cube) circle_btn = button('mkCircle', label = "Make Circle", c = make_circle) setParen("..") setParent("..") setParent("..") showWindow(win)
from mGui.gui import * # note the caps: all of these are wrapper objects, not maya.cmds! window = Window('main window', title="How's this") with ColumnLayout('gui', width = 256) as gui: with FrameLayout("t_buttons", label = "buttons column"): with ColumnLayout("col"): Button('mkSphere', label = "Make Sphere") Button('mkCone', label ="Make Cone") Button('mkCube', label ="Make Cube") with FrameLayout("r_buttons", label = "buttons row"): with RowLayout ("row", numberOfColumns=3): Button('mkSphere', label = "Make Sphere") Button('mkCone', label ="Make Cone") Button('mkCube', label ="Make Cube") with FrameLayout("g_buttons", label = "buttons grid"): with GridLayout("grid", numberOfColumns = 2): Button('mkSphere', label = "Make Sphere") Button('mkCone', label ="Make Cone") Button('mkCube', label ="Make Cube") Button('mkCircle', label = "Make Circle")
add
method in Nested
you’ll see that it adds child wrapper objects to it’s own __dict__
. That makes them accessible without having to explicitly store them. In this example, you could get to the last sphere-making button in this example as gui.g_buttons.grid.mk_sphere
without having to manually capture the name of the underlying widgets they way the first example must. Since Maya GUI is always a single-rooted hierarchy, as long as you know the first parent of a window or panel you can always get to any of its the child layouts or controls. This saves a lot of the boring boilerplate you would otherwise need to do just keeping track of bits and pieces.There’s one little extra bit of magic in there to let the add method discriminate between children you care about and those you don’t. If your child controls have no key set, they won’t be added to the
__dict__
. On a related note, you can also be tricksy and add a control which is not a direct child of the layout - for example, if you had a layout with a list of widgets in a scrollLayout, you don’t usually don’t care about the scrollbar - it’s just along for the ride. So you can add the widgets directly to the ‘real’ parent layout and keep the paths nice and trim. The goal, after all, is to make the gui layout a logical tree you can work with efficiently. There’s a practical example of this trick in the lists.py file on GithubHere’s a snippet tacked on to the end of that last sample showing how you can use the iterability of the layouts to set properties in bulk. You can see how the work of turning command-style access into property style access, combined with the extra clarity we get from context managers, really pays off:
# using the iterability of the layout to set widths for item in gui.t_buttons: item.width = 256 for item in gui.r_buttons.row: item.width = 85 item.width = 256 # the last item is gui.r_buttons.row itself item.columnWidth3 = (85,85,85) # ditto for item in gui.g_buttons.grid: item.width = 128 item.width = 256 # now the last item is the grid item.cellWidth = 128 cmds.showWindow(window)
I don’t even want to think about the equivalent code in
cmds
!One parting note about the naming scheme, It does have one, inevitable drawback: it means that the child wrappers have unique names inside a given context. Not much we can do about that. They can, however, have the same name under different parents - the example above has , gui.t_buttons.grid.mk_sphere, and gui.g_buttons.grid.mk_sphere Thats a useful thing to exploit if you want to, say, find all of the ‘Select’ buttons on a form and disable them or something off that sort.
Make All Zig!
Hopefully, the combination of some syntax sugar in our wrappers and turning layouts into context managers will make Maya GUI layout less of a pain in the butt. However, we still need to actually crank out all the wrappers for all those scores of classes in the maya GUI library. Descriptors and metaclasses are powerful tools, but few of us have the intestinal fortitude to plow through the dozens of classes in the Maya GUI library getting every flag and command correct.In an ideal world we’d have a way of reflecting over some kind of assembly information and extracting all of the maya GUI commands with their flags and options. Of course, in an ideal world we would not have to do this in the first place, since the native GUI system would not be the unfortunate SNES-era mishmash that it is.
Mass production is a pain in the ass.
Luckily, the TA spirit cannot be kept down by adversity. In this case we don’t have a nice clean api but we do have MEL.... poor, neglected, wallflower MEL. Well, here’s a chance for the wallflower to save the party: MEL’s help command can list all of the commands and all of the flags in Maya. So, what we need to do is to run through all of the Mel commands in help, find the ones that look like GUI commands, and capture their command - flag combinations as raw material for our metaclass control factory.
See? This was getting all programmery, but now we’re back in familiar TA spit-and-bailing-wire territory. Comfier?
The actual code to build the wrappers isn’t particularly interesting (its here if you want to see it). In two sentences: Use the mel
help *
command to find all of the commands in Maya which share flags with cmds.control or cmds.layout. Then collect their flags to make the list of class attributes that the metaclass uses to create property descriptors. The final output will be a big ol’ string of class definitions like this:class FloatSlider(Control): '''sample output from mGui.helpers.tools.generate_commands()''' CMD = cmds.floatSlider _ATTRIBS = ['horizontal','step','maxValue','value','minValue'] _CALLBACKS = ['changeCommand','dragCommand']
There is one judgement call here that is worth mentioning in passing.
The logic in the helper modules which generate this is all deterministic, it doesn’t need human intervention so it could actually be run at module load time rather than being run and dumped out to a file. For what I want to do, I felt that physical files were a better choice, because they allow the option of hand tailoring the wrapper classes as the project evolves. Plus, the startup cost of trolling through every MEL command, while it’s not very big, is real and it seems good to avoid it. I’ve have heard enough grumbling over the years about PyMel’s startup speed that I thought it wisest to opt for speed and clarity over fewer files on disk.
One nice side effect of generating our wrappers this way: we’ve added some functionality through our base classes but fundamentally we’ve kept the same names and options we already know from plain old maya.cmds. The only changes are the mandatory names and the fact that I’ve capitalized the class names to make them more pep-8-friendly.
Hopefully, keeps the learning curve short for new user. Its hard enough to pick up a new style, making you memorize hundreds of new property names seem like a big tax on users.
In the version up on Github (and in this example) I opted to use only the long name for the properties. This is definitely a matter of taste; I’m sure that many TAs out there are sufficiently familiar with the old maya command flags that a howler like
cmds.rowLayout(nc=2, cw2=(50,100), ct2=('both', 5), bgc = (.8,.6,.6), cl2=("left", "right")
makes its meaning clear. for my part, though, the long names clarify the intent of the code enormously if you make a fairly small upfront investment in typing. If you are of the opposite opinion, though, you can call the
generate_helpers
and generate_controls
functions in mGui.helpers.tools with includeShortNames
set to true make your own wrappers with the short flags too.What You Say!!!
Now we’ve got a complete library of all the widgets. You can see the results in controls.py and layouts.py on GitHub. (The base classes are also up there for your perusal in the root of the core module). If all you want is to stop writing long commands every time you touch a GUI item, you’re done. You can write crisper layout code, tweak your properties, and so on with what we’ve covered so far. If you’re interested in making practical use of this setup – remember that WIP warning! – you should read the docs in the events.py module to make sure you know how to hook up callback events. I’ll cover that in more detail in the future.However… Simpler syntax is just scratching the surface of what we can get up to now that we have a proper class library for our widgets. Next time out we’ll look at the event mechanism in more detail and talk about how to cleanly separate your functional code, GUI layouts, and the display styles of your widgets.
Until next time....
Subscribe to:
Posts (Atom)