@viamrobotics/motion-tools 0.14.12 → 0.15.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/WorldObject.svelte.d.ts +9 -2
- package/dist/color.d.ts +2 -0
- package/dist/color.js +8 -2
- package/dist/components/Details.svelte +3 -2
- package/dist/components/FileDrop.svelte +57 -20
- package/dist/components/Geometry.svelte +6 -1
- package/dist/components/SceneProviders.svelte +3 -1
- package/dist/components/Tree/buildTree.js +4 -1
- package/dist/components/WorldState.svelte +36 -25
- package/dist/geometry.d.ts +2 -0
- package/dist/geometry.js +34 -0
- package/dist/hooks/useDrawAPI.svelte.d.ts +2 -1
- package/dist/hooks/useDrawAPI.svelte.js +7 -10
- package/dist/hooks/useEnvironment.svelte.d.ts +2 -0
- package/dist/hooks/useEnvironment.svelte.js +8 -4
- package/dist/hooks/useFrames.svelte.js +75 -113
- package/dist/hooks/usePose.svelte.js +7 -11
- package/dist/hooks/useResourceByName.svelte.d.ts +7 -0
- package/dist/hooks/useResourceByName.svelte.js +21 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Geometry, PlainMessage, Pose, Struct, TransformWithUUID } from '@viamrobotics/sdk';
|
|
2
|
-
import { BatchedMesh, Color, Object3D, Vector3 } from 'three';
|
|
2
|
+
import { BatchedMesh, Color, Object3D, Vector3, type BufferGeometry } from 'three';
|
|
3
3
|
import type { ValueOf } from 'type-fest';
|
|
4
4
|
import type { OBB } from 'three/addons/math/OBB.js';
|
|
5
5
|
export type PointsGeometry = {
|
|
@@ -16,7 +16,14 @@ export type LinesGeometry = {
|
|
|
16
16
|
value: Float32Array;
|
|
17
17
|
};
|
|
18
18
|
};
|
|
19
|
-
export type
|
|
19
|
+
export type ThreeBufferGeometry = {
|
|
20
|
+
center: undefined;
|
|
21
|
+
geometryType: {
|
|
22
|
+
case: 'bufferGeometry';
|
|
23
|
+
value: BufferGeometry;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export type Geometries = Geometry | PointsGeometry | LinesGeometry | ThreeBufferGeometry;
|
|
20
27
|
export declare const SupportedShapes: {
|
|
21
28
|
readonly points: "points";
|
|
22
29
|
readonly line: "line";
|
package/dist/color.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Color, type ColorRepresentation, type RGB } from 'three';
|
|
2
|
+
import { ResourceName } from '@viamrobotics/sdk';
|
|
2
3
|
/**
|
|
3
4
|
* Darkens a THREE.Color by a given percentage while preserving hue.
|
|
4
5
|
* @param color The original THREE.Color instance.
|
|
@@ -6,6 +7,7 @@ import { Color, type ColorRepresentation, type RGB } from 'three';
|
|
|
6
7
|
* @returns A new THREE.Color instance with the darkened color.
|
|
7
8
|
*/
|
|
8
9
|
export declare const darkenColor: (value: ColorRepresentation, percent: number) => Color;
|
|
10
|
+
export declare const resourceNameToColor: (resourceName?: ResourceName) => Color | undefined;
|
|
9
11
|
export declare const colors: {
|
|
10
12
|
readonly default: string;
|
|
11
13
|
};
|
package/dist/color.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Color } from 'three';
|
|
2
2
|
import twColors from 'tailwindcss/colors';
|
|
3
3
|
import { isNumber } from 'lodash-es';
|
|
4
|
+
import { ResourceName } from '@viamrobotics/sdk';
|
|
4
5
|
// Step 3: linear sRGB → sRGB
|
|
5
6
|
const linearToSrgb = (x) => {
|
|
6
7
|
return x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055;
|
|
@@ -53,6 +54,11 @@ export const darkenColor = (value, percent) => {
|
|
|
53
54
|
return new Color().setHSL(hsl.h, hsl.s, hsl.l);
|
|
54
55
|
};
|
|
55
56
|
const darkness = '600';
|
|
57
|
+
export const resourceNameToColor = (resourceName) => {
|
|
58
|
+
return resourceName
|
|
59
|
+
? new Color(resourceColors[resourceName.subtype])
|
|
60
|
+
: undefined;
|
|
61
|
+
};
|
|
56
62
|
export const colors = {
|
|
57
63
|
default: oklchToHex(twColors.red[darkness]),
|
|
58
64
|
};
|
|
@@ -102,8 +108,8 @@ export const isRGB = (color) => {
|
|
|
102
108
|
};
|
|
103
109
|
export const parseRGB = (color, defaultColor = { r: 0, g: 0, b: 0 }) => {
|
|
104
110
|
if (!isRGB(color))
|
|
105
|
-
return new Color().setRGB(defaultColor.r, defaultColor.g, defaultColor.b);
|
|
106
|
-
return new Color().setRGB(color.r, color.g, color.b);
|
|
111
|
+
return new Color().setRGB(defaultColor.r > 1 ? defaultColor.r / 255 : defaultColor.r, defaultColor.g > 1 ? defaultColor.g / 255 : defaultColor.g, defaultColor.b > 1 ? defaultColor.b / 255 : defaultColor.b);
|
|
112
|
+
return new Color().setRGB(color.r > 1 ? color.r / 255 : color.r, color.g > 1 ? color.g / 255 : color.g, color.b > 1 ? color.b / 255 : color.b);
|
|
107
113
|
};
|
|
108
114
|
export const parseOpacity = (opacity, defaultOpacity = 1) => {
|
|
109
115
|
if (!isNumber(opacity))
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import { FrameConfigUpdater } from '../FrameConfigUpdater.svelte'
|
|
29
29
|
import { useWeblabs } from '../hooks/useWeblabs.svelte'
|
|
30
30
|
import { WEBLABS_EXPERIMENTS } from '../hooks/useWeblabs.svelte'
|
|
31
|
+
import { useEnvironment } from '../hooks/useEnvironment.svelte'
|
|
31
32
|
const { ...rest } = $props()
|
|
32
33
|
|
|
33
34
|
const focused = useFocused()
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
const selectedObject = useSelectedObject()
|
|
39
40
|
const selectedObject3d = useSelectedObject3d()
|
|
40
41
|
const weblab = useWeblabs()
|
|
41
|
-
|
|
42
|
+
const environment = useEnvironment()
|
|
42
43
|
const object = $derived(focusedObject.current ?? selectedObject.current)
|
|
43
44
|
const object3d = $derived(focusedObject3d.current ?? selectedObject3d.current)
|
|
44
45
|
const worldPosition = $state({ x: 0, y: 0, z: 0 })
|
|
@@ -564,7 +565,7 @@
|
|
|
564
565
|
{/if}
|
|
565
566
|
|
|
566
567
|
<WeblabActive experiment={WEBLABS_EXPERIMENTS.MOTION_TOOLS_EDIT_FRAME}>
|
|
567
|
-
{#if showEditFrameOptions}
|
|
568
|
+
{#if showEditFrameOptions && environment.current.isStandalone}
|
|
568
569
|
<Button
|
|
569
570
|
variant="danger"
|
|
570
571
|
class="mt-2 w-full"
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
import { useDrawAPI } from '../hooks/useDrawAPI.svelte'
|
|
3
3
|
import { parsePcdInWorker, WorldObject } from '../lib'
|
|
4
4
|
import { useToast, ToastVariant } from '@viamrobotics/prime-core'
|
|
5
|
+
import { PLYLoader } from 'three/examples/jsm/Addons.js'
|
|
5
6
|
|
|
6
7
|
let { ...rest } = $props()
|
|
7
8
|
|
|
8
|
-
const { addPoints } = useDrawAPI()
|
|
9
|
+
const { addPoints, addMesh } = useDrawAPI()
|
|
9
10
|
|
|
10
11
|
type DropStates = 'inactive' | 'hovering' | 'loading'
|
|
11
12
|
|
|
@@ -31,6 +32,12 @@
|
|
|
31
32
|
|
|
32
33
|
const toast = useToast()
|
|
33
34
|
|
|
35
|
+
const extensions = {
|
|
36
|
+
PCD: 'pcd',
|
|
37
|
+
PLY: 'ply',
|
|
38
|
+
}
|
|
39
|
+
const supportedFiles = [extensions.PCD, extensions.PLY]
|
|
40
|
+
|
|
34
41
|
const ondrop = (event: DragEvent) => {
|
|
35
42
|
event.preventDefault()
|
|
36
43
|
|
|
@@ -45,9 +52,18 @@
|
|
|
45
52
|
for (const file of files) {
|
|
46
53
|
const ext = file.name.split('.').at(-1)
|
|
47
54
|
|
|
48
|
-
if (ext
|
|
55
|
+
if (!ext) {
|
|
56
|
+
toast({
|
|
57
|
+
message: `Could not determine file extension.`,
|
|
58
|
+
variant: ToastVariant.Danger,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!supportedFiles.includes(ext)) {
|
|
49
65
|
toast({
|
|
50
|
-
message: `.${
|
|
66
|
+
message: `Only ${supportedFiles.map((file) => `.${file}`).join(', ')} files are supported.`,
|
|
51
67
|
variant: ToastVariant.Danger,
|
|
52
68
|
})
|
|
53
69
|
|
|
@@ -65,34 +81,55 @@
|
|
|
65
81
|
})
|
|
66
82
|
|
|
67
83
|
reader.addEventListener('error', () => {
|
|
68
|
-
toast({
|
|
84
|
+
toast({
|
|
85
|
+
message: `${file.name} failed to load.`,
|
|
86
|
+
variant: ToastVariant.Danger,
|
|
87
|
+
})
|
|
69
88
|
})
|
|
70
89
|
|
|
71
90
|
reader.addEventListener('load', async (event) => {
|
|
72
91
|
const arrayBuffer = event.target?.result
|
|
73
92
|
|
|
74
93
|
if (!arrayBuffer || typeof arrayBuffer === 'string') {
|
|
94
|
+
toast({
|
|
95
|
+
message: `${file.name} failed to load.`,
|
|
96
|
+
variant: ToastVariant.Danger,
|
|
97
|
+
})
|
|
98
|
+
|
|
75
99
|
return
|
|
76
100
|
}
|
|
77
101
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
102
|
+
if (ext === extensions.PCD) {
|
|
103
|
+
const result = await parsePcdInWorker(new Uint8Array(arrayBuffer))
|
|
104
|
+
|
|
105
|
+
addPoints(
|
|
106
|
+
new WorldObject(
|
|
107
|
+
file.name,
|
|
108
|
+
undefined,
|
|
109
|
+
undefined,
|
|
110
|
+
{
|
|
111
|
+
center: undefined,
|
|
112
|
+
geometryType: {
|
|
113
|
+
case: 'points',
|
|
114
|
+
value: result.positions,
|
|
115
|
+
},
|
|
90
116
|
},
|
|
91
|
-
|
|
92
|
-
|
|
117
|
+
result.colors ? { colors: result.colors } : undefined
|
|
118
|
+
)
|
|
93
119
|
)
|
|
94
|
-
|
|
95
|
-
|
|
120
|
+
|
|
121
|
+
toast({ message: `Loaded ${file.name}`, variant: ToastVariant.Success })
|
|
122
|
+
} else if (ext === extensions.PLY) {
|
|
123
|
+
const result = new PLYLoader().parse(arrayBuffer)
|
|
124
|
+
const worldObject = new WorldObject(file.name, undefined, undefined, {
|
|
125
|
+
center: undefined,
|
|
126
|
+
geometryType: { case: 'bufferGeometry', value: result },
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
addMesh(worldObject)
|
|
130
|
+
|
|
131
|
+
toast({ message: `Loaded ${file.name}`, variant: ToastVariant.Success })
|
|
132
|
+
}
|
|
96
133
|
})
|
|
97
134
|
|
|
98
135
|
reader.readAsArrayBuffer(file)
|
|
@@ -103,7 +103,12 @@
|
|
|
103
103
|
{uuid}
|
|
104
104
|
bvh={{ enabled: false }}
|
|
105
105
|
>
|
|
106
|
-
{#if geometry.geometryType.case === '
|
|
106
|
+
{#if geometry.geometryType.case === 'bufferGeometry'}
|
|
107
|
+
<T
|
|
108
|
+
is={geometry.geometryType.value}
|
|
109
|
+
{oncreate}
|
|
110
|
+
/>
|
|
111
|
+
{:else if geometry.geometryType.case === 'mesh'}
|
|
107
112
|
{@const mesh = geometry.geometryType.value.mesh}
|
|
108
113
|
{@const meshGeometry = parsePlyInput(mesh)}
|
|
109
114
|
<T
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { provideArmClient } from '../hooks/useArmClient.svelte'
|
|
19
19
|
import { provideArrows } from '../hooks/useArrows.svelte'
|
|
20
20
|
import { provideFramelessComponents } from '../hooks/useFramelessComponents.svelte'
|
|
21
|
+
import { provideResourceByName } from '../hooks/useResourceByName.svelte'
|
|
21
22
|
interface Props {
|
|
22
23
|
children: Snippet<[{ focus: boolean }]>
|
|
23
24
|
}
|
|
@@ -36,13 +37,14 @@
|
|
|
36
37
|
provideStaticGeometries()
|
|
37
38
|
provideDrawAPI()
|
|
38
39
|
|
|
40
|
+
provideResourceByName(() => partID.current)
|
|
39
41
|
provideFrames(() => partID.current)
|
|
40
42
|
provideGeometries(() => partID.current)
|
|
41
43
|
providePointclouds(() => partID.current)
|
|
42
44
|
provideMotionClient(() => partID.current)
|
|
45
|
+
provideArmClient(() => partID.current)
|
|
43
46
|
provideObjects()
|
|
44
47
|
provideWorldStates()
|
|
45
|
-
provideArmClient(() => partID.current)
|
|
46
48
|
provideFramelessComponents()
|
|
47
49
|
|
|
48
50
|
const { focus } = provideSelection()
|
|
@@ -39,8 +39,11 @@ export const buildTreeNodes = (objects, worldStates) => {
|
|
|
39
39
|
children: [],
|
|
40
40
|
href: `/world-state/${worldState.name}/${object.name}`,
|
|
41
41
|
};
|
|
42
|
+
const parentNode = object.referenceFrame && nodeMap.has(object.referenceFrame)
|
|
43
|
+
? nodeMap.get(object.referenceFrame)
|
|
44
|
+
: node;
|
|
42
45
|
nodeMap.set(object.name, child);
|
|
43
|
-
|
|
46
|
+
parentNode.children?.push(child);
|
|
44
47
|
}
|
|
45
48
|
nodeMap.set(worldState.name, node);
|
|
46
49
|
rootNodes.push(node);
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
import Portal from './portal/Portal.svelte'
|
|
7
7
|
import PortalTarget from './portal/PortalTarget.svelte'
|
|
8
8
|
import { WorldObject } from '../WorldObject.svelte'
|
|
9
|
-
import { useArrows } from '../hooks/useArrows.svelte'
|
|
10
9
|
import { poseToDirection } from '../transform'
|
|
10
|
+
import { BatchedArrow } from '../three/BatchedArrow'
|
|
11
|
+
import { T } from '@threlte/core'
|
|
11
12
|
|
|
12
13
|
interface Props {
|
|
13
14
|
worldObjects: WorldObject[]
|
|
@@ -15,38 +16,36 @@
|
|
|
15
16
|
|
|
16
17
|
let { worldObjects }: Props = $props()
|
|
17
18
|
|
|
18
|
-
const batchedArrow = useArrows()
|
|
19
19
|
const currentArrows: Record<string, { id: number; arrow: WorldObject }> = {}
|
|
20
|
+
const arrowBatches = $state<Record<string, BatchedArrow>>({})
|
|
20
21
|
|
|
21
22
|
const arrows = $derived(worldObjects.filter((object) => object.metadata?.shape === 'arrow'))
|
|
22
23
|
const objects = $derived(worldObjects.filter((object) => object.metadata?.shape !== 'arrow'))
|
|
23
24
|
|
|
24
25
|
const getArrows = () => ({ ...currentArrows })
|
|
25
|
-
const getArrow = (uuid: string) =>
|
|
26
|
-
|
|
26
|
+
const getArrow = (referenceFrame: string, uuid: string) =>
|
|
27
|
+
currentArrows[`${referenceFrame}:${uuid}`]
|
|
28
|
+
|
|
29
|
+
const removeArrow = (referenceFrame: string, uuid: string) => {
|
|
30
|
+
delete currentArrows[`${referenceFrame}:${uuid}`]
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
const setArrow = (arrow: WorldObject) => {
|
|
28
|
-
const
|
|
34
|
+
const referenceFrame = arrow.referenceFrame ?? 'world'
|
|
35
|
+
const currentArrow = getArrow(referenceFrame, arrow.uuid)
|
|
29
36
|
const color = arrow.metadata?.color ?? new Color('yellow')
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
0.1,
|
|
36
|
-
color,
|
|
37
|
-
true
|
|
38
|
-
)
|
|
37
|
+
const direction = poseToDirection(arrow.pose)
|
|
38
|
+
const position = new Vector3(arrow.pose.x, arrow.pose.y, arrow.pose.z)
|
|
39
|
+
|
|
40
|
+
arrowBatches[referenceFrame] ??= new BatchedArrow()
|
|
41
|
+
const batchedArrow = arrowBatches[referenceFrame]
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
if (currentArrow) {
|
|
44
|
+
batchedArrow.updateArrow(currentArrow.id, direction, position, 0.1, color, true)
|
|
45
|
+
currentArrows[`${referenceFrame}:${arrow.uuid}`] = { id: currentArrow.id, arrow }
|
|
41
46
|
} else {
|
|
42
|
-
const id = batchedArrow.addArrow(
|
|
43
|
-
|
|
44
|
-
new Vector3(arrow.pose.x, arrow.pose.y, arrow.pose.z),
|
|
45
|
-
0.1,
|
|
46
|
-
color,
|
|
47
|
-
true
|
|
48
|
-
)
|
|
49
|
-
currentArrows[arrow.uuid] = { id, arrow }
|
|
47
|
+
const id = batchedArrow.addArrow(direction, position, 0.1, color, true)
|
|
48
|
+
currentArrows[`${referenceFrame}:${arrow.uuid}`] = { id, arrow }
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
|
|
@@ -58,12 +57,24 @@
|
|
|
58
57
|
})
|
|
59
58
|
|
|
60
59
|
Object.values(toRemove).forEach(({ id, arrow }) => {
|
|
61
|
-
|
|
62
|
-
removeArrow(
|
|
60
|
+
const referenceFrame = arrow.referenceFrame ?? 'world'
|
|
61
|
+
arrowBatches[referenceFrame].removeArrow(id)
|
|
62
|
+
removeArrow(referenceFrame, arrow.uuid)
|
|
63
63
|
})
|
|
64
64
|
})
|
|
65
65
|
</script>
|
|
66
66
|
|
|
67
|
+
{#each Object.entries(arrowBatches) as [referenceFrame, batch] (referenceFrame)}
|
|
68
|
+
<Portal id={referenceFrame}>
|
|
69
|
+
<T
|
|
70
|
+
name={batch.object3d.name}
|
|
71
|
+
is={batch.object3d}
|
|
72
|
+
dispose={false}
|
|
73
|
+
bvh={{ enabled: false }}
|
|
74
|
+
/>
|
|
75
|
+
</Portal>
|
|
76
|
+
{/each}
|
|
77
|
+
|
|
67
78
|
{#each objects as object (object.uuid)}
|
|
68
79
|
<Portal id={object.referenceFrame}>
|
|
69
80
|
<Frame
|
package/dist/geometry.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import type { Geometry } from '@viamrobotics/sdk';
|
|
2
|
+
import type { Frame } from './frame';
|
|
2
3
|
export declare const createGeometry: (geometryType?: Geometry["geometryType"], label?: string) => Geometry;
|
|
4
|
+
export declare const createGeometryFromFrame: (frame: Frame) => import("@viamrobotics/sdk").PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Geometry> | undefined;
|
package/dist/geometry.js
CHANGED
|
@@ -6,3 +6,37 @@ export const createGeometry = (geometryType, label = '') => {
|
|
|
6
6
|
geometryType: geometryType ?? { case: undefined, value: undefined },
|
|
7
7
|
};
|
|
8
8
|
};
|
|
9
|
+
export const createGeometryFromFrame = (frame) => {
|
|
10
|
+
if (!frame.geometry) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (frame.geometry.type === 'box') {
|
|
14
|
+
return createGeometry({
|
|
15
|
+
case: 'box',
|
|
16
|
+
value: {
|
|
17
|
+
dimsMm: {
|
|
18
|
+
x: frame.geometry.x,
|
|
19
|
+
y: frame.geometry.y,
|
|
20
|
+
z: frame.geometry.z,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (frame.geometry.type === 'sphere') {
|
|
26
|
+
return createGeometry({
|
|
27
|
+
case: 'sphere',
|
|
28
|
+
value: {
|
|
29
|
+
radiusMm: frame.geometry.r,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (frame.geometry.type === 'capsule') {
|
|
34
|
+
return createGeometry({
|
|
35
|
+
case: 'capsule',
|
|
36
|
+
value: {
|
|
37
|
+
radiusMm: frame.geometry.r,
|
|
38
|
+
lengthMm: frame.geometry.l,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
};
|
|
@@ -2,7 +2,6 @@ import { Vector3 } from 'three';
|
|
|
2
2
|
import { WorldObject, type PointsGeometry } from '../WorldObject.svelte';
|
|
3
3
|
type ConnectionStatus = 'connecting' | 'open' | 'closed';
|
|
4
4
|
interface Context {
|
|
5
|
-
addPoints(worldObject: WorldObject<PointsGeometry>): void;
|
|
6
5
|
points: WorldObject<PointsGeometry>[];
|
|
7
6
|
frames: WorldObject[];
|
|
8
7
|
lines: WorldObject[];
|
|
@@ -16,6 +15,8 @@ interface Context {
|
|
|
16
15
|
lookAt: Vector3;
|
|
17
16
|
animate: boolean;
|
|
18
17
|
} | undefined;
|
|
18
|
+
addPoints(worldObject: WorldObject<PointsGeometry>): void;
|
|
19
|
+
addMesh(worldObject: WorldObject): void;
|
|
19
20
|
clearCamera: () => void;
|
|
20
21
|
}
|
|
21
22
|
export declare const provideDrawAPI: () => void;
|
|
@@ -112,13 +112,7 @@ export const provideDrawAPI = () => {
|
|
|
112
112
|
result.pose = data.center;
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
115
|
-
const geometry =
|
|
116
|
-
label: data.label,
|
|
117
|
-
center: undefined,
|
|
118
|
-
geometryType: {
|
|
119
|
-
case: undefined,
|
|
120
|
-
},
|
|
121
|
-
};
|
|
115
|
+
const geometry = createGeometry();
|
|
122
116
|
if ('mesh' in data) {
|
|
123
117
|
geometry.geometryType.case = 'mesh';
|
|
124
118
|
geometry.geometryType.value = data.mesh;
|
|
@@ -443,9 +437,6 @@ export const provideDrawAPI = () => {
|
|
|
443
437
|
get points() {
|
|
444
438
|
return points;
|
|
445
439
|
},
|
|
446
|
-
addPoints(worldObject) {
|
|
447
|
-
points.push(worldObject);
|
|
448
|
-
},
|
|
449
440
|
get lines() {
|
|
450
441
|
return lines;
|
|
451
442
|
},
|
|
@@ -467,6 +458,12 @@ export const provideDrawAPI = () => {
|
|
|
467
458
|
get camera() {
|
|
468
459
|
return camera;
|
|
469
460
|
},
|
|
461
|
+
addPoints(worldObject) {
|
|
462
|
+
points.push(worldObject);
|
|
463
|
+
},
|
|
464
|
+
addMesh(worldObject) {
|
|
465
|
+
meshes.push(worldObject);
|
|
466
|
+
},
|
|
470
467
|
clearCamera: () => {
|
|
471
468
|
camera = undefined;
|
|
472
469
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const ENVIRONMENT_CONTEXT_KEY: unique symbol;
|
|
1
2
|
interface Environemnt {
|
|
2
3
|
viewerMode: 'edit' | 'monitor';
|
|
3
4
|
isStandalone: boolean;
|
|
@@ -5,6 +6,7 @@ interface Environemnt {
|
|
|
5
6
|
interface Context {
|
|
6
7
|
current: Environemnt;
|
|
7
8
|
}
|
|
9
|
+
export declare const createEnvironment: () => Context;
|
|
8
10
|
export declare const provideEnvironment: () => Context;
|
|
9
11
|
export declare const useEnvironment: () => Context;
|
|
10
12
|
export {};
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { getContext, setContext } from 'svelte';
|
|
2
|
-
const
|
|
2
|
+
export const ENVIRONMENT_CONTEXT_KEY = Symbol('environment');
|
|
3
3
|
const defaults = () => ({
|
|
4
4
|
viewerMode: 'monitor',
|
|
5
5
|
isStandalone: true,
|
|
6
6
|
});
|
|
7
|
-
export const
|
|
7
|
+
export const createEnvironment = () => {
|
|
8
8
|
const environment = $state(defaults());
|
|
9
9
|
const context = {
|
|
10
10
|
get current() {
|
|
11
11
|
return environment;
|
|
12
12
|
},
|
|
13
13
|
};
|
|
14
|
-
|
|
14
|
+
return context;
|
|
15
|
+
};
|
|
16
|
+
export const provideEnvironment = () => {
|
|
17
|
+
const context = createEnvironment();
|
|
18
|
+
setContext(ENVIRONMENT_CONTEXT_KEY, context);
|
|
15
19
|
return context;
|
|
16
20
|
};
|
|
17
21
|
export const useEnvironment = () => {
|
|
18
|
-
return getContext(
|
|
22
|
+
return getContext(ENVIRONMENT_CONTEXT_KEY);
|
|
19
23
|
};
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { getContext, setContext, untrack } from 'svelte';
|
|
2
|
-
import { useRobotClient, createRobotQuery, useMachineStatus
|
|
2
|
+
import { useRobotClient, createRobotQuery, useMachineStatus } from '@viamrobotics/svelte-sdk';
|
|
3
3
|
import { WorldObject } from '../WorldObject.svelte';
|
|
4
4
|
import { useLogs } from './useLogs.svelte';
|
|
5
|
-
import {
|
|
5
|
+
import { resourceNameToColor } from '../color';
|
|
6
6
|
import { usePartConfig } from './usePartConfig.svelte';
|
|
7
|
-
import { Color } from 'three';
|
|
8
7
|
import { useEnvironment } from './useEnvironment.svelte';
|
|
9
8
|
import { createPoseFromFrame } from '../transform';
|
|
10
|
-
import {
|
|
9
|
+
import { usePersistentUUIDs } from './usePersistentUUIDs.svelte';
|
|
10
|
+
import { createGeometryFromFrame } from '../geometry';
|
|
11
|
+
import { useResourceByName } from './useResourceByName.svelte';
|
|
11
12
|
const key = Symbol('frames-context');
|
|
12
13
|
export const provideFrames = (partID) => {
|
|
13
|
-
const
|
|
14
|
+
const resourceByName = useResourceByName();
|
|
14
15
|
const client = useRobotClient(partID);
|
|
15
16
|
const machineStatus = useMachineStatus(partID);
|
|
16
17
|
const logs = useLogs();
|
|
@@ -18,8 +19,11 @@ export const provideFrames = (partID) => {
|
|
|
18
19
|
const revision = $derived(machineStatus.current?.config?.revision);
|
|
19
20
|
const partConfig = usePartConfig();
|
|
20
21
|
const environment = useEnvironment();
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
const { updateUUIDs } = usePersistentUUIDs();
|
|
23
|
+
$effect.pre(() => {
|
|
24
|
+
if (revision) {
|
|
25
|
+
untrack(() => query.current).refetch();
|
|
26
|
+
}
|
|
23
27
|
logs.add('Fetching frames...');
|
|
24
28
|
});
|
|
25
29
|
$effect.pre(() => {
|
|
@@ -30,109 +34,41 @@ export const provideFrames = (partID) => {
|
|
|
30
34
|
environment.current.viewerMode = 'monitor';
|
|
31
35
|
}
|
|
32
36
|
});
|
|
33
|
-
|
|
34
|
-
const objects =
|
|
37
|
+
const machineFrames = $derived.by(() => {
|
|
38
|
+
const objects = {};
|
|
35
39
|
for (const { frame } of query.current.data ?? []) {
|
|
36
40
|
if (frame === undefined) {
|
|
37
41
|
continue;
|
|
38
42
|
}
|
|
39
|
-
const resourceName =
|
|
43
|
+
const resourceName = resourceByName.current[frame.referenceFrame];
|
|
40
44
|
const frameName = frame.referenceFrame ? frame.referenceFrame : 'Unnamed frame';
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
color: new Color(resourceColors[resourceName.subtype]),
|
|
44
|
-
}
|
|
45
|
-
: undefined));
|
|
45
|
+
const color = resourceNameToColor(resourceName);
|
|
46
|
+
objects[frameName] = new WorldObject(frameName, frame.poseInObserverFrame?.pose, frame.poseInObserverFrame?.referenceFrame, frame.physicalObject, color ? { color } : undefined);
|
|
46
47
|
}
|
|
47
48
|
return objects;
|
|
48
49
|
});
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const setWorldObject = (component, worldObject) => {
|
|
53
|
-
if (!component.frame) {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
worldObject.referenceFrame = component.frame.parent;
|
|
57
|
-
worldObject.localEditedPose = createPoseFromFrame(component.frame);
|
|
58
|
-
if (component.frame.geometry) {
|
|
59
|
-
switch (component.frame.geometry.type) {
|
|
60
|
-
case 'box':
|
|
61
|
-
worldObject.geometry = {
|
|
62
|
-
...worldObject.geometry,
|
|
63
|
-
geometryType: {
|
|
64
|
-
case: 'box',
|
|
65
|
-
value: {
|
|
66
|
-
dimsMm: {
|
|
67
|
-
x: component.frame.geometry.x,
|
|
68
|
-
y: component.frame.geometry.y,
|
|
69
|
-
z: component.frame.geometry.z,
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
break;
|
|
75
|
-
case 'sphere':
|
|
76
|
-
worldObject.geometry = {
|
|
77
|
-
...worldObject.geometry,
|
|
78
|
-
geometryType: { case: 'sphere', value: { radiusMm: component.frame.geometry.r } },
|
|
79
|
-
};
|
|
80
|
-
break;
|
|
81
|
-
case 'capsule':
|
|
82
|
-
worldObject.geometry = {
|
|
83
|
-
...worldObject.geometry,
|
|
84
|
-
geometryType: {
|
|
85
|
-
case: 'capsule',
|
|
86
|
-
value: {
|
|
87
|
-
radiusMm: component.frame.geometry.r,
|
|
88
|
-
lengthMm: component.frame.geometry.l,
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
break;
|
|
93
|
-
default:
|
|
94
|
-
worldObject.geometry = undefined;
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
worldObject.geometry = undefined;
|
|
100
|
-
}
|
|
101
|
-
currentWorldObjects[component.name] = worldObject;
|
|
102
|
-
};
|
|
103
|
-
const deleteWorldObject = (componentName) => {
|
|
104
|
-
delete currentWorldObjects[componentName];
|
|
105
|
-
};
|
|
106
|
-
$effect.pre(() => {
|
|
107
|
-
untrack(() => {
|
|
108
|
-
currentWorldObjects = {};
|
|
109
|
-
for (const currentWorldObject of current) {
|
|
110
|
-
currentWorldObjects[currentWorldObject.name] = currentWorldObject;
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
const components = partConfig.localPartConfig.toJson()?.components;
|
|
114
|
-
const fragmentMods = partConfig.localPartConfig.toJson()
|
|
115
|
-
?.fragment_mods;
|
|
116
|
-
const fragmentDefinedComponents = Object.keys(partConfig.componentNameToFragmentId);
|
|
50
|
+
const configFrames = $derived.by(() => {
|
|
51
|
+
const components = partConfig.localPartConfig.toJson().components;
|
|
52
|
+
const objects = [];
|
|
117
53
|
// deal with part defined frame config
|
|
118
|
-
for (const component of components
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
setWorldObject(component, worldObject);
|
|
122
|
-
}
|
|
123
|
-
else if (component.frame && Object.keys(getWorldObjects()).length > 0) {
|
|
124
|
-
// extra clause to prevent adding a component to the world objects when it may be loaded via frame system config later (first tick issue where config updated but current world objects not triggered yet)
|
|
125
|
-
const pose = createPoseFromFrame(component.frame);
|
|
126
|
-
const newWorldObject = new WorldObject(component.name, pose, component.frame.parent);
|
|
127
|
-
setWorldObject(component, newWorldObject);
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
deleteWorldObject(component.name);
|
|
54
|
+
for (const component of components ?? []) {
|
|
55
|
+
if (!component.frame) {
|
|
56
|
+
continue;
|
|
131
57
|
}
|
|
58
|
+
const pose = createPoseFromFrame(component.frame);
|
|
59
|
+
const geometry = createGeometryFromFrame(component.frame);
|
|
60
|
+
const worldObject = new WorldObject(component.name, pose, component.frame.parent, geometry);
|
|
61
|
+
objects.push(worldObject);
|
|
132
62
|
}
|
|
63
|
+
return objects;
|
|
64
|
+
});
|
|
65
|
+
const [fragmentFrames, fragmentUnsetFrames] = $derived.by(() => {
|
|
66
|
+
const { fragment_mods: fragmentMods = [] } = partConfig.localPartConfig.toJson() ?? {};
|
|
67
|
+
const fragmentDefinedComponents = Object.keys(partConfig.componentNameToFragmentId);
|
|
68
|
+
const objects = [];
|
|
69
|
+
const unsetObjects = [];
|
|
133
70
|
// deal with fragment defined components
|
|
134
71
|
for (const fragmentComponentName of fragmentDefinedComponents || []) {
|
|
135
|
-
const worldObject = getWorldObject(fragmentComponentName);
|
|
136
72
|
const fragmentId = partConfig.componentNameToFragmentId[fragmentComponentName];
|
|
137
73
|
const fragmentMod = fragmentMods?.find((mod) => mod.fragment_id === fragmentId);
|
|
138
74
|
if (!fragmentMod) {
|
|
@@ -141,27 +77,53 @@ export const provideFrames = (partID) => {
|
|
|
141
77
|
const setComponentModIndex = fragmentMod.mods.findLastIndex((mod) => mod['$set']?.[`components.${fragmentComponentName}.frame`] !== undefined);
|
|
142
78
|
const unsetComponentModIndex = fragmentMod.mods.findLastIndex((mod) => mod['$unset']?.[`components.${fragmentComponentName}.frame`] !== undefined);
|
|
143
79
|
if (setComponentModIndex < unsetComponentModIndex) {
|
|
144
|
-
|
|
80
|
+
unsetObjects.push(fragmentComponentName);
|
|
145
81
|
}
|
|
146
82
|
else if (unsetComponentModIndex < setComponentModIndex) {
|
|
147
83
|
const frameData = fragmentMod.mods[setComponentModIndex]['$set'][`components.${fragmentComponentName}.frame`];
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
84
|
+
const pose = createPoseFromFrame(frameData);
|
|
85
|
+
const geometry = createGeometryFromFrame(frameData);
|
|
86
|
+
const worldObject = new WorldObject(fragmentComponentName, pose, frameData.parent, geometry);
|
|
87
|
+
objects.push(worldObject);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return [objects, unsetObjects];
|
|
91
|
+
});
|
|
92
|
+
$effect.pre(() => {
|
|
93
|
+
for (const frame of configFrames) {
|
|
94
|
+
const result = machineFrames[frame.name];
|
|
95
|
+
if (result) {
|
|
96
|
+
result.referenceFrame = frame.referenceFrame;
|
|
97
|
+
result.pose = frame.pose;
|
|
98
|
+
result.geometry = frame.geometry;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
machineFrames[frame.name] = frame;
|
|
160
102
|
}
|
|
161
103
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
104
|
+
});
|
|
105
|
+
$effect.pre(() => {
|
|
106
|
+
for (const frame of fragmentFrames) {
|
|
107
|
+
const result = machineFrames[frame.name];
|
|
108
|
+
if (result) {
|
|
109
|
+
result.referenceFrame = frame.referenceFrame;
|
|
110
|
+
result.pose = frame.pose;
|
|
111
|
+
result.geometry = frame.geometry;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
machineFrames[frame.name] = frame;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
$effect.pre(() => {
|
|
119
|
+
for (const name of fragmentUnsetFrames) {
|
|
120
|
+
delete machineFrames[name];
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
const current = $derived.by(() => {
|
|
124
|
+
const results = Object.values(machineFrames);
|
|
125
|
+
updateUUIDs(results);
|
|
126
|
+
return results;
|
|
165
127
|
});
|
|
166
128
|
const error = $derived(query.current.error ?? undefined);
|
|
167
129
|
const fetching = $derived(query.current.isFetching);
|
|
@@ -21,23 +21,15 @@ export const usePose = (name, parent) => {
|
|
|
21
21
|
const options = $derived(queryOptions({
|
|
22
22
|
enabled: interval !== -1 &&
|
|
23
23
|
client.current !== undefined &&
|
|
24
|
-
resource !== undefined &&
|
|
25
24
|
environment.current.viewerMode === 'monitor',
|
|
26
25
|
refetchInterval: interval === 0 ? false : interval,
|
|
27
|
-
queryKey: [
|
|
28
|
-
'partID',
|
|
29
|
-
partID.current,
|
|
30
|
-
client.current?.name,
|
|
31
|
-
'getPose',
|
|
32
|
-
resource?.name,
|
|
33
|
-
parent(),
|
|
34
|
-
],
|
|
26
|
+
queryKey: ['partID', partID.current, client.current?.name, 'getPose', name(), parent()],
|
|
35
27
|
queryFn: async () => {
|
|
36
|
-
if (!client.current
|
|
28
|
+
if (!client.current) {
|
|
37
29
|
throw new Error('No client');
|
|
38
30
|
}
|
|
39
31
|
const resolvedParent = parentResource?.subtype === 'arm' ? `${parent()}_origin` : parent();
|
|
40
|
-
const pose = await client.current.getPose(
|
|
32
|
+
const pose = await client.current.getPose(name(), resolvedParent ?? 'world', []);
|
|
41
33
|
return pose;
|
|
42
34
|
},
|
|
43
35
|
}));
|
|
@@ -49,6 +41,10 @@ export const usePose = (name, parent) => {
|
|
|
49
41
|
});
|
|
50
42
|
return {
|
|
51
43
|
get current() {
|
|
44
|
+
/**
|
|
45
|
+
* Do not return the pose of an arm because in this case the pose represents
|
|
46
|
+
* the end effector frame and not the origin frame
|
|
47
|
+
*/
|
|
52
48
|
if (resource?.subtype === 'arm') {
|
|
53
49
|
return;
|
|
54
50
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ResourceName } from '@viamrobotics/sdk';
|
|
2
|
+
interface Context {
|
|
3
|
+
current: Record<string, ResourceName>;
|
|
4
|
+
}
|
|
5
|
+
export declare const provideResourceByName: (partID: () => string) => void;
|
|
6
|
+
export declare const useResourceByName: () => Context;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useResourceNames } from '@viamrobotics/svelte-sdk';
|
|
2
|
+
import { getContext, setContext } from 'svelte';
|
|
3
|
+
const key = Symbol('resource-by-name-context');
|
|
4
|
+
export const provideResourceByName = (partID) => {
|
|
5
|
+
const resourceNames = useResourceNames(partID);
|
|
6
|
+
const resourceByName = $derived.by(() => {
|
|
7
|
+
const results = {};
|
|
8
|
+
for (const resourceName of resourceNames.current) {
|
|
9
|
+
results[resourceName.name] = resourceName;
|
|
10
|
+
}
|
|
11
|
+
return results;
|
|
12
|
+
});
|
|
13
|
+
setContext(key, {
|
|
14
|
+
get current() {
|
|
15
|
+
return resourceByName;
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
export const useResourceByName = () => {
|
|
20
|
+
return getContext(key);
|
|
21
|
+
};
|