/* This file downloaded from Highend3d.com '' '' Highend3d.com File Information: '' '' Script Name: saveWeights v1.0 '' Author: Big Idea Productions '' Last Updated: August 16, 2001 '' Update/Change this file at: '' http://www.highend3d.com/maya/mel/?section=animation#1201 '' '' Please do not alter any information above this line '' it is generated dynamically by Highend3d.com and will '' be changed automatically on any updates. */ //----BEGINDOC---------------------------------------------------------------- // Copyright (C) 2001, Big Idea Productions // //---------------------------------------------------------------------------- // Name: saveWeights.mel - MEL Script //--------------------------------------------------------------------------- // // Synopsis: saveWeights() // // Description: Saves weighting info for a smooth skinned object and // allows reloading via point order, or point location. // The latter type of save/load allows reloading even // if point count changes. // Point position saving can be done via local or world // space, based on the appropriate UI checkbox. // In addition, for easy re-skinning the "Joint" button can // be used to easily reselect all the joints used in the original // skinning // Works for NURBS, Polys, SubD's and Lattices that are skinned. // // Arguments: Skin Cluster Node : Enter the skin cluster the object is using // Filename : Output path+filename for saving/loading. // Threshold : For Point Position saves/loads, this specifies // how close a point must be on loading to be considered // at the same spot. If you can't get PP loads to work, // try raising this number to allow for more distance // between the original and current point positions. // Global/Local : Specifies if point position saves are stored // in object or world coordinates. // Point Order : Saves data by point order. Positions // can change later, but not point count. // Point Position : Saves data by storing the XYZ position of each // point. Point count can change later, but points must // be at nearly the same place (within threshold value) // to be reloaded. // Load/Save : Does the appropriate action. // Select Joints : Assuming a valid filename is provided for a // saved weights file, this will Select all the joints // that were used during skinning. Including // influenceObjects. // // Returns: Nothing // // Examples: source "saveWeights.mel"; saveWeights(); // // Requires: NURBS, Poly, SubD or Lattice object, SMOOTH skin bound. // // Authors: Michael B. Comet - michael.comet@bigidea.com // // Versions: ver. 1.00 - 3/27/00 - michael.comet@bigidea.com // ver. 1.01 - comet - Added UI. // ver. 1.20 - comet - Added saving/loading of vertex loc // so can change count // ver. 1.21 - comet - Made it so it only output // weights that are not 0.00 which means you don't have // to rebind to everything. Only the joints that are // used by the binding. // ver. 1.22 - comet - Added -sm 46 option to filter expand // so this will all work on lattices as well as // polys+nurbs. // ver. 1.23 - comet - Added -sm 36 option to filter expand // so this will all work on SubD surfaces too. // ver 1.24 - comet - Added option to load/save pos data in // World or Local coords. // ver 1.25 - comet - Save Weights now stores a function that // keeps a global string array of joints that were bound // to the mesh so that now you can click a button to // select the joints that were bound to the mesh for // easy reskinning. // ver 1.26 - comet - Added showWindow so it will restore // if minimized. // //---------------------------------------------------------------------------- //-----ENDDOC---- // global string $version = "ver. 1.26"; global string $selectedBones[]; /* * saveWeights() - Main entry and UI start. * */ global proc saveWeights() { global string $version; global string $selectedBones[]; clear $selectedBones; if (!`window -q -ex saveWeightsWin`) { window -widthHeight 350 420 -title ("Save Weights -- "+$version) saveWeightsWin; showWindow saveWeightsWin; columnLayout -p saveWeightsWin saveForm; // Search & Replace Frame frameLayout -cll true -cl false -w 360 -h 220 -label "Load/Save Options" -labelAlign "center" -borderStyle "out" -p saveForm saveOptFrame; columnLayout srC; text "Skin Cluster Node (i.e.: skinCluster1):"; textField -w 300 -text "skinCluster1" skinclText; text "Filename:"; textFieldButtonGrp -w 300 -text "/tmp/output.mel" -bc "dialog" -bl "..." filenameText; text "Threshold for Point Position matching during load:"; floatField -w 150 -v 0.0001 -pre 6 -min 0 threshFIELD; rowLayout -nc 2 -cw2 175 175 -p srC; radioCollection; radioButton -label "Global Point Pos." -sl globalRB; radioButton -label "Local Point Pos." localRB; separator -p srC -h 8 -w 350; radioCollection; radioButton -p srC -label "Save by Point Order (Point count cannot change later)" -sl gensaveRB; radioButton -p srC -label "Save by Point Position (Point count can change later)" vertsaveRB; separator -p srC -h 8 -w 350; rowLayout -nc 3 -cw3 50 150 150 -p srC; text " "; button -label " Load Weights " -command "doloadWeights" -w 135 -al center loadWeightsBTN; button -label " Save Weights " -command "dosaveWeights" -w 135 -al center saveWeightsBTN; rowLayout -nc 2 -cw2 50 150 -p srC; text " "; button -label " Select Joints " -command "selectOldJoints;" -w 135 -al center selJointsBTN; separator -h 8 -p srC; } else { showWindow saveWeightsWin; } } /* * dosaveWeights() - Prompts for a skincluster node for the selected object, * and a fileanme and outputs a mel script that can be ran * to reload the weighting for a smooth skin object. */ global proc dosaveWeights() { if (`radioButton -q -sl gensaveRB`) dosaveGenericWeights; else if (`radioButton -q -sl vertsaveRB`) dosaveVertWeights; } /* * dosaveGenericWeights() - Save weighting based on point # ordering. * ie: point[1] has weights X1 point[2] has weights X2 */ global proc dosaveGenericWeights() { string $obj; string $joints[]; float $weights[]; string $cmd; string $vsel; string $skincl; int $fileId; string $filename; global string $selectedBones[]; clear $selectedBones; // clear off list of joints that are binded string $sel[0] = `ls -sl`; // gets the selected object components // 28 means CV's and 31 means poly vertices..this expands it into // 46 means lattice points a nice list 36 means SubD Points string $expandedSel[0] = `filterExpand -ex 1 -sm 28 -sm 31 -sm 46 -sm 36 $sel`; int $nVerts = `size $expandedSel`; if ($nVerts <= 0) { error -sl 1 ("saveWeights.mel: There are no CV's or Vertices selected!"); return; } /* * At this point the string array expandedSel[] has stuff like * nurbsSphere1.cv[4][5] for NURBS * pSphere1.vtx[361] for polys * ffd1Lattice.pt[1][2][3] for lattices * in it, so we can just use each of those for the stuff below! */ // Prompt user for the skinCluster... $skincl = `textField -q -text skinclText`; if ($skincl == "") { error -sl 1 ("saveWeights.mel: Please enter Skin Cluster Node Name."); return; } /* Prompt for fileSave dialog */ // Now query the real string user entered $filename = `textFieldButtonGrp -q -text filenameText`; if ($filename == "") { warning "saveWeights.mel: Please enter a filename for output."; return; } /* open output file */ $fileId = `fopen $filename "w"`; if ($fileId == 0) { error -sl 1 ("saveWeights.mel: Error Opening File: "+$filename); return; } fprint $fileId "// saveWeights.mel OUTPUT Script.\n\n"; fprint $fileId "global proc reloadWeights(string $skincl)\n"; fprint $fileId "{\n"; fprint $fileId " waitCursor -state on;\n\n"; waitCursor -state on; for ($vsel in $expandedSel) { clear $joints; // erase array // What joints are being used? $cmd = "skinPercent -q -t "+$skincl+" "+$vsel; $joints = eval($cmd); // And what are their skinPercent weights? $cmd = "skinPercent -q -v "+$skincl+" "+$vsel; $weights = eval($cmd); string $output; $output = "skinPercent "; for ($n=0; $n < size($joints); ++$n) { if ($weights[$n] != 0.0) $output = $output + ("-tv "+$joints[$n]+" "+$weights[$n]+" "); // Now keep list of joints we've seen... storeSelectedBone($joints[$n]); } $output = $output+("$skincl "+$vsel+";\n"); // note here we print // $skincl as var /*DEBUG print $output; */ fprint $fileId (" "+$output); // write to file } fprint $fileId "\n waitCursor -state off;\n"; fprint $fileId " print \"reloadWeights - Generic, done.\\n\";\n"; fprint $fileId "}\n\n"; // Now print out code to reset the selectedBone array on reloading... writeSelectedBones($fileId); fprint $fileId "\n\n"; fprint $fileId "// end of output.\n"; fclose $fileId; waitCursor -state off; print ("// saveWeights ended successfully. //\n"); } /* * stripCR - removes leading AND trailing \r\n stuff. */ global proc string stripCR(string $s) { return ( match( "^[^(\r\n)]*", $s ) ); } /* * doloadWeights() - loads up the weights from a saved script. */ global proc doloadWeights() { string $filename; int $fileId; string $line; $filename = `textFieldButtonGrp -q -text filenameText`; $fileId = `fopen $filename "r"`; if ($fileId == 0) { error -sl 1 ("saveWeights.mel: Error Opening File or File does not exist: "+$filename); return; } $line = `fgetline $fileId`; // get the first comment line/header $line = stripCR($line); fclose $fileId; if ($line == "// saveWeights.mel OUTPUT Script.") doloadGenericWeights(); else if ($line == "// saveWeights.mel VERT SAVE DATA") doloadVertWeights(); else error -sl 1 ("saveWeights.mel: Unknown format for data. Cannot load "+$filename); } /* * doloadGenericWeights() - Loads a saved generic weight mel script and runs it * This is really easy since the generic one is saved as an executable * MEL script so all we need to do is source it and run it. */ global proc doloadGenericWeights() { string $filename, $skincl; $skincl = `textField -q -text skinclText`; $filename = `textFieldButtonGrp -q -text filenameText`; eval ("source \""+$filename+"\";"); evalDeferred ("reloadWeights(\""+$skincl+"\");"); } /* * dosaveVertWeights() - This saves weights based on LOCATION. So it * stores the weight of each vertex as well as the vertex * position into a file. Later on we can reload this, look * for a closest matching vertex and load up the weights. */ global proc dosaveVertWeights() { string $obj; string $joints[]; float $weights[]; float $pos[]; string $cmd; string $vsel; string $skincl; int $fileId; string $filename; global string $selectedBones[]; clear $selectedBones; // clear array of what joints are binded string $sel[0] = `ls -sl`; // gets the selected object components // 28 means CV's and 31 means poly vertices..this expands it into // 46 means lattice points a nice list 36 means SubD points string $expandedSel[0] = `filterExpand -ex 1 -sm 28 -sm 31 -sm 46 -sm 36 $sel`; int $nVerts = `size $expandedSel`; if ($nVerts <= 0) { error -sl 1 ("saveWeights.mel: There are no CV's or Vertices selected!"); return; } /* * At this point the string array expandedSel[] has stuff like * nurbsSphere1.cv[4][5] for NURBS * pSphere1.vtx[361] for polys * ffd1Lattice.pt[1][2][3] for lattices * in it, so we can just use each of those for the stuff below! */ // Prompt user for the skinCluster... $skincl = `textField -q -text skinclText`; if ($skincl == "") { error -sl 1 ("saveWeights.mel: Please enter Skin Cluster Node Name."); return; } /* Prompt for fileSave dialog */ // Now query the real string user entered $filename = `textFieldButtonGrp -q -text filenameText`; if ($filename == "") { warning "saveWeights.mel: Please enter a filename for output."; return; } /* open output file */ $fileId = `fopen $filename "w"`; if ($fileId == 0) { error -sl 1 ("saveWeights.mel: Error Opening File: "+$filename); return; } fprint $fileId "// saveWeights.mel VERT SAVE DATA\n\n"; fprint $fileId "/*\n"; waitCursor -state on; for ($vsel in $expandedSel) { clear $joints; // erase array $cmd = "skinPercent -q -t "+$skincl+" "+$vsel; $joints = eval($cmd); $cmd = "skinPercent -q -v "+$skincl+" "+$vsel; $weights = eval($cmd); if (`radioButton -q -sl globalRB`) $pos = `pointPosition -w $vsel`; else $pos = `pointPosition -l $vsel`; fprint $fileId ($vsel+"\n"); // print vertex name fprint $fileId ($pos[0]+" "+$pos[1]+" "+$pos[2]+"\n"); // print pos string $output=""; for ($n=0; $n < size($joints); ++$n) { if ($weights[$n] != 0.0) $output = $output + ("-tv "+$joints[$n]+" "+$weights[$n]+" "); // Now keep list of joints we've seen... storeSelectedBone($joints[$n]); } fprint $fileId ($output+"\n"); // finally print weighting info } fprint $fileId "*/\n\n"; // Now print out code to reset the selectedBone array on reloading... writeSelectedBones($fileId); fprint $fileId "\n\n"; fprint $fileId "// end of output.\n"; fclose $fileId; waitCursor -state off; print ("// saveWeights ended successfully. //\n"); } /* * doloadVertWeights() - Loads up weights based on vertex position so * that even if point count has changed, it will still load. * Vertices are found by a closest match, where they must be * withing the threshold to be considered for a "match". * Vertices that don't match are left as they were. */ global proc doloadVertWeights() { string $filename; int $fileId; string $line; string $skincl; int $totalv=0; // total number of read in verts string $verts[]; // list of verts string $weights[]; // list of weighting strings for verts float $xpos[], $ypos[], $zpos[]; // list of x y z position for verts float $pos[]; float $threshold; string $pstr[]; string $vsel; int $i, $match; float $delta[], $mindelta; print "// Starting load of VERT Weights.\n"; $threshold = `floatField -q -v threshFIELD`; /* * See what vertices user has loaded */ string $sel[0] = `ls -sl`; // gets the selected object components // 28 means CV's and 31 means poly vertices..this expands it into // 46 meean lattices a nice list 36 means SubD points string $expandedSel[0] = `filterExpand -ex 1 -sm 28 -sm 31 -sm 46 -sm 36 $sel`; int $nVerts = `size $expandedSel`; if ($nVerts <= 0) { error -sl 1 ("saveWeights.mel: There are no CV's or Vertices selected to load over!"); return; } $filename = `textFieldButtonGrp -q -text filenameText`; // Prompt user for the skinCluster... $skincl = `textField -q -text skinclText`; if ($skincl == "") { error -sl 1 ("saveWeights.mel: Please enter Skin Cluster Node Name."); return; } $fileId = `fopen $filename "r"`; if ($fileId == 0) { error -sl 1 ("saveWeights.mel: Error Opening File or File does not exist: "+$filename); return; } // Skip over header $line = `fgetline $fileId`; // get the first comment line/header $line = `fgetline $fileId`; // get the spacing blank line $line = `fgetline $fileId`; // get the start comment /* line waitCursor -state on; // Now read in the data $line = `fgetline $fileId`; // get the vertex string $line = stripCR($line); while ($line != "*/") { $verts[$totalv] = $line; // store vertex string $line = `fgetline $fileId`; // get the position; $line = stripCR($line); tokenize($line, $pstr); // since there are spaces between xyz $xpos[$totalv] = (float)($pstr[0]); $ypos[$totalv] = (float)($pstr[1]); $zpos[$totalv] = (float)($pstr[2]); $line = `fgetline $fileId`; // get the weighting string; $line = stripCR($line); $weights[$totalv] = $line; /* // DEBUG OUTPUT print ("Vert: "+$verts[$totalv]+"\n"); print ("Pos: "+$xpos[$totalv]+","+$ypos[$totalv]+","+$zpos[$totalv]+"\n"); print ("Weight: "+$weights[$totalv]+"\n"); */ ++$totalv; $line = `fgetline $fileId`; // get the next vertex string $line = stripCR($line); } fclose $fileId; /* * At this point we have a list of the original verts, weighting and * pos and the current set of selected vertices. */ for ($vsel in $expandedSel) { if (`radioButton -q -sl globalRB`) $pos = `pointPosition -w $vsel`; // get current pos else $pos = `pointPosition -l $vsel`; // get current pos // now store distance from each saved vert for ($i=0; $i < $totalv; ++$i) { $delta[$i] = ( abs($pos[0]-$xpos[$i]) + abs($pos[1]-$ypos[$i]) + abs($pos[2]-$zpos[$i]) ) / 3; } // Now figure which one is closest $match = 0; // by default matches first one $mindelta = $delta[0]; for ($i=1; $i < $totalv; ++$i) { if ($delta[$i] < $mindelta) { // found a closer match? $match = $i; $mindelta = $delta[$i]; } } // Finally if it is within threshold, load new weights! if ($pos[0] < ($xpos[$match]+$threshold) && $pos[0] > ($xpos[$match]-$threshold) && $pos[1] < ($ypos[$match]+$threshold) && $pos[1] > ($ypos[$match]-$threshold) && $pos[2] < ($zpos[$match]+$threshold) && $pos[2] > ($zpos[$match]-$threshold) ) { // found a close enough match! string $cmd = ("skinPercent "+$weights[$match]+" "+$skincl+" "+$vsel); eval($cmd); // so update it! //print ("Vert: "+$vsel+" matches old vertex: "+$verts[$match]+"\n"); } else { print ("Vert: "+$vsel+" is not within threshold enough to match: "+$verts[$match]+", so not changing.\n"); } } waitCursor -state off; print "// Load of VERT Weights done.//\n"; } /* * selectOldJoints() - This checks to make sure the filename is a valid * saved file from saveWeights, and then calls the MEL function that * is in that file, by sourcing it and evaling it. */ global proc selectOldJoints() { global string $selectedBones[]; string $filename; int $fileId; string $line; $filename = `textFieldButtonGrp -q -text filenameText`; $fileId = `fopen $filename "r"`; if ($fileId == 0) { error -sl 1 ("saveWeights.mel: Error Opening File or File does not exist: "+$filename); return; } $line = `fgetline $fileId`; // get the first comment line/header $line = stripCR($line); fclose $fileId; if ($line == "// saveWeights.mel OUTPUT Script.") print "Selecting joints from generic saved point data...\n"; else if ($line == "// saveWeights.mel VERT SAVE DATA") print "Selecting joints from VERT POS saved point data...\n"; else { error -sl 1 ("saveWeights.mel: Unknown format for data. Cannot load "+$filename); return; } eval ("source \""+$filename+"\";"); // source the file since it's really a MEL script doLoadJoints(); // and now call the func in it that will store the bone names select -clear; // select nothing print ("select -clear;\n"); int $total = size($selectedBones); // how many bones now? int $n; if ($total <= 0) print "There are no joints to select.\n"; for ($n=0; $n < $total; ++$n) { if (objExists($selectedBones[$n])) { select -add $selectedBones[$n]; print ("select -add "+$selectedBones[$n]+";\n"); } else { warning ("Cannot select bone \""+$selectedBones[$n]+"\", it does not exist."); } } } /* * storeSelectedBone() - Helper function that checks to see if the passed in * string/bonename is in our list, and if not, adds it to the end. */ global proc storeSelectedBone(string $b) { global string $selectedBones[]; int $n; int $found=0; int $total = size($selectedBones); for ($n=0; $n < $total; ++$n) { if ($selectedBones[$n] == $b) { $found = 1; break; } } if ($found == 0) // haven't seen it yet, add to end of list. $selectedBones[$total] = $b; // add it } /* * writeSelectedBones() - This proc given a output fileId that is already * opened for writing, will write out a MEL script function into it * called "doLoadJoints" that when run, will store the proper joints * intot he global variable selectedBones. */ global proc writeSelectedBones(int $fileId) { global string $selectedBones[]; int $total = size($selectedBones); int $n; // Now print out code to reset the selectedBone array on reloading... fprint $fileId "global proc doLoadJoints()\n"; fprint $fileId "{\n"; fprint $fileId " global string $selectedBones[];\n\n"; fprint $fileId " clear $selectedBones;\n\n"; for ($n=0; $n < $total; ++$n) { string $output = " $selectedBones["+$n+"] = \""+$selectedBones[$n]+"\";\n"; fprint $fileId $output; } fprint $fileId "}\n\n"; } /* * doLoadJoint() - this is a dummy proc that will be resourced by the * reloading function for joint selecting */ global proc doLoadJoints() { print "No joints to select - dummy function.\n"; } global proc dialog() { textFieldButtonGrp -e -text (`fileDialog -dm "/tmp/*.mel"`) filenameText; }