2025-03-18 19:00:50 +08:00
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEditor;
|
|
|
|
|
using UnityEditor.Build.Reporting;
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
2025-03-19 20:03:12 +08:00
|
|
|
using System;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using System.Collections.Generic;
|
2025-03-20 19:24:31 +08:00
|
|
|
using System.Linq;
|
2025-03-18 19:00:50 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
namespace UnityMCP.Editor.Commands
|
2025-03-18 19:00:50 +08:00
|
|
|
{
|
|
|
|
|
/// <summary>
|
2025-03-20 19:24:31 +08:00
|
|
|
/// Handles editor control commands like undo, redo, play, pause, stop, and build operations.
|
2025-03-18 19:00:50 +08:00
|
|
|
/// </summary>
|
2025-03-20 19:24:31 +08:00
|
|
|
public static class EditorControlHandler
|
2025-03-18 19:00:50 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Handles editor control commands
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static object HandleEditorControl(JObject @params)
|
2025-03-18 19:00:50 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
string command = (string)@params["command"];
|
|
|
|
|
JObject commandParams = (JObject)@params["params"];
|
2025-03-18 19:00:50 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
return command.ToUpper() switch
|
|
|
|
|
{
|
|
|
|
|
"UNDO" => HandleUndo(),
|
|
|
|
|
"REDO" => HandleRedo(),
|
|
|
|
|
"PLAY" => HandlePlay(),
|
|
|
|
|
"PAUSE" => HandlePause(),
|
|
|
|
|
"STOP" => HandleStop(),
|
|
|
|
|
"BUILD" => HandleBuild(commandParams),
|
|
|
|
|
"EXECUTE_COMMAND" => HandleExecuteCommand(commandParams),
|
|
|
|
|
"READ_CONSOLE" => ReadConsole(commandParams),
|
2025-03-21 03:51:26 +08:00
|
|
|
"GET_AVAILABLE_COMMANDS" => GetAvailableCommands(),
|
2025-03-20 19:24:31 +08:00
|
|
|
_ => new { error = $"Unknown editor control command: {command}" },
|
|
|
|
|
};
|
2025-03-18 19:00:50 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
private static object HandleUndo()
|
2025-03-18 19:00:50 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
Undo.PerformUndo();
|
|
|
|
|
return new { message = "Undo performed successfully" };
|
2025-03-18 19:00:50 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
private static object HandleRedo()
|
2025-03-18 19:00:50 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
Undo.PerformRedo();
|
|
|
|
|
return new { message = "Redo performed successfully" };
|
2025-03-18 19:00:50 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
private static object HandlePlay()
|
2025-03-18 19:00:50 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
if (!EditorApplication.isPlaying)
|
2025-03-18 19:00:50 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
EditorApplication.isPlaying = true;
|
|
|
|
|
return new { message = "Entered play mode" };
|
2025-03-18 19:00:50 +08:00
|
|
|
}
|
2025-03-20 19:24:31 +08:00
|
|
|
return new { message = "Already in play mode" };
|
2025-03-18 19:00:50 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
private static object HandlePause()
|
2025-03-18 19:00:50 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
if (EditorApplication.isPlaying)
|
|
|
|
|
{
|
|
|
|
|
EditorApplication.isPaused = !EditorApplication.isPaused;
|
|
|
|
|
return new { message = EditorApplication.isPaused ? "Game paused" : "Game resumed" };
|
|
|
|
|
}
|
|
|
|
|
return new { message = "Not in play mode" };
|
2025-03-18 19:00:50 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
private static object HandleStop()
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
if (EditorApplication.isPlaying)
|
|
|
|
|
{
|
|
|
|
|
EditorApplication.isPlaying = false;
|
|
|
|
|
return new { message = "Exited play mode" };
|
|
|
|
|
}
|
|
|
|
|
return new { message = "Not in play mode" };
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
private static object HandleBuild(JObject @params)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
string platform = (string)@params["platform"];
|
|
|
|
|
string buildPath = (string)@params["buildPath"];
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
BuildTarget target = GetBuildTarget(platform);
|
|
|
|
|
if ((int)target == -1)
|
2025-03-20 05:12:58 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
return new { error = $"Unsupported platform: {platform}" };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BuildPlayerOptions buildPlayerOptions = new()
|
|
|
|
|
{
|
|
|
|
|
scenes = GetEnabledScenes(),
|
|
|
|
|
target = target,
|
|
|
|
|
locationPathName = buildPath
|
2025-03-20 05:12:58 +08:00
|
|
|
};
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions);
|
2025-03-20 05:12:58 +08:00
|
|
|
return new
|
|
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
message = "Build completed successfully",
|
|
|
|
|
report.summary
|
2025-03-20 05:12:58 +08:00
|
|
|
};
|
2025-03-20 19:24:31 +08:00
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
return new { error = $"Build failed: {e.Message}" };
|
|
|
|
|
}
|
2025-03-18 19:00:50 +08:00
|
|
|
}
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
private static object HandleExecuteCommand(JObject @params)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
string commandName = (string)@params["commandName"];
|
|
|
|
|
try
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
EditorApplication.ExecuteMenuItem(commandName);
|
|
|
|
|
return new { message = $"Executed command: {commandName}" };
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
return new { error = $"Failed to execute command: {e.Message}" };
|
|
|
|
|
}
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Reads log messages from the Unity Console
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="params">Parameters containing filtering options</param>
|
|
|
|
|
/// <returns>Object containing console messages filtered by type</returns>
|
|
|
|
|
public static object ReadConsole(JObject @params)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
// Default values for show flags
|
|
|
|
|
bool showLogs = true;
|
|
|
|
|
bool showWarnings = true;
|
|
|
|
|
bool showErrors = true;
|
|
|
|
|
string searchTerm = string.Empty;
|
|
|
|
|
|
|
|
|
|
// Get filter parameters if provided
|
|
|
|
|
if (@params != null)
|
|
|
|
|
{
|
|
|
|
|
if (@params["show_logs"] != null) showLogs = (bool)@params["show_logs"];
|
|
|
|
|
if (@params["show_warnings"] != null) showWarnings = (bool)@params["show_warnings"];
|
|
|
|
|
if (@params["show_errors"] != null) showErrors = (bool)@params["show_errors"];
|
|
|
|
|
if (@params["search_term"] != null) searchTerm = (string)@params["search_term"];
|
|
|
|
|
}
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// Get required types and methods via reflection
|
|
|
|
|
Type logEntriesType = Type.GetType("UnityEditor.LogEntries,UnityEditor");
|
|
|
|
|
Type logEntryType = Type.GetType("UnityEditor.LogEntry,UnityEditor");
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
if (logEntriesType == null || logEntryType == null)
|
|
|
|
|
return new { error = "Could not find required Unity logging types", entries = new List<object>() };
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
// Get essential methods
|
|
|
|
|
MethodInfo getCountMethod = logEntriesType.GetMethod("GetCount", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
|
|
|
|
MethodInfo getEntryMethod = logEntriesType.GetMethod("GetEntryAt", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ??
|
|
|
|
|
logEntriesType.GetMethod("GetEntryInternal", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
if (getCountMethod == null || getEntryMethod == null)
|
|
|
|
|
return new { error = "Could not find required Unity logging methods", entries = new List<object>() };
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
// Get stack trace method if available
|
|
|
|
|
MethodInfo getStackTraceMethod = logEntriesType.GetMethod("GetEntryStackTrace", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
|
|
|
|
|
null, new[] { typeof(int) }, null) ?? logEntriesType.GetMethod("GetStackTrace", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
|
|
|
|
|
null, new[] { typeof(int) }, null);
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
// Get entry count and prepare result list
|
|
|
|
|
int count = (int)getCountMethod.Invoke(null, null);
|
|
|
|
|
var entries = new List<object>();
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
// Create LogEntry instance to populate
|
|
|
|
|
object logEntryInstance = Activator.CreateInstance(logEntryType);
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
// Find properties on LogEntry type
|
|
|
|
|
PropertyInfo modeProperty = logEntryType.GetProperty("mode") ?? logEntryType.GetProperty("Mode");
|
|
|
|
|
PropertyInfo messageProperty = logEntryType.GetProperty("message") ?? logEntryType.GetProperty("Message");
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
// Parse search terms if provided
|
|
|
|
|
string[] searchWords = !string.IsNullOrWhiteSpace(searchTerm) ?
|
|
|
|
|
searchTerm.ToLower().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) : null;
|
|
|
|
|
|
|
|
|
|
// Process each log entry
|
|
|
|
|
for (int i = 0; i < count; i++)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
try
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
// Get log entry at index i
|
|
|
|
|
var methodParams = getEntryMethod.GetParameters();
|
|
|
|
|
if (methodParams.Length == 2 && methodParams[1].ParameterType == logEntryType)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
getEntryMethod.Invoke(null, new object[] { i, logEntryInstance });
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
2025-03-20 19:24:31 +08:00
|
|
|
else if (methodParams.Length >= 1 && methodParams[0].ParameterType == typeof(int))
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
var parameters = new object[methodParams.Length];
|
|
|
|
|
parameters[0] = i;
|
|
|
|
|
for (int p = 1; p < parameters.Length; p++)
|
|
|
|
|
{
|
|
|
|
|
parameters[p] = methodParams[p].ParameterType.IsValueType ?
|
|
|
|
|
Activator.CreateInstance(methodParams[p].ParameterType) : null;
|
|
|
|
|
}
|
|
|
|
|
getEntryMethod.Invoke(null, parameters);
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
2025-03-20 19:24:31 +08:00
|
|
|
else continue;
|
|
|
|
|
|
|
|
|
|
// Extract log data
|
|
|
|
|
int logType = modeProperty != null ?
|
|
|
|
|
Convert.ToInt32(modeProperty.GetValue(logEntryInstance) ?? 0) : 0;
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
string message = messageProperty != null ?
|
|
|
|
|
(messageProperty.GetValue(logEntryInstance)?.ToString() ?? "") : "";
|
|
|
|
|
|
|
|
|
|
// If message is empty, try to get it via a field
|
2025-03-19 20:03:12 +08:00
|
|
|
if (string.IsNullOrEmpty(message))
|
|
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
var msgField = logEntryType.GetField("message", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
|
|
|
if (msgField != null)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
object msgValue = msgField.GetValue(logEntryInstance);
|
|
|
|
|
message = msgValue != null ? msgValue.ToString() : "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If still empty, try alternate approach with Console window
|
|
|
|
|
if (string.IsNullOrEmpty(message))
|
|
|
|
|
{
|
|
|
|
|
// Access ConsoleWindow and its data
|
|
|
|
|
Type consoleWindowType = Type.GetType("UnityEditor.ConsoleWindow,UnityEditor");
|
|
|
|
|
if (consoleWindowType != null)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
try
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
// Get Console window instance
|
|
|
|
|
var getWindowMethod = consoleWindowType.GetMethod("GetWindow",
|
|
|
|
|
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
|
|
|
|
|
null, new[] { typeof(bool) }, null) ??
|
|
|
|
|
consoleWindowType.GetMethod("GetConsoleWindow",
|
|
|
|
|
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
|
|
|
|
|
|
|
|
|
if (getWindowMethod != null)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
object consoleWindow = getWindowMethod.Invoke(null,
|
|
|
|
|
getWindowMethod.GetParameters().Length > 0 ? new object[] { false } : null);
|
|
|
|
|
|
|
|
|
|
if (consoleWindow != null)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
// Try to find log entries collection
|
|
|
|
|
foreach (var prop in consoleWindowType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
if (prop.PropertyType.IsArray ||
|
|
|
|
|
(prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(List<>)))
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
try
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
var logItems = prop.GetValue(consoleWindow);
|
|
|
|
|
if (logItems != null)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
if (logItems.GetType().IsArray && i < ((Array)logItems).Length)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
var entry = ((Array)logItems).GetValue(i);
|
|
|
|
|
if (entry != null)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
var entryType = entry.GetType();
|
|
|
|
|
var entryMessageProp = entryType.GetProperty("message") ??
|
|
|
|
|
entryType.GetProperty("Message");
|
|
|
|
|
if (entryMessageProp != null)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
object value = entryMessageProp.GetValue(entry);
|
|
|
|
|
if (value != null)
|
|
|
|
|
{
|
|
|
|
|
message = value.ToString();
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-20 19:24:31 +08:00
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// Ignore errors in this fallback approach
|
|
|
|
|
}
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-20 19:24:31 +08:00
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// Ignore errors in this fallback approach
|
|
|
|
|
}
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
// If still empty, try one more approach with log files
|
|
|
|
|
if (string.IsNullOrEmpty(message))
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
// This is our last resort - try to get log messages from the most recent Unity log file
|
|
|
|
|
try
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
string logPath = string.Empty;
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
// Determine the log file path based on the platform
|
|
|
|
|
if (Application.platform == RuntimePlatform.WindowsEditor)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
logPath = System.IO.Path.Combine(
|
|
|
|
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
|
|
|
|
"Unity", "Editor", "Editor.log");
|
|
|
|
|
}
|
|
|
|
|
else if (Application.platform == RuntimePlatform.OSXEditor)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
logPath = System.IO.Path.Combine(
|
|
|
|
|
Environment.GetFolderPath(Environment.SpecialFolder.Personal),
|
|
|
|
|
"Library", "Logs", "Unity", "Editor.log");
|
|
|
|
|
}
|
|
|
|
|
else if (Application.platform == RuntimePlatform.LinuxEditor)
|
|
|
|
|
{
|
|
|
|
|
logPath = System.IO.Path.Combine(
|
|
|
|
|
Environment.GetFolderPath(Environment.SpecialFolder.Personal),
|
|
|
|
|
".config", "unity3d", "logs", "Editor.log");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(logPath) && System.IO.File.Exists(logPath))
|
|
|
|
|
{
|
|
|
|
|
// Read last few lines from the log file
|
|
|
|
|
var logLines = ReadLastLines(logPath, 100);
|
|
|
|
|
if (logLines.Count > i)
|
|
|
|
|
{
|
|
|
|
|
message = logLines[logLines.Count - 1 - i];
|
|
|
|
|
}
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-03-20 19:24:31 +08:00
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// Ignore errors in this fallback approach
|
|
|
|
|
}
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
// Get stack trace if method available
|
|
|
|
|
string stackTrace = "";
|
|
|
|
|
if (getStackTraceMethod != null)
|
|
|
|
|
{
|
|
|
|
|
stackTrace = getStackTraceMethod.Invoke(null, new object[] { i })?.ToString() ?? "";
|
|
|
|
|
}
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
// Filter by type
|
|
|
|
|
bool typeMatch = (logType == 0 && showLogs) ||
|
|
|
|
|
(logType == 1 && showWarnings) ||
|
|
|
|
|
(logType == 2 && showErrors);
|
|
|
|
|
if (!typeMatch) continue;
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
// Filter by search term
|
|
|
|
|
bool searchMatch = true;
|
|
|
|
|
if (searchWords != null && searchWords.Length > 0)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
string lowerMessage = message.ToLower();
|
|
|
|
|
string lowerStackTrace = stackTrace.ToLower();
|
|
|
|
|
|
|
|
|
|
foreach (string word in searchWords)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
if (!lowerMessage.Contains(word) && !lowerStackTrace.Contains(word))
|
|
|
|
|
{
|
|
|
|
|
searchMatch = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-03-20 19:24:31 +08:00
|
|
|
if (!searchMatch) continue;
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
// Add matching entry to results
|
|
|
|
|
string typeStr = logType == 0 ? "Log" : logType == 1 ? "Warning" : "Error";
|
|
|
|
|
entries.Add(new
|
2025-03-20 05:12:58 +08:00
|
|
|
{
|
|
|
|
|
type = typeStr,
|
2025-03-20 19:24:31 +08:00
|
|
|
message,
|
|
|
|
|
stackTrace
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
catch (Exception)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
// Skip entries that cause errors
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
2025-03-20 19:24:31 +08:00
|
|
|
|
|
|
|
|
// Return filtered results
|
|
|
|
|
return new
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
message = "Console logs retrieved successfully",
|
|
|
|
|
entries,
|
|
|
|
|
total_entries = count,
|
|
|
|
|
filtered_count = entries.Count,
|
|
|
|
|
show_logs = showLogs,
|
|
|
|
|
show_warnings = showWarnings,
|
|
|
|
|
show_errors = showErrors
|
|
|
|
|
};
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
2025-03-20 19:24:31 +08:00
|
|
|
catch (Exception e)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
return new
|
|
|
|
|
{
|
|
|
|
|
error = $"Failed to read console logs: {e.Message}",
|
|
|
|
|
entries = new List<object>()
|
|
|
|
|
};
|
|
|
|
|
}
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
private static MethodInfo FindMethod(Type type, string[] methodNames)
|
2025-03-18 19:00:50 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
foreach (var methodName in methodNames)
|
|
|
|
|
{
|
|
|
|
|
var method = type.GetMethod(methodName,
|
|
|
|
|
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
|
|
|
|
if (method != null)
|
|
|
|
|
return method;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
2025-03-18 19:00:50 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
private static BuildTarget GetBuildTarget(string platform)
|
2025-03-18 19:00:50 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
BuildTarget target;
|
|
|
|
|
switch (platform.ToLower())
|
2025-03-18 19:00:50 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
case "windows": target = BuildTarget.StandaloneWindows64; break;
|
|
|
|
|
case "mac": target = BuildTarget.StandaloneOSX; break;
|
|
|
|
|
case "linux": target = BuildTarget.StandaloneLinux64; break;
|
|
|
|
|
case "android": target = BuildTarget.Android; break;
|
|
|
|
|
case "ios": target = BuildTarget.iOS; break;
|
|
|
|
|
case "webgl": target = BuildTarget.WebGL; break;
|
|
|
|
|
default: target = (BuildTarget)(-1); break; // Invalid target
|
2025-03-18 19:00:50 +08:00
|
|
|
}
|
2025-03-20 19:24:31 +08:00
|
|
|
return target;
|
2025-03-18 19:00:50 +08:00
|
|
|
}
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
private static string[] GetEnabledScenes()
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
var scenes = new List<string>();
|
|
|
|
|
for (int i = 0; i < EditorBuildSettings.scenes.Length; i++)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
if (EditorBuildSettings.scenes[i].enabled)
|
|
|
|
|
{
|
|
|
|
|
scenes.Add(EditorBuildSettings.scenes[i].path);
|
|
|
|
|
}
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
2025-03-20 19:24:31 +08:00
|
|
|
return scenes.ToArray();
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Helper method to get information about available properties and fields in a type
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static Dictionary<string, object> GetTypeInfo(Type type)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
var result = new Dictionary<string, object>();
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
// Get all public and non-public properties
|
|
|
|
|
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic |
|
|
|
|
|
BindingFlags.Static | BindingFlags.Instance);
|
|
|
|
|
var propList = new List<string>();
|
|
|
|
|
foreach (var prop in properties)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
propList.Add($"{prop.PropertyType.Name} {prop.Name}");
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
2025-03-20 19:24:31 +08:00
|
|
|
result["Properties"] = propList;
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
// Get all public and non-public fields
|
|
|
|
|
var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic |
|
|
|
|
|
BindingFlags.Static | BindingFlags.Instance);
|
|
|
|
|
var fieldList = new List<string>();
|
|
|
|
|
foreach (var field in fields)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
fieldList.Add($"{field.FieldType.Name} {field.Name}");
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
2025-03-20 19:24:31 +08:00
|
|
|
result["Fields"] = fieldList;
|
|
|
|
|
|
|
|
|
|
// Get all public and non-public methods
|
|
|
|
|
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic |
|
|
|
|
|
BindingFlags.Static | BindingFlags.Instance);
|
|
|
|
|
var methodList = new List<string>();
|
|
|
|
|
foreach (var method in methods)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
if (!method.Name.StartsWith("get_") && !method.Name.StartsWith("set_"))
|
|
|
|
|
{
|
|
|
|
|
var parameters = string.Join(", ", method.GetParameters()
|
|
|
|
|
.Select(p => $"{p.ParameterType.Name} {p.Name}"));
|
|
|
|
|
methodList.Add($"{method.ReturnType.Name} {method.Name}({parameters})");
|
|
|
|
|
}
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
2025-03-20 19:24:31 +08:00
|
|
|
result["Methods"] = methodList;
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
return result;
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Helper method to get all property and field values from an object
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static Dictionary<string, string> GetObjectValues(object obj)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
if (obj == null) return new Dictionary<string, string>();
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
var result = new Dictionary<string, string>();
|
|
|
|
|
var type = obj.GetType();
|
|
|
|
|
|
|
|
|
|
// Get all property values
|
|
|
|
|
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
|
|
|
foreach (var prop in properties)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
try
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
var value = prop.GetValue(obj);
|
|
|
|
|
result[$"Property:{prop.Name}"] = value?.ToString() ?? "null";
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
2025-03-20 19:24:31 +08:00
|
|
|
catch (Exception)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
result[$"Property:{prop.Name}"] = "ERROR";
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
// Get all field values
|
|
|
|
|
var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
|
|
|
foreach (var field in fields)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
try
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
var value = field.GetValue(obj);
|
|
|
|
|
result[$"Field:{field.Name}"] = value?.ToString() ?? "null";
|
|
|
|
|
}
|
|
|
|
|
catch (Exception)
|
|
|
|
|
{
|
|
|
|
|
result[$"Field:{field.Name}"] = "ERROR";
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
return result;
|
|
|
|
|
}
|
2025-03-20 05:12:58 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Reads the last N lines from a file
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static List<string> ReadLastLines(string filePath, int lineCount)
|
2025-03-20 05:12:58 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
var result = new List<string>();
|
2025-03-20 05:12:58 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
using (var stream = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite))
|
|
|
|
|
using (var reader = new System.IO.StreamReader(stream))
|
2025-03-20 05:12:58 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
string line;
|
|
|
|
|
var circularBuffer = new List<string>(lineCount);
|
|
|
|
|
int currentIndex = 0;
|
|
|
|
|
|
|
|
|
|
// Read all lines keeping only the last N in a circular buffer
|
|
|
|
|
while ((line = reader.ReadLine()) != null)
|
|
|
|
|
{
|
|
|
|
|
if (circularBuffer.Count < lineCount)
|
|
|
|
|
{
|
|
|
|
|
circularBuffer.Add(line);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
circularBuffer[currentIndex] = line;
|
|
|
|
|
currentIndex = (currentIndex + 1) % lineCount;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reorder the circular buffer so that lines are returned in order
|
|
|
|
|
if (circularBuffer.Count == lineCount)
|
2025-03-19 20:03:12 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
for (int i = 0; i < lineCount; i++)
|
|
|
|
|
{
|
|
|
|
|
result.Add(circularBuffer[(currentIndex + i) % lineCount]);
|
|
|
|
|
}
|
2025-03-19 20:03:12 +08:00
|
|
|
}
|
|
|
|
|
else
|
2025-03-20 05:12:58 +08:00
|
|
|
{
|
2025-03-20 19:24:31 +08:00
|
|
|
result.AddRange(circularBuffer);
|
2025-03-20 05:12:58 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-03-19 20:03:12 +08:00
|
|
|
|
2025-03-20 19:24:31 +08:00
|
|
|
return result;
|
2025-03-20 05:12:58 +08:00
|
|
|
}
|
2025-03-21 03:51:26 +08:00
|
|
|
|
|
|
|
|
/// <summary>
|
2025-03-21 18:30:41 +08:00
|
|
|
/// Gets a comprehensive list of available Unity commands, including editor menu items,
|
|
|
|
|
/// internal commands, utility methods, and other actionable operations that can be executed.
|
2025-03-21 03:51:26 +08:00
|
|
|
/// </summary>
|
2025-03-21 18:30:41 +08:00
|
|
|
/// <returns>Object containing categorized lists of available command paths</returns>
|
2025-03-21 03:51:26 +08:00
|
|
|
private static object GetAvailableCommands()
|
2025-03-20 05:12:58 +08:00
|
|
|
{
|
2025-03-21 18:30:41 +08:00
|
|
|
var menuCommands = new HashSet<string>();
|
|
|
|
|
var utilityCommands = new HashSet<string>();
|
|
|
|
|
var assetCommands = new HashSet<string>();
|
|
|
|
|
var sceneCommands = new HashSet<string>();
|
|
|
|
|
var gameObjectCommands = new HashSet<string>();
|
|
|
|
|
var prefabCommands = new HashSet<string>();
|
|
|
|
|
var shortcutCommands = new HashSet<string>();
|
|
|
|
|
var otherCommands = new HashSet<string>();
|
|
|
|
|
|
|
|
|
|
// Add a simple command that we know will work for testing
|
|
|
|
|
menuCommands.Add("Window/Unity MCP");
|
|
|
|
|
|
|
|
|
|
Debug.Log("Starting command collection...");
|
|
|
|
|
|
|
|
|
|
try
|
2025-03-21 03:51:26 +08:00
|
|
|
{
|
2025-03-21 18:30:41 +08:00
|
|
|
// Add all EditorApplication static methods - these are guaranteed to work
|
|
|
|
|
Debug.Log("Adding EditorApplication methods...");
|
|
|
|
|
foreach (MethodInfo method in typeof(EditorApplication).GetMethods(BindingFlags.Public | BindingFlags.Static))
|
|
|
|
|
{
|
|
|
|
|
utilityCommands.Add($"EditorApplication.{method.Name}");
|
|
|
|
|
}
|
|
|
|
|
Debug.Log($"Added {utilityCommands.Count} EditorApplication methods");
|
|
|
|
|
|
|
|
|
|
// Add built-in menu commands directly - these are common ones that should always be available
|
|
|
|
|
Debug.Log("Adding built-in menu commands...");
|
|
|
|
|
string[] builtInMenus = new[] {
|
|
|
|
|
"File/New Scene",
|
|
|
|
|
"File/Open Scene",
|
|
|
|
|
"File/Save",
|
|
|
|
|
"File/Save As...",
|
|
|
|
|
"Edit/Undo",
|
|
|
|
|
"Edit/Redo",
|
|
|
|
|
"Edit/Cut",
|
|
|
|
|
"Edit/Copy",
|
|
|
|
|
"Edit/Paste",
|
|
|
|
|
"Edit/Duplicate",
|
|
|
|
|
"Edit/Delete",
|
|
|
|
|
"GameObject/Create Empty",
|
|
|
|
|
"GameObject/3D Object/Cube",
|
|
|
|
|
"GameObject/3D Object/Sphere",
|
|
|
|
|
"GameObject/3D Object/Capsule",
|
|
|
|
|
"GameObject/3D Object/Cylinder",
|
|
|
|
|
"GameObject/3D Object/Plane",
|
|
|
|
|
"GameObject/Light/Directional Light",
|
|
|
|
|
"GameObject/Light/Point Light",
|
|
|
|
|
"GameObject/Light/Spotlight",
|
|
|
|
|
"GameObject/Light/Area Light",
|
|
|
|
|
"Component/Mesh/Mesh Filter",
|
|
|
|
|
"Component/Mesh/Mesh Renderer",
|
|
|
|
|
"Component/Physics/Rigidbody",
|
|
|
|
|
"Component/Physics/Box Collider",
|
|
|
|
|
"Component/Physics/Sphere Collider",
|
|
|
|
|
"Component/Physics/Capsule Collider",
|
|
|
|
|
"Component/Audio/Audio Source",
|
|
|
|
|
"Component/Audio/Audio Listener",
|
|
|
|
|
"Window/General/Scene",
|
|
|
|
|
"Window/General/Game",
|
|
|
|
|
"Window/General/Inspector",
|
|
|
|
|
"Window/General/Hierarchy",
|
|
|
|
|
"Window/General/Project",
|
|
|
|
|
"Window/General/Console",
|
|
|
|
|
"Window/Analysis/Profiler",
|
|
|
|
|
"Window/Package Manager",
|
|
|
|
|
"Assets/Create/Material",
|
|
|
|
|
"Assets/Create/C# Script",
|
|
|
|
|
"Assets/Create/Prefab",
|
|
|
|
|
"Assets/Create/Scene",
|
|
|
|
|
"Assets/Create/Folder",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
foreach (string menuItem in builtInMenus)
|
|
|
|
|
{
|
|
|
|
|
menuCommands.Add(menuItem);
|
|
|
|
|
}
|
|
|
|
|
Debug.Log($"Added {builtInMenus.Length} built-in menu commands");
|
|
|
|
|
|
|
|
|
|
// Get menu commands from MenuItem attributes - wrapped in separate try block
|
|
|
|
|
Debug.Log("Searching for MenuItem attributes...");
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
int itemCount = 0;
|
|
|
|
|
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
|
|
|
|
|
{
|
|
|
|
|
if (assembly.IsDynamic) continue;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
foreach (Type type in assembly.GetExportedTypes())
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
foreach (MethodInfo method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
object[] attributes = method.GetCustomAttributes(typeof(UnityEditor.MenuItem), false);
|
|
|
|
|
if (attributes != null && attributes.Length > 0)
|
|
|
|
|
{
|
|
|
|
|
foreach (var attr in attributes)
|
|
|
|
|
{
|
|
|
|
|
var menuItem = attr as UnityEditor.MenuItem;
|
|
|
|
|
if (menuItem != null && !string.IsNullOrEmpty(menuItem.menuItem))
|
|
|
|
|
{
|
|
|
|
|
menuCommands.Add(menuItem.menuItem);
|
|
|
|
|
itemCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception methodEx)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"Error getting menu items for method {method.Name}: {methodEx.Message}");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception typeEx)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"Error processing type: {typeEx.Message}");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception assemblyEx)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"Error examining assembly {assembly.GetName().Name}: {assemblyEx.Message}");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Debug.Log($"Found {itemCount} menu items from attributes");
|
|
|
|
|
}
|
|
|
|
|
catch (Exception menuItemEx)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError($"Failed to get menu items: {menuItemEx.Message}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add EditorUtility methods as commands
|
|
|
|
|
Debug.Log("Adding EditorUtility methods...");
|
|
|
|
|
foreach (MethodInfo method in typeof(EditorUtility).GetMethods(BindingFlags.Public | BindingFlags.Static))
|
|
|
|
|
{
|
|
|
|
|
utilityCommands.Add($"EditorUtility.{method.Name}");
|
|
|
|
|
}
|
|
|
|
|
Debug.Log($"Added {typeof(EditorUtility).GetMethods(BindingFlags.Public | BindingFlags.Static).Length} EditorUtility methods");
|
|
|
|
|
|
|
|
|
|
// Add AssetDatabase methods as commands
|
|
|
|
|
Debug.Log("Adding AssetDatabase methods...");
|
|
|
|
|
foreach (MethodInfo method in typeof(AssetDatabase).GetMethods(BindingFlags.Public | BindingFlags.Static))
|
|
|
|
|
{
|
|
|
|
|
assetCommands.Add($"AssetDatabase.{method.Name}");
|
|
|
|
|
}
|
|
|
|
|
Debug.Log($"Added {typeof(AssetDatabase).GetMethods(BindingFlags.Public | BindingFlags.Static).Length} AssetDatabase methods");
|
|
|
|
|
|
|
|
|
|
// Add EditorSceneManager methods as commands
|
|
|
|
|
Debug.Log("Adding EditorSceneManager methods...");
|
|
|
|
|
Type sceneManagerType = typeof(UnityEditor.SceneManagement.EditorSceneManager);
|
|
|
|
|
if (sceneManagerType != null)
|
|
|
|
|
{
|
|
|
|
|
foreach (MethodInfo method in sceneManagerType.GetMethods(BindingFlags.Public | BindingFlags.Static))
|
|
|
|
|
{
|
|
|
|
|
sceneCommands.Add($"EditorSceneManager.{method.Name}");
|
|
|
|
|
}
|
|
|
|
|
Debug.Log($"Added {sceneManagerType.GetMethods(BindingFlags.Public | BindingFlags.Static).Length} EditorSceneManager methods");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add GameObject manipulation commands
|
|
|
|
|
Debug.Log("Adding GameObject methods...");
|
|
|
|
|
foreach (MethodInfo method in typeof(GameObject).GetMethods(BindingFlags.Public | BindingFlags.Static))
|
|
|
|
|
{
|
|
|
|
|
gameObjectCommands.Add($"GameObject.{method.Name}");
|
|
|
|
|
}
|
|
|
|
|
Debug.Log($"Added {typeof(GameObject).GetMethods(BindingFlags.Public | BindingFlags.Static).Length} GameObject methods");
|
|
|
|
|
|
|
|
|
|
// Add Selection-related commands
|
|
|
|
|
Debug.Log("Adding Selection methods...");
|
|
|
|
|
foreach (MethodInfo method in typeof(Selection).GetMethods(BindingFlags.Public | BindingFlags.Static))
|
|
|
|
|
{
|
|
|
|
|
gameObjectCommands.Add($"Selection.{method.Name}");
|
|
|
|
|
}
|
|
|
|
|
Debug.Log($"Added {typeof(Selection).GetMethods(BindingFlags.Public | BindingFlags.Static).Length} Selection methods");
|
|
|
|
|
|
|
|
|
|
// Add PrefabUtility methods as commands
|
|
|
|
|
Debug.Log("Adding PrefabUtility methods...");
|
|
|
|
|
Type prefabUtilityType = typeof(UnityEditor.PrefabUtility);
|
|
|
|
|
if (prefabUtilityType != null)
|
|
|
|
|
{
|
|
|
|
|
foreach (MethodInfo method in prefabUtilityType.GetMethods(BindingFlags.Public | BindingFlags.Static))
|
|
|
|
|
{
|
|
|
|
|
prefabCommands.Add($"PrefabUtility.{method.Name}");
|
|
|
|
|
}
|
|
|
|
|
Debug.Log($"Added {prefabUtilityType.GetMethods(BindingFlags.Public | BindingFlags.Static).Length} PrefabUtility methods");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add Undo related methods
|
|
|
|
|
Debug.Log("Adding Undo methods...");
|
|
|
|
|
foreach (MethodInfo method in typeof(Undo).GetMethods(BindingFlags.Public | BindingFlags.Static))
|
2025-03-21 03:51:26 +08:00
|
|
|
{
|
2025-03-21 18:30:41 +08:00
|
|
|
utilityCommands.Add($"Undo.{method.Name}");
|
|
|
|
|
}
|
|
|
|
|
Debug.Log($"Added {typeof(Undo).GetMethods(BindingFlags.Public | BindingFlags.Static).Length} Undo methods");
|
|
|
|
|
|
|
|
|
|
// The rest of the command gathering can be attempted but might not be critical
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// Get commands from Unity's internal command system
|
|
|
|
|
Debug.Log("Trying to get internal CommandService commands...");
|
|
|
|
|
Type commandServiceType = typeof(UnityEditor.EditorWindow).Assembly.GetType("UnityEditor.CommandService");
|
|
|
|
|
if (commandServiceType != null)
|
2025-03-21 03:51:26 +08:00
|
|
|
{
|
2025-03-21 18:30:41 +08:00
|
|
|
Debug.Log("Found CommandService type");
|
|
|
|
|
PropertyInfo instanceProperty = commandServiceType.GetProperty("Instance",
|
|
|
|
|
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
|
|
|
|
|
|
|
|
|
if (instanceProperty != null)
|
|
|
|
|
{
|
|
|
|
|
Debug.Log("Found Instance property");
|
|
|
|
|
object commandService = instanceProperty.GetValue(null);
|
|
|
|
|
if (commandService != null)
|
|
|
|
|
{
|
|
|
|
|
Debug.Log("Got CommandService instance");
|
|
|
|
|
MethodInfo findAllCommandsMethod = commandServiceType.GetMethod("FindAllCommands",
|
|
|
|
|
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
|
|
|
|
|
|
|
|
|
if (findAllCommandsMethod != null)
|
|
|
|
|
{
|
|
|
|
|
Debug.Log("Found FindAllCommands method");
|
|
|
|
|
var commandsResult = findAllCommandsMethod.Invoke(commandService, null);
|
|
|
|
|
if (commandsResult != null)
|
|
|
|
|
{
|
|
|
|
|
Debug.Log("Got commands result");
|
|
|
|
|
var commandsList = commandsResult as System.Collections.IEnumerable;
|
|
|
|
|
if (commandsList != null)
|
|
|
|
|
{
|
|
|
|
|
int commandCount = 0;
|
|
|
|
|
foreach (var cmd in commandsList)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
PropertyInfo nameProperty = cmd.GetType().GetProperty("name") ??
|
|
|
|
|
cmd.GetType().GetProperty("path") ??
|
|
|
|
|
cmd.GetType().GetProperty("commandName");
|
|
|
|
|
if (nameProperty != null)
|
|
|
|
|
{
|
|
|
|
|
string commandName = nameProperty.GetValue(cmd)?.ToString();
|
|
|
|
|
if (!string.IsNullOrEmpty(commandName))
|
|
|
|
|
{
|
|
|
|
|
otherCommands.Add(commandName);
|
|
|
|
|
commandCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception cmdEx)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"Error processing command: {cmdEx.Message}");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Debug.Log($"Added {commandCount} internal commands");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning("FindAllCommands returned null");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning("FindAllCommands method not found");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning("CommandService instance is null");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning("Instance property not found on CommandService");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning("CommandService type not found");
|
2025-03-21 03:51:26 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-03-21 18:30:41 +08:00
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogWarning($"Failed to get internal Unity commands: {e.Message}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Other additional command sources can be tried
|
|
|
|
|
// ... other commands ...
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError($"Error getting Unity commands: {e.Message}\n{e.StackTrace}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create command categories dictionary for the result
|
|
|
|
|
var commandCategories = new Dictionary<string, List<string>>
|
|
|
|
|
{
|
|
|
|
|
{ "MenuCommands", menuCommands.OrderBy(x => x).ToList() },
|
|
|
|
|
{ "UtilityCommands", utilityCommands.OrderBy(x => x).ToList() },
|
|
|
|
|
{ "AssetCommands", assetCommands.OrderBy(x => x).ToList() },
|
|
|
|
|
{ "SceneCommands", sceneCommands.OrderBy(x => x).ToList() },
|
|
|
|
|
{ "GameObjectCommands", gameObjectCommands.OrderBy(x => x).ToList() },
|
|
|
|
|
{ "PrefabCommands", prefabCommands.OrderBy(x => x).ToList() },
|
|
|
|
|
{ "ShortcutCommands", shortcutCommands.OrderBy(x => x).ToList() },
|
|
|
|
|
{ "OtherCommands", otherCommands.OrderBy(x => x).ToList() }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Calculate total command count
|
|
|
|
|
int totalCount = commandCategories.Values.Sum(list => list.Count);
|
|
|
|
|
|
|
|
|
|
Debug.Log($"Command retrieval complete. Found {totalCount} total commands.");
|
|
|
|
|
|
|
|
|
|
// Create a simplified response with just the essential data
|
|
|
|
|
// The complex object structure might be causing serialization issues
|
|
|
|
|
var allCommandsList = commandCategories.Values.SelectMany(x => x).OrderBy(x => x).ToList();
|
|
|
|
|
|
|
|
|
|
// Use simple string array instead of JArray for better serialization
|
|
|
|
|
string[] commandsArray = allCommandsList.ToArray();
|
|
|
|
|
|
|
|
|
|
// Log the array size for verification
|
|
|
|
|
Debug.Log($"Final commands array contains {commandsArray.Length} items");
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// Return a simple object with just the commands array and count
|
|
|
|
|
var result = new
|
|
|
|
|
{
|
|
|
|
|
commands = commandsArray,
|
|
|
|
|
count = commandsArray.Length
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Verify the result can be serialized properly
|
|
|
|
|
var jsonTest = JsonUtility.ToJson(new { test = "This is a test" });
|
|
|
|
|
Debug.Log($"JSON serialization test successful: {jsonTest}");
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError($"Error creating response: {ex.Message}");
|
|
|
|
|
|
|
|
|
|
// Ultimate fallback - don't use any JObject/JArray
|
|
|
|
|
return new
|
|
|
|
|
{
|
|
|
|
|
message = $"Found {commandsArray.Length} commands",
|
|
|
|
|
firstTen = commandsArray.Take(10).ToArray(),
|
|
|
|
|
count = commandsArray.Length
|
|
|
|
|
};
|
2025-03-21 03:51:26 +08:00
|
|
|
}
|
2025-03-20 05:12:58 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-03-18 19:00:50 +08:00
|
|
|
}
|