@viamrobotics/motion-tools 1.10.0 → 1.11.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.
Files changed (106) hide show
  1. package/dist/HoverUpdater.svelte.d.ts +0 -3
  2. package/dist/HoverUpdater.svelte.js +8 -50
  3. package/dist/WorldObject.svelte.d.ts +27 -0
  4. package/dist/WorldObject.svelte.js +8 -55
  5. package/dist/{draw → buf/draw}/v1/drawing_pb.d.ts +6 -0
  6. package/dist/{draw → buf/draw}/v1/drawing_pb.js +7 -0
  7. package/dist/buf/draw/v1/service_connect.d.ts +122 -0
  8. package/dist/buf/draw/v1/service_connect.js +126 -0
  9. package/dist/buf/draw/v1/service_pb.d.ts +382 -0
  10. package/dist/buf/draw/v1/service_pb.js +612 -0
  11. package/dist/components/App.svelte +0 -1
  12. package/dist/components/Arrows/Arrows.svelte +16 -3
  13. package/dist/components/FileDrop/file-dropper.d.ts +1 -1
  14. package/dist/components/FileDrop/snapshot-dropper.js +1 -1
  15. package/dist/components/FileDrop/useFileDrop.svelte.d.ts +2 -1
  16. package/dist/components/Frame.svelte +1 -1
  17. package/dist/components/Geometry.svelte +113 -71
  18. package/dist/components/Geometry.svelte.d.ts +6 -7
  19. package/dist/components/SceneProviders.svelte +2 -0
  20. package/dist/components/Snapshot.svelte +1 -1
  21. package/dist/components/Snapshot.svelte.d.ts +1 -1
  22. package/dist/components/overlay/Details.svelte +20 -0
  23. package/dist/components/overlay/left-pane/TreeContainer.svelte +0 -2
  24. package/dist/components/overlay/settings/Settings.svelte +51 -0
  25. package/dist/components/overlay/widgets/Camera.svelte +20 -12
  26. package/dist/components/xr/ArmTeleop.svelte +469 -0
  27. package/dist/components/xr/ArmTeleop.svelte.d.ts +10 -0
  28. package/dist/components/xr/CameraFeed.svelte +191 -47
  29. package/dist/components/xr/CameraFeed.svelte.d.ts +7 -0
  30. package/dist/components/xr/Controllers.svelte +45 -38
  31. package/dist/components/xr/Controllers.svelte.d.ts +2 -17
  32. package/dist/components/xr/Hands.svelte +2 -4
  33. package/dist/components/xr/JointLimitsWidget.svelte +209 -0
  34. package/dist/components/xr/JointLimitsWidget.svelte.d.ts +13 -0
  35. package/dist/components/xr/OriginMarker.svelte +1 -15
  36. package/dist/components/xr/XR.svelte +78 -5
  37. package/dist/components/xr/XRConfigPanel.svelte +449 -0
  38. package/dist/components/xr/XRConfigPanel.svelte.d.ts +11 -0
  39. package/dist/components/xr/XRControllerSettings.svelte +240 -0
  40. package/dist/components/xr/XRControllerSettings.svelte.d.ts +3 -0
  41. package/dist/components/xr/XRToast.svelte +215 -0
  42. package/dist/components/xr/XRToast.svelte.d.ts +3 -0
  43. package/dist/components/xr/math.d.ts +14 -0
  44. package/dist/components/xr/math.js +26 -0
  45. package/dist/components/xr/toasts.svelte.d.ts +20 -0
  46. package/dist/components/xr/toasts.svelte.js +32 -0
  47. package/dist/components/xr/useOrigin.svelte.d.ts +2 -2
  48. package/dist/components/xr/useOrigin.svelte.js +4 -4
  49. package/dist/ecs/traits.d.ts +9 -0
  50. package/dist/ecs/traits.js +9 -0
  51. package/dist/ecs/useTrait.svelte.d.ts +3 -3
  52. package/dist/frame.d.ts +0 -3
  53. package/dist/hooks/useArmKinematics.svelte.d.ts +12 -0
  54. package/dist/hooks/useArmKinematics.svelte.js +31 -0
  55. package/dist/hooks/useGeometries.svelte.js +46 -35
  56. package/dist/hooks/useObjectEvents.svelte.js +24 -7
  57. package/dist/hooks/usePartConfig.svelte.d.ts +0 -35
  58. package/dist/hooks/usePartConfig.svelte.js +2 -2
  59. package/dist/hooks/usePointcloudObjects.svelte.js +44 -63
  60. package/dist/hooks/usePointclouds.svelte.js +10 -6
  61. package/dist/hooks/usePose.svelte.js +4 -1
  62. package/dist/hooks/useResourceByName.svelte.d.ts +7 -0
  63. package/dist/hooks/useResourceByName.svelte.js +2 -2
  64. package/dist/hooks/useSettings.svelte.d.ts +14 -0
  65. package/dist/hooks/useSettings.svelte.js +10 -0
  66. package/dist/hooks/useWorldState.svelte.d.ts +0 -8
  67. package/dist/lib.d.ts +1 -3
  68. package/dist/lib.js +1 -3
  69. package/dist/snapshot.d.ts +2 -2
  70. package/dist/snapshot.js +2 -2
  71. package/dist/three/InstancedArrows/raycast.d.ts +2 -4
  72. package/dist/three/InstancedArrows/raycast.js +5 -5
  73. package/dist/transform.js +1 -0
  74. package/package.json +4 -5
  75. package/dist/assert.d.ts +0 -14
  76. package/dist/assert.js +0 -21
  77. package/dist/components/BatchedGeometry.svelte +0 -0
  78. package/dist/components/BatchedGeometry.svelte.d.ts +0 -26
  79. package/dist/components/Detections.svelte +0 -41
  80. package/dist/components/Detections.svelte.d.ts +0 -3
  81. package/dist/components/DetectionsPlane.svelte +0 -23
  82. package/dist/components/DetectionsPlane.svelte.d.ts +0 -21
  83. package/dist/components/Geometry2.svelte +0 -211
  84. package/dist/components/Geometry2.svelte.d.ts +0 -19
  85. package/dist/components/overlay/left-pane/Widgets.svelte +0 -65
  86. package/dist/components/overlay/left-pane/Widgets.svelte.d.ts +0 -3
  87. package/dist/entries.d.ts +0 -1
  88. package/dist/entries.js +0 -3
  89. package/dist/hooks/index.d.ts +0 -0
  90. package/dist/hooks/index.js +0 -1
  91. package/dist/test.d.ts +0 -1
  92. package/dist/test.js +0 -1
  93. package/dist/three/BoxHelper.d.ts +0 -50
  94. package/dist/three/BoxHelper.js +0 -134
  95. /package/dist/{common → buf/common}/v1/common_pb.d.ts +0 -0
  96. /package/dist/{common → buf/common}/v1/common_pb.js +0 -0
  97. /package/dist/{draw → buf/draw}/v1/metadata_pb.d.ts +0 -0
  98. /package/dist/{draw → buf/draw}/v1/metadata_pb.js +0 -0
  99. /package/dist/{draw → buf/draw}/v1/scene_pb.d.ts +0 -0
  100. /package/dist/{draw → buf/draw}/v1/scene_pb.js +0 -0
  101. /package/dist/{draw → buf/draw}/v1/snapshot_pb.d.ts +0 -0
  102. /package/dist/{draw → buf/draw}/v1/snapshot_pb.js +0 -0
  103. /package/dist/{draw → buf/draw}/v1/transforms_pb.d.ts +0 -0
  104. /package/dist/{draw → buf/draw}/v1/transforms_pb.js +0 -0
  105. /package/dist/components/{BentPlaneGeometry.svelte → xr/BentPlaneGeometry.svelte} +0 -0
  106. /package/dist/components/{BentPlaneGeometry.svelte.d.ts → xr/BentPlaneGeometry.svelte.d.ts} +0 -0
@@ -0,0 +1,240 @@
1
+ <script lang="ts">
2
+ import { Select, Switch } from '@viamrobotics/prime-core'
3
+ import { useArmClient } from '../../hooks/useArmClient.svelte'
4
+ import { usePartID } from '../../hooks/usePartID.svelte'
5
+ import { useResourceNames } from '@viamrobotics/svelte-sdk'
6
+ import { useSettings } from '../../hooks/useSettings.svelte'
7
+
8
+ const settings = useSettings()
9
+ const armClient = useArmClient()
10
+ const partID = usePartID()
11
+ const resources = useResourceNames(() => partID.current, 'gripper')
12
+
13
+ const armNames = $derived(armClient.names || [])
14
+ const gripperNames = $derived(
15
+ resources.current
16
+ .filter((r) => r.subtype === 'gripper' && r.type === 'component')
17
+ .map((r) => r.name)
18
+ )
19
+
20
+ const config = $derived(settings.current.xrController)
21
+
22
+ // Filter available arms/grippers - exclude what the other controller has selected
23
+ const leftAvailableArms = $derived(
24
+ armNames.filter((name) => name === config.left.armName || name !== config.right.armName)
25
+ )
26
+ const rightAvailableArms = $derived(
27
+ armNames.filter((name) => name === config.right.armName || name !== config.left.armName)
28
+ )
29
+
30
+ const leftAvailableGrippers = $derived(
31
+ gripperNames.filter(
32
+ (name) => name === config.left.gripperName || name !== config.right.gripperName
33
+ )
34
+ )
35
+ const rightAvailableGrippers = $derived(
36
+ gripperNames.filter(
37
+ (name) => name === config.right.gripperName || name !== config.left.gripperName
38
+ )
39
+ )
40
+
41
+ function updateConfig(
42
+ hand: 'left' | 'right',
43
+ key: string,
44
+ value: string | number | boolean | undefined
45
+ ) {
46
+ settings.current.xrController[hand] = {
47
+ ...settings.current.xrController[hand],
48
+ [key]: value,
49
+ }
50
+ }
51
+ </script>
52
+
53
+ {#snippet SectionTitle(title: string)}
54
+ <h3 class="border-gray-3 border-b py-1 text-sm"><strong>{title}</strong></h3>
55
+ {/snippet}
56
+
57
+ <div class="flex flex-col gap-2.5">
58
+ <label class="flex items-center justify-between gap-2">
59
+ Enable VR / AR mode <Switch bind:on={settings.current.enableXR} />
60
+ </label>
61
+
62
+ <!-- Left Controller -->
63
+ {@render SectionTitle('Left Controller')}
64
+
65
+ <label class="flex items-center justify-between gap-2">
66
+ Arm
67
+ <Select
68
+ value={config.left.armName || ''}
69
+ onchange={(event: Event) => {
70
+ if (event.target instanceof HTMLSelectElement) {
71
+ updateConfig('left', 'armName', event.target.value || undefined)
72
+ }
73
+ }}
74
+ >
75
+ <option value="">None</option>
76
+ {#each leftAvailableArms as armName (armName)}
77
+ <option value={armName}>{armName}</option>
78
+ {/each}
79
+ </Select>
80
+ </label>
81
+
82
+ <label class="flex items-center justify-between gap-2">
83
+ Gripper
84
+ <Select
85
+ value={config.left.gripperName || ''}
86
+ onchange={(event: Event) => {
87
+ if (event.target instanceof HTMLSelectElement) {
88
+ updateConfig('left', 'gripperName', event.target.value || undefined)
89
+ }
90
+ }}
91
+ >
92
+ <option value="">None</option>
93
+ {#each leftAvailableGrippers as gripperName (gripperName)}
94
+ <option value={gripperName}>{gripperName}</option>
95
+ {/each}
96
+ </Select>
97
+ </label>
98
+
99
+ <label class="flex items-center justify-between gap-2">
100
+ Scale: {config.left.scaleFactor.toFixed(1)}
101
+ <input
102
+ class="w-20"
103
+ type="range"
104
+ min="0.1"
105
+ max="3.0"
106
+ step="0.1"
107
+ value={config.left.scaleFactor}
108
+ style="--value: {((config.left.scaleFactor - 0.1) / (3.0 - 0.1)) * 100}%"
109
+ oninput={(e) => updateConfig('left', 'scaleFactor', parseFloat(e.currentTarget.value))}
110
+ />
111
+ </label>
112
+
113
+ <label class="flex items-center justify-between gap-2">
114
+ Rotation
115
+ <Switch
116
+ on={config.left.rotationEnabled}
117
+ on:change={(event) => {
118
+ updateConfig('left', 'rotationEnabled', event.detail)
119
+ }}
120
+ />
121
+ </label>
122
+
123
+ <!-- Right Controller -->
124
+ {@render SectionTitle('Right Controller')}
125
+
126
+ <label class="flex items-center justify-between gap-2">
127
+ Arm
128
+ <Select
129
+ value={config.right.armName || ''}
130
+ onchange={(event: Event) => {
131
+ if (event.target instanceof HTMLSelectElement) {
132
+ updateConfig('right', 'armName', event.target.value || undefined)
133
+ }
134
+ }}
135
+ >
136
+ <option value="">None</option>
137
+ {#each rightAvailableArms as armName (armName)}
138
+ <option value={armName}>{armName}</option>
139
+ {/each}
140
+ </Select>
141
+ </label>
142
+
143
+ <label class="flex items-center justify-between gap-2">
144
+ Gripper
145
+ <Select
146
+ value={config.right.gripperName || ''}
147
+ onchange={(event: Event) => {
148
+ if (event.target instanceof HTMLSelectElement) {
149
+ updateConfig('right', 'gripperName', event.target.value || undefined)
150
+ }
151
+ }}
152
+ >
153
+ <option value="">None</option>
154
+ {#each rightAvailableGrippers as gripperName (gripperName)}
155
+ <option value={gripperName}>{gripperName}</option>
156
+ {/each}
157
+ </Select>
158
+ </label>
159
+
160
+ <label class="flex items-center justify-between gap-2">
161
+ Scale: {config.right.scaleFactor.toFixed(1)}
162
+ <input
163
+ class="w-20"
164
+ type="range"
165
+ min="0.1"
166
+ max="3.0"
167
+ step="0.1"
168
+ value={config.right.scaleFactor}
169
+ style="--value: {((config.right.scaleFactor - 0.1) / (3.0 - 0.1)) * 100}%"
170
+ oninput={(e) => updateConfig('right', 'scaleFactor', parseFloat(e.currentTarget.value))}
171
+ />
172
+ </label>
173
+
174
+ <label class="flex items-center justify-between gap-2">
175
+ Rotation
176
+ <Switch
177
+ on={config.right.rotationEnabled}
178
+ on:change={(event) => {
179
+ updateConfig('right', 'rotationEnabled', event.detail)
180
+ }}
181
+ />
182
+ </label>
183
+ </div>
184
+
185
+ <style>
186
+ input[type='range'] {
187
+ -webkit-appearance: none;
188
+ appearance: none;
189
+ width: 100%;
190
+ background: linear-gradient(
191
+ to right,
192
+ #3d7d3f 0%,
193
+ #3d7d3f var(--value),
194
+ #d1d5db var(--value),
195
+ #d1d5db 100%
196
+ );
197
+ border-radius: 0.25rem;
198
+ height: 0.5rem;
199
+ cursor: pointer;
200
+ outline: none;
201
+ }
202
+
203
+ /* Webkit browsers (Chrome, Safari, Edge) */
204
+ input[type='range']::-webkit-slider-track {
205
+ background: transparent;
206
+ height: 0.5rem;
207
+ border-radius: 0.25rem;
208
+ }
209
+
210
+ input[type='range']::-webkit-slider-thumb {
211
+ -webkit-appearance: none;
212
+ appearance: none;
213
+ background: #3d7d3f;
214
+ height: 1.25rem;
215
+ width: 1.25rem;
216
+ border-radius: 50%;
217
+ border: 2px solid white;
218
+ }
219
+
220
+ /* Firefox */
221
+ input[type='range']::-moz-range-track {
222
+ background: transparent;
223
+ height: 0.5rem;
224
+ border-radius: 0.25rem;
225
+ }
226
+
227
+ input[type='range']::-moz-range-thumb {
228
+ background: #3d7d3f;
229
+ height: 1.25rem;
230
+ width: 1.25rem;
231
+ border-radius: 50%;
232
+ border: 2px solid white;
233
+ }
234
+
235
+ input[type='range']::-moz-range-progress {
236
+ background: #3d7d3f;
237
+ height: 0.5rem;
238
+ border-radius: 0.25rem;
239
+ }
240
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const XRControllerSettings: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type XRControllerSettings = ReturnType<typeof XRControllerSettings>;
3
+ export default XRControllerSettings;
@@ -0,0 +1,215 @@
1
+ <script lang="ts">
2
+ import { untrack } from 'svelte'
3
+ import { T } from '@threlte/core'
4
+ import { CanvasTexture, PlaneGeometry } from 'three'
5
+ import { xrToast, type XRToastItem, type ToastVariant } from './toasts.svelte'
6
+ import { Headset } from '@threlte/xr'
7
+
8
+ const CANVAS_WIDTH = 700
9
+ const TOAST_HEIGHT = 80
10
+ const TOAST_GAP = 10
11
+ const MAX_VISIBLE = 5
12
+ const PLANE_WIDTH = 0.7
13
+
14
+ // Offscreen canvas (created once)
15
+ const canvas = document.createElement('canvas')
16
+ canvas.width = CANVAS_WIDTH
17
+ canvas.height = 1
18
+ const texture = new CanvasTexture(canvas)
19
+
20
+ let geometry: PlaneGeometry | undefined = $state()
21
+
22
+ const visibleToasts = $derived(xrToast.toasts.slice(-MAX_VISIBLE))
23
+ const hasToasts = $derived(visibleToasts.length > 0)
24
+
25
+ // Variant styling matching Prime design tokens
26
+ const VARIANT_STYLES: Record<ToastVariant, { accent: string; bg: string }> = {
27
+ success: { accent: '#22C55E', bg: 'rgba(13, 40, 24, 0.94)' },
28
+ danger: { accent: '#EF4444', bg: 'rgba(45, 15, 15, 0.94)' },
29
+ warning: { accent: '#F59E0B', bg: 'rgba(45, 34, 16, 0.94)' },
30
+ info: { accent: '#3B82F6', bg: 'rgba(15, 26, 45, 0.94)' },
31
+ }
32
+
33
+ function drawRoundRect(
34
+ ctx: CanvasRenderingContext2D,
35
+ x: number,
36
+ y: number,
37
+ w: number,
38
+ h: number,
39
+ r: number
40
+ ) {
41
+ ctx.beginPath()
42
+ ctx.moveTo(x + r, y)
43
+ ctx.lineTo(x + w - r, y)
44
+ ctx.quadraticCurveTo(x + w, y, x + w, y + r)
45
+ ctx.lineTo(x + w, y + h - r)
46
+ ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h)
47
+ ctx.lineTo(x + r, y + h)
48
+ ctx.quadraticCurveTo(x, y + h, x, y + h - r)
49
+ ctx.lineTo(x, y + r)
50
+ ctx.quadraticCurveTo(x, y, x + r, y)
51
+ ctx.closePath()
52
+ }
53
+
54
+ function drawIcon(
55
+ ctx: CanvasRenderingContext2D,
56
+ variant: ToastVariant,
57
+ cx: number,
58
+ cy: number,
59
+ size: number
60
+ ) {
61
+ const color = VARIANT_STYLES[variant].accent
62
+ ctx.strokeStyle = color
63
+ ctx.fillStyle = color
64
+ ctx.lineWidth = 3.5
65
+ ctx.lineCap = 'round'
66
+ ctx.lineJoin = 'round'
67
+
68
+ if (variant === 'success') {
69
+ // Checkmark
70
+ ctx.beginPath()
71
+ ctx.moveTo(cx - size * 0.35, cy + size * 0.05)
72
+ ctx.lineTo(cx - size * 0.08, cy + size * 0.3)
73
+ ctx.lineTo(cx + size * 0.4, cy - size * 0.25)
74
+ ctx.stroke()
75
+ } else if (variant === 'danger') {
76
+ // X mark
77
+ const s = size * 0.28
78
+ ctx.beginPath()
79
+ ctx.moveTo(cx - s, cy - s)
80
+ ctx.lineTo(cx + s, cy + s)
81
+ ctx.moveTo(cx + s, cy - s)
82
+ ctx.lineTo(cx - s, cy + s)
83
+ ctx.stroke()
84
+ } else if (variant === 'warning') {
85
+ // Triangle outline with !
86
+ ctx.lineWidth = 3
87
+ ctx.beginPath()
88
+ ctx.moveTo(cx, cy - size * 0.32)
89
+ ctx.lineTo(cx + size * 0.34, cy + size * 0.26)
90
+ ctx.lineTo(cx - size * 0.34, cy + size * 0.26)
91
+ ctx.closePath()
92
+ ctx.stroke()
93
+ // Exclamation mark
94
+ ctx.lineWidth = 3
95
+ ctx.beginPath()
96
+ ctx.moveTo(cx, cy - size * 0.12)
97
+ ctx.lineTo(cx, cy + size * 0.06)
98
+ ctx.stroke()
99
+ ctx.beginPath()
100
+ ctx.arc(cx, cy + size * 0.16, 2, 0, Math.PI * 2)
101
+ ctx.fill()
102
+ } else {
103
+ // Info: circle with i
104
+ ctx.lineWidth = 3
105
+ ctx.beginPath()
106
+ ctx.arc(cx, cy, size * 0.32, 0, Math.PI * 2)
107
+ ctx.stroke()
108
+ // i dot
109
+ ctx.beginPath()
110
+ ctx.arc(cx, cy - size * 0.15, 2.5, 0, Math.PI * 2)
111
+ ctx.fill()
112
+ // i stem
113
+ ctx.lineWidth = 3
114
+ ctx.beginPath()
115
+ ctx.moveTo(cx, cy - size * 0.04)
116
+ ctx.lineTo(cx, cy + size * 0.2)
117
+ ctx.stroke()
118
+ }
119
+ }
120
+
121
+ function renderToasts(toasts: XRToastItem[]) {
122
+ const ctx = canvas.getContext('2d')!
123
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
124
+
125
+ if (toasts.length === 0) {
126
+ texture.needsUpdate = true
127
+ return
128
+ }
129
+
130
+ const radius = 12
131
+ const accentBarWidth = 8
132
+ const iconCenterX = accentBarWidth + 30
133
+ const textStartX = accentBarWidth + 56
134
+
135
+ toasts.forEach((toast, index) => {
136
+ const y = index * (TOAST_HEIGHT + TOAST_GAP)
137
+ const style = VARIANT_STYLES[toast.variant]
138
+
139
+ // Draw background with clip (so accent bar respects rounded corners)
140
+ ctx.save()
141
+ drawRoundRect(ctx, 0, y, CANVAS_WIDTH, TOAST_HEIGHT, radius)
142
+ ctx.clip()
143
+
144
+ // Fill background
145
+ ctx.fillStyle = style.bg
146
+ ctx.fillRect(0, y, CANVAS_WIDTH, TOAST_HEIGHT)
147
+
148
+ // Accent bar on left
149
+ ctx.fillStyle = style.accent
150
+ ctx.fillRect(0, y, accentBarWidth, TOAST_HEIGHT)
151
+
152
+ ctx.restore()
153
+
154
+ // Subtle border
155
+ ctx.strokeStyle = style.accent + '50' // 31% opacity
156
+ ctx.lineWidth = 2
157
+ drawRoundRect(ctx, 1, y + 1, CANVAS_WIDTH - 2, TOAST_HEIGHT - 2, radius)
158
+ ctx.stroke()
159
+
160
+ // Icon
161
+ drawIcon(ctx, toast.variant, iconCenterX, y + TOAST_HEIGHT / 2, 28)
162
+
163
+ // Message text
164
+ ctx.fillStyle = '#ffffff'
165
+ ctx.font = '28px sans-serif'
166
+ ctx.textBaseline = 'middle'
167
+ ctx.fillText(toast.message, textStartX, y + TOAST_HEIGHT / 2)
168
+ })
169
+
170
+ texture.needsUpdate = true
171
+ }
172
+
173
+ // Combined effect: resize canvas, update geometry, render
174
+ $effect(() => {
175
+ const toasts = visibleToasts
176
+
177
+ if (toasts.length === 0) {
178
+ renderToasts([])
179
+ return
180
+ }
181
+
182
+ const h = toasts.length * (TOAST_HEIGHT + TOAST_GAP) - TOAST_GAP
183
+ canvas.height = h
184
+
185
+ untrack(() => geometry?.dispose())
186
+ const aspect = CANVAS_WIDTH / h
187
+ geometry = new PlaneGeometry(PLANE_WIDTH, PLANE_WIDTH / aspect)
188
+
189
+ renderToasts(toasts)
190
+ })
191
+
192
+ // Cleanup
193
+ $effect(() => {
194
+ return () => {
195
+ texture.dispose()
196
+ untrack(() => geometry?.dispose())
197
+ }
198
+ })
199
+ </script>
200
+
201
+ <Headset>
202
+ {#if hasToasts && geometry}
203
+ <T.Mesh
204
+ position={[0, -0.3, -1.5]}
205
+ renderOrder={999}
206
+ >
207
+ <T is={geometry} />
208
+ <T.MeshBasicMaterial
209
+ map={texture}
210
+ transparent
211
+ depthTest={false}
212
+ />
213
+ </T.Mesh>
214
+ {/if}
215
+ </Headset>
@@ -0,0 +1,3 @@
1
+ declare const XRToast: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type XRToast = ReturnType<typeof XRToast>;
3
+ export default XRToast;
@@ -0,0 +1,14 @@
1
+ import { Quaternion, Vector3 } from 'three';
2
+ export declare function getFrameTransformationQuaternion(): Quaternion;
3
+ /**
4
+ * Calculates the delta position in Robot Frame
5
+ */
6
+ export declare function calculatePositionTarget(currentControllerPos: Vector3, referenceControllerPos: Vector3, robotReferencePos: {
7
+ x: number;
8
+ y: number;
9
+ z: number;
10
+ }, qTransform: Quaternion, scaleFactor: number): {
11
+ x: number;
12
+ y: number;
13
+ z: number;
14
+ };
@@ -0,0 +1,26 @@
1
+ import { Quaternion, Vector3 } from 'three';
2
+ export function getFrameTransformationQuaternion() {
3
+ // MATCHING DART IMPLEMENTATION EXACTLY:
4
+ // 1: Rotate -90° around Z-axis
5
+ const rotZ = new Quaternion().setFromAxisAngle(new Vector3(0, 0, 1), -Math.PI / 2);
6
+ // 2: Rotate 90° around X-axis
7
+ const rotX = new Quaternion().setFromAxisAngle(new Vector3(1, 0, 0), Math.PI / 2);
8
+ // Combine: Apply rotX first, then rotZ
9
+ return rotZ.multiply(rotX);
10
+ }
11
+ /**
12
+ * Calculates the delta position in Robot Frame
13
+ */
14
+ export function calculatePositionTarget(currentControllerPos, referenceControllerPos, robotReferencePos, qTransform, scaleFactor) {
15
+ // 1. Get delta in XR space (Meters)
16
+ const deltaXR = currentControllerPos.clone().sub(referenceControllerPos);
17
+ // 2. Convert to Robot Frame
18
+ const deltaRobot = deltaXR.clone().applyQuaternion(qTransform);
19
+ // 3. Scale (Meters -> Millimeters) and Apply
20
+ const scaleMM = scaleFactor * 1000;
21
+ return {
22
+ x: robotReferencePos.x + deltaRobot.x * scaleMM,
23
+ y: robotReferencePos.y + deltaRobot.y * scaleMM,
24
+ z: robotReferencePos.z + deltaRobot.z * scaleMM,
25
+ };
26
+ }
@@ -0,0 +1,20 @@
1
+ export type ToastVariant = 'success' | 'danger' | 'warning' | 'info';
2
+ export interface XRToastItem {
3
+ id: number;
4
+ message: string;
5
+ variant: ToastVariant;
6
+ createdAt: number;
7
+ duration: number;
8
+ }
9
+ declare class VRToastStore {
10
+ toasts: XRToastItem[];
11
+ private nextId;
12
+ add(message: string, variant?: ToastVariant, duration?: number): number;
13
+ remove(id: number): void;
14
+ success(message: string, duration?: number): number;
15
+ danger(message: string, duration?: number): number;
16
+ warning(message: string, duration?: number): number;
17
+ info(message: string, duration?: number): number;
18
+ }
19
+ export declare const xrToast: VRToastStore;
20
+ export {};
@@ -0,0 +1,32 @@
1
+ class VRToastStore {
2
+ toasts = $state([]);
3
+ nextId = 0;
4
+ add(message, variant = 'info', duration = 3000) {
5
+ const id = this.nextId++;
6
+ this.toasts.push({
7
+ id,
8
+ message,
9
+ variant,
10
+ createdAt: Date.now(),
11
+ duration,
12
+ });
13
+ setTimeout(() => this.remove(id), duration);
14
+ return id;
15
+ }
16
+ remove(id) {
17
+ this.toasts = this.toasts.filter((t) => t.id !== id);
18
+ }
19
+ success(message, duration) {
20
+ return this.add(message, 'success', duration);
21
+ }
22
+ danger(message, duration) {
23
+ return this.add(message, 'danger', duration);
24
+ }
25
+ warning(message, duration) {
26
+ return this.add(message, 'warning', duration);
27
+ }
28
+ info(message, duration) {
29
+ return this.add(message, 'info', duration);
30
+ }
31
+ }
32
+ export const xrToast = new VRToastStore();
@@ -1,8 +1,8 @@
1
- import type { Vector3, Vector3Tuple } from 'three';
1
+ import type { Vector3Tuple } from 'three';
2
2
  interface Context {
3
3
  position: Vector3Tuple;
4
4
  rotation: number;
5
- set: (pos?: Vector3, rot?: number) => void;
5
+ set: (pos?: Vector3Tuple, rot?: number) => void;
6
6
  }
7
7
  export declare const provideOrigin: () => void;
8
8
  export declare const useOrigin: () => Context;
@@ -12,11 +12,11 @@ export const provideOrigin = () => {
12
12
  },
13
13
  set(pos, rot) {
14
14
  if (pos) {
15
- position[0] = pos.x;
16
- position[1] = pos.y;
17
- position[2] = pos.z;
15
+ position[0] = pos[0];
16
+ position[1] = pos[1];
17
+ position[2] = pos[2];
18
18
  }
19
- if (rot) {
19
+ if (rot !== undefined) {
20
20
  rotation = rot;
21
21
  }
22
22
  },
@@ -40,6 +40,15 @@ export declare const InstancedPose: import("koota").Trait<{
40
40
  theta: number;
41
41
  index: number;
42
42
  }>;
43
+ export declare const WorldPose: import("koota").Trait<{
44
+ x: number;
45
+ y: number;
46
+ z: number;
47
+ oX: number;
48
+ oY: number;
49
+ oZ: number;
50
+ theta: number;
51
+ }>;
43
52
  export declare const Hovered: import("koota").Trait<() => boolean>;
44
53
  /**
45
54
  * Represents that an entity is composed of many instances, so that the treeview and
@@ -18,6 +18,15 @@ export const InstancedPose = trait({
18
18
  theta: 0,
19
19
  index: -1,
20
20
  });
21
+ export const WorldPose = trait({
22
+ x: 0,
23
+ y: 0,
24
+ z: 0,
25
+ oX: 0,
26
+ oY: 0,
27
+ oZ: 1,
28
+ theta: 0,
29
+ });
21
30
  export const Hovered = trait(() => true);
22
31
  /**
23
32
  * Represents that an entity is composed of many instances, so that the treeview and
@@ -1,6 +1,6 @@
1
1
  import { type Entity, type Trait, type World } from 'koota';
2
- export type AoSFactory = () => unknown;
3
- export type Schema = {
2
+ type AoSFactory = () => unknown;
3
+ type Schema = {
4
4
  [key: string]: number | bigint | string | boolean | null | undefined | (() => unknown);
5
5
  } | AoSFactory | Record<string, never>;
6
6
  type TraitRecordFromSchema<T extends Schema> = T extends AoSFactory ? ReturnType<T> : {
@@ -11,7 +11,7 @@ type TraitRecordFromSchema<T extends Schema> = T extends AoSFactory ? ReturnType
11
11
  * For SoA it is a snapshot of the state for a single entity.
12
12
  * For AoS it is the state instance for a single entity.
13
13
  */
14
- export type TraitRecord<T extends Trait | Schema> = T extends Trait ? TraitRecordFromSchema<T['schema']> : TraitRecordFromSchema<T>;
14
+ type TraitRecord<T extends Trait | Schema> = T extends Trait ? TraitRecordFromSchema<T['schema']> : TraitRecordFromSchema<T>;
15
15
  export declare function isWorld(target: Entity | World | null | undefined): target is World;
16
16
  export declare function useTrait<T extends Trait>(target: () => Entity | World | undefined | null, trait: T): {
17
17
  current: TraitRecord<T> | undefined;
package/dist/frame.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import type { Transform } from '@viamrobotics/sdk';
2
- import type { ValueOf } from 'type-fest';
3
2
  type FrameGeometryMap = {
4
3
  none: {
5
4
  type: 'none';
@@ -21,7 +20,6 @@ type FrameGeometryMap = {
21
20
  };
22
21
  };
23
22
  export type FrameGeometry = keyof FrameGeometryMap;
24
- export type FrameGeometries = ValueOf<FrameGeometryMap>;
25
23
  type FrameOrientationMap = {
26
24
  quaternion: {
27
25
  type: 'quaternion';
@@ -60,7 +58,6 @@ type FrameOrientationMap = {
60
58
  };
61
59
  };
62
60
  export type FrameOrientation = keyof FrameOrientationMap;
63
- export type FrameOrientations = ValueOf<FrameOrientationMap>;
64
61
  export interface Frame<T extends FrameGeometry = FrameGeometry, K extends FrameOrientation = FrameOrientation> {
65
62
  id?: string;
66
63
  name?: string;
@@ -0,0 +1,12 @@
1
+ export interface JointLimit {
2
+ id: string;
3
+ min: number;
4
+ max: number;
5
+ }
6
+ interface Context {
7
+ names: string[];
8
+ kinematics: Record<string, JointLimit[] | undefined>;
9
+ }
10
+ export declare const provideArmKinematics: (partID: () => string) => void;
11
+ export declare const useArmKinematics: () => Context;
12
+ export {};