diff --git a/Assets/Scripts/Editor/MotionCaptureFileRenamer.cs b/Assets/Scripts/Editor/MotionCaptureFileRenamer.cs
new file mode 100644
index 00000000..c76973d7
--- /dev/null
+++ b/Assets/Scripts/Editor/MotionCaptureFileRenamer.cs
@@ -0,0 +1,691 @@
+using UnityEngine;
+using UnityEditor;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System;
+
+///
+/// 모션 캡쳐 파일 네이밍 편의성 툴
+/// - 선택한 파일들에 일괄적으로 접미사/접두사 추가
+/// - 번호 매기기, 날짜 스탬프 등 다양한 네이밍 패턴 지원
+///
+public class MotionCaptureFileRenamer : EditorWindow
+{
+ #region Nested Classes
+ [Serializable]
+ public class NamingPreset
+ {
+ public string presetName = "New Preset";
+ public NamingMode mode = NamingMode.Suffix;
+ public string customText = "";
+ public int startNumber = 1;
+ public int numberPadding = 3;
+ public bool includeDate = false;
+ public bool includeTime = false;
+ public string dateFormat = "yyyyMMdd";
+ public string timeFormat = "HHmmss";
+ public string separator = "_";
+ }
+
+ public enum NamingMode
+ {
+ Suffix, // 파일명 뒤에 추가
+ Prefix, // 파일명 앞에 추가
+ Replace, // 전체 이름 변경
+ Sequential // 순차 번호
+ }
+ #endregion
+
+ #region Fields
+ private List selectedFiles = new List();
+ private Vector2 scrollPosition;
+
+ // Naming settings
+ private NamingMode namingMode = NamingMode.Suffix;
+ private string customText = "_ok";
+ private int startNumber = 1;
+ private int numberPadding = 3;
+ private bool includeDate = false;
+ private bool includeTime = false;
+ private string dateFormat = "yyyyMMdd";
+ private string timeFormat = "HHmmss";
+ private string separator = "_";
+
+ // Preview
+ private bool showPreview = true;
+ private Dictionary previewNames = new Dictionary();
+
+ // Presets
+ private List presets = new List();
+ private int selectedPresetIndex = -1;
+ private bool showPresetSection = false;
+ private string newPresetName = "My Preset";
+
+ // UI Style
+ private GUIStyle headerStyle;
+ private GUIStyle boxStyle;
+ private Color successColor = new Color(0.5f, 1f, 0.5f, 0.3f);
+ private Color warningColor = new Color(1f, 1f, 0.5f, 0.3f);
+ private Color errorColor = new Color(1f, 0.5f, 0.5f, 0.3f);
+ #endregion
+
+ #region Window Initialization
+ [MenuItem("Tools/Motion Capture/File Renamer")]
+ public static void ShowWindow()
+ {
+ var window = GetWindow("MoCap File Renamer");
+ window.minSize = new Vector2(450, 500);
+ }
+
+ private void OnEnable()
+ {
+ LoadPresets();
+ RefreshSelectedFiles();
+ }
+
+ private void OnDisable()
+ {
+ SavePresets();
+ }
+
+ private void InitializeStyles()
+ {
+ if (headerStyle == null)
+ {
+ headerStyle = new GUIStyle(EditorStyles.boldLabel)
+ {
+ fontSize = 14,
+ alignment = TextAnchor.MiddleLeft
+ };
+ }
+
+ if (boxStyle == null)
+ {
+ boxStyle = new GUIStyle(EditorStyles.helpBox)
+ {
+ padding = new RectOffset(10, 10, 10, 10)
+ };
+ }
+ }
+ #endregion
+
+ #region GUI
+ private void OnGUI()
+ {
+ InitializeStyles();
+
+ scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
+
+ DrawHeader();
+ DrawFileSelectionSection();
+ DrawNamingOptionsSection();
+ DrawPreviewSection();
+ DrawPresetsSection();
+ DrawActionButtons();
+
+ EditorGUILayout.EndScrollView();
+ }
+
+ private void DrawHeader()
+ {
+ EditorGUILayout.Space(10);
+ EditorGUILayout.LabelField("Motion Capture File Renamer", headerStyle);
+ EditorGUILayout.LabelField("모션 캡쳐 파일 일괄 네이밍 도구", EditorStyles.miniLabel);
+ EditorGUILayout.Space(10);
+ EditorGUILayout.HelpBox("프로젝트 뷰에서 파일을 선택하거나 아래 영역에 드래그 앤 드롭하세요.", MessageType.Info);
+ EditorGUILayout.Space(5);
+ }
+
+ private void DrawFileSelectionSection()
+ {
+ EditorGUILayout.BeginVertical(boxStyle);
+ EditorGUILayout.LabelField("📁 File Selection", EditorStyles.boldLabel);
+ EditorGUILayout.Space(5);
+
+ // Drag and drop area
+ Rect dropArea = GUILayoutUtility.GetRect(0f, 50f, GUILayout.ExpandWidth(true));
+ GUI.Box(dropArea, "Drag & Drop Files Here\n또는 'Load Selected Files' 버튼 클릭", EditorStyles.helpBox);
+
+ HandleDragAndDrop(dropArea);
+
+ EditorGUILayout.Space(5);
+
+ EditorGUILayout.BeginHorizontal();
+ if (GUILayout.Button("Load Selected Files", GUILayout.Height(30)))
+ {
+ RefreshSelectedFiles();
+ }
+ if (GUILayout.Button("Clear", GUILayout.Width(80), GUILayout.Height(30)))
+ {
+ selectedFiles.Clear();
+ previewNames.Clear();
+ }
+ EditorGUILayout.EndHorizontal();
+
+ EditorGUILayout.Space(5);
+ EditorGUILayout.LabelField($"Selected Files: {selectedFiles.Count}", EditorStyles.miniLabel);
+
+ if (selectedFiles.Count > 0)
+ {
+ EditorGUILayout.Space(5);
+ EditorGUILayout.LabelField("Current Files:", EditorStyles.miniBoldLabel);
+
+ var rect = EditorGUILayout.BeginVertical(GUILayout.MaxHeight(100));
+ scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.MaxHeight(100));
+
+ for (int i = 0; i < selectedFiles.Count; i++)
+ {
+ if (selectedFiles[i] != null)
+ {
+ EditorGUILayout.LabelField($"{i + 1}. {selectedFiles[i].name}", EditorStyles.miniLabel);
+ }
+ }
+
+ EditorGUILayout.EndScrollView();
+ EditorGUILayout.EndVertical();
+ }
+
+ EditorGUILayout.EndVertical();
+ EditorGUILayout.Space(10);
+ }
+
+ private void DrawNamingOptionsSection()
+ {
+ EditorGUILayout.BeginVertical(boxStyle);
+ EditorGUILayout.LabelField("⚙️ Naming Options", EditorStyles.boldLabel);
+ EditorGUILayout.Space(5);
+
+ // Naming mode
+ namingMode = (NamingMode)EditorGUILayout.EnumPopup("Naming Mode", namingMode);
+ EditorGUILayout.Space(5);
+
+ switch (namingMode)
+ {
+ case NamingMode.Suffix:
+ DrawSuffixOptions();
+ break;
+ case NamingMode.Prefix:
+ DrawPrefixOptions();
+ break;
+ case NamingMode.Replace:
+ DrawReplaceOptions();
+ break;
+ case NamingMode.Sequential:
+ DrawSequentialOptions();
+ break;
+ }
+
+ EditorGUILayout.Space(5);
+
+ // Common options
+ EditorGUILayout.LabelField("Additional Options", EditorStyles.miniBoldLabel);
+ separator = EditorGUILayout.TextField("Separator", separator);
+ includeDate = EditorGUILayout.Toggle("Include Date", includeDate);
+ if (includeDate)
+ {
+ EditorGUI.indentLevel++;
+ dateFormat = EditorGUILayout.TextField("Date Format", dateFormat);
+ EditorGUILayout.LabelField($"Example: {DateTime.Now.ToString(dateFormat)}", EditorStyles.miniLabel);
+ EditorGUI.indentLevel--;
+ }
+
+ includeTime = EditorGUILayout.Toggle("Include Time", includeTime);
+ if (includeTime)
+ {
+ EditorGUI.indentLevel++;
+ timeFormat = EditorGUILayout.TextField("Time Format", timeFormat);
+ EditorGUILayout.LabelField($"Example: {DateTime.Now.ToString(timeFormat)}", EditorStyles.miniLabel);
+ EditorGUI.indentLevel--;
+ }
+
+ EditorGUILayout.EndVertical();
+ EditorGUILayout.Space(10);
+ }
+
+ private void DrawSuffixOptions()
+ {
+ EditorGUILayout.HelpBox("파일명 뒤에 텍스트를 추가합니다.\n예: Animation.anim → Animation_ok.anim", MessageType.None);
+ customText = EditorGUILayout.TextField("Suffix Text", customText);
+
+ EditorGUILayout.Space(5);
+ EditorGUILayout.LabelField("Quick Presets:", EditorStyles.miniBoldLabel);
+ EditorGUILayout.BeginHorizontal();
+ if (GUILayout.Button("_ok")) customText = "_ok";
+ if (GUILayout.Button("_final")) customText = "_final";
+ if (GUILayout.Button("_test")) customText = "_test";
+ if (GUILayout.Button("_backup")) customText = "_backup";
+ EditorGUILayout.EndHorizontal();
+ }
+
+ private void DrawPrefixOptions()
+ {
+ EditorGUILayout.HelpBox("파일명 앞에 텍스트를 추가합니다.\n예: Animation.anim → Take001_Animation.anim", MessageType.None);
+ customText = EditorGUILayout.TextField("Prefix Text", customText);
+
+ EditorGUILayout.Space(5);
+ EditorGUILayout.LabelField("Quick Presets:", EditorStyles.miniBoldLabel);
+ EditorGUILayout.BeginHorizontal();
+ if (GUILayout.Button("Take001")) customText = "Take001";
+ if (GUILayout.Button("MoCap")) customText = "MoCap";
+ if (GUILayout.Button("Test")) customText = "Test";
+ if (GUILayout.Button("WIP")) customText = "WIP";
+ EditorGUILayout.EndHorizontal();
+ }
+
+ private void DrawReplaceOptions()
+ {
+ EditorGUILayout.HelpBox("파일명을 완전히 새로운 이름으로 변경합니다.\n(확장자는 유지됩니다)", MessageType.Warning);
+ customText = EditorGUILayout.TextField("New Base Name", customText);
+
+ EditorGUILayout.Space(5);
+ bool autoNumber = EditorGUILayout.Toggle("Auto Number", true);
+ if (autoNumber)
+ {
+ EditorGUI.indentLevel++;
+ startNumber = EditorGUILayout.IntField("Start Number", startNumber);
+ numberPadding = EditorGUILayout.IntSlider("Number Padding", numberPadding, 1, 5);
+ EditorGUI.indentLevel--;
+ }
+ }
+
+ private void DrawSequentialOptions()
+ {
+ EditorGUILayout.HelpBox("선택한 파일들에 순차적으로 번호를 부여합니다.\n예: Take001, Take002, Take003...", MessageType.None);
+
+ customText = EditorGUILayout.TextField("Base Name", customText);
+ startNumber = EditorGUILayout.IntField("Start Number", startNumber);
+ numberPadding = EditorGUILayout.IntSlider("Number Padding", numberPadding, 1, 5);
+
+ EditorGUILayout.Space(5);
+ EditorGUILayout.LabelField($"Example: {customText}{startNumber.ToString().PadLeft(numberPadding, '0')}", EditorStyles.miniLabel);
+ }
+
+ private void DrawPreviewSection()
+ {
+ showPreview = EditorGUILayout.Foldout(showPreview, "🔍 Preview", true, EditorStyles.foldoutHeader);
+
+ if (showPreview)
+ {
+ EditorGUILayout.BeginVertical(boxStyle);
+
+ if (selectedFiles.Count == 0)
+ {
+ EditorGUILayout.HelpBox("파일을 먼저 선택하세요.", MessageType.Info);
+ }
+ else
+ {
+ if (GUILayout.Button("Generate Preview", GUILayout.Height(25)))
+ {
+ GeneratePreview();
+ }
+
+ if (previewNames.Count > 0)
+ {
+ EditorGUILayout.Space(5);
+ EditorGUILayout.LabelField("변경 예정:", EditorStyles.miniBoldLabel);
+
+ EditorGUILayout.BeginVertical(EditorStyles.helpBox);
+ Vector2 previewScroll = EditorGUILayout.BeginScrollView(scrollPosition, GUILayout.MaxHeight(150));
+
+ foreach (var kvp in previewNames)
+ {
+ EditorGUILayout.BeginHorizontal();
+ EditorGUILayout.LabelField(kvp.Key, GUILayout.Width(150));
+ EditorGUILayout.LabelField("→", GUILayout.Width(20));
+ EditorGUILayout.LabelField(kvp.Value, EditorStyles.boldLabel);
+ EditorGUILayout.EndHorizontal();
+ }
+
+ EditorGUILayout.EndScrollView();
+ EditorGUILayout.EndVertical();
+ }
+ }
+
+ EditorGUILayout.EndVertical();
+ }
+
+ EditorGUILayout.Space(10);
+ }
+
+ private void DrawPresetsSection()
+ {
+ showPresetSection = EditorGUILayout.Foldout(showPresetSection, "💾 Presets", true, EditorStyles.foldoutHeader);
+
+ if (showPresetSection)
+ {
+ EditorGUILayout.BeginVertical(boxStyle);
+
+ EditorGUILayout.BeginHorizontal();
+ newPresetName = EditorGUILayout.TextField("Preset Name", newPresetName);
+ if (GUILayout.Button("Save Current", GUILayout.Width(100)))
+ {
+ SaveCurrentAsPreset();
+ }
+ EditorGUILayout.EndHorizontal();
+
+ EditorGUILayout.Space(5);
+
+ if (presets.Count > 0)
+ {
+ EditorGUILayout.LabelField("Saved Presets:", EditorStyles.miniBoldLabel);
+
+ for (int i = 0; i < presets.Count; i++)
+ {
+ EditorGUILayout.BeginHorizontal();
+
+ if (GUILayout.Button(presets[i].presetName, GUILayout.Height(25)))
+ {
+ LoadPreset(i);
+ }
+
+ if (GUILayout.Button("×", GUILayout.Width(25), GUILayout.Height(25)))
+ {
+ if (EditorUtility.DisplayDialog("Delete Preset",
+ $"'{presets[i].presetName}' 프리셋을 삭제하시겠습니까?", "Delete", "Cancel"))
+ {
+ presets.RemoveAt(i);
+ SavePresets();
+ }
+ }
+
+ EditorGUILayout.EndHorizontal();
+ }
+ }
+ else
+ {
+ EditorGUILayout.HelpBox("저장된 프리셋이 없습니다.", MessageType.Info);
+ }
+
+ EditorGUILayout.EndVertical();
+ }
+
+ EditorGUILayout.Space(10);
+ }
+
+ private void DrawActionButtons()
+ {
+ EditorGUILayout.BeginVertical(boxStyle);
+
+ GUI.enabled = selectedFiles.Count > 0;
+
+ EditorGUILayout.BeginHorizontal();
+
+ if (GUILayout.Button("Apply Rename", GUILayout.Height(40)))
+ {
+ if (EditorUtility.DisplayDialog("Confirm Rename",
+ $"{selectedFiles.Count}개의 파일 이름을 변경하시겠습니까?\n\n이 작업은 Undo로 되돌릴 수 있습니다.",
+ "Rename", "Cancel"))
+ {
+ ApplyRename();
+ }
+ }
+
+ EditorGUILayout.EndHorizontal();
+
+ GUI.enabled = true;
+
+ EditorGUILayout.EndVertical();
+ }
+ #endregion
+
+ #region File Selection
+ private void RefreshSelectedFiles()
+ {
+ selectedFiles.Clear();
+
+ var selected = Selection.objects;
+ foreach (var obj in selected)
+ {
+ string path = AssetDatabase.GetAssetPath(obj);
+ if (!string.IsNullOrEmpty(path) && !AssetDatabase.IsValidFolder(path))
+ {
+ selectedFiles.Add(obj);
+ }
+ }
+
+ previewNames.Clear();
+ Debug.Log($"Loaded {selectedFiles.Count} files");
+ }
+
+ private void HandleDragAndDrop(Rect dropArea)
+ {
+ Event evt = Event.current;
+
+ if (dropArea.Contains(evt.mousePosition))
+ {
+ if (evt.type == EventType.DragUpdated)
+ {
+ DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
+ evt.Use();
+ }
+ else if (evt.type == EventType.DragPerform)
+ {
+ DragAndDrop.AcceptDrag();
+
+ foreach (var obj in DragAndDrop.objectReferences)
+ {
+ string path = AssetDatabase.GetAssetPath(obj);
+ if (!string.IsNullOrEmpty(path) && !AssetDatabase.IsValidFolder(path))
+ {
+ if (!selectedFiles.Contains(obj))
+ {
+ selectedFiles.Add(obj);
+ }
+ }
+ }
+
+ evt.Use();
+ }
+ }
+ }
+ #endregion
+
+ #region Naming Logic
+ private void GeneratePreview()
+ {
+ previewNames.Clear();
+
+ for (int i = 0; i < selectedFiles.Count; i++)
+ {
+ var file = selectedFiles[i];
+ if (file == null) continue;
+
+ string originalName = file.name;
+ string newName = GenerateNewName(originalName, i);
+
+ previewNames[originalName] = newName;
+ }
+ }
+
+ private string GenerateNewName(string originalName, int index)
+ {
+ string baseName = originalName;
+ string result = "";
+
+ // Build timestamp if needed
+ string timestamp = "";
+ if (includeDate)
+ {
+ timestamp += DateTime.Now.ToString(dateFormat);
+ }
+ if (includeTime)
+ {
+ if (!string.IsNullOrEmpty(timestamp))
+ timestamp += separator;
+ timestamp += DateTime.Now.ToString(timeFormat);
+ }
+
+ switch (namingMode)
+ {
+ case NamingMode.Suffix:
+ result = baseName + separator + customText;
+ if (!string.IsNullOrEmpty(timestamp))
+ result += separator + timestamp;
+ break;
+
+ case NamingMode.Prefix:
+ result = customText + separator + baseName;
+ if (!string.IsNullOrEmpty(timestamp))
+ result += separator + timestamp;
+ break;
+
+ case NamingMode.Replace:
+ string number = (startNumber + index).ToString().PadLeft(numberPadding, '0');
+ result = customText + separator + number;
+ if (!string.IsNullOrEmpty(timestamp))
+ result += separator + timestamp;
+ break;
+
+ case NamingMode.Sequential:
+ string seqNumber = (startNumber + index).ToString().PadLeft(numberPadding, '0');
+ result = customText + seqNumber;
+ if (!string.IsNullOrEmpty(timestamp))
+ result += separator + timestamp;
+ break;
+ }
+
+ // Remove invalid characters
+ result = string.Join("_", result.Split(Path.GetInvalidFileNameChars()));
+
+ return result;
+ }
+
+ private void ApplyRename()
+ {
+ if (selectedFiles.Count == 0)
+ {
+ EditorUtility.DisplayDialog("Error", "선택된 파일이 없습니다.", "OK");
+ return;
+ }
+
+ int successCount = 0;
+ int failCount = 0;
+
+ AssetDatabase.StartAssetEditing();
+
+ try
+ {
+ for (int i = 0; i < selectedFiles.Count; i++)
+ {
+ var file = selectedFiles[i];
+ if (file == null) continue;
+
+ string assetPath = AssetDatabase.GetAssetPath(file);
+ string directory = Path.GetDirectoryName(assetPath);
+ string extension = Path.GetExtension(assetPath);
+ string oldName = file.name;
+ string newName = GenerateNewName(oldName, i);
+ string newPath = Path.Combine(directory, newName + extension);
+
+ // Check if file already exists
+ if (File.Exists(newPath) && assetPath != newPath)
+ {
+ Debug.LogWarning($"File already exists: {newPath}. Skipping {oldName}.");
+ failCount++;
+ continue;
+ }
+
+ // Rename asset
+ string error = AssetDatabase.RenameAsset(assetPath, newName);
+
+ if (string.IsNullOrEmpty(error))
+ {
+ Debug.Log($"Renamed: {oldName} → {newName}");
+ successCount++;
+ }
+ else
+ {
+ Debug.LogError($"Failed to rename {oldName}: {error}");
+ failCount++;
+ }
+ }
+ }
+ finally
+ {
+ AssetDatabase.StopAssetEditing();
+ AssetDatabase.SaveAssets();
+ AssetDatabase.Refresh();
+ }
+
+ string message = $"Rename completed!\n\nSuccess: {successCount}\nFailed: {failCount}";
+ EditorUtility.DisplayDialog("Rename Result", message, "OK");
+
+ // Clear selection and preview
+ selectedFiles.Clear();
+ previewNames.Clear();
+ }
+ #endregion
+
+ #region Presets
+ private void SaveCurrentAsPreset()
+ {
+ var preset = new NamingPreset
+ {
+ presetName = newPresetName,
+ mode = namingMode,
+ customText = customText,
+ startNumber = startNumber,
+ numberPadding = numberPadding,
+ includeDate = includeDate,
+ includeTime = includeTime,
+ dateFormat = dateFormat,
+ timeFormat = timeFormat,
+ separator = separator
+ };
+
+ presets.Add(preset);
+ SavePresets();
+
+ Debug.Log($"Preset saved: {newPresetName}");
+ EditorUtility.DisplayDialog("Success", $"프리셋 '{newPresetName}'이(가) 저장되었습니다.", "OK");
+ }
+
+ private void LoadPreset(int index)
+ {
+ if (index < 0 || index >= presets.Count) return;
+
+ var preset = presets[index];
+ namingMode = preset.mode;
+ customText = preset.customText;
+ startNumber = preset.startNumber;
+ numberPadding = preset.numberPadding;
+ includeDate = preset.includeDate;
+ includeTime = preset.includeTime;
+ dateFormat = preset.dateFormat;
+ timeFormat = preset.timeFormat;
+ separator = preset.separator;
+
+ Debug.Log($"Preset loaded: {preset.presetName}");
+ }
+
+ private void SavePresets()
+ {
+ string json = JsonUtility.ToJson(new PresetList { presets = presets }, true);
+ EditorPrefs.SetString("MotionCaptureFileRenamer_Presets", json);
+ }
+
+ private void LoadPresets()
+ {
+ string json = EditorPrefs.GetString("MotionCaptureFileRenamer_Presets", "");
+ if (!string.IsNullOrEmpty(json))
+ {
+ try
+ {
+ var presetList = JsonUtility.FromJson(json);
+ presets = presetList.presets ?? new List();
+ }
+ catch
+ {
+ presets = new List();
+ }
+ }
+ }
+
+ [Serializable]
+ private class PresetList
+ {
+ public List presets = new List();
+ }
+ #endregion
+}