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.
Files changed (163) hide show
  1. simview/__init__.py +69 -0
  2. simview/__main__.py +174 -0
  3. simview/launcher.py +109 -0
  4. simview/live.py +204 -0
  5. simview/merge.py +593 -0
  6. simview/model.py +690 -0
  7. simview/py.typed +0 -0
  8. simview/scene.py +603 -0
  9. simview/server.py +739 -0
  10. simview/state.py +190 -0
  11. simview/static/css/controls.css +36 -0
  12. simview/static/js/SimView.js +601 -0
  13. simview/static/js/components/AnimationController.js +654 -0
  14. simview/static/js/components/BatchManager.js +198 -0
  15. simview/static/js/components/InteractionController.js +446 -0
  16. simview/static/js/components/InteractionControls.js +116 -0
  17. simview/static/js/components/Scene.js +166 -0
  18. simview/static/js/components/StateStore.js +188 -0
  19. simview/static/js/config.js +233 -0
  20. simview/static/js/main.js +3 -0
  21. simview/static/js/objects/Body.js +616 -0
  22. simview/static/js/objects/StaticObject.js +181 -0
  23. simview/static/js/objects/Terrain.js +514 -0
  24. simview/static/js/objects/utils.js +378 -0
  25. simview/static/js/ui/AnalysisPanel.js +233 -0
  26. simview/static/js/ui/BatchLegend.js +207 -0
  27. simview/static/js/ui/BodyStateWindow.js +521 -0
  28. simview/static/js/ui/Controls.js +586 -0
  29. simview/static/js/ui/ErrorMetrics.js +664 -0
  30. simview/static/js/ui/Legend.js +87 -0
  31. simview/static/js/ui/PlaybackControls.js +314 -0
  32. simview/static/js/ui/ScalarPlotter.js +575 -0
  33. simview/static/js/utils/blobCodec.js +73 -0
  34. simview/static/js/utils/bodyTransforms.js +151 -0
  35. simview/static/js/utils/csv.js +46 -0
  36. simview/static/js/utils/errorMath.js +69 -0
  37. simview/static/js/utils/injectStyles.js +16 -0
  38. simview/static/js/utils/interpolate.js +65 -0
  39. simview/static/js/utils/liveFollow.js +17 -0
  40. simview/static/js/utils/loadRecordingLibs.js +34 -0
  41. simview/static/js/utils/viewState.js +226 -0
  42. simview/static/lib/chroma-js-3.1.2/index.min.js +8 -0
  43. simview/static/lib/chroma-js-3.1.2/src/Color.js +49 -0
  44. simview/static/lib/chroma-js-3.1.2/src/chroma.js +10 -0
  45. simview/static/lib/chroma-js-3.1.2/src/colors/colorbrewer.js +81 -0
  46. simview/static/lib/chroma-js-3.1.2/src/colors/w3cx11.js +164 -0
  47. simview/static/lib/chroma-js-3.1.2/src/generator/average.js +88 -0
  48. simview/static/lib/chroma-js-3.1.2/src/generator/bezier.js +86 -0
  49. simview/static/lib/chroma-js-3.1.2/src/generator/blend.js +57 -0
  50. simview/static/lib/chroma-js-3.1.2/src/generator/cubehelix.js +87 -0
  51. simview/static/lib/chroma-js-3.1.2/src/generator/mix.js +19 -0
  52. simview/static/lib/chroma-js-3.1.2/src/generator/random.js +12 -0
  53. simview/static/lib/chroma-js-3.1.2/src/generator/scale.js +394 -0
  54. simview/static/lib/chroma-js-3.1.2/src/interpolator/_hsx.js +59 -0
  55. simview/static/lib/chroma-js-3.1.2/src/interpolator/hcg.js +12 -0
  56. simview/static/lib/chroma-js-3.1.2/src/interpolator/hsi.js +12 -0
  57. simview/static/lib/chroma-js-3.1.2/src/interpolator/hsl.js +12 -0
  58. simview/static/lib/chroma-js-3.1.2/src/interpolator/hsv.js +12 -0
  59. simview/static/lib/chroma-js-3.1.2/src/interpolator/index.js +1 -0
  60. simview/static/lib/chroma-js-3.1.2/src/interpolator/lab.js +19 -0
  61. simview/static/lib/chroma-js-3.1.2/src/interpolator/lch.js +13 -0
  62. simview/static/lib/chroma-js-3.1.2/src/interpolator/lrgb.js +19 -0
  63. simview/static/lib/chroma-js-3.1.2/src/interpolator/num.js +15 -0
  64. simview/static/lib/chroma-js-3.1.2/src/interpolator/oklab.js +19 -0
  65. simview/static/lib/chroma-js-3.1.2/src/interpolator/oklch.js +12 -0
  66. simview/static/lib/chroma-js-3.1.2/src/interpolator/rgb.js +18 -0
  67. simview/static/lib/chroma-js-3.1.2/src/io/cmyk/cmyk2rgb.js +16 -0
  68. simview/static/lib/chroma-js-3.1.2/src/io/cmyk/index.js +27 -0
  69. simview/static/lib/chroma-js-3.1.2/src/io/cmyk/rgb2cmyk.js +17 -0
  70. simview/static/lib/chroma-js-3.1.2/src/io/css/css2rgb.js +238 -0
  71. simview/static/lib/chroma-js-3.1.2/src/io/css/hsl2css.js +26 -0
  72. simview/static/lib/chroma-js-3.1.2/src/io/css/index.js +27 -0
  73. simview/static/lib/chroma-js-3.1.2/src/io/css/lab2css.js +24 -0
  74. simview/static/lib/chroma-js-3.1.2/src/io/css/lch2css.js +24 -0
  75. simview/static/lib/chroma-js-3.1.2/src/io/css/oklab2css.js +16 -0
  76. simview/static/lib/chroma-js-3.1.2/src/io/css/oklch2css.js +16 -0
  77. simview/static/lib/chroma-js-3.1.2/src/io/css/rgb2css.js +61 -0
  78. simview/static/lib/chroma-js-3.1.2/src/io/gl/index.js +22 -0
  79. simview/static/lib/chroma-js-3.1.2/src/io/hcg/hcg2rgb.js +55 -0
  80. simview/static/lib/chroma-js-3.1.2/src/io/hcg/index.js +27 -0
  81. simview/static/lib/chroma-js-3.1.2/src/io/hcg/rgb2hcg.js +23 -0
  82. simview/static/lib/chroma-js-3.1.2/src/io/hex/hex2rgb.js +56 -0
  83. simview/static/lib/chroma-js-3.1.2/src/io/hex/index.js +29 -0
  84. simview/static/lib/chroma-js-3.1.2/src/io/hex/rgb2hex.js +29 -0
  85. simview/static/lib/chroma-js-3.1.2/src/io/hsi/hsi2rgb.js +45 -0
  86. simview/static/lib/chroma-js-3.1.2/src/io/hsi/index.js +27 -0
  87. simview/static/lib/chroma-js-3.1.2/src/io/hsi/rgb2hsi.js +31 -0
  88. simview/static/lib/chroma-js-3.1.2/src/io/hsl/hsl2rgb.js +35 -0
  89. simview/static/lib/chroma-js-3.1.2/src/io/hsl/index.js +27 -0
  90. simview/static/lib/chroma-js-3.1.2/src/io/hsl/rgb2hsl.js +45 -0
  91. simview/static/lib/chroma-js-3.1.2/src/io/hsv/hsv2rgb.js +47 -0
  92. simview/static/lib/chroma-js-3.1.2/src/io/hsv/index.js +27 -0
  93. simview/static/lib/chroma-js-3.1.2/src/io/hsv/rgb2hsv.js +32 -0
  94. simview/static/lib/chroma-js-3.1.2/src/io/input.js +4 -0
  95. simview/static/lib/chroma-js-3.1.2/src/io/lab/index.js +28 -0
  96. simview/static/lib/chroma-js-3.1.2/src/io/lab/lab-constants.js +120 -0
  97. simview/static/lib/chroma-js-3.1.2/src/io/lab/lab2rgb.js +101 -0
  98. simview/static/lib/chroma-js-3.1.2/src/io/lab/rgb2lab.js +67 -0
  99. simview/static/lib/chroma-js-3.1.2/src/io/lch/hcl2rgb.js +9 -0
  100. simview/static/lib/chroma-js-3.1.2/src/io/lch/index.js +35 -0
  101. simview/static/lib/chroma-js-3.1.2/src/io/lch/lab2lch.js +12 -0
  102. simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2lab.js +18 -0
  103. simview/static/lib/chroma-js-3.1.2/src/io/lch/lch2rgb.js +13 -0
  104. simview/static/lib/chroma-js-3.1.2/src/io/lch/rgb2lch.js +12 -0
  105. simview/static/lib/chroma-js-3.1.2/src/io/named/index.js +30 -0
  106. simview/static/lib/chroma-js-3.1.2/src/io/num/index.js +32 -0
  107. simview/static/lib/chroma-js-3.1.2/src/io/num/num2rgb.js +13 -0
  108. simview/static/lib/chroma-js-3.1.2/src/io/num/rgb2num.js +8 -0
  109. simview/static/lib/chroma-js-3.1.2/src/io/oklab/index.js +27 -0
  110. simview/static/lib/chroma-js-3.1.2/src/io/oklab/oklab2rgb.js +34 -0
  111. simview/static/lib/chroma-js-3.1.2/src/io/oklab/rgb2oklab.js +37 -0
  112. simview/static/lib/chroma-js-3.1.2/src/io/oklch/index.js +27 -0
  113. simview/static/lib/chroma-js-3.1.2/src/io/oklch/oklch2rgb.js +13 -0
  114. simview/static/lib/chroma-js-3.1.2/src/io/oklch/rgb2oklch.js +12 -0
  115. simview/static/lib/chroma-js-3.1.2/src/io/rgb/index.js +44 -0
  116. simview/static/lib/chroma-js-3.1.2/src/io/temp/index.js +22 -0
  117. simview/static/lib/chroma-js-3.1.2/src/io/temp/rgb2temperature.js +30 -0
  118. simview/static/lib/chroma-js-3.1.2/src/io/temp/temperature2rgb.js +39 -0
  119. simview/static/lib/chroma-js-3.1.2/src/ops/alpha.js +13 -0
  120. simview/static/lib/chroma-js-3.1.2/src/ops/clipped.js +5 -0
  121. simview/static/lib/chroma-js-3.1.2/src/ops/darken.js +17 -0
  122. simview/static/lib/chroma-js-3.1.2/src/ops/get.js +13 -0
  123. simview/static/lib/chroma-js-3.1.2/src/ops/luminance.js +54 -0
  124. simview/static/lib/chroma-js-3.1.2/src/ops/mix.js +10 -0
  125. simview/static/lib/chroma-js-3.1.2/src/ops/premultiply.js +12 -0
  126. simview/static/lib/chroma-js-3.1.2/src/ops/saturate.js +15 -0
  127. simview/static/lib/chroma-js-3.1.2/src/ops/set.js +43 -0
  128. simview/static/lib/chroma-js-3.1.2/src/ops/shade.js +11 -0
  129. simview/static/lib/chroma-js-3.1.2/src/utils/analyze.js +191 -0
  130. simview/static/lib/chroma-js-3.1.2/src/utils/clip_rgb.js +15 -0
  131. simview/static/lib/chroma-js-3.1.2/src/utils/contrast.js +12 -0
  132. simview/static/lib/chroma-js-3.1.2/src/utils/contrastAPCA.js +68 -0
  133. simview/static/lib/chroma-js-3.1.2/src/utils/delta-e.js +62 -0
  134. simview/static/lib/chroma-js-3.1.2/src/utils/distance.js +17 -0
  135. simview/static/lib/chroma-js-3.1.2/src/utils/index.js +27 -0
  136. simview/static/lib/chroma-js-3.1.2/src/utils/last.js +8 -0
  137. simview/static/lib/chroma-js-3.1.2/src/utils/limit.js +5 -0
  138. simview/static/lib/chroma-js-3.1.2/src/utils/multiply-matrices.js +36 -0
  139. simview/static/lib/chroma-js-3.1.2/src/utils/scales.js +15 -0
  140. simview/static/lib/chroma-js-3.1.2/src/utils/type.js +18 -0
  141. simview/static/lib/chroma-js-3.1.2/src/utils/unpack.js +17 -0
  142. simview/static/lib/chroma-js-3.1.2/src/utils/valid.js +11 -0
  143. simview/static/lib/chroma-js-3.1.2/src/version.js +2 -0
  144. simview/static/lib/download.js +132 -0
  145. simview/static/lib/js-colormaps.js +19335 -0
  146. simview/static/lib/tar.js +334 -0
  147. simview/static/lib/three-0.174.0/addons/controls/OrbitControls.js +1556 -0
  148. simview/static/lib/three-0.174.0/addons/libs/lil-gui.module.min.js +8 -0
  149. simview/static/lib/three-0.174.0/three.core.js +48830 -0
  150. simview/static/lib/three-0.174.0/three.module.js +17313 -0
  151. simview/static/lib/uPlot.esm.js +6140 -0
  152. simview/static/lib/uPlot.min.css +2 -0
  153. simview/static/textures/contacts/red-cross0.png +0 -0
  154. simview/static/textures/points/ball0.png +0 -0
  155. simview/static/textures/points/ball1.png +0 -0
  156. simview/templates/index.html +81 -0
  157. simview/utils.py +41 -0
  158. simview-3.2.dist-info/METADATA +552 -0
  159. simview-3.2.dist-info/RECORD +163 -0
  160. simview-3.2.dist-info/WHEEL +5 -0
  161. simview-3.2.dist-info/entry_points.txt +2 -0
  162. simview-3.2.dist-info/licenses/LICENSE +28 -0
  163. 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
+ }