@viamrobotics/motion-tools 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +10 -10
  2. package/dist/components/App.svelte +2 -0
  3. package/dist/components/AxesHelper.svelte +69 -9
  4. package/dist/components/AxesHelper.svelte.d.ts +9 -2
  5. package/dist/components/Camera.svelte +5 -12
  6. package/dist/components/Details.svelte +11 -2
  7. package/dist/components/Focus.svelte +0 -2
  8. package/dist/components/Geometry.svelte +1 -1
  9. package/dist/components/Scene.svelte +1 -1
  10. package/dist/components/SceneProviders.svelte +2 -0
  11. package/dist/components/Tree/Tree.svelte +11 -2
  12. package/dist/components/Tree/Tree.svelte.d.ts +2 -0
  13. package/dist/components/Tree/TreeContainer.svelte +21 -40
  14. package/dist/components/dashboard/Button.svelte +47 -0
  15. package/dist/components/dashboard/Button.svelte.d.ts +12 -0
  16. package/dist/components/dashboard/Dashboard.svelte +77 -0
  17. package/dist/components/dashboard/Dashboard.svelte.d.ts +26 -0
  18. package/dist/hooks/useDraggable.svelte.d.ts +10 -2
  19. package/dist/hooks/useDraggable.svelte.js +24 -13
  20. package/dist/hooks/usePointclouds.svelte.js +2 -2
  21. package/dist/hooks/useSettings.svelte.d.ts +9 -0
  22. package/dist/hooks/useSettings.svelte.js +25 -0
  23. package/dist/hooks/useShapes.svelte.js +2 -2
  24. package/dist/lib.d.ts +3 -1
  25. package/dist/lib.js +6 -1
  26. package/dist/loaders/pcd/index.d.ts +1 -1
  27. package/dist/loaders/pcd/index.js +1 -2
  28. package/dist/test/createRandomPcdBinary.d.ts +1 -0
  29. package/dist/test/createRandomPcdBinary.js +31 -0
  30. package/dist/test.d.ts +1 -0
  31. package/dist/test.js +1 -0
  32. package/dist/three/OrientationVector.d.ts +71 -0
  33. package/dist/three/OrientationVector.js +233 -0
  34. package/dist/transform.js +1 -1
  35. package/package.json +24 -21
  36. package/dist/three/AxesHelper.d.ts +0 -5
  37. package/dist/three/AxesHelper.js +0 -35
package/README.md CHANGED
@@ -11,19 +11,19 @@ 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
14
+ - animated sequence of motion plan
15
+ - double click to set trackball center in object view
16
+ - Give better fetching / connection state info
17
+ - Set default pointcloud color in settings
18
+ - remote IP access when custom drawing, to draw on remote computers
19
+ - points are not sized right in ortho cam view
20
+ - geometries need to be parented to parent
23
21
  - bounding boxes should include just the thing and not children
24
- - configure frames from here
22
+ - configure frames in app
25
23
  - color pallet for resource to color
26
24
 
25
+ - foxglove / rviz
26
+
27
27
  ## Env files
28
28
 
29
29
  To add a list of connection configs in an `.env.local` file, use the following format:
@@ -11,6 +11,7 @@
11
11
  import XR from './xr/XR.svelte'
12
12
  import { World } from '@threlte/rapier'
13
13
  import { createPartIDContext } from '../hooks/usePartID.svelte'
14
+ import Dashboard from './dashboard/Dashboard.svelte'
14
15
 
15
16
  interface Props {
16
17
  partID?: string
@@ -51,6 +52,7 @@
51
52
  </Scene>
52
53
 
53
54
  <DomPortal element={root}>
55
+ <Dashboard />
54
56
  <Details />
55
57
  </DomPortal>
56
58
 
@@ -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 = $state(new LineMaterial())
28
+ const geometry = new LineGeometry()
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))
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
@@ -2,6 +2,7 @@
2
2
  import { useSelectedObject, useFocusedObject, useFocused } from '../hooks/useSelection.svelte'
3
3
  import { Check, Copy } from 'lucide-svelte'
4
4
  import { Button, Icon } from '@viamrobotics/prime-core'
5
+ import { useDraggable } from '../hooks/useDraggable.svelte'
5
6
 
6
7
  const focused = useFocused()
7
8
  const selectedObject = useSelectedObject()
@@ -9,14 +10,22 @@
9
10
  const object = $derived(focusedObject.current ?? selectedObject.current)
10
11
 
11
12
  let copied = $state(false)
13
+
14
+ const draggable = useDraggable('details')
12
15
  </script>
13
16
 
14
17
  {#if object}
15
18
  {@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">
19
+ <div
20
+ class="border-medium bg-extralight absolute top-0 right-0 z-10 m-2 w-60 border p-2 text-xs"
21
+ style:transform="translate({draggable.current.x}px, {draggable.current.y}px)"
22
+ >
17
23
  <div class="flex items-center justify-between gap-2 pb-2">
18
24
  <div class="flex items-center gap-1">
19
- <button>
25
+ <button
26
+ onmousedown={draggable.onDragStart}
27
+ onmouseup={draggable.onDragEnd}
28
+ >
20
29
  <Icon name="drag" />
21
30
  </button>
22
31
  {object.name}
@@ -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
 
@@ -80,7 +80,7 @@
80
80
  />
81
81
  {:else}
82
82
  <AxesHelper
83
- width={5}
83
+ width={3}
84
84
  length={0.1}
85
85
  />
86
86
  {/if}
@@ -65,7 +65,7 @@
65
65
  {#if !$isPresenting}
66
66
  <Grid
67
67
  plane="xy"
68
- sectionColor="lightgrey"
68
+ sectionColor="#333"
69
69
  infiniteGrid
70
70
  fadeOrigin={new Vector3()}
71
71
  fadeDistance={25}
@@ -14,6 +14,7 @@
14
14
  import { provideObjects } from '../hooks/useObjects.svelte'
15
15
  import { provideMotionClient } from '../hooks/useMotionClient.svelte'
16
16
  import { provideLogs } from '../hooks/useLogs.svelte'
17
+ import { provideSettings } from '../hooks/useSettings.svelte'
17
18
 
18
19
  interface Props {
19
20
  children: Snippet<[{ focus: boolean }]>
@@ -23,6 +24,7 @@
23
24
 
24
25
  const partID = usePartID()
25
26
 
27
+ provideSettings()
26
28
  provideTransformControls()
27
29
  provideVisibility()
28
30
  provideRefreshRates()
@@ -8,6 +8,7 @@
8
8
  import { useExpanded } from './useExpanded.svelte'
9
9
  import { VirtualList } from 'svelte-virtuallists'
10
10
  import { observe } from '@threlte/core'
11
+ import { Icon } from '@viamrobotics/prime-core'
11
12
 
12
13
  const visibility = useVisibility()
13
14
  const expanded = useExpanded()
@@ -16,9 +17,11 @@
16
17
  rootNode: TreeNode
17
18
  selections: string[]
18
19
  onSelectionChange?: (event: tree.SelectionChangeDetails) => void
20
+ onDragStart?: (event: MouseEvent) => void
21
+ onDragEnd?: (event: MouseEvent) => void
19
22
  }
20
23
 
21
- let { rootNode, selections, onSelectionChange }: Props = $props()
24
+ let { rootNode, selections, onSelectionChange, onDragStart, onDragEnd }: Props = $props()
22
25
 
23
26
  const collection = tree.collection<TreeNode>({
24
27
  nodeToValue: (node) => node.id,
@@ -143,7 +146,13 @@
143
146
 
144
147
  <div class="root-node">
145
148
  <div {...api.getRootProps() as object}>
146
- <div class="border-medium border-b p-2">
149
+ <div class="border-medium flex items-center gap-1 border-b p-2">
150
+ <button
151
+ onmousedown={onDragStart}
152
+ onmouseup={onDragEnd}
153
+ >
154
+ <Icon name="drag" />
155
+ </button>
147
156
  <h3 {...api.getLabelProps() as object}>{rootNode.name}</h3>
148
157
  </div>
149
158
 
@@ -4,6 +4,8 @@ interface Props {
4
4
  rootNode: TreeNode;
5
5
  selections: string[];
6
6
  onSelectionChange?: (event: tree.SelectionChangeDetails) => void;
7
+ onDragStart?: (event: MouseEvent) => void;
8
+ onDragEnd?: (event: MouseEvent) => void;
7
9
  }
8
10
  declare const Tree: import("svelte").Component<Props, {}, "">;
9
11
  type Tree = ReturnType<typeof Tree>;
@@ -1,9 +1,6 @@
1
1
  <script lang="ts">
2
- import { PersistedState } from 'runed'
3
2
  import Tree from './Tree.svelte'
4
- import { fly } from 'svelte/transition'
5
- import { Keybindings } from '../../keybindings'
6
- import { ListTree } from 'lucide-svelte'
3
+
7
4
  import { buildTreeNodes, type TreeNode } from './buildTree'
8
5
  import { useSelected } from '../../hooks/useSelection.svelte'
9
6
  import { provideTreeExpandedContext } from './useExpanded.svelte'
@@ -11,13 +8,13 @@
11
8
  import { useObjects } from '../../hooks/useObjects.svelte'
12
9
  import Settings from './Settings.svelte'
13
10
  import Logs from './Logs.svelte'
14
-
15
- const showTreeview = new PersistedState('show-treeview', true)
11
+ import { useDraggable } from '../../hooks/useDraggable.svelte'
16
12
 
17
13
  provideTreeExpandedContext()
18
14
 
19
15
  const selected = useSelected()
20
16
  const objects = useObjects()
17
+ const draggable = useDraggable('treeview')
21
18
 
22
19
  let rootNode = $state<TreeNode>({
23
20
  id: 'world',
@@ -35,38 +32,22 @@
35
32
  })
36
33
  </script>
37
34
 
38
- <svelte:window
39
- onkeydown={({ key }) => {
40
- if (key === Keybindings.TREEVIEW) {
41
- showTreeview.current = !showTreeview.current
42
- }
43
- }}
44
- />
45
-
46
- <button
47
- class="absolute top-2 left-2 p-2"
48
- onclick={() => (showTreeview.current = !showTreeview.current)}
35
+ <div
36
+ class="bg-extralight border-medium absolute top-0 left-0 m-2 overflow-y-auto border text-xs"
37
+ style:transform="translate({draggable.current.x}px, {draggable.current.y}px)"
49
38
  >
50
- <ListTree />
51
- </button>
52
-
53
- {#if showTreeview.current}
54
- <div
55
- class="bg-extralight border-medium absolute top-0 left-0 m-2 overflow-y-auto border text-xs"
56
- in:fly={{ duration: 250, x: -100 }}
57
- out:fly={{ duration: 250, x: -100 }}
58
- >
59
- {#key rootNode}
60
- <Tree
61
- {rootNode}
62
- selections={selected.current ? [selected.current] : []}
63
- onSelectionChange={(event) => {
64
- selected.set(event.selectedValue[0])
65
- }}
66
- />
67
- {/key}
68
-
69
- <Logs />
70
- <Settings />
71
- </div>
72
- {/if}
39
+ {#key rootNode}
40
+ <Tree
41
+ {rootNode}
42
+ selections={selected.current ? [selected.current] : []}
43
+ onSelectionChange={(event) => {
44
+ selected.set(event.selectedValue[0])
45
+ }}
46
+ onDragStart={draggable.onDragStart}
47
+ onDragEnd={draggable.onDragEnd}
48
+ />
49
+ {/key}
50
+
51
+ <Logs />
52
+ <Settings />
53
+ </div>
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ import { Icon, type IconName, Tooltip } from '@viamrobotics/prime-core'
3
+
4
+ interface Props {
5
+ icon: IconName
6
+ active?: boolean
7
+ description: string
8
+ hotkey: string
9
+ class?: string
10
+ onclick?: () => void
11
+ }
12
+
13
+ let {
14
+ icon,
15
+ active = false,
16
+ description,
17
+ hotkey,
18
+ class: className = '',
19
+ onclick,
20
+ }: Props = $props()
21
+
22
+ const activeClasses = 'z-10 border-gray-5 bg-white text-gray-8'
23
+ const inactiveClasses = 'bg-light border-medium text-disabled'
24
+ </script>
25
+
26
+ <Tooltip
27
+ let:tooltipID
28
+ location="bottom"
29
+ >
30
+ <label
31
+ class={[className, 'relative block border', active ? activeClasses : inactiveClasses]}
32
+ aria-describedby={tooltipID}
33
+ >
34
+ <button
35
+ class="p-1.5"
36
+ role="radio"
37
+ aria-label={description}
38
+ aria-checked={active}
39
+ {onclick}
40
+ >
41
+ <Icon name={icon} />
42
+ </button>
43
+ </label>
44
+ <p slot="description">
45
+ {description} <span class="text-gray-5 pl-1">{hotkey}</span>
46
+ </p>
47
+ </Tooltip>
@@ -0,0 +1,12 @@
1
+ import { type IconName } from '@viamrobotics/prime-core';
2
+ interface Props {
3
+ icon: IconName;
4
+ active?: boolean;
5
+ description: string;
6
+ hotkey: string;
7
+ class?: string;
8
+ onclick?: () => void;
9
+ }
10
+ declare const Button: import("svelte").Component<Props, {}, "">;
11
+ type Button = ReturnType<typeof Button>;
12
+ export default Button;
@@ -0,0 +1,77 @@
1
+ <script>
2
+ import { useSettings } from '../../hooks/useSettings.svelte'
3
+ import Button from './Button.svelte'
4
+
5
+ const settings = useSettings()
6
+ </script>
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
+ <div class="absolute top-2 flex w-full justify-center">
18
+ <!-- transform -->
19
+ <!-- <fieldset>
20
+ <Button
21
+ icon="cursor-move"
22
+ active={$transformMode === TransformModes.TRANSLATE}
23
+ description="Translate"
24
+ hotkey="T"
25
+ onclick={() => transformMode.set(TransformModes.TRANSLATE)}
26
+ />
27
+ <Button
28
+ icon="sync"
29
+ active={$transformMode === TransformModes.ROTATE}
30
+ description="Rotate"
31
+ hotkey="R"
32
+ class="-my-px"
33
+ onclick={() => transformMode.set(TransformModes.ROTATE)}
34
+ />
35
+ <Button
36
+ icon="resize"
37
+ active={$transformMode === TransformModes.SCALE}
38
+ description="Scale"
39
+ hotkey="S"
40
+ onclick={() => transformMode.set(TransformModes.SCALE)}
41
+ />
42
+ </fieldset> -->
43
+
44
+ <!-- snapping -->
45
+ <!-- <fieldset>
46
+ <Button
47
+ icon={$snapMode ? 'magnet' : 'magnet-off'}
48
+ active={$snapMode === true}
49
+ description="Snapping"
50
+ hotkey="Spacebar"
51
+ onClick={() => snapMode.set(!$snapMode)}
52
+ />
53
+ </fieldset> -->
54
+
55
+ <!-- camera view -->
56
+ <fieldset class="flex">
57
+ <Button
58
+ icon="grid-orthographic"
59
+ active={settings.current.cameraMode === 'orthographic'}
60
+ description="Orthographic view"
61
+ hotkey="C"
62
+ onclick={() => {
63
+ settings.current.cameraMode = 'orthographic'
64
+ }}
65
+ />
66
+ <Button
67
+ icon="grid-perspective"
68
+ active={settings.current.cameraMode === 'perspective'}
69
+ description="Perspective view"
70
+ hotkey="C"
71
+ class="-ml-px"
72
+ onclick={() => {
73
+ settings.current.cameraMode = 'perspective'
74
+ }}
75
+ />
76
+ </fieldset>
77
+ </div>
@@ -0,0 +1,26 @@
1
+ export default Dashboard;
2
+ type Dashboard = SvelteComponent<{
3
+ [x: string]: never;
4
+ }, {
5
+ [evt: string]: CustomEvent<any>;
6
+ }, {}> & {
7
+ $$bindings?: string | undefined;
8
+ };
9
+ declare const Dashboard: $$__sveltets_2_IsomorphicComponent<{
10
+ [x: string]: never;
11
+ }, {
12
+ [evt: string]: CustomEvent<any>;
13
+ }, {}, {}, string>;
14
+ 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> {
15
+ new (options: import("svelte").ComponentConstructorOptions<Props>): import("svelte").SvelteComponent<Props, Events, Slots> & {
16
+ $$bindings?: Bindings;
17
+ } & Exports;
18
+ (internal: unknown, props: {
19
+ $$events?: Events;
20
+ $$slots?: Slots;
21
+ }): Exports & {
22
+ $set?: any;
23
+ $on?: any;
24
+ };
25
+ z_$$bindings?: Bindings;
26
+ }
@@ -1,2 +1,10 @@
1
- export declare const provideDraggables: () => void;
2
- export declare const useDraggable: (name: string) => void;
1
+ interface Context {
2
+ onDragStart: (event: MouseEvent) => void;
3
+ onDragEnd: (event: MouseEvent) => void;
4
+ readonly current: {
5
+ x: number;
6
+ y: number;
7
+ };
8
+ }
9
+ export declare const useDraggable: (name: string) => Context;
10
+ export {};
@@ -1,25 +1,36 @@
1
- import { PersistedState } from 'runed';
2
- import { setContext } from 'svelte';
3
- const key = Symbol('draggables-context');
4
- export const provideDraggables = () => { };
1
+ import { get, set } from 'idb-keyval';
5
2
  export const useDraggable = (name) => {
6
3
  const down = { x: 0, y: 0 };
7
- const onDragMove = () => { };
4
+ const last = { x: 0, y: 0 };
5
+ let translate = $state({ x: 0, y: 0 });
6
+ const onDragMove = (event) => {
7
+ translate.x = event.clientX - down.x + last.x;
8
+ translate.y = event.clientY - down.y + last.y;
9
+ };
8
10
  const onDragStart = (event) => {
9
11
  down.x = event.clientX;
10
12
  down.y = event.clientY;
13
+ last.x = translate.x;
14
+ last.y = translate.y;
15
+ window.addEventListener('pointermove', onDragMove, { passive: true });
11
16
  };
12
- const onDragEnd = (event) => {
13
- translate.current.x += event.clientX - down.x;
14
- translate.current.y += event.clientY - down.y;
17
+ const onDragEnd = () => {
18
+ set(`${name}-draggable`, $state.snapshot(translate));
19
+ window.removeEventListener('pointermove', onDragMove);
15
20
  };
16
- const translate = new PersistedState(`${name} draggable`, { x: 0, y: 0 });
17
- setContext(key, {
21
+ get(`${name}-draggable`).then((response) => {
22
+ if (response) {
23
+ translate = response;
24
+ }
25
+ });
26
+ $effect(() => {
27
+ return () => window.removeEventListener('pointermove', onDragMove);
28
+ });
29
+ return {
18
30
  onDragStart,
19
- onDragMove,
20
31
  onDragEnd,
21
32
  get current() {
22
- return translate.current;
33
+ return translate;
23
34
  },
24
- });
35
+ };
25
36
  };
@@ -3,7 +3,7 @@ import { CameraClient } from '@viamrobotics/sdk';
3
3
  import { setContext, getContext } from 'svelte';
4
4
  import { fromStore, toStore } from 'svelte/store';
5
5
  import { createResourceClient, useResourceNames } from '@viamrobotics/svelte-sdk';
6
- import { parsePCD } from '../loaders/pcd';
6
+ import { parsePcdInWorker } from '../loaders/pcd';
7
7
  import { useRefreshRates } from './useRefreshRates.svelte';
8
8
  import { WorldObject } from '../WorldObject';
9
9
  import { usePersistentUUIDs } from './usePersistentUUIDs.svelte';
@@ -32,7 +32,7 @@ export const providePointclouds = (partID) => {
32
32
  const response = await cameraClient.current.getPointCloud();
33
33
  if (!response)
34
34
  return null;
35
- const { positions, colors } = await parsePCD(new Uint8Array(response));
35
+ const { positions, colors } = await parsePcdInWorker(new Uint8Array(response));
36
36
  return new WorldObject(`${name}:pointcloud`, undefined, name, { case: 'points', value: new Float32Array(positions) }, colors ? { colors: new Float32Array(colors) } : undefined);
37
37
  },
38
38
  });
@@ -0,0 +1,9 @@
1
+ interface Settings {
2
+ cameraMode: 'orthographic' | 'perspective';
3
+ }
4
+ interface Context {
5
+ current: Settings;
6
+ }
7
+ export declare const provideSettings: () => void;
8
+ export declare const useSettings: () => Context;
9
+ export {};
@@ -0,0 +1,25 @@
1
+ import { get, set } from 'idb-keyval';
2
+ import { getContext, setContext } from 'svelte';
3
+ const key = Symbol('dashboard-context');
4
+ const defaults = () => ({
5
+ cameraMode: 'perspective',
6
+ });
7
+ export const provideSettings = () => {
8
+ let settings = $state(defaults());
9
+ get('motion-tools-settings').then((response) => {
10
+ if (response) {
11
+ settings = response;
12
+ }
13
+ });
14
+ $effect(() => {
15
+ set('motion-tools-settings', $state.snapshot(settings));
16
+ });
17
+ setContext(key, {
18
+ get current() {
19
+ return settings;
20
+ },
21
+ });
22
+ };
23
+ export const useSettings = () => {
24
+ return getContext(key);
25
+ };
@@ -1,7 +1,7 @@
1
1
  import { getContext, setContext } from 'svelte';
2
2
  import { Vector3, Vector4 } from 'three';
3
3
  import { NURBSCurve } from 'three/addons/curves/NURBSCurve.js';
4
- import { parsePCD } from '../loaders/pcd';
4
+ import { parsePcdInWorker } from '../loaders/pcd';
5
5
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
6
6
  import { BatchedArrow } from '../three/BatchedArrow';
7
7
  import { WorldObject } from '../WorldObject';
@@ -31,7 +31,7 @@ export const provideShapes = () => {
31
31
  const loader = new GLTFLoader();
32
32
  const addPcd = async (data) => {
33
33
  const buffer = await data.arrayBuffer();
34
- const { positions, colors } = await parsePCD(new Uint8Array(buffer));
34
+ const { positions, colors } = await parsePcdInWorker(new Uint8Array(buffer));
35
35
  points.push(new WorldObject(`points ${++pointsIndex}`, undefined, undefined, {
36
36
  case: 'points',
37
37
  value: new Float32Array(positions),
package/dist/lib.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  export { default as CameraControls } from './components/CameraControls.svelte';
2
2
  export { type default as CameraController } from 'camera-controls';
3
3
  export { default as Geometry } from './components/Geometry.svelte';
4
- export { AxesHelper } from './three/AxesHelper';
4
+ export { default as AxesHelper } from './components/AxesHelper.svelte';
5
5
  export { BatchedArrow } from './three/BatchedArrow';
6
6
  export { CapsuleGeometry } from './three/CapsuleGeometry';
7
+ export { OrientationVector } from './three/OrientationVector';
7
8
  export { WorldObject } from './WorldObject';
9
+ export { parsePcdInWorker } from './loaders/pcd';
package/dist/lib.js CHANGED
@@ -1,7 +1,12 @@
1
+ // Components
1
2
  export { default as CameraControls } from './components/CameraControls.svelte';
2
3
  export {} from 'camera-controls';
3
4
  export { default as Geometry } from './components/Geometry.svelte';
4
- export { AxesHelper } from './three/AxesHelper';
5
+ export { default as AxesHelper } from './components/AxesHelper.svelte';
6
+ // Classes
5
7
  export { BatchedArrow } from './three/BatchedArrow';
6
8
  export { CapsuleGeometry } from './three/CapsuleGeometry';
9
+ export { OrientationVector } from './three/OrientationVector';
7
10
  export { WorldObject } from './WorldObject';
11
+ // Functions
12
+ export { parsePcdInWorker } from './loaders/pcd';
@@ -1,4 +1,4 @@
1
- export declare const parsePCD: (array: Uint8Array<ArrayBufferLike>) => Promise<{
1
+ export declare const parsePcdInWorker: (array: Uint8Array<ArrayBufferLike>) => Promise<{
2
2
  positions: ArrayBuffer;
3
3
  colors: ArrayBuffer | undefined;
4
4
  }>;
@@ -1,6 +1,5 @@
1
- // main.js
2
1
  const worker = new Worker(new URL('./worker', import.meta.url), { type: 'module' });
3
- export const parsePCD = async (array) => {
2
+ export const parsePcdInWorker = async (array) => {
4
3
  return new Promise((resolve, reject) => {
5
4
  worker.onmessage = (event) => {
6
5
  if (event.data.error) {
@@ -0,0 +1 @@
1
+ export declare const createRandomPcdBinary: (numPoints?: number, scale?: number, axes?: string) => Uint8Array;
@@ -0,0 +1,31 @@
1
+ export const createRandomPcdBinary = (numPoints = 200, scale = 1, axes = 'xyz') => {
2
+ const header = `
3
+ # .PCD v0.7 - Point Cloud Data file format
4
+ VERSION 0.7
5
+ FIELDS x y z rgb
6
+ SIZE 4 4 4 4
7
+ TYPE F F F F
8
+ COUNT 1 1 1
9
+ WIDTH ${numPoints}
10
+ HEIGHT 1
11
+ VIEWPOINT 0 0 0 1 0 0 0
12
+ POINTS ${numPoints}
13
+ DATA ascii
14
+ `.trim();
15
+ const doX = axes.includes('x');
16
+ const doY = axes.includes('y');
17
+ const doZ = axes.includes('z');
18
+ const points = Array.from({ length: numPoints }, () => {
19
+ const x = doX ? ((Math.random() - 0.5) * scale).toFixed(6) : '0.000000';
20
+ const y = doY ? ((Math.random() - 0.5) * scale).toFixed(6) : '0.000000';
21
+ const z = doZ ? ((Math.random() - 0.5) * scale).toFixed(6) : '0.000000';
22
+ const red = Math.floor(Math.random() * 256);
23
+ const green = Math.floor(Math.random() * 256);
24
+ const blue = Math.floor(Math.random() * 256);
25
+ const rgbInt = (red << 16) | (green << 8) | blue;
26
+ const rgbFloat = String(new Float32Array(new Uint32Array([rgbInt]).buffer)[0]);
27
+ return `${x} ${y} ${z} ${rgbFloat}`;
28
+ });
29
+ const encoder = new TextEncoder();
30
+ return encoder.encode(`${header}\n${points.join('\n')}`);
31
+ };
package/dist/test.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { createRandomPcdBinary } from './test/createRandomPcdBinary';
package/dist/test.js ADDED
@@ -0,0 +1 @@
1
+ export { createRandomPcdBinary } from './test/createRandomPcdBinary';
@@ -0,0 +1,71 @@
1
+ import { Euler, Quaternion } from 'three';
2
+ export declare const EPSILON = 0.0001;
3
+ /**
4
+ * Golang: https://github.com/viamrobotics/rdk/blob/main/spatialmath/orientationVector.go
5
+ * Rust: https://github.com/viamrobotics/rust-utils/blob/main/src/spatialmath/utils.rs
6
+ */
7
+ /**
8
+ * Viam’s orientation vector is a method for describing the orientation of an object in 3D space.
9
+ * It is part of a Pose which also includes the position in 3D space.
10
+ *
11
+ * The vector extends from the center of the object to another point in the reference frame. This defines the direction something is pointing in.
12
+ *
13
+ * @see https://docs.viam.com/internals/orientation-vector/
14
+ */
15
+ export declare class OrientationVector {
16
+ #private;
17
+ readonly isOrientationVector = true;
18
+ autoNormalize: boolean;
19
+ constructor(x?: number, y?: number, z?: number, th?: number);
20
+ get units(): 'degrees' | 'radians';
21
+ /**
22
+ * The vector's x component.
23
+ * @default 0
24
+ */
25
+ get x(): number;
26
+ set x(value: number);
27
+ /**
28
+ * The vector's y component.
29
+ * @default 0
30
+ */
31
+ get y(): number;
32
+ set y(value: number);
33
+ /**
34
+ * The vector's z component.
35
+ * @default 0
36
+ */
37
+ get z(): number;
38
+ set z(value: number);
39
+ /**
40
+ * Describes the rotation around the vector.
41
+ * @default 0
42
+ */
43
+ get th(): number;
44
+ set th(value: number);
45
+ get w(): number;
46
+ set w(value: number);
47
+ _onChange(callback: () => void): this;
48
+ /**
49
+ * Sets the value of this orientation vector.
50
+ */
51
+ set(x?: number, y?: number, z?: number, th?: number): this;
52
+ setUnits(units: 'degrees' | 'radians'): this;
53
+ /**
54
+ * Computes the length of this orientation vector.
55
+ */
56
+ length(): number;
57
+ /**
58
+ * Normalizes the vector component.
59
+ */
60
+ normalize(): this;
61
+ /**
62
+ * Copies value of ov to this orientation vector.
63
+ */
64
+ copy(ov: OrientationVector): this;
65
+ fromArray(array: number[], offset?: number): this;
66
+ toArray(array?: number[], offset?: number): number[];
67
+ toJson(): number[];
68
+ setFromQuaternion(quaternion: Quaternion): this;
69
+ toQuaternion(dest: Quaternion): Quaternion;
70
+ toEuler(dest: Euler): Euler;
71
+ }
@@ -0,0 +1,233 @@
1
+ import { Euler, Quaternion, Vector3, MathUtils } from 'three';
2
+ export const EPSILON = 0.0001;
3
+ const xAxis = new Quaternion(-1, 0, 0, 0);
4
+ const zAxis = new Quaternion(0, 0, +1, 0);
5
+ const quatA = new Quaternion();
6
+ const quatB = new Quaternion();
7
+ const quatC = new Quaternion();
8
+ const quatD = new Quaternion();
9
+ const quatE = new Quaternion();
10
+ const vecA = new Vector3();
11
+ const vecB = new Vector3();
12
+ const vecC = new Vector3();
13
+ const vecD = new Vector3();
14
+ const vecE = new Vector3();
15
+ const vecF = new Vector3();
16
+ const vecG = new Vector3();
17
+ const vecH = new Vector3();
18
+ /**
19
+ * Golang: https://github.com/viamrobotics/rdk/blob/main/spatialmath/orientationVector.go
20
+ * Rust: https://github.com/viamrobotics/rust-utils/blob/main/src/spatialmath/utils.rs
21
+ */
22
+ /**
23
+ * Viam’s orientation vector is a method for describing the orientation of an object in 3D space.
24
+ * It is part of a Pose which also includes the position in 3D space.
25
+ *
26
+ * The vector extends from the center of the object to another point in the reference frame. This defines the direction something is pointing in.
27
+ *
28
+ * @see https://docs.viam.com/internals/orientation-vector/
29
+ */
30
+ export class OrientationVector {
31
+ isOrientationVector = true;
32
+ #units = 'radians';
33
+ #vec = new Vector3();
34
+ #th = 0;
35
+ #onChangeCallback;
36
+ autoNormalize = true;
37
+ constructor(x = 0, y = 0, z = 1, th = 0) {
38
+ this.#vec.set(x, y, z);
39
+ if (this.autoNormalize) {
40
+ this.#vec.normalize();
41
+ }
42
+ this.#th = th;
43
+ }
44
+ get units() {
45
+ return this.#units;
46
+ }
47
+ /**
48
+ * The vector's x component.
49
+ * @default 0
50
+ */
51
+ get x() {
52
+ return this.#vec.x;
53
+ }
54
+ set x(value) {
55
+ this.#vec.setX(value);
56
+ if (this.autoNormalize) {
57
+ this.#vec.normalize();
58
+ }
59
+ this.#onChangeCallback?.();
60
+ }
61
+ /**
62
+ * The vector's y component.
63
+ * @default 0
64
+ */
65
+ get y() {
66
+ return this.#vec.y;
67
+ }
68
+ set y(value) {
69
+ this.#vec.setY(value);
70
+ if (this.autoNormalize) {
71
+ this.#vec.normalize();
72
+ }
73
+ this.#onChangeCallback?.();
74
+ }
75
+ /**
76
+ * The vector's z component.
77
+ * @default 0
78
+ */
79
+ get z() {
80
+ return this.#vec.z;
81
+ }
82
+ set z(value) {
83
+ this.#vec.setZ(value);
84
+ if (this.autoNormalize) {
85
+ this.#vec.normalize();
86
+ }
87
+ this.#onChangeCallback?.();
88
+ }
89
+ /**
90
+ * Describes the rotation around the vector.
91
+ * @default 0
92
+ */
93
+ get th() {
94
+ if (this.#units === 'degrees') {
95
+ return MathUtils.radToDeg(this.#th);
96
+ }
97
+ return this.#th;
98
+ }
99
+ set th(value) {
100
+ this.#th = this.#units === 'degrees' ? MathUtils.degToRad(value) : value;
101
+ this.#onChangeCallback?.();
102
+ }
103
+ get w() {
104
+ return this.th;
105
+ }
106
+ set w(value) {
107
+ this.th = value;
108
+ }
109
+ _onChange(callback) {
110
+ this.#onChangeCallback = callback;
111
+ return this;
112
+ }
113
+ /**
114
+ * Sets the value of this orientation vector.
115
+ */
116
+ set(x = 0, y = 0, z = 0, th = 0) {
117
+ this.#vec.set(x, y, z);
118
+ if (this.autoNormalize) {
119
+ this.#vec.normalize();
120
+ }
121
+ this.th = th;
122
+ this.#onChangeCallback?.();
123
+ return this;
124
+ }
125
+ setUnits(units) {
126
+ this.#units = units;
127
+ return this;
128
+ }
129
+ /**
130
+ * Computes the length of this orientation vector.
131
+ */
132
+ length() {
133
+ return this.#vec.length();
134
+ }
135
+ /**
136
+ * Normalizes the vector component.
137
+ */
138
+ normalize() {
139
+ this.#vec.normalize();
140
+ return this;
141
+ }
142
+ /**
143
+ * Copies value of ov to this orientation vector.
144
+ */
145
+ copy(ov) {
146
+ this.#vec.set(ov.x, ov.y, ov.z);
147
+ if (this.autoNormalize) {
148
+ this.#vec.normalize();
149
+ }
150
+ this.th = ov.th;
151
+ this.#onChangeCallback?.();
152
+ return this;
153
+ }
154
+ fromArray(array, offset = 0) {
155
+ this.#vec.set(array[offset] ?? 0, array[offset + 1] ?? 0, array[offset + 2] ?? 0);
156
+ this.th = array[offset + 3] ?? 0;
157
+ this.#onChangeCallback?.();
158
+ return this;
159
+ }
160
+ toArray(array = [], offset = 0) {
161
+ array[offset] = this.x;
162
+ array[offset + 1] = this.y;
163
+ array[offset + 2] = this.z;
164
+ array[offset + 3] = this.th;
165
+ return array;
166
+ }
167
+ toJson() {
168
+ return this.toArray();
169
+ }
170
+ setFromQuaternion(quaternion) {
171
+ // Get the transform of our +X and +Z points
172
+ const conj = quatA.copy(quaternion).conjugate();
173
+ const newX = quatB.multiplyQuaternions(quaternion, xAxis).multiply(conj);
174
+ const newZ = quatC.multiplyQuaternions(quaternion, zAxis).multiply(conj);
175
+ let th = 0;
176
+ /*
177
+ * The contents of ov.newX.Kmag are not in radians but we can use angleEpsilon anyway to check how close we are to
178
+ * the pole because it's a convenient small number
179
+ */
180
+ if (1 - Math.abs(newZ.z) > EPSILON) {
181
+ const newZimag = vecA.set(newZ.x, newZ.y, newZ.z);
182
+ const newXimag = vecB.set(newX.x, newX.y, newX.z);
183
+ const zImagAxis = vecC.set(zAxis.x, zAxis.y, zAxis.z);
184
+ // Get the vector normal to the local-x, local-z, origin plane
185
+ const normal1 = vecD.copy(newZimag).cross(newXimag);
186
+ // Get the vector normal to the global-z, local-z, origin plane
187
+ const normal2 = vecE.copy(newZimag).cross(zImagAxis);
188
+ // For theta, find the angle between the planes defined by local-x, global-z, origin and local-x, local-z, origin
189
+ const cosThetaCand = normal1.dot(normal2) / (normal1.length() * normal2.length());
190
+ const cosTheta = MathUtils.clamp(cosThetaCand, -1, 1);
191
+ const theta = Math.acos(cosTheta);
192
+ if (theta > EPSILON) {
193
+ const newZImagUnit = vecF.copy(newXimag).normalize();
194
+ const rotQuatUnit = quatD.setFromAxisAngle(newZImagUnit, -1 * theta);
195
+ const conj2 = quatE.copy(rotQuatUnit).conjugate();
196
+ const testZ = rotQuatUnit.multiplyQuaternions(rotQuatUnit.multiply(zAxis), conj2);
197
+ const normal3 = vecG.copy(newZimag).cross(vecH.set(testZ.x, testZ.y, testZ.z));
198
+ const cosTest = normal1.dot(normal3) / (normal1.length() * normal3.length());
199
+ th = 1 - cosTest < EPSILON ** 2 ? -theta : theta;
200
+ }
201
+ else {
202
+ th = 0;
203
+ }
204
+ /*
205
+ * Special case for when we point directly along the Z axis
206
+ * Get the vector normal to the local-x, global-z, origin plane
207
+ */
208
+ }
209
+ else if (newZ.z < 0) {
210
+ th = -Math.atan2(newX.y, newX.x);
211
+ }
212
+ else {
213
+ th = -Math.atan2(newX.y, -newX.x);
214
+ }
215
+ this.set(newZ.x, newZ.y, newZ.z, th);
216
+ this.#onChangeCallback?.();
217
+ return this;
218
+ }
219
+ toQuaternion(dest) {
220
+ const lat = Math.acos(this.z);
221
+ const lon = 1 - Math.abs(this.z) > EPSILON ? Math.atan2(this.y, this.x) : 0;
222
+ const s0 = Math.sin(lon / 2);
223
+ const c0 = Math.cos(lon / 2);
224
+ const s1 = Math.sin(lat / 2);
225
+ const c1 = Math.cos(lat / 2);
226
+ const s2 = Math.sin(this.th / 2);
227
+ const c2 = Math.cos(this.th / 2);
228
+ return dest.set(c0 * s1 * s2 - s0 * s1 * c2, c0 * s1 * c2 + s0 * s1 * s2, s0 * c1 * c2 + c0 * c1 * s2, c0 * c1 * c2 - s0 * c1 * s2);
229
+ }
230
+ toEuler(dest) {
231
+ return dest.setFromQuaternion(this.toQuaternion(quatA), 'ZYX');
232
+ }
233
+ }
package/dist/transform.js CHANGED
@@ -1,4 +1,4 @@
1
- import { OrientationVector } from '@viamrobotics/three';
1
+ import { OrientationVector } from './three/OrientationVector';
2
2
  import { MathUtils, Quaternion, Vector3 } from 'three';
3
3
  const ov = new OrientationVector();
4
4
  export const createPose = (pose) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -9,9 +9,9 @@
9
9
  "@ag-grid-community/core": "^32.3.5",
10
10
  "@ag-grid-community/styles": "^32.3.5",
11
11
  "@changesets/cli": "^2.29.4",
12
- "@dimforge/rapier3d-compat": "^0.17.1",
12
+ "@dimforge/rapier3d-compat": "^0.17.3",
13
13
  "@eslint/compat": "^1.2.9",
14
- "@eslint/js": "^9.27.0",
14
+ "@eslint/js": "^9.28.0",
15
15
  "@playwright/test": "^1.52.0",
16
16
  "@skeletonlabs/skeleton": "3.1.3",
17
17
  "@skeletonlabs/skeleton-svelte": "1.2.3",
@@ -21,30 +21,29 @@
21
21
  "@sveltejs/vite-plugin-svelte": "^5.0.3",
22
22
  "@tailwindcss/forms": "^0.5.10",
23
23
  "@tailwindcss/vite": "^4.1.8",
24
- "@tanstack/svelte-query": "^5.79.0",
25
- "@tanstack/svelte-query-devtools": "^5.79.0",
24
+ "@tanstack/svelte-query": "^5.79.2",
25
+ "@tanstack/svelte-query-devtools": "^5.79.2",
26
26
  "@testing-library/jest-dom": "^6.6.3",
27
27
  "@testing-library/svelte": "^5.2.8",
28
28
  "@threlte/core": "^8.0.4",
29
29
  "@threlte/extras": "^9.2.1",
30
30
  "@threlte/rapier": "^3.1.4",
31
- "@threlte/xr": "^1.0.5",
31
+ "@threlte/xr": "^1.0.6",
32
32
  "@types/bun": "^1.2.15",
33
33
  "@types/lodash-es": "^4.17.12",
34
- "@types/three": "^0.176.0",
35
- "@typescript-eslint/eslint-plugin": "^8.33.0",
36
- "@typescript-eslint/parser": "^8.33.0",
34
+ "@types/three": "^0.177.0",
35
+ "@typescript-eslint/eslint-plugin": "^8.33.1",
36
+ "@typescript-eslint/parser": "^8.33.1",
37
37
  "@viamrobotics/prime-core": "^0.1.5",
38
38
  "@viamrobotics/sdk": "0.42.0",
39
- "@viamrobotics/svelte-sdk": "0.1.4",
40
- "@viamrobotics/three": "^0.0.9",
39
+ "@viamrobotics/svelte-sdk": "0.2.0",
41
40
  "@vitejs/plugin-basic-ssl": "^2.0.0",
42
- "@zag-js/svelte": "1.13.1",
43
- "@zag-js/tree-view": "1.13.1",
41
+ "@zag-js/svelte": "1.14.0",
42
+ "@zag-js/tree-view": "1.14.0",
44
43
  "camera-controls": "^2.10.1",
45
- "eslint": "^9.27.0",
44
+ "eslint": "^9.28.0",
46
45
  "eslint-config-prettier": "^10.1.5",
47
- "eslint-plugin-svelte": "^3.9.0",
46
+ "eslint-plugin-svelte": "^3.9.1",
48
47
  "globals": "^16.2.0",
49
48
  "idb-keyval": "^6.2.2",
50
49
  "jsdom": "^26.1.0",
@@ -52,21 +51,21 @@
52
51
  "lucide-svelte": "^0.511.0",
53
52
  "prettier": "^3.5.3",
54
53
  "prettier-plugin-svelte": "^3.4.0",
55
- "prettier-plugin-tailwindcss": "^0.6.11",
54
+ "prettier-plugin-tailwindcss": "^0.6.12",
56
55
  "publint": "^0.3.12",
57
56
  "runed": "^0.28.0",
58
- "svelte": "5.33.8",
57
+ "svelte": "5.33.14",
59
58
  "svelte-check": "^4.2.1",
60
59
  "svelte-virtuallists": "^1.4.2",
61
60
  "tailwindcss": "^4.1.8",
62
- "three": "^0.176.0",
63
- "threlte-uikit": "^1.0.0",
61
+ "three": "^0.177.0",
62
+ "threlte-uikit": "^1.1.0",
64
63
  "tsx": "^4.19.4",
65
64
  "typescript": "^5.8.3",
66
- "typescript-eslint": "^8.33.0",
65
+ "typescript-eslint": "^8.33.1",
67
66
  "vite": "^6.3.5",
68
67
  "vite-plugin-mkcert": "^1.17.8",
69
- "vitest": "^3.1.4"
68
+ "vitest": "^3.2.0"
70
69
  },
71
70
  "peerDependencies": {
72
71
  "@dimforge/rapier3d-compat": ">=0.17",
@@ -97,6 +96,10 @@
97
96
  "./lib": {
98
97
  "types": "./dist/lib.d.ts",
99
98
  "svelte": "./dist/lib.js"
99
+ },
100
+ "./test": {
101
+ "types": "./dist/test.d.ts",
102
+ "svelte": "./dist/test.js"
100
103
  }
101
104
  },
102
105
  "repository": {
@@ -1,5 +0,0 @@
1
- import { Mesh } from 'three';
2
- export declare class AxesHelper extends Mesh {
3
- constructor(size?: number, thickness?: number);
4
- dispose(): void;
5
- }
@@ -1,35 +0,0 @@
1
- import { Mesh, Vector3, Color, BoxGeometry, BufferAttribute, MeshBasicMaterial } from 'three';
2
- import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
3
- export class AxesHelper extends Mesh {
4
- constructor(size = 0.1, thickness = 0.005) {
5
- const axisGeometries = [];
6
- const axes = [
7
- { dir: new Vector3(1, 0, 0), color: new Color(0xff0000) }, // X - Red
8
- { dir: new Vector3(0, 1, 0), color: new Color(0x00ff00) }, // Y - Green
9
- { dir: new Vector3(0, 0, 1), color: new Color(0x0000ff) }, // Z - Blue
10
- ];
11
- const dimensions = new Vector3();
12
- for (const axis of axes) {
13
- dimensions.set(axis.dir.x ? size : thickness, axis.dir.y ? size : thickness, axis.dir.z ? size : thickness);
14
- const geometry = new BoxGeometry(dimensions.x, dimensions.y, dimensions.z);
15
- // Translate so it starts at the origin (like traditional AxesHelper)
16
- geometry.translate(dimensions.x / 2, dimensions.y / 2, dimensions.z / 2);
17
- // Add vertex colors
18
- const { count } = geometry.attributes.position;
19
- const colorArray = new Float32Array(count * 3);
20
- for (let i = 0; i < count; i++) {
21
- colorArray[i * 3 + 0] = axis.color.r;
22
- colorArray[i * 3 + 1] = axis.color.g;
23
- colorArray[i * 3 + 2] = axis.color.b;
24
- }
25
- geometry.setAttribute('color', new BufferAttribute(colorArray, 3));
26
- axisGeometries.push(geometry);
27
- }
28
- const mergedGeometry = mergeGeometries(axisGeometries);
29
- const material = new MeshBasicMaterial({ vertexColors: true });
30
- super(mergedGeometry, material);
31
- }
32
- dispose() {
33
- this.geometry.dispose();
34
- }
35
- }