diff --git a/Assets/ModelRenderer/Editor/CombineActionSOAssigner.cs b/Assets/ModelRenderer/Editor/CombineActionSOAssigner.cs index e7040e863f..5fe1ff8806 100644 --- a/Assets/ModelRenderer/Editor/CombineActionSOAssigner.cs +++ b/Assets/ModelRenderer/Editor/CombineActionSOAssigner.cs @@ -1,6 +1,7 @@ // UTF-8 with BOM — required for Chinese character paths using System.Collections.Generic; using System.IO; +using System.Text; using BrewMonster.Scripts.ECModel; using UnityEditor; using UnityEngine; @@ -10,10 +11,14 @@ public class CombineActionSOAssigner : EditorWindow // Fixed — this is always where the weapon prefabs live private const string PrefabRootPath = "Assets/ModelRenderer/Art/Models/models/weapons"; + // Log files land next to the project's Logs/ folder + private static readonly string LogDir = Path.GetFullPath("Logs/SOAssigner"); + private string _soRootPath = "Assets/ModelRenderer/Art/Models/models/weapons"; private bool _isDryRun = true; private Vector2 _scrollPos; private readonly List _log = new(); + private string _lastLogFile; private static readonly GUIStyle HeaderStyle = new(EditorStyles.boldLabel) { @@ -94,10 +99,18 @@ public class CombineActionSOAssigner : EditorWindow { EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("Log", EditorStyles.boldLabel); + if (!string.IsNullOrEmpty(_lastLogFile) && GUILayout.Button("Open Log File", GUILayout.Width(100))) + EditorUtility.RevealInFinder(_lastLogFile); if (GUILayout.Button("Clear", GUILayout.Width(60))) + { _log.Clear(); + _lastLogFile = null; + } EditorGUILayout.EndHorizontal(); + if (!string.IsNullOrEmpty(_lastLogFile)) + EditorGUILayout.LabelField(_lastLogFile, EditorStyles.miniLabel); + _scrollPos = EditorGUILayout.BeginScrollView(_scrollPos, GUILayout.ExpandHeight(true)); foreach (var line in _log) EditorGUILayout.LabelField(line, EditorStyles.wordWrappedLabel); @@ -121,6 +134,7 @@ public class CombineActionSOAssigner : EditorWindow string[] guids = AssetDatabase.FindAssets("t:Prefab", new[] { PrefabRootPath }); int orphans = 0; + var fileLines = new List { $"=== ORPHAN CHECK {Timestamp()} ===" }; foreach (string guid in guids) { @@ -129,24 +143,36 @@ public class CombineActionSOAssigner : EditorWindow if (AssetDatabase.LoadAssetAtPath(soPath) == null) { - _log.Add($"[ORPHAN] {Path.GetFileNameWithoutExtension(prefabPath)}"); - _log.Add($" expected SO: {soPath}"); + string line1 = $"[ORPHAN] {Path.GetFileNameWithoutExtension(prefabPath)}"; + string line2 = $" expected SO: {soPath}"; + _log.Add(line1); + _log.Add(line2); + fileLines.Add(line1); + fileLines.Add(line2); orphans++; } } - _log.Add($"=== {orphans} orphan(s) out of {guids.Length} prefab(s) ==="); + string summary = $"=== {orphans} orphan(s) out of {guids.Length} prefab(s) ==="; + _log.Add(summary); + fileLines.Add(summary); + + _lastLogFile = WriteLogFile("OrphanCheck", fileLines); + Debug.Log($"[SO Assigner] Orphan Check — {orphans}/{guids.Length} orphans. Log: {_lastLogFile}"); Repaint(); } private void RunAssignment() { _log.Clear(); - _log.Add(_isDryRun ? "=== DRY RUN ===" : "=== LIVE RUN ==="); + string header = _isDryRun ? "=== DRY RUN ===" : "=== LIVE RUN ==="; + _log.Add(header); string[] guids = AssetDatabase.FindAssets("t:Prefab", new[] { PrefabRootPath }); int matched = 0, skipped = 0, failed = 0; + var fileLines = new List { $"{header} {Timestamp()}" }; + try { for (int i = 0; i < guids.Length; i++) @@ -163,26 +189,32 @@ public class CombineActionSOAssigner : EditorWindow CombinedActionSO so = AssetDatabase.LoadAssetAtPath(soPath); if (so == null) { - _log.Add($"[SKIP] {prefabName} → no SO at: {soPath}"); + string skipLine = $"[SKIP] {prefabName} → {soPath}"; + fileLines.Add(skipLine); + if (!_isDryRun) + _log.Add(skipLine); // window only gets skips in live mode skipped++; continue; } if (_isDryRun) { - _log.Add($"[MATCH] {prefabName} → {soPath}"); + // File gets every match; window stays quiet + fileLines.Add($"[MATCH] {prefabName} → {soPath}"); matched++; } else { if (!TryAssign(prefabPath, so, out string error)) { - _log.Add($"[ERROR] {prefabName} — {error}"); + string errLine = $"[ERROR] {prefabName} — {error}"; + _log.Add(errLine); + fileLines.Add(errLine); failed++; } else { - _log.Add($"[OK] {prefabName}"); + fileLines.Add($"[OK] {prefabName}"); matched++; } } @@ -203,7 +235,18 @@ public class CombineActionSOAssigner : EditorWindow if (!_isDryRun) AssetDatabase.SaveAssets(); - _log.Add($"=== DONE — matched: {matched}, skipped: {skipped}, failed: {failed} (total: {guids.Length}) ==="); + string summary = $"matched: {matched}, skipped: {skipped}, failed: {failed} (total: {guids.Length})"; + string summaryLine = $"=== DONE — {summary} ==="; + _log.Add(summaryLine); + fileLines.Add(summaryLine); + + _lastLogFile = WriteLogFile(_isDryRun ? "DryRun" : "LiveRun", fileLines); + + Debug.Log($"[SO Assigner] {(_isDryRun ? "DRY RUN" : "LIVE")} — {summary} | Log: {_lastLogFile}"); + + if (skipped > 0) + Debug.LogWarning($"[SO Assigner] {skipped} unmatched prefab(s) — see log file for details: {_lastLogFile}"); + Repaint(); } @@ -248,6 +291,20 @@ public class CombineActionSOAssigner : EditorWindow } } + // ── File logging ───────────────────────────────────────────────────────── + + private static string WriteLogFile(string prefix, List lines) + { + Directory.CreateDirectory(LogDir); + string fileName = $"{prefix}_{System.DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log"; + string fullPath = Path.Combine(LogDir, fileName); + File.WriteAllLines(fullPath, lines, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)); + return fullPath; + } + + private static string Timestamp() => + System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + // ── Path helpers ───────────────────────────────────────────────────────── ///