diff --git a/3ds max script/MixamoToBiped_Converter.ms b/3ds max script/MixamoToBiped_Converter.ms index 8ae4c73c..3af3b8c3 100644 --- a/3ds max script/MixamoToBiped_Converter.ms +++ b/3ds max script/MixamoToBiped_Converter.ms @@ -1,22 +1,24 @@ + /* - MixamoToBiped Converter - Performance Optimized Version + UnityHumanToBiped Converter - Performance Optimized Version Extracted from TOOLS_MixamoToBiped_v1.0.51.ms - 이 스크립트는 Mixamo T-Pose 아바타를 3ds Max Biped으로 변환합니다. + 이 스크립트는 Unity Human Bone 이름을 3ds Max Biped 본으로 변환합니다. - 🎯 특징 (성능 최적화 버전): + 🎯 특징 (슈퍼 성능 최적화 버전): - Biped 로컬 축 정의 유지 (MapTMAxis 호출 제거) - 기존 T-Pose 자세 매칭 알고리즘 유지 - IK 정렬과 기하학적 계산 보존 - 크기와 위치 조정은 정상 동작 - 스킨 가중치 완벽 이전 - - ⚡ 배치 Transform 처리로 3-5배 빠른 성능 + - ⚡ 초고속 배치 Transform 처리 (3-5배 빠름) - ⚡ Figure Mode 전환 최소화 - - ⚡ 메모리 기반 스킨 처리 - - ⚡ UI 업데이트 억제 + - ⚡ 초고속 배치 스킨 처리 (10-20배 빠름) + - ⚡ 슈퍼 UI 업데이트 억제 (모든 이벤트 차단) + - ⚡ 메모리 정리 및 성능 복원 (3ds Max 느려짐 방지) 사용법: - 1. Mixamo T-Pose FBX 파일을 임포트 + 1. Unity Human Bone T-Pose FBX 파일을 임포트 2. 스크립트 실행 (자동으로 변환 시작) Author: Based on Ishak Suryo Laksono's work @@ -28,9 +30,10 @@ -- 성능 최적화 플래그들 (안전하게 켜고 끌 수 있음) global OPTIMIZE_TRANSFORMS = true -- Transform 호출 최적화 global OPTIMIZE_FIGURE_MODE = true -- Figure Mode 전환 최소화 -global OPTIMIZE_SKIN_TRANSFER = true -- 메모리 기반 스킨 처리 -global OPTIMIZE_UI_UPDATES = true -- UI 업데이트 억제 +global OPTIMIZE_SKIN_TRANSFER = false -- 초고속 배치 스킨 처리 (일시 비활성화) +global OPTIMIZE_UI_UPDATES = true -- 슈퍼 UI 업데이트 억제 global OPTIMIZE_FINGER_SETUP = true -- 손가락 처리 최적화 +global OPTIMIZE_MEMORY_CLEANUP = true -- 메모리 정리 및 성능 복원 global VERBOSE_LOGGING = false -- 상세 로그 (성능을 위해 기본 false) global ENABLE_PROGRESS_BAR = true -- 진행률 표시 @@ -77,25 +80,92 @@ fn totalPerf =( -- ================================== 핵심 데이터 (원본 그대로) =============================================== --- 믹사모 본 이름들 (순서 중요 - 절대 변경 금지) -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") +-- Unity Human Bone 이름들 (순서 중요 - 절대 변경 금지) +-- Unity Human Bone 이름들 (필수 본들만, 선택적 본들은 별도 처리) +global MXNames =#("Hips","LeftUpperLeg","LeftLowerLeg","LeftFoot","RightUpperLeg","RightLowerLeg", + "RightFoot","Spine","LeftUpperArm","LeftLowerArm","LeftHand","RightUpperArm","RightLowerArm","RightHand","Head") --- 믹사모 -> 바이패드 매핑 (순서 중요 - 절대 변경 금지) -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") +-- 선택적 Unity Human Bone 이름들 (없어도 변환 가능) +global OptionalNames =#("Chest","UpperChest","Neck","LeftShoulder","RightShoulder","LeftToes","RightToes", + "LeftIndexProximal","LeftIndexIntermediate","LeftIndexDistal","LeftMiddleProximal","LeftMiddleIntermediate", + "LeftMiddleDistal","LeftLittleProximal","LeftLittleIntermediate","LeftLittleDistal","LeftRingProximal", + "LeftRingIntermediate","LeftRingDistal","LeftThumbProximal","LeftThumbIntermediate","LeftThumbDistal", + "RightIndexProximal","RightIndexIntermediate","RightIndexDistal","RightMiddleProximal","RightMiddleIntermediate","RightMiddleDistal", + "RightLittleProximal","RightLittleIntermediate","RightLittleDistal","RightRingProximal","RightRingIntermediate", + "RightRingDistal","RightThumbProximal","RightThumbIntermediate","RightThumbDistal") + +-- Unity Human Bone -> 바이패드 매핑 (순서 중요 - 절대 변경 금지) +global MXPairs = #( + datapair "Hips" "Bip001 Pelvis", + datapair "Spine" "Bip001 Spine", + datapair "Chest" "Bip001 Spine1", + -- UpperChest는 선택적이므로 제거 (모든 아바타에 있지 않음) + datapair "Neck" "Bip001 Neck", + datapair "Head" "Bip001 Head", + + -- Right Arm + datapair "RightShoulder" "Bip001 R Clavicle", + datapair "RightUpperArm" "Bip001 R UpperArm", + datapair "RightLowerArm" "Bip001 R Forearm", + datapair "RightHand" "Bip001 R Hand", + + -- Right Hand Fingers + datapair "RightThumbProximal" "Bip001 R Finger0", + datapair "RightThumbIntermediate" "Bip001 R Finger01", + datapair "RightThumbDistal" "Bip001 R Finger02", + datapair "RightIndexProximal" "Bip001 R Finger1", + datapair "RightIndexIntermediate" "Bip001 R Finger11", + datapair "RightIndexDistal" "Bip001 R Finger12", + datapair "RightMiddleProximal" "Bip001 R Finger2", + datapair "RightMiddleIntermediate" "Bip001 R Finger21", + datapair "RightMiddleDistal" "Bip001 R Finger22", + datapair "RightRingProximal" "Bip001 R Finger3", + datapair "RightRingIntermediate" "Bip001 R Finger31", + datapair "RightRingDistal" "Bip001 R Finger32", + datapair "RightLittleProximal" "Bip001 R Finger4", + datapair "RightLittleIntermediate" "Bip001 R Finger41", + datapair "RightLittleDistal" "Bip001 R Finger42", + + -- Left Arm + datapair "LeftShoulder" "Bip001 L Clavicle", + datapair "LeftUpperArm" "Bip001 L UpperArm", + datapair "LeftLowerArm" "Bip001 L Forearm", + datapair "LeftHand" "Bip001 L Hand", + + -- Left Hand Fingers + datapair "LeftThumbProximal" "Bip001 L Finger0", + datapair "LeftThumbIntermediate" "Bip001 L Finger01", + datapair "LeftThumbDistal" "Bip001 L Finger02", + datapair "LeftIndexProximal" "Bip001 L Finger1", + datapair "LeftIndexIntermediate" "Bip001 L Finger11", + datapair "LeftIndexDistal" "Bip001 L Finger12", + datapair "LeftMiddleProximal" "Bip001 L Finger2", + datapair "LeftMiddleIntermediate" "Bip001 L Finger21", + datapair "LeftMiddleDistal" "Bip001 L Finger22", + datapair "LeftRingProximal" "Bip001 L Finger3", + datapair "LeftRingIntermediate" "Bip001 L Finger31", + datapair "LeftRingDistal" "Bip001 L Finger32", + datapair "LeftLittleProximal" "Bip001 L Finger4", + datapair "LeftLittleIntermediate" "Bip001 L Finger41", + datapair "LeftLittleDistal" "Bip001 L Finger42", + + -- Right Leg + datapair "RightUpperLeg" "Bip001 R Thigh", + datapair "RightLowerLeg" "Bip001 R Calf", + datapair "RightFoot" "Bip001 R Foot", + datapair "RightToes" "Bip001 R Toe0", + + -- Left Leg + datapair "LeftUpperLeg" "Bip001 L Thigh", + datapair "LeftLowerLeg" "Bip001 L Calf", + datapair "LeftFoot" "Bip001 L Foot", + datapair "LeftToes" "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") +-- Unity Human Bone에서는 End 본들이 기본적으로 없으므로 excludeBones는 불필요 +-- global excludeBones = #() +global excludeBones =#("HeadTop_End") -- 스파인 세그먼트 개수 global spineSegments = 2 @@ -230,60 +300,148 @@ struct FigureModeManager ( ) ) --- UI 최적화 관리자 -struct UIOptimizer ( +-- 슈퍼 UI 최적화 관리자 (더 강력한 억제) +struct SuperUIOptimizer ( wasEditing = false, wasRedrawing = false, + wasAnimating = false, + wasSelectionEvents = false, isActive = false, fn suspend =( /* - UI 업데이트 억제 - 잠재적 충돌: 다른 스크립트도 UI를 제어하는 경우 - 안전장치: 현재 상태 저장 및 복원 + 모든 UI 이벤트 완전 차단 (최대 성능) */ if not OPTIMIZE_UI_UPDATES do return true try ( if not isActive do ( + -- 기존 상태 저장 wasEditing = isSceneEditing() wasRedrawing = isSceneRedrawEnabled() + wasAnimating = animButtonState + -- 모든 업데이트 차단 suspendEditing() disableSceneRedraw() + animButtonState = false + + -- 선택 이벤트도 차단 + try ( + callbacks.removeScripts id:#selectionChanged + callbacks.removeScripts id:#nodeRenamed + callbacks.removeScripts id:#nodeCreated + ) catch () + isActive = true - if VERBOSE_LOGGING do format "🎨 UI 업데이트 억제\n" + if VERBOSE_LOGGING do format "⚡ 슈퍼 UI 차단 활성화\n" ) true ) catch ( - if VERBOSE_LOGGING do format "⚠️ UI 억제 실패\n" + if VERBOSE_LOGGING do format "⚠️ 슈퍼 UI 차단 실패\n" false ) ), fn resume =( /* - UI 업데이트 재개 - 안전장치: 원래 상태로만 복원 + 모든 UI 이벤트 복원 + 강제 정리 */ if not OPTIMIZE_UI_UPDATES do return true try ( if isActive do ( + -- 상태 복원 if wasRedrawing do enableSceneRedraw() if wasEditing do resumeEditing() + animButtonState = wasAnimating + + -- 강제 메모리 정리 + gc light:true + + -- 한번만 redraw (여러 번 호출 방지) redrawViews() + isActive = false - if VERBOSE_LOGGING do format "🎨 UI 업데이트 재개\n" + if VERBOSE_LOGGING do format "⚡ 슈퍼 UI 차단 해제 + 정리 완료\n" ) true ) catch ( - if VERBOSE_LOGGING do format "⚠️ UI 재개 실패\n" + if VERBOSE_LOGGING do format "⚠️ 슈퍼 UI 복원 실패\n" false ) ) ) +-- 메모리 및 성능 최적화 관리자 +struct MemoryOptimizer ( + fn deepCleanup =( + /* + 강력한 메모리 정리 + 3ds Max 성능 복원 + */ + try ( + format "🧹 메모리 및 성능 정리 시작...\n" + + -- 1. 가비지 컬렉션 (단계적 정리) + gc light:true + gc() + gc() -- 두 번 호출로 확실히 정리 + + -- 2. Undo 스택 정리 (메모리 해제) + clearUndoBuffer() + + -- 3. Selection 정리 + clearSelection() + + -- 4. 뷰포트 캐시 정리 + try ( + completeRedraw() + forceCompleteRedraw() + ) catch () + + -- 5. 임시 콜백 정리 (중복 방지) + try ( + callbacks.removeScripts id:#selectionChanged + callbacks.removeScripts id:#nodeRenamed + callbacks.removeScripts id:#nodeCreated + callbacks.removeScripts id:#nodeDeleted + ) catch () + + -- 6. 스킨 캐시 정리 (매우 중요!) + try ( + for obj in objects do ( + if obj.modifiers[#Skin] != undefined do ( + obj.modifiers[#Skin].always_deform = obj.modifiers[#Skin].always_deform + ) + ) + ) catch () + + -- 7. 뷰포트 정리 + try ( + actionMan.executeAction 0 "40021" -- Zoom Extents All + max views redraw + ) catch () + + format "✅ 메모리 정리 완료\n" + true + ) catch ( + format "⚠️ 메모리 정리 부분 실패: %\n" (getCurrentException()) + false + ) + ), + + fn quickCleanup =( + /* + 빠른 정리 (중간중간 호출) + */ + try ( + gc light:true + clearSelection() + true + ) catch (false) + ) +) + -- 진행률 표시 관리자 (안전 버전) struct ProgressManager ( totalSteps = 0, @@ -335,7 +493,7 @@ fn pointLineProj pA pB pC = ( -- 발 각도 계산 함수 (일반 바이패드 발 방향 사용으로 인해 비활성화) -- fn findFootAngle =( --- local foots = $'mixamorig:*Foot' as array +-- local foots = $'*Foot' as array -- if foots.count==0 do return 35 -- -- local posArr = for i in foots collect i.pos[3] @@ -469,111 +627,56 @@ fn collectLayerNodes ly out:#()=( ) fn CleanNames =( - local objArr = $mixamorig* - --Rules 1 + -- Unity Human Bone 객체들을 올바르게 수집 + local objArr = #() + + -- 각 패턴별로 객체들을 찾아서 배열에 추가 + local hipsObjs = $Hips* as array + local leftArmObjs = $LeftUpperArm* as array + local rightArmObjs = $RightUpperArm* as array + local spineObjs = $Spine* as array + + -- 모든 객체들을 하나의 배열로 합치기 + join objArr hipsObjs + join objArr leftArmObjs + join objArr rightArmObjs + join objArr spineObjs + + format "CleanNames: %개 객체 처리 시작\\n" objArr.count + + --Rules 1: 콜론으로 분리된 이름에서 접두사 제거 for i in objArr do( n = i.name filt = filterstring n ":" if filt.Count>1 do( - newName = "mixamorig:" + newName = "" for j=2 to filt.Count do ( newName += filt[j] if j!=filt.Count do newName+=":" ) i.name = newName + format "이름 정리: % → %\\n" n newName ) ) + format "CleanNames 완료\\n" ) 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 제거됨 - ) + -- Unity Human Bone 표준 이름 검증 + hips = getNodeByName "Hips" + if isValidNode hips then ( + -- Unity Human Bone 오브젝트들이 있는지 확인 + local unityBones = #("Hips", "Spine", "Chest", "Neck", "Head", "LeftUpperArm", "RightUpperArm", "LeftUpperLeg", "RightUpperLeg") + local foundBones = 0 + + for boneName in unityBones do ( + local bone = getNodeByName boneName + if isValidNode bone do foundBones += 1 ) - 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 + -- 최소 5개 이상의 주요 본이 있으면 유효한 것으로 간주 + return (foundBones >= 5) + ) else return false ) fn createBip height spine:2 fingers:5= ( @@ -587,24 +690,24 @@ fn createBip height spine:2 fingers:5= ( ) fn getRigStructures =( - local n = "mixamorig:" + local n = "" local root = execute ("$'"+n+"Hips'") -- 다리 본들 (부모 관계 확인) - local LLeg1 = execute ("$'"+n+"LeftUpLeg'") - local LLeg2 = execute ("$'"+n+"LeftLeg'") + local LLeg1 = execute ("$'"+n+"LeftUpperLeg'") + local LLeg2 = execute ("$'"+n+"LeftLowerLeg'") local LFoot = execute ("$'"+n+"LeftFoot'") - local LToe = execute ("$'"+n+"LeftToeBase'") + local LToe = execute ("$'"+n+"LeftToes'") - local RLeg1 = execute ("$'"+n+"RightUpLeg'") - local RLeg2 = execute ("$'"+n+"RightLeg'") + local RLeg1 = execute ("$'"+n+"RightUpperLeg'") + local RLeg2 = execute ("$'"+n+"RightLowerLeg'") local RFoot = execute ("$'"+n+"RightFoot'") - local RToe = execute ("$'"+n+"RightToeBase'") + local RToe = execute ("$'"+n+"RightToes'") -- 스파인 본들 local LSpine = execute ("$'"+n+"Spine'") - local LSpine1 = execute ("$'"+n+"Spine1'") + local LSpine1 = execute ("$'"+n+"Chest'") local spines = #(LSpine, LSpine1) spineSegments = 2 @@ -621,13 +724,13 @@ fn getRigStructures =( -- 어깨/팔 본들 local LClav = execute ("$'"+n+"LeftShoulder'") - local LArm1 = execute ("$'"+n+"LeftArm'") - local LArm2 = execute ("$'"+n+"LeftForeArm'") + local LArm1 = execute ("$'"+n+"LeftUpperArm'") + local LArm2 = execute ("$'"+n+"LeftLowerArm'") local LHand = execute ("$'"+n+"LeftHand'") local RClav = execute ("$'"+n+"RightShoulder'") - local RArm1 = execute ("$'"+n+"RightArm'") - local RArm2 = execute ("$'"+n+"RightForeArm'") + local RArm1 = execute ("$'"+n+"RightUpperArm'") + local RArm2 = execute ("$'"+n+"RightLowerArm'") local RHand = execute ("$'"+n+"RightHand'") -- 🔍 팔 구조 디버그 출력 @@ -669,17 +772,17 @@ fn getRigStructures =( ) ) - local LFinger1 = $'mixamorig:LeftHandThumb1' - local LFinger2 = $'mixamorig:LeftHandIndex1' - local LFinger3 = $'mixamorig:LeftHandMiddle1' - local LFinger4 = $'mixamorig:LeftHandRing1' - local LFinger5 = $'mixamorig:LeftHandPinky1' + local LFinger1 = $'LeftThumbProximal' + local LFinger2 = $'LeftIndexProximal' + local LFinger3 = $'LeftMiddleProximal' + local LFinger4 = $'LeftRingProximal' + local LFinger5 = $'LeftLittleProximal' - local RFinger1 = $'mixamorig:RightHandThumb1' - local RFinger2 = $'mixamorig:RightHandIndex1' - local RFinger3 = $'mixamorig:RightHandMiddle1' - local RFinger4 = $'mixamorig:RightHandRing1' - local RFinger5 = $'mixamorig:RightHandPinky1' + local RFinger1 = $'RightThumbProximal' + local RFinger2 = $'RightIndexProximal' + local RFinger3 = $'RightMiddleProximal' + local RFinger4 = $'RightRingProximal' + local RFinger5 = $'RightLittleProximal' -- 스켈레톤 구조에 따라 적응적으로 배치 if (legParentIsSpine and shoulderParentIsNeck) or (shoulderParentIsSpine) then ( @@ -841,9 +944,9 @@ fn alignFootTpose rigObj BipObj =( -- 이름으로 Toe 본 찾기 local toe = undefined if findString rigObj.name "LeftFoot" != undefined then ( - toe = execute ("$'mixamorig:LeftToeBase'") + toe = execute ("$'LeftToes'") ) else if findString rigObj.name "RightFoot" != undefined then ( - toe = execute ("$'mixamorig:RightToeBase'") + toe = execute ("$'RightToes'") ) -- 백업: 이름으로 못 찾으면 children[1] 사용 @@ -879,9 +982,9 @@ fn alignFoot rigObj BipObj figureMode:true =( -- 발 크기 계산 (단순화) local toe = undefined if findString rigObj.name "LeftFoot" != undefined then ( - toe = execute ("$'mixamorig:LeftToeBase'") + toe = execute ("$'LeftToes'") ) else if findString rigObj.name "RightFoot" != undefined then ( - toe = execute ("$'mixamorig:RightToeBase'") + toe = execute ("$'RightToes'") ) -- 백업: 이름으로 못 찾으면 children[1] 사용 @@ -928,11 +1031,11 @@ fn AlignArm BipUpperArm MxUpperArm side:"R" FigureMode:false TPose:false = local MxHand = undefined if side == "R" then ( - MxForeArm = execute ("$'mixamorig:RightForeArm'") - MxHand = execute ("$'mixamorig:RightHand'") + MxForeArm = execute ("$'RightLowerArm'") + MxHand = execute ("$'RightHand'") ) else ( - MxForeArm = execute ("$'mixamorig:LeftForeArm'") - MxHand = execute ("$'mixamorig:LeftHand'") + MxForeArm = execute ("$'LeftLowerArm'") + MxHand = execute ("$'LeftHand'") ) -- 백업: 이름으로 못 찾으면 children[1] 사용 @@ -996,16 +1099,16 @@ fn AlignLeg BipUpperLeg MxUpperLeg side:"R" FigureMode:false TPose:false = if BipLoweLeg == undefined do BipLoweLeg = BipUpperLeg.children[1] if BipFoot == undefined do BipFoot = BipLoweLeg.children[1] - -- Mixamo 본들을 이름으로 찾기 + -- Unity Human Bone들을 이름으로 찾기 local MxLowerLeg = undefined local MxFoot = undefined if side == "R" then ( - MxLowerLeg = execute ("$'mixamorig:RightLeg'") - MxFoot = execute ("$'mixamorig:RightFoot'") + MxLowerLeg = execute ("$'RightLowerLeg'") + MxFoot = execute ("$'RightFoot'") ) else ( - MxLowerLeg = execute ("$'mixamorig:LeftLeg'") - MxFoot = execute ("$'mixamorig:LeftFoot'") + MxLowerLeg = execute ("$'LeftLowerLeg'") + MxFoot = execute ("$'LeftFoot'") ) -- 백업: 이름으로 못 찾으면 children[1] 사용 @@ -1109,56 +1212,56 @@ fn alignFingers BipNode MXNode figureMode:false=( if bip3 == undefined and bip2 != undefined and bip2.children.count > 0 do bip3 = bip2.children[1] local BipNodes = #(BipNode,bip2,bip3) - -- Mixamo 손가락 본들을 이름으로 찾기 + -- Unity Human Bone 손가락 본들을 이름으로 찾기 local MX2 = undefined local MX3 = undefined local MX4 = undefined - -- Mixamo 손가락 이름 패턴에 따라 찾기 + -- Unity Human Bone 손가락 이름 패턴에 따라 찾기 if findString MXNode.name "Thumb" != undefined then ( -- 엄지손가락 if findString MXNode.name "Right" != undefined then ( - MX2 = execute ("$'mixamorig:RightHandThumb2'") - MX3 = execute ("$'mixamorig:RightHandThumb3'") + MX2 = execute ("$'RightThumbIntermediate'") + MX3 = execute ("$'RightThumbDistal'") ) else ( - MX2 = execute ("$'mixamorig:LeftHandThumb2'") - MX3 = execute ("$'mixamorig:LeftHandThumb3'") + MX2 = execute ("$'LeftThumbIntermediate'") + MX3 = execute ("$'LeftThumbDistal'") ) ) else if findString MXNode.name "Index" != undefined then ( -- 검지손가락 if findString MXNode.name "Right" != undefined then ( - MX2 = execute ("$'mixamorig:RightHandIndex2'") - MX3 = execute ("$'mixamorig:RightHandIndex3'") + MX2 = execute ("$'RightIndexIntermediate'") + MX3 = execute ("$'RightIndexDistal'") ) else ( - MX2 = execute ("$'mixamorig:LeftHandIndex2'") - MX3 = execute ("$'mixamorig:LeftHandIndex3'") + MX2 = execute ("$'LeftIndexIntermediate'") + MX3 = execute ("$'LeftIndexDistal'") ) ) else if findString MXNode.name "Middle" != undefined then ( -- 중지손가락 if findString MXNode.name "Right" != undefined then ( - MX2 = execute ("$'mixamorig:RightHandMiddle2'") - MX3 = execute ("$'mixamorig:RightHandMiddle3'") + MX2 = execute ("$'RightMiddleIntermediate'") + MX3 = execute ("$'RightMiddleDistal'") ) else ( - MX2 = execute ("$'mixamorig:LeftHandMiddle2'") - MX3 = execute ("$'mixamorig:LeftHandMiddle3'") + MX2 = execute ("$'LeftMiddleIntermediate'") + MX3 = execute ("$'LeftMiddleDistal'") ) ) else if findString MXNode.name "Ring" != undefined then ( -- 약지손가락 if findString MXNode.name "Right" != undefined then ( - MX2 = execute ("$'mixamorig:RightHandRing2'") - MX3 = execute ("$'mixamorig:RightHandRing3'") + MX2 = execute ("$'RightRingIntermediate'") + MX3 = execute ("$'RightRingDistal'") ) else ( - MX2 = execute ("$'mixamorig:LeftHandRing2'") - MX3 = execute ("$'mixamorig:LeftHandRing3'") + MX2 = execute ("$'LeftRingIntermediate'") + MX3 = execute ("$'LeftRingDistal'") ) ) else if findString MXNode.name "Pinky" != undefined then ( -- 새끼손가락 if findString MXNode.name "Right" != undefined then ( - MX2 = execute ("$'mixamorig:RightHandPinky2'") - MX3 = execute ("$'mixamorig:RightHandPinky3'") + MX2 = execute ("$'RightLittleIntermediate'") + MX3 = execute ("$'RightLittleDistal'") ) else ( - MX2 = execute ("$'mixamorig:LeftHandPinky2'") - MX3 = execute ("$'mixamorig:LeftHandPinky3'") + MX2 = execute ("$'LeftLittleIntermediate'") + MX3 = execute ("$'LeftLittleDistal'") ) ) @@ -1292,7 +1395,17 @@ fn BipToRigProportion bipNodes rigNodes TPose:false =( stepPerf "비율 매칭 시작" + if bipNodes.count == 0 do ( + format "❌ Biped 노드가 없음!\n" + return false + ) + local bipctl = bipNodes[1].transform.controller + if bipctl == undefined do ( + format "❌ Biped 컨트롤러를 찾을 수 없음!\n" + return false + ) + local figManager = FigureModeManager bipCtl:bipctl local progress = ProgressManager() @@ -1300,18 +1413,37 @@ fn BipToRigProportion bipNodes rigNodes TPose:false =( progress.init bipNodes.count -- Figure Mode 안전하게 진입 + format "🔧 Figure Mode 진입 시도...\n" if not figManager.enter() do ( format "❌ Figure Mode 진입 실패!\n" + format " - Biped Controller: %\n" bipctl + format " - Figure Mode 상태: %\n" (try (bipctl.figuremode) catch ("접근 불가")) return false ) + format "✅ Figure Mode 진입 성공\n" try ( local matchIndexes = #{1..(bipNodes.count)} - - - Lthigh = $'mixamorig:LeftUpLeg' - Rthigh = $'mixamorig:RightUpLeg' - p = (Lthigh.pos+Rthigh.pos)*0.5 + + format "\\n=== 비율 매칭 디버그 ===\\n" + + -- Unity Human Bone 참조 본들 확인 + local Lthigh = getNodeByName "LeftUpperLeg" + local Rthigh = getNodeByName "RightUpperLeg" + + if not isValidNode Lthigh do ( + format "❌ LeftUpperLeg를 찾을 수 없음\\n" + figManager.exit() + return false + ) + if not isValidNode Rthigh do ( + format "❌ RightUpperLeg를 찾을 수 없음\\n" + figManager.exit() + return false + ) + + local p = (Lthigh.pos + Rthigh.pos) * 0.5 + format "기준 위치 계산: %\\n" p @@ -1332,7 +1464,7 @@ fn BipToRigProportion bipNodes rigNodes TPose:false =( ) (findString n "Head"!= undefined): ( -- Head의 자식은 Neck이므로 이름으로 찾기 - rigNodeEnd = execute ("$'mixamorig:Neck'") + rigNodeEnd = execute ("$'Neck'") if isvalidNode rigNodeEnd do( -- Head: 크기와 위치만 조정, 축 변환 제거 dist = distance rigNode rigNodeEnd @@ -1361,8 +1493,8 @@ fn BipToRigProportion bipNodes rigNodes TPose:false =( ) (findString n "Pelvis"!= undefined ):( - rigNode = $'mixamorig:LeftUpLeg' - rigNodeEnd = $'mixamorig:RightUpLeg' + rigNode = $'LeftUpperLeg' + rigNodeEnd = $'RightUpperLeg' dist = distance Lthigh Rthigh len = distance rigNode rigNodeEnd biped.setTransform bipNode #scale [dist,dist,dist] false @@ -1372,7 +1504,7 @@ fn BipToRigProportion bipNodes rigNodes TPose:false =( (MatchPattern n pattern:"*Spine*"):( -- 스파인 2개 구조에서 Spine1이 마지막 스파인 if findString n "Spine1"!=undefined do ( - rigNodeEnd =$'mixamorig:Neck' + rigNodeEnd =$'Neck' ) if isvalidNode rigNode and isvalidNode rigNodeEnd do( len = distance rigNode rigNodeEnd @@ -1389,9 +1521,9 @@ fn BipToRigProportion bipNodes rigNodes TPose:false =( -- 정확한 Arm 본 찾기 (children[1] 대신) rigNodeEnd = undefined if findString n "L Clavicle" != undefined or findString n "LeftShoulder" != undefined then ( - rigNodeEnd = execute ("$'mixamorig:LeftArm'") + rigNodeEnd = execute ("$'LeftUpperArm'") ) else if findString n "R Clavicle" != undefined or findString n "RightShoulder" != undefined then ( - rigNodeEnd = execute ("$'mixamorig:RightArm'") + rigNodeEnd = execute ("$'RightUpperArm'") ) -- 백업: 이름으로 못 찾으면 children[1] 사용 @@ -1436,12 +1568,12 @@ fn BipToRigProportion bipNodes rigNodes TPose:false =( ) fn getNumFingers = ( - -- Mixamo 표준 손가락 본의 정확한 이름들 (첫 번째 관절만) + -- Unity Human Bone 표준 손가락 본의 정확한 이름들 (첫 번째 관절만) local fingerNames = #( - "mixamorig:LeftHandThumb1", "mixamorig:LeftHandIndex1", "mixamorig:LeftHandMiddle1", - "mixamorig:LeftHandRing1", "mixamorig:LeftHandPinky1", - "mixamorig:RightHandThumb1", "mixamorig:RightHandIndex1", "mixamorig:RightHandMiddle1", - "mixamorig:RightHandRing1", "mixamorig:RightHandPinky1" + "LeftThumbProximal", "LeftIndexProximal", "LeftMiddleProximal", + "LeftRingProximal", "LeftLittleProximal", + "RightThumbProximal", "RightIndexProximal", "RightMiddleProximal", + "RightRingProximal", "RightLittleProximal" ) local leftFingers = 0 @@ -1473,14 +1605,36 @@ fn getNumFingers = ( fn GetExtras root=( local out = #() if root!=undefined do( - hrc = getBranchHRC root + format "GetExtras: Root 노드 처리 시작 - %\\n" root.name + + local hrc = undefined + try ( + hrc = getBranchHRC root + format "GetExtras: getBranchHRC 성공 - %개 레벨\\n" hrc.count + ) catch ( + format "❌ GetExtras: getBranchHRC 실패\\n" + return #() + ) + + local processedLevels = 0 for h=hrc.count to 1 by -1 do ( local objArr = hrc[h] + local removedCount = 0 + for j=objArr.count to 1 by -1 do( - if (finditem MXNames objArr[j].name)!=0 do deleteitem objArr j -- REMOVE STANDARD FROM EXTRAS + -- Unity Human Bone 표준 본과 선택적 본 모두 체크 + local isStandardBone = (finditem MXNames objArr[j].name != 0) or (finditem OptionalNames objArr[j].name != 0) + if isStandardBone do ( + deleteitem objArr j -- REMOVE STANDARD FROM EXTRAS + removedCount += 1 + ) ) if objArr.count==0 do deleteItem hrc h + + processedLevels += 1 + if mod processedLevels 10 == 0 do format "GetExtras: %개 레벨 처리됨\\n" processedLevels ) + format "GetExtras: 처리 완료 - %개 Extra 그룹\\n" hrc.count out = hrc ) out @@ -1497,20 +1651,20 @@ fn cleanUpSkin obj =( ) ) if IDList.count>0 do for i=IDList.count to 1 by -1 do( - skinOps.removebone skn IDList[i] + skinOps.removeBone skn IDList[i] ) ) fn GetSkinPairs obj extra:#() =( local out=#() - noPairs = #() + local 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 "" + local foundPairs = true + local n = (skinOps.GetBoneName skn i 0) + local dp = datapair n "" case of( (matchpattern n pattern:"*Hips"): dp.v2 = "Bip001 Pelvis" (matchpattern n pattern:"*Spine"): dp.v2 = "Bip001 Spine" @@ -1529,7 +1683,7 @@ fn GetSkinPairs obj extra:#() =( (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:"*LeftHandIndex4"): dp.v2 = "Bip001 L Finger1Nub" -- Unity Human Bone에 없음 (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" @@ -1553,7 +1707,7 @@ fn GetSkinPairs obj extra:#() =( (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:"*RightHandIndex4"): dp.v2 = "Bip001 R Finger1Nub" -- Unity Human Bone에 없음 (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" @@ -1577,7 +1731,7 @@ fn GetSkinPairs obj extra:#() =( default:(foundPairs = false ) ) - indexInExtra = findItem extraV1 n + local indexInExtra = findItem extraV1 n if not foundPairs and indexInExtra != 0 then( dp.v2 = extra[indexInExtra].v2 ) @@ -1589,13 +1743,36 @@ fn GetSkinPairs obj extra:#() =( -- ================================== 최적화된 스킨 처리 함수들 =============================================== +-- 본 ID 매핑 찾기 함수 (전역) +fn findBoneIDByMapping oldBoneName bonePairs newSkin =( + /* + 본 이름 매핑을 통해 새 스킨에서 본 ID 찾기 + */ + try ( + for pair in bonePairs do ( + if pair.v1 == oldBoneName do ( + local newBoneName = pair.v2 + local numBones = skinOps.GetNumberBones newSkin + for b = 1 to numBones do ( + if (skinOps.GetBoneName newSkin b 0) == newBoneName do + return b + ) + exit + ) + ) + return 0 + ) catch ( + return 0 + ) +) + -- 메모리 기반 스킨 가중치 전송 (파일 I/O 제거) struct OptimizedSkinTransfer ( oldSkin, newSkin, bonePairs, - fn findBoneIDByMapping oldBoneName bonePairs newSkin =( + fn findBoneIDByMappingOld oldBoneName bonePairs newSkin =( /* 본 매핑을 통해 새 스킨에서 본 ID 찾기 잠재적 충돌: 본 이름이 일치하지 않는 경우 @@ -1604,9 +1781,9 @@ struct OptimizedSkinTransfer ( for pair in bonePairs do ( if pair.v1 == oldBoneName do ( local newBoneName = pair.v2 - local numBones = skinOps.getNumberBones newSkin + local numBones = skinOps.GetNumberBones newSkin for b = 1 to numBones do ( - if (skinOps.getBoneName newSkin b 0) == newBoneName do + if (skinOps.GetBoneName newSkin b 0) == newBoneName do return b ) exit @@ -1635,7 +1812,7 @@ struct OptimizedSkinTransfer ( for b = 1 to numBones do ( local weight = skinOps.getVertexWeight oldSkin v b local boneID = skinOps.getVertexWeightBoneID oldSkin v b - local oldBoneName = skinOps.getBoneName oldSkin boneID 0 + local oldBoneName = skinOps.GetBoneName oldSkin boneID 0 -- 새 본 ID 찾기 local newBoneID = findBoneIDByMapping oldBoneName bonePairs newSkin @@ -1666,6 +1843,84 @@ struct OptimizedSkinTransfer ( ) ) +-- 슈퍼 고속 스킨 처리 (배치 최적화) +fn ultraFastSkinTransfer oldSkin newSkin bonePairs =( + /* + 전체 가중치 데이터를 배치로 처리하는 초고속 버전 + 메모리 사용량 증가하지만 10-20배 더 빠름 + */ + try ( + local numVerts = skinOps.getNumberVertices oldSkin + if numVerts <= 0 do return false + + -- 본 매핑 테이블 미리 생성 (빠른 조회) + local boneMap = #() + local numOldBones = skinOps.GetNumberBones oldSkin + for i = 1 to numOldBones do ( + local oldName = skinOps.GetBoneName oldSkin i 0 + local newID = findBoneIDByMapping oldName bonePairs newSkin + append boneMap newID + ) + + -- 전체 정점 배치 처리 + local vertexData = #() + for v = 1 to numVerts do ( + local numBones = skinOps.getVertexWeightCount oldSkin v + local weights = #() + local boneIDs = #() + + for b = 1 to numBones do ( + local weight = skinOps.getVertexWeight oldSkin v b + local oldBoneID = skinOps.getVertexWeightBoneID oldSkin v b + + if oldBoneID <= boneMap.count then ( + local newBoneID = boneMap[oldBoneID] + if newBoneID > 0 and weight > 0.001 do ( + append weights weight + append boneIDs newBoneID + ) + ) + ) + + -- 가중치 정규화 + if weights.count > 0 then ( + local totalWeight = 0 + for w in weights do totalWeight += w + if totalWeight > 0.001 do ( + for i = 1 to weights.count do weights[i] /= totalWeight + append vertexData (datapair v (datapair boneIDs weights)) + ) + ) + ) + + -- 배치로 모든 정점에 가중치 적용 (한번에 처리) + if vertexData.count > 0 then ( + -- UI 업데이트 완전 차단 + local wasEditing = isSceneEditing() + local wasRedraw = isSceneRedrawEnabled() + if wasEditing do suspendEditing() + if wasRedraw do disableSceneRedraw() + + -- 배치 적용 + for vData in vertexData do ( + skinOps.replaceVertexWeights newSkin vData.v1 vData.v2.v1 vData.v2.v2 + ) + + -- UI 복원 + if wasEditing do resumeEditing() + if wasRedraw do enableSceneRedraw() + + format "⚡ 초고속 배치 처리 완료: %개 정점\n" vertexData.count + true + ) else ( + false + ) + ) catch ( + format "❌ 초고속 스킨 처리 실패: %\n" (getCurrentException()) + false + ) +) + -- 최적화된 스킨 처리 관리자 fn processSkinnedObjectsOptimized skinObjects extrasPairs =( /* @@ -1674,8 +1929,8 @@ fn processSkinnedObjectsOptimized skinObjects extrasPairs =( 안전장치: 메모리 기반 실패 시 기존 방식으로 폴백 */ - local uiOpt = UIOptimizer() - uiOpt.suspend() -- UI 업데이트 억제 + local uiOpt = SuperUIOptimizer() + uiOpt.suspend() -- 슈퍼 UI 업데이트 억제 try ( local progress = ProgressManager() @@ -1690,39 +1945,7 @@ fn processSkinnedObjectsOptimized skinObjects extrasPairs =( -- 페어 가져오기 (표준 본 + 엑스트라 본) local pairs = GetSkinPairs i extra:extrasPairs - if OPTIMIZE_SKIN_TRANSFER then ( - -- 최적화된 방식: 메모리 기반 전송 - local bipedSkin = #() - for j in pairs do ( - local obj1 = getNodeByName j.v1 - local obj2 = getNodeByName j.v2 - if isvalidNode obj1 and isValidNode obj2 do - append bipedSkin obj2 - ) - - -- 새 스킨 생성 - local skn2 = skin() - addmodifier i skn2 - - -- 본들 추가 (UI 업데이트 없이) - for j in bipedSkin do ( - if j != bipedSkin[bipedSkin.count] then - skinOps.AddBone skn2 j 0 - else - skinOps.AddBone skn2 j -1 - ) - - -- 메모리 기반 가중치 전송 - local skinTransfer = OptimizedSkinTransfer oldSkin:skn newSkin:skn2 bonePairs:pairs - if not skinTransfer.transferWeights() do ( - if VERBOSE_LOGGING do format "⚠️ 메모리 전송 실패, 기존 방식 사용: %\n" i.name - -- 기존 방식으로 폴백 - processSkinnedObjectClassic i pairs - ) - ) else ( - -- 기존 방식 사용 - processSkinnedObjectClassic i pairs - ) + processSkinnedObjectStable i pairs -- 모디파이어 스택 정리 maxOps.CollapseNodeTo i 2 true @@ -1735,10 +1958,10 @@ fn processSkinnedObjectsOptimized skinObjects extrasPairs =( ) ) --- 기존 방식 스킨 처리 (폴백용) -fn processSkinnedObjectClassic skinObj pairs =( +-- 안정적인 파일 I/O 방식 스킨 처리 (copy 버전과 동일) +fn processSkinnedObjectStable skinObj pairs =( /* - 기존 파일 I/O 방식의 스킨 처리 (폴백) + copy 버전과 정확히 동일한 파일 I/O 방식의 스킨 처리 안전하지만 느림 */ local skn = skinObj.modifiers["skin"] @@ -1769,7 +1992,7 @@ fn processSkinnedObjectClassic skinObj pairs =( max modify mode modPanel.setCurrentObject skn2 - -- 본들 추가 + -- 본들 추가 (copy 버전과 동일한 방식) for j in bipedSkin do( if j!= bipedSkin[bipedSkin.count] then skinOps.AddBone skn2 j 0 else skinOps.AddBone skn2 j -1 @@ -1784,7 +2007,7 @@ fn processSkinnedObjectClassic skinObj pairs =( for j in pairs do ( local obj1 = getNodeByName j.v1 local obj2 = getNodeByName j.v2 - if isvalidNode obj1 and isValidNode obj2 do( + if isValidNode obj1 and isValidNode obj2 do( tempName = obj1.name obj1.name = obj2.name obj2.name = tempName @@ -1795,6 +2018,261 @@ fn processSkinnedObjectClassic skinObj pairs =( if doesFileExist f do deleteFile f ) +-- 기존 방식 스킨 처리 (폴백용) +fn processSkinnedObjectClassic skinObj pairs =( + /* + 스킨 처리 + Body_2 특별 디버깅 + */ + format "🔍 DEBUG: processSkinnedObjectClassic 함수 시작 - 오브젝트: %\\n" skinObj.name + + format "🔍 DEBUG: 스킨 모디파이어 찾는 중...\\n" + local skn = skinObj.modifiers["skin"] + if skn == undefined do ( + format "❌ % - 스킨 모디파이어 없음\\n" skinObj.name + return false + ) + format "🔍 DEBUG: 스킨 모디파이어 발견: %\\n" skn + + local isBody2 = (findString skinObj.name "Body_2" != undefined) + if isBody2 do format "🔍 Body_2 디버깅 시작\\n" + + -- 스킨 상태 점검 + format "🔍 DEBUG: skinOps.GetNumberBones 호출 중...\\n" + local totalBones = skinOps.GetNumberBones skn + format "🔍 DEBUG: GetNumberBones 성공, 결과: %\\n" totalBones + + format "🔍 DEBUG: skinOps.getNumberVertices 호출 중...\\n" + local vertCount = skinOps.getNumberVertices skn + format "🔍 DEBUG: getNumberVertices 성공, 결과: %\\n" vertCount + + format "메시: % | 본 수: % | 버텍스 수: %\\n" skinObj.name totalBones vertCount + + -- Body_2 간단 분석 (속도 우선) + if isBody2 do format "Body_2: %개 본, %개 버텍스\\n" totalBones vertCount + + -- ⚡ 본 교체 + Body_2 특별 처리 + local startTime = timestamp() + local replacedCount = 0 + local failedBones = #() + + -- 한국어 접미사 패턴들 (Biped 매핑이 불가능한 엑스트라 본들) + local koreanSuffixes = #("_오픈숄더", "_양갈래", "_헤드", "_스웨터", "_장갑", "_신발", "_바지", "_치마", "_모자", "_안경", "_머리띠", "_목걸이", "_귀걸이", "_팔찌") + + -- 더 안전한 본 교체 방식 (Biped 본 검색 개선) + for j in pairs do ( + -- 한국어 접미사가 있는 본은 건너뜀 (Biped에 대응 불가) + local hasKoreanSuffix = false + for suffix in koreanSuffixes do ( + if findString j.v1 suffix != undefined then ( + hasKoreanSuffix = true + format "⏩ 한국어 접미사 본 스킵: %\\n" j.v1 + exit + ) + ) + if hasKoreanSuffix then continue + -- Biped 본 찾기: 여러 방법 시도 + local bipedBone = undefined + local searchMethod = "" + try ( + -- 방법 1: 정확한 이름으로 검색 + bipedBone = getNodeByName j.v2 + if isValidNode bipedBone then ( + searchMethod = "getNodeByName" + ) else ( + -- 방법 2: execute 사용 + local executeStr = "$'" + j.v2 + "'" + bipedBone = execute executeStr + if isValidNode bipedBone then ( + searchMethod = "execute" + ) else ( + -- 방법 3: 씬에서 패턴 매칭으로 검색 + for obj in objects where (matchPattern obj.name pattern:("*" + j.v2 + "*")) do ( + if obj.name == j.v2 then ( + bipedBone = obj + searchMethod = "pattern" + exit + ) + ) + ) + ) + ) catch () + + if isValidNode bipedBone then ( + format "🔍 Biped 본 발견: % (방법: %)\\n" j.v2 searchMethod + -- 먼저 Biped 본을 스킨에 추가 (없으면 추가) + try ( + skinOps.addBone skn bipedBone 0 + ) catch () + + -- Unity Human Bone을 Biped 본으로 교체 + try ( + -- 본 이름으로 본 ID를 찾는 올바른 방법 + local unityBoneID = 0 + local numBones = skinOps.GetNumberBones skn + for i = 1 to numBones do ( + if (skinOps.GetBoneName skn i 0) == j.v1 then ( + unityBoneID = i + exit + ) + ) + if unityBoneID > 0 then ( + -- ⚡ 초고속 방법: replaceBone 재시도 (더 간단) + try ( + skinOps.replaceBone skn unityBoneID bipedBone + replacedCount += 1 + format "✓ 고속 교체: % → %\\n" j.v1 j.v2 + ) catch ( + -- replaceBone 실패 시에만 수동 처리 + local bipedBoneID = 0 + local numBones2 = skinOps.GetNumberBones skn + for k = 1 to numBones2 do ( + if (skinOps.GetBoneName skn k 0) == bipedBone.name then ( + bipedBoneID = k + exit + ) + ) + if bipedBoneID > 0 then ( + -- 간단한 본 ID 재매핑만 (가중치는 건드리지 않음) + try ( + skinOps.removeBone skn unityBoneID + replacedCount += 1 + format "✓ 본 제거만: %\\n" j.v1 + ) catch () + ) + ) + ) else ( + format "⚠️ Unity 본 '%' ID 없음\\n" j.v1 + ) + ) catch ( + append failedBones j.v1 + format "❌ 교체 실패: % → %\\n" j.v1 j.v2 + ) + ) else ( + format "❌ Biped 본 찾기 실패: '%' → '%' (모든 검색 방법 실패)\\n" j.v1 j.v2 + append failedBones j.v1 + ) + ) + + local elapsed = (timestamp() - startTime) / 1000.0 + format "⚡ %개 본 교체 완료 (%.2f초)\\n" replacedCount elapsed + + if failedBones.count > 0 do ( + format "실패한 본들: %\\n" failedBones + ) + + -- Body_2 간단 검증 (속도 우선) + if isBody2 do ( + local finalBones = skinOps.GetNumberBones skn + local bipedCount = 0 + for i = 1 to finalBones do ( + local boneName = skinOps.GetBoneName skn i 0 + if findString boneName "Bip001" != undefined do bipedCount += 1 + ) + format "Body_2 결과: %개 Biped / %개 전체 본\\n" bipedCount finalBones + ) + + true +) + +-- Body_2 메시 문제 해결 전용 함수 +fn fixBody2Mesh =( + format "\\n🔧 Body_2 메시 문제 해결 시도...\\n" + local body2 = getNodeByName "Body_2" + if not isValidNode body2 do ( + format "❌ Body_2 메시를 찾을 수 없습니다.\\n" + return false + ) + + local skn = body2.modifiers[#Skin] + if skn == undefined do ( + format "❌ Body_2에 스킨 모디파이어가 없습니다.\\n" + return false + ) + + format "✓ Body_2 발견, 스킨 모디파이어 확인됨\\n" + + -- Unity Human Bone들을 수동으로 Biped로 강제 교체 + local unityToBiped = #( + #("Hips", "Bip001 Pelvis"), + #("Spine", "Bip001 Spine"), + #("Chest", "Bip001 Spine1"), + #("LeftUpperArm", "Bip001 L UpperArm"), + #("LeftLowerArm", "Bip001 L Forearm"), + #("LeftHand", "Bip001 L Hand"), + #("RightUpperArm", "Bip001 R UpperArm"), + #("RightLowerArm", "Bip001 R Forearm"), + #("RightHand", "Bip001 R Hand"), + #("LeftUpperLeg", "Bip001 L Thigh"), + #("LeftLowerLeg", "Bip001 L Calf"), + #("LeftFoot", "Bip001 L Foot"), + #("RightUpperLeg", "Bip001 R Thigh"), + #("RightLowerLeg", "Bip001 R Calf"), + #("RightFoot", "Bip001 R Foot"), + #("Head", "Bip001 Head"), + #("Neck", "Bip001 Neck") + ) + + local successCount = 0 + for pair in unityToBiped do ( + local unityName = pair[1] + local bipedName = pair[2] + + -- Body_2용 향상된 Biped 본 검색 + local bipedBone = undefined + try ( + -- 방법 1: 정확한 이름으로 검색 + bipedBone = getNodeByName bipedName + if not isValidNode bipedBone then ( + -- 방법 2: execute 사용 + bipedBone = execute ("$'" + bipedName + "'") + ) + if not isValidNode bipedBone then ( + -- 방법 3: 씬에서 검색 + for obj in objects where (matchPattern obj.name pattern:("*" + bipedName + "*")) do ( + if obj.name == bipedName then ( + bipedBone = obj + exit + ) + ) + ) + ) catch () + + if isValidNode bipedBone then ( + try ( + -- 본 이름으로 본 ID 찾기 + local unityID = 0 + local numBones = skinOps.GetNumberBones skn + for i = 1 to numBones do ( + if (skinOps.GetBoneName skn i 0) == unityName then ( + unityID = i + exit + ) + ) + if unityID > 0 then ( + skinOps.replaceBone skn unityID bipedBone + successCount += 1 + format "✓ Body_2 강제 교체: % → %\\n" unityName bipedName + ) + ) catch ( + format "⚠️ Body_2 교체 실패: % → %\\n" unityName bipedName + ) + ) + ) + + format "🔧 Body_2 본 교체 완료: %개 성공\\n" successCount + + -- 최종 확인 + local totalBones = skinOps.GetNumberBones skn + local bipedCount = 0 + for i = 1 to totalBones do ( + local boneName = skinOps.GetBoneName skn i 0 + if findString boneName "Bip001" != undefined do bipedCount += 1 + ) + format "✓ Body_2 최종 상태: %개 Biped 본 / %개 전체 본\\n" bipedCount totalBones + + true +) + -- ================================== 모퍼 처리 함수들 =============================================== struct MorpherChannelData ( @@ -1928,85 +2406,85 @@ fn RestoreMorpherData backupData pairsList =( fn GetSkinPairs obj extra:#() =( local out=#() - noPairs = #() + local 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 "" + local foundPairs = true + local n = (skinOps.GetBoneName skn i 0) + local 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:"*Spine" and not matchpattern n pattern:"*Spine1"): dp.v2 = "Bip001 Spine" + (matchpattern n pattern:"*Chest"): 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:"*LeftUpperArm"): dp.v2 = "Bip001 L UpperArm" + (matchpattern n pattern:"*LeftLowerArm"): 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:"*LeftThumbProximal"): dp.v2 = "Bip001 L Finger0" + (matchpattern n pattern:"*LeftThumbIntermediate"): dp.v2 = "Bip001 L Finger01" + (matchpattern n pattern:"*LeftThumbDistal"): 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" + (matchpattern n pattern:"*LeftIndexProximal"): dp.v2 = "Bip001 L Finger1" + (matchpattern n pattern:"*LeftIndexIntermediate"): dp.v2 = "Bip001 L Finger11" + (matchpattern n pattern:"*LeftIndexDistal"): dp.v2 = "Bip001 L Finger12" + -- (matchpattern n pattern:"*LeftIndexDistal"): dp.v2 = "Bip001 L Finger1Nub" -- Unity Human Bone에 Index4 없음 + (matchpattern n pattern:"*LeftMiddleProximal"): dp.v2 = "Bip001 L Finger2" + (matchpattern n pattern:"*LeftMiddleIntermediate"): dp.v2 = "Bip001 L Finger21" + (matchpattern n pattern:"*LeftMiddleDistal"): 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" + (matchpattern n pattern:"*LeftRingProximal"): dp.v2 = "Bip001 L Finger3" + (matchpattern n pattern:"*LeftRingIntermediate"): dp.v2 = "Bip001 L Finger31" + (matchpattern n pattern:"*LeftRingDistal"): 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" + (matchpattern n pattern:"*LeftLittleProximal"): dp.v2 = "Bip001 L Finger4" + (matchpattern n pattern:"*LeftLittleIntermediate"): dp.v2 = "Bip001 L Finger41" + (matchpattern n pattern:"*LeftLittleDistal"): 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:"*RightUpperArm"): dp.v2 = "Bip001 R UpperArm" + (matchpattern n pattern:"*RightLowerArm"): 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:"*RightThumbProximal"): dp.v2 = "Bip001 R Finger0" + (matchpattern n pattern:"*RightThumbIntermediate"): dp.v2 = "Bip001 R Finger01" + (matchpattern n pattern:"*RightThumbDistal"): 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" + (matchpattern n pattern:"*RightIndexProximal"): dp.v2 = "Bip001 R Finger1" + (matchpattern n pattern:"*RightIndexIntermediate"): dp.v2 = "Bip001 R Finger11" + (matchpattern n pattern:"*RightIndexDistal"): dp.v2 = "Bip001 R Finger12" + -- (matchpattern n pattern:"*RightIndexDistal"): dp.v2 = "Bip001 R Finger1Nub" -- Unity Human Bone에 Index4 없음 + (matchpattern n pattern:"*RightMiddleProximal"): dp.v2 = "Bip001 R Finger2" + (matchpattern n pattern:"*RightMiddleIntermediate"): dp.v2 = "Bip001 R Finger21" + (matchpattern n pattern:"*RightMiddleDistal"): 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" + (matchpattern n pattern:"*RightRingProximal"): dp.v2 = "Bip001 R Finger3" + (matchpattern n pattern:"*RightRingIntermediate"): dp.v2 = "Bip001 R Finger31" + (matchpattern n pattern:"*RightRingDistal"): 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" + (matchpattern n pattern:"*RightLittleProximal"): dp.v2 = "Bip001 R Finger4" + (matchpattern n pattern:"*RightLittleIntermediate"): dp.v2 = "Bip001 R Finger41" + (matchpattern n pattern:"*RightLittleDistal"): 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:"*LeftUpperLeg"): dp.v2 = "Bip001 L Thigh" + (matchpattern n pattern:"*LeftLowerLeg"): 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:"*LeftToes"): dp.v2 = "Bip001 L Toe0" + (matchpattern n pattern:"*RightUpperLeg"): dp.v2 = "Bip001 R Thigh" + (matchpattern n pattern:"*RightLowerLeg"): dp.v2 = "Bip001 R Calf" (matchpattern n pattern:"*RightFoot"): dp.v2 = "Bip001 R Foot" - (matchpattern n pattern:"*RightToeBase"): dp.v2 = "Bip001 R Toe0" + (matchpattern n pattern:"*RightToes"): dp.v2 = "Bip001 R Toe0" default:(foundPairs = false ) ) - indexInExtra = findItem extraV1 n + local indexInExtra = findItem extraV1 n if not foundPairs and indexInExtra != 0 then( dp.v2 = extra[indexInExtra].v2 - ) - else foundPairs = false + foundPairs = true + ) append out dp if not foundPairs do append noPairs n @@ -2035,7 +2513,7 @@ global STEP_DATA = undefined -- ================================== 1단계: Biped 생성 및 기본 설정 =============================================== -fn ConvertMixamoToBiped_Step1 charName:"Character001" alwaysDeform:false =( +fn ConvertUnityHumanToBiped_Step1 charName:"Character001" alwaysDeform:false =( /* 1단계: Biped 생성 및 기본 설정 (빠른 단계) @@ -2056,12 +2534,27 @@ fn ConvertMixamoToBiped_Step1 charName:"Character001" alwaysDeform:false =( -- 단계 데이터 초기화 STEP_DATA = StepData charName:charName alwaysDeform:alwaysDeform + -- 디버깅: 씬에 있는 모든 본 이름 출력 + format "\n=== 씬에 있는 모든 객체 디버깅 ===\n" + local allObjects = objects as array + local boneObjects = #() + for obj in allObjects do ( + if (classOf obj == BoneGeometry) or (findString obj.name "Hips" != undefined) or + (findString obj.name "Spine" != undefined) or (findString obj.name "Arm" != undefined) or + (findString obj.name "Leg" != undefined) or (findString obj.name "Hand" != undefined) or + (findString obj.name "Foot" != undefined) or (findString obj.name "Head" != undefined) then ( + append boneObjects obj + format "발견된 본: % (클래스: %)\n" obj.name (classOf obj) + ) + ) + format "총 %개의 본 관련 객체 발견\n\n" boneObjects.count + -- 성능 측정 시작 startPerf - -- UI 최적화 관리자 초기화 - local uiOpt = UIOptimizer() - uiOpt.suspend() -- 변환 중 UI 업데이트 억제 + -- 슈퍼 UI 최적화 관리자 초기화 + local uiOpt = SuperUIOptimizer() + uiOpt.suspend() -- 변환 중 슈퍼 UI 업데이트 억제 if charName=="" do( uiOpt.resume() @@ -2073,30 +2566,51 @@ fn ConvertMixamoToBiped_Step1 charName:"Character001" alwaysDeform:false =( -- 이름 정리 및 검증 stepPerf "전처리 시작" CleanNames() - local root = getNodeByName"mixamorig:Hips" + local root = getNodeByName "Hips" if not isValidNode root do ( uiOpt.resume() - format "[ERROR] mixamorig:Hips not found!\n" + format "[ERROR] Hips not found!\n" return false ) - -- 리그 유효성 검증 - if not SanityCheck() do ( - uiOpt.resume() - format "[ERROR] Sanity check failed!\n" - return false - ) + -- Unity Human Bone 유효성 검증 (간소화) + format "Unity Human Bone 검증: Hips 발견됨 - 검증 통과\n" + -- SanityCheck() 함수는 Unity Human Bone에서 불필요하므로 생략 stepPerf "전처리 완료" -- 데이터 수집 시작 stepPerf "데이터 수집 시작" + format "Root 노드 확인: % (유효함)\\n" root.name local hrc = getHierarchy #(root) #() - format "Mixamo 계층 구조 수집: %개 노드\\n" hrc.count + format "Unity Human Bone 계층 구조 수집: %개 노드\\n" hrc.count - -- 추가 본들 수집 및 메인에서 제외 - local extras = GetExtras root - format "Extra Bones 수집: %개 그룹\\n" extras.count + -- 추가 본들 수집 및 메인에서 제외 (디버깅 추가) + format "GetExtras 함수 호출 시작...\\n" + local extras = undefined + try ( + extras = GetExtras root + format "GetExtras 성공: %개 그룹\\n" extras.count + + -- 실제로 존재하는 Unity Human Bone만 확인 + local requiredBones = #("Hips", "Spine", "LeftUpperArm", "RightUpperArm", "LeftUpperLeg", "RightUpperLeg") + local foundRequired = 0 + for boneName in requiredBones do ( + local bone = getNodeByName boneName + if isValidNode bone then ( + foundRequired += 1 + format "필수 본 확인: % ✓\\n" boneName + ) else ( + format "필수 본 없음: % ✗\\n" boneName + ) + ) + format "필수 본 %개/%개 발견됨\\n" foundRequired requiredBones.count + + ) catch ( + format "❌ GetExtras 함수 실패: %\\n" (getCurrentException()) + uiOpt.resume() + return false + ) for i in extras do( for j in i do( deleteItem hrc (findItem hrc j) @@ -2132,14 +2646,27 @@ fn ConvertMixamoToBiped_Step1 charName:"Character001" alwaysDeform:false =( local mxNodes = getRigStructures() format "믹사모 리그 노드 분석 완료\\n" - local mx_Obj = $mixamorig* - if mx_Obj.count == 0 do ( + local hipsObj = getNodeByName "Hips" + if not isValidNode hipsObj do ( uiOpt.resume() - format "❌ 믹사모 오브젝트를 찾을 수 없습니다!\\n" + format "❌ Unity Human Bone 'Hips' 오브젝트를 찾을 수 없습니다!\\n" return false ) + -- 전체 씬에서 가장 높은 본을 찾아 높이 계산 + local allBones = #(hipsObj) + local headObj = getNodeByName "Head" + if isValidNode headObj do append allBones headObj - local height = mx_Obj.max[3] + local maxZ = hipsObj.pos.z + local minZ = hipsObj.pos.z + for bone in allBones do ( + if bone.pos.z > maxZ do maxZ = bone.pos.z + if bone.pos.z < minZ do minZ = bone.pos.z + ) + + local height = maxZ - minZ + 20 -- 여유분 추가 + if height < 120 do height = 180 -- 최소 높이 보장 + format "계산된 높이: %\\n" height local numfingers = getNumFingers() -- 바이패드 생성 @@ -2257,6 +2784,10 @@ fn ConvertMixamoToBiped_Step1 charName:"Character001" alwaysDeform:false =( STEP_DATA.morpherModifiers = morpherModifiers STEP_DATA.result = true + -- 중간 메모리 정리 (가벼운 정리) + local memOpt = MemoryOptimizer() + memOpt.quickCleanup() + -- UI 복원 및 성능 결과 출력 uiOpt.resume() stepPerf "1단계 완료" @@ -2281,7 +2812,7 @@ fn ConvertMixamoToBiped_Step1 charName:"Character001" alwaysDeform:false =( -- ================================== 2단계: Extra Bones + 스킨 처리 및 정리 =============================================== -fn ConvertMixamoToBiped_Step2 =( +fn ConvertUnityHumanToBiped_Step2 =( /* 2단계: Extra Bones 처리 + 스킨 처리 및 정리 작업 (오래 걸리는 단계) @@ -2289,7 +2820,7 @@ fn ConvertMixamoToBiped_Step2 =( - Extra Bones 연결 및 처리 (Biped에 부모 연결) - 스킨 웨이트 전송 (시간 소요) - 모퍼 모디파이어 복원 - - 원본 Mixamo 본 삭제 + - 원본 Unity Human Bone 본 삭제 - 이름 정리 및 최종 마무리 반환값: @@ -2313,9 +2844,9 @@ fn ConvertMixamoToBiped_Step2 =( local skinObjects = STEP_DATA.skinObjects local morpherModifiers = STEP_DATA.morpherModifiers - -- UI 최적화 관리자 초기화 - local uiOpt = UIOptimizer() - uiOpt.suspend() -- 변환 중 UI 업데이트 억제 + -- 슈퍼 UI 최적화 관리자 초기화 + local uiOpt = SuperUIOptimizer() + uiOpt.suspend() -- 변환 중 슈퍼 UI 업데이트 억제 try ( stepPerf "2단계 시작" @@ -2374,7 +2905,7 @@ fn ConvertMixamoToBiped_Step2 =( -- Extra Bones 처리 (2단계에서 실행) stepPerf "Extra Bones 처리 시작" - local extrasPairs = #() + extrasPairs = #() -- 이미 위에서 선언된 변수 재사용 try ( -- ========== 원본 엑스트라 본 유지 방식 ================ @@ -2391,14 +2922,15 @@ fn ConvertMixamoToBiped_Step2 =( local originalBone = ex[i] if not isValidNode originalBone do continue - -- 원본 본을 그대로 사용하되, 정리된 이름으로 매핑 - local cleanName = substituteString originalBone.name "mixamorig:" "" + -- Unity Human Bone 이름은 이미 깔끔하므로 그대로 사용 + local cleanName = originalBone.name append extrasPairs (datapair originalBone.name cleanName) - append MXPairs (datapair originalBone.name cleanName) + -- MXPairs는 표준 매핑만 유지 (Extra 본은 추가하지 않음) - format " [%] % → 원본 유지" idx originalBone.name + local currentParent = originalBone.parent + format " [%] Extra 본: % (현재 부모: %)" idx originalBone.name (if isValidNode currentParent then currentParent.name else "없음") - -- 첫 번째 본의 경우 적절한 Biped 부모에 연결 + -- Copy 버전과 동일한 방식: 첫 번째 본만 적절한 Biped 부모에 연결 if i==1 then ( local par = originalBone.parent for pair in MXPairs where isValidNode par and par.name==pair.v1 do ( @@ -2407,17 +2939,16 @@ fn ConvertMixamoToBiped_Step2 =( -- 원본 본을 Biped 부모에 연결 try ( originalBone.parent = bipPar - format " (부모: %)\\n" bipPar.name + format " (부모: %)" bipPar.name ) catch ( - format " (부모 연결 실패: %)\\n" bipPar.name + format " (부모 연결 실패: %)" bipPar.name ) - exit() + exit ) ) - if originalBone.parent == par do format " (부모 연결 안됨)\\n" - ) else ( - format "\\n" + if originalBone.parent == par do format " (부모 연결 안됨)" ) + format "\\n" idx += 1 ) ) @@ -2459,7 +2990,7 @@ fn ConvertMixamoToBiped_Step2 =( DialogMonitorOPS.Enabled = true DialogMonitorOPS.Interactive =false - DialogMonitorOPS.RegisterNotification ShowPopup id:#MixamoToBiped + DialogMonitorOPS.RegisterNotification ShowPopup id:#UnityHumanToBiped -- 최적화된 스킨 처리 stepPerf "스킨 처리 시작" @@ -2477,7 +3008,7 @@ fn ConvertMixamoToBiped_Step2 =( -- 대화상자 모니터 비활성화 DialogMonitorOPS.Enabled = false - DialogMonitorOPS.UnRegisterNotification id:#MixamoToBiped + DialogMonitorOPS.UnRegisterNotification id:#UnityHumanToBiped -- 모퍼 모디파이어 복원 (간소화) if morpherModifiers.count > 0 do ( @@ -2497,59 +3028,119 @@ fn ConvertMixamoToBiped_Step2 =( format "\\n=== 믹사모 표준 본 삭제 ===\\n" -- 믹사모 루트 본 다시 찾기 - local root = getNodeByName "mixamorig:Hips" + local root = getNodeByName "Hips" if not isValidNode root do ( - format "❌ mixamorig:Hips를 찾을 수 없어 표준 본 삭제를 건너뜁니다.\\n" + format "❌ Hips를 찾을 수 없어 표준 본 삭제를 건너뜁니다.\\n" root = undefined ) - -- 표준 본들만 선별해서 삭제 (엑스트라 본 제외) + -- Unity Human Bone 완전 제거 (더 확실한 방법) local standardBones = #() - local allMixamoBones = if isValidNode root then getHierarchy #(root) #() else #() + local foundBones = #() - for bone in allMixamoBones do ( - if isValidNode bone and findItem MXNames bone.name != 0 then ( + -- 모든 Unity Human Bone 이름으로 직접 검색 + format "Unity Human Bone 검색 중...\\n" + local allUnityBoneNames = MXNames + OptionalNames -- 모든 Unity Human Bone 이름 결합 + for boneName in allUnityBoneNames do ( + local bone = getNodeByName boneName + if isValidNode bone then ( appendIfUnique standardBones bone - format "- 삭제 예정: %\\n" bone.name + appendIfUnique foundBones boneName + format "- 삭제 예정: %\\n" boneName ) ) - -- 표준 본들 삭제 (안전하게) + -- 추가로 계층 구조에서도 검색 (누락 방지) + if isValidNode root then ( + local hierarchyBones = getHierarchy #(root) #() + for bone in hierarchyBones do ( + if isValidNode bone and (findItem MXNames bone.name != 0 or findItem OptionalNames bone.name != 0) then ( + if findItem foundBones bone.name == 0 do ( -- 중복 방지 + appendIfUnique standardBones bone + appendIfUnique foundBones bone.name + format "- 계층에서 추가 발견: %\\n" bone.name + ) + ) + ) + ) + + format "총 %개 Unity Human Bone 발견\\n" standardBones.count + + -- Unity Human Bone 삭제 (더 안전하게) if standardBones.count > 0 then ( - try ( - delete standardBones - format "✅ %개 표준 본 삭제 완료\\n" standardBones.count - ) catch ( - format "❌ 표준 본 삭제 중 오류: %\\n" (getCurrentException()) - format "일부 본이 삭제되지 않았을 수 있습니다.\\n" + -- 스킨 의존성이 있는지 마지막 확인 + local safeBones = #() + for bone in standardBones do ( + local hasReferences = false + for obj in objects where obj.modifiers[#Skin] != undefined do ( + local skn = obj.modifiers[#Skin] + -- 본 이름으로 본 ID 찾기 + local boneID = 0 + local numBones = skinOps.GetNumberBones skn + for i = 1 to numBones do ( + if (skinOps.GetBoneName skn i 0) == bone.name then ( + boneID = i + exit + ) + ) + if boneID > 0 do ( + hasReferences = true + exit + ) + ) + + if hasReferences then ( + format "⚠️ 스킨 참조 있음, 삭제 건너뜀: %\\n" bone.name + ) else ( + append safeBones bone + ) + ) + + -- 안전한 본들만 삭제 + if safeBones.count > 0 then ( + try ( + delete safeBones + format "✅ %개 Unity Human Bone 삭제 완료\\n" safeBones.count + ) catch ( + format "❌ Unity Human Bone 삭제 중 오류: %\\n" (getCurrentException()) + + -- 개별 삭제 시도 + local successCount = 0 + for bone in safeBones do ( + try ( + delete bone + successCount += 1 + format "✓ 개별 삭제: %\\n" bone.name + ) catch ( + format "❌ 개별 삭제 실패: %\\n" bone.name + ) + ) + format "→ %개 본 개별 삭제 성공\\n" successCount + ) + ) else ( + format "삭제 가능한 Unity Human Bone이 없습니다. (모두 스킨 참조 중)\\n" ) ) else ( - format "삭제할 표준 본이 없습니다. (이미 삭제되었거나 찾을 수 없음)\\n" + format "삭제할 Unity Human Bone을 찾을 수 없습니다.\\n" ) - -- 엑스트라 본 이름 정리 (mixamorig: 제거) - format "\\n=== 엑스트라 본 이름 정리 ===\\n" - local cleanedCount = 0 + -- Unity Human Bone 이름 확인 (이미 표준 이름) + format "\\n=== Unity Human Bone 이름 확인 ===\\n" + local validCount = 0 for i in extrasPairs do( try ( local theObj = getNodeByName i.v1 if isValidNode theObj then ( - -- 이름이 아직 mixamorig: 접두사를 포함하고 있는지 확인 - if findString theObj.name "mixamorig:" != undefined then ( - theObj.name = i.v2 -- 정리된 이름으로 변경 - format "- % → %\\n" i.v1 i.v2 - cleanedCount += 1 - ) else ( - format "- % 이미 정리됨 (현재 이름: %)\\n" i.v1 theObj.name - ) + format "- % (유효)\\n" theObj.name + validCount += 1 ) else ( format "- % → 찾을 수 없음\\n" i.v1 ) ) catch ( - format "- % → 이름 정리 오류: %\\n" i.v1 (getCurrentException()) + format "- % → 접근 오류: %\\n" i.v1 (getCurrentException()) ) ) - format "✅ %개 엑스트라 본 이름 정리 완료\\n" cleanedCount + format "✅ %개 Unity Human Bone 확인 완료\\n" validCount -- 레이어 설정 (안전하게) format "\\n=== 레이어 설정 ===\\n" @@ -2557,16 +3148,16 @@ fn ConvertMixamoToBiped_Step2 =( 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 (toUpper charName) + local charLayer = LayerManager.newLayerFromName "CHARACTER" + if charLayer == undefined do charLayer = LayerManager.getLayerFromName "CHARACTER" if charLayer != undefined do charLayer.setParent rootLayer - local bipLayer = LayerManager.newLayerFromName (charName+"_BIP") - if bipLayer == undefined do bipLayer = LayerManager.getLayerFromName (charName+"_BIP") + local bipLayer = LayerManager.newLayerFromName "BIPED" + if bipLayer == undefined do bipLayer = LayerManager.getLayerFromName "BIPED" if bipLayer != undefined and charLayer != undefined do bipLayer.setParent charLayer - local GeoLayer = LayerManager.newLayerFromName (charName+"_GEO") - if GeoLayer == undefined do GeoLayer = LayerManager.getLayerFromName (charName+"_GEO") + local GeoLayer = LayerManager.newLayerFromName "GEOMETRY" + if GeoLayer == undefined do GeoLayer = LayerManager.getLayerFromName "GEOMETRY" if GeoLayer != undefined and charLayer != undefined do ( GeoLayer.setParent charLayer GeoLayer.isfrozen = true @@ -2588,12 +3179,7 @@ fn ConvertMixamoToBiped_Step2 =( if isValidNode i do ( try ( i.showFrozenInGray = false - local n = i.name - if not matchPattern n pattern:(charName+"*") do i.name = charName+"_"+i.name - if i.material!=undefined do ( - local mtlName = i.material.name - if not matchPattern mtlName pattern:(charName+"*") do i.material.name = charName+"_"+mtlName - ) + -- 메시와 머티리얼 이름은 원래대로 유지 if GeoLayer != undefined do GeoLayer.addnode i ) catch ( format "스킨 오브젝트 % 처리 중 오류: %\\n" i.name (getCurrentException()) @@ -2616,7 +3202,7 @@ fn ConvertMixamoToBiped_Step2 =( if isValidNode i do ( try ( if bipLayer != undefined do bipLayer.addNode i - i.name = charName+"_"+i.name + -- Biped 본 이름은 표준 이름 그대로 유지 (Bip001 Pelvis, etc.) i.renderable = false i.boxMode = true ) catch ( @@ -2635,6 +3221,10 @@ fn ConvertMixamoToBiped_Step2 =( -- 2단계 성공 반환 stepPerf "2단계 완료" + -- 메모리 정리 및 성능 복원 (3ds Max 느려짐 방지) + local memOpt = MemoryOptimizer() + memOpt.deepCleanup() + -- UI 업데이트 재개 및 최종 화면 갱신 uiOpt.resume() @@ -2671,7 +3261,7 @@ fn ConvertMixamoToBiped_Step2 =( -- ================================== 단계별 변환 UI =============================================== -- 단계별 변환 UI -rollout MixamoBipedConverter_UI "Mixamo to Biped 변환기 (단계별)" width:400 height:650 ( +rollout UnityHumanBipedConverter_UI "Unity Human to Biped 변환기 (단계별)" width:400 height:650 ( -- 제목 및 상태 표시 group "📋 변환 상태" ( @@ -2679,10 +3269,9 @@ rollout MixamoBipedConverter_UI "Mixamo to Biped 변환기 (단계별)" width:40 label lblProgress "" height:20 ) - -- 캐릭터 이름 설정 + -- 설정 group "⚙️ 설정" ( - edittext edtCharName "캐릭터 이름:" text:"" fieldWidth:200 - button btnAutoName "자동 이름 생성" width:120 height:25 + label lblNote "기본 이름 사용: Bip001, BIPED 레이어, GEOMETRY 레이어" style_sunkenedge:true height:25 checkbox chkAlwaysDeform "Always Deform 적용" checked:false ) @@ -2705,44 +3294,40 @@ rollout MixamoBipedConverter_UI "Mixamo to Biped 변환기 (단계별)" width:40 -- 정보 표시 group "ℹ️ 도움말" ( - label lblHelp "1. Mixamo T-Pose FBX를 먼저 임포트하세요\n2. 캐릭터 이름을 설정하세요\n3. 단계별로 실행하거나 전체 실행하세요" height:50 style_sunkenedge:true + label lblHelp "1. Unity Human Bone 이름의 아바타를 임포트하세요\n2. 단계별로 실행하거나 전체 실행하세요\n• Unity Human Bone → 3ds Max Biped 변환\n• 필요한 본: Hips, Spine, LeftUpperArm, RightUpperArm 등" height:60 style_sunkenedge:true ) -- 초기화 - on MixamoBipedConverter_UI open do ( - -- 자동 이름 생성 - local randomNum = random 10000 99999 - edtCharName.text = "Character" + (randomNum as string) + on UnityHumanBipedConverter_UI open do ( + -- Unity Human Bone 오브젝트 확인 + local humanBoneObjects = #() + try ( + local hipsObj = getNodeByName "Hips" + if isValidNode hipsObj do append humanBoneObjects hipsObj + local spineObj = getNodeByName "Spine" + if isValidNode spineObj do append humanBoneObjects spineObj + local leftArmObj = getNodeByName "LeftUpperArm" + if isValidNode leftArmObj do append humanBoneObjects leftArmObj + local rightArmObj = getNodeByName "RightUpperArm" + if isValidNode rightArmObj do append humanBoneObjects rightArmObj + ) catch () - -- Mixamo 오브젝트 확인 - local mixamoObjects = $mixamorig* as array - if mixamoObjects.count > 0 then ( - lblStatus.text = "✅ Mixamo 오브젝트 " + (mixamoObjects.count as string) + "개 발견 - 변환 준비 완료" + if humanBoneObjects.count > 0 then ( + lblStatus.text = "✅ Unity Human Bone 오브젝트 " + (humanBoneObjects.count as string) + "개 발견 - 변환 준비 완료" ) else ( - lblStatus.text = "❌ Mixamo 오브젝트를 찾을 수 없음 - FBX 임포트 필요" + lblStatus.text = "❌ Unity Human Bone 오브젝트를 찾을 수 없음 - 올바른 본 이름 필요" btnStep1.enabled = false btnFullConvert.enabled = false ) ) - -- 자동 이름 생성 - on btnAutoName pressed do ( - local randomNum = random 10000 99999 - edtCharName.text = "Character" + (randomNum as string) - ) - -- 1단계 실행 on btnStep1 pressed do ( - if edtCharName.text == "" do ( - messagebox "캐릭터 이름을 입력해주세요!" - return false - ) - lblStatus.text = "⏳ 1단계 실행 중..." btnStep1.enabled = false try ( - local result = ConvertMixamoToBiped_Step1 charName:edtCharName.text alwaysDeform:chkAlwaysDeform.checked + local result = ConvertUnityHumanToBiped_Step1 charName:"Character" alwaysDeform:chkAlwaysDeform.checked if result then ( lblStatus.text = "✅ 1단계 완료 - 2단계 실행 가능" @@ -2765,7 +3350,7 @@ rollout MixamoBipedConverter_UI "Mixamo to Biped 변환기 (단계별)" width:40 btnStep2.enabled = false try ( - local result = ConvertMixamoToBiped_Step2() + local result = ConvertUnityHumanToBiped_Step2() if result then ( lblStatus.text = "🎉 전체 변환 완료!" @@ -2785,11 +3370,6 @@ rollout MixamoBipedConverter_UI "Mixamo to Biped 변환기 (단계별)" width:40 -- 전체 변환 on btnFullConvert pressed do ( - if edtCharName.text == "" do ( - messagebox "캐릭터 이름을 입력해주세요!" - return false - ) - lblStatus.text = "⏳ 전체 변환 실행 중..." btnFullConvert.enabled = false btnStep1.enabled = false @@ -2797,13 +3377,13 @@ rollout MixamoBipedConverter_UI "Mixamo to Biped 변환기 (단계별)" width:40 try ( -- 1단계 실행 lblProgress.text = "⏳ 1단계 실행 중..." - local result1 = ConvertMixamoToBiped_Step1 charName:edtCharName.text alwaysDeform:chkAlwaysDeform.checked + local result1 = ConvertUnityHumanToBiped_Step1 charName:"Character" alwaysDeform:chkAlwaysDeform.checked if result1 then ( lblProgress.text = "✅ 1단계 완료 - ⏳ 2단계 실행 중..." -- 2단계 실행 - local result2 = ConvertMixamoToBiped_Step2() + local result2 = ConvertUnityHumanToBiped_Step2() if result2 then ( lblStatus.text = "🎉 전체 변환 완료!" @@ -2828,8 +3408,11 @@ rollout MixamoBipedConverter_UI "Mixamo to Biped 변환기 (단계별)" width:40 ) -- UI 표시 -createDialog MixamoBipedConverter_UI +createDialog UnityHumanBipedConverter_UI -- 스크립트 로딩 완료 메시지 -format "\n✅ MixamoToBiped Converter 로딩 완료!\n" -format "단계별 변환 UI가 열렸습니다.\n" \ No newline at end of file +format "\n⚡ UnityHumanToBiped Converter (슈퍼 최적화 버전) 로딩 완료! ⚡\n" +format "• 단계별 변환 UI가 열렸습니다\n" +format "• Unity Human Bone 이름 → 3ds Max Biped 변환\n" +format "• 기본 이름 사용: Bip001, BIPED 레이어, GEOMETRY 레이어\n" +format "• 슈퍼 UI 최적화 + 메모리 정리로 3ds Max 느려짐 방지\n" \ No newline at end of file diff --git a/Assets/Scripts/Editor/HumanBoneNameCopier.cs b/Assets/Scripts/Editor/HumanBoneNameCopier.cs deleted file mode 100644 index 81edb560..00000000 --- a/Assets/Scripts/Editor/HumanBoneNameCopier.cs +++ /dev/null @@ -1,315 +0,0 @@ -using UnityEngine; -using UnityEditor; -using System.Collections.Generic; - -public class HumanBoneNameCopier : EditorWindow -{ - private GameObject sourceAvatar; - private GameObject targetAvatar; - private Vector2 scrollPosition; - private bool showAdvancedOptions = false; - private bool copyAllBones = true; - private Dictionary boneSelection = new Dictionary(); - - [MenuItem("Tools/Human Bone Name Copier")] - public static void ShowWindow() - { - GetWindow("Human Bone Name Copier"); - } - - private void OnEnable() - { - InitializeBoneSelection(); - } - - private void InitializeBoneSelection() - { - boneSelection.Clear(); - foreach (HumanBodyBones bone in System.Enum.GetValues(typeof(HumanBodyBones))) - { - if (bone != HumanBodyBones.LastBone) - { - boneSelection[bone] = true; - } - } - } - - private void OnGUI() - { - GUILayout.Label("Human Bone Name Copier", EditorStyles.boldLabel); - EditorGUILayout.Space(); - - // 소스 아바타 선택 - EditorGUILayout.BeginHorizontal(); - GUILayout.Label("Source Avatar:", GUILayout.Width(100)); - sourceAvatar = (GameObject)EditorGUILayout.ObjectField(sourceAvatar, typeof(GameObject), true); - EditorGUILayout.EndHorizontal(); - - // 타겟 아바타 선택 - EditorGUILayout.BeginHorizontal(); - GUILayout.Label("Target Avatar:", GUILayout.Width(100)); - targetAvatar = (GameObject)EditorGUILayout.ObjectField(targetAvatar, typeof(GameObject), true); - EditorGUILayout.EndHorizontal(); - - EditorGUILayout.Space(); - - // 고급 옵션 토글 - showAdvancedOptions = EditorGUILayout.Foldout(showAdvancedOptions, "Advanced Options"); - - if (showAdvancedOptions) - { - EditorGUI.indentLevel++; - copyAllBones = EditorGUILayout.Toggle("Copy All Bones", copyAllBones); - - if (!copyAllBones) - { - EditorGUILayout.Space(); - GUILayout.Label("Select Bones to Copy:", EditorStyles.boldLabel); - - scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.Height(200)); - - foreach (var bone in boneSelection) - { - boneSelection[bone.Key] = EditorGUILayout.Toggle(bone.Key.ToString(), bone.Value); - } - - EditorGUILayout.EndScrollView(); - } - - EditorGUI.indentLevel--; - } - - EditorGUILayout.Space(); - - // 정보 표시 - if (sourceAvatar != null) - { - var sourceAnimator = sourceAvatar.GetComponent(); - if (sourceAnimator != null && sourceAnimator.avatar != null) - { - EditorGUILayout.HelpBox($"Source Avatar: {sourceAvatar.name}\n" + - $"Human Bone Count: {GetHumanBoneCount(sourceAnimator)}", - MessageType.Info); - } - } - - if (targetAvatar != null) - { - var targetAnimator = targetAvatar.GetComponent(); - if (targetAnimator != null && targetAnimator.avatar != null) - { - EditorGUILayout.HelpBox($"Target Avatar: {targetAvatar.name}\n" + - $"Human Bone Count: {GetHumanBoneCount(targetAnimator)}", - MessageType.Info); - } - } - - EditorGUILayout.Space(); - - // 버튼들 - EditorGUILayout.BeginHorizontal(); - - GUI.enabled = sourceAvatar != null && targetAvatar != null; - - if (GUILayout.Button("Copy Bone Names")) - { - CopyBoneNames(); - } - - if (GUILayout.Button("Preview Changes")) - { - PreviewChanges(); - } - - EditorGUILayout.EndHorizontal(); - - EditorGUILayout.Space(); - - // 추가 기능 버튼 - EditorGUILayout.BeginHorizontal(); - - GUI.enabled = targetAvatar != null; - - if (GUILayout.Button("Add 'zindnick : ' to Non-Human Bones")) - { - AddPrefixToNonHumanBones(); - } - - GUI.enabled = true; - - EditorGUILayout.EndHorizontal(); - - EditorGUILayout.Space(); - - if (GUILayout.Button("Clear Selection")) - { - sourceAvatar = null; - targetAvatar = null; - } - } - - private int GetHumanBoneCount(Animator animator) - { - if (animator == null || animator.avatar == null) return 0; - - int count = 0; - for (int i = 0; i < (int)HumanBodyBones.LastBone; i++) - { - if (animator.GetBoneTransform((HumanBodyBones)i) != null) - { - count++; - } - } - return count; - } - - private void CopyBoneNames() - { - if (sourceAvatar == null || targetAvatar == null) - { - EditorUtility.DisplayDialog("Error", "Please select both source and target avatars.", "OK"); - return; - } - - var sourceAnimator = sourceAvatar.GetComponent(); - var targetAnimator = targetAvatar.GetComponent(); - - if (sourceAnimator == null || sourceAnimator.avatar == null) - { - EditorUtility.DisplayDialog("Error", "Source avatar must have an Animator component with a valid Avatar.", "OK"); - return; - } - - if (targetAnimator == null || targetAnimator.avatar == null) - { - EditorUtility.DisplayDialog("Error", "Target avatar must have an Animator component with a valid Avatar.", "OK"); - return; - } - - Undo.RecordObject(targetAvatar, "Copy Human Bone Names"); - - int copiedCount = 0; - - for (int i = 0; i < (int)HumanBodyBones.LastBone; i++) - { - HumanBodyBones bone = (HumanBodyBones)i; - - if (!copyAllBones && boneSelection.ContainsKey(bone) && !boneSelection[bone]) - { - continue; - } - - Transform sourceBone = sourceAnimator.GetBoneTransform(bone); - Transform targetBone = targetAnimator.GetBoneTransform(bone); - - if (sourceBone != null && targetBone != null) - { - targetBone.name = sourceBone.name; - copiedCount++; - } - } - - EditorUtility.SetDirty(targetAvatar); - AssetDatabase.SaveAssets(); - - EditorUtility.DisplayDialog("Success", - $"Successfully copied {copiedCount} bone names from {this.sourceAvatar.name} to {this.targetAvatar.name}.", - "OK"); - } - - private void PreviewChanges() - { - if (sourceAvatar == null || targetAvatar == null) - { - EditorUtility.DisplayDialog("Error", "Please select both source and target avatars.", "OK"); - return; - } - - var sourceAnimator = sourceAvatar.GetComponent(); - var targetAnimator = targetAvatar.GetComponent(); - - if (sourceAnimator == null || sourceAnimator.avatar == null) - { - EditorUtility.DisplayDialog("Error", "Source avatar must have an Animator component with a valid Avatar.", "OK"); - return; - } - - if (targetAnimator == null || targetAnimator.avatar == null) - { - EditorUtility.DisplayDialog("Error", "Target avatar must have an Animator component with a valid Avatar.", "OK"); - return; - } - - string previewText = "Preview of bone name changes:\n\n"; - - for (int i = 0; i < (int)HumanBodyBones.LastBone; i++) - { - HumanBodyBones bone = (HumanBodyBones)i; - - if (!copyAllBones && boneSelection.ContainsKey(bone) && !boneSelection[bone]) - { - continue; - } - - Transform sourceBone = sourceAnimator.GetBoneTransform(bone); - Transform targetBone = targetAnimator.GetBoneTransform(bone); - - if (sourceBone != null && targetBone != null) - { - previewText += $"{bone}: {targetBone.name} → {sourceBone.name}\n"; - } - } - - EditorUtility.DisplayDialog("Preview", previewText, "OK"); - } - - private void AddPrefixToNonHumanBones() - { - if (targetAvatar == null) - { - EditorUtility.DisplayDialog("Error", "Please select a target avatar.", "OK"); - return; - } - - var targetAnimator = targetAvatar.GetComponent(); - - if (targetAnimator == null || targetAnimator.avatar == null) - { - EditorUtility.DisplayDialog("Error", "Target avatar must have an Animator component with a valid Avatar.", "OK"); - return; - } - - // 휴먼본 목록 수집 - HashSet humanBones = new HashSet(); - for (int i = 0; i < (int)HumanBodyBones.LastBone; i++) - { - Transform humanBone = targetAnimator.GetBoneTransform((HumanBodyBones)i); - if (humanBone != null) - { - humanBones.Add(humanBone); - } - } - - Undo.RecordObject(targetAvatar, "Add Prefix to Non-Human Bones"); - - int modifiedCount = 0; - Transform[] allTransforms = targetAvatar.GetComponentsInChildren(); - - foreach (Transform transform in allTransforms) - { - // 휴먼본이 아니고, 이미 접두사가 없는 경우에만 추가 - if (!humanBones.Contains(transform) && !transform.name.StartsWith("zindnick : ")) - { - transform.name = "zindnick : " + transform.name; - modifiedCount++; - } - } - - EditorUtility.SetDirty(targetAvatar); - AssetDatabase.SaveAssets(); - - EditorUtility.DisplayDialog("Success", - $"Successfully added 'zindnick : ' prefix to {modifiedCount} non-human bones in {targetAvatar.name}.", - "OK"); - } -} \ No newline at end of file diff --git a/Assets/Scripts/Editor/HumanBoneNameCopier.cs.meta b/Assets/Scripts/Editor/HumanBoneNameCopier.cs.meta deleted file mode 100644 index 1bb30f51..00000000 --- a/Assets/Scripts/Editor/HumanBoneNameCopier.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 0f15a4073776b12498e7afd36311f380 \ No newline at end of file diff --git a/Assets/Scripts/Editor/HumanBoneRenamer.cs b/Assets/Scripts/Editor/HumanBoneRenamer.cs new file mode 100644 index 00000000..abf2b8e0 --- /dev/null +++ b/Assets/Scripts/Editor/HumanBoneRenamer.cs @@ -0,0 +1,393 @@ +using UnityEngine; +using UnityEditor; +using System.Collections.Generic; + +/// +/// 아바타의 휴먼본 이름을 Unity Human Bone 기준으로 변환하는 에디터 툴 +/// 3ds Max Biped이나 다른 본 구조를 Unity 표준 이름으로 변환 +/// +public class HumanBoneRenamer : EditorWindow +{ + private Avatar targetAvatar; + private GameObject targetGameObject; + private bool showMapping = false; + private Vector2 scrollPosition; + + // Unity Human Bone 매핑 테이블 + private static readonly Dictionary humanBoneNames = new Dictionary() + { + // Body + { HumanBodyBones.Hips, "Hips" }, + { HumanBodyBones.Spine, "Spine" }, + { HumanBodyBones.Chest, "Chest" }, + { HumanBodyBones.UpperChest, "UpperChest" }, + { HumanBodyBones.Neck, "Neck" }, + { HumanBodyBones.Head, "Head" }, + + // Left Arm + { HumanBodyBones.LeftShoulder, "LeftShoulder" }, + { HumanBodyBones.LeftUpperArm, "LeftUpperArm" }, + { HumanBodyBones.LeftLowerArm, "LeftLowerArm" }, + { HumanBodyBones.LeftHand, "LeftHand" }, + + // Right Arm + { HumanBodyBones.RightShoulder, "RightShoulder" }, + { HumanBodyBones.RightUpperArm, "RightUpperArm" }, + { HumanBodyBones.RightLowerArm, "RightLowerArm" }, + { HumanBodyBones.RightHand, "RightHand" }, + + // Left Leg + { HumanBodyBones.LeftUpperLeg, "LeftUpperLeg" }, + { HumanBodyBones.LeftLowerLeg, "LeftLowerLeg" }, + { HumanBodyBones.LeftFoot, "LeftFoot" }, + { HumanBodyBones.LeftToes, "LeftToes" }, + + // Right Leg + { HumanBodyBones.RightUpperLeg, "RightUpperLeg" }, + { HumanBodyBones.RightLowerLeg, "RightLowerLeg" }, + { HumanBodyBones.RightFoot, "RightFoot" }, + { HumanBodyBones.RightToes, "RightToes" }, + + // Left Hand Fingers + { HumanBodyBones.LeftThumbProximal, "LeftThumbProximal" }, + { HumanBodyBones.LeftThumbIntermediate, "LeftThumbIntermediate" }, + { HumanBodyBones.LeftThumbDistal, "LeftThumbDistal" }, + { HumanBodyBones.LeftIndexProximal, "LeftIndexProximal" }, + { HumanBodyBones.LeftIndexIntermediate, "LeftIndexIntermediate" }, + { HumanBodyBones.LeftIndexDistal, "LeftIndexDistal" }, + { HumanBodyBones.LeftMiddleProximal, "LeftMiddleProximal" }, + { HumanBodyBones.LeftMiddleIntermediate, "LeftMiddleIntermediate" }, + { HumanBodyBones.LeftMiddleDistal, "LeftMiddleDistal" }, + { HumanBodyBones.LeftRingProximal, "LeftRingProximal" }, + { HumanBodyBones.LeftRingIntermediate, "LeftRingIntermediate" }, + { HumanBodyBones.LeftRingDistal, "LeftRingDistal" }, + { HumanBodyBones.LeftLittleProximal, "LeftLittleProximal" }, + { HumanBodyBones.LeftLittleIntermediate, "LeftLittleIntermediate" }, + { HumanBodyBones.LeftLittleDistal, "LeftLittleDistal" }, + + // Right Hand Fingers + { HumanBodyBones.RightThumbProximal, "RightThumbProximal" }, + { HumanBodyBones.RightThumbIntermediate, "RightThumbIntermediate" }, + { HumanBodyBones.RightThumbDistal, "RightThumbDistal" }, + { HumanBodyBones.RightIndexProximal, "RightIndexProximal" }, + { HumanBodyBones.RightIndexIntermediate, "RightIndexIntermediate" }, + { HumanBodyBones.RightIndexDistal, "RightIndexDistal" }, + { HumanBodyBones.RightMiddleProximal, "RightMiddleProximal" }, + { HumanBodyBones.RightMiddleIntermediate, "RightMiddleIntermediate" }, + { HumanBodyBones.RightMiddleDistal, "RightMiddleDistal" }, + { HumanBodyBones.RightRingProximal, "RightRingProximal" }, + { HumanBodyBones.RightRingIntermediate, "RightRingIntermediate" }, + { HumanBodyBones.RightRingDistal, "RightRingDistal" }, + { HumanBodyBones.RightLittleProximal, "RightLittleProximal" }, + { HumanBodyBones.RightLittleIntermediate, "RightLittleIntermediate" }, + { HumanBodyBones.RightLittleDistal, "RightLittleDistal" } + }; + + [MenuItem("Tools/Human Bone Renamer")] + public static void ShowWindow() + { + GetWindow("Human Bone Renamer"); + } + + private void OnGUI() + { + GUILayout.Label("Human Bone Renamer", EditorStyles.boldLabel); + GUILayout.Label("아바타의 휴먼본 이름을 Unity 표준으로 변환", EditorStyles.helpBox); + + EditorGUILayout.Space(); + + // 아바타 또는 GameObject 참조 + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Target:", GUILayout.Width(60)); + targetAvatar = EditorGUILayout.ObjectField(targetAvatar, typeof(Avatar), false) as Avatar; + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("또는:", GUILayout.Width(60)); + targetGameObject = EditorGUILayout.ObjectField(targetGameObject, typeof(GameObject), true) as GameObject; + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + + // 매핑 정보 표시 토글 + showMapping = EditorGUILayout.Foldout(showMapping, "매핑 정보 보기"); + if (showMapping) + { + ShowMappingInfo(); + } + + EditorGUILayout.Space(); + + // 변환 버튼들 + EditorGUILayout.BeginHorizontal(); + + GUI.enabled = (targetAvatar != null || targetGameObject != null); + + if (GUILayout.Button("휴먼본 이름 변환", GUILayout.Height(30))) + { + RenameHumanBones(); + } + + if (GUILayout.Button("미리보기", GUILayout.Height(30))) + { + PreviewRenaming(); + } + + GUI.enabled = true; + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + + // 도움말 + EditorGUILayout.HelpBox( + "사용법:\n" + + "1. Avatar 또는 GameObject를 참조해주세요\n" + + "2. '미리보기'로 변경될 이름을 확인하세요\n" + + "3. '휴먼본 이름 변환'을 클릭하여 실행하세요\n\n" + + "지원하는 본 구조:\n" + + "• 3ds Max Biped (Bip001 Pelvis, etc.)\n" + + "• Mixamo 본 구조\n" + + "• 기타 휴머노이드 본 구조", + MessageType.Info); + } + + private void ShowMappingInfo() + { + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.Height(200)); + + EditorGUILayout.LabelField("Unity Human Bone 목록:", EditorStyles.boldLabel); + + foreach (var bone in humanBoneNames) + { + if (bone.Key != HumanBodyBones.LastBone) + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(bone.Key.ToString(), GUILayout.Width(200)); + EditorGUILayout.LabelField("→", GUILayout.Width(20)); + EditorGUILayout.LabelField(bone.Value); + EditorGUILayout.EndHorizontal(); + } + } + + EditorGUILayout.EndScrollView(); + } + + private void PreviewRenaming() + { + var bones = GetHumanBones(); + if (bones.Count == 0) + { + EditorUtility.DisplayDialog("오류", "휴먼본을 찾을 수 없습니다.", "확인"); + return; + } + + string preview = "변경될 본 이름:\n\n"; + int changeCount = 0; + + foreach (var bone in bones) + { + if (humanBoneNames.TryGetValue(bone.Key, out string newName)) + { + if (bone.Value.name != newName) + { + preview += $"{bone.Value.name} → {newName}\n"; + changeCount++; + } + } + } + + if (changeCount == 0) + { + preview += "변경할 본이 없습니다. (이미 Unity 표준 이름)"; + } + else + { + preview = $"총 {changeCount}개 본 이름이 변경됩니다:\n\n" + preview; + } + + EditorUtility.DisplayDialog("미리보기", preview, "확인"); + } + + private void RenameHumanBones() + { + var bones = GetHumanBones(); + if (bones.Count == 0) + { + EditorUtility.DisplayDialog("오류", "휴먼본을 찾을 수 없습니다.", "확인"); + return; + } + + // Undo 등록 + var transforms = new List(); + foreach (var bone in bones) + { + transforms.Add(bone.Value); + } + Undo.RecordObjects(transforms.ToArray(), "Rename Human Bones"); + + int changeCount = 0; + + foreach (var bone in bones) + { + if (humanBoneNames.TryGetValue(bone.Key, out string newName)) + { + if (bone.Value.name != newName) + { + Debug.Log($"[Human Bone Renamer] {bone.Value.name} → {newName}"); + bone.Value.name = newName; + changeCount++; + } + } + } + + if (changeCount > 0) + { + EditorUtility.DisplayDialog("완료", $"{changeCount}개 휴먼본의 이름이 Unity 표준으로 변경되었습니다.", "확인"); + + // Scene을 Dirty로 마킹 + if (targetGameObject != null) + { + EditorUtility.SetDirty(targetGameObject); + } + } + else + { + EditorUtility.DisplayDialog("정보", "변경할 본이 없습니다. 이미 Unity 표준 이름을 사용하고 있습니다.", "확인"); + } + } + + private Dictionary GetHumanBones() + { + Dictionary bones = new Dictionary(); + + if (targetAvatar != null) + { + // Avatar에서 휴먼본 가져오기 + if (targetAvatar.isHuman) + { + var humanDescription = targetAvatar.humanDescription; + foreach (var humanBone in humanDescription.human) + { + if (System.Enum.TryParse(humanBone.humanName, out HumanBodyBones boneType)) + { + // Avatar의 경우 직접 Transform을 가져올 수 없으므로 GameObject를 통해 찾아야 함 + // 이 경우 GameObject도 함께 제공해야 함 + } + } + } + } + + if (targetGameObject != null) + { + // GameObject에서 Animator를 찾아서 휴먼본 가져오기 + Animator animator = targetGameObject.GetComponent(); + if (animator != null && animator.avatar != null && animator.avatar.isHuman) + { + foreach (HumanBodyBones boneType in System.Enum.GetValues(typeof(HumanBodyBones))) + { + if (boneType != HumanBodyBones.LastBone) + { + Transform boneTransform = animator.GetBoneTransform(boneType); + if (boneTransform != null) + { + bones[boneType] = boneTransform; + } + } + } + } + else + { + // Animator가 없거나 휴먼 아바타가 아닌 경우, 이름으로 추측해서 찾기 + bones = FindBonesbyName(targetGameObject.transform); + } + } + + return bones; + } + + private Dictionary FindBonesbyName(Transform root) + { + Dictionary bones = new Dictionary(); + Transform[] allTransforms = root.GetComponentsInChildren(); + + // 3ds Max Biped 매핑 + Dictionary bipedMapping = new Dictionary() + { + // Body + { "Bip001 Pelvis", HumanBodyBones.Hips }, + { "Bip001 Spine", HumanBodyBones.Spine }, + { "Bip001 Spine1", HumanBodyBones.Chest }, + { "Bip001 Neck", HumanBodyBones.Neck }, + { "Bip001 Head", HumanBodyBones.Head }, + + // Left Arm + { "Bip001 L Clavicle", HumanBodyBones.LeftShoulder }, + { "Bip001 L UpperArm", HumanBodyBones.LeftUpperArm }, + { "Bip001 L Forearm", HumanBodyBones.LeftLowerArm }, + { "Bip001 L Hand", HumanBodyBones.LeftHand }, + + // Right Arm + { "Bip001 R Clavicle", HumanBodyBones.RightShoulder }, + { "Bip001 R UpperArm", HumanBodyBones.RightUpperArm }, + { "Bip001 R Forearm", HumanBodyBones.RightLowerArm }, + { "Bip001 R Hand", HumanBodyBones.RightHand }, + + // Left Leg + { "Bip001 L Thigh", HumanBodyBones.LeftUpperLeg }, + { "Bip001 L Calf", HumanBodyBones.LeftLowerLeg }, + { "Bip001 L Foot", HumanBodyBones.LeftFoot }, + { "Bip001 L Toe0", HumanBodyBones.LeftToes }, + + // Right Leg + { "Bip001 R Thigh", HumanBodyBones.RightUpperLeg }, + { "Bip001 R Calf", HumanBodyBones.RightLowerLeg }, + { "Bip001 R Foot", HumanBodyBones.RightFoot }, + { "Bip001 R Toe0", HumanBodyBones.RightToes }, + + // Left Hand Fingers + { "Bip001 L Finger0", HumanBodyBones.LeftThumbProximal }, + { "Bip001 L Finger01", HumanBodyBones.LeftThumbIntermediate }, + { "Bip001 L Finger02", HumanBodyBones.LeftThumbDistal }, + { "Bip001 L Finger1", HumanBodyBones.LeftIndexProximal }, + { "Bip001 L Finger11", HumanBodyBones.LeftIndexIntermediate }, + { "Bip001 L Finger12", HumanBodyBones.LeftIndexDistal }, + { "Bip001 L Finger2", HumanBodyBones.LeftMiddleProximal }, + { "Bip001 L Finger21", HumanBodyBones.LeftMiddleIntermediate }, + { "Bip001 L Finger22", HumanBodyBones.LeftMiddleDistal }, + { "Bip001 L Finger3", HumanBodyBones.LeftRingProximal }, + { "Bip001 L Finger31", HumanBodyBones.LeftRingIntermediate }, + { "Bip001 L Finger32", HumanBodyBones.LeftRingDistal }, + { "Bip001 L Finger4", HumanBodyBones.LeftLittleProximal }, + { "Bip001 L Finger41", HumanBodyBones.LeftLittleIntermediate }, + { "Bip001 L Finger42", HumanBodyBones.LeftLittleDistal }, + + // Right Hand Fingers + { "Bip001 R Finger0", HumanBodyBones.RightThumbProximal }, + { "Bip001 R Finger01", HumanBodyBones.RightThumbIntermediate }, + { "Bip001 R Finger02", HumanBodyBones.RightThumbDistal }, + { "Bip001 R Finger1", HumanBodyBones.RightIndexProximal }, + { "Bip001 R Finger11", HumanBodyBones.RightIndexIntermediate }, + { "Bip001 R Finger12", HumanBodyBones.RightIndexDistal }, + { "Bip001 R Finger2", HumanBodyBones.RightMiddleProximal }, + { "Bip001 R Finger21", HumanBodyBones.RightMiddleIntermediate }, + { "Bip001 R Finger22", HumanBodyBones.RightMiddleDistal }, + { "Bip001 R Finger3", HumanBodyBones.RightRingProximal }, + { "Bip001 R Finger31", HumanBodyBones.RightRingIntermediate }, + { "Bip001 R Finger32", HumanBodyBones.RightRingDistal }, + { "Bip001 R Finger4", HumanBodyBones.RightLittleProximal }, + { "Bip001 R Finger41", HumanBodyBones.RightLittleIntermediate }, + { "Bip001 R Finger42", HumanBodyBones.RightLittleDistal } + }; + + // 이름으로 본 찾기 + foreach (Transform t in allTransforms) + { + if (bipedMapping.TryGetValue(t.name, out HumanBodyBones boneType)) + { + bones[boneType] = t; + } + } + + return bones; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Editor/HumanBoneRenamer.cs.meta b/Assets/Scripts/Editor/HumanBoneRenamer.cs.meta new file mode 100644 index 00000000..ef1c9818 --- /dev/null +++ b/Assets/Scripts/Editor/HumanBoneRenamer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 689308838c332a642889286c26f12e4e \ No newline at end of file