@viamrobotics/motion-tools 1.9.0 → 1.10.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 (94) hide show
  1. package/dist/HoverUpdater.svelte.d.ts +19 -0
  2. package/dist/HoverUpdater.svelte.js +120 -0
  3. package/dist/components/App.svelte +34 -35
  4. package/dist/components/CameraControls.svelte +1 -1
  5. package/dist/components/Focus.svelte +1 -1
  6. package/dist/components/Frame.svelte +1 -1
  7. package/dist/components/Geometry2.svelte +8 -5
  8. package/dist/components/Label.svelte +1 -1
  9. package/dist/components/MeasureTool/MeasurePoint.svelte +5 -3
  10. package/dist/components/MeasureTool/MeasureTool.svelte +11 -11
  11. package/dist/components/SceneProviders.svelte +2 -0
  12. package/dist/components/hover/HoveredEntities.svelte +23 -0
  13. package/dist/components/hover/HoveredEntity.svelte +15 -0
  14. package/dist/components/hover/HoveredEntity.svelte.d.ts +3 -0
  15. package/dist/components/hover/HoveredEntityTooltip.svelte +70 -0
  16. package/dist/components/{HoveredEntityTooltip.svelte.d.ts → hover/HoveredEntityTooltip.svelte.d.ts} +2 -2
  17. package/dist/components/hover/LinkedHoveredEntity.svelte +55 -0
  18. package/dist/components/hover/LinkedHoveredEntity.svelte.d.ts +9 -0
  19. package/dist/components/overlay/AddRelationship.svelte +131 -0
  20. package/dist/components/overlay/AddRelationship.svelte.d.ts +7 -0
  21. package/dist/components/{Details.svelte → overlay/Details.svelte} +44 -11
  22. package/dist/components/overlay/FloatingPanel.svelte +78 -0
  23. package/dist/components/overlay/FloatingPanel.svelte.d.ts +13 -0
  24. package/dist/components/{LiveUpdatesBanner.svelte → overlay/LiveUpdatesBanner.svelte} +2 -2
  25. package/dist/components/{RefreshRate.svelte → overlay/RefreshRate.svelte} +1 -1
  26. package/dist/components/overlay/ToggleGroup.svelte +56 -0
  27. package/dist/components/{Overlay → overlay}/ToggleGroup.svelte.d.ts +6 -7
  28. package/dist/components/{__tests__ → overlay/__tests__}/__fixtures__/entity.js +1 -1
  29. package/dist/components/{dashboard → overlay/dashboard}/Button.svelte +1 -1
  30. package/dist/components/{dashboard → overlay/dashboard}/Dashboard.svelte +2 -2
  31. package/dist/components/{Tree → overlay/left-pane}/AddFrames.svelte +2 -2
  32. package/dist/components/{Tree → overlay/left-pane}/Logs.svelte +1 -1
  33. package/dist/components/{Tree → overlay/left-pane}/Tree.svelte +3 -3
  34. package/dist/components/{Tree → overlay/left-pane}/TreeContainer.svelte +8 -10
  35. package/dist/components/{Tree → overlay/left-pane}/Widgets.svelte +3 -3
  36. package/dist/components/{Tree → overlay/left-pane}/buildTree.js +1 -1
  37. package/dist/components/overlay/settings/Settings.svelte +279 -0
  38. package/dist/components/overlay/settings/Tabs.svelte +54 -0
  39. package/dist/components/overlay/settings/Tabs.svelte.d.ts +12 -0
  40. package/dist/components/{widgets → overlay/widgets}/ArmPositions.svelte +4 -4
  41. package/dist/components/{widgets → overlay/widgets}/Camera.svelte +5 -5
  42. package/dist/ecs/index.d.ts +1 -0
  43. package/dist/ecs/index.js +1 -0
  44. package/dist/ecs/relations.d.ts +7 -0
  45. package/dist/ecs/relations.js +7 -0
  46. package/dist/ecs/traits.d.ts +8 -2
  47. package/dist/ecs/traits.js +11 -5
  48. package/dist/hooks/useDrawAPI.svelte.js +1 -1
  49. package/dist/hooks/useFrames.svelte.js +1 -0
  50. package/dist/hooks/useGeometries.svelte.js +1 -1
  51. package/dist/hooks/useLinked.svelte.d.ts +7 -0
  52. package/dist/hooks/useLinked.svelte.js +35 -0
  53. package/dist/hooks/useObjectEvents.svelte.js +35 -16
  54. package/dist/hooks/usePointcloudObjects.svelte.js +1 -1
  55. package/dist/hooks/usePointclouds.svelte.js +30 -43
  56. package/dist/hooks/usePose.svelte.js +1 -1
  57. package/dist/hooks/useWorldState.svelte.js +1 -1
  58. package/package.json +4 -1
  59. package/dist/components/DotSprite.svelte +0 -59
  60. package/dist/components/DotSprite.svelte.d.ts +0 -10
  61. package/dist/components/HoveredEntities.svelte +0 -19
  62. package/dist/components/HoveredEntityTooltip.svelte +0 -241
  63. package/dist/components/MeasureTool.svelte +0 -123
  64. package/dist/components/MeasureTool.svelte.d.ts +0 -3
  65. package/dist/components/Overlay/ToggleGroup.svelte +0 -60
  66. package/dist/components/Tree/Settings.svelte +0 -221
  67. package/dist/components/null-states/Connection.svelte +0 -0
  68. package/dist/components/null-states/Connection.svelte.d.ts +0 -26
  69. /package/dist/components/{HoveredEntities.svelte.d.ts → hover/HoveredEntities.svelte.d.ts} +0 -0
  70. /package/dist/components/{Details.svelte.d.ts → overlay/Details.svelte.d.ts} +0 -0
  71. /package/dist/components/{LiveUpdatesBanner.svelte.d.ts → overlay/LiveUpdatesBanner.svelte.d.ts} +0 -0
  72. /package/dist/components/{Overlay → overlay}/Popover.svelte +0 -0
  73. /package/dist/components/{Overlay → overlay}/Popover.svelte.d.ts +0 -0
  74. /package/dist/components/{RefreshRate.svelte.d.ts → overlay/RefreshRate.svelte.d.ts} +0 -0
  75. /package/dist/components/{shared → overlay}/Table.svelte +0 -0
  76. /package/dist/components/{shared → overlay}/Table.svelte.d.ts +0 -0
  77. /package/dist/components/{__tests__ → overlay/__tests__}/__fixtures__/entity.d.ts +0 -0
  78. /package/dist/components/{__tests__ → overlay/__tests__}/__fixtures__/resource.d.ts +0 -0
  79. /package/dist/components/{__tests__ → overlay/__tests__}/__fixtures__/resource.js +0 -0
  80. /package/dist/components/{dashboard → overlay/dashboard}/Button.svelte.d.ts +0 -0
  81. /package/dist/components/{dashboard → overlay/dashboard}/Dashboard.svelte.d.ts +0 -0
  82. /package/dist/components/{Tree → overlay/left-pane}/AddFrames.svelte.d.ts +0 -0
  83. /package/dist/components/{Tree → overlay/left-pane}/Drawer.svelte +0 -0
  84. /package/dist/components/{Tree → overlay/left-pane}/Drawer.svelte.d.ts +0 -0
  85. /package/dist/components/{Tree → overlay/left-pane}/Logs.svelte.d.ts +0 -0
  86. /package/dist/components/{Tree → overlay/left-pane}/Tree.svelte.d.ts +0 -0
  87. /package/dist/components/{Tree → overlay/left-pane}/TreeContainer.svelte.d.ts +0 -0
  88. /package/dist/components/{Tree → overlay/left-pane}/Widgets.svelte.d.ts +0 -0
  89. /package/dist/components/{Tree → overlay/left-pane}/buildTree.d.ts +0 -0
  90. /package/dist/components/{Tree → overlay/left-pane}/useExpanded.svelte.d.ts +0 -0
  91. /package/dist/components/{Tree → overlay/left-pane}/useExpanded.svelte.js +0 -0
  92. /package/dist/components/{Tree → overlay/settings}/Settings.svelte.d.ts +0 -0
  93. /package/dist/components/{widgets → overlay/widgets}/ArmPositions.svelte.d.ts +0 -0
  94. /package/dist/components/{widgets → overlay/widgets}/Camera.svelte.d.ts +0 -0
@@ -0,0 +1,19 @@
1
+ import { Vector3 } from 'three';
2
+ import type { Entity } from 'koota';
3
+ import type { IntersectionEvent } from '@threlte/extras';
4
+ export interface HoverInfo {
5
+ index: number;
6
+ x: number;
7
+ y: number;
8
+ z: number;
9
+ oX: number;
10
+ oY: number;
11
+ oZ: number;
12
+ theta: number;
13
+ }
14
+ export declare const getClosestArrow: (positions: Float32Array, point: Vector3) => HoverInfo;
15
+ export declare const getClosestPoint: (positions: Float32Array, point: Vector3) => HoverInfo;
16
+ export declare const getPointAtIndex: (positions: Float32Array, index: number) => HoverInfo | null;
17
+ export declare const getArrowAtIndex: (positions: Float32Array, index: number) => HoverInfo | null;
18
+ export declare const updateHoverInfo: (entity: Entity, hoverEvent: IntersectionEvent<MouseEvent>) => HoverInfo | null;
19
+ export declare const getLinkedHoverInfo: (index: number, linkedEntity: Entity) => HoverInfo | null;
@@ -0,0 +1,120 @@
1
+ import { Vector3 } from 'three';
2
+ import { traits } from './ecs';
3
+ const hoverPosition = new Vector3();
4
+ export const getClosestArrow = (positions, point) => {
5
+ let smallestDistance = Infinity;
6
+ let index = -1;
7
+ for (let i = 0; i < positions.length; i += 6) {
8
+ const x = positions[i] / 1000;
9
+ const y = positions[i + 1] / 1000;
10
+ const z = positions[i + 2] / 1000;
11
+ const distance = point.distanceToSquared({ x, y, z });
12
+ if (distance < smallestDistance) {
13
+ smallestDistance = distance;
14
+ index = i;
15
+ }
16
+ }
17
+ return {
18
+ index: Math.floor(index / 6),
19
+ x: positions[index] / 1000,
20
+ y: positions[index + 1] / 1000,
21
+ z: positions[index + 2] / 1000,
22
+ oX: positions[index + 3],
23
+ oY: positions[index + 4],
24
+ oZ: positions[index + 5],
25
+ theta: 0,
26
+ };
27
+ };
28
+ export const getClosestPoint = (positions, point) => {
29
+ let smallestDistance = Infinity;
30
+ let index = -1;
31
+ for (let i = 0; i < positions.length; i += 3) {
32
+ const x = positions[i];
33
+ const y = positions[i + 1];
34
+ const z = positions[i + 2];
35
+ const distance = point.distanceToSquared({ x, y, z });
36
+ if (distance < smallestDistance) {
37
+ smallestDistance = distance;
38
+ index = i;
39
+ }
40
+ }
41
+ return {
42
+ index: Math.floor(index / 3),
43
+ x: positions[index],
44
+ y: positions[index + 1],
45
+ z: positions[index + 2],
46
+ oX: 0,
47
+ oY: 0,
48
+ oZ: 0,
49
+ theta: 0,
50
+ };
51
+ };
52
+ export const getPointAtIndex = (positions, index) => {
53
+ if (index < 0 || index >= positions.length / 3) {
54
+ return null;
55
+ }
56
+ return {
57
+ index,
58
+ x: positions[index * 3],
59
+ y: positions[index * 3 + 1],
60
+ z: positions[index * 3 + 2],
61
+ oX: 0,
62
+ oY: 0,
63
+ oZ: 0,
64
+ theta: 0,
65
+ };
66
+ };
67
+ export const getArrowAtIndex = (positions, index) => {
68
+ if (index < 0 || index >= positions.length / 6) {
69
+ return null;
70
+ }
71
+ return {
72
+ index,
73
+ x: positions[index * 6] / 1000,
74
+ y: positions[index * 6 + 1] / 1000,
75
+ z: positions[index * 6 + 2] / 1000,
76
+ oX: positions[index * 6 + 3],
77
+ oY: positions[index * 6 + 4],
78
+ oZ: positions[index * 6 + 5],
79
+ theta: 0,
80
+ };
81
+ };
82
+ export const updateHoverInfo = (entity, hoverEvent) => {
83
+ const { index, point } = hoverEvent;
84
+ if (index === -1) {
85
+ return null;
86
+ }
87
+ hoverPosition.set(point.x, point.y, point.z);
88
+ let hoverInfo = null;
89
+ if (entity.has(traits.Arrows)) {
90
+ const closestArrow = getClosestArrow(entity.get(traits.Positions), hoverPosition);
91
+ if (closestArrow) {
92
+ hoverInfo = closestArrow;
93
+ }
94
+ }
95
+ else if (entity.has(traits.Points)) {
96
+ const positions = entity.get(traits.BufferGeometry)?.attributes.position.array;
97
+ const closestPoint = getClosestPoint(positions, hoverPosition);
98
+ if (closestPoint) {
99
+ hoverInfo = closestPoint;
100
+ }
101
+ }
102
+ return hoverInfo;
103
+ };
104
+ export const getLinkedHoverInfo = (index, linkedEntity) => {
105
+ if (linkedEntity.has(traits.Arrows)) {
106
+ const closestArrow = getArrowAtIndex(linkedEntity.get(traits.Positions), index);
107
+ if (closestArrow) {
108
+ return closestArrow;
109
+ }
110
+ }
111
+ else if (linkedEntity.has(traits.Points)) {
112
+ const positions = linkedEntity.get(traits.BufferGeometry)?.attributes.position
113
+ .array;
114
+ const closestPoint = getPointAtIndex(positions, index);
115
+ if (closestPoint) {
116
+ return closestPoint;
117
+ }
118
+ }
119
+ return null;
120
+ };
@@ -5,20 +5,20 @@
5
5
  import { provideToast, ToastContainer } from '@viamrobotics/prime-core'
6
6
  import type { Struct } from '@viamrobotics/sdk'
7
7
  import Scene from './Scene.svelte'
8
- import TreeContainer from './Tree/TreeContainer.svelte'
9
- import Details from './Details.svelte'
8
+ import TreeContainer from './overlay/left-pane/TreeContainer.svelte'
9
+ import Details from './overlay/Details.svelte'
10
10
  import SceneProviders from './SceneProviders.svelte'
11
11
  import XR from './xr/XR.svelte'
12
12
  import { createPartIDContext } from '../hooks/usePartID.svelte'
13
- import Dashboard from './dashboard/Dashboard.svelte'
13
+ import Dashboard from './overlay/dashboard/Dashboard.svelte'
14
14
  import { domPortal } from '../portal'
15
15
  import { provideSettings } from '../hooks/useSettings.svelte'
16
16
  import FileDrop from './FileDrop/FileDrop.svelte'
17
17
  import { provideWeblabs } from '../hooks/useWeblabs.svelte'
18
18
  import { providePartConfig } from '../hooks/usePartConfig.svelte'
19
19
  import { useViamClient } from '@viamrobotics/svelte-sdk'
20
- import LiveUpdatesBanner from './LiveUpdatesBanner.svelte'
21
- import ArmPositions from './widgets/ArmPositions.svelte'
20
+ import LiveUpdatesBanner from './overlay/LiveUpdatesBanner.svelte'
21
+ import ArmPositions from './overlay/widgets/ArmPositions.svelte'
22
22
  import { provideEnvironment } from '../hooks/useEnvironment.svelte'
23
23
  import type { CameraPose } from '../hooks/useControls.svelte'
24
24
  import { provideWorld } from '../ecs'
@@ -26,8 +26,9 @@
26
26
  provideDrawConnectionConfig,
27
27
  type DrawConnectionConfig,
28
28
  } from '../hooks/useDrawConnectionConfig.svelte'
29
- import Camera from './widgets/Camera.svelte'
30
- import HoveredEntities from './HoveredEntities.svelte'
29
+ import Camera from './overlay/widgets/Camera.svelte'
30
+ import HoveredEntities from './hover/HoveredEntities.svelte'
31
+ import Settings from './overlay/settings/Settings.svelte'
31
32
 
32
33
  interface LocalConfigProps {
33
34
  getLocalPartConfig: () => Struct
@@ -125,37 +126,35 @@
125
126
 
126
127
  <XR {@attach domPortal(root)} />
127
128
 
128
- <Dashboard
129
- {@attach domPortal(root)}
130
- {dashboard}
131
- />
132
-
133
129
  {#if settings.current.renderSubEntityHoverDetail}
134
- <HoveredEntities {@attach domPortal(root)} />
135
- {/if}
136
- <Details {@attach domPortal(root)} />
137
- {#if environment.current.isStandalone}
138
- <LiveUpdatesBanner {@attach domPortal(root)} />
139
- {/if}
140
-
141
- {#if !focus}
142
- <TreeContainer {@attach domPortal(root)} />
143
- {/if}
144
-
145
- {#if !focus && settings.current.enableArmPositionsWidget}
146
- <ArmPositions {@attach domPortal(root)} />
147
- {/if}
148
-
149
- {#if !focus}
150
- {#each currentRobotCameraWidgets as cameraName (cameraName)}
151
- <Camera
152
- name={cameraName}
153
- {@attach domPortal(root)}
154
- />
155
- {/each}
130
+ <HoveredEntities />
156
131
  {/if}
157
132
 
158
- <FileDrop {@attach domPortal(root)} />
133
+ <!-- Overlays that need Threlte context -->
134
+ <div {@attach domPortal(root)}>
135
+ <FileDrop />
136
+ <Dashboard {dashboard} />
137
+ <Details />
138
+ <Settings />
139
+
140
+ {#if environment.current.isStandalone}
141
+ <LiveUpdatesBanner />
142
+ {/if}
143
+
144
+ {#if !focus}
145
+ <TreeContainer />
146
+ {/if}
147
+
148
+ {#if !focus && settings.current.enableArmPositionsWidget}
149
+ <ArmPositions />
150
+ {/if}
151
+
152
+ {#if !focus}
153
+ {#each currentRobotCameraWidgets as cameraName (cameraName)}
154
+ <Camera name={cameraName} />
155
+ {/each}
156
+ {/if}
157
+ </div>
159
158
  {/snippet}
160
159
  </SceneProviders>
161
160
  </Canvas>
@@ -3,7 +3,7 @@
3
3
  import { CameraControls, type CameraControlsRef, Gizmo, Portal } from '@threlte/extras'
4
4
  import { useCameraControls, useTransformControls } from '../hooks/useControls.svelte'
5
5
  import KeyboardControls from './KeyboardControls.svelte'
6
- import Button from './dashboard/Button.svelte'
6
+ import Button from './overlay/dashboard/Button.svelte'
7
7
  import { useSettings } from '../hooks/useSettings.svelte'
8
8
 
9
9
  const cameraControls = useCameraControls()
@@ -4,7 +4,7 @@
4
4
  import { Box3, type Object3D, Vector3 } from 'three'
5
5
  import { TrackballControls as ThreeTrackballControls } from 'three/examples/jsm/controls/TrackballControls.js'
6
6
  import Camera from './Camera.svelte'
7
- import Button from './dashboard/Button.svelte'
7
+ import Button from './overlay/dashboard/Button.svelte'
8
8
 
9
9
  interface Props {
10
10
  object3d: Object3D
@@ -62,7 +62,7 @@
62
62
  if (!componentName || !id) {
63
63
  return
64
64
  }
65
- return componentModels.current?.[componentName]?.[id]
65
+ return componentModels.current?.[componentName]?.[id].clone()
66
66
  })
67
67
  </script>
68
68
 
@@ -46,6 +46,7 @@
46
46
  const linePositions = useTrait(() => entity, traits.LinePositions)
47
47
  const lineWidth = useTrait(() => entity, traits.LineWidth)
48
48
  const center = useTrait(() => entity, traits.Center)
49
+ const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
49
50
 
50
51
  const geometryType = $derived.by(() => {
51
52
  if (box.current) return 'box'
@@ -124,10 +125,12 @@
124
125
  {...rest}
125
126
  >
126
127
  {#if geometryType}
127
- <AxesHelper
128
- width={3}
129
- length={0.1}
130
- />
128
+ {#if showAxesHelper.current}
129
+ <AxesHelper
130
+ width={3}
131
+ length={0.1}
132
+ />
133
+ {/if}
131
134
 
132
135
  <T
133
136
  is={mesh}
@@ -196,7 +199,7 @@
196
199
  {/if}
197
200
  {/if}
198
201
  </T>
199
- {:else}
202
+ {:else if showAxesHelper.current}
200
203
  <AxesHelper
201
204
  name={name.current}
202
205
  width={3}
@@ -17,7 +17,7 @@
17
17
  {#if labels && text}
18
18
  <HTML
19
19
  center
20
- zIndexRange={[100, 0]}
20
+ zIndexRange={[3, 0]}
21
21
  class="border-gray-7 border bg-white px-2 py-1 text-xs"
22
22
  >
23
23
  {text}
@@ -17,30 +17,32 @@
17
17
  >
18
18
  <HTML
19
19
  center
20
+ zIndexRange={[3, 0]}
20
21
  class="h-2.5 w-2.5 rounded-full bg-black/70"
21
22
  />
22
23
 
23
24
  <HTML
24
25
  class="pointer-events-none mb-2 w-16 -translate-x-1/2 -translate-y-[calc(100%+10px)] border border-black bg-white px-1 py-0.5 text-xs text-wrap"
26
+ zIndexRange={[3, 0]}
25
27
  >
26
28
  <div class="flex justify-between">
27
29
  <span class="text-subtle-2">x</span>
28
30
  <div>
29
- {position[0].toFixed(2)}<span class="text-subtle-2">m</span>
31
+ {position[0].toFixed(3)}<span class="text-subtle-2">m</span>
30
32
  </div>
31
33
  </div>
32
34
 
33
35
  <div class="flex justify-between">
34
36
  <span class="text-subtle-2">y</span>
35
37
  <div>
36
- {position[1].toFixed(2)}<span class="text-subtle-2">m</span>
38
+ {position[1].toFixed(3)}<span class="text-subtle-2">m</span>
37
39
  </div>
38
40
  </div>
39
41
 
40
42
  <div class="flex justify-between">
41
43
  <span class="text-subtle-2">z</span>
42
44
  <div>
43
- {position[2].toFixed(2)}<span class="text-subtle-2">m</span>
45
+ {position[2].toFixed(3)}<span class="text-subtle-2">m</span>
44
46
  </div>
45
47
  </div>
46
48
  </HTML>
@@ -4,20 +4,19 @@
4
4
  import { T } from '@threlte/core'
5
5
  import { HTML, MeshLineGeometry, MeshLineMaterial, Portal } from '@threlte/extras'
6
6
  import { useSettings } from '../../hooks/useSettings.svelte'
7
- import Button from '../dashboard/Button.svelte'
7
+ import Button from '../overlay/dashboard/Button.svelte'
8
8
  import MeasurePoint from './MeasurePoint.svelte'
9
9
  import { useMouseRaycaster } from '../../hooks/useMouseRaycaster.svelte'
10
10
  import { useFocusedEntity } from '../../hooks/useSelection.svelte'
11
- import ToggleGroup from '../Overlay/ToggleGroup.svelte'
12
- import Popover from '../Overlay/Popover.svelte'
11
+ import ToggleGroup from '../overlay/ToggleGroup.svelte'
12
+ import Popover from '../overlay/Popover.svelte'
13
13
 
14
14
  const focusedEntity = useFocusedEntity()
15
15
  const settings = useSettings()
16
16
 
17
17
  const htmlPosition = new Vector3()
18
18
 
19
- let step: 'idle' | 'p1' | 'p2' = 'idle'
20
-
19
+ let step = $state<'idle' | 'p1' | 'p2'>('idle')
21
20
  let intersection = $state<Intersection>()
22
21
  let p1 = $state.raw<Vector3>()
23
22
  let p2 = $state.raw<Vector3>()
@@ -105,12 +104,12 @@
105
104
  Enabled axes
106
105
  <ToggleGroup
107
106
  multiple
108
- buttons={[
109
- { value: 'x', on: settings.current.enableMeasureAxisX },
110
- { value: 'y', on: settings.current.enableMeasureAxisY },
111
- { value: 'z', on: settings.current.enableMeasureAxisZ },
107
+ options={[
108
+ { label: 'x', selected: settings.current.enableMeasureAxisX },
109
+ { label: 'y', selected: settings.current.enableMeasureAxisY },
110
+ { label: 'z', selected: settings.current.enableMeasureAxisZ },
112
111
  ]}
113
- onclick={(details) => {
112
+ onSelect={(details) => {
114
113
  settings.current.enableMeasureAxisX = details.includes('x')
115
114
  settings.current.enableMeasureAxisY = details.includes('y')
116
115
  settings.current.enableMeasureAxisZ = details.includes('z')
@@ -166,9 +165,10 @@
166
165
  <HTML
167
166
  center
168
167
  position={htmlPosition.lerpVectors(p1, p2, 0.5).toArray()}
168
+ zIndexRange={[3, 0]}
169
169
  >
170
170
  <div class="border border-black bg-white px-1 py-0.5 text-xs">
171
- {p1.distanceTo(p2).toFixed(2)}<span class="text-subtle-2">m</span>
171
+ {p1.distanceTo(p2).toFixed(3)}<span class="text-subtle-2">m</span>
172
172
  </div>
173
173
  </HTML>
174
174
  {/if}
@@ -21,6 +21,7 @@
21
21
  import { provideResourceByName } from '../hooks/useResourceByName.svelte'
22
22
  import { provide3DModels } from '../hooks/use3DModels.svelte'
23
23
  import { providePointcloudObjects } from '../hooks/usePointcloudObjects.svelte'
24
+ import { provideLinkedEntities } from '../hooks/useLinked.svelte'
24
25
 
25
26
  interface Props {
26
27
  cameraPose?: CameraPose
@@ -51,6 +52,7 @@
51
52
  provideFramelessComponents()
52
53
 
53
54
  const { focus } = provideSelection()
55
+ provideLinkedEntities()
54
56
  </script>
55
57
 
56
58
  {@render children({ focus: focus.current !== undefined })}
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import { traits, useTrait } from '../../ecs'
3
+ import { useSelectedEntity } from '../../hooks/useSelection.svelte'
4
+ import { useFocusedEntity } from '../../hooks/useSelection.svelte'
5
+ import HoveredEntity from './HoveredEntity.svelte'
6
+ import LinkedHoveredEntity from './LinkedHoveredEntity.svelte'
7
+ import { useLinkedEntities } from '../../hooks/useLinked.svelte'
8
+
9
+ const selectedEntity = useSelectedEntity()
10
+ const focusedEntity = useFocusedEntity()
11
+ const linkedEntities = useLinkedEntities()
12
+
13
+ const displayEntity = $derived(selectedEntity.current ?? focusedEntity.current)
14
+ const isHovered = useTrait(() => displayEntity, traits.Hovered)
15
+ </script>
16
+
17
+ {#if isHovered}
18
+ <HoveredEntity />
19
+
20
+ {#each linkedEntities.current as entity (entity)}
21
+ <LinkedHoveredEntity linkedEntity={entity} />
22
+ {/each}
23
+ {/if}
@@ -0,0 +1,15 @@
1
+ <script lang="ts">
2
+ import { traits, useTrait } from '../../ecs'
3
+ import HoveredEntityTooltip from './HoveredEntityTooltip.svelte'
4
+ import { useFocusedEntity, useSelectedEntity } from '../../hooks/useSelection.svelte'
5
+
6
+ const selectedEntity = useSelectedEntity()
7
+ const focusedEntity = useFocusedEntity()
8
+
9
+ const displayEntity = $derived(selectedEntity.current ?? focusedEntity.current)
10
+ const hoverInfo = useTrait(() => displayEntity, traits.InstancedPose)
11
+ </script>
12
+
13
+ {#if hoverInfo.current}
14
+ <HoveredEntityTooltip hoverInfo={hoverInfo.current} />
15
+ {/if}
@@ -0,0 +1,3 @@
1
+ declare const HoveredEntity: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type HoveredEntity = ReturnType<typeof HoveredEntity>;
3
+ export default HoveredEntity;
@@ -0,0 +1,70 @@
1
+ <script lang="ts">
2
+ import { HTML } from '@threlte/extras'
3
+ import { type HoverInfo } from '../../HoverUpdater.svelte'
4
+
5
+ interface Props {
6
+ hoverInfo: HoverInfo
7
+ }
8
+
9
+ let { hoverInfo }: Props = $props()
10
+ </script>
11
+
12
+ {#if hoverInfo}
13
+ <HTML
14
+ position={[hoverInfo.x, hoverInfo.y, hoverInfo.z]}
15
+ class="pointer-events-none"
16
+ zIndexRange={[3, 0]}
17
+ >
18
+ <div
19
+ class="border-medium pointer-events-none relative -mb-2 -translate-x-1/2 -translate-y-full border bg-white px-3 py-2.5 text-xs shadow-md"
20
+ >
21
+ <!-- Arrow -->
22
+ <div
23
+ class="border-medium absolute -bottom-[5px] left-1/2 size-2.5 -translate-x-1/2 rotate-45 border-r border-b bg-white"
24
+ ></div>
25
+
26
+ <div class="flex flex-col gap-2.5">
27
+ <div>
28
+ <div class="mb-1"><strong class="font-semibold">index</strong></div>
29
+ <div>{hoverInfo.index}</div>
30
+ </div>
31
+
32
+ <div>
33
+ <div class="mb-1">
34
+ <strong class="font-semibold">world position</strong>
35
+ <span class="text-subtle-2"> (m)</span>
36
+ </div>
37
+ <div class="flex gap-3">
38
+ <div>
39
+ <span class="text-subtle-2 mr-1">x </span>{hoverInfo.x.toFixed(2)}
40
+ </div>
41
+ <div>
42
+ <span class="text-subtle-2 mr-1">y </span>{hoverInfo.y.toFixed(2)}
43
+ </div>
44
+ <div>
45
+ <span class="text-subtle-2 mr-1">z </span>{hoverInfo.z.toFixed(2)}
46
+ </div>
47
+ </div>
48
+ </div>
49
+
50
+ <div>
51
+ <div class="mb-1">
52
+ <strong class="font-semibold">world orientation</strong>
53
+ <span class="text-subtle-2"> (deg)</span>
54
+ </div>
55
+ <div class="flex gap-3">
56
+ <div>
57
+ <span class="text-subtle-2 mr-1">x </span>{hoverInfo.oX.toFixed(2)}
58
+ </div>
59
+ <div>
60
+ <span class="text-subtle-2 mr-1">y </span>{hoverInfo.oY.toFixed(2)}
61
+ </div>
62
+ <div>
63
+ <span class="text-subtle-2 mr-1">z </span>{hoverInfo.oZ.toFixed(2)}
64
+ </div>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </HTML>
70
+ {/if}
@@ -1,6 +1,6 @@
1
- import type { Entity } from 'koota';
1
+ import { type HoverInfo } from '../../HoverUpdater.svelte';
2
2
  interface Props {
3
- hoveredEntity: Entity;
3
+ hoverInfo: HoverInfo;
4
4
  }
5
5
  declare const HoveredEntityTooltip: import("svelte").Component<Props, {}, "">;
6
6
  type HoveredEntityTooltip = ReturnType<typeof HoveredEntityTooltip>;
@@ -0,0 +1,55 @@
1
+ <script
2
+ lang="ts"
3
+ module
4
+ >
5
+ import { Parser } from 'expr-eval'
6
+
7
+ export const parser = new Parser()
8
+ </script>
9
+
10
+ <script lang="ts">
11
+ import { relations, traits } from '../../ecs'
12
+ import type { Entity } from 'koota'
13
+ import HoveredEntityTooltip from './HoveredEntityTooltip.svelte'
14
+ import { getLinkedHoverInfo, type HoverInfo } from '../../HoverUpdater.svelte'
15
+ import { useSelectedEntity } from '../../hooks/useSelection.svelte'
16
+ import { useFocusedEntity } from '../../hooks/useSelection.svelte'
17
+ import { useTrait } from '../../ecs'
18
+ import { SubEntityLinkType } from '../../ecs/relations'
19
+
20
+ interface Props {
21
+ linkedEntity: Entity
22
+ }
23
+
24
+ let { linkedEntity }: Props = $props()
25
+
26
+ const selectedEntity = useSelectedEntity()
27
+ const focusedEntity = useFocusedEntity()
28
+ const displayEntity = $derived(selectedEntity.current ?? focusedEntity.current)
29
+
30
+ const displayedHoverInfo = useTrait(() => displayEntity, traits.InstancedPose)
31
+
32
+ let hoverInfo = $state.raw<HoverInfo | null>(null)
33
+
34
+ $effect(() => {
35
+ if (displayEntity && displayedHoverInfo.current) {
36
+ const linkType = displayEntity?.get(relations.SubEntityLink(linkedEntity))?.type
37
+ if (linkType !== SubEntityLinkType.HoverLink) {
38
+ return
39
+ }
40
+ // Index Mapping is a formula with the variable 'index' in it, available operations can be found here: https://github.com/silentmatt/expr-eval/tree/master
41
+ const indexMapping =
42
+ displayEntity?.get(relations.SubEntityLink(linkedEntity))?.indexMapping ?? 'index'
43
+ const expression = parser.parse(indexMapping)
44
+ const resolvedIndex = expression.evaluate({ index: displayedHoverInfo.current.index })
45
+ const linkedHoverInfo = getLinkedHoverInfo(resolvedIndex, linkedEntity)
46
+ hoverInfo = linkedHoverInfo
47
+ } else {
48
+ hoverInfo = null
49
+ }
50
+ })
51
+ </script>
52
+
53
+ {#if hoverInfo}
54
+ <HoveredEntityTooltip {hoverInfo} />
55
+ {/if}
@@ -0,0 +1,9 @@
1
+ import { Parser } from 'expr-eval';
2
+ export declare const parser: Parser;
3
+ import type { Entity } from 'koota';
4
+ interface Props {
5
+ linkedEntity: Entity;
6
+ }
7
+ declare const LinkedHoveredEntity: import("svelte").Component<Props, {}, "">;
8
+ type LinkedHoveredEntity = ReturnType<typeof LinkedHoveredEntity>;
9
+ export default LinkedHoveredEntity;