@viamrobotics/motion-tools 1.7.0 → 1.9.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.
- package/dist/components/App.svelte +5 -0
- package/dist/components/HoveredEntities.svelte +19 -0
- package/dist/components/HoveredEntities.svelte.d.ts +3 -0
- package/dist/components/HoveredEntityTooltip.svelte +241 -0
- package/dist/components/HoveredEntityTooltip.svelte.d.ts +7 -0
- package/dist/components/MeasureTool/MeasurePoint.svelte +47 -0
- package/dist/components/MeasureTool/MeasurePoint.svelte.d.ts +8 -0
- package/dist/components/MeasureTool/MeasureTool.svelte +176 -0
- package/dist/components/MeasureTool/MeasureTool.svelte.d.ts +3 -0
- 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/Scene.svelte +1 -1
- package/dist/components/Tree/Settings.svelte +5 -0
- package/dist/components/dashboard/Button.svelte +7 -3
- package/dist/components/dashboard/Button.svelte.d.ts +3 -2
- package/dist/ecs/traits.d.ts +18 -12
- package/dist/ecs/traits.js +17 -11
- package/dist/ecs/useQuery.svelte.js +10 -10
- package/dist/hooks/useObjectEvents.svelte.d.ts +1 -0
- package/dist/hooks/useObjectEvents.svelte.js +24 -0
- package/dist/hooks/useSettings.svelte.d.ts +4 -0
- package/dist/hooks/useSettings.svelte.js +4 -0
- package/dist/three/InstancedArrows/raycast.js +2 -6
- package/package.json +6 -2
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
type DrawConnectionConfig,
|
|
28
28
|
} from '../hooks/useDrawConnectionConfig.svelte'
|
|
29
29
|
import Camera from './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)} />
|
|
@@ -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,241 @@
|
|
|
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
|
+
>
|
|
162
|
+
<div
|
|
163
|
+
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"
|
|
164
|
+
>
|
|
165
|
+
<!-- Arrow -->
|
|
166
|
+
<div
|
|
167
|
+
class="border-medium absolute -bottom-[5px] left-1/2 size-2.5 -translate-x-1/2 rotate-45 border-r border-b bg-white"
|
|
168
|
+
></div>
|
|
169
|
+
|
|
170
|
+
<div class="flex flex-col gap-2.5">
|
|
171
|
+
{#if tooltipData.closestArrow}
|
|
172
|
+
<div>
|
|
173
|
+
<div class="mb-1"><strong class="font-semibold">index</strong></div>
|
|
174
|
+
<div>{tooltipData.closestArrow.index}</div>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<div>
|
|
178
|
+
<div class="mb-1">
|
|
179
|
+
<strong class="font-semibold">world position</strong>
|
|
180
|
+
<span class="text-subtle-2"> (m)</span>
|
|
181
|
+
</div>
|
|
182
|
+
<div class="flex gap-3">
|
|
183
|
+
<div>
|
|
184
|
+
<span class="text-subtle-2 mr-1">x </span>{tooltipData.closestArrow.x.toFixed(2)}
|
|
185
|
+
</div>
|
|
186
|
+
<div>
|
|
187
|
+
<span class="text-subtle-2 mr-1">y </span>{tooltipData.closestArrow.y.toFixed(2)}
|
|
188
|
+
</div>
|
|
189
|
+
<div>
|
|
190
|
+
<span class="text-subtle-2 mr-1">z </span>{tooltipData.closestArrow.z.toFixed(2)}
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<div>
|
|
196
|
+
<div class="mb-1">
|
|
197
|
+
<strong class="font-semibold">world orientation</strong>
|
|
198
|
+
<span class="text-subtle-2"> (deg)</span>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="flex gap-3">
|
|
201
|
+
<div>
|
|
202
|
+
<span class="text-subtle-2 mr-1">x </span>{tooltipData.closestArrow.oX.toFixed(2)}
|
|
203
|
+
</div>
|
|
204
|
+
<div>
|
|
205
|
+
<span class="text-subtle-2 mr-1">y </span>{tooltipData.closestArrow.oY.toFixed(2)}
|
|
206
|
+
</div>
|
|
207
|
+
<div>
|
|
208
|
+
<span class="text-subtle-2 mr-1">z </span>{tooltipData.closestArrow.oZ.toFixed(2)}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
{/if}
|
|
213
|
+
|
|
214
|
+
{#if tooltipData.closestPoint}
|
|
215
|
+
<div>
|
|
216
|
+
<div class="mb-1"><strong class="font-semibold">index</strong></div>
|
|
217
|
+
<div>{tooltipData.closestPoint.index}</div>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<div>
|
|
221
|
+
<div class="mb-1">
|
|
222
|
+
<strong class="font-semibold">world position</strong>
|
|
223
|
+
<span class="text-subtle-2"> (m)</span>
|
|
224
|
+
</div>
|
|
225
|
+
<div class="flex gap-3">
|
|
226
|
+
<div>
|
|
227
|
+
<span class="text-subtle-2">x </span>{tooltipData.closestPoint.x.toFixed(2)}
|
|
228
|
+
</div>
|
|
229
|
+
<div>
|
|
230
|
+
<span class="text-subtle-2">y </span>{tooltipData.closestPoint.y.toFixed(2)}
|
|
231
|
+
</div>
|
|
232
|
+
<div>
|
|
233
|
+
<span class="text-subtle-2">z </span>{tooltipData.closestPoint.z.toFixed(2)}
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
{/if}
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
</HTML>
|
|
241
|
+
{/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;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { T, type Props as ThrelteProps } from '@threlte/core'
|
|
3
|
+
import type { Vector3Tuple, Group } from 'three'
|
|
4
|
+
import { HTML } from '@threlte/extras'
|
|
5
|
+
|
|
6
|
+
interface Props extends ThrelteProps<typeof Group> {
|
|
7
|
+
position: Vector3Tuple
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let { position, ref = $bindable(), ...rest }: Props = $props()
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<T.Group
|
|
14
|
+
bind:ref
|
|
15
|
+
{...rest}
|
|
16
|
+
{position}
|
|
17
|
+
>
|
|
18
|
+
<HTML
|
|
19
|
+
center
|
|
20
|
+
class="h-2.5 w-2.5 rounded-full bg-black/70"
|
|
21
|
+
/>
|
|
22
|
+
|
|
23
|
+
<HTML
|
|
24
|
+
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"
|
|
25
|
+
>
|
|
26
|
+
<div class="flex justify-between">
|
|
27
|
+
<span class="text-subtle-2">x</span>
|
|
28
|
+
<div>
|
|
29
|
+
{position[0].toFixed(2)}<span class="text-subtle-2">m</span>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="flex justify-between">
|
|
34
|
+
<span class="text-subtle-2">y</span>
|
|
35
|
+
<div>
|
|
36
|
+
{position[1].toFixed(2)}<span class="text-subtle-2">m</span>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="flex justify-between">
|
|
41
|
+
<span class="text-subtle-2">z</span>
|
|
42
|
+
<div>
|
|
43
|
+
{position[2].toFixed(2)}<span class="text-subtle-2">m</span>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</HTML>
|
|
47
|
+
</T.Group>
|
|
@@ -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 '../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: 'idle' | 'p1' | 'p2' = 'idle'
|
|
20
|
+
|
|
21
|
+
let intersection = $state<Intersection>()
|
|
22
|
+
let p1 = $state.raw<Vector3>()
|
|
23
|
+
let p2 = $state.raw<Vector3>()
|
|
24
|
+
|
|
25
|
+
const enabled = $derived(settings.current.enableMeasure)
|
|
26
|
+
|
|
27
|
+
const { onclick, onmove, raycaster } = useMouseRaycaster(() => ({
|
|
28
|
+
enabled,
|
|
29
|
+
}))
|
|
30
|
+
raycaster.firstHitOnly = true
|
|
31
|
+
raycaster.params.Points.threshold = 0.005
|
|
32
|
+
|
|
33
|
+
onmove((event) => {
|
|
34
|
+
intersection = event.intersections[0]
|
|
35
|
+
|
|
36
|
+
// Only handle axis restrictions if a first point has been placed
|
|
37
|
+
if (!p1) {
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (settings.current.enableMeasureAxisX === false) {
|
|
42
|
+
intersection.point.x = p1.x
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (settings.current.enableMeasureAxisY === false) {
|
|
46
|
+
intersection.point.y = p1.y
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (settings.current.enableMeasureAxisZ === false) {
|
|
50
|
+
intersection.point.z = p1.z
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
onclick(() => {
|
|
55
|
+
if (step === 'idle' && intersection) {
|
|
56
|
+
p1 = intersection.point.clone()
|
|
57
|
+
step = 'p1'
|
|
58
|
+
} else if (step === 'p1' && intersection) {
|
|
59
|
+
p2 = intersection.point.clone()
|
|
60
|
+
step = 'p2'
|
|
61
|
+
} else if (step === 'p2') {
|
|
62
|
+
p1 = undefined
|
|
63
|
+
p2 = undefined
|
|
64
|
+
step = 'idle'
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const clear = () => {
|
|
69
|
+
p1 = undefined
|
|
70
|
+
p2 = undefined
|
|
71
|
+
step = 'idle'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
$effect(() => {
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
76
|
+
;(focusedEntity.current, enabled)
|
|
77
|
+
untrack(() => clear())
|
|
78
|
+
})
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<Portal id="dashboard">
|
|
82
|
+
<fieldset class="relative">
|
|
83
|
+
<div class="flex">
|
|
84
|
+
<Button
|
|
85
|
+
active={enabled}
|
|
86
|
+
icon="ruler"
|
|
87
|
+
description="{enabled ? 'Disable' : 'Enable'} measurement"
|
|
88
|
+
onclick={() => {
|
|
89
|
+
settings.current.enableMeasure = !settings.current.enableMeasure
|
|
90
|
+
}}
|
|
91
|
+
/>
|
|
92
|
+
<Popover>
|
|
93
|
+
{#snippet trigger(triggerProps)}
|
|
94
|
+
<Button
|
|
95
|
+
{...triggerProps}
|
|
96
|
+
active={enabled}
|
|
97
|
+
class="border-l-0"
|
|
98
|
+
icon="filter-sliders"
|
|
99
|
+
description="Measurement settings"
|
|
100
|
+
/>
|
|
101
|
+
{/snippet}
|
|
102
|
+
|
|
103
|
+
<div class="border-medium m-2 border bg-white p-2 text-xs">
|
|
104
|
+
<div class="flex items-center gap-2">
|
|
105
|
+
Enabled axes
|
|
106
|
+
<ToggleGroup
|
|
107
|
+
multiple
|
|
108
|
+
buttons={[
|
|
109
|
+
{ value: 'x', on: settings.current.enableMeasureAxisX },
|
|
110
|
+
{ value: 'y', on: settings.current.enableMeasureAxisY },
|
|
111
|
+
{ value: 'z', on: settings.current.enableMeasureAxisZ },
|
|
112
|
+
]}
|
|
113
|
+
onclick={(details) => {
|
|
114
|
+
settings.current.enableMeasureAxisX = details.includes('x')
|
|
115
|
+
settings.current.enableMeasureAxisY = details.includes('y')
|
|
116
|
+
settings.current.enableMeasureAxisZ = details.includes('z')
|
|
117
|
+
}}
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</Popover>
|
|
122
|
+
</div>
|
|
123
|
+
</fieldset>
|
|
124
|
+
</Portal>
|
|
125
|
+
|
|
126
|
+
{#if enabled}
|
|
127
|
+
{#if intersection && step !== 'p2'}
|
|
128
|
+
<MeasurePoint
|
|
129
|
+
position={intersection?.point.toArray()}
|
|
130
|
+
opacity={0.5}
|
|
131
|
+
/>
|
|
132
|
+
{/if}
|
|
133
|
+
|
|
134
|
+
{#if p1}
|
|
135
|
+
<MeasurePoint
|
|
136
|
+
position={p1.toArray()}
|
|
137
|
+
opacity={0.5}
|
|
138
|
+
/>
|
|
139
|
+
{/if}
|
|
140
|
+
|
|
141
|
+
{#if p2}
|
|
142
|
+
<MeasurePoint
|
|
143
|
+
position={p2.toArray()}
|
|
144
|
+
opacity={0.5}
|
|
145
|
+
/>
|
|
146
|
+
{/if}
|
|
147
|
+
|
|
148
|
+
{#if p1 && (p2 || intersection)}
|
|
149
|
+
<T.Mesh
|
|
150
|
+
raycast={() => null}
|
|
151
|
+
bvh={{ enabled: false }}
|
|
152
|
+
renderOrder={1}
|
|
153
|
+
>
|
|
154
|
+
<MeshLineGeometry points={[p1, p2 ?? intersection?.point ?? new Vector3()]} />
|
|
155
|
+
<MeshLineMaterial
|
|
156
|
+
width={2.5}
|
|
157
|
+
depthTest={false}
|
|
158
|
+
color="black"
|
|
159
|
+
opacity={p2 ? 0.5 : 0.2}
|
|
160
|
+
attenuate={false}
|
|
161
|
+
transparent
|
|
162
|
+
/>
|
|
163
|
+
</T.Mesh>
|
|
164
|
+
|
|
165
|
+
{#if p2}
|
|
166
|
+
<HTML
|
|
167
|
+
center
|
|
168
|
+
position={htmlPosition.lerpVectors(p1, p2, 0.5).toArray()}
|
|
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}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as popover from '@zag-js/popover'
|
|
3
|
+
import { portal, useMachine, normalizeProps } from '@zag-js/svelte'
|
|
4
|
+
import type { Snippet } from 'svelte'
|
|
5
|
+
import type { HTMLButtonAttributes } from 'svelte/elements'
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
trigger: Snippet<[HTMLButtonAttributes]>
|
|
9
|
+
children: Snippet
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let { children, trigger }: Props = $props()
|
|
13
|
+
|
|
14
|
+
const id = $props.id()
|
|
15
|
+
const service = useMachine(popover.machine, { id })
|
|
16
|
+
const api = $derived(popover.connect(service, normalizeProps))
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
{@render trigger(api.getTriggerProps())}
|
|
20
|
+
|
|
21
|
+
<div
|
|
22
|
+
use:portal={{ disabled: !api.portalled }}
|
|
23
|
+
{...api.getPositionerProps()}
|
|
24
|
+
>
|
|
25
|
+
<div {...api.getContentProps()}>
|
|
26
|
+
{@render children()}
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props {
|
|
4
|
+
trigger: Snippet<[HTMLButtonAttributes]>;
|
|
5
|
+
children: Snippet;
|
|
6
|
+
}
|
|
7
|
+
declare const Popover: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type Popover = ReturnType<typeof Popover>;
|
|
9
|
+
export default Popover;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { normalizeProps, useMachine } from '@zag-js/svelte'
|
|
3
|
+
import * as toggle from '@zag-js/toggle-group'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
multiple: boolean
|
|
7
|
+
buttons: {
|
|
8
|
+
on?: boolean
|
|
9
|
+
disabled?: boolean
|
|
10
|
+
label?: string
|
|
11
|
+
value: string
|
|
12
|
+
}[]
|
|
13
|
+
onclick: (details: string[]) => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let { multiple, buttons, onclick }: Props = $props()
|
|
17
|
+
|
|
18
|
+
const id = $props.id()
|
|
19
|
+
const service = useMachine(toggle.machine, () => ({
|
|
20
|
+
id,
|
|
21
|
+
value: buttons.filter((button) => button.on).map((button) => button.value),
|
|
22
|
+
multiple,
|
|
23
|
+
onValueChange(details) {
|
|
24
|
+
onclick(details.value)
|
|
25
|
+
},
|
|
26
|
+
}))
|
|
27
|
+
const api = $derived(toggle.connect(service, normalizeProps))
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<div
|
|
31
|
+
class="flex items-center"
|
|
32
|
+
{...api.getRootProps()}
|
|
33
|
+
>
|
|
34
|
+
{#each buttons as button (button.value)}
|
|
35
|
+
<button
|
|
36
|
+
class="-ml-px flex h-5 w-5 items-center justify-center border text-xs"
|
|
37
|
+
{...api.getItemProps({ value: button.value })}
|
|
38
|
+
>
|
|
39
|
+
{button.label ?? button.value}
|
|
40
|
+
</button>
|
|
41
|
+
{/each}
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<style>
|
|
45
|
+
button[data-state='on'] {
|
|
46
|
+
background: green;
|
|
47
|
+
border-color: black;
|
|
48
|
+
color: white;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
button[data-disabled] {
|
|
52
|
+
opacity: 0.5;
|
|
53
|
+
|
|
54
|
+
filter: grayscale(100%);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
button[data-focus] {
|
|
58
|
+
outline: none;
|
|
59
|
+
}
|
|
60
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
multiple: boolean;
|
|
3
|
+
buttons: {
|
|
4
|
+
on?: boolean;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
label?: string;
|
|
7
|
+
value: string;
|
|
8
|
+
}[];
|
|
9
|
+
onclick: (details: string[]) => void;
|
|
10
|
+
}
|
|
11
|
+
declare const ToggleGroup: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type ToggleGroup = ReturnType<typeof ToggleGroup>;
|
|
13
|
+
export default ToggleGroup;
|
|
@@ -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'
|
|
@@ -193,6 +193,11 @@
|
|
|
193
193
|
<label class="flex items-center justify-between gap-2">
|
|
194
194
|
Render stats <Switch bind:on={settings.current.renderStats} />
|
|
195
195
|
</label>
|
|
196
|
+
<label class="flex items-center justify-between gap-2">
|
|
197
|
+
Render sub-entity hover detail <Switch
|
|
198
|
+
bind:on={settings.current.renderSubEntityHoverDetail}
|
|
199
|
+
/>
|
|
200
|
+
</label>
|
|
196
201
|
<label class="flex items-center justify-between gap-2">
|
|
197
202
|
Render Arm Models
|
|
198
203
|
<Select
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Icon, type IconName, Tooltip } from '@viamrobotics/prime-core'
|
|
3
3
|
import { Ruler } from 'lucide-svelte'
|
|
4
|
-
import type { ClassValue, MouseEventHandler } from 'svelte/elements'
|
|
4
|
+
import type { ClassValue, HTMLButtonAttributes, MouseEventHandler } from 'svelte/elements'
|
|
5
5
|
|
|
6
|
-
interface Props {
|
|
6
|
+
interface Props extends HTMLButtonAttributes {
|
|
7
7
|
icon: IconName | 'ruler'
|
|
8
8
|
active?: boolean
|
|
9
9
|
description: string
|
|
10
10
|
hotkey?: string
|
|
11
11
|
class?: ClassValue | null | undefined
|
|
12
|
+
tooltipLocation?: 'bottom' | 'right'
|
|
12
13
|
onclick?: MouseEventHandler<HTMLButtonElement> | null | undefined
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -18,13 +19,15 @@
|
|
|
18
19
|
description,
|
|
19
20
|
hotkey = '',
|
|
20
21
|
class: className = '',
|
|
22
|
+
tooltipLocation,
|
|
21
23
|
onclick,
|
|
24
|
+
...rest
|
|
22
25
|
}: Props = $props()
|
|
23
26
|
</script>
|
|
24
27
|
|
|
25
28
|
<Tooltip
|
|
26
29
|
let:tooltipID
|
|
27
|
-
location=
|
|
30
|
+
location={tooltipLocation ?? 'bottom'}
|
|
28
31
|
>
|
|
29
32
|
<label
|
|
30
33
|
class={[
|
|
@@ -40,6 +43,7 @@
|
|
|
40
43
|
aria-label={description}
|
|
41
44
|
aria-checked={active}
|
|
42
45
|
{onclick}
|
|
46
|
+
{...rest}
|
|
43
47
|
>
|
|
44
48
|
{#if icon === 'ruler'}
|
|
45
49
|
<Ruler size="16" />
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type IconName } from '@viamrobotics/prime-core';
|
|
2
|
-
import type { ClassValue, MouseEventHandler } from 'svelte/elements';
|
|
3
|
-
interface Props {
|
|
2
|
+
import type { ClassValue, HTMLButtonAttributes, MouseEventHandler } from 'svelte/elements';
|
|
3
|
+
interface Props extends HTMLButtonAttributes {
|
|
4
4
|
icon: IconName | 'ruler';
|
|
5
5
|
active?: boolean;
|
|
6
6
|
description: string;
|
|
7
7
|
hotkey?: string;
|
|
8
8
|
class?: ClassValue | null | undefined;
|
|
9
|
+
tooltipLocation?: 'bottom' | 'right';
|
|
9
10
|
onclick?: MouseEventHandler<HTMLButtonElement> | null | undefined;
|
|
10
11
|
}
|
|
11
12
|
declare const Button: import("svelte").Component<Props, {}, "">;
|
package/dist/ecs/traits.d.ts
CHANGED
|
@@ -30,11 +30,17 @@ export declare const Center: import("koota").Trait<{
|
|
|
30
30
|
oZ: number;
|
|
31
31
|
theta: number;
|
|
32
32
|
}>;
|
|
33
|
+
export declare const Hover: import("koota").Trait<{
|
|
34
|
+
index: number;
|
|
35
|
+
x: number;
|
|
36
|
+
y: number;
|
|
37
|
+
z: number;
|
|
38
|
+
}>;
|
|
33
39
|
/**
|
|
34
40
|
* Represents that an entity is composed of many instances, so that the treeview and
|
|
35
41
|
* details panel may display all instances
|
|
36
42
|
*/
|
|
37
|
-
export declare const Instanced: import("koota").
|
|
43
|
+
export declare const Instanced: import("koota").Trait<() => boolean>;
|
|
38
44
|
export declare const Instance: import("koota").Trait<{
|
|
39
45
|
meshID: number;
|
|
40
46
|
instanceID: number;
|
|
@@ -49,7 +55,7 @@ export declare const Color: import("koota").Trait<{
|
|
|
49
55
|
g: number;
|
|
50
56
|
b: number;
|
|
51
57
|
}>;
|
|
52
|
-
export declare const Arrow: import("koota").
|
|
58
|
+
export declare const Arrow: import("koota").Trait<() => boolean>;
|
|
53
59
|
export declare const Positions: import("koota").Trait<() => Float32Array<ArrayBuffer>>;
|
|
54
60
|
export declare const Colors: import("koota").Trait<() => Uint8Array<ArrayBuffer>>;
|
|
55
61
|
export declare const Instances: import("koota").Trait<{
|
|
@@ -61,7 +67,7 @@ export declare const Arrows: import("koota").Trait<{
|
|
|
61
67
|
/**
|
|
62
68
|
* Render entity as points
|
|
63
69
|
*/
|
|
64
|
-
export declare const Points: import("koota").
|
|
70
|
+
export declare const Points: import("koota").Trait<() => boolean>;
|
|
65
71
|
/**
|
|
66
72
|
* A box, in mm
|
|
67
73
|
*/
|
|
@@ -108,15 +114,15 @@ export declare const Scale: import("koota").Trait<{
|
|
|
108
114
|
y: number;
|
|
109
115
|
z: number;
|
|
110
116
|
}>;
|
|
111
|
-
export declare const FramesAPI: import("koota").
|
|
112
|
-
export declare const GeometriesAPI: import("koota").
|
|
113
|
-
export declare const DrawAPI: import("koota").
|
|
114
|
-
export declare const WorldStateStoreAPI: import("koota").
|
|
115
|
-
export declare const SnapshotAPI: import("koota").
|
|
117
|
+
export declare const FramesAPI: import("koota").Trait<() => boolean>;
|
|
118
|
+
export declare const GeometriesAPI: import("koota").Trait<() => boolean>;
|
|
119
|
+
export declare const DrawAPI: import("koota").Trait<() => boolean>;
|
|
120
|
+
export declare const WorldStateStoreAPI: import("koota").Trait<() => boolean>;
|
|
121
|
+
export declare const SnapshotAPI: import("koota").Trait<() => boolean>;
|
|
116
122
|
/**
|
|
117
123
|
* Marker trait for entities created from user-dropped files (PLY, PCD, etc.)
|
|
118
124
|
*/
|
|
119
|
-
export declare const DroppedFile: import("koota").
|
|
125
|
+
export declare const DroppedFile: import("koota").Trait<() => boolean>;
|
|
120
126
|
/**
|
|
121
127
|
* Point size, in mm
|
|
122
128
|
*/
|
|
@@ -125,12 +131,12 @@ export declare const PointSize: import("koota").Trait<() => number>;
|
|
|
125
131
|
* Line width, in mm
|
|
126
132
|
*/
|
|
127
133
|
export declare const LineWidth: import("koota").Trait<() => number>;
|
|
128
|
-
export declare const ReferenceFrame: import("koota").
|
|
134
|
+
export declare const ReferenceFrame: import("koota").Trait<() => boolean>;
|
|
129
135
|
/**
|
|
130
136
|
* This entity can be safetly removed from the scene by the user
|
|
131
137
|
*/
|
|
132
|
-
export declare const Removable: import("koota").
|
|
133
|
-
export declare const Geometry: (geometry: ViamGeometry) => import("koota").
|
|
138
|
+
export declare const Removable: import("koota").Trait<() => boolean>;
|
|
139
|
+
export declare const Geometry: (geometry: ViamGeometry) => import("koota").Trait<() => boolean> | [import("koota").Trait<{
|
|
134
140
|
x: number;
|
|
135
141
|
y: number;
|
|
136
142
|
z: number;
|
package/dist/ecs/traits.js
CHANGED
|
@@ -8,11 +8,17 @@ export const Parent = trait(() => 'world');
|
|
|
8
8
|
export const Pose = trait({ x: 0, y: 0, z: 0, oX: 0, oY: 0, oZ: 1, theta: 0 });
|
|
9
9
|
export const EditedPose = trait({ x: 0, y: 0, z: 0, oX: 0, oY: 0, oZ: 1, theta: 0 });
|
|
10
10
|
export const Center = trait({ x: 0, y: 0, z: 0, oX: 0, oY: 0, oZ: 1, theta: 0 });
|
|
11
|
+
export const Hover = trait({
|
|
12
|
+
index: -1, // Sub-entity index, -1 if not applicable
|
|
13
|
+
x: 0, // World position X in meters
|
|
14
|
+
y: 0, // World position Y in meters
|
|
15
|
+
z: 0, // World position Z in meters
|
|
16
|
+
});
|
|
11
17
|
/**
|
|
12
18
|
* Represents that an entity is composed of many instances, so that the treeview and
|
|
13
19
|
* details panel may display all instances
|
|
14
20
|
*/
|
|
15
|
-
export const Instanced = trait();
|
|
21
|
+
export const Instanced = trait(() => true);
|
|
16
22
|
export const Instance = trait({
|
|
17
23
|
meshID: -1,
|
|
18
24
|
instanceID: -1,
|
|
@@ -23,7 +29,7 @@ export const Opacity = trait(() => 1);
|
|
|
23
29
|
* @default { r: 1, g: 0, b: 0 }
|
|
24
30
|
*/
|
|
25
31
|
export const Color = trait({ r: 0, g: 0, b: 0 });
|
|
26
|
-
export const Arrow = trait();
|
|
32
|
+
export const Arrow = trait(() => true);
|
|
27
33
|
export const Positions = trait(() => new Float32Array());
|
|
28
34
|
export const Colors = trait(() => new Uint8Array());
|
|
29
35
|
export const Instances = trait({
|
|
@@ -35,7 +41,7 @@ export const Arrows = trait({
|
|
|
35
41
|
/**
|
|
36
42
|
* Render entity as points
|
|
37
43
|
*/
|
|
38
|
-
export const Points = trait();
|
|
44
|
+
export const Points = trait(() => true);
|
|
39
45
|
/**
|
|
40
46
|
* A box, in mm
|
|
41
47
|
*/
|
|
@@ -59,15 +65,15 @@ export const GLTF = trait(() => ({
|
|
|
59
65
|
animationName: '',
|
|
60
66
|
}));
|
|
61
67
|
export const Scale = trait({ x: 1, y: 1, z: 1 });
|
|
62
|
-
export const FramesAPI = trait();
|
|
63
|
-
export const GeometriesAPI = trait();
|
|
64
|
-
export const DrawAPI = trait();
|
|
65
|
-
export const WorldStateStoreAPI = trait();
|
|
66
|
-
export const SnapshotAPI = trait();
|
|
68
|
+
export const FramesAPI = trait(() => true);
|
|
69
|
+
export const GeometriesAPI = trait(() => true);
|
|
70
|
+
export const DrawAPI = trait(() => true);
|
|
71
|
+
export const WorldStateStoreAPI = trait(() => true);
|
|
72
|
+
export const SnapshotAPI = trait(() => true);
|
|
67
73
|
/**
|
|
68
74
|
* Marker trait for entities created from user-dropped files (PLY, PCD, etc.)
|
|
69
75
|
*/
|
|
70
|
-
export const DroppedFile = trait();
|
|
76
|
+
export const DroppedFile = trait(() => true);
|
|
71
77
|
// === Shape Properties ===
|
|
72
78
|
/**
|
|
73
79
|
* Point size, in mm
|
|
@@ -77,11 +83,11 @@ export const PointSize = trait(() => 10);
|
|
|
77
83
|
* Line width, in mm
|
|
78
84
|
*/
|
|
79
85
|
export const LineWidth = trait(() => 5);
|
|
80
|
-
export const ReferenceFrame = trait();
|
|
86
|
+
export const ReferenceFrame = trait(() => true);
|
|
81
87
|
/**
|
|
82
88
|
* This entity can be safetly removed from the scene by the user
|
|
83
89
|
*/
|
|
84
|
-
export const Removable = trait();
|
|
90
|
+
export const Removable = trait(() => true);
|
|
85
91
|
export const Geometry = (geometry) => {
|
|
86
92
|
if (geometry.geometryType.case === 'box') {
|
|
87
93
|
return Box(createBox(geometry.geometryType.value));
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
import { untrack } from 'svelte';
|
|
2
|
-
import { $internal as internal,
|
|
2
|
+
import { $internal as internal, createQuery } from 'koota';
|
|
3
3
|
import { useWorld } from './useWorld';
|
|
4
4
|
export function useQuery(...parameters) {
|
|
5
5
|
const world = useWorld();
|
|
6
|
-
const
|
|
6
|
+
const createdQuery = createQuery(...parameters);
|
|
7
7
|
// Using internals to get the query data.
|
|
8
|
-
const query = world[internal].queriesHashMap.get(hash);
|
|
8
|
+
const query = world[internal].queriesHashMap.get(createdQuery.hash);
|
|
9
9
|
const initialQueryVersion = query?.version;
|
|
10
10
|
let version = $state.raw(0);
|
|
11
|
-
let entities = $state.raw(world.query(
|
|
11
|
+
let entities = $state.raw(world.query(createdQuery));
|
|
12
12
|
$effect(() => {
|
|
13
13
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
14
14
|
version;
|
|
15
15
|
// Compare the initial version to the current version to
|
|
16
16
|
// see it the query has changed.
|
|
17
|
-
const query = world[internal].queriesHashMap.get(hash);
|
|
17
|
+
const query = world[internal].queriesHashMap.get(createdQuery.hash);
|
|
18
18
|
if (query?.version !== initialQueryVersion) {
|
|
19
|
-
entities = world.query(
|
|
19
|
+
entities = world.query(createdQuery);
|
|
20
20
|
}
|
|
21
21
|
return untrack(() => {
|
|
22
|
-
const unsubAdd = world.onQueryAdd(
|
|
23
|
-
entities = world.query(
|
|
22
|
+
const unsubAdd = world.onQueryAdd(createdQuery, () => {
|
|
23
|
+
entities = world.query(createdQuery);
|
|
24
24
|
});
|
|
25
|
-
const unsubRemove = world.onQueryRemove(
|
|
26
|
-
entities = world.query(
|
|
25
|
+
const unsubRemove = world.onQueryRemove(createdQuery, () => {
|
|
26
|
+
entities = world.query(createdQuery);
|
|
27
27
|
});
|
|
28
28
|
return () => {
|
|
29
29
|
unsubAdd();
|
|
@@ -3,6 +3,7 @@ import type { Entity } from 'koota';
|
|
|
3
3
|
export declare const useObjectEvents: (entity: () => Entity | undefined) => {
|
|
4
4
|
readonly visible: boolean;
|
|
5
5
|
onpointerenter: (event: IntersectionEvent<MouseEvent>) => void;
|
|
6
|
+
onpointermove: (event: IntersectionEvent<MouseEvent>) => void;
|
|
6
7
|
onpointerleave: (event: IntersectionEvent<MouseEvent>) => void;
|
|
7
8
|
ondblclick: (event: IntersectionEvent<MouseEvent>) => void;
|
|
8
9
|
onpointerdown: (event: IntersectionEvent<MouseEvent>) => void;
|
|
@@ -2,6 +2,7 @@ import { useCursor } from '@threlte/extras';
|
|
|
2
2
|
import { useFocusedEntity, useSelectedEntity } from './useSelection.svelte';
|
|
3
3
|
import { useVisibility } from './useVisibility.svelte';
|
|
4
4
|
import { Vector2 } from 'three';
|
|
5
|
+
import { traits } from '../ecs';
|
|
5
6
|
export const useObjectEvents = (entity) => {
|
|
6
7
|
const down = new Vector2();
|
|
7
8
|
const selectedEntity = useSelectedEntity();
|
|
@@ -13,10 +14,32 @@ export const useObjectEvents = (entity) => {
|
|
|
13
14
|
const onpointerenter = (event) => {
|
|
14
15
|
event.stopPropagation();
|
|
15
16
|
cursor.onPointerEnter();
|
|
17
|
+
if (currentEntity && !currentEntity.has(traits.Hover)) {
|
|
18
|
+
currentEntity.add(traits.Hover({
|
|
19
|
+
index: -1,
|
|
20
|
+
x: event.point.x,
|
|
21
|
+
y: event.point.y,
|
|
22
|
+
z: event.point.z,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const onpointermove = (event) => {
|
|
27
|
+
event.stopPropagation();
|
|
28
|
+
if (currentEntity && currentEntity.has(traits.Hover)) {
|
|
29
|
+
currentEntity.set(traits.Hover, {
|
|
30
|
+
index: event.index ?? -1,
|
|
31
|
+
x: event.point.x,
|
|
32
|
+
y: event.point.y,
|
|
33
|
+
z: event.point.z,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
16
36
|
};
|
|
17
37
|
const onpointerleave = (event) => {
|
|
18
38
|
event.stopPropagation();
|
|
19
39
|
cursor.onPointerLeave();
|
|
40
|
+
if (currentEntity?.has(traits.Hover)) {
|
|
41
|
+
currentEntity.remove(traits.Hover);
|
|
42
|
+
}
|
|
20
43
|
};
|
|
21
44
|
const ondblclick = (event) => {
|
|
22
45
|
event.stopPropagation();
|
|
@@ -41,6 +64,7 @@ export const useObjectEvents = (entity) => {
|
|
|
41
64
|
return visible;
|
|
42
65
|
},
|
|
43
66
|
onpointerenter,
|
|
67
|
+
onpointermove,
|
|
44
68
|
onpointerleave,
|
|
45
69
|
ondblclick,
|
|
46
70
|
onpointerdown,
|
|
@@ -13,6 +13,9 @@ export interface Settings {
|
|
|
13
13
|
lineWidth: number;
|
|
14
14
|
lineDotSize: number;
|
|
15
15
|
enableMeasure: boolean;
|
|
16
|
+
enableMeasureAxisX: boolean;
|
|
17
|
+
enableMeasureAxisY: boolean;
|
|
18
|
+
enableMeasureAxisZ: boolean;
|
|
16
19
|
enableLabels: boolean;
|
|
17
20
|
enableKeybindings: boolean;
|
|
18
21
|
enableQueryDevtools: boolean;
|
|
@@ -21,6 +24,7 @@ export interface Settings {
|
|
|
21
24
|
openCameraWidgets: Record<string, string[]>;
|
|
22
25
|
renderStats: boolean;
|
|
23
26
|
renderArmModels: 'colliders' | 'colliders+model' | 'model';
|
|
27
|
+
renderSubEntityHoverDetail: boolean;
|
|
24
28
|
}
|
|
25
29
|
interface Context {
|
|
26
30
|
current: Settings;
|
|
@@ -16,6 +16,9 @@ const defaults = () => ({
|
|
|
16
16
|
lineWidth: 0.005,
|
|
17
17
|
lineDotSize: 0.01,
|
|
18
18
|
enableMeasure: false,
|
|
19
|
+
enableMeasureAxisX: true,
|
|
20
|
+
enableMeasureAxisY: true,
|
|
21
|
+
enableMeasureAxisZ: true,
|
|
19
22
|
enableLabels: false,
|
|
20
23
|
enableKeybindings: true,
|
|
21
24
|
enableQueryDevtools: false,
|
|
@@ -24,6 +27,7 @@ const defaults = () => ({
|
|
|
24
27
|
openCameraWidgets: {},
|
|
25
28
|
renderStats: false,
|
|
26
29
|
renderArmModels: 'colliders+model',
|
|
30
|
+
renderSubEntityHoverDetail: false,
|
|
27
31
|
});
|
|
28
32
|
export const provideSettings = () => {
|
|
29
33
|
let settings = $state(defaults());
|
|
@@ -2,7 +2,6 @@ import { Object3D, BufferGeometry, Ray, Matrix4, Raycaster, Vector3, Box3, RawSh
|
|
|
2
2
|
const vec3 = new Vector3();
|
|
3
3
|
const inverseMatrix = new Matrix4();
|
|
4
4
|
const localRay = new Ray();
|
|
5
|
-
const ray = new Ray();
|
|
6
5
|
const box = new Box3();
|
|
7
6
|
const segmentStart = new Vector3();
|
|
8
7
|
const segmentEnd = new Vector3();
|
|
@@ -46,17 +45,14 @@ export function meshBoundsRaycast(raycaster, intersects) {
|
|
|
46
45
|
this.geometry.computeBoundingBox();
|
|
47
46
|
}
|
|
48
47
|
box.copy(this.geometry.boundingBox ?? box);
|
|
49
|
-
box.applyMatrix4(this.matrixWorld);
|
|
50
48
|
if (!raycaster.ray.intersectsBox(box)) {
|
|
51
49
|
return;
|
|
52
50
|
}
|
|
53
|
-
|
|
54
|
-
ray.copy(raycaster.ray).applyMatrix4(inverseMatrix);
|
|
51
|
+
raycaster.ray.intersectBox(box, vec3);
|
|
55
52
|
const distance = vec3.distanceTo(raycaster.ray.origin);
|
|
56
|
-
const point = vec3.clone();
|
|
57
53
|
intersects.push({
|
|
58
54
|
distance,
|
|
59
|
-
point,
|
|
55
|
+
point: vec3.clone(),
|
|
60
56
|
object: this,
|
|
61
57
|
});
|
|
62
58
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viamrobotics/motion-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Motion visualization with Viam",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -41,7 +41,9 @@
|
|
|
41
41
|
"@vitest/coverage-v8": "^3.2.4",
|
|
42
42
|
"@zag-js/collapsible": "1.22.1",
|
|
43
43
|
"@zag-js/floating-panel": "1.22.1",
|
|
44
|
+
"@zag-js/popover": "1.22.1",
|
|
44
45
|
"@zag-js/svelte": "1.22.1",
|
|
46
|
+
"@zag-js/toggle-group": "1.22.1",
|
|
45
47
|
"@zag-js/tree-view": "1.22.1",
|
|
46
48
|
"camera-controls": "3.1.0",
|
|
47
49
|
"eslint": "9.34.0",
|
|
@@ -87,7 +89,9 @@
|
|
|
87
89
|
"@zag-js/collapsible": ">=1",
|
|
88
90
|
"@zag-js/dialog": ">=1.31",
|
|
89
91
|
"@zag-js/floating-panel": ">=1",
|
|
92
|
+
"@zag-js/popover": ">=1",
|
|
90
93
|
"@zag-js/svelte": ">=1",
|
|
94
|
+
"@zag-js/toggle-group": ">=1",
|
|
91
95
|
"@zag-js/tree-view": ">=1",
|
|
92
96
|
"camera-controls": ">=3",
|
|
93
97
|
"idb-keyval": ">=6",
|
|
@@ -128,7 +132,7 @@
|
|
|
128
132
|
"@bufbuild/protobuf": "1.10.1",
|
|
129
133
|
"@neodrag/svelte": "^2.3.3",
|
|
130
134
|
"@tanstack/svelte-query-devtools": "^6.0.2",
|
|
131
|
-
"koota": "
|
|
135
|
+
"koota": "0.6.5",
|
|
132
136
|
"lodash-es": "4.17.23",
|
|
133
137
|
"uuid-tool": "^2.0.3"
|
|
134
138
|
},
|