@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.
Files changed (33) hide show
  1. package/dist/buf/draw/v1/metadata_pb.d.ts +39 -0
  2. package/dist/buf/draw/v1/metadata_pb.js +55 -0
  3. package/dist/buf/draw/v1/service_connect.d.ts +34 -1
  4. package/dist/buf/draw/v1/service_connect.js +34 -1
  5. package/dist/buf/draw/v1/service_pb.d.ts +136 -0
  6. package/dist/buf/draw/v1/service_pb.js +201 -0
  7. package/dist/components/Entities/Arrows/ArrowGroups.svelte +1 -0
  8. package/dist/components/Entities/Arrows/Arrows.svelte +1 -1
  9. package/dist/components/Entities/Entities.svelte +1 -0
  10. package/dist/components/Entities/Points.svelte +23 -23
  11. package/dist/components/Entities/hooks/useEntityEvents.svelte.js +18 -1
  12. package/dist/components/FileDrop/FileDrop.svelte +8 -1
  13. package/dist/components/PCD.svelte +9 -1
  14. package/dist/components/PCD.svelte.d.ts +2 -0
  15. package/dist/components/SceneProviders.svelte +2 -0
  16. package/dist/components/Snapshot.svelte +12 -7
  17. package/dist/components/overlay/AddRelationship.svelte +25 -3
  18. package/dist/components/overlay/Details.svelte +293 -227
  19. package/dist/draw.d.ts +22 -9
  20. package/dist/draw.js +75 -46
  21. package/dist/ecs/relations.js +1 -1
  22. package/dist/ecs/traits.d.ts +2 -0
  23. package/dist/ecs/traits.js +63 -0
  24. package/dist/hooks/useDrawService.svelte.d.ts +2 -0
  25. package/dist/hooks/useDrawService.svelte.js +139 -20
  26. package/dist/hooks/useRelationships.svelte.d.ts +12 -0
  27. package/dist/hooks/useRelationships.svelte.js +78 -0
  28. package/dist/hooks/useWorldState.svelte.js +10 -4
  29. package/dist/metadata.d.ts +7 -3
  30. package/dist/metadata.js +26 -2
  31. package/dist/snapshot.d.ts +6 -1
  32. package/dist/snapshot.js +10 -5
  33. package/package.json +5 -2
@@ -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
+ };
@@ -9,6 +9,7 @@ import { isPointCloud } from '../geometry';
9
9
  import { metadataFromStruct } from '../metadata';
10
10
  import { createPose } from '../transform';
11
11
  import { usePartID } from './usePartID.svelte';
12
+ import { useRelationships } from './useRelationships.svelte';
12
13
  export const provideWorldStates = () => {
13
14
  const partID = usePartID();
14
15
  const resourceNames = useResourceNames(() => partID.current, 'world_state_store');
@@ -83,6 +84,7 @@ const decodeWorldStateChunk = (response, fallbackStart) => {
83
84
  const createWorldState = (client) => {
84
85
  const { invalidate } = useThrelte();
85
86
  const world = useWorld();
87
+ const relationships = useRelationships();
86
88
  const entities = new Map();
87
89
  const chunkLoader = createChunkLoader({
88
90
  world,
@@ -105,10 +107,12 @@ const createWorldState = (client) => {
105
107
  if (entities.has(transform.uuidString)) {
106
108
  return;
107
109
  }
108
- const entity = drawTransform(world, transform, traits.WorldStateStoreAPI, { removable: false });
109
- entities.set(transform.uuidString, entity);
110
+ const spawned = drawTransform(world, transform, traits.WorldStateStoreAPI, { removable: false });
111
+ entities.set(transform.uuidString, spawned.entity);
112
+ relationships.apply(spawned.entity, spawned.relationships);
110
113
  const parsedMetadata = metadataFromStruct(transform.metadata?.fields);
111
- chunkLoader.start(transform.uuidString, entity, parsedMetadata);
114
+ chunkLoader.start(transform.uuidString, spawned.entity, parsedMetadata);
115
+ relationships.flush(transform.uuidString);
112
116
  if (isPointCloud(transform.physicalObject?.geometryType))
113
117
  invalidate();
114
118
  };
@@ -140,9 +144,11 @@ const createWorldState = (client) => {
140
144
  }
141
145
  }
142
146
  if (metadataDirty) {
143
- updateMetadata(entity, metadataFromStruct(transform.metadata?.fields), {
147
+ const parsedMetadata = metadataFromStruct(transform.metadata?.fields);
148
+ updateMetadata(entity, parsedMetadata, {
144
149
  pointCloud: isPointCloud(transform.physicalObject?.geometryType),
145
150
  });
151
+ relationships.apply(entity, parsedMetadata.relationships);
146
152
  }
147
153
  };
148
154
  let initialized = false;
@@ -1,7 +1,11 @@
1
1
  import type { PlainMessage, Struct } from '@viamrobotics/sdk';
2
- import { Metadata as MetadataProto } from './buf/draw/v1/metadata_pb';
3
- /** Metadata for a `Drawing` or `Transform`. */
4
- export type Metadata = PlainMessage<MetadataProto>;
2
+ import { Metadata as MetadataProto, type Relationship as RelationshipProto } from './buf/draw/v1/metadata_pb';
3
+ /** Metadata for a `Drawing` or `Transform`. Relationships default to empty. */
4
+ export type Metadata = Omit<PlainMessage<MetadataProto>, 'relationships'> & {
5
+ relationships?: PlainMessage<MetadataProto>['relationships'];
6
+ };
7
+ /** Plain-object representation of a Relationship, usable outside proto classes. */
8
+ export type Relationship = PlainMessage<RelationshipProto>;
5
9
  /** Type guard that checks whether a string is a recognised metadata wire key. */
6
10
  export declare const isMetadataField: (key: string) => boolean;
7
11
  /**
package/dist/metadata.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ColorFormat, Metadata as MetadataProto } from './buf/draw/v1/metadata_pb';
1
+ import { ColorFormat, Metadata as MetadataProto, } from './buf/draw/v1/metadata_pb';
2
2
  /** Type guard that checks whether a string is a recognised metadata wire key. */
3
3
  export const isMetadataField = (key) => {
4
4
  return (key === 'colors' ||
@@ -6,7 +6,8 @@ export const isMetadataField = (key) => {
6
6
  key === 'opacities' ||
7
7
  key === 'show_axes_helper' ||
8
8
  key === 'invisible' ||
9
- key === 'chunks');
9
+ key === 'chunks' ||
10
+ key === 'relationships');
10
11
  };
11
12
  /**
12
13
  * Extracts typed {@link Metadata} from a proto `Struct` fields map.
@@ -77,6 +78,29 @@ export const metadataFromStruct = (fields = {}) => {
77
78
  }
78
79
  break;
79
80
  }
81
+ case 'relationships': {
82
+ if (Array.isArray(unwrappedValue)) {
83
+ json.relationships = unwrappedValue
84
+ .filter((item) => typeof item === 'object' && item !== null)
85
+ .map((item) => {
86
+ const targetUuidStr = item['target_uuid'];
87
+ let targetUuid = new Uint8Array();
88
+ if (typeof targetUuidStr === 'string' && targetUuidStr.length > 0) {
89
+ const binary = atob(targetUuidStr);
90
+ targetUuid = new Uint8Array(binary.length);
91
+ for (let i = 0; i < binary.length; i++) {
92
+ targetUuid[i] = binary.charCodeAt(i);
93
+ }
94
+ }
95
+ return {
96
+ targetUuid,
97
+ type: typeof item['type'] === 'string' ? item['type'] : '',
98
+ indexMapping: typeof item['index_mapping'] === 'string' ? item['index_mapping'] : undefined,
99
+ };
100
+ });
101
+ }
102
+ break;
103
+ }
80
104
  }
81
105
  }
82
106
  return json;
@@ -2,6 +2,11 @@ import type { Entity, World } from 'koota';
2
2
  import type { Snapshot } from './buf/draw/v1/snapshot_pb';
3
3
  import type { Settings } from './hooks/useSettings.svelte';
4
4
  import { type SceneMetadata } from './buf/draw/v1/scene_pb';
5
+ import type { Relationship } from './metadata';
6
+ export type SnapshotEntity = {
7
+ entity: Entity;
8
+ relationships: Relationship[] | undefined;
9
+ };
5
10
  /**
6
11
  * Merges scene-level metadata (grid, camera, point/line settings) into the
7
12
  * current viewer settings. Millimeter values from the proto are converted
@@ -18,4 +23,4 @@ export declare const applySceneMetadata: (settings: Settings, metadata: SceneMet
18
23
  *
19
24
  * @returns The spawned entities
20
25
  */
21
- export declare const spawnSnapshotEntities: (world: World, snapshot: Snapshot) => Entity[];
26
+ export declare const spawnSnapshotEntities: (world: World, snapshot: Snapshot) => SnapshotEntity[];
package/dist/snapshot.js CHANGED
@@ -58,13 +58,18 @@ export const spawnSnapshotEntities = (world, snapshot) => {
58
58
  const entities = [];
59
59
  const options = { removable: true, showAxesHelper: false };
60
60
  for (const transform of snapshot.transforms) {
61
- entities.push(drawTransform(world, transform, traits.SnapshotAPI, options));
61
+ const spawned = drawTransform(world, transform, traits.SnapshotAPI, options);
62
+ entities.push({
63
+ entity: spawned.entity,
64
+ relationships: spawned.relationships,
65
+ });
62
66
  }
63
67
  for (const drawing of snapshot.drawings) {
64
- const drawingEntities = drawDrawing(world, drawing, traits.SnapshotAPI, options);
65
- for (const e of drawingEntities) {
66
- entities.push(e);
67
- }
68
+ const spawned = drawDrawing(world, drawing, traits.SnapshotAPI, options);
69
+ entities.push({
70
+ entity: spawned.entity,
71
+ relationships: spawned.relationships,
72
+ });
68
73
  }
69
74
  return entities;
70
75
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "1.19.0",
3
+ "version": "1.21.0",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -32,6 +32,7 @@
32
32
  "@types/bun": "1.2.21",
33
33
  "@types/earcut": "^3.0.0",
34
34
  "@types/lodash-es": "4.17.12",
35
+ "@types/node": "^25.6.0",
35
36
  "@types/three": "0.183.1",
36
37
  "@typescript-eslint/eslint-plugin": "8.56.1",
37
38
  "@typescript-eslint/parser": "8.56.1",
@@ -65,6 +66,7 @@
65
66
  "runed": "0.31.1",
66
67
  "svelte": "5.55.0",
67
68
  "svelte-check": "4.4.5",
69
+ "svelte-tweakpane-ui": "^1.5.16",
68
70
  "svelte-virtuallists": "1.4.2",
69
71
  "tailwindcss": "4.1.13",
70
72
  "three": "0.183.2",
@@ -104,6 +106,7 @@
104
106
  "lucide-svelte": ">=0.511",
105
107
  "runed": ">=0.28",
106
108
  "svelte": ">=5",
109
+ "svelte-tweakpane-ui": ">=1.5",
107
110
  "svelte-virtuallists": ">=1"
108
111
  },
109
112
  "engines": {
@@ -123,7 +126,7 @@
123
126
  },
124
127
  "repository": {
125
128
  "type": "git",
126
- "url": "git+https://github.com/viam-labs/motion-tools.git"
129
+ "url": "git+https://github.com/viamrobotics/visualization.git"
127
130
  },
128
131
  "files": [
129
132
  "dist",