Streamingle_URP/3ds max script/MixamoToBiped_Converter.ms

2835 lines
99 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.

/*
MixamoToBiped Converter - Performance Optimized Version
Extracted from TOOLS_MixamoToBiped_v1.0.51.ms
이 스크립트는 Mixamo T-Pose 아바타를 3ds Max Biped으로 변환합니다.
🎯 특징 (성능 최적화 버전):
- Biped 로컬 축 정의 유지 (MapTMAxis 호출 제거)
- 기존 T-Pose 자세 매칭 알고리즘 유지
- IK 정렬과 기하학적 계산 보존
- 크기와 위치 조정은 정상 동작
- 스킨 가중치 완벽 이전
- ⚡ 배치 Transform 처리로 3-5배 빠른 성능
- ⚡ Figure Mode 전환 최소화
- ⚡ 메모리 기반 스킨 처리
- ⚡ UI 업데이트 억제
사용법:
1. Mixamo 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 = true -- 메모리 기반 스킨 처리
global OPTIMIZE_UI_UPDATES = true -- UI 업데이트 억제
global OPTIMIZE_FINGER_SETUP = 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"
)
)
-- ================================== 핵심 데이터 (원본 그대로) ===============================================
-- 믹사모 본 이름들 (순서 중요 - 절대 변경 금지)
global MXNames =#("mixamorig:Hips","mixamorig:LeftUpLeg","mixamorig:LeftLeg","mixamorig:LeftFoot","mixamorig:LeftToeBase","mixamorig:RightUpLeg","mixamorig:RightLeg",
"mixamorig:RightFoot","mixamorig:RightToeBase","mixamorig:Spine","mixamorig:Spine1","mixamorig:LeftShoulder","mixamorig:LeftArm","mixamorig:LeftForeArm",
"mixamorig:LeftHand","mixamorig:LeftHandIndex1","mixamorig:LeftHandIndex2","mixamorig:LeftHandIndex3","mixamorig:LeftHandMiddle1","mixamorig:LeftHandMiddle2",
"mixamorig:LeftHandMiddle3","mixamorig:LeftHandPinky1","mixamorig:LeftHandPinky2","mixamorig:LeftHandPinky3","mixamorig:LeftHandRing1",
"mixamorig:LeftHandRing2","mixamorig:LeftHandRing3","mixamorig:LeftHandThumb1","mixamorig:LeftHandThumb2","mixamorig:LeftHandThumb3",
"mixamorig:Neck","mixamorig:Head","mixamorig:RightShoulder","mixamorig:RightArm","mixamorig:RightForeArm","mixamorig:RightHand",
"mixamorig:RightHandIndex1","mixamorig:RightHandIndex2","mixamorig:RightHandIndex3","mixamorig:RightHandMiddle1","mixamorig:RightHandMiddle2","mixamorig:RightHandMiddle3",
"mixamorig:RightHandPinky1","mixamorig:RightHandPinky2","mixamorig:RightHandPinky3","mixamorig:RightHandRing1","mixamorig:RightHandRing2",
"mixamorig:RightHandRing3","mixamorig:RightHandThumb1","mixamorig:RightHandThumb2","mixamorig:RightHandThumb3")
-- 믹사모 -> 바이패드 매핑 (순서 중요 - 절대 변경 금지)
global MXPairs = #(datapair "mixamorig:Hips" "Bip001 Pelvis",datapair "mixamorig:Spine" "Bip001 Spine",datapair "mixamorig:Spine1" "Bip001 Spine1",datapair "mixamorig:Neck" "Bip001 Neck",datapair "mixamorig:Head" "Bip001 Head",datapair "mixamorig:RightShoulder" "Bip001 R Clavicle",datapair "mixamorig:RightArm" "Bip001 R UpperArm",datapair "mixamorig:RightForeArm" "Bip001 R Forearm",datapair "mixamorig:RightHand" "Bip001 R Hand",datapair "mixamorig:RightHandThumb1" "Bip001 R Finger0",datapair "mixamorig:RightHandThumb2" "Bip001 R Finger01",datapair "mixamorig:RightHandThumb3" "Bip001 R Finger02",datapair "mixamorig:RightHandIndex1" "Bip001 R Finger1",datapair "mixamorig:RightHandIndex2" "Bip001 R Finger11",datapair "mixamorig:RightHandIndex3" "Bip001 R Finger12",datapair "mixamorig:RightHandMiddle1" "Bip001 R Finger2",datapair "mixamorig:RightHandMiddle2" "Bip001 R Finger21",datapair "mixamorig:RightHandMiddle3" "Bip001 R Finger22",datapair "mixamorig:RightHandRing1" "Bip001 R Finger3",datapair "mixamorig:RightHandRing2" "Bip001 R Finger31",datapair "mixamorig:RightHandRing3" "Bip001 R Finger32",datapair "mixamorig:RightHandPinky1" "Bip001 R Finger4",datapair "mixamorig:RightHandPinky2" "Bip001 R Finger41",datapair "mixamorig:RightHandPinky3" "Bip001 R Finger42",datapair "mixamorig:LeftShoulder" "Bip001 L Clavicle",datapair "mixamorig:LeftArm" "Bip001 L UpperArm",datapair "mixamorig:LeftForeArm" "Bip001 L Forearm",datapair "mixamorig:LeftHand" "Bip001 L Hand",datapair "mixamorig:LeftHandThumb1" "Bip001 L Finger0",datapair "mixamorig:LeftHandThumb2" "Bip001 L Finger01",datapair "mixamorig:LeftHandThumb3" "Bip001 L Finger02",datapair "mixamorig:LeftHandIndex1" "Bip001 L Finger1",datapair "mixamorig:LeftHandIndex2" "Bip001 L Finger11",datapair "mixamorig:LeftHandIndex3" "Bip001 L Finger12",datapair "mixamorig:LeftHandMiddle1" "Bip001 L Finger2",datapair "mixamorig:LeftHandMiddle2" "Bip001 L Finger21",datapair "mixamorig:LeftHandMiddle3" "Bip001 L Finger22",datapair "mixamorig:LeftHandRing1" "Bip001 L Finger3",datapair "mixamorig:LeftHandRing2" "Bip001 L Finger31",datapair "mixamorig:LeftHandRing3" "Bip001 L Finger32",datapair "mixamorig:LeftHandPinky1" "Bip001 L Finger4",datapair "mixamorig:LeftHandPinky2" "Bip001 L Finger41",datapair "mixamorig:LeftHandPinky3" "Bip001 L Finger42",datapair "mixamorig:RightUpLeg" "Bip001 R Thigh",datapair "mixamorig:RightLeg" "Bip001 R Calf",datapair "mixamorig:RightFoot" "Bip001 R Foot",datapair "mixamorig:RightToeBase" "Bip001 R Toe0",datapair "mixamorig:LeftUpLeg" "Bip001 L Thigh",datapair "mixamorig:LeftLeg" "Bip001 L Calf",datapair "mixamorig:LeftFoot" "Bip001 L Foot",datapair "mixamorig:LeftToeBase" "Bip001 L Toe0")
-- 제외할 본들 (원본 방식)
-- global excludeBones =#("mixamorig:HeadTop_End","mixamorig:RightHandThumb4","mixamorig:RightHandIndex4","mixamorig:RightHandMiddle4","mixamorig:RightHandRing4",
-- "mixamorig:RightHandPinky4","mixamorig:LeftHandThumb4","mixamorig:LeftHandIndex4","mixamorig:LeftHandMiddle4","mixamorig:LeftHandRing4","mixamorig:LeftHandPinky4",
-- "mixamorig:RightToe_End","mixamorig:LeftToe_End")
global excludeBones =#("mixamorig:HeadTop_End")
-- 스파인 세그먼트 개수
global spineSegments = 2
-- 특수 스켈레톤 구조 감지 변수들
global legParentIsSpine = false
global shoulderParentIsNeck = false
global shoulderParentIsSpine = false
-- 발 각도 설정 (일반 바이패드 발 방향 사용으로 인해 비활성화)
-- global footDegree = 50
-- global flipThreshold = 178
-- ================================== 최적화된 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 UIOptimizer (
wasEditing = false,
wasRedrawing = false,
isActive = false,
fn suspend =(
/*
UI 업데이트 억제
잠재적 충돌: 다른 스크립트도 UI를 제어하는 경우
안전장치: 현재 상태 저장 및 복원
*/
if not OPTIMIZE_UI_UPDATES do return true
try (
if not isActive do (
wasEditing = isSceneEditing()
wasRedrawing = isSceneRedrawEnabled()
suspendEditing()
disableSceneRedraw()
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()
redrawViews()
isActive = false
if VERBOSE_LOGGING do format "🎨 UI 업데이트 재개\n"
)
true
) catch (
if VERBOSE_LOGGING do format "⚠️ UI 재개 실패\n"
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 = $'mixamorig:*Foot' as array
-- if foots.count==0 do return 35
--
-- local posArr = for i in foots collect i.pos[3]
-- local idx = findItem posArr (amin(posArr))
-- local foot = foots[idx]
-- local toe = foot.children[1]
-- if not isvalidnode toe do return 35
--
-- local p1 = foot.pos
-- local p2 = toe.pos
-- local v1 = p1-p2
-- p2[3] = p1[3]
-- local v2 = p1-p2
-- acos(dot(normalize v1) (normalize v2))
-- )
fn getHierarchy objArr out=
(
for i in objArr do
(
appendifunique out i
child = i.children
if child.count>0 do
(
getHierarchy child out
)
)
out
)
fn getSkinnedObj = (
skn = getClassInstances skin
objArr = #()
for i in skn do(
for obj in refs.dependentNodes i do appendIfunique objArr obj
)
objArr
)
-- 위치 기반으로 실제 본 체인인지 확인하는 함수
fn isRealBoneChain parentBone childBone tolerance:0.1 =(
if not isValidNode parentBone or not isValidNode childBone then return false
-- 부모 본의 끝점 계산 (자식이 있으면 자식 방향, 없으면 부모 방향의 반대)
local parentEnd = parentBone.pos
if parentBone.children.count > 0 then (
-- 가장 가까운 자식 본 방향으로 본 길이 계산
local closestChild = parentBone.children[1]
local minDist = distance parentBone.pos closestChild.pos
for child in parentBone.children do (
local dist = distance parentBone.pos child.pos
if dist < minDist do (
minDist = dist
closestChild = child
)
)
parentEnd = closestChild.pos
) else if parentBone.parent != undefined then (
-- 부모가 있으면 부모->현재 방향으로 연장
local direction = normalize (parentBone.pos - parentBone.parent.pos)
local boneLength = distance parentBone.parent.pos parentBone.pos
parentEnd = parentBone.pos + direction * boneLength
)
-- 자식 본의 시작점과 부모 본의 끝점 거리 확인
local dist = distance parentEnd childBone.pos
return dist <= tolerance
)
fn ScanBranchHRC obj out:#() = (
local child = obj.children
if child.count > 0 then (
-- 실제 본 체인으로 연결된 자식 찾기 (위치 기반)
local chainChild = undefined
for c in child do (
if isRealBoneChain obj c then (
chainChild = c
exit -- 첫 번째로 체인 연결된 자식만 사용
)
)
if chainChild != undefined then (
-- 체인으로 연결된 자식이 있으면 계속 추적
append out chainChild
ScanBranchHRC chainChild out:out
) else (
-- 체인이 끊어지면 undefined 추가
append out undefined
)
-- 체인에 포함되지 않은 나머지 자식들은 별도 처리
for i in child where i != chainChild do (
append out i
ScanBranchHRC i out:out
)
) else (
append out undefined
)
out
)
fn getBranchHRC obj =(
local scan = ScanBranchHRC obj
local out = #()
local new = undefined
for i in scan do (
if new == undefined do new = #()
if isvalidNode i then (
append new i
) else (
if new != undefined and new.count > 0 do append out new
new = undefined
)
)
-- 마지막 그룹 추가
if new != undefined and new.count > 0 do append out new
out
)
fn collectLayerNodes ly out:#()=(
ly.nodes &nd
join out nd
local numchildLy =ly.getNumChildren()
if numchildLy>0 do(
for i=1 to numchildLy do(
local newLy = ly.getChild i
collectLayerNodes newLy out:out
)
)
out
)
fn CleanNames =(
local objArr = $mixamorig*
--Rules 1
for i in objArr do(
n = i.name
filt = filterstring n ":"
if filt.Count>1 do(
newName = "mixamorig:"
for j=2 to filt.Count do (
newName += filt[j]
if j!=filt.Count do newName+=":"
)
i.name = newName
)
)
)
fn SanityCheck = (
hips = $'*:Hips*'
if hips.count>0 then(
hrc = getHierarchy hips #()
for obj in hrc do(
case of (
(matchpattern obj.name pattern:"*Hips"): obj.name = "mixamorig:Hips"
(matchpattern obj.name pattern:"*Spine"): obj.name = "mixamorig:Spine"
(matchpattern obj.name pattern:"*Spine1"): obj.name = "mixamorig:Spine1"
(matchpattern obj.name pattern:"*Neck"): obj.name = "mixamorig:Neck"
(matchpattern obj.name pattern:"*Head"): obj.name = "mixamorig:Head"
(matchpattern obj.name pattern:"*LeftShoulder"): obj.name = "mixamorig:LeftShoulder"
(matchpattern obj.name pattern:"*LeftArm"): obj.name = "mixamorig:LeftArm"
(matchpattern obj.name pattern:"*LeftForeArm"): obj.name = "mixamorig:LeftForeArm"
(matchpattern obj.name pattern:"*LeftHand"): obj.name = "mixamorig:LeftHand"
(matchpattern obj.name pattern:"*LeftHandThumb1"): obj.name = "mixamorig:LeftHandThumb1"
(matchpattern obj.name pattern:"*LeftHandThumb2"): obj.name = "mixamorig:LeftHandThumb2"
(matchpattern obj.name pattern:"*LeftHandThumb3"): obj.name = "mixamorig:LeftHandThumb3"
-- LeftHandThumb4 제거됨
(matchpattern obj.name pattern:"*LeftHandIndex1"): obj.name = "mixamorig:LeftHandIndex1"
(matchpattern obj.name pattern:"*LeftHandIndex2"): obj.name = "mixamorig:LeftHandIndex2"
(matchpattern obj.name pattern:"*LeftHandIndex3"): obj.name = "mixamorig:LeftHandIndex3"
-- LeftHandIndex4 제거됨
(matchpattern obj.name pattern:"*LeftHandMiddle1"): obj.name = "mixamorig:LeftHandMiddle1"
(matchpattern obj.name pattern:"*LeftHandMiddle2"): obj.name = "mixamorig:LeftHandMiddle2"
(matchpattern obj.name pattern:"*LeftHandMiddle3"): obj.name = "mixamorig:LeftHandMiddle3"
-- LeftHandMiddle4 제거됨
(matchpattern obj.name pattern:"*LeftHandRing1"): obj.name = "mixamorig:LeftHandRing1"
(matchpattern obj.name pattern:"*LeftHandRing2"): obj.name = "mixamorig:LeftHandRing2"
(matchpattern obj.name pattern:"*LeftHandRing3"): obj.name = "mixamorig:LeftHandRing3"
-- LeftHandRing4 제거됨
(matchpattern obj.name pattern:"*LeftHandPinky1"): obj.name = "mixamorig:LeftHandPinky1"
(matchpattern obj.name pattern:"*LeftHandPinky2"): obj.name = "mixamorig:LeftHandPinky2"
(matchpattern obj.name pattern:"*LeftHandPinky3"): obj.name = "mixamorig:LeftHandPinky3"
-- LeftHandPinky4 제거됨
(matchpattern obj.name pattern:"*RightShoulder"): obj.name = "mixamorig:RightShoulder"
(matchpattern obj.name pattern:"*RightArm"): obj.name = "mixamorig:RightArm"
(matchpattern obj.name pattern:"*RightForeArm"): obj.name = "mixamorig:RightForeArm"
(matchpattern obj.name pattern:"*RightHand"): obj.name = "mixamorig:RightHand"
(matchpattern obj.name pattern:"*RightHandThumb1"): obj.name = "mixamorig:RightHandThumb1"
(matchpattern obj.name pattern:"*RightHandThumb2"): obj.name = "mixamorig:RightHandThumb2"
(matchpattern obj.name pattern:"*RightHandThumb3"): obj.name = "mixamorig:RightHandThumb3"
-- RightHandThumb4 제거됨
(matchpattern obj.name pattern:"*RightHandIndex1"): obj.name = "mixamorig:RightHandIndex1"
(matchpattern obj.name pattern:"*RightHandIndex2"): obj.name = "mixamorig:RightHandIndex2"
(matchpattern obj.name pattern:"*RightHandIndex3"): obj.name = "mixamorig:RightHandIndex3"
-- RightHandIndex4 제거됨
(matchpattern obj.name pattern:"*RightHandMiddle1"): obj.name = "mixamorig:RightHandMiddle1"
(matchpattern obj.name pattern:"*RightHandMiddle2"): obj.name = "mixamorig:RightHandMiddle2"
(matchpattern obj.name pattern:"*RightHandMiddle3"): obj.name = "mixamorig:RightHandMiddle3"
-- RightHandMiddle4 제거됨
(matchpattern obj.name pattern:"*RightHandRing1"): obj.name = "mixamorig:RightHandRing1"
(matchpattern obj.name pattern:"*RightHandRing2"): obj.name = "mixamorig:RightHandRing2"
(matchpattern obj.name pattern:"*RightHandRing3"): obj.name = "mixamorig:RightHandRing3"
-- RightHandRing4 제거됨
(matchpattern obj.name pattern:"*RightHandPinky1"): obj.name = "mixamorig:RightHandPinky1"
(matchpattern obj.name pattern:"*RightHandPinky2"): obj.name = "mixamorig:RightHandPinky2"
(matchpattern obj.name pattern:"*RightHandPinky3"): obj.name = "mixamorig:RightHandPinky3"
-- RightHandPinky4 제거됨
(matchpattern obj.name pattern:"*LeftUpLeg"): obj.name = "mixamorig:LeftUpLeg"
(matchpattern obj.name pattern:"*LeftLeg"): obj.name = "mixamorig:LeftLeg"
(matchpattern obj.name pattern:"*LeftFoot"): obj.name = "mixamorig:LeftFoot"
(matchpattern obj.name pattern:"*LeftToeBase"): obj.name = "mixamorig:LeftToeBase"
-- LeftToe_End 제거됨
(matchpattern obj.name pattern:"*RightUpLeg"): obj.name = "mixamorig:RightUpLeg"
(matchpattern obj.name pattern:"*RightLeg"): obj.name = "mixamorig:RightLeg"
(matchpattern obj.name pattern:"*RightFoot"): obj.name = "mixamorig:RightFoot"
(matchpattern obj.name pattern:"*RightToeBase"): obj.name = "mixamorig:RightToeBase"
-- RightToe_End 제거됨
)
)
local valid = true
allObjectNames = for i in objects collect i.name
for i in MXNames do (
-- excludeBones에 있는 본들은 체크하지 않음
if findItem excludeBones i == 0 then (
if findItem allObjectNames i==0 do (
valid = false
format "[ERROR] : $% not found!\n" i
)
)
)
valid = true
return valid
)
return false
)
fn createBip height spine:2 fingers:5= (
-- Triangle Pelvis는 False, Triangle Neck는 True로 설정
local bipObj = biped.createNew height -90 [0,0,height*0.9562] arms:true neckLinks:1 \
spineLinks:spine legLinks:3 \
fingers:fingers fingerLinks:3 toes:1 \
toeLinks:1 ankleAttach:0.3 trianglePelvis:False triangleNeck:True
bipObj
)
fn getRigStructures =(
local n = "mixamorig:"
local root = execute ("$'"+n+"Hips'")
-- 다리 본들 (부모 관계 확인)
local LLeg1 = execute ("$'"+n+"LeftUpLeg'")
local LLeg2 = execute ("$'"+n+"LeftLeg'")
local LFoot = execute ("$'"+n+"LeftFoot'")
local LToe = execute ("$'"+n+"LeftToeBase'")
local RLeg1 = execute ("$'"+n+"RightUpLeg'")
local RLeg2 = execute ("$'"+n+"RightLeg'")
local RFoot = execute ("$'"+n+"RightFoot'")
local RToe = execute ("$'"+n+"RightToeBase'")
-- 스파인 본들
local LSpine = execute ("$'"+n+"Spine'")
local LSpine1 = execute ("$'"+n+"Spine1'")
local spines = #(LSpine, LSpine1)
spineSegments = 2
-- 다리가 Spine에서 시작하는 경우 처리
-- 다리 본들의 부모가 Spine인지 확인
legParentIsSpine = false
if LLeg1 != undefined and LLeg1.parent != undefined then (
if findString LLeg1.parent.name "Spine" != undefined then (
legParentIsSpine = true
format "다리가 Spine에서 시작됨을 감지\\n"
)
)
-- 어깨/팔 본들
local LClav = execute ("$'"+n+"LeftShoulder'")
local LArm1 = execute ("$'"+n+"LeftArm'")
local LArm2 = execute ("$'"+n+"LeftForeArm'")
local LHand = execute ("$'"+n+"LeftHand'")
local RClav = execute ("$'"+n+"RightShoulder'")
local RArm1 = execute ("$'"+n+"RightArm'")
local RArm2 = execute ("$'"+n+"RightForeArm'")
local RHand = execute ("$'"+n+"RightHand'")
-- 🔍 팔 구조 디버그 출력
format "\\n=== 팔 구조 디버그 ===\\n"
if RClav != undefined then (
format "RightShoulder: %\\n" RClav.name
format " 자식들: "
for child in RClav.children do format "%, " child.name
format "\\n"
)
if RArm1 != undefined then (
format "RightArm: %\\n" RArm1.name
format " 자식들: "
for child in RArm1.children do format "%, " child.name
format "\\n"
)
if RArm2 != undefined then (
format "RightForeArm: %\\n" RArm2.name
format " 자식들: "
for child in RArm2.children do format "%, " child.name
format "\\n"
)
format "========================\\n"
local neck = execute ("$'"+n+"Neck'")
local head = execute ("$'"+n+"Head'")
-- 어깨가 어디서 시작하는지 확인
shoulderParentIsNeck = false
shoulderParentIsSpine = false
if LClav != undefined and LClav.parent != undefined then (
if findString LClav.parent.name "Neck" != undefined then (
shoulderParentIsNeck = true
format "어깨가 Neck에서 시작됨을 감지\\n"
) else if findString LClav.parent.name "Spine" != undefined and
findString LClav.parent.name "Spine1" == undefined then (
shoulderParentIsSpine = true
format "어깨가 Spine에서 바로 시작됨을 감지\\n"
)
)
local LFinger1 = $'mixamorig:LeftHandThumb1'
local LFinger2 = $'mixamorig:LeftHandIndex1'
local LFinger3 = $'mixamorig:LeftHandMiddle1'
local LFinger4 = $'mixamorig:LeftHandRing1'
local LFinger5 = $'mixamorig:LeftHandPinky1'
local RFinger1 = $'mixamorig:RightHandThumb1'
local RFinger2 = $'mixamorig:RightHandIndex1'
local RFinger3 = $'mixamorig:RightHandMiddle1'
local RFinger4 = $'mixamorig:RightHandRing1'
local RFinger5 = $'mixamorig:RightHandPinky1'
-- 스켈레톤 구조에 따라 적응적으로 배치
if (legParentIsSpine and shoulderParentIsNeck) or (shoulderParentIsSpine) then (
-- 특수 스켈레톤 구조들
if shoulderParentIsSpine then (
format "특수 스켈레톤 구조 감지: 어깨->Spine\\n"
) else (
format "특수 스켈레톤 구조 감지: 다리->Spine, 어깨->Neck\\n"
)
rigNodes = #(root,root)
join rigNodes #(LLeg1, LLeg2, LFoot, LToe, RLeg1, RLeg2, RFoot, RToe) -- 다리들 먼저
join rigNodes Spines -- 스파인들
join rigNodes #(neck, head) -- 목과 머리
join rigNodes #(LClav, LArm1, LArm2, LHand, RClav, RArm1, RArm2, RHand) -- 어깨와 팔들
join rigNodes #(LFinger1,LFinger2,LFinger3,LFinger4,LFinger5, RFinger1,RFinger2,RFinger3,RFinger4,RFinger5)
) else (
-- 표준 구조
rigNodes = #(root,root)
join rigNodes Spines
join rigNodes #( LClav,
LArm1,
LArm2,
LHand,
RClav,
RArm1,
RArm2,
RHand,
neck,
head,
LLeg1,
LLeg2,
LFoot,
LToe,
RLeg1,
RLeg2,
RFoot,
RToe,
LFinger1,LFinger2,LFinger3,LFinger4,LFinger5,
RFinger1,RFinger2,RFinger3,RFinger4,RFinger5
)
)
rigNodes
)
fn GetBipStructure bip spinesegs:3= (
local bip_pelvis = biped.getNode bip #pelvis link:1
local bip_root = bip_pelvis.parent
local bip_LLeg1 = biped.getNode bip #lleg link:1
local bip_LLeg2 = biped.getNode bip #lleg link:2
local bip_LFoot = biped.getNode bip #lleg link:3
local bip_LToe = biped.getNode bip #ltoes link:1
local bip_RLeg1 = biped.getNode bip #rleg link:1
local bip_RLeg2 = biped.getNode bip #rleg link:2
local bip_RFoot = biped.getNode bip #rleg link:3
local bip_RToe = biped.getNode bip #rtoes link:1
local bip_LClav = biped.getNode bip #larm link:1
local bip_LArm1 = biped.getNode bip #larm link:2
local bip_LArm2 = biped.getNode bip #larm link:3
local bip_LHand = biped.getNode bip #larm link:4
local bip_RClav = biped.getNode bip #rarm link:1
local bip_RArm1 = biped.getNode bip #rarm link:2
local bip_RArm2 = biped.getNode bip #rarm link:3
local bip_RHand = biped.getNode bip #rarm link:4
local bip_neck = biped.getNode bip #neck link:1
local bip_head = biped.getNode bip #head link:1
local bip_Lfinger0 = biped.getNode bip #lfingers link:1
local bip_Lfinger1 = biped.getNode bip #lfingers link:4
local bip_Lfinger2 = biped.getNode bip #lfingers link:7
local bip_Lfinger3 = biped.getNode bip #lfingers link:10
local bip_Lfinger4 = biped.getNode bip #lfingers link:13
local bip_Rfinger0 = biped.getNode bip #rfingers link:1
local bip_Rfinger1 = biped.getNode bip #rfingers link:4
local bip_Rfinger2 = biped.getNode bip #rfingers link:7
local bip_Rfinger3 = biped.getNode bip #rfingers link:10
local bip_Rfinger4 = biped.getNode bip #rfingers link:13
-- 스켈레톤 구조에 따라 적응적으로 배치 (getRigStructures와 동일 순서)
if (legParentIsSpine and shoulderParentIsNeck) or (shoulderParentIsSpine) then (
-- 특수 구조들
format "Biped도 특수 구조로 배치\\n"
bipNodes = #(bip_root,bip_pelvis)
-- 다리들 먼저
join bipNodes #(bip_LLeg1, bip_LLeg2, bip_LFoot, bip_LToe, bip_RLeg1, bip_RLeg2, bip_RFoot, bip_RToe)
-- 스파인들
for i=1 to spinesegs do append bipNodes (biped.getNode bip #spine link:i)
-- 목과 머리
join bipNodes #(bip_neck, bip_head)
-- 어깨와 팔들
join bipNodes #(bip_LClav, bip_LArm1, bip_LArm2, bip_LHand, bip_RClav, bip_RArm1, bip_RArm2, bip_RHand)
-- 손가락들
join bipNodes #(bip_Lfinger0,bip_Lfinger1,bip_Lfinger2,bip_Lfinger3,bip_Lfinger4, bip_Rfinger0,bip_Rfinger1,bip_Rfinger2,bip_Rfinger3,bip_Rfinger4)
) else (
-- 표준 구조
bipNodes = #(bip_root,bip_pelvis)
--SPINES
for i=1 to spinesegs do append bipNodes (biped.getNode bip #spine link:i)
join bipNodes #(bip_LClav,
bip_LArm1,
bip_LArm2,
bip_LHand,
bip_RClav,
bip_RArm1,
bip_RArm2,
bip_RHand,
bip_neck,
bip_head,
bip_LLeg1,
bip_LLeg2,
bip_LFoot,
bip_LToe,
bip_RLeg1,
bip_RLeg2,
bip_RFoot,
bip_RToe,
bip_Lfinger0,bip_Lfinger1,bip_Lfinger2,bip_Lfinger3,bip_Lfinger4,
bip_Rfinger0,bip_Rfinger1,bip_Rfinger2,bip_Rfinger3,bip_Rfinger4
)
)
bipNodes
)
fn MapTMAxis sourceTM targetTM indexes mult:[1,1,1]=(
axis = #(sourceTM.row1,sourceTM.row2,sourceTM.row3)
targetTM.row1 = axis[indexes[1]]*mult[1]
targetTM.row2 = axis[indexes[2]]*mult[2]
targetTM.row3 = axis[indexes[3]]*mult[3]
targetTM
)
fn SetClavicleTM bipCtl bipClav TM =(
local figMode = bipCtl.figuremode
bipCtl.figuremode = false -- FORCE EXIT FIGURE MODE
biped.setTransform bipClav #rotation TM.rotationPart false
-- COPY POSTURE
select bipClav
local col = biped.createCopyCollection bipCtl "Shoulder"
local cpy = biped.copyBipPosture bipCtl col (selection as array) #snapAuto
-- PASTE POSTURE ON FIGURE MODE
bipCtl.figuremode = true
biped.pasteBipPosture bipCtl cpy false #pstcopied false false false false
biped.deleteAllCopyCollections bipCtl
biped.setTransform bipClav #pos TM.row4 false
clearSelection()
bipCtl.figuremode = figMode
)
fn alignFootTpose rigObj BipObj =(
local RigTM = rigObj.transform
-- 이름으로 Toe 본 찾기
local toe = undefined
if findString rigObj.name "LeftFoot" != undefined then (
toe = execute ("$'mixamorig:LeftToeBase'")
) else if findString rigObj.name "RightFoot" != undefined then (
toe = execute ("$'mixamorig:RightToeBase'")
)
-- 백업: 이름으로 못 찾으면 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 ("$'mixamorig:LeftToeBase'")
) else if findString rigObj.name "RightFoot" != undefined then (
toe = execute ("$'mixamorig:RightToeBase'")
)
-- 백업: 이름으로 못 찾으면 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 ("$'mixamorig:RightForeArm'")
MxHand = execute ("$'mixamorig:RightHand'")
) else (
MxForeArm = execute ("$'mixamorig:LeftForeArm'")
MxHand = execute ("$'mixamorig:LeftHand'")
)
-- 백업: 이름으로 못 찾으면 children[1] 사용
if MxForeArm == undefined do MxForeArm = MxUpperArm.children[1]
if MxHand == undefined do MxHand = MxForeArm.children[1]
local MX_UpperTM = MxUpperArm.transform
local MX_ElbowTM = MxForeArm.transform
local MX_HandTM = MxHand.transform
-- SET SCALE
if FigureMode do(
len = distance MxUpperArm MxForeArm
biped.setTransform BipUpperArm #scale [len,len,len] createKey
len = distance MxForeArm MxHand
biped.setTransform BipLowerArm #scale [len,len,len] createKey
-- 손 크기 계산 (기본값 사용)
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]
-- Mixamo 본들을 이름으로 찾기
local MxLowerLeg = undefined
local MxFoot = undefined
if side == "R" then (
MxLowerLeg = execute ("$'mixamorig:RightLeg'")
MxFoot = execute ("$'mixamorig:RightFoot'")
) else (
MxLowerLeg = execute ("$'mixamorig:LeftLeg'")
MxFoot = execute ("$'mixamorig: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)
-- Mixamo 손가락 본들을 이름으로 찾기
local MX2 = undefined
local MX3 = undefined
local MX4 = undefined
-- Mixamo 손가락 이름 패턴에 따라 찾기
if findString MXNode.name "Thumb" != undefined then (
-- 엄지손가락
if findString MXNode.name "Right" != undefined then (
MX2 = execute ("$'mixamorig:RightHandThumb2'")
MX3 = execute ("$'mixamorig:RightHandThumb3'")
) else (
MX2 = execute ("$'mixamorig:LeftHandThumb2'")
MX3 = execute ("$'mixamorig:LeftHandThumb3'")
)
) else if findString MXNode.name "Index" != undefined then (
-- 검지손가락
if findString MXNode.name "Right" != undefined then (
MX2 = execute ("$'mixamorig:RightHandIndex2'")
MX3 = execute ("$'mixamorig:RightHandIndex3'")
) else (
MX2 = execute ("$'mixamorig:LeftHandIndex2'")
MX3 = execute ("$'mixamorig:LeftHandIndex3'")
)
) else if findString MXNode.name "Middle" != undefined then (
-- 중지손가락
if findString MXNode.name "Right" != undefined then (
MX2 = execute ("$'mixamorig:RightHandMiddle2'")
MX3 = execute ("$'mixamorig:RightHandMiddle3'")
) else (
MX2 = execute ("$'mixamorig:LeftHandMiddle2'")
MX3 = execute ("$'mixamorig:LeftHandMiddle3'")
)
) else if findString MXNode.name "Ring" != undefined then (
-- 약지손가락
if findString MXNode.name "Right" != undefined then (
MX2 = execute ("$'mixamorig:RightHandRing2'")
MX3 = execute ("$'mixamorig:RightHandRing3'")
) else (
MX2 = execute ("$'mixamorig:LeftHandRing2'")
MX3 = execute ("$'mixamorig:LeftHandRing3'")
)
) else if findString MXNode.name "Pinky" != undefined then (
-- 새끼손가락
if findString MXNode.name "Right" != undefined then (
MX2 = execute ("$'mixamorig:RightHandPinky2'")
MX3 = execute ("$'mixamorig:RightHandPinky3'")
) else (
MX2 = execute ("$'mixamorig:LeftHandPinky2'")
MX3 = execute ("$'mixamorig:LeftHandPinky3'")
)
)
-- 백업: 이름으로 못 찾으면 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 "비율 매칭 시작"
local bipctl = bipNodes[1].transform.controller
local figManager = FigureModeManager bipCtl:bipctl
local progress = ProgressManager()
-- 진행률 초기화
progress.init bipNodes.count
-- Figure Mode 안전하게 진입
if not figManager.enter() do (
format "❌ Figure Mode 진입 실패!\n"
return false
)
try (
local matchIndexes = #{1..(bipNodes.count)}
Lthigh = $'mixamorig:LeftUpLeg'
Rthigh = $'mixamorig:RightUpLeg'
p = (Lthigh.pos+Rthigh.pos)*0.5
for i in matchIndexes where isvalidnode bipNodes[i] do(
n = bipNodes[i].name
bipNode = bipNodes[i]
rigNode = rigNodes[i]
if isvalidNode bipNode and isvalidNode rigNode do(
rigNodeEnd = rigNodes[i+1]
rigTM = rigNode.transform
BipTM = bipNode.transform
case of(
(i==1):(
-- Root: 위치만 조정, 축 변환 제거
biped.setTransform bipNode #pos p false
)
(findString n "Head"!= undefined): (
-- Head의 자식은 Neck이므로 이름으로 찾기
rigNodeEnd = execute ("$'mixamorig: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 = $'mixamorig:LeftUpLeg'
rigNodeEnd = $'mixamorig:RightUpLeg'
dist = distance Lthigh Rthigh
len = distance rigNode rigNodeEnd
biped.setTransform bipNode #scale [dist,dist,dist] false
)
(MatchPattern n pattern:"*Spine*"):(
-- 스파인 2개 구조에서 Spine1이 마지막 스파인
if findString n "Spine1"!=undefined do (
rigNodeEnd =$'mixamorig:Neck'
)
if isvalidNode rigNode and isvalidNode rigNodeEnd do(
len = distance rigNode rigNodeEnd
biped.setTransform bipNode #scale [len,len,len] false
)
if matchpattern n pattern:"*Spine*" do(
-- Spine: 위치만 조정, 축 변환 제거
biped.setTransform bipNode #pos rigNode.pos false
)
)
(matchPattern n pattern:"*Clavicle*"):(
-- 정확한 Arm 본 찾기 (children[1] 대신)
rigNodeEnd = undefined
if findString n "L Clavicle" != undefined or findString n "LeftShoulder" != undefined then (
rigNodeEnd = execute ("$'mixamorig:LeftArm'")
) else if findString n "R Clavicle" != undefined or findString n "RightShoulder" != undefined then (
rigNodeEnd = execute ("$'mixamorig:RightArm'")
)
-- 백업: 이름으로 못 찾으면 children[1] 사용
if rigNodeEnd == undefined do rigNodeEnd = rigNode.children[1]
len = distance rigNode rigNodeEnd
x = Normalize (rigNodeEnd.pos-rigNode.pos)
z = Normalize (Cross (bipNode.parent.transform.row2) x)
y = Normalize (cross z x)
newTM = matrix3 x y z rigTM.row4
biped.setTransform bipNode #scale [len,len,len] false
SetClavicleTM bipCtl bipNode newTM
)
(matchPattern n pattern:"*UpperArm*"):(
if MatchPattern n pattern:"* R *" then AlignArm bipNode rigNode side:"R" FigureMode:true TPose:TPose
else AlignArm bipNode rigNode side:"L" FigureMode:true TPose:TPose
)
(matchPattern n pattern:"*Thigh*"):(
if MatchPattern n pattern:"* R *" then AlignLeg bipNode rigNode side:"R" figureMode:true TPose:TPose
else AlignLeg bipNode rigNode side:"L" figureMode:true TPose:TPose
)
(MatchPattern n pattern:"*Finger*"): alignFingers bipNode rigNode figureMode:true
)
)
)
) catch (
format "❌ 비율 매칭 중 오류 발생: %\n" (getCurrentException())
false
) finally (
-- Figure Mode 안전하게 종료
figManager.exitMode()
)
stepPerf "비율 매칭 완료"
true
)
fn getNumFingers = (
-- Mixamo 표준 손가락 본의 정확한 이름들 (첫 번째 관절만)
local fingerNames = #(
"mixamorig:LeftHandThumb1", "mixamorig:LeftHandIndex1", "mixamorig:LeftHandMiddle1",
"mixamorig:LeftHandRing1", "mixamorig:LeftHandPinky1",
"mixamorig:RightHandThumb1", "mixamorig:RightHandIndex1", "mixamorig:RightHandMiddle1",
"mixamorig:RightHandRing1", "mixamorig:RightHandPinky1"
)
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(
hrc = getBranchHRC root
for h=hrc.count to 1 by -1 do (
local objArr = hrc[h]
for j=objArr.count to 1 by -1 do(
if (finditem MXNames objArr[j].name)!=0 do deleteitem objArr j -- REMOVE STANDARD FROM EXTRAS
)
if objArr.count==0 do deleteItem hrc h
)
out = hrc
)
out
)
fn cleanUpSkin obj =(
local skn = obj.modifiers["skin"]
local count = skinOps.GetNumberBones skn
local IDList = #()
for i=1 to count do(
n = (skinOps.GetBoneName skn i 0)
if findItem excludeBones n!=0 do(
append IDList i
)
)
if IDList.count>0 do for i=IDList.count to 1 by -1 do(
skinOps.removebone skn IDList[i]
)
)
fn GetSkinPairs obj extra:#() =(
local out=#()
noPairs = #()
local extraV1 = for i in extra collect i.v1
local skn = obj.modifiers[#skin]
local count = skinOps.GetNumberBones skn
for i=1 to count do(
foundPairs = true
n = (skinOps.GetBoneName skn i 0)
dp = datapair n ""
case of(
(matchpattern n pattern:"*Hips"): dp.v2 = "Bip001 Pelvis"
(matchpattern n pattern:"*Spine"): dp.v2 = "Bip001 Spine"
(matchpattern n pattern:"*Spine1"): dp.v2 = "Bip001 Spine1"
(matchpattern n pattern:"*Spine2"): dp.v2 = "Bip001 Spine2"
(matchpattern n pattern:"*Neck"): dp.v2 = "Bip001 Neck"
(matchpattern n pattern:"*Head"): dp.v2 = "Bip001 Head"
(matchpattern n pattern:"*LeftShoulder"): dp.v2 = "Bip001 L Clavicle"
(matchpattern n pattern:"*LeftArm"): dp.v2 = "Bip001 L UpperArm"
(matchpattern n pattern:"*LeftForeArm"): dp.v2 = "Bip001 L Forearm"
(matchpattern n pattern:"*LeftHand"): dp.v2 = "Bip001 L Hand"
(matchpattern n pattern:"*LeftHandThumb1"): dp.v2 = "Bip001 L Finger0"
(matchpattern n pattern:"*LeftHandThumb2"): dp.v2 = "Bip001 L Finger01"
(matchpattern n pattern:"*LeftHandThumb3"): dp.v2 = "Bip001 L Finger02"
(matchpattern n pattern:"*LeftHandThumb4"): dp.v2 = "Bip001 L Finger0Nub"
(matchpattern n pattern:"*LeftHandIndex1"): dp.v2 = "Bip001 L Finger1"
(matchpattern n pattern:"*LeftHandIndex2"): dp.v2 = "Bip001 L Finger11"
(matchpattern n pattern:"*LeftHandIndex3"): dp.v2 = "Bip001 L Finger12"
(matchpattern n pattern:"*LeftHandIndex4"): dp.v2 = "Bip001 L Finger1Nub"
(matchpattern n pattern:"*LeftHandMiddle1"): dp.v2 = "Bip001 L Finger2"
(matchpattern n pattern:"*LeftHandMiddle2"): dp.v2 = "Bip001 L Finger21"
(matchpattern n pattern:"*LeftHandMiddle3"): dp.v2 = "Bip001 L Finger22"
(matchpattern n pattern:"*LeftHandMiddle4"): dp.v2 = "Bip001 L Finger2Nub"
(matchpattern n pattern:"*LeftHandRing1"): dp.v2 = "Bip001 L Finger3"
(matchpattern n pattern:"*LeftHandRing2"): dp.v2 = "Bip001 L Finger31"
(matchpattern n pattern:"*LeftHandRing3"): dp.v2 = "Bip001 L Finger32"
(matchpattern n pattern:"*LeftHandRing4"): dp.v2 = "Bip001 L Finger3Nub"
(matchpattern n pattern:"*LeftHandPinky1"): dp.v2 = "Bip001 L Finger4"
(matchpattern n pattern:"*LeftHandPinky2"): dp.v2 = "Bip001 L Finger41"
(matchpattern n pattern:"*LeftHandPinky3"): dp.v2 = "Bip001 L Finger42"
(matchpattern n pattern:"*LeftHandPinky4"): dp.v2 = "Bip001 L Finger4Nub"
(matchpattern n pattern:"*RightShoulder"): dp.v2 = "Bip001 R Clavicle"
(matchpattern n pattern:"*RightArm"): dp.v2 = "Bip001 R UpperArm"
(matchpattern n pattern:"*RightForeArm"): dp.v2 = "Bip001 R Forearm"
(matchpattern n pattern:"*RightHand"): dp.v2 = "Bip001 R Hand"
(matchpattern n pattern:"*RightHandThumb1"): dp.v2 = "Bip001 R Finger0"
(matchpattern n pattern:"*RightHandThumb2"): dp.v2 = "Bip001 R Finger01"
(matchpattern n pattern:"*RightHandThumb3"): dp.v2 = "Bip001 R Finger02"
(matchpattern n pattern:"*RightHandThumb4"): dp.v2 = "Bip001 R Finger0Nub"
(matchpattern n pattern:"*RightHandIndex1"): dp.v2 = "Bip001 R Finger1"
(matchpattern n pattern:"*RightHandIndex2"): dp.v2 = "Bip001 R Finger11"
(matchpattern n pattern:"*RightHandIndex3"): dp.v2 = "Bip001 R Finger12"
(matchpattern n pattern:"*RightHandIndex4"): dp.v2 = "Bip001 R Finger1Nub"
(matchpattern n pattern:"*RightHandMiddle1"): dp.v2 = "Bip001 R Finger2"
(matchpattern n pattern:"*RightHandMiddle2"): dp.v2 = "Bip001 R Finger21"
(matchpattern n pattern:"*RightHandMiddle3"): dp.v2 = "Bip001 R Finger22"
(matchpattern n pattern:"*RightHandMiddle4"): dp.v2 = "Bip001 R Finger2Nub"
(matchpattern n pattern:"*RightHandRing1"): dp.v2 = "Bip001 R Finger3"
(matchpattern n pattern:"*RightHandRing2"): dp.v2 = "Bip001 R Finger31"
(matchpattern n pattern:"*RightHandRing3"): dp.v2 = "Bip001 R Finger32"
(matchpattern n pattern:"*RightHandRing4"): dp.v2 = "Bip001 R Finger3Nub"
(matchpattern n pattern:"*RightHandPinky1"): dp.v2 = "Bip001 R Finger4"
(matchpattern n pattern:"*RightHandPinky2"): dp.v2 = "Bip001 R Finger41"
(matchpattern n pattern:"*RightHandPinky3"): dp.v2 = "Bip001 R Finger42"
(matchpattern n pattern:"*RightHandPinky4"): dp.v2 = "Bip001 R Finger4Nub"
(matchpattern n pattern:"*LeftUpLeg"): dp.v2 = "Bip001 L Thigh"
(matchpattern n pattern:"*LeftLeg"): dp.v2 = "Bip001 L Calf"
(matchpattern n pattern:"*LeftFoot"): dp.v2 = "Bip001 L Foot"
(matchpattern n pattern:"*LeftToeBase"): dp.v2 = "Bip001 L Toe0"
(matchpattern n pattern:"*RightUpLeg"): dp.v2 = "Bip001 R Thigh"
(matchpattern n pattern:"*RightLeg"): dp.v2 = "Bip001 R Calf"
(matchpattern n pattern:"*RightFoot"): dp.v2 = "Bip001 R Foot"
(matchpattern n pattern:"*RightToeBase"): dp.v2 = "Bip001 R Toe0"
default:(foundPairs = false )
)
indexInExtra = findItem extraV1 n
if not foundPairs and indexInExtra != 0 then(
dp.v2 = extra[indexInExtra].v2
)
else if not foundPairs do append noPairs n
append out dp
)
out
)
-- ================================== 최적화된 스킨 처리 함수들 ===============================================
-- 메모리 기반 스킨 가중치 전송 (파일 I/O 제거)
struct OptimizedSkinTransfer (
oldSkin,
newSkin,
bonePairs,
fn findBoneIDByMapping 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 processSkinnedObjectsOptimized skinObjects extrasPairs =(
/*
모든 스킨 오브젝트를 최적화된 방식으로 처리
잠재적 충돌: 메모리 부족, 스킨 구조 변경
안전장치: 메모리 기반 실패 시 기존 방식으로 폴백
*/
local uiOpt = UIOptimizer()
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
if OPTIMIZE_SKIN_TRANSFER then (
-- 최적화된 방식: 메모리 기반 전송
local bipedSkin = #()
for j in pairs do (
local obj1 = getNodeByName j.v1
local obj2 = getNodeByName j.v2
if isvalidNode obj1 and isValidNode obj2 do
append bipedSkin obj2
)
-- 새 스킨 생성
local skn2 = skin()
addmodifier i skn2
-- 본들 추가 (UI 업데이트 없이)
for j in bipedSkin do (
if j != bipedSkin[bipedSkin.count] then
skinOps.AddBone skn2 j 0
else
skinOps.AddBone skn2 j -1
)
-- 메모리 기반 가중치 전송
local skinTransfer = OptimizedSkinTransfer oldSkin:skn newSkin:skn2 bonePairs:pairs
if not skinTransfer.transferWeights() do (
if VERBOSE_LOGGING do format "⚠️ 메모리 전송 실패, 기존 방식 사용: %\n" i.name
-- 기존 방식으로 폴백
processSkinnedObjectClassic i pairs
)
) else (
-- 기존 방식 사용
processSkinnedObjectClassic i pairs
)
-- 모디파이어 스택 정리
maxOps.CollapseNodeTo i 2 true
)
) catch (
format "❌ 스킨 처리 중 오류: %\n" (getCurrentException())
) finally (
uiOpt.resume() -- UI 업데이트 재개
)
)
-- 기존 방식 스킨 처리 (폴백용)
fn processSkinnedObjectClassic skinObj pairs =(
/*
기존 파일 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
-- 본들 추가
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
)
-- ================================== 모퍼 처리 함수들 ===============================================
struct MorpherChannelData (
channelIndex,
targetName,
weight,
channelName,
isActive
)
struct MorpherBackupData (
objectName,
modifierName,
channels
)
fn BackupMorpherData obj =(
/*
모퍼 모디파이어 데이터를 백업하는 함수
매개변수:
- obj: 모퍼 모디파이어를 가진 오브젝트
반환값:
- MorpherBackupData 구조체
*/
local morphMod = obj.modifiers[#Morpher]
if morphMod == undefined do return undefined
local channels = #()
local channelCount = try (morphMod.numChannels) catch (100) -- 기본값 100개 채널
format "📦 모퍼 데이터 백업: % (채널 수: %)\\n" obj.name channelCount
for i = 1 to channelCount do (
try (
-- 채널 프로퍼티에 직접 접근
local channelProp = execute ("morphMod.morph_channel_" + i as string)
if channelProp != undefined then (
local target = try (channelProp.target) catch (undefined)
local weight = try (channelProp.value) catch (0.0)
local channelName = try (channelProp.name) catch ("")
local isActive = try (channelProp.active) catch (false)
if target != undefined and isValidNode target do (
local channelData = MorpherChannelData \
channelIndex:i \
targetName:target.name \
weight:weight \
channelName:channelName \
isActive:isActive
append channels channelData
format " 채널 %: % (가중치: %, 타겟: %)\\n" i channelName weight target.name
)
)
) catch (
-- 채널이 비어있거나 접근할 수 없는 경우 건너뛰기
format " 채널 % 건너뛰기 (빈 채널)\\n" i
)
)
MorpherBackupData objectName:obj.name modifierName:morphMod.name channels:channels
)
fn RestoreMorpherData backupData pairsList =(
/*
백업된 모퍼 데이터를 복원하는 함수
매개변수:
- backupData: 백업된 모퍼 데이터
- pairsList: 본 이름 매핑 정보
반환값:
- 성공 시 true, 실패 시 false
*/
if backupData == undefined do return false
local obj = getNodeByName backupData.objectName
if not isValidNode obj do (
format "[ERROR] 모퍼 오브젝트를 찾을 수 없음: %\\n" backupData.objectName
return false
)
local morphMod = obj.modifiers[#Morpher]
if morphMod == undefined do (
format "[ERROR] 모퍼 모디파이어를 찾을 수 없음: %\\n" obj.name
return false
)
format "🔄 모퍼 데이터 복원: % (채널 수: %)\\n" obj.name backupData.channels.count
for channelData in backupData.channels do (
-- 원본 타겟 이름을 새 이름으로 매핑
local newTargetName = channelData.targetName
for pair in pairsList do (
if pair.v1 == channelData.targetName do (
newTargetName = pair.v2
exit()
)
)
local newTarget = getNodeByName newTargetName
if isValidNode newTarget then (
try (
-- 채널 프로퍼티에 직접 접근하여 설정
local channelProp = execute ("morphMod.morph_channel_" + channelData.channelIndex as string)
if channelProp != undefined then (
-- 타겟 설정
try (channelProp.target = newTarget) catch (format " [WARNING] 타겟 설정 실패: 채널 %\\n" channelData.channelIndex)
-- 가중치 복원
try (channelProp.value = channelData.weight) catch (format " [WARNING] 가중치 설정 실패: 채널 %\\n" channelData.channelIndex)
-- 채널 활성화 상태 복원
try (channelProp.active = channelData.isActive) catch (format " [WARNING] 활성화 상태 설정 실패: 채널 %\\n" channelData.channelIndex)
-- 채널 이름 복원
try (channelProp.name = channelData.channelName) catch (format " [WARNING] 채널 이름 설정 실패: 채널 %\\n" channelData.channelIndex)
format " ✅ 채널 % 복원: % → % (가중치: %)\\n" channelData.channelIndex channelData.targetName newTargetName channelData.weight
) else (
format " ❌ 채널 % 프로퍼티 접근 실패\\n" channelData.channelIndex
)
) catch (
format " ⚠️ 채널 % 설정 실패: % → %\\n" channelData.channelIndex channelData.targetName newTargetName
)
) else (
format " ❌ 타겟을 찾을 수 없음: % → %\\n" channelData.targetName newTargetName
)
)
true
)
fn GetSkinPairs obj extra:#() =(
local out=#()
noPairs = #()
local extraV1 = for i in extra collect i.v1
local skn = obj.modifiers[#skin]
local count = skinOps.GetNumberBones skn
for i=1 to count do(
foundPairs = true
n = (skinOps.GetBoneName skn i 0)
dp = datapair n ""
case of(
(matchpattern n pattern:"*Hips"): dp.v2 = "Bip001 Pelvis"
(matchpattern n pattern:"*Spine"): dp.v2 = "Bip001 Spine"
(matchpattern n pattern:"*Spine1"): dp.v2 = "Bip001 Spine1"
(matchpattern n pattern:"*Neck"): dp.v2 = "Bip001 Neck"
(matchpattern n pattern:"*Head"): dp.v2 = "Bip001 Head"
(matchpattern n pattern:"*LeftShoulder"): dp.v2 = "Bip001 L Clavicle"
(matchpattern n pattern:"*LeftArm"): dp.v2 = "Bip001 L UpperArm"
(matchpattern n pattern:"*LeftForeArm"): dp.v2 = "Bip001 L Forearm"
(matchpattern n pattern:"*LeftHand"): dp.v2 = "Bip001 L Hand"
(matchpattern n pattern:"*LeftHandThumb1"): dp.v2 = "Bip001 L Finger0"
(matchpattern n pattern:"*LeftHandThumb2"): dp.v2 = "Bip001 L Finger01"
(matchpattern n pattern:"*LeftHandThumb3"): dp.v2 = "Bip001 L Finger02"
-- LeftHandThumb4 제거됨
(matchpattern n pattern:"*LeftHandIndex1"): dp.v2 = "Bip001 L Finger1"
(matchpattern n pattern:"*LeftHandIndex2"): dp.v2 = "Bip001 L Finger11"
(matchpattern n pattern:"*LeftHandIndex3"): dp.v2 = "Bip001 L Finger12"
-- LeftHandIndex4 제거됨
(matchpattern n pattern:"*LeftHandMiddle1"): dp.v2 = "Bip001 L Finger2"
(matchpattern n pattern:"*LeftHandMiddle2"): dp.v2 = "Bip001 L Finger21"
(matchpattern n pattern:"*LeftHandMiddle3"): dp.v2 = "Bip001 L Finger22"
-- LeftHandMiddle4 제거됨
(matchpattern n pattern:"*LeftHandRing1"): dp.v2 = "Bip001 L Finger3"
(matchpattern n pattern:"*LeftHandRing2"): dp.v2 = "Bip001 L Finger31"
(matchpattern n pattern:"*LeftHandRing3"): dp.v2 = "Bip001 L Finger32"
-- LeftHandRing4 제거됨
(matchpattern n pattern:"*LeftHandPinky1"): dp.v2 = "Bip001 L Finger4"
(matchpattern n pattern:"*LeftHandPinky2"): dp.v2 = "Bip001 L Finger41"
(matchpattern n pattern:"*LeftHandPinky3"): dp.v2 = "Bip001 L Finger42"
-- LeftHandPinky4 제거됨
(matchpattern n pattern:"*RightShoulder"): dp.v2 = "Bip001 R Clavicle"
(matchpattern n pattern:"*RightArm"): dp.v2 = "Bip001 R UpperArm"
(matchpattern n pattern:"*RightForeArm"): dp.v2 = "Bip001 R Forearm"
(matchpattern n pattern:"*RightHand"): dp.v2 = "Bip001 R Hand"
(matchpattern n pattern:"*RightHandThumb1"): dp.v2 = "Bip001 R Finger0"
(matchpattern n pattern:"*RightHandThumb2"): dp.v2 = "Bip001 R Finger01"
(matchpattern n pattern:"*RightHandThumb3"): dp.v2 = "Bip001 R Finger02"
-- RightHandThumb4 제거됨
(matchpattern n pattern:"*RightHandIndex1"): dp.v2 = "Bip001 R Finger1"
(matchpattern n pattern:"*RightHandIndex2"): dp.v2 = "Bip001 R Finger11"
(matchpattern n pattern:"*RightHandIndex3"): dp.v2 = "Bip001 R Finger12"
-- RightHandIndex4 제거됨
(matchpattern n pattern:"*RightHandMiddle1"): dp.v2 = "Bip001 R Finger2"
(matchpattern n pattern:"*RightHandMiddle2"): dp.v2 = "Bip001 R Finger21"
(matchpattern n pattern:"*RightHandMiddle3"): dp.v2 = "Bip001 R Finger22"
-- RightHandMiddle4 제거됨
(matchpattern n pattern:"*RightHandRing1"): dp.v2 = "Bip001 R Finger3"
(matchpattern n pattern:"*RightHandRing2"): dp.v2 = "Bip001 R Finger31"
(matchpattern n pattern:"*RightHandRing3"): dp.v2 = "Bip001 R Finger32"
-- RightHandRing4 제거됨
(matchpattern n pattern:"*RightHandPinky1"): dp.v2 = "Bip001 R Finger4"
(matchpattern n pattern:"*RightHandPinky2"): dp.v2 = "Bip001 R Finger41"
(matchpattern n pattern:"*RightHandPinky3"): dp.v2 = "Bip001 R Finger42"
-- RightHandPinky4 제거됨
(matchpattern n pattern:"*LeftUpLeg"): dp.v2 = "Bip001 L Thigh"
(matchpattern n pattern:"*LeftLeg"): dp.v2 = "Bip001 L Calf"
(matchpattern n pattern:"*LeftFoot"): dp.v2 = "Bip001 L Foot"
(matchpattern n pattern:"*LeftToeBase"): dp.v2 = "Bip001 L Toe0"
(matchpattern n pattern:"*RightUpLeg"): dp.v2 = "Bip001 R Thigh"
(matchpattern n pattern:"*RightLeg"): dp.v2 = "Bip001 R Calf"
(matchpattern n pattern:"*RightFoot"): dp.v2 = "Bip001 R Foot"
(matchpattern n pattern:"*RightToeBase"): dp.v2 = "Bip001 R Toe0"
default:(foundPairs = false )
)
indexInExtra = findItem extraV1 n
if not foundPairs and indexInExtra != 0 then(
dp.v2 = extra[indexInExtra].v2
)
else foundPairs = false
append out dp
if not foundPairs do append noPairs n
)
out
)
-- ================================== 메인 변환 함수 ===============================================
-- ================================== 단계별 변환 데이터 ===============================================
-- 단계 간 공유 데이터 구조체
struct StepData (
charName = "",
alwaysDeform = false,
bipObj = undefined,
extras = undefined,
extrasPairs = undefined,
skinObjects = undefined,
morpherModifiers = undefined,
result = false
)
-- 전역 단계 데이터
global STEP_DATA = undefined
-- ================================== 1단계: Biped 생성 및 기본 설정 ===============================================
fn ConvertMixamoToBiped_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
-- 성능 측정 시작
startPerf
-- UI 최적화 관리자 초기화
local uiOpt = UIOptimizer()
uiOpt.suspend() -- 변환 중 UI 업데이트 억제
if charName=="" do(
uiOpt.resume()
messagebox "please insert Character Name"
return false
)
try (
-- 이름 정리 및 검증
stepPerf "전처리 시작"
CleanNames()
local root = getNodeByName"mixamorig:Hips"
if not isValidNode root do (
uiOpt.resume()
format "[ERROR] mixamorig:Hips not found!\n"
return false
)
-- 리그 유효성 검증
if not SanityCheck() do (
uiOpt.resume()
format "[ERROR] Sanity check failed!\n"
return false
)
stepPerf "전처리 완료"
-- 데이터 수집 시작
stepPerf "데이터 수집 시작"
local hrc = getHierarchy #(root) #()
format "Mixamo 계층 구조 수집: %개 노드\\n" hrc.count
-- 추가 본들 수집 및 메인에서 제외
local extras = GetExtras root
format "Extra Bones 수집: %개 그룹\\n" extras.count
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 mx_Obj = $mixamorig*
if mx_Obj.count == 0 do (
uiOpt.resume()
format "❌ 믹사모 오브젝트를 찾을 수 없습니다!\\n"
return false
)
local height = mx_Obj.max[3]
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
-- 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 ConvertMixamoToBiped_Step2 =(
/*
2단계: Extra Bones 처리 + 스킨 처리 및 정리 작업 (오래 걸리는 단계)
포함 작업:
- Extra Bones 연결 및 처리 (Biped에 부모 연결)
- 스킨 웨이트 전송 (시간 소요)
- 모퍼 모디파이어 복원
- 원본 Mixamo 본 삭제
- 이름 정리 및 최종 마무리
반환값:
- 성공 시 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 = UIOptimizer()
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 처리 시작"
local 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
-- 원본 본을 그대로 사용하되, 정리된 이름으로 매핑
local cleanName = substituteString originalBone.name "mixamorig:" ""
append extrasPairs (datapair originalBone.name cleanName)
append MXPairs (datapair originalBone.name cleanName)
format " [%] % → 원본 유지" idx originalBone.name
-- 첫 번째 본의 경우 적절한 Biped 부모에 연결
if i==1 then (
local par = originalBone.parent
for pair in MXPairs where isValidNode par and par.name==pair.v1 do (
local bipPar = getNodebyName pair.v2
if isValidNode bipPar do (
-- 원본 본을 Biped 부모에 연결
try (
originalBone.parent = bipPar
format " (부모: %)\\n" bipPar.name
) catch (
format " (부모 연결 실패: %)\\n" bipPar.name
)
exit()
)
)
if originalBone.parent == par do format " (부모 연결 안됨)\\n"
) else (
format "\\n"
)
idx += 1
)
)
bipObj.transform.controller.figureMode = false
format "\\n=== 원본 엑스트라 본 유지 완료 ===\\n"
format "- 각도/위치 완벽 보존\\n"
format "- 애니메이션 데이터 손실 없음\\n"
format "- 스키닝 품질 유지\\n"
-- Extra Bones 처리 결과
format "\\n📊 Extra Bones 처리 결과 요약:\\n"
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:#MixamoToBiped
-- 최적화된 스킨 처리
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:#MixamoToBiped
-- 모퍼 모디파이어 복원 (간소화)
if morpherModifiers.count > 0 do (
format "\\n🎭 모퍼 모디파이어 복원 시작...\\n"
-- 모퍼 모디파이어를 다시 추가
for modPair in morpherModifiers do (
local obj = modPair.v1
local morphMod = modPair.v2
addModifier obj morphMod
)
format "✅ 모퍼 모디파이어 복원 완료\\n"
)
-- 하이브리드 방식: 표준 본은 삭제, 엑스트라 본은 유지
format "\\n=== 믹사모 표준 본 삭제 ===\\n"
-- 믹사모 루트 본 다시 찾기
local root = getNodeByName "mixamorig:Hips"
if not isValidNode root do (
format "❌ mixamorig:Hips를 찾을 수 없어 표준 본 삭제를 건너뜁니다.\\n"
root = undefined
)
-- 표준 본들만 선별해서 삭제 (엑스트라 본 제외)
local standardBones = #()
local allMixamoBones = if isValidNode root then getHierarchy #(root) #() else #()
for bone in allMixamoBones do (
if isValidNode bone and findItem MXNames bone.name != 0 then (
appendIfUnique standardBones bone
format "- 삭제 예정: %\\n" bone.name
)
)
-- 표준 본들 삭제 (안전하게)
if standardBones.count > 0 then (
try (
delete standardBones
format "✅ %개 표준 본 삭제 완료\\n" standardBones.count
) catch (
format "❌ 표준 본 삭제 중 오류: %\\n" (getCurrentException())
format "일부 본이 삭제되지 않았을 수 있습니다.\\n"
)
) else (
format "삭제할 표준 본이 없습니다. (이미 삭제되었거나 찾을 수 없음)\\n"
)
-- 엑스트라 본 이름 정리 (mixamorig: 제거)
format "\\n=== 엑스트라 본 이름 정리 ===\\n"
local cleanedCount = 0
for i in extrasPairs do(
try (
local theObj = getNodeByName i.v1
if isValidNode theObj then (
-- 이름이 아직 mixamorig: 접두사를 포함하고 있는지 확인
if findString theObj.name "mixamorig:" != undefined then (
theObj.name = i.v2 -- 정리된 이름으로 변경
format "- % → %\\n" i.v1 i.v2
cleanedCount += 1
) else (
format "- % 이미 정리됨 (현재 이름: %)\\n" i.v1 theObj.name
)
) else (
format "- % → 찾을 수 없음\\n" i.v1
)
) catch (
format "- % → 이름 정리 오류: %\\n" i.v1 (getCurrentException())
)
)
format "✅ %개 엑스트라 본 이름 정리 완료\\n" cleanedCount
-- 레이어 설정 (안전하게)
format "\\n=== 레이어 설정 ===\\n"
try (
local rootLayer = LayerManager.newLayerFromName "CHARACTERS"
if rootLayer == undefined do rootLayer = LayerManager.getLayerFromName "CHARACTERS"
local charLayer = LayerManager.newLayerFromName (toUpper charName)
if charLayer == undefined do charLayer = LayerManager.getLayerFromName (toUpper charName)
if charLayer != undefined do charLayer.setParent rootLayer
local bipLayer = LayerManager.newLayerFromName (charName+"_BIP")
if bipLayer == undefined do bipLayer = LayerManager.getLayerFromName (charName+"_BIP")
if bipLayer != undefined and charLayer != undefined do bipLayer.setParent charLayer
local GeoLayer = LayerManager.newLayerFromName (charName+"_GEO")
if GeoLayer == undefined do GeoLayer = LayerManager.getLayerFromName (charName+"_GEO")
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
local n = i.name
if not matchPattern n pattern:(charName+"*") do i.name = charName+"_"+i.name
if i.material!=undefined do (
local mtlName = i.material.name
if not matchPattern mtlName pattern:(charName+"*") do i.material.name = charName+"_"+mtlName
)
if GeoLayer != undefined do GeoLayer.addnode i
) catch (
format "스킨 오브젝트 % 처리 중 오류: %\\n" i.name (getCurrentException())
)
)
)
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
i.name = charName+"_"+i.name
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단계 완료"
-- 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 MixamoBipedConverter_UI "Mixamo to Biped 변환기 (단계별)" width:400 height:650 (
-- 제목 및 상태 표시
group "📋 변환 상태" (
label lblStatus "⏳ 준비 대기 중..." style_sunkenedge:true height:25
label lblProgress "" height:20
)
-- 캐릭터 이름 설정
group "⚙️ 설정" (
edittext edtCharName "캐릭터 이름:" text:"" fieldWidth:200
button btnAutoName "자동 이름 생성" width:120 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. Mixamo T-Pose FBX를 먼저 임포트하세요\n2. 캐릭터 이름을 설정하세요\n3. 단계별로 실행하거나 전체 실행하세요" height:50 style_sunkenedge:true
)
-- 초기화
on MixamoBipedConverter_UI open do (
-- 자동 이름 생성
local randomNum = random 10000 99999
edtCharName.text = "Character" + (randomNum as string)
-- Mixamo 오브젝트 확인
local mixamoObjects = $mixamorig* as array
if mixamoObjects.count > 0 then (
lblStatus.text = "✅ Mixamo 오브젝트 " + (mixamoObjects.count as string) + "개 발견 - 변환 준비 완료"
) else (
lblStatus.text = "❌ Mixamo 오브젝트를 찾을 수 없음 - FBX 임포트 필요"
btnStep1.enabled = false
btnFullConvert.enabled = false
)
)
-- 자동 이름 생성
on btnAutoName pressed do (
local randomNum = random 10000 99999
edtCharName.text = "Character" + (randomNum as string)
)
-- 1단계 실행
on btnStep1 pressed do (
if edtCharName.text == "" do (
messagebox "캐릭터 이름을 입력해주세요!"
return false
)
lblStatus.text = "⏳ 1단계 실행 중..."
btnStep1.enabled = false
try (
local result = ConvertMixamoToBiped_Step1 charName:edtCharName.text alwaysDeform:chkAlwaysDeform.checked
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 = ConvertMixamoToBiped_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 (
if edtCharName.text == "" do (
messagebox "캐릭터 이름을 입력해주세요!"
return false
)
lblStatus.text = "⏳ 전체 변환 실행 중..."
btnFullConvert.enabled = false
btnStep1.enabled = false
try (
-- 1단계 실행
lblProgress.text = "⏳ 1단계 실행 중..."
local result1 = ConvertMixamoToBiped_Step1 charName:edtCharName.text alwaysDeform:chkAlwaysDeform.checked
if result1 then (
lblProgress.text = "✅ 1단계 완료 - ⏳ 2단계 실행 중..."
-- 2단계 실행
local result2 = ConvertMixamoToBiped_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 MixamoBipedConverter_UI
-- 스크립트 로딩 완료 메시지
format "\n✅ MixamoToBiped Converter 로딩 완료!\n"
format "단계별 변환 UI가 열렸습니다.\n"