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 UnityEditor.SceneManagement;
|
||||
using UnityMCP.Editor.Helpers;
|
||||
using System.Reflection;
|
||||
|
||||
namespace UnityMCP.Editor.Commands
|
||||
{
|
||||
|
|
@ -398,5 +399,107 @@ namespace UnityMCP.Editor.Commands
|
|||
light.shadows = LightShadows.Soft;
|
||||
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),
|
||||
"MODIFY_OBJECT" => ObjectCommandHandler.ModifyObject(command.@params),
|
||||
"DELETE_OBJECT" => ObjectCommandHandler.DeleteObject(command.@params),
|
||||
"EXECUTE_CONTEXT_MENU_ITEM" => ObjectCommandHandler.ExecuteContextMenuItem(command.@params),
|
||||
"GET_OBJECT_PROPERTIES" => ObjectCommandHandler.GetObjectProperties(command.@params),
|
||||
"GET_COMPONENT_PROPERTIES" => ObjectCommandHandler.GetComponentProperties(command.@params),
|
||||
"FIND_OBJECTS_BY_NAME" => ObjectCommandHandler.FindObjectsByName(command.@params),
|
||||
|
|
|
|||
|
|
@ -194,4 +194,57 @@ def register_object_tools(mcp: FastMCP):
|
|||
})
|
||||
return response.get("assets", [])
|
||||
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