@viamrobotics/motion-tools 1.21.0 → 1.23.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 (37) hide show
  1. package/README.md +18 -100
  2. package/dist/FrameConfigUpdater.svelte.d.ts +0 -1
  3. package/dist/FrameConfigUpdater.svelte.js +6 -24
  4. package/dist/components/App.svelte +3 -3
  5. package/dist/components/App.svelte.d.ts +1 -1
  6. package/dist/components/CameraControls.svelte +6 -6
  7. package/dist/components/Entities/Pose.svelte +18 -13
  8. package/dist/components/FileDrop/useFileDrop.svelte.js +16 -2
  9. package/dist/components/{KeyboardControls.svelte → InputBindings.svelte} +50 -77
  10. package/dist/components/InputBindings.svelte.d.ts +7 -0
  11. package/dist/components/PointerMissBox.svelte +1 -1
  12. package/dist/components/Scene.svelte +2 -0
  13. package/dist/components/SceneProviders.svelte +2 -0
  14. package/dist/components/SelectedTransformControls.svelte +227 -0
  15. package/dist/components/SelectedTransformControls.svelte.d.ts +3 -0
  16. package/dist/components/StaticGeometries.svelte +3 -56
  17. package/dist/components/overlay/Details.svelte +82 -54
  18. package/dist/components/overlay/dashboard/Button.svelte +4 -2
  19. package/dist/components/overlay/dashboard/Button.svelte.d.ts +1 -1
  20. package/dist/components/overlay/dashboard/Dashboard.svelte +43 -33
  21. package/dist/ecs/traits.d.ts +15 -0
  22. package/dist/ecs/traits.js +7 -0
  23. package/dist/editing/FrameEditSession.d.ts +37 -0
  24. package/dist/editing/FrameEditSession.js +178 -0
  25. package/dist/hooks/useEnvironment.svelte.d.ts +1 -0
  26. package/dist/hooks/useEnvironment.svelte.js +1 -0
  27. package/dist/hooks/useFrameEditSession.svelte.d.ts +15 -0
  28. package/dist/hooks/useFrameEditSession.svelte.js +36 -0
  29. package/dist/hooks/useFrames.svelte.js +45 -5
  30. package/dist/hooks/usePartConfig.svelte.js +10 -0
  31. package/dist/hooks/useSettings.svelte.d.ts +1 -3
  32. package/dist/hooks/useSettings.svelte.js +1 -3
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.js +2 -0
  35. package/dist/transform.js +13 -0
  36. package/package.json +8 -6
  37. package/dist/components/KeyboardControls.svelte.d.ts +0 -7
@@ -11,6 +11,7 @@ export const Parent = trait(() => 'world');
11
11
  export const UUID = trait(() => '');
12
12
  export const Pose = trait({ x: 0, y: 0, z: 0, oX: 0, oY: 0, oZ: 1, theta: 0 });
13
13
  export const EditedPose = trait({ x: 0, y: 0, z: 0, oX: 0, oY: 0, oZ: 1, theta: 0 });
14
+ export const LivePose = trait({ x: 0, y: 0, z: 0, oX: 0, oY: 0, oZ: 1, theta: 0 });
14
15
  export const Center = trait({ x: 0, y: 0, z: 0, oX: 0, oY: 0, oZ: 1, theta: 0 });
15
16
  export const InstancedPose = trait({
16
17
  x: 0,
@@ -103,6 +104,12 @@ export const SnapshotAPI = trait(() => true);
103
104
  * Marker trait for entities created from user-dropped files (PLY, PCD, etc.)
104
105
  */
105
106
  export const DroppedFile = trait(() => true);
107
+ /**
108
+ * Marker trait for entities the dashboard's TransformControls may attach to —
109
+ * editable frames and ad-hoc custom geometries. Other entity kinds (lines,
110
+ * points, batched arrows, etc.) are deliberately excluded.
111
+ */
112
+ export const Transformable = trait(() => true);
106
113
  export const ShowAxesHelper = trait(() => true);
107
114
  /**
108
115
  * Marker trait for entities that should be rendered in screen space (CSS pixels)
@@ -0,0 +1,37 @@
1
+ import type { Pose } from '@viamrobotics/sdk';
2
+ import type { Entity } from 'koota';
3
+ import type { Frame } from '../frame';
4
+ export type UpdateFrameFn = (componentName: string, referenceFrame: string, pose: Pose, geometry?: Frame['geometry']) => void;
5
+ export type DeleteFrameFn = (componentName: string) => void;
6
+ /**
7
+ * A single user gesture against one or more frames (drag, parent change, geometry tweak).
8
+ * Owns the affected entities until commit() or abort() runs. Snapshots their pre-gesture
9
+ * trait state so abort() can restore — both the ECS view and the dirty part config.
10
+ *
11
+ * Replaces the Transforming marker trait: while a session is active, useFrames asks
12
+ * `session.owns(entity)` instead of inspecting a per-entity flag.
13
+ */
14
+ export declare class FrameEditSession {
15
+ #private;
16
+ private snapshots;
17
+ private updateFrame;
18
+ private deleteFrame;
19
+ private onClose;
20
+ constructor(entities: Entity[], updateFrame: UpdateFrameFn, deleteFrame: DeleteFrameFn, onClose: () => void);
21
+ get isClosed(): boolean;
22
+ owns(entity: Entity | undefined): boolean;
23
+ stagePose: (entity: Entity, pose: Partial<Pose>) => void;
24
+ stageGeometry: (entity: Entity, geometry: Frame["geometry"]) => void;
25
+ stageParent: (entity: Entity, parent: string) => void;
26
+ stageDelete: (entity: Entity) => void;
27
+ /**
28
+ * Validate and close. Returns true on success. On invalid pose data
29
+ * (NaN/infinite from a degenerate gizmo state), aborts and returns false.
30
+ */
31
+ commit: () => boolean;
32
+ /**
33
+ * Restore each owned entity's traits to its pre-session state and re-issue
34
+ * an updateFrame so the dirty part config matches.
35
+ */
36
+ abort: () => void;
37
+ }
@@ -0,0 +1,178 @@
1
+ import { traits } from '../ecs';
2
+ import { isFinitePose } from '../transform';
3
+ const captureGeometry = (entity) => {
4
+ const box = entity.get(traits.Box);
5
+ if (box)
6
+ return { type: 'box', box: { ...box } };
7
+ const sphere = entity.get(traits.Sphere);
8
+ if (sphere)
9
+ return { type: 'sphere', sphere: { ...sphere } };
10
+ const capsule = entity.get(traits.Capsule);
11
+ if (capsule)
12
+ return { type: 'capsule', capsule: { ...capsule } };
13
+ return { type: 'none' };
14
+ };
15
+ const restoreGeometryTrait = (entity, snap) => {
16
+ if (snap.type === 'none') {
17
+ entity.remove(traits.Box, traits.Sphere, traits.Capsule);
18
+ return;
19
+ }
20
+ if (snap.type === 'box' && snap.box) {
21
+ entity.remove(traits.Sphere, traits.Capsule);
22
+ if (entity.has(traits.Box))
23
+ entity.set(traits.Box, snap.box);
24
+ else
25
+ entity.add(traits.Box(snap.box));
26
+ return;
27
+ }
28
+ if (snap.type === 'sphere' && snap.sphere) {
29
+ entity.remove(traits.Box, traits.Capsule);
30
+ if (entity.has(traits.Sphere))
31
+ entity.set(traits.Sphere, snap.sphere);
32
+ else
33
+ entity.add(traits.Sphere(snap.sphere));
34
+ return;
35
+ }
36
+ if (snap.type === 'capsule' && snap.capsule) {
37
+ entity.remove(traits.Box, traits.Sphere);
38
+ if (entity.has(traits.Capsule))
39
+ entity.set(traits.Capsule, snap.capsule);
40
+ else
41
+ entity.add(traits.Capsule(snap.capsule));
42
+ }
43
+ };
44
+ const snapshotToFrameGeometry = (snap) => {
45
+ if (snap.type === 'box' && snap.box)
46
+ return { type: 'box', ...snap.box };
47
+ if (snap.type === 'sphere' && snap.sphere)
48
+ return { type: 'sphere', ...snap.sphere };
49
+ if (snap.type === 'capsule' && snap.capsule)
50
+ return { type: 'capsule', ...snap.capsule };
51
+ return { type: 'none' };
52
+ };
53
+ const liveGeometry = (entity) => snapshotToFrameGeometry(captureGeometry(entity));
54
+ /**
55
+ * A single user gesture against one or more frames (drag, parent change, geometry tweak).
56
+ * Owns the affected entities until commit() or abort() runs. Snapshots their pre-gesture
57
+ * trait state so abort() can restore — both the ECS view and the dirty part config.
58
+ *
59
+ * Replaces the Transforming marker trait: while a session is active, useFrames asks
60
+ * `session.owns(entity)` instead of inspecting a per-entity flag.
61
+ */
62
+ export class FrameEditSession {
63
+ snapshots = new Map();
64
+ updateFrame;
65
+ deleteFrame;
66
+ onClose;
67
+ #closed = false;
68
+ constructor(entities, updateFrame, deleteFrame, onClose) {
69
+ this.updateFrame = updateFrame;
70
+ this.deleteFrame = deleteFrame;
71
+ this.onClose = onClose;
72
+ for (const entity of entities) {
73
+ const name = entity.get(traits.Name);
74
+ const editedPose = entity.get(traits.EditedPose);
75
+ if (!name || !editedPose)
76
+ continue;
77
+ this.snapshots.set(entity, {
78
+ name,
79
+ parent: entity.get(traits.Parent) ?? 'world',
80
+ editedPose: { ...editedPose },
81
+ geometry: captureGeometry(entity),
82
+ });
83
+ }
84
+ }
85
+ get isClosed() {
86
+ return this.#closed;
87
+ }
88
+ owns(entity) {
89
+ return entity !== undefined && !this.#closed && this.snapshots.has(entity);
90
+ }
91
+ stagePose = (entity, pose) => {
92
+ const snap = this.snapshots.get(entity);
93
+ if (!snap || this.#closed)
94
+ return;
95
+ const current = entity.get(traits.EditedPose);
96
+ if (!current)
97
+ return;
98
+ const next = { ...current, ...pose };
99
+ entity.set(traits.EditedPose, next);
100
+ this.updateFrame(snap.name, entity.get(traits.Parent) ?? 'world', next, liveGeometry(entity));
101
+ };
102
+ stageGeometry = (entity, geometry) => {
103
+ const snap = this.snapshots.get(entity);
104
+ if (!snap || this.#closed || !geometry)
105
+ return;
106
+ if (geometry.type === 'none') {
107
+ entity.remove(traits.Box, traits.Sphere, traits.Capsule);
108
+ }
109
+ else if (geometry.type === 'box') {
110
+ const data = { x: geometry.x, y: geometry.y, z: geometry.z };
111
+ restoreGeometryTrait(entity, { type: 'box', box: data });
112
+ }
113
+ else if (geometry.type === 'sphere') {
114
+ restoreGeometryTrait(entity, { type: 'sphere', sphere: { r: geometry.r } });
115
+ }
116
+ else if (geometry.type === 'capsule') {
117
+ restoreGeometryTrait(entity, { type: 'capsule', capsule: { r: geometry.r, l: geometry.l } });
118
+ }
119
+ const editedPose = entity.get(traits.EditedPose);
120
+ if (editedPose) {
121
+ this.updateFrame(snap.name, entity.get(traits.Parent) ?? 'world', editedPose, geometry);
122
+ }
123
+ };
124
+ stageParent = (entity, parent) => {
125
+ const snap = this.snapshots.get(entity);
126
+ if (!snap || this.#closed)
127
+ return;
128
+ traits.setParentTrait(entity, parent === 'world' ? undefined : parent);
129
+ const editedPose = entity.get(traits.EditedPose);
130
+ if (editedPose) {
131
+ this.updateFrame(snap.name, parent, editedPose, liveGeometry(entity));
132
+ }
133
+ };
134
+ stageDelete = (entity) => {
135
+ const snap = this.snapshots.get(entity);
136
+ if (!snap || this.#closed)
137
+ return;
138
+ this.deleteFrame(snap.name);
139
+ };
140
+ /**
141
+ * Validate and close. Returns true on success. On invalid pose data
142
+ * (NaN/infinite from a degenerate gizmo state), aborts and returns false.
143
+ */
144
+ commit = () => {
145
+ if (this.#closed)
146
+ return false;
147
+ for (const [entity] of this.snapshots) {
148
+ const pose = entity.get(traits.EditedPose);
149
+ if (pose && !isFinitePose(pose)) {
150
+ this.abort();
151
+ return false;
152
+ }
153
+ }
154
+ this.#close();
155
+ return true;
156
+ };
157
+ /**
158
+ * Restore each owned entity's traits to its pre-session state and re-issue
159
+ * an updateFrame so the dirty part config matches.
160
+ */
161
+ abort = () => {
162
+ if (this.#closed)
163
+ return;
164
+ for (const [entity, snap] of this.snapshots) {
165
+ if (entity.isAlive()) {
166
+ entity.set(traits.EditedPose, snap.editedPose);
167
+ traits.setParentTrait(entity, snap.parent === 'world' ? undefined : snap.parent);
168
+ restoreGeometryTrait(entity, snap.geometry);
169
+ }
170
+ this.updateFrame(snap.name, snap.parent, snap.editedPose, snapshotToFrameGeometry(snap.geometry));
171
+ }
172
+ this.#close();
173
+ };
174
+ #close = () => {
175
+ this.#closed = true;
176
+ this.onClose();
177
+ };
178
+ }
@@ -2,6 +2,7 @@ export declare const ENVIRONMENT_CONTEXT_KEY: unique symbol;
2
2
  interface Environemnt {
3
3
  viewerMode: 'edit' | 'monitor' | 'focus';
4
4
  isStandalone: boolean;
5
+ inputBindingsEnabled: boolean;
5
6
  }
6
7
  interface Context {
7
8
  current: Environemnt;
@@ -3,6 +3,7 @@ export const ENVIRONMENT_CONTEXT_KEY = Symbol('environment');
3
3
  const defaults = () => ({
4
4
  viewerMode: 'monitor',
5
5
  isStandalone: true,
6
+ inputBindingsEnabled: true,
6
7
  });
7
8
  export const createEnvironment = () => {
8
9
  const environment = $state(defaults());
@@ -0,0 +1,15 @@
1
+ import type { Entity } from 'koota';
2
+ import { FrameEditSession } from '../editing/FrameEditSession';
3
+ interface FrameEditSessionContext {
4
+ /** The currently-active session, or undefined when no gesture is in flight. */
5
+ current: FrameEditSession | undefined;
6
+ /**
7
+ * Open a new session over the given entities. If a previous session is still
8
+ * active (e.g. selection changed mid-drag and onMouseUp never fired), it is
9
+ * aborted first so its snapshot is restored.
10
+ */
11
+ begin: (entities: Entity[]) => FrameEditSession;
12
+ }
13
+ export declare const provideFrameEditSession: (partID: () => string) => void;
14
+ export declare const useFrameEditSession: () => FrameEditSessionContext;
15
+ export {};
@@ -0,0 +1,36 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ import { FrameEditSession } from '../editing/FrameEditSession';
3
+ import { usePartConfig } from './usePartConfig.svelte';
4
+ const key = Symbol('frame-edit-session-context');
5
+ export const provideFrameEditSession = (partID) => {
6
+ const partConfig = usePartConfig();
7
+ let active = $state(undefined);
8
+ const begin = (entities) => {
9
+ active?.abort();
10
+ const session = new FrameEditSession(entities, partConfig.updateFrame, partConfig.deleteFrame, () => {
11
+ if (active === session)
12
+ active = undefined;
13
+ });
14
+ active = session;
15
+ return session;
16
+ };
17
+ // Drop any in-flight session when the partID changes — its snapshots reference
18
+ // entities from the old world that useFrames will destroy, and aborting it
19
+ // after the swap would write old frame names into the new part's config.
20
+ let lastPartID;
21
+ $effect.pre(() => {
22
+ const id = partID();
23
+ if (lastPartID !== undefined && lastPartID !== id) {
24
+ active?.abort();
25
+ active = undefined;
26
+ }
27
+ lastPartID = id;
28
+ });
29
+ setContext(key, {
30
+ get current() {
31
+ return active;
32
+ },
33
+ begin,
34
+ });
35
+ };
36
+ export const useFrameEditSession = () => getContext(key);
@@ -7,6 +7,7 @@ import { traits, useWorld } from '../ecs';
7
7
  import { createPose } from '../transform';
8
8
  import { useConfigFrames } from './useConfigFrames.svelte';
9
9
  import { useEnvironment } from './useEnvironment.svelte';
10
+ import { useFrameEditSession } from './useFrameEditSession.svelte';
10
11
  import { useLogs } from './useLogs.svelte';
11
12
  import { usePartConfig } from './usePartConfig.svelte';
12
13
  import { useResourceByName } from './useResourceByName.svelte';
@@ -14,6 +15,7 @@ const key = Symbol('frames-context');
14
15
  export const provideFrames = (partID) => {
15
16
  const configFrames = useConfigFrames();
16
17
  const partConfig = usePartConfig();
18
+ const editSession = useFrameEditSession();
17
19
  const environment = useEnvironment();
18
20
  const world = useWorld();
19
21
  const resourceByName = useResourceByName();
@@ -23,6 +25,17 @@ export const provideFrames = (partID) => {
23
25
  const logs = useLogs();
24
26
  const pendingSaveKey = $derived(`viam-pending-save-revision:${partID()}`);
25
27
  let didRecentlyEdit = $state(false);
28
+ let lastPartID;
29
+ $effect.pre(() => {
30
+ const id = partID();
31
+ if (lastPartID !== undefined && lastPartID !== id) {
32
+ // Don't let an edited flag from the previous part bleed into the
33
+ // new one — the merge condition would otherwise stay forced on for
34
+ // a freshly-switched part the user hasn't touched.
35
+ didRecentlyEdit = false;
36
+ }
37
+ lastPartID = id;
38
+ });
26
39
  const isEditMode = $derived(environment.current.viewerMode === 'edit');
27
40
  const query = createRobotQuery(client, 'frameSystemConfig', () => ({
28
41
  refetchOnWindowFocus: false,
@@ -47,11 +60,15 @@ export const provideFrames = (partID) => {
47
60
  frames[frame.referenceFrame] = frame;
48
61
  }
49
62
  }
50
- // Let config frames take priority if the user has made edits,
51
- // has a pending save, or is disconnected
63
+ // Let config frames take priority if the user has made edits, has a
64
+ // pending save, or we don't have a live robot connection. The latter
65
+ // covers DISCONNECTED, CONNECTING, and the undefined case where the
66
+ // embedder never provided a dial config (e.g. the Viam app's
67
+ // dialConfigsForParts filters to live parts only, so offline parts
68
+ // never transition through DISCONNECTED).
52
69
  if (didRecentlyEdit ||
53
70
  partConfig.hasPendingSave ||
54
- connectionStatus.current === MachineConnectionEvent.DISCONNECTED) {
71
+ connectionStatus.current !== MachineConnectionEvent.CONNECTED) {
55
72
  const mergedFrames = {
56
73
  ...frames,
57
74
  ...configFrames.current,
@@ -73,7 +90,7 @@ export const provideFrames = (partID) => {
73
90
  });
74
91
  const current = $derived(Object.values(frames));
75
92
  const entities = new Map();
76
- $effect.pre(() => {
93
+ $effect(() => {
77
94
  if (revision) {
78
95
  untrack(() => query.refetch());
79
96
  }
@@ -145,6 +162,13 @@ export const provideFrames = (partID) => {
145
162
  subtypeToColor(currentComponentSubtypeByName[frame.referenceFrame]);
146
163
  const existing = entities.get(entityKey);
147
164
  if (existing) {
165
+ // Active edit session owns the entity's traits for the duration of
166
+ // the user's gesture. Skip the entire re-sync — re-setting Parent
167
+ // would re-evaluate the <Portal> id and re-mount the group,
168
+ // detaching the gizmo's drag target mid-stroke.
169
+ if (editSession.current?.owns(existing)) {
170
+ continue;
171
+ }
148
172
  traits.setParentTrait(existing, parent);
149
173
  if (color) {
150
174
  existing.set(traits.Color, color);
@@ -153,14 +177,30 @@ export const provideFrames = (partID) => {
153
177
  existing.set(traits.Center, center);
154
178
  }
155
179
  traits.updateGeometryTrait(existing, frame.physicalObject);
156
- existing.set(traits.EditedPose, pose);
180
+ if (!isEditMode && !partConfig.hasPendingSave) {
181
+ existing.set(traits.Pose, pose);
182
+ }
183
+ if (!existing.has(traits.LivePose)) {
184
+ existing.add(traits.LivePose(pose));
185
+ }
186
+ // Skip the EditedPose overwrite while in edit mode. The merged
187
+ // `frames` source can differ from query.data once didRecentlyEdit
188
+ // flips (fragment overrides, round-trip drift), and writing those
189
+ // values would shift entities whose parents the user is portaling
190
+ // into — the gizmo's drag target moves underneath it. Once we're
191
+ // back in monitor mode, the next sync resumes the overwrite.
192
+ if (!isEditMode) {
193
+ existing.set(traits.EditedPose, pose);
194
+ }
157
195
  continue;
158
196
  }
159
197
  const entityTraits = [
160
198
  traits.Name(name),
161
199
  traits.Pose(pose),
162
200
  traits.EditedPose(pose),
201
+ traits.LivePose(pose),
163
202
  traits.FramesAPI,
203
+ traits.Transformable,
164
204
  traits.ShowAxesHelper,
165
205
  ...traits.getParentTrait(parent),
166
206
  ];
@@ -273,7 +273,17 @@ const useStandalonePartConfig = (partID) => {
273
273
  }
274
274
  return results;
275
275
  });
276
+ let lastPartID;
276
277
  $effect.pre(() => {
278
+ const id = partID();
279
+ if (lastPartID !== undefined && lastPartID !== id) {
280
+ // Part changed: drop any in-memory edits/pending-save state from the
281
+ // previous part. `current` is left for the existing sync below to
282
+ // repopulate once the new part's networkPartConfig arrives.
283
+ isDirty = false;
284
+ hasPendingSave = false;
285
+ }
286
+ lastPartID = id;
277
287
  if (!networkPartConfig || isDirty) {
278
288
  return;
279
289
  }
@@ -9,9 +9,8 @@ export interface Settings {
9
9
  };
10
10
  disabledCameras: Record<string, boolean>;
11
11
  disabledVisionServices: Record<string, boolean>;
12
- transforming: boolean;
13
12
  snapping: boolean;
14
- transformMode: 'translate' | 'rotate' | 'scale';
13
+ transformMode: 'none' | 'translate' | 'rotate' | 'scale';
15
14
  grid: boolean;
16
15
  gridCellSize: number;
17
16
  gridSectionSize: number;
@@ -24,7 +23,6 @@ export interface Settings {
24
23
  enableMeasureAxisY: boolean;
25
24
  enableMeasureAxisZ: boolean;
26
25
  enableLabels: boolean;
27
- enableKeybindings: boolean;
28
26
  enableQueryDevtools: boolean;
29
27
  enableArmPositionsWidget: boolean;
30
28
  openCameraWidgets: Record<string, string[]>;
@@ -15,9 +15,8 @@ const defaults = () => ({
15
15
  },
16
16
  disabledCameras: {},
17
17
  disabledVisionServices: {},
18
- transforming: false,
19
18
  snapping: false,
20
- transformMode: 'translate',
19
+ transformMode: 'none',
21
20
  grid: true,
22
21
  gridCellSize: 0.5,
23
22
  gridSectionSize: 10,
@@ -31,7 +30,6 @@ const defaults = () => ({
31
30
  enableMeasureAxisY: true,
32
31
  enableMeasureAxisZ: true,
33
32
  enableLabels: false,
34
- enableKeybindings: true,
35
33
  enableQueryDevtools: false,
36
34
  enableArmPositionsWidget: false,
37
35
  openCameraWidgets: {},
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
+ /** @deprecated MotionTools has been renamed to Visualizer. This export will be removed in v2. */
1
2
  export { default as MotionTools } from './components/App.svelte';
3
+ export { default as Visualizer } from './components/App.svelte';
2
4
  export { default as SelectionTool } from './components/Selection/Tool.svelte';
3
5
  export { default as PCD } from './components/PCD.svelte';
4
6
  export * as relations from './ecs/relations';
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
+ /** @deprecated MotionTools has been renamed to Visualizer. This export will be removed in v2. */
1
2
  export { default as MotionTools } from './components/App.svelte';
3
+ export { default as Visualizer } from './components/App.svelte';
2
4
  // Plugins
3
5
  export { default as SelectionTool } from './components/Selection/Tool.svelte';
4
6
  export { default as PCD } from './components/PCD.svelte';
package/dist/transform.js CHANGED
@@ -105,3 +105,16 @@ export const matrixToPose = (matrix) => {
105
105
  pose.theta = MathUtils.radToDeg(ov.th);
106
106
  return pose;
107
107
  };
108
+ export const composeRenderedPose = (livePose, baselinePose, editedPose) => matrixToPose(poseToMatrix(livePose)
109
+ .multiply(poseToMatrix(baselinePose).invert())
110
+ .multiply(poseToMatrix(editedPose)));
111
+ export const composeEditedPoseForRenderedPose = (baselinePose, livePose, renderedPose) => matrixToPose(poseToMatrix(baselinePose)
112
+ .multiply(poseToMatrix(livePose).invert())
113
+ .multiply(poseToMatrix(renderedPose)));
114
+ export const isFinitePose = (pose) => Number.isFinite(pose.x) &&
115
+ Number.isFinite(pose.y) &&
116
+ Number.isFinite(pose.z) &&
117
+ Number.isFinite(pose.oX) &&
118
+ Number.isFinite(pose.oY) &&
119
+ Number.isFinite(pose.oZ) &&
120
+ Number.isFinite(pose.theta);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "1.21.0",
3
+ "version": "1.23.0",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -25,8 +25,8 @@
25
25
  "@testing-library/jest-dom": "6.8.0",
26
26
  "@testing-library/svelte": "5.2.8",
27
27
  "@testing-library/user-event": "^14.6.1",
28
- "@threlte/core": "8.5.5",
29
- "@threlte/extras": "9.14.1",
28
+ "@threlte/core": "8.5.11",
29
+ "@threlte/extras": "9.15.0",
30
30
  "@threlte/rapier": "3.4.1",
31
31
  "@threlte/xr": "1.5.2",
32
32
  "@types/bun": "1.2.21",
@@ -147,9 +147,8 @@
147
147
  "uuid-tool": "^2.0.3"
148
148
  },
149
149
  "scripts": {
150
- "dev": "pnpm dev:bun",
150
+ "dev": "concurrently \"pnpm dev:bun\" \"go run cmd/draw-server/main.go -port 3030\"",
151
151
  "dev:bun": "tsx server/check-bun && bun run server/server.ts",
152
- "dev:next": "concurrently \"pnpm dev:bun\" \"go run cmd/draw-server/main.go -port 3030\"",
153
152
  "dev:https": "vite dev -- --https",
154
153
  "build": "vite build && npm run prepack",
155
154
  "build:workers": "node scripts/build-workers.js",
@@ -174,6 +173,9 @@
174
173
  "vet:client": "go vet ./client/...",
175
174
  "vet": "pnpm vet:draw && pnpm vet:client",
176
175
  "model-pipeline:run": "node scripts/model-pipeline.js",
177
- "release": "changeset publish"
176
+ "release": "changeset publish",
177
+ "docs:dev": "pnpm --dir docs dev",
178
+ "docs:build": "pnpm --dir docs build",
179
+ "docs:preview": "pnpm --dir docs preview"
178
180
  }
179
181
  }
@@ -1,7 +0,0 @@
1
- import type { CameraControlsRef } from '@threlte/extras';
2
- interface Props {
3
- cameraControls: CameraControlsRef;
4
- }
5
- declare const KeyboardControls: import("svelte").Component<Props, {}, "">;
6
- type KeyboardControls = ReturnType<typeof KeyboardControls>;
7
- export default KeyboardControls;