@viamrobotics/motion-tools 0.15.2 → 0.15.3

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.
@@ -56,7 +56,9 @@ export declare class WorldObject<T extends Geometries = Geometries> {
56
56
  geometry?: T;
57
57
  metadata: Metadata;
58
58
  localEditedPose: PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Pose>;
59
- constructor(name: string, pose?: Pose, parent?: string, geometry?: T, metadata?: Metadata);
59
+ constructor(name?: string, pose?: Pose, parent?: string, geometry?: T, metadata?: Metadata);
60
+ toJSON(): Omit<WorldObject, 'toJSON' | 'fromJSON' | 'metadata'>;
61
+ fromJSON(json: WorldObject): this;
60
62
  }
61
63
  export declare const parseMetadata: (fields?: PlainMessage<Struct>["fields"]) => Metadata;
62
64
  export declare const fromTransform: (transform: TransformWithUUID) => WorldObject<PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Geometry>>;
@@ -22,15 +22,14 @@ export const isMetadataKey = (key) => {
22
22
  return METADATA_KEYS.includes(key);
23
23
  };
24
24
  export class WorldObject {
25
- uuid;
26
- name;
25
+ uuid = MathUtils.generateUUID();
26
+ name = '';
27
27
  referenceFrame = $state.raw();
28
28
  pose = $state.raw(createPose());
29
- geometry;
29
+ geometry = $state();
30
30
  metadata = $state({});
31
31
  localEditedPose = $state.raw(createPose());
32
- constructor(name, pose, parent = 'world', geometry, metadata) {
33
- this.uuid = MathUtils.generateUUID();
32
+ constructor(name = '', pose, parent = 'world', geometry, metadata) {
34
33
  this.name = name;
35
34
  this.referenceFrame = parent;
36
35
  this.geometry = geometry;
@@ -42,6 +41,26 @@ export class WorldObject {
42
41
  this.localEditedPose = { ...pose };
43
42
  }
44
43
  }
44
+ toJSON() {
45
+ return {
46
+ uuid: this.uuid,
47
+ name: this.name,
48
+ referenceFrame: $state.snapshot(this.referenceFrame),
49
+ pose: $state.snapshot(this.pose),
50
+ geometry: $state.snapshot(this.geometry),
51
+ localEditedPose: $state.snapshot(this.localEditedPose),
52
+ };
53
+ }
54
+ fromJSON(json) {
55
+ this.uuid = json.uuid;
56
+ this.name = json.name;
57
+ this.referenceFrame = json.referenceFrame;
58
+ this.pose = json.pose;
59
+ this.geometry = json.geometry;
60
+ this.localEditedPose = json.localEditedPose;
61
+ this.metadata = {};
62
+ return this;
63
+ }
45
64
  }
46
65
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
66
  const unwrapValue = (value) => {
@@ -40,7 +40,7 @@
40
40
  metadata={object.metadata}
41
41
  >
42
42
  {#snippet children({ ref })}
43
- {#if selected.current === ref.uuid}
43
+ {#if selected.current === object.uuid}
44
44
  {#key mode}
45
45
  <TransformControls
46
46
  object={ref}
@@ -63,6 +63,8 @@
63
63
  scaleToDimensions(ref.scale, object.geometry.geometryType)
64
64
  ref.scale.setScalar(1)
65
65
  }
66
+
67
+ object.pose = { ...object.pose }
66
68
  }}
67
69
  />
68
70
  {/key}
@@ -0,0 +1,2 @@
1
+ import { WorldObject } from '../../../WorldObject.svelte';
2
+ export declare const createWorldObjectFixture: () => WorldObject<import("../../../WorldObject.svelte").Geometries>;
@@ -0,0 +1,35 @@
1
+ import { WorldObject } from '../../../WorldObject.svelte';
2
+ export const createWorldObjectFixture = () => {
3
+ const object = new WorldObject();
4
+ object.name = 'Test Object';
5
+ object.uuid = '1234-5678';
6
+ object.referenceFrame = 'parent_frame';
7
+ object.pose = {
8
+ x: 10,
9
+ y: 20,
10
+ z: 30,
11
+ oX: 0.1,
12
+ oY: 0.2,
13
+ oZ: 0.3,
14
+ theta: 0.4,
15
+ };
16
+ object.geometry = {
17
+ label: 'my geometry',
18
+ geometryType: {
19
+ case: 'box',
20
+ value: {
21
+ dimsMm: { x: 10, y: 20, z: 30 },
22
+ },
23
+ },
24
+ };
25
+ object.localEditedPose = {
26
+ x: 10,
27
+ y: 20,
28
+ z: 30,
29
+ oX: 0.1,
30
+ oY: 0.2,
31
+ oZ: 0.3,
32
+ theta: 0.4,
33
+ };
34
+ return object;
35
+ };
@@ -8,6 +8,7 @@ import { useEnvironment } from './useEnvironment.svelte';
8
8
  import { createPoseFromFrame } from '../transform';
9
9
  import { createGeometryFromFrame } from '../geometry';
10
10
  import { useResourceByName } from './useResourceByName.svelte';
11
+ import { usePersistentUUIDs } from './usePersistentUUIDs.svelte';
11
12
  const key = Symbol('frames-context');
12
13
  export const provideFrames = (partID) => {
13
14
  const resourceByName = useResourceByName();
@@ -18,6 +19,7 @@ export const provideFrames = (partID) => {
18
19
  const revision = $derived(machineStatus.current?.config?.revision);
19
20
  const partConfig = usePartConfig();
20
21
  const environment = useEnvironment();
22
+ const { updateUUIDs } = usePersistentUUIDs();
21
23
  $effect.pre(() => {
22
24
  if (revision) {
23
25
  untrack(() => query.current).refetch();
@@ -32,8 +34,8 @@ export const provideFrames = (partID) => {
32
34
  environment.current.viewerMode = 'monitor';
33
35
  }
34
36
  });
35
- let current = $derived.by(() => {
36
- const objects = [];
37
+ const machineFrames = $derived.by(() => {
38
+ const objects = {};
37
39
  for (const { frame } of query.current.data ?? []) {
38
40
  if (frame === undefined) {
39
41
  continue;
@@ -41,60 +43,34 @@ export const provideFrames = (partID) => {
41
43
  const resourceName = resourceByName.current[frame.referenceFrame];
42
44
  const frameName = frame.referenceFrame ? frame.referenceFrame : 'Unnamed frame';
43
45
  const color = resourceNameToColor(resourceName);
44
- objects.push(new WorldObject(frameName, frame.poseInObserverFrame?.pose, frame.poseInObserverFrame?.referenceFrame, frame.physicalObject, color ? { color } : undefined));
46
+ objects[frameName] = new WorldObject(frameName, frame.poseInObserverFrame?.pose, frame.poseInObserverFrame?.referenceFrame, frame.physicalObject, color ? { color } : undefined);
45
47
  }
46
48
  return objects;
47
49
  });
48
- let currentWorldObjects = {};
49
- const getWorldObjects = () => ({ ...currentWorldObjects });
50
- const getWorldObject = (componentName) => currentWorldObjects[componentName];
51
- const setWorldObject = (component, worldObject) => {
52
- if (!component.frame) {
53
- return;
54
- }
55
- worldObject.referenceFrame = component.frame.parent;
56
- worldObject.localEditedPose = createPoseFromFrame(component.frame);
57
- if (component.frame.geometry) {
58
- worldObject.geometry = createGeometryFromFrame(component.frame);
59
- }
60
- else {
61
- worldObject.geometry = undefined;
62
- }
63
- currentWorldObjects[component.name] = worldObject;
64
- };
65
- const deleteWorldObject = (componentName) => {
66
- delete currentWorldObjects[componentName];
67
- };
68
- $effect.pre(() => {
69
- untrack(() => {
70
- currentWorldObjects = {};
71
- for (const currentWorldObject of current) {
72
- currentWorldObjects[currentWorldObject.name] = currentWorldObject;
73
- }
74
- });
75
- const components = partConfig.localPartConfig.toJson()?.components;
76
- const fragmentMods = partConfig.localPartConfig.toJson()
77
- ?.fragment_mods;
78
- const fragmentDefinedComponents = Object.keys(partConfig.componentNameToFragmentId);
50
+ const [configFrames, configUnsetFrames] = $derived.by(() => {
51
+ const components = partConfig.localPartConfig.toJson().components;
52
+ const objects = [];
53
+ const unsetObjects = [];
79
54
  // deal with part defined frame config
80
- for (const component of components || []) {
81
- const worldObject = getWorldObject(component.name);
82
- if (worldObject && component.frame) {
83
- setWorldObject(component, worldObject);
84
- }
85
- else if (component.frame && Object.keys(getWorldObjects()).length > 0) {
86
- // extra clause to prevent adding a component to the world objects when it may be loaded via frame system config later (first tick issue where config updated but current world objects not triggered yet)
87
- const pose = createPoseFromFrame(component.frame);
88
- const newWorldObject = new WorldObject(component.name, pose, component.frame.parent);
89
- setWorldObject(component, newWorldObject);
90
- }
91
- else {
92
- deleteWorldObject(component.name);
55
+ for (const component of components ?? []) {
56
+ if (!component.frame) {
57
+ unsetObjects.push(component.name);
58
+ continue;
93
59
  }
60
+ const pose = createPoseFromFrame(component.frame);
61
+ const geometry = createGeometryFromFrame(component.frame);
62
+ const worldObject = new WorldObject(component.name, pose, component.frame.parent, geometry);
63
+ objects.push(worldObject);
94
64
  }
65
+ return [objects, unsetObjects];
66
+ });
67
+ const [fragmentFrames, fragmentUnsetFrames] = $derived.by(() => {
68
+ const { fragment_mods: fragmentMods = [] } = partConfig.localPartConfig.toJson() ?? {};
69
+ const fragmentDefinedComponents = Object.keys(partConfig.componentNameToFragmentId);
70
+ const objects = [];
71
+ const unsetObjects = [];
95
72
  // deal with fragment defined components
96
73
  for (const fragmentComponentName of fragmentDefinedComponents || []) {
97
- const worldObject = getWorldObject(fragmentComponentName);
98
74
  const fragmentId = partConfig.componentNameToFragmentId[fragmentComponentName];
99
75
  const fragmentMod = fragmentMods?.find((mod) => mod.fragment_id === fragmentId);
100
76
  if (!fragmentMod) {
@@ -103,27 +79,62 @@ export const provideFrames = (partID) => {
103
79
  const setComponentModIndex = fragmentMod.mods.findLastIndex((mod) => mod['$set']?.[`components.${fragmentComponentName}.frame`] !== undefined);
104
80
  const unsetComponentModIndex = fragmentMod.mods.findLastIndex((mod) => mod['$unset']?.[`components.${fragmentComponentName}.frame`] !== undefined);
105
81
  if (setComponentModIndex < unsetComponentModIndex) {
106
- deleteWorldObject(fragmentComponentName);
82
+ unsetObjects.push(fragmentComponentName);
107
83
  }
108
84
  else if (unsetComponentModIndex < setComponentModIndex) {
109
85
  const frameData = fragmentMod.mods[setComponentModIndex]['$set'][`components.${fragmentComponentName}.frame`];
110
- const componentConfig = {
111
- name: fragmentComponentName,
112
- frame: frameData,
113
- };
114
- if (worldObject) {
115
- setWorldObject(componentConfig, worldObject);
116
- }
117
- else if (Object.keys(getWorldObjects()).length > 0) {
118
- const pose = createPoseFromFrame(frameData);
119
- const newWorldObject = new WorldObject(fragmentComponentName, pose, frameData.parent);
120
- setWorldObject(componentConfig, newWorldObject);
121
- }
86
+ const pose = createPoseFromFrame(frameData);
87
+ const geometry = createGeometryFromFrame(frameData);
88
+ const worldObject = new WorldObject(fragmentComponentName, pose, frameData.parent, geometry);
89
+ objects.push(worldObject);
122
90
  }
123
91
  }
124
- untrack(() => {
125
- current = [...Object.values(getWorldObjects())];
126
- });
92
+ return [objects, unsetObjects];
93
+ });
94
+ $effect.pre(() => {
95
+ for (const frame of configFrames) {
96
+ const result = machineFrames[frame.name];
97
+ if (result) {
98
+ result.referenceFrame = frame.referenceFrame;
99
+ result.localEditedPose = frame.pose;
100
+ result.geometry = frame.geometry;
101
+ }
102
+ else {
103
+ machineFrames[frame.name] = frame;
104
+ }
105
+ }
106
+ });
107
+ $effect.pre(() => {
108
+ for (const frame of fragmentFrames) {
109
+ const result = machineFrames[frame.name];
110
+ if (result) {
111
+ result.referenceFrame = frame.referenceFrame;
112
+ result.localEditedPose = frame.pose;
113
+ result.geometry = frame.geometry;
114
+ }
115
+ else {
116
+ machineFrames[frame.name] = frame;
117
+ }
118
+ }
119
+ });
120
+ $effect.pre(() => {
121
+ for (const name of configUnsetFrames) {
122
+ delete machineFrames[name];
123
+ }
124
+ });
125
+ $effect.pre(() => {
126
+ for (const name of fragmentUnsetFrames) {
127
+ delete machineFrames[name];
128
+ }
129
+ });
130
+ const current = $derived.by(() => {
131
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
132
+ const _configFrames = configFrames;
133
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
134
+ const _fragmentFrames = fragmentFrames;
135
+ const results = Object.values(machineFrames);
136
+ updateUUIDs(results);
137
+ return results;
127
138
  });
128
139
  const error = $derived(query.current.error ?? undefined);
129
140
  const fetching = $derived(query.current.isFetching);
@@ -8,6 +8,7 @@ import { useMotionClient } from './useMotionClient.svelte';
8
8
  import { useEnvironment } from './useEnvironment.svelte';
9
9
  import { observe } from '@threlte/core';
10
10
  import { untrack } from 'svelte';
11
+ import { useFrames } from './useFrames.svelte';
11
12
  export const usePose = (name, parent) => {
12
13
  const { refreshRates } = useMachineSettings();
13
14
  const partID = usePartID();
@@ -16,6 +17,7 @@ export const usePose = (name, parent) => {
16
17
  const resource = $derived(resources.current.find((resource) => resource.name === name()));
17
18
  const parentResource = $derived(resources.current.find((resource) => resource.name === parent()));
18
19
  const environment = useEnvironment();
20
+ const frames = useFrames();
19
21
  const client = createResourceClient(MotionClient, () => partID.current, () => motionClient.current ?? '');
20
22
  const interval = $derived(refreshRates.get(RefreshRates.poses));
21
23
  const options = $derived(queryOptions({
@@ -34,7 +36,7 @@ export const usePose = (name, parent) => {
34
36
  },
35
37
  }));
36
38
  const query = fromStore(createQuery(toStore(() => options)));
37
- observe.pre(() => [environment.current.viewerMode], () => {
39
+ observe.pre(() => [environment.current.viewerMode, frames.current], () => {
38
40
  if (environment.current.viewerMode === 'monitor') {
39
41
  untrack(() => query.current).refetch();
40
42
  }
@@ -5,15 +5,25 @@ import { createGeometry } from '../geometry';
5
5
  import { WorldObject } from '../WorldObject.svelte';
6
6
  const key = Symbol('static-geometries-context');
7
7
  export const provideStaticGeometries = () => {
8
- let geometries = $state([]);
8
+ const geometries = $state([]);
9
+ let loaded = $state(false);
9
10
  const debounced = new Debounced(() => geometries, 500);
10
11
  get('static-geometries').then((response) => {
11
12
  if (Array.isArray(response)) {
12
- geometries = response;
13
+ for (const json of response) {
14
+ geometries.push(new WorldObject().fromJSON(json));
15
+ }
13
16
  }
17
+ loaded = true;
14
18
  });
15
19
  $effect(() => {
16
- set('static-geometries', $state.snapshot(debounced.current));
20
+ if (!loaded)
21
+ return;
22
+ const results = [];
23
+ for (const geometry of debounced.current) {
24
+ results.push(geometry.toJSON());
25
+ }
26
+ set('static-geometries', results);
17
27
  });
18
28
  setContext(key, {
19
29
  get current() {
@@ -24,7 +34,7 @@ export const provideStaticGeometries = () => {
24
34
  case: 'box',
25
35
  value: { dimsMm: { x: 100, y: 100, z: 100 } },
26
36
  }));
27
- geometries.push(structuredClone(object));
37
+ geometries.push(object);
28
38
  },
29
39
  remove(name) {
30
40
  const index = geometries.findIndex((geo) => geo.name === name);
@@ -5,7 +5,6 @@ export declare class BatchedArrow {
5
5
  _geometryId: number;
6
6
  _pool: number[];
7
7
  _ids: Set<number>;
8
- _id: number;
9
8
  _max: number;
10
9
  constructor();
11
10
  addArrow(direction: Vector3, origin: Vector3, length?: number, color?: Color, arrowHeadAtPose?: boolean): number;
@@ -12,7 +12,6 @@ export class BatchedArrow {
12
12
  _geometryId;
13
13
  _pool = [];
14
14
  _ids = new Set();
15
- _id = 0;
16
15
  _max = 20_000;
17
16
  constructor() {
18
17
  const material = new MeshBasicMaterial({ color: 0xffffff, toneMapped: false });
@@ -2,7 +2,7 @@ import type { Geometry, Pose } from '@viamrobotics/sdk';
2
2
  import { type Object3D, Matrix4, Quaternion, Vector3 } from 'three';
3
3
  import type { Frame } from './frame';
4
4
  export declare const createPose: (pose?: Pose) => Pose;
5
- export declare const createPoseFromFrame: (frame: Frame) => Pose;
5
+ export declare const createPoseFromFrame: (frame: Partial<Frame>) => Pose;
6
6
  export declare const quaternionToPose: (quaternion: Quaternion, pose: Partial<Pose>) => void;
7
7
  export declare const vector3ToPose: (vec3: Vector3, pose: Partial<Pose>) => void;
8
8
  export declare const object3dToPose: (object3d: Object3D, pose: Partial<Pose>) => Partial<import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Pose>>;
package/dist/transform.js CHANGED
@@ -17,26 +17,26 @@ export const createPose = (pose) => {
17
17
  };
18
18
  };
19
19
  export const createPoseFromFrame = (frame) => {
20
- if (frame.orientation.type === 'quaternion') {
20
+ if (frame.orientation?.type === 'quaternion') {
21
21
  quaternion.copy(frame.orientation.value);
22
22
  ov.setFromQuaternion(quaternion);
23
23
  }
24
- else if (frame.orientation.type === 'euler_angles') {
24
+ else if (frame.orientation?.type === 'euler_angles') {
25
25
  euler.set(frame.orientation.value.roll, frame.orientation.value.pitch, frame.orientation.value.yaw, 'ZYX');
26
26
  quaternion.setFromEuler(euler);
27
27
  ov.setFromQuaternion(quaternion);
28
28
  }
29
- else if (frame.orientation.type === 'ov_radians') {
29
+ else if (frame.orientation?.type === 'ov_radians') {
30
30
  ov.copy(frame.orientation.value);
31
31
  }
32
32
  else {
33
- const th = MathUtils.degToRad(frame.orientation.value.th);
34
- ov.set(frame.orientation.value.x, frame.orientation.value.y, frame.orientation.value.z, th);
33
+ const th = MathUtils.degToRad(frame.orientation?.value.th ?? 0);
34
+ ov.set(frame.orientation?.value.x, frame.orientation?.value.y, frame.orientation?.value.z, th);
35
35
  }
36
36
  return {
37
- x: frame.translation.x ?? 0,
38
- y: frame.translation.y ?? 0,
39
- z: frame.translation.z ?? 0,
37
+ x: frame.translation?.x ?? 0,
38
+ y: frame.translation?.y ?? 0,
39
+ z: frame.translation?.z ?? 0,
40
40
  oX: ov.x,
41
41
  oY: ov.y,
42
42
  oZ: ov.z,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "0.15.2",
3
+ "version": "0.15.3",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -56,7 +56,7 @@
56
56
  "prettier-plugin-tailwindcss": "0.6.14",
57
57
  "publint": "0.3.12",
58
58
  "runed": "0.31.1",
59
- "svelte": "5.38.7",
59
+ "svelte": "5.43.0",
60
60
  "svelte-check": "4.3.1",
61
61
  "svelte-virtuallists": "1.4.2",
62
62
  "tailwindcss": "4.1.13",