@viamrobotics/motion-tools 0.1.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 (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -0
  3. package/dist/WorldObject.d.ts +33 -0
  4. package/dist/WorldObject.js +18 -0
  5. package/dist/assert.d.ts +14 -0
  6. package/dist/assert.js +21 -0
  7. package/dist/color.d.ts +8 -0
  8. package/dist/color.js +14 -0
  9. package/dist/components/App.svelte +63 -0
  10. package/dist/components/App.svelte.d.ts +8 -0
  11. package/dist/components/AxesHelper.svelte +17 -0
  12. package/dist/components/AxesHelper.svelte.d.ts +4 -0
  13. package/dist/components/BentPlaneGeometry.svelte +52 -0
  14. package/dist/components/BentPlaneGeometry.svelte.d.ts +29 -0
  15. package/dist/components/Camera.svelte +45 -0
  16. package/dist/components/Camera.svelte.d.ts +5 -0
  17. package/dist/components/CameraControls.svelte +82 -0
  18. package/dist/components/CameraControls.svelte.d.ts +9 -0
  19. package/dist/components/Details.svelte +161 -0
  20. package/dist/components/Details.svelte.d.ts +3 -0
  21. package/dist/components/Detections.svelte +41 -0
  22. package/dist/components/Detections.svelte.d.ts +3 -0
  23. package/dist/components/DetectionsPlane.svelte +23 -0
  24. package/dist/components/DetectionsPlane.svelte.d.ts +21 -0
  25. package/dist/components/DomPortal.svelte +20 -0
  26. package/dist/components/DomPortal.svelte.d.ts +5 -0
  27. package/dist/components/Focus.svelte +49 -0
  28. package/dist/components/Focus.svelte.d.ts +3 -0
  29. package/dist/components/Frame.svelte +112 -0
  30. package/dist/components/Frame.svelte.d.ts +16 -0
  31. package/dist/components/Frames.svelte +54 -0
  32. package/dist/components/Frames.svelte.d.ts +18 -0
  33. package/dist/components/Pointcloud.svelte +55 -0
  34. package/dist/components/Pointcloud.svelte.d.ts +10 -0
  35. package/dist/components/Pointclouds.svelte +21 -0
  36. package/dist/components/Pointclouds.svelte.d.ts +18 -0
  37. package/dist/components/Pose.svelte +19 -0
  38. package/dist/components/Pose.svelte.d.ts +12 -0
  39. package/dist/components/RefreshRate.svelte +47 -0
  40. package/dist/components/RefreshRate.svelte.d.ts +8 -0
  41. package/dist/components/Scene.svelte +81 -0
  42. package/dist/components/Scene.svelte.d.ts +7 -0
  43. package/dist/components/SceneProviders.svelte +41 -0
  44. package/dist/components/SceneProviders.svelte.d.ts +9 -0
  45. package/dist/components/Selected.svelte +44 -0
  46. package/dist/components/Selected.svelte.d.ts +3 -0
  47. package/dist/components/Shapes.svelte +49 -0
  48. package/dist/components/Shapes.svelte.d.ts +18 -0
  49. package/dist/components/StaticGeometries.svelte +79 -0
  50. package/dist/components/StaticGeometries.svelte.d.ts +18 -0
  51. package/dist/components/Tree/Settings.svelte +54 -0
  52. package/dist/components/Tree/Settings.svelte.d.ts +18 -0
  53. package/dist/components/Tree/Tree.svelte +204 -0
  54. package/dist/components/Tree/Tree.svelte.d.ts +10 -0
  55. package/dist/components/Tree/TreeContainer.svelte +70 -0
  56. package/dist/components/Tree/TreeContainer.svelte.d.ts +3 -0
  57. package/dist/components/Tree/buildTree.d.ts +11 -0
  58. package/dist/components/Tree/buildTree.js +29 -0
  59. package/dist/components/Tree/useExpanded.svelte.d.ts +5 -0
  60. package/dist/components/Tree/useExpanded.svelte.js +21 -0
  61. package/dist/components/WorldObject.svelte +27 -0
  62. package/dist/components/WorldObject.svelte.d.ts +11 -0
  63. package/dist/components/XR.svelte +20 -0
  64. package/dist/components/XR.svelte.d.ts +3 -0
  65. package/dist/components/models/README.md +5 -0
  66. package/dist/components/null-states/Connection.svelte +0 -0
  67. package/dist/components/null-states/Connection.svelte.d.ts +26 -0
  68. package/dist/components/portal/Portal.svelte +25 -0
  69. package/dist/components/portal/Portal.svelte.d.ts +8 -0
  70. package/dist/components/portal/PortalTarget.svelte +18 -0
  71. package/dist/components/portal/PortalTarget.svelte.d.ts +6 -0
  72. package/dist/components/portal/index.d.ts +2 -0
  73. package/dist/components/portal/index.js +2 -0
  74. package/dist/components/portal/usePortalContext.svelte.d.ts +5 -0
  75. package/dist/components/portal/usePortalContext.svelte.js +8 -0
  76. package/dist/components/xr/CameraFeed.svelte +81 -0
  77. package/dist/components/xr/CameraFeed.svelte.d.ts +6 -0
  78. package/dist/components/xr/Controllers.svelte +71 -0
  79. package/dist/components/xr/Controllers.svelte.d.ts +3 -0
  80. package/dist/components/xr/Draggable.svelte +101 -0
  81. package/dist/components/xr/Draggable.svelte.d.ts +11 -0
  82. package/dist/components/xr/HandCollider.svelte +19 -0
  83. package/dist/components/xr/HandCollider.svelte.d.ts +18 -0
  84. package/dist/components/xr/Hands.svelte +24 -0
  85. package/dist/components/xr/Hands.svelte.d.ts +18 -0
  86. package/dist/components/xr/OriginMarker.svelte +100 -0
  87. package/dist/components/xr/OriginMarker.svelte.d.ts +3 -0
  88. package/dist/components/xr/PointDistance.svelte +52 -0
  89. package/dist/components/xr/PointDistance.svelte.d.ts +3 -0
  90. package/dist/hooks/index.d.ts +1 -0
  91. package/dist/hooks/index.js +1 -0
  92. package/dist/hooks/useConnectionConfigs.svelte.d.ts +17 -0
  93. package/dist/hooks/useConnectionConfigs.svelte.js +40 -0
  94. package/dist/hooks/useControls.svelte.d.ts +7 -0
  95. package/dist/hooks/useControls.svelte.js +16 -0
  96. package/dist/hooks/useDraggable.svelte.d.ts +2 -0
  97. package/dist/hooks/useDraggable.svelte.js +25 -0
  98. package/dist/hooks/useFrames.svelte.d.ts +9 -0
  99. package/dist/hooks/useFrames.svelte.js +50 -0
  100. package/dist/hooks/useGeometries.svelte.d.ts +7 -0
  101. package/dist/hooks/useGeometries.svelte.js +58 -0
  102. package/dist/hooks/useMotionClient.svelte.d.ts +8 -0
  103. package/dist/hooks/useMotionClient.svelte.js +31 -0
  104. package/dist/hooks/useObjectEvents.svelte.d.ts +9 -0
  105. package/dist/hooks/useObjectEvents.svelte.js +31 -0
  106. package/dist/hooks/useObjects.svelte.d.ts +7 -0
  107. package/dist/hooks/useObjects.svelte.js +33 -0
  108. package/dist/hooks/usePartID.svelte.d.ts +6 -0
  109. package/dist/hooks/usePartID.svelte.js +14 -0
  110. package/dist/hooks/usePersistentUUIDs.svelte.d.ts +5 -0
  111. package/dist/hooks/usePersistentUUIDs.svelte.js +14 -0
  112. package/dist/hooks/usePointclouds.svelte.d.ts +7 -0
  113. package/dist/hooks/usePointclouds.svelte.js +58 -0
  114. package/dist/hooks/usePose.svelte.d.ts +3 -0
  115. package/dist/hooks/usePose.svelte.js +44 -0
  116. package/dist/hooks/usePoses.svelte.d.ts +12 -0
  117. package/dist/hooks/usePoses.svelte.js +63 -0
  118. package/dist/hooks/useRefreshRates.svelte.d.ts +5 -0
  119. package/dist/hooks/useRefreshRates.svelte.js +23 -0
  120. package/dist/hooks/useSelection.svelte.d.ts +40 -0
  121. package/dist/hooks/useSelection.svelte.js +92 -0
  122. package/dist/hooks/useShapes.svelte.d.ts +17 -0
  123. package/dist/hooks/useShapes.svelte.js +264 -0
  124. package/dist/hooks/useStaticGeometries.svelte.d.ts +9 -0
  125. package/dist/hooks/useStaticGeometries.svelte.js +37 -0
  126. package/dist/hooks/useVisibility.svelte.d.ts +5 -0
  127. package/dist/hooks/useVisibility.svelte.js +22 -0
  128. package/dist/hooks/xr/useAnchors.svelte.d.ts +0 -0
  129. package/dist/hooks/xr/useAnchors.svelte.js +1 -0
  130. package/dist/index.d.ts +1 -0
  131. package/dist/index.js +1 -0
  132. package/dist/keybindings.d.ts +12 -0
  133. package/dist/keybindings.js +12 -0
  134. package/dist/loaders/pcd/index.d.ts +4 -0
  135. package/dist/loaders/pcd/index.js +13 -0
  136. package/dist/loaders/pcd/worker.d.ts +1 -0
  137. package/dist/loaders/pcd/worker.js +26 -0
  138. package/dist/three/AxesHelper.d.ts +5 -0
  139. package/dist/three/AxesHelper.js +35 -0
  140. package/dist/three/BatchedArrow.d.ts +30 -0
  141. package/dist/three/BatchedArrow.js +126 -0
  142. package/dist/three/BoxHelper.d.ts +50 -0
  143. package/dist/three/BoxHelper.js +134 -0
  144. package/dist/three/CapsuleGeometry.d.ts +10 -0
  145. package/dist/three/CapsuleGeometry.js +17 -0
  146. package/dist/transform.d.ts +11 -0
  147. package/dist/transform.js +65 -0
  148. package/package.json +110 -0
@@ -0,0 +1,63 @@
1
+ import { createQueries, queryOptions } from '@tanstack/svelte-query';
2
+ import { MotionClient, PoseInFrame, ResourceName } from '@viamrobotics/sdk';
3
+ import { createResourceClient, useResourceNames } from '@viamrobotics/svelte-sdk';
4
+ import { getContext, setContext } from 'svelte';
5
+ import { fromStore, toStore } from 'svelte/store';
6
+ import { useRefreshRates } from './useRefreshRates.svelte';
7
+ const key = Symbol('poses-context');
8
+ export const providePoses = (partID) => {
9
+ const refreshRates = useRefreshRates();
10
+ const resources = useResourceNames(partID);
11
+ const components = $derived(resources.current.filter(({ type }) => type === 'component'));
12
+ const motionResources = useResourceNames(partID, 'motion');
13
+ const clients = $derived(motionResources.current.map((resource) => createResourceClient(MotionClient, partID, () => resource.name)));
14
+ if (!refreshRates.has('Poses')) {
15
+ refreshRates.set('Poses', 1000);
16
+ }
17
+ const options = $derived(clients.map((motionClient) => {
18
+ const interval = refreshRates.get('Poses');
19
+ return queryOptions({
20
+ enabled: interval !== -1 && motionClient.current !== undefined,
21
+ refetchInterval: interval === 0 ? false : interval,
22
+ queryKey: ['partID', partID(), motionClient.current?.name, 'getPose'],
23
+ queryFn: async () => {
24
+ const client = motionClient.current;
25
+ if (!client) {
26
+ throw new Error('No client');
27
+ }
28
+ const promises = components.map((component) => {
29
+ return client.getPose(component, 'world', []);
30
+ });
31
+ const results = await Promise.allSettled(promises);
32
+ return results
33
+ .map((result, index) => ({ ...result, component: components[index] }))
34
+ .filter((result) => result.status === 'fulfilled')
35
+ .map((result) => ({ ...result.value, component: result.component }));
36
+ },
37
+ });
38
+ }));
39
+ const queries = fromStore(createQueries({
40
+ queries: toStore(() => options),
41
+ combine: (results) => {
42
+ const obj = {};
43
+ for (const service of results) {
44
+ let i = 0;
45
+ if (service.data) {
46
+ for (const pose of service.data) {
47
+ obj[components[i].name] = pose;
48
+ i += 1;
49
+ }
50
+ }
51
+ }
52
+ return obj;
53
+ },
54
+ }));
55
+ setContext(key, {
56
+ get current() {
57
+ return queries.current;
58
+ },
59
+ });
60
+ };
61
+ export const usePoses = () => {
62
+ return getContext(key);
63
+ };
@@ -0,0 +1,5 @@
1
+ import { SvelteMap } from 'svelte/reactivity';
2
+ type Context = SvelteMap<string, number>;
3
+ export declare const provideRefreshRates: () => SvelteMap<string, number>;
4
+ export declare const useRefreshRates: () => Context;
5
+ export {};
@@ -0,0 +1,23 @@
1
+ import { get, set } from 'idb-keyval';
2
+ import { getContext, setContext } from 'svelte';
3
+ import { SvelteMap } from 'svelte/reactivity';
4
+ const key = Symbol('polling-rate-context');
5
+ const idbKey = 'polling-rate';
6
+ export const provideRefreshRates = () => {
7
+ const map = new SvelteMap();
8
+ get(idbKey).then((entries) => {
9
+ if (entries) {
10
+ for (const [key, value] of entries) {
11
+ map.set(key, value);
12
+ }
13
+ }
14
+ });
15
+ $effect(() => {
16
+ set(idbKey, [...map.entries()]);
17
+ });
18
+ setContext(key, map);
19
+ return map;
20
+ };
21
+ export const useRefreshRates = () => {
22
+ return getContext(key);
23
+ };
@@ -0,0 +1,40 @@
1
+ import type { Object3D } from 'three';
2
+ import type { WorldObject } from '../WorldObject';
3
+ type UUID = string;
4
+ interface SelectionContext {
5
+ readonly current: UUID | undefined;
6
+ set(value?: UUID): void;
7
+ }
8
+ interface FocusContext {
9
+ readonly current: UUID | undefined;
10
+ set(value?: UUID): void;
11
+ }
12
+ export declare const provideSelection: () => {
13
+ selection: {
14
+ readonly current: string | undefined;
15
+ set(value?: UUID): void;
16
+ };
17
+ focus: {
18
+ readonly current: string | undefined;
19
+ set(value?: UUID): void;
20
+ };
21
+ hover: {
22
+ readonly current: string | undefined;
23
+ set(value?: UUID): void;
24
+ };
25
+ };
26
+ export declare const useSelected: () => SelectionContext;
27
+ export declare const useFocused: () => FocusContext;
28
+ export declare const useFocusedObject: () => {
29
+ current: WorldObject | undefined;
30
+ };
31
+ export declare const useSelectedObject: () => {
32
+ current: WorldObject | undefined;
33
+ };
34
+ export declare const useFocusedObject3d: () => {
35
+ current: Object3D | undefined;
36
+ };
37
+ export declare const useSelectedObject3d: () => {
38
+ current: Object3D | undefined;
39
+ };
40
+ export {};
@@ -0,0 +1,92 @@
1
+ import { useThrelte } from '@threlte/core';
2
+ import { getContext, setContext } from 'svelte';
3
+ import { useObjects } from './useObjects.svelte';
4
+ const hoverKey = Symbol('hover-context');
5
+ const selectionKey = Symbol('selection-context');
6
+ const focusKey = Symbol('focus-context');
7
+ const selectedObjectKey = Symbol('selected-frame-context');
8
+ const focusedObjectKey = Symbol('focused-frame-context');
9
+ export const provideSelection = () => {
10
+ let selected = $state();
11
+ let focused = $state();
12
+ let hovered = $state();
13
+ const selectionContext = {
14
+ get current() {
15
+ return selected;
16
+ },
17
+ set(value) {
18
+ selected = value;
19
+ },
20
+ };
21
+ setContext(selectionKey, selectionContext);
22
+ const focusContext = {
23
+ get current() {
24
+ return focused;
25
+ },
26
+ set(value) {
27
+ focused = value;
28
+ },
29
+ };
30
+ setContext(focusKey, focusContext);
31
+ const hoverContext = {
32
+ get current() {
33
+ return hovered;
34
+ },
35
+ set(value) {
36
+ hovered = value;
37
+ },
38
+ };
39
+ setContext(hoverKey, hoverContext);
40
+ const objects = useObjects();
41
+ const selectedObject = $derived(objects.current.find((object) => object.uuid === selected));
42
+ const selectedObjectContext = {
43
+ get current() {
44
+ return selectedObject;
45
+ },
46
+ };
47
+ setContext(selectedObjectKey, selectedObjectContext);
48
+ const focusedFrame = $derived(objects.current.find((object) => object.uuid === focused));
49
+ const focusedFrameContext = {
50
+ get current() {
51
+ return focusedFrame;
52
+ },
53
+ };
54
+ setContext(focusedObjectKey, focusedFrameContext);
55
+ return {
56
+ selection: selectionContext,
57
+ focus: focusContext,
58
+ hover: hoverContext,
59
+ };
60
+ };
61
+ export const useSelected = () => {
62
+ return getContext(selectionKey);
63
+ };
64
+ export const useFocused = () => {
65
+ return getContext(focusKey);
66
+ };
67
+ export const useFocusedObject = () => {
68
+ return getContext(focusedObjectKey);
69
+ };
70
+ export const useSelectedObject = () => {
71
+ return getContext(selectedObjectKey);
72
+ };
73
+ export const useFocusedObject3d = () => {
74
+ const focusedObject = useFocusedObject();
75
+ const { scene } = useThrelte();
76
+ const object = $derived(focusedObject.current ? scene.getObjectByName(focusedObject.current.name)?.clone() : undefined);
77
+ return {
78
+ get current() {
79
+ return object;
80
+ },
81
+ };
82
+ };
83
+ export const useSelectedObject3d = () => {
84
+ const selectedObject = useSelectedObject();
85
+ const { scene } = useThrelte();
86
+ const object = $derived(selectedObject.current ? scene.getObjectByName(selectedObject.current.name) : undefined);
87
+ return {
88
+ get current() {
89
+ return object;
90
+ },
91
+ };
92
+ };
@@ -0,0 +1,17 @@
1
+ import { BatchedArrow } from '../three/BatchedArrow';
2
+ import { WorldObject, type PointsGeometry } from '../WorldObject';
3
+ type ConnectionStatus = 'connecting' | 'open' | 'closed';
4
+ interface Context {
5
+ points: WorldObject<PointsGeometry>[];
6
+ meshes: WorldObject[];
7
+ poses: WorldObject[];
8
+ nurbs: WorldObject[];
9
+ models: WorldObject[];
10
+ connectionStatus: ConnectionStatus;
11
+ object3ds: {
12
+ batchedArrow: BatchedArrow;
13
+ };
14
+ }
15
+ export declare const provideShapes: () => void;
16
+ export declare const useShapes: () => Context;
17
+ export {};
@@ -0,0 +1,264 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ import { Vector3, Vector4 } from 'three';
3
+ import { NURBSCurve } from 'three/addons/curves/NURBSCurve.js';
4
+ import { parsePCD } from '../loaders/pcd';
5
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
6
+ import { BatchedArrow } from '../three/BatchedArrow';
7
+ import { WorldObject } from '../WorldObject';
8
+ const key = Symbol('websocket-context-key');
9
+ const tryParse = (json) => {
10
+ try {
11
+ return JSON.parse(json);
12
+ }
13
+ catch (error) {
14
+ console.warn('Failed to parse JSON:', error);
15
+ return;
16
+ }
17
+ };
18
+ export const provideShapes = () => {
19
+ let pointsIndex = 0;
20
+ let geometryIndex = 0;
21
+ let poseIndex = 0;
22
+ let reconnectDelay = 200;
23
+ const maxReconnectDelay = 5_000;
24
+ const { BACKEND_IP, BUN_SERVER_PORT } = globalThis;
25
+ let ws;
26
+ const points = $state([]);
27
+ const meshes = $state([]);
28
+ const poses = $state([]);
29
+ const nurbs = $state([]);
30
+ const models = $state([]);
31
+ let connectionStatus = $state('connecting');
32
+ const loader = new GLTFLoader();
33
+ const addPcd = async (data) => {
34
+ const buffer = await data.arrayBuffer();
35
+ const { positions, colors } = await parsePCD(new Uint8Array(buffer));
36
+ points.push(new WorldObject(`points ${++pointsIndex}`, undefined, undefined, {
37
+ case: 'points',
38
+ value: new Float32Array(positions),
39
+ }, colors ? { colors: new Float32Array(colors) } : undefined));
40
+ };
41
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
+ const addGeometry = (data, color, parent) => {
43
+ let geometry;
44
+ if ('mesh' in data) {
45
+ geometry = {
46
+ case: 'mesh',
47
+ value: { contentType: '', mesh: data.mesh.mesh },
48
+ };
49
+ }
50
+ else if ('box' in data) {
51
+ geometry = { case: 'box', value: data.box };
52
+ }
53
+ else if ('sphere' in data) {
54
+ geometry = { case: 'sphere', value: data.sphere };
55
+ }
56
+ else if ('capsule' in data) {
57
+ geometry = { case: 'capsule', value: data.capsule };
58
+ }
59
+ else {
60
+ geometry = { case: undefined, value: undefined };
61
+ }
62
+ const object = new WorldObject(data.label ?? ++geometryIndex, data.center, parent, geometry, {
63
+ color,
64
+ });
65
+ meshes.push(object);
66
+ };
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ const addNurbs = (data, color) => {
69
+ const controlPoints = data.ControlPts.map((point) => new Vector4(point.x / 1000, point.y / 1000, point.z / 1000));
70
+ const curve = new NURBSCurve(data.Degree, data.Knots, controlPoints);
71
+ const object = new WorldObject(data.name, data.pose, data.parent, { case: 'line', value: new Float32Array() }, { color, points: curve.getPoints(200) });
72
+ nurbs.push(object);
73
+ };
74
+ const direction = new Vector3();
75
+ const origin = new Vector3();
76
+ const vec3 = new Vector3();
77
+ const batchedArrow = new BatchedArrow();
78
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
79
+ const addPoses = (nextPoses, colors, arrowHeadAtPose) => {
80
+ for (let i = 0, l = nextPoses.length; i < l; i += 1) {
81
+ const pose = nextPoses[i];
82
+ const length = 0.1;
83
+ direction.set(pose.oX ?? 0, pose.oY ?? 0, pose.oZ ?? 0);
84
+ origin.set((pose.x ?? 0) / 1000, (pose.y ?? 0) / 1000, (pose.z ?? 0) / 1000);
85
+ if (arrowHeadAtPose) {
86
+ // Compute the base position so the arrow ends at the origin
87
+ origin.sub(vec3.copy(direction).multiplyScalar(length));
88
+ }
89
+ const arrowId = batchedArrow.addArrow(direction, origin, length, colors[i]);
90
+ poses.push(new WorldObject(`pose ${++poseIndex}`, pose, undefined, undefined, {
91
+ getBoundingBoxAt(box3) {
92
+ return batchedArrow.getBoundingBoxAt(arrowId, box3);
93
+ },
94
+ batched: {
95
+ id: arrowId,
96
+ name: batchedArrow.object3d.name,
97
+ },
98
+ }));
99
+ }
100
+ };
101
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
102
+ const addGeometries = (geometries, colors, parent) => {
103
+ let i = 0;
104
+ for (const geometry of geometries) {
105
+ addGeometry(geometry, colors[i], parent);
106
+ i += 1;
107
+ }
108
+ };
109
+ const remove = (names) => {
110
+ let index = -1;
111
+ for (const name of names) {
112
+ index = points.findIndex((p) => p.name === name);
113
+ if (index !== -1) {
114
+ points.splice(index, 1);
115
+ continue;
116
+ }
117
+ index = meshes.findIndex((m) => m.name === name);
118
+ if (index !== -1) {
119
+ meshes.splice(index, 1);
120
+ continue;
121
+ }
122
+ index = poses.findIndex((p) => p.name === name);
123
+ if (index !== -1) {
124
+ const id = poses[index].metadata.batched?.id;
125
+ if (id) {
126
+ batchedArrow.removeArrow(id);
127
+ poses.splice(index, 1);
128
+ continue;
129
+ }
130
+ }
131
+ index = nurbs.findIndex((n) => n.name === name);
132
+ if (index !== -1) {
133
+ nurbs.splice(index, 1);
134
+ continue;
135
+ }
136
+ index = models.findIndex((m) => m.name === name);
137
+ if (index !== -1) {
138
+ models.splice(index, 1);
139
+ continue;
140
+ }
141
+ }
142
+ };
143
+ const removeAll = () => {
144
+ points.splice(0, points.length);
145
+ meshes.splice(0, meshes.length);
146
+ nurbs.splice(0, nurbs.length);
147
+ models.splice(0, models.length);
148
+ poses.splice(0, poses.length);
149
+ batchedArrow.clear();
150
+ pointsIndex = 0;
151
+ geometryIndex = 0;
152
+ poseIndex = 0;
153
+ };
154
+ let metadata = undefined;
155
+ const handleMetadata = (data) => {
156
+ const json = tryParse(data);
157
+ if ('ext' in json) {
158
+ metadata = json;
159
+ return true;
160
+ }
161
+ return false;
162
+ };
163
+ const loadGLTF = async (data) => {
164
+ const buffer = await data.arrayBuffer();
165
+ const blob = new Blob([buffer], { type: 'model/gltf-binary' });
166
+ const url = URL.createObjectURL(blob);
167
+ const gltf = await loader.loadAsync(url);
168
+ models.push(new WorldObject(gltf.scene.name, undefined, undefined, undefined, { gltf }));
169
+ URL.revokeObjectURL(url);
170
+ };
171
+ const scheduleReconnect = () => {
172
+ setTimeout(() => {
173
+ reconnectDelay = Math.min(reconnectDelay * 2, maxReconnectDelay);
174
+ console.log(`Reconnecting in ${reconnectDelay / 1000} seconds...`);
175
+ connect();
176
+ }, reconnectDelay);
177
+ };
178
+ const onOpen = () => {
179
+ connectionStatus = 'open';
180
+ reconnectDelay = 1000;
181
+ console.log(`Connected to websocket server at ${BACKEND_IP}:${BUN_SERVER_PORT}`);
182
+ };
183
+ const onClose = () => {
184
+ connectionStatus = 'closed';
185
+ console.log('Disconnected from websocket server');
186
+ scheduleReconnect();
187
+ };
188
+ const onError = (event) => {
189
+ console.log('Websocket error', JSON.stringify(event));
190
+ ws.close();
191
+ };
192
+ const onMessage = (event) => {
193
+ if (typeof event.data === 'string') {
194
+ if (handleMetadata(event.data)) {
195
+ return;
196
+ }
197
+ }
198
+ if (typeof event.data === 'object' && 'arrayBuffer' in event.data) {
199
+ if (metadata?.ext === 'glb') {
200
+ loadGLTF(event.data);
201
+ }
202
+ else if (metadata?.ext === 'pcd') {
203
+ addPcd(event.data);
204
+ }
205
+ return;
206
+ }
207
+ const data = tryParse(event.data);
208
+ if (!data)
209
+ return;
210
+ if ('geometries' in data) {
211
+ return addGeometries(data.geometries, data.colors, data.parent);
212
+ }
213
+ if ('remove' in data) {
214
+ return remove(data.names);
215
+ }
216
+ if ('removeAll' in data) {
217
+ return removeAll();
218
+ }
219
+ if ('Knots' in data) {
220
+ return addNurbs(data, data.Color);
221
+ }
222
+ if ('poses' in data) {
223
+ return addPoses(data.poses, data.colors, data.arrowHeadAtPose);
224
+ }
225
+ if ('geometry' in data) {
226
+ addGeometry(data.geometry, data.color);
227
+ }
228
+ };
229
+ const connect = () => {
230
+ const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
231
+ ws = new WebSocket(`${protocol}://${BACKEND_IP}:${BUN_SERVER_PORT}/ws`);
232
+ ws.onclose = onClose;
233
+ ws.onerror = onError;
234
+ ws.onopen = onOpen;
235
+ ws.onmessage = onMessage;
236
+ };
237
+ connect();
238
+ setContext(key, {
239
+ get points() {
240
+ return points;
241
+ },
242
+ get meshes() {
243
+ return meshes;
244
+ },
245
+ get poses() {
246
+ return poses;
247
+ },
248
+ get nurbs() {
249
+ return nurbs;
250
+ },
251
+ get models() {
252
+ return models;
253
+ },
254
+ get connectionStatus() {
255
+ return connectionStatus;
256
+ },
257
+ object3ds: {
258
+ batchedArrow,
259
+ },
260
+ });
261
+ };
262
+ export const useShapes = () => {
263
+ return getContext(key);
264
+ };
@@ -0,0 +1,9 @@
1
+ import { WorldObject } from '../WorldObject';
2
+ interface Context {
3
+ current: WorldObject[];
4
+ add: () => void;
5
+ remove: (name: string) => void;
6
+ }
7
+ export declare const provideStaticGeometries: () => void;
8
+ export declare const useStaticGeometries: () => Context;
9
+ export {};
@@ -0,0 +1,37 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ import { get, set } from 'idb-keyval';
3
+ import { Debounced } from 'runed';
4
+ import { createGeometry } from '../transform';
5
+ import { WorldObject } from '../WorldObject';
6
+ const key = Symbol('static-geometries-context');
7
+ export const provideStaticGeometries = () => {
8
+ let geometries = $state([]);
9
+ const debounced = new Debounced(() => geometries, 500);
10
+ get('static-geometries').then((response) => {
11
+ if (Array.isArray(response)) {
12
+ geometries = response;
13
+ }
14
+ });
15
+ $effect(() => {
16
+ set('static-geometries', $state.snapshot(debounced.current));
17
+ });
18
+ setContext(key, {
19
+ get current() {
20
+ return geometries;
21
+ },
22
+ add() {
23
+ const object = new WorldObject(`custom geometry ${geometries.length + 1}`, undefined, undefined, createGeometry({
24
+ case: 'box',
25
+ value: { dimsMm: { x: 100, y: 100, z: 100 } },
26
+ }).geometryType);
27
+ geometries.push(structuredClone(object));
28
+ },
29
+ remove(name) {
30
+ const index = geometries.findIndex((geo) => geo.name === name);
31
+ geometries.splice(index, 1);
32
+ },
33
+ });
34
+ };
35
+ export const useStaticGeometries = () => {
36
+ return getContext(key);
37
+ };
@@ -0,0 +1,5 @@
1
+ import { SvelteMap } from 'svelte/reactivity';
2
+ type Context = SvelteMap<string, boolean>;
3
+ export declare const provideVisibility: () => void;
4
+ export declare const useVisibility: () => Context;
5
+ export {};
@@ -0,0 +1,22 @@
1
+ import { get, set } from 'idb-keyval';
2
+ import { getContext, setContext } from 'svelte';
3
+ import { SvelteMap } from 'svelte/reactivity';
4
+ const key = Symbol('object-visibility-context');
5
+ const idbKey = 'object-visibility';
6
+ export const provideVisibility = () => {
7
+ const map = new SvelteMap();
8
+ get(idbKey).then((entries) => {
9
+ if (entries) {
10
+ for (const [key, value] of entries) {
11
+ map.set(key, value);
12
+ }
13
+ }
14
+ });
15
+ $effect(() => {
16
+ set(idbKey, [...map.entries()]);
17
+ });
18
+ setContext(key, map);
19
+ };
20
+ export const useVisibility = () => {
21
+ return getContext(key);
22
+ };
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1 @@
1
+ export { default as MotionTools } from './components/App.svelte';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { default as MotionTools } from './components/App.svelte';
@@ -0,0 +1,12 @@
1
+ export declare const Keybindings: {
2
+ MACHINES: string;
3
+ TREEVIEW: string;
4
+ ESCAPE: string;
5
+ UP: string;
6
+ DOWN: string;
7
+ ADD_GEOMETRY: string;
8
+ REMOVE_GEOMETRY: string;
9
+ TRANSLATE: string;
10
+ ROTATE: string;
11
+ SCALE: string;
12
+ };
@@ -0,0 +1,12 @@
1
+ export const Keybindings = {
2
+ MACHINES: 'm',
3
+ TREEVIEW: '`',
4
+ ESCAPE: 'Escape',
5
+ UP: 'ArrowUp',
6
+ DOWN: 'ArrowDown',
7
+ ADD_GEOMETRY: '=',
8
+ REMOVE_GEOMETRY: '-',
9
+ TRANSLATE: 't',
10
+ ROTATE: 'r',
11
+ SCALE: 's',
12
+ };
@@ -0,0 +1,4 @@
1
+ export declare const parsePCD: (array: Uint8Array<ArrayBufferLike>) => Promise<{
2
+ positions: ArrayBuffer;
3
+ colors: ArrayBuffer | undefined;
4
+ }>;
@@ -0,0 +1,13 @@
1
+ // main.js
2
+ const worker = new Worker(new URL('./worker', import.meta.url), { type: 'module' });
3
+ export const parsePCD = async (array) => {
4
+ return new Promise((resolve, reject) => {
5
+ worker.onmessage = (event) => {
6
+ if (event.data.error) {
7
+ return reject(event.data.error);
8
+ }
9
+ resolve({ positions: event.data.positionArray, colors: event.data.colorArray });
10
+ };
11
+ worker.postMessage({ data: array }, [array.buffer]);
12
+ });
13
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ // worker.js
2
+ import { PCDLoader } from 'three/examples/jsm/loaders/PCDLoader.js';
3
+ const loader = new PCDLoader();
4
+ self.onmessage = async (event) => {
5
+ const { data } = event.data;
6
+ if (!(data instanceof Uint8Array)) {
7
+ postMessage({ error: 'Invalid data format' });
8
+ return;
9
+ }
10
+ try {
11
+ const pcd = loader.parse(data.buffer);
12
+ if (pcd.geometry) {
13
+ const positionArray = pcd.geometry.attributes.position.array;
14
+ const colorArray = pcd.geometry.attributes.color ? pcd.geometry.attributes.color.array : null;
15
+ postMessage({ success: true, positionArray, colorArray }, colorArray
16
+ ? [positionArray.buffer, colorArray.buffer]
17
+ : [positionArray.buffer]);
18
+ }
19
+ else {
20
+ postMessage({ error: 'Failed to extract geometry' });
21
+ }
22
+ }
23
+ catch (error) {
24
+ postMessage({ error: error.message });
25
+ }
26
+ };