SetComponentValueCommand.cs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. using System;
  2. using System.Linq;
  3. using UnityEngine;
  4. using UnityEditor;
  5. using LLM.Editor.Helper;
  6. using Newtonsoft.Json.Linq;
  7. using JetBrains.Annotations;
  8. namespace LLM.Editor.Commands
  9. {
  10. [Serializable]
  11. public class SetComponentValueParams
  12. {
  13. public string subjectIdentifier;
  14. public string componentName;
  15. public string memberName;
  16. public JToken value; // Changed to JToken to handle both simple values and objects
  17. }
  18. /// <summary>
  19. /// The final action command in an analysis workflow. It takes a calculated value
  20. /// from the LLM and applies it to a specific property on a component.
  21. /// Now supports setting simple values AND object references.
  22. /// </summary>
  23. [UsedImplicitly]
  24. public class SetComponentValueCommand : ICommand
  25. {
  26. [Serializable]
  27. private class ObjectReferenceValue
  28. {
  29. public string identifier;
  30. public string childPath; // Optional
  31. }
  32. private readonly SetComponentValueParams _params;
  33. public SetComponentValueCommand(string jsonParams)
  34. {
  35. _params = jsonParams?.FromJson<SetComponentValueParams>();
  36. }
  37. public CommandOutcome Execute(Data.CommandContext context)
  38. {
  39. if (_params == null || string.IsNullOrEmpty(_params.subjectIdentifier) || string.IsNullOrEmpty(_params.componentName) || string.IsNullOrEmpty(_params.memberName))
  40. {
  41. Debug.LogError("[SetComponentValueCommand] Invalid parameters. Subject, component, and member names are required.");
  42. return CommandOutcome.Error;
  43. }
  44. var targetObject = ResolveIdentifier(_params.subjectIdentifier);
  45. if (!targetObject)
  46. {
  47. Debug.LogError($"[SetComponentValueCommand] Could not find subject GameObject '{_params.subjectIdentifier}'.");
  48. return CommandOutcome.Error;
  49. }
  50. var targetComponent = targetObject.GetComponent(_params.componentName);
  51. if (!targetComponent)
  52. {
  53. Debug.LogError($"[SetComponentValueCommand] Could not find component '{_params.componentName}' on subject '{_params.subjectIdentifier}'.");
  54. return CommandOutcome.Error;
  55. }
  56. var serializedObject = new SerializedObject(targetComponent);
  57. var serializedProperty = serializedObject.FindProperty(_params.memberName);
  58. if (serializedProperty == null)
  59. {
  60. 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.");
  61. return CommandOutcome.Error;
  62. }
  63. serializedObject.Update();
  64. // Set value based on the property type
  65. switch (serializedProperty.propertyType)
  66. {
  67. case SerializedPropertyType.ObjectReference:
  68. if (!SetObjectReferenceValue(serializedProperty, targetObject))
  69. {
  70. return CommandOutcome.Error;
  71. }
  72. break;
  73. case SerializedPropertyType.Float:
  74. serializedProperty.floatValue = _params.value.Value<float>();
  75. break;
  76. case SerializedPropertyType.Integer:
  77. serializedProperty.intValue = _params.value.Value<int>();
  78. break;
  79. case SerializedPropertyType.Boolean:
  80. serializedProperty.boolValue = _params.value.Value<bool>();
  81. break;
  82. case SerializedPropertyType.String:
  83. serializedProperty.stringValue = _params.value.Value<string>();
  84. break;
  85. default:
  86. Debug.LogError($"[SetComponentValueCommand] The property type '{serializedProperty.propertyType}' is not currently supported for setting values.");
  87. return CommandOutcome.Error;
  88. }
  89. serializedObject.ApplyModifiedProperties();
  90. Debug.Log($"[SetComponentValueCommand] Successfully set '{_params.memberName}' on '{_params.componentName}'.");
  91. return CommandOutcome.Success;
  92. }
  93. private bool SetObjectReferenceValue(SerializedProperty property, GameObject contextObject)
  94. {
  95. var referenceData = _params.value.ToObject<ObjectReferenceValue>();
  96. if (referenceData == null)
  97. {
  98. // Handle case where value is null to clear the reference
  99. if (_params.value.Type == JTokenType.Null)
  100. {
  101. property.objectReferenceValue = null;
  102. return true;
  103. }
  104. Debug.LogError("[SetComponentValueCommand] The 'value' for an ObjectReference must be an object with an 'identifier' or be null.");
  105. return false;
  106. }
  107. // The object to assign can be identified relative to the object being modified,
  108. // or it can be a completely different object identified by its own GUID/InstanceID.
  109. var baseObject = ResolveIdentifier(referenceData.identifier) ?? contextObject;
  110. if (!baseObject)
  111. {
  112. Debug.LogError($"[SetComponentValueCommand] Could not resolve the base identifier '{referenceData.identifier}' for the object reference.");
  113. return false;
  114. }
  115. if (!baseObject && !string.IsNullOrEmpty(referenceData.childPath))
  116. {
  117. Debug.LogError($"[SetComponentValueCommand] Cannot use 'childPath' because the base object '{baseObject.name}' is not a GameObject.");
  118. return false;
  119. }
  120. UnityEngine.Object objectToAssign = baseObject;
  121. if (baseObject && !string.IsNullOrEmpty(referenceData.childPath))
  122. {
  123. var childTransform = baseObject.transform.Find(referenceData.childPath);
  124. if (!childTransform)
  125. {
  126. Debug.LogError($"[SetComponentValueCommand] Could not find child '{referenceData.childPath}' on object '{baseObject.name}'.");
  127. return false;
  128. }
  129. objectToAssign = childTransform.gameObject; // Default to assigning the GameObject
  130. // If the property is looking for a specific component type (like Transform), get that instead.
  131. var componentType = GetTypeFromProperty(property);
  132. if (componentType != null)
  133. {
  134. var component = childTransform.GetComponent(componentType);
  135. if (component)
  136. {
  137. objectToAssign = component;
  138. }
  139. }
  140. }
  141. property.objectReferenceValue = objectToAssign;
  142. return true;
  143. }
  144. private static GameObject ResolveIdentifier(string identifier)
  145. {
  146. if (string.IsNullOrEmpty(identifier)) return null;
  147. if (int.TryParse(identifier, out var instanceId))
  148. {
  149. return EditorUtility.InstanceIDToObject(instanceId) as GameObject;
  150. }
  151. var path = AssetDatabase.GUIDToAssetPath(identifier);
  152. return !string.IsNullOrEmpty(path) ? AssetDatabase.LoadAssetAtPath<GameObject>(path) : null;
  153. }
  154. private static Type GetTypeFromProperty(SerializedProperty property)
  155. {
  156. // Extracts the type from a property string like "PPtr<Transform>"
  157. var typeString = property.type;
  158. var startIndex = typeString.IndexOf('<');
  159. var endIndex = typeString.IndexOf('>');
  160. if (startIndex == -1 || endIndex == -1) return null;
  161. var managedTypeName = typeString.Substring(startIndex + 1, endIndex - startIndex - 1).Trim();
  162. return GetTypeByName(managedTypeName);
  163. }
  164. private static Type GetTypeByName(string typeName)
  165. {
  166. return AppDomain.CurrentDomain.GetAssemblies().Select(assembly => assembly.GetType(typeName)).FirstOrDefault(type => type != null);
  167. }
  168. }
  169. }