@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.
- package/README.md +10 -10
- package/dist/components/App.svelte +2 -0
- package/dist/components/AxesHelper.svelte +69 -9
- package/dist/components/AxesHelper.svelte.d.ts +9 -2
- package/dist/components/Camera.svelte +5 -12
- package/dist/components/Details.svelte +11 -2
- package/dist/components/Focus.svelte +0 -2
- package/dist/components/Geometry.svelte +1 -1
- package/dist/components/Scene.svelte +1 -1
- package/dist/components/SceneProviders.svelte +2 -0
- package/dist/components/Tree/Tree.svelte +11 -2
- package/dist/components/Tree/Tree.svelte.d.ts +2 -0
- package/dist/components/Tree/TreeContainer.svelte +21 -40
- package/dist/components/dashboard/Button.svelte +47 -0
- package/dist/components/dashboard/Button.svelte.d.ts +12 -0
- package/dist/components/dashboard/Dashboard.svelte +77 -0
- package/dist/components/dashboard/Dashboard.svelte.d.ts +26 -0
- package/dist/hooks/useDraggable.svelte.d.ts +10 -2
- package/dist/hooks/useDraggable.svelte.js +24 -13
- package/dist/hooks/usePointclouds.svelte.js +2 -2
- package/dist/hooks/useSettings.svelte.d.ts +9 -0
- package/dist/hooks/useSettings.svelte.js +25 -0
- package/dist/hooks/useShapes.svelte.js +2 -2
- package/dist/lib.d.ts +3 -1
- package/dist/lib.js +6 -1
- package/dist/loaders/pcd/index.d.ts +1 -1
- package/dist/loaders/pcd/index.js +1 -2
- package/dist/test/createRandomPcdBinary.d.ts +1 -0
- package/dist/test/createRandomPcdBinary.js +31 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +1 -0
- package/dist/three/OrientationVector.d.ts +71 -0
- package/dist/three/OrientationVector.js +233 -0
- package/dist/transform.js +1 -1
- package/package.json +24 -21
- package/dist/three/AxesHelper.d.ts +0 -5
- 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
|
|
17
|
-
- default pointcloud color
|
|
18
|
-
- remote IP access
|
|
19
|
-
-
|
|
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
|
|
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 {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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={
|
|
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 {
|
|
2
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
{
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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 {
|
|
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
|
|
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 = (
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
const onDragEnd = () => {
|
|
18
|
+
set(`${name}-draggable`, $state.snapshot(translate));
|
|
19
|
+
window.removeEventListener('pointermove', onDragMove);
|
|
15
20
|
};
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
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 {
|
|
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
|
|
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,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 {
|
|
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
|
|
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 './
|
|
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 './
|
|
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,6 +1,5 @@
|
|
|
1
|
-
// main.js
|
|
2
1
|
const worker = new Worker(new URL('./worker', import.meta.url), { type: 'module' });
|
|
3
|
-
export const
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viamrobotics/motion-tools",
|
|
3
|
-
"version": "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.
|
|
12
|
+
"@dimforge/rapier3d-compat": "^0.17.3",
|
|
13
13
|
"@eslint/compat": "^1.2.9",
|
|
14
|
-
"@eslint/js": "^9.
|
|
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.
|
|
25
|
-
"@tanstack/svelte-query-devtools": "^5.79.
|
|
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.
|
|
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.
|
|
35
|
-
"@typescript-eslint/eslint-plugin": "^8.33.
|
|
36
|
-
"@typescript-eslint/parser": "^8.33.
|
|
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.
|
|
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.
|
|
43
|
-
"@zag-js/tree-view": "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.
|
|
44
|
+
"eslint": "^9.28.0",
|
|
46
45
|
"eslint-config-prettier": "^10.1.5",
|
|
47
|
-
"eslint-plugin-svelte": "^3.9.
|
|
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.
|
|
54
|
+
"prettier-plugin-tailwindcss": "^0.6.12",
|
|
56
55
|
"publint": "^0.3.12",
|
|
57
56
|
"runed": "^0.28.0",
|
|
58
|
-
"svelte": "5.33.
|
|
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.
|
|
63
|
-
"threlte-uikit": "^1.
|
|
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.
|
|
65
|
+
"typescript-eslint": "^8.33.1",
|
|
67
66
|
"vite": "^6.3.5",
|
|
68
67
|
"vite-plugin-mkcert": "^1.17.8",
|
|
69
|
-
"vitest": "^3.
|
|
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": {
|
package/dist/three/AxesHelper.js
DELETED
|
@@ -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
|
-
}
|