matterviz 0.3.0 → 0.3.2
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 +37 -20
- package/dist/Icon.svelte +2 -2
- package/dist/MillerIndexInput.svelte +60 -0
- package/dist/MillerIndexInput.svelte.d.ts +7 -0
- package/dist/app.css +38 -2
- package/dist/brillouin/BrillouinZone.svelte +20 -62
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneExportPane.svelte +12 -20
- package/dist/brillouin/BrillouinZoneScene.svelte +2 -2
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram.svelte +192 -0
- package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +677 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2688 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
- package/dist/chempot-diagram/color.d.ts +10 -0
- package/dist/chempot-diagram/color.js +33 -0
- package/dist/chempot-diagram/compute.d.ts +38 -0
- package/dist/chempot-diagram/compute.js +650 -0
- package/dist/chempot-diagram/index.d.ts +5 -0
- package/dist/chempot-diagram/index.js +5 -0
- package/dist/chempot-diagram/pointer.d.ts +16 -0
- package/dist/chempot-diagram/pointer.js +40 -0
- package/dist/chempot-diagram/temperature.d.ts +15 -0
- package/dist/chempot-diagram/temperature.js +37 -0
- package/dist/chempot-diagram/types.d.ts +83 -0
- package/dist/chempot-diagram/types.js +27 -0
- package/dist/colors/index.d.ts +3 -1
- package/dist/colors/index.js +4 -0
- package/dist/composition/BarChart.svelte +13 -22
- package/dist/composition/BubbleChart.svelte +5 -3
- package/dist/composition/FormulaFilter.svelte +770 -90
- package/dist/composition/FormulaFilter.svelte.d.ts +37 -1
- package/dist/composition/PieChart.svelte +43 -18
- package/dist/composition/PieChart.svelte.d.ts +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -0
- package/dist/convex-hull/ConvexHull.svelte +14 -1
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +14 -45
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +396 -134
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +93 -42
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullControls.svelte +94 -31
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +4 -2
- package/dist/convex-hull/ConvexHullStats.svelte +697 -128
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
- package/dist/convex-hull/ConvexHullTooltip.svelte +1 -0
- package/dist/convex-hull/GasPressureControls.svelte +72 -38
- package/dist/convex-hull/GasPressureControls.svelte.d.ts +2 -1
- package/dist/convex-hull/TemperatureSlider.svelte +46 -19
- package/dist/convex-hull/TemperatureSlider.svelte.d.ts +2 -1
- package/dist/convex-hull/demo-temperature.d.ts +6 -0
- package/dist/convex-hull/demo-temperature.js +36 -0
- package/dist/convex-hull/gas-thermodynamics.js +16 -5
- package/dist/convex-hull/helpers.d.ts +7 -1
- package/dist/convex-hull/helpers.js +45 -15
- package/dist/convex-hull/index.d.ts +15 -1
- package/dist/convex-hull/index.js +1 -0
- package/dist/convex-hull/thermodynamics.d.ts +8 -21
- package/dist/convex-hull/thermodynamics.js +106 -17
- package/dist/convex-hull/types.d.ts +7 -0
- package/dist/convex-hull/types.js +11 -0
- package/dist/coordination/CoordinationBarPlot.svelte +29 -46
- package/dist/element/BohrAtom.svelte +1 -1
- package/dist/element/data.js +2 -14
- package/dist/element/data.json.gz +0 -0
- package/dist/element/index.d.ts +1 -1
- package/dist/element/index.js +1 -0
- package/dist/element/types.d.ts +1 -0
- package/dist/fermi-surface/FermiSurface.svelte +21 -65
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/compute.js +1 -21
- package/dist/fermi-surface/marching-cubes.d.ts +2 -13
- package/dist/fermi-surface/marching-cubes.js +2 -519
- package/dist/fermi-surface/parse.js +17 -23
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +1273 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +171 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +31 -0
- package/dist/heatmap-matrix/index.d.ts +53 -0
- package/dist/heatmap-matrix/index.js +100 -0
- package/dist/heatmap-matrix/shared.d.ts +2 -0
- package/dist/heatmap-matrix/shared.js +4 -0
- package/dist/icons.d.ts +119 -0
- package/dist/icons.js +119 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/io/export.js +15 -3
- package/dist/io/file-drop.d.ts +7 -0
- package/dist/io/file-drop.js +43 -0
- package/dist/io/index.d.ts +2 -2
- package/dist/io/index.js +2 -112
- package/dist/io/types.d.ts +1 -0
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +118 -0
- package/dist/isosurface/Isosurface.svelte +231 -0
- package/dist/isosurface/Isosurface.svelte.d.ts +8 -0
- package/dist/isosurface/IsosurfaceControls.svelte +273 -0
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +9 -0
- package/dist/isosurface/index.d.ts +5 -0
- package/dist/isosurface/index.js +6 -0
- package/dist/isosurface/parse.d.ts +6 -0
- package/dist/isosurface/parse.js +548 -0
- package/dist/isosurface/slice.d.ts +11 -0
- package/dist/isosurface/slice.js +145 -0
- package/dist/isosurface/types.d.ts +55 -0
- package/dist/isosurface/types.js +178 -0
- package/dist/labels.d.ts +2 -1
- package/dist/labels.js +1 -0
- package/dist/layout/InfoTag.svelte +62 -62
- package/dist/layout/SubpageGrid.svelte +74 -0
- package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/index.js +1 -0
- package/dist/layout/json-tree/JsonNode.svelte +226 -53
- package/dist/layout/json-tree/JsonTree.svelte +425 -51
- package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
- package/dist/layout/json-tree/JsonValue.svelte +218 -97
- package/dist/layout/json-tree/types.d.ts +27 -2
- package/dist/layout/json-tree/utils.d.ts +14 -1
- package/dist/layout/json-tree/utils.js +254 -0
- package/dist/marching-cubes.d.ts +14 -0
- package/dist/marching-cubes.js +519 -0
- package/dist/math.d.ts +8 -0
- package/dist/math.js +374 -7
- package/dist/overlays/ContextMenu.svelte +3 -2
- package/dist/overlays/DraggablePane.svelte +163 -58
- package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +232 -77
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
- package/dist/phase-diagram/PhaseDiagramControls.svelte +32 -11
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +103 -0
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +102 -95
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +7 -0
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +100 -26
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
- package/dist/phase-diagram/index.d.ts +2 -0
- package/dist/phase-diagram/index.js +2 -0
- package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
- package/dist/phase-diagram/svg-to-diagram.js +865 -0
- package/dist/phase-diagram/types.d.ts +10 -0
- package/dist/phase-diagram/utils.d.ts +7 -4
- package/dist/phase-diagram/utils.js +149 -59
- package/dist/plot/AxisLabel.svelte +26 -0
- package/dist/plot/AxisLabel.svelte.d.ts +16 -0
- package/dist/plot/BarPlot.svelte +473 -228
- package/dist/plot/BarPlot.svelte.d.ts +3 -3
- package/dist/plot/BarPlotControls.svelte +3 -2
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ColorBar.svelte +54 -54
- package/dist/plot/ColorBar.svelte.d.ts +1 -1
- package/dist/plot/ElementScatter.svelte +4 -3
- package/dist/plot/FillArea.svelte +4 -1
- package/dist/plot/Histogram.svelte +320 -230
- package/dist/plot/Histogram.svelte.d.ts +2 -2
- package/dist/plot/HistogramControls.svelte +29 -10
- package/dist/plot/HistogramControls.svelte.d.ts +6 -2
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +2 -2
- package/dist/plot/PlotControls.svelte +109 -27
- package/dist/plot/PlotControls.svelte.d.ts +1 -1
- package/dist/plot/PlotLegend.svelte +1 -1
- package/dist/plot/PortalSelect.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
- package/dist/plot/ReferencePlane.svelte +1 -3
- package/dist/plot/ScatterPlot.svelte +343 -209
- package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
- package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlot3DControls.svelte +203 -250
- package/dist/plot/ScatterPlot3DScene.svelte +4 -7
- package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlotControls.svelte +95 -55
- package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ZeroLines.svelte +44 -0
- package/dist/plot/ZeroLines.svelte.d.ts +32 -0
- package/dist/plot/ZoomRect.svelte +21 -0
- package/dist/plot/ZoomRect.svelte.d.ts +8 -0
- package/dist/plot/axis-utils.d.ts +1 -1
- package/dist/plot/data-cleaning.js +1 -5
- package/dist/plot/index.d.ts +6 -2
- package/dist/plot/index.js +6 -2
- package/dist/plot/interactions.d.ts +8 -10
- package/dist/plot/interactions.js +10 -19
- package/dist/plot/layout.d.ts +7 -1
- package/dist/plot/layout.js +12 -4
- package/dist/plot/reference-line.d.ts +4 -21
- package/dist/plot/reference-line.js +7 -81
- package/dist/plot/types.d.ts +42 -17
- package/dist/plot/types.js +10 -0
- package/dist/plot/utils/label-placement.js +14 -11
- package/dist/plot/utils.d.ts +1 -0
- package/dist/plot/utils.js +14 -0
- package/dist/rdf/RdfPlot.svelte +55 -66
- package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
- package/dist/rdf/index.d.ts +1 -1
- package/dist/rdf/index.js +1 -1
- package/dist/settings.d.ts +5 -0
- package/dist/settings.js +37 -3
- package/dist/spectral/Bands.svelte +515 -143
- package/dist/spectral/Bands.svelte.d.ts +22 -2
- package/dist/spectral/helpers.d.ts +23 -1
- package/dist/spectral/helpers.js +65 -9
- package/dist/spectral/types.d.ts +2 -0
- package/dist/structure/AtomLegend.svelte +31 -10
- package/dist/structure/AtomLegend.svelte.d.ts +1 -1
- package/dist/structure/CellSelect.svelte +92 -22
- package/dist/structure/Lattice.svelte +2 -0
- package/dist/structure/Structure.svelte +716 -173
- package/dist/structure/Structure.svelte.d.ts +7 -2
- package/dist/structure/StructureControls.svelte +26 -14
- package/dist/structure/StructureControls.svelte.d.ts +5 -1
- package/dist/structure/StructureInfoPane.svelte +7 -1
- package/dist/structure/StructureScene.svelte +386 -95
- package/dist/structure/StructureScene.svelte.d.ts +15 -4
- package/dist/structure/atom-properties.d.ts +6 -2
- package/dist/structure/atom-properties.js +38 -25
- package/dist/structure/export.js +10 -7
- package/dist/structure/ferrox-wasm-types.d.ts +3 -2
- package/dist/structure/ferrox-wasm-types.js +0 -3
- package/dist/structure/ferrox-wasm.d.ts +3 -2
- package/dist/structure/ferrox-wasm.js +1 -2
- package/dist/structure/index.d.ts +7 -0
- package/dist/structure/index.js +22 -0
- package/dist/structure/parse.js +19 -16
- package/dist/structure/partial-occupancy.d.ts +25 -0
- package/dist/structure/partial-occupancy.js +102 -0
- package/dist/structure/validation.js +6 -3
- package/dist/symmetry/SymmetryStats.svelte +18 -4
- package/dist/symmetry/WyckoffTable.svelte +18 -10
- package/dist/symmetry/index.d.ts +7 -4
- package/dist/symmetry/index.js +83 -18
- package/dist/table/HeatmapTable.svelte +468 -69
- package/dist/table/HeatmapTable.svelte.d.ts +13 -1
- package/dist/table/ToggleMenu.svelte +291 -44
- package/dist/table/ToggleMenu.svelte.d.ts +4 -1
- package/dist/table/index.d.ts +3 -0
- package/dist/tooltip/index.d.ts +1 -1
- package/dist/tooltip/index.js +1 -0
- package/dist/trajectory/Trajectory.svelte +147 -145
- package/dist/trajectory/TrajectoryExportPane.svelte +13 -9
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
- package/dist/trajectory/constants.d.ts +6 -0
- package/dist/trajectory/constants.js +7 -0
- package/dist/trajectory/extract.js +3 -5
- package/dist/trajectory/format-detect.d.ts +9 -0
- package/dist/trajectory/format-detect.js +76 -0
- package/dist/trajectory/frame-reader.d.ts +17 -0
- package/dist/trajectory/frame-reader.js +339 -0
- package/dist/trajectory/helpers.d.ts +15 -0
- package/dist/trajectory/helpers.js +187 -0
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +11 -4
- package/dist/trajectory/parse/ase.d.ts +2 -0
- package/dist/trajectory/parse/ase.js +76 -0
- package/dist/trajectory/parse/hdf5.d.ts +2 -0
- package/dist/trajectory/parse/hdf5.js +121 -0
- package/dist/trajectory/parse/index.d.ts +12 -0
- package/dist/trajectory/parse/index.js +304 -0
- package/dist/trajectory/parse/lammps.d.ts +5 -0
- package/dist/trajectory/parse/lammps.js +169 -0
- package/dist/trajectory/parse/vasp.d.ts +2 -0
- package/dist/trajectory/parse/vasp.js +65 -0
- package/dist/trajectory/parse/xyz.d.ts +2 -0
- package/dist/trajectory/parse/xyz.js +109 -0
- package/dist/trajectory/types.d.ts +11 -0
- package/dist/trajectory/types.js +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +4 -0
- package/dist/xrd/XrdPlot.svelte +6 -4
- package/dist/xrd/calc-xrd.js +0 -1
- package/package.json +33 -23
- package/readme.md +4 -4
- package/dist/trajectory/parse.d.ts +0 -42
- package/dist/trajectory/parse.js +0 -1267
- /package/dist/element/{data.json.d.ts → data.json.gz.d.ts} +0 -0
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
<script lang="ts">import { AXIS_COLORS, NEG_AXIS_COLORS } from '../colors';
|
|
2
2
|
import { element_data } from '../element';
|
|
3
|
+
import Isosurface from '../isosurface/Isosurface.svelte';
|
|
4
|
+
import { DEFAULT_ISOSURFACE_SETTINGS } from '../isosurface/types';
|
|
3
5
|
import { format_num } from '../labels';
|
|
4
6
|
import * as math from '../math';
|
|
5
7
|
import { DEFAULTS } from '../settings';
|
|
6
8
|
import { colors } from '../state.svelte';
|
|
7
|
-
import { Arrow, atomic_radii, Cylinder, get_center_of_mass, Lattice, } from './';
|
|
9
|
+
import { Arrow, atomic_radii, Cylinder, get_center_of_mass, get_site_vector_info, Lattice, } from './';
|
|
8
10
|
import { get_orig_site_idx, get_property_colors, } from './atom-properties';
|
|
9
11
|
import * as measure from './measure';
|
|
12
|
+
import { compute_slice_geometry, merge_split_partial_sites, PARTIAL_OCCUPANCY_CAP_ARC, } from './partial-occupancy';
|
|
10
13
|
import { T, useThrelte } from '@threlte/core';
|
|
11
14
|
import * as extras from '@threlte/extras';
|
|
12
15
|
import { untrack } from 'svelte';
|
|
13
|
-
import { SvelteMap } from 'svelte/reactivity';
|
|
16
|
+
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
|
17
|
+
import { Color } from 'three';
|
|
14
18
|
import Bond from './Bond.svelte';
|
|
15
19
|
import { BONDING_STRATEGIES, compute_bond_transform } from './bonding';
|
|
16
20
|
import { CanvasTooltip } from './index';
|
|
@@ -32,21 +36,21 @@ $effect(() => {
|
|
|
32
36
|
frame_id = requestAnimationFrame(animate);
|
|
33
37
|
return () => cancelAnimationFrame(frame_id);
|
|
34
38
|
});
|
|
35
|
-
let { structure = undefined, base_structure = undefined, atom_radius = DEFAULTS.structure.atom_radius, same_size_atoms = false, camera_position = DEFAULTS.structure.camera_position, camera_projection = DEFAULTS.structure.camera_projection, rotation_damping = DEFAULTS.structure.rotation_damping, max_zoom = DEFAULTS.structure.max_zoom, min_zoom = DEFAULTS.structure.min_zoom, rotate_speed = DEFAULTS.structure.rotate_speed, zoom_speed = DEFAULTS.structure.zoom_speed, pan_speed = DEFAULTS.structure.pan_speed, zoom_to_cursor = DEFAULTS.structure.zoom_to_cursor, show_atoms = DEFAULTS.structure.show_atoms, show_bonds = DEFAULTS.structure.show_bonds, show_site_labels = DEFAULTS.structure.show_site_labels, show_site_indices = DEFAULTS.structure.show_site_indices, site_label_size = DEFAULTS.structure.site_label_size, site_label_offset = $bindable(DEFAULTS.structure.site_label_offset), site_label_bg_color = `color-mix(in srgb, #000000 0%, transparent)`, site_label_color = `#ffffff`, site_label_padding = 3, show_force_vectors = DEFAULTS.structure.show_force_vectors, force_scale = DEFAULTS.structure.force_scale, force_color = DEFAULTS.structure.force_color, gizmo = DEFAULTS.structure.show_gizmo, hovered_idx = $bindable(null), hovered_site = $bindable(null), float_fmt = `.3~f`, auto_rotate = DEFAULTS.structure.auto_rotate, bond_thickness = DEFAULTS.structure.bond_thickness, bond_color = DEFAULTS.structure.bond_color, bonding_strategy = DEFAULTS.structure.bonding_strategy, bonding_options = {}, fov = DEFAULTS.structure.fov, initial_zoom = DEFAULTS.structure.initial_zoom, ambient_light = DEFAULTS.structure.ambient_light, directional_light = DEFAULTS.structure.directional_light, sphere_segments = DEFAULTS.structure.sphere_segments, lattice_props = {}, atom_label, camera_is_moving = $bindable(false), width = 0, height = 0, measure_mode = `distance`, selected_sites = $bindable([]), measured_sites = $bindable([]), added_bonds = $bindable([]), removed_bonds = $bindable([]), selection_highlight_color = `#6cf0ff`,
|
|
39
|
+
let { structure = undefined, base_structure = undefined, atom_radius = DEFAULTS.structure.atom_radius, same_size_atoms = false, camera_position = DEFAULTS.structure.camera_position, camera_target = undefined, camera_projection = DEFAULTS.structure.camera_projection, rotation_damping = DEFAULTS.structure.rotation_damping, max_zoom = DEFAULTS.structure.max_zoom, min_zoom = DEFAULTS.structure.min_zoom, rotate_speed = DEFAULTS.structure.rotate_speed, zoom_speed = DEFAULTS.structure.zoom_speed, pan_speed = DEFAULTS.structure.pan_speed, zoom_to_cursor = DEFAULTS.structure.zoom_to_cursor, show_atoms = DEFAULTS.structure.show_atoms, show_bonds = DEFAULTS.structure.show_bonds, show_site_labels = DEFAULTS.structure.show_site_labels, show_site_indices = DEFAULTS.structure.show_site_indices, site_label_size = DEFAULTS.structure.site_label_size, site_label_offset = $bindable(DEFAULTS.structure.site_label_offset), site_label_bg_color = `color-mix(in srgb, #000000 0%, transparent)`, site_label_color = `#ffffff`, site_label_padding = 3, show_force_vectors = DEFAULTS.structure.show_force_vectors, force_scale = DEFAULTS.structure.force_scale, force_color = DEFAULTS.structure.force_color, gizmo = DEFAULTS.structure.show_gizmo, hovered_idx = $bindable(null), hovered_site = $bindable(null), float_fmt = `.3~f`, auto_rotate = DEFAULTS.structure.auto_rotate, bond_thickness = DEFAULTS.structure.bond_thickness, bond_color = DEFAULTS.structure.bond_color, bonding_strategy = DEFAULTS.structure.bonding_strategy, bonding_options = {}, fov = DEFAULTS.structure.fov, initial_zoom = DEFAULTS.structure.initial_zoom, ambient_light = DEFAULTS.structure.ambient_light, directional_light = DEFAULTS.structure.directional_light, sphere_segments = DEFAULTS.structure.sphere_segments, lattice_props = {}, atom_label, camera_is_moving = $bindable(false), width = 0, height = 0, measure_mode = `distance`, selected_sites = $bindable([]), measured_sites = $bindable([]), added_bonds = $bindable([]), removed_bonds = $bindable([]), selection_highlight_color = `#6cf0ff`,
|
|
36
40
|
// Active highlight group with different color
|
|
37
|
-
active_sites = $bindable([]), active_highlight_color = `var(--struct-active-highlight-color, #2563eb)`, rotation = DEFAULTS.structure.rotation, scene = $bindable(), camera = $bindable(), orbit_controls = $bindable(), rotation_target_ref = $bindable(), initial_computed_zoom = $bindable(), hidden_elements = $bindable(new
|
|
41
|
+
active_sites = $bindable([]), active_highlight_color = `var(--struct-active-highlight-color, #2563eb)`, rotation = DEFAULTS.structure.rotation, scene = $bindable(), camera = $bindable(), orbit_controls = $bindable(), rotation_target_ref = $bindable(), initial_computed_zoom = $bindable(), hidden_elements = $bindable(new SvelteSet()), hidden_prop_vals = $bindable(new SvelteSet()), element_radius_overrides = $bindable({}), site_radius_overrides = $bindable(new SvelteMap()), atom_color_config = {
|
|
38
42
|
mode: DEFAULTS.structure.atom_color_mode,
|
|
39
43
|
scale: DEFAULTS.structure.atom_color_scale,
|
|
40
44
|
scale_type: DEFAULTS.structure.atom_color_scale_type,
|
|
41
|
-
}, sym_data = null,
|
|
45
|
+
}, sym_data = null,
|
|
46
|
+
// Edit-atoms mode callbacks
|
|
47
|
+
on_sites_moved, on_operation_start, on_add_atom, add_atom_mode = $bindable(false), add_element = $bindable(`C`), cursor = $bindable(`default`), dragging_atoms = $bindable(false), volumetric_data = undefined, isosurface_settings = DEFAULT_ISOSURFACE_SETTINGS, } = $props();
|
|
42
48
|
const threlte = useThrelte();
|
|
43
49
|
$effect(() => {
|
|
44
50
|
scene = threlte.scene;
|
|
45
51
|
camera = threlte.camera.current;
|
|
46
52
|
if (threlte.renderer) {
|
|
47
|
-
Object.assign(threlte.renderer.domElement, {
|
|
48
|
-
__renderer: threlte.renderer,
|
|
49
|
-
});
|
|
53
|
+
Object.assign(threlte.renderer.domElement, { __renderer: threlte.renderer });
|
|
50
54
|
}
|
|
51
55
|
});
|
|
52
56
|
// Expose rotation target for external reset
|
|
@@ -63,11 +67,75 @@ $effect(() => {
|
|
|
63
67
|
});
|
|
64
68
|
let bond_pairs = $state([]);
|
|
65
69
|
let active_tooltip = $state(null);
|
|
70
|
+
let hovered_bond_key = $state(null);
|
|
71
|
+
// Cursor style for the canvas, derived from mode and hover state
|
|
72
|
+
let canvas_cursor = $derived.by(() => {
|
|
73
|
+
if (measure_mode === `edit-atoms` && add_atom_mode)
|
|
74
|
+
return `crosshair`;
|
|
75
|
+
if (hovered_idx != null) {
|
|
76
|
+
if (measure_mode === `edit-atoms`) {
|
|
77
|
+
const site = structure?.sites?.[hovered_idx];
|
|
78
|
+
if (site?.properties?.orig_site_idx != null)
|
|
79
|
+
return `not-allowed`;
|
|
80
|
+
return `pointer`;
|
|
81
|
+
}
|
|
82
|
+
return `pointer`;
|
|
83
|
+
}
|
|
84
|
+
return `default`;
|
|
85
|
+
});
|
|
86
|
+
// Desaturate a color by blending it toward gray (for ghosting image atoms in edit mode)
|
|
87
|
+
const gray = new Color(0x999999);
|
|
88
|
+
function desaturate(hex, amount = 0.4) {
|
|
89
|
+
return `#${new Color(hex ?? 0x999999).lerp(gray, amount).getHexString()}`;
|
|
90
|
+
}
|
|
91
|
+
// === Edit-atoms mode state ===
|
|
92
|
+
let transform_object = $state(undefined);
|
|
93
|
+
// Plain variable — only used imperatively in TransformControls drag handlers
|
|
94
|
+
let drag_start_centroid = null;
|
|
95
|
+
// Frozen centroid set on drag start. While non-null, the TransformControls mesh
|
|
96
|
+
// position stays at this fixed value so Svelte's reactive centroid updates (from
|
|
97
|
+
// PBC wrapping) don't fight TransformControls. Cleared on mouseUp so the mesh
|
|
98
|
+
// snaps to the new wrapped centroid.
|
|
99
|
+
let frozen_centroid = $state(null);
|
|
66
100
|
function get_bond_key(idx1, idx2) {
|
|
67
101
|
return idx1 < idx2 ? `${idx1}-${idx2}` : `${idx2}-${idx1}`;
|
|
68
102
|
}
|
|
103
|
+
// Toggle a bond between two atoms: cycles through add → remove → restore states
|
|
104
|
+
function toggle_bond(site_1, site_2) {
|
|
105
|
+
const idx_i = Math.min(site_1, site_2);
|
|
106
|
+
const idx_j = Math.max(site_1, site_2);
|
|
107
|
+
// added/removed pairs are stored sorted, so direct comparison works
|
|
108
|
+
const match = ([a, b]) => a === idx_i && b === idx_j;
|
|
109
|
+
const added_idx = added_bonds.findIndex(match);
|
|
110
|
+
if (added_idx >= 0) {
|
|
111
|
+
added_bonds = added_bonds.toSpliced(added_idx, 1);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const removed_idx = removed_bonds.findIndex(match);
|
|
115
|
+
if (removed_idx >= 0) {
|
|
116
|
+
removed_bonds = removed_bonds.toSpliced(removed_idx, 1);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// bond_pairs may not be sorted, so use get_bond_key for comparison
|
|
120
|
+
const key = `${idx_i}-${idx_j}`;
|
|
121
|
+
if (bond_pairs.some((bond) => get_bond_key(bond.site_idx_1, bond.site_idx_2) === key))
|
|
122
|
+
removed_bonds = [...removed_bonds, [idx_i, idx_j]];
|
|
123
|
+
else
|
|
124
|
+
added_bonds = [...added_bonds, [idx_i, idx_j]];
|
|
125
|
+
}
|
|
126
|
+
// Deduplicate clicks: when a highlight sphere and the underlying atom both
|
|
127
|
+
// intercept the same native click, only the first intersection should fire.
|
|
128
|
+
// All threlte intersection events from one click share the same nativeEvent ref.
|
|
129
|
+
let last_native_event = null;
|
|
69
130
|
function toggle_selection(site_index, evt) {
|
|
70
131
|
evt?.stopPropagation?.();
|
|
132
|
+
const native_event = evt
|
|
133
|
+
?.nativeEvent;
|
|
134
|
+
if (native_event instanceof Event) {
|
|
135
|
+
if (native_event === last_native_event)
|
|
136
|
+
return;
|
|
137
|
+
last_native_event = native_event;
|
|
138
|
+
}
|
|
71
139
|
if (measure_mode === `edit-bonds`) {
|
|
72
140
|
// In edit-bonds mode, select atoms to add/remove bonds between them
|
|
73
141
|
const new_sites = measured_sites.includes(site_index)
|
|
@@ -77,34 +145,35 @@ function toggle_selection(site_index, evt) {
|
|
|
77
145
|
selected_sites = new_sites;
|
|
78
146
|
// When two atoms are selected, toggle the bond between them
|
|
79
147
|
if (measured_sites.length === 2) {
|
|
80
|
-
|
|
81
|
-
const key = get_bond_key(idx_i, idx_j);
|
|
82
|
-
// Check if bond exists in calculated bonds (and not removed)
|
|
83
|
-
const calculated_exists = bond_pairs.some((bond) => get_bond_key(bond.site_idx_1, bond.site_idx_2) === key);
|
|
84
|
-
const is_removed = removed_bonds.some((pair) => get_bond_key(pair[0], pair[1]) === key);
|
|
85
|
-
const is_added = added_bonds.some((pair) => get_bond_key(pair[0], pair[1]) === key);
|
|
86
|
-
if (is_added) {
|
|
87
|
-
// Toggle off added bond
|
|
88
|
-
added_bonds = added_bonds.filter((pair) => get_bond_key(pair[0], pair[1]) !== key);
|
|
89
|
-
}
|
|
90
|
-
else if (calculated_exists && !is_removed) {
|
|
91
|
-
// Remove calculated bond
|
|
92
|
-
removed_bonds = [...removed_bonds, [idx_i, idx_j]];
|
|
93
|
-
}
|
|
94
|
-
else if (is_removed) {
|
|
95
|
-
// Restore calculated bond
|
|
96
|
-
removed_bonds = removed_bonds.filter((pair) => get_bond_key(pair[0], pair[1]) !== key);
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
// Add new bond
|
|
100
|
-
added_bonds = [...added_bonds, [idx_i, idx_j]];
|
|
101
|
-
}
|
|
102
|
-
// Reset selection after toggling bond
|
|
148
|
+
toggle_bond(measured_sites[0], measured_sites[1]);
|
|
103
149
|
measured_sites = [];
|
|
104
150
|
selected_sites = [];
|
|
105
151
|
}
|
|
106
152
|
return;
|
|
107
153
|
}
|
|
154
|
+
if (measure_mode === `edit-atoms`) {
|
|
155
|
+
// Block image atoms (detected by orig_site_idx property from PBC)
|
|
156
|
+
const site = structure?.sites?.[site_index];
|
|
157
|
+
if (site?.properties?.orig_site_idx != null)
|
|
158
|
+
return;
|
|
159
|
+
const is_selected = selected_sites.includes(site_index);
|
|
160
|
+
const is_shift = evt instanceof MouseEvent && evt.shiftKey;
|
|
161
|
+
// In edit-atoms mode, selected_sites and measured_sites always stay in sync
|
|
162
|
+
let new_sites;
|
|
163
|
+
if (is_shift) {
|
|
164
|
+
// Multi-select: toggle this site in/out of selection
|
|
165
|
+
new_sites = is_selected
|
|
166
|
+
? selected_sites.filter((idx) => idx !== site_index)
|
|
167
|
+
: [...selected_sites, site_index];
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
// Single-select: replace selection (or deselect if already selected)
|
|
171
|
+
new_sites = is_selected ? [] : [site_index];
|
|
172
|
+
}
|
|
173
|
+
selected_sites = new_sites;
|
|
174
|
+
measured_sites = new_sites;
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
108
177
|
if (!measured_sites.includes(site_index) &&
|
|
109
178
|
measured_sites.length >= measure.MAX_SELECTED_SITES) {
|
|
110
179
|
console.warn(`Selection size limit reached (${measure.MAX_SELECTED_SITES}). Deselect some sites first.`);
|
|
@@ -127,6 +196,9 @@ $effect(() => {
|
|
|
127
196
|
measured_sites = measured_sites.filter((idx) => idx >= 0 && idx < count);
|
|
128
197
|
});
|
|
129
198
|
});
|
|
199
|
+
$effect(() => {
|
|
200
|
+
cursor = canvas_cursor;
|
|
201
|
+
});
|
|
130
202
|
extras.interactivity();
|
|
131
203
|
$effect.pre(() => {
|
|
132
204
|
hovered_site = structure?.sites?.[hovered_idx ?? -1] ?? null;
|
|
@@ -159,7 +231,7 @@ $effect(() => {
|
|
|
159
231
|
computed_zoom = new_zoom;
|
|
160
232
|
});
|
|
161
233
|
$effect.pre(() => {
|
|
162
|
-
if (camera_position.every((
|
|
234
|
+
if (camera_position.every((val) => val === 0) && structure) {
|
|
163
235
|
const distance = Math.max(1, structure_size) * (60 / fov);
|
|
164
236
|
camera_position = [distance, distance * 0.3, distance * 0.8];
|
|
165
237
|
}
|
|
@@ -195,7 +267,8 @@ const calc_weighted_radius = (site) => {
|
|
|
195
267
|
let atom_data = $derived.by(() => {
|
|
196
268
|
if (!show_atoms || !structure?.sites)
|
|
197
269
|
return [];
|
|
198
|
-
|
|
270
|
+
const render_sites = merge_split_partial_sites(structure.sites, hidden_elements);
|
|
271
|
+
return render_sites.flatMap(({ site_idx, site, is_image_atom }) => {
|
|
199
272
|
const orig_idx = get_orig_site_idx(site, site_idx);
|
|
200
273
|
// Skip sites with hidden property values
|
|
201
274
|
const prop_val = property_colors?.values[orig_idx];
|
|
@@ -210,20 +283,25 @@ let atom_data = $derived.by(() => {
|
|
|
210
283
|
// Use property color if available (e.g. coordination number, Wyckoff position)
|
|
211
284
|
// Otherwise, each species gets its own element color (important for disordered sites)
|
|
212
285
|
const site_property_color = property_colors?.colors[orig_idx];
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
286
|
+
const visible_species = site.species.filter(({ element }) => !hidden_elements.has(element));
|
|
287
|
+
const slice_geometry = compute_slice_geometry(visible_species);
|
|
288
|
+
return slice_geometry.map((slice_data) => {
|
|
289
|
+
return {
|
|
290
|
+
site_idx,
|
|
291
|
+
element: slice_data.element,
|
|
292
|
+
occupancy: slice_data.occupancy,
|
|
293
|
+
position: site.xyz,
|
|
294
|
+
radius,
|
|
295
|
+
color: site_property_color ?? colors.element?.[slice_data.element],
|
|
296
|
+
has_partial_occupancy: slice_data.occupancy < 1,
|
|
297
|
+
start_phi: slice_data.start_phi,
|
|
298
|
+
end_phi: slice_data.end_phi,
|
|
299
|
+
phi_length: slice_data.phi_length,
|
|
300
|
+
render_start_cap: slice_data.render_start_cap,
|
|
301
|
+
render_end_cap: slice_data.render_end_cap,
|
|
302
|
+
is_image_atom,
|
|
303
|
+
};
|
|
304
|
+
});
|
|
227
305
|
});
|
|
228
306
|
});
|
|
229
307
|
let filtered_bond_pairs = $derived.by(() => {
|
|
@@ -313,28 +391,55 @@ const get_site_radius = (site, site_idx) => {
|
|
|
313
391
|
const base_radius = same_size_atoms ? 1 : override ?? calc_weighted_radius(site);
|
|
314
392
|
return base_radius * atom_radius;
|
|
315
393
|
};
|
|
316
|
-
|
|
317
|
-
|
|
394
|
+
// Interpolate between spin-down (#3498db blue) and spin-up (#e74c3c red)
|
|
395
|
+
// based on the z-component direction of a magnetic vector
|
|
396
|
+
function spin_direction_color(vec) {
|
|
397
|
+
const mag = Math.hypot(...vec);
|
|
398
|
+
const z_frac = mag > 1e-10 ? (vec[2] / mag + 1) / 2 : 0.5; // 0=down, 1=up
|
|
399
|
+
const red = Math.round(52 + (231 - 52) * z_frac);
|
|
400
|
+
const grn = Math.round(152 + (76 - 152) * z_frac);
|
|
401
|
+
const blu = Math.round(219 + (60 - 219) * z_frac);
|
|
402
|
+
return `#${red.toString(16).padStart(2, `0`)}${grn.toString(16).padStart(2, `0`)}${blu.toString(16).padStart(2, `0`)}`;
|
|
403
|
+
}
|
|
404
|
+
// Extract per-site vectors from force, magmom, or spin properties.
|
|
405
|
+
// For magmom/spin, color interpolates between spin-up (red) and spin-down (blue).
|
|
406
|
+
let force_data = $derived.by(() => {
|
|
407
|
+
if (!show_force_vectors || !structure?.sites)
|
|
408
|
+
return [];
|
|
409
|
+
return structure.sites
|
|
318
410
|
.map((site) => {
|
|
319
|
-
|
|
411
|
+
const info = get_site_vector_info(site);
|
|
412
|
+
if (!info)
|
|
320
413
|
return null;
|
|
321
|
-
|
|
414
|
+
let arrow_color;
|
|
415
|
+
if (info.key !== `force`) {
|
|
416
|
+
arrow_color = spin_direction_color(info.vec);
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
const majority_element = site.species.length > 0
|
|
420
|
+
? site.species.reduce((max, spec) => spec.occu > max.occu ? spec : max)
|
|
421
|
+
.element
|
|
422
|
+
: undefined;
|
|
423
|
+
arrow_color = (majority_element && colors.element?.[majority_element]) ||
|
|
424
|
+
force_color;
|
|
425
|
+
}
|
|
322
426
|
return {
|
|
323
427
|
position: site.xyz,
|
|
324
|
-
vector:
|
|
428
|
+
vector: info.vec,
|
|
325
429
|
scale: force_scale,
|
|
326
|
-
color:
|
|
430
|
+
color: arrow_color,
|
|
327
431
|
};
|
|
328
432
|
})
|
|
329
|
-
.filter((item) => item !== null)
|
|
330
|
-
|
|
433
|
+
.filter((item) => item !== null);
|
|
434
|
+
});
|
|
331
435
|
let instanced_atom_groups = $derived(Object.values(atom_data
|
|
332
436
|
.filter((atom) => !atom.has_partial_occupancy)
|
|
333
437
|
.reduce((groups, atom) => {
|
|
334
|
-
const { element, radius, color } = atom;
|
|
335
|
-
|
|
438
|
+
const { element, radius, color, is_image_atom } = atom;
|
|
439
|
+
// Separate image atoms into their own groups for distinct styling in edit-atoms mode
|
|
440
|
+
const key = `${element}-${format_num(radius, `.3~`)}-${color}-${is_image_atom ? `img` : `base`}`;
|
|
336
441
|
const bucket = groups[key] ||
|
|
337
|
-
(groups[key] = { element, radius, color, atoms: [] });
|
|
442
|
+
(groups[key] = { element, radius, color, is_image_atom, atoms: [] });
|
|
338
443
|
bucket.atoms.push(atom);
|
|
339
444
|
return groups;
|
|
340
445
|
}, {})));
|
|
@@ -375,7 +480,7 @@ let orbit_controls_props = $derived({
|
|
|
375
480
|
zoomToCursor: zoom_to_cursor,
|
|
376
481
|
enablePan: pan_speed > 0,
|
|
377
482
|
panSpeed: pan_speed,
|
|
378
|
-
target: rotation_target,
|
|
483
|
+
target: camera_target ?? rotation_target,
|
|
379
484
|
maxZoom: max_zoom,
|
|
380
485
|
minZoom: min_zoom,
|
|
381
486
|
autoRotate: Boolean(auto_rotate),
|
|
@@ -421,27 +526,17 @@ let measure_line_color = $derived.by(() => {
|
|
|
421
526
|
style:padding="{site_label_padding}px"
|
|
422
527
|
style:color={site_label_color}
|
|
423
528
|
>
|
|
424
|
-
{#if show_site_labels
|
|
425
|
-
{#if site.species.length === 1}
|
|
426
|
-
{site.species[0].element}-{site_idx + 1}
|
|
427
|
-
{:else}
|
|
428
|
-
{@html site.species.map((spec) =>
|
|
429
|
-
`${spec.element}<sub>${
|
|
430
|
-
format_num(spec.occu, `.3~`).replace(`0.`, `.`)
|
|
431
|
-
}</sub>`
|
|
432
|
-
).join(``)}-{
|
|
433
|
-
site_idx + 1
|
|
434
|
-
}
|
|
435
|
-
{/if}
|
|
436
|
-
{:else if show_site_labels}
|
|
529
|
+
{#if show_site_labels}
|
|
437
530
|
{#if site.species.length === 1}
|
|
438
|
-
{site.species[0].element}
|
|
531
|
+
{site.species[0].element}{#if show_site_indices}-{site_idx + 1}{/if}
|
|
439
532
|
{:else}
|
|
440
533
|
{@html site.species.map((spec) =>
|
|
441
534
|
`${spec.element}<sub>${
|
|
442
535
|
format_num(spec.occu, `.3~`).replace(`0.`, `.`)
|
|
443
536
|
}</sub>`
|
|
444
|
-
).join(``)}
|
|
537
|
+
).join(``)}{#if show_site_indices}-{
|
|
538
|
+
site_idx + 1
|
|
539
|
+
}{/if}
|
|
445
540
|
{/if}
|
|
446
541
|
{:else if show_site_indices}
|
|
447
542
|
{site_idx + 1}
|
|
@@ -487,31 +582,38 @@ let measure_line_color = $derived.by(() => {
|
|
|
487
582
|
{#if show_atoms}
|
|
488
583
|
<!-- Instanced rendering for full occupancy atoms -->
|
|
489
584
|
{#each instanced_atom_groups as
|
|
490
|
-
{ element, radius, color, atoms }
|
|
491
|
-
(`${element}-${radius}-${color}`)
|
|
585
|
+
{ element, radius, color, is_image_atom, atoms }
|
|
586
|
+
(`${element}-${radius}-${color}-${is_image_atom ? `img` : `base`}`)
|
|
492
587
|
}
|
|
588
|
+
{@const edit_mode_image = measure_mode === `edit-atoms` && is_image_atom}
|
|
493
589
|
<extras.InstancedMesh
|
|
494
|
-
key="{element}-{format_num(radius, `.3~`)}-{color}-{
|
|
590
|
+
key="{element}-{format_num(radius, `.3~`)}-{color}-{is_image_atom ? `img` : `base`}-{edit_mode_image}"
|
|
495
591
|
range={atoms.length}
|
|
496
592
|
frustumCulled={false}
|
|
497
593
|
>
|
|
498
594
|
<T.SphereGeometry args={[0.5, sphere_segments, sphere_segments]} />
|
|
499
|
-
<T.MeshStandardMaterial
|
|
595
|
+
<T.MeshStandardMaterial
|
|
596
|
+
color={edit_mode_image ? desaturate(color) : color}
|
|
597
|
+
opacity={edit_mode_image ? 0.5 : 1}
|
|
598
|
+
transparent={edit_mode_image}
|
|
599
|
+
/>
|
|
500
600
|
{#each atoms as atom (atom.site_idx)}
|
|
501
601
|
<extras.Instance
|
|
502
602
|
position={atom.position}
|
|
503
603
|
scale={atom.radius}
|
|
504
604
|
onpointerenter={() => {
|
|
605
|
+
if (edit_mode_image) return
|
|
505
606
|
hovered_idx = atom.site_idx
|
|
506
607
|
active_tooltip = `atom`
|
|
507
608
|
}}
|
|
508
609
|
onpointerleave={() => {
|
|
610
|
+
if (edit_mode_image) return
|
|
509
611
|
hovered_idx = null
|
|
510
612
|
active_tooltip = null
|
|
511
613
|
}}
|
|
512
614
|
onclick={(event: MouseEvent) => {
|
|
513
|
-
|
|
514
|
-
toggle_selection(site_idx, event)
|
|
615
|
+
if (edit_mode_image) return
|
|
616
|
+
toggle_selection(atom.site_idx, event)
|
|
515
617
|
}}
|
|
516
618
|
/>
|
|
517
619
|
{/each}
|
|
@@ -523,22 +625,29 @@ let measure_line_color = $derived.by(() => {
|
|
|
523
625
|
atom
|
|
524
626
|
(atom.site_idx + atom.element + atom.occupancy)
|
|
525
627
|
}
|
|
628
|
+
{@const partial_edit_image = measure_mode === `edit-atoms` && atom.is_image_atom}
|
|
629
|
+
{@const ghost_opacity = partial_edit_image ? 0.5 : 1}
|
|
526
630
|
<T.Group
|
|
527
631
|
position={atom.position}
|
|
528
632
|
scale={atom.radius}
|
|
529
633
|
onpointerenter={() => {
|
|
634
|
+
if (partial_edit_image) return
|
|
530
635
|
hovered_idx = atom.site_idx
|
|
531
636
|
active_tooltip = `atom`
|
|
532
637
|
}}
|
|
533
638
|
onpointerleave={() => {
|
|
639
|
+
if (partial_edit_image) return
|
|
534
640
|
hovered_idx = null
|
|
535
641
|
active_tooltip = null
|
|
536
642
|
}}
|
|
537
643
|
onclick={(event: MouseEvent) => {
|
|
538
|
-
|
|
539
|
-
toggle_selection(site_idx, event)
|
|
644
|
+
if (partial_edit_image) return
|
|
645
|
+
toggle_selection(atom.site_idx, event)
|
|
540
646
|
}}
|
|
541
647
|
>
|
|
648
|
+
{@const partial_color = partial_edit_image
|
|
649
|
+
? desaturate(atom.color)
|
|
650
|
+
: atom.color}
|
|
542
651
|
<T.Mesh>
|
|
543
652
|
<T.SphereGeometry
|
|
544
653
|
args={[
|
|
@@ -546,20 +655,50 @@ let measure_line_color = $derived.by(() => {
|
|
|
546
655
|
sphere_segments,
|
|
547
656
|
sphere_segments,
|
|
548
657
|
atom.start_phi,
|
|
549
|
-
|
|
658
|
+
atom.phi_length,
|
|
550
659
|
]}
|
|
551
660
|
/>
|
|
552
|
-
<T.MeshStandardMaterial
|
|
661
|
+
<T.MeshStandardMaterial
|
|
662
|
+
color={partial_color}
|
|
663
|
+
opacity={ghost_opacity}
|
|
664
|
+
transparent={partial_edit_image}
|
|
665
|
+
/>
|
|
553
666
|
</T.Mesh>
|
|
554
667
|
|
|
555
|
-
{#if atom.has_partial_occupancy}
|
|
668
|
+
{#if atom.has_partial_occupancy && atom.render_start_cap}
|
|
556
669
|
<T.Mesh rotation={[0, atom.start_phi, 0]}>
|
|
557
|
-
<T.CircleGeometry
|
|
558
|
-
|
|
670
|
+
<T.CircleGeometry
|
|
671
|
+
args={[
|
|
672
|
+
0.5,
|
|
673
|
+
sphere_segments,
|
|
674
|
+
PARTIAL_OCCUPANCY_CAP_ARC.start_cap_arc_start,
|
|
675
|
+
PARTIAL_OCCUPANCY_CAP_ARC.arc_length,
|
|
676
|
+
]}
|
|
677
|
+
/>
|
|
678
|
+
<T.MeshStandardMaterial
|
|
679
|
+
color={partial_color}
|
|
680
|
+
side={2}
|
|
681
|
+
opacity={ghost_opacity}
|
|
682
|
+
transparent={partial_edit_image}
|
|
683
|
+
/>
|
|
559
684
|
</T.Mesh>
|
|
685
|
+
{/if}
|
|
686
|
+
{#if atom.has_partial_occupancy && atom.render_end_cap}
|
|
560
687
|
<T.Mesh rotation={[0, atom.end_phi, 0]}>
|
|
561
|
-
<T.CircleGeometry
|
|
562
|
-
|
|
688
|
+
<T.CircleGeometry
|
|
689
|
+
args={[
|
|
690
|
+
0.5,
|
|
691
|
+
sphere_segments,
|
|
692
|
+
PARTIAL_OCCUPANCY_CAP_ARC.end_cap_arc_start,
|
|
693
|
+
PARTIAL_OCCUPANCY_CAP_ARC.arc_length,
|
|
694
|
+
]}
|
|
695
|
+
/>
|
|
696
|
+
<T.MeshStandardMaterial
|
|
697
|
+
color={partial_color}
|
|
698
|
+
side={2}
|
|
699
|
+
opacity={ghost_opacity}
|
|
700
|
+
transparent={partial_edit_image}
|
|
701
|
+
/>
|
|
563
702
|
</T.Mesh>
|
|
564
703
|
{/if}
|
|
565
704
|
</T.Group>
|
|
@@ -592,6 +731,41 @@ let measure_line_color = $derived.by(() => {
|
|
|
592
731
|
{/each}
|
|
593
732
|
{/if}
|
|
594
733
|
|
|
734
|
+
<!-- Clickable bond hit-test cylinders in edit-bonds mode -->
|
|
735
|
+
{#if measure_mode === `edit-bonds` && filtered_bond_pairs.length > 0}
|
|
736
|
+
{#each filtered_bond_pairs as
|
|
737
|
+
bond
|
|
738
|
+
(`bond-hit-${bond.site_idx_1}-${bond.site_idx_2}`)
|
|
739
|
+
}
|
|
740
|
+
{@const bond_key = get_bond_key(bond.site_idx_1, bond.site_idx_2)}
|
|
741
|
+
{@const is_hovered = hovered_bond_key === bond_key}
|
|
742
|
+
<T.Mesh
|
|
743
|
+
matrixAutoUpdate={false}
|
|
744
|
+
oncreate={(ref) => {
|
|
745
|
+
ref.matrix.fromArray(bond.transform_matrix)
|
|
746
|
+
ref.matrixWorldNeedsUpdate = true
|
|
747
|
+
}}
|
|
748
|
+
onclick={(event: MouseEvent) => {
|
|
749
|
+
event.stopPropagation()
|
|
750
|
+
toggle_bond(bond.site_idx_1, bond.site_idx_2)
|
|
751
|
+
measured_sites = []
|
|
752
|
+
selected_sites = []
|
|
753
|
+
hovered_bond_key = null
|
|
754
|
+
}}
|
|
755
|
+
onpointerenter={() => (hovered_bond_key = bond_key)}
|
|
756
|
+
onpointerleave={() => (hovered_bond_key = null)}
|
|
757
|
+
>
|
|
758
|
+
<T.CylinderGeometry args={[bond_thickness * 3, bond_thickness * 3, 1, 6]} />
|
|
759
|
+
<T.MeshBasicMaterial
|
|
760
|
+
transparent
|
|
761
|
+
opacity={is_hovered ? 0.25 : 0}
|
|
762
|
+
color={is_hovered ? `#ff4444` : `white`}
|
|
763
|
+
depthWrite={false}
|
|
764
|
+
/>
|
|
765
|
+
</T.Mesh>
|
|
766
|
+
{/each}
|
|
767
|
+
{/if}
|
|
768
|
+
|
|
595
769
|
<!-- highlight hovered, active and selected sites -->
|
|
596
770
|
{#each [
|
|
597
771
|
{
|
|
@@ -648,8 +822,9 @@ let measure_line_color = $derived.by(() => {
|
|
|
648
822
|
{/if}
|
|
649
823
|
{/each}
|
|
650
824
|
|
|
651
|
-
<!-- selection order labels (1, 2, 3, ...) for measured sites -->
|
|
652
|
-
{#if structure?.sites && (measured_sites?.length ?? 0) > 0
|
|
825
|
+
<!-- selection order labels (1, 2, 3, ...) for measured sites (hidden in edit-atoms mode) -->
|
|
826
|
+
{#if structure?.sites && (measured_sites?.length ?? 0) > 0 &&
|
|
827
|
+
measure_mode !== `edit-atoms`}
|
|
653
828
|
{#each measured_sites as site_index, loop_idx (site_index)}
|
|
654
829
|
{@const site = structure.sites[site_index]}
|
|
655
830
|
{#if site}
|
|
@@ -665,8 +840,36 @@ let measure_line_color = $derived.by(() => {
|
|
|
665
840
|
|
|
666
841
|
<!-- hovered site tooltip -->
|
|
667
842
|
{#if hovered_site && !camera_is_moving && active_tooltip === `atom`}
|
|
668
|
-
{@const abc = hovered_site.abc.map((
|
|
669
|
-
|
|
843
|
+
{@const abc = hovered_site.abc.map((val) => format_num(val, float_fmt)).join(
|
|
844
|
+
`, `,
|
|
845
|
+
)}
|
|
846
|
+
{@const xyz = hovered_site.xyz.map((val) => format_num(val, float_fmt)).join(
|
|
847
|
+
`, `,
|
|
848
|
+
)}
|
|
849
|
+
{@const bond_neighbors = (() => {
|
|
850
|
+
if (hovered_idx == null || !structure?.sites) return []
|
|
851
|
+
return filtered_bond_pairs
|
|
852
|
+
.filter((b) =>
|
|
853
|
+
b.site_idx_1 === hovered_idx || b.site_idx_2 === hovered_idx
|
|
854
|
+
)
|
|
855
|
+
.map((b) => {
|
|
856
|
+
const neighbor_idx = b.site_idx_1 === hovered_idx
|
|
857
|
+
? b.site_idx_2
|
|
858
|
+
: b.site_idx_1
|
|
859
|
+
return structure.sites[neighbor_idx]?.species[0]?.element ?? `?`
|
|
860
|
+
})
|
|
861
|
+
})()}
|
|
862
|
+
{@const bond_summary = (() => {
|
|
863
|
+
if (bond_neighbors.length === 0) return ``
|
|
864
|
+
const counts: Record<string, number> = {}
|
|
865
|
+
for (const elem of bond_neighbors) {
|
|
866
|
+
counts[elem] = (counts[elem] ?? 0) + 1
|
|
867
|
+
}
|
|
868
|
+
const parts = Object.entries(counts)
|
|
869
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
870
|
+
.map(([elem, count]) => `${elem}: ${count}`)
|
|
871
|
+
return ` (${parts.join(`, `)})`
|
|
872
|
+
})()}
|
|
670
873
|
<CanvasTooltip position={hovered_site.xyz}>
|
|
671
874
|
<!-- Element symbols with occupancies for disordered sites -->
|
|
672
875
|
<div class="elements">
|
|
@@ -694,6 +897,9 @@ let measure_line_color = $derived.by(() => {
|
|
|
694
897
|
</div>
|
|
695
898
|
<div class="coordinates fractional">abc: ({abc})</div>
|
|
696
899
|
<div class="coordinates cartesian">xyz: ({xyz}) Å</div>
|
|
900
|
+
{#if bond_neighbors.length > 0}
|
|
901
|
+
<div class="coordinates">Bonds: {bond_neighbors.length}{bond_summary}</div>
|
|
902
|
+
{/if}
|
|
697
903
|
</CanvasTooltip>
|
|
698
904
|
{/if}
|
|
699
905
|
|
|
@@ -701,6 +907,91 @@ let measure_line_color = $derived.by(() => {
|
|
|
701
907
|
<Lattice matrix={visual_lattice.matrix} {...lattice_props} />
|
|
702
908
|
{/if}
|
|
703
909
|
|
|
910
|
+
<!-- TransformControls for editing atoms in edit-atoms mode -->
|
|
911
|
+
{#if measure_mode === `edit-atoms` && selected_sites.length > 0 &&
|
|
912
|
+
structure?.sites}
|
|
913
|
+
{@const selected_atoms = selected_sites
|
|
914
|
+
.map((idx) => structure?.sites?.[idx])
|
|
915
|
+
.filter((site): site is Site => site != null)}
|
|
916
|
+
{#if selected_atoms.length > 0}
|
|
917
|
+
{@const avg = (dim: number) =>
|
|
918
|
+
selected_atoms.reduce((sum, atom) => sum + atom.xyz[dim], 0) /
|
|
919
|
+
selected_atoms.length}
|
|
920
|
+
{@const centroid = [avg(0), avg(1), avg(2)] as Vec3}
|
|
921
|
+
<!-- Invisible mesh at centroid for TransformControls to manipulate.
|
|
922
|
+
During drag, use frozen_centroid so Svelte doesn't override TransformControls
|
|
923
|
+
with the wrapped centroid (which jumps on PBC boundary crossings). -->
|
|
924
|
+
<T.Mesh
|
|
925
|
+
position={frozen_centroid ?? centroid}
|
|
926
|
+
bind:ref={transform_object}
|
|
927
|
+
>
|
|
928
|
+
<T.SphereGeometry args={[0.01, 4, 4]} />
|
|
929
|
+
<T.MeshBasicMaterial transparent opacity={0} />
|
|
930
|
+
</T.Mesh>
|
|
931
|
+
<extras.TransformControls
|
|
932
|
+
object={transform_object}
|
|
933
|
+
translationSnap={0.1}
|
|
934
|
+
size={1.2}
|
|
935
|
+
space="world"
|
|
936
|
+
onobjectChange={() => {
|
|
937
|
+
if (!transform_object?.position || !drag_start_centroid) return
|
|
938
|
+
const { x: tx, y: ty, z: tz } = transform_object.position
|
|
939
|
+
const delta: Vec3 = [
|
|
940
|
+
tx - drag_start_centroid[0],
|
|
941
|
+
ty - drag_start_centroid[1],
|
|
942
|
+
tz - drag_start_centroid[2],
|
|
943
|
+
]
|
|
944
|
+
// Update reference point so deltas are incremental, not cumulative.
|
|
945
|
+
// Without this, each frame compounds: sites already moved by previous
|
|
946
|
+
// delta get the full cumulative delta re-applied.
|
|
947
|
+
drag_start_centroid = [tx, ty, tz]
|
|
948
|
+
on_sites_moved?.(selected_sites, delta)
|
|
949
|
+
}}
|
|
950
|
+
onmouseDown={() => {
|
|
951
|
+
dragging_atoms = true
|
|
952
|
+
drag_start_centroid = frozen_centroid = [...centroid] as Vec3
|
|
953
|
+
on_operation_start?.()
|
|
954
|
+
}}
|
|
955
|
+
onmouseUp={() => {
|
|
956
|
+
dragging_atoms = false
|
|
957
|
+
frozen_centroid = null
|
|
958
|
+
drag_start_centroid = null
|
|
959
|
+
}}
|
|
960
|
+
/>
|
|
961
|
+
{/if}
|
|
962
|
+
{/if}
|
|
963
|
+
|
|
964
|
+
<!-- Invisible plane for click-to-place atom in add-atom mode -->
|
|
965
|
+
<!-- Uses onBeforeRender to orient normal toward camera so raycasts always hit -->
|
|
966
|
+
{#if measure_mode === `edit-atoms` && add_atom_mode}
|
|
967
|
+
{@const center = rotation_target ?? [0, 0, 0]}
|
|
968
|
+
<T.Mesh
|
|
969
|
+
position={center}
|
|
970
|
+
onBeforeRender={(mesh: Mesh) => {
|
|
971
|
+
if (camera) {
|
|
972
|
+
mesh.lookAt(camera.position)
|
|
973
|
+
}
|
|
974
|
+
}}
|
|
975
|
+
onclick={(event: { point: { x: number; y: number; z: number } }) => {
|
|
976
|
+
const { x, y, z } = event.point
|
|
977
|
+
on_add_atom?.([x, y, z] as Vec3, add_element as ElementSymbol)
|
|
978
|
+
}}
|
|
979
|
+
>
|
|
980
|
+
<T.PlaneGeometry
|
|
981
|
+
args={[
|
|
982
|
+
Math.max(200, structure_size * 4),
|
|
983
|
+
Math.max(200, structure_size * 4),
|
|
984
|
+
]}
|
|
985
|
+
/>
|
|
986
|
+
<T.MeshBasicMaterial transparent opacity={0} side={2} depthWrite={false} />
|
|
987
|
+
</T.Mesh>
|
|
988
|
+
{/if}
|
|
989
|
+
|
|
990
|
+
<!-- Isosurface rendering from volumetric data (CHGCAR, .cube files) -->
|
|
991
|
+
{#if volumetric_data && isosurface_settings}
|
|
992
|
+
<Isosurface volume={volumetric_data} settings={isosurface_settings} />
|
|
993
|
+
{/if}
|
|
994
|
+
|
|
704
995
|
<!-- Measurement overlays for measured sites -->
|
|
705
996
|
{#if structure?.sites && (measured_sites?.length ?? 0) > 0}
|
|
706
997
|
{#if measure_mode === `distance`}
|
|
@@ -741,12 +1032,12 @@ let measure_line_color = $derived.by(() => {
|
|
|
741
1032
|
{:else if measure_mode === `angle` && measured_sites.length >= 3}
|
|
742
1033
|
{#each measured_sites as idx_center (idx_center)}
|
|
743
1034
|
{@const center = structure.sites[idx_center]}
|
|
744
|
-
{#each measured_sites.filter((
|
|
1035
|
+
{#each measured_sites.filter((idx) => idx !== idx_center) as
|
|
745
1036
|
idx_a,
|
|
746
1037
|
loop_idx
|
|
747
1038
|
(idx_center + `-` + idx_a)
|
|
748
1039
|
}
|
|
749
|
-
{#each measured_sites.filter((
|
|
1040
|
+
{#each measured_sites.filter((idx) => idx !== idx_center).slice(loop_idx + 1) as
|
|
750
1041
|
idx_b
|
|
751
1042
|
(idx_center + `-` + idx_a + `-` + idx_b)
|
|
752
1043
|
}
|