@viamrobotics/motion-tools 1.15.4 → 1.15.6
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/Entities/Arrows/Arrows.svelte +6 -3
- package/dist/components/Entities/Frame.svelte +5 -1
- package/dist/components/Entities/GLTF.svelte +2 -0
- package/dist/components/Entities/Geometry.svelte +20 -17
- package/dist/components/Entities/Line.svelte +2 -0
- package/dist/components/Entities/Points.svelte +2 -0
- package/dist/components/Entities/hooks/useEntityEvents.svelte.d.ts +0 -1
- package/dist/components/Entities/hooks/useEntityEvents.svelte.js +3 -8
- package/dist/components/KeyboardControls.svelte +6 -4
- package/dist/components/SceneProviders.svelte +0 -2
- package/dist/components/overlay/FloatingPanel.svelte +1 -1
- package/dist/components/overlay/Logs.svelte +95 -28
- package/dist/components/overlay/left-pane/Tree.svelte +17 -147
- package/dist/components/overlay/left-pane/Tree.svelte.d.ts +3 -3
- package/dist/components/overlay/left-pane/TreeNode.svelte +169 -0
- package/dist/components/overlay/left-pane/TreeNode.svelte.d.ts +10 -0
- package/dist/components/xr/OriginMarker.svelte +4 -20
- package/dist/components/xr/XRConfigPanel.svelte +1 -7
- package/dist/components/xr/useAnchors.svelte.d.ts +8 -1
- package/dist/components/xr/useAnchors.svelte.js +27 -27
- package/dist/ecs/traits.d.ts +1 -0
- package/dist/ecs/traits.js +1 -0
- package/dist/hooks/useFramelessComponents.svelte.js +3 -3
- package/dist/hooks/useFrames.svelte.js +19 -4
- package/dist/hooks/useGeometries.svelte.js +11 -1
- package/dist/hooks/usePointcloudObjects.svelte.js +8 -0
- package/dist/hooks/usePointclouds.svelte.js +8 -0
- package/package.json +3 -3
- package/dist/hooks/useVisibility.svelte.d.ts +0 -6
- package/dist/hooks/useVisibility.svelte.js +0 -10
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import type { InstancedArrows } from '../../../three/InstancedArrows/InstancedArrows'
|
|
8
8
|
|
|
9
9
|
import { useEntityEvents } from '../hooks/useEntityEvents.svelte'
|
|
10
|
-
import { traits } from '../../../ecs'
|
|
10
|
+
import { traits, useTrait } from '../../../ecs'
|
|
11
11
|
import { useFocusedEntity, useSelectedEntity } from '../../../hooks/useSelection.svelte'
|
|
12
12
|
import { meshBoundsRaycast, raycast } from '../../../three/InstancedArrows/raycast'
|
|
13
13
|
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
|
|
19
19
|
let { entity, arrows }: Props = $props()
|
|
20
20
|
|
|
21
|
+
const parent = useTrait(() => entity, traits.Parent)
|
|
22
|
+
const invisible = useTrait(() => entity, traits.Invisible)
|
|
23
|
+
|
|
21
24
|
const events = useEntityEvents(() => entity)
|
|
22
25
|
const selectedEntity = useSelectedEntity()
|
|
23
26
|
const focusedEntity = useFocusedEntity()
|
|
@@ -32,7 +35,7 @@
|
|
|
32
35
|
})
|
|
33
36
|
</script>
|
|
34
37
|
|
|
35
|
-
<Portal id={
|
|
38
|
+
<Portal id={parent.current}>
|
|
36
39
|
<T
|
|
37
40
|
is={arrows}
|
|
38
41
|
name={entity}
|
|
@@ -43,7 +46,7 @@
|
|
|
43
46
|
is={arrows.headMesh}
|
|
44
47
|
bvh={{ enabled: false }}
|
|
45
48
|
raycast={() => null}
|
|
46
|
-
visible={
|
|
49
|
+
visible={invisible.current}
|
|
47
50
|
/>
|
|
48
51
|
<T
|
|
49
52
|
is={arrows.shaftMesh}
|
|
@@ -42,6 +42,7 @@ Renders a Viam Frame object
|
|
|
42
42
|
const entityColor = useTrait(() => entity, traits.Color)
|
|
43
43
|
const entityPose = useTrait(() => entity, traits.Pose)
|
|
44
44
|
const center = useTrait(() => entity, traits.Center)
|
|
45
|
+
const invisible = useTrait(() => entity, traits.Invisible)
|
|
45
46
|
|
|
46
47
|
const events = useEntityEvents(() => entity)
|
|
47
48
|
|
|
@@ -72,7 +73,10 @@ Renders a Viam Frame object
|
|
|
72
73
|
</script>
|
|
73
74
|
|
|
74
75
|
<Portal id={parent.current}>
|
|
75
|
-
<T
|
|
76
|
+
<T
|
|
77
|
+
is={group}
|
|
78
|
+
visible={invisible.current !== true}
|
|
79
|
+
>
|
|
76
80
|
<Mesh
|
|
77
81
|
{entity}
|
|
78
82
|
{color}
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
const pose = useTrait(() => entity, traits.Pose)
|
|
39
39
|
const gltfTrait = useTrait(() => entity, traits.GLTF)
|
|
40
40
|
const scale = useTrait(() => entity, traits.Scale)
|
|
41
|
+
const invisible = useTrait(() => entity, traits.Invisible)
|
|
41
42
|
const events = useEntityEvents(() => entity)
|
|
42
43
|
|
|
43
44
|
const animationName = $derived(gltfTrait.current?.animationName)
|
|
@@ -88,6 +89,7 @@
|
|
|
88
89
|
is={$gltf.scene as Object3D}
|
|
89
90
|
scale={[scale.current?.x ?? 1, scale.current?.y ?? 1, scale.current?.z ?? 1]}
|
|
90
91
|
name={entity}
|
|
92
|
+
visible={invisible.current !== true}
|
|
91
93
|
{...events}
|
|
92
94
|
{...rest}
|
|
93
95
|
>
|
|
@@ -33,6 +33,7 @@ Renders a Viam Geometry object
|
|
|
33
33
|
const name = useTrait(() => entity, traits.Name)
|
|
34
34
|
const parent = useTrait(() => entity, traits.Parent)
|
|
35
35
|
const center = useTrait(() => entity, traits.Center)
|
|
36
|
+
const invisible = useTrait(() => entity, traits.Invisible)
|
|
36
37
|
|
|
37
38
|
const model = $derived.by(() => {
|
|
38
39
|
if (!settings.current.renderArmModels.includes('model')) {
|
|
@@ -62,21 +63,23 @@ Renders a Viam Geometry object
|
|
|
62
63
|
</script>
|
|
63
64
|
|
|
64
65
|
<Portal id={parent.current}>
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
66
|
+
<T.Group visible={invisible.current !== true}>
|
|
67
|
+
{#if model}
|
|
68
|
+
<T
|
|
69
|
+
is={model}
|
|
70
|
+
name={entity}
|
|
71
|
+
{...events}
|
|
72
|
+
/>
|
|
73
|
+
{/if}
|
|
74
|
+
|
|
75
|
+
{#if settings.current.renderArmModels.includes('colliders') || !model}
|
|
76
|
+
<Mesh
|
|
77
|
+
{entity}
|
|
78
|
+
center={center.current}
|
|
79
|
+
{...events}
|
|
80
|
+
>
|
|
81
|
+
{@render children?.()}
|
|
82
|
+
</Mesh>
|
|
83
|
+
{/if}
|
|
84
|
+
</T.Group>
|
|
82
85
|
</Portal>
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
const opacity = useTrait(() => entity, traits.Opacity)
|
|
39
39
|
const materialProps = useTrait(() => entity, traits.Material)
|
|
40
40
|
const renderOrder = useTrait(() => entity, traits.RenderOrder)
|
|
41
|
+
const invisible = useTrait(() => entity, traits.Invisible)
|
|
41
42
|
|
|
42
43
|
const events = useEntityEvents(() => entity)
|
|
43
44
|
|
|
@@ -60,6 +61,7 @@
|
|
|
60
61
|
userData.name={name}
|
|
61
62
|
raycast={meshBounds}
|
|
62
63
|
renderOrder={renderOrder.current}
|
|
64
|
+
visible={invisible.current !== true}
|
|
63
65
|
{...events}
|
|
64
66
|
>
|
|
65
67
|
<LineGeometry positions={linePositions.current} />
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
const color = useTrait(() => entity, traits.Color)
|
|
29
29
|
const opacity = useTrait(() => entity, traits.Opacity)
|
|
30
30
|
const entityPointSize = useTrait(() => entity, traits.PointSize)
|
|
31
|
+
const invisible = useTrait(() => entity, traits.Invisible)
|
|
31
32
|
|
|
32
33
|
const pointSize = $derived(
|
|
33
34
|
entityPointSize.current ? entityPointSize.current * 0.001 : settings.current.pointSize
|
|
@@ -126,6 +127,7 @@
|
|
|
126
127
|
is={points}
|
|
127
128
|
name={entity}
|
|
128
129
|
bvh={{ maxDepth: 40, maxLeafSize: 20 }}
|
|
130
|
+
visible={invisible.current !== true}
|
|
129
131
|
{...events}
|
|
130
132
|
>
|
|
131
133
|
<T is={geometry.current} />
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Entity } from 'koota';
|
|
2
2
|
import { type IntersectionEvent } from '@threlte/extras';
|
|
3
3
|
export declare const useEntityEvents: (entity: () => Entity | undefined) => {
|
|
4
|
-
readonly visible: boolean;
|
|
5
4
|
onpointerenter: (event: IntersectionEvent<MouseEvent>) => void;
|
|
6
5
|
onpointermove: (event: IntersectionEvent<MouseEvent>) => void;
|
|
7
6
|
onpointerleave: (event: IntersectionEvent<MouseEvent>) => void;
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import { useCursor } from '@threlte/extras';
|
|
2
2
|
import { Vector2 } from 'three';
|
|
3
|
-
import { traits } from '../../../ecs';
|
|
3
|
+
import { traits, useTrait } from '../../../ecs';
|
|
4
4
|
import { useFocusedEntity, useSelectedEntity } from '../../../hooks/useSelection.svelte';
|
|
5
|
-
import { useVisibility } from '../../../hooks/useVisibility.svelte';
|
|
6
5
|
import { updateHoverInfo } from '../../../HoverUpdater.svelte';
|
|
7
6
|
import { createPose, matrixToPose, poseToMatrix } from '../../../transform';
|
|
8
7
|
export const useEntityEvents = (entity) => {
|
|
9
8
|
const down = new Vector2();
|
|
10
9
|
const selectedEntity = useSelectedEntity();
|
|
11
10
|
const focusedEntity = useFocusedEntity();
|
|
12
|
-
const visibility = useVisibility();
|
|
13
11
|
const cursor = useCursor();
|
|
14
12
|
const currentEntity = $derived(entity());
|
|
15
|
-
const visible = $derived(currentEntity ? (visibility.get(currentEntity) ?? true) : true);
|
|
16
13
|
const onpointerenter = (event) => {
|
|
17
14
|
event.stopPropagation();
|
|
18
15
|
cursor.onPointerEnter();
|
|
@@ -90,15 +87,13 @@ export const useEntityEvents = (entity) => {
|
|
|
90
87
|
selectedEntity.set(currentEntity, event.instanceId ?? event.batchId);
|
|
91
88
|
}
|
|
92
89
|
};
|
|
90
|
+
const invisible = useTrait(entity, traits.Invisible);
|
|
93
91
|
$effect(() => {
|
|
94
|
-
if (
|
|
92
|
+
if (invisible.current) {
|
|
95
93
|
cursor.onPointerLeave();
|
|
96
94
|
}
|
|
97
95
|
});
|
|
98
96
|
return {
|
|
99
|
-
get visible() {
|
|
100
|
-
return visible;
|
|
101
|
-
},
|
|
102
97
|
onpointerenter,
|
|
103
98
|
onpointermove,
|
|
104
99
|
onpointerleave,
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
import { PressedKeys } from 'runed'
|
|
6
6
|
import { MathUtils, Vector3 } from 'three'
|
|
7
7
|
|
|
8
|
+
import { traits } from '../ecs'
|
|
8
9
|
import { useFocusedEntity, useSelectedEntity } from '../hooks/useSelection.svelte'
|
|
9
10
|
import { useSettings } from '../hooks/useSettings.svelte'
|
|
10
|
-
import { useVisibility } from '../hooks/useVisibility.svelte'
|
|
11
11
|
|
|
12
12
|
interface Props {
|
|
13
13
|
cameraControls: CameraControlsRef
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
const entity = $derived(focusedEntity.current ?? selectedEntity.current)
|
|
22
22
|
|
|
23
23
|
const settings = useSettings()
|
|
24
|
-
const visibility = useVisibility()
|
|
25
24
|
|
|
26
25
|
const keys = new PressedKeys()
|
|
27
26
|
const meta = $derived(keys.has('meta'))
|
|
@@ -181,9 +180,12 @@
|
|
|
181
180
|
|
|
182
181
|
event.stopImmediatePropagation()
|
|
183
182
|
|
|
184
|
-
|
|
183
|
+
if (entity.has(traits.Invisible)) {
|
|
184
|
+
entity.remove(traits.Invisible)
|
|
185
|
+
} else {
|
|
186
|
+
entity.add(traits.Invisible)
|
|
187
|
+
}
|
|
185
188
|
|
|
186
|
-
visibility.set(entity, !visible)
|
|
187
189
|
return
|
|
188
190
|
}
|
|
189
191
|
}
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
import { providePointclouds } from '../hooks/usePointclouds.svelte'
|
|
23
23
|
import { provideResourceByName } from '../hooks/useResourceByName.svelte'
|
|
24
24
|
import { provideSelection } from '../hooks/useSelection.svelte'
|
|
25
|
-
import { provideVisibility } from '../hooks/useVisibility.svelte'
|
|
26
25
|
import { provideWorldStates } from '../hooks/useWorldState.svelte'
|
|
27
26
|
|
|
28
27
|
import { provideOrigin } from './xr/useOrigin.svelte'
|
|
@@ -38,7 +37,6 @@
|
|
|
38
37
|
|
|
39
38
|
provideCameraControls(() => cameraPose)
|
|
40
39
|
provideTransformControls()
|
|
41
|
-
provideVisibility()
|
|
42
40
|
provideMachineSettings()
|
|
43
41
|
provideLogs()
|
|
44
42
|
|
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
const logs = useLogs()
|
|
11
11
|
|
|
12
12
|
const isOpen = new PersistedState('logs-is-open', false)
|
|
13
|
+
|
|
14
|
+
let levels = new PersistedState('logs-selected-levels', {
|
|
15
|
+
info: true,
|
|
16
|
+
warn: true,
|
|
17
|
+
error: true,
|
|
18
|
+
})
|
|
13
19
|
</script>
|
|
14
20
|
|
|
15
21
|
<Portal id="dashboard">
|
|
@@ -44,34 +50,95 @@
|
|
|
44
50
|
title="Logs"
|
|
45
51
|
bind:isOpen={isOpen.current}
|
|
46
52
|
defaultSize={{ width: 240, height: 315 }}
|
|
53
|
+
resizable
|
|
47
54
|
>
|
|
48
|
-
<div class="flex h-
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
55
|
+
<div class="flex h-full flex-col">
|
|
56
|
+
<div class="flex gap-1 px-3 py-2">
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
class={[
|
|
60
|
+
'chip border px-2',
|
|
61
|
+
{
|
|
62
|
+
'border-danger-dark bg-danger-dark text-white hover:border-red-700 hover:bg-red-700':
|
|
63
|
+
levels.current.error,
|
|
64
|
+
'bg-light hover:bg-ghost-light hover:border-light border-light text-subtle-1':
|
|
65
|
+
!levels.current.error,
|
|
66
|
+
},
|
|
67
|
+
]}
|
|
68
|
+
onclick={() => {
|
|
69
|
+
levels.current.error = !levels.current.error
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
error
|
|
73
|
+
</button>
|
|
74
|
+
|
|
75
|
+
<button
|
|
76
|
+
type="button"
|
|
77
|
+
class={[
|
|
78
|
+
'chip border',
|
|
79
|
+
{
|
|
80
|
+
'border-amber-400 bg-amber-400 text-white hover:border-amber-500 hover:bg-amber-500':
|
|
81
|
+
levels.current.warn,
|
|
82
|
+
'bg-light hover:bg-ghost-light hover:border-light border-light text-subtle-1':
|
|
83
|
+
!levels.current.warn,
|
|
84
|
+
},
|
|
85
|
+
]}
|
|
86
|
+
onclick={() => {
|
|
87
|
+
levels.current.warn = !levels.current.warn
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
warn
|
|
91
|
+
</button>
|
|
92
|
+
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
class={[
|
|
96
|
+
'chip border',
|
|
97
|
+
{
|
|
98
|
+
'border-blue-400 bg-blue-400 text-white hover:border-blue-500 hover:bg-blue-500':
|
|
99
|
+
levels.current.info,
|
|
100
|
+
'bg-light hover:bg-ghost-light hover:border-light border-light text-subtle-1':
|
|
101
|
+
!levels.current.info,
|
|
102
|
+
},
|
|
103
|
+
]}
|
|
104
|
+
onclick={() => {
|
|
105
|
+
levels.current.info = !levels.current.info
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
info
|
|
109
|
+
</button>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div class="flex flex-col gap-2 overflow-auto px-3 pb-3 text-xs">
|
|
113
|
+
{#each logs.current as log (log.uuid)}
|
|
114
|
+
{#if levels.current[log.level]}
|
|
115
|
+
<div>
|
|
116
|
+
<div class="flex flex-wrap items-center gap-1.5">
|
|
117
|
+
<div
|
|
118
|
+
class={[
|
|
119
|
+
'h-2 w-2 rounded-full',
|
|
120
|
+
{
|
|
121
|
+
'bg-danger-dark': log.level === 'error',
|
|
122
|
+
'bg-amber-300': log.level === 'warn',
|
|
123
|
+
'bg-blue-400': log.level === 'info',
|
|
124
|
+
},
|
|
125
|
+
]}
|
|
126
|
+
></div>
|
|
127
|
+
<div class="text-subtle-2">{log.timestamp}</div>
|
|
128
|
+
</div>
|
|
129
|
+
<div>
|
|
130
|
+
{#if log.count > 1}
|
|
131
|
+
<span class="mr-1 rounded bg-green-700 px-1 py-0.5 text-xs text-white">
|
|
132
|
+
{log.count}
|
|
133
|
+
</span>
|
|
134
|
+
{/if}
|
|
135
|
+
{log.message}
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
{/if}
|
|
139
|
+
{:else}
|
|
140
|
+
No logs
|
|
141
|
+
{/each}
|
|
142
|
+
</div>
|
|
76
143
|
</div>
|
|
77
144
|
</FloatingPanel>
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { normalizeProps, useMachine } from '@zag-js/svelte'
|
|
3
3
|
import * as tree from '@zag-js/tree-view'
|
|
4
|
-
import { ChevronRight, Eye, EyeOff } from 'lucide-svelte'
|
|
5
4
|
import { VirtualList } from 'svelte-virtuallists'
|
|
6
5
|
import { SvelteSet } from 'svelte/reactivity'
|
|
7
6
|
|
|
8
7
|
import { traits } from '../../../ecs'
|
|
9
8
|
import { useSelectedEntity } from '../../../hooks/useSelection.svelte'
|
|
10
|
-
import { useVisibility } from '../../../hooks/useVisibility.svelte'
|
|
11
9
|
|
|
12
|
-
import type { TreeNode } from './buildTree'
|
|
10
|
+
import type { TreeNode as TreeNodeType } from './buildTree'
|
|
11
|
+
|
|
12
|
+
import TreeNode from './TreeNode.svelte'
|
|
13
13
|
|
|
14
14
|
const selected = useSelectedEntity()
|
|
15
|
-
const visibility = useVisibility()
|
|
16
15
|
|
|
17
16
|
interface Props {
|
|
18
|
-
rootNode:
|
|
19
|
-
nodeMap: Record<string,
|
|
17
|
+
rootNode: TreeNodeType
|
|
18
|
+
nodeMap: Record<string, TreeNodeType | undefined>
|
|
20
19
|
dragElement?: HTMLElement
|
|
21
20
|
onSelectionChange?: (event: tree.SelectionChangeDetails) => void
|
|
22
21
|
}
|
|
@@ -24,7 +23,7 @@
|
|
|
24
23
|
let { rootNode, nodeMap, onSelectionChange, dragElement = $bindable() }: Props = $props()
|
|
25
24
|
|
|
26
25
|
const collection = $derived(
|
|
27
|
-
tree.collection<
|
|
26
|
+
tree.collection<TreeNodeType>({
|
|
28
27
|
nodeToValue: (node) => `${node.entity}`,
|
|
29
28
|
nodeToString: (node) => node.entity.get(traits.Name) ?? '',
|
|
30
29
|
rootNode,
|
|
@@ -74,108 +73,6 @@
|
|
|
74
73
|
})
|
|
75
74
|
</script>
|
|
76
75
|
|
|
77
|
-
{#snippet treeNode({
|
|
78
|
-
node,
|
|
79
|
-
indexPath,
|
|
80
|
-
api,
|
|
81
|
-
}: {
|
|
82
|
-
node: TreeNode
|
|
83
|
-
indexPath: number[]
|
|
84
|
-
api: tree.Api
|
|
85
|
-
})}
|
|
86
|
-
{@const nodeProps = { indexPath, node }}
|
|
87
|
-
{@const nodeState = api.getNodeState(nodeProps)}
|
|
88
|
-
{@const isVisible = visibility.get(node.entity) ?? true}
|
|
89
|
-
{@const { selected } = nodeState}
|
|
90
|
-
|
|
91
|
-
{#if nodeState.isBranch}
|
|
92
|
-
{@const { expanded } = nodeState}
|
|
93
|
-
{@const { children = [] } = node}
|
|
94
|
-
<div
|
|
95
|
-
{...api.getBranchProps(nodeProps)}
|
|
96
|
-
class={[
|
|
97
|
-
'w-full',
|
|
98
|
-
{
|
|
99
|
-
'text-disabled': !isVisible,
|
|
100
|
-
'bg-medium': selected,
|
|
101
|
-
sticky: true,
|
|
102
|
-
},
|
|
103
|
-
]}
|
|
104
|
-
>
|
|
105
|
-
<div {...api.getBranchControlProps(nodeProps)}>
|
|
106
|
-
<span
|
|
107
|
-
{...api.getBranchIndicatorProps(nodeProps)}
|
|
108
|
-
class={{ 'rotate-90': expanded }}
|
|
109
|
-
>
|
|
110
|
-
<ChevronRight size={14} />
|
|
111
|
-
</span>
|
|
112
|
-
<span
|
|
113
|
-
class="flex items-center overflow-hidden text-ellipsis"
|
|
114
|
-
{...api.getBranchTextProps(nodeProps)}
|
|
115
|
-
>
|
|
116
|
-
{node.entity.get(traits.Name)}
|
|
117
|
-
</span>
|
|
118
|
-
|
|
119
|
-
<button
|
|
120
|
-
class="text-gray-6"
|
|
121
|
-
onclick={(event) => {
|
|
122
|
-
event.stopPropagation()
|
|
123
|
-
visibility.set(node.entity, !isVisible)
|
|
124
|
-
}}
|
|
125
|
-
>
|
|
126
|
-
{#if isVisible}
|
|
127
|
-
<Eye size={14} />
|
|
128
|
-
{:else}
|
|
129
|
-
<EyeOff size={14} />
|
|
130
|
-
{/if}
|
|
131
|
-
</button>
|
|
132
|
-
</div>
|
|
133
|
-
<div {...api.getBranchContentProps(nodeProps)}>
|
|
134
|
-
<div {...api.getBranchIndentGuideProps(nodeProps)}></div>
|
|
135
|
-
|
|
136
|
-
{#if children.length > 200}
|
|
137
|
-
<VirtualList
|
|
138
|
-
class="w-full"
|
|
139
|
-
style="height:{Math.min(8, Math.max(children.length, 5)) * 32}px;"
|
|
140
|
-
items={children}
|
|
141
|
-
>
|
|
142
|
-
{#snippet vl_slot({ index, item })}
|
|
143
|
-
{@render treeNode({ node: item, indexPath: [...indexPath, Number(index)], api })}
|
|
144
|
-
{/snippet}
|
|
145
|
-
</VirtualList>
|
|
146
|
-
{:else}
|
|
147
|
-
{#each children as node, index (node.entity)}
|
|
148
|
-
{@render treeNode({ node, indexPath: [...indexPath, index], api })}
|
|
149
|
-
{/each}
|
|
150
|
-
{/if}
|
|
151
|
-
</div>
|
|
152
|
-
</div>
|
|
153
|
-
{:else}
|
|
154
|
-
<div
|
|
155
|
-
class={{ 'flex justify-between': true, 'text-disabled': !isVisible, 'bg-medium': selected }}
|
|
156
|
-
{...api.getItemProps(nodeProps)}
|
|
157
|
-
>
|
|
158
|
-
<span class="flex items-center gap-1.5 overflow-hidden text-nowrap text-ellipsis">
|
|
159
|
-
{node.entity.get(traits.Name)}
|
|
160
|
-
</span>
|
|
161
|
-
|
|
162
|
-
<button
|
|
163
|
-
class="text-gray-6"
|
|
164
|
-
onclick={(event) => {
|
|
165
|
-
event.stopPropagation()
|
|
166
|
-
visibility.set(node.entity, !isVisible)
|
|
167
|
-
}}
|
|
168
|
-
>
|
|
169
|
-
{#if isVisible}
|
|
170
|
-
<Eye size={14} />
|
|
171
|
-
{:else}
|
|
172
|
-
<EyeOff size={14} />
|
|
173
|
-
{/if}
|
|
174
|
-
</button>
|
|
175
|
-
</div>
|
|
176
|
-
{/if}
|
|
177
|
-
{/snippet}
|
|
178
|
-
|
|
179
76
|
<div
|
|
180
77
|
{...api.getRootProps()}
|
|
181
78
|
class="h-full overflow-auto text-xs"
|
|
@@ -188,49 +85,22 @@
|
|
|
188
85
|
class="w-full"
|
|
189
86
|
items={rootChildren}
|
|
190
87
|
>
|
|
191
|
-
{#snippet vl_slot({ index, item })}
|
|
192
|
-
|
|
88
|
+
{#snippet vl_slot({ index, item: node })}
|
|
89
|
+
<TreeNode
|
|
90
|
+
{node}
|
|
91
|
+
indexPath={[Number(index)]}
|
|
92
|
+
{api}
|
|
93
|
+
/>
|
|
193
94
|
{/snippet}
|
|
194
95
|
</VirtualList>
|
|
195
96
|
{:else}
|
|
196
97
|
{#each rootChildren as node, index (node.entity)}
|
|
197
|
-
|
|
98
|
+
<TreeNode
|
|
99
|
+
{node}
|
|
100
|
+
indexPath={[Number(index)]}
|
|
101
|
+
{api}
|
|
102
|
+
/>
|
|
198
103
|
{/each}
|
|
199
104
|
{/if}
|
|
200
105
|
</div>
|
|
201
106
|
</div>
|
|
202
|
-
|
|
203
|
-
<style>
|
|
204
|
-
:global(:root) {
|
|
205
|
-
[data-scope='tree-view'][data-part='item'],
|
|
206
|
-
[data-scope='tree-view'][data-part='branch-control'] {
|
|
207
|
-
user-select: none;
|
|
208
|
-
--padding-inline: 16px;
|
|
209
|
-
padding-inline-start: calc(var(--depth) * var(--padding-inline));
|
|
210
|
-
padding-inline-end: var(--padding-inline);
|
|
211
|
-
display: flex;
|
|
212
|
-
align-items: center;
|
|
213
|
-
gap: 8px;
|
|
214
|
-
min-height: 32px;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
[data-scope='tree-view'][data-part='item-text'],
|
|
218
|
-
[data-scope='tree-view'][data-part='branch-text'] {
|
|
219
|
-
flex: 1;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
[data-scope='tree-view'][data-part='branch-content'] {
|
|
223
|
-
position: relative;
|
|
224
|
-
isolation: isolate;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
[data-scope='tree-view'][data-part='branch-indent-guide'] {
|
|
228
|
-
position: absolute;
|
|
229
|
-
content: '';
|
|
230
|
-
border-left: 1px solid #eee;
|
|
231
|
-
height: 100%;
|
|
232
|
-
translate: calc(var(--depth) * 1.25rem);
|
|
233
|
-
z-index: 1;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
</style>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as tree from '@zag-js/tree-view';
|
|
2
|
-
import type { TreeNode } from './buildTree';
|
|
2
|
+
import type { TreeNode as TreeNodeType } from './buildTree';
|
|
3
3
|
interface Props {
|
|
4
|
-
rootNode:
|
|
5
|
-
nodeMap: Record<string,
|
|
4
|
+
rootNode: TreeNodeType;
|
|
5
|
+
nodeMap: Record<string, TreeNodeType | undefined>;
|
|
6
6
|
dragElement?: HTMLElement;
|
|
7
7
|
onSelectionChange?: (event: tree.SelectionChangeDetails) => void;
|
|
8
8
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Api } from '@zag-js/tree-view'
|
|
3
|
+
|
|
4
|
+
import { ChevronRight, Eye, EyeOff } from 'lucide-svelte'
|
|
5
|
+
import { VirtualList } from 'svelte-virtuallists'
|
|
6
|
+
|
|
7
|
+
import { traits, useTrait } from '../../../ecs'
|
|
8
|
+
|
|
9
|
+
import type { TreeNode } from './buildTree'
|
|
10
|
+
|
|
11
|
+
import Self from './TreeNode.svelte'
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
node: TreeNode
|
|
15
|
+
indexPath: number[]
|
|
16
|
+
api: Api
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let { node, indexPath, api }: Props = $props()
|
|
20
|
+
|
|
21
|
+
const name = useTrait(() => node.entity, traits.Name)
|
|
22
|
+
const invisible = useTrait(() => node.entity, traits.Invisible)
|
|
23
|
+
|
|
24
|
+
const nodeProps = $derived({ indexPath, node })
|
|
25
|
+
const nodeState = $derived(api.getNodeState(nodeProps))
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
{#if nodeState.isBranch}
|
|
29
|
+
{@const { expanded } = nodeState}
|
|
30
|
+
{@const { children = [] } = node}
|
|
31
|
+
<div
|
|
32
|
+
{...api.getBranchProps(nodeProps)}
|
|
33
|
+
class={[
|
|
34
|
+
'w-full',
|
|
35
|
+
{
|
|
36
|
+
'text-disabled': invisible.current,
|
|
37
|
+
'bg-medium': nodeState.selected,
|
|
38
|
+
sticky: true,
|
|
39
|
+
},
|
|
40
|
+
]}
|
|
41
|
+
>
|
|
42
|
+
<div {...api.getBranchControlProps(nodeProps)}>
|
|
43
|
+
<span
|
|
44
|
+
{...api.getBranchIndicatorProps(nodeProps)}
|
|
45
|
+
class={{ 'rotate-90': expanded }}
|
|
46
|
+
>
|
|
47
|
+
<ChevronRight size={14} />
|
|
48
|
+
</span>
|
|
49
|
+
<span
|
|
50
|
+
class="flex items-center overflow-hidden text-ellipsis"
|
|
51
|
+
{...api.getBranchTextProps(nodeProps)}
|
|
52
|
+
>
|
|
53
|
+
{name.current}
|
|
54
|
+
</span>
|
|
55
|
+
|
|
56
|
+
<button
|
|
57
|
+
class="text-gray-6"
|
|
58
|
+
onclick={(event) => {
|
|
59
|
+
event.stopPropagation()
|
|
60
|
+
|
|
61
|
+
if (node.entity.has(traits.Invisible)) {
|
|
62
|
+
node.entity.remove(traits.Invisible)
|
|
63
|
+
} else {
|
|
64
|
+
node.entity.add(traits.Invisible)
|
|
65
|
+
}
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
{#if invisible.current}
|
|
69
|
+
<EyeOff size={14} />
|
|
70
|
+
{:else}
|
|
71
|
+
<Eye size={14} />
|
|
72
|
+
{/if}
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
<div {...api.getBranchContentProps(nodeProps)}>
|
|
76
|
+
<div {...api.getBranchIndentGuideProps(nodeProps)}></div>
|
|
77
|
+
|
|
78
|
+
{#if children.length > 200}
|
|
79
|
+
<VirtualList
|
|
80
|
+
class="w-full"
|
|
81
|
+
style="height:{Math.min(8, Math.max(children.length, 5)) * 32}px;"
|
|
82
|
+
items={children}
|
|
83
|
+
>
|
|
84
|
+
{#snippet vl_slot({ index, item: node })}
|
|
85
|
+
<Self
|
|
86
|
+
{node}
|
|
87
|
+
indexPath={[...indexPath, Number(index)]}
|
|
88
|
+
{api}
|
|
89
|
+
/>
|
|
90
|
+
{/snippet}
|
|
91
|
+
</VirtualList>
|
|
92
|
+
{:else}
|
|
93
|
+
{#each children as node, index (node.entity)}
|
|
94
|
+
<Self
|
|
95
|
+
{node}
|
|
96
|
+
indexPath={[...indexPath, Number(index)]}
|
|
97
|
+
{api}
|
|
98
|
+
/>
|
|
99
|
+
{/each}
|
|
100
|
+
{/if}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
{:else}
|
|
104
|
+
<div
|
|
105
|
+
class={{
|
|
106
|
+
'flex justify-between': true,
|
|
107
|
+
'text-disabled': invisible.current,
|
|
108
|
+
'bg-medium': nodeState.selected,
|
|
109
|
+
}}
|
|
110
|
+
{...api.getItemProps(nodeProps)}
|
|
111
|
+
>
|
|
112
|
+
<span class="flex items-center gap-1.5 overflow-hidden text-nowrap text-ellipsis">
|
|
113
|
+
{node.entity.get(traits.Name)}
|
|
114
|
+
</span>
|
|
115
|
+
|
|
116
|
+
<button
|
|
117
|
+
class="text-gray-6"
|
|
118
|
+
onclick={(event) => {
|
|
119
|
+
event.stopPropagation()
|
|
120
|
+
if (node.entity.has(traits.Invisible)) {
|
|
121
|
+
node.entity.remove(traits.Invisible)
|
|
122
|
+
} else {
|
|
123
|
+
node.entity.add(traits.Invisible)
|
|
124
|
+
}
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
{#if invisible.current}
|
|
128
|
+
<EyeOff size={14} />
|
|
129
|
+
{:else}
|
|
130
|
+
<Eye size={14} />
|
|
131
|
+
{/if}
|
|
132
|
+
</button>
|
|
133
|
+
</div>
|
|
134
|
+
{/if}
|
|
135
|
+
|
|
136
|
+
<style>
|
|
137
|
+
:global(:root) {
|
|
138
|
+
[data-scope='tree-view'][data-part='item'],
|
|
139
|
+
[data-scope='tree-view'][data-part='branch-control'] {
|
|
140
|
+
user-select: none;
|
|
141
|
+
--padding-inline: 16px;
|
|
142
|
+
padding-inline-start: calc(var(--depth) * var(--padding-inline));
|
|
143
|
+
padding-inline-end: var(--padding-inline);
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
gap: 8px;
|
|
147
|
+
min-height: 32px;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
[data-scope='tree-view'][data-part='item-text'],
|
|
151
|
+
[data-scope='tree-view'][data-part='branch-text'] {
|
|
152
|
+
flex: 1;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
[data-scope='tree-view'][data-part='branch-content'] {
|
|
156
|
+
position: relative;
|
|
157
|
+
isolation: isolate;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
[data-scope='tree-view'][data-part='branch-indent-guide'] {
|
|
161
|
+
position: absolute;
|
|
162
|
+
content: '';
|
|
163
|
+
border-left: 1px solid #eee;
|
|
164
|
+
height: 100%;
|
|
165
|
+
translate: calc(var(--depth) * 1.25rem);
|
|
166
|
+
z-index: 1;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Api } from '@zag-js/tree-view';
|
|
2
|
+
import type { TreeNode } from './buildTree';
|
|
3
|
+
interface Props {
|
|
4
|
+
node: TreeNode;
|
|
5
|
+
indexPath: number[];
|
|
6
|
+
api: Api;
|
|
7
|
+
}
|
|
8
|
+
declare const TreeNode: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type TreeNode = ReturnType<typeof TreeNode>;
|
|
10
|
+
export default TreeNode;
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
})
|
|
52
52
|
leftPad.trigger.on('up', () => (dragging = false))
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
useTask(
|
|
55
55
|
() => {
|
|
56
56
|
if (!$left || !rigidBody) return
|
|
57
57
|
|
|
@@ -62,11 +62,11 @@
|
|
|
62
62
|
rigidBody.setNextKinematicTranslation({ x: position.x, y: position.y, z: position.z })
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
|
-
|
|
65
|
+
running: () => dragging,
|
|
66
66
|
}
|
|
67
67
|
)
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
useTask(
|
|
70
70
|
() => {
|
|
71
71
|
if (!$right || !rigidBody) return
|
|
72
72
|
|
|
@@ -80,24 +80,8 @@
|
|
|
80
80
|
|
|
81
81
|
rigidBody.setNextKinematicRotation(quaternion.setFromEuler(euler))
|
|
82
82
|
},
|
|
83
|
-
{
|
|
83
|
+
{ running: () => rotating }
|
|
84
84
|
)
|
|
85
|
-
|
|
86
|
-
$effect.pre(() => {
|
|
87
|
-
if (dragging) {
|
|
88
|
-
dragTask.start()
|
|
89
|
-
} else {
|
|
90
|
-
dragTask.stop()
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
$effect.pre(() => {
|
|
95
|
-
if (rotating) {
|
|
96
|
-
rotateTask.start()
|
|
97
|
-
} else {
|
|
98
|
-
rotateTask.stop()
|
|
99
|
-
}
|
|
100
|
-
})
|
|
101
85
|
</script>
|
|
102
86
|
|
|
103
87
|
<T
|
|
@@ -18,13 +18,7 @@
|
|
|
18
18
|
const settings = useSettings()
|
|
19
19
|
const armClient = useArmClient()
|
|
20
20
|
const partID = usePartID()
|
|
21
|
-
|
|
22
|
-
let resources: ReturnType<typeof useResourceNames> | undefined
|
|
23
|
-
try {
|
|
24
|
-
resources = useResourceNames(() => partID.current)
|
|
25
|
-
} catch (error) {
|
|
26
|
-
console.warn('Failed to get resources, robot may not be connected yet:', error)
|
|
27
|
-
}
|
|
21
|
+
let resources = useResourceNames(() => partID.current)
|
|
28
22
|
|
|
29
23
|
// Get available arms and grippers
|
|
30
24
|
const armNames = $derived(armClient.names || [])
|
|
@@ -1,2 +1,9 @@
|
|
|
1
|
+
import { type Object3D, type Quaternion, type Vector3 } from 'three';
|
|
2
|
+
interface Context {
|
|
3
|
+
createAnchor: (position: Vector3, orientation: Quaternion) => Promise<XRAnchor> | undefined;
|
|
4
|
+
bindAnchorObject: (anchor: XRAnchor, object: Object3D) => void;
|
|
5
|
+
unbindAnchorObject: (anchor: XRAnchor) => void;
|
|
6
|
+
}
|
|
1
7
|
export declare const provideAnchors: () => void;
|
|
2
|
-
export declare const useAnchors: () =>
|
|
8
|
+
export declare const useAnchors: () => Context;
|
|
9
|
+
export {};
|
|
@@ -1,28 +1,32 @@
|
|
|
1
|
-
import { useTask, useThrelte
|
|
1
|
+
import { useTask, useThrelte } from '@threlte/core';
|
|
2
2
|
import { useXR } from '@threlte/xr';
|
|
3
3
|
import { getContext, setContext } from 'svelte';
|
|
4
|
-
import {
|
|
4
|
+
import { fromStore } from 'svelte/store';
|
|
5
|
+
import {} from 'three';
|
|
5
6
|
const key = Symbol('anchors-context');
|
|
6
7
|
export const provideAnchors = () => {
|
|
7
|
-
const matrix4 = new Matrix4();
|
|
8
8
|
const { renderer } = useThrelte();
|
|
9
|
-
const { isPresenting } = useXR();
|
|
9
|
+
const { isPresenting: isPresentingStore } = useXR();
|
|
10
|
+
const isPresenting = fromStore(isPresentingStore);
|
|
10
11
|
const map = new WeakMap();
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (space
|
|
12
|
+
const createAnchor = (position, quaternion) => {
|
|
13
|
+
const space = renderer.xr.getReferenceSpace();
|
|
14
|
+
const frame = renderer.xr.getFrame();
|
|
15
|
+
if (!space || !frame)
|
|
15
16
|
return;
|
|
16
|
-
const pose = new XRRigidTransform(position,
|
|
17
|
-
return
|
|
17
|
+
const pose = new XRRigidTransform({ x: position.x, y: position.y, z: position.z }, { x: quaternion.x, y: quaternion.y, z: quaternion.z, w: quaternion.w });
|
|
18
|
+
return frame.createAnchor?.(pose, space);
|
|
18
19
|
};
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
const bindAnchorObject = (anchor, object) => {
|
|
21
|
+
map.set(anchor, object);
|
|
22
|
+
};
|
|
23
|
+
const unbindAnchorObject = (anchor) => {
|
|
24
|
+
map.delete(anchor);
|
|
25
|
+
};
|
|
26
|
+
useTask(() => {
|
|
27
|
+
const space = renderer.xr.getReferenceSpace();
|
|
24
28
|
const frame = renderer.xr.getFrame();
|
|
25
|
-
if (!frame
|
|
29
|
+
if (!space || !frame?.trackedAnchors) {
|
|
26
30
|
return;
|
|
27
31
|
}
|
|
28
32
|
for (const anchor of frame.trackedAnchors) {
|
|
@@ -34,22 +38,18 @@ export const provideAnchors = () => {
|
|
|
34
38
|
if (!anchorPose) {
|
|
35
39
|
continue;
|
|
36
40
|
}
|
|
37
|
-
|
|
38
|
-
object3d.
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
watch(isPresenting, ($isPresenting) => {
|
|
42
|
-
if ($isPresenting) {
|
|
43
|
-
start();
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
stop();
|
|
41
|
+
object3d.matrixAutoUpdate = false;
|
|
42
|
+
object3d.matrix.fromArray(anchorPose.transform.matrix);
|
|
47
43
|
}
|
|
44
|
+
}, {
|
|
45
|
+
running: () => isPresenting.current,
|
|
48
46
|
});
|
|
49
47
|
setContext(key, {
|
|
50
48
|
createAnchor,
|
|
49
|
+
bindAnchorObject,
|
|
50
|
+
unbindAnchorObject,
|
|
51
51
|
});
|
|
52
52
|
};
|
|
53
53
|
export const useAnchors = () => {
|
|
54
|
-
getContext(key);
|
|
54
|
+
return getContext(key);
|
|
55
55
|
};
|
package/dist/ecs/traits.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ export declare const WorldPose: import("koota").Trait<{
|
|
|
51
51
|
theta: number;
|
|
52
52
|
}>;
|
|
53
53
|
export declare const Hovered: import("koota").Trait<() => boolean>;
|
|
54
|
+
export declare const Invisible: import("koota").Trait<() => boolean>;
|
|
54
55
|
/**
|
|
55
56
|
* Represents that an entity is composed of many instances, so that the treeview and
|
|
56
57
|
* details panel may display all instances
|
package/dist/ecs/traits.js
CHANGED
|
@@ -28,6 +28,7 @@ export const WorldPose = trait({
|
|
|
28
28
|
theta: 0,
|
|
29
29
|
});
|
|
30
30
|
export const Hovered = trait(() => true);
|
|
31
|
+
export const Invisible = trait(() => true);
|
|
31
32
|
/**
|
|
32
33
|
* Represents that an entity is composed of many instances, so that the treeview and
|
|
33
34
|
* details panel may display all instances
|
|
@@ -10,14 +10,14 @@ export const provideFramelessComponents = () => {
|
|
|
10
10
|
const partComponentsWIthNoFrame = components
|
|
11
11
|
?.filter((component) => component.frame === undefined)
|
|
12
12
|
.map((component) => component.name) ?? [];
|
|
13
|
-
const fragmentComponentsWithNoFrame =
|
|
13
|
+
const fragmentComponentsWithNoFrame = new Set(partComponentsWIthNoFrame);
|
|
14
14
|
for (const fragmentComponentName of Object.keys(partConfig.componentNameToFragmentId)) {
|
|
15
15
|
if (frames.current.some((frame) => frame.referenceFrame === fragmentComponentName)) {
|
|
16
16
|
continue;
|
|
17
17
|
}
|
|
18
|
-
fragmentComponentsWithNoFrame.
|
|
18
|
+
fragmentComponentsWithNoFrame.add(fragmentComponentName);
|
|
19
19
|
}
|
|
20
|
-
return [...
|
|
20
|
+
return [...fragmentComponentsWithNoFrame];
|
|
21
21
|
});
|
|
22
22
|
setContext(key, {
|
|
23
23
|
get current() {
|
|
@@ -20,8 +20,10 @@ export const provideFrames = (partID) => {
|
|
|
20
20
|
const connectionStatus = useConnectionStatus(partID);
|
|
21
21
|
const machineStatus = useMachineStatus(partID);
|
|
22
22
|
const logs = useLogs();
|
|
23
|
+
let didRecentlyEdit = $state(false);
|
|
23
24
|
const isEditMode = $derived(environment.current.viewerMode === 'edit');
|
|
24
25
|
const query = createRobotQuery(client, 'frameSystemConfig', () => ({
|
|
26
|
+
refetchOnWindowFocus: false,
|
|
25
27
|
enabled: partID() !== '' && !isEditMode,
|
|
26
28
|
}));
|
|
27
29
|
const revision = $derived(machineStatus.current?.config?.revision);
|
|
@@ -41,7 +43,8 @@ export const provideFrames = (partID) => {
|
|
|
41
43
|
}
|
|
42
44
|
frames[frame.referenceFrame] = frame;
|
|
43
45
|
}
|
|
44
|
-
if
|
|
46
|
+
// Let config frames take priority if the user has made edits
|
|
47
|
+
if (didRecentlyEdit || connectionStatus.current === MachineConnectionEvent.DISCONNECTED) {
|
|
45
48
|
const mergedFrames = {
|
|
46
49
|
...frames,
|
|
47
50
|
...configFrames.current,
|
|
@@ -56,9 +59,8 @@ export const provideFrames = (partID) => {
|
|
|
56
59
|
return mergedFrames;
|
|
57
60
|
}
|
|
58
61
|
/**
|
|
59
|
-
* If we'
|
|
62
|
+
* If we haven't edited and we have a robot connection,
|
|
60
63
|
* we only use frames reported by the machine
|
|
61
|
-
*
|
|
62
64
|
*/
|
|
63
65
|
return frames;
|
|
64
66
|
});
|
|
@@ -69,6 +71,11 @@ export const provideFrames = (partID) => {
|
|
|
69
71
|
untrack(() => query.refetch());
|
|
70
72
|
}
|
|
71
73
|
});
|
|
74
|
+
$effect(() => {
|
|
75
|
+
if (isEditMode) {
|
|
76
|
+
didRecentlyEdit = true;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
72
79
|
$effect.pre(() => {
|
|
73
80
|
const currentResourcesByName = resourceByName.current;
|
|
74
81
|
const currentPartID = partID();
|
|
@@ -136,11 +143,19 @@ export const provideFrames = (partID) => {
|
|
|
136
143
|
if (!active[entityKey]) {
|
|
137
144
|
entity?.destroy();
|
|
138
145
|
entities.delete(entityKey);
|
|
139
|
-
continue;
|
|
140
146
|
}
|
|
141
147
|
}
|
|
142
148
|
});
|
|
143
149
|
});
|
|
150
|
+
// Clear all entities on unmount
|
|
151
|
+
$effect(() => {
|
|
152
|
+
return () => {
|
|
153
|
+
for (const [, entity] of entities) {
|
|
154
|
+
entity?.destroy();
|
|
155
|
+
}
|
|
156
|
+
entities.clear();
|
|
157
|
+
};
|
|
158
|
+
});
|
|
144
159
|
setContext(key, {
|
|
145
160
|
get current() {
|
|
146
161
|
return current;
|
|
@@ -117,14 +117,24 @@ export const provideGeometries = (partID) => {
|
|
|
117
117
|
if (!activeQueryKeys.has(queryKey)) {
|
|
118
118
|
for (const key of keys) {
|
|
119
119
|
const entity = entities.get(key);
|
|
120
|
-
if (entity && world.has(entity))
|
|
120
|
+
if (entity && world.has(entity)) {
|
|
121
121
|
entity.destroy();
|
|
122
|
+
}
|
|
122
123
|
entities.delete(key);
|
|
123
124
|
}
|
|
124
125
|
queryEntityKeys.delete(queryKey);
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
});
|
|
129
|
+
// Clear all entities on unmount
|
|
130
|
+
$effect(() => {
|
|
131
|
+
return () => {
|
|
132
|
+
for (const [, entity] of entities) {
|
|
133
|
+
entity?.destroy();
|
|
134
|
+
}
|
|
135
|
+
entities.clear();
|
|
136
|
+
};
|
|
137
|
+
});
|
|
128
138
|
setContext(key, {
|
|
129
139
|
refetch() {
|
|
130
140
|
for (const [, query] of queries) {
|
|
@@ -194,6 +194,14 @@ export const providePointcloudObjects = (partID) => {
|
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
});
|
|
197
|
+
$effect(() => {
|
|
198
|
+
return () => {
|
|
199
|
+
for (const [, entity] of entities) {
|
|
200
|
+
entity.destroy();
|
|
201
|
+
}
|
|
202
|
+
entities.clear();
|
|
203
|
+
};
|
|
204
|
+
});
|
|
197
205
|
setContext(key, {
|
|
198
206
|
refetch() {
|
|
199
207
|
for (const [, query] of queries) {
|
|
@@ -133,6 +133,14 @@ export const providePointclouds = (partID) => {
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
});
|
|
136
|
+
$effect(() => {
|
|
137
|
+
return () => {
|
|
138
|
+
for (const [, entity] of entities) {
|
|
139
|
+
entity.destroy();
|
|
140
|
+
}
|
|
141
|
+
entities.clear();
|
|
142
|
+
};
|
|
143
|
+
});
|
|
136
144
|
setContext(key, {
|
|
137
145
|
refetch() {
|
|
138
146
|
for (const [, query] of queries) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viamrobotics/motion-tools",
|
|
3
|
-
"version": "1.15.
|
|
3
|
+
"version": "1.15.6",
|
|
4
4
|
"description": "Motion visualization with Viam",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,6 @@
|
|
|
38
38
|
"@viamrobotics/prime-core": "0.1.5",
|
|
39
39
|
"@viamrobotics/sdk": "0.58.0",
|
|
40
40
|
"@viamrobotics/svelte-sdk": "1.0.1",
|
|
41
|
-
"@vitejs/plugin-basic-ssl": "2.1.0",
|
|
42
41
|
"@vitest/coverage-v8": "^3.2.4",
|
|
43
42
|
"@zag-js/collapsible": "1.22.1",
|
|
44
43
|
"@zag-js/floating-panel": "1.22.1",
|
|
@@ -63,7 +62,7 @@
|
|
|
63
62
|
"prettier-plugin-tailwindcss": "0.6.14",
|
|
64
63
|
"publint": "0.3.12",
|
|
65
64
|
"runed": "0.31.1",
|
|
66
|
-
"svelte": "5.
|
|
65
|
+
"svelte": "5.55.0",
|
|
67
66
|
"svelte-check": "4.4.5",
|
|
68
67
|
"svelte-virtuallists": "1.4.2",
|
|
69
68
|
"tailwindcss": "4.1.13",
|
|
@@ -145,6 +144,7 @@
|
|
|
145
144
|
},
|
|
146
145
|
"scripts": {
|
|
147
146
|
"dev": "tsx server/check-bun && bun run server/server.ts",
|
|
147
|
+
"dev:https": "vite dev -- --https",
|
|
148
148
|
"build": "vite build && npm run prepack",
|
|
149
149
|
"build:workers": "node scripts/build-workers.js",
|
|
150
150
|
"preview": "vite preview",
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { getContext, setContext } from 'svelte';
|
|
2
|
-
import { SvelteMap } from 'svelte/reactivity';
|
|
3
|
-
const key = Symbol('object-visibility-context');
|
|
4
|
-
export const provideVisibility = () => {
|
|
5
|
-
const map = new SvelteMap();
|
|
6
|
-
setContext(key, map);
|
|
7
|
-
};
|
|
8
|
-
export const useVisibility = () => {
|
|
9
|
-
return getContext(key);
|
|
10
|
-
};
|