@viamrobotics/motion-tools 1.19.0 → 1.21.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/buf/draw/v1/metadata_pb.d.ts +39 -0
- package/dist/buf/draw/v1/metadata_pb.js +55 -0
- package/dist/buf/draw/v1/service_connect.d.ts +34 -1
- package/dist/buf/draw/v1/service_connect.js +34 -1
- package/dist/buf/draw/v1/service_pb.d.ts +136 -0
- package/dist/buf/draw/v1/service_pb.js +201 -0
- package/dist/components/Entities/Arrows/ArrowGroups.svelte +1 -0
- package/dist/components/Entities/Arrows/Arrows.svelte +1 -1
- package/dist/components/Entities/Entities.svelte +1 -0
- package/dist/components/Entities/Points.svelte +23 -23
- package/dist/components/Entities/hooks/useEntityEvents.svelte.js +18 -1
- package/dist/components/FileDrop/FileDrop.svelte +8 -1
- package/dist/components/PCD.svelte +9 -1
- package/dist/components/PCD.svelte.d.ts +2 -0
- package/dist/components/SceneProviders.svelte +2 -0
- package/dist/components/Snapshot.svelte +12 -7
- package/dist/components/overlay/AddRelationship.svelte +25 -3
- package/dist/components/overlay/Details.svelte +293 -227
- package/dist/draw.d.ts +22 -9
- package/dist/draw.js +75 -46
- package/dist/ecs/relations.js +1 -1
- package/dist/ecs/traits.d.ts +2 -0
- package/dist/ecs/traits.js +63 -0
- package/dist/hooks/useDrawService.svelte.d.ts +2 -0
- package/dist/hooks/useDrawService.svelte.js +139 -20
- package/dist/hooks/useRelationships.svelte.d.ts +12 -0
- package/dist/hooks/useRelationships.svelte.js +78 -0
- package/dist/hooks/useWorldState.svelte.js +10 -4
- package/dist/metadata.d.ts +7 -3
- package/dist/metadata.js +26 -2
- package/dist/snapshot.d.ts +6 -1
- package/dist/snapshot.js +10 -5
- package/package.json +5 -2
package/dist/draw.d.ts
CHANGED
|
@@ -2,18 +2,31 @@ import type { TransformWithUUID } from '@viamrobotics/sdk';
|
|
|
2
2
|
import type { Entity, Trait, World } from 'koota';
|
|
3
3
|
import type { Transform as TransformProto } from './buf/common/v1/common_pb';
|
|
4
4
|
import type { Drawing } from './buf/draw/v1/drawing_pb';
|
|
5
|
+
import type { Relationship } from './metadata';
|
|
5
6
|
import { type Metadata } from './metadata';
|
|
6
7
|
export type Transform = TransformWithUUID | TransformProto;
|
|
7
|
-
|
|
8
|
+
export declare const uuidBytesToString: (bytes: Uint8Array | undefined) => string | undefined;
|
|
9
|
+
export declare const uuidStringToBytes: (uuid: string) => Uint8Array<ArrayBuffer>;
|
|
10
|
+
interface DrawOptions {
|
|
8
11
|
removable?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare const drawTransform: (world: World, { referenceFrame, poseInObserverFrame, physicalObject, metadata, uuid }: Transform, api: Trait, { removable }?: DrawOptions) => {
|
|
14
|
+
entity: Entity;
|
|
15
|
+
relationships: import("@bufbuild/protobuf").PlainMessage<import("./buf/draw/v1/metadata_pb").Relationship>[] | undefined;
|
|
9
16
|
};
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
export interface DrawingResult {
|
|
18
|
+
entity: Entity;
|
|
19
|
+
relationships: Relationship[] | undefined;
|
|
20
|
+
}
|
|
21
|
+
export declare const drawDrawing: (world: World, drawing: Drawing, api: Trait, { removable }?: DrawOptions) => DrawingResult;
|
|
22
|
+
export declare const updateTransform: (entity: Entity, { poseInObserverFrame, physicalObject, metadata }: Transform, { removable }?: DrawOptions) => {
|
|
23
|
+
entity: Entity;
|
|
24
|
+
relationships: import("@bufbuild/protobuf").PlainMessage<import("./buf/draw/v1/metadata_pb").Relationship>[] | undefined;
|
|
25
|
+
};
|
|
26
|
+
interface MetadataOptions {
|
|
14
27
|
pointCloud?: boolean;
|
|
15
|
-
}
|
|
16
|
-
export declare const
|
|
17
|
-
export declare const
|
|
18
|
-
export declare const
|
|
28
|
+
}
|
|
29
|
+
export declare const updateMetadata: (entity: Entity, metadata: Metadata, { pointCloud }?: MetadataOptions) => void;
|
|
30
|
+
export declare const updateDrawing: (world: World, entity: Entity, drawing: Drawing, { removable }?: DrawOptions) => DrawingResult;
|
|
31
|
+
export declare const updateModel: (world: World, entity: Entity, drawing: Drawing, api: Trait, { removable }?: DrawOptions) => DrawingResult;
|
|
19
32
|
export {};
|
package/dist/draw.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Vector3, Vector4 } from 'three';
|
|
2
2
|
import { NURBSCurve } from 'three/addons/curves/NURBSCurve.js';
|
|
3
|
+
import { UuidTool } from 'uuid-tool';
|
|
3
4
|
import { createBufferGeometry, preAllocateBufferGeometry, updateBufferGeometry, writeBufferGeometryRange, } from './attribute';
|
|
4
5
|
import { asFloat32Array, asOpacity, asRGB, inMeters, isSingleColor, isVertexColors, STRIDE, } from './buffer';
|
|
5
|
-
import { traits } from './ecs';
|
|
6
|
+
import { relations, traits } from './ecs';
|
|
6
7
|
import { parsePcdInWorker } from './loaders/pcd';
|
|
7
8
|
import { metadataFromStruct } from './metadata';
|
|
8
9
|
import { createPose } from './transform';
|
|
@@ -21,12 +22,28 @@ const DEFAULT_LINE_DOT_COLORS = new Uint8Array([0, 0, 139]);
|
|
|
21
22
|
const DEFAULT_POINTS_COLORS = new Uint8Array([51, 51, 51]);
|
|
22
23
|
const DEFAULT_NURBS_COLORS = new Uint8Array([0, 255, 255]);
|
|
23
24
|
const DEFAULT_OPACITY = 1;
|
|
24
|
-
export const
|
|
25
|
+
export const uuidBytesToString = (bytes) => {
|
|
26
|
+
if (!bytes || bytes.length === 0)
|
|
27
|
+
return undefined;
|
|
28
|
+
return UuidTool.toString([...bytes]);
|
|
29
|
+
};
|
|
30
|
+
export const uuidStringToBytes = (uuid) => {
|
|
31
|
+
const arr = new Uint8Array(16);
|
|
32
|
+
arr.set(UuidTool.toBytes(uuid));
|
|
33
|
+
return arr;
|
|
34
|
+
};
|
|
35
|
+
const isModel = (drawing) => {
|
|
36
|
+
return drawing.physicalObject?.geometryType?.case === 'model';
|
|
37
|
+
};
|
|
38
|
+
export const drawTransform = (world, { referenceFrame, poseInObserverFrame, physicalObject, metadata, uuid }, api, { removable = true } = {}) => {
|
|
25
39
|
const entityTraits = [
|
|
26
40
|
traits.Name(referenceFrame),
|
|
27
41
|
traits.Pose(createPose(poseInObserverFrame?.pose)),
|
|
28
42
|
api,
|
|
29
43
|
];
|
|
44
|
+
const uuidStr = uuidBytesToString(uuid);
|
|
45
|
+
if (uuidStr)
|
|
46
|
+
entityTraits.push(traits.UUID(uuidStr));
|
|
30
47
|
if (physicalObject) {
|
|
31
48
|
entityTraits.push(traits.Geometry(physicalObject));
|
|
32
49
|
const center = physicalObject.center;
|
|
@@ -36,7 +53,7 @@ export const drawTransform = (world, { referenceFrame, poseInObserverFrame, phys
|
|
|
36
53
|
else {
|
|
37
54
|
entityTraits.push(traits.ReferenceFrame);
|
|
38
55
|
}
|
|
39
|
-
if (
|
|
56
|
+
if (removable)
|
|
40
57
|
entityTraits.push(traits.Removable);
|
|
41
58
|
entityTraits.push(...traits.getParentTrait(poseInObserverFrame?.referenceFrame));
|
|
42
59
|
const parsedMetadata = metadataFromStruct(metadata?.fields);
|
|
@@ -60,23 +77,28 @@ export const drawTransform = (world, { referenceFrame, poseInObserverFrame, phys
|
|
|
60
77
|
const entity = world.spawn(...entityTraits);
|
|
61
78
|
if (pointCloud)
|
|
62
79
|
parsePointCloud(world, entity, pointCloud, parsedMetadata);
|
|
63
|
-
return entity;
|
|
80
|
+
return { entity, relationships: parsedMetadata.relationships };
|
|
64
81
|
};
|
|
65
|
-
export const drawDrawing = (world, drawing, api,
|
|
66
|
-
const { referenceFrame, poseInObserverFrame,
|
|
67
|
-
if (
|
|
68
|
-
return drawModel(world, drawing, api,
|
|
69
|
-
|
|
70
|
-
|
|
82
|
+
export const drawDrawing = (world, drawing, api, { removable = true } = {}) => {
|
|
83
|
+
const { referenceFrame, poseInObserverFrame, metadata, uuid } = drawing;
|
|
84
|
+
if (isModel(drawing)) {
|
|
85
|
+
return drawModel(world, drawing, api, { removable });
|
|
86
|
+
}
|
|
87
|
+
const uuidTraits = [];
|
|
88
|
+
const uuidStr = uuidBytesToString(uuid);
|
|
89
|
+
if (uuidStr)
|
|
90
|
+
uuidTraits.push(traits.UUID(uuidStr));
|
|
91
|
+
const entity = world.spawn(traits.Name(referenceFrame), traits.Pose(createPose(poseInObserverFrame?.pose)), api, ...traits.getParentTrait(poseInObserverFrame?.referenceFrame), ...uuidTraits);
|
|
92
|
+
if (removable)
|
|
71
93
|
entity.add(traits.Removable);
|
|
72
94
|
if (metadata?.showAxesHelper)
|
|
73
95
|
entity.add(traits.ShowAxesHelper);
|
|
74
96
|
if (metadata?.invisible)
|
|
75
97
|
entity.add(traits.Invisible);
|
|
76
98
|
applyShape(entity, drawing);
|
|
77
|
-
return
|
|
99
|
+
return { entity, relationships: metadata?.relationships };
|
|
78
100
|
};
|
|
79
|
-
export const updateTransform = (entity, { poseInObserverFrame, physicalObject, metadata },
|
|
101
|
+
export const updateTransform = (entity, { poseInObserverFrame, physicalObject, metadata }, { removable = true } = {}) => {
|
|
80
102
|
entity.set(traits.Pose, createPose(poseInObserverFrame?.pose));
|
|
81
103
|
traits.setParentTrait(entity, poseInObserverFrame?.referenceFrame);
|
|
82
104
|
if (physicalObject) {
|
|
@@ -89,13 +111,15 @@ export const updateTransform = (entity, { poseInObserverFrame, physicalObject, m
|
|
|
89
111
|
entity.remove(traits.Center);
|
|
90
112
|
}
|
|
91
113
|
}
|
|
92
|
-
|
|
114
|
+
const parsedMetadata = metadataFromStruct(metadata?.fields);
|
|
115
|
+
updateMetadata(entity, parsedMetadata, {
|
|
93
116
|
pointCloud: isPointCloud(physicalObject?.geometryType),
|
|
94
117
|
});
|
|
95
|
-
if (
|
|
118
|
+
if (removable)
|
|
96
119
|
entity.add(traits.Removable);
|
|
97
|
-
if (!
|
|
120
|
+
if (!removable)
|
|
98
121
|
entity.remove(traits.Removable);
|
|
122
|
+
return { entity, relationships: parsedMetadata.relationships };
|
|
99
123
|
};
|
|
100
124
|
export const updateMetadata = (entity, metadata, { pointCloud = false } = {}) => {
|
|
101
125
|
if (metadata.showAxesHelper)
|
|
@@ -109,28 +133,17 @@ export const updateMetadata = (entity, metadata, { pointCloud = false } = {}) =>
|
|
|
109
133
|
const { colors, opacities } = metadata;
|
|
110
134
|
if (colors) {
|
|
111
135
|
if (pointCloud) {
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
setColorTraits(entity, colors);
|
|
136
|
+
updatePointCloudColors(entity, metadata);
|
|
116
137
|
}
|
|
138
|
+
// Always set color traits so any subsequent async work can read them
|
|
139
|
+
setColorTraits(entity, colors);
|
|
117
140
|
}
|
|
118
141
|
entity.set(traits.Opacity, asOpacity(opacities, DEFAULT_OPACITY));
|
|
119
142
|
};
|
|
120
|
-
export const updateDrawing = (world,
|
|
121
|
-
const { poseInObserverFrame,
|
|
122
|
-
if (physicalObject?.geometryType?.case === 'model') {
|
|
123
|
-
for (const entity of entities) {
|
|
124
|
-
if (world.has(entity))
|
|
125
|
-
entity.destroy();
|
|
126
|
-
}
|
|
127
|
-
return drawDrawing(world, drawing, api, options);
|
|
128
|
-
}
|
|
129
|
-
if (entities.length === 0)
|
|
130
|
-
return entities;
|
|
131
|
-
const entity = entities[0];
|
|
143
|
+
export const updateDrawing = (world, entity, drawing, { removable = true } = {}) => {
|
|
144
|
+
const { poseInObserverFrame, metadata } = drawing;
|
|
132
145
|
if (!world.has(entity))
|
|
133
|
-
return
|
|
146
|
+
return { entity, relationships: metadata?.relationships };
|
|
134
147
|
entity.set(traits.Pose, createPose(poseInObserverFrame?.pose));
|
|
135
148
|
traits.setParentTrait(entity, poseInObserverFrame?.referenceFrame);
|
|
136
149
|
if (metadata?.showAxesHelper)
|
|
@@ -141,8 +154,17 @@ export const updateDrawing = (world, entities, drawing, api, options = { removab
|
|
|
141
154
|
entity.add(traits.Invisible);
|
|
142
155
|
if (!metadata?.invisible)
|
|
143
156
|
entity.remove(traits.Invisible);
|
|
157
|
+
if (removable)
|
|
158
|
+
entity.add(traits.Removable);
|
|
159
|
+
if (!removable)
|
|
160
|
+
entity.remove(traits.Removable);
|
|
144
161
|
updateShape(entity, drawing);
|
|
145
|
-
return
|
|
162
|
+
return { entity, relationships: metadata?.relationships };
|
|
163
|
+
};
|
|
164
|
+
export const updateModel = (world, entity, drawing, api, { removable = true } = {}) => {
|
|
165
|
+
if (world.has(entity))
|
|
166
|
+
entity.destroy();
|
|
167
|
+
return drawDrawing(world, drawing, api, { removable });
|
|
146
168
|
};
|
|
147
169
|
const applyShape = (entity, { physicalObject, metadata }) => {
|
|
148
170
|
const colors = metadata?.colors;
|
|
@@ -243,32 +265,39 @@ const applyShape = (entity, { physicalObject, metadata }) => {
|
|
|
243
265
|
}
|
|
244
266
|
}
|
|
245
267
|
};
|
|
246
|
-
const drawModel = (world,
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
if (geometryType?.case !== 'model')
|
|
250
|
-
return entities;
|
|
268
|
+
const drawModel = (world, model, api, { removable = true }) => {
|
|
269
|
+
const { referenceFrame, physicalObject, poseInObserverFrame, metadata, uuid } = model;
|
|
270
|
+
const { animationName, assets, scale } = physicalObject.geometryType.value;
|
|
251
271
|
const baseTraits = [
|
|
252
272
|
traits.Name(referenceFrame),
|
|
253
273
|
traits.Pose(createPose(poseInObserverFrame?.pose)),
|
|
254
274
|
api,
|
|
255
275
|
...traits.getParentTrait(poseInObserverFrame?.referenceFrame),
|
|
256
276
|
];
|
|
277
|
+
const uuidStr = uuidBytesToString(uuid);
|
|
278
|
+
if (uuidStr)
|
|
279
|
+
baseTraits.push(traits.UUID(uuidStr));
|
|
257
280
|
if (removable)
|
|
258
281
|
baseTraits.push(traits.Removable);
|
|
259
282
|
if (metadata?.invisible)
|
|
260
283
|
baseTraits.push(traits.Invisible);
|
|
261
|
-
|
|
262
|
-
|
|
284
|
+
if (metadata?.showAxesHelper)
|
|
285
|
+
baseTraits.push(traits.ShowAxesHelper);
|
|
286
|
+
const root = world.spawn(...baseTraits, traits.ReferenceFrame);
|
|
263
287
|
let i = 1;
|
|
264
|
-
for (const asset of
|
|
288
|
+
for (const asset of assets) {
|
|
265
289
|
const subEntityTraits = [
|
|
266
290
|
traits.Name(`${referenceFrame} model ${i++}`),
|
|
267
291
|
traits.Parent(referenceFrame),
|
|
292
|
+
relations.ChildOf(root),
|
|
268
293
|
api,
|
|
269
294
|
];
|
|
270
295
|
if (scale)
|
|
271
296
|
subEntityTraits.push(traits.Scale(scale));
|
|
297
|
+
if (metadata?.invisible)
|
|
298
|
+
subEntityTraits.push(traits.Invisible);
|
|
299
|
+
if (metadata?.showAxesHelper)
|
|
300
|
+
subEntityTraits.push(traits.ShowAxesHelper);
|
|
272
301
|
if (asset.content.case === 'url') {
|
|
273
302
|
subEntityTraits.push(traits.GLTF({
|
|
274
303
|
source: { url: asset.content.value },
|
|
@@ -281,9 +310,9 @@ const drawModel = (world, { referenceFrame, poseInObserverFrame, physicalObject,
|
|
|
281
310
|
animationName: animationName ?? DEFAULT_ANIMATION_NAME,
|
|
282
311
|
}));
|
|
283
312
|
}
|
|
284
|
-
|
|
313
|
+
world.spawn(...subEntityTraits);
|
|
285
314
|
}
|
|
286
|
-
return
|
|
315
|
+
return { entity: root, relationships: metadata?.relationships };
|
|
287
316
|
};
|
|
288
317
|
const parsePointCloud = (world, entity, pointCloud, metadata) => {
|
|
289
318
|
parsePcdInWorker(new Uint8Array(pointCloud)).then((pointcloud) => {
|
|
@@ -315,7 +344,7 @@ const parsePointCloud = (world, entity, pointCloud, metadata) => {
|
|
|
315
344
|
entity.add(traits.Points);
|
|
316
345
|
});
|
|
317
346
|
};
|
|
318
|
-
const
|
|
347
|
+
const updatePointCloudColors = (entity, metadata) => {
|
|
319
348
|
const buffer = entity.get(traits.BufferGeometry);
|
|
320
349
|
if (!buffer) {
|
|
321
350
|
if (metadata.colors)
|
|
@@ -423,7 +452,7 @@ const updateShape = (entity, { physicalObject, metadata }) => {
|
|
|
423
452
|
}
|
|
424
453
|
}
|
|
425
454
|
};
|
|
426
|
-
|
|
455
|
+
const addColorTraits = (entity, colors) => {
|
|
427
456
|
if (isVertexColors(colors)) {
|
|
428
457
|
entity.add(traits.Colors(colors));
|
|
429
458
|
}
|
|
@@ -431,7 +460,7 @@ export const addColorTraits = (entity, colors) => {
|
|
|
431
460
|
entity.add(traits.Color(asRGB(colors, rgb)));
|
|
432
461
|
}
|
|
433
462
|
};
|
|
434
|
-
|
|
463
|
+
const setColorTraits = (entity, colors) => {
|
|
435
464
|
if (isVertexColors(colors)) {
|
|
436
465
|
if (entity.has(traits.Colors))
|
|
437
466
|
entity.set(traits.Colors, colors);
|
package/dist/ecs/relations.js
CHANGED
package/dist/ecs/traits.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ 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>;
|
|
7
|
+
export declare const UUID: import("koota").Trait<() => string>;
|
|
7
8
|
export declare const Pose: import("koota").Trait<{
|
|
8
9
|
x: number;
|
|
9
10
|
y: number;
|
|
@@ -77,6 +78,7 @@ export declare const Color: import("koota").Trait<{
|
|
|
77
78
|
*/
|
|
78
79
|
export declare const Material: import("koota").Trait<{
|
|
79
80
|
depthTest: boolean;
|
|
81
|
+
depthWrite: boolean;
|
|
80
82
|
}>;
|
|
81
83
|
export declare const DepthTest: import("koota").Trait<() => boolean>;
|
|
82
84
|
export declare const Arrow: import("koota").Trait<() => boolean>;
|
package/dist/ecs/traits.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
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');
|
|
11
|
+
export const UUID = trait(() => '');
|
|
8
12
|
export const Pose = trait({ x: 0, y: 0, z: 0, oX: 0, oY: 0, oZ: 1, theta: 0 });
|
|
9
13
|
export const EditedPose = trait({ x: 0, y: 0, z: 0, oX: 0, oY: 0, oZ: 1, theta: 0 });
|
|
10
14
|
export const Center = trait({ x: 0, y: 0, z: 0, oX: 0, oY: 0, oZ: 1, theta: 0 });
|
|
@@ -50,6 +54,7 @@ export const Color = trait({ r: 0, g: 0, b: 0 });
|
|
|
50
54
|
*/
|
|
51
55
|
export const Material = trait({
|
|
52
56
|
depthTest: false,
|
|
57
|
+
depthWrite: true,
|
|
53
58
|
});
|
|
54
59
|
export const DepthTest = trait(() => true);
|
|
55
60
|
export const Arrow = trait(() => true);
|
|
@@ -203,4 +208,62 @@ export const updateGeometryTrait = (entity, geometry) => {
|
|
|
203
208
|
entity.add(BufferGeometry(parsePlyInput(geometry.geometryType.value.mesh)));
|
|
204
209
|
}
|
|
205
210
|
}
|
|
211
|
+
else if (geometry.geometryType.case === 'pointcloud') {
|
|
212
|
+
updatePointCloud(entity, geometry.geometryType.value.pointCloud);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
const updatePointCloud = (entity, pointCloud) => {
|
|
216
|
+
parsePcdInWorker(new Uint8Array(pointCloud))
|
|
217
|
+
.then((parsed) => {
|
|
218
|
+
if (!entity.isAlive())
|
|
219
|
+
return;
|
|
220
|
+
const buffer = entity.get(BufferGeometry);
|
|
221
|
+
let colors = parsed.colors;
|
|
222
|
+
if (buffer) {
|
|
223
|
+
// Reapply single color trait if the point count changed
|
|
224
|
+
if (parsed.colors === undefined) {
|
|
225
|
+
const color = entity.get(Color);
|
|
226
|
+
if (color) {
|
|
227
|
+
const newCount = parsed.positions.length / 3;
|
|
228
|
+
colors = new Uint8Array(newCount * 3);
|
|
229
|
+
const r = Math.round(color.r * 255);
|
|
230
|
+
const g = Math.round(color.g * 255);
|
|
231
|
+
const b = Math.round(color.b * 255);
|
|
232
|
+
for (let i = 0; i < newCount; i++) {
|
|
233
|
+
colors[i * 3] = r;
|
|
234
|
+
colors[i * 3 + 1] = g;
|
|
235
|
+
colors[i * 3 + 2] = b;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// When the point count changes, attributes must be reallocated.
|
|
240
|
+
const oldCount = buffer.getAttribute('position').count;
|
|
241
|
+
const newCount = parsed.positions.length / 3;
|
|
242
|
+
if (oldCount === newCount) {
|
|
243
|
+
updateBufferGeometry(buffer, parsed.positions, {
|
|
244
|
+
colors,
|
|
245
|
+
colorFormat: ColorFormat.RGB,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
const fresh = createBufferGeometry(parsed.positions, {
|
|
250
|
+
colors,
|
|
251
|
+
colorFormat: ColorFormat.RGB,
|
|
252
|
+
});
|
|
253
|
+
buffer.dispose();
|
|
254
|
+
entity.set(BufferGeometry, fresh);
|
|
255
|
+
}
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
entity.remove(Box, Capsule, Sphere);
|
|
259
|
+
entity.add(BufferGeometry(createBufferGeometry(parsed.positions, {
|
|
260
|
+
colors: parsed.colors,
|
|
261
|
+
colorFormat: ColorFormat.RGB,
|
|
262
|
+
})));
|
|
263
|
+
if (!entity.has(Points))
|
|
264
|
+
entity.add(Points);
|
|
265
|
+
})
|
|
266
|
+
.catch((error) => {
|
|
267
|
+
console.error('Failed to update pointcloud buffer geometry:', error);
|
|
268
|
+
});
|
|
206
269
|
};
|
|
@@ -6,6 +6,8 @@ declare const ConnectionStatus: {
|
|
|
6
6
|
type ConnectionStatusType = (typeof ConnectionStatus)[keyof typeof ConnectionStatus];
|
|
7
7
|
interface Context {
|
|
8
8
|
connectionStatus: ConnectionStatusType;
|
|
9
|
+
createRelationship: (sourceUuid: string, targetUuid: string, type: string, indexMapping?: string) => Promise<void>;
|
|
10
|
+
deleteRelationship: (sourceUuid: string, targetUuid: string) => Promise<void>;
|
|
9
11
|
}
|
|
10
12
|
export declare function provideDrawService(): void;
|
|
11
13
|
export declare function useDrawService(): Context;
|
|
@@ -5,13 +5,17 @@ import { useThrelte } from '@threlte/core';
|
|
|
5
5
|
import {} from 'koota';
|
|
6
6
|
import { getContext, setContext } from 'svelte';
|
|
7
7
|
import { UuidTool } from 'uuid-tool';
|
|
8
|
+
import { writeBufferGeometryRange } from '../attribute';
|
|
8
9
|
import { DrawService } from '../buf/draw/v1/service_connect';
|
|
9
|
-
import { EntityChangeType, StreamEntityChangesResponse } from '../buf/draw/v1/service_pb';
|
|
10
|
-
import {
|
|
10
|
+
import { CreateRelationshipRequest, DeleteRelationshipRequest, EntityChangeType, StreamEntityChangesResponse, } from '../buf/draw/v1/service_pb';
|
|
11
|
+
import { asFloat32Array, inMeters, STRIDE } from '../buffer';
|
|
12
|
+
import { drawDrawing, drawTransform, updateDrawing, updateModel, updateTransform, uuidStringToBytes, } from '../draw';
|
|
11
13
|
import { traits, useWorld } from '../ecs';
|
|
12
14
|
import { useCameraControls } from './useControls.svelte';
|
|
13
15
|
import { useDrawConnectionConfig } from './useDrawConnectionConfig.svelte';
|
|
16
|
+
import { useRelationships } from './useRelationships.svelte';
|
|
14
17
|
const DRAW_SERVICE_KEY = Symbol('draw-service-context');
|
|
18
|
+
const FLOAT32_SIZE = 4;
|
|
15
19
|
const ConnectionStatus = {
|
|
16
20
|
CONNECTED: 'connected',
|
|
17
21
|
DISCONNECTED: 'disconnected',
|
|
@@ -22,6 +26,7 @@ export function provideDrawService() {
|
|
|
22
26
|
const world = useWorld();
|
|
23
27
|
const cameraControls = useCameraControls();
|
|
24
28
|
const drawConnectionConfig = useDrawConnectionConfig();
|
|
29
|
+
const relationships = useRelationships();
|
|
25
30
|
let connectionStatus = $state(ConnectionStatus.DISCONNECTED);
|
|
26
31
|
const url = $derived(drawConnectionConfig.current?.backendIP
|
|
27
32
|
? `http://${drawConnectionConfig.current.backendIP}:3030`
|
|
@@ -30,6 +35,9 @@ export function provideDrawService() {
|
|
|
30
35
|
const drawingEntities = new Map();
|
|
31
36
|
let pendingEvents = [];
|
|
32
37
|
let flushScheduled = false;
|
|
38
|
+
let activeClient;
|
|
39
|
+
let activeSignal;
|
|
40
|
+
const activeChunkPulls = new Set();
|
|
33
41
|
const destroyTransform = (uuidStr) => {
|
|
34
42
|
const entity = transformEntities.get(uuidStr);
|
|
35
43
|
if (!entity)
|
|
@@ -39,13 +47,11 @@ export function provideDrawService() {
|
|
|
39
47
|
transformEntities.delete(uuidStr);
|
|
40
48
|
};
|
|
41
49
|
const destroyDrawing = (uuidStr) => {
|
|
42
|
-
const
|
|
43
|
-
if (!
|
|
50
|
+
const entity = drawingEntities.get(uuidStr);
|
|
51
|
+
if (!entity)
|
|
44
52
|
return;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
entity.destroy();
|
|
48
|
-
}
|
|
53
|
+
if (world.has(entity))
|
|
54
|
+
entity.destroy();
|
|
49
55
|
drawingEntities.delete(uuidStr);
|
|
50
56
|
};
|
|
51
57
|
const processEvent = (event) => {
|
|
@@ -62,7 +68,9 @@ export function provideDrawService() {
|
|
|
62
68
|
if (changeType === EntityChangeType.ADDED) {
|
|
63
69
|
if (!transformEntities.has(uuid)) {
|
|
64
70
|
const spawned = drawTransform(world, transform, traits.DrawServiceAPI);
|
|
65
|
-
|
|
71
|
+
relationships.apply(spawned.entity, spawned.relationships);
|
|
72
|
+
transformEntities.set(uuid, spawned.entity);
|
|
73
|
+
relationships.flush(uuid);
|
|
66
74
|
}
|
|
67
75
|
}
|
|
68
76
|
else if (changeType === EntityChangeType.REMOVED) {
|
|
@@ -71,11 +79,76 @@ export function provideDrawService() {
|
|
|
71
79
|
else if (changeType === EntityChangeType.UPDATED) {
|
|
72
80
|
const existing = transformEntities.get(uuid);
|
|
73
81
|
if (existing) {
|
|
74
|
-
updateTransform(existing, transform);
|
|
82
|
+
const updated = updateTransform(existing, transform);
|
|
83
|
+
relationships.apply(updated.entity, updated.relationships);
|
|
75
84
|
}
|
|
76
85
|
else {
|
|
77
86
|
const spawned = drawTransform(world, transform, traits.DrawServiceAPI);
|
|
78
|
-
|
|
87
|
+
relationships.apply(spawned.entity, spawned.relationships);
|
|
88
|
+
transformEntities.set(uuid, spawned.entity);
|
|
89
|
+
relationships.flush(uuid);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const isChunkedDrawing = (drawing) => {
|
|
94
|
+
return drawing.metadata?.chunks !== undefined && drawing.metadata.chunks.total > 0;
|
|
95
|
+
};
|
|
96
|
+
const getChunkInfo = (drawing) => {
|
|
97
|
+
const meta = drawing.metadata?.chunks;
|
|
98
|
+
if (!meta || meta.total === 0)
|
|
99
|
+
return undefined;
|
|
100
|
+
const shape = drawing.physicalObject?.geometryType;
|
|
101
|
+
if (shape?.case === 'points') {
|
|
102
|
+
const chunkElements = shape.value.positions.length / (STRIDE.POSITIONS * FLOAT32_SIZE);
|
|
103
|
+
return {
|
|
104
|
+
total: meta.total,
|
|
105
|
+
firstEnd: chunkElements,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
};
|
|
110
|
+
const pullChunks = async (client, uuid, uuidBytes, entity, totalElements, firstChunkEnd, signal) => {
|
|
111
|
+
if (activeChunkPulls.has(uuid))
|
|
112
|
+
return;
|
|
113
|
+
activeChunkPulls.add(uuid);
|
|
114
|
+
try {
|
|
115
|
+
let nextStart = firstChunkEnd;
|
|
116
|
+
while (!signal.aborted) {
|
|
117
|
+
const response = await client.getEntityChunk({ uuid: uuidBytes, start: nextStart }, { signal });
|
|
118
|
+
// done with no payload is the server's "past end" sentinel (startByte >= posLen), not the final real chunk
|
|
119
|
+
if (response.done && !response.entity.value)
|
|
120
|
+
break;
|
|
121
|
+
const drawing = response.entity.case === 'drawing' ? response.entity.value : undefined;
|
|
122
|
+
if (!drawing)
|
|
123
|
+
break;
|
|
124
|
+
const shape = drawing.physicalObject?.geometryType;
|
|
125
|
+
if (shape?.case !== 'points')
|
|
126
|
+
break;
|
|
127
|
+
const buffer = entity.get(traits.BufferGeometry);
|
|
128
|
+
if (!buffer)
|
|
129
|
+
break;
|
|
130
|
+
const positions = asFloat32Array(shape.value.positions, inMeters);
|
|
131
|
+
const metadata = drawing.metadata;
|
|
132
|
+
if (!metadata)
|
|
133
|
+
break;
|
|
134
|
+
writeBufferGeometryRange(buffer, positions, response.start, metadata);
|
|
135
|
+
const chunkElements = positions.length / 3;
|
|
136
|
+
nextStart = response.start + chunkElements;
|
|
137
|
+
entity.set(traits.ChunkProgress, { loaded: nextStart, total: totalElements });
|
|
138
|
+
invalidate();
|
|
139
|
+
if (response.done)
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (!signal.aborted) {
|
|
145
|
+
console.error(`Chunk pull failed for entity ${uuid}:`, error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
activeChunkPulls.delete(uuid);
|
|
150
|
+
if (world.has(entity)) {
|
|
151
|
+
entity.remove(traits.ChunkProgress);
|
|
79
152
|
}
|
|
80
153
|
}
|
|
81
154
|
};
|
|
@@ -83,7 +156,17 @@ export function provideDrawService() {
|
|
|
83
156
|
if (changeType === EntityChangeType.ADDED) {
|
|
84
157
|
if (!drawingEntities.has(uuid)) {
|
|
85
158
|
const spawned = drawDrawing(world, drawing, traits.DrawServiceAPI);
|
|
86
|
-
|
|
159
|
+
relationships.apply(spawned.entity, spawned.relationships);
|
|
160
|
+
drawingEntities.set(uuid, spawned.entity);
|
|
161
|
+
relationships.flush(uuid);
|
|
162
|
+
if (isChunkedDrawing(drawing) && activeClient && activeSignal) {
|
|
163
|
+
const chunk = getChunkInfo(drawing);
|
|
164
|
+
if (chunk) {
|
|
165
|
+
spawned.entity.add(traits.ChunkProgress({ loaded: chunk.firstEnd, total: chunk.total }));
|
|
166
|
+
const uuidBytes = drawing.uuid ?? new Uint8Array();
|
|
167
|
+
void pullChunks(activeClient, uuid, uuidBytes, spawned.entity, chunk.total, chunk.firstEnd, activeSignal);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
87
170
|
}
|
|
88
171
|
}
|
|
89
172
|
else if (changeType === EntityChangeType.REMOVED) {
|
|
@@ -92,12 +175,18 @@ export function provideDrawService() {
|
|
|
92
175
|
else if (changeType === EntityChangeType.UPDATED) {
|
|
93
176
|
const existing = drawingEntities.get(uuid);
|
|
94
177
|
if (existing) {
|
|
95
|
-
const
|
|
96
|
-
|
|
178
|
+
const isModel = drawing.physicalObject?.geometryType?.case === 'model';
|
|
179
|
+
const result = isModel
|
|
180
|
+
? updateModel(world, existing, drawing, traits.DrawServiceAPI)
|
|
181
|
+
: updateDrawing(world, existing, drawing);
|
|
182
|
+
relationships.apply(result.entity, result.relationships);
|
|
183
|
+
drawingEntities.set(uuid, result.entity);
|
|
97
184
|
}
|
|
98
185
|
else {
|
|
99
186
|
const spawned = drawDrawing(world, drawing, traits.DrawServiceAPI);
|
|
100
|
-
|
|
187
|
+
relationships.apply(spawned.entity, spawned.relationships);
|
|
188
|
+
drawingEntities.set(uuid, spawned.entity);
|
|
189
|
+
relationships.flush(uuid);
|
|
101
190
|
}
|
|
102
191
|
}
|
|
103
192
|
};
|
|
@@ -201,38 +290,68 @@ export function provideDrawService() {
|
|
|
201
290
|
}
|
|
202
291
|
}
|
|
203
292
|
};
|
|
293
|
+
const createRelationship = async (sourceUuid, targetUuid, type, indexMapping) => {
|
|
294
|
+
if (!activeClient)
|
|
295
|
+
return;
|
|
296
|
+
const rel = {
|
|
297
|
+
targetUuid: uuidStringToBytes(targetUuid),
|
|
298
|
+
type,
|
|
299
|
+
};
|
|
300
|
+
if (indexMapping !== undefined) {
|
|
301
|
+
rel.indexMapping = indexMapping;
|
|
302
|
+
}
|
|
303
|
+
await activeClient.createRelationship(new CreateRelationshipRequest({
|
|
304
|
+
sourceUuid: uuidStringToBytes(sourceUuid),
|
|
305
|
+
relationship: rel,
|
|
306
|
+
}));
|
|
307
|
+
};
|
|
308
|
+
const deleteRelationship = async (sourceUuid, targetUuid) => {
|
|
309
|
+
if (!activeClient)
|
|
310
|
+
return;
|
|
311
|
+
await activeClient.deleteRelationship(new DeleteRelationshipRequest({
|
|
312
|
+
sourceUuid: uuidStringToBytes(sourceUuid),
|
|
313
|
+
targetUuid: uuidStringToBytes(targetUuid),
|
|
314
|
+
}));
|
|
315
|
+
};
|
|
204
316
|
$effect(() => {
|
|
205
317
|
if (!url) {
|
|
206
318
|
connectionStatus = ConnectionStatus.DISCONNECTED;
|
|
319
|
+
activeClient = undefined;
|
|
207
320
|
return;
|
|
208
321
|
}
|
|
209
322
|
const controller = new AbortController();
|
|
210
323
|
connectionStatus = ConnectionStatus.CONNECTING;
|
|
211
324
|
const transport = createConnectTransport({ baseUrl: url });
|
|
212
325
|
const client = createClient(DrawService, transport);
|
|
326
|
+
activeClient = client;
|
|
327
|
+
activeSignal = controller.signal;
|
|
213
328
|
void streamEntityChanges(client, controller.signal);
|
|
214
329
|
void streamSceneChanges(client, controller.signal);
|
|
215
330
|
return () => {
|
|
216
331
|
controller.abort();
|
|
332
|
+
activeClient = undefined;
|
|
333
|
+
activeSignal = undefined;
|
|
217
334
|
connectionStatus = ConnectionStatus.DISCONNECTED;
|
|
335
|
+
activeClient = undefined;
|
|
218
336
|
for (const entity of transformEntities.values()) {
|
|
219
337
|
if (world.has(entity))
|
|
220
338
|
entity.destroy();
|
|
221
339
|
}
|
|
222
340
|
transformEntities.clear();
|
|
223
|
-
for (const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
entity.destroy();
|
|
227
|
-
}
|
|
341
|
+
for (const entity of drawingEntities.values()) {
|
|
342
|
+
if (world.has(entity))
|
|
343
|
+
entity.destroy();
|
|
228
344
|
}
|
|
229
345
|
drawingEntities.clear();
|
|
346
|
+
relationships.clear();
|
|
230
347
|
};
|
|
231
348
|
});
|
|
232
349
|
setContext(DRAW_SERVICE_KEY, {
|
|
233
350
|
get connectionStatus() {
|
|
234
351
|
return connectionStatus;
|
|
235
352
|
},
|
|
353
|
+
createRelationship,
|
|
354
|
+
deleteRelationship,
|
|
236
355
|
});
|
|
237
356
|
}
|
|
238
357
|
export function useDrawService() {
|