Add ExecuteContextMenuItem functionality to ObjectCommandHandler and Python tool
- Implemented ExecuteContextMenuItem method in ObjectCommandHandler.cs to execute context menu methods on game object components. - Added corresponding execute_context_menu_item tool in object_tools.py to facilitate the execution of context menu items from Python. - Enhanced error handling and logging for better debugging and user feedback.main
parent
f334b0a6db
commit
8102460c7c
|
|
@ -7,6 +7,7 @@ using UnityEditor;
|
||||||
using UnityEngine.SceneManagement;
|
using UnityEngine.SceneManagement;
|
||||||
using UnityEditor.SceneManagement;
|
using UnityEditor.SceneManagement;
|
||||||
using UnityMCP.Editor.Helpers;
|
using UnityMCP.Editor.Helpers;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace UnityMCP.Editor.Commands
|
namespace UnityMCP.Editor.Commands
|
||||||
{
|
{
|
||||||
|
|
@ -398,5 +399,107 @@ namespace UnityMCP.Editor.Commands
|
||||||
light.shadows = LightShadows.Soft;
|
light.shadows = LightShadows.Soft;
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes a context menu method on a component of a game object
|
||||||
|
/// </summary>
|
||||||
|
public static object ExecuteContextMenuItem(JObject @params)
|
||||||
|
{
|
||||||
|
string objectName = (string)@params["object_name"] ?? throw new Exception("Parameter 'object_name' is required.");
|
||||||
|
string componentName = (string)@params["component"] ?? throw new Exception("Parameter 'component' is required.");
|
||||||
|
string contextMenuItemName = (string)@params["context_menu_item"] ?? throw new Exception("Parameter 'context_menu_item' is required.");
|
||||||
|
|
||||||
|
// Find the game object
|
||||||
|
var obj = GameObject.Find(objectName) ?? throw new Exception($"Object '{objectName}' not found.");
|
||||||
|
|
||||||
|
// Find the component type
|
||||||
|
Type componentType = FindTypeInLoadedAssemblies(componentName) ??
|
||||||
|
throw new Exception($"Component type '{componentName}' not found.");
|
||||||
|
|
||||||
|
// Get the component from the game object
|
||||||
|
var component = obj.GetComponent(componentType) ??
|
||||||
|
throw new Exception($"Component '{componentName}' not found on object '{objectName}'.");
|
||||||
|
|
||||||
|
// Find methods with ContextMenu attribute matching the context menu item name
|
||||||
|
var methods = componentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
||||||
|
.Where(m => m.GetCustomAttributes(typeof(ContextMenuItemAttribute), true).Any() ||
|
||||||
|
m.GetCustomAttributes(typeof(ContextMenu), true)
|
||||||
|
.Cast<ContextMenu>()
|
||||||
|
.Any(attr => attr.menuItem == contextMenuItemName))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// If no methods with ContextMenuItemAttribute are found, look for methods with name matching the context menu item
|
||||||
|
if (methods.Count == 0)
|
||||||
|
{
|
||||||
|
methods = componentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
||||||
|
.Where(m => m.Name == contextMenuItemName)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methods.Count == 0)
|
||||||
|
throw new Exception($"No context menu method '{contextMenuItemName}' found on component '{componentName}'.");
|
||||||
|
|
||||||
|
// If multiple methods match, use the first one and log a warning
|
||||||
|
if (methods.Count > 1)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"Found multiple methods for context menu item '{contextMenuItemName}' on component '{componentName}'. Using the first one.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var method = methods[0];
|
||||||
|
|
||||||
|
// Execute the method
|
||||||
|
try
|
||||||
|
{
|
||||||
|
method.Invoke(component, null);
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
success = true,
|
||||||
|
message = $"Successfully executed context menu item '{contextMenuItemName}' on component '{componentName}' of object '{objectName}'."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Error executing context menu item: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this helper method to find types across all loaded assemblies
|
||||||
|
private static Type FindTypeInLoadedAssemblies(string typeName)
|
||||||
|
{
|
||||||
|
// First try standard approach
|
||||||
|
Type type = Type.GetType(typeName);
|
||||||
|
if (type != null)
|
||||||
|
return type;
|
||||||
|
|
||||||
|
type = Type.GetType($"UnityEngine.{typeName}");
|
||||||
|
if (type != null)
|
||||||
|
return type;
|
||||||
|
|
||||||
|
// Then search all loaded assemblies
|
||||||
|
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||||
|
{
|
||||||
|
// Try with the simple name
|
||||||
|
type = assembly.GetType(typeName);
|
||||||
|
if (type != null)
|
||||||
|
return type;
|
||||||
|
|
||||||
|
// Try with the fully qualified name (assembly.GetTypes() can be expensive, so we do this last)
|
||||||
|
var types = assembly.GetTypes().Where(t => t.Name == typeName).ToArray();
|
||||||
|
|
||||||
|
if (types.Length > 0)
|
||||||
|
{
|
||||||
|
// If we found multiple types with the same name, log a warning
|
||||||
|
if (types.Length > 1)
|
||||||
|
{
|
||||||
|
Debug.LogWarning(
|
||||||
|
$"Found multiple types named '{typeName}'. Using the first one: {types[0].FullName}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return types[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -286,6 +286,7 @@ namespace UnityMCP.Editor
|
||||||
"CREATE_OBJECT" => ObjectCommandHandler.CreateObject(command.@params),
|
"CREATE_OBJECT" => ObjectCommandHandler.CreateObject(command.@params),
|
||||||
"MODIFY_OBJECT" => ObjectCommandHandler.ModifyObject(command.@params),
|
"MODIFY_OBJECT" => ObjectCommandHandler.ModifyObject(command.@params),
|
||||||
"DELETE_OBJECT" => ObjectCommandHandler.DeleteObject(command.@params),
|
"DELETE_OBJECT" => ObjectCommandHandler.DeleteObject(command.@params),
|
||||||
|
"EXECUTE_CONTEXT_MENU_ITEM" => ObjectCommandHandler.ExecuteContextMenuItem(command.@params),
|
||||||
"GET_OBJECT_PROPERTIES" => ObjectCommandHandler.GetObjectProperties(command.@params),
|
"GET_OBJECT_PROPERTIES" => ObjectCommandHandler.GetObjectProperties(command.@params),
|
||||||
"GET_COMPONENT_PROPERTIES" => ObjectCommandHandler.GetComponentProperties(command.@params),
|
"GET_COMPONENT_PROPERTIES" => ObjectCommandHandler.GetComponentProperties(command.@params),
|
||||||
"FIND_OBJECTS_BY_NAME" => ObjectCommandHandler.FindObjectsByName(command.@params),
|
"FIND_OBJECTS_BY_NAME" => ObjectCommandHandler.FindObjectsByName(command.@params),
|
||||||
|
|
|
||||||
|
|
@ -194,4 +194,57 @@ def register_object_tools(mcp: FastMCP):
|
||||||
})
|
})
|
||||||
return response.get("assets", [])
|
return response.get("assets", [])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return [{"error": f"Failed to get asset list: {str(e)}"}]
|
return [{"error": f"Failed to get asset list: {str(e)}"}]
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def execute_context_menu_item(
|
||||||
|
ctx: Context,
|
||||||
|
object_name: str,
|
||||||
|
component: str,
|
||||||
|
context_menu_item: str
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""Execute a specific [ContextMenu] method on a component of a given game object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ctx: The MCP context
|
||||||
|
object_name: Name of the game object to call
|
||||||
|
component: Name of the component type
|
||||||
|
context_menu_item: Name of the context menu item to execute
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict containing the result of the operation
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
unity = get_unity_connection()
|
||||||
|
|
||||||
|
# Check if the object exists
|
||||||
|
found_objects = unity.send_command("FIND_OBJECTS_BY_NAME", {
|
||||||
|
"name": object_name
|
||||||
|
}).get("objects", [])
|
||||||
|
|
||||||
|
if not found_objects:
|
||||||
|
return {"error": f"Object with name '{object_name}' not found in the scene."}
|
||||||
|
|
||||||
|
# Check if the component exists on the object
|
||||||
|
object_props = unity.send_command("GET_OBJECT_PROPERTIES", {
|
||||||
|
"name": object_name
|
||||||
|
})
|
||||||
|
|
||||||
|
if "error" in object_props:
|
||||||
|
return {"error": f"Failed to get object properties: {object_props['error']}"}
|
||||||
|
|
||||||
|
components = object_props.get("components", [])
|
||||||
|
component_exists = any(comp.get("type") == component for comp in components)
|
||||||
|
|
||||||
|
if not component_exists:
|
||||||
|
return {"error": f"Component '{component}' is not attached to object '{object_name}'."}
|
||||||
|
|
||||||
|
# Now execute the context menu item
|
||||||
|
response = unity.send_command("EXECUTE_CONTEXT_MENU_ITEM", {
|
||||||
|
"object_name": object_name,
|
||||||
|
"component": component,
|
||||||
|
"context_menu_item": context_menu_item
|
||||||
|
})
|
||||||
|
return response
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": f"Failed to execute context menu item: {str(e)}"}
|
||||||
Loading…
Reference in New Issue