SetComponentValueCommand.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using UnityEngine;
  6. using UnityEditor;
  7. using LLM.Editor.Helper;
  8. using Newtonsoft.Json.Linq;
  9. using JetBrains.Annotations;
  10. using Object = UnityEngine.Object;
  11. namespace LLM.Editor.Commands
  12. {
  13. [Serializable]
  14. public class MemberValue
  15. {
  16. public string memberName;
  17. public JToken value;
  18. }
  19. [Serializable]
  20. public class SetComponentValueParams
  21. {
  22. public string subjectIdentifier;
  23. public string componentName;
  24. public List<MemberValue> members;
  25. }
  26. [UsedImplicitly]
  27. public class SetComponentValueCommand : ICommand
  28. {
  29. private readonly SetComponentValueParams _params;
  30. public SetComponentValueCommand(string jsonParams)
  31. {
  32. _params = jsonParams?.FromJson<SetComponentValueParams>();
  33. }
  34. public CommandOutcome Execute(Data.CommandContext context)
  35. {
  36. if (_params == null || string.IsNullOrEmpty(_params.subjectIdentifier) || string.IsNullOrEmpty(_params.componentName))
  37. {
  38. context.ErrorMessage = "Invalid parameters. Subject and component names are required.";
  39. return CommandOutcome.Error;
  40. }
  41. if (_params.members == null || !_params.members.Any())
  42. {
  43. context.ErrorMessage = "No members provided to set.";
  44. return CommandOutcome.Error;
  45. }
  46. var targetObject = CommandUtility.ResolveIdentifier(context, _params.subjectIdentifier);
  47. if (!targetObject)
  48. {
  49. context.ErrorMessage = $"Could not find subject with logical name '{_params.subjectIdentifier}'.";
  50. return CommandOutcome.Error;
  51. }
  52. if (targetObject is not GameObject go)
  53. {
  54. context.ErrorMessage = $"The identified subject '{targetObject.name}' is not a GameObject.";
  55. return CommandOutcome.Error;
  56. }
  57. var targetComponent = _params.componentName == "UnityEngine.Transform" ? go.transform : go.GetComponent(GetTypeByName(_params.componentName));
  58. if (!targetComponent)
  59. {
  60. context.ErrorMessage = $"Could not find component '{_params.componentName}' on subject '{_params.subjectIdentifier}'.";
  61. return CommandOutcome.Error;
  62. }
  63. foreach (var member in _params.members)
  64. {
  65. var outcome = SetMemberValue(context, targetComponent, member.memberName, member.value);
  66. if (outcome == CommandOutcome.Error)
  67. {
  68. // The error message will be set by SetMemberValue
  69. return CommandOutcome.Error;
  70. }
  71. }
  72. return CommandOutcome.Success;
  73. }
  74. private CommandOutcome SetMemberValue(Data.CommandContext context, Component targetComponent, string memberName, JToken value)
  75. {
  76. var serializedObject = new SerializedObject(targetComponent);
  77. var serializedProperty = serializedObject.FindProperty(memberName);
  78. if (serializedProperty != null)
  79. {
  80. return SetValueWithSerializedProperty(context, serializedObject, serializedProperty, value);
  81. }
  82. var propertyInfo = targetComponent.GetType().GetProperty(memberName, BindingFlags.Public | BindingFlags.Instance);
  83. if (propertyInfo != null && propertyInfo.CanWrite)
  84. {
  85. return SetValueWithReflection(context, targetComponent, propertyInfo, value);
  86. }
  87. context.ErrorMessage = $"Could not find a writable serialized field or public property named '{memberName}' on component '{targetComponent.GetType().Name}'.";
  88. return CommandOutcome.Error;
  89. }
  90. private CommandOutcome SetValueWithSerializedProperty(Data.CommandContext context, SerializedObject serializedObject, SerializedProperty property, JToken value)
  91. {
  92. serializedObject.Update();
  93. try
  94. {
  95. switch (property.propertyType)
  96. {
  97. case SerializedPropertyType.ObjectReference:
  98. var objToAssign = GetObjectReferenceValue(context, property, value);
  99. if (objToAssign == null && value.Type != JTokenType.Null) return CommandOutcome.Error;
  100. property.objectReferenceValue = objToAssign;
  101. break;
  102. case SerializedPropertyType.Float:
  103. property.floatValue = value.Value<float>();
  104. break;
  105. case SerializedPropertyType.Integer:
  106. property.intValue = value.Value<int>();
  107. break;
  108. case SerializedPropertyType.Boolean:
  109. property.boolValue = value.Value<bool>();
  110. break;
  111. case SerializedPropertyType.String:
  112. property.stringValue = value.Value<string>();
  113. break;
  114. case SerializedPropertyType.Vector3:
  115. property.vector3Value = value.ToVector3();
  116. break;
  117. case SerializedPropertyType.Quaternion:
  118. property.quaternionValue = value.ToQuaternion();
  119. break;
  120. default:
  121. context.ErrorMessage = $"The property type '{property.propertyType}' is not currently supported for setting values via SerializedObject.";
  122. return CommandOutcome.Error;
  123. }
  124. }
  125. catch (Exception e)
  126. {
  127. context.ErrorMessage = $"Failed to set value for '{property.name}'. Error: {e.Message}";
  128. return CommandOutcome.Error;
  129. }
  130. serializedObject.ApplyModifiedProperties();
  131. Debug.Log($"[SetComponentValueCommand] Successfully set '{property.name}' on '{serializedObject.targetObject.GetType().Name}' using SerializedObject.");
  132. return CommandOutcome.Success;
  133. }
  134. private CommandOutcome SetValueWithReflection(Data.CommandContext context, Component target, PropertyInfo propertyInfo, JToken value)
  135. {
  136. Undo.RecordObject(target, $"Set {propertyInfo.Name}");
  137. try
  138. {
  139. object valueToSet;
  140. var targetType = propertyInfo.PropertyType;
  141. if (targetType == typeof(Vector3)) valueToSet = value.ToVector3();
  142. else if (targetType == typeof(Quaternion)) valueToSet = value.ToQuaternion();
  143. else if (targetType.IsSubclassOf(typeof(Object)) || targetType == typeof(Object))
  144. {
  145. valueToSet = GetObjectReferenceValue(context, null, value);
  146. if (valueToSet == null && value.Type != JTokenType.Null) return CommandOutcome.Error;
  147. }
  148. else
  149. {
  150. valueToSet = Convert.ChangeType(value.ToObject(targetType), targetType);
  151. }
  152. propertyInfo.SetValue(target, valueToSet);
  153. }
  154. catch (Exception e)
  155. {
  156. context.ErrorMessage = $"Failed to set value for '{propertyInfo.Name}' using Reflection. Error: {e.Message}";
  157. return CommandOutcome.Error;
  158. }
  159. EditorUtility.SetDirty(target);
  160. Debug.Log($"[SetComponentValueCommand] Successfully set '{propertyInfo.Name}' on '{target.GetType().Name}' using Reflection.");
  161. return CommandOutcome.Success;
  162. }
  163. private Object GetObjectReferenceValue(Data.CommandContext context, SerializedProperty property, JToken value)
  164. {
  165. var referenceData = value.ToObject<ObjectReferenceValue>();
  166. if (referenceData == null)
  167. {
  168. if (value.Type == JTokenType.Null) return null;
  169. context.ErrorMessage = "The 'value' for an ObjectReference must be an object with an 'identifier' or be null.";
  170. return null;
  171. }
  172. var baseObject = CommandUtility.ResolveIdentifier(context, referenceData.identifier);
  173. if (!baseObject)
  174. {
  175. context.ErrorMessage = $"Could not resolve the base identifier '{referenceData.identifier}' for the object reference.";
  176. return null;
  177. }
  178. Object objectToAssign = baseObject;
  179. if (baseObject is GameObject go && !string.IsNullOrEmpty(referenceData.childPath))
  180. {
  181. var childTransform = go.transform.Find(referenceData.childPath);
  182. if (!childTransform)
  183. {
  184. context.ErrorMessage = $"Could not find child '{referenceData.childPath}' on object '{baseObject.name}'.";
  185. return null;
  186. }
  187. objectToAssign = childTransform.gameObject;
  188. var componentType = GetTypeFromProperty(property);
  189. if (componentType != null && childTransform.TryGetComponent(componentType, out var component))
  190. {
  191. objectToAssign = component;
  192. }
  193. }
  194. return objectToAssign;
  195. }
  196. private static Type GetTypeFromProperty(SerializedProperty property)
  197. {
  198. if (property == null) return null;
  199. var typeString = property.type;
  200. var startIndex = typeString.IndexOf('<');
  201. var endIndex = typeString.IndexOf('>');
  202. if (startIndex == -1 || endIndex == -1) return null;
  203. var managedTypeName = typeString.Substring(startIndex + 1, endIndex - startIndex - 1).Trim();
  204. return GetTypeByName(managedTypeName);
  205. }
  206. private static Type GetTypeByName(string typeName)
  207. {
  208. return AppDomain.CurrentDomain.GetAssemblies()
  209. .Select(assembly => assembly.GetType(typeName, false))
  210. .FirstOrDefault(type => type != null);
  211. }
  212. [Serializable]
  213. private class ObjectReferenceValue
  214. {
  215. public string identifier;
  216. public string childPath;
  217. }
  218. }
  219. }