matterviz 0.4.2 → 0.4.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 (244) hide show
  1. package/dist/FilePicker.svelte +40 -50
  2. package/dist/api/optimade.js +10 -7
  3. package/dist/app.css +15 -0
  4. package/dist/brillouin/BrillouinZone.svelte +18 -9
  5. package/dist/brillouin/BrillouinZoneScene.svelte +20 -29
  6. package/dist/brillouin/compute.js +10 -16
  7. package/dist/brillouin/geometry.js +2 -2
  8. package/dist/chempot-diagram/ChemPotDiagram.svelte +18 -15
  9. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +17 -113
  10. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +104 -264
  11. package/dist/chempot-diagram/async-compute.svelte.js +12 -13
  12. package/dist/chempot-diagram/color.d.ts +21 -1
  13. package/dist/chempot-diagram/color.js +70 -1
  14. package/dist/chempot-diagram/compute.d.ts +7 -0
  15. package/dist/chempot-diagram/compute.js +85 -26
  16. package/dist/chempot-diagram/export.js +1 -6
  17. package/dist/chempot-diagram/temperature.js +9 -10
  18. package/dist/composition/BarChart.svelte +7 -9
  19. package/dist/composition/Composition.svelte +3 -8
  20. package/dist/composition/Formula.svelte +2 -2
  21. package/dist/composition/FormulaFilter.svelte +27 -93
  22. package/dist/composition/format.js +6 -8
  23. package/dist/composition/parse.js +50 -105
  24. package/dist/constants.d.ts +0 -1
  25. package/dist/constants.js +6 -17
  26. package/dist/convex-hull/ConvexHull2D.svelte +0 -2
  27. package/dist/convex-hull/ConvexHull3D.svelte +19 -47
  28. package/dist/convex-hull/ConvexHull4D.svelte +65 -95
  29. package/dist/convex-hull/StructurePopup.svelte +1 -1
  30. package/dist/convex-hull/gas-thermodynamics.js +21 -53
  31. package/dist/convex-hull/helpers.js +1 -7
  32. package/dist/convex-hull/thermodynamics.js +19 -27
  33. package/dist/coordination/CoordinationBarPlot.svelte +12 -21
  34. package/dist/element/BohrAtom.svelte +0 -1
  35. package/dist/element/ElementTile.svelte +26 -44
  36. package/dist/element/data.d.ts +2 -0
  37. package/dist/element/data.js +5 -1
  38. package/dist/element/index.d.ts +1 -1
  39. package/dist/element/index.js +1 -1
  40. package/dist/fermi-surface/FermiSurface.svelte +22 -23
  41. package/dist/fermi-surface/FermiSurface.svelte.d.ts +2 -6
  42. package/dist/fermi-surface/FermiSurfaceScene.svelte +32 -77
  43. package/dist/fermi-surface/compute.js +22 -55
  44. package/dist/fermi-surface/parse.js +38 -70
  45. package/dist/file-viewer/JsonBrowser.svelte +1043 -0
  46. package/dist/file-viewer/JsonBrowser.svelte.d.ts +9 -0
  47. package/dist/file-viewer/PlotPanel.svelte +350 -0
  48. package/dist/file-viewer/PlotPanel.svelte.d.ts +9 -0
  49. package/dist/file-viewer/TrajectoryWithDos.svelte +30 -0
  50. package/dist/file-viewer/TrajectoryWithDos.svelte.d.ts +10 -0
  51. package/dist/file-viewer/detect.d.ts +11 -0
  52. package/dist/file-viewer/detect.js +381 -0
  53. package/dist/file-viewer/eligibility.d.ts +4 -0
  54. package/dist/file-viewer/eligibility.js +53 -0
  55. package/dist/file-viewer/host-protocol.d.ts +50 -0
  56. package/dist/file-viewer/host-transfer.d.ts +25 -0
  57. package/dist/file-viewer/host-transfer.js +42 -0
  58. package/dist/file-viewer/index.d.ts +5 -0
  59. package/dist/file-viewer/index.js +5 -0
  60. package/dist/file-viewer/main.d.ts +44 -0
  61. package/dist/file-viewer/main.js +535 -0
  62. package/dist/file-viewer/parse.d.ts +9 -0
  63. package/dist/file-viewer/parse.js +170 -0
  64. package/dist/file-viewer/plot-utils.d.ts +24 -0
  65. package/dist/file-viewer/plot-utils.js +191 -0
  66. package/dist/file-viewer/types.d.ts +4 -0
  67. package/dist/file-viewer/types.js +5 -0
  68. package/dist/heatmap-matrix/HeatmapMatrix.svelte +57 -84
  69. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
  70. package/dist/index.d.ts +2 -2
  71. package/dist/index.js +1 -1
  72. package/dist/io/ExportPane.svelte +1 -1
  73. package/dist/io/decompress.d.ts +2 -0
  74. package/dist/io/decompress.js +3 -2
  75. package/dist/io/export.d.ts +7 -4
  76. package/dist/io/export.js +70 -18
  77. package/dist/io/file-drop.js +60 -35
  78. package/dist/io/url-drop.d.ts +2 -0
  79. package/dist/io/url-drop.js +28 -16
  80. package/dist/isosurface/Isosurface.svelte +687 -196
  81. package/dist/isosurface/Isosurface.svelte.d.ts +7 -1
  82. package/dist/isosurface/IsosurfaceControls.svelte +564 -85
  83. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +1 -1
  84. package/dist/isosurface/VolumeSlice.svelte +224 -0
  85. package/dist/isosurface/VolumeSlice.svelte.d.ts +28 -0
  86. package/dist/isosurface/coloring.d.ts +20 -0
  87. package/dist/isosurface/coloring.js +118 -0
  88. package/dist/isosurface/geometry-worker-types.d.ts +38 -0
  89. package/dist/isosurface/geometry.worker.d.ts +1 -0
  90. package/dist/isosurface/geometry.worker.js +59 -0
  91. package/dist/isosurface/grid.d.ts +10 -0
  92. package/dist/isosurface/grid.js +31 -0
  93. package/dist/isosurface/index.d.ts +4 -0
  94. package/dist/isosurface/index.js +4 -0
  95. package/dist/isosurface/profile.d.ts +12 -0
  96. package/dist/isosurface/profile.js +19 -0
  97. package/dist/isosurface/sampling.d.ts +34 -0
  98. package/dist/isosurface/sampling.js +408 -0
  99. package/dist/isosurface/slice-rendering.d.ts +13 -0
  100. package/dist/isosurface/slice-rendering.js +79 -0
  101. package/dist/isosurface/slice.d.ts +22 -2
  102. package/dist/isosurface/slice.js +200 -116
  103. package/dist/isosurface/types.d.ts +24 -1
  104. package/dist/isosurface/types.js +120 -8
  105. package/dist/json-path.d.ts +4 -0
  106. package/dist/json-path.js +89 -0
  107. package/dist/labels.js +1 -1
  108. package/dist/layout/InfoCard.svelte +1 -3
  109. package/dist/layout/SettingsSection.svelte +52 -61
  110. package/dist/layout/SettingsSection.svelte.d.ts +6 -7
  111. package/dist/layout/ViewerChrome.svelte +7 -1
  112. package/dist/layout/json-tree/JsonNode.svelte +14 -48
  113. package/dist/layout/json-tree/JsonTree.svelte +48 -95
  114. package/dist/layout/json-tree/JsonValue.svelte +3 -18
  115. package/dist/layout/json-tree/utils.d.ts +0 -3
  116. package/dist/layout/json-tree/utils.js +50 -179
  117. package/dist/marching-cubes.d.ts +8 -1
  118. package/dist/marching-cubes.js +115 -48
  119. package/dist/math.d.ts +3 -2
  120. package/dist/math.js +29 -38
  121. package/dist/overlays/ContextMenu.svelte +15 -32
  122. package/dist/overlays/DraggablePane.svelte +39 -58
  123. package/dist/overlays/DraggablePane.svelte.d.ts +1 -1
  124. package/dist/overlays/GlassChip.svelte +3 -1
  125. package/dist/periodic-table/PeriodicTableControls.svelte +26 -52
  126. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +18 -34
  127. package/dist/phase-diagram/build-diagram.js +1 -6
  128. package/dist/phase-diagram/svg-to-diagram.js +9 -14
  129. package/dist/plot/bar/BarPlot.svelte +38 -42
  130. package/dist/plot/bar/SpacegroupBarPlot.svelte +18 -42
  131. package/dist/plot/box/BoxPlot.svelte +27 -23
  132. package/dist/plot/core/auto-place.d.ts +1 -1
  133. package/dist/plot/core/auto-place.js +1 -1
  134. package/dist/plot/core/data-transform.d.ts +1 -2
  135. package/dist/plot/core/data-transform.js +0 -10
  136. package/dist/plot/core/layout.d.ts +12 -5
  137. package/dist/plot/core/layout.js +37 -27
  138. package/dist/plot/core/pan-zoom.svelte.d.ts +2 -4
  139. package/dist/plot/core/pan-zoom.svelte.js +34 -17
  140. package/dist/plot/core/placed-tween.svelte.d.ts +3 -0
  141. package/dist/plot/core/placed-tween.svelte.js +68 -21
  142. package/dist/plot/core/svg.js +0 -1
  143. package/dist/plot/core/utils/hierarchy-chart.d.ts +1 -0
  144. package/dist/plot/core/utils/hierarchy-chart.js +12 -0
  145. package/dist/plot/core/utils/hierarchy-labels.js +3 -2
  146. package/dist/plot/histogram/Histogram.svelte +21 -26
  147. package/dist/plot/sankey/sankey.js +13 -9
  148. package/dist/plot/scatter/BinnedScatterPlot.svelte +59 -43
  149. package/dist/plot/scatter/ScatterPlot.svelte +67 -52
  150. package/dist/plot/scatter/adaptive-density.d.ts +1 -1
  151. package/dist/plot/scatter/adaptive-density.js +36 -20
  152. package/dist/plot/sunburst/Sunburst.svelte +20 -20
  153. package/dist/plot/treemap/Treemap.svelte +175 -60
  154. package/dist/plot/treemap/Treemap.svelte.d.ts +6 -0
  155. package/dist/plot/treemap/index.d.ts +1 -0
  156. package/dist/plot/treemap/labels.d.ts +38 -0
  157. package/dist/plot/treemap/labels.js +80 -0
  158. package/dist/plot/treemap/treemap.d.ts +4 -2
  159. package/dist/plot/treemap/treemap.js +5 -2
  160. package/dist/rdf/RdfPlot.svelte +17 -35
  161. package/dist/rdf/calc-rdf.js +59 -44
  162. package/dist/rdf/index.d.ts +0 -1
  163. package/dist/sanitize.js +84 -26
  164. package/dist/scene/SceneCamera.svelte +1 -1
  165. package/dist/scene/SceneCamera.svelte.d.ts +1 -1
  166. package/dist/scene/index.d.ts +1 -2
  167. package/dist/scene/index.js +1 -1
  168. package/dist/scene/{props.js → props.svelte.js} +15 -1
  169. package/dist/settings.js +159 -482
  170. package/dist/spectral/Bands.svelte +151 -158
  171. package/dist/spectral/BrillouinBandsDos.svelte +4 -19
  172. package/dist/spectral/Dos.svelte +26 -24
  173. package/dist/spectral/helpers.js +40 -105
  174. package/dist/structure/AtomLegend.svelte +8 -13
  175. package/dist/structure/AtomLegend.svelte.d.ts +2 -3
  176. package/dist/structure/CellSelect.svelte +35 -10
  177. package/dist/structure/Structure.svelte +177 -99
  178. package/dist/structure/Structure.svelte.d.ts +1 -1
  179. package/dist/structure/StructureCarousel.svelte +10 -3
  180. package/dist/structure/StructureControls.svelte +8 -26
  181. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  182. package/dist/structure/StructureInfoPane.svelte +65 -76
  183. package/dist/structure/StructureScene.svelte +75 -109
  184. package/dist/structure/StructureScene.svelte.d.ts +3 -1
  185. package/dist/structure/StructureViewport.svelte +120 -55
  186. package/dist/structure/StructureViewport.svelte.d.ts +3 -1
  187. package/dist/structure/atom-properties.d.ts +1 -1
  188. package/dist/structure/atom-properties.js +4 -3
  189. package/dist/structure/bond-order-perception.js +3 -8
  190. package/dist/structure/bonding.d.ts +0 -1
  191. package/dist/structure/bonding.js +16 -23
  192. package/dist/structure/export.js +50 -74
  193. package/dist/structure/format-detect.js +5 -7
  194. package/dist/structure/index.js +6 -12
  195. package/dist/structure/parse.js +38 -100
  196. package/dist/structure/partial-occupancy.js +7 -4
  197. package/dist/structure/pbc.js +8 -10
  198. package/dist/structure/polyhedra.js +5 -4
  199. package/dist/structure/prop-groups.d.ts +85 -0
  200. package/dist/structure/prop-groups.js +18 -0
  201. package/dist/symmetry/SymmetryStats.svelte +0 -4
  202. package/dist/symmetry/WyckoffTable.svelte +5 -4
  203. package/dist/symmetry/cell-transform.js +5 -14
  204. package/dist/symmetry/index.js +6 -9
  205. package/dist/table/HeatmapTable.svelte +61 -103
  206. package/dist/table/ToggleMenu.svelte +166 -44
  207. package/dist/table/ToggleMenu.svelte.d.ts +1 -1
  208. package/dist/theme/embedded.d.ts +18 -0
  209. package/dist/theme/embedded.js +206 -0
  210. package/dist/theme/index.d.ts +4 -0
  211. package/dist/theme/index.js +1 -2
  212. package/dist/theme/themes.mjs +31 -60
  213. package/dist/tooltip/TooltipContent.svelte +1 -1
  214. package/dist/tooltip/TooltipContent.svelte.d.ts +1 -1
  215. package/dist/tooltip/index.d.ts +8 -1
  216. package/dist/trajectory/Trajectory.svelte +140 -120
  217. package/dist/trajectory/TrajectoryError.svelte +1 -66
  218. package/dist/trajectory/TrajectoryExportPane.svelte +0 -1
  219. package/dist/trajectory/TrajectoryInfoPane.svelte +55 -80
  220. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +2 -1
  221. package/dist/trajectory/extract.js +19 -29
  222. package/dist/trajectory/format-detect.d.ts +2 -0
  223. package/dist/trajectory/format-detect.js +7 -5
  224. package/dist/trajectory/frame-reader.js +3 -4
  225. package/dist/trajectory/helpers.js +16 -49
  226. package/dist/trajectory/index.js +2 -8
  227. package/dist/trajectory/parse/ase.js +1 -1
  228. package/dist/trajectory/parse/index.d.ts +4 -3
  229. package/dist/trajectory/parse/index.js +12 -17
  230. package/dist/trajectory/parse/vaspout-h5.d.ts +0 -1
  231. package/dist/trajectory/parse/vaspout-h5.js +1 -4
  232. package/dist/trajectory/parse/xyz.js +48 -2
  233. package/dist/trajectory/plotting.js +14 -37
  234. package/dist/xrd/XrdPlot.svelte +43 -68
  235. package/dist/xrd/calc-xrd.js +22 -58
  236. package/dist/xrd/parse.js +13 -22
  237. package/package.json +40 -8
  238. package/readme.md +6 -5
  239. package/dist/scene/types.d.ts +0 -26
  240. package/dist/tooltip/types.d.ts +0 -8
  241. package/dist/trajectory/constants.d.ts +0 -6
  242. package/dist/trajectory/constants.js +0 -11
  243. /package/dist/{scene/types.js → file-viewer/host-protocol.js} +0 -0
  244. /package/dist/{tooltip/types.js → isosurface/geometry-worker-types.js} +0 -0
@@ -1,22 +1,20 @@
1
- import { CONFIG_DIRS_REGEX, STRUCT_KEYWORDS_REGEX, STRUCT_KEYWORDS_STRICT_REGEX, STRUCTURE_EXTENSIONS_REGEX, TRAJ_KEYWORDS_REGEX, VASP_FILES_REGEX, } from '../constants';
1
+ import { CONFIG_DIRS_REGEX, STRUCT_KEYWORDS_REGEX, STRUCT_KEYWORDS_STRICT_REGEX, STRUCTURE_EXTENSIONS_REGEX, TRAJ_KEYWORDS_REGEX, VASP_FILES_REGEX, XDATCAR_REGEX, XYZ_EXTXYZ_REGEX, } from '../constants';
2
2
  import { strip_compression_extensions } from '../io/decompress';
3
3
  // Filename-only detection lives apart from the parsers so lightweight callers
4
4
  // (desktop recents, file pickers) do not load YAML, structure math, and element
5
5
  // data just to choose an icon.
6
6
  export function is_structure_file(filename) {
7
7
  const name = strip_compression_extensions(filename);
8
- if (/\.(?:traj|xtc|h5|hdf5)$/i.test(name) || /xdatcar/i.test(name))
8
+ if (/\.(?:traj|xtc|h5|hdf5)$/i.test(name) || XDATCAR_REGEX.test(name))
9
9
  return false;
10
10
  if (STRUCTURE_EXTENSIONS_REGEX.test(name) || VASP_FILES_REGEX.test(name))
11
11
  return true;
12
- if (/\.(?:xyz|extxyz)$/i.test(name))
12
+ if (XYZ_EXTXYZ_REGEX.test(name))
13
13
  return !TRAJ_KEYWORDS_REGEX.test(name);
14
14
  if (/\.(?:yaml|yml|xml)$/i.test(name) && STRUCT_KEYWORDS_REGEX.test(name))
15
15
  return true;
16
- if (/\.json$/i.test(name) &&
16
+ return (/\.json$/i.test(name) &&
17
17
  STRUCT_KEYWORDS_STRICT_REGEX.test(name) &&
18
18
  !TRAJ_KEYWORDS_REGEX.test(name) &&
19
- !CONFIG_DIRS_REGEX.test(name))
20
- return true;
21
- return false;
19
+ !CONFIG_DIRS_REGEX.test(name));
22
20
  }
@@ -1,5 +1,5 @@
1
1
  import { ATOMIC_WEIGHTS } from '../composition/parse';
2
- import { element_data } from '../element';
2
+ import { element_by_symbol, element_data } from '../element';
3
3
  import * as math from '../math';
4
4
  export { default as Arrow } from './Arrow.svelte';
5
5
  export * from './atom-properties';
@@ -42,23 +42,17 @@ export function get_element_counts(structure) {
42
42
  export function format_chemical_formula(structure, sort_fn) {
43
43
  // concatenate elements in a structure followed by their amount
44
44
  const elements = get_element_counts(structure);
45
- const formula = [];
46
- for (const element of sort_fn(Object.keys(elements))) {
45
+ const formula = sort_fn(Object.keys(elements)).map((element) => {
47
46
  const amount = elements[element] ?? 0;
48
- if (amount === 1)
49
- formula.push(element);
50
- else
51
- formula.push(`${element}<sub>${amount}</sub>`);
52
- }
47
+ return amount === 1 ? element : `${element}<sub>${amount}</sub>`;
48
+ });
53
49
  return formula.join(` `);
54
50
  }
55
- // Electronegativity by element symbol (0 when missing), for O(1) sort comparisons
56
- const electronegativity_by_symbol = new Map(element_data.map((el) => [el.symbol, el.electronegativity ?? 0]));
57
51
  export function format_formula_by_electronegativity(structure) {
58
52
  // concatenate elements in a structure followed by their amount sorted by electronegativity
59
53
  return format_chemical_formula(structure, (symbols) => symbols.sort((el1, el2) => {
60
- const elec_neg1 = electronegativity_by_symbol.get(el1) ?? 0;
61
- const elec_neg2 = electronegativity_by_symbol.get(el2) ?? 0;
54
+ const elec_neg1 = element_by_symbol.get(el1)?.electronegativity ?? 0;
55
+ const elec_neg2 = element_by_symbol.get(el2)?.electronegativity ?? 0;
62
56
  // Sort by electronegativity (ascending), then alphabetically for ties
63
57
  if (elec_neg1 !== elec_neg2)
64
58
  return elec_neg1 - elec_neg2;
@@ -37,7 +37,6 @@ const aggregate_parse_error = (filename) => {
37
37
  return new Error(`Failed to parse structure${filename ? ` from '${filename}'` : ``}${detail}`);
38
38
  };
39
39
  const cif_coords_key = (coords) => `${coords[0].toFixed(6)},${coords[1].toFixed(6)},${coords[2].toFixed(6)}`;
40
- const cif_site_key = (element, abc, label) => `${element}|${label}|${cif_coords_key(abc)}`;
41
40
  // Bravais lattice centering translations (excluding the identity) keyed by the
42
41
  // leading letter of a space-group Hermann-Mauguin symbol. R is the obverse
43
42
  // hexagonal setting.
@@ -72,15 +71,9 @@ const vec3_from_values = (values, context) => {
72
71
  throw new Error(`Invalid ${context}: expected 3 coordinates, got ${values?.length ?? 0}`);
73
72
  }
74
73
  const coords = math.finite_vec3_from_values(values);
75
- if (coords)
76
- return coords;
77
- for (let idx = 0; idx < 3; idx++) {
78
- const value = values[idx];
79
- if (typeof value !== `number` || !Number.isFinite(value)) {
80
- throw new TypeError(`Invalid ${context}: coordinate ${idx} must be finite, got ${String(value)}`);
81
- }
82
- }
83
- throw new Error(`Invalid ${context}: expected 3 finite coordinates`);
74
+ if (!coords)
75
+ throw new Error(`Invalid ${context}: expected 3 finite coordinates, got [${values.map(String).join(`, `)}]`);
76
+ return coords;
84
77
  };
85
78
  // Parse a coordinate value that might be in various scientific notation formats
86
79
  function parse_coordinate(str) {
@@ -219,9 +212,9 @@ export function parse_poscar(content) {
219
212
  // Try to parse the first token as a number - if it succeeds, it's VASP 4 format
220
213
  const has_element_symbols = isNaN(parse_leading_num(lines[line_index]));
221
214
  if (has_element_symbols) {
222
- // VASP 5+ format - parse element symbols (may span multiple lines)
215
+ // VASP 5+ format - element symbols (possibly spanning multiple lines),
216
+ // followed by as many atom-count lines. Look ahead to find where numbers start.
223
217
  let symbol_lines = 1;
224
- // Look ahead to find where numbers start (atom counts)
225
218
  for (let lookahead_idx = 1; lookahead_idx < 10; lookahead_idx++) {
226
219
  if (line_index + lookahead_idx >= lines.length)
227
220
  break;
@@ -230,21 +223,11 @@ export function parse_poscar(content) {
230
223
  break;
231
224
  }
232
225
  }
233
- // Collect all element symbols from the symbol lines
234
- for (let symbol_line_idx = 0; symbol_line_idx < symbol_lines; symbol_line_idx++) {
235
- if (line_index + symbol_line_idx < lines.length) {
236
- element_symbols.push(...lines[line_index + symbol_line_idx].trim().split(/\s+/));
237
- }
238
- }
239
- // Parse atom counts (may span multiple lines)
240
- for (let count_line_idx = 0; count_line_idx < symbol_lines; count_line_idx++) {
241
- if (line_index + symbol_lines + count_line_idx < lines.length) {
242
- const counts = lines[line_index + symbol_lines + count_line_idx]
243
- .trim()
244
- .split(/\s+/)
245
- .map(Number);
246
- atom_counts.push(...counts);
247
- }
226
+ for (let offset = 0; offset < symbol_lines; offset++) {
227
+ const symbol_tokens = lines[line_index + offset]?.trim().split(/\s+/) ?? [];
228
+ element_symbols.push(...symbol_tokens);
229
+ const count_tokens = lines[line_index + symbol_lines + offset]?.trim().split(/\s+/);
230
+ atom_counts.push(...(count_tokens?.map(Number) ?? []));
248
231
  }
249
232
  line_index += 2 * symbol_lines;
250
233
  }
@@ -393,17 +376,14 @@ export function parse_xyz(content) {
393
376
  return null;
394
377
  }
395
378
  const element = validate_element_symbol(parts[0], atom_idx);
396
- const xyz = vec3_from_values(parts.slice(1, 4).map(parse_coordinate), `XYZ atom position ${atom_idx + 1}`);
379
+ let xyz = vec3_from_values(parts.slice(1, 4).map(parse_coordinate), `XYZ atom position ${atom_idx + 1}`);
397
380
  // Calculate fractional coordinates if lattice is available
398
381
  let abc = [0, 0, 0];
399
382
  if (lattice && xyz_frac_to_cart && xyz_cart_to_frac) {
400
- // Ensure fractional coordinates are wrapped into [0, 1) for consistency
383
+ // Wrap fractional coordinates into [0, 1) and recompute xyz from them so
384
+ // rendered atoms stay inside the primary unit cell
401
385
  abc = wrap_to_unit_cell(xyz_cart_to_frac(xyz));
402
- // Keep rendered atoms inside primary unit cell by recomputing xyz
403
- const wrapped_xyz = xyz_frac_to_cart(abc);
404
- xyz[0] = wrapped_xyz[0];
405
- xyz[1] = wrapped_xyz[1];
406
- xyz[2] = wrapped_xyz[2];
386
+ xyz = xyz_frac_to_cart(abc);
407
387
  }
408
388
  sites.push(make_site(element, abc, xyz, `${element}${atom_idx + 1}`));
409
389
  }
@@ -420,67 +400,31 @@ export function parse_xyz(content) {
420
400
  const parse_symmetry_expression = (expr_input) => {
421
401
  const coefficients = [0, 0, 0];
422
402
  let translation = 0;
423
- // Remove all whitespace
403
+ // Strip whitespace, then split into signed terms: "x-y+1/3" → ["x", "-y", "+1/3"].
404
+ // Dangling operators (e.g. "x+") produce no token and are silently ignored.
424
405
  const expr = expr_input.replaceAll(/\s+/g, ``);
425
- if (!expr)
426
- return { coefficients, translation };
427
- // Tokenize: split into terms while preserving signs
428
- // E.g., "x-y+1/3" → ["x", "-y", "+1/3"] or "-x+y" → ["-x", "+y"]
429
- const tokens = [];
430
- let current_token = ``;
431
- for (const char of expr) {
432
- if ((char === `+` || char === `-`) && current_token.length > 0) {
433
- tokens.push(current_token);
434
- current_token = char;
435
- }
436
- else {
437
- current_token += char;
438
- }
439
- }
440
- if (current_token)
441
- tokens.push(current_token);
442
- for (const token of tokens) {
443
- // Check if this token is a variable term (x, y, or z with optional sign)
444
- const var_match = /^(?<sign>[+-]?)(?<axis>[xyz])$/.exec(token);
445
- if (var_match) {
446
- const sign = var_match[1] === `-` ? -1 : 1;
447
- const var_char = var_match[2];
448
- const var_idx = var_char === `x` ? 0 : var_char === `y` ? 1 : 2;
406
+ for (const token of expr.match(/[+-]?[^+-]+/g) ?? []) {
407
+ const sign = token.startsWith(`-`) ? -1 : 1;
408
+ const term = token.replace(/^[+-]/, ``);
409
+ // Variable term (x, y, or z)
410
+ const var_idx = [`x`, `y`, `z`].indexOf(term);
411
+ if (var_idx !== -1) {
449
412
  coefficients[var_idx] += sign;
450
413
  continue;
451
414
  }
452
- // Check if this is a numeric term (integer, decimal, or fraction)
453
- let sign = 1;
454
- let num_str = token;
455
- // Handle leading sign
456
- if (num_str.startsWith(`+`)) {
457
- num_str = num_str.slice(1);
458
- }
459
- else if (num_str.startsWith(`-`)) {
460
- sign = -1;
461
- num_str = num_str.slice(1);
462
- }
463
- // Skip empty tokens (from dangling operators like "x+")
464
- if (!num_str || num_str === `+` || num_str === `-`)
415
+ // Numeric term: integer, decimal, or fraction like "1/3"
416
+ const parts = term.split(`/`);
417
+ // skip malformed terms like "1/2/3"
418
+ if (parts.length > 2) {
419
+ diag_warn(`Skipping malformed symmetry term '${term}'`);
465
420
  continue;
466
- if (num_str.includes(`/`)) {
467
- // Fraction
468
- const parts = num_str.split(`/`);
469
- if (parts.length === 2) {
470
- const numerator = Number(parts[0]);
471
- const denominator = Number(parts[1]);
472
- if (!isNaN(numerator) && !isNaN(denominator) && denominator !== 0) {
473
- translation += sign * (numerator / denominator);
474
- }
475
- }
476
- }
477
- else {
478
- // Integer or decimal
479
- const val = Number(num_str);
480
- if (!isNaN(val)) {
481
- translation += sign * val;
482
- }
483
421
  }
422
+ const [numerator, denominator = `1`] = parts;
423
+ const value = Number(numerator) / Number(denominator);
424
+ if (Number.isFinite(value))
425
+ translation += sign * value;
426
+ else
427
+ diag_warn(`Skipping non-finite symmetry term '${term}'`);
484
428
  }
485
429
  return { coefficients, translation };
486
430
  };
@@ -606,12 +550,9 @@ const split_cif_tokens = (line) => (line.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)
606
550
  // Parse atom data from CIF with robust error handling
607
551
  const parse_cif_atom_data = (raw_data, indices, coords_type) => {
608
552
  const { label = 0, symbol = -1, occupancy = -1 } = indices;
553
+ // cif_coords_type already guaranteed all three indices for coords_type exist
609
554
  const coord_indices = cif_coord_indices(indices, coords_type);
610
- if (coord_indices.some((idx) => idx === undefined)) {
611
- throw new Error(`Missing coordinate indices`);
612
- }
613
555
  const coords_triplet = vec3_from_values(coord_indices.map((idx) => {
614
- // idx cannot be undefined: the `.some` guard above already threw
615
556
  const coord_str = raw_data[idx];
616
557
  if (!coord_str)
617
558
  throw new Error(`Missing coordinate at index ${idx}`);
@@ -623,8 +564,8 @@ const parse_cif_atom_data = (raw_data, indices, coords_type) => {
623
564
  const raw_occu = occupancy >= 0 && raw_data[occupancy]
624
565
  ? parse_cif_uncertain_number(raw_data[occupancy])
625
566
  : null;
626
- // invalid or zero occupancy defaults to fully occupied
627
- const occu = raw_occu == null || raw_occu === 0 ? 1.0 : raw_occu;
567
+ // Explicit 0 is kept; missing / `.` / `?` (null) default to fully occupied.
568
+ const occu = raw_occu ?? 1.0;
628
569
  const from_symbol = symbol >= 0 ? /^(?<element>[A-Z][a-z]*)/.exec(raw_data[symbol])?.[1] : undefined;
629
570
  const element_symbol = from_symbol ?? raw_data[label]?.match(/(?:[A-Z][a-z]*)/g)?.[0];
630
571
  if (!element_symbol) {
@@ -816,7 +757,7 @@ export function parse_cif(content, wrap_fractional_coords = true, strict = true)
816
757
  const equiv_atoms = apply_symmetry_ops(fractional_atom, ops_to_use, wrap_fractional_coords, extra_centering);
817
758
  for (const equiv_atom of equiv_atoms) {
818
759
  const abc = wrap_vec3(equiv_atom.coords);
819
- const key = cif_site_key(element, abc, equiv_atom.id);
760
+ const key = `${element}|${equiv_atom.id}|${cif_coords_key(abc)}`;
820
761
  if (seen_site_keys.has(key))
821
762
  continue;
822
763
  seen_site_keys.add(key);
@@ -1321,10 +1262,7 @@ const unwrap_data = (value) => value && typeof value === `object` && `data` in v
1321
1262
  function is_optimade_structure_object(value) {
1322
1263
  if (!value || typeof value !== `object`)
1323
1264
  return false;
1324
- const obj = value;
1325
- const type = obj.type;
1326
- const id = obj.id;
1327
- const attributes = obj.attributes;
1265
+ const { type, id, attributes } = value;
1328
1266
  return (type === `structures` &&
1329
1267
  typeof id === `string` &&
1330
1268
  typeof attributes === `object` &&
@@ -10,7 +10,6 @@ const make_render_site = (sites, site_idx, source_site_indices, site_override) =
10
10
  is_image_atom: source_site_indices.some((source_site_idx) => is_image_atom(sites[source_site_idx])),
11
11
  source_site_indices,
12
12
  });
13
- const sq_dist = (xyz_1, xyz_2) => (xyz_1[0] - xyz_2[0]) ** 2 + (xyz_1[1] - xyz_2[1]) ** 2 + (xyz_1[2] - xyz_2[2]) ** 2;
14
13
  const is_split_partial_site = (site, hidden_elements) => {
15
14
  const visible_species = site.species.filter(({ element }) => !hidden_elements.has(element));
16
15
  const total_visible_occupancy = visible_species.reduce((occupancy_sum, { occu }) => occupancy_sum + occu, 0);
@@ -25,7 +24,12 @@ const group_split_partial_indices = (sites, hidden_elements) => {
25
24
  non_grouped_site_indices.push(site_idx);
26
25
  continue;
27
26
  }
28
- const matched_group_idx = grouped_centers.findIndex((center_xyz) => sq_dist(center_xyz, site.xyz) <= MERGE_DISTANCE_TOLERANCE ** 2);
27
+ const matched_group_idx = grouped_centers.findIndex((center_xyz) => {
28
+ const dx = center_xyz[0] - site.xyz[0];
29
+ const dy = center_xyz[1] - site.xyz[1];
30
+ const dz = center_xyz[2] - site.xyz[2];
31
+ return dx * dx + dy * dy + dz * dz <= MERGE_DISTANCE_TOLERANCE ** 2;
32
+ });
29
33
  if (matched_group_idx === -1) {
30
34
  grouped_centers.push(site.xyz);
31
35
  grouped_site_indices.push([site_idx]);
@@ -59,8 +63,7 @@ export const PARTIAL_OCCUPANCY_CAP_ARC = {
59
63
  arc_length: Math.PI,
60
64
  };
61
65
  export const merge_split_partial_sites = (sites, hidden_elements = new Set()) => {
62
- const grouped_indices = group_split_partial_indices(sites, hidden_elements);
63
- const { non_grouped_site_indices, grouped_site_indices } = grouped_indices;
66
+ const { non_grouped_site_indices, grouped_site_indices } = group_split_partial_indices(sites, hidden_elements);
64
67
  return build_render_sites(sites, non_grouped_site_indices, grouped_site_indices);
65
68
  };
66
69
  export const compute_slice_geometry = (visible_species, slice_gap_rad = PARTIAL_OCCUPANCY_SLICE_GAP_RAD) => {
@@ -1,5 +1,7 @@
1
+ // Periodic boundary conditions utilities
2
+ import { element_by_symbol } from '../element/data';
1
3
  import * as math from '../math';
2
- import { element_lookup, get_majority_element, has_framework_potential, is_spectator_center, pack_cell_key, } from './bonding';
4
+ import { get_majority_element, has_framework_potential, is_spectator_center, pack_cell_key, } from './bonding';
3
5
  // Distance slack added to the covalent-radii sum when deciding whether a
4
6
  // candidate image atom bonds to a base atom (VESTA-like bond search criterion)
5
7
  const BOND_SLACK = 0.4; // Å
@@ -107,7 +109,7 @@ export function find_image_atoms(structure, { tolerance } = {}) {
107
109
  let max_radius = 0;
108
110
  for (const site of structure.sites) {
109
111
  const elem = get_majority_element(site);
110
- const data = elem === null ? undefined : element_lookup.get(elem);
112
+ const data = elem === null ? undefined : element_by_symbol.get(elem);
111
113
  const radius = data?.covalent_radius ?? null;
112
114
  site_elements.push(elem);
113
115
  if (elem !== null)
@@ -250,17 +252,13 @@ export function find_image_atoms(structure, { tolerance } = {}) {
250
252
  }
251
253
  return image_sites;
252
254
  }
253
- // Return structure with image atoms added
255
+ // Return structure with image atoms added (unchanged when none are generated,
256
+ // e.g. for scattered trajectory-like data)
254
257
  export function get_pbc_image_sites(...args) {
255
258
  const structure = args[0];
256
- if (!structure || !structure.sites || structure.sites.length === 0) {
257
- return structure;
258
- }
259
- // Return trajectory data unchanged
260
- if (is_scattered_trajectory(structure.sites))
261
- return structure;
262
- // Add image atoms to regular crystal structures
263
259
  const image_sites = find_image_atoms(...args);
260
+ if (image_sites.length === 0)
261
+ return structure;
264
262
  const imaged_struct = { ...structure, sites: [...structure.sites] };
265
263
  for (const [site_idx, img_xyz, img_abc, is_completion] of image_sites) {
266
264
  const orig_site = structure.sites[site_idx];
@@ -4,9 +4,10 @@
4
4
  // so the whole scene renders in 1-2 draw calls regardless of supercell size.
5
5
  // Hot paths use scalar math and per-element caches to scale to large structures.
6
6
  import { rgb as parse_rgb } from 'd3-color';
7
+ import { element_by_symbol } from '../element/data';
7
8
  import { DEFAULTS } from '../settings';
8
9
  import { get_orig_site_idx } from './atom-properties';
9
- import { element_lookup, get_majority_element, has_framework_potential, is_spectator_center, } from './bonding';
10
+ import { get_majority_element, has_framework_potential, is_spectator_center } from './bonding';
10
11
  const face_of = (points, vert_a, vert_b, vert_c) => {
11
12
  const [ax, ay, az] = points[vert_a];
12
13
  const abx = points[vert_b][0] - ax;
@@ -301,7 +302,7 @@ export function build_adjacency(bonds) {
301
302
  function is_anion_vertex(center_en, center_is_metal, neighbor_element, electronegativity_margin) {
302
303
  if (!neighbor_element)
303
304
  return false;
304
- const n_data = element_lookup.get(neighbor_element);
305
+ const n_data = element_by_symbol.get(neighbor_element);
305
306
  if (n_data?.metal)
306
307
  return false;
307
308
  const n_en = n_data?.electronegativity ?? null;
@@ -341,12 +342,12 @@ export function compute_polyhedra(structure, bonds, options = {}) {
341
342
  const center_info = (element) => {
342
343
  let info = center_info_cache.get(element);
343
344
  if (!info) {
344
- const data = element_lookup.get(element);
345
+ const data = element_by_symbol.get(element);
345
346
  const center_en = data?.electronegativity ?? null;
346
347
  const r_center = data?.covalent_radius ?? null;
347
348
  const accepts = new Set(unique_elements.filter((n_elem) => is_anion_vertex(center_en, data?.metal === true, n_elem, electronegativity_margin)));
348
349
  const radii_sums = new Map(unique_elements.map((n_elem) => {
349
- const r_n = element_lookup.get(n_elem)?.covalent_radius ?? null;
350
+ const r_n = element_by_symbol.get(n_elem)?.covalent_radius ?? null;
350
351
  return [n_elem, r_center !== null && r_n !== null ? r_center + r_n : null];
351
352
  }));
352
353
  info = { accepts, radii_sums };
@@ -0,0 +1,85 @@
1
+ import type { DefaultSettings } from '../settings';
2
+ export declare const build_structure_props_from_settings: (defaults: DefaultSettings) => {
3
+ scene_props: {
4
+ gizmo: boolean;
5
+ atom_radius: number;
6
+ same_size_atoms: boolean;
7
+ show_atoms: boolean;
8
+ show_image_atoms: boolean;
9
+ sphere_segments: number;
10
+ bond_thickness: number;
11
+ auto_bond_order: boolean;
12
+ aromatic_display: "aromatic" | "kekule";
13
+ show_bonds: "always" | "never" | "crystals" | "molecules";
14
+ bond_color: string;
15
+ bonding_strategy: "electroneg_ratio" | "solid_angle";
16
+ show_polyhedra: "always" | "never" | "crystals" | "molecules";
17
+ polyhedra_opacity: number;
18
+ polyhedra_show_edges: boolean;
19
+ polyhedra_edge_color: string;
20
+ polyhedra_color_mode: import("./polyhedra").PolyhedraColorMode;
21
+ polyhedra_color: string;
22
+ polyhedra_hide_center_atoms: boolean;
23
+ polyhedra_min_neighbors: number;
24
+ polyhedra_max_neighbors: number;
25
+ polyhedra_excluded_elements: readonly string[];
26
+ polyhedra_included_elements: readonly string[];
27
+ atom_color_mode: "element" | "coordination" | "wyckoff" | "custom";
28
+ atom_color_scale: import("..").D3InterpolateName;
29
+ atom_color_scale_type: "continuous" | "categorical";
30
+ show_gizmo: boolean;
31
+ camera_position: import("..").Vec3;
32
+ camera_projection: import("../settings").CameraProjection;
33
+ initial_zoom: number;
34
+ fov: number;
35
+ rotation_damping: number;
36
+ rotate_speed: number;
37
+ zoom_speed: number;
38
+ pan_speed: number;
39
+ zoom_to_cursor: boolean;
40
+ max_zoom: number | undefined;
41
+ min_zoom: number | undefined;
42
+ auto_rotate: number;
43
+ rotation: import("..").Vec3;
44
+ show_site_labels: boolean;
45
+ show_site_indices: boolean;
46
+ site_label_size: number;
47
+ site_label_color: string;
48
+ site_label_bg_color: string;
49
+ site_label_padding: number;
50
+ site_label_offset: import("..").Vec3;
51
+ ambient_light: number;
52
+ directional_light: number;
53
+ vector_configs: Record<string, import("../settings").VectorLayerConfig>;
54
+ vector_scale: number;
55
+ vector_color: string;
56
+ vector_color_mode: "auto" | "element" | "uniform" | "spin_direction" | "magnitude";
57
+ vector_color_scale: import("..").D3InterpolateName;
58
+ vector_normalize: boolean;
59
+ vector_uniform_thickness: boolean;
60
+ vector_origin_gap: number;
61
+ vector_shaft_radius: number;
62
+ vector_arrow_head_radius: number;
63
+ vector_arrow_head_length: number;
64
+ show_cell: boolean;
65
+ show_cell_vectors: boolean;
66
+ cell_edge_opacity: number;
67
+ cell_surface_opacity: number;
68
+ cell_edge_color: string;
69
+ cell_surface_color: string;
70
+ cell_edge_width: number;
71
+ fullscreen_toggle: boolean;
72
+ };
73
+ lattice_props: {
74
+ cell_edge_opacity: number;
75
+ cell_surface_opacity: number;
76
+ cell_edge_color: string;
77
+ cell_surface_color: string;
78
+ cell_edge_width: number;
79
+ show_cell_vectors: boolean;
80
+ };
81
+ color_scheme: string;
82
+ background_color: string;
83
+ background_opacity: number;
84
+ show_image_atoms: boolean;
85
+ };
@@ -0,0 +1,18 @@
1
+ export const build_structure_props_from_settings = (defaults) => {
2
+ const { structure } = defaults;
3
+ return {
4
+ scene_props: { ...structure, gizmo: structure.show_gizmo },
5
+ lattice_props: {
6
+ cell_edge_opacity: structure.cell_edge_opacity,
7
+ cell_surface_opacity: structure.cell_surface_opacity,
8
+ cell_edge_color: structure.cell_edge_color,
9
+ cell_surface_color: structure.cell_surface_color,
10
+ cell_edge_width: structure.cell_edge_width,
11
+ show_cell_vectors: structure.show_cell_vectors,
12
+ },
13
+ color_scheme: defaults.color_scheme,
14
+ background_color: defaults.background_color,
15
+ background_opacity: defaults.background_opacity,
16
+ show_image_atoms: structure.show_image_atoms,
17
+ };
18
+ };
@@ -95,14 +95,10 @@
95
95
  hermann_mauguin: `Hermann-Mauguin symbol describes symmetry operations. Format: Lattice type + Point group symmetry. Example: P4/mmm = Primitive + 4-fold rotation + mirror planes`,
96
96
  hall_number: `Hall number: alternative numbering system for space groups. Useful for crystallographic software compatibility.`,
97
97
  pearson_symbol: `Pearson symbol. Format: Crystal system + Number of atoms per unit cell. Example: tP2 = tetragonal primitive with 2 atoms`,
98
- symmetry_operations: `Total symmetry operations that map the crystal structure onto itself. Includes rotations, translations, and combinations.`,
99
98
  distinct_orbits: `Number of unique Wyckoff positions (symmetry-equivalent atomic sites) in the crystal structure.`,
100
99
  wyckoff_sequence: `Wyckoff sequence: letters of all occupied Wyckoff positions in descending alphabetical order, with superscript counts for letters occupied by multiple orbits. A standard structure-type fingerprint (complements the Pearson symbol).`,
101
100
  free_params: `Internal degrees of freedom: number of free fractional-coordinate parameters (x, y, z in the ITA representative coordinates) summed over occupied Wyckoff orbits. 0 means all atomic positions are fully fixed by symmetry.`,
102
101
  settings: `All settings of this space group in the International Tables (origin choices, unique axes, cell choices, hexagonal vs rhombohedral axes). The setting detected for this structure is highlighted.`,
103
- translations: `Number of translations in the crystal structure.`,
104
- rotations: `Number of rotations in the crystal structure.`,
105
- roto_translations: `Number of roto-translations in the crystal structure.`,
106
102
  }
107
103
  const tooltips: Record<string, string> = $derived(show_tooltips ? titles : {})
108
104
 
@@ -102,13 +102,13 @@
102
102
  <td>
103
103
  <span
104
104
  style:background-color={colors.element[elem]}
105
- style="display: inline-block; padding: 0 6pt; border-radius: 3pt; line-height: 1.4"
105
+ style="display: inline-block; padding: 0 4pt; border-radius: 3pt; line-height: 1.25"
106
106
  {@attach contrast_color()}
107
107
  >
108
108
  {elem}
109
109
  </span>
110
110
  </td>
111
- <td>({abc?.map(format_fractional).join(` , `) ?? `N/A`})</td>
111
+ <td>({abc?.map(format_fractional).join(`, `) ?? `N/A`})</td>
112
112
  {#if has_ita_coords}
113
113
  <td>{wyckoff_pos.coordinates ? `(${wyckoff_pos.coordinates})` : ``}</td>
114
114
  {/if}
@@ -136,10 +136,11 @@
136
136
 
137
137
  <style>
138
138
  .wyckoff-table {
139
- margin-top: 1em;
139
+ margin-top: 0.5em;
140
+ border-collapse: collapse;
140
141
  }
141
142
  .wyckoff-table :is(th, td) {
142
- padding: 2px 6px;
143
+ padding: 1px 3px;
143
144
  text-align: center;
144
145
  vertical-align: middle;
145
146
  }
@@ -19,10 +19,8 @@ original_structure) {
19
19
  // serialization for both input and output (see to_cell_json: column-major(B) ==
20
20
  // row-major(RB)), so the flat array is row-major with each row a lattice vector.
21
21
  const lattice_matrix = math.vec9_to_mat3x3([...cell.lattice.basis]);
22
- // Calculate lattice parameters from matrix
23
22
  const lattice_params = math.calc_lattice_params(lattice_matrix);
24
23
  const frac_to_cart = math.create_frac_to_cart(lattice_matrix);
25
- // Build sites from positions and atomic numbers
26
24
  const sites = cell.positions.map((abc, idx) => {
27
25
  const atomic_number = cell.numbers[idx];
28
26
  const element = ATOMIC_NUMBER_TO_SYMBOL[atomic_number];
@@ -31,11 +29,10 @@ original_structure) {
31
29
  }
32
30
  // Wrap fractional coordinates to [0, 1) range (moyo-wasm may return outside)
33
31
  const wrapped_abc = wrap_to_unit_cell(abc);
34
- const xyz = frac_to_cart(wrapped_abc);
35
32
  // Oxidation state is set to 0 (unknown) because moyo-wasm only provides atomic numbers.
36
33
  // transformed cell may have different/reordered sites, making it non-trivial to
37
34
  // map oxidation states from original structure.
38
- return make_site(element, wrapped_abc, xyz, element);
35
+ return make_site(element, wrapped_abc, frac_to_cart(wrapped_abc), element);
39
36
  });
40
37
  const lattice = {
41
38
  matrix: lattice_matrix,
@@ -55,14 +52,12 @@ original_structure) {
55
52
  // The conventional cell is the standard crystallographic setting for the space group.
56
53
  export function get_conventional_cell(original_structure, // The original input structure
57
54
  sym_data) {
58
- // The conventional cell as a Crystal
59
55
  return moyo_cell_to_structure(sym_data.std_cell, original_structure);
60
56
  }
61
57
  // Get the primitive cell from symmetry analysis data.
62
58
  // The primitive cell is the smallest unit cell with one lattice point.
63
59
  export function get_primitive_cell(original_structure, // The original input structure
64
60
  sym_data) {
65
- // The primitive cell as a Crystal
66
61
  return moyo_cell_to_structure(sym_data.prim_std_cell, original_structure);
67
62
  }
68
63
  // Transform a structure based on the selected cell type.
@@ -70,16 +65,12 @@ sym_data) {
70
65
  export function transform_cell(structure, // The original structure
71
66
  cell_type, // The desired cell type ('original', 'conventional', or 'primitive')
72
67
  sym_data) {
73
- //transformed structure (or original if no transformation needed)
74
- if (cell_type === `original` || !sym_data) {
68
+ if (!sym_data)
75
69
  return structure;
76
- }
77
- if (cell_type === `conventional`) {
70
+ if (cell_type === `conventional`)
78
71
  return get_conventional_cell(structure, sym_data);
79
- }
80
- if (cell_type === `primitive`) {
72
+ if (cell_type === `primitive`)
81
73
  return get_primitive_cell(structure, sym_data);
82
- }
83
- // Unknown cell_type at runtime (e.g. from stale persisted settings): leave unchanged
74
+ // 'original' or unknown runtime cell_type (e.g. stale persisted settings): unchanged
84
75
  return structure;
85
76
  }
@@ -89,10 +89,9 @@ function get_site_atomic_number(site, site_idx) {
89
89
  best_occupancy = occupancy;
90
90
  }
91
91
  });
92
- if (selected_element === undefined) {
93
- throw new Error(`Unknown element at site ${site_idx}: ${selected_element}`);
94
- }
95
- const atomic_number = SYMBOL_TO_ATOMIC_NUMBER[selected_element];
92
+ const atomic_number = selected_element
93
+ ? SYMBOL_TO_ATOMIC_NUMBER[selected_element]
94
+ : undefined;
96
95
  if (atomic_number === undefined) {
97
96
  throw new Error(`Unknown element at site ${site_idx}: ${selected_element}`);
98
97
  }
@@ -233,13 +232,11 @@ function wyckoff_rows_from_input_orbits(sym_data) {
233
232
  };
234
233
  });
235
234
  }
236
- // Generate Wyckoff table rows from symmetry data by grouping moyo's input-cell sites into
237
- // crystallographic orbits. moyo's per-site arrays (wyckoffs, orbits, site_symmetry_symbols)
238
- // always index the input cell and analyze_structure_symmetry always attaches input_cell, so
239
- // the orbit grouping is the single source of truth for any input cell setting.
240
- // Rows sort by ascending multiplicity, then Wyckoff label.
241
235
  // Numeric multiplicity prefix of a Wyckoff label like `4a` (NaN when absent)
242
236
  export const wyckoff_multiplicity = (label) => Number(/^\d+/.exec(label)?.[0] ?? NaN);
237
+ // Generate Wyckoff table rows from symmetry data via wyckoff_rows_from_input_orbits
238
+ // (analyze_structure_symmetry always attaches the input_cell that grouping needs).
239
+ // Rows sort by ascending multiplicity, then Wyckoff label.
243
240
  export function wyckoff_positions_from_moyo(sym_data) {
244
241
  if (!sym_data)
245
242
  return [];