@viamrobotics/motion-tools 1.18.1 → 1.19.1
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/Entities.svelte +1 -0
- 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/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 +66 -45
- package/dist/ecs/traits.d.ts +12 -2
- package/dist/ecs/traits.js +80 -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
package/dist/draw.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Vector3, Vector4 } from 'three';
|
|
2
2
|
import { NURBSCurve } from 'three/addons/curves/NURBSCurve.js';
|
|
3
|
-
import { createBufferGeometry, updateBufferGeometry } from './attribute';
|
|
3
|
+
import { createBufferGeometry, preAllocateBufferGeometry, updateBufferGeometry, writeBufferGeometryRange, } from './attribute';
|
|
4
4
|
import { asFloat32Array, asOpacity, asRGB, inMeters, isSingleColor, isVertexColors, STRIDE, } from './buffer';
|
|
5
5
|
import { traits } from './ecs';
|
|
6
6
|
import { parsePcdInWorker } from './loaders/pcd';
|
|
@@ -38,9 +38,7 @@ export const drawTransform = (world, { referenceFrame, poseInObserverFrame, phys
|
|
|
38
38
|
}
|
|
39
39
|
if (options.removable)
|
|
40
40
|
entityTraits.push(traits.Removable);
|
|
41
|
-
|
|
42
|
-
if (parent && parent !== 'world')
|
|
43
|
-
entityTraits.push(traits.Parent(parent));
|
|
41
|
+
entityTraits.push(...traits.getParentTrait(poseInObserverFrame?.referenceFrame));
|
|
44
42
|
const parsedMetadata = metadataFromStruct(metadata?.fields);
|
|
45
43
|
if (parsedMetadata.showAxesHelper)
|
|
46
44
|
entityTraits.push(traits.ShowAxesHelper);
|
|
@@ -68,10 +66,7 @@ export const drawDrawing = (world, drawing, api, options = { removable: true })
|
|
|
68
66
|
const { referenceFrame, poseInObserverFrame, physicalObject, metadata } = drawing;
|
|
69
67
|
if (physicalObject?.geometryType?.case === 'model')
|
|
70
68
|
return drawModel(world, drawing, api, options);
|
|
71
|
-
const entity = world.spawn(traits.Name(referenceFrame), traits.Pose(createPose(poseInObserverFrame?.pose)), api);
|
|
72
|
-
const parent = poseInObserverFrame?.referenceFrame;
|
|
73
|
-
if (parent && parent !== 'world')
|
|
74
|
-
entity.add(traits.Parent(parent));
|
|
69
|
+
const entity = world.spawn(traits.Name(referenceFrame), traits.Pose(createPose(poseInObserverFrame?.pose)), api, ...traits.getParentTrait(poseInObserverFrame?.referenceFrame));
|
|
75
70
|
if (options.removable)
|
|
76
71
|
entity.add(traits.Removable);
|
|
77
72
|
if (metadata?.showAxesHelper)
|
|
@@ -83,9 +78,7 @@ export const drawDrawing = (world, drawing, api, options = { removable: true })
|
|
|
83
78
|
};
|
|
84
79
|
export const updateTransform = (entity, { poseInObserverFrame, physicalObject, metadata }, options = { removable: true }) => {
|
|
85
80
|
entity.set(traits.Pose, createPose(poseInObserverFrame?.pose));
|
|
86
|
-
|
|
87
|
-
if (parent && parent !== 'world')
|
|
88
|
-
entity.set(traits.Parent, parent);
|
|
81
|
+
traits.setParentTrait(entity, poseInObserverFrame?.referenceFrame);
|
|
89
82
|
if (physicalObject) {
|
|
90
83
|
traits.updateGeometryTrait(entity, physicalObject);
|
|
91
84
|
const center = physicalObject.center;
|
|
@@ -96,31 +89,32 @@ export const updateTransform = (entity, { poseInObserverFrame, physicalObject, m
|
|
|
96
89
|
entity.remove(traits.Center);
|
|
97
90
|
}
|
|
98
91
|
}
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
updateMetadata(entity, metadataFromStruct(metadata?.fields), {
|
|
93
|
+
pointCloud: isPointCloud(physicalObject?.geometryType),
|
|
94
|
+
});
|
|
95
|
+
if (options.removable)
|
|
96
|
+
entity.add(traits.Removable);
|
|
97
|
+
if (!options.removable)
|
|
98
|
+
entity.remove(traits.Removable);
|
|
99
|
+
};
|
|
100
|
+
export const updateMetadata = (entity, metadata, { pointCloud = false } = {}) => {
|
|
101
|
+
if (metadata.showAxesHelper)
|
|
101
102
|
entity.add(traits.ShowAxesHelper);
|
|
102
103
|
else
|
|
103
104
|
entity.remove(traits.ShowAxesHelper);
|
|
104
|
-
if (
|
|
105
|
+
if (metadata.invisible)
|
|
105
106
|
entity.add(traits.Invisible);
|
|
106
107
|
else
|
|
107
108
|
entity.remove(traits.Invisible);
|
|
108
|
-
const { colors, opacities } =
|
|
109
|
+
const { colors, opacities } = metadata;
|
|
109
110
|
if (colors) {
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
addColorTraits(entity, colors);
|
|
111
|
+
if (pointCloud) {
|
|
112
|
+
updatePointCloudColors(entity, metadata);
|
|
115
113
|
}
|
|
114
|
+
// Always set color traits so any subsequent async work can read them
|
|
115
|
+
setColorTraits(entity, colors);
|
|
116
116
|
}
|
|
117
|
-
|
|
118
|
-
if (opacity < 1)
|
|
119
|
-
entity.add(traits.Opacity(opacity));
|
|
120
|
-
if (options.removable)
|
|
121
|
-
entity.add(traits.Removable);
|
|
122
|
-
if (!options.removable)
|
|
123
|
-
entity.remove(traits.Removable);
|
|
117
|
+
entity.set(traits.Opacity, asOpacity(opacities, DEFAULT_OPACITY));
|
|
124
118
|
};
|
|
125
119
|
export const updateDrawing = (world, entities, drawing, api, options = { removable: true }) => {
|
|
126
120
|
const { poseInObserverFrame, physicalObject, metadata } = drawing;
|
|
@@ -137,9 +131,7 @@ export const updateDrawing = (world, entities, drawing, api, options = { removab
|
|
|
137
131
|
if (!world.has(entity))
|
|
138
132
|
return entities;
|
|
139
133
|
entity.set(traits.Pose, createPose(poseInObserverFrame?.pose));
|
|
140
|
-
|
|
141
|
-
if (parent && parent !== 'world')
|
|
142
|
-
entity.set(traits.Parent, parent);
|
|
134
|
+
traits.setParentTrait(entity, poseInObserverFrame?.referenceFrame);
|
|
143
135
|
if (metadata?.showAxesHelper)
|
|
144
136
|
entity.add(traits.ShowAxesHelper);
|
|
145
137
|
if (!metadata?.showAxesHelper)
|
|
@@ -181,15 +173,30 @@ const applyShape = (entity, { physicalObject, metadata }) => {
|
|
|
181
173
|
}
|
|
182
174
|
case 'points': {
|
|
183
175
|
const positions = asFloat32Array(geometryType.value.positions, inMeters);
|
|
176
|
+
const total = metadata?.chunks?.total;
|
|
184
177
|
const center = physicalObject?.center;
|
|
185
178
|
if (center)
|
|
186
179
|
entity.add(traits.Center(center));
|
|
187
180
|
addColorTraits(entity, colors ?? DEFAULT_POINTS_COLORS);
|
|
188
181
|
entity.add(traits.PointSize(geometryType.value.pointSize ?? DEFAULT_POINT_SIZE));
|
|
189
|
-
|
|
190
|
-
|
|
182
|
+
const vertexColors = isVertexColors(colors) ? colors : undefined;
|
|
183
|
+
const pointsMetadata = {
|
|
184
|
+
colors: vertexColors,
|
|
191
185
|
colorFormat: metadata?.colorFormat ?? ColorFormat.UNSPECIFIED,
|
|
192
|
-
|
|
186
|
+
opacities: metadata?.opacities,
|
|
187
|
+
};
|
|
188
|
+
if (total !== undefined && total > 0) {
|
|
189
|
+
const allocMetadata = {
|
|
190
|
+
...pointsMetadata,
|
|
191
|
+
colors: vertexColors ? new Uint8Array(0) : undefined,
|
|
192
|
+
};
|
|
193
|
+
const geometry = preAllocateBufferGeometry(total, STRIDE.POSITIONS, allocMetadata);
|
|
194
|
+
writeBufferGeometryRange(geometry, positions, 0, pointsMetadata);
|
|
195
|
+
entity.add(traits.BufferGeometry(geometry));
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
entity.add(traits.BufferGeometry(createBufferGeometry(positions, pointsMetadata)));
|
|
199
|
+
}
|
|
193
200
|
entity.add(traits.Points);
|
|
194
201
|
break;
|
|
195
202
|
}
|
|
@@ -237,7 +244,6 @@ const applyShape = (entity, { physicalObject, metadata }) => {
|
|
|
237
244
|
};
|
|
238
245
|
const drawModel = (world, { referenceFrame, poseInObserverFrame, physicalObject, metadata }, api, { removable = true }) => {
|
|
239
246
|
const entities = [];
|
|
240
|
-
const parent = poseInObserverFrame?.referenceFrame;
|
|
241
247
|
const geometryType = physicalObject?.geometryType;
|
|
242
248
|
if (geometryType?.case !== 'model')
|
|
243
249
|
return entities;
|
|
@@ -245,9 +251,8 @@ const drawModel = (world, { referenceFrame, poseInObserverFrame, physicalObject,
|
|
|
245
251
|
traits.Name(referenceFrame),
|
|
246
252
|
traits.Pose(createPose(poseInObserverFrame?.pose)),
|
|
247
253
|
api,
|
|
254
|
+
...traits.getParentTrait(poseInObserverFrame?.referenceFrame),
|
|
248
255
|
];
|
|
249
|
-
if (parent && parent !== 'world')
|
|
250
|
-
baseTraits.push(traits.Parent(parent));
|
|
251
256
|
if (removable)
|
|
252
257
|
baseTraits.push(traits.Removable);
|
|
253
258
|
if (metadata?.invisible)
|
|
@@ -292,15 +297,24 @@ const parsePointCloud = (world, entity, pointCloud, metadata) => {
|
|
|
292
297
|
let vertexColors = pointcloud.colors;
|
|
293
298
|
if (colors && colors.length > 0)
|
|
294
299
|
vertexColors = parseColors(colors, numPoints);
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
300
|
+
const total = metadata.chunks?.total;
|
|
301
|
+
const chunkMetadata = { colors: vertexColors ?? undefined, colorFormat };
|
|
302
|
+
let geometry;
|
|
303
|
+
if (total !== undefined && total > numPoints) {
|
|
304
|
+
geometry = preAllocateBufferGeometry(total, STRIDE.POSITIONS, {
|
|
305
|
+
...chunkMetadata,
|
|
306
|
+
colors: vertexColors ? new Uint8Array(0) : undefined,
|
|
307
|
+
});
|
|
308
|
+
writeBufferGeometryRange(geometry, pointcloud.positions, 0, chunkMetadata);
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
geometry = createBufferGeometry(pointcloud.positions, chunkMetadata);
|
|
312
|
+
}
|
|
299
313
|
entity.add(traits.BufferGeometry(geometry));
|
|
300
314
|
entity.add(traits.Points);
|
|
301
315
|
});
|
|
302
316
|
};
|
|
303
|
-
const
|
|
317
|
+
const updatePointCloudColors = (entity, metadata) => {
|
|
304
318
|
const buffer = entity.get(traits.BufferGeometry);
|
|
305
319
|
if (!buffer) {
|
|
306
320
|
if (metadata.colors)
|
|
@@ -408,7 +422,7 @@ const updateShape = (entity, { physicalObject, metadata }) => {
|
|
|
408
422
|
}
|
|
409
423
|
}
|
|
410
424
|
};
|
|
411
|
-
const addColorTraits = (entity, colors) => {
|
|
425
|
+
export const addColorTraits = (entity, colors) => {
|
|
412
426
|
if (isVertexColors(colors)) {
|
|
413
427
|
entity.add(traits.Colors(colors));
|
|
414
428
|
}
|
|
@@ -416,13 +430,20 @@ const addColorTraits = (entity, colors) => {
|
|
|
416
430
|
entity.add(traits.Color(asRGB(colors, rgb)));
|
|
417
431
|
}
|
|
418
432
|
};
|
|
419
|
-
const setColorTraits = (entity, colors) => {
|
|
433
|
+
export const setColorTraits = (entity, colors) => {
|
|
420
434
|
if (isVertexColors(colors)) {
|
|
421
|
-
entity.
|
|
435
|
+
if (entity.has(traits.Colors))
|
|
436
|
+
entity.set(traits.Colors, colors);
|
|
437
|
+
else
|
|
438
|
+
entity.add(traits.Colors(colors));
|
|
422
439
|
entity.remove(traits.Color);
|
|
423
440
|
}
|
|
424
441
|
else {
|
|
425
|
-
|
|
442
|
+
const color = asRGB(colors, rgb);
|
|
443
|
+
if (entity.has(traits.Color))
|
|
444
|
+
entity.set(traits.Color, color);
|
|
445
|
+
else
|
|
446
|
+
entity.add(traits.Color(color));
|
|
426
447
|
entity.remove(traits.Colors);
|
|
427
448
|
}
|
|
428
449
|
};
|
package/dist/ecs/traits.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { GLTF as ThreeGltf } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
2
2
|
import { Geometry as ViamGeometry } from '@viamrobotics/sdk';
|
|
3
|
-
import { type Entity } from 'koota';
|
|
3
|
+
import { type ConfigurableTrait, type Entity } from 'koota';
|
|
4
4
|
import { BufferGeometry as ThreeBufferGeometry } from 'three';
|
|
5
5
|
export declare const Name: import("koota").Trait<() => string>;
|
|
6
6
|
export declare const Parent: import("koota").Trait<() => string>;
|
|
@@ -170,13 +170,21 @@ export declare const DotColors: import("koota").Trait<() => Uint8Array<ArrayBuff
|
|
|
170
170
|
*/
|
|
171
171
|
export declare const DotSize: import("koota").Trait<() => number>;
|
|
172
172
|
export declare const ReferenceFrame: import("koota").Trait<() => boolean>;
|
|
173
|
+
/**
|
|
174
|
+
* Tracks chunk loading progress for progressively-loaded entities.
|
|
175
|
+
* `loaded` is the number of elements received so far; `total` is the target.
|
|
176
|
+
*/
|
|
177
|
+
export declare const ChunkProgress: import("koota").Trait<{
|
|
178
|
+
loaded: number;
|
|
179
|
+
total: number;
|
|
180
|
+
}>;
|
|
173
181
|
/**
|
|
174
182
|
* Interaction layers for entities
|
|
175
183
|
*/
|
|
176
184
|
export type InteractionLayerValue = 'selectTool';
|
|
177
185
|
export declare const SelectToolInteractionLayer: import("koota").Trait<() => boolean>;
|
|
178
186
|
/**
|
|
179
|
-
* This entity can be
|
|
187
|
+
* This entity can be safely removed from the scene by the user
|
|
180
188
|
*/
|
|
181
189
|
export declare const Removable: import("koota").Trait<() => boolean>;
|
|
182
190
|
export declare const Geometry: (geometry: ViamGeometry) => import("koota").Trait<() => boolean> | [import("koota").Trait<{
|
|
@@ -198,4 +206,6 @@ export declare const Geometry: (geometry: ViamGeometry) => import("koota").Trait
|
|
|
198
206
|
}>, Partial<{
|
|
199
207
|
r: number;
|
|
200
208
|
}>] | [import("koota").Trait<() => ThreeBufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>>, ThreeBufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>];
|
|
209
|
+
export declare const getParentTrait: (parent: string | undefined) => ConfigurableTrait[];
|
|
210
|
+
export declare const setParentTrait: (entity: Entity, parent: string | undefined) => void;
|
|
201
211
|
export declare const updateGeometryTrait: (entity: Entity, geometry?: ViamGeometry) => void;
|
package/dist/ecs/traits.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { Geometry as ViamGeometry } from '@viamrobotics/sdk';
|
|
2
2
|
import { trait } from 'koota';
|
|
3
3
|
import { BufferGeometry as ThreeBufferGeometry } from 'three';
|
|
4
|
+
import { createBufferGeometry, updateBufferGeometry } from '../attribute';
|
|
5
|
+
import { ColorFormat } from '../buf/draw/v1/metadata_pb';
|
|
4
6
|
import { createBox, createCapsule, createSphere } from '../geometry';
|
|
7
|
+
import { parsePcdInWorker } from '../loaders/pcd';
|
|
5
8
|
import { parsePlyInput } from '../ply';
|
|
6
9
|
export const Name = trait(() => '');
|
|
7
10
|
export const Parent = trait(() => 'world');
|
|
@@ -124,9 +127,14 @@ export const DotColors = trait(() => new Uint8Array());
|
|
|
124
127
|
*/
|
|
125
128
|
export const DotSize = trait(() => 10);
|
|
126
129
|
export const ReferenceFrame = trait(() => true);
|
|
130
|
+
/**
|
|
131
|
+
* Tracks chunk loading progress for progressively-loaded entities.
|
|
132
|
+
* `loaded` is the number of elements received so far; `total` is the target.
|
|
133
|
+
*/
|
|
134
|
+
export const ChunkProgress = trait({ loaded: 0, total: 0 });
|
|
127
135
|
export const SelectToolInteractionLayer = trait(() => true);
|
|
128
136
|
/**
|
|
129
|
-
* This entity can be
|
|
137
|
+
* This entity can be safely removed from the scene by the user
|
|
130
138
|
*/
|
|
131
139
|
export const Removable = trait(() => true);
|
|
132
140
|
export const Geometry = (geometry) => {
|
|
@@ -144,6 +152,19 @@ export const Geometry = (geometry) => {
|
|
|
144
152
|
}
|
|
145
153
|
return ReferenceFrame;
|
|
146
154
|
};
|
|
155
|
+
export const getParentTrait = (parent) => !parent || parent === 'world' ? [] : [Parent(parent)];
|
|
156
|
+
export const setParentTrait = (entity, parent) => {
|
|
157
|
+
if (!parent || parent === 'world') {
|
|
158
|
+
entity.remove(Parent);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (entity.has(Parent)) {
|
|
162
|
+
entity.set(Parent, parent);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
entity.add(Parent(parent));
|
|
166
|
+
}
|
|
167
|
+
};
|
|
147
168
|
export const updateGeometryTrait = (entity, geometry) => {
|
|
148
169
|
if (!geometry) {
|
|
149
170
|
entity.remove(Box, Capsule, Sphere, BufferGeometry);
|
|
@@ -185,4 +206,62 @@ export const updateGeometryTrait = (entity, geometry) => {
|
|
|
185
206
|
entity.add(BufferGeometry(parsePlyInput(geometry.geometryType.value.mesh)));
|
|
186
207
|
}
|
|
187
208
|
}
|
|
209
|
+
else if (geometry.geometryType.case === 'pointcloud') {
|
|
210
|
+
updatePointCloud(entity, geometry.geometryType.value.pointCloud);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
const updatePointCloud = (entity, pointCloud) => {
|
|
214
|
+
parsePcdInWorker(new Uint8Array(pointCloud))
|
|
215
|
+
.then((parsed) => {
|
|
216
|
+
if (!entity.isAlive())
|
|
217
|
+
return;
|
|
218
|
+
const buffer = entity.get(BufferGeometry);
|
|
219
|
+
let colors = parsed.colors;
|
|
220
|
+
if (buffer) {
|
|
221
|
+
// Reapply single color trait if the point count changed
|
|
222
|
+
if (parsed.colors === undefined) {
|
|
223
|
+
const color = entity.get(Color);
|
|
224
|
+
if (color) {
|
|
225
|
+
const newCount = parsed.positions.length / 3;
|
|
226
|
+
colors = new Uint8Array(newCount * 3);
|
|
227
|
+
const r = Math.round(color.r * 255);
|
|
228
|
+
const g = Math.round(color.g * 255);
|
|
229
|
+
const b = Math.round(color.b * 255);
|
|
230
|
+
for (let i = 0; i < newCount; i++) {
|
|
231
|
+
colors[i * 3] = r;
|
|
232
|
+
colors[i * 3 + 1] = g;
|
|
233
|
+
colors[i * 3 + 2] = b;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// When the point count changes, attributes must be reallocated.
|
|
238
|
+
const oldCount = buffer.getAttribute('position').count;
|
|
239
|
+
const newCount = parsed.positions.length / 3;
|
|
240
|
+
if (oldCount === newCount) {
|
|
241
|
+
updateBufferGeometry(buffer, parsed.positions, {
|
|
242
|
+
colors,
|
|
243
|
+
colorFormat: ColorFormat.RGB,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
const fresh = createBufferGeometry(parsed.positions, {
|
|
248
|
+
colors,
|
|
249
|
+
colorFormat: ColorFormat.RGB,
|
|
250
|
+
});
|
|
251
|
+
buffer.dispose();
|
|
252
|
+
entity.set(BufferGeometry, fresh);
|
|
253
|
+
}
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
entity.remove(Box, Capsule, Sphere);
|
|
257
|
+
entity.add(BufferGeometry(createBufferGeometry(parsed.positions, {
|
|
258
|
+
colors: parsed.colors,
|
|
259
|
+
colorFormat: ColorFormat.RGB,
|
|
260
|
+
})));
|
|
261
|
+
if (!entity.has(Points))
|
|
262
|
+
entity.add(Points);
|
|
263
|
+
})
|
|
264
|
+
.catch((error) => {
|
|
265
|
+
console.error('Failed to update pointcloud buffer geometry:', error);
|
|
266
|
+
});
|
|
188
267
|
};
|
|
@@ -127,9 +127,7 @@ export const provideDrawAPI = () => {
|
|
|
127
127
|
const existing = entities.get(name);
|
|
128
128
|
if (existing) {
|
|
129
129
|
existing.set(traits.Pose, pose);
|
|
130
|
-
|
|
131
|
-
existing.set(traits.Parent, parent);
|
|
132
|
-
}
|
|
130
|
+
traits.setParentTrait(existing, parent);
|
|
133
131
|
continue;
|
|
134
132
|
}
|
|
135
133
|
const geometryTrait = () => {
|
|
@@ -144,10 +142,7 @@ export const provideDrawAPI = () => {
|
|
|
144
142
|
}
|
|
145
143
|
return traits.ReferenceFrame;
|
|
146
144
|
};
|
|
147
|
-
const entityTraits = [];
|
|
148
|
-
if (parent && parent !== 'world') {
|
|
149
|
-
entityTraits.push(traits.Parent(parent));
|
|
150
|
-
}
|
|
145
|
+
const entityTraits = [...traits.getParentTrait(parent)];
|
|
151
146
|
if (frame.geometry) {
|
|
152
147
|
entityTraits.push(geometryTrait());
|
|
153
148
|
}
|
|
@@ -181,11 +176,15 @@ export const provideDrawAPI = () => {
|
|
|
181
176
|
}
|
|
182
177
|
return traits.ReferenceFrame;
|
|
183
178
|
};
|
|
184
|
-
const entityTraits = [
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
179
|
+
const entityTraits = [
|
|
180
|
+
traits.Name(data.label ?? ++geometryIndex),
|
|
181
|
+
...traits.getParentTrait(parent),
|
|
182
|
+
traits.Pose(pose),
|
|
183
|
+
traits.Color(colorUtil.set(color)),
|
|
184
|
+
geometryTrait(),
|
|
185
|
+
traits.DrawAPI,
|
|
186
|
+
traits.Removable,
|
|
187
|
+
];
|
|
189
188
|
const entity = world.spawn(...entityTraits);
|
|
190
189
|
entities.set(name, entity);
|
|
191
190
|
};
|
|
@@ -2,17 +2,18 @@ import { MachineConnectionEvent, Transform } from '@viamrobotics/sdk';
|
|
|
2
2
|
import { createRobotQuery, useConnectionStatus, useMachineStatus, useRobotClient, } from '@viamrobotics/svelte-sdk';
|
|
3
3
|
import {} from 'koota';
|
|
4
4
|
import { getContext, setContext, untrack } from 'svelte';
|
|
5
|
-
import { resourceNameToColor } from '../color';
|
|
5
|
+
import { resourceNameToColor, subtypeToColor } from '../color';
|
|
6
6
|
import { traits, useWorld } from '../ecs';
|
|
7
|
-
import { updateGeometryTrait } from '../ecs/traits';
|
|
8
7
|
import { createPose } from '../transform';
|
|
9
8
|
import { useConfigFrames } from './useConfigFrames.svelte';
|
|
10
9
|
import { useEnvironment } from './useEnvironment.svelte';
|
|
11
10
|
import { useLogs } from './useLogs.svelte';
|
|
11
|
+
import { usePartConfig } from './usePartConfig.svelte';
|
|
12
12
|
import { useResourceByName } from './useResourceByName.svelte';
|
|
13
13
|
const key = Symbol('frames-context');
|
|
14
14
|
export const provideFrames = (partID) => {
|
|
15
15
|
const configFrames = useConfigFrames();
|
|
16
|
+
const partConfig = usePartConfig();
|
|
16
17
|
const environment = useEnvironment();
|
|
17
18
|
const world = useWorld();
|
|
18
19
|
const resourceByName = useResourceByName();
|
|
@@ -20,6 +21,7 @@ export const provideFrames = (partID) => {
|
|
|
20
21
|
const connectionStatus = useConnectionStatus(partID);
|
|
21
22
|
const machineStatus = useMachineStatus(partID);
|
|
22
23
|
const logs = useLogs();
|
|
24
|
+
const pendingSaveKey = $derived(`viam-pending-save-revision:${partID()}`);
|
|
23
25
|
let didRecentlyEdit = $state(false);
|
|
24
26
|
const isEditMode = $derived(environment.current.viewerMode === 'edit');
|
|
25
27
|
const query = createRobotQuery(client, 'frameSystemConfig', () => ({
|
|
@@ -37,14 +39,19 @@ export const provideFrames = (partID) => {
|
|
|
37
39
|
});
|
|
38
40
|
const frames = $derived.by(() => {
|
|
39
41
|
const frames = {};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
if (!partConfig.hasPendingSave) {
|
|
43
|
+
for (const { frame } of query.data ?? []) {
|
|
44
|
+
if (frame === undefined) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
frames[frame.referenceFrame] = frame;
|
|
43
48
|
}
|
|
44
|
-
frames[frame.referenceFrame] = frame;
|
|
45
49
|
}
|
|
46
|
-
// Let config frames take priority if the user has made edits
|
|
47
|
-
|
|
50
|
+
// Let config frames take priority if the user has made edits,
|
|
51
|
+
// has a pending save, or is disconnected
|
|
52
|
+
if (didRecentlyEdit ||
|
|
53
|
+
partConfig.hasPendingSave ||
|
|
54
|
+
connectionStatus.current === MachineConnectionEvent.DISCONNECTED) {
|
|
48
55
|
const mergedFrames = {
|
|
49
56
|
...frames,
|
|
50
57
|
...configFrames.current,
|
|
@@ -71,6 +78,45 @@ export const provideFrames = (partID) => {
|
|
|
71
78
|
untrack(() => query.refetch());
|
|
72
79
|
}
|
|
73
80
|
});
|
|
81
|
+
$effect(() => {
|
|
82
|
+
const key = pendingSaveKey;
|
|
83
|
+
const storedRevision = sessionStorage.getItem(key);
|
|
84
|
+
if (!storedRevision) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (!revision) {
|
|
88
|
+
if (!partConfig.hasPendingSave) {
|
|
89
|
+
partConfig.setPendingSave();
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (revision === storedRevision) {
|
|
94
|
+
if (!partConfig.hasPendingSave) {
|
|
95
|
+
partConfig.setPendingSave();
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
sessionStorage.removeItem(key);
|
|
100
|
+
partConfig.clearPendingSave();
|
|
101
|
+
didRecentlyEdit = true;
|
|
102
|
+
});
|
|
103
|
+
$effect(() => {
|
|
104
|
+
if (partConfig.hasPendingSave && revision) {
|
|
105
|
+
sessionStorage.setItem(pendingSaveKey, revision);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
const componentSubtypeByName = $derived.by(() => {
|
|
109
|
+
const result = {};
|
|
110
|
+
for (const { name, api } of partConfig.current.components ?? []) {
|
|
111
|
+
if (api) {
|
|
112
|
+
const subtype = api.split(':').at(-1);
|
|
113
|
+
if (subtype) {
|
|
114
|
+
result[name] = subtype;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
119
|
+
});
|
|
74
120
|
$effect(() => {
|
|
75
121
|
if (isEditMode) {
|
|
76
122
|
didRecentlyEdit = true;
|
|
@@ -79,6 +125,7 @@ export const provideFrames = (partID) => {
|
|
|
79
125
|
$effect.pre(() => {
|
|
80
126
|
const currentResourcesByName = resourceByName.current;
|
|
81
127
|
const currentPartID = partID();
|
|
128
|
+
const currentComponentSubtypeByName = componentSubtypeByName;
|
|
82
129
|
// We only want to update whenever "current" or "resourceByName.current" changes
|
|
83
130
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
84
131
|
current.length;
|
|
@@ -94,25 +141,18 @@ export const provideFrames = (partID) => {
|
|
|
94
141
|
? createPose(frame.physicalObject.center)
|
|
95
142
|
: undefined;
|
|
96
143
|
const resourceName = currentResourcesByName[frame.referenceFrame];
|
|
97
|
-
const color = resourceNameToColor(resourceName)
|
|
144
|
+
const color = resourceNameToColor(resourceName) ??
|
|
145
|
+
subtypeToColor(currentComponentSubtypeByName[frame.referenceFrame]);
|
|
98
146
|
const existing = entities.get(entityKey);
|
|
99
147
|
if (existing) {
|
|
100
|
-
|
|
101
|
-
existing.remove(traits.Parent);
|
|
102
|
-
}
|
|
103
|
-
else if (parent && existing.has(traits.Parent)) {
|
|
104
|
-
existing.set(traits.Parent, parent);
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
existing.add(traits.Parent(parent));
|
|
108
|
-
}
|
|
148
|
+
traits.setParentTrait(existing, parent);
|
|
109
149
|
if (color) {
|
|
110
150
|
existing.set(traits.Color, color);
|
|
111
151
|
}
|
|
112
152
|
if (center) {
|
|
113
153
|
existing.set(traits.Center, center);
|
|
114
154
|
}
|
|
115
|
-
updateGeometryTrait(existing, frame.physicalObject);
|
|
155
|
+
traits.updateGeometryTrait(existing, frame.physicalObject);
|
|
116
156
|
existing.set(traits.EditedPose, pose);
|
|
117
157
|
continue;
|
|
118
158
|
}
|
|
@@ -122,10 +162,8 @@ export const provideFrames = (partID) => {
|
|
|
122
162
|
traits.EditedPose(pose),
|
|
123
163
|
traits.FramesAPI,
|
|
124
164
|
traits.ShowAxesHelper,
|
|
165
|
+
...traits.getParentTrait(parent),
|
|
125
166
|
];
|
|
126
|
-
if (parent && parent !== 'world') {
|
|
127
|
-
entityTraits.push(traits.Parent(parent));
|
|
128
|
-
}
|
|
129
167
|
if (color) {
|
|
130
168
|
entityTraits.push(traits.Color(color));
|
|
131
169
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ArmClient, CameraClient, GantryClient, GripperClient } from '@viamrobotics/sdk';
|
|
1
|
+
import { ArmClient, BaseClient, CameraClient, GantryClient, GripperClient } from '@viamrobotics/sdk';
|
|
2
2
|
import { createResourceClient, createResourceQuery, useResourceNames, } from '@viamrobotics/svelte-sdk';
|
|
3
3
|
import {} from 'koota';
|
|
4
4
|
import { getContext, setContext, untrack } from 'svelte';
|
|
@@ -20,12 +20,14 @@ export const provideGeometries = (partID) => {
|
|
|
20
20
|
const world = useWorld();
|
|
21
21
|
const logs = useLogs();
|
|
22
22
|
const arms = useResourceNames(partID, 'arm');
|
|
23
|
+
const bases = useResourceNames(partID, 'base');
|
|
23
24
|
const cameras = useResourceNames(partID, 'camera');
|
|
24
25
|
const grippers = useResourceNames(partID, 'gripper');
|
|
25
26
|
const gantries = useResourceNames(partID, 'gantry');
|
|
26
27
|
const settings = useSettings();
|
|
27
28
|
const { refreshRates } = $derived(settings.current);
|
|
28
29
|
const armClients = $derived(arms.current.map((arm) => createResourceClient(ArmClient, partID, () => arm.name)));
|
|
30
|
+
const baseClients = $derived(bases.current.map((base) => createResourceClient(BaseClient, partID, () => base.name)));
|
|
29
31
|
const gripperClients = $derived(grippers.current.map((gripper) => createResourceClient(GripperClient, partID, () => gripper.name)));
|
|
30
32
|
const cameraClients = $derived(cameras.current.map((camera) => createResourceClient(CameraClient, partID, () => camera.name)));
|
|
31
33
|
const gantryClients = $derived(gantries.current.map((gantry) => createResourceClient(GantryClient, partID, () => gantry.name)));
|
|
@@ -35,10 +37,17 @@ export const provideGeometries = (partID) => {
|
|
|
35
37
|
refetchInterval: interval === RefetchRates.MANUAL ? false : interval,
|
|
36
38
|
});
|
|
37
39
|
const armQueries = $derived(armClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
|
|
40
|
+
const baseQueries = $derived(baseClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
|
|
38
41
|
const gripperQueries = $derived(gripperClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
|
|
39
42
|
const cameraQueries = $derived(cameraClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
|
|
40
43
|
const gantryQueries = $derived(gantryClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
|
|
41
|
-
const queries = $derived([
|
|
44
|
+
const queries = $derived([
|
|
45
|
+
...armQueries,
|
|
46
|
+
...baseQueries,
|
|
47
|
+
...gripperQueries,
|
|
48
|
+
...cameraQueries,
|
|
49
|
+
...gantryQueries,
|
|
50
|
+
]);
|
|
42
51
|
$effect(() => {
|
|
43
52
|
if (interval === RefetchRates.FPS_30 || interval === RefetchRates.FPS_60) {
|
|
44
53
|
return logs.add(`Fetching geometries every ${interval}ms...`);
|
|
@@ -44,11 +44,11 @@ export const provideLogs = () => {
|
|
|
44
44
|
}
|
|
45
45
|
if (logs.length > 200) {
|
|
46
46
|
const log = logs.pop();
|
|
47
|
-
if (log
|
|
47
|
+
if (log?.level === 'error') {
|
|
48
48
|
errors.splice(errors.indexOf(log), 1);
|
|
49
49
|
}
|
|
50
|
-
else if (log
|
|
51
|
-
warnings.splice(
|
|
50
|
+
else if (log?.level === 'warn') {
|
|
51
|
+
warnings.splice(warnings.indexOf(log), 1);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
});
|
|
@@ -3,6 +3,7 @@ import { type Frame } from '../frame';
|
|
|
3
3
|
export interface PartConfig {
|
|
4
4
|
components: {
|
|
5
5
|
name: string;
|
|
6
|
+
api?: string;
|
|
6
7
|
frame?: Frame;
|
|
7
8
|
}[];
|
|
8
9
|
fragment_mods?: {
|
|
@@ -13,6 +14,7 @@ export interface PartConfig {
|
|
|
13
14
|
interface PartConfigContext {
|
|
14
15
|
current: PartConfig;
|
|
15
16
|
isDirty: boolean;
|
|
17
|
+
hasPendingSave: boolean;
|
|
16
18
|
hasEditPermissions: boolean;
|
|
17
19
|
componentNameToFragmentId: Record<string, string>;
|
|
18
20
|
updateFrame: (componentName: string, referenceFrame: string, pose: Pose, geometry?: Frame['geometry']) => void;
|
|
@@ -20,6 +22,8 @@ interface PartConfigContext {
|
|
|
20
22
|
createFrame: (componentName: string) => void;
|
|
21
23
|
save: () => void;
|
|
22
24
|
discardChanges: () => void;
|
|
25
|
+
clearPendingSave: () => void;
|
|
26
|
+
setPendingSave: () => void;
|
|
23
27
|
}
|
|
24
28
|
export declare const providePartConfig: (partID: () => string, params: () => AppEmbeddedPartConfigProps | undefined) => void;
|
|
25
29
|
export declare const usePartConfig: () => PartConfigContext;
|