simview 3.2__py3-none-any.whl
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.
- simview/__init__.py +69 -0
- simview/__main__.py +174 -0
- simview/launcher.py +109 -0
- simview/live.py +204 -0
- simview/merge.py +593 -0
- simview/model.py +690 -0
- simview/py.typed +0 -0
- simview/scene.py +603 -0
- simview/server.py +739 -0
- simview/state.py +190 -0
- simview/static/css/controls.css +36 -0
- simview/static/js/SimView.js +601 -0
- simview/static/js/components/AnimationController.js +654 -0
- simview/static/js/components/BatchManager.js +198 -0
- simview/static/js/components/InteractionController.js +446 -0
- simview/static/js/components/InteractionControls.js +116 -0
- simview/static/js/components/Scene.js +166 -0
- simview/static/js/components/StateStore.js +188 -0
- simview/static/js/config.js +233 -0
- simview/static/js/main.js +3 -0
- simview/static/js/objects/Body.js +616 -0
- simview/static/js/objects/StaticObject.js +181 -0
- simview/static/js/objects/Terrain.js +514 -0
- simview/static/js/objects/utils.js +378 -0
- simview/static/js/ui/AnalysisPanel.js +233 -0
- simview/static/js/ui/BatchLegend.js +207 -0
- simview/static/js/ui/BodyStateWindow.js +521 -0
- simview/static/js/ui/Controls.js +586 -0
- simview/static/js/ui/ErrorMetrics.js +664 -0
- simview/static/js/ui/Legend.js +87 -0
- simview/static/js/ui/PlaybackControls.js +314 -0
- simview/static/js/ui/ScalarPlotter.js +575 -0
- simview/static/js/utils/blobCodec.js +73 -0
- simview/static/js/utils/bodyTransforms.js +151 -0
- simview/static/js/utils/csv.js +46 -0
- simview/static/js/utils/errorMath.js +69 -0
- simview/static/js/utils/injectStyles.js +16 -0
- simview/static/js/utils/interpolate.js +65 -0
- simview/static/js/utils/liveFollow.js +17 -0
- simview/static/js/utils/loadRecordingLibs.js +34 -0
- simview/static/js/utils/viewState.js +226 -0
- simview/static/lib/chroma-js-3.1.2/index.min.js +8 -0
- simview/static/lib/chroma-js-3.1.2/src/Color.js +49 -0
- simview/static/lib/chroma-js-3.1.2/src/chroma.js +10 -0
- simview/static/lib/chroma-js-3.1.2/src/colors/colorbrewer.js +81 -0
- simview/static/lib/chroma-js-3.1.2/src/colors/w3cx11.js +164 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/average.js +88 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/bezier.js +86 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/blend.js +57 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/cubehelix.js +87 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/mix.js +19 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/random.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/generator/scale.js +394 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/_hsx.js +59 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/hcg.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/hsi.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/hsl.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/hsv.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/index.js +1 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/lab.js +19 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/lch.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/lrgb.js +19 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/num.js +15 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/oklab.js +19 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/oklch.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/interpolator/rgb.js +18 -0
- simview/static/lib/chroma-js-3.1.2/src/io/cmyk/cmyk2rgb.js +16 -0
- simview/static/lib/chroma-js-3.1.2/src/io/cmyk/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/cmyk/rgb2cmyk.js +17 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/css2rgb.js +238 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/hsl2css.js +26 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/lab2css.js +24 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/lch2css.js +24 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/oklab2css.js +16 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/oklch2css.js +16 -0
- simview/static/lib/chroma-js-3.1.2/src/io/css/rgb2css.js +61 -0
- simview/static/lib/chroma-js-3.1.2/src/io/gl/index.js +22 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hcg/hcg2rgb.js +55 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hcg/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hcg/rgb2hcg.js +23 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hex/hex2rgb.js +56 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hex/index.js +29 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hex/rgb2hex.js +29 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsi/hsi2rgb.js +45 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsi/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsi/rgb2hsi.js +31 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsl/hsl2rgb.js +35 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsl/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsl/rgb2hsl.js +45 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsv/hsv2rgb.js +47 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsv/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/hsv/rgb2hsv.js +32 -0
- simview/static/lib/chroma-js-3.1.2/src/io/input.js +4 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lab/index.js +28 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lab/lab-constants.js +120 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lab/lab2rgb.js +101 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lab/rgb2lab.js +67 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/hcl2rgb.js +9 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/index.js +35 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/lab2lch.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2lab.js +18 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2rgb.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/io/lch/rgb2lch.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/io/named/index.js +30 -0
- simview/static/lib/chroma-js-3.1.2/src/io/num/index.js +32 -0
- simview/static/lib/chroma-js-3.1.2/src/io/num/num2rgb.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/io/num/rgb2num.js +8 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklab/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklab/oklab2rgb.js +34 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklab/rgb2oklab.js +37 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklch/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklch/oklch2rgb.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/io/oklch/rgb2oklch.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/io/rgb/index.js +44 -0
- simview/static/lib/chroma-js-3.1.2/src/io/temp/index.js +22 -0
- simview/static/lib/chroma-js-3.1.2/src/io/temp/rgb2temperature.js +30 -0
- simview/static/lib/chroma-js-3.1.2/src/io/temp/temperature2rgb.js +39 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/alpha.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/clipped.js +5 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/darken.js +17 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/get.js +13 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/luminance.js +54 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/mix.js +10 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/premultiply.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/saturate.js +15 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/set.js +43 -0
- simview/static/lib/chroma-js-3.1.2/src/ops/shade.js +11 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/analyze.js +191 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/clip_rgb.js +15 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/contrast.js +12 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/contrastAPCA.js +68 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/delta-e.js +62 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/distance.js +17 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/index.js +27 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/last.js +8 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/limit.js +5 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/multiply-matrices.js +36 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/scales.js +15 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/type.js +18 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/unpack.js +17 -0
- simview/static/lib/chroma-js-3.1.2/src/utils/valid.js +11 -0
- simview/static/lib/chroma-js-3.1.2/src/version.js +2 -0
- simview/static/lib/download.js +132 -0
- simview/static/lib/js-colormaps.js +19335 -0
- simview/static/lib/tar.js +334 -0
- simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js +1556 -0
- simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js +8 -0
- simview/static/lib/three-0.174.0/three.core.js +48830 -0
- simview/static/lib/three-0.174.0/three.module.js +17313 -0
- simview/static/lib/uPlot.esm.js +6140 -0
- simview/static/lib/uPlot.min.css +2 -0
- simview/static/textures/contacts/red-cross0.png +0 -0
- simview/static/textures/points/ball0.png +0 -0
- simview/static/textures/points/ball1.png +0 -0
- simview/templates/index.html +81 -0
- simview/utils.py +41 -0
- simview-3.2.dist-info/METADATA +552 -0
- simview-3.2.dist-info/RECORD +163 -0
- simview-3.2.dist-info/WHEEL +5 -0
- simview-3.2.dist-info/entry_points.txt +2 -0
- simview-3.2.dist-info/licenses/LICENSE +28 -0
- simview-3.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import chroma from "chroma";
|
|
3
|
+
|
|
4
|
+
// Default configurations
|
|
5
|
+
const DEFAULT_GEOMETRY_CONFIG = {
|
|
6
|
+
box: {
|
|
7
|
+
widthSegments: 1,
|
|
8
|
+
heightSegments: 1,
|
|
9
|
+
depthSegments: 1,
|
|
10
|
+
},
|
|
11
|
+
sphere: {
|
|
12
|
+
widthSegments: 32,
|
|
13
|
+
heightSegments: 16,
|
|
14
|
+
},
|
|
15
|
+
cylinder: {
|
|
16
|
+
radialSegments: 32,
|
|
17
|
+
heightSegments: 1,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const DEFAULT_POINTS_CONFIG = {
|
|
22
|
+
size: 1,
|
|
23
|
+
opacity: 1,
|
|
24
|
+
alphaTest: 0.5,
|
|
25
|
+
transparent: true,
|
|
26
|
+
sizeAttenuation: true,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const DEFAULT_WIREFRAME_CONFIG = {
|
|
30
|
+
linewidth: 1,
|
|
31
|
+
color: 0x000000,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const DEFAULT_MESH_CONFIG = {
|
|
35
|
+
opacity: 1,
|
|
36
|
+
color: 0xffffff,
|
|
37
|
+
roughness: 0.5,
|
|
38
|
+
metalness: 0.5,
|
|
39
|
+
envMapIntensity: 1,
|
|
40
|
+
transparent: false,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const DEFAULT_ARROW_CONFIG = {
|
|
44
|
+
lineWidth: 1,
|
|
45
|
+
headWidth: 0.2,
|
|
46
|
+
headLength: 0.2,
|
|
47
|
+
color: 0xff0000,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Creates a THREE.js geometry based on the shape type and configuration
|
|
52
|
+
* @param {Object} shape - Object containing shape parameters (type, dimensions)
|
|
53
|
+
* @param {GeometryConfig} [geometryConfig={}] - Configuration for geometry segments
|
|
54
|
+
* @returns {THREE.BufferGeometry|null} The created geometry or null if shape type is invalid
|
|
55
|
+
*/
|
|
56
|
+
export function createGeometry(shape, geometryConfig) {
|
|
57
|
+
if (!shape || !shape.type) return null;
|
|
58
|
+
const config = { ...DEFAULT_GEOMETRY_CONFIG, ...geometryConfig };
|
|
59
|
+
let geometry;
|
|
60
|
+
|
|
61
|
+
switch (shape.type) {
|
|
62
|
+
case "box":
|
|
63
|
+
geometry = new THREE.BoxGeometry(
|
|
64
|
+
shape.hx * 2,
|
|
65
|
+
shape.hy * 2,
|
|
66
|
+
shape.hz * 2,
|
|
67
|
+
config.box.widthSegments,
|
|
68
|
+
config.box.heightSegments,
|
|
69
|
+
config.box.depthSegments
|
|
70
|
+
);
|
|
71
|
+
break;
|
|
72
|
+
case "sphere":
|
|
73
|
+
geometry = new THREE.SphereGeometry(
|
|
74
|
+
shape.radius,
|
|
75
|
+
config.sphere.widthSegments,
|
|
76
|
+
config.sphere.heightSegments
|
|
77
|
+
);
|
|
78
|
+
break;
|
|
79
|
+
case "cylinder":
|
|
80
|
+
geometry = new THREE.CylinderGeometry(
|
|
81
|
+
shape.radius,
|
|
82
|
+
shape.radius,
|
|
83
|
+
shape.height,
|
|
84
|
+
config.cylinder.radialSegments,
|
|
85
|
+
config.cylinder.heightSegments
|
|
86
|
+
);
|
|
87
|
+
geometry.rotateX(Math.PI / 2);
|
|
88
|
+
break;
|
|
89
|
+
case "mesh":
|
|
90
|
+
geometry = new THREE.BufferGeometry();
|
|
91
|
+
const positions = new Float32Array(shape.vertices.flat());
|
|
92
|
+
geometry.setAttribute(
|
|
93
|
+
"position",
|
|
94
|
+
new THREE.BufferAttribute(positions, 3)
|
|
95
|
+
);
|
|
96
|
+
const indices = new Uint16Array(shape.faces.flat());
|
|
97
|
+
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
|
|
98
|
+
geometry.computeVertexNormals();
|
|
99
|
+
break;
|
|
100
|
+
case "pointcloud":
|
|
101
|
+
geometry = null; // Handled separately in createVisualRepresentations
|
|
102
|
+
break;
|
|
103
|
+
default:
|
|
104
|
+
console.error("Invalid shape type:", shape.type);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
return geometry;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Creates a THREE.js Points object from a point cloud
|
|
112
|
+
* @param {Array<Array<number>>} pointCloud - Array of 3D points
|
|
113
|
+
* @param {PointsConfig} [pointsConfig={}] - Configuration for points appearance
|
|
114
|
+
* @param {boolean} [visible=false] - Initial visibility of the points
|
|
115
|
+
* @returns {THREE.Points|null} The created Points object or null if pointCloud is empty
|
|
116
|
+
*/
|
|
117
|
+
export function createPoints(pointCloud, pointsConfig, visible = true) {
|
|
118
|
+
if (!pointCloud || pointCloud.length === 0) return null;
|
|
119
|
+
const config = { ...DEFAULT_POINTS_CONFIG, ...pointsConfig };
|
|
120
|
+
const geometry = new THREE.BufferGeometry();
|
|
121
|
+
const positions = new Float32Array(pointCloud.flat());
|
|
122
|
+
geometry.setAttribute(
|
|
123
|
+
"position",
|
|
124
|
+
new THREE.Float32BufferAttribute(positions, 3)
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const material = new THREE.PointsMaterial({
|
|
128
|
+
size: config.size,
|
|
129
|
+
opacity: config.opacity,
|
|
130
|
+
alphaTest: config.alphaTest,
|
|
131
|
+
transparent: config.transparent,
|
|
132
|
+
sizeAttenuation: config.sizeAttenuation,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (config.texture) {
|
|
136
|
+
const texture = new THREE.TextureLoader().load(config.texture);
|
|
137
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
138
|
+
material.map = texture;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (config.color) {
|
|
142
|
+
material.color = new THREE.Color(config.color);
|
|
143
|
+
}
|
|
144
|
+
// Create points object
|
|
145
|
+
const points = new THREE.Points(geometry, material);
|
|
146
|
+
points.isPoints = true;
|
|
147
|
+
points.visible = visible;
|
|
148
|
+
return points;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Creates a THREE.js Points object from a point cloud
|
|
153
|
+
* @param {Array<Array<number>>} pointCloud - Array of 3D points
|
|
154
|
+
* @param {PointsConfig} [pointsConfig={}] - Configuration for points appearance
|
|
155
|
+
* @param {boolean} [visible=false] - Initial visibility of the points
|
|
156
|
+
* @returns {THREE.Points|null} The created Points object or null if pointCloud is empty
|
|
157
|
+
*/
|
|
158
|
+
export function createContactPoints(pointCloud, pointsConfig) {
|
|
159
|
+
if (!pointCloud || pointCloud.length === 0) return null;
|
|
160
|
+
|
|
161
|
+
const config = { ...DEFAULT_POINTS_CONFIG, ...pointsConfig };
|
|
162
|
+
|
|
163
|
+
const geometry = new THREE.BufferGeometry();
|
|
164
|
+
const positions = new Float32Array(pointCloud.flat());
|
|
165
|
+
geometry.setAttribute(
|
|
166
|
+
"position",
|
|
167
|
+
new THREE.Float32BufferAttribute(positions, 3)
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const material = new THREE.ShaderMaterial({
|
|
171
|
+
uniforms: {
|
|
172
|
+
color: { value: new THREE.Color(config.color) },
|
|
173
|
+
opacity: { value: config.opacity },
|
|
174
|
+
sizeAttenuation: { value: config.sizeAttenuation !== false },
|
|
175
|
+
useTexture: { value: false },
|
|
176
|
+
pointTexture: { value: null },
|
|
177
|
+
alphaTest: { value: 0.5 }, // Add alphaTest uniform
|
|
178
|
+
},
|
|
179
|
+
vertexShader: `
|
|
180
|
+
attribute float size;
|
|
181
|
+
uniform bool sizeAttenuation;
|
|
182
|
+
|
|
183
|
+
void main() {
|
|
184
|
+
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
|
185
|
+
gl_Position = projectionMatrix * mvPosition;
|
|
186
|
+
|
|
187
|
+
// Handle size attenuation
|
|
188
|
+
if (sizeAttenuation) {
|
|
189
|
+
gl_PointSize = size * (300.0 / -mvPosition.z);
|
|
190
|
+
} else {
|
|
191
|
+
gl_PointSize = size;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
`,
|
|
195
|
+
fragmentShader: `
|
|
196
|
+
uniform vec3 color;
|
|
197
|
+
uniform float opacity;
|
|
198
|
+
uniform bool useTexture;
|
|
199
|
+
uniform sampler2D pointTexture;
|
|
200
|
+
uniform float alphaTest;
|
|
201
|
+
|
|
202
|
+
void main() {
|
|
203
|
+
// Create a circular point with smooth edges
|
|
204
|
+
vec2 center = gl_PointCoord - vec2(0.5);
|
|
205
|
+
float dist = length(center) * 2.0;
|
|
206
|
+
|
|
207
|
+
// Smooth circle with anti-aliasing
|
|
208
|
+
float alpha = 1.0 - smoothstep(0.8, 1.0, dist);
|
|
209
|
+
|
|
210
|
+
// Apply alpha test
|
|
211
|
+
if (alpha < alphaTest) discard;
|
|
212
|
+
|
|
213
|
+
vec4 outputColor = vec4(color, alpha * opacity);
|
|
214
|
+
if (useTexture) {
|
|
215
|
+
vec4 texColor = texture2D(pointTexture, gl_PointCoord);
|
|
216
|
+
outputColor *= texColor;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
gl_FragColor = outputColor;
|
|
220
|
+
}
|
|
221
|
+
`,
|
|
222
|
+
transparent: true,
|
|
223
|
+
depthWrite: false, // Important for proper transparency
|
|
224
|
+
depthTest: true,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// If texture is provided, load and set it
|
|
228
|
+
if (config.texture) {
|
|
229
|
+
const texture = new THREE.TextureLoader().load(config.texture);
|
|
230
|
+
material.uniforms.pointTexture.value = texture;
|
|
231
|
+
material.uniforms.useTexture.value = true;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const points = new THREE.Points(geometry, material);
|
|
235
|
+
points.visible = config.visible !== false;
|
|
236
|
+
|
|
237
|
+
return points;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Creates a wireframe representation of a geometry
|
|
242
|
+
* @param {THREE.BufferGeometry} geometry - The geometry to create wireframe from
|
|
243
|
+
* @param {WireframeConfig} [wireframeConfig={}] - Configuration for wireframe appearance
|
|
244
|
+
* @param {boolean} [visible=false] - Initial visibility of the wireframe
|
|
245
|
+
* @returns {THREE.LineSegments} The created wireframe object
|
|
246
|
+
*/
|
|
247
|
+
export function createWireframe(geometry, wireframeConfig, visible = true) {
|
|
248
|
+
if (!geometry) return null;
|
|
249
|
+
|
|
250
|
+
const config = { ...DEFAULT_WIREFRAME_CONFIG, ...wireframeConfig };
|
|
251
|
+
|
|
252
|
+
const wireframe = new THREE.LineSegments(
|
|
253
|
+
new THREE.WireframeGeometry(geometry),
|
|
254
|
+
new THREE.LineBasicMaterial(config)
|
|
255
|
+
);
|
|
256
|
+
wireframe.visible = visible;
|
|
257
|
+
wireframe.isWireframe = true;
|
|
258
|
+
return wireframe;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Creates a THREE.js mesh with standard material and environment mapping
|
|
263
|
+
* @param {THREE.BufferGeometry} geometry - The geometry for the mesh
|
|
264
|
+
* @param {MeshConfig} [meshConfig={}] - Configuration for mesh appearance
|
|
265
|
+
* @param {boolean} [visible=true] - Initial visibility of the mesh
|
|
266
|
+
* @returns {THREE.Mesh} The created mesh object
|
|
267
|
+
*/
|
|
268
|
+
export function createMesh(geometry, meshConfig, visible = true) {
|
|
269
|
+
if (!geometry) return null;
|
|
270
|
+
|
|
271
|
+
const config = { ...DEFAULT_MESH_CONFIG, ...meshConfig };
|
|
272
|
+
|
|
273
|
+
let envMap = null;
|
|
274
|
+
if (config.envMapPath) {
|
|
275
|
+
const format = ".jpg";
|
|
276
|
+
const urls = [
|
|
277
|
+
config.envMapPath + "nx" + format,
|
|
278
|
+
config.envMapPath + "px" + format,
|
|
279
|
+
config.envMapPath + "pz" + format,
|
|
280
|
+
config.envMapPath + "nz" + format,
|
|
281
|
+
config.envMapPath + "py" + format,
|
|
282
|
+
config.envMapPath + "ny" + format,
|
|
283
|
+
];
|
|
284
|
+
envMap = new THREE.CubeTextureLoader().load(urls);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const material = new THREE.MeshStandardMaterial({
|
|
288
|
+
color: config.color,
|
|
289
|
+
roughness: config.roughness,
|
|
290
|
+
metalness: config.metalness,
|
|
291
|
+
opacity: config.opacity,
|
|
292
|
+
envMapIntensity: config.envMapIntensity,
|
|
293
|
+
transparent: config.transparent,
|
|
294
|
+
envMap: envMap,
|
|
295
|
+
side: THREE.DoubleSide,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
299
|
+
mesh.isMesh = true;
|
|
300
|
+
mesh.visible = visible;
|
|
301
|
+
return mesh;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Creates a single arrow
|
|
306
|
+
* @param {THREE.Vector3} start - Starting point of the arrow
|
|
307
|
+
* @param {THREE.Vector3} end - End point of the arrow
|
|
308
|
+
* @param {ArrowConfig} [arrowConfig={}] - Arrow configuration
|
|
309
|
+
* @returns {THREE.ArrowHelper} The created arrow object
|
|
310
|
+
*/
|
|
311
|
+
export function createArrow(start, end, arrowConfig = {}) {
|
|
312
|
+
const config = { ...DEFAULT_ARROW_CONFIG, ...arrowConfig };
|
|
313
|
+
const dir = end.clone().sub(start);
|
|
314
|
+
const length = dir.length();
|
|
315
|
+
|
|
316
|
+
const arrow = new THREE.ArrowHelper(
|
|
317
|
+
dir.normalize(),
|
|
318
|
+
start,
|
|
319
|
+
length,
|
|
320
|
+
config.color,
|
|
321
|
+
config.headLength,
|
|
322
|
+
config.headWidth
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
arrow.line.material.linewidth = config.lineWidth;
|
|
326
|
+
return arrow;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Creates multiple arrows from arrays of start and end points
|
|
331
|
+
* @param {THREE.Vector3[]} starts - Array of starting points
|
|
332
|
+
* @param {THREE.Vector3[]} ends - Array of end points
|
|
333
|
+
* @param {ArrowConfig|ArrowConfig[]} configs - Single config or array of configs for each arrow
|
|
334
|
+
* @returns {THREE.Group} Group containing all created arrows
|
|
335
|
+
*/
|
|
336
|
+
export function createArrows(starts, ends, configs = {}) {
|
|
337
|
+
if (starts.length !== ends.length) {
|
|
338
|
+
console.error("Number of start and end points must match");
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const arrowGroup = new THREE.Group();
|
|
343
|
+
|
|
344
|
+
starts.forEach((start, index) => {
|
|
345
|
+
const config = Array.isArray(configs) ? configs[index] : configs;
|
|
346
|
+
const arrow = createArrow(start, ends[index], config);
|
|
347
|
+
arrowGroup.add(arrow);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
return arrowGroup;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function generateDivergingPalette(colors, numColors, correctLightness) {
|
|
354
|
+
// Split colors into left/right gradients
|
|
355
|
+
const midpointIndex = Math.floor(colors.length / 2);
|
|
356
|
+
const leftColors = colors.slice(0, midpointIndex + 1);
|
|
357
|
+
const rightColors = colors.slice(midpointIndex);
|
|
358
|
+
|
|
359
|
+
// Create two separate scales
|
|
360
|
+
const leftScale = chroma
|
|
361
|
+
.bezier(leftColors)
|
|
362
|
+
.scale()
|
|
363
|
+
.correctLightness(correctLightness)
|
|
364
|
+
.mode("lab");
|
|
365
|
+
|
|
366
|
+
const rightScale = chroma
|
|
367
|
+
.bezier(rightColors)
|
|
368
|
+
.scale()
|
|
369
|
+
.correctLightness(correctLightness)
|
|
370
|
+
.mode("lab");
|
|
371
|
+
|
|
372
|
+
// Generate and combine halves
|
|
373
|
+
const numEach = Math.ceil(numColors / 2);
|
|
374
|
+
return [
|
|
375
|
+
...leftScale.colors(numEach).slice(0, -1),
|
|
376
|
+
...rightScale.colors(numEach),
|
|
377
|
+
];
|
|
378
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { injectStyles } from "../utils/injectStyles.js";
|
|
2
|
+
|
|
3
|
+
// Shared top-center panel hosting the Scalars plots and the Error Metrics
|
|
4
|
+
// comparison. Owns the collapsible container and, when both are present, the
|
|
5
|
+
// mode switcher between them; ScalarPlotter and ErrorMetrics just mount their
|
|
6
|
+
// content into the sections this panel provides.
|
|
7
|
+
export class AnalysisPanel {
|
|
8
|
+
constructor(app) {
|
|
9
|
+
this.app = app;
|
|
10
|
+
this.isExpanded = false;
|
|
11
|
+
this.mode = "scalars";
|
|
12
|
+
this.scalarPlotter = null;
|
|
13
|
+
this.errorMetrics = null;
|
|
14
|
+
this.modeTabElements = {};
|
|
15
|
+
|
|
16
|
+
this._injectStyles();
|
|
17
|
+
this._setupHTML();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_injectStyles() {
|
|
21
|
+
const styleId = "analysis-panel-styles";
|
|
22
|
+
const containerWidthPercentage = 40;
|
|
23
|
+
const css = `
|
|
24
|
+
.analysis-container {
|
|
25
|
+
width: ${containerWidthPercentage}%;
|
|
26
|
+
position: absolute;
|
|
27
|
+
top: 10px;
|
|
28
|
+
left: 50%;
|
|
29
|
+
transform: translateX(-50%);
|
|
30
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
31
|
+
color: white;
|
|
32
|
+
border-radius: 5px;
|
|
33
|
+
font-family: Arial, sans-serif;
|
|
34
|
+
font-size: 12px;
|
|
35
|
+
z-index: 1000;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.analysis-header {
|
|
39
|
+
display: flex;
|
|
40
|
+
justify-content: flex-start;
|
|
41
|
+
background-color: transparent;
|
|
42
|
+
align-items: center;
|
|
43
|
+
width: 100%;
|
|
44
|
+
padding: 7px 10px;
|
|
45
|
+
color: white;
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
border-radius: 5px 5px 0 0;
|
|
48
|
+
transition: background-color 0.2s ease;
|
|
49
|
+
box-sizing: border-box;
|
|
50
|
+
}
|
|
51
|
+
.analysis-header:hover {
|
|
52
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
53
|
+
}
|
|
54
|
+
.analysis-header-title {
|
|
55
|
+
font-weight: bold;
|
|
56
|
+
font-size: 1.1em;
|
|
57
|
+
}
|
|
58
|
+
.analysis-header-icon {
|
|
59
|
+
display: inline-block;
|
|
60
|
+
user-select: none;
|
|
61
|
+
padding-right: 0.4rem;
|
|
62
|
+
color: #ccc;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.analysis-content {
|
|
66
|
+
border-radius: 0 0 5px 5px;
|
|
67
|
+
display: none;
|
|
68
|
+
box-sizing: border-box;
|
|
69
|
+
}
|
|
70
|
+
.analysis-content.visible {
|
|
71
|
+
display: block;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.analysis-mode-tab-bar {
|
|
75
|
+
display: none;
|
|
76
|
+
width: 100%;
|
|
77
|
+
}
|
|
78
|
+
.analysis-mode-tab-bar.visible {
|
|
79
|
+
display: flex;
|
|
80
|
+
}
|
|
81
|
+
.analysis-mode-tab {
|
|
82
|
+
flex: 1;
|
|
83
|
+
text-align: center;
|
|
84
|
+
padding: 6px 10px;
|
|
85
|
+
cursor: pointer;
|
|
86
|
+
background-color: rgba(255, 255, 255, 0.05);
|
|
87
|
+
font-size: 1.0em;
|
|
88
|
+
color: #ccc;
|
|
89
|
+
border: none;
|
|
90
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
|
|
91
|
+
font-weight: bold;
|
|
92
|
+
}
|
|
93
|
+
.analysis-mode-tab:hover {
|
|
94
|
+
background-color: rgba(255, 255, 255, 0.12);
|
|
95
|
+
}
|
|
96
|
+
.analysis-mode-tab.active {
|
|
97
|
+
color: white;
|
|
98
|
+
background-color: rgba(255, 255, 255, 0.18);
|
|
99
|
+
border-bottom: 1px solid white;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.analysis-section {
|
|
103
|
+
display: none;
|
|
104
|
+
}
|
|
105
|
+
.analysis-section.visible {
|
|
106
|
+
display: block;
|
|
107
|
+
}
|
|
108
|
+
`;
|
|
109
|
+
injectStyles(styleId, css);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
_setupHTML() {
|
|
113
|
+
this.container = document.createElement("div");
|
|
114
|
+
this.container.className = "analysis-container";
|
|
115
|
+
|
|
116
|
+
this.header = document.createElement("div");
|
|
117
|
+
this.header.className = "analysis-header";
|
|
118
|
+
this.icon = document.createElement("span");
|
|
119
|
+
this.icon.className = "analysis-header-icon";
|
|
120
|
+
this.icon.textContent = "▸";
|
|
121
|
+
this.titleEl = document.createElement("span");
|
|
122
|
+
this.titleEl.className = "analysis-header-title";
|
|
123
|
+
this.titleEl.textContent = "Analysis";
|
|
124
|
+
this.header.appendChild(this.icon);
|
|
125
|
+
this.header.appendChild(this.titleEl);
|
|
126
|
+
this.header.addEventListener("click", () => this._toggleDropdown());
|
|
127
|
+
|
|
128
|
+
this.content = document.createElement("div");
|
|
129
|
+
this.content.className = "analysis-content";
|
|
130
|
+
|
|
131
|
+
this.modeTabBar = document.createElement("div");
|
|
132
|
+
this.modeTabBar.className = "analysis-mode-tab-bar";
|
|
133
|
+
this.modeTabElements.scalars = this._addModeTab("Scalars", "scalars");
|
|
134
|
+
this.modeTabElements.errorMetrics = this._addModeTab("Error Metrics", "errorMetrics");
|
|
135
|
+
|
|
136
|
+
this.scalarsSection = document.createElement("div");
|
|
137
|
+
this.scalarsSection.className = "analysis-section";
|
|
138
|
+
this.errorMetricsSection = document.createElement("div");
|
|
139
|
+
this.errorMetricsSection.className = "analysis-section";
|
|
140
|
+
|
|
141
|
+
this.content.appendChild(this.modeTabBar);
|
|
142
|
+
this.content.appendChild(this.scalarsSection);
|
|
143
|
+
this.content.appendChild(this.errorMetricsSection);
|
|
144
|
+
this.container.appendChild(this.header);
|
|
145
|
+
this.container.appendChild(this.content);
|
|
146
|
+
document.body.appendChild(this.container);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
_addModeTab(label, mode) {
|
|
150
|
+
const tab = document.createElement("button");
|
|
151
|
+
tab.className = "analysis-mode-tab";
|
|
152
|
+
tab.textContent = label;
|
|
153
|
+
tab.addEventListener("click", () => this._switchMode(mode));
|
|
154
|
+
this.modeTabBar.appendChild(tab);
|
|
155
|
+
return tab;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
attachScalarPlotter(scalarPlotter) {
|
|
159
|
+
this.scalarPlotter = scalarPlotter;
|
|
160
|
+
this.scalarsSection.appendChild(scalarPlotter.tabBar);
|
|
161
|
+
this.scalarsSection.appendChild(scalarPlotter.plotArea);
|
|
162
|
+
this._refreshLayout();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
attachErrorMetrics(errorMetrics) {
|
|
166
|
+
this.errorMetrics = errorMetrics;
|
|
167
|
+
this.errorMetricsSection.appendChild(errorMetrics.content);
|
|
168
|
+
this._refreshLayout();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
_refreshLayout() {
|
|
172
|
+
const hasScalars = !!this.scalarPlotter;
|
|
173
|
+
const hasErrorMetrics = !!this.errorMetrics;
|
|
174
|
+
const showModeBar = hasScalars && hasErrorMetrics;
|
|
175
|
+
this.modeTabBar.classList.toggle("visible", showModeBar);
|
|
176
|
+
|
|
177
|
+
if (!hasScalars) this.mode = "errorMetrics";
|
|
178
|
+
else if (!hasErrorMetrics) this.mode = "scalars";
|
|
179
|
+
|
|
180
|
+
this.titleEl.textContent = showModeBar
|
|
181
|
+
? "Analysis"
|
|
182
|
+
: hasErrorMetrics
|
|
183
|
+
? "Error Metrics"
|
|
184
|
+
: "Scalars";
|
|
185
|
+
|
|
186
|
+
this._applyMode();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
_applyMode() {
|
|
190
|
+
this.scalarsSection.classList.toggle("visible", this.mode === "scalars");
|
|
191
|
+
this.errorMetricsSection.classList.toggle(
|
|
192
|
+
"visible",
|
|
193
|
+
this.mode === "errorMetrics"
|
|
194
|
+
);
|
|
195
|
+
if (this.modeTabElements.scalars) {
|
|
196
|
+
this.modeTabElements.scalars.classList.toggle(
|
|
197
|
+
"active",
|
|
198
|
+
this.mode === "scalars"
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
if (this.modeTabElements.errorMetrics) {
|
|
202
|
+
this.modeTabElements.errorMetrics.classList.toggle(
|
|
203
|
+
"active",
|
|
204
|
+
this.mode === "errorMetrics"
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
if (this.scalarPlotter) {
|
|
208
|
+
this.scalarPlotter.setVisible(this.isExpanded && this.mode === "scalars");
|
|
209
|
+
}
|
|
210
|
+
if (this.errorMetrics) {
|
|
211
|
+
this.errorMetrics.setVisible(this.isExpanded && this.mode === "errorMetrics");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
_switchMode(mode) {
|
|
216
|
+
if (mode === this.mode) return;
|
|
217
|
+
this.mode = mode;
|
|
218
|
+
this._applyMode();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
_toggleDropdown() {
|
|
222
|
+
this.isExpanded = !this.isExpanded;
|
|
223
|
+
this.content.classList.toggle("visible", this.isExpanded);
|
|
224
|
+
this.icon.textContent = this.isExpanded ? "▾" : "▸";
|
|
225
|
+
this._applyMode();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
dispose() {
|
|
229
|
+
if (this.container && this.container.parentElement) {
|
|
230
|
+
this.container.parentElement.removeChild(this.container);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|