@viamrobotics/motion-tools 0.9.4 → 0.9.5
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/dist/WorldObject.d.ts +20 -1
- package/dist/WorldObject.js +5 -0
- package/dist/components/AxesHelper.svelte +1 -0
- package/dist/components/DotSprite.svelte +48 -33
- package/dist/components/DotSprite.svelte.d.ts +3 -2
- package/dist/components/Focus.svelte +36 -3
- package/dist/components/Geometry.svelte +5 -1
- package/dist/components/MeasureTool.svelte +38 -55
- package/dist/components/Pointcloud.svelte +1 -3
- package/dist/components/PointerMissBox.svelte +7 -1
- package/dist/components/PointerMissBox.svelte.d.ts +2 -17
- package/dist/components/Scene.svelte +24 -15
- package/dist/components/SceneProviders.svelte +1 -0
- package/dist/components/Selected.svelte +1 -0
- package/dist/components/Tree/TreeContainer.svelte +3 -1
- package/dist/components/Tree/buildTree.d.ts +4 -1
- package/dist/components/Tree/buildTree.js +23 -1
- package/dist/components/WorldObjects.svelte +15 -5
- package/dist/components/WorldState.svelte +28 -0
- package/dist/components/WorldState.svelte.d.ts +7 -0
- package/dist/hooks/useMouseRaycaster.svelte.d.ts +17 -0
- package/dist/hooks/useMouseRaycaster.svelte.js +108 -0
- package/dist/hooks/useObjectEvents.svelte.js +1 -12
- package/dist/hooks/useSelection.svelte.js +12 -2
- package/dist/hooks/useWorldState.svelte.d.ts +19 -0
- package/dist/hooks/useWorldState.svelte.js +97 -0
- package/package.json +42 -40
- package/dist/components/Labels.svelte +0 -0
- package/dist/components/Labels.svelte.d.ts +0 -26
package/dist/WorldObject.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Geometry, Pose } from '@viamrobotics/sdk';
|
|
1
|
+
import type { Geometry, Pose, TransformWithUUID } from '@viamrobotics/sdk';
|
|
2
2
|
import { BatchedMesh, Box3, Object3D, Vector3, type ColorRepresentation } from 'three';
|
|
3
3
|
export type PointsGeometry = {
|
|
4
4
|
case: 'points';
|
|
@@ -34,3 +34,22 @@ export declare class WorldObject<T extends Geometries = Geometries> {
|
|
|
34
34
|
metadata: Metadata;
|
|
35
35
|
constructor(name: string, pose?: Pose, parent?: string, geometry?: T, metadata?: Metadata);
|
|
36
36
|
}
|
|
37
|
+
export declare const fromTransform: (transform: TransformWithUUID) => WorldObject<{
|
|
38
|
+
case: undefined;
|
|
39
|
+
value?: undefined;
|
|
40
|
+
} | {
|
|
41
|
+
case: "sphere";
|
|
42
|
+
value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Sphere>;
|
|
43
|
+
} | {
|
|
44
|
+
case: "box";
|
|
45
|
+
value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").RectangularPrism>;
|
|
46
|
+
} | {
|
|
47
|
+
case: "capsule";
|
|
48
|
+
value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Capsule>;
|
|
49
|
+
} | {
|
|
50
|
+
case: "mesh";
|
|
51
|
+
value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Mesh>;
|
|
52
|
+
} | {
|
|
53
|
+
case: "pointcloud";
|
|
54
|
+
value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").PointCloud>;
|
|
55
|
+
}>;
|
package/dist/WorldObject.js
CHANGED
|
@@ -16,3 +16,8 @@ export class WorldObject {
|
|
|
16
16
|
this.metadata = metadata ?? {};
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
export const fromTransform = (transform) => {
|
|
20
|
+
const metadata = { ...transform.metadata?.fields };
|
|
21
|
+
const worldObject = new WorldObject(transform.referenceFrame, transform.poseInObserverFrame?.pose, transform.poseInObserverFrame?.referenceFrame, transform.physicalObject?.geometryType, metadata);
|
|
22
|
+
return worldObject;
|
|
23
|
+
};
|
|
@@ -1,44 +1,59 @@
|
|
|
1
|
-
<script
|
|
2
|
-
module
|
|
3
|
-
lang="ts"
|
|
4
|
-
>
|
|
1
|
+
<script lang="ts">
|
|
5
2
|
import { T, type Props as ThrelteProps } from '@threlte/core'
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
const size = 128
|
|
9
|
-
const canvas = new OffscreenCanvas(size, size)
|
|
10
|
-
const ctx = canvas.getContext('2d')
|
|
3
|
+
import type { ColorRepresentation, Vector3Tuple, Group } from 'three'
|
|
4
|
+
import { HTML } from '@threlte/extras'
|
|
11
5
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
ctx.beginPath()
|
|
15
|
-
ctx.arc(size / 2, size / 2, size / 2, 0, Math.PI * 2)
|
|
16
|
-
ctx.fillStyle = 'white'
|
|
17
|
-
ctx.fill()
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const map = new CanvasTexture(canvas)
|
|
21
|
-
</script>
|
|
22
|
-
|
|
23
|
-
<script lang="ts">
|
|
24
|
-
interface Props extends ThrelteProps<typeof Sprite> {
|
|
6
|
+
interface Props extends ThrelteProps<typeof Group> {
|
|
7
|
+
position: Vector3Tuple
|
|
25
8
|
color?: ColorRepresentation
|
|
26
9
|
opacity?: number
|
|
27
10
|
}
|
|
28
11
|
|
|
29
|
-
let { color, opacity = 1, ref = $bindable(), ...rest }: Props = $props()
|
|
12
|
+
let { position, color, opacity = 1, ref = $bindable(), ...rest }: Props = $props()
|
|
30
13
|
</script>
|
|
31
14
|
|
|
32
|
-
<T.
|
|
15
|
+
<T.Group
|
|
33
16
|
bind:ref
|
|
34
|
-
scale={0.05}
|
|
35
17
|
{...rest}
|
|
18
|
+
{position}
|
|
36
19
|
>
|
|
37
|
-
<T.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{
|
|
41
|
-
{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
20
|
+
<T.Mesh
|
|
21
|
+
bvh={{ enabled: false }}
|
|
22
|
+
raycast={() => null}
|
|
23
|
+
scale={0.01}
|
|
24
|
+
renderOrder={1}
|
|
25
|
+
>
|
|
26
|
+
<T.SphereGeometry />
|
|
27
|
+
<T.MeshBasicMaterial
|
|
28
|
+
color={color ?? 'black'}
|
|
29
|
+
transparent
|
|
30
|
+
depthTest={false}
|
|
31
|
+
{opacity}
|
|
32
|
+
/>
|
|
33
|
+
</T.Mesh>
|
|
34
|
+
|
|
35
|
+
<HTML
|
|
36
|
+
class="pointer-events-none mb-2 w-16 -translate-x-1/2 -translate-y-[calc(100%+10px)] border border-black bg-white px-1 py-0.5 text-xs text-wrap"
|
|
37
|
+
>
|
|
38
|
+
<div class="flex justify-between">
|
|
39
|
+
<span class="text-subtle-2">x</span>
|
|
40
|
+
<div>
|
|
41
|
+
{position[0].toFixed(2)}<span class="text-subtle-2">m</span>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="flex justify-between">
|
|
46
|
+
<span class="text-subtle-2">y</span>
|
|
47
|
+
<div>
|
|
48
|
+
{position[1].toFixed(2)}<span class="text-subtle-2">m</span>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div class="flex justify-between">
|
|
53
|
+
<span class="text-subtle-2">z</span>
|
|
54
|
+
<div>
|
|
55
|
+
{position[2].toFixed(2)}<span class="text-subtle-2">m</span>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</HTML>
|
|
59
|
+
</T.Group>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Props as ThrelteProps } from '@threlte/core';
|
|
2
|
-
import {
|
|
3
|
-
interface Props extends ThrelteProps<typeof
|
|
2
|
+
import type { ColorRepresentation, Vector3Tuple, Group } from 'three';
|
|
3
|
+
interface Props extends ThrelteProps<typeof Group> {
|
|
4
|
+
position: Vector3Tuple;
|
|
4
5
|
color?: ColorRepresentation;
|
|
5
6
|
opacity?: number;
|
|
6
7
|
}
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
import { T } from '@threlte/core'
|
|
3
3
|
import { TrackballControls, Gizmo } from '@threlte/extras'
|
|
4
4
|
import { Box3, type Object3D, Vector3 } from 'three'
|
|
5
|
+
import { TrackballControls as ThreeTrackballControls } from 'three/examples/jsm/controls/TrackballControls.js'
|
|
5
6
|
import Camera from './Camera.svelte'
|
|
7
|
+
import Portal from './portal/Portal.svelte'
|
|
8
|
+
import Button from './dashboard/Button.svelte'
|
|
6
9
|
|
|
7
10
|
interface Props {
|
|
8
11
|
object3d: Object3D
|
|
@@ -16,6 +19,8 @@
|
|
|
16
19
|
let center = $state.raw<[number, number, number]>([0, 0, 0])
|
|
17
20
|
let size = $state.raw<[number, number, number]>([0, 0, 0])
|
|
18
21
|
|
|
22
|
+
let controls = $state.raw<ThreeTrackballControls>()
|
|
23
|
+
|
|
19
24
|
$effect.pre(() => {
|
|
20
25
|
box.setFromObject(object3d)
|
|
21
26
|
size = box.getSize(vec).toArray()
|
|
@@ -23,11 +28,39 @@
|
|
|
23
28
|
})
|
|
24
29
|
</script>
|
|
25
30
|
|
|
31
|
+
<Portal id="dashboard">
|
|
32
|
+
<fieldset>
|
|
33
|
+
<Button
|
|
34
|
+
active
|
|
35
|
+
icon="camera-outline"
|
|
36
|
+
description="Reset camera"
|
|
37
|
+
onclick={() => {
|
|
38
|
+
controls?.reset()
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
41
|
+
</fieldset>
|
|
42
|
+
</Portal>
|
|
43
|
+
|
|
26
44
|
<Camera position={[size[0] + 1, size[0] + 1, size[0] + 1]}>
|
|
27
|
-
<TrackballControls
|
|
45
|
+
<TrackballControls
|
|
46
|
+
bind:ref={controls}
|
|
47
|
+
target={center}
|
|
48
|
+
>
|
|
28
49
|
<Gizmo />
|
|
29
50
|
</TrackballControls>
|
|
30
51
|
</Camera>
|
|
31
52
|
|
|
32
|
-
<T
|
|
33
|
-
|
|
53
|
+
<T
|
|
54
|
+
is={object3d}
|
|
55
|
+
bvh={{
|
|
56
|
+
enabled: object3d.type === 'Points',
|
|
57
|
+
maxDepth: 40,
|
|
58
|
+
maxLeafTris: 20,
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
61
|
+
|
|
62
|
+
<T.BoxHelper
|
|
63
|
+
args={[object3d, 'red']}
|
|
64
|
+
bvh={{ enabled: false }}
|
|
65
|
+
raycast={() => null}
|
|
66
|
+
/>
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
{name}
|
|
63
63
|
{uuid}
|
|
64
64
|
{...rest}
|
|
65
|
+
bvh={{ enabled: false }}
|
|
65
66
|
>
|
|
66
67
|
{#if geometry?.case === 'mesh'}
|
|
67
68
|
{@const mesh = geometry.value.mesh as Uint8Array<ArrayBuffer>}
|
|
@@ -112,7 +113,10 @@
|
|
|
112
113
|
/>
|
|
113
114
|
|
|
114
115
|
{#if geo}
|
|
115
|
-
<T.LineSegments
|
|
116
|
+
<T.LineSegments
|
|
117
|
+
raycast={() => null}
|
|
118
|
+
bvh={{ enabled: false }}
|
|
119
|
+
>
|
|
116
120
|
<T.EdgesGeometry args={[geo, 0]} />
|
|
117
121
|
<T.LineBasicMaterial color={darkenColor(color, 10)} />
|
|
118
122
|
</T.LineSegments>
|
|
@@ -1,41 +1,39 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { untrack } from 'svelte'
|
|
3
|
-
import {
|
|
4
|
-
import { T
|
|
5
|
-
import { HTML, MeshLineGeometry, MeshLineMaterial
|
|
3
|
+
import { Vector3, type Intersection } from 'three'
|
|
4
|
+
import { T } from '@threlte/core'
|
|
5
|
+
import { HTML, MeshLineGeometry, MeshLineMaterial } from '@threlte/extras'
|
|
6
6
|
import { useSettings } from '../hooks/useSettings.svelte'
|
|
7
7
|
import Button from './dashboard/Button.svelte'
|
|
8
8
|
import Portal from './portal/Portal.svelte'
|
|
9
9
|
import DotSprite from './DotSprite.svelte'
|
|
10
|
+
import { useMouseRaycaster } from '../hooks/useMouseRaycaster.svelte'
|
|
11
|
+
import { useFocused } from '../hooks/useSelection.svelte'
|
|
10
12
|
|
|
13
|
+
const focus = useFocused()
|
|
11
14
|
const settings = useSettings()
|
|
12
|
-
const { camera } = useThrelte()
|
|
13
|
-
const interactivity = useInteractivity()
|
|
14
|
-
const raycaster = new Raycaster()
|
|
15
15
|
|
|
16
16
|
const htmlPosition = new Vector3()
|
|
17
|
-
const pointerDown = new Vector2()
|
|
18
|
-
const pointerUp = new Vector2()
|
|
19
17
|
|
|
20
18
|
let step: 'idle' | 'p1' | 'p2' = 'idle'
|
|
21
19
|
|
|
22
|
-
let intersection
|
|
20
|
+
let intersection = $state.raw<Intersection>()
|
|
23
21
|
let p1 = $state.raw<Vector3>()
|
|
24
22
|
let p2 = $state.raw<Vector3>()
|
|
25
23
|
|
|
26
24
|
const enabled = $derived(settings.current.enableMeasure)
|
|
27
25
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
pointerUp.set(event.clientX, event.clientY)
|
|
26
|
+
const { onclick, onmove, raycaster } = useMouseRaycaster(() => ({
|
|
27
|
+
enabled,
|
|
28
|
+
}))
|
|
29
|
+
raycaster.firstHitOnly = true
|
|
30
|
+
raycaster.params.Points.threshold = 0.005
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
onmove((event) => {
|
|
33
|
+
intersection = event.intersections[0]
|
|
34
|
+
})
|
|
38
35
|
|
|
36
|
+
onclick(() => {
|
|
39
37
|
if (step === 'idle' && intersection) {
|
|
40
38
|
p1 = intersection.point.clone()
|
|
41
39
|
step = 'p1'
|
|
@@ -47,38 +45,18 @@
|
|
|
47
45
|
p2 = undefined
|
|
48
46
|
step = 'idle'
|
|
49
47
|
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const { start, stop } = useTask(
|
|
53
|
-
() => {
|
|
54
|
-
if (interactivity.hovered.size === 0) {
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
for (const [, event] of interactivity.hovered) {
|
|
59
|
-
raycaster.setFromCamera(interactivity.pointer.current, camera.current)
|
|
60
|
-
intersection = raycaster.intersectObject(event.object)[0]
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
{ autoStart: false }
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
$effect(() => {
|
|
67
|
-
if (!enabled) {
|
|
68
|
-
untrack(() => {
|
|
69
|
-
p1 = undefined
|
|
70
|
-
p2 = undefined
|
|
71
|
-
step = 'idle'
|
|
72
|
-
})
|
|
73
|
-
}
|
|
74
48
|
})
|
|
75
49
|
|
|
50
|
+
const clear = () => {
|
|
51
|
+
p1 = undefined
|
|
52
|
+
p2 = undefined
|
|
53
|
+
step = 'idle'
|
|
54
|
+
}
|
|
55
|
+
|
|
76
56
|
$effect(() => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
stop()
|
|
81
|
-
}
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
58
|
+
;(focus.current, enabled)
|
|
59
|
+
untrack(() => clear())
|
|
82
60
|
})
|
|
83
61
|
</script>
|
|
84
62
|
|
|
@@ -96,12 +74,11 @@
|
|
|
96
74
|
</fieldset>
|
|
97
75
|
</Portal>
|
|
98
76
|
|
|
99
|
-
<svelte:window
|
|
100
|
-
onpointerdown={enabled ? onpointerdown : undefined}
|
|
101
|
-
onpointerup={enabled ? onpointerup : undefined}
|
|
102
|
-
/>
|
|
103
|
-
|
|
104
77
|
{#if enabled}
|
|
78
|
+
{#if intersection}
|
|
79
|
+
<DotSprite position={intersection?.point.toArray()} />
|
|
80
|
+
{/if}
|
|
81
|
+
|
|
105
82
|
{#if p1}
|
|
106
83
|
<DotSprite position={p1.toArray()} />
|
|
107
84
|
{/if}
|
|
@@ -111,12 +88,18 @@
|
|
|
111
88
|
{/if}
|
|
112
89
|
|
|
113
90
|
{#if p1 && p2}
|
|
114
|
-
<T.Mesh
|
|
91
|
+
<T.Mesh
|
|
92
|
+
raycast={() => null}
|
|
93
|
+
bvh={{ enabled: false }}
|
|
94
|
+
renderOrder={1}
|
|
95
|
+
>
|
|
115
96
|
<MeshLineGeometry points={[p1, p2]} />
|
|
116
97
|
<MeshLineMaterial
|
|
117
|
-
width={
|
|
98
|
+
width={2.5}
|
|
118
99
|
depthTest={false}
|
|
119
100
|
color="black"
|
|
101
|
+
attenuate={false}
|
|
102
|
+
transparent
|
|
120
103
|
/>
|
|
121
104
|
</T.Mesh>
|
|
122
105
|
<HTML
|
|
@@ -124,7 +107,7 @@
|
|
|
124
107
|
position={htmlPosition.lerpVectors(p1, p2, 0.5).toArray()}
|
|
125
108
|
>
|
|
126
109
|
<div class="border border-black bg-white px-1 py-0.5 text-xs">
|
|
127
|
-
{p1.distanceTo(p2).toFixed(2)}m
|
|
110
|
+
{p1.distanceTo(p2).toFixed(2)}<span class="text-subtle-2">m</span>
|
|
128
111
|
</div>
|
|
129
112
|
</HTML>
|
|
130
113
|
{/if}
|
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
PointsMaterial,
|
|
7
7
|
OrthographicCamera,
|
|
8
8
|
} from 'three'
|
|
9
|
-
|
|
10
9
|
import { T, useTask, useThrelte } from '@threlte/core'
|
|
11
10
|
import type { WorldObject } from '../WorldObject'
|
|
12
11
|
import { useObjectEvents } from '../hooks/useObjectEvents.svelte'
|
|
13
|
-
import { meshBounds } from '@threlte/extras'
|
|
14
12
|
import { poseToObject3d } from '../transform'
|
|
15
13
|
import { useSettings } from '../hooks/useSettings.svelte'
|
|
16
14
|
import type { Snippet } from 'svelte'
|
|
@@ -85,8 +83,8 @@
|
|
|
85
83
|
is={points}
|
|
86
84
|
name={object.name}
|
|
87
85
|
uuid={object.uuid}
|
|
88
|
-
raycast={meshBounds}
|
|
89
86
|
{...events}
|
|
87
|
+
bvh={{ maxDepth: 40, maxLeafTris: 20 }}
|
|
90
88
|
>
|
|
91
89
|
<T is={geometry} />
|
|
92
90
|
<T is={material} />
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { BackSide, Vector3 } from 'three'
|
|
2
|
+
import { BackSide, Mesh, Vector3 } from 'three'
|
|
3
3
|
import { T, useThrelte } from '@threlte/core'
|
|
4
4
|
import { MeshDiscardMaterial } from '@threlte/extras'
|
|
5
5
|
import { useSelected } from '../hooks/useSelection.svelte'
|
|
6
6
|
import { useTransformControls } from '../hooks/useControls.svelte'
|
|
7
|
+
import { useSettings } from '../hooks/useSettings.svelte'
|
|
7
8
|
|
|
8
9
|
const { camera } = useThrelte()
|
|
10
|
+
const settings = useSettings()
|
|
9
11
|
const selected = useSelected()
|
|
10
12
|
const transformControls = useTransformControls()
|
|
11
13
|
const cameraDown = new Vector3()
|
|
12
14
|
|
|
15
|
+
const enabled = $derived(!settings.current.enableMeasure)
|
|
16
|
+
|
|
13
17
|
const size = 1_000
|
|
14
18
|
</script>
|
|
15
19
|
|
|
16
20
|
<T.Mesh
|
|
21
|
+
raycast={enabled ? Mesh.prototype.raycast : () => null}
|
|
22
|
+
bvh={{ enabled: false }}
|
|
17
23
|
onpointerdown={() => {
|
|
18
24
|
cameraDown.copy(camera.current.position)
|
|
19
25
|
}}
|
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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 PointerMissBox: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
-
[evt: string]: CustomEvent<any>;
|
|
16
|
-
}, {}, {}, string>;
|
|
17
|
-
type PointerMissBox = InstanceType<typeof PointerMissBox>;
|
|
1
|
+
declare const PointerMissBox: import("svelte").Component<Record<string, never>, {}, "">;
|
|
2
|
+
type PointerMissBox = ReturnType<typeof PointerMissBox>;
|
|
18
3
|
export default PointerMissBox;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Vector3 } from 'three'
|
|
3
3
|
import { T } from '@threlte/core'
|
|
4
|
-
import { Grid, interactivity, PerfMonitor } from '@threlte/extras'
|
|
4
|
+
import { Grid, interactivity, PerfMonitor, bvh } from '@threlte/extras'
|
|
5
5
|
import { PortalTarget } from './portal'
|
|
6
6
|
import WorldObjects from './WorldObjects.svelte'
|
|
7
7
|
import Selected from './Selected.svelte'
|
|
@@ -24,7 +24,11 @@
|
|
|
24
24
|
|
|
25
25
|
let { children }: Props = $props()
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
const settings = useSettings()
|
|
28
|
+
const focusedObject3d = useFocusedObject3d()
|
|
29
|
+
const origin = useOrigin()
|
|
30
|
+
|
|
31
|
+
const { raycaster, enabled } = interactivity({
|
|
28
32
|
filter: (items) => {
|
|
29
33
|
const item = items.find((item) => {
|
|
30
34
|
return item.object.visible === undefined || item.object.visible === true
|
|
@@ -33,12 +37,14 @@
|
|
|
33
37
|
return item ? [item] : []
|
|
34
38
|
},
|
|
35
39
|
})
|
|
40
|
+
$effect.pre(() => {
|
|
41
|
+
enabled.set(!settings.current.enableMeasure)
|
|
42
|
+
})
|
|
43
|
+
raycaster.firstHitOnly = true
|
|
36
44
|
|
|
37
|
-
|
|
38
|
-
const focusedObject3d = useFocusedObject3d()
|
|
39
|
-
const origin = useOrigin()
|
|
45
|
+
bvh(() => ({ helper: false }))
|
|
40
46
|
|
|
41
|
-
const
|
|
47
|
+
const focusedObject = $derived(focusedObject3d.current)
|
|
42
48
|
|
|
43
49
|
const { isPresenting } = useXR()
|
|
44
50
|
</script>
|
|
@@ -52,8 +58,11 @@
|
|
|
52
58
|
rotation.x={$isPresenting ? -Math.PI / 2 : 0}
|
|
53
59
|
rotation.z={origin.rotation}
|
|
54
60
|
>
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
<PointerMissBox />
|
|
62
|
+
<MeasureTool />
|
|
63
|
+
|
|
64
|
+
{#if focusedObject}
|
|
65
|
+
<Focus object3d={focusedObject} />
|
|
57
66
|
{:else}
|
|
58
67
|
{#if !$isPresenting}
|
|
59
68
|
<Camera position={[3, 3, 3]}>
|
|
@@ -61,18 +70,13 @@
|
|
|
61
70
|
</Camera>
|
|
62
71
|
{/if}
|
|
63
72
|
|
|
64
|
-
<PortalTarget id="world" />
|
|
65
|
-
|
|
66
|
-
<MeasureTool />
|
|
67
73
|
<StaticGeometries />
|
|
68
|
-
|
|
69
|
-
<WorldObjects />
|
|
70
|
-
<PointerMissBox />
|
|
71
|
-
|
|
72
74
|
<Selected />
|
|
73
75
|
|
|
74
76
|
{#if !$isPresenting && settings.current.grid}
|
|
75
77
|
<Grid
|
|
78
|
+
raycast={() => null}
|
|
79
|
+
bvh={{ enabled: false }}
|
|
76
80
|
plane="xy"
|
|
77
81
|
sectionColor="#333"
|
|
78
82
|
infiniteGrid
|
|
@@ -84,6 +88,11 @@
|
|
|
84
88
|
{/if}
|
|
85
89
|
{/if}
|
|
86
90
|
|
|
91
|
+
<T.Group attach={focusedObject ? false : undefined}>
|
|
92
|
+
<PortalTarget id="world" />
|
|
93
|
+
<WorldObjects />
|
|
94
|
+
</T.Group>
|
|
95
|
+
|
|
87
96
|
{@render children?.()}
|
|
88
97
|
|
|
89
98
|
<T.DirectionalLight position={[3, 3, 3]} />
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import Settings from './Settings.svelte'
|
|
10
10
|
import Logs from './Logs.svelte'
|
|
11
11
|
import { useDraggable } from '../../hooks/useDraggable.svelte'
|
|
12
|
+
import { useWorldStates } from '../../hooks/useWorldState.svelte'
|
|
12
13
|
|
|
13
14
|
const { ...rest } = $props()
|
|
14
15
|
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
const selected = useSelected()
|
|
18
19
|
const objects = useObjects()
|
|
19
20
|
const draggable = useDraggable('treeview')
|
|
21
|
+
const worldStates = useWorldStates()
|
|
20
22
|
|
|
21
23
|
let rootNode = $state<TreeNode>({
|
|
22
24
|
id: 'world',
|
|
@@ -25,7 +27,7 @@
|
|
|
25
27
|
href: '/',
|
|
26
28
|
})
|
|
27
29
|
|
|
28
|
-
const nodes = $derived(buildTreeNodes(objects.current))
|
|
30
|
+
const nodes = $derived(buildTreeNodes(objects.current, worldStates.current))
|
|
29
31
|
|
|
30
32
|
$effect.pre(() => {
|
|
31
33
|
if (!isEqual(rootNode.children, nodes)) {
|
|
@@ -8,4 +8,7 @@ export interface TreeNode {
|
|
|
8
8
|
/**
|
|
9
9
|
* Creates a tree representing parent child / relationships from a set of frames.
|
|
10
10
|
*/
|
|
11
|
-
export declare const buildTreeNodes: (objects: WorldObject[]
|
|
11
|
+
export declare const buildTreeNodes: (objects: WorldObject[], worldStates: {
|
|
12
|
+
name: string;
|
|
13
|
+
objects: WorldObject[];
|
|
14
|
+
}[]) => TreeNode[];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Creates a tree representing parent child / relationships from a set of frames.
|
|
3
3
|
*/
|
|
4
|
-
export const buildTreeNodes = (objects) => {
|
|
4
|
+
export const buildTreeNodes = (objects, worldStates) => {
|
|
5
5
|
const nodeMap = new Map();
|
|
6
6
|
const rootNodes = [];
|
|
7
7
|
for (const object of objects) {
|
|
@@ -25,5 +25,27 @@ export const buildTreeNodes = (objects) => {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
for (const worldState of worldStates) {
|
|
29
|
+
const node = {
|
|
30
|
+
name: worldState.name,
|
|
31
|
+
id: worldState.name,
|
|
32
|
+
children: [],
|
|
33
|
+
href: `/world-state/${worldState.name}`,
|
|
34
|
+
};
|
|
35
|
+
console.log('worldState', worldState);
|
|
36
|
+
for (const object of worldState.objects) {
|
|
37
|
+
const child = {
|
|
38
|
+
name: object.name,
|
|
39
|
+
id: object.uuid,
|
|
40
|
+
children: [],
|
|
41
|
+
href: `/world-state/${worldState.name}/${object.name}`,
|
|
42
|
+
};
|
|
43
|
+
nodeMap.set(object.name, child);
|
|
44
|
+
node.children?.push(child);
|
|
45
|
+
console.log('child', child);
|
|
46
|
+
}
|
|
47
|
+
nodeMap.set(worldState.name, node);
|
|
48
|
+
rootNodes.push(node);
|
|
49
|
+
}
|
|
28
50
|
return rootNodes;
|
|
29
51
|
};
|
|
@@ -11,11 +11,14 @@
|
|
|
11
11
|
import Pointcloud from './Pointcloud.svelte'
|
|
12
12
|
import Model from './WorldObject.svelte'
|
|
13
13
|
import Label from './Label.svelte'
|
|
14
|
+
import { useWorldStates } from '../hooks/useWorldState.svelte'
|
|
15
|
+
import WorldState from './WorldState.svelte'
|
|
14
16
|
|
|
15
17
|
const points = usePointClouds()
|
|
16
18
|
const drawAPI = useDrawAPI()
|
|
17
19
|
const frames = useFrames()
|
|
18
20
|
const geometries = useGeometries()
|
|
21
|
+
const worldStates = useWorldStates()
|
|
19
22
|
</script>
|
|
20
23
|
|
|
21
24
|
{#each frames.current as object (object.uuid)}
|
|
@@ -65,6 +68,10 @@
|
|
|
65
68
|
</Portal>
|
|
66
69
|
{/each}
|
|
67
70
|
|
|
71
|
+
{#each worldStates.current as { name, objects } (name)}
|
|
72
|
+
<WorldState {objects} />
|
|
73
|
+
{/each}
|
|
74
|
+
|
|
68
75
|
{#each points.current as object (object.uuid)}
|
|
69
76
|
<Portal id={object.referenceFrame}>
|
|
70
77
|
<Pointcloud {object}>
|
|
@@ -81,11 +88,14 @@
|
|
|
81
88
|
</Portal>
|
|
82
89
|
{/each}
|
|
83
90
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
{#if drawAPI.poses.length > 0}
|
|
92
|
+
<T
|
|
93
|
+
name={drawAPI.object3ds.batchedArrow.object3d.name}
|
|
94
|
+
is={drawAPI.object3ds.batchedArrow.object3d}
|
|
95
|
+
dispose={false}
|
|
96
|
+
bvh={{ enabled: false }}
|
|
97
|
+
/>
|
|
98
|
+
{/if}
|
|
89
99
|
|
|
90
100
|
{#each drawAPI.meshes as object (object.uuid)}
|
|
91
101
|
<Portal id={object.referenceFrame}>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Frame from './Frame.svelte'
|
|
3
|
+
import Label from './Label.svelte'
|
|
4
|
+
import Portal from './portal/Portal.svelte'
|
|
5
|
+
import PortalTarget from './portal/PortalTarget.svelte'
|
|
6
|
+
import { WorldObject } from '../WorldObject'
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
objects: WorldObject[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let { objects }: Props = $props()
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
{#each objects as object (object.uuid)}
|
|
16
|
+
<Portal id={object.referenceFrame}>
|
|
17
|
+
<Frame
|
|
18
|
+
uuid={object.uuid}
|
|
19
|
+
name={object.name}
|
|
20
|
+
pose={object.pose}
|
|
21
|
+
geometry={object.geometry}
|
|
22
|
+
metadata={object.metadata}
|
|
23
|
+
>
|
|
24
|
+
<PortalTarget id={object.name} />
|
|
25
|
+
<Label text={object.name} />
|
|
26
|
+
</Frame>
|
|
27
|
+
</Portal>
|
|
28
|
+
{/each}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Raycaster, type Intersection } from 'three';
|
|
2
|
+
type EventNames = 'click' | 'move' | 'pointerenter' | 'pointerleave';
|
|
3
|
+
interface RaycastEvent<T extends EventNames> {
|
|
4
|
+
type: T;
|
|
5
|
+
intersections: Intersection[];
|
|
6
|
+
}
|
|
7
|
+
type Callback<T extends EventNames> = (event: RaycastEvent<T>) => void;
|
|
8
|
+
export declare const useMouseRaycaster: (getOptions?: () => {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
}) => {
|
|
11
|
+
raycaster: Raycaster;
|
|
12
|
+
onclick: (cb: Callback<"click">) => void;
|
|
13
|
+
onmove: (cb: Callback<"move">) => void;
|
|
14
|
+
onpointerenter: (cb: Callback<"pointerenter">) => void;
|
|
15
|
+
onpointerleave: (cb: Callback<"pointerleave">) => void;
|
|
16
|
+
};
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Vector2, Raycaster, EventDispatcher } from 'three';
|
|
2
|
+
import { useThrelte } from '@threlte/core';
|
|
3
|
+
const pointerDown = new Vector2();
|
|
4
|
+
const pointerUp = new Vector2();
|
|
5
|
+
const pointerMove = new Vector2();
|
|
6
|
+
export const useMouseRaycaster = (getOptions) => {
|
|
7
|
+
let intersections = [];
|
|
8
|
+
const options = $derived({
|
|
9
|
+
enabled: true,
|
|
10
|
+
...getOptions?.(),
|
|
11
|
+
});
|
|
12
|
+
const eventDispatcher = new EventDispatcher();
|
|
13
|
+
const raycaster = new Raycaster();
|
|
14
|
+
const { camera, dom, scene } = useThrelte();
|
|
15
|
+
const getNormalizedCoordinates = (event, vec) => {
|
|
16
|
+
const rect = dom.getBoundingClientRect();
|
|
17
|
+
/*
|
|
18
|
+
* Calculate pointer position in normalized device coordinates
|
|
19
|
+
* (-1 to +1) for both components
|
|
20
|
+
*/
|
|
21
|
+
vec.x = ((event.clientX - rect.x) / dom.clientWidth) * 2 - 1;
|
|
22
|
+
vec.y = -(((event.clientY - rect.y) / dom.clientHeight) * 2) + 1;
|
|
23
|
+
};
|
|
24
|
+
const onPointerDown = (event) => {
|
|
25
|
+
getNormalizedCoordinates(event, pointerDown);
|
|
26
|
+
};
|
|
27
|
+
const onPointerUp = (event) => {
|
|
28
|
+
if (camera.current === undefined) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
getNormalizedCoordinates(event, pointerUp);
|
|
32
|
+
if (pointerDown.sub(pointerUp).lengthSq() > 0.001) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Update the picking ray with the camera and pointer position
|
|
36
|
+
raycaster.setFromCamera(pointerUp, camera.current);
|
|
37
|
+
const currentIntersections = raycaster.intersectObjects(scene.children, true);
|
|
38
|
+
eventDispatcher.dispatchEvent({ type: 'click', intersections: currentIntersections });
|
|
39
|
+
};
|
|
40
|
+
const onPointerMove = (event) => {
|
|
41
|
+
if (camera.current === undefined) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
getNormalizedCoordinates(event, pointerMove);
|
|
45
|
+
raycaster.setFromCamera(pointerMove, camera.current);
|
|
46
|
+
const currentIntersections = raycaster.intersectObjects(scene.children, true);
|
|
47
|
+
const enterIntersections = [];
|
|
48
|
+
const leaveIntersections = [];
|
|
49
|
+
for (const a of currentIntersections) {
|
|
50
|
+
if (!intersections.some((b) => b.object.uuid === a.object.uuid)) {
|
|
51
|
+
enterIntersections.push(a);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const a of intersections) {
|
|
55
|
+
if (!currentIntersections.some((b) => b.object.uuid === a.object.uuid)) {
|
|
56
|
+
leaveIntersections.push(a);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (enterIntersections.length > 0) {
|
|
60
|
+
eventDispatcher.dispatchEvent({ type: 'pointerenter', intersections: enterIntersections });
|
|
61
|
+
}
|
|
62
|
+
if (leaveIntersections.length > 0) {
|
|
63
|
+
eventDispatcher.dispatchEvent({ type: 'pointerleave', intersections: leaveIntersections });
|
|
64
|
+
}
|
|
65
|
+
eventDispatcher.dispatchEvent({ type: 'move', intersections: currentIntersections });
|
|
66
|
+
intersections = currentIntersections;
|
|
67
|
+
};
|
|
68
|
+
$effect(() => {
|
|
69
|
+
if (!options.enabled) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
dom.addEventListener('pointermove', onPointerMove, { passive: true });
|
|
73
|
+
dom.addEventListener('pointerdown', onPointerDown, { passive: true });
|
|
74
|
+
dom.addEventListener('pointerup', onPointerUp, { passive: true });
|
|
75
|
+
return () => {
|
|
76
|
+
dom.removeEventListener('pointerdown', onPointerDown);
|
|
77
|
+
dom.removeEventListener('pointerup', onPointerUp);
|
|
78
|
+
dom.removeEventListener('pointermove', onPointerMove);
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
raycaster,
|
|
83
|
+
onclick: (cb) => {
|
|
84
|
+
$effect(() => {
|
|
85
|
+
eventDispatcher.addEventListener('click', cb);
|
|
86
|
+
return () => eventDispatcher.removeEventListener('click', cb);
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
onmove: (cb) => {
|
|
90
|
+
$effect(() => {
|
|
91
|
+
eventDispatcher.addEventListener('move', cb);
|
|
92
|
+
return () => eventDispatcher.removeEventListener('move', cb);
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
onpointerenter: (cb) => {
|
|
96
|
+
$effect(() => {
|
|
97
|
+
eventDispatcher.addEventListener('pointerenter', cb);
|
|
98
|
+
return () => eventDispatcher.removeEventListener('pointerenter', cb);
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
onpointerleave: (cb) => {
|
|
102
|
+
$effect(() => {
|
|
103
|
+
eventDispatcher.addEventListener('pointerleave', cb);
|
|
104
|
+
return () => eventDispatcher.removeEventListener('pointerleave', cb);
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
};
|
|
@@ -2,17 +2,12 @@ import { useCursor } from '@threlte/extras';
|
|
|
2
2
|
import { useFocused, useSelected } from './useSelection.svelte';
|
|
3
3
|
import { useVisibility } from './useVisibility.svelte';
|
|
4
4
|
import { Vector2 } from 'three';
|
|
5
|
-
import { useSettings } from './useSettings.svelte';
|
|
6
5
|
export const useObjectEvents = (uuid) => {
|
|
7
|
-
const settings = useSettings();
|
|
8
6
|
const selected = useSelected();
|
|
9
7
|
const focused = useFocused();
|
|
10
8
|
const visibility = useVisibility();
|
|
11
9
|
const down = new Vector2();
|
|
12
|
-
const
|
|
13
|
-
const hoverCursor = useCursor();
|
|
14
|
-
const measuring = $derived(settings.current.enableMeasure);
|
|
15
|
-
const cursor = $derived(measuring ? measureCursor : hoverCursor);
|
|
10
|
+
const cursor = useCursor();
|
|
16
11
|
return {
|
|
17
12
|
get visible() {
|
|
18
13
|
return visibility.get(uuid());
|
|
@@ -27,9 +22,6 @@ export const useObjectEvents = (uuid) => {
|
|
|
27
22
|
},
|
|
28
23
|
ondblclick: (event) => {
|
|
29
24
|
event.stopPropagation();
|
|
30
|
-
if (measuring) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
25
|
focused.set(uuid());
|
|
34
26
|
},
|
|
35
27
|
onpointerdown: (event) => {
|
|
@@ -37,9 +29,6 @@ export const useObjectEvents = (uuid) => {
|
|
|
37
29
|
},
|
|
38
30
|
onclick: (event) => {
|
|
39
31
|
event.stopPropagation();
|
|
40
|
-
if (measuring) {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
32
|
if (down.distanceToSquared(event.pointer) < 0.1) {
|
|
44
33
|
selected.set(uuid());
|
|
45
34
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useThrelte } from '@threlte/core';
|
|
1
|
+
import { isInstanceOf, useThrelte } from '@threlte/core';
|
|
2
2
|
import { getContext, setContext } from 'svelte';
|
|
3
3
|
import { Matrix4, Object3D } from 'three';
|
|
4
4
|
import { useObjects } from './useObjects.svelte';
|
|
@@ -56,7 +56,17 @@ export const provideSelection = () => {
|
|
|
56
56
|
setContext(focusedObjectKey, focusedObjectContext);
|
|
57
57
|
const { scene } = useThrelte();
|
|
58
58
|
const uuid = $derived(focusedObject?.uuid);
|
|
59
|
-
const focusedObject3d = $derived
|
|
59
|
+
const focusedObject3d = $derived.by(() => {
|
|
60
|
+
if (!uuid)
|
|
61
|
+
return;
|
|
62
|
+
const object = scene.getObjectByProperty('uuid', uuid)?.clone();
|
|
63
|
+
object?.traverse((child) => {
|
|
64
|
+
if (isInstanceOf(child, 'LineSegments')) {
|
|
65
|
+
child.raycast = () => null;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return object;
|
|
69
|
+
});
|
|
60
70
|
setContext(focusedObject3dKey, {
|
|
61
71
|
get current() {
|
|
62
72
|
return focusedObject3d;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type TransformChangeEvent } from '@viamrobotics/sdk';
|
|
2
|
+
import { WorldObject } from '../WorldObject';
|
|
3
|
+
export declare const useWorldStates: () => {
|
|
4
|
+
readonly names: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").ResourceName>[];
|
|
5
|
+
readonly current: {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly objects: WorldObject<import("../WorldObject").Geometries>[];
|
|
8
|
+
readonly listUUIDs: import("@tanstack/svelte-query").QueryObserverResult<string[]>;
|
|
9
|
+
readonly getTransforms: import("@tanstack/svelte-query").QueryObserverResult<import("@viamrobotics/sdk").TransformWithUUID>[] | undefined;
|
|
10
|
+
readonly changeStream: import("@tanstack/svelte-query").DefinedQueryObserverResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverLoadingErrorResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverLoadingResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverPendingResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverPlaceholderResult<TransformChangeEvent[], Error>;
|
|
11
|
+
}[];
|
|
12
|
+
};
|
|
13
|
+
export declare const useWorldState: (partID: () => string, resourceName: () => string) => {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly objects: WorldObject<import("../WorldObject").Geometries>[];
|
|
16
|
+
readonly listUUIDs: import("@tanstack/svelte-query").QueryObserverResult<string[]>;
|
|
17
|
+
readonly getTransforms: import("@tanstack/svelte-query").QueryObserverResult<import("@viamrobotics/sdk").TransformWithUUID>[] | undefined;
|
|
18
|
+
readonly changeStream: import("@tanstack/svelte-query").DefinedQueryObserverResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverLoadingErrorResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverLoadingResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverPendingResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverPlaceholderResult<TransformChangeEvent[], Error>;
|
|
19
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { toPath, getInUnsafe, mutInUnsafe } from '@thi.ng/paths';
|
|
2
|
+
import { WorldStateStoreClient, TransformChangeType, } from '@viamrobotics/sdk';
|
|
3
|
+
import { createResourceClient, createResourceQuery, createResourceStream, useResourceNames, } from '@viamrobotics/svelte-sdk';
|
|
4
|
+
import { fromTransform, WorldObject } from '../WorldObject';
|
|
5
|
+
import { usePartID } from './usePartID.svelte';
|
|
6
|
+
export const useWorldStates = () => {
|
|
7
|
+
const partID = usePartID();
|
|
8
|
+
const resourceNames = useResourceNames(() => partID.current, 'world_state_store');
|
|
9
|
+
const current = $derived.by(() => resourceNames.current.map(({ name }) => useWorldState(() => partID.current, () => name)));
|
|
10
|
+
return {
|
|
11
|
+
get names() {
|
|
12
|
+
return resourceNames.current;
|
|
13
|
+
},
|
|
14
|
+
get current() {
|
|
15
|
+
return current;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export const useWorldState = (partID, resourceName) => {
|
|
20
|
+
const client = createResourceClient(WorldStateStoreClient, partID, resourceName);
|
|
21
|
+
let initialized = false;
|
|
22
|
+
const listUUIDs = createResourceQuery(client, 'listUUIDs');
|
|
23
|
+
const getTransforms = $derived(listUUIDs.current.data?.map((uuid) => {
|
|
24
|
+
return createResourceQuery(client, 'getTransform', () => [uuid], () => ({ refetchInterval: false }));
|
|
25
|
+
}));
|
|
26
|
+
const changeStream = createResourceStream(client, 'streamTransformChanges');
|
|
27
|
+
const worldObjects = $state({});
|
|
28
|
+
const initializeCurrent = (objects) => {
|
|
29
|
+
for (const object of objects) {
|
|
30
|
+
worldObjects[object.uuid] = object;
|
|
31
|
+
}
|
|
32
|
+
initialized = true;
|
|
33
|
+
};
|
|
34
|
+
$effect(() => {
|
|
35
|
+
if (!getTransforms) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (initialized) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const queries = getTransforms.map((query) => query.current);
|
|
42
|
+
if (queries.some((query) => query?.isLoading)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const objects = [];
|
|
46
|
+
for (const transform of queries.flatMap((query) => query.data) ?? []) {
|
|
47
|
+
if (transform === undefined) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
objects.push(fromTransform(transform));
|
|
51
|
+
}
|
|
52
|
+
initializeCurrent(objects);
|
|
53
|
+
});
|
|
54
|
+
const processChangeEvent = async (event) => {
|
|
55
|
+
if (event.transform === undefined) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
switch (event.changeType) {
|
|
59
|
+
case TransformChangeType.ADDED:
|
|
60
|
+
worldObjects[event.transform.uuidString] = fromTransform(event.transform);
|
|
61
|
+
break;
|
|
62
|
+
case TransformChangeType.UPDATED:
|
|
63
|
+
for (const path of event.updatedFields?.paths ?? []) {
|
|
64
|
+
// Type inference is tough here, so we use unsafe APIs
|
|
65
|
+
const paths = toPath(path);
|
|
66
|
+
const next = getInUnsafe(event.transform, paths);
|
|
67
|
+
mutInUnsafe(worldObjects[event.transform.uuidString], paths, next);
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
case TransformChangeType.REMOVED:
|
|
71
|
+
delete worldObjects[event.transform.uuidString];
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
$effect(() => {
|
|
76
|
+
for (const event of changeStream.current?.data ?? []) {
|
|
77
|
+
void processChangeEvent(event);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
get name() {
|
|
82
|
+
return resourceName();
|
|
83
|
+
},
|
|
84
|
+
get objects() {
|
|
85
|
+
return Object.values(worldObjects);
|
|
86
|
+
},
|
|
87
|
+
get listUUIDs() {
|
|
88
|
+
return listUUIDs.current;
|
|
89
|
+
},
|
|
90
|
+
get getTransforms() {
|
|
91
|
+
return getTransforms?.map((query) => query.current);
|
|
92
|
+
},
|
|
93
|
+
get changeStream() {
|
|
94
|
+
return changeStream.current;
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
};
|
package/package.json
CHANGED
|
@@ -1,71 +1,73 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viamrobotics/motion-tools",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
4
4
|
"description": "Motion visualization with Viam",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"devDependencies": {
|
|
8
|
-
"@ag-grid-community/client-side-row-model": "32.3.
|
|
9
|
-
"@ag-grid-community/core": "32.3.
|
|
10
|
-
"@ag-grid-community/styles": "32.3.
|
|
11
|
-
"@changesets/cli": "2.29.
|
|
12
|
-
"@dimforge/rapier3d-compat": "0.18.
|
|
13
|
-
"@eslint/compat": "1.3.
|
|
14
|
-
"@eslint/js": "9.
|
|
15
|
-
"@playwright/test": "1.
|
|
16
|
-
"@sentry/sveltekit": "10.
|
|
17
|
-
"@skeletonlabs/skeleton": "3.
|
|
18
|
-
"@skeletonlabs/skeleton-svelte": "1.
|
|
19
|
-
"@sveltejs/adapter-static": "3.0.
|
|
20
|
-
"@sveltejs/kit": "2.
|
|
21
|
-
"@sveltejs/package": "2.
|
|
22
|
-
"@sveltejs/vite-plugin-svelte": "6.1.
|
|
8
|
+
"@ag-grid-community/client-side-row-model": "32.3.9",
|
|
9
|
+
"@ag-grid-community/core": "32.3.9",
|
|
10
|
+
"@ag-grid-community/styles": "32.3.9",
|
|
11
|
+
"@changesets/cli": "2.29.6",
|
|
12
|
+
"@dimforge/rapier3d-compat": "0.18.2",
|
|
13
|
+
"@eslint/compat": "1.3.2",
|
|
14
|
+
"@eslint/js": "9.34.0",
|
|
15
|
+
"@playwright/test": "1.55.0",
|
|
16
|
+
"@sentry/sveltekit": "10.10.0",
|
|
17
|
+
"@skeletonlabs/skeleton": "3.2.0",
|
|
18
|
+
"@skeletonlabs/skeleton-svelte": "1.5.1",
|
|
19
|
+
"@sveltejs/adapter-static": "3.0.9",
|
|
20
|
+
"@sveltejs/kit": "2.37.0",
|
|
21
|
+
"@sveltejs/package": "2.5.0",
|
|
22
|
+
"@sveltejs/vite-plugin-svelte": "6.1.4",
|
|
23
23
|
"@tailwindcss/forms": "0.5.10",
|
|
24
|
-
"@tailwindcss/vite": "4.1.
|
|
25
|
-
"@tanstack/svelte-query": "5.
|
|
26
|
-
"@tanstack/svelte-query-devtools": "5.
|
|
27
|
-
"@testing-library/jest-dom": "6.
|
|
24
|
+
"@tailwindcss/vite": "4.1.13",
|
|
25
|
+
"@tanstack/svelte-query": "5.86.0",
|
|
26
|
+
"@tanstack/svelte-query-devtools": "5.86.0",
|
|
27
|
+
"@testing-library/jest-dom": "6.8.0",
|
|
28
28
|
"@testing-library/svelte": "5.2.8",
|
|
29
|
-
"@
|
|
30
|
-
"@threlte/
|
|
29
|
+
"@thi.ng/paths": "5.2.21",
|
|
30
|
+
"@threlte/core": "8.1.5",
|
|
31
|
+
"@threlte/extras": "9.5.4",
|
|
31
32
|
"@threlte/rapier": "3.1.5",
|
|
32
33
|
"@threlte/xr": "1.0.8",
|
|
33
|
-
"@types/bun": "1.2.
|
|
34
|
+
"@types/bun": "1.2.21",
|
|
34
35
|
"@types/lodash-es": "4.17.12",
|
|
35
36
|
"@types/three": "0.179.0",
|
|
36
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
37
|
-
"@typescript-eslint/parser": "8.
|
|
37
|
+
"@typescript-eslint/eslint-plugin": "8.42.0",
|
|
38
|
+
"@typescript-eslint/parser": "8.42.0",
|
|
38
39
|
"@viamrobotics/prime-core": "0.1.5",
|
|
39
|
-
"@viamrobotics/sdk": "0.
|
|
40
|
-
"@viamrobotics/svelte-sdk": "0.
|
|
40
|
+
"@viamrobotics/sdk": "0.50.0",
|
|
41
|
+
"@viamrobotics/svelte-sdk": "0.6.0",
|
|
41
42
|
"@vitejs/plugin-basic-ssl": "2.1.0",
|
|
42
|
-
"@zag-js/svelte": "1.
|
|
43
|
-
"@zag-js/tree-view": "1.
|
|
43
|
+
"@zag-js/svelte": "1.22.1",
|
|
44
|
+
"@zag-js/tree-view": "1.22.1",
|
|
44
45
|
"camera-controls": "3.1.0",
|
|
45
|
-
"eslint": "9.
|
|
46
|
+
"eslint": "9.34.0",
|
|
46
47
|
"eslint-config-prettier": "10.1.8",
|
|
47
|
-
"eslint-plugin-svelte": "3.
|
|
48
|
+
"eslint-plugin-svelte": "3.12.1",
|
|
48
49
|
"globals": "16.3.0",
|
|
49
50
|
"idb-keyval": "6.2.2",
|
|
50
51
|
"jsdom": "26.1.0",
|
|
51
52
|
"lodash-es": "4.17.21",
|
|
52
|
-
"lucide-svelte": "0.
|
|
53
|
+
"lucide-svelte": "0.542.0",
|
|
53
54
|
"prettier": "3.6.2",
|
|
54
55
|
"prettier-plugin-svelte": "3.4.0",
|
|
55
56
|
"prettier-plugin-tailwindcss": "0.6.14",
|
|
56
57
|
"publint": "0.3.12",
|
|
57
58
|
"runed": "0.31.1",
|
|
58
|
-
"svelte": "5.
|
|
59
|
+
"svelte": "5.38.7",
|
|
59
60
|
"svelte-check": "4.3.1",
|
|
60
61
|
"svelte-virtuallists": "1.4.2",
|
|
61
|
-
"tailwindcss": "4.1.
|
|
62
|
-
"three": "0.179.
|
|
63
|
-
"
|
|
64
|
-
"
|
|
62
|
+
"tailwindcss": "4.1.13",
|
|
63
|
+
"three": "0.179.0",
|
|
64
|
+
"three-mesh-bvh": "^0.9.1",
|
|
65
|
+
"threlte-uikit": "1.2.1",
|
|
66
|
+
"tsx": "4.20.5",
|
|
65
67
|
"typescript": "5.9.2",
|
|
66
|
-
"typescript-eslint": "8.
|
|
67
|
-
"vite": "
|
|
68
|
-
"vite-plugin-devtools-json": "0.
|
|
68
|
+
"typescript-eslint": "8.42.0",
|
|
69
|
+
"vite": "7.1.4",
|
|
70
|
+
"vite-plugin-devtools-json": "1.0.0",
|
|
69
71
|
"vite-plugin-mkcert": "1.17.8",
|
|
70
72
|
"vitest": "3.2.4"
|
|
71
73
|
},
|
|
File without changes
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export default Labels;
|
|
2
|
-
type Labels = SvelteComponent<{
|
|
3
|
-
[x: string]: never;
|
|
4
|
-
}, {
|
|
5
|
-
[evt: string]: CustomEvent<any>;
|
|
6
|
-
}, {}> & {
|
|
7
|
-
$$bindings?: string | undefined;
|
|
8
|
-
};
|
|
9
|
-
declare const Labels: $$__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
|
-
}
|