@viamrobotics/motion-tools 1.7.0 → 1.9.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.
- package/dist/components/App.svelte +11 -6
- package/dist/components/CameraControls.svelte +1 -1
- package/dist/components/Focus.svelte +1 -1
- package/dist/components/Frame.svelte +1 -1
- package/dist/components/Geometry2.svelte +8 -5
- package/dist/components/HoveredEntities.svelte +19 -0
- package/dist/components/HoveredEntities.svelte.d.ts +3 -0
- package/dist/components/HoveredEntityTooltip.svelte +242 -0
- package/dist/components/HoveredEntityTooltip.svelte.d.ts +7 -0
- package/dist/components/Label.svelte +1 -1
- package/dist/components/{DotSprite.svelte → MeasureTool/MeasurePoint.svelte} +8 -18
- package/dist/components/MeasureTool/MeasurePoint.svelte.d.ts +8 -0
- package/dist/components/MeasureTool/MeasureTool.svelte +176 -0
- package/dist/components/Scene.svelte +1 -1
- package/dist/components/{Details.svelte → overlay/Details.svelte} +10 -10
- package/dist/components/{LiveUpdatesBanner.svelte → overlay/LiveUpdatesBanner.svelte} +2 -2
- package/dist/components/overlay/Popover.svelte +28 -0
- package/dist/components/overlay/Popover.svelte.d.ts +9 -0
- package/dist/components/overlay/ToggleGroup.svelte +60 -0
- package/dist/components/overlay/ToggleGroup.svelte.d.ts +13 -0
- package/dist/components/{__tests__ → overlay/__tests__}/__fixtures__/entity.js +1 -1
- package/dist/components/{dashboard → overlay/dashboard}/Button.svelte +8 -4
- package/dist/components/{dashboard → overlay/dashboard}/Button.svelte.d.ts +3 -2
- package/dist/components/{dashboard → overlay/dashboard}/Dashboard.svelte +2 -2
- package/dist/components/{Tree → overlay/left-pane}/AddFrames.svelte +2 -2
- package/dist/components/{Tree → overlay/left-pane}/Logs.svelte +1 -1
- package/dist/components/{RefreshRate.svelte → overlay/left-pane/RefreshRate.svelte} +1 -1
- package/dist/components/{Tree → overlay/left-pane}/Settings.svelte +12 -7
- package/dist/components/{Tree → overlay/left-pane}/Tree.svelte +3 -3
- package/dist/components/{Tree → overlay/left-pane}/TreeContainer.svelte +8 -8
- package/dist/components/{Tree → overlay/left-pane}/Widgets.svelte +3 -3
- package/dist/components/{Tree → overlay/left-pane}/buildTree.js +1 -1
- package/dist/components/{widgets → overlay/widgets}/ArmPositions.svelte +4 -4
- package/dist/components/{widgets → overlay/widgets}/Camera.svelte +5 -5
- package/dist/ecs/traits.d.ts +19 -12
- package/dist/ecs/traits.js +18 -11
- package/dist/ecs/useQuery.svelte.js +10 -10
- package/dist/hooks/useDrawAPI.svelte.js +1 -1
- package/dist/hooks/useFrames.svelte.js +1 -0
- package/dist/hooks/useGeometries.svelte.js +1 -1
- package/dist/hooks/useObjectEvents.svelte.d.ts +1 -0
- package/dist/hooks/useObjectEvents.svelte.js +24 -0
- package/dist/hooks/usePointcloudObjects.svelte.js +1 -1
- package/dist/hooks/usePointclouds.svelte.js +28 -41
- package/dist/hooks/usePose.svelte.js +1 -1
- package/dist/hooks/useSettings.svelte.d.ts +4 -0
- package/dist/hooks/useSettings.svelte.js +4 -0
- package/dist/hooks/useWorldState.svelte.js +1 -1
- package/dist/three/InstancedArrows/raycast.js +2 -6
- package/package.json +6 -2
- package/dist/components/DotSprite.svelte.d.ts +0 -10
- package/dist/components/MeasureTool.svelte +0 -123
- package/dist/components/null-states/Connection.svelte +0 -0
- package/dist/components/null-states/Connection.svelte.d.ts +0 -26
- /package/dist/components/{MeasureTool.svelte.d.ts → MeasureTool/MeasureTool.svelte.d.ts} +0 -0
- /package/dist/components/{Details.svelte.d.ts → overlay/Details.svelte.d.ts} +0 -0
- /package/dist/components/{LiveUpdatesBanner.svelte.d.ts → overlay/LiveUpdatesBanner.svelte.d.ts} +0 -0
- /package/dist/components/{shared → overlay}/Table.svelte +0 -0
- /package/dist/components/{shared → overlay}/Table.svelte.d.ts +0 -0
- /package/dist/components/{__tests__ → overlay/__tests__}/__fixtures__/entity.d.ts +0 -0
- /package/dist/components/{__tests__ → overlay/__tests__}/__fixtures__/resource.d.ts +0 -0
- /package/dist/components/{__tests__ → overlay/__tests__}/__fixtures__/resource.js +0 -0
- /package/dist/components/{dashboard → overlay/dashboard}/Dashboard.svelte.d.ts +0 -0
- /package/dist/components/{Tree → overlay/left-pane}/AddFrames.svelte.d.ts +0 -0
- /package/dist/components/{Tree → overlay/left-pane}/Drawer.svelte +0 -0
- /package/dist/components/{Tree → overlay/left-pane}/Drawer.svelte.d.ts +0 -0
- /package/dist/components/{Tree → overlay/left-pane}/Logs.svelte.d.ts +0 -0
- /package/dist/components/{RefreshRate.svelte.d.ts → overlay/left-pane/RefreshRate.svelte.d.ts} +0 -0
- /package/dist/components/{Tree → overlay/left-pane}/Settings.svelte.d.ts +0 -0
- /package/dist/components/{Tree → overlay/left-pane}/Tree.svelte.d.ts +0 -0
- /package/dist/components/{Tree → overlay/left-pane}/TreeContainer.svelte.d.ts +0 -0
- /package/dist/components/{Tree → overlay/left-pane}/Widgets.svelte.d.ts +0 -0
- /package/dist/components/{Tree → overlay/left-pane}/buildTree.d.ts +0 -0
- /package/dist/components/{Tree → overlay/left-pane}/useExpanded.svelte.d.ts +0 -0
- /package/dist/components/{Tree → overlay/left-pane}/useExpanded.svelte.js +0 -0
- /package/dist/components/{widgets → overlay/widgets}/ArmPositions.svelte.d.ts +0 -0
- /package/dist/components/{widgets → overlay/widgets}/Camera.svelte.d.ts +0 -0
|
@@ -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 './
|
|
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,7 +26,8 @@
|
|
|
26
26
|
provideDrawConnectionConfig,
|
|
27
27
|
type DrawConnectionConfig,
|
|
28
28
|
} from '../hooks/useDrawConnectionConfig.svelte'
|
|
29
|
-
import Camera from './widgets/Camera.svelte'
|
|
29
|
+
import Camera from './overlay/widgets/Camera.svelte'
|
|
30
|
+
import HoveredEntities from './HoveredEntities.svelte'
|
|
30
31
|
|
|
31
32
|
interface LocalConfigProps {
|
|
32
33
|
getLocalPartConfig: () => Struct
|
|
@@ -128,6 +129,10 @@
|
|
|
128
129
|
{@attach domPortal(root)}
|
|
129
130
|
{dashboard}
|
|
130
131
|
/>
|
|
132
|
+
|
|
133
|
+
{#if settings.current.renderSubEntityHoverDetail}
|
|
134
|
+
<HoveredEntities {@attach domPortal(root)} />
|
|
135
|
+
{/if}
|
|
131
136
|
<Details {@attach domPortal(root)} />
|
|
132
137
|
{#if environment.current.isStandalone}
|
|
133
138
|
<LiveUpdatesBanner {@attach domPortal(root)} />
|
|
@@ -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
|
|
@@ -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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useQuery } from '../ecs'
|
|
3
|
+
import { traits } from '../ecs'
|
|
4
|
+
import HoveredEntityTooltip from './HoveredEntityTooltip.svelte'
|
|
5
|
+
import { useSelectedEntity } from '../hooks/useSelection.svelte'
|
|
6
|
+
import { useFocusedEntity } from '../hooks/useSelection.svelte'
|
|
7
|
+
|
|
8
|
+
const hoveredEntities = useQuery(traits.Hover)
|
|
9
|
+
const selectedEntity = useSelectedEntity()
|
|
10
|
+
const focusedEntity = useFocusedEntity()
|
|
11
|
+
|
|
12
|
+
const displayEntity = $derived(selectedEntity.current ?? focusedEntity.current) // for now, only display hover tooltip if the entity is selected or focused
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
{#each hoveredEntities.current as entity (entity)}
|
|
16
|
+
{#if entity === displayEntity}
|
|
17
|
+
<HoveredEntityTooltip hoveredEntity={entity} />
|
|
18
|
+
{/if}
|
|
19
|
+
{/each}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
<script
|
|
2
|
+
module
|
|
3
|
+
lang="ts"
|
|
4
|
+
>
|
|
5
|
+
import { Vector3 } from 'three'
|
|
6
|
+
|
|
7
|
+
interface ClosestArrow {
|
|
8
|
+
index: number
|
|
9
|
+
x: number
|
|
10
|
+
y: number
|
|
11
|
+
z: number
|
|
12
|
+
oX: number
|
|
13
|
+
oY: number
|
|
14
|
+
oZ: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ClosestPoint {
|
|
18
|
+
index: number
|
|
19
|
+
x: number
|
|
20
|
+
y: number
|
|
21
|
+
z: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const getClosestArrow = (positions: Float32Array, point: Vector3): ClosestArrow => {
|
|
25
|
+
let smallestDistance = Infinity
|
|
26
|
+
let index = -1
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < positions.length; i += 6) {
|
|
29
|
+
const x = positions[i] / 1000
|
|
30
|
+
const y = positions[i + 1] / 1000
|
|
31
|
+
const z = positions[i + 2] / 1000
|
|
32
|
+
|
|
33
|
+
const distance = point.distanceToSquared(new Vector3(x, y, z))
|
|
34
|
+
|
|
35
|
+
if (distance < smallestDistance) {
|
|
36
|
+
smallestDistance = distance
|
|
37
|
+
index = i
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
index: Math.floor(index / 6),
|
|
43
|
+
x: positions[index] / 1000,
|
|
44
|
+
y: positions[index + 1] / 1000,
|
|
45
|
+
z: positions[index + 2] / 1000,
|
|
46
|
+
oX: positions[index + 3],
|
|
47
|
+
oY: positions[index + 4],
|
|
48
|
+
oZ: positions[index + 5],
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const getClosestPoint = (positions: Float32Array, point: Vector3): ClosestPoint => {
|
|
53
|
+
let smallestDistance = Infinity
|
|
54
|
+
let index = -1
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < positions.length; i += 3) {
|
|
57
|
+
const x = positions[i]
|
|
58
|
+
const y = positions[i + 1]
|
|
59
|
+
const z = positions[i + 2]
|
|
60
|
+
|
|
61
|
+
const distance = point.distanceToSquared(new Vector3(x, y, z))
|
|
62
|
+
|
|
63
|
+
if (distance < smallestDistance) {
|
|
64
|
+
smallestDistance = distance
|
|
65
|
+
index = i
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
index: Math.floor(index / 3),
|
|
71
|
+
x: positions[index],
|
|
72
|
+
y: positions[index + 1],
|
|
73
|
+
z: positions[index + 2],
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const getPointAtIndex = (positions: Float32Array, index: number): ClosestPoint => ({
|
|
78
|
+
index,
|
|
79
|
+
x: positions[index * 3],
|
|
80
|
+
y: positions[index * 3 + 1],
|
|
81
|
+
z: positions[index * 3 + 2],
|
|
82
|
+
})
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<script lang="ts">
|
|
86
|
+
import { traits } from '../ecs'
|
|
87
|
+
import { HTML } from '@threlte/extras'
|
|
88
|
+
import type { Entity } from 'koota'
|
|
89
|
+
import { useWorld } from '../ecs'
|
|
90
|
+
import { onDestroy } from 'svelte'
|
|
91
|
+
|
|
92
|
+
interface Props {
|
|
93
|
+
hoveredEntity: Entity
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let { hoveredEntity }: Props = $props()
|
|
97
|
+
|
|
98
|
+
const world = useWorld()
|
|
99
|
+
|
|
100
|
+
let tooltipData: {
|
|
101
|
+
subEntityPosition: Vector3 | undefined
|
|
102
|
+
closestArrow?: ClosestArrow
|
|
103
|
+
closestPoint?: ClosestPoint
|
|
104
|
+
} | null = $state.raw(null)
|
|
105
|
+
|
|
106
|
+
const getTooltipData = (entity: Entity) => {
|
|
107
|
+
if (entity !== hoveredEntity) {
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const hover = entity.get(traits.Hover)
|
|
112
|
+
if (!hover) return null
|
|
113
|
+
|
|
114
|
+
const hoverPosition = new Vector3(hover.x, hover.y, hover.z)
|
|
115
|
+
const index = hover.index >= 0 ? hover.index : undefined
|
|
116
|
+
|
|
117
|
+
let closestArrow: ClosestArrow | undefined
|
|
118
|
+
let closestPoint: ClosestPoint | undefined
|
|
119
|
+
let subEntityPosition: Vector3 | undefined
|
|
120
|
+
|
|
121
|
+
if (entity.has(traits.Arrows)) {
|
|
122
|
+
// TODO: maybe we could store the arrows in a buffered geometry to avoid the slow getClosestArrow
|
|
123
|
+
closestArrow = getClosestArrow(entity.get(traits.Positions) as Float32Array, hoverPosition)
|
|
124
|
+
subEntityPosition = new Vector3(closestArrow.x, closestArrow.y, closestArrow.z)
|
|
125
|
+
} else if (entity.has(traits.Points)) {
|
|
126
|
+
const positions = entity.get(traits.BufferGeometry)?.attributes.position.array as Float32Array
|
|
127
|
+
|
|
128
|
+
// we can skip the slow getClosestPoint if the points provided an index already
|
|
129
|
+
if (index !== undefined) {
|
|
130
|
+
closestPoint = getPointAtIndex(positions, index)
|
|
131
|
+
} else {
|
|
132
|
+
closestPoint = getClosestPoint(positions, hoverPosition)
|
|
133
|
+
}
|
|
134
|
+
subEntityPosition = new Vector3(closestPoint.x, closestPoint.y, closestPoint.z)
|
|
135
|
+
}
|
|
136
|
+
return { subEntityPosition, closestArrow, closestPoint }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const unsubChange = world.onChange(traits.Hover, (entity) => {
|
|
140
|
+
if (entity === hoveredEntity) {
|
|
141
|
+
tooltipData = getTooltipData(entity)
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const unsubRemove = world.onRemove(traits.Hover, (entity) => {
|
|
146
|
+
if (entity === hoveredEntity) {
|
|
147
|
+
tooltipData = null
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
onDestroy(() => {
|
|
152
|
+
unsubChange()
|
|
153
|
+
unsubRemove()
|
|
154
|
+
})
|
|
155
|
+
</script>
|
|
156
|
+
|
|
157
|
+
{#if tooltipData?.subEntityPosition}
|
|
158
|
+
<HTML
|
|
159
|
+
position={tooltipData.subEntityPosition.toArray()}
|
|
160
|
+
class="pointer-events-none"
|
|
161
|
+
zIndexRange={[3, 0]}
|
|
162
|
+
>
|
|
163
|
+
<div
|
|
164
|
+
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"
|
|
165
|
+
>
|
|
166
|
+
<!-- Arrow -->
|
|
167
|
+
<div
|
|
168
|
+
class="border-medium absolute -bottom-[5px] left-1/2 size-2.5 -translate-x-1/2 rotate-45 border-r border-b bg-white"
|
|
169
|
+
></div>
|
|
170
|
+
|
|
171
|
+
<div class="flex flex-col gap-2.5">
|
|
172
|
+
{#if tooltipData.closestArrow}
|
|
173
|
+
<div>
|
|
174
|
+
<div class="mb-1"><strong class="font-semibold">index</strong></div>
|
|
175
|
+
<div>{tooltipData.closestArrow.index}</div>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
<div>
|
|
179
|
+
<div class="mb-1">
|
|
180
|
+
<strong class="font-semibold">world position</strong>
|
|
181
|
+
<span class="text-subtle-2"> (m)</span>
|
|
182
|
+
</div>
|
|
183
|
+
<div class="flex gap-3">
|
|
184
|
+
<div>
|
|
185
|
+
<span class="text-subtle-2 mr-1">x </span>{tooltipData.closestArrow.x.toFixed(2)}
|
|
186
|
+
</div>
|
|
187
|
+
<div>
|
|
188
|
+
<span class="text-subtle-2 mr-1">y </span>{tooltipData.closestArrow.y.toFixed(2)}
|
|
189
|
+
</div>
|
|
190
|
+
<div>
|
|
191
|
+
<span class="text-subtle-2 mr-1">z </span>{tooltipData.closestArrow.z.toFixed(2)}
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<div>
|
|
197
|
+
<div class="mb-1">
|
|
198
|
+
<strong class="font-semibold">world orientation</strong>
|
|
199
|
+
<span class="text-subtle-2"> (deg)</span>
|
|
200
|
+
</div>
|
|
201
|
+
<div class="flex gap-3">
|
|
202
|
+
<div>
|
|
203
|
+
<span class="text-subtle-2 mr-1">x </span>{tooltipData.closestArrow.oX.toFixed(2)}
|
|
204
|
+
</div>
|
|
205
|
+
<div>
|
|
206
|
+
<span class="text-subtle-2 mr-1">y </span>{tooltipData.closestArrow.oY.toFixed(2)}
|
|
207
|
+
</div>
|
|
208
|
+
<div>
|
|
209
|
+
<span class="text-subtle-2 mr-1">z </span>{tooltipData.closestArrow.oZ.toFixed(2)}
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
{/if}
|
|
214
|
+
|
|
215
|
+
{#if tooltipData.closestPoint}
|
|
216
|
+
<div>
|
|
217
|
+
<div class="mb-1"><strong class="font-semibold">index</strong></div>
|
|
218
|
+
<div>{tooltipData.closestPoint.index}</div>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<div>
|
|
222
|
+
<div class="mb-1">
|
|
223
|
+
<strong class="font-semibold">world position</strong>
|
|
224
|
+
<span class="text-subtle-2"> (m)</span>
|
|
225
|
+
</div>
|
|
226
|
+
<div class="flex gap-3">
|
|
227
|
+
<div>
|
|
228
|
+
<span class="text-subtle-2">x </span>{tooltipData.closestPoint.x.toFixed(2)}
|
|
229
|
+
</div>
|
|
230
|
+
<div>
|
|
231
|
+
<span class="text-subtle-2">y </span>{tooltipData.closestPoint.y.toFixed(2)}
|
|
232
|
+
</div>
|
|
233
|
+
<div>
|
|
234
|
+
<span class="text-subtle-2">z </span>{tooltipData.closestPoint.z.toFixed(2)}
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
{/if}
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
</HTML>
|
|
242
|
+
{/if}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Entity } from 'koota';
|
|
2
|
+
interface Props {
|
|
3
|
+
hoveredEntity: Entity;
|
|
4
|
+
}
|
|
5
|
+
declare const HoveredEntityTooltip: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type HoveredEntityTooltip = ReturnType<typeof HoveredEntityTooltip>;
|
|
7
|
+
export default HoveredEntityTooltip;
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { T, type Props as ThrelteProps } from '@threlte/core'
|
|
3
|
-
import type {
|
|
3
|
+
import type { Vector3Tuple, Group } from 'three'
|
|
4
4
|
import { HTML } from '@threlte/extras'
|
|
5
5
|
|
|
6
6
|
interface Props extends ThrelteProps<typeof Group> {
|
|
7
7
|
position: Vector3Tuple
|
|
8
|
-
color?: ColorRepresentation
|
|
9
|
-
opacity?: number
|
|
10
8
|
}
|
|
11
9
|
|
|
12
|
-
let { position,
|
|
10
|
+
let { position, ref = $bindable(), ...rest }: Props = $props()
|
|
13
11
|
</script>
|
|
14
12
|
|
|
15
13
|
<T.Group
|
|
@@ -17,23 +15,15 @@
|
|
|
17
15
|
{...rest}
|
|
18
16
|
{position}
|
|
19
17
|
>
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
>
|
|
26
|
-
<T.SphereGeometry />
|
|
27
|
-
<T.MeshBasicMaterial
|
|
28
|
-
color={color ?? 'black'}
|
|
29
|
-
transparent
|
|
30
|
-
depthTest={false}
|
|
31
|
-
{opacity}
|
|
32
|
-
/>
|
|
33
|
-
</T.Mesh>
|
|
18
|
+
<HTML
|
|
19
|
+
center
|
|
20
|
+
zIndexRange={[3, 0]}
|
|
21
|
+
class="h-2.5 w-2.5 rounded-full bg-black/70"
|
|
22
|
+
/>
|
|
34
23
|
|
|
35
24
|
<HTML
|
|
36
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]}
|
|
37
27
|
>
|
|
38
28
|
<div class="flex justify-between">
|
|
39
29
|
<span class="text-subtle-2">x</span>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Props as ThrelteProps } from '@threlte/core';
|
|
2
|
+
import type { Vector3Tuple, Group } from 'three';
|
|
3
|
+
interface Props extends ThrelteProps<typeof Group> {
|
|
4
|
+
position: Vector3Tuple;
|
|
5
|
+
}
|
|
6
|
+
declare const MeasurePoint: import("svelte").Component<Props, {}, "ref">;
|
|
7
|
+
type MeasurePoint = ReturnType<typeof MeasurePoint>;
|
|
8
|
+
export default MeasurePoint;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { untrack } from 'svelte'
|
|
3
|
+
import { Vector3, type Intersection } from 'three'
|
|
4
|
+
import { T } from '@threlte/core'
|
|
5
|
+
import { HTML, MeshLineGeometry, MeshLineMaterial, Portal } from '@threlte/extras'
|
|
6
|
+
import { useSettings } from '../../hooks/useSettings.svelte'
|
|
7
|
+
import Button from '../overlay/dashboard/Button.svelte'
|
|
8
|
+
import MeasurePoint from './MeasurePoint.svelte'
|
|
9
|
+
import { useMouseRaycaster } from '../../hooks/useMouseRaycaster.svelte'
|
|
10
|
+
import { useFocusedEntity } from '../../hooks/useSelection.svelte'
|
|
11
|
+
import ToggleGroup from '../overlay/ToggleGroup.svelte'
|
|
12
|
+
import Popover from '../overlay/Popover.svelte'
|
|
13
|
+
|
|
14
|
+
const focusedEntity = useFocusedEntity()
|
|
15
|
+
const settings = useSettings()
|
|
16
|
+
|
|
17
|
+
const htmlPosition = new Vector3()
|
|
18
|
+
|
|
19
|
+
let step = $state<'idle' | 'p1' | 'p2'>('idle')
|
|
20
|
+
let intersection = $state<Intersection>()
|
|
21
|
+
let p1 = $state.raw<Vector3>()
|
|
22
|
+
let p2 = $state.raw<Vector3>()
|
|
23
|
+
|
|
24
|
+
const enabled = $derived(settings.current.enableMeasure)
|
|
25
|
+
|
|
26
|
+
const { onclick, onmove, raycaster } = useMouseRaycaster(() => ({
|
|
27
|
+
enabled,
|
|
28
|
+
}))
|
|
29
|
+
raycaster.firstHitOnly = true
|
|
30
|
+
raycaster.params.Points.threshold = 0.005
|
|
31
|
+
|
|
32
|
+
onmove((event) => {
|
|
33
|
+
intersection = event.intersections[0]
|
|
34
|
+
|
|
35
|
+
// Only handle axis restrictions if a first point has been placed
|
|
36
|
+
if (!p1) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (settings.current.enableMeasureAxisX === false) {
|
|
41
|
+
intersection.point.x = p1.x
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (settings.current.enableMeasureAxisY === false) {
|
|
45
|
+
intersection.point.y = p1.y
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (settings.current.enableMeasureAxisZ === false) {
|
|
49
|
+
intersection.point.z = p1.z
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
onclick(() => {
|
|
54
|
+
if (step === 'idle' && intersection) {
|
|
55
|
+
p1 = intersection.point.clone()
|
|
56
|
+
step = 'p1'
|
|
57
|
+
} else if (step === 'p1' && intersection) {
|
|
58
|
+
p2 = intersection.point.clone()
|
|
59
|
+
step = 'p2'
|
|
60
|
+
} else if (step === 'p2') {
|
|
61
|
+
p1 = undefined
|
|
62
|
+
p2 = undefined
|
|
63
|
+
step = 'idle'
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const clear = () => {
|
|
68
|
+
p1 = undefined
|
|
69
|
+
p2 = undefined
|
|
70
|
+
step = 'idle'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
$effect(() => {
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
75
|
+
;(focusedEntity.current, enabled)
|
|
76
|
+
untrack(() => clear())
|
|
77
|
+
})
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<Portal id="dashboard">
|
|
81
|
+
<fieldset class="relative">
|
|
82
|
+
<div class="flex">
|
|
83
|
+
<Button
|
|
84
|
+
active={enabled}
|
|
85
|
+
icon="ruler"
|
|
86
|
+
description="{enabled ? 'Disable' : 'Enable'} measurement"
|
|
87
|
+
onclick={() => {
|
|
88
|
+
settings.current.enableMeasure = !settings.current.enableMeasure
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
<Popover>
|
|
92
|
+
{#snippet trigger(triggerProps)}
|
|
93
|
+
<Button
|
|
94
|
+
{...triggerProps}
|
|
95
|
+
active={enabled}
|
|
96
|
+
class="border-l-0"
|
|
97
|
+
icon="filter-sliders"
|
|
98
|
+
description="Measurement settings"
|
|
99
|
+
/>
|
|
100
|
+
{/snippet}
|
|
101
|
+
|
|
102
|
+
<div class="border-medium m-2 border bg-white p-2 text-xs">
|
|
103
|
+
<div class="flex items-center gap-2">
|
|
104
|
+
Enabled axes
|
|
105
|
+
<ToggleGroup
|
|
106
|
+
multiple
|
|
107
|
+
buttons={[
|
|
108
|
+
{ value: 'x', on: settings.current.enableMeasureAxisX },
|
|
109
|
+
{ value: 'y', on: settings.current.enableMeasureAxisY },
|
|
110
|
+
{ value: 'z', on: settings.current.enableMeasureAxisZ },
|
|
111
|
+
]}
|
|
112
|
+
onclick={(details) => {
|
|
113
|
+
settings.current.enableMeasureAxisX = details.includes('x')
|
|
114
|
+
settings.current.enableMeasureAxisY = details.includes('y')
|
|
115
|
+
settings.current.enableMeasureAxisZ = details.includes('z')
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
</Popover>
|
|
121
|
+
</div>
|
|
122
|
+
</fieldset>
|
|
123
|
+
</Portal>
|
|
124
|
+
|
|
125
|
+
{#if enabled}
|
|
126
|
+
{#if intersection && step !== 'p2'}
|
|
127
|
+
<MeasurePoint
|
|
128
|
+
position={intersection?.point.toArray()}
|
|
129
|
+
opacity={0.5}
|
|
130
|
+
/>
|
|
131
|
+
{/if}
|
|
132
|
+
|
|
133
|
+
{#if p1}
|
|
134
|
+
<MeasurePoint
|
|
135
|
+
position={p1.toArray()}
|
|
136
|
+
opacity={0.5}
|
|
137
|
+
/>
|
|
138
|
+
{/if}
|
|
139
|
+
|
|
140
|
+
{#if p2}
|
|
141
|
+
<MeasurePoint
|
|
142
|
+
position={p2.toArray()}
|
|
143
|
+
opacity={0.5}
|
|
144
|
+
/>
|
|
145
|
+
{/if}
|
|
146
|
+
|
|
147
|
+
{#if p1 && (p2 || intersection)}
|
|
148
|
+
<T.Mesh
|
|
149
|
+
raycast={() => null}
|
|
150
|
+
bvh={{ enabled: false }}
|
|
151
|
+
renderOrder={1}
|
|
152
|
+
>
|
|
153
|
+
<MeshLineGeometry points={[p1, p2 ?? intersection?.point ?? new Vector3()]} />
|
|
154
|
+
<MeshLineMaterial
|
|
155
|
+
width={2.5}
|
|
156
|
+
depthTest={false}
|
|
157
|
+
color="black"
|
|
158
|
+
opacity={p2 ? 0.5 : 0.2}
|
|
159
|
+
attenuate={false}
|
|
160
|
+
transparent
|
|
161
|
+
/>
|
|
162
|
+
</T.Mesh>
|
|
163
|
+
|
|
164
|
+
{#if p2}
|
|
165
|
+
<HTML
|
|
166
|
+
center
|
|
167
|
+
position={htmlPosition.lerpVectors(p1, p2, 0.5).toArray()}
|
|
168
|
+
zIndexRange={[3, 0]}
|
|
169
|
+
>
|
|
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>
|
|
172
|
+
</div>
|
|
173
|
+
</HTML>
|
|
174
|
+
{/if}
|
|
175
|
+
{/if}
|
|
176
|
+
{/if}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { useOrigin } from './xr/useOrigin.svelte'
|
|
15
15
|
import { useSettings } from '../hooks/useSettings.svelte'
|
|
16
16
|
import CameraControls from './CameraControls.svelte'
|
|
17
|
-
import MeasureTool from './MeasureTool.svelte'
|
|
17
|
+
import MeasureTool from './MeasureTool/MeasureTool.svelte'
|
|
18
18
|
import PointerMissBox from './PointerMissBox.svelte'
|
|
19
19
|
import BatchedArrows from './BatchedArrows.svelte'
|
|
20
20
|
import Arrows from './Arrows/ArrowGroups.svelte'
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
module
|
|
3
3
|
lang="ts"
|
|
4
4
|
>
|
|
5
|
-
import { OrientationVector } from '
|
|
5
|
+
import { OrientationVector } from '../../three/OrientationVector'
|
|
6
6
|
import { Quaternion, Vector3, MathUtils, BufferAttribute } from 'three'
|
|
7
7
|
|
|
8
8
|
const vec3 = new Vector3()
|
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
useFocusedEntity,
|
|
21
21
|
useFocusedObject3d,
|
|
22
22
|
useSelectedObject3d,
|
|
23
|
-
} from '
|
|
24
|
-
import { useFrames } from '
|
|
25
|
-
import { usePartConfig } from '
|
|
26
|
-
import { FrameConfigUpdater } from '
|
|
27
|
-
import { useEnvironment } from '
|
|
28
|
-
import { traits, useTrait, useWorld } from '
|
|
29
|
-
import { useResourceByName } from '
|
|
30
|
-
import { useCameraControls } from '
|
|
23
|
+
} from '../../hooks/useSelection.svelte'
|
|
24
|
+
import { useFrames } from '../../hooks/useFrames.svelte'
|
|
25
|
+
import { usePartConfig } from '../../hooks/usePartConfig.svelte'
|
|
26
|
+
import { FrameConfigUpdater } from '../../FrameConfigUpdater.svelte'
|
|
27
|
+
import { useEnvironment } from '../../hooks/useEnvironment.svelte'
|
|
28
|
+
import { traits, useTrait, useWorld } from '../../ecs'
|
|
29
|
+
import { useResourceByName } from '../../hooks/useResourceByName.svelte'
|
|
30
|
+
import { useCameraControls } from '../../hooks/useControls.svelte'
|
|
31
31
|
|
|
32
32
|
const { ...rest } = $props()
|
|
33
33
|
|
|
@@ -232,7 +232,7 @@
|
|
|
232
232
|
|
|
233
233
|
<div
|
|
234
234
|
id="details-panel"
|
|
235
|
-
class="border-medium bg-extralight absolute top-0 right-0 z-
|
|
235
|
+
class="border-medium bg-extralight absolute top-0 right-0 z-4 m-2 {showEditFrameOptions
|
|
236
236
|
? 'w-80'
|
|
237
237
|
: 'w-60'} border p-2 text-xs"
|
|
238
238
|
use:draggable={{
|