123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- using System;
- using System.Linq;
- using UnityEngine;
- using UnityEditor;
- using LLM.Editor.Helper;
- using Newtonsoft.Json.Linq;
- using JetBrains.Annotations;
- namespace LLM.Editor.Commands
- {
- [Serializable]
- public class SetComponentValueParams
- {
- public string subjectIdentifier;
- public string componentName;
- public string memberName;
- public JToken value; // Changed to JToken to handle both simple values and objects
- }
- /// <summary>
- /// The final action command in an analysis workflow. It takes a calculated value
- /// from the LLM and applies it to a specific property on a component.
- /// Now supports setting simple values AND object references.
- /// </summary>
- [UsedImplicitly]
- public class SetComponentValueCommand : ICommand
- {
- [Serializable]
- private class ObjectReferenceValue
- {
- public string identifier;
- public string childPath; // Optional
- }
-
- private readonly SetComponentValueParams _params;
- public SetComponentValueCommand(string jsonParams)
- {
- _params = jsonParams?.FromJson<SetComponentValueParams>();
- }
- public CommandOutcome Execute(Data.CommandContext context)
- {
- if (_params == null || string.IsNullOrEmpty(_params.subjectIdentifier) || string.IsNullOrEmpty(_params.componentName) || string.IsNullOrEmpty(_params.memberName))
- {
- Debug.LogError("[SetComponentValueCommand] Invalid parameters. Subject, component, and member names are required.");
- return CommandOutcome.Error;
- }
- var targetObject = ResolveIdentifier(_params.subjectIdentifier);
- if (!targetObject)
- {
- Debug.LogError($"[SetComponentValueCommand] Could not find subject GameObject '{_params.subjectIdentifier}'.");
- return CommandOutcome.Error;
- }
- var targetComponent = targetObject.GetComponent(_params.componentName);
- if (!targetComponent)
- {
- Debug.LogError($"[SetComponentValueCommand] Could not find component '{_params.componentName}' on subject '{_params.subjectIdentifier}'.");
- return CommandOutcome.Error;
- }
- var serializedObject = new SerializedObject(targetComponent);
- var serializedProperty = serializedObject.FindProperty(_params.memberName);
- if (serializedProperty == null)
- {
- Debug.LogError($"[SetComponentValueCommand] Could not find serialized property or field '{_params.memberName}' on component '{_params.componentName}'. Make sure it is public or has a [SerializeField] attribute.");
- return CommandOutcome.Error;
- }
-
- serializedObject.Update();
- // Set value based on the property type
- switch (serializedProperty.propertyType)
- {
- case SerializedPropertyType.ObjectReference:
- if (!SetObjectReferenceValue(serializedProperty, targetObject))
- {
- return CommandOutcome.Error;
- }
- break;
- case SerializedPropertyType.Float:
- serializedProperty.floatValue = _params.value.Value<float>();
- break;
- case SerializedPropertyType.Integer:
- serializedProperty.intValue = _params.value.Value<int>();
- break;
- case SerializedPropertyType.Boolean:
- serializedProperty.boolValue = _params.value.Value<bool>();
- break;
- case SerializedPropertyType.String:
- serializedProperty.stringValue = _params.value.Value<string>();
- break;
- default:
- Debug.LogError($"[SetComponentValueCommand] The property type '{serializedProperty.propertyType}' is not currently supported for setting values.");
- return CommandOutcome.Error;
- }
-
- serializedObject.ApplyModifiedProperties();
- Debug.Log($"[SetComponentValueCommand] Successfully set '{_params.memberName}' on '{_params.componentName}'.");
- return CommandOutcome.Success;
- }
- private bool SetObjectReferenceValue(SerializedProperty property, GameObject contextObject)
- {
- var referenceData = _params.value.ToObject<ObjectReferenceValue>();
- if (referenceData == null)
- {
- // Handle case where value is null to clear the reference
- if (_params.value.Type == JTokenType.Null)
- {
- property.objectReferenceValue = null;
- return true;
- }
- Debug.LogError("[SetComponentValueCommand] The 'value' for an ObjectReference must be an object with an 'identifier' or be null.");
- return false;
- }
- // The object to assign can be identified relative to the object being modified,
- // or it can be a completely different object identified by its own GUID/InstanceID.
- var baseObject = ResolveIdentifier(referenceData.identifier) ?? contextObject;
- if (!baseObject)
- {
- Debug.LogError($"[SetComponentValueCommand] Could not resolve the base identifier '{referenceData.identifier}' for the object reference.");
- return false;
- }
- if (!baseObject && !string.IsNullOrEmpty(referenceData.childPath))
- {
- Debug.LogError($"[SetComponentValueCommand] Cannot use 'childPath' because the base object '{baseObject.name}' is not a GameObject.");
- return false;
- }
- UnityEngine.Object objectToAssign = baseObject;
- if (baseObject && !string.IsNullOrEmpty(referenceData.childPath))
- {
- var childTransform = baseObject.transform.Find(referenceData.childPath);
- if (!childTransform)
- {
- Debug.LogError($"[SetComponentValueCommand] Could not find child '{referenceData.childPath}' on object '{baseObject.name}'.");
- return false;
- }
- objectToAssign = childTransform.gameObject; // Default to assigning the GameObject
-
- // If the property is looking for a specific component type (like Transform), get that instead.
- var componentType = GetTypeFromProperty(property);
- if (componentType != null)
- {
- var component = childTransform.GetComponent(componentType);
- if (component)
- {
- objectToAssign = component;
- }
- }
- }
-
- property.objectReferenceValue = objectToAssign;
- return true;
- }
- private static GameObject ResolveIdentifier(string identifier)
- {
- if (string.IsNullOrEmpty(identifier)) return null;
- if (int.TryParse(identifier, out var instanceId))
- {
- return EditorUtility.InstanceIDToObject(instanceId) as GameObject;
- }
- var path = AssetDatabase.GUIDToAssetPath(identifier);
- return !string.IsNullOrEmpty(path) ? AssetDatabase.LoadAssetAtPath<GameObject>(path) : null;
- }
-
- private static Type GetTypeFromProperty(SerializedProperty property)
- {
- // Extracts the type from a property string like "PPtr<Transform>"
- var typeString = property.type;
- var startIndex = typeString.IndexOf('<');
- var endIndex = typeString.IndexOf('>');
- if (startIndex == -1 || endIndex == -1) return null;
- var managedTypeName = typeString.Substring(startIndex + 1, endIndex - startIndex - 1).Trim();
- return GetTypeByName(managedTypeName);
- }
- private static Type GetTypeByName(string typeName)
- {
- return AppDomain.CurrentDomain.GetAssemblies().Select(assembly => assembly.GetType(typeName)).FirstOrDefault(type => type != null);
- }
- }
- }
|