@viamrobotics/motion-tools 1.2.2 → 1.2.3

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.
@@ -0,0 +1,3 @@
1
+ import { BufferGeometry } from 'three';
2
+ export declare const createBufferGeometry: (positions: Float32Array, colors?: Float32Array | null) => BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>;
3
+ export declare const updateBufferGeometry: (geometry: BufferGeometry, positions: Float32Array, colors?: Float32Array | null) => void;
@@ -0,0 +1,30 @@
1
+ import { BufferGeometry, BufferAttribute } from 'three';
2
+ export const createBufferGeometry = (positions, colors) => {
3
+ const geometry = new BufferGeometry();
4
+ geometry.setAttribute('position', new BufferAttribute(positions, 3));
5
+ if (colors) {
6
+ geometry.setAttribute('color', new BufferAttribute(colors, 3));
7
+ }
8
+ return geometry;
9
+ };
10
+ export const updateBufferGeometry = (geometry, positions, colors) => {
11
+ const positionAttr = geometry.getAttribute('position');
12
+ if (positionAttr && positionAttr.array.length >= positions.length) {
13
+ positionAttr.array.set(positions, 0);
14
+ geometry.setDrawRange(0, positions.length);
15
+ positionAttr.needsUpdate = true;
16
+ }
17
+ else {
18
+ geometry.setAttribute('position', new BufferAttribute(positions, 3));
19
+ }
20
+ if (colors) {
21
+ const colorAttr = geometry.getAttribute('color');
22
+ if (colorAttr && colorAttr.array.length >= colors.length) {
23
+ colorAttr.array.set(colors, 0);
24
+ colorAttr.needsUpdate = true;
25
+ }
26
+ else {
27
+ geometry.setAttribute('color', new BufferAttribute(colors, 3));
28
+ }
29
+ }
30
+ };
@@ -37,10 +37,11 @@
37
37
  Not(traits.FramesAPI),
38
38
  Not(traits.GeometriesAPI),
39
39
  Not(traits.WorldStateStoreAPI),
40
+ Not(traits.Points),
40
41
  Or(traits.Box, traits.Capsule, traits.Sphere, traits.BufferGeometry, traits.ReferenceFrame)
41
42
  )
42
43
 
43
- const points = useQuery(traits.PointsPositions)
44
+ const points = useQuery(traits.Points)
44
45
  const lines = useQuery(traits.LinePositions)
45
46
  const gltfs = useQuery(traits.GLTF)
46
47
  </script>
@@ -7,6 +7,7 @@
7
7
  import { traits } from '../../ecs'
8
8
  import { spawnSnapshotEntities } from '../../snapshot'
9
9
  import { useCameraControls } from '../../hooks/useControls.svelte'
10
+ import { createBufferGeometry } from '../../attribute'
10
11
 
11
12
  const props: HTMLAttributes<HTMLDivElement> = $props()
12
13
 
@@ -34,21 +35,25 @@
34
35
 
35
36
  break
36
37
  }
37
- case 'pcd':
38
+ case 'pcd': {
39
+ const geometry = createBufferGeometry(result.pcd.positions, result.pcd.colors)
40
+
38
41
  world.spawn(
39
42
  traits.Name(result.name),
40
- traits.PointsPositions(result.pcd.positions),
41
- result.pcd.colors ? traits.VertexColors(result.pcd.colors) : traits.Color,
43
+ traits.BufferGeometry(geometry),
44
+ traits.Points,
42
45
  traits.DroppedFile
43
46
  )
44
47
  break
45
- case 'ply':
48
+ }
49
+ case 'ply': {
46
50
  world.spawn(
47
51
  traits.Name(result.name),
48
52
  traits.BufferGeometry(result.ply),
49
53
  traits.DroppedFile
50
54
  )
51
55
  break
56
+ }
52
57
  }
53
58
 
54
59
  toast({ message: `${result.name} loaded.`, variant: ToastVariant.Success })
@@ -1,11 +1,5 @@
1
1
  <script lang="ts">
2
- import {
3
- Points,
4
- BufferAttribute,
5
- BufferGeometry,
6
- PointsMaterial,
7
- OrthographicCamera,
8
- } from 'three'
2
+ import { Points, PointsMaterial, OrthographicCamera } from 'three'
9
3
  import { T, useTask, useThrelte } from '@threlte/core'
10
4
  import { Portal } from '@threlte/extras'
11
5
  import { useObjectEvents } from '../hooks/useObjectEvents.svelte'
@@ -28,9 +22,8 @@
28
22
  const name = useTrait(() => entity, traits.Name)
29
23
  const parent = useTrait(() => entity, traits.Parent)
30
24
  const pose = useTrait(() => entity, traits.Pose)
31
- const positions = useTrait(() => entity, traits.PointsPositions)
25
+ const geometry = useTrait(() => entity, traits.BufferGeometry)
32
26
  const color = useTrait(() => entity, traits.Color)
33
- const colors = useTrait(() => entity, traits.VertexColors)
34
27
  const opacity = useTrait(() => entity, traits.Opacity)
35
28
  const entityPointSize = useTrait(() => entity, traits.PointSize)
36
29
 
@@ -40,8 +33,7 @@
40
33
  const orthographic = $derived(settings.current.cameraMode === 'orthographic')
41
34
 
42
35
  const points = new Points()
43
- const geometry = new BufferGeometry()
44
- const material = new PointsMaterial()
36
+ const material = points.material as PointsMaterial
45
37
  material.toneMapped = false
46
38
 
47
39
  $effect.pre(() => {
@@ -49,7 +41,7 @@
49
41
  })
50
42
 
51
43
  $effect.pre(() => {
52
- if (colors.current) {
44
+ if (geometry.current?.getAttribute('color')) {
53
45
  material.color.set(0xffffff)
54
46
  } else if (color.current) {
55
47
  material.color.setRGB(color.current.r, color.current.g, color.current.b)
@@ -74,25 +66,18 @@
74
66
  })
75
67
 
76
68
  $effect.pre(() => {
77
- if (positions.current) {
78
- geometry.setAttribute('position', new BufferAttribute(positions.current, 3))
79
- }
80
- })
69
+ const colors = geometry.current?.getAttribute('color')
70
+ const positions = geometry.current?.getAttribute('position')
81
71
 
82
- $effect.pre(() => {
83
- material.vertexColors = colors.current !== undefined
72
+ material.vertexColors = colors !== undefined
84
73
 
85
- if (colors.current && positions.current) {
86
- const vertexColors = colors.current
87
- const hasAlphaChannel = positions.current.length / vertexColors.length === 0.75
88
- const itemSize = hasAlphaChannel ? 4 : 3
89
- geometry.setAttribute('color', new BufferAttribute(vertexColors, itemSize))
90
- geometry.attributes.color.needsUpdate = true
74
+ if (colors && positions) {
75
+ const hasAlphaChannel = positions.array.length / colors.array.length === 0.75
91
76
 
92
77
  let transparent = false
93
78
  if (hasAlphaChannel) {
94
- for (let i = 3, l = vertexColors.length; i < l; i += 4) {
95
- if (vertexColors[i] < 1) {
79
+ for (let i = 3, l = colors.array.length; i < l; i += 4) {
80
+ if (colors.array[i] < 1) {
96
81
  transparent = true
97
82
  break
98
83
  }
@@ -133,15 +118,17 @@
133
118
  })
134
119
  </script>
135
120
 
136
- <Portal id={parent.current}>
137
- <T
138
- is={points}
139
- name={name.current}
140
- {...events}
141
- bvh={{ maxDepth: 40, maxLeafTris: 20 }}
142
- >
143
- <T is={geometry} />
144
- <T is={material} />
145
- {@render children?.()}
146
- </T>
147
- </Portal>
121
+ {#if geometry.current}
122
+ <Portal id={parent.current}>
123
+ <T
124
+ is={points}
125
+ name={name.current}
126
+ {...events}
127
+ bvh={{ maxDepth: 40, maxLeafTris: 20 }}
128
+ >
129
+ <T is={geometry.current} />
130
+ <T is={material} />
131
+ {@render children?.()}
132
+ </T>
133
+ </Portal>
134
+ {/if}
@@ -42,6 +42,7 @@
42
42
  const flush = () => {
43
43
  if (pending) return
44
44
  pending = true
45
+
45
46
  window.setTimeout(() => {
46
47
  const results = buildTreeNodes(world.query(traits.Name))
47
48
  children = results.rootNodes
@@ -45,6 +45,10 @@ export declare const Color: import("koota").Trait<{
45
45
  b: number;
46
46
  }>;
47
47
  export declare const Arrow: import("koota").TagTrait;
48
+ /**
49
+ * Render entity as points
50
+ */
51
+ export declare const Points: import("koota").TagTrait;
48
52
  /**
49
53
  * A box, in mm
50
54
  */
@@ -73,7 +77,6 @@ export declare const PointColor: import("koota").Trait<{
73
77
  }>;
74
78
  /** format [x, y, z, ...] */
75
79
  export declare const LinePositions: import("koota").Trait<() => Float32Array<ArrayBuffer>>;
76
- export declare const PointsPositions: import("koota").Trait<() => Float32Array<ArrayBuffer>>;
77
80
  export declare const BufferGeometry: import("koota").Trait<() => ThreeBufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>>;
78
81
  /** format [r, g, b, ...] */
79
82
  export declare const VertexColors: import("koota").Trait<() => Float32Array<ArrayBuffer>>;
@@ -19,6 +19,10 @@ export const Opacity = trait(() => 1);
19
19
  */
20
20
  export const Color = trait({ r: 0, g: 0, b: 0 });
21
21
  export const Arrow = trait();
22
+ /**
23
+ * Render entity as points
24
+ */
25
+ export const Points = trait();
22
26
  /**
23
27
  * A box, in mm
24
28
  */
@@ -34,7 +38,6 @@ export const Sphere = trait({ r: 200 });
34
38
  export const PointColor = trait({ r: 0, g: 0, b: 0 });
35
39
  /** format [x, y, z, ...] */
36
40
  export const LinePositions = trait(() => new Float32Array());
37
- export const PointsPositions = trait(() => new Float32Array());
38
41
  export const BufferGeometry = trait(() => new ThreeBufferGeometry());
39
42
  /** format [r, g, b, ...] */
40
43
  export const VertexColors = trait(() => new Float32Array());
@@ -2,7 +2,6 @@ import { getContext, setContext } from 'svelte';
2
2
  import { Color, Vector3, Vector4 } from 'three';
3
3
  import { NURBSCurve } from 'three/addons/curves/NURBSCurve.js';
4
4
  import { UuidTool } from 'uuid-tool';
5
- import { parsePcdInWorker } from '../loaders/pcd';
6
5
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
7
6
  import { createPose, createPoseFromFrame } from '../transform';
8
7
  import { useCameraControls } from './useControls.svelte';
@@ -13,6 +12,7 @@ import { parsePlyInput } from '../ply';
13
12
  import { useLogs } from './useLogs.svelte';
14
13
  import { createBox, createCapsule, createSphere } from '../geometry';
15
14
  import { useDrawConnectionConfig } from './useDrawConnectionConfig.svelte';
15
+ import { createBufferGeometry, updateBufferGeometry } from '../attribute';
16
16
  const colorUtil = new Color();
17
17
  const bufferTypes = {
18
18
  DRAW_POINTS: 0,
@@ -74,7 +74,6 @@ export const provideDrawAPI = () => {
74
74
  const drawConnectionConfig = useDrawConnectionConfig();
75
75
  const backendIP = $derived(drawConnectionConfig.current?.backendIP);
76
76
  const websocketPort = $derived(drawConnectionConfig.current?.websocketPort);
77
- let pointsIndex = 0;
78
77
  let geometryIndex = 0;
79
78
  let poseIndex = 0;
80
79
  let reconnectDelay = 200;
@@ -126,13 +125,6 @@ export const provideDrawAPI = () => {
126
125
  entities.set(name, entity);
127
126
  }
128
127
  };
129
- const drawPCD = async (buffer) => {
130
- const { positions, colors } = await parsePcdInWorker(new Uint8Array(buffer));
131
- const entity = world.spawn(traits.Name(`Points ${++pointsIndex}`), traits.PointsPositions(positions), traits.DrawAPI);
132
- if (colors) {
133
- entity.add(traits.VertexColors(colors));
134
- }
135
- };
136
128
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
137
129
  const drawGeometry = (data, color, parent) => {
138
130
  const name = data.label ?? `geometry ${++geometryIndex}`;
@@ -225,9 +217,6 @@ export const provideDrawAPI = () => {
225
217
  for (let i = 0; i < labelLen; i++) {
226
218
  label += String.fromCharCode(reader.read());
227
219
  }
228
- const entities = world.query(traits.DrawAPI);
229
- const entity = entities.find((entity) => entity.get(traits.Name) === label);
230
- entity?.destroy();
231
220
  // Read counts
232
221
  const nPoints = reader.read();
233
222
  const nColors = reader.read();
@@ -250,7 +239,17 @@ export const provideDrawAPI = () => {
250
239
  colors[offset + 1] = g;
251
240
  colors[offset + 2] = b;
252
241
  }
253
- world.spawn(traits.Name(label), traits.Color(colorUtil.set(r, g, b)), traits.PointsPositions(positions), traits.VertexColors(colors), traits.DrawAPI);
242
+ const entities = world.query(traits.DrawAPI);
243
+ const entity = entities.find((entity) => entity.get(traits.Name) === label);
244
+ if (entity) {
245
+ const geometry = entity.get(traits.BufferGeometry);
246
+ if (geometry) {
247
+ updateBufferGeometry(geometry, positions, colors);
248
+ return;
249
+ }
250
+ }
251
+ const geometry = createBufferGeometry(positions, colors);
252
+ world.spawn(traits.Name(label), traits.Color(colorUtil.set(r, g, b)), traits.BufferGeometry(geometry), traits.Points, traits.DrawAPI);
254
253
  };
255
254
  const drawLine = async (reader) => {
256
255
  // Read label length
@@ -310,7 +309,6 @@ export const provideDrawAPI = () => {
310
309
  entity.destroy();
311
310
  }
312
311
  entities.clear();
313
- pointsIndex = 0;
314
312
  geometryIndex = 0;
315
313
  poseIndex = 0;
316
314
  };
@@ -364,10 +362,6 @@ export const provideDrawAPI = () => {
364
362
  operation = 'DrawLine';
365
363
  drawLine(reader);
366
364
  }
367
- else if (type === bufferTypes.DRAW_PCD) {
368
- operation = 'DrawPCD';
369
- drawPCD(reader.buffer);
370
- }
371
365
  else if (type === bufferTypes.DRAW_GLTF) {
372
366
  operation = 'DrawGLTF';
373
367
  drawGLTF(reader.buffer);
@@ -1,5 +1,5 @@
1
1
  import { CameraClient } from '@viamrobotics/sdk';
2
- import { setContext, getContext, untrack } from 'svelte';
2
+ import { setContext, getContext } from 'svelte';
3
3
  import { createResourceClient, createResourceQuery, useResourceNames, } from '@viamrobotics/svelte-sdk';
4
4
  import { parsePcdInWorker } from '../loaders/pcd';
5
5
  import { RefreshRates, useMachineSettings } from './useMachineSettings.svelte';
@@ -7,6 +7,7 @@ import { useLogs } from './useLogs.svelte';
7
7
  import { RefetchRates } from '../components/RefreshRate.svelte';
8
8
  import { traits, useWorld } from '../ecs';
9
9
  import { useEnvironment } from './useEnvironment.svelte';
10
+ import { createBufferGeometry, updateBufferGeometry } from '../attribute';
10
11
  const typeSafeObjectFromEntries = (entries) => {
11
12
  return Object.fromEntries(entries);
12
13
  };
@@ -26,12 +27,13 @@ export const providePointclouds = (partID) => {
26
27
  refetchInterval: false,
27
28
  }),
28
29
  ]));
29
- const fetchedPropQueries = propQueries.every(([, query]) => query.isPending === false);
30
+ const fetchedPropQueries = $derived(propQueries.every(([, query]) => query.isPending === false));
30
31
  const interval = $derived(refreshRates.get(RefreshRates.pointclouds));
31
32
  const enabledClients = $derived.by(() => {
32
33
  const results = [];
33
34
  for (const client of clients) {
34
- if (fetchedPropQueries &&
35
+ if (environment.current.viewerMode === 'monitor' &&
36
+ fetchedPropQueries &&
35
37
  client.current?.name &&
36
38
  interval !== RefetchRates.OFF &&
37
39
  disabledCameras.get(client.current?.name) !== true) {
@@ -57,7 +59,6 @@ export const providePointclouds = (partID) => {
57
59
  }
58
60
  });
59
61
  const options = $derived({
60
- enabled: environment.current.viewerMode === 'edit',
61
62
  refetchInterval: interval,
62
63
  });
63
64
  const queries = $derived(enabledClients.map((client) => [client.current.name, createResourceQuery(client, 'getPointCloud', () => options)]));
@@ -72,7 +73,7 @@ export const providePointclouds = (partID) => {
72
73
  }
73
74
  }
74
75
  });
75
- const pcObjects = $state([]);
76
+ let pcObjects = $state.raw([]);
76
77
  $effect(() => {
77
78
  const binaries = [];
78
79
  for (const [name, query] of queries) {
@@ -85,14 +86,16 @@ export const providePointclouds = (partID) => {
85
86
  const { positions, colors } = await parsePcdInWorker(new Uint8Array(uint8array));
86
87
  return { name, positions, colors };
87
88
  })).then((results) => {
89
+ const fulfilledResults = [];
88
90
  for (const result of results) {
89
91
  if (result.status === 'fulfilled') {
90
- untrack(() => pcObjects.push(result.value));
92
+ fulfilledResults.push(result.value);
91
93
  }
92
94
  else if (result.status === 'rejected') {
93
95
  logs.add(result.reason, 'error');
94
96
  }
95
97
  }
98
+ pcObjects = fulfilledResults;
96
99
  });
97
100
  });
98
101
  const entities = new Map();
@@ -101,13 +104,14 @@ export const providePointclouds = (partID) => {
101
104
  for (const { name, positions, colors } of pcObjects) {
102
105
  const existing = entities.get(name);
103
106
  if (existing) {
104
- existing.set(traits.PointsPositions, positions);
105
- if (colors) {
106
- existing.set(traits.VertexColors, colors);
107
+ const geometry = existing.get(traits.BufferGeometry);
108
+ if (geometry) {
109
+ updateBufferGeometry(geometry, positions, colors);
110
+ continue;
107
111
  }
108
- continue;
109
112
  }
110
- const entity = world.spawn(traits.Parent(name), traits.Name(`${name} pointcloud`), traits.PointsPositions(positions), colors ? traits.VertexColors(colors) : traits.Color);
113
+ const geometry = createBufferGeometry(positions, colors);
114
+ const entity = world.spawn(traits.Parent(name), traits.Name(`${name} pointcloud`), traits.BufferGeometry(geometry), traits.Points);
111
115
  entities.set(name, entity);
112
116
  }
113
117
  // Clean up old entities
package/dist/snapshot.js CHANGED
@@ -8,6 +8,7 @@ import { parseMetadata } from './WorldObject.svelte';
8
8
  import { rgbaBytesToFloat32, rgbaToHex } from './color';
9
9
  import { asFloat32Array, STRIDE } from './buffer';
10
10
  import { createPose } from './transform';
11
+ import { createBufferGeometry } from './attribute';
11
12
  const vec3 = new Vector3();
12
13
  const origin = new Vector3();
13
14
  const direction = new Vector3();
@@ -222,10 +223,15 @@ const spawnEntitiesFromDrawing = (world, drawing) => {
222
223
  for (let i = 0, l = positions.length; i < l; i += 1) {
223
224
  positions[i] *= 0.001;
224
225
  }
225
- entityTraits.push(traits.PointsPositions(positions));
226
+ const colors = drawing.metadata?.colors
227
+ ? rgbaBytesToFloat32(drawing.metadata.colors)
228
+ : undefined;
229
+ const geometry = createBufferGeometry(positions, colors);
230
+ entityTraits.push(traits.BufferGeometry(geometry));
226
231
  if (geometryType.value.pointSize) {
227
232
  entityTraits.push(traits.PointSize(geometryType.value.pointSize * 0.001));
228
233
  }
234
+ entityTraits.push(traits.Points);
229
235
  }
230
236
  else if (geometryType?.case === 'nurbs') {
231
237
  const { degree = 3, knots: knotsBuffer, weights: weightsBuffer, controlPoints: controlPointsBuffer, } = geometryType.value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viamrobotics/motion-tools",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "Motion visualization with Viam",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",