user 4a49ecd772 Refactor: 배경/프랍 브라우저 IMGUI→UI Toolkit 전환 + USS 리디자인
- BackgroundSceneLoaderWindow: OnGUI → CreateGUI (Toolbar + ToolbarSearchField)
- PropBrowserWindow: OnGUI → CreateGUI (Toolbar + ToolbarSearchField)
- StreamingleCommon.uss: 브라우저 공통 스타일 추가 (그리드/리스트/뷰토글/액션바/상태바)
- excludeFromWeb 상태 새로고침 시 보존 수정
- 삭제된 배경 리소스 정리

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 01:55:48 +09:00

327 lines
14 KiB
C#

using UnityEngine;
using UnityEditor;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using System.Collections.Generic;
/// <summary>
/// 아바타의 휴먼본 이름을 Unity Human Bone 기준으로 변환하는 에디터 툴
/// 3ds Max Biped이나 다른 본 구조를 Unity 표준 이름으로 변환
/// </summary>
public class HumanBoneRenamer : EditorWindow
{
private const string CommonUssPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/StreamingleCommon.uss";
private ObjectField avatarField;
private ObjectField gameObjectField;
// Unity Human Bone 매핑 테이블
private static readonly Dictionary<HumanBodyBones, string> humanBoneNames = new Dictionary<HumanBodyBones, string>()
{
// Body
{ HumanBodyBones.Hips, "Hips" },
{ HumanBodyBones.Spine, "Spine" },
{ HumanBodyBones.Chest, "Chest" },
{ HumanBodyBones.UpperChest, "UpperChest" },
{ HumanBodyBones.Neck, "Neck" },
{ HumanBodyBones.Head, "Head" },
// Left Arm
{ HumanBodyBones.LeftShoulder, "LeftShoulder" },
{ HumanBodyBones.LeftUpperArm, "LeftUpperArm" },
{ HumanBodyBones.LeftLowerArm, "LeftLowerArm" },
{ HumanBodyBones.LeftHand, "LeftHand" },
// Right Arm
{ HumanBodyBones.RightShoulder, "RightShoulder" },
{ HumanBodyBones.RightUpperArm, "RightUpperArm" },
{ HumanBodyBones.RightLowerArm, "RightLowerArm" },
{ HumanBodyBones.RightHand, "RightHand" },
// Left Leg
{ HumanBodyBones.LeftUpperLeg, "LeftUpperLeg" },
{ HumanBodyBones.LeftLowerLeg, "LeftLowerLeg" },
{ HumanBodyBones.LeftFoot, "LeftFoot" },
{ HumanBodyBones.LeftToes, "LeftToes" },
// Right Leg
{ HumanBodyBones.RightUpperLeg, "RightUpperLeg" },
{ HumanBodyBones.RightLowerLeg, "RightLowerLeg" },
{ HumanBodyBones.RightFoot, "RightFoot" },
{ HumanBodyBones.RightToes, "RightToes" },
// Left Hand Fingers
{ HumanBodyBones.LeftThumbProximal, "LeftThumbProximal" },
{ HumanBodyBones.LeftThumbIntermediate, "LeftThumbIntermediate" },
{ HumanBodyBones.LeftThumbDistal, "LeftThumbDistal" },
{ HumanBodyBones.LeftIndexProximal, "LeftIndexProximal" },
{ HumanBodyBones.LeftIndexIntermediate, "LeftIndexIntermediate" },
{ HumanBodyBones.LeftIndexDistal, "LeftIndexDistal" },
{ HumanBodyBones.LeftMiddleProximal, "LeftMiddleProximal" },
{ HumanBodyBones.LeftMiddleIntermediate, "LeftMiddleIntermediate" },
{ HumanBodyBones.LeftMiddleDistal, "LeftMiddleDistal" },
{ HumanBodyBones.LeftRingProximal, "LeftRingProximal" },
{ HumanBodyBones.LeftRingIntermediate, "LeftRingIntermediate" },
{ HumanBodyBones.LeftRingDistal, "LeftRingDistal" },
{ HumanBodyBones.LeftLittleProximal, "LeftLittleProximal" },
{ HumanBodyBones.LeftLittleIntermediate, "LeftLittleIntermediate" },
{ HumanBodyBones.LeftLittleDistal, "LeftLittleDistal" },
// Right Hand Fingers
{ HumanBodyBones.RightThumbProximal, "RightThumbProximal" },
{ HumanBodyBones.RightThumbIntermediate, "RightThumbIntermediate" },
{ HumanBodyBones.RightThumbDistal, "RightThumbDistal" },
{ HumanBodyBones.RightIndexProximal, "RightIndexProximal" },
{ HumanBodyBones.RightIndexIntermediate, "RightIndexIntermediate" },
{ HumanBodyBones.RightIndexDistal, "RightIndexDistal" },
{ HumanBodyBones.RightMiddleProximal, "RightMiddleProximal" },
{ HumanBodyBones.RightMiddleIntermediate, "RightMiddleIntermediate" },
{ HumanBodyBones.RightMiddleDistal, "RightMiddleDistal" },
{ HumanBodyBones.RightRingProximal, "RightRingProximal" },
{ HumanBodyBones.RightRingIntermediate, "RightRingIntermediate" },
{ HumanBodyBones.RightRingDistal, "RightRingDistal" },
{ HumanBodyBones.RightLittleProximal, "RightLittleProximal" },
{ HumanBodyBones.RightLittleIntermediate, "RightLittleIntermediate" },
{ HumanBodyBones.RightLittleDistal, "RightLittleDistal" }
};
[MenuItem("Tools/Bone Tools/휴먼본 이름 변환기")]
public static void ShowWindow()
{
GetWindow<HumanBoneRenamer>("휴먼본 이름 변환기");
}
public void CreateGUI()
{
var root = rootVisualElement;
root.AddToClassList("tool-root");
var commonUss = AssetDatabase.LoadAssetAtPath<StyleSheet>(CommonUssPath);
if (commonUss != null) root.styleSheets.Add(commonUss);
root.Add(new Label("휴먼본 이름 변환기") { name = "title" });
root.Q<Label>("title").AddToClassList("tool-title");
root.Add(new HelpBox("아바타의 휴먼본 이름을 Unity 표준으로 변환합니다.", HelpBoxMessageType.Info));
avatarField = new ObjectField("Avatar") { objectType = typeof(Avatar), allowSceneObjects = false };
root.Add(avatarField);
gameObjectField = new ObjectField("또는 GameObject") { objectType = typeof(GameObject), allowSceneObjects = true };
root.Add(gameObjectField);
// 매핑 정보 Foldout
var mappingFoldout = new Foldout { text = "매핑 정보 보기", value = false };
var mappingScroll = new ScrollView { style = { maxHeight = 200 } };
foreach (var bone in humanBoneNames)
{
var row = new VisualElement { style = { flexDirection = FlexDirection.Row, paddingTop = 1, paddingBottom = 1 } };
row.Add(new Label(bone.Key.ToString()) { style = { minWidth = 200 } });
row.Add(new Label("\u2192") { style = { width = 20, unityTextAlign = TextAnchor.MiddleCenter } });
row.Add(new Label(bone.Value));
mappingScroll.Add(row);
}
mappingFoldout.Add(mappingScroll);
root.Add(mappingFoldout);
// 버튼
var btnRow = new VisualElement { style = { flexDirection = FlexDirection.Row, marginTop = 8 } };
var renameBtn = new Button(RenameHumanBones) { text = "휴먼본 이름 변환" };
renameBtn.style.height = 30;
renameBtn.style.flexGrow = 1;
renameBtn.style.marginRight = 4;
renameBtn.AddToClassList("btn-primary");
btnRow.Add(renameBtn);
var previewBtn = new Button(PreviewRenaming) { text = "미리보기" };
previewBtn.style.height = 30;
previewBtn.style.flexGrow = 1;
btnRow.Add(previewBtn);
root.Add(btnRow);
// 도움말
root.Add(new HelpBox(
"사용법:\n" +
"1. Avatar 또는 GameObject를 참조해주세요\n" +
"2. '미리보기'로 변경될 이름을 확인하세요\n" +
"3. '휴먼본 이름 변환'을 클릭하여 실행하세요\n\n" +
"지원하는 본 구조:\n" +
"\u2022 3ds Max Biped (Bip001 Pelvis, etc.)\n" +
"\u2022 Mixamo 본 구조\n" +
"\u2022 기타 휴머노이드 본 구조",
HelpBoxMessageType.Info) { style = { marginTop = 8 } });
root.schedule.Execute(() =>
{
bool hasTarget = avatarField.value != null || gameObjectField.value != null;
renameBtn.SetEnabled(hasTarget);
previewBtn.SetEnabled(hasTarget);
}).Every(200);
}
private void PreviewRenaming()
{
var bones = GetHumanBones();
if (bones.Count == 0)
{
EditorUtility.DisplayDialog("오류", "휴먼본을 찾을 수 없습니다.", "확인");
return;
}
string preview = "";
int changeCount = 0;
foreach (var bone in bones)
{
if (humanBoneNames.TryGetValue(bone.Key, out string newName))
{
if (bone.Value.name != newName)
{
preview += $"{bone.Value.name} \u2192 {newName}\n";
changeCount++;
}
}
}
if (changeCount == 0)
preview = "변경할 본이 없습니다. (이미 Unity 표준 이름)";
else
preview = $"총 {changeCount}개 본 이름이 변경됩니다:\n\n" + preview;
EditorUtility.DisplayDialog("미리보기", preview, "확인");
}
private void RenameHumanBones()
{
var bones = GetHumanBones();
if (bones.Count == 0)
{
EditorUtility.DisplayDialog("오류", "휴먼본을 찾을 수 없습니다.", "확인");
return;
}
var transforms = new List<Transform>();
foreach (var bone in bones) transforms.Add(bone.Value);
Undo.RecordObjects(transforms.ToArray(), "Rename Human Bones");
int changeCount = 0;
foreach (var bone in bones)
{
if (humanBoneNames.TryGetValue(bone.Key, out string newName))
{
if (bone.Value.name != newName)
{
Debug.Log($"[휴먼본 변환] {bone.Value.name} \u2192 {newName}");
bone.Value.name = newName;
changeCount++;
}
}
}
if (changeCount > 0)
{
EditorUtility.DisplayDialog("완료", $"{changeCount}개 휴먼본의 이름이 Unity 표준으로 변경되었습니다.", "확인");
var targetGameObject = gameObjectField.value as GameObject;
if (targetGameObject != null) EditorUtility.SetDirty(targetGameObject);
}
else
{
EditorUtility.DisplayDialog("정보", "변경할 본이 없습니다. 이미 Unity 표준 이름을 사용하고 있습니다.", "확인");
}
}
private Dictionary<HumanBodyBones, Transform> GetHumanBones()
{
Dictionary<HumanBodyBones, Transform> bones = new Dictionary<HumanBodyBones, Transform>();
var targetGameObject = gameObjectField?.value as GameObject;
if (targetGameObject != null)
{
Animator animator = targetGameObject.GetComponent<Animator>();
if (animator != null && animator.avatar != null && animator.avatar.isHuman)
{
foreach (HumanBodyBones boneType in System.Enum.GetValues(typeof(HumanBodyBones)))
{
if (boneType != HumanBodyBones.LastBone)
{
Transform boneTransform = animator.GetBoneTransform(boneType);
if (boneTransform != null) bones[boneType] = boneTransform;
}
}
}
else
{
bones = FindBonesByName(targetGameObject.transform);
}
}
return bones;
}
private Dictionary<HumanBodyBones, Transform> FindBonesByName(Transform root)
{
Dictionary<HumanBodyBones, Transform> bones = new Dictionary<HumanBodyBones, Transform>();
Transform[] allTransforms = root.GetComponentsInChildren<Transform>();
Dictionary<string, HumanBodyBones> bipedMapping = new Dictionary<string, HumanBodyBones>()
{
{ "Bip001 Pelvis", HumanBodyBones.Hips },
{ "Bip001 Spine", HumanBodyBones.Spine },
{ "Bip001 Spine1", HumanBodyBones.Chest },
{ "Bip001 Neck", HumanBodyBones.Neck },
{ "Bip001 Head", HumanBodyBones.Head },
{ "Bip001 L Clavicle", HumanBodyBones.LeftShoulder },
{ "Bip001 L UpperArm", HumanBodyBones.LeftUpperArm },
{ "Bip001 L Forearm", HumanBodyBones.LeftLowerArm },
{ "Bip001 L Hand", HumanBodyBones.LeftHand },
{ "Bip001 R Clavicle", HumanBodyBones.RightShoulder },
{ "Bip001 R UpperArm", HumanBodyBones.RightUpperArm },
{ "Bip001 R Forearm", HumanBodyBones.RightLowerArm },
{ "Bip001 R Hand", HumanBodyBones.RightHand },
{ "Bip001 L Thigh", HumanBodyBones.LeftUpperLeg },
{ "Bip001 L Calf", HumanBodyBones.LeftLowerLeg },
{ "Bip001 L Foot", HumanBodyBones.LeftFoot },
{ "Bip001 L Toe0", HumanBodyBones.LeftToes },
{ "Bip001 R Thigh", HumanBodyBones.RightUpperLeg },
{ "Bip001 R Calf", HumanBodyBones.RightLowerLeg },
{ "Bip001 R Foot", HumanBodyBones.RightFoot },
{ "Bip001 R Toe0", HumanBodyBones.RightToes },
{ "Bip001 L Finger0", HumanBodyBones.LeftThumbProximal },
{ "Bip001 L Finger01", HumanBodyBones.LeftThumbIntermediate },
{ "Bip001 L Finger02", HumanBodyBones.LeftThumbDistal },
{ "Bip001 L Finger1", HumanBodyBones.LeftIndexProximal },
{ "Bip001 L Finger11", HumanBodyBones.LeftIndexIntermediate },
{ "Bip001 L Finger12", HumanBodyBones.LeftIndexDistal },
{ "Bip001 L Finger2", HumanBodyBones.LeftMiddleProximal },
{ "Bip001 L Finger21", HumanBodyBones.LeftMiddleIntermediate },
{ "Bip001 L Finger22", HumanBodyBones.LeftMiddleDistal },
{ "Bip001 L Finger3", HumanBodyBones.LeftRingProximal },
{ "Bip001 L Finger31", HumanBodyBones.LeftRingIntermediate },
{ "Bip001 L Finger32", HumanBodyBones.LeftRingDistal },
{ "Bip001 L Finger4", HumanBodyBones.LeftLittleProximal },
{ "Bip001 L Finger41", HumanBodyBones.LeftLittleIntermediate },
{ "Bip001 L Finger42", HumanBodyBones.LeftLittleDistal },
{ "Bip001 R Finger0", HumanBodyBones.RightThumbProximal },
{ "Bip001 R Finger01", HumanBodyBones.RightThumbIntermediate },
{ "Bip001 R Finger02", HumanBodyBones.RightThumbDistal },
{ "Bip001 R Finger1", HumanBodyBones.RightIndexProximal },
{ "Bip001 R Finger11", HumanBodyBones.RightIndexIntermediate },
{ "Bip001 R Finger12", HumanBodyBones.RightIndexDistal },
{ "Bip001 R Finger2", HumanBodyBones.RightMiddleProximal },
{ "Bip001 R Finger21", HumanBodyBones.RightMiddleIntermediate },
{ "Bip001 R Finger22", HumanBodyBones.RightMiddleDistal },
{ "Bip001 R Finger3", HumanBodyBones.RightRingProximal },
{ "Bip001 R Finger31", HumanBodyBones.RightRingIntermediate },
{ "Bip001 R Finger32", HumanBodyBones.RightRingDistal },
{ "Bip001 R Finger4", HumanBodyBones.RightLittleProximal },
{ "Bip001 R Finger41", HumanBodyBones.RightLittleIntermediate },
{ "Bip001 R Finger42", HumanBodyBones.RightLittleDistal }
};
foreach (Transform t in allTransforms)
{
if (bipedMapping.TryGetValue(t.name, out HumanBodyBones boneType))
bones[boneType] = t;
}
return bones;
}
}