--tool to query animations and then generate face fx 1d sliders -- Load the common maxscript functions --include "rockstar/export/settings.ms" -- This is SLOW! filein "rockstar/export/settings.ms" -- This is fast -- Figure out the project theProjectRoot = RsConfigGetProjRootDir() theProject = RSConfigGetProjectName() theWildWest = RsConfigGetWildWestDir() theProjectConfig = RsConfigGetProjBinConfigDir() -- Load common functions filein (theWildWest + "script/max/Rockstar_North/character/includes/FN_common.ms") filein (theWildWest + "script/max/Rockstar_North/character/includes/FN_bone_tagger.ms") filein (theWildWest + "script/max/Rockstar_North/character/includes/FN_Rigging.ms") -- filein (theProjectRoot + "tools/dcc/current/max2010/scripts/pipeline/util/xml.ms") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- facialBoneArray = #( "FB_L_Brow_Out", "FB_Brow_Centre", "FB_L_Lid_Upper", "FB_L_Eye", "FB_L_CheekBone", "FB_UpperLipRoot", "FB_UpperLip", "FB_L_Lip_Top", "FB_R_Lip_Top", "FB_L_Lip_Corner", "FB_Jaw", "FB_LowerLipRoot", "FB_LowerLip", "FB_L_Lip_Bot", "FB_R_Lip_Bot", "FB_Tongue", "FB_R_Lid_Upper", "FB_R_Eye", "FB_R_CheekBone", "FB_R_Brow_Out", "FB_R_Lip_Corner" ) ambientArray = #( --array of ambient joysticks "Tongue", "L_Blink", "LookAT_Activator", "L_Cheek", "LowerLip", "UpperLip_Curl", "LowerLip_Curl", "UpperLip", "L_Mouth", "Jaw", "Mouth", "R_Eye", "L_Eye", "R_Mouth", "C_Brow", "Tongue_In_Out", "R_Cheek", "R_Brow", "L_Brow", "R_Blink" ) poseArray= #( --facefx 1d slider joystick Name and frame at which pose is derived and position for joystick -- #("Neutral", 0, [0.0,0,0]), #("open", 10, [0.1,0,0]), #("W", 20, [0.2,0,0]), #("ShCh", 30, [0.3,0,0]), #("PBM", 40, [0.4,0,0]), #("FV", 50, [0.5,0,0]), #("wide", 60, [0.6,0,0]), #("tBack", 70, [0.7,0,0]), #("tTeeth", 80, [0.8,0,0]), #("tRoof", 90, [0.9,0,0]), #("Blink_Left", 100, [0,0,-0.2]), #("Blink_Right", 110, [0.1,0,-0.3]), #("Eyebrow_Raise_Left", 120, [0.2,0,-0.2]), #("Eyebrow_Raise_Right", 130, [0.3,0,-0.3]), #("Squint_Left", 140, [0.4,0,-0.2]), #("Squint_Right", 150, [0.5,0,-0.3]), -- #("Head_Pitch_Pos", 200), -- #("Head_Yaw_Pos", 210), -- #("Head_Roll_Pos", 220), -- #("LeftEye_Pitch_Pos", 230, [0.6,0,-0.2]), -- #("LeftEye_Yaw_Pos", 240, [0.7,0,-0.3]), -- #("RightEye_Pitch_Pos", 250, [0.8,0,-0.2]), -- #("RightEye_Yaw_Pos", 260, [0.9,0,-0.3]), -- // Negative Rotations below -- #("Head_Pitch_Neg", 270), -- #("Head_Yaw_Neg", 280), -- #("Head_Roll_Neg", 290), -- #("LeftEye_Pitch_Neg", 300, [0.0,0,-0.4]), -- #("LeftEye_Yaw_Neg", 310, [0.1,0,-0.5]), -- #("RightEye_Pitch_Neg", 320, [0.2,0,-0.4]), -- #("RightEye_Yaw_Neg", 330, [0.3,0,-0.5]), -- #("AU1-Inner_Brow_Raiser_Left", 400, [0.4,0,-0.4]), -- #("AU1-Inner_Brow_Raiser_Right", 410, [0.5,0,-0.5]), -- #("AU2-Outer_Brow_Raiser_Left", 420, [0.6,0,-0.4]), -- #("AU2-Outer_Brow_Raiser_Right", 430, [0.7,0,-0.5]), -- #("AU4-Brow_Lowerer", 440, [0.8,0,-0.4]), -- #("AU10-Upper_Lip_Raiser", 450, [0.9,0,-0.5]), -- #("AU15-Lip_Corner_Depressor_Left", 460, [0,0,-0.6]), -- #("AU15-Lip_Corner_Depressor_Right", 470, [0.1,0,-0.7]), -- #("AU16-Lower_Lip_Depressor", 480, [0.2,0,-0.6]), -- #("AU20-Lip_Stretcher", 490, [0.3,0,-0.7]), -- #("AU23-Lip_Tightener", 500, [0.4,0,-0.6]), -- #("AU26-Jaw_Drop", 510, [0.5,0,-0.7]), -- #("AU29-Jaw_Sideways", 530, [0.6,0,-0.6]), #("Cheek_Up_Left", 600, [0.7,0,-0.7]), #("Cheek_Up_Right", 610, [0.8,0,-0.6]), #("Snarl_Left", 620, [0.9,0,-0.7]), #("Snarl_Right", 630, [0,0,-0.8]) ) dupJoystickArray = #( --array of ambient joysticks which we need to disconnect after facefx joystick creation -- "Tongue", "L_Blink", -- "LookAT_Activator", -- "L_Cheek", -- "LowerLip", -- "UpperLip_Curl", -- "LowerLip_Curl", -- "UpperLip", -- "L_Mouth", -- "Jaw", -- "Mouth", -- "R_Eye", -- "L_Eye", -- "R_Mouth", -- "C_Brow", -- "Tongue_In_Out", -- "R_Cheek", -- "R_Brow", -- "L_Brow", "R_Blink" ) -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- fn disconnectAmbientExpr joystickName = --this will disconnect expressions for specified joystickName ( -- for o in objects do for o in objects do ( if (substring o.name 1 3 ) == "FB_" do ( if classOf o.position.controller == position_list do ( for p = 1 to o.position.controller.count do ( if o.position.controller[p].name == joystickName do ( o.position.controller.delete p print ("Disconnected "+joystickName+" position from "+o.name) ) ) ) if classOf o.rotation.controller == rotation_list do ( for r = 1 to o.rotation.controller.count do ( if o.rotation.controller[r].name == joystickName do ( o.rotation.controller.delete r print ("Disconnected "+joystickName+" rotation from "+o.name) ) ) ) if classOf o.scale.controller == scale_list do ( for s = 1 to o.scale.controller.count do ( if o.scale.controller[s].name == joystickName do ( o.scale.controller.delete s print ("Disconnected "+joystickName+" scale from "+o.name) ) ) ) ) ) ) fn create1DSlider sliderCount mySliderName sliderPos = ( -- call the joystick creation function with the UI data jstemp = mySliderName as string -- jstemp = edtSliderName.text as string jsname = replacespace jstemp jstyle = 2 jpos = sliderPos -- the default position on screen. This can overridden later, perhaps by mouse click. -- Create a joystick slider and select it seljoy = createJoystick ("FFX_CTRL_"+jsname) jstyle jpos 1 -- This can also be copied and pasted to a script to auto build a controller ) fn create1DExpr facialBone contType fbAxis poseName ambientName scalarNames scalarTargets jsMultiplier scalarToChangeIndex theexpr = ( if contType == "Position" then ( contTypeB = "Position" ) else ( contTypeB = "Euler" ) ambientJoystickName = (substring ambientName 6 30 ) --first off add a new controller to the face bone and name it to match the pose contCount = ("$"+facialBone.name+"."+contType+".controller.count" ) contCount = execute contCount -- print ("contCount = "+(contCount as string)) newCont = ("$"+facialBone.name+"."+contType+".Available.controller = "+contTypeB+"_XYZ ()") newCnt = execute newCont newControllerNameStr = ("FFX_"+poseName+"_"+ambientName) newCont = ("$"+facialBone.name+"."+contType+".controller"+".setName "+((contCount + 1) as string)+" "+"\""+newControllerNameStr +"\"") newCont = execute newCont --now we can add a float expression to the relevant controller axis fcStr = ( "$"+facialBone.name+"."+contType+".controller."+newControllerNameStr+"."+fbAxis+"_"+contType+".controller = Float_Expression() ") newFcStr = execute fcStr --now we can start to edit that expression --first need to find the original driving scalar and then swap that to be the faceFX joystick with its multiplier originalScalarName = undefined replaceMentScalarName = undefined for i = 1 to scalarNames.count do --this should loop through the array of scalars and their controllers and create a scalar for each ( if i == scalarToChangeIndex then ( originalScalarName = scalarNames[i] replaceMentScalarName =( "("+"("+poseName+" / "+(jsMultiplier as string)+")"+" * 1)") --ok we're going to point this scalar at the Y movement of the appropriate faceFX joystick ffxJoystick = getNodeByName ("FFX_CTRL_"+poseName) SVN = poseName --name of the scalar scalContStr = ("$"+ffxJoystick.name+".position.controller.Zero_Pos_XYZ.controller.Y_Position.controller") --actually controller the scale points to scalCont = execute scalContStr ) else ( SVN = scalarNames[i] --name of the scalar scalContStr = (scalarTargets[i] as string) --actually controller the scale points to scalCont = execute scalContStr ) newfcStr.AddScalarTarget SVN scalCont --add scalar pointing to the controller of the scalar object ) --now we need to edit 'theexpr' and replace the scalarNames[i] with the new 'scalarNames[i]' plus the jsMultiplier --so we need to test the 'theExpr' string and replace originalScalarName with newScalarName originalScalarNameLength = originalScalarName.count replaceThisString = theExpr for r = 1 to 3 do --need to loop through the expression string 3 times because the original scalar name is in the expression 3 times ( newExprStrStart = findString replaceThisString originalScalarName replaceThisString = replace replaceThisString newExprStrStart originalScalarNameLength replaceMentScalarName ) -- print "--------------------------" -- print "--------------------------" -- print ("origninal expression:\r\n") -- print theExpr -- print "--------------------------" -- print ("new expression:\r\n") -- print replaceThisString -- print "--------------------------" -- print "--------------------------" newfcStr.SetExpression replaceThisString ) -- fn readExpression controller joystickAxis facialBone poseName axisUsed transType = fn readExpression controller joystickAxis facialBone jsAx fbAx contType poseName ambientName jsMultiplier= ( joystickAxis = exprformaxobject joystickAxis scalarNames=#() scalarTargets=#() -- print ("Controller = "+controller) theExprStr = ((controller as string)+".getExpression()") theexpr = execute theExprStr theScalarStr = ((controller as string)+".NumScalars()") theScalarCount = execute theScalarStr if theScalarCount != 4 do --need to remember there are always 4 for ticks, frames, secs and normalised-time ( for i = 5 to theScalarCount do ( thisScalarNameStr = ((controller as string)+".GetScalarName "+(i as string)) thisScalarName = execute thisScalarNameStr appendIfUnique scalarNames thisScalarName ) ) for i = 1 to scalarNames.count do ( thisScalarTgtStr = ((controller as string)+".GetScalarTarget "+"\""+scalarNames[i]+"\""+" asController:true") thisScalarTgt = execute thisScalarTgtStr -- print ("Scalar value = "+(thisScalarTgt.value as string)) thisScalarTgtCont = exprformaxobject thisScalarTgt appendIfUnique scalarTargets (thisScalarTgtCont as string) -- print ("appending "+(thisScalarTgtCont as string)+" to controller array...") ) --now we need to check if any of the scalaras match the joystick axis. --if they do then we can do a create expression. for i = 1 to scalarTargets.count do ( -- print ("testing "+(scalarTargets[i] as string)+" against "+(joystickAxis as string)) if scalarTargets[i] == joystickAxis do ( -- print ("WOOHOO "+(scalarTargets[i] as string)+" matched "+(joystickAxis as string)+"!") --ok we now know that this jsAxis matches this expression. --so we need to tell the expression creator what axis to add a controller to on what bone -- print "\r\n" -- print ("***************** Running create1dExpr on "+facialBone.name+", faceboneAxis "+fbax+", controller type "+conttype+", for pose "+poseName+" *******************") -- print "\r\n" scalarToChangeIndex = i create1DExpr facialBone contType fbAx poseName ambientName scalarNames scalarTargets jsMultiplier scalarToChangeIndex theexpr ) ) ) fn passDataToReadExpr ambientName jsAxis boneNumber jsAx poseName jsMultiplier = ( for fb = 1 to facialBoneArray.count do ( facialBone = getNodeByName (facialBoneArray[fb]+boneNumber) posCount = facialBone.position.controller.count for cC = 1 to posCount do ( contType = "Position" contName = facialBone.position.controller.getName cC if contName == (ambientName) do ( -- print ("found a "+ambientName+" Position controller on "+facialBone.name) --ok so now we need to query if the x y or z pos controller are float expressions --ok so now we need to query if the x y or z pos controller are float expressions if (facialBone.position.controller[cC].X_position.controller as string) == "Controller:Float_Expression" do ( --now we need to query if the jsAxis is the driver in the expression -- print ("X ROT was an EXPR") controller = ("$"+facialBone.name+".position.controller["+(cC as string)+"].X_position.controller") fbAx = "X" readExpression controller jsAxis facialBone jsAx fbAx contType poseName ambientName jsMultiplier ) if (facialBone.position.controller[cC].Y_position.controller as string) == "Controller:Float_Expression" do ( -- print ("Y ROT was an EXPR") -- controller = facialBone.position.controller[cC].Y_position.controller controller = ("$"+facialBone.name+".position.controller["+(cC as string)+"].Y_position.controller") fbAx = "Y" readExpression controller jsAxis facialBone jsAx fbAx contType poseName ambientName jsMultiplier ) if (facialBone.position.controller[cC].Z_position.controller as string) == "Controller:Float_Expression" do ( -- print ("Z ROT was an EXPR") -- controller = facialBone.position.controller[cC].Z_position.controller controller = ("$"+facialBone.name+".position.controller["+(cC as string)+"].Z_position.controller") fbAx = "Z" readExpression controller jsAxis facialBone jsAx fbAx contType poseName ambientName jsMultiplier ) ) ) rotCount = facialBone.rotation.controller.count for cC = 1 to rotCount do ( contType = "Rotation" contName = facialBone.rotation.controller.getName cC if contName == (ambientName) do ( -- print ("found a "+ambientName+" Rotation controller on "+facialBone.name) --ok so now we need to query if the x y or z pos controller are float expressions if (facialBone.rotation.controller[cC].X_rotation.controller as string) == "Controller:Float_Expression" do ( --now we need to query if the jsAxis is the driver in the expression -- print ("X ROT was an EXPR") controller = ("$"+facialBone.name+".rotation.controller["+(cC as string)+"].X_Rotation.controller") fbAx = "X" readExpression controller jsAxis facialBone jsAx fbAx contType poseName ambientName jsMultiplier ) if (facialBone.rotation.controller[cC].Y_rotation.controller as string) == "Controller:Float_Expression" do ( -- print ("Y ROT was an EXPR") -- controller = facialBone.rotation.controller[cC].Y_Rotation.controller controller = ("$"+facialBone.name+".rotation.controller["+(cC as string)+"].Y_Rotation.controller") fbAx = "Y" readExpression controller jsAxis facialBone jsAx fbAx contType poseName ambientName jsMultiplier ) if (facialBone.rotation.controller[cC].Z_rotation.controller as string) == "Controller:Float_Expression" do ( -- print ("Z ROT was an EXPR") -- controller = facialBone.rotation.controller[cC].Z_Rotation.controller controller = ("$"+facialBone.name+".rotation.controller["+(cC as string)+"].Z_Rotation.controller") fbAx = "Z" readExpression controller jsAxis facialBone jsAx fbAx contType poseName ambientName jsMultiplier ) ) ) ) ) fn parsePoses boneNumber = ( clearListener() for i = 1 to poseArray.count do ( --first set frame to the 2nd element of this item ie frame number poseFrame = posearray[i][2] poseName = poseArray[i][1] sliderTime = poseFrame --now for each joystick query their x and y positions for js = 1 to ambientArray.count do ( thisJoystick = getNodeByName ("CTRL_"+ambientArray[js]) jsPos = in coordsys parent thisJoystick.position myX = jsPos[1] newX = ( 1 / myX ) -- jsXMultiplier = myX * newX as integer jsXMultiplier = newX as float myY = jsPos[2] newY = ( 1 / myY ) -- jsYMultiplier = myY * newY as integer jsYMultiplier = newY as float if myX != 0 do ( -- print ("jsXMultiplier ="+(jsXMultiplier as string)+" for "+thisJoystick.name+" with pose "+poseName) -- print ("derived by myX ( 1 / "+(myX as string)+")"+" gives "+(jsXMultiplier as string)) --ok the joystick has moved in the x position -- print ("Need to create X expr on "+ambientArray[js]+" for pose "+poseName) jsAxis = thisJoystick.position.controller.zero_pos_xyz.x_position.controller jsAx = "X" passDataToReadExpr ambientArray[js] jsAxis boneNumber jsAx poseName jsXMultiplier ) if myY != 0 do ( -- print ("jsYMultiplier ="+(jsYMultiplier as string)+" for "+thisJoystick.name+" with pose "+poseName) -- print ("derived by myY ( 1 / "+(myY as string)+")"+" gives "+(jsYMultiplier as string)) -- print ("Need to create Y expr on "+ambientArray[js]+" for pose "+poseName) jsAxis = thisJoystick.position.controller.zero_pos_xyz.Y_position.controller jsAx = "Y" passDataToReadExpr ambientArray[js] jsAxis boneNumber jsAx poseName jsYMultiplier ) ) sliderTime = 0f ) ) fn connect1dToAmbientJoysticks = ( --firstly make the 1d joysticks for sl = 1 to poseArray.count do -- for sl = 1 to 2 do ( create1DSlider 1 poseArray[sl][1] poseArray[sl][3] ) --now create the facefx text parent obj faceFXText = text size:0.25 kerning:0 leading:0 transform:(matrix3 [1,0,0] [0,0,1] [0,-1,0] [0.215069,0,-0.0633061]) isSelected:on faceFXText.text = "FaceFX" faceFXText.render_displayRenderMesh = true faceFXText.thickness = 0.01 faceFXText.sides = 3 faceFXText.name = "FaceFX" in coordsys world faceFXText.pos = [0.45,0,0.1] --now link all the ffx rectangles to the faceFX object for o in objects where (substring o.name 1 8) == "FFX_RECT" do ( o.parent = faceFXText ) faceFXText.scale = [0.3,0.3,0.3] in coordsys world faceFXText.pos = [0.5,0.0,1.8] --setup done. --now we need to find all the facial bones in the scene faceBoneIterations = #() for o in objects do ( if substring o.name 1 8 == "FaceRoot" do ( --ok we've found a faceroot node and therefore a rig so we need to find the number of it rigNumber = (substring o.name 14 4) appendIfUnique faceBoneIterations rigNumber ) ) --start the clever stuff! for fNo = 1 to faceBoneIterations.count do ( --boneNumber is the suffix numbers on the bones --thisBoneNumber = "_000" thisBoneNumber = faceBoneIterations[fNo] parsePoses thisBoneNumber ) ) -- generate1dStruct() clearListener() connect1dToAmbientJoysticks() for i = 1 to dupJoystickArray.count do ( disconnectAmbientExpr dupJoystickArray[i] ) print ("FaceFx creation complete.") -- select $FB_R_Lip_Corner_000