@viamrobotics/motion-tools 0.4.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.
Files changed (54) hide show
  1. package/README.md +21 -11
  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 +56 -1
  6. package/dist/components/App.svelte +4 -20
  7. package/dist/components/AxesHelper.svelte +69 -9
  8. package/dist/components/AxesHelper.svelte.d.ts +9 -2
  9. package/dist/components/Camera.svelte +5 -12
  10. package/dist/components/Details.svelte +46 -15
  11. package/dist/components/Focus.svelte +0 -2
  12. package/dist/components/Frame.svelte +4 -0
  13. package/dist/components/Geometry.svelte +32 -20
  14. package/dist/components/Geometry.svelte.d.ts +3 -2
  15. package/dist/components/Pointcloud.svelte +4 -4
  16. package/dist/components/Pointcloud.svelte.d.ts +1 -1
  17. package/dist/components/Scene.svelte +1 -1
  18. package/dist/components/SceneProviders.svelte +3 -2
  19. package/dist/components/Tree/Tree.svelte +11 -2
  20. package/dist/components/Tree/Tree.svelte.d.ts +2 -0
  21. package/dist/components/Tree/TreeContainer.svelte +21 -40
  22. package/dist/components/WorldObject.svelte +1 -0
  23. package/dist/components/dashboard/Button.svelte +47 -0
  24. package/dist/components/dashboard/Button.svelte.d.ts +12 -0
  25. package/dist/components/dashboard/Dashboard.svelte +77 -0
  26. package/dist/components/dashboard/Dashboard.svelte.d.ts +26 -0
  27. package/dist/components/xr/XR.svelte +20 -14
  28. package/dist/components/xr/XR.svelte.d.ts +17 -2
  29. package/dist/hooks/useDraggable.svelte.d.ts +10 -2
  30. package/dist/hooks/useDraggable.svelte.js +24 -13
  31. package/dist/hooks/useFrames.svelte.js +1 -1
  32. package/dist/hooks/useGeometries.svelte.js +1 -2
  33. package/dist/hooks/usePointclouds.svelte.js +3 -3
  34. package/dist/hooks/useSelection.svelte.d.ts +1 -1
  35. package/dist/hooks/useSelection.svelte.js +18 -2
  36. package/dist/hooks/useSettings.svelte.d.ts +9 -0
  37. package/dist/hooks/useSettings.svelte.js +25 -0
  38. package/dist/hooks/useShapes.svelte.js +5 -5
  39. package/dist/lib.d.ts +3 -1
  40. package/dist/lib.js +6 -1
  41. package/dist/loaders/pcd/index.d.ts +2 -4
  42. package/dist/loaders/pcd/index.js +7 -6
  43. package/dist/loaders/pcd/worker.d.ts +7 -1
  44. package/dist/loaders/pcd/worker.js +3 -5
  45. package/dist/test/createRandomPcdBinary.d.ts +1 -0
  46. package/dist/test/createRandomPcdBinary.js +31 -0
  47. package/dist/test.d.ts +1 -0
  48. package/dist/test.js +1 -0
  49. package/dist/three/OrientationVector.d.ts +71 -0
  50. package/dist/three/OrientationVector.js +233 -0
  51. package/dist/transform.js +1 -1
  52. package/package.json +28 -25
  53. package/dist/three/AxesHelper.d.ts +0 -5
  54. package/dist/three/AxesHelper.js +0 -35
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
- - animated sequence
15
- - double click to set trackball center
16
- - Give error logs
17
- - default pointcloud color
18
- - remote IP access
19
- - ortho points are messed up size-wise
20
- - geometries parented to parent
21
- - end effector pose visualized
22
- - poses of all frames
23
- - bounding boxes should include just the thing and not children
24
- - configure frames from here
14
+ ----- hard -----
15
+
16
+ - animated sequence of motion plan
17
+ - Give better fetching / connection state info
18
+ - configure frames in app
19
+ - embed in teleop
20
+
21
+ ----- medium -----
22
+
23
+ - remote IP access when custom drawing, to draw on remote computers
24
+ - geometries need to be parented to parent
25
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
34
+
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,16 +1,15 @@
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'
12
+ import Dashboard from './dashboard/Dashboard.svelte'
14
13
 
15
14
  interface Props {
16
15
  partID?: string
@@ -21,19 +20,9 @@
21
20
 
22
21
  createPartIDContext(() => partID)
23
22
 
24
- const enableXR = new PersistedState('enable-xr', false)
25
-
26
- let root: HTMLElement
23
+ let root = $state<HTMLElement>()
27
24
  </script>
28
25
 
29
- <svelte:window
30
- onkeydown={(event) => {
31
- if (event.ctrlKey && event.key.toLowerCase() === 'a') {
32
- enableXR.current = !enableXR.current
33
- }
34
- }}
35
- />
36
-
37
26
  <div
38
27
  class="relative h-full w-full"
39
28
  bind:this={root}
@@ -45,12 +34,11 @@
45
34
  <Scene>
46
35
  {@render appChildren?.()}
47
36
 
48
- {#if enableXR.current}
49
- <XR />
50
- {/if}
37
+ <XR />
51
38
  </Scene>
52
39
 
53
40
  <DomPortal element={root}>
41
+ <Dashboard />
54
42
  <Details />
55
43
  </DomPortal>
56
44
 
@@ -64,7 +52,3 @@
64
52
  </World>
65
53
  </Canvas>
66
54
  </div>
67
-
68
- {#if enableXR.current}
69
- <XRButton mode="immersive-ar" />
70
- {/if}
@@ -1,17 +1,77 @@
1
1
  <script lang="ts">
2
2
  import { T, type Props as ThrelteProps } from '@threlte/core'
3
- import { AxesHelper } from '../three/AxesHelper'
4
- import { meshBounds } from '@threlte/extras'
5
- interface Props extends ThrelteProps<AxesHelper> {
6
- size?: number
7
- thickness?: number
3
+ import { Color } from 'three'
4
+ import { Line2 } from 'three/examples/jsm/lines/Line2.js'
5
+ import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js'
6
+ import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js'
7
+
8
+ interface Props extends ThrelteProps<Line2> {
9
+ length?: number
10
+ width?: number
11
+ axesColors?: [x: string, y: string, z: string]
12
+ depthTest?: boolean
8
13
  }
9
14
 
10
- let { size, thickness, ...rest }: Props = $props()
15
+ const {
16
+ length = 1,
17
+ width = 0.1,
18
+ axesColors = ['red', 'green', 'blue'],
19
+ depthTest = true,
20
+ ...rest
21
+ }: Props = $props()
22
+
23
+ const TOTAL_VERTICES = 9
24
+ const VERTEX_COMPONENTS = 3
25
+
26
+ const line = new Line2()
27
+ const material = new LineMaterial()
28
+ const geometry = new LineGeometry()
29
+ const color = new Color()
30
+ const colors = new Float32Array(TOTAL_VERTICES * VERTEX_COMPONENTS)
31
+ const positions = new Float32Array(TOTAL_VERTICES * VERTEX_COMPONENTS)
32
+
33
+ // Assign colors per vertex
34
+ $effect.pre(() => {
35
+ for (let i = 0, l = axesColors.length; i < l; i += 1) {
36
+ const axis = axesColors[i]
37
+
38
+ color.set(axis)
39
+
40
+ const axisBufferStart = i * TOTAL_VERTICES
41
+ const axisBufferEnd = axisBufferStart + TOTAL_VERTICES
42
+
43
+ for (let j = axisBufferStart; j < axisBufferEnd; j += VERTEX_COMPONENTS) {
44
+ colors[j + 0] = color.r
45
+ colors[j + 1] = color.g
46
+ colors[j + 2] = color.b
47
+ }
48
+ }
49
+
50
+ geometry.setColors(colors)
51
+ })
52
+
53
+ const X_AXIS_X_COMPONENT_INDEX = 3
54
+ const Y_AXIS_Y_COMPONENT_INDEX = 13
55
+ const Z_AXIS_Z_COMPONENT_INDEX = 23
56
+
57
+ $effect.pre(() => {
58
+ positions[X_AXIS_X_COMPONENT_INDEX] = length
59
+ positions[Y_AXIS_Y_COMPONENT_INDEX] = length
60
+ positions[Z_AXIS_Z_COMPONENT_INDEX] = length
61
+ geometry.setPositions(positions)
62
+ })
11
63
  </script>
12
64
 
13
65
  <T
14
- is={new AxesHelper(size, thickness)}
15
- raycast={meshBounds}
66
+ is={line}
16
67
  {...rest}
17
- />
68
+ raycast={() => null}
69
+ >
70
+ <T is={geometry} />
71
+ <T
72
+ is={material}
73
+ vertexColors
74
+ linewidth={width}
75
+ {depthTest}
76
+ />
77
+ </T>
@@ -1,4 +1,11 @@
1
- import { AxesHelper } from '../three/AxesHelper';
2
- declare const AxesHelper: any;
1
+ import { type Props as ThrelteProps } from '@threlte/core';
2
+ import { Line2 } from 'three/examples/jsm/lines/Line2.js';
3
+ interface Props extends ThrelteProps<Line2> {
4
+ length?: number;
5
+ width?: number;
6
+ axesColors?: [x: string, y: string, z: string];
7
+ depthTest?: boolean;
8
+ }
9
+ declare const AxesHelper: import("svelte").Component<Props, {}, "">;
3
10
  type AxesHelper = ReturnType<typeof AxesHelper>;
4
11
  export default AxesHelper;
@@ -1,11 +1,12 @@
1
1
  <script lang="ts">
2
- import { PersistedState } from 'runed'
3
2
  import { T } from '@threlte/core'
4
3
  import { PerspectiveCamera, OrthographicCamera } from 'three'
4
+ import { useSettings } from '../hooks/useSettings.svelte'
5
5
 
6
6
  let { children, ...rest } = $props()
7
7
 
8
- const mode = new PersistedState<'perspective' | 'orthographic'>('camera-type', 'perspective')
8
+ const settings = useSettings()
9
+ const mode = $derived(settings.current.cameraMode)
9
10
 
10
11
  const perspective = new PerspectiveCamera()
11
12
  perspective.near = 0.01
@@ -18,15 +19,7 @@
18
19
  orthographic.zoom = 200
19
20
  </script>
20
21
 
21
- <svelte:window
22
- onkeydown={({ key }) => {
23
- if (key.toLowerCase() === 'c') {
24
- mode.current = mode.current === 'perspective' ? 'orthographic' : 'perspective'
25
- }
26
- }}
27
- />
28
-
29
- {#if mode.current === 'perspective'}
22
+ {#if mode === 'perspective'}
30
23
  <T
31
24
  is={perspective}
32
25
  makeDefault
@@ -34,7 +27,7 @@
34
27
  >
35
28
  {@render children?.()}
36
29
  </T>
37
- {:else if mode.current === 'orthographic'}
30
+ {:else if mode === 'orthographic'}
38
31
  <T
39
32
  is={orthographic}
40
33
  makeDefault
@@ -1,22 +1,50 @@
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'
12
+ import { useDraggable } from '../hooks/useDraggable.svelte'
13
+ import { OrientationVector } from '../three/OrientationVector'
5
14
 
6
15
  const focused = useFocused()
7
- const selectedObject = useSelectedObject()
8
16
  const focusedObject = useFocusedObject()
17
+ const focusedObject3d = useFocusedObject3d()
18
+
19
+ const selectedObject = useSelectedObject()
20
+ const selectedObject3d = useSelectedObject3d()
21
+
9
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
+ )
10
30
 
11
31
  let copied = $state(false)
32
+
33
+ const draggable = useDraggable('details')
12
34
  </script>
13
35
 
14
36
  {#if object}
15
- {@const { geometry, pose } = object}
16
- <div class="border-medium bg-extralight absolute top-0 right-0 z-10 m-2 w-60 border p-2 text-xs">
37
+ {@const { geometry } = object}
38
+ <div
39
+ class="border-medium bg-extralight absolute top-0 right-0 z-10 m-2 w-60 border p-2 text-xs"
40
+ style:transform="translate({draggable.current.x}px, {draggable.current.y}px)"
41
+ >
17
42
  <div class="flex items-center justify-between gap-2 pb-2">
18
43
  <div class="flex items-center gap-1">
19
- <button>
44
+ <button
45
+ onmousedown={draggable.onDragStart}
46
+ onmouseup={draggable.onDragEnd}
47
+ >
20
48
  <Icon name="drag" />
21
49
  </button>
22
50
  {object.name}
@@ -44,43 +72,46 @@
44
72
  </h3>
45
73
 
46
74
  <div class="flex flex-col gap-2.5">
47
- {#if pose}
75
+ {#if worldPosition}
48
76
  <div>
49
- <strong class="font-semibold">position</strong>
77
+ <strong class="font-semibold">world position</strong>
78
+
50
79
  <div class="flex gap-3">
51
80
  <div>
52
81
  <span class="text-subtle-2">x</span>
53
- {pose.x !== undefined ? pose.x.toFixed(2) : '-'}
82
+ {(worldPosition.x * 1000).toFixed(2)}
54
83
  </div>
55
84
  <div>
56
85
  <span class="text-subtle-2">y</span>
57
- {pose.y !== undefined ? pose.y.toFixed(2) : '-'}
86
+ {(worldPosition.y * 1000).toFixed(2)}
58
87
  </div>
59
88
  <div>
60
89
  <span class="text-subtle-2">z</span>
61
- {pose.z !== undefined ? pose.z.toFixed(2) : '-'}
90
+ {(worldPosition.z * 1000).toFixed(2)}
62
91
  </div>
63
92
  </div>
64
93
  </div>
94
+ {/if}
65
95
 
96
+ {#if worldOrientation}
66
97
  <div>
67
- <strong class="font-semibold">orientation</strong>
98
+ <strong class="font-semibold">world orientation</strong>
68
99
  <div class="flex gap-3">
69
100
  <div>
70
101
  <span class="text-subtle-2">x</span>
71
- {pose.oX !== undefined ? pose.oX.toFixed(2) : '-'}
102
+ {worldOrientation.x.toFixed(2)}
72
103
  </div>
73
104
  <div>
74
105
  <span class="text-subtle-2">y</span>
75
- {pose.oY !== undefined ? pose.oY.toFixed(2) : '-'}
106
+ {worldOrientation.y.toFixed(2)}
76
107
  </div>
77
108
  <div>
78
109
  <span class="text-subtle-2">z</span>
79
- {pose.oZ !== undefined ? pose.oZ.toFixed(2) : '-'}
110
+ {worldOrientation.z.toFixed(2)}
80
111
  </div>
81
112
  <div>
82
113
  <span class="text-subtle-2">th</span>
83
- {pose.theta !== undefined ? pose.theta.toFixed(2) : '-'}
114
+ {worldOrientation.th.toFixed(2)}
84
115
  </div>
85
116
  </div>
86
117
  </div>
@@ -14,13 +14,11 @@
14
14
  const vec = new Vector3()
15
15
 
16
16
  let center = $state.raw<[number, number, number]>([0, 0, 0])
17
- // let size = $state.raw<[number, number, number]>([0, 0, 0])
18
17
 
19
18
  $effect(() => {
20
19
  if (object3d) {
21
20
  box.setFromObject(object3d)
22
21
  center = box.getCenter(vec).toArray()
23
- // size = box.getSize(vec).toArray()
24
22
  }
25
23
  })
26
24
 
@@ -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,49 +65,46 @@
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
83
- width={5}
95
+ width={3}
84
96
  length={0.1}
85
97
  />
86
98
  {/if}
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>;