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 }