@viamrobotics/motion-tools 1.12.2 → 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.
Files changed (29) hide show
  1. package/dist/assets/ferndale_studio_11_1k.hdr +0 -0
  2. package/dist/components/App.svelte +11 -25
  3. package/dist/components/App.svelte.d.ts +3 -3
  4. package/dist/components/Geometry.svelte +42 -42
  5. package/dist/components/Scene.svelte +4 -1
  6. package/dist/components/SceneProviders.svelte +2 -0
  7. package/dist/components/Selected.svelte +9 -9
  8. package/dist/components/StaticGeometries.svelte +5 -2
  9. package/dist/components/overlay/Details.svelte +3 -3
  10. package/dist/components/overlay/FloatingPanel.svelte +3 -0
  11. package/dist/components/overlay/FloatingPanel.svelte.d.ts +1 -0
  12. package/dist/components/overlay/LiveUpdatesBanner.svelte +3 -3
  13. package/dist/components/overlay/Logs.svelte +75 -0
  14. package/dist/components/overlay/__tests__/__fixtures__/resource.d.ts +10 -1
  15. package/dist/components/overlay/__tests__/__fixtures__/resource.js +1 -0
  16. package/dist/components/overlay/dashboard/Dashboard.svelte +1 -1
  17. package/dist/components/overlay/left-pane/TreeContainer.svelte +0 -3
  18. package/dist/hooks/use3DModels.svelte.js +10 -0
  19. package/dist/hooks/useConfigFrames.svelte.d.ts +9 -0
  20. package/dist/hooks/useConfigFrames.svelte.js +92 -0
  21. package/dist/hooks/useFramelessComponents.svelte.js +2 -2
  22. package/dist/hooks/useFrames.svelte.d.ts +1 -6
  23. package/dist/hooks/useFrames.svelte.js +39 -145
  24. package/dist/hooks/usePartConfig.svelte.d.ts +10 -19
  25. package/dist/hooks/usePartConfig.svelte.js +138 -165
  26. package/dist/hooks/useVisibility.svelte.js +0 -12
  27. package/package.json +1 -1
  28. package/dist/components/overlay/left-pane/Logs.svelte +0 -52
  29. /package/dist/components/overlay/{left-pane/Logs.svelte.d.ts → Logs.svelte.d.ts} +0 -0
@@ -17,7 +17,6 @@
17
17
  import FileDrop from './FileDrop/FileDrop.svelte'
18
18
  import { provideWeblabs } from '../hooks/useWeblabs.svelte'
19
19
  import { providePartConfig } from '../hooks/usePartConfig.svelte'
20
- import { useViamClient } from '@viamrobotics/svelte-sdk'
21
20
  import LiveUpdatesBanner from './overlay/LiveUpdatesBanner.svelte'
22
21
  import ArmPositions from './overlay/widgets/ArmPositions.svelte'
23
22
  import { provideEnvironment } from '../hooks/useEnvironment.svelte'
@@ -31,12 +30,13 @@
31
30
  import HoveredEntities from './hover/HoveredEntities.svelte'
32
31
  import Settings from './overlay/settings/Settings.svelte'
33
32
  import { useXR } from '@threlte/xr'
33
+ import Logs from './overlay/Logs.svelte'
34
34
 
35
35
  interface LocalConfigProps {
36
- getLocalPartConfig: () => Struct
36
+ current: Struct
37
+ isDirty: boolean
38
+ componentToFragId: Record<string, string>
37
39
  setLocalPartConfig: (config: Struct) => void
38
- isDirty: () => boolean
39
- getComponentToFragId: () => Record<string, string>
40
40
  }
41
41
 
42
42
  interface Props {
@@ -65,7 +65,6 @@
65
65
 
66
66
  provideWorld()
67
67
 
68
- const appClient = useViamClient()
69
68
  const settings = provideSettings()
70
69
  const environment = provideEnvironment()
71
70
  const currentRobotCameraWidgets = $derived(settings.current.openCameraWidgets[partID] || [])
@@ -82,25 +81,10 @@
82
81
 
83
82
  let root = $state.raw<HTMLElement>()
84
83
 
85
- providePartConfig(() => {
86
- if (localConfigProps) {
87
- return {
88
- appEmbeddedPartConfigProps: {
89
- isDirty: () => localConfigProps.isDirty(),
90
- getLocalPartConfig: () => localConfigProps.getLocalPartConfig(),
91
- setLocalPartConfig: (config: Struct) => localConfigProps.setLocalPartConfig(config),
92
- getComponentToFragId: () => localConfigProps.getComponentToFragId(),
93
- },
94
- }
95
- } else {
96
- return {
97
- standalonePartConfigProps: {
98
- viamClient: () => appClient?.current,
99
- partID: () => partID,
100
- },
101
- }
102
- }
103
- })
84
+ providePartConfig(
85
+ () => partID,
86
+ () => localConfigProps
87
+ )
104
88
 
105
89
  $effect.pre(() => {
106
90
  if (localConfigProps) {
@@ -137,7 +121,6 @@
137
121
  <FileDrop />
138
122
  <Dashboard {dashboard} />
139
123
  <Details />
140
- <Settings />
141
124
 
142
125
  {#if environment.current.isStandalone}
143
126
  <LiveUpdatesBanner />
@@ -158,6 +141,9 @@
158
141
  {/if}
159
142
 
160
143
  <PortalTarget id="dom" />
144
+
145
+ <Settings />
146
+ <Logs />
161
147
  </div>
162
148
  {/snippet}
163
149
  </SceneProviders>
@@ -3,10 +3,10 @@ import type { Struct } from '@viamrobotics/sdk';
3
3
  import type { CameraPose } from '../hooks/useControls.svelte';
4
4
  import { type DrawConnectionConfig } from '../hooks/useDrawConnectionConfig.svelte';
5
5
  interface LocalConfigProps {
6
- getLocalPartConfig: () => Struct;
6
+ current: Struct;
7
+ isDirty: boolean;
8
+ componentToFragId: Record<string, string>;
7
9
  setLocalPartConfig: (config: Struct) => void;
8
- isDirty: () => boolean;
9
- getComponentToFragId: () => Record<string, string>;
10
10
  }
11
11
  interface Props {
12
12
  partID?: string;
@@ -135,17 +135,17 @@
135
135
  />
136
136
  {/if}
137
137
 
138
- <T
139
- is={mesh}
140
- name={entity}
141
- userData.name={name}
142
- renderOrder={renderOrder.current}
143
- >
144
- {#if model && renderMode.includes('model')}
145
- <T is={model} />
146
- {/if}
147
-
148
- {#if !model || renderMode.includes('colliders')}
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
- {/if}
172
-
173
- {#if linePositions.current}
174
- <T
175
- is={LineMaterial}
176
- {color}
177
- width={lineWidth.current ? lineWidth.current * 0.001 : 0.5}
178
- depthTest={materialProps.current?.depthTest}
179
- />
180
- {:else}
181
- {@const currentOpacity = opacity.current ?? 0.7}
182
- <T.MeshToonMaterial
183
- {color}
184
- side={geometryType === 'buffer' ? DoubleSide : FrontSide}
185
- transparent={currentOpacity < 1}
186
- depthWrite={currentOpacity === 1}
187
- opacity={currentOpacity}
188
- depthTest={materialProps.current?.depthTest}
189
- />
190
-
191
- {#if geo && (renderMode.includes('colliders') || !model)}
192
- <T.LineSegments
193
- raycast={() => null}
194
- bvh={{ enabled: false }}
195
- >
196
- <T.EdgesGeometry args={[geo, 0]} />
197
- <T.LineBasicMaterial color={darkenColor(color, 10)} />
198
- </T.LineSegments>
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
- {/if}
201
- </T>
200
+ </T>
201
+ {/if}
202
202
  {:else if showAxesHelper.current}
203
203
  <AxesHelper
204
- name={name.current}
204
+ name={entity}
205
205
  width={3}
206
206
  length={0.1}
207
207
  />
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { ShaderMaterial, Vector3 } from 'three'
3
3
  import { T } from '@threlte/core'
4
- import { Grid, interactivity, PerfMonitor, PortalTarget } from '@threlte/extras'
4
+ import { Environment, Grid, interactivity, PerfMonitor, PortalTarget } from '@threlte/extras'
5
5
  import Entities from './Entities.svelte'
6
6
  import Selected from './Selected.svelte'
7
7
  import Focus from './Focus.svelte'
@@ -18,6 +18,7 @@
18
18
  import PointerMissBox from './PointerMissBox.svelte'
19
19
  import BatchedArrows from './BatchedArrows.svelte'
20
20
  import Arrows from './Arrows/ArrowGroups.svelte'
21
+ import hdrImage from '../assets/ferndale_studio_11_1k.hdr'
21
22
 
22
23
  interface Props {
23
24
  children?: Snippet
@@ -54,6 +55,8 @@
54
55
  <PerfMonitor anchorX="right" />
55
56
  {/if}
56
57
 
58
+ <Environment url={hdrImage} />
59
+
57
60
  <T.Group
58
61
  position={origin.position}
59
62
  rotation.x={$isPresenting ? -Math.PI / 2 : 0}
@@ -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 (!isInstanceOf(selectedObject3d.current, 'Mesh')) {
19
- return selectedObject3d.current
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
- const result = selectedObject3d.current?.clone(false)
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: 0.1, y: 0.1, z: 0.1 }),
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
- entity.set(traits.Box, ref.scale)
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 { useFrames } from '../../hooks/useFrames.svelte'
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 frames = useFrames()
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: frames.getParentFrameOptions(name.current ?? ''),
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
  }))
@@ -10,6 +10,7 @@ interface Props {
10
10
  y: number;
11
11
  };
12
12
  exitable?: boolean;
13
+ persistRect?: boolean;
13
14
  strategy?: 'absolute' | 'fixed';
14
15
  isOpen?: boolean;
15
16
  children: Snippet;
@@ -17,7 +17,7 @@
17
17
  if (event.key.toLowerCase() === 's') {
18
18
  event.preventDefault()
19
19
  event.stopImmediatePropagation()
20
- partConfig.saveLocalPartConfig()
20
+ partConfig.save()
21
21
  }
22
22
  }
23
23
  }}
@@ -43,7 +43,7 @@
43
43
  <Button
44
44
  class="cursor-pointer text-blue-600"
45
45
  onclick={() => {
46
- partConfig.resetLocalPartConfig()
46
+ partConfig.discardChanges()
47
47
  }}
48
48
  >
49
49
  Discard
@@ -54,7 +54,7 @@
54
54
  aria-label="Save"
55
55
  class="cursor-pointer text-blue-600"
56
56
  onclick={() => {
57
- partConfig.saveLocalPartConfig()
57
+ partConfig.save()
58
58
  }}
59
59
  >
60
60
  <div class="flex gap-2">
@@ -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>
@@ -7,8 +7,17 @@ export declare const resource: {
7
7
  y: number;
8
8
  z: number;
9
9
  };
10
+ orientation: {
11
+ type: "ov_degrees";
12
+ value: {
13
+ x: number;
14
+ y: number;
15
+ z: number;
16
+ th: number;
17
+ };
18
+ };
10
19
  geometry: {
11
- type: string;
20
+ type: "box";
12
21
  x: number;
13
22
  y: number;
14
23
  z: number;
@@ -3,6 +3,7 @@ export const resource = {
3
3
  frame: {
4
4
  parent: 'parent_frame',
5
5
  translation: { x: 10, y: 20, z: 30 },
6
+ orientation: { type: 'ov_degrees', value: { x: 0, y: 0, z: 1, th: 0 } },
6
7
  geometry: {
7
8
  type: 'box',
8
9
  x: 10,
@@ -9,7 +9,7 @@
9
9
  </script>
10
10
 
11
11
  <div
12
- class="absolute top-2 z-4 flex w-full justify-center gap-2"
12
+ class="absolute top-2 z-4 flex w-full items-center justify-center gap-2"
13
13
  {...rest}
14
14
  >
15
15
  <!-- camera view -->
@@ -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>
@@ -4,6 +4,7 @@ import { getContext, setContext } from 'svelte';
4
4
  import { useSettings } from './useSettings.svelte';
5
5
  import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
6
6
  import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
7
+ import { isInstanceOf } from '@threlte/core';
7
8
  const gltfLoader = new GLTFLoader();
8
9
  const dracoLoader = new DRACOLoader();
9
10
  dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.6/');
@@ -38,6 +39,15 @@ export const provide3DModels = (partID) => {
38
39
  const arrayBuffer = model.mesh.buffer.slice(model.mesh.byteOffset, model.mesh.byteOffset + model.mesh.byteLength);
39
40
  const gltfModel = await gltfLoader.parseAsync(arrayBuffer, '');
40
41
  next[prefix][id] = gltfModel.scene;
42
+ gltfModel.scene.traverse((object) => {
43
+ if (isInstanceOf(object, 'Mesh')) {
44
+ const { material } = object;
45
+ if (isInstanceOf(material, 'MeshStandardMaterial')) {
46
+ material.roughness = 0.3;
47
+ material.metalness = 0.1;
48
+ }
49
+ }
50
+ });
41
51
  }
42
52
  current = next;
43
53
  }
@@ -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
+ };
@@ -6,13 +6,13 @@ export const provideFramelessComponents = () => {
6
6
  const partConfig = usePartConfig();
7
7
  const frames = useFrames();
8
8
  const current = $derived.by(() => {
9
- const components = partConfig.localPartConfig.toJson()?.components;
9
+ const { components } = partConfig.current;
10
10
  const partComponentsWIthNoFrame = components
11
11
  ?.filter((component) => component.frame === undefined)
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.transform.referenceFrame === fragmentComponentName)) {
15
+ if (frames.current.find((frame) => frame.referenceFrame === fragmentComponentName)) {
16
16
  continue;
17
17
  }
18
18
  fragmentComponentsWithNoFrame.push(fragmentComponentName);