@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.
- package/README.md +18 -100
- package/dist/FrameConfigUpdater.svelte.d.ts +0 -1
- package/dist/FrameConfigUpdater.svelte.js +6 -24
- package/dist/components/App.svelte +3 -3
- package/dist/components/App.svelte.d.ts +1 -1
- package/dist/components/CameraControls.svelte +6 -6
- package/dist/components/Entities/Pose.svelte +18 -13
- package/dist/components/FileDrop/useFileDrop.svelte.js +16 -2
- package/dist/components/{KeyboardControls.svelte → InputBindings.svelte} +50 -77
- package/dist/components/InputBindings.svelte.d.ts +7 -0
- package/dist/components/PointerMissBox.svelte +1 -1
- package/dist/components/Scene.svelte +2 -0
- package/dist/components/SceneProviders.svelte +2 -0
- package/dist/components/SelectedTransformControls.svelte +227 -0
- package/dist/components/SelectedTransformControls.svelte.d.ts +3 -0
- package/dist/components/StaticGeometries.svelte +3 -56
- package/dist/components/overlay/Details.svelte +82 -54
- package/dist/components/overlay/dashboard/Button.svelte +4 -2
- package/dist/components/overlay/dashboard/Button.svelte.d.ts +1 -1
- package/dist/components/overlay/dashboard/Dashboard.svelte +43 -33
- package/dist/ecs/traits.d.ts +15 -0
- package/dist/ecs/traits.js +7 -0
- package/dist/editing/FrameEditSession.d.ts +37 -0
- package/dist/editing/FrameEditSession.js +178 -0
- package/dist/hooks/useEnvironment.svelte.d.ts +1 -0
- package/dist/hooks/useEnvironment.svelte.js +1 -0
- package/dist/hooks/useFrameEditSession.svelte.d.ts +15 -0
- package/dist/hooks/useFrameEditSession.svelte.js +36 -0
- package/dist/hooks/useFrames.svelte.js +45 -5
- package/dist/hooks/usePartConfig.svelte.js +10 -0
- package/dist/hooks/useSettings.svelte.d.ts +1 -3
- package/dist/hooks/useSettings.svelte.js +1 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/transform.js +13 -0
- package/package.json +8 -6
- package/dist/components/KeyboardControls.svelte.d.ts +0 -7
package/dist/ecs/traits.js
CHANGED
|
@@ -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
|
+
}
|
|
@@ -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
|
-
//
|
|
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
|
|
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
|
|
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
|
-
|
|
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: '
|
|
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.
|
|
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.
|
|
29
|
-
"@threlte/extras": "9.
|
|
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;
|