///////////////////////// //Tank tread creator by Dan Neufeldt // this script creates tread pieces attached to a curve // it can also create an expression for the tread to animate it when the tank is moving and turning // { proc string[] djnTankTread (string $curve, string $normalGeo, string $treadPiece, float $mlt, int $number, int $create, int $pieceUp[], int $constrainAxes[], int $attachType) { progressWindow -t "Making Tanks" -st "Tank be cool" -max $number; string $treadPieces[]; string $createType; if (1 == $create){ $createType = "duplicate"; }else{ $createType = "instance"; } if (!(`attributeExists "treadAnim" $curve`)){ addAttr -at "double" -ln "treadAnim" $curve; setAttr -k 1 ($curve + ".treadAnim"); } string $parent[] = `listRelatives -p $treadPiece`; if ($curve == $parent[0]){ parent -w $treadPiece; } string $curveShape[] = `listRelatives -s $curve`; string $treadGroup = `group -p $curve -em -n ($curve + "TrdPcGrp#")`; string $constraintGrp; if (1 == $attachType){ $constraintGrp = `group -p $curve -em -n ($curve + "ConstGrp#")`; } int $i; for ($i = 0; $i < $number; $i++) { progressWindow -e -s 1; string $newPiece[] = `eval($createType + " " + $treadPiece)`; string $pathNode = `createNode motionPath`; connectAttr ($curveShape[0] + ".worldSpace[0]") ($pathNode + ".geometryPath"); setAttr ($pathNode + ".fractionMode") 1; setDrivenKeyframe -cd ($curve + ".treadAnim") -dv ($i * $mlt) -v 0 -at "uValue" $pathNode; setDrivenKeyframe -cd ($curve + ".treadAnim") -dv (($i * $mlt) + 200) -v 1 -at "uValue" $pathNode; string $animCurve[] = `listConnections -s 1 -d 0 ($pathNode + ".uValue")`; setAttr ($animCurve[0] + ".preInfinity") 3; setAttr ($animCurve[0] + ".postInfinity") 3; string $attachTo; if (1 == $attachType){ $attachTo = `group -w -em -n ($newPiece[0] + "Pt")`; pointConstraint $attachTo $newPiece[0]; parent $attachTo $constraintGrp; }else{ $attachTo = $newPiece[0]; } connectAttr -f ($pathNode + ".xCoordinate") ($attachTo + ".translateX"); connectAttr -f ($pathNode + ".yCoordinate") ($attachTo + ".translateY"); connectAttr -f ($pathNode + ".zCoordinate") ($attachTo + ".translateZ"); string $const[] = `normalConstraint -aim $pieceUp[0] $pieceUp[1] $pieceUp[2] -wut "none" $normalGeo $newPiece[0]`; if (!$constrainAxes[0]){ disconnectAttr ($const[0] + ".constraintRotateX") ($newPiece[0] + ".rx"); } if (!$constrainAxes[1]){ disconnectAttr ($const[0] + ".constraintRotateY") ($newPiece[0] + ".ry"); } if (!$constrainAxes[2]){ disconnectAttr ($const[0] + ".constraintRotateZ") ($newPiece[0] + ".rz"); } string $parented[] = `parent -r $newPiece[0] $treadGroup`; $treadPieces[`size($treadPieces)`] = $parented[0]; } progressWindow -endProgress; return $treadPieces; } // this procedure takes the information from the UI and feeds it to the tread creation procedures proc djnInitTread (string $multField, string $numField, string $creationRadio, string $facingRadio, string $pieceUpRadio, string $invertUpCheck, string $constrainAxesChecks, string $antiFlipCheck, string $antiFlipAxesChecks, string $flipSensField, string $curveOffsetField, string $attachmentRadio) { int $attachType = `radioButtonGrp -q -sl $attachmentRadio`; int $doFlipExp = `checkBox -q -v $antiFlipCheck`; string $sel[] = `ls -sl`; int $numSel = `size($sel)`; // the selection order should be this: the curve to create the tread from, the geometry for the normal constraint (optional), the tread piece string $curve = $sel[0]; string $curveShape[] = `listRelatives -s $curve`; if (`nodeType($curveShape[0])` != "nurbsCurve"){ error "First selected object is not a NURBS curve. Tread not created"; } string $normalGeo; string $treadPiece; // the treadFacingX attribute is used by the procedure that creates the tread animation expression to determine where to place the tracking points if (!(`attributeExists "treadFacingX" $curve`)){ addAttr -ln "treadFacingX" -at "bool" $curve; } int $face = `radioButtonGrp -q -sl $facingRadio`; int $facingDir[]; switch ($face){ case 1: $facingDir = {0, 0, 1}; setAttr ($curve + ".treadFacingX") 1; break; default: $facingDir = {1, 0, 0}; setAttr ($curve + ".treadFacingX") 0; break; } // if three objects are selected the second object in the selection list is assumed to be the geometry to normal constrain the tread pieces to // if only two objects are selected the normal geometry will be created from the curve if ($numSel != 3){ float $curveOffset = `floatFieldGrp -q -v1 $curveOffsetField`; $normalGeo = `djnTreadNormalGeo $curve $facingDir $curveOffset`; $treadPiece = `djnDupMoveToOrigin $sel[1]`; }else{ $normalGeo = $sel[1]; $treadPiece = `djnDupMoveToOrigin $sel[2]`; } float $widthMult = `floatFieldGrp -q -v1 $multField`; int $number = `intFieldGrp -q -v1 $numField`; int $create = `radioButtonGrp -q -sl $creationRadio`; int $pieceUpType = `radioButtonGrp -q -sl $pieceUpRadio`; int $pieceUp[]; // determine from the UI which axis on the tread piece should be used as the aim axis for the normal constraint int $invertUp = `checkBox -q -v $invertUpCheck`; int $up = 1; if ($invertUp){ $up = -1; } switch ($pieceUpType){ case 1: $pieceUp = {$up, 0, 0}; break; case 3: $pieceUp = {0, 0, $up}; break; default: $pieceUp = {0, $up, 0}; break; } // check to see which axes should be constrained int $constrainAxes[]; $constrainAxes[0] = `checkBoxGrp -q -v1 $constrainAxesChecks`; $constrainAxes[1] = `checkBoxGrp -q -v2 $constrainAxesChecks`; $constrainAxes[2] = `checkBoxGrp -q -v3 $constrainAxesChecks`; // create the tank tread string $treadPieces[] = `djnTankTread $curve $normalGeo $treadPiece $widthMult $number $create $pieceUp $constrainAxes $attachType`; // check to see if anti-flip expression should be created and create it if ($doFlipExp){ float $sensitivity = `floatFieldGrp -q -v1 $flipSensField`; int $flipAxes[]; $flipAxes[0] = `checkBoxGrp -q -v1 $antiFlipAxesChecks`; $flipAxes[1] = `checkBoxGrp -q -v2 $antiFlipAxesChecks`; $flipAxes[2] = `checkBoxGrp -q -v3 $antiFlipAxesChecks`; djnInitAntiFlip $treadPieces $flipAxes $sensitivity; } // check to see if the normal geometry is not parented to the curve, if it isn't, parent it string $nrmlPrnt[] = `listRelatives -p $normalGeo`; if ($curve != $nrmlPrnt[0]){ parent $normalGeo $curve; } delete $treadPiece; select -r $curve; } // this is the procedure to initiate creation of the anti-flip expression proc djnInitAntiFlip (string $pieces[], int $flipAxes[], float $sensitivity) { string $piece; for ($piece in $pieces) { // list all connections to the piece and find the one that is a normal constraint string $conn[] = `listConnections -s 1 -d 0 -scn 1 ($piece)`; string $con = "nono"; string $const; for ($con in $conn) { string $type = `nodeType $con`; if ("normalConstraint" == $type){ $const = $con; break; } } // if there is no constraint found issue a warning but continue with the loop in case the object was accidentally selected if ("nono" == $const){ warning ($piece + "Does not have a normal constraint"); }else{ addAttr -at "double" -ln "lastRotateX" -sn "lrX" $const; addAttr -at "double" -ln "lastRotateY" -sn "lrY" $const; addAttr -at "double" -ln "lastRotateZ" -sn "lrZ" $const; djnAntiFlipExpression $piece $const $flipAxes $sensitivity; } } } // the procedure to create the normal geometry from the tread curve proc string djnTreadNormalGeo (string $curve, int $loftDir[], float $curveOffset) { string $newCurve[] = `offsetCurve -ch 0 -rn 0 -cl 1 -cr 0 -d $curveOffset -tol 0.01 -sd 5 -ugn 0 $curve`; closeCurve -ch 0 -ps 2 -rpo 1 -bb 0.5 -bki 0 -p 0.1 $newCurve[0]; move $loftDir[0] $loftDir[1] $loftDir[2] $newCurve[0]; string $dup[] = `duplicate $newCurve[0]`; move ($loftDir[0] * -1) ($loftDir[1] * -1) ($loftDir[2] * -1) $dup[0]; string $geo[] = `loft -n ($curve + "NrmlGeo") -ch 0 $newCurve[0] $dup[0]`; // hide the geometry as it is annoying setAttr ($geo[0] + ".v") 0; delete $newCurve[0]; delete $dup[0]; return $geo[0]; } proc string djnDupMoveToOrigin (string $obj) { string $dup[] = `duplicate $obj`; move 0 0 0 $dup[0]; float $piv[] = `xform -q -ws -rp $dup[0]`; move (0 - $piv[0]) (0 - $piv[1]) (0 - $piv[2]) $dup[0]; makeIdentity -a 1 $dup[0]; return $dup[0]; } // this procedure creates the tracking points used in the tread animation expression to control movement // two transform nodes are created and parented to the tread controller the second point is placed one unit ahead of the first // two more transform nodes are created and point constrained to the other two transform nodes // those points are used by the expression to determine world position translation proc string[] djnCreateTreadTrackPts (string $treadControl) { string $return[]; int $facingX = `getAttr ($treadControl + ".treadFacingX")`; string $points[]; $points[0] = `createNode -n ($treadControl + "TrackPt1_#") transform`; $points[1] = `createNode -n ($treadControl + "OutPt1_#") transform`; $points[2] = `createNode -n ($treadControl + "TrackPt2_#") transform`; $points[3] = `createNode -n ($treadControl + "OutPt2_#") transform`; float $piv[] = `xform -q -ws -rp $treadControl`; move $piv[0] 0 $piv[2] $points; makeIdentity -a 1 $points[1] $points[3]; if ($facingX){ move -r 1 0 0 $points[2] $points[3]; }else{ move -r 0 0 1 $points[2] $points[3]; } parent $points[1] $points[3] $treadControl; makeIdentity -a 1 $points[1] $points[3]; pointConstraint $points[1] $points[0]; pointConstraint $points[3] $points[2]; $return = {$points[0], $points[2]}; setAttr ($points[0] + ".displayHandle") 1; setAttr ($points[2] + ".displayHandle") 1; addAttr -ln "lastTranslateX" -sn "ltX" -at "double" $points[2]; addAttr -ln "lastTranslateY" -sn "ltY" -at "double" $points[2]; addAttr -ln "lastTranslateZ" -sn "ltZ" -at "double" $points[2]; return $return; } // This procedure initiates the creation of the expression to animate the tread based on tank movement proc djnInitTreadExpression () { int $isOwnScaler; string $sel[] = `ls -sl`; string $treadControl = $sel[0]; string $scaler; // if only the tread curve is selected it is assumed to be in charge of scale for the tread // if two objects are selected the second object is assumed to be in charge of scale if ((`size($sel)`) == 1){ $scaler = $sel[0]; }else{ $scaler = $sel[1]; } if (!(`attributeExists "velocity" $treadControl`)){ addAttr -at "double" -ln "velocity" $treadControl; setAttr -k 1 ($treadControl + ".velocity"); } if (!(`attributeExists "sticky" $treadControl`)){ addAttr -at "double" -ln "sticky" -dv 38 $treadControl; setAttr -k 1 ($treadControl + ".sticky"); } // if the first selected object does not have the attribute "treadAnim" is must not be the tread controller if (`attributeExists "treadAnim" $treadControl`){ string $points[] = `djnCreateTreadTrackPts $treadControl`; djnCreateTreadExpression $points[0] $points[1] $treadControl $scaler; }else{ error ($treadControl + " does not have attribute \"treadAnim\". It may not be a tank tread controller."); } } proc djnCreateTreadExpression (string $point1, string $point2, string $treadControl, string $scaler) { string $exp; $exp = ( "// This expression drives the animation of the tank tread based on movement of the tank \r\n" + "// $sticky is the number used to multiply the distance traveled before feeding into the treadAnim channel\r\n" + "//changing this number will affect how well the tread pieces appear to \"stick\" to the ground\r\n" + "// Get the scale of the object that controls the scaling of the tank.\r\n" + "// This will be used to ensure that the movement is correct regardless of scale\r\n" + "{\r\n" + "\tfloat $sticky = " + $treadControl + ".sticky;\r\n" + "\tfloat $scale = (" + $scaler + ".sx + " + $scaler + ".sy + " + $scaler + ".sz ) / 3;\r\n" + "\t// get the current translation of the treads and their last translation\r\n" + "\t// tracking points for the tread are " + $point1 + " and " + $point2 + "\r\n" + "\t// the position of the tread.\r\n" + "\tfloat $ref[];\r\n" + "\t\t$ref[0] = " + $point1 + ".translateX;\r\n" + "\t\t$ref[1] = " + $point1 + ".translateY;\r\n" + "\t\t$ref[2] = " + $point1 + ".translateZ;\r\n" + "\t// the current position of the tracking point\r\n" + "\tfloat $ct[];\r\n" + "\t\t$ct[0] = " + $point2 + ".translateX;\r\n" + "\t\t$ct[1] = " + $point2 + ".translateY;\r\n" + "\t\t$ct[2] = " + $point2 + ".translateZ;\r\n" + "\t// the previous position of the tread\r\n" + "\tfloat $ot[];\r\n" + "\t\t$ot[0] = " + $point2 + ".ltX;\r\n" + "\t\t$ot[1] = " + $point2 + ".ltY;\r\n" + "\t\t$ot[2] = " + $point2 + ".ltZ;\r\n" + "\t// subtract the track points current translation from the tread position\r\n" + "\tfloat $diffC[];\r\n" + "\t\t$diffC[0] = $ct[0] - $ref[0];\r\n" + "\t\t$diffC[1] = $ct[1] - $ref[1];\r\n" + "\t\t$diffC[2] = $ct[2] - $ref[2];\r\n" + "\t// get the distance between the current position and the tracking point\r\n" + "\tfloat $distC = mag(<<$diffC[0], $diffC[1], $diffC[2]>>);\r\n" + "\t// subtract the last position from the current position of the tracking point\r\n" + "\tfloat $diffL[];\r\n" + "\t\t$diffL[0] = $ot[0] - $ref[0];\r\n" + "\t\t$diffL[1] = $ot[1] - $ref[1];\r\n" + "\t\t$diffL[2] = $ot[2] - $ref[2];\r\n" + "\t// get the distance that the last position was from the current position of the tracking point\r\n" + "\tfloat $distL = mag (<<$diffL[0], $diffL[1], $diffL[2]>>);\r\n" + "\t// subtract the last distance from the current distance to get the amount of change\r\n" + "\tfloat $trav = $distL - $distC;\r\n" + "\t//get the current value the tread is animated at\r\n" + "\tfloat $tread = " + $treadControl + ".treadAnim;\r\n" + "\t" + $treadControl + ".treadAnim = $tread + ($trav * ($sticky / $scale));\r\n" + "\t" + $treadControl + ".velocity = $trav;\r\n" + "\t// store the current translation of the tracking point to be used in the next iteration of the expression\r\n" + "\t" + $point2 + ".ltX = $ct[0];\r\n" + "\t" + $point2 + ".ltY = $ct[1];\r\n" + "\t" + $point2 + ".ltZ = $ct[2];\r\n" + "\t//The end of the expression\r\n" + "}" ); expression -n ($treadControl + "TreadAnim") -s $exp; } // this procedure creates an anti flip expression for the object given to it. // it takes the name of the object, the name of the normal constraint on the object, // and an integer array describing which axes to apply the expression to proc djnAntiFlipExpression (string $object, string $const, int $rotateAxes[], float $sensitivity) { string $exp = ( "//This expression is to keep the normal constraint from flipping\r\n" + "{" ); if ($rotateAxes[0]){ $exp += `djnAntiFlipElement $const X $sensitivity`; connectAttr -f ($const + ".lrX") ($object + ".rotateX"); } if ($rotateAxes[1]){ $exp += `djnAntiFlipElement $const Y $sensitivity`; connectAttr -f ($const + ".lrY") ($object + ".rotateY"); } if ($rotateAxes[2]){ $exp += `djnAnitFlipElement $const Z $sensitivity`; connectAttr -f ($const + ".lrZ") ($object + ".rotateZ"); } $exp += "}"; expression -s $exp -n ($const + "XFlp"); } // this procedure creates an element of the antiFlipExpression for one axis proc string djnAntiFlipElement (string $const, string $axis, float $sensitivity) { string $exp = ( "//Anti-Flip expression for " + $const + "\r\n" + "\tif (frame <= 1){\r\n" + "\t\t" + $const + ".lr" + $axis + " = " + $const + ".constraintRotate" + $axis + ";\r\n" + "\t}\r\n" + "\tfloat $r" + $axis + " = " + $const + ".constraintRotate" + $axis + ";\r\n" + "\tfloat $l" + $axis + " = " + $const + ".lr" + $axis + ";\r\n" + "\tif ((abs($r" + $axis + ") < (abs($l" + $axis + ") - " + $sensitivity + ")) || (abs($r" + $axis + ") > (abs($l" + $axis + ") + " + $sensitivity + "))){\r\n" + "\t\t" + $const + ".lr" + $axis + " = $l" + $axis + ";\r\n" + "\t}else{\r\n" + "\t\t" + $const + ".lr" + $axis + " = $r" + $axis + ";\r\n" + "\t}\r\n" ); return $exp; } // this procedure is used when the create anti-flip button is pressed. // all the tread pieces should be selected. This procedure feeds that and the information from the axes check boxes to the expression creation proc. proc djnSoloFlipExp (string $antiFlipAxesChecks, string $sensField) { string $sel[] = `ls -sl`; int $flipAxes[]; $flipAxes[0] = `checkBoxGrp -q -v1 $antiFlipAxesChecks`; $flipAxes[1] = `checkBoxGrp -q -v2 $antiFlipAxesChecks`; $flipAxes[2] = `checkBoxGrp -q -v3 $antiFlipAxesChecks`; float $sensitivity = `floatFieldGrp -q -v1 $sensField`; djnInitAntiFlip $sel $flipAxes $sensitivity; } // creation of the UI // the pink button is the coolest float $pink[] = {.994, .743, 1.}; string $windowName = "djnTreadWin"; if (`window -exists $windowName`) deleteUI $windowName; window -t "Make Tank Tread" $windowName; columnLayout ; string $multField = `floatFieldGrp -nf 1 -l "Spacing" -v1 2 -cw 1 130 -cat 1 "left" 0 -cw 2 70 `; string $numField = `intFieldGrp -nf 1 -l "Number" -v1 100 -cw 1 130 -cat 1 "left" 0 -cw 2 70`; string $creationRadio = `radioButtonGrp -nrb 2 -la2 "Duplicate" "Instance" -sl 1 -cw 1 100 -cw 2 100`; text -l "Attachment Options:"; string $attachmentRadio = `radioButtonGrp -nrb 2 -la2 "Point Constraint" "Translation" -sl 2 -cw 1 100 -cw 2 100`; separator -w 300 -h 8; text -l "Tread Rotation:" -fn "tinyBoldLabelFont" -al "left" -w 200; text -l "Direction the tread is facing"; string $facingRadio = `radioButtonGrp -nrb 2 -la2 "X" "Z" -sl 2 -cw 1 50 -cw 2 50`; // rowColumnLayout -nc 2 -cw 1 140 -cw 2 100; text -l "Up Axis for the Tread Piece" ; string $invertUpCheck = `checkBox -l "Invert Up"`; // setParent..; string $pieceUpRadio = `radioButtonGrp -nrb 3 -la3 "X up" "Y up" "Z up" -sl 2 -cw 1 50 -cw 2 50 -cw 3 50`; text -l "Constrain Rotation Axes:"; string $constrainAxesChecks = `checkBoxGrp -ncb 3 -la3 "X" "Y" "Z" -v1 1 -v2 0 -v3 0 -cw 1 50 -cw 2 50 -cw 3 50`; string $curveOffsetField = `floatFieldGrp -nf 1 -l "Curve Offset" -v1 0.1 -cw 1 130 -cat 1 "left" 0 -cw 2 70`; separator -w 300 -h 8; rowColumnLayout -nc 2 -cw 1 130 -cw 2 77 ; columnLayout; text -l "Anti Flip expression:" -fn "tinyBoldLabelFont" -al "left" -w 120; string $antiFlipCheck = `checkBox -l "Create with treads"`; text -l "For these Axes:"; string $antiFlipAxesChecks = `checkBoxGrp -ncb 3 -la3 "x" "y" "z" -cw 1 50 -cw 2 50 -cw 3 50 `; setParent..; string $antiFlipButton = `button -l "AntiFlip Expr." -h 40 -bgc $pink[0] $pink[1] $pink[2]`; setParent..; string $flipSensField = `floatFieldGrp -nf 1 -l "Expression Tolerance" -v1 25 -cw 1 130 -cat 1 "left" 0 -cw 2 70`; button -e -c ("djnSoloFlipExp " + $antiFlipAxesChecks + " " + $flipSensField) $antiFlipButton; separator -w 300 -h 18; rowColumnLayout -nc 3 -cw 1 100 -cw 2 7 -cw 3 100; button -l "Make Treads" -c ("djnInitTread " + $multField + " " + $numField + " " + $creationRadio + " " + $facingRadio + " " + $pieceUpRadio + " " + $invertUpCheck + " " + $constrainAxesChecks + " " + $antiFlipCheck + " " + $antiFlipAxesChecks + " " + $flipSensField + " " + $curveOffsetField + " " + $attachmentRadio); text -l " "; button -l "Driver Expression" -c "djnInitTreadExpression "; setParent..; setParent..; window -e -wh 216 412 $windowName; showWindow $windowName; }