@viamrobotics/motion-tools 1.19.1 → 1.21.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/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/hooks/useEntityEvents.svelte.js +18 -1
- package/dist/components/FileDrop/FileDrop.svelte +8 -1
- package/dist/components/PCD.svelte +9 -1
- package/dist/components/PCD.svelte.d.ts +2 -0
- package/dist/components/SceneProviders.svelte +2 -0
- package/dist/components/Snapshot.svelte +12 -7
- package/dist/components/overlay/AddRelationship.svelte +25 -3
- package/dist/components/overlay/Details.svelte +293 -227
- 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 +2 -0
- package/dist/ecs/traits.js +2 -0
- package/dist/hooks/useDrawService.svelte.d.ts +2 -0
- package/dist/hooks/useDrawService.svelte.js +139 -20
- package/dist/hooks/useRelationships.svelte.d.ts +12 -0
- package/dist/hooks/useRelationships.svelte.js +78 -0
- 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/package.json +5 -2
|
@@ -2,13 +2,21 @@
|
|
|
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()
|
|
14
|
+
|
|
15
|
+
ThemeUtils.setGlobalDefaultTheme({
|
|
16
|
+
...ThemeUtils.presets.light,
|
|
17
|
+
baseBackgroundColor: '#fbfbfc',
|
|
18
|
+
baseShadowColor: 'transparent',
|
|
19
|
+
})
|
|
12
20
|
</script>
|
|
13
21
|
|
|
14
22
|
<script lang="ts">
|
|
@@ -17,14 +25,30 @@
|
|
|
17
25
|
|
|
18
26
|
import { draggable } from '@neodrag/svelte'
|
|
19
27
|
import { isInstanceOf, useTask } from '@threlte/core'
|
|
20
|
-
import { Button, Icon,
|
|
28
|
+
import { Button, Icon, Tooltip } from '@viamrobotics/prime-core'
|
|
21
29
|
import { Check, Copy } from 'lucide-svelte'
|
|
30
|
+
import {
|
|
31
|
+
List,
|
|
32
|
+
type ListChangeEvent,
|
|
33
|
+
Point,
|
|
34
|
+
type PointChangeEvent,
|
|
35
|
+
type PointValue3dObject,
|
|
36
|
+
type PointValue4dObject,
|
|
37
|
+
RotationEuler,
|
|
38
|
+
type RotationEulerChangeEvent,
|
|
39
|
+
type RotationEulerValueObject,
|
|
40
|
+
Slider,
|
|
41
|
+
type SliderChangeEvent,
|
|
42
|
+
TabGroup,
|
|
43
|
+
TabPage,
|
|
44
|
+
} from 'svelte-tweakpane-ui'
|
|
22
45
|
|
|
23
46
|
import AddRelationship from './AddRelationship.svelte'
|
|
24
47
|
import { relations, traits, useTrait, useWorld } from '../../ecs'
|
|
25
48
|
import { FrameConfigUpdater } from '../../FrameConfigUpdater.svelte'
|
|
26
49
|
import { useConfigFrames } from '../../hooks/useConfigFrames.svelte'
|
|
27
50
|
import { useCameraControls } from '../../hooks/useControls.svelte'
|
|
51
|
+
import { useDrawService } from '../../hooks/useDrawService.svelte'
|
|
28
52
|
import { useEnvironment } from '../../hooks/useEnvironment.svelte'
|
|
29
53
|
import { useLinkedEntities } from '../../hooks/useLinked.svelte'
|
|
30
54
|
import { usePartConfig } from '../../hooks/usePartConfig.svelte'
|
|
@@ -44,6 +68,7 @@
|
|
|
44
68
|
const { details }: Props = $props()
|
|
45
69
|
|
|
46
70
|
const world = useWorld()
|
|
71
|
+
const drawService = useDrawService()
|
|
47
72
|
const controls = useCameraControls()
|
|
48
73
|
const resourceByName = useResourceByName()
|
|
49
74
|
const configFrames = useConfigFrames()
|
|
@@ -83,12 +108,111 @@
|
|
|
83
108
|
return 'none'
|
|
84
109
|
})
|
|
85
110
|
|
|
111
|
+
const geometryTypes = ['none', 'box', 'sphere', 'capsule'] as const
|
|
112
|
+
// Writable derived: re-derives from the trait, but TabGroup's bind:selectedIndex
|
|
113
|
+
// can write a transient override that lasts until the trait re-derives.
|
|
114
|
+
let geometryTabIndex = $derived(geometryTypes.indexOf(geometryType))
|
|
115
|
+
|
|
116
|
+
$effect(() => {
|
|
117
|
+
// setGeometryType guards against no-ops, so this is safe to fire on every
|
|
118
|
+
// tab-index change (whether user-initiated or trait-derived).
|
|
119
|
+
setGeometryType(geometryTypes[geometryTabIndex])
|
|
120
|
+
})
|
|
121
|
+
|
|
86
122
|
let copied = $state(false)
|
|
87
123
|
|
|
88
124
|
let dragElement = $state.raw<HTMLElement>()
|
|
89
125
|
|
|
126
|
+
const eulerValue = $derived.by<RotationEulerValueObject>(() => {
|
|
127
|
+
if (!localPose.current) return { x: 0, y: 0, z: 0 }
|
|
128
|
+
ov.set(
|
|
129
|
+
localPose.current.oX,
|
|
130
|
+
localPose.current.oY,
|
|
131
|
+
localPose.current.oZ,
|
|
132
|
+
MathUtils.degToRad(localPose.current.theta)
|
|
133
|
+
)
|
|
134
|
+
ov.toEuler(euler)
|
|
135
|
+
return {
|
|
136
|
+
x: MathUtils.radToDeg(euler.x),
|
|
137
|
+
y: MathUtils.radToDeg(euler.y),
|
|
138
|
+
z: MathUtils.radToDeg(euler.z),
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const formatTwoDecimals = (value: number) => value.toFixed(2)
|
|
143
|
+
|
|
90
144
|
const detailConfigUpdater = new FrameConfigUpdater(partConfig.updateFrame, partConfig.deleteFrame)
|
|
91
145
|
|
|
146
|
+
const handlePositionChange = (event: PointChangeEvent) => {
|
|
147
|
+
if (event.detail.origin !== 'internal' || !entity) return
|
|
148
|
+
const next = event.detail.value as PointValue3dObject
|
|
149
|
+
detailConfigUpdater.updateLocalPosition(entity, next)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const handleOrientationOVChange = (event: PointChangeEvent) => {
|
|
153
|
+
if (event.detail.origin !== 'internal' || !entity) return
|
|
154
|
+
const next = event.detail.value as PointValue4dObject
|
|
155
|
+
detailConfigUpdater.updateLocalOrientation(entity, {
|
|
156
|
+
oX: next.x,
|
|
157
|
+
oY: next.y,
|
|
158
|
+
oZ: next.z,
|
|
159
|
+
theta: next.w,
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const handleOrientationEulerChange = (event: RotationEulerChangeEvent) => {
|
|
164
|
+
if (event.detail.origin !== 'internal' || !entity) return
|
|
165
|
+
const next = event.detail.value as RotationEulerValueObject
|
|
166
|
+
euler.set(
|
|
167
|
+
MathUtils.degToRad(next.x),
|
|
168
|
+
MathUtils.degToRad(next.y),
|
|
169
|
+
MathUtils.degToRad(next.z),
|
|
170
|
+
'ZYX'
|
|
171
|
+
)
|
|
172
|
+
quaternion.setFromEuler(euler)
|
|
173
|
+
ov.setFromQuaternion(quaternion)
|
|
174
|
+
detailConfigUpdater.updateLocalOrientation(entity, {
|
|
175
|
+
oX: ov.x,
|
|
176
|
+
oY: ov.y,
|
|
177
|
+
oZ: ov.z,
|
|
178
|
+
theta: MathUtils.radToDeg(ov.th),
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const handleBoxChange = (event: PointChangeEvent) => {
|
|
183
|
+
if (event.detail.origin !== 'internal' || !entity) return
|
|
184
|
+
const next = event.detail.value as PointValue3dObject
|
|
185
|
+
detailConfigUpdater.updateGeometry(entity, {
|
|
186
|
+
type: 'box',
|
|
187
|
+
x: next.x,
|
|
188
|
+
y: next.y,
|
|
189
|
+
z: next.z,
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const handleSphereRChange = (event: SliderChangeEvent) => {
|
|
194
|
+
if (event.detail.origin !== 'internal' || !entity) return
|
|
195
|
+
detailConfigUpdater.updateGeometry(entity, { type: 'sphere', r: event.detail.value })
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const handleCapsuleRChange = (event: SliderChangeEvent) => {
|
|
199
|
+
if (event.detail.origin !== 'internal' || !entity) return
|
|
200
|
+
detailConfigUpdater.updateGeometry(entity, { type: 'capsule', r: event.detail.value })
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const handleCapsuleLChange = (event: SliderChangeEvent) => {
|
|
204
|
+
if (event.detail.origin !== 'internal' || !entity) return
|
|
205
|
+
detailConfigUpdater.updateGeometry(entity, { type: 'capsule', l: event.detail.value })
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const handleParentChange = (event: ListChangeEvent) => {
|
|
209
|
+
if (event.detail.origin !== 'internal' || !entity) return
|
|
210
|
+
const value = event.detail.value as string
|
|
211
|
+
if (value === parent.current) return
|
|
212
|
+
traits.setParentTrait(entity, value)
|
|
213
|
+
detailConfigUpdater.setFrameParent(entity, value)
|
|
214
|
+
}
|
|
215
|
+
|
|
92
216
|
const setGeometryType = (type: 'none' | 'box' | 'sphere' | 'capsule') => {
|
|
93
217
|
if (type === geometryType) {
|
|
94
218
|
return
|
|
@@ -171,19 +295,6 @@
|
|
|
171
295
|
2
|
|
172
296
|
)
|
|
173
297
|
}
|
|
174
|
-
|
|
175
|
-
const isIntermediateInput = (input: string) => {
|
|
176
|
-
if (input === '0') return false
|
|
177
|
-
|
|
178
|
-
return (
|
|
179
|
-
input.startsWith('0') ||
|
|
180
|
-
input.startsWith('.') ||
|
|
181
|
-
input.startsWith('-0') ||
|
|
182
|
-
input.startsWith('-.') ||
|
|
183
|
-
(input.includes('.') && input.endsWith('0')) ||
|
|
184
|
-
input.endsWith('.')
|
|
185
|
-
)
|
|
186
|
-
}
|
|
187
298
|
</script>
|
|
188
299
|
|
|
189
300
|
{#snippet ImmutableField({
|
|
@@ -207,55 +318,7 @@
|
|
|
207
318
|
</div>
|
|
208
319
|
{/snippet}
|
|
209
320
|
|
|
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
321
|
{#if entity}
|
|
256
|
-
{@const ParentFrame = showEditFrameOptions ? DropDownField : ImmutableField}
|
|
257
|
-
{@const ScalarAttribute = showEditFrameOptions ? MutableField : ImmutableField}
|
|
258
|
-
|
|
259
322
|
<div
|
|
260
323
|
id="details-panel"
|
|
261
324
|
class="border-medium bg-extralight absolute top-0 right-0 z-4 m-2 {showEditFrameOptions
|
|
@@ -398,18 +461,22 @@
|
|
|
398
461
|
|
|
399
462
|
<div>
|
|
400
463
|
<strong class="font-semibold">parent frame</strong>
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
464
|
+
{#if showEditFrameOptions}
|
|
465
|
+
<div aria-label="mutable parent frame">
|
|
466
|
+
<List
|
|
467
|
+
options={configFrames.getParentFrameOptions(name.current ?? '') ?? []}
|
|
468
|
+
value={parent.current ?? 'world'}
|
|
469
|
+
on:change={handleParentChange}
|
|
470
|
+
/>
|
|
471
|
+
</div>
|
|
472
|
+
{:else}
|
|
473
|
+
<div class="mt-0.5 flex gap-3">
|
|
474
|
+
{@render ImmutableField({
|
|
475
|
+
ariaLabel: 'parent frame name',
|
|
476
|
+
value: parent.current ?? 'world',
|
|
477
|
+
})}
|
|
478
|
+
</div>
|
|
479
|
+
{/if}
|
|
413
480
|
</div>
|
|
414
481
|
|
|
415
482
|
{#if localPose.current}
|
|
@@ -417,159 +484,162 @@
|
|
|
417
484
|
<strong class="font-semibold">local position</strong>
|
|
418
485
|
<span class="text-subtle-2">(mm)</span>
|
|
419
486
|
|
|
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
|
-
|
|
487
|
+
{#if showEditFrameOptions}
|
|
488
|
+
<div aria-label="mutable local position">
|
|
489
|
+
<Point
|
|
490
|
+
value={{
|
|
491
|
+
x: localPose.current.x,
|
|
492
|
+
y: localPose.current.y,
|
|
493
|
+
z: localPose.current.z,
|
|
494
|
+
}}
|
|
495
|
+
format={formatTwoDecimals}
|
|
496
|
+
on:change={handlePositionChange}
|
|
497
|
+
/>
|
|
498
|
+
</div>
|
|
499
|
+
{:else}
|
|
500
|
+
<div class="mt-0.5 flex gap-3">
|
|
501
|
+
{@render ImmutableField({
|
|
502
|
+
label: 'x',
|
|
503
|
+
ariaLabel: 'local position x coordinate',
|
|
504
|
+
value: localPose.current.x,
|
|
505
|
+
})}
|
|
506
|
+
{@render ImmutableField({
|
|
507
|
+
label: 'y',
|
|
508
|
+
ariaLabel: 'local position y coordinate',
|
|
509
|
+
value: localPose.current.y,
|
|
510
|
+
})}
|
|
511
|
+
{@render ImmutableField({
|
|
512
|
+
label: 'z',
|
|
513
|
+
ariaLabel: 'local position z coordinate',
|
|
514
|
+
value: localPose.current.z,
|
|
515
|
+
})}
|
|
516
|
+
</div>
|
|
517
|
+
{/if}
|
|
449
518
|
</div>
|
|
450
519
|
|
|
451
520
|
<div>
|
|
452
521
|
<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
|
-
|
|
522
|
+
|
|
523
|
+
{#if showEditFrameOptions}
|
|
524
|
+
<div aria-label="mutable local orientation">
|
|
525
|
+
<TabGroup>
|
|
526
|
+
<TabPage title="OV (deg)">
|
|
527
|
+
<Point
|
|
528
|
+
value={{
|
|
529
|
+
x: localPose.current.oX,
|
|
530
|
+
y: localPose.current.oY,
|
|
531
|
+
z: localPose.current.oZ,
|
|
532
|
+
w: localPose.current.theta,
|
|
533
|
+
}}
|
|
534
|
+
format={formatTwoDecimals}
|
|
535
|
+
on:change={handleOrientationOVChange}
|
|
536
|
+
/>
|
|
537
|
+
</TabPage>
|
|
538
|
+
<TabPage title="Euler">
|
|
539
|
+
<RotationEuler
|
|
540
|
+
value={eulerValue}
|
|
541
|
+
unit="deg"
|
|
542
|
+
on:change={handleOrientationEulerChange}
|
|
543
|
+
/>
|
|
544
|
+
</TabPage>
|
|
545
|
+
</TabGroup>
|
|
546
|
+
</div>
|
|
547
|
+
{:else}
|
|
548
|
+
<div class="mt-0.5 flex gap-3">
|
|
549
|
+
{@render ImmutableField({
|
|
550
|
+
label: 'x',
|
|
551
|
+
ariaLabel: 'local orientation x coordinate',
|
|
552
|
+
value: localPose.current.oX,
|
|
553
|
+
})}
|
|
554
|
+
{@render ImmutableField({
|
|
555
|
+
label: 'y',
|
|
556
|
+
ariaLabel: 'local orientation y coordinate',
|
|
557
|
+
value: localPose.current.oY,
|
|
558
|
+
})}
|
|
559
|
+
{@render ImmutableField({
|
|
560
|
+
label: 'z',
|
|
561
|
+
ariaLabel: 'local orientation z coordinate',
|
|
562
|
+
value: localPose.current.oZ,
|
|
563
|
+
})}
|
|
564
|
+
{@render ImmutableField({
|
|
565
|
+
label: 'th',
|
|
566
|
+
ariaLabel: 'local orientation theta degrees',
|
|
567
|
+
value: localPose.current.theta,
|
|
568
|
+
})}
|
|
569
|
+
</div>
|
|
570
|
+
{/if}
|
|
494
571
|
</div>
|
|
495
572
|
{/if}
|
|
496
573
|
|
|
497
574
|
{#if showEditFrameOptions}
|
|
498
575
|
<div>
|
|
499
576
|
<strong class="font-semibold">geometry</strong>
|
|
500
|
-
<
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
</
|
|
508
|
-
<Button
|
|
509
|
-
variant={geometryType === 'box' ? 'dark' : 'primary'}
|
|
510
|
-
class="h-6 px-2 py-1 text-xs"
|
|
511
|
-
onclick={() => setGeometryType('box')}
|
|
512
|
-
>
|
|
513
|
-
Box
|
|
514
|
-
</Button>
|
|
515
|
-
<Button
|
|
516
|
-
variant={geometryType === 'sphere' ? 'dark' : 'primary'}
|
|
517
|
-
class="h-6 px-2 py-1 text-xs"
|
|
518
|
-
onclick={() => setGeometryType('sphere')}
|
|
519
|
-
>
|
|
520
|
-
Sphere
|
|
521
|
-
</Button>
|
|
522
|
-
<Button
|
|
523
|
-
variant={geometryType === 'capsule' ? 'dark' : 'primary'}
|
|
524
|
-
class="h-6 px-2 py-1 text-xs"
|
|
525
|
-
onclick={() => setGeometryType('capsule')}
|
|
526
|
-
>
|
|
527
|
-
Capsule
|
|
528
|
-
</Button>
|
|
577
|
+
<span class="text-subtle-2">(mm)</span>
|
|
578
|
+
<div aria-label="mutable geometry">
|
|
579
|
+
<TabGroup bind:selectedIndex={geometryTabIndex}>
|
|
580
|
+
<TabPage title="None" />
|
|
581
|
+
<TabPage title="Box" />
|
|
582
|
+
<TabPage title="Sphere" />
|
|
583
|
+
<TabPage title="Capsule" />
|
|
584
|
+
</TabGroup>
|
|
529
585
|
</div>
|
|
586
|
+
{#if geometryTabIndex === 1 && box.current}
|
|
587
|
+
<div aria-label="mutable box dimensions">
|
|
588
|
+
<Point
|
|
589
|
+
value={{
|
|
590
|
+
x: box.current.x,
|
|
591
|
+
y: box.current.y,
|
|
592
|
+
z: box.current.z,
|
|
593
|
+
}}
|
|
594
|
+
format={formatTwoDecimals}
|
|
595
|
+
on:change={handleBoxChange}
|
|
596
|
+
/>
|
|
597
|
+
</div>
|
|
598
|
+
{:else if geometryTabIndex === 2 && sphere.current}
|
|
599
|
+
<div aria-label="mutable sphere dimensions">
|
|
600
|
+
<Slider
|
|
601
|
+
label="r"
|
|
602
|
+
value={sphere.current.r}
|
|
603
|
+
format={formatTwoDecimals}
|
|
604
|
+
on:change={handleSphereRChange}
|
|
605
|
+
/>
|
|
606
|
+
</div>
|
|
607
|
+
{:else if geometryTabIndex === 3 && capsule.current}
|
|
608
|
+
<div aria-label="mutable capsule dimensions">
|
|
609
|
+
<Slider
|
|
610
|
+
label="r"
|
|
611
|
+
value={capsule.current.r}
|
|
612
|
+
format={formatTwoDecimals}
|
|
613
|
+
on:change={handleCapsuleRChange}
|
|
614
|
+
/>
|
|
615
|
+
<Slider
|
|
616
|
+
label="l"
|
|
617
|
+
value={capsule.current.l}
|
|
618
|
+
format={formatTwoDecimals}
|
|
619
|
+
on:change={handleCapsuleLChange}
|
|
620
|
+
/>
|
|
621
|
+
</div>
|
|
622
|
+
{/if}
|
|
530
623
|
</div>
|
|
531
|
-
{
|
|
532
|
-
|
|
533
|
-
{#if box.current}
|
|
624
|
+
{:else if box.current}
|
|
534
625
|
<div>
|
|
535
|
-
<strong class="font-semibold">
|
|
626
|
+
<strong class="font-semibold">dimensions</strong>
|
|
536
627
|
<span class="text-subtle-2">(box) (mm)</span>
|
|
537
628
|
<div class="mt-0.5 flex items-center gap-2">
|
|
538
|
-
{@render
|
|
629
|
+
{@render ImmutableField({
|
|
539
630
|
label: 'x',
|
|
540
631
|
ariaLabel: 'box dimensions x value input',
|
|
541
632
|
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
633
|
})}
|
|
550
|
-
{@render
|
|
634
|
+
{@render ImmutableField({
|
|
551
635
|
label: 'y',
|
|
552
636
|
ariaLabel: 'box dimensions y value input',
|
|
553
637
|
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
638
|
})}
|
|
562
|
-
{@render
|
|
639
|
+
{@render ImmutableField({
|
|
563
640
|
label: 'z',
|
|
564
641
|
ariaLabel: 'box dimensions z value input',
|
|
565
642
|
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
643
|
})}
|
|
574
644
|
</div>
|
|
575
645
|
</div>
|
|
@@ -578,29 +648,15 @@
|
|
|
578
648
|
<strong class="font-semibold">dimensions</strong>
|
|
579
649
|
<span class="text-subtle-2">(capsule) (mm)</span>
|
|
580
650
|
<div class="mt-0.5 flex items-center gap-2">
|
|
581
|
-
{@render
|
|
651
|
+
{@render ImmutableField({
|
|
582
652
|
label: 'r',
|
|
583
653
|
ariaLabel: 'capsule dimensions radius value input',
|
|
584
654
|
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
655
|
})}
|
|
593
|
-
{@render
|
|
656
|
+
{@render ImmutableField({
|
|
594
657
|
label: 'l',
|
|
595
658
|
ariaLabel: 'capsule dimensions length value input',
|
|
596
659
|
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
660
|
})}
|
|
605
661
|
</div>
|
|
606
662
|
</div>
|
|
@@ -608,17 +664,10 @@
|
|
|
608
664
|
<div>
|
|
609
665
|
<strong class="font-semibold">dimensions (sphere)</strong>
|
|
610
666
|
<div class="flex items-center gap-2">
|
|
611
|
-
{@render
|
|
667
|
+
{@render ImmutableField({
|
|
612
668
|
label: 'r',
|
|
613
669
|
ariaLabel: 'sphere dimensions radius value',
|
|
614
670
|
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
671
|
})}
|
|
623
672
|
</div>
|
|
624
673
|
</div>
|
|
@@ -653,7 +702,13 @@
|
|
|
653
702
|
name="trash-can-outline"
|
|
654
703
|
class="h-6 cursor-pointer px-2 py-1 text-xs text-red-500"
|
|
655
704
|
onclick={() => {
|
|
656
|
-
entity.
|
|
705
|
+
const sourceUuid = entity.get(traits.UUID)
|
|
706
|
+
const targetUuid = linkedEntity.get(traits.UUID)
|
|
707
|
+
if (sourceUuid && targetUuid) {
|
|
708
|
+
void drawService.deleteRelationship(sourceUuid, targetUuid)
|
|
709
|
+
} else {
|
|
710
|
+
entity.remove(relations.SubEntityLink(linkedEntity))
|
|
711
|
+
}
|
|
657
712
|
}}
|
|
658
713
|
/>
|
|
659
714
|
</div>
|
|
@@ -700,3 +755,14 @@
|
|
|
700
755
|
{/if}
|
|
701
756
|
</div>
|
|
702
757
|
{/if}
|
|
758
|
+
|
|
759
|
+
<style>
|
|
760
|
+
:global(.tp-tabv_i) {
|
|
761
|
+
display: none;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
:global(.tp-lblv),
|
|
765
|
+
:global(.tp-tbpv_c) {
|
|
766
|
+
padding-left: 0 !important;
|
|
767
|
+
}
|
|
768
|
+
</style>
|