@viamrobotics/motion-tools 1.3.5 → 1.5.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/attribute.d.ts +2 -2
- package/dist/attribute.js +2 -2
- package/dist/common/v1/common_pb.d.ts +8 -0
- package/dist/common/v1/common_pb.js +7 -0
- package/dist/components/Details.svelte +15 -2
- package/dist/components/Tree/Drawer.svelte +0 -1
- package/dist/components/Tree/TreeContainer.svelte +8 -1
- package/dist/hooks/useDrawAPI.svelte.js +29 -15
- package/dist/hooks/useFrames.svelte.js +18 -14
- package/dist/hooks/usePose.svelte.js +1 -1
- package/dist/hooks/useWorldState.svelte.js +18 -1
- package/dist/loaders/pcd/worker.d.ts +1 -1
- package/dist/loaders/pcd/worker.js +7 -1
- package/dist/snapshot.js +1 -3
- package/package.json +2 -2
package/dist/attribute.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { BufferGeometry } from 'three';
|
|
2
|
-
export declare const createBufferGeometry: (positions: Float32Array, colors?:
|
|
3
|
-
export declare const updateBufferGeometry: (geometry: BufferGeometry, positions: Float32Array, colors?:
|
|
2
|
+
export declare const createBufferGeometry: (positions: Float32Array, colors?: Uint8Array | null) => BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>;
|
|
3
|
+
export declare const updateBufferGeometry: (geometry: BufferGeometry, positions: Float32Array, colors?: Uint8Array | null) => void;
|
package/dist/attribute.js
CHANGED
|
@@ -3,7 +3,7 @@ export const createBufferGeometry = (positions, colors) => {
|
|
|
3
3
|
const geometry = new BufferGeometry();
|
|
4
4
|
geometry.setAttribute('position', new BufferAttribute(positions, 3));
|
|
5
5
|
if (colors) {
|
|
6
|
-
geometry.setAttribute('color', new BufferAttribute(colors, 3));
|
|
6
|
+
geometry.setAttribute('color', new BufferAttribute(colors, 3, true));
|
|
7
7
|
}
|
|
8
8
|
return geometry;
|
|
9
9
|
};
|
|
@@ -24,7 +24,7 @@ export const updateBufferGeometry = (geometry, positions, colors) => {
|
|
|
24
24
|
colorAttr.needsUpdate = true;
|
|
25
25
|
}
|
|
26
26
|
else {
|
|
27
|
-
geometry.setAttribute('color', new BufferAttribute(colors, 3));
|
|
27
|
+
geometry.setAttribute('color', new BufferAttribute(colors, 3, true));
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
};
|
|
@@ -665,6 +665,14 @@ export declare class GetKinematicsResponse extends Message<GetKinematicsResponse
|
|
|
665
665
|
* @generated from field: bytes kinematics_data = 2;
|
|
666
666
|
*/
|
|
667
667
|
kinematicsData: Uint8Array<ArrayBuffer>;
|
|
668
|
+
/**
|
|
669
|
+
* Map of URDF mesh file paths to mesh data
|
|
670
|
+
*
|
|
671
|
+
* @generated from field: map<string, viam.common.v1.Mesh> meshes_by_urdf_filepath = 3;
|
|
672
|
+
*/
|
|
673
|
+
meshesByUrdfFilepath: {
|
|
674
|
+
[key: string]: Mesh;
|
|
675
|
+
};
|
|
668
676
|
constructor(data?: PartialMessage<GetKinematicsResponse>);
|
|
669
677
|
static readonly runtime: typeof proto3;
|
|
670
678
|
static readonly typeName = "viam.common.v1.GetKinematicsResponse";
|
|
@@ -959,6 +959,12 @@ export class GetKinematicsResponse extends Message {
|
|
|
959
959
|
* @generated from field: bytes kinematics_data = 2;
|
|
960
960
|
*/
|
|
961
961
|
kinematicsData = new Uint8Array(0);
|
|
962
|
+
/**
|
|
963
|
+
* Map of URDF mesh file paths to mesh data
|
|
964
|
+
*
|
|
965
|
+
* @generated from field: map<string, viam.common.v1.Mesh> meshes_by_urdf_filepath = 3;
|
|
966
|
+
*/
|
|
967
|
+
meshesByUrdfFilepath = {};
|
|
962
968
|
constructor(data) {
|
|
963
969
|
super();
|
|
964
970
|
proto3.util.initPartial(data, this);
|
|
@@ -968,6 +974,7 @@ export class GetKinematicsResponse extends Message {
|
|
|
968
974
|
static fields = proto3.util.newFieldList(() => [
|
|
969
975
|
{ no: 1, name: "format", kind: "enum", T: proto3.getEnumType(KinematicsFileFormat) },
|
|
970
976
|
{ no: 2, name: "kinematics_data", kind: "scalar", T: 12 /* ScalarType.BYTES */ },
|
|
977
|
+
{ no: 3, name: "meshes_by_urdf_filepath", kind: "map", K: 9 /* ScalarType.STRING */, V: { kind: "message", T: Mesh } },
|
|
971
978
|
]);
|
|
972
979
|
static fromBinary(bytes, options) {
|
|
973
980
|
return new GetKinematicsResponse().fromBinary(bytes, options);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
lang="ts"
|
|
4
4
|
>
|
|
5
5
|
import { OrientationVector } from '../three/OrientationVector'
|
|
6
|
-
import { Quaternion, Vector3, MathUtils } from 'three'
|
|
6
|
+
import { Quaternion, Vector3, MathUtils, BufferAttribute } from 'three'
|
|
7
7
|
|
|
8
8
|
const vec3 = new Vector3()
|
|
9
9
|
const quaternion = new Quaternion()
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<script lang="ts">
|
|
14
14
|
import { draggable } from '@neodrag/svelte'
|
|
15
15
|
import { Check, Copy } from 'lucide-svelte'
|
|
16
|
-
import { useTask } from '@threlte/core'
|
|
16
|
+
import { useTask, isInstanceOf } from '@threlte/core'
|
|
17
17
|
import { Button, Icon, Select, Input, Tooltip } from '@viamrobotics/prime-core'
|
|
18
18
|
import {
|
|
19
19
|
useSelectedEntity,
|
|
@@ -596,6 +596,19 @@
|
|
|
596
596
|
</div>
|
|
597
597
|
</div>
|
|
598
598
|
{/if}
|
|
599
|
+
|
|
600
|
+
{#if isInstanceOf(object3d, 'Points')}
|
|
601
|
+
<div>
|
|
602
|
+
<strong class="font-semibold">points</strong>
|
|
603
|
+
{@render ImmutableField({
|
|
604
|
+
label: 'count',
|
|
605
|
+
ariaLabel: 'points count',
|
|
606
|
+
value: new Intl.NumberFormat().format(
|
|
607
|
+
(object3d.geometry.getAttribute('position') as BufferAttribute).array.length / 3
|
|
608
|
+
),
|
|
609
|
+
})}
|
|
610
|
+
</div>
|
|
611
|
+
{/if}
|
|
599
612
|
</div>
|
|
600
613
|
|
|
601
614
|
<h3 class="text-subtle-2 pt-3 pb-2">Actions</h3>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { useEnvironment } from '../../hooks/useEnvironment.svelte'
|
|
11
11
|
import { usePartID } from '../../hooks/usePartID.svelte'
|
|
12
12
|
import { usePartConfig } from '../../hooks/usePartConfig.svelte'
|
|
13
|
+
import { useFrames } from '../../hooks/useFrames.svelte'
|
|
13
14
|
import { traits, useQuery, useWorld } from '../../ecs'
|
|
14
15
|
import { IsExcluded, type Entity } from 'koota'
|
|
15
16
|
import { buildTreeNodes, type TreeNode } from './buildTree'
|
|
@@ -30,13 +31,19 @@
|
|
|
30
31
|
)
|
|
31
32
|
const environment = useEnvironment()
|
|
32
33
|
const partConfig = usePartConfig()
|
|
34
|
+
const frames = useFrames()
|
|
33
35
|
const world = useWorld()
|
|
34
36
|
|
|
35
37
|
const worldEntity = world.spawn(IsExcluded, traits.Name('World'))
|
|
36
38
|
|
|
37
39
|
const allEntities = useQuery(traits.Name)
|
|
38
40
|
|
|
39
|
-
const { rootNodes, nodeMap } = $derived(
|
|
41
|
+
const { rootNodes, nodeMap } = $derived.by(() => {
|
|
42
|
+
// This ensures the tree rebuilds when frame parent relationships change
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
44
|
+
frames.current
|
|
45
|
+
return buildTreeNodes(allEntities.current)
|
|
46
|
+
})
|
|
40
47
|
|
|
41
48
|
const rootNode = $derived<TreeNode>({
|
|
42
49
|
entity: worldEntity,
|
|
@@ -71,6 +71,11 @@ class BinaryReader {
|
|
|
71
71
|
this.offsetBytes += 4;
|
|
72
72
|
return v;
|
|
73
73
|
}
|
|
74
|
+
readU32() {
|
|
75
|
+
const v = this.view.getUint32(this.offsetBytes, this.littleEndian);
|
|
76
|
+
this.offsetBytes += 4;
|
|
77
|
+
return v;
|
|
78
|
+
}
|
|
74
79
|
/**
|
|
75
80
|
* Get a Float32Array VIEW into the underlying buffer (no copy) and advance.
|
|
76
81
|
* Requires current offset to be 4-byte aligned (it will be, if you only readF32 so far).
|
|
@@ -222,24 +227,32 @@ export const provideDrawAPI = () => {
|
|
|
222
227
|
label += String.fromCharCode(reader.read());
|
|
223
228
|
}
|
|
224
229
|
// Read counts
|
|
225
|
-
const nPoints = reader.
|
|
226
|
-
const nColors = reader.
|
|
230
|
+
const nPoints = reader.readU32();
|
|
231
|
+
const nColors = reader.readU32();
|
|
227
232
|
// Read default color
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
233
|
+
let r = reader.read();
|
|
234
|
+
let g = reader.read();
|
|
235
|
+
let b = reader.read();
|
|
231
236
|
const nPointsElements = nPoints * 3;
|
|
232
237
|
const positions = reader.readF32Array(nPointsElements);
|
|
233
238
|
const nColorsElements = nColors * 3;
|
|
234
|
-
const rawColors = reader.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
239
|
+
const rawColors = reader.readU8Array(nColorsElements);
|
|
240
|
+
let colors = null;
|
|
241
|
+
if (nColors > 1) {
|
|
242
|
+
colors = new Uint8Array(nPointsElements);
|
|
243
|
+
colors.set(rawColors);
|
|
244
|
+
// Cover the gap for any points not colored
|
|
245
|
+
for (let i = nColors; i < nPoints; i++) {
|
|
246
|
+
const offset = i * 3;
|
|
247
|
+
colors[offset] = Math.round(r * 255);
|
|
248
|
+
colors[offset + 1] = Math.round(g * 255);
|
|
249
|
+
colors[offset + 2] = Math.round(b * 255);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else if (nColors === 1) {
|
|
253
|
+
r = rawColors[0] / 255;
|
|
254
|
+
g = rawColors[1] / 255;
|
|
255
|
+
b = rawColors[2] / 255;
|
|
243
256
|
}
|
|
244
257
|
const entities = world.query(traits.DrawAPI);
|
|
245
258
|
const entity = entities.find((entity) => entity.get(traits.Name) === label);
|
|
@@ -370,7 +383,8 @@ export const provideDrawAPI = () => {
|
|
|
370
383
|
}
|
|
371
384
|
else if (type === bufferTypes.DRAW_GLTF) {
|
|
372
385
|
operation = 'DrawGLTF';
|
|
373
|
-
|
|
386
|
+
// GLTF payload starts after the 20-byte header (16 bytes UUID + 4 bytes type)
|
|
387
|
+
drawGLTF(reader.buffer.slice(20));
|
|
374
388
|
}
|
|
375
389
|
else {
|
|
376
390
|
throw new Error('Invalid buffer');
|
|
@@ -22,11 +22,6 @@ export const provideFrames = (partID) => {
|
|
|
22
22
|
}));
|
|
23
23
|
const revision = $derived(machineStatus.current?.config?.revision);
|
|
24
24
|
const partConfig = usePartConfig();
|
|
25
|
-
$effect(() => {
|
|
26
|
-
if (revision) {
|
|
27
|
-
untrack(() => query.refetch());
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
25
|
$effect(() => {
|
|
31
26
|
if (query.isFetching) {
|
|
32
27
|
logs.add('Fetching frames...');
|
|
@@ -118,15 +113,24 @@ export const provideFrames = (partID) => {
|
|
|
118
113
|
const current = $derived(Object.values(frames));
|
|
119
114
|
const entities = new Map();
|
|
120
115
|
$effect.pre(() => {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
116
|
+
if (revision) {
|
|
117
|
+
untrack(async () => {
|
|
118
|
+
await query.refetch();
|
|
119
|
+
for (const [name, machineFrame] of Object.entries(machineFrames)) {
|
|
120
|
+
if (machineFrame === undefined) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const existing = entities.get(name);
|
|
124
|
+
if (existing) {
|
|
125
|
+
const pose = createPose(machineFrame.transform.poseInObserverFrame?.pose);
|
|
126
|
+
existing.set(traits.Pose, pose);
|
|
127
|
+
if (environment.current.viewerMode === 'monitor') {
|
|
128
|
+
// if we are in monitor mode, we want the network pose to overwrite any leftover edited poses
|
|
129
|
+
existing.set(traits.EditedPose, pose);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
130
134
|
}
|
|
131
135
|
});
|
|
132
136
|
$effect.pre(() => {
|
|
@@ -10,7 +10,7 @@ import { RefetchRates } from '../components/RefreshRate.svelte';
|
|
|
10
10
|
import { useLogs } from './useLogs.svelte';
|
|
11
11
|
import { useResourceByName } from './useResourceByName.svelte';
|
|
12
12
|
import { useRefetchPoses } from './useRefetchPoses';
|
|
13
|
-
const origingFrameComponentTypes = ['arm', 'gantry', 'gripper'];
|
|
13
|
+
const origingFrameComponentTypes = ['arm', 'gantry', 'gripper', 'base'];
|
|
14
14
|
export const usePose = (name, parent) => {
|
|
15
15
|
const environment = useEnvironment();
|
|
16
16
|
const logs = useLogs();
|
|
@@ -7,6 +7,8 @@ import { createPose } from '../transform';
|
|
|
7
7
|
import { useThrelte } from '@threlte/core';
|
|
8
8
|
import { createBox, createCapsule, createSphere } from '../geometry';
|
|
9
9
|
import { parsePlyInput } from '../ply';
|
|
10
|
+
import { parsePcdInWorker } from '../loaders/pcd';
|
|
11
|
+
import { createBufferGeometry } from '../attribute';
|
|
10
12
|
export const provideWorldStates = () => {
|
|
11
13
|
const partID = usePartID();
|
|
12
14
|
const resourceNames = useResourceNames(() => partID.current, 'world_state_store');
|
|
@@ -45,7 +47,22 @@ const createWorldState = (client) => {
|
|
|
45
47
|
entityTraits.push(traits.VertexColors(metadata.colors));
|
|
46
48
|
}
|
|
47
49
|
if (transform.physicalObject) {
|
|
48
|
-
|
|
50
|
+
if (transform.physicalObject.geometryType.case === 'pointcloud') {
|
|
51
|
+
parsePcdInWorker(new Uint8Array(transform.physicalObject.geometryType.value.pointCloud)).then((pointcloud) => {
|
|
52
|
+
// pcds are a special case since they have to be loaded in a worker and the trait will be added to the existing entity
|
|
53
|
+
const entity = entities.get(transform.uuidString);
|
|
54
|
+
if (!entity) {
|
|
55
|
+
console.error('Entity not found to add pointcloud trait to', transform.uuidString);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const geometry = createBufferGeometry(pointcloud.positions, pointcloud.colors);
|
|
59
|
+
entity.add(traits.BufferGeometry(geometry));
|
|
60
|
+
entity.add(traits.Points);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
entityTraits.push(traits.Geometry(transform.physicalObject));
|
|
65
|
+
}
|
|
49
66
|
}
|
|
50
67
|
if (metadata.shape === 'line' && metadata.points) {
|
|
51
68
|
const { points } = metadata;
|
|
@@ -15,7 +15,13 @@ self.onmessage = async (event) => {
|
|
|
15
15
|
*/
|
|
16
16
|
const positions = pcd.geometry.attributes.position?.array ??
|
|
17
17
|
new Float32Array(0);
|
|
18
|
-
const
|
|
18
|
+
const colorsFloat = pcd.geometry.attributes.color?.array ?? null;
|
|
19
|
+
const colors = colorsFloat ? new Uint8Array(colorsFloat.length) : null;
|
|
20
|
+
if (colors) {
|
|
21
|
+
for (let i = 0, l = colorsFloat.length; i < l; i++) {
|
|
22
|
+
colors[i] = Math.round(colorsFloat[i] * 255);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
19
25
|
postMessage({ positions, colors, id }, colors ? [positions.buffer, colors.buffer] : [positions.buffer]);
|
|
20
26
|
}
|
|
21
27
|
else {
|
package/dist/snapshot.js
CHANGED
|
@@ -192,9 +192,7 @@ const spawnEntitiesFromDrawing = (world, drawing) => {
|
|
|
192
192
|
for (let i = 0, l = positions.length; i < l; i += 1) {
|
|
193
193
|
positions[i] *= 0.001;
|
|
194
194
|
}
|
|
195
|
-
const colors = drawing.metadata?.colors
|
|
196
|
-
? rgbaBytesToFloat32(drawing.metadata.colors)
|
|
197
|
-
: undefined;
|
|
195
|
+
const colors = drawing.metadata?.colors;
|
|
198
196
|
const geometry = createBufferGeometry(positions, colors);
|
|
199
197
|
entityTraits.push(traits.BufferGeometry(geometry));
|
|
200
198
|
if (geometryType.value.pointSize) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viamrobotics/motion-tools",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Motion visualization with Viam",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
"@neodrag/svelte": "^2.3.3",
|
|
130
130
|
"@tanstack/svelte-query-devtools": "^6.0.2",
|
|
131
131
|
"koota": "^0.5.3",
|
|
132
|
-
"lodash-es": "4.17.
|
|
132
|
+
"lodash-es": "4.17.23",
|
|
133
133
|
"uuid-tool": "^2.0.3"
|
|
134
134
|
},
|
|
135
135
|
"scripts": {
|