3418 lines
112 KiB
Plaintext
3418 lines
112 KiB
Plaintext
|
||
/*
|
||
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" |