@viamrobotics/motion-tools 1.33.0 → 1.33.2
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/components/Entities/Entities.svelte +18 -25
- package/dist/components/Entities/Entities.svelte.d.ts +2 -17
- package/dist/components/Entities/Label.svelte +79 -13
- package/dist/components/Entities/Label.svelte.d.ts +2 -1
- package/dist/components/Entities/Labels.svelte +36 -0
- package/dist/components/Entities/Labels.svelte.d.ts +3 -0
- package/dist/components/Entities/LineDots.svelte +8 -3
- package/dist/components/Entities/labelLayout/applyTeleports.d.ts +9 -0
- package/dist/components/Entities/labelLayout/applyTeleports.js +39 -0
- package/dist/components/Entities/labelLayout/buildNeighborhood.d.ts +8 -0
- package/dist/components/Entities/labelLayout/buildNeighborhood.js +26 -0
- package/dist/components/Entities/labelLayout/cameraHash.d.ts +8 -0
- package/dist/components/Entities/labelLayout/cameraHash.js +25 -0
- package/dist/components/Entities/labelLayout/cost.d.ts +44 -0
- package/dist/components/Entities/labelLayout/cost.js +126 -0
- package/dist/components/Entities/labelLayout/createLabelLayout.d.ts +27 -0
- package/dist/components/Entities/labelLayout/createLabelLayout.js +194 -0
- package/dist/components/Entities/labelLayout/geometry.d.ts +20 -0
- package/dist/components/Entities/labelLayout/geometry.js +151 -0
- package/dist/components/Entities/labelLayout/labelStore.svelte.d.ts +17 -0
- package/dist/components/Entities/labelLayout/labelStore.svelte.js +28 -0
- package/dist/components/Entities/labelLayout/measure.d.ts +13 -0
- package/dist/components/Entities/labelLayout/measure.js +42 -0
- package/dist/components/Entities/labelLayout/slots.d.ts +11 -0
- package/dist/components/Entities/labelLayout/slots.js +47 -0
- package/dist/components/Entities/labelLayout/solve.d.ts +11 -0
- package/dist/components/Entities/labelLayout/solve.js +93 -0
- package/dist/components/Entities/labelLayout/spatialHash.d.ts +15 -0
- package/dist/components/Entities/labelLayout/spatialHash.js +53 -0
- package/dist/components/Entities/labelLayout/types.d.ts +105 -0
- package/dist/components/Entities/labelLayout/types.js +19 -0
- package/dist/components/Entities/labelLayout/writeBack.d.ts +20 -0
- package/dist/components/Entities/labelLayout/writeBack.js +51 -0
- package/dist/components/Scene.svelte +2 -1
- package/dist/components/SelectedTransformControls.svelte +65 -47
- package/dist/components/overlay/Details.svelte +210 -226
- package/dist/components/overlay/Details.svelte.d.ts +1 -1
- package/dist/components/overlay/Popover.svelte +6 -4
- package/dist/components/overlay/Popover.svelte.d.ts +6 -2
- package/dist/components/overlay/dashboard/Button.svelte +7 -2
- package/dist/components/overlay/dashboard/Button.svelte.d.ts +2 -1
- package/dist/components/overlay/details/AxesHelperDetails.svelte +32 -0
- package/dist/components/overlay/details/AxesHelperDetails.svelte.d.ts +7 -0
- package/dist/components/overlay/details/ColorDetails.svelte +35 -0
- package/dist/components/overlay/details/ColorDetails.svelte.d.ts +7 -0
- package/dist/components/overlay/details/GeometryDetails.svelte +104 -0
- package/dist/components/overlay/details/GeometryDetails.svelte.d.ts +7 -0
- package/dist/components/overlay/details/LineDetails/LineDetails.svelte +196 -0
- package/dist/components/overlay/details/LineDetails/LineDetails.svelte.d.ts +7 -0
- package/dist/components/overlay/details/LineDetails/linePositions.d.ts +3 -0
- package/dist/components/overlay/details/LineDetails/linePositions.js +30 -0
- package/dist/components/overlay/details/OpacityDetails.svelte +44 -0
- package/dist/components/overlay/details/OpacityDetails.svelte.d.ts +7 -0
- package/dist/components/overlay/details/PoseDetails.svelte +189 -0
- package/dist/components/overlay/details/PoseDetails.svelte.d.ts +14 -0
- package/dist/ecs/traits.d.ts +1 -1
- package/dist/ecs/traits.js +1 -1
- package/dist/hooks/usePartConfig.svelte.js +8 -6
- package/dist/hooks/useWorldState.svelte.js +94 -69
- package/package.json +4 -2
|
@@ -13,13 +13,14 @@
|
|
|
13
13
|
|
|
14
14
|
<script lang="ts">
|
|
15
15
|
import type { Pose } from '@viamrobotics/sdk'
|
|
16
|
-
import type { Entity } from 'koota'
|
|
17
16
|
import type { Snippet } from 'svelte'
|
|
18
17
|
import type { HTMLAttributes } from 'svelte/elements'
|
|
19
18
|
|
|
20
19
|
import { draggable } from '@neodrag/svelte'
|
|
21
20
|
import { isInstanceOf, useThrelte } from '@threlte/core'
|
|
21
|
+
import { PortalTarget } from '@threlte/extras'
|
|
22
22
|
import { Button, Icon, Tooltip } from '@viamrobotics/prime-core'
|
|
23
|
+
import { type Entity } from 'koota'
|
|
23
24
|
import { Check, Copy } from 'lucide-svelte'
|
|
24
25
|
import {
|
|
25
26
|
List,
|
|
@@ -38,7 +39,9 @@
|
|
|
38
39
|
} from 'svelte-tweakpane-ui'
|
|
39
40
|
|
|
40
41
|
import AddRelationship from './AddRelationship.svelte'
|
|
41
|
-
import
|
|
42
|
+
import AxesHelperDetails from './details/AxesHelperDetails.svelte'
|
|
43
|
+
import OpacityDetails from './details/OpacityDetails.svelte'
|
|
44
|
+
import { hierarchy, relations, traits, useParentName, useTag, useTrait, useWorld } from '../../ecs'
|
|
42
45
|
import { FrameConfigUpdater } from '../../FrameConfigUpdater.svelte'
|
|
43
46
|
import { useConfigFrames } from '../../hooks/useConfigFrames.svelte'
|
|
44
47
|
import { useCameraControls } from '../../hooks/useControls.svelte'
|
|
@@ -58,7 +61,7 @@
|
|
|
58
61
|
const { entity, details, ...rest }: Props = $props()
|
|
59
62
|
|
|
60
63
|
const world = useWorld()
|
|
61
|
-
const { scene
|
|
64
|
+
const { scene } = useThrelte()
|
|
62
65
|
const controls = useCameraControls()
|
|
63
66
|
const resourceByName = useResourceByName()
|
|
64
67
|
const configFrames = useConfigFrames()
|
|
@@ -82,9 +85,9 @@
|
|
|
82
85
|
const removable = useTrait(() => entity, traits.Removable)
|
|
83
86
|
const points = useTrait(() => entity, traits.Points)
|
|
84
87
|
const arrows = useTrait(() => entity, traits.Arrows)
|
|
85
|
-
const opacity = useTrait(() => entity, traits.Opacity)
|
|
86
88
|
const framesAPI = useTrait(() => entity, traits.FramesAPI)
|
|
87
89
|
const geometriesAPI = useTrait(() => entity, traits.GeometriesAPI)
|
|
90
|
+
const customDetails = useTag(() => entity, traits.CustomDetails)
|
|
88
91
|
|
|
89
92
|
const localPose = $derived.by<Pose | undefined>(() => {
|
|
90
93
|
const source = editedMatrix.current ?? matrix.current
|
|
@@ -112,7 +115,7 @@
|
|
|
112
115
|
const resourceName = $derived(name.current ? resourceByName.current[name.current] : undefined)
|
|
113
116
|
const displayType = $derived(isFrameNode ? resourceName?.subtype : isGeometry ? 'geometry' : '')
|
|
114
117
|
|
|
115
|
-
|
|
118
|
+
const geometryType = $derived.by(() => {
|
|
116
119
|
if (box.current) return 'box'
|
|
117
120
|
if (sphere.current) return 'sphere'
|
|
118
121
|
if (capsule.current) return 'capsule'
|
|
@@ -124,13 +127,22 @@
|
|
|
124
127
|
let geometryTabIndex = $derived(geometryTypes.indexOf(geometryType))
|
|
125
128
|
|
|
126
129
|
$effect(() => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
const nextType = geometryTypes[geometryTabIndex]
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* geometryTabIndex is derived from the entity's geometry traits, so on
|
|
134
|
+
* selection (or any trait-driven recompute) nextType already equals
|
|
135
|
+
* geometryType — firing then would call updateFrame, dirtying the part
|
|
136
|
+
* config and resetting the geometry to default dimensions. Only a user
|
|
137
|
+
* tab pick sets geometryTabIndex ahead of the trait, so guard on the two
|
|
138
|
+
* differing to fire solely for user-initiated changes.
|
|
139
|
+
*/
|
|
140
|
+
if (nextType === geometryType) return
|
|
141
|
+
|
|
142
|
+
detailConfigUpdater.setGeometryType(entity, nextType)
|
|
130
143
|
})
|
|
131
144
|
|
|
132
145
|
let copied = $state(false)
|
|
133
|
-
|
|
134
146
|
let dragElement = $state.raw<HTMLElement>()
|
|
135
147
|
|
|
136
148
|
const eulerValue = $derived.by<RotationEulerValueObject>(() => {
|
|
@@ -212,23 +224,6 @@
|
|
|
212
224
|
detailConfigUpdater.updateGeometry(entity, { type: 'capsule', l: event.detail.value })
|
|
213
225
|
}
|
|
214
226
|
|
|
215
|
-
const opacityValue = $derived(opacity.current ?? 1)
|
|
216
|
-
|
|
217
|
-
const handleOpacityChange = (event: SliderChangeEvent) => {
|
|
218
|
-
if (event.detail.origin !== 'internal' || !entity) return
|
|
219
|
-
const next = event.detail.value
|
|
220
|
-
// No trait === fully opaque, so drop the trait when the user returns to 1
|
|
221
|
-
// instead of leaving an Opacity(1) entry on the entity.
|
|
222
|
-
if (next >= 1) {
|
|
223
|
-
entity.remove(traits.Opacity)
|
|
224
|
-
} else if (entity.has(traits.Opacity)) {
|
|
225
|
-
entity.set(traits.Opacity, next)
|
|
226
|
-
} else {
|
|
227
|
-
entity.add(traits.Opacity(next))
|
|
228
|
-
}
|
|
229
|
-
invalidate()
|
|
230
|
-
}
|
|
231
|
-
|
|
232
227
|
const handleParentChange = (event: ListChangeEvent) => {
|
|
233
228
|
if (event.detail.origin !== 'internal' || !entity) return
|
|
234
229
|
const value = event.detail.value as string
|
|
@@ -237,18 +232,6 @@
|
|
|
237
232
|
detailConfigUpdater.setFrameParent(entity, value)
|
|
238
233
|
}
|
|
239
234
|
|
|
240
|
-
const setGeometryType = (type: 'none' | 'box' | 'sphere' | 'capsule') => {
|
|
241
|
-
if (type === geometryType) {
|
|
242
|
-
return
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
geometryType = type
|
|
246
|
-
|
|
247
|
-
if (entity) {
|
|
248
|
-
detailConfigUpdater.setGeometryType(entity, type)
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
235
|
const getCopyClipboardText = () => {
|
|
253
236
|
return JSON.stringify(
|
|
254
237
|
{
|
|
@@ -327,6 +310,7 @@
|
|
|
327
310
|
bind:this={dragElement}
|
|
328
311
|
>
|
|
329
312
|
<div class="flex w-[90%] items-center gap-1">
|
|
313
|
+
<PortalTarget id="details-header-icon" />
|
|
330
314
|
<strong class="overflow-hidden text-nowrap text-ellipsis">{name.current}</strong>
|
|
331
315
|
<span class="text-subtle-2">{displayType}</span>
|
|
332
316
|
</div>
|
|
@@ -411,6 +395,32 @@
|
|
|
411
395
|
<p slot="description">Remove from scene</p>
|
|
412
396
|
</Tooltip>
|
|
413
397
|
{/if}
|
|
398
|
+
|
|
399
|
+
<Tooltip
|
|
400
|
+
let:tooltipID
|
|
401
|
+
location="bottom"
|
|
402
|
+
>
|
|
403
|
+
<button
|
|
404
|
+
class="text-subtle-2"
|
|
405
|
+
aria-describedby={tooltipID}
|
|
406
|
+
onclick={async () => {
|
|
407
|
+
try {
|
|
408
|
+
await navigator.clipboard.writeText(getCopyClipboardText())
|
|
409
|
+
} catch {
|
|
410
|
+
// clipboard unavailable (non-secure context or permission denied)
|
|
411
|
+
}
|
|
412
|
+
copied = true
|
|
413
|
+
setTimeout(() => (copied = false), 1000)
|
|
414
|
+
}}
|
|
415
|
+
>
|
|
416
|
+
{#if copied}
|
|
417
|
+
<Check size={14} />
|
|
418
|
+
{:else}
|
|
419
|
+
<Copy size={14} />
|
|
420
|
+
{/if}
|
|
421
|
+
</button>
|
|
422
|
+
<p slot="description">Copy details to clipboard</p>
|
|
423
|
+
</Tooltip>
|
|
414
424
|
</div>
|
|
415
425
|
|
|
416
426
|
<div class="border-medium -mx-2 w-[100%+0.5rem] border-b"></div>
|
|
@@ -425,75 +435,57 @@
|
|
|
425
435
|
</p>
|
|
426
436
|
{/if}
|
|
427
437
|
|
|
428
|
-
<h3
|
|
429
|
-
class="text-subtle-2 flex justify-between py-2"
|
|
430
|
-
data-testid="details-header"
|
|
431
|
-
>
|
|
432
|
-
Details
|
|
433
|
-
|
|
434
|
-
<button
|
|
435
|
-
onclick={async () => {
|
|
436
|
-
navigator.clipboard.writeText(getCopyClipboardText())
|
|
437
|
-
copied = true
|
|
438
|
-
setTimeout(() => (copied = false), 1000)
|
|
439
|
-
}}
|
|
440
|
-
>
|
|
441
|
-
{#if copied}
|
|
442
|
-
<Check size={14} />
|
|
443
|
-
{:else}
|
|
444
|
-
<Copy size={14} />
|
|
445
|
-
{/if}
|
|
446
|
-
</button>
|
|
447
|
-
</h3>
|
|
438
|
+
<h3 class="text-subtle-2 pt-3 pb-2">Details</h3>
|
|
448
439
|
|
|
449
440
|
<div class="flex flex-col gap-2.5">
|
|
450
|
-
|
|
451
|
-
<
|
|
452
|
-
|
|
441
|
+
{#if !customDetails.current}
|
|
442
|
+
<div>
|
|
443
|
+
<strong class="font-semibold">world position</strong>
|
|
444
|
+
<span class="text-subtle-2">(mm)</span>
|
|
453
445
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
446
|
+
<div class="flex gap-3">
|
|
447
|
+
<div>
|
|
448
|
+
<span class="text-subtle-2">x</span>
|
|
449
|
+
{(worldPose?.x ?? 0).toFixed(2)}
|
|
450
|
+
</div>
|
|
451
|
+
<div>
|
|
452
|
+
<span class="text-subtle-2">y</span>
|
|
453
|
+
{(worldPose?.y ?? 0).toFixed(2)}
|
|
454
|
+
</div>
|
|
455
|
+
<div>
|
|
456
|
+
<span class="text-subtle-2">z</span>
|
|
457
|
+
{(worldPose?.z ?? 0).toFixed(2)}
|
|
458
|
+
</div>
|
|
466
459
|
</div>
|
|
467
460
|
</div>
|
|
468
|
-
</div>
|
|
469
461
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
462
|
+
<div>
|
|
463
|
+
<strong class="font-semibold">world orientation</strong>
|
|
464
|
+
<span class="text-subtle-2">(deg)</span>
|
|
465
|
+
<div class="flex gap-3">
|
|
466
|
+
<div>
|
|
467
|
+
<span class="text-subtle-2">x</span>
|
|
468
|
+
{(worldPose?.oX ?? 0).toFixed(2)}
|
|
469
|
+
</div>
|
|
470
|
+
<div>
|
|
471
|
+
<span class="text-subtle-2">y</span>
|
|
472
|
+
{(worldPose?.oY ?? 0).toFixed(2)}
|
|
473
|
+
</div>
|
|
474
|
+
<div>
|
|
475
|
+
<span class="text-subtle-2">z</span>
|
|
476
|
+
{(worldPose?.oZ ?? 0).toFixed(2)}
|
|
477
|
+
</div>
|
|
478
|
+
<div>
|
|
479
|
+
<span class="text-subtle-2">th</span>
|
|
480
|
+
{(worldPose?.theta ?? 0).toFixed(2)}
|
|
481
|
+
</div>
|
|
489
482
|
</div>
|
|
490
483
|
</div>
|
|
491
|
-
</div>
|
|
492
484
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
485
|
+
<div>
|
|
486
|
+
<strong class="font-semibold">parent frame</strong>
|
|
487
|
+
{#if showEditFrameOptions}
|
|
488
|
+
<!--
|
|
497
489
|
Remount on entity change. svelte-tweakpane-ui's List runs
|
|
498
490
|
`listBlade.value = value` on the still-mounted blade before its
|
|
499
491
|
`options` prop has propagated, so the new entity's parent name
|
|
@@ -502,113 +494,114 @@
|
|
|
502
494
|
event that handleParentChange interprets as a user pick — silently
|
|
503
495
|
reparenting the clicked frame.
|
|
504
496
|
-->
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
{:else}
|
|
515
|
-
<div class="mt-0.5 flex gap-3">
|
|
516
|
-
{@render ImmutableField({
|
|
517
|
-
ariaLabel: 'parent frame name',
|
|
518
|
-
value: parent.current ?? 'world',
|
|
519
|
-
})}
|
|
520
|
-
</div>
|
|
521
|
-
{/if}
|
|
522
|
-
</div>
|
|
523
|
-
|
|
524
|
-
{#if localPose}
|
|
525
|
-
<div>
|
|
526
|
-
<strong class="font-semibold">local position</strong>
|
|
527
|
-
<span class="text-subtle-2">(mm)</span>
|
|
528
|
-
|
|
529
|
-
{#if showEditFrameOptions}
|
|
530
|
-
<div aria-label="mutable local position">
|
|
531
|
-
<Point
|
|
532
|
-
value={{
|
|
533
|
-
x: localPose.x,
|
|
534
|
-
y: localPose.y,
|
|
535
|
-
z: localPose.z,
|
|
536
|
-
}}
|
|
537
|
-
on:change={handlePositionChange}
|
|
538
|
-
/>
|
|
539
|
-
</div>
|
|
497
|
+
{#key entity}
|
|
498
|
+
<div aria-label="mutable parent frame">
|
|
499
|
+
<List
|
|
500
|
+
options={configFrames.getParentFrameOptions(name.current ?? '') ?? []}
|
|
501
|
+
value={parent.current ?? 'world'}
|
|
502
|
+
on:change={handleParentChange}
|
|
503
|
+
/>
|
|
504
|
+
</div>
|
|
505
|
+
{/key}
|
|
540
506
|
{:else}
|
|
541
507
|
<div class="mt-0.5 flex gap-3">
|
|
542
508
|
{@render ImmutableField({
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
value: localPose.x,
|
|
546
|
-
})}
|
|
547
|
-
{@render ImmutableField({
|
|
548
|
-
label: 'y',
|
|
549
|
-
ariaLabel: 'local position y coordinate',
|
|
550
|
-
value: localPose.y,
|
|
551
|
-
})}
|
|
552
|
-
{@render ImmutableField({
|
|
553
|
-
label: 'z',
|
|
554
|
-
ariaLabel: 'local position z coordinate',
|
|
555
|
-
value: localPose.z,
|
|
509
|
+
ariaLabel: 'parent frame name',
|
|
510
|
+
value: parent.current ?? 'world',
|
|
556
511
|
})}
|
|
557
512
|
</div>
|
|
558
513
|
{/if}
|
|
559
514
|
</div>
|
|
560
515
|
|
|
561
|
-
|
|
562
|
-
<
|
|
516
|
+
{#if localPose}
|
|
517
|
+
<div>
|
|
518
|
+
<strong class="font-semibold">local position</strong>
|
|
519
|
+
<span class="text-subtle-2">(mm)</span>
|
|
520
|
+
|
|
521
|
+
{#if showEditFrameOptions}
|
|
522
|
+
<div aria-label="mutable local position">
|
|
523
|
+
<Point
|
|
524
|
+
value={{
|
|
525
|
+
x: localPose.x,
|
|
526
|
+
y: localPose.y,
|
|
527
|
+
z: localPose.z,
|
|
528
|
+
}}
|
|
529
|
+
on:change={handlePositionChange}
|
|
530
|
+
/>
|
|
531
|
+
</div>
|
|
532
|
+
{:else}
|
|
533
|
+
<div class="mt-0.5 flex gap-3">
|
|
534
|
+
{@render ImmutableField({
|
|
535
|
+
label: 'x',
|
|
536
|
+
ariaLabel: 'local position x coordinate',
|
|
537
|
+
value: localPose.x,
|
|
538
|
+
})}
|
|
539
|
+
{@render ImmutableField({
|
|
540
|
+
label: 'y',
|
|
541
|
+
ariaLabel: 'local position y coordinate',
|
|
542
|
+
value: localPose.y,
|
|
543
|
+
})}
|
|
544
|
+
{@render ImmutableField({
|
|
545
|
+
label: 'z',
|
|
546
|
+
ariaLabel: 'local position z coordinate',
|
|
547
|
+
value: localPose.z,
|
|
548
|
+
})}
|
|
549
|
+
</div>
|
|
550
|
+
{/if}
|
|
551
|
+
</div>
|
|
563
552
|
|
|
564
|
-
|
|
565
|
-
<
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
553
|
+
<div>
|
|
554
|
+
<strong class="font-semibold">local orientation</strong>
|
|
555
|
+
|
|
556
|
+
{#if showEditFrameOptions}
|
|
557
|
+
<div aria-label="mutable local orientation">
|
|
558
|
+
<TabGroup>
|
|
559
|
+
<TabPage title="OV (deg)">
|
|
560
|
+
<Point
|
|
561
|
+
value={{
|
|
562
|
+
x: localPose.oX,
|
|
563
|
+
y: localPose.oY,
|
|
564
|
+
z: localPose.oZ,
|
|
565
|
+
w: localPose.theta,
|
|
566
|
+
}}
|
|
567
|
+
on:change={handleOrientationOVChange}
|
|
568
|
+
/>
|
|
569
|
+
</TabPage>
|
|
570
|
+
<TabPage title="Euler">
|
|
571
|
+
<RotationEuler
|
|
572
|
+
value={eulerValue}
|
|
573
|
+
unit="deg"
|
|
574
|
+
on:change={handleOrientationEulerChange}
|
|
575
|
+
/>
|
|
576
|
+
</TabPage>
|
|
577
|
+
</TabGroup>
|
|
578
|
+
</div>
|
|
579
|
+
{:else}
|
|
580
|
+
<div class="mt-0.5 flex gap-3">
|
|
581
|
+
{@render ImmutableField({
|
|
582
|
+
label: 'x',
|
|
583
|
+
ariaLabel: 'local orientation x coordinate',
|
|
584
|
+
value: localPose.oX,
|
|
585
|
+
})}
|
|
586
|
+
{@render ImmutableField({
|
|
587
|
+
label: 'y',
|
|
588
|
+
ariaLabel: 'local orientation y coordinate',
|
|
589
|
+
value: localPose.oY,
|
|
590
|
+
})}
|
|
591
|
+
{@render ImmutableField({
|
|
592
|
+
label: 'z',
|
|
593
|
+
ariaLabel: 'local orientation z coordinate',
|
|
594
|
+
value: localPose.oZ,
|
|
595
|
+
})}
|
|
596
|
+
{@render ImmutableField({
|
|
597
|
+
label: 'th',
|
|
598
|
+
ariaLabel: 'local orientation theta degrees',
|
|
599
|
+
value: localPose.theta,
|
|
600
|
+
})}
|
|
601
|
+
</div>
|
|
602
|
+
{/if}
|
|
603
|
+
</div>
|
|
604
|
+
{/if}
|
|
612
605
|
{/if}
|
|
613
606
|
|
|
614
607
|
{#if showEditFrameOptions}
|
|
@@ -718,20 +711,6 @@
|
|
|
718
711
|
</div>
|
|
719
712
|
{/if}
|
|
720
713
|
|
|
721
|
-
<div>
|
|
722
|
-
<strong class="font-semibold">opacity</strong>
|
|
723
|
-
<div aria-label="mutable opacity">
|
|
724
|
-
<Slider
|
|
725
|
-
value={opacityValue}
|
|
726
|
-
min={0}
|
|
727
|
-
max={1}
|
|
728
|
-
step={0.01}
|
|
729
|
-
format={(v) => v.toFixed(2)}
|
|
730
|
-
on:change={handleOpacityChange}
|
|
731
|
-
/>
|
|
732
|
-
</div>
|
|
733
|
-
</div>
|
|
734
|
-
|
|
735
714
|
{#if isInstanceOf(object3d, 'Points')}
|
|
736
715
|
<div>
|
|
737
716
|
<strong class="font-semibold">points</strong>
|
|
@@ -744,27 +723,32 @@
|
|
|
744
723
|
})}
|
|
745
724
|
</div>
|
|
746
725
|
{/if}
|
|
726
|
+
|
|
727
|
+
<PortalTarget id="details-extensions" />
|
|
728
|
+
|
|
729
|
+
{#if !customDetails.current}
|
|
730
|
+
<OpacityDetails {entity} />
|
|
731
|
+
<AxesHelperDetails {entity} />
|
|
732
|
+
{/if}
|
|
747
733
|
</div>
|
|
748
734
|
|
|
749
735
|
{#if linkedEntities.current.length > 0}
|
|
750
736
|
<h3 class="text-subtle-2 pt-3 pb-2">Relationships</h3>
|
|
751
737
|
|
|
752
|
-
<div>
|
|
753
|
-
<
|
|
754
|
-
|
|
755
|
-
{
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
<
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
{/each}
|
|
767
|
-
</div>
|
|
738
|
+
<div class="mt-0.5 flex flex-col gap-1">
|
|
739
|
+
<strong class="font-semibold">Linked entities</strong>
|
|
740
|
+
{#each linkedEntities.current as linkedEntity (linkedEntity)}
|
|
741
|
+
{@const linkedEntityName = linkedEntity.get(traits.Name)}
|
|
742
|
+
{@const linkType = entity.get(relations.SubEntityLink(linkedEntity))?.type}
|
|
743
|
+
<div class="flex items-center gap-1">
|
|
744
|
+
<span class="text-primary">{linkedEntityName} ({linkType})</span>
|
|
745
|
+
<Icon
|
|
746
|
+
name="trash-can-outline"
|
|
747
|
+
class="h-6 cursor-pointer px-2 py-1 text-xs text-red-500"
|
|
748
|
+
onclick={() => entity.remove(relations.SubEntityLink(linkedEntity))}
|
|
749
|
+
/>
|
|
750
|
+
</div>
|
|
751
|
+
{/each}
|
|
768
752
|
</div>
|
|
769
753
|
{/if}
|
|
770
754
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { Entity } from 'koota';
|
|
2
1
|
import type { Snippet } from 'svelte';
|
|
3
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type Entity } from 'koota';
|
|
4
4
|
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
5
5
|
entity: Entity;
|
|
6
6
|
details?: Snippet<[{
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import { normalizeProps, portal, useMachine } from '@zag-js/svelte'
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
|
-
trigger: Snippet<[HTMLButtonAttributes]>
|
|
10
|
-
children: Snippet
|
|
9
|
+
trigger: Snippet<[HTMLButtonAttributes, { isOpen: boolean }]>
|
|
10
|
+
children: Snippet<[{ close: () => void }]>
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
let { children, trigger }: Props = $props()
|
|
@@ -15,15 +15,17 @@
|
|
|
15
15
|
const id = $props.id()
|
|
16
16
|
const service = useMachine(popover.machine, { id })
|
|
17
17
|
const api = $derived(popover.connect(service, normalizeProps))
|
|
18
|
+
|
|
19
|
+
const close = () => api.setOpen(false)
|
|
18
20
|
</script>
|
|
19
21
|
|
|
20
|
-
{@render trigger(api.getTriggerProps())}
|
|
22
|
+
{@render trigger(api.getTriggerProps(), { isOpen: api.open })}
|
|
21
23
|
|
|
22
24
|
<div
|
|
23
25
|
use:portal={{ disabled: !api.portalled }}
|
|
24
26
|
{...api.getPositionerProps()}
|
|
25
27
|
>
|
|
26
28
|
<div {...api.getContentProps()}>
|
|
27
|
-
{@render children()}
|
|
29
|
+
{@render children({ close })}
|
|
28
30
|
</div>
|
|
29
31
|
</div>
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
3
|
interface Props {
|
|
4
|
-
trigger: Snippet<[HTMLButtonAttributes
|
|
5
|
-
|
|
4
|
+
trigger: Snippet<[HTMLButtonAttributes, {
|
|
5
|
+
isOpen: boolean;
|
|
6
|
+
}]>;
|
|
7
|
+
children: Snippet<[{
|
|
8
|
+
close: () => void;
|
|
9
|
+
}]>;
|
|
6
10
|
}
|
|
7
11
|
declare const Popover: import("svelte").Component<Props, {}, "">;
|
|
8
12
|
type Popover = ReturnType<typeof Popover>;
|
|
@@ -2,15 +2,16 @@
|
|
|
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 { Focus, MousePointer2, Ruler } from 'lucide-svelte'
|
|
5
|
+
import { Focus, MousePointer2, Ruler, Shapes } from 'lucide-svelte'
|
|
6
6
|
|
|
7
7
|
interface Props extends HTMLButtonAttributes {
|
|
8
|
-
icon: IconName | 'ruler' | 'mouse-pointer' | 'focus'
|
|
8
|
+
icon: IconName | 'ruler' | 'mouse-pointer' | 'shapes' | 'focus'
|
|
9
9
|
active?: boolean
|
|
10
10
|
description: string
|
|
11
11
|
hotkey?: string
|
|
12
12
|
class?: ClassValue | null | undefined
|
|
13
13
|
tooltipLocation?: 'bottom' | 'right' | 'left' | 'top'
|
|
14
|
+
disableTooltip?: boolean
|
|
14
15
|
onclick?: MouseEventHandler<HTMLButtonElement> | null | undefined
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -21,6 +22,7 @@
|
|
|
21
22
|
hotkey = '',
|
|
22
23
|
class: className = '',
|
|
23
24
|
tooltipLocation,
|
|
25
|
+
disableTooltip = false,
|
|
24
26
|
onclick,
|
|
25
27
|
...rest
|
|
26
28
|
}: Props = $props()
|
|
@@ -29,6 +31,7 @@
|
|
|
29
31
|
<Tooltip
|
|
30
32
|
let:tooltipID
|
|
31
33
|
location={tooltipLocation ?? 'bottom'}
|
|
34
|
+
state={disableTooltip ? 'invisible' : undefined}
|
|
32
35
|
>
|
|
33
36
|
<label
|
|
34
37
|
class={[
|
|
@@ -50,6 +53,8 @@
|
|
|
50
53
|
<Ruler size="16" />
|
|
51
54
|
{:else if icon === 'mouse-pointer'}
|
|
52
55
|
<MousePointer2 size="16" />
|
|
56
|
+
{:else if icon === 'shapes'}
|
|
57
|
+
<Shapes size="16" />
|
|
53
58
|
{:else if icon === 'focus'}
|
|
54
59
|
<Focus size="16" />
|
|
55
60
|
{:else}
|
|
@@ -1,12 +1,13 @@
|
|
|
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' | 'mouse-pointer' | 'focus';
|
|
4
|
+
icon: IconName | 'ruler' | 'mouse-pointer' | 'shapes' | 'focus';
|
|
5
5
|
active?: boolean;
|
|
6
6
|
description: string;
|
|
7
7
|
hotkey?: string;
|
|
8
8
|
class?: ClassValue | null | undefined;
|
|
9
9
|
tooltipLocation?: 'bottom' | 'right' | 'left' | 'top';
|
|
10
|
+
disableTooltip?: boolean;
|
|
10
11
|
onclick?: MouseEventHandler<HTMLButtonElement> | null | undefined;
|
|
11
12
|
}
|
|
12
13
|
declare const Button: import("svelte").Component<Props, {}, "">;
|