@viamrobotics/motion-tools 1.31.0 → 1.32.0

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.
Files changed (62) hide show
  1. package/dist/components/App.svelte +51 -46
  2. package/dist/components/App.svelte.d.ts +1 -1
  3. package/dist/components/Entities/Arrows/Arrows.svelte +4 -7
  4. package/dist/components/Entities/hooks/useEntityEvents.svelte.d.ts +0 -1
  5. package/dist/components/Entities/hooks/useEntityEvents.svelte.js +30 -16
  6. package/dist/components/InputBindings.svelte +0 -43
  7. package/dist/components/KeyboardBindings.svelte +38 -0
  8. package/dist/components/KeyboardBindings.svelte.d.ts +18 -0
  9. package/dist/components/PointerMissBox.svelte +6 -3
  10. package/dist/components/Scene.svelte +34 -45
  11. package/dist/components/SceneProviders.svelte +2 -4
  12. package/dist/components/SceneProviders.svelte.d.ts +1 -3
  13. package/dist/components/Selected.svelte +20 -27
  14. package/dist/components/SelectedTransformControls.svelte +8 -7
  15. package/dist/components/StaticGeometries.svelte +3 -5
  16. package/dist/components/hover/HoveredEntities.svelte +15 -14
  17. package/dist/components/hover/HoveredEntities.svelte.d.ts +17 -2
  18. package/dist/components/hover/HoveredEntity.svelte +8 -5
  19. package/dist/components/hover/HoveredEntity.svelte.d.ts +5 -1
  20. package/dist/components/hover/LinkedHoveredEntity.svelte +7 -11
  21. package/dist/components/hover/LinkedHoveredEntity.svelte.d.ts +1 -0
  22. package/dist/components/overlay/Details.svelte +22 -37
  23. package/dist/components/overlay/Details.svelte.d.ts +3 -1
  24. package/dist/components/overlay/controls/Controls.svelte +0 -2
  25. package/dist/components/overlay/dashboard/Button.svelte +5 -3
  26. package/dist/components/overlay/dashboard/Button.svelte.d.ts +1 -1
  27. package/dist/components/overlay/left-pane/Tree.svelte +13 -10
  28. package/dist/components/overlay/left-pane/TreeContainer.svelte +9 -4
  29. package/dist/components/overlay/left-pane/TreeNode.svelte +6 -4
  30. package/dist/draw.d.ts +1 -0
  31. package/dist/draw.js +1 -1
  32. package/dist/ecs/index.d.ts +1 -0
  33. package/dist/ecs/index.js +1 -0
  34. package/dist/ecs/traits.d.ts +22 -5
  35. package/dist/ecs/traits.js +33 -4
  36. package/dist/ecs/useTag.svelte.d.ts +5 -0
  37. package/dist/ecs/useTag.svelte.js +43 -0
  38. package/dist/hooks/useEnvironment.svelte.d.ts +1 -1
  39. package/dist/hooks/useLinked.svelte.js +7 -8
  40. package/dist/hooks/useMouseRaycaster.svelte.d.ts +4 -3
  41. package/dist/hooks/useMouseRaycaster.svelte.js +1 -0
  42. package/dist/hooks/useSettings.svelte.d.ts +1 -1
  43. package/dist/plugins/Focus/Focus.svelte +45 -0
  44. package/dist/plugins/Focus/Focus.svelte.d.ts +3 -0
  45. package/dist/plugins/Focus/FocusBox.svelte +75 -0
  46. package/dist/plugins/Focus/FocusBox.svelte.d.ts +3 -0
  47. package/dist/plugins/Focus/provideFocus.svelte.d.ts +1 -0
  48. package/dist/plugins/Focus/provideFocus.svelte.js +61 -0
  49. package/dist/{components → plugins}/MeasureTool/MeasureTool.svelte +6 -8
  50. package/dist/plugins/Selection/SelectionTool.svelte +10 -3
  51. package/dist/plugins/index.d.ts +2 -0
  52. package/dist/plugins/index.js +2 -0
  53. package/dist/three/arrow.d.ts +2 -0
  54. package/dist/three/arrow.js +3 -1
  55. package/package.json +16 -4
  56. package/dist/components/Focus.svelte +0 -46
  57. package/dist/components/Focus.svelte.d.ts +0 -7
  58. package/dist/hooks/useSelection.svelte.d.ts +0 -33
  59. package/dist/hooks/useSelection.svelte.js +0 -94
  60. /package/dist/{components → plugins}/MeasureTool/MeasurePoint.svelte +0 -0
  61. /package/dist/{components → plugins}/MeasureTool/MeasurePoint.svelte.d.ts +0 -0
  62. /package/dist/{components → plugins}/MeasureTool/MeasureTool.svelte.d.ts +0 -0
@@ -6,6 +6,7 @@ import { ColorFormat } from '../buf/draw/v1/metadata_pb';
6
6
  import { createBox, createCapsule, createSphere } from '../geometry';
7
7
  import { parsePcdInWorker } from '../loaders/pcd';
8
8
  import { parsePlyInput } from '../ply';
9
+ import { createPose, matrixToPose, poseToMatrix } from '../transform';
9
10
  export const Name = trait(() => '');
10
11
  export const UUID = trait(() => '');
11
12
  /**
@@ -61,6 +62,13 @@ export const InstancedMatrix = trait(() => ({
61
62
  }));
62
63
  export const Hovered = trait(() => true);
63
64
  export const Invisible = trait(() => true);
65
+ /**
66
+ * Suppresses the default frame-style world/local pose and parent-frame blocks
67
+ * in the details panel. Entities that render their own pose UI via the
68
+ * `details-extensions` portal target (e.g. gizmo plugin entities) opt in by
69
+ * adding this trait.
70
+ */
71
+ export const CustomDetails = trait(() => true);
64
72
  /**
65
73
  * True when the entity itself, or any of its parents up the `ChildOf`
66
74
  * chain, has `Invisible`. Maintained by `provideInheritedInvisible`;
@@ -72,11 +80,14 @@ export const InheritedInvisible = trait(() => true);
72
80
  * Represents that an entity is composed of many instances, so that the treeview and
73
81
  * details panel may display all instances
74
82
  */
75
- export const Instanced = trait(() => true);
83
+ export const InstanceId = trait(() => -1);
76
84
  export const Instance = trait({
77
85
  meshID: -1,
78
86
  instanceID: -1,
79
87
  });
88
+ export const Instances = trait({
89
+ count: 0,
90
+ });
80
91
  export const RenderOrder = trait(() => 0);
81
92
  export const Opacity = trait(() => 1);
82
93
  /**
@@ -100,9 +111,6 @@ export const Colors = trait(() => new Uint8Array());
100
111
  * Per-vertex opacity values packed as uint8 (0-255).
101
112
  */
102
113
  export const Opacities = trait(() => new Uint8Array());
103
- export const Instances = trait({
104
- count: 0,
105
- });
106
114
  export const Arrows = trait({
107
115
  headAtPose: true,
108
116
  });
@@ -175,6 +183,10 @@ export const ReferenceFrame = trait(() => true);
175
183
  */
176
184
  export const ChunkProgress = trait({ loaded: 0, total: 0 });
177
185
  export const SelectToolInteractionLayer = trait(() => true);
186
+ /**
187
+ * This entity is selected by the user
188
+ */
189
+ export const Selected = trait();
178
190
  /**
179
191
  * This entity can be safely removed from the scene by the user
180
192
  */
@@ -250,6 +262,23 @@ export const updateGeometryTrait = (entity, geometry) => {
250
262
  updatePointCloud(entity, geometry.geometryType.value.pointCloud);
251
263
  }
252
264
  };
265
+ /**
266
+ * Patches an entity's `Matrix` trait in-place via the `Pose` round-trip
267
+ * (`matrixToPose` → merge → `poseToMatrix`), then signals `entity.changed(Matrix)`.
268
+ * No-ops silently if the entity has no `Matrix` trait.
269
+ */
270
+ export const writeMatrix = (entity, patch) => {
271
+ const matrix = entity.get(Matrix);
272
+ if (!matrix)
273
+ return;
274
+ const pose = matrixToPose(matrix, createPose());
275
+ const filtered = Object.fromEntries(Object.entries(patch).filter(([, v]) => v !== undefined));
276
+ if (Object.keys(filtered).length === 0)
277
+ return;
278
+ Object.assign(pose, filtered);
279
+ poseToMatrix(pose, matrix);
280
+ entity.changed(Matrix);
281
+ };
253
282
  const updatePointCloud = (entity, pointCloud) => {
254
283
  parsePcdInWorker(new Uint8Array(pointCloud))
255
284
  .then((parsed) => {
@@ -0,0 +1,5 @@
1
+ import { type Entity, type TagTrait, type World } from 'koota';
2
+ export declare function isWorld(target: Entity | World): target is World;
3
+ export declare function useTag(target: () => Entity | World | undefined | null, tag: TagTrait): {
4
+ readonly current: boolean;
5
+ };
@@ -0,0 +1,43 @@
1
+ import { $internal as internal } from 'koota';
2
+ import { useWorld } from './useWorld';
3
+ export function isWorld(target) {
4
+ return typeof target?.spawn === 'function';
5
+ }
6
+ export function useTag(target, tag) {
7
+ const contextWorld = useWorld();
8
+ let value = $state(false);
9
+ $effect(() => {
10
+ const t = target();
11
+ if (!t) {
12
+ value = false;
13
+ return;
14
+ }
15
+ const world = isWorld(t) ? t : contextWorld;
16
+ // ???
17
+ // eslint-disable-next-line prefer-const
18
+ let entity;
19
+ /**
20
+ * Subscribe before reading worldEntity: world.onAdd triggers lazy
21
+ * registration so worldEntity is guaranteed to exist after this.
22
+ */
23
+ const onAddUnsub = world.onAdd(tag, (e) => {
24
+ if (e === entity)
25
+ value = true;
26
+ });
27
+ const onRemoveUnsub = world.onRemove(tag, (e) => {
28
+ if (e === entity)
29
+ value = false;
30
+ });
31
+ entity = isWorld(t) ? t[internal].worldEntity : t;
32
+ value = entity.has(tag);
33
+ return () => {
34
+ onAddUnsub();
35
+ onRemoveUnsub();
36
+ };
37
+ });
38
+ return {
39
+ get current() {
40
+ return value;
41
+ },
42
+ };
43
+ }
@@ -1,6 +1,6 @@
1
1
  export declare const ENVIRONMENT_CONTEXT_KEY: unique symbol;
2
2
  interface Environemnt {
3
- viewerMode: 'edit' | 'monitor' | 'focus';
3
+ viewerMode: 'edit' | 'monitor';
4
4
  isStandalone: boolean;
5
5
  inputBindingsEnabled: boolean;
6
6
  }
@@ -1,20 +1,19 @@
1
1
  import { getContext, setContext } from 'svelte';
2
- import { relations, useWorld } from '../ecs';
3
- import { useFocusedEntity, useSelectedEntity } from './useSelection.svelte';
2
+ import { relations, traits, useQuery, useWorld } from '../ecs';
4
3
  const linkedKey = Symbol('linked-context');
5
4
  export const provideLinkedEntities = () => {
6
5
  const world = useWorld();
7
- const selectedEntity = useSelectedEntity();
8
- const focusedEntity = useFocusedEntity();
9
- const displayEntity = $derived(selectedEntity.current ?? focusedEntity.current);
10
- let linkedEntities = $derived(displayEntity?.targetsFor(relations.SubEntityLink) ?? []);
6
+ const selected = useQuery(traits.Selected);
7
+ let linkedEntities = $derived(selected.current
8
+ .flatMap((entity) => entity.targetFor(relations.SubEntityLink))
9
+ .filter((entity) => entity !== undefined));
11
10
  const unsubAdd = world.onAdd(relations.SubEntityLink, (entity, target) => {
12
- if (entity === displayEntity) {
11
+ if (selected.current.includes(entity)) {
13
12
  linkedEntities = [...linkedEntities, target];
14
13
  }
15
14
  });
16
15
  const unsubRemove = world.onRemove(relations.SubEntityLink, (entity, target) => {
17
- if (entity === displayEntity) {
16
+ if (selected.current.includes(entity)) {
18
17
  linkedEntities = linkedEntities.filter((e) => e !== target);
19
18
  }
20
19
  });
@@ -5,9 +5,10 @@ interface RaycastEvent<T extends EventNames> {
5
5
  intersections: Intersection[];
6
6
  }
7
7
  type Callback<T extends EventNames> = (event: RaycastEvent<T>) => void;
8
- export declare const useMouseRaycaster: (getOptions?: () => {
9
- enabled: boolean;
10
- }) => {
8
+ interface MouseRaycasterOptions {
9
+ enabled?: boolean;
10
+ }
11
+ export declare const useMouseRaycaster: (getOptions?: () => MouseRaycasterOptions) => {
11
12
  raycaster: Raycaster;
12
13
  onclick: (cb: Callback<"click">) => void;
13
14
  onmove: (cb: Callback<"move">) => void;
@@ -69,6 +69,7 @@ export const useMouseRaycaster = (getOptions) => {
69
69
  if (!options.enabled) {
70
70
  return;
71
71
  }
72
+ raycaster.firstHitOnly = true;
72
73
  dom.addEventListener('pointermove', onPointerMove, { passive: true });
73
74
  dom.addEventListener('pointerdown', onPointerDown, { passive: true });
74
75
  dom.addEventListener('pointerup', onPointerUp, { passive: true });
@@ -1,7 +1,7 @@
1
1
  import type { ColorRepresentation } from 'three';
2
2
  export interface Settings {
3
3
  cameraMode: 'orthographic' | 'perspective';
4
- interactionMode: 'navigate' | 'measure' | 'select';
4
+ interactionMode: 'navigate' | 'measure' | 'select' | 'gizmo';
5
5
  refreshRates: {
6
6
  poses: number;
7
7
  pointclouds: number;
@@ -0,0 +1,45 @@
1
+ <script lang="ts">
2
+ import { Portal } from '@threlte/extras'
3
+ import { PressedKeys } from 'runed'
4
+
5
+ import Button from '../../components/overlay/dashboard/Button.svelte'
6
+ import { traits, useQuery } from '../../ecs'
7
+
8
+ import FocusBox from './FocusBox.svelte'
9
+ import { provideFocus } from './provideFocus.svelte'
10
+
11
+ let focusing = $state(false)
12
+
13
+ provideFocus(() => focusing)
14
+
15
+ const selected = useQuery(traits.Selected)
16
+
17
+ const canFocus = $derived(selected.current.length > 0 || focusing)
18
+
19
+ const keys = new PressedKeys()
20
+
21
+ keys.onKeys('/', () => {
22
+ if (selected.current.length > 0 && !focusing) {
23
+ focusing = true
24
+ } else if (focusing) {
25
+ focusing = false
26
+ }
27
+ })
28
+ </script>
29
+
30
+ <Portal id="dashboard">
31
+ <fieldset class="flex">
32
+ <Button
33
+ icon="focus"
34
+ active={focusing}
35
+ disabled={!canFocus}
36
+ description="Focus selection"
37
+ hotkey="/"
38
+ onclick={() => (focusing = !focusing)}
39
+ />
40
+ </fieldset>
41
+ </Portal>
42
+
43
+ {#if focusing}
44
+ <FocusBox />
45
+ {/if}
@@ -0,0 +1,3 @@
1
+ declare const Focus: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type Focus = ReturnType<typeof Focus>;
3
+ export default Focus;
@@ -0,0 +1,75 @@
1
+ <script lang="ts">
2
+ import { T, useThrelte } from '@threlte/core'
3
+ import { Gizmo, TrackballControls } from '@threlte/extras'
4
+ import { untrack } from 'svelte'
5
+ import { Box3, Vector3 } from 'three'
6
+
7
+ import Camera from '../../components/Camera.svelte'
8
+ import { traits, useQuery } from '../../ecs'
9
+ import { useCameraControls } from '../../hooks/useControls.svelte'
10
+
11
+ const { scene } = useThrelte()
12
+ const cameraControls = useCameraControls()
13
+ const selected = useQuery(traits.Selected)
14
+
15
+ /**
16
+ * Save the main camera controls and their state the instant focus begins —
17
+ * this runs before the TrackballControls below swaps the shared context, so
18
+ * `current` is still the main controls. On teardown we hand `current` back to
19
+ * them (the trackball never restores it) and reset them to the saved view, so
20
+ * exiting focus returns the camera to where it was. camera-controls exposes
21
+ * saveState()/reset(); TrackballControls does not, which also narrows the type.
22
+ */
23
+ $effect.pre(() => {
24
+ const previousControls = untrack(() => cameraControls.current)
25
+ const restorableControls =
26
+ previousControls && 'saveState' in previousControls ? previousControls : undefined
27
+ restorableControls?.saveState()
28
+
29
+ return () => {
30
+ if (!restorableControls) return
31
+ cameraControls.set(restorableControls)
32
+ restorableControls.reset(false)
33
+ }
34
+ })
35
+
36
+ const box = new Box3()
37
+ const vec = new Vector3()
38
+
39
+ let center = $state.raw<[number, number, number]>([0, 0, 0])
40
+ let size = $state.raw<[number, number, number]>([0, 0, 0])
41
+
42
+ /**
43
+ * Frame the camera on the selection captured when focus was entered. Reading
44
+ * the selection untracked leaves this effect with no reactive dependencies,
45
+ * so it runs once on mount and the framing stays put — changing the selection
46
+ * while focused must not re-frame or reset the camera.
47
+ */
48
+ $effect(() => {
49
+ box.makeEmpty()
50
+ for (const entity of untrack(() => selected.current)) {
51
+ const object3d = scene.getObjectByName(entity as unknown as string)
52
+ if (object3d) {
53
+ box.expandByObject(object3d)
54
+ }
55
+ }
56
+
57
+ size = box.getSize(vec).toArray()
58
+ center = box.getCenter(vec).toArray()
59
+ })
60
+ </script>
61
+
62
+ <Camera position={[size[0] + 1, size[0] + 1, size[0] + 1]}>
63
+ <TrackballControls
64
+ target={center}
65
+ oncreate={(ref) => cameraControls.set(ref)}
66
+ >
67
+ <Gizmo placement="bottom-right" />
68
+ </TrackballControls>
69
+ </Camera>
70
+
71
+ <T.Box3Helper
72
+ args={[box, 'red']}
73
+ bvh={{ enabled: false }}
74
+ raycast={() => null}
75
+ />
@@ -0,0 +1,3 @@
1
+ declare const FocusBox: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type FocusBox = ReturnType<typeof FocusBox>;
3
+ export default FocusBox;
@@ -0,0 +1 @@
1
+ export declare const provideFocus: (focusing: () => boolean) => void;
@@ -0,0 +1,61 @@
1
+ import { trait } from 'koota';
2
+ import { untrack } from 'svelte';
3
+ import { relations, traits, useQuery, useWorld } from '../../ecs';
4
+ const HiddenByFocus = trait();
5
+ export const provideFocus = (focusing) => {
6
+ const world = useWorld();
7
+ const selected = useQuery(traits.Selected);
8
+ $effect(() => {
9
+ if (!focusing()) {
10
+ for (const entity of world.query(HiddenByFocus)) {
11
+ entity.remove(HiddenByFocus, traits.Invisible);
12
+ }
13
+ return;
14
+ }
15
+ /**
16
+ * Snapshot the selection at the moment focus is entered. Reading it
17
+ * untracked makes `focusing()` this effect's only dependency, so the
18
+ * focused view stays frozen: selecting or deselecting entities while
19
+ * focused must not change what's hidden. Everything is restored when
20
+ * focus exits.
21
+ */
22
+ const selectedEntities = untrack(() => selected.current);
23
+ /**
24
+ * Entities only render when their `InheritedInvisible` is unset, and that
25
+ * trait is computed by walking `ChildOf` ancestors (see
26
+ * `useInheritedInvisible`). So hiding a selected entity's parent — or its
27
+ * renderable sub-entities, which are `ChildOf` children that never carry
28
+ * `Selected` — makes the selection itself disappear. Keep the whole
29
+ * connected subtree of each selection visible: its ancestors (so the
30
+ * cascade can't reach it) and its descendants (so its geometry shows).
31
+ */
32
+ const keep = new Set();
33
+ const keepSubtree = (entity) => {
34
+ if (keep.has(entity))
35
+ return;
36
+ keep.add(entity);
37
+ for (const child of world.query(relations.ChildOf(entity))) {
38
+ keepSubtree(child);
39
+ }
40
+ };
41
+ for (const entity of selectedEntities) {
42
+ let ancestor = entity.targetFor(relations.ChildOf);
43
+ while (ancestor?.isAlive()) {
44
+ keep.add(ancestor);
45
+ ancestor = ancestor.targetFor(relations.ChildOf);
46
+ }
47
+ keepSubtree(entity);
48
+ }
49
+ /**
50
+ * Hide the rest. Skip already-invisible entities so we don't take
51
+ * ownership of — and later wrongly reveal — user-hidden entities.
52
+ */
53
+ for (const entity of world.query(traits.Name)) {
54
+ if (keep.has(entity))
55
+ continue;
56
+ if (!entity.has(traits.Invisible)) {
57
+ entity.add(HiddenByFocus, traits.Invisible);
58
+ }
59
+ }
60
+ });
61
+ };
@@ -4,16 +4,14 @@
4
4
  import { untrack } from 'svelte'
5
5
  import { type Intersection, Vector3 } from 'three'
6
6
 
7
- import Button from '../overlay/dashboard/Button.svelte'
7
+ import Button from '../../components/overlay/dashboard/Button.svelte'
8
+ import Popover from '../../components/overlay/Popover.svelte'
9
+ import ToggleGroup from '../../components/overlay/ToggleGroup.svelte'
8
10
  import { useMouseRaycaster } from '../../hooks/useMouseRaycaster.svelte'
9
- import { useFocusedEntity } from '../../hooks/useSelection.svelte'
10
11
  import { useSettings } from '../../hooks/useSettings.svelte'
11
12
 
12
- import Popover from '../overlay/Popover.svelte'
13
- import ToggleGroup from '../overlay/ToggleGroup.svelte'
14
13
  import MeasurePoint from './MeasurePoint.svelte'
15
14
 
16
- const focusedEntity = useFocusedEntity()
17
15
  const settings = useSettings()
18
16
 
19
17
  const htmlPosition = new Vector3()
@@ -73,9 +71,9 @@
73
71
  }
74
72
 
75
73
  $effect(() => {
76
- // eslint-disable-next-line @typescript-eslint/no-unused-expressions
77
- ;(focusedEntity.current, enabled)
78
- untrack(() => clear())
74
+ if (!enabled) {
75
+ untrack(() => clear())
76
+ }
79
77
  })
80
78
  </script>
81
79
 
@@ -9,7 +9,7 @@
9
9
  import DashboardButton from '../../components/overlay/dashboard/Button.svelte'
10
10
  import Popover from '../../components/overlay/Popover.svelte'
11
11
  import ToggleGroup from '../../components/overlay/ToggleGroup.svelte'
12
- import { useSelectedEntity } from '../../hooks/useSelection.svelte'
12
+ import { traits, useWorld } from '../../ecs'
13
13
  import { useSettings } from '../../hooks/useSettings.svelte'
14
14
 
15
15
  import Ellipse from './Ellipse.svelte'
@@ -29,11 +29,12 @@
29
29
  let { enabled = false, autoSelectNewEntities = false, children }: Props = $props()
30
30
 
31
31
  const { dom } = useThrelte()
32
+ const world = useWorld()
32
33
  const settings = useSettings()
33
34
  const isSelectionMode = $derived(settings.current.interactionMode === 'select')
34
35
 
35
36
  const selectionPlugin = provideSelectionPlugin()
36
- const selectedEntity = useSelectedEntity()
37
+
37
38
  let selectionType = $state<SelectionType>('lasso')
38
39
 
39
40
  $effect(() => {
@@ -58,7 +59,13 @@
58
59
 
59
60
  const newest = newEntities.at(-1)
60
61
  if (newest === undefined) return
61
- selectedEntity.set(newest)
62
+
63
+ const selected = world.query(traits.Selected)
64
+ for (const entity of selected) {
65
+ entity.remove(traits.Selected)
66
+ }
67
+
68
+ newest.add(traits.Selected)
62
69
  })
63
70
 
64
71
  const rect = new ElementRect(() => dom)
@@ -2,6 +2,8 @@ export { default as SelectionTool } from './Selection/SelectionTool.svelte';
2
2
  export * as selectionTraits from './Selection/traits';
3
3
  export * as selectionRelations from './Selection/relations';
4
4
  export { useSelectionPlugin } from './Selection/useSelectionPlugin.svelte';
5
+ export { default as MeasureTool } from './MeasureTool/MeasureTool.svelte';
5
6
  export { default as DrawService } from './DrawService/DrawService.svelte';
6
7
  export { default as Skybox } from './Skybox/Skybox.svelte';
7
8
  export { default as Debug } from './Debug/Debug.svelte';
9
+ export { default as Focus } from './Focus/Focus.svelte';
@@ -3,9 +3,11 @@ export { default as SelectionTool } from './Selection/SelectionTool.svelte';
3
3
  export * as selectionTraits from './Selection/traits';
4
4
  export * as selectionRelations from './Selection/relations';
5
5
  export { useSelectionPlugin } from './Selection/useSelectionPlugin.svelte';
6
+ export { default as MeasureTool } from './MeasureTool/MeasureTool.svelte';
6
7
  // DrawService
7
8
  export { default as DrawService } from './DrawService/DrawService.svelte';
8
9
  // Skybox
9
10
  export { default as Skybox } from './Skybox/Skybox.svelte';
10
11
  // Debug
11
12
  export { default as Debug } from './Debug/Debug.svelte';
13
+ export { default as Focus } from './Focus/Focus.svelte';
@@ -1,4 +1,6 @@
1
1
  import { type BufferGeometry } from 'three';
2
+ /** Total length of the geometry produced by `createArrowGeometry`, in meters. */
3
+ export declare const ARROW_LENGTH = 0.1;
2
4
  /**
3
5
  * Returns one merged geometry for an arrow (box tail + cone head)
4
6
  *
@@ -1,12 +1,14 @@
1
1
  import { BoxGeometry, ConeGeometry } from 'three';
2
2
  import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
3
+ /** Total length of the geometry produced by `createArrowGeometry`, in meters. */
4
+ export const ARROW_LENGTH = 0.1;
3
5
  /**
4
6
  * Returns one merged geometry for an arrow (box tail + cone head)
5
7
  *
6
8
  * Arrow points along +Y with its base at y = 0
7
9
  */
8
10
  export const createArrowGeometry = () => {
9
- const length = 0.1;
11
+ const length = ARROW_LENGTH;
10
12
  const headLength = length * 0.3;
11
13
  const headWidth = headLength * 0.3;
12
14
  const tailLength = length - headLength;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "1.31.0",
3
+ "version": "1.32.0",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -9,6 +9,8 @@
9
9
  "@ag-grid-community/core": "32.3.9",
10
10
  "@ag-grid-community/styles": "32.3.9",
11
11
  "@changesets/cli": "2.29.6",
12
+ "@connectrpc/connect": "1.7.0",
13
+ "@connectrpc/connect-web": "1.7.0",
12
14
  "@dimforge/rapier3d-compat": "0.18.2",
13
15
  "@eslint/compat": "2.0.2",
14
16
  "@eslint/js": "10.0.1",
@@ -52,6 +54,7 @@
52
54
  "@zag-js/tree-view": "1.22.1",
53
55
  "camera-controls": "3.1.0",
54
56
  "concurrently": "^9.2.1",
57
+ "earcut": "^3.0.2",
55
58
  "esbuild": "^0.27.3",
56
59
  "eslint": "10.0.2",
57
60
  "eslint-config-prettier": "10.1.8",
@@ -87,6 +90,8 @@
87
90
  "@ag-grid-community/client-side-row-model": ">=32.3.0",
88
91
  "@ag-grid-community/core": ">=32.3.0",
89
92
  "@ag-grid-community/styles": ">=32.3.0",
93
+ "@connectrpc/connect": ">=1",
94
+ "@connectrpc/connect-web": ">=1",
90
95
  "@dimforge/rapier3d-compat": ">=0.17",
91
96
  "@tanstack/svelte-query-devtools": ">=6",
92
97
  "@threlte/core": ">=8",
@@ -105,6 +110,7 @@
105
110
  "@zag-js/toggle-group": ">=1",
106
111
  "@zag-js/tree-view": ">=1",
107
112
  "camera-controls": ">=3",
113
+ "earcut": ">=3",
108
114
  "idb-keyval": ">=6",
109
115
  "lucide-svelte": ">=0.511",
110
116
  "runed": ">=0.28",
@@ -113,8 +119,17 @@
113
119
  "svelte-virtuallists": ">=1"
114
120
  },
115
121
  "peerDependenciesMeta": {
122
+ "@connectrpc/connect": {
123
+ "optional": true
124
+ },
125
+ "@connectrpc/connect-web": {
126
+ "optional": true
127
+ },
116
128
  "@tanstack/svelte-query-devtools": {
117
129
  "optional": true
130
+ },
131
+ "earcut": {
132
+ "optional": true
118
133
  }
119
134
  },
120
135
  "engines": {
@@ -147,10 +162,7 @@
147
162
  ],
148
163
  "dependencies": {
149
164
  "@bufbuild/protobuf": "1.10.1",
150
- "@connectrpc/connect": "1.7.0",
151
- "@connectrpc/connect-web": "1.7.0",
152
165
  "@neodrag/svelte": "^2.3.3",
153
- "earcut": "^3.0.2",
154
166
  "filtrex": "^3.1.0",
155
167
  "koota": "0.6.5",
156
168
  "lodash-es": "4.18.1",
@@ -1,46 +0,0 @@
1
- <script lang="ts">
2
- import { T } from '@threlte/core'
3
- import { Gizmo, TrackballControls } from '@threlte/extras'
4
- import { Box3, type Object3D, Vector3 } from 'three'
5
-
6
- import { useCameraControls } from '../hooks/useControls.svelte'
7
-
8
- import Camera from './Camera.svelte'
9
-
10
- interface Props {
11
- object3d: Object3D
12
- }
13
-
14
- let { object3d }: Props = $props()
15
-
16
- const cameraControls = useCameraControls()
17
-
18
- const box = new Box3()
19
- const vec = new Vector3()
20
-
21
- let center = $state.raw<[number, number, number]>([0, 0, 0])
22
- let size = $state.raw<[number, number, number]>([0, 0, 0])
23
-
24
- $effect.pre(() => {
25
- box.setFromObject(object3d)
26
- size = box.getSize(vec).toArray()
27
- center = box.getCenter(vec).toArray()
28
- })
29
- </script>
30
-
31
- <Camera position={[size[0] + 1, size[0] + 1, size[0] + 1]}>
32
- <TrackballControls
33
- target={center}
34
- oncreate={(ref) => cameraControls.set(ref)}
35
- >
36
- <Gizmo placement="bottom-right" />
37
- </TrackballControls>
38
- </Camera>
39
-
40
- <T is={object3d} />
41
-
42
- <T.BoxHelper
43
- args={[object3d, 'red']}
44
- bvh={{ enabled: false }}
45
- raycast={() => null}
46
- />
@@ -1,7 +0,0 @@
1
- import { type Object3D } from 'three';
2
- interface Props {
3
- object3d: Object3D;
4
- }
5
- declare const Focus: import("svelte").Component<Props, {}, "">;
6
- type Focus = ReturnType<typeof Focus>;
7
- export default Focus;