@viamrobotics/motion-tools 0.18.3 → 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/__tests__/fixtures/MockCanvas.svelte +3 -4
- 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/components/Selected.svelte +8 -1
- package/dist/components/Tree/Logs.svelte +0 -29
- package/dist/components/Tree/Logs.svelte.d.ts +17 -2
- package/dist/components/Tree/Settings.svelte +22 -4
- package/dist/components/Tree/Settings.svelte.d.ts +2 -17
- package/dist/geometry.js +18 -0
- package/dist/hooks/use3DModels.svelte.js +9 -3
- package/dist/hooks/useArmClient.svelte.js +3 -15
- package/dist/hooks/useDrawAPI.svelte.js +96 -46
- package/dist/hooks/useFrames.svelte.d.ts +0 -2
- package/dist/hooks/useFrames.svelte.js +7 -12
- package/dist/hooks/useGeometries.svelte.d.ts +1 -1
- package/dist/hooks/useGeometries.svelte.js +33 -37
- package/dist/hooks/usePointclouds.svelte.d.ts +1 -1
- package/dist/hooks/usePointclouds.svelte.js +74 -45
- package/dist/hooks/usePose.svelte.js +24 -29
- package/dist/hooks/useRefetchPoses.d.ts +8 -0
- package/dist/hooks/useRefetchPoses.js +16 -0
- package/dist/hooks/useResourceByName.svelte.d.ts +1 -1
- package/dist/hooks/useWorldState.svelte.js +7 -8
- 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 +8 -7
|
@@ -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
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { T, useTask } from '@threlte/core'
|
|
2
|
+
import { T, useTask, useThrelte } from '@threlte/core'
|
|
3
3
|
import { useSelectedObject, useSelectedObject3d } from '../hooks/useSelection.svelte'
|
|
4
4
|
import { OBBHelper } from '../three/OBBHelper'
|
|
5
5
|
import { OBB } from 'three/addons/math/OBB.js'
|
|
6
6
|
|
|
7
7
|
const obb = new OBB()
|
|
8
8
|
const obbHelper = new OBBHelper()
|
|
9
|
+
|
|
10
|
+
const { invalidate } = useThrelte()
|
|
9
11
|
const selected = useSelectedObject()
|
|
10
12
|
const selectedObject3d = useSelectedObject3d()
|
|
11
13
|
|
|
@@ -27,6 +29,7 @@
|
|
|
27
29
|
if (selected.current.metadata.batched) {
|
|
28
30
|
selected.current.metadata.getBoundingBoxAt?.(obb)
|
|
29
31
|
obbHelper.setFromOBB(obb)
|
|
32
|
+
invalidate()
|
|
30
33
|
return
|
|
31
34
|
}
|
|
32
35
|
|
|
@@ -34,6 +37,7 @@
|
|
|
34
37
|
selectedObject3d.current?.getWorldPosition(clone.position)
|
|
35
38
|
selectedObject3d.current?.getWorldQuaternion(clone.quaternion)
|
|
36
39
|
obbHelper.setFromObject(clone)
|
|
40
|
+
invalidate()
|
|
37
41
|
}
|
|
38
42
|
},
|
|
39
43
|
{
|
|
@@ -48,8 +52,11 @@
|
|
|
48
52
|
obbHelper.visible = true
|
|
49
53
|
} else {
|
|
50
54
|
stop()
|
|
55
|
+
|
|
51
56
|
obbHelper.visible = false
|
|
52
57
|
}
|
|
58
|
+
|
|
59
|
+
invalidate()
|
|
53
60
|
})
|
|
54
61
|
</script>
|
|
55
62
|
|
|
@@ -1,37 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { useFrames } from '../../hooks/useFrames.svelte'
|
|
3
|
-
import { useGeometries } from '../../hooks/useGeometries.svelte'
|
|
4
2
|
import { useLogs } from '../../hooks/useLogs.svelte'
|
|
5
|
-
import { usePointClouds } from '../../hooks/usePointclouds.svelte'
|
|
6
3
|
import Drawer from './Drawer.svelte'
|
|
7
4
|
|
|
8
|
-
const frames = useFrames()
|
|
9
|
-
const geometries = useGeometries()
|
|
10
|
-
const pointclouds = usePointClouds()
|
|
11
5
|
const logs = useLogs()
|
|
12
|
-
|
|
13
|
-
$effect(() => {
|
|
14
|
-
if (frames.error) {
|
|
15
|
-
const message = `Frames: ${frames.error.message}`
|
|
16
|
-
logs.add(message, 'error')
|
|
17
|
-
}
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
$effect(() => {
|
|
21
|
-
if (geometries.errors.length > 0) {
|
|
22
|
-
for (const error of geometries.errors) {
|
|
23
|
-
logs.add(`Geometries: ${error.message}`, 'error')
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
$effect(() => {
|
|
29
|
-
if (pointclouds.errors.length > 0) {
|
|
30
|
-
for (const error of pointclouds.errors) {
|
|
31
|
-
logs.add(`Pointclouds: ${error.message}`, 'error')
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
})
|
|
35
6
|
</script>
|
|
36
7
|
|
|
37
8
|
<Drawer name="Logs">
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const Logs: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type Logs = InstanceType<typeof Logs>;
|
|
3
18
|
export default Logs;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Select, Switch, Input } from '@viamrobotics/prime-core'
|
|
3
|
-
import { useQueryClient } from '@tanstack/svelte-query'
|
|
4
3
|
import RefreshRate from '../RefreshRate.svelte'
|
|
5
4
|
import { useMotionClient } from '../../hooks/useMotionClient.svelte'
|
|
6
5
|
import Drawer from './Drawer.svelte'
|
|
@@ -10,13 +9,31 @@
|
|
|
10
9
|
import { RefreshRates, useMachineSettings } from '../../hooks/useMachineSettings.svelte'
|
|
11
10
|
import WeblabActive from '../weblab/WeblabActive.svelte'
|
|
12
11
|
import { WEBLABS_EXPERIMENTS } from '../../hooks/useWeblabs.svelte'
|
|
12
|
+
import { useGeometries } from '../../hooks/useGeometries.svelte'
|
|
13
|
+
import { usePointClouds } from '../../hooks/usePointclouds.svelte'
|
|
14
|
+
import { useThrelte } from '@threlte/core'
|
|
15
|
+
import { useRefetchPoses } from '../../hooks/useRefetchPoses'
|
|
13
16
|
|
|
14
|
-
const
|
|
17
|
+
const { invalidate } = useThrelte()
|
|
15
18
|
const partID = usePartID()
|
|
16
19
|
const cameras = useResourceNames(() => partID.current, 'camera')
|
|
17
20
|
const settings = useSettings()
|
|
18
21
|
const { disabledCameras } = useMachineSettings()
|
|
19
22
|
const motionClient = useMotionClient()
|
|
23
|
+
const geometries = useGeometries()
|
|
24
|
+
const pointclouds = usePointClouds()
|
|
25
|
+
|
|
26
|
+
const { refetchPoses } = useRefetchPoses()
|
|
27
|
+
|
|
28
|
+
// Invalidate the renderer for any settings change
|
|
29
|
+
$effect(() => {
|
|
30
|
+
for (const key in settings.current) {
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
32
|
+
settings.current[key as keyof typeof settings.current]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
invalidate()
|
|
36
|
+
})
|
|
20
37
|
</script>
|
|
21
38
|
|
|
22
39
|
<Drawer
|
|
@@ -31,14 +48,15 @@
|
|
|
31
48
|
label="Poses"
|
|
32
49
|
allowLive
|
|
33
50
|
onManualRefetch={() => {
|
|
34
|
-
|
|
51
|
+
refetchPoses()
|
|
52
|
+
geometries.refetch()
|
|
35
53
|
}}
|
|
36
54
|
/>
|
|
37
55
|
<RefreshRate
|
|
38
56
|
id={RefreshRates.pointclouds}
|
|
39
57
|
label="Pointclouds"
|
|
40
58
|
onManualRefetch={() => {
|
|
41
|
-
|
|
59
|
+
pointclouds.refetch()
|
|
42
60
|
}}
|
|
43
61
|
/>
|
|
44
62
|
<div>
|
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
$$bindings?: Bindings;
|
|
4
|
-
} & Exports;
|
|
5
|
-
(internal: unknown, props: {
|
|
6
|
-
$$events?: Events;
|
|
7
|
-
$$slots?: Slots;
|
|
8
|
-
}): Exports & {
|
|
9
|
-
$set?: any;
|
|
10
|
-
$on?: any;
|
|
11
|
-
};
|
|
12
|
-
z_$$bindings?: Bindings;
|
|
13
|
-
}
|
|
14
|
-
declare const Settings: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
-
[evt: string]: CustomEvent<any>;
|
|
16
|
-
}, {}, {}, string>;
|
|
17
|
-
type Settings = InstanceType<typeof Settings>;
|
|
1
|
+
declare const Settings: import("svelte").Component<Record<string, never>, {}, "">;
|
|
2
|
+
type Settings = ReturnType<typeof Settings>;
|
|
18
3
|
export default Settings;
|
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,4 +1,4 @@
|
|
|
1
|
-
import { ArmClient
|
|
1
|
+
import { ArmClient } from '@viamrobotics/sdk';
|
|
2
2
|
import { createResourceClient, createResourceQuery, useResourceNames, } from '@viamrobotics/svelte-sdk';
|
|
3
3
|
import { getContext, setContext } from 'svelte';
|
|
4
4
|
const key = Symbol('arm-client-context');
|
|
@@ -7,20 +7,8 @@ export const provideArmClient = (partID) => {
|
|
|
7
7
|
const options = { refetchInterval: 500 };
|
|
8
8
|
const names = $derived(arms.current.map((arm) => arm.name));
|
|
9
9
|
const clients = $derived(arms.current.map((arm) => createResourceClient(ArmClient, partID, () => arm.name)));
|
|
10
|
-
const jointPositionsQueries = $derived.
|
|
11
|
-
|
|
12
|
-
for (const client of clients) {
|
|
13
|
-
if (!client.current)
|
|
14
|
-
continue;
|
|
15
|
-
const query = createResourceQuery(client, 'getJointPositions', options);
|
|
16
|
-
results[client.current.name] = query;
|
|
17
|
-
}
|
|
18
|
-
return results;
|
|
19
|
-
});
|
|
20
|
-
const currentPositions = $derived(Object.fromEntries(Object.entries(jointPositionsQueries).map(([name, query]) => [
|
|
21
|
-
name,
|
|
22
|
-
query.current.data?.values,
|
|
23
|
-
])));
|
|
10
|
+
const jointPositionsQueries = $derived(clients.map((client) => [client.current?.name, createResourceQuery(client, 'getJointPositions', options)]));
|
|
11
|
+
const currentPositions = $derived(Object.fromEntries(jointPositionsQueries.map(([name, query]) => [name, query.data?.values])));
|
|
24
12
|
setContext(key, {
|
|
25
13
|
get names() {
|
|
26
14
|
return names;
|
|
@@ -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) {
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { WorldObject } from '../WorldObject.svelte';
|
|
2
2
|
interface FramesContext {
|
|
3
3
|
current: WorldObject[];
|
|
4
|
-
error?: Error;
|
|
5
|
-
fetching: boolean;
|
|
6
4
|
getParentFrameOptions: (componentName: string) => string[];
|
|
7
5
|
}
|
|
8
6
|
export declare const provideFrames: (partID: () => string) => void;
|
|
@@ -22,13 +22,16 @@ export const provideFrames = (partID) => {
|
|
|
22
22
|
const { updateUUIDs } = usePersistentUUIDs();
|
|
23
23
|
$effect.pre(() => {
|
|
24
24
|
if (revision) {
|
|
25
|
-
untrack(() => query.
|
|
25
|
+
untrack(() => query.refetch());
|
|
26
26
|
}
|
|
27
27
|
});
|
|
28
|
-
$effect
|
|
29
|
-
if (query.
|
|
28
|
+
$effect(() => {
|
|
29
|
+
if (query.isFetching) {
|
|
30
30
|
logs.add('Fetching frames...');
|
|
31
31
|
}
|
|
32
|
+
else if (query.error) {
|
|
33
|
+
logs.add(`Frames: ${query.error.message}`, 'error');
|
|
34
|
+
}
|
|
32
35
|
});
|
|
33
36
|
$effect.pre(() => {
|
|
34
37
|
if (partConfig.isDirty) {
|
|
@@ -40,7 +43,7 @@ export const provideFrames = (partID) => {
|
|
|
40
43
|
});
|
|
41
44
|
const machineFrames = $derived.by(() => {
|
|
42
45
|
const objects = {};
|
|
43
|
-
for (const { frame } of query.
|
|
46
|
+
for (const { frame } of query.data ?? []) {
|
|
44
47
|
if (frame === undefined) {
|
|
45
48
|
continue;
|
|
46
49
|
}
|
|
@@ -140,8 +143,6 @@ export const provideFrames = (partID) => {
|
|
|
140
143
|
updateUUIDs(results);
|
|
141
144
|
return results;
|
|
142
145
|
});
|
|
143
|
-
const error = $derived(query.current.error ?? undefined);
|
|
144
|
-
const fetching = $derived(query.current.isFetching);
|
|
145
146
|
const getParentFrameOptions = (componentName) => {
|
|
146
147
|
const validFrames = new Set(current.map((frame) => frame.name));
|
|
147
148
|
validFrames.add('world');
|
|
@@ -163,12 +164,6 @@ export const provideFrames = (partID) => {
|
|
|
163
164
|
get current() {
|
|
164
165
|
return current;
|
|
165
166
|
},
|
|
166
|
-
get error() {
|
|
167
|
-
return error;
|
|
168
|
-
},
|
|
169
|
-
get fetching() {
|
|
170
|
-
return fetching;
|
|
171
|
-
},
|
|
172
167
|
});
|
|
173
168
|
};
|
|
174
169
|
export const useFrames = () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { WorldObject } from '../WorldObject.svelte';
|
|
2
2
|
interface Context {
|
|
3
3
|
current: WorldObject[];
|
|
4
|
-
|
|
4
|
+
refetch: () => void;
|
|
5
5
|
}
|
|
6
6
|
export declare const provideGeometries: (partID: () => string) => void;
|
|
7
7
|
export declare const useGeometries: () => Context;
|
|
@@ -1,65 +1,59 @@
|
|
|
1
|
-
import { ArmClient, CameraClient, GantryClient,
|
|
2
|
-
import {
|
|
3
|
-
import { createResourceClient, useResourceNames } from '@viamrobotics/svelte-sdk';
|
|
1
|
+
import { ArmClient, CameraClient, GantryClient, GripperClient } from '@viamrobotics/sdk';
|
|
2
|
+
import { createResourceClient, createResourceQuery, useResourceNames, } from '@viamrobotics/svelte-sdk';
|
|
4
3
|
import { setContext, getContext } from 'svelte';
|
|
5
|
-
import { fromStore, toStore } from 'svelte/store';
|
|
6
4
|
import { useMachineSettings, RefreshRates } from './useMachineSettings.svelte';
|
|
7
5
|
import { WorldObject } from '../WorldObject.svelte';
|
|
8
6
|
import { usePersistentUUIDs } from './usePersistentUUIDs.svelte';
|
|
9
7
|
import { useLogs } from './useLogs.svelte';
|
|
10
8
|
import { resourceColors } from '../color';
|
|
11
9
|
import { Color } from 'three';
|
|
12
|
-
import { useFrames } from './useFrames.svelte';
|
|
13
10
|
import { RefetchRates } from '../components/RefreshRate.svelte';
|
|
11
|
+
import { useResourceByName } from './useResourceByName.svelte';
|
|
14
12
|
const key = Symbol('geometries-context');
|
|
15
13
|
export const provideGeometries = (partID) => {
|
|
16
|
-
const
|
|
17
|
-
const
|
|
14
|
+
const logs = useLogs();
|
|
15
|
+
const resourceByName = useResourceByName();
|
|
18
16
|
const arms = useResourceNames(partID, 'arm');
|
|
19
17
|
const cameras = useResourceNames(partID, 'camera');
|
|
20
18
|
const grippers = useResourceNames(partID, 'gripper');
|
|
21
19
|
const gantries = useResourceNames(partID, 'gantry');
|
|
22
|
-
const logs = useLogs();
|
|
23
20
|
const { refreshRates } = useMachineSettings();
|
|
24
21
|
const armClients = $derived(arms.current.map((arm) => createResourceClient(ArmClient, partID, () => arm.name)));
|
|
25
22
|
const gripperClients = $derived(grippers.current.map((gripper) => createResourceClient(GripperClient, partID, () => gripper.name)));
|
|
26
23
|
const cameraClients = $derived(cameras.current.map((camera) => createResourceClient(CameraClient, partID, () => camera.name)));
|
|
27
24
|
const gantryClients = $derived(gantries.current.map((gantry) => createResourceClient(GantryClient, partID, () => gantry.name)));
|
|
28
|
-
const clients = $derived([...armClients, ...gripperClients, ...cameraClients, ...gantryClients].filter((client) => {
|
|
29
|
-
return frames.current.some((frame) => frame.name === client.current?.name);
|
|
30
|
-
}));
|
|
31
25
|
const options = $derived.by(() => {
|
|
32
26
|
const interval = refreshRates.get(RefreshRates.poses);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
27
|
+
return {
|
|
28
|
+
enabled: refreshRates.get(RefreshRates.poses) !== RefetchRates.OFF,
|
|
29
|
+
refetchInterval: interval === RefetchRates.MANUAL ? false : interval,
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
const armQueries = $derived(armClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
|
|
33
|
+
const gripperQueries = $derived(gripperClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
|
|
34
|
+
const cameraQueries = $derived(cameraClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
|
|
35
|
+
const gantryQueries = $derived(gantryClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
|
|
36
|
+
$effect(() => {
|
|
37
|
+
for (const [name, query] of queries) {
|
|
38
|
+
if (query.isFetching) {
|
|
39
|
+
logs.add(`Fetching geometries for ${name}...`);
|
|
40
|
+
}
|
|
41
|
+
else if (query.error) {
|
|
42
|
+
logs.add(`Error fetching geometries from ${name}: ${query.error.message}`, 'error');
|
|
43
|
+
}
|
|
49
44
|
}
|
|
50
|
-
return results;
|
|
51
45
|
});
|
|
52
46
|
const { updateUUIDs } = usePersistentUUIDs();
|
|
53
|
-
const queries =
|
|
54
|
-
const errors = $derived(queries.current.map((query) => query.error).filter((error) => error !== null));
|
|
47
|
+
const queries = $derived([...armQueries, ...gripperQueries, ...cameraQueries, ...gantryQueries]);
|
|
55
48
|
const geometries = $derived.by(() => {
|
|
56
49
|
const results = [];
|
|
57
|
-
for (const query of queries
|
|
58
|
-
if (!query.data)
|
|
50
|
+
for (const [name, query] of queries) {
|
|
51
|
+
if (!name || !query.data) {
|
|
59
52
|
continue;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
53
|
+
}
|
|
54
|
+
for (const geometry of query.data) {
|
|
55
|
+
const resourceName = resourceByName.current[name];
|
|
56
|
+
const worldObject = new WorldObject(geometry.label ? geometry.label : `${name} geometry`, undefined, name, geometry, resourceName
|
|
63
57
|
? {
|
|
64
58
|
color: new Color(resourceColors[resourceName.subtype]),
|
|
65
59
|
}
|
|
@@ -74,8 +68,10 @@ export const provideGeometries = (partID) => {
|
|
|
74
68
|
get current() {
|
|
75
69
|
return geometries;
|
|
76
70
|
},
|
|
77
|
-
|
|
78
|
-
|
|
71
|
+
refetch() {
|
|
72
|
+
for (const [, query] of queries) {
|
|
73
|
+
query.refetch();
|
|
74
|
+
}
|
|
79
75
|
},
|
|
80
76
|
});
|
|
81
77
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { WorldObject, type PointsGeometry } from '../WorldObject.svelte';
|
|
2
2
|
interface Context {
|
|
3
3
|
current: WorldObject<PointsGeometry>[];
|
|
4
|
-
|
|
4
|
+
refetch: () => void;
|
|
5
5
|
}
|
|
6
6
|
export declare const providePointclouds: (partID: () => string) => void;
|
|
7
7
|
export declare const usePointClouds: () => Context;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { createQueries, queryOptions } from '@tanstack/svelte-query';
|
|
2
1
|
import { CameraClient } from '@viamrobotics/sdk';
|
|
3
2
|
import { setContext, getContext } from 'svelte';
|
|
4
|
-
import {
|
|
5
|
-
import { createResourceClient, useResourceNames } from '@viamrobotics/svelte-sdk';
|
|
3
|
+
import { createResourceClient, createResourceQuery, useResourceNames, } from '@viamrobotics/svelte-sdk';
|
|
6
4
|
import { parsePcdInWorker } from '../loaders/pcd';
|
|
7
5
|
import { RefreshRates, useMachineSettings } from './useMachineSettings.svelte';
|
|
8
6
|
import { WorldObject } from '../WorldObject.svelte';
|
|
@@ -15,54 +13,85 @@ export const providePointclouds = (partID) => {
|
|
|
15
13
|
const { refreshRates, disabledCameras } = useMachineSettings();
|
|
16
14
|
const cameras = useResourceNames(partID, 'camera');
|
|
17
15
|
const clients = $derived(cameras.current.map((camera) => createResourceClient(CameraClient, partID, () => camera.name)));
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
16
|
+
const propQueries = $derived(clients.map((client) => [
|
|
17
|
+
client.current?.name,
|
|
18
|
+
createResourceQuery(client, 'getProperties', {
|
|
19
|
+
staleTime: Infinity,
|
|
20
|
+
refetchOnMount: false,
|
|
21
|
+
refetchInterval: false,
|
|
22
|
+
}),
|
|
23
|
+
]));
|
|
24
|
+
const fetchedPropQueries = propQueries.every(([, query]) => query.isPending === false);
|
|
25
|
+
const interval = $derived(refreshRates.get(RefreshRates.pointclouds));
|
|
26
|
+
const enabledClients = $derived(clients.filter((client) => fetchedPropQueries &&
|
|
27
|
+
client.current?.name &&
|
|
28
|
+
interval !== RefetchRates.OFF &&
|
|
29
|
+
disabledCameras.get(client.current?.name) !== true));
|
|
30
|
+
/**
|
|
31
|
+
* Some machines have a lot of cameras, so before enabling all of them
|
|
32
|
+
* we'll first check pointcloud support.
|
|
33
|
+
*
|
|
34
|
+
* We'll disable cameras that don't support pointclouds,
|
|
35
|
+
* but still allow users to manually enable if they want to.
|
|
36
|
+
*/
|
|
37
|
+
$effect(() => {
|
|
38
|
+
for (const [name, query] of propQueries) {
|
|
39
|
+
if (name && query.data?.supportsPcd === false) {
|
|
40
|
+
if (disabledCameras.get(name) === undefined) {
|
|
41
|
+
disabledCameras.set(name, true);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
42
44
|
}
|
|
43
|
-
return results;
|
|
44
45
|
});
|
|
46
|
+
const queries = $derived(enabledClients.map((client) => [
|
|
47
|
+
client.current?.name,
|
|
48
|
+
createResourceQuery(client, 'getPointCloud', () => ({ refetchInterval: interval })),
|
|
49
|
+
]));
|
|
45
50
|
const { updateUUIDs } = usePersistentUUIDs();
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
let current = $state.raw([]);
|
|
52
|
+
$effect(() => {
|
|
53
|
+
for (const [name, query] of queries) {
|
|
54
|
+
if (query.isFetching) {
|
|
55
|
+
logs.add(`Fetching pointcloud for ${name}...`);
|
|
56
|
+
}
|
|
57
|
+
else if (query.error) {
|
|
58
|
+
logs.add(`Error fetching pointcloud from ${name}: ${query.error.message}`, 'error');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
$effect(() => {
|
|
63
|
+
const binaries = [];
|
|
64
|
+
for (const [name, query] of queries) {
|
|
65
|
+
const { data } = query;
|
|
66
|
+
if (name && data) {
|
|
67
|
+
binaries.push([name, data]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
Promise.allSettled(binaries.map(async ([name, uint8array]) => {
|
|
71
|
+
const { positions, colors } = await parsePcdInWorker(new Uint8Array(uint8array));
|
|
72
|
+
return new WorldObject(`${name}:pointcloud`, undefined, name, { center: undefined, geometryType: { case: 'points', value: positions } }, colors ? { colors } : undefined);
|
|
73
|
+
})).then((results) => {
|
|
74
|
+
const worldObjects = [];
|
|
75
|
+
for (const result of results) {
|
|
76
|
+
if (result.status === 'fulfilled') {
|
|
77
|
+
worldObjects.push(result.value);
|
|
78
|
+
}
|
|
79
|
+
else if (result.status === 'rejected') {
|
|
80
|
+
logs.add(result.reason, 'error');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
updateUUIDs(worldObjects);
|
|
84
|
+
current = worldObjects;
|
|
85
|
+
});
|
|
86
|
+
});
|
|
60
87
|
setContext(key, {
|
|
61
88
|
get current() {
|
|
62
|
-
return
|
|
89
|
+
return current;
|
|
63
90
|
},
|
|
64
|
-
|
|
65
|
-
|
|
91
|
+
refetch() {
|
|
92
|
+
for (const [, query] of queries) {
|
|
93
|
+
query.refetch();
|
|
94
|
+
}
|
|
66
95
|
},
|
|
67
96
|
});
|
|
68
97
|
};
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { createResourceClient,
|
|
1
|
+
import { createResourceClient, createResourceQuery } from '@viamrobotics/svelte-sdk';
|
|
2
2
|
import { usePartID } from './usePartID.svelte';
|
|
3
|
-
import { MotionClient } from '@viamrobotics/sdk';
|
|
4
|
-
import { createQuery, queryOptions } from '@tanstack/svelte-query';
|
|
3
|
+
import { MotionClient, Transform } from '@viamrobotics/sdk';
|
|
5
4
|
import { RefreshRates, useMachineSettings } from './useMachineSettings.svelte';
|
|
6
|
-
import { fromStore, toStore } from 'svelte/store';
|
|
7
5
|
import { useMotionClient } from './useMotionClient.svelte';
|
|
8
6
|
import { useEnvironment } from './useEnvironment.svelte';
|
|
9
7
|
import { observe } from '@threlte/core';
|
|
@@ -11,43 +9,40 @@ import { untrack } from 'svelte';
|
|
|
11
9
|
import { useFrames } from './useFrames.svelte';
|
|
12
10
|
import { RefetchRates } from '../components/RefreshRate.svelte';
|
|
13
11
|
import { useLogs } from './useLogs.svelte';
|
|
12
|
+
import { useResourceByName } from './useResourceByName.svelte';
|
|
13
|
+
import { useRefetchPoses } from './useRefetchPoses';
|
|
14
14
|
export const usePose = (name, parent) => {
|
|
15
15
|
const logs = useLogs();
|
|
16
16
|
const { refreshRates } = useMachineSettings();
|
|
17
17
|
const partID = usePartID();
|
|
18
18
|
const motionClient = useMotionClient();
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
19
|
+
const currentName = $derived(name());
|
|
20
|
+
const currentParent = $derived(parent());
|
|
21
|
+
const resourceByName = useResourceByName();
|
|
22
|
+
const { addQueryToRefetch } = useRefetchPoses();
|
|
23
|
+
const resource = $derived(currentName ? resourceByName.current[currentName] : undefined);
|
|
24
|
+
const parentResource = $derived(currentParent ? resourceByName.current[currentParent] : undefined);
|
|
22
25
|
const environment = useEnvironment();
|
|
23
26
|
const frames = useFrames();
|
|
24
27
|
const client = createResourceClient(MotionClient, () => partID.current, () => motionClient.current ?? '');
|
|
25
28
|
const interval = $derived(refreshRates.get(RefreshRates.poses));
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
environment.current.viewerMode === 'monitor',
|
|
29
|
+
const resolvedParent = $derived(parentResource?.subtype === 'arm' ? `${parent()}_origin` : parent());
|
|
30
|
+
const query = createResourceQuery(client, 'getPose', () => [currentName, resolvedParent ?? 'world', []], () => ({
|
|
31
|
+
enabled: interval !== RefetchRates.OFF,
|
|
30
32
|
refetchInterval: interval === RefetchRates.MANUAL ? false : interval,
|
|
31
|
-
queryKey: ['getPose', 'partID', partID.current, client.current?.name, name(), parent()],
|
|
32
|
-
queryFn: async () => {
|
|
33
|
-
if (!client.current) {
|
|
34
|
-
throw new Error('No client');
|
|
35
|
-
}
|
|
36
|
-
logs.add(`Fetching pose for ${name()}...`);
|
|
37
|
-
const resolvedParent = parentResource?.subtype === 'arm' ? `${parent()}_origin` : parent();
|
|
38
|
-
const pose = await client.current.getPose(name(), resolvedParent ?? 'world', []);
|
|
39
|
-
return pose;
|
|
40
|
-
},
|
|
41
33
|
}));
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (
|
|
45
|
-
|
|
34
|
+
$effect(() => addQueryToRefetch(query));
|
|
35
|
+
$effect(() => {
|
|
36
|
+
if (query.isFetching) {
|
|
37
|
+
logs.add(`Fetching pose for ${currentName}...`);
|
|
38
|
+
}
|
|
39
|
+
else if (query.error) {
|
|
40
|
+
logs.add(`Error fetching pose for ${currentName}: ${query.error.message}`, 'error');
|
|
46
41
|
}
|
|
47
42
|
});
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
|
|
43
|
+
observe.pre(() => [environment.current.viewerMode, frames.current], () => {
|
|
44
|
+
if (environment.current.viewerMode === 'monitor') {
|
|
45
|
+
untrack(() => query.refetch());
|
|
51
46
|
}
|
|
52
47
|
});
|
|
53
48
|
return {
|
|
@@ -59,7 +54,7 @@ export const usePose = (name, parent) => {
|
|
|
59
54
|
if (resource?.subtype === 'arm') {
|
|
60
55
|
return;
|
|
61
56
|
}
|
|
62
|
-
return query.
|
|
57
|
+
return query.data?.pose;
|
|
63
58
|
},
|
|
64
59
|
};
|
|
65
60
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const queries = new Set();
|
|
2
|
+
const addQueryToRefetch = (query) => {
|
|
3
|
+
queries.add(query);
|
|
4
|
+
return () => queries.delete(query);
|
|
5
|
+
};
|
|
6
|
+
const refetchPoses = () => {
|
|
7
|
+
for (const query of queries) {
|
|
8
|
+
query.refetch();
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
export const useRefetchPoses = () => {
|
|
12
|
+
return {
|
|
13
|
+
addQueryToRefetch,
|
|
14
|
+
refetchPoses,
|
|
15
|
+
};
|
|
16
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ResourceName } from '@viamrobotics/sdk';
|
|
2
2
|
interface Context {
|
|
3
|
-
current: Record<string, ResourceName>;
|
|
3
|
+
current: Record<string, ResourceName | undefined>;
|
|
4
4
|
}
|
|
5
5
|
export declare const provideResourceByName: (partID: () => string) => void;
|
|
6
6
|
export declare const useResourceByName: () => Context;
|
|
@@ -39,7 +39,7 @@ const createWorldState = (partID, resourceName) => {
|
|
|
39
39
|
let pendingEvents = [];
|
|
40
40
|
let flushScheduled = false;
|
|
41
41
|
const listUUIDs = createResourceQuery(client, 'listUUIDs');
|
|
42
|
-
const getTransforms = $derived(listUUIDs.
|
|
42
|
+
const getTransforms = $derived(listUUIDs.data?.map((uuid) => {
|
|
43
43
|
return createResourceQuery(client, 'getTransform', () => [uuid], () => ({ refetchInterval: false }));
|
|
44
44
|
}));
|
|
45
45
|
const changeStream = createResourceStream(client, 'streamTransformChanges', {
|
|
@@ -99,10 +99,9 @@ const createWorldState = (partID, resourceName) => {
|
|
|
99
99
|
return;
|
|
100
100
|
if (initialized)
|
|
101
101
|
return;
|
|
102
|
-
|
|
103
|
-
if (queries.some((query) => query?.isLoading))
|
|
102
|
+
if (getTransforms.some((query) => query?.isLoading))
|
|
104
103
|
return;
|
|
105
|
-
const data =
|
|
104
|
+
const data = getTransforms
|
|
106
105
|
.flatMap((query) => query?.data ?? [])
|
|
107
106
|
.filter((transform) => transform !== undefined);
|
|
108
107
|
if (data.length === 0)
|
|
@@ -124,9 +123,9 @@ const createWorldState = (partID, resourceName) => {
|
|
|
124
123
|
};
|
|
125
124
|
});
|
|
126
125
|
$effect.pre(() => {
|
|
127
|
-
if (changeStream
|
|
126
|
+
if (changeStream?.data === undefined)
|
|
128
127
|
return;
|
|
129
|
-
const events = changeStream.
|
|
128
|
+
const events = changeStream.data.filter((event) => event.transform !== undefined);
|
|
130
129
|
if (events.length === 0)
|
|
131
130
|
return;
|
|
132
131
|
worker.postMessage({ type: 'change', events });
|
|
@@ -142,10 +141,10 @@ const createWorldState = (partID, resourceName) => {
|
|
|
142
141
|
return worldObjectsList;
|
|
143
142
|
},
|
|
144
143
|
get listUUIDs() {
|
|
145
|
-
return listUUIDs
|
|
144
|
+
return listUUIDs;
|
|
146
145
|
},
|
|
147
146
|
get getTransforms() {
|
|
148
|
-
return getTransforms
|
|
147
|
+
return getTransforms;
|
|
149
148
|
},
|
|
150
149
|
};
|
|
151
150
|
};
|
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.
|
|
3
|
+
"version": "0.19.1",
|
|
4
4
|
"description": "Motion visualization with Viam",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -22,8 +22,6 @@
|
|
|
22
22
|
"@sveltejs/vite-plugin-svelte": "6.1.4",
|
|
23
23
|
"@tailwindcss/forms": "0.5.10",
|
|
24
24
|
"@tailwindcss/vite": "4.1.13",
|
|
25
|
-
"@tanstack/svelte-query": "5.87.1",
|
|
26
|
-
"@tanstack/svelte-query-devtools": "5.87.3",
|
|
27
25
|
"@testing-library/jest-dom": "6.8.0",
|
|
28
26
|
"@testing-library/svelte": "5.2.8",
|
|
29
27
|
"@thi.ng/paths": "5.2.21",
|
|
@@ -37,8 +35,8 @@
|
|
|
37
35
|
"@typescript-eslint/eslint-plugin": "8.42.0",
|
|
38
36
|
"@typescript-eslint/parser": "8.42.0",
|
|
39
37
|
"@viamrobotics/prime-core": "0.1.5",
|
|
40
|
-
"@viamrobotics/sdk": "0.
|
|
41
|
-
"@viamrobotics/svelte-sdk": "0.
|
|
38
|
+
"@viamrobotics/sdk": "0.56.0",
|
|
39
|
+
"@viamrobotics/svelte-sdk": "1.0.1",
|
|
42
40
|
"@vitejs/plugin-basic-ssl": "2.1.0",
|
|
43
41
|
"@vitest/coverage-v8": "^3.2.4",
|
|
44
42
|
"@zag-js/svelte": "1.22.1",
|
|
@@ -75,7 +73,6 @@
|
|
|
75
73
|
},
|
|
76
74
|
"peerDependencies": {
|
|
77
75
|
"@dimforge/rapier3d-compat": ">=0.17",
|
|
78
|
-
"@tanstack/svelte-query": ">=5",
|
|
79
76
|
"@threlte/core": ">=8",
|
|
80
77
|
"@threlte/extras": ">=9",
|
|
81
78
|
"@threlte/rapier": ">=3",
|
|
@@ -92,7 +89,7 @@
|
|
|
92
89
|
"svelte-virtuallists": ">=1"
|
|
93
90
|
},
|
|
94
91
|
"engines": {
|
|
95
|
-
"node": ">=22.
|
|
92
|
+
"node": ">=22.12.0"
|
|
96
93
|
},
|
|
97
94
|
"svelte": "./dist/index.js",
|
|
98
95
|
"types": "./dist/index.d.ts",
|
|
@@ -119,6 +116,10 @@
|
|
|
119
116
|
"!dist/**/*.test.*",
|
|
120
117
|
"!dist/**/*.spec.*"
|
|
121
118
|
],
|
|
119
|
+
"dependencies": {
|
|
120
|
+
"@tanstack/svelte-query-devtools": "^6.0.2",
|
|
121
|
+
"uuid-tool": "^2.0.3"
|
|
122
|
+
},
|
|
122
123
|
"scripts": {
|
|
123
124
|
"dev": "tsx server/check-bun && bun run server/server.ts",
|
|
124
125
|
"build": "vite build && npm run prepack",
|