matterviz 0.3.1 → 0.3.3

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 (358) hide show
  1. package/dist/EmptyState.svelte +10 -2
  2. package/dist/FilePicker.svelte +154 -96
  3. package/dist/Icon.svelte +20 -14
  4. package/dist/MillerIndexInput.svelte +27 -21
  5. package/dist/api/optimade.js +6 -6
  6. package/dist/app.css +216 -178
  7. package/dist/brillouin/BrillouinZone.svelte +299 -198
  8. package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
  9. package/dist/brillouin/BrillouinZoneControls.svelte +32 -5
  10. package/dist/brillouin/BrillouinZoneExportPane.svelte +74 -55
  11. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
  12. package/dist/brillouin/BrillouinZoneInfoPane.svelte +99 -68
  13. package/dist/brillouin/BrillouinZoneScene.svelte +277 -165
  14. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
  15. package/dist/brillouin/BrillouinZoneTooltip.svelte +17 -7
  16. package/dist/brillouin/compute.js +11 -6
  17. package/dist/chempot-diagram/ChemPotDiagram.svelte +327 -0
  18. package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
  19. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +847 -0
  20. package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
  21. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +3194 -0
  22. package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
  23. package/dist/chempot-diagram/ChemPotScene3D.svelte +11 -0
  24. package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
  25. package/dist/chempot-diagram/async-compute.svelte.d.ts +3 -0
  26. package/dist/chempot-diagram/async-compute.svelte.js +77 -0
  27. package/dist/chempot-diagram/chempot-worker.d.ts +1 -0
  28. package/dist/chempot-diagram/chempot-worker.js +11 -0
  29. package/dist/chempot-diagram/color.d.ts +10 -0
  30. package/dist/chempot-diagram/color.js +32 -0
  31. package/dist/chempot-diagram/compute.d.ts +48 -0
  32. package/dist/chempot-diagram/compute.js +812 -0
  33. package/dist/chempot-diagram/index.d.ts +6 -0
  34. package/dist/chempot-diagram/index.js +6 -0
  35. package/dist/chempot-diagram/pointer.d.ts +16 -0
  36. package/dist/chempot-diagram/pointer.js +40 -0
  37. package/dist/chempot-diagram/temperature.d.ts +15 -0
  38. package/dist/chempot-diagram/temperature.js +36 -0
  39. package/dist/chempot-diagram/types.d.ts +86 -0
  40. package/dist/chempot-diagram/types.js +28 -0
  41. package/dist/colors/index.d.ts +3 -1
  42. package/dist/colors/index.js +9 -3
  43. package/dist/composition/BarChart.svelte +141 -77
  44. package/dist/composition/BubbleChart.svelte +107 -52
  45. package/dist/composition/Composition.svelte +100 -79
  46. package/dist/composition/Formula.svelte +108 -62
  47. package/dist/composition/FormulaFilter.svelte +973 -353
  48. package/dist/composition/FormulaFilter.svelte.d.ts +35 -1
  49. package/dist/composition/PieChart.svelte +199 -99
  50. package/dist/composition/PieChart.svelte.d.ts +1 -1
  51. package/dist/composition/format.d.ts +5 -0
  52. package/dist/composition/format.js +20 -3
  53. package/dist/composition/parse.js +14 -9
  54. package/dist/convex-hull/ConvexHull.svelte +93 -38
  55. package/dist/convex-hull/ConvexHull2D.svelte +551 -393
  56. package/dist/convex-hull/ConvexHull3D.svelte +1303 -825
  57. package/dist/convex-hull/ConvexHull4D.svelte +1012 -686
  58. package/dist/convex-hull/ConvexHullControls.svelte +115 -28
  59. package/dist/convex-hull/ConvexHullInfoPane.svelte +29 -3
  60. package/dist/convex-hull/ConvexHullStats.svelte +821 -249
  61. package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
  62. package/dist/convex-hull/ConvexHullTooltip.svelte +41 -16
  63. package/dist/convex-hull/GasPressureControls.svelte +104 -61
  64. package/dist/convex-hull/StructurePopup.svelte +25 -4
  65. package/dist/convex-hull/TemperatureSlider.svelte +45 -25
  66. package/dist/convex-hull/barycentric-coords.js +13 -7
  67. package/dist/convex-hull/demo-temperature.d.ts +6 -0
  68. package/dist/convex-hull/demo-temperature.js +40 -0
  69. package/dist/convex-hull/gas-thermodynamics.js +17 -12
  70. package/dist/convex-hull/helpers.d.ts +10 -1
  71. package/dist/convex-hull/helpers.js +79 -38
  72. package/dist/convex-hull/index.d.ts +1 -0
  73. package/dist/convex-hull/index.js +1 -0
  74. package/dist/convex-hull/thermodynamics.d.ts +8 -21
  75. package/dist/convex-hull/thermodynamics.js +163 -69
  76. package/dist/convex-hull/types.d.ts +12 -12
  77. package/dist/convex-hull/types.js +0 -12
  78. package/dist/coordination/CoordinationBarPlot.svelte +232 -176
  79. package/dist/element/BohrAtom.svelte +56 -13
  80. package/dist/element/ElementHeading.svelte +7 -2
  81. package/dist/element/ElementPhoto.svelte +15 -9
  82. package/dist/element/ElementStats.svelte +10 -4
  83. package/dist/element/ElementTile.svelte +137 -73
  84. package/dist/element/Nucleus.svelte +39 -11
  85. package/dist/element/data.js +2 -14
  86. package/dist/element/data.json.gz +0 -0
  87. package/dist/element/types.d.ts +1 -0
  88. package/dist/feedback/ClickFeedback.svelte +16 -5
  89. package/dist/feedback/DragOverlay.svelte +10 -2
  90. package/dist/feedback/Spinner.svelte +4 -2
  91. package/dist/feedback/StatusMessage.svelte +8 -2
  92. package/dist/fermi-surface/FermiSlice.svelte +118 -88
  93. package/dist/fermi-surface/FermiSurface.svelte +336 -239
  94. package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
  95. package/dist/fermi-surface/FermiSurfaceControls.svelte +113 -46
  96. package/dist/fermi-surface/FermiSurfaceScene.svelte +536 -343
  97. package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
  98. package/dist/fermi-surface/FermiSurfaceTooltip.svelte +14 -5
  99. package/dist/fermi-surface/compute.js +16 -20
  100. package/dist/fermi-surface/parse.js +37 -33
  101. package/dist/fermi-surface/symmetry.js +2 -7
  102. package/dist/fermi-surface/types.d.ts +3 -5
  103. package/dist/heatmap-matrix/HeatmapMatrix.svelte +1527 -0
  104. package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
  105. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +225 -0
  106. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +30 -0
  107. package/dist/heatmap-matrix/index.d.ts +53 -0
  108. package/dist/heatmap-matrix/index.js +100 -0
  109. package/dist/heatmap-matrix/shared.d.ts +2 -0
  110. package/dist/heatmap-matrix/shared.js +4 -0
  111. package/dist/icons.d.ts +111 -0
  112. package/dist/icons.js +158 -0
  113. package/dist/index.d.ts +5 -2
  114. package/dist/index.js +5 -2
  115. package/dist/io/decompress.js +1 -1
  116. package/dist/io/export.d.ts +3 -0
  117. package/dist/io/export.js +138 -140
  118. package/dist/io/file-drop.d.ts +7 -0
  119. package/dist/io/file-drop.js +43 -0
  120. package/dist/io/index.d.ts +2 -2
  121. package/dist/io/index.js +2 -112
  122. package/dist/io/is-binary.js +2 -3
  123. package/dist/io/types.d.ts +1 -0
  124. package/dist/io/url-drop.d.ts +2 -0
  125. package/dist/io/url-drop.js +117 -0
  126. package/dist/isosurface/Isosurface.svelte +220 -110
  127. package/dist/isosurface/IsosurfaceControls.svelte +65 -28
  128. package/dist/isosurface/parse.js +104 -56
  129. package/dist/isosurface/slice.d.ts +2 -1
  130. package/dist/isosurface/slice.js +8 -13
  131. package/dist/isosurface/types.d.ts +14 -1
  132. package/dist/isosurface/types.js +152 -5
  133. package/dist/labels.d.ts +2 -1
  134. package/dist/labels.js +12 -8
  135. package/dist/layout/FullscreenToggle.svelte +11 -2
  136. package/dist/layout/InfoCard.svelte +38 -6
  137. package/dist/layout/InfoTag.svelte +125 -94
  138. package/dist/layout/PropertyFilter.svelte +82 -37
  139. package/dist/layout/SettingsSection.svelte +85 -55
  140. package/dist/layout/SubpageGrid.svelte +82 -0
  141. package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
  142. package/dist/layout/index.d.ts +1 -0
  143. package/dist/layout/index.js +1 -0
  144. package/dist/layout/json-tree/JsonNode.svelte +266 -223
  145. package/dist/layout/json-tree/JsonTree.svelte +516 -429
  146. package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
  147. package/dist/layout/json-tree/JsonValue.svelte +281 -173
  148. package/dist/layout/json-tree/types.d.ts +10 -2
  149. package/dist/layout/json-tree/utils.d.ts +2 -0
  150. package/dist/layout/json-tree/utils.js +37 -2
  151. package/dist/marching-cubes.js +25 -2
  152. package/dist/math.d.ts +20 -17
  153. package/dist/math.js +474 -57
  154. package/dist/overlays/ContextMenu.svelte +66 -40
  155. package/dist/overlays/DraggablePane.svelte +331 -154
  156. package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
  157. package/dist/periodic-table/PeriodicTable.svelte +278 -145
  158. package/dist/periodic-table/PeriodicTableControls.svelte +178 -128
  159. package/dist/periodic-table/PropertySelect.svelte +25 -7
  160. package/dist/periodic-table/TableInset.svelte +8 -3
  161. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +559 -267
  162. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
  163. package/dist/phase-diagram/PhaseDiagramControls.svelte +131 -51
  164. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
  165. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +126 -0
  166. package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
  167. package/dist/phase-diagram/PhaseDiagramExportPane.svelte +160 -110
  168. package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +8 -1
  169. package/dist/phase-diagram/PhaseDiagramTooltip.svelte +217 -86
  170. package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
  171. package/dist/phase-diagram/TdbInfoPanel.svelte +28 -4
  172. package/dist/phase-diagram/build-diagram.js +9 -9
  173. package/dist/phase-diagram/colors.js +1 -3
  174. package/dist/phase-diagram/index.d.ts +2 -0
  175. package/dist/phase-diagram/index.js +2 -0
  176. package/dist/phase-diagram/parse.js +10 -9
  177. package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
  178. package/dist/phase-diagram/svg-to-diagram.js +869 -0
  179. package/dist/phase-diagram/types.d.ts +10 -0
  180. package/dist/phase-diagram/utils.d.ts +8 -4
  181. package/dist/phase-diagram/utils.js +219 -74
  182. package/dist/plot/AxisLabel.svelte +51 -0
  183. package/dist/plot/AxisLabel.svelte.d.ts +16 -0
  184. package/dist/plot/BarPlot.svelte +1461 -768
  185. package/dist/plot/BarPlot.svelte.d.ts +3 -3
  186. package/dist/plot/BarPlotControls.svelte +33 -6
  187. package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
  188. package/dist/plot/ColorBar.svelte +533 -383
  189. package/dist/plot/ColorBar.svelte.d.ts +1 -1
  190. package/dist/plot/ColorScaleSelect.svelte +28 -7
  191. package/dist/plot/ElementScatter.svelte +38 -16
  192. package/dist/plot/FillArea.svelte +152 -92
  193. package/dist/plot/Histogram.svelte +1162 -709
  194. package/dist/plot/Histogram.svelte.d.ts +1 -1
  195. package/dist/plot/HistogramControls.svelte +81 -18
  196. package/dist/plot/HistogramControls.svelte.d.ts +6 -2
  197. package/dist/plot/InteractiveAxisLabel.svelte +34 -11
  198. package/dist/plot/InteractiveAxisLabel.svelte.d.ts +1 -1
  199. package/dist/plot/Line.svelte +63 -28
  200. package/dist/plot/PlotControls.svelte +221 -96
  201. package/dist/plot/PlotControls.svelte.d.ts +1 -1
  202. package/dist/plot/PlotLegend.svelte +174 -91
  203. package/dist/plot/PlotTooltip.svelte +45 -6
  204. package/dist/plot/PortalSelect.svelte +175 -146
  205. package/dist/plot/ReferenceLine.svelte +77 -22
  206. package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
  207. package/dist/plot/ReferenceLine3D.svelte +132 -107
  208. package/dist/plot/ReferencePlane.svelte +146 -123
  209. package/dist/plot/ScatterPlot.svelte +1880 -1156
  210. package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
  211. package/dist/plot/ScatterPlot3D.svelte +256 -131
  212. package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
  213. package/dist/plot/ScatterPlot3DControls.svelte +300 -297
  214. package/dist/plot/ScatterPlot3DControls.svelte.d.ts +2 -1
  215. package/dist/plot/ScatterPlot3DScene.svelte +608 -406
  216. package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
  217. package/dist/plot/ScatterPlotControls.svelte +150 -70
  218. package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
  219. package/dist/plot/ScatterPoint.svelte +98 -26
  220. package/dist/plot/ScatterPoint.svelte.d.ts +1 -0
  221. package/dist/plot/SpacegroupBarPlot.svelte +142 -85
  222. package/dist/plot/Surface3D.svelte +159 -108
  223. package/dist/plot/ZeroLines.svelte +96 -0
  224. package/dist/plot/ZeroLines.svelte.d.ts +32 -0
  225. package/dist/plot/ZoomRect.svelte +23 -0
  226. package/dist/plot/ZoomRect.svelte.d.ts +8 -0
  227. package/dist/plot/axis-utils.d.ts +1 -1
  228. package/dist/plot/axis-utils.js +1 -3
  229. package/dist/plot/data-cleaning.js +12 -28
  230. package/dist/plot/data-transform.js +2 -1
  231. package/dist/plot/fill-utils.js +2 -0
  232. package/dist/plot/index.d.ts +6 -2
  233. package/dist/plot/index.js +6 -2
  234. package/dist/plot/interactions.d.ts +8 -10
  235. package/dist/plot/interactions.js +2 -3
  236. package/dist/plot/layout.d.ts +11 -2
  237. package/dist/plot/layout.js +44 -17
  238. package/dist/plot/reference-line.d.ts +5 -22
  239. package/dist/plot/reference-line.js +12 -84
  240. package/dist/plot/scales.js +24 -36
  241. package/dist/plot/types.d.ts +53 -40
  242. package/dist/plot/types.js +12 -7
  243. package/dist/plot/utils/label-placement.d.ts +32 -15
  244. package/dist/plot/utils/label-placement.js +227 -63
  245. package/dist/plot/utils/series-visibility.js +2 -3
  246. package/dist/plot/utils.d.ts +1 -0
  247. package/dist/plot/utils.js +14 -0
  248. package/dist/rdf/RdfPlot.svelte +173 -132
  249. package/dist/rdf/calc-rdf.js +4 -5
  250. package/dist/sanitize.d.ts +4 -0
  251. package/dist/sanitize.js +107 -0
  252. package/dist/settings.d.ts +21 -6
  253. package/dist/settings.js +63 -19
  254. package/dist/spectral/Bands.svelte +963 -412
  255. package/dist/spectral/Bands.svelte.d.ts +22 -2
  256. package/dist/spectral/BandsAndDos.svelte +90 -49
  257. package/dist/spectral/BrillouinBandsDos.svelte +151 -93
  258. package/dist/spectral/Dos.svelte +389 -258
  259. package/dist/spectral/helpers.d.ts +23 -1
  260. package/dist/spectral/helpers.js +119 -51
  261. package/dist/spectral/types.d.ts +2 -0
  262. package/dist/state.svelte.d.ts +1 -1
  263. package/dist/state.svelte.js +3 -2
  264. package/dist/structure/Arrow.svelte +59 -20
  265. package/dist/structure/AtomLegend.svelte +231 -129
  266. package/dist/structure/AtomLegend.svelte.d.ts +1 -1
  267. package/dist/structure/Bond.svelte +73 -47
  268. package/dist/structure/CanvasTooltip.svelte +10 -2
  269. package/dist/structure/CellSelect.svelte +148 -51
  270. package/dist/structure/Cylinder.svelte +33 -17
  271. package/dist/structure/Lattice.svelte +88 -33
  272. package/dist/structure/Structure.svelte +1077 -821
  273. package/dist/structure/Structure.svelte.d.ts +1 -1
  274. package/dist/structure/StructureControls.svelte +373 -139
  275. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  276. package/dist/structure/StructureExportPane.svelte +124 -89
  277. package/dist/structure/StructureExportPane.svelte.d.ts +1 -1
  278. package/dist/structure/StructureInfoPane.svelte +304 -231
  279. package/dist/structure/StructureScene.svelte +919 -445
  280. package/dist/structure/StructureScene.svelte.d.ts +16 -7
  281. package/dist/structure/atom-properties.d.ts +6 -2
  282. package/dist/structure/atom-properties.js +42 -29
  283. package/dist/structure/bonding.js +6 -7
  284. package/dist/structure/export.js +22 -34
  285. package/dist/structure/ferrox-wasm-types.d.ts +3 -2
  286. package/dist/structure/ferrox-wasm-types.js +0 -3
  287. package/dist/structure/ferrox-wasm.d.ts +3 -2
  288. package/dist/structure/ferrox-wasm.js +2 -3
  289. package/dist/structure/index.d.ts +16 -0
  290. package/dist/structure/index.js +88 -6
  291. package/dist/structure/measure.d.ts +2 -2
  292. package/dist/structure/measure.js +4 -44
  293. package/dist/structure/parse.js +130 -155
  294. package/dist/structure/partial-occupancy.d.ts +25 -0
  295. package/dist/structure/partial-occupancy.js +99 -0
  296. package/dist/structure/pbc.d.ts +1 -0
  297. package/dist/structure/pbc.js +16 -6
  298. package/dist/structure/supercell.d.ts +2 -2
  299. package/dist/structure/supercell.js +12 -22
  300. package/dist/structure/validation.js +5 -3
  301. package/dist/symmetry/SymmetryStats.svelte +94 -37
  302. package/dist/symmetry/WyckoffTable.svelte +42 -14
  303. package/dist/symmetry/cell-transform.js +5 -3
  304. package/dist/symmetry/index.d.ts +7 -4
  305. package/dist/symmetry/index.js +87 -21
  306. package/dist/symmetry/spacegroups.js +148 -148
  307. package/dist/table/HeatmapTable.svelte +1112 -516
  308. package/dist/table/HeatmapTable.svelte.d.ts +12 -1
  309. package/dist/table/ToggleMenu.svelte +125 -90
  310. package/dist/table/index.d.ts +2 -0
  311. package/dist/table/index.js +2 -4
  312. package/dist/theme/ThemeControl.svelte +21 -12
  313. package/dist/time.js +4 -1
  314. package/dist/tooltip/TooltipContent.svelte +33 -8
  315. package/dist/trajectory/Trajectory.svelte +889 -687
  316. package/dist/trajectory/TrajectoryError.svelte +14 -3
  317. package/dist/trajectory/TrajectoryExportPane.svelte +148 -90
  318. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
  319. package/dist/trajectory/TrajectoryInfoPane.svelte +272 -143
  320. package/dist/trajectory/constants.d.ts +6 -0
  321. package/dist/trajectory/constants.js +7 -0
  322. package/dist/trajectory/extract.js +13 -31
  323. package/dist/trajectory/format-detect.d.ts +9 -0
  324. package/dist/trajectory/format-detect.js +76 -0
  325. package/dist/trajectory/frame-reader.d.ts +17 -0
  326. package/dist/trajectory/frame-reader.js +332 -0
  327. package/dist/trajectory/helpers.d.ts +14 -0
  328. package/dist/trajectory/helpers.js +172 -0
  329. package/dist/trajectory/index.d.ts +1 -0
  330. package/dist/trajectory/index.js +23 -14
  331. package/dist/trajectory/parse/ase.d.ts +2 -0
  332. package/dist/trajectory/parse/ase.js +77 -0
  333. package/dist/trajectory/parse/hdf5.d.ts +2 -0
  334. package/dist/trajectory/parse/hdf5.js +129 -0
  335. package/dist/trajectory/parse/index.d.ts +12 -0
  336. package/dist/trajectory/parse/index.js +299 -0
  337. package/dist/trajectory/parse/lammps.d.ts +5 -0
  338. package/dist/trajectory/parse/lammps.js +179 -0
  339. package/dist/trajectory/parse/vasp.d.ts +2 -0
  340. package/dist/trajectory/parse/vasp.js +68 -0
  341. package/dist/trajectory/parse/xyz.d.ts +2 -0
  342. package/dist/trajectory/parse/xyz.js +110 -0
  343. package/dist/trajectory/plotting.js +13 -8
  344. package/dist/trajectory/types.d.ts +11 -0
  345. package/dist/trajectory/types.js +1 -0
  346. package/dist/utils.d.ts +3 -0
  347. package/dist/utils.js +17 -0
  348. package/dist/xrd/XrdPlot.svelte +337 -245
  349. package/dist/xrd/broadening.js +14 -9
  350. package/dist/xrd/calc-xrd.js +12 -19
  351. package/dist/xrd/parse.d.ts +1 -1
  352. package/dist/xrd/parse.js +17 -17
  353. package/package.json +103 -101
  354. package/readme.md +4 -4
  355. package/dist/trajectory/parse.d.ts +0 -42
  356. package/dist/trajectory/parse.js +0 -1267
  357. /package/dist/element/{data.json.d.ts → data.json.gz.d.ts} +0 -0
  358. /package/dist/theme/{themes.js → themes.mjs} +0 -0
@@ -0,0 +1,117 @@
1
+ import { load_binary_traj } from '../trajectory/parse';
2
+ const BINARY_EXTENSIONS = new Set(`h5 hdf5 traj npz pkl dat gz gzip zip bz2 xz brml`.split(` `));
3
+ const TEXT_EXTENSIONS = new Set(`xyz extxyz json cif poscar yaml yml txt md py js ts css html xml`.split(` `));
4
+ const VASP_BASENAME_RE = /^(poscar|xdatcar|contcar)$/i;
5
+ // Extract filename from Content-Disposition header, falling back to url_basename.
6
+ function extract_filename(headers, fallback) {
7
+ if (!headers)
8
+ return fallback;
9
+ const content_disposition_str = headers.get(`content-disposition`);
10
+ if (!content_disposition_str)
11
+ return fallback;
12
+ const star_match = /filename\*=(?:UTF-8''|)([^;]+)/i.exec(content_disposition_str);
13
+ if (star_match?.[1]) {
14
+ const raw = star_match[1].trim().replace(/^"|"$/g, ``);
15
+ try {
16
+ return decodeURIComponent(raw);
17
+ }
18
+ catch {
19
+ return raw;
20
+ }
21
+ }
22
+ const plain_match = /filename\s*=\s*"?([^";]+)"?/i.exec(content_disposition_str);
23
+ return plain_match?.[1]?.trim() || fallback;
24
+ }
25
+ // Handle URL-based file drop data by fetching content lazily
26
+ export async function handle_url_drop(drag_event, callback) {
27
+ const json_data = drag_event.dataTransfer?.getData(`application/json`);
28
+ if (!json_data)
29
+ return false;
30
+ let file_info;
31
+ try {
32
+ file_info = JSON.parse(json_data);
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ if (!file_info.url)
38
+ return false;
39
+ await load_from_url(file_info.url, callback);
40
+ return true;
41
+ }
42
+ export async function load_from_url(url, callback) {
43
+ const url_basename = url.split(`/`).pop() || url;
44
+ const ext = url_basename.split(`.`).pop()?.toLowerCase() || ``;
45
+ if (BINARY_EXTENSIONS.has(ext)) {
46
+ // Force binary mode for known binary files to handle GitHub Pages content-type issues
47
+ const resp = await fetch(url);
48
+ if (!resp.ok)
49
+ throw new Error(`Fetch failed: ${resp.status}`);
50
+ const filename = extract_filename(resp.headers, url_basename);
51
+ // Handle gzipped files with proper content-encoding detection
52
+ if (ext === `gz` || ext === `gzip`) {
53
+ if (resp.headers.get(`content-encoding`) === `gzip`) {
54
+ // Browser automatically decompressed it, so it's text
55
+ return callback(await resp.text(), filename);
56
+ }
57
+ else {
58
+ // Need to decompress manually
59
+ const { decompress_data } = await import(`./decompress`);
60
+ const buffer = await resp.arrayBuffer();
61
+ const content = await decompress_data(buffer, `gzip`);
62
+ // Remove .gz extension when manually decompressing
63
+ return callback(content, filename.replace(/\.gz$/, ``));
64
+ }
65
+ }
66
+ // For H5 files, always load as binary regardless of signature
67
+ // to handle files that have .h5/.hdf5 extensions but may not have the proper HDF5 signature
68
+ if (ext === `h5` || ext === `hdf5`) {
69
+ const result = await load_binary_traj(resp, `H5`, true);
70
+ // Log warning if signature doesn't match (only for ArrayBuffer results)
71
+ if (result instanceof ArrayBuffer && result.byteLength >= 8) {
72
+ const view = new Uint8Array(result.slice(0, 8));
73
+ const hdf5_signature = [0x89, 0x48, 0x44, 0x46, 0x0d, 0x0a, 0x1a, 0x0a];
74
+ if (!hdf5_signature.every((byte, idx) => view[idx] === byte)) {
75
+ console.warn(`File has .h5/.hdf5 extension but missing HDF5 signature`);
76
+ }
77
+ }
78
+ return callback(result, filename);
79
+ }
80
+ // For .traj files, ensure we always get ArrayBuffer for proper ASE parsing
81
+ if (ext === `traj`) {
82
+ const buffer = await load_binary_traj(resp, `.traj`);
83
+ return callback(buffer, filename);
84
+ }
85
+ if (resp.headers.get(`content-encoding`) === `gzip`) {
86
+ return callback(await resp.text(), filename);
87
+ }
88
+ return callback(await resp.arrayBuffer(), filename);
89
+ }
90
+ // Skip Range requests for known text formats to avoid production server issues
91
+ // Include VASP files that don't have extensions (POSCAR, XDATCAR, CONTCAR)
92
+ const is_known_text = TEXT_EXTENSIONS.has(ext) || VASP_BASENAME_RE.test(url_basename);
93
+ if (!is_known_text) {
94
+ try {
95
+ // Check for magic bytes only for unknown formats
96
+ const head = await fetch(url, { headers: { Range: `bytes=0-15` } });
97
+ if (head.ok) {
98
+ const buf = new Uint8Array(await head.arrayBuffer());
99
+ const is_gzip = buf[0] === 0x1f && buf[1] === 0x8b;
100
+ const is_hdf5 = buf[0] === 0x89 && buf[1] === 0x48 && buf[2] === 0x44 && buf[3] === 0x46;
101
+ if (is_gzip || is_hdf5) {
102
+ const resp = await fetch(url);
103
+ if (!resp.ok)
104
+ throw new Error(`Fetch failed: ${resp.status}`);
105
+ return callback(await resp.arrayBuffer(), extract_filename(resp.headers, url_basename));
106
+ }
107
+ }
108
+ }
109
+ catch {
110
+ // Fall through to text fetch if HEAD request fails
111
+ }
112
+ }
113
+ const resp = await fetch(url);
114
+ if (!resp.ok)
115
+ throw new Error(`Fetch failed: ${resp.status}`);
116
+ return callback(await resp.text(), extract_filename(resp.headers, url_basename));
117
+ }
@@ -1,122 +1,232 @@
1
- <script lang="ts">// Threlte component that renders isosurface meshes from volumetric data using marching cubes.
2
- // Supports multiple layers at different isovalues with independent colors,
3
- // plus positive/negative lobes and two-pass transparency.
4
- import { marching_cubes } from '../marching-cubes';
5
- import { T } from '@threlte/core';
6
- import { BackSide, BufferAttribute, BufferGeometry, DoubleSide, FrontSide, Uint32BufferAttribute, } from 'three';
7
- import { DEFAULT_ISOSURFACE_SETTINGS } from './types';
8
- let { volume, settings = DEFAULT_ISOSURFACE_SETTINGS, } = $props();
9
- // Resolve layers: use explicit layers array if provided, else build from single-isovalue settings
10
- let resolved_layers = $derived.by(() => {
11
- if (settings.layers?.length)
12
- return settings.layers;
1
+ <script lang="ts">
2
+ // Threlte component that renders isosurface meshes from volumetric data using marching cubes.
3
+ // Supports multiple layers at different isovalues with independent colors,
4
+ // plus positive/negative lobes and two-pass transparency.
5
+ import { marching_cubes } from '../marching-cubes'
6
+ import type { Matrix3x3, Vec3 } from '../math'
7
+ import { T } from '@threlte/core'
8
+ import {
9
+ BackSide,
10
+ BufferAttribute,
11
+ BufferGeometry,
12
+ DoubleSide,
13
+ FrontSide,
14
+ Uint32BufferAttribute,
15
+ } from 'three'
16
+ import type { IsosurfaceLayer, IsosurfaceSettings, VolumetricData } from './types'
17
+ import {
18
+ DEFAULT_ISOSURFACE_SETTINGS,
19
+ downsample_grid,
20
+ pad_periodic_grid,
21
+ } from './types'
22
+
23
+ let { volume, settings = DEFAULT_ISOSURFACE_SETTINGS }: {
24
+ volume: VolumetricData
25
+ settings?: IsosurfaceSettings
26
+ } = $props()
27
+
28
+ // Resolve layers: use explicit layers array if provided, else build from single-isovalue settings
29
+ let resolved_layers = $derived.by((): IsosurfaceLayer[] => {
30
+ if (settings.layers?.length) return settings.layers
13
31
  return [{
14
- isovalue: settings.isovalue,
15
- color: settings.positive_color,
16
- opacity: settings.opacity,
17
- visible: true,
18
- show_negative: settings.show_negative,
19
- negative_color: settings.negative_color,
20
- }];
21
- });
22
- // Build indexed BufferGeometry from marching cubes output.
23
- // Uses Three.js index buffer to avoid tripling vertex data, and
24
- // computeVertexNormals() for fast GPU-friendly normals.
25
- function build_geometry(vertices, faces) {
26
- if (vertices.length === 0 || faces.length === 0)
27
- return null;
32
+ isovalue: settings.isovalue,
33
+ color: settings.positive_color,
34
+ opacity: settings.opacity,
35
+ visible: true,
36
+ show_negative: settings.show_negative,
37
+ negative_color: settings.negative_color,
38
+ }]
39
+ })
40
+
41
+ // Build indexed BufferGeometry from marching cubes output.
42
+ // Uses Three.js index buffer to avoid tripling vertex data, and
43
+ // computeVertexNormals() for fast GPU-friendly normals.
44
+ function build_geometry(
45
+ vertices: Vec3[],
46
+ faces: number[][],
47
+ ): BufferGeometry | null {
48
+ if (vertices.length === 0 || faces.length === 0) return null
49
+
28
50
  // Flatten vertices: Vec3[] → Float32Array
29
- const positions = new Float32Array(vertices.length * 3);
51
+ const positions = new Float32Array(vertices.length * 3)
30
52
  for (let idx = 0; idx < vertices.length; idx++) {
31
- const vert = vertices[idx];
32
- positions[idx * 3] = vert[0];
33
- positions[idx * 3 + 1] = vert[1];
34
- positions[idx * 3 + 2] = vert[2];
53
+ const vert = vertices[idx]
54
+ positions[idx * 3] = vert[0]
55
+ positions[idx * 3 + 1] = vert[1]
56
+ positions[idx * 3 + 2] = vert[2]
35
57
  }
58
+
36
59
  // Flatten face indices: number[][] → Uint32Array
37
- const indices = new Uint32Array(faces.length * 3);
60
+ const indices = new Uint32Array(faces.length * 3)
38
61
  for (let idx = 0; idx < faces.length; idx++) {
39
- const face = faces[idx];
40
- indices[idx * 3] = face[0];
41
- indices[idx * 3 + 1] = face[1];
42
- indices[idx * 3 + 2] = face[2];
62
+ const face = faces[idx]
63
+ indices[idx * 3] = face[0]
64
+ indices[idx * 3 + 1] = face[1]
65
+ indices[idx * 3 + 2] = face[2]
43
66
  }
44
- const geometry = new BufferGeometry();
45
- geometry.setAttribute(`position`, new BufferAttribute(positions, 3));
46
- geometry.setIndex(new Uint32BufferAttribute(indices, 1));
47
- geometry.computeVertexNormals();
48
- geometry.computeBoundingSphere();
49
- return geometry;
50
- }
51
- // Run marching cubes at the given isovalue.
52
- // Uses volume.periodic for correct coordinate scaling:
53
- // periodic grids (CHGCAR) use 1/N spacing, non-periodic (.cube) use 1/(N-1).
54
- function extract_surface(isovalue) {
55
- if (!volume || isovalue === 0)
56
- return null;
57
- const result = marching_cubes(volume.grid, isovalue, volume.lattice, {
58
- periodic: volume.periodic,
59
- interpolate: true,
60
- centered: false,
61
- normals: false,
62
- });
63
- return build_geometry(result.vertices, result.faces);
64
- }
65
- let active_geometries = $state([]);
66
- let raf_id = 0;
67
- // Dispose all current geometries
68
- function dispose_all() {
69
- for (const entry of active_geometries)
70
- entry.geometry.dispose();
71
- active_geometries = [];
72
- }
73
- // Dispose on unmount
74
- $effect(() => () => dispose_all());
75
- // Rebuild all layer geometries on next animation frame when layers or volume change
76
- $effect(() => {
77
- const layers = resolved_layers;
78
- const vol = volume;
79
- if (!vol) {
80
- dispose_all();
81
- return;
67
+
68
+ const geometry = new BufferGeometry()
69
+ geometry.setAttribute(`position`, new BufferAttribute(positions, 3))
70
+ geometry.setIndex(new Uint32BufferAttribute(indices, 1))
71
+ geometry.computeVertexNormals()
72
+ geometry.computeBoundingSphere()
73
+ return geometry
74
+ }
75
+
76
+ // Downsample large grids once when volume changes to keep marching cubes interactive
77
+ let ds_result = $derived.by(() => {
78
+ if (!volume) return undefined
79
+ return downsample_grid(volume.grid, volume.grid_dims)
80
+ })
81
+
82
+ // Run marching cubes at the given isovalue with pre-prepared grid/lattice/shift.
83
+ function extract_surface(
84
+ isovalue: number,
85
+ mc_grid: number[][][],
86
+ mc_lattice: Matrix3x3,
87
+ origin_shift: Vec3 | null,
88
+ ): BufferGeometry | null {
89
+ if (isovalue === 0) return null
90
+
91
+ const result = marching_cubes(mc_grid, isovalue, mc_lattice, {
92
+ periodic: false,
93
+ interpolate: true,
94
+ centered: false,
95
+ normals: false,
96
+ })
97
+
98
+ if (origin_shift) {
99
+ for (const vert of result.vertices) {
100
+ vert[0] += origin_shift[0]
101
+ vert[1] += origin_shift[1]
102
+ vert[2] += origin_shift[2]
103
+ }
82
104
  }
83
- raf_id = requestAnimationFrame(() => {
84
- const old = active_geometries;
85
- const entries = [];
86
- for (let layer_idx = 0; layer_idx < layers.length; layer_idx++) {
87
- const layer = layers[layer_idx];
88
- if (!layer.visible || layer.isovalue <= 0)
89
- continue;
90
- // Render order: inner shells (higher isovalue) first for correct transparency.
91
- // Each layer gets 4 slots (positive back/front + negative back/front).
92
- const base_order = (layers.length - 1 - layer_idx) * 4;
93
- const pos_geo = extract_surface(layer.isovalue);
94
- if (pos_geo) {
95
- entries.push({
96
- geometry: pos_geo,
97
- color: layer.color,
98
- opacity: layer.opacity,
99
- render_order: base_order,
100
- });
101
- }
102
- if (layer.show_negative) {
103
- const neg_geo = extract_surface(-layer.isovalue);
104
- if (neg_geo) {
105
- entries.push({
106
- geometry: neg_geo,
107
- color: layer.negative_color,
108
- opacity: layer.opacity,
109
- render_order: base_order + 2,
110
- });
111
- }
112
- }
105
+
106
+ return build_geometry(result.vertices, result.faces)
107
+ }
108
+
109
+ // === Multi-layer geometry management ===
110
+ // Each layer produces up to 2 geometries (positive + optional negative lobe).
111
+ // Keyed by "layer_idx:sign" for cache invalidation.
112
+ type GeoEntry = {
113
+ geometry: BufferGeometry
114
+ color: string
115
+ opacity: number
116
+ render_order: number
117
+ }
118
+ let active_geometries = $state<GeoEntry[]>([])
119
+ let raf_id = 0
120
+ let debounce_id = 0
121
+
122
+ // Dispose all current geometries
123
+ function dispose_all() {
124
+ for (const entry of active_geometries) entry.geometry.dispose()
125
+ active_geometries = []
126
+ }
127
+
128
+ // Dispose on unmount
129
+ $effect(() => () => dispose_all())
130
+
131
+ function rebuild_geometries(layers: IsosurfaceLayer[]) {
132
+ if (!volume || !ds_result) return
133
+ const old = active_geometries
134
+ const entries: GeoEntry[] = []
135
+
136
+ // Prepare grid/lattice/shift once for all layers.
137
+ // When halo > 0 for periodic volumes, the downsampled grid is padded with
138
+ // halo cells from the opposite face so isosurfaces extend beyond the unit
139
+ // cell and close into complete enclosed shapes around boundary atoms.
140
+ let mc_grid = ds_result.grid
141
+ let mc_lattice: Matrix3x3 = volume.lattice
142
+ let origin_shift: Vec3 | null = null
143
+
144
+ if (settings.halo > 0 && volume.periodic) {
145
+ const padded = pad_periodic_grid(ds_result.grid, ds_result.dims, settings.halo)
146
+ mc_grid = padded.grid
147
+ // marching_cubes maps [0,1] fractional -> Cartesian via lattice.
148
+ // The padded grid covers a wider fractional range, so scale the lattice
149
+ // to match. Then shift all vertices by the fractional offset.
150
+ const [la, lb, lc] = volume.lattice
151
+ const sx = padded.dims[0] / ds_result.dims[0]
152
+ const sy = padded.dims[1] / ds_result.dims[1]
153
+ const sz = padded.dims[2] / ds_result.dims[2]
154
+ mc_lattice = [
155
+ [la[0] * sx, la[1] * sx, la[2] * sx],
156
+ [lb[0] * sy, lb[1] * sy, lb[2] * sy],
157
+ [lc[0] * sz, lc[1] * sz, lc[2] * sz],
158
+ ]
159
+ const [ox, oy, oz] = padded.offset
160
+ origin_shift = [
161
+ ox * la[0] + oy * lb[0] + oz * lc[0],
162
+ ox * la[1] + oy * lb[1] + oz * lc[1],
163
+ ox * la[2] + oy * lb[2] + oz * lc[2],
164
+ ]
165
+ }
166
+
167
+ const surface_at = (isovalue: number) =>
168
+ extract_surface(isovalue, mc_grid, mc_lattice, origin_shift)
169
+
170
+ // Render lower-isovalue (outer) shells earlier so per-layer back/front passes
171
+ // interleave back-to-front across shells and reduce transparency artefacts.
172
+ const layer_render_rank = new Map<number, number>(
173
+ layers
174
+ .map((layer, layer_idx) => ({ layer_idx, isovalue: layer.isovalue }))
175
+ .sort((layer_a, layer_b) => layer_a.isovalue - layer_b.isovalue)
176
+ .map(({ layer_idx }, rank) => [layer_idx, rank]),
177
+ )
178
+
179
+ for (let layer_idx = 0; layer_idx < layers.length; layer_idx++) {
180
+ const layer = layers[layer_idx]
181
+ if (!layer.visible || layer.isovalue <= 0) continue
182
+
183
+ // Each layer gets 4 slots (positive back/front + negative back/front).
184
+ const base_order = (layer_render_rank.get(layer_idx) ?? layer_idx) * 4
185
+
186
+ const pos_geo = surface_at(layer.isovalue)
187
+ if (pos_geo) {
188
+ entries.push({
189
+ geometry: pos_geo,
190
+ color: layer.color,
191
+ opacity: layer.opacity,
192
+ render_order: base_order,
193
+ })
194
+ }
195
+
196
+ if (layer.show_negative) {
197
+ const neg_geo = surface_at(-layer.isovalue)
198
+ if (neg_geo) {
199
+ entries.push({
200
+ geometry: neg_geo,
201
+ color: layer.negative_color,
202
+ opacity: layer.opacity,
203
+ render_order: base_order + 2,
204
+ })
113
205
  }
114
- active_geometries = entries;
115
- for (const entry of old)
116
- entry.geometry.dispose();
117
- });
118
- return () => cancelAnimationFrame(raf_id);
119
- });
206
+ }
207
+ }
208
+
209
+ active_geometries = entries
210
+ for (const entry of old) entry.geometry.dispose()
211
+ }
212
+
213
+ // Rebuild all layer geometries when layers or volume change.
214
+ // Debounces rapid changes (e.g. slider drags) to avoid repeated expensive marching cubes.
215
+ $effect(() => {
216
+ const layers = resolved_layers
217
+ void settings.halo
218
+ if (!ds_result) {
219
+ dispose_all()
220
+ return
221
+ }
222
+ debounce_id = window.setTimeout(() => {
223
+ raf_id = requestAnimationFrame(() => rebuild_geometries(layers))
224
+ }, 50)
225
+ return () => {
226
+ clearTimeout(debounce_id)
227
+ cancelAnimationFrame(raf_id)
228
+ }
229
+ })
120
230
  </script>
121
231
 
122
232
  <!-- Render each geometry entry with appropriate material -->
@@ -1,35 +1,53 @@
1
- <script lang="ts">// Controls panel for isosurface visualization settings (isovalue, opacity, colors, etc.)
2
- // Supports both single-isovalue mode and multi-layer mode.
3
- import { format_num } from '../labels';
4
- import { SettingsSection } from '../layout';
5
- import { tooltip } from 'svelte-multiselect/attachments';
6
- import { DEFAULT_ISOSURFACE_SETTINGS, generate_layers } from './types';
7
- let { settings = $bindable({ ...DEFAULT_ISOSURFACE_SETTINGS }), volumes = [], active_volume_idx = $bindable(0), } = $props();
8
- // Clamp active_volume_idx when volumes list changes (e.g. dataset swap)
9
- $effect(() => {
1
+ <script lang="ts">
2
+ // Controls panel for isosurface visualization settings (isovalue, opacity, colors, etc.)
3
+ // Supports both single-isovalue mode and multi-layer mode.
4
+ import { format_num } from '../labels'
5
+ import { SettingsSection } from '../layout'
6
+ import { tooltip } from 'svelte-multiselect/attachments'
7
+ import type { IsosurfaceLayer, IsosurfaceSettings, VolumetricData } from './types'
8
+ import { DEFAULT_ISOSURFACE_SETTINGS, generate_layers } from './types'
9
+
10
+ let {
11
+ settings = $bindable({ ...DEFAULT_ISOSURFACE_SETTINGS }),
12
+ volumes = [],
13
+ active_volume_idx = $bindable(0),
14
+ }: {
15
+ settings?: IsosurfaceSettings
16
+ volumes?: VolumetricData[]
17
+ active_volume_idx?: number
18
+ } = $props()
19
+
20
+ // Clamp active_volume_idx when volumes list changes (e.g. dataset swap)
21
+ $effect(() => {
10
22
  if (volumes.length > 0 && active_volume_idx >= volumes.length) {
11
- active_volume_idx = 0;
23
+ active_volume_idx = 0
12
24
  }
13
- });
14
- // Use precomputed data_range from the active volume
15
- let data_range = $derived(volumes[active_volume_idx]?.data_range ?? { min: 0, max: 1, abs_max: 1, mean: 0 });
16
- let slider_max = $derived(Math.max(data_range.abs_max, 0.001));
17
- let step = $derived(slider_max / 200);
18
- let is_multi_layer = $derived((settings.layers?.length ?? 0) > 0);
19
- let n_layers = $derived(settings.layers?.length ?? 1);
20
- function set_layer_count(count) {
25
+ })
26
+
27
+ // Use precomputed data_range from the active volume
28
+ let data_range = $derived(
29
+ volumes[active_volume_idx]?.data_range ?? { min: 0, max: 1, abs_max: 1, mean: 0 },
30
+ )
31
+
32
+ let slider_max = $derived(Math.max(data_range.abs_max, 0.001))
33
+ let step = $derived(slider_max / 200)
34
+ let is_multi_layer = $derived((settings.layers?.length ?? 0) > 0)
35
+ let n_layers = $derived(settings.layers?.length ?? 1)
36
+
37
+ function set_layer_count(count: number) {
21
38
  if (count <= 1) {
22
- settings.layers = undefined;
39
+ settings.layers = undefined
40
+ } else {
41
+ settings.layers = generate_layers(data_range, count)
23
42
  }
24
- else {
25
- settings.layers = generate_layers(data_range, count);
26
- }
27
- }
28
- function update_layer(idx, updates) {
29
- if (!settings.layers)
30
- return;
31
- settings.layers = settings.layers.map((layer, layer_idx) => layer_idx === idx ? { ...layer, ...updates } : layer);
32
- }
43
+ }
44
+
45
+ function update_layer(idx: number, updates: Partial<IsosurfaceLayer>) {
46
+ if (!settings.layers) return
47
+ settings.layers = settings.layers.map((layer, layer_idx) =>
48
+ layer_idx === idx ? { ...layer, ...updates } : layer
49
+ )
50
+ }
33
51
  </script>
34
52
 
35
53
  <SettingsSection
@@ -39,6 +57,7 @@ function update_layer(idx, updates) {
39
57
  opacity: settings.opacity,
40
58
  show_negative: settings.show_negative,
41
59
  wireframe: settings.wireframe,
60
+ halo: settings.halo,
42
61
  layers: n_layers,
43
62
  }}
44
63
  on_reset={() => {
@@ -205,6 +224,24 @@ function update_layer(idx, updates) {
205
224
  </div>
206
225
  {/if}
207
226
 
227
+ {#if volumes?.[active_volume_idx]?.periodic}
228
+ <label
229
+ {@attach tooltip({
230
+ content:
231
+ `Extend isosurface beyond cell boundaries to close partial spheres (fraction of cell)`,
232
+ })}
233
+ >
234
+ Halo: {format_num(settings.halo, `.2f`)}
235
+ <input
236
+ type="range"
237
+ min={0}
238
+ max={0.5}
239
+ step={0.01}
240
+ bind:value={settings.halo}
241
+ />
242
+ </label>
243
+ {/if}
244
+
208
245
  {#if volumes[active_volume_idx]}
209
246
  <div class="grid-info">
210
247
  {volumes[active_volume_idx].grid_dims.join(` × `)} grid &nbsp;|&nbsp; [{