matterviz 0.1.12 → 0.1.13
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/ContextMenu.svelte +1 -1
- package/dist/ContextMenu.svelte.d.ts +3 -3
- package/dist/DraggablePane.svelte +10 -5
- package/dist/DraggablePane.svelte.d.ts +3 -3
- package/dist/FilePicker.svelte.d.ts +3 -3
- package/dist/Icon.svelte.d.ts +3 -3
- package/dist/Nav.svelte +76 -31
- package/dist/SettingsSection.svelte.d.ts +3 -3
- package/dist/Spinner.svelte +18 -29
- package/dist/Spinner.svelte.d.ts +3 -3
- package/dist/app.css +4 -0
- package/dist/bands/Bands.svelte +216 -0
- package/dist/bands/Bands.svelte.d.ts +16 -0
- package/dist/bands/BandsAndDos.svelte +26 -0
- package/dist/bands/BandsAndDos.svelte.d.ts +16 -0
- package/dist/bands/Dos.svelte +124 -0
- package/dist/bands/Dos.svelte.d.ts +18 -0
- package/dist/bands/helpers.d.ts +11 -0
- package/dist/bands/helpers.js +195 -0
- package/dist/bands/index.d.ts +5 -0
- package/dist/bands/index.js +6 -0
- package/dist/bands/types.d.ts +54 -0
- package/dist/bands/types.js +2 -0
- package/dist/brillouin/BrillouinZone.svelte +373 -0
- package/dist/brillouin/BrillouinZone.svelte.d.ts +57 -0
- package/dist/brillouin/BrillouinZoneControls.svelte +88 -0
- package/dist/brillouin/BrillouinZoneControls.svelte.d.ts +14 -0
- package/dist/brillouin/BrillouinZoneExportPane.svelte +129 -0
- package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +15 -0
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +115 -0
- package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +13 -0
- package/dist/brillouin/BrillouinZoneScene.svelte +180 -0
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +34 -0
- package/dist/brillouin/compute.d.ts +7 -0
- package/dist/brillouin/compute.js +215 -0
- package/dist/brillouin/index.d.ts +7 -0
- package/dist/brillouin/index.js +7 -0
- package/dist/brillouin/types.d.ts +23 -0
- package/dist/brillouin/types.js +1 -0
- package/dist/composition/BarChart.svelte.d.ts +3 -3
- package/dist/composition/BubbleChart.svelte +7 -6
- package/dist/composition/BubbleChart.svelte.d.ts +3 -3
- package/dist/composition/Composition.svelte.d.ts +3 -3
- package/dist/composition/PieChart.svelte +1 -1
- package/dist/composition/PieChart.svelte.d.ts +3 -3
- package/dist/composition/parse.d.ts +3 -3
- package/dist/coordination/CoordinationBarPlot.svelte +241 -0
- package/dist/coordination/CoordinationBarPlot.svelte.d.ts +29 -0
- package/dist/coordination/calc-coordination.d.ts +15 -0
- package/dist/coordination/calc-coordination.js +66 -0
- package/dist/coordination/index.d.ts +8 -0
- package/dist/coordination/index.js +7 -0
- package/dist/element/BohrAtom.svelte.d.ts +3 -3
- package/dist/element/ElementHeading.svelte.d.ts +3 -3
- package/dist/element/ElementPhoto.svelte.d.ts +3 -3
- package/dist/element/ElementStats.svelte.d.ts +3 -3
- package/dist/element/ElementTile.svelte +1 -1
- package/dist/element/ElementTile.svelte.d.ts +4 -4
- package/dist/element/Nucleus.svelte.d.ts +3 -3
- package/dist/icons.d.ts +4 -0
- package/dist/icons.js +4 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/io/export.d.ts +1 -6
- package/dist/io/export.js +12 -12
- package/dist/labels.d.ts +19 -0
- package/dist/labels.js +59 -0
- package/dist/math.d.ts +7 -2
- package/dist/math.js +24 -12
- package/dist/periodic-table/PeriodicTable.svelte.d.ts +3 -3
- package/dist/periodic-table/PeriodicTableControls.svelte.d.ts +3 -3
- package/dist/periodic-table/PropertySelect.svelte +1 -1
- package/dist/periodic-table/PropertySelect.svelte.d.ts +5 -4
- package/dist/periodic-table/TableInset.svelte.d.ts +3 -3
- package/dist/phase-diagram/PhaseDiagram2D.svelte +44 -107
- package/dist/phase-diagram/PhaseDiagram2D.svelte.d.ts +7 -27
- package/dist/phase-diagram/PhaseDiagram3D.svelte +67 -170
- package/dist/phase-diagram/PhaseDiagram3D.svelte.d.ts +7 -31
- package/dist/phase-diagram/PhaseDiagram4D.svelte +87 -145
- package/dist/phase-diagram/PhaseDiagram4D.svelte.d.ts +5 -28
- package/dist/phase-diagram/PhaseDiagramControls.svelte +9 -9
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +6 -6
- package/dist/phase-diagram/PhaseDiagramInfoPane.svelte +4 -4
- package/dist/phase-diagram/PhaseDiagramInfoPane.svelte.d.ts +5 -5
- package/dist/phase-diagram/PhaseDiagramStats.svelte +9 -16
- package/dist/phase-diagram/PhaseDiagramStats.svelte.d.ts +3 -3
- package/dist/phase-diagram/StructurePopup.svelte.d.ts +3 -3
- package/dist/phase-diagram/barycentric-coords.js +13 -24
- package/dist/phase-diagram/helpers.d.ts +12 -14
- package/dist/phase-diagram/helpers.js +70 -23
- package/dist/phase-diagram/index.d.ts +58 -0
- package/dist/phase-diagram/index.js +44 -0
- package/dist/phase-diagram/thermodynamics.js +2 -3
- package/dist/phase-diagram/types.d.ts +3 -8
- package/dist/plot/BarPlot.svelte +474 -240
- package/dist/plot/BarPlot.svelte.d.ts +16 -37
- package/dist/plot/BarPlotControls.svelte +33 -201
- package/dist/plot/BarPlotControls.svelte.d.ts +11 -22
- package/dist/plot/ColorBar.svelte.d.ts +3 -3
- package/dist/plot/ColorScaleSelect.svelte +1 -1
- package/dist/plot/ColorScaleSelect.svelte.d.ts +3 -3
- package/dist/plot/ElementScatter.svelte +5 -5
- package/dist/plot/ElementScatter.svelte.d.ts +8 -8
- package/dist/plot/Histogram.svelte +318 -170
- package/dist/plot/Histogram.svelte.d.ts +21 -41
- package/dist/plot/HistogramControls.svelte +115 -347
- package/dist/plot/HistogramControls.svelte.d.ts +13 -28
- package/dist/plot/Line.svelte +1 -1
- package/dist/plot/Line.svelte.d.ts +4 -4
- package/dist/plot/PlotControls.svelte +280 -0
- package/dist/plot/PlotControls.svelte.d.ts +4 -0
- package/dist/plot/PlotLegend.svelte +9 -24
- package/dist/plot/PlotLegend.svelte.d.ts +3 -3
- package/dist/plot/ScatterPlot.svelte +536 -571
- package/dist/plot/ScatterPlot.svelte.d.ts +18 -90
- package/dist/plot/ScatterPlotControls.svelte +108 -316
- package/dist/plot/ScatterPlotControls.svelte.d.ts +11 -38
- package/dist/plot/ScatterPoint.svelte +1 -1
- package/dist/plot/ScatterPoint.svelte.d.ts +5 -5
- package/dist/plot/SpacegroupBarPlot.svelte +235 -0
- package/dist/plot/SpacegroupBarPlot.svelte.d.ts +9 -0
- package/dist/plot/data-transform.d.ts +2 -2
- package/dist/plot/data-transform.js +1 -5
- package/dist/plot/index.d.ts +12 -200
- package/dist/plot/index.js +14 -6
- package/dist/plot/layout.d.ts +10 -16
- package/dist/plot/layout.js +40 -25
- package/dist/plot/scales.d.ts +7 -8
- package/dist/plot/scales.js +22 -7
- package/dist/plot/types.d.ts +303 -0
- package/dist/plot/types.js +1 -0
- package/dist/rdf/RdfPlot.svelte +156 -0
- package/dist/rdf/RdfPlot.svelte.d.ts +20 -0
- package/dist/rdf/calc-rdf.d.ts +4 -0
- package/dist/rdf/calc-rdf.js +107 -0
- package/dist/rdf/index.d.ts +22 -0
- package/dist/rdf/index.js +2 -0
- package/dist/settings.d.ts +86 -75
- package/dist/settings.js +257 -155
- package/dist/structure/Bond.svelte +126 -110
- package/dist/structure/Bond.svelte.d.ts +7 -20
- package/dist/structure/CanvasTooltip.svelte.d.ts +4 -4
- package/dist/structure/Cylinder.svelte +29 -0
- package/dist/structure/Cylinder.svelte.d.ts +10 -0
- package/dist/structure/Lattice.svelte +1 -2
- package/dist/structure/Lattice.svelte.d.ts +3 -3
- package/dist/structure/Structure.svelte +23 -8
- package/dist/structure/Structure.svelte.d.ts +10 -7
- package/dist/structure/StructureControls.svelte +25 -134
- package/dist/structure/StructureControls.svelte.d.ts +4 -9
- package/dist/structure/StructureExportPane.svelte +188 -0
- package/dist/structure/StructureExportPane.svelte.d.ts +17 -0
- package/dist/structure/StructureInfoPane.svelte.d.ts +3 -3
- package/dist/structure/StructureLegend.svelte +91 -27
- package/dist/structure/StructureLegend.svelte.d.ts +5 -4
- package/dist/structure/StructureScene.svelte +93 -100
- package/dist/structure/StructureScene.svelte.d.ts +15 -12
- package/dist/structure/Vector.svelte +19 -25
- package/dist/structure/Vector.svelte.d.ts +3 -3
- package/dist/structure/bonding.d.ts +16 -14
- package/dist/structure/bonding.js +207 -216
- package/dist/structure/export.d.ts +3 -0
- package/dist/structure/export.js +120 -0
- package/dist/structure/index.d.ts +15 -1
- package/dist/structure/index.js +2 -0
- package/dist/symmetry/WyckoffTable.svelte.d.ts +3 -3
- package/dist/symmetry/index.d.ts +1 -0
- package/dist/symmetry/index.js +1 -0
- package/dist/symmetry/spacegroups.d.ts +9 -0
- package/dist/symmetry/spacegroups.js +397 -0
- package/dist/theme/ThemeControl.svelte.d.ts +3 -3
- package/dist/theme/themes.js +2 -16
- package/dist/trajectory/Trajectory.svelte +52 -78
- package/dist/trajectory/Trajectory.svelte.d.ts +5 -5
- package/dist/trajectory/TrajectoryError.svelte.d.ts +3 -3
- package/dist/trajectory/TrajectoryExportPane.svelte +1 -1
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +5 -5
- package/dist/trajectory/TrajectoryInfoPane.svelte +1 -1
- package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +3 -3
- package/dist/trajectory/extract.js +2 -1
- package/dist/trajectory/index.d.ts +1 -1
- package/dist/trajectory/index.js +1 -1
- package/dist/trajectory/parse.js +21 -14
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +17 -0
- package/dist/xrd/XrdPlot.svelte +14 -11
- package/dist/xrd/XrdPlot.svelte.d.ts +6 -5
- package/package.json +20 -12
- package/readme.md +1 -1
- package/dist/material/MaterialCard.svelte +0 -123
- package/dist/material/MaterialCard.svelte.d.ts +0 -9
- package/dist/material/index.d.ts +0 -1
- package/dist/material/index.js +0 -1
- package/dist/plot/formatting.d.ts +0 -7
- package/dist/plot/formatting.js +0 -53
|
@@ -1,266 +1,257 @@
|
|
|
1
|
+
// Bonding algorithms for structure visualization
|
|
1
2
|
import { element_data } from '../element';
|
|
2
|
-
// Bonding strategy map
|
|
3
|
-
export const BONDING_STRATEGIES = {
|
|
4
|
-
max_dist,
|
|
5
|
-
nearest_neighbor,
|
|
6
|
-
electroneg_ratio,
|
|
7
|
-
};
|
|
8
|
-
// Performance-optimized element lookup map
|
|
9
3
|
const element_lookup = new Map(element_data.map((el) => [el.symbol, el]));
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
4
|
+
const covalent_radii = new Map(element_data.filter((el) => el.covalent_radius !== null).map((el) => [el.symbol, el.covalent_radius]));
|
|
5
|
+
// Get the species with highest occupancy from a site.
|
|
6
|
+
function get_majority_species(site) {
|
|
7
|
+
return (site.species ?? []).reduce((max, spec) => (spec.occu > max.occu ? spec : max), site.species?.[0] ?? { element: ``, occu: -1 });
|
|
8
|
+
}
|
|
9
|
+
// Compute 4x4 transformation matrix for bond cylinder between two positions.
|
|
10
|
+
// Uses Y-up, right-handed coordinate system convention for Three.js compatibility.
|
|
11
|
+
function compute_bond_transform(pos_1, pos_2) {
|
|
12
|
+
const [dx, dy, dz] = [pos_2[0] - pos_1[0], pos_2[1] - pos_1[1], pos_2[2] - pos_1[2]];
|
|
13
|
+
const height = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
14
|
+
if (height < 1e-10) {
|
|
15
|
+
return new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
|
16
|
+
}
|
|
17
|
+
const [dir_x, dir_y, dir_z] = [dx / height, dy / height, dz / height];
|
|
18
|
+
let [m00, m01, m02, m10, m11, m12, m20, m21, m22] = [0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
19
|
+
// Special case: bond pointing straight up (+Y)
|
|
20
|
+
if (Math.abs(dir_y - 1.0) < 1e-10) {
|
|
21
|
+
;
|
|
22
|
+
[m00, m01, m02, m10, m11, m12, m20, m21, m22] = [1, 0, 0, 0, 1, 0, 0, 0, 1];
|
|
23
|
+
}
|
|
24
|
+
else if (Math.abs(dir_y + 1.0) < 1e-10) {
|
|
25
|
+
// Special case: bond pointing straight down (-Y)
|
|
26
|
+
;
|
|
27
|
+
[m00, m01, m02, m10, m11, m12, m20, m21, m22] = [1, 0, 0, 0, -1, 0, 0, 0, 1];
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
// General case: construct orthonormal basis (right, dir, up)
|
|
31
|
+
// Right vector: perpendicular to dir in XZ plane
|
|
32
|
+
const [rx, rz] = [-dir_z, dir_x];
|
|
33
|
+
const r_len = Math.sqrt(rx * rx + rz * rz);
|
|
34
|
+
const [right_x, right_z] = [rx / r_len, rz / r_len];
|
|
35
|
+
// Up vector: cross product of dir and right
|
|
36
|
+
const [up_x, up_y, up_z] = [
|
|
37
|
+
dir_y * right_z,
|
|
38
|
+
dir_z * right_x - dir_x * right_z,
|
|
39
|
+
-dir_y * right_x,
|
|
40
|
+
];
|
|
41
|
+
[m00, m01, m02, m10, m11, m12, m20, m21, m22] = [
|
|
42
|
+
right_x,
|
|
43
|
+
dir_x,
|
|
44
|
+
up_x,
|
|
45
|
+
0,
|
|
46
|
+
dir_y,
|
|
47
|
+
up_y,
|
|
48
|
+
right_z,
|
|
49
|
+
dir_z,
|
|
50
|
+
up_z,
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
// Position at midpoint between the two atoms
|
|
54
|
+
const [px, py, pz] = [
|
|
55
|
+
(pos_1[0] + pos_2[0]) / 2,
|
|
56
|
+
(pos_1[1] + pos_2[1]) / 2,
|
|
57
|
+
(pos_1[2] + pos_2[2]) / 2,
|
|
58
|
+
];
|
|
59
|
+
return new Float32Array([
|
|
60
|
+
...[m00, m10, m20, 0],
|
|
61
|
+
...[m01 * height, m11 * height, m21 * height, 0],
|
|
62
|
+
...[m02, m12, m22, 0],
|
|
63
|
+
...[px, py, pz, 1],
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
// Build spatial grid by dividing 3D space into cubic cells.
|
|
67
|
+
function build_spatial_grid(sites, cell_size) {
|
|
68
|
+
const grid = new Map();
|
|
69
|
+
for (let idx = 0; idx < sites.length; idx++) {
|
|
70
|
+
const [x, y, z] = sites[idx].xyz;
|
|
71
|
+
const key = `${Math.floor(x / cell_size)},${Math.floor(y / cell_size)},${Math.floor(z / cell_size)}`;
|
|
72
|
+
const cell = grid.get(key);
|
|
73
|
+
if (cell)
|
|
74
|
+
cell.push(idx);
|
|
75
|
+
else
|
|
76
|
+
grid.set(key, [idx]);
|
|
77
|
+
}
|
|
78
|
+
return grid;
|
|
79
|
+
}
|
|
80
|
+
// Get all site indices in 3x3x3 cube of cells around position.
|
|
81
|
+
function get_neighbors_from_grid(pos, grid, cell_size) {
|
|
82
|
+
const [cx, cy, cz] = [
|
|
83
|
+
Math.floor(pos[0] / cell_size),
|
|
84
|
+
Math.floor(pos[1] / cell_size),
|
|
85
|
+
Math.floor(pos[2] / cell_size),
|
|
86
|
+
];
|
|
87
|
+
const neighbors = [];
|
|
88
|
+
for (let dx = -1; dx <= 1; dx++) {
|
|
89
|
+
for (let dy = -1; dy <= 1; dy++) {
|
|
90
|
+
for (let dz = -1; dz <= 1; dz++) {
|
|
91
|
+
const cell = grid.get(`${cx + dx},${cy + dy},${cz + dz}`);
|
|
92
|
+
if (cell)
|
|
93
|
+
neighbors.push(...cell);
|
|
72
94
|
}
|
|
73
95
|
}
|
|
74
96
|
}
|
|
75
|
-
return
|
|
97
|
+
return neighbors;
|
|
98
|
+
}
|
|
99
|
+
// Setup spatial decomposition for structures with >50 atoms.
|
|
100
|
+
function setup_spatial_grid(sites, cutoff) {
|
|
101
|
+
const use_grid = sites.length > 50;
|
|
102
|
+
return use_grid ? { grid: build_spatial_grid(sites, cutoff), cell_size: cutoff } : null;
|
|
103
|
+
}
|
|
104
|
+
// Get candidate neighbor indices using spatial grid or all sites.
|
|
105
|
+
function get_candidates(pos, sites, spatial) {
|
|
106
|
+
return spatial
|
|
107
|
+
? get_neighbors_from_grid(pos, spatial.grid, spatial.cell_size)
|
|
108
|
+
: Array.from({ length: sites.length }, (_, idx) => idx);
|
|
76
109
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
110
|
+
export const BONDING_STRATEGIES = { electroneg_ratio, solid_angle };
|
|
111
|
+
// Electronegativity-based bonding with chemical preferences.
|
|
112
|
+
// This algorithm considers electronegativity differences between atoms, metal/nonmetal
|
|
113
|
+
// properties, and distance to determine bond strength. Bonds are only created if the
|
|
114
|
+
// computed strength exceeds the strength_threshold parameter (default: 0.3).
|
|
115
|
+
export function electroneg_ratio(structure, { electronegativity_threshold = 1.7, // Max electronegativity difference for bonding
|
|
116
|
+
max_distance_ratio = 2.0, // Max distance as multiple of sum of covalent radii
|
|
117
|
+
min_bond_dist = 0.4, // Minimum bond distance in Angstroms
|
|
118
|
+
metal_metal_penalty = 0.7, // Strength penalty for metal-metal bonds
|
|
119
|
+
metal_nonmetal_bonus = 1.5, // Strength bonus for metal-nonmetal bonds
|
|
120
|
+
similar_electronegativity_bonus = 1.2, // Bonus for similar electronegativity
|
|
121
|
+
same_species_penalty = 0.5, // Penalty for bonds between same element
|
|
122
|
+
strength_threshold = 0.3, // Minimum bond strength to include in results
|
|
123
|
+
} = {}) {
|
|
124
|
+
const { sites } = structure;
|
|
80
125
|
if (sites.length < 2)
|
|
81
126
|
return [];
|
|
82
127
|
const bonds = [];
|
|
83
128
|
const min_dist_sq = min_bond_dist ** 2;
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
129
|
+
const closest = new Map();
|
|
130
|
+
const props = sites.map((site) => {
|
|
131
|
+
const majority = get_majority_species(site);
|
|
132
|
+
const elem = majority.element;
|
|
133
|
+
const data = element_lookup.get(elem);
|
|
134
|
+
return {
|
|
135
|
+
element: elem,
|
|
136
|
+
electroneg: data?.electronegativity ?? 2.0,
|
|
137
|
+
is_metal: data?.metal ?? false,
|
|
138
|
+
is_nonmetal: data?.nonmetal ?? false,
|
|
139
|
+
radius: elem ? covalent_radii.get(elem) : undefined,
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
let max_radius = 0;
|
|
143
|
+
for (const radius of covalent_radii.values()) {
|
|
144
|
+
if (radius > max_radius)
|
|
145
|
+
max_radius = radius;
|
|
146
|
+
}
|
|
147
|
+
const max_cutoff = max_radius * 2 * max_distance_ratio;
|
|
148
|
+
const spatial = setup_spatial_grid(sites, max_cutoff);
|
|
149
|
+
for (let idx_a = 0; idx_a < sites.length - 1; idx_a++) {
|
|
88
150
|
const [x1, y1, z1] = sites[idx_a].xyz;
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
const neighbors = [];
|
|
93
|
-
for (let idx_b = 0; idx_b < sites.length; idx_b++) {
|
|
94
|
-
if (idx_a === idx_b)
|
|
151
|
+
const pa = props[idx_a];
|
|
152
|
+
for (const idx_b of get_candidates(sites[idx_a].xyz, sites, spatial)) {
|
|
153
|
+
if (idx_b <= idx_a)
|
|
95
154
|
continue;
|
|
96
155
|
const [x2, y2, z2] = sites[idx_b].xyz;
|
|
97
|
-
const
|
|
98
|
-
const radius_b = covalent_radii.get(element_b);
|
|
156
|
+
const pb = props[idx_b];
|
|
99
157
|
const [dx, dy, dz] = [x2 - x1, y2 - y1, z2 - z1];
|
|
100
158
|
const dist_sq = dx * dx + dy * dy + dz * dz;
|
|
101
|
-
const
|
|
102
|
-
if (dist_sq
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
radius: radius_b,
|
|
113
|
-
});
|
|
159
|
+
const dist = Math.sqrt(dist_sq);
|
|
160
|
+
if (dist_sq < min_dist_sq || !pa.radius || !pb.radius)
|
|
161
|
+
continue;
|
|
162
|
+
const expected = pa.radius + pb.radius;
|
|
163
|
+
if (dist > expected * max_distance_ratio)
|
|
164
|
+
continue;
|
|
165
|
+
const en_diff = Math.abs(pa.electroneg - pb.electroneg);
|
|
166
|
+
const en_ratio = en_diff / (pa.electroneg + pb.electroneg);
|
|
167
|
+
let bond_strength = 1.0;
|
|
168
|
+
if (pa.is_metal && pb.is_metal) {
|
|
169
|
+
bond_strength *= metal_metal_penalty;
|
|
114
170
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const distance_ratio = neighbor.normalized_distance / nearest_normalized_dist;
|
|
136
|
-
let strength = Math.exp(-((distance_ratio - 1) ** 2) / (2 * 0.15 ** 2));
|
|
137
|
-
// Apply same-species penalty
|
|
138
|
-
if (element_a === neighbor.element)
|
|
139
|
-
strength *= same_species_penalty;
|
|
140
|
-
// Apply bond saturation penalty more efficiently
|
|
141
|
-
const closest_a = closest_bond_distances.get(idx_a) ?? Infinity;
|
|
142
|
-
const closest_b = closest_bond_distances.get(idx_b) ?? Infinity;
|
|
143
|
-
if (neighbor.distance > closest_a) {
|
|
144
|
-
const distance_ratio_a = neighbor.distance / closest_a;
|
|
145
|
-
strength *= Math.exp(-(distance_ratio_a - 1) / 0.5);
|
|
146
|
-
}
|
|
147
|
-
if (neighbor.distance > closest_b) {
|
|
148
|
-
const distance_ratio_b = neighbor.distance / closest_b;
|
|
149
|
-
strength *= Math.exp(-(distance_ratio_b - 1) / 0.5);
|
|
150
|
-
}
|
|
171
|
+
else if ((pa.is_metal && pb.is_nonmetal) || (pa.is_nonmetal && pb.is_metal)) {
|
|
172
|
+
bond_strength *= metal_nonmetal_bonus;
|
|
173
|
+
if (en_diff > electronegativity_threshold)
|
|
174
|
+
bond_strength *= 1.3;
|
|
175
|
+
}
|
|
176
|
+
else if (en_diff < 0.5) {
|
|
177
|
+
bond_strength *= similar_electronegativity_bonus;
|
|
178
|
+
}
|
|
179
|
+
const dist_weight = Math.exp(-((dist / expected - 1) ** 2) / 0.18);
|
|
180
|
+
const en_weight = 1.0 - 0.3 * en_ratio;
|
|
181
|
+
let strength = bond_strength * dist_weight * en_weight;
|
|
182
|
+
if (pa.element === pb.element)
|
|
183
|
+
strength *= same_species_penalty;
|
|
184
|
+
const ca = closest.get(idx_a) ?? Infinity;
|
|
185
|
+
const cb = closest.get(idx_b) ?? Infinity;
|
|
186
|
+
if (dist > ca)
|
|
187
|
+
strength *= Math.exp(-(dist / ca - 1) / 0.5);
|
|
188
|
+
if (dist > cb)
|
|
189
|
+
strength *= Math.exp(-(dist / cb - 1) / 0.5);
|
|
190
|
+
if (strength > strength_threshold) {
|
|
151
191
|
bonds.push({
|
|
152
192
|
pos_1: sites[idx_a].xyz,
|
|
153
193
|
pos_2: sites[idx_b].xyz,
|
|
154
194
|
site_idx_1: idx_a,
|
|
155
195
|
site_idx_2: idx_b,
|
|
156
|
-
bond_length:
|
|
196
|
+
bond_length: dist,
|
|
157
197
|
strength,
|
|
198
|
+
transform_matrix: compute_bond_transform(sites[idx_a].xyz, sites[idx_b].xyz),
|
|
158
199
|
});
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (neighbor.distance < closest_b) {
|
|
164
|
-
closest_bond_distances.set(idx_b, neighbor.distance);
|
|
165
|
-
}
|
|
200
|
+
if (dist < ca)
|
|
201
|
+
closest.set(idx_a, dist);
|
|
202
|
+
if (dist < cb)
|
|
203
|
+
closest.set(idx_b, dist);
|
|
166
204
|
}
|
|
167
205
|
}
|
|
168
206
|
}
|
|
169
207
|
return bonds;
|
|
170
208
|
}
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
209
|
+
// Solid angle-based bonding using geometric proximity heuristics.
|
|
210
|
+
// Inspired by Voronoi tessellation without having to actually compute Voronoi cells.
|
|
211
|
+
// This algorithm computes bond strength based on the solid angle subtended by atoms
|
|
212
|
+
// and their distance penalty. Bonds are only created if the computed strength exceeds
|
|
213
|
+
// the strength_threshold parameter.
|
|
214
|
+
export function solid_angle(structure, { min_solid_angle = 0.01, min_face_area = 0.05, max_distance = 5.0, min_bond_dist = 0.4, strength_threshold = 0.05, } = {}) {
|
|
215
|
+
const { sites } = structure;
|
|
174
216
|
if (sites.length < 2)
|
|
175
217
|
return [];
|
|
176
218
|
const bonds = [];
|
|
177
219
|
const min_dist_sq = min_bond_dist ** 2;
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
// Pre-calculate element properties
|
|
181
|
-
const site_properties = sites.map((site) => {
|
|
182
|
-
const element = element_lookup.get(site.species?.[0]?.element);
|
|
183
|
-
return {
|
|
184
|
-
element: site.species?.[0]?.element,
|
|
185
|
-
electronegativity: element?.electronegativity ?? 2.0,
|
|
186
|
-
is_metal: element?.metal ?? false,
|
|
187
|
-
is_nonmetal: element?.nonmetal ?? false,
|
|
188
|
-
covalent_radius: covalent_radii.get(site.species?.[0]?.element),
|
|
189
|
-
};
|
|
190
|
-
});
|
|
191
|
-
// Phase 1: Find all potential bonds with electronegativity weighting
|
|
220
|
+
const max_dist_sq = max_distance ** 2;
|
|
221
|
+
const spatial = setup_spatial_grid(sites, max_distance);
|
|
192
222
|
for (let idx_a = 0; idx_a < sites.length - 1; idx_a++) {
|
|
193
223
|
const [x1, y1, z1] = sites[idx_a].xyz;
|
|
194
|
-
const
|
|
195
|
-
|
|
224
|
+
const majority_a = get_majority_species(sites[idx_a]);
|
|
225
|
+
const ra = majority_a.element ? covalent_radii.get(majority_a.element) : undefined;
|
|
226
|
+
for (const idx_b of get_candidates(sites[idx_a].xyz, sites, spatial)) {
|
|
227
|
+
if (idx_b <= idx_a)
|
|
228
|
+
continue;
|
|
196
229
|
const [x2, y2, z2] = sites[idx_b].xyz;
|
|
197
|
-
const
|
|
230
|
+
const majority_b = get_majority_species(sites[idx_b]);
|
|
231
|
+
const rb = majority_b.element ? covalent_radii.get(majority_b.element) : undefined;
|
|
198
232
|
const [dx, dy, dz] = [x2 - x1, y2 - y1, z2 - z1];
|
|
199
233
|
const dist_sq = dx * dx + dy * dy + dz * dz;
|
|
200
|
-
const
|
|
201
|
-
if (dist_sq < min_dist_sq)
|
|
202
|
-
continue;
|
|
203
|
-
if (!props_a.covalent_radius || !props_b.covalent_radius)
|
|
234
|
+
const dist = Math.sqrt(dist_sq);
|
|
235
|
+
if (dist_sq < min_dist_sq || dist_sq > max_dist_sq || !ra || !rb)
|
|
204
236
|
continue;
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
|
|
237
|
+
const avg_r = (ra + rb) / 2.0;
|
|
238
|
+
const face_area = Math.PI * avg_r * avg_r;
|
|
239
|
+
const solid_angle = face_area / dist_sq;
|
|
240
|
+
if (solid_angle < min_solid_angle || face_area < min_face_area)
|
|
208
241
|
continue;
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
(props_a.electronegativity + props_b.electronegativity);
|
|
213
|
-
let bond_strength = 1.0;
|
|
214
|
-
// Chemical bonding preferences
|
|
215
|
-
if (props_a.is_metal && props_b.is_metal) {
|
|
216
|
-
bond_strength *= metal_metal_penalty;
|
|
217
|
-
}
|
|
218
|
-
else if ((props_a.is_metal && props_b.is_nonmetal) ||
|
|
219
|
-
(props_a.is_nonmetal && props_b.is_metal)) {
|
|
220
|
-
bond_strength *= metal_nonmetal_bonus;
|
|
221
|
-
if (electronegativity_diff > electronegativity_threshold) {
|
|
222
|
-
bond_strength *= 1.3; // Ionic character bonus
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
else if (electronegativity_diff < 0.5) {
|
|
226
|
-
bond_strength *= similar_electronegativity_bonus;
|
|
227
|
-
}
|
|
228
|
-
// Distance-dependent weighting (CrystalNN approach)
|
|
229
|
-
const distance_ratio = distance / expected_dist;
|
|
230
|
-
const distance_weight = Math.exp(-((distance_ratio - 1) ** 2) / (2 * 0.3 ** 2));
|
|
231
|
-
// Electronegativity-based weighting
|
|
232
|
-
const electro_weight = 1.0 - 0.3 * electronegativity_ratio;
|
|
233
|
-
// Combined strength score
|
|
234
|
-
let strength = bond_strength * distance_weight * electro_weight;
|
|
235
|
-
// Apply same-species penalty
|
|
236
|
-
if (props_a.element === props_b.element) {
|
|
237
|
-
strength *= same_species_penalty;
|
|
238
|
-
}
|
|
239
|
-
// Apply bond saturation penalty more efficiently
|
|
240
|
-
const closest_a = closest_bond_distances.get(idx_a) ?? Infinity;
|
|
241
|
-
const closest_b = closest_bond_distances.get(idx_b) ?? Infinity;
|
|
242
|
-
if (distance > closest_a) {
|
|
243
|
-
const distance_ratio_a = distance / closest_a;
|
|
244
|
-
strength *= Math.exp(-(distance_ratio_a - 1) / 0.5);
|
|
245
|
-
}
|
|
246
|
-
if (distance > closest_b) {
|
|
247
|
-
const distance_ratio_b = distance / closest_b;
|
|
248
|
-
strength *= Math.exp(-(distance_ratio_b - 1) / 0.5);
|
|
249
|
-
}
|
|
242
|
+
const dist_penalty = Math.exp(-((dist / (ra + rb) - 1) ** 2) / 0.4);
|
|
243
|
+
const angle_strength = Math.min(solid_angle / (4.0 * Math.PI), 1.0);
|
|
244
|
+
const strength = angle_strength * dist_penalty;
|
|
250
245
|
if (strength > strength_threshold) {
|
|
251
246
|
bonds.push({
|
|
252
247
|
pos_1: sites[idx_a].xyz,
|
|
253
248
|
pos_2: sites[idx_b].xyz,
|
|
254
249
|
site_idx_1: idx_a,
|
|
255
250
|
site_idx_2: idx_b,
|
|
256
|
-
bond_length:
|
|
251
|
+
bond_length: dist,
|
|
257
252
|
strength,
|
|
253
|
+
transform_matrix: compute_bond_transform(sites[idx_a].xyz, sites[idx_b].xyz),
|
|
258
254
|
});
|
|
259
|
-
// Update closest bond distances
|
|
260
|
-
if (distance < closest_a)
|
|
261
|
-
closest_bond_distances.set(idx_a, distance);
|
|
262
|
-
if (distance < closest_b)
|
|
263
|
-
closest_bond_distances.set(idx_b, distance);
|
|
264
255
|
}
|
|
265
256
|
}
|
|
266
257
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AnyStructure } from '..';
|
|
2
|
+
import { type Scene } from 'three';
|
|
2
3
|
export declare function create_structure_filename(structure: AnyStructure | undefined, extension: string): string;
|
|
3
4
|
export declare function structure_to_xyz_str(structure?: AnyStructure): string;
|
|
4
5
|
export declare function structure_to_cif_str(structure?: AnyStructure): string;
|
|
@@ -8,3 +9,5 @@ export declare function export_structure_as_cif(structure?: AnyStructure): void;
|
|
|
8
9
|
export declare function export_structure_as_poscar(structure?: AnyStructure): void;
|
|
9
10
|
export declare function export_structure_as_xyz(structure?: AnyStructure): void;
|
|
10
11
|
export declare function export_structure_as_json(structure?: AnyStructure): void;
|
|
12
|
+
export declare function export_structure_as_glb(scene: Scene | null, structure: AnyStructure | undefined): void;
|
|
13
|
+
export declare function export_structure_as_obj(scene: Scene | null, structure: AnyStructure | undefined): void;
|
package/dist/structure/export.js
CHANGED
|
@@ -1,6 +1,76 @@
|
|
|
1
1
|
import { electro_neg_formula } from '..';
|
|
2
2
|
import { download } from '../io/fetch';
|
|
3
3
|
import * as math from '../math';
|
|
4
|
+
import { Group, Matrix4, Mesh, MeshStandardMaterial, } from 'three';
|
|
5
|
+
import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';
|
|
6
|
+
import { OBJExporter } from 'three/examples/jsm/exporters/OBJExporter.js';
|
|
7
|
+
// Helper function to convert InstancedMesh to regular Mesh objects for export
|
|
8
|
+
// This is necessary because GLB/OBJ exporters don't handle InstancedMesh properly
|
|
9
|
+
// Note: Threlte's InstancedMesh sets isInstancedMesh=true but type remains "Mesh"
|
|
10
|
+
function convert_instanced_meshes_to_regular(scene) {
|
|
11
|
+
const cloned_scene = scene.clone();
|
|
12
|
+
// Find all InstancedMesh objects in the cloned scene
|
|
13
|
+
const instanced_meshes = [];
|
|
14
|
+
cloned_scene.traverse((object) => {
|
|
15
|
+
// Check for isInstancedMesh property (Threlte) or type === InstancedMesh (vanilla Three.js)
|
|
16
|
+
// @ts-expect-error - checking for isInstancedMesh property
|
|
17
|
+
const is_instanced = object.isInstancedMesh === true ||
|
|
18
|
+
object.type === `InstancedMesh`;
|
|
19
|
+
if (is_instanced) {
|
|
20
|
+
instanced_meshes.push(object);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
// Convert each InstancedMesh to individual Mesh objects
|
|
24
|
+
for (const instanced_mesh of instanced_meshes) {
|
|
25
|
+
const parent = instanced_mesh.parent;
|
|
26
|
+
if (!parent || !instanced_mesh.instanceMatrix)
|
|
27
|
+
continue;
|
|
28
|
+
// Create a group to hold all the individual meshes
|
|
29
|
+
const group = new Group();
|
|
30
|
+
group.name = instanced_mesh.name;
|
|
31
|
+
// Get the base transform from the InstancedMesh
|
|
32
|
+
const base_matrix = new Matrix4();
|
|
33
|
+
base_matrix.copy(instanced_mesh.matrix);
|
|
34
|
+
// Create individual meshes for each instance
|
|
35
|
+
const instance_matrix = new Matrix4();
|
|
36
|
+
for (let idx = 0; idx < instanced_mesh.count; idx++) {
|
|
37
|
+
instanced_mesh.getMatrixAt(idx, instance_matrix);
|
|
38
|
+
// Clone geometry for each instance (applyMatrix4 modifies geometry in place)
|
|
39
|
+
const mesh = new Mesh(instanced_mesh.geometry.clone(), instanced_mesh.material instanceof Array
|
|
40
|
+
? instanced_mesh.material.map((mat) => mat.clone())
|
|
41
|
+
: instanced_mesh.material.clone());
|
|
42
|
+
// Combine base transform with instance transform
|
|
43
|
+
const combined_matrix = new Matrix4();
|
|
44
|
+
combined_matrix.multiplyMatrices(base_matrix, instance_matrix);
|
|
45
|
+
mesh.applyMatrix4(combined_matrix);
|
|
46
|
+
// Copy instance color if it exists
|
|
47
|
+
if (instanced_mesh.instanceColor) {
|
|
48
|
+
const color_r = instanced_mesh.instanceColor.getX(idx);
|
|
49
|
+
const color_g = instanced_mesh.instanceColor.getY(idx);
|
|
50
|
+
const color_b = instanced_mesh.instanceColor.getZ(idx);
|
|
51
|
+
if (mesh.material instanceof MeshStandardMaterial) {
|
|
52
|
+
mesh.material.color.setRGB(color_r, color_g, color_b);
|
|
53
|
+
}
|
|
54
|
+
else if (Array.isArray(mesh.material)) {
|
|
55
|
+
mesh.material.forEach((mat) => {
|
|
56
|
+
if (mat instanceof MeshStandardMaterial) {
|
|
57
|
+
mat.color.setRGB(color_r, color_g, color_b);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
group.add(mesh);
|
|
63
|
+
}
|
|
64
|
+
// Replace the InstancedMesh with the Group in the parent
|
|
65
|
+
parent.remove(instanced_mesh);
|
|
66
|
+
parent.add(group);
|
|
67
|
+
// Update world matrices after scene graph modification
|
|
68
|
+
group.updateMatrixWorld(true);
|
|
69
|
+
}
|
|
70
|
+
// Update all world matrices in the modified scene
|
|
71
|
+
cloned_scene.updateMatrixWorld(true);
|
|
72
|
+
return cloned_scene;
|
|
73
|
+
}
|
|
4
74
|
// Generate a filename for structure exports based on structure metadata
|
|
5
75
|
export function create_structure_filename(structure, extension) {
|
|
6
76
|
if (!structure)
|
|
@@ -337,3 +407,53 @@ export function export_structure_as_json(structure) {
|
|
|
337
407
|
console.error(`Error exporting JSON:`, error);
|
|
338
408
|
}
|
|
339
409
|
}
|
|
410
|
+
// Export Three.js scene as GLB (binary GLTF) file
|
|
411
|
+
// GLB preserves materials and colors, making it ideal for element visualization
|
|
412
|
+
export function export_structure_as_glb(scene, structure) {
|
|
413
|
+
try {
|
|
414
|
+
if (!scene) {
|
|
415
|
+
console.warn(`No scene available for GLB export`);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
// Convert instanced meshes to regular meshes for export
|
|
419
|
+
const export_scene = convert_instanced_meshes_to_regular(scene);
|
|
420
|
+
const exporter = new GLTFExporter();
|
|
421
|
+
const filename = create_structure_filename(structure, `glb`);
|
|
422
|
+
// Export as binary GLB format
|
|
423
|
+
exporter.parse(export_scene, (result) => {
|
|
424
|
+
if (result instanceof ArrayBuffer) {
|
|
425
|
+
const blob = new Blob([result], { type: `model/gltf-binary` });
|
|
426
|
+
download(blob, filename, `model/gltf-binary`);
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
console.error(`GLB export returned unexpected format`);
|
|
430
|
+
}
|
|
431
|
+
}, (error) => {
|
|
432
|
+
console.error(`GLB export failed:`, error);
|
|
433
|
+
}, { binary: true });
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
console.error(`Error exporting GLB:`, error);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// Export Three.js scene as OBJ (Wavefront Object) file
|
|
440
|
+
// OBJ exports geometry with material references, widely supported format
|
|
441
|
+
export function export_structure_as_obj(scene, structure) {
|
|
442
|
+
try {
|
|
443
|
+
if (!scene) {
|
|
444
|
+
console.warn(`No scene available for OBJ export`);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
// Convert instanced meshes to regular meshes for export
|
|
448
|
+
const export_scene = convert_instanced_meshes_to_regular(scene);
|
|
449
|
+
const exporter = new OBJExporter();
|
|
450
|
+
const filename = create_structure_filename(structure, `obj`);
|
|
451
|
+
const result = exporter.parse(export_scene);
|
|
452
|
+
// OBJ exporter returns a string
|
|
453
|
+
const blob = new Blob([result], { type: `text/plain` });
|
|
454
|
+
download(blob, filename, `text/plain`);
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
console.error(`Error exporting OBJ:`, error);
|
|
458
|
+
}
|
|
459
|
+
}
|