@viamrobotics/motion-tools 0.19.0 → 0.19.2

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.
@@ -14,9 +14,7 @@ and should remain pure, i.e. no hooks should be used.
14
14
  import { colors, darkenColor } from '../color'
15
15
  import AxesHelper from './AxesHelper.svelte'
16
16
  import type { WorldObject } from '../WorldObject.svelte'
17
- import { PLYLoader } from 'three/addons/loaders/PLYLoader.js'
18
-
19
- const plyLoader = new PLYLoader()
17
+ import { parsePlyInput } from '../ply'
20
18
 
21
19
  interface Props extends ThrelteProps<Group> {
22
20
  uuid: string
@@ -75,26 +73,6 @@ and should remain pure, i.e. no hooks should be used.
75
73
  const oncreate = (ref: BufferGeometry) => {
76
74
  geo = ref
77
75
  }
78
-
79
- const parsePlyInput = (mesh: string | Uint8Array): BufferGeometry => {
80
- // Case 1: already a base64 or ASCII string
81
- if (typeof mesh === 'string') {
82
- return plyLoader.parse(atob(mesh))
83
- }
84
-
85
- // Case 2: detect text vs binary PLY in Uint8Array
86
- const header = new TextDecoder().decode(mesh.slice(0, 50))
87
- const isAscii = header.includes('format ascii')
88
-
89
- // Case 3: text-mode PLY → decode bytes to string
90
- if (isAscii) {
91
- const text = new TextDecoder().decode(mesh)
92
- return plyLoader.parse(text)
93
- }
94
-
95
- // Case 4: binary PLY → pass ArrayBuffer directly
96
- return plyLoader.parse(mesh.buffer as ArrayBuffer)
97
- }
98
76
  </script>
99
77
 
100
78
  <T
@@ -4,7 +4,7 @@
4
4
  import { useSettings } from '../hooks/useSettings.svelte'
5
5
 
6
6
  interface Props {
7
- text: string
7
+ text?: string
8
8
  }
9
9
 
10
10
  let { text }: Props = $props()
@@ -14,7 +14,7 @@
14
14
  const labels = $derived(settings.current.enableLabels)
15
15
  </script>
16
16
 
17
- {#if labels}
17
+ {#if labels && text}
18
18
  <HTML
19
19
  center
20
20
  zIndexRange={[100, 0]}
@@ -1,5 +1,5 @@
1
1
  interface Props {
2
- text: string;
2
+ text?: string;
3
3
  }
4
4
  declare const Label: import("svelte").Component<Props, {}, "">;
5
5
  type Label = ReturnType<typeof Label>;
@@ -4,7 +4,7 @@
4
4
  import type { Snippet } from 'svelte'
5
5
 
6
6
  interface Props {
7
- name: string
7
+ name?: string
8
8
  parent?: string
9
9
  children: Snippet<[{ pose: Pose | undefined }]>
10
10
  }
@@ -1,7 +1,7 @@
1
1
  import type { Pose } from '@viamrobotics/sdk';
2
2
  import type { Snippet } from 'svelte';
3
3
  interface Props {
4
- name: string;
4
+ name?: string;
5
5
  parent?: string;
6
6
  children: Snippet<[{
7
7
  pose: Pose | undefined;
@@ -37,7 +37,7 @@
37
37
  return item ? [item] : []
38
38
  },
39
39
  })
40
- $effect.pre(() => {
40
+ $effect(() => {
41
41
  enabled.set(!settings.current.enableMeasure)
42
42
  })
43
43
  raycaster.firstHitOnly = true
@@ -89,7 +89,10 @@
89
89
  {/if}
90
90
 
91
91
  <T.Group attach={focusedObject ? false : undefined}>
92
+ <!-- Capture "default" portals if "world" is not explicit -->
93
+ <PortalTarget />
92
94
  <PortalTarget id="world" />
95
+
93
96
  <WorldObjects />
94
97
  </T.Group>
95
98
 
package/dist/frame.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { Transform } from '@viamrobotics/sdk';
1
2
  import type { ValueOf } from 'type-fest';
2
3
  type FrameGeometryMap = {
3
4
  none: {
@@ -73,4 +74,5 @@ export interface Frame<T extends FrameGeometry = FrameGeometry, K extends FrameO
73
74
  geometry?: FrameGeometryMap[T];
74
75
  }
75
76
  export declare const createFrame: <T extends FrameGeometry = "box", K extends FrameOrientation = "ov_degrees">(geometry?: FrameGeometryMap[T]) => Frame<T>;
77
+ export declare const createTransformFromFrame: (name: string, frame: Partial<Frame>) => Transform;
76
78
  export {};
package/dist/frame.js CHANGED
@@ -1,4 +1,7 @@
1
1
  // TODO: replace with types exported from the sdk when created
2
+ import { UuidTool } from 'uuid-tool';
3
+ import { createPoseFromFrame } from './transform';
4
+ import { createGeometryFromFrame } from './geometry';
2
5
  export const createFrame = (geometry) => {
3
6
  return {
4
7
  parent: 'world',
@@ -10,3 +13,14 @@ export const createFrame = (geometry) => {
10
13
  geometry: (geometry ?? { type: 'box', x: 100, y: 100, z: 100 }),
11
14
  };
12
15
  };
16
+ export const createTransformFromFrame = (name, frame) => {
17
+ return {
18
+ uuid: new Uint8Array(UuidTool.toBytes(UuidTool.newUuid())),
19
+ referenceFrame: name,
20
+ poseInObserverFrame: {
21
+ referenceFrame: frame.parent ?? 'world',
22
+ pose: createPoseFromFrame(frame),
23
+ },
24
+ physicalObject: createGeometryFromFrame(frame),
25
+ };
26
+ };
package/dist/geometry.js CHANGED
@@ -40,3 +40,21 @@ export const createGeometryFromFrame = (frame) => {
40
40
  });
41
41
  }
42
42
  };
43
+ export const createBox = (box) => {
44
+ return {
45
+ x: (box?.dimsMm?.x ?? 0) * 0.001,
46
+ y: (box?.dimsMm?.y ?? 0) * 0.001,
47
+ z: (box?.dimsMm?.z ?? 0) * 0.001,
48
+ };
49
+ };
50
+ export const createCapsule = (capsule) => {
51
+ return {
52
+ r: (capsule?.radiusMm ?? 0) * 0.001,
53
+ l: (capsule?.lengthMm ?? 0) * 0.001,
54
+ };
55
+ };
56
+ export const createSphere = (sphere) => {
57
+ return {
58
+ r: (sphere?.radiusMm ?? 0) * 0.001,
59
+ };
60
+ };
@@ -25,14 +25,20 @@ export const provide3DModels = (partID) => {
25
25
  if (!client.current)
26
26
  continue;
27
27
  try {
28
+ const geometries = await client.current.getGeometries();
29
+ if (geometries.length === 0) {
30
+ continue;
31
+ }
32
+ const geometryLabel = geometries[0].label;
33
+ const prefix = geometryLabel.split(':')[0];
28
34
  const models = await client.current.get3DModels();
29
- if (!(client.current.name in current)) {
30
- current[client.current.name] = {};
35
+ if (!(prefix in current)) {
36
+ current[prefix] = {};
31
37
  }
32
38
  for (const [id, model] of Object.entries(models)) {
33
39
  const arrayBuffer = model.mesh.buffer.slice(model.mesh.byteOffset, model.mesh.byteOffset + model.mesh.byteLength);
34
40
  const gltfModel = await gltfLoader.parseAsync(arrayBuffer, '');
35
- current[client.current.name][id] = gltfModel.scene;
41
+ current[prefix][id] = gltfModel.scene;
36
42
  }
37
43
  }
38
44
  catch (error) {
@@ -1,6 +1,7 @@
1
1
  import { getContext, setContext } from 'svelte';
2
2
  import { Color, MathUtils, Quaternion, Vector3, Vector4 } from 'three';
3
3
  import { NURBSCurve } from 'three/addons/curves/NURBSCurve.js';
4
+ import { UuidTool } from 'uuid-tool';
4
5
  import { parsePcdInWorker } from '../loaders/pcd';
5
6
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
6
7
  import { WorldObject } from '../WorldObject.svelte';
@@ -11,6 +12,13 @@ import { useCameraControls } from './useControls.svelte';
11
12
  import { useThrelte } from '@threlte/core';
12
13
  import { OrientationVector } from '../three/OrientationVector';
13
14
  import { useLogs } from './useLogs.svelte';
15
+ const bufferTypes = {
16
+ DRAW_POINTS: 0,
17
+ DRAW_POSES: 1,
18
+ DRAW_LINE: 2,
19
+ DRAW_PCD: 3,
20
+ DRAW_GLTF: 4,
21
+ };
14
22
  const axis = new Vector3();
15
23
  const quaternion = new Quaternion();
16
24
  const ov = new OrientationVector();
@@ -43,8 +51,15 @@ class Float32Reader {
43
51
  offset = 0;
44
52
  buffer = new ArrayBuffer();
45
53
  view = new DataView(this.buffer);
54
+ header = { requestID: '', type: -1 };
46
55
  async init(data) {
47
56
  this.buffer = await data.arrayBuffer();
57
+ this.header = {
58
+ requestID: UuidTool.toString([...new Uint8Array(this.buffer.slice(0, 16))]),
59
+ type: new DataView(this.buffer).getFloat32(16, true),
60
+ };
61
+ // Slice away the request header and leave the body
62
+ this.buffer = this.buffer.slice(20);
48
63
  this.view = new DataView(this.buffer);
49
64
  return this;
50
65
  }
@@ -77,6 +92,9 @@ export const provideDrawAPI = () => {
77
92
  const origin = new Vector3();
78
93
  const loader = new GLTFLoader();
79
94
  const batchedArrow = useArrows();
95
+ const sendResponse = (response) => {
96
+ ws.send(JSON.stringify(response));
97
+ };
80
98
  const drawFrames = async (data) => {
81
99
  for (const frame of data) {
82
100
  const name = frame.name || frame.id || '';
@@ -204,7 +222,6 @@ export const provideDrawAPI = () => {
204
222
  },
205
223
  }));
206
224
  }
207
- invalidate();
208
225
  };
209
226
  const drawPoints = async (reader) => {
210
227
  // Read label length
@@ -404,56 +421,90 @@ export const provideDrawAPI = () => {
404
421
  logs.add(`Drawing server error: ${JSON.stringify(event)}`, 'error');
405
422
  };
406
423
  const onMessage = async (event) => {
407
- if (typeof event.data === 'object' && 'arrayBuffer' in event.data) {
408
- const reader = await new Float32Reader().init(event.data);
409
- const type = reader.read();
410
- if (type === 0) {
411
- return drawPoints(reader);
412
- }
413
- else if (type === 1) {
414
- return drawPoses(reader);
415
- }
416
- else if (type === 2) {
417
- return drawLine(reader);
418
- }
419
- else if (type === 3) {
420
- return drawPCD(reader.buffer);
424
+ let operation = 'UNKNOWN';
425
+ let requestID = '';
426
+ try {
427
+ if (typeof event.data === 'object' && 'arrayBuffer' in event.data) {
428
+ const reader = await new Float32Reader().init(event.data);
429
+ requestID = reader.header.requestID;
430
+ const { type } = reader.header;
431
+ if (type === bufferTypes.DRAW_POINTS) {
432
+ operation = 'DrawPoints';
433
+ drawPoints(reader);
434
+ }
435
+ else if (type === bufferTypes.DRAW_POSES) {
436
+ operation = 'DrawPoses';
437
+ drawPoses(reader);
438
+ }
439
+ else if (type === bufferTypes.DRAW_LINE) {
440
+ operation = 'DrawLine';
441
+ drawLine(reader);
442
+ }
443
+ else if (type === bufferTypes.DRAW_PCD) {
444
+ operation = 'DrawPCD';
445
+ drawPCD(reader.buffer);
446
+ }
447
+ else if (type === bufferTypes.DRAW_GLTF) {
448
+ operation = 'DrawGLTF';
449
+ drawGLTF(reader.buffer);
450
+ }
451
+ else {
452
+ throw new Error('Invalid buffer');
453
+ }
421
454
  }
422
455
  else {
423
- return drawGLTF(reader.buffer);
456
+ const [error, data] = tryParse(event.data);
457
+ if (error) {
458
+ logs.add(`Failed to parse JSON from drawing server: ${JSON.stringify(error)}`, 'error');
459
+ throw new Error(`Failed to parse JSON from drawing server: ${JSON.stringify(error)}`);
460
+ }
461
+ if (!data) {
462
+ throw new Error('No drawing data sent to client.');
463
+ }
464
+ requestID = data.requestID;
465
+ if ('setCameraPose' in data) {
466
+ operation = 'SetCameraPose';
467
+ cameraControls.setPose({
468
+ position: [data.Position.X, data.Position.Y, data.Position.Z],
469
+ lookAt: [data.LookAt.X, data.LookAt.Y, data.LookAt.Z],
470
+ }, data.Animate);
471
+ }
472
+ else if ('geometries' in data) {
473
+ operation = 'DrawGeometries';
474
+ drawGeometries(data.geometries, data.colors, data.parent);
475
+ }
476
+ else if ('geometry' in data) {
477
+ operation = 'DrawGeometry';
478
+ drawGeometry(data.geometry, data.color);
479
+ }
480
+ else if ('frames' in data) {
481
+ operation = 'DrawFrames';
482
+ drawFrames(data.frames);
483
+ }
484
+ else if ('Knots' in data) {
485
+ operation = 'DrawNurbs';
486
+ drawNurbs(data, data.Color);
487
+ }
488
+ else if ('remove' in data) {
489
+ operation = 'Remove';
490
+ remove(data.names);
491
+ }
492
+ else if ('removeAll' in data) {
493
+ operation = 'RemoveAll';
494
+ removeAll();
495
+ }
424
496
  }
497
+ sendResponse({ code: 200, requestID, message: `${operation} succeeded.` });
425
498
  }
426
- const [error, data] = tryParse(event.data);
427
- if (error) {
428
- logs.add(`Failed to parse JSON from drawing server: ${JSON.stringify(error)}`, 'error');
429
- }
430
- if (!data)
431
- return;
432
- if ('setCameraPose' in data) {
433
- cameraControls.setPose({
434
- position: [data.Position.X, data.Position.Y, data.Position.Z],
435
- lookAt: [data.LookAt.X, data.LookAt.Y, data.LookAt.Z],
436
- }, data.Animate);
437
- return;
438
- }
439
- if ('geometries' in data) {
440
- return drawGeometries(data.geometries, data.colors, data.parent);
441
- }
442
- if ('geometry' in data) {
443
- return drawGeometry(data.geometry, data.color);
444
- }
445
- if ('frames' in data) {
446
- return drawFrames(data.frames);
447
- }
448
- if ('Knots' in data) {
449
- return drawNurbs(data, data.Color);
450
- }
451
- if ('remove' in data) {
452
- return remove(data.names);
453
- }
454
- if ('removeAll' in data) {
455
- return removeAll();
499
+ catch (error) {
500
+ logs.add(`${error}`, 'error');
501
+ sendResponse({
502
+ code: 500,
503
+ requestID,
504
+ message: `${operation} failed. Reason: ${error}`,
505
+ });
456
506
  }
507
+ invalidate();
457
508
  };
458
509
  const connect = () => {
459
510
  if (BACKEND_IP && BUN_SERVER_PORT) {
@@ -1,12 +1,12 @@
1
1
  import { getContext, setContext, untrack } from 'svelte';
2
+ import { Transform } from '@viamrobotics/sdk';
2
3
  import { useRobotClient, createRobotQuery, useMachineStatus } from '@viamrobotics/svelte-sdk';
3
4
  import { WorldObject } from '../WorldObject.svelte';
4
5
  import { useLogs } from './useLogs.svelte';
5
6
  import { resourceNameToColor } from '../color';
7
+ import { createTransformFromFrame } from '../frame';
6
8
  import { usePartConfig } from './usePartConfig.svelte';
7
9
  import { useEnvironment } from './useEnvironment.svelte';
8
- import { createPoseFromFrame } from '../transform';
9
- import { createGeometryFromFrame } from '../geometry';
10
10
  import { useResourceByName } from './useResourceByName.svelte';
11
11
  import { usePersistentUUIDs } from './usePersistentUUIDs.svelte';
12
12
  const key = Symbol('frames-context');
@@ -20,7 +20,7 @@ export const provideFrames = (partID) => {
20
20
  const partConfig = usePartConfig();
21
21
  const environment = useEnvironment();
22
22
  const { updateUUIDs } = usePersistentUUIDs();
23
- $effect.pre(() => {
23
+ $effect(() => {
24
24
  if (revision) {
25
25
  untrack(() => query.refetch());
26
26
  }
@@ -33,7 +33,7 @@ export const provideFrames = (partID) => {
33
33
  logs.add(`Frames: ${query.error.message}`, 'error');
34
34
  }
35
35
  });
36
- $effect.pre(() => {
36
+ $effect(() => {
37
37
  if (partConfig.isDirty) {
38
38
  environment.current.viewerMode = 'edit';
39
39
  }
@@ -42,40 +42,33 @@ export const provideFrames = (partID) => {
42
42
  }
43
43
  });
44
44
  const machineFrames = $derived.by(() => {
45
- const objects = {};
45
+ const frames = {};
46
46
  for (const { frame } of query.data ?? []) {
47
47
  if (frame === undefined) {
48
48
  continue;
49
49
  }
50
- const resourceName = resourceByName.current[frame.referenceFrame];
51
- const frameName = frame.referenceFrame ? frame.referenceFrame : 'Unnamed frame';
52
- const color = resourceNameToColor(resourceName);
53
- objects[frameName] = new WorldObject(frameName, frame.poseInObserverFrame?.pose, frame.poseInObserverFrame?.referenceFrame, frame.physicalObject, color ? { color } : undefined);
50
+ frames[frame.referenceFrame] = frame;
54
51
  }
55
- return objects;
52
+ return frames;
56
53
  });
57
- const [configFrames, configUnsetFrames] = $derived.by(() => {
54
+ const [configFrames, configUnsetFrameNames] = $derived.by(() => {
58
55
  const components = partConfig.localPartConfig.toJson().components;
59
- const objects = [];
60
- const unsetObjects = [];
61
- // deal with part defined frame config
62
- for (const component of components ?? []) {
63
- if (!component.frame) {
64
- unsetObjects.push(component.name);
56
+ const results = {};
57
+ const unsetResults = [];
58
+ for (const { name, frame } of components ?? []) {
59
+ if (!frame) {
60
+ unsetResults.push(name);
65
61
  continue;
66
62
  }
67
- const pose = createPoseFromFrame(component.frame);
68
- const geometry = createGeometryFromFrame(component.frame);
69
- const worldObject = new WorldObject(component.name, pose, component.frame.parent, geometry);
70
- objects.push(worldObject);
63
+ results[name] = createTransformFromFrame(name, frame);
71
64
  }
72
- return [objects, unsetObjects];
65
+ return [results, unsetResults];
73
66
  });
74
- const [fragmentFrames, fragmentUnsetFrames] = $derived.by(() => {
67
+ const [fragmentFrames, fragmentUnsetFrameNames] = $derived.by(() => {
75
68
  const { fragment_mods: fragmentMods = [] } = partConfig.localPartConfig.toJson() ?? {};
76
69
  const fragmentDefinedComponents = Object.keys(partConfig.componentNameToFragmentId);
77
- const objects = [];
78
- const unsetObjects = [];
70
+ const results = {};
71
+ const unsetResults = [];
79
72
  // deal with fragment defined components
80
73
  for (const fragmentComponentName of fragmentDefinedComponents || []) {
81
74
  const fragmentId = partConfig.componentNameToFragmentId[fragmentComponentName];
@@ -86,60 +79,39 @@ export const provideFrames = (partID) => {
86
79
  const setComponentModIndex = fragmentMod.mods.findLastIndex((mod) => mod['$set']?.[`components.${fragmentComponentName}.frame`] !== undefined);
87
80
  const unsetComponentModIndex = fragmentMod.mods.findLastIndex((mod) => mod['$unset']?.[`components.${fragmentComponentName}.frame`] !== undefined);
88
81
  if (setComponentModIndex < unsetComponentModIndex) {
89
- unsetObjects.push(fragmentComponentName);
82
+ unsetResults.push(fragmentComponentName);
90
83
  }
91
84
  else if (unsetComponentModIndex < setComponentModIndex) {
92
85
  const frameData = fragmentMod.mods[setComponentModIndex]['$set'][`components.${fragmentComponentName}.frame`];
93
- const pose = createPoseFromFrame(frameData);
94
- const geometry = createGeometryFromFrame(frameData);
95
- const worldObject = new WorldObject(fragmentComponentName, pose, frameData.parent, geometry);
96
- objects.push(worldObject);
97
- }
98
- }
99
- return [objects, unsetObjects];
100
- });
101
- $effect.pre(() => {
102
- for (const frame of configFrames) {
103
- const result = machineFrames[frame.name];
104
- if (result) {
105
- result.referenceFrame = frame.referenceFrame;
106
- result.localEditedPose = frame.pose;
107
- result.geometry = frame.geometry;
108
- }
109
- else {
110
- machineFrames[frame.name] = frame;
86
+ results[fragmentComponentName] = createTransformFromFrame(fragmentComponentName, frameData);
111
87
  }
112
88
  }
89
+ return [results, unsetResults];
113
90
  });
114
- $effect.pre(() => {
115
- for (const frame of fragmentFrames) {
116
- const result = machineFrames[frame.name];
117
- if (result) {
118
- result.referenceFrame = frame.referenceFrame;
119
- result.localEditedPose = frame.pose;
120
- result.geometry = frame.geometry;
121
- }
122
- else {
123
- machineFrames[frame.name] = frame;
124
- }
91
+ const frames = $derived.by(() => {
92
+ const result = {
93
+ ...machineFrames,
94
+ ...configFrames,
95
+ ...fragmentFrames,
96
+ };
97
+ // Remove frames that have just been deleted locally for optimistic updates
98
+ for (const name of configUnsetFrameNames) {
99
+ delete result[name];
125
100
  }
126
- });
127
- $effect.pre(() => {
128
- for (const name of configUnsetFrames) {
129
- delete machineFrames[name];
130
- }
131
- });
132
- $effect.pre(() => {
133
- for (const name of fragmentUnsetFrames) {
134
- delete machineFrames[name];
101
+ // Remove frames that have been removed by fragment overrides
102
+ for (const name of fragmentUnsetFrameNames) {
103
+ delete result[name];
135
104
  }
105
+ return result;
136
106
  });
137
107
  const current = $derived.by(() => {
138
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
139
- const _configFrames = configFrames;
140
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
141
- const _fragmentFrames = fragmentFrames;
142
- const results = Object.values(machineFrames);
108
+ const results = [];
109
+ for (const frame of Object.values(frames)) {
110
+ const resourceName = resourceByName.current[frame.referenceFrame];
111
+ const frameName = frame.referenceFrame ? frame.referenceFrame : 'Unnamed frame';
112
+ const color = resourceNameToColor(resourceName);
113
+ results.push(new WorldObject(frameName, frame.poseInObserverFrame?.pose, frame.poseInObserverFrame?.referenceFrame, frame.physicalObject, color ? { color } : undefined));
114
+ }
143
115
  updateUUIDs(results);
144
116
  return results;
145
117
  });
@@ -20,7 +20,7 @@ export const usePose = (name, parent) => {
20
20
  const currentParent = $derived(parent());
21
21
  const resourceByName = useResourceByName();
22
22
  const { addQueryToRefetch } = useRefetchPoses();
23
- const resource = $derived(resourceByName.current[currentName]);
23
+ const resource = $derived(currentName ? resourceByName.current[currentName] : undefined);
24
24
  const parentResource = $derived(currentParent ? resourceByName.current[currentParent] : undefined);
25
25
  const environment = useEnvironment();
26
26
  const frames = useFrames();
@@ -1,7 +1,7 @@
1
1
  export interface SuccessMessage {
2
2
  id: number;
3
3
  positions: Float32Array<ArrayBuffer>;
4
- colors: Float32Array | null;
4
+ colors: Float32Array<ArrayBuffer> | null;
5
5
  }
6
6
  export type Message = SuccessMessage | {
7
7
  id: number;
package/dist/ply.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import type { BufferGeometry } from 'three';
2
+ export declare const parsePlyInput: (mesh: string | Uint8Array) => BufferGeometry;
package/dist/ply.js ADDED
@@ -0,0 +1,18 @@
1
+ import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';
2
+ const plyLoader = new PLYLoader();
3
+ export const parsePlyInput = (mesh) => {
4
+ // Case 1: already a base64 or ASCII string
5
+ if (typeof mesh === 'string') {
6
+ return plyLoader.parse(atob(mesh));
7
+ }
8
+ // Case 2: detect text vs binary PLY in Uint8Array
9
+ const header = new TextDecoder().decode(mesh.slice(0, 50));
10
+ const isAscii = header.includes('format ascii');
11
+ // Case 3: text-mode PLY → decode bytes to string
12
+ if (isAscii) {
13
+ const text = new TextDecoder().decode(mesh);
14
+ return plyLoader.parse(text);
15
+ }
16
+ // Case 4: binary PLY → pass ArrayBuffer directly
17
+ return plyLoader.parse(mesh.buffer);
18
+ };
package/dist/transform.js CHANGED
@@ -66,12 +66,14 @@ export const object3dToPose = (object3d, pose) => {
66
66
  return pose;
67
67
  };
68
68
  export const poseToQuaternion = (pose, quaternion) => {
69
- const th = MathUtils.degToRad(pose.theta ?? 0);
70
- ov.set(pose.oX, pose.oY, pose.oZ, th);
71
- ov.toQuaternion(quaternion);
69
+ const th = MathUtils.degToRad(pose?.theta ?? 0);
70
+ ov.set(pose?.oX, pose?.oY, pose?.oZ, th);
71
+ if (quaternion) {
72
+ ov.toQuaternion(quaternion);
73
+ }
72
74
  };
73
75
  export const poseToVector3 = (pose, vec3) => {
74
- vec3.set(pose.x ?? 0, pose.y ?? 0, pose.z ?? 0).multiplyScalar(0.001);
76
+ vec3?.set(pose?.x ?? 0, pose?.y ?? 0, pose?.z ?? 0).multiplyScalar(0.001);
75
77
  };
76
78
  export const poseToObject3d = (pose, object3d) => {
77
79
  poseToVector3(pose, object3d.position);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "0.19.0",
3
+ "version": "0.19.2",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -89,7 +89,7 @@
89
89
  "svelte-virtuallists": ">=1"
90
90
  },
91
91
  "engines": {
92
- "node": ">=22.0.0"
92
+ "node": ">=22.12.0"
93
93
  },
94
94
  "svelte": "./dist/index.js",
95
95
  "types": "./dist/index.d.ts",
@@ -117,7 +117,8 @@
117
117
  "!dist/**/*.spec.*"
118
118
  ],
119
119
  "dependencies": {
120
- "@tanstack/svelte-query-devtools": "^6.0.2"
120
+ "@tanstack/svelte-query-devtools": "^6.0.2",
121
+ "uuid-tool": "^2.0.3"
121
122
  },
122
123
  "scripts": {
123
124
  "dev": "tsx server/check-bun && bun run server/server.ts",