Friday, 27 May 2011

Copying skin weight values between vertices

I was writing a new post on an automatic hair plane skinning tool and realised it had a dependency in the form of another tool I use all the time so thought I'd better quickly post up about this one first.

So, this is a procedure that copies skin weight values from one source vertex to one or more targets and it's something I use a huge amount when skinning characters. It will work if the vert selection is across multiple meshes (though they do need to have the same influences so it can assign weight values to them) so it's useful for things like unifying weight values on seams between meshes or, for example, weighting a button mesh so it follows the nearest weighted vertex on a coat.

I know anyone with Maya 2011 will be saying about now "but isn't this kind of unnecessary with the new skinning tools?" and it kind of is is... if you're running 2011 (I think they finally included this in the tool set). Unfortunately, being part of a wider studio I don't get the final say on what version we upgrade to.... so I'm on 2010. Yeah, I'm kind of gutted...
That said, I wrote this script a while back and over time it's become an integral part of my tool set so even if I was on 2011 it would still be a useful.

Anyway, I'm not going to spend a load of time with this as really I'm only posting it up as a precursor to a new post I've mostly written already. Also I've commented the script itself pretty exhaustively and I'll only end up repeating myself.

To use, source the script. Select your vert(s) you want to copy weight values to, then the vert you want to copy weight values from and run NT_copyWeightsVertexToVertex("")

global proc NT_copyWeightsVertexToVertex(string $lastVertName)
// This copies skin weight values from one vertex to x number of vertices. The source vertex is either the last selected as defined by an undo queue query or
// the string $lastVertName passed by another procedure
// Vert selections DO NOT have to be on the same mesh, so you can use this to fix weighting on seams between two different skinned meshes.

// When setting new weight values, each time a value is set the influence is set to HOLD (lock influence weight). By the time the last value is set,
// automatic weights normalisation will have ensured all other values are zero. After the last weight value is set I unlock them again.
// Grab vert selection, use -flatten flag to give me an element per vert rather than a range
string $sel[] = `ls -sl -fl`;
// If standalone, then query the undo queue to find the last vertex selection. 
// If called by another tool, then use the passed string $lastVertName as the source vertex for weight values
string $lastSelection = $lastVertName;
if (`size $lastVertName` ==  0)
$lastSelection = `undoInfo -q -undoName`;
string $tokenized[];
// Our source vertex is now stored in $lastSelection whether passed by another tool or based on the undo queue query
// If retrieved from the undo queue we'll get a load of rubbish back, e.g "select -tgl plane.vtx[286]" so we need to strip that out
// so we just have the vertex string
tokenize $lastSelection " " $tokenized;
$lastSelection = $tokenized[`size $tokenized` -1];
string $itemsToRemove[] = {$lastSelection};
$sel = stringArrayRemoveExact($itemsToRemove, $sel);
// Array $sel now contains all the verts we want to copy weights TO

// Find the skincluster associated with the source vertex. We need to strip the vertex number off so we just use the poly name
// for findRelatedSkinCluster
tokenize $lastSelection "." $tokenized;
string $skinCluster = `findRelatedSkinCluster $tokenized[0]`;
// grab a list of joints associated with the skinCluster
string $influenceArray[] = `listConnections ($skinCluster + ".matrix")`;
// ...and weight values
float $skinValues[] = `skinPercent -q -v $skinCluster $lastSelection`;
string $reducedInfluenceArray[];
clear $reducedInfluenceArray;
float $reducedSkinValue[];
clear $reducedSkinValue;
// Here, build a new array of joints and weight values ommitting any stored in $influenceArray that have zero weights. 
// There is no point processing zero weight values as this can slow the process down significantly for large joint hierachies.
for ($counter = 0; $counter < `size $skinValues`; $counter ++) {
if ($skinValues[$counter] != 0) {
$reducedInfluenceArray[`size $reducedInfluenceArray`] = $influenceArray[$counter];
$reducedSkinValue[`size $reducedSkinValue`] = $skinValues[$counter];
$influenceArray = $reducedInfluenceArray;
$skinValues = $reducedSkinValue;
// Now we have a new joint and weight array with all zero weight values and influences stripped out
// This is our influence and weight value list that we want to apply to the other verts
// For each vert to copy weights to...
for ($targetCounter = 0; $targetCounter < `size $sel`; $targetCounter ++) {
// tokenize the vert name e.g plane.vtx[685] to get the poly name, use this to find the skin cluster for the mesh. 
tokenize $sel[$targetCounter] "." $tokenized;
$skinCluster = `findRelatedSkinCluster $tokenized[0]`;
// for each weight value/influence of the source vert
for ($counter = 0;$counter<`size($skinValues)`;$counter++) {
// set the weight value for the influence
skinPercent -tv $influenceArrayskinValues[$counter] $skinCluster $sel[$targetCounter];
// lock (set HOLD) the influence so it cannot be changed by weights normalisation caused by subsequent weight assigning
setAttr (($influenceArray[$counter])+".liw") 1;
// quickly loop through each influence we have set on the vert and unlock
for ($influence in $influenceArray) {
setAttr (($influence)+".liw") 0;

No comments:

Post a Comment