diff --git a/Assets/Scripts/Editor/HumanBoneNameCopier.cs b/Assets/Scripts/Editor/HumanBoneNameCopier.cs new file mode 100644 index 00000000..81edb560 --- /dev/null +++ b/Assets/Scripts/Editor/HumanBoneNameCopier.cs @@ -0,0 +1,315 @@ +using UnityEngine; +using UnityEditor; +using System.Collections.Generic; + +public class HumanBoneNameCopier : EditorWindow +{ + private GameObject sourceAvatar; + private GameObject targetAvatar; + private Vector2 scrollPosition; + private bool showAdvancedOptions = false; + private bool copyAllBones = true; + private Dictionary boneSelection = new Dictionary(); + + [MenuItem("Tools/Human Bone Name Copier")] + public static void ShowWindow() + { + GetWindow("Human Bone Name Copier"); + } + + private void OnEnable() + { + InitializeBoneSelection(); + } + + private void InitializeBoneSelection() + { + boneSelection.Clear(); + foreach (HumanBodyBones bone in System.Enum.GetValues(typeof(HumanBodyBones))) + { + if (bone != HumanBodyBones.LastBone) + { + boneSelection[bone] = true; + } + } + } + + private void OnGUI() + { + GUILayout.Label("Human Bone Name Copier", EditorStyles.boldLabel); + EditorGUILayout.Space(); + + // 소스 아바타 선택 + EditorGUILayout.BeginHorizontal(); + GUILayout.Label("Source Avatar:", GUILayout.Width(100)); + sourceAvatar = (GameObject)EditorGUILayout.ObjectField(sourceAvatar, typeof(GameObject), true); + EditorGUILayout.EndHorizontal(); + + // 타겟 아바타 선택 + EditorGUILayout.BeginHorizontal(); + GUILayout.Label("Target Avatar:", GUILayout.Width(100)); + targetAvatar = (GameObject)EditorGUILayout.ObjectField(targetAvatar, typeof(GameObject), true); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + + // 고급 옵션 토글 + showAdvancedOptions = EditorGUILayout.Foldout(showAdvancedOptions, "Advanced Options"); + + if (showAdvancedOptions) + { + EditorGUI.indentLevel++; + copyAllBones = EditorGUILayout.Toggle("Copy All Bones", copyAllBones); + + if (!copyAllBones) + { + EditorGUILayout.Space(); + GUILayout.Label("Select Bones to Copy:", EditorStyles.boldLabel); + + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.Height(200)); + + foreach (var bone in boneSelection) + { + boneSelection[bone.Key] = EditorGUILayout.Toggle(bone.Key.ToString(), bone.Value); + } + + EditorGUILayout.EndScrollView(); + } + + EditorGUI.indentLevel--; + } + + EditorGUILayout.Space(); + + // 정보 표시 + if (sourceAvatar != null) + { + var sourceAnimator = sourceAvatar.GetComponent(); + if (sourceAnimator != null && sourceAnimator.avatar != null) + { + EditorGUILayout.HelpBox($"Source Avatar: {sourceAvatar.name}\n" + + $"Human Bone Count: {GetHumanBoneCount(sourceAnimator)}", + MessageType.Info); + } + } + + if (targetAvatar != null) + { + var targetAnimator = targetAvatar.GetComponent(); + if (targetAnimator != null && targetAnimator.avatar != null) + { + EditorGUILayout.HelpBox($"Target Avatar: {targetAvatar.name}\n" + + $"Human Bone Count: {GetHumanBoneCount(targetAnimator)}", + MessageType.Info); + } + } + + EditorGUILayout.Space(); + + // 버튼들 + EditorGUILayout.BeginHorizontal(); + + GUI.enabled = sourceAvatar != null && targetAvatar != null; + + if (GUILayout.Button("Copy Bone Names")) + { + CopyBoneNames(); + } + + if (GUILayout.Button("Preview Changes")) + { + PreviewChanges(); + } + + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + + // 추가 기능 버튼 + EditorGUILayout.BeginHorizontal(); + + GUI.enabled = targetAvatar != null; + + if (GUILayout.Button("Add 'zindnick : ' to Non-Human Bones")) + { + AddPrefixToNonHumanBones(); + } + + GUI.enabled = true; + + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + + if (GUILayout.Button("Clear Selection")) + { + sourceAvatar = null; + targetAvatar = null; + } + } + + private int GetHumanBoneCount(Animator animator) + { + if (animator == null || animator.avatar == null) return 0; + + int count = 0; + for (int i = 0; i < (int)HumanBodyBones.LastBone; i++) + { + if (animator.GetBoneTransform((HumanBodyBones)i) != null) + { + count++; + } + } + return count; + } + + private void CopyBoneNames() + { + if (sourceAvatar == null || targetAvatar == null) + { + EditorUtility.DisplayDialog("Error", "Please select both source and target avatars.", "OK"); + return; + } + + var sourceAnimator = sourceAvatar.GetComponent(); + var targetAnimator = targetAvatar.GetComponent(); + + if (sourceAnimator == null || sourceAnimator.avatar == null) + { + EditorUtility.DisplayDialog("Error", "Source avatar must have an Animator component with a valid Avatar.", "OK"); + return; + } + + if (targetAnimator == null || targetAnimator.avatar == null) + { + EditorUtility.DisplayDialog("Error", "Target avatar must have an Animator component with a valid Avatar.", "OK"); + return; + } + + Undo.RecordObject(targetAvatar, "Copy Human Bone Names"); + + int copiedCount = 0; + + for (int i = 0; i < (int)HumanBodyBones.LastBone; i++) + { + HumanBodyBones bone = (HumanBodyBones)i; + + if (!copyAllBones && boneSelection.ContainsKey(bone) && !boneSelection[bone]) + { + continue; + } + + Transform sourceBone = sourceAnimator.GetBoneTransform(bone); + Transform targetBone = targetAnimator.GetBoneTransform(bone); + + if (sourceBone != null && targetBone != null) + { + targetBone.name = sourceBone.name; + copiedCount++; + } + } + + EditorUtility.SetDirty(targetAvatar); + AssetDatabase.SaveAssets(); + + EditorUtility.DisplayDialog("Success", + $"Successfully copied {copiedCount} bone names from {this.sourceAvatar.name} to {this.targetAvatar.name}.", + "OK"); + } + + private void PreviewChanges() + { + if (sourceAvatar == null || targetAvatar == null) + { + EditorUtility.DisplayDialog("Error", "Please select both source and target avatars.", "OK"); + return; + } + + var sourceAnimator = sourceAvatar.GetComponent(); + var targetAnimator = targetAvatar.GetComponent(); + + if (sourceAnimator == null || sourceAnimator.avatar == null) + { + EditorUtility.DisplayDialog("Error", "Source avatar must have an Animator component with a valid Avatar.", "OK"); + return; + } + + if (targetAnimator == null || targetAnimator.avatar == null) + { + EditorUtility.DisplayDialog("Error", "Target avatar must have an Animator component with a valid Avatar.", "OK"); + return; + } + + string previewText = "Preview of bone name changes:\n\n"; + + for (int i = 0; i < (int)HumanBodyBones.LastBone; i++) + { + HumanBodyBones bone = (HumanBodyBones)i; + + if (!copyAllBones && boneSelection.ContainsKey(bone) && !boneSelection[bone]) + { + continue; + } + + Transform sourceBone = sourceAnimator.GetBoneTransform(bone); + Transform targetBone = targetAnimator.GetBoneTransform(bone); + + if (sourceBone != null && targetBone != null) + { + previewText += $"{bone}: {targetBone.name} → {sourceBone.name}\n"; + } + } + + EditorUtility.DisplayDialog("Preview", previewText, "OK"); + } + + private void AddPrefixToNonHumanBones() + { + if (targetAvatar == null) + { + EditorUtility.DisplayDialog("Error", "Please select a target avatar.", "OK"); + return; + } + + var targetAnimator = targetAvatar.GetComponent(); + + if (targetAnimator == null || targetAnimator.avatar == null) + { + EditorUtility.DisplayDialog("Error", "Target avatar must have an Animator component with a valid Avatar.", "OK"); + return; + } + + // 휴먼본 목록 수집 + HashSet humanBones = new HashSet(); + for (int i = 0; i < (int)HumanBodyBones.LastBone; i++) + { + Transform humanBone = targetAnimator.GetBoneTransform((HumanBodyBones)i); + if (humanBone != null) + { + humanBones.Add(humanBone); + } + } + + Undo.RecordObject(targetAvatar, "Add Prefix to Non-Human Bones"); + + int modifiedCount = 0; + Transform[] allTransforms = targetAvatar.GetComponentsInChildren(); + + foreach (Transform transform in allTransforms) + { + // 휴먼본이 아니고, 이미 접두사가 없는 경우에만 추가 + if (!humanBones.Contains(transform) && !transform.name.StartsWith("zindnick : ")) + { + transform.name = "zindnick : " + transform.name; + modifiedCount++; + } + } + + EditorUtility.SetDirty(targetAvatar); + AssetDatabase.SaveAssets(); + + EditorUtility.DisplayDialog("Success", + $"Successfully added 'zindnick : ' prefix to {modifiedCount} non-human bones in {targetAvatar.name}.", + "OK"); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Editor/HumanBoneNameCopier.cs.meta b/Assets/Scripts/Editor/HumanBoneNameCopier.cs.meta new file mode 100644 index 00000000..1bb30f51 --- /dev/null +++ b/Assets/Scripts/Editor/HumanBoneNameCopier.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0f15a4073776b12498e7afd36311f380 \ No newline at end of file