// =========================================================================== // Copyright 2018 Autodesk, Inc. All rights reserved. // // Use of this software is subject to the terms of the Autodesk license // agreement provided at the time of installation or download, or which // otherwise accompanies this software in either electronic or hard copy form. // =========================================================================== // // hikBakeOperation.mel // // Description: // Utility methods Related to the baking feature. // These methods could trigger UI updates in any part of the // unified character context. // /////////////////////////////////////////////////////////////////////// // Global variables... /////////////////////////////////////////////////////////////////////// global string $gHikBakeToControlRigLayer; // Internally used to pass info between hikBakeToControlRigPre and hikBakeToControlRigPost global int $gHikPreBakeAlignState; /////////////////////////////////////////////////////////////////////// // Local methodes... /////////////////////////////////////////////////////////////////////// proc int isKeyableXYZ( string $node, string $attr ) { string $parentAttr = $node+"."+$attr; return ( `getAttr -k ($parentAttr+"X")` && `getAttr -k ($parentAttr+"Y")` && `getAttr -k ($parentAttr+"Z")` ); } proc getHikNodeAttributesToBake( string $characterOrRig, string $attributes[], int $mask ) { int $LastNodeId = hikGetNodeCount(); for($i = 1 /* 0=reference to skip */ ; $i < $LastNodeId; $i++) { if( $mask && !`hikBodyPart -inMask $mask -forNode $i` ) continue; string $node = hikGetSkNode( $characterOrRig, $i ); if( size( $node ) > 0 ) { if( isKeyableXYZ( $node, "rotate" ) ) { $attributes[size($attributes)] = $node+".rotate"; } if( !hikIsRotateOnlyFK( $i ) ) { if( isKeyableXYZ( $node, "translate" ) ) { $attributes[size($attributes)] = $node+".translate"; } } } } } proc getHikEffAttributesToBake( string $ctrlSet, string $attributes[], int $mask ) { int $lastEffectorId = GetHIKEffectorCount(); for( $i=0; $i<$lastEffectorId; $i++) { if( $mask && !`hikBodyPart -inMask $mask -forEffector $i` ) continue; string $hikEffName = GetHIKEffectorName($i); string $iks[] = `listConnections -s 1 -d 0 ($ctrlSet + "." + $hikEffName)`; for( $ik in $iks ) { if(!hikIsAuxiliary( $ik ) ) { if( isKeyableXYZ( $ik, "translate" ) ) { $attributes[size($attributes)] = $ik+".translate"; } if( isKeyableXYZ( $ik, "rotate" ) ) { $attributes[size($attributes)] = $ik+".rotate"; } } } } } proc string[] pairBlendCurves( string $node, string $type, int $channel ) // // Description // Determine the names of animCurves attaches to a named pairBlend node. // // Inputs // $node - the name of the pairBlend node // $type - Either translate or rotate // $channel - Either 1 or 2 // { string $curves[] = { "", "", "" }; if ( size( $node ) == 0 || nodeType( $node ) != "pairBlend" || ( $channel != 1 && $channel != 2 ) ) return $curves; string $attrs[] = ( $type == "translate" ) ? { ".inTranslateX", ".inTranslateY", ".inTranslateZ" } : { ".inRotateX", ".inRotateY", ".inRotateZ" }; for ( $i=0; $i<3; $i++ ) { string $full = $node + $attrs[$i] + $channel; string $c[] = `listConnections -type "animCurve" $full`; if ( size($c) == 1 ) $curves[ $i ] = $c[0]; } return $curves; } proc bakeToCustomRigPostCleanup( string $retargeter ) // // Description // The bakeToCustomRig operation will place new animCurves on XYZ channels // of the translate or rotate attributes. If animCurves already existed // prior to creating the custom rig, these anim curves will be on a pairBlend // node connected to the translate/rotate compound. To make sure that we don't // leave a bunch of zombie nodes in the scene and that the retargeter is // properly cleaned up, prior to disconnecting the retargeting node n/w // (see disconnectBakeAttrs below), we do some cleanup and move the new // anim curves onto the pairBlend and delete the old anim curves. // { // For each retargeter mapping string $attrs[] = RetargeterDestinationAttributes( $retargeter ); for ( $a in $attrs ) { // Determine if there is a pairBlend on the compound. This indicates // the transform was originally driven by anim curves prior to // creating and hooking up the retargeter. If no pairBlend exists // move on to the next mapping. string $blends[] = `listConnections -type "pairBlend" $a`; if ( size( $blends ) == 0 ) continue; // If a pairBlend was found determine the names of animCurves on // x1, y1, z1 input blend attributes string $dest = plugNode( $a ); string $attr = plugAttr( $a ); string $old[] = pairBlendCurves( $blends[0], $attr, 1 ); // Next, for each XYZ input translate or rotate attribute on // the destination tranform ... for ( $i=0; $i<3; $i++ ) { string $channel[] = { "X", "Y", "Z" }; string $plug = ( $dest + "." + $attr + $channel[$i] ); $channel = ( $attr == "translate" ) ? { ".inTranslateX1", ".inTranslateY1", ".inTranslateZ1" } : { ".inRotateX1", ".inRotateY1", ".inRotateZ1" }; // ... get the name of the animCurve driving the attr. // If no animCurve exists move on to the next attribute. string $new[] = `listConnections -type "animCurve" $plug`; if ( size( $new ) == 0 ) continue; // Otherwise move the new animCurve onto the pairBlend. // To do this ... string $old_src = ( $old[$i] + ".output" ); string $old_dst = ( $blends[0] + $channel[$i] ); string $new_src = ( $new[0] + ".output" ); string $new_dst = $plug; // ... disconnect the old anim curve from the pairBlend disconnectAttr $old_src $old_dst; // ... disconnect the new anim curve from the destination transform disconnectAttr $new_src $new_dst; // ... connect the new anim curve to the pairBlend connectAttr $new_src $old_dst; // ... and delete the old anim curve delete $old[$i]; } } } // Disconnect the connections from the compounds since they have been baked // proc disconnectBakedAttrs( string $character ) { if( hikGetStateToGlobalSk( $character, 0 ) == "" ) { hikDisconnectSkByDeletingPairBlends( $character ); } else { string $retargeter = RetargeterGetName( $character ); if ( RetargeterIsConnected( $retargeter ) ) { bakeToCustomRigPostCleanup( $retargeter ); RetargeterDisconnect( $retargeter ) ; } } } proc createSkFeedbackPipe(string $pCharacter, string $layerName ) { // string $lSrcStateOut; // string $lDstRetargeter; // string $lSrcState; ////////////////// // SrcCharacter // ////////////////// string $lSrcSK2State = hikGetOrBuildStateFromSk($pCharacter); $lSrcStateOut = $lSrcSK2State + ".OutputCharacterState"; ////////////////// // DstCharacter // ////////////////// string $lDstControlRig = hikGetControlRig($pCharacter); if( size($lDstControlRig) ) // With Dst Rig { hikBuildEffectorFromCharacterState($lSrcSK2State, $lDstControlRig, $layerName); // Connects both ends, src state and effectors string $lDstState2FK = hikBuildFKFromState($lDstControlRig,$layerName); // This one only connect the destination string $lDstState2FKIn = $lDstState2FK + ".InputCharacterState"; connectAttr -f $lSrcStateOut $lDstState2FKIn; } } /////////////////////////////////////////////////////////////////////// // Global methodes... /////////////////////////////////////////////////////////////////////// global proc hikBakeCharacter( int $displayOptions ) { string $currCharacter = hikGetCurrentCharacter(); if( $currCharacter != "" ) { string $preBakeCmd = "hikBakeCharacterPre( \""+$currCharacter+"\" )"; string $postBakeCmd = "hikBakeCharacterPost( \""+$currCharacter+"\" )"; performBakeSimulationArgList 2 { string($displayOptions), "animationList", $preBakeCmd, $postBakeCmd }; } } global proc hikBakeCharacterPre( string $character ) { // Callback methods used by hikBakeCharacter, should not be called directly // Prepare the skeleton to be baked on and select the bones to bake string $attributes[]; string $lGlobal2SkNode = hikGetStateToGlobalSk( $character, 0 ); if( $lGlobal2SkNode != "" ) { // Baking to custom Rig controlers $attributes = RetargeterDestinationAttributes( RetargeterGetName($character) ); } else { if( hikIsCharacterEnabled( $character ) ) { // All pairblends must be removed before baking data from the solver, otherwise new curves wil be crated and // we will end up with 2 curves by atttr // After the bake, connections from the solver to the sk will have been broken and replaced with anim curves. // The character will be disabled then string $state2sk[] = hikGetState2SKFromCharacter( $character ); if( size($state2sk)>0 ) { hikDisconnectSkFromCharacterState( $state2sk[0], $character, 1 ); hikConnectSkFromCharacterState( $state2sk[0], $character, 1 ); } } getHikNodeAttributesToBake( $character, $attributes , 0); } select -cl; select $attributes; } global proc hikBakeCharacterPost( string $character ) { // Callback methods used by hikBakeCharacter, should not be called directly // After the bake, the character is left in a bad state and connections // must be cleanned up disconnectBakedAttrs( $character ); hikShowHideSkOrRigOnSourceInput( $character ); // Refresh using evalDeferred since otherwise the refresh can happen // before the temp character is deleted. // evalDeferred( "hikUpdateLiveConnectionUI;hikSetCurrentSource \"\";hikUpdateCharacterControlsUI(false);" ); } global proc hikBakeLiveCharacter( int $displayOptions ) { string $character = hikGetCurrentCharacter(); // Request Remote character and wait for acknowledge string $liveNode = hikGetLiveStateClient( $character, false ); string $remoteAddress; if( size( $liveNode )>0 ) { $remoteAddress = `getAttr ($liveNode + ".serverName")`; } int $transactionId = `OneClickAcknowledgeCallback -rcb ("hikBakeLiveCallback "+$character+" \""+$character+"_TempCharacter\" "+$displayOptions )`; float $frameRate = `currentTime -update no -e 1sec`; OneClickFetchRemoteCharacter $character $transactionId $frameRate $remoteAddress; } global proc hikBakeLiveCallback( string $character, string $src, int $displayOptions, int $id, int $errorCode ) { // adjust the source reference offset because we are using offset from the // restination reference string $referenceNode[] = `listConnections -type transform -s true -d false ($src+".Reference")`; if( size( $referenceNode ) > 0 ) { setAttr ($referenceNode[0]+".translate") 0 0 0; setAttr ($referenceNode[0]+".rotate") 0 0 0; setAttr ($referenceNode[0]+".scale") 1 1 1; } hikSetDirectCharacterInputNoRetarget( $character, $src ); string $preBakeCmd = "hikBakeCharacterPre( \""+$character+"\" )"; string $postBakeCmd = "hikBakeCharacterPost( \""+$character+"\" );hikBakeLiveDeleteTempCharacter(\""+$src+"\")"; float $start = -1; float $end = -1; string $hips[] = `listConnections -s 1 -d 0 ( $src + ".Hips" )`; if( size( $hips ) > 0 ) { float $startSrc = `findKeyframe -w first -hi "below" $hips[0]`; float $endSrc = `findKeyframe -w last -hi "below" $hips[0]`; float $startMaya = `playbackOptions -q -minTime`; float $endMaya = `playbackOptions -q -maxTime`; $start = floor(min( $startSrc, $startMaya )); $end = ceil(max( $endSrc, $endMaya )); } performBakeSimulationArgList 3 { string($displayOptions),"animationList", $preBakeCmd, $postBakeCmd, string($start), string($end) }; } global proc hikBakeLiveDeleteTempCharacter( string $character ) { // Specialised version of delete character that remove the characterization, solvers and skeleton string $sk[] = hikGetSkeletonNodes( $character ); hikDeleteCharacter( $character ); for($node in $sk ) { // deleting other nodes may cause the current node to be already deleted. // make sure it is still there before trying to delete it. if(size(`ls $node`) > 0) { delete $node; } } } global proc hikBakeToControlRig( int $displayOptions ) { string $currCharacter = hikGetCurrentCharacter(); if( ($currCharacter != "") && hikIsDefinitionLocked($currCharacter) ) { string $preBakeCmd = "hikBakeToControlRigPre( \""+$currCharacter+"\" )"; string $postBakeCmd = "hikBakeToControlRigPost( \""+$currCharacter+"\" )"; performBakeSimulationArgList 2 {string($displayOptions), "animationList", $preBakeCmd, $postBakeCmd }; } else { error((uiRes("m_hikBakeOperation.kBakeUnlockedCharError"))); } } global proc hikBakeToControlRigPre( string $character ) { // Callback methods used by hikBakeToControlRig, should not be called directly // Prepare the Rig to be baked. global string $gHikBakeToControlRigLayer; $gHikBakeToControlRigLayer = ""; int $characterEnabled = hikIsCharacterEnabled( $character ); int $inputType= hikGetInputType( $character ); string $rig = hikGetControlRig( $character ); if( size( $rig ) == 0 ) { string $srcCharacter = ""; if( $characterEnabled && $inputType == 3 ) { $srcCharacter = hikGetRetargetCharacterInput( $character ); } $rig = hikDoCreateControlRig( $character, hikGetCharacterScale( $character ) ); hikSetRigLookAndFeel( $character, hikGetLastSelectedRigStyle( ) ); // reestablish original input mode lost when crating rig creation if( $characterEnabled ) { if( $inputType == 3 /* direct retarget*/ ) { hikSetCharacterInput( $character, $srcCharacter ); } else if( $inputType == 4 /* Live Input */ ) { hikSetLiveCharacterInput( $character ); } } else { hikEnableCharacter( $character, false ); } } global int $gHikPreBakeAlignState; $gHikPreBakeAlignState = `hikRigAlign -q -enable $rig`; if( !$characterEnabled || $inputType > 2 ) { // If in skeleton input, or if input type does not go through the rig, create a temporary override layer // that will overide the Rig input usin the transforms used for the synch $gHikBakeToControlRigLayer = `animLayer -override 1 -passthrough 1 "tempLayer"`; createSkFeedbackPipe( $character, $gHikBakeToControlRigLayer ); // Disable rig align which will prevent having to do useless character evaluations hikRigAlign -enable 0 $rig; } else { // Enable righ align to ensure that IK and FK will be baked in synch hikRigAlign -enable 1 $rig; } string $attributes[]; int $mask = hikGetBodyPartSelectionMask( $rig, true ); getHikNodeAttributesToBake( $rig, $attributes, $mask ); getHikEffAttributesToBake( $rig, $attributes, $mask ); select -cl; select $attributes; } proc string getinterpolationFromIndex(int $interpidx) { string $intermode = "none";//independent is 1 if($interpidx == 2) $intermode = "euler"; if($interpidx == 3) $intermode = "quaternion"; if($interpidx == 4) $intermode = "quaternionSlerp"; if($interpidx == 5) $intermode = "quaternionSquad"; return $intermode; } global proc hikBakeToControlRigPost( string $character ) { // Callback methods used by hikBakeToControlRig, should not be called directly // Cleanup temporary graph created for baking purpose // go in Rig input global string $gHikBakeToControlRigLayer; if( $gHikBakeToControlRigLayer != "" ) { // Delete that temporary layer wich will also trigger the deletion of the // SK to Rig nodes network delete $gHikBakeToControlRigLayer; $gHikBakeToControlRigLayer = ""; } hikSetRigInput( $character ); // Restore original character alignement state global int $gHikPreBakeAlignState; string $rig = hikGetControlRig( $character ); hikRigAlign -enable $gHikPreBakeAlignState $rig; hikUpdateLiveConnectionUI( ); hikSelectControlRigTab( ); hikUpdateCharacterControlsUI(false); //put rotation in default hik rig interpolation mode //baking always produce euler independant curve string $rigFKNodes[] = hikGetRigFKNodes($character,1); string $rigIKNodes[] = hikGetRigIkNodes($character); int $interpidxfk = `optionVar -q rotationInterpolationDefault`; int $interpidxik = `optionVar -q hikRotationInterpolationDefault`; for( $node in $rigFKNodes ) { string $intermode = getinterpolationFromIndex($interpidxfk); rotationInterpolation -c $intermode ($node + ".rotateX") ($node + ".rotateY") ($node + ".rotateZ"); } for( $node in $rigIKNodes ) { string $intermode = getinterpolationFromIndex($interpidxik); rotationInterpolation -c $intermode ($node + ".rotateX") ($node + ".rotateY") ($node + ".rotateZ"); } }