@viamrobotics/motion-tools 1.33.0 → 1.33.2
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/dist/components/Entities/Entities.svelte +18 -25
- package/dist/components/Entities/Entities.svelte.d.ts +2 -17
- package/dist/components/Entities/Label.svelte +79 -13
- package/dist/components/Entities/Label.svelte.d.ts +2 -1
- package/dist/components/Entities/Labels.svelte +36 -0
- package/dist/components/Entities/Labels.svelte.d.ts +3 -0
- package/dist/components/Entities/LineDots.svelte +8 -3
- package/dist/components/Entities/labelLayout/applyTeleports.d.ts +9 -0
- package/dist/components/Entities/labelLayout/applyTeleports.js +39 -0
- package/dist/components/Entities/labelLayout/buildNeighborhood.d.ts +8 -0
- package/dist/components/Entities/labelLayout/buildNeighborhood.js +26 -0
- package/dist/components/Entities/labelLayout/cameraHash.d.ts +8 -0
- package/dist/components/Entities/labelLayout/cameraHash.js +25 -0
- package/dist/components/Entities/labelLayout/cost.d.ts +44 -0
- package/dist/components/Entities/labelLayout/cost.js +126 -0
- package/dist/components/Entities/labelLayout/createLabelLayout.d.ts +27 -0
- package/dist/components/Entities/labelLayout/createLabelLayout.js +194 -0
- package/dist/components/Entities/labelLayout/geometry.d.ts +20 -0
- package/dist/components/Entities/labelLayout/geometry.js +151 -0
- package/dist/components/Entities/labelLayout/labelStore.svelte.d.ts +17 -0
- package/dist/components/Entities/labelLayout/labelStore.svelte.js +28 -0
- package/dist/components/Entities/labelLayout/measure.d.ts +13 -0
- package/dist/components/Entities/labelLayout/measure.js +42 -0
- package/dist/components/Entities/labelLayout/slots.d.ts +11 -0
- package/dist/components/Entities/labelLayout/slots.js +47 -0
- package/dist/components/Entities/labelLayout/solve.d.ts +11 -0
- package/dist/components/Entities/labelLayout/solve.js +93 -0
- package/dist/components/Entities/labelLayout/spatialHash.d.ts +15 -0
- package/dist/components/Entities/labelLayout/spatialHash.js +53 -0
- package/dist/components/Entities/labelLayout/types.d.ts +105 -0
- package/dist/components/Entities/labelLayout/types.js +19 -0
- package/dist/components/Entities/labelLayout/writeBack.d.ts +20 -0
- package/dist/components/Entities/labelLayout/writeBack.js +51 -0
- package/dist/components/Scene.svelte +2 -1
- package/dist/components/SelectedTransformControls.svelte +65 -47
- package/dist/components/overlay/Details.svelte +210 -226
- package/dist/components/overlay/Details.svelte.d.ts +1 -1
- package/dist/components/overlay/Popover.svelte +6 -4
- package/dist/components/overlay/Popover.svelte.d.ts +6 -2
- package/dist/components/overlay/dashboard/Button.svelte +7 -2
- package/dist/components/overlay/dashboard/Button.svelte.d.ts +2 -1
- package/dist/components/overlay/details/AxesHelperDetails.svelte +32 -0
- package/dist/components/overlay/details/AxesHelperDetails.svelte.d.ts +7 -0
- package/dist/components/overlay/details/ColorDetails.svelte +35 -0
- package/dist/components/overlay/details/ColorDetails.svelte.d.ts +7 -0
- package/dist/components/overlay/details/GeometryDetails.svelte +104 -0
- package/dist/components/overlay/details/GeometryDetails.svelte.d.ts +7 -0
- package/dist/components/overlay/details/LineDetails/LineDetails.svelte +196 -0
- package/dist/components/overlay/details/LineDetails/LineDetails.svelte.d.ts +7 -0
- package/dist/components/overlay/details/LineDetails/linePositions.d.ts +3 -0
- package/dist/components/overlay/details/LineDetails/linePositions.js +30 -0
- package/dist/components/overlay/details/OpacityDetails.svelte +44 -0
- package/dist/components/overlay/details/OpacityDetails.svelte.d.ts +7 -0
- package/dist/components/overlay/details/PoseDetails.svelte +189 -0
- package/dist/components/overlay/details/PoseDetails.svelte.d.ts +14 -0
- package/dist/ecs/traits.d.ts +1 -1
- package/dist/ecs/traits.js +1 -1
- package/dist/hooks/usePartConfig.svelte.js +8 -6
- package/dist/hooks/useWorldState.svelte.js +94 -69
- package/package.json +4 -2
package/dist/ecs/traits.d.ts
CHANGED
|
@@ -71,7 +71,7 @@ export declare const Invisible: import("koota").Trait<() => boolean>;
|
|
|
71
71
|
* `details-extensions` portal target (e.g. gizmo plugin entities) opt in by
|
|
72
72
|
* adding this trait.
|
|
73
73
|
*/
|
|
74
|
-
export declare const CustomDetails: import("koota").
|
|
74
|
+
export declare const CustomDetails: import("koota").TagTrait;
|
|
75
75
|
/**
|
|
76
76
|
* True when the entity itself, or any of its parents up the `ChildOf`
|
|
77
77
|
* chain, has `Invisible`. Maintained by `provideInheritedInvisible`;
|
package/dist/ecs/traits.js
CHANGED
|
@@ -68,7 +68,7 @@ export const Invisible = trait(() => true);
|
|
|
68
68
|
* `details-extensions` portal target (e.g. gizmo plugin entities) opt in by
|
|
69
69
|
* adding this trait.
|
|
70
70
|
*/
|
|
71
|
-
export const CustomDetails = trait(
|
|
71
|
+
export const CustomDetails = trait();
|
|
72
72
|
/**
|
|
73
73
|
* True when the entity itself, or any of its parents up the `ChildOf`
|
|
74
74
|
* chain, has `Invisible`. Maintained by `provideInheritedInvisible`;
|
|
@@ -105,12 +105,14 @@ export const providePartConfig = (partID, params) => {
|
|
|
105
105
|
y: pose.y ?? currentPose.y,
|
|
106
106
|
z: pose.z ?? currentPose.z,
|
|
107
107
|
};
|
|
108
|
-
component.frame.orientation
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
component.frame.orientation = {
|
|
109
|
+
type: 'ov_degrees',
|
|
110
|
+
value: {
|
|
111
|
+
x: pose.oX ?? currentPose.oX,
|
|
112
|
+
y: pose.oY ?? currentPose.oY,
|
|
113
|
+
z: pose.oZ ?? currentPose.oZ,
|
|
114
|
+
th: pose.theta ?? currentPose.theta,
|
|
115
|
+
},
|
|
114
116
|
};
|
|
115
117
|
if (geometry) {
|
|
116
118
|
if (geometry.type === 'none') {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useThrelte } from '@threlte/core';
|
|
2
2
|
import { Struct, TransformChangeType, WorldStateStoreClient, } from '@viamrobotics/sdk';
|
|
3
|
-
import { createResourceClient, createResourceQuery,
|
|
3
|
+
import { createResourceClient, createResourceQuery, useResourceNames, } from '@viamrobotics/svelte-sdk';
|
|
4
4
|
import { Matrix4 } from 'three';
|
|
5
5
|
import { asFloat32Array, inMeters } from '../buffer';
|
|
6
6
|
import { createChunkLoader } from '../chunking';
|
|
7
7
|
import { drawTransform, updateMetadata } from '../draw';
|
|
8
|
-
import { traits, useWorld } from '../ecs';
|
|
8
|
+
import { hierarchy, traits, useWorld } from '../ecs';
|
|
9
9
|
import { isPointCloud } from '../geometry';
|
|
10
10
|
import { metadataFromStruct } from '../metadata';
|
|
11
11
|
import { createPose, poseToMatrix } from '../transform';
|
|
@@ -27,6 +27,10 @@ export const provideWorldStates = () => {
|
|
|
27
27
|
};
|
|
28
28
|
});
|
|
29
29
|
};
|
|
30
|
+
// FieldMask paths are proto field names; spec-compliant backends emit
|
|
31
|
+
// snake_case (`pose_in_observer_frame`) while some emit camelCase. Normalize
|
|
32
|
+
// to camelCase so matching against the message's accessors is casing-agnostic.
|
|
33
|
+
const snakeToCamel = (path) => path.replaceAll(/_([a-z])/g, (_, char) => char.toUpperCase());
|
|
30
34
|
const decodeBase64 = (encoded) => {
|
|
31
35
|
const binary = atob(encoded);
|
|
32
36
|
const bytes = new Uint8Array(binary.length);
|
|
@@ -87,6 +91,11 @@ const createWorldState = (client) => {
|
|
|
87
91
|
const world = useWorld();
|
|
88
92
|
const relationships = useRelationships();
|
|
89
93
|
const entities = new Map();
|
|
94
|
+
// UUIDs the stream has removed; guards against a stale initial snapshot or a
|
|
95
|
+
// self-heal fetch re-creating an entity the server has already deleted.
|
|
96
|
+
const removedUUIDs = new Set();
|
|
97
|
+
// UUIDs with an in-flight self-heal `getTransform`, to dedupe concurrent fetches.
|
|
98
|
+
const pendingSpawns = new Set();
|
|
90
99
|
const chunkLoader = createChunkLoader({
|
|
91
100
|
world,
|
|
92
101
|
invalidate,
|
|
@@ -105,7 +114,7 @@ const createWorldState = (client) => {
|
|
|
105
114
|
},
|
|
106
115
|
});
|
|
107
116
|
const spawnEntity = (transform) => {
|
|
108
|
-
if (entities.has(transform.uuidString)) {
|
|
117
|
+
if (entities.has(transform.uuidString) || removedUUIDs.has(transform.uuidString)) {
|
|
109
118
|
return;
|
|
110
119
|
}
|
|
111
120
|
const spawned = drawTransform(world, transform, traits.WorldStateStoreAPI, { removable: false });
|
|
@@ -118,6 +127,7 @@ const createWorldState = (client) => {
|
|
|
118
127
|
invalidate();
|
|
119
128
|
};
|
|
120
129
|
const destroyEntity = (uuid) => {
|
|
130
|
+
removedUUIDs.add(uuid);
|
|
121
131
|
const entity = entities.get(uuid);
|
|
122
132
|
if (!entity)
|
|
123
133
|
return;
|
|
@@ -126,29 +136,54 @@ const createWorldState = (client) => {
|
|
|
126
136
|
}
|
|
127
137
|
entities.delete(uuid);
|
|
128
138
|
};
|
|
139
|
+
// Spawn an entity whose UPDATE delta arrived before the initial snapshot
|
|
140
|
+
// created it. The delta carries only changed fields, so fetch the full
|
|
141
|
+
// transform; skip if it was removed or already spawned meanwhile.
|
|
142
|
+
const spawnFromServer = async (uuid) => {
|
|
143
|
+
if (entities.has(uuid) || removedUUIDs.has(uuid) || pendingSpawns.has(uuid))
|
|
144
|
+
return;
|
|
145
|
+
pendingSpawns.add(uuid);
|
|
146
|
+
try {
|
|
147
|
+
const transform = await client.current?.getTransform(uuid);
|
|
148
|
+
if (transform && !removedUUIDs.has(uuid)) {
|
|
149
|
+
spawnEntity(transform);
|
|
150
|
+
invalidate();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
console.error('World state self-heal failed for', uuid, error);
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
pendingSpawns.delete(uuid);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
129
160
|
const updateEntity = (transform, changes) => {
|
|
130
161
|
const entity = entities.get(transform.uuidString);
|
|
131
|
-
if (!entity)
|
|
162
|
+
if (!entity) {
|
|
163
|
+
void spawnFromServer(transform.uuidString);
|
|
132
164
|
return;
|
|
165
|
+
}
|
|
133
166
|
let metadataDirty = false;
|
|
134
|
-
for (const
|
|
135
|
-
if (typeof
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
entity.add(traits.Matrix(poseToMatrix(createPose(transform.poseInObserverFrame?.pose), new Matrix4())));
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
else if (path.startsWith('physicalObject') && transform.physicalObject) {
|
|
147
|
-
traits.updateGeometryTrait(entity, transform.physicalObject);
|
|
167
|
+
for (const rawPath of changes) {
|
|
168
|
+
if (typeof rawPath !== 'string')
|
|
169
|
+
continue;
|
|
170
|
+
const path = snakeToCamel(rawPath);
|
|
171
|
+
if (path.startsWith('poseInObserverFrame')) {
|
|
172
|
+
const matrix = entity.get(traits.Matrix);
|
|
173
|
+
if (matrix) {
|
|
174
|
+
poseToMatrix(createPose(transform.poseInObserverFrame?.pose), matrix);
|
|
175
|
+
entity.changed(traits.Matrix);
|
|
148
176
|
}
|
|
149
|
-
else
|
|
150
|
-
|
|
177
|
+
else {
|
|
178
|
+
entity.add(traits.Matrix(poseToMatrix(createPose(transform.poseInObserverFrame?.pose), new Matrix4())));
|
|
151
179
|
}
|
|
180
|
+
hierarchy.setParent(entity, transform.poseInObserverFrame?.referenceFrame);
|
|
181
|
+
}
|
|
182
|
+
else if (path.startsWith('physicalObject') && transform.physicalObject) {
|
|
183
|
+
traits.updateGeometryTrait(entity, transform.physicalObject);
|
|
184
|
+
}
|
|
185
|
+
else if (path.startsWith('metadata')) {
|
|
186
|
+
metadataDirty = true;
|
|
152
187
|
}
|
|
153
188
|
}
|
|
154
189
|
if (metadataDirty) {
|
|
@@ -161,17 +196,16 @@ const createWorldState = (client) => {
|
|
|
161
196
|
};
|
|
162
197
|
let initialized = false;
|
|
163
198
|
let flushScheduled = false;
|
|
199
|
+
let rafId = 0;
|
|
164
200
|
let pendingEvents = [];
|
|
165
201
|
const listUUIDs = createResourceQuery(client, 'listUUIDs');
|
|
166
202
|
const getTransformQueries = $derived(listUUIDs.data?.map((uuid) => {
|
|
167
203
|
return createResourceQuery(client, 'getTransform', () => [uuid], () => ({ refetchInterval: false }));
|
|
168
204
|
}));
|
|
169
|
-
const changeStream = createResourceStream(client, 'streamTransformChanges', {
|
|
170
|
-
refetchMode: 'replace',
|
|
171
|
-
});
|
|
172
205
|
const applyEvents = (events) => {
|
|
173
206
|
for (const event of events) {
|
|
174
207
|
if (event.changeType === TransformChangeType.ADDED) {
|
|
208
|
+
removedUUIDs.delete(event.transform.uuidString);
|
|
175
209
|
spawnEntity(event.transform);
|
|
176
210
|
}
|
|
177
211
|
else if (event.changeType === TransformChangeType.REMOVED) {
|
|
@@ -190,11 +224,12 @@ const createWorldState = (client) => {
|
|
|
190
224
|
if (flushScheduled)
|
|
191
225
|
return;
|
|
192
226
|
flushScheduled = true;
|
|
193
|
-
requestAnimationFrame(() => {
|
|
194
|
-
|
|
195
|
-
applyEvents(toApply);
|
|
227
|
+
rafId = requestAnimationFrame(() => {
|
|
228
|
+
rafId = 0;
|
|
196
229
|
flushScheduled = false;
|
|
230
|
+
const toApply = pendingEvents;
|
|
197
231
|
pendingEvents = [];
|
|
232
|
+
applyEvents(toApply);
|
|
198
233
|
});
|
|
199
234
|
};
|
|
200
235
|
$effect(() => {
|
|
@@ -213,55 +248,45 @@ const createWorldState = (client) => {
|
|
|
213
248
|
invalidate();
|
|
214
249
|
initialized = true;
|
|
215
250
|
});
|
|
216
|
-
|
|
217
|
-
|
|
251
|
+
/**
|
|
252
|
+
* Consumes the `streamTransformChanges` server stream directly.
|
|
253
|
+
* Transform changes are write-once into the ECS world, so we drain
|
|
254
|
+
* each event into `pendingEvents` (cleared every flush) and never
|
|
255
|
+
* retain history. Mirrors `useDrawService`'s stream consumption.
|
|
256
|
+
*/
|
|
257
|
+
const consumeChanges = async (signal) => {
|
|
258
|
+
const activeClient = client.current;
|
|
259
|
+
if (!activeClient)
|
|
218
260
|
return;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
continue;
|
|
223
|
-
}
|
|
224
|
-
const uuid = event.transform.uuidString;
|
|
225
|
-
const existing = eventsByUUID.get(uuid);
|
|
226
|
-
if (!existing) {
|
|
227
|
-
eventsByUUID.set(uuid, event);
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
switch (event.changeType) {
|
|
231
|
-
case TransformChangeType.REMOVED: {
|
|
232
|
-
eventsByUUID.set(uuid, event);
|
|
233
|
-
break;
|
|
234
|
-
}
|
|
235
|
-
case TransformChangeType.ADDED: {
|
|
236
|
-
if (existing.changeType !== TransformChangeType.REMOVED) {
|
|
237
|
-
eventsByUUID.set(uuid, event);
|
|
238
|
-
}
|
|
239
|
-
break;
|
|
240
|
-
}
|
|
241
|
-
case TransformChangeType.UPDATED: {
|
|
242
|
-
// merge with existing updated event
|
|
243
|
-
if (existing.changeType === TransformChangeType.UPDATED) {
|
|
244
|
-
existing.updatedFields ??= { paths: [] };
|
|
245
|
-
const paths = event.updatedFields?.paths ?? [];
|
|
246
|
-
for (const path of paths) {
|
|
247
|
-
if (existing.updatedFields.paths.includes(path)) {
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
existing.updatedFields.paths.push(path);
|
|
251
|
-
}
|
|
252
|
-
existing.transform = event.transform;
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
eventsByUUID.set(uuid, event);
|
|
256
|
-
}
|
|
261
|
+
try {
|
|
262
|
+
for await (const event of activeClient.streamTransformChanges(undefined, { signal })) {
|
|
263
|
+
if (signal.aborted)
|
|
257
264
|
break;
|
|
258
|
-
|
|
265
|
+
if (!event.transform)
|
|
266
|
+
continue;
|
|
267
|
+
pendingEvents.push(event);
|
|
268
|
+
scheduleFlush();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
if (!signal.aborted) {
|
|
273
|
+
console.error('World state transform stream error:', error);
|
|
259
274
|
}
|
|
260
275
|
}
|
|
261
|
-
|
|
262
|
-
|
|
276
|
+
};
|
|
277
|
+
$effect(() => {
|
|
278
|
+
if (!client.current)
|
|
279
|
+
return;
|
|
280
|
+
const controller = new AbortController();
|
|
281
|
+
void consumeChanges(controller.signal);
|
|
282
|
+
return () => {
|
|
283
|
+
controller.abort();
|
|
284
|
+
};
|
|
263
285
|
});
|
|
264
286
|
return () => {
|
|
287
|
+
if (rafId)
|
|
288
|
+
cancelAnimationFrame(rafId);
|
|
289
|
+
pendingEvents = [];
|
|
265
290
|
chunkLoader.dispose();
|
|
266
291
|
for (const [, entity] of entities) {
|
|
267
292
|
if (world.has(entity)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viamrobotics/motion-tools",
|
|
3
|
-
"version": "1.33.
|
|
3
|
+
"version": "1.33.2",
|
|
4
4
|
"description": "Motion visualization with Viam",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@threlte/rapier": "3.4.1",
|
|
34
34
|
"@threlte/xr": "1.6.0",
|
|
35
35
|
"@types/bun": "1.2.21",
|
|
36
|
+
"@types/d3-force": "^3.0.10",
|
|
36
37
|
"@types/earcut": "^3.0.0",
|
|
37
38
|
"@types/lodash-es": "4.17.12",
|
|
38
39
|
"@types/node": "^25.6.0",
|
|
@@ -84,7 +85,7 @@
|
|
|
84
85
|
"vite-plugin-devtools-json": "1.0.0",
|
|
85
86
|
"vite-plugin-glsl": "^1.5.5",
|
|
86
87
|
"vite-plugin-mkcert": "1.17.9",
|
|
87
|
-
"vitest": "3.2.
|
|
88
|
+
"vitest": "3.2.6"
|
|
88
89
|
},
|
|
89
90
|
"peerDependencies": {
|
|
90
91
|
"@ag-grid-community/client-side-row-model": ">=32.3.0",
|
|
@@ -163,6 +164,7 @@
|
|
|
163
164
|
"dependencies": {
|
|
164
165
|
"@bufbuild/protobuf": "1.10.1",
|
|
165
166
|
"@neodrag/svelte": "^2.3.3",
|
|
167
|
+
"d3-force": "^3.0.0",
|
|
166
168
|
"filtrex": "^3.1.0",
|
|
167
169
|
"koota": "0.6.5",
|
|
168
170
|
"lodash-es": "4.18.1",
|