@viamrobotics/motion-tools 1.19.1 → 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/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 +71 -41
- package/dist/ecs/relations.js +1 -1
- package/dist/ecs/traits.d.ts +2 -0
- package/dist/ecs/traits.js +2 -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)
|
|
@@ -116,20 +140,10 @@ export const updateMetadata = (entity, metadata, { pointCloud = false } = {}) =>
|
|
|
116
140
|
}
|
|
117
141
|
entity.set(traits.Opacity, asOpacity(opacities, DEFAULT_OPACITY));
|
|
118
142
|
};
|
|
119
|
-
export const updateDrawing = (world,
|
|
120
|
-
const { poseInObserverFrame,
|
|
121
|
-
if (physicalObject?.geometryType?.case === 'model') {
|
|
122
|
-
for (const entity of entities) {
|
|
123
|
-
if (world.has(entity))
|
|
124
|
-
entity.destroy();
|
|
125
|
-
}
|
|
126
|
-
return drawDrawing(world, drawing, api, options);
|
|
127
|
-
}
|
|
128
|
-
if (entities.length === 0)
|
|
129
|
-
return entities;
|
|
130
|
-
const entity = entities[0];
|
|
143
|
+
export const updateDrawing = (world, entity, drawing, { removable = true } = {}) => {
|
|
144
|
+
const { poseInObserverFrame, metadata } = drawing;
|
|
131
145
|
if (!world.has(entity))
|
|
132
|
-
return
|
|
146
|
+
return { entity, relationships: metadata?.relationships };
|
|
133
147
|
entity.set(traits.Pose, createPose(poseInObserverFrame?.pose));
|
|
134
148
|
traits.setParentTrait(entity, poseInObserverFrame?.referenceFrame);
|
|
135
149
|
if (metadata?.showAxesHelper)
|
|
@@ -140,8 +154,17 @@ export const updateDrawing = (world, entities, drawing, api, options = { removab
|
|
|
140
154
|
entity.add(traits.Invisible);
|
|
141
155
|
if (!metadata?.invisible)
|
|
142
156
|
entity.remove(traits.Invisible);
|
|
157
|
+
if (removable)
|
|
158
|
+
entity.add(traits.Removable);
|
|
159
|
+
if (!removable)
|
|
160
|
+
entity.remove(traits.Removable);
|
|
143
161
|
updateShape(entity, drawing);
|
|
144
|
-
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 });
|
|
145
168
|
};
|
|
146
169
|
const applyShape = (entity, { physicalObject, metadata }) => {
|
|
147
170
|
const colors = metadata?.colors;
|
|
@@ -242,32 +265,39 @@ const applyShape = (entity, { physicalObject, metadata }) => {
|
|
|
242
265
|
}
|
|
243
266
|
}
|
|
244
267
|
};
|
|
245
|
-
const drawModel = (world,
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
if (geometryType?.case !== 'model')
|
|
249
|
-
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;
|
|
250
271
|
const baseTraits = [
|
|
251
272
|
traits.Name(referenceFrame),
|
|
252
273
|
traits.Pose(createPose(poseInObserverFrame?.pose)),
|
|
253
274
|
api,
|
|
254
275
|
...traits.getParentTrait(poseInObserverFrame?.referenceFrame),
|
|
255
276
|
];
|
|
277
|
+
const uuidStr = uuidBytesToString(uuid);
|
|
278
|
+
if (uuidStr)
|
|
279
|
+
baseTraits.push(traits.UUID(uuidStr));
|
|
256
280
|
if (removable)
|
|
257
281
|
baseTraits.push(traits.Removable);
|
|
258
282
|
if (metadata?.invisible)
|
|
259
283
|
baseTraits.push(traits.Invisible);
|
|
260
|
-
|
|
261
|
-
|
|
284
|
+
if (metadata?.showAxesHelper)
|
|
285
|
+
baseTraits.push(traits.ShowAxesHelper);
|
|
286
|
+
const root = world.spawn(...baseTraits, traits.ReferenceFrame);
|
|
262
287
|
let i = 1;
|
|
263
|
-
for (const asset of
|
|
288
|
+
for (const asset of assets) {
|
|
264
289
|
const subEntityTraits = [
|
|
265
290
|
traits.Name(`${referenceFrame} model ${i++}`),
|
|
266
291
|
traits.Parent(referenceFrame),
|
|
292
|
+
relations.ChildOf(root),
|
|
267
293
|
api,
|
|
268
294
|
];
|
|
269
295
|
if (scale)
|
|
270
296
|
subEntityTraits.push(traits.Scale(scale));
|
|
297
|
+
if (metadata?.invisible)
|
|
298
|
+
subEntityTraits.push(traits.Invisible);
|
|
299
|
+
if (metadata?.showAxesHelper)
|
|
300
|
+
subEntityTraits.push(traits.ShowAxesHelper);
|
|
271
301
|
if (asset.content.case === 'url') {
|
|
272
302
|
subEntityTraits.push(traits.GLTF({
|
|
273
303
|
source: { url: asset.content.value },
|
|
@@ -280,9 +310,9 @@ const drawModel = (world, { referenceFrame, poseInObserverFrame, physicalObject,
|
|
|
280
310
|
animationName: animationName ?? DEFAULT_ANIMATION_NAME,
|
|
281
311
|
}));
|
|
282
312
|
}
|
|
283
|
-
|
|
313
|
+
world.spawn(...subEntityTraits);
|
|
284
314
|
}
|
|
285
|
-
return
|
|
315
|
+
return { entity: root, relationships: metadata?.relationships };
|
|
286
316
|
};
|
|
287
317
|
const parsePointCloud = (world, entity, pointCloud, metadata) => {
|
|
288
318
|
parsePcdInWorker(new Uint8Array(pointCloud)).then((pointcloud) => {
|
|
@@ -422,7 +452,7 @@ const updateShape = (entity, { physicalObject, metadata }) => {
|
|
|
422
452
|
}
|
|
423
453
|
}
|
|
424
454
|
};
|
|
425
|
-
|
|
455
|
+
const addColorTraits = (entity, colors) => {
|
|
426
456
|
if (isVertexColors(colors)) {
|
|
427
457
|
entity.add(traits.Colors(colors));
|
|
428
458
|
}
|
|
@@ -430,7 +460,7 @@ export const addColorTraits = (entity, colors) => {
|
|
|
430
460
|
entity.add(traits.Color(asRGB(colors, rgb)));
|
|
431
461
|
}
|
|
432
462
|
};
|
|
433
|
-
|
|
463
|
+
const setColorTraits = (entity, colors) => {
|
|
434
464
|
if (isVertexColors(colors)) {
|
|
435
465
|
if (entity.has(traits.Colors))
|
|
436
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
|
@@ -8,6 +8,7 @@ import { parsePcdInWorker } from '../loaders/pcd';
|
|
|
8
8
|
import { parsePlyInput } from '../ply';
|
|
9
9
|
export const Name = trait(() => '');
|
|
10
10
|
export const Parent = trait(() => 'world');
|
|
11
|
+
export const UUID = trait(() => '');
|
|
11
12
|
export const Pose = trait({ x: 0, y: 0, z: 0, oX: 0, oY: 0, oZ: 1, theta: 0 });
|
|
12
13
|
export const EditedPose = trait({ x: 0, y: 0, z: 0, oX: 0, oY: 0, oZ: 1, theta: 0 });
|
|
13
14
|
export const Center = trait({ x: 0, y: 0, z: 0, oX: 0, oY: 0, oZ: 1, theta: 0 });
|
|
@@ -53,6 +54,7 @@ export const Color = trait({ r: 0, g: 0, b: 0 });
|
|
|
53
54
|
*/
|
|
54
55
|
export const Material = trait({
|
|
55
56
|
depthTest: false,
|
|
57
|
+
depthWrite: true,
|
|
56
58
|
});
|
|
57
59
|
export const DepthTest = trait(() => true);
|
|
58
60
|
export const Arrow = trait(() => true);
|
|
@@ -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() {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Entity } from 'koota';
|
|
2
|
+
import type { Relationship } from '../metadata';
|
|
3
|
+
export declare const provideRelationships: () => {
|
|
4
|
+
apply(entity: Entity, relationships: Relationship[] | undefined): void;
|
|
5
|
+
flush(targetUuid: string): void;
|
|
6
|
+
clear(): void;
|
|
7
|
+
};
|
|
8
|
+
export declare const useRelationships: () => {
|
|
9
|
+
apply(entity: Entity, relationships: Relationship[] | undefined): void;
|
|
10
|
+
flush(targetUuid: string): void;
|
|
11
|
+
clear(): void;
|
|
12
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { getContext, setContext } from 'svelte';
|
|
2
|
+
import { uuidBytesToString } from '../draw';
|
|
3
|
+
import { relations, traits, useQuery } from '../ecs';
|
|
4
|
+
const RELATIONSHIPS_CONTEXT_KEY = Symbol('relationships');
|
|
5
|
+
export const provideRelationships = () => {
|
|
6
|
+
const uuids = useQuery(traits.UUID);
|
|
7
|
+
const pending = new Map();
|
|
8
|
+
const addPending = (targetUuid, relationship) => {
|
|
9
|
+
const next = pending.get(targetUuid) ?? [];
|
|
10
|
+
next.push(relationship);
|
|
11
|
+
pending.set(targetUuid, next);
|
|
12
|
+
};
|
|
13
|
+
return setContext(RELATIONSHIPS_CONTEXT_KEY, {
|
|
14
|
+
apply(entity, relationships) {
|
|
15
|
+
const desired = relationships ?? [];
|
|
16
|
+
const currentTargets = entity.targetsFor(relations.SubEntityLink);
|
|
17
|
+
const desiredByUuid = new Map();
|
|
18
|
+
for (const rel of desired) {
|
|
19
|
+
const targetUUID = uuidBytesToString(rel.targetUuid);
|
|
20
|
+
if (!targetUUID)
|
|
21
|
+
continue;
|
|
22
|
+
desiredByUuid.set(targetUUID, rel);
|
|
23
|
+
}
|
|
24
|
+
for (const target of currentTargets) {
|
|
25
|
+
if (!target.isAlive())
|
|
26
|
+
continue;
|
|
27
|
+
const targetUuid = target.get(traits.UUID);
|
|
28
|
+
if (!targetUuid || !desiredByUuid.has(targetUuid)) {
|
|
29
|
+
entity.remove(relations.SubEntityLink(target));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (const [uuid, relationship] of desiredByUuid) {
|
|
33
|
+
const targetEntity = uuids.current.find((e) => e.get(traits.UUID) === uuid);
|
|
34
|
+
if (!targetEntity) {
|
|
35
|
+
addPending(uuid, {
|
|
36
|
+
entity: entity,
|
|
37
|
+
type: relationship.type,
|
|
38
|
+
indexMapping: relationship.indexMapping,
|
|
39
|
+
});
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const existing = entity.get(relations.SubEntityLink(targetEntity));
|
|
43
|
+
if (existing &&
|
|
44
|
+
existing.type === relationship.type &&
|
|
45
|
+
existing.indexMapping === (relationship.indexMapping ?? 'index')) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
entity.add(relations.SubEntityLink(targetEntity, {
|
|
49
|
+
type: relationship.type,
|
|
50
|
+
indexMapping: relationship.indexMapping ?? 'index',
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
flush(targetUuid) {
|
|
55
|
+
const relationship = pending.get(targetUuid);
|
|
56
|
+
if (!relationship)
|
|
57
|
+
return;
|
|
58
|
+
pending.delete(targetUuid);
|
|
59
|
+
const targetEntity = uuids.current.find((e) => e.get(traits.UUID) === targetUuid);
|
|
60
|
+
if (!targetEntity)
|
|
61
|
+
return;
|
|
62
|
+
for (const { entity, type, indexMapping } of relationship) {
|
|
63
|
+
if (!entity.isAlive())
|
|
64
|
+
continue;
|
|
65
|
+
entity.add(relations.SubEntityLink(targetEntity, {
|
|
66
|
+
type,
|
|
67
|
+
indexMapping: indexMapping ?? 'index',
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
clear() {
|
|
72
|
+
pending.clear();
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
export const useRelationships = () => {
|
|
77
|
+
return getContext(RELATIONSHIPS_CONTEXT_KEY);
|
|
78
|
+
};
|