@viamrobotics/motion-tools 1.29.1 → 1.31.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/SelectedTransformControls.svelte +10 -1
  4. package/dist/components/overlay/AddFrames.svelte +1 -1
  5. package/dist/components/overlay/Details.svelte +18 -1
  6. package/dist/components/overlay/Logs.svelte +1 -1
  7. package/dist/components/overlay/dashboard/Button.svelte +1 -1
  8. package/dist/components/overlay/settings/Settings.svelte +1 -5
  9. package/dist/hooks/useConfigFrames.svelte.js +3 -3
  10. package/dist/hooks/useFramelessComponents.svelte.js +1 -1
  11. package/dist/hooks/usePartConfig.svelte.d.ts +6 -2
  12. package/dist/hooks/usePartConfig.svelte.js +23 -11
  13. package/dist/hooks/useSettings.svelte.d.ts +0 -1
  14. package/dist/hooks/useSettings.svelte.js +0 -1
  15. package/dist/lib.d.ts +1 -0
  16. package/dist/lib.js +1 -0
  17. package/dist/loaders/pcd/worker.inline.d.ts +1 -1
  18. package/dist/loaders/pcd/worker.inline.js +1 -1
  19. package/dist/loaders/pcd/worker.js +3 -1
  20. package/dist/plugins/Debug/Debug.svelte +5 -0
  21. package/dist/plugins/Debug/Debug.svelte.d.ts +18 -0
  22. package/dist/plugins/Selection/Ellipse.svelte +21 -16
  23. package/dist/plugins/Selection/Lasso.svelte +21 -16
  24. package/dist/plugins/Selection/relations.d.ts +6 -0
  25. package/dist/plugins/Selection/relations.js +7 -0
  26. package/dist/plugins/Selection/traits.d.ts +0 -5
  27. package/dist/plugins/Selection/traits.js +1 -6
  28. package/dist/plugins/index.d.ts +2 -0
  29. package/dist/plugins/index.js +3 -0
  30. package/dist/snapshot.d.ts +14 -0
  31. package/dist/snapshot.js +23 -0
  32. package/package.json +9 -3
@@ -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 {
@@ -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,21 @@
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 >
47
+ 0
48
+ )
40
49
 
41
50
  // Mesh sets name={entity} on its inner mesh, so useSelectedObject3d resolves
42
51
  // to that mesh — not the parent Frame Group we actually want to drive. Walk
@@ -241,7 +250,7 @@
241
250
  }
242
251
  </script>
243
252
 
244
- {#if ref && entity && activeMode}
253
+ {#if ref && entity && activeMode && !isFragmentComponentWithVariables && !invisible.current}
245
254
  {#key entity}
246
255
  <TransformControls
247
256
  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,14 @@
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 >
115
+ 0
116
+ )
117
+ const showEditFrameOptions = $derived(
118
+ isFrameNode && partConfig.hasEditPermissions && !isFragmentComponentWithVariables
119
+ )
113
120
  const showRelationshipOptions = $derived(points.current || arrows.current)
114
121
  const resourceName = $derived(name.current ? resourceByName.current[name.current] : undefined)
115
122
  const displayType = $derived(isFrameNode ? resourceName?.subtype : isGeometry ? 'geometry' : '')
@@ -406,6 +413,16 @@
406
413
 
407
414
  <div class="border-medium -mx-2 w-[100%+0.5rem] border-b"></div>
408
415
 
416
+ {#if isFragmentComponentWithVariables}
417
+ <p
418
+ class="mt-2 rounded border-l-4 border-yellow-600 bg-yellow-50 px-2 py-1.5 text-yellow-900"
419
+ data-testid="fragment-variables-warning"
420
+ role="status"
421
+ >
422
+ This component is from a fragment with variables, editing frames in 3D scene is disabled
423
+ </p>
424
+ {/if}
425
+
409
426
  <h3
410
427
  class="text-subtle-2 flex justify-between py-2"
411
428
  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
  >
@@ -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>
@@ -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
  }
@@ -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: {},
package/dist/lib.d.ts CHANGED
@@ -7,3 +7,4 @@ export { OrientationVector } from './three/OrientationVector';
7
7
  export { parsePcdInWorker } from './loaders/pcd';
8
8
  export { createBinaryPCD } from './pcd';
9
9
  export { metadataFromStruct } from './metadata';
10
+ export { decodeDrawnSnapshotPointClouds } from './snapshot';
package/dist/lib.js CHANGED
@@ -13,3 +13,4 @@ export { OrientationVector } from './three/OrientationVector';
13
13
  export { parsePcdInWorker } from './loaders/pcd';
14
14
  export { createBinaryPCD } from './pcd';
15
15
  export { metadataFromStruct } from './metadata';
16
+ export { decodeDrawnSnapshotPointClouds } from './snapshot';