@viamrobotics/motion-tools 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,18 +11,28 @@ Open the machine config page (bottom right) and enter in connection details to v
11
11
 
12
12
  ## Todo
13
13
 
14
+ ----- hard -----
15
+
14
16
  - animated sequence of motion plan
15
- - double click to set trackball center in object view
16
17
  - Give better fetching / connection state info
17
- - Set default pointcloud color in settings
18
+ - configure frames in app
19
+ - embed in teleop
20
+
21
+ ----- medium -----
22
+
18
23
  - remote IP access when custom drawing, to draw on remote computers
19
- - points are not sized right in ortho cam view
20
24
  - geometries need to be parented to parent
21
- - bounding boxes should include just the thing and not children
22
- - configure frames in app
23
25
  - color pallet for resource to color
26
+ - measurement tool
27
+
28
+ ----- easy ------
29
+
30
+ - double click to set trackball center in object view
31
+ - Set default pointcloud color in settings
32
+ - points are not sized right in ortho cam view
33
+ - bounding boxes should include just the thing and not children
24
34
 
25
- - foxglove / rviz
35
+ --- action items ----
26
36
 
27
37
  ## Env files
28
38
 
@@ -1,8 +1,8 @@
1
1
  import type { Geometry, Pose } from '@viamrobotics/sdk';
2
- import { Box3, Object3D, Vector3 } from 'three';
2
+ import { BatchedMesh, Box3, Object3D, Vector3 } from 'three';
3
3
  export type PointsGeometry = {
4
4
  case: 'points';
5
- value: Float32Array;
5
+ value: Float32Array<ArrayBuffer>;
6
6
  };
7
7
  export type LinesGeometry = {
8
8
  case: 'line';
@@ -18,7 +18,7 @@ export type Metadata = {
18
18
  points?: Vector3[];
19
19
  batched?: {
20
20
  id: number;
21
- name: string;
21
+ object: BatchedMesh;
22
22
  };
23
23
  getBoundingBoxAt?: (box: Box3) => void;
24
24
  };
@@ -1,4 +1,4 @@
1
- import { Box3, MathUtils, Object3D, Vector3 } from 'three';
1
+ import { BatchedMesh, Box3, MathUtils, Object3D, Vector3 } from 'three';
2
2
  import { createPose } from './transform';
3
3
  export class WorldObject {
4
4
  uuid;
package/dist/color.d.ts CHANGED
@@ -6,4 +6,19 @@ import { Color, type ColorRepresentation } from 'three';
6
6
  * @returns A new THREE.Color instance with the darkened color.
7
7
  */
8
8
  export declare const darkenColor: (value: ColorRepresentation, percent: number) => Color;
9
- export declare const mapStringToColor: () => void;
9
+ export declare const colors: {
10
+ readonly selected: string;
11
+ readonly default: string;
12
+ readonly arm: {
13
+ readonly selected: string;
14
+ readonly default: string;
15
+ };
16
+ readonly camera: {
17
+ readonly selected: string;
18
+ readonly default: string;
19
+ };
20
+ readonly gripper: {
21
+ readonly selected: string;
22
+ readonly default: string;
23
+ };
24
+ };
package/dist/color.js CHANGED
@@ -1,4 +1,44 @@
1
1
  import { Color } from 'three';
2
+ import twColors from 'tailwindcss/colors';
3
+ // Step 3: linear sRGB → sRGB
4
+ const linearToSrgb = (x) => {
5
+ return x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055;
6
+ };
7
+ // Step 4: sRGB → hex
8
+ const toHex = (x) => {
9
+ const hex = Math.round(x * 255)
10
+ .toString(16)
11
+ .padStart(2, '0');
12
+ return hex;
13
+ };
14
+ const oklchToHex = (raw) => {
15
+ const match = raw.match(/oklch\(\s*([\d.]+)%\s+([\d.]+)\s+([\d.]+)\s*\)/);
16
+ if (!match) {
17
+ return '#000000';
18
+ }
19
+ const l = parseFloat(match[1]) / 100;
20
+ const c = parseFloat(match[2]);
21
+ const h = parseFloat(match[3]);
22
+ // Convert h from degrees to radians
23
+ const hRad = (h * Math.PI) / 180;
24
+ // Step 1: OKLCH → OKLab
25
+ const aa = c * Math.cos(hRad);
26
+ const bb = c * Math.sin(hRad);
27
+ // Step 2: OKLab → linear sRGB
28
+ const l_ = l + 0.3963377774 * aa + 0.2158037573 * bb;
29
+ const m_ = l - 0.1055613458 * aa - 0.0638541728 * bb;
30
+ const s_ = l - 0.0894841775 * aa - 1.291485548 * bb;
31
+ const l_cubed = l_ ** 3;
32
+ const m_cubed = m_ ** 3;
33
+ const s_cubed = s_ ** 3;
34
+ const r_linear = +4.0767416621 * l_cubed - 3.3077115913 * m_cubed + 0.2309699292 * s_cubed;
35
+ const g_linear = -1.2684380046 * l_cubed + 2.6097574011 * m_cubed - 0.3413193965 * s_cubed;
36
+ const b_linear = -0.0041960863 * l_cubed - 0.7034186147 * m_cubed + 1.707614701 * s_cubed;
37
+ const r = Math.max(0, Math.min(1, linearToSrgb(r_linear)));
38
+ const g = Math.max(0, Math.min(1, linearToSrgb(g_linear)));
39
+ const b = Math.max(0, Math.min(1, linearToSrgb(b_linear)));
40
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
41
+ };
2
42
  /**
3
43
  * Darkens a THREE.Color by a given percentage while preserving hue.
4
44
  * @param color The original THREE.Color instance.
@@ -12,4 +52,19 @@ export const darkenColor = (value, percent) => {
12
52
  hsl.l = Math.max(0, hsl.l * (1 - percent / 100));
13
53
  return color.setHSL(hsl.h, hsl.s, hsl.l);
14
54
  };
15
- export const mapStringToColor = () => { };
55
+ export const colors = {
56
+ selected: oklchToHex(twColors.red['900']),
57
+ default: oklchToHex(twColors.red['500']),
58
+ arm: {
59
+ selected: oklchToHex(twColors.amber['900']),
60
+ default: oklchToHex(twColors.amber['500']),
61
+ },
62
+ camera: {
63
+ selected: oklchToHex(twColors.blue['900']),
64
+ default: oklchToHex(twColors.blue['500']),
65
+ },
66
+ gripper: {
67
+ selected: oklchToHex(twColors.cyan['900']),
68
+ default: oklchToHex(twColors.cyan['500']),
69
+ },
70
+ };
@@ -1,13 +1,11 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte'
3
3
  import { Canvas } from '@threlte/core'
4
- import { XRButton } from '@threlte/xr'
5
4
  import Scene from './Scene.svelte'
6
5
  import TreeContainer from './Tree/TreeContainer.svelte'
7
6
  import Details from './Details.svelte'
8
7
  import SceneProviders from './SceneProviders.svelte'
9
8
  import DomPortal from './DomPortal.svelte'
10
- import { PersistedState } from 'runed'
11
9
  import XR from './xr/XR.svelte'
12
10
  import { World } from '@threlte/rapier'
13
11
  import { createPartIDContext } from '../hooks/usePartID.svelte'
@@ -22,19 +20,9 @@
22
20
 
23
21
  createPartIDContext(() => partID)
24
22
 
25
- const enableXR = new PersistedState('enable-xr', false)
26
-
27
- let root: HTMLElement
23
+ let root = $state<HTMLElement>()
28
24
  </script>
29
25
 
30
- <svelte:window
31
- onkeydown={(event) => {
32
- if (event.ctrlKey && event.key.toLowerCase() === 'a') {
33
- enableXR.current = !enableXR.current
34
- }
35
- }}
36
- />
37
-
38
26
  <div
39
27
  class="relative h-full w-full"
40
28
  bind:this={root}
@@ -46,9 +34,7 @@
46
34
  <Scene>
47
35
  {@render appChildren?.()}
48
36
 
49
- {#if enableXR.current}
50
- <XR />
51
- {/if}
37
+ <XR />
52
38
  </Scene>
53
39
 
54
40
  <DomPortal element={root}>
@@ -66,7 +52,3 @@
66
52
  </World>
67
53
  </Canvas>
68
54
  </div>
69
-
70
- {#if enableXR.current}
71
- <XRButton mode="immersive-ar" />
72
- {/if}
@@ -24,11 +24,11 @@
24
24
  const VERTEX_COMPONENTS = 3
25
25
 
26
26
  const line = new Line2()
27
- const material = $state(new LineMaterial())
27
+ const material = new LineMaterial()
28
28
  const geometry = new LineGeometry()
29
29
  const color = new Color()
30
- const colors = $state(new Float32Array(TOTAL_VERTICES * VERTEX_COMPONENTS))
31
- const positions = $state(new Float32Array(TOTAL_VERTICES * VERTEX_COMPONENTS))
30
+ const colors = new Float32Array(TOTAL_VERTICES * VERTEX_COMPONENTS)
31
+ const positions = new Float32Array(TOTAL_VERTICES * VERTEX_COMPONENTS)
32
32
 
33
33
  // Assign colors per vertex
34
34
  $effect.pre(() => {
@@ -1,13 +1,32 @@
1
1
  <script lang="ts">
2
- import { useSelectedObject, useFocusedObject, useFocused } from '../hooks/useSelection.svelte'
2
+ import { Quaternion, Vector3 } from 'three'
3
3
  import { Check, Copy } from 'lucide-svelte'
4
4
  import { Button, Icon } from '@viamrobotics/prime-core'
5
+ import {
6
+ useSelectedObject,
7
+ useFocusedObject,
8
+ useFocused,
9
+ useFocusedObject3d,
10
+ useSelectedObject3d,
11
+ } from '../hooks/useSelection.svelte'
5
12
  import { useDraggable } from '../hooks/useDraggable.svelte'
13
+ import { OrientationVector } from '../three/OrientationVector'
6
14
 
7
15
  const focused = useFocused()
8
- const selectedObject = useSelectedObject()
9
16
  const focusedObject = useFocusedObject()
17
+ const focusedObject3d = useFocusedObject3d()
18
+
19
+ const selectedObject = useSelectedObject()
20
+ const selectedObject3d = useSelectedObject3d()
21
+
10
22
  const object = $derived(focusedObject.current ?? selectedObject.current)
23
+ const object3d = $derived(focusedObject3d.current ?? selectedObject3d.current)
24
+
25
+ const worldPosition = $derived(object3d?.getWorldPosition(new Vector3()))
26
+ const worldQuaternion = $derived(object3d?.getWorldQuaternion(new Quaternion()))
27
+ const worldOrientation = $derived(
28
+ worldQuaternion ? new OrientationVector().setFromQuaternion(worldQuaternion) : undefined
29
+ )
11
30
 
12
31
  let copied = $state(false)
13
32
 
@@ -15,7 +34,7 @@
15
34
  </script>
16
35
 
17
36
  {#if object}
18
- {@const { geometry, pose } = object}
37
+ {@const { geometry } = object}
19
38
  <div
20
39
  class="border-medium bg-extralight absolute top-0 right-0 z-10 m-2 w-60 border p-2 text-xs"
21
40
  style:transform="translate({draggable.current.x}px, {draggable.current.y}px)"
@@ -53,43 +72,46 @@
53
72
  </h3>
54
73
 
55
74
  <div class="flex flex-col gap-2.5">
56
- {#if pose}
75
+ {#if worldPosition}
57
76
  <div>
58
- <strong class="font-semibold">position</strong>
77
+ <strong class="font-semibold">world position</strong>
78
+
59
79
  <div class="flex gap-3">
60
80
  <div>
61
81
  <span class="text-subtle-2">x</span>
62
- {pose.x !== undefined ? pose.x.toFixed(2) : '-'}
82
+ {(worldPosition.x * 1000).toFixed(2)}
63
83
  </div>
64
84
  <div>
65
85
  <span class="text-subtle-2">y</span>
66
- {pose.y !== undefined ? pose.y.toFixed(2) : '-'}
86
+ {(worldPosition.y * 1000).toFixed(2)}
67
87
  </div>
68
88
  <div>
69
89
  <span class="text-subtle-2">z</span>
70
- {pose.z !== undefined ? pose.z.toFixed(2) : '-'}
90
+ {(worldPosition.z * 1000).toFixed(2)}
71
91
  </div>
72
92
  </div>
73
93
  </div>
94
+ {/if}
74
95
 
96
+ {#if worldOrientation}
75
97
  <div>
76
- <strong class="font-semibold">orientation</strong>
98
+ <strong class="font-semibold">world orientation</strong>
77
99
  <div class="flex gap-3">
78
100
  <div>
79
101
  <span class="text-subtle-2">x</span>
80
- {pose.oX !== undefined ? pose.oX.toFixed(2) : '-'}
102
+ {worldOrientation.x.toFixed(2)}
81
103
  </div>
82
104
  <div>
83
105
  <span class="text-subtle-2">y</span>
84
- {pose.oY !== undefined ? pose.oY.toFixed(2) : '-'}
106
+ {worldOrientation.y.toFixed(2)}
85
107
  </div>
86
108
  <div>
87
109
  <span class="text-subtle-2">z</span>
88
- {pose.oZ !== undefined ? pose.oZ.toFixed(2) : '-'}
110
+ {worldOrientation.z.toFixed(2)}
89
111
  </div>
90
112
  <div>
91
113
  <span class="text-subtle-2">th</span>
92
- {pose.theta !== undefined ? pose.theta.toFixed(2) : '-'}
114
+ {worldOrientation.th.toFixed(2)}
93
115
  </div>
94
116
  </div>
95
117
  </div>
@@ -4,6 +4,8 @@
4
4
  import type { WorldObject } from '../WorldObject'
5
5
  import { useObjectEvents } from '../hooks/useObjectEvents.svelte'
6
6
  import Geometry from './Geometry.svelte'
7
+ import { useSelected } from '../hooks/useSelection.svelte'
8
+ import { colors } from '../color'
7
9
 
8
10
  interface Props {
9
11
  uuid: string
@@ -16,11 +18,13 @@
16
18
 
17
19
  let { uuid, ...rest }: Props = $props()
18
20
 
21
+ const selected = useSelected()
19
22
  const events = useObjectEvents(() => uuid)
20
23
  </script>
21
24
 
22
25
  <Geometry
23
26
  {uuid}
27
+ color={selected.current === uuid ? colors.selected : undefined}
24
28
  {...events}
25
29
  {...rest}
26
30
  />
@@ -1,28 +1,37 @@
1
1
  <script lang="ts">
2
- import { T } from '@threlte/core'
2
+ import { T, type Props as ThrelteProps } from '@threlte/core'
3
3
  import { type Snippet } from 'svelte'
4
4
  import { meshBounds, MeshLineGeometry, MeshLineMaterial } from '@threlte/extras'
5
5
  import { BufferGeometry, DoubleSide, FrontSide, Mesh, Object3D } from 'three'
6
6
  import { CapsuleGeometry } from '../three/CapsuleGeometry'
7
7
  import { poseToObject3d } from '../transform'
8
- import { darkenColor } from '../color'
8
+ import { colors, darkenColor } from '../color'
9
9
  import AxesHelper from './AxesHelper.svelte'
10
10
  import type { WorldObject } from '../WorldObject'
11
11
  import { PLYLoader } from 'three/addons/loaders/PLYLoader.js'
12
12
 
13
13
  const plyLoader = new PLYLoader()
14
14
 
15
- interface Props {
15
+ interface Props extends ThrelteProps<Object3D> {
16
16
  uuid: string
17
17
  name: string
18
18
  geometry?: WorldObject['geometry']
19
19
  pose: WorldObject['pose']
20
20
  metadata: WorldObject['metadata']
21
21
  children?: Snippet<[{ ref: Object3D }]>
22
- [prop: string]: unknown
22
+ color?: string
23
23
  }
24
24
 
25
- let { uuid, name, geometry, metadata, pose, children, ...rest }: Props = $props()
25
+ let {
26
+ uuid,
27
+ name,
28
+ geometry,
29
+ metadata,
30
+ pose,
31
+ color: overrideColor,
32
+ children,
33
+ ...rest
34
+ }: Props = $props()
26
35
 
27
36
  const type = $derived(geometry?.case)
28
37
  const mesh = $derived.by(() => {
@@ -40,6 +49,12 @@
40
49
  })
41
50
 
42
51
  let geo = $state<BufferGeometry>()
52
+
53
+ const oncreate = (ref: BufferGeometry) => {
54
+ geo = ref
55
+ }
56
+
57
+ const color = $derived(overrideColor ?? metadata.color ?? colors.default)
43
58
  </script>
44
59
 
45
60
  <T
@@ -50,33 +65,30 @@
50
65
  >
51
66
  {#if geometry?.case === 'mesh'}
52
67
  {@const meshGeometry = plyLoader.parse(atob(geometry.value.mesh as unknown as string))}
53
- <T is={meshGeometry} />
68
+ <T
69
+ is={meshGeometry}
70
+ {oncreate}
71
+ />
54
72
  {:else if geometry?.case === 'line' && metadata.points}
55
73
  <MeshLineGeometry points={metadata.points} />
56
74
  {:else if geometry?.case === 'box'}
57
75
  {@const dimsMm = geometry.value.dimsMm ?? { x: 0, y: 0, z: 0 }}
58
76
  <T.BoxGeometry
59
77
  args={[dimsMm.x * 0.001, dimsMm.y * 0.001, dimsMm.z * 0.001]}
60
- oncreate={(ref) => {
61
- geo = ref
62
- }}
78
+ {oncreate}
63
79
  />
64
80
  {:else if geometry?.case === 'sphere'}
65
81
  {@const radiusMm = geometry.value.radiusMm ?? 0}
66
82
  <T.SphereGeometry
67
83
  args={[radiusMm * 0.001]}
68
- oncreate={(ref) => {
69
- geo = ref
70
- }}
84
+ {oncreate}
71
85
  />
72
86
  {:else if geometry?.case === 'capsule'}
73
87
  {@const { lengthMm, radiusMm } = geometry.value}
74
88
  <T
75
89
  is={CapsuleGeometry}
76
90
  args={[radiusMm * 0.001, lengthMm * 0.001]}
77
- oncreate={(ref) => {
78
- geo = ref
79
- }}
91
+ {oncreate}
80
92
  />
81
93
  {:else}
82
94
  <AxesHelper
@@ -87,12 +99,12 @@
87
99
 
88
100
  {#if geometry?.case === 'line'}
89
101
  <MeshLineMaterial
90
- color={metadata.color ?? 'red'}
102
+ {color}
91
103
  width={0.005}
92
104
  />
93
105
  {:else if geometry}
94
106
  <T.MeshToonMaterial
95
- color={metadata.color ?? 'red'}
107
+ {color}
96
108
  side={geometry.case === 'mesh' ? DoubleSide : FrontSide}
97
109
  transparent
98
110
  opacity={0.7}
@@ -100,8 +112,8 @@
100
112
 
101
113
  {#if geo}
102
114
  <T.LineSegments raycast={() => null}>
103
- <T.EdgesGeometry args={[geo, 1]} />
104
- <T.LineBasicMaterial color={darkenColor(metadata.color ?? 'red', 10)} />
115
+ <T.EdgesGeometry args={[geo, 0]} />
116
+ <T.LineBasicMaterial color={darkenColor(color, 10)} />
105
117
  </T.LineSegments>
106
118
  {/if}
107
119
  {/if}
@@ -1,7 +1,8 @@
1
+ import { type Props as ThrelteProps } from '@threlte/core';
1
2
  import { type Snippet } from 'svelte';
2
3
  import { Object3D } from 'three';
3
4
  import type { WorldObject } from '../WorldObject';
4
- interface Props {
5
+ interface Props extends ThrelteProps<Object3D> {
5
6
  uuid: string;
6
7
  name: string;
7
8
  geometry?: WorldObject['geometry'];
@@ -10,7 +11,7 @@ interface Props {
10
11
  children?: Snippet<[{
11
12
  ref: Object3D;
12
13
  }]>;
13
- [prop: string]: unknown;
14
+ color?: string;
14
15
  }
15
16
  declare const Geometry: import("svelte").Component<Props, {}, "">;
16
17
  type Geometry = ReturnType<typeof Geometry>;
@@ -7,7 +7,7 @@
7
7
  import { poseToObject3d } from '../transform'
8
8
 
9
9
  interface Props {
10
- object: WorldObject<{ case: 'points'; value: Float32Array }>
10
+ object: WorldObject<{ case: 'points'; value: Float32Array<ArrayBuffer> }>
11
11
  }
12
12
 
13
13
  let { object }: Props = $props()
@@ -20,19 +20,19 @@
20
20
  })
21
21
 
22
22
  const colors = $derived(object.metadata.colors)
23
- const positions = $derived(object.geometry?.value ?? [])
23
+ const positions = $derived(object.geometry?.value ?? new Float32Array())
24
24
 
25
25
  $effect(() => {
26
26
  material.vertexColors = colors !== undefined
27
27
  })
28
28
 
29
29
  $effect.pre(() => {
30
- geometry.setAttribute('position', new BufferAttribute(new Float32Array(positions), 3))
30
+ geometry.setAttribute('position', new BufferAttribute(positions, 3))
31
31
  })
32
32
 
33
33
  $effect.pre(() => {
34
34
  if (colors) {
35
- geometry.setAttribute('color', new BufferAttribute(new Float32Array(colors), 3))
35
+ geometry.setAttribute('color', new BufferAttribute(colors, 3))
36
36
  }
37
37
  })
38
38
 
@@ -2,7 +2,7 @@ import type { WorldObject } from '../WorldObject';
2
2
  interface Props {
3
3
  object: WorldObject<{
4
4
  case: 'points';
5
- value: Float32Array;
5
+ value: Float32Array<ArrayBuffer>;
6
6
  }>;
7
7
  }
8
8
  declare const Pointcloud: import("svelte").Component<Props, {}, "">;
@@ -2,7 +2,6 @@
2
2
  import { provideFrames } from '../hooks/useFrames.svelte'
3
3
  import { provideGeometries } from '../hooks/useGeometries.svelte'
4
4
  import { providePointclouds } from '../hooks/usePointclouds.svelte'
5
- import { providePoses } from '../hooks/usePoses.svelte'
6
5
  import { usePartID } from '../hooks/usePartID.svelte'
7
6
  import { provideSelection } from '../hooks/useSelection.svelte'
8
7
  import { provideStaticGeometries } from '../hooks/useStaticGeometries.svelte'
@@ -32,8 +31,8 @@
32
31
 
33
32
  provideStaticGeometries()
34
33
  provideShapes()
34
+
35
35
  provideFrames(() => partID.current)
36
- providePoses(() => partID.current)
37
36
  provideGeometries(() => partID.current)
38
37
  providePointclouds(() => partID.current)
39
38
  provideMotionClient(() => partID.current)
@@ -18,6 +18,7 @@
18
18
  {#if object.metadata.gltf?.scene}
19
19
  <T
20
20
  is={object.metadata.gltf.scene}
21
+ uuid={object.uuid}
21
22
  name={object.name}
22
23
  {...objectProps}
23
24
  {...rest}
@@ -1,20 +1,26 @@
1
1
  <script lang="ts">
2
- import { useXR, XR } from '@threlte/xr'
2
+ import { PersistedState } from 'runed'
3
+ import { XR, XRButton } from '@threlte/xr'
3
4
  import OriginMarker from './OriginMarker.svelte'
4
- import { useThrelte } from '@threlte/core'
5
+ import DomPortal from '../DomPortal.svelte'
5
6
 
6
- const { renderer } = useThrelte()
7
- const { isPresenting } = useXR()
8
-
9
- $effect.pre(() => {
10
- if ($isPresenting) {
11
- const [left, right] = renderer.xr.getCamera().cameras
7
+ const enableXR = new PersistedState('enable-xr', false)
8
+ </script>
12
9
 
13
- console.log(left, right)
10
+ <svelte:window
11
+ onkeydown={(event) => {
12
+ if (event.ctrlKey && event.key.toLowerCase() === 'a') {
13
+ enableXR.current = !enableXR.current
14
14
  }
15
- })
16
- </script>
15
+ }}
16
+ />
17
+
18
+ {#if enableXR.current}
19
+ <XR>
20
+ <OriginMarker />
21
+ </XR>
17
22
 
18
- <XR>
19
- <OriginMarker />
20
- </XR>
23
+ <DomPortal>
24
+ <XRButton mode="immersive-ar" />
25
+ </DomPortal>
26
+ {/if}
@@ -1,3 +1,18 @@
1
- declare const Xr: import("svelte").Component<Record<string, never>, {}, "">;
2
- type Xr = ReturnType<typeof Xr>;
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const Xr: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type Xr = InstanceType<typeof Xr>;
3
18
  export default Xr;
@@ -29,7 +29,7 @@ export const provideFrames = (partID) => {
29
29
  }
30
30
  for (const { frame } of query.current.data ?? []) {
31
31
  if (frame) {
32
- objects.push(new WorldObject(frame.referenceFrame ?? 'Unnamed frame', frame.poseInObserverFrame?.pose, frame.poseInObserverFrame?.referenceFrame, frame.physicalObject?.geometryType));
32
+ objects.push(new WorldObject(frame.referenceFrame ? frame.referenceFrame : 'Unnamed frame', frame.poseInObserverFrame?.pose, frame.poseInObserverFrame?.referenceFrame, frame.physicalObject?.geometryType));
33
33
  }
34
34
  }
35
35
  return objects;
@@ -8,7 +8,6 @@ import { WorldObject } from '../WorldObject';
8
8
  import { usePersistentUUIDs } from './usePersistentUUIDs.svelte';
9
9
  import { useLogs } from './useLogs.svelte';
10
10
  const key = Symbol('geometries-context');
11
- let index = 0;
12
11
  export const provideGeometries = (partID) => {
13
12
  const logs = useLogs();
14
13
  const refreshRates = useRefreshRates();
@@ -46,7 +45,7 @@ export const provideGeometries = (partID) => {
46
45
  if (!query.data)
47
46
  continue;
48
47
  for (const { center, label, geometryType } of query.data.geometries) {
49
- results.push(new WorldObject(label ? label : `Unnamed geometry ${++index}`, center, query.data.name, geometryType));
48
+ results.push(new WorldObject(label ? label : 'Unnamed geometry', center, query.data.name, geometryType));
50
49
  }
51
50
  }
52
51
  updateUUIDs(results);
@@ -33,7 +33,7 @@ export const providePointclouds = (partID) => {
33
33
  if (!response)
34
34
  return null;
35
35
  const { positions, colors } = await parsePcdInWorker(new Uint8Array(response));
36
- return new WorldObject(`${name}:pointcloud`, undefined, name, { case: 'points', value: new Float32Array(positions) }, colors ? { colors: new Float32Array(colors) } : undefined);
36
+ return new WorldObject(`${name}:pointcloud`, undefined, name, { case: 'points', value: positions }, colors ? { colors } : undefined);
37
37
  },
38
38
  });
39
39
  }));
@@ -1,4 +1,4 @@
1
- import type { Object3D } from 'three';
1
+ import { Object3D } from 'three';
2
2
  import type { WorldObject } from '../WorldObject';
3
3
  type UUID = string;
4
4
  interface SelectionContext {
@@ -1,5 +1,6 @@
1
1
  import { useThrelte } from '@threlte/core';
2
2
  import { getContext, setContext } from 'svelte';
3
+ import { Matrix4, Object3D } from 'three';
3
4
  import { useObjects } from './useObjects.svelte';
4
5
  const hoverKey = Symbol('hover-context');
5
6
  const selectionKey = Symbol('selection-context');
@@ -73,17 +74,32 @@ export const useSelectedObject = () => {
73
74
  export const useFocusedObject3d = () => {
74
75
  const focusedObject = useFocusedObject();
75
76
  const { scene } = useThrelte();
76
- const object = $derived(focusedObject.current ? scene.getObjectByName(focusedObject.current.name)?.clone() : undefined);
77
+ const object = $derived(focusedObject.current
78
+ ? scene.getObjectByProperty('uuid', focusedObject.current.uuid)?.clone()
79
+ : undefined);
77
80
  return {
78
81
  get current() {
79
82
  return object;
80
83
  },
81
84
  };
82
85
  };
86
+ const matrix = new Matrix4();
83
87
  export const useSelectedObject3d = () => {
84
88
  const selectedObject = useSelectedObject();
85
89
  const { scene } = useThrelte();
86
- const object = $derived(selectedObject.current ? scene.getObjectByName(selectedObject.current.name) : undefined);
90
+ const object = $derived.by(() => {
91
+ if (!selectedObject.current) {
92
+ return;
93
+ }
94
+ if (selectedObject.current.metadata.batched) {
95
+ const proxy = new Object3D();
96
+ const { id, object } = selectedObject.current.metadata.batched;
97
+ object.getMatrixAt(id, matrix);
98
+ proxy.applyMatrix4(matrix);
99
+ return proxy;
100
+ }
101
+ return scene.getObjectByProperty('uuid', selectedObject.current.uuid);
102
+ });
87
103
  return {
88
104
  get current() {
89
105
  return object;
@@ -34,8 +34,8 @@ export const provideShapes = () => {
34
34
  const { positions, colors } = await parsePcdInWorker(new Uint8Array(buffer));
35
35
  points.push(new WorldObject(`points ${++pointsIndex}`, undefined, undefined, {
36
36
  case: 'points',
37
- value: new Float32Array(positions),
38
- }, colors ? { colors: new Float32Array(colors) } : undefined));
37
+ value: positions,
38
+ }, colors ? { colors } : undefined));
39
39
  };
40
40
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
41
  const addGeometry = (data, color, parent) => {
@@ -92,7 +92,7 @@ export const provideShapes = () => {
92
92
  },
93
93
  batched: {
94
94
  id: arrowId,
95
- name: batchedArrow.object3d.name,
95
+ object: batchedArrow.object3d,
96
96
  },
97
97
  }));
98
98
  }
@@ -1,4 +1,2 @@
1
- export declare const parsePcdInWorker: (array: Uint8Array<ArrayBufferLike>) => Promise<{
2
- positions: ArrayBuffer;
3
- colors: ArrayBuffer | undefined;
4
- }>;
1
+ import type { SuccessMessage } from './worker';
2
+ export declare const parsePcdInWorker: (data: Uint8Array<ArrayBufferLike>) => Promise<SuccessMessage>;
@@ -1,12 +1,14 @@
1
1
  const worker = new Worker(new URL('./worker', import.meta.url), { type: 'module' });
2
- export const parsePcdInWorker = async (array) => {
2
+ export const parsePcdInWorker = async (data) => {
3
3
  return new Promise((resolve, reject) => {
4
- worker.onmessage = (event) => {
5
- if (event.data.error) {
4
+ const onMessage = (event) => {
5
+ worker.removeEventListener('message', onMessage);
6
+ if ('error' in event.data) {
6
7
  return reject(event.data.error);
7
8
  }
8
- resolve({ positions: event.data.positionArray, colors: event.data.colorArray });
9
+ resolve(event.data);
9
10
  };
10
- worker.postMessage({ data: array }, [array.buffer]);
11
+ worker.addEventListener('message', onMessage);
12
+ worker.postMessage({ data }, [data.buffer]);
11
13
  });
12
14
  };
@@ -1 +1,7 @@
1
- export {};
1
+ export interface SuccessMessage {
2
+ positions: Float32Array<ArrayBuffer>;
3
+ colors: Float32Array | null;
4
+ }
5
+ export type Message = SuccessMessage | {
6
+ error: string;
7
+ };
@@ -10,11 +10,9 @@ self.onmessage = async (event) => {
10
10
  try {
11
11
  const pcd = loader.parse(data.buffer);
12
12
  if (pcd.geometry) {
13
- const positionArray = pcd.geometry.attributes.position.array;
14
- const colorArray = pcd.geometry.attributes.color ? pcd.geometry.attributes.color.array : null;
15
- postMessage({ success: true, positionArray, colorArray }, colorArray
16
- ? [positionArray.buffer, colorArray.buffer]
17
- : [positionArray.buffer]);
13
+ const positions = pcd.geometry.attributes.position.array;
14
+ const colors = pcd.geometry.attributes.color?.array ?? null;
15
+ postMessage({ positions, colors }, colors ? [positions.buffer, colors.buffer] : [positions.buffer]);
18
16
  }
19
17
  else {
20
18
  postMessage({ error: 'Failed to extract geometry' });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -16,13 +16,13 @@
16
16
  "@skeletonlabs/skeleton": "3.1.3",
17
17
  "@skeletonlabs/skeleton-svelte": "1.2.3",
18
18
  "@sveltejs/adapter-static": "^3.0.8",
19
- "@sveltejs/kit": "^2.21.1",
19
+ "@sveltejs/kit": "^2.21.3",
20
20
  "@sveltejs/package": "^2.3.11",
21
- "@sveltejs/vite-plugin-svelte": "^5.0.3",
21
+ "@sveltejs/vite-plugin-svelte": "^5.1.0",
22
22
  "@tailwindcss/forms": "^0.5.10",
23
23
  "@tailwindcss/vite": "^4.1.8",
24
- "@tanstack/svelte-query": "^5.79.2",
25
- "@tanstack/svelte-query-devtools": "^5.79.2",
24
+ "@tanstack/svelte-query": "^5.80.6",
25
+ "@tanstack/svelte-query-devtools": "^5.80.6",
26
26
  "@testing-library/jest-dom": "^6.6.3",
27
27
  "@testing-library/svelte": "^5.2.8",
28
28
  "@threlte/core": "^8.0.4",
@@ -35,11 +35,11 @@
35
35
  "@typescript-eslint/eslint-plugin": "^8.33.1",
36
36
  "@typescript-eslint/parser": "^8.33.1",
37
37
  "@viamrobotics/prime-core": "^0.1.5",
38
- "@viamrobotics/sdk": "0.42.0",
39
- "@viamrobotics/svelte-sdk": "0.2.0",
38
+ "@viamrobotics/sdk": "0.43.0",
39
+ "@viamrobotics/svelte-sdk": "0.3.3",
40
40
  "@vitejs/plugin-basic-ssl": "^2.0.0",
41
- "@zag-js/svelte": "1.14.0",
42
- "@zag-js/tree-view": "1.14.0",
41
+ "@zag-js/svelte": "1.15.0",
42
+ "@zag-js/tree-view": "1.15.0",
43
43
  "camera-controls": "^2.10.1",
44
44
  "eslint": "^9.28.0",
45
45
  "eslint-config-prettier": "^10.1.5",
@@ -48,13 +48,13 @@
48
48
  "idb-keyval": "^6.2.2",
49
49
  "jsdom": "^26.1.0",
50
50
  "lodash-es": "^4.17.21",
51
- "lucide-svelte": "^0.511.0",
51
+ "lucide-svelte": "^0.513.0",
52
52
  "prettier": "^3.5.3",
53
53
  "prettier-plugin-svelte": "^3.4.0",
54
54
  "prettier-plugin-tailwindcss": "^0.6.12",
55
55
  "publint": "^0.3.12",
56
56
  "runed": "^0.28.0",
57
- "svelte": "5.33.14",
57
+ "svelte": "5.33.18",
58
58
  "svelte-check": "^4.2.1",
59
59
  "svelte-virtuallists": "^1.4.2",
60
60
  "tailwindcss": "^4.1.8",
@@ -65,7 +65,7 @@
65
65
  "typescript-eslint": "^8.33.1",
66
66
  "vite": "^6.3.5",
67
67
  "vite-plugin-mkcert": "^1.17.8",
68
- "vitest": "^3.2.0"
68
+ "vitest": "^3.2.3"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "@dimforge/rapier3d-compat": ">=0.17",