@viamrobotics/motion-tools 1.19.1 → 1.22.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.d.ts +0 -1
- package/dist/FrameConfigUpdater.svelte.js +6 -24
- package/dist/buf/draw/v1/metadata_pb.d.ts +39 -0
- package/dist/buf/draw/v1/metadata_pb.js +55 -0
- package/dist/buf/draw/v1/service_connect.d.ts +34 -1
- package/dist/buf/draw/v1/service_connect.js +34 -1
- package/dist/buf/draw/v1/service_pb.d.ts +136 -0
- package/dist/buf/draw/v1/service_pb.js +201 -0
- package/dist/components/Entities/Arrows/ArrowGroups.svelte +1 -0
- package/dist/components/Entities/Arrows/Arrows.svelte +1 -1
- package/dist/components/Entities/Points.svelte +23 -23
- package/dist/components/Entities/Pose.svelte +18 -13
- package/dist/components/Entities/hooks/useEntityEvents.svelte.js +18 -1
- package/dist/components/FileDrop/FileDrop.svelte +8 -1
- package/dist/components/FileDrop/useFileDrop.svelte.js +16 -2
- package/dist/components/PCD.svelte +9 -1
- package/dist/components/PCD.svelte.d.ts +2 -0
- package/dist/components/PointerMissBox.svelte +1 -1
- package/dist/components/Scene.svelte +2 -0
- package/dist/components/SceneProviders.svelte +4 -0
- package/dist/components/SelectedTransformControls.svelte +227 -0
- package/dist/components/SelectedTransformControls.svelte.d.ts +3 -0
- package/dist/components/Snapshot.svelte +12 -7
- package/dist/components/StaticGeometries.svelte +3 -56
- package/dist/components/overlay/AddRelationship.svelte +25 -3
- package/dist/components/overlay/Details.svelte +290 -229
- package/dist/components/overlay/dashboard/Button.svelte +4 -2
- package/dist/components/overlay/dashboard/Button.svelte.d.ts +1 -1
- package/dist/components/overlay/dashboard/Dashboard.svelte +43 -33
- package/dist/draw.d.ts +22 -9
- package/dist/draw.js +71 -41
- package/dist/ecs/relations.js +1 -1
- package/dist/ecs/traits.d.ts +17 -0
- package/dist/ecs/traits.js +9 -0
- package/dist/editing/FrameEditSession.d.ts +37 -0
- package/dist/editing/FrameEditSession.js +178 -0
- package/dist/hooks/useDrawService.svelte.d.ts +2 -0
- package/dist/hooks/useDrawService.svelte.js +139 -20
- package/dist/hooks/useFrameEditSession.svelte.d.ts +15 -0
- package/dist/hooks/useFrameEditSession.svelte.js +36 -0
- package/dist/hooks/useFrames.svelte.js +37 -2
- package/dist/hooks/usePartConfig.svelte.js +10 -0
- package/dist/hooks/useRelationships.svelte.d.ts +12 -0
- package/dist/hooks/useRelationships.svelte.js +78 -0
- package/dist/hooks/useSettings.svelte.d.ts +1 -2
- package/dist/hooks/useSettings.svelte.js +1 -2
- package/dist/hooks/useWorldState.svelte.js +10 -4
- package/dist/metadata.d.ts +7 -3
- package/dist/metadata.js +26 -2
- package/dist/snapshot.d.ts +6 -1
- package/dist/snapshot.js +10 -5
- package/dist/transform.js +13 -0
- package/package.json +7 -4
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
module
|
|
3
3
|
lang="ts"
|
|
4
4
|
>
|
|
5
|
-
import {
|
|
5
|
+
import { ThemeUtils } from 'svelte-tweakpane-ui'
|
|
6
|
+
import { BufferAttribute, Euler, MathUtils, Quaternion, Vector3 } from 'three'
|
|
6
7
|
|
|
7
8
|
import { OrientationVector } from '../../three/OrientationVector'
|
|
8
9
|
|
|
9
10
|
const vec3 = new Vector3()
|
|
10
11
|
const quaternion = new Quaternion()
|
|
11
12
|
const ov = new OrientationVector()
|
|
13
|
+
const euler = new Euler()
|
|
12
14
|
</script>
|
|
13
15
|
|
|
14
16
|
<script lang="ts">
|
|
@@ -17,14 +19,30 @@
|
|
|
17
19
|
|
|
18
20
|
import { draggable } from '@neodrag/svelte'
|
|
19
21
|
import { isInstanceOf, useTask } from '@threlte/core'
|
|
20
|
-
import { Button, Icon,
|
|
22
|
+
import { Button, Icon, Tooltip } from '@viamrobotics/prime-core'
|
|
21
23
|
import { Check, Copy } from 'lucide-svelte'
|
|
24
|
+
import {
|
|
25
|
+
List,
|
|
26
|
+
type ListChangeEvent,
|
|
27
|
+
Point,
|
|
28
|
+
type PointChangeEvent,
|
|
29
|
+
type PointValue3dObject,
|
|
30
|
+
type PointValue4dObject,
|
|
31
|
+
RotationEuler,
|
|
32
|
+
type RotationEulerChangeEvent,
|
|
33
|
+
type RotationEulerValueObject,
|
|
34
|
+
Slider,
|
|
35
|
+
type SliderChangeEvent,
|
|
36
|
+
TabGroup,
|
|
37
|
+
TabPage,
|
|
38
|
+
} from 'svelte-tweakpane-ui'
|
|
22
39
|
|
|
23
40
|
import AddRelationship from './AddRelationship.svelte'
|
|
24
41
|
import { relations, traits, useTrait, useWorld } from '../../ecs'
|
|
25
42
|
import { FrameConfigUpdater } from '../../FrameConfigUpdater.svelte'
|
|
26
43
|
import { useConfigFrames } from '../../hooks/useConfigFrames.svelte'
|
|
27
44
|
import { useCameraControls } from '../../hooks/useControls.svelte'
|
|
45
|
+
import { useDrawService } from '../../hooks/useDrawService.svelte'
|
|
28
46
|
import { useEnvironment } from '../../hooks/useEnvironment.svelte'
|
|
29
47
|
import { useLinkedEntities } from '../../hooks/useLinked.svelte'
|
|
30
48
|
import { usePartConfig } from '../../hooks/usePartConfig.svelte'
|
|
@@ -44,6 +62,7 @@
|
|
|
44
62
|
const { details }: Props = $props()
|
|
45
63
|
|
|
46
64
|
const world = useWorld()
|
|
65
|
+
const drawService = useDrawService()
|
|
47
66
|
const controls = useCameraControls()
|
|
48
67
|
const resourceByName = useResourceByName()
|
|
49
68
|
const configFrames = useConfigFrames()
|
|
@@ -83,12 +102,109 @@
|
|
|
83
102
|
return 'none'
|
|
84
103
|
})
|
|
85
104
|
|
|
105
|
+
const geometryTypes = ['none', 'box', 'sphere', 'capsule'] as const
|
|
106
|
+
// Writable derived: re-derives from the trait, but TabGroup's bind:selectedIndex
|
|
107
|
+
// can write a transient override that lasts until the trait re-derives.
|
|
108
|
+
let geometryTabIndex = $derived(geometryTypes.indexOf(geometryType))
|
|
109
|
+
|
|
110
|
+
$effect(() => {
|
|
111
|
+
// setGeometryType guards against no-ops, so this is safe to fire on every
|
|
112
|
+
// tab-index change (whether user-initiated or trait-derived).
|
|
113
|
+
setGeometryType(geometryTypes[geometryTabIndex])
|
|
114
|
+
})
|
|
115
|
+
|
|
86
116
|
let copied = $state(false)
|
|
87
117
|
|
|
88
118
|
let dragElement = $state.raw<HTMLElement>()
|
|
89
119
|
|
|
120
|
+
const eulerValue = $derived.by<RotationEulerValueObject>(() => {
|
|
121
|
+
if (!localPose.current) return { x: 0, y: 0, z: 0 }
|
|
122
|
+
ov.set(
|
|
123
|
+
localPose.current.oX,
|
|
124
|
+
localPose.current.oY,
|
|
125
|
+
localPose.current.oZ,
|
|
126
|
+
MathUtils.degToRad(localPose.current.theta)
|
|
127
|
+
)
|
|
128
|
+
ov.toEuler(euler)
|
|
129
|
+
return {
|
|
130
|
+
x: MathUtils.radToDeg(euler.x),
|
|
131
|
+
y: MathUtils.radToDeg(euler.y),
|
|
132
|
+
z: MathUtils.radToDeg(euler.z),
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
|
|
90
136
|
const detailConfigUpdater = new FrameConfigUpdater(partConfig.updateFrame, partConfig.deleteFrame)
|
|
91
137
|
|
|
138
|
+
const handlePositionChange = (event: PointChangeEvent) => {
|
|
139
|
+
if (event.detail.origin !== 'internal' || !entity) return
|
|
140
|
+
const next = event.detail.value as PointValue3dObject
|
|
141
|
+
detailConfigUpdater.updateLocalPosition(entity, next)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const handleOrientationOVChange = (event: PointChangeEvent) => {
|
|
145
|
+
if (event.detail.origin !== 'internal' || !entity) return
|
|
146
|
+
const next = event.detail.value as PointValue4dObject
|
|
147
|
+
detailConfigUpdater.updateLocalOrientation(entity, {
|
|
148
|
+
oX: next.x,
|
|
149
|
+
oY: next.y,
|
|
150
|
+
oZ: next.z,
|
|
151
|
+
theta: next.w,
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const handleOrientationEulerChange = (event: RotationEulerChangeEvent) => {
|
|
156
|
+
if (event.detail.origin !== 'internal' || !entity) return
|
|
157
|
+
const next = event.detail.value as RotationEulerValueObject
|
|
158
|
+
euler.set(
|
|
159
|
+
MathUtils.degToRad(next.x),
|
|
160
|
+
MathUtils.degToRad(next.y),
|
|
161
|
+
MathUtils.degToRad(next.z),
|
|
162
|
+
'ZYX'
|
|
163
|
+
)
|
|
164
|
+
quaternion.setFromEuler(euler)
|
|
165
|
+
ov.setFromQuaternion(quaternion)
|
|
166
|
+
detailConfigUpdater.updateLocalOrientation(entity, {
|
|
167
|
+
oX: ov.x,
|
|
168
|
+
oY: ov.y,
|
|
169
|
+
oZ: ov.z,
|
|
170
|
+
theta: MathUtils.radToDeg(ov.th),
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const handleBoxChange = (event: PointChangeEvent) => {
|
|
175
|
+
if (event.detail.origin !== 'internal' || !entity) return
|
|
176
|
+
const next = event.detail.value as PointValue3dObject
|
|
177
|
+
detailConfigUpdater.updateGeometry(entity, {
|
|
178
|
+
type: 'box',
|
|
179
|
+
x: next.x,
|
|
180
|
+
y: next.y,
|
|
181
|
+
z: next.z,
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const handleSphereRChange = (event: SliderChangeEvent) => {
|
|
186
|
+
if (event.detail.origin !== 'internal' || !entity) return
|
|
187
|
+
detailConfigUpdater.updateGeometry(entity, { type: 'sphere', r: event.detail.value })
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const handleCapsuleRChange = (event: SliderChangeEvent) => {
|
|
191
|
+
if (event.detail.origin !== 'internal' || !entity) return
|
|
192
|
+
detailConfigUpdater.updateGeometry(entity, { type: 'capsule', r: event.detail.value })
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const handleCapsuleLChange = (event: SliderChangeEvent) => {
|
|
196
|
+
if (event.detail.origin !== 'internal' || !entity) return
|
|
197
|
+
detailConfigUpdater.updateGeometry(entity, { type: 'capsule', l: event.detail.value })
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const handleParentChange = (event: ListChangeEvent) => {
|
|
201
|
+
if (event.detail.origin !== 'internal' || !entity) return
|
|
202
|
+
const value = event.detail.value as string
|
|
203
|
+
if (value === parent.current) return
|
|
204
|
+
traits.setParentTrait(entity, value)
|
|
205
|
+
detailConfigUpdater.setFrameParent(entity, value)
|
|
206
|
+
}
|
|
207
|
+
|
|
92
208
|
const setGeometryType = (type: 'none' | 'box' | 'sphere' | 'capsule') => {
|
|
93
209
|
if (type === geometryType) {
|
|
94
210
|
return
|
|
@@ -172,18 +288,11 @@
|
|
|
172
288
|
)
|
|
173
289
|
}
|
|
174
290
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
input.startsWith('.') ||
|
|
181
|
-
input.startsWith('-0') ||
|
|
182
|
-
input.startsWith('-.') ||
|
|
183
|
-
(input.includes('.') && input.endsWith('0')) ||
|
|
184
|
-
input.endsWith('.')
|
|
185
|
-
)
|
|
186
|
-
}
|
|
291
|
+
ThemeUtils.setGlobalDefaultTheme({
|
|
292
|
+
...ThemeUtils.presets.light,
|
|
293
|
+
baseBackgroundColor: '#fbfbfc',
|
|
294
|
+
baseShadowColor: 'transparent',
|
|
295
|
+
})
|
|
187
296
|
</script>
|
|
188
297
|
|
|
189
298
|
{#snippet ImmutableField({
|
|
@@ -207,60 +316,10 @@
|
|
|
207
316
|
</div>
|
|
208
317
|
{/snippet}
|
|
209
318
|
|
|
210
|
-
{#snippet MutableField({
|
|
211
|
-
label,
|
|
212
|
-
value,
|
|
213
|
-
ariaLabel,
|
|
214
|
-
onInput,
|
|
215
|
-
}: {
|
|
216
|
-
label: string
|
|
217
|
-
value?: number
|
|
218
|
-
ariaLabel: string
|
|
219
|
-
onInput: (value: string) => void
|
|
220
|
-
})}
|
|
221
|
-
<div class="flex items-center gap-1">
|
|
222
|
-
<span class="text-subtle-2">{label}</span>
|
|
223
|
-
<Input
|
|
224
|
-
aria-label={`mutable ${ariaLabel}`}
|
|
225
|
-
{value}
|
|
226
|
-
on:input={(event) => onInput((event.target as HTMLInputElement).value)}
|
|
227
|
-
/>
|
|
228
|
-
</div>
|
|
229
|
-
{/snippet}
|
|
230
|
-
|
|
231
|
-
{#snippet DropDownField({
|
|
232
|
-
value,
|
|
233
|
-
ariaLabel,
|
|
234
|
-
options,
|
|
235
|
-
onChange,
|
|
236
|
-
}: {
|
|
237
|
-
value: string
|
|
238
|
-
ariaLabel: string
|
|
239
|
-
options: string[]
|
|
240
|
-
onChange: (value: string) => void
|
|
241
|
-
})}
|
|
242
|
-
<Select
|
|
243
|
-
aria-label={`dropdown ${ariaLabel}`}
|
|
244
|
-
{value}
|
|
245
|
-
onchange={(event: InputEvent) => {
|
|
246
|
-
onChange((event.target as HTMLSelectElement).value)
|
|
247
|
-
}}
|
|
248
|
-
>
|
|
249
|
-
{#each options as option (option)}
|
|
250
|
-
<option value={option}>{option}</option>
|
|
251
|
-
{/each}
|
|
252
|
-
</Select>
|
|
253
|
-
{/snippet}
|
|
254
|
-
|
|
255
319
|
{#if entity}
|
|
256
|
-
{@const ParentFrame = showEditFrameOptions ? DropDownField : ImmutableField}
|
|
257
|
-
{@const ScalarAttribute = showEditFrameOptions ? MutableField : ImmutableField}
|
|
258
|
-
|
|
259
320
|
<div
|
|
260
321
|
id="details-panel"
|
|
261
|
-
class="border-medium bg-extralight absolute top-0 right-0 z-4 m-2
|
|
262
|
-
? 'w-80'
|
|
263
|
-
: 'w-60'} border p-2 text-xs dark:text-black"
|
|
322
|
+
class="border-medium bg-extralight absolute top-0 right-0 z-4 m-2 w-70 border p-2 text-xs dark:text-black"
|
|
264
323
|
use:draggable={{
|
|
265
324
|
bounds: 'body',
|
|
266
325
|
handle: dragElement,
|
|
@@ -398,18 +457,22 @@
|
|
|
398
457
|
|
|
399
458
|
<div>
|
|
400
459
|
<strong class="font-semibold">parent frame</strong>
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
460
|
+
{#if showEditFrameOptions}
|
|
461
|
+
<div aria-label="mutable parent frame">
|
|
462
|
+
<List
|
|
463
|
+
options={configFrames.getParentFrameOptions(name.current ?? '') ?? []}
|
|
464
|
+
value={parent.current ?? 'world'}
|
|
465
|
+
on:change={handleParentChange}
|
|
466
|
+
/>
|
|
467
|
+
</div>
|
|
468
|
+
{:else}
|
|
469
|
+
<div class="mt-0.5 flex gap-3">
|
|
470
|
+
{@render ImmutableField({
|
|
471
|
+
ariaLabel: 'parent frame name',
|
|
472
|
+
value: parent.current ?? 'world',
|
|
473
|
+
})}
|
|
474
|
+
</div>
|
|
475
|
+
{/if}
|
|
413
476
|
</div>
|
|
414
477
|
|
|
415
478
|
{#if localPose.current}
|
|
@@ -417,159 +480,161 @@
|
|
|
417
480
|
<strong class="font-semibold">local position</strong>
|
|
418
481
|
<span class="text-subtle-2">(mm)</span>
|
|
419
482
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
483
|
+
{#if showEditFrameOptions}
|
|
484
|
+
<div aria-label="mutable local position">
|
|
485
|
+
<Point
|
|
486
|
+
value={{
|
|
487
|
+
x: localPose.current.x,
|
|
488
|
+
y: localPose.current.y,
|
|
489
|
+
z: localPose.current.z,
|
|
490
|
+
}}
|
|
491
|
+
on:change={handlePositionChange}
|
|
492
|
+
/>
|
|
493
|
+
</div>
|
|
494
|
+
{:else}
|
|
495
|
+
<div class="mt-0.5 flex gap-3">
|
|
496
|
+
{@render ImmutableField({
|
|
497
|
+
label: 'x',
|
|
498
|
+
ariaLabel: 'local position x coordinate',
|
|
499
|
+
value: localPose.current.x,
|
|
500
|
+
})}
|
|
501
|
+
{@render ImmutableField({
|
|
502
|
+
label: 'y',
|
|
503
|
+
ariaLabel: 'local position y coordinate',
|
|
504
|
+
value: localPose.current.y,
|
|
505
|
+
})}
|
|
506
|
+
{@render ImmutableField({
|
|
507
|
+
label: 'z',
|
|
508
|
+
ariaLabel: 'local position z coordinate',
|
|
509
|
+
value: localPose.current.z,
|
|
510
|
+
})}
|
|
511
|
+
</div>
|
|
512
|
+
{/if}
|
|
449
513
|
</div>
|
|
450
514
|
|
|
451
515
|
<div>
|
|
452
516
|
<strong class="font-semibold">local orientation</strong>
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
517
|
+
|
|
518
|
+
{#if showEditFrameOptions}
|
|
519
|
+
<div aria-label="mutable local orientation">
|
|
520
|
+
<TabGroup>
|
|
521
|
+
<TabPage title="OV (deg)">
|
|
522
|
+
<Point
|
|
523
|
+
value={{
|
|
524
|
+
x: localPose.current.oX,
|
|
525
|
+
y: localPose.current.oY,
|
|
526
|
+
z: localPose.current.oZ,
|
|
527
|
+
w: localPose.current.theta,
|
|
528
|
+
}}
|
|
529
|
+
on:change={handleOrientationOVChange}
|
|
530
|
+
/>
|
|
531
|
+
</TabPage>
|
|
532
|
+
<TabPage title="Euler">
|
|
533
|
+
<RotationEuler
|
|
534
|
+
value={eulerValue}
|
|
535
|
+
unit="deg"
|
|
536
|
+
on:change={handleOrientationEulerChange}
|
|
537
|
+
/>
|
|
538
|
+
</TabPage>
|
|
539
|
+
</TabGroup>
|
|
540
|
+
</div>
|
|
541
|
+
{:else}
|
|
542
|
+
<div class="mt-0.5 flex gap-3">
|
|
543
|
+
{@render ImmutableField({
|
|
544
|
+
label: 'x',
|
|
545
|
+
ariaLabel: 'local orientation x coordinate',
|
|
546
|
+
value: localPose.current.oX,
|
|
547
|
+
})}
|
|
548
|
+
{@render ImmutableField({
|
|
549
|
+
label: 'y',
|
|
550
|
+
ariaLabel: 'local orientation y coordinate',
|
|
551
|
+
value: localPose.current.oY,
|
|
552
|
+
})}
|
|
553
|
+
{@render ImmutableField({
|
|
554
|
+
label: 'z',
|
|
555
|
+
ariaLabel: 'local orientation z coordinate',
|
|
556
|
+
value: localPose.current.oZ,
|
|
557
|
+
})}
|
|
558
|
+
{@render ImmutableField({
|
|
559
|
+
label: 'th',
|
|
560
|
+
ariaLabel: 'local orientation theta degrees',
|
|
561
|
+
value: localPose.current.theta,
|
|
562
|
+
})}
|
|
563
|
+
</div>
|
|
564
|
+
{/if}
|
|
494
565
|
</div>
|
|
495
566
|
{/if}
|
|
496
567
|
|
|
497
568
|
{#if showEditFrameOptions}
|
|
498
569
|
<div>
|
|
499
570
|
<strong class="font-semibold">geometry</strong>
|
|
500
|
-
<
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
571
|
+
<span class="text-subtle-2">(mm)</span>
|
|
572
|
+
<div aria-label="mutable geometry">
|
|
573
|
+
<TabGroup bind:selectedIndex={geometryTabIndex}>
|
|
574
|
+
<TabPage title="None" />
|
|
575
|
+
<TabPage title="Box">
|
|
576
|
+
{#if box.current}
|
|
577
|
+
<div aria-label="mutable box dimensions">
|
|
578
|
+
<Point
|
|
579
|
+
value={{
|
|
580
|
+
x: box.current.x,
|
|
581
|
+
y: box.current.y,
|
|
582
|
+
z: box.current.z,
|
|
583
|
+
}}
|
|
584
|
+
on:change={handleBoxChange}
|
|
585
|
+
/>
|
|
586
|
+
</div>
|
|
587
|
+
{/if}
|
|
588
|
+
</TabPage>
|
|
589
|
+
<TabPage title="Sphere">
|
|
590
|
+
{#if sphere.current}
|
|
591
|
+
<div aria-label="mutable sphere dimensions">
|
|
592
|
+
<Slider
|
|
593
|
+
label="r"
|
|
594
|
+
value={sphere.current.r}
|
|
595
|
+
on:change={handleSphereRChange}
|
|
596
|
+
/>
|
|
597
|
+
</div>
|
|
598
|
+
{/if}
|
|
599
|
+
</TabPage>
|
|
600
|
+
<TabPage title="Capsule">
|
|
601
|
+
{#if capsule.current}
|
|
602
|
+
<div aria-label="mutable capsule dimensions">
|
|
603
|
+
<Slider
|
|
604
|
+
label="r"
|
|
605
|
+
value={capsule.current.r}
|
|
606
|
+
on:change={handleCapsuleRChange}
|
|
607
|
+
/>
|
|
608
|
+
<Slider
|
|
609
|
+
label="l"
|
|
610
|
+
value={capsule.current.l}
|
|
611
|
+
on:change={handleCapsuleLChange}
|
|
612
|
+
/>
|
|
613
|
+
</div>
|
|
614
|
+
{/if}
|
|
615
|
+
</TabPage>
|
|
616
|
+
</TabGroup>
|
|
529
617
|
</div>
|
|
530
618
|
</div>
|
|
531
|
-
{
|
|
532
|
-
|
|
533
|
-
{#if box.current}
|
|
619
|
+
{:else if box.current}
|
|
534
620
|
<div>
|
|
535
|
-
<strong class="font-semibold">
|
|
621
|
+
<strong class="font-semibold">dimensions</strong>
|
|
536
622
|
<span class="text-subtle-2">(box) (mm)</span>
|
|
537
623
|
<div class="mt-0.5 flex items-center gap-2">
|
|
538
|
-
{@render
|
|
624
|
+
{@render ImmutableField({
|
|
539
625
|
label: 'x',
|
|
540
626
|
ariaLabel: 'box dimensions x value input',
|
|
541
627
|
value: box.current.x,
|
|
542
|
-
onInput: (value) => {
|
|
543
|
-
if (isIntermediateInput(value)) return
|
|
544
|
-
detailConfigUpdater.updateGeometry(entity, {
|
|
545
|
-
type: 'box',
|
|
546
|
-
x: Number.parseFloat(value),
|
|
547
|
-
})
|
|
548
|
-
},
|
|
549
628
|
})}
|
|
550
|
-
{@render
|
|
629
|
+
{@render ImmutableField({
|
|
551
630
|
label: 'y',
|
|
552
631
|
ariaLabel: 'box dimensions y value input',
|
|
553
632
|
value: box.current.y,
|
|
554
|
-
onInput: (value) => {
|
|
555
|
-
if (isIntermediateInput(value)) return
|
|
556
|
-
detailConfigUpdater.updateGeometry(entity, {
|
|
557
|
-
type: 'box',
|
|
558
|
-
y: Number.parseFloat(value),
|
|
559
|
-
})
|
|
560
|
-
},
|
|
561
633
|
})}
|
|
562
|
-
{@render
|
|
634
|
+
{@render ImmutableField({
|
|
563
635
|
label: 'z',
|
|
564
636
|
ariaLabel: 'box dimensions z value input',
|
|
565
637
|
value: box.current.z,
|
|
566
|
-
onInput: (value) => {
|
|
567
|
-
if (isIntermediateInput(value)) return
|
|
568
|
-
detailConfigUpdater.updateGeometry(entity, {
|
|
569
|
-
type: 'box',
|
|
570
|
-
z: Number.parseFloat(value),
|
|
571
|
-
})
|
|
572
|
-
},
|
|
573
638
|
})}
|
|
574
639
|
</div>
|
|
575
640
|
</div>
|
|
@@ -578,29 +643,15 @@
|
|
|
578
643
|
<strong class="font-semibold">dimensions</strong>
|
|
579
644
|
<span class="text-subtle-2">(capsule) (mm)</span>
|
|
580
645
|
<div class="mt-0.5 flex items-center gap-2">
|
|
581
|
-
{@render
|
|
646
|
+
{@render ImmutableField({
|
|
582
647
|
label: 'r',
|
|
583
648
|
ariaLabel: 'capsule dimensions radius value input',
|
|
584
649
|
value: capsule.current.r,
|
|
585
|
-
onInput: (value) => {
|
|
586
|
-
if (isIntermediateInput(value)) return
|
|
587
|
-
detailConfigUpdater.updateGeometry(entity, {
|
|
588
|
-
type: 'capsule',
|
|
589
|
-
r: Number.parseFloat(value),
|
|
590
|
-
})
|
|
591
|
-
},
|
|
592
650
|
})}
|
|
593
|
-
{@render
|
|
651
|
+
{@render ImmutableField({
|
|
594
652
|
label: 'l',
|
|
595
653
|
ariaLabel: 'capsule dimensions length value input',
|
|
596
654
|
value: capsule.current.l,
|
|
597
|
-
onInput: (value) => {
|
|
598
|
-
if (isIntermediateInput(value)) return
|
|
599
|
-
detailConfigUpdater.updateGeometry(entity, {
|
|
600
|
-
type: 'capsule',
|
|
601
|
-
l: Number.parseFloat(value),
|
|
602
|
-
})
|
|
603
|
-
},
|
|
604
655
|
})}
|
|
605
656
|
</div>
|
|
606
657
|
</div>
|
|
@@ -608,17 +659,10 @@
|
|
|
608
659
|
<div>
|
|
609
660
|
<strong class="font-semibold">dimensions (sphere)</strong>
|
|
610
661
|
<div class="flex items-center gap-2">
|
|
611
|
-
{@render
|
|
662
|
+
{@render ImmutableField({
|
|
612
663
|
label: 'r',
|
|
613
664
|
ariaLabel: 'sphere dimensions radius value',
|
|
614
665
|
value: sphere.current.r,
|
|
615
|
-
onInput: (value) => {
|
|
616
|
-
if (isIntermediateInput(value)) return
|
|
617
|
-
detailConfigUpdater.updateGeometry(entity, {
|
|
618
|
-
type: 'sphere',
|
|
619
|
-
r: Number.parseFloat(value),
|
|
620
|
-
})
|
|
621
|
-
},
|
|
622
666
|
})}
|
|
623
667
|
</div>
|
|
624
668
|
</div>
|
|
@@ -653,7 +697,13 @@
|
|
|
653
697
|
name="trash-can-outline"
|
|
654
698
|
class="h-6 cursor-pointer px-2 py-1 text-xs text-red-500"
|
|
655
699
|
onclick={() => {
|
|
656
|
-
entity.
|
|
700
|
+
const sourceUuid = entity.get(traits.UUID)
|
|
701
|
+
const targetUuid = linkedEntity.get(traits.UUID)
|
|
702
|
+
if (sourceUuid && targetUuid) {
|
|
703
|
+
void drawService.deleteRelationship(sourceUuid, targetUuid)
|
|
704
|
+
} else {
|
|
705
|
+
entity.remove(relations.SubEntityLink(linkedEntity))
|
|
706
|
+
}
|
|
657
707
|
}}
|
|
658
708
|
/>
|
|
659
709
|
</div>
|
|
@@ -700,3 +750,14 @@
|
|
|
700
750
|
{/if}
|
|
701
751
|
</div>
|
|
702
752
|
{/if}
|
|
753
|
+
|
|
754
|
+
<style>
|
|
755
|
+
:global(.tp-tabv_i) {
|
|
756
|
+
display: none;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
:global(.tp-lblv),
|
|
760
|
+
:global(.tp-tbpv_c) {
|
|
761
|
+
padding-left: 0 !important;
|
|
762
|
+
}
|
|
763
|
+
</style>
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import type { ClassValue, HTMLButtonAttributes, MouseEventHandler } from 'svelte/elements'
|
|
3
3
|
|
|
4
4
|
import { Icon, type IconName, Tooltip } from '@viamrobotics/prime-core'
|
|
5
|
-
import { Ruler } from 'lucide-svelte'
|
|
5
|
+
import { MousePointer2, Ruler } from 'lucide-svelte'
|
|
6
6
|
|
|
7
7
|
interface Props extends HTMLButtonAttributes {
|
|
8
|
-
icon: IconName | 'ruler'
|
|
8
|
+
icon: IconName | 'ruler' | 'mouse-pointer'
|
|
9
9
|
active?: boolean
|
|
10
10
|
description: string
|
|
11
11
|
hotkey?: string
|
|
@@ -48,6 +48,8 @@
|
|
|
48
48
|
>
|
|
49
49
|
{#if icon === 'ruler'}
|
|
50
50
|
<Ruler size="16" />
|
|
51
|
+
{:else if icon === 'mouse-pointer'}
|
|
52
|
+
<MousePointer2 size="16" />
|
|
51
53
|
{:else}
|
|
52
54
|
<Icon name={icon} />
|
|
53
55
|
{/if}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ClassValue, HTMLButtonAttributes, MouseEventHandler } from 'svelte/elements';
|
|
2
2
|
import { type IconName } from '@viamrobotics/prime-core';
|
|
3
3
|
interface Props extends HTMLButtonAttributes {
|
|
4
|
-
icon: IconName | 'ruler';
|
|
4
|
+
icon: IconName | 'ruler' | 'mouse-pointer';
|
|
5
5
|
active?: boolean;
|
|
6
6
|
description: string;
|
|
7
7
|
hotkey?: string;
|