From d05ec6738f7fd2a09c2cad76c1e2a9f4f5ec5dcc Mon Sep 17 00:00:00 2001 From: KINDNICK <68893236+KINDNICK@users.noreply.github.com> Date: Tue, 29 Jul 2025 01:26:39 +0900 Subject: [PATCH] =?UTF-8?q?Add=20:=20=ED=8E=98=EC=9D=B4=EC=85=9C=20?= =?UTF-8?q?=EB=85=B9=ED=99=94=20=EC=95=88=EB=90=98=EB=8A=94=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MixamoToBiped_Converter - y up.ms | 1759 +++++++++++++++++ .../MixamoToBiped_Converter - z up.ms | 1759 +++++++++++++++++ 3ds max script/MixamoToBiped_Converter.ms | 1759 +++++++++++++++++ .../Scripts/CharacterFacialData.cs | 3 + .../Scripts/FaceAnimationRecorder.cs | 133 +- 5 files changed, 5399 insertions(+), 14 deletions(-) create mode 100644 3ds max script/MixamoToBiped_Converter - y up.ms create mode 100644 3ds max script/MixamoToBiped_Converter - z up.ms create mode 100644 3ds max script/MixamoToBiped_Converter.ms diff --git a/3ds max script/MixamoToBiped_Converter - y up.ms b/3ds max script/MixamoToBiped_Converter - y up.ms new file mode 100644 index 00000000..8ce647a9 --- /dev/null +++ b/3ds max script/MixamoToBiped_Converter - y up.ms @@ -0,0 +1,1759 @@ +/* + MixamoToBiped Converter - No Axis Twist Version + Extracted from TOOLS_MixamoToBiped_v1.0.51.ms + + 이 스크립트는 Mixamo T-Pose 아바타를 3ds Max Biped으로 변환합니다. + + 🎯 특징 (축 변환 제거 버전): + - Biped 로컬 축 정의 유지 (MapTMAxis 호출 제거) + - 기존 T-Pose 자세 매칭 알고리즘 유지 + - IK 정렬과 기하학적 계산 보존 + - 크기와 위치 조정은 정상 동작 + - 스킨 가중치 완벽 이전 + + 사용법: + 1. Mixamo T-Pose FBX 파일을 임포트 + 2. 스크립트 실행 (자동으로 변환 시작) + + Author: Based on Ishak Suryo Laksono's work + Modified: Removed MapTMAxis calls to preserve Biped local axes +*/ + +-- ================================== 핵심 데이터 (원본 그대로) =============================================== + +-- 믹사모 본 이름들 (순서 중요 - 절대 변경 금지) +global MXNames =#("mixamorig:Hips","mixamorig:LeftUpLeg","mixamorig:LeftLeg","mixamorig:LeftFoot","mixamorig:LeftToeBase","mixamorig:RightUpLeg","mixamorig:RightLeg", + "mixamorig:RightFoot","mixamorig:RightToeBase","mixamorig:Spine","mixamorig:Spine1","mixamorig:LeftShoulder","mixamorig:LeftArm","mixamorig:LeftForeArm", + "mixamorig:LeftHand","mixamorig:LeftHandIndex1","mixamorig:LeftHandIndex2","mixamorig:LeftHandIndex3","mixamorig:LeftHandMiddle1","mixamorig:LeftHandMiddle2", + "mixamorig:LeftHandMiddle3","mixamorig:LeftHandPinky1","mixamorig:LeftHandPinky2","mixamorig:LeftHandPinky3","mixamorig:LeftHandRing1", + "mixamorig:LeftHandRing2","mixamorig:LeftHandRing3","mixamorig:LeftHandThumb1","mixamorig:LeftHandThumb2","mixamorig:LeftHandThumb3", + "mixamorig:Neck","mixamorig:Head","mixamorig:RightShoulder","mixamorig:RightArm","mixamorig:RightForeArm","mixamorig:RightHand", + "mixamorig:RightHandIndex1","mixamorig:RightHandIndex2","mixamorig:RightHandIndex3","mixamorig:RightHandMiddle1","mixamorig:RightHandMiddle2","mixamorig:RightHandMiddle3", + "mixamorig:RightHandPinky1","mixamorig:RightHandPinky2","mixamorig:RightHandPinky3","mixamorig:RightHandRing1","mixamorig:RightHandRing2", + "mixamorig:RightHandRing3","mixamorig:RightHandThumb1","mixamorig:RightHandThumb2","mixamorig:RightHandThumb3") + +-- 믹사모 -> 바이패드 매핑 (순서 중요 - 절대 변경 금지) +global MXPairs = #(datapair "mixamorig:Hips" "Bip001 Pelvis",datapair "mixamorig:Spine" "Bip001 Spine",datapair "mixamorig:Spine1" "Bip001 Spine1",datapair "mixamorig:Neck" "Bip001 Neck",datapair "mixamorig:Head" "Bip001 Head",datapair "mixamorig:RightShoulder" "Bip001 R Clavicle",datapair "mixamorig:RightArm" "Bip001 R UpperArm",datapair "mixamorig:RightForeArm" "Bip001 R Forearm",datapair "mixamorig:RightHand" "Bip001 R Hand",datapair "mixamorig:RightHandThumb1" "Bip001 R Finger0",datapair "mixamorig:RightHandThumb2" "Bip001 R Finger01",datapair "mixamorig:RightHandThumb3" "Bip001 R Finger02",datapair "mixamorig:RightHandIndex1" "Bip001 R Finger1",datapair "mixamorig:RightHandIndex2" "Bip001 R Finger11",datapair "mixamorig:RightHandIndex3" "Bip001 R Finger12",datapair "mixamorig:RightHandMiddle1" "Bip001 R Finger2",datapair "mixamorig:RightHandMiddle2" "Bip001 R Finger21",datapair "mixamorig:RightHandMiddle3" "Bip001 R Finger22",datapair "mixamorig:RightHandRing1" "Bip001 R Finger3",datapair "mixamorig:RightHandRing2" "Bip001 R Finger31",datapair "mixamorig:RightHandRing3" "Bip001 R Finger32",datapair "mixamorig:RightHandPinky1" "Bip001 R Finger4",datapair "mixamorig:RightHandPinky2" "Bip001 R Finger41",datapair "mixamorig:RightHandPinky3" "Bip001 R Finger42",datapair "mixamorig:LeftShoulder" "Bip001 L Clavicle",datapair "mixamorig:LeftArm" "Bip001 L UpperArm",datapair "mixamorig:LeftForeArm" "Bip001 L Forearm",datapair "mixamorig:LeftHand" "Bip001 L Hand",datapair "mixamorig:LeftHandThumb1" "Bip001 L Finger0",datapair "mixamorig:LeftHandThumb2" "Bip001 L Finger01",datapair "mixamorig:LeftHandThumb3" "Bip001 L Finger02",datapair "mixamorig:LeftHandIndex1" "Bip001 L Finger1",datapair "mixamorig:LeftHandIndex2" "Bip001 L Finger11",datapair "mixamorig:LeftHandIndex3" "Bip001 L Finger12",datapair "mixamorig:LeftHandMiddle1" "Bip001 L Finger2",datapair "mixamorig:LeftHandMiddle2" "Bip001 L Finger21",datapair "mixamorig:LeftHandMiddle3" "Bip001 L Finger22",datapair "mixamorig:LeftHandRing1" "Bip001 L Finger3",datapair "mixamorig:LeftHandRing2" "Bip001 L Finger31",datapair "mixamorig:LeftHandRing3" "Bip001 L Finger32",datapair "mixamorig:LeftHandPinky1" "Bip001 L Finger4",datapair "mixamorig:LeftHandPinky2" "Bip001 L Finger41",datapair "mixamorig:LeftHandPinky3" "Bip001 L Finger42",datapair "mixamorig:RightUpLeg" "Bip001 R Thigh",datapair "mixamorig:RightLeg" "Bip001 R Calf",datapair "mixamorig:RightFoot" "Bip001 R Foot",datapair "mixamorig:RightToeBase" "Bip001 R Toe0",datapair "mixamorig:LeftUpLeg" "Bip001 L Thigh",datapair "mixamorig:LeftLeg" "Bip001 L Calf",datapair "mixamorig:LeftFoot" "Bip001 L Foot",datapair "mixamorig:LeftToeBase" "Bip001 L Toe0") + +-- 제외할 본들 (원본 방식) +-- global excludeBones =#("mixamorig:HeadTop_End","mixamorig:RightHandThumb4","mixamorig:RightHandIndex4","mixamorig:RightHandMiddle4","mixamorig:RightHandRing4", +-- "mixamorig:RightHandPinky4","mixamorig:LeftHandThumb4","mixamorig:LeftHandIndex4","mixamorig:LeftHandMiddle4","mixamorig:LeftHandRing4","mixamorig:LeftHandPinky4", +-- "mixamorig:RightToe_End","mixamorig:LeftToe_End") +global excludeBones =#("mixamorig:HeadTop_End") + +-- 스파인 세그먼트 개수 +global spineSegments = 2 + +-- 특수 스켈레톤 구조 감지 변수들 +global legParentIsSpine = false +global shoulderParentIsNeck = false +global shoulderParentIsSpine = false + + + +-- 발 각도 설정 +global footDegree = 50 +global flipThreshold = 178 + +-- ================================== 유틸리티 함수들 (원본 그대로) =============================================== + +fn pointLineProj pA pB pC = ( + local vAB=pB-pA + local vAC=pC-pA + local d=dot (normalize vAB) (normalize vAC) + (pA+(vAB*(d*(length vAC/length vAB)))) +) + +fn findFootAngle =( + local foots = $'mixamorig:*Foot' as array + if foots.count==0 do return 35 + + local posArr = for i in foots collect i.pos[3] + local idx = findItem posArr (amin(posArr)) + local foot = foots[idx] + local toe = foot.children[1] + if not isvalidnode toe do return 35 + + local p1 = foot.pos + local p2 = toe.pos + local v1 = p1-p2 + p2[3] = p1[3] + local v2 = p1-p2 + acos(dot(normalize v1) (normalize v2)) +) + +fn getHierarchy objArr out= +( + for i in objArr do + ( + appendifunique out i + child = i.children + if child.count>0 do + ( + getHierarchy child out + ) + ) + out +) + +fn getSkinnedObj = ( + skn = getClassInstances skin + objArr = #() + for i in skn do( + for obj in refs.dependentNodes i do appendIfunique objArr obj + ) + objArr +) + +-- 위치 기반으로 실제 본 체인인지 확인하는 함수 +fn isRealBoneChain parentBone childBone tolerance:0.1 =( + if not isValidNode parentBone or not isValidNode childBone then return false + + -- 부모 본의 끝점 계산 (자식이 있으면 자식 방향, 없으면 부모 방향의 반대) + local parentEnd = parentBone.pos + if parentBone.children.count > 0 then ( + -- 가장 가까운 자식 본 방향으로 본 길이 계산 + local closestChild = parentBone.children[1] + local minDist = distance parentBone.pos closestChild.pos + for child in parentBone.children do ( + local dist = distance parentBone.pos child.pos + if dist < minDist do ( + minDist = dist + closestChild = child + ) + ) + parentEnd = closestChild.pos + ) else if parentBone.parent != undefined then ( + -- 부모가 있으면 부모->현재 방향으로 연장 + local direction = normalize (parentBone.pos - parentBone.parent.pos) + local boneLength = distance parentBone.parent.pos parentBone.pos + parentEnd = parentBone.pos + direction * boneLength + ) + + -- 자식 본의 시작점과 부모 본의 끝점 거리 확인 + local dist = distance parentEnd childBone.pos + return dist <= tolerance +) + +fn ScanBranchHRC obj out:#() = ( + local child = obj.children + if child.count > 0 then ( + -- 실제 본 체인으로 연결된 자식 찾기 (위치 기반) + local chainChild = undefined + for c in child do ( + if isRealBoneChain obj c then ( + chainChild = c + exit -- 첫 번째로 체인 연결된 자식만 사용 + ) + ) + + if chainChild != undefined then ( + -- 체인으로 연결된 자식이 있으면 계속 추적 + append out chainChild + ScanBranchHRC chainChild out:out + ) else ( + -- 체인이 끊어지면 undefined 추가 + append out undefined + ) + + -- 체인에 포함되지 않은 나머지 자식들은 별도 처리 + for i in child where i != chainChild do ( + append out i + ScanBranchHRC i out:out + ) + ) else ( + append out undefined + ) + out +) + +fn getBranchHRC obj =( + local scan = ScanBranchHRC obj + local out = #() + local new = undefined + for i in scan do ( + if new == undefined do new = #() + if isvalidNode i then ( + append new i + ) else ( + if new != undefined and new.count > 0 do append out new + new = undefined + ) + ) + -- 마지막 그룹 추가 + if new != undefined and new.count > 0 do append out new + out +) + +fn collectLayerNodes ly out:#()=( + ly.nodes &nd + join out nd + local numchildLy =ly.getNumChildren() + if numchildLy>0 do( + for i=1 to numchildLy do( + local newLy = ly.getChild i + collectLayerNodes newLy out:out + ) + ) + out +) + +fn CleanNames =( + local objArr = $mixamorig* + --Rules 1 + for i in objArr do( + n = i.name + filt = filterstring n ":" + if filt.Count>1 do( + newName = "mixamorig:" + for j=2 to filt.Count do ( + newName += filt[j] + if j!=filt.Count do newName+=":" + ) + i.name = newName + ) + ) +) + +fn SanityCheck = ( + hips = $'*:Hips*' + if hips.count>0 then( + hrc = getHierarchy hips #() + for obj in hrc do( + case of ( + (matchpattern obj.name pattern:"*Hips"): obj.name = "mixamorig:Hips" + (matchpattern obj.name pattern:"*Spine"): obj.name = "mixamorig:Spine" + (matchpattern obj.name pattern:"*Spine1"): obj.name = "mixamorig:Spine1" + + (matchpattern obj.name pattern:"*Neck"): obj.name = "mixamorig:Neck" + (matchpattern obj.name pattern:"*Head"): obj.name = "mixamorig:Head" + + (matchpattern obj.name pattern:"*LeftShoulder"): obj.name = "mixamorig:LeftShoulder" + (matchpattern obj.name pattern:"*LeftArm"): obj.name = "mixamorig:LeftArm" + (matchpattern obj.name pattern:"*LeftForeArm"): obj.name = "mixamorig:LeftForeArm" + (matchpattern obj.name pattern:"*LeftHand"): obj.name = "mixamorig:LeftHand" + (matchpattern obj.name pattern:"*LeftHandThumb1"): obj.name = "mixamorig:LeftHandThumb1" + (matchpattern obj.name pattern:"*LeftHandThumb2"): obj.name = "mixamorig:LeftHandThumb2" + (matchpattern obj.name pattern:"*LeftHandThumb3"): obj.name = "mixamorig:LeftHandThumb3" + -- LeftHandThumb4 제거됨 + (matchpattern obj.name pattern:"*LeftHandIndex1"): obj.name = "mixamorig:LeftHandIndex1" + (matchpattern obj.name pattern:"*LeftHandIndex2"): obj.name = "mixamorig:LeftHandIndex2" + (matchpattern obj.name pattern:"*LeftHandIndex3"): obj.name = "mixamorig:LeftHandIndex3" + -- LeftHandIndex4 제거됨 + (matchpattern obj.name pattern:"*LeftHandMiddle1"): obj.name = "mixamorig:LeftHandMiddle1" + (matchpattern obj.name pattern:"*LeftHandMiddle2"): obj.name = "mixamorig:LeftHandMiddle2" + (matchpattern obj.name pattern:"*LeftHandMiddle3"): obj.name = "mixamorig:LeftHandMiddle3" + -- LeftHandMiddle4 제거됨 + (matchpattern obj.name pattern:"*LeftHandRing1"): obj.name = "mixamorig:LeftHandRing1" + (matchpattern obj.name pattern:"*LeftHandRing2"): obj.name = "mixamorig:LeftHandRing2" + (matchpattern obj.name pattern:"*LeftHandRing3"): obj.name = "mixamorig:LeftHandRing3" + -- LeftHandRing4 제거됨 + (matchpattern obj.name pattern:"*LeftHandPinky1"): obj.name = "mixamorig:LeftHandPinky1" + (matchpattern obj.name pattern:"*LeftHandPinky2"): obj.name = "mixamorig:LeftHandPinky2" + (matchpattern obj.name pattern:"*LeftHandPinky3"): obj.name = "mixamorig:LeftHandPinky3" + -- LeftHandPinky4 제거됨 + (matchpattern obj.name pattern:"*RightShoulder"): obj.name = "mixamorig:RightShoulder" + (matchpattern obj.name pattern:"*RightArm"): obj.name = "mixamorig:RightArm" + (matchpattern obj.name pattern:"*RightForeArm"): obj.name = "mixamorig:RightForeArm" + (matchpattern obj.name pattern:"*RightHand"): obj.name = "mixamorig:RightHand" + (matchpattern obj.name pattern:"*RightHandThumb1"): obj.name = "mixamorig:RightHandThumb1" + (matchpattern obj.name pattern:"*RightHandThumb2"): obj.name = "mixamorig:RightHandThumb2" + (matchpattern obj.name pattern:"*RightHandThumb3"): obj.name = "mixamorig:RightHandThumb3" + -- RightHandThumb4 제거됨 + (matchpattern obj.name pattern:"*RightHandIndex1"): obj.name = "mixamorig:RightHandIndex1" + (matchpattern obj.name pattern:"*RightHandIndex2"): obj.name = "mixamorig:RightHandIndex2" + (matchpattern obj.name pattern:"*RightHandIndex3"): obj.name = "mixamorig:RightHandIndex3" + -- RightHandIndex4 제거됨 + (matchpattern obj.name pattern:"*RightHandMiddle1"): obj.name = "mixamorig:RightHandMiddle1" + (matchpattern obj.name pattern:"*RightHandMiddle2"): obj.name = "mixamorig:RightHandMiddle2" + (matchpattern obj.name pattern:"*RightHandMiddle3"): obj.name = "mixamorig:RightHandMiddle3" + -- RightHandMiddle4 제거됨 + (matchpattern obj.name pattern:"*RightHandRing1"): obj.name = "mixamorig:RightHandRing1" + (matchpattern obj.name pattern:"*RightHandRing2"): obj.name = "mixamorig:RightHandRing2" + (matchpattern obj.name pattern:"*RightHandRing3"): obj.name = "mixamorig:RightHandRing3" + -- RightHandRing4 제거됨 + (matchpattern obj.name pattern:"*RightHandPinky1"): obj.name = "mixamorig:RightHandPinky1" + (matchpattern obj.name pattern:"*RightHandPinky2"): obj.name = "mixamorig:RightHandPinky2" + (matchpattern obj.name pattern:"*RightHandPinky3"): obj.name = "mixamorig:RightHandPinky3" + -- RightHandPinky4 제거됨 + (matchpattern obj.name pattern:"*LeftUpLeg"): obj.name = "mixamorig:LeftUpLeg" + (matchpattern obj.name pattern:"*LeftLeg"): obj.name = "mixamorig:LeftLeg" + (matchpattern obj.name pattern:"*LeftFoot"): obj.name = "mixamorig:LeftFoot" + (matchpattern obj.name pattern:"*LeftToeBase"): obj.name = "mixamorig:LeftToeBase" + -- LeftToe_End 제거됨 + (matchpattern obj.name pattern:"*RightUpLeg"): obj.name = "mixamorig:RightUpLeg" + (matchpattern obj.name pattern:"*RightLeg"): obj.name = "mixamorig:RightLeg" + (matchpattern obj.name pattern:"*RightFoot"): obj.name = "mixamorig:RightFoot" + (matchpattern obj.name pattern:"*RightToeBase"): obj.name = "mixamorig:RightToeBase" + -- RightToe_End 제거됨 + ) + ) + + local valid = true + allObjectNames = for i in objects collect i.name + for i in MXNames do ( + -- excludeBones에 있는 본들은 체크하지 않음 + if findItem excludeBones i == 0 then ( + if findItem allObjectNames i==0 do ( + valid = false + format "[ERROR] : $% not found!\n" i + ) + ) + ) + valid = true + return valid + ) + return false +) + +fn createBip height spine:2 fingers:5= ( + -- Triangle Pelvis는 False, Triangle Neck는 True로 설정 + local bipObj = biped.createNew height -90 [0,0,height*0.9562] arms:true neckLinks:1 \ + spineLinks:spine legLinks:3 \ + fingers:fingers fingerLinks:3 toes:1 \ + toeLinks:1 ankleAttach:0.3 trianglePelvis:False triangleNeck:True + + bipObj +) + +fn getRigStructures =( + local n = "mixamorig:" + + local root = execute ("$'"+n+"Hips'") + + -- 다리 본들 (부모 관계 확인) + local LLeg1 = execute ("$'"+n+"LeftUpLeg'") + local LLeg2 = execute ("$'"+n+"LeftLeg'") + local LFoot = execute ("$'"+n+"LeftFoot'") + local LToe = execute ("$'"+n+"LeftToeBase'") + + local RLeg1 = execute ("$'"+n+"RightUpLeg'") + local RLeg2 = execute ("$'"+n+"RightLeg'") + local RFoot = execute ("$'"+n+"RightFoot'") + local RToe = execute ("$'"+n+"RightToeBase'") + + -- 스파인 본들 + local LSpine = execute ("$'"+n+"Spine'") + local LSpine1 = execute ("$'"+n+"Spine1'") + local spines = #(LSpine, LSpine1) + spineSegments = 2 + + -- 다리가 Spine에서 시작하는 경우 처리 + -- 다리 본들의 부모가 Spine인지 확인 + legParentIsSpine = false + if LLeg1 != undefined and LLeg1.parent != undefined then ( + if findString LLeg1.parent.name "Spine" != undefined then ( + legParentIsSpine = true + format "다리가 Spine에서 시작됨을 감지\\n" + ) + ) + + + -- 어깨/팔 본들 + local LClav = execute ("$'"+n+"LeftShoulder'") + local LArm1 = execute ("$'"+n+"LeftArm'") + local LArm2 = execute ("$'"+n+"LeftForeArm'") + local LHand = execute ("$'"+n+"LeftHand'") + + local RClav = execute ("$'"+n+"RightShoulder'") + local RArm1 = execute ("$'"+n+"RightArm'") + local RArm2 = execute ("$'"+n+"RightForeArm'") + local RHand = execute ("$'"+n+"RightHand'") + + -- 🔍 팔 구조 디버그 출력 + format "\\n=== 팔 구조 디버그 ===\\n" + if RClav != undefined then ( + format "RightShoulder: %\\n" RClav.name + format " 자식들: " + for child in RClav.children do format "%, " child.name + format "\\n" + ) + if RArm1 != undefined then ( + format "RightArm: %\\n" RArm1.name + format " 자식들: " + for child in RArm1.children do format "%, " child.name + format "\\n" + ) + if RArm2 != undefined then ( + format "RightForeArm: %\\n" RArm2.name + format " 자식들: " + for child in RArm2.children do format "%, " child.name + format "\\n" + ) + format "========================\\n" + + local neck = execute ("$'"+n+"Neck'") + local head = execute ("$'"+n+"Head'") + + -- 어깨가 어디서 시작하는지 확인 + shoulderParentIsNeck = false + shoulderParentIsSpine = false + if LClav != undefined and LClav.parent != undefined then ( + if findString LClav.parent.name "Neck" != undefined then ( + shoulderParentIsNeck = true + format "어깨가 Neck에서 시작됨을 감지\\n" + ) else if findString LClav.parent.name "Spine" != undefined and + findString LClav.parent.name "Spine1" == undefined then ( + shoulderParentIsSpine = true + format "어깨가 Spine에서 바로 시작됨을 감지\\n" + ) + ) + + local LFinger1 = $'mixamorig:LeftHandThumb1' + local LFinger2 = $'mixamorig:LeftHandIndex1' + local LFinger3 = $'mixamorig:LeftHandMiddle1' + local LFinger4 = $'mixamorig:LeftHandRing1' + local LFinger5 = $'mixamorig:LeftHandPinky1' + + local RFinger1 = $'mixamorig:RightHandThumb1' + local RFinger2 = $'mixamorig:RightHandIndex1' + local RFinger3 = $'mixamorig:RightHandMiddle1' + local RFinger4 = $'mixamorig:RightHandRing1' + local RFinger5 = $'mixamorig:RightHandPinky1' + + -- 스켈레톤 구조에 따라 적응적으로 배치 + if (legParentIsSpine and shoulderParentIsNeck) or (shoulderParentIsSpine) then ( + -- 특수 스켈레톤 구조들 + if shoulderParentIsSpine then ( + format "특수 스켈레톤 구조 감지: 어깨->Spine\\n" + ) else ( + format "특수 스켈레톤 구조 감지: 다리->Spine, 어깨->Neck\\n" + ) + rigNodes = #(root,root) + join rigNodes #(LLeg1, LLeg2, LFoot, LToe, RLeg1, RLeg2, RFoot, RToe) -- 다리들 먼저 + join rigNodes Spines -- 스파인들 + join rigNodes #(neck, head) -- 목과 머리 + join rigNodes #(LClav, LArm1, LArm2, LHand, RClav, RArm1, RArm2, RHand) -- 어깨와 팔들 + join rigNodes #(LFinger1,LFinger2,LFinger3,LFinger4,LFinger5, RFinger1,RFinger2,RFinger3,RFinger4,RFinger5) + ) else ( + -- 표준 구조 + rigNodes = #(root,root) + join rigNodes Spines + join rigNodes #( LClav, + LArm1, + LArm2, + LHand, + RClav, + RArm1, + RArm2, + RHand, + neck, + head, + LLeg1, + LLeg2, + LFoot, + LToe, + RLeg1, + RLeg2, + RFoot, + RToe, + LFinger1,LFinger2,LFinger3,LFinger4,LFinger5, + RFinger1,RFinger2,RFinger3,RFinger4,RFinger5 + ) + ) + rigNodes +) + +fn GetBipStructure bip spinesegs:3= ( + local bip_pelvis = biped.getNode bip #pelvis link:1 + local bip_root = bip_pelvis.parent + + local bip_LLeg1 = biped.getNode bip #lleg link:1 + local bip_LLeg2 = biped.getNode bip #lleg link:2 + local bip_LFoot = biped.getNode bip #lleg link:3 + local bip_LToe = biped.getNode bip #ltoes link:1 + + local bip_RLeg1 = biped.getNode bip #rleg link:1 + local bip_RLeg2 = biped.getNode bip #rleg link:2 + local bip_RFoot = biped.getNode bip #rleg link:3 + local bip_RToe = biped.getNode bip #rtoes link:1 + + local bip_LClav = biped.getNode bip #larm link:1 + local bip_LArm1 = biped.getNode bip #larm link:2 + local bip_LArm2 = biped.getNode bip #larm link:3 + local bip_LHand = biped.getNode bip #larm link:4 + + local bip_RClav = biped.getNode bip #rarm link:1 + local bip_RArm1 = biped.getNode bip #rarm link:2 + local bip_RArm2 = biped.getNode bip #rarm link:3 + local bip_RHand = biped.getNode bip #rarm link:4 + + local bip_neck = biped.getNode bip #neck link:1 + local bip_head = biped.getNode bip #head link:1 + + local bip_Lfinger0 = biped.getNode bip #lfingers link:1 + local bip_Lfinger1 = biped.getNode bip #lfingers link:4 + local bip_Lfinger2 = biped.getNode bip #lfingers link:7 + local bip_Lfinger3 = biped.getNode bip #lfingers link:10 + local bip_Lfinger4 = biped.getNode bip #lfingers link:13 + + local bip_Rfinger0 = biped.getNode bip #rfingers link:1 + local bip_Rfinger1 = biped.getNode bip #rfingers link:4 + local bip_Rfinger2 = biped.getNode bip #rfingers link:7 + local bip_Rfinger3 = biped.getNode bip #rfingers link:10 + local bip_Rfinger4 = biped.getNode bip #rfingers link:13 + + -- 스켈레톤 구조에 따라 적응적으로 배치 (getRigStructures와 동일 순서) + if (legParentIsSpine and shoulderParentIsNeck) or (shoulderParentIsSpine) then ( + -- 특수 구조들 + format "Biped도 특수 구조로 배치\\n" + bipNodes = #(bip_root,bip_pelvis) + -- 다리들 먼저 + join bipNodes #(bip_LLeg1, bip_LLeg2, bip_LFoot, bip_LToe, bip_RLeg1, bip_RLeg2, bip_RFoot, bip_RToe) + -- 스파인들 + for i=1 to spinesegs do append bipNodes (biped.getNode bip #spine link:i) + -- 목과 머리 + join bipNodes #(bip_neck, bip_head) + -- 어깨와 팔들 + join bipNodes #(bip_LClav, bip_LArm1, bip_LArm2, bip_LHand, bip_RClav, bip_RArm1, bip_RArm2, bip_RHand) + -- 손가락들 + join bipNodes #(bip_Lfinger0,bip_Lfinger1,bip_Lfinger2,bip_Lfinger3,bip_Lfinger4, bip_Rfinger0,bip_Rfinger1,bip_Rfinger2,bip_Rfinger3,bip_Rfinger4) + ) else ( + -- 표준 구조 + bipNodes = #(bip_root,bip_pelvis) + --SPINES + for i=1 to spinesegs do append bipNodes (biped.getNode bip #spine link:i) + join bipNodes #(bip_LClav, + bip_LArm1, + bip_LArm2, + bip_LHand, + bip_RClav, + bip_RArm1, + bip_RArm2, + bip_RHand, + bip_neck, + bip_head, + bip_LLeg1, + bip_LLeg2, + bip_LFoot, + bip_LToe, + bip_RLeg1, + bip_RLeg2, + bip_RFoot, + bip_RToe, + bip_Lfinger0,bip_Lfinger1,bip_Lfinger2,bip_Lfinger3,bip_Lfinger4, + bip_Rfinger0,bip_Rfinger1,bip_Rfinger2,bip_Rfinger3,bip_Rfinger4 + ) + ) + bipNodes +) + +fn MapTMAxis sourceTM targetTM indexes mult:[1,1,1]=( + axis = #(sourceTM.row1,sourceTM.row2,sourceTM.row3) + targetTM.row1 = axis[indexes[1]]*mult[1] + targetTM.row2 = axis[indexes[2]]*mult[2] + targetTM.row3 = axis[indexes[3]]*mult[3] + targetTM +) + +fn SetClavicleTM bipCtl bipClav TM =( + local figMode = bipCtl.figuremode + bipCtl.figuremode = false -- FORCE EXIT FIGURE MODE + biped.setTransform bipClav #rotation TM.rotationPart false + + -- COPY POSTURE + select bipClav + local col = biped.createCopyCollection bipCtl "Shoulder" + local cpy = biped.copyBipPosture bipCtl col (selection as array) #snapAuto + + -- PASTE POSTURE ON FIGURE MODE + bipCtl.figuremode = true + biped.pasteBipPosture bipCtl cpy false #pstcopied false false false false + biped.deleteAllCopyCollections bipCtl + biped.setTransform bipClav #pos TM.row4 false + clearSelection() + bipCtl.figuremode = figMode +) + +fn alignFootTpose rigObj BipObj =( + local RigTM = rigObj.transform + + local endPoint = rigObj.children[1].pos + local height = abs (endPoint[3]-RigTM.row4[3]) + + + endPoint[3] = RigTM.row4[3] + local v = endPoint-RigTM.row4 + local len = length v + local y = normalize(v) + local x = [0,0,-1] + local z = normalize(cross x y) + local TM = Matrix3 x y z RigTM.row4 + + -- #SET ROTATION + biped.setTransform BipObj #rotation TM.rotationpart false + biped.setTransform bipObj #scale [height,len*1.25,len*0.6] false + biped.setTransform BipObj #pos RigTM.row4 false +) + +fn alignFoot rigObj BipObj figureMode:true degree:footDegree =( + local isSetKey = not figureMode + local RigTM = rigObj.transform + local bipTM = rotateYmatrix (90)* RigTM + local bipTM = rotateZmatrix degree*bipTM + + -- #SET ROTATION + biped.setTransform BipObj #rotation bipTM.rotationpart isSetKey + biped.setTransform BipObj #pos RigTM.row4 isSetKey + + + -- SET SCALE (ONLY IN FIGURE MODE) + if figureMode do( + + local toeTM = copy rigTM + local p1 = rigObj.children[1].pos + toeTM.row4 = p1 + + local v = rigTM.row2*-1 + local p2 = rigTM.row4*inverse toeTM + local rotTM = RotateXMatrix degree*toeTM + p2 *= rotTM + + local v = p2-p1 + local p3 = pointLineProj p2 p1 RigTM.row4 + local len = (length v ) + local height = length (p2-RigTM.row4) + biped.setTransform bipObj #scale [height,len,len*0.6] isSetKey + ) +) + +fn AlignArm BipUpperArm MxUpperArm side:"R" FigureMode:false TPose:false = +( + local createKey = not FigureMode + local BipLowerArm = BipUpperArm.children[1] + local Biphand = BipLowerArm.children[1] + local Bip_UpperTM = BipUpperArm.transform + local Bip_LowerTM = BipLowerArm.transform + local Bip_HandTM = Biphand.transform + + -- 정확한 본 이름으로 찾기 (children[1] 대신) + local MxForeArm = undefined + local MxHand = undefined + + if side == "R" then ( + MxForeArm = execute ("$'mixamorig:RightForeArm'") + MxHand = execute ("$'mixamorig:RightHand'") + ) else ( + MxForeArm = execute ("$'mixamorig:LeftForeArm'") + MxHand = execute ("$'mixamorig:LeftHand'") + ) + + -- 백업: 이름으로 못 찾으면 children[1] 사용 + if MxForeArm == undefined do MxForeArm = MxUpperArm.children[1] + if MxHand == undefined do MxHand = MxForeArm.children[1] + local MX_UpperTM = MxUpperArm.transform + local MX_ElbowTM = MxForeArm.transform + local MX_HandTM = MxHand.transform + + + -- SET SCALE + if FigureMode do( + len = distance MxUpperArm MxForeArm + biped.setTransform BipUpperArm #scale [len,len,len] createKey + len = distance MxForeArm MxHand + biped.setTransform BipLowerArm #scale [len,len,len] createKey + midFing = MxHand.children[3] + if isvalidNode midFing then len = distance MxHand MxHand.children[3] + else len *=0.2 + biped.setTransform Biphand #scale [len,len,len] createKey + ) + + local mid = (MX_HandTM.row4+MX_UpperTM.row4)*0.5 + local up = Normalize (mid-MX_ElbowTM.row4) + if TPose do up = [0,-1,0] + + --SET HAND FIRST BECAUSE THE IK + biped.setTransform Biphand #pos MX_HandTM.row4 createKey + + -- UpperArm + local x = Normalize (MX_ElbowTM.row4-Bip_UpperTM.row4) + local z = Normalize (cross up x ) + local y = Normalize (cross z x) + + local upperTM = matrix3 x y z MX_UpperTM.row4 + + -- UpperArm: 기하학적 계산은 유지, 축 변환만 제거 + biped.setTransform BipUpperArm #rotation upperTM.rotationpart createKey + + -- Hand: 위치만 설정, 축 변환 제거 + biped.setTransform Biphand #pos MX_HandTM.row4 createKey + +) + +fn AlignLeg BipUpperLeg MxUpperLeg side:"R" FigureMode:false TPose:false = +( + local createKey = not FigureMode + local BipLoweLeg = BipUpperLeg.children[1] + local BipFoot = BipLoweLeg.children[1] + local MxLowerLeg = MxUpperLeg.children[1] + local MxFoot = MxLowerLeg.children[1] + local rigTM = MxUpperLeg.transform + local BipUpperTM = BipUpperLeg.transform + local KneeTM = MxLowerLeg.transform + local endTM = MxFoot.transform + + if FigureMode do( + dist = distance MxUpperLeg MxLowerLeg + biped.setTransform BipUpperLeg #scale [dist,dist,dist] createKey + dist = distance MxLowerLeg MxFoot + biped.setTransform BipLoweLeg #scale [dist,dist,dist] createKey + ) + + local mid = (endTM.row4+rigTM.row4)*0.5 + local up = Normalize (mid-KneeTM.row4) + if TPose do up = [0,1,0] + + -- Foot + biped.setTransform BipFoot #pos endTM.row4 createKey + + -- UpperLeg + local x = Normalize (KneeTM.row4-BipUpperTM.row4) + local z = Normalize (cross up x ) + local y = Normalize (cross z x) + + local newTM = matrix3 x y z rigTM.row4 + + -- UpperLeg: 기하학적 계산은 유지, 축 변환만 제거 + biped.setTransform BipUpperLeg #rotation newTM.rotationpart createKey +) + +fn alignFingers BipNode MXNode figureMode:false=( + local bip2 = BipNode.children[1] + local bip3 = bip2.children[1] + local BipNodes = #(BipNode,bip2,bip3) + + local MX2 = MXNode.children[1] + local MX3 = MX2.children[1] + local MX4 = MX3.children[1] + local MXNodes = #(MXNode,MX2,MX3,MX4) + + if figureMode then( + for i=1 to 3 where isvalidNode MXNodes[i] do( + -- 단순 크기 조정만 (축 변환 제거) + if isValidNode MXNodes[i+1] then len = distance MXNodes[i] MXNodes[i+1] + else len = distance MXNodes[i] MXNodes[i].parent + + biped.setTransform BipNodes[i] #scale [len,len,len] false + + -- 첫 번째 관절만 위치 설정 + if i==1 do biped.setTransform BipNodes[i] #pos MXNodes[i].transform.row4 false + ) + + -- 손가락 펴기: 좌우손 엄지/다른손가락 하드코딩 처리 + try ( + local isThumb = (findString BipNode.name "Finger0" != undefined) + local isLeftHand = (findString BipNode.name " L " != undefined) or (findString BipNode.name "Left" != undefined) + + -- === 왼손 엄지 === + if isThumb and isLeftHand then ( + if isvalidNode BipNodes[1] do + biped.setTransform BipNodes[1] #rotation (eulerAngles -90 10 -30) false + if isvalidNode BipNodes[2] do + biped.setTransform BipNodes[2] #rotation (eulerAngles -90 10 -30) false + if isvalidNode BipNodes[3] do + biped.setTransform BipNodes[3] #rotation (eulerAngles -90 10 -30) false + ) + -- === 오른손 엄지 === + else if isThumb and not isLeftHand then ( + if isvalidNode BipNodes[1] do + biped.setTransform BipNodes[1] #rotation (eulerAngles -90 10 -150) false + if isvalidNode BipNodes[2] do + biped.setTransform BipNodes[2] #rotation (eulerAngles -90 10 -150) false + if isvalidNode BipNodes[3] do + biped.setTransform BipNodes[3] #rotation (eulerAngles -90 10 -150) false + ) + -- === 왼손 다른 손가락들 === + else if not isThumb and isLeftHand then ( + if isvalidNode BipNodes[1] do + biped.setTransform BipNodes[1] #rotation (eulerAngles -90 0 0) false + if isvalidNode BipNodes[2] do + biped.setTransform BipNodes[2] #rotation (eulerAngles -100 0 0) false + if isvalidNode BipNodes[3] do + biped.setTransform BipNodes[3] #rotation (eulerAngles -105 0 0) false + ) + -- === 오른손 다른 손가락들 === + else if not isThumb and not isLeftHand then ( + if isvalidNode BipNodes[1] do + biped.setTransform BipNodes[1] #rotation (eulerAngles -90 0 180) false + if isvalidNode BipNodes[2] do + biped.setTransform BipNodes[2] #rotation (eulerAngles -100 0 180) false + if isvalidNode BipNodes[3] do + biped.setTransform BipNodes[3] #rotation (eulerAngles -105 0 180) false + ) + ) catch() + ) + -- Animation Mode에서는 회전 조정 안함 (축 변환 제거) + +) + +fn BipToRigProportion bipNodes rigNodes TPose:false =( + footDegree = findFootAngle() + matchIndexes = #{1..(bipNodes.count)} + + + bipctl = bipNodes[1].transform.controller + bipctl.figureMode = true + + + Lthigh = $'mixamorig:LeftUpLeg' + Rthigh = $'mixamorig:RightUpLeg' + p = (Lthigh.pos+Rthigh.pos)*0.5 + + + + for i in matchIndexes where isvalidnode bipNodes[i] do( + n = bipNodes[i].name + bipNode = bipNodes[i] + rigNode = rigNodes[i] + if isvalidNode bipNode and isvalidNode rigNode do( + + rigNodeEnd = rigNodes[i+1] + rigTM = rigNode.transform + BipTM = bipNode.transform + + case of( + (i==1):( + -- Root: 위치만 조정, 축 변환 제거 + biped.setTransform bipNode #pos p false + ) + (findString n "Head"!= undefined): ( + rigNodeEnd = rigNode.children[1] + if isvalidNode rigNodeEnd do( + -- Head: 크기와 위치만 조정, 축 변환 제거 + dist = distance rigNode rigNodeEnd + biped.setTransform bipNode #scale [dist,dist,dist] false + biped.setTransform bipNode #pos rigNode.transform.row4 false + ) + ) + + (matchPattern n pattern:"*Neck*"):( + -- Neck: 크기만 조정, 축 변환 제거 + dist = distance rigNode rigNodeEnd + biped.setTransform bipNode #scale [dist,dist,dist] false + ) + + + (matchPattern n pattern:"*Foot*" ):( + if TPose then alignFootTpose rigNode bipNode + else alignFoot rigNode bipNode figureMode:true + ) + + (matchPattern n pattern:"*Toe*"):( + rigNodeEnd = rigNode.children[1] + if isvalidNode rigNodeEnd do( + -- Toe: 크기와 위치만 조정, 축 변환 제거 + dist = distance rigNode rigNodeEnd + biped.setTransform bipNode #scale [dist,dist*0.2,dist] false + biped.setTransform bipNode #pos rigNode.pos false + ) + ) + + (findString n "Pelvis"!= undefined ):( + rigNode = $'mixamorig:LeftUpLeg' + rigNodeEnd = $'mixamorig:RightUpLeg' + dist = distance Lthigh Rthigh + len = distance rigNode rigNodeEnd + biped.setTransform bipNode #scale [dist,dist,dist] false + ) + + + (MatchPattern n pattern:"*Spine*"):( + -- 스파인 2개 구조에서 Spine1이 마지막 스파인 + if findString n "Spine1"!=undefined do ( + rigNodeEnd =$'mixamorig:Neck' + ) + if isvalidNode rigNode and isvalidNode rigNodeEnd do( + len = distance rigNode rigNodeEnd + biped.setTransform bipNode #scale [len,len,len] false + ) + + if matchpattern n pattern:"*Spine*" do( + -- Spine: 위치만 조정, 축 변환 제거 + biped.setTransform bipNode #pos rigNode.pos false + ) + ) + + (matchPattern n pattern:"*Clavicle*"):( + -- 정확한 Arm 본 찾기 (children[1] 대신) + rigNodeEnd = undefined + if findString n "L Clavicle" != undefined or findString n "LeftShoulder" != undefined then ( + rigNodeEnd = execute ("$'mixamorig:LeftArm'") + ) else if findString n "R Clavicle" != undefined or findString n "RightShoulder" != undefined then ( + rigNodeEnd = execute ("$'mixamorig:RightArm'") + ) + + -- 백업: 이름으로 못 찾으면 children[1] 사용 + if rigNodeEnd == undefined do rigNodeEnd = rigNode.children[1] + + len = distance rigNode rigNodeEnd + + x = Normalize (rigNodeEnd.pos-rigNode.pos) + z = Normalize (Cross (bipNode.parent.transform.row2) x) + y = Normalize (cross z x) + newTM = matrix3 x y z rigTM.row4 + biped.setTransform bipNode #scale [len,len,len] false + SetClavicleTM bipCtl bipNode newTM + ) + + (matchPattern n pattern:"*UpperArm*"):( + + if MatchPattern n pattern:"* R *" then AlignArm bipNode rigNode side:"R" FigureMode:true TPose:TPose + else AlignArm bipNode rigNode side:"L" FigureMode:true TPose:TPose + + ) + + (matchPattern n pattern:"*Thigh*"):( + if MatchPattern n pattern:"* R *" then AlignLeg bipNode rigNode side:"R" figureMode:true TPose:TPose + else AlignLeg bipNode rigNode side:"L" figureMode:true TPose:TPose + ) + (MatchPattern n pattern:"*Finger*"): alignFingers bipNode rigNode figureMode:true + ) + ) + ) + + bipctl.figureMode = false +) + +fn getNumFingers = ( + local counts = #(0,0) + local lHand = (getNodeByName "mixamorig:LeftHand") + local rHand = (getNodeByName "mixamorig:RightHand") + if isValidnode lHand do counts[1] = lHand.children.count + if isValidnode rHand do counts[2] = rHand.children.count + amax counts +) + +fn GetExtras root=( + local out = #() + if root!=undefined do( + hrc = getBranchHRC root + for h=hrc.count to 1 by -1 do ( + local objArr = hrc[h] + for j=objArr.count to 1 by -1 do( + if (finditem MXNames objArr[j].name)!=0 do deleteitem objArr j -- REMOVE STANDARD FROM EXTRAS + ) + if objArr.count==0 do deleteItem hrc h + ) + out = hrc + ) + out +) + +fn cleanUpSkin obj =( + local skn = obj.modifiers["skin"] + local count = skinOps.GetNumberBones skn + local IDList = #() + for i=1 to count do( + n = (skinOps.GetBoneName skn i 0) + if findItem excludeBones n!=0 do( + append IDList i + ) + ) + if IDList.count>0 do for i=IDList.count to 1 by -1 do( + skinOps.removebone skn IDList[i] + ) +) + +fn GetSkinPairs obj extra:#() =( + local out=#() + noPairs = #() + local extraV1 = for i in extra collect i.v1 + local skn = obj.modifiers[#skin] + local count = skinOps.GetNumberBones skn + for i=1 to count do( + foundPairs = true + n = (skinOps.GetBoneName skn i 0) + dp = datapair n "" + case of( + (matchpattern n pattern:"*Hips"): dp.v2 = "Bip001 Pelvis" + (matchpattern n pattern:"*Spine"): dp.v2 = "Bip001 Spine" + (matchpattern n pattern:"*Spine1"): dp.v2 = "Bip001 Spine1" + (matchpattern n pattern:"*Spine2"): dp.v2 = "Bip001 Spine2" + (matchpattern n pattern:"*Neck"): dp.v2 = "Bip001 Neck" + (matchpattern n pattern:"*Head"): dp.v2 = "Bip001 Head" + (matchpattern n pattern:"*LeftShoulder"): dp.v2 = "Bip001 L Clavicle" + (matchpattern n pattern:"*LeftArm"): dp.v2 = "Bip001 L UpperArm" + (matchpattern n pattern:"*LeftForeArm"): dp.v2 = "Bip001 L Forearm" + (matchpattern n pattern:"*LeftHand"): dp.v2 = "Bip001 L Hand" + (matchpattern n pattern:"*LeftHandThumb1"): dp.v2 = "Bip001 L Finger0" + (matchpattern n pattern:"*LeftHandThumb2"): dp.v2 = "Bip001 L Finger01" + (matchpattern n pattern:"*LeftHandThumb3"): dp.v2 = "Bip001 L Finger02" + (matchpattern n pattern:"*LeftHandThumb4"): dp.v2 = "Bip001 L Finger0Nub" + (matchpattern n pattern:"*LeftHandIndex1"): dp.v2 = "Bip001 L Finger1" + (matchpattern n pattern:"*LeftHandIndex2"): dp.v2 = "Bip001 L Finger11" + (matchpattern n pattern:"*LeftHandIndex3"): dp.v2 = "Bip001 L Finger12" + (matchpattern n pattern:"*LeftHandIndex4"): dp.v2 = "Bip001 L Finger1Nub" + (matchpattern n pattern:"*LeftHandMiddle1"): dp.v2 = "Bip001 L Finger2" + (matchpattern n pattern:"*LeftHandMiddle2"): dp.v2 = "Bip001 L Finger21" + (matchpattern n pattern:"*LeftHandMiddle3"): dp.v2 = "Bip001 L Finger22" + (matchpattern n pattern:"*LeftHandMiddle4"): dp.v2 = "Bip001 L Finger2Nub" + (matchpattern n pattern:"*LeftHandRing1"): dp.v2 = "Bip001 L Finger3" + (matchpattern n pattern:"*LeftHandRing2"): dp.v2 = "Bip001 L Finger31" + (matchpattern n pattern:"*LeftHandRing3"): dp.v2 = "Bip001 L Finger32" + (matchpattern n pattern:"*LeftHandRing4"): dp.v2 = "Bip001 L Finger3Nub" + (matchpattern n pattern:"*LeftHandPinky1"): dp.v2 = "Bip001 L Finger4" + (matchpattern n pattern:"*LeftHandPinky2"): dp.v2 = "Bip001 L Finger41" + (matchpattern n pattern:"*LeftHandPinky3"): dp.v2 = "Bip001 L Finger42" + (matchpattern n pattern:"*LeftHandPinky4"): dp.v2 = "Bip001 L Finger4Nub" + (matchpattern n pattern:"*RightShoulder"): dp.v2 = "Bip001 R Clavicle" + (matchpattern n pattern:"*RightArm"): dp.v2 = "Bip001 R UpperArm" + (matchpattern n pattern:"*RightForeArm"): dp.v2 = "Bip001 R Forearm" + (matchpattern n pattern:"*RightHand"): dp.v2 = "Bip001 R Hand" + (matchpattern n pattern:"*RightHandThumb1"): dp.v2 = "Bip001 R Finger0" + (matchpattern n pattern:"*RightHandThumb2"): dp.v2 = "Bip001 R Finger01" + (matchpattern n pattern:"*RightHandThumb3"): dp.v2 = "Bip001 R Finger02" + (matchpattern n pattern:"*RightHandThumb4"): dp.v2 = "Bip001 R Finger0Nub" + (matchpattern n pattern:"*RightHandIndex1"): dp.v2 = "Bip001 R Finger1" + (matchpattern n pattern:"*RightHandIndex2"): dp.v2 = "Bip001 R Finger11" + (matchpattern n pattern:"*RightHandIndex3"): dp.v2 = "Bip001 R Finger12" + (matchpattern n pattern:"*RightHandIndex4"): dp.v2 = "Bip001 R Finger1Nub" + (matchpattern n pattern:"*RightHandMiddle1"): dp.v2 = "Bip001 R Finger2" + (matchpattern n pattern:"*RightHandMiddle2"): dp.v2 = "Bip001 R Finger21" + (matchpattern n pattern:"*RightHandMiddle3"): dp.v2 = "Bip001 R Finger22" + (matchpattern n pattern:"*RightHandMiddle4"): dp.v2 = "Bip001 R Finger2Nub" + (matchpattern n pattern:"*RightHandRing1"): dp.v2 = "Bip001 R Finger3" + (matchpattern n pattern:"*RightHandRing2"): dp.v2 = "Bip001 R Finger31" + (matchpattern n pattern:"*RightHandRing3"): dp.v2 = "Bip001 R Finger32" + (matchpattern n pattern:"*RightHandRing4"): dp.v2 = "Bip001 R Finger3Nub" + (matchpattern n pattern:"*RightHandPinky1"): dp.v2 = "Bip001 R Finger4" + (matchpattern n pattern:"*RightHandPinky2"): dp.v2 = "Bip001 R Finger41" + (matchpattern n pattern:"*RightHandPinky3"): dp.v2 = "Bip001 R Finger42" + (matchpattern n pattern:"*RightHandPinky4"): dp.v2 = "Bip001 R Finger4Nub" + (matchpattern n pattern:"*LeftUpLeg"): dp.v2 = "Bip001 L Thigh" + (matchpattern n pattern:"*LeftLeg"): dp.v2 = "Bip001 L Calf" + (matchpattern n pattern:"*LeftFoot"): dp.v2 = "Bip001 L Foot" + (matchpattern n pattern:"*LeftToeBase"): dp.v2 = "Bip001 L Toe0" + (matchpattern n pattern:"*RightUpLeg"): dp.v2 = "Bip001 R Thigh" + (matchpattern n pattern:"*RightLeg"): dp.v2 = "Bip001 R Calf" + (matchpattern n pattern:"*RightFoot"): dp.v2 = "Bip001 R Foot" + (matchpattern n pattern:"*RightToeBase"): dp.v2 = "Bip001 R Toe0" + default:(foundPairs = false ) + ) + + indexInExtra = findItem extraV1 n + if not foundPairs and indexInExtra != 0 then( + dp.v2 = extra[indexInExtra].v2 + ) + else if not foundPairs do append noPairs n + append out dp + ) + out +) + +-- ================================== 모퍼 처리 함수들 =============================================== + +struct MorpherChannelData ( + channelIndex, + targetName, + weight, + channelName, + isActive +) + +struct MorpherBackupData ( + objectName, + modifierName, + channels +) + +fn BackupMorpherData obj =( + /* + 모퍼 모디파이어 데이터를 백업하는 함수 + + 매개변수: + - obj: 모퍼 모디파이어를 가진 오브젝트 + + 반환값: + - MorpherBackupData 구조체 + */ + local morphMod = obj.modifiers[#Morpher] + if morphMod == undefined do return undefined + + local channels = #() + local channelCount = try (morphMod.numChannels) catch (100) -- 기본값 100개 채널 + + format "📦 모퍼 데이터 백업: % (채널 수: %)\\n" obj.name channelCount + + for i = 1 to channelCount do ( + try ( + -- 채널 프로퍼티에 직접 접근 + local channelProp = execute ("morphMod.morph_channel_" + i as string) + if channelProp != undefined then ( + local target = try (channelProp.target) catch (undefined) + local weight = try (channelProp.value) catch (0.0) + local channelName = try (channelProp.name) catch ("") + local isActive = try (channelProp.active) catch (false) + + if target != undefined and isValidNode target do ( + local channelData = MorpherChannelData \ + channelIndex:i \ + targetName:target.name \ + weight:weight \ + channelName:channelName \ + isActive:isActive + append channels channelData + format " 채널 %: % (가중치: %, 타겟: %)\\n" i channelName weight target.name + ) + ) + ) catch ( + -- 채널이 비어있거나 접근할 수 없는 경우 건너뛰기 + format " 채널 % 건너뛰기 (빈 채널)\\n" i + ) + ) + + MorpherBackupData objectName:obj.name modifierName:morphMod.name channels:channels +) + +fn RestoreMorpherData backupData pairsList =( + /* + 백업된 모퍼 데이터를 복원하는 함수 + + 매개변수: + - backupData: 백업된 모퍼 데이터 + - pairsList: 본 이름 매핑 정보 + + 반환값: + - 성공 시 true, 실패 시 false + */ + if backupData == undefined do return false + + local obj = getNodeByName backupData.objectName + if not isValidNode obj do ( + format "[ERROR] 모퍼 오브젝트를 찾을 수 없음: %\\n" backupData.objectName + return false + ) + + local morphMod = obj.modifiers[#Morpher] + if morphMod == undefined do ( + format "[ERROR] 모퍼 모디파이어를 찾을 수 없음: %\\n" obj.name + return false + ) + + format "🔄 모퍼 데이터 복원: % (채널 수: %)\\n" obj.name backupData.channels.count + + for channelData in backupData.channels do ( + -- 원본 타겟 이름을 새 이름으로 매핑 + local newTargetName = channelData.targetName + for pair in pairsList do ( + if pair.v1 == channelData.targetName do ( + newTargetName = pair.v2 + exit() + ) + ) + + local newTarget = getNodeByName newTargetName + if isValidNode newTarget then ( + try ( + -- 채널 프로퍼티에 직접 접근하여 설정 + local channelProp = execute ("morphMod.morph_channel_" + channelData.channelIndex as string) + if channelProp != undefined then ( + -- 타겟 설정 + try (channelProp.target = newTarget) catch (format " [WARNING] 타겟 설정 실패: 채널 %\\n" channelData.channelIndex) + -- 가중치 복원 + try (channelProp.value = channelData.weight) catch (format " [WARNING] 가중치 설정 실패: 채널 %\\n" channelData.channelIndex) + -- 채널 활성화 상태 복원 + try (channelProp.active = channelData.isActive) catch (format " [WARNING] 활성화 상태 설정 실패: 채널 %\\n" channelData.channelIndex) + -- 채널 이름 복원 + try (channelProp.name = channelData.channelName) catch (format " [WARNING] 채널 이름 설정 실패: 채널 %\\n" channelData.channelIndex) + + format " ✅ 채널 % 복원: % → % (가중치: %)\\n" channelData.channelIndex channelData.targetName newTargetName channelData.weight + ) else ( + format " ❌ 채널 % 프로퍼티 접근 실패\\n" channelData.channelIndex + ) + ) catch ( + format " ⚠️ 채널 % 설정 실패: % → %\\n" channelData.channelIndex channelData.targetName newTargetName + ) + ) else ( + format " ❌ 타겟을 찾을 수 없음: % → %\\n" channelData.targetName newTargetName + ) + ) + + true +) + +fn GetSkinPairs obj extra:#() =( + local out=#() + noPairs = #() + local extraV1 = for i in extra collect i.v1 + local skn = obj.modifiers[#skin] + local count = skinOps.GetNumberBones skn + for i=1 to count do( + foundPairs = true + n = (skinOps.GetBoneName skn i 0) + dp = datapair n "" + case of( + (matchpattern n pattern:"*Hips"): dp.v2 = "Bip001 Pelvis" + (matchpattern n pattern:"*Spine"): dp.v2 = "Bip001 Spine" + (matchpattern n pattern:"*Spine1"): dp.v2 = "Bip001 Spine1" + + (matchpattern n pattern:"*Neck"): dp.v2 = "Bip001 Neck" + (matchpattern n pattern:"*Head"): dp.v2 = "Bip001 Head" + (matchpattern n pattern:"*LeftShoulder"): dp.v2 = "Bip001 L Clavicle" + (matchpattern n pattern:"*LeftArm"): dp.v2 = "Bip001 L UpperArm" + (matchpattern n pattern:"*LeftForeArm"): dp.v2 = "Bip001 L Forearm" + (matchpattern n pattern:"*LeftHand"): dp.v2 = "Bip001 L Hand" + (matchpattern n pattern:"*LeftHandThumb1"): dp.v2 = "Bip001 L Finger0" + (matchpattern n pattern:"*LeftHandThumb2"): dp.v2 = "Bip001 L Finger01" + (matchpattern n pattern:"*LeftHandThumb3"): dp.v2 = "Bip001 L Finger02" + -- LeftHandThumb4 제거됨 + (matchpattern n pattern:"*LeftHandIndex1"): dp.v2 = "Bip001 L Finger1" + (matchpattern n pattern:"*LeftHandIndex2"): dp.v2 = "Bip001 L Finger11" + (matchpattern n pattern:"*LeftHandIndex3"): dp.v2 = "Bip001 L Finger12" + -- LeftHandIndex4 제거됨 + (matchpattern n pattern:"*LeftHandMiddle1"): dp.v2 = "Bip001 L Finger2" + (matchpattern n pattern:"*LeftHandMiddle2"): dp.v2 = "Bip001 L Finger21" + (matchpattern n pattern:"*LeftHandMiddle3"): dp.v2 = "Bip001 L Finger22" + -- LeftHandMiddle4 제거됨 + (matchpattern n pattern:"*LeftHandRing1"): dp.v2 = "Bip001 L Finger3" + (matchpattern n pattern:"*LeftHandRing2"): dp.v2 = "Bip001 L Finger31" + (matchpattern n pattern:"*LeftHandRing3"): dp.v2 = "Bip001 L Finger32" + -- LeftHandRing4 제거됨 + (matchpattern n pattern:"*LeftHandPinky1"): dp.v2 = "Bip001 L Finger4" + (matchpattern n pattern:"*LeftHandPinky2"): dp.v2 = "Bip001 L Finger41" + (matchpattern n pattern:"*LeftHandPinky3"): dp.v2 = "Bip001 L Finger42" + -- LeftHandPinky4 제거됨 + (matchpattern n pattern:"*RightShoulder"): dp.v2 = "Bip001 R Clavicle" + (matchpattern n pattern:"*RightArm"): dp.v2 = "Bip001 R UpperArm" + (matchpattern n pattern:"*RightForeArm"): dp.v2 = "Bip001 R Forearm" + (matchpattern n pattern:"*RightHand"): dp.v2 = "Bip001 R Hand" + (matchpattern n pattern:"*RightHandThumb1"): dp.v2 = "Bip001 R Finger0" + (matchpattern n pattern:"*RightHandThumb2"): dp.v2 = "Bip001 R Finger01" + (matchpattern n pattern:"*RightHandThumb3"): dp.v2 = "Bip001 R Finger02" + -- RightHandThumb4 제거됨 + (matchpattern n pattern:"*RightHandIndex1"): dp.v2 = "Bip001 R Finger1" + (matchpattern n pattern:"*RightHandIndex2"): dp.v2 = "Bip001 R Finger11" + (matchpattern n pattern:"*RightHandIndex3"): dp.v2 = "Bip001 R Finger12" + -- RightHandIndex4 제거됨 + (matchpattern n pattern:"*RightHandMiddle1"): dp.v2 = "Bip001 R Finger2" + (matchpattern n pattern:"*RightHandMiddle2"): dp.v2 = "Bip001 R Finger21" + (matchpattern n pattern:"*RightHandMiddle3"): dp.v2 = "Bip001 R Finger22" + -- RightHandMiddle4 제거됨 + (matchpattern n pattern:"*RightHandRing1"): dp.v2 = "Bip001 R Finger3" + (matchpattern n pattern:"*RightHandRing2"): dp.v2 = "Bip001 R Finger31" + (matchpattern n pattern:"*RightHandRing3"): dp.v2 = "Bip001 R Finger32" + -- RightHandRing4 제거됨 + (matchpattern n pattern:"*RightHandPinky1"): dp.v2 = "Bip001 R Finger4" + (matchpattern n pattern:"*RightHandPinky2"): dp.v2 = "Bip001 R Finger41" + (matchpattern n pattern:"*RightHandPinky3"): dp.v2 = "Bip001 R Finger42" + -- RightHandPinky4 제거됨 + (matchpattern n pattern:"*LeftUpLeg"): dp.v2 = "Bip001 L Thigh" + (matchpattern n pattern:"*LeftLeg"): dp.v2 = "Bip001 L Calf" + (matchpattern n pattern:"*LeftFoot"): dp.v2 = "Bip001 L Foot" + (matchpattern n pattern:"*LeftToeBase"): dp.v2 = "Bip001 L Toe0" + (matchpattern n pattern:"*RightUpLeg"): dp.v2 = "Bip001 R Thigh" + (matchpattern n pattern:"*RightLeg"): dp.v2 = "Bip001 R Calf" + (matchpattern n pattern:"*RightFoot"): dp.v2 = "Bip001 R Foot" + (matchpattern n pattern:"*RightToeBase"): dp.v2 = "Bip001 R Toe0" + default:(foundPairs = false ) + ) + + indexInExtra = findItem extraV1 n + if not foundPairs and indexInExtra != 0 then( + dp.v2 = extra[indexInExtra].v2 + ) + else foundPairs = false + + append out dp + if not foundPairs do append noPairs n + ) + out +) + +-- ================================== 메인 변환 함수 =============================================== + +fn ConvertMixamoToBiped charName:"Character001" alwaysDeform:false =( + /* + Mixamo T-Pose 아바타를 Biped으로 변환하는 메인 함수 + + 매개변수: + - charName: 캐릭터 이름 (기본값: "Character001") + - alwaysDeform: 스킨 Always Deform 설정 (기본값: false) + + 반환값: + - 성공 시 true, 실패 시 false + */ + + if charName=="" do( + messagebox "please insert Character Name" + return false + ) + + -- 이름 정리 및 검증 + CleanNames() + local root = getNodeByName"mixamorig:Hips" + if not isValidNode root do ( + format "[ERROR] mixamorig:Hips not found!\n" + return false + ) + + -- 리그 유효성 검증 + if not SanityCheck() do ( + format "[ERROR] Sanity check failed!\n" + return false + ) + + local hrc = getHierarchy #(root) #() + + -- 추가 본들 수집 및 메인에서 제외 + local extras = GetExtras root + for i in extras do( + for j in i do( + deleteItem hrc (findItem hrc j) + ) + ) + + -- 스킨 오브젝트들 수집 + local skinObjects = for i in objects where i.modifiers[#Skin]!=undefined collect i + if alwaysDeform do for i in skinObjects do ( + cleanUpSkin i + i.modifiers[#Skin].always_deform =false + i.modifiers[#Skin].always_deform =true + ) + + -- 모퍼 오브젝트들 수집 (간소화) + local morpherObjects = for i in objects where i.modifiers[#Morpher]!=undefined collect i + local morpherModifiers = #() -- 제거된 모퍼 모디파이어들을 저장 + + for obj in morpherObjects do ( + -- 모퍼 모디파이어를 임시로 제거하고 저장 + local morphMod = obj.modifiers[#Morpher] + append morpherModifiers (datapair obj morphMod) + deleteModifier obj morphMod + ) + + -- 믹사모 리그 구조 분석 + local mxNodes = getRigStructures() + local mx_Obj = $mixamorig* + local height = mx_Obj.max[3] + local numfingers = getNumFingers() + + -- 바이패드 생성 + format "=== BIPED 생성 디버그 ===\\n" + format "spineSegments: %\\n" spineSegments + format "height: %\\n" height + format "numfingers: %\\n" numfingers + + local bipObj = createBip height spine:spineSegments fingers:numfingers + + -- 생성 직후 구조 확인 + format "\\n=== 생성 직후 Biped 구조 ===\\n" + local pelvis = biped.getNode bipObj #pelvis link:1 + local spine1 = biped.getNode bipObj #spine link:1 + local spine2 = try (biped.getNode bipObj #spine link:2) catch (undefined) + local lthigh = biped.getNode bipObj #lleg link:1 + local lclavicle = biped.getNode bipObj #larm link:1 + local neck = biped.getNode bipObj #neck link:1 + + format "Pelvis: % (부모: %)\\n" pelvis.name (if pelvis.parent != undefined then pelvis.parent.name else "없음") + format "Spine1: % (부모: %)\\n" spine1.name (if spine1.parent != undefined then spine1.parent.name else "없음") + if spine2 != undefined then format "Spine2: % (부모: %)\\n" spine2.name (if spine2.parent != undefined then spine2.parent.name else "없음") + format "LThigh: % (부모: %)\\n" lthigh.name (if lthigh.parent != undefined then lthigh.parent.name else "없음") + format "LClavicle: % (부모: %)\\n" lclavicle.name (if lclavicle.parent != undefined then lclavicle.parent.name else "없음") + format "Neck: % (부모: %)\\n" neck.name (if neck.parent != undefined then neck.parent.name else "없음") + + -- 🔧 잘못된 구조 수정 + format "\\n=== Biped 구조 수정 중 ===\\n" + + -- Figure Mode 활성화 (구조 수정 가능) + bipObj.transform.controller.figureMode = true + + -- 다리 본들을 Pelvis로 이동 (현재 Spine에서 → Pelvis로) + local rthigh = biped.getNode bipObj #rleg link:1 + if lthigh.parent != pelvis then ( + format "다리를 Pelvis로 이동: % → %\\n" lthigh.parent.name pelvis.name + lthigh.parent = pelvis + rthigh.parent = pelvis + ) + + -- 어깨 본들을 최상위 Spine으로 이동 (현재 Neck에서 → Spine1로) + local rclavicle = biped.getNode bipObj #rarm link:1 + local topSpine = if spine2 != undefined then spine2 else spine1 -- 최상위 Spine + if lclavicle.parent != topSpine then ( + format "어깨를 최상위 Spine으로 이동: % → %\\n" lclavicle.parent.name topSpine.name + lclavicle.parent = topSpine + rclavicle.parent = topSpine + ) + + format "구조 수정 완료!\\n" + + -- 구조 수정 후 확인 + format "\\n=== 구조 수정 후 확인 ===\\n" + format "LThigh: % (부모: %)\\n" lthigh.name (if lthigh.parent != undefined then lthigh.parent.name else "없음") + format "RThigh: % (부모: %)\\n" rthigh.name (if rthigh.parent != undefined then rthigh.parent.name else "없음") + format "LClavicle: % (부모: %)\\n" lclavicle.name (if lclavicle.parent != undefined then lclavicle.parent.name else "없음") + format "RClavicle: % (부모: %)\\n" rclavicle.name (if rclavicle.parent != undefined then rclavicle.parent.name else "없음") + + local bipedNodes = GetBipStructure bipObj spinesegs:spineSegments + + -- 비율 매칭 + BipToRigProportion bipedNodes mxNodes TPose:true + + -- 비율 매칭 후 구조 재확인 + format "\\n=== 비율 매칭 후 Biped 구조 ===\\n" + local pelvis_after = biped.getNode bipObj #pelvis link:1 + local spine1_after = biped.getNode bipObj #spine link:1 + local lthigh_after = biped.getNode bipObj #lleg link:1 + local lclavicle_after = biped.getNode bipObj #larm link:1 + local neck_after = biped.getNode bipObj #neck link:1 + + format "Pelvis: % (부모: %)\\n" pelvis_after.name (if pelvis_after.parent != undefined then pelvis_after.parent.name else "없음") + format "Spine1: % (부모: %)\\n" spine1_after.name (if spine1_after.parent != undefined then spine1_after.parent.name else "없음") + format "LThigh: % (부모: %)\\n" lthigh_after.name (if lthigh_after.parent != undefined then lthigh_after.parent.name else "없음") + format "LClavicle: % (부모: %)\\n" lclavicle_after.name (if lclavicle_after.parent != undefined then lclavicle_after.parent.name else "없음") + format "Neck: % (부모: %)\\n" neck_after.name (if neck_after.parent != undefined then neck_after.parent.name else "없음") + + -- 추가 본들 생성 (선택사항) + local extrasPairs = #() + + -- ========== 원본 엑스트라 본 유지 방식 ================ + local totalExtraCount = 0 + for ex in extras do totalExtraCount += ex.count + format "전체 Extra Bones 개수: %개 (원본 본 그대로 유지)\\n" totalExtraCount + + bipObj.transform.controller.figureMode = true + local idx = 1 + for ex in extras do( + for i=1 to ex.count do ( + local originalBone = ex[i] + if not isValidNode originalBone do continue + + -- 원본 본을 그대로 사용하되, 정리된 이름으로 매핑 + local cleanName = substituteString originalBone.name "mixamorig:" "" + append extrasPairs (datapair originalBone.name cleanName) + append MXPairs (datapair originalBone.name cleanName) + + format " [%] % → 원본 유지" idx originalBone.name + + -- 첫 번째 본의 경우 적절한 Biped 부모에 연결 + if i==1 then ( + local par = originalBone.parent + for pair in MXPairs where isValidNode par and par.name==pair.v1 do ( + local bipPar = getNodebyName pair.v2 + if isValidNode bipPar do ( + -- 원본 본을 Biped 부모에 연결 + try ( + originalBone.parent = bipPar + format " (부모: %)\\n" bipPar.name + ) catch ( + format " (부모 연결 실패: %)\\n" bipPar.name + ) + exit() + ) + ) + if originalBone.parent == par do format " (부모 연결 안됨)\\n" + ) else ( + format "\\n" + ) + idx += 1 + ) + ) + bipObj.transform.controller.figureMode = false + + format "\\n=== 원본 엑스트라 본 유지 완료 ===\\n" + format "- 각도/위치 완벽 보존\\n" + format "- 애니메이션 데이터 손실 없음\\n" + format "- 스키닝 품질 유지\\n" + + -- Extra Bones 생성 후 최종 구조 확인 + format "\\n=== Extra Bones 생성 후 최종 구조 ===\\n" + local pelvis_final = biped.getNode bipObj #pelvis link:1 + local spine1_final = biped.getNode bipObj #spine link:1 + local lthigh_final = biped.getNode bipObj #lleg link:1 + local lclavicle_final = biped.getNode bipObj #larm link:1 + local neck_final = biped.getNode bipObj #neck link:1 + + format "Pelvis: % (부모: %)\\n" pelvis_final.name (if pelvis_final.parent != undefined then pelvis_final.parent.name else "없음") + format "Spine1: % (부모: %)\\n" spine1_final.name (if spine1_final.parent != undefined then spine1_final.parent.name else "없음") + format "LThigh: % (부모: %)\\n" lthigh_final.name (if lthigh_final.parent != undefined then lthigh_final.parent.name else "없음") + format "LClavicle: % (부모: %)\\n" lclavicle_final.name (if lclavicle_final.parent != undefined then lclavicle_final.parent.name else "없음") + format "Neck: % (부모: %)\\n" neck_final.name (if neck_final.parent != undefined then neck_final.parent.name else "없음") + + -- 하이브리드 방식: 엑스트라 본 처리 결과 + if extrasPairs.count > 0 then ( + format "\\n📊 Extra Bones 처리 결과 요약:\\n" + format "✅ 총 %개 엑스트라 본 처리 완료\\n" extrasPairs.count + format "✅ 모든 엑스트라 본이 원본 위치/각도 유지\\n" + format "✅ 위치 변화: 0 (원본 본 그대로 사용)\\n" + format "✅ 각도 변화: 0 (원본 각도 그대로 사용)\\n" + format "✅ 애니메이션 데이터: 완벽 보존\\n" + format "✅ 스키닝 품질: 원본 유지\\n" + format "✅ 표준 본: Biped로 대체됨\\n" + format "✅ 엑스트라 본: 원본 유지됨\\n" + ) + + format "================================\\n" + + -- 스킨 팝업 자동 처리 함수 (강화 버전) + fn ShowPopup =( + local hwnd = (DialogMonitorOPS.GetWindowHandle() ) + if hwnd!=0 then( + local windowTitle = uiAccessor.getWindowText hwnd + local children = uiAccessor.getChildWindows hwnd + + -- 다이얼로그 타입별 처리 + case of ( + -- Load Envelopes 다이얼로그 + (findString windowTitle "Load Envelopes" != undefined): ( + format "Load Envelopes 다이얼로그 감지 - 자동 OK 처리\\n" + for c in children do( + local buttonText = uiAccessor.getWindowText c + if buttonText == "OK" do ( + UIAccessor.PressButton c + format "OK 버튼 자동 클릭 완료\\n" + return true + ) + ) + ) + + -- Match by Name 다이얼로그 + (findString windowTitle "Match by Name" != undefined): ( + format "Match by Name 다이얼로그 감지 - 자동 처리\\n" + for c in children do( + if (uiAccessor.getWindowText c) =="Match by Name" do UIAccessor.PressButton c + ) + ) + + -- 기타 다이얼로그들 + default: ( + -- 일반적인 OK 버튼 찾기 + for c in children do( + local buttonText = uiAccessor.getWindowText c + if buttonText == "OK" do ( + format "일반 OK 버튼 감지 - 자동 클릭: %\\n" windowTitle + UIAccessor.PressButton c + return true + ) + ) + ) + ) + + -- 기본 버튼 처리 + uiAccessor.pressDefaultButton() + true + ) + else false + ) + + DialogMonitorOPS.Enabled = true + DialogMonitorOPS.Interactive =false + DialogMonitorOPS.RegisterNotification ShowPopup id:#MixamoToBiped + + local pth = GetDir #export + -- 하이브리드 방식: 표준 본은 Biped로, 엑스트라 본은 원본으로 + format "\\n=== 스킨 오브젝트 처리 (Biped + 엑스트라 본) ===\\n" + + for i in skinObjects do( + format "처리 중: %\\n" i.name + local skn = i.modifiers["skin"] + local n = i.name+"_skin.env" + local f = (pth+"\\"+n) + + -- 기존 스킨 데이터 저장 + if skn != undefined then ( + skinOps.saveEnvelope skn f + format "- 기존 스킨 데이터 저장: %\\n" f + ) + + -- 페어 가져오기 (표준 본 + 엑스트라 본) + local pairs = GetSkinPairs i extra:extrasPairs + format "- 매핑된 본 쌍: %개\\n" pairs.count + + local bipedSkin = #() + for j in pairs do ( + local obj1 = getNodeByName j.v1 + local obj2 = getNodeByName j.v2 + if isvalidNode obj1 and isValidNode obj2 do( + tempName = obj1.name + obj1.name = obj2.name + obj2.name = tempName + append bipedSkin obj2 + ) + ) + + -- 새 스킨 추가 + local skn2 = skin() + addmodifier i skn2 + select i + max modify mode + modPanel.setCurrentObject skn2 + + -- Biped 본들과 엑스트라 본들 추가 + for j in bipedSkin do( + if j!= bipedSkin[bipedSkin.count] then skinOps.AddBone skn2 j 0 + else skinOps.AddBone skn2 j -1 + ) + max create mode + + -- 스킨 데이터 로드 (저장된 것이 있는 경우) + if doesFileExist f then ( + skinOps.loadEnvelope skn2 f + format "- 스킨 데이터 로드 완료\\n" + ) + maxOps.CollapseNodeTo i 2 true + + -- 이름 원래대로 되돌리기 + for j in pairs do ( + obj1 = getNodeByName j.v1 + obj2 = getNodeByName j.v2 + if isvalidNode obj1 and isValidNode obj2 do( + tempName = obj1.name + obj1.name = obj2.name + obj2.name = tempName + ) + ) + + -- 임시 파일 삭제 + if doesFileExist f do deleteFile f + + format "- % 처리 완료\\n" i.name + ) + + format "\\n✅ 모든 스킨 오브젝트 처리 완료 (Biped + 엑스트라)\\n" + + -- 대화상자 모니터 비활성화 + DialogMonitorOPS.Enabled = false + DialogMonitorOPS.UnRegisterNotification id:#MixamoToBiped + + -- 모퍼 모디파이어 복원 (간소화) + if morpherModifiers.count > 0 do ( + format "\\n🎭 모퍼 모디파이어 복원 시작...\\n" + + -- 모퍼 모디파이어를 다시 추가 + for modPair in morpherModifiers do ( + local obj = modPair.v1 + local morphMod = modPair.v2 + addModifier obj morphMod + ) + + format "✅ 모퍼 모디파이어 복원 완료\\n" + ) + + -- 하이브리드 방식: 표준 본은 삭제, 엑스트라 본은 유지 + format "\\n=== 믹사모 표준 본 삭제 ===\\n" + + -- 표준 본들만 선별해서 삭제 (엑스트라 본 제외) + local standardBones = #() + local allMixamoBones = getHierarchy #(root) #() + + for bone in allMixamoBones do ( + if isValidNode bone and findItem MXNames bone.name != 0 then ( + appendIfUnique standardBones bone + format "- 삭제 예정: %\\n" bone.name + ) + ) + + -- 표준 본들 삭제 + if standardBones.count > 0 then ( + delete standardBones + format "✅ %개 표준 본 삭제 완료\\n" standardBones.count + ) + + -- 엑스트라 본 이름 정리 (mixamorig: 제거) + format "\\n=== 엑스트라 본 이름 정리 ===\\n" + for i in extrasPairs do( + local theObj = getNodeByName i.v1 + if isValidNode theObj then ( + theObj.name = i.v2 -- 이미 정리된 이름으로 변경 + format "- % → %\\n" i.v1 i.v2 + ) + ) + + -- 레이어 설정 + local rootLayer = LayerManager.newLayerFromName "CHARACTERS" + if rootLayer == undefined do rootLayer = LayerManager.getLayerFromName "CHARACTERS" + + local charLayer = LayerManager.newLayerFromName (toUpper charName) + if charLayer == undefined do charLayer = LayerManager.getLayerFromName charName + charLayer.setParent rootLayer + + + local bipLayer = LayerManager.newLayerFromName (charName+"_BIP") + if bipLayer == undefined do bipLayer = LayerManager.getLayerFromName (charName+"_BIP") + bipLayer.setParent charLayer + + local GeoLayer = LayerManager.newLayerFromName (charName+"_GEO") + if GeoLayer == undefined do GeoLayer = LayerManager.getLayerFromName (charName+"_GEO") + GeoLayer.setParent charLayer + GeoLayer.isfrozen =true + + -- 오브젝트 관리 + for i in skinObjects do ( + i.showFrozenInGray = false + local n = i.name + if not matchPattern n pattern:(charName+"*") do i.name = charName+"_"+i.name + if i.material!=undefined do ( + mtlName = i.material.name + if not matchPattern mtlName pattern:(charName+"*") do i.material.name = charName+"_"+mtlName + ) + GeoLayer.addnode i + ) + + local allBip = getHierarchy bipObj #() + for i in allBip do( + bipLayer.addNode i + i.name = charName+"_"+i.name + i.renderable =false + i.boxMode = true + ) + + -- 성공 반환 + format "[SUCCESS] Mixamo to Biped conversion completed for: %\n" charName + redrawViews() + true +) + +-- ================================== 자동 실행 =============================================== + +-- 스크립트 로딩 완료 후 바로 변환 시작 +format "\n✅ MixamoToBiped Converter 로딩 완료!\n" +format "자동으로 변환을 시작합니다...\n\n" + +try ( + format "=== Mixamo to Biped 자동 변환기 ===\n" + + -- Mixamo 오브젝트 확인 + local mixamoObjects = $mixamorig* as array + if mixamoObjects.count > 0 then ( + format "Mixamo 오브젝트 발견: %개\n" mixamoObjects.count + + -- 캐릭터 이름 자동 생성 (랜덤 번호 기반) + local randomNum = random 10000 99999 + local charName = "Character" + (randomNum as string) + + format "기본 이름 사용: %\n" charName + format "변환 시작: %\n" charName + format "=================================\n" + + -- 변환 실행 + try ( + local result = ConvertMixamoToBiped charName:charName + + if result then ( + format "\n🎉 변환 완료! 🎉\n" + format "캐릭터: %\n" charName + format "✅ Biped 로컬 축: 표준 유지 (축 뒤틀림 없음)\n" + format "✅ T-Pose 자세: 기존 알고리즘으로 매칭\n" + format "✅ 애니메이션 호환성: 완벽\n" + format "레이어: CHARACTERS > %\n" (toUpper charName) + redrawViews() + ) else ( + format "\n❌ 변환 실패!\n" + format "Mixamo 리그 구조를 확인해주세요.\n" + ) + ) catch ( + format "\n❌ 변환 중 오류 발생!\n" + format "오류 내용: %\n" (getCurrentException()) + ) + ) else ( + format "\n⚠️ Mixamo 오브젝트를 찾을 수 없습니다!\n" + format "1. Mixamo T-Pose FBX 파일을 먼저 임포트해주세요.\n" + format "2. 임포트 후 이 스크립트를 다시 실행하세요.\n" + format "\n사용법:\n" + format "1. File > Import > Mixamo T-Pose FBX 선택\n" + format "2. 스크립트 실행 (자동으로 변환 시작)\n" + format "3. 자동 변환 완료! ✨\n" + ) +) catch ( + format "❌ 스크립트 실행 중 심각한 오류 발생!\n" + format "오류 내용: %\n" (getCurrentException()) +) \ No newline at end of file diff --git a/3ds max script/MixamoToBiped_Converter - z up.ms b/3ds max script/MixamoToBiped_Converter - z up.ms new file mode 100644 index 00000000..2db28d50 --- /dev/null +++ b/3ds max script/MixamoToBiped_Converter - z up.ms @@ -0,0 +1,1759 @@ +/* + MixamoToBiped Converter - No Axis Twist Version + Extracted from TOOLS_MixamoToBiped_v1.0.51.ms + + 이 스크립트는 Mixamo T-Pose 아바타를 3ds Max Biped으로 변환합니다. + + 🎯 특징 (축 변환 제거 버전): + - Biped 로컬 축 정의 유지 (MapTMAxis 호출 제거) + - 기존 T-Pose 자세 매칭 알고리즘 유지 + - IK 정렬과 기하학적 계산 보존 + - 크기와 위치 조정은 정상 동작 + - 스킨 가중치 완벽 이전 + + 사용법: + 1. Mixamo T-Pose FBX 파일을 임포트 + 2. 스크립트 실행 (자동으로 변환 시작) + + Author: Based on Ishak Suryo Laksono's work + Modified: Removed MapTMAxis calls to preserve Biped local axes +*/ + +-- ================================== 핵심 데이터 (원본 그대로) =============================================== + +-- 믹사모 본 이름들 (순서 중요 - 절대 변경 금지) +global MXNames =#("mixamorig:Hips","mixamorig:LeftUpLeg","mixamorig:LeftLeg","mixamorig:LeftFoot","mixamorig:LeftToeBase","mixamorig:RightUpLeg","mixamorig:RightLeg", + "mixamorig:RightFoot","mixamorig:RightToeBase","mixamorig:Spine","mixamorig:Spine1","mixamorig:LeftShoulder","mixamorig:LeftArm","mixamorig:LeftForeArm", + "mixamorig:LeftHand","mixamorig:LeftHandIndex1","mixamorig:LeftHandIndex2","mixamorig:LeftHandIndex3","mixamorig:LeftHandMiddle1","mixamorig:LeftHandMiddle2", + "mixamorig:LeftHandMiddle3","mixamorig:LeftHandPinky1","mixamorig:LeftHandPinky2","mixamorig:LeftHandPinky3","mixamorig:LeftHandRing1", + "mixamorig:LeftHandRing2","mixamorig:LeftHandRing3","mixamorig:LeftHandThumb1","mixamorig:LeftHandThumb2","mixamorig:LeftHandThumb3", + "mixamorig:Neck","mixamorig:Head","mixamorig:RightShoulder","mixamorig:RightArm","mixamorig:RightForeArm","mixamorig:RightHand", + "mixamorig:RightHandIndex1","mixamorig:RightHandIndex2","mixamorig:RightHandIndex3","mixamorig:RightHandMiddle1","mixamorig:RightHandMiddle2","mixamorig:RightHandMiddle3", + "mixamorig:RightHandPinky1","mixamorig:RightHandPinky2","mixamorig:RightHandPinky3","mixamorig:RightHandRing1","mixamorig:RightHandRing2", + "mixamorig:RightHandRing3","mixamorig:RightHandThumb1","mixamorig:RightHandThumb2","mixamorig:RightHandThumb3") + +-- 믹사모 -> 바이패드 매핑 (순서 중요 - 절대 변경 금지) +global MXPairs = #(datapair "mixamorig:Hips" "Bip001 Pelvis",datapair "mixamorig:Spine" "Bip001 Spine",datapair "mixamorig:Spine1" "Bip001 Spine1",datapair "mixamorig:Neck" "Bip001 Neck",datapair "mixamorig:Head" "Bip001 Head",datapair "mixamorig:RightShoulder" "Bip001 R Clavicle",datapair "mixamorig:RightArm" "Bip001 R UpperArm",datapair "mixamorig:RightForeArm" "Bip001 R Forearm",datapair "mixamorig:RightHand" "Bip001 R Hand",datapair "mixamorig:RightHandThumb1" "Bip001 R Finger0",datapair "mixamorig:RightHandThumb2" "Bip001 R Finger01",datapair "mixamorig:RightHandThumb3" "Bip001 R Finger02",datapair "mixamorig:RightHandIndex1" "Bip001 R Finger1",datapair "mixamorig:RightHandIndex2" "Bip001 R Finger11",datapair "mixamorig:RightHandIndex3" "Bip001 R Finger12",datapair "mixamorig:RightHandMiddle1" "Bip001 R Finger2",datapair "mixamorig:RightHandMiddle2" "Bip001 R Finger21",datapair "mixamorig:RightHandMiddle3" "Bip001 R Finger22",datapair "mixamorig:RightHandRing1" "Bip001 R Finger3",datapair "mixamorig:RightHandRing2" "Bip001 R Finger31",datapair "mixamorig:RightHandRing3" "Bip001 R Finger32",datapair "mixamorig:RightHandPinky1" "Bip001 R Finger4",datapair "mixamorig:RightHandPinky2" "Bip001 R Finger41",datapair "mixamorig:RightHandPinky3" "Bip001 R Finger42",datapair "mixamorig:LeftShoulder" "Bip001 L Clavicle",datapair "mixamorig:LeftArm" "Bip001 L UpperArm",datapair "mixamorig:LeftForeArm" "Bip001 L Forearm",datapair "mixamorig:LeftHand" "Bip001 L Hand",datapair "mixamorig:LeftHandThumb1" "Bip001 L Finger0",datapair "mixamorig:LeftHandThumb2" "Bip001 L Finger01",datapair "mixamorig:LeftHandThumb3" "Bip001 L Finger02",datapair "mixamorig:LeftHandIndex1" "Bip001 L Finger1",datapair "mixamorig:LeftHandIndex2" "Bip001 L Finger11",datapair "mixamorig:LeftHandIndex3" "Bip001 L Finger12",datapair "mixamorig:LeftHandMiddle1" "Bip001 L Finger2",datapair "mixamorig:LeftHandMiddle2" "Bip001 L Finger21",datapair "mixamorig:LeftHandMiddle3" "Bip001 L Finger22",datapair "mixamorig:LeftHandRing1" "Bip001 L Finger3",datapair "mixamorig:LeftHandRing2" "Bip001 L Finger31",datapair "mixamorig:LeftHandRing3" "Bip001 L Finger32",datapair "mixamorig:LeftHandPinky1" "Bip001 L Finger4",datapair "mixamorig:LeftHandPinky2" "Bip001 L Finger41",datapair "mixamorig:LeftHandPinky3" "Bip001 L Finger42",datapair "mixamorig:RightUpLeg" "Bip001 R Thigh",datapair "mixamorig:RightLeg" "Bip001 R Calf",datapair "mixamorig:RightFoot" "Bip001 R Foot",datapair "mixamorig:RightToeBase" "Bip001 R Toe0",datapair "mixamorig:LeftUpLeg" "Bip001 L Thigh",datapair "mixamorig:LeftLeg" "Bip001 L Calf",datapair "mixamorig:LeftFoot" "Bip001 L Foot",datapair "mixamorig:LeftToeBase" "Bip001 L Toe0") + +-- 제외할 본들 (원본 방식) +-- global excludeBones =#("mixamorig:HeadTop_End","mixamorig:RightHandThumb4","mixamorig:RightHandIndex4","mixamorig:RightHandMiddle4","mixamorig:RightHandRing4", +-- "mixamorig:RightHandPinky4","mixamorig:LeftHandThumb4","mixamorig:LeftHandIndex4","mixamorig:LeftHandMiddle4","mixamorig:LeftHandRing4","mixamorig:LeftHandPinky4", +-- "mixamorig:RightToe_End","mixamorig:LeftToe_End") +global excludeBones =#("mixamorig:HeadTop_End") + +-- 스파인 세그먼트 개수 +global spineSegments = 2 + +-- 특수 스켈레톤 구조 감지 변수들 +global legParentIsSpine = false +global shoulderParentIsNeck = false +global shoulderParentIsSpine = false + + + +-- 발 각도 설정 +global footDegree = 50 +global flipThreshold = 178 + +-- ================================== 유틸리티 함수들 (원본 그대로) =============================================== + +fn pointLineProj pA pB pC = ( + local vAB=pB-pA + local vAC=pC-pA + local d=dot (normalize vAB) (normalize vAC) + (pA+(vAB*(d*(length vAC/length vAB)))) +) + +fn findFootAngle =( + local foots = $'mixamorig:*Foot' as array + if foots.count==0 do return 35 + + local posArr = for i in foots collect i.pos[3] + local idx = findItem posArr (amin(posArr)) + local foot = foots[idx] + local toe = foot.children[1] + if not isvalidnode toe do return 35 + + local p1 = foot.pos + local p2 = toe.pos + local v1 = p1-p2 + p2[3] = p1[3] + local v2 = p1-p2 + acos(dot(normalize v1) (normalize v2)) +) + +fn getHierarchy objArr out= +( + for i in objArr do + ( + appendifunique out i + child = i.children + if child.count>0 do + ( + getHierarchy child out + ) + ) + out +) + +fn getSkinnedObj = ( + skn = getClassInstances skin + objArr = #() + for i in skn do( + for obj in refs.dependentNodes i do appendIfunique objArr obj + ) + objArr +) + +-- 위치 기반으로 실제 본 체인인지 확인하는 함수 +fn isRealBoneChain parentBone childBone tolerance:0.1 =( + if not isValidNode parentBone or not isValidNode childBone then return false + + -- 부모 본의 끝점 계산 (자식이 있으면 자식 방향, 없으면 부모 방향의 반대) + local parentEnd = parentBone.pos + if parentBone.children.count > 0 then ( + -- 가장 가까운 자식 본 방향으로 본 길이 계산 + local closestChild = parentBone.children[1] + local minDist = distance parentBone.pos closestChild.pos + for child in parentBone.children do ( + local dist = distance parentBone.pos child.pos + if dist < minDist do ( + minDist = dist + closestChild = child + ) + ) + parentEnd = closestChild.pos + ) else if parentBone.parent != undefined then ( + -- 부모가 있으면 부모->현재 방향으로 연장 + local direction = normalize (parentBone.pos - parentBone.parent.pos) + local boneLength = distance parentBone.parent.pos parentBone.pos + parentEnd = parentBone.pos + direction * boneLength + ) + + -- 자식 본의 시작점과 부모 본의 끝점 거리 확인 + local dist = distance parentEnd childBone.pos + return dist <= tolerance +) + +fn ScanBranchHRC obj out:#() = ( + local child = obj.children + if child.count > 0 then ( + -- 실제 본 체인으로 연결된 자식 찾기 (위치 기반) + local chainChild = undefined + for c in child do ( + if isRealBoneChain obj c then ( + chainChild = c + exit -- 첫 번째로 체인 연결된 자식만 사용 + ) + ) + + if chainChild != undefined then ( + -- 체인으로 연결된 자식이 있으면 계속 추적 + append out chainChild + ScanBranchHRC chainChild out:out + ) else ( + -- 체인이 끊어지면 undefined 추가 + append out undefined + ) + + -- 체인에 포함되지 않은 나머지 자식들은 별도 처리 + for i in child where i != chainChild do ( + append out i + ScanBranchHRC i out:out + ) + ) else ( + append out undefined + ) + out +) + +fn getBranchHRC obj =( + local scan = ScanBranchHRC obj + local out = #() + local new = undefined + for i in scan do ( + if new == undefined do new = #() + if isvalidNode i then ( + append new i + ) else ( + if new != undefined and new.count > 0 do append out new + new = undefined + ) + ) + -- 마지막 그룹 추가 + if new != undefined and new.count > 0 do append out new + out +) + +fn collectLayerNodes ly out:#()=( + ly.nodes &nd + join out nd + local numchildLy =ly.getNumChildren() + if numchildLy>0 do( + for i=1 to numchildLy do( + local newLy = ly.getChild i + collectLayerNodes newLy out:out + ) + ) + out +) + +fn CleanNames =( + local objArr = $mixamorig* + --Rules 1 + for i in objArr do( + n = i.name + filt = filterstring n ":" + if filt.Count>1 do( + newName = "mixamorig:" + for j=2 to filt.Count do ( + newName += filt[j] + if j!=filt.Count do newName+=":" + ) + i.name = newName + ) + ) +) + +fn SanityCheck = ( + hips = $'*:Hips*' + if hips.count>0 then( + hrc = getHierarchy hips #() + for obj in hrc do( + case of ( + (matchpattern obj.name pattern:"*Hips"): obj.name = "mixamorig:Hips" + (matchpattern obj.name pattern:"*Spine"): obj.name = "mixamorig:Spine" + (matchpattern obj.name pattern:"*Spine1"): obj.name = "mixamorig:Spine1" + + (matchpattern obj.name pattern:"*Neck"): obj.name = "mixamorig:Neck" + (matchpattern obj.name pattern:"*Head"): obj.name = "mixamorig:Head" + + (matchpattern obj.name pattern:"*LeftShoulder"): obj.name = "mixamorig:LeftShoulder" + (matchpattern obj.name pattern:"*LeftArm"): obj.name = "mixamorig:LeftArm" + (matchpattern obj.name pattern:"*LeftForeArm"): obj.name = "mixamorig:LeftForeArm" + (matchpattern obj.name pattern:"*LeftHand"): obj.name = "mixamorig:LeftHand" + (matchpattern obj.name pattern:"*LeftHandThumb1"): obj.name = "mixamorig:LeftHandThumb1" + (matchpattern obj.name pattern:"*LeftHandThumb2"): obj.name = "mixamorig:LeftHandThumb2" + (matchpattern obj.name pattern:"*LeftHandThumb3"): obj.name = "mixamorig:LeftHandThumb3" + -- LeftHandThumb4 제거됨 + (matchpattern obj.name pattern:"*LeftHandIndex1"): obj.name = "mixamorig:LeftHandIndex1" + (matchpattern obj.name pattern:"*LeftHandIndex2"): obj.name = "mixamorig:LeftHandIndex2" + (matchpattern obj.name pattern:"*LeftHandIndex3"): obj.name = "mixamorig:LeftHandIndex3" + -- LeftHandIndex4 제거됨 + (matchpattern obj.name pattern:"*LeftHandMiddle1"): obj.name = "mixamorig:LeftHandMiddle1" + (matchpattern obj.name pattern:"*LeftHandMiddle2"): obj.name = "mixamorig:LeftHandMiddle2" + (matchpattern obj.name pattern:"*LeftHandMiddle3"): obj.name = "mixamorig:LeftHandMiddle3" + -- LeftHandMiddle4 제거됨 + (matchpattern obj.name pattern:"*LeftHandRing1"): obj.name = "mixamorig:LeftHandRing1" + (matchpattern obj.name pattern:"*LeftHandRing2"): obj.name = "mixamorig:LeftHandRing2" + (matchpattern obj.name pattern:"*LeftHandRing3"): obj.name = "mixamorig:LeftHandRing3" + -- LeftHandRing4 제거됨 + (matchpattern obj.name pattern:"*LeftHandPinky1"): obj.name = "mixamorig:LeftHandPinky1" + (matchpattern obj.name pattern:"*LeftHandPinky2"): obj.name = "mixamorig:LeftHandPinky2" + (matchpattern obj.name pattern:"*LeftHandPinky3"): obj.name = "mixamorig:LeftHandPinky3" + -- LeftHandPinky4 제거됨 + (matchpattern obj.name pattern:"*RightShoulder"): obj.name = "mixamorig:RightShoulder" + (matchpattern obj.name pattern:"*RightArm"): obj.name = "mixamorig:RightArm" + (matchpattern obj.name pattern:"*RightForeArm"): obj.name = "mixamorig:RightForeArm" + (matchpattern obj.name pattern:"*RightHand"): obj.name = "mixamorig:RightHand" + (matchpattern obj.name pattern:"*RightHandThumb1"): obj.name = "mixamorig:RightHandThumb1" + (matchpattern obj.name pattern:"*RightHandThumb2"): obj.name = "mixamorig:RightHandThumb2" + (matchpattern obj.name pattern:"*RightHandThumb3"): obj.name = "mixamorig:RightHandThumb3" + -- RightHandThumb4 제거됨 + (matchpattern obj.name pattern:"*RightHandIndex1"): obj.name = "mixamorig:RightHandIndex1" + (matchpattern obj.name pattern:"*RightHandIndex2"): obj.name = "mixamorig:RightHandIndex2" + (matchpattern obj.name pattern:"*RightHandIndex3"): obj.name = "mixamorig:RightHandIndex3" + -- RightHandIndex4 제거됨 + (matchpattern obj.name pattern:"*RightHandMiddle1"): obj.name = "mixamorig:RightHandMiddle1" + (matchpattern obj.name pattern:"*RightHandMiddle2"): obj.name = "mixamorig:RightHandMiddle2" + (matchpattern obj.name pattern:"*RightHandMiddle3"): obj.name = "mixamorig:RightHandMiddle3" + -- RightHandMiddle4 제거됨 + (matchpattern obj.name pattern:"*RightHandRing1"): obj.name = "mixamorig:RightHandRing1" + (matchpattern obj.name pattern:"*RightHandRing2"): obj.name = "mixamorig:RightHandRing2" + (matchpattern obj.name pattern:"*RightHandRing3"): obj.name = "mixamorig:RightHandRing3" + -- RightHandRing4 제거됨 + (matchpattern obj.name pattern:"*RightHandPinky1"): obj.name = "mixamorig:RightHandPinky1" + (matchpattern obj.name pattern:"*RightHandPinky2"): obj.name = "mixamorig:RightHandPinky2" + (matchpattern obj.name pattern:"*RightHandPinky3"): obj.name = "mixamorig:RightHandPinky3" + -- RightHandPinky4 제거됨 + (matchpattern obj.name pattern:"*LeftUpLeg"): obj.name = "mixamorig:LeftUpLeg" + (matchpattern obj.name pattern:"*LeftLeg"): obj.name = "mixamorig:LeftLeg" + (matchpattern obj.name pattern:"*LeftFoot"): obj.name = "mixamorig:LeftFoot" + (matchpattern obj.name pattern:"*LeftToeBase"): obj.name = "mixamorig:LeftToeBase" + -- LeftToe_End 제거됨 + (matchpattern obj.name pattern:"*RightUpLeg"): obj.name = "mixamorig:RightUpLeg" + (matchpattern obj.name pattern:"*RightLeg"): obj.name = "mixamorig:RightLeg" + (matchpattern obj.name pattern:"*RightFoot"): obj.name = "mixamorig:RightFoot" + (matchpattern obj.name pattern:"*RightToeBase"): obj.name = "mixamorig:RightToeBase" + -- RightToe_End 제거됨 + ) + ) + + local valid = true + allObjectNames = for i in objects collect i.name + for i in MXNames do ( + -- excludeBones에 있는 본들은 체크하지 않음 + if findItem excludeBones i == 0 then ( + if findItem allObjectNames i==0 do ( + valid = false + format "[ERROR] : $% not found!\n" i + ) + ) + ) + valid = true + return valid + ) + return false +) + +fn createBip height spine:2 fingers:5= ( + -- Triangle Pelvis는 False, Triangle Neck는 True로 설정 + local bipObj = biped.createNew height -90 [0,0,height*0.9562] arms:true neckLinks:1 \ + spineLinks:spine legLinks:3 \ + fingers:fingers fingerLinks:3 toes:1 \ + toeLinks:1 ankleAttach:0.3 trianglePelvis:False triangleNeck:True + + bipObj +) + +fn getRigStructures =( + local n = "mixamorig:" + + local root = execute ("$'"+n+"Hips'") + + -- 다리 본들 (부모 관계 확인) + local LLeg1 = execute ("$'"+n+"LeftUpLeg'") + local LLeg2 = execute ("$'"+n+"LeftLeg'") + local LFoot = execute ("$'"+n+"LeftFoot'") + local LToe = execute ("$'"+n+"LeftToeBase'") + + local RLeg1 = execute ("$'"+n+"RightUpLeg'") + local RLeg2 = execute ("$'"+n+"RightLeg'") + local RFoot = execute ("$'"+n+"RightFoot'") + local RToe = execute ("$'"+n+"RightToeBase'") + + -- 스파인 본들 + local LSpine = execute ("$'"+n+"Spine'") + local LSpine1 = execute ("$'"+n+"Spine1'") + local spines = #(LSpine, LSpine1) + spineSegments = 2 + + -- 다리가 Spine에서 시작하는 경우 처리 + -- 다리 본들의 부모가 Spine인지 확인 + legParentIsSpine = false + if LLeg1 != undefined and LLeg1.parent != undefined then ( + if findString LLeg1.parent.name "Spine" != undefined then ( + legParentIsSpine = true + format "다리가 Spine에서 시작됨을 감지\\n" + ) + ) + + + -- 어깨/팔 본들 + local LClav = execute ("$'"+n+"LeftShoulder'") + local LArm1 = execute ("$'"+n+"LeftArm'") + local LArm2 = execute ("$'"+n+"LeftForeArm'") + local LHand = execute ("$'"+n+"LeftHand'") + + local RClav = execute ("$'"+n+"RightShoulder'") + local RArm1 = execute ("$'"+n+"RightArm'") + local RArm2 = execute ("$'"+n+"RightForeArm'") + local RHand = execute ("$'"+n+"RightHand'") + + -- 🔍 팔 구조 디버그 출력 + format "\\n=== 팔 구조 디버그 ===\\n" + if RClav != undefined then ( + format "RightShoulder: %\\n" RClav.name + format " 자식들: " + for child in RClav.children do format "%, " child.name + format "\\n" + ) + if RArm1 != undefined then ( + format "RightArm: %\\n" RArm1.name + format " 자식들: " + for child in RArm1.children do format "%, " child.name + format "\\n" + ) + if RArm2 != undefined then ( + format "RightForeArm: %\\n" RArm2.name + format " 자식들: " + for child in RArm2.children do format "%, " child.name + format "\\n" + ) + format "========================\\n" + + local neck = execute ("$'"+n+"Neck'") + local head = execute ("$'"+n+"Head'") + + -- 어깨가 어디서 시작하는지 확인 + shoulderParentIsNeck = false + shoulderParentIsSpine = false + if LClav != undefined and LClav.parent != undefined then ( + if findString LClav.parent.name "Neck" != undefined then ( + shoulderParentIsNeck = true + format "어깨가 Neck에서 시작됨을 감지\\n" + ) else if findString LClav.parent.name "Spine" != undefined and + findString LClav.parent.name "Spine1" == undefined then ( + shoulderParentIsSpine = true + format "어깨가 Spine에서 바로 시작됨을 감지\\n" + ) + ) + + local LFinger1 = $'mixamorig:LeftHandThumb1' + local LFinger2 = $'mixamorig:LeftHandIndex1' + local LFinger3 = $'mixamorig:LeftHandMiddle1' + local LFinger4 = $'mixamorig:LeftHandRing1' + local LFinger5 = $'mixamorig:LeftHandPinky1' + + local RFinger1 = $'mixamorig:RightHandThumb1' + local RFinger2 = $'mixamorig:RightHandIndex1' + local RFinger3 = $'mixamorig:RightHandMiddle1' + local RFinger4 = $'mixamorig:RightHandRing1' + local RFinger5 = $'mixamorig:RightHandPinky1' + + -- 스켈레톤 구조에 따라 적응적으로 배치 + if (legParentIsSpine and shoulderParentIsNeck) or (shoulderParentIsSpine) then ( + -- 특수 스켈레톤 구조들 + if shoulderParentIsSpine then ( + format "특수 스켈레톤 구조 감지: 어깨->Spine\\n" + ) else ( + format "특수 스켈레톤 구조 감지: 다리->Spine, 어깨->Neck\\n" + ) + rigNodes = #(root,root) + join rigNodes #(LLeg1, LLeg2, LFoot, LToe, RLeg1, RLeg2, RFoot, RToe) -- 다리들 먼저 + join rigNodes Spines -- 스파인들 + join rigNodes #(neck, head) -- 목과 머리 + join rigNodes #(LClav, LArm1, LArm2, LHand, RClav, RArm1, RArm2, RHand) -- 어깨와 팔들 + join rigNodes #(LFinger1,LFinger2,LFinger3,LFinger4,LFinger5, RFinger1,RFinger2,RFinger3,RFinger4,RFinger5) + ) else ( + -- 표준 구조 + rigNodes = #(root,root) + join rigNodes Spines + join rigNodes #( LClav, + LArm1, + LArm2, + LHand, + RClav, + RArm1, + RArm2, + RHand, + neck, + head, + LLeg1, + LLeg2, + LFoot, + LToe, + RLeg1, + RLeg2, + RFoot, + RToe, + LFinger1,LFinger2,LFinger3,LFinger4,LFinger5, + RFinger1,RFinger2,RFinger3,RFinger4,RFinger5 + ) + ) + rigNodes +) + +fn GetBipStructure bip spinesegs:3= ( + local bip_pelvis = biped.getNode bip #pelvis link:1 + local bip_root = bip_pelvis.parent + + local bip_LLeg1 = biped.getNode bip #lleg link:1 + local bip_LLeg2 = biped.getNode bip #lleg link:2 + local bip_LFoot = biped.getNode bip #lleg link:3 + local bip_LToe = biped.getNode bip #ltoes link:1 + + local bip_RLeg1 = biped.getNode bip #rleg link:1 + local bip_RLeg2 = biped.getNode bip #rleg link:2 + local bip_RFoot = biped.getNode bip #rleg link:3 + local bip_RToe = biped.getNode bip #rtoes link:1 + + local bip_LClav = biped.getNode bip #larm link:1 + local bip_LArm1 = biped.getNode bip #larm link:2 + local bip_LArm2 = biped.getNode bip #larm link:3 + local bip_LHand = biped.getNode bip #larm link:4 + + local bip_RClav = biped.getNode bip #rarm link:1 + local bip_RArm1 = biped.getNode bip #rarm link:2 + local bip_RArm2 = biped.getNode bip #rarm link:3 + local bip_RHand = biped.getNode bip #rarm link:4 + + local bip_neck = biped.getNode bip #neck link:1 + local bip_head = biped.getNode bip #head link:1 + + local bip_Lfinger0 = biped.getNode bip #lfingers link:1 + local bip_Lfinger1 = biped.getNode bip #lfingers link:4 + local bip_Lfinger2 = biped.getNode bip #lfingers link:7 + local bip_Lfinger3 = biped.getNode bip #lfingers link:10 + local bip_Lfinger4 = biped.getNode bip #lfingers link:13 + + local bip_Rfinger0 = biped.getNode bip #rfingers link:1 + local bip_Rfinger1 = biped.getNode bip #rfingers link:4 + local bip_Rfinger2 = biped.getNode bip #rfingers link:7 + local bip_Rfinger3 = biped.getNode bip #rfingers link:10 + local bip_Rfinger4 = biped.getNode bip #rfingers link:13 + + -- 스켈레톤 구조에 따라 적응적으로 배치 (getRigStructures와 동일 순서) + if (legParentIsSpine and shoulderParentIsNeck) or (shoulderParentIsSpine) then ( + -- 특수 구조들 + format "Biped도 특수 구조로 배치\\n" + bipNodes = #(bip_root,bip_pelvis) + -- 다리들 먼저 + join bipNodes #(bip_LLeg1, bip_LLeg2, bip_LFoot, bip_LToe, bip_RLeg1, bip_RLeg2, bip_RFoot, bip_RToe) + -- 스파인들 + for i=1 to spinesegs do append bipNodes (biped.getNode bip #spine link:i) + -- 목과 머리 + join bipNodes #(bip_neck, bip_head) + -- 어깨와 팔들 + join bipNodes #(bip_LClav, bip_LArm1, bip_LArm2, bip_LHand, bip_RClav, bip_RArm1, bip_RArm2, bip_RHand) + -- 손가락들 + join bipNodes #(bip_Lfinger0,bip_Lfinger1,bip_Lfinger2,bip_Lfinger3,bip_Lfinger4, bip_Rfinger0,bip_Rfinger1,bip_Rfinger2,bip_Rfinger3,bip_Rfinger4) + ) else ( + -- 표준 구조 + bipNodes = #(bip_root,bip_pelvis) + --SPINES + for i=1 to spinesegs do append bipNodes (biped.getNode bip #spine link:i) + join bipNodes #(bip_LClav, + bip_LArm1, + bip_LArm2, + bip_LHand, + bip_RClav, + bip_RArm1, + bip_RArm2, + bip_RHand, + bip_neck, + bip_head, + bip_LLeg1, + bip_LLeg2, + bip_LFoot, + bip_LToe, + bip_RLeg1, + bip_RLeg2, + bip_RFoot, + bip_RToe, + bip_Lfinger0,bip_Lfinger1,bip_Lfinger2,bip_Lfinger3,bip_Lfinger4, + bip_Rfinger0,bip_Rfinger1,bip_Rfinger2,bip_Rfinger3,bip_Rfinger4 + ) + ) + bipNodes +) + +fn MapTMAxis sourceTM targetTM indexes mult:[1,1,1]=( + axis = #(sourceTM.row1,sourceTM.row2,sourceTM.row3) + targetTM.row1 = axis[indexes[1]]*mult[1] + targetTM.row2 = axis[indexes[2]]*mult[2] + targetTM.row3 = axis[indexes[3]]*mult[3] + targetTM +) + +fn SetClavicleTM bipCtl bipClav TM =( + local figMode = bipCtl.figuremode + bipCtl.figuremode = false -- FORCE EXIT FIGURE MODE + biped.setTransform bipClav #rotation TM.rotationPart false + + -- COPY POSTURE + select bipClav + local col = biped.createCopyCollection bipCtl "Shoulder" + local cpy = biped.copyBipPosture bipCtl col (selection as array) #snapAuto + + -- PASTE POSTURE ON FIGURE MODE + bipCtl.figuremode = true + biped.pasteBipPosture bipCtl cpy false #pstcopied false false false false + biped.deleteAllCopyCollections bipCtl + biped.setTransform bipClav #pos TM.row4 false + clearSelection() + bipCtl.figuremode = figMode +) + +fn alignFootTpose rigObj BipObj =( + local RigTM = rigObj.transform + + local endPoint = rigObj.children[1].pos + local height = abs (endPoint[3]-RigTM.row4[3]) + + + endPoint[3] = RigTM.row4[3] + local v = endPoint-RigTM.row4 + local len = length v + local y = normalize(v) + local x = [0,0,-1] + local z = normalize(cross x y) + local TM = Matrix3 x y z RigTM.row4 + + -- #SET ROTATION + biped.setTransform BipObj #rotation TM.rotationpart false + biped.setTransform bipObj #scale [height,len*1.25,len*0.6] false + biped.setTransform BipObj #pos RigTM.row4 false +) + +fn alignFoot rigObj BipObj figureMode:true degree:footDegree =( + local isSetKey = not figureMode + local RigTM = rigObj.transform + local bipTM = rotateYmatrix (90)* RigTM + local bipTM = rotateZmatrix degree*bipTM + + -- #SET ROTATION + biped.setTransform BipObj #rotation bipTM.rotationpart isSetKey + biped.setTransform BipObj #pos RigTM.row4 isSetKey + + + -- SET SCALE (ONLY IN FIGURE MODE) + if figureMode do( + + local toeTM = copy rigTM + local p1 = rigObj.children[1].pos + toeTM.row4 = p1 + + local v = rigTM.row2*-1 + local p2 = rigTM.row4*inverse toeTM + local rotTM = RotateXMatrix degree*toeTM + p2 *= rotTM + + local v = p2-p1 + local p3 = pointLineProj p2 p1 RigTM.row4 + local len = (length v ) + local height = length (p2-RigTM.row4) + biped.setTransform bipObj #scale [height,len,len*0.6] isSetKey + ) +) + +fn AlignArm BipUpperArm MxUpperArm side:"R" FigureMode:false TPose:false = +( + local createKey = not FigureMode + local BipLowerArm = BipUpperArm.children[1] + local Biphand = BipLowerArm.children[1] + local Bip_UpperTM = BipUpperArm.transform + local Bip_LowerTM = BipLowerArm.transform + local Bip_HandTM = Biphand.transform + + -- 정확한 본 이름으로 찾기 (children[1] 대신) + local MxForeArm = undefined + local MxHand = undefined + + if side == "R" then ( + MxForeArm = execute ("$'mixamorig:RightForeArm'") + MxHand = execute ("$'mixamorig:RightHand'") + ) else ( + MxForeArm = execute ("$'mixamorig:LeftForeArm'") + MxHand = execute ("$'mixamorig:LeftHand'") + ) + + -- 백업: 이름으로 못 찾으면 children[1] 사용 + if MxForeArm == undefined do MxForeArm = MxUpperArm.children[1] + if MxHand == undefined do MxHand = MxForeArm.children[1] + local MX_UpperTM = MxUpperArm.transform + local MX_ElbowTM = MxForeArm.transform + local MX_HandTM = MxHand.transform + + + -- SET SCALE + if FigureMode do( + len = distance MxUpperArm MxForeArm + biped.setTransform BipUpperArm #scale [len,len,len] createKey + len = distance MxForeArm MxHand + biped.setTransform BipLowerArm #scale [len,len,len] createKey + midFing = MxHand.children[3] + if isvalidNode midFing then len = distance MxHand MxHand.children[3] + else len *=0.2 + biped.setTransform Biphand #scale [len,len,len] createKey + ) + + local mid = (MX_HandTM.row4+MX_UpperTM.row4)*0.5 + local up = Normalize (mid-MX_ElbowTM.row4) + if TPose do up = [0,-1,0] + + --SET HAND FIRST BECAUSE THE IK + biped.setTransform Biphand #pos MX_HandTM.row4 createKey + + -- UpperArm + local x = Normalize (MX_ElbowTM.row4-Bip_UpperTM.row4) + local z = Normalize (cross up x ) + local y = Normalize (cross z x) + + local upperTM = matrix3 x y z MX_UpperTM.row4 + + -- UpperArm: 기하학적 계산은 유지, 축 변환만 제거 + biped.setTransform BipUpperArm #rotation upperTM.rotationpart createKey + + -- Hand: 위치만 설정, 축 변환 제거 + biped.setTransform Biphand #pos MX_HandTM.row4 createKey + +) + +fn AlignLeg BipUpperLeg MxUpperLeg side:"R" FigureMode:false TPose:false = +( + local createKey = not FigureMode + local BipLoweLeg = BipUpperLeg.children[1] + local BipFoot = BipLoweLeg.children[1] + local MxLowerLeg = MxUpperLeg.children[1] + local MxFoot = MxLowerLeg.children[1] + local rigTM = MxUpperLeg.transform + local BipUpperTM = BipUpperLeg.transform + local KneeTM = MxLowerLeg.transform + local endTM = MxFoot.transform + + if FigureMode do( + dist = distance MxUpperLeg MxLowerLeg + biped.setTransform BipUpperLeg #scale [dist,dist,dist] createKey + dist = distance MxLowerLeg MxFoot + biped.setTransform BipLoweLeg #scale [dist,dist,dist] createKey + ) + + local mid = (endTM.row4+rigTM.row4)*0.5 + local up = Normalize (mid-KneeTM.row4) + if TPose do up = [0,1,0] + + -- Foot + biped.setTransform BipFoot #pos endTM.row4 createKey + + -- UpperLeg + local x = Normalize (KneeTM.row4-BipUpperTM.row4) + local z = Normalize (cross up x ) + local y = Normalize (cross z x) + + local newTM = matrix3 x y z rigTM.row4 + + -- UpperLeg: 기하학적 계산은 유지, 축 변환만 제거 + biped.setTransform BipUpperLeg #rotation newTM.rotationpart createKey +) + +fn alignFingers BipNode MXNode figureMode:false=( + local bip2 = BipNode.children[1] + local bip3 = bip2.children[1] + local BipNodes = #(BipNode,bip2,bip3) + + local MX2 = MXNode.children[1] + local MX3 = MX2.children[1] + local MX4 = MX3.children[1] + local MXNodes = #(MXNode,MX2,MX3,MX4) + + if figureMode then( + for i=1 to 3 where isvalidNode MXNodes[i] do( + -- 단순 크기 조정만 (축 변환 제거) + if isValidNode MXNodes[i+1] then len = distance MXNodes[i] MXNodes[i+1] + else len = distance MXNodes[i] MXNodes[i].parent + + biped.setTransform BipNodes[i] #scale [len,len,len] false + + -- 첫 번째 관절만 위치 설정 + if i==1 do biped.setTransform BipNodes[i] #pos MXNodes[i].transform.row4 false + ) + + -- 손가락 펴기: 좌우손 엄지/다른손가락 하드코딩 처리 + try ( + local isThumb = (findString BipNode.name "Finger0" != undefined) + local isLeftHand = (findString BipNode.name " L " != undefined) or (findString BipNode.name "Left" != undefined) + + -- === 왼손 엄지 === + if isThumb and isLeftHand then ( + if isvalidNode BipNodes[1] do + biped.setTransform BipNodes[1] #rotation (eulerAngles 0 10 -30) false + if isvalidNode BipNodes[2] do + biped.setTransform BipNodes[2] #rotation (eulerAngles 0 10 -30) false + if isvalidNode BipNodes[3] do + biped.setTransform BipNodes[3] #rotation (eulerAngles 0 10 -30) false + ) + -- === 오른손 엄지 === + else if isThumb and not isLeftHand then ( + if isvalidNode BipNodes[1] do + biped.setTransform BipNodes[1] #rotation (eulerAngles 0 10 -150) false + if isvalidNode BipNodes[2] do + biped.setTransform BipNodes[2] #rotation (eulerAngles 0 10 -150) false + if isvalidNode BipNodes[3] do + biped.setTransform BipNodes[3] #rotation (eulerAngles 0 10 -150) false + ) + -- === 왼손 다른 손가락들 === + else if not isThumb and isLeftHand then ( + if isvalidNode BipNodes[1] do + biped.setTransform BipNodes[1] #rotation (eulerAngles -90 0 0) false + if isvalidNode BipNodes[2] do + biped.setTransform BipNodes[2] #rotation (eulerAngles -100 0 0) false + if isvalidNode BipNodes[3] do + biped.setTransform BipNodes[3] #rotation (eulerAngles -105 0 0) false + ) + -- === 오른손 다른 손가락들 === + else if not isThumb and not isLeftHand then ( + if isvalidNode BipNodes[1] do + biped.setTransform BipNodes[1] #rotation (eulerAngles -90 0 180) false + if isvalidNode BipNodes[2] do + biped.setTransform BipNodes[2] #rotation (eulerAngles -100 0 180) false + if isvalidNode BipNodes[3] do + biped.setTransform BipNodes[3] #rotation (eulerAngles -105 0 180) false + ) + ) catch() + ) + -- Animation Mode에서는 회전 조정 안함 (축 변환 제거) + +) + +fn BipToRigProportion bipNodes rigNodes TPose:false =( + footDegree = findFootAngle() + matchIndexes = #{1..(bipNodes.count)} + + + bipctl = bipNodes[1].transform.controller + bipctl.figureMode = true + + + Lthigh = $'mixamorig:LeftUpLeg' + Rthigh = $'mixamorig:RightUpLeg' + p = (Lthigh.pos+Rthigh.pos)*0.5 + + + + for i in matchIndexes where isvalidnode bipNodes[i] do( + n = bipNodes[i].name + bipNode = bipNodes[i] + rigNode = rigNodes[i] + if isvalidNode bipNode and isvalidNode rigNode do( + + rigNodeEnd = rigNodes[i+1] + rigTM = rigNode.transform + BipTM = bipNode.transform + + case of( + (i==1):( + -- Root: 위치만 조정, 축 변환 제거 + biped.setTransform bipNode #pos p false + ) + (findString n "Head"!= undefined): ( + rigNodeEnd = rigNode.children[1] + if isvalidNode rigNodeEnd do( + -- Head: 크기와 위치만 조정, 축 변환 제거 + dist = distance rigNode rigNodeEnd + biped.setTransform bipNode #scale [dist,dist,dist] false + biped.setTransform bipNode #pos rigNode.transform.row4 false + ) + ) + + (matchPattern n pattern:"*Neck*"):( + -- Neck: 크기만 조정, 축 변환 제거 + dist = distance rigNode rigNodeEnd + biped.setTransform bipNode #scale [dist,dist,dist] false + ) + + + (matchPattern n pattern:"*Foot*" ):( + if TPose then alignFootTpose rigNode bipNode + else alignFoot rigNode bipNode figureMode:true + ) + + (matchPattern n pattern:"*Toe*"):( + rigNodeEnd = rigNode.children[1] + if isvalidNode rigNodeEnd do( + -- Toe: 크기와 위치만 조정, 축 변환 제거 + dist = distance rigNode rigNodeEnd + biped.setTransform bipNode #scale [dist,dist*0.2,dist] false + biped.setTransform bipNode #pos rigNode.pos false + ) + ) + + (findString n "Pelvis"!= undefined ):( + rigNode = $'mixamorig:LeftUpLeg' + rigNodeEnd = $'mixamorig:RightUpLeg' + dist = distance Lthigh Rthigh + len = distance rigNode rigNodeEnd + biped.setTransform bipNode #scale [dist,dist,dist] false + ) + + + (MatchPattern n pattern:"*Spine*"):( + -- 스파인 2개 구조에서 Spine1이 마지막 스파인 + if findString n "Spine1"!=undefined do ( + rigNodeEnd =$'mixamorig:Neck' + ) + if isvalidNode rigNode and isvalidNode rigNodeEnd do( + len = distance rigNode rigNodeEnd + biped.setTransform bipNode #scale [len,len,len] false + ) + + if matchpattern n pattern:"*Spine*" do( + -- Spine: 위치만 조정, 축 변환 제거 + biped.setTransform bipNode #pos rigNode.pos false + ) + ) + + (matchPattern n pattern:"*Clavicle*"):( + -- 정확한 Arm 본 찾기 (children[1] 대신) + rigNodeEnd = undefined + if findString n "L Clavicle" != undefined or findString n "LeftShoulder" != undefined then ( + rigNodeEnd = execute ("$'mixamorig:LeftArm'") + ) else if findString n "R Clavicle" != undefined or findString n "RightShoulder" != undefined then ( + rigNodeEnd = execute ("$'mixamorig:RightArm'") + ) + + -- 백업: 이름으로 못 찾으면 children[1] 사용 + if rigNodeEnd == undefined do rigNodeEnd = rigNode.children[1] + + len = distance rigNode rigNodeEnd + + x = Normalize (rigNodeEnd.pos-rigNode.pos) + z = Normalize (Cross (bipNode.parent.transform.row2) x) + y = Normalize (cross z x) + newTM = matrix3 x y z rigTM.row4 + biped.setTransform bipNode #scale [len,len,len] false + SetClavicleTM bipCtl bipNode newTM + ) + + (matchPattern n pattern:"*UpperArm*"):( + + if MatchPattern n pattern:"* R *" then AlignArm bipNode rigNode side:"R" FigureMode:true TPose:TPose + else AlignArm bipNode rigNode side:"L" FigureMode:true TPose:TPose + + ) + + (matchPattern n pattern:"*Thigh*"):( + if MatchPattern n pattern:"* R *" then AlignLeg bipNode rigNode side:"R" figureMode:true TPose:TPose + else AlignLeg bipNode rigNode side:"L" figureMode:true TPose:TPose + ) + (MatchPattern n pattern:"*Finger*"): alignFingers bipNode rigNode figureMode:true + ) + ) + ) + + bipctl.figureMode = false +) + +fn getNumFingers = ( + local counts = #(0,0) + local lHand = (getNodeByName "mixamorig:LeftHand") + local rHand = (getNodeByName "mixamorig:RightHand") + if isValidnode lHand do counts[1] = lHand.children.count + if isValidnode rHand do counts[2] = rHand.children.count + amax counts +) + +fn GetExtras root=( + local out = #() + if root!=undefined do( + hrc = getBranchHRC root + for h=hrc.count to 1 by -1 do ( + local objArr = hrc[h] + for j=objArr.count to 1 by -1 do( + if (finditem MXNames objArr[j].name)!=0 do deleteitem objArr j -- REMOVE STANDARD FROM EXTRAS + ) + if objArr.count==0 do deleteItem hrc h + ) + out = hrc + ) + out +) + +fn cleanUpSkin obj =( + local skn = obj.modifiers["skin"] + local count = skinOps.GetNumberBones skn + local IDList = #() + for i=1 to count do( + n = (skinOps.GetBoneName skn i 0) + if findItem excludeBones n!=0 do( + append IDList i + ) + ) + if IDList.count>0 do for i=IDList.count to 1 by -1 do( + skinOps.removebone skn IDList[i] + ) +) + +fn GetSkinPairs obj extra:#() =( + local out=#() + noPairs = #() + local extraV1 = for i in extra collect i.v1 + local skn = obj.modifiers[#skin] + local count = skinOps.GetNumberBones skn + for i=1 to count do( + foundPairs = true + n = (skinOps.GetBoneName skn i 0) + dp = datapair n "" + case of( + (matchpattern n pattern:"*Hips"): dp.v2 = "Bip001 Pelvis" + (matchpattern n pattern:"*Spine"): dp.v2 = "Bip001 Spine" + (matchpattern n pattern:"*Spine1"): dp.v2 = "Bip001 Spine1" + (matchpattern n pattern:"*Spine2"): dp.v2 = "Bip001 Spine2" + (matchpattern n pattern:"*Neck"): dp.v2 = "Bip001 Neck" + (matchpattern n pattern:"*Head"): dp.v2 = "Bip001 Head" + (matchpattern n pattern:"*LeftShoulder"): dp.v2 = "Bip001 L Clavicle" + (matchpattern n pattern:"*LeftArm"): dp.v2 = "Bip001 L UpperArm" + (matchpattern n pattern:"*LeftForeArm"): dp.v2 = "Bip001 L Forearm" + (matchpattern n pattern:"*LeftHand"): dp.v2 = "Bip001 L Hand" + (matchpattern n pattern:"*LeftHandThumb1"): dp.v2 = "Bip001 L Finger0" + (matchpattern n pattern:"*LeftHandThumb2"): dp.v2 = "Bip001 L Finger01" + (matchpattern n pattern:"*LeftHandThumb3"): dp.v2 = "Bip001 L Finger02" + (matchpattern n pattern:"*LeftHandThumb4"): dp.v2 = "Bip001 L Finger0Nub" + (matchpattern n pattern:"*LeftHandIndex1"): dp.v2 = "Bip001 L Finger1" + (matchpattern n pattern:"*LeftHandIndex2"): dp.v2 = "Bip001 L Finger11" + (matchpattern n pattern:"*LeftHandIndex3"): dp.v2 = "Bip001 L Finger12" + (matchpattern n pattern:"*LeftHandIndex4"): dp.v2 = "Bip001 L Finger1Nub" + (matchpattern n pattern:"*LeftHandMiddle1"): dp.v2 = "Bip001 L Finger2" + (matchpattern n pattern:"*LeftHandMiddle2"): dp.v2 = "Bip001 L Finger21" + (matchpattern n pattern:"*LeftHandMiddle3"): dp.v2 = "Bip001 L Finger22" + (matchpattern n pattern:"*LeftHandMiddle4"): dp.v2 = "Bip001 L Finger2Nub" + (matchpattern n pattern:"*LeftHandRing1"): dp.v2 = "Bip001 L Finger3" + (matchpattern n pattern:"*LeftHandRing2"): dp.v2 = "Bip001 L Finger31" + (matchpattern n pattern:"*LeftHandRing3"): dp.v2 = "Bip001 L Finger32" + (matchpattern n pattern:"*LeftHandRing4"): dp.v2 = "Bip001 L Finger3Nub" + (matchpattern n pattern:"*LeftHandPinky1"): dp.v2 = "Bip001 L Finger4" + (matchpattern n pattern:"*LeftHandPinky2"): dp.v2 = "Bip001 L Finger41" + (matchpattern n pattern:"*LeftHandPinky3"): dp.v2 = "Bip001 L Finger42" + (matchpattern n pattern:"*LeftHandPinky4"): dp.v2 = "Bip001 L Finger4Nub" + (matchpattern n pattern:"*RightShoulder"): dp.v2 = "Bip001 R Clavicle" + (matchpattern n pattern:"*RightArm"): dp.v2 = "Bip001 R UpperArm" + (matchpattern n pattern:"*RightForeArm"): dp.v2 = "Bip001 R Forearm" + (matchpattern n pattern:"*RightHand"): dp.v2 = "Bip001 R Hand" + (matchpattern n pattern:"*RightHandThumb1"): dp.v2 = "Bip001 R Finger0" + (matchpattern n pattern:"*RightHandThumb2"): dp.v2 = "Bip001 R Finger01" + (matchpattern n pattern:"*RightHandThumb3"): dp.v2 = "Bip001 R Finger02" + (matchpattern n pattern:"*RightHandThumb4"): dp.v2 = "Bip001 R Finger0Nub" + (matchpattern n pattern:"*RightHandIndex1"): dp.v2 = "Bip001 R Finger1" + (matchpattern n pattern:"*RightHandIndex2"): dp.v2 = "Bip001 R Finger11" + (matchpattern n pattern:"*RightHandIndex3"): dp.v2 = "Bip001 R Finger12" + (matchpattern n pattern:"*RightHandIndex4"): dp.v2 = "Bip001 R Finger1Nub" + (matchpattern n pattern:"*RightHandMiddle1"): dp.v2 = "Bip001 R Finger2" + (matchpattern n pattern:"*RightHandMiddle2"): dp.v2 = "Bip001 R Finger21" + (matchpattern n pattern:"*RightHandMiddle3"): dp.v2 = "Bip001 R Finger22" + (matchpattern n pattern:"*RightHandMiddle4"): dp.v2 = "Bip001 R Finger2Nub" + (matchpattern n pattern:"*RightHandRing1"): dp.v2 = "Bip001 R Finger3" + (matchpattern n pattern:"*RightHandRing2"): dp.v2 = "Bip001 R Finger31" + (matchpattern n pattern:"*RightHandRing3"): dp.v2 = "Bip001 R Finger32" + (matchpattern n pattern:"*RightHandRing4"): dp.v2 = "Bip001 R Finger3Nub" + (matchpattern n pattern:"*RightHandPinky1"): dp.v2 = "Bip001 R Finger4" + (matchpattern n pattern:"*RightHandPinky2"): dp.v2 = "Bip001 R Finger41" + (matchpattern n pattern:"*RightHandPinky3"): dp.v2 = "Bip001 R Finger42" + (matchpattern n pattern:"*RightHandPinky4"): dp.v2 = "Bip001 R Finger4Nub" + (matchpattern n pattern:"*LeftUpLeg"): dp.v2 = "Bip001 L Thigh" + (matchpattern n pattern:"*LeftLeg"): dp.v2 = "Bip001 L Calf" + (matchpattern n pattern:"*LeftFoot"): dp.v2 = "Bip001 L Foot" + (matchpattern n pattern:"*LeftToeBase"): dp.v2 = "Bip001 L Toe0" + (matchpattern n pattern:"*RightUpLeg"): dp.v2 = "Bip001 R Thigh" + (matchpattern n pattern:"*RightLeg"): dp.v2 = "Bip001 R Calf" + (matchpattern n pattern:"*RightFoot"): dp.v2 = "Bip001 R Foot" + (matchpattern n pattern:"*RightToeBase"): dp.v2 = "Bip001 R Toe0" + default:(foundPairs = false ) + ) + + indexInExtra = findItem extraV1 n + if not foundPairs and indexInExtra != 0 then( + dp.v2 = extra[indexInExtra].v2 + ) + else if not foundPairs do append noPairs n + append out dp + ) + out +) + +-- ================================== 모퍼 처리 함수들 =============================================== + +struct MorpherChannelData ( + channelIndex, + targetName, + weight, + channelName, + isActive +) + +struct MorpherBackupData ( + objectName, + modifierName, + channels +) + +fn BackupMorpherData obj =( + /* + 모퍼 모디파이어 데이터를 백업하는 함수 + + 매개변수: + - obj: 모퍼 모디파이어를 가진 오브젝트 + + 반환값: + - MorpherBackupData 구조체 + */ + local morphMod = obj.modifiers[#Morpher] + if morphMod == undefined do return undefined + + local channels = #() + local channelCount = try (morphMod.numChannels) catch (100) -- 기본값 100개 채널 + + format "📦 모퍼 데이터 백업: % (채널 수: %)\\n" obj.name channelCount + + for i = 1 to channelCount do ( + try ( + -- 채널 프로퍼티에 직접 접근 + local channelProp = execute ("morphMod.morph_channel_" + i as string) + if channelProp != undefined then ( + local target = try (channelProp.target) catch (undefined) + local weight = try (channelProp.value) catch (0.0) + local channelName = try (channelProp.name) catch ("") + local isActive = try (channelProp.active) catch (false) + + if target != undefined and isValidNode target do ( + local channelData = MorpherChannelData \ + channelIndex:i \ + targetName:target.name \ + weight:weight \ + channelName:channelName \ + isActive:isActive + append channels channelData + format " 채널 %: % (가중치: %, 타겟: %)\\n" i channelName weight target.name + ) + ) + ) catch ( + -- 채널이 비어있거나 접근할 수 없는 경우 건너뛰기 + format " 채널 % 건너뛰기 (빈 채널)\\n" i + ) + ) + + MorpherBackupData objectName:obj.name modifierName:morphMod.name channels:channels +) + +fn RestoreMorpherData backupData pairsList =( + /* + 백업된 모퍼 데이터를 복원하는 함수 + + 매개변수: + - backupData: 백업된 모퍼 데이터 + - pairsList: 본 이름 매핑 정보 + + 반환값: + - 성공 시 true, 실패 시 false + */ + if backupData == undefined do return false + + local obj = getNodeByName backupData.objectName + if not isValidNode obj do ( + format "[ERROR] 모퍼 오브젝트를 찾을 수 없음: %\\n" backupData.objectName + return false + ) + + local morphMod = obj.modifiers[#Morpher] + if morphMod == undefined do ( + format "[ERROR] 모퍼 모디파이어를 찾을 수 없음: %\\n" obj.name + return false + ) + + format "🔄 모퍼 데이터 복원: % (채널 수: %)\\n" obj.name backupData.channels.count + + for channelData in backupData.channels do ( + -- 원본 타겟 이름을 새 이름으로 매핑 + local newTargetName = channelData.targetName + for pair in pairsList do ( + if pair.v1 == channelData.targetName do ( + newTargetName = pair.v2 + exit() + ) + ) + + local newTarget = getNodeByName newTargetName + if isValidNode newTarget then ( + try ( + -- 채널 프로퍼티에 직접 접근하여 설정 + local channelProp = execute ("morphMod.morph_channel_" + channelData.channelIndex as string) + if channelProp != undefined then ( + -- 타겟 설정 + try (channelProp.target = newTarget) catch (format " [WARNING] 타겟 설정 실패: 채널 %\\n" channelData.channelIndex) + -- 가중치 복원 + try (channelProp.value = channelData.weight) catch (format " [WARNING] 가중치 설정 실패: 채널 %\\n" channelData.channelIndex) + -- 채널 활성화 상태 복원 + try (channelProp.active = channelData.isActive) catch (format " [WARNING] 활성화 상태 설정 실패: 채널 %\\n" channelData.channelIndex) + -- 채널 이름 복원 + try (channelProp.name = channelData.channelName) catch (format " [WARNING] 채널 이름 설정 실패: 채널 %\\n" channelData.channelIndex) + + format " ✅ 채널 % 복원: % → % (가중치: %)\\n" channelData.channelIndex channelData.targetName newTargetName channelData.weight + ) else ( + format " ❌ 채널 % 프로퍼티 접근 실패\\n" channelData.channelIndex + ) + ) catch ( + format " ⚠️ 채널 % 설정 실패: % → %\\n" channelData.channelIndex channelData.targetName newTargetName + ) + ) else ( + format " ❌ 타겟을 찾을 수 없음: % → %\\n" channelData.targetName newTargetName + ) + ) + + true +) + +fn GetSkinPairs obj extra:#() =( + local out=#() + noPairs = #() + local extraV1 = for i in extra collect i.v1 + local skn = obj.modifiers[#skin] + local count = skinOps.GetNumberBones skn + for i=1 to count do( + foundPairs = true + n = (skinOps.GetBoneName skn i 0) + dp = datapair n "" + case of( + (matchpattern n pattern:"*Hips"): dp.v2 = "Bip001 Pelvis" + (matchpattern n pattern:"*Spine"): dp.v2 = "Bip001 Spine" + (matchpattern n pattern:"*Spine1"): dp.v2 = "Bip001 Spine1" + + (matchpattern n pattern:"*Neck"): dp.v2 = "Bip001 Neck" + (matchpattern n pattern:"*Head"): dp.v2 = "Bip001 Head" + (matchpattern n pattern:"*LeftShoulder"): dp.v2 = "Bip001 L Clavicle" + (matchpattern n pattern:"*LeftArm"): dp.v2 = "Bip001 L UpperArm" + (matchpattern n pattern:"*LeftForeArm"): dp.v2 = "Bip001 L Forearm" + (matchpattern n pattern:"*LeftHand"): dp.v2 = "Bip001 L Hand" + (matchpattern n pattern:"*LeftHandThumb1"): dp.v2 = "Bip001 L Finger0" + (matchpattern n pattern:"*LeftHandThumb2"): dp.v2 = "Bip001 L Finger01" + (matchpattern n pattern:"*LeftHandThumb3"): dp.v2 = "Bip001 L Finger02" + -- LeftHandThumb4 제거됨 + (matchpattern n pattern:"*LeftHandIndex1"): dp.v2 = "Bip001 L Finger1" + (matchpattern n pattern:"*LeftHandIndex2"): dp.v2 = "Bip001 L Finger11" + (matchpattern n pattern:"*LeftHandIndex3"): dp.v2 = "Bip001 L Finger12" + -- LeftHandIndex4 제거됨 + (matchpattern n pattern:"*LeftHandMiddle1"): dp.v2 = "Bip001 L Finger2" + (matchpattern n pattern:"*LeftHandMiddle2"): dp.v2 = "Bip001 L Finger21" + (matchpattern n pattern:"*LeftHandMiddle3"): dp.v2 = "Bip001 L Finger22" + -- LeftHandMiddle4 제거됨 + (matchpattern n pattern:"*LeftHandRing1"): dp.v2 = "Bip001 L Finger3" + (matchpattern n pattern:"*LeftHandRing2"): dp.v2 = "Bip001 L Finger31" + (matchpattern n pattern:"*LeftHandRing3"): dp.v2 = "Bip001 L Finger32" + -- LeftHandRing4 제거됨 + (matchpattern n pattern:"*LeftHandPinky1"): dp.v2 = "Bip001 L Finger4" + (matchpattern n pattern:"*LeftHandPinky2"): dp.v2 = "Bip001 L Finger41" + (matchpattern n pattern:"*LeftHandPinky3"): dp.v2 = "Bip001 L Finger42" + -- LeftHandPinky4 제거됨 + (matchpattern n pattern:"*RightShoulder"): dp.v2 = "Bip001 R Clavicle" + (matchpattern n pattern:"*RightArm"): dp.v2 = "Bip001 R UpperArm" + (matchpattern n pattern:"*RightForeArm"): dp.v2 = "Bip001 R Forearm" + (matchpattern n pattern:"*RightHand"): dp.v2 = "Bip001 R Hand" + (matchpattern n pattern:"*RightHandThumb1"): dp.v2 = "Bip001 R Finger0" + (matchpattern n pattern:"*RightHandThumb2"): dp.v2 = "Bip001 R Finger01" + (matchpattern n pattern:"*RightHandThumb3"): dp.v2 = "Bip001 R Finger02" + -- RightHandThumb4 제거됨 + (matchpattern n pattern:"*RightHandIndex1"): dp.v2 = "Bip001 R Finger1" + (matchpattern n pattern:"*RightHandIndex2"): dp.v2 = "Bip001 R Finger11" + (matchpattern n pattern:"*RightHandIndex3"): dp.v2 = "Bip001 R Finger12" + -- RightHandIndex4 제거됨 + (matchpattern n pattern:"*RightHandMiddle1"): dp.v2 = "Bip001 R Finger2" + (matchpattern n pattern:"*RightHandMiddle2"): dp.v2 = "Bip001 R Finger21" + (matchpattern n pattern:"*RightHandMiddle3"): dp.v2 = "Bip001 R Finger22" + -- RightHandMiddle4 제거됨 + (matchpattern n pattern:"*RightHandRing1"): dp.v2 = "Bip001 R Finger3" + (matchpattern n pattern:"*RightHandRing2"): dp.v2 = "Bip001 R Finger31" + (matchpattern n pattern:"*RightHandRing3"): dp.v2 = "Bip001 R Finger32" + -- RightHandRing4 제거됨 + (matchpattern n pattern:"*RightHandPinky1"): dp.v2 = "Bip001 R Finger4" + (matchpattern n pattern:"*RightHandPinky2"): dp.v2 = "Bip001 R Finger41" + (matchpattern n pattern:"*RightHandPinky3"): dp.v2 = "Bip001 R Finger42" + -- RightHandPinky4 제거됨 + (matchpattern n pattern:"*LeftUpLeg"): dp.v2 = "Bip001 L Thigh" + (matchpattern n pattern:"*LeftLeg"): dp.v2 = "Bip001 L Calf" + (matchpattern n pattern:"*LeftFoot"): dp.v2 = "Bip001 L Foot" + (matchpattern n pattern:"*LeftToeBase"): dp.v2 = "Bip001 L Toe0" + (matchpattern n pattern:"*RightUpLeg"): dp.v2 = "Bip001 R Thigh" + (matchpattern n pattern:"*RightLeg"): dp.v2 = "Bip001 R Calf" + (matchpattern n pattern:"*RightFoot"): dp.v2 = "Bip001 R Foot" + (matchpattern n pattern:"*RightToeBase"): dp.v2 = "Bip001 R Toe0" + default:(foundPairs = false ) + ) + + indexInExtra = findItem extraV1 n + if not foundPairs and indexInExtra != 0 then( + dp.v2 = extra[indexInExtra].v2 + ) + else foundPairs = false + + append out dp + if not foundPairs do append noPairs n + ) + out +) + +-- ================================== 메인 변환 함수 =============================================== + +fn ConvertMixamoToBiped charName:"Character001" alwaysDeform:false =( + /* + Mixamo T-Pose 아바타를 Biped으로 변환하는 메인 함수 + + 매개변수: + - charName: 캐릭터 이름 (기본값: "Character001") + - alwaysDeform: 스킨 Always Deform 설정 (기본값: false) + + 반환값: + - 성공 시 true, 실패 시 false + */ + + if charName=="" do( + messagebox "please insert Character Name" + return false + ) + + -- 이름 정리 및 검증 + CleanNames() + local root = getNodeByName"mixamorig:Hips" + if not isValidNode root do ( + format "[ERROR] mixamorig:Hips not found!\n" + return false + ) + + -- 리그 유효성 검증 + if not SanityCheck() do ( + format "[ERROR] Sanity check failed!\n" + return false + ) + + local hrc = getHierarchy #(root) #() + + -- 추가 본들 수집 및 메인에서 제외 + local extras = GetExtras root + for i in extras do( + for j in i do( + deleteItem hrc (findItem hrc j) + ) + ) + + -- 스킨 오브젝트들 수집 + local skinObjects = for i in objects where i.modifiers[#Skin]!=undefined collect i + if alwaysDeform do for i in skinObjects do ( + cleanUpSkin i + i.modifiers[#Skin].always_deform =false + i.modifiers[#Skin].always_deform =true + ) + + -- 모퍼 오브젝트들 수집 (간소화) + local morpherObjects = for i in objects where i.modifiers[#Morpher]!=undefined collect i + local morpherModifiers = #() -- 제거된 모퍼 모디파이어들을 저장 + + for obj in morpherObjects do ( + -- 모퍼 모디파이어를 임시로 제거하고 저장 + local morphMod = obj.modifiers[#Morpher] + append morpherModifiers (datapair obj morphMod) + deleteModifier obj morphMod + ) + + -- 믹사모 리그 구조 분석 + local mxNodes = getRigStructures() + local mx_Obj = $mixamorig* + local height = mx_Obj.max[3] + local numfingers = getNumFingers() + + -- 바이패드 생성 + format "=== BIPED 생성 디버그 ===\\n" + format "spineSegments: %\\n" spineSegments + format "height: %\\n" height + format "numfingers: %\\n" numfingers + + local bipObj = createBip height spine:spineSegments fingers:numfingers + + -- 생성 직후 구조 확인 + format "\\n=== 생성 직후 Biped 구조 ===\\n" + local pelvis = biped.getNode bipObj #pelvis link:1 + local spine1 = biped.getNode bipObj #spine link:1 + local spine2 = try (biped.getNode bipObj #spine link:2) catch (undefined) + local lthigh = biped.getNode bipObj #lleg link:1 + local lclavicle = biped.getNode bipObj #larm link:1 + local neck = biped.getNode bipObj #neck link:1 + + format "Pelvis: % (부모: %)\\n" pelvis.name (if pelvis.parent != undefined then pelvis.parent.name else "없음") + format "Spine1: % (부모: %)\\n" spine1.name (if spine1.parent != undefined then spine1.parent.name else "없음") + if spine2 != undefined then format "Spine2: % (부모: %)\\n" spine2.name (if spine2.parent != undefined then spine2.parent.name else "없음") + format "LThigh: % (부모: %)\\n" lthigh.name (if lthigh.parent != undefined then lthigh.parent.name else "없음") + format "LClavicle: % (부모: %)\\n" lclavicle.name (if lclavicle.parent != undefined then lclavicle.parent.name else "없음") + format "Neck: % (부모: %)\\n" neck.name (if neck.parent != undefined then neck.parent.name else "없음") + + -- 🔧 잘못된 구조 수정 + format "\\n=== Biped 구조 수정 중 ===\\n" + + -- Figure Mode 활성화 (구조 수정 가능) + bipObj.transform.controller.figureMode = true + + -- 다리 본들을 Pelvis로 이동 (현재 Spine에서 → Pelvis로) + local rthigh = biped.getNode bipObj #rleg link:1 + if lthigh.parent != pelvis then ( + format "다리를 Pelvis로 이동: % → %\\n" lthigh.parent.name pelvis.name + lthigh.parent = pelvis + rthigh.parent = pelvis + ) + + -- 어깨 본들을 최상위 Spine으로 이동 (현재 Neck에서 → Spine1로) + local rclavicle = biped.getNode bipObj #rarm link:1 + local topSpine = if spine2 != undefined then spine2 else spine1 -- 최상위 Spine + if lclavicle.parent != topSpine then ( + format "어깨를 최상위 Spine으로 이동: % → %\\n" lclavicle.parent.name topSpine.name + lclavicle.parent = topSpine + rclavicle.parent = topSpine + ) + + format "구조 수정 완료!\\n" + + -- 구조 수정 후 확인 + format "\\n=== 구조 수정 후 확인 ===\\n" + format "LThigh: % (부모: %)\\n" lthigh.name (if lthigh.parent != undefined then lthigh.parent.name else "없음") + format "RThigh: % (부모: %)\\n" rthigh.name (if rthigh.parent != undefined then rthigh.parent.name else "없음") + format "LClavicle: % (부모: %)\\n" lclavicle.name (if lclavicle.parent != undefined then lclavicle.parent.name else "없음") + format "RClavicle: % (부모: %)\\n" rclavicle.name (if rclavicle.parent != undefined then rclavicle.parent.name else "없음") + + local bipedNodes = GetBipStructure bipObj spinesegs:spineSegments + + -- 비율 매칭 + BipToRigProportion bipedNodes mxNodes TPose:true + + -- 비율 매칭 후 구조 재확인 + format "\\n=== 비율 매칭 후 Biped 구조 ===\\n" + local pelvis_after = biped.getNode bipObj #pelvis link:1 + local spine1_after = biped.getNode bipObj #spine link:1 + local lthigh_after = biped.getNode bipObj #lleg link:1 + local lclavicle_after = biped.getNode bipObj #larm link:1 + local neck_after = biped.getNode bipObj #neck link:1 + + format "Pelvis: % (부모: %)\\n" pelvis_after.name (if pelvis_after.parent != undefined then pelvis_after.parent.name else "없음") + format "Spine1: % (부모: %)\\n" spine1_after.name (if spine1_after.parent != undefined then spine1_after.parent.name else "없음") + format "LThigh: % (부모: %)\\n" lthigh_after.name (if lthigh_after.parent != undefined then lthigh_after.parent.name else "없음") + format "LClavicle: % (부모: %)\\n" lclavicle_after.name (if lclavicle_after.parent != undefined then lclavicle_after.parent.name else "없음") + format "Neck: % (부모: %)\\n" neck_after.name (if neck_after.parent != undefined then neck_after.parent.name else "없음") + + -- 추가 본들 생성 (선택사항) + local extrasPairs = #() + + -- ========== 원본 엑스트라 본 유지 방식 ================ + local totalExtraCount = 0 + for ex in extras do totalExtraCount += ex.count + format "전체 Extra Bones 개수: %개 (원본 본 그대로 유지)\\n" totalExtraCount + + bipObj.transform.controller.figureMode = true + local idx = 1 + for ex in extras do( + for i=1 to ex.count do ( + local originalBone = ex[i] + if not isValidNode originalBone do continue + + -- 원본 본을 그대로 사용하되, 정리된 이름으로 매핑 + local cleanName = substituteString originalBone.name "mixamorig:" "" + append extrasPairs (datapair originalBone.name cleanName) + append MXPairs (datapair originalBone.name cleanName) + + format " [%] % → 원본 유지" idx originalBone.name + + -- 첫 번째 본의 경우 적절한 Biped 부모에 연결 + if i==1 then ( + local par = originalBone.parent + for pair in MXPairs where isValidNode par and par.name==pair.v1 do ( + local bipPar = getNodebyName pair.v2 + if isValidNode bipPar do ( + -- 원본 본을 Biped 부모에 연결 + try ( + originalBone.parent = bipPar + format " (부모: %)\\n" bipPar.name + ) catch ( + format " (부모 연결 실패: %)\\n" bipPar.name + ) + exit() + ) + ) + if originalBone.parent == par do format " (부모 연결 안됨)\\n" + ) else ( + format "\\n" + ) + idx += 1 + ) + ) + bipObj.transform.controller.figureMode = false + + format "\\n=== 원본 엑스트라 본 유지 완료 ===\\n" + format "- 각도/위치 완벽 보존\\n" + format "- 애니메이션 데이터 손실 없음\\n" + format "- 스키닝 품질 유지\\n" + + -- Extra Bones 생성 후 최종 구조 확인 + format "\\n=== Extra Bones 생성 후 최종 구조 ===\\n" + local pelvis_final = biped.getNode bipObj #pelvis link:1 + local spine1_final = biped.getNode bipObj #spine link:1 + local lthigh_final = biped.getNode bipObj #lleg link:1 + local lclavicle_final = biped.getNode bipObj #larm link:1 + local neck_final = biped.getNode bipObj #neck link:1 + + format "Pelvis: % (부모: %)\\n" pelvis_final.name (if pelvis_final.parent != undefined then pelvis_final.parent.name else "없음") + format "Spine1: % (부모: %)\\n" spine1_final.name (if spine1_final.parent != undefined then spine1_final.parent.name else "없음") + format "LThigh: % (부모: %)\\n" lthigh_final.name (if lthigh_final.parent != undefined then lthigh_final.parent.name else "없음") + format "LClavicle: % (부모: %)\\n" lclavicle_final.name (if lclavicle_final.parent != undefined then lclavicle_final.parent.name else "없음") + format "Neck: % (부모: %)\\n" neck_final.name (if neck_final.parent != undefined then neck_final.parent.name else "없음") + + -- 하이브리드 방식: 엑스트라 본 처리 결과 + if extrasPairs.count > 0 then ( + format "\\n📊 Extra Bones 처리 결과 요약:\\n" + format "✅ 총 %개 엑스트라 본 처리 완료\\n" extrasPairs.count + format "✅ 모든 엑스트라 본이 원본 위치/각도 유지\\n" + format "✅ 위치 변화: 0 (원본 본 그대로 사용)\\n" + format "✅ 각도 변화: 0 (원본 각도 그대로 사용)\\n" + format "✅ 애니메이션 데이터: 완벽 보존\\n" + format "✅ 스키닝 품질: 원본 유지\\n" + format "✅ 표준 본: Biped로 대체됨\\n" + format "✅ 엑스트라 본: 원본 유지됨\\n" + ) + + format "================================\\n" + + -- 스킨 팝업 자동 처리 함수 (강화 버전) + fn ShowPopup =( + local hwnd = (DialogMonitorOPS.GetWindowHandle() ) + if hwnd!=0 then( + local windowTitle = uiAccessor.getWindowText hwnd + local children = uiAccessor.getChildWindows hwnd + + -- 다이얼로그 타입별 처리 + case of ( + -- Load Envelopes 다이얼로그 + (findString windowTitle "Load Envelopes" != undefined): ( + format "Load Envelopes 다이얼로그 감지 - 자동 OK 처리\\n" + for c in children do( + local buttonText = uiAccessor.getWindowText c + if buttonText == "OK" do ( + UIAccessor.PressButton c + format "OK 버튼 자동 클릭 완료\\n" + return true + ) + ) + ) + + -- Match by Name 다이얼로그 + (findString windowTitle "Match by Name" != undefined): ( + format "Match by Name 다이얼로그 감지 - 자동 처리\\n" + for c in children do( + if (uiAccessor.getWindowText c) =="Match by Name" do UIAccessor.PressButton c + ) + ) + + -- 기타 다이얼로그들 + default: ( + -- 일반적인 OK 버튼 찾기 + for c in children do( + local buttonText = uiAccessor.getWindowText c + if buttonText == "OK" do ( + format "일반 OK 버튼 감지 - 자동 클릭: %\\n" windowTitle + UIAccessor.PressButton c + return true + ) + ) + ) + ) + + -- 기본 버튼 처리 + uiAccessor.pressDefaultButton() + true + ) + else false + ) + + DialogMonitorOPS.Enabled = true + DialogMonitorOPS.Interactive =false + DialogMonitorOPS.RegisterNotification ShowPopup id:#MixamoToBiped + + local pth = GetDir #export + -- 하이브리드 방식: 표준 본은 Biped로, 엑스트라 본은 원본으로 + format "\\n=== 스킨 오브젝트 처리 (Biped + 엑스트라 본) ===\\n" + + for i in skinObjects do( + format "처리 중: %\\n" i.name + local skn = i.modifiers["skin"] + local n = i.name+"_skin.env" + local f = (pth+"\\"+n) + + -- 기존 스킨 데이터 저장 + if skn != undefined then ( + skinOps.saveEnvelope skn f + format "- 기존 스킨 데이터 저장: %\\n" f + ) + + -- 페어 가져오기 (표준 본 + 엑스트라 본) + local pairs = GetSkinPairs i extra:extrasPairs + format "- 매핑된 본 쌍: %개\\n" pairs.count + + local bipedSkin = #() + for j in pairs do ( + local obj1 = getNodeByName j.v1 + local obj2 = getNodeByName j.v2 + if isvalidNode obj1 and isValidNode obj2 do( + tempName = obj1.name + obj1.name = obj2.name + obj2.name = tempName + append bipedSkin obj2 + ) + ) + + -- 새 스킨 추가 + local skn2 = skin() + addmodifier i skn2 + select i + max modify mode + modPanel.setCurrentObject skn2 + + -- Biped 본들과 엑스트라 본들 추가 + for j in bipedSkin do( + if j!= bipedSkin[bipedSkin.count] then skinOps.AddBone skn2 j 0 + else skinOps.AddBone skn2 j -1 + ) + max create mode + + -- 스킨 데이터 로드 (저장된 것이 있는 경우) + if doesFileExist f then ( + skinOps.loadEnvelope skn2 f + format "- 스킨 데이터 로드 완료\\n" + ) + maxOps.CollapseNodeTo i 2 true + + -- 이름 원래대로 되돌리기 + for j in pairs do ( + obj1 = getNodeByName j.v1 + obj2 = getNodeByName j.v2 + if isvalidNode obj1 and isValidNode obj2 do( + tempName = obj1.name + obj1.name = obj2.name + obj2.name = tempName + ) + ) + + -- 임시 파일 삭제 + if doesFileExist f do deleteFile f + + format "- % 처리 완료\\n" i.name + ) + + format "\\n✅ 모든 스킨 오브젝트 처리 완료 (Biped + 엑스트라)\\n" + + -- 대화상자 모니터 비활성화 + DialogMonitorOPS.Enabled = false + DialogMonitorOPS.UnRegisterNotification id:#MixamoToBiped + + -- 모퍼 모디파이어 복원 (간소화) + if morpherModifiers.count > 0 do ( + format "\\n🎭 모퍼 모디파이어 복원 시작...\\n" + + -- 모퍼 모디파이어를 다시 추가 + for modPair in morpherModifiers do ( + local obj = modPair.v1 + local morphMod = modPair.v2 + addModifier obj morphMod + ) + + format "✅ 모퍼 모디파이어 복원 완료\\n" + ) + + -- 하이브리드 방식: 표준 본은 삭제, 엑스트라 본은 유지 + format "\\n=== 믹사모 표준 본 삭제 ===\\n" + + -- 표준 본들만 선별해서 삭제 (엑스트라 본 제외) + local standardBones = #() + local allMixamoBones = getHierarchy #(root) #() + + for bone in allMixamoBones do ( + if isValidNode bone and findItem MXNames bone.name != 0 then ( + appendIfUnique standardBones bone + format "- 삭제 예정: %\\n" bone.name + ) + ) + + -- 표준 본들 삭제 + if standardBones.count > 0 then ( + delete standardBones + format "✅ %개 표준 본 삭제 완료\\n" standardBones.count + ) + + -- 엑스트라 본 이름 정리 (mixamorig: 제거) + format "\\n=== 엑스트라 본 이름 정리 ===\\n" + for i in extrasPairs do( + local theObj = getNodeByName i.v1 + if isValidNode theObj then ( + theObj.name = i.v2 -- 이미 정리된 이름으로 변경 + format "- % → %\\n" i.v1 i.v2 + ) + ) + + -- 레이어 설정 + local rootLayer = LayerManager.newLayerFromName "CHARACTERS" + if rootLayer == undefined do rootLayer = LayerManager.getLayerFromName "CHARACTERS" + + local charLayer = LayerManager.newLayerFromName (toUpper charName) + if charLayer == undefined do charLayer = LayerManager.getLayerFromName charName + charLayer.setParent rootLayer + + + local bipLayer = LayerManager.newLayerFromName (charName+"_BIP") + if bipLayer == undefined do bipLayer = LayerManager.getLayerFromName (charName+"_BIP") + bipLayer.setParent charLayer + + local GeoLayer = LayerManager.newLayerFromName (charName+"_GEO") + if GeoLayer == undefined do GeoLayer = LayerManager.getLayerFromName (charName+"_GEO") + GeoLayer.setParent charLayer + GeoLayer.isfrozen =true + + -- 오브젝트 관리 + for i in skinObjects do ( + i.showFrozenInGray = false + local n = i.name + if not matchPattern n pattern:(charName+"*") do i.name = charName+"_"+i.name + if i.material!=undefined do ( + mtlName = i.material.name + if not matchPattern mtlName pattern:(charName+"*") do i.material.name = charName+"_"+mtlName + ) + GeoLayer.addnode i + ) + + local allBip = getHierarchy bipObj #() + for i in allBip do( + bipLayer.addNode i + i.name = charName+"_"+i.name + i.renderable =false + i.boxMode = true + ) + + -- 성공 반환 + format "[SUCCESS] Mixamo to Biped conversion completed for: %\n" charName + redrawViews() + true +) + +-- ================================== 자동 실행 =============================================== + +-- 스크립트 로딩 완료 후 바로 변환 시작 +format "\n✅ MixamoToBiped Converter 로딩 완료!\n" +format "자동으로 변환을 시작합니다...\n\n" + +try ( + format "=== Mixamo to Biped 자동 변환기 ===\n" + + -- Mixamo 오브젝트 확인 + local mixamoObjects = $mixamorig* as array + if mixamoObjects.count > 0 then ( + format "Mixamo 오브젝트 발견: %개\n" mixamoObjects.count + + -- 캐릭터 이름 자동 생성 (랜덤 번호 기반) + local randomNum = random 10000 99999 + local charName = "Character" + (randomNum as string) + + format "기본 이름 사용: %\n" charName + format "변환 시작: %\n" charName + format "=================================\n" + + -- 변환 실행 + try ( + local result = ConvertMixamoToBiped charName:charName + + if result then ( + format "\n🎉 변환 완료! 🎉\n" + format "캐릭터: %\n" charName + format "✅ Biped 로컬 축: 표준 유지 (축 뒤틀림 없음)\n" + format "✅ T-Pose 자세: 기존 알고리즘으로 매칭\n" + format "✅ 애니메이션 호환성: 완벽\n" + format "레이어: CHARACTERS > %\n" (toUpper charName) + redrawViews() + ) else ( + format "\n❌ 변환 실패!\n" + format "Mixamo 리그 구조를 확인해주세요.\n" + ) + ) catch ( + format "\n❌ 변환 중 오류 발생!\n" + format "오류 내용: %\n" (getCurrentException()) + ) + ) else ( + format "\n⚠️ Mixamo 오브젝트를 찾을 수 없습니다!\n" + format "1. Mixamo T-Pose FBX 파일을 먼저 임포트해주세요.\n" + format "2. 임포트 후 이 스크립트를 다시 실행하세요.\n" + format "\n사용법:\n" + format "1. File > Import > Mixamo T-Pose FBX 선택\n" + format "2. 스크립트 실행 (자동으로 변환 시작)\n" + format "3. 자동 변환 완료! ✨\n" + ) +) catch ( + format "❌ 스크립트 실행 중 심각한 오류 발생!\n" + format "오류 내용: %\n" (getCurrentException()) +) \ No newline at end of file diff --git a/3ds max script/MixamoToBiped_Converter.ms b/3ds max script/MixamoToBiped_Converter.ms new file mode 100644 index 00000000..8ce647a9 --- /dev/null +++ b/3ds max script/MixamoToBiped_Converter.ms @@ -0,0 +1,1759 @@ +/* + MixamoToBiped Converter - No Axis Twist Version + Extracted from TOOLS_MixamoToBiped_v1.0.51.ms + + 이 스크립트는 Mixamo T-Pose 아바타를 3ds Max Biped으로 변환합니다. + + 🎯 특징 (축 변환 제거 버전): + - Biped 로컬 축 정의 유지 (MapTMAxis 호출 제거) + - 기존 T-Pose 자세 매칭 알고리즘 유지 + - IK 정렬과 기하학적 계산 보존 + - 크기와 위치 조정은 정상 동작 + - 스킨 가중치 완벽 이전 + + 사용법: + 1. Mixamo T-Pose FBX 파일을 임포트 + 2. 스크립트 실행 (자동으로 변환 시작) + + Author: Based on Ishak Suryo Laksono's work + Modified: Removed MapTMAxis calls to preserve Biped local axes +*/ + +-- ================================== 핵심 데이터 (원본 그대로) =============================================== + +-- 믹사모 본 이름들 (순서 중요 - 절대 변경 금지) +global MXNames =#("mixamorig:Hips","mixamorig:LeftUpLeg","mixamorig:LeftLeg","mixamorig:LeftFoot","mixamorig:LeftToeBase","mixamorig:RightUpLeg","mixamorig:RightLeg", + "mixamorig:RightFoot","mixamorig:RightToeBase","mixamorig:Spine","mixamorig:Spine1","mixamorig:LeftShoulder","mixamorig:LeftArm","mixamorig:LeftForeArm", + "mixamorig:LeftHand","mixamorig:LeftHandIndex1","mixamorig:LeftHandIndex2","mixamorig:LeftHandIndex3","mixamorig:LeftHandMiddle1","mixamorig:LeftHandMiddle2", + "mixamorig:LeftHandMiddle3","mixamorig:LeftHandPinky1","mixamorig:LeftHandPinky2","mixamorig:LeftHandPinky3","mixamorig:LeftHandRing1", + "mixamorig:LeftHandRing2","mixamorig:LeftHandRing3","mixamorig:LeftHandThumb1","mixamorig:LeftHandThumb2","mixamorig:LeftHandThumb3", + "mixamorig:Neck","mixamorig:Head","mixamorig:RightShoulder","mixamorig:RightArm","mixamorig:RightForeArm","mixamorig:RightHand", + "mixamorig:RightHandIndex1","mixamorig:RightHandIndex2","mixamorig:RightHandIndex3","mixamorig:RightHandMiddle1","mixamorig:RightHandMiddle2","mixamorig:RightHandMiddle3", + "mixamorig:RightHandPinky1","mixamorig:RightHandPinky2","mixamorig:RightHandPinky3","mixamorig:RightHandRing1","mixamorig:RightHandRing2", + "mixamorig:RightHandRing3","mixamorig:RightHandThumb1","mixamorig:RightHandThumb2","mixamorig:RightHandThumb3") + +-- 믹사모 -> 바이패드 매핑 (순서 중요 - 절대 변경 금지) +global MXPairs = #(datapair "mixamorig:Hips" "Bip001 Pelvis",datapair "mixamorig:Spine" "Bip001 Spine",datapair "mixamorig:Spine1" "Bip001 Spine1",datapair "mixamorig:Neck" "Bip001 Neck",datapair "mixamorig:Head" "Bip001 Head",datapair "mixamorig:RightShoulder" "Bip001 R Clavicle",datapair "mixamorig:RightArm" "Bip001 R UpperArm",datapair "mixamorig:RightForeArm" "Bip001 R Forearm",datapair "mixamorig:RightHand" "Bip001 R Hand",datapair "mixamorig:RightHandThumb1" "Bip001 R Finger0",datapair "mixamorig:RightHandThumb2" "Bip001 R Finger01",datapair "mixamorig:RightHandThumb3" "Bip001 R Finger02",datapair "mixamorig:RightHandIndex1" "Bip001 R Finger1",datapair "mixamorig:RightHandIndex2" "Bip001 R Finger11",datapair "mixamorig:RightHandIndex3" "Bip001 R Finger12",datapair "mixamorig:RightHandMiddle1" "Bip001 R Finger2",datapair "mixamorig:RightHandMiddle2" "Bip001 R Finger21",datapair "mixamorig:RightHandMiddle3" "Bip001 R Finger22",datapair "mixamorig:RightHandRing1" "Bip001 R Finger3",datapair "mixamorig:RightHandRing2" "Bip001 R Finger31",datapair "mixamorig:RightHandRing3" "Bip001 R Finger32",datapair "mixamorig:RightHandPinky1" "Bip001 R Finger4",datapair "mixamorig:RightHandPinky2" "Bip001 R Finger41",datapair "mixamorig:RightHandPinky3" "Bip001 R Finger42",datapair "mixamorig:LeftShoulder" "Bip001 L Clavicle",datapair "mixamorig:LeftArm" "Bip001 L UpperArm",datapair "mixamorig:LeftForeArm" "Bip001 L Forearm",datapair "mixamorig:LeftHand" "Bip001 L Hand",datapair "mixamorig:LeftHandThumb1" "Bip001 L Finger0",datapair "mixamorig:LeftHandThumb2" "Bip001 L Finger01",datapair "mixamorig:LeftHandThumb3" "Bip001 L Finger02",datapair "mixamorig:LeftHandIndex1" "Bip001 L Finger1",datapair "mixamorig:LeftHandIndex2" "Bip001 L Finger11",datapair "mixamorig:LeftHandIndex3" "Bip001 L Finger12",datapair "mixamorig:LeftHandMiddle1" "Bip001 L Finger2",datapair "mixamorig:LeftHandMiddle2" "Bip001 L Finger21",datapair "mixamorig:LeftHandMiddle3" "Bip001 L Finger22",datapair "mixamorig:LeftHandRing1" "Bip001 L Finger3",datapair "mixamorig:LeftHandRing2" "Bip001 L Finger31",datapair "mixamorig:LeftHandRing3" "Bip001 L Finger32",datapair "mixamorig:LeftHandPinky1" "Bip001 L Finger4",datapair "mixamorig:LeftHandPinky2" "Bip001 L Finger41",datapair "mixamorig:LeftHandPinky3" "Bip001 L Finger42",datapair "mixamorig:RightUpLeg" "Bip001 R Thigh",datapair "mixamorig:RightLeg" "Bip001 R Calf",datapair "mixamorig:RightFoot" "Bip001 R Foot",datapair "mixamorig:RightToeBase" "Bip001 R Toe0",datapair "mixamorig:LeftUpLeg" "Bip001 L Thigh",datapair "mixamorig:LeftLeg" "Bip001 L Calf",datapair "mixamorig:LeftFoot" "Bip001 L Foot",datapair "mixamorig:LeftToeBase" "Bip001 L Toe0") + +-- 제외할 본들 (원본 방식) +-- global excludeBones =#("mixamorig:HeadTop_End","mixamorig:RightHandThumb4","mixamorig:RightHandIndex4","mixamorig:RightHandMiddle4","mixamorig:RightHandRing4", +-- "mixamorig:RightHandPinky4","mixamorig:LeftHandThumb4","mixamorig:LeftHandIndex4","mixamorig:LeftHandMiddle4","mixamorig:LeftHandRing4","mixamorig:LeftHandPinky4", +-- "mixamorig:RightToe_End","mixamorig:LeftToe_End") +global excludeBones =#("mixamorig:HeadTop_End") + +-- 스파인 세그먼트 개수 +global spineSegments = 2 + +-- 특수 스켈레톤 구조 감지 변수들 +global legParentIsSpine = false +global shoulderParentIsNeck = false +global shoulderParentIsSpine = false + + + +-- 발 각도 설정 +global footDegree = 50 +global flipThreshold = 178 + +-- ================================== 유틸리티 함수들 (원본 그대로) =============================================== + +fn pointLineProj pA pB pC = ( + local vAB=pB-pA + local vAC=pC-pA + local d=dot (normalize vAB) (normalize vAC) + (pA+(vAB*(d*(length vAC/length vAB)))) +) + +fn findFootAngle =( + local foots = $'mixamorig:*Foot' as array + if foots.count==0 do return 35 + + local posArr = for i in foots collect i.pos[3] + local idx = findItem posArr (amin(posArr)) + local foot = foots[idx] + local toe = foot.children[1] + if not isvalidnode toe do return 35 + + local p1 = foot.pos + local p2 = toe.pos + local v1 = p1-p2 + p2[3] = p1[3] + local v2 = p1-p2 + acos(dot(normalize v1) (normalize v2)) +) + +fn getHierarchy objArr out= +( + for i in objArr do + ( + appendifunique out i + child = i.children + if child.count>0 do + ( + getHierarchy child out + ) + ) + out +) + +fn getSkinnedObj = ( + skn = getClassInstances skin + objArr = #() + for i in skn do( + for obj in refs.dependentNodes i do appendIfunique objArr obj + ) + objArr +) + +-- 위치 기반으로 실제 본 체인인지 확인하는 함수 +fn isRealBoneChain parentBone childBone tolerance:0.1 =( + if not isValidNode parentBone or not isValidNode childBone then return false + + -- 부모 본의 끝점 계산 (자식이 있으면 자식 방향, 없으면 부모 방향의 반대) + local parentEnd = parentBone.pos + if parentBone.children.count > 0 then ( + -- 가장 가까운 자식 본 방향으로 본 길이 계산 + local closestChild = parentBone.children[1] + local minDist = distance parentBone.pos closestChild.pos + for child in parentBone.children do ( + local dist = distance parentBone.pos child.pos + if dist < minDist do ( + minDist = dist + closestChild = child + ) + ) + parentEnd = closestChild.pos + ) else if parentBone.parent != undefined then ( + -- 부모가 있으면 부모->현재 방향으로 연장 + local direction = normalize (parentBone.pos - parentBone.parent.pos) + local boneLength = distance parentBone.parent.pos parentBone.pos + parentEnd = parentBone.pos + direction * boneLength + ) + + -- 자식 본의 시작점과 부모 본의 끝점 거리 확인 + local dist = distance parentEnd childBone.pos + return dist <= tolerance +) + +fn ScanBranchHRC obj out:#() = ( + local child = obj.children + if child.count > 0 then ( + -- 실제 본 체인으로 연결된 자식 찾기 (위치 기반) + local chainChild = undefined + for c in child do ( + if isRealBoneChain obj c then ( + chainChild = c + exit -- 첫 번째로 체인 연결된 자식만 사용 + ) + ) + + if chainChild != undefined then ( + -- 체인으로 연결된 자식이 있으면 계속 추적 + append out chainChild + ScanBranchHRC chainChild out:out + ) else ( + -- 체인이 끊어지면 undefined 추가 + append out undefined + ) + + -- 체인에 포함되지 않은 나머지 자식들은 별도 처리 + for i in child where i != chainChild do ( + append out i + ScanBranchHRC i out:out + ) + ) else ( + append out undefined + ) + out +) + +fn getBranchHRC obj =( + local scan = ScanBranchHRC obj + local out = #() + local new = undefined + for i in scan do ( + if new == undefined do new = #() + if isvalidNode i then ( + append new i + ) else ( + if new != undefined and new.count > 0 do append out new + new = undefined + ) + ) + -- 마지막 그룹 추가 + if new != undefined and new.count > 0 do append out new + out +) + +fn collectLayerNodes ly out:#()=( + ly.nodes &nd + join out nd + local numchildLy =ly.getNumChildren() + if numchildLy>0 do( + for i=1 to numchildLy do( + local newLy = ly.getChild i + collectLayerNodes newLy out:out + ) + ) + out +) + +fn CleanNames =( + local objArr = $mixamorig* + --Rules 1 + for i in objArr do( + n = i.name + filt = filterstring n ":" + if filt.Count>1 do( + newName = "mixamorig:" + for j=2 to filt.Count do ( + newName += filt[j] + if j!=filt.Count do newName+=":" + ) + i.name = newName + ) + ) +) + +fn SanityCheck = ( + hips = $'*:Hips*' + if hips.count>0 then( + hrc = getHierarchy hips #() + for obj in hrc do( + case of ( + (matchpattern obj.name pattern:"*Hips"): obj.name = "mixamorig:Hips" + (matchpattern obj.name pattern:"*Spine"): obj.name = "mixamorig:Spine" + (matchpattern obj.name pattern:"*Spine1"): obj.name = "mixamorig:Spine1" + + (matchpattern obj.name pattern:"*Neck"): obj.name = "mixamorig:Neck" + (matchpattern obj.name pattern:"*Head"): obj.name = "mixamorig:Head" + + (matchpattern obj.name pattern:"*LeftShoulder"): obj.name = "mixamorig:LeftShoulder" + (matchpattern obj.name pattern:"*LeftArm"): obj.name = "mixamorig:LeftArm" + (matchpattern obj.name pattern:"*LeftForeArm"): obj.name = "mixamorig:LeftForeArm" + (matchpattern obj.name pattern:"*LeftHand"): obj.name = "mixamorig:LeftHand" + (matchpattern obj.name pattern:"*LeftHandThumb1"): obj.name = "mixamorig:LeftHandThumb1" + (matchpattern obj.name pattern:"*LeftHandThumb2"): obj.name = "mixamorig:LeftHandThumb2" + (matchpattern obj.name pattern:"*LeftHandThumb3"): obj.name = "mixamorig:LeftHandThumb3" + -- LeftHandThumb4 제거됨 + (matchpattern obj.name pattern:"*LeftHandIndex1"): obj.name = "mixamorig:LeftHandIndex1" + (matchpattern obj.name pattern:"*LeftHandIndex2"): obj.name = "mixamorig:LeftHandIndex2" + (matchpattern obj.name pattern:"*LeftHandIndex3"): obj.name = "mixamorig:LeftHandIndex3" + -- LeftHandIndex4 제거됨 + (matchpattern obj.name pattern:"*LeftHandMiddle1"): obj.name = "mixamorig:LeftHandMiddle1" + (matchpattern obj.name pattern:"*LeftHandMiddle2"): obj.name = "mixamorig:LeftHandMiddle2" + (matchpattern obj.name pattern:"*LeftHandMiddle3"): obj.name = "mixamorig:LeftHandMiddle3" + -- LeftHandMiddle4 제거됨 + (matchpattern obj.name pattern:"*LeftHandRing1"): obj.name = "mixamorig:LeftHandRing1" + (matchpattern obj.name pattern:"*LeftHandRing2"): obj.name = "mixamorig:LeftHandRing2" + (matchpattern obj.name pattern:"*LeftHandRing3"): obj.name = "mixamorig:LeftHandRing3" + -- LeftHandRing4 제거됨 + (matchpattern obj.name pattern:"*LeftHandPinky1"): obj.name = "mixamorig:LeftHandPinky1" + (matchpattern obj.name pattern:"*LeftHandPinky2"): obj.name = "mixamorig:LeftHandPinky2" + (matchpattern obj.name pattern:"*LeftHandPinky3"): obj.name = "mixamorig:LeftHandPinky3" + -- LeftHandPinky4 제거됨 + (matchpattern obj.name pattern:"*RightShoulder"): obj.name = "mixamorig:RightShoulder" + (matchpattern obj.name pattern:"*RightArm"): obj.name = "mixamorig:RightArm" + (matchpattern obj.name pattern:"*RightForeArm"): obj.name = "mixamorig:RightForeArm" + (matchpattern obj.name pattern:"*RightHand"): obj.name = "mixamorig:RightHand" + (matchpattern obj.name pattern:"*RightHandThumb1"): obj.name = "mixamorig:RightHandThumb1" + (matchpattern obj.name pattern:"*RightHandThumb2"): obj.name = "mixamorig:RightHandThumb2" + (matchpattern obj.name pattern:"*RightHandThumb3"): obj.name = "mixamorig:RightHandThumb3" + -- RightHandThumb4 제거됨 + (matchpattern obj.name pattern:"*RightHandIndex1"): obj.name = "mixamorig:RightHandIndex1" + (matchpattern obj.name pattern:"*RightHandIndex2"): obj.name = "mixamorig:RightHandIndex2" + (matchpattern obj.name pattern:"*RightHandIndex3"): obj.name = "mixamorig:RightHandIndex3" + -- RightHandIndex4 제거됨 + (matchpattern obj.name pattern:"*RightHandMiddle1"): obj.name = "mixamorig:RightHandMiddle1" + (matchpattern obj.name pattern:"*RightHandMiddle2"): obj.name = "mixamorig:RightHandMiddle2" + (matchpattern obj.name pattern:"*RightHandMiddle3"): obj.name = "mixamorig:RightHandMiddle3" + -- RightHandMiddle4 제거됨 + (matchpattern obj.name pattern:"*RightHandRing1"): obj.name = "mixamorig:RightHandRing1" + (matchpattern obj.name pattern:"*RightHandRing2"): obj.name = "mixamorig:RightHandRing2" + (matchpattern obj.name pattern:"*RightHandRing3"): obj.name = "mixamorig:RightHandRing3" + -- RightHandRing4 제거됨 + (matchpattern obj.name pattern:"*RightHandPinky1"): obj.name = "mixamorig:RightHandPinky1" + (matchpattern obj.name pattern:"*RightHandPinky2"): obj.name = "mixamorig:RightHandPinky2" + (matchpattern obj.name pattern:"*RightHandPinky3"): obj.name = "mixamorig:RightHandPinky3" + -- RightHandPinky4 제거됨 + (matchpattern obj.name pattern:"*LeftUpLeg"): obj.name = "mixamorig:LeftUpLeg" + (matchpattern obj.name pattern:"*LeftLeg"): obj.name = "mixamorig:LeftLeg" + (matchpattern obj.name pattern:"*LeftFoot"): obj.name = "mixamorig:LeftFoot" + (matchpattern obj.name pattern:"*LeftToeBase"): obj.name = "mixamorig:LeftToeBase" + -- LeftToe_End 제거됨 + (matchpattern obj.name pattern:"*RightUpLeg"): obj.name = "mixamorig:RightUpLeg" + (matchpattern obj.name pattern:"*RightLeg"): obj.name = "mixamorig:RightLeg" + (matchpattern obj.name pattern:"*RightFoot"): obj.name = "mixamorig:RightFoot" + (matchpattern obj.name pattern:"*RightToeBase"): obj.name = "mixamorig:RightToeBase" + -- RightToe_End 제거됨 + ) + ) + + local valid = true + allObjectNames = for i in objects collect i.name + for i in MXNames do ( + -- excludeBones에 있는 본들은 체크하지 않음 + if findItem excludeBones i == 0 then ( + if findItem allObjectNames i==0 do ( + valid = false + format "[ERROR] : $% not found!\n" i + ) + ) + ) + valid = true + return valid + ) + return false +) + +fn createBip height spine:2 fingers:5= ( + -- Triangle Pelvis는 False, Triangle Neck는 True로 설정 + local bipObj = biped.createNew height -90 [0,0,height*0.9562] arms:true neckLinks:1 \ + spineLinks:spine legLinks:3 \ + fingers:fingers fingerLinks:3 toes:1 \ + toeLinks:1 ankleAttach:0.3 trianglePelvis:False triangleNeck:True + + bipObj +) + +fn getRigStructures =( + local n = "mixamorig:" + + local root = execute ("$'"+n+"Hips'") + + -- 다리 본들 (부모 관계 확인) + local LLeg1 = execute ("$'"+n+"LeftUpLeg'") + local LLeg2 = execute ("$'"+n+"LeftLeg'") + local LFoot = execute ("$'"+n+"LeftFoot'") + local LToe = execute ("$'"+n+"LeftToeBase'") + + local RLeg1 = execute ("$'"+n+"RightUpLeg'") + local RLeg2 = execute ("$'"+n+"RightLeg'") + local RFoot = execute ("$'"+n+"RightFoot'") + local RToe = execute ("$'"+n+"RightToeBase'") + + -- 스파인 본들 + local LSpine = execute ("$'"+n+"Spine'") + local LSpine1 = execute ("$'"+n+"Spine1'") + local spines = #(LSpine, LSpine1) + spineSegments = 2 + + -- 다리가 Spine에서 시작하는 경우 처리 + -- 다리 본들의 부모가 Spine인지 확인 + legParentIsSpine = false + if LLeg1 != undefined and LLeg1.parent != undefined then ( + if findString LLeg1.parent.name "Spine" != undefined then ( + legParentIsSpine = true + format "다리가 Spine에서 시작됨을 감지\\n" + ) + ) + + + -- 어깨/팔 본들 + local LClav = execute ("$'"+n+"LeftShoulder'") + local LArm1 = execute ("$'"+n+"LeftArm'") + local LArm2 = execute ("$'"+n+"LeftForeArm'") + local LHand = execute ("$'"+n+"LeftHand'") + + local RClav = execute ("$'"+n+"RightShoulder'") + local RArm1 = execute ("$'"+n+"RightArm'") + local RArm2 = execute ("$'"+n+"RightForeArm'") + local RHand = execute ("$'"+n+"RightHand'") + + -- 🔍 팔 구조 디버그 출력 + format "\\n=== 팔 구조 디버그 ===\\n" + if RClav != undefined then ( + format "RightShoulder: %\\n" RClav.name + format " 자식들: " + for child in RClav.children do format "%, " child.name + format "\\n" + ) + if RArm1 != undefined then ( + format "RightArm: %\\n" RArm1.name + format " 자식들: " + for child in RArm1.children do format "%, " child.name + format "\\n" + ) + if RArm2 != undefined then ( + format "RightForeArm: %\\n" RArm2.name + format " 자식들: " + for child in RArm2.children do format "%, " child.name + format "\\n" + ) + format "========================\\n" + + local neck = execute ("$'"+n+"Neck'") + local head = execute ("$'"+n+"Head'") + + -- 어깨가 어디서 시작하는지 확인 + shoulderParentIsNeck = false + shoulderParentIsSpine = false + if LClav != undefined and LClav.parent != undefined then ( + if findString LClav.parent.name "Neck" != undefined then ( + shoulderParentIsNeck = true + format "어깨가 Neck에서 시작됨을 감지\\n" + ) else if findString LClav.parent.name "Spine" != undefined and + findString LClav.parent.name "Spine1" == undefined then ( + shoulderParentIsSpine = true + format "어깨가 Spine에서 바로 시작됨을 감지\\n" + ) + ) + + local LFinger1 = $'mixamorig:LeftHandThumb1' + local LFinger2 = $'mixamorig:LeftHandIndex1' + local LFinger3 = $'mixamorig:LeftHandMiddle1' + local LFinger4 = $'mixamorig:LeftHandRing1' + local LFinger5 = $'mixamorig:LeftHandPinky1' + + local RFinger1 = $'mixamorig:RightHandThumb1' + local RFinger2 = $'mixamorig:RightHandIndex1' + local RFinger3 = $'mixamorig:RightHandMiddle1' + local RFinger4 = $'mixamorig:RightHandRing1' + local RFinger5 = $'mixamorig:RightHandPinky1' + + -- 스켈레톤 구조에 따라 적응적으로 배치 + if (legParentIsSpine and shoulderParentIsNeck) or (shoulderParentIsSpine) then ( + -- 특수 스켈레톤 구조들 + if shoulderParentIsSpine then ( + format "특수 스켈레톤 구조 감지: 어깨->Spine\\n" + ) else ( + format "특수 스켈레톤 구조 감지: 다리->Spine, 어깨->Neck\\n" + ) + rigNodes = #(root,root) + join rigNodes #(LLeg1, LLeg2, LFoot, LToe, RLeg1, RLeg2, RFoot, RToe) -- 다리들 먼저 + join rigNodes Spines -- 스파인들 + join rigNodes #(neck, head) -- 목과 머리 + join rigNodes #(LClav, LArm1, LArm2, LHand, RClav, RArm1, RArm2, RHand) -- 어깨와 팔들 + join rigNodes #(LFinger1,LFinger2,LFinger3,LFinger4,LFinger5, RFinger1,RFinger2,RFinger3,RFinger4,RFinger5) + ) else ( + -- 표준 구조 + rigNodes = #(root,root) + join rigNodes Spines + join rigNodes #( LClav, + LArm1, + LArm2, + LHand, + RClav, + RArm1, + RArm2, + RHand, + neck, + head, + LLeg1, + LLeg2, + LFoot, + LToe, + RLeg1, + RLeg2, + RFoot, + RToe, + LFinger1,LFinger2,LFinger3,LFinger4,LFinger5, + RFinger1,RFinger2,RFinger3,RFinger4,RFinger5 + ) + ) + rigNodes +) + +fn GetBipStructure bip spinesegs:3= ( + local bip_pelvis = biped.getNode bip #pelvis link:1 + local bip_root = bip_pelvis.parent + + local bip_LLeg1 = biped.getNode bip #lleg link:1 + local bip_LLeg2 = biped.getNode bip #lleg link:2 + local bip_LFoot = biped.getNode bip #lleg link:3 + local bip_LToe = biped.getNode bip #ltoes link:1 + + local bip_RLeg1 = biped.getNode bip #rleg link:1 + local bip_RLeg2 = biped.getNode bip #rleg link:2 + local bip_RFoot = biped.getNode bip #rleg link:3 + local bip_RToe = biped.getNode bip #rtoes link:1 + + local bip_LClav = biped.getNode bip #larm link:1 + local bip_LArm1 = biped.getNode bip #larm link:2 + local bip_LArm2 = biped.getNode bip #larm link:3 + local bip_LHand = biped.getNode bip #larm link:4 + + local bip_RClav = biped.getNode bip #rarm link:1 + local bip_RArm1 = biped.getNode bip #rarm link:2 + local bip_RArm2 = biped.getNode bip #rarm link:3 + local bip_RHand = biped.getNode bip #rarm link:4 + + local bip_neck = biped.getNode bip #neck link:1 + local bip_head = biped.getNode bip #head link:1 + + local bip_Lfinger0 = biped.getNode bip #lfingers link:1 + local bip_Lfinger1 = biped.getNode bip #lfingers link:4 + local bip_Lfinger2 = biped.getNode bip #lfingers link:7 + local bip_Lfinger3 = biped.getNode bip #lfingers link:10 + local bip_Lfinger4 = biped.getNode bip #lfingers link:13 + + local bip_Rfinger0 = biped.getNode bip #rfingers link:1 + local bip_Rfinger1 = biped.getNode bip #rfingers link:4 + local bip_Rfinger2 = biped.getNode bip #rfingers link:7 + local bip_Rfinger3 = biped.getNode bip #rfingers link:10 + local bip_Rfinger4 = biped.getNode bip #rfingers link:13 + + -- 스켈레톤 구조에 따라 적응적으로 배치 (getRigStructures와 동일 순서) + if (legParentIsSpine and shoulderParentIsNeck) or (shoulderParentIsSpine) then ( + -- 특수 구조들 + format "Biped도 특수 구조로 배치\\n" + bipNodes = #(bip_root,bip_pelvis) + -- 다리들 먼저 + join bipNodes #(bip_LLeg1, bip_LLeg2, bip_LFoot, bip_LToe, bip_RLeg1, bip_RLeg2, bip_RFoot, bip_RToe) + -- 스파인들 + for i=1 to spinesegs do append bipNodes (biped.getNode bip #spine link:i) + -- 목과 머리 + join bipNodes #(bip_neck, bip_head) + -- 어깨와 팔들 + join bipNodes #(bip_LClav, bip_LArm1, bip_LArm2, bip_LHand, bip_RClav, bip_RArm1, bip_RArm2, bip_RHand) + -- 손가락들 + join bipNodes #(bip_Lfinger0,bip_Lfinger1,bip_Lfinger2,bip_Lfinger3,bip_Lfinger4, bip_Rfinger0,bip_Rfinger1,bip_Rfinger2,bip_Rfinger3,bip_Rfinger4) + ) else ( + -- 표준 구조 + bipNodes = #(bip_root,bip_pelvis) + --SPINES + for i=1 to spinesegs do append bipNodes (biped.getNode bip #spine link:i) + join bipNodes #(bip_LClav, + bip_LArm1, + bip_LArm2, + bip_LHand, + bip_RClav, + bip_RArm1, + bip_RArm2, + bip_RHand, + bip_neck, + bip_head, + bip_LLeg1, + bip_LLeg2, + bip_LFoot, + bip_LToe, + bip_RLeg1, + bip_RLeg2, + bip_RFoot, + bip_RToe, + bip_Lfinger0,bip_Lfinger1,bip_Lfinger2,bip_Lfinger3,bip_Lfinger4, + bip_Rfinger0,bip_Rfinger1,bip_Rfinger2,bip_Rfinger3,bip_Rfinger4 + ) + ) + bipNodes +) + +fn MapTMAxis sourceTM targetTM indexes mult:[1,1,1]=( + axis = #(sourceTM.row1,sourceTM.row2,sourceTM.row3) + targetTM.row1 = axis[indexes[1]]*mult[1] + targetTM.row2 = axis[indexes[2]]*mult[2] + targetTM.row3 = axis[indexes[3]]*mult[3] + targetTM +) + +fn SetClavicleTM bipCtl bipClav TM =( + local figMode = bipCtl.figuremode + bipCtl.figuremode = false -- FORCE EXIT FIGURE MODE + biped.setTransform bipClav #rotation TM.rotationPart false + + -- COPY POSTURE + select bipClav + local col = biped.createCopyCollection bipCtl "Shoulder" + local cpy = biped.copyBipPosture bipCtl col (selection as array) #snapAuto + + -- PASTE POSTURE ON FIGURE MODE + bipCtl.figuremode = true + biped.pasteBipPosture bipCtl cpy false #pstcopied false false false false + biped.deleteAllCopyCollections bipCtl + biped.setTransform bipClav #pos TM.row4 false + clearSelection() + bipCtl.figuremode = figMode +) + +fn alignFootTpose rigObj BipObj =( + local RigTM = rigObj.transform + + local endPoint = rigObj.children[1].pos + local height = abs (endPoint[3]-RigTM.row4[3]) + + + endPoint[3] = RigTM.row4[3] + local v = endPoint-RigTM.row4 + local len = length v + local y = normalize(v) + local x = [0,0,-1] + local z = normalize(cross x y) + local TM = Matrix3 x y z RigTM.row4 + + -- #SET ROTATION + biped.setTransform BipObj #rotation TM.rotationpart false + biped.setTransform bipObj #scale [height,len*1.25,len*0.6] false + biped.setTransform BipObj #pos RigTM.row4 false +) + +fn alignFoot rigObj BipObj figureMode:true degree:footDegree =( + local isSetKey = not figureMode + local RigTM = rigObj.transform + local bipTM = rotateYmatrix (90)* RigTM + local bipTM = rotateZmatrix degree*bipTM + + -- #SET ROTATION + biped.setTransform BipObj #rotation bipTM.rotationpart isSetKey + biped.setTransform BipObj #pos RigTM.row4 isSetKey + + + -- SET SCALE (ONLY IN FIGURE MODE) + if figureMode do( + + local toeTM = copy rigTM + local p1 = rigObj.children[1].pos + toeTM.row4 = p1 + + local v = rigTM.row2*-1 + local p2 = rigTM.row4*inverse toeTM + local rotTM = RotateXMatrix degree*toeTM + p2 *= rotTM + + local v = p2-p1 + local p3 = pointLineProj p2 p1 RigTM.row4 + local len = (length v ) + local height = length (p2-RigTM.row4) + biped.setTransform bipObj #scale [height,len,len*0.6] isSetKey + ) +) + +fn AlignArm BipUpperArm MxUpperArm side:"R" FigureMode:false TPose:false = +( + local createKey = not FigureMode + local BipLowerArm = BipUpperArm.children[1] + local Biphand = BipLowerArm.children[1] + local Bip_UpperTM = BipUpperArm.transform + local Bip_LowerTM = BipLowerArm.transform + local Bip_HandTM = Biphand.transform + + -- 정확한 본 이름으로 찾기 (children[1] 대신) + local MxForeArm = undefined + local MxHand = undefined + + if side == "R" then ( + MxForeArm = execute ("$'mixamorig:RightForeArm'") + MxHand = execute ("$'mixamorig:RightHand'") + ) else ( + MxForeArm = execute ("$'mixamorig:LeftForeArm'") + MxHand = execute ("$'mixamorig:LeftHand'") + ) + + -- 백업: 이름으로 못 찾으면 children[1] 사용 + if MxForeArm == undefined do MxForeArm = MxUpperArm.children[1] + if MxHand == undefined do MxHand = MxForeArm.children[1] + local MX_UpperTM = MxUpperArm.transform + local MX_ElbowTM = MxForeArm.transform + local MX_HandTM = MxHand.transform + + + -- SET SCALE + if FigureMode do( + len = distance MxUpperArm MxForeArm + biped.setTransform BipUpperArm #scale [len,len,len] createKey + len = distance MxForeArm MxHand + biped.setTransform BipLowerArm #scale [len,len,len] createKey + midFing = MxHand.children[3] + if isvalidNode midFing then len = distance MxHand MxHand.children[3] + else len *=0.2 + biped.setTransform Biphand #scale [len,len,len] createKey + ) + + local mid = (MX_HandTM.row4+MX_UpperTM.row4)*0.5 + local up = Normalize (mid-MX_ElbowTM.row4) + if TPose do up = [0,-1,0] + + --SET HAND FIRST BECAUSE THE IK + biped.setTransform Biphand #pos MX_HandTM.row4 createKey + + -- UpperArm + local x = Normalize (MX_ElbowTM.row4-Bip_UpperTM.row4) + local z = Normalize (cross up x ) + local y = Normalize (cross z x) + + local upperTM = matrix3 x y z MX_UpperTM.row4 + + -- UpperArm: 기하학적 계산은 유지, 축 변환만 제거 + biped.setTransform BipUpperArm #rotation upperTM.rotationpart createKey + + -- Hand: 위치만 설정, 축 변환 제거 + biped.setTransform Biphand #pos MX_HandTM.row4 createKey + +) + +fn AlignLeg BipUpperLeg MxUpperLeg side:"R" FigureMode:false TPose:false = +( + local createKey = not FigureMode + local BipLoweLeg = BipUpperLeg.children[1] + local BipFoot = BipLoweLeg.children[1] + local MxLowerLeg = MxUpperLeg.children[1] + local MxFoot = MxLowerLeg.children[1] + local rigTM = MxUpperLeg.transform + local BipUpperTM = BipUpperLeg.transform + local KneeTM = MxLowerLeg.transform + local endTM = MxFoot.transform + + if FigureMode do( + dist = distance MxUpperLeg MxLowerLeg + biped.setTransform BipUpperLeg #scale [dist,dist,dist] createKey + dist = distance MxLowerLeg MxFoot + biped.setTransform BipLoweLeg #scale [dist,dist,dist] createKey + ) + + local mid = (endTM.row4+rigTM.row4)*0.5 + local up = Normalize (mid-KneeTM.row4) + if TPose do up = [0,1,0] + + -- Foot + biped.setTransform BipFoot #pos endTM.row4 createKey + + -- UpperLeg + local x = Normalize (KneeTM.row4-BipUpperTM.row4) + local z = Normalize (cross up x ) + local y = Normalize (cross z x) + + local newTM = matrix3 x y z rigTM.row4 + + -- UpperLeg: 기하학적 계산은 유지, 축 변환만 제거 + biped.setTransform BipUpperLeg #rotation newTM.rotationpart createKey +) + +fn alignFingers BipNode MXNode figureMode:false=( + local bip2 = BipNode.children[1] + local bip3 = bip2.children[1] + local BipNodes = #(BipNode,bip2,bip3) + + local MX2 = MXNode.children[1] + local MX3 = MX2.children[1] + local MX4 = MX3.children[1] + local MXNodes = #(MXNode,MX2,MX3,MX4) + + if figureMode then( + for i=1 to 3 where isvalidNode MXNodes[i] do( + -- 단순 크기 조정만 (축 변환 제거) + if isValidNode MXNodes[i+1] then len = distance MXNodes[i] MXNodes[i+1] + else len = distance MXNodes[i] MXNodes[i].parent + + biped.setTransform BipNodes[i] #scale [len,len,len] false + + -- 첫 번째 관절만 위치 설정 + if i==1 do biped.setTransform BipNodes[i] #pos MXNodes[i].transform.row4 false + ) + + -- 손가락 펴기: 좌우손 엄지/다른손가락 하드코딩 처리 + try ( + local isThumb = (findString BipNode.name "Finger0" != undefined) + local isLeftHand = (findString BipNode.name " L " != undefined) or (findString BipNode.name "Left" != undefined) + + -- === 왼손 엄지 === + if isThumb and isLeftHand then ( + if isvalidNode BipNodes[1] do + biped.setTransform BipNodes[1] #rotation (eulerAngles -90 10 -30) false + if isvalidNode BipNodes[2] do + biped.setTransform BipNodes[2] #rotation (eulerAngles -90 10 -30) false + if isvalidNode BipNodes[3] do + biped.setTransform BipNodes[3] #rotation (eulerAngles -90 10 -30) false + ) + -- === 오른손 엄지 === + else if isThumb and not isLeftHand then ( + if isvalidNode BipNodes[1] do + biped.setTransform BipNodes[1] #rotation (eulerAngles -90 10 -150) false + if isvalidNode BipNodes[2] do + biped.setTransform BipNodes[2] #rotation (eulerAngles -90 10 -150) false + if isvalidNode BipNodes[3] do + biped.setTransform BipNodes[3] #rotation (eulerAngles -90 10 -150) false + ) + -- === 왼손 다른 손가락들 === + else if not isThumb and isLeftHand then ( + if isvalidNode BipNodes[1] do + biped.setTransform BipNodes[1] #rotation (eulerAngles -90 0 0) false + if isvalidNode BipNodes[2] do + biped.setTransform BipNodes[2] #rotation (eulerAngles -100 0 0) false + if isvalidNode BipNodes[3] do + biped.setTransform BipNodes[3] #rotation (eulerAngles -105 0 0) false + ) + -- === 오른손 다른 손가락들 === + else if not isThumb and not isLeftHand then ( + if isvalidNode BipNodes[1] do + biped.setTransform BipNodes[1] #rotation (eulerAngles -90 0 180) false + if isvalidNode BipNodes[2] do + biped.setTransform BipNodes[2] #rotation (eulerAngles -100 0 180) false + if isvalidNode BipNodes[3] do + biped.setTransform BipNodes[3] #rotation (eulerAngles -105 0 180) false + ) + ) catch() + ) + -- Animation Mode에서는 회전 조정 안함 (축 변환 제거) + +) + +fn BipToRigProportion bipNodes rigNodes TPose:false =( + footDegree = findFootAngle() + matchIndexes = #{1..(bipNodes.count)} + + + bipctl = bipNodes[1].transform.controller + bipctl.figureMode = true + + + Lthigh = $'mixamorig:LeftUpLeg' + Rthigh = $'mixamorig:RightUpLeg' + p = (Lthigh.pos+Rthigh.pos)*0.5 + + + + for i in matchIndexes where isvalidnode bipNodes[i] do( + n = bipNodes[i].name + bipNode = bipNodes[i] + rigNode = rigNodes[i] + if isvalidNode bipNode and isvalidNode rigNode do( + + rigNodeEnd = rigNodes[i+1] + rigTM = rigNode.transform + BipTM = bipNode.transform + + case of( + (i==1):( + -- Root: 위치만 조정, 축 변환 제거 + biped.setTransform bipNode #pos p false + ) + (findString n "Head"!= undefined): ( + rigNodeEnd = rigNode.children[1] + if isvalidNode rigNodeEnd do( + -- Head: 크기와 위치만 조정, 축 변환 제거 + dist = distance rigNode rigNodeEnd + biped.setTransform bipNode #scale [dist,dist,dist] false + biped.setTransform bipNode #pos rigNode.transform.row4 false + ) + ) + + (matchPattern n pattern:"*Neck*"):( + -- Neck: 크기만 조정, 축 변환 제거 + dist = distance rigNode rigNodeEnd + biped.setTransform bipNode #scale [dist,dist,dist] false + ) + + + (matchPattern n pattern:"*Foot*" ):( + if TPose then alignFootTpose rigNode bipNode + else alignFoot rigNode bipNode figureMode:true + ) + + (matchPattern n pattern:"*Toe*"):( + rigNodeEnd = rigNode.children[1] + if isvalidNode rigNodeEnd do( + -- Toe: 크기와 위치만 조정, 축 변환 제거 + dist = distance rigNode rigNodeEnd + biped.setTransform bipNode #scale [dist,dist*0.2,dist] false + biped.setTransform bipNode #pos rigNode.pos false + ) + ) + + (findString n "Pelvis"!= undefined ):( + rigNode = $'mixamorig:LeftUpLeg' + rigNodeEnd = $'mixamorig:RightUpLeg' + dist = distance Lthigh Rthigh + len = distance rigNode rigNodeEnd + biped.setTransform bipNode #scale [dist,dist,dist] false + ) + + + (MatchPattern n pattern:"*Spine*"):( + -- 스파인 2개 구조에서 Spine1이 마지막 스파인 + if findString n "Spine1"!=undefined do ( + rigNodeEnd =$'mixamorig:Neck' + ) + if isvalidNode rigNode and isvalidNode rigNodeEnd do( + len = distance rigNode rigNodeEnd + biped.setTransform bipNode #scale [len,len,len] false + ) + + if matchpattern n pattern:"*Spine*" do( + -- Spine: 위치만 조정, 축 변환 제거 + biped.setTransform bipNode #pos rigNode.pos false + ) + ) + + (matchPattern n pattern:"*Clavicle*"):( + -- 정확한 Arm 본 찾기 (children[1] 대신) + rigNodeEnd = undefined + if findString n "L Clavicle" != undefined or findString n "LeftShoulder" != undefined then ( + rigNodeEnd = execute ("$'mixamorig:LeftArm'") + ) else if findString n "R Clavicle" != undefined or findString n "RightShoulder" != undefined then ( + rigNodeEnd = execute ("$'mixamorig:RightArm'") + ) + + -- 백업: 이름으로 못 찾으면 children[1] 사용 + if rigNodeEnd == undefined do rigNodeEnd = rigNode.children[1] + + len = distance rigNode rigNodeEnd + + x = Normalize (rigNodeEnd.pos-rigNode.pos) + z = Normalize (Cross (bipNode.parent.transform.row2) x) + y = Normalize (cross z x) + newTM = matrix3 x y z rigTM.row4 + biped.setTransform bipNode #scale [len,len,len] false + SetClavicleTM bipCtl bipNode newTM + ) + + (matchPattern n pattern:"*UpperArm*"):( + + if MatchPattern n pattern:"* R *" then AlignArm bipNode rigNode side:"R" FigureMode:true TPose:TPose + else AlignArm bipNode rigNode side:"L" FigureMode:true TPose:TPose + + ) + + (matchPattern n pattern:"*Thigh*"):( + if MatchPattern n pattern:"* R *" then AlignLeg bipNode rigNode side:"R" figureMode:true TPose:TPose + else AlignLeg bipNode rigNode side:"L" figureMode:true TPose:TPose + ) + (MatchPattern n pattern:"*Finger*"): alignFingers bipNode rigNode figureMode:true + ) + ) + ) + + bipctl.figureMode = false +) + +fn getNumFingers = ( + local counts = #(0,0) + local lHand = (getNodeByName "mixamorig:LeftHand") + local rHand = (getNodeByName "mixamorig:RightHand") + if isValidnode lHand do counts[1] = lHand.children.count + if isValidnode rHand do counts[2] = rHand.children.count + amax counts +) + +fn GetExtras root=( + local out = #() + if root!=undefined do( + hrc = getBranchHRC root + for h=hrc.count to 1 by -1 do ( + local objArr = hrc[h] + for j=objArr.count to 1 by -1 do( + if (finditem MXNames objArr[j].name)!=0 do deleteitem objArr j -- REMOVE STANDARD FROM EXTRAS + ) + if objArr.count==0 do deleteItem hrc h + ) + out = hrc + ) + out +) + +fn cleanUpSkin obj =( + local skn = obj.modifiers["skin"] + local count = skinOps.GetNumberBones skn + local IDList = #() + for i=1 to count do( + n = (skinOps.GetBoneName skn i 0) + if findItem excludeBones n!=0 do( + append IDList i + ) + ) + if IDList.count>0 do for i=IDList.count to 1 by -1 do( + skinOps.removebone skn IDList[i] + ) +) + +fn GetSkinPairs obj extra:#() =( + local out=#() + noPairs = #() + local extraV1 = for i in extra collect i.v1 + local skn = obj.modifiers[#skin] + local count = skinOps.GetNumberBones skn + for i=1 to count do( + foundPairs = true + n = (skinOps.GetBoneName skn i 0) + dp = datapair n "" + case of( + (matchpattern n pattern:"*Hips"): dp.v2 = "Bip001 Pelvis" + (matchpattern n pattern:"*Spine"): dp.v2 = "Bip001 Spine" + (matchpattern n pattern:"*Spine1"): dp.v2 = "Bip001 Spine1" + (matchpattern n pattern:"*Spine2"): dp.v2 = "Bip001 Spine2" + (matchpattern n pattern:"*Neck"): dp.v2 = "Bip001 Neck" + (matchpattern n pattern:"*Head"): dp.v2 = "Bip001 Head" + (matchpattern n pattern:"*LeftShoulder"): dp.v2 = "Bip001 L Clavicle" + (matchpattern n pattern:"*LeftArm"): dp.v2 = "Bip001 L UpperArm" + (matchpattern n pattern:"*LeftForeArm"): dp.v2 = "Bip001 L Forearm" + (matchpattern n pattern:"*LeftHand"): dp.v2 = "Bip001 L Hand" + (matchpattern n pattern:"*LeftHandThumb1"): dp.v2 = "Bip001 L Finger0" + (matchpattern n pattern:"*LeftHandThumb2"): dp.v2 = "Bip001 L Finger01" + (matchpattern n pattern:"*LeftHandThumb3"): dp.v2 = "Bip001 L Finger02" + (matchpattern n pattern:"*LeftHandThumb4"): dp.v2 = "Bip001 L Finger0Nub" + (matchpattern n pattern:"*LeftHandIndex1"): dp.v2 = "Bip001 L Finger1" + (matchpattern n pattern:"*LeftHandIndex2"): dp.v2 = "Bip001 L Finger11" + (matchpattern n pattern:"*LeftHandIndex3"): dp.v2 = "Bip001 L Finger12" + (matchpattern n pattern:"*LeftHandIndex4"): dp.v2 = "Bip001 L Finger1Nub" + (matchpattern n pattern:"*LeftHandMiddle1"): dp.v2 = "Bip001 L Finger2" + (matchpattern n pattern:"*LeftHandMiddle2"): dp.v2 = "Bip001 L Finger21" + (matchpattern n pattern:"*LeftHandMiddle3"): dp.v2 = "Bip001 L Finger22" + (matchpattern n pattern:"*LeftHandMiddle4"): dp.v2 = "Bip001 L Finger2Nub" + (matchpattern n pattern:"*LeftHandRing1"): dp.v2 = "Bip001 L Finger3" + (matchpattern n pattern:"*LeftHandRing2"): dp.v2 = "Bip001 L Finger31" + (matchpattern n pattern:"*LeftHandRing3"): dp.v2 = "Bip001 L Finger32" + (matchpattern n pattern:"*LeftHandRing4"): dp.v2 = "Bip001 L Finger3Nub" + (matchpattern n pattern:"*LeftHandPinky1"): dp.v2 = "Bip001 L Finger4" + (matchpattern n pattern:"*LeftHandPinky2"): dp.v2 = "Bip001 L Finger41" + (matchpattern n pattern:"*LeftHandPinky3"): dp.v2 = "Bip001 L Finger42" + (matchpattern n pattern:"*LeftHandPinky4"): dp.v2 = "Bip001 L Finger4Nub" + (matchpattern n pattern:"*RightShoulder"): dp.v2 = "Bip001 R Clavicle" + (matchpattern n pattern:"*RightArm"): dp.v2 = "Bip001 R UpperArm" + (matchpattern n pattern:"*RightForeArm"): dp.v2 = "Bip001 R Forearm" + (matchpattern n pattern:"*RightHand"): dp.v2 = "Bip001 R Hand" + (matchpattern n pattern:"*RightHandThumb1"): dp.v2 = "Bip001 R Finger0" + (matchpattern n pattern:"*RightHandThumb2"): dp.v2 = "Bip001 R Finger01" + (matchpattern n pattern:"*RightHandThumb3"): dp.v2 = "Bip001 R Finger02" + (matchpattern n pattern:"*RightHandThumb4"): dp.v2 = "Bip001 R Finger0Nub" + (matchpattern n pattern:"*RightHandIndex1"): dp.v2 = "Bip001 R Finger1" + (matchpattern n pattern:"*RightHandIndex2"): dp.v2 = "Bip001 R Finger11" + (matchpattern n pattern:"*RightHandIndex3"): dp.v2 = "Bip001 R Finger12" + (matchpattern n pattern:"*RightHandIndex4"): dp.v2 = "Bip001 R Finger1Nub" + (matchpattern n pattern:"*RightHandMiddle1"): dp.v2 = "Bip001 R Finger2" + (matchpattern n pattern:"*RightHandMiddle2"): dp.v2 = "Bip001 R Finger21" + (matchpattern n pattern:"*RightHandMiddle3"): dp.v2 = "Bip001 R Finger22" + (matchpattern n pattern:"*RightHandMiddle4"): dp.v2 = "Bip001 R Finger2Nub" + (matchpattern n pattern:"*RightHandRing1"): dp.v2 = "Bip001 R Finger3" + (matchpattern n pattern:"*RightHandRing2"): dp.v2 = "Bip001 R Finger31" + (matchpattern n pattern:"*RightHandRing3"): dp.v2 = "Bip001 R Finger32" + (matchpattern n pattern:"*RightHandRing4"): dp.v2 = "Bip001 R Finger3Nub" + (matchpattern n pattern:"*RightHandPinky1"): dp.v2 = "Bip001 R Finger4" + (matchpattern n pattern:"*RightHandPinky2"): dp.v2 = "Bip001 R Finger41" + (matchpattern n pattern:"*RightHandPinky3"): dp.v2 = "Bip001 R Finger42" + (matchpattern n pattern:"*RightHandPinky4"): dp.v2 = "Bip001 R Finger4Nub" + (matchpattern n pattern:"*LeftUpLeg"): dp.v2 = "Bip001 L Thigh" + (matchpattern n pattern:"*LeftLeg"): dp.v2 = "Bip001 L Calf" + (matchpattern n pattern:"*LeftFoot"): dp.v2 = "Bip001 L Foot" + (matchpattern n pattern:"*LeftToeBase"): dp.v2 = "Bip001 L Toe0" + (matchpattern n pattern:"*RightUpLeg"): dp.v2 = "Bip001 R Thigh" + (matchpattern n pattern:"*RightLeg"): dp.v2 = "Bip001 R Calf" + (matchpattern n pattern:"*RightFoot"): dp.v2 = "Bip001 R Foot" + (matchpattern n pattern:"*RightToeBase"): dp.v2 = "Bip001 R Toe0" + default:(foundPairs = false ) + ) + + indexInExtra = findItem extraV1 n + if not foundPairs and indexInExtra != 0 then( + dp.v2 = extra[indexInExtra].v2 + ) + else if not foundPairs do append noPairs n + append out dp + ) + out +) + +-- ================================== 모퍼 처리 함수들 =============================================== + +struct MorpherChannelData ( + channelIndex, + targetName, + weight, + channelName, + isActive +) + +struct MorpherBackupData ( + objectName, + modifierName, + channels +) + +fn BackupMorpherData obj =( + /* + 모퍼 모디파이어 데이터를 백업하는 함수 + + 매개변수: + - obj: 모퍼 모디파이어를 가진 오브젝트 + + 반환값: + - MorpherBackupData 구조체 + */ + local morphMod = obj.modifiers[#Morpher] + if morphMod == undefined do return undefined + + local channels = #() + local channelCount = try (morphMod.numChannels) catch (100) -- 기본값 100개 채널 + + format "📦 모퍼 데이터 백업: % (채널 수: %)\\n" obj.name channelCount + + for i = 1 to channelCount do ( + try ( + -- 채널 프로퍼티에 직접 접근 + local channelProp = execute ("morphMod.morph_channel_" + i as string) + if channelProp != undefined then ( + local target = try (channelProp.target) catch (undefined) + local weight = try (channelProp.value) catch (0.0) + local channelName = try (channelProp.name) catch ("") + local isActive = try (channelProp.active) catch (false) + + if target != undefined and isValidNode target do ( + local channelData = MorpherChannelData \ + channelIndex:i \ + targetName:target.name \ + weight:weight \ + channelName:channelName \ + isActive:isActive + append channels channelData + format " 채널 %: % (가중치: %, 타겟: %)\\n" i channelName weight target.name + ) + ) + ) catch ( + -- 채널이 비어있거나 접근할 수 없는 경우 건너뛰기 + format " 채널 % 건너뛰기 (빈 채널)\\n" i + ) + ) + + MorpherBackupData objectName:obj.name modifierName:morphMod.name channels:channels +) + +fn RestoreMorpherData backupData pairsList =( + /* + 백업된 모퍼 데이터를 복원하는 함수 + + 매개변수: + - backupData: 백업된 모퍼 데이터 + - pairsList: 본 이름 매핑 정보 + + 반환값: + - 성공 시 true, 실패 시 false + */ + if backupData == undefined do return false + + local obj = getNodeByName backupData.objectName + if not isValidNode obj do ( + format "[ERROR] 모퍼 오브젝트를 찾을 수 없음: %\\n" backupData.objectName + return false + ) + + local morphMod = obj.modifiers[#Morpher] + if morphMod == undefined do ( + format "[ERROR] 모퍼 모디파이어를 찾을 수 없음: %\\n" obj.name + return false + ) + + format "🔄 모퍼 데이터 복원: % (채널 수: %)\\n" obj.name backupData.channels.count + + for channelData in backupData.channels do ( + -- 원본 타겟 이름을 새 이름으로 매핑 + local newTargetName = channelData.targetName + for pair in pairsList do ( + if pair.v1 == channelData.targetName do ( + newTargetName = pair.v2 + exit() + ) + ) + + local newTarget = getNodeByName newTargetName + if isValidNode newTarget then ( + try ( + -- 채널 프로퍼티에 직접 접근하여 설정 + local channelProp = execute ("morphMod.morph_channel_" + channelData.channelIndex as string) + if channelProp != undefined then ( + -- 타겟 설정 + try (channelProp.target = newTarget) catch (format " [WARNING] 타겟 설정 실패: 채널 %\\n" channelData.channelIndex) + -- 가중치 복원 + try (channelProp.value = channelData.weight) catch (format " [WARNING] 가중치 설정 실패: 채널 %\\n" channelData.channelIndex) + -- 채널 활성화 상태 복원 + try (channelProp.active = channelData.isActive) catch (format " [WARNING] 활성화 상태 설정 실패: 채널 %\\n" channelData.channelIndex) + -- 채널 이름 복원 + try (channelProp.name = channelData.channelName) catch (format " [WARNING] 채널 이름 설정 실패: 채널 %\\n" channelData.channelIndex) + + format " ✅ 채널 % 복원: % → % (가중치: %)\\n" channelData.channelIndex channelData.targetName newTargetName channelData.weight + ) else ( + format " ❌ 채널 % 프로퍼티 접근 실패\\n" channelData.channelIndex + ) + ) catch ( + format " ⚠️ 채널 % 설정 실패: % → %\\n" channelData.channelIndex channelData.targetName newTargetName + ) + ) else ( + format " ❌ 타겟을 찾을 수 없음: % → %\\n" channelData.targetName newTargetName + ) + ) + + true +) + +fn GetSkinPairs obj extra:#() =( + local out=#() + noPairs = #() + local extraV1 = for i in extra collect i.v1 + local skn = obj.modifiers[#skin] + local count = skinOps.GetNumberBones skn + for i=1 to count do( + foundPairs = true + n = (skinOps.GetBoneName skn i 0) + dp = datapair n "" + case of( + (matchpattern n pattern:"*Hips"): dp.v2 = "Bip001 Pelvis" + (matchpattern n pattern:"*Spine"): dp.v2 = "Bip001 Spine" + (matchpattern n pattern:"*Spine1"): dp.v2 = "Bip001 Spine1" + + (matchpattern n pattern:"*Neck"): dp.v2 = "Bip001 Neck" + (matchpattern n pattern:"*Head"): dp.v2 = "Bip001 Head" + (matchpattern n pattern:"*LeftShoulder"): dp.v2 = "Bip001 L Clavicle" + (matchpattern n pattern:"*LeftArm"): dp.v2 = "Bip001 L UpperArm" + (matchpattern n pattern:"*LeftForeArm"): dp.v2 = "Bip001 L Forearm" + (matchpattern n pattern:"*LeftHand"): dp.v2 = "Bip001 L Hand" + (matchpattern n pattern:"*LeftHandThumb1"): dp.v2 = "Bip001 L Finger0" + (matchpattern n pattern:"*LeftHandThumb2"): dp.v2 = "Bip001 L Finger01" + (matchpattern n pattern:"*LeftHandThumb3"): dp.v2 = "Bip001 L Finger02" + -- LeftHandThumb4 제거됨 + (matchpattern n pattern:"*LeftHandIndex1"): dp.v2 = "Bip001 L Finger1" + (matchpattern n pattern:"*LeftHandIndex2"): dp.v2 = "Bip001 L Finger11" + (matchpattern n pattern:"*LeftHandIndex3"): dp.v2 = "Bip001 L Finger12" + -- LeftHandIndex4 제거됨 + (matchpattern n pattern:"*LeftHandMiddle1"): dp.v2 = "Bip001 L Finger2" + (matchpattern n pattern:"*LeftHandMiddle2"): dp.v2 = "Bip001 L Finger21" + (matchpattern n pattern:"*LeftHandMiddle3"): dp.v2 = "Bip001 L Finger22" + -- LeftHandMiddle4 제거됨 + (matchpattern n pattern:"*LeftHandRing1"): dp.v2 = "Bip001 L Finger3" + (matchpattern n pattern:"*LeftHandRing2"): dp.v2 = "Bip001 L Finger31" + (matchpattern n pattern:"*LeftHandRing3"): dp.v2 = "Bip001 L Finger32" + -- LeftHandRing4 제거됨 + (matchpattern n pattern:"*LeftHandPinky1"): dp.v2 = "Bip001 L Finger4" + (matchpattern n pattern:"*LeftHandPinky2"): dp.v2 = "Bip001 L Finger41" + (matchpattern n pattern:"*LeftHandPinky3"): dp.v2 = "Bip001 L Finger42" + -- LeftHandPinky4 제거됨 + (matchpattern n pattern:"*RightShoulder"): dp.v2 = "Bip001 R Clavicle" + (matchpattern n pattern:"*RightArm"): dp.v2 = "Bip001 R UpperArm" + (matchpattern n pattern:"*RightForeArm"): dp.v2 = "Bip001 R Forearm" + (matchpattern n pattern:"*RightHand"): dp.v2 = "Bip001 R Hand" + (matchpattern n pattern:"*RightHandThumb1"): dp.v2 = "Bip001 R Finger0" + (matchpattern n pattern:"*RightHandThumb2"): dp.v2 = "Bip001 R Finger01" + (matchpattern n pattern:"*RightHandThumb3"): dp.v2 = "Bip001 R Finger02" + -- RightHandThumb4 제거됨 + (matchpattern n pattern:"*RightHandIndex1"): dp.v2 = "Bip001 R Finger1" + (matchpattern n pattern:"*RightHandIndex2"): dp.v2 = "Bip001 R Finger11" + (matchpattern n pattern:"*RightHandIndex3"): dp.v2 = "Bip001 R Finger12" + -- RightHandIndex4 제거됨 + (matchpattern n pattern:"*RightHandMiddle1"): dp.v2 = "Bip001 R Finger2" + (matchpattern n pattern:"*RightHandMiddle2"): dp.v2 = "Bip001 R Finger21" + (matchpattern n pattern:"*RightHandMiddle3"): dp.v2 = "Bip001 R Finger22" + -- RightHandMiddle4 제거됨 + (matchpattern n pattern:"*RightHandRing1"): dp.v2 = "Bip001 R Finger3" + (matchpattern n pattern:"*RightHandRing2"): dp.v2 = "Bip001 R Finger31" + (matchpattern n pattern:"*RightHandRing3"): dp.v2 = "Bip001 R Finger32" + -- RightHandRing4 제거됨 + (matchpattern n pattern:"*RightHandPinky1"): dp.v2 = "Bip001 R Finger4" + (matchpattern n pattern:"*RightHandPinky2"): dp.v2 = "Bip001 R Finger41" + (matchpattern n pattern:"*RightHandPinky3"): dp.v2 = "Bip001 R Finger42" + -- RightHandPinky4 제거됨 + (matchpattern n pattern:"*LeftUpLeg"): dp.v2 = "Bip001 L Thigh" + (matchpattern n pattern:"*LeftLeg"): dp.v2 = "Bip001 L Calf" + (matchpattern n pattern:"*LeftFoot"): dp.v2 = "Bip001 L Foot" + (matchpattern n pattern:"*LeftToeBase"): dp.v2 = "Bip001 L Toe0" + (matchpattern n pattern:"*RightUpLeg"): dp.v2 = "Bip001 R Thigh" + (matchpattern n pattern:"*RightLeg"): dp.v2 = "Bip001 R Calf" + (matchpattern n pattern:"*RightFoot"): dp.v2 = "Bip001 R Foot" + (matchpattern n pattern:"*RightToeBase"): dp.v2 = "Bip001 R Toe0" + default:(foundPairs = false ) + ) + + indexInExtra = findItem extraV1 n + if not foundPairs and indexInExtra != 0 then( + dp.v2 = extra[indexInExtra].v2 + ) + else foundPairs = false + + append out dp + if not foundPairs do append noPairs n + ) + out +) + +-- ================================== 메인 변환 함수 =============================================== + +fn ConvertMixamoToBiped charName:"Character001" alwaysDeform:false =( + /* + Mixamo T-Pose 아바타를 Biped으로 변환하는 메인 함수 + + 매개변수: + - charName: 캐릭터 이름 (기본값: "Character001") + - alwaysDeform: 스킨 Always Deform 설정 (기본값: false) + + 반환값: + - 성공 시 true, 실패 시 false + */ + + if charName=="" do( + messagebox "please insert Character Name" + return false + ) + + -- 이름 정리 및 검증 + CleanNames() + local root = getNodeByName"mixamorig:Hips" + if not isValidNode root do ( + format "[ERROR] mixamorig:Hips not found!\n" + return false + ) + + -- 리그 유효성 검증 + if not SanityCheck() do ( + format "[ERROR] Sanity check failed!\n" + return false + ) + + local hrc = getHierarchy #(root) #() + + -- 추가 본들 수집 및 메인에서 제외 + local extras = GetExtras root + for i in extras do( + for j in i do( + deleteItem hrc (findItem hrc j) + ) + ) + + -- 스킨 오브젝트들 수집 + local skinObjects = for i in objects where i.modifiers[#Skin]!=undefined collect i + if alwaysDeform do for i in skinObjects do ( + cleanUpSkin i + i.modifiers[#Skin].always_deform =false + i.modifiers[#Skin].always_deform =true + ) + + -- 모퍼 오브젝트들 수집 (간소화) + local morpherObjects = for i in objects where i.modifiers[#Morpher]!=undefined collect i + local morpherModifiers = #() -- 제거된 모퍼 모디파이어들을 저장 + + for obj in morpherObjects do ( + -- 모퍼 모디파이어를 임시로 제거하고 저장 + local morphMod = obj.modifiers[#Morpher] + append morpherModifiers (datapair obj morphMod) + deleteModifier obj morphMod + ) + + -- 믹사모 리그 구조 분석 + local mxNodes = getRigStructures() + local mx_Obj = $mixamorig* + local height = mx_Obj.max[3] + local numfingers = getNumFingers() + + -- 바이패드 생성 + format "=== BIPED 생성 디버그 ===\\n" + format "spineSegments: %\\n" spineSegments + format "height: %\\n" height + format "numfingers: %\\n" numfingers + + local bipObj = createBip height spine:spineSegments fingers:numfingers + + -- 생성 직후 구조 확인 + format "\\n=== 생성 직후 Biped 구조 ===\\n" + local pelvis = biped.getNode bipObj #pelvis link:1 + local spine1 = biped.getNode bipObj #spine link:1 + local spine2 = try (biped.getNode bipObj #spine link:2) catch (undefined) + local lthigh = biped.getNode bipObj #lleg link:1 + local lclavicle = biped.getNode bipObj #larm link:1 + local neck = biped.getNode bipObj #neck link:1 + + format "Pelvis: % (부모: %)\\n" pelvis.name (if pelvis.parent != undefined then pelvis.parent.name else "없음") + format "Spine1: % (부모: %)\\n" spine1.name (if spine1.parent != undefined then spine1.parent.name else "없음") + if spine2 != undefined then format "Spine2: % (부모: %)\\n" spine2.name (if spine2.parent != undefined then spine2.parent.name else "없음") + format "LThigh: % (부모: %)\\n" lthigh.name (if lthigh.parent != undefined then lthigh.parent.name else "없음") + format "LClavicle: % (부모: %)\\n" lclavicle.name (if lclavicle.parent != undefined then lclavicle.parent.name else "없음") + format "Neck: % (부모: %)\\n" neck.name (if neck.parent != undefined then neck.parent.name else "없음") + + -- 🔧 잘못된 구조 수정 + format "\\n=== Biped 구조 수정 중 ===\\n" + + -- Figure Mode 활성화 (구조 수정 가능) + bipObj.transform.controller.figureMode = true + + -- 다리 본들을 Pelvis로 이동 (현재 Spine에서 → Pelvis로) + local rthigh = biped.getNode bipObj #rleg link:1 + if lthigh.parent != pelvis then ( + format "다리를 Pelvis로 이동: % → %\\n" lthigh.parent.name pelvis.name + lthigh.parent = pelvis + rthigh.parent = pelvis + ) + + -- 어깨 본들을 최상위 Spine으로 이동 (현재 Neck에서 → Spine1로) + local rclavicle = biped.getNode bipObj #rarm link:1 + local topSpine = if spine2 != undefined then spine2 else spine1 -- 최상위 Spine + if lclavicle.parent != topSpine then ( + format "어깨를 최상위 Spine으로 이동: % → %\\n" lclavicle.parent.name topSpine.name + lclavicle.parent = topSpine + rclavicle.parent = topSpine + ) + + format "구조 수정 완료!\\n" + + -- 구조 수정 후 확인 + format "\\n=== 구조 수정 후 확인 ===\\n" + format "LThigh: % (부모: %)\\n" lthigh.name (if lthigh.parent != undefined then lthigh.parent.name else "없음") + format "RThigh: % (부모: %)\\n" rthigh.name (if rthigh.parent != undefined then rthigh.parent.name else "없음") + format "LClavicle: % (부모: %)\\n" lclavicle.name (if lclavicle.parent != undefined then lclavicle.parent.name else "없음") + format "RClavicle: % (부모: %)\\n" rclavicle.name (if rclavicle.parent != undefined then rclavicle.parent.name else "없음") + + local bipedNodes = GetBipStructure bipObj spinesegs:spineSegments + + -- 비율 매칭 + BipToRigProportion bipedNodes mxNodes TPose:true + + -- 비율 매칭 후 구조 재확인 + format "\\n=== 비율 매칭 후 Biped 구조 ===\\n" + local pelvis_after = biped.getNode bipObj #pelvis link:1 + local spine1_after = biped.getNode bipObj #spine link:1 + local lthigh_after = biped.getNode bipObj #lleg link:1 + local lclavicle_after = biped.getNode bipObj #larm link:1 + local neck_after = biped.getNode bipObj #neck link:1 + + format "Pelvis: % (부모: %)\\n" pelvis_after.name (if pelvis_after.parent != undefined then pelvis_after.parent.name else "없음") + format "Spine1: % (부모: %)\\n" spine1_after.name (if spine1_after.parent != undefined then spine1_after.parent.name else "없음") + format "LThigh: % (부모: %)\\n" lthigh_after.name (if lthigh_after.parent != undefined then lthigh_after.parent.name else "없음") + format "LClavicle: % (부모: %)\\n" lclavicle_after.name (if lclavicle_after.parent != undefined then lclavicle_after.parent.name else "없음") + format "Neck: % (부모: %)\\n" neck_after.name (if neck_after.parent != undefined then neck_after.parent.name else "없음") + + -- 추가 본들 생성 (선택사항) + local extrasPairs = #() + + -- ========== 원본 엑스트라 본 유지 방식 ================ + local totalExtraCount = 0 + for ex in extras do totalExtraCount += ex.count + format "전체 Extra Bones 개수: %개 (원본 본 그대로 유지)\\n" totalExtraCount + + bipObj.transform.controller.figureMode = true + local idx = 1 + for ex in extras do( + for i=1 to ex.count do ( + local originalBone = ex[i] + if not isValidNode originalBone do continue + + -- 원본 본을 그대로 사용하되, 정리된 이름으로 매핑 + local cleanName = substituteString originalBone.name "mixamorig:" "" + append extrasPairs (datapair originalBone.name cleanName) + append MXPairs (datapair originalBone.name cleanName) + + format " [%] % → 원본 유지" idx originalBone.name + + -- 첫 번째 본의 경우 적절한 Biped 부모에 연결 + if i==1 then ( + local par = originalBone.parent + for pair in MXPairs where isValidNode par and par.name==pair.v1 do ( + local bipPar = getNodebyName pair.v2 + if isValidNode bipPar do ( + -- 원본 본을 Biped 부모에 연결 + try ( + originalBone.parent = bipPar + format " (부모: %)\\n" bipPar.name + ) catch ( + format " (부모 연결 실패: %)\\n" bipPar.name + ) + exit() + ) + ) + if originalBone.parent == par do format " (부모 연결 안됨)\\n" + ) else ( + format "\\n" + ) + idx += 1 + ) + ) + bipObj.transform.controller.figureMode = false + + format "\\n=== 원본 엑스트라 본 유지 완료 ===\\n" + format "- 각도/위치 완벽 보존\\n" + format "- 애니메이션 데이터 손실 없음\\n" + format "- 스키닝 품질 유지\\n" + + -- Extra Bones 생성 후 최종 구조 확인 + format "\\n=== Extra Bones 생성 후 최종 구조 ===\\n" + local pelvis_final = biped.getNode bipObj #pelvis link:1 + local spine1_final = biped.getNode bipObj #spine link:1 + local lthigh_final = biped.getNode bipObj #lleg link:1 + local lclavicle_final = biped.getNode bipObj #larm link:1 + local neck_final = biped.getNode bipObj #neck link:1 + + format "Pelvis: % (부모: %)\\n" pelvis_final.name (if pelvis_final.parent != undefined then pelvis_final.parent.name else "없음") + format "Spine1: % (부모: %)\\n" spine1_final.name (if spine1_final.parent != undefined then spine1_final.parent.name else "없음") + format "LThigh: % (부모: %)\\n" lthigh_final.name (if lthigh_final.parent != undefined then lthigh_final.parent.name else "없음") + format "LClavicle: % (부모: %)\\n" lclavicle_final.name (if lclavicle_final.parent != undefined then lclavicle_final.parent.name else "없음") + format "Neck: % (부모: %)\\n" neck_final.name (if neck_final.parent != undefined then neck_final.parent.name else "없음") + + -- 하이브리드 방식: 엑스트라 본 처리 결과 + if extrasPairs.count > 0 then ( + format "\\n📊 Extra Bones 처리 결과 요약:\\n" + format "✅ 총 %개 엑스트라 본 처리 완료\\n" extrasPairs.count + format "✅ 모든 엑스트라 본이 원본 위치/각도 유지\\n" + format "✅ 위치 변화: 0 (원본 본 그대로 사용)\\n" + format "✅ 각도 변화: 0 (원본 각도 그대로 사용)\\n" + format "✅ 애니메이션 데이터: 완벽 보존\\n" + format "✅ 스키닝 품질: 원본 유지\\n" + format "✅ 표준 본: Biped로 대체됨\\n" + format "✅ 엑스트라 본: 원본 유지됨\\n" + ) + + format "================================\\n" + + -- 스킨 팝업 자동 처리 함수 (강화 버전) + fn ShowPopup =( + local hwnd = (DialogMonitorOPS.GetWindowHandle() ) + if hwnd!=0 then( + local windowTitle = uiAccessor.getWindowText hwnd + local children = uiAccessor.getChildWindows hwnd + + -- 다이얼로그 타입별 처리 + case of ( + -- Load Envelopes 다이얼로그 + (findString windowTitle "Load Envelopes" != undefined): ( + format "Load Envelopes 다이얼로그 감지 - 자동 OK 처리\\n" + for c in children do( + local buttonText = uiAccessor.getWindowText c + if buttonText == "OK" do ( + UIAccessor.PressButton c + format "OK 버튼 자동 클릭 완료\\n" + return true + ) + ) + ) + + -- Match by Name 다이얼로그 + (findString windowTitle "Match by Name" != undefined): ( + format "Match by Name 다이얼로그 감지 - 자동 처리\\n" + for c in children do( + if (uiAccessor.getWindowText c) =="Match by Name" do UIAccessor.PressButton c + ) + ) + + -- 기타 다이얼로그들 + default: ( + -- 일반적인 OK 버튼 찾기 + for c in children do( + local buttonText = uiAccessor.getWindowText c + if buttonText == "OK" do ( + format "일반 OK 버튼 감지 - 자동 클릭: %\\n" windowTitle + UIAccessor.PressButton c + return true + ) + ) + ) + ) + + -- 기본 버튼 처리 + uiAccessor.pressDefaultButton() + true + ) + else false + ) + + DialogMonitorOPS.Enabled = true + DialogMonitorOPS.Interactive =false + DialogMonitorOPS.RegisterNotification ShowPopup id:#MixamoToBiped + + local pth = GetDir #export + -- 하이브리드 방식: 표준 본은 Biped로, 엑스트라 본은 원본으로 + format "\\n=== 스킨 오브젝트 처리 (Biped + 엑스트라 본) ===\\n" + + for i in skinObjects do( + format "처리 중: %\\n" i.name + local skn = i.modifiers["skin"] + local n = i.name+"_skin.env" + local f = (pth+"\\"+n) + + -- 기존 스킨 데이터 저장 + if skn != undefined then ( + skinOps.saveEnvelope skn f + format "- 기존 스킨 데이터 저장: %\\n" f + ) + + -- 페어 가져오기 (표준 본 + 엑스트라 본) + local pairs = GetSkinPairs i extra:extrasPairs + format "- 매핑된 본 쌍: %개\\n" pairs.count + + local bipedSkin = #() + for j in pairs do ( + local obj1 = getNodeByName j.v1 + local obj2 = getNodeByName j.v2 + if isvalidNode obj1 and isValidNode obj2 do( + tempName = obj1.name + obj1.name = obj2.name + obj2.name = tempName + append bipedSkin obj2 + ) + ) + + -- 새 스킨 추가 + local skn2 = skin() + addmodifier i skn2 + select i + max modify mode + modPanel.setCurrentObject skn2 + + -- Biped 본들과 엑스트라 본들 추가 + for j in bipedSkin do( + if j!= bipedSkin[bipedSkin.count] then skinOps.AddBone skn2 j 0 + else skinOps.AddBone skn2 j -1 + ) + max create mode + + -- 스킨 데이터 로드 (저장된 것이 있는 경우) + if doesFileExist f then ( + skinOps.loadEnvelope skn2 f + format "- 스킨 데이터 로드 완료\\n" + ) + maxOps.CollapseNodeTo i 2 true + + -- 이름 원래대로 되돌리기 + for j in pairs do ( + obj1 = getNodeByName j.v1 + obj2 = getNodeByName j.v2 + if isvalidNode obj1 and isValidNode obj2 do( + tempName = obj1.name + obj1.name = obj2.name + obj2.name = tempName + ) + ) + + -- 임시 파일 삭제 + if doesFileExist f do deleteFile f + + format "- % 처리 완료\\n" i.name + ) + + format "\\n✅ 모든 스킨 오브젝트 처리 완료 (Biped + 엑스트라)\\n" + + -- 대화상자 모니터 비활성화 + DialogMonitorOPS.Enabled = false + DialogMonitorOPS.UnRegisterNotification id:#MixamoToBiped + + -- 모퍼 모디파이어 복원 (간소화) + if morpherModifiers.count > 0 do ( + format "\\n🎭 모퍼 모디파이어 복원 시작...\\n" + + -- 모퍼 모디파이어를 다시 추가 + for modPair in morpherModifiers do ( + local obj = modPair.v1 + local morphMod = modPair.v2 + addModifier obj morphMod + ) + + format "✅ 모퍼 모디파이어 복원 완료\\n" + ) + + -- 하이브리드 방식: 표준 본은 삭제, 엑스트라 본은 유지 + format "\\n=== 믹사모 표준 본 삭제 ===\\n" + + -- 표준 본들만 선별해서 삭제 (엑스트라 본 제외) + local standardBones = #() + local allMixamoBones = getHierarchy #(root) #() + + for bone in allMixamoBones do ( + if isValidNode bone and findItem MXNames bone.name != 0 then ( + appendIfUnique standardBones bone + format "- 삭제 예정: %\\n" bone.name + ) + ) + + -- 표준 본들 삭제 + if standardBones.count > 0 then ( + delete standardBones + format "✅ %개 표준 본 삭제 완료\\n" standardBones.count + ) + + -- 엑스트라 본 이름 정리 (mixamorig: 제거) + format "\\n=== 엑스트라 본 이름 정리 ===\\n" + for i in extrasPairs do( + local theObj = getNodeByName i.v1 + if isValidNode theObj then ( + theObj.name = i.v2 -- 이미 정리된 이름으로 변경 + format "- % → %\\n" i.v1 i.v2 + ) + ) + + -- 레이어 설정 + local rootLayer = LayerManager.newLayerFromName "CHARACTERS" + if rootLayer == undefined do rootLayer = LayerManager.getLayerFromName "CHARACTERS" + + local charLayer = LayerManager.newLayerFromName (toUpper charName) + if charLayer == undefined do charLayer = LayerManager.getLayerFromName charName + charLayer.setParent rootLayer + + + local bipLayer = LayerManager.newLayerFromName (charName+"_BIP") + if bipLayer == undefined do bipLayer = LayerManager.getLayerFromName (charName+"_BIP") + bipLayer.setParent charLayer + + local GeoLayer = LayerManager.newLayerFromName (charName+"_GEO") + if GeoLayer == undefined do GeoLayer = LayerManager.getLayerFromName (charName+"_GEO") + GeoLayer.setParent charLayer + GeoLayer.isfrozen =true + + -- 오브젝트 관리 + for i in skinObjects do ( + i.showFrozenInGray = false + local n = i.name + if not matchPattern n pattern:(charName+"*") do i.name = charName+"_"+i.name + if i.material!=undefined do ( + mtlName = i.material.name + if not matchPattern mtlName pattern:(charName+"*") do i.material.name = charName+"_"+mtlName + ) + GeoLayer.addnode i + ) + + local allBip = getHierarchy bipObj #() + for i in allBip do( + bipLayer.addNode i + i.name = charName+"_"+i.name + i.renderable =false + i.boxMode = true + ) + + -- 성공 반환 + format "[SUCCESS] Mixamo to Biped conversion completed for: %\n" charName + redrawViews() + true +) + +-- ================================== 자동 실행 =============================================== + +-- 스크립트 로딩 완료 후 바로 변환 시작 +format "\n✅ MixamoToBiped Converter 로딩 완료!\n" +format "자동으로 변환을 시작합니다...\n\n" + +try ( + format "=== Mixamo to Biped 자동 변환기 ===\n" + + -- Mixamo 오브젝트 확인 + local mixamoObjects = $mixamorig* as array + if mixamoObjects.count > 0 then ( + format "Mixamo 오브젝트 발견: %개\n" mixamoObjects.count + + -- 캐릭터 이름 자동 생성 (랜덤 번호 기반) + local randomNum = random 10000 99999 + local charName = "Character" + (randomNum as string) + + format "기본 이름 사용: %\n" charName + format "변환 시작: %\n" charName + format "=================================\n" + + -- 변환 실행 + try ( + local result = ConvertMixamoToBiped charName:charName + + if result then ( + format "\n🎉 변환 완료! 🎉\n" + format "캐릭터: %\n" charName + format "✅ Biped 로컬 축: 표준 유지 (축 뒤틀림 없음)\n" + format "✅ T-Pose 자세: 기존 알고리즘으로 매칭\n" + format "✅ 애니메이션 호환성: 완벽\n" + format "레이어: CHARACTERS > %\n" (toUpper charName) + redrawViews() + ) else ( + format "\n❌ 변환 실패!\n" + format "Mixamo 리그 구조를 확인해주세요.\n" + ) + ) catch ( + format "\n❌ 변환 중 오류 발생!\n" + format "오류 내용: %\n" (getCurrentException()) + ) + ) else ( + format "\n⚠️ Mixamo 오브젝트를 찾을 수 없습니다!\n" + format "1. Mixamo T-Pose FBX 파일을 먼저 임포트해주세요.\n" + format "2. 임포트 후 이 스크립트를 다시 실행하세요.\n" + format "\n사용법:\n" + format "1. File > Import > Mixamo T-Pose FBX 선택\n" + format "2. 스크립트 실행 (자동으로 변환 시작)\n" + format "3. 자동 변환 완료! ✨\n" + ) +) catch ( + format "❌ 스크립트 실행 중 심각한 오류 발생!\n" + format "오류 내용: %\n" (getCurrentException()) +) \ No newline at end of file diff --git a/Assets/External/EasyMotionRecorder/Scripts/CharacterFacialData.cs b/Assets/External/EasyMotionRecorder/Scripts/CharacterFacialData.cs index c6f9d100..b48ceedb 100644 --- a/Assets/External/EasyMotionRecorder/Scripts/CharacterFacialData.cs +++ b/Assets/External/EasyMotionRecorder/Scripts/CharacterFacialData.cs @@ -26,6 +26,9 @@ namespace EasyMotionRecorder [SerializeField] public List BlendShapeValues = new List(); + [SerializeField] + public List SkinnedMeshRendererNames = new List(); + [SerializeField] public int FrameCount; diff --git a/Assets/External/EasyMotionRecorder/Scripts/FaceAnimationRecorder.cs b/Assets/External/EasyMotionRecorder/Scripts/FaceAnimationRecorder.cs index df778aba..48f0d04d 100644 --- a/Assets/External/EasyMotionRecorder/Scripts/FaceAnimationRecorder.cs +++ b/Assets/External/EasyMotionRecorder/Scripts/FaceAnimationRecorder.cs @@ -50,7 +50,11 @@ namespace Entum { private int _frameCount = 0; - CharacterFacialData.SerializeHumanoidFace _past = new CharacterFacialData.SerializeHumanoidFace(); + CharacterFacialData.SerializeHumanoidFace _past = new CharacterFacialData.SerializeHumanoidFace() { + BlendShapeNames = new List(), + BlendShapeValues = new List(), + SkinnedMeshRendererNames = new List() + }; private float _recordedTime = 0f; private float _startTime; @@ -152,29 +156,90 @@ namespace Entum { var clip = new AnimationClip(); clip.frameRate = 30; - var blendShapeNames = facial.Faces[0].BlendShapeNames; - var curves = new Dictionary(); + // 블렌드쉐이프와 렌더러별로 애니메이션 커브를 그룹화 + var rendererCurves = new Dictionary>(); - for(int i = 0; i < blendShapeNames.Count; i++) { - curves[blendShapeNames[i]] = new AnimationCurve(); + // 첫 번째 프레임에서 모든 블렌드쉐이프와 렌더러 정보 수집 + if (facial.Faces.Count > 0) + { + var firstFace = facial.Faces[0]; + + // 기존 데이터 호환성 체크 (SkinnedMeshRendererNames가 없는 경우) + bool hasRendererNames = firstFace.SkinnedMeshRendererNames != null && + firstFace.SkinnedMeshRendererNames.Count > 0; + + for(int i = 0; i < firstFace.BlendShapeNames.Count; i++) { + var blendShapeName = firstFace.BlendShapeNames[i]; + var rendererName = hasRendererNames && i < firstFace.SkinnedMeshRendererNames.Count + ? firstFace.SkinnedMeshRendererNames[i] + : "DefaultRenderer"; // 기존 데이터 호환성을 위한 기본값 + + if (!rendererCurves.ContainsKey(rendererName)) { + rendererCurves[rendererName] = new Dictionary(); + } + + if (!rendererCurves[rendererName].ContainsKey(blendShapeName)) { + rendererCurves[rendererName][blendShapeName] = new AnimationCurve(); + } + } + + if (!hasRendererNames) { + Debug.LogWarning("기존 데이터 형식 감지: SkinnedMeshRenderer 정보가 없습니다. 기본 경로를 사용합니다."); + } } - for(int i = 0; i < facial.Faces.Count; i++) { - var face = facial.Faces[i]; + // 모든 프레임의 데이터를 커브에 추가 + for(int frameIdx = 0; frameIdx < facial.Faces.Count; frameIdx++) { + var face = facial.Faces[frameIdx]; var time = face.Time; + + // 기존 데이터 호환성 체크 + bool hasRendererNames = face.SkinnedMeshRendererNames != null && + face.SkinnedMeshRendererNames.Count > 0; - for(int j = 0; j < face.BlendShapeNames.Count; j++) { - var blendShapeName = face.BlendShapeNames[j]; - var value = face.BlendShapeValues[j]; + for(int i = 0; i < face.BlendShapeNames.Count; i++) { + var blendShapeName = face.BlendShapeNames[i]; + var value = face.BlendShapeValues[i]; + var rendererName = hasRendererNames && i < face.SkinnedMeshRendererNames.Count + ? face.SkinnedMeshRendererNames[i] + : "DefaultRenderer"; // 기존 데이터 호환성을 위한 기본값 - if(curves.ContainsKey(blendShapeName)) { - curves[blendShapeName].AddKey(time, value); + if (rendererCurves.ContainsKey(rendererName) && + rendererCurves[rendererName].ContainsKey(blendShapeName)) { + rendererCurves[rendererName][blendShapeName].AddKey(time, value); } } } - foreach(var curve in curves) { - clip.SetCurve("", typeof(SkinnedMeshRenderer), "blendShape." + curve.Key, curve.Value); + // 렌더러별로 애니메이션 커브를 클립에 추가 + foreach(var rendererPair in rendererCurves) { + var rendererName = rendererPair.Key; + var curves = rendererPair.Value; + + string rendererPath; + + // 기존 데이터 호환성: DefaultRenderer인 경우 빈 경로 사용 + if (rendererName == "DefaultRenderer") { + rendererPath = ""; + Debug.Log($"기존 데이터 호환성: 빈 경로로 {curves.Count}개 블렌드쉐이프 커브 추가"); + } else { + // 해당 렌더러의 Transform 경로 찾기 + rendererPath = FindRendererPath(root.transform, rendererName); + + if (string.IsNullOrEmpty(rendererPath)) { + Debug.LogWarning($"렌더러 '{rendererName}'의 경로를 찾을 수 없습니다. 루트 경로를 사용합니다."); + rendererPath = rendererName; + } + + Debug.Log($"렌더러 '{rendererName}' ({rendererPath})에 {curves.Count}개 블렌드쉐이프 커브 추가"); + } + + foreach(var curvePair in curves) { + var blendShapeName = curvePair.Key; + var curve = curvePair.Value; + + clip.SetCurve(rendererPath, typeof(SkinnedMeshRenderer), "blendShape." + blendShapeName, curve); + } } // 캐릭터 이름 가져오기 @@ -198,6 +263,29 @@ namespace Entum { #endif } + /// + /// 지정된 렌더러 이름의 Transform 경로를 찾습니다. + /// + private string FindRendererPath(Transform root, string rendererName) { + if (root.name == rendererName) { + return ""; + } + + for (int i = 0; i < root.childCount; i++) { + var child = root.GetChild(i); + if (child.name == rendererName) { + return child.name; + } + + string childPath = FindRendererPath(child, rendererName); + if (!string.IsNullOrEmpty(childPath)) { + return child.name + "/" + childPath; + } + } + + return null; + } + private string GetCharacterName() { if (_animRecorder?.CharacterAnimator == null) return ""; @@ -272,10 +360,25 @@ namespace Entum { return false; } + if(a.SkinnedMeshRendererNames.Count != b.SkinnedMeshRendererNames.Count) { + return false; + } + for(int i = 0; i < a.BlendShapeNames.Count; i++) { if(a.BlendShapeValues[i] != b.BlendShapeValues[i]) { return false; } + + if(a.BlendShapeNames[i] != b.BlendShapeNames[i]) { + return false; + } + + // SkinnedMeshRenderer 이름도 비교 + if(i < a.SkinnedMeshRendererNames.Count && i < b.SkinnedMeshRendererNames.Count) { + if(a.SkinnedMeshRendererNames[i] != b.SkinnedMeshRendererNames[i]) { + return false; + } + } } return true; @@ -300,6 +403,7 @@ namespace Entum { var current = new CharacterFacialData.SerializeHumanoidFace(); current.BlendShapeNames = new List(); current.BlendShapeValues = new List(); + current.SkinnedMeshRendererNames = new List(); for(int i = 0; i < _smeshs.Length; i++) { var mesh = _smeshs[i]; @@ -324,6 +428,7 @@ namespace Entum { var weight = mesh.GetBlendShapeWeight(j); current.BlendShapeNames.Add(blendShapeName); current.BlendShapeValues.Add(weight); + current.SkinnedMeshRendererNames.Add(mesh.name); } }