@viamrobotics/motion-tools 1.31.0 → 1.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/dist/components/App.svelte +64 -53
  2. package/dist/components/App.svelte.d.ts +14 -7
  3. package/dist/components/Entities/Arrows/Arrows.svelte +4 -7
  4. package/dist/components/Entities/hooks/useEntityEvents.svelte.d.ts +0 -1
  5. package/dist/components/Entities/hooks/useEntityEvents.svelte.js +30 -16
  6. package/dist/components/InputBindings.svelte +0 -43
  7. package/dist/components/KeyboardBindings.svelte +38 -0
  8. package/dist/components/KeyboardBindings.svelte.d.ts +18 -0
  9. package/dist/components/PointerMissBox.svelte +6 -3
  10. package/dist/components/Scene.svelte +43 -61
  11. package/dist/components/SceneProviders.svelte +2 -7
  12. package/dist/components/SceneProviders.svelte.d.ts +1 -3
  13. package/dist/components/Selected.svelte +20 -27
  14. package/dist/components/SelectedTransformControls.svelte +8 -7
  15. package/dist/components/StaticGeometries.svelte +3 -5
  16. package/dist/components/hover/HoveredEntities.svelte +15 -14
  17. package/dist/components/hover/HoveredEntities.svelte.d.ts +17 -2
  18. package/dist/components/hover/HoveredEntity.svelte +8 -5
  19. package/dist/components/hover/HoveredEntity.svelte.d.ts +5 -1
  20. package/dist/components/hover/LinkedHoveredEntity.svelte +7 -11
  21. package/dist/components/hover/LinkedHoveredEntity.svelte.d.ts +1 -0
  22. package/dist/components/overlay/Details.svelte +22 -37
  23. package/dist/components/overlay/Details.svelte.d.ts +3 -1
  24. package/dist/components/overlay/controls/Controls.svelte +0 -2
  25. package/dist/components/overlay/dashboard/Button.svelte +5 -3
  26. package/dist/components/overlay/dashboard/Button.svelte.d.ts +1 -1
  27. package/dist/components/overlay/left-pane/Tree.svelte +13 -10
  28. package/dist/components/overlay/left-pane/TreeContainer.svelte +9 -4
  29. package/dist/components/overlay/left-pane/TreeNode.svelte +6 -4
  30. package/dist/components/overlay/settings/ConnectionSettings.svelte +42 -0
  31. package/dist/components/overlay/settings/ConnectionSettings.svelte.d.ts +18 -0
  32. package/dist/components/overlay/settings/DebugSettings.svelte +13 -0
  33. package/dist/components/{xr/frame-configure/Controllers.svelte.d.ts → overlay/settings/DebugSettings.svelte.d.ts} +3 -3
  34. package/dist/components/overlay/settings/PointcloudSettings.svelte +61 -0
  35. package/dist/components/overlay/settings/PointcloudSettings.svelte.d.ts +3 -0
  36. package/dist/components/overlay/settings/SceneSettings.svelte +110 -0
  37. package/dist/components/overlay/settings/SceneSettings.svelte.d.ts +18 -0
  38. package/dist/components/overlay/settings/Settings.svelte +27 -312
  39. package/dist/components/overlay/settings/Settings.svelte.d.ts +8 -1
  40. package/dist/components/overlay/settings/Tabs.svelte +5 -3
  41. package/dist/components/overlay/settings/Tabs.svelte.d.ts +3 -3
  42. package/dist/components/overlay/settings/VisionSettings.svelte +31 -0
  43. package/dist/components/overlay/settings/VisionSettings.svelte.d.ts +3 -0
  44. package/dist/components/overlay/settings/WeblabSettings.svelte +27 -0
  45. package/dist/components/overlay/settings/WeblabSettings.svelte.d.ts +18 -0
  46. package/dist/components/overlay/settings/WidgetSettings.svelte +49 -0
  47. package/dist/components/overlay/settings/WidgetSettings.svelte.d.ts +3 -0
  48. package/dist/components/overlay/widgets/FramePov.svelte +1 -12
  49. package/dist/draw.d.ts +1 -0
  50. package/dist/draw.js +1 -1
  51. package/dist/ecs/index.d.ts +1 -0
  52. package/dist/ecs/index.js +1 -0
  53. package/dist/ecs/traits.d.ts +22 -5
  54. package/dist/ecs/traits.js +33 -4
  55. package/dist/ecs/useTag.svelte.d.ts +5 -0
  56. package/dist/ecs/useTag.svelte.js +43 -0
  57. package/dist/hooks/useEnvironment.svelte.d.ts +1 -1
  58. package/dist/hooks/useLinked.svelte.js +7 -8
  59. package/dist/hooks/useMouseRaycaster.svelte.d.ts +4 -3
  60. package/dist/hooks/useMouseRaycaster.svelte.js +1 -0
  61. package/dist/hooks/useSettings.svelte.d.ts +1 -1
  62. package/dist/plugins/Focus/Focus.svelte +45 -0
  63. package/dist/plugins/Focus/Focus.svelte.d.ts +3 -0
  64. package/dist/plugins/Focus/FocusBox.svelte +75 -0
  65. package/dist/plugins/Focus/FocusBox.svelte.d.ts +3 -0
  66. package/dist/plugins/Focus/provideFocus.svelte.d.ts +1 -0
  67. package/dist/plugins/Focus/provideFocus.svelte.js +61 -0
  68. package/dist/{components → plugins}/MeasureTool/MeasureTool.svelte +6 -8
  69. package/dist/plugins/Selection/SelectionTool.svelte +10 -3
  70. package/dist/{components/xr → plugins/XR}/ArmTeleop.svelte +3 -5
  71. package/dist/plugins/XR/DebugPanel.svelte +29 -0
  72. package/dist/plugins/XR/DebugPanel.svelte.d.ts +3 -0
  73. package/dist/plugins/XR/OriginMarker.svelte +341 -0
  74. package/dist/plugins/XR/PendingEditsPanel.svelte +60 -0
  75. package/dist/plugins/XR/PendingEditsPanel.svelte.d.ts +18 -0
  76. package/dist/plugins/XR/WristDisplay.svelte +60 -0
  77. package/dist/plugins/XR/WristDisplay.svelte.d.ts +19 -0
  78. package/dist/{components/xr → plugins/XR}/XR.svelte +69 -23
  79. package/dist/plugins/XR/XRPlugins.svelte +9 -0
  80. package/dist/plugins/XR/XRPlugins.svelte.d.ts +26 -0
  81. package/dist/plugins/XR/XRSettings.svelte +240 -0
  82. package/dist/plugins/XR/XRSettings.svelte.d.ts +3 -0
  83. package/dist/{components/xr → plugins/XR}/XRToast.svelte +6 -9
  84. package/dist/plugins/XR/debug.svelte.d.ts +7 -0
  85. package/dist/plugins/XR/debug.svelte.js +13 -0
  86. package/dist/plugins/XR/frame-configure/Controllers.svelte +413 -0
  87. package/dist/plugins/XR/teleop/Controllers.svelte.d.ts +3 -0
  88. package/dist/{components/xr → plugins/XR}/useAnchors.svelte.d.ts +4 -0
  89. package/dist/{components/xr → plugins/XR}/useAnchors.svelte.js +22 -0
  90. package/dist/plugins/XR/useOrigin.svelte.d.ts +24 -0
  91. package/dist/plugins/XR/useOrigin.svelte.js +50 -0
  92. package/dist/plugins/index.d.ts +4 -0
  93. package/dist/plugins/index.js +4 -0
  94. package/dist/three/OBBHelper.js +1 -0
  95. package/dist/three/arrow.d.ts +2 -0
  96. package/dist/three/arrow.js +3 -1
  97. package/package.json +16 -4
  98. package/dist/components/Focus.svelte +0 -46
  99. package/dist/components/Focus.svelte.d.ts +0 -7
  100. package/dist/components/xr/OriginMarker.svelte +0 -151
  101. package/dist/components/xr/XRControllerSettings.svelte +0 -242
  102. package/dist/components/xr/XRControllerSettings.svelte.d.ts +0 -3
  103. package/dist/components/xr/frame-configure/Controllers.svelte +0 -6
  104. package/dist/components/xr/useOrigin.svelte.d.ts +0 -9
  105. package/dist/components/xr/useOrigin.svelte.js +0 -27
  106. package/dist/hooks/useSelection.svelte.d.ts +0 -33
  107. package/dist/hooks/useSelection.svelte.js +0 -94
  108. /package/dist/{components → plugins}/MeasureTool/MeasurePoint.svelte +0 -0
  109. /package/dist/{components → plugins}/MeasureTool/MeasurePoint.svelte.d.ts +0 -0
  110. /package/dist/{components → plugins}/MeasureTool/MeasureTool.svelte.d.ts +0 -0
  111. /package/dist/{components/xr → plugins/XR}/ArmTeleop.svelte.d.ts +0 -0
  112. /package/dist/{components/xr → plugins/XR}/BentPlaneGeometry.svelte +0 -0
  113. /package/dist/{components/xr → plugins/XR}/BentPlaneGeometry.svelte.d.ts +0 -0
  114. /package/dist/{components/xr → plugins/XR}/CameraFeed.svelte +0 -0
  115. /package/dist/{components/xr → plugins/XR}/CameraFeed.svelte.d.ts +0 -0
  116. /package/dist/{components/xr → plugins/XR}/JointLimitsWidget.svelte +0 -0
  117. /package/dist/{components/xr → plugins/XR}/JointLimitsWidget.svelte.d.ts +0 -0
  118. /package/dist/{components/xr → plugins/XR}/OriginMarker.svelte.d.ts +0 -0
  119. /package/dist/{components/xr → plugins/XR}/PointDistance.svelte +0 -0
  120. /package/dist/{components/xr → plugins/XR}/PointDistance.svelte.d.ts +0 -0
  121. /package/dist/{components/xr → plugins/XR}/XR.svelte.d.ts +0 -0
  122. /package/dist/{components/xr → plugins/XR}/XRConfigPanel.svelte +0 -0
  123. /package/dist/{components/xr → plugins/XR}/XRConfigPanel.svelte.d.ts +0 -0
  124. /package/dist/{components/xr → plugins/XR}/XRToast.svelte.d.ts +0 -0
  125. /package/dist/{components/xr/teleop → plugins/XR/frame-configure}/Controllers.svelte.d.ts +0 -0
  126. /package/dist/{components/xr → plugins/XR}/math.d.ts +0 -0
  127. /package/dist/{components/xr → plugins/XR}/math.js +0 -0
  128. /package/dist/{components/xr → plugins/XR}/teleop/Controllers.svelte +0 -0
  129. /package/dist/{components/xr → plugins/XR}/toasts.svelte.d.ts +0 -0
  130. /package/dist/{components/xr → plugins/XR}/toasts.svelte.js +0 -0
@@ -1,55 +1,48 @@
1
1
  <script lang="ts">
2
- import { isInstanceOf, T, useTask, useThrelte } from '@threlte/core'
2
+ import { T, useTask, useThrelte } from '@threlte/core'
3
3
  import { BatchedMesh, Box3 } from 'three'
4
4
  import { OBB } from 'three/addons/math/OBB.js'
5
5
 
6
- import type { InstancedArrows } from '../three/InstancedArrows/InstancedArrows'
7
-
8
- import { useSelectedEntity, useSelectedObject3d } from '../hooks/useSelection.svelte'
6
+ import { traits, useQuery } from '../ecs'
9
7
  import { OBBHelper } from '../three/OBBHelper'
10
8
 
11
9
  const box3 = new Box3()
12
10
  const obb = new OBB()
13
- const obbHelper = new OBBHelper()
14
11
 
15
- const { invalidate } = useThrelte()
16
- const selectedEntity = useSelectedEntity()
17
- const selectedObject3d = useSelectedObject3d()
12
+ const { scene, invalidate } = useThrelte()
13
+ const selected = useQuery(traits.Selected)
18
14
 
19
- const object = $derived(selectedObject3d.current)
15
+ const obbHelpers = $derived(selected.current.map((entity) => [entity, new OBBHelper()] as const))
20
16
 
21
17
  useTask(
22
18
  () => {
23
- if (object === undefined) {
24
- return
25
- }
26
-
27
- if (
28
- selectedEntity.instance &&
29
- (isInstanceOf(object, 'BatchedMesh') || (object as InstancedArrows).isInstancedArrows)
30
- ) {
31
- const mesh = object as BatchedMesh | InstancedArrows
32
- mesh.getBoundingBoxAt(selectedEntity.instance, box3)
33
- obb.fromBox3(box3)
34
- obbHelper.setFromOBB(obb)
35
- } else {
36
- obbHelper.setFromObject(object)
19
+ for (const [entity, obbHelper] of obbHelpers) {
20
+ const object = scene.getObjectByName(entity as unknown as string)
21
+ if (!object) continue
22
+
23
+ const instance = entity.get(traits.InstanceId)
24
+ if (instance !== undefined && instance >= 0) {
25
+ ;(object as BatchedMesh).getBoundingBoxAt(instance, box3)
26
+ obb.fromBox3(box3)
27
+ obbHelper.setFromOBB(obb)
28
+ } else {
29
+ obbHelper.setFromObject(object)
30
+ }
37
31
  }
38
32
 
39
33
  invalidate()
40
34
  },
41
35
  {
42
- running: () => selectedEntity.current !== undefined,
36
+ running: () => selected.current.length > 0,
43
37
  autoInvalidate: false,
44
38
  }
45
39
  )
46
40
  </script>
47
41
 
48
- {#if selectedEntity.current}
42
+ {#each obbHelpers as [entity, obbHelper] (entity)}
49
43
  <T
50
44
  is={obbHelper}
51
- dispose={false}
52
45
  raycast={() => null}
53
46
  bvh={{ enabled: false }}
54
47
  />
55
- {/if}
48
+ {/each}
@@ -1,15 +1,15 @@
1
1
  <script lang="ts">
2
+ import { useThrelte } from '@threlte/core'
2
3
  import { TransformControls } from '@threlte/extras'
3
4
  import { Matrix4, Quaternion, Vector3 } from 'three'
4
5
 
5
6
  import type { FrameEditSession } from '../editing/FrameEditSession'
6
7
 
7
- import { relations, traits, useTrait } from '../ecs'
8
+ import { relations, traits, useQuery, useTrait } from '../ecs'
8
9
  import { useTransformControls } from '../hooks/useControls.svelte'
9
10
  import { useEnvironment } from '../hooks/useEnvironment.svelte'
10
11
  import { useFrameEditSession } from '../hooks/useFrameEditSession.svelte'
11
12
  import { usePartConfig } from '../hooks/usePartConfig.svelte'
12
- import { useSelectedEntity, useSelectedObject3d } from '../hooks/useSelection.svelte'
13
13
  import { useSettings } from '../hooks/useSettings.svelte'
14
14
  import {
15
15
  createPose,
@@ -20,16 +20,17 @@
20
20
  vector3ToPose,
21
21
  } from '../transform'
22
22
 
23
+ const { scene } = useThrelte()
23
24
  const settings = useSettings()
24
25
  const environment = useEnvironment()
25
26
  const partConfig = usePartConfig()
26
27
  const transformControls = useTransformControls()
27
- const selectedEntity = useSelectedEntity()
28
- const selectedObject3d = useSelectedObject3d()
29
28
  const sessions = useFrameEditSession()
29
+ const selected = useQuery(traits.Selected)
30
30
 
31
31
  const mode = $derived(settings.current.transformMode)
32
- const entity = $derived(selectedEntity.current)
32
+ const entity = $derived(selected.current[0])
33
+ const object3d = $derived(scene.getObjectByName(entity as unknown as string))
33
34
  const transformable = useTrait(() => entity, traits.Transformable)
34
35
  const invisible = useTrait(() => entity, traits.InheritedInvisible)
35
36
  const configMatrix = useTrait(() => entity, traits.Matrix)
@@ -47,11 +48,11 @@
47
48
  0
48
49
  )
49
50
 
50
- // Mesh sets name={entity} on its inner mesh, so useSelectedObject3d resolves
51
+ // Mesh sets name={entity} on its inner mesh, so getObjectByName resolves
51
52
  // to that mesh — not the parent Frame Group we actually want to drive. Walk
52
53
  // up to the Group so translate/rotate/scale apply to the whole frame, not
53
54
  // the geometry inside it.
54
- const ref = $derived(selectedObject3d.current?.parent ?? selectedObject3d.current)
55
+ const ref = $derived(object3d?.parent ?? object3d)
55
56
 
56
57
  const activeMode = $derived.by<'translate' | 'rotate' | 'scale' | undefined>(() => {
57
58
  if (mode === 'none' || !transformable.current) return
@@ -11,17 +11,16 @@
11
11
  import { PressedKeys } from 'runed'
12
12
  import { SvelteSet } from 'svelte/reactivity'
13
13
 
14
- import { traits, useWorld } from '../ecs'
15
- import { useSelectedEntity } from '../hooks/useSelection.svelte'
14
+ import { traits, useQuery, useWorld } from '../ecs'
16
15
 
17
16
  import Frame from './Entities/Frame.svelte'
18
17
 
19
18
  const world = useWorld()
20
- const selectedEntity = useSelectedEntity()
19
+ const selected = useQuery(traits.Selected)
21
20
 
22
21
  const entities = new SvelteSet<Entity>()
23
22
  const selectedCustomGeometry = $derived(
24
- [...entities].find((entity) => entity === selectedEntity.current)
23
+ [...entities].find((entity) => entity === selected.current[0])
25
24
  )
26
25
 
27
26
  const keys = new PressedKeys()
@@ -43,7 +42,6 @@
43
42
  const entity = selectedCustomGeometry
44
43
  entity.destroy()
45
44
  entities.delete(entity)
46
- selectedEntity.set()
47
45
  }
48
46
  })
49
47
  </script>
@@ -1,24 +1,25 @@
1
1
  <script lang="ts">
2
- import { traits, useTrait } from '../../ecs'
2
+ import { traits, useQuery, useTrait } from '../../ecs'
3
3
  import { useLinkedEntities } from '../../hooks/useLinked.svelte'
4
- import { useSelectedEntity } from '../../hooks/useSelection.svelte'
5
- import { useFocusedEntity } from '../../hooks/useSelection.svelte'
6
4
 
7
5
  import HoveredEntity from './HoveredEntity.svelte'
8
6
  import LinkedHoveredEntity from './LinkedHoveredEntity.svelte'
9
7
 
10
- const selectedEntity = useSelectedEntity()
11
- const focusedEntity = useFocusedEntity()
12
8
  const linkedEntities = useLinkedEntities()
13
-
14
- const displayEntity = $derived(selectedEntity.current ?? focusedEntity.current)
15
- const isHovered = useTrait(() => displayEntity, traits.Hovered)
9
+ const selected = useQuery(traits.Selected)
16
10
  </script>
17
11
 
18
- {#if isHovered}
19
- <HoveredEntity />
12
+ {#each selected.current as entity (entity)}
13
+ {@const isHovered = useTrait(() => entity, traits.Hovered)}
14
+
15
+ {#if isHovered}
16
+ <HoveredEntity {entity} />
20
17
 
21
- {#each linkedEntities.current as entity (entity)}
22
- <LinkedHoveredEntity linkedEntity={entity} />
23
- {/each}
24
- {/if}
18
+ {#each linkedEntities.current as linkedEntity (linkedEntity)}
19
+ <LinkedHoveredEntity
20
+ {linkedEntity}
21
+ {entity}
22
+ />
23
+ {/each}
24
+ {/if}
25
+ {/each}
@@ -1,3 +1,18 @@
1
- declare const HoveredEntities: import("svelte").Component<Record<string, never>, {}, "">;
2
- type HoveredEntities = ReturnType<typeof HoveredEntities>;
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const HoveredEntities: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type HoveredEntities = InstanceType<typeof HoveredEntities>;
3
18
  export default HoveredEntities;
@@ -1,19 +1,22 @@
1
1
  <script lang="ts">
2
+ import type { Entity } from 'koota'
3
+
2
4
  import { MathUtils, Quaternion, Vector3 } from 'three'
3
5
 
4
6
  import type { HoverInfo } from '../../HoverUpdater.svelte'
5
7
 
6
8
  import { traits, useTrait } from '../../ecs'
7
- import { useFocusedEntity, useSelectedEntity } from '../../hooks/useSelection.svelte'
8
9
  import { OrientationVector } from '../../three/OrientationVector'
9
10
 
10
11
  import HoveredEntityTooltip from './HoveredEntityTooltip.svelte'
11
12
 
12
- const selectedEntity = useSelectedEntity()
13
- const focusedEntity = useFocusedEntity()
13
+ interface Props {
14
+ entity: Entity
15
+ }
16
+
17
+ let { entity }: Props = $props()
14
18
 
15
- const displayEntity = $derived(selectedEntity.current ?? focusedEntity.current)
16
- const instancedMatrix = useTrait(() => displayEntity, traits.InstancedMatrix)
19
+ const instancedMatrix = useTrait(() => entity, traits.InstancedMatrix)
17
20
 
18
21
  // Pool: InstancedMatrix's `Matrix4` is in metres (matches Three.js).
19
22
  // Decompose for the tooltip's display, which expects metres for position
@@ -1,3 +1,7 @@
1
- declare const HoveredEntity: import("svelte").Component<Record<string, never>, {}, "">;
1
+ import type { Entity } from 'koota';
2
+ interface Props {
3
+ entity: Entity;
4
+ }
5
+ declare const HoveredEntity: import("svelte").Component<Props, {}, "">;
2
6
  type HoveredEntity = ReturnType<typeof HoveredEntity>;
3
7
  export default HoveredEntity;
@@ -5,36 +5,32 @@
5
5
 
6
6
  import { relations, traits, useTrait } from '../../ecs'
7
7
  import { SubEntityLinkType } from '../../ecs/relations'
8
- import { useSelectedEntity } from '../../hooks/useSelection.svelte'
9
- import { useFocusedEntity } from '../../hooks/useSelection.svelte'
10
8
  import { getLinkedHoverInfo, type HoverInfo } from '../../HoverUpdater.svelte'
11
9
 
12
10
  import HoveredEntityTooltip from './HoveredEntityTooltip.svelte'
13
11
 
14
12
  interface Props {
15
13
  linkedEntity: Entity
14
+ entity: Entity
16
15
  }
17
16
 
18
- let { linkedEntity }: Props = $props()
17
+ let { linkedEntity, entity }: Props = $props()
19
18
 
20
- const selectedEntity = useSelectedEntity()
21
- const focusedEntity = useFocusedEntity()
22
- const displayEntity = $derived(selectedEntity.current ?? focusedEntity.current)
23
-
24
- const displayedHoverInfo = useTrait(() => displayEntity, traits.InstancedMatrix)
19
+ const displayedHoverInfo = useTrait(() => entity, traits.InstancedMatrix)
25
20
 
26
21
  let hoverInfo = $state.raw<HoverInfo | null>(null)
27
22
 
28
23
  $effect(() => {
29
- if (displayEntity && displayedHoverInfo.current) {
30
- const linkType = displayEntity?.get(relations.SubEntityLink(linkedEntity))?.type
24
+ if (entity && displayedHoverInfo.current) {
25
+ const linkType = entity?.get(relations.SubEntityLink(linkedEntity))?.type
31
26
  if (linkType !== SubEntityLinkType.HoverLink) {
32
27
  return
33
28
  }
29
+
34
30
  // Index mapping is a formula with the variable 'index' in it.
35
31
  // Supported operations: https://github.com/cshaa/filtrex#expressions
36
32
  const indexMapping =
37
- displayEntity?.get(relations.SubEntityLink(linkedEntity))?.indexMapping ?? 'index'
33
+ entity?.get(relations.SubEntityLink(linkedEntity))?.indexMapping ?? 'index'
38
34
  const evaluate = compileExpression(indexMapping)
39
35
  const resolvedIndex = evaluate({ index: displayedHoverInfo.current.index })
40
36
  const linkedHoverInfo = getLinkedHoverInfo(resolvedIndex, linkedEntity)
@@ -1,6 +1,7 @@
1
1
  import type { Entity } from 'koota';
2
2
  interface Props {
3
3
  linkedEntity: Entity;
4
+ entity: Entity;
4
5
  }
5
6
  declare const LinkedHoveredEntity: import("svelte").Component<Props, {}, "">;
6
7
  type LinkedHoveredEntity = ReturnType<typeof LinkedHoveredEntity>;
@@ -15,6 +15,7 @@
15
15
  import type { Pose } from '@viamrobotics/sdk'
16
16
  import type { Entity } from 'koota'
17
17
  import type { Snippet } from 'svelte'
18
+ import type { HTMLAttributes } from 'svelte/elements'
18
19
 
19
20
  import { draggable } from '@neodrag/svelte'
20
21
  import { isInstanceOf, useThrelte } from '@threlte/core'
@@ -46,38 +47,28 @@
46
47
  import { usePartConfig } from '../../hooks/usePartConfig.svelte'
47
48
  import { usePartID } from '../../hooks/usePartID.svelte'
48
49
  import { useResourceByName } from '../../hooks/useResourceByName.svelte'
49
- import {
50
- useFocusedEntity,
51
- useFocusedObject3d,
52
- useSelectedEntity,
53
- useSelectedObject3d,
54
- } from '../../hooks/useSelection.svelte'
55
50
  import { useSettings } from '../../hooks/useSettings.svelte'
56
51
  import { createPose, matrixToPose } from '../../transform'
57
52
 
58
- interface Props {
53
+ interface Props extends HTMLAttributes<HTMLDivElement> {
54
+ entity: Entity
59
55
  details?: Snippet<[{ entity: Entity }]>
60
56
  }
61
57
 
62
- const { details }: Props = $props()
58
+ const { entity, details, ...rest }: Props = $props()
63
59
 
64
60
  const world = useWorld()
65
- const { invalidate } = useThrelte()
61
+ const { scene, invalidate } = useThrelte()
66
62
  const controls = useCameraControls()
67
63
  const resourceByName = useResourceByName()
68
64
  const configFrames = useConfigFrames()
69
65
  const partConfig = usePartConfig()
70
66
  const partID = usePartID()
71
67
  const settings = useSettings()
72
- const selectedEntity = useSelectedEntity()
73
- const selectedObject3d = useSelectedObject3d()
74
68
  const environment = useEnvironment()
75
- const focusedEntity = useFocusedEntity()
76
- const focusedObject3d = useFocusedObject3d()
77
69
  const linkedEntities = useLinkedEntities()
78
70
 
79
- const entity = $derived(focusedEntity.current ?? selectedEntity.current)
80
- const object3d = $derived(focusedObject3d.current ?? selectedObject3d.current)
71
+ const object3d = $derived(scene.getObjectByName(entity as unknown as string))
81
72
 
82
73
  const name = useTrait(() => entity, traits.Name)
83
74
  const parent = useParentName(() => entity)
@@ -129,8 +120,7 @@
129
120
  })
130
121
 
131
122
  const geometryTypes = ['none', 'box', 'sphere', 'capsule'] as const
132
- // Writable derived: re-derives from the trait, but TabGroup's bind:selectedIndex
133
- // can write a transient override that lasts until the trait re-derives.
123
+
134
124
  let geometryTabIndex = $derived(geometryTypes.indexOf(geometryType))
135
125
 
136
126
  $effect(() => {
@@ -156,6 +146,10 @@
156
146
 
157
147
  const detailConfigUpdater = new FrameConfigUpdater(partConfig.updateFrame, partConfig.deleteFrame)
158
148
 
149
+ const stopKeyboardPropagation = (event: KeyboardEvent) => {
150
+ event.stopPropagation()
151
+ }
152
+
159
153
  const handlePositionChange = (event: PointChangeEvent) => {
160
154
  if (event.detail.origin !== 'internal' || !entity) return
161
155
  const next = event.detail.value as PointValue3dObject
@@ -312,13 +306,21 @@
312
306
  {/snippet}
313
307
 
314
308
  {#if entity}
309
+ <!-- tabindex makes the whole panel focusable so a click anywhere in it (not
310
+ just the inputs) raises it via `focus-within:z-5`. -->
315
311
  <div
316
312
  id="details-panel"
317
- class="border-medium bg-extralight absolute top-0 right-0 z-4 m-2 w-70 border p-2 text-xs"
313
+ class="border-medium bg-extralight absolute top-0 right-0 z-4 m-2 w-70 border p-2 text-xs focus-within:z-5"
314
+ role="region"
315
+ aria-label="Details panel"
316
+ tabindex="-1"
317
+ onkeydown={stopKeyboardPropagation}
318
+ onkeyup={stopKeyboardPropagation}
318
319
  use:draggable={{
319
320
  bounds: 'body',
320
321
  handle: dragElement,
321
322
  }}
323
+ {...rest}
322
324
  >
323
325
  <div
324
326
  class="flex cursor-move items-center justify-between gap-2 pb-2"
@@ -768,25 +770,8 @@
768
770
 
769
771
  {@render details?.({ entity })}
770
772
 
771
- <h3 class="text-subtle-2 pt-3 pb-2">Actions</h3>
772
-
773
- {#if focusedEntity.current}
774
- <Button
775
- class="w-full"
776
- icon="arrow-left"
777
- variant="dark"
778
- onclick={() => focusedEntity.set()}
779
- >
780
- Exit object view
781
- </Button>
782
- {:else}
783
- <Button
784
- class="w-full"
785
- icon="image-filter-center-focus"
786
- onclick={() => focusedEntity.set(entity)}
787
- >
788
- Enter object view
789
- </Button>
773
+ {#if showRelationshipOptions || (showEditFrameOptions && environment.current.isStandalone)}
774
+ <h3 class="text-subtle-2 pt-3 pb-2">Actions</h3>
790
775
  {/if}
791
776
 
792
777
  {#if showRelationshipOptions}
@@ -1,6 +1,8 @@
1
1
  import type { Entity } from 'koota';
2
2
  import type { Snippet } from 'svelte';
3
- interface Props {
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ interface Props extends HTMLAttributes<HTMLDivElement> {
5
+ entity: Entity;
4
6
  details?: Snippet<[{
5
7
  entity: Entity;
6
8
  }]>;
@@ -16,7 +16,6 @@
16
16
 
17
17
  <fieldset class="flex flex-col">
18
18
  <Button
19
- active
20
19
  class="rounded-b-none"
21
20
  icon="camera-outline"
22
21
  description="Reset camera"
@@ -26,7 +25,6 @@
26
25
  }}
27
26
  />
28
27
  <Button
29
- active
30
28
  class="-my-0.5 rounded-t-none"
31
29
  icon={isOrthographic ? 'grid-orthographic' : 'grid-perspective'}
32
30
  description={isOrthographic ? 'Switch to perspective view' : 'Switch to orthographic view'}
@@ -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 { MousePointer2, Ruler } from 'lucide-svelte'
5
+ import { Focus, MousePointer2, Ruler } from 'lucide-svelte'
6
6
 
7
7
  interface Props extends HTMLButtonAttributes {
8
- icon: IconName | 'ruler' | 'mouse-pointer'
8
+ icon: IconName | 'ruler' | 'mouse-pointer' | 'focus'
9
9
  active?: boolean
10
10
  description: string
11
11
  hotkey?: string
@@ -33,7 +33,7 @@
33
33
  <label
34
34
  class={[
35
35
  className,
36
- 'relative block rounded-md border',
36
+ 'relative block rounded-md border active:z-4 active:border-[#666] active:bg-[#666] active:text-white',
37
37
  active ? 'z-4 border-[#666] bg-[#666] text-white' : 'border-gray-5 text-gray-8 bg-white',
38
38
  ]}
39
39
  aria-describedby={tooltipID}
@@ -50,6 +50,8 @@
50
50
  <Ruler size="16" />
51
51
  {:else if icon === 'mouse-pointer'}
52
52
  <MousePointer2 size="16" />
53
+ {:else if icon === 'focus'}
54
+ <Focus size="16" />
53
55
  {:else}
54
56
  <Icon name={icon} />
55
57
  {/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' | 'mouse-pointer';
4
+ icon: IconName | 'ruler' | 'mouse-pointer' | 'focus';
5
5
  active?: boolean;
6
6
  description: string;
7
7
  hotkey?: string;
@@ -6,15 +6,12 @@
6
6
  import { VirtualList } from 'svelte-virtuallists'
7
7
  import { SvelteSet } from 'svelte/reactivity'
8
8
 
9
- import { relations, traits } from '../../../ecs'
10
- import { useSelectedEntity } from '../../../hooks/useSelection.svelte'
9
+ import { relations, traits, useQuery } from '../../../ecs'
11
10
 
12
11
  import type { TreeNode as TreeNodeType } from './useTree.svelte'
13
12
 
14
13
  import TreeNode from './TreeNode.svelte'
15
14
 
16
- const selected = useSelectedEntity()
17
-
18
15
  interface Props {
19
16
  rootNode: TreeNodeType
20
17
  dragElement?: HTMLElement
@@ -31,14 +28,18 @@
31
28
  })
32
29
  )
33
30
 
34
- const selectedValue = $derived(selected.current ? [`${selected.current}`] : [])
31
+ const selected = useQuery(traits.Selected)
32
+
33
+ const selectedValue = $derived(selected.current.map((entity) => `${entity}`))
35
34
  const expandedValues = new SvelteSet<string>()
36
35
 
37
36
  $effect(() => {
38
- let entity: Entity | undefined = selected.current
39
- while (entity) {
40
- expandedValues.add(`${entity}`)
41
- entity = entity.targetFor(relations.ChildOf)
37
+ for (const entity of selected.current) {
38
+ let ancestor: Entity | undefined = entity.targetFor(relations.ChildOf)
39
+ while (ancestor) {
40
+ expandedValues.add(`${ancestor}`)
41
+ ancestor = ancestor.targetFor(relations.ChildOf)
42
+ }
42
43
  }
43
44
  })
44
45
 
@@ -46,6 +47,8 @@
46
47
  const service = useMachine(tree.machine, () => ({
47
48
  id,
48
49
  collection,
50
+ selectionMode: 'multiple' as const,
51
+ expandOnClick: false,
49
52
  selectedValue,
50
53
  expandedValue: [...expandedValues],
51
54
  onSelectionChange(details) {
@@ -64,7 +67,7 @@
64
67
 
65
68
  $effect(() => {
66
69
  const element = document.querySelector(
67
- `[data-scope="tree-view"][data-value="${selected.current}"]`
70
+ `[data-scope="tree-view"][data-value="${selected.current.at(-1)}"]`
68
71
  )
69
72
 
70
73
  requestAnimationFrame(() => {
@@ -2,7 +2,6 @@
2
2
  import { type Entity, IsExcluded } from 'koota'
3
3
 
4
4
  import { traits, useWorld } from '../../../ecs'
5
- import { useSelectedEntity } from '../../../hooks/useSelection.svelte'
6
5
 
7
6
  import FloatingPanel from '../FloatingPanel.svelte'
8
7
  import Tree from './Tree.svelte'
@@ -11,7 +10,6 @@
11
10
 
12
11
  provideTreeExpandedContext()
13
12
 
14
- const selectedEntity = useSelectedEntity()
15
13
  const world = useWorld()
16
14
 
17
15
  const worldEntity = world.spawn(IsExcluded, traits.Name('World'))
@@ -35,9 +33,16 @@
35
33
  <Tree
36
34
  {rootNode}
37
35
  onSelectionChange={(event) => {
38
- const value = event.selectedValue[0]
36
+ const next = new Set(event.selectedValue.map(Number))
39
37
 
40
- selectedEntity.set(value ? (Number(value) as Entity) : undefined)
38
+ for (const entity of world.query(traits.Selected)) {
39
+ if (!next.has(entity as number)) entity.remove(traits.Selected)
40
+ }
41
+
42
+ for (const id of next) {
43
+ const entity = id as Entity
44
+ if (!entity.has(traits.Selected)) entity.add(traits.Selected)
45
+ }
41
46
  }}
42
47
  />
43
48
  </FloatingPanel>
@@ -63,12 +63,14 @@
63
63
  ]}
64
64
  >
65
65
  <div {...api.getBranchControlProps(nodeProps)}>
66
- <span
67
- {...api.getBranchIndicatorProps(nodeProps)}
68
- class={{ 'rotate-90': expanded }}
66
+ <button
67
+ type="button"
68
+ aria-label={expanded ? 'Collapse' : 'Expand'}
69
+ {...api.getBranchTriggerProps(nodeProps)}
70
+ class={['flex items-center', { 'rotate-90': expanded }]}
69
71
  >
70
72
  <ChevronRight size={14} />
71
- </span>
73
+ </button>
72
74
  <span
73
75
  class="flex items-center overflow-hidden text-ellipsis"
74
76
  {...api.getBranchTextProps(nodeProps)}
@@ -0,0 +1,42 @@
1
+ <script lang="ts">
2
+ import { useGeometries } from '../../../hooks/useGeometries.svelte'
3
+ import { usePointcloudObjects } from '../../../hooks/usePointcloudObjects.svelte'
4
+ import { usePointClouds } from '../../../hooks/usePointclouds.svelte'
5
+ import { useRefetchPoses } from '../../../hooks/useRefetchPoses'
6
+ import { RefreshRates } from '../../../hooks/useSettings.svelte'
7
+
8
+ import RefreshRate from '../RefreshRate.svelte'
9
+
10
+ const geometries = useGeometries()
11
+ const pointclouds = usePointClouds()
12
+ const pointcloudObjects = usePointcloudObjects()
13
+ const { refetchPoses } = useRefetchPoses()
14
+ </script>
15
+
16
+ <div class="flex flex-col gap-2.5 text-xs">
17
+ <h3 class="border-gray-3 border-b py-1 text-sm"><strong>Polling rates</strong></h3>
18
+
19
+ <RefreshRate
20
+ id={RefreshRates.poses}
21
+ label="Poses"
22
+ allowLive
23
+ onManualRefetch={() => {
24
+ refetchPoses()
25
+ geometries.refetch()
26
+ }}
27
+ />
28
+ <RefreshRate
29
+ id={RefreshRates.pointclouds}
30
+ label="Pointclouds from cameras"
31
+ onManualRefetch={() => {
32
+ pointclouds.refetch()
33
+ }}
34
+ />
35
+ <RefreshRate
36
+ id={RefreshRates.vision}
37
+ label="Vision service pointcloud segments and objects"
38
+ onManualRefetch={() => {
39
+ pointcloudObjects.refetch()
40
+ }}
41
+ />
42
+ </div>