@viamrobotics/motion-tools 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/color.d.ts CHANGED
@@ -6,3 +6,4 @@ import { Color, type ColorRepresentation } from 'three';
6
6
  * @returns A new THREE.Color instance with the darkened color.
7
7
  */
8
8
  export declare const darkenColor: (value: ColorRepresentation, percent: number) => Color;
9
+ export declare const mapStringToColor: () => void;
package/dist/color.js CHANGED
@@ -12,3 +12,4 @@ export const darkenColor = (value, percent) => {
12
12
  hsl.l = Math.max(0, hsl.l * (1 - percent / 100));
13
13
  return color.setHSL(hsl.h, hsl.s, hsl.l);
14
14
  };
15
+ export const mapStringToColor = () => { };
@@ -17,7 +17,6 @@
17
17
  import CameraControls from 'camera-controls'
18
18
  import { T, useTask, useThrelte } from '@threlte/core'
19
19
  import type { Snippet } from 'svelte'
20
- import { useTransformControls } from '../hooks/useControls.svelte'
21
20
 
22
21
  let installed = false
23
22
 
@@ -42,17 +41,17 @@
42
41
  <script lang="ts">
43
42
  interface Props {
44
43
  ref?: CameraControls
44
+ enabled?: boolean
45
45
  children?: Snippet
46
46
  }
47
47
 
48
- let { ref = $bindable(), children }: Props = $props()
48
+ let { ref = $bindable(), enabled = true, children }: Props = $props()
49
49
 
50
50
  if (!installed) {
51
51
  install()
52
52
  }
53
53
 
54
54
  const { camera, dom, invalidate } = useThrelte()
55
- const transformControls = useTransformControls()
56
55
 
57
56
  const controls = new CameraControls(camera.current as PerspectiveCamera, dom)
58
57
 
@@ -61,7 +60,7 @@
61
60
  })
62
61
 
63
62
  $effect.pre(() => {
64
- controls.enabled = !transformControls.active
63
+ controls.enabled = enabled
65
64
  })
66
65
 
67
66
  useTask(
@@ -2,6 +2,7 @@ import CameraControls from 'camera-controls';
2
2
  import type { Snippet } from 'svelte';
3
3
  interface Props {
4
4
  ref?: CameraControls;
5
+ enabled?: boolean;
5
6
  children?: Snippet;
6
7
  }
7
8
  declare const CameraControls: import("svelte").Component<Props, {}, "ref">;
@@ -1,17 +1,9 @@
1
1
  <script lang="ts">
2
- import { T } from '@threlte/core'
3
- import { type Snippet } from 'svelte'
4
- import { meshBounds, MeshLineGeometry, MeshLineMaterial } from '@threlte/extras'
5
- import { BufferGeometry, DoubleSide, FrontSide, Mesh, Object3D } from 'three'
6
- import { CapsuleGeometry } from '../three/CapsuleGeometry'
7
- import { poseToObject3d } from '../transform'
8
- import { darkenColor } from '../color'
9
- import AxesHelper from './AxesHelper.svelte'
2
+ import type { Snippet } from 'svelte'
3
+ import type { Object3D } from 'three'
10
4
  import type { WorldObject } from '../WorldObject'
11
- import { PLYLoader } from 'three/addons/loaders/PLYLoader.js'
12
5
  import { useObjectEvents } from '../hooks/useObjectEvents.svelte'
13
-
14
- const plyLoader = new PLYLoader()
6
+ import Geometry from './Geometry.svelte'
15
7
 
16
8
  interface Props {
17
9
  uuid: string
@@ -22,91 +14,13 @@
22
14
  children?: Snippet<[{ ref: Object3D }]>
23
15
  }
24
16
 
25
- let { uuid, name, geometry, metadata, pose, children }: Props = $props()
26
-
27
- const type = $derived(geometry?.case)
28
- const mesh = $derived.by(() => {
29
- const object3d = type === undefined ? new Object3D() : new Mesh()
30
-
31
- if (type === 'mesh' || type === 'points' || type === 'line') {
32
- object3d.raycast = meshBounds
33
- }
34
-
35
- return object3d
36
- })
37
-
38
- $effect.pre(() => {
39
- poseToObject3d(pose, mesh)
40
- })
17
+ let { uuid, ...rest }: Props = $props()
41
18
 
42
19
  const events = useObjectEvents(() => uuid)
43
-
44
- let geo = $state<BufferGeometry>()
45
20
  </script>
46
21
 
47
- <T
48
- is={mesh}
49
- {name}
22
+ <Geometry
50
23
  {uuid}
51
24
  {...events}
52
- >
53
- {#if geometry?.case === 'mesh'}
54
- {@const meshGeometry = plyLoader.parse(atob(geometry.value.mesh as unknown as string))}
55
- <T is={meshGeometry} />
56
- {:else if geometry?.case === 'line' && metadata.points}
57
- <MeshLineGeometry points={metadata.points} />
58
- {:else if geometry?.case === 'box'}
59
- {@const dimsMm = geometry.value.dimsMm ?? { x: 0, y: 0, z: 0 }}
60
- <T.BoxGeometry
61
- args={[dimsMm.x * 0.001, dimsMm.y * 0.001, dimsMm.z * 0.001]}
62
- oncreate={(ref) => {
63
- geo = ref
64
- }}
65
- />
66
- {:else if geometry?.case === 'sphere'}
67
- {@const radiusMm = geometry.value.radiusMm ?? 0}
68
- <T.SphereGeometry
69
- args={[radiusMm * 0.001]}
70
- oncreate={(ref) => {
71
- geo = ref
72
- }}
73
- />
74
- {:else if geometry?.case === 'capsule'}
75
- {@const { lengthMm, radiusMm } = geometry.value}
76
- <T
77
- is={CapsuleGeometry}
78
- args={[radiusMm * 0.001, lengthMm * 0.001]}
79
- oncreate={(ref) => {
80
- geo = ref
81
- }}
82
- />
83
- {:else}
84
- <AxesHelper
85
- width={5}
86
- length={0.1}
87
- />
88
- {/if}
89
-
90
- {#if geometry?.case === 'line'}
91
- <MeshLineMaterial
92
- color={metadata.color ?? 'red'}
93
- width={0.005}
94
- />
95
- {:else if geometry}
96
- <T.MeshToonMaterial
97
- color={metadata.color ?? 'red'}
98
- side={geometry.case === 'mesh' ? DoubleSide : FrontSide}
99
- transparent
100
- opacity={0.7}
101
- />
102
-
103
- {#if geo}
104
- <T.LineSegments raycast={() => null}>
105
- <T.EdgesGeometry args={[geo, 1]} />
106
- <T.LineBasicMaterial color={darkenColor(metadata.color ?? 'red', 10)} />
107
- </T.LineSegments>
108
- {/if}
109
- {/if}
110
-
111
- {@render children?.({ ref: mesh })}
112
- </T>
25
+ {...rest}
26
+ />
@@ -1,5 +1,5 @@
1
- import { type Snippet } from 'svelte';
2
- import { Object3D } from 'three';
1
+ import type { Snippet } from 'svelte';
2
+ import type { Object3D } from 'three';
3
3
  import type { WorldObject } from '../WorldObject';
4
4
  interface Props {
5
5
  uuid: string;
@@ -0,0 +1,110 @@
1
+ <script lang="ts">
2
+ import { T } from '@threlte/core'
3
+ import { type Snippet } from 'svelte'
4
+ import { meshBounds, MeshLineGeometry, MeshLineMaterial } from '@threlte/extras'
5
+ import { BufferGeometry, DoubleSide, FrontSide, Mesh, Object3D } from 'three'
6
+ import { CapsuleGeometry } from '../three/CapsuleGeometry'
7
+ import { poseToObject3d } from '../transform'
8
+ import { darkenColor } from '../color'
9
+ import AxesHelper from './AxesHelper.svelte'
10
+ import type { WorldObject } from '../WorldObject'
11
+ import { PLYLoader } from 'three/addons/loaders/PLYLoader.js'
12
+
13
+ const plyLoader = new PLYLoader()
14
+
15
+ interface Props {
16
+ uuid: string
17
+ name: string
18
+ geometry?: WorldObject['geometry']
19
+ pose: WorldObject['pose']
20
+ metadata: WorldObject['metadata']
21
+ children?: Snippet<[{ ref: Object3D }]>
22
+ [prop: string]: unknown
23
+ }
24
+
25
+ let { uuid, name, geometry, metadata, pose, children, ...rest }: Props = $props()
26
+
27
+ const type = $derived(geometry?.case)
28
+ const mesh = $derived.by(() => {
29
+ const object3d = type === undefined ? new Object3D() : new Mesh()
30
+
31
+ if (type === 'mesh' || type === 'points' || type === 'line') {
32
+ object3d.raycast = meshBounds
33
+ }
34
+
35
+ return object3d
36
+ })
37
+
38
+ $effect.pre(() => {
39
+ poseToObject3d(pose, mesh)
40
+ })
41
+
42
+ let geo = $state<BufferGeometry>()
43
+ </script>
44
+
45
+ <T
46
+ is={mesh}
47
+ {name}
48
+ {uuid}
49
+ {...rest}
50
+ >
51
+ {#if geometry?.case === 'mesh'}
52
+ {@const meshGeometry = plyLoader.parse(atob(geometry.value.mesh as unknown as string))}
53
+ <T is={meshGeometry} />
54
+ {:else if geometry?.case === 'line' && metadata.points}
55
+ <MeshLineGeometry points={metadata.points} />
56
+ {:else if geometry?.case === 'box'}
57
+ {@const dimsMm = geometry.value.dimsMm ?? { x: 0, y: 0, z: 0 }}
58
+ <T.BoxGeometry
59
+ args={[dimsMm.x * 0.001, dimsMm.y * 0.001, dimsMm.z * 0.001]}
60
+ oncreate={(ref) => {
61
+ geo = ref
62
+ }}
63
+ />
64
+ {:else if geometry?.case === 'sphere'}
65
+ {@const radiusMm = geometry.value.radiusMm ?? 0}
66
+ <T.SphereGeometry
67
+ args={[radiusMm * 0.001]}
68
+ oncreate={(ref) => {
69
+ geo = ref
70
+ }}
71
+ />
72
+ {:else if geometry?.case === 'capsule'}
73
+ {@const { lengthMm, radiusMm } = geometry.value}
74
+ <T
75
+ is={CapsuleGeometry}
76
+ args={[radiusMm * 0.001, lengthMm * 0.001]}
77
+ oncreate={(ref) => {
78
+ geo = ref
79
+ }}
80
+ />
81
+ {:else}
82
+ <AxesHelper
83
+ width={5}
84
+ length={0.1}
85
+ />
86
+ {/if}
87
+
88
+ {#if geometry?.case === 'line'}
89
+ <MeshLineMaterial
90
+ color={metadata.color ?? 'red'}
91
+ width={0.005}
92
+ />
93
+ {:else if geometry}
94
+ <T.MeshToonMaterial
95
+ color={metadata.color ?? 'red'}
96
+ side={geometry.case === 'mesh' ? DoubleSide : FrontSide}
97
+ transparent
98
+ opacity={0.7}
99
+ />
100
+
101
+ {#if geo}
102
+ <T.LineSegments raycast={() => null}>
103
+ <T.EdgesGeometry args={[geo, 1]} />
104
+ <T.LineBasicMaterial color={darkenColor(metadata.color ?? 'red', 10)} />
105
+ </T.LineSegments>
106
+ {/if}
107
+ {/if}
108
+
109
+ {@render children?.({ ref: mesh })}
110
+ </T>
@@ -0,0 +1,17 @@
1
+ import { type Snippet } from 'svelte';
2
+ import { Object3D } from 'three';
3
+ import type { WorldObject } from '../WorldObject';
4
+ interface Props {
5
+ uuid: string;
6
+ name: string;
7
+ geometry?: WorldObject['geometry'];
8
+ pose: WorldObject['pose'];
9
+ metadata: WorldObject['metadata'];
10
+ children?: Snippet<[{
11
+ ref: Object3D;
12
+ }]>;
13
+ [prop: string]: unknown;
14
+ }
15
+ declare const Geometry: import("svelte").Component<Props, {}, "">;
16
+ type Geometry = ReturnType<typeof Geometry>;
17
+ export default Geometry;
@@ -14,6 +14,7 @@
14
14
  import { useFocused } from '../hooks/useSelection.svelte'
15
15
  import type { Snippet } from 'svelte'
16
16
  import { useXR } from '@threlte/xr'
17
+ import { useTransformControls } from '../hooks/useControls.svelte'
17
18
 
18
19
  interface Props {
19
20
  children?: Snippet
@@ -32,6 +33,7 @@
32
33
  })
33
34
 
34
35
  const focused = useFocused()
36
+ const transformControls = useTransformControls()
35
37
 
36
38
  const { isPresenting } = useXR()
37
39
  </script>
@@ -45,7 +47,7 @@
45
47
  {#if focused.current === undefined}
46
48
  {#if !$isPresenting}
47
49
  <Camera position={[3, 3, 3]}>
48
- <CameraControls>
50
+ <CameraControls enabled={!transformControls.active}>
49
51
  <Gizmo />
50
52
  </CameraControls>
51
53
  </Camera>
@@ -153,7 +153,7 @@
153
153
  >
154
154
  {#if rootChildren.length === 0}
155
155
  <p class="text-subtle-2 px-2 py-4">No objects displayed</p>
156
- {:else}
156
+ {:else if rootChildren.length > 200}
157
157
  <VirtualList
158
158
  class="w-full"
159
159
  style="height:{Math.min(8, Math.max(rootChildren.length, 5)) * 32}px;"
@@ -163,6 +163,15 @@
163
163
  {@render treeNode({ node: item, indexPath: [Number(index)], api })}
164
164
  {/snippet}
165
165
  </VirtualList>
166
+ {:else}
167
+ <div
168
+ style="height:{Math.min(8, Math.max(rootChildren.length, 5)) * 32}px;"
169
+ class="overflow-auto"
170
+ >
171
+ {#each rootChildren as node, index (node.id)}
172
+ {@render treeNode({ node, indexPath: [Number(index)], api })}
173
+ {/each}
174
+ </div>
166
175
  {/if}
167
176
  </div>
168
177
  </div>
@@ -1 +0,0 @@
1
- export { provideConnectionConfigs, useConnectionConfigs, useActiveConnectionConfig, } from './useConnectionConfigs.svelte';
@@ -1 +1 @@
1
- export { provideConnectionConfigs, useConnectionConfigs, useActiveConnectionConfig, } from './useConnectionConfigs.svelte';
1
+ "use strict";
@@ -29,7 +29,7 @@ export const provideFrames = (partID) => {
29
29
  }
30
30
  for (const { frame } of query.current.data ?? []) {
31
31
  if (frame) {
32
- objects.push(new WorldObject(frame.referenceFrame, frame.poseInObserverFrame?.pose, frame.poseInObserverFrame?.referenceFrame, frame.physicalObject?.geometryType));
32
+ objects.push(new WorldObject(frame.referenceFrame ?? 'Unnamed frame', frame.poseInObserverFrame?.pose, frame.poseInObserverFrame?.referenceFrame, frame.physicalObject?.geometryType));
33
33
  }
34
34
  }
35
35
  return objects;
@@ -8,6 +8,7 @@ import { WorldObject } from '../WorldObject';
8
8
  import { usePersistentUUIDs } from './usePersistentUUIDs.svelte';
9
9
  import { useLogs } from './useLogs.svelte';
10
10
  const key = Symbol('geometries-context');
11
+ let index = 0;
11
12
  export const provideGeometries = (partID) => {
12
13
  const logs = useLogs();
13
14
  const refreshRates = useRefreshRates();
@@ -45,7 +46,7 @@ export const provideGeometries = (partID) => {
45
46
  if (!query.data)
46
47
  continue;
47
48
  for (const { center, label, geometryType } of query.data.geometries) {
48
- results.push(new WorldObject(label, center, query.data.name, geometryType));
49
+ results.push(new WorldObject(label ? label : `Unnamed geometry ${++index}`, center, query.data.name, geometryType));
49
50
  }
50
51
  }
51
52
  updateUUIDs(results);
@@ -1,13 +1,12 @@
1
1
  export const usePersistentUUIDs = () => {
2
2
  const uuids = new Map();
3
3
  const updateUUIDs = (objects) => {
4
- if (uuids.size === 0) {
5
- for (const object of objects) {
6
- uuids.set(object.name, object.uuid);
7
- }
8
- }
9
4
  for (const object of objects) {
10
- object.uuid = uuids.get(object.name) ?? object.uuid;
5
+ const ref = `${object.referenceFrame}-${object.name}`;
6
+ if (uuids.has(ref) === false) {
7
+ uuids.set(ref, object.uuid);
8
+ }
9
+ object.uuid = uuids.get(ref) ?? object.uuid;
11
10
  }
12
11
  };
13
12
  return { uuids, updateUUIDs };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  export { default as MotionTools } from './components/App.svelte';
2
+ export { default as CameraControls } from './components/CameraControls.svelte';
3
+ export { default as Geometry } from './components/Geometry.svelte';
2
4
  export { AxesHelper } from './three/AxesHelper';
3
5
  export { BatchedArrow } from './three/BatchedArrow';
4
6
  export { CapsuleGeometry } from './three/CapsuleGeometry';
7
+ export { WorldObject } from './WorldObject';
package/dist/index.js CHANGED
@@ -1,4 +1,7 @@
1
1
  export { default as MotionTools } from './components/App.svelte';
2
+ export { default as CameraControls } from './components/CameraControls.svelte';
3
+ export { default as Geometry } from './components/Geometry.svelte';
2
4
  export { AxesHelper } from './three/AxesHelper';
3
5
  export { BatchedArrow } from './three/BatchedArrow';
4
6
  export { CapsuleGeometry } from './three/CapsuleGeometry';
7
+ export { WorldObject } from './WorldObject';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -20,20 +20,20 @@
20
20
  "@sveltejs/package": "^2.3.11",
21
21
  "@sveltejs/vite-plugin-svelte": "^5.0.3",
22
22
  "@tailwindcss/forms": "^0.5.10",
23
- "@tailwindcss/vite": "^4.1.7",
24
- "@tanstack/svelte-query": "^5.77.2",
25
- "@tanstack/svelte-query-devtools": "^5.77.2",
23
+ "@tailwindcss/vite": "^4.1.8",
24
+ "@tanstack/svelte-query": "^5.79.0",
25
+ "@tanstack/svelte-query-devtools": "^5.79.0",
26
26
  "@testing-library/jest-dom": "^6.6.3",
27
27
  "@testing-library/svelte": "^5.2.8",
28
28
  "@threlte/core": "^8.0.4",
29
29
  "@threlte/extras": "^9.2.1",
30
30
  "@threlte/rapier": "^3.1.4",
31
31
  "@threlte/xr": "^1.0.5",
32
- "@types/bun": "^1.2.14",
32
+ "@types/bun": "^1.2.15",
33
33
  "@types/lodash-es": "^4.17.12",
34
34
  "@types/three": "^0.176.0",
35
- "@typescript-eslint/eslint-plugin": "^8.32.1",
36
- "@typescript-eslint/parser": "^8.32.1",
35
+ "@typescript-eslint/eslint-plugin": "^8.33.0",
36
+ "@typescript-eslint/parser": "^8.33.0",
37
37
  "@viamrobotics/prime-core": "^0.1.5",
38
38
  "@viamrobotics/sdk": "0.42.0",
39
39
  "@viamrobotics/svelte-sdk": "0.1.4",
@@ -55,15 +55,15 @@
55
55
  "prettier-plugin-tailwindcss": "^0.6.11",
56
56
  "publint": "^0.3.12",
57
57
  "runed": "^0.28.0",
58
- "svelte": "5.33.4",
58
+ "svelte": "5.33.8",
59
59
  "svelte-check": "^4.2.1",
60
60
  "svelte-virtuallists": "^1.4.2",
61
- "tailwindcss": "^4.1.7",
61
+ "tailwindcss": "^4.1.8",
62
62
  "three": "^0.176.0",
63
63
  "threlte-uikit": "^1.0.0",
64
64
  "tsx": "^4.19.4",
65
65
  "typescript": "^5.8.3",
66
- "typescript-eslint": "^8.32.1",
66
+ "typescript-eslint": "^8.33.0",
67
67
  "vite": "^6.3.5",
68
68
  "vite-plugin-mkcert": "^1.17.8",
69
69
  "vitest": "^3.1.4"
@@ -1,20 +0,0 @@
1
- interface ConnectionConfig {
2
- host: string;
3
- partId: string;
4
- apiKeyId: string;
5
- apiKeyValue: string;
6
- signalingAddress: string;
7
- }
8
- interface Context {
9
- current: ConnectionConfig[];
10
- add: () => void;
11
- remove: (index: number) => void;
12
- isEnvConfig: (config: ConnectionConfig) => boolean;
13
- }
14
- export declare const provideConnectionConfigs: () => void;
15
- export declare const useConnectionConfigs: () => Context;
16
- export declare const useActiveConnectionConfig: () => {
17
- readonly current: ConnectionConfig | undefined;
18
- set(index: number | undefined): void;
19
- };
20
- export {};
@@ -1,59 +0,0 @@
1
- import { get, set } from 'idb-keyval';
2
- import { PersistedState } from 'runed';
3
- import { getContext, setContext } from 'svelte';
4
- import { envConfigs } from '../../routes/lib/configs';
5
- import { isEqual } from 'lodash-es';
6
- const key = Symbol('connection-config-context');
7
- const activeConfig = new PersistedState('active-connection-config', 0);
8
- export const provideConnectionConfigs = () => {
9
- let connectionConfigs = $state([]);
10
- get('connection-configs').then((response) => {
11
- if (Array.isArray(response)) {
12
- connectionConfigs = response;
13
- }
14
- });
15
- $effect(() => {
16
- set('connection-configs', $state.snapshot(connectionConfigs));
17
- });
18
- const merged = $derived([...envConfigs, ...connectionConfigs]);
19
- const add = () => {
20
- connectionConfigs.push({
21
- host: '',
22
- partId: '',
23
- apiKeyId: '',
24
- apiKeyValue: '',
25
- signalingAddress: '',
26
- });
27
- };
28
- const remove = (index) => {
29
- connectionConfigs.splice(index - envConfigs.length, 1);
30
- };
31
- const isEnvConfig = (config) => {
32
- return envConfigs.some((value) => isEqual(config, value));
33
- };
34
- setContext(key, {
35
- get current() {
36
- return merged;
37
- },
38
- add,
39
- remove,
40
- isEnvConfig,
41
- });
42
- };
43
- export const useConnectionConfigs = () => {
44
- return getContext(key);
45
- };
46
- export const useActiveConnectionConfig = () => {
47
- const connectionConfigs = useConnectionConfigs();
48
- return {
49
- get current() {
50
- if (activeConfig.current === -1) {
51
- return undefined;
52
- }
53
- return connectionConfigs.current.at(activeConfig.current);
54
- },
55
- set(index) {
56
- activeConfig.current = index ?? -1;
57
- },
58
- };
59
- };