@viamrobotics/motion-tools 0.10.0 → 0.11.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/dist/WorldObject.svelte.d.ts +1 -0
- package/dist/WorldObject.svelte.js +44 -2
- package/dist/components/Geometry.svelte +1 -1
- package/dist/components/SceneProviders.svelte +2 -1
- package/dist/components/Tree/buildTree.d.ts +2 -4
- package/dist/components/Tree/buildTree.js +2 -4
- package/dist/components/WorldObjects.svelte +2 -2
- package/dist/components/WorldState.svelte +3 -3
- package/dist/components/WorldState.svelte.d.ts +1 -1
- package/dist/hooks/useWorldState.svelte.d.ts +54 -15
- package/dist/hooks/useWorldState.svelte.js +111 -57
- package/dist/workers/worldStateWorker.d.ts +1 -0
- package/dist/workers/worldStateWorker.js +109 -0
- package/dist/world-state-messages.d.ts +23 -0
- package/dist/world-state-messages.js +1 -0
- package/package.json +5 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BatchedMesh, Box3, MathUtils, Object3D, Vector3 } from 'three';
|
|
1
|
+
import { BatchedMesh, Box3, Color, MathUtils, Object3D, Vector3, } from 'three';
|
|
2
2
|
import { createPose } from './transform';
|
|
3
3
|
export class WorldObject {
|
|
4
4
|
uuid;
|
|
@@ -18,8 +18,50 @@ export class WorldObject {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
const unwrapValue = (value) => {
|
|
22
|
+
if (!value?.kind)
|
|
23
|
+
return value;
|
|
24
|
+
switch (value.kind.case) {
|
|
25
|
+
case 'numberValue':
|
|
26
|
+
case 'stringValue':
|
|
27
|
+
case 'boolValue':
|
|
28
|
+
return value.kind.value;
|
|
29
|
+
case 'structValue': {
|
|
30
|
+
// Recursively unwrap nested struct
|
|
31
|
+
const result = {};
|
|
32
|
+
for (const [key, val] of Object.entries(value.kind.value.fields || {})) {
|
|
33
|
+
result[key] = unwrapValue(val);
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
case 'listValue':
|
|
38
|
+
return value.kind.value.values?.map(unwrapValue) || [];
|
|
39
|
+
case 'nullValue':
|
|
40
|
+
return null;
|
|
41
|
+
default:
|
|
42
|
+
return value.kind.value;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const parseMetadata = (metadata) => {
|
|
46
|
+
let json = {};
|
|
47
|
+
for (const [k, v] of Object.entries(metadata)) {
|
|
48
|
+
const unwrappedValue = unwrapValue(v);
|
|
49
|
+
// TODO: Remove special case and add better handling for metadata
|
|
50
|
+
if (k === 'color' && unwrappedValue && typeof unwrappedValue === 'object') {
|
|
51
|
+
const { r, g, b } = unwrappedValue;
|
|
52
|
+
json[k] = new Color().setRGB(r / 255, g / 255, b / 255);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
json = { ...json, [k]: unwrappedValue };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return json;
|
|
59
|
+
};
|
|
21
60
|
export const fromTransform = (transform) => {
|
|
22
|
-
const metadata =
|
|
61
|
+
const metadata = transform.metadata
|
|
62
|
+
? parseMetadata(transform.metadata.fields)
|
|
63
|
+
: {};
|
|
23
64
|
const worldObject = new WorldObject(transform.referenceFrame, transform.poseInObserverFrame?.pose, transform.poseInObserverFrame?.referenceFrame, transform.physicalObject?.geometryType, metadata);
|
|
65
|
+
worldObject.uuid = transform.uuidString;
|
|
24
66
|
return worldObject;
|
|
25
67
|
};
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { provideMotionClient } from '../hooks/useMotionClient.svelte'
|
|
15
15
|
import { provideLogs } from '../hooks/useLogs.svelte'
|
|
16
16
|
import { provideOrigin } from './xr/useOrigin.svelte'
|
|
17
|
-
|
|
17
|
+
import { provideWorldStates } from '../hooks/useWorldState.svelte'
|
|
18
18
|
interface Props {
|
|
19
19
|
children: Snippet<[{ focus: boolean }]>
|
|
20
20
|
}
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
providePointclouds(() => partID.current)
|
|
38
38
|
provideMotionClient(() => partID.current)
|
|
39
39
|
provideObjects()
|
|
40
|
+
provideWorldStates()
|
|
40
41
|
|
|
41
42
|
const { focus } = provideSelection()
|
|
42
43
|
</script>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { useWorldStates } from '../../hooks/useWorldState.svelte';
|
|
1
2
|
import type { WorldObject } from '../../WorldObject.svelte';
|
|
2
3
|
export interface TreeNode {
|
|
3
4
|
id: string;
|
|
@@ -8,7 +9,4 @@ export interface TreeNode {
|
|
|
8
9
|
/**
|
|
9
10
|
* Creates a tree representing parent child / relationships from a set of frames.
|
|
10
11
|
*/
|
|
11
|
-
export declare const buildTreeNodes: (objects: WorldObject[], worldStates:
|
|
12
|
-
name: string;
|
|
13
|
-
objects: WorldObject[];
|
|
14
|
-
}[]) => TreeNode[];
|
|
12
|
+
export declare const buildTreeNodes: (objects: WorldObject[], worldStates: ReturnType<typeof useWorldStates>["current"]) => TreeNode[];
|
|
@@ -25,15 +25,14 @@ export const buildTreeNodes = (objects, worldStates) => {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
for (const worldState of worldStates) {
|
|
28
|
+
for (const worldState of Object.values(worldStates)) {
|
|
29
29
|
const node = {
|
|
30
30
|
name: worldState.name,
|
|
31
31
|
id: worldState.name,
|
|
32
32
|
children: [],
|
|
33
33
|
href: `/world-state/${worldState.name}`,
|
|
34
34
|
};
|
|
35
|
-
|
|
36
|
-
for (const object of worldState.objects) {
|
|
35
|
+
for (const object of worldState.worldObjects) {
|
|
37
36
|
const child = {
|
|
38
37
|
name: object.name,
|
|
39
38
|
id: object.uuid,
|
|
@@ -42,7 +41,6 @@ export const buildTreeNodes = (objects, worldStates) => {
|
|
|
42
41
|
};
|
|
43
42
|
nodeMap.set(object.name, child);
|
|
44
43
|
node.children?.push(child);
|
|
45
|
-
console.log('child', child);
|
|
46
44
|
}
|
|
47
45
|
nodeMap.set(worldState.name, node);
|
|
48
46
|
rootNodes.push(node);
|
|
@@ -68,8 +68,8 @@
|
|
|
68
68
|
</Portal>
|
|
69
69
|
{/each}
|
|
70
70
|
|
|
71
|
-
{#each worldStates.
|
|
72
|
-
<WorldState {
|
|
71
|
+
{#each worldStates.names as { name } (name)}
|
|
72
|
+
<WorldState worldObjects={worldStates.current[name].worldObjects} />
|
|
73
73
|
{/each}
|
|
74
74
|
|
|
75
75
|
{#each points.current as object (object.uuid)}
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
import { WorldObject } from '../WorldObject.svelte'
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
|
-
|
|
9
|
+
worldObjects: WorldObject[]
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
let {
|
|
12
|
+
let { worldObjects }: Props = $props()
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
|
-
{#each
|
|
15
|
+
{#each worldObjects as object (object.uuid)}
|
|
16
16
|
<Portal id={object.referenceFrame}>
|
|
17
17
|
<Frame
|
|
18
18
|
uuid={object.uuid}
|
|
@@ -1,19 +1,58 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { type TransformWithUUID, ResourceName } from '@viamrobotics/sdk';
|
|
2
|
+
interface Context {
|
|
3
|
+
names: ResourceName[];
|
|
4
|
+
current: Record<string, ReturnType<typeof createWorldState>>;
|
|
5
|
+
}
|
|
6
|
+
export declare const provideWorldStates: () => void;
|
|
7
|
+
export declare const useWorldStates: () => Context;
|
|
8
|
+
export declare const useWorldState: (resourceName: () => string) => {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly transforms: TransformWithUUID[];
|
|
11
|
+
readonly worldObjects: import("../WorldObject.svelte").WorldObject<{
|
|
12
|
+
case: undefined;
|
|
13
|
+
value?: undefined;
|
|
14
|
+
} | {
|
|
15
|
+
case: "sphere";
|
|
16
|
+
value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Sphere>;
|
|
17
|
+
} | {
|
|
18
|
+
case: "box";
|
|
19
|
+
value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").RectangularPrism>;
|
|
20
|
+
} | {
|
|
21
|
+
case: "capsule";
|
|
22
|
+
value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Capsule>;
|
|
23
|
+
} | {
|
|
24
|
+
case: "mesh";
|
|
25
|
+
value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Mesh>;
|
|
26
|
+
} | {
|
|
27
|
+
case: "pointcloud";
|
|
28
|
+
value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").PointCloud>;
|
|
29
|
+
}>[];
|
|
30
|
+
readonly listUUIDs: import("@tanstack/svelte-query").QueryObserverResult<string[]>;
|
|
31
|
+
readonly getTransforms: import("@tanstack/svelte-query").QueryObserverResult<TransformWithUUID>[] | undefined;
|
|
12
32
|
};
|
|
13
|
-
|
|
33
|
+
declare const createWorldState: (partID: () => string, resourceName: () => string) => {
|
|
14
34
|
readonly name: string;
|
|
15
|
-
readonly
|
|
35
|
+
readonly transforms: TransformWithUUID[];
|
|
36
|
+
readonly worldObjects: import("../WorldObject.svelte").WorldObject<{
|
|
37
|
+
case: undefined;
|
|
38
|
+
value?: undefined;
|
|
39
|
+
} | {
|
|
40
|
+
case: "sphere";
|
|
41
|
+
value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Sphere>;
|
|
42
|
+
} | {
|
|
43
|
+
case: "box";
|
|
44
|
+
value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").RectangularPrism>;
|
|
45
|
+
} | {
|
|
46
|
+
case: "capsule";
|
|
47
|
+
value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Capsule>;
|
|
48
|
+
} | {
|
|
49
|
+
case: "mesh";
|
|
50
|
+
value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Mesh>;
|
|
51
|
+
} | {
|
|
52
|
+
case: "pointcloud";
|
|
53
|
+
value: import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").PointCloud>;
|
|
54
|
+
}>[];
|
|
16
55
|
readonly listUUIDs: import("@tanstack/svelte-query").QueryObserverResult<string[]>;
|
|
17
|
-
readonly getTransforms: import("@tanstack/svelte-query").QueryObserverResult<
|
|
18
|
-
readonly changeStream: import("@tanstack/svelte-query").DefinedQueryObserverResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverLoadingErrorResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverLoadingResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverPendingResult<TransformChangeEvent[], Error> | import("@tanstack/svelte-query").QueryObserverPlaceholderResult<TransformChangeEvent[], Error>;
|
|
56
|
+
readonly getTransforms: import("@tanstack/svelte-query").QueryObserverResult<TransformWithUUID>[] | undefined;
|
|
19
57
|
};
|
|
58
|
+
export {};
|
|
@@ -1,88 +1,145 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { WorldStateStoreClient, TransformChangeType, } from '@viamrobotics/sdk';
|
|
1
|
+
import { WorldStateStoreClient, TransformChangeType, ResourceName, } from '@viamrobotics/sdk';
|
|
3
2
|
import { createResourceClient, createResourceQuery, createResourceStream, useResourceNames, } from '@viamrobotics/svelte-sdk';
|
|
4
|
-
import { fromTransform
|
|
3
|
+
import { fromTransform } from '../WorldObject.svelte';
|
|
5
4
|
import { usePartID } from './usePartID.svelte';
|
|
6
|
-
|
|
5
|
+
import { setInUnsafe } from '@thi.ng/paths';
|
|
6
|
+
import { getContext, setContext } from 'svelte';
|
|
7
|
+
const key = Symbol('world-state-context');
|
|
8
|
+
const worker = new Worker(new URL('../workers/worldStateWorker.ts', import.meta.url), {
|
|
9
|
+
type: 'module',
|
|
10
|
+
});
|
|
11
|
+
export const provideWorldStates = () => {
|
|
7
12
|
const partID = usePartID();
|
|
8
13
|
const resourceNames = useResourceNames(() => partID.current, 'world_state_store');
|
|
9
|
-
const current = $derived.by(() => resourceNames.current.map(({ name }) =>
|
|
10
|
-
|
|
14
|
+
const current = $derived.by(() => Object.fromEntries(resourceNames.current.map(({ name }) => [
|
|
15
|
+
name,
|
|
16
|
+
createWorldState(() => partID.current, () => name),
|
|
17
|
+
])));
|
|
18
|
+
setContext(key, {
|
|
11
19
|
get names() {
|
|
12
20
|
return resourceNames.current;
|
|
13
21
|
},
|
|
14
22
|
get current() {
|
|
15
23
|
return current;
|
|
16
24
|
},
|
|
17
|
-
};
|
|
25
|
+
});
|
|
18
26
|
};
|
|
19
|
-
export const
|
|
27
|
+
export const useWorldStates = () => {
|
|
28
|
+
return getContext(key);
|
|
29
|
+
};
|
|
30
|
+
export const useWorldState = (resourceName) => {
|
|
31
|
+
return useWorldStates().current[resourceName()];
|
|
32
|
+
};
|
|
33
|
+
const createWorldState = (partID, resourceName) => {
|
|
20
34
|
const client = createResourceClient(WorldStateStoreClient, partID, resourceName);
|
|
21
|
-
let initialized = false;
|
|
35
|
+
let initialized = $state(false);
|
|
36
|
+
let transforms = $state.raw({});
|
|
37
|
+
const transformsList = $derived.by(() => Object.values(transforms));
|
|
38
|
+
const worldObjectsList = $derived.by(() => transformsList.map(fromTransform));
|
|
39
|
+
let pendingEvents = [];
|
|
40
|
+
let flushScheduled = false;
|
|
22
41
|
const listUUIDs = createResourceQuery(client, 'listUUIDs');
|
|
23
42
|
const getTransforms = $derived(listUUIDs.current.data?.map((uuid) => {
|
|
24
43
|
return createResourceQuery(client, 'getTransform', () => [uuid], () => ({ refetchInterval: false }));
|
|
25
44
|
}));
|
|
26
|
-
const changeStream = createResourceStream(client, 'streamTransformChanges'
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
const changeStream = createResourceStream(client, 'streamTransformChanges', {
|
|
46
|
+
refetchMode: 'replace',
|
|
47
|
+
});
|
|
48
|
+
const initialize = (initial) => {
|
|
49
|
+
const next = { ...transforms };
|
|
50
|
+
for (const transform of initial) {
|
|
51
|
+
next[transform.uuidString] = transform;
|
|
31
52
|
}
|
|
53
|
+
transforms = next;
|
|
32
54
|
initialized = true;
|
|
33
55
|
};
|
|
34
|
-
|
|
35
|
-
if (
|
|
56
|
+
const applyEvents = (events) => {
|
|
57
|
+
if (events.length === 0)
|
|
36
58
|
return;
|
|
59
|
+
const next = { ...transforms };
|
|
60
|
+
for (const event of events) {
|
|
61
|
+
switch (event.type) {
|
|
62
|
+
case TransformChangeType.ADDED:
|
|
63
|
+
next[event.uuidString] = event.transform;
|
|
64
|
+
break;
|
|
65
|
+
case TransformChangeType.REMOVED:
|
|
66
|
+
delete next[event.uuidString];
|
|
67
|
+
break;
|
|
68
|
+
case TransformChangeType.UPDATED: {
|
|
69
|
+
if (event.changes.length === 0)
|
|
70
|
+
continue;
|
|
71
|
+
let toUpdate = next[event.uuidString];
|
|
72
|
+
if (!toUpdate)
|
|
73
|
+
continue;
|
|
74
|
+
for (const [path, value] of event.changes) {
|
|
75
|
+
toUpdate = setInUnsafe(toUpdate, path, value);
|
|
76
|
+
}
|
|
77
|
+
next[event.uuidString] = toUpdate;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
37
81
|
}
|
|
38
|
-
|
|
82
|
+
transforms = next;
|
|
83
|
+
};
|
|
84
|
+
const scheduleFlush = () => {
|
|
85
|
+
if (flushScheduled)
|
|
86
|
+
return;
|
|
87
|
+
flushScheduled = true;
|
|
88
|
+
requestAnimationFrame(() => {
|
|
89
|
+
const toApply = pendingEvents;
|
|
90
|
+
if (toApply.length === 0)
|
|
91
|
+
return;
|
|
92
|
+
applyEvents(toApply);
|
|
93
|
+
flushScheduled = false;
|
|
94
|
+
pendingEvents = [];
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
$effect(() => {
|
|
98
|
+
if (!getTransforms)
|
|
99
|
+
return;
|
|
100
|
+
if (initialized)
|
|
39
101
|
return;
|
|
40
|
-
}
|
|
41
102
|
const queries = getTransforms.map((query) => query.current);
|
|
42
|
-
if (queries.some((query) => query?.isLoading))
|
|
103
|
+
if (queries.some((query) => query?.isLoading))
|
|
43
104
|
return;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
objects.push(fromTransform(transform));
|
|
51
|
-
}
|
|
52
|
-
initializeCurrent(objects);
|
|
53
|
-
});
|
|
54
|
-
const processChangeEvent = async (event) => {
|
|
55
|
-
if (event.transform === undefined) {
|
|
105
|
+
const data = queries
|
|
106
|
+
.flatMap((query) => query?.data ?? [])
|
|
107
|
+
.filter((transform) => transform !== undefined);
|
|
108
|
+
if (data.length === 0)
|
|
56
109
|
return;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
case TransformChangeType.ADDED:
|
|
60
|
-
worldObjects[event.transform.uuidString] = fromTransform(event.transform);
|
|
61
|
-
break;
|
|
62
|
-
case TransformChangeType.UPDATED:
|
|
63
|
-
for (const path of event.updatedFields?.paths ?? []) {
|
|
64
|
-
// Type inference is tough here, so we use unsafe APIs
|
|
65
|
-
const paths = toPath(path);
|
|
66
|
-
const next = getInUnsafe(event.transform, paths);
|
|
67
|
-
mutInUnsafe(worldObjects[event.transform.uuidString], paths, next);
|
|
68
|
-
}
|
|
69
|
-
break;
|
|
70
|
-
case TransformChangeType.REMOVED:
|
|
71
|
-
delete worldObjects[event.transform.uuidString];
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
};
|
|
110
|
+
initialize(data);
|
|
111
|
+
});
|
|
75
112
|
$effect(() => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
113
|
+
worker.onmessage = (e) => {
|
|
114
|
+
if (e.data.type !== 'process')
|
|
115
|
+
return;
|
|
116
|
+
const { events } = e.data ?? { events: [] };
|
|
117
|
+
if (events.length === 0)
|
|
118
|
+
return;
|
|
119
|
+
pendingEvents.push(...events);
|
|
120
|
+
scheduleFlush();
|
|
121
|
+
};
|
|
122
|
+
return () => {
|
|
123
|
+
worker.terminate();
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
$effect.pre(() => {
|
|
127
|
+
if (changeStream.current?.data === undefined)
|
|
128
|
+
return;
|
|
129
|
+
const events = changeStream.current.data.filter((event) => event.transform !== undefined);
|
|
130
|
+
if (events.length === 0)
|
|
131
|
+
return;
|
|
132
|
+
worker.postMessage({ type: 'change', events });
|
|
79
133
|
});
|
|
80
134
|
return {
|
|
81
135
|
get name() {
|
|
82
136
|
return resourceName();
|
|
83
137
|
},
|
|
84
|
-
get
|
|
85
|
-
return
|
|
138
|
+
get transforms() {
|
|
139
|
+
return transformsList;
|
|
140
|
+
},
|
|
141
|
+
get worldObjects() {
|
|
142
|
+
return worldObjectsList;
|
|
86
143
|
},
|
|
87
144
|
get listUUIDs() {
|
|
88
145
|
return listUUIDs.current;
|
|
@@ -90,8 +147,5 @@ export const useWorldState = (partID, resourceName) => {
|
|
|
90
147
|
get getTransforms() {
|
|
91
148
|
return getTransforms?.map((query) => query.current);
|
|
92
149
|
},
|
|
93
|
-
get changeStream() {
|
|
94
|
-
return changeStream.current;
|
|
95
|
-
},
|
|
96
150
|
};
|
|
97
151
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { getInUnsafe, toPath } from '@thi.ng/paths';
|
|
2
|
+
import { TransformChangeType, } from '@viamrobotics/sdk';
|
|
3
|
+
const createEntry = (event) => {
|
|
4
|
+
if (!event.transform)
|
|
5
|
+
return undefined;
|
|
6
|
+
switch (event.changeType) {
|
|
7
|
+
case TransformChangeType.ADDED:
|
|
8
|
+
return {
|
|
9
|
+
type: event.changeType,
|
|
10
|
+
uuidString: event.transform.uuidString,
|
|
11
|
+
transform: event.transform,
|
|
12
|
+
};
|
|
13
|
+
case TransformChangeType.REMOVED:
|
|
14
|
+
return {
|
|
15
|
+
type: event.changeType,
|
|
16
|
+
uuidString: event.transform.uuidString,
|
|
17
|
+
};
|
|
18
|
+
case TransformChangeType.UPDATED: {
|
|
19
|
+
const changes = {};
|
|
20
|
+
const paths = toPath(event.updatedFields?.paths ?? []);
|
|
21
|
+
for (const path of paths) {
|
|
22
|
+
changes[path.toString()] = getInUnsafe(event.transform, path);
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
type: event.changeType,
|
|
26
|
+
uuidString: event.transform.uuidString,
|
|
27
|
+
transform: event.transform,
|
|
28
|
+
changes,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
self.onmessage = (e) => {
|
|
34
|
+
const { events } = e.data;
|
|
35
|
+
if (events.length === 0)
|
|
36
|
+
return;
|
|
37
|
+
const eventsByUUID = new Map();
|
|
38
|
+
for (const event of events) {
|
|
39
|
+
const entry = createEntry(event);
|
|
40
|
+
if (!entry)
|
|
41
|
+
continue;
|
|
42
|
+
const uuid = entry.uuidString;
|
|
43
|
+
const existing = eventsByUUID.get(uuid);
|
|
44
|
+
if (!existing) {
|
|
45
|
+
eventsByUUID.set(uuid, entry);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
switch (entry.type) {
|
|
49
|
+
case TransformChangeType.REMOVED:
|
|
50
|
+
eventsByUUID.set(uuid, entry);
|
|
51
|
+
break;
|
|
52
|
+
case TransformChangeType.ADDED:
|
|
53
|
+
if (existing.type !== TransformChangeType.REMOVED)
|
|
54
|
+
eventsByUUID.set(uuid, entry);
|
|
55
|
+
break;
|
|
56
|
+
case TransformChangeType.UPDATED:
|
|
57
|
+
// merge with existing updated event
|
|
58
|
+
if (existing.type === TransformChangeType.UPDATED) {
|
|
59
|
+
const paths = toPath(event.updatedFields?.paths ?? []);
|
|
60
|
+
if (paths.length === 0)
|
|
61
|
+
continue;
|
|
62
|
+
for (const path of paths) {
|
|
63
|
+
if (!existing.changes)
|
|
64
|
+
existing.changes = {};
|
|
65
|
+
existing.changes[path.toString()] = getInUnsafe(entry.transform, path);
|
|
66
|
+
}
|
|
67
|
+
existing.transform = event.transform;
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const processedEvents = [];
|
|
73
|
+
for (const entry of eventsByUUID.values()) {
|
|
74
|
+
switch (entry.type) {
|
|
75
|
+
case TransformChangeType.ADDED:
|
|
76
|
+
if (!entry.transform)
|
|
77
|
+
continue;
|
|
78
|
+
processedEvents.push({
|
|
79
|
+
type: TransformChangeType.ADDED,
|
|
80
|
+
uuidString: entry.uuidString,
|
|
81
|
+
transform: entry.transform,
|
|
82
|
+
});
|
|
83
|
+
break;
|
|
84
|
+
case TransformChangeType.REMOVED:
|
|
85
|
+
processedEvents.push({
|
|
86
|
+
type: TransformChangeType.REMOVED,
|
|
87
|
+
uuidString: entry.uuidString,
|
|
88
|
+
});
|
|
89
|
+
break;
|
|
90
|
+
case TransformChangeType.UPDATED: {
|
|
91
|
+
const changes = Object.entries(entry.changes ?? {});
|
|
92
|
+
if (changes.length === 0)
|
|
93
|
+
continue;
|
|
94
|
+
processedEvents.push({
|
|
95
|
+
type: TransformChangeType.UPDATED,
|
|
96
|
+
uuidString: entry.uuidString,
|
|
97
|
+
changes,
|
|
98
|
+
});
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const message = {
|
|
104
|
+
type: 'process',
|
|
105
|
+
events: processedEvents,
|
|
106
|
+
};
|
|
107
|
+
self.postMessage(message);
|
|
108
|
+
};
|
|
109
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { TransformChangeEvent, TransformChangeType, TransformWithUUID } from '@viamrobotics/sdk';
|
|
2
|
+
export type ChangeMessage = {
|
|
3
|
+
type: 'change';
|
|
4
|
+
events: TransformChangeEvent[];
|
|
5
|
+
};
|
|
6
|
+
export type AddedEvent = {
|
|
7
|
+
type: TransformChangeType.ADDED;
|
|
8
|
+
uuidString: string;
|
|
9
|
+
transform: TransformWithUUID;
|
|
10
|
+
};
|
|
11
|
+
export type RemovedEvent = {
|
|
12
|
+
type: TransformChangeType.REMOVED;
|
|
13
|
+
uuidString: string;
|
|
14
|
+
};
|
|
15
|
+
export type UpdatedEvent = {
|
|
16
|
+
type: TransformChangeType.UPDATED;
|
|
17
|
+
uuidString: string;
|
|
18
|
+
changes: [path: string, value: unknown][];
|
|
19
|
+
};
|
|
20
|
+
export type ProcessMessage = {
|
|
21
|
+
type: 'process';
|
|
22
|
+
events: (AddedEvent | RemovedEvent | UpdatedEvent)[];
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viamrobotics/motion-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Motion visualization with Viam",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"@sveltejs/vite-plugin-svelte": "6.1.4",
|
|
23
23
|
"@tailwindcss/forms": "0.5.10",
|
|
24
24
|
"@tailwindcss/vite": "4.1.13",
|
|
25
|
-
"@tanstack/svelte-query": "5.
|
|
26
|
-
"@tanstack/svelte-query-devtools": "5.
|
|
25
|
+
"@tanstack/svelte-query": "5.87.1",
|
|
26
|
+
"@tanstack/svelte-query-devtools": "5.87.3",
|
|
27
27
|
"@testing-library/jest-dom": "6.8.0",
|
|
28
28
|
"@testing-library/svelte": "5.2.8",
|
|
29
29
|
"@thi.ng/paths": "5.2.21",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"@typescript-eslint/eslint-plugin": "8.42.0",
|
|
38
38
|
"@typescript-eslint/parser": "8.42.0",
|
|
39
39
|
"@viamrobotics/prime-core": "0.1.5",
|
|
40
|
-
"@viamrobotics/sdk": "0.
|
|
41
|
-
"@viamrobotics/svelte-sdk": "0.6.
|
|
40
|
+
"@viamrobotics/sdk": "0.51.0",
|
|
41
|
+
"@viamrobotics/svelte-sdk": "0.6.1",
|
|
42
42
|
"@vitejs/plugin-basic-ssl": "2.1.0",
|
|
43
43
|
"@zag-js/svelte": "1.22.1",
|
|
44
44
|
"@zag-js/tree-view": "1.22.1",
|