::Setting Skin Weight Recipe::

October 1st, 2010 by hamish download the zooToolBox

I may be slow to the party here, but setting skin weights is SOOOO much faster if you just set the attributes directly.

So a little background – I have a tool that does skin weight transfer between meshes – I’ve mentioned it a few times.  Anyway, it was at the point where it would literally spend 80-90% of its time just setting the skin weights.  Finding the data was fast, but applying it was slow as hell.  Anyway I tried doing it a bunch of ways – via mel, via the MFnSkinCluster api class etc…  But I just couldn’t break through that speed wall.  And man was it a big ass wall.

I had assumed I was at the mercy of a crappy api until I mentioned it to a colleague at work, Jeff Hameluck.  Jeff is an awesome tools developer and has taught me heaps over the years we’ve worked together.  Anyway he suggested I try to just set the attribute values directly on the skinCluster node – which is something I had never done before.  In fact I’m ashamed to say, I didn’t even know in much detail how the skinCluster node worked – y’know in terms of the attribute machinery on the node.

So whats the difference?  Well, on my 3ghz machine at work it would take about 10 seconds per 6k verts just to set the data.  By contrast the position based search for this size data set was about 2.5 seconds.  Setting the attribute data directly (ie using setAttr and getAttr) took the time to set the skin weight data down to around half a second (possibly less – I didn’t bother timing it because it was so damn fast).

Anyway so what does the code look like?  Its not quite as straight forward as you’d like, but here it is in all its ugly glory.

import apiExtensions
from maya.OpenMayaAnim import MFnSkinCluster
from maya.OpenMaya import MIntArray, MDagPathArray

def setSkinWeights( skinCluster, vertJointWeightData ):
	'''
	vertJointWeightData is a list of 2-tuples containing the vertex component name, and a list of 2-tuples
	containing the joint name and weight.  ie it looks like this:
	[ ('someMesh.vtx[0]', [('joint1', 0.25), 'joint2', 0.75)]),
	  ('someMesh.vtx[1]', [('joint1', 0.2), 'joint2', 0.7, 'joint3', 0.1)]),
	  ... ]
	'''

	#convert the vertex component names into vertex indices
	idxJointWeight = []
	for vert, jointsAndWeights in vertJointWeightData:
		idx = int( vert[ vert.rindex( '[' )+1:-1 ] )
		idxJointWeight.append( (idx, jointsAndWeights) )

	#get an MObject for the skin cluster node
	skinCluster = apiExtensions.asMObject( skinCluster )
	skinFn = MFnSkinCluster( skinCluster )

	#construct a dict mapping joint names to joint indices
	jApiIndices = {}
	_tmp = MDagPathArray()
	skinFn.influenceObjects( _tmp )
	for n in range( _tmp.length() ):
		jApiIndices[ str( _tmp[n].node() ) ] = skinFn.indexForInfluenceObject( _tmp[n] )

	weightListP = skinFn.findPlug( "weightList" )
	weightListObj = weightListP.attribute()
	weightsP = skinFn.findPlug( "weights" )

	tmpIntArray = MIntArray()
	baseFmtStr = str( skinCluster ) +'.weightList[%d]'  #pre build this string: fewer string ops == faster-ness!

	for vertIdx, jointsAndWeights in idxJointWeight:

		#we need to use the api to query the physical indices used
		weightsP.selectAncestorLogicalIndex( vertIdx, weightListObj )
		weightsP.getExistingArrayAttributeIndices( tmpIntArray )

		weightFmtStr = baseFmtStr % vertIdx +'.weights[%d]'

		#clear out any existing skin data - and awesomely we cannot do this with the api - so we need to use a weird ass mel command
		for n in range( tmpIntArray.length() ):
			removeMultiInstance( weightFmtStr % tmpIntArray[n] )

		#at this point using the api or mel to set the data is a moot point...  we have the strings already so just use mel
		for joint, weight in jointsAndWeights:
			if weight:
				infIdx = jApiIndices[ joint ]
				setAttr( weightFmtStr % infIdx, weight )

Easy huh? Anyway, if you have any scripts that need to set skin weights on mesh data, this is the fastest way to do it I’ve managed to find. Try it out! If you’ve got something faster – tell me!

Share

This post is public domain

This entry was posted on Friday, October 1st, 2010 at 13:54 and is filed under main. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

  • tristratos

    Should we expect an update to the zootollbox from you?
    Now that maya 2011 is released would there be any update with those and even more features of yours fully featured?

  • hamish

    Hopefully! I can’t make any promises, but I would love to find some time to get a serious release happening again.

  • tristratos

    I can’t wait!
    Judging from the one we have in our hand it just was amazing
    I hope a new one will blow us once more..
    and I am sure I am speaking out for the whole community (because plenty of us are using your magic mel spells ;)

  • kn

    Good post. I’m also trying to do something similar (import/export). Please, correct me If I’m wrong, but isn’t that the way comet’s “save weights” (or something like that) plug-in works as well?

  • hamish

    kn: maybe! I’ve not actually used comet’s tool, so perhaps like I said I’m just slow to the party. :) Its entirely possible. I was just excited by the discovery so I figured I’d spread the word.

  • kn

    Definitely mate. I’m glad you’ve done that. Much appreciated.
    I’m trying to write my own only because comet’s stuff, when importing, for each individual vertex prints out a line in the editor, therefore a process instead of taking 3 sec., takes 5 minutes to load a skWeight. Anyway, I find it a good challenge as an api newbie, to do such thing.

  • hamish

    kn: nice! as you saw, I’m not even really using the API because its so horrible – although sometimes its a necessary evil… thats funny that it spews to the console though. I wonder why. thats a lot of spew for even a low res model… anyway, good luck!

  • uiron

    from the looks of the code, MFnSkinCluster.setWeights (the one where you set all weights for all your vertices in one call) would be just so much faster here. setAttr() calls alone are a big killer here.

  • hamish

    yeah thats what you’d think hey. but its not. try it out! or at least, not when you accumulate all the extra time spend constructing all the array structures involved. In general I think the python wrapping of the maya api is fundamentally slow – its not SWIG per-se, because we use swig to wrap a bunch of code here at work and the speed difference is fairly negligible. Maya’s bindings just seem to be super slow.

  • uiron

    i’m too lazy to try this now on python (i got my skin manipulation code in C) – you might be right, i often get into the situation where python is horribly slow with array structures (e.g, “x,y,z = something.value()” is 3 times slower than “xyz = something.value()” due to unpack). still, looking at the code, i think there’s a lot of room for improvement, in particular i don’t really like the vertJointWeightData structure. in particular case of skin weight transfer, you don’t ever need to create a “someMesh.vtx[0]” values – from source mesh point of view, it’s vertices as ints, from destination mesh point of view, it’s same ints. joint NAME coupling with weight means joint name gets duplicated with each vertex, etc.

  • hamish

    you’re absolutely right urion – my code could be more efficient. So just FYI, the main reason I’m dealing with strings is that the input to that function can be an arbitrary component list (ie across n-number of meshes) so at some point I need to deal with the strings directly – admitedly I could just throw them in a MSelectionList and get maya to do the heavy lifting, but I already had the code that you see there – and I didn’t want to do THAT much refactoring. :) so there is a heap of laziness on my part. But as I mentioned, the changes I made result in a roughly 10x improvement, and optimizing this code would at best make this a 11x improvement. Didn’t seem worth it to me.

  • CaptainSam

    I just get a dict key error on line 53 (infIdx = jApiIndices[ joint ])

    Judging from the comments, jApiIndices is supposed to be keyed by joint name, but the keys are string representation of the entire object:

    {
    “<maya.OpenMaya.MObject; proxy of >”: 1,
    “<maya.OpenMaya.MObject; proxy of >”: 0
    }

  • hamish

    are you importing the apiExtensions module? I have some code in apiExtensions which modifies the way MObject works which should make these errors go away. The modifications make MObject instances hashable, and also adds a __unicode__ method so they’re compatible with maya.cmds (amongst other things).

    you can get it here:
    http://zootoolbox.googlecode.com/svn/trunk/apiExtensions.py

  • CaptainSam

    Yessss, now it works. I was using the asMObject method from one of your previous post.

    This really is fast indeed. Importing weights on a 23k vertex mesh took 8 seconds! Now the skin saving is the bottle neck instead.

    Thanks so much for posting this.

  • hamish

    CaptainSam: sweet! thats awesome. Incidentally, regarding skin weight saving – I simply use python’s builtin “pickle” functionality for a lot of data serialization of this nature. Its super fast, and mind bogglingly convenient.

    You literally ask python to write some data to disk (in the case of skin weights perhaps a big dictionary of lists containing joint/weight pair tuples) and when you want it back just ask pickle to load the file and you get back the exact same data structure you saved in the first place.

    Its beyond cool. Of course, the resulting file isn’t terribly human readable, so you lose that. But if you use cPickle its really fast too.

  • CaptainSam

    Yep, Im using cPickle. Actually writing that part right now – cant wait to get rid of our entire ugly old mel skinning pipeline. I was thinking of the actual querying of weights. Im using the skinPercent command; just wondering if that can be made faster as well (it’s pretty fast already, though).

    By the way, I’m looking at your skinning scripts (latest svn revision). There are a few import issues, most of them easily fixable, but skinWeightsBase is asking for a couple of modules that dont appear to exist (some inhouse tools, perhaps?):

    from vs import dmxedit
    from vsUtils import DmxFile, iterParents, getJointList
    from modelpipeline import apps

  • hamish

    oh yeah none of that will work. I’m slowly working on getting that code working so I can include it in the toolbox, but for now its unavailable without a bunch of work.

  • Shawn

    Hey Hamish,

    I’m finding our tools is rather slow writing out weighting information. Would you use the same paradigm when creating you vertJointWeightData?

    Cheers and thanks for the info,
    Shanw

  • hamish

    Hi Shawn,
    I’m not sure I understand the question – this paradigm I described is used by the tool that I wrote and I would highly recommend using it to others. Its pretty easy to shoehorn into an existing tool.

  • Shawn

    Hey Hamish,

    Have you considered using the skinCluster API setWeights? I was reading that it may be possible to set all the weights at once.

    Cheers,
    Shawn

  • hamish

    yes I tried that – but its also quite expensive – pretty much anything using the API wrappers is slow. Not sure why, but for whatever reason their API python bindings are stupidly slow (see my rants on pymel for more details on this).

    The API expects to recieve API data structures so you still have to write code to schlep your python lists to MDoubleArrays and whatnot, and this overhead kills any benefits of using the API. I guess you code write the code to just store everything in these data structures to begin with, but thats a pretty high cost and really ties the code tightly to maya – which I don’t like doing.

    Hope that makes sense!

  • http://twitter.com/renatopolimeno Renato Polimeno

    ..

  • http://twitter.com/renatopolimeno Renato Polimeno

    Does it work from one type to another ie. nurbs (cv/surface) to polygons(vert/face) ?