@viamrobotics/motion-tools 0.5.0 → 0.5.2

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 (42) hide show
  1. package/README.md +16 -6
  2. package/dist/WorldObject.d.ts +3 -3
  3. package/dist/WorldObject.js +1 -1
  4. package/dist/color.d.ts +16 -1
  5. package/dist/color.js +59 -5
  6. package/dist/components/App.svelte +2 -20
  7. package/dist/components/AxesHelper.svelte +3 -3
  8. package/dist/components/CameraControls.svelte +2 -2
  9. package/dist/components/CameraControls.svelte.d.ts +3 -1
  10. package/dist/components/Details.svelte +35 -13
  11. package/dist/components/Focus.svelte +2 -16
  12. package/dist/components/Frame.svelte +6 -0
  13. package/dist/components/Geometry.svelte +31 -19
  14. package/dist/components/Geometry.svelte.d.ts +3 -2
  15. package/dist/components/KeyboardControls.svelte +102 -0
  16. package/dist/components/KeyboardControls.svelte.d.ts +7 -0
  17. package/dist/components/Pointcloud.svelte +4 -4
  18. package/dist/components/Pointcloud.svelte.d.ts +1 -1
  19. package/dist/components/Scene.svelte +5 -1
  20. package/dist/components/SceneProviders.svelte +1 -2
  21. package/dist/components/StaticGeometries.svelte +13 -31
  22. package/dist/components/StaticGeometries.svelte.d.ts +2 -17
  23. package/dist/components/WorldObject.svelte +1 -0
  24. package/dist/components/dashboard/Dashboard.svelte +0 -9
  25. package/dist/components/xr/XR.svelte +14 -15
  26. package/dist/hooks/useFrames.svelte.js +1 -1
  27. package/dist/hooks/useGeometries.svelte.js +1 -2
  28. package/dist/hooks/useObjectEvents.svelte.d.ts +1 -0
  29. package/dist/hooks/useObjectEvents.svelte.js +8 -1
  30. package/dist/hooks/usePointclouds.svelte.js +1 -1
  31. package/dist/hooks/useSelection.svelte.d.ts +1 -1
  32. package/dist/hooks/useSelection.svelte.js +18 -2
  33. package/dist/hooks/useSettings.svelte.d.ts +2 -0
  34. package/dist/hooks/useSettings.svelte.js +3 -1
  35. package/dist/hooks/useShapes.svelte.js +3 -3
  36. package/dist/loaders/pcd/index.d.ts +2 -4
  37. package/dist/loaders/pcd/index.js +7 -5
  38. package/dist/loaders/pcd/worker.d.ts +7 -1
  39. package/dist/loaders/pcd/worker.js +3 -5
  40. package/package.json +16 -16
  41. package/dist/keybindings.d.ts +0 -12
  42. package/dist/keybindings.js +0 -12
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.
@@ -6,10 +46,24 @@ import { Color } from 'three';
6
46
  * @returns A new THREE.Color instance with the darkened color.
7
47
  */
8
48
  export const darkenColor = (value, percent) => {
9
- const color = new Color(value);
10
- const hsl = { h: 0, s: 0, l: 0 };
11
- color.getHSL(hsl);
49
+ const original = new Color(value);
50
+ const hsl = original.getHSL({ h: 0, s: 0, l: 0 });
12
51
  hsl.l = Math.max(0, hsl.l * (1 - percent / 100));
13
- return color.setHSL(hsl.h, hsl.s, hsl.l);
52
+ return new Color().setHSL(hsl.h, hsl.s, hsl.l);
53
+ };
54
+ export const colors = {
55
+ selected: oklchToHex(twColors.red['900']),
56
+ default: oklchToHex(twColors.red['500']),
57
+ arm: {
58
+ selected: oklchToHex(twColors.amber['900']),
59
+ default: oklchToHex(twColors.amber['500']),
60
+ },
61
+ camera: {
62
+ selected: oklchToHex(twColors.blue['900']),
63
+ default: oklchToHex(twColors.blue['500']),
64
+ },
65
+ gripper: {
66
+ selected: oklchToHex(twColors.cyan['900']),
67
+ default: oklchToHex(twColors.cyan['500']),
68
+ },
14
69
  };
15
- export const mapStringToColor = () => { };
@@ -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(() => {
@@ -42,7 +42,7 @@
42
42
  interface Props {
43
43
  ref?: CameraController
44
44
  enabled?: boolean
45
- children?: Snippet
45
+ children?: Snippet<[{ ref: CameraController }]>
46
46
  }
47
47
 
48
48
  let { ref = $bindable(), enabled = true, children }: Props = $props()
@@ -77,5 +77,5 @@
77
77
  is={controls}
78
78
  bind:ref
79
79
  >
80
- {@render children?.()}
80
+ {@render children?.({ ref: controls })}
81
81
  </T>
@@ -3,7 +3,9 @@ import type { Snippet } from 'svelte';
3
3
  interface Props {
4
4
  ref?: CameraController;
5
5
  enabled?: boolean;
6
- children?: Snippet;
6
+ children?: Snippet<[{
7
+ ref: CameraController;
8
+ }]>;
7
9
  }
8
10
  declare const CameraControls: import("svelte").Component<Props, {}, "ref">;
9
11
  type CameraControls = ReturnType<typeof CameraControls>;
@@ -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>
@@ -1,12 +1,10 @@
1
1
  <script lang="ts">
2
- import { isInstanceOf, T } from '@threlte/core'
2
+ import { T } from '@threlte/core'
3
3
  import { TrackballControls, Gizmo } from '@threlte/extras'
4
- import { useFocused, useFocusedObject3d } from '../hooks/useSelection.svelte'
5
- import { Keybindings } from '../keybindings'
4
+ import { useFocusedObject3d } from '../hooks/useSelection.svelte'
6
5
  import { Box3, Vector3 } from 'three'
7
6
  import Camera from './Camera.svelte'
8
7
 
9
- const focus = useFocused()
10
8
  const focusedObject = useFocusedObject3d()
11
9
  const object3d = $derived(focusedObject.current)
12
10
 
@@ -21,20 +19,8 @@
21
19
  center = box.getCenter(vec).toArray()
22
20
  }
23
21
  })
24
-
25
- const onkeydown = ({ key }: KeyboardEvent) => {
26
- if (key === Keybindings.ESCAPE) {
27
- focus.set(undefined)
28
- } else if (key === Keybindings.UP || key === Keybindings.DOWN) {
29
- if (object3d && 'material' in object3d && isInstanceOf(object3d.material, 'PointsMaterial')) {
30
- object3d.material.size += key === Keybindings.UP ? 0.001 : -0.001
31
- }
32
- }
33
- }
34
22
  </script>
35
23
 
36
- <svelte:window {onkeydown} />
37
-
38
24
  <Camera position={[2, 0, 0]}>
39
25
  <TrackballControls target={center}>
40
26
  <Gizmo />
@@ -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, darkenColor } from '../color'
7
9
 
8
10
  interface Props {
9
11
  uuid: string
@@ -16,11 +18,15 @@
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
28
+ ? `#${darkenColor(rest.metadata.color ?? colors.default, 75).getHexString()}`
29
+ : undefined}
24
30
  {...events}
25
31
  {...rest}
26
32
  />
@@ -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>;
@@ -0,0 +1,102 @@
1
+ <script lang="ts">
2
+ import { MathUtils } from 'three'
3
+ import { useTask } from '@threlte/core'
4
+ import type CameraController from 'camera-controls'
5
+ import { PressedKeys } from 'runed'
6
+ import { useFocused } from '../hooks/useSelection.svelte'
7
+ import { useSettings } from '../hooks/useSettings.svelte'
8
+
9
+ interface Props {
10
+ cameraControls: CameraController
11
+ }
12
+
13
+ let { cameraControls }: Props = $props()
14
+
15
+ const focus = useFocused()
16
+ const settings = useSettings()
17
+
18
+ const keys = new PressedKeys()
19
+ const w = $derived(keys.has('w'))
20
+ const s = $derived(keys.has('s'))
21
+ const a = $derived(keys.has('a'))
22
+ const d = $derived(keys.has('d'))
23
+ const up = $derived(keys.has('arrowup'))
24
+ const left = $derived(keys.has('arrowleft'))
25
+ const down = $derived(keys.has('arrowdown'))
26
+ const right = $derived(keys.has('arrowright'))
27
+ const any = $derived(w || s || a || d || up || left || down || right)
28
+
29
+ const { start, stop } = useTask(
30
+ (delta) => {
31
+ const dt = delta * 1000
32
+
33
+ if (a) {
34
+ cameraControls.truck(-0.01 * dt, 0, true)
35
+ }
36
+
37
+ if (d) {
38
+ cameraControls.truck(0.01 * dt, 0, true)
39
+ }
40
+
41
+ if (w) {
42
+ cameraControls.forward(0.01 * dt, true)
43
+ }
44
+
45
+ if (s) {
46
+ cameraControls.forward(-0.01 * dt, true)
47
+ }
48
+
49
+ if (left) {
50
+ cameraControls.rotate(-0.1 * MathUtils.DEG2RAD * dt, 0, true)
51
+ }
52
+
53
+ if (right) {
54
+ cameraControls.rotate(0.1 * MathUtils.DEG2RAD * dt, 0, true)
55
+ }
56
+
57
+ if (up) {
58
+ cameraControls.rotate(0, -0.05 * MathUtils.DEG2RAD * dt, true)
59
+ }
60
+
61
+ if (down) {
62
+ cameraControls.rotate(0, 0.05 * MathUtils.DEG2RAD * dt, true)
63
+ }
64
+ },
65
+ { autoStart: false, autoInvalidate: false }
66
+ )
67
+
68
+ $effect.pre(() => {
69
+ if (any) {
70
+ start()
71
+ } else {
72
+ stop()
73
+ }
74
+ })
75
+
76
+ keys.onKeys('escape', () => {
77
+ if (keys.has('escape')) {
78
+ focus.set(undefined)
79
+ }
80
+ })
81
+
82
+ keys.onKeys('c', () => {
83
+ settings.current.cameraMode =
84
+ settings.current.cameraMode === 'perspective' ? 'orthographic' : 'perspective'
85
+ })
86
+
87
+ keys.onKeys('1', () => {
88
+ settings.current.transformMode = 'translate'
89
+ })
90
+
91
+ keys.onKeys('2', () => {
92
+ settings.current.transformMode = 'rotate'
93
+ })
94
+
95
+ keys.onKeys('3', () => {
96
+ settings.current.transformMode = 'scale'
97
+ })
98
+
99
+ keys.onKeys(['ctrl', 'x'], () => {
100
+ settings.current.enableXR = !settings.current.enableXR
101
+ })
102
+ </script>
@@ -0,0 +1,7 @@
1
+ import type CameraController from 'camera-controls';
2
+ interface Props {
3
+ cameraControls: CameraController;
4
+ }
5
+ declare const KeyboardControls: import("svelte").Component<Props, {}, "">;
6
+ type KeyboardControls = ReturnType<typeof KeyboardControls>;
7
+ export default KeyboardControls;
@@ -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, {}, "">;
@@ -15,6 +15,7 @@
15
15
  import type { Snippet } from 'svelte'
16
16
  import { useXR } from '@threlte/xr'
17
17
  import { useTransformControls } from '../hooks/useControls.svelte'
18
+ import KeyboardControls from './KeyboardControls.svelte'
18
19
 
19
20
  interface Props {
20
21
  children?: Snippet
@@ -48,7 +49,10 @@
48
49
  {#if !$isPresenting}
49
50
  <Camera position={[3, 3, 3]}>
50
51
  <CameraControls enabled={!transformControls.active}>
51
- <Gizmo />
52
+ {#snippet children({ ref })}
53
+ <KeyboardControls cameraControls={ref} />
54
+ <Gizmo />
55
+ {/snippet}
52
56
  </CameraControls>
53
57
  </Camera>
54
58
  {/if}
@@ -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)
@@ -3,45 +3,27 @@
3
3
  import { useSelected } from '../hooks/useSelection.svelte'
4
4
  import { useStaticGeometries } from '../hooks/useStaticGeometries.svelte'
5
5
  import { useTransformControls } from '../hooks/useControls.svelte'
6
- import { Keybindings } from '../keybindings'
7
- import { PersistedState } from 'runed'
6
+ import { PressedKeys } from 'runed'
8
7
  import { quaternionToPose, scaleToDimensions, vector3ToPose } from '../transform'
9
8
  import { Quaternion, Vector3 } from 'three'
10
9
  import Frame from './Frame.svelte'
10
+ import { useSettings } from '../hooks/useSettings.svelte'
11
11
 
12
- type Modes = 'translate' | 'rotate' | 'scale'
13
-
12
+ const settings = useSettings()
14
13
  const transformControls = useTransformControls()
15
14
  const geometries = useStaticGeometries()
16
15
  const selected = useSelected()
17
16
 
18
- let mode = new PersistedState<Modes>('transform-mode', 'translate')
17
+ const mode = $derived(settings.current.transformMode)
19
18
 
20
19
  const quaternion = new Quaternion()
21
20
  const vector3 = new Vector3()
22
- </script>
23
21
 
24
- <svelte:window
25
- onkeydown={(event) => {
26
- if (event.metaKey || event.ctrlKey) {
27
- return
28
- }
22
+ const keys = new PressedKeys()
29
23
 
30
- const key = event.key.toLowerCase()
31
-
32
- if (key === Keybindings.ADD_GEOMETRY) {
33
- geometries.add()
34
- } else if (key === Keybindings.REMOVE_GEOMETRY) {
35
- geometries.remove(selected.current ?? '')
36
- } else if (key === Keybindings.TRANSLATE) {
37
- mode.current = 'translate'
38
- } else if (key === Keybindings.ROTATE) {
39
- mode.current = 'rotate'
40
- } else if (key === Keybindings.SCALE) {
41
- mode.current = 'scale'
42
- }
43
- }}
44
- />
24
+ keys.onKeys('=', () => geometries.add())
25
+ keys.onKeys('-', () => geometries.remove(selected.current ?? ''))
26
+ </script>
45
27
 
46
28
  {#each geometries.current as object (object.uuid)}
47
29
  <Frame
@@ -53,20 +35,20 @@
53
35
  >
54
36
  {#snippet children({ ref })}
55
37
  {#if selected.current === ref.uuid}
56
- {#key mode.current}
38
+ {#key mode}
57
39
  <TransformControls
58
40
  object={ref}
59
- mode={mode.current}
41
+ {mode}
60
42
  onmouseDown={() => transformControls.setActive(true)}
61
43
  onmouseUp={() => {
62
44
  transformControls.setActive(false)
63
45
 
64
- if (mode.current === 'translate') {
46
+ if (mode === 'translate') {
65
47
  vector3ToPose(ref.getWorldPosition(vector3), object.pose)
66
- } else if (mode.current === 'rotate') {
48
+ } else if (mode === 'rotate') {
67
49
  quaternionToPose(ref.getWorldQuaternion(quaternion), object.pose)
68
50
  ref.quaternion.copy(quaternion)
69
- } else if (mode.current === 'scale' && object.geometry?.case === 'box') {
51
+ } else if (mode === 'scale' && object.geometry?.case === 'box') {
70
52
  scaleToDimensions(ref.scale, object.geometry)
71
53
  ref.scale.setScalar(1)
72
54
  }
@@ -1,18 +1,3 @@
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 StaticGeometries: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
- [evt: string]: CustomEvent<any>;
16
- }, {}, {}, string>;
17
- type StaticGeometries = InstanceType<typeof StaticGeometries>;
1
+ declare const StaticGeometries: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type StaticGeometries = ReturnType<typeof StaticGeometries>;
18
3
  export default StaticGeometries;
@@ -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}
@@ -5,15 +5,6 @@
5
5
  const settings = useSettings()
6
6
  </script>
7
7
 
8
- <svelte:window
9
- onkeydown={({ key }) => {
10
- if (key.toLowerCase() === 'c') {
11
- settings.current.cameraMode =
12
- settings.current.cameraMode === 'perspective' ? 'orthographic' : 'perspective'
13
- }
14
- }}
15
- />
16
-
17
8
  <div class="absolute top-2 flex w-full justify-center">
18
9
  <!-- transform -->
19
10
  <!-- <fieldset>
@@ -1,20 +1,19 @@
1
1
  <script lang="ts">
2
- import { useXR, XR } from '@threlte/xr'
2
+ import { XR, XRButton } from '@threlte/xr'
3
3
  import OriginMarker from './OriginMarker.svelte'
4
- import { useThrelte } from '@threlte/core'
4
+ import DomPortal from '../DomPortal.svelte'
5
+ import { useSettings } from '../../hooks/useSettings.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
12
-
13
- console.log(left, right)
14
- }
15
- })
7
+ const settings = useSettings()
8
+ const enableXR = $derived(settings.current.enableXR)
16
9
  </script>
17
10
 
18
- <XR>
19
- <OriginMarker />
20
- </XR>
11
+ {#if enableXR}
12
+ <XR>
13
+ <OriginMarker />
14
+ </XR>
15
+
16
+ <DomPortal>
17
+ <XRButton mode="immersive-ar" />
18
+ </DomPortal>
19
+ {/if}
@@ -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);
@@ -4,6 +4,7 @@ export declare const useObjectEvents: (uuid: () => string) => {
4
4
  onpointerenter: (event: IntersectionEvent<MouseEvent>) => void;
5
5
  onpointerleave: (event: IntersectionEvent<MouseEvent>) => void;
6
6
  ondblclick: (event: IntersectionEvent<MouseEvent>) => void;
7
+ onpointerdown: (event: IntersectionEvent<MouseEvent>) => void;
7
8
  onclick: (event: IntersectionEvent<MouseEvent>) => void;
8
9
  onpointermissed: () => void;
9
10
  };
@@ -1,11 +1,13 @@
1
1
  import { useCursor } from '@threlte/extras';
2
2
  import { useFocused, useSelected } from './useSelection.svelte';
3
3
  import { useVisibility } from './useVisibility.svelte';
4
+ import { Vector2 } from 'three';
4
5
  export const useObjectEvents = (uuid) => {
5
6
  const { onPointerEnter, onPointerLeave } = useCursor();
6
7
  const selected = useSelected();
7
8
  const focused = useFocused();
8
9
  const visibility = useVisibility();
10
+ const down = new Vector2();
9
11
  return {
10
12
  get visible() {
11
13
  return visibility.get(uuid());
@@ -22,9 +24,14 @@ export const useObjectEvents = (uuid) => {
22
24
  event.stopPropagation();
23
25
  focused.set(uuid());
24
26
  },
27
+ onpointerdown: (event) => {
28
+ down.copy(event.pointer);
29
+ },
25
30
  onclick: (event) => {
26
31
  event.stopPropagation();
27
- selected.set(uuid());
32
+ if (down.distanceToSquared(event.pointer) < 0.1) {
33
+ selected.set(uuid());
34
+ }
28
35
  },
29
36
  onpointermissed: () => selected.set(),
30
37
  };
@@ -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;
@@ -1,5 +1,7 @@
1
1
  interface Settings {
2
2
  cameraMode: 'orthographic' | 'perspective';
3
+ transformMode: 'translate' | 'rotate' | 'scale';
4
+ enableXR: boolean;
3
5
  }
4
6
  interface Context {
5
7
  current: Settings;
@@ -3,12 +3,14 @@ import { getContext, setContext } from 'svelte';
3
3
  const key = Symbol('dashboard-context');
4
4
  const defaults = () => ({
5
5
  cameraMode: 'perspective',
6
+ transformMode: 'translate',
7
+ enableXR: false,
6
8
  });
7
9
  export const provideSettings = () => {
8
10
  let settings = $state(defaults());
9
11
  get('motion-tools-settings').then((response) => {
10
12
  if (response) {
11
- settings = response;
13
+ settings = { ...settings, ...response };
12
14
  }
13
15
  });
14
16
  $effect(() => {
@@ -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.2",
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.4",
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",
@@ -32,29 +32,29 @@
32
32
  "@types/bun": "^1.2.15",
33
33
  "@types/lodash-es": "^4.17.12",
34
34
  "@types/three": "^0.177.0",
35
- "@typescript-eslint/eslint-plugin": "^8.33.1",
36
- "@typescript-eslint/parser": "^8.33.1",
35
+ "@typescript-eslint/eslint-plugin": "^8.34.0",
36
+ "@typescript-eslint/parser": "^8.34.0",
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.1",
42
+ "@zag-js/tree-view": "1.15.1",
43
43
  "camera-controls": "^2.10.1",
44
44
  "eslint": "^9.28.0",
45
45
  "eslint-config-prettier": "^10.1.5",
46
- "eslint-plugin-svelte": "^3.9.1",
46
+ "eslint-plugin-svelte": "^3.9.2",
47
47
  "globals": "^16.2.0",
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.19",
58
58
  "svelte-check": "^4.2.1",
59
59
  "svelte-virtuallists": "^1.4.2",
60
60
  "tailwindcss": "^4.1.8",
@@ -62,10 +62,10 @@
62
62
  "threlte-uikit": "^1.1.0",
63
63
  "tsx": "^4.19.4",
64
64
  "typescript": "^5.8.3",
65
- "typescript-eslint": "^8.33.1",
65
+ "typescript-eslint": "^8.34.0",
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",
@@ -1,12 +0,0 @@
1
- export declare const Keybindings: {
2
- MACHINES: string;
3
- TREEVIEW: string;
4
- ESCAPE: string;
5
- UP: string;
6
- DOWN: string;
7
- ADD_GEOMETRY: string;
8
- REMOVE_GEOMETRY: string;
9
- TRANSLATE: string;
10
- ROTATE: string;
11
- SCALE: string;
12
- };
@@ -1,12 +0,0 @@
1
- export const Keybindings = {
2
- MACHINES: 'm',
3
- TREEVIEW: '`',
4
- ESCAPE: 'Escape',
5
- UP: 'ArrowUp',
6
- DOWN: 'ArrowDown',
7
- ADD_GEOMETRY: '=',
8
- REMOVE_GEOMETRY: '-',
9
- TRANSLATE: 't',
10
- ROTATE: 'r',
11
- SCALE: 's',
12
- };