@viamrobotics/motion-tools 1.13.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/FrameConfigUpdater.svelte.js +1 -1
- package/dist/attribute.js +10 -2
- 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/buffer.d.ts +56 -7
- package/dist/buffer.js +70 -12
- 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} +7 -3
- package/dist/components/Entities/Frame.svelte +86 -0
- package/dist/components/{Frame.svelte.d.ts → Entities/Frame.svelte.d.ts} +1 -0
- package/dist/components/{GLTF.svelte → Entities/GLTF.svelte} +5 -5
- package/dist/components/Entities/Geometry.svelte +75 -0
- package/dist/components/Entities/Geometry.svelte.d.ts +10 -0
- package/dist/components/{Label.svelte → Entities/Label.svelte} +1 -1
- package/dist/components/Entities/Line.svelte +90 -0
- package/dist/components/Entities/Mesh.svelte +130 -0
- package/dist/components/Entities/Mesh.svelte.d.ts +4 -0
- 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/Selected.svelte +2 -0
- 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/left-pane/buildTree.js +15 -0
- 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/traits.d.ts +2 -19
- package/dist/ecs/traits.js +33 -6
- package/dist/ecs/useQuery.svelte.js +3 -3
- package/dist/format.js +1 -1
- package/dist/hooks/use3DModels.svelte.js +36 -38
- 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 +62 -56
- package/dist/hooks/useGeometries.svelte.js +59 -36
- package/dist/hooks/useLinked.svelte.js +5 -4
- package/dist/hooks/usePartConfig.svelte.js +16 -17
- package/dist/hooks/usePointcloudObjects.svelte.js +107 -62
- package/dist/hooks/usePointclouds.svelte.js +50 -32
- 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 +31 -28
- 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/metadata.d.ts +22 -0
- package/dist/metadata.js +66 -0
- package/dist/snapshot.d.ts +20 -0
- package/dist/snapshot.js +72 -28
- 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/dist/three/OBBHelper.d.ts +3 -2
- package/dist/three/OBBHelper.js +17 -5
- package/package.json +19 -11
- package/dist/WorldObject.svelte.d.ts +0 -27
- package/dist/WorldObject.svelte.js +0 -114
- package/dist/components/Frame.svelte +0 -89
- package/dist/components/Geometry.svelte +0 -211
- package/dist/components/Geometry.svelte.d.ts +0 -19
- package/dist/components/Line.svelte +0 -43
- /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/{GLTF.svelte.d.ts → Entities/GLTF.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
|
@@ -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
|
|
|
@@ -132,7 +132,8 @@
|
|
|
132
132
|
const iconCenterX = accentBarWidth + 30
|
|
133
133
|
const textStartX = accentBarWidth + 56
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
let index = 0
|
|
136
|
+
for (const toast of toasts) {
|
|
136
137
|
const y = index * (TOAST_HEIGHT + TOAST_GAP)
|
|
137
138
|
const style = VARIANT_STYLES[toast.variant]
|
|
138
139
|
|
|
@@ -165,7 +166,9 @@
|
|
|
165
166
|
ctx.font = '28px sans-serif'
|
|
166
167
|
ctx.textBaseline = 'middle'
|
|
167
168
|
ctx.fillText(toast.message, textStartX, y + TOAST_HEIGHT / 2)
|
|
168
|
-
|
|
169
|
+
|
|
170
|
+
index += 1
|
|
171
|
+
}
|
|
169
172
|
|
|
170
173
|
texture.needsUpdate = true
|
|
171
174
|
}
|
|
@@ -189,12 +192,14 @@
|
|
|
189
192
|
renderToasts(toasts)
|
|
190
193
|
})
|
|
191
194
|
|
|
195
|
+
const dispose = () => {
|
|
196
|
+
texture.dispose()
|
|
197
|
+
untrack(() => geometry?.dispose())
|
|
198
|
+
}
|
|
199
|
+
|
|
192
200
|
// Cleanup
|
|
193
201
|
$effect(() => {
|
|
194
|
-
return
|
|
195
|
-
texture.dispose()
|
|
196
|
-
untrack(() => geometry?.dispose())
|
|
197
|
-
}
|
|
202
|
+
return dispose
|
|
198
203
|
})
|
|
199
204
|
</script>
|
|
200
205
|
|
package/dist/ecs/relations.d.ts
CHANGED
package/dist/ecs/relations.js
CHANGED
package/dist/ecs/traits.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GLTF as ThreeGltf } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
2
|
+
import { type Entity } from 'koota';
|
|
2
3
|
import { BufferGeometry as ThreeBufferGeometry } from 'three';
|
|
3
4
|
import { Geometry as ViamGeometry } from '@viamrobotics/sdk';
|
|
4
5
|
export declare const Name: import("koota").Trait<() => string>;
|
|
@@ -178,22 +179,4 @@ export declare const Geometry: (geometry: ViamGeometry) => import("koota").Trait
|
|
|
178
179
|
}>, Partial<{
|
|
179
180
|
r: number;
|
|
180
181
|
}>] | [import("koota").Trait<() => ThreeBufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>>, ThreeBufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>];
|
|
181
|
-
export declare const
|
|
182
|
-
x: number;
|
|
183
|
-
y: number;
|
|
184
|
-
z: number;
|
|
185
|
-
} | import("koota").Trait<{
|
|
186
|
-
x: number;
|
|
187
|
-
y: number;
|
|
188
|
-
z: number;
|
|
189
|
-
}>)[] | ({
|
|
190
|
-
r: number;
|
|
191
|
-
l: number;
|
|
192
|
-
} | import("koota").Trait<{
|
|
193
|
-
l: number;
|
|
194
|
-
r: number;
|
|
195
|
-
}>)[] | ({
|
|
196
|
-
r: number;
|
|
197
|
-
} | import("koota").Trait<{
|
|
198
|
-
r: number;
|
|
199
|
-
}>)[];
|
|
182
|
+
export declare const updateGeometryTrait: (entity: Entity, geometry?: ViamGeometry) => void;
|
package/dist/ecs/traits.js
CHANGED
|
@@ -126,18 +126,45 @@ export const Geometry = (geometry) => {
|
|
|
126
126
|
}
|
|
127
127
|
return ReferenceFrame;
|
|
128
128
|
};
|
|
129
|
-
export const
|
|
129
|
+
export const updateGeometryTrait = (entity, geometry) => {
|
|
130
|
+
if (!geometry) {
|
|
131
|
+
entity.remove(Box, Capsule, Sphere, BufferGeometry);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
130
134
|
if (geometry.geometryType.case === 'box') {
|
|
131
|
-
|
|
135
|
+
if (entity.has(Box)) {
|
|
136
|
+
entity.set(Box, createBox(geometry.geometryType.value));
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
entity.remove(Capsule, Sphere, BufferGeometry);
|
|
140
|
+
entity.add(Box(createBox(geometry.geometryType.value)));
|
|
141
|
+
}
|
|
132
142
|
}
|
|
133
143
|
else if (geometry.geometryType.case === 'capsule') {
|
|
134
|
-
|
|
144
|
+
if (entity.has(Capsule)) {
|
|
145
|
+
entity.set(Capsule, createCapsule(geometry.geometryType.value));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
entity.remove(Box, Sphere, BufferGeometry);
|
|
149
|
+
entity.add(Capsule(createCapsule(geometry.geometryType.value)));
|
|
150
|
+
}
|
|
135
151
|
}
|
|
136
152
|
else if (geometry.geometryType.case === 'sphere') {
|
|
137
|
-
|
|
153
|
+
if (entity.has(Sphere)) {
|
|
154
|
+
entity.set(Sphere, createSphere(geometry.geometryType.value));
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
entity.remove(Box, Capsule, BufferGeometry);
|
|
158
|
+
entity.add(Sphere(createSphere(geometry.geometryType.value)));
|
|
159
|
+
}
|
|
138
160
|
}
|
|
139
161
|
else if (geometry.geometryType.case === 'mesh') {
|
|
140
|
-
|
|
162
|
+
if (entity.has(BufferGeometry)) {
|
|
163
|
+
entity.set(BufferGeometry, parsePlyInput(geometry.geometryType.value.mesh));
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
entity.remove(Box, Sphere, Capsule);
|
|
167
|
+
entity.add(BufferGeometry(parsePlyInput(geometry.geometryType.value.mesh)));
|
|
168
|
+
}
|
|
141
169
|
}
|
|
142
|
-
return [];
|
|
143
170
|
};
|
|
@@ -31,11 +31,11 @@ export function useQuery(...parameters) {
|
|
|
31
31
|
};
|
|
32
32
|
});
|
|
33
33
|
});
|
|
34
|
+
const handler = () => {
|
|
35
|
+
version += 1;
|
|
36
|
+
};
|
|
34
37
|
// Force reattaching event listeners when the world is reset.
|
|
35
38
|
$effect(() => {
|
|
36
|
-
const handler = () => {
|
|
37
|
-
version += 1;
|
|
38
|
-
};
|
|
39
39
|
world[internal].resetSubscriptions.add(handler);
|
|
40
40
|
return () => {
|
|
41
41
|
world[internal].resetSubscriptions.delete(handler);
|
package/dist/format.js
CHANGED
|
@@ -18,48 +18,46 @@ export const provide3DModels = (partID) => {
|
|
|
18
18
|
const clients = $derived(armClients.filter((client) => {
|
|
19
19
|
return arms.current.some((arm) => arm.name === client.current?.name);
|
|
20
20
|
}));
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
const fetch3DModels = async () => {
|
|
22
|
+
const next = {};
|
|
23
|
+
for (const client of clients) {
|
|
24
|
+
if (!client.current)
|
|
25
|
+
continue;
|
|
26
|
+
try {
|
|
27
|
+
const geometries = await client.current.getGeometries();
|
|
28
|
+
if (geometries.length === 0) {
|
|
26
29
|
continue;
|
|
27
|
-
try {
|
|
28
|
-
const geometries = await client.current.getGeometries();
|
|
29
|
-
if (geometries.length === 0) {
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
const geometryLabel = geometries[0].label;
|
|
33
|
-
const prefix = geometryLabel.split(':')[0];
|
|
34
|
-
const models = await client.current.get3DModels();
|
|
35
|
-
if (!(prefix in next)) {
|
|
36
|
-
next[prefix] = {};
|
|
37
|
-
}
|
|
38
|
-
for (const [id, model] of Object.entries(models)) {
|
|
39
|
-
const arrayBuffer = model.mesh.buffer.slice(model.mesh.byteOffset, model.mesh.byteOffset + model.mesh.byteLength);
|
|
40
|
-
const gltfModel = await gltfLoader.parseAsync(arrayBuffer, '');
|
|
41
|
-
next[prefix][id] = gltfModel.scene;
|
|
42
|
-
gltfModel.scene.traverse((object) => {
|
|
43
|
-
if (isInstanceOf(object, 'Mesh')) {
|
|
44
|
-
const { material } = object;
|
|
45
|
-
if (isInstanceOf(material, 'MeshStandardMaterial')) {
|
|
46
|
-
material.roughness = 0.3;
|
|
47
|
-
material.metalness = 0.1;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
current = next;
|
|
53
30
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
31
|
+
const geometryLabel = geometries[0].label;
|
|
32
|
+
const prefix = geometryLabel.split(':')[0];
|
|
33
|
+
const models = await client.current.get3DModels();
|
|
34
|
+
if (!(prefix in next)) {
|
|
35
|
+
next[prefix] = {};
|
|
36
|
+
}
|
|
37
|
+
for (const [id, model] of Object.entries(models)) {
|
|
38
|
+
const arrayBuffer = model.mesh.buffer.slice(model.mesh.byteOffset, model.mesh.byteOffset + model.mesh.byteLength);
|
|
39
|
+
const gltfModel = await gltfLoader.parseAsync(arrayBuffer, '');
|
|
40
|
+
next[prefix][id] = gltfModel.scene;
|
|
41
|
+
gltfModel.scene.traverse((object) => {
|
|
42
|
+
if (isInstanceOf(object, 'Mesh')) {
|
|
43
|
+
const { material } = object;
|
|
44
|
+
if (isInstanceOf(material, 'MeshStandardMaterial')) {
|
|
45
|
+
material.roughness = 0.3;
|
|
46
|
+
material.metalness = 0.1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
57
50
|
}
|
|
51
|
+
current = next;
|
|
58
52
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
53
|
+
catch (error) {
|
|
54
|
+
// some arms may not implement this api yet
|
|
55
|
+
console.warn(`${client.current.name} returned an error: ${error} when getting 3D models`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
$effect(() => {
|
|
60
|
+
const shouldFetchModels = settings.current.isLoaded && settings.current.renderArmModels.includes('model');
|
|
63
61
|
if (shouldFetchModels) {
|
|
64
62
|
fetch3DModels();
|
|
65
63
|
}
|
|
@@ -8,12 +8,7 @@ export const provideConfigFrames = () => {
|
|
|
8
8
|
const environment = useEnvironment();
|
|
9
9
|
const partConfig = usePartConfig();
|
|
10
10
|
$effect(() => {
|
|
11
|
-
|
|
12
|
-
environment.current.viewerMode = 'edit';
|
|
13
|
-
}
|
|
14
|
-
else {
|
|
15
|
-
environment.current.viewerMode = 'monitor';
|
|
16
|
-
}
|
|
11
|
+
environment.current.viewerMode = partConfig.isDirty ? 'edit' : 'monitor';
|
|
17
12
|
});
|
|
18
13
|
const [configFrames, configUnsetFrameNames] = $derived.by(() => {
|
|
19
14
|
const { components } = partConfig.current;
|
|
@@ -74,7 +69,7 @@ export const provideConfigFrames = () => {
|
|
|
74
69
|
}
|
|
75
70
|
}
|
|
76
71
|
}
|
|
77
|
-
return
|
|
72
|
+
return [...validFrames];
|
|
78
73
|
};
|
|
79
74
|
const unsetFrames = $derived([...new Set([...configUnsetFrameNames, ...fragmentUnsetFrameNames])]);
|
|
80
75
|
setContext(key, {
|