升级XR插件版本
This commit is contained in:
351
Packages/MCPForUnity/Editor/Tools/ManageComponents.cs
Normal file
351
Packages/MCPForUnity/Editor/Tools/ManageComponents.cs
Normal file
@@ -0,0 +1,351 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MCPForUnity.Editor.Tools
|
||||
{
|
||||
/// <summary>
|
||||
/// Tool for managing components on GameObjects.
|
||||
/// Actions: add, remove, set_property
|
||||
///
|
||||
/// This is a focused tool for component lifecycle operations.
|
||||
/// For reading component data, use the unity://scene/gameobject/{id}/components resource.
|
||||
/// </summary>
|
||||
[McpForUnityTool("manage_components")]
|
||||
public static class ManageComponents
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles the manage_components command.
|
||||
/// </summary>
|
||||
/// <param name="params">Command parameters</param>
|
||||
/// <returns>Result of the component operation</returns>
|
||||
public static object HandleCommand(JObject @params)
|
||||
{
|
||||
if (@params == null)
|
||||
{
|
||||
return new ErrorResponse("Parameters cannot be null.");
|
||||
}
|
||||
|
||||
string action = ParamCoercion.CoerceString(@params["action"], null)?.ToLowerInvariant();
|
||||
if (string.IsNullOrEmpty(action))
|
||||
{
|
||||
return new ErrorResponse("'action' parameter is required (add, remove, set_property).");
|
||||
}
|
||||
|
||||
// Target resolution
|
||||
JToken targetToken = @params["target"];
|
||||
string searchMethod = ParamCoercion.CoerceString(@params["searchMethod"] ?? @params["search_method"], null);
|
||||
|
||||
if (targetToken == null)
|
||||
{
|
||||
return new ErrorResponse("'target' parameter is required.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return action switch
|
||||
{
|
||||
"add" => AddComponent(@params, targetToken, searchMethod),
|
||||
"remove" => RemoveComponent(@params, targetToken, searchMethod),
|
||||
"set_property" => SetProperty(@params, targetToken, searchMethod),
|
||||
_ => new ErrorResponse($"Unknown action: '{action}'. Supported actions: add, remove, set_property")
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
McpLog.Error($"[ManageComponents] Action '{action}' failed: {e}");
|
||||
return new ErrorResponse($"Internal error processing action '{action}': {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#region Action Implementations
|
||||
|
||||
private static object AddComponent(JObject @params, JToken targetToken, string searchMethod)
|
||||
{
|
||||
GameObject targetGo = FindTarget(targetToken, searchMethod);
|
||||
if (targetGo == null)
|
||||
{
|
||||
return new ErrorResponse($"Target GameObject ('{targetToken}') not found using method '{searchMethod ?? "default"}'.");
|
||||
}
|
||||
|
||||
string componentTypeName = ParamCoercion.CoerceString(@params["componentType"] ?? @params["component_type"], null);
|
||||
if (string.IsNullOrEmpty(componentTypeName))
|
||||
{
|
||||
return new ErrorResponse("'componentType' parameter is required for 'add' action.");
|
||||
}
|
||||
|
||||
// Resolve component type using unified type resolver
|
||||
Type type = UnityTypeResolver.ResolveComponent(componentTypeName);
|
||||
if (type == null)
|
||||
{
|
||||
return new ErrorResponse($"Component type '{componentTypeName}' not found. Use a fully-qualified name if needed.");
|
||||
}
|
||||
|
||||
// Use ComponentOps for the actual operation
|
||||
Component newComponent = ComponentOps.AddComponent(targetGo, type, out string error);
|
||||
if (newComponent == null)
|
||||
{
|
||||
return new ErrorResponse(error ?? $"Failed to add component '{componentTypeName}'.");
|
||||
}
|
||||
|
||||
// Set properties if provided
|
||||
JObject properties = @params["properties"] as JObject ?? @params["componentProperties"] as JObject;
|
||||
if (properties != null && properties.HasValues)
|
||||
{
|
||||
// Record for undo before modifying properties
|
||||
Undo.RecordObject(newComponent, "Modify Component Properties");
|
||||
SetPropertiesOnComponent(newComponent, properties);
|
||||
}
|
||||
|
||||
EditorUtility.SetDirty(targetGo);
|
||||
MarkOwningSceneDirty(targetGo);
|
||||
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
message = $"Component '{componentTypeName}' added to '{targetGo.name}'.",
|
||||
data = new
|
||||
{
|
||||
instanceID = targetGo.GetInstanceID(),
|
||||
componentType = type.FullName,
|
||||
componentInstanceID = newComponent.GetInstanceID()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static object RemoveComponent(JObject @params, JToken targetToken, string searchMethod)
|
||||
{
|
||||
GameObject targetGo = FindTarget(targetToken, searchMethod);
|
||||
if (targetGo == null)
|
||||
{
|
||||
return new ErrorResponse($"Target GameObject ('{targetToken}') not found using method '{searchMethod ?? "default"}'.");
|
||||
}
|
||||
|
||||
string componentTypeName = ParamCoercion.CoerceString(@params["componentType"] ?? @params["component_type"], null);
|
||||
if (string.IsNullOrEmpty(componentTypeName))
|
||||
{
|
||||
return new ErrorResponse("'componentType' parameter is required for 'remove' action.");
|
||||
}
|
||||
|
||||
// Resolve component type using unified type resolver
|
||||
Type type = UnityTypeResolver.ResolveComponent(componentTypeName);
|
||||
if (type == null)
|
||||
{
|
||||
return new ErrorResponse($"Component type '{componentTypeName}' not found.");
|
||||
}
|
||||
|
||||
// Use ComponentOps for the actual operation
|
||||
bool removed = ComponentOps.RemoveComponent(targetGo, type, out string error);
|
||||
if (!removed)
|
||||
{
|
||||
return new ErrorResponse(error ?? $"Failed to remove component '{componentTypeName}'.");
|
||||
}
|
||||
|
||||
EditorUtility.SetDirty(targetGo);
|
||||
MarkOwningSceneDirty(targetGo);
|
||||
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
message = $"Component '{componentTypeName}' removed from '{targetGo.name}'.",
|
||||
data = new
|
||||
{
|
||||
instanceID = targetGo.GetInstanceID()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static object SetProperty(JObject @params, JToken targetToken, string searchMethod)
|
||||
{
|
||||
GameObject targetGo = FindTarget(targetToken, searchMethod);
|
||||
if (targetGo == null)
|
||||
{
|
||||
return new ErrorResponse($"Target GameObject ('{targetToken}') not found using method '{searchMethod ?? "default"}'.");
|
||||
}
|
||||
|
||||
string componentType = ParamCoercion.CoerceString(@params["componentType"] ?? @params["component_type"], null);
|
||||
if (string.IsNullOrEmpty(componentType))
|
||||
{
|
||||
return new ErrorResponse("'componentType' parameter is required for 'set_property' action.");
|
||||
}
|
||||
|
||||
// Resolve component type using unified type resolver
|
||||
Type type = UnityTypeResolver.ResolveComponent(componentType);
|
||||
if (type == null)
|
||||
{
|
||||
return new ErrorResponse($"Component type '{componentType}' not found.");
|
||||
}
|
||||
|
||||
Component component = targetGo.GetComponent(type);
|
||||
if (component == null)
|
||||
{
|
||||
return new ErrorResponse($"Component '{componentType}' not found on '{targetGo.name}'.");
|
||||
}
|
||||
|
||||
// Get property and value
|
||||
string propertyName = ParamCoercion.CoerceString(@params["property"], null);
|
||||
JToken valueToken = @params["value"];
|
||||
|
||||
// Support both single property or properties object
|
||||
JObject properties = @params["properties"] as JObject;
|
||||
|
||||
if (string.IsNullOrEmpty(propertyName) && (properties == null || !properties.HasValues))
|
||||
{
|
||||
return new ErrorResponse("Either 'property'+'value' or 'properties' object is required for 'set_property' action.");
|
||||
}
|
||||
|
||||
var errors = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
Undo.RecordObject(component, $"Set property on {componentType}");
|
||||
|
||||
if (!string.IsNullOrEmpty(propertyName) && valueToken != null)
|
||||
{
|
||||
// Single property mode
|
||||
var error = TrySetProperty(component, propertyName, valueToken);
|
||||
if (error != null)
|
||||
{
|
||||
errors.Add(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (properties != null && properties.HasValues)
|
||||
{
|
||||
// Multiple properties mode
|
||||
foreach (var prop in properties.Properties())
|
||||
{
|
||||
var error = TrySetProperty(component, prop.Name, prop.Value);
|
||||
if (error != null)
|
||||
{
|
||||
errors.Add(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorUtility.SetDirty(component);
|
||||
MarkOwningSceneDirty(targetGo);
|
||||
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
return new
|
||||
{
|
||||
success = false,
|
||||
message = $"Some properties failed to set on '{componentType}'.",
|
||||
data = new
|
||||
{
|
||||
instanceID = targetGo.GetInstanceID(),
|
||||
errors = errors
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
message = $"Properties set on component '{componentType}' on '{targetGo.name}'.",
|
||||
data = new
|
||||
{
|
||||
instanceID = targetGo.GetInstanceID()
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ErrorResponse($"Error setting properties on component '{componentType}': {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Marks the appropriate scene as dirty for the given GameObject.
|
||||
/// Handles both regular scenes and prefab stages.
|
||||
/// </summary>
|
||||
private static void MarkOwningSceneDirty(GameObject targetGo)
|
||||
{
|
||||
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
|
||||
if (prefabStage != null)
|
||||
{
|
||||
EditorSceneManager.MarkSceneDirty(prefabStage.scene);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorSceneManager.MarkSceneDirty(targetGo.scene);
|
||||
}
|
||||
}
|
||||
|
||||
private static GameObject FindTarget(JToken targetToken, string searchMethod)
|
||||
{
|
||||
if (targetToken == null)
|
||||
return null;
|
||||
|
||||
// Try instance ID first
|
||||
if (targetToken.Type == JTokenType.Integer)
|
||||
{
|
||||
int instanceId = targetToken.Value<int>();
|
||||
return GameObjectLookup.FindById(instanceId);
|
||||
}
|
||||
|
||||
string targetStr = targetToken.ToString();
|
||||
|
||||
// Try parsing as instance ID
|
||||
if (int.TryParse(targetStr, out int parsedId))
|
||||
{
|
||||
var byId = GameObjectLookup.FindById(parsedId);
|
||||
if (byId != null)
|
||||
return byId;
|
||||
}
|
||||
|
||||
// Use GameObjectLookup for search
|
||||
return GameObjectLookup.FindByTarget(targetToken, searchMethod ?? "by_name", true);
|
||||
}
|
||||
|
||||
private static void SetPropertiesOnComponent(Component component, JObject properties)
|
||||
{
|
||||
if (component == null || properties == null)
|
||||
return;
|
||||
|
||||
var errors = new List<string>();
|
||||
foreach (var prop in properties.Properties())
|
||||
{
|
||||
var error = TrySetProperty(component, prop.Name, prop.Value);
|
||||
if (error != null)
|
||||
errors.Add(error);
|
||||
}
|
||||
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
McpLog.Warn($"[ManageComponents] Some properties failed to set on {component.GetType().Name}: {string.Join(", ", errors)}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to set a property or field on a component.
|
||||
/// Delegates to ComponentOps.SetProperty for unified implementation.
|
||||
/// </summary>
|
||||
private static string TrySetProperty(Component component, string propertyName, JToken value)
|
||||
{
|
||||
if (component == null || string.IsNullOrEmpty(propertyName))
|
||||
return "Invalid component or property name";
|
||||
|
||||
if (ComponentOps.SetProperty(component, propertyName, value, out string error))
|
||||
{
|
||||
return null; // Success
|
||||
}
|
||||
|
||||
McpLog.Warn($"[ManageComponents] {error}");
|
||||
return error;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user