struct sGTAVFacialConverter ( --------------------------------------------------------------------------- -- LOCALS --------------------------------------------------------------------------- solverAssetPath = (RsConfigGetProjRootDir core:true) + "art/animation/resources/characters/solver_assets/", assetsPath = (RsConfigGetAssetsDir core:true) + "anim/expressions/", outputPath = undefined, tempPath = this.solverAssetPath + "temp/", scriptRoot = (RsConfigGetWildwestDir() + "script/3dsMax/Characters/Setup/GTAVFacialConverter/"), convertBat = this.scriptRoot + "Bin/convertexpressions.bat", makeExprBat = this.scriptRoot + "Bin/makeexpressions.bat", idXML = this.scriptRoot + "Config/ExpressionIDs.xml", idTable = DotNetObject "System.Collections.HashTable", clampFile = this.scriptRoot + "Config/scalesToClamp.xml", scalesToClamp = #(), pedName = undefined, expressionList = #(), fileList = #(), skelset = undefined, --------------------------------------------------------------------------- -- FUNCTIONS --------------------------------------------------------------------------- --------------------------------------------------------------------------- -- make sure save paths exist, otherwise create them fn ValidateSavePath = ( this.outputPath = this.solverAssetPath + this.pedName + "/" local pathsToValidate = #( this.outputPath, (this.outputPath + "skeleton/"), (this.outputPath + "expressions/") ) for p in pathsToValidate do ( if( not DoesFileExist p )then MakeDir p all:true ) ), --------------------------------------------------------------------------- -- attempt to get the expressions for this ped fn GetPedExpressions = ( if( this.pedName != undefined )then ( local exprPath = this.assetsPath + this.pedName + "/components/" if( DoesFileExist exprPath )then ( local dirFiles = GetFiles (exprPath + "*.xml") for x in dirFiles do ( local fName = GetFileNameFile x if( MatchPattern fName pattern:"head_000_r" or MatchPattern fName pattern:"teef_000_u" )then AppendIfUnique this.expressionList x ) ) ) ), --------------------------------------------------------------------------- -- replace expression IDs that are incorrect due to the ConvertExpressions process fn FixExprIDs x = ( local xml = RSTA_XML_IO xmlFile:x local exprsToClamp = #() local objIter = (xml.root.SelectNodes "//object").GetEnumerator() while objIter.MoveNext() do ( local id = objIter.current.GetAttribute "id" local track = objIter.current.GetAttribute "track" if( this.idTable.item[id] != undefined )then ( objIter.current.SetAttribute "id" this.idTable.item[id] id = this.idTable.item[id] ) if( track == "facialControl" )then ( objIter.current.SetAttribute "track" "facialTranslation" objIter.current.SetAttribute "type" "vector3" objIter.current.SetAttribute "component" "x" ) if( FindItem this.scalesToClamp id != 0 and track == "boneScale" )then ( local tarName = objIter.current.GetAttribute "name" Append exprsToClamp tarName ) ) local exprIter = (xml.root.SelectNodes "//expression").GetEnumerator() while exprIter.MoveNext() do ( local target = exprIter.current.GetAttribute "target" if( FindItem exprsToClamp target != 0 )then exprIter.current.SetAttribute "string" "vec( 1, 1, 1 )" ) xml.Save saveWarning:false xml.Dispose() ), --------------------------------------------------------------------------- -- run an external process fn ExternalProcess xml bat = ( -- local msgList = #() -- local errList = #() local cmd = ( xml as string ) local convertProcess = RsProcess() convertProcess.RunProcess bat cmd workingDir:(RsConfigGetProjRootDir()) --messageList:&msgList errorList:&errList ), --------------------------------------------------------------------------- -- convert expressions to new format and them output the .expr file fn ProcessExpressions = ( for xml in this.expressionList do ( local targetName = GetFileNameFile xml local targetFile = (this.outputPath + "expressions/" + targetName + ".xml") local convertSuccess = this.ExternalProcess xml this.convertBat if( convertSuccess != 0 )then Format "Failed in ConvertExpressions for %\n" xml else ( if( DoesFileExist targetFile )then ( gRsPerforce.Edit targetFile DeleteFile targetFile CopyFile (this.tempPath + "expr.xml") targetFile ) else ( CopyFile (this.tempPath + "expr.xml") targetFile gRsPerforce.Add_Or_Edit targetFile ) this.FixExprIDs targetFile local makeExprSuccess = this.ExternalProcess targetFile this.makeExprBat if( makeExprSuccess != 0 )then Format "Failed in MakeExpressions for %\n" targetFile ) ) ), --------------------------------------------------------------------------- -- build a table of the hashes for all valid facial bones and controllers fn CollectHashIDTable = ( local xml = RSTA_XML_IO xmlFile:this.idXML local objIter = (xml.root.SelectNodes "//object").GetEnumerator() while objIter.MoveNext() do ( this.idTable.Add (objIter.current.GetAttribute "id") (objIter.current.GetAttribute "name") ) xml.Dispose() ), --------------------------------------------------------------------------- -- collect a list of scale controllers that can be clamped to [1,1,1] fn CollectScalesToClamp = ( local xml = RSTA_XML_IO xmlFile:this.clampFile local objectIter = (xml.root.SelectNodes "//object").GetEnumerator() while objectIter.MoveNext() do Append this.scalesToClamp (objectIter.current.GetAttribute "name") xml.Dispose() ), --------------------------------------------------------------------------- -- align a bone to another and freeze transform fn AlignAndFreeze b target = ( b.transform = target.transform ClearSelection() Select b FreezeTransform() ), --------------------------------------------------------------------------- -- reparent and freeze bones fn ParentAndFreeze b bParent = ( b.parent = bParent ClearSelection() Select b FreezeTransform() ), --------------------------------------------------------------------------- -- parent the on-face controls into the skeleton for export fn SetUpFacialOffCtrls = ( local offRoot = $'facialRoot_C_OFF' local offJaw = $'jaw_C_OFF' local facialRoot = $'FACIAL_facialRoot' if( offRoot != undefined and facialRoot != undefined and offJaw != undefined)then ( local offRootChildren = for c in offRoot.children collect c for c in offRootChildren do c.parent = undefined local offJawChildren = for c in offJaw.children collect c for c in offJawChildren do c.parent = undefined this.AlignAndFreeze offJaw $'FACIAL_jaw' this.AlignAndFreeze offRoot $'FACIAL_facialRoot' for c in offJawChildren do this.ParentAndFreeze c offJaw for c in offRootChildren do this.ParentAndFreeze c offRoot local skelRoot = $'SKEL_ROOT' if( skelRoot != undefined )then offRoot.parent = skelRoot ) else Format "Missing one of the Facial root nodes: %, %, \n" "FACIAL_facialRoot" "facialRoot_C_OFF" "jaw_C_OFF" ), --------------------------------------------------------------------------- -- remove the on-face controls from the skeleton after export fn CleanUpFacialOffCtrls = ( local offRoot = $'facialRoot_C_OFF' if( offRoot != undefined )then offRoot.parent = undefined ), --------------------------------------------------------------------------- -- export the skeleton fn ExportSkeleton = ( this.SetUpFacialOffCtrls() local skelExporter = sGTAVSkeletonExporter pedName:this.pedName skelExporter.Init() skelExporter.WriteSkelset this.solverAssetPath this.outputPath skelExporter.WriteSkeleton() this.CleanUpFacialOffCtrls() ), --------------------------------------------------------------------------- -- initialize the clamp and ID table pvariables fn Init = ( this.CollectHashIDTable() this.CollectScalesToClamp() ) )