@viamrobotics/motion-tools 1.32.0 → 1.33.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/dist/components/App.svelte +17 -11
- package/dist/components/App.svelte.d.ts +14 -7
- package/dist/components/Entities/Entities.svelte +18 -25
- package/dist/components/Entities/Entities.svelte.d.ts +2 -17
- package/dist/components/Entities/Label.svelte +79 -13
- package/dist/components/Entities/Label.svelte.d.ts +2 -1
- package/dist/components/Entities/Labels.svelte +36 -0
- package/dist/components/Entities/Labels.svelte.d.ts +3 -0
- package/dist/components/Entities/LineDots.svelte +8 -3
- package/dist/components/Entities/labelLayout/applyTeleports.d.ts +9 -0
- package/dist/components/Entities/labelLayout/applyTeleports.js +39 -0
- package/dist/components/Entities/labelLayout/buildNeighborhood.d.ts +8 -0
- package/dist/components/Entities/labelLayout/buildNeighborhood.js +26 -0
- package/dist/components/Entities/labelLayout/cameraHash.d.ts +8 -0
- package/dist/components/Entities/labelLayout/cameraHash.js +25 -0
- package/dist/components/Entities/labelLayout/cost.d.ts +44 -0
- package/dist/components/Entities/labelLayout/cost.js +126 -0
- package/dist/components/Entities/labelLayout/createLabelLayout.d.ts +27 -0
- package/dist/components/Entities/labelLayout/createLabelLayout.js +194 -0
- package/dist/components/Entities/labelLayout/geometry.d.ts +20 -0
- package/dist/components/Entities/labelLayout/geometry.js +151 -0
- package/dist/components/Entities/labelLayout/labelStore.svelte.d.ts +17 -0
- package/dist/components/Entities/labelLayout/labelStore.svelte.js +28 -0
- package/dist/components/Entities/labelLayout/measure.d.ts +13 -0
- package/dist/components/Entities/labelLayout/measure.js +42 -0
- package/dist/components/Entities/labelLayout/slots.d.ts +11 -0
- package/dist/components/Entities/labelLayout/slots.js +47 -0
- package/dist/components/Entities/labelLayout/solve.d.ts +11 -0
- package/dist/components/Entities/labelLayout/solve.js +93 -0
- package/dist/components/Entities/labelLayout/spatialHash.d.ts +15 -0
- package/dist/components/Entities/labelLayout/spatialHash.js +53 -0
- package/dist/components/Entities/labelLayout/types.d.ts +105 -0
- package/dist/components/Entities/labelLayout/types.js +19 -0
- package/dist/components/Entities/labelLayout/writeBack.d.ts +20 -0
- package/dist/components/Entities/labelLayout/writeBack.js +51 -0
- package/dist/components/Scene.svelte +42 -48
- package/dist/components/SceneProviders.svelte +0 -3
- package/dist/components/SelectedTransformControls.svelte +65 -47
- package/dist/components/overlay/Details.svelte +198 -224
- package/dist/components/overlay/Details.svelte.d.ts +1 -1
- package/dist/components/overlay/Popover.svelte +6 -4
- package/dist/components/overlay/Popover.svelte.d.ts +6 -2
- package/dist/components/overlay/dashboard/Button.svelte +7 -2
- package/dist/components/overlay/dashboard/Button.svelte.d.ts +2 -1
- package/dist/components/overlay/details/AxesHelperDetails.svelte +32 -0
- package/dist/components/overlay/details/AxesHelperDetails.svelte.d.ts +7 -0
- package/dist/components/overlay/details/ColorDetails.svelte +35 -0
- package/dist/components/overlay/details/ColorDetails.svelte.d.ts +7 -0
- package/dist/components/overlay/details/GeometryDetails.svelte +104 -0
- package/dist/components/overlay/details/GeometryDetails.svelte.d.ts +7 -0
- package/dist/components/overlay/details/LineDetails/LineDetails.svelte +196 -0
- package/dist/components/overlay/details/LineDetails/LineDetails.svelte.d.ts +7 -0
- package/dist/components/overlay/details/LineDetails/linePositions.d.ts +3 -0
- package/dist/components/overlay/details/LineDetails/linePositions.js +30 -0
- package/dist/components/overlay/details/OpacityDetails.svelte +44 -0
- package/dist/components/overlay/details/OpacityDetails.svelte.d.ts +7 -0
- package/dist/components/overlay/details/PoseDetails.svelte +189 -0
- package/dist/components/overlay/details/PoseDetails.svelte.d.ts +14 -0
- package/dist/components/overlay/settings/ConnectionSettings.svelte +42 -0
- package/dist/components/overlay/settings/ConnectionSettings.svelte.d.ts +18 -0
- package/dist/components/overlay/settings/DebugSettings.svelte +13 -0
- package/dist/components/{xr/frame-configure/Controllers.svelte.d.ts → overlay/settings/DebugSettings.svelte.d.ts} +3 -3
- package/dist/components/overlay/settings/PointcloudSettings.svelte +61 -0
- package/dist/components/overlay/settings/PointcloudSettings.svelte.d.ts +3 -0
- package/dist/components/overlay/settings/SceneSettings.svelte +110 -0
- package/dist/components/overlay/settings/SceneSettings.svelte.d.ts +18 -0
- package/dist/components/overlay/settings/Settings.svelte +27 -312
- package/dist/components/overlay/settings/Settings.svelte.d.ts +8 -1
- package/dist/components/overlay/settings/Tabs.svelte +5 -3
- package/dist/components/overlay/settings/Tabs.svelte.d.ts +3 -3
- package/dist/components/overlay/settings/VisionSettings.svelte +31 -0
- package/dist/components/overlay/settings/VisionSettings.svelte.d.ts +3 -0
- package/dist/components/overlay/settings/WeblabSettings.svelte +27 -0
- package/dist/components/overlay/settings/WeblabSettings.svelte.d.ts +18 -0
- package/dist/components/overlay/settings/WidgetSettings.svelte +49 -0
- package/dist/components/overlay/settings/WidgetSettings.svelte.d.ts +3 -0
- package/dist/components/overlay/widgets/FramePov.svelte +1 -12
- package/dist/ecs/traits.d.ts +1 -1
- package/dist/ecs/traits.js +1 -1
- package/dist/hooks/useWorldState.svelte.js +39 -50
- package/dist/{components/xr → plugins/XR}/ArmTeleop.svelte +3 -5
- package/dist/plugins/XR/DebugPanel.svelte +29 -0
- package/dist/plugins/XR/DebugPanel.svelte.d.ts +3 -0
- package/dist/plugins/XR/OriginMarker.svelte +341 -0
- package/dist/plugins/XR/PendingEditsPanel.svelte +60 -0
- package/dist/plugins/XR/PendingEditsPanel.svelte.d.ts +18 -0
- package/dist/plugins/XR/WristDisplay.svelte +60 -0
- package/dist/plugins/XR/WristDisplay.svelte.d.ts +19 -0
- package/dist/{components/xr → plugins/XR}/XR.svelte +69 -23
- package/dist/plugins/XR/XRPlugins.svelte +9 -0
- package/dist/plugins/XR/XRPlugins.svelte.d.ts +26 -0
- package/dist/plugins/XR/XRSettings.svelte +240 -0
- package/dist/plugins/XR/XRSettings.svelte.d.ts +3 -0
- package/dist/{components/xr → plugins/XR}/XRToast.svelte +6 -9
- package/dist/plugins/XR/debug.svelte.d.ts +7 -0
- package/dist/plugins/XR/debug.svelte.js +13 -0
- package/dist/plugins/XR/frame-configure/Controllers.svelte +413 -0
- package/dist/plugins/XR/teleop/Controllers.svelte.d.ts +3 -0
- package/dist/{components/xr → plugins/XR}/useAnchors.svelte.d.ts +4 -0
- package/dist/{components/xr → plugins/XR}/useAnchors.svelte.js +22 -0
- package/dist/plugins/XR/useOrigin.svelte.d.ts +24 -0
- package/dist/plugins/XR/useOrigin.svelte.js +50 -0
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/index.js +2 -0
- package/dist/three/OBBHelper.js +1 -0
- package/package.json +3 -1
- package/dist/components/xr/OriginMarker.svelte +0 -151
- package/dist/components/xr/XRControllerSettings.svelte +0 -242
- package/dist/components/xr/XRControllerSettings.svelte.d.ts +0 -3
- package/dist/components/xr/frame-configure/Controllers.svelte +0 -6
- package/dist/components/xr/useOrigin.svelte.d.ts +0 -9
- package/dist/components/xr/useOrigin.svelte.js +0 -27
- /package/dist/{components/xr → plugins/XR}/ArmTeleop.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/BentPlaneGeometry.svelte +0 -0
- /package/dist/{components/xr → plugins/XR}/BentPlaneGeometry.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/CameraFeed.svelte +0 -0
- /package/dist/{components/xr → plugins/XR}/CameraFeed.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/JointLimitsWidget.svelte +0 -0
- /package/dist/{components/xr → plugins/XR}/JointLimitsWidget.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/OriginMarker.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/PointDistance.svelte +0 -0
- /package/dist/{components/xr → plugins/XR}/PointDistance.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/XR.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/XRConfigPanel.svelte +0 -0
- /package/dist/{components/xr → plugins/XR}/XRConfigPanel.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/XRToast.svelte.d.ts +0 -0
- /package/dist/{components/xr/teleop → plugins/XR/frame-configure}/Controllers.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/math.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/math.js +0 -0
- /package/dist/{components/xr → plugins/XR}/teleop/Controllers.svelte +0 -0
- /package/dist/{components/xr → plugins/XR}/toasts.svelte.d.ts +0 -0
- /package/dist/{components/xr → plugins/XR}/toasts.svelte.js +0 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useTask, useThrelte } from '@threlte/core'
|
|
3
|
+
import { useGamepad } from '@threlte/extras'
|
|
4
|
+
import { Hand, useController, useHand, useHeadset, useXR } from '@threlte/xr'
|
|
5
|
+
import { useDebounce } from 'runed'
|
|
6
|
+
import { Euler, Quaternion, Vector3 } from 'three'
|
|
7
|
+
|
|
8
|
+
import { usePartID } from '../../hooks/usePartID.svelte'
|
|
9
|
+
|
|
10
|
+
import { useAnchors } from './useAnchors.svelte'
|
|
11
|
+
import { useOrigin } from './useOrigin.svelte'
|
|
12
|
+
|
|
13
|
+
const origin = useOrigin()
|
|
14
|
+
const anchors = useAnchors()
|
|
15
|
+
const partID = usePartID()
|
|
16
|
+
const headset = useHeadset()
|
|
17
|
+
|
|
18
|
+
const storageKey = $derived(`xr-origin-anchor:${partID.current}`)
|
|
19
|
+
|
|
20
|
+
const DEFAULT_ORIGIN: [number, number, number] = [-1, -1, 0]
|
|
21
|
+
const COMMIT_DEBOUNCE_MS = 500
|
|
22
|
+
|
|
23
|
+
const leftPad = useGamepad({ xr: true, hand: 'left' })
|
|
24
|
+
const rightPad = useGamepad({ xr: true, hand: 'right' })
|
|
25
|
+
const leftController = useController('left')
|
|
26
|
+
const rightController = useController('right')
|
|
27
|
+
|
|
28
|
+
const THUMBSTICK_SPEED = 0.05
|
|
29
|
+
|
|
30
|
+
// Head-relative translation basis. Recomputed per thumbstick tick so
|
|
31
|
+
// stick-forward always moves content away from the viewer, regardless of
|
|
32
|
+
// how the scene was rotated or which way the user is physically facing.
|
|
33
|
+
const headForward = new Vector3()
|
|
34
|
+
const headRight = new Vector3()
|
|
35
|
+
|
|
36
|
+
// The anchor that currently represents the persisted origin. Kept so we can
|
|
37
|
+
// delete it when a new calibration is committed.
|
|
38
|
+
let persistedAnchor: XRAnchor | undefined
|
|
39
|
+
|
|
40
|
+
// The restored anchor we're waiting to localize on session start. Once the
|
|
41
|
+
// device reports it as tracked we snap origin to its pose and clear this.
|
|
42
|
+
let pendingRestore = $state.raw<XRAnchor | undefined>(undefined)
|
|
43
|
+
|
|
44
|
+
const restoreQuat = new Quaternion()
|
|
45
|
+
const restoreEuler = new Euler(0, 0, 0, 'ZYX')
|
|
46
|
+
|
|
47
|
+
const commitVec = new Vector3()
|
|
48
|
+
const commitQuat = new Quaternion()
|
|
49
|
+
|
|
50
|
+
const commit = useDebounce(async () => {
|
|
51
|
+
// origin.position/rotation define the composed XR reference space's
|
|
52
|
+
// offset from zUp, so an anchor at identity in the current (composed)
|
|
53
|
+
// space IS the anchor at origin's pose in zUp. commitVec/commitQuat
|
|
54
|
+
// are left at their default-constructed zero/identity values.
|
|
55
|
+
const anchor = await anchors.createAnchor(commitVec, commitQuat)
|
|
56
|
+
if (!anchor) return
|
|
57
|
+
|
|
58
|
+
const uuid = await anchors.persist(anchor)
|
|
59
|
+
if (!uuid) {
|
|
60
|
+
anchor.delete()
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const prev = localStorage.getItem(storageKey)
|
|
65
|
+
if (prev && prev !== uuid) {
|
|
66
|
+
anchors.remove(prev).catch(() => {})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (persistedAnchor && persistedAnchor !== anchor) {
|
|
70
|
+
persistedAnchor.delete()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
persistedAnchor = anchor
|
|
74
|
+
localStorage.setItem(storageKey, uuid)
|
|
75
|
+
}, COMMIT_DEBOUNCE_MS)
|
|
76
|
+
|
|
77
|
+
origin.registerCommit(() => commit())
|
|
78
|
+
|
|
79
|
+
leftPad.thumbstick.on('change', ({ value }) => {
|
|
80
|
+
// While the grip is held, the left controller drives fine rotation;
|
|
81
|
+
// ignore the thumbstick so the two inputs don't fight each other.
|
|
82
|
+
if (leftPad.squeeze.pressed || typeof value === 'number') return
|
|
83
|
+
|
|
84
|
+
const { x: vx, y: vy } = value
|
|
85
|
+
const [x, y, z] = origin.position
|
|
86
|
+
|
|
87
|
+
origin.set([x, y, z + vy * THUMBSTICK_SPEED], origin.rotation + vx * THUMBSTICK_SPEED)
|
|
88
|
+
commit()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
rightPad.thumbstick.on('change', ({ value }) => {
|
|
92
|
+
if (rightPad.squeeze.pressed || typeof value === 'number') return
|
|
93
|
+
|
|
94
|
+
const { x: vx, y: vy } = value
|
|
95
|
+
const [x, y, z] = origin.position
|
|
96
|
+
|
|
97
|
+
headset.getWorldDirection(headForward)
|
|
98
|
+
|
|
99
|
+
// Flatten onto the XY (ground) plane so pitch doesn't bleed into horizontal motion.
|
|
100
|
+
headForward.z = 0
|
|
101
|
+
if (headForward.lengthSq() < 1e-6) {
|
|
102
|
+
// Viewer gaze was purely vertical (or pose not yet reported) — fall back to scene +Y.
|
|
103
|
+
headForward.set(0, 1, 0)
|
|
104
|
+
} else {
|
|
105
|
+
headForward.normalize()
|
|
106
|
+
}
|
|
107
|
+
// headForward is in the composed XR reference space; rotate into zUp
|
|
108
|
+
// so the stick direction maps to the user's physical gaze regardless
|
|
109
|
+
// of the current origin rotation.
|
|
110
|
+
origin.toZUpDir(headForward)
|
|
111
|
+
headRight.set(headForward.y, -headForward.x, 0)
|
|
112
|
+
|
|
113
|
+
const deltaX = headRight.x * vx * THUMBSTICK_SPEED + headForward.x * vy * THUMBSTICK_SPEED
|
|
114
|
+
const deltaY = headRight.y * vx * THUMBSTICK_SPEED + headForward.y * vy * THUMBSTICK_SPEED
|
|
115
|
+
|
|
116
|
+
origin.set([x + deltaX, y + deltaY, z], origin.rotation)
|
|
117
|
+
commit()
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// Fine calibration: hold the grip, then move/rotate the controller to nudge
|
|
121
|
+
// the origin 1:1 with the controller delta. Extract world-Z yaw from the
|
|
122
|
+
// controller quaternion for rotation so pitch/roll don't leak in.
|
|
123
|
+
const quatYaw = (q: Quaternion) =>
|
|
124
|
+
Math.atan2(2 * (q.w * q.z + q.x * q.y), 1 - 2 * (q.y * q.y + q.z * q.z))
|
|
125
|
+
|
|
126
|
+
const fineTranslateStart = new Vector3()
|
|
127
|
+
const fineTranslateOriginStart = new Vector3()
|
|
128
|
+
const fineTranslateCurrent = new Vector3()
|
|
129
|
+
let fineTranslating = $state(false)
|
|
130
|
+
|
|
131
|
+
rightPad.squeeze.on('change', () => {
|
|
132
|
+
const ray = rightController.current?.targetRay
|
|
133
|
+
if (rightPad.squeeze.pressed && ray) {
|
|
134
|
+
// Save start position in zUp so the delta stays stable as the
|
|
135
|
+
// composed reference space recomposes each tick on origin changes.
|
|
136
|
+
origin.toZUpPos(fineTranslateStart, ray.position)
|
|
137
|
+
const [ox, oy, oz] = origin.position
|
|
138
|
+
fineTranslateOriginStart.set(ox, oy, oz)
|
|
139
|
+
fineTranslating = true
|
|
140
|
+
} else {
|
|
141
|
+
fineTranslating = false
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
useTask(
|
|
146
|
+
() => {
|
|
147
|
+
const ray = rightController.current?.targetRay
|
|
148
|
+
if (!ray) return
|
|
149
|
+
origin.toZUpPos(fineTranslateCurrent, ray.position)
|
|
150
|
+
origin.set(
|
|
151
|
+
[
|
|
152
|
+
fineTranslateOriginStart.x + fineTranslateCurrent.x - fineTranslateStart.x,
|
|
153
|
+
fineTranslateOriginStart.y + fineTranslateCurrent.y - fineTranslateStart.y,
|
|
154
|
+
fineTranslateOriginStart.z + fineTranslateCurrent.z - fineTranslateStart.z,
|
|
155
|
+
],
|
|
156
|
+
origin.rotation
|
|
157
|
+
)
|
|
158
|
+
commit()
|
|
159
|
+
},
|
|
160
|
+
{ running: () => fineTranslating }
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
let fineRotateStartYaw = 0
|
|
164
|
+
let fineRotateOriginStart = 0
|
|
165
|
+
let fineRotating = $state(false)
|
|
166
|
+
|
|
167
|
+
leftPad.squeeze.on('change', () => {
|
|
168
|
+
const ray = leftController.current?.targetRay
|
|
169
|
+
if (leftPad.squeeze.pressed && ray) {
|
|
170
|
+
// Controller yaw in composed = yaw_zUp − origin.rotation; convert
|
|
171
|
+
// to zUp so the delta stays stable while origin.rotation updates.
|
|
172
|
+
fineRotateStartYaw = quatYaw(ray.quaternion) + origin.rotation
|
|
173
|
+
fineRotateOriginStart = origin.rotation
|
|
174
|
+
fineRotating = true
|
|
175
|
+
} else {
|
|
176
|
+
fineRotating = false
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
useTask(
|
|
181
|
+
() => {
|
|
182
|
+
const ray = leftController.current?.targetRay
|
|
183
|
+
if (!ray) return
|
|
184
|
+
const yawZup = quatYaw(ray.quaternion) + origin.rotation
|
|
185
|
+
origin.set(origin.position, fineRotateOriginStart + yawZup - fineRotateStartYaw)
|
|
186
|
+
commit()
|
|
187
|
+
},
|
|
188
|
+
{ running: () => fineRotating }
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
let startLeftPinchTranslation = new Vector3()
|
|
192
|
+
let leftPinchTranslation = new Vector3()
|
|
193
|
+
let startRightPinchTranslation = new Vector3()
|
|
194
|
+
let rightPinchCurrent = new Vector3()
|
|
195
|
+
let startRightPinchRotation = 0
|
|
196
|
+
|
|
197
|
+
const leftHand = useHand('left')
|
|
198
|
+
const rightHand = useHand('right')
|
|
199
|
+
|
|
200
|
+
let translating = $state(false)
|
|
201
|
+
let rotating = $state(false)
|
|
202
|
+
|
|
203
|
+
const { renderer } = useThrelte()
|
|
204
|
+
const { isPresenting } = useXR()
|
|
205
|
+
|
|
206
|
+
// Session start: try to restore a persisted anchor for this part. If none
|
|
207
|
+
// exists or restore fails, fall back to a default offset so the origin is
|
|
208
|
+
// visible in front of the user.
|
|
209
|
+
$effect(() => {
|
|
210
|
+
if (!$isPresenting) {
|
|
211
|
+
pendingRestore = undefined
|
|
212
|
+
persistedAnchor = undefined
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const uuid = localStorage.getItem(storageKey)
|
|
217
|
+
if (!uuid) {
|
|
218
|
+
origin.set(DEFAULT_ORIGIN, 0)
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let cancelled = false
|
|
223
|
+
anchors
|
|
224
|
+
.restore(uuid)
|
|
225
|
+
.then((anchor) => {
|
|
226
|
+
if (cancelled) return
|
|
227
|
+
if (anchor) {
|
|
228
|
+
persistedAnchor = anchor
|
|
229
|
+
pendingRestore = anchor
|
|
230
|
+
} else {
|
|
231
|
+
localStorage.removeItem(storageKey)
|
|
232
|
+
origin.set(DEFAULT_ORIGIN, 0)
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
.catch(() => {
|
|
236
|
+
if (cancelled) return
|
|
237
|
+
localStorage.removeItem(storageKey)
|
|
238
|
+
origin.set(DEFAULT_ORIGIN, 0)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
return () => {
|
|
242
|
+
cancelled = true
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
// Once the restored anchor has localized, snap origin to its pose. This
|
|
247
|
+
// runs only while a restore is pending so it won't fight user input.
|
|
248
|
+
useTask(
|
|
249
|
+
() => {
|
|
250
|
+
const anchor = pendingRestore
|
|
251
|
+
if (!anchor) return
|
|
252
|
+
|
|
253
|
+
const pose = anchors.getAnchorPose(anchor)
|
|
254
|
+
if (!pose) return
|
|
255
|
+
|
|
256
|
+
const { position: p, orientation: o } = pose.transform
|
|
257
|
+
restoreQuat.set(o.x, o.y, o.z, o.w)
|
|
258
|
+
restoreEuler.setFromQuaternion(restoreQuat, 'ZYX')
|
|
259
|
+
origin.set([p.x, p.y, p.z], restoreEuler.z)
|
|
260
|
+
pendingRestore = undefined
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
running: () => pendingRestore !== undefined,
|
|
264
|
+
}
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
$effect(() => {
|
|
268
|
+
if (!$isPresenting) {
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
renderer.xr.getHand(0).addEventListener('pinchstart', () => {
|
|
272
|
+
const p = leftHand.current?.targetRay.position
|
|
273
|
+
if (p) {
|
|
274
|
+
translating = true
|
|
275
|
+
// Pinch start position in zUp; delta is cumulative from here.
|
|
276
|
+
origin.toZUpPos(startLeftPinchTranslation, p)
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
useTask(
|
|
282
|
+
() => {
|
|
283
|
+
const p = leftHand.current?.targetRay.position
|
|
284
|
+
if (p && translating) {
|
|
285
|
+
origin.toZUpPos(leftPinchTranslation, p)
|
|
286
|
+
origin.set(
|
|
287
|
+
[
|
|
288
|
+
leftPinchTranslation.x - startLeftPinchTranslation.x,
|
|
289
|
+
leftPinchTranslation.y - startLeftPinchTranslation.y,
|
|
290
|
+
leftPinchTranslation.z - startLeftPinchTranslation.z,
|
|
291
|
+
],
|
|
292
|
+
origin.rotation
|
|
293
|
+
)
|
|
294
|
+
commit()
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
running: () => translating,
|
|
299
|
+
}
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
useTask(
|
|
303
|
+
() => {
|
|
304
|
+
const p = rightHand.current?.targetRay.position
|
|
305
|
+
if (p && rotating) {
|
|
306
|
+
origin.toZUpPos(rightPinchCurrent, p)
|
|
307
|
+
const deltaX = rightPinchCurrent.x - startRightPinchTranslation.x
|
|
308
|
+
origin.set(origin.position, startRightPinchRotation + deltaX)
|
|
309
|
+
commit()
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
running: () => rotating,
|
|
314
|
+
}
|
|
315
|
+
)
|
|
316
|
+
</script>
|
|
317
|
+
|
|
318
|
+
<Hand
|
|
319
|
+
left
|
|
320
|
+
onpinchstart={() => {
|
|
321
|
+
const p = leftHand.current?.targetRay.position
|
|
322
|
+
if (p) {
|
|
323
|
+
translating = true
|
|
324
|
+
origin.toZUpPos(startLeftPinchTranslation, p)
|
|
325
|
+
}
|
|
326
|
+
}}
|
|
327
|
+
onpinchend={() => (translating = false)}
|
|
328
|
+
/>
|
|
329
|
+
|
|
330
|
+
<Hand
|
|
331
|
+
right
|
|
332
|
+
onpinchstart={() => {
|
|
333
|
+
const p = rightHand.current?.targetRay.position
|
|
334
|
+
if (p) {
|
|
335
|
+
rotating = true
|
|
336
|
+
origin.toZUpPos(startRightPinchTranslation, p)
|
|
337
|
+
startRightPinchRotation = origin.rotation
|
|
338
|
+
}
|
|
339
|
+
}}
|
|
340
|
+
onpinchend={() => (rotating = false)}
|
|
341
|
+
/>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Text } from 'threlte-uikit'
|
|
3
|
+
import { Button, ButtonLabel, Panel } from 'threlte-uikit/horizon'
|
|
4
|
+
|
|
5
|
+
import { usePartConfig } from '../../hooks/usePartConfig.svelte'
|
|
6
|
+
|
|
7
|
+
import WristDisplay from './WristDisplay.svelte'
|
|
8
|
+
|
|
9
|
+
const partConfig = usePartConfig()
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
{#if partConfig.isDirty}
|
|
13
|
+
<WristDisplay position={[0, 0.005, 0.1]}>
|
|
14
|
+
<Panel
|
|
15
|
+
flexDirection="column"
|
|
16
|
+
padding={16}
|
|
17
|
+
gap={12}
|
|
18
|
+
backgroundColor="#111"
|
|
19
|
+
borderRadius={16}
|
|
20
|
+
minWidth={420}
|
|
21
|
+
>
|
|
22
|
+
<Text
|
|
23
|
+
text="Pending frame edits"
|
|
24
|
+
fontSize={18}
|
|
25
|
+
color="#ffffff"
|
|
26
|
+
/>
|
|
27
|
+
<Panel
|
|
28
|
+
flexDirection="row"
|
|
29
|
+
gap={8}
|
|
30
|
+
>
|
|
31
|
+
<Button
|
|
32
|
+
variant="tertiary"
|
|
33
|
+
size="sm"
|
|
34
|
+
onclick={() => partConfig.discardChanges()}
|
|
35
|
+
>
|
|
36
|
+
<ButtonLabel>
|
|
37
|
+
<Text
|
|
38
|
+
text="Discard"
|
|
39
|
+
fontSize={14}
|
|
40
|
+
color="#ffffff"
|
|
41
|
+
/>
|
|
42
|
+
</ButtonLabel>
|
|
43
|
+
</Button>
|
|
44
|
+
<Button
|
|
45
|
+
variant="primary"
|
|
46
|
+
size="sm"
|
|
47
|
+
onclick={() => partConfig.save()}
|
|
48
|
+
>
|
|
49
|
+
<ButtonLabel>
|
|
50
|
+
<Text
|
|
51
|
+
text="Save"
|
|
52
|
+
fontSize={14}
|
|
53
|
+
color="#ffffff"
|
|
54
|
+
/>
|
|
55
|
+
</ButtonLabel>
|
|
56
|
+
</Button>
|
|
57
|
+
</Panel>
|
|
58
|
+
</Panel>
|
|
59
|
+
</WristDisplay>
|
|
60
|
+
{/if}
|
|
@@ -0,0 +1,18 @@
|
|
|
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 PendingEditsPanel: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type PendingEditsPanel = InstanceType<typeof PendingEditsPanel>;
|
|
18
|
+
export default PendingEditsPanel;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
|
|
4
|
+
import { T } from '@threlte/core'
|
|
5
|
+
import { useController, useHandJoint } from '@threlte/xr'
|
|
6
|
+
import { fromStore } from 'svelte/store'
|
|
7
|
+
import { Group, type Vector3Tuple } from 'three'
|
|
8
|
+
import { provideDefaultProperties } from 'threlte-uikit'
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
/** Offset from the wrist in the wrist-local frame, meters. */
|
|
12
|
+
position?: Vector3Tuple
|
|
13
|
+
/**
|
|
14
|
+
* Rotation in the wrist-local frame. The default orients uikit content
|
|
15
|
+
* (panels face their own +Z) onto the dorsal side of the wrist, so the
|
|
16
|
+
* user sees it when turning their palm down, like a smartwatch.
|
|
17
|
+
*/
|
|
18
|
+
rotation?: Vector3Tuple
|
|
19
|
+
/** Uniform scale for the wrist group. Smaller than the HUD default
|
|
20
|
+
* because panels live at arm's length instead of ~1 m away. */
|
|
21
|
+
scale?: number
|
|
22
|
+
children?: Snippet
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
position = [0, 0.005, 0.08],
|
|
27
|
+
rotation = [-Math.PI / 2, 0, 0],
|
|
28
|
+
scale = 0.03,
|
|
29
|
+
children,
|
|
30
|
+
}: Props = $props()
|
|
31
|
+
|
|
32
|
+
// Draw uikit content on top of the scene (real-world depth, selection OBB,
|
|
33
|
+
// etc.), matching the old HUD behavior.
|
|
34
|
+
provideDefaultProperties(() => ({
|
|
35
|
+
depthTest: false,
|
|
36
|
+
renderOrder: 999,
|
|
37
|
+
}))
|
|
38
|
+
|
|
39
|
+
const leftController = fromStore(useController('left'))
|
|
40
|
+
const leftWrist = fromStore(useHandJoint('left', 'wrist'))
|
|
41
|
+
|
|
42
|
+
// Prefer the hand wrist joint when hand tracking is active; fall back to
|
|
43
|
+
// the controller grip. Both are three.js Groups updated per frame by
|
|
44
|
+
// WebXR, so attaching as a child follows the wrist automatically.
|
|
45
|
+
const parent = $derived(leftWrist.current ?? leftController.current?.grip)
|
|
46
|
+
|
|
47
|
+
const group = new Group()
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
{#if parent}
|
|
51
|
+
<T
|
|
52
|
+
is={group}
|
|
53
|
+
attach={parent}
|
|
54
|
+
{position}
|
|
55
|
+
{rotation}
|
|
56
|
+
{scale}
|
|
57
|
+
>
|
|
58
|
+
{@render children?.()}
|
|
59
|
+
</T>
|
|
60
|
+
{/if}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import { type Vector3Tuple } from 'three';
|
|
3
|
+
interface Props {
|
|
4
|
+
/** Offset from the wrist in the wrist-local frame, meters. */
|
|
5
|
+
position?: Vector3Tuple;
|
|
6
|
+
/**
|
|
7
|
+
* Rotation in the wrist-local frame. The default orients uikit content
|
|
8
|
+
* (panels face their own +Z) onto the dorsal side of the wrist, so the
|
|
9
|
+
* user sees it when turning their palm down, like a smartwatch.
|
|
10
|
+
*/
|
|
11
|
+
rotation?: Vector3Tuple;
|
|
12
|
+
/** Uniform scale for the wrist group. Smaller than the HUD default
|
|
13
|
+
* because panels live at arm's length instead of ~1 m away. */
|
|
14
|
+
scale?: number;
|
|
15
|
+
children?: Snippet;
|
|
16
|
+
}
|
|
17
|
+
declare const WristDisplay: import("svelte").Component<Props, {}, "">;
|
|
18
|
+
type WristDisplay = ReturnType<typeof WristDisplay>;
|
|
19
|
+
export default WristDisplay;
|
|
@@ -8,24 +8,29 @@
|
|
|
8
8
|
import { useSettings } from '../../hooks/useSettings.svelte'
|
|
9
9
|
|
|
10
10
|
import CameraFeed from './CameraFeed.svelte'
|
|
11
|
+
import DebugPanel from './DebugPanel.svelte'
|
|
11
12
|
import FrameConfigureControllers from './frame-configure/Controllers.svelte'
|
|
12
13
|
import JointLimitsWidget from './JointLimitsWidget.svelte'
|
|
13
14
|
import OriginMarker from './OriginMarker.svelte'
|
|
15
|
+
import PendingEditsPanel from './PendingEditsPanel.svelte'
|
|
14
16
|
import TeleopControllers from './teleop/Controllers.svelte'
|
|
15
17
|
import { provideAnchors } from './useAnchors.svelte'
|
|
16
|
-
import {
|
|
18
|
+
import { provideOrigin } from './useOrigin.svelte'
|
|
19
|
+
import XRPlugins from './XRPlugins.svelte'
|
|
17
20
|
import XRToast from './XRToast.svelte'
|
|
18
21
|
|
|
19
22
|
const { ...rest } = $props()
|
|
20
23
|
|
|
21
|
-
const
|
|
22
|
-
const settings = useSettings()
|
|
23
|
-
const origin = useOrigin()
|
|
24
|
+
const origin = provideOrigin()
|
|
24
25
|
provideAnchors()
|
|
25
|
-
const enableXR = $derived(settings.current.enableXR)
|
|
26
26
|
|
|
27
|
+
const { renderer } = useThrelte()
|
|
28
|
+
const { isPresenting } = useXR()
|
|
29
|
+
const settings = useSettings()
|
|
27
30
|
const partID = usePartID()
|
|
28
31
|
|
|
32
|
+
const enableXR = $derived(settings.current.enableXR)
|
|
33
|
+
|
|
29
34
|
// Get all enabled camera widgets for the current part
|
|
30
35
|
const enabledCameras = $derived.by(() => {
|
|
31
36
|
const openWidgets = settings.current.openCameraWidgets
|
|
@@ -48,34 +53,57 @@
|
|
|
48
53
|
const leftArmName = $derived(controllerConfig.left.armName)
|
|
49
54
|
const rightArmName = $derived(controllerConfig.right.armName)
|
|
50
55
|
|
|
51
|
-
|
|
56
|
+
// Compose the XR reference space from:
|
|
57
|
+
// 1) a -π/2 rotation around X to switch from WebXR's Y-up to Viam's Z-up
|
|
58
|
+
// 2) the scene origin (position + yaw) so the origin's pose lives at the
|
|
59
|
+
// composed space's identity. With this, controllers, camera, and scene
|
|
60
|
+
// content all share one frame — no separate origin group needed.
|
|
61
|
+
let baseRefSpace: XRReferenceSpace | undefined
|
|
52
62
|
|
|
53
|
-
// Move into Viam's coordinate system. This basically accomplishes
|
|
54
|
-
// the same thing as setting z up in the Camera component.
|
|
55
63
|
$effect(() => {
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const baseRefSpace = renderer.xr.getReferenceSpace()
|
|
61
|
-
if (baseRefSpace) {
|
|
62
|
-
const rotatedRefSpace = baseRefSpace.getOffsetReferenceSpace(
|
|
63
|
-
new XRRigidTransform({ x: 0, y: 0, z: 0, w: 1 }, { x: q.x, y: q.y, z: q.z, w: q.w })
|
|
64
|
-
)
|
|
64
|
+
if (!$isPresenting) {
|
|
65
|
+
baseRefSpace = undefined
|
|
66
|
+
return
|
|
67
|
+
}
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
if (!baseRefSpace) {
|
|
70
|
+
const current = renderer.xr.getReferenceSpace()
|
|
71
|
+
if (!current) return
|
|
72
|
+
baseRefSpace = current
|
|
68
73
|
}
|
|
74
|
+
|
|
75
|
+
const [ox, oy, oz] = origin.position
|
|
76
|
+
const oRot = origin.rotation
|
|
77
|
+
|
|
78
|
+
const zUpQ = new Quaternion().setFromAxisAngle({ x: 1, y: 0, z: 0 }, -Math.PI / 2)
|
|
79
|
+
const originQ = new Quaternion().setFromAxisAngle({ x: 0, y: 0, z: 1 }, oRot)
|
|
80
|
+
|
|
81
|
+
const composed = baseRefSpace
|
|
82
|
+
.getOffsetReferenceSpace(
|
|
83
|
+
new XRRigidTransform(
|
|
84
|
+
{ x: 0, y: 0, z: 0, w: 1 },
|
|
85
|
+
{ x: zUpQ.x, y: zUpQ.y, z: zUpQ.z, w: zUpQ.w }
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
.getOffsetReferenceSpace(
|
|
89
|
+
new XRRigidTransform(
|
|
90
|
+
{ x: ox, y: oy, z: oz },
|
|
91
|
+
{ x: originQ.x, y: originQ.y, z: originQ.z, w: originQ.w }
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
renderer.xr.setReferenceSpace(composed)
|
|
69
96
|
})
|
|
70
97
|
</script>
|
|
71
98
|
|
|
99
|
+
{#if $isPresenting}
|
|
100
|
+
<XRPlugins />
|
|
101
|
+
{/if}
|
|
102
|
+
|
|
72
103
|
{#if enableXR}
|
|
73
104
|
<XR
|
|
74
|
-
onsessionstart={() => {
|
|
75
|
-
origin.set([-1, -1, 0])
|
|
76
|
-
}}
|
|
77
105
|
onsessionend={() => {
|
|
78
|
-
origin.set([0, 0, 0])
|
|
106
|
+
origin.set([0, 0, 0], 0)
|
|
79
107
|
}}
|
|
80
108
|
>
|
|
81
109
|
<!-- Render camera feeds only when presenting to avoid conflicting with overlay Camera widgets -->
|
|
@@ -115,6 +143,8 @@
|
|
|
115
143
|
<!-- <XRConfigPanel offset={{ x: 0, y: 2.5, z: -2.5 }} scale={0.7} /> -->
|
|
116
144
|
|
|
117
145
|
<XRToast />
|
|
146
|
+
<DebugPanel />
|
|
147
|
+
<PendingEditsPanel />
|
|
118
148
|
|
|
119
149
|
{#if settings.current.xrMode === 'arm-teleop'}
|
|
120
150
|
<TeleopControllers />
|
|
@@ -127,6 +157,22 @@
|
|
|
127
157
|
|
|
128
158
|
<XRButton
|
|
129
159
|
mode="immersive-ar"
|
|
160
|
+
sessionInit={{
|
|
161
|
+
optionalFeatures: [
|
|
162
|
+
'local-floor',
|
|
163
|
+
'bounded-floor',
|
|
164
|
+
'anchors',
|
|
165
|
+
// Required for cross-session persistence — enables
|
|
166
|
+
// `anchor.requestPersistentHandle()` and
|
|
167
|
+
// `session.restorePersistentAnchor()` on Quest Browser.
|
|
168
|
+
'persistent-anchors',
|
|
169
|
+
'plane-detection',
|
|
170
|
+
'hand-tracking',
|
|
171
|
+
'layers',
|
|
172
|
+
'hit-test',
|
|
173
|
+
],
|
|
174
|
+
}}
|
|
175
|
+
style="color: #555; border-color: #ccc; backdrop-filter: blur(4px); background: rgba(255,255,255,0.5)"
|
|
130
176
|
{...rest}
|
|
131
177
|
/>
|
|
132
178
|
{/if}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export default XRPlugins;
|
|
2
|
+
type XRPlugins = SvelteComponent<{
|
|
3
|
+
[x: string]: never;
|
|
4
|
+
}, {
|
|
5
|
+
[evt: string]: CustomEvent<any>;
|
|
6
|
+
}, {}> & {
|
|
7
|
+
$$bindings?: string | undefined;
|
|
8
|
+
};
|
|
9
|
+
declare const XRPlugins: $$__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
|
+
}
|