@viamrobotics/motion-tools 1.15.3 → 1.15.5

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.
@@ -1,9 +1,9 @@
1
1
  <script lang="ts">
2
2
  import type { CameraControlsRef } from '@threlte/extras'
3
3
 
4
- import { useTask } from '@threlte/core'
4
+ import { isInstanceOf, useTask } from '@threlte/core'
5
5
  import { PressedKeys } from 'runed'
6
- import { MathUtils } from 'three'
6
+ import { MathUtils, Vector3 } from 'three'
7
7
 
8
8
  import { useFocusedEntity, useSelectedEntity } from '../hooks/useSelection.svelte'
9
9
  import { useSettings } from '../hooks/useSettings.svelte'
@@ -35,7 +35,36 @@
35
35
  const left = $derived(keys.has('arrowleft'))
36
36
  const down = $derived(keys.has('arrowdown'))
37
37
  const right = $derived(keys.has('arrowright'))
38
- const any = $derived(w || s || a || d || r || f || up || left || down || right)
38
+ const anyKeysPressed = $derived(w || s || a || d || r || f || up || left || down || right)
39
+
40
+ const target = new Vector3()
41
+
42
+ const PERSPECTIVE_DISTANCE_FACTOR = 0.0001
43
+ const PERSPECTIVE_MIN_SPEED = 0.00001
44
+
45
+ const ORTHOGRAPHIC_ZOOM_FACTOR = 0.1
46
+ const ORTHOGRAPHIC_MIN_SPEED = 0.00005
47
+
48
+ const FALLBACK_SPEED = 0.001
49
+
50
+ const getMovementScale = () => {
51
+ const camera = cameraControls.camera
52
+
53
+ if (isInstanceOf(camera, 'PerspectiveCamera')) {
54
+ cameraControls.getTarget(target)
55
+
56
+ const distance = camera.position.distanceTo(target)
57
+ const scaled = distance * PERSPECTIVE_DISTANCE_FACTOR
58
+ return Math.max(scaled, PERSPECTIVE_MIN_SPEED)
59
+ }
60
+
61
+ if (isInstanceOf(camera, 'OrthographicCamera')) {
62
+ const scaled = ORTHOGRAPHIC_ZOOM_FACTOR / camera.zoom
63
+ return Math.max(scaled, ORTHOGRAPHIC_MIN_SPEED)
64
+ }
65
+
66
+ return FALLBACK_SPEED
67
+ }
39
68
 
40
69
  const { start, stop } = useTask(
41
70
  (delta) => {
@@ -46,44 +75,58 @@
46
75
  return
47
76
  }
48
77
 
78
+ const moveSpeed = getMovementScale() * dt
79
+ const rotateSpeed = 0.1 * MathUtils.DEG2RAD * dt
80
+ const tiltSpeed = 0.05 * MathUtils.DEG2RAD * dt
81
+ const dollySpeed = 0.005 * dt
82
+ const zoomSpeed = 0.5 * dt
83
+
49
84
  if (a) {
50
- cameraControls.truck(-0.01 * dt, 0, true)
85
+ cameraControls.truck(-moveSpeed * dt, 0, true)
51
86
  }
52
87
 
53
88
  if (d) {
54
- cameraControls.truck(0.01 * dt, 0, true)
89
+ cameraControls.truck(moveSpeed * dt, 0, true)
55
90
  }
56
91
 
57
92
  if (w) {
58
- cameraControls.forward(0.01 * dt, true)
93
+ cameraControls.forward(moveSpeed * dt, true)
59
94
  }
60
95
 
61
96
  if (s) {
62
- cameraControls.forward(-0.01 * dt, true)
97
+ cameraControls.forward(-moveSpeed * dt, true)
63
98
  }
64
99
 
65
100
  if (r) {
66
- cameraControls.dolly(0.01 * dt, true)
101
+ if (isInstanceOf(cameraControls.camera, 'PerspectiveCamera')) {
102
+ cameraControls.dolly(dollySpeed, true)
103
+ } else {
104
+ cameraControls.zoom(zoomSpeed, true)
105
+ }
67
106
  }
68
107
 
69
108
  if (f) {
70
- cameraControls.dolly(-0.01 * dt, true)
109
+ if (isInstanceOf(cameraControls.camera, 'PerspectiveCamera')) {
110
+ cameraControls.dolly(-dollySpeed, true)
111
+ } else {
112
+ cameraControls.zoom(-zoomSpeed, true)
113
+ }
71
114
  }
72
115
 
73
116
  if (left) {
74
- cameraControls.rotate(-0.1 * MathUtils.DEG2RAD * dt, 0, true)
117
+ cameraControls.rotate(-rotateSpeed, 0, true)
75
118
  }
76
119
 
77
120
  if (right) {
78
- cameraControls.rotate(0.1 * MathUtils.DEG2RAD * dt, 0, true)
121
+ cameraControls.rotate(rotateSpeed, 0, true)
79
122
  }
80
123
 
81
124
  if (up) {
82
- cameraControls.rotate(0, -0.05 * MathUtils.DEG2RAD * dt, true)
125
+ cameraControls.rotate(0, -tiltSpeed, true)
83
126
  }
84
127
 
85
128
  if (down) {
86
- cameraControls.rotate(0, 0.05 * MathUtils.DEG2RAD * dt, true)
129
+ cameraControls.rotate(0, tiltSpeed, true)
87
130
  }
88
131
  },
89
132
  {
@@ -93,7 +136,7 @@
93
136
  )
94
137
 
95
138
  $effect.pre(() => {
96
- if (any) {
139
+ if (anyKeysPressed) {
97
140
  start()
98
141
  } else {
99
142
  stop()
@@ -35,7 +35,7 @@
35
35
  intersection = event.intersections[0]
36
36
 
37
37
  // Only handle axis restrictions if a first point has been placed
38
- if (!p1) {
38
+ if (!p1 || !intersection) {
39
39
  return
40
40
  }
41
41
 
@@ -127,7 +127,7 @@
127
127
  {#if enabled}
128
128
  {#if intersection && step !== 'p2'}
129
129
  <MeasurePoint
130
- position={intersection?.point.toArray()}
130
+ position={intersection.point.toArray()}
131
131
  opacity={0.5}
132
132
  />
133
133
  {/if}
@@ -51,7 +51,7 @@
51
51
  })
52
52
  leftPad.trigger.on('up', () => (dragging = false))
53
53
 
54
- const dragTask = useTask(
54
+ useTask(
55
55
  () => {
56
56
  if (!$left || !rigidBody) return
57
57
 
@@ -62,11 +62,11 @@
62
62
  rigidBody.setNextKinematicTranslation({ x: position.x, y: position.y, z: position.z })
63
63
  },
64
64
  {
65
- autoStart: false,
65
+ running: () => dragging,
66
66
  }
67
67
  )
68
68
 
69
- const rotateTask = useTask(
69
+ useTask(
70
70
  () => {
71
71
  if (!$right || !rigidBody) return
72
72
 
@@ -80,24 +80,8 @@
80
80
 
81
81
  rigidBody.setNextKinematicRotation(quaternion.setFromEuler(euler))
82
82
  },
83
- { autoStart: false }
83
+ { running: () => rotating }
84
84
  )
85
-
86
- $effect.pre(() => {
87
- if (dragging) {
88
- dragTask.start()
89
- } else {
90
- dragTask.stop()
91
- }
92
- })
93
-
94
- $effect.pre(() => {
95
- if (rotating) {
96
- rotateTask.start()
97
- } else {
98
- rotateTask.stop()
99
- }
100
- })
101
85
  </script>
102
86
 
103
87
  <T
@@ -18,13 +18,7 @@
18
18
  const settings = useSettings()
19
19
  const armClient = useArmClient()
20
20
  const partID = usePartID()
21
-
22
- let resources: ReturnType<typeof useResourceNames> | undefined
23
- try {
24
- resources = useResourceNames(() => partID.current)
25
- } catch (error) {
26
- console.warn('Failed to get resources, robot may not be connected yet:', error)
27
- }
21
+ let resources = useResourceNames(() => partID.current)
28
22
 
29
23
  // Get available arms and grippers
30
24
  const armNames = $derived(armClient.names || [])
@@ -1,2 +1,9 @@
1
+ import { type Object3D, type Quaternion, type Vector3 } from 'three';
2
+ interface Context {
3
+ createAnchor: (position: Vector3, orientation: Quaternion) => Promise<XRAnchor> | undefined;
4
+ bindAnchorObject: (anchor: XRAnchor, object: Object3D) => void;
5
+ unbindAnchorObject: (anchor: XRAnchor) => void;
6
+ }
1
7
  export declare const provideAnchors: () => void;
2
- export declare const useAnchors: () => void;
8
+ export declare const useAnchors: () => Context;
9
+ export {};
@@ -1,28 +1,32 @@
1
- import { useTask, useThrelte, watch } from '@threlte/core';
1
+ import { useTask, useThrelte } from '@threlte/core';
2
2
  import { useXR } from '@threlte/xr';
3
3
  import { getContext, setContext } from 'svelte';
4
- import { Matrix4 } from 'three';
4
+ import { fromStore } from 'svelte/store';
5
+ import {} from 'three';
5
6
  const key = Symbol('anchors-context');
6
7
  export const provideAnchors = () => {
7
- const matrix4 = new Matrix4();
8
8
  const { renderer } = useThrelte();
9
- const { isPresenting } = useXR();
9
+ const { isPresenting: isPresentingStore } = useXR();
10
+ const isPresenting = fromStore(isPresentingStore);
10
11
  const map = new WeakMap();
11
- let space = renderer.xr.getReferenceSpace();
12
- const createAnchor = (position, orientation) => {
13
- space ??= renderer.xr.getReferenceSpace();
14
- if (space === null)
12
+ const createAnchor = (position, quaternion) => {
13
+ const space = renderer.xr.getReferenceSpace();
14
+ const frame = renderer.xr.getFrame();
15
+ if (!space || !frame)
15
16
  return;
16
- const pose = new XRRigidTransform(position, orientation);
17
- return renderer.xr.getFrame().createAnchor?.(pose, space);
17
+ const pose = new XRRigidTransform({ x: position.x, y: position.y, z: position.z }, { x: quaternion.x, y: quaternion.y, z: quaternion.z, w: quaternion.w });
18
+ return frame.createAnchor?.(pose, space);
18
19
  };
19
- const { start, stop } = useTask(() => {
20
- space ??= renderer.xr.getReferenceSpace();
21
- if (!space) {
22
- return;
23
- }
20
+ const bindAnchorObject = (anchor, object) => {
21
+ map.set(anchor, object);
22
+ };
23
+ const unbindAnchorObject = (anchor) => {
24
+ map.delete(anchor);
25
+ };
26
+ useTask(() => {
27
+ const space = renderer.xr.getReferenceSpace();
24
28
  const frame = renderer.xr.getFrame();
25
- if (!frame.trackedAnchors) {
29
+ if (!space || !frame?.trackedAnchors) {
26
30
  return;
27
31
  }
28
32
  for (const anchor of frame.trackedAnchors) {
@@ -34,22 +38,18 @@ export const provideAnchors = () => {
34
38
  if (!anchorPose) {
35
39
  continue;
36
40
  }
37
- matrix4.fromArray(anchorPose.transform.matrix);
38
- object3d.applyMatrix4(matrix4);
39
- }
40
- });
41
- watch(isPresenting, ($isPresenting) => {
42
- if ($isPresenting) {
43
- start();
44
- }
45
- else {
46
- stop();
41
+ object3d.matrixAutoUpdate = false;
42
+ object3d.matrix.fromArray(anchorPose.transform.matrix);
47
43
  }
44
+ }, {
45
+ running: () => isPresenting.current,
48
46
  });
49
47
  setContext(key, {
50
48
  createAnchor,
49
+ bindAnchorObject,
50
+ unbindAnchorObject,
51
51
  });
52
52
  };
53
53
  export const useAnchors = () => {
54
- getContext(key);
54
+ return getContext(key);
55
55
  };
@@ -20,8 +20,10 @@ export const provideFrames = (partID) => {
20
20
  const connectionStatus = useConnectionStatus(partID);
21
21
  const machineStatus = useMachineStatus(partID);
22
22
  const logs = useLogs();
23
+ let didRecentlyEdit = $state(false);
23
24
  const isEditMode = $derived(environment.current.viewerMode === 'edit');
24
25
  const query = createRobotQuery(client, 'frameSystemConfig', () => ({
26
+ refetchOnWindowFocus: false,
25
27
  enabled: partID() !== '' && !isEditMode,
26
28
  }));
27
29
  const revision = $derived(machineStatus.current?.config?.revision);
@@ -41,7 +43,8 @@ export const provideFrames = (partID) => {
41
43
  }
42
44
  frames[frame.referenceFrame] = frame;
43
45
  }
44
- if (isEditMode || connectionStatus.current === MachineConnectionEvent.DISCONNECTED) {
46
+ // Let config frames take priority if the user has made edits
47
+ if (didRecentlyEdit || connectionStatus.current === MachineConnectionEvent.DISCONNECTED) {
45
48
  const mergedFrames = {
46
49
  ...frames,
47
50
  ...configFrames.current,
@@ -56,9 +59,8 @@ export const provideFrames = (partID) => {
56
59
  return mergedFrames;
57
60
  }
58
61
  /**
59
- * If we're not in edit mode and we have a robot connection,
62
+ * If we haven't edited and we have a robot connection,
60
63
  * we only use frames reported by the machine
61
- *
62
64
  */
63
65
  return frames;
64
66
  });
@@ -69,6 +71,11 @@ export const provideFrames = (partID) => {
69
71
  untrack(() => query.refetch());
70
72
  }
71
73
  });
74
+ $effect(() => {
75
+ if (isEditMode) {
76
+ didRecentlyEdit = true;
77
+ }
78
+ });
72
79
  $effect.pre(() => {
73
80
  const currentResourcesByName = resourceByName.current;
74
81
  const currentPartID = partID();
@@ -136,11 +143,19 @@ export const provideFrames = (partID) => {
136
143
  if (!active[entityKey]) {
137
144
  entity?.destroy();
138
145
  entities.delete(entityKey);
139
- continue;
140
146
  }
141
147
  }
142
148
  });
143
149
  });
150
+ // Clear all entities on unmount
151
+ $effect(() => {
152
+ return () => {
153
+ for (const [, entity] of entities) {
154
+ entity?.destroy();
155
+ }
156
+ entities.clear();
157
+ };
158
+ });
144
159
  setContext(key, {
145
160
  get current() {
146
161
  return current;
@@ -117,14 +117,24 @@ export const provideGeometries = (partID) => {
117
117
  if (!activeQueryKeys.has(queryKey)) {
118
118
  for (const key of keys) {
119
119
  const entity = entities.get(key);
120
- if (entity && world.has(entity))
120
+ if (entity && world.has(entity)) {
121
121
  entity.destroy();
122
+ }
122
123
  entities.delete(key);
123
124
  }
124
125
  queryEntityKeys.delete(queryKey);
125
126
  }
126
127
  }
127
128
  });
129
+ // Clear all entities on unmount
130
+ $effect(() => {
131
+ return () => {
132
+ for (const [, entity] of entities) {
133
+ entity?.destroy();
134
+ }
135
+ entities.clear();
136
+ };
137
+ });
128
138
  setContext(key, {
129
139
  refetch() {
130
140
  for (const [, query] of queries) {
@@ -194,6 +194,14 @@ export const providePointcloudObjects = (partID) => {
194
194
  }
195
195
  }
196
196
  });
197
+ $effect(() => {
198
+ return () => {
199
+ for (const [, entity] of entities) {
200
+ entity.destroy();
201
+ }
202
+ entities.clear();
203
+ };
204
+ });
197
205
  setContext(key, {
198
206
  refetch() {
199
207
  for (const [, query] of queries) {
@@ -133,6 +133,14 @@ export const providePointclouds = (partID) => {
133
133
  }
134
134
  }
135
135
  });
136
+ $effect(() => {
137
+ return () => {
138
+ for (const [, entity] of entities) {
139
+ entity.destroy();
140
+ }
141
+ entities.clear();
142
+ };
143
+ });
136
144
  setContext(key, {
137
145
  refetch() {
138
146
  for (const [, query] of queries) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "1.15.3",
3
+ "version": "1.15.5",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -38,7 +38,6 @@
38
38
  "@viamrobotics/prime-core": "0.1.5",
39
39
  "@viamrobotics/sdk": "0.58.0",
40
40
  "@viamrobotics/svelte-sdk": "1.0.1",
41
- "@vitejs/plugin-basic-ssl": "2.1.0",
42
41
  "@vitest/coverage-v8": "^3.2.4",
43
42
  "@zag-js/collapsible": "1.22.1",
44
43
  "@zag-js/floating-panel": "1.22.1",
@@ -145,6 +144,7 @@
145
144
  },
146
145
  "scripts": {
147
146
  "dev": "tsx server/check-bun && bun run server/server.ts",
147
+ "dev:https": "vite dev -- --https",
148
148
  "build": "vite build && npm run prepack",
149
149
  "build:workers": "node scripts/build-workers.js",
150
150
  "preview": "vite preview",