@viamrobotics/motion-tools 1.16.0 → 1.18.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 (77) hide show
  1. package/dist/attribute.d.ts +3 -2
  2. package/dist/attribute.js +24 -16
  3. package/dist/buf/draw/v1/drawing_pb.d.ts +33 -16
  4. package/dist/buf/draw/v1/drawing_pb.js +35 -17
  5. package/dist/buf/draw/v1/metadata_pb.d.ts +44 -3
  6. package/dist/buf/draw/v1/metadata_pb.js +54 -3
  7. package/dist/buf/draw/v1/scene_pb.d.ts +6 -6
  8. package/dist/buf/draw/v1/scene_pb.js +7 -7
  9. package/dist/buffer.d.ts +54 -45
  10. package/dist/buffer.js +91 -57
  11. package/dist/color.d.ts +1 -2
  12. package/dist/color.js +5 -12
  13. package/dist/components/App.svelte +18 -3
  14. package/dist/components/App.svelte.d.ts +15 -2
  15. package/dist/components/Entities/Arrows/ArrowGroups.svelte +5 -6
  16. package/dist/components/Entities/Arrows/Arrows.svelte +9 -0
  17. package/dist/components/Entities/Entities.svelte +18 -1
  18. package/dist/components/Entities/Frame.svelte +7 -1
  19. package/dist/components/Entities/GLTF.svelte +13 -2
  20. package/dist/components/Entities/Line.svelte +46 -18
  21. package/dist/components/Entities/LineDots.svelte +38 -8
  22. package/dist/components/Entities/LineDots.svelte.d.ts +2 -2
  23. package/dist/components/Entities/LineGeometry.svelte +2 -1
  24. package/dist/components/Entities/LineGeometry.svelte.d.ts +2 -0
  25. package/dist/components/Entities/Mesh.svelte +8 -1
  26. package/dist/components/Entities/Points.svelte +22 -11
  27. package/dist/components/Entities/hooks/useEntityEvents.svelte.js +6 -2
  28. package/dist/components/FileDrop/FileDrop.svelte +5 -1
  29. package/dist/components/KeyboardControls.svelte +2 -10
  30. package/dist/components/PCD.svelte +11 -4
  31. package/dist/components/PCD.svelte.d.ts +3 -1
  32. package/dist/components/SceneProviders.svelte +2 -0
  33. package/dist/components/Selected.svelte +2 -12
  34. package/dist/components/Selection/Ellipse.svelte +1 -0
  35. package/dist/components/Selection/Lasso.svelte +2 -0
  36. package/dist/components/Selection/Tool.svelte +7 -56
  37. package/dist/components/Selection/Tool.svelte.d.ts +2 -2
  38. package/dist/components/Selection/useSelectionPlugin.svelte.d.ts +8 -0
  39. package/dist/components/Selection/useSelectionPlugin.svelte.js +24 -0
  40. package/dist/components/Snapshot.svelte +4 -2
  41. package/dist/components/overlay/AddRelationship.svelte +1 -2
  42. package/dist/components/overlay/AddRelationship.svelte.d.ts +1 -1
  43. package/dist/components/overlay/Details.svelte +12 -12
  44. package/dist/components/overlay/Details.svelte.d.ts +8 -1
  45. package/dist/components/overlay/settings/Settings.svelte +8 -1
  46. package/dist/components/xr/XR.svelte +1 -1
  47. package/dist/draw.d.ts +13 -0
  48. package/dist/draw.js +428 -0
  49. package/dist/ecs/traits.d.ts +31 -13
  50. package/dist/ecs/traits.js +25 -8
  51. package/dist/geometry.js +3 -0
  52. package/dist/hooks/useDrawAPI.svelte.js +61 -24
  53. package/dist/hooks/useDrawService.svelte.d.ts +12 -0
  54. package/dist/hooks/useDrawService.svelte.js +240 -0
  55. package/dist/hooks/usePointcloudObjects.svelte.js +7 -2
  56. package/dist/hooks/usePointclouds.svelte.js +7 -2
  57. package/dist/hooks/useSettings.svelte.d.ts +2 -1
  58. package/dist/hooks/useSettings.svelte.js +1 -1
  59. package/dist/hooks/useWorldState.svelte.js +5 -52
  60. package/dist/index.d.ts +8 -0
  61. package/dist/index.js +9 -0
  62. package/dist/lib.d.ts +2 -0
  63. package/dist/lib.js +2 -0
  64. package/dist/loaders/pcd/index.d.ts +1 -1
  65. package/dist/loaders/pcd/messages.d.ts +2 -2
  66. package/dist/loaders/pcd/worker.inline.d.ts +1 -1
  67. package/dist/loaders/pcd/worker.inline.js +229 -187
  68. package/dist/loaders/pcd/worker.js +2 -2
  69. package/dist/metadata.d.ts +9 -15
  70. package/dist/metadata.js +45 -9
  71. package/dist/plugins/bvh.svelte.js +6 -2
  72. package/dist/snapshot.d.ts +3 -9
  73. package/dist/snapshot.js +11 -204
  74. package/dist/three/InstancedArrows/InstancedArrows.js +3 -2
  75. package/package.json +14 -11
  76. package/dist/components/xr/Hands.svelte +0 -23
  77. package/dist/components/xr/Hands.svelte.d.ts +0 -18
@@ -54,7 +54,12 @@ export const Material = trait({
54
54
  export const DepthTest = trait(() => true);
55
55
  export const Arrow = trait(() => true);
56
56
  export const Positions = trait(() => new Float32Array());
57
+ /** Per-vertex RGB colors packed as [r, g, b, ...], stride of 3, values 0-255. */
57
58
  export const Colors = trait(() => new Uint8Array());
59
+ /**
60
+ * Per-vertex opacity values packed as uint8 (0-255).
61
+ */
62
+ export const Opacities = trait(() => new Uint8Array());
58
63
  export const Instances = trait({
59
64
  count: 0,
60
65
  });
@@ -77,12 +82,7 @@ export const Capsule = trait({ l: 200, r: 50 });
77
82
  * A sphere, in mm
78
83
  */
79
84
  export const Sphere = trait({ r: 200 });
80
- export const PointColor = trait({ r: 0, g: 0, b: 0 });
81
- /** format [x, y, z, ...] */
82
- export const LinePositions = trait(() => new Float32Array());
83
85
  export const BufferGeometry = trait(() => new ThreeBufferGeometry());
84
- /** format [r, g, b, ...] */
85
- export const VertexColors = trait(() => new Float32Array());
86
86
  export const GLTF = trait(() => ({
87
87
  source: { url: '' },
88
88
  animationName: '',
@@ -91,6 +91,7 @@ export const Scale = trait({ x: 1, y: 1, z: 1 });
91
91
  export const FramesAPI = trait(() => true);
92
92
  export const GeometriesAPI = trait(() => true);
93
93
  export const DrawAPI = trait(() => true);
94
+ export const DrawServiceAPI = trait(() => true);
94
95
  export const WorldStateStoreAPI = trait(() => true);
95
96
  export const SnapshotAPI = trait(() => true);
96
97
  /**
@@ -98,16 +99,32 @@ export const SnapshotAPI = trait(() => true);
98
99
  */
99
100
  export const DroppedFile = trait(() => true);
100
101
  export const ShowAxesHelper = trait(() => true);
101
- // === Shape Properties ===
102
+ /**
103
+ * Marker trait for entities that should be rendered in screen space (CSS pixels)
104
+ */
105
+ export const ScreenSpace = trait(() => true);
102
106
  /**
103
107
  * Point size, in mm
104
108
  */
105
- export const PointSize = trait(() => 10);
109
+ export const PointSize = trait(() => 5);
110
+ /**
111
+ * Line positions, format [x, y, z, ...]
112
+ */
113
+ export const LinePositions = trait(() => new Float32Array());
106
114
  /**
107
- * Line width, in mm
115
+ * Line width, in mm when in world units, or CSS pixels when in screen space
108
116
  */
109
117
  export const LineWidth = trait(() => 5);
118
+ /**
119
+ * Dot colors for line vertices, format [r, g, b, a, ...]
120
+ */
121
+ export const DotColors = trait(() => new Uint8Array());
122
+ /**
123
+ * Dot size for line vertices, in mm when in world units, or CSS pixels when in screen space
124
+ */
125
+ export const DotSize = trait(() => 10);
110
126
  export const ReferenceFrame = trait(() => true);
127
+ export const SelectToolInteractionLayer = trait(() => true);
111
128
  /**
112
129
  * This entity can be safetly removed from the scene by the user
113
130
  */
package/dist/geometry.js CHANGED
@@ -58,3 +58,6 @@ export const createSphere = (sphere) => {
58
58
  r: sphere?.radiusMm ?? 0,
59
59
  };
60
60
  };
61
+ export const isPointCloud = (geometry) => {
62
+ return geometry?.case === 'pointcloud';
63
+ };
@@ -6,7 +6,8 @@ import { NURBSCurve } from 'three/addons/curves/NURBSCurve.js';
6
6
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
7
7
  import { UuidTool } from 'uuid-tool';
8
8
  import { createBufferGeometry, updateBufferGeometry } from '../attribute';
9
- import { STRIDE } from '../buffer';
9
+ import { ColorFormat } from '../buf/draw/v1/metadata_pb';
10
+ import { asRGB, STRIDE } from '../buffer';
10
11
  import { traits, useWorld } from '../ecs';
11
12
  import { createBox, createCapsule, createSphere } from '../geometry';
12
13
  import { parsePlyInput } from '../ply';
@@ -15,6 +16,7 @@ import { useCameraControls } from './useControls.svelte';
15
16
  import { useDrawConnectionConfig } from './useDrawConnectionConfig.svelte';
16
17
  import { useLogs } from './useLogs.svelte';
17
18
  const colorUtil = new Color();
19
+ const rgb = { r: 0, g: 0, b: 0 };
18
20
  const bufferTypes = {
19
21
  DRAW_POINTS: 0,
20
22
  DRAW_POSES: 1,
@@ -215,9 +217,9 @@ export const provideDrawAPI = () => {
215
217
  const nPoints = reader.read();
216
218
  const nColors = reader.read();
217
219
  const arrowHeadAtPose = reader.read();
218
- const entities = [];
219
- const entity = world.spawn(traits.Name(`Arrow group ${++poseIndex}`), traits.Positions(reader.readF32Array(nPoints * STRIDE.ARROWS)), traits.Colors(reader.readU8Array(nColors * STRIDE.COLORS_RGB)), traits.Arrows({ headAtPose: arrowHeadAtPose === 1 }), traits.DrawAPI, traits.Removable);
220
- entities.push(entity);
220
+ const positions = reader.readF32Array(nPoints * STRIDE.ARROWS);
221
+ const rawColors = reader.readU8Array(nColors * STRIDE.COLORS_RGB);
222
+ world.spawn(traits.Name(`Arrow group ${++poseIndex}`), traits.Positions(positions), nColors === 1 ? traits.Color(asRGB(rawColors, rgb)) : traits.Colors(rawColors), traits.Arrows({ headAtPose: arrowHeadAtPose === 1 }), traits.DrawAPI, traits.Removable);
221
223
  };
222
224
  const drawPoints = async (reader) => {
223
225
  // Read label length
@@ -230,41 +232,66 @@ export const provideDrawAPI = () => {
230
232
  const nPoints = reader.readU32();
231
233
  const nColors = reader.readU32();
232
234
  // Read default color
233
- let r = reader.read();
234
- let g = reader.read();
235
- let b = reader.read();
235
+ const r = reader.read();
236
+ const g = reader.read();
237
+ const b = reader.read();
238
+ // Normalize to uint8 immediately so the rest of the function works in a single color space.
239
+ const defaultColor = r > -1
240
+ ? new Uint8Array([Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)])
241
+ : undefined;
236
242
  const nPointsElements = nPoints * 3;
237
243
  const positions = reader.readF32Array(nPointsElements);
238
244
  const nColorsElements = nColors * 3;
239
245
  const rawColors = reader.readU8Array(nColorsElements);
240
- let colors = null;
241
- if (nColors > 1) {
242
- colors = new Uint8Array(nPointsElements);
243
- colors.set(rawColors);
244
- // Cover the gap for any points not colored
245
- for (let i = nColors; i < nPoints; i++) {
246
- const offset = i * 3;
247
- colors[offset] = Math.round(r * 255);
248
- colors[offset + 1] = Math.round(g * 255);
249
- colors[offset + 2] = Math.round(b * 255);
246
+ let vertexColors = undefined;
247
+ let uniformColor = undefined;
248
+ if (nColors > 1 && nColors >= nPoints) {
249
+ vertexColors = rawColors;
250
+ }
251
+ else if (nColors > 1) {
252
+ vertexColors = new Uint8Array(nPointsElements);
253
+ vertexColors.set(rawColors);
254
+ if (defaultColor) {
255
+ for (let i = nColors; i < nPoints; i++) {
256
+ const offset = i * STRIDE.COLORS_RGB;
257
+ vertexColors[offset] = defaultColor[0];
258
+ vertexColors[offset + 1] = defaultColor[1];
259
+ vertexColors[offset + 2] = defaultColor[2];
260
+ }
250
261
  }
251
262
  }
252
263
  else if (nColors === 1) {
253
- r = rawColors[0] / 255;
254
- g = rawColors[1] / 255;
255
- b = rawColors[2] / 255;
264
+ uniformColor = new Uint8Array([rawColors[0], rawColors[1], rawColors[2]]);
265
+ }
266
+ else {
267
+ uniformColor = defaultColor;
256
268
  }
257
269
  const entities = world.query(traits.DrawAPI);
258
270
  const entity = entities.find((entity) => entity.get(traits.Name) === label);
259
271
  if (entity) {
260
272
  const geometry = entity.get(traits.BufferGeometry);
261
273
  if (geometry) {
262
- updateBufferGeometry(geometry, positions, colors);
274
+ updateBufferGeometry(geometry, positions, {
275
+ colors: vertexColors,
276
+ colorFormat: ColorFormat.RGB,
277
+ });
263
278
  return;
264
279
  }
265
280
  }
266
- const geometry = createBufferGeometry(positions, colors);
267
- world.spawn(traits.Name(label), traits.Color(colorUtil.set(r, g, b)), traits.BufferGeometry(geometry), traits.Points, traits.DrawAPI, traits.Removable);
281
+ const geometry = createBufferGeometry(positions, {
282
+ colors: vertexColors,
283
+ colorFormat: ColorFormat.RGB,
284
+ });
285
+ const spawnTraits = [
286
+ traits.Name(label),
287
+ traits.BufferGeometry(geometry),
288
+ traits.Points,
289
+ traits.DrawAPI,
290
+ traits.Removable,
291
+ ];
292
+ if (uniformColor)
293
+ spawnTraits.push(traits.Color(asRGB(uniformColor, rgb)));
294
+ world.spawn(...spawnTraits);
268
295
  };
269
296
  const drawLine = async (reader) => {
270
297
  // Read label length
@@ -292,7 +319,17 @@ export const provideDrawAPI = () => {
292
319
  points[i + 1] = reader.read();
293
320
  points[i + 2] = reader.read();
294
321
  }
295
- world.spawn(traits.Name(label), traits.Color({ r, g, b }), traits.LinePositions(points), traits.PointColor({ r: dotR, g: dotG, b: dotB }), traits.DrawAPI, traits.Removable);
322
+ const lineColors = new Uint8Array([
323
+ Math.round(r * 255),
324
+ Math.round(g * 255),
325
+ Math.round(b * 255),
326
+ ]);
327
+ const dotColors = new Uint8Array([
328
+ Math.round(dotR * 255),
329
+ Math.round(dotG * 255),
330
+ Math.round(dotB * 255),
331
+ ]);
332
+ world.spawn(traits.Name(label), traits.Color(asRGB(lineColors, rgb)), traits.Opacity(1), traits.LinePositions(points), traits.DotColors(dotColors), traits.DrawAPI, traits.Removable);
296
333
  };
297
334
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
298
335
  const drawGeometries = (geometries, colors, parent) => {
@@ -0,0 +1,12 @@
1
+ declare const ConnectionStatus: {
2
+ readonly CONNECTED: "connected";
3
+ readonly DISCONNECTED: "disconnected";
4
+ readonly CONNECTING: "connecting";
5
+ };
6
+ type ConnectionStatusType = (typeof ConnectionStatus)[keyof typeof ConnectionStatus];
7
+ interface Context {
8
+ connectionStatus: ConnectionStatusType;
9
+ }
10
+ export declare function provideDrawService(): void;
11
+ export declare function useDrawService(): Context;
12
+ export {};
@@ -0,0 +1,240 @@
1
+ import { FieldMask } from '@bufbuild/protobuf';
2
+ import { createClient } from '@connectrpc/connect';
3
+ import { createConnectTransport } from '@connectrpc/connect-web';
4
+ import { useThrelte } from '@threlte/core';
5
+ import {} from 'koota';
6
+ import { getContext, setContext } from 'svelte';
7
+ import { UuidTool } from 'uuid-tool';
8
+ import { DrawService } from '../buf/draw/v1/service_connect';
9
+ import { EntityChangeType, StreamEntityChangesResponse } from '../buf/draw/v1/service_pb';
10
+ import { drawDrawing, drawTransform, updateDrawing, updateTransform, } from '../draw';
11
+ import { traits, useWorld } from '../ecs';
12
+ import { useCameraControls } from './useControls.svelte';
13
+ import { useDrawConnectionConfig } from './useDrawConnectionConfig.svelte';
14
+ const DRAW_SERVICE_KEY = Symbol('draw-service-context');
15
+ const ConnectionStatus = {
16
+ CONNECTED: 'connected',
17
+ DISCONNECTED: 'disconnected',
18
+ CONNECTING: 'connecting',
19
+ };
20
+ export function provideDrawService() {
21
+ const { invalidate } = useThrelte();
22
+ const world = useWorld();
23
+ const cameraControls = useCameraControls();
24
+ const drawConnectionConfig = useDrawConnectionConfig();
25
+ let connectionStatus = $state(ConnectionStatus.DISCONNECTED);
26
+ const url = $derived(drawConnectionConfig.current?.backendIP
27
+ ? `http://${drawConnectionConfig.current.backendIP}:3030`
28
+ : undefined);
29
+ const transformEntities = new Map();
30
+ const drawingEntities = new Map();
31
+ let pendingEvents = [];
32
+ let flushScheduled = false;
33
+ const destroyTransform = (uuidStr) => {
34
+ const entity = transformEntities.get(uuidStr);
35
+ if (!entity)
36
+ return;
37
+ if (world.has(entity))
38
+ entity.destroy();
39
+ transformEntities.delete(uuidStr);
40
+ };
41
+ const destroyDrawing = (uuidStr) => {
42
+ const entities = drawingEntities.get(uuidStr);
43
+ if (!entities)
44
+ return;
45
+ for (const entity of entities) {
46
+ if (world.has(entity))
47
+ entity.destroy();
48
+ }
49
+ drawingEntities.delete(uuidStr);
50
+ };
51
+ const processEvent = (event) => {
52
+ const { changeType, entity, uuid } = event;
53
+ if (entity.case === 'transform') {
54
+ processTransformEvent(entity.value, changeType, uuid);
55
+ }
56
+ else if (entity.case === 'drawing') {
57
+ processDrawingEvent(entity.value, changeType, uuid);
58
+ }
59
+ invalidate();
60
+ };
61
+ const processTransformEvent = (transform, changeType, uuid) => {
62
+ if (changeType === EntityChangeType.ADDED) {
63
+ if (!transformEntities.has(uuid)) {
64
+ const spawned = drawTransform(world, transform, traits.DrawServiceAPI);
65
+ transformEntities.set(uuid, spawned);
66
+ }
67
+ }
68
+ else if (changeType === EntityChangeType.REMOVED) {
69
+ destroyTransform(uuid);
70
+ }
71
+ else if (changeType === EntityChangeType.UPDATED) {
72
+ const existing = transformEntities.get(uuid);
73
+ if (existing) {
74
+ updateTransform(existing, transform);
75
+ }
76
+ else {
77
+ const spawned = drawTransform(world, transform, traits.DrawServiceAPI);
78
+ transformEntities.set(uuid, spawned);
79
+ }
80
+ }
81
+ };
82
+ const processDrawingEvent = (drawing, changeType, uuid) => {
83
+ if (changeType === EntityChangeType.ADDED) {
84
+ if (!drawingEntities.has(uuid)) {
85
+ const spawned = drawDrawing(world, drawing, traits.DrawServiceAPI);
86
+ drawingEntities.set(uuid, spawned);
87
+ }
88
+ }
89
+ else if (changeType === EntityChangeType.REMOVED) {
90
+ destroyDrawing(uuid);
91
+ }
92
+ else if (changeType === EntityChangeType.UPDATED) {
93
+ const existing = drawingEntities.get(uuid);
94
+ if (existing) {
95
+ const next = updateDrawing(world, existing, drawing, traits.DrawServiceAPI);
96
+ drawingEntities.set(uuid, next);
97
+ }
98
+ else {
99
+ const spawned = drawDrawing(world, drawing, traits.DrawServiceAPI);
100
+ drawingEntities.set(uuid, spawned);
101
+ }
102
+ }
103
+ };
104
+ const applyEvents = (events) => {
105
+ const eventsByUUID = new Map();
106
+ for (const event of events) {
107
+ const existing = eventsByUUID.get(event.uuid);
108
+ if (!existing) {
109
+ eventsByUUID.set(event.uuid, event);
110
+ continue;
111
+ }
112
+ switch (event.changeType) {
113
+ case EntityChangeType.REMOVED: {
114
+ eventsByUUID.set(event.uuid, event);
115
+ break;
116
+ }
117
+ case EntityChangeType.ADDED: {
118
+ if (existing.changeType !== EntityChangeType.REMOVED) {
119
+ eventsByUUID.set(event.uuid, event);
120
+ }
121
+ break;
122
+ }
123
+ case EntityChangeType.UPDATED: {
124
+ if (existing.changeType === EntityChangeType.ADDED) {
125
+ existing.entity = event.entity;
126
+ }
127
+ else if (existing.changeType === EntityChangeType.UPDATED) {
128
+ existing.updatedFields ??= new FieldMask();
129
+ const paths = event.updatedFields?.paths ?? [];
130
+ for (const path of paths) {
131
+ if (!existing.updatedFields.paths.includes(path)) {
132
+ existing.updatedFields.paths.push(path);
133
+ }
134
+ }
135
+ existing.entity = event.entity;
136
+ }
137
+ else {
138
+ eventsByUUID.set(event.uuid, event);
139
+ }
140
+ break;
141
+ }
142
+ }
143
+ }
144
+ for (const event of eventsByUUID.values()) {
145
+ processEvent(event);
146
+ }
147
+ };
148
+ const scheduleFlush = () => {
149
+ if (flushScheduled)
150
+ return;
151
+ flushScheduled = true;
152
+ requestAnimationFrame(() => {
153
+ flushScheduled = false;
154
+ const toApply = pendingEvents;
155
+ pendingEvents = [];
156
+ applyEvents(toApply);
157
+ });
158
+ };
159
+ const streamEntityChanges = async (client, signal) => {
160
+ try {
161
+ for await (const response of client.streamEntityChanges({}, { signal })) {
162
+ connectionStatus = ConnectionStatus.CONNECTED;
163
+ const { entity } = response;
164
+ if (!entity.case)
165
+ continue;
166
+ const uuid = UuidTool.toString([...(entity.value.uuid ?? [])]);
167
+ pendingEvents.push({
168
+ uuid,
169
+ changeType: response.changeType,
170
+ entity,
171
+ updatedFields: response.updatedFields,
172
+ });
173
+ scheduleFlush();
174
+ }
175
+ }
176
+ catch (error) {
177
+ if (!signal.aborted) {
178
+ console.error('Draw service entity stream error:', error);
179
+ connectionStatus = ConnectionStatus.DISCONNECTED;
180
+ }
181
+ }
182
+ };
183
+ const streamSceneChanges = async (client, signal) => {
184
+ try {
185
+ for await (const response of client.streamSceneChanges({}, { signal })) {
186
+ const { sceneMetadata } = response;
187
+ if (!sceneMetadata)
188
+ continue;
189
+ if (sceneMetadata.sceneCamera?.position && sceneMetadata.sceneCamera?.lookAt) {
190
+ const { position, lookAt, animated } = sceneMetadata.sceneCamera;
191
+ cameraControls.setPose({
192
+ position: [position.x * 0.001, position.y * 0.001, position.z * 0.001],
193
+ lookAt: [lookAt.x * 0.001, lookAt.y * 0.001, lookAt.z * 0.001],
194
+ }, animated ?? false);
195
+ }
196
+ }
197
+ }
198
+ catch (error) {
199
+ if (!signal.aborted) {
200
+ console.error('Draw service scene stream error:', error);
201
+ }
202
+ }
203
+ };
204
+ $effect(() => {
205
+ if (!url) {
206
+ connectionStatus = ConnectionStatus.DISCONNECTED;
207
+ return;
208
+ }
209
+ const controller = new AbortController();
210
+ connectionStatus = ConnectionStatus.CONNECTING;
211
+ const transport = createConnectTransport({ baseUrl: url });
212
+ const client = createClient(DrawService, transport);
213
+ void streamEntityChanges(client, controller.signal);
214
+ void streamSceneChanges(client, controller.signal);
215
+ return () => {
216
+ controller.abort();
217
+ connectionStatus = ConnectionStatus.DISCONNECTED;
218
+ for (const entity of transformEntities.values()) {
219
+ if (world.has(entity))
220
+ entity.destroy();
221
+ }
222
+ transformEntities.clear();
223
+ for (const entities of drawingEntities.values()) {
224
+ for (const entity of entities) {
225
+ if (world.has(entity))
226
+ entity.destroy();
227
+ }
228
+ }
229
+ drawingEntities.clear();
230
+ };
231
+ });
232
+ setContext(DRAW_SERVICE_KEY, {
233
+ get connectionStatus() {
234
+ return connectionStatus;
235
+ },
236
+ });
237
+ }
238
+ export function useDrawService() {
239
+ return getContext(DRAW_SERVICE_KEY);
240
+ }
@@ -2,6 +2,7 @@ import { VisionClient } from '@viamrobotics/sdk';
2
2
  import { createResourceClient, createResourceQuery, useResourceNames, } from '@viamrobotics/svelte-sdk';
3
3
  import { getContext, setContext, untrack } from 'svelte';
4
4
  import { createBufferGeometry, updateBufferGeometry } from '../attribute';
5
+ import { ColorFormat } from '../buf/draw/v1/metadata_pb';
5
6
  import { RefetchRates } from '../components/overlay/RefreshRate.svelte';
6
7
  import { traits, useWorld } from '../ecs';
7
8
  import { updateGeometryTrait } from '../ecs/traits';
@@ -129,14 +130,18 @@ export const providePointcloudObjects = (partID) => {
129
130
  return;
130
131
  }
131
132
  const existing = entities.get(pointcloudLabel);
133
+ const metadata = {
134
+ colors,
135
+ colorFormat: ColorFormat.RGB,
136
+ };
132
137
  if (existing) {
133
138
  const geometry = existing.get(traits.BufferGeometry);
134
139
  if (geometry) {
135
- updateBufferGeometry(geometry, positions, colors);
140
+ updateBufferGeometry(geometry, positions, metadata);
136
141
  }
137
142
  }
138
143
  else {
139
- const geometry = createBufferGeometry(positions, colors);
144
+ const geometry = createBufferGeometry(positions, metadata);
140
145
  const entity = world.spawn(traits.Name(pointcloudLabel), traits.BufferGeometry(geometry), traits.Points);
141
146
  entities.set(pointcloudLabel, entity);
142
147
  }
@@ -2,6 +2,7 @@ import { CameraClient } from '@viamrobotics/sdk';
2
2
  import { createResourceClient, createResourceQuery, useResourceNames, } from '@viamrobotics/svelte-sdk';
3
3
  import { getContext, setContext, untrack } from 'svelte';
4
4
  import { createBufferGeometry, updateBufferGeometry } from '../attribute';
5
+ import { ColorFormat } from '../buf/draw/v1/metadata_pb';
5
6
  import { RefetchRates } from '../components/overlay/RefreshRate.svelte';
6
7
  import { traits, useWorld } from '../ecs';
7
8
  import { parsePcdInWorker } from '../loaders/pcd';
@@ -102,14 +103,18 @@ export const providePointclouds = (partID) => {
102
103
  return;
103
104
  }
104
105
  const existing = entities.get(queryKey);
106
+ const metadata = {
107
+ colors,
108
+ colorFormat: ColorFormat.RGB,
109
+ };
105
110
  if (existing) {
106
111
  const geometry = existing.get(traits.BufferGeometry);
107
112
  if (geometry) {
108
- updateBufferGeometry(geometry, positions, colors);
113
+ updateBufferGeometry(geometry, positions, metadata);
109
114
  return;
110
115
  }
111
116
  }
112
- const geometry = createBufferGeometry(positions, colors);
117
+ const geometry = createBufferGeometry(positions, metadata);
113
118
  const entity = world.spawn(traits.Parent(name), traits.Name(`${name} pointcloud`), traits.BufferGeometry(geometry), traits.Points);
114
119
  entities.set(queryKey, entity);
115
120
  })
@@ -1,3 +1,4 @@
1
+ import type { ColorRepresentation } from 'three';
1
2
  export interface Settings {
2
3
  cameraMode: 'orthographic' | 'perspective';
3
4
  interactionMode: 'navigate' | 'measure' | 'select';
@@ -16,7 +17,7 @@ export interface Settings {
16
17
  gridSectionSize: number;
17
18
  gridFadeDistance: number;
18
19
  pointSize: number;
19
- pointColor: string;
20
+ pointColor: ColorRepresentation;
20
21
  lineWidth: number;
21
22
  lineDotSize: number;
22
23
  enableMeasureAxisX: boolean;
@@ -25,7 +25,7 @@ const defaults = () => ({
25
25
  pointSize: 0.01,
26
26
  pointColor: '#333333',
27
27
  lineWidth: 0.005,
28
- lineDotSize: 0.01,
28
+ lineDotSize: 0.005,
29
29
  interactionMode: 'navigate',
30
30
  enableMeasureAxisX: true,
31
31
  enableMeasureAxisY: true,
@@ -1,17 +1,13 @@
1
1
  import { useThrelte } from '@threlte/core';
2
2
  import { TransformChangeType, WorldStateStoreClient, } from '@viamrobotics/sdk';
3
3
  import { createResourceClient, createResourceQuery, createResourceStream, useResourceNames, } from '@viamrobotics/svelte-sdk';
4
- import { Color } from 'three';
5
- import { createBufferGeometry } from '../attribute';
6
- import { asColor, asOpacity, isPerVertexColors, STRIDE } from '../buffer';
4
+ import { drawTransform } from '../draw';
7
5
  import { traits, useWorld } from '../ecs';
8
6
  import { createBox, createCapsule, createSphere } from '../geometry';
9
- import { parsePcdInWorker } from '../loaders/pcd';
10
- import { parseMetadata } from '../metadata';
7
+ import { isPointCloud } from '../geometry';
11
8
  import { parsePlyInput } from '../ply';
12
9
  import { createPose } from '../transform';
13
10
  import { usePartID } from './usePartID.svelte';
14
- const colorUtil = new Color();
15
11
  export const provideWorldStates = () => {
16
12
  const partID = usePartID();
17
13
  const resourceNames = useResourceNames(() => partID.current, 'world_state_store');
@@ -36,53 +32,10 @@ const createWorldState = (client) => {
36
32
  if (entities.has(transform.uuidString)) {
37
33
  return;
38
34
  }
39
- const metadata = parseMetadata(transform.metadata?.fields);
40
- const pose = createPose(transform.poseInObserverFrame?.pose);
41
- const entityTraits = [];
42
- const parent = transform.poseInObserverFrame?.referenceFrame;
43
- if (parent && parent !== 'world') {
44
- entityTraits.push(traits.Parent(parent));
45
- }
46
- if (transform.physicalObject) {
47
- if (transform.physicalObject.geometryType.case === 'pointcloud') {
48
- const metadataColors = metadata.colors;
49
- parsePcdInWorker(new Uint8Array(transform.physicalObject.geometryType.value.pointCloud)).then((pointcloud) => {
50
- const entity = entities.get(transform.uuidString);
51
- if (!entity) {
52
- console.error('Entity not found to add pointcloud trait to', transform.uuidString);
53
- return;
54
- }
55
- const numPoints = pointcloud.positions.length / STRIDE.POSITIONS;
56
- const vertexColors = metadataColors && isPerVertexColors(metadataColors, numPoints)
57
- ? metadataColors
58
- : pointcloud.colors;
59
- const geometry = createBufferGeometry(pointcloud.positions, vertexColors);
60
- entity.add(traits.BufferGeometry(geometry));
61
- entity.add(traits.Points);
62
- if (metadataColors && !isPerVertexColors(metadataColors, numPoints)) {
63
- asColor(metadataColors, colorUtil);
64
- entity.add(traits.Color({ r: colorUtil.r, g: colorUtil.g, b: colorUtil.b }));
65
- if (metadataColors.length % STRIDE.COLORS_RGBA === 0) {
66
- entity.add(traits.Opacity(asOpacity(metadataColors)));
67
- }
68
- }
69
- invalidate();
70
- });
71
- }
72
- else {
73
- if (metadata.colors) {
74
- asColor(metadata.colors, colorUtil);
75
- entityTraits.push(traits.Color({ r: colorUtil.r, g: colorUtil.g, b: colorUtil.b }));
76
- if (metadata.colors.length % STRIDE.COLORS_RGBA === 0) {
77
- entityTraits.push(traits.Opacity(asOpacity(metadata.colors)));
78
- }
79
- }
80
- entityTraits.push(traits.Geometry(transform.physicalObject));
81
- }
82
- }
83
- entityTraits.push(traits.Name(transform.referenceFrame), traits.Pose(pose), traits.ShowAxesHelper, traits.WorldStateStoreAPI);
84
- const entity = world.spawn(...entityTraits);
35
+ const entity = drawTransform(world, transform, traits.WorldStateStoreAPI, { removable: false });
85
36
  entities.set(transform.uuidString, entity);
37
+ if (isPointCloud(transform.physicalObject?.geometryType))
38
+ invalidate();
86
39
  };
87
40
  const destroyEntity = (uuid) => {
88
41
  const entity = entities.get(uuid);
package/dist/index.d.ts CHANGED
@@ -1,3 +1,11 @@
1
1
  export { default as MotionTools } from './components/App.svelte';
2
2
  export { default as SelectionTool } from './components/Selection/Tool.svelte';
3
3
  export { default as PCD } from './components/PCD.svelte';
4
+ export * as relations from './ecs/relations';
5
+ export * as traits from './ecs/traits';
6
+ export * as selectionTraits from './components/Selection/traits';
7
+ export { useSelectionPlugin as useSelection } from './components/Selection/useSelectionPlugin.svelte';
8
+ export { default as FloatingPanel } from './components/overlay/FloatingPanel.svelte';
9
+ export { provideWorld, useWorld } from './ecs/useWorld';
10
+ export { useQuery } from './ecs/useQuery.svelte';
11
+ export { useTrait } from './ecs/useTrait.svelte';
package/dist/index.js CHANGED
@@ -2,3 +2,12 @@ export { default as MotionTools } from './components/App.svelte';
2
2
  // Plugins
3
3
  export { default as SelectionTool } from './components/Selection/Tool.svelte';
4
4
  export { default as PCD } from './components/PCD.svelte';
5
+ // ECS
6
+ export * as relations from './ecs/relations';
7
+ export * as traits from './ecs/traits';
8
+ export * as selectionTraits from './components/Selection/traits';
9
+ export { useSelectionPlugin as useSelection } from './components/Selection/useSelectionPlugin.svelte';
10
+ export { default as FloatingPanel } from './components/overlay/FloatingPanel.svelte';
11
+ export { provideWorld, useWorld } from './ecs/useWorld';
12
+ export { useQuery } from './ecs/useQuery.svelte';
13
+ export { useTrait } from './ecs/useTrait.svelte';
package/dist/lib.d.ts CHANGED
@@ -5,3 +5,5 @@ export { BatchedArrow } from './three/BatchedArrow';
5
5
  export { CapsuleGeometry } from './three/CapsuleGeometry';
6
6
  export { OrientationVector } from './three/OrientationVector';
7
7
  export { parsePcdInWorker } from './loaders/pcd';
8
+ export { createBinaryPCD } from './pcd';
9
+ export { metadataFromStruct } from './metadata';