@viamrobotics/motion-tools 1.15.6 → 1.15.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.
@@ -61,7 +61,6 @@
61
61
 
62
62
  <T.Group
63
63
  position={origin.position}
64
- rotation.x={$isPresenting ? -Math.PI / 2 : 0}
65
64
  rotation.z={origin.rotation}
66
65
  >
67
66
  <PointerMissBox />
@@ -1,15 +1,8 @@
1
- <script
2
- lang="ts"
3
- module
4
- >
5
- import { Parser } from 'expr-eval'
6
-
7
- export const parser = new Parser()
8
- </script>
9
-
10
1
  <script lang="ts">
11
2
  import type { Entity } from 'koota'
12
3
 
4
+ import { compileExpression } from 'filtrex'
5
+
13
6
  import { relations, traits } from '../../ecs'
14
7
  import { useTrait } from '../../ecs'
15
8
  import { SubEntityLinkType } from '../../ecs/relations'
@@ -39,11 +32,12 @@
39
32
  if (linkType !== SubEntityLinkType.HoverLink) {
40
33
  return
41
34
  }
42
- // Index Mapping is a formula with the variable 'index' in it, available operations can be found here: https://github.com/silentmatt/expr-eval/tree/master
35
+ // Index mapping is a formula with the variable 'index' in it.
36
+ // Supported operations: https://github.com/cshaa/filtrex#expressions
43
37
  const indexMapping =
44
38
  displayEntity?.get(relations.SubEntityLink(linkedEntity))?.indexMapping ?? 'index'
45
- const expression = parser.parse(indexMapping)
46
- const resolvedIndex = expression.evaluate({ index: displayedHoverInfo.current.index })
39
+ const evaluate = compileExpression(indexMapping)
40
+ const resolvedIndex = evaluate({ index: displayedHoverInfo.current.index })
47
41
  const linkedHoverInfo = getLinkedHoverInfo(resolvedIndex, linkedEntity)
48
42
  hoverInfo = linkedHoverInfo
49
43
  } else {
@@ -1,5 +1,3 @@
1
- import { Parser } from 'expr-eval';
2
- export declare const parser: Parser;
3
1
  import type { Entity } from 'koota';
4
2
  interface Props {
5
3
  linkedEntity: Entity;
@@ -1,113 +1,74 @@
1
1
  <script lang="ts">
2
- import { RigidBody as RigidBodyType } from '@dimforge/rapier3d-compat'
3
- import { T, useTask } from '@threlte/core'
2
+ import { T } from '@threlte/core'
4
3
  import { Grid, useGamepad } from '@threlte/extras'
5
- import { Collider, RigidBody } from '@threlte/rapier'
6
- import { useController } from '@threlte/xr'
7
- import { Euler, Group, Quaternion, Vector3 } from 'three'
4
+ import { Group, Quaternion, Vector2, Vector3 } from 'three'
8
5
 
6
+ import { useAnchors } from './useAnchors.svelte'
9
7
  import { useOrigin } from './useOrigin.svelte'
10
8
 
11
9
  const origin = useOrigin()
12
-
13
- const height = 0.1
14
- const radius = 0.05
10
+ const anchors = useAnchors()
15
11
 
16
12
  const group = new Group()
17
- const innerGroup = new Group()
18
-
19
- const vec3 = new Vector3()
20
-
21
- const quaternion = new Quaternion()
22
- const euler = new Euler()
23
-
24
- const offset = new Vector3()
25
-
26
- const position = new Vector3()
27
-
28
- let dragging = $state(false)
29
- let rotating = $state(false)
30
-
31
- let currentDistance = 0
32
- const rotateDown = new Vector3()
33
-
34
- let rigidBody = $state<RigidBodyType>()
35
-
36
- const left = useController('left')
37
- const right = useController('right')
13
+ const anchorObject = new Group()
38
14
 
39
15
  const leftPad = useGamepad({ xr: true, hand: 'left' })
16
+ const rightPad = useGamepad({ xr: true, hand: 'right' })
40
17
 
41
- leftPad.trigger.on('down', () => {
42
- const grip = $left?.grip
18
+ const speed = 0.05
43
19
 
44
- if (!grip) {
20
+ const vec2 = new Vector2()
21
+ const target = new Vector2()
22
+
23
+ leftPad.thumbstick.on('change', ({ value }) => {
24
+ if (typeof value === 'number') {
45
25
  return
46
26
  }
47
27
 
48
- dragging = true
49
- innerGroup.getWorldPosition(vec3)
50
- offset.copy($left!.grip.position).sub(vec3)
51
- })
52
- leftPad.trigger.on('up', () => (dragging = false))
28
+ const { x: vx, y: vy } = value
29
+ const [x, y, z] = origin.position
30
+ const r = origin.rotation
53
31
 
54
- useTask(
55
- () => {
56
- if (!$left || !rigidBody) return
32
+ origin.set([x, y, z + vy * speed], r + vx * speed)
33
+ })
57
34
 
58
- position.copy($left.grip.position).sub(offset)
35
+ rightPad.thumbstick.on('change', ({ value }) => {
36
+ if (typeof value === 'number') {
37
+ return
38
+ }
59
39
 
60
- origin.set([position.x, position.y, position.z])
40
+ const { x: vx, y: vy } = value
41
+ const [x, y, z] = origin.position
42
+ const r = origin.rotation
61
43
 
62
- rigidBody.setNextKinematicTranslation({ x: position.x, y: position.y, z: position.z })
63
- },
64
- {
65
- running: () => dragging,
66
- }
67
- )
44
+ vec2.set(x, y).lerp(target.set(x + vx * speed, y + vy * speed), 0.5)
68
45
 
69
- useTask(
70
- () => {
71
- if (!$right || !rigidBody) return
46
+ origin.set([vec2.x, vec2.y, z], r)
47
+ })
72
48
 
73
- const distance = rotateDown.distanceToSquared($right.grip.position)
49
+ const vec3 = new Vector3()
50
+ const quaternion = new Quaternion()
74
51
 
75
- const rotation = rigidBody.rotation()
76
- quaternion.copy(rotation)
77
- euler.setFromQuaternion(quaternion)
78
- euler.z = distance + currentDistance
79
- origin.set(undefined, euler.z)
52
+ $effect(() => {
53
+ vec3.fromArray(origin.position)
80
54
 
81
- rigidBody.setNextKinematicRotation(quaternion.setFromEuler(euler))
82
- },
83
- { running: () => rotating }
84
- )
55
+ anchors.createAnchor(vec3, quaternion)?.then((anchor) => {
56
+ anchors.bindAnchorObject(anchor, anchorObject)
57
+ })
58
+ })
85
59
  </script>
86
60
 
87
61
  <T
88
62
  is={group}
89
63
  position={[0, 0.05, 0]}
90
64
  >
91
- <RigidBody
92
- bind:rigidBody
93
- type="kinematicPosition"
94
- >
95
- <Collider
96
- sensor
97
- shape="cone"
98
- args={[height / 2, radius]}
99
- >
100
- <T is={innerGroup}>
101
- <Grid
102
- plane="xy"
103
- position.y={0.05}
104
- fadeDistance={5}
105
- fadeOrigin={new Vector3()}
106
- cellSize={0.1}
107
- cellColor="#fff"
108
- sectionColor="#fff"
109
- />
110
- </T>
111
- </Collider>
112
- </RigidBody>
65
+ <Grid
66
+ plane="xy"
67
+ position.y={0.05}
68
+ fadeDistance={5}
69
+ fadeOrigin={new Vector3()}
70
+ cellSize={0.1}
71
+ cellColor="#fff"
72
+ sectionColor="#fff"
73
+ />
113
74
  </T>
@@ -3,8 +3,6 @@
3
3
  import { Billboard, Text } from '@threlte/extras'
4
4
  import { Mesh, Vector3 } from 'three'
5
5
 
6
- import Draggable from './Draggable.svelte'
7
-
8
6
  const mesh1 = new Mesh()
9
7
  const mesh2 = new Mesh()
10
8
  const distance = new Vector3()
@@ -29,12 +27,10 @@
29
27
  </script>
30
28
 
31
29
  <T.Group position={[-1, 1, 0]}>
32
- <Draggable onPointerEnter={() => null}>
33
- <T is={mesh1}>
34
- <T.SphereGeometry args={[0.05]} />
35
- <T.MeshStandardMaterial />
36
- </T>
37
- </Draggable>
30
+ <T is={mesh1}>
31
+ <T.SphereGeometry args={[0.05]} />
32
+ <T.MeshStandardMaterial />
33
+ </T>
38
34
  </T.Group>
39
35
 
40
36
  <T.Group position={textPosition}>
@@ -44,10 +40,8 @@
44
40
  </T.Group>
45
41
 
46
42
  <T.Group position={[-1.5, 1, 0]}>
47
- <Draggable>
48
- <T is={mesh2}>
49
- <T.SphereGeometry args={[0.05]} />
50
- <T.MeshStandardMaterial />
51
- </T>
52
- </Draggable>
43
+ <T is={mesh2}>
44
+ <T.SphereGeometry args={[0.05]} />
45
+ <T.MeshStandardMaterial />
46
+ </T>
53
47
  </T.Group>
@@ -1,16 +1,18 @@
1
1
  <script lang="ts">
2
- import { T } from '@threlte/core'
3
- import { World } from '@threlte/rapier'
2
+ import { useThrelte } from '@threlte/core'
4
3
  import { useXR, XR, XRButton } from '@threlte/xr'
5
4
  import { SvelteMap } from 'svelte/reactivity'
5
+ import { Quaternion } from 'three'
6
6
 
7
7
  import { usePartID } from '../../hooks/usePartID.svelte'
8
8
  import { useSettings } from '../../hooks/useSettings.svelte'
9
9
 
10
10
  import CameraFeed from './CameraFeed.svelte'
11
- import Controllers from './Controllers.svelte'
11
+ import FrameConfigureControllers from './frame-configure/Controllers.svelte'
12
12
  import JointLimitsWidget from './JointLimitsWidget.svelte'
13
13
  import OriginMarker from './OriginMarker.svelte'
14
+ import TeleopControllers from './teleop/Controllers.svelte'
15
+ import { provideAnchors } from './useAnchors.svelte'
14
16
  import { useOrigin } from './useOrigin.svelte'
15
17
  import XRToast from './XRToast.svelte'
16
18
 
@@ -19,6 +21,7 @@
19
21
  const { isPresenting } = useXR()
20
22
  const settings = useSettings()
21
23
  const origin = useOrigin()
24
+ provideAnchors()
22
25
  const enableXR = $derived(settings.current.enableXR)
23
26
 
24
27
  const partID = usePartID()
@@ -44,6 +47,26 @@
44
47
  const controllerConfig = $derived(settings.current.xrController)
45
48
  const leftArmName = $derived(controllerConfig.left.armName)
46
49
  const rightArmName = $derived(controllerConfig.right.armName)
50
+
51
+ const { renderer } = useThrelte()
52
+
53
+ // Move into Viam's coordinate system. This basically accomplishes
54
+ // the same thing as setting z up in the Camera component.
55
+ $effect(() => {
56
+ if ($isPresenting) {
57
+ const q = new Quaternion().setFromAxisAngle({ x: 1, y: 0, z: 0 }, -Math.PI / 2)
58
+
59
+ // after the XR session has started and a reference space exists:
60
+ const baseRefSpace = renderer.xr.getReferenceSpace()
61
+ if (baseRefSpace) {
62
+ const rotatedRefSpace = baseRefSpace.getOffsetReferenceSpace(
63
+ new XRRigidTransform({ x: 0, y: 0, z: 0, w: 1 }, { x: q.x, y: q.y, z: q.z, w: q.w })
64
+ )
65
+
66
+ renderer.xr.setReferenceSpace(rotatedRefSpace)
67
+ }
68
+ }
69
+ })
47
70
  </script>
48
71
 
49
72
  {#if enableXR}
@@ -93,15 +116,13 @@
93
116
 
94
117
  <XRToast />
95
118
 
96
- <World>
97
- <Controllers />
119
+ {#if settings.current.xrMode === 'arm-teleop'}
120
+ <TeleopControllers />
121
+ {:else if settings.current.xrMode === 'frame-configure'}
122
+ <FrameConfigureControllers />
123
+ {/if}
98
124
 
99
- <T.Group position.z={-2}>
100
- <T.Group rotation.x={$isPresenting ? -Math.PI / 2 : 0}>
101
- <OriginMarker />
102
- </T.Group>
103
- </T.Group>
104
- </World>
125
+ <OriginMarker />
105
126
  </XR>
106
127
 
107
128
  <XRButton
@@ -0,0 +1,6 @@
1
+ <script lang="ts">
2
+ import { Controller } from '@threlte/xr'
3
+ </script>
4
+
5
+ <Controller left />
6
+ <Controller right />
@@ -11,8 +11,8 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
11
11
  };
12
12
  z_$$bindings?: Bindings;
13
13
  }
14
- declare const HandCollider: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
14
+ declare const Controllers: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
15
  [evt: string]: CustomEvent<any>;
16
16
  }, {}, {}, string>;
17
- type HandCollider = InstanceType<typeof HandCollider>;
18
- export default HandCollider;
17
+ type Controllers = InstanceType<typeof Controllers>;
18
+ export default Controllers;
@@ -1,11 +1,9 @@
1
1
  <script lang="ts">
2
- import { RigidBody } from '@threlte/rapier'
3
2
  import { Controller } from '@threlte/xr'
4
3
 
5
- import { useSettings } from '../../hooks/useSettings.svelte'
4
+ import { useSettings } from '../../../hooks/useSettings.svelte'
6
5
 
7
- import ArmTeleop from './ArmTeleop.svelte'
8
- import HandCollider from './HandCollider.svelte'
6
+ import ArmTeleop from '../ArmTeleop.svelte'
9
7
 
10
8
  const settings = useSettings()
11
9
 
@@ -25,21 +23,8 @@
25
23
  const rightRotationEnabled = $derived(config.right.rotationEnabled)
26
24
  </script>
27
25
 
28
- <Controller left>
29
- {#snippet grip()}
30
- <RigidBody type="kinematicPosition">
31
- <HandCollider />
32
- </RigidBody>
33
- {/snippet}
34
- </Controller>
35
-
36
- <Controller right>
37
- {#snippet grip()}
38
- <RigidBody type="kinematicPosition">
39
- <HandCollider />
40
- </RigidBody>
41
- {/snippet}
42
- </Controller>
26
+ <Controller left />
27
+ <Controller right />
43
28
 
44
29
  <!-- Left Controller Arm Teleop -->
45
30
  {#if leftArmName}
@@ -57,7 +57,7 @@ export const provide3DModels = (partID) => {
57
57
  }
58
58
  };
59
59
  $effect(() => {
60
- const shouldFetchModels = settings.current.isLoaded && settings.current.renderArmModels.includes('model');
60
+ const shouldFetchModels = settings.isLoaded && settings.current.renderArmModels.includes('model');
61
61
  if (shouldFetchModels) {
62
62
  fetch3DModels();
63
63
  }
@@ -1,5 +1,4 @@
1
1
  export interface Settings {
2
- isLoaded: boolean;
3
2
  cameraMode: 'orthographic' | 'perspective';
4
3
  transforming: boolean;
5
4
  snapping: boolean;
@@ -19,12 +18,13 @@ export interface Settings {
19
18
  enableLabels: boolean;
20
19
  enableKeybindings: boolean;
21
20
  enableQueryDevtools: boolean;
22
- enableXR: boolean;
23
21
  enableArmPositionsWidget: boolean;
24
22
  openCameraWidgets: Record<string, string[]>;
25
23
  renderStats: boolean;
26
24
  renderArmModels: 'colliders' | 'colliders+model' | 'model';
27
25
  renderSubEntityHoverDetail: boolean;
26
+ enableXR: boolean;
27
+ xrMode: 'frame-configure' | 'arm-teleop';
28
28
  xrController: {
29
29
  left: {
30
30
  armName?: string;
@@ -42,6 +42,8 @@ export interface Settings {
42
42
  }
43
43
  interface Context {
44
44
  current: Settings;
45
+ isLoaded: boolean;
46
+ merge(value: Settings): void;
45
47
  }
46
48
  export declare const provideSettings: () => Context;
47
49
  export declare const useSettings: () => Context;
@@ -2,7 +2,6 @@ import { get, set } from 'idb-keyval';
2
2
  import { getContext, setContext } from 'svelte';
3
3
  const key = Symbol('dashboard-context');
4
4
  const defaults = () => ({
5
- isLoaded: false,
6
5
  cameraMode: 'perspective',
7
6
  transforming: false,
8
7
  snapping: false,
@@ -22,12 +21,13 @@ const defaults = () => ({
22
21
  enableLabels: false,
23
22
  enableKeybindings: true,
24
23
  enableQueryDevtools: false,
25
- enableXR: false,
26
24
  enableArmPositionsWidget: false,
27
25
  openCameraWidgets: {},
28
26
  renderStats: false,
29
27
  renderArmModels: 'colliders+model',
30
28
  renderSubEntityHoverDetail: false,
29
+ enableXR: false,
30
+ xrMode: 'frame-configure',
31
31
  xrController: {
32
32
  left: {
33
33
  scaleFactor: 1,
@@ -40,17 +40,19 @@ const defaults = () => ({
40
40
  },
41
41
  });
42
42
  export const provideSettings = () => {
43
+ let isLoaded = $state(false);
43
44
  let settings = $state(defaults());
44
- let settingsLoaded = $state(false);
45
- get('motion-tools-settings').then((response) => {
45
+ get('motion-tools-settings')
46
+ .then((response) => {
46
47
  if (response) {
47
48
  settings = { ...settings, ...response };
48
49
  }
49
- settingsLoaded = true;
50
- settings.isLoaded = true;
50
+ })
51
+ .finally(() => {
52
+ isLoaded = true;
51
53
  });
52
54
  $effect(() => {
53
- if (settingsLoaded) {
55
+ if (isLoaded) {
54
56
  set('motion-tools-settings', $state.snapshot(settings));
55
57
  }
56
58
  });
@@ -61,6 +63,12 @@ export const provideSettings = () => {
61
63
  set current(value) {
62
64
  settings = value;
63
65
  },
66
+ get isLoaded() {
67
+ return isLoaded;
68
+ },
69
+ merge(value) {
70
+ settings = { ...settings, ...value };
71
+ },
64
72
  };
65
73
  setContext(key, context);
66
74
  return context;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "1.15.6",
3
+ "version": "1.15.7",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -136,7 +136,7 @@
136
136
  "@neodrag/svelte": "^2.3.3",
137
137
  "@tanstack/svelte-query-devtools": "^6.0.2",
138
138
  "earcut": "^3.0.2",
139
- "expr-eval": "^2.0.2",
139
+ "filtrex": "^3.1.0",
140
140
  "koota": "0.6.5",
141
141
  "lodash-es": "4.17.23",
142
142
  "three-mesh-bvh": "^0.9.8",
@@ -1,102 +0,0 @@
1
- <script lang="ts">
2
- import type { Snippet } from 'svelte'
3
-
4
- import { RigidBody as RigidBodyType } from '@dimforge/rapier3d-compat'
5
- import { T, useTask } from '@threlte/core'
6
- import { useGamepad } from '@threlte/extras'
7
- import { AutoColliders, RigidBody } from '@threlte/rapier'
8
- import { useController } from '@threlte/xr'
9
- import { Group, Vector3 } from 'three'
10
-
11
- interface Props {
12
- children: Snippet
13
- onPointerEnter?: () => void
14
- onPointerLeave?: () => void
15
- onPointerDown?: () => void
16
- onPointerUp?: () => void
17
- }
18
-
19
- let { onPointerEnter, onPointerLeave, onPointerDown, onPointerUp, children }: Props = $props()
20
-
21
- let hovering = $state(false)
22
-
23
- let dragging = $state(false)
24
- let rigidBody: RigidBodyType | undefined = $state()
25
-
26
- const group = new Group()
27
- const vec3 = new Vector3()
28
- const offset = new Vector3()
29
- const position = new Vector3()
30
-
31
- const left = useController('left')
32
- const right = useController('right')
33
- const leftPad = useGamepad({ xr: true, hand: 'left' })
34
- const rightPad = useGamepad({ xr: true, hand: 'right' })
35
-
36
- leftPad.trigger.on('down', () => {
37
- if (!$left) return
38
-
39
- dragging = true
40
- group.getWorldPosition(vec3)
41
- offset.copy($left.grip.position).sub(vec3)
42
- onPointerDown?.()
43
- })
44
-
45
- leftPad.trigger.on('up', () => {
46
- dragging = false
47
- onPointerUp?.()
48
- })
49
-
50
- rightPad.trigger.on('down', () => {
51
- if (!$right) return
52
-
53
- dragging = true
54
- group.getWorldPosition(vec3)
55
- offset.copy($right.grip.position).sub(vec3)
56
- onPointerDown?.()
57
- })
58
-
59
- rightPad.trigger.on('up', () => {
60
- dragging = true
61
- onPointerUp?.()
62
- })
63
-
64
- const onsensorenter = () => {
65
- hovering = true
66
- onPointerEnter?.()
67
- }
68
-
69
- const onsensorexit = () => {
70
- hovering = false
71
- onPointerLeave?.()
72
- }
73
-
74
- const { start, stop } = useTask(
75
- () => {
76
- if (!$left || !rigidBody) return
77
-
78
- position.copy($left.grip.position).sub(offset)
79
-
80
- rigidBody.setNextKinematicTranslation({ x: position.x, y: position.y, z: position.z })
81
- },
82
- { autoStart: false }
83
- )
84
-
85
- $effect(() => (hovering && dragging ? start() : stop()))
86
- </script>
87
-
88
- <T is={group}>
89
- <RigidBody
90
- bind:rigidBody
91
- type="kinematicPosition"
92
- >
93
- <AutoColliders
94
- sensor
95
- shape="convexHull"
96
- {onsensorenter}
97
- {onsensorexit}
98
- >
99
- {@render children?.()}
100
- </AutoColliders>
101
- </RigidBody>
102
- </T>
@@ -1,11 +0,0 @@
1
- import type { Snippet } from 'svelte';
2
- interface Props {
3
- children: Snippet;
4
- onPointerEnter?: () => void;
5
- onPointerLeave?: () => void;
6
- onPointerDown?: () => void;
7
- onPointerUp?: () => void;
8
- }
9
- declare const Draggable: import("svelte").Component<Props, {}, "">;
10
- type Draggable = ReturnType<typeof Draggable>;
11
- export default Draggable;
@@ -1,20 +0,0 @@
1
- <script lang="ts">
2
- import type { Group } from 'three'
3
-
4
- import { useParent, useTask } from '@threlte/core'
5
- import { Collider, useRigidBody } from '@threlte/rapier'
6
-
7
- const parent = useParent()
8
- const rb = useRigidBody()
9
-
10
- useTask(() => {
11
- const { position } = $parent as Group
12
- rb?.setNextKinematicTranslation({ x: position.x, y: position.y, z: position.z })
13
- })
14
- </script>
15
-
16
- <Collider
17
- sensor
18
- shape="ball"
19
- args={[0.1]}
20
- />