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
package/dist/isosurface/slice.js
CHANGED
|
@@ -2,56 +2,194 @@
|
|
|
2
2
|
// crystallographic plane defined by Miller indices, using trilinear interpolation.
|
|
3
3
|
import { reciprocal_lattice } from '../brillouin';
|
|
4
4
|
import * as math from '../math';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
5
|
+
import { create_volume_sampler, sanitize_display_range } from './sampling';
|
|
6
|
+
const CELL_EDGES = [
|
|
7
|
+
[0, 1],
|
|
8
|
+
[0, 2],
|
|
9
|
+
[0, 4],
|
|
10
|
+
[1, 3],
|
|
11
|
+
[1, 5],
|
|
12
|
+
[2, 3],
|
|
13
|
+
[2, 6],
|
|
14
|
+
[3, 7],
|
|
15
|
+
[4, 5],
|
|
16
|
+
[4, 6],
|
|
17
|
+
[5, 7],
|
|
18
|
+
[6, 7],
|
|
19
|
+
];
|
|
20
|
+
const PLANE_TOLERANCE = 1e-9;
|
|
21
|
+
const DEFAULT_MAX_PIXELS = 512 * 512;
|
|
22
|
+
const UNIT_CELL_RANGE = [
|
|
23
|
+
[0, 1],
|
|
24
|
+
[0, 1],
|
|
25
|
+
[0, 1],
|
|
26
|
+
];
|
|
27
|
+
const cell_corners = (volume, bounds) => {
|
|
28
|
+
const frac_to_cart = math.create_frac_to_cart(volume.lattice);
|
|
29
|
+
return Array.from({ length: 8 }, (_, corner_idx) => math.add(volume.origin, frac_to_cart([
|
|
30
|
+
bounds[0][corner_idx & 1 ? 1 : 0],
|
|
31
|
+
bounds[1][corner_idx & 2 ? 1 : 0],
|
|
32
|
+
bounds[2][corner_idx & 4 ? 1 : 0],
|
|
33
|
+
])));
|
|
34
|
+
};
|
|
35
|
+
const add_unique_point = (points, point) => {
|
|
36
|
+
if (points.some((existing) => (existing[0] - point[0]) ** 2 +
|
|
37
|
+
(existing[1] - point[1]) ** 2 +
|
|
38
|
+
(existing[2] - point[2]) ** 2 <
|
|
39
|
+
PLANE_TOLERANCE ** 2))
|
|
40
|
+
return;
|
|
41
|
+
points.push(point);
|
|
42
|
+
};
|
|
43
|
+
const plane_basis = (normal, up) => {
|
|
44
|
+
if (!up)
|
|
45
|
+
return math.compute_in_plane_basis(normal);
|
|
46
|
+
const normal_projection = math.dot(up, normal);
|
|
47
|
+
const projected = math.subtract(up, math.scale(normal, normal_projection));
|
|
48
|
+
if (Math.hypot(...projected) < PLANE_TOLERANCE) {
|
|
49
|
+
return math.compute_in_plane_basis(normal);
|
|
22
50
|
}
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
const u_axis = math.normalize_vec(projected);
|
|
52
|
+
return [u_axis, math.cross_3d(normal, u_axis)];
|
|
53
|
+
};
|
|
54
|
+
const intersect_plane_cell = (corners, point, normal, u_axis, v_axis) => {
|
|
55
|
+
const intersections = [];
|
|
56
|
+
for (const [start_idx, end_idx] of CELL_EDGES) {
|
|
57
|
+
const start = corners[start_idx];
|
|
58
|
+
const end = corners[end_idx];
|
|
59
|
+
const start_distance = math.dot(math.subtract(start, point), normal);
|
|
60
|
+
const end_distance = math.dot(math.subtract(end, point), normal);
|
|
61
|
+
if (Math.abs(start_distance) <= PLANE_TOLERANCE)
|
|
62
|
+
add_unique_point(intersections, start);
|
|
63
|
+
if (Math.abs(end_distance) <= PLANE_TOLERANCE)
|
|
64
|
+
add_unique_point(intersections, end);
|
|
65
|
+
if (start_distance * end_distance >= -(PLANE_TOLERANCE ** 2))
|
|
66
|
+
continue;
|
|
67
|
+
const fraction = start_distance / (start_distance - end_distance);
|
|
68
|
+
add_unique_point(intersections, [
|
|
69
|
+
start[0] + fraction * (end[0] - start[0]),
|
|
70
|
+
start[1] + fraction * (end[1] - start[1]),
|
|
71
|
+
start[2] + fraction * (end[2] - start[2]),
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
74
|
+
return math.convex_hull_2d(intersections.map((intersection) => {
|
|
75
|
+
const relative = math.subtract(intersection, point);
|
|
76
|
+
return [math.dot(relative, u_axis), math.dot(relative, v_axis)];
|
|
77
|
+
}), PLANE_TOLERANCE);
|
|
78
|
+
};
|
|
79
|
+
const point_in_convex_polygon = (point, polygon) => {
|
|
80
|
+
let orientation = 0;
|
|
81
|
+
for (let point_idx = 0; point_idx < polygon.length; point_idx++) {
|
|
82
|
+
const start = polygon[point_idx];
|
|
83
|
+
const end = polygon[(point_idx + 1) % polygon.length];
|
|
84
|
+
const cross = (end[0] - start[0]) * (point[1] - start[1]) - (end[1] - start[1]) * (point[0] - start[0]);
|
|
85
|
+
if (Math.abs(cross) <= PLANE_TOLERANCE)
|
|
86
|
+
continue;
|
|
87
|
+
const current_orientation = Math.sign(cross);
|
|
88
|
+
if (orientation && current_orientation !== orientation)
|
|
89
|
+
return false;
|
|
90
|
+
orientation = current_orientation;
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
};
|
|
94
|
+
const resolve_resolution = (resolution, u_span, v_span, max_grid_dim, max_pixels) => {
|
|
95
|
+
let counts;
|
|
96
|
+
if (Array.isArray(resolution)) {
|
|
97
|
+
counts = [Math.max(2, Math.round(resolution[0])), Math.max(2, Math.round(resolution[1]))];
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const longest_count = Math.max(2, Math.round(resolution ?? max_grid_dim));
|
|
101
|
+
const longest_span = Math.max(u_span, v_span, PLANE_TOLERANCE);
|
|
102
|
+
counts = [
|
|
103
|
+
Math.max(2, Math.round((longest_count * u_span) / longest_span)),
|
|
104
|
+
Math.max(2, Math.round((longest_count * v_span) / longest_span)),
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
const pixel_budget = Number.isFinite(max_pixels)
|
|
108
|
+
? Math.max(4, Math.floor(max_pixels))
|
|
109
|
+
: DEFAULT_MAX_PIXELS;
|
|
110
|
+
const shrink = Math.min(1, Math.sqrt(pixel_budget / (counts[0] * counts[1])));
|
|
111
|
+
if (shrink >= 1)
|
|
112
|
+
return counts;
|
|
113
|
+
counts = counts.map((count) => Math.max(2, Math.floor(count * shrink)));
|
|
114
|
+
if (counts[0] * counts[1] > pixel_budget) {
|
|
115
|
+
const axis = counts[0] >= counts[1] ? 0 : 1;
|
|
116
|
+
counts[axis] = Math.max(2, Math.floor(pixel_budget / counts[axis === 0 ? 1 : 0]));
|
|
117
|
+
}
|
|
118
|
+
return counts;
|
|
119
|
+
};
|
|
120
|
+
/** Sample a scalar volume on an arbitrary absolute Cartesian plane. */
|
|
121
|
+
export function sample_plane_slice(volume, plane, options = {}) {
|
|
122
|
+
if (!plane.point.every(Number.isFinite) || !plane.normal.every(Number.isFinite))
|
|
123
|
+
return null;
|
|
124
|
+
if (plane.up && !plane.up.every(Number.isFinite))
|
|
125
|
+
return null;
|
|
126
|
+
if (Math.hypot(...plane.normal) < PLANE_TOLERANCE)
|
|
127
|
+
return null; // degenerate normal
|
|
128
|
+
const normal = math.normalize_vec(plane.normal);
|
|
129
|
+
// In-plane basis vectors
|
|
130
|
+
const [u_axis, v_axis] = plane_basis(normal, plane.up);
|
|
131
|
+
const bounds = sanitize_display_range(options.fractional_bounds ?? UNIT_CELL_RANGE, volume.periodic);
|
|
132
|
+
const corners = cell_corners(volume, bounds);
|
|
133
|
+
const polygon = intersect_plane_cell(corners, plane.point, normal, u_axis, v_axis);
|
|
134
|
+
if (polygon.length < 3)
|
|
135
|
+
return null;
|
|
136
|
+
// Project all 8 unit cell corners onto the (u, v) plane to find sampling bounds.
|
|
137
|
+
// Corners are at fractional coords (0 or 1) for each axis.
|
|
138
|
+
const { min, max, width: u_span, height: v_span } = math.compute_bounding_box_2d(polygon);
|
|
139
|
+
const u_range = [min[0], max[0]];
|
|
140
|
+
const v_range = [min[1], max[1]];
|
|
141
|
+
if (u_span <= PLANE_TOLERANCE || v_span <= PLANE_TOLERANCE)
|
|
142
|
+
return null;
|
|
143
|
+
// Sampling resolution: caller-specified or default to max grid dimension
|
|
144
|
+
const [width, height] = resolve_resolution(options.resolution, u_span, v_span, Math.max(...volume.grid_dims), options.max_pixels ?? DEFAULT_MAX_PIXELS);
|
|
145
|
+
const data = new Float64Array(width * height);
|
|
146
|
+
data.fill(Number.NaN);
|
|
147
|
+
const mask = new Uint8Array(width * height);
|
|
148
|
+
let data_min = Infinity;
|
|
149
|
+
let data_max = -Infinity;
|
|
150
|
+
const sample = create_volume_sampler(volume, {
|
|
151
|
+
out_of_bounds: volume.periodic ? `clamp` : `fallback`,
|
|
152
|
+
});
|
|
153
|
+
const u_step = u_span / (width - 1);
|
|
154
|
+
const v_step = v_span / (height - 1);
|
|
155
|
+
const cartesian = [0, 0, 0];
|
|
156
|
+
for (let row = 0; row < height; row++) {
|
|
157
|
+
const v_value = v_range[0] + row * v_step;
|
|
158
|
+
for (let col = 0; col < width; col++) {
|
|
159
|
+
const u_value = u_range[0] + col * u_step;
|
|
160
|
+
if (!point_in_convex_polygon([u_value, v_value], polygon))
|
|
161
|
+
continue;
|
|
162
|
+
const data_idx = row * width + col;
|
|
163
|
+
mask[data_idx] = 1;
|
|
164
|
+
// Cartesian position on the plane
|
|
165
|
+
cartesian[0] = plane.point[0] + u_value * u_axis[0] + v_value * v_axis[0];
|
|
166
|
+
cartesian[1] = plane.point[1] + u_value * u_axis[1] + v_value * v_axis[1];
|
|
167
|
+
cartesian[2] = plane.point[2] + u_value * u_axis[2] + v_value * v_axis[2];
|
|
168
|
+
const value = sample(cartesian);
|
|
169
|
+
if (!Number.isFinite(value))
|
|
170
|
+
continue;
|
|
171
|
+
data[data_idx] = value;
|
|
172
|
+
if (value < data_min)
|
|
173
|
+
data_min = value;
|
|
174
|
+
if (value > data_max)
|
|
175
|
+
data_max = value;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
data,
|
|
180
|
+
mask,
|
|
181
|
+
width,
|
|
182
|
+
height,
|
|
183
|
+
min: data_min === Infinity ? 0 : data_min,
|
|
184
|
+
max: data_max === -Infinity ? 0 : data_max,
|
|
185
|
+
point: [...plane.point],
|
|
186
|
+
normal,
|
|
187
|
+
u_axis,
|
|
188
|
+
v_axis,
|
|
189
|
+
u_range,
|
|
190
|
+
v_range,
|
|
191
|
+
polygon,
|
|
192
|
+
};
|
|
55
193
|
}
|
|
56
194
|
// Sample a 2D slice through volumetric data along a Miller-index plane.
|
|
57
195
|
// `miller_indices` [h,k,l] defines the plane normal in reciprocal space.
|
|
@@ -61,81 +199,27 @@ export function sample_hkl_slice(volume, miller_indices, distance, n_points) {
|
|
|
61
199
|
const [h_idx, k_idx, l_idx] = miller_indices;
|
|
62
200
|
if (h_idx === 0 && k_idx === 0 && l_idx === 0)
|
|
63
201
|
return null;
|
|
64
|
-
const { grid, grid_dims, lattice, periodic } = volume;
|
|
65
|
-
const [nx, ny, nz] = grid_dims;
|
|
66
202
|
// Plane normal G = h*b1 + k*b2 + l*b3 where b_i are reciprocal lattice rows
|
|
67
|
-
const recip = reciprocal_lattice(lattice);
|
|
203
|
+
const recip = reciprocal_lattice(volume.lattice);
|
|
68
204
|
const plane_normal = [
|
|
69
205
|
h_idx * recip[0][0] + k_idx * recip[1][0] + l_idx * recip[2][0],
|
|
70
206
|
h_idx * recip[0][1] + k_idx * recip[1][1] + l_idx * recip[2][1],
|
|
71
207
|
h_idx * recip[0][2] + k_idx * recip[1][2] + l_idx * recip[2][2],
|
|
72
208
|
];
|
|
73
|
-
if (Math.hypot(...plane_normal) <
|
|
209
|
+
if (Math.hypot(...plane_normal) < PLANE_TOLERANCE)
|
|
74
210
|
return null; // degenerate normal
|
|
75
211
|
const unit_normal = math.normalize_vec(plane_normal);
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
// Corners are at fractional coords (0 or 1) for each axis.
|
|
81
|
-
let u_min = Infinity;
|
|
82
|
-
let u_max = -Infinity;
|
|
83
|
-
let v_min = Infinity;
|
|
84
|
-
let v_max = -Infinity;
|
|
85
|
-
let normal_min = Infinity;
|
|
86
|
-
let normal_max = -Infinity;
|
|
87
|
-
for (let ci = 0; ci < 8; ci++) {
|
|
88
|
-
const fi = ci & 1 ? 1 : 0;
|
|
89
|
-
const fj = ci & 2 ? 1 : 0;
|
|
90
|
-
const fk = ci & 4 ? 1 : 0;
|
|
91
|
-
// Corner in Cartesian: frac * lattice
|
|
92
|
-
const corner = [
|
|
93
|
-
fi * lattice[0][0] + fj * lattice[1][0] + fk * lattice[2][0],
|
|
94
|
-
fi * lattice[0][1] + fj * lattice[1][1] + fk * lattice[2][1],
|
|
95
|
-
fi * lattice[0][2] + fj * lattice[1][2] + fk * lattice[2][2],
|
|
96
|
-
];
|
|
97
|
-
const u_proj = math.dot(corner, u_vec);
|
|
98
|
-
const v_proj = math.dot(corner, v_vec);
|
|
99
|
-
const n_proj = math.dot(corner, unit_normal);
|
|
100
|
-
if (u_proj < u_min)
|
|
101
|
-
u_min = u_proj;
|
|
102
|
-
if (u_proj > u_max)
|
|
103
|
-
u_max = u_proj;
|
|
104
|
-
if (v_proj < v_min)
|
|
105
|
-
v_min = v_proj;
|
|
106
|
-
if (v_proj > v_max)
|
|
107
|
-
v_max = v_proj;
|
|
108
|
-
if (n_proj < normal_min)
|
|
109
|
-
normal_min = n_proj;
|
|
110
|
-
if (n_proj > normal_max)
|
|
111
|
-
normal_max = n_proj;
|
|
112
|
-
}
|
|
212
|
+
const corners = cell_corners(volume, UNIT_CELL_RANGE);
|
|
213
|
+
const projections = corners.map((corner) => math.dot(corner, unit_normal));
|
|
214
|
+
const normal_min = Math.min(...projections);
|
|
215
|
+
const normal_max = Math.max(...projections);
|
|
113
216
|
// Plane position: fractional distance [0,1] along the normal extent
|
|
114
217
|
const d_cartesian = normal_min + distance * (normal_max - normal_min);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const v_step = (v_max - v_min) / (height - 1 || 1);
|
|
123
|
-
for (let row = 0; row < height; row++) {
|
|
124
|
-
const v_val = v_min + row * v_step;
|
|
125
|
-
for (let col = 0; col < width; col++) {
|
|
126
|
-
const u_val = u_min + col * u_step;
|
|
127
|
-
// Cartesian position on the plane
|
|
128
|
-
const px = d_cartesian * unit_normal[0] + u_val * u_vec[0] + v_val * v_vec[0];
|
|
129
|
-
const py = d_cartesian * unit_normal[1] + u_val * u_vec[1] + v_val * v_vec[1];
|
|
130
|
-
const pz = d_cartesian * unit_normal[2] + u_val * u_vec[2] + v_val * v_vec[2];
|
|
131
|
-
const [fx, fy, fz] = cart_to_frac([px, py, pz]);
|
|
132
|
-
const val = trilinear_interpolate(grid, fx, fy, fz, periodic);
|
|
133
|
-
data[row * width + col] = val;
|
|
134
|
-
if (val < data_min)
|
|
135
|
-
data_min = val;
|
|
136
|
-
if (val > data_max)
|
|
137
|
-
data_max = val;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return { data, width, height, min: data_min, max: data_max };
|
|
218
|
+
const point = [
|
|
219
|
+
d_cartesian * unit_normal[0],
|
|
220
|
+
d_cartesian * unit_normal[1],
|
|
221
|
+
d_cartesian * unit_normal[2],
|
|
222
|
+
];
|
|
223
|
+
const resolution = n_points ?? Math.max(...volume.grid_dims);
|
|
224
|
+
return sample_plane_slice(volume, { point, normal: unit_normal }, { resolution: [resolution, resolution] });
|
|
141
225
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { D3InterpolateName } from '../colors';
|
|
2
|
+
import type { Matrix3x3, Vec2, Vec3 } from '../math';
|
|
2
3
|
import type { ParsedStructure } from '../structure/parse';
|
|
3
4
|
export interface DataRange {
|
|
4
5
|
min: number;
|
|
@@ -15,6 +16,7 @@ export interface VolumetricData {
|
|
|
15
16
|
data_order?: `x_fastest` | `z_fastest`;
|
|
16
17
|
periodic: boolean;
|
|
17
18
|
label?: string;
|
|
19
|
+
source?: string;
|
|
18
20
|
}
|
|
19
21
|
export interface VolumetricFileData {
|
|
20
22
|
structure: ParsedStructure;
|
|
@@ -27,6 +29,10 @@ export interface IsosurfaceLayer {
|
|
|
27
29
|
visible: boolean;
|
|
28
30
|
show_negative: boolean;
|
|
29
31
|
negative_color: string;
|
|
32
|
+
volume_idx?: number;
|
|
33
|
+
color_volume_idx?: number;
|
|
34
|
+
colormap?: D3InterpolateName;
|
|
35
|
+
color_range?: Vec2;
|
|
30
36
|
}
|
|
31
37
|
export interface IsosurfaceSettings {
|
|
32
38
|
isovalue: number;
|
|
@@ -37,6 +43,7 @@ export interface IsosurfaceSettings {
|
|
|
37
43
|
wireframe: boolean;
|
|
38
44
|
halo: number;
|
|
39
45
|
layers?: IsosurfaceLayer[];
|
|
46
|
+
display_range?: [Vec2, Vec2, Vec2];
|
|
40
47
|
}
|
|
41
48
|
export declare const LAYER_COLORS: readonly ["#3b82f6", "#ef4444", "#22c55e", "#a855f7", "#f97316", "#06b6d4", "#eab308", "#ec4899"];
|
|
42
49
|
export declare function grid_data_range(grid: number[][][]): DataRange;
|
|
@@ -45,6 +52,7 @@ export declare function pad_periodic_grid(grid: number[][][], dims: Vec3, pad_fr
|
|
|
45
52
|
dims: Vec3;
|
|
46
53
|
offset: Vec3;
|
|
47
54
|
};
|
|
55
|
+
export declare const MAX_GRID_POINTS = 500000;
|
|
48
56
|
export declare function downsample_grid(grid: number[][][], dims: Vec3, max_points?: number): {
|
|
49
57
|
grid: number[][][];
|
|
50
58
|
dims: Vec3;
|
|
@@ -53,4 +61,19 @@ export declare function downsample_grid(grid: number[][][], dims: Vec3, max_poin
|
|
|
53
61
|
export declare const DEFAULT_ISOSURFACE_SETTINGS: IsosurfaceSettings;
|
|
54
62
|
export declare function auto_isosurface_settings(data_range: DataRange): IsosurfaceSettings;
|
|
55
63
|
export declare function generate_layers(data_range: DataRange, n_layers: number): IsosurfaceLayer[];
|
|
64
|
+
export declare function auto_volume_layer(volume: VolumetricData, volume_idx: number, color_offset?: number): IsosurfaceLayer;
|
|
65
|
+
export declare function materialize_layers(settings: IsosurfaceSettings, active_volume_idx: number): IsosurfaceLayer[];
|
|
66
|
+
export declare function remove_volume(volumes: VolumetricData[], layers: IsosurfaceLayer[], removed_idx: number, active_volume_idx?: number): {
|
|
67
|
+
volumes: VolumetricData[];
|
|
68
|
+
layers: IsosurfaceLayer[];
|
|
69
|
+
};
|
|
70
|
+
export declare function label_file_volumes(volumes: VolumetricData[], filename: string): VolumetricData[];
|
|
71
|
+
export declare const lattices_match: (lattice_a: readonly (readonly number[])[] | undefined, lattice_b: readonly (readonly number[])[] | undefined, tolerance?: number) => boolean;
|
|
72
|
+
export interface VolumeMergeResult {
|
|
73
|
+
volumes: VolumetricData[];
|
|
74
|
+
layers: IsosurfaceLayer[];
|
|
75
|
+
first_touched_idx: number;
|
|
76
|
+
n_added: number;
|
|
77
|
+
}
|
|
78
|
+
export declare function merge_imported_volumes(existing: VolumetricData[], existing_layers: IsosurfaceLayer[], incoming: VolumetricData[], active_volume_idx?: number): VolumeMergeResult;
|
|
56
79
|
export declare function tile_volumetric_data(volume: VolumetricData, scaling: Vec3): VolumetricData;
|
package/dist/isosurface/types.js
CHANGED
|
@@ -75,7 +75,7 @@ export function pad_periodic_grid(grid, dims, pad_fraction) {
|
|
|
75
75
|
}
|
|
76
76
|
// Max total grid points before downsampling is applied for isosurface extraction.
|
|
77
77
|
// 500K balances visual quality with interactive performance (<200ms marching cubes).
|
|
78
|
-
const MAX_GRID_POINTS = 500_000;
|
|
78
|
+
export const MAX_GRID_POINTS = 500_000;
|
|
79
79
|
// Downsample a 3D volumetric grid to keep total point count under a budget.
|
|
80
80
|
// Uses block averaging to preserve data fidelity while reducing grid dimensions.
|
|
81
81
|
// Returns original grid/dims if already within budget.
|
|
@@ -147,16 +147,17 @@ export const DEFAULT_ISOSURFACE_SETTINGS = {
|
|
|
147
147
|
wireframe: false,
|
|
148
148
|
halo: 0,
|
|
149
149
|
};
|
|
150
|
+
const field_has_significant_negatives = ({ min, abs_max }) => min < -abs_max * 0.01;
|
|
151
|
+
const default_isovalue = ({ abs_max }) => abs_max > 0 ? abs_max * 0.2 : DEFAULT_ISOSURFACE_SETTINGS.isovalue;
|
|
150
152
|
// Compute reasonable isosurface settings from a volume's data range.
|
|
151
153
|
// Sets isovalue to 20% of abs_max and enables negative lobe when data has
|
|
152
154
|
// significant negative values (>1% of max).
|
|
153
155
|
export function auto_isosurface_settings(data_range) {
|
|
154
|
-
const has_negatives = data_range.min < -data_range.abs_max * 0.01;
|
|
155
156
|
return {
|
|
156
157
|
...DEFAULT_ISOSURFACE_SETTINGS,
|
|
157
158
|
// Fall back to default isovalue for all-zero grids to keep controls usable
|
|
158
|
-
isovalue: data_range
|
|
159
|
-
show_negative:
|
|
159
|
+
isovalue: default_isovalue(data_range),
|
|
160
|
+
show_negative: field_has_significant_negatives(data_range),
|
|
160
161
|
};
|
|
161
162
|
}
|
|
162
163
|
// Generate N evenly-spaced isosurface layers across a data range.
|
|
@@ -165,7 +166,7 @@ export function auto_isosurface_settings(data_range) {
|
|
|
165
166
|
export function generate_layers(data_range, n_layers) {
|
|
166
167
|
if (n_layers <= 0 || data_range.abs_max <= 0)
|
|
167
168
|
return [];
|
|
168
|
-
const
|
|
169
|
+
const show_negative = field_has_significant_negatives(data_range);
|
|
169
170
|
// Space isovalues from high (inner) to low (outer)
|
|
170
171
|
return Array.from({ length: n_layers }, (_, idx) => {
|
|
171
172
|
// Fraction from 0.8 (inner) to 0.1 (outer)
|
|
@@ -175,18 +176,129 @@ export function generate_layers(data_range, n_layers) {
|
|
|
175
176
|
color: LAYER_COLORS[idx % LAYER_COLORS.length],
|
|
176
177
|
opacity: n_layers === 1 ? 0.6 : 0.8 - idx * (0.5 / Math.max(n_layers - 1, 1)),
|
|
177
178
|
visible: true,
|
|
178
|
-
show_negative
|
|
179
|
+
show_negative,
|
|
179
180
|
negative_color: LAYER_COLORS[(idx + 1) % LAYER_COLORS.length],
|
|
180
181
|
};
|
|
181
182
|
});
|
|
182
183
|
}
|
|
183
|
-
//
|
|
184
|
+
// Build a default isosurface layer for a newly added volume: isovalue at 20% of
|
|
185
|
+
// abs_max, next unused palette color, and negative lobe when the data is signed.
|
|
186
|
+
export function auto_volume_layer(volume, volume_idx, color_offset = 0) {
|
|
187
|
+
return {
|
|
188
|
+
isovalue: default_isovalue(volume.data_range),
|
|
189
|
+
color: LAYER_COLORS[color_offset % LAYER_COLORS.length],
|
|
190
|
+
opacity: 0.6,
|
|
191
|
+
visible: true,
|
|
192
|
+
show_negative: field_has_significant_negatives(volume.data_range),
|
|
193
|
+
negative_color: LAYER_COLORS[(color_offset + 1) % LAYER_COLORS.length],
|
|
194
|
+
volume_idx,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
// Convert single-isovalue settings into an explicit layers array (no-op when
|
|
198
|
+
// layers already exist — an explicit empty array means zero surfaces and is
|
|
199
|
+
// preserved). Used when entering multi-volume mode so the implicit
|
|
200
|
+
// active-volume surface survives as an editable layer.
|
|
201
|
+
export function materialize_layers(settings, active_volume_idx) {
|
|
202
|
+
if (settings.layers) {
|
|
203
|
+
// Pin any layers still relying on the implicit active volume
|
|
204
|
+
return settings.layers.map((layer) => ({
|
|
205
|
+
...layer,
|
|
206
|
+
volume_idx: layer.volume_idx ?? active_volume_idx,
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
return [
|
|
210
|
+
{
|
|
211
|
+
isovalue: settings.isovalue,
|
|
212
|
+
color: settings.positive_color,
|
|
213
|
+
opacity: settings.opacity,
|
|
214
|
+
visible: true,
|
|
215
|
+
show_negative: settings.show_negative,
|
|
216
|
+
negative_color: settings.negative_color,
|
|
217
|
+
volume_idx: active_volume_idx,
|
|
218
|
+
},
|
|
219
|
+
];
|
|
220
|
+
}
|
|
221
|
+
// Remove a volume from the registry: drops layers whose geometry references it,
|
|
222
|
+
// unsets color sources pointing at it, and shifts higher indices down by one.
|
|
223
|
+
// Layers without an explicit volume_idx implicitly reference `active_volume_idx`.
|
|
224
|
+
export function remove_volume(volumes, layers, removed_idx, active_volume_idx = 0) {
|
|
225
|
+
const remap = (idx) => {
|
|
226
|
+
if (idx === undefined || idx === removed_idx)
|
|
227
|
+
return undefined;
|
|
228
|
+
return idx > removed_idx ? idx - 1 : idx;
|
|
229
|
+
};
|
|
230
|
+
return {
|
|
231
|
+
volumes: volumes.filter((_vol, idx) => idx !== removed_idx),
|
|
232
|
+
layers: layers
|
|
233
|
+
.filter((layer) => (layer.volume_idx ?? active_volume_idx) !== removed_idx)
|
|
234
|
+
.map((layer) => ({
|
|
235
|
+
...layer,
|
|
236
|
+
volume_idx: remap(layer.volume_idx ?? active_volume_idx) ?? 0,
|
|
237
|
+
color_volume_idx: remap(layer.color_volume_idx),
|
|
238
|
+
})),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
// Label volumes parsed from a file with a stable `source` id (compression-
|
|
242
|
+
// stripped filename) and a display label (multi-block files like spin-polarized
|
|
243
|
+
// CHGCAR get "<file>: <block label>" labels).
|
|
244
|
+
export function label_file_volumes(volumes, filename) {
|
|
245
|
+
const source = filename.replace(/\.(?:gz|gzip|bz2|xz|zst)$/i, ``);
|
|
246
|
+
return volumes.map((vol, idx) => ({
|
|
247
|
+
...vol,
|
|
248
|
+
source,
|
|
249
|
+
label: volumes.length > 1 ? `${source}: ${vol.label ?? idx + 1}` : source,
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
252
|
+
// Two lattices describe the same cell when all matrix entries agree within
|
|
253
|
+
// tolerance — the signal that an imported file is another scalar field of the
|
|
254
|
+
// already-loaded system and should be appended rather than replace the scene.
|
|
255
|
+
export const lattices_match = (lattice_a, lattice_b, tolerance = 0.05) => Boolean(lattice_a && lattice_b) &&
|
|
256
|
+
lattice_a.every((row, row_idx) => row.every((val, col_idx) => Math.abs(val - lattice_b[row_idx][col_idx]) < tolerance));
|
|
257
|
+
// Merge volumes from a newly imported file (pre-labeled via label_file_volumes)
|
|
258
|
+
// into an existing registry. Reimporting a source with the same block count
|
|
259
|
+
// replaces its volumes in place, preserving user-tuned layers; a changed block
|
|
260
|
+
// count drops the stale group (remapping layer indices) before appending fresh
|
|
261
|
+
// volumes. New volumes each get an auto-generated layer.
|
|
262
|
+
// Layers without an explicit volume_idx implicitly reference `active_volume_idx`
|
|
263
|
+
// and are pinned to it up front so index remapping treats them correctly.
|
|
264
|
+
export function merge_imported_volumes(existing, existing_layers, incoming, active_volume_idx = 0) {
|
|
265
|
+
const source = incoming[0]?.source;
|
|
266
|
+
let volumes = [...existing];
|
|
267
|
+
let layers = existing_layers.map((layer) => ({
|
|
268
|
+
...layer,
|
|
269
|
+
volume_idx: layer.volume_idx ?? active_volume_idx,
|
|
270
|
+
}));
|
|
271
|
+
const group_indices = volumes
|
|
272
|
+
.map((vol, idx) => (source !== undefined && vol.source === source ? idx : -1))
|
|
273
|
+
.filter((idx) => idx >= 0);
|
|
274
|
+
if (group_indices.length === incoming.length && incoming.length > 0) {
|
|
275
|
+
// Same source, same block count: replace in place, keep layer settings
|
|
276
|
+
for (const [incoming_idx, vol_idx] of group_indices.entries()) {
|
|
277
|
+
volumes[vol_idx] = incoming[incoming_idx];
|
|
278
|
+
}
|
|
279
|
+
return { volumes, layers, first_touched_idx: group_indices[0], n_added: 0 };
|
|
280
|
+
}
|
|
281
|
+
// Drop any stale volumes from the same source (block count changed)
|
|
282
|
+
for (let removed = group_indices.length - 1; removed >= 0; removed--) {
|
|
283
|
+
;
|
|
284
|
+
({ volumes, layers } = remove_volume(volumes, layers, group_indices[removed]));
|
|
285
|
+
}
|
|
286
|
+
const first_touched_idx = volumes.length;
|
|
287
|
+
for (const vol of incoming) {
|
|
288
|
+
layers.push(auto_volume_layer(vol, volumes.length, layers.length));
|
|
289
|
+
volumes.push(vol);
|
|
290
|
+
}
|
|
291
|
+
return { volumes, layers, first_touched_idx, n_added: incoming.length };
|
|
292
|
+
}
|
|
293
|
+
// Tile (repeat) periodic volumetric data to fill a supercell. Finite volumes
|
|
294
|
+
// remain unchanged: structure supercells must not implicitly repeat molecular
|
|
295
|
+
// or other bounded scalar fields.
|
|
184
296
|
// Pre-downsamples the source grid when the tiled result would exceed MAX_GRID_POINTS
|
|
185
297
|
// to avoid large temporary allocations. Returns the original volume unchanged for
|
|
186
298
|
// [1,1,1] scaling.
|
|
187
299
|
export function tile_volumetric_data(volume, scaling) {
|
|
188
300
|
const [sx, sy, sz] = scaling;
|
|
189
|
-
if (sx === 1 && sy === 1 && sz === 1)
|
|
301
|
+
if (!volume.periodic || (sx === 1 && sy === 1 && sz === 1))
|
|
190
302
|
return volume;
|
|
191
303
|
const total_cells = sx * sy * sz;
|
|
192
304
|
let src_grid = volume.grid;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const format_path: (segments: (string | number)[]) => string;
|
|
2
|
+
export declare const build_path: (parent_path: string, key: string | number) => string;
|
|
3
|
+
export declare function parse_path(path: string): (string | number)[];
|
|
4
|
+
export declare function resolve_path(root: unknown, path: string): unknown;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Dot/bracket path codec for arbitrary JavaScript object keys.
|
|
2
|
+
// Special keys use JSON.stringify so quotes/backslashes/brackets round-trip.
|
|
3
|
+
const PATH_IDENTIFIER_RE = /^[A-Za-z_$][\w$]*$/u;
|
|
4
|
+
function format_path_segment(segment, is_first = false) {
|
|
5
|
+
if (typeof segment === `number`)
|
|
6
|
+
return `[${segment}]`;
|
|
7
|
+
if (PATH_IDENTIFIER_RE.test(segment))
|
|
8
|
+
return is_first ? segment : `.${segment}`;
|
|
9
|
+
return `[${JSON.stringify(segment)}]`;
|
|
10
|
+
}
|
|
11
|
+
export const format_path = (segments) => segments.map((segment, idx) => format_path_segment(segment, idx === 0)).join(``);
|
|
12
|
+
export const build_path = (parent_path, key) => parent_path ? parent_path + format_path_segment(key) : format_path_segment(key, true);
|
|
13
|
+
export function parse_path(path) {
|
|
14
|
+
if (!path)
|
|
15
|
+
return [];
|
|
16
|
+
const segments = [];
|
|
17
|
+
let pos = 0;
|
|
18
|
+
while (pos < path.length) {
|
|
19
|
+
const char = path[pos];
|
|
20
|
+
if (char === `.`) {
|
|
21
|
+
pos++;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (char !== `[`) {
|
|
25
|
+
const start = pos;
|
|
26
|
+
while (pos < path.length && path[pos] !== `.` && path[pos] !== `[`)
|
|
27
|
+
pos++;
|
|
28
|
+
if (pos > start)
|
|
29
|
+
segments.push(path.slice(start, pos));
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
pos++; // skip `[`
|
|
33
|
+
if (path[pos] === `]`) {
|
|
34
|
+
pos++;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (path[pos] === `"`) {
|
|
38
|
+
const json_start = pos;
|
|
39
|
+
let escaped = false;
|
|
40
|
+
pos++;
|
|
41
|
+
while (pos < path.length) {
|
|
42
|
+
const quote_char = path[pos];
|
|
43
|
+
if (escaped)
|
|
44
|
+
escaped = false;
|
|
45
|
+
else if (quote_char === `\\`)
|
|
46
|
+
escaped = true;
|
|
47
|
+
else if (quote_char === `"`)
|
|
48
|
+
break;
|
|
49
|
+
pos++;
|
|
50
|
+
}
|
|
51
|
+
if (pos >= path.length) {
|
|
52
|
+
segments.push(path.slice(json_start + 1));
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
segments.push(JSON.parse(path.slice(json_start, pos + 1)));
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
segments.push(path.slice(json_start + 1, pos));
|
|
60
|
+
}
|
|
61
|
+
pos++;
|
|
62
|
+
if (path[pos] === `]`)
|
|
63
|
+
pos++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const start = pos;
|
|
67
|
+
while (pos < path.length && path[pos] !== `]`)
|
|
68
|
+
pos++;
|
|
69
|
+
const token = path.slice(start, pos);
|
|
70
|
+
if (token) {
|
|
71
|
+
const num = Number(token);
|
|
72
|
+
segments.push(Number.isNaN(num) ? token : num);
|
|
73
|
+
}
|
|
74
|
+
if (path[pos] === `]`)
|
|
75
|
+
pos++;
|
|
76
|
+
}
|
|
77
|
+
return segments;
|
|
78
|
+
}
|
|
79
|
+
export function resolve_path(root, path) {
|
|
80
|
+
if (!path)
|
|
81
|
+
return root;
|
|
82
|
+
let current = root;
|
|
83
|
+
for (const key of parse_path(path)) {
|
|
84
|
+
if (current == null || typeof current !== `object`)
|
|
85
|
+
return undefined;
|
|
86
|
+
current = current[key];
|
|
87
|
+
}
|
|
88
|
+
return current;
|
|
89
|
+
}
|