@viamrobotics/motion-tools 0.13.0 → 0.14.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/Detail.svelte.d.ts +6 -1
- package/dist/Detail.svelte.js +9 -1
- package/dist/WorldObject.svelte.d.ts +17 -8
- package/dist/WorldObject.svelte.js +76 -56
- package/dist/color.d.ts +6 -1
- package/dist/color.js +60 -0
- package/dist/components/App.svelte +5 -4
- package/dist/components/Details.svelte +30 -16
- package/dist/components/LiveUpdatesBanner.svelte +4 -4
- package/dist/components/SceneProviders.svelte +2 -0
- package/dist/components/Tree/AddFrames.svelte +30 -0
- package/dist/components/Tree/AddFrames.svelte.d.ts +18 -0
- package/dist/components/Tree/Tree.svelte +1 -4
- package/dist/components/Tree/TreeContainer.svelte +6 -1
- package/dist/components/WorldState.svelte +53 -1
- package/dist/hooks/useDrawAPI.svelte.js +2 -2
- package/dist/hooks/useEnvironment.svelte.d.ts +10 -0
- package/dist/hooks/useEnvironment.svelte.js +19 -0
- package/dist/hooks/useFramelessComponents.svelte.d.ts +6 -0
- package/dist/hooks/useFramelessComponents.svelte.js +30 -0
- package/dist/hooks/useFrames.svelte.js +127 -135
- package/dist/hooks/useGeometries.svelte.js +4 -1
- package/dist/hooks/usePartConfig.svelte.d.ts +27 -24
- package/dist/hooks/usePartConfig.svelte.js +107 -16
- package/dist/hooks/usePose.svelte.js +10 -3
- package/dist/hooks/useSettings.svelte.d.ts +0 -1
- package/dist/hooks/useSettings.svelte.js +0 -1
- package/dist/three/BatchedArrow.d.ts +2 -0
- package/dist/three/BatchedArrow.js +31 -22
- package/dist/transform.d.ts +7 -1
- package/dist/transform.js +70 -1
- package/dist/workers/worldStateWorker.js +7 -2
- package/package.json +2 -1
package/dist/Detail.svelte.d.ts
CHANGED
|
@@ -11,11 +11,15 @@ type UpdateFrameCallback = {
|
|
|
11
11
|
z?: number;
|
|
12
12
|
}): void;
|
|
13
13
|
};
|
|
14
|
+
type RemoveFrameCallback = {
|
|
15
|
+
(componentName: string): void;
|
|
16
|
+
};
|
|
14
17
|
export declare class DetailConfigUpdater {
|
|
15
18
|
private object;
|
|
16
19
|
private referenceFrame;
|
|
17
20
|
private updateFrame;
|
|
18
|
-
|
|
21
|
+
private removeFrame;
|
|
22
|
+
constructor(object: () => WorldObject<Geometries> | undefined, updateFrame: UpdateFrameCallback, removeFrame: RemoveFrameCallback, referenceFrame: () => string);
|
|
19
23
|
updateLocalPosition: ({ x, y, z }: {
|
|
20
24
|
x?: number;
|
|
21
25
|
y?: number;
|
|
@@ -36,6 +40,7 @@ export declare class DetailConfigUpdater {
|
|
|
36
40
|
z?: number;
|
|
37
41
|
}) => void;
|
|
38
42
|
setFrameParent: (parentName: string) => void;
|
|
43
|
+
deleteFrame: () => void;
|
|
39
44
|
setGeometryType: (type: "none" | "box" | "sphere" | "capsule") => void;
|
|
40
45
|
}
|
|
41
46
|
export {};
|
package/dist/Detail.svelte.js
CHANGED
|
@@ -2,10 +2,12 @@ export class DetailConfigUpdater {
|
|
|
2
2
|
object;
|
|
3
3
|
referenceFrame;
|
|
4
4
|
updateFrame;
|
|
5
|
-
|
|
5
|
+
removeFrame;
|
|
6
|
+
constructor(object, updateFrame, removeFrame, referenceFrame) {
|
|
6
7
|
this.referenceFrame = referenceFrame;
|
|
7
8
|
this.object = object;
|
|
8
9
|
this.updateFrame = updateFrame;
|
|
10
|
+
this.removeFrame = removeFrame;
|
|
9
11
|
}
|
|
10
12
|
updateLocalPosition = ({ x, y, z }) => {
|
|
11
13
|
const object = this.object();
|
|
@@ -95,6 +97,12 @@ export class DetailConfigUpdater {
|
|
|
95
97
|
theta: object.localEditedPose.theta,
|
|
96
98
|
});
|
|
97
99
|
};
|
|
100
|
+
deleteFrame = () => {
|
|
101
|
+
const object = this.object();
|
|
102
|
+
if (!object)
|
|
103
|
+
return;
|
|
104
|
+
this.removeFrame(object.name ?? '');
|
|
105
|
+
};
|
|
98
106
|
setGeometryType = (type) => {
|
|
99
107
|
const object = this.object();
|
|
100
108
|
if (!object)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { Geometry, Pose, TransformWithUUID } from '@viamrobotics/sdk';
|
|
2
|
-
import { BatchedMesh, Object3D, Vector3
|
|
1
|
+
import type { Geometry, PlainMessage, Pose, Struct, TransformWithUUID } from '@viamrobotics/sdk';
|
|
2
|
+
import { BatchedMesh, Color, Object3D, Vector3 } from 'three';
|
|
3
|
+
import type { ValueOf } from 'type-fest';
|
|
3
4
|
import type { OBB } from 'three/addons/math/OBB.js';
|
|
4
5
|
export type PointsGeometry = {
|
|
5
6
|
center: undefined;
|
|
@@ -16,9 +17,14 @@ export type LinesGeometry = {
|
|
|
16
17
|
};
|
|
17
18
|
};
|
|
18
19
|
export type Geometries = Geometry | PointsGeometry | LinesGeometry;
|
|
20
|
+
export declare const SupportedShapes: {
|
|
21
|
+
readonly points: "points";
|
|
22
|
+
readonly line: "line";
|
|
23
|
+
readonly arrow: "arrow";
|
|
24
|
+
};
|
|
19
25
|
export type Metadata = {
|
|
20
26
|
colors?: Float32Array;
|
|
21
|
-
color?:
|
|
27
|
+
color?: Color;
|
|
22
28
|
opacity?: number;
|
|
23
29
|
gltf?: {
|
|
24
30
|
scene: Object3D;
|
|
@@ -26,22 +32,25 @@ export type Metadata = {
|
|
|
26
32
|
points?: Vector3[];
|
|
27
33
|
pointSize?: number;
|
|
28
34
|
lineWidth?: number;
|
|
29
|
-
lineDotColor?:
|
|
35
|
+
lineDotColor?: Color;
|
|
30
36
|
batched?: {
|
|
31
37
|
id: number;
|
|
32
38
|
object: BatchedMesh;
|
|
33
39
|
};
|
|
40
|
+
shape?: ValueOf<typeof SupportedShapes>;
|
|
34
41
|
getBoundingBoxAt?: (box: OBB) => void;
|
|
35
42
|
};
|
|
36
|
-
export declare const
|
|
43
|
+
export declare const isMetadataKey: (key: string) => key is keyof Metadata;
|
|
37
44
|
export declare class WorldObject<T extends Geometries = Geometries> {
|
|
38
45
|
uuid: string;
|
|
39
46
|
name: string;
|
|
40
47
|
referenceFrame: string | undefined;
|
|
41
|
-
pose:
|
|
48
|
+
pose: PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Pose>;
|
|
42
49
|
geometry?: T;
|
|
43
50
|
metadata: Metadata;
|
|
44
|
-
localEditedPose:
|
|
51
|
+
localEditedPose: PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Pose>;
|
|
45
52
|
constructor(name: string, pose?: Pose, parent?: string, geometry?: T, metadata?: Metadata);
|
|
46
53
|
}
|
|
47
|
-
export declare const
|
|
54
|
+
export declare const parseMetadata: (fields?: PlainMessage<Struct>["fields"]) => Metadata;
|
|
55
|
+
export declare const fromTransform: (transform: TransformWithUUID) => WorldObject<PlainMessage<import("@viamrobotics/sdk/dist/gen/common/v1/common_pb").Geometry>>;
|
|
56
|
+
export declare const determinePose: (object: WorldObject, pose: Pose | undefined) => Pose;
|
|
@@ -1,17 +1,25 @@
|
|
|
1
|
-
import { BatchedMesh, Color, MathUtils,
|
|
2
|
-
import { createPose } from './transform';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import { BatchedMesh, Color, MathUtils, Object3D, Vector3 } from 'three';
|
|
2
|
+
import { createPose, matrixToPose, poseToMatrix } from './transform';
|
|
3
|
+
import { isColorRepresentation, isRGB, parseColor, parseOpacity, parseRGB } from './color';
|
|
4
|
+
export const SupportedShapes = {
|
|
5
|
+
points: 'points',
|
|
6
|
+
line: 'line',
|
|
7
|
+
arrow: 'arrow',
|
|
8
|
+
};
|
|
9
|
+
const METADATA_KEYS = [
|
|
10
|
+
'colors',
|
|
11
|
+
'color',
|
|
12
|
+
'opacity',
|
|
13
|
+
'gltf',
|
|
14
|
+
'points',
|
|
15
|
+
'pointSize',
|
|
16
|
+
'lineWidth',
|
|
17
|
+
'lineDotColor',
|
|
18
|
+
'batched',
|
|
19
|
+
'shape',
|
|
20
|
+
];
|
|
21
|
+
export const isMetadataKey = (key) => {
|
|
22
|
+
return METADATA_KEYS.includes(key);
|
|
15
23
|
};
|
|
16
24
|
export class WorldObject {
|
|
17
25
|
uuid;
|
|
@@ -19,20 +27,23 @@ export class WorldObject {
|
|
|
19
27
|
referenceFrame = $state.raw();
|
|
20
28
|
pose = $state.raw(createPose());
|
|
21
29
|
geometry;
|
|
22
|
-
metadata;
|
|
30
|
+
metadata = $state({});
|
|
23
31
|
localEditedPose = $state.raw(createPose());
|
|
24
32
|
constructor(name, pose, parent = 'world', geometry, metadata) {
|
|
25
33
|
this.uuid = MathUtils.generateUUID();
|
|
26
34
|
this.name = name;
|
|
27
35
|
this.referenceFrame = parent;
|
|
28
36
|
this.geometry = geometry;
|
|
29
|
-
|
|
37
|
+
if (metadata) {
|
|
38
|
+
this.metadata = metadata;
|
|
39
|
+
}
|
|
30
40
|
if (pose) {
|
|
31
41
|
this.pose = pose;
|
|
32
42
|
this.localEditedPose = { ...pose };
|
|
33
43
|
}
|
|
34
44
|
}
|
|
35
45
|
}
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
47
|
const unwrapValue = (value) => {
|
|
37
48
|
if (!value?.kind)
|
|
38
49
|
return value;
|
|
@@ -42,9 +53,9 @@ const unwrapValue = (value) => {
|
|
|
42
53
|
case 'boolValue':
|
|
43
54
|
return value.kind.value;
|
|
44
55
|
case 'structValue': {
|
|
45
|
-
// Recursively unwrap nested struct
|
|
46
56
|
const result = {};
|
|
47
57
|
for (const [key, val] of Object.entries(value.kind.value.fields || {})) {
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
48
59
|
result[key] = unwrapValue(val);
|
|
49
60
|
}
|
|
50
61
|
return result;
|
|
@@ -57,56 +68,65 @@ const unwrapValue = (value) => {
|
|
|
57
68
|
return value.kind.value;
|
|
58
69
|
}
|
|
59
70
|
};
|
|
60
|
-
const parseMetadata = (
|
|
61
|
-
|
|
62
|
-
for (const [k, v] of Object.entries(
|
|
71
|
+
export const parseMetadata = (fields = {}) => {
|
|
72
|
+
const json = {};
|
|
73
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
74
|
+
if (!isMetadataKey(k))
|
|
75
|
+
continue;
|
|
63
76
|
const unwrappedValue = unwrapValue(v);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
switch (k) {
|
|
78
|
+
case 'color':
|
|
79
|
+
case 'lineDotColor':
|
|
80
|
+
json[k] = readColor(unwrappedValue);
|
|
81
|
+
break;
|
|
82
|
+
case 'opacity':
|
|
83
|
+
json[k] = parseOpacity(unwrappedValue);
|
|
84
|
+
break;
|
|
85
|
+
case 'gltf':
|
|
86
|
+
json[k] = unwrappedValue;
|
|
87
|
+
break;
|
|
88
|
+
case 'points':
|
|
89
|
+
json[k] = unwrappedValue;
|
|
90
|
+
break;
|
|
91
|
+
case 'pointSize':
|
|
92
|
+
json[k] = unwrappedValue;
|
|
93
|
+
break;
|
|
94
|
+
case 'lineWidth':
|
|
95
|
+
json[k] = unwrappedValue;
|
|
96
|
+
break;
|
|
97
|
+
case 'batched':
|
|
98
|
+
json[k] = unwrappedValue;
|
|
99
|
+
break;
|
|
100
|
+
case 'shape':
|
|
101
|
+
json[k] = unwrappedValue;
|
|
102
|
+
break;
|
|
71
103
|
}
|
|
72
104
|
}
|
|
73
105
|
return json;
|
|
74
106
|
};
|
|
107
|
+
const readColor = (color) => {
|
|
108
|
+
if (isColorRepresentation(color))
|
|
109
|
+
return parseColor(color);
|
|
110
|
+
if (isRGB(color))
|
|
111
|
+
return parseRGB(color);
|
|
112
|
+
return new Color('black');
|
|
113
|
+
};
|
|
75
114
|
export const fromTransform = (transform) => {
|
|
76
|
-
const metadata = transform.metadata
|
|
77
|
-
? parseMetadata(transform.metadata.fields)
|
|
78
|
-
: {};
|
|
115
|
+
const metadata = transform.metadata ? parseMetadata(transform.metadata.fields) : {};
|
|
79
116
|
const worldObject = new WorldObject(transform.referenceFrame, transform.poseInObserverFrame?.pose, transform.poseInObserverFrame?.referenceFrame, transform.physicalObject, metadata);
|
|
80
117
|
worldObject.uuid = transform.uuidString;
|
|
81
118
|
return worldObject;
|
|
82
119
|
};
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
matrix.makeRotationFromQuaternion(poseQuaternion);
|
|
87
|
-
matrix.setPosition(new Vector3(pose.x, pose.y, pose.z));
|
|
88
|
-
return matrix;
|
|
89
|
-
};
|
|
90
|
-
const matrixToPose = (matrix) => {
|
|
91
|
-
const pose = createPose();
|
|
92
|
-
const translation = new Vector3();
|
|
93
|
-
const quaternion = new Quaternion();
|
|
94
|
-
matrix.decompose(translation, quaternion, new Vector3());
|
|
95
|
-
pose.x = translation.x;
|
|
96
|
-
pose.y = translation.y;
|
|
97
|
-
pose.z = translation.z;
|
|
98
|
-
const s = Math.sqrt(1 - quaternion.w * quaternion.w);
|
|
99
|
-
if (s < 0.000001) {
|
|
100
|
-
pose.oX = 0;
|
|
101
|
-
pose.oY = 0;
|
|
102
|
-
pose.oZ = 1;
|
|
103
|
-
pose.theta = 0;
|
|
120
|
+
export const determinePose = (object, pose) => {
|
|
121
|
+
if (pose === undefined) {
|
|
122
|
+
return object.localEditedPose;
|
|
104
123
|
}
|
|
105
124
|
else {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
125
|
+
const poseNetwork = poseToMatrix(object.pose);
|
|
126
|
+
const poseUsePose = poseToMatrix(pose);
|
|
127
|
+
const poseLocalEditedPose = poseToMatrix(object.localEditedPose);
|
|
128
|
+
const poseNetworkInverse = poseNetwork.invert();
|
|
129
|
+
const resultMatrix = poseUsePose.multiply(poseNetworkInverse).multiply(poseLocalEditedPose);
|
|
130
|
+
return matrixToPose(resultMatrix);
|
|
110
131
|
}
|
|
111
|
-
return pose;
|
|
112
132
|
};
|
package/dist/color.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Color, type ColorRepresentation } from 'three';
|
|
1
|
+
import { Color, type ColorRepresentation, type RGB } from 'three';
|
|
2
2
|
/**
|
|
3
3
|
* Darkens a THREE.Color by a given percentage while preserving hue.
|
|
4
4
|
* @param color The original THREE.Color instance.
|
|
@@ -27,3 +27,8 @@ export declare const resourceColors: {
|
|
|
27
27
|
readonly switch: string;
|
|
28
28
|
readonly webcam: string;
|
|
29
29
|
};
|
|
30
|
+
export declare const isColorRepresentation: (color: unknown) => color is ColorRepresentation;
|
|
31
|
+
export declare const parseColor: (color: unknown, defaultColor?: ColorRepresentation) => Color;
|
|
32
|
+
export declare const isRGB: (color: unknown) => color is RGB;
|
|
33
|
+
export declare const parseRGB: (color: unknown, defaultColor?: RGB) => Color;
|
|
34
|
+
export declare const parseOpacity: (opacity: unknown, defaultOpacity?: number) => number;
|
package/dist/color.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Color } from 'three';
|
|
2
2
|
import twColors from 'tailwindcss/colors';
|
|
3
|
+
import { isNumber } from 'lodash-es';
|
|
3
4
|
// Step 3: linear sRGB → sRGB
|
|
4
5
|
const linearToSrgb = (x) => {
|
|
5
6
|
return x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055;
|
|
@@ -73,3 +74,62 @@ export const resourceColors = {
|
|
|
73
74
|
switch: oklchToHex(twColors.stone[darkness]),
|
|
74
75
|
webcam: oklchToHex(twColors.sky[darkness]),
|
|
75
76
|
};
|
|
77
|
+
export const isColorRepresentation = (color) => {
|
|
78
|
+
if (!color)
|
|
79
|
+
return false;
|
|
80
|
+
if (isColorString(color))
|
|
81
|
+
return true;
|
|
82
|
+
if (isColorHex(color))
|
|
83
|
+
return true;
|
|
84
|
+
if (isColor(color))
|
|
85
|
+
return true;
|
|
86
|
+
return false;
|
|
87
|
+
};
|
|
88
|
+
export const parseColor = (color, defaultColor = 'black') => {
|
|
89
|
+
if (!isColorRepresentation(color))
|
|
90
|
+
return new Color(defaultColor);
|
|
91
|
+
return new Color(color);
|
|
92
|
+
};
|
|
93
|
+
export const isRGB = (color) => {
|
|
94
|
+
if (!color ||
|
|
95
|
+
typeof color !== 'object' ||
|
|
96
|
+
!('r' in color) ||
|
|
97
|
+
!('g' in color) ||
|
|
98
|
+
!('b' in color)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return isNumber(color.r) && isNumber(color.g) && isNumber(color.b);
|
|
102
|
+
};
|
|
103
|
+
export const parseRGB = (color, defaultColor = { r: 0, g: 0, b: 0 }) => {
|
|
104
|
+
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);
|
|
107
|
+
};
|
|
108
|
+
export const parseOpacity = (opacity, defaultOpacity = 1) => {
|
|
109
|
+
if (!isNumber(opacity))
|
|
110
|
+
return defaultOpacity;
|
|
111
|
+
return opacity > 1 ? opacity / 100 : opacity;
|
|
112
|
+
};
|
|
113
|
+
const isColor = (color) => {
|
|
114
|
+
if (!color)
|
|
115
|
+
return false;
|
|
116
|
+
return color instanceof Color;
|
|
117
|
+
};
|
|
118
|
+
const isColorString = (color) => {
|
|
119
|
+
if (!color)
|
|
120
|
+
return false;
|
|
121
|
+
if (typeof color === 'string') {
|
|
122
|
+
const parsed = new Color(color);
|
|
123
|
+
return parsed.isColor;
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
};
|
|
127
|
+
const isColorHex = (color) => {
|
|
128
|
+
if (!color)
|
|
129
|
+
return false;
|
|
130
|
+
if (typeof color === 'number') {
|
|
131
|
+
const parsed = new Color(color);
|
|
132
|
+
return parsed.isColor;
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
};
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import { useViamClient } from '@viamrobotics/svelte-sdk'
|
|
22
22
|
import LiveUpdatesBanner from './LiveUpdatesBanner.svelte'
|
|
23
23
|
import ArmPositions from './widgets/ArmPositions.svelte'
|
|
24
|
+
import { provideEnvironment } from '../hooks/useEnvironment.svelte'
|
|
24
25
|
|
|
25
26
|
interface LocalConfigProps {
|
|
26
27
|
getLocalPartConfig: () => Struct
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
|
|
46
47
|
const appClient = useViamClient()
|
|
47
48
|
const settings = provideSettings()
|
|
49
|
+
const environment = provideEnvironment()
|
|
48
50
|
|
|
49
51
|
$effect(() => {
|
|
50
52
|
settings.current.enableKeybindings = enableKeybindings
|
|
@@ -55,10 +57,9 @@
|
|
|
55
57
|
provideToast()
|
|
56
58
|
|
|
57
59
|
let root = $state.raw<HTMLElement>()
|
|
58
|
-
let isStandalone = $state(false)
|
|
59
60
|
|
|
60
61
|
if (localConfigProps) {
|
|
61
|
-
isStandalone = false
|
|
62
|
+
environment.current.isStandalone = false
|
|
62
63
|
providePartConfig({
|
|
63
64
|
appEmbeddedPartConfigProps: {
|
|
64
65
|
isDirty: () => localConfigProps.isDirty(),
|
|
@@ -68,7 +69,7 @@
|
|
|
68
69
|
},
|
|
69
70
|
})
|
|
70
71
|
} else {
|
|
71
|
-
isStandalone = true
|
|
72
|
+
environment.current.isStandalone = true
|
|
72
73
|
providePartConfig({
|
|
73
74
|
standalonePartConfigProps: {
|
|
74
75
|
viamClient: () => appClient?.current,
|
|
@@ -99,7 +100,7 @@
|
|
|
99
100
|
|
|
100
101
|
<Dashboard {@attach domPortal(root)} />
|
|
101
102
|
<Details {@attach domPortal(root)} />
|
|
102
|
-
{#if isStandalone}
|
|
103
|
+
{#if environment.current.isStandalone}
|
|
103
104
|
<LiveUpdatesBanner {@attach domPortal(root)} />
|
|
104
105
|
{/if}
|
|
105
106
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<script lang="ts">
|
|
14
14
|
import { Check, Copy } from 'lucide-svelte'
|
|
15
15
|
import { useTask } from '@threlte/core'
|
|
16
|
-
import { Button, Icon } from '@viamrobotics/prime-core'
|
|
16
|
+
import { Button, Icon, Select, Input } from '@viamrobotics/prime-core'
|
|
17
17
|
import {
|
|
18
18
|
useSelectedObject,
|
|
19
19
|
useFocusedObject,
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
const detailConfigUpdater: DetailConfigUpdater = new DetailConfigUpdater(
|
|
61
61
|
() => object,
|
|
62
62
|
partConfig.updateFrame,
|
|
63
|
+
partConfig.deleteFrame,
|
|
63
64
|
() => referenceFrame
|
|
64
65
|
)
|
|
65
66
|
|
|
@@ -105,15 +106,18 @@
|
|
|
105
106
|
value,
|
|
106
107
|
ariaLabel,
|
|
107
108
|
}: {
|
|
108
|
-
label
|
|
109
|
+
label?: string
|
|
109
110
|
value: string
|
|
110
111
|
ariaLabel: string
|
|
111
112
|
})}
|
|
112
113
|
<div>
|
|
113
114
|
<span
|
|
114
115
|
class="text-subtle-2"
|
|
115
|
-
aria-label={`immutable ${ariaLabel}`}
|
|
116
|
+
aria-label={`immutable ${ariaLabel}`}
|
|
116
117
|
>
|
|
118
|
+
{label}
|
|
119
|
+
</span>
|
|
120
|
+
|
|
117
121
|
{value}
|
|
118
122
|
</div>
|
|
119
123
|
{/snippet}
|
|
@@ -129,14 +133,16 @@
|
|
|
129
133
|
ariaLabel: string
|
|
130
134
|
onInput: (value: string) => void
|
|
131
135
|
})}
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
<div class="flex items-center gap-1">
|
|
137
|
+
<span class="text-subtle-2">{label}</span>
|
|
138
|
+
<Input
|
|
139
|
+
type="number"
|
|
140
|
+
aria-label={`mutable ${ariaLabel}`}
|
|
141
|
+
class="max-w-24 min-w-0 flex-1 rounded border px-1 py-0.5 text-xs"
|
|
142
|
+
{value}
|
|
143
|
+
on:input={(event) => onInput((event.target as HTMLInputElement).value)}
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
140
146
|
{/snippet}
|
|
141
147
|
|
|
142
148
|
{#snippet DropDownField({
|
|
@@ -150,16 +156,15 @@
|
|
|
150
156
|
options: string[]
|
|
151
157
|
onChange: (value: string) => void
|
|
152
158
|
})}
|
|
153
|
-
<
|
|
159
|
+
<Select
|
|
154
160
|
aria-label={`dropdown ${ariaLabel}`}
|
|
155
|
-
class="w-full rounded border border-gray-300 px-2 py-1 text-sm"
|
|
156
161
|
{value}
|
|
157
|
-
|
|
162
|
+
on:input={(event) => onChange((event.target as HTMLSelectElement).value)}
|
|
158
163
|
>
|
|
159
164
|
{#each options as option (option)}
|
|
160
165
|
<option value={option}>{option}</option>
|
|
161
166
|
{/each}
|
|
162
|
-
</
|
|
167
|
+
</Select>
|
|
163
168
|
{/snippet}
|
|
164
169
|
|
|
165
170
|
{#if object}
|
|
@@ -258,7 +263,6 @@
|
|
|
258
263
|
<strong class="font-semibold">parent frame</strong>
|
|
259
264
|
<div class="flex gap-3">
|
|
260
265
|
{@render ParentFrame({
|
|
261
|
-
label: 'name',
|
|
262
266
|
ariaLabel: 'parent frame name',
|
|
263
267
|
value: referenceFrame,
|
|
264
268
|
options: referenceFrameOptions,
|
|
@@ -511,5 +515,15 @@
|
|
|
511
515
|
Enter object view
|
|
512
516
|
</Button>
|
|
513
517
|
{/if}
|
|
518
|
+
|
|
519
|
+
<WeblabActive experiment="MOTION_TOOLS_EDIT_FRAME">
|
|
520
|
+
{#if isFrameNode}
|
|
521
|
+
<Button
|
|
522
|
+
variant="danger"
|
|
523
|
+
class="mt-2 w-full"
|
|
524
|
+
onclick={() => detailConfigUpdater.deleteFrame()}>Delete Frame</Button
|
|
525
|
+
>
|
|
526
|
+
{/if}
|
|
527
|
+
</WeblabActive>
|
|
514
528
|
</div>
|
|
515
529
|
{/if}
|
|
@@ -19,18 +19,18 @@
|
|
|
19
19
|
</p>
|
|
20
20
|
|
|
21
21
|
<p class="text-sm">
|
|
22
|
-
<
|
|
22
|
+
<button
|
|
23
23
|
class="cursor-pointer text-blue-600"
|
|
24
24
|
onclick={() => {
|
|
25
25
|
partConfig.saveLocalPartConfig()
|
|
26
|
-
}}>Save</
|
|
26
|
+
}}>Save</button
|
|
27
27
|
>
|
|
28
28
|
or
|
|
29
|
-
<
|
|
29
|
+
<button
|
|
30
30
|
class="cursor-pointer text-blue-600"
|
|
31
31
|
onclick={() => {
|
|
32
32
|
partConfig.resetLocalPartConfig()
|
|
33
|
-
}}>Discard</
|
|
33
|
+
}}>Discard</button
|
|
34
34
|
>
|
|
35
35
|
to resume.
|
|
36
36
|
</p>
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { provideWorldStates } from '../hooks/useWorldState.svelte'
|
|
18
18
|
import { provideArmClient } from '../hooks/useArmClient.svelte'
|
|
19
19
|
import { provideArrows } from '../hooks/useArrows.svelte'
|
|
20
|
+
import { provideFramelessComponents } from '../hooks/useFramelessComponents.svelte'
|
|
20
21
|
interface Props {
|
|
21
22
|
children: Snippet<[{ focus: boolean }]>
|
|
22
23
|
}
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
provideObjects()
|
|
43
44
|
provideWorldStates()
|
|
44
45
|
provideArmClient(() => partID.current)
|
|
46
|
+
provideFramelessComponents()
|
|
45
47
|
|
|
46
48
|
const { focus } = provideSelection()
|
|
47
49
|
</script>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { IconButton } from '@viamrobotics/prime-core'
|
|
3
|
+
import Drawer from './Drawer.svelte'
|
|
4
|
+
import { usePartConfig } from '../../hooks/usePartConfig.svelte'
|
|
5
|
+
import { useFramelessComponents } from '../../hooks/useFramelessComponents.svelte'
|
|
6
|
+
|
|
7
|
+
const framelessComponents = useFramelessComponents()
|
|
8
|
+
const partConfig = usePartConfig()
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<Drawer name="Add frames">
|
|
12
|
+
<div class="flex h-64 w-60 flex-col gap-2 overflow-auto p-3">
|
|
13
|
+
{#if framelessComponents.current.length > 0}
|
|
14
|
+
<ul class="space-y-1">
|
|
15
|
+
{#each framelessComponents.current as component (component)}
|
|
16
|
+
<li class="flex items-center gap-2 text-xs text-gray-700">
|
|
17
|
+
{component}
|
|
18
|
+
<IconButton
|
|
19
|
+
label="Add frame"
|
|
20
|
+
icon="plus"
|
|
21
|
+
onclick={() => partConfig.createFrame(component)}
|
|
22
|
+
/>
|
|
23
|
+
</li>
|
|
24
|
+
{/each}
|
|
25
|
+
</ul>
|
|
26
|
+
{:else}
|
|
27
|
+
No components without frames
|
|
28
|
+
{/if}
|
|
29
|
+
</div>
|
|
30
|
+
</Drawer>
|
|
@@ -0,0 +1,18 @@
|
|
|
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 AddFrames: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type AddFrames = InstanceType<typeof AddFrames>;
|
|
18
|
+
export default AddFrames;
|
|
@@ -156,10 +156,7 @@
|
|
|
156
156
|
<h3 {...api.getLabelProps() as object}>{rootNode.name}</h3>
|
|
157
157
|
</div>
|
|
158
158
|
|
|
159
|
-
<div
|
|
160
|
-
{...api.getTreeProps()}
|
|
161
|
-
class="w-60"
|
|
162
|
-
>
|
|
159
|
+
<div {...api.getTreeProps()}>
|
|
163
160
|
{#if rootChildren.length === 0}
|
|
164
161
|
<p class="text-subtle-2 px-2 py-4">No objects displayed</p>
|
|
165
162
|
{:else if rootChildren.length > 200}
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
import { useDraggable } from '../../hooks/useDraggable.svelte'
|
|
12
12
|
import { useWorldStates } from '../../hooks/useWorldState.svelte'
|
|
13
13
|
import Widgets from './Widgets.svelte'
|
|
14
|
-
|
|
14
|
+
import AddFrames from './AddFrames.svelte'
|
|
15
|
+
import { useEnvironment } from '../../hooks/useEnvironment.svelte'
|
|
15
16
|
const { ...rest } = $props()
|
|
16
17
|
|
|
17
18
|
provideTreeExpandedContext()
|
|
@@ -20,6 +21,7 @@
|
|
|
20
21
|
const objects = useObjects()
|
|
21
22
|
const draggable = useDraggable('treeview')
|
|
22
23
|
const worldStates = useWorldStates()
|
|
24
|
+
const environment = useEnvironment()
|
|
23
25
|
|
|
24
26
|
let rootNode = $state<TreeNode>({
|
|
25
27
|
id: 'world',
|
|
@@ -54,6 +56,9 @@
|
|
|
54
56
|
/>
|
|
55
57
|
{/key}
|
|
56
58
|
|
|
59
|
+
{#if environment.current.isStandalone}
|
|
60
|
+
<AddFrames />
|
|
61
|
+
{/if}
|
|
57
62
|
<Logs />
|
|
58
63
|
<Settings />
|
|
59
64
|
<Widgets />
|