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