@viamrobotics/motion-tools 1.3.2 → 1.3.4

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,14 +1,11 @@
1
1
  <script lang="ts">
2
- import { T } from '@threlte/core'
3
- import { Portal } from '@threlte/extras'
4
- import { InstancedArrows } from '../three/InstancedArrows/InstancedArrows'
5
- import { traits, useWorld } from '../ecs'
2
+ import { InstancedArrows } from '../../three/InstancedArrows/InstancedArrows'
3
+ import { traits, useWorld } from '../../ecs'
6
4
  import type { Entity } from 'koota'
7
- import { STRIDE } from '../buffer'
8
- import { useObjectEvents } from '../hooks/useObjectEvents.svelte'
5
+ import { STRIDE } from '../../buffer'
9
6
  import { SvelteMap } from 'svelte/reactivity'
10
7
  import { Color } from 'three'
11
- import { meshBoundsRaycast } from '../three/InstancedArrows/raycast'
8
+ import Arrows from './Arrows.svelte'
12
9
 
13
10
  const world = useWorld()
14
11
 
@@ -59,23 +56,8 @@
59
56
  </script>
60
57
 
61
58
  {#each map as [entity, arrows] (entity)}
62
- {@const events = useObjectEvents(() => entity)}
63
- <Portal id={entity.get(traits.Parent)}>
64
- <T
65
- is={arrows}
66
- name={entity}
67
- >
68
- <T
69
- is={arrows.headMesh}
70
- bvh={{ enabled: false }}
71
- raycast={() => null}
72
- />
73
- <T
74
- is={arrows.shaftMesh}
75
- bvh={{ enabled: false }}
76
- raycast={meshBoundsRaycast}
77
- {...events}
78
- />
79
- </T>
80
- </Portal>
59
+ <Arrows
60
+ {entity}
61
+ {arrows}
62
+ />
81
63
  {/each}
@@ -0,0 +1,3 @@
1
+ declare const ArrowGroups: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type ArrowGroups = ReturnType<typeof ArrowGroups>;
3
+ export default ArrowGroups;
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import { T } from '@threlte/core'
3
+ import { Portal } from '@threlte/extras'
4
+ import type { Entity } from 'koota'
5
+ import { traits } from '../../ecs'
6
+ import { useObjectEvents } from '../../hooks/useObjectEvents.svelte'
7
+ import type { InstancedArrows } from '../../three/InstancedArrows/InstancedArrows'
8
+ import { meshBoundsRaycast } from '../../three/InstancedArrows/raycast'
9
+
10
+ interface Props {
11
+ entity: Entity
12
+ arrows: InstancedArrows
13
+ }
14
+
15
+ let { entity, arrows }: Props = $props()
16
+
17
+ const events = useObjectEvents(() => entity)
18
+ </script>
19
+
20
+ <Portal id={entity.get(traits.Parent)}>
21
+ <T
22
+ is={arrows}
23
+ name={entity}
24
+ >
25
+ <T
26
+ is={arrows.headMesh}
27
+ bvh={{ enabled: false }}
28
+ raycast={() => null}
29
+ visible={events.visible}
30
+ />
31
+ <T
32
+ is={arrows.shaftMesh}
33
+ bvh={{ enabled: false }}
34
+ raycast={meshBoundsRaycast}
35
+ {...events}
36
+ />
37
+ </T>
38
+ </Portal>
@@ -0,0 +1,9 @@
1
+ import type { Entity } from 'koota';
2
+ import type { InstancedArrows } from '../../three/InstancedArrows/InstancedArrows';
3
+ interface Props {
4
+ entity: Entity;
5
+ arrows: InstancedArrows;
6
+ }
7
+ declare const Arrows: import("svelte").Component<Props, {}, "">;
8
+ type Arrows = ReturnType<typeof Arrows>;
9
+ export default Arrows;
@@ -14,7 +14,7 @@
14
14
  import { draggable } from '@neodrag/svelte'
15
15
  import { Check, Copy } from 'lucide-svelte'
16
16
  import { useTask } from '@threlte/core'
17
- import { Button, Icon, Select, Input } from '@viamrobotics/prime-core'
17
+ import { Button, Icon, Select, Input, Tooltip } from '@viamrobotics/prime-core'
18
18
  import {
19
19
  useSelectedEntity,
20
20
  useFocusedEntity,
@@ -28,14 +28,13 @@
28
28
  import { traits, useTrait } from '../ecs'
29
29
  import { useResourceByName } from '../hooks/useResourceByName.svelte'
30
30
  import { PersistedState } from 'runed'
31
+ import { useCameraControls } from '../hooks/useControls.svelte'
31
32
 
32
33
  const { ...rest } = $props()
33
34
 
34
- const dragPosition = new PersistedState<Vector2Like | undefined>(
35
- 'details-drag-position',
36
- undefined
37
- )
35
+ const dragPosition = new PersistedState<Vector2Like>('details-drag-position', { x: 0, y: 0 })
38
36
 
37
+ const controls = useCameraControls()
39
38
  const resourceByName = useResourceByName()
40
39
  const frames = useFrames()
41
40
  const partConfig = usePartConfig()
@@ -247,13 +246,46 @@
247
246
  {...rest}
248
247
  >
249
248
  <div class="flex items-center justify-between gap-2 pb-2">
250
- <div class="flex items-center gap-1">
249
+ <div class="flex w-[90%] items-center gap-1">
251
250
  <button bind:this={dragElement}>
252
251
  <Icon name="drag" />
253
252
  </button>
254
- <strong>{name.current}</strong>
253
+ <strong class="overflow-hidden text-nowrap text-ellipsis">{name.current}</strong>
255
254
  <span class="text-subtle-2">{resourceName?.subtype}</span>
256
255
  </div>
256
+
257
+ {#if object3d}
258
+ <Tooltip
259
+ let:tooltipID
260
+ location="bottom"
261
+ >
262
+ <button
263
+ class="text-subtle-2"
264
+ aria-describedby={tooltipID}
265
+ onclick={() => {
266
+ const padding = 0.4
267
+
268
+ if (!controls.current) return
269
+
270
+ const { azimuthAngle, polarAngle } = controls.current
271
+
272
+ controls.current.fitToBox(object3d, true, {
273
+ paddingTop: padding,
274
+ paddingBottom: padding,
275
+ paddingLeft: padding,
276
+ paddingRight: padding,
277
+ })
278
+
279
+ // Preserve previous rotation
280
+ controls.current?.rotateAzimuthTo(azimuthAngle, true)
281
+ controls.current?.rotatePolarTo(polarAngle, true)
282
+ }}
283
+ >
284
+ <Icon name="image-filter-center-focus" />
285
+ </button>
286
+ <p slot="description">Zoom to object</p>
287
+ </Tooltip>
288
+ {/if}
257
289
  </div>
258
290
 
259
291
  <div class="border-medium -mx-2 w-[100%+0.5rem] border-b"></div>
@@ -132,6 +132,7 @@
132
132
  <T
133
133
  is={mesh}
134
134
  name={entity}
135
+ userData.name={name}
135
136
  bvh={{ enabled: geometryType === 'buffer' }}
136
137
  >
137
138
  {#if model && renderMode.includes('model')}
@@ -3,8 +3,9 @@
3
3
  import { useTask } from '@threlte/core'
4
4
  import type { CameraControlsRef } from '@threlte/extras'
5
5
  import { PressedKeys } from 'runed'
6
- import { useFocusedEntity } from '../hooks/useSelection.svelte'
6
+ import { useFocusedEntity, useSelectedEntity } from '../hooks/useSelection.svelte'
7
7
  import { useSettings } from '../hooks/useSettings.svelte'
8
+ import { useVisibility } from '../hooks/useVisibility.svelte'
8
9
 
9
10
  interface Props {
10
11
  cameraControls: CameraControlsRef
@@ -13,7 +14,12 @@
13
14
  let { cameraControls }: Props = $props()
14
15
 
15
16
  const focusedEntity = useFocusedEntity()
17
+ const selectedEntity = useSelectedEntity()
18
+
19
+ const entity = $derived(focusedEntity.current ?? selectedEntity.current)
20
+
16
21
  const settings = useSettings()
22
+ const visibility = useVisibility()
17
23
 
18
24
  const keys = new PressedKeys()
19
25
  const meta = $derived(keys.has('meta'))
@@ -28,6 +34,7 @@
28
34
  const down = $derived(keys.has('arrowdown'))
29
35
  const right = $derived(keys.has('arrowright'))
30
36
  const any = $derived(w || s || a || d || r || f || up || left || down || right)
37
+
31
38
  const { start, stop } = useTask(
32
39
  (delta) => {
33
40
  const dt = delta * 1000
@@ -117,4 +124,24 @@
117
124
  keys.onKeys('x', () => {
118
125
  settings.current.enableXR = !settings.current.enableXR
119
126
  })
127
+
128
+ /**
129
+ * Handler for any keybindings that need to access the event object
130
+ */
131
+ const onkeydown = (event: KeyboardEvent) => {
132
+ const key = event.key.toLowerCase()
133
+
134
+ if (key === 'h') {
135
+ if (!entity) return
136
+
137
+ event.stopImmediatePropagation()
138
+
139
+ const visible = visibility.get(entity) ?? true
140
+
141
+ visibility.set(entity, !visible)
142
+ return
143
+ }
144
+ }
120
145
  </script>
146
+
147
+ <svelte:window {onkeydown} />
@@ -17,7 +17,7 @@
17
17
  import MeasureTool from './MeasureTool.svelte'
18
18
  import PointerMissBox from './PointerMissBox.svelte'
19
19
  import BatchedArrows from './BatchedArrows.svelte'
20
- import Arrows from './Arrows.svelte'
20
+ import Arrows from './Arrows/ArrowGroups.svelte'
21
21
 
22
22
  interface Props {
23
23
  children?: Snippet
@@ -30,17 +30,19 @@
30
30
  const origin = useOrigin()
31
31
 
32
32
  const { raycaster, enabled } = interactivity({
33
- filter: (items) => {
34
- const item = items.find((item) => {
35
- return item.object.visible === undefined || item.object.visible === true
33
+ filter: (intersections) => {
34
+ const match = intersections.find((intersection) => {
35
+ return intersection.object.visible === undefined || intersection.object.visible === true
36
36
  })
37
37
 
38
- return item ? [item] : []
38
+ return match ? [match] : []
39
39
  },
40
40
  })
41
+
41
42
  $effect(() => {
42
43
  enabled.set(!settings.current.enableMeasure)
43
44
  })
45
+
44
46
  raycaster.firstHitOnly = true
45
47
  raycaster.params.Points.threshold = 0.005
46
48
 
@@ -18,7 +18,7 @@
18
18
  const cameras = useResourceNames(() => partID.current, 'camera')
19
19
  const visionServices = useResourceNames(() => partID.current, 'vision')
20
20
  const settings = useSettings()
21
- const { disabledCameras, disabledVisionServicesObjectPointclouds } = useMachineSettings()
21
+ const { disabledCameras, disabledVisionServices } = useMachineSettings()
22
22
  const geometries = useGeometries()
23
23
  const pointclouds = usePointClouds()
24
24
  const { refetchPoses } = useRefetchPoses()
@@ -81,9 +81,9 @@
81
81
  <div class="flex items-center justify-between gap-4 py-2">
82
82
  {visionService.name}
83
83
  <Switch
84
- on={disabledVisionServicesObjectPointclouds.get(visionService.name) !== true}
84
+ on={disabledVisionServices.get(visionService.name) !== true}
85
85
  on:change={(event) => {
86
- disabledVisionServicesObjectPointclouds.set(visionService.name, !event.detail)
86
+ disabledVisionServices.set(visionService.name, !event.detail)
87
87
  }}
88
88
  />
89
89
  </div>
@@ -92,11 +92,14 @@
92
92
  {@const { children = [] } = node}
93
93
  <div
94
94
  {...api.getBranchProps(nodeProps)}
95
- class={{
96
- 'text-disabled': !isVisible,
97
- 'bg-medium': selected,
98
- sticky: true,
99
- }}
95
+ class={[
96
+ 'w-full',
97
+ {
98
+ 'text-disabled': !isVisible,
99
+ 'bg-medium': selected,
100
+ sticky: true,
101
+ },
102
+ ]}
100
103
  >
101
104
  <div {...api.getBranchControlProps(nodeProps)}>
102
105
  <span
@@ -106,7 +109,7 @@
106
109
  <ChevronRight size={14} />
107
110
  </span>
108
111
  <span
109
- class="flex items-center"
112
+ class="flex items-center overflow-hidden text-ellipsis"
110
113
  {...api.getBranchTextProps(nodeProps)}
111
114
  >
112
115
  {node.entity.get(traits.Name)}
@@ -151,7 +154,7 @@
151
154
  class={{ 'flex justify-between': true, 'text-disabled': !isVisible, 'bg-medium': selected }}
152
155
  {...api.getItemProps(nodeProps)}
153
156
  >
154
- <span class="flex items-center gap-1.5">
157
+ <span class="flex items-center gap-1.5 overflow-hidden text-nowrap text-ellipsis">
155
158
  {node.entity.get(traits.Name)}
156
159
  </span>
157
160
 
@@ -6,7 +6,7 @@ export declare const RefreshRates: {
6
6
  type Context = {
7
7
  refreshRates: SvelteMap<string, number>;
8
8
  disabledCameras: SvelteMap<string, boolean>;
9
- disabledVisionServicesObjectPointclouds: SvelteMap<string, boolean>;
9
+ disabledVisionServices: SvelteMap<string, boolean>;
10
10
  };
11
11
  export declare const provideMachineSettings: () => void;
12
12
  export declare const useMachineSettings: () => Context;
@@ -4,7 +4,7 @@ import { SvelteMap } from 'svelte/reactivity';
4
4
  const key = Symbol('polling-rate-context');
5
5
  const refreshRatesKey = 'polling-rate';
6
6
  const disabledCamerasKey = 'disabled-cameras';
7
- const disabledVisionServicesObjectPointcloudsKey = 'disabled-vision-services-object-pointcloud';
7
+ const disabledVisionServicesKey = 'disabled-vision-services-object-pointcloud';
8
8
  export const RefreshRates = {
9
9
  poses: 'poses',
10
10
  pointclouds: 'pointclouds',
@@ -22,15 +22,15 @@ export const provideMachineSettings = () => {
22
22
  [RefreshRates.pointclouds, -1],
23
23
  ]);
24
24
  const disabledCameras = new SvelteMap();
25
- const disabledVisionServicesObjectPointclouds = new SvelteMap();
25
+ const disabledVisionServices = new SvelteMap();
26
26
  get(refreshRatesKey).then((entries) => {
27
27
  setFromEntries(refreshRates, entries);
28
28
  });
29
29
  get(disabledCamerasKey).then((entries) => {
30
30
  setFromEntries(disabledCameras, entries);
31
31
  });
32
- get(disabledVisionServicesObjectPointcloudsKey).then((entries) => {
33
- setFromEntries(disabledVisionServicesObjectPointclouds, entries);
32
+ get(disabledVisionServicesKey).then((entries) => {
33
+ setFromEntries(disabledVisionServices, entries);
34
34
  });
35
35
  $effect(() => {
36
36
  set(refreshRatesKey, [...refreshRates.entries()]);
@@ -38,6 +38,9 @@ export const provideMachineSettings = () => {
38
38
  $effect(() => {
39
39
  set(disabledCamerasKey, [...disabledCameras.entries()]);
40
40
  });
41
+ $effect(() => {
42
+ set(disabledVisionServicesKey, [...disabledVisionServices.entries()]);
43
+ });
41
44
  setContext(key, {
42
45
  get refreshRates() {
43
46
  return refreshRates;
@@ -45,8 +48,8 @@ export const provideMachineSettings = () => {
45
48
  get disabledCameras() {
46
49
  return disabledCameras;
47
50
  },
48
- get disabledVisionServicesObjectPointclouds() {
49
- return disabledVisionServicesObjectPointclouds;
51
+ get disabledVisionServices() {
52
+ return disabledVisionServices;
50
53
  },
51
54
  });
52
55
  };
@@ -3,39 +3,47 @@ import { useFocusedEntity, useSelectedEntity } from './useSelection.svelte';
3
3
  import { useVisibility } from './useVisibility.svelte';
4
4
  import { Vector2 } from 'three';
5
5
  export const useObjectEvents = (entity) => {
6
+ const down = new Vector2();
6
7
  const selectedEntity = useSelectedEntity();
7
8
  const focusedEntity = useFocusedEntity();
8
9
  const visibility = useVisibility();
9
- const down = new Vector2();
10
- const currentEntity = $derived(entity());
11
10
  const cursor = useCursor();
11
+ const currentEntity = $derived(entity());
12
+ const visible = $derived(currentEntity ? (visibility.get(currentEntity) ?? true) : true);
13
+ const onpointerenter = (event) => {
14
+ event.stopPropagation();
15
+ cursor.onPointerEnter();
16
+ };
17
+ const onpointerleave = (event) => {
18
+ event.stopPropagation();
19
+ cursor.onPointerLeave();
20
+ };
21
+ const ondblclick = (event) => {
22
+ event.stopPropagation();
23
+ focusedEntity.set(currentEntity, event.instanceId ?? event.batchId);
24
+ };
25
+ const onpointerdown = (event) => {
26
+ down.copy(event.pointer);
27
+ };
28
+ const onclick = (event) => {
29
+ event.stopPropagation();
30
+ if (down.distanceToSquared(event.pointer) < 0.1) {
31
+ selectedEntity.set(currentEntity, event.instanceId ?? event.batchId);
32
+ }
33
+ };
34
+ $effect(() => {
35
+ if (!visible) {
36
+ cursor.onPointerLeave();
37
+ }
38
+ });
12
39
  return {
13
40
  get visible() {
14
- if (!currentEntity) {
15
- return true;
16
- }
17
- return visibility.get(currentEntity) ?? true;
18
- },
19
- onpointerenter: (event) => {
20
- event.stopPropagation();
21
- cursor.onPointerEnter();
22
- },
23
- onpointerleave: (event) => {
24
- event.stopPropagation();
25
- cursor.onPointerLeave();
26
- },
27
- ondblclick: (event) => {
28
- event.stopPropagation();
29
- focusedEntity.set(currentEntity, event.instanceId ?? event.batchId);
30
- },
31
- onpointerdown: (event) => {
32
- down.copy(event.pointer);
33
- },
34
- onclick: (event) => {
35
- event.stopPropagation();
36
- if (down.distanceToSquared(event.pointer) < 0.1) {
37
- selectedEntity.set(currentEntity, event.instanceId ?? event.batchId);
38
- }
41
+ return visible;
39
42
  },
43
+ onpointerenter,
44
+ onpointerleave,
45
+ ondblclick,
46
+ onpointerdown,
47
+ onclick,
40
48
  };
41
49
  };
@@ -13,7 +13,7 @@ const key = Symbol('pointcloud-object-context');
13
13
  export const providePointcloudObjects = (partID) => {
14
14
  const world = useWorld();
15
15
  const environment = useEnvironment();
16
- const { refreshRates, disabledVisionServicesObjectPointclouds } = useMachineSettings();
16
+ const { refreshRates, disabledVisionServices } = useMachineSettings();
17
17
  const services = useResourceNames(partID, 'vision');
18
18
  const clients = $derived(services.current.map((service) => createResourceClient(VisionClient, partID, () => service.name)));
19
19
  const propQueries = $derived(clients.map((client) => [
@@ -31,7 +31,8 @@ export const providePointcloudObjects = (partID) => {
31
31
  if (environment.current.viewerMode === 'monitor' &&
32
32
  fetchedPropQueries &&
33
33
  client.current?.name &&
34
- interval !== RefetchRates.OFF) {
34
+ interval !== RefetchRates.OFF &&
35
+ disabledVisionServices.get(client.current?.name) !== true) {
35
36
  results.push(client);
36
37
  }
37
38
  }
@@ -47,8 +48,8 @@ export const providePointcloudObjects = (partID) => {
47
48
  $effect(() => {
48
49
  for (const [name, query] of propQueries) {
49
50
  if (name && query.data?.objectPointCloudsSupported === false) {
50
- if (disabledVisionServicesObjectPointclouds.get(name) === undefined) {
51
- disabledVisionServicesObjectPointclouds.set(name, true);
51
+ if (disabledVisionServices.get(name) === undefined) {
52
+ disabledVisionServices.set(name, true);
52
53
  }
53
54
  }
54
55
  }
@@ -83,7 +84,11 @@ export const providePointcloudObjects = (partID) => {
83
84
  }
84
85
  }
85
86
  Promise.allSettled(responses.map(async ([name, pointcloudObjects]) => {
86
- const pointclouds = await Promise.all(pointcloudObjects.map((value) => parsePcdInWorker(new Uint8Array(value.pointCloud))));
87
+ const pointclouds = await Promise.all(pointcloudObjects
88
+ .filter((value) => value !== undefined)
89
+ .map((value) => {
90
+ return parsePcdInWorker(new Uint8Array(value.pointCloud));
91
+ }));
87
92
  return {
88
93
  name,
89
94
  pointclouds,
@@ -10,6 +10,7 @@ import { RefetchRates } from '../components/RefreshRate.svelte';
10
10
  import { useLogs } from './useLogs.svelte';
11
11
  import { useResourceByName } from './useResourceByName.svelte';
12
12
  import { useRefetchPoses } from './useRefetchPoses';
13
+ const origingFrameComponentTypes = ['arm', 'gantry', 'gripper'];
13
14
  export const usePose = (name, parent) => {
14
15
  const environment = useEnvironment();
15
16
  const logs = useLogs();
@@ -25,10 +26,10 @@ export const usePose = (name, parent) => {
25
26
  const frames = useFrames();
26
27
  let pose = $state(undefined);
27
28
  const interval = $derived(refreshRates.get(RefreshRates.poses));
28
- const resolvedParent = $derived(parentResource?.subtype === 'arm' || parentResource?.subtype === 'gantry'
29
+ const resolvedParent = $derived(origingFrameComponentTypes.includes(parentResource?.subtype ?? '')
29
30
  ? `${parent()}_origin`
30
31
  : parent());
31
- const resolvedName = $derived(resource?.subtype === 'arm' || resource?.subtype === 'gantry'
32
+ const resolvedName = $derived(origingFrameComponentTypes.includes(resource?.subtype ?? '')
32
33
  ? `${currentName}_origin`
33
34
  : currentName);
34
35
  const query = createRobotQuery(robotClient, 'getPose', () => [resolvedName, resolvedParent ?? 'world', []], () => ({
@@ -9,7 +9,12 @@ self.onmessage = async (event) => {
9
9
  try {
10
10
  const pcd = loader.parse(data.buffer);
11
11
  if (pcd.geometry) {
12
- const positions = pcd.geometry.attributes.position.array;
12
+ /**
13
+ * Positions is _usually_ defined. However, we have experienced parsing PCDs from Viam APIs that
14
+ * result in the Three.js parser not attaching this attribute, throwing errors downstream.
15
+ */
16
+ const positions = pcd.geometry.attributes.position?.array ??
17
+ new Float32Array(0);
13
18
  const colors = pcd.geometry.attributes.color?.array ?? null;
14
19
  postMessage({ positions, colors, id }, colors ? [positions.buffer, colors.buffer] : [positions.buffer]);
15
20
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,3 +0,0 @@
1
- declare const Arrows: import("svelte").Component<Record<string, never>, {}, "">;
2
- type Arrows = ReturnType<typeof Arrows>;
3
- export default Arrows;