ADD : 네이밍 툴 추가

This commit is contained in:
qsxft258@gmail.com 2025-10-16 20:39:12 +09:00
parent 11112e9fcd
commit 540b8645f2

View File

@ -0,0 +1,691 @@
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
}