@viamrobotics/motion-tools 1.9.1 → 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.
- package/dist/HoverUpdater.svelte.d.ts +19 -0
- package/dist/HoverUpdater.svelte.js +120 -0
- package/dist/components/App.svelte +28 -29
- package/dist/components/MeasureTool/MeasurePoint.svelte +3 -3
- package/dist/components/MeasureTool/MeasureTool.svelte +6 -6
- package/dist/components/SceneProviders.svelte +2 -0
- package/dist/components/hover/HoveredEntities.svelte +23 -0
- package/dist/components/hover/HoveredEntity.svelte +15 -0
- package/dist/components/hover/HoveredEntity.svelte.d.ts +3 -0
- package/dist/components/hover/HoveredEntityTooltip.svelte +70 -0
- package/dist/components/{HoveredEntityTooltip.svelte.d.ts → hover/HoveredEntityTooltip.svelte.d.ts} +2 -2
- package/dist/components/hover/LinkedHoveredEntity.svelte +55 -0
- package/dist/components/hover/LinkedHoveredEntity.svelte.d.ts +9 -0
- package/dist/components/overlay/AddRelationship.svelte +131 -0
- package/dist/components/overlay/AddRelationship.svelte.d.ts +7 -0
- package/dist/components/overlay/Details.svelte +35 -2
- package/dist/components/overlay/FloatingPanel.svelte +78 -0
- package/dist/components/overlay/FloatingPanel.svelte.d.ts +13 -0
- package/dist/components/overlay/{left-pane/RefreshRate.svelte → RefreshRate.svelte} +1 -1
- package/dist/components/overlay/ToggleGroup.svelte +22 -26
- package/dist/components/overlay/ToggleGroup.svelte.d.ts +6 -7
- package/dist/components/overlay/left-pane/TreeContainer.svelte +0 -2
- package/dist/components/overlay/settings/Settings.svelte +279 -0
- package/dist/components/overlay/settings/Tabs.svelte +54 -0
- package/dist/components/overlay/settings/Tabs.svelte.d.ts +12 -0
- package/dist/ecs/index.d.ts +1 -0
- package/dist/ecs/index.js +1 -0
- package/dist/ecs/relations.d.ts +7 -0
- package/dist/ecs/relations.js +7 -0
- package/dist/ecs/traits.d.ts +7 -2
- package/dist/ecs/traits.js +10 -5
- package/dist/hooks/useGeometries.svelte.js +1 -1
- package/dist/hooks/useLinked.svelte.d.ts +7 -0
- package/dist/hooks/useLinked.svelte.js +35 -0
- package/dist/hooks/useObjectEvents.svelte.js +35 -16
- package/dist/hooks/usePointcloudObjects.svelte.js +1 -1
- package/dist/hooks/usePointclouds.svelte.js +3 -3
- package/dist/hooks/usePose.svelte.js +1 -1
- package/package.json +4 -1
- package/dist/components/HoveredEntities.svelte +0 -19
- package/dist/components/HoveredEntityTooltip.svelte +0 -242
- package/dist/components/overlay/left-pane/Settings.svelte +0 -221
- /package/dist/components/{HoveredEntities.svelte.d.ts → hover/HoveredEntities.svelte.d.ts} +0 -0
- /package/dist/components/overlay/{left-pane/RefreshRate.svelte.d.ts → RefreshRate.svelte.d.ts} +0 -0
- /package/dist/components/overlay/{left-pane → settings}/Settings.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
|
+
};
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
type DrawConnectionConfig,
|
|
28
28
|
} from '../hooks/useDrawConnectionConfig.svelte'
|
|
29
29
|
import Camera from './overlay/widgets/Camera.svelte'
|
|
30
|
-
import HoveredEntities from './HoveredEntities.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
|
|
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
|
-
|
|
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>
|
|
@@ -28,21 +28,21 @@
|
|
|
28
28
|
<div class="flex justify-between">
|
|
29
29
|
<span class="text-subtle-2">x</span>
|
|
30
30
|
<div>
|
|
31
|
-
{position[0].toFixed(
|
|
31
|
+
{position[0].toFixed(3)}<span class="text-subtle-2">m</span>
|
|
32
32
|
</div>
|
|
33
33
|
</div>
|
|
34
34
|
|
|
35
35
|
<div class="flex justify-between">
|
|
36
36
|
<span class="text-subtle-2">y</span>
|
|
37
37
|
<div>
|
|
38
|
-
{position[1].toFixed(
|
|
38
|
+
{position[1].toFixed(3)}<span class="text-subtle-2">m</span>
|
|
39
39
|
</div>
|
|
40
40
|
</div>
|
|
41
41
|
|
|
42
42
|
<div class="flex justify-between">
|
|
43
43
|
<span class="text-subtle-2">z</span>
|
|
44
44
|
<div>
|
|
45
|
-
{position[2].toFixed(
|
|
45
|
+
{position[2].toFixed(3)}<span class="text-subtle-2">m</span>
|
|
46
46
|
</div>
|
|
47
47
|
</div>
|
|
48
48
|
</HTML>
|
|
@@ -104,12 +104,12 @@
|
|
|
104
104
|
Enabled axes
|
|
105
105
|
<ToggleGroup
|
|
106
106
|
multiple
|
|
107
|
-
|
|
108
|
-
{
|
|
109
|
-
{
|
|
110
|
-
{
|
|
107
|
+
options={[
|
|
108
|
+
{ label: 'x', selected: settings.current.enableMeasureAxisX },
|
|
109
|
+
{ label: 'y', selected: settings.current.enableMeasureAxisY },
|
|
110
|
+
{ label: 'z', selected: settings.current.enableMeasureAxisZ },
|
|
111
111
|
]}
|
|
112
|
-
|
|
112
|
+
onSelect={(details) => {
|
|
113
113
|
settings.current.enableMeasureAxisX = details.includes('x')
|
|
114
114
|
settings.current.enableMeasureAxisY = details.includes('y')
|
|
115
115
|
settings.current.enableMeasureAxisZ = details.includes('z')
|
|
@@ -168,7 +168,7 @@
|
|
|
168
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(
|
|
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,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}
|
package/dist/components/{HoveredEntityTooltip.svelte.d.ts → hover/HoveredEntityTooltip.svelte.d.ts}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type HoverInfo } from '../../HoverUpdater.svelte';
|
|
2
2
|
interface Props {
|
|
3
|
-
|
|
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;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Entity } from 'koota'
|
|
3
|
+
import { Button, Select, Input } from '@viamrobotics/prime-core'
|
|
4
|
+
import { traits, useQuery, relations, useTrait } from '../../ecs'
|
|
5
|
+
import { SubEntityLinkType } from '../../ecs/relations'
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
entity: Entity | undefined
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { entity }: Props = $props()
|
|
12
|
+
|
|
13
|
+
const allEntities = useQuery(traits.Name)
|
|
14
|
+
const name = useTrait(() => entity, traits.Name)
|
|
15
|
+
const entityNames = $derived.by(() => {
|
|
16
|
+
const currentEntityName = name.current
|
|
17
|
+
return allEntities.current
|
|
18
|
+
.map((e: Entity) => e.get(traits.Name))
|
|
19
|
+
.filter((n: string | undefined): n is string => n !== undefined && n !== currentEntityName)
|
|
20
|
+
.sort()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
let showRelationshipOptions = $state(false)
|
|
24
|
+
let selectedRelationshipType = $state<string>('')
|
|
25
|
+
let selectedRelationshipEntity = $state<string>('')
|
|
26
|
+
let relationshipFormula = $state('index')
|
|
27
|
+
|
|
28
|
+
const linkType = $derived.by(() => {
|
|
29
|
+
return selectedRelationshipType === SubEntityLinkType.HoverLink
|
|
30
|
+
? SubEntityLinkType.HoverLink
|
|
31
|
+
: null
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
function resetForm() {
|
|
35
|
+
selectedRelationshipType = ''
|
|
36
|
+
selectedRelationshipEntity = ''
|
|
37
|
+
relationshipFormula = 'index'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function handleAdd() {
|
|
41
|
+
if (!entity || !relationshipFormula.includes('index')) return
|
|
42
|
+
const selectedEntity = allEntities.current.find(
|
|
43
|
+
(e: Entity) => e.get(traits.Name) === selectedRelationshipEntity
|
|
44
|
+
)
|
|
45
|
+
if (selectedEntity) {
|
|
46
|
+
entity.add(
|
|
47
|
+
relations.SubEntityLink(selectedEntity, {
|
|
48
|
+
indexMapping: relationshipFormula,
|
|
49
|
+
type: linkType,
|
|
50
|
+
})
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
showRelationshipOptions = false
|
|
54
|
+
resetForm()
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<Button
|
|
59
|
+
class="mt-2 w-full"
|
|
60
|
+
icon={showRelationshipOptions ? undefined : 'plus'}
|
|
61
|
+
variant={showRelationshipOptions ? 'dark' : 'primary'}
|
|
62
|
+
onclick={() => {
|
|
63
|
+
if (showRelationshipOptions) {
|
|
64
|
+
resetForm()
|
|
65
|
+
}
|
|
66
|
+
showRelationshipOptions = !showRelationshipOptions
|
|
67
|
+
}}>{showRelationshipOptions ? 'Cancel' : 'Add Relationship'}</Button
|
|
68
|
+
>
|
|
69
|
+
|
|
70
|
+
{#if showRelationshipOptions}
|
|
71
|
+
<div class="mt-2 flex flex-col gap-2">
|
|
72
|
+
<div>
|
|
73
|
+
<label
|
|
74
|
+
for="relationship-type-select"
|
|
75
|
+
class="text-subtle-2 mb-1 block text-xs">Relationship type</label
|
|
76
|
+
>
|
|
77
|
+
<Select
|
|
78
|
+
id="relationship-type-select"
|
|
79
|
+
aria-label="Select relationship type"
|
|
80
|
+
value={selectedRelationshipType}
|
|
81
|
+
onchange={(event: InputEvent) => {
|
|
82
|
+
selectedRelationshipType = (event.target as HTMLSelectElement).value as 'HoverLink'
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
<option value="">Select a relationship type...</option>
|
|
86
|
+
<option value="HoverLink">HoverLink</option>
|
|
87
|
+
</Select>
|
|
88
|
+
</div>
|
|
89
|
+
<div>
|
|
90
|
+
<label
|
|
91
|
+
for="relationship-entity-select"
|
|
92
|
+
class="text-subtle-2 mb-1 block text-xs">Entity</label
|
|
93
|
+
>
|
|
94
|
+
<Select
|
|
95
|
+
id="relationship-entity-select"
|
|
96
|
+
aria-label="Select entity for relationship"
|
|
97
|
+
value={selectedRelationshipEntity}
|
|
98
|
+
onchange={(event: InputEvent) => {
|
|
99
|
+
selectedRelationshipEntity = (event.target as HTMLSelectElement).value
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
<option value="">Select an entity...</option>
|
|
103
|
+
{#each entityNames as entityName (entityName)}
|
|
104
|
+
<option value={entityName}>{entityName}</option>
|
|
105
|
+
{/each}
|
|
106
|
+
</Select>
|
|
107
|
+
</div>
|
|
108
|
+
<div>
|
|
109
|
+
<label
|
|
110
|
+
for="relationship-formula-input"
|
|
111
|
+
class="text-subtle-2 mb-1 block text-xs">Index mapping</label
|
|
112
|
+
>
|
|
113
|
+
<Input
|
|
114
|
+
on:keydown={(e) => e.stopPropagation()}
|
|
115
|
+
id="relationship-formula-input"
|
|
116
|
+
aria-label="Math formula for index mapping"
|
|
117
|
+
bind:value={relationshipFormula}
|
|
118
|
+
placeholder="index"
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
<div>
|
|
122
|
+
<Button
|
|
123
|
+
class="w-full"
|
|
124
|
+
variant="primary"
|
|
125
|
+
onclick={handleAdd}
|
|
126
|
+
>
|
|
127
|
+
Add
|
|
128
|
+
</Button>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
{/if}
|