@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.
- package/dist/components/KeyboardControls.svelte +57 -14
- package/dist/components/MeasureTool/MeasureTool.svelte +2 -2
- package/dist/components/xr/OriginMarker.svelte +4 -20
- package/dist/components/xr/XRConfigPanel.svelte +1 -7
- package/dist/components/xr/useAnchors.svelte.d.ts +8 -1
- package/dist/components/xr/useAnchors.svelte.js +27 -27
- package/dist/hooks/useFrames.svelte.js +19 -4
- package/dist/hooks/useGeometries.svelte.js +11 -1
- package/dist/hooks/usePointcloudObjects.svelte.js +8 -0
- package/dist/hooks/usePointclouds.svelte.js +8 -0
- package/package.json +2 -2
|
@@ -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
|
|
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(-
|
|
85
|
+
cameraControls.truck(-moveSpeed * dt, 0, true)
|
|
51
86
|
}
|
|
52
87
|
|
|
53
88
|
if (d) {
|
|
54
|
-
cameraControls.truck(
|
|
89
|
+
cameraControls.truck(moveSpeed * dt, 0, true)
|
|
55
90
|
}
|
|
56
91
|
|
|
57
92
|
if (w) {
|
|
58
|
-
cameraControls.forward(
|
|
93
|
+
cameraControls.forward(moveSpeed * dt, true)
|
|
59
94
|
}
|
|
60
95
|
|
|
61
96
|
if (s) {
|
|
62
|
-
cameraControls.forward(-
|
|
97
|
+
cameraControls.forward(-moveSpeed * dt, true)
|
|
63
98
|
}
|
|
64
99
|
|
|
65
100
|
if (r) {
|
|
66
|
-
cameraControls.
|
|
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.
|
|
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(-
|
|
117
|
+
cameraControls.rotate(-rotateSpeed, 0, true)
|
|
75
118
|
}
|
|
76
119
|
|
|
77
120
|
if (right) {
|
|
78
|
-
cameraControls.rotate(
|
|
121
|
+
cameraControls.rotate(rotateSpeed, 0, true)
|
|
79
122
|
}
|
|
80
123
|
|
|
81
124
|
if (up) {
|
|
82
|
-
cameraControls.rotate(0, -
|
|
125
|
+
cameraControls.rotate(0, -tiltSpeed, true)
|
|
83
126
|
}
|
|
84
127
|
|
|
85
128
|
if (down) {
|
|
86
|
-
cameraControls.rotate(0,
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
65
|
+
running: () => dragging,
|
|
66
66
|
}
|
|
67
67
|
)
|
|
68
68
|
|
|
69
|
-
|
|
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
|
-
{
|
|
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: () =>
|
|
8
|
+
export declare const useAnchors: () => Context;
|
|
9
|
+
export {};
|
|
@@ -1,28 +1,32 @@
|
|
|
1
|
-
import { useTask, useThrelte
|
|
1
|
+
import { useTask, useThrelte } from '@threlte/core';
|
|
2
2
|
import { useXR } from '@threlte/xr';
|
|
3
3
|
import { getContext, setContext } from 'svelte';
|
|
4
|
-
import {
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (space
|
|
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,
|
|
17
|
-
return
|
|
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
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
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
|
-
|
|
38
|
-
object3d.
|
|
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
|
|
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'
|
|
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
|
+
"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",
|