Streamingle_URP/Assets/Scripts/Editor/MotionCaptureFileRenamer.cs

692 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System;
/// <summary>
/// 모션 캡쳐 파일 네이밍 편의성 툴
/// - 선택한 파일들에 일괄적으로 접미사/접두사 추가
/// - 번호 매기기, 날짜 스탬프 등 다양한 네이밍 패턴 지원
/// </summary>
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<UnityEngine.Object> selectedFiles = new List<UnityEngine.Object>();
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<string, string> previewNames = new Dictionary<string, string>();
// Presets
private List<NamingPreset> presets = new List<NamingPreset>();
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<MotionCaptureFileRenamer>("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<PresetList>(json);
presets = presetList.presets ?? new List<NamingPreset>();
}
catch
{
presets = new List<NamingPreset>();
}
}
}
[Serializable]
private class PresetList
{
public List<NamingPreset> presets = new List<NamingPreset>();
}
#endregion
}