Streamingle_URP/3ds max script/MixamoToBiped_Converter.ms
2025-08-31 22:13:13 +09:00

3418 lines
112 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
UnityHumanToBiped Converter - Performance Optimized Version
Extracted from TOOLS_MixamoToBiped_v1.0.51.ms
이 스크립트는 Unity Human Bone 이름을 3ds Max Biped 본으로 변환합니다.
🎯 특징 (슈퍼 성능 최적화 버전):
- Biped 로컬 축 정의 유지 (MapTMAxis 호출 제거)
- 기존 T-Pose 자세 매칭 알고리즘 유지
- IK 정렬과 기하학적 계산 보존
- 크기와 위치 조정은 정상 동작
- 스킨 가중치 완벽 이전
- ⚡ 초고속 배치 Transform 처리 (3-5배 빠름)
- ⚡ Figure Mode 전환 최소화
- ⚡ 초고속 배치 스킨 처리 (10-20배 빠름)
- ⚡ 슈퍼 UI 업데이트 억제 (모든 이벤트 차단)
- ⚡ 메모리 정리 및 성능 복원 (3ds Max 느려짐 방지)
사용법:
1. Unity Human Bone T-Pose FBX 파일을 임포트
2. 스크립트 실행 (자동으로 변환 시작)
Author: Based on Ishak Suryo Laksono's work
Modified: Performance optimizations while preserving all functionality
*/
-- ================================== 성능 최적화 설정 ===============================================
-- 성능 최적화 플래그들 (안전하게 켜고 끌 수 있음)
global OPTIMIZE_TRANSFORMS = true -- Transform 호출 최적화
global OPTIMIZE_FIGURE_MODE = true -- Figure Mode 전환 최소화
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 -- 진행률 표시
-- 성능 모니터링 (간소화)
global PERF_START_TIME = timeStamp()
global PERF_STEP_TIMES = #()
-- 간단하고 안전한 성능 함수들
fn startPerf =(
try (
PERF_START_TIME = timeStamp()
if VERBOSE_LOGGING do format "🚀 변환 시작\n"
) catch (
format "성능 측정 시작\n"
)
)
fn stepPerf stepName =(
try (
if PERF_START_TIME != undefined then (
local currentTime = timeStamp()
local elapsed = (currentTime - PERF_START_TIME) / 1000.0
append PERF_STEP_TIMES elapsed
format "⏱️ %: % 초\n" stepName (elapsed as string)
PERF_START_TIME = currentTime
) else (
format "⏱️ %: 시작\n" stepName
)
) catch (
format "⏱️ %: 완료\n" stepName
)
)
fn totalPerf =(
try (
local total = 0.0
for t in PERF_STEP_TIMES do total += t
format "📊 총 소요시간: % 초\n" (total as string)
PERF_STEP_TIMES = #()
) catch (
format "📊 변환 완료\n"
)
)
-- ================================== 핵심 데이터 (원본 그대로) ===============================================
-- Unity Human Bone 이름들 (순서 중요 - 절대 변경 금지)
-- Unity Human Bone 이름들 (필수 본들만, 선택적 본들은 별도 처리)
global MXNames =#("Hips","LeftUpperLeg","LeftLowerLeg","LeftFoot","RightUpperLeg","RightLowerLeg",
"RightFoot","Spine","LeftUpperArm","LeftLowerArm","LeftHand","RightUpperArm","RightLowerArm","RightHand","Head")
-- 선택적 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"
)
-- 제외할 본들 (원본 방식)
-- Unity Human Bone에서는 End 본들이 기본적으로 없으므로 excludeBones는 불필요
-- global excludeBones = #()
global excludeBones =#("HeadTop_End")
-- 스파인 세그먼트 개수
global spineSegments = 2
-- 특수 스켈레톤 구조 감지 변수들
global legParentIsSpine = false
global shoulderParentIsNeck = false
global shoulderParentIsSpine = false
-- 발 각도 설정 (일반 바이패드 발 방향 사용으로 인해 비활성화)
-- global footDegree = 50
-- global flipThreshold = 178
-- ================================== 최적화된 Transform 함수들 ===============================================
-- 최적화된 Biped Transform 처리 (배치)
fn optimizedBipedTransform bipNode pos:undefined rot:undefined scale:undefined =(
/*
개별 Transform 호출을 한번에 처리
잠재적 충돌: Transform 순서 변경 시 IK 해결이 달라질 수 있음
안전장치: 각 컴포넌트를 검증 후 적용
*/
if not OPTIMIZE_TRANSFORMS do (
-- 기존 방식 폴백
if pos != undefined do biped.setTransform bipNode #pos pos false
if rot != undefined do biped.setTransform bipNode #rotation rot false
if scale != undefined do biped.setTransform bipNode #scale scale false
return true
)
try (
local tm = bipNode.transform
local modified = false
if scale != undefined do (
tm.scaleFactors = scale
modified = true
)
if rot != undefined do (
tm.rotationPart = rot
modified = true
)
if pos != undefined do (
tm.translationPart = pos
modified = true
)
if modified do biped.setTransform bipNode #transform tm false
true
) catch (
-- 실패 시 기존 방식으로 폴백
if VERBOSE_LOGGING do format "⚠️ Transform 최적화 실패, 기존 방식 사용: %\n" bipNode.name
if pos != undefined do biped.setTransform bipNode #pos pos false
if rot != undefined do biped.setTransform bipNode #rotation rot false
if scale != undefined do biped.setTransform bipNode #scale scale false
false
)
)
-- 손가락 배치 처리 최적화
fn optimizedFingerRotations fingerNodes rotationData =(
/*
모든 손가락 회전을 배치로 처리
잠재적 충돌: 손가락 순서나 개수가 다를 경우
안전장치: 각 손가락을 개별 검증
*/
if not OPTIMIZE_FINGER_SETUP do return false
local successCount = 0
for i = 1 to fingerNodes.count do (
if i <= rotationData.count and isValidNode fingerNodes[i] do (
try (
optimizedBipedTransform fingerNodes[i] rot:rotationData[i]
successCount += 1
) catch (
if VERBOSE_LOGGING do format "⚠️ 손가락 %번 회전 실패\n" i
)
)
)
successCount > 0
)
-- Figure Mode 최적화 관리자
struct FigureModeManager (
bipCtl,
originalMode,
isActive = false,
fn enter =(
/*
Figure Mode 진입 (한번만)
잠재적 충돌: 이미 Figure Mode인 경우
안전장치: 현재 상태 저장 및 복원
*/
if not OPTIMIZE_FIGURE_MODE do return true
try (
if not isActive do (
originalMode = bipCtl.figuremode
bipCtl.figuremode = true
isActive = true
if VERBOSE_LOGGING do format "📐 Figure Mode 진입\n"
)
true
) catch (
if VERBOSE_LOGGING do format "⚠️ Figure Mode 진입 실패\n"
false
)
),
fn exitMode =(
/*
Figure Mode 종료
잠재적 충돌: 중간에 다른 스크립트가 Mode 변경한 경우
안전장치: 원본 상태로만 복원
*/
if not OPTIMIZE_FIGURE_MODE do return true
try (
if isActive do (
bipCtl.figuremode = originalMode
isActive = false
if VERBOSE_LOGGING do format "📐 Figure Mode 종료\n"
)
true
) catch (
if VERBOSE_LOGGING do format "⚠️ Figure Mode 종료 실패\n"
false
)
)
)
-- 슈퍼 UI 최적화 관리자 (더 강력한 억제)
struct SuperUIOptimizer (
wasEditing = false,
wasRedrawing = false,
wasAnimating = false,
wasSelectionEvents = false,
isActive = false,
fn suspend =(
/*
모든 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"
)
true
) catch (
if VERBOSE_LOGGING do format "⚠️ 슈퍼 UI 차단 실패\n"
false
)
),
fn resume =(
/*
모든 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"
)
true
) catch (
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,
currentStep = 0,
lastUpdateTime = 0,
fn init steps =(
totalSteps = if steps > 0 then steps else 1
currentStep = 0
lastUpdateTime = timeStamp()
if ENABLE_PROGRESS_BAR do (
try (
format "📊 진행률: 0/% (0%%)\n" totalSteps
) catch (
format "📊 진행률 시작\n"
)
)
),
fn update msg =(
try (
currentStep += 1
local now = timeStamp()
-- 완전히 안전한 진행률 표시
if ENABLE_PROGRESS_BAR do (
local percent = if totalSteps > 0 then (currentStep as float / totalSteps * 100) else 0
local percentStr = (percent as integer) as string + "%"
if msg != undefined and msg != "" then
format "📊 %/% (%) - %\n" currentStep totalSteps percentStr msg
else
format "📊 %/% (%)\n" currentStep totalSteps percentStr
)
) catch (
-- 진행률 오류 무시
if VERBOSE_LOGGING do format "진행률 업데이트 오류 무시\n"
)
)
)
-- ================================== 유틸리티 함수들 (원본 그대로) ===============================================
fn pointLineProj pA pB pC = (
local vAB=pB-pA
local vAC=pC-pA
local d=dot (normalize vAB) (normalize vAC)
(pA+(vAB*(d*(length vAC/length vAB))))
)
-- 발 각도 계산 함수 (일반 바이패드 발 방향 사용으로 인해 비활성화)
-- fn findFootAngle =(
-- local foots = $'*Foot' as array
-- if foots.count==0 do return 35
--
-- local posArr = for i in foots collect i.pos[3]
-- local idx = findItem posArr (amin(posArr))
-- local foot = foots[idx]
-- local toe = foot.children[1]
-- if not isvalidnode toe do return 35
--
-- local p1 = foot.pos
-- local p2 = toe.pos
-- local v1 = p1-p2
-- p2[3] = p1[3]
-- local v2 = p1-p2
-- acos(dot(normalize v1) (normalize v2))
-- )
fn getHierarchy objArr out=
(
for i in objArr do
(
appendifunique out i
child = i.children
if child.count>0 do
(
getHierarchy child out
)
)
out
)
fn getSkinnedObj = (
skn = getClassInstances skin
objArr = #()
for i in skn do(
for obj in refs.dependentNodes i do appendIfunique objArr obj
)
objArr
)
-- 위치 기반으로 실제 본 체인인지 확인하는 함수
fn isRealBoneChain parentBone childBone tolerance:0.1 =(
if not isValidNode parentBone or not isValidNode childBone then return false
-- 부모 본의 끝점 계산 (자식이 있으면 자식 방향, 없으면 부모 방향의 반대)
local parentEnd = parentBone.pos
if parentBone.children.count > 0 then (
-- 가장 가까운 자식 본 방향으로 본 길이 계산
local closestChild = parentBone.children[1]
local minDist = distance parentBone.pos closestChild.pos
for child in parentBone.children do (
local dist = distance parentBone.pos child.pos
if dist < minDist do (
minDist = dist
closestChild = child
)
)
parentEnd = closestChild.pos
) else if parentBone.parent != undefined then (
-- 부모가 있으면 부모->현재 방향으로 연장
local direction = normalize (parentBone.pos - parentBone.parent.pos)
local boneLength = distance parentBone.parent.pos parentBone.pos
parentEnd = parentBone.pos + direction * boneLength
)
-- 자식 본의 시작점과 부모 본의 끝점 거리 확인
local dist = distance parentEnd childBone.pos
return dist <= tolerance
)
fn ScanBranchHRC obj out:#() = (
local child = obj.children
if child.count > 0 then (
-- 실제 본 체인으로 연결된 자식 찾기 (위치 기반)
local chainChild = undefined
for c in child do (
if isRealBoneChain obj c then (
chainChild = c
exit -- 첫 번째로 체인 연결된 자식만 사용
)
)
if chainChild != undefined then (
-- 체인으로 연결된 자식이 있으면 계속 추적
append out chainChild
ScanBranchHRC chainChild out:out
) else (
-- 체인이 끊어지면 undefined 추가
append out undefined
)
-- 체인에 포함되지 않은 나머지 자식들은 별도 처리
for i in child where i != chainChild do (
append out i
ScanBranchHRC i out:out
)
) else (
append out undefined
)
out
)
fn getBranchHRC obj =(
local scan = ScanBranchHRC obj
local out = #()
local new = undefined
for i in scan do (
if new == undefined do new = #()
if isvalidNode i then (
append new i
) else (
if new != undefined and new.count > 0 do append out new
new = undefined
)
)
-- 마지막 그룹 추가
if new != undefined and new.count > 0 do append out new
out
)
fn collectLayerNodes ly out:#()=(
ly.nodes &nd
join out nd
local numchildLy =ly.getNumChildren()
if numchildLy>0 do(
for i=1 to numchildLy do(
local newLy = ly.getChild i
collectLayerNodes newLy out:out
)
)
out
)
fn CleanNames =(
-- 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 = ""
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 = (
-- 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
)
-- 최소 5개 이상의 주요 본이 있으면 유효한 것으로 간주
return (foundBones >= 5)
) else return false
)
fn createBip height spine:2 fingers:5= (
-- Triangle Pelvis는 False, Triangle Neck는 True로 설정
local bipObj = biped.createNew height -90 [0,0,height*0.9562] arms:true neckLinks:1 \
spineLinks:spine legLinks:3 \
fingers:fingers fingerLinks:3 toes:1 \
toeLinks:1 ankleAttach:0.3 trianglePelvis:False triangleNeck:True
bipObj
)
fn getRigStructures =(
local n = ""
local root = execute ("$'"+n+"Hips'")
-- 다리 본들 (부모 관계 확인)
local LLeg1 = execute ("$'"+n+"LeftUpperLeg'")
local LLeg2 = execute ("$'"+n+"LeftLowerLeg'")
local LFoot = execute ("$'"+n+"LeftFoot'")
local LToe = execute ("$'"+n+"LeftToes'")
local RLeg1 = execute ("$'"+n+"RightUpperLeg'")
local RLeg2 = execute ("$'"+n+"RightLowerLeg'")
local RFoot = execute ("$'"+n+"RightFoot'")
local RToe = execute ("$'"+n+"RightToes'")
-- 스파인 본들
local LSpine = execute ("$'"+n+"Spine'")
local LSpine1 = execute ("$'"+n+"Chest'")
local spines = #(LSpine, LSpine1)
spineSegments = 2
-- 다리가 Spine에서 시작하는 경우 처리
-- 다리 본들의 부모가 Spine인지 확인
legParentIsSpine = false
if LLeg1 != undefined and LLeg1.parent != undefined then (
if findString LLeg1.parent.name "Spine" != undefined then (
legParentIsSpine = true
format "다리가 Spine에서 시작됨을 감지\\n"
)
)
-- 어깨/팔 본들
local LClav = execute ("$'"+n+"LeftShoulder'")
local LArm1 = execute ("$'"+n+"LeftUpperArm'")
local LArm2 = execute ("$'"+n+"LeftLowerArm'")
local LHand = execute ("$'"+n+"LeftHand'")
local RClav = execute ("$'"+n+"RightShoulder'")
local RArm1 = execute ("$'"+n+"RightUpperArm'")
local RArm2 = execute ("$'"+n+"RightLowerArm'")
local RHand = execute ("$'"+n+"RightHand'")
-- 🔍 팔 구조 디버그 출력
format "\\n=== 팔 구조 디버그 ===\\n"
if RClav != undefined then (
format "RightShoulder: %\\n" RClav.name
format " 자식들: "
for child in RClav.children do format "%, " child.name
format "\\n"
)
if RArm1 != undefined then (
format "RightArm: %\\n" RArm1.name
format " 자식들: "
for child in RArm1.children do format "%, " child.name
format "\\n"
)
if RArm2 != undefined then (
format "RightForeArm: %\\n" RArm2.name
format " 자식들: "
for child in RArm2.children do format "%, " child.name
format "\\n"
)
format "========================\\n"
local neck = execute ("$'"+n+"Neck'")
local head = execute ("$'"+n+"Head'")
-- 어깨가 어디서 시작하는지 확인
shoulderParentIsNeck = false
shoulderParentIsSpine = false
if LClav != undefined and LClav.parent != undefined then (
if findString LClav.parent.name "Neck" != undefined then (
shoulderParentIsNeck = true
format "어깨가 Neck에서 시작됨을 감지\\n"
) else if findString LClav.parent.name "Spine" != undefined and
findString LClav.parent.name "Spine1" == undefined then (
shoulderParentIsSpine = true
format "어깨가 Spine에서 바로 시작됨을 감지\\n"
)
)
local LFinger1 = $'LeftThumbProximal'
local LFinger2 = $'LeftIndexProximal'
local LFinger3 = $'LeftMiddleProximal'
local LFinger4 = $'LeftRingProximal'
local LFinger5 = $'LeftLittleProximal'
local RFinger1 = $'RightThumbProximal'
local RFinger2 = $'RightIndexProximal'
local RFinger3 = $'RightMiddleProximal'
local RFinger4 = $'RightRingProximal'
local RFinger5 = $'RightLittleProximal'
-- 스켈레톤 구조에 따라 적응적으로 배치
if (legParentIsSpine and shoulderParentIsNeck) or (shoulderParentIsSpine) then (
-- 특수 스켈레톤 구조들
if shoulderParentIsSpine then (
format "특수 스켈레톤 구조 감지: 어깨->Spine\\n"
) else (
format "특수 스켈레톤 구조 감지: 다리->Spine, 어깨->Neck\\n"
)
rigNodes = #(root,root)
join rigNodes #(LLeg1, LLeg2, LFoot, LToe, RLeg1, RLeg2, RFoot, RToe) -- 다리들 먼저
join rigNodes Spines -- 스파인들
join rigNodes #(neck, head) -- 목과 머리
join rigNodes #(LClav, LArm1, LArm2, LHand, RClav, RArm1, RArm2, RHand) -- 어깨와 팔들
join rigNodes #(LFinger1,LFinger2,LFinger3,LFinger4,LFinger5, RFinger1,RFinger2,RFinger3,RFinger4,RFinger5)
) else (
-- 표준 구조
rigNodes = #(root,root)
join rigNodes Spines
join rigNodes #( LClav,
LArm1,
LArm2,
LHand,
RClav,
RArm1,
RArm2,
RHand,
neck,
head,
LLeg1,
LLeg2,
LFoot,
LToe,
RLeg1,
RLeg2,
RFoot,
RToe,
LFinger1,LFinger2,LFinger3,LFinger4,LFinger5,
RFinger1,RFinger2,RFinger3,RFinger4,RFinger5
)
)
rigNodes
)
fn GetBipStructure bip spinesegs:3= (
local bip_pelvis = biped.getNode bip #pelvis link:1
local bip_root = bip_pelvis.parent
local bip_LLeg1 = biped.getNode bip #lleg link:1
local bip_LLeg2 = biped.getNode bip #lleg link:2
local bip_LFoot = biped.getNode bip #lleg link:3
local bip_LToe = biped.getNode bip #ltoes link:1
local bip_RLeg1 = biped.getNode bip #rleg link:1
local bip_RLeg2 = biped.getNode bip #rleg link:2
local bip_RFoot = biped.getNode bip #rleg link:3
local bip_RToe = biped.getNode bip #rtoes link:1
local bip_LClav = biped.getNode bip #larm link:1
local bip_LArm1 = biped.getNode bip #larm link:2
local bip_LArm2 = biped.getNode bip #larm link:3
local bip_LHand = biped.getNode bip #larm link:4
local bip_RClav = biped.getNode bip #rarm link:1
local bip_RArm1 = biped.getNode bip #rarm link:2
local bip_RArm2 = biped.getNode bip #rarm link:3
local bip_RHand = biped.getNode bip #rarm link:4
local bip_neck = biped.getNode bip #neck link:1
local bip_head = biped.getNode bip #head link:1
local bip_Lfinger0 = biped.getNode bip #lfingers link:1
local bip_Lfinger1 = biped.getNode bip #lfingers link:4
local bip_Lfinger2 = biped.getNode bip #lfingers link:7
local bip_Lfinger3 = biped.getNode bip #lfingers link:10
local bip_Lfinger4 = biped.getNode bip #lfingers link:13
local bip_Rfinger0 = biped.getNode bip #rfingers link:1
local bip_Rfinger1 = biped.getNode bip #rfingers link:4
local bip_Rfinger2 = biped.getNode bip #rfingers link:7
local bip_Rfinger3 = biped.getNode bip #rfingers link:10
local bip_Rfinger4 = biped.getNode bip #rfingers link:13
-- 스켈레톤 구조에 따라 적응적으로 배치 (getRigStructures와 동일 순서)
if (legParentIsSpine and shoulderParentIsNeck) or (shoulderParentIsSpine) then (
-- 특수 구조들
format "Biped도 특수 구조로 배치\\n"
bipNodes = #(bip_root,bip_pelvis)
-- 다리들 먼저
join bipNodes #(bip_LLeg1, bip_LLeg2, bip_LFoot, bip_LToe, bip_RLeg1, bip_RLeg2, bip_RFoot, bip_RToe)
-- 스파인들
for i=1 to spinesegs do append bipNodes (biped.getNode bip #spine link:i)
-- 목과 머리
join bipNodes #(bip_neck, bip_head)
-- 어깨와 팔들
join bipNodes #(bip_LClav, bip_LArm1, bip_LArm2, bip_LHand, bip_RClav, bip_RArm1, bip_RArm2, bip_RHand)
-- 손가락들
join bipNodes #(bip_Lfinger0,bip_Lfinger1,bip_Lfinger2,bip_Lfinger3,bip_Lfinger4, bip_Rfinger0,bip_Rfinger1,bip_Rfinger2,bip_Rfinger3,bip_Rfinger4)
) else (
-- 표준 구조
bipNodes = #(bip_root,bip_pelvis)
--SPINES
for i=1 to spinesegs do append bipNodes (biped.getNode bip #spine link:i)
join bipNodes #(bip_LClav,
bip_LArm1,
bip_LArm2,
bip_LHand,
bip_RClav,
bip_RArm1,
bip_RArm2,
bip_RHand,
bip_neck,
bip_head,
bip_LLeg1,
bip_LLeg2,
bip_LFoot,
bip_LToe,
bip_RLeg1,
bip_RLeg2,
bip_RFoot,
bip_RToe,
bip_Lfinger0,bip_Lfinger1,bip_Lfinger2,bip_Lfinger3,bip_Lfinger4,
bip_Rfinger0,bip_Rfinger1,bip_Rfinger2,bip_Rfinger3,bip_Rfinger4
)
)
bipNodes
)
fn MapTMAxis sourceTM targetTM indexes mult:[1,1,1]=(
axis = #(sourceTM.row1,sourceTM.row2,sourceTM.row3)
targetTM.row1 = axis[indexes[1]]*mult[1]
targetTM.row2 = axis[indexes[2]]*mult[2]
targetTM.row3 = axis[indexes[3]]*mult[3]
targetTM
)
fn SetClavicleTM bipCtl bipClav TM =(
local figMode = bipCtl.figuremode
bipCtl.figuremode = false -- FORCE EXIT FIGURE MODE
biped.setTransform bipClav #rotation TM.rotationPart false
-- COPY POSTURE
select bipClav
local col = biped.createCopyCollection bipCtl "Shoulder"
local cpy = biped.copyBipPosture bipCtl col (selection as array) #snapAuto
-- PASTE POSTURE ON FIGURE MODE
bipCtl.figuremode = true
biped.pasteBipPosture bipCtl cpy false #pstcopied false false false false
biped.deleteAllCopyCollections bipCtl
biped.setTransform bipClav #pos TM.row4 false
clearSelection()
bipCtl.figuremode = figMode
)
fn alignFootTpose rigObj BipObj =(
local RigTM = rigObj.transform
-- 이름으로 Toe 본 찾기
local toe = undefined
if findString rigObj.name "LeftFoot" != undefined then (
toe = execute ("$'LeftToes'")
) else if findString rigObj.name "RightFoot" != undefined then (
toe = execute ("$'RightToes'")
)
-- 백업: 이름으로 못 찾으면 children[1] 사용
if toe == undefined do toe = rigObj.children[1]
local endPoint = toe.pos
local height = abs (endPoint[3]-RigTM.row4[3])
endPoint[3] = RigTM.row4[3]
local v = endPoint-RigTM.row4
local len = length v
local y = normalize(v)
local x = [0,0,-1]
local z = normalize(cross x y)
local TM = Matrix3 x y z RigTM.row4
-- #SET ROTATION
biped.setTransform BipObj #rotation TM.rotationpart false
biped.setTransform bipObj #scale [height,len*1.25,len*0.6] false
biped.setTransform BipObj #pos RigTM.row4 false
)
fn alignFoot rigObj BipObj figureMode:true =(
local isSetKey = not figureMode
local RigTM = rigObj.transform
-- 일반 바이패드 발 방향 사용 (복잡한 각도 계산 제거)
-- 위치만 설정하고 회전은 바이패드 기본값 유지
biped.setTransform BipObj #pos RigTM.row4 isSetKey
-- SET SCALE (ONLY IN FIGURE MODE)
if figureMode do(
-- 발 크기 계산 (단순화)
local toe = undefined
if findString rigObj.name "LeftFoot" != undefined then (
toe = execute ("$'LeftToes'")
) else if findString rigObj.name "RightFoot" != undefined then (
toe = execute ("$'RightToes'")
)
-- 백업: 이름으로 못 찾으면 children[1] 사용
if toe == undefined do toe = rigObj.children[1]
if isValidNode toe then (
local footLength = distance rigObj.pos toe.pos
local footHeight = abs(rigObj.pos[3] - toe.pos[3])
-- 바이패드 발 크기 설정 (비례 유지)
biped.setTransform bipObj #scale [footHeight, footLength, footLength*0.6] isSetKey
) else (
-- 기본 크기 설정
biped.setTransform bipObj #scale [1, 1, 0.6] isSetKey
)
)
)
fn AlignArm BipUpperArm MxUpperArm side:"R" FigureMode:false TPose:false =
(
local createKey = not FigureMode
-- Biped 본들을 이름으로 찾기
local BipLowerArm = undefined
local Biphand = undefined
if side == "R" then (
BipLowerArm = execute ("$'Bip001 R Forearm'")
Biphand = execute ("$'Bip001 R Hand'")
) else (
BipLowerArm = execute ("$'Bip001 L Forearm'")
Biphand = execute ("$'Bip001 L Hand'")
)
-- 백업: 이름으로 못 찾으면 children[1] 사용
if BipLowerArm == undefined do BipLowerArm = BipUpperArm.children[1]
if Biphand == undefined do Biphand = BipLowerArm.children[1]
local Bip_UpperTM = BipUpperArm.transform
local Bip_LowerTM = BipLowerArm.transform
local Bip_HandTM = Biphand.transform
-- 정확한 본 이름으로 찾기 (children[1] 대신)
local MxForeArm = undefined
local MxHand = undefined
if side == "R" then (
MxForeArm = execute ("$'RightLowerArm'")
MxHand = execute ("$'RightHand'")
) else (
MxForeArm = execute ("$'LeftLowerArm'")
MxHand = execute ("$'LeftHand'")
)
-- 백업: 이름으로 못 찾으면 children[1] 사용
if MxForeArm == undefined do MxForeArm = MxUpperArm.children[1]
if MxHand == undefined do MxHand = MxForeArm.children[1]
local MX_UpperTM = MxUpperArm.transform
local MX_ElbowTM = MxForeArm.transform
local MX_HandTM = MxHand.transform
-- SET SCALE
if FigureMode do(
len = distance MxUpperArm MxForeArm
biped.setTransform BipUpperArm #scale [len,len,len] createKey
len = distance MxForeArm MxHand
biped.setTransform BipLowerArm #scale [len,len,len] createKey
-- 손 크기 계산 (기본값 사용)
len *= 0.2
biped.setTransform Biphand #scale [len,len,len] createKey
)
local mid = (MX_HandTM.row4+MX_UpperTM.row4)*0.5
local up = Normalize (mid-MX_ElbowTM.row4)
if TPose do up = [0,-1,0]
--SET HAND FIRST BECAUSE THE IK
biped.setTransform Biphand #pos MX_HandTM.row4 createKey
-- UpperArm
local x = Normalize (MX_ElbowTM.row4-Bip_UpperTM.row4)
local z = Normalize (cross up x )
local y = Normalize (cross z x)
local upperTM = matrix3 x y z MX_UpperTM.row4
-- UpperArm: 기하학적 계산은 유지, 축 변환만 제거
biped.setTransform BipUpperArm #rotation upperTM.rotationpart createKey
-- Hand: 위치만 설정, 축 변환 제거
biped.setTransform Biphand #pos MX_HandTM.row4 createKey
)
fn AlignLeg BipUpperLeg MxUpperLeg side:"R" FigureMode:false TPose:false =
(
local createKey = not FigureMode
-- Biped 본들을 이름으로 찾기
local BipLoweLeg = undefined
local BipFoot = undefined
if side == "R" then (
BipLoweLeg = execute ("$'Bip001 R Calf'")
BipFoot = execute ("$'Bip001 R Foot'")
) else (
BipLoweLeg = execute ("$'Bip001 L Calf'")
BipFoot = execute ("$'Bip001 L Foot'")
)
-- 백업: 이름으로 못 찾으면 children[1] 사용
if BipLoweLeg == undefined do BipLoweLeg = BipUpperLeg.children[1]
if BipFoot == undefined do BipFoot = BipLoweLeg.children[1]
-- Unity Human Bone들을 이름으로 찾기
local MxLowerLeg = undefined
local MxFoot = undefined
if side == "R" then (
MxLowerLeg = execute ("$'RightLowerLeg'")
MxFoot = execute ("$'RightFoot'")
) else (
MxLowerLeg = execute ("$'LeftLowerLeg'")
MxFoot = execute ("$'LeftFoot'")
)
-- 백업: 이름으로 못 찾으면 children[1] 사용
if MxLowerLeg == undefined do MxLowerLeg = MxUpperLeg.children[1]
if MxFoot == undefined do MxFoot = MxLowerLeg.children[1]
local rigTM = MxUpperLeg.transform
local BipUpperTM = BipUpperLeg.transform
local KneeTM = MxLowerLeg.transform
local endTM = MxFoot.transform
if FigureMode do(
dist = distance MxUpperLeg MxLowerLeg
biped.setTransform BipUpperLeg #scale [dist,dist,dist] createKey
dist = distance MxLowerLeg MxFoot
biped.setTransform BipLoweLeg #scale [dist,dist,dist] createKey
)
local mid = (endTM.row4+rigTM.row4)*0.5
local up = Normalize (mid-KneeTM.row4)
if TPose do up = [0,1,0]
-- Foot
biped.setTransform BipFoot #pos endTM.row4 createKey
-- UpperLeg
local x = Normalize (KneeTM.row4-BipUpperTM.row4)
local z = Normalize (cross up x )
local y = Normalize (cross z x)
local newTM = matrix3 x y z rigTM.row4
-- UpperLeg: 기하학적 계산은 유지, 축 변환만 제거
biped.setTransform BipUpperLeg #rotation newTM.rotationpart createKey
)
fn alignFingers BipNode MXNode figureMode:false=(
/*
손가락 정렬 함수 - 최적화 버전
잠재적 충돌: 손가락 구조가 다르거나 개수가 다른 경우
안전장치: 각 관절별 유효성 검사 및 폴백
*/
-- 빠른 종료 조건
if not figureMode do return true
if not isValidNode BipNode or not isValidNode MXNode do return false
-- Biped 손가락 본들을 이름으로 찾기 (첫 번째 관절만)
local bip2 = undefined
local bip3 = undefined
-- Biped 손가락 이름 패턴에 따라 찾기
if findString BipNode.name "Finger0" != undefined then (
-- 엄지손가락
if findString BipNode.name " R " != undefined then (
bip2 = execute ("$'Bip001 R Finger01'")
bip3 = execute ("$'Bip001 R Finger02'")
) else (
bip2 = execute ("$'Bip001 L Finger01'")
bip3 = execute ("$'Bip001 L Finger02'")
)
) else if findString BipNode.name "Finger1" != undefined then (
-- 검지손가락
if findString BipNode.name " R " != undefined then (
bip2 = execute ("$'Bip001 R Finger11'")
bip3 = execute ("$'Bip001 R Finger12'")
) else (
bip2 = execute ("$'Bip001 L Finger11'")
bip3 = execute ("$'Bip001 L Finger12'")
)
) else if findString BipNode.name "Finger2" != undefined then (
-- 중지손가락
if findString BipNode.name " R " != undefined then (
bip2 = execute ("$'Bip001 R Finger21'")
bip3 = execute ("$'Bip001 R Finger22'")
) else (
bip2 = execute ("$'Bip001 L Finger21'")
bip3 = execute ("$'Bip001 L Finger22'")
)
) else if findString BipNode.name "Finger3" != undefined then (
-- 약지손가락
if findString BipNode.name " R " != undefined then (
bip2 = execute ("$'Bip001 R Finger31'")
bip3 = execute ("$'Bip001 R Finger32'")
) else (
bip2 = execute ("$'Bip001 L Finger31'")
bip3 = execute ("$'Bip001 L Finger32'")
)
) else if findString BipNode.name "Finger4" != undefined then (
-- 새끼손가락
if findString BipNode.name " R " != undefined then (
bip2 = execute ("$'Bip001 R Finger41'")
bip3 = execute ("$'Bip001 R Finger42'")
) else (
bip2 = execute ("$'Bip001 L Finger41'")
bip3 = execute ("$'Bip001 L Finger42'")
)
)
-- 백업: 이름으로 못 찾으면 children[1] 사용
if bip2 == undefined and BipNode.children.count > 0 do bip2 = BipNode.children[1]
if bip3 == undefined and bip2 != undefined and bip2.children.count > 0 do bip3 = bip2.children[1]
local BipNodes = #(BipNode,bip2,bip3)
-- Unity Human Bone 손가락 본들을 이름으로 찾기
local MX2 = undefined
local MX3 = undefined
local MX4 = undefined
-- Unity Human Bone 손가락 이름 패턴에 따라 찾기
if findString MXNode.name "Thumb" != undefined then (
-- 엄지손가락
if findString MXNode.name "Right" != undefined then (
MX2 = execute ("$'RightThumbIntermediate'")
MX3 = execute ("$'RightThumbDistal'")
) else (
MX2 = execute ("$'LeftThumbIntermediate'")
MX3 = execute ("$'LeftThumbDistal'")
)
) else if findString MXNode.name "Index" != undefined then (
-- 검지손가락
if findString MXNode.name "Right" != undefined then (
MX2 = execute ("$'RightIndexIntermediate'")
MX3 = execute ("$'RightIndexDistal'")
) else (
MX2 = execute ("$'LeftIndexIntermediate'")
MX3 = execute ("$'LeftIndexDistal'")
)
) else if findString MXNode.name "Middle" != undefined then (
-- 중지손가락
if findString MXNode.name "Right" != undefined then (
MX2 = execute ("$'RightMiddleIntermediate'")
MX3 = execute ("$'RightMiddleDistal'")
) else (
MX2 = execute ("$'LeftMiddleIntermediate'")
MX3 = execute ("$'LeftMiddleDistal'")
)
) else if findString MXNode.name "Ring" != undefined then (
-- 약지손가락
if findString MXNode.name "Right" != undefined then (
MX2 = execute ("$'RightRingIntermediate'")
MX3 = execute ("$'RightRingDistal'")
) else (
MX2 = execute ("$'LeftRingIntermediate'")
MX3 = execute ("$'LeftRingDistal'")
)
) else if findString MXNode.name "Pinky" != undefined then (
-- 새끼손가락
if findString MXNode.name "Right" != undefined then (
MX2 = execute ("$'RightLittleIntermediate'")
MX3 = execute ("$'RightLittleDistal'")
) else (
MX2 = execute ("$'LeftLittleIntermediate'")
MX3 = execute ("$'LeftLittleDistal'")
)
)
-- 백업: 이름으로 못 찾으면 children[1] 사용
if MX2 == undefined and MXNode.children.count > 0 do MX2 = MXNode.children[1]
if MX3 == undefined and MX2 != undefined and MX2.children.count > 0 do MX3 = MX2.children[1]
if MX4 == undefined and MX3 != undefined and MX3.children.count > 0 do MX4 = MX3.children[1]
local MXNodes = #(MXNode,MX2,MX3,MX4)
-- 크기 및 위치 조정 (최적화된 Transform 사용)
for i=1 to 3 where isvalidNode MXNodes[i] and isvalidNode BipNodes[i] do(
local len = 1.0
if isValidNode MXNodes[i+1] then
len = distance MXNodes[i] MXNodes[i+1]
else if MXNodes[i].parent != undefined then
len = distance MXNodes[i] MXNodes[i].parent
local pos = if i==1 then MXNodes[i].transform.row4 else undefined
local scale = [len,len,len]
optimizedBipedTransform BipNodes[i] pos:pos scale:scale
)
-- 손가락 펴기: 최적화된 배치 처리
if OPTIMIZE_FINGER_SETUP then (
try (
local isThumb = (findString BipNode.name "Finger0" != undefined)
local isLeftHand = (findString BipNode.name " L " != undefined) or (findString BipNode.name "Left" != undefined)
local rotations = #()
-- 회전 데이터 준비 (미리 계산)
case of (
-- === 왼손 엄지 ===
(isThumb and isLeftHand): (
rotations = #(
(eulerAngles -90 10 -30),
(eulerAngles -90 10 -30),
(eulerAngles -90 10 -30)
)
)
-- === 오른손 엄지 ===
(isThumb and not isLeftHand): (
rotations = #(
(eulerAngles -90 10 -150),
(eulerAngles -90 10 -150),
(eulerAngles -90 10 -150)
)
)
-- === 왼손 다른 손가락들 ===
(not isThumb and isLeftHand): (
rotations = #(
(eulerAngles -90 0 0),
(eulerAngles -100 0 0),
(eulerAngles -105 0 0)
)
)
-- === 오른손 다른 손가락들 ===
(not isThumb and not isLeftHand): (
rotations = #(
(eulerAngles -90 0 180),
(eulerAngles -100 0 180),
(eulerAngles -105 0 180)
)
)
)
-- 배치 회전 적용
if rotations.count > 0 do
optimizedFingerRotations BipNodes rotations
) catch (
-- 실패 시 기존 방식으로 폴백
if VERBOSE_LOGGING do format "⚠️ 손가락 최적화 실패, 기존 방식 사용: %\n" BipNode.name
local isThumb = (findString BipNode.name "Finger0" != undefined)
local isLeftHand = (findString BipNode.name " L " != undefined) or (findString BipNode.name "Left" != undefined)
-- === 기존 방식 폴백 ===
if isThumb and isLeftHand then (
if isvalidNode BipNodes[1] do biped.setTransform BipNodes[1] #rotation (eulerAngles -90 10 -30) false
if isvalidNode BipNodes[2] do biped.setTransform BipNodes[2] #rotation (eulerAngles -90 10 -30) false
if isvalidNode BipNodes[3] do biped.setTransform BipNodes[3] #rotation (eulerAngles -90 10 -30) false
) else if isThumb and not isLeftHand then (
if isvalidNode BipNodes[1] do biped.setTransform BipNodes[1] #rotation (eulerAngles -90 10 -150) false
if isvalidNode BipNodes[2] do biped.setTransform BipNodes[2] #rotation (eulerAngles -90 10 -150) false
if isvalidNode BipNodes[3] do biped.setTransform BipNodes[3] #rotation (eulerAngles -90 10 -150) false
) else if not isThumb and isLeftHand then (
if isvalidNode BipNodes[1] do biped.setTransform BipNodes[1] #rotation (eulerAngles -90 0 0) false
if isvalidNode BipNodes[2] do biped.setTransform BipNodes[2] #rotation (eulerAngles -100 0 0) false
if isvalidNode BipNodes[3] do biped.setTransform BipNodes[3] #rotation (eulerAngles -105 0 0) false
) else if not isThumb and not isLeftHand then (
if isvalidNode BipNodes[1] do biped.setTransform BipNodes[1] #rotation (eulerAngles -90 0 180) false
if isvalidNode BipNodes[2] do biped.setTransform BipNodes[2] #rotation (eulerAngles -100 0 180) false
if isvalidNode BipNodes[3] do biped.setTransform BipNodes[3] #rotation (eulerAngles -105 0 180) false
)
)
) else (
-- 최적화 비활성화 시 기존 방식 사용
local isThumb = (findString BipNode.name "Finger0" != undefined)
local isLeftHand = (findString BipNode.name " L " != undefined) or (findString BipNode.name "Left" != undefined)
if isThumb and isLeftHand then (
if isvalidNode BipNodes[1] do biped.setTransform BipNodes[1] #rotation (eulerAngles -90 10 -30) false
if isvalidNode BipNodes[2] do biped.setTransform BipNodes[2] #rotation (eulerAngles -90 10 -30) false
if isvalidNode BipNodes[3] do biped.setTransform BipNodes[3] #rotation (eulerAngles -90 10 -30) false
) else if isThumb and not isLeftHand then (
if isvalidNode BipNodes[1] do biped.setTransform BipNodes[1] #rotation (eulerAngles -90 10 -150) false
if isvalidNode BipNodes[2] do biped.setTransform BipNodes[2] #rotation (eulerAngles -90 10 -150) false
if isvalidNode BipNodes[3] do biped.setTransform BipNodes[3] #rotation (eulerAngles -90 10 -150) false
) else if not isThumb and isLeftHand then (
if isvalidNode BipNodes[1] do biped.setTransform BipNodes[1] #rotation (eulerAngles -90 0 0) false
if isvalidNode BipNodes[2] do biped.setTransform BipNodes[2] #rotation (eulerAngles -100 0 0) false
if isvalidNode BipNodes[3] do biped.setTransform BipNodes[3] #rotation (eulerAngles -105 0 0) false
) else if not isThumb and not isLeftHand then (
if isvalidNode BipNodes[1] do biped.setTransform BipNodes[1] #rotation (eulerAngles -90 0 180) false
if isvalidNode BipNodes[2] do biped.setTransform BipNodes[2] #rotation (eulerAngles -100 0 180) false
if isvalidNode BipNodes[3] do biped.setTransform BipNodes[3] #rotation (eulerAngles -105 0 180) false
)
)
true
)
fn BipToRigProportion bipNodes rigNodes TPose:false =(
/*
Biped 비율 매칭 함수 - 최적화 버전
잠재적 충돌: Transform 순서 변경, Figure Mode 상태
안전장치: Figure Mode 관리자, 배치 처리, 진행률 표시
*/
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()
-- 진행률 초기화
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)}
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
for i in matchIndexes where isvalidnode bipNodes[i] do(
n = bipNodes[i].name
bipNode = bipNodes[i]
rigNode = rigNodes[i]
if isvalidNode bipNode and isvalidNode rigNode do(
rigNodeEnd = rigNodes[i+1]
rigTM = rigNode.transform
BipTM = bipNode.transform
case of(
(i==1):(
-- Root: 위치만 조정, 축 변환 제거
biped.setTransform bipNode #pos p false
)
(findString n "Head"!= undefined): (
-- Head의 자식은 Neck이므로 이름으로 찾기
rigNodeEnd = execute ("$'Neck'")
if isvalidNode rigNodeEnd do(
-- Head: 크기와 위치만 조정, 축 변환 제거
dist = distance rigNode rigNodeEnd
biped.setTransform bipNode #scale [dist,dist,dist] false
biped.setTransform bipNode #pos rigNode.transform.row4 false
)
)
(matchPattern n pattern:"*Neck*"):(
-- Neck: 크기만 조정, 축 변환 제거
dist = distance rigNode rigNodeEnd
biped.setTransform bipNode #scale [dist,dist,dist] false
)
(matchPattern n pattern:"*Foot*" ):(
if TPose then alignFootTpose rigNode bipNode
else alignFoot rigNode bipNode figureMode:true
)
(matchPattern n pattern:"*Toe*"):(
-- Toe의 자식은 없으므로 기본 크기 사용
-- Toe: 크기와 위치만 조정, 축 변환 제거
biped.setTransform bipNode #scale [1,1*0.2,1] false
biped.setTransform bipNode #pos rigNode.pos false
)
(findString n "Pelvis"!= undefined ):(
rigNode = $'LeftUpperLeg'
rigNodeEnd = $'RightUpperLeg'
dist = distance Lthigh Rthigh
len = distance rigNode rigNodeEnd
biped.setTransform bipNode #scale [dist,dist,dist] false
)
(MatchPattern n pattern:"*Spine*"):(
-- 스파인 2개 구조에서 Spine1이 마지막 스파인
if findString n "Spine1"!=undefined do (
rigNodeEnd =$'Neck'
)
if isvalidNode rigNode and isvalidNode rigNodeEnd do(
len = distance rigNode rigNodeEnd
biped.setTransform bipNode #scale [len,len,len] false
)
if matchpattern n pattern:"*Spine*" do(
-- Spine: 위치만 조정, 축 변환 제거
biped.setTransform bipNode #pos rigNode.pos false
)
)
(matchPattern n pattern:"*Clavicle*"):(
-- 정확한 Arm 본 찾기 (children[1] 대신)
rigNodeEnd = undefined
if findString n "L Clavicle" != undefined or findString n "LeftShoulder" != undefined then (
rigNodeEnd = execute ("$'LeftUpperArm'")
) else if findString n "R Clavicle" != undefined or findString n "RightShoulder" != undefined then (
rigNodeEnd = execute ("$'RightUpperArm'")
)
-- 백업: 이름으로 못 찾으면 children[1] 사용
if rigNodeEnd == undefined do rigNodeEnd = rigNode.children[1]
len = distance rigNode rigNodeEnd
x = Normalize (rigNodeEnd.pos-rigNode.pos)
z = Normalize (Cross (bipNode.parent.transform.row2) x)
y = Normalize (cross z x)
newTM = matrix3 x y z rigTM.row4
biped.setTransform bipNode #scale [len,len,len] false
SetClavicleTM bipCtl bipNode newTM
)
(matchPattern n pattern:"*UpperArm*"):(
if MatchPattern n pattern:"* R *" then AlignArm bipNode rigNode side:"R" FigureMode:true TPose:TPose
else AlignArm bipNode rigNode side:"L" FigureMode:true TPose:TPose
)
(matchPattern n pattern:"*Thigh*"):(
if MatchPattern n pattern:"* R *" then AlignLeg bipNode rigNode side:"R" figureMode:true TPose:TPose
else AlignLeg bipNode rigNode side:"L" figureMode:true TPose:TPose
)
(MatchPattern n pattern:"*Finger*"): alignFingers bipNode rigNode figureMode:true
)
)
)
) catch (
format "❌ 비율 매칭 중 오류 발생: %\n" (getCurrentException())
false
) finally (
-- Figure Mode 안전하게 종료
figManager.exitMode()
)
stepPerf "비율 매칭 완료"
true
)
fn getNumFingers = (
-- Unity Human Bone 표준 손가락 본의 정확한 이름들 (첫 번째 관절만)
local fingerNames = #(
"LeftThumbProximal", "LeftIndexProximal", "LeftMiddleProximal",
"LeftRingProximal", "LeftLittleProximal",
"RightThumbProximal", "RightIndexProximal", "RightMiddleProximal",
"RightRingProximal", "RightLittleProximal"
)
local leftFingers = 0
local rightFingers = 0
-- 왼손 손가락 체크
for i = 1 to 5 do (
if isValidNode (getNodeByName fingerNames[i]) do leftFingers += 1
)
-- 오른손 손가락 체크
for i = 6 to 10 do (
if isValidNode (getNodeByName fingerNames[i]) do rightFingers += 1
)
local maxFingers = amax #(leftFingers, rightFingers)
format "손가락 본 체크: 왼손 %개, 오른손 %개\\n" leftFingers rightFingers
-- 3ds Max Biped 제한 (0-5)
if maxFingers > 5 then (
format "⚠️ 손가락 수 %개 → 5개로 제한\\n" maxFingers
maxFingers = 5
)
format "최종 Biped fingers: %\\n" maxFingers
maxFingers
)
fn GetExtras root=(
local out = #()
if root!=undefined do(
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(
-- 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
)
fn cleanUpSkin obj =(
local skn = obj.modifiers["skin"]
local count = skinOps.GetNumberBones skn
local IDList = #()
for i=1 to count do(
n = (skinOps.GetBoneName skn i 0)
if findItem excludeBones n!=0 do(
append IDList i
)
)
if IDList.count>0 do for i=IDList.count to 1 by -1 do(
skinOps.removeBone skn IDList[i]
)
)
fn GetSkinPairs obj extra:#() =(
local out=#()
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(
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:"*Spine2"): dp.v2 = "Bip001 Spine2"
(matchpattern n pattern:"*Neck"): dp.v2 = "Bip001 Neck"
(matchpattern n pattern:"*Head"): dp.v2 = "Bip001 Head"
(matchpattern n pattern:"*LeftShoulder"): dp.v2 = "Bip001 L Clavicle"
(matchpattern n pattern:"*LeftArm"): dp.v2 = "Bip001 L UpperArm"
(matchpattern n pattern:"*LeftForeArm"): dp.v2 = "Bip001 L Forearm"
(matchpattern n pattern:"*LeftHand"): dp.v2 = "Bip001 L Hand"
(matchpattern n pattern:"*LeftHandThumb1"): dp.v2 = "Bip001 L Finger0"
(matchpattern n pattern:"*LeftHandThumb2"): dp.v2 = "Bip001 L Finger01"
(matchpattern n pattern:"*LeftHandThumb3"): dp.v2 = "Bip001 L Finger02"
(matchpattern n pattern:"*LeftHandThumb4"): dp.v2 = "Bip001 L Finger0Nub"
(matchpattern n pattern:"*LeftHandIndex1"): dp.v2 = "Bip001 L Finger1"
(matchpattern n pattern:"*LeftHandIndex2"): dp.v2 = "Bip001 L Finger11"
(matchpattern n pattern:"*LeftHandIndex3"): dp.v2 = "Bip001 L Finger12"
-- (matchpattern n pattern:"*LeftHandIndex4"): dp.v2 = "Bip001 L Finger1Nub" -- 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"
(matchpattern n pattern:"*LeftHandMiddle4"): dp.v2 = "Bip001 L Finger2Nub"
(matchpattern n pattern:"*LeftHandRing1"): dp.v2 = "Bip001 L Finger3"
(matchpattern n pattern:"*LeftHandRing2"): dp.v2 = "Bip001 L Finger31"
(matchpattern n pattern:"*LeftHandRing3"): dp.v2 = "Bip001 L Finger32"
(matchpattern n pattern:"*LeftHandRing4"): dp.v2 = "Bip001 L Finger3Nub"
(matchpattern n pattern:"*LeftHandPinky1"): dp.v2 = "Bip001 L Finger4"
(matchpattern n pattern:"*LeftHandPinky2"): dp.v2 = "Bip001 L Finger41"
(matchpattern n pattern:"*LeftHandPinky3"): dp.v2 = "Bip001 L Finger42"
(matchpattern n pattern:"*LeftHandPinky4"): dp.v2 = "Bip001 L Finger4Nub"
(matchpattern n pattern:"*RightShoulder"): dp.v2 = "Bip001 R Clavicle"
(matchpattern n pattern:"*RightArm"): dp.v2 = "Bip001 R UpperArm"
(matchpattern n pattern:"*RightForeArm"): dp.v2 = "Bip001 R Forearm"
(matchpattern n pattern:"*RightHand"): dp.v2 = "Bip001 R Hand"
(matchpattern n pattern:"*RightHandThumb1"): dp.v2 = "Bip001 R Finger0"
(matchpattern n pattern:"*RightHandThumb2"): dp.v2 = "Bip001 R Finger01"
(matchpattern n pattern:"*RightHandThumb3"): dp.v2 = "Bip001 R Finger02"
(matchpattern n pattern:"*RightHandThumb4"): dp.v2 = "Bip001 R Finger0Nub"
(matchpattern n pattern:"*RightHandIndex1"): dp.v2 = "Bip001 R Finger1"
(matchpattern n pattern:"*RightHandIndex2"): dp.v2 = "Bip001 R Finger11"
(matchpattern n pattern:"*RightHandIndex3"): dp.v2 = "Bip001 R Finger12"
-- (matchpattern n pattern:"*RightHandIndex4"): dp.v2 = "Bip001 R Finger1Nub" -- 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"
(matchpattern n pattern:"*RightHandMiddle4"): dp.v2 = "Bip001 R Finger2Nub"
(matchpattern n pattern:"*RightHandRing1"): dp.v2 = "Bip001 R Finger3"
(matchpattern n pattern:"*RightHandRing2"): dp.v2 = "Bip001 R Finger31"
(matchpattern n pattern:"*RightHandRing3"): dp.v2 = "Bip001 R Finger32"
(matchpattern n pattern:"*RightHandRing4"): dp.v2 = "Bip001 R Finger3Nub"
(matchpattern n pattern:"*RightHandPinky1"): dp.v2 = "Bip001 R Finger4"
(matchpattern n pattern:"*RightHandPinky2"): dp.v2 = "Bip001 R Finger41"
(matchpattern n pattern:"*RightHandPinky3"): dp.v2 = "Bip001 R Finger42"
(matchpattern n pattern:"*RightHandPinky4"): dp.v2 = "Bip001 R Finger4Nub"
(matchpattern n pattern:"*LeftUpLeg"): dp.v2 = "Bip001 L Thigh"
(matchpattern n pattern:"*LeftLeg"): dp.v2 = "Bip001 L Calf"
(matchpattern n pattern:"*LeftFoot"): dp.v2 = "Bip001 L Foot"
(matchpattern n pattern:"*LeftToeBase"): dp.v2 = "Bip001 L Toe0"
(matchpattern n pattern:"*RightUpLeg"): dp.v2 = "Bip001 R Thigh"
(matchpattern n pattern:"*RightLeg"): dp.v2 = "Bip001 R Calf"
(matchpattern n pattern:"*RightFoot"): dp.v2 = "Bip001 R Foot"
(matchpattern n pattern:"*RightToeBase"): dp.v2 = "Bip001 R Toe0"
default:(foundPairs = false )
)
local indexInExtra = findItem extraV1 n
if not foundPairs and indexInExtra != 0 then(
dp.v2 = extra[indexInExtra].v2
)
else if not foundPairs do append noPairs n
append out dp
)
out
)
-- ================================== 최적화된 스킨 처리 함수들 ===============================================
-- 본 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 findBoneIDByMappingOld oldBoneName bonePairs newSkin =(
/*
본 매핑을 통해 새 스킨에서 본 ID 찾기
잠재적 충돌: 본 이름이 일치하지 않는 경우
안전장치: 이름 매핑 테이블 사용 및 fallback
*/
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
)
)
0 -- 찾지 못함
),
fn transferWeights =(
/*
가중치 데이터 직접 메모리 전송
잠재적 충돌: 버텍스 수 불일치, 본 구조 변경
안전장치: 각 버텍스별 유효성 검사
*/
if not OPTIMIZE_SKIN_TRANSFER do return false
try (
local numVerts = skinOps.getNumberVertices oldSkin
local successCount = 0
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 boneID = skinOps.getVertexWeightBoneID oldSkin v b
local oldBoneName = skinOps.GetBoneName oldSkin boneID 0
-- 새 본 ID 찾기
local newBoneID = findBoneIDByMapping oldBoneName bonePairs newSkin
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 then (
for i = 1 to weights.count do weights[i] /= totalWeight
skinOps.replaceVertexWeights newSkin v boneIDs weights
successCount += 1
)
)
)
if VERBOSE_LOGGING do format " 📊 성공적으로 전송된 버텍스: %d/%d\n" successCount numVerts
true
) catch (
if VERBOSE_LOGGING do format "⚠️ 메모리 기반 스킨 전송 실패\n"
false
)
)
)
-- 슈퍼 고속 스킨 처리 (배치 최적화)
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 =(
/*
모든 스킨 오브젝트를 최적화된 방식으로 처리
잠재적 충돌: 메모리 부족, 스킨 구조 변경
안전장치: 메모리 기반 실패 시 기존 방식으로 폴백
*/
local uiOpt = SuperUIOptimizer()
uiOpt.suspend() -- 슈퍼 UI 업데이트 억제
try (
local progress = ProgressManager()
progress.init skinObjects.count
for i in skinObjects do (
progress.update ("스킨 처리: " + i.name)
local skn = i.modifiers["skin"]
if skn == undefined do continue
-- 페어 가져오기 (표준 본 + 엑스트라 본)
local pairs = GetSkinPairs i extra:extrasPairs
processSkinnedObjectStable i pairs
-- 모디파이어 스택 정리
maxOps.CollapseNodeTo i 2 true
)
) catch (
format "❌ 스킨 처리 중 오류: %\n" (getCurrentException())
) finally (
uiOpt.resume() -- UI 업데이트 재개
)
)
-- 안정적인 파일 I/O 방식 스킨 처리 (copy 버전과 동일)
fn processSkinnedObjectStable skinObj pairs =(
/*
copy 버전과 정확히 동일한 파일 I/O 방식의 스킨 처리
안전하지만 느림
*/
local skn = skinObj.modifiers["skin"]
local pth = GetDir #export
local n = skinObj.name+"_skin.env"
local f = (pth+"\\"+n)
-- 기존 스킨 데이터 저장
if skn != undefined then
skinOps.saveEnvelope skn f
local bipedSkin = #()
for j in pairs do (
local obj1 = getNodeByName j.v1
local obj2 = getNodeByName j.v2
if isvalidNode obj1 and isValidNode obj2 do(
tempName = obj1.name
obj1.name = obj2.name
obj2.name = tempName
append bipedSkin obj2
)
)
-- 새 스킨 추가
local skn2 = skin()
addmodifier skinObj skn2
select skinObj
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
)
max create mode
-- 스킨 데이터 로드
if doesFileExist f then
skinOps.loadEnvelope skn2 f
-- 이름 되돌리기
for j in pairs do (
local obj1 = getNodeByName j.v1
local obj2 = getNodeByName j.v2
if isValidNode obj1 and isValidNode obj2 do(
tempName = obj1.name
obj1.name = obj2.name
obj2.name = tempName
)
)
-- 임시 파일 삭제
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 (
channelIndex,
targetName,
weight,
channelName,
isActive
)
struct MorpherBackupData (
objectName,
modifierName,
channels
)
fn BackupMorpherData obj =(
/*
모퍼 모디파이어 데이터를 백업하는 함수
매개변수:
- obj: 모퍼 모디파이어를 가진 오브젝트
반환값:
- MorpherBackupData 구조체
*/
local morphMod = obj.modifiers[#Morpher]
if morphMod == undefined do return undefined
local channels = #()
local channelCount = try (morphMod.numChannels) catch (100) -- 기본값 100개 채널
format "📦 모퍼 데이터 백업: % (채널 수: %)\\n" obj.name channelCount
for i = 1 to channelCount do (
try (
-- 채널 프로퍼티에 직접 접근
local channelProp = execute ("morphMod.morph_channel_" + i as string)
if channelProp != undefined then (
local target = try (channelProp.target) catch (undefined)
local weight = try (channelProp.value) catch (0.0)
local channelName = try (channelProp.name) catch ("")
local isActive = try (channelProp.active) catch (false)
if target != undefined and isValidNode target do (
local channelData = MorpherChannelData \
channelIndex:i \
targetName:target.name \
weight:weight \
channelName:channelName \
isActive:isActive
append channels channelData
format " 채널 %: % (가중치: %, 타겟: %)\\n" i channelName weight target.name
)
)
) catch (
-- 채널이 비어있거나 접근할 수 없는 경우 건너뛰기
format " 채널 % 건너뛰기 (빈 채널)\\n" i
)
)
MorpherBackupData objectName:obj.name modifierName:morphMod.name channels:channels
)
fn RestoreMorpherData backupData pairsList =(
/*
백업된 모퍼 데이터를 복원하는 함수
매개변수:
- backupData: 백업된 모퍼 데이터
- pairsList: 본 이름 매핑 정보
반환값:
- 성공 시 true, 실패 시 false
*/
if backupData == undefined do return false
local obj = getNodeByName backupData.objectName
if not isValidNode obj do (
format "[ERROR] 모퍼 오브젝트를 찾을 수 없음: %\\n" backupData.objectName
return false
)
local morphMod = obj.modifiers[#Morpher]
if morphMod == undefined do (
format "[ERROR] 모퍼 모디파이어를 찾을 수 없음: %\\n" obj.name
return false
)
format "🔄 모퍼 데이터 복원: % (채널 수: %)\\n" obj.name backupData.channels.count
for channelData in backupData.channels do (
-- 원본 타겟 이름을 새 이름으로 매핑
local newTargetName = channelData.targetName
for pair in pairsList do (
if pair.v1 == channelData.targetName do (
newTargetName = pair.v2
exit()
)
)
local newTarget = getNodeByName newTargetName
if isValidNode newTarget then (
try (
-- 채널 프로퍼티에 직접 접근하여 설정
local channelProp = execute ("morphMod.morph_channel_" + channelData.channelIndex as string)
if channelProp != undefined then (
-- 타겟 설정
try (channelProp.target = newTarget) catch (format " [WARNING] 타겟 설정 실패: 채널 %\\n" channelData.channelIndex)
-- 가중치 복원
try (channelProp.value = channelData.weight) catch (format " [WARNING] 가중치 설정 실패: 채널 %\\n" channelData.channelIndex)
-- 채널 활성화 상태 복원
try (channelProp.active = channelData.isActive) catch (format " [WARNING] 활성화 상태 설정 실패: 채널 %\\n" channelData.channelIndex)
-- 채널 이름 복원
try (channelProp.name = channelData.channelName) catch (format " [WARNING] 채널 이름 설정 실패: 채널 %\\n" channelData.channelIndex)
format " ✅ 채널 % 복원: % → % (가중치: %)\\n" channelData.channelIndex channelData.targetName newTargetName channelData.weight
) else (
format " ❌ 채널 % 프로퍼티 접근 실패\\n" channelData.channelIndex
)
) catch (
format " ⚠️ 채널 % 설정 실패: % → %\\n" channelData.channelIndex channelData.targetName newTargetName
)
) else (
format " ❌ 타겟을 찾을 수 없음: % → %\\n" channelData.targetName newTargetName
)
)
true
)
fn GetSkinPairs obj extra:#() =(
local out=#()
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(
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" 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:"*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:"*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:"*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:"*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:"*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:"*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:"*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:"*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:"*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:"*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:"*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:"*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:"*RightToes"): dp.v2 = "Bip001 R Toe0"
default:(foundPairs = false )
)
local indexInExtra = findItem extraV1 n
if not foundPairs and indexInExtra != 0 then(
dp.v2 = extra[indexInExtra].v2
foundPairs = true
)
append out dp
if not foundPairs do append noPairs n
)
out
)
-- ================================== 메인 변환 함수 ===============================================
-- ================================== 단계별 변환 데이터 ===============================================
-- 단계 간 공유 데이터 구조체
struct StepData (
charName = "",
alwaysDeform = false,
bipObj = undefined,
extras = undefined,
extrasPairs = undefined,
skinObjects = undefined,
morpherModifiers = undefined,
result = false
)
-- 전역 단계 데이터
global STEP_DATA = undefined
-- ================================== 1단계: Biped 생성 및 기본 설정 ===============================================
fn ConvertUnityHumanToBiped_Step1 charName:"Character001" alwaysDeform:false =(
/*
1단계: Biped 생성 및 기본 설정 (빠른 단계)
포함 작업:
- 전처리 및 검증
- Biped 생성 및 구조 수정
- 비율 매칭 (BipToRigProportion)
- Extra Bones 처리
매개변수:
- charName: 캐릭터 이름 (기본값: "Character001")
- alwaysDeform: 스킨 Always Deform 설정 (기본값: false)
반환값:
- 성공 시 true, 실패 시 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 = SuperUIOptimizer()
uiOpt.suspend() -- 변환 중 슈퍼 UI 업데이트 억제
if charName=="" do(
uiOpt.resume()
messagebox "please insert Character Name"
return false
)
try (
-- 이름 정리 및 검증
stepPerf "전처리 시작"
CleanNames()
local root = getNodeByName "Hips"
if not isValidNode root do (
uiOpt.resume()
format "[ERROR] Hips not found!\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 "Unity Human Bone 계층 구조 수집: %개 노드\\n" hrc.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)
)
)
-- 스킨 오브젝트들 수집
local skinObjects = for i in objects where i.modifiers[#Skin]!=undefined collect i
format "스킨 오브젝트 발견: %개\\n" skinObjects.count
if alwaysDeform do for i in skinObjects do (
cleanUpSkin i
i.modifiers[#Skin].always_deform =false
i.modifiers[#Skin].always_deform =true
)
-- 모퍼 오브젝트들 수집 (간소화)
local morpherObjects = for i in objects where i.modifiers[#Morpher]!=undefined collect i
local morpherModifiers = #() -- 제거된 모퍼 모디파이어들을 저장
format "모퍼 오브젝트 발견: %개\\n" morpherObjects.count
for obj in morpherObjects do (
-- 모퍼 모디파이어를 임시로 제거하고 저장
local morphMod = obj.modifiers[#Morpher]
append morpherModifiers (datapair obj morphMod)
deleteModifier obj morphMod
)
stepPerf "데이터 수집 완료"
-- 믹사모 리그 구조 분석
stepPerf "리그 분석 시작"
local mxNodes = getRigStructures()
format "믹사모 리그 노드 분석 완료\\n"
local hipsObj = getNodeByName "Hips"
if not isValidNode hipsObj do (
uiOpt.resume()
format "❌ Unity Human Bone 'Hips' 오브젝트를 찾을 수 없습니다!\\n"
return false
)
-- 전체 씬에서 가장 높은 본을 찾아 높이 계산
local allBones = #(hipsObj)
local headObj = getNodeByName "Head"
if isValidNode headObj do append allBones headObj
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()
-- 바이패드 생성
stepPerf "Biped 생성 시작"
format "\\n=== BIPED 생성 디버그 ===\\n"
format "spineSegments: %\\n" spineSegments
format "height: %\\n" height
format "numfingers: %\\n" numfingers
local bipObj = undefined
try (
bipObj = createBip height spine:spineSegments fingers:numfingers
format "✅ Biped 생성 성공\\n"
) catch (
uiOpt.resume()
format "❌ Biped 생성 실패: %\\n" (getCurrentException())
return false
)
if not isValidNode bipObj do (
uiOpt.resume()
format "❌ 생성된 Biped이 유효하지 않습니다!\\n"
return false
)
-- 생성 직후 구조 확인
format "\\n=== 생성 직후 Biped 구조 ===\\n"
local pelvis = biped.getNode bipObj #pelvis link:1
local spine1 = biped.getNode bipObj #spine link:1
local spine2 = try (biped.getNode bipObj #spine link:2) catch (undefined)
local lthigh = biped.getNode bipObj #lleg link:1
local lclavicle = biped.getNode bipObj #larm link:1
local neck = biped.getNode bipObj #neck link:1
format "Pelvis: % (부모: %)\\n" pelvis.name (if pelvis.parent != undefined then pelvis.parent.name else "없음")
format "Spine1: % (부모: %)\\n" spine1.name (if spine1.parent != undefined then spine1.parent.name else "없음")
if spine2 != undefined then format "Spine2: % (부모: %)\\n" spine2.name (if spine2.parent != undefined then spine2.parent.name else "없음")
format "LThigh: % (부모: %)\\n" lthigh.name (if lthigh.parent != undefined then lthigh.parent.name else "없음")
format "LClavicle: % (부모: %)\\n" lclavicle.name (if lclavicle.parent != undefined then lclavicle.parent.name else "없음")
format "Neck: % (부모: %)\\n" neck.name (if neck.parent != undefined then neck.parent.name else "없음")
-- 🔧 잘못된 구조 수정
format "\\n=== Biped 구조 수정 중 ===\\n"
-- Figure Mode 활성화 (구조 수정 가능)
bipObj.transform.controller.figureMode = true
-- 다리 본들을 Pelvis로 이동 (현재 Spine에서 → Pelvis로)
local rthigh = biped.getNode bipObj #rleg link:1
if lthigh.parent != pelvis then (
format "다리를 Pelvis로 이동: % → %\\n" lthigh.parent.name pelvis.name
lthigh.parent = pelvis
rthigh.parent = pelvis
)
-- 어깨 본들을 최상위 Spine으로 이동 (현재 Neck에서 → Spine1로)
local rclavicle = biped.getNode bipObj #rarm link:1
local topSpine = if spine2 != undefined then spine2 else spine1 -- 최상위 Spine
if lclavicle.parent != topSpine then (
format "어깨를 최상위 Spine으로 이동: % → %\\n" lclavicle.parent.name topSpine.name
lclavicle.parent = topSpine
rclavicle.parent = topSpine
)
format "구조 수정 완료!\\n"
-- 구조 수정 후 확인
format "\\n=== 구조 수정 후 확인 ===\\n"
format "LThigh: % (부모: %)\\n" lthigh.name (if lthigh.parent != undefined then lthigh.parent.name else "없음")
format "RThigh: % (부모: %)\\n" rthigh.name (if rthigh.parent != undefined then rthigh.parent.name else "없음")
format "LClavicle: % (부모: %)\\n" lclavicle.name (if lclavicle.parent != undefined then lclavicle.parent.name else "없음")
format "RClavicle: % (부모: %)\\n" rclavicle.name (if rclavicle.parent != undefined then rclavicle.parent.name else "없음")
local bipedNodes = GetBipStructure bipObj spinesegs:spineSegments
format "Biped 노드 구조 분석 완료: %개 노드\\n" bipedNodes.count
-- 비율 매칭 (안전하게)
stepPerf "비율 매칭 시작"
try (
BipToRigProportion bipedNodes mxNodes TPose:true
format "✅ 비율 매칭 성공\\n"
stepPerf "비율 매칭 완료"
) catch (
uiOpt.resume()
format "❌ 비율 매칭 실패: %\\n" (getCurrentException())
return false
)
-- 비율 매칭 후 구조 재확인
format "\\n=== 비율 매칭 후 Biped 구조 ===\\n"
local pelvis_after = biped.getNode bipObj #pelvis link:1
local spine1_after = biped.getNode bipObj #spine link:1
local lthigh_after = biped.getNode bipObj #lleg link:1
local lclavicle_after = biped.getNode bipObj #larm link:1
local neck_after = biped.getNode bipObj #neck link:1
format "Pelvis: % (부모: %)\\n" pelvis_after.name (if pelvis_after.parent != undefined then pelvis_after.parent.name else "없음")
format "Spine1: % (부모: %)\\n" spine1_after.name (if spine1_after.parent != undefined then spine1_after.parent.name else "없음")
format "LThigh: % (부모: %)\\n" lthigh_after.name (if lthigh_after.parent != undefined then lthigh_after.parent.name else "없음")
format "LClavicle: % (부모: %)\\n" lclavicle_after.name (if lclavicle_after.parent != undefined then lclavicle_after.parent.name else "없음")
format "Neck: % (부모: %)\\n" neck_after.name (if neck_after.parent != undefined then neck_after.parent.name else "없음")
-- 1단계에서는 Extra Bones 수집만 (처리는 2단계에서)
local totalExtraCount = 0
for ex in extras do totalExtraCount += ex.count
format "전체 Extra Bones 개수: %개 (2단계에서 처리 예정)\\n" totalExtraCount
format "================================\\n"
-- 1단계 완료 - 단계 데이터 저장 (extrasPairs는 2단계에서 생성)
STEP_DATA.bipObj = bipObj
STEP_DATA.extras = extras
STEP_DATA.extrasPairs = #() -- 2단계에서 채워질 예정
STEP_DATA.skinObjects = skinObjects
STEP_DATA.morpherModifiers = morpherModifiers
STEP_DATA.result = true
-- 중간 메모리 정리 (가벼운 정리)
local memOpt = MemoryOptimizer()
memOpt.quickCleanup()
-- UI 복원 및 성능 결과 출력
uiOpt.resume()
stepPerf "1단계 완료"
format "\\n🎉 1단계 완료! 🎉\\n"
format "✅ Biped 생성 및 구조 설정 완료\\n"
format "✅ 비율 매칭 완료\\n"
format "✅ Extra Bones % 개 수집 완료 (2단계에서 처리)\\n" totalExtraCount
format "\\n▶ 이제 [2단계: Extra Bones + 스킨 처리] 버튼을 클릭하세요\\n"
return true
) catch (
-- 오류 발생 시 UI 복원
if uiOpt != undefined do uiOpt.resume()
format "\\n❌ 1단계 변환 중 오류 발생!\\n"
format "오류 내용: %\\n" (getCurrentException())
STEP_DATA = undefined
return false
)
)
-- ================================== 2단계: Extra Bones + 스킨 처리 및 정리 ===============================================
fn ConvertUnityHumanToBiped_Step2 =(
/*
2단계: Extra Bones 처리 + 스킨 처리 및 정리 작업 (오래 걸리는 단계)
포함 작업:
- Extra Bones 연결 및 처리 (Biped에 부모 연결)
- 스킨 웨이트 전송 (시간 소요)
- 모퍼 모디파이어 복원
- 원본 Unity Human Bone 본 삭제
- 이름 정리 및 최종 마무리
반환값:
- 성공 시 true, 실패 시 false
주의: 1단계를 먼저 실행해야 합니다.
*/
-- 1단계 완료 여부 검증
if STEP_DATA == undefined or not STEP_DATA.result do (
messagebox "먼저 [1단계: Biped 생성]을 완료해주세요!"
return false
)
-- 단계 데이터에서 변수 복원
local charName = STEP_DATA.charName
local alwaysDeform = STEP_DATA.alwaysDeform
local bipObj = STEP_DATA.bipObj
local extras = STEP_DATA.extras
local extrasPairs = STEP_DATA.extrasPairs
local skinObjects = STEP_DATA.skinObjects
local morpherModifiers = STEP_DATA.morpherModifiers
-- 슈퍼 UI 최적화 관리자 초기화
local uiOpt = SuperUIOptimizer()
uiOpt.suspend() -- 변환 중 슈퍼 UI 업데이트 억제
try (
stepPerf "2단계 시작"
format "\\n=== 2단계: 스킨 처리 및 정리 시작 ===\\n"
-- 스킨 팝업 자동 처리 함수 (강화 버전)
fn ShowPopup =(
local hwnd = (DialogMonitorOPS.GetWindowHandle() )
if hwnd!=0 then(
local windowTitle = uiAccessor.getWindowText hwnd
local children = uiAccessor.getChildWindows hwnd
-- 다이얼로그 타입별 처리
case of (
-- Load Envelopes 다이얼로그
(findString windowTitle "Load Envelopes" != undefined): (
format "Load Envelopes 다이얼로그 감지 - 자동 OK 처리\\n"
for c in children do(
local buttonText = uiAccessor.getWindowText c
if buttonText == "OK" do (
UIAccessor.PressButton c
format "OK 버튼 자동 클릭 완료\\n"
return true
)
)
)
-- Match by Name 다이얼로그
(findString windowTitle "Match by Name" != undefined): (
format "Match by Name 다이얼로그 감지 - 자동 처리\\n"
for c in children do(
if (uiAccessor.getWindowText c) =="Match by Name" do UIAccessor.PressButton c
)
)
-- 기타 다이얼로그들
default: (
-- 일반적인 OK 버튼 찾기
for c in children do(
local buttonText = uiAccessor.getWindowText c
if buttonText == "OK" do (
format "일반 OK 버튼 감지 - 자동 클릭: %\\n" windowTitle
UIAccessor.PressButton c
return true
)
)
)
)
-- 기본 버튼 처리
uiAccessor.pressDefaultButton()
true
)
else false
)
-- Extra Bones 처리 (2단계에서 실행)
stepPerf "Extra Bones 처리 시작"
extrasPairs = #() -- 이미 위에서 선언된 변수 재사용
try (
-- ========== 원본 엑스트라 본 유지 방식 ================
local totalExtraCount = 0
for ex in extras do totalExtraCount += ex.count
format "\\n=== Extra Bones 처리 시작 ===\\n"
format "전체 Extra Bones 개수: %개 (원본 본 그대로 유지)\\n" totalExtraCount
if totalExtraCount > 0 then (
bipObj.transform.controller.figureMode = true
local idx = 1
for ex in extras do(
for i=1 to ex.count do (
local originalBone = ex[i]
if not isValidNode originalBone do continue
-- Unity Human Bone 이름은 이미 깔끔하므로 그대로 사용
local cleanName = originalBone.name
append extrasPairs (datapair originalBone.name cleanName)
-- MXPairs는 표준 매핑만 유지 (Extra 본은 추가하지 않음)
local currentParent = originalBone.parent
format " [%] Extra 본: % (현재 부모: %)" idx originalBone.name (if isValidNode currentParent then currentParent.name else "없음")
-- Copy 버전과 동일한 방식: 첫 번째 본만 적절한 Biped 부모에 연결
if i==1 then (
local par = originalBone.parent
for pair in MXPairs where isValidNode par and par.name==pair.v1 do (
local bipPar = getNodebyName pair.v2
if isValidNode bipPar do (
-- 원본 본을 Biped 부모에 연결
try (
originalBone.parent = bipPar
format " (부모: %)" bipPar.name
) catch (
format " (부모 연결 실패: %)" bipPar.name
)
exit
)
)
if originalBone.parent == par do format " (부모 연결 안됨)"
)
format "\\n"
idx += 1
)
)
bipObj.transform.controller.figureMode = false
format "\\n=== 원본 엑스트라 본 유지 완료 ===\\n"
format "- 각도/위치 완벽 보존\\n"
format "- 애니메이션 데이터 손실 없음\\n"
format "- 스키닝 품질 유지\\n"
-- Extra Bones 처리 결과
format "\\n📊 Extra Bones 처리 결과 요약:\\n"
format "✅ 총 %개 엑스트라 본 처리 완료\\n" extrasPairs.count
format "✅ 모든 엑스트라 본이 원본 위치/각도 유지\\n"
format "✅ 위치 변화: 0 (원본 본 그대로 사용)\\n"
format "✅ 각도 변화: 0 (원본 각도 그대로 사용)\\n"
format "✅ 애니메이션 데이터: 완벽 보존\\n"
format "✅ 스키닝 품질: 원본 유지\\n"
format "✅ 표준 본: Biped로 대체됨\\n"
format "✅ 엑스트라 본: 원본 유지됨\\n"
) else (
format "Extra Bones가 없습니다.\\n"
)
-- 단계 데이터 업데이트
STEP_DATA.extrasPairs = extrasPairs
stepPerf "Extra Bones 처리 완료"
) catch (
-- Figure Mode 안전하게 해제
try (
bipObj.transform.controller.figureMode = false
) catch ()
uiOpt.resume()
format "❌ Extra Bones 처리 실패: %\\n" (getCurrentException())
return false
)
DialogMonitorOPS.Enabled = true
DialogMonitorOPS.Interactive =false
DialogMonitorOPS.RegisterNotification ShowPopup id:#UnityHumanToBiped
-- 최적화된 스킨 처리
stepPerf "스킨 처리 시작"
format "\\n=== 스킨 오브젝트 처리 (최적화 버전) ===\\n"
if skinObjects.count > 0 then (
-- 최적화된 스킨 처리 함수 사용
processSkinnedObjectsOptimized skinObjects extrasPairs
) else (
format "처리할 스킨 오브젝트가 없습니다.\\n"
)
stepPerf "스킨 처리 완료"
format "\\n✅ 모든 스킨 오브젝트 처리 완료 (최적화)\\n"
-- 대화상자 모니터 비활성화
DialogMonitorOPS.Enabled = false
DialogMonitorOPS.UnRegisterNotification id:#UnityHumanToBiped
-- 모퍼 모디파이어 복원 (간소화)
if morpherModifiers.count > 0 do (
format "\\n🎭 모퍼 모디파이어 복원 시작...\\n"
-- 모퍼 모디파이어를 다시 추가
for modPair in morpherModifiers do (
local obj = modPair.v1
local morphMod = modPair.v2
addModifier obj morphMod
)
format "✅ 모퍼 모디파이어 복원 완료\\n"
)
-- 하이브리드 방식: 표준 본은 삭제, 엑스트라 본은 유지
format "\\n=== 믹사모 표준 본 삭제 ===\\n"
-- 믹사모 루트 본 다시 찾기
local root = getNodeByName "Hips"
if not isValidNode root do (
format "❌ Hips를 찾을 수 없어 표준 본 삭제를 건너뜁니다.\\n"
root = undefined
)
-- Unity Human Bone 완전 제거 (더 확실한 방법)
local standardBones = #()
local foundBones = #()
-- 모든 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
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 (
-- 스킨 의존성이 있는지 마지막 확인
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 "삭제할 Unity Human Bone을 찾을 수 없습니다.\\n"
)
-- 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 (
format "- % (유효)\\n" theObj.name
validCount += 1
) else (
format "- % → 찾을 수 없음\\n" i.v1
)
) catch (
format "- % → 접근 오류: %\\n" i.v1 (getCurrentException())
)
)
format "✅ %개 Unity Human Bone 확인 완료\\n" validCount
-- 레이어 설정 (안전하게)
format "\\n=== 레이어 설정 ===\\n"
try (
local rootLayer = LayerManager.newLayerFromName "CHARACTERS"
if rootLayer == undefined do rootLayer = LayerManager.getLayerFromName "CHARACTERS"
local charLayer = LayerManager.newLayerFromName "CHARACTER"
if charLayer == undefined do charLayer = LayerManager.getLayerFromName "CHARACTER"
if charLayer != undefined do charLayer.setParent rootLayer
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 "GEOMETRY"
if GeoLayer == undefined do GeoLayer = LayerManager.getLayerFromName "GEOMETRY"
if GeoLayer != undefined and charLayer != undefined do (
GeoLayer.setParent charLayer
GeoLayer.isfrozen = true
)
format "✅ 레이어 설정 완료\\n"
) catch (
format "❌ 레이어 설정 중 오류: %\\n" (getCurrentException())
format "레이어 설정을 건너뜁니다.\\n"
)
-- 오브젝트 관리 (안전하게)
format "\\n=== 오브젝트 관리 ===\\n"
-- 스킨 오브젝트 처리
try (
if skinObjects != undefined and skinObjects.count > 0 then (
for i in skinObjects do (
if isValidNode i do (
try (
i.showFrozenInGray = false
-- 메시와 머티리얼 이름은 원래대로 유지
if GeoLayer != undefined do GeoLayer.addnode i
) catch (
format "스킨 오브젝트 % 처리 중 오류: %\\n" i.name (getCurrentException())
)
)
)
format "✅ %개 스킨 오브젝트 처리 완료\\n" skinObjects.count
) else (
format "처리할 스킨 오브젝트가 없습니다.\\n"
)
) catch (
format "❌ 스킨 오브젝트 관리 중 오류: %\\n" (getCurrentException())
)
-- Biped 오브젝트 처리
try (
if isValidNode bipObj then (
local allBip = getHierarchy bipObj #()
for i in allBip do(
if isValidNode i do (
try (
if bipLayer != undefined do bipLayer.addNode i
-- Biped 본 이름은 표준 이름 그대로 유지 (Bip001 Pelvis, etc.)
i.renderable = false
i.boxMode = true
) catch (
format "Biped 오브젝트 % 처리 중 오류: %\\n" i.name (getCurrentException())
)
)
)
format "✅ %개 Biped 오브젝트 처리 완료\\n" allBip.count
) else (
format "❌ Biped 오브젝트를 찾을 수 없습니다.\\n"
)
) catch (
format "❌ Biped 오브젝트 관리 중 오류: %\\n" (getCurrentException())
)
-- 2단계 성공 반환
stepPerf "2단계 완료"
-- 메모리 정리 및 성능 복원 (3ds Max 느려짐 방지)
local memOpt = MemoryOptimizer()
memOpt.deepCleanup()
-- UI 업데이트 재개 및 최종 화면 갱신
uiOpt.resume()
-- 성능 결과 출력
totalPerf
format "\\n🎉 2단계 완료! 🎉\\n"
format "✅ 스킨 처리 완료\\n"
format "✅ 모퍼 복원 완료\\n"
format "✅ 정리 작업 완료\\n"
format "\\n🎉 전체 변환 완료! 🎉\\n"
format "캐릭터: %\\n" charName
format "✅ Biped 로컬 축: 표준 유지 (축 뒤틀림 없음)\\n"
format "✅ T-Pose 자세: 기존 알고리즘으로 매칭\\n"
format "✅ 애니메이션 호환성: 완벽\\n"
format "레이어: CHARACTERS > %\\n" (toUpper charName)
-- 단계 데이터 정리
STEP_DATA = undefined
redrawViews()
return true
) catch (
-- 에러 처리: UI 복원 및 에러 메시지
if uiOpt != undefined do uiOpt.resume()
format "\\n❌ 2단계 변환 중 오류 발생!\\n"
format "오류 내용: %\\n" (getCurrentException())
STEP_DATA = undefined
return false
)
)
-- ================================== 단계별 변환 UI ===============================================
-- 단계별 변환 UI
rollout UnityHumanBipedConverter_UI "Unity Human to Biped 변환기 (단계별)" width:400 height:650 (
-- 제목 및 상태 표시
group "📋 변환 상태" (
label lblStatus "⏳ 준비 대기 중..." style_sunkenedge:true height:25
label lblProgress "" height:20
)
-- 설정
group "⚙️ 설정" (
label lblNote "기본 이름 사용: Bip001, BIPED 레이어, GEOMETRY 레이어" style_sunkenedge:true height:25
checkbox chkAlwaysDeform "Always Deform 적용" checked:false
)
-- 1단계 버튼
group "🚀 1단계: Biped 생성 및 기본 설정 (빠름)" (
label lbl1Info "• 전처리 및 검증\n• Biped 생성 및 구조 수정\n• 비율 매칭\n• Extra Bones 수집" height:60 style_sunkenedge:true
button btnStep1 "1단계 실행" width:350 height:35 color:green
)
-- 2단계 버튼
group "⏳ 2단계: Extra Bones + 스킨 처리 (시간 소요)" (
label lbl2Info "• Extra Bones 연결 및 처리\n• 스킨 웨이트 전송 (오래 걸림)\n• 모퍼 모디파이어 복원\n• 원본 본 삭제 및 최종 정리" height:70 style_sunkenedge:true
button btnStep2 "2단계 실행" width:350 height:35 color:orange enabled:false
)
-- 전체 실행 버튼
group "⚡ 통합 실행" (
button btnFullConvert "전체 변환 (1단계 + 2단계)" width:350 height:30 color:blue
)
-- 정보 표시
group " 도움말" (
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 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 ()
if humanBoneObjects.count > 0 then (
lblStatus.text = "✅ Unity Human Bone 오브젝트 " + (humanBoneObjects.count as string) + "개 발견 - 변환 준비 완료"
) else (
lblStatus.text = "❌ Unity Human Bone 오브젝트를 찾을 수 없음 - 올바른 본 이름 필요"
btnStep1.enabled = false
btnFullConvert.enabled = false
)
)
-- 1단계 실행
on btnStep1 pressed do (
lblStatus.text = "⏳ 1단계 실행 중..."
btnStep1.enabled = false
try (
local result = ConvertUnityHumanToBiped_Step1 charName:"Character" alwaysDeform:chkAlwaysDeform.checked
if result then (
lblStatus.text = "✅ 1단계 완료 - 2단계 실행 가능"
btnStep2.enabled = true
lblProgress.text = "✅ Biped 생성 및 구조 설정 완료 (Extra Bones는 2단계에서 처리)"
) else (
lblStatus.text = "❌ 1단계 실패"
btnStep1.enabled = true
)
) catch (
lblStatus.text = "❌ 1단계 오류 발생"
btnStep1.enabled = true
messagebox ("1단계 오류: " + (getCurrentException() as string))
)
)
-- 2단계 실행
on btnStep2 pressed do (
lblStatus.text = "⏳ 2단계 실행 중... (시간 소요)"
btnStep2.enabled = false
try (
local result = ConvertUnityHumanToBiped_Step2()
if result then (
lblStatus.text = "🎉 전체 변환 완료!"
lblProgress.text = "🎉 모든 변환 작업 완료!"
btnStep1.enabled = true
btnStep2.enabled = false
) else (
lblStatus.text = "❌ 2단계 실패"
btnStep2.enabled = true
)
) catch (
lblStatus.text = "❌ 2단계 오류 발생"
btnStep2.enabled = true
messagebox ("2단계 오류: " + (getCurrentException() as string))
)
)
-- 전체 변환
on btnFullConvert pressed do (
lblStatus.text = "⏳ 전체 변환 실행 중..."
btnFullConvert.enabled = false
btnStep1.enabled = false
try (
-- 1단계 실행
lblProgress.text = "⏳ 1단계 실행 중..."
local result1 = ConvertUnityHumanToBiped_Step1 charName:"Character" alwaysDeform:chkAlwaysDeform.checked
if result1 then (
lblProgress.text = "✅ 1단계 완료 - ⏳ 2단계 실행 중..."
-- 2단계 실행
local result2 = ConvertUnityHumanToBiped_Step2()
if result2 then (
lblStatus.text = "🎉 전체 변환 완료!"
lblProgress.text = "🎉 모든 변환 작업 완료!"
) else (
lblStatus.text = "❌ 2단계 실패"
)
) else (
lblStatus.text = "❌ 1단계 실패"
)
btnFullConvert.enabled = true
btnStep1.enabled = true
) catch (
lblStatus.text = "❌ 변환 중 오류 발생"
btnFullConvert.enabled = true
btnStep1.enabled = true
messagebox ("변환 오류: " + (getCurrentException() as string))
)
)
)
-- UI 표시
createDialog UnityHumanBipedConverter_UI
-- 스크립트 로딩 완료 메시지
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"