@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.
- package/README.md +1 -1
- package/bridge/com.ucp.bridge/Editor/AssemblyInfo.cs +3 -0
- package/bridge/com.ucp.bridge/Editor/AssemblyInfo.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +10 -1
- package/bridge/com.ucp.bridge/Editor/Compatibility/UnityObjectCompat.cs +26 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs +2 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorModalGuard.cs +60 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorModalGuard.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs +56 -5
- package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs +2 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectLocator.cs +207 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectLocator.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs +1 -1
- package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs +1 -35
- package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs +3 -3
- package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs +1 -1
- package/bridge/com.ucp.bridge/Editor/Controllers/ReferenceController.cs +1 -1
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs +6 -6
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +2 -34
- package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +4 -4
- package/bridge/com.ucp.bridge/Editor/Controllers/SpatialController.cs +322 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SpatialController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/TransformController.cs +249 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/TransformController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ViewController.cs +415 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ViewController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +63 -7
- package/bridge/com.ucp.bridge/Tests/Editor/SpatialVisualControllerTests.cs +252 -0
- package/bridge/com.ucp.bridge/Tests/Editor/SpatialVisualControllerTests.cs.meta +2 -0
- package/bridge/com.ucp.bridge/package.json +1 -1
- 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
|
+
}
|