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.
- package/dist/FilePicker.svelte +40 -50
- package/dist/api/optimade.js +10 -7
- package/dist/app.css +15 -0
- package/dist/brillouin/BrillouinZone.svelte +18 -9
- package/dist/brillouin/BrillouinZoneScene.svelte +20 -29
- package/dist/brillouin/compute.js +10 -16
- package/dist/brillouin/geometry.js +2 -2
- package/dist/chempot-diagram/ChemPotDiagram.svelte +18 -15
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +17 -113
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +104 -264
- package/dist/chempot-diagram/async-compute.svelte.js +12 -13
- package/dist/chempot-diagram/color.d.ts +21 -1
- package/dist/chempot-diagram/color.js +70 -1
- package/dist/chempot-diagram/compute.d.ts +7 -0
- package/dist/chempot-diagram/compute.js +85 -26
- package/dist/chempot-diagram/export.js +1 -6
- package/dist/chempot-diagram/temperature.js +9 -10
- package/dist/composition/BarChart.svelte +7 -9
- package/dist/composition/Composition.svelte +3 -8
- package/dist/composition/Formula.svelte +2 -2
- package/dist/composition/FormulaFilter.svelte +27 -93
- package/dist/composition/format.js +6 -8
- package/dist/composition/parse.js +50 -105
- package/dist/constants.d.ts +0 -1
- package/dist/constants.js +6 -17
- package/dist/convex-hull/ConvexHull2D.svelte +0 -2
- package/dist/convex-hull/ConvexHull3D.svelte +19 -47
- package/dist/convex-hull/ConvexHull4D.svelte +65 -95
- package/dist/convex-hull/StructurePopup.svelte +1 -1
- package/dist/convex-hull/gas-thermodynamics.js +21 -53
- package/dist/convex-hull/helpers.js +1 -7
- package/dist/convex-hull/thermodynamics.js +19 -27
- package/dist/coordination/CoordinationBarPlot.svelte +12 -21
- package/dist/element/BohrAtom.svelte +0 -1
- package/dist/element/ElementTile.svelte +26 -44
- package/dist/element/data.d.ts +2 -0
- package/dist/element/data.js +5 -1
- package/dist/element/index.d.ts +1 -1
- package/dist/element/index.js +1 -1
- package/dist/fermi-surface/FermiSurface.svelte +22 -23
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +2 -6
- package/dist/fermi-surface/FermiSurfaceScene.svelte +32 -77
- package/dist/fermi-surface/compute.js +22 -55
- package/dist/fermi-surface/parse.js +38 -70
- package/dist/file-viewer/JsonBrowser.svelte +1043 -0
- package/dist/file-viewer/JsonBrowser.svelte.d.ts +9 -0
- package/dist/file-viewer/PlotPanel.svelte +350 -0
- package/dist/file-viewer/PlotPanel.svelte.d.ts +9 -0
- package/dist/file-viewer/TrajectoryWithDos.svelte +30 -0
- package/dist/file-viewer/TrajectoryWithDos.svelte.d.ts +10 -0
- package/dist/file-viewer/detect.d.ts +11 -0
- package/dist/file-viewer/detect.js +381 -0
- package/dist/file-viewer/eligibility.d.ts +4 -0
- package/dist/file-viewer/eligibility.js +53 -0
- package/dist/file-viewer/host-protocol.d.ts +50 -0
- package/dist/file-viewer/host-transfer.d.ts +25 -0
- package/dist/file-viewer/host-transfer.js +42 -0
- package/dist/file-viewer/index.d.ts +5 -0
- package/dist/file-viewer/index.js +5 -0
- package/dist/file-viewer/main.d.ts +44 -0
- package/dist/file-viewer/main.js +535 -0
- package/dist/file-viewer/parse.d.ts +9 -0
- package/dist/file-viewer/parse.js +170 -0
- package/dist/file-viewer/plot-utils.d.ts +24 -0
- package/dist/file-viewer/plot-utils.js +191 -0
- package/dist/file-viewer/types.d.ts +4 -0
- package/dist/file-viewer/types.js +5 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +57 -84
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/io/ExportPane.svelte +1 -1
- package/dist/io/decompress.d.ts +2 -0
- package/dist/io/decompress.js +3 -2
- package/dist/io/export.d.ts +7 -4
- package/dist/io/export.js +70 -18
- package/dist/io/file-drop.js +60 -35
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +28 -16
- package/dist/isosurface/Isosurface.svelte +687 -196
- package/dist/isosurface/Isosurface.svelte.d.ts +7 -1
- package/dist/isosurface/IsosurfaceControls.svelte +564 -85
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +1 -1
- package/dist/isosurface/VolumeSlice.svelte +224 -0
- package/dist/isosurface/VolumeSlice.svelte.d.ts +28 -0
- package/dist/isosurface/coloring.d.ts +20 -0
- package/dist/isosurface/coloring.js +118 -0
- package/dist/isosurface/geometry-worker-types.d.ts +38 -0
- package/dist/isosurface/geometry.worker.d.ts +1 -0
- package/dist/isosurface/geometry.worker.js +59 -0
- package/dist/isosurface/grid.d.ts +10 -0
- package/dist/isosurface/grid.js +31 -0
- package/dist/isosurface/index.d.ts +4 -0
- package/dist/isosurface/index.js +4 -0
- package/dist/isosurface/profile.d.ts +12 -0
- package/dist/isosurface/profile.js +19 -0
- package/dist/isosurface/sampling.d.ts +34 -0
- package/dist/isosurface/sampling.js +408 -0
- package/dist/isosurface/slice-rendering.d.ts +13 -0
- package/dist/isosurface/slice-rendering.js +79 -0
- package/dist/isosurface/slice.d.ts +22 -2
- package/dist/isosurface/slice.js +200 -116
- package/dist/isosurface/types.d.ts +24 -1
- package/dist/isosurface/types.js +120 -8
- package/dist/json-path.d.ts +4 -0
- package/dist/json-path.js +89 -0
- package/dist/labels.js +1 -1
- package/dist/layout/InfoCard.svelte +1 -3
- package/dist/layout/SettingsSection.svelte +52 -61
- package/dist/layout/SettingsSection.svelte.d.ts +6 -7
- package/dist/layout/ViewerChrome.svelte +7 -1
- package/dist/layout/json-tree/JsonNode.svelte +14 -48
- package/dist/layout/json-tree/JsonTree.svelte +48 -95
- package/dist/layout/json-tree/JsonValue.svelte +3 -18
- package/dist/layout/json-tree/utils.d.ts +0 -3
- package/dist/layout/json-tree/utils.js +50 -179
- package/dist/marching-cubes.d.ts +8 -1
- package/dist/marching-cubes.js +115 -48
- package/dist/math.d.ts +3 -2
- package/dist/math.js +29 -38
- package/dist/overlays/ContextMenu.svelte +15 -32
- package/dist/overlays/DraggablePane.svelte +39 -58
- package/dist/overlays/DraggablePane.svelte.d.ts +1 -1
- package/dist/overlays/GlassChip.svelte +3 -1
- package/dist/periodic-table/PeriodicTableControls.svelte +26 -52
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +18 -34
- package/dist/phase-diagram/build-diagram.js +1 -6
- package/dist/phase-diagram/svg-to-diagram.js +9 -14
- package/dist/plot/bar/BarPlot.svelte +38 -42
- package/dist/plot/bar/SpacegroupBarPlot.svelte +18 -42
- package/dist/plot/box/BoxPlot.svelte +27 -23
- package/dist/plot/core/auto-place.d.ts +1 -1
- package/dist/plot/core/auto-place.js +1 -1
- package/dist/plot/core/data-transform.d.ts +1 -2
- package/dist/plot/core/data-transform.js +0 -10
- package/dist/plot/core/layout.d.ts +12 -5
- package/dist/plot/core/layout.js +37 -27
- package/dist/plot/core/pan-zoom.svelte.d.ts +2 -4
- package/dist/plot/core/pan-zoom.svelte.js +34 -17
- package/dist/plot/core/placed-tween.svelte.d.ts +3 -0
- package/dist/plot/core/placed-tween.svelte.js +68 -21
- package/dist/plot/core/svg.js +0 -1
- package/dist/plot/core/utils/hierarchy-chart.d.ts +1 -0
- package/dist/plot/core/utils/hierarchy-chart.js +12 -0
- package/dist/plot/core/utils/hierarchy-labels.js +3 -2
- package/dist/plot/histogram/Histogram.svelte +21 -26
- package/dist/plot/sankey/sankey.js +13 -9
- package/dist/plot/scatter/BinnedScatterPlot.svelte +59 -43
- package/dist/plot/scatter/ScatterPlot.svelte +67 -52
- package/dist/plot/scatter/adaptive-density.d.ts +1 -1
- package/dist/plot/scatter/adaptive-density.js +36 -20
- package/dist/plot/sunburst/Sunburst.svelte +20 -20
- package/dist/plot/treemap/Treemap.svelte +175 -60
- package/dist/plot/treemap/Treemap.svelte.d.ts +6 -0
- package/dist/plot/treemap/index.d.ts +1 -0
- package/dist/plot/treemap/labels.d.ts +38 -0
- package/dist/plot/treemap/labels.js +80 -0
- package/dist/plot/treemap/treemap.d.ts +4 -2
- package/dist/plot/treemap/treemap.js +5 -2
- package/dist/rdf/RdfPlot.svelte +17 -35
- package/dist/rdf/calc-rdf.js +59 -44
- package/dist/rdf/index.d.ts +0 -1
- package/dist/sanitize.js +84 -26
- package/dist/scene/SceneCamera.svelte +1 -1
- package/dist/scene/SceneCamera.svelte.d.ts +1 -1
- package/dist/scene/index.d.ts +1 -2
- package/dist/scene/index.js +1 -1
- package/dist/scene/{props.js → props.svelte.js} +15 -1
- package/dist/settings.js +159 -482
- package/dist/spectral/Bands.svelte +151 -158
- package/dist/spectral/BrillouinBandsDos.svelte +4 -19
- package/dist/spectral/Dos.svelte +26 -24
- package/dist/spectral/helpers.js +40 -105
- package/dist/structure/AtomLegend.svelte +8 -13
- package/dist/structure/AtomLegend.svelte.d.ts +2 -3
- package/dist/structure/CellSelect.svelte +35 -10
- package/dist/structure/Structure.svelte +177 -99
- package/dist/structure/Structure.svelte.d.ts +1 -1
- package/dist/structure/StructureCarousel.svelte +10 -3
- package/dist/structure/StructureControls.svelte +8 -26
- package/dist/structure/StructureControls.svelte.d.ts +1 -1
- package/dist/structure/StructureInfoPane.svelte +65 -76
- package/dist/structure/StructureScene.svelte +75 -109
- package/dist/structure/StructureScene.svelte.d.ts +3 -1
- package/dist/structure/StructureViewport.svelte +120 -55
- package/dist/structure/StructureViewport.svelte.d.ts +3 -1
- package/dist/structure/atom-properties.d.ts +1 -1
- package/dist/structure/atom-properties.js +4 -3
- package/dist/structure/bond-order-perception.js +3 -8
- package/dist/structure/bonding.d.ts +0 -1
- package/dist/structure/bonding.js +16 -23
- package/dist/structure/export.js +50 -74
- package/dist/structure/format-detect.js +5 -7
- package/dist/structure/index.js +6 -12
- package/dist/structure/parse.js +38 -100
- package/dist/structure/partial-occupancy.js +7 -4
- package/dist/structure/pbc.js +8 -10
- package/dist/structure/polyhedra.js +5 -4
- package/dist/structure/prop-groups.d.ts +85 -0
- package/dist/structure/prop-groups.js +18 -0
- package/dist/symmetry/SymmetryStats.svelte +0 -4
- package/dist/symmetry/WyckoffTable.svelte +5 -4
- package/dist/symmetry/cell-transform.js +5 -14
- package/dist/symmetry/index.js +6 -9
- package/dist/table/HeatmapTable.svelte +61 -103
- package/dist/table/ToggleMenu.svelte +166 -44
- package/dist/table/ToggleMenu.svelte.d.ts +1 -1
- package/dist/theme/embedded.d.ts +18 -0
- package/dist/theme/embedded.js +206 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/index.js +1 -2
- package/dist/theme/themes.mjs +31 -60
- package/dist/tooltip/TooltipContent.svelte +1 -1
- package/dist/tooltip/TooltipContent.svelte.d.ts +1 -1
- package/dist/tooltip/index.d.ts +8 -1
- package/dist/trajectory/Trajectory.svelte +140 -120
- package/dist/trajectory/TrajectoryError.svelte +1 -66
- package/dist/trajectory/TrajectoryExportPane.svelte +0 -1
- package/dist/trajectory/TrajectoryInfoPane.svelte +55 -80
- package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +2 -1
- package/dist/trajectory/extract.js +19 -29
- package/dist/trajectory/format-detect.d.ts +2 -0
- package/dist/trajectory/format-detect.js +7 -5
- package/dist/trajectory/frame-reader.js +3 -4
- package/dist/trajectory/helpers.js +16 -49
- package/dist/trajectory/index.js +2 -8
- package/dist/trajectory/parse/ase.js +1 -1
- package/dist/trajectory/parse/index.d.ts +4 -3
- package/dist/trajectory/parse/index.js +12 -17
- package/dist/trajectory/parse/vaspout-h5.d.ts +0 -1
- package/dist/trajectory/parse/vaspout-h5.js +1 -4
- package/dist/trajectory/parse/xyz.js +48 -2
- package/dist/trajectory/plotting.js +14 -37
- package/dist/xrd/XrdPlot.svelte +43 -68
- package/dist/xrd/calc-xrd.js +22 -58
- package/dist/xrd/parse.js +13 -22
- package/package.json +40 -8
- package/readme.md +6 -5
- package/dist/scene/types.d.ts +0 -26
- package/dist/tooltip/types.d.ts +0 -8
- package/dist/trajectory/constants.d.ts +0 -6
- package/dist/trajectory/constants.js +0 -11
- /package/dist/{scene/types.js → file-viewer/host-protocol.js} +0 -0
- /package/dist/{tooltip/types.js → isosurface/geometry-worker-types.js} +0 -0
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// callbacks so it drives Structure's external camera API. Camera state is per-pane:
|
|
9
9
|
// the primary pane binds it back to Structure's scene_props, while side panes keep it local.
|
|
10
10
|
import type { ElementSymbol } from '../element'
|
|
11
|
+
import { StatusMessage } from '../feedback'
|
|
11
12
|
import type { IsosurfaceSettings, VolumetricData } from '../isosurface/types'
|
|
12
13
|
import type { Vec3 } from '../math'
|
|
13
14
|
import type { CameraProjection } from '../settings'
|
|
@@ -46,13 +47,46 @@
|
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
// Self-heal evicted WebGL contexts: browsers cap live contexts (~8-16 per
|
|
51
|
+
// page) and evict the oldest without notice, leaving its canvas permanently
|
|
52
|
+
// blank. three.js handles `webglcontextrestored` when the browser restores
|
|
53
|
+
// on its own; when it doesn't, remount the <Canvas> for a fresh context.
|
|
54
|
+
// Bounded retries avoid eviction ping-pong when over the context budget.
|
|
55
|
+
let canvas_remount_token = $state(0)
|
|
56
|
+
let remount_timer: ReturnType<typeof setTimeout> | undefined
|
|
57
|
+
let recovery_reset_timer: ReturnType<typeof setTimeout> | undefined
|
|
58
|
+
let recovery_attempts = 0
|
|
59
|
+
let recovery_failed = $state(false)
|
|
60
|
+
|
|
61
|
+
const create_renderer = (canvas: HTMLCanvasElement) => {
|
|
62
|
+
canvas.addEventListener(`webglcontextlost`, () => {
|
|
63
|
+
// canvas.isConnected is false when the loss came from our own dispose()
|
|
64
|
+
// (component unmounted), not from browser eviction
|
|
65
|
+
if (!canvas.isConnected) return
|
|
66
|
+
clearTimeout(recovery_reset_timer)
|
|
67
|
+
if (recovery_attempts >= 3) {
|
|
68
|
+
recovery_failed = true
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
recovery_attempts += 1
|
|
72
|
+
remount_timer = setTimeout(() => (canvas_remount_token += 1), 1000)
|
|
73
|
+
})
|
|
74
|
+
canvas.addEventListener(`webglcontextrestored`, () => {
|
|
75
|
+
clearTimeout(remount_timer)
|
|
76
|
+
recovery_attempts = 0
|
|
77
|
+
recovery_failed = false
|
|
78
|
+
})
|
|
79
|
+
const renderer = new StructureRenderer({
|
|
51
80
|
canvas,
|
|
52
81
|
powerPreference: `high-performance`,
|
|
53
82
|
antialias: true,
|
|
54
83
|
alpha: true,
|
|
55
84
|
})
|
|
85
|
+
// Reset only after a stable remount; an immediate reset would allow
|
|
86
|
+
// endless eviction ping-pong while the page exceeds its context budget.
|
|
87
|
+
recovery_reset_timer = setTimeout(() => (recovery_attempts = 0), 5000)
|
|
88
|
+
return renderer
|
|
89
|
+
}
|
|
56
90
|
|
|
57
91
|
let {
|
|
58
92
|
// Multi-view chrome
|
|
@@ -74,6 +108,8 @@
|
|
|
74
108
|
lattice_props = {},
|
|
75
109
|
volumetric_data = undefined,
|
|
76
110
|
isosurface_settings = undefined,
|
|
111
|
+
active_volume_idx = 0,
|
|
112
|
+
volume_scaling = [1, 1, 1],
|
|
77
113
|
bond_edits_enabled = true,
|
|
78
114
|
bond_edit_order = 1,
|
|
79
115
|
measure_mode = `distance`,
|
|
@@ -126,8 +162,10 @@
|
|
|
126
162
|
scene_props?: ComponentProps<typeof StructureScene>
|
|
127
163
|
gizmo?: boolean | ComponentProps<typeof StructureScene>[`gizmo`]
|
|
128
164
|
lattice_props?: ComponentProps<typeof StructureScene>[`lattice_props`]
|
|
129
|
-
volumetric_data?: VolumetricData
|
|
165
|
+
volumetric_data?: VolumetricData | VolumetricData[]
|
|
130
166
|
isosurface_settings?: IsosurfaceSettings
|
|
167
|
+
active_volume_idx?: number
|
|
168
|
+
volume_scaling?: Vec3
|
|
131
169
|
bond_edits_enabled?: boolean
|
|
132
170
|
bond_edit_order?: BondOrder
|
|
133
171
|
measure_mode?: MeasureMode
|
|
@@ -275,6 +313,11 @@
|
|
|
275
313
|
return
|
|
276
314
|
reset_camera()
|
|
277
315
|
}
|
|
316
|
+
|
|
317
|
+
$effect(() => () => {
|
|
318
|
+
clearTimeout(remount_timer)
|
|
319
|
+
clearTimeout(recovery_reset_timer)
|
|
320
|
+
})
|
|
278
321
|
</script>
|
|
279
322
|
|
|
280
323
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
@@ -289,57 +332,69 @@
|
|
|
289
332
|
ondblclick={handle_dblclick}
|
|
290
333
|
>
|
|
291
334
|
{#if label}<span class="viewport-label">{label}</span>{/if}
|
|
292
|
-
|
|
293
|
-
<
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
335
|
+
{#if recovery_failed}
|
|
336
|
+
<div class="context-recovery-error">
|
|
337
|
+
<StatusMessage
|
|
338
|
+
message="Unable to restore the 3D view after repeated WebGL context loss. Reload the page to retry."
|
|
339
|
+
type="error"
|
|
340
|
+
/>
|
|
341
|
+
</div>
|
|
342
|
+
{/if}
|
|
343
|
+
{#key canvas_remount_token}
|
|
344
|
+
<Canvas createRenderer={create_renderer}>
|
|
345
|
+
<StructureScene
|
|
346
|
+
{structure}
|
|
347
|
+
{base_structure}
|
|
348
|
+
{...scene_props}
|
|
349
|
+
{...in_grid ? { auto_rotate: 0 } : {}}
|
|
350
|
+
{camera_position}
|
|
351
|
+
{camera_target}
|
|
352
|
+
{camera_projection}
|
|
353
|
+
{camera_direction}
|
|
354
|
+
{interactive}
|
|
355
|
+
gizmo={gizmo_prop}
|
|
356
|
+
{lattice_props}
|
|
357
|
+
{volumetric_data}
|
|
358
|
+
{isosurface_settings}
|
|
359
|
+
{active_volume_idx}
|
|
360
|
+
{volume_scaling}
|
|
361
|
+
bind:camera_is_moving
|
|
362
|
+
bind:selected_sites
|
|
363
|
+
{active_sites}
|
|
364
|
+
bind:hovered_idx={hovered_site_idx}
|
|
365
|
+
bind:measured_sites
|
|
366
|
+
bind:scene
|
|
367
|
+
bind:camera
|
|
368
|
+
bind:orbit_controls
|
|
369
|
+
bind:rotation_target_ref
|
|
370
|
+
bind:initial_computed_zoom
|
|
371
|
+
bind:hidden_elements
|
|
372
|
+
bind:hidden_prop_vals
|
|
373
|
+
bind:element_radius_overrides
|
|
374
|
+
bind:site_radius_overrides
|
|
375
|
+
bind:added_bonds
|
|
376
|
+
bind:removed_bonds
|
|
377
|
+
bind:bond_order_overrides
|
|
378
|
+
{bond_edits_enabled}
|
|
379
|
+
bind:bond_edit_mode
|
|
380
|
+
{bond_edit_order}
|
|
381
|
+
{measure_mode}
|
|
382
|
+
{width}
|
|
383
|
+
{height}
|
|
384
|
+
{atom_color_config}
|
|
385
|
+
{sym_data}
|
|
386
|
+
{on_sites_moved}
|
|
387
|
+
{on_operation_start}
|
|
388
|
+
{on_bond_edit_start}
|
|
389
|
+
{on_add_atom}
|
|
390
|
+
bind:add_atom_mode
|
|
391
|
+
bind:add_element
|
|
392
|
+
bind:cursor
|
|
393
|
+
bind:dragging_atoms
|
|
394
|
+
bind:polyhedra_rendered_elements
|
|
395
|
+
/>
|
|
396
|
+
</Canvas>
|
|
397
|
+
{/key}
|
|
343
398
|
</div>
|
|
344
399
|
|
|
345
400
|
<style>
|
|
@@ -368,7 +423,7 @@
|
|
|
368
423
|
position: absolute;
|
|
369
424
|
top: 3px;
|
|
370
425
|
left: 5px;
|
|
371
|
-
z-index: 1;
|
|
426
|
+
z-index: var(--z-index-viewer-label, 1);
|
|
372
427
|
pointer-events: none;
|
|
373
428
|
font-size: var(--struct-viewport-label-font-size, 0.8em);
|
|
374
429
|
font-weight: 500;
|
|
@@ -380,6 +435,16 @@
|
|
|
380
435
|
color-mix(in srgb, var(--page-bg, Canvas) 65%, transparent)
|
|
381
436
|
);
|
|
382
437
|
}
|
|
438
|
+
.context-recovery-error {
|
|
439
|
+
position: absolute;
|
|
440
|
+
inset: 0;
|
|
441
|
+
z-index: var(--z-index-viewer-tooltip, 1000);
|
|
442
|
+
display: grid;
|
|
443
|
+
place-items: center;
|
|
444
|
+
padding: 1em;
|
|
445
|
+
pointer-events: none;
|
|
446
|
+
will-change: transform;
|
|
447
|
+
}
|
|
383
448
|
.viewport-cell :global(canvas) {
|
|
384
449
|
cursor: var(--canvas-cursor, default);
|
|
385
450
|
}
|
|
@@ -24,8 +24,10 @@ type $$ComponentProps = {
|
|
|
24
24
|
scene_props?: ComponentProps<typeof StructureScene>;
|
|
25
25
|
gizmo?: boolean | ComponentProps<typeof StructureScene>[`gizmo`];
|
|
26
26
|
lattice_props?: ComponentProps<typeof StructureScene>[`lattice_props`];
|
|
27
|
-
volumetric_data?: VolumetricData;
|
|
27
|
+
volumetric_data?: VolumetricData | VolumetricData[];
|
|
28
28
|
isosurface_settings?: IsosurfaceSettings;
|
|
29
|
+
active_volume_idx?: number;
|
|
30
|
+
volume_scaling?: Vec3;
|
|
29
31
|
bond_edits_enabled?: boolean;
|
|
30
32
|
bond_edit_order?: BondOrder;
|
|
31
33
|
measure_mode?: MeasureMode;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ColorScaleType, D3InterpolateName } from '../colors';
|
|
2
|
-
import {
|
|
2
|
+
import type { CoordinationData } from '../coordination/calc-coordination';
|
|
3
3
|
import type { AtomColorMode } from '../settings';
|
|
4
4
|
import type { AnyStructure, Site } from './';
|
|
5
5
|
import type { BondingStrategy } from './bonding';
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// Utility functions for computing atom properties and applying color scales
|
|
2
2
|
import { get_d3_interpolator } from '../colors';
|
|
3
|
-
import { calc_coordination_nums
|
|
3
|
+
import { calc_coordination_nums } from '../coordination/calc-coordination';
|
|
4
|
+
import { element_by_symbol } from '../element/data';
|
|
4
5
|
import * as math from '../math';
|
|
5
|
-
import {
|
|
6
|
+
import { get_majority_element } from './bonding';
|
|
6
7
|
import { wrap_frac_coord } from './pbc';
|
|
7
8
|
import { rgb } from 'd3-color';
|
|
8
9
|
import * as d3_sc from 'd3-scale-chromatic';
|
|
@@ -106,7 +107,7 @@ function expand_structure_for_pbc(structure, strategy) {
|
|
|
106
107
|
// Covalent radius per atom (0 = unknown → forms no bonds → needs no images)
|
|
107
108
|
const radii = cell_sites.map((site) => {
|
|
108
109
|
const elem = get_majority_element(site);
|
|
109
|
-
return (elem ?
|
|
110
|
+
return (elem ? element_by_symbol.get(elem)?.covalent_radius : undefined) ?? 0;
|
|
110
111
|
});
|
|
111
112
|
let max_radius = 0;
|
|
112
113
|
for (const radius of radii)
|
|
@@ -52,12 +52,10 @@ function formal_charge(symbol, bond_valence) {
|
|
|
52
52
|
const is_main_group = (symbol) => symbol in ATOMIC_VALENCE;
|
|
53
53
|
// Cap per-fragment valence enumeration (3^k for catenated S/Se/Te/P chains).
|
|
54
54
|
const MAX_VALENCE_COMBOS = 4096;
|
|
55
|
+
// Edges are bounds-checked by perceive_bond_orders before reaching here.
|
|
55
56
|
function split_fragments(n_atoms, edges) {
|
|
56
57
|
const adjacency = Array.from({ length: n_atoms }, () => []);
|
|
57
58
|
for (const [atom_idx_1, atom_idx_2] of edges) {
|
|
58
|
-
if (adjacency[atom_idx_1] === undefined || adjacency[atom_idx_2] === undefined) {
|
|
59
|
-
throw new Error(`Invalid edge ${atom_idx_1}-${atom_idx_2} for ${n_atoms} atoms`);
|
|
60
|
-
}
|
|
61
59
|
adjacency[atom_idx_1].push(atom_idx_2);
|
|
62
60
|
adjacency[atom_idx_2].push(atom_idx_1);
|
|
63
61
|
}
|
|
@@ -140,9 +138,6 @@ function assign_bond_orders(edges, target_valence) {
|
|
|
140
138
|
function find_rings(n_atoms, edges) {
|
|
141
139
|
const adjacency = Array.from({ length: n_atoms }, () => new Set());
|
|
142
140
|
for (const [atom_idx_1, atom_idx_2] of edges) {
|
|
143
|
-
if (adjacency[atom_idx_1] === undefined || adjacency[atom_idx_2] === undefined) {
|
|
144
|
-
throw new Error(`Invalid edge ${atom_idx_1}-${atom_idx_2} for ${n_atoms} atoms`);
|
|
145
|
-
}
|
|
146
141
|
adjacency[atom_idx_1].add(atom_idx_2);
|
|
147
142
|
adjacency[atom_idx_2].add(atom_idx_1);
|
|
148
143
|
}
|
|
@@ -200,7 +195,6 @@ function find_rings(n_atoms, edges) {
|
|
|
200
195
|
}
|
|
201
196
|
return [...uniq.values()];
|
|
202
197
|
}
|
|
203
|
-
const order_to_bond_order = (order) => order >= 3 ? 3 : order === 2 ? 2 : 1;
|
|
204
198
|
// Conservative planarity check: degenerate first-3-atom planes are non-planar.
|
|
205
199
|
function ring_is_planar(ring, sites) {
|
|
206
200
|
if (ring.length < 3)
|
|
@@ -294,7 +288,8 @@ export function perceive_bond_orders(sites, bonds, opts = {}) {
|
|
|
294
288
|
if (!solved)
|
|
295
289
|
continue;
|
|
296
290
|
local_edges.forEach((edge, edge_idx) => {
|
|
297
|
-
const
|
|
291
|
+
const solved_order = solved[edge_idx];
|
|
292
|
+
const order = solved_order >= 3 ? 3 : solved_order === 2 ? 2 : 1;
|
|
298
293
|
result.set(edge.bond, { ...edge.bond, bond_order: order, perceived: true });
|
|
299
294
|
});
|
|
300
295
|
// Hückel aromatic post-pass, retaining Kekulé orders for display toggles.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ElementSymbol } from '../element';
|
|
2
2
|
import type { Vec3 } from '../math';
|
|
3
3
|
import type { AnyStructure, BondOrder, BondPair, Site, StructureBond } from './';
|
|
4
|
-
export declare const element_lookup: Map<"Ac" | "Ag" | "Al" | "Am" | "Ar" | "As" | "At" | "Au" | "B" | "Ba" | "Be" | "Bh" | "Bi" | "Bk" | "Br" | "C" | "Ca" | "Cd" | "Ce" | "Cf" | "Cl" | "Cm" | "Co" | "Cr" | "Cs" | "Cu" | "Db" | "Dy" | "Er" | "Es" | "Eu" | "F" | "Fe" | "Fm" | "Fr" | "Ga" | "Gd" | "Ge" | "H" | "He" | "Hf" | "Hg" | "Ho" | "Hs" | "I" | "In" | "Ir" | "K" | "Kr" | "La" | "Li" | "Lr" | "Lu" | "Md" | "Mg" | "Mn" | "Mo" | "Mt" | "N" | "Na" | "Nb" | "Nd" | "Ne" | "Ni" | "No" | "Np" | "O" | "Os" | "P" | "Pa" | "Pb" | "Pd" | "Pm" | "Po" | "Pr" | "Pt" | "Pu" | "Ra" | "Rb" | "Re" | "Rf" | "Rh" | "Rn" | "Ru" | "S" | "Sb" | "Sc" | "Se" | "Sg" | "Si" | "Sm" | "Sn" | "Sr" | "Ta" | "Tb" | "Tc" | "Te" | "Th" | "Ti" | "Tl" | "Tm" | "U" | "V" | "W" | "Xe" | "Y" | "Yb" | "Zn" | "Zr" | "Ds" | "Rg" | "Cn" | "Nh" | "Fl" | "Mc" | "Lv" | "Ts" | "Og", import("../element").ChemicalElement>;
|
|
5
4
|
export declare const get_majority_element: (site: Site | undefined) => ElementSymbol | null;
|
|
6
5
|
export declare const is_spectator_center: (element: string) => boolean;
|
|
7
6
|
export declare function has_framework_potential(elements: Iterable<string>): boolean;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// Bonding algorithms for structure visualization
|
|
2
|
-
import {
|
|
2
|
+
import element_data, { element_by_symbol } from '../element/data';
|
|
3
3
|
import * as math from '../math';
|
|
4
|
-
// Shared per-symbol element data lookup (also used by pbc.ts and polyhedra.ts)
|
|
5
|
-
export const element_lookup = new Map(element_data.map((el) => [el.symbol, el]));
|
|
6
4
|
const covalent_radii = new Map(element_data.flatMap((el) => el.covalent_radius === null ? [] : [[el.symbol, el.covalent_radius]]));
|
|
7
5
|
// Majority-occupancy element of a (possibly disordered) site
|
|
8
6
|
export const get_majority_element = (site) => {
|
|
@@ -17,9 +15,8 @@ export const get_majority_element = (site) => {
|
|
|
17
15
|
// or when force-included via `included_center_elements`. Shared by polyhedra.ts
|
|
18
16
|
// (vertex/center selection) and pbc.ts (phase-2 boundary completion) so the bond
|
|
19
17
|
// graph and the polyhedra it feeds stay consistent.
|
|
20
|
-
const SPECTATOR_CATEGORIES = new Set([`alkali metal`]);
|
|
21
18
|
const HEAVY_ALKALINE_EARTHS = new Set([`Ca`, `Sr`, `Ba`, `Ra`]);
|
|
22
|
-
export const is_spectator_center = (element) =>
|
|
19
|
+
export const is_spectator_center = (element) => element_by_symbol.get(element)?.category === `alkali metal` ||
|
|
23
20
|
HEAVY_ALKALINE_EARTHS.has(element);
|
|
24
21
|
// True if the composition contains a framework cation: a non-spectator element
|
|
25
22
|
// strictly less electronegative than the most electronegative element present
|
|
@@ -32,23 +29,18 @@ export function has_framework_potential(elements) {
|
|
|
32
29
|
const els = [...new Set(elements)]; // dedupe so callers can pass per-site element lists
|
|
33
30
|
let max_en = -Infinity;
|
|
34
31
|
for (const el of els) {
|
|
35
|
-
const en =
|
|
32
|
+
const en = element_by_symbol.get(el)?.electronegativity;
|
|
36
33
|
if (en != null && en > max_en)
|
|
37
34
|
max_en = en;
|
|
38
35
|
}
|
|
39
36
|
return els.some((el) => {
|
|
40
37
|
if (is_spectator_center(el))
|
|
41
38
|
return false;
|
|
42
|
-
const en =
|
|
39
|
+
const en = element_by_symbol.get(el)?.electronegativity;
|
|
43
40
|
return en != null && en < max_en;
|
|
44
41
|
});
|
|
45
42
|
}
|
|
46
43
|
const is_zero_cell_shift = (cell_shift) => cell_shift === undefined || cell_shift.every((val) => val === 0);
|
|
47
|
-
const format_cell_shift = (cell_shift) => {
|
|
48
|
-
if (cell_shift === undefined || is_zero_cell_shift(cell_shift))
|
|
49
|
-
return ``;
|
|
50
|
-
return `@${cell_shift.join(`,`)}`;
|
|
51
|
-
};
|
|
52
44
|
const negate_cell_shift = (cell_shift) => [
|
|
53
45
|
cell_shift[0] === 0 ? 0 : -cell_shift[0],
|
|
54
46
|
cell_shift[1] === 0 ? 0 : -cell_shift[1],
|
|
@@ -83,7 +75,10 @@ export const normalize_structure_bond = (site_idx_1, site_idx_2, order, cell_shi
|
|
|
83
75
|
};
|
|
84
76
|
export const get_bond_key = (idx_1, idx_2, cell_shift) => {
|
|
85
77
|
const normalized = normalize_bond_endpoints(idx_1, idx_2, cell_shift);
|
|
86
|
-
|
|
78
|
+
const shift_suffix = normalized.cell_shift === undefined || is_zero_cell_shift(normalized.cell_shift)
|
|
79
|
+
? ``
|
|
80
|
+
: `@${normalized.cell_shift.join(`,`)}`;
|
|
81
|
+
return `${normalized.site_idx_1}-${normalized.site_idx_2}${shift_suffix}`;
|
|
87
82
|
};
|
|
88
83
|
// Remap explicit bond metadata after site deletion: drop bonds touching deleted
|
|
89
84
|
// sites and shift each surviving index down by the number of deleted indices below it.
|
|
@@ -280,7 +275,6 @@ export const merge_bond_edits = (base_bonds, added, removed, overrides) => {
|
|
|
280
275
|
}
|
|
281
276
|
return [...merged.values()];
|
|
282
277
|
};
|
|
283
|
-
const is_record = (value) => typeof value === `object` && value !== null;
|
|
284
278
|
export function normalize_bond_order(order) {
|
|
285
279
|
if (order === `aromatic`)
|
|
286
280
|
return order;
|
|
@@ -338,13 +332,14 @@ export function get_explicit_bond_metadata(structure) {
|
|
|
338
332
|
}
|
|
339
333
|
const explicit_bonds = new Map();
|
|
340
334
|
for (const [entry_idx, raw_bond] of raw_bonds.entries()) {
|
|
341
|
-
if (
|
|
335
|
+
if (typeof raw_bond !== `object` || raw_bond === null) {
|
|
342
336
|
console.warn(`Ignoring invalid explicit bond at index ${entry_idx}: expected object`);
|
|
343
337
|
continue;
|
|
344
338
|
}
|
|
345
|
-
const
|
|
346
|
-
const
|
|
347
|
-
const
|
|
339
|
+
const bond_record = raw_bond;
|
|
340
|
+
const { order } = bond_record;
|
|
341
|
+
const site_idx_1 = bond_record.site_idx_1;
|
|
342
|
+
const site_idx_2 = bond_record.site_idx_2;
|
|
348
343
|
if (typeof site_idx_1 !== `number` ||
|
|
349
344
|
typeof site_idx_2 !== `number` ||
|
|
350
345
|
!Number.isInteger(site_idx_1) ||
|
|
@@ -364,7 +359,7 @@ export function get_explicit_bond_metadata(structure) {
|
|
|
364
359
|
console.warn(`Ignoring invalid explicit bond at index ${entry_idx}: unsupported order ${String(order)}`);
|
|
365
360
|
continue;
|
|
366
361
|
}
|
|
367
|
-
const cell_shift = normalize_cell_shift(
|
|
362
|
+
const cell_shift = normalize_cell_shift(bond_record.cell_shift);
|
|
368
363
|
if (cell_shift === null) {
|
|
369
364
|
console.warn(`Ignoring invalid explicit bond at index ${entry_idx}: cell_shift must be three integers`);
|
|
370
365
|
continue;
|
|
@@ -373,9 +368,7 @@ export function get_explicit_bond_metadata(structure) {
|
|
|
373
368
|
console.warn(`Ignoring invalid explicit bond at index ${entry_idx}: endpoints match`);
|
|
374
369
|
continue;
|
|
375
370
|
}
|
|
376
|
-
if (cell_shift
|
|
377
|
-
!is_zero_cell_shift(cell_shift) &&
|
|
378
|
-
!(`lattice` in structure)) {
|
|
371
|
+
if (!is_zero_cell_shift(cell_shift) && !(`lattice` in structure)) {
|
|
379
372
|
console.warn(`Ignoring invalid explicit bond at index ${entry_idx}: cell_shift requires a crystal lattice`);
|
|
380
373
|
continue;
|
|
381
374
|
}
|
|
@@ -650,7 +643,7 @@ center_count = Infinity, } = {}) {
|
|
|
650
643
|
const elem_id_lookup = new Map();
|
|
651
644
|
for (let idx = 0; idx < n_sites; idx++) {
|
|
652
645
|
const elem = get_majority_element(sites[idx]);
|
|
653
|
-
const data = elem ?
|
|
646
|
+
const data = elem ? element_by_symbol.get(elem) : undefined;
|
|
654
647
|
electronegs[idx] = data?.electronegativity ?? 2.0;
|
|
655
648
|
metal_flags[idx] = data?.metal ? 1 : 0;
|
|
656
649
|
nonmetal_flags[idx] = data?.nonmetal ? 1 : 0;
|
package/dist/structure/export.js
CHANGED
|
@@ -6,22 +6,15 @@ import { Color, Group, Matrix4, Mesh, MeshStandardMaterial, ShaderMaterial } fro
|
|
|
6
6
|
import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';
|
|
7
7
|
import { OBJExporter } from 'three/examples/jsm/exporters/OBJExporter.js';
|
|
8
8
|
// @internal exported only for tests - not part of the public API.
|
|
9
|
-
// Type guard to check if a material has a
|
|
10
|
-
//
|
|
9
|
+
// Type guard to check if a material has a Color-like property with numeric r/g/b
|
|
10
|
+
// channels (duck typing for bundler compatibility across different Three.js
|
|
11
|
+
// instances - Threlte vs vanilla Three.js).
|
|
11
12
|
export function has_color_property(mat) {
|
|
12
|
-
if (!(`color` in mat))
|
|
13
|
-
return false;
|
|
14
13
|
const color = mat.color;
|
|
15
14
|
if (!color || typeof color !== `object`)
|
|
16
15
|
return false;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const red_channel = color_obj.r;
|
|
20
|
-
const green_channel = color_obj.g;
|
|
21
|
-
const blue_channel = color_obj.b;
|
|
22
|
-
return (typeof red_channel === `number` &&
|
|
23
|
-
typeof green_channel === `number` &&
|
|
24
|
-
typeof blue_channel === `number`);
|
|
16
|
+
const { r: red, g: green, b: blue } = color;
|
|
17
|
+
return typeof red === `number` && typeof green === `number` && typeof blue === `number`;
|
|
25
18
|
}
|
|
26
19
|
// Extract color from a ShaderMaterial by checking common color uniform patterns
|
|
27
20
|
function extract_shader_color(shader_mat) {
|
|
@@ -34,8 +27,8 @@ function extract_shader_color(shader_mat) {
|
|
|
34
27
|
return uniforms[key].value.clone();
|
|
35
28
|
}
|
|
36
29
|
}
|
|
37
|
-
//
|
|
38
|
-
//
|
|
30
|
+
// Gradient bonds store colors as instanceColorStart/End geometry attributes,
|
|
31
|
+
// not uniforms — nothing to extract here (caller falls back to gray)
|
|
39
32
|
return null;
|
|
40
33
|
}
|
|
41
34
|
// Convert ShaderMaterial to MeshStandardMaterial for export compatibility
|
|
@@ -146,13 +139,6 @@ export function generate_mtl_content(scene) {
|
|
|
146
139
|
});
|
|
147
140
|
return lines.join(`\n`);
|
|
148
141
|
}
|
|
149
|
-
// Extract color from material, returning RGB values or null if not found
|
|
150
|
-
function extract_material_color(mat) {
|
|
151
|
-
if (has_color_property(mat)) {
|
|
152
|
-
return { r: mat.color.r, g: mat.color.g, b: mat.color.b };
|
|
153
|
-
}
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
142
|
// Helper function to convert InstancedMesh to regular Mesh objects for export
|
|
157
143
|
// This is necessary because GLB/OBJ exporters don't handle InstancedMesh properly
|
|
158
144
|
// Note: Threlte's InstancedMesh sets isInstancedMesh=true but type remains "Mesh"
|
|
@@ -209,25 +195,29 @@ export function convert_instanced_meshes_to_regular(scene) {
|
|
|
209
195
|
else {
|
|
210
196
|
// Extract shared material color for atoms
|
|
211
197
|
const single_mat = Array.isArray(mat) ? mat[0] : mat;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
198
|
+
if (has_color_property(single_mat)) {
|
|
199
|
+
material_colors.set(mesh_id, {
|
|
200
|
+
r: single_mat.color.r,
|
|
201
|
+
g: single_mat.color.g,
|
|
202
|
+
b: single_mat.color.b,
|
|
203
|
+
});
|
|
215
204
|
}
|
|
216
205
|
}
|
|
217
206
|
});
|
|
218
207
|
// STEP 2: Clone the scene
|
|
219
208
|
const cloned_scene = scene.clone();
|
|
220
|
-
// STEP 3: Find all InstancedMesh objects in
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
209
|
+
// STEP 3: Find all InstancedMesh objects in original + cloned scene (same traversal
|
|
210
|
+
// order) to map cloned meshes back to their original uuid
|
|
211
|
+
const collect_instanced = (root) => {
|
|
212
|
+
const meshes = [];
|
|
213
|
+
root.traverse((object) => {
|
|
214
|
+
if (is_instanced_mesh(object))
|
|
215
|
+
meshes.push(object);
|
|
216
|
+
});
|
|
217
|
+
return meshes;
|
|
218
|
+
};
|
|
219
|
+
const original_meshes = collect_instanced(scene);
|
|
220
|
+
const cloned_meshes = collect_instanced(cloned_scene);
|
|
231
221
|
// STEP 4: Convert each InstancedMesh to individual Mesh objects
|
|
232
222
|
for (let mesh_idx = 0; mesh_idx < cloned_meshes.length; mesh_idx++) {
|
|
233
223
|
const instanced_mesh = cloned_meshes[mesh_idx];
|
|
@@ -329,26 +319,18 @@ export function create_structure_filename(structure, extension) {
|
|
|
329
319
|
if (formula && formula !== `Unknown`) {
|
|
330
320
|
safe_push(formula.replaceAll(` `, ``));
|
|
331
321
|
}
|
|
332
|
-
// Add space group if available
|
|
333
|
-
|
|
334
|
-
structure.symmetry
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (space_group && typeof space_group === `string`) {
|
|
339
|
-
safe_push(space_group.replaceAll(` `, ``));
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
// Add lattice system if available
|
|
343
|
-
if (`lattice` in structure &&
|
|
344
|
-
structure.lattice &&
|
|
345
|
-
typeof structure.lattice === `object` &&
|
|
346
|
-
`lattice_system` in structure.lattice) {
|
|
347
|
-
const lattice_system = structure.lattice.lattice_system;
|
|
348
|
-
if (lattice_system && typeof lattice_system === `string`) {
|
|
349
|
-
safe_push(lattice_system);
|
|
350
|
-
}
|
|
322
|
+
// Add space group and lattice system if available
|
|
323
|
+
const symmetry = `symmetry` in structure && is_plain_object(structure.symmetry)
|
|
324
|
+
? structure.symmetry
|
|
325
|
+
: undefined;
|
|
326
|
+
if (typeof symmetry?.space_group_symbol === `string`) {
|
|
327
|
+
safe_push(symmetry.space_group_symbol.replaceAll(` `, ``));
|
|
351
328
|
}
|
|
329
|
+
const lattice = `lattice` in structure && is_plain_object(structure.lattice)
|
|
330
|
+
? structure.lattice
|
|
331
|
+
: undefined;
|
|
332
|
+
if (lattice && `lattice_system` in lattice && typeof lattice.lattice_system === `string`)
|
|
333
|
+
safe_push(lattice.lattice_system);
|
|
352
334
|
// Add number of sites
|
|
353
335
|
if (structure.sites?.length)
|
|
354
336
|
parts.push(`${structure.sites.length}sites`);
|
|
@@ -465,21 +447,14 @@ export function structure_to_cif_str(structure) {
|
|
|
465
447
|
}
|
|
466
448
|
const lines = [];
|
|
467
449
|
// CIF header with data block (required by pymatgen and CIF spec)
|
|
468
|
-
lines.push(`# CIF file generated by MatterViz`);
|
|
469
|
-
const block_name = get_cif_block_name(structure);
|
|
470
|
-
lines.push(`data_${block_name}`);
|
|
471
|
-
lines.push(``);
|
|
450
|
+
lines.push(`# CIF file generated by MatterViz`, `data_${get_cif_block_name(structure)}`, ``);
|
|
472
451
|
// Cell parameters
|
|
473
452
|
const lattice = structure.lattice;
|
|
474
453
|
if (lattice.a && lattice.b && lattice.c) {
|
|
475
|
-
lines.push(`_cell_length_a ${lattice.a.toFixed(6)}`);
|
|
476
|
-
lines.push(`_cell_length_b ${lattice.b.toFixed(6)}`);
|
|
477
|
-
lines.push(`_cell_length_c ${lattice.c.toFixed(6)}`);
|
|
454
|
+
lines.push(`_cell_length_a ${lattice.a.toFixed(6)}`, `_cell_length_b ${lattice.b.toFixed(6)}`, `_cell_length_c ${lattice.c.toFixed(6)}`);
|
|
478
455
|
}
|
|
479
456
|
if (lattice.alpha && lattice.beta && lattice.gamma) {
|
|
480
|
-
lines.push(`_cell_angle_alpha ${lattice.alpha.toFixed(6)}`);
|
|
481
|
-
lines.push(`_cell_angle_beta ${lattice.beta.toFixed(6)}`);
|
|
482
|
-
lines.push(`_cell_angle_gamma ${lattice.gamma.toFixed(6)}`);
|
|
457
|
+
lines.push(`_cell_angle_alpha ${lattice.alpha.toFixed(6)}`, `_cell_angle_beta ${lattice.beta.toFixed(6)}`, `_cell_angle_gamma ${lattice.gamma.toFixed(6)}`);
|
|
483
458
|
}
|
|
484
459
|
// Space group information
|
|
485
460
|
if (`symmetry` in structure && is_plain_object(structure.symmetry)) {
|
|
@@ -498,13 +473,7 @@ export function structure_to_cif_str(structure) {
|
|
|
498
473
|
// full P1 list, so without it parsers would re-apply the H-M ops and multiply sites
|
|
499
474
|
lines.push(`loop_`, `_symmetry_equiv_pos_as_xyz`, ` 'x, y, z'`, ``);
|
|
500
475
|
// Atom site loop header
|
|
501
|
-
lines.push(`loop_`);
|
|
502
|
-
lines.push(`_atom_site_label`);
|
|
503
|
-
lines.push(`_atom_site_type_symbol`);
|
|
504
|
-
lines.push(`_atom_site_fract_x`);
|
|
505
|
-
lines.push(`_atom_site_fract_y`);
|
|
506
|
-
lines.push(`_atom_site_fract_z`);
|
|
507
|
-
lines.push(`_atom_site_occupancy`);
|
|
476
|
+
lines.push(`loop_`, `_atom_site_label`, `_atom_site_type_symbol`, `_atom_site_fract_x`, `_atom_site_fract_y`, `_atom_site_fract_z`, `_atom_site_occupancy`);
|
|
508
477
|
// Cache inverse transpose for Cartesian→fractional conversion (avoids recomputing per site)
|
|
509
478
|
const cart_to_frac = lattice.matrix?.length === 3 ? math.create_cart_to_frac(lattice.matrix) : null;
|
|
510
479
|
// Atom sites: one row per species entry so disordered (multi-species) sites
|
|
@@ -580,16 +549,23 @@ export function structure_to_poscar_str(structure) {
|
|
|
580
549
|
for (const group of sites_by_element.values()) {
|
|
581
550
|
for (const site of group) {
|
|
582
551
|
const frac_coords = get_frac_coords(site, cart_to_frac);
|
|
583
|
-
|
|
552
|
+
const coords_str = frac_coords
|
|
553
|
+
.slice(0, 3)
|
|
554
|
+
.map((coord) => coord.toFixed(8))
|
|
555
|
+
.join(` `);
|
|
556
|
+
let sel_dyn_str = ``;
|
|
584
557
|
if (has_selective_dynamics) {
|
|
585
558
|
const sel_dyn = (site.properties?.selective_dynamics ?? [
|
|
586
559
|
true,
|
|
587
560
|
true,
|
|
588
561
|
true,
|
|
589
562
|
]);
|
|
590
|
-
|
|
563
|
+
sel_dyn_str = ` ${sel_dyn
|
|
564
|
+
.slice(0, 3)
|
|
565
|
+
.map((flag) => (flag ? `T` : `F`))
|
|
566
|
+
.join(` `)}`;
|
|
591
567
|
}
|
|
592
|
-
lines.push(`${
|
|
568
|
+
lines.push(`${coords_str}${sel_dyn_str}`);
|
|
593
569
|
}
|
|
594
570
|
}
|
|
595
571
|
return lines.join(`\n`);
|