@viamrobotics/motion-tools 1.12.3 → 1.13.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 +4 -1
- package/dist/components/Geometry.svelte +42 -42
- package/dist/components/SceneProviders.svelte +2 -0
- package/dist/components/Selected.svelte +9 -9
- package/dist/components/StaticGeometries.svelte +5 -2
- package/dist/components/overlay/Details.svelte +3 -3
- package/dist/components/overlay/FloatingPanel.svelte +3 -0
- package/dist/components/overlay/FloatingPanel.svelte.d.ts +1 -0
- package/dist/components/overlay/Logs.svelte +75 -0
- package/dist/components/overlay/left-pane/TreeContainer.svelte +0 -3
- package/dist/hooks/use3DModels.svelte.js +0 -1
- package/dist/hooks/useConfigFrames.svelte.d.ts +9 -0
- package/dist/hooks/useConfigFrames.svelte.js +92 -0
- package/dist/hooks/useFramelessComponents.svelte.js +1 -1
- package/dist/hooks/useFrames.svelte.d.ts +1 -6
- package/dist/hooks/useFrames.svelte.js +39 -145
- package/dist/hooks/usePartConfig.svelte.js +1 -1
- package/dist/hooks/useVisibility.svelte.js +0 -12
- package/package.json +1 -1
- package/dist/components/overlay/left-pane/Logs.svelte +0 -52
- /package/dist/components/overlay/{left-pane/Logs.svelte.d.ts → Logs.svelte.d.ts} +0 -0
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
import HoveredEntities from './hover/HoveredEntities.svelte'
|
|
31
31
|
import Settings from './overlay/settings/Settings.svelte'
|
|
32
32
|
import { useXR } from '@threlte/xr'
|
|
33
|
+
import Logs from './overlay/Logs.svelte'
|
|
33
34
|
|
|
34
35
|
interface LocalConfigProps {
|
|
35
36
|
current: Struct
|
|
@@ -120,7 +121,6 @@
|
|
|
120
121
|
<FileDrop />
|
|
121
122
|
<Dashboard {dashboard} />
|
|
122
123
|
<Details />
|
|
123
|
-
<Settings />
|
|
124
124
|
|
|
125
125
|
{#if environment.current.isStandalone}
|
|
126
126
|
<LiveUpdatesBanner />
|
|
@@ -141,6 +141,9 @@
|
|
|
141
141
|
{/if}
|
|
142
142
|
|
|
143
143
|
<PortalTarget id="dom" />
|
|
144
|
+
|
|
145
|
+
<Settings />
|
|
146
|
+
<Logs />
|
|
144
147
|
</div>
|
|
145
148
|
{/snippet}
|
|
146
149
|
</SceneProviders>
|
|
@@ -135,17 +135,17 @@
|
|
|
135
135
|
/>
|
|
136
136
|
{/if}
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
is={
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
138
|
+
{#if model && renderMode.includes('model')}
|
|
139
|
+
<T is={model} />
|
|
140
|
+
{/if}
|
|
141
|
+
|
|
142
|
+
{#if !model || renderMode.includes('colliders')}
|
|
143
|
+
<T
|
|
144
|
+
is={mesh}
|
|
145
|
+
name={entity}
|
|
146
|
+
userData.name={name}
|
|
147
|
+
renderOrder={renderOrder.current}
|
|
148
|
+
>
|
|
149
149
|
{#if linePositions.current}
|
|
150
150
|
<LineGeometry positions={linePositions.current} />
|
|
151
151
|
{:else if box.current}
|
|
@@ -168,40 +168,40 @@
|
|
|
168
168
|
{oncreate}
|
|
169
169
|
/>
|
|
170
170
|
{/if}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
171
|
+
|
|
172
|
+
{#if linePositions.current}
|
|
173
|
+
<T
|
|
174
|
+
is={LineMaterial}
|
|
175
|
+
{color}
|
|
176
|
+
width={lineWidth.current ? lineWidth.current * 0.001 : 0.5}
|
|
177
|
+
depthTest={materialProps.current?.depthTest ?? true}
|
|
178
|
+
/>
|
|
179
|
+
{:else}
|
|
180
|
+
{@const currentOpacity = opacity.current ?? 0.7}
|
|
181
|
+
<T.MeshToonMaterial
|
|
182
|
+
{color}
|
|
183
|
+
side={geometryType === 'buffer' ? DoubleSide : FrontSide}
|
|
184
|
+
transparent={currentOpacity < 1}
|
|
185
|
+
depthWrite={currentOpacity === 1}
|
|
186
|
+
opacity={currentOpacity}
|
|
187
|
+
depthTest={materialProps.current?.depthTest ?? true}
|
|
188
|
+
/>
|
|
189
|
+
|
|
190
|
+
{#if geo && (renderMode.includes('colliders') || !model)}
|
|
191
|
+
<T.LineSegments
|
|
192
|
+
raycast={() => null}
|
|
193
|
+
bvh={{ enabled: false }}
|
|
194
|
+
>
|
|
195
|
+
<T.EdgesGeometry args={[geo, 0]} />
|
|
196
|
+
<T.LineBasicMaterial color={darkenColor(color, 10)} />
|
|
197
|
+
</T.LineSegments>
|
|
198
|
+
{/if}
|
|
199
199
|
{/if}
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
</T>
|
|
201
|
+
{/if}
|
|
202
202
|
{:else if showAxesHelper.current}
|
|
203
203
|
<AxesHelper
|
|
204
|
-
name={
|
|
204
|
+
name={entity}
|
|
205
205
|
width={3}
|
|
206
206
|
length={0.1}
|
|
207
207
|
/>
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import { provide3DModels } from '../hooks/use3DModels.svelte'
|
|
24
24
|
import { providePointcloudObjects } from '../hooks/usePointcloudObjects.svelte'
|
|
25
25
|
import { provideLinkedEntities } from '../hooks/useLinked.svelte'
|
|
26
|
+
import { provideConfigFrames } from '../hooks/useConfigFrames.svelte'
|
|
26
27
|
|
|
27
28
|
interface Props {
|
|
28
29
|
cameraPose?: CameraPose
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
provideDrawAPI()
|
|
44
45
|
|
|
45
46
|
provideResourceByName(() => partID.current)
|
|
47
|
+
provideConfigFrames()
|
|
46
48
|
provideFrames(() => partID.current)
|
|
47
49
|
provideGeometries(() => partID.current)
|
|
48
50
|
provide3DModels(() => partID.current)
|
|
@@ -15,19 +15,17 @@
|
|
|
15
15
|
const selectedObject3d = useSelectedObject3d()
|
|
16
16
|
|
|
17
17
|
const object = $derived.by(() => {
|
|
18
|
-
if (!
|
|
19
|
-
return
|
|
18
|
+
if (!selectedObject3d.current) {
|
|
19
|
+
return
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// Create a clone in the case of meshes, which could be frames with geometries,
|
|
23
23
|
// so that our bounding box doesn't include children
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (result) {
|
|
27
|
-
selectedObject3d.current?.getWorldPosition(result.position)
|
|
28
|
-
selectedObject3d.current?.getWorldQuaternion(result.quaternion)
|
|
29
|
-
return result
|
|
24
|
+
if (isInstanceOf(selectedObject3d.current, 'Mesh')) {
|
|
25
|
+
return selectedObject3d.current?.clone(false)
|
|
30
26
|
}
|
|
27
|
+
|
|
28
|
+
return selectedObject3d.current
|
|
31
29
|
})
|
|
32
30
|
|
|
33
31
|
const { start, stop } = useTask(
|
|
@@ -44,7 +42,9 @@
|
|
|
44
42
|
mesh.getBoundingBoxAt(selectedEntity.instance, box3)
|
|
45
43
|
obb.fromBox3(box3)
|
|
46
44
|
obbHelper.setFromOBB(obb)
|
|
47
|
-
} else {
|
|
45
|
+
} else if (isInstanceOf(selectedObject3d.current, 'Mesh')) {
|
|
46
|
+
selectedObject3d.current?.getWorldPosition(object.position)
|
|
47
|
+
selectedObject3d.current?.getWorldQuaternion(object.quaternion)
|
|
48
48
|
obbHelper.setFromObject(object)
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
const entity = world.spawn(
|
|
40
40
|
traits.Name(`custom geometry ${++index}`),
|
|
41
41
|
traits.Pose,
|
|
42
|
-
traits.Box({ x:
|
|
42
|
+
traits.Box({ x: 100, y: 100, z: 100 }),
|
|
43
43
|
traits.Removable
|
|
44
44
|
)
|
|
45
45
|
|
|
@@ -87,7 +87,10 @@
|
|
|
87
87
|
ref.quaternion.copy(quaternion)
|
|
88
88
|
entity.set(traits.Pose, pose)
|
|
89
89
|
} else if (box && mode === 'scale') {
|
|
90
|
-
|
|
90
|
+
box.x *= ref.scale.x
|
|
91
|
+
box.y *= ref.scale.y
|
|
92
|
+
box.z *= ref.scale.z
|
|
93
|
+
entity.set(traits.Box, box)
|
|
91
94
|
ref.scale.setScalar(1)
|
|
92
95
|
}
|
|
93
96
|
}}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
useFocusedObject3d,
|
|
22
22
|
useSelectedObject3d,
|
|
23
23
|
} from '../../hooks/useSelection.svelte'
|
|
24
|
-
import {
|
|
24
|
+
import { useConfigFrames } from '../../hooks/useConfigFrames.svelte'
|
|
25
25
|
import { usePartConfig } from '../../hooks/usePartConfig.svelte'
|
|
26
26
|
import { FrameConfigUpdater } from '../../FrameConfigUpdater.svelte'
|
|
27
27
|
import { useEnvironment } from '../../hooks/useEnvironment.svelte'
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
const world = useWorld()
|
|
38
38
|
const controls = useCameraControls()
|
|
39
39
|
const resourceByName = useResourceByName()
|
|
40
|
-
const
|
|
40
|
+
const configFrames = useConfigFrames()
|
|
41
41
|
const partConfig = usePartConfig()
|
|
42
42
|
const selectedEntity = useSelectedEntity()
|
|
43
43
|
const selectedObject3d = useSelectedObject3d()
|
|
@@ -402,7 +402,7 @@
|
|
|
402
402
|
{@render ParentFrame({
|
|
403
403
|
ariaLabel: 'parent frame name',
|
|
404
404
|
value: parent.current ?? 'world',
|
|
405
|
-
options:
|
|
405
|
+
options: configFrames.getParentFrameOptions(name.current ?? ''),
|
|
406
406
|
onChange: (value) => {
|
|
407
407
|
detailConfigUpdater.setFrameParent(entity, value)
|
|
408
408
|
},
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
defaultSize?: { width: number; height: number }
|
|
11
11
|
defaultPosition?: { x: number; y: number }
|
|
12
12
|
exitable?: boolean
|
|
13
|
+
persistRect?: boolean
|
|
13
14
|
strategy?: 'absolute' | 'fixed'
|
|
14
15
|
isOpen?: boolean
|
|
15
16
|
children: Snippet
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
title = '',
|
|
20
21
|
defaultSize = { width: 700, height: 500 },
|
|
21
22
|
exitable = true,
|
|
23
|
+
persistRect = true,
|
|
22
24
|
isOpen = $bindable(false),
|
|
23
25
|
children,
|
|
24
26
|
...props
|
|
@@ -30,6 +32,7 @@
|
|
|
30
32
|
defaultSize,
|
|
31
33
|
resizable: false,
|
|
32
34
|
allowOverflow: false,
|
|
35
|
+
persistRect,
|
|
33
36
|
open: isOpen,
|
|
34
37
|
...props,
|
|
35
38
|
}))
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Portal } from '@threlte/extras'
|
|
3
|
+
import { useLogs } from '../../hooks/useLogs.svelte'
|
|
4
|
+
import FloatingPanel from './FloatingPanel.svelte'
|
|
5
|
+
import DashboardButton from './dashboard/Button.svelte'
|
|
6
|
+
import { PersistedState } from 'runed'
|
|
7
|
+
|
|
8
|
+
const logs = useLogs()
|
|
9
|
+
|
|
10
|
+
const isOpen = new PersistedState('logs-is-open', false)
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<Portal id="dashboard">
|
|
14
|
+
<fieldset class="relative">
|
|
15
|
+
<DashboardButton
|
|
16
|
+
active
|
|
17
|
+
icon="article"
|
|
18
|
+
description="Logs"
|
|
19
|
+
onclick={() => {
|
|
20
|
+
isOpen.current = !isOpen.current
|
|
21
|
+
}}
|
|
22
|
+
/>
|
|
23
|
+
{#if logs.warnings.length > 0}
|
|
24
|
+
<span
|
|
25
|
+
class="absolute z-4 -mt-1.5 -ml-1.5 h-4 w-4 rounded-full bg-yellow-700 text-center text-[10px] text-white"
|
|
26
|
+
>
|
|
27
|
+
{logs.warnings.length}
|
|
28
|
+
</span>
|
|
29
|
+
{/if}
|
|
30
|
+
|
|
31
|
+
{#if logs.errors.length > 0}
|
|
32
|
+
<span
|
|
33
|
+
class="absolute z-4 -mt-1.5 -ml-1.5 h-4 rounded-full bg-red-700 px-1.25 text-center text-[10px] text-white"
|
|
34
|
+
>
|
|
35
|
+
{logs.errors.length}
|
|
36
|
+
</span>
|
|
37
|
+
{/if}
|
|
38
|
+
</fieldset>
|
|
39
|
+
</Portal>
|
|
40
|
+
|
|
41
|
+
<FloatingPanel
|
|
42
|
+
title="Logs"
|
|
43
|
+
bind:isOpen={isOpen.current}
|
|
44
|
+
defaultSize={{ width: 240, height: 315 }}
|
|
45
|
+
>
|
|
46
|
+
<div class="flex h-70 flex-col gap-2 overflow-auto p-3 text-xs">
|
|
47
|
+
{#each logs.current as log (log.uuid)}
|
|
48
|
+
<div>
|
|
49
|
+
<div class="flex flex-wrap items-center gap-1.5">
|
|
50
|
+
<div
|
|
51
|
+
class={[
|
|
52
|
+
'h-2 w-2 rounded-full',
|
|
53
|
+
{
|
|
54
|
+
'bg-danger-dark': log.level === 'error',
|
|
55
|
+
'bg-amber-300': log.level === 'warn',
|
|
56
|
+
'bg-blue-400': log.level === 'info',
|
|
57
|
+
},
|
|
58
|
+
]}
|
|
59
|
+
></div>
|
|
60
|
+
<div class="text-subtle-2">{log.timestamp}</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div>
|
|
63
|
+
{#if log.count > 1}
|
|
64
|
+
<span class="mr-1 rounded bg-green-700 px-1 py-0.5 text-xs text-white">
|
|
65
|
+
{log.count}
|
|
66
|
+
</span>
|
|
67
|
+
{/if}
|
|
68
|
+
{log.message}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
{:else}
|
|
72
|
+
No logs
|
|
73
|
+
{/each}
|
|
74
|
+
</div>
|
|
75
|
+
</FloatingPanel>
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import Tree from './Tree.svelte'
|
|
4
4
|
import { useSelectedEntity } from '../../../hooks/useSelection.svelte'
|
|
5
5
|
import { provideTreeExpandedContext } from './useExpanded.svelte'
|
|
6
|
-
import Logs from './Logs.svelte'
|
|
7
6
|
import AddFrames from './AddFrames.svelte'
|
|
8
7
|
import { useEnvironment } from '../../../hooks/useEnvironment.svelte'
|
|
9
8
|
import { usePartID } from '../../../hooks/usePartID.svelte'
|
|
@@ -82,6 +81,4 @@
|
|
|
82
81
|
{#if environment.current.isStandalone && partID.current && partConfig.hasEditPermissions}
|
|
83
82
|
<AddFrames />
|
|
84
83
|
{/if}
|
|
85
|
-
|
|
86
|
-
<Logs />
|
|
87
84
|
</div>
|
|
@@ -42,7 +42,6 @@ export const provide3DModels = (partID) => {
|
|
|
42
42
|
gltfModel.scene.traverse((object) => {
|
|
43
43
|
if (isInstanceOf(object, 'Mesh')) {
|
|
44
44
|
const { material } = object;
|
|
45
|
-
console.log(material);
|
|
46
45
|
if (isInstanceOf(material, 'MeshStandardMaterial')) {
|
|
47
46
|
material.roughness = 0.3;
|
|
48
47
|
material.metalness = 0.1;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Transform } from '@viamrobotics/sdk';
|
|
2
|
+
interface ConfigFramesContext {
|
|
3
|
+
unsetFrames: string[];
|
|
4
|
+
current: Record<string, Transform>;
|
|
5
|
+
getParentFrameOptions: (componentName: string) => string[];
|
|
6
|
+
}
|
|
7
|
+
export declare const provideConfigFrames: () => void;
|
|
8
|
+
export declare const useConfigFrames: () => ConfigFramesContext;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useEnvironment } from './useEnvironment.svelte';
|
|
2
|
+
import { usePartConfig } from './usePartConfig.svelte';
|
|
3
|
+
import { createTransformFromFrame } from '../frame';
|
|
4
|
+
import { Transform } from '@viamrobotics/sdk';
|
|
5
|
+
import { getContext, setContext } from 'svelte';
|
|
6
|
+
const key = Symbol('config-frames-context');
|
|
7
|
+
export const provideConfigFrames = () => {
|
|
8
|
+
const environment = useEnvironment();
|
|
9
|
+
const partConfig = usePartConfig();
|
|
10
|
+
$effect(() => {
|
|
11
|
+
if (partConfig.isDirty) {
|
|
12
|
+
environment.current.viewerMode = 'edit';
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
environment.current.viewerMode = 'monitor';
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
const [configFrames, configUnsetFrameNames] = $derived.by(() => {
|
|
19
|
+
const { components } = partConfig.current;
|
|
20
|
+
const results = {};
|
|
21
|
+
const unsetResults = [];
|
|
22
|
+
for (const { name, frame } of components ?? []) {
|
|
23
|
+
if (!frame) {
|
|
24
|
+
unsetResults.push(name);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
results[name] = createTransformFromFrame(name, frame);
|
|
28
|
+
}
|
|
29
|
+
return [results, unsetResults];
|
|
30
|
+
});
|
|
31
|
+
const [fragmentFrames, fragmentUnsetFrameNames] = $derived.by(() => {
|
|
32
|
+
const { fragment_mods: fragmentMods = [] } = partConfig.current;
|
|
33
|
+
const fragmentDefinedComponents = Object.keys(partConfig.componentNameToFragmentId);
|
|
34
|
+
const results = {};
|
|
35
|
+
const unsetResults = [];
|
|
36
|
+
// deal with fragment defined components
|
|
37
|
+
for (const fragmentComponentName of fragmentDefinedComponents || []) {
|
|
38
|
+
const fragmentId = partConfig.componentNameToFragmentId[fragmentComponentName];
|
|
39
|
+
const fragmentMod = fragmentMods?.find((mod) => mod.fragment_id === fragmentId);
|
|
40
|
+
if (!fragmentMod) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const setComponentModIndex = fragmentMod.mods.findLastIndex((mod) => mod['$set']?.[`components.${fragmentComponentName}.frame`] !== undefined);
|
|
44
|
+
const unsetComponentModIndex = fragmentMod.mods.findLastIndex((mod) => mod['$unset']?.[`components.${fragmentComponentName}.frame`] !== undefined);
|
|
45
|
+
if (setComponentModIndex < unsetComponentModIndex) {
|
|
46
|
+
unsetResults.push(fragmentComponentName);
|
|
47
|
+
}
|
|
48
|
+
else if (unsetComponentModIndex < setComponentModIndex) {
|
|
49
|
+
const frameData = fragmentMod.mods[setComponentModIndex]['$set'][`components.${fragmentComponentName}.frame`];
|
|
50
|
+
results[fragmentComponentName] = createTransformFromFrame(fragmentComponentName, frameData);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return [results, unsetResults];
|
|
54
|
+
});
|
|
55
|
+
const frames = $derived.by(() => {
|
|
56
|
+
const result = {
|
|
57
|
+
...configFrames,
|
|
58
|
+
...fragmentFrames,
|
|
59
|
+
};
|
|
60
|
+
return result;
|
|
61
|
+
});
|
|
62
|
+
const frameValues = $derived(Object.values(frames));
|
|
63
|
+
const getParentFrameOptions = (componentName) => {
|
|
64
|
+
const validFrames = new Set(frameValues.map((frame) => frame.referenceFrame));
|
|
65
|
+
validFrames.add('world');
|
|
66
|
+
const frameNameQueue = [componentName];
|
|
67
|
+
while (frameNameQueue.length > 0) {
|
|
68
|
+
const frameName = frameNameQueue.shift();
|
|
69
|
+
if (frameName) {
|
|
70
|
+
validFrames.delete(frameName);
|
|
71
|
+
const filteredFrames = frameValues.filter((frame) => frame.poseInObserverFrame?.referenceFrame === frameName);
|
|
72
|
+
for (const frame of filteredFrames) {
|
|
73
|
+
frameNameQueue.push(frame.referenceFrame);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return Array.from(validFrames);
|
|
78
|
+
};
|
|
79
|
+
const unsetFrames = $derived([...new Set([...configUnsetFrameNames, ...fragmentUnsetFrameNames])]);
|
|
80
|
+
setContext(key, {
|
|
81
|
+
getParentFrameOptions,
|
|
82
|
+
get unsetFrames() {
|
|
83
|
+
return unsetFrames;
|
|
84
|
+
},
|
|
85
|
+
get current() {
|
|
86
|
+
return frames;
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
export const useConfigFrames = () => {
|
|
91
|
+
return getContext(key);
|
|
92
|
+
};
|
|
@@ -12,7 +12,7 @@ export const provideFramelessComponents = () => {
|
|
|
12
12
|
.map((component) => component.name) ?? [];
|
|
13
13
|
const fragmentComponentsWithNoFrame = [];
|
|
14
14
|
for (const fragmentComponentName of Object.keys(partConfig.componentNameToFragmentId)) {
|
|
15
|
-
if (frames.current.find((frame) => frame.
|
|
15
|
+
if (frames.current.find((frame) => frame.referenceFrame === fragmentComponentName)) {
|
|
16
16
|
continue;
|
|
17
17
|
}
|
|
18
18
|
fragmentComponentsWithNoFrame.push(fragmentComponentName);
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { Transform } from '@viamrobotics/sdk';
|
|
2
|
-
interface FrameTransform {
|
|
3
|
-
type: 'machine' | 'config' | 'fragment';
|
|
4
|
-
transform: Transform;
|
|
5
|
-
}
|
|
6
2
|
interface FramesContext {
|
|
7
|
-
current:
|
|
8
|
-
getParentFrameOptions: (componentName: string) => string[];
|
|
3
|
+
current: Transform[];
|
|
9
4
|
}
|
|
10
5
|
export declare const provideFrames: (partID: () => string) => void;
|
|
11
6
|
export declare const useFrames: () => FramesContext;
|
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
import { getContext, setContext, untrack } from 'svelte';
|
|
2
|
-
import { Transform } from '@viamrobotics/sdk';
|
|
3
|
-
import { useRobotClient, createRobotQuery, useMachineStatus } from '@viamrobotics/svelte-sdk';
|
|
2
|
+
import { MachineConnectionEvent, Transform } from '@viamrobotics/sdk';
|
|
3
|
+
import { useRobotClient, createRobotQuery, useMachineStatus, useConnectionStatus, } from '@viamrobotics/svelte-sdk';
|
|
4
4
|
import { useLogs } from './useLogs.svelte';
|
|
5
5
|
import { resourceNameToColor } from '../color';
|
|
6
|
-
import { createTransformFromFrame } from '../frame';
|
|
7
|
-
import { usePartConfig } from './usePartConfig.svelte';
|
|
8
6
|
import { useEnvironment } from './useEnvironment.svelte';
|
|
9
7
|
import { createPose } from '../transform';
|
|
10
8
|
import { useResourceByName } from './useResourceByName.svelte';
|
|
11
9
|
import { traits, useWorld } from '../ecs';
|
|
10
|
+
import { useConfigFrames } from './useConfigFrames.svelte';
|
|
12
11
|
const key = Symbol('frames-context');
|
|
13
12
|
export const provideFrames = (partID) => {
|
|
13
|
+
const configFrames = useConfigFrames();
|
|
14
14
|
const environment = useEnvironment();
|
|
15
15
|
const world = useWorld();
|
|
16
16
|
const resourceByName = useResourceByName();
|
|
17
17
|
const client = useRobotClient(partID);
|
|
18
|
+
const connectionStatus = useConnectionStatus(partID);
|
|
18
19
|
const machineStatus = useMachineStatus(partID);
|
|
19
20
|
const logs = useLogs();
|
|
21
|
+
const isEditMode = $derived(environment.current.viewerMode === 'edit');
|
|
20
22
|
const query = createRobotQuery(client, 'frameSystemConfig', () => ({
|
|
21
|
-
enabled: partID() !== '' &&
|
|
23
|
+
enabled: partID() !== '' && !isEditMode,
|
|
22
24
|
}));
|
|
23
25
|
const revision = $derived(machineStatus.current?.config?.revision);
|
|
24
|
-
const partConfig = usePartConfig();
|
|
25
26
|
$effect(() => {
|
|
26
27
|
if (query.isFetching) {
|
|
27
28
|
logs.add('Fetching frames...');
|
|
@@ -30,131 +31,40 @@ export const provideFrames = (partID) => {
|
|
|
30
31
|
logs.add(`Frames: ${query.error.message}`, 'error');
|
|
31
32
|
}
|
|
32
33
|
});
|
|
33
|
-
$
|
|
34
|
-
if (partConfig.isDirty) {
|
|
35
|
-
environment.current.viewerMode = 'edit';
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
environment.current.viewerMode = 'monitor';
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
const machineFrames = $derived.by(() => {
|
|
34
|
+
const frames = $derived.by(() => {
|
|
42
35
|
const frames = {};
|
|
43
36
|
for (const { frame } of query.data ?? []) {
|
|
44
37
|
if (frame === undefined) {
|
|
45
38
|
continue;
|
|
46
39
|
}
|
|
47
|
-
frames[frame.referenceFrame] =
|
|
48
|
-
type: 'machine',
|
|
49
|
-
transform: frame,
|
|
50
|
-
};
|
|
40
|
+
frames[frame.referenceFrame] = frame;
|
|
51
41
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const results = {};
|
|
57
|
-
const unsetResults = [];
|
|
58
|
-
for (const { name, frame } of components ?? []) {
|
|
59
|
-
if (!frame) {
|
|
60
|
-
unsetResults.push(name);
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
results[name] = {
|
|
64
|
-
type: 'config',
|
|
65
|
-
transform: createTransformFromFrame(name, frame),
|
|
42
|
+
if (isEditMode || connectionStatus.current === MachineConnectionEvent.DISCONNECTED) {
|
|
43
|
+
const mergedFrames = {
|
|
44
|
+
...frames,
|
|
45
|
+
...configFrames.current,
|
|
66
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* Remove frames that have just been deleted locally for optimistic updates,
|
|
49
|
+
* or frames that have been removed by fragment overrides
|
|
50
|
+
*/
|
|
51
|
+
for (const name of configFrames.unsetFrames) {
|
|
52
|
+
delete mergedFrames[name];
|
|
53
|
+
}
|
|
54
|
+
return mergedFrames;
|
|
67
55
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const unsetResults = [];
|
|
75
|
-
// deal with fragment defined components
|
|
76
|
-
for (const fragmentComponentName of fragmentDefinedComponents || []) {
|
|
77
|
-
const fragmentId = partConfig.componentNameToFragmentId[fragmentComponentName];
|
|
78
|
-
const fragmentMod = fragmentMods?.find((mod) => mod.fragment_id === fragmentId);
|
|
79
|
-
if (!fragmentMod) {
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
const setComponentModIndex = fragmentMod.mods.findLastIndex((mod) => mod['$set']?.[`components.${fragmentComponentName}.frame`] !== undefined);
|
|
83
|
-
const unsetComponentModIndex = fragmentMod.mods.findLastIndex((mod) => mod['$unset']?.[`components.${fragmentComponentName}.frame`] !== undefined);
|
|
84
|
-
if (setComponentModIndex < unsetComponentModIndex) {
|
|
85
|
-
unsetResults.push(fragmentComponentName);
|
|
86
|
-
}
|
|
87
|
-
else if (unsetComponentModIndex < setComponentModIndex) {
|
|
88
|
-
const frameData = fragmentMod.mods[setComponentModIndex]['$set'][`components.${fragmentComponentName}.frame`];
|
|
89
|
-
results[fragmentComponentName] = {
|
|
90
|
-
type: 'fragment',
|
|
91
|
-
transform: createTransformFromFrame(fragmentComponentName, frameData),
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return [results, unsetResults];
|
|
96
|
-
});
|
|
97
|
-
const frames = $derived.by(() => {
|
|
98
|
-
const result = {
|
|
99
|
-
...machineFrames,
|
|
100
|
-
...configFrames,
|
|
101
|
-
...fragmentFrames,
|
|
102
|
-
};
|
|
103
|
-
// Remove frames that have just been deleted locally for optimistic updates
|
|
104
|
-
for (const name of configUnsetFrameNames) {
|
|
105
|
-
delete result[name];
|
|
106
|
-
}
|
|
107
|
-
// Remove frames that have been removed by fragment overrides
|
|
108
|
-
for (const name of fragmentUnsetFrameNames) {
|
|
109
|
-
delete result[name];
|
|
110
|
-
}
|
|
111
|
-
return result;
|
|
56
|
+
/**
|
|
57
|
+
* If we're not in edit mode and we have a robot connection,
|
|
58
|
+
* we only use frames reported by the machine
|
|
59
|
+
*
|
|
60
|
+
*/
|
|
61
|
+
return frames;
|
|
112
62
|
});
|
|
113
63
|
const current = $derived(Object.values(frames));
|
|
114
64
|
const entities = new Map();
|
|
115
65
|
$effect.pre(() => {
|
|
116
66
|
if (revision) {
|
|
117
|
-
untrack(
|
|
118
|
-
await query.refetch();
|
|
119
|
-
for (const [name, machineFrame] of Object.entries(machineFrames)) {
|
|
120
|
-
if (machineFrame === undefined) {
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
const existing = entities.get(name);
|
|
124
|
-
if (existing) {
|
|
125
|
-
const pose = createPose(machineFrame.transform.poseInObserverFrame?.pose);
|
|
126
|
-
existing.set(traits.Pose, pose);
|
|
127
|
-
if (environment.current.viewerMode === 'monitor') {
|
|
128
|
-
// if we are in monitor mode, we want the network pose to overwrite any leftover edited poses
|
|
129
|
-
existing.set(traits.EditedPose, pose);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
$effect.pre(() => {
|
|
137
|
-
for (const [name, configFrame] of Object.entries(configFrames)) {
|
|
138
|
-
if (configFrame === undefined) {
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
const existing = entities.get(name);
|
|
142
|
-
if (existing) {
|
|
143
|
-
const pose = createPose(configFrame.transform.poseInObserverFrame?.pose);
|
|
144
|
-
existing.set(traits.EditedPose, pose);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
$effect.pre(() => {
|
|
149
|
-
for (const [name, fragmentFrame] of Object.entries(fragmentFrames)) {
|
|
150
|
-
if (fragmentFrame === undefined) {
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
const existing = entities.get(name);
|
|
154
|
-
if (existing) {
|
|
155
|
-
const pose = createPose(fragmentFrame.transform.poseInObserverFrame?.pose);
|
|
156
|
-
existing.set(traits.EditedPose, pose);
|
|
157
|
-
}
|
|
67
|
+
untrack(() => query.refetch());
|
|
158
68
|
}
|
|
159
69
|
});
|
|
160
70
|
$effect.pre(() => {
|
|
@@ -162,13 +72,13 @@ export const provideFrames = (partID) => {
|
|
|
162
72
|
if (frame === undefined) {
|
|
163
73
|
continue;
|
|
164
74
|
}
|
|
165
|
-
const name = frame.
|
|
166
|
-
const parent = frame.
|
|
167
|
-
const pose = createPose(frame.
|
|
168
|
-
const center = frame.
|
|
169
|
-
? createPose(frame.
|
|
75
|
+
const name = frame.referenceFrame;
|
|
76
|
+
const parent = frame.poseInObserverFrame?.referenceFrame;
|
|
77
|
+
const pose = createPose(frame.poseInObserverFrame?.pose);
|
|
78
|
+
const center = frame.physicalObject?.center
|
|
79
|
+
? createPose(frame.physicalObject.center)
|
|
170
80
|
: undefined;
|
|
171
|
-
const resourceName = resourceByName.current[frame.
|
|
81
|
+
const resourceName = resourceByName.current[frame.referenceFrame];
|
|
172
82
|
const color = resourceNameToColor(resourceName);
|
|
173
83
|
const existing = entities.get(name);
|
|
174
84
|
if (existing) {
|
|
@@ -188,10 +98,11 @@ export const provideFrames = (partID) => {
|
|
|
188
98
|
existing.set(traits.Center, center);
|
|
189
99
|
}
|
|
190
100
|
existing.remove(traits.Box, traits.Sphere, traits.BufferGeometry, traits.Capsule);
|
|
191
|
-
if (frame.
|
|
192
|
-
const geometry = traits.Geometry(frame.
|
|
101
|
+
if (frame.physicalObject) {
|
|
102
|
+
const geometry = traits.Geometry(frame.physicalObject);
|
|
193
103
|
existing.add(geometry);
|
|
194
104
|
}
|
|
105
|
+
existing.set(traits.EditedPose, pose);
|
|
195
106
|
continue;
|
|
196
107
|
}
|
|
197
108
|
const entityTraits = [
|
|
@@ -210,8 +121,8 @@ export const provideFrames = (partID) => {
|
|
|
210
121
|
if (center) {
|
|
211
122
|
entityTraits.push(traits.Center(center));
|
|
212
123
|
}
|
|
213
|
-
if (frame.
|
|
214
|
-
entityTraits.push(traits.Geometry(frame.
|
|
124
|
+
if (frame.physicalObject) {
|
|
125
|
+
entityTraits.push(traits.Geometry(frame.physicalObject));
|
|
215
126
|
}
|
|
216
127
|
const entity = world.spawn(...entityTraits);
|
|
217
128
|
entities.set(name, entity);
|
|
@@ -224,24 +135,7 @@ export const provideFrames = (partID) => {
|
|
|
224
135
|
}
|
|
225
136
|
}
|
|
226
137
|
});
|
|
227
|
-
const getParentFrameOptions = (componentName) => {
|
|
228
|
-
const validFrames = new Set(current.map((frame) => frame.transform.referenceFrame));
|
|
229
|
-
validFrames.add('world');
|
|
230
|
-
const frameNameQueue = [componentName];
|
|
231
|
-
while (frameNameQueue.length > 0) {
|
|
232
|
-
const frameName = frameNameQueue.shift();
|
|
233
|
-
if (frameName) {
|
|
234
|
-
validFrames.delete(frameName);
|
|
235
|
-
const frames = current.filter((frame) => frame.transform.poseInObserverFrame?.referenceFrame === frameName);
|
|
236
|
-
for (const frame of frames) {
|
|
237
|
-
frameNameQueue.push(frame.transform.referenceFrame);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return Array.from(validFrames);
|
|
242
|
-
};
|
|
243
138
|
setContext(key, {
|
|
244
|
-
getParentFrameOptions,
|
|
245
139
|
get current() {
|
|
246
140
|
return current;
|
|
247
141
|
},
|
|
@@ -126,7 +126,7 @@ export const providePartConfig = (partID, params) => {
|
|
|
126
126
|
};
|
|
127
127
|
const deletePartFrame = (componentName) => {
|
|
128
128
|
const newConfig = getCurrent();
|
|
129
|
-
const component = newConfig?.components?.find((
|
|
129
|
+
const component = newConfig?.components?.find(({ name }) => name === componentName);
|
|
130
130
|
if (!component) {
|
|
131
131
|
return;
|
|
132
132
|
}
|
|
@@ -1,20 +1,8 @@
|
|
|
1
|
-
import { get, set } from 'idb-keyval';
|
|
2
1
|
import { getContext, setContext } from 'svelte';
|
|
3
2
|
import { SvelteMap } from 'svelte/reactivity';
|
|
4
3
|
const key = Symbol('object-visibility-context');
|
|
5
|
-
const idbKey = 'object-visibility';
|
|
6
4
|
export const provideVisibility = () => {
|
|
7
5
|
const map = new SvelteMap();
|
|
8
|
-
get(idbKey).then((entries) => {
|
|
9
|
-
if (entries) {
|
|
10
|
-
for (const [key, value] of entries) {
|
|
11
|
-
map.set(key, value);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
$effect(() => {
|
|
16
|
-
set(idbKey, [...map.entries()]);
|
|
17
|
-
});
|
|
18
6
|
setContext(key, map);
|
|
19
7
|
};
|
|
20
8
|
export const useVisibility = () => {
|
package/package.json
CHANGED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { useLogs } from '../../../hooks/useLogs.svelte'
|
|
3
|
-
import Drawer from './Drawer.svelte'
|
|
4
|
-
|
|
5
|
-
const logs = useLogs()
|
|
6
|
-
</script>
|
|
7
|
-
|
|
8
|
-
<Drawer name="Logs">
|
|
9
|
-
{#snippet titleAlert()}
|
|
10
|
-
{#if logs.warnings.length > 0}
|
|
11
|
-
<span class="mr-1 rounded bg-yellow-700 px-1 py-0.5 text-xs text-white">
|
|
12
|
-
{logs.warnings.length}
|
|
13
|
-
</span>
|
|
14
|
-
{/if}
|
|
15
|
-
|
|
16
|
-
{#if logs.errors.length > 0}
|
|
17
|
-
<span class="mr-1 rounded bg-red-700 px-1 py-0.5 text-xs text-white">
|
|
18
|
-
{logs.errors.length}
|
|
19
|
-
</span>
|
|
20
|
-
{/if}
|
|
21
|
-
{/snippet}
|
|
22
|
-
|
|
23
|
-
<div class="flex h-64 flex-col gap-2 overflow-auto p-3">
|
|
24
|
-
{#each logs.current as log (log.uuid)}
|
|
25
|
-
<div>
|
|
26
|
-
<div class="flex flex-wrap items-center gap-1.5">
|
|
27
|
-
<div
|
|
28
|
-
class={[
|
|
29
|
-
'h-2 w-2 rounded-full',
|
|
30
|
-
{
|
|
31
|
-
'bg-danger-dark': log.level === 'error',
|
|
32
|
-
'bg-amber-300': log.level === 'warn',
|
|
33
|
-
'bg-blue-400': log.level === 'info',
|
|
34
|
-
},
|
|
35
|
-
]}
|
|
36
|
-
></div>
|
|
37
|
-
<div class="text-subtle-2">{log.timestamp}</div>
|
|
38
|
-
</div>
|
|
39
|
-
<div>
|
|
40
|
-
{#if log.count > 1}
|
|
41
|
-
<span class="mr-1 rounded bg-green-700 px-1 py-0.5 text-xs text-white">
|
|
42
|
-
{log.count}
|
|
43
|
-
</span>
|
|
44
|
-
{/if}
|
|
45
|
-
{log.message}
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
{:else}
|
|
49
|
-
No logs
|
|
50
|
-
{/each}
|
|
51
|
-
</div>
|
|
52
|
-
</Drawer>
|
|
File without changes
|