@viamrobotics/motion-tools 0.19.0 → 0.19.1
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/components/Geometry.svelte +1 -23
- package/dist/components/Label.svelte +2 -2
- package/dist/components/Label.svelte.d.ts +1 -1
- package/dist/components/Pose.svelte +1 -1
- package/dist/components/Pose.svelte.d.ts +1 -1
- package/dist/components/Scene.svelte +4 -1
- package/dist/geometry.js +18 -0
- package/dist/hooks/use3DModels.svelte.js +9 -3
- package/dist/hooks/useDrawAPI.svelte.js +96 -46
- package/dist/hooks/usePose.svelte.js +1 -1
- package/dist/loaders/pcd/worker.d.ts +1 -1
- package/dist/ply.d.ts +2 -0
- package/dist/ply.js +18 -0
- package/dist/transform.js +6 -4
- package/package.json +4 -3
|
@@ -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 {
|
|
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
|
|
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]}
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
return item ? [item] : []
|
|
38
38
|
},
|
|
39
39
|
})
|
|
40
|
-
$effect
|
|
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/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 (!(
|
|
30
|
-
current[
|
|
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[
|
|
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,89 @@ export const provideDrawAPI = () => {
|
|
|
404
421
|
logs.add(`Drawing server error: ${JSON.stringify(event)}`, 'error');
|
|
405
422
|
};
|
|
406
423
|
const onMessage = async (event) => {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if (
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
+
sendResponse({
|
|
501
|
+
code: 500,
|
|
502
|
+
requestID,
|
|
503
|
+
message: `${operation} failed. Reason: ${error}`,
|
|
504
|
+
});
|
|
456
505
|
}
|
|
506
|
+
invalidate();
|
|
457
507
|
};
|
|
458
508
|
const connect = () => {
|
|
459
509
|
if (BACKEND_IP && BUN_SERVER_PORT) {
|
|
@@ -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();
|
package/dist/ply.d.ts
ADDED
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
|
|
70
|
-
ov.set(pose
|
|
71
|
-
|
|
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
|
|
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.
|
|
3
|
+
"version": "0.19.1",
|
|
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.
|
|
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",
|