@viamrobotics/motion-tools 0.9.5 → 0.11.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 (41) hide show
  1. package/dist/{WorldObject.d.ts → WorldObject.svelte.d.ts} +2 -1
  2. package/dist/WorldObject.svelte.js +67 -0
  3. package/dist/components/Frame.svelte +1 -1
  4. package/dist/components/Frame.svelte.d.ts +1 -1
  5. package/dist/components/Geometry.svelte +2 -2
  6. package/dist/components/Geometry.svelte.d.ts +1 -1
  7. package/dist/components/Line.svelte +1 -1
  8. package/dist/components/Line.svelte.d.ts +1 -1
  9. package/dist/components/Pointcloud.svelte +1 -1
  10. package/dist/components/Pointcloud.svelte.d.ts +1 -1
  11. package/dist/components/SceneProviders.svelte +2 -1
  12. package/dist/components/Tree/buildTree.d.ts +3 -5
  13. package/dist/components/Tree/buildTree.js +2 -4
  14. package/dist/components/WorldObject.svelte +1 -1
  15. package/dist/components/WorldObject.svelte.d.ts +1 -1
  16. package/dist/components/WorldObjects.svelte +2 -2
  17. package/dist/components/WorldState.svelte +4 -4
  18. package/dist/components/WorldState.svelte.d.ts +2 -2
  19. package/dist/hooks/useDrawAPI.svelte.d.ts +1 -1
  20. package/dist/hooks/useDrawAPI.svelte.js +16 -11
  21. package/dist/hooks/useFrames.svelte.d.ts +1 -1
  22. package/dist/hooks/useFrames.svelte.js +1 -1
  23. package/dist/hooks/useGeometries.svelte.d.ts +1 -1
  24. package/dist/hooks/useGeometries.svelte.js +1 -1
  25. package/dist/hooks/useObjects.svelte.d.ts +1 -1
  26. package/dist/hooks/usePersistentUUIDs.svelte.d.ts +1 -1
  27. package/dist/hooks/usePointclouds.svelte.d.ts +1 -1
  28. package/dist/hooks/usePointclouds.svelte.js +1 -1
  29. package/dist/hooks/useSelection.svelte.d.ts +1 -1
  30. package/dist/hooks/useStaticGeometries.svelte.d.ts +1 -1
  31. package/dist/hooks/useStaticGeometries.svelte.js +1 -1
  32. package/dist/hooks/useWorldState.svelte.d.ts +54 -15
  33. package/dist/hooks/useWorldState.svelte.js +111 -57
  34. package/dist/lib.d.ts +1 -1
  35. package/dist/lib.js +1 -1
  36. package/dist/workers/worldStateWorker.d.ts +1 -0
  37. package/dist/workers/worldStateWorker.js +109 -0
  38. package/dist/world-state-messages.d.ts +23 -0
  39. package/dist/world-state-messages.js +1 -0
  40. package/package.json +5 -5
  41. package/dist/WorldObject.js +0 -23
@@ -12,6 +12,7 @@ export type Geometries = Geometry['geometryType'] | PointsGeometry | LinesGeomet
12
12
  export type Metadata = {
13
13
  colors?: Float32Array;
14
14
  color?: ColorRepresentation;
15
+ opacity?: number;
15
16
  gltf?: {
16
17
  scene: Object3D;
17
18
  };
@@ -29,7 +30,7 @@ export declare class WorldObject<T extends Geometries = Geometries> {
29
30
  uuid: string;
30
31
  name: string;
31
32
  referenceFrame: string;
32
- pose: Pose;
33
+ pose: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Pose>;
33
34
  geometry?: T;
34
35
  metadata: Metadata;
35
36
  constructor(name: string, pose?: Pose, parent?: string, geometry?: T, metadata?: Metadata);
@@ -0,0 +1,67 @@
1
+ import { BatchedMesh, Box3, Color, MathUtils, Object3D, Vector3, } from 'three';
2
+ import { createPose } from './transform';
3
+ export class WorldObject {
4
+ uuid;
5
+ name;
6
+ referenceFrame;
7
+ pose = $state.raw(createPose());
8
+ geometry;
9
+ metadata;
10
+ constructor(name, pose, parent = 'world', geometry, metadata) {
11
+ this.uuid = MathUtils.generateUUID();
12
+ this.name = name;
13
+ this.referenceFrame = parent;
14
+ this.geometry = geometry;
15
+ this.metadata = metadata ?? {};
16
+ if (pose) {
17
+ this.pose = pose;
18
+ }
19
+ }
20
+ }
21
+ const unwrapValue = (value) => {
22
+ if (!value?.kind)
23
+ return value;
24
+ switch (value.kind.case) {
25
+ case 'numberValue':
26
+ case 'stringValue':
27
+ case 'boolValue':
28
+ return value.kind.value;
29
+ case 'structValue': {
30
+ // Recursively unwrap nested struct
31
+ const result = {};
32
+ for (const [key, val] of Object.entries(value.kind.value.fields || {})) {
33
+ result[key] = unwrapValue(val);
34
+ }
35
+ return result;
36
+ }
37
+ case 'listValue':
38
+ return value.kind.value.values?.map(unwrapValue) || [];
39
+ case 'nullValue':
40
+ return null;
41
+ default:
42
+ return value.kind.value;
43
+ }
44
+ };
45
+ const parseMetadata = (metadata) => {
46
+ let json = {};
47
+ for (const [k, v] of Object.entries(metadata)) {
48
+ const unwrappedValue = unwrapValue(v);
49
+ // TODO: Remove special case and add better handling for metadata
50
+ if (k === 'color' && unwrappedValue && typeof unwrappedValue === 'object') {
51
+ const { r, g, b } = unwrappedValue;
52
+ json[k] = new Color().setRGB(r / 255, g / 255, b / 255);
53
+ }
54
+ else {
55
+ json = { ...json, [k]: unwrappedValue };
56
+ }
57
+ }
58
+ return json;
59
+ };
60
+ export const fromTransform = (transform) => {
61
+ const metadata = transform.metadata
62
+ ? parseMetadata(transform.metadata.fields)
63
+ : {};
64
+ const worldObject = new WorldObject(transform.referenceFrame, transform.poseInObserverFrame?.pose, transform.poseInObserverFrame?.referenceFrame, transform.physicalObject?.geometryType, metadata);
65
+ worldObject.uuid = transform.uuidString;
66
+ return worldObject;
67
+ };
@@ -6,7 +6,7 @@
6
6
 
7
7
  <script lang="ts">
8
8
  import type { Snippet } from 'svelte'
9
- import type { WorldObject } from '../WorldObject'
9
+ import type { WorldObject } from '../WorldObject.svelte'
10
10
  import { useObjectEvents } from '../hooks/useObjectEvents.svelte'
11
11
  import Geometry from './Geometry.svelte'
12
12
  import { useSelected } from '../hooks/useSelection.svelte'
@@ -1,6 +1,6 @@
1
1
  import { type Object3D } from 'three';
2
2
  import type { Snippet } from 'svelte';
3
- import type { WorldObject } from '../WorldObject';
3
+ import type { WorldObject } from '../WorldObject.svelte';
4
4
  interface Props {
5
5
  uuid: string;
6
6
  name: string;
@@ -7,7 +7,7 @@
7
7
  import { poseToObject3d } from '../transform'
8
8
  import { colors, darkenColor } from '../color'
9
9
  import AxesHelper from './AxesHelper.svelte'
10
- import type { WorldObject } from '../WorldObject'
10
+ import type { WorldObject } from '../WorldObject.svelte'
11
11
  import { PLYLoader } from 'three/addons/loaders/PLYLoader.js'
12
12
 
13
13
  const plyLoader = new PLYLoader()
@@ -109,7 +109,7 @@
109
109
  {color}
110
110
  side={geometry.case === 'mesh' ? DoubleSide : FrontSide}
111
111
  transparent
112
- opacity={0.7}
112
+ opacity={metadata.opacity ?? 0.7}
113
113
  />
114
114
 
115
115
  {#if geo}
@@ -1,7 +1,7 @@
1
1
  import { type Props as ThrelteProps } from '@threlte/core';
2
2
  import { type Snippet } from 'svelte';
3
3
  import { Object3D } from 'three';
4
- import type { WorldObject } from '../WorldObject';
4
+ import type { WorldObject } from '../WorldObject.svelte';
5
5
  interface Props extends ThrelteProps<Object3D> {
6
6
  uuid: string;
7
7
  name: string;
@@ -2,7 +2,7 @@
2
2
  import { T } from '@threlte/core'
3
3
  import { Instance, InstancedMesh } from '@threlte/extras'
4
4
  import Frame from './Frame.svelte'
5
- import type { WorldObject } from '../WorldObject'
5
+ import type { WorldObject } from '../WorldObject.svelte'
6
6
  import { useSettings } from '../hooks/useSettings.svelte'
7
7
  import type { Snippet } from 'svelte'
8
8
 
@@ -1,4 +1,4 @@
1
- import type { WorldObject } from '../WorldObject';
1
+ import type { WorldObject } from '../WorldObject.svelte';
2
2
  import type { Snippet } from 'svelte';
3
3
  interface Props {
4
4
  object: WorldObject;
@@ -7,7 +7,7 @@
7
7
  OrthographicCamera,
8
8
  } from 'three'
9
9
  import { T, useTask, useThrelte } from '@threlte/core'
10
- import type { WorldObject } from '../WorldObject'
10
+ import type { WorldObject } from '../WorldObject.svelte'
11
11
  import { useObjectEvents } from '../hooks/useObjectEvents.svelte'
12
12
  import { poseToObject3d } from '../transform'
13
13
  import { useSettings } from '../hooks/useSettings.svelte'
@@ -1,4 +1,4 @@
1
- import type { WorldObject } from '../WorldObject';
1
+ import type { WorldObject } from '../WorldObject.svelte';
2
2
  import type { Snippet } from 'svelte';
3
3
  interface Props {
4
4
  object: WorldObject<{
@@ -14,7 +14,7 @@
14
14
  import { provideMotionClient } from '../hooks/useMotionClient.svelte'
15
15
  import { provideLogs } from '../hooks/useLogs.svelte'
16
16
  import { provideOrigin } from './xr/useOrigin.svelte'
17
-
17
+ import { provideWorldStates } from '../hooks/useWorldState.svelte'
18
18
  interface Props {
19
19
  children: Snippet<[{ focus: boolean }]>
20
20
  }
@@ -37,6 +37,7 @@
37
37
  providePointclouds(() => partID.current)
38
38
  provideMotionClient(() => partID.current)
39
39
  provideObjects()
40
+ provideWorldStates()
40
41
 
41
42
  const { focus } = provideSelection()
42
43
  </script>
@@ -1,4 +1,5 @@
1
- import type { WorldObject } from '../../WorldObject';
1
+ import type { useWorldStates } from '../../hooks/useWorldState.svelte';
2
+ import type { WorldObject } from '../../WorldObject.svelte';
2
3
  export interface TreeNode {
3
4
  id: string;
4
5
  name: string;
@@ -8,7 +9,4 @@ export interface TreeNode {
8
9
  /**
9
10
  * Creates a tree representing parent child / relationships from a set of frames.
10
11
  */
11
- export declare const buildTreeNodes: (objects: WorldObject[], worldStates: {
12
- name: string;
13
- objects: WorldObject[];
14
- }[]) => TreeNode[];
12
+ export declare const buildTreeNodes: (objects: WorldObject[], worldStates: ReturnType<typeof useWorldStates>["current"]) => TreeNode[];
@@ -25,15 +25,14 @@ export const buildTreeNodes = (objects, worldStates) => {
25
25
  }
26
26
  }
27
27
  }
28
- for (const worldState of worldStates) {
28
+ for (const worldState of Object.values(worldStates)) {
29
29
  const node = {
30
30
  name: worldState.name,
31
31
  id: worldState.name,
32
32
  children: [],
33
33
  href: `/world-state/${worldState.name}`,
34
34
  };
35
- console.log('worldState', worldState);
36
- for (const object of worldState.objects) {
35
+ for (const object of worldState.worldObjects) {
37
36
  const child = {
38
37
  name: object.name,
39
38
  id: object.uuid,
@@ -42,7 +41,6 @@ export const buildTreeNodes = (objects, worldStates) => {
42
41
  };
43
42
  nodeMap.set(object.name, child);
44
43
  node.children?.push(child);
45
- console.log('child', child);
46
44
  }
47
45
  nodeMap.set(worldState.name, node);
48
46
  rootNodes.push(node);
@@ -2,7 +2,7 @@
2
2
  import { T, type Props as ThrelteProps } from '@threlte/core'
3
3
  import type { Snippet } from 'svelte'
4
4
  import type { Object3D } from 'three'
5
- import type { WorldObject } from '../WorldObject'
5
+ import type { WorldObject } from '../WorldObject.svelte'
6
6
  import { useObjectEvents } from '../hooks/useObjectEvents.svelte'
7
7
 
8
8
  interface Props extends ThrelteProps<Object3D> {
@@ -1,7 +1,7 @@
1
1
  import { type Props as ThrelteProps } from '@threlte/core';
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { Object3D } from 'three';
4
- import type { WorldObject } from '../WorldObject';
4
+ import type { WorldObject } from '../WorldObject.svelte';
5
5
  interface Props extends ThrelteProps<Object3D> {
6
6
  object: WorldObject;
7
7
  children?: Snippet;
@@ -68,8 +68,8 @@
68
68
  </Portal>
69
69
  {/each}
70
70
 
71
- {#each worldStates.current as { name, objects } (name)}
72
- <WorldState {objects} />
71
+ {#each worldStates.names as { name } (name)}
72
+ <WorldState worldObjects={worldStates.current[name].worldObjects} />
73
73
  {/each}
74
74
 
75
75
  {#each points.current as object (object.uuid)}
@@ -3,16 +3,16 @@
3
3
  import Label from './Label.svelte'
4
4
  import Portal from './portal/Portal.svelte'
5
5
  import PortalTarget from './portal/PortalTarget.svelte'
6
- import { WorldObject } from '../WorldObject'
6
+ import { WorldObject } from '../WorldObject.svelte'
7
7
 
8
8
  interface Props {
9
- objects: WorldObject[]
9
+ worldObjects: WorldObject[]
10
10
  }
11
11
 
12
- let { objects }: Props = $props()
12
+ let { worldObjects }: Props = $props()
13
13
  </script>
14
14
 
15
- {#each objects as object (object.uuid)}
15
+ {#each worldObjects as object (object.uuid)}
16
16
  <Portal id={object.referenceFrame}>
17
17
  <Frame
18
18
  uuid={object.uuid}
@@ -1,6 +1,6 @@
1
- import { WorldObject } from '../WorldObject';
1
+ import { WorldObject } from '../WorldObject.svelte';
2
2
  interface Props {
3
- objects: WorldObject[];
3
+ worldObjects: WorldObject[];
4
4
  }
5
5
  declare const WorldState: import("svelte").Component<Props, {}, "">;
6
6
  type WorldState = ReturnType<typeof WorldState>;
@@ -1,6 +1,6 @@
1
1
  import { Vector3 } from 'three';
2
2
  import { BatchedArrow } from '../three/BatchedArrow';
3
- import { WorldObject, type PointsGeometry } from '../WorldObject';
3
+ import { WorldObject, type PointsGeometry } from '../WorldObject.svelte';
4
4
  type ConnectionStatus = 'connecting' | 'open' | 'closed';
5
5
  interface Context {
6
6
  points: WorldObject<PointsGeometry>[];
@@ -4,7 +4,7 @@ import { NURBSCurve } from 'three/addons/curves/NURBSCurve.js';
4
4
  import { parsePcdInWorker } from '../loaders/pcd';
5
5
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
6
6
  import { BatchedArrow } from '../three/BatchedArrow';
7
- import { WorldObject } from '../WorldObject';
7
+ import { WorldObject } from '../WorldObject.svelte';
8
8
  const key = Symbol('draw-api-context-key');
9
9
  const tryParse = (json) => {
10
10
  try {
@@ -59,8 +59,13 @@ export const provideDrawAPI = () => {
59
59
  }, colors ? { colors } : undefined));
60
60
  };
61
61
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
62
- const addGeometry = (data, color, parent) => {
62
+ const drawGeometry = (data, color, parent) => {
63
63
  let geometry;
64
+ const existingMesh = meshes.find((mesh) => mesh.name === data.label);
65
+ if (existingMesh) {
66
+ existingMesh.pose = data.center;
67
+ return;
68
+ }
64
69
  if ('mesh' in data) {
65
70
  geometry = {
66
71
  case: 'mesh',
@@ -214,10 +219,10 @@ export const provideDrawAPI = () => {
214
219
  }));
215
220
  };
216
221
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
217
- const addGeometries = (geometries, colors, parent) => {
222
+ const drawGeometries = (geometries, colors, parent) => {
218
223
  let i = 0;
219
224
  for (const geometry of geometries) {
220
- addGeometry(geometry, colors[i], parent);
225
+ drawGeometry(geometry, colors[i], parent);
221
226
  i += 1;
222
227
  }
223
228
  };
@@ -332,7 +337,13 @@ export const provideDrawAPI = () => {
332
337
  };
333
338
  }
334
339
  if ('geometries' in data) {
335
- return addGeometries(data.geometries, data.colors, data.parent);
340
+ return drawGeometries(data.geometries, data.colors, data.parent);
341
+ }
342
+ if ('geometry' in data) {
343
+ return drawGeometry(data.geometry, data.color);
344
+ }
345
+ if ('Knots' in data) {
346
+ return addNurbs(data, data.Color);
336
347
  }
337
348
  if ('remove' in data) {
338
349
  return remove(data.names);
@@ -340,12 +351,6 @@ export const provideDrawAPI = () => {
340
351
  if ('removeAll' in data) {
341
352
  return removeAll();
342
353
  }
343
- if ('Knots' in data) {
344
- return addNurbs(data, data.Color);
345
- }
346
- if ('geometry' in data) {
347
- addGeometry(data.geometry, data.color);
348
- }
349
354
  };
350
355
  const connect = () => {
351
356
  if (BACKEND_IP && BUN_SERVER_PORT) {
@@ -1,4 +1,4 @@
1
- import { WorldObject } from '../WorldObject';
1
+ import { WorldObject } from '../WorldObject.svelte';
2
2
  interface FramesContext {
3
3
  current: WorldObject[];
4
4
  error?: Error;
@@ -1,6 +1,6 @@
1
1
  import { getContext, setContext, untrack } from 'svelte';
2
2
  import { useRobotClient, createRobotQuery, useMachineStatus, useResourceNames, } from '@viamrobotics/svelte-sdk';
3
- import { WorldObject } from '../WorldObject';
3
+ import { WorldObject } from '../WorldObject.svelte';
4
4
  import { observe } from '@threlte/core';
5
5
  import { useLogs } from './useLogs.svelte';
6
6
  import { resourceColors } from '../color';
@@ -1,4 +1,4 @@
1
- import { WorldObject } from '../WorldObject';
1
+ import { WorldObject } from '../WorldObject.svelte';
2
2
  interface Context {
3
3
  current: WorldObject[];
4
4
  }
@@ -4,7 +4,7 @@ import { createResourceClient, useResourceNames } from '@viamrobotics/svelte-sdk
4
4
  import { setContext, getContext } from 'svelte';
5
5
  import { fromStore, toStore } from 'svelte/store';
6
6
  import { useMachineSettings } from './useMachineSettings.svelte';
7
- import { WorldObject } from '../WorldObject';
7
+ import { WorldObject } from '../WorldObject.svelte';
8
8
  import { usePersistentUUIDs } from './usePersistentUUIDs.svelte';
9
9
  import { useLogs } from './useLogs.svelte';
10
10
  import { resourceColors } from '../color';
@@ -1,4 +1,4 @@
1
- import type { WorldObject } from '../WorldObject';
1
+ import type { WorldObject } from '../WorldObject.svelte';
2
2
  interface Context {
3
3
  current: WorldObject[];
4
4
  }
@@ -1,4 +1,4 @@
1
- import type { WorldObject } from '../WorldObject';
1
+ import type { WorldObject } from '../WorldObject.svelte';
2
2
  export declare const usePersistentUUIDs: () => {
3
3
  uuids: Map<string, string>;
4
4
  updateUUIDs: (objects: WorldObject[]) => void;
@@ -1,4 +1,4 @@
1
- import { WorldObject, type PointsGeometry } from '../WorldObject';
1
+ import { WorldObject, type PointsGeometry } from '../WorldObject.svelte';
2
2
  interface Context {
3
3
  current: WorldObject<PointsGeometry>[];
4
4
  }
@@ -5,7 +5,7 @@ import { fromStore, toStore } from 'svelte/store';
5
5
  import { createResourceClient, useResourceNames } from '@viamrobotics/svelte-sdk';
6
6
  import { parsePcdInWorker } from '../loaders/pcd';
7
7
  import { useMachineSettings } from './useMachineSettings.svelte';
8
- import { WorldObject } from '../WorldObject';
8
+ import { WorldObject } from '../WorldObject.svelte';
9
9
  import { usePersistentUUIDs } from './usePersistentUUIDs.svelte';
10
10
  import { useLogs } from './useLogs.svelte';
11
11
  const key = Symbol('pointcloud-context');
@@ -1,5 +1,5 @@
1
1
  import { Object3D } from 'three';
2
- import type { WorldObject } from '../WorldObject';
2
+ import type { WorldObject } from '../WorldObject.svelte';
3
3
  interface SelectionContext {
4
4
  readonly current: string | undefined;
5
5
  set(value?: string): void;
@@ -1,4 +1,4 @@
1
- import { WorldObject } from '../WorldObject';
1
+ import { WorldObject } from '../WorldObject.svelte';
2
2
  interface Context {
3
3
  current: WorldObject[];
4
4
  add: () => void;
@@ -2,7 +2,7 @@ import { getContext, setContext } from 'svelte';
2
2
  import { get, set } from 'idb-keyval';
3
3
  import { Debounced } from 'runed';
4
4
  import { createGeometry } from '../transform';
5
- import { WorldObject } from '../WorldObject';
5
+ import { WorldObject } from '../WorldObject.svelte';
6
6
  const key = Symbol('static-geometries-context');
7
7
  export const provideStaticGeometries = () => {
8
8
  let geometries = $state([]);
@@ -1,19 +1,58 @@
1
- import { type TransformChangeEvent } from '@viamrobotics/sdk';
2
- import { WorldObject } from '../WorldObject';
3
- export declare const useWorldStates: () => {
4
- readonly names: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").ResourceName>[];
5
- readonly current: {
6
- readonly name: string;
7
- readonly objects: WorldObject<import("../WorldObject").Geometries>[];
8
- readonly listUUIDs: import("@tanstack/svelte-query").QueryObserverResult<string[]>;
9
- readonly getTransforms: import("@tanstack/svelte-query").QueryObserverResult<import("@viamrobotics/sdk").TransformWithUUID>[] | undefined;
10
- readonly changeStream: import("@tanstack/svelte-query").DefinedQueryObserverResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverLoadingErrorResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverLoadingResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverPendingResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverPlaceholderResult<TransformChangeEvent[], Error>;
11
- }[];
1
+ import { type TransformWithUUID, ResourceName } from '@viamrobotics/sdk';
2
+ interface Context {
3
+ names: ResourceName[];
4
+ current: Record<string, ReturnType<typeof createWorldState>>;
5
+ }
6
+ export declare const provideWorldStates: () => void;
7
+ export declare const useWorldStates: () => Context;
8
+ export declare const useWorldState: (resourceName: () => string) => {
9
+ readonly name: string;
10
+ readonly transforms: TransformWithUUID[];
11
+ readonly worldObjects: import("../WorldObject.svelte").WorldObject<{
12
+ case: undefined;
13
+ value?: undefined;
14
+ } | {
15
+ case: "sphere";
16
+ value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Sphere>;
17
+ } | {
18
+ case: "box";
19
+ value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").RectangularPrism>;
20
+ } | {
21
+ case: "capsule";
22
+ value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Capsule>;
23
+ } | {
24
+ case: "mesh";
25
+ value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Mesh>;
26
+ } | {
27
+ case: "pointcloud";
28
+ value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").PointCloud>;
29
+ }>[];
30
+ readonly listUUIDs: import("@tanstack/svelte-query").QueryObserverResult<string[]>;
31
+ readonly getTransforms: import("@tanstack/svelte-query").QueryObserverResult<TransformWithUUID>[] | undefined;
12
32
  };
13
- export declare const useWorldState: (partID: () => string, resourceName: () => string) => {
33
+ declare const createWorldState: (partID: () => string, resourceName: () => string) => {
14
34
  readonly name: string;
15
- readonly objects: WorldObject<import("../WorldObject").Geometries>[];
35
+ readonly transforms: TransformWithUUID[];
36
+ readonly worldObjects: import("../WorldObject.svelte").WorldObject<{
37
+ case: undefined;
38
+ value?: undefined;
39
+ } | {
40
+ case: "sphere";
41
+ value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Sphere>;
42
+ } | {
43
+ case: "box";
44
+ value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").RectangularPrism>;
45
+ } | {
46
+ case: "capsule";
47
+ value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Capsule>;
48
+ } | {
49
+ case: "mesh";
50
+ value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Mesh>;
51
+ } | {
52
+ case: "pointcloud";
53
+ value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").PointCloud>;
54
+ }>[];
16
55
  readonly listUUIDs: import("@tanstack/svelte-query").QueryObserverResult<string[]>;
17
- readonly getTransforms: import("@tanstack/svelte-query").QueryObserverResult<import("@viamrobotics/sdk").TransformWithUUID>[] | undefined;
18
- readonly changeStream: import("@tanstack/svelte-query").DefinedQueryObserverResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverLoadingErrorResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverLoadingResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverPendingResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverPlaceholderResult<TransformChangeEvent[], Error>;
56
+ readonly getTransforms: import("@tanstack/svelte-query").QueryObserverResult<TransformWithUUID>[] | undefined;
19
57
  };
58
+ export {};
@@ -1,88 +1,145 @@
1
- import { toPath, getInUnsafe, mutInUnsafe } from '@thi.ng/paths';
2
- import { WorldStateStoreClient, TransformChangeType, } from '@viamrobotics/sdk';
1
+ import { WorldStateStoreClient, TransformChangeType, ResourceName, } from '@viamrobotics/sdk';
3
2
  import { createResourceClient, createResourceQuery, createResourceStream, useResourceNames, } from '@viamrobotics/svelte-sdk';
4
- import { fromTransform, WorldObject } from '../WorldObject';
3
+ import { fromTransform } from '../WorldObject.svelte';
5
4
  import { usePartID } from './usePartID.svelte';
6
- export const useWorldStates = () => {
5
+ import { setInUnsafe } from '@thi.ng/paths';
6
+ import { getContext, setContext } from 'svelte';
7
+ const key = Symbol('world-state-context');
8
+ const worker = new Worker(new URL('../workers/worldStateWorker.ts', import.meta.url), {
9
+ type: 'module',
10
+ });
11
+ export const provideWorldStates = () => {
7
12
  const partID = usePartID();
8
13
  const resourceNames = useResourceNames(() => partID.current, 'world_state_store');
9
- const current = $derived.by(() => resourceNames.current.map(({ name }) => useWorldState(() => partID.current, () => name)));
10
- return {
14
+ const current = $derived.by(() => Object.fromEntries(resourceNames.current.map(({ name }) => [
15
+ name,
16
+ createWorldState(() => partID.current, () => name),
17
+ ])));
18
+ setContext(key, {
11
19
  get names() {
12
20
  return resourceNames.current;
13
21
  },
14
22
  get current() {
15
23
  return current;
16
24
  },
17
- };
25
+ });
18
26
  };
19
- export const useWorldState = (partID, resourceName) => {
27
+ export const useWorldStates = () => {
28
+ return getContext(key);
29
+ };
30
+ export const useWorldState = (resourceName) => {
31
+ return useWorldStates().current[resourceName()];
32
+ };
33
+ const createWorldState = (partID, resourceName) => {
20
34
  const client = createResourceClient(WorldStateStoreClient, partID, resourceName);
21
- let initialized = false;
35
+ let initialized = $state(false);
36
+ let transforms = $state.raw({});
37
+ const transformsList = $derived.by(() => Object.values(transforms));
38
+ const worldObjectsList = $derived.by(() => transformsList.map(fromTransform));
39
+ let pendingEvents = [];
40
+ let flushScheduled = false;
22
41
  const listUUIDs = createResourceQuery(client, 'listUUIDs');
23
42
  const getTransforms = $derived(listUUIDs.current.data?.map((uuid) => {
24
43
  return createResourceQuery(client, 'getTransform', () => [uuid], () => ({ refetchInterval: false }));
25
44
  }));
26
- const changeStream = createResourceStream(client, 'streamTransformChanges');
27
- const worldObjects = $state({});
28
- const initializeCurrent = (objects) => {
29
- for (const object of objects) {
30
- worldObjects[object.uuid] = object;
45
+ const changeStream = createResourceStream(client, 'streamTransformChanges', {
46
+ refetchMode: 'replace',
47
+ });
48
+ const initialize = (initial) => {
49
+ const next = { ...transforms };
50
+ for (const transform of initial) {
51
+ next[transform.uuidString] = transform;
31
52
  }
53
+ transforms = next;
32
54
  initialized = true;
33
55
  };
34
- $effect(() => {
35
- if (!getTransforms) {
56
+ const applyEvents = (events) => {
57
+ if (events.length === 0)
36
58
  return;
59
+ const next = { ...transforms };
60
+ for (const event of events) {
61
+ switch (event.type) {
62
+ case TransformChangeType.ADDED:
63
+ next[event.uuidString] = event.transform;
64
+ break;
65
+ case TransformChangeType.REMOVED:
66
+ delete next[event.uuidString];
67
+ break;
68
+ case TransformChangeType.UPDATED: {
69
+ if (event.changes.length === 0)
70
+ continue;
71
+ let toUpdate = next[event.uuidString];
72
+ if (!toUpdate)
73
+ continue;
74
+ for (const [path, value] of event.changes) {
75
+ toUpdate = setInUnsafe(toUpdate, path, value);
76
+ }
77
+ next[event.uuidString] = toUpdate;
78
+ break;
79
+ }
80
+ }
37
81
  }
38
- if (initialized) {
82
+ transforms = next;
83
+ };
84
+ const scheduleFlush = () => {
85
+ if (flushScheduled)
86
+ return;
87
+ flushScheduled = true;
88
+ requestAnimationFrame(() => {
89
+ const toApply = pendingEvents;
90
+ if (toApply.length === 0)
91
+ return;
92
+ applyEvents(toApply);
93
+ flushScheduled = false;
94
+ pendingEvents = [];
95
+ });
96
+ };
97
+ $effect(() => {
98
+ if (!getTransforms)
99
+ return;
100
+ if (initialized)
39
101
  return;
40
- }
41
102
  const queries = getTransforms.map((query) => query.current);
42
- if (queries.some((query) => query?.isLoading)) {
103
+ if (queries.some((query) => query?.isLoading))
43
104
  return;
44
- }
45
- const objects = [];
46
- for (const transform of queries.flatMap((query) => query.data) ?? []) {
47
- if (transform === undefined) {
48
- continue;
49
- }
50
- objects.push(fromTransform(transform));
51
- }
52
- initializeCurrent(objects);
53
- });
54
- const processChangeEvent = async (event) => {
55
- if (event.transform === undefined) {
105
+ const data = queries
106
+ .flatMap((query) => query?.data ?? [])
107
+ .filter((transform) => transform !== undefined);
108
+ if (data.length === 0)
56
109
  return;
57
- }
58
- switch (event.changeType) {
59
- case TransformChangeType.ADDED:
60
- worldObjects[event.transform.uuidString] = fromTransform(event.transform);
61
- break;
62
- case TransformChangeType.UPDATED:
63
- for (const path of event.updatedFields?.paths ?? []) {
64
- // Type inference is tough here, so we use unsafe APIs
65
- const paths = toPath(path);
66
- const next = getInUnsafe(event.transform, paths);
67
- mutInUnsafe(worldObjects[event.transform.uuidString], paths, next);
68
- }
69
- break;
70
- case TransformChangeType.REMOVED:
71
- delete worldObjects[event.transform.uuidString];
72
- break;
73
- }
74
- };
110
+ initialize(data);
111
+ });
75
112
  $effect(() => {
76
- for (const event of changeStream.current?.data ?? []) {
77
- void processChangeEvent(event);
78
- }
113
+ worker.onmessage = (e) => {
114
+ if (e.data.type !== 'process')
115
+ return;
116
+ const { events } = e.data ?? { events: [] };
117
+ if (events.length === 0)
118
+ return;
119
+ pendingEvents.push(...events);
120
+ scheduleFlush();
121
+ };
122
+ return () => {
123
+ worker.terminate();
124
+ };
125
+ });
126
+ $effect.pre(() => {
127
+ if (changeStream.current?.data === undefined)
128
+ return;
129
+ const events = changeStream.current.data.filter((event) => event.transform !== undefined);
130
+ if (events.length === 0)
131
+ return;
132
+ worker.postMessage({ type: 'change', events });
79
133
  });
80
134
  return {
81
135
  get name() {
82
136
  return resourceName();
83
137
  },
84
- get objects() {
85
- return Object.values(worldObjects);
138
+ get transforms() {
139
+ return transformsList;
140
+ },
141
+ get worldObjects() {
142
+ return worldObjectsList;
86
143
  },
87
144
  get listUUIDs() {
88
145
  return listUUIDs.current;
@@ -90,8 +147,5 @@ export const useWorldState = (partID, resourceName) => {
90
147
  get getTransforms() {
91
148
  return getTransforms?.map((query) => query.current);
92
149
  },
93
- get changeStream() {
94
- return changeStream.current;
95
- },
96
150
  };
97
151
  };
package/dist/lib.d.ts CHANGED
@@ -3,5 +3,5 @@ export { default as AxesHelper } from './components/AxesHelper.svelte';
3
3
  export { BatchedArrow } from './three/BatchedArrow';
4
4
  export { CapsuleGeometry } from './three/CapsuleGeometry';
5
5
  export { OrientationVector } from './three/OrientationVector';
6
- export { WorldObject } from './WorldObject';
6
+ export { WorldObject } from './WorldObject.svelte';
7
7
  export { parsePcdInWorker } from './loaders/pcd';
package/dist/lib.js CHANGED
@@ -5,6 +5,6 @@ export { default as AxesHelper } from './components/AxesHelper.svelte';
5
5
  export { BatchedArrow } from './three/BatchedArrow';
6
6
  export { CapsuleGeometry } from './three/CapsuleGeometry';
7
7
  export { OrientationVector } from './three/OrientationVector';
8
- export { WorldObject } from './WorldObject';
8
+ export { WorldObject } from './WorldObject.svelte';
9
9
  // Functions
10
10
  export { parsePcdInWorker } from './loaders/pcd';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,109 @@
1
+ import { getInUnsafe, toPath } from '@thi.ng/paths';
2
+ import { TransformChangeType, } from '@viamrobotics/sdk';
3
+ const createEntry = (event) => {
4
+ if (!event.transform)
5
+ return undefined;
6
+ switch (event.changeType) {
7
+ case TransformChangeType.ADDED:
8
+ return {
9
+ type: event.changeType,
10
+ uuidString: event.transform.uuidString,
11
+ transform: event.transform,
12
+ };
13
+ case TransformChangeType.REMOVED:
14
+ return {
15
+ type: event.changeType,
16
+ uuidString: event.transform.uuidString,
17
+ };
18
+ case TransformChangeType.UPDATED: {
19
+ const changes = {};
20
+ const paths = toPath(event.updatedFields?.paths ?? []);
21
+ for (const path of paths) {
22
+ changes[path.toString()] = getInUnsafe(event.transform, path);
23
+ }
24
+ return {
25
+ type: event.changeType,
26
+ uuidString: event.transform.uuidString,
27
+ transform: event.transform,
28
+ changes,
29
+ };
30
+ }
31
+ }
32
+ };
33
+ self.onmessage = (e) => {
34
+ const { events } = e.data;
35
+ if (events.length === 0)
36
+ return;
37
+ const eventsByUUID = new Map();
38
+ for (const event of events) {
39
+ const entry = createEntry(event);
40
+ if (!entry)
41
+ continue;
42
+ const uuid = entry.uuidString;
43
+ const existing = eventsByUUID.get(uuid);
44
+ if (!existing) {
45
+ eventsByUUID.set(uuid, entry);
46
+ continue;
47
+ }
48
+ switch (entry.type) {
49
+ case TransformChangeType.REMOVED:
50
+ eventsByUUID.set(uuid, entry);
51
+ break;
52
+ case TransformChangeType.ADDED:
53
+ if (existing.type !== TransformChangeType.REMOVED)
54
+ eventsByUUID.set(uuid, entry);
55
+ break;
56
+ case TransformChangeType.UPDATED:
57
+ // merge with existing updated event
58
+ if (existing.type === TransformChangeType.UPDATED) {
59
+ const paths = toPath(event.updatedFields?.paths ?? []);
60
+ if (paths.length === 0)
61
+ continue;
62
+ for (const path of paths) {
63
+ if (!existing.changes)
64
+ existing.changes = {};
65
+ existing.changes[path.toString()] = getInUnsafe(entry.transform, path);
66
+ }
67
+ existing.transform = event.transform;
68
+ }
69
+ break;
70
+ }
71
+ }
72
+ const processedEvents = [];
73
+ for (const entry of eventsByUUID.values()) {
74
+ switch (entry.type) {
75
+ case TransformChangeType.ADDED:
76
+ if (!entry.transform)
77
+ continue;
78
+ processedEvents.push({
79
+ type: TransformChangeType.ADDED,
80
+ uuidString: entry.uuidString,
81
+ transform: entry.transform,
82
+ });
83
+ break;
84
+ case TransformChangeType.REMOVED:
85
+ processedEvents.push({
86
+ type: TransformChangeType.REMOVED,
87
+ uuidString: entry.uuidString,
88
+ });
89
+ break;
90
+ case TransformChangeType.UPDATED: {
91
+ const changes = Object.entries(entry.changes ?? {});
92
+ if (changes.length === 0)
93
+ continue;
94
+ processedEvents.push({
95
+ type: TransformChangeType.UPDATED,
96
+ uuidString: entry.uuidString,
97
+ changes,
98
+ });
99
+ break;
100
+ }
101
+ }
102
+ }
103
+ const message = {
104
+ type: 'process',
105
+ events: processedEvents,
106
+ };
107
+ self.postMessage(message);
108
+ };
109
+ export {};
@@ -0,0 +1,23 @@
1
+ import type { TransformChangeEvent, TransformChangeType, TransformWithUUID } from '@viamrobotics/sdk';
2
+ export type ChangeMessage = {
3
+ type: 'change';
4
+ events: TransformChangeEvent[];
5
+ };
6
+ export type AddedEvent = {
7
+ type: TransformChangeType.ADDED;
8
+ uuidString: string;
9
+ transform: TransformWithUUID;
10
+ };
11
+ export type RemovedEvent = {
12
+ type: TransformChangeType.REMOVED;
13
+ uuidString: string;
14
+ };
15
+ export type UpdatedEvent = {
16
+ type: TransformChangeType.UPDATED;
17
+ uuidString: string;
18
+ changes: [path: string, value: unknown][];
19
+ };
20
+ export type ProcessMessage = {
21
+ type: 'process';
22
+ events: (AddedEvent | RemovedEvent | UpdatedEvent)[];
23
+ };
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "0.9.5",
3
+ "version": "0.11.0",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -22,8 +22,8 @@
22
22
  "@sveltejs/vite-plugin-svelte": "6.1.4",
23
23
  "@tailwindcss/forms": "0.5.10",
24
24
  "@tailwindcss/vite": "4.1.13",
25
- "@tanstack/svelte-query": "5.86.0",
26
- "@tanstack/svelte-query-devtools": "5.86.0",
25
+ "@tanstack/svelte-query": "5.87.1",
26
+ "@tanstack/svelte-query-devtools": "5.87.3",
27
27
  "@testing-library/jest-dom": "6.8.0",
28
28
  "@testing-library/svelte": "5.2.8",
29
29
  "@thi.ng/paths": "5.2.21",
@@ -37,8 +37,8 @@
37
37
  "@typescript-eslint/eslint-plugin": "8.42.0",
38
38
  "@typescript-eslint/parser": "8.42.0",
39
39
  "@viamrobotics/prime-core": "0.1.5",
40
- "@viamrobotics/sdk": "0.50.0",
41
- "@viamrobotics/svelte-sdk": "0.6.0",
40
+ "@viamrobotics/sdk": "0.51.0",
41
+ "@viamrobotics/svelte-sdk": "0.6.1",
42
42
  "@vitejs/plugin-basic-ssl": "2.1.0",
43
43
  "@zag-js/svelte": "1.22.1",
44
44
  "@zag-js/tree-view": "1.22.1",
@@ -1,23 +0,0 @@
1
- import { BatchedMesh, Box3, MathUtils, Object3D, Vector3 } from 'three';
2
- import { createPose } from './transform';
3
- export class WorldObject {
4
- uuid;
5
- name;
6
- referenceFrame;
7
- pose;
8
- geometry;
9
- metadata;
10
- constructor(name, pose, parent = 'world', geometry, metadata) {
11
- this.uuid = MathUtils.generateUUID();
12
- this.name = name;
13
- this.referenceFrame = parent;
14
- this.pose = pose ?? createPose();
15
- this.geometry = geometry;
16
- this.metadata = metadata ?? {};
17
- }
18
- }
19
- export const fromTransform = (transform) => {
20
- const metadata = { ...transform.metadata?.fields };
21
- const worldObject = new WorldObject(transform.referenceFrame, transform.poseInObserverFrame?.pose, transform.poseInObserverFrame?.referenceFrame, transform.physicalObject?.geometryType, metadata);
22
- return worldObject;
23
- };