@viamrobotics/motion-tools 1.18.0 → 1.19.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/dist/attribute.d.ts +2 -0
- package/dist/attribute.js +47 -1
- package/dist/buf/draw/v1/metadata_pb.d.ts +39 -0
- package/dist/buf/draw/v1/metadata_pb.js +55 -0
- package/dist/chunking.d.ts +26 -0
- package/dist/chunking.js +59 -0
- package/dist/color.d.ts +1 -0
- package/dist/color.js +11 -3
- package/dist/components/Entities/Arrows/Arrows.svelte +1 -1
- package/dist/components/Entities/Mesh.svelte +21 -5
- package/dist/components/Entities/Pose.svelte +3 -1
- package/dist/components/PCD.svelte +6 -2
- package/dist/components/Scene.svelte +0 -1
- package/dist/components/Selection/Ellipse.svelte +1 -0
- package/dist/components/overlay/Details.svelte +2 -0
- package/dist/components/overlay/left-pane/TreeNode.svelte +65 -36
- package/dist/draw.d.ts +6 -0
- package/dist/draw.js +64 -42
- package/dist/ecs/traits.d.ts +12 -2
- package/dist/ecs/traits.js +19 -1
- package/dist/hooks/useDrawAPI.svelte.js +11 -12
- package/dist/hooks/useFrames.svelte.js +60 -22
- package/dist/hooks/useGeometries.svelte.js +11 -2
- package/dist/hooks/useLogs.svelte.js +3 -3
- package/dist/hooks/usePartConfig.svelte.d.ts +4 -0
- package/dist/hooks/usePartConfig.svelte.js +29 -9
- package/dist/hooks/usePointcloudObjects.svelte.js +2 -5
- package/dist/hooks/useWorldState.svelte.js +90 -17
- package/dist/metadata.js +13 -1
- package/package.json +3 -3
|
@@ -5,7 +5,6 @@ import { createBufferGeometry, updateBufferGeometry } from '../attribute';
|
|
|
5
5
|
import { ColorFormat } from '../buf/draw/v1/metadata_pb';
|
|
6
6
|
import { RefetchRates } from '../components/overlay/RefreshRate.svelte';
|
|
7
7
|
import { traits, useWorld } from '../ecs';
|
|
8
|
-
import { updateGeometryTrait } from '../ecs/traits';
|
|
9
8
|
import { parsePcdInWorker } from '../lib';
|
|
10
9
|
import { createPose } from '../transform';
|
|
11
10
|
import { useEnvironment } from './useEnvironment.svelte';
|
|
@@ -162,20 +161,18 @@ export const providePointcloudObjects = (partID) => {
|
|
|
162
161
|
const existing = entities.get(geometryLabel);
|
|
163
162
|
if (existing) {
|
|
164
163
|
existing.set(traits.Center, center);
|
|
165
|
-
updateGeometryTrait(existing, geometry);
|
|
164
|
+
traits.updateGeometryTrait(existing, geometry);
|
|
166
165
|
}
|
|
167
166
|
else {
|
|
168
167
|
const entityTraits = [
|
|
169
168
|
traits.Name(geometryLabel),
|
|
169
|
+
...traits.getParentTrait(geometriesInFrame.referenceFrame),
|
|
170
170
|
traits.Center(center),
|
|
171
171
|
traits.GeometriesAPI,
|
|
172
172
|
traits.Geometry(geometry),
|
|
173
173
|
traits.Opacity(0.2),
|
|
174
174
|
traits.Color({ r: 0, g: 1, b: 0 }),
|
|
175
175
|
];
|
|
176
|
-
if (geometriesInFrame.referenceFrame) {
|
|
177
|
-
entityTraits.push(traits.Parent(geometriesInFrame.referenceFrame));
|
|
178
|
-
}
|
|
179
176
|
const entity = world.spawn(...entityTraits);
|
|
180
177
|
entities.set(geometryLabel, entity);
|
|
181
178
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { useThrelte } from '@threlte/core';
|
|
2
|
-
import { TransformChangeType, WorldStateStoreClient, } from '@viamrobotics/sdk';
|
|
2
|
+
import { Struct, TransformChangeType, WorldStateStoreClient, } from '@viamrobotics/sdk';
|
|
3
3
|
import { createResourceClient, createResourceQuery, createResourceStream, useResourceNames, } from '@viamrobotics/svelte-sdk';
|
|
4
|
-
import {
|
|
4
|
+
import { asFloat32Array, inMeters } from '../buffer';
|
|
5
|
+
import { createChunkLoader } from '../chunking';
|
|
6
|
+
import { drawTransform, updateMetadata } from '../draw';
|
|
5
7
|
import { traits, useWorld } from '../ecs';
|
|
6
|
-
import { createBox, createCapsule, createSphere } from '../geometry';
|
|
7
8
|
import { isPointCloud } from '../geometry';
|
|
8
|
-
import {
|
|
9
|
+
import { metadataFromStruct } from '../metadata';
|
|
9
10
|
import { createPose } from '../transform';
|
|
10
11
|
import { usePartID } from './usePartID.svelte';
|
|
11
12
|
export const provideWorldStates = () => {
|
|
@@ -24,16 +25,90 @@ export const provideWorldStates = () => {
|
|
|
24
25
|
};
|
|
25
26
|
});
|
|
26
27
|
};
|
|
28
|
+
const decodeBase64 = (encoded) => {
|
|
29
|
+
const binary = atob(encoded);
|
|
30
|
+
const bytes = new Uint8Array(binary.length);
|
|
31
|
+
for (let i = 0; i < binary.length; i++) {
|
|
32
|
+
bytes[i] = binary.charCodeAt(i);
|
|
33
|
+
}
|
|
34
|
+
return bytes;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Unpacks a `get_entity_chunk` DoCommand response into the shape the shared
|
|
38
|
+
* chunk loader expects. The world-state store sends binary buffers as base64
|
|
39
|
+
* strings inside a JSON `Struct`, which is why this adapter exists.
|
|
40
|
+
*
|
|
41
|
+
* Request:
|
|
42
|
+
* { "command": "get_entity_chunk", "uuid": "<uuid-string>", "start": <element-offset> }
|
|
43
|
+
*
|
|
44
|
+
* Response:
|
|
45
|
+
* {
|
|
46
|
+
* "entity": {
|
|
47
|
+
* "metadata": {
|
|
48
|
+
* "colors": "<base64 Uint8Array>" (optional),
|
|
49
|
+
* "opacities": "<base64 Uint8Array>" (optional)
|
|
50
|
+
* },
|
|
51
|
+
* "physical_object": {
|
|
52
|
+
* "points": { "positions": "<base64 Float32Array>" }
|
|
53
|
+
* }
|
|
54
|
+
* },
|
|
55
|
+
* "start": <number>,
|
|
56
|
+
* "done": <boolean>
|
|
57
|
+
* }
|
|
58
|
+
*/
|
|
59
|
+
const decodeWorldStateChunk = (response, fallbackStart) => {
|
|
60
|
+
const fields = response;
|
|
61
|
+
const done = fields['done'] === true;
|
|
62
|
+
const start = typeof fields['start'] === 'number' ? fields['start'] : fallbackStart;
|
|
63
|
+
const chunkEntity = fields['entity'];
|
|
64
|
+
if (!chunkEntity)
|
|
65
|
+
return null;
|
|
66
|
+
const physicalObject = chunkEntity['physical_object'];
|
|
67
|
+
const points = physicalObject?.['points'];
|
|
68
|
+
const encodedPositions = points?.['positions'];
|
|
69
|
+
if (typeof encodedPositions !== 'string' || encodedPositions.length === 0)
|
|
70
|
+
return null;
|
|
71
|
+
const positions = asFloat32Array(decodeBase64(encodedPositions), inMeters);
|
|
72
|
+
const metadata = chunkEntity['metadata'];
|
|
73
|
+
const encodedColors = metadata?.['colors'];
|
|
74
|
+
const colors = typeof encodedColors === 'string' && encodedColors.length > 0
|
|
75
|
+
? decodeBase64(encodedColors)
|
|
76
|
+
: undefined;
|
|
77
|
+
const encodedOpacities = metadata?.['opacities'];
|
|
78
|
+
const opacities = typeof encodedOpacities === 'string' && encodedOpacities.length > 0
|
|
79
|
+
? decodeBase64(encodedOpacities)
|
|
80
|
+
: undefined;
|
|
81
|
+
return { start, positions, colors, opacities, done };
|
|
82
|
+
};
|
|
27
83
|
const createWorldState = (client) => {
|
|
28
84
|
const { invalidate } = useThrelte();
|
|
29
85
|
const world = useWorld();
|
|
30
86
|
const entities = new Map();
|
|
87
|
+
const chunkLoader = createChunkLoader({
|
|
88
|
+
world,
|
|
89
|
+
invalidate,
|
|
90
|
+
fetchChunk: async (uuid, start, signal) => {
|
|
91
|
+
const activeClient = client.current;
|
|
92
|
+
if (!activeClient)
|
|
93
|
+
return null;
|
|
94
|
+
const response = await activeClient.doCommand(Struct.fromJson({
|
|
95
|
+
command: 'get_entity_chunk',
|
|
96
|
+
uuid,
|
|
97
|
+
start,
|
|
98
|
+
}));
|
|
99
|
+
if (signal.aborted)
|
|
100
|
+
return null;
|
|
101
|
+
return decodeWorldStateChunk(response, start);
|
|
102
|
+
},
|
|
103
|
+
});
|
|
31
104
|
const spawnEntity = (transform) => {
|
|
32
105
|
if (entities.has(transform.uuidString)) {
|
|
33
106
|
return;
|
|
34
107
|
}
|
|
35
108
|
const entity = drawTransform(world, transform, traits.WorldStateStoreAPI, { removable: false });
|
|
36
109
|
entities.set(transform.uuidString, entity);
|
|
110
|
+
const parsedMetadata = metadataFromStruct(transform.metadata?.fields);
|
|
111
|
+
chunkLoader.start(transform.uuidString, entity, parsedMetadata);
|
|
37
112
|
if (isPointCloud(transform.physicalObject?.geometryType))
|
|
38
113
|
invalidate();
|
|
39
114
|
};
|
|
@@ -50,28 +125,25 @@ const createWorldState = (client) => {
|
|
|
50
125
|
const entity = entities.get(transform.uuidString);
|
|
51
126
|
if (!entity)
|
|
52
127
|
return;
|
|
128
|
+
let metadataDirty = false;
|
|
53
129
|
for (const path of changes) {
|
|
54
130
|
if (typeof path === 'string') {
|
|
55
131
|
if (path.startsWith('poseInObserverFrame.pose')) {
|
|
56
132
|
entity.set(traits.Pose, transform.poseInObserverFrame?.pose ?? createPose());
|
|
57
133
|
}
|
|
58
134
|
else if (path.startsWith('physicalObject') && transform.physicalObject) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
else if (geometryType.case === 'capsule') {
|
|
64
|
-
entity.set(traits.Capsule, createCapsule(geometryType.value));
|
|
65
|
-
}
|
|
66
|
-
else if (geometryType.case === 'sphere') {
|
|
67
|
-
entity.set(traits.Sphere, createSphere(geometryType.value));
|
|
68
|
-
}
|
|
69
|
-
else if (geometryType.case === 'mesh') {
|
|
70
|
-
entity.set(traits.BufferGeometry, parsePlyInput(geometryType.value.mesh));
|
|
71
|
-
}
|
|
135
|
+
traits.updateGeometryTrait(entity, transform.physicalObject);
|
|
136
|
+
}
|
|
137
|
+
else if (path.startsWith('metadata')) {
|
|
138
|
+
metadataDirty = true;
|
|
72
139
|
}
|
|
73
140
|
}
|
|
74
141
|
}
|
|
142
|
+
if (metadataDirty) {
|
|
143
|
+
updateMetadata(entity, metadataFromStruct(transform.metadata?.fields), {
|
|
144
|
+
pointCloud: isPointCloud(transform.physicalObject?.geometryType),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
75
147
|
};
|
|
76
148
|
let initialized = false;
|
|
77
149
|
let flushScheduled = false;
|
|
@@ -176,6 +248,7 @@ const createWorldState = (client) => {
|
|
|
176
248
|
scheduleFlush();
|
|
177
249
|
});
|
|
178
250
|
return () => {
|
|
251
|
+
chunkLoader.dispose();
|
|
179
252
|
for (const [, entity] of entities) {
|
|
180
253
|
if (world.has(entity)) {
|
|
181
254
|
entity.destroy();
|
package/dist/metadata.js
CHANGED
|
@@ -5,7 +5,8 @@ export const isMetadataField = (key) => {
|
|
|
5
5
|
key === 'color_format' ||
|
|
6
6
|
key === 'opacities' ||
|
|
7
7
|
key === 'show_axes_helper' ||
|
|
8
|
-
key === 'invisible'
|
|
8
|
+
key === 'invisible' ||
|
|
9
|
+
key === 'chunks');
|
|
9
10
|
};
|
|
10
11
|
/**
|
|
11
12
|
* Extracts typed {@link Metadata} from a proto `Struct` fields map.
|
|
@@ -65,6 +66,17 @@ export const metadataFromStruct = (fields = {}) => {
|
|
|
65
66
|
}
|
|
66
67
|
break;
|
|
67
68
|
}
|
|
69
|
+
case 'chunks': {
|
|
70
|
+
if (typeof unwrappedValue === 'object' && unwrappedValue !== null) {
|
|
71
|
+
const obj = unwrappedValue;
|
|
72
|
+
json.chunks = {
|
|
73
|
+
chunkSize: typeof obj['chunk_size'] === 'number' ? obj['chunk_size'] : 0,
|
|
74
|
+
total: typeof obj['total'] === 'number' ? obj['total'] : 0,
|
|
75
|
+
stride: typeof obj['stride'] === 'number' ? obj['stride'] : 0,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
68
80
|
}
|
|
69
81
|
}
|
|
70
82
|
return json;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viamrobotics/motion-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"description": "Motion visualization with Viam",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -165,8 +165,8 @@
|
|
|
165
165
|
"test:draw": "go test ./draw/... -count=1",
|
|
166
166
|
"test": "pnpm test:unit -- --run",
|
|
167
167
|
"test:coverage": "npx vitest run --coverage",
|
|
168
|
-
"test:e2e": "playwright test",
|
|
169
|
-
"test:e2e-ui": "playwright test --ui",
|
|
168
|
+
"test:e2e": "./e2e/setup.sh && playwright test",
|
|
169
|
+
"test:e2e-ui": "./e2e/setup.sh && playwright test --ui",
|
|
170
170
|
"vet:draw": "go vet ./draw/...",
|
|
171
171
|
"vet:client": "go vet ./client/...",
|
|
172
172
|
"vet": "pnpm vet:draw && pnpm vet:client",
|