@viamrobotics/motion-tools 0.15.4 → 0.16.1

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.
@@ -9,7 +9,6 @@
9
9
  import Details from './Details.svelte'
10
10
  import SceneProviders from './SceneProviders.svelte'
11
11
  import XR from './xr/XR.svelte'
12
- import { World } from '@threlte/rapier'
13
12
  import { createPartIDContext } from '../hooks/usePartID.svelte'
14
13
  import Dashboard from './dashboard/Dashboard.svelte'
15
14
  import { domPortal } from '../portal'
@@ -21,6 +20,7 @@
21
20
  import LiveUpdatesBanner from './LiveUpdatesBanner.svelte'
22
21
  import ArmPositions from './widgets/ArmPositions.svelte'
23
22
  import { provideEnvironment } from '../hooks/useEnvironment.svelte'
23
+ import type { CameraPose } from '../hooks/useControls.svelte'
24
24
 
25
25
  interface LocalConfigProps {
26
26
  getLocalPartConfig: () => Struct
@@ -34,6 +34,11 @@
34
34
  enableKeybindings?: boolean
35
35
  children?: Snippet
36
36
  localConfigProps?: LocalConfigProps
37
+
38
+ /**
39
+ * Allows setting the initial camera pose
40
+ */
41
+ cameraPose?: CameraPose
37
42
  }
38
43
 
39
44
  let {
@@ -41,6 +46,7 @@
41
46
  enableKeybindings = true,
42
47
  children: appChildren,
43
48
  localConfigProps,
49
+ cameraPose,
44
50
  }: Props = $props()
45
51
 
46
52
  const appClient = useViamClient()
@@ -87,34 +93,32 @@
87
93
  class="relative h-full w-full overflow-hidden"
88
94
  bind:this={root}
89
95
  >
90
- <Canvas renderMode="always">
91
- <World>
92
- <SceneProviders>
93
- {#snippet children({ focus })}
94
- <Scene>
95
- {@render appChildren?.()}
96
- </Scene>
97
-
98
- <XR {@attach domPortal(root)} />
99
-
100
- <Dashboard {@attach domPortal(root)} />
101
- <Details {@attach domPortal(root)} />
102
- {#if environment.current.isStandalone}
103
- <LiveUpdatesBanner {@attach domPortal(root)} />
104
- {/if}
105
-
106
- {#if !focus}
107
- <TreeContainer {@attach domPortal(root)} />
108
- {/if}
109
-
110
- {#if !focus && settings.current.enableArmPositionsWidget}
111
- <ArmPositions {@attach domPortal(root)} />
112
- {/if}
113
-
114
- <FileDrop {@attach domPortal(root)} />
115
- {/snippet}
116
- </SceneProviders>
117
- </World>
96
+ <Canvas renderMode="on-demand">
97
+ <SceneProviders {cameraPose}>
98
+ {#snippet children({ focus })}
99
+ <Scene>
100
+ {@render appChildren?.()}
101
+ </Scene>
102
+
103
+ <XR {@attach domPortal(root)} />
104
+
105
+ <Dashboard {@attach domPortal(root)} />
106
+ <Details {@attach domPortal(root)} />
107
+ {#if environment.current.isStandalone}
108
+ <LiveUpdatesBanner {@attach domPortal(root)} />
109
+ {/if}
110
+
111
+ {#if !focus}
112
+ <TreeContainer {@attach domPortal(root)} />
113
+ {/if}
114
+
115
+ {#if !focus && settings.current.enableArmPositionsWidget}
116
+ <ArmPositions {@attach domPortal(root)} />
117
+ {/if}
118
+
119
+ <FileDrop {@attach domPortal(root)} />
120
+ {/snippet}
121
+ </SceneProviders>
118
122
  </Canvas>
119
123
 
120
124
  <ToastContainer />
@@ -1,5 +1,6 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { Struct } from '@viamrobotics/sdk';
3
+ import type { CameraPose } from '../hooks/useControls.svelte';
3
4
  interface LocalConfigProps {
4
5
  getLocalPartConfig: () => Struct;
5
6
  setLocalPartConfig: (config: Struct) => void;
@@ -11,6 +12,10 @@ interface Props {
11
12
  enableKeybindings?: boolean;
12
13
  children?: Snippet;
13
14
  localConfigProps?: LocalConfigProps;
15
+ /**
16
+ * Allows setting the initial camera pose
17
+ */
18
+ cameraPose?: CameraPose;
14
19
  }
15
20
  declare const App: import("svelte").Component<Props, {}, "">;
16
21
  type App = ReturnType<typeof App>;
@@ -1,36 +1,17 @@
1
1
  <script lang="ts">
2
2
  import { MathUtils } from 'three'
3
3
  import { CameraControls, type CameraControlsRef, Gizmo } from '@threlte/extras'
4
- import { useTransformControls } from '../hooks/useControls.svelte'
4
+ import { useCameraControls, useTransformControls } from '../hooks/useControls.svelte'
5
5
  import KeyboardControls from './KeyboardControls.svelte'
6
6
  import Portal from './portal/Portal.svelte'
7
7
  import Button from './dashboard/Button.svelte'
8
- import { useDrawAPI } from '../hooks/useDrawAPI.svelte'
9
8
  import { useSettings } from '../hooks/useSettings.svelte'
10
9
 
10
+ const cameraControls = useCameraControls()
11
11
  const settings = useSettings()
12
- const drawAPI = useDrawAPI()
13
12
  const transformControls = useTransformControls()
14
13
 
15
14
  const enableKeybindings = $derived(settings.current.enableKeybindings)
16
-
17
- let ref = $state.raw<CameraControlsRef>()
18
-
19
- $effect(() => {
20
- if (drawAPI.camera) {
21
- const { position, lookAt, animate } = drawAPI.camera
22
- ref?.setPosition(position.x, position.y, position.z, animate)
23
- ref?.setLookAt(position.x, position.y, position.z, lookAt.x, lookAt.y, lookAt.z, animate)
24
- drawAPI.clearCamera()
25
- }
26
- })
27
-
28
- $effect(() => {
29
- if (ref) {
30
- ;(window as unknown as { MathUtils: typeof MathUtils }).MathUtils = MathUtils
31
- ;(window as unknown as { cameraControls: CameraControlsRef }).cameraControls = ref
32
- }
33
- })
34
15
  </script>
35
16
 
36
17
  <Portal id="dashboard">
@@ -40,15 +21,19 @@
40
21
  icon="camera-outline"
41
22
  description="Reset camera"
42
23
  onclick={() => {
43
- ref?.reset(true)
24
+ cameraControls.current?.reset(true)
44
25
  }}
45
26
  />
46
27
  </fieldset>
47
28
  </Portal>
48
29
 
49
30
  <CameraControls
50
- bind:ref
51
31
  enabled={!transformControls.active}
32
+ oncreate={(ref) => {
33
+ cameraControls.set(ref)
34
+ ;(window as unknown as { MathUtils: typeof MathUtils }).MathUtils = MathUtils
35
+ ;(window as unknown as { cameraControls: CameraControlsRef }).cameraControls = ref
36
+ }}
52
37
  >
53
38
  {#snippet children({ ref }: { ref: CameraControlsRef })}
54
39
  {#if enableKeybindings}
@@ -3,7 +3,7 @@
3
3
  lang="ts"
4
4
  >
5
5
  import { OrientationVector } from '../three/OrientationVector'
6
- import { Quaternion, Vector3 } from 'three'
6
+ import { Quaternion, Vector3, MathUtils } from 'three'
7
7
 
8
8
  const vec3 = new Vector3()
9
9
  const quaternion = new Quaternion()
@@ -29,6 +29,7 @@
29
29
  import { useWeblabs } from '../hooks/useWeblabs.svelte'
30
30
  import { WEBLABS_EXPERIMENTS } from '../hooks/useWeblabs.svelte'
31
31
  import { useEnvironment } from '../hooks/useEnvironment.svelte'
32
+
32
33
  const { ...rest } = $props()
33
34
 
34
35
  const focused = useFocused()
@@ -93,7 +94,10 @@
93
94
  worldOrientation.th = ov.th
94
95
  }
95
96
  },
96
- { autoStart: false }
97
+ {
98
+ autoStart: false,
99
+ autoInvalidate: false,
100
+ }
97
101
  )
98
102
 
99
103
  $effect.pre(() => {
@@ -262,19 +266,20 @@
262
266
  {#if worldPosition}
263
267
  <div>
264
268
  <strong class="font-semibold">world position</strong>
269
+ <span class="text-subtle-2">(m)</span>
265
270
 
266
271
  <div class="flex gap-3">
267
272
  <div>
268
273
  <span class="text-subtle-2">x</span>
269
- {(worldPosition.x * 1000).toFixed(2)}
274
+ {worldPosition.x.toFixed(2)}
270
275
  </div>
271
276
  <div>
272
277
  <span class="text-subtle-2">y</span>
273
- {(worldPosition.y * 1000).toFixed(2)}
278
+ {worldPosition.y.toFixed(2)}
274
279
  </div>
275
280
  <div>
276
281
  <span class="text-subtle-2">z</span>
277
- {(worldPosition.z * 1000).toFixed(2)}
282
+ {worldPosition.z.toFixed(2)}
278
283
  </div>
279
284
  </div>
280
285
  </div>
@@ -283,6 +288,7 @@
283
288
  {#if worldOrientation}
284
289
  <div>
285
290
  <strong class="font-semibold">world orientation</strong>
291
+ <span class="text-subtle-2">(deg)</span>
286
292
  <div class="flex gap-3">
287
293
  <div>
288
294
  <span class="text-subtle-2">x</span>
@@ -298,7 +304,7 @@
298
304
  </div>
299
305
  <div>
300
306
  <span class="text-subtle-2">th</span>
301
- {worldOrientation.th.toFixed(2)}
307
+ {MathUtils.radToDeg(worldOrientation.th).toFixed(2)}
302
308
  </div>
303
309
  </div>
304
310
  </div>
@@ -323,6 +329,7 @@
323
329
  {@const PoseAttribute = showEditFrameOptions ? MutableField : ImmutableField}
324
330
  <div>
325
331
  <strong class="font-semibold">local position</strong>
332
+ <span class="text-subtle-2">(m)</span>
326
333
 
327
334
  <div class="flex gap-3">
328
335
  {@render PoseAttribute({
@@ -351,6 +358,7 @@
351
358
 
352
359
  <div>
353
360
  <strong class="font-semibold">local orientation</strong>
361
+ <span class="text-subtle-2">(deg)</span>
354
362
  <div class="flex {showEditFrameOptions ? 'gap-2' : 'gap-3'}">
355
363
  {@render PoseAttribute({
356
364
  label: 'x',
@@ -77,7 +77,10 @@
77
77
  cameraControls.rotate(0, 0.05 * MathUtils.DEG2RAD * dt, true)
78
78
  }
79
79
  },
80
- { autoStart: false, autoInvalidate: false }
80
+ {
81
+ autoStart: false,
82
+ autoInvalidate: false,
83
+ }
81
84
  )
82
85
 
83
86
  $effect.pre(() => {
@@ -66,7 +66,10 @@
66
66
  // resized to half zoom to take up the same screen space.
67
67
  material.size = pointSize * ((camera.current as OrthographicCamera).zoom / 2)
68
68
  },
69
- { autoStart: false }
69
+ {
70
+ autoStart: false,
71
+ autoInvalidate: false,
72
+ }
70
73
  )
71
74
 
72
75
  $effect(() => {
@@ -9,7 +9,11 @@
9
9
  import { provideVisibility } from '../hooks/useVisibility.svelte'
10
10
  import { provideDrawAPI } from '../hooks/useDrawAPI.svelte'
11
11
  import { provideMachineSettings } from '../hooks/useMachineSettings.svelte'
12
- import { provideTransformControls } from '../hooks/useControls.svelte'
12
+ import {
13
+ provideCameraControls,
14
+ provideTransformControls,
15
+ type CameraPose,
16
+ } from '../hooks/useControls.svelte'
13
17
  import { provideObjects } from '../hooks/useObjects.svelte'
14
18
  import { provideMotionClient } from '../hooks/useMotionClient.svelte'
15
19
  import { provideLogs } from '../hooks/useLogs.svelte'
@@ -19,14 +23,17 @@
19
23
  import { provideArrows } from '../hooks/useArrows.svelte'
20
24
  import { provideFramelessComponents } from '../hooks/useFramelessComponents.svelte'
21
25
  import { provideResourceByName } from '../hooks/useResourceByName.svelte'
26
+
22
27
  interface Props {
28
+ cameraPose?: CameraPose
23
29
  children: Snippet<[{ focus: boolean }]>
24
30
  }
25
31
 
26
- let { children }: Props = $props()
32
+ let { cameraPose, children }: Props = $props()
27
33
 
28
34
  const partID = usePartID()
29
35
 
36
+ provideCameraControls(() => cameraPose)
30
37
  provideTransformControls()
31
38
  provideVisibility()
32
39
  provideMachineSettings()
@@ -1,5 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
+ import { type CameraPose } from '../hooks/useControls.svelte';
2
3
  interface Props {
4
+ cameraPose?: CameraPose;
3
5
  children: Snippet<[{
4
6
  focus: boolean;
5
7
  }]>;
@@ -36,7 +36,10 @@
36
36
  obbHelper.setFromObject(clone)
37
37
  }
38
38
  },
39
- { autoStart: false }
39
+ {
40
+ autoStart: false,
41
+ autoInvalidate: false,
42
+ }
40
43
  )
41
44
 
42
45
  $effect.pre(() => {
@@ -51,7 +51,9 @@
51
51
 
52
52
  mesh.lookAt(headset.position)
53
53
  },
54
- { autoStart: false }
54
+ {
55
+ autoStart: false,
56
+ }
55
57
  )
56
58
 
57
59
  $effect(() => {
@@ -74,7 +74,9 @@
74
74
 
75
75
  rigidBody.setNextKinematicTranslation({ x: position.x, y: position.y, z: position.z })
76
76
  },
77
- { autoStart: false }
77
+ {
78
+ autoStart: false,
79
+ }
78
80
  )
79
81
 
80
82
  const rotateTask = useTask(
@@ -1,7 +1,20 @@
1
- interface Context {
1
+ import type { CameraControlsRef } from '@threlte/extras';
2
+ import type { Vector3Tuple } from 'three';
3
+ export interface CameraPose {
4
+ position: Vector3Tuple;
5
+ lookAt: Vector3Tuple;
6
+ }
7
+ interface CameraControlsContext {
8
+ current: CameraControlsRef | undefined;
9
+ set(current: CameraControlsRef): void;
10
+ setPose(pose: CameraPose, animate?: boolean): void;
11
+ }
12
+ export declare const provideCameraControls: (cameraPose: () => CameraPose | undefined) => void;
13
+ export declare const useCameraControls: () => CameraControlsContext;
14
+ interface TransformControlsContext {
2
15
  active: boolean;
3
16
  setActive: (value: boolean) => void;
4
17
  }
5
18
  export declare const provideTransformControls: () => void;
6
- export declare const useTransformControls: () => Context;
19
+ export declare const useTransformControls: () => TransformControlsContext;
7
20
  export {};
@@ -1,8 +1,36 @@
1
1
  import { getContext, setContext } from 'svelte';
2
- const key = Symbol('tranform-controls-context');
2
+ const TRANSFORM_CONTROLS_KEY = Symbol('tranform-controls-context');
3
+ const CAMERA_CONTROLS_KEY = Symbol('camera-controls-context');
4
+ export const provideCameraControls = (cameraPose) => {
5
+ let controls = $state.raw();
6
+ const setPose = (pose, animate = false) => {
7
+ const [x, y, z] = pose.position;
8
+ const [lookAtX, lookAtY, lookAtZ] = pose.lookAt;
9
+ controls?.setPosition(x, y, z, animate);
10
+ controls?.setLookAt(x, y, z, lookAtX, lookAtY, lookAtZ, animate);
11
+ };
12
+ $effect(() => {
13
+ const pose = cameraPose();
14
+ if (pose) {
15
+ setPose(pose);
16
+ }
17
+ });
18
+ setContext(CAMERA_CONTROLS_KEY, {
19
+ get current() {
20
+ return controls;
21
+ },
22
+ set(current) {
23
+ controls = current;
24
+ },
25
+ setPose,
26
+ });
27
+ };
28
+ export const useCameraControls = () => {
29
+ return getContext(CAMERA_CONTROLS_KEY);
30
+ };
3
31
  export const provideTransformControls = () => {
4
32
  let active = $state(false);
5
- setContext(key, {
33
+ setContext(TRANSFORM_CONTROLS_KEY, {
6
34
  get active() {
7
35
  return active;
8
36
  },
@@ -12,5 +40,5 @@ export const provideTransformControls = () => {
12
40
  });
13
41
  };
14
42
  export const useTransformControls = () => {
15
- return getContext(key);
43
+ return getContext(TRANSFORM_CONTROLS_KEY);
16
44
  };
@@ -1,4 +1,3 @@
1
- import { Vector3 } from 'three';
2
1
  import { WorldObject, type PointsGeometry } from '../WorldObject.svelte';
3
2
  type ConnectionStatus = 'connecting' | 'open' | 'closed';
4
3
  interface Context {
@@ -10,14 +9,8 @@ interface Context {
10
9
  nurbs: WorldObject[];
11
10
  models: WorldObject[];
12
11
  connectionStatus: ConnectionStatus;
13
- camera: {
14
- position: Vector3;
15
- lookAt: Vector3;
16
- animate: boolean;
17
- } | undefined;
18
12
  addPoints(worldObject: WorldObject<PointsGeometry>): void;
19
13
  addMesh(worldObject: WorldObject): void;
20
- clearCamera: () => void;
21
14
  }
22
15
  export declare const provideDrawAPI: () => void;
23
16
  export declare const useDrawAPI: () => Context;
@@ -7,6 +7,8 @@ import { WorldObject } from '../WorldObject.svelte';
7
7
  import { useArrows } from './useArrows.svelte';
8
8
  import { createGeometry } from '../geometry';
9
9
  import { createPose, createPoseFromFrame } from '../transform';
10
+ import { useCameraControls } from './useControls.svelte';
11
+ import { useThrelte } from '@threlte/core';
10
12
  const key = Symbol('draw-api-context-key');
11
13
  const tryParse = (json) => {
12
14
  try {
@@ -49,6 +51,8 @@ class Float32Reader {
49
51
  }
50
52
  }
51
53
  export const provideDrawAPI = () => {
54
+ const cameraControls = useCameraControls();
55
+ const { invalidate } = useThrelte();
52
56
  let pointsIndex = 0;
53
57
  let geometryIndex = 0;
54
58
  let poseIndex = 0;
@@ -62,7 +66,6 @@ export const provideDrawAPI = () => {
62
66
  const poses = $state([]);
63
67
  const nurbs = $state([]);
64
68
  const models = $state([]);
65
- let camera = $state.raw();
66
69
  let connectionStatus = $state('connecting');
67
70
  const color = new Color();
68
71
  const direction = new Vector3();
@@ -166,7 +169,14 @@ export const provideDrawAPI = () => {
166
169
  direction.set(nextPoses[i + 3], nextPoses[i + 4], nextPoses[i + 5]);
167
170
  color.set(colors[j], colors[j + 1], colors[j + 2]);
168
171
  const arrowId = batchedArrow.addArrow(direction, origin, length, color, arrowHeadAtPose === 1);
169
- poses.push(new WorldObject(`pose ${++poseIndex}`, undefined, undefined, undefined, {
172
+ const pose = createPose();
173
+ pose.x = origin.x;
174
+ pose.y = origin.y;
175
+ pose.z = origin.z;
176
+ pose.oX = direction.x;
177
+ pose.oY = direction.y;
178
+ pose.oZ = direction.z;
179
+ poses.push(new WorldObject(`pose ${++poseIndex}`, pose, 'world', undefined, {
170
180
  getBoundingBoxAt(box3) {
171
181
  return batchedArrow.getBoundingBoxAt(arrowId, box3);
172
182
  },
@@ -176,6 +186,7 @@ export const provideDrawAPI = () => {
176
186
  },
177
187
  }));
178
188
  }
189
+ invalidate();
179
190
  };
180
191
  const drawPoints = async (reader) => {
181
192
  // Read label length
@@ -394,11 +405,11 @@ export const provideDrawAPI = () => {
394
405
  if (!data)
395
406
  return;
396
407
  if ('setCameraPose' in data) {
397
- camera = {
398
- position: new Vector3(data.Position.X, data.Position.Y, data.Position.Z),
399
- lookAt: new Vector3(data.LookAt.X, data.LookAt.Y, data.LookAt.Z),
400
- animate: data.Animate,
401
- };
408
+ cameraControls.setPose({
409
+ position: [data.Position.X, data.Position.Y, data.Position.Z],
410
+ lookAt: [data.LookAt.X, data.LookAt.Y, data.LookAt.Z],
411
+ }, data.Animate);
412
+ return;
402
413
  }
403
414
  if ('geometries' in data) {
404
415
  return drawGeometries(data.geometries, data.colors, data.parent);
@@ -455,18 +466,12 @@ export const provideDrawAPI = () => {
455
466
  get connectionStatus() {
456
467
  return connectionStatus;
457
468
  },
458
- get camera() {
459
- return camera;
460
- },
461
469
  addPoints(worldObject) {
462
470
  points.push(worldObject);
463
471
  },
464
472
  addMesh(worldObject) {
465
473
  meshes.push(worldObject);
466
474
  },
467
- clearCamera: () => {
468
- camera = undefined;
469
- },
470
475
  });
471
476
  };
472
477
  export const useDrawAPI = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "0.15.4",
3
+ "version": "0.16.1",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",