@viamrobotics/motion-tools 1.2.2 → 1.3.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 +3 -0
- package/dist/attribute.js +30 -0
- package/dist/components/Entities.svelte +2 -1
- package/dist/components/FileDrop/FileDrop.svelte +9 -4
- package/dist/components/Points.svelte +25 -38
- package/dist/components/SceneProviders.svelte +2 -0
- package/dist/components/Tree/Settings.svelte +22 -3
- package/dist/components/Tree/TreeContainer.svelte +5 -1
- package/dist/ecs/traits.d.ts +4 -1
- package/dist/ecs/traits.js +4 -1
- package/dist/entries.d.ts +1 -0
- package/dist/entries.js +3 -0
- package/dist/hooks/useDrawAPI.svelte.js +12 -18
- package/dist/hooks/useMachineSettings.svelte.d.ts +1 -0
- package/dist/hooks/useMachineSettings.svelte.js +8 -0
- package/dist/hooks/usePointcloudObjects.svelte.d.ts +6 -0
- package/dist/hooks/usePointcloudObjects.svelte.js +174 -0
- package/dist/hooks/usePointclouds.svelte.js +15 -11
- package/dist/hooks/useResizable.svelte.d.ts +1 -1
- package/dist/hooks/useResizable.svelte.js +2 -2
- package/dist/snapshot.js +7 -1
- package/dist/three/arrow.js +2 -2
- package/package.json +1 -1
|
@@ -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.
|
|
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.
|
|
41
|
-
|
|
43
|
+
traits.BufferGeometry(geometry),
|
|
44
|
+
traits.Points,
|
|
42
45
|
traits.DroppedFile
|
|
43
46
|
)
|
|
44
47
|
break
|
|
45
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
})
|
|
69
|
+
const colors = geometry.current?.getAttribute('color')
|
|
70
|
+
const positions = geometry.current?.getAttribute('position')
|
|
81
71
|
|
|
82
|
-
|
|
83
|
-
material.vertexColors = colors.current !== undefined
|
|
72
|
+
material.vertexColors = colors !== undefined
|
|
84
73
|
|
|
85
|
-
if (colors
|
|
86
|
-
const
|
|
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 =
|
|
95
|
-
if (
|
|
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
|
-
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
</
|
|
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}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import { provideFramelessComponents } from '../hooks/useFramelessComponents.svelte'
|
|
21
21
|
import { provideResourceByName } from '../hooks/useResourceByName.svelte'
|
|
22
22
|
import { provide3DModels } from '../hooks/use3DModels.svelte'
|
|
23
|
+
import { providePointcloudObjects } from '../hooks/usePointcloudObjects.svelte'
|
|
23
24
|
|
|
24
25
|
interface Props {
|
|
25
26
|
cameraPose?: CameraPose
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
provideGeometries(() => partID.current)
|
|
45
46
|
provide3DModels(() => partID.current)
|
|
46
47
|
providePointclouds(() => partID.current)
|
|
48
|
+
providePointcloudObjects(() => partID.current)
|
|
47
49
|
provideArmClient(() => partID.current)
|
|
48
50
|
provideWorldStates()
|
|
49
51
|
provideFramelessComponents()
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
const { invalidate } = useThrelte()
|
|
17
17
|
const partID = usePartID()
|
|
18
18
|
const cameras = useResourceNames(() => partID.current, 'camera')
|
|
19
|
+
const visionServices = useResourceNames(() => partID.current, 'vision')
|
|
19
20
|
const settings = useSettings()
|
|
20
|
-
const { disabledCameras } = useMachineSettings()
|
|
21
|
+
const { disabledCameras, disabledVisionServicesObjectPointclouds } = useMachineSettings()
|
|
21
22
|
const geometries = useGeometries()
|
|
22
23
|
const pointclouds = usePointClouds()
|
|
23
24
|
const { refetchPoses } = useRefetchPoses()
|
|
@@ -56,8 +57,9 @@
|
|
|
56
57
|
pointclouds.refetch()
|
|
57
58
|
}}
|
|
58
59
|
/>
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
|
|
61
|
+
<div class="mt-4">
|
|
62
|
+
<h3 class="text-sm"><strong>Enabled pointcloud cameras</strong></h3>
|
|
61
63
|
{#each cameras.current as camera (camera)}
|
|
62
64
|
<div class="flex items-center justify-between gap-4 py-2">
|
|
63
65
|
{camera.name}
|
|
@@ -73,6 +75,23 @@
|
|
|
73
75
|
{/each}
|
|
74
76
|
</div>
|
|
75
77
|
|
|
78
|
+
<div class="mt-4">
|
|
79
|
+
<h3 class="text-sm"><strong>Enabled vision services</strong></h3>
|
|
80
|
+
{#each visionServices.current as visionService (visionService)}
|
|
81
|
+
<div class="flex items-center justify-between gap-4 py-2">
|
|
82
|
+
{visionService.name}
|
|
83
|
+
<Switch
|
|
84
|
+
on={disabledVisionServicesObjectPointclouds.get(visionService.name) !== true}
|
|
85
|
+
on:change={(event) => {
|
|
86
|
+
disabledVisionServicesObjectPointclouds.set(visionService.name, !event.detail)
|
|
87
|
+
}}
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
{:else}
|
|
91
|
+
No vision services detected
|
|
92
|
+
{/each}
|
|
93
|
+
</div>
|
|
94
|
+
|
|
76
95
|
<h3 class="pt-2 text-sm"><strong>Pointclouds</strong></h3>
|
|
77
96
|
<div class="flex flex-col gap-2.5">
|
|
78
97
|
<label class="flex items-center justify-between gap-2">
|
|
@@ -28,7 +28,10 @@
|
|
|
28
28
|
|
|
29
29
|
const partID = usePartID()
|
|
30
30
|
const selectedEntity = useSelectedEntity()
|
|
31
|
-
const resizable = useResizable(
|
|
31
|
+
const resizable = useResizable(
|
|
32
|
+
() => 'treeview',
|
|
33
|
+
() => ({ width: 240, height: window.innerHeight - 20 })
|
|
34
|
+
)
|
|
32
35
|
const environment = useEnvironment()
|
|
33
36
|
const partConfig = usePartConfig()
|
|
34
37
|
const world = useWorld()
|
|
@@ -42,6 +45,7 @@
|
|
|
42
45
|
const flush = () => {
|
|
43
46
|
if (pending) return
|
|
44
47
|
pending = true
|
|
48
|
+
|
|
45
49
|
window.setTimeout(() => {
|
|
46
50
|
const results = buildTreeNodes(world.query(traits.Name))
|
|
47
51
|
children = results.rootNodes
|
package/dist/ecs/traits.d.ts
CHANGED
|
@@ -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>>;
|
package/dist/ecs/traits.js
CHANGED
|
@@ -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());
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const typeSafeObjectFromEntries: <const T extends ReadonlyArray<readonly [PropertyKey, unknown]>>(entries: T) => { [K in T[number] as K[0]]: K[1]; };
|
package/dist/entries.js
ADDED
|
@@ -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
|
-
|
|
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);
|
|
@@ -6,6 +6,7 @@ export declare const RefreshRates: {
|
|
|
6
6
|
type Context = {
|
|
7
7
|
refreshRates: SvelteMap<string, number>;
|
|
8
8
|
disabledCameras: SvelteMap<string, boolean>;
|
|
9
|
+
disabledVisionServicesObjectPointclouds: SvelteMap<string, boolean>;
|
|
9
10
|
};
|
|
10
11
|
export declare const provideMachineSettings: () => void;
|
|
11
12
|
export declare const useMachineSettings: () => Context;
|
|
@@ -4,6 +4,7 @@ import { SvelteMap } from 'svelte/reactivity';
|
|
|
4
4
|
const key = Symbol('polling-rate-context');
|
|
5
5
|
const refreshRatesKey = 'polling-rate';
|
|
6
6
|
const disabledCamerasKey = 'disabled-cameras';
|
|
7
|
+
const disabledVisionServicesObjectPointcloudsKey = 'disabled-vision-services-object-pointcloud';
|
|
7
8
|
export const RefreshRates = {
|
|
8
9
|
poses: 'poses',
|
|
9
10
|
pointclouds: 'pointclouds',
|
|
@@ -21,12 +22,16 @@ export const provideMachineSettings = () => {
|
|
|
21
22
|
[RefreshRates.pointclouds, -1],
|
|
22
23
|
]);
|
|
23
24
|
const disabledCameras = new SvelteMap();
|
|
25
|
+
const disabledVisionServicesObjectPointclouds = new SvelteMap();
|
|
24
26
|
get(refreshRatesKey).then((entries) => {
|
|
25
27
|
setFromEntries(refreshRates, entries);
|
|
26
28
|
});
|
|
27
29
|
get(disabledCamerasKey).then((entries) => {
|
|
28
30
|
setFromEntries(disabledCameras, entries);
|
|
29
31
|
});
|
|
32
|
+
get(disabledVisionServicesObjectPointcloudsKey).then((entries) => {
|
|
33
|
+
setFromEntries(disabledVisionServicesObjectPointclouds, entries);
|
|
34
|
+
});
|
|
30
35
|
$effect(() => {
|
|
31
36
|
set(refreshRatesKey, [...refreshRates.entries()]);
|
|
32
37
|
});
|
|
@@ -40,6 +45,9 @@ export const provideMachineSettings = () => {
|
|
|
40
45
|
get disabledCameras() {
|
|
41
46
|
return disabledCameras;
|
|
42
47
|
},
|
|
48
|
+
get disabledVisionServicesObjectPointclouds() {
|
|
49
|
+
return disabledVisionServicesObjectPointclouds;
|
|
50
|
+
},
|
|
43
51
|
});
|
|
44
52
|
};
|
|
45
53
|
export const useMachineSettings = () => {
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { GeometriesInFrame, PointCloudObject, VisionClient } from '@viamrobotics/sdk';
|
|
2
|
+
import { createResourceClient, createResourceQuery, useResourceNames, } from '@viamrobotics/svelte-sdk';
|
|
3
|
+
import { RefreshRates, useMachineSettings } from './useMachineSettings.svelte';
|
|
4
|
+
import { useLogs } from './useLogs.svelte';
|
|
5
|
+
import { parsePcdInWorker } from '../lib';
|
|
6
|
+
import { getContext, setContext } from 'svelte';
|
|
7
|
+
import { traits, useWorld } from '../ecs';
|
|
8
|
+
import { createBufferGeometry, updateBufferGeometry } from '../attribute';
|
|
9
|
+
import { useEnvironment } from './useEnvironment.svelte';
|
|
10
|
+
import { RefetchRates } from '../components/RefreshRate.svelte';
|
|
11
|
+
import { createPose } from '../transform';
|
|
12
|
+
const key = Symbol('pointcloud-object-context');
|
|
13
|
+
export const providePointcloudObjects = (partID) => {
|
|
14
|
+
const world = useWorld();
|
|
15
|
+
const environment = useEnvironment();
|
|
16
|
+
const { refreshRates, disabledVisionServicesObjectPointclouds } = useMachineSettings();
|
|
17
|
+
const services = useResourceNames(partID, 'vision');
|
|
18
|
+
const clients = $derived(services.current.map((service) => createResourceClient(VisionClient, partID, () => service.name)));
|
|
19
|
+
const propQueries = $derived(clients.map((client) => [
|
|
20
|
+
client.current?.name,
|
|
21
|
+
createResourceQuery(client, 'getProperties', {
|
|
22
|
+
staleTime: Infinity,
|
|
23
|
+
refetchOnMount: false,
|
|
24
|
+
refetchInterval: false,
|
|
25
|
+
}),
|
|
26
|
+
]));
|
|
27
|
+
const fetchedPropQueries = $derived(propQueries.every(([, query]) => query.isPending === false));
|
|
28
|
+
const enabledClients = $derived.by(() => {
|
|
29
|
+
const results = [];
|
|
30
|
+
for (const client of clients) {
|
|
31
|
+
if (environment.current.viewerMode === 'monitor' &&
|
|
32
|
+
fetchedPropQueries &&
|
|
33
|
+
client.current?.name &&
|
|
34
|
+
interval !== RefetchRates.OFF) {
|
|
35
|
+
results.push(client);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return results;
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* Some machines have a lot of vision services, so before enabling all of them
|
|
42
|
+
* we'll first check pointcloud object support.
|
|
43
|
+
*
|
|
44
|
+
* We'll disable cameras that don't support pointclouds,
|
|
45
|
+
* but still allow users to manually enable if they want to.
|
|
46
|
+
*/
|
|
47
|
+
$effect(() => {
|
|
48
|
+
for (const [name, query] of propQueries) {
|
|
49
|
+
if (name && query.data?.objectPointCloudsSupported === false) {
|
|
50
|
+
if (disabledVisionServicesObjectPointclouds.get(name) === undefined) {
|
|
51
|
+
disabledVisionServicesObjectPointclouds.set(name, true);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
const logs = useLogs();
|
|
57
|
+
const interval = $derived(refreshRates.get(RefreshRates.pointclouds));
|
|
58
|
+
const options = $derived({
|
|
59
|
+
enabled: interval !== -1,
|
|
60
|
+
refetchInterval: (interval === 0 ? false : interval),
|
|
61
|
+
});
|
|
62
|
+
const queries = $derived(enabledClients.map((client) => [
|
|
63
|
+
client.current.name,
|
|
64
|
+
createResourceQuery(client, 'getObjectPointClouds', [''], () => options),
|
|
65
|
+
]));
|
|
66
|
+
$effect(() => {
|
|
67
|
+
for (const [name, query] of queries) {
|
|
68
|
+
if (query.isFetching) {
|
|
69
|
+
logs.add(`Fetching pointcloud for ${name}...`);
|
|
70
|
+
}
|
|
71
|
+
else if (query.error) {
|
|
72
|
+
logs.add(`Error fetching pointcloud from ${name}: ${query.error.message}`, 'error');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
let pcResults = $state.raw([]);
|
|
77
|
+
$effect(() => {
|
|
78
|
+
const responses = [];
|
|
79
|
+
for (const [name, query] of queries) {
|
|
80
|
+
const { data } = query;
|
|
81
|
+
if (name && data) {
|
|
82
|
+
responses.push([name, data]);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
Promise.allSettled(responses.map(async ([name, pointcloudObjects]) => {
|
|
86
|
+
const pointclouds = await Promise.all(pointcloudObjects.map((value) => parsePcdInWorker(new Uint8Array(value.pointCloud))));
|
|
87
|
+
return {
|
|
88
|
+
name,
|
|
89
|
+
pointclouds,
|
|
90
|
+
geometries: pointcloudObjects.map((value) => value.geometries),
|
|
91
|
+
};
|
|
92
|
+
})).then((results) => {
|
|
93
|
+
const fulfilledResults = [];
|
|
94
|
+
for (const result of results) {
|
|
95
|
+
if (result.status === 'fulfilled') {
|
|
96
|
+
fulfilledResults.push(result.value);
|
|
97
|
+
}
|
|
98
|
+
else if (result.status === 'rejected') {
|
|
99
|
+
logs.add(result.reason, 'error');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
pcResults = fulfilledResults;
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
const entities = new Map();
|
|
106
|
+
$effect(() => {
|
|
107
|
+
const active = {};
|
|
108
|
+
for (const { name, pointclouds, geometries } of pcResults) {
|
|
109
|
+
for (const [pointcloudIndex, pointcloud] of pointclouds.entries()) {
|
|
110
|
+
const poincloudLabel = `${name} pointcloud ${pointcloudIndex + 1}`;
|
|
111
|
+
const existing = entities.get(poincloudLabel);
|
|
112
|
+
active[poincloudLabel] = true;
|
|
113
|
+
if (existing) {
|
|
114
|
+
const geometry = existing.get(traits.BufferGeometry);
|
|
115
|
+
if (geometry) {
|
|
116
|
+
updateBufferGeometry(geometry, pointcloud.positions, pointcloud.colors);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
const geometry = createBufferGeometry(pointcloud.positions, pointcloud.colors);
|
|
121
|
+
const entity = world.spawn(traits.Name(poincloudLabel), traits.BufferGeometry(geometry), traits.Points);
|
|
122
|
+
entities.set(poincloudLabel, entity);
|
|
123
|
+
}
|
|
124
|
+
if (geometries) {
|
|
125
|
+
for (const geometriesInFrame of geometries) {
|
|
126
|
+
if (geometriesInFrame) {
|
|
127
|
+
for (const [geometryIndex, geometry] of geometriesInFrame.geometries.entries()) {
|
|
128
|
+
const geometryLabel = `${name} pointcloud ${pointcloudIndex} geometry ${geometryIndex + 1}`;
|
|
129
|
+
const pose = createPose(geometry.center);
|
|
130
|
+
active[geometryLabel] = true;
|
|
131
|
+
const existing = entities.get(geometryLabel);
|
|
132
|
+
if (existing) {
|
|
133
|
+
existing.set(traits.Pose, pose);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
const entityTraits = [
|
|
137
|
+
traits.Name(geometryLabel),
|
|
138
|
+
traits.Pose(pose),
|
|
139
|
+
traits.GeometriesAPI,
|
|
140
|
+
traits.Geometry(geometry),
|
|
141
|
+
traits.Opacity(0.2),
|
|
142
|
+
traits.Color({ r: 0, g: 1, b: 0 }),
|
|
143
|
+
];
|
|
144
|
+
if (geometriesInFrame.referenceFrame) {
|
|
145
|
+
entityTraits.push(traits.Parent(geometriesInFrame.referenceFrame));
|
|
146
|
+
}
|
|
147
|
+
const entity = world.spawn(...entityTraits);
|
|
148
|
+
entities.set(geometryLabel, entity);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Clean up old entities
|
|
157
|
+
for (const [label, entity] of entities) {
|
|
158
|
+
if (!active[label]) {
|
|
159
|
+
entity.destroy();
|
|
160
|
+
entities.delete(label);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
setContext(key, {
|
|
165
|
+
refetch() {
|
|
166
|
+
for (const [, query] of queries) {
|
|
167
|
+
query.refetch();
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
export const usePointcloudObjects = () => {
|
|
173
|
+
return getContext(key);
|
|
174
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CameraClient } from '@viamrobotics/sdk';
|
|
2
|
-
import { setContext, getContext
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
105
|
-
if (
|
|
106
|
-
|
|
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
|
|
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
|
|
@@ -8,5 +8,5 @@ interface Context {
|
|
|
8
8
|
observe: (target: HTMLElement) => void;
|
|
9
9
|
}
|
|
10
10
|
export declare const MIN_DIMENSIONS: Dimensions;
|
|
11
|
-
export declare const useResizable: (name: () => string) => Context;
|
|
11
|
+
export declare const useResizable: (name: () => string, defaultDimensions?: () => Dimensions) => Context;
|
|
12
12
|
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { get, set } from 'idb-keyval';
|
|
2
2
|
export const MIN_DIMENSIONS = { width: 240, height: 320 };
|
|
3
|
-
export const useResizable = (name) => {
|
|
3
|
+
export const useResizable = (name, defaultDimensions) => {
|
|
4
4
|
const key = $derived(`${name()}-resizable`);
|
|
5
|
-
let dimensions = $
|
|
5
|
+
let dimensions = $derived(defaultDimensions?.() ?? MIN_DIMENSIONS);
|
|
6
6
|
let loaded = $state(false);
|
|
7
7
|
let observer;
|
|
8
8
|
$effect(() => {
|
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
|
-
|
|
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/dist/three/arrow.js
CHANGED
|
@@ -7,7 +7,7 @@ import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js
|
|
|
7
7
|
*/
|
|
8
8
|
export const createArrowGeometry = () => {
|
|
9
9
|
const length = 0.1;
|
|
10
|
-
const headLength = length * 0.
|
|
10
|
+
const headLength = length * 0.3;
|
|
11
11
|
const headWidth = headLength * 0.3;
|
|
12
12
|
const tailLength = length - headLength;
|
|
13
13
|
const tailWidth = 0.001;
|
|
@@ -15,7 +15,7 @@ export const createArrowGeometry = () => {
|
|
|
15
15
|
const tailGeometry = new BoxGeometry(tailWidth, tailLength, tailWidth);
|
|
16
16
|
tailGeometry.translate(0, tailLength * 0.5, 0);
|
|
17
17
|
// Head: cone centered at origin spanning [-h/2, +h/2] in y
|
|
18
|
-
const radialSegments =
|
|
18
|
+
const radialSegments = 3;
|
|
19
19
|
const headGeo = new ConeGeometry(headWidth * 0.5, headLength, radialSegments, 1, false);
|
|
20
20
|
// Place its center at y = shaftLength + headLength/2 so tip lands at y = shaftLength + headLength
|
|
21
21
|
headGeo.translate(0, tailLength + headLength * 0.5, 0);
|