@viamrobotics/motion-tools 1.32.0 → 1.33.1

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 (132) hide show
  1. package/dist/components/App.svelte +17 -11
  2. package/dist/components/App.svelte.d.ts +14 -7
  3. package/dist/components/Entities/Entities.svelte +18 -25
  4. package/dist/components/Entities/Entities.svelte.d.ts +2 -17
  5. package/dist/components/Entities/Label.svelte +79 -13
  6. package/dist/components/Entities/Label.svelte.d.ts +2 -1
  7. package/dist/components/Entities/Labels.svelte +36 -0
  8. package/dist/components/Entities/Labels.svelte.d.ts +3 -0
  9. package/dist/components/Entities/LineDots.svelte +8 -3
  10. package/dist/components/Entities/labelLayout/applyTeleports.d.ts +9 -0
  11. package/dist/components/Entities/labelLayout/applyTeleports.js +39 -0
  12. package/dist/components/Entities/labelLayout/buildNeighborhood.d.ts +8 -0
  13. package/dist/components/Entities/labelLayout/buildNeighborhood.js +26 -0
  14. package/dist/components/Entities/labelLayout/cameraHash.d.ts +8 -0
  15. package/dist/components/Entities/labelLayout/cameraHash.js +25 -0
  16. package/dist/components/Entities/labelLayout/cost.d.ts +44 -0
  17. package/dist/components/Entities/labelLayout/cost.js +126 -0
  18. package/dist/components/Entities/labelLayout/createLabelLayout.d.ts +27 -0
  19. package/dist/components/Entities/labelLayout/createLabelLayout.js +194 -0
  20. package/dist/components/Entities/labelLayout/geometry.d.ts +20 -0
  21. package/dist/components/Entities/labelLayout/geometry.js +151 -0
  22. package/dist/components/Entities/labelLayout/labelStore.svelte.d.ts +17 -0
  23. package/dist/components/Entities/labelLayout/labelStore.svelte.js +28 -0
  24. package/dist/components/Entities/labelLayout/measure.d.ts +13 -0
  25. package/dist/components/Entities/labelLayout/measure.js +42 -0
  26. package/dist/components/Entities/labelLayout/slots.d.ts +11 -0
  27. package/dist/components/Entities/labelLayout/slots.js +47 -0
  28. package/dist/components/Entities/labelLayout/solve.d.ts +11 -0
  29. package/dist/components/Entities/labelLayout/solve.js +93 -0
  30. package/dist/components/Entities/labelLayout/spatialHash.d.ts +15 -0
  31. package/dist/components/Entities/labelLayout/spatialHash.js +53 -0
  32. package/dist/components/Entities/labelLayout/types.d.ts +105 -0
  33. package/dist/components/Entities/labelLayout/types.js +19 -0
  34. package/dist/components/Entities/labelLayout/writeBack.d.ts +20 -0
  35. package/dist/components/Entities/labelLayout/writeBack.js +51 -0
  36. package/dist/components/Scene.svelte +42 -48
  37. package/dist/components/SceneProviders.svelte +0 -3
  38. package/dist/components/SelectedTransformControls.svelte +65 -47
  39. package/dist/components/overlay/Details.svelte +198 -224
  40. package/dist/components/overlay/Details.svelte.d.ts +1 -1
  41. package/dist/components/overlay/Popover.svelte +6 -4
  42. package/dist/components/overlay/Popover.svelte.d.ts +6 -2
  43. package/dist/components/overlay/dashboard/Button.svelte +7 -2
  44. package/dist/components/overlay/dashboard/Button.svelte.d.ts +2 -1
  45. package/dist/components/overlay/details/AxesHelperDetails.svelte +32 -0
  46. package/dist/components/overlay/details/AxesHelperDetails.svelte.d.ts +7 -0
  47. package/dist/components/overlay/details/ColorDetails.svelte +35 -0
  48. package/dist/components/overlay/details/ColorDetails.svelte.d.ts +7 -0
  49. package/dist/components/overlay/details/GeometryDetails.svelte +104 -0
  50. package/dist/components/overlay/details/GeometryDetails.svelte.d.ts +7 -0
  51. package/dist/components/overlay/details/LineDetails/LineDetails.svelte +196 -0
  52. package/dist/components/overlay/details/LineDetails/LineDetails.svelte.d.ts +7 -0
  53. package/dist/components/overlay/details/LineDetails/linePositions.d.ts +3 -0
  54. package/dist/components/overlay/details/LineDetails/linePositions.js +30 -0
  55. package/dist/components/overlay/details/OpacityDetails.svelte +44 -0
  56. package/dist/components/overlay/details/OpacityDetails.svelte.d.ts +7 -0
  57. package/dist/components/overlay/details/PoseDetails.svelte +189 -0
  58. package/dist/components/overlay/details/PoseDetails.svelte.d.ts +14 -0
  59. package/dist/components/overlay/settings/ConnectionSettings.svelte +42 -0
  60. package/dist/components/overlay/settings/ConnectionSettings.svelte.d.ts +18 -0
  61. package/dist/components/overlay/settings/DebugSettings.svelte +13 -0
  62. package/dist/components/{xr/frame-configure/Controllers.svelte.d.ts → overlay/settings/DebugSettings.svelte.d.ts} +3 -3
  63. package/dist/components/overlay/settings/PointcloudSettings.svelte +61 -0
  64. package/dist/components/overlay/settings/PointcloudSettings.svelte.d.ts +3 -0
  65. package/dist/components/overlay/settings/SceneSettings.svelte +110 -0
  66. package/dist/components/overlay/settings/SceneSettings.svelte.d.ts +18 -0
  67. package/dist/components/overlay/settings/Settings.svelte +27 -312
  68. package/dist/components/overlay/settings/Settings.svelte.d.ts +8 -1
  69. package/dist/components/overlay/settings/Tabs.svelte +5 -3
  70. package/dist/components/overlay/settings/Tabs.svelte.d.ts +3 -3
  71. package/dist/components/overlay/settings/VisionSettings.svelte +31 -0
  72. package/dist/components/overlay/settings/VisionSettings.svelte.d.ts +3 -0
  73. package/dist/components/overlay/settings/WeblabSettings.svelte +27 -0
  74. package/dist/components/overlay/settings/WeblabSettings.svelte.d.ts +18 -0
  75. package/dist/components/overlay/settings/WidgetSettings.svelte +49 -0
  76. package/dist/components/overlay/settings/WidgetSettings.svelte.d.ts +3 -0
  77. package/dist/components/overlay/widgets/FramePov.svelte +1 -12
  78. package/dist/ecs/traits.d.ts +1 -1
  79. package/dist/ecs/traits.js +1 -1
  80. package/dist/hooks/useWorldState.svelte.js +39 -50
  81. package/dist/{components/xr → plugins/XR}/ArmTeleop.svelte +3 -5
  82. package/dist/plugins/XR/DebugPanel.svelte +29 -0
  83. package/dist/plugins/XR/DebugPanel.svelte.d.ts +3 -0
  84. package/dist/plugins/XR/OriginMarker.svelte +341 -0
  85. package/dist/plugins/XR/PendingEditsPanel.svelte +60 -0
  86. package/dist/plugins/XR/PendingEditsPanel.svelte.d.ts +18 -0
  87. package/dist/plugins/XR/WristDisplay.svelte +60 -0
  88. package/dist/plugins/XR/WristDisplay.svelte.d.ts +19 -0
  89. package/dist/{components/xr → plugins/XR}/XR.svelte +69 -23
  90. package/dist/plugins/XR/XRPlugins.svelte +9 -0
  91. package/dist/plugins/XR/XRPlugins.svelte.d.ts +26 -0
  92. package/dist/plugins/XR/XRSettings.svelte +240 -0
  93. package/dist/plugins/XR/XRSettings.svelte.d.ts +3 -0
  94. package/dist/{components/xr → plugins/XR}/XRToast.svelte +6 -9
  95. package/dist/plugins/XR/debug.svelte.d.ts +7 -0
  96. package/dist/plugins/XR/debug.svelte.js +13 -0
  97. package/dist/plugins/XR/frame-configure/Controllers.svelte +413 -0
  98. package/dist/plugins/XR/teleop/Controllers.svelte.d.ts +3 -0
  99. package/dist/{components/xr → plugins/XR}/useAnchors.svelte.d.ts +4 -0
  100. package/dist/{components/xr → plugins/XR}/useAnchors.svelte.js +22 -0
  101. package/dist/plugins/XR/useOrigin.svelte.d.ts +24 -0
  102. package/dist/plugins/XR/useOrigin.svelte.js +50 -0
  103. package/dist/plugins/index.d.ts +2 -0
  104. package/dist/plugins/index.js +2 -0
  105. package/dist/three/OBBHelper.js +1 -0
  106. package/package.json +3 -1
  107. package/dist/components/xr/OriginMarker.svelte +0 -151
  108. package/dist/components/xr/XRControllerSettings.svelte +0 -242
  109. package/dist/components/xr/XRControllerSettings.svelte.d.ts +0 -3
  110. package/dist/components/xr/frame-configure/Controllers.svelte +0 -6
  111. package/dist/components/xr/useOrigin.svelte.d.ts +0 -9
  112. package/dist/components/xr/useOrigin.svelte.js +0 -27
  113. /package/dist/{components/xr → plugins/XR}/ArmTeleop.svelte.d.ts +0 -0
  114. /package/dist/{components/xr → plugins/XR}/BentPlaneGeometry.svelte +0 -0
  115. /package/dist/{components/xr → plugins/XR}/BentPlaneGeometry.svelte.d.ts +0 -0
  116. /package/dist/{components/xr → plugins/XR}/CameraFeed.svelte +0 -0
  117. /package/dist/{components/xr → plugins/XR}/CameraFeed.svelte.d.ts +0 -0
  118. /package/dist/{components/xr → plugins/XR}/JointLimitsWidget.svelte +0 -0
  119. /package/dist/{components/xr → plugins/XR}/JointLimitsWidget.svelte.d.ts +0 -0
  120. /package/dist/{components/xr → plugins/XR}/OriginMarker.svelte.d.ts +0 -0
  121. /package/dist/{components/xr → plugins/XR}/PointDistance.svelte +0 -0
  122. /package/dist/{components/xr → plugins/XR}/PointDistance.svelte.d.ts +0 -0
  123. /package/dist/{components/xr → plugins/XR}/XR.svelte.d.ts +0 -0
  124. /package/dist/{components/xr → plugins/XR}/XRConfigPanel.svelte +0 -0
  125. /package/dist/{components/xr → plugins/XR}/XRConfigPanel.svelte.d.ts +0 -0
  126. /package/dist/{components/xr → plugins/XR}/XRToast.svelte.d.ts +0 -0
  127. /package/dist/{components/xr/teleop → plugins/XR/frame-configure}/Controllers.svelte.d.ts +0 -0
  128. /package/dist/{components/xr → plugins/XR}/math.d.ts +0 -0
  129. /package/dist/{components/xr → plugins/XR}/math.js +0 -0
  130. /package/dist/{components/xr → plugins/XR}/teleop/Controllers.svelte +0 -0
  131. /package/dist/{components/xr → plugins/XR}/toasts.svelte.d.ts +0 -0
  132. /package/dist/{components/xr → plugins/XR}/toasts.svelte.js +0 -0
@@ -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 { hierarchy, relations, traits, useParentName, useTrait, useWorld } from '../../ecs'
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, invalidate } = useThrelte()
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
- let geometryType = $derived.by<'box' | 'sphere' | 'capsule' | 'none'>(() => {
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'
@@ -126,11 +129,10 @@
126
129
  $effect(() => {
127
130
  // setGeometryType guards against no-ops, so this is safe to fire on every
128
131
  // tab-index change (whether user-initiated or trait-derived).
129
- setGeometryType(geometryTypes[geometryTabIndex])
132
+ detailConfigUpdater.setGeometryType(entity, geometryTypes[geometryTabIndex])
130
133
  })
131
134
 
132
135
  let copied = $state(false)
133
-
134
136
  let dragElement = $state.raw<HTMLElement>()
135
137
 
136
138
  const eulerValue = $derived.by<RotationEulerValueObject>(() => {
@@ -212,23 +214,6 @@
212
214
  detailConfigUpdater.updateGeometry(entity, { type: 'capsule', l: event.detail.value })
213
215
  }
214
216
 
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
217
  const handleParentChange = (event: ListChangeEvent) => {
233
218
  if (event.detail.origin !== 'internal' || !entity) return
234
219
  const value = event.detail.value as string
@@ -237,18 +222,6 @@
237
222
  detailConfigUpdater.setFrameParent(entity, value)
238
223
  }
239
224
 
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
225
  const getCopyClipboardText = () => {
253
226
  return JSON.stringify(
254
227
  {
@@ -327,6 +300,7 @@
327
300
  bind:this={dragElement}
328
301
  >
329
302
  <div class="flex w-[90%] items-center gap-1">
303
+ <PortalTarget id="details-header-icon" />
330
304
  <strong class="overflow-hidden text-nowrap text-ellipsis">{name.current}</strong>
331
305
  <span class="text-subtle-2">{displayType}</span>
332
306
  </div>
@@ -411,6 +385,32 @@
411
385
  <p slot="description">Remove from scene</p>
412
386
  </Tooltip>
413
387
  {/if}
388
+
389
+ <Tooltip
390
+ let:tooltipID
391
+ location="bottom"
392
+ >
393
+ <button
394
+ class="text-subtle-2"
395
+ aria-describedby={tooltipID}
396
+ onclick={async () => {
397
+ try {
398
+ await navigator.clipboard.writeText(getCopyClipboardText())
399
+ } catch {
400
+ // clipboard unavailable (non-secure context or permission denied)
401
+ }
402
+ copied = true
403
+ setTimeout(() => (copied = false), 1000)
404
+ }}
405
+ >
406
+ {#if copied}
407
+ <Check size={14} />
408
+ {:else}
409
+ <Copy size={14} />
410
+ {/if}
411
+ </button>
412
+ <p slot="description">Copy details to clipboard</p>
413
+ </Tooltip>
414
414
  </div>
415
415
 
416
416
  <div class="border-medium -mx-2 w-[100%+0.5rem] border-b"></div>
@@ -425,75 +425,57 @@
425
425
  </p>
426
426
  {/if}
427
427
 
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>
428
+ <h3 class="text-subtle-2 pt-3 pb-2">Details</h3>
448
429
 
449
430
  <div class="flex flex-col gap-2.5">
450
- <div>
451
- <strong class="font-semibold">world position</strong>
452
- <span class="text-subtle-2">(mm)</span>
431
+ {#if !customDetails.current}
432
+ <div>
433
+ <strong class="font-semibold">world position</strong>
434
+ <span class="text-subtle-2">(mm)</span>
453
435
 
454
- <div class="flex gap-3">
455
- <div>
456
- <span class="text-subtle-2">x</span>
457
- {(worldPose?.x ?? 0).toFixed(2)}
458
- </div>
459
- <div>
460
- <span class="text-subtle-2">y</span>
461
- {(worldPose?.y ?? 0).toFixed(2)}
462
- </div>
463
- <div>
464
- <span class="text-subtle-2">z</span>
465
- {(worldPose?.z ?? 0).toFixed(2)}
436
+ <div class="flex gap-3">
437
+ <div>
438
+ <span class="text-subtle-2">x</span>
439
+ {(worldPose?.x ?? 0).toFixed(2)}
440
+ </div>
441
+ <div>
442
+ <span class="text-subtle-2">y</span>
443
+ {(worldPose?.y ?? 0).toFixed(2)}
444
+ </div>
445
+ <div>
446
+ <span class="text-subtle-2">z</span>
447
+ {(worldPose?.z ?? 0).toFixed(2)}
448
+ </div>
466
449
  </div>
467
450
  </div>
468
- </div>
469
451
 
470
- <div>
471
- <strong class="font-semibold">world orientation</strong>
472
- <span class="text-subtle-2">(deg)</span>
473
- <div class="flex gap-3">
474
- <div>
475
- <span class="text-subtle-2">x</span>
476
- {(worldPose?.oX ?? 0).toFixed(2)}
477
- </div>
478
- <div>
479
- <span class="text-subtle-2">y</span>
480
- {(worldPose?.oY ?? 0).toFixed(2)}
481
- </div>
482
- <div>
483
- <span class="text-subtle-2">z</span>
484
- {(worldPose?.oZ ?? 0).toFixed(2)}
485
- </div>
486
- <div>
487
- <span class="text-subtle-2">th</span>
488
- {(worldPose?.theta ?? 0).toFixed(2)}
452
+ <div>
453
+ <strong class="font-semibold">world orientation</strong>
454
+ <span class="text-subtle-2">(deg)</span>
455
+ <div class="flex gap-3">
456
+ <div>
457
+ <span class="text-subtle-2">x</span>
458
+ {(worldPose?.oX ?? 0).toFixed(2)}
459
+ </div>
460
+ <div>
461
+ <span class="text-subtle-2">y</span>
462
+ {(worldPose?.oY ?? 0).toFixed(2)}
463
+ </div>
464
+ <div>
465
+ <span class="text-subtle-2">z</span>
466
+ {(worldPose?.oZ ?? 0).toFixed(2)}
467
+ </div>
468
+ <div>
469
+ <span class="text-subtle-2">th</span>
470
+ {(worldPose?.theta ?? 0).toFixed(2)}
471
+ </div>
489
472
  </div>
490
473
  </div>
491
- </div>
492
474
 
493
- <div>
494
- <strong class="font-semibold">parent frame</strong>
495
- {#if showEditFrameOptions}
496
- <!--
475
+ <div>
476
+ <strong class="font-semibold">parent frame</strong>
477
+ {#if showEditFrameOptions}
478
+ <!--
497
479
  Remount on entity change. svelte-tweakpane-ui's List runs
498
480
  `listBlade.value = value` on the still-mounted blade before its
499
481
  `options` prop has propagated, so the new entity's parent name
@@ -502,113 +484,114 @@
502
484
  event that handleParentChange interprets as a user pick — silently
503
485
  reparenting the clicked frame.
504
486
  -->
505
- {#key entity}
506
- <div aria-label="mutable parent frame">
507
- <List
508
- options={configFrames.getParentFrameOptions(name.current ?? '') ?? []}
509
- value={parent.current ?? 'world'}
510
- on:change={handleParentChange}
511
- />
512
- </div>
513
- {/key}
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>
487
+ {#key entity}
488
+ <div aria-label="mutable parent frame">
489
+ <List
490
+ options={configFrames.getParentFrameOptions(name.current ?? '') ?? []}
491
+ value={parent.current ?? 'world'}
492
+ on:change={handleParentChange}
493
+ />
494
+ </div>
495
+ {/key}
540
496
  {:else}
541
497
  <div class="mt-0.5 flex gap-3">
542
498
  {@render ImmutableField({
543
- label: 'x',
544
- ariaLabel: 'local position x coordinate',
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,
499
+ ariaLabel: 'parent frame name',
500
+ value: parent.current ?? 'world',
556
501
  })}
557
502
  </div>
558
503
  {/if}
559
504
  </div>
560
505
 
561
- <div>
562
- <strong class="font-semibold">local orientation</strong>
506
+ {#if localPose}
507
+ <div>
508
+ <strong class="font-semibold">local position</strong>
509
+ <span class="text-subtle-2">(mm)</span>
510
+
511
+ {#if showEditFrameOptions}
512
+ <div aria-label="mutable local position">
513
+ <Point
514
+ value={{
515
+ x: localPose.x,
516
+ y: localPose.y,
517
+ z: localPose.z,
518
+ }}
519
+ on:change={handlePositionChange}
520
+ />
521
+ </div>
522
+ {:else}
523
+ <div class="mt-0.5 flex gap-3">
524
+ {@render ImmutableField({
525
+ label: 'x',
526
+ ariaLabel: 'local position x coordinate',
527
+ value: localPose.x,
528
+ })}
529
+ {@render ImmutableField({
530
+ label: 'y',
531
+ ariaLabel: 'local position y coordinate',
532
+ value: localPose.y,
533
+ })}
534
+ {@render ImmutableField({
535
+ label: 'z',
536
+ ariaLabel: 'local position z coordinate',
537
+ value: localPose.z,
538
+ })}
539
+ </div>
540
+ {/if}
541
+ </div>
563
542
 
564
- {#if showEditFrameOptions}
565
- <div aria-label="mutable local orientation">
566
- <TabGroup>
567
- <TabPage title="OV (deg)">
568
- <Point
569
- value={{
570
- x: localPose.oX,
571
- y: localPose.oY,
572
- z: localPose.oZ,
573
- w: localPose.theta,
574
- }}
575
- on:change={handleOrientationOVChange}
576
- />
577
- </TabPage>
578
- <TabPage title="Euler">
579
- <RotationEuler
580
- value={eulerValue}
581
- unit="deg"
582
- on:change={handleOrientationEulerChange}
583
- />
584
- </TabPage>
585
- </TabGroup>
586
- </div>
587
- {:else}
588
- <div class="mt-0.5 flex gap-3">
589
- {@render ImmutableField({
590
- label: 'x',
591
- ariaLabel: 'local orientation x coordinate',
592
- value: localPose.oX,
593
- })}
594
- {@render ImmutableField({
595
- label: 'y',
596
- ariaLabel: 'local orientation y coordinate',
597
- value: localPose.oY,
598
- })}
599
- {@render ImmutableField({
600
- label: 'z',
601
- ariaLabel: 'local orientation z coordinate',
602
- value: localPose.oZ,
603
- })}
604
- {@render ImmutableField({
605
- label: 'th',
606
- ariaLabel: 'local orientation theta degrees',
607
- value: localPose.theta,
608
- })}
609
- </div>
610
- {/if}
611
- </div>
543
+ <div>
544
+ <strong class="font-semibold">local orientation</strong>
545
+
546
+ {#if showEditFrameOptions}
547
+ <div aria-label="mutable local orientation">
548
+ <TabGroup>
549
+ <TabPage title="OV (deg)">
550
+ <Point
551
+ value={{
552
+ x: localPose.oX,
553
+ y: localPose.oY,
554
+ z: localPose.oZ,
555
+ w: localPose.theta,
556
+ }}
557
+ on:change={handleOrientationOVChange}
558
+ />
559
+ </TabPage>
560
+ <TabPage title="Euler">
561
+ <RotationEuler
562
+ value={eulerValue}
563
+ unit="deg"
564
+ on:change={handleOrientationEulerChange}
565
+ />
566
+ </TabPage>
567
+ </TabGroup>
568
+ </div>
569
+ {:else}
570
+ <div class="mt-0.5 flex gap-3">
571
+ {@render ImmutableField({
572
+ label: 'x',
573
+ ariaLabel: 'local orientation x coordinate',
574
+ value: localPose.oX,
575
+ })}
576
+ {@render ImmutableField({
577
+ label: 'y',
578
+ ariaLabel: 'local orientation y coordinate',
579
+ value: localPose.oY,
580
+ })}
581
+ {@render ImmutableField({
582
+ label: 'z',
583
+ ariaLabel: 'local orientation z coordinate',
584
+ value: localPose.oZ,
585
+ })}
586
+ {@render ImmutableField({
587
+ label: 'th',
588
+ ariaLabel: 'local orientation theta degrees',
589
+ value: localPose.theta,
590
+ })}
591
+ </div>
592
+ {/if}
593
+ </div>
594
+ {/if}
612
595
  {/if}
613
596
 
614
597
  {#if showEditFrameOptions}
@@ -718,20 +701,6 @@
718
701
  </div>
719
702
  {/if}
720
703
 
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
704
  {#if isInstanceOf(object3d, 'Points')}
736
705
  <div>
737
706
  <strong class="font-semibold">points</strong>
@@ -744,27 +713,32 @@
744
713
  })}
745
714
  </div>
746
715
  {/if}
716
+
717
+ <PortalTarget id="details-extensions" />
718
+
719
+ {#if !customDetails.current}
720
+ <OpacityDetails {entity} />
721
+ <AxesHelperDetails {entity} />
722
+ {/if}
747
723
  </div>
748
724
 
749
725
  {#if linkedEntities.current.length > 0}
750
726
  <h3 class="text-subtle-2 pt-3 pb-2">Relationships</h3>
751
727
 
752
- <div>
753
- <div class="mt-0.5 flex flex-col gap-1">
754
- <strong class="font-semibold">Linked entities</strong>
755
- {#each linkedEntities.current as linkedEntity (linkedEntity)}
756
- {@const linkedEntityName = linkedEntity.get(traits.Name)}
757
- {@const linkType = entity.get(relations.SubEntityLink(linkedEntity))?.type}
758
- <div class="flex items-center gap-1">
759
- <span class="text-primary">{linkedEntityName} ({linkType})</span>
760
- <Icon
761
- name="trash-can-outline"
762
- class="h-6 cursor-pointer px-2 py-1 text-xs text-red-500"
763
- onclick={() => entity.remove(relations.SubEntityLink(linkedEntity))}
764
- />
765
- </div>
766
- {/each}
767
- </div>
728
+ <div class="mt-0.5 flex flex-col gap-1">
729
+ <strong class="font-semibold">Linked entities</strong>
730
+ {#each linkedEntities.current as linkedEntity (linkedEntity)}
731
+ {@const linkedEntityName = linkedEntity.get(traits.Name)}
732
+ {@const linkType = entity.get(relations.SubEntityLink(linkedEntity))?.type}
733
+ <div class="flex items-center gap-1">
734
+ <span class="text-primary">{linkedEntityName} ({linkType})</span>
735
+ <Icon
736
+ name="trash-can-outline"
737
+ class="h-6 cursor-pointer px-2 py-1 text-xs text-red-500"
738
+ onclick={() => entity.remove(relations.SubEntityLink(linkedEntity))}
739
+ />
740
+ </div>
741
+ {/each}
768
742
  </div>
769
743
  {/if}
770
744
 
@@ -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
- children: Snippet;
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, {}, "">;