::Maya UI Wrappers::

August 25th, 2010 by hamish download the zooToolBox

Ever since I first started writing python code in maya I’ve been slowly building up my own UI wrappers.  I’m certainly not the only one.  Chad has done it with pymel, Byron has done it with MRV, and I’m sure there are a bunch of others.  But of course, I wasn’t using pymel back then, nor did I know anything about MRV.

The good thing about my implementation however, is that its really small and self contained.  Its a single script.  Its fairly gratuitously commented, and there is a page with some tips, tricks and general high level discussion about it here.  It a nutshell it tries to make UI authoring more like writing UI code in WX or QT.

Anyway, for those interested take a look at it here.  Enjoy!

::Oh. Duh.::

August 20th, 2010 by hamish download the zooToolBox

On the new vs init thing…  Duh.  Multiple inheritance.  Separating new and init mean you can leverage more instance setup when doing multiple inheritance.  I guess I generally don’t use multiple inheritance…  But good to know.

[edit:] to be more explicit – new can only be called once because it is responsible for actual instance creation.  new does the actual object creation whereas init just takes the new instance and initializes it.  Initialization methods on multiple parent classes can both do complementary things on the same instance, so being able to split out initialization from construction makes a lot of things possible if you’re working with multiple parent classes.  Its not as useful with single inheritance – arguably it makes things more confusing.  But multiple inheritance its a godsend.

::python’s new and init::

August 11th, 2010 by hamish download the zooToolBox

I’ve never realized this before, but the **kw that gets passed to the __new__ method is NOT the same dictionary thats given to __init__.  Ie if you want to mess with the **kw dict in the __new__ method for some reason, you can’t because python gives __init__ a new copy of the original **kw dict passed in.

Seems kinda weird to me.  Any python gurus out there that know why this is?  I’ve never really understood why python has both a __new__ and an __init__.  Why not just ditch one of them and be done with it?

::auto complete in maya’s script editor::

August 10th, 2010 by hamish download the zooToolBox

I just discovered this.

I never realized how badly you could screw up an auto completer.  Do these people use their own software?!

::Kahn Academy FTW!::

August 3rd, 2010 by hamish download the zooToolBox

This is too awesome not to share – the Kahn Academy is some dude just spewing out heaps of video tutorials covering a wide variety of subjects.  Most interestingly to me at the moment, he covers a heap of linear algebra.

I took linear algebra in school, but it didn’t seem at all applicable to life at the time so its since been purged from my brain.  My lack of 3d math skills has always been one of my weaknesses, and I try to rectify that whenever the opportunity arises.  Anyway this guy has some videos that have helped, and I figured others would probably benefit as well.

::Finding Dependencies::

July 27th, 2010 by hamish download the zooToolBox

For some time now I’ve been almost the only one really writing python tools at work. But that has started to change recently.  So I’ve been becoming more and more concerned about the lack of formalization in the existing code base. There aren’t really any unit-tests, and there hasn’t been any way of querying what other tools depend on the one you’re changing.  Which of course means its easy to break things and not even know it.

So last week I decided to bite the bullet and learn what I could about these sorts of things.  I started off with the dependency problem because testing seemed useless without an understanding of what to test.

A quick google search pointed me to this page. Not quite what I was after, but it was a fantastic piece of code to learn just how all encompassing the python standard library is. To save you having to look at the code and trawl through it yourself, this is basically what it does.

In the python standard library is a module called moduleFinder. What it does is it takes a module, finds where it exists on disk, opens the file and reads it in. This is important, at this point it has not imported the code – ie the code has not been executed. It has simply loaded the file and read its contents into memory. Then it takes this and compiles it into a code object using the compile builtin function.

Now the next bit is cool – it takes the code object, which contains the bytecode for the python code that was just compiled, and iterates over all the instructions looking for various commands – most importantly import commands. So its pretty robust, because its using python to reverse engineer its own code and walk through the dependencies.

Because the code isn’t being executed you can query dependencies for maya tools without having to run the script from inside maya.  So if I make a change to say my vectors module (which is a standalone python module) the dependency query will still be able to list all the maya scripts that use this module.  This is obviously important if you’re in a studio where you have a significant portion of your python codebase outside maya.

Surprisingly this is super fast too, because the bytecode is literally just a stream of bytes. So its just integer comparisons when iterating over the bytecode. For the 320 scripts in one of our branches it takes 20 seconds to scan.

Now 20 seconds is still a drag, so I wrote a simple caching mechanism. The cache basically stores a crc for each file, and that file’s immediate downstream dependencies. When the cache is loaded all entries are checked to make sure they still exist on disk, and the crc’s are matched. Those that have changed get re-scanned and the cache is updated. With the caching, even a huge change that touches many files only takes a second or two to make a query against. Which is super cool.

Now all I have to do is figure out how to associate a set of unit tests with each script and then the dependency query could be made to run the downstream scripts potentially affected to ensure the change is sound. That would be cool.

So anyway, if you’re thinking about writing tools to query dependencies, do it – its really easy, and the link above gives you a great place to start.

::Winning The Perf War::

July 21st, 2010 by hamish download the zooToolBox

So one super convenient thing pymel gives you is a name independent way of tracking nodes in your code. So doing this:

from maya.cmds import *
o = group( em=True )
rename( o, 'something_else' )
select( o )

This will fail because the o variable is just the name of the group when it was originally created, and the name changes before the select command is run. Now obviously the above is a trivial case and can be easily solved, but sometimes its not so easy.

For example, if you have a group with a non-unique leaf name that is nested deeply in the hierarchy, just by re-parenting one of its parents, the node will change. Tracking this changed name is non-trivial, and is the sort of problem you run into every now and then when writing generalized tools for rigging, or just general scene construction.

Anyhoo, so poking around a bit I experimented with adding a few methods to MObject, kinda like this:

from maya.OpenMaya import *
def asMObject( otherMobject=None ):
	'''
	tries to cast the given obj to an mobject - it can be string
	'''
	if otherMobject is None:
		return MObject()

	if isinstance( otherMobject, basestring ):
		sel = MSelectionList()
		sel.add( otherMobject )

		tmp = MObject()
		sel.getDependNode( 0, tmp )
		#tmp.cast()

		return tmp

def partialPathName( self ):
	'''
	returns the partial name of the node
	'''
	if self.hasFn( MFn.kDagNode ):
		dagPath = MDagPath()
		MDagPath.getAPathTo( self, dagPath )

		return dagPath.partialPathName()

	return MFnDependencyNode( self ).name()

MObject.__str__ = partialPathName
MObject.__unicode__ = partialPathName
MObject.partialPathName = partialPathName
MObject.name = partialPathName

def _hash( self ):
	return MObjectHandle( self ).hashCode()

MObject.__hash__ = _hash

def cmpNodes( a, b ):
	'''
	compares two nodes and returns whether they're equivalent or not.
	the compare is done on MObjects not strings so passing the fullname
	and a partial name to the same dag will still return True
	'''
	if not isinstance( a, MObject ):
		a = asMObject( a )

	if not isinstance( b, MObject ):
		b = asMObject( b )

	return a.name() == b.name()

Now this is hardly rocket science here, but it does go along way toward making MObjects useful in just your average script, and more to the point it solves the problem discussed above.

How? Well, first off, its easy to cast a node name to an MObject with the asMObject function. More importantly, you can still pass the variable to a normal mel command. ie this works:

from maya.cmds import *
o = asMObject( group( em=True ) )
rename( o, 'something_else' )
select( o )

Plus you can compare two nodes without having to worry about whether one is a full path to the node or a partial path.

This code can be packaged up into a simple little module that you can import at will – simply by importing the script you get the functionality added to the MObject class, so all you need to do really is add this to the top of your scripts:

from apiExtensions import asMObject

And wham – you get the rest for free.

Casting to these MObjects is still slow – there’s no way around that. Thats just maya being a slut. But you can easily target which parts of your script to add this functionality to without having to change your entire code base to use something like pymel or MRV. Plus you can also pass this MObject to the api class – again without having to wrap anything.

I’m smelling win.

Thoughts?

::Not to be repetitive::

July 20th, 2010 by hamish download the zooToolBox

But pymel really is horribly slow. Perhaps I’m doing something wrong – although if I am I’m pretty sure I’m not alone. But I just spent a few hours tearing it out of my rigging tool. There is still a long way to go, but all of a sudden the tool is unbelievably faster. Like to the point where I no longer have to spend time trying to optimize the tool.

So be warned people! Pymel might make for more readable code, but there is a huge cost. HUGE.

::more pymel speed findings::

July 19th, 2010 by hamish download the zooToolBox

I started poking the speed problems that plague pymel a bit more. It seems the problem may lie in the simple fact that instantiating MObject’s is expensive… try it out:

from maya.OpenMaya import *

import time
import maya.cmds as cmd

def asMObject( item ):
	sel = MSelectionList()
	sel.add( item )

	tmp = MObject()
	sel.getDependNode( 0, tmp )

	return tmp

def test():
	start = time.clock()
	for obj in cmd.ls():
		asMObject( obj )

	print 'time taken %0.3f' % (time.clock()-start)

This is pretty simple stuff. Simply iterating over all nodes in a blank scene and casting them to MObjects individually is slow. It takes 0.05 seconds on my machine on an empty scene. Doing the same thing with pymel takes about 0.21 seconds which is a little more than 4 times slower than this. Quite a leap from 350x.

So it seems Autodesk is largely to blame for some bloated script bindings.

From this little bit of investigation I’m not really convinced that its possible to have a fast unified abstraction of mel/api without help from Autodesk in cleaning up and speeding up their wrappers.

What might be interesting is looking into subclassing unicode and lazily populating the instance with api data as its requested from script to minimize the time spent instantiating maya’s object types. Incidentally pymel’s PyNode class used to inherit from unicode, but I’m unaware of how aggressive it was with lazy evaluation. I believe the reason Chad moved away from this idea in pymel was because strings in python are immutable and node names in maya are not. Having an immutable object represent mutable data is kinda sucky. But hey, we have to deal with this currently anyway, so its not like we’re moving backwards. Just not as far forward as we’d like.

The sucky thing about doing the lazy evaluation thing is that it’d be tricker to leverage inheritance – you wouldn’t want to determine the most appropriate class to cast the node as on instantiation because thats where it becomes expensive. So instead you’d have to dynamically populate the instance dictionary as functionality was requested.

Anyway… Once again we’re at the mercy of some shitty corner cutting from Autodesk.

In other news – has anyone seen how WELL blender has integrated scripting? Way better than any dcc app I’ve ever seen.

::no mo callable?!::

July 19th, 2010 by hamish download the zooToolBox

why did they remove callable( someObj ) in favor of hasattr( someObj, '__call__' )??? that’s horrible to write and to read! :(

ref: porting code to python 3