@viamrobotics/motion-tools 1.29.0 → 1.30.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 (32) hide show
  1. package/dist/components/App.svelte +4 -10
  2. package/dist/components/App.svelte.d.ts +2 -1
  3. package/dist/components/Entities/Arrows/Arrows.svelte +1 -1
  4. package/dist/components/Entities/Frame.svelte +1 -1
  5. package/dist/components/Entities/GLTF.svelte +1 -1
  6. package/dist/components/Entities/Geometry.svelte +1 -1
  7. package/dist/components/Entities/Line.svelte +1 -1
  8. package/dist/components/Entities/Points.svelte +1 -1
  9. package/dist/components/Entities/hooks/useEntityEvents.svelte.js +1 -1
  10. package/dist/components/SceneProviders.svelte +2 -0
  11. package/dist/components/SelectedTransformControls.svelte +9 -1
  12. package/dist/components/overlay/AddFrames.svelte +1 -1
  13. package/dist/components/overlay/Details.svelte +17 -1
  14. package/dist/components/overlay/Logs.svelte +1 -1
  15. package/dist/components/overlay/dashboard/Button.svelte +1 -1
  16. package/dist/components/overlay/left-pane/TreeNode.svelte +3 -2
  17. package/dist/components/overlay/settings/Settings.svelte +1 -5
  18. package/dist/ecs/traits.d.ts +7 -0
  19. package/dist/ecs/traits.js +7 -0
  20. package/dist/hooks/useConfigFrames.svelte.js +3 -3
  21. package/dist/hooks/useFramelessComponents.svelte.js +1 -1
  22. package/dist/hooks/useInheritedInvisible.svelte.d.ts +3 -0
  23. package/dist/hooks/useInheritedInvisible.svelte.js +89 -0
  24. package/dist/hooks/usePartConfig.svelte.d.ts +6 -2
  25. package/dist/hooks/usePartConfig.svelte.js +23 -11
  26. package/dist/hooks/useSettings.svelte.d.ts +0 -1
  27. package/dist/hooks/useSettings.svelte.js +0 -1
  28. package/dist/plugins/Debug/Debug.svelte +5 -0
  29. package/dist/plugins/Debug/Debug.svelte.d.ts +18 -0
  30. package/dist/plugins/index.d.ts +1 -0
  31. package/dist/plugins/index.js +2 -0
  32. package/package.json +8 -2
@@ -3,7 +3,6 @@
3
3
  import type { Entity } from 'koota'
4
4
  import type { Snippet } from 'svelte'
5
5
 
6
- import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'
7
6
  import { Canvas } from '@threlte/core'
8
7
  import { PortalTarget } from '@threlte/extras'
9
8
  import { useXR } from '@threlte/xr'
@@ -11,6 +10,8 @@
11
10
  import { primeTheme } from '@viamrobotics/tweakpane-config'
12
11
  import { ThemeUtils } from 'svelte-tweakpane-ui'
13
12
 
13
+ import type { FragmentInfo } from '../hooks/usePartConfig.svelte'
14
+
14
15
  import Controls from './overlay/controls/Controls.svelte'
15
16
  import Dashboard from './overlay/dashboard/Dashboard.svelte'
16
17
  import Details from './overlay/Details.svelte'
@@ -40,7 +41,7 @@
40
41
  interface LocalConfigProps {
41
42
  current: Struct
42
43
  isDirty: boolean
43
- componentToFragId: Record<string, string>
44
+ componentNameToFragmentInfo: Record<string, FragmentInfo>
44
45
  setLocalPartConfig: (config: Struct) => void
45
46
  }
46
47
 
@@ -111,18 +112,11 @@
111
112
  })
112
113
  </script>
113
114
 
114
- {#if settings.current.enableQueryDevtools}
115
- <SvelteQueryDevtools initialIsOpen />
116
- {/if}
117
-
118
115
  <div
119
116
  class="relative h-full w-full overflow-hidden dark:bg-white"
120
117
  bind:this={root}
121
118
  >
122
- <Canvas
123
- renderMode="on-demand"
124
- dpr={[1, 2]}
125
- >
119
+ <Canvas renderMode="on-demand">
126
120
  <SceneProviders>
127
121
  {#snippet children({ focus })}
128
122
  <Scene>
@@ -1,11 +1,12 @@
1
1
  import type { Struct } from '@viamrobotics/sdk';
2
2
  import type { Entity } from 'koota';
3
3
  import type { Snippet } from 'svelte';
4
+ import type { FragmentInfo } from '../hooks/usePartConfig.svelte';
4
5
  import { type CameraPose } from '../hooks/useControls.svelte';
5
6
  interface LocalConfigProps {
6
7
  current: Struct;
7
8
  isDirty: boolean;
8
- componentToFragId: Record<string, string>;
9
+ componentNameToFragmentInfo: Record<string, FragmentInfo>;
9
10
  setLocalPartConfig: (config: Struct) => void;
10
11
  }
11
12
  interface Props {
@@ -20,7 +20,7 @@
20
20
 
21
21
  const { invalidate } = useThrelte()
22
22
  const worldMatrix = useTrait(() => entity, traits.WorldMatrix)
23
- const invisible = useTrait(() => entity, traits.Invisible)
23
+ const invisible = useTrait(() => entity, traits.InheritedInvisible)
24
24
  const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
25
25
 
26
26
  const events = useEntityEvents(() => entity)
@@ -39,7 +39,7 @@ Renders a Viam Frame object
39
39
  const entityColor = useTrait(() => entity, traits.Color)
40
40
  const worldMatrix = useTrait(() => entity, traits.WorldMatrix)
41
41
  const center = useTrait(() => entity, traits.Center)
42
- const invisible = useTrait(() => entity, traits.Invisible)
42
+ const invisible = useTrait(() => entity, traits.InheritedInvisible)
43
43
 
44
44
  const events = useEntityEvents(() => entity)
45
45
 
@@ -35,7 +35,7 @@
35
35
 
36
36
  const worldMatrix = useTrait(() => entity, traits.WorldMatrix)
37
37
  const gltfTrait = useTrait(() => entity, traits.GLTF)
38
- const invisible = useTrait(() => entity, traits.Invisible)
38
+ const invisible = useTrait(() => entity, traits.InheritedInvisible)
39
39
  const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
40
40
  const events = useEntityEvents(() => entity)
41
41
 
@@ -33,7 +33,7 @@ Renders a Viam Geometry object
33
33
  const name = useTrait(() => entity, traits.Name)
34
34
  const worldMatrix = useTrait(() => entity, traits.WorldMatrix)
35
35
  const center = useTrait(() => entity, traits.Center)
36
- const invisible = useTrait(() => entity, traits.Invisible)
36
+ const invisible = useTrait(() => entity, traits.InheritedInvisible)
37
37
 
38
38
  const model = $derived.by(() => {
39
39
  if (!settings.current.renderArmModels.includes('model')) {
@@ -34,7 +34,7 @@
34
34
  const renderOrder = useTrait(() => entity, traits.RenderOrder)
35
35
  const opacity = useTrait(() => entity, traits.Opacity)
36
36
  const screenSpace = useTrait(() => entity, traits.ScreenSpace)
37
- const invisible = useTrait(() => entity, traits.Invisible)
37
+ const invisible = useTrait(() => entity, traits.InheritedInvisible)
38
38
  const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
39
39
 
40
40
  const events = useEntityEvents(() => entity)
@@ -28,7 +28,7 @@
28
28
  const colors = useTrait(() => entity, traits.Colors)
29
29
  const entityPointSize = useTrait(() => entity, traits.PointSize)
30
30
  const opacity = useTrait(() => entity, traits.Opacity)
31
- const invisible = useTrait(() => entity, traits.Invisible)
31
+ const invisible = useTrait(() => entity, traits.InheritedInvisible)
32
32
  const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
33
33
  const renderOrder = useTrait(() => entity, traits.RenderOrder)
34
34
  const materialProps = useTrait(() => entity, traits.Material)
@@ -18,7 +18,7 @@ export const useEntityEvents = (entity) => {
18
18
  const selectedEntity = useSelectedEntity();
19
19
  const focusedEntity = useFocusedEntity();
20
20
  const cursor = useCursor();
21
- const invisible = useTrait(entity, traits.Invisible);
21
+ const invisible = useTrait(entity, traits.InheritedInvisible);
22
22
  const onpointerenter = (event) => {
23
23
  if (invisible.current)
24
24
  return;
@@ -11,6 +11,7 @@
11
11
  import { provideFramelessComponents } from '../hooks/useFramelessComponents.svelte'
12
12
  import { provideFrames } from '../hooks/useFrames.svelte'
13
13
  import { provideGeometries } from '../hooks/useGeometries.svelte'
14
+ import { provideInheritedInvisible } from '../hooks/useInheritedInvisible.svelte'
14
15
  import { provideLinkedEntities } from '../hooks/useLinked.svelte'
15
16
  import { provideLogs } from '../hooks/useLogs.svelte'
16
17
  import { usePartID } from '../hooks/usePartID.svelte'
@@ -36,6 +37,7 @@
36
37
 
37
38
  provideHierarchy()
38
39
  provideWorldMatrix()
40
+ provideInheritedInvisible()
39
41
  provideOrigin()
40
42
 
41
43
  provideRelationships()
@@ -8,6 +8,7 @@
8
8
  import { useTransformControls } from '../hooks/useControls.svelte'
9
9
  import { useEnvironment } from '../hooks/useEnvironment.svelte'
10
10
  import { useFrameEditSession } from '../hooks/useFrameEditSession.svelte'
11
+ import { usePartConfig } from '../hooks/usePartConfig.svelte'
11
12
  import { useSelectedEntity, useSelectedObject3d } from '../hooks/useSelection.svelte'
12
13
  import { useSettings } from '../hooks/useSettings.svelte'
13
14
  import {
@@ -21,6 +22,7 @@
21
22
 
22
23
  const settings = useSettings()
23
24
  const environment = useEnvironment()
25
+ const partConfig = usePartConfig()
24
26
  const transformControls = useTransformControls()
25
27
  const selectedEntity = useSelectedEntity()
26
28
  const selectedObject3d = useSelectedObject3d()
@@ -29,14 +31,20 @@
29
31
  const mode = $derived(settings.current.transformMode)
30
32
  const entity = $derived(selectedEntity.current)
31
33
  const transformable = useTrait(() => entity, traits.Transformable)
34
+ const invisible = useTrait(() => entity, traits.InheritedInvisible)
32
35
  const configMatrix = useTrait(() => entity, traits.Matrix)
33
36
  const liveMatrix = useTrait(() => entity, traits.LiveMatrix)
34
37
  const box = useTrait(() => entity, traits.Box)
35
38
  const sphere = useTrait(() => entity, traits.Sphere)
36
39
  const capsule = useTrait(() => entity, traits.Capsule)
40
+ const name = useTrait(() => entity, traits.Name)
37
41
  const hasScalableGeometry = $derived(
38
42
  box.current !== undefined || sphere.current !== undefined || capsule.current !== undefined
39
43
  )
44
+ const isFragmentComponentWithVariables = $derived(
45
+ name.current &&
46
+ Object.keys(partConfig.componentNameToFragmentInfo[name.current]?.variables ?? {}).length > 0
47
+ )
40
48
 
41
49
  // Mesh sets name={entity} on its inner mesh, so useSelectedObject3d resolves
42
50
  // to that mesh — not the parent Frame Group we actually want to drive. Walk
@@ -241,7 +249,7 @@
241
249
  }
242
250
  </script>
243
251
 
244
- {#if ref && entity && activeMode}
252
+ {#if ref && entity && activeMode && !isFragmentComponentWithVariables && !invisible.current}
245
253
  {#key entity}
246
254
  <TransformControls
247
255
  object={ref}
@@ -22,7 +22,7 @@
22
22
  <Portal id="dashboard">
23
23
  <fieldset>
24
24
  <DashboardButton
25
- active
25
+ active={isOpen}
26
26
  icon="axis-arrow"
27
27
  description="Add frames"
28
28
  onclick={() => {
@@ -109,7 +109,13 @@
109
109
 
110
110
  const isFrameNode = $derived(!!framesAPI.current)
111
111
  const isGeometry = $derived(!!geometriesAPI.current)
112
- const showEditFrameOptions = $derived(isFrameNode && partConfig.hasEditPermissions)
112
+ const isFragmentComponentWithVariables = $derived(
113
+ name.current &&
114
+ Object.keys(partConfig.componentNameToFragmentInfo[name.current]?.variables ?? {}).length > 0
115
+ )
116
+ const showEditFrameOptions = $derived(
117
+ isFrameNode && partConfig.hasEditPermissions && !isFragmentComponentWithVariables
118
+ )
113
119
  const showRelationshipOptions = $derived(points.current || arrows.current)
114
120
  const resourceName = $derived(name.current ? resourceByName.current[name.current] : undefined)
115
121
  const displayType = $derived(isFrameNode ? resourceName?.subtype : isGeometry ? 'geometry' : '')
@@ -406,6 +412,16 @@
406
412
 
407
413
  <div class="border-medium -mx-2 w-[100%+0.5rem] border-b"></div>
408
414
 
415
+ {#if isFragmentComponentWithVariables}
416
+ <p
417
+ class="mt-2 rounded border-l-4 border-yellow-600 bg-yellow-50 px-2 py-1.5 text-yellow-900"
418
+ data-testid="fragment-variables-warning"
419
+ role="status"
420
+ >
421
+ This component is from a fragment with variables, editing frames in 3D scene is disabled
422
+ </p>
423
+ {/if}
424
+
409
425
  <h3
410
426
  class="text-subtle-2 flex justify-between py-2"
411
427
  data-testid="details-header"
@@ -21,7 +21,7 @@
21
21
  <Portal id="dashboard">
22
22
  <fieldset class="relative">
23
23
  <DashboardButton
24
- active
24
+ active={isOpen.current}
25
25
  icon="article"
26
26
  description="Logs"
27
27
  onclick={() => {
@@ -34,7 +34,7 @@
34
34
  class={[
35
35
  className,
36
36
  'relative block rounded-md border',
37
- active ? 'border-gray-5 text-gray-8 z-4 bg-white' : 'bg-light border-medium text-disabled',
37
+ active ? 'z-4 border-[#666] bg-[#666] text-white' : 'border-gray-5 text-gray-8 bg-white',
38
38
  ]}
39
39
  aria-describedby={tooltipID}
40
40
  >
@@ -20,6 +20,7 @@
20
20
 
21
21
  const name = useTrait(() => node.entity, traits.Name)
22
22
  const invisible = useTrait(() => node.entity, traits.Invisible)
23
+ const inheritedInvisible = useTrait(() => node.entity, traits.InheritedInvisible)
23
24
  const chunkProgress = useTrait(() => node.entity, traits.ChunkProgress)
24
25
  const loading = $derived(chunkProgress.current !== undefined)
25
26
  const progress = $derived(
@@ -55,7 +56,7 @@
55
56
  class={[
56
57
  'w-full',
57
58
  {
58
- 'text-disabled': invisible.current,
59
+ 'text-disabled': inheritedInvisible.current,
59
60
  'bg-medium': nodeState.selected,
60
61
  sticky: true,
61
62
  },
@@ -129,7 +130,7 @@
129
130
  <div
130
131
  class={{
131
132
  'flex justify-between': true,
132
- 'text-disabled': invisible.current,
133
+ 'text-disabled': inheritedInvisible.current,
133
134
  'bg-medium': nodeState.selected,
134
135
  }}
135
136
  {...api.getItemProps(nodeProps)}
@@ -62,7 +62,7 @@
62
62
  <Portal id="dashboard">
63
63
  <fieldset>
64
64
  <DashboardButton
65
- active
65
+ active={isOpen.current}
66
66
  icon="cog"
67
67
  description="Settings"
68
68
  onclick={() => {
@@ -278,10 +278,6 @@
278
278
 
279
279
  {#snippet Stats()}
280
280
  <div class="flex w-full flex-col gap-2.5 text-xs">
281
- <label class="flex items-center justify-between gap-2">
282
- Query devtools <Switch bind:on={settings.current.enableQueryDevtools} />
283
- </label>
284
-
285
281
  <label class="flex items-center justify-between gap-2">
286
282
  Render stats <Switch bind:on={settings.current.renderStats} />
287
283
  </label>
@@ -65,6 +65,13 @@ export declare const InstancedMatrix: import("koota").Trait<() => {
65
65
  }>;
66
66
  export declare const Hovered: import("koota").Trait<() => boolean>;
67
67
  export declare const Invisible: import("koota").Trait<() => boolean>;
68
+ /**
69
+ * True when the entity itself, or any of its parents up the `ChildOf`
70
+ * chain, has `Invisible`. Maintained by `provideInheritedInvisible`;
71
+ * don't add or remove it by hand — toggle `Invisible` and the cascade
72
+ * follows.
73
+ */
74
+ export declare const InheritedInvisible: import("koota").Trait<() => boolean>;
68
75
  /**
69
76
  * Represents that an entity is composed of many instances, so that the treeview and
70
77
  * details panel may display all instances
@@ -61,6 +61,13 @@ export const InstancedMatrix = trait(() => ({
61
61
  }));
62
62
  export const Hovered = trait(() => true);
63
63
  export const Invisible = trait(() => true);
64
+ /**
65
+ * True when the entity itself, or any of its parents up the `ChildOf`
66
+ * chain, has `Invisible`. Maintained by `provideInheritedInvisible`;
67
+ * don't add or remove it by hand — toggle `Invisible` and the cascade
68
+ * follows.
69
+ */
70
+ export const InheritedInvisible = trait(() => true);
64
71
  /**
65
72
  * Represents that an entity is composed of many instances, so that the treeview and
66
73
  * details panel may display all instances
@@ -25,12 +25,12 @@ export const provideConfigFrames = () => {
25
25
  });
26
26
  const [fragmentFrames, fragmentUnsetFrameNames] = $derived.by(() => {
27
27
  const { fragment_mods: fragmentMods = [] } = partConfig.current;
28
- const fragmentDefinedComponents = Object.keys(partConfig.componentNameToFragmentId);
28
+ const fragmentDefinedComponents = Object.keys(partConfig.componentNameToFragmentInfo);
29
29
  const results = {};
30
30
  const unsetResults = [];
31
31
  // deal with fragment defined components
32
32
  for (const fragmentComponentName of fragmentDefinedComponents || []) {
33
- const fragmentId = partConfig.componentNameToFragmentId[fragmentComponentName];
33
+ const fragmentId = partConfig.componentNameToFragmentInfo[fragmentComponentName].id;
34
34
  const fragmentMod = fragmentMods?.find((mod) => mod.fragment_id === fragmentId);
35
35
  if (!fragmentMod) {
36
36
  continue;
@@ -64,7 +64,7 @@ export const provideConfigFrames = () => {
64
64
  * any whose frame the user has $unset.
65
65
  */
66
66
  const unsetFragmentNames = new Set(fragmentUnsetFrameNames);
67
- for (const name of Object.keys(partConfig.componentNameToFragmentId)) {
67
+ for (const name of Object.keys(partConfig.componentNameToFragmentInfo)) {
68
68
  if (!unsetFragmentNames.has(name)) {
69
69
  validFrames.add(name);
70
70
  }
@@ -11,7 +11,7 @@ export const provideFramelessComponents = () => {
11
11
  ?.filter((component) => component.frame === undefined)
12
12
  .map((component) => component.name) ?? [];
13
13
  const fragmentComponentsWithNoFrame = new Set(partComponentsWIthNoFrame);
14
- for (const fragmentComponentName of Object.keys(partConfig.componentNameToFragmentId)) {
14
+ for (const fragmentComponentName of Object.keys(partConfig.componentNameToFragmentInfo)) {
15
15
  if (frames.current.some((frame) => frame.referenceFrame === fragmentComponentName)) {
16
16
  continue;
17
17
  }
@@ -0,0 +1,3 @@
1
+ import { type World } from 'koota';
2
+ export declare const addInheritedInvisibleListeners: (world: World) => () => void;
3
+ export declare const provideInheritedInvisible: () => void;
@@ -0,0 +1,89 @@
1
+ import {} from 'koota';
2
+ import { ChildOf } from '../ecs/relations';
3
+ import { InheritedInvisible, Invisible } from '../ecs/traits';
4
+ import { useWorld } from '../ecs/useWorld';
5
+ /**
6
+ * Walks up `ChildOf` and returns true if the entity itself or any
7
+ * ancestor has `Invisible`. Memoizes via `cache` so siblings in the
8
+ * same flush reuse a parent's result.
9
+ */
10
+ const hasInherited = (entity, cache) => {
11
+ const cached = cache.get(entity);
12
+ if (cached !== undefined)
13
+ return cached;
14
+ if (!entity.isAlive())
15
+ return false;
16
+ if (entity.has(Invisible)) {
17
+ cache.set(entity, true);
18
+ return true;
19
+ }
20
+ const parent = entity.targetFor(ChildOf);
21
+ const inherited = parent && parent.isAlive() ? hasInherited(parent, cache) : false;
22
+ cache.set(entity, inherited);
23
+ return inherited;
24
+ };
25
+ const flushDirty = (world, dirty) => {
26
+ if (dirty.size === 0)
27
+ return;
28
+ const cache = new Map();
29
+ const allEntities = new Set();
30
+ const collectChildren = (entity) => {
31
+ if (allEntities.has(entity))
32
+ return;
33
+ allEntities.add(entity);
34
+ for (const child of world.query(ChildOf(entity))) {
35
+ collectChildren(child);
36
+ }
37
+ };
38
+ for (const entity of dirty) {
39
+ collectChildren(entity);
40
+ }
41
+ dirty.clear();
42
+ for (const entity of allEntities) {
43
+ if (!entity.isAlive()) {
44
+ continue;
45
+ }
46
+ const hasInheritedTrait = hasInherited(entity, cache);
47
+ const hasTrait = entity.has(InheritedInvisible);
48
+ if (hasInheritedTrait && !hasTrait) {
49
+ entity.add(InheritedInvisible);
50
+ }
51
+ else if (!hasInheritedTrait && hasTrait) {
52
+ entity.remove(InheritedInvisible);
53
+ }
54
+ }
55
+ };
56
+ export const addInheritedInvisibleListeners = (world) => {
57
+ const dirty = new Set();
58
+ let scheduled = false;
59
+ const enqueue = (entity) => {
60
+ dirty.add(entity);
61
+ if (scheduled)
62
+ return;
63
+ scheduled = true;
64
+ // Microtask-deferred so a burst of changes is grouped into one subtree walk.
65
+ queueMicrotask(() => {
66
+ scheduled = false;
67
+ flushDirty(world, dirty);
68
+ });
69
+ };
70
+ for (const entity of world.query(Invisible)) {
71
+ enqueue(entity);
72
+ }
73
+ const unsubs = [
74
+ world.onAdd(Invisible, enqueue),
75
+ world.onRemove(Invisible, enqueue),
76
+ world.onAdd(ChildOf, enqueue),
77
+ world.onChange(ChildOf, enqueue),
78
+ world.onRemove(ChildOf, enqueue),
79
+ ];
80
+ return () => {
81
+ for (const unsub of unsubs) {
82
+ unsub();
83
+ }
84
+ };
85
+ };
86
+ export const provideInheritedInvisible = () => {
87
+ const world = useWorld();
88
+ $effect(() => addInheritedInvisibleListeners(world));
89
+ };
@@ -11,12 +11,16 @@ export interface PartConfig {
11
11
  mods: any[];
12
12
  }[];
13
13
  }
14
+ export type FragmentInfo = {
15
+ id: string;
16
+ variables: Record<string, string>;
17
+ };
14
18
  interface PartConfigContext {
15
19
  current: PartConfig;
16
20
  isDirty: boolean;
17
21
  hasPendingSave: boolean;
18
22
  hasEditPermissions: boolean;
19
- componentNameToFragmentId: Record<string, string>;
23
+ componentNameToFragmentInfo: Record<string, FragmentInfo>;
20
24
  updateFrame: (componentName: string, referenceFrame: string, pose: Pose, geometry?: Frame['geometry']) => void;
21
25
  deleteFrame: (componentName: string) => void;
22
26
  createFrame: (componentName: string) => void;
@@ -30,7 +34,7 @@ export declare const usePartConfig: () => PartConfigContext;
30
34
  interface AppEmbeddedPartConfigProps {
31
35
  current: Struct;
32
36
  isDirty: boolean;
33
- componentToFragId: Record<string, string>;
37
+ componentNameToFragmentInfo: Record<string, FragmentInfo>;
34
38
  setLocalPartConfig: (config: Struct) => void;
35
39
  }
36
40
  export {};
@@ -155,8 +155,8 @@ export const providePartConfig = (partID, params) => {
155
155
  get current() {
156
156
  return current;
157
157
  },
158
- get componentNameToFragmentId() {
159
- return config.componentNameToFragmentId;
158
+ get componentNameToFragmentInfo() {
159
+ return config.componentNameToFragmentInfo;
160
160
  },
161
161
  get isDirty() {
162
162
  return config.isDirty;
@@ -168,7 +168,7 @@ export const providePartConfig = (partID, params) => {
168
168
  return config.hasEditPermissions;
169
169
  },
170
170
  updateFrame: (componentName, referenceFrame, framePosition, frameGeometry) => {
171
- const fragmentId = config.componentNameToFragmentId[componentName];
171
+ const fragmentId = config.componentNameToFragmentInfo[componentName]?.id;
172
172
  if (fragmentId === undefined) {
173
173
  updatePartFrame(componentName, referenceFrame, framePosition, frameGeometry);
174
174
  }
@@ -177,7 +177,7 @@ export const providePartConfig = (partID, params) => {
177
177
  }
178
178
  },
179
179
  deleteFrame: (componentName) => {
180
- const fragmentId = config.componentNameToFragmentId[componentName];
180
+ const fragmentId = config.componentNameToFragmentInfo[componentName]?.id;
181
181
  if (fragmentId === undefined) {
182
182
  deletePartFrame(componentName);
183
183
  }
@@ -186,7 +186,7 @@ export const providePartConfig = (partID, params) => {
186
186
  }
187
187
  },
188
188
  createFrame: (componentName) => {
189
- const fragmentId = config.componentNameToFragmentId[componentName];
189
+ const fragmentId = config.componentNameToFragmentInfo[componentName]?.id;
190
190
  if (fragmentId === undefined) {
191
191
  createPartFrame(componentName);
192
192
  }
@@ -244,8 +244,8 @@ const useEmbeddedPartConfig = (props) => {
244
244
  get current() {
245
245
  return props.current ?? new Struct();
246
246
  },
247
- get componentNameToFragmentId() {
248
- return props.componentToFragId;
247
+ get componentNameToFragmentInfo() {
248
+ return props.componentNameToFragmentInfo;
249
249
  },
250
250
  set(config) {
251
251
  const struct = Struct.fromJson(config);
@@ -287,7 +287,16 @@ const useStandalonePartConfig = (partID) => {
287
287
  const id = typeof fragmentId === 'string' ? fragmentId : fragmentId.id;
288
288
  return createAppQuery('getFragment', () => [id], { refetchInterval: false });
289
289
  }));
290
- const componentNameToFragmentId = $derived.by(() => {
290
+ const fragmentIdToVariables = $derived.by(() => {
291
+ const results = {};
292
+ for (const fragment of configJSON?.fragments ?? []) {
293
+ const id = typeof fragment === 'string' ? fragment : fragment.id;
294
+ const variables = typeof fragment === 'string' ? {} : fragment.variables;
295
+ results[id] = variables;
296
+ }
297
+ return results;
298
+ });
299
+ const componentNameToFragmentInfo = $derived.by(() => {
291
300
  const results = {};
292
301
  for (const query of fragmentQueries) {
293
302
  if (!query.data) {
@@ -300,7 +309,10 @@ const useStandalonePartConfig = (partID) => {
300
309
  if (component.kind.case === 'structValue') {
301
310
  const componentName = component.kind.value.fields['name']?.kind;
302
311
  if (componentName.case === 'stringValue') {
303
- results[componentName.value] = fragmentId;
312
+ results[componentName.value] = {
313
+ id: fragmentId,
314
+ variables: fragmentIdToVariables[fragmentId] ?? {},
315
+ };
304
316
  }
305
317
  }
306
318
  }
@@ -340,8 +352,8 @@ const useStandalonePartConfig = (partID) => {
340
352
  get hasEditPermissions() {
341
353
  return hasEditPermissions;
342
354
  },
343
- get componentNameToFragmentId() {
344
- return componentNameToFragmentId;
355
+ get componentNameToFragmentInfo() {
356
+ return componentNameToFragmentInfo;
345
357
  },
346
358
  set(config) {
347
359
  current = Struct.fromJson(config);
@@ -23,7 +23,6 @@ export interface Settings {
23
23
  enableMeasureAxisY: boolean;
24
24
  enableMeasureAxisZ: boolean;
25
25
  enableLabels: boolean;
26
- enableQueryDevtools: boolean;
27
26
  enableArmPositionsWidget: boolean;
28
27
  openCameraWidgets: Record<string, string[]>;
29
28
  openFramePovWidgets: Record<string, string[]>;
@@ -30,7 +30,6 @@ const defaults = () => ({
30
30
  enableMeasureAxisY: true,
31
31
  enableMeasureAxisZ: true,
32
32
  enableLabels: false,
33
- enableQueryDevtools: false,
34
33
  enableArmPositionsWidget: false,
35
34
  openCameraWidgets: {},
36
35
  openFramePovWidgets: {},
@@ -0,0 +1,5 @@
1
+ <script lang="ts">
2
+ import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'
3
+ </script>
4
+
5
+ <SvelteQueryDevtools buttonPosition="bottom-left" />
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const Debug: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type Debug = InstanceType<typeof Debug>;
18
+ export default Debug;
@@ -3,3 +3,4 @@ export * as selectionTraits from './Selection/traits';
3
3
  export { useSelectionPlugin } from './Selection/useSelectionPlugin.svelte';
4
4
  export { default as DrawService } from './DrawService/DrawService.svelte';
5
5
  export { default as Skybox } from './Skybox/Skybox.svelte';
6
+ export { default as Debug } from './Debug/Debug.svelte';
@@ -6,3 +6,5 @@ export { useSelectionPlugin } from './Selection/useSelectionPlugin.svelte';
6
6
  export { default as DrawService } from './DrawService/DrawService.svelte';
7
7
  // Skybox
8
8
  export { default as Skybox } from './Skybox/Skybox.svelte';
9
+ // Debug
10
+ export { default as Debug } from './Debug/Debug.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "1.29.0",
3
+ "version": "1.30.0",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -22,6 +22,7 @@
22
22
  "@sveltejs/vite-plugin-svelte": "6.1.4",
23
23
  "@tailwindcss/forms": "0.5.10",
24
24
  "@tailwindcss/vite": "4.1.13",
25
+ "@tanstack/svelte-query-devtools": "6.0.2",
25
26
  "@testing-library/jest-dom": "6.8.0",
26
27
  "@testing-library/svelte": "5.2.8",
27
28
  "@testing-library/user-event": "^14.6.1",
@@ -87,6 +88,7 @@
87
88
  "@ag-grid-community/core": ">=32.3.0",
88
89
  "@ag-grid-community/styles": ">=32.3.0",
89
90
  "@dimforge/rapier3d-compat": ">=0.17",
91
+ "@tanstack/svelte-query-devtools": ">=6",
90
92
  "@threlte/core": ">=8",
91
93
  "@threlte/extras": ">=9",
92
94
  "@threlte/rapier": ">=3",
@@ -110,6 +112,11 @@
110
112
  "svelte-tweakpane-ui": ">=1.5",
111
113
  "svelte-virtuallists": ">=1"
112
114
  },
115
+ "peerDependenciesMeta": {
116
+ "@tanstack/svelte-query-devtools": {
117
+ "optional": true
118
+ }
119
+ },
113
120
  "engines": {
114
121
  "node": ">=22.12.0"
115
122
  },
@@ -143,7 +150,6 @@
143
150
  "@connectrpc/connect": "1.7.0",
144
151
  "@connectrpc/connect-web": "1.7.0",
145
152
  "@neodrag/svelte": "^2.3.3",
146
- "@tanstack/svelte-query-devtools": "^6.0.2",
147
153
  "earcut": "^3.0.2",
148
154
  "filtrex": "^3.1.0",
149
155
  "koota": "0.6.5",