Archive for the 'tutorials' Category

crazy use for python metaclasses

Saturday, December 19th, 2009

i’m not a programmer, so things that might be common place to the average programmer are new and exciting for me. when python was integrated into maya, I got the chance to see what a real programming language looked like, and its been super fun discovering all these amazing concepts, which leads me to metaclasses.

browsing around on the nets it seems metaclasses aren’t generally considered practical, so maybe i’m just using em wrong, but i’ve used them on a few occasions since i discovered them, and for what i consider to be super practical things.

for example, the rigging tools I’ve written at work use them.  the rigging tools define a collection of parts. these parts know how to manage themselves, from building to aligning to rigging. in code these parts are described as classes. now writing things like GUIs to leverage the functionality of the parts I wanted to make the GUI dynamic, so that I wouldn’t ever have to maintain it after the initial writing – I wanted it to just know what parts had been defined, and display UI for those parts as appropriate.

so this is where metaclasses come in handy.  normally when you define a class what you’re doing is creating an instance of a type object. remember classes in python are objects, and a class object is an instance of the type class. so by writing your own metaclass you can “hook” into the creation of class objects. in this case I wanted to keep track of subclasses of the base class as they got defined, without having to remember to register the classes somewhere. now technically this can be done in a variety of different ways, but this is by far the most elegant and simple solution that i’ve come up with.

so on to the code?

allClasses = []
class TrackableClass(type):
    def __new__( cls, name, bases, members ):
        newCls = type.__new__( cls, name, bases, members )
        allClasses.append( newCls )
        return newCls

class BaseClass(object):
    __metaclass__ = TrackableClass
    @classmethod
    def GetSubclasses( cls ):
        return [ c for c in allClasses if issubclass( c, cls ) ]

now you can call BaseClass.GetSubclasses() and it will return a list of all classes that inherit from BaseClass. this is interesting, but not super useful, because you have to re-define the above code everywhere you want to use it. so what we can do is wrap up the above code and build the metaclass and the GetSubclass method procedurally.

def trackableClassFactory( superClass=object ):
	'''
	returns a class that tracks subclasses.  for example, if you had classB(classA)
	ad you wanted to track subclasses, you could do this:

	class classB(trackableClassFactory( classA )): pass

	a classmethod called GetSubclasses is created in the returned class for
	querying the list of subclasses
	'''
	subclassList = []
	class TrackableType(type):
		def __new__( cls, name, bases, attrs ):
			new = type.__new__( cls, name, bases, attrs )
			subclassList.append( new )

			return new

	class TrackableClass(superClass): __metaclass__ = TrackableType
	def GetSubclasses( cls ):
		'''
		returns a list of subclasses
		'''
		toReturn = []
		for c in subclassList:
			if c is cls: continue
			if issubclass( c, cls ):
				toReturn.append( c )

		return toReturn
	def GetNamedSubclass( cls, name ):
		'''
		returns the first subclass found with the given name
		'''
		for c in cls.GetSubclasses():
			if c.__name__ == name: return c

	TrackableClass.GetSubclasses = classmethod( GetSubclasses )
	TrackableClass.GetNamedSubclass = classmethod( GetNamedSubclass )

	return TrackableClass

pretty simple hey. so now you can just insert the function call above into your class’s superclass definition like so:

class SomeClass(trackableClassFactory(SuperClass)): pass

and voila! you now have the GetSubclasses method available on the class, and can query all subclasses that have been defined! neato eh?

changing skeleton after binding

Saturday, December 5th, 2009

sometimes I just feel slow – I expect alot of you already know this, but its new to me – and super useful.  just say you’ve skinned up your dude and spent a bunch of time getting things looking sha-weet! and you suddenly realize that where you placed the clavicles is kinda dumb.

well of course, you could use maya’s move joint without affecting skin tool, but its a bit of a shit tool imho – its super clunky, you can’t do things like rotate freeze transform etc – it simply lets you move.  you could *also* use something like zooWeightSave to store weights, blow weights away, change skeleton reapply weights…  but gah!  thats more than one step!

anyhoo, so the following script will take the current pose of the skeleton and redefine it as the bind pose, and resets the skin.  nothing else is changed, so your weighting etc is untouched.  in fact the skin cluster doesn’t even need to be turned off.

def resetSkinCluster( skinCluster ):
	'''
	splats the current pose of the skeleton into the skinCluster - ie whatever
	the current pose is becomes the bindpose
	'''
	nInf = len( cmd.listConnections( '%s.matrix' % skinCluster, destination=False ) )
	for n in range( nInf ):
		try:
			slotNJoint = cmd.listConnections( '%s.matrix[ %d ]' % (skinCluster, n), destination=False )[ 0 ]
		except IndexError: continue

		matrixAsStr = ' '.join( map( str, cmd.getAttr( '%s.worldInverseMatrix' % slotNJoint ) ) )
		melStr = 'setAttr -type "matrix" %s.bindPreMatrix[ %d ] %s' % (skinCluster, n, matrixAsStr)
		#melStr = 'setAttr -type "matrix" %s.bindPose %s' % (slotNJoint, matrixAsStr)
		mel.eval( melStr )

		#reset the stored pose in any dagposes that are conn
		for dPose in cmd.listConnections( skinCluster, d=False, type='dagPose' ) or []:
			cmd.dagPose( slotNJoint, reset=True, n=dPose )

anyway, I actually use this as a step in our auto-rigger.  basically before a skeleton is auto-rigged, it gets checked to ensure alignment of everything is kosher – and if not it fixes things up and resets the bind pose.  so modelers and animators don’t have to worry about joint alignment, they just need to worry about placement (which is much easier to get right), and alignment is taken care of auto-magically.  placement can also be changed just as easily – because the rig is auto generated they can change the placement of a clavicle and hit auto-rig and the clav placement gets updated and the rig rebuilt to suit.

anyway – its an awesome time saver.  I plan to write functionality that will emulate maya’s crappy joint re-placement tool. but it’ll be state based: so turn skin off, change skel with any normal maya tools (freeze transforms as well) then re-enable skinning.

flushing python code

Friday, December 4th, 2009

so maybe I’m just slow, but I figured out how to get python to purge its previously imported modules today. this is super useful for developing any sort of python script that has dependencies that are also changing.

without flush you have to make sure you reload all the modules that are changing, and you usually need to do it in the right order – as code objects hold references to module objects. so if you have moduleA that imports moduleB, they both change but you reload moduleA and then moduleB – the freshly reloaded moduleA will still hold references to the old versions of moduleB. if you don’t realize this, you will most likely end up with less hair by the end of the day than you started with. noticeably less.

anyhoo – its super simple, which is why i feel like a retard for not having thought of it before. there are a couple of gotchas – binary modules don’t seem to flush, so you may need to put in special cases for them (i just remove any binary module names from the keysToDelete list before doing the removal).

and I guess the other gotcha is that this uses the filesystem module in the zooToolbox. there is no reason you can’t roll your own though.

def flush():
	'''
	flushes all loaded modules from sys.modules which causes them to be reloaded
	when next imported...  super useful for developing crap within a persistent
	python environment
	'''
	dirsToFlush = filesystem.Path( 'd:/tools' ), filesystem.Path( 'd:/studio/maya' )

	keysToDelete = []
	for modName, mod in sys.modules.iteritems():
		try:
			modPath = filesystem.Path( mod.__file__ )
		except AttributeError: continue

		for flushDir in dirsToFlush:
			if modPath.isUnder( flushDir ):
				keysToDelete.append( modName )
				break

	for keyToDelete in keysToDelete:
		del( sys.modules[ keyToDelete] )

	gc.collect()

UI in maya using python

Sunday, November 29th, 2009

UI is super important.  At least if you’re developing tools for anyone other than yourself.  And even then, its still a good idea to try and make your tools accessible because sometimes you don’t come back to a tool for months, or occasionally years.  And for the record, I’m not even nearly as good as I would like to be (or even should be) at building usable UIs.

Unfortunately maya is fighting you all the way in this regard.  Maya is, and lets not be too harsh here, is a piece of steaming turd when it comes to UI – it was great 10 years ago, but it hasn’t changed since…  The UI that you can build in mel/python is painfully limiting to use for anything but a bunch of buttons and checkboxes.  But unfortunately you have to either live with that, or go through the pain of learning and integrating qtpython/wxpython into your tools – but thats a different (and entirely valid) discussion.

Writing UI using mel is horrible – and pretty well understood because mel is so simplistic and straight forward.  Its horrible because its essentially impossible to write UI without hardcoding widget names which makes it impossible to modularize your UI into re-usable chunks, its difficult to store local data on a piece of UI.

But writing maya UI using python is a different story.  Python provides you with the ability to abstract the UI code so you can make it more modular, provide widgets and collections of widgets with their own local storage and methods etc.  By writing thin wrappers around the existing maya widgets you can “class-ify” them and make them work more like code should in an object oriented language.

Now, I’m pretty new to all this as well, so I’m certainly not claiming anything I say here is the best way to do things.  But these ideas have made my life easier and more productive, and more importantly its increased the functionality in the tools I’ve created.

Anyway the wrapper code I work from is here.

As you can see its not terribly complicated – in fact its painfully simple.  As you can see the UI classes inherit from the python built-in type “str” – they’re strings.  This is because everything in maya is a string, so by inheriting from str, we can pass the actual UI python objects to the maya commands which makes mixing and matching this sort of UI code with older UI code a snap.  But because we’re now working with actual objects, we now have local storage for the widget (whether it be a simple button, or a more complicated layout of widgets) plus we can write methods for these widgets.  So by doing things this way for example, you can write a “setVisibilty” method on the base class if you wanted to, and now every object has the same interface to hiding and unhiding itself.  So we now have local storage and polymorphism.

By using this sort of paradigm you can also extend classes for more complex UI.  As you can see here, this makes writing modular UI alot easier as well.  This is a great example of some UI that is just not possible to write in mel in a way that makes it this modular.  At least not where you can have multiple instances of the UI open at once.

So the mappingEditor example above – it is a generic widget that inherits from the formLayout class (so it knows how to lay itself out) but it adds a heap of other functionality related to mapping two lists to one another.  I’ve used this widget in a few tools now (only one of them public) – often many instances of them are on screen at once.  This just isn’t possible using mel.

Exception Handling in Maya

Tuesday, November 17th, 2009

As a tool writer python is awesome because it gives you proper control over your code.  You can create classes, inheritance, you no longer have to rely on nasty hacks for data storage in UIs etc…  Plus you get to cross into the API and mix and match functionality to some extent should you find that useful.

But there is one neat thing that non embedded python lets you do that is super useful – global exception handling.  In normal python you can roll your own exception handler so that when a piece of code throws an unhandled exception, the global exception handler catches it and tries to do something useful with it.  For me I usually try to display some generic failure message to the user, but then I usually get all the data I can from the exception and put it in an email and send that email to my address to help with the inevitable debugging that follows.

This is super useful to be able to do, and I do it all the time with my standalone python tools.  But in maya, not being able to write a global exception handler is a pain, because it means maya tool errors go unhandled (and sometimes even unreported) which means I’m left to try and repro failures from users myself.  At least, until I figured out this neat little trick.

So its kinda scary sounding, but hey, if I can understand it, anyone can.  It involves metaclasses.  What is a metaclass you ask?  Well, they’re basically the objects that create classes.  So just like a class is an object that creates instances of itself, metaclasses are classes that create instances of themselves.  Its crazy mind blowing (for a simple script monkey like me anyway) but it makes sense once you use them a few times.

Anyway, so basically what I did was create a metaclass that would wrap all methods of a class in an exception handling decorator.  In my case the exception handler grabs a heap of extra data from the exception like the callstack and variables sent to the functions in the callstack, and a few other things and sends an email to my work address (take a look if you’re interested, its the d_handleExceptions decorator in the exceptionsHandler script in the zooToolbox).  So now, whenever a tool throws an unhandled exception, I get a detailed mail about the problem which makes debugging a lot easier.  No longer do errors go unreported.

I haven’t released the code for the metaclass thingy, but I will.  But its fairly simple – do some searching for decorators and metaclasses in python.