@mflrevan/ucp 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +1 -1
  2. package/bridge/com.ucp.bridge/Editor/AssemblyInfo.cs +3 -0
  3. package/bridge/com.ucp.bridge/Editor/AssemblyInfo.cs.meta +2 -0
  4. package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +10 -1
  5. package/bridge/com.ucp.bridge/Editor/Compatibility/UnityObjectCompat.cs +26 -0
  6. package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs +2 -2
  7. package/bridge/com.ucp.bridge/Editor/Controllers/EditorModalGuard.cs +60 -0
  8. package/bridge/com.ucp.bridge/Editor/Controllers/EditorModalGuard.cs.meta +2 -0
  9. package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs +56 -5
  10. package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs +2 -2
  11. package/bridge/com.ucp.bridge/Editor/Controllers/ObjectLocator.cs +207 -0
  12. package/bridge/com.ucp.bridge/Editor/Controllers/ObjectLocator.cs.meta +2 -0
  13. package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs +1 -1
  14. package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs +1 -35
  15. package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs +3 -3
  16. package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs +1 -1
  17. package/bridge/com.ucp.bridge/Editor/Controllers/ReferenceController.cs +1 -1
  18. package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs +6 -6
  19. package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +2 -34
  20. package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +4 -4
  21. package/bridge/com.ucp.bridge/Editor/Controllers/SpatialController.cs +322 -0
  22. package/bridge/com.ucp.bridge/Editor/Controllers/SpatialController.cs.meta +2 -0
  23. package/bridge/com.ucp.bridge/Editor/Controllers/TransformController.cs +249 -0
  24. package/bridge/com.ucp.bridge/Editor/Controllers/TransformController.cs.meta +2 -0
  25. package/bridge/com.ucp.bridge/Editor/Controllers/ViewController.cs +415 -0
  26. package/bridge/com.ucp.bridge/Editor/Controllers/ViewController.cs.meta +2 -0
  27. package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +63 -7
  28. package/bridge/com.ucp.bridge/Tests/Editor/SpatialVisualControllerTests.cs +252 -0
  29. package/bridge/com.ucp.bridge/Tests/Editor/SpatialVisualControllerTests.cs.meta +2 -0
  30. package/bridge/com.ucp.bridge/package.json +1 -1
  31. package/package.json +1 -1
@@ -0,0 +1,252 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using NUnit.Framework;
4
+ using UnityEditor.SceneManagement;
5
+ using UnityEngine;
6
+ using UnityEngine.Rendering;
7
+ using UnityEngine.SceneManagement;
8
+
9
+ namespace UCP.Bridge.Tests
10
+ {
11
+ /// <summary>
12
+ /// Edit-mode coverage for the spatial/visual controllers added for in-scene authoring:
13
+ /// TransformController, SpatialController, ViewController, and the shared ObjectLocator.
14
+ /// </summary>
15
+ public class SpatialVisualControllerTests
16
+ {
17
+ private CommandRouter _router;
18
+ private readonly List<GameObject> _spawned = new();
19
+
20
+ [SetUp]
21
+ public void SetUp()
22
+ {
23
+ EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
24
+ _router = new CommandRouter();
25
+ HierarchyController.Register(_router);
26
+ TransformController.Register(_router);
27
+ SpatialController.Register(_router);
28
+ ViewController.Register(_router);
29
+ _spawned.Clear();
30
+ }
31
+
32
+ [TearDown]
33
+ public void TearDown()
34
+ {
35
+ foreach (var go in _spawned)
36
+ if (go != null) UnityEngine.Object.DestroyImmediate(go);
37
+ _spawned.Clear();
38
+ }
39
+
40
+ private GameObject Spawn(PrimitiveType type, string name, Vector3 pos)
41
+ {
42
+ var go = GameObject.CreatePrimitive(type);
43
+ go.name = name;
44
+ go.transform.position = pos;
45
+ _spawned.Add(go);
46
+ return go;
47
+ }
48
+
49
+ private static Dictionary<string, object> Result(JsonRpcResponse r)
50
+ {
51
+ Assert.That(r.error, Is.Null, "RPC returned an error");
52
+ return (Dictionary<string, object>)r.result;
53
+ }
54
+
55
+ private static float F(object o) => Convert.ToSingle(o);
56
+ private static List<object> Vec(Dictionary<string, object> d, string key) => (List<object>)d[key];
57
+
58
+ // --- Primitive builder ---------------------------------------------
59
+
60
+ [Test]
61
+ public void Object_CreatePrimitive_AddsMeshAndCollider()
62
+ {
63
+ var res = Result(_router.Dispatch("object/create", 1,
64
+ "{\"name\":\"Greybox\",\"primitive\":\"Cube\"}"));
65
+ var id = Convert.ToInt32(res["instanceId"]);
66
+ var go = ObjectLocator.FindByInstanceId(id);
67
+ _spawned.Add(go);
68
+
69
+ Assert.That(go, Is.Not.Null);
70
+ Assert.That(go.name, Is.EqualTo("Greybox"));
71
+ Assert.That(go.GetComponent<MeshFilter>(), Is.Not.Null, "primitive should have a mesh");
72
+ Assert.That(go.GetComponent<MeshFilter>().sharedMesh, Is.Not.Null);
73
+ Assert.That(go.GetComponent<Collider>(), Is.Not.Null, "primitive should have a collider");
74
+ }
75
+
76
+ [Test]
77
+ public void Object_CreatePrimitive_RejectsUnknownType()
78
+ {
79
+ var res = _router.Dispatch("object/create", 1,
80
+ "{\"name\":\"Bad\",\"primitive\":\"Dodecahedron\"}");
81
+ Assert.That(res.error, Is.Not.Null, "unknown primitive must be an InvalidParams error, not a crash");
82
+ }
83
+
84
+ [Test]
85
+ public void Object_Instantiate_PrimitiveName_RedirectsToPrimitiveFlag()
86
+ {
87
+ // The exact wrong path a weak agent took: treating "PrimitiveType.Cube" as a prefab.
88
+ var res = _router.Dispatch("object/instantiate", 1,
89
+ "{\"prefab\":\"PrimitiveType.Cube\"}");
90
+ Assert.That(res.error, Is.Not.Null);
91
+ Assert.That(res.error.message, Does.Contain("--primitive"),
92
+ "the not-found error should redirect to object create --primitive");
93
+ }
94
+
95
+ // --- Transform -----------------------------------------------------
96
+
97
+ [Test]
98
+ public void Transform_Move_WorldAbsoluteAndRelative()
99
+ {
100
+ var go = Spawn(PrimitiveType.Cube, "Mover", Vector3.zero);
101
+ var id = go.GetId();
102
+
103
+ var abs = Result(_router.Dispatch("transform/move", 1,
104
+ "{\"instanceId\":" + id + ",\"position\":[5,0,0],\"space\":\"world\"}"));
105
+ Assert.That(go.transform.position.x, Is.EqualTo(5f).Within(0.001f));
106
+ Assert.That(F(Vec(abs, "position")[0]), Is.EqualTo(5f).Within(0.001f));
107
+
108
+ _router.Dispatch("transform/move", 1,
109
+ "{\"instanceId\":" + id + ",\"position\":[1,0,0],\"relative\":true}");
110
+ Assert.That(go.transform.position.x, Is.EqualTo(6f).Within(0.001f));
111
+ }
112
+
113
+ [Test]
114
+ public void Transform_Rotate_AbsoluteEulerWorld()
115
+ {
116
+ var go = Spawn(PrimitiveType.Cube, "Rotor", Vector3.zero);
117
+ _router.Dispatch("transform/rotate", 1,
118
+ "{\"instanceId\":" + go.GetId() + ",\"euler\":[0,90,0]}");
119
+ Assert.That(go.transform.eulerAngles.y, Is.EqualTo(90f).Within(0.01f));
120
+ }
121
+
122
+ [Test]
123
+ public void Transform_Scale_UniformAndNonUniform()
124
+ {
125
+ var go = Spawn(PrimitiveType.Cube, "Scaler", Vector3.zero);
126
+ _router.Dispatch("transform/scale", 1, "{\"instanceId\":" + go.GetId() + ",\"uniform\":2}");
127
+ Assert.That(go.transform.localScale, Is.EqualTo(Vector3.one * 2f));
128
+
129
+ _router.Dispatch("transform/scale", 1, "{\"instanceId\":" + go.GetId() + ",\"scale\":[1,3,1],\"relative\":true}");
130
+ Assert.That(go.transform.localScale.y, Is.EqualTo(6f).Within(0.001f));
131
+ }
132
+
133
+ [Test]
134
+ public void Transform_LookAt_FacesWorldPoint()
135
+ {
136
+ var go = Spawn(PrimitiveType.Cube, "Looker", Vector3.zero);
137
+ _router.Dispatch("transform/look-at", 1,
138
+ "{\"instanceId\":" + go.GetId() + ",\"target\":[10,0,0]}");
139
+ // forward should point along +X
140
+ Assert.That(Vector3.Dot(go.transform.forward, Vector3.right), Is.GreaterThan(0.99f));
141
+ }
142
+
143
+ [Test]
144
+ public void Transform_Get_BulkReadByIds()
145
+ {
146
+ var a = Spawn(PrimitiveType.Cube, "A", new Vector3(1, 0, 0));
147
+ var b = Spawn(PrimitiveType.Cube, "B", new Vector3(2, 0, 0));
148
+ var res = Result(_router.Dispatch("transform/get", 1,
149
+ "{\"ids\":[" + a.GetId() + "," + b.GetId() + "]}"));
150
+ Assert.That(Convert.ToInt32(res["count"]), Is.EqualTo(2));
151
+ }
152
+
153
+ // --- ObjectLocator -------------------------------------------------
154
+
155
+ [Test]
156
+ public void Locator_ResolvesByNameAndPath()
157
+ {
158
+ var root = Spawn(PrimitiveType.Cube, "Root", Vector3.zero);
159
+ var child = GameObject.CreatePrimitive(PrimitiveType.Cube);
160
+ child.name = "Child";
161
+ child.transform.SetParent(root.transform, false);
162
+ _spawned.Add(child);
163
+
164
+ _router.Dispatch("transform/move", 1, "{\"name\":\"Child\",\"position\":[0,4,0]}");
165
+ Assert.That(child.transform.position.y, Is.EqualTo(4f).Within(0.001f));
166
+
167
+ _router.Dispatch("transform/move", 1, "{\"path\":\"Root/Child\",\"position\":[0,7,0]}");
168
+ Assert.That(child.transform.position.y, Is.EqualTo(7f).Within(0.001f));
169
+ }
170
+
171
+ // --- Spatial -------------------------------------------------------
172
+
173
+ [Test]
174
+ public void Spatial_Raycast_HitsColliderBelow()
175
+ {
176
+ var ground = Spawn(PrimitiveType.Cube, "Ground", Vector3.zero);
177
+ ground.transform.localScale = new Vector3(20, 1, 20);
178
+
179
+ var res = Result(_router.Dispatch("physics/raycast", 1,
180
+ "{\"origin\":[0,5,0],\"direction\":[0,-1,0]}"));
181
+ Assert.That(Convert.ToBoolean(res["hit"]), Is.True);
182
+ Assert.That(Convert.ToInt32(res["instanceId"]), Is.EqualTo(ground.GetId()));
183
+ }
184
+
185
+ [Test]
186
+ public void Spatial_Overlap_FindsSphereOverlap()
187
+ {
188
+ var box = Spawn(PrimitiveType.Cube, "Box", Vector3.zero);
189
+ var res = Result(_router.Dispatch("physics/overlap", 1,
190
+ "{\"shape\":\"sphere\",\"center\":[0,0,0],\"radius\":2}"));
191
+ Assert.That(Convert.ToInt32(res["count"]), Is.GreaterThanOrEqualTo(1));
192
+ _ = box;
193
+ }
194
+
195
+ [Test]
196
+ public void Spatial_Bounds_ReturnsWorldAabb()
197
+ {
198
+ var go = Spawn(PrimitiveType.Cube, "Bounded", new Vector3(3, 0, 0));
199
+ var res = Result(_router.Dispatch("object/bounds", 1, "{\"instanceId\":" + go.GetId() + "}"));
200
+ Assert.That(F(Vec(res, "center")[0]), Is.EqualTo(3f).Within(0.01f));
201
+ Assert.That(Convert.ToBoolean(res["empty"]), Is.False);
202
+ }
203
+
204
+ [Test]
205
+ public void Spatial_Ground_DropsObjectOntoSurface()
206
+ {
207
+ var ground = Spawn(PrimitiveType.Cube, "Floor", Vector3.zero);
208
+ ground.transform.localScale = new Vector3(20, 1, 20); // top surface at y=0.5
209
+ var cube = Spawn(PrimitiveType.Cube, "Falling", new Vector3(0, 8, 0));
210
+
211
+ var res = Result(_router.Dispatch("spatial/ground", 1,
212
+ "{\"instanceId\":" + cube.GetId() + ",\"apply\":true}"));
213
+ Assert.That(Convert.ToBoolean(res["hit"]), Is.True);
214
+ // Cube (half-height 0.5) should rest with its centre at surface(0.5) + 0.5 = 1.0
215
+ Assert.That(cube.transform.position.y, Is.EqualTo(1f).Within(0.05f));
216
+ }
217
+
218
+ [Test]
219
+ public void Spatial_Nearest_SortsByDistance()
220
+ {
221
+ Spawn(PrimitiveType.Cube, "Near", new Vector3(1, 0, 0));
222
+ Spawn(PrimitiveType.Cube, "Mid", new Vector3(5, 0, 0));
223
+ Spawn(PrimitiveType.Cube, "Far", new Vector3(20, 0, 0));
224
+
225
+ var res = Result(_router.Dispatch("spatial/nearest", 1,
226
+ "{\"point\":[0,0,0],\"max\":2}"));
227
+ var objects = (List<object>)res["objects"];
228
+ Assert.That(objects.Count, Is.EqualTo(2));
229
+ var first = (Dictionary<string, object>)objects[0];
230
+ Assert.That(first["name"].ToString(), Is.EqualTo("Near"));
231
+ }
232
+
233
+ // --- View ----------------------------------------------------------
234
+
235
+ [Test]
236
+ public void View_Isolate_ProducesPngForSingleView()
237
+ {
238
+ if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Null)
239
+ Assert.Ignore("No graphics device (headless -nographics); skipping render test.");
240
+
241
+ var go = Spawn(PrimitiveType.Cube, "Hero", Vector3.zero);
242
+ var res = Result(_router.Dispatch("view/isolate", 1,
243
+ "{\"instanceId\":" + go.GetId() + ",\"views\":[\"front\"],\"maxEdge\":128}"));
244
+
245
+ Assert.That(res["encoding"].ToString(), Is.EqualTo("base64"));
246
+ Assert.That(res["data"].ToString().Length, Is.GreaterThan(0));
247
+ Assert.That(Convert.ToInt32(res["width"]), Is.EqualTo(128));
248
+ // Isolation must restore the object's original layer.
249
+ Assert.That(go.layer, Is.EqualTo(0));
250
+ }
251
+ }
252
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 7a8b9c0d1e2f3041a2b3c4d5e6f70819
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.ucp.bridge",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "displayName": "Unity Control Protocol Bridge",
5
5
  "description": "WebSocket bridge for programmatic Unity Editor control via CLI and AI agents.",
6
6
  "unity": "2021.3",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mflrevan/ucp",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Unity Control Protocol - CLI for programmatic Unity Editor control",
5
5
  "license": "MIT",
6
6
  "repository": {