@viamrobotics/motion-tools 1.13.0 → 1.13.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/FrameConfigUpdater.svelte.js +1 -1
- package/dist/WorldObject.svelte.js +26 -13
- package/dist/buf/draw/v1/service_connect.d.ts +14 -25
- package/dist/buf/draw/v1/service_connect.js +14 -25
- package/dist/buf/draw/v1/service_pb.d.ts +61 -76
- package/dist/buf/draw/v1/service_pb.js +58 -111
- package/dist/color.js +3 -3
- package/dist/components/App.svelte +1 -5
- package/dist/components/Camera.svelte +1 -7
- package/dist/components/Camera.svelte.d.ts +0 -1
- package/dist/components/CameraControls.svelte +2 -2
- package/dist/components/{Arrows → Entities/Arrows}/ArrowGroups.svelte +3 -3
- package/dist/components/{Arrows → Entities/Arrows}/Arrows.svelte +6 -6
- package/dist/components/{Arrows → Entities/Arrows}/Arrows.svelte.d.ts +1 -1
- package/dist/components/{Entities.svelte → Entities/Entities.svelte} +5 -1
- package/dist/components/{Frame.svelte → Entities/Frame.svelte} +8 -8
- package/dist/components/{GLTF.svelte → Entities/GLTF.svelte} +5 -5
- package/dist/components/{Geometry.svelte → Entities/Geometry.svelte} +8 -13
- package/dist/components/{Label.svelte → Entities/Label.svelte} +1 -1
- package/dist/components/{Line.svelte → Entities/Line.svelte} +2 -2
- package/dist/components/{Points.svelte → Entities/Points.svelte} +5 -5
- package/dist/components/{Pose.svelte → Entities/Pose.svelte} +3 -3
- package/dist/{hooks/useObjectEvents.svelte.d.ts → components/Entities/hooks/useEntityEvents.svelte.d.ts} +1 -1
- package/dist/{hooks/useObjectEvents.svelte.js → components/Entities/hooks/useEntityEvents.svelte.js} +6 -6
- package/dist/components/FileDrop/file-names.js +6 -3
- package/dist/components/FileDrop/snapshot-dropper.js +8 -4
- package/dist/components/FileDrop/useFileDrop.svelte.js +9 -6
- package/dist/components/Lasso/Lasso.svelte +4 -4
- package/dist/components/PCD.svelte +14 -6
- package/dist/components/PCD.svelte.d.ts +2 -0
- package/dist/components/Scene.svelte +1 -3
- package/dist/components/StaticGeometries.svelte +1 -1
- package/dist/components/overlay/AddRelationship.svelte +1 -1
- package/dist/components/overlay/LiveUpdatesBanner.svelte +4 -6
- package/dist/components/overlay/settings/Settings.svelte +11 -13
- package/dist/components/overlay/widgets/Camera.svelte +11 -9
- package/dist/components/xr/ArmTeleop.svelte +33 -33
- package/dist/components/xr/CameraFeed.svelte +21 -23
- package/dist/components/xr/JointLimitsWidget.svelte +19 -5
- package/dist/components/xr/XRConfigPanel.svelte +17 -16
- package/dist/components/xr/XRControllerSettings.svelte +5 -4
- package/dist/components/xr/XRToast.svelte +11 -6
- package/dist/ecs/relations.d.ts +1 -0
- package/dist/ecs/relations.js +1 -0
- package/dist/ecs/useQuery.svelte.js +3 -3
- package/dist/format.js +1 -1
- package/dist/hooks/use3DModels.svelte.js +35 -35
- package/dist/hooks/useConfigFrames.svelte.js +2 -7
- package/dist/hooks/useDrawAPI.svelte.js +1 -1
- package/dist/hooks/useFramelessComponents.svelte.js +1 -1
- package/dist/hooks/useFrames.svelte.js +63 -56
- package/dist/hooks/useGeometries.svelte.js +1 -1
- package/dist/hooks/useLinked.svelte.js +5 -4
- package/dist/hooks/usePartConfig.svelte.js +16 -17
- package/dist/hooks/usePointcloudObjects.svelte.js +4 -4
- package/dist/hooks/usePointclouds.svelte.js +2 -4
- package/dist/hooks/usePose.svelte.js +3 -7
- package/dist/hooks/useResizable.svelte.js +4 -3
- package/dist/hooks/useSettings.svelte.js +2 -2
- package/dist/hooks/useWeblabs.svelte.js +3 -2
- package/dist/hooks/useWorldState.svelte.js +6 -3
- package/dist/loaders/pcd/index.js +2 -1
- package/dist/loaders/pcd/worker.inline.d.ts +1 -1
- package/dist/loaders/pcd/worker.inline.js +1 -1
- package/dist/loaders/pcd/worker.js +1 -1
- package/dist/snapshot.js +7 -5
- package/dist/three/InstancedArrows/InstancedArrows.js +1 -1
- package/dist/three/InstancedArrows/box.js +1 -1
- package/dist/three/InstancedArrows/raycast.js +12 -12
- package/package.json +19 -11
- /package/dist/components/{Arrows → Entities/Arrows}/ArrowGroups.svelte.d.ts +0 -0
- /package/dist/components/{Entities.svelte.d.ts → Entities/Entities.svelte.d.ts} +0 -0
- /package/dist/components/{Frame.svelte.d.ts → Entities/Frame.svelte.d.ts} +0 -0
- /package/dist/components/{GLTF.svelte.d.ts → Entities/GLTF.svelte.d.ts} +0 -0
- /package/dist/components/{Geometry.svelte.d.ts → Entities/Geometry.svelte.d.ts} +0 -0
- /package/dist/components/{Label.svelte.d.ts → Entities/Label.svelte.d.ts} +0 -0
- /package/dist/components/{Line.svelte.d.ts → Entities/Line.svelte.d.ts} +0 -0
- /package/dist/components/{LineDots.svelte → Entities/LineDots.svelte} +0 -0
- /package/dist/components/{LineDots.svelte.d.ts → Entities/LineDots.svelte.d.ts} +0 -0
- /package/dist/components/{LineGeometry.svelte → Entities/LineGeometry.svelte} +0 -0
- /package/dist/components/{LineGeometry.svelte.d.ts → Entities/LineGeometry.svelte.d.ts} +0 -0
- /package/dist/components/{Points.svelte.d.ts → Entities/Points.svelte.d.ts} +0 -0
- /package/dist/components/{Pose.svelte.d.ts → Entities/Pose.svelte.d.ts} +0 -0
|
@@ -81,16 +81,20 @@ const decodeGzip = async (params) => {
|
|
|
81
81
|
};
|
|
82
82
|
export const snapshotDropper = async (params) => {
|
|
83
83
|
switch (params.extension) {
|
|
84
|
-
case 'json':
|
|
84
|
+
case 'json': {
|
|
85
85
|
return decodeJson(params);
|
|
86
|
-
|
|
86
|
+
}
|
|
87
|
+
case 'pb': {
|
|
87
88
|
return decodeBinary(params);
|
|
88
|
-
|
|
89
|
+
}
|
|
90
|
+
case 'pb.gz': {
|
|
89
91
|
return decodeGzip(params);
|
|
90
|
-
|
|
92
|
+
}
|
|
93
|
+
default: {
|
|
91
94
|
return {
|
|
92
95
|
success: false,
|
|
93
96
|
error: new FileDropperError(`Only ${Extensions.JSON}, ${Extensions.PB} and ${Extensions.PB_GZ} snapshot files are supported.`),
|
|
94
97
|
};
|
|
98
|
+
}
|
|
95
99
|
}
|
|
96
100
|
};
|
|
@@ -4,14 +4,17 @@ import { plyDropper } from './ply-dropper';
|
|
|
4
4
|
import { snapshotDropper } from './snapshot-dropper';
|
|
5
5
|
const createFileDropper = (extension, prefix) => {
|
|
6
6
|
switch (prefix) {
|
|
7
|
-
case Prefixes.Snapshot:
|
|
7
|
+
case Prefixes.Snapshot: {
|
|
8
8
|
return snapshotDropper;
|
|
9
|
+
}
|
|
9
10
|
}
|
|
10
11
|
switch (extension) {
|
|
11
|
-
case Extensions.PCD:
|
|
12
|
+
case Extensions.PCD: {
|
|
12
13
|
return pcdDropper;
|
|
13
|
-
|
|
14
|
+
}
|
|
15
|
+
case Extensions.PLY: {
|
|
14
16
|
return plyDropper;
|
|
17
|
+
}
|
|
15
18
|
}
|
|
16
19
|
return undefined;
|
|
17
20
|
};
|
|
@@ -74,11 +77,11 @@ export const useFileDrop = (onSuccess, onError) => {
|
|
|
74
77
|
prefix,
|
|
75
78
|
content,
|
|
76
79
|
});
|
|
77
|
-
if (
|
|
78
|
-
|
|
80
|
+
if (result.success) {
|
|
81
|
+
onSuccess(result);
|
|
79
82
|
}
|
|
80
83
|
else {
|
|
81
|
-
|
|
84
|
+
handleError(result.error.message);
|
|
82
85
|
}
|
|
83
86
|
});
|
|
84
87
|
readFile(file, reader, extension);
|
|
@@ -245,16 +245,16 @@
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
$effect(() => {
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
globalThis.addEventListener('keydown', onkeydown)
|
|
249
|
+
globalThis.addEventListener('keyup', onkeyup)
|
|
250
250
|
dom.addEventListener('pointerdown', onpointerdown)
|
|
251
251
|
dom.addEventListener('pointermove', onpointermove)
|
|
252
252
|
dom.addEventListener('pointerup', onpointerup)
|
|
253
253
|
dom.addEventListener('pointerleave', onpointerleave)
|
|
254
254
|
|
|
255
255
|
return () => {
|
|
256
|
-
|
|
257
|
-
|
|
256
|
+
globalThis.removeEventListener('keydown', onkeydown)
|
|
257
|
+
globalThis.removeEventListener('keyup', onkeyup)
|
|
258
258
|
dom.removeEventListener('pointerdown', onpointerdown)
|
|
259
259
|
dom.removeEventListener('pointermove', onpointermove)
|
|
260
260
|
dom.removeEventListener('pointerup', onpointerup)
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
import { parsePcdInWorker } from '../lib'
|
|
3
3
|
import { traits, useWorld } from '../ecs'
|
|
4
4
|
import { createBufferGeometry } from '../attribute'
|
|
5
|
-
import type { Entity } from 'koota'
|
|
5
|
+
import type { ConfigurableTrait, Entity } from 'koota'
|
|
6
6
|
|
|
7
7
|
interface Props {
|
|
8
8
|
data: Uint8Array
|
|
9
|
+
name?: string
|
|
10
|
+
renderOrder?: number
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
let { data }: Props = $props()
|
|
13
|
+
let { data, name, renderOrder }: Props = $props()
|
|
12
14
|
|
|
13
15
|
const world = useWorld()
|
|
14
16
|
|
|
@@ -18,11 +20,17 @@
|
|
|
18
20
|
parsePcdInWorker(data).then(({ positions, colors }) => {
|
|
19
21
|
const geometry = createBufferGeometry(positions, colors)
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
traits.Name('Random points'),
|
|
23
|
+
const entityTraits: ConfigurableTrait[] = [
|
|
24
|
+
traits.Name(name ?? 'Random points'),
|
|
23
25
|
traits.Points,
|
|
24
|
-
traits.BufferGeometry(geometry)
|
|
25
|
-
|
|
26
|
+
traits.BufferGeometry(geometry),
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
if (renderOrder) {
|
|
30
|
+
entityTraits.push(traits.RenderOrder(renderOrder))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
entity = world.spawn(...entityTraits)
|
|
26
34
|
})
|
|
27
35
|
|
|
28
36
|
return () => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { ShaderMaterial, Vector3 } from 'three'
|
|
3
3
|
import { T } from '@threlte/core'
|
|
4
4
|
import { Environment, Grid, interactivity, PerfMonitor, PortalTarget } from '@threlte/extras'
|
|
5
|
-
import Entities from './Entities.svelte'
|
|
5
|
+
import Entities from './Entities/Entities.svelte'
|
|
6
6
|
import Selected from './Selected.svelte'
|
|
7
7
|
import Focus from './Focus.svelte'
|
|
8
8
|
import StaticGeometries from './StaticGeometries.svelte'
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
import MeasureTool from './MeasureTool/MeasureTool.svelte'
|
|
18
18
|
import PointerMissBox from './PointerMissBox.svelte'
|
|
19
19
|
import BatchedArrows from './BatchedArrows.svelte'
|
|
20
|
-
import Arrows from './Arrows/ArrowGroups.svelte'
|
|
21
20
|
import hdrImage from '../assets/ferndale_studio_11_1k.hdr'
|
|
22
21
|
|
|
23
22
|
interface Props {
|
|
@@ -103,7 +102,6 @@
|
|
|
103
102
|
|
|
104
103
|
<Entities />
|
|
105
104
|
<BatchedArrows />
|
|
106
|
-
<Arrows />
|
|
107
105
|
</T.Group>
|
|
108
106
|
|
|
109
107
|
{@render children?.()}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { PressedKeys } from 'runed'
|
|
13
13
|
import { quaternionToPose, vector3ToPose } from '../transform'
|
|
14
14
|
import { Quaternion, Vector3 } from 'three'
|
|
15
|
-
import Frame from './Frame.svelte'
|
|
15
|
+
import Frame from './Entities/Frame.svelte'
|
|
16
16
|
import { useSettings } from '../hooks/useSettings.svelte'
|
|
17
17
|
import { useWorld, traits } from '../ecs'
|
|
18
18
|
import type { Entity } from 'koota'
|
|
@@ -13,12 +13,10 @@
|
|
|
13
13
|
|
|
14
14
|
<svelte:window
|
|
15
15
|
onkeydown={(event) => {
|
|
16
|
-
if (event.metaKey) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
partConfig.save()
|
|
21
|
-
}
|
|
16
|
+
if (event.metaKey && event.key.toLowerCase() === 's') {
|
|
17
|
+
event.preventDefault()
|
|
18
|
+
event.stopImmediatePropagation()
|
|
19
|
+
partConfig.save()
|
|
22
20
|
}
|
|
23
21
|
}}
|
|
24
22
|
/>
|
|
@@ -285,19 +285,17 @@
|
|
|
285
285
|
<Switch
|
|
286
286
|
on={isWidgetOpen}
|
|
287
287
|
on:change={(event) => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
}
|
|
288
|
+
settings.current.openCameraWidgets = event.detail
|
|
289
|
+
? {
|
|
290
|
+
...settings.current.openCameraWidgets,
|
|
291
|
+
[partID.current]: [...currentRobotCameraWidgets, camera.name],
|
|
292
|
+
}
|
|
293
|
+
: {
|
|
294
|
+
...settings.current.openCameraWidgets,
|
|
295
|
+
[partID.current]: currentRobotCameraWidgets.filter(
|
|
296
|
+
(widget) => widget !== camera.name
|
|
297
|
+
),
|
|
298
|
+
}
|
|
301
299
|
}}
|
|
302
300
|
/>
|
|
303
301
|
</div>
|
|
@@ -31,12 +31,14 @@
|
|
|
31
31
|
let fpsInterval: ReturnType<typeof setInterval> | undefined
|
|
32
32
|
let fpsCounterActive = false
|
|
33
33
|
|
|
34
|
+
const cleanup = () => {
|
|
35
|
+
if (fpsInterval) clearInterval(fpsInterval)
|
|
36
|
+
fpsCounterActive = false
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
// Cleanup on destroy
|
|
35
40
|
$effect(() => {
|
|
36
|
-
return
|
|
37
|
-
if (fpsInterval) clearInterval(fpsInterval)
|
|
38
|
-
fpsCounterActive = false
|
|
39
|
-
}
|
|
41
|
+
return cleanup
|
|
40
42
|
})
|
|
41
43
|
|
|
42
44
|
const onMediaLoad = (e: Event) => {
|
|
@@ -89,8 +91,8 @@
|
|
|
89
91
|
resolutions = options.map((opt) => ({ width: opt.width, height: opt.height }))
|
|
90
92
|
isLoading = false
|
|
91
93
|
})
|
|
92
|
-
.catch((
|
|
93
|
-
error =
|
|
94
|
+
.catch((error_) => {
|
|
95
|
+
error = error_ instanceof Error ? error_.message : 'Failed to get stream options'
|
|
94
96
|
isLoading = false
|
|
95
97
|
})
|
|
96
98
|
}
|
|
@@ -101,13 +103,13 @@
|
|
|
101
103
|
if (!target.value || !streamClient) return
|
|
102
104
|
|
|
103
105
|
const [w, h] = target.value.split('x').map(Number)
|
|
104
|
-
if (isNaN(w) || isNaN(h)) return
|
|
106
|
+
if (Number.isNaN(w) || Number.isNaN(h)) return
|
|
105
107
|
|
|
106
108
|
try {
|
|
107
109
|
await streamClient.setOptions(name, w, h)
|
|
108
110
|
error = undefined
|
|
109
|
-
} catch (
|
|
110
|
-
error =
|
|
111
|
+
} catch (error_) {
|
|
112
|
+
error = error_ instanceof Error ? error_.message : 'Failed to set resolution'
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
115
|
</script>
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
let {
|
|
25
25
|
armName,
|
|
26
26
|
gripperName,
|
|
27
|
-
scaleFactor = 1
|
|
27
|
+
scaleFactor = 1,
|
|
28
28
|
hand = 'right',
|
|
29
29
|
rotationEnabled = true,
|
|
30
30
|
}: Props = $props()
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
|
|
34
34
|
// Capture initial prop values — parent uses {#key} to force remount on changes.
|
|
35
35
|
// Wrapped in an IIFE to avoid Svelte's state_referenced_locally warning.
|
|
36
|
+
// eslint-disable-next-line unicorn/no-unreadable-iife
|
|
36
37
|
const { initialHand, initialGripperName } = (() => ({
|
|
37
38
|
initialHand: hand,
|
|
38
39
|
initialGripperName: gripperName,
|
|
@@ -101,16 +102,14 @@
|
|
|
101
102
|
const currentSession = $session
|
|
102
103
|
if (!currentSession) return
|
|
103
104
|
|
|
104
|
-
const inputSource =
|
|
105
|
-
(s) => s.handedness === initialHand
|
|
106
|
-
)
|
|
105
|
+
const inputSource = [...currentSession.inputSources].find((s) => s.handedness === initialHand)
|
|
107
106
|
if (!inputSource?.gamepad?.hapticActuators?.length) return
|
|
108
107
|
|
|
109
108
|
const actuator = inputSource.gamepad.hapticActuators[0]
|
|
110
109
|
if ('pulse' in actuator) {
|
|
111
110
|
actuator
|
|
112
111
|
.pulse(intensity, duration)
|
|
113
|
-
.catch((
|
|
112
|
+
.catch((error) => console.warn('[ArmTeleop] Haptic pulse failed:', error))
|
|
114
113
|
}
|
|
115
114
|
}
|
|
116
115
|
|
|
@@ -143,9 +142,7 @@
|
|
|
143
142
|
const currentSession = $session
|
|
144
143
|
if (!currentSession || !controller.current) return
|
|
145
144
|
|
|
146
|
-
const inputSource =
|
|
147
|
-
(s) => s.handedness === initialHand
|
|
148
|
-
)
|
|
145
|
+
const inputSource = [...currentSession.inputSources].find((s) => s.handedness === initialHand)
|
|
149
146
|
|
|
150
147
|
if (!inputSource || !inputSource.gamepad) return
|
|
151
148
|
|
|
@@ -166,15 +163,16 @@
|
|
|
166
163
|
if (armClient.current) {
|
|
167
164
|
handleStartControl(controller.current)
|
|
168
165
|
}
|
|
169
|
-
} else if (
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
166
|
+
} else if (
|
|
167
|
+
!isPressed &&
|
|
168
|
+
wasPressed && // Falling Edge: Stop Control
|
|
169
|
+
isControlling
|
|
170
|
+
) {
|
|
171
|
+
isControlling = false
|
|
172
|
+
// Haptic feedback: short pulse on teleop end
|
|
173
|
+
triggerHapticFeedback(0.3, 80)
|
|
174
|
+
// Log final position
|
|
175
|
+
handleStopControl()
|
|
178
176
|
}
|
|
179
177
|
|
|
180
178
|
// 4. Edge Detection - GRIPPER CONTROL (Trigger)
|
|
@@ -186,7 +184,7 @@
|
|
|
186
184
|
clearTimeout(gripperStopTimeout)
|
|
187
185
|
gripperStopTimeout = null
|
|
188
186
|
}
|
|
189
|
-
gripperClient.current.grab().catch((
|
|
187
|
+
gripperClient.current.grab().catch((error) => console.warn('Gripper grab failed:', error))
|
|
190
188
|
} else if (!isTriggerPressed && wasTriggerPressed) {
|
|
191
189
|
// Trigger released: Open gripper, then stop after 1 second
|
|
192
190
|
// Clear any pending stop timeout
|
|
@@ -194,11 +192,13 @@
|
|
|
194
192
|
clearTimeout(gripperStopTimeout)
|
|
195
193
|
gripperStopTimeout = null
|
|
196
194
|
}
|
|
197
|
-
gripperClient.current.open().catch((
|
|
195
|
+
gripperClient.current.open().catch((error) => console.warn('Gripper open failed:', error))
|
|
198
196
|
|
|
199
197
|
// Schedule stop after 1 second
|
|
200
198
|
gripperStopTimeout = setTimeout(() => {
|
|
201
|
-
gripperClient?.current
|
|
199
|
+
gripperClient?.current
|
|
200
|
+
?.stop()
|
|
201
|
+
.catch((error) => console.warn('Gripper stop failed:', error))
|
|
202
202
|
gripperStopTimeout = null
|
|
203
203
|
}, 1000)
|
|
204
204
|
}
|
|
@@ -275,16 +275,16 @@
|
|
|
275
275
|
|
|
276
276
|
// Haptic feedback: short pulse on teleop start
|
|
277
277
|
triggerHapticFeedback(0.5, 100)
|
|
278
|
-
} catch (
|
|
279
|
-
console.error('[ArmTeleop] Failed to start teleop:',
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.error('[ArmTeleop] Failed to start teleop:', error)
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
async function handleStopControl() {
|
|
284
284
|
try {
|
|
285
285
|
await armClient.current!.getEndPosition()
|
|
286
|
-
} catch (
|
|
287
|
-
console.error('[ArmTeleop] Failed to get final position:',
|
|
286
|
+
} catch (error) {
|
|
287
|
+
console.error('[ArmTeleop] Failed to get final position:', error)
|
|
288
288
|
}
|
|
289
289
|
}
|
|
290
290
|
|
|
@@ -361,7 +361,7 @@
|
|
|
361
361
|
lastCommandTime = now
|
|
362
362
|
isSending = true
|
|
363
363
|
|
|
364
|
-
if (isNaN(targetPos.x) || isNaN(targetOV.th)) {
|
|
364
|
+
if (Number.isNaN(targetPos.x) || Number.isNaN(targetOV.th)) {
|
|
365
365
|
console.warn('Teleop Safety: NaN detected', targetPos, targetOV)
|
|
366
366
|
isSending = false
|
|
367
367
|
return
|
|
@@ -387,12 +387,12 @@
|
|
|
387
387
|
if (client) {
|
|
388
388
|
client
|
|
389
389
|
.doCommand(VIAM.Struct.fromJson(command))
|
|
390
|
-
.catch((
|
|
391
|
-
console.warn('Move failed:',
|
|
390
|
+
.catch((error) => {
|
|
391
|
+
console.warn('Move failed:', error)
|
|
392
392
|
errorTimeout = Date.now() + ERROR_COOLDOWN
|
|
393
393
|
triggerHapticFeedback(0.8, 200)
|
|
394
394
|
lastErrorHapticTime = Date.now()
|
|
395
|
-
showArmErrorToast(
|
|
395
|
+
showArmErrorToast(error)
|
|
396
396
|
})
|
|
397
397
|
.finally(() => {
|
|
398
398
|
isSending = false
|
|
@@ -409,12 +409,12 @@
|
|
|
409
409
|
oZ: targetOV.z,
|
|
410
410
|
theta: (targetOV.th * 180) / Math.PI,
|
|
411
411
|
})
|
|
412
|
-
.catch((
|
|
413
|
-
console.warn('Move failed:',
|
|
412
|
+
.catch((error) => {
|
|
413
|
+
console.warn('Move failed:', error)
|
|
414
414
|
errorTimeout = Date.now() + ERROR_COOLDOWN
|
|
415
415
|
triggerHapticFeedback(0.8, 200)
|
|
416
416
|
lastErrorHapticTime = Date.now()
|
|
417
|
-
showArmErrorToast(
|
|
417
|
+
showArmErrorToast(error)
|
|
418
418
|
})
|
|
419
419
|
.finally(() => {
|
|
420
420
|
isSending = false
|
|
@@ -434,8 +434,8 @@
|
|
|
434
434
|
// Use moveToPosition to return to the saved pose
|
|
435
435
|
await armClient.current.moveToPosition(savedPose)
|
|
436
436
|
xrToast.success('Returned to saved position')
|
|
437
|
-
} catch (
|
|
438
|
-
console.error('[ArmTeleop] Failed to return to saved pose:',
|
|
437
|
+
} catch (error) {
|
|
438
|
+
console.error('[ArmTeleop] Failed to return to saved pose:', error)
|
|
439
439
|
xrToast.danger('Failed to return to position')
|
|
440
440
|
} finally {
|
|
441
441
|
isReturning = false
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
// Force play to ensure stream is active
|
|
89
|
-
video.play().catch((
|
|
89
|
+
video.play().catch((error) => console.warn('Video play failed:', error))
|
|
90
90
|
ready = true
|
|
91
91
|
|
|
92
92
|
// PROFILING: Video ready
|
|
@@ -153,28 +153,26 @@
|
|
|
153
153
|
const captureTime = metadata.captureTime || metadata.mediaTime
|
|
154
154
|
const presentationTime = metadata.presentationTime || metadata.expectedDisplayTime
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
metrics.totalLatency = captureToPresentMs
|
|
177
|
-
}
|
|
156
|
+
// The times might be in different epochs, so we can only reliably calculate
|
|
157
|
+
// the difference between capture and presentation
|
|
158
|
+
if (captureTime && presentationTime) {
|
|
159
|
+
// Encoding + Network + Decoding time
|
|
160
|
+
const captureToPresentMs = (presentationTime - captureTime) / 1000
|
|
161
|
+
metrics.captureToPresent = captureToPresentMs
|
|
162
|
+
|
|
163
|
+
// Time since video element presented the frame to when we render it
|
|
164
|
+
// This should be very small (< 16ms ideally)
|
|
165
|
+
const presentMsRelative = presentationTime / 1000
|
|
166
|
+
const timeSincePresentation = now - presentMsRelative
|
|
167
|
+
|
|
168
|
+
// Only use this if the time domains seem aligned (value is reasonable)
|
|
169
|
+
if (Math.abs(timeSincePresentation) < 1000) {
|
|
170
|
+
metrics.presentToRender = timeSincePresentation
|
|
171
|
+
metrics.totalLatency = captureToPresentMs + timeSincePresentation
|
|
172
|
+
} else {
|
|
173
|
+
// Time domains don't align - just use capture to present as approximation
|
|
174
|
+
metrics.presentToRender = undefined
|
|
175
|
+
metrics.totalLatency = captureToPresentMs
|
|
178
176
|
}
|
|
179
177
|
}
|
|
180
178
|
}
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
return jointLimits.map((limit, index) => {
|
|
37
37
|
const current = currentPositions[index] ?? 0
|
|
38
38
|
const range = limit.max - limit.min
|
|
39
|
-
const percentage = range
|
|
39
|
+
const percentage = range === 0 ? 50 : ((current - limit.min) / range) * 100
|
|
40
40
|
|
|
41
41
|
let status: 'safe' | 'caution' | 'danger'
|
|
42
42
|
if (percentage < 10 || percentage > 90) {
|
|
@@ -122,6 +122,18 @@
|
|
|
122
122
|
ctx.stroke()
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
const getJointColor = (status: 'safe' | 'caution' | 'danger') => {
|
|
126
|
+
if (status === 'danger') {
|
|
127
|
+
return '#ff4444'
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (status === 'caution') {
|
|
131
|
+
return '#ffaa00'
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return '#44ff44'
|
|
135
|
+
}
|
|
136
|
+
|
|
125
137
|
// Render joint data to canvas
|
|
126
138
|
function renderJointLimits(
|
|
127
139
|
ctx: CanvasRenderingContext2D,
|
|
@@ -132,7 +144,8 @@
|
|
|
132
144
|
const s = RESOLUTION_SCALE
|
|
133
145
|
const rowHeight = (height - HEADER_HEIGHT) / joints.length
|
|
134
146
|
|
|
135
|
-
|
|
147
|
+
let index = 0
|
|
148
|
+
for (const joint of joints) {
|
|
136
149
|
const y = HEADER_HEIGHT + index * rowHeight
|
|
137
150
|
|
|
138
151
|
// Background row
|
|
@@ -157,8 +170,7 @@
|
|
|
157
170
|
|
|
158
171
|
// Progress bar fill (colored by status)
|
|
159
172
|
const fillWidth = barWidth * (joint.percentage / 100)
|
|
160
|
-
ctx.fillStyle =
|
|
161
|
-
joint.status === 'danger' ? '#ff4444' : joint.status === 'caution' ? '#ffaa00' : '#44ff44'
|
|
173
|
+
ctx.fillStyle = getJointColor(joint.status)
|
|
162
174
|
ctx.fillRect(barX, barY, fillWidth, barHeight)
|
|
163
175
|
|
|
164
176
|
// Progress bar border
|
|
@@ -174,7 +186,9 @@
|
|
|
174
186
|
barX + barWidth + 20 * s,
|
|
175
187
|
y + rowHeight / 2
|
|
176
188
|
)
|
|
177
|
-
|
|
189
|
+
|
|
190
|
+
index += 1
|
|
191
|
+
}
|
|
178
192
|
}
|
|
179
193
|
|
|
180
194
|
// Update canvas when joint data changes
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
let resources: ReturnType<typeof useResourceNames> | undefined
|
|
22
22
|
try {
|
|
23
23
|
resources = useResourceNames(() => partID.current)
|
|
24
|
-
} catch (
|
|
25
|
-
console.warn('Failed to get resources, robot may not be connected yet:',
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.warn('Failed to get resources, robot may not be connected yet:', error)
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// Get available arms and grippers
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
const currentConfig = $derived(settings.current.xrController[selectedHand])
|
|
42
42
|
|
|
43
43
|
// Local form state (editable) — synced from currentConfig via effect
|
|
44
|
-
let formArmName = $state<string
|
|
45
|
-
let formGripperName = $state<string
|
|
46
|
-
let formScaleFactor = $state<number>(1
|
|
44
|
+
let formArmName = $state<string>()
|
|
45
|
+
let formGripperName = $state<string>()
|
|
46
|
+
let formScaleFactor = $state<number>(1)
|
|
47
47
|
let formRotationEnabled = $state<boolean>(true)
|
|
48
48
|
|
|
49
49
|
// Sync form state when selected hand or config changes
|
|
@@ -59,9 +59,9 @@
|
|
|
59
59
|
const CANVAS_WIDTH = 600
|
|
60
60
|
const CANVAS_HEIGHT = 500
|
|
61
61
|
|
|
62
|
-
let canvas
|
|
63
|
-
let texture
|
|
64
|
-
let geometry
|
|
62
|
+
let canvas = $state<HTMLCanvasElement>()
|
|
63
|
+
let texture = $state<CanvasTexture>()
|
|
64
|
+
let geometry = $state<PlaneGeometry>()
|
|
65
65
|
|
|
66
66
|
// Initialize canvas
|
|
67
67
|
$effect(() => {
|
|
@@ -90,14 +90,14 @@
|
|
|
90
90
|
let uiElements: UIElement[] = []
|
|
91
91
|
|
|
92
92
|
// Mesh ref for raycasting
|
|
93
|
-
let meshRef = $state<Mesh
|
|
93
|
+
let meshRef = $state<Mesh>()
|
|
94
94
|
|
|
95
95
|
// Controller interaction
|
|
96
96
|
const rightController = useController('right')
|
|
97
97
|
const leftController = useController('left')
|
|
98
98
|
|
|
99
99
|
// Interaction state
|
|
100
|
-
let hoveredElement = $state<UIElement
|
|
100
|
+
let hoveredElement = $state<UIElement>()
|
|
101
101
|
let lastButtonPressed = $state(false)
|
|
102
102
|
|
|
103
103
|
// Handle click on UI element
|
|
@@ -340,7 +340,7 @@
|
|
|
340
340
|
ctx.fillRect(sliderX, sliderY, sliderWidth, sliderHeight)
|
|
341
341
|
|
|
342
342
|
// Slider thumb
|
|
343
|
-
const thumbPos = ((formScaleFactor - 0.1) / (3
|
|
343
|
+
const thumbPos = ((formScaleFactor - 0.1) / (3 - 0.1)) * sliderWidth
|
|
344
344
|
ctx.fillStyle = '#4CAF50'
|
|
345
345
|
ctx.beginPath()
|
|
346
346
|
ctx.arc(sliderX + thumbPos, sliderY + sliderHeight / 2, 12, 0, Math.PI * 2)
|
|
@@ -428,12 +428,13 @@
|
|
|
428
428
|
}
|
|
429
429
|
})
|
|
430
430
|
|
|
431
|
-
|
|
431
|
+
const cleanup = () => {
|
|
432
|
+
texture?.dispose()
|
|
433
|
+
geometry?.dispose()
|
|
434
|
+
}
|
|
435
|
+
|
|
432
436
|
$effect(() => {
|
|
433
|
-
return
|
|
434
|
-
texture?.dispose()
|
|
435
|
-
geometry?.dispose()
|
|
436
|
-
}
|
|
437
|
+
return cleanup
|
|
437
438
|
})
|
|
438
439
|
</script>
|
|
439
440
|
|
|
@@ -105,8 +105,8 @@
|
|
|
105
105
|
max="3.0"
|
|
106
106
|
step="0.1"
|
|
107
107
|
value={config.left.scaleFactor}
|
|
108
|
-
style="--value: {((config.left.scaleFactor - 0.1) / (3
|
|
109
|
-
oninput={(e) => updateConfig('left', 'scaleFactor', parseFloat(e.currentTarget.value))}
|
|
108
|
+
style="--value: {((config.left.scaleFactor - 0.1) / (3 - 0.1)) * 100}%"
|
|
109
|
+
oninput={(e) => updateConfig('left', 'scaleFactor', Number.parseFloat(e.currentTarget.value))}
|
|
110
110
|
/>
|
|
111
111
|
</label>
|
|
112
112
|
|
|
@@ -166,8 +166,9 @@
|
|
|
166
166
|
max="3.0"
|
|
167
167
|
step="0.1"
|
|
168
168
|
value={config.right.scaleFactor}
|
|
169
|
-
style="--value: {((config.right.scaleFactor - 0.1) / (3
|
|
170
|
-
oninput={(e) =>
|
|
169
|
+
style="--value: {((config.right.scaleFactor - 0.1) / (3 - 0.1)) * 100}%"
|
|
170
|
+
oninput={(e) =>
|
|
171
|
+
updateConfig('right', 'scaleFactor', Number.parseFloat(e.currentTarget.value))}
|
|
171
172
|
/>
|
|
172
173
|
</label>
|
|
173
174
|
|