@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.
@@ -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
- <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 ?? true}
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 ?? true}
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
  />
@@ -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;
@@ -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.transform.referenceFrame === fragmentComponentName)) {
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: FrameTransform[];
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() !== '' && environment.current.viewerMode === 'monitor',
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
- $effect(() => {
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
- return frames;
53
- });
54
- const [configFrames, configUnsetFrameNames] = $derived.by(() => {
55
- const { components } = partConfig.current;
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
- return [results, unsetResults];
69
- });
70
- const [fragmentFrames, fragmentUnsetFrameNames] = $derived.by(() => {
71
- const { fragment_mods: fragmentMods = [] } = partConfig.current;
72
- const fragmentDefinedComponents = Object.keys(partConfig.componentNameToFragmentId);
73
- const results = {};
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(async () => {
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.transform.referenceFrame;
166
- const parent = frame.transform.poseInObserverFrame?.referenceFrame;
167
- const pose = createPose(frame.transform.poseInObserverFrame?.pose);
168
- const center = frame.transform.physicalObject?.center
169
- ? createPose(frame.transform.physicalObject.center)
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.transform.referenceFrame];
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.transform.physicalObject) {
192
- const geometry = traits.Geometry(frame.transform.physicalObject);
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.transform.physicalObject) {
214
- entityTraits.push(traits.Geometry(frame.transform.physicalObject));
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((comp) => comp.name === componentName);
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "1.12.3",
3
+ "version": "1.13.0",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -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>