matterviz 0.4.1 → 0.4.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/EmptyState.svelte +10 -4
- package/dist/FilePicker.svelte +12 -14
- package/dist/Icon.svelte +7 -4
- package/dist/MillerIndexInput.svelte +2 -2
- package/dist/app.css +53 -0
- package/dist/brillouin/BrillouinZone.svelte +75 -85
- package/dist/brillouin/BrillouinZone.svelte.d.ts +5 -7
- package/dist/brillouin/BrillouinZoneControls.svelte +3 -6
- package/dist/brillouin/BrillouinZoneExportPane.svelte +18 -14
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +6 -10
- package/dist/brillouin/BrillouinZoneScene.svelte +26 -39
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram.svelte +33 -38
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +62 -74
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +511 -547
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +1 -1
- package/dist/chempot-diagram/async-compute.svelte.js +1 -1
- package/dist/chempot-diagram/controls-state.svelte.js +1 -1
- package/dist/colors/index.d.ts +0 -4
- package/dist/colors/index.js +19 -20
- package/dist/composition/BarChart.svelte +37 -31
- package/dist/composition/BubbleChart.svelte +17 -17
- package/dist/composition/Composition.svelte +7 -11
- package/dist/composition/Formula.svelte +18 -25
- package/dist/composition/FormulaFilter.svelte +92 -95
- package/dist/composition/PieChart.svelte +55 -41
- package/dist/composition/format.js +1 -1
- package/dist/composition/index.d.ts +2 -0
- package/dist/composition/index.js +14 -0
- package/dist/composition/parse.d.ts +2 -2
- package/dist/composition/parse.js +11 -10
- package/dist/constants.js +11 -9
- package/dist/convex-hull/ConvexHull.svelte +12 -15
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +93 -92
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +109 -140
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +75 -94
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullChrome.svelte +36 -40
- package/dist/convex-hull/ConvexHullControls.svelte +46 -48
- package/dist/convex-hull/ConvexHullStats.svelte +193 -195
- package/dist/convex-hull/ConvexHullTooltip.svelte +5 -12
- package/dist/convex-hull/GasPressureControls.svelte +5 -12
- package/dist/convex-hull/StructurePopup.svelte +17 -21
- package/dist/convex-hull/StructurePopup.svelte.d.ts +2 -0
- package/dist/convex-hull/TemperatureSlider.svelte +4 -10
- package/dist/convex-hull/canvas-interactions.svelte.js +2 -1
- package/dist/convex-hull/helpers.d.ts +8 -10
- package/dist/convex-hull/helpers.js +36 -36
- package/dist/convex-hull/hull-state.svelte.d.ts +1 -1
- package/dist/convex-hull/thermodynamics.js +18 -17
- package/dist/coordination/CoordinationBarPlot.svelte +38 -33
- package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
- package/dist/coordination/calc-coordination.d.ts +1 -1
- package/dist/coordination/calc-coordination.js +28 -25
- package/dist/element/BohrAtom.svelte +5 -8
- package/dist/element/ElementHeading.svelte +4 -1
- package/dist/element/ElementPhoto.svelte +11 -9
- package/dist/element/ElementStats.svelte +12 -10
- package/dist/element/ElementTile.svelte +24 -29
- package/dist/element/Nucleus.svelte +4 -2
- package/dist/element/data.d.ts +1 -2
- package/dist/element/helpers.d.ts +4 -0
- package/dist/element/helpers.js +18 -0
- package/dist/element/index.d.ts +1 -4
- package/dist/element/index.js +3 -18
- package/dist/feedback/DragOverlay.svelte +5 -5
- package/dist/feedback/DragOverlay.svelte.d.ts +2 -2
- package/dist/feedback/StatusMessage.svelte +7 -4
- package/dist/fermi-surface/FermiSlice.svelte +21 -18
- package/dist/fermi-surface/FermiSurface.svelte +14 -22
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +5 -7
- package/dist/fermi-surface/FermiSurfaceControls.svelte +6 -20
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +37 -64
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte +9 -6
- package/dist/fermi-surface/compute.js +3 -3
- package/dist/fermi-surface/parse.js +22 -24
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +109 -148
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +2 -2
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +9 -7
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
- package/dist/heatmap-matrix/index.d.ts +5 -0
- package/dist/icons.d.ts +4 -0
- package/dist/icons.js +4 -0
- package/dist/io/ExportPane.svelte +10 -6
- package/dist/io/decompress.d.ts +2 -1
- package/dist/io/decompress.js +22 -35
- package/dist/io/export.d.ts +4 -0
- package/dist/io/export.js +5 -1
- package/dist/io/fetch.d.ts +0 -3
- package/dist/io/fetch.js +0 -11
- package/dist/io/index.d.ts +0 -1
- package/dist/io/index.js +2 -9
- package/dist/io/is-binary.d.ts +9 -0
- package/dist/io/is-binary.js +45 -1
- package/dist/io/url-drop.js +13 -25
- package/dist/isosurface/Isosurface.svelte +17 -23
- package/dist/isosurface/IsosurfaceControls.svelte +37 -42
- package/dist/isosurface/parse-vaspwave.d.ts +3 -0
- package/dist/isosurface/parse-vaspwave.js +138 -0
- package/dist/isosurface/parse.js +6 -6
- package/dist/labels.d.ts +6 -2
- package/dist/labels.js +43 -13
- package/dist/layout/FullscreenButton.svelte +2 -1
- package/dist/layout/FullscreenToggle.svelte +10 -4
- package/dist/layout/InfoCard.svelte +7 -18
- package/dist/layout/InfoTag.svelte +1 -3
- package/dist/layout/NumberRangeInput.svelte +46 -0
- package/dist/layout/NumberRangeInput.svelte.d.ts +12 -0
- package/dist/layout/PropertyFilter.svelte +2 -6
- package/dist/layout/SettingsSection.svelte +21 -17
- package/dist/layout/SubpageGrid.svelte +3 -1
- package/dist/layout/ViewerChrome.svelte +9 -10
- package/dist/layout/ViewerChrome.svelte.d.ts +2 -1
- package/dist/layout/fullscreen.js +3 -5
- package/dist/layout/fullscreen.svelte.js +1 -2
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/index.js +1 -0
- package/dist/layout/json-tree/JsonNode.svelte +8 -11
- package/dist/layout/json-tree/JsonTree.svelte +53 -77
- package/dist/layout/json-tree/JsonValue.svelte +7 -15
- package/dist/layout/json-tree/utils.js +14 -23
- package/dist/math.d.ts +2 -0
- package/dist/math.js +19 -0
- package/dist/overlays/ContextMenu.svelte +4 -2
- package/dist/overlays/CopyButton.svelte +11 -5
- package/dist/overlays/DragControlTab.svelte +6 -1
- package/dist/overlays/DraggablePane.svelte +81 -26
- package/dist/overlays/DraggablePane.svelte.d.ts +1 -0
- package/dist/overlays/GlassChip.svelte +30 -0
- package/dist/overlays/GlassChip.svelte.d.ts +8 -0
- package/dist/overlays/InfoPaneCards.svelte +11 -23
- package/dist/overlays/index.d.ts +7 -0
- package/dist/overlays/index.js +26 -0
- package/dist/overlays/portal.d.ts +2 -0
- package/dist/overlays/portal.js +14 -0
- package/dist/periodic-table/PeriodicTable.svelte +113 -145
- package/dist/periodic-table/PeriodicTable.svelte.d.ts +5 -3
- package/dist/periodic-table/PeriodicTableControls.svelte +11 -63
- package/dist/periodic-table/TableInset.svelte +9 -4
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +50 -71
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -8
- package/dist/phase-diagram/PhaseDiagramControls.svelte +70 -94
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +2 -1
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +12 -12
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +54 -58
- package/dist/phase-diagram/TdbInfoPanel.svelte +21 -15
- package/dist/phase-diagram/build-diagram.js +3 -3
- package/dist/phase-diagram/diagram-input.d.ts +1 -2
- package/dist/phase-diagram/parse.js +15 -15
- package/dist/phase-diagram/svg-to-diagram.js +21 -15
- package/dist/phase-diagram/utils.js +2 -3
- package/dist/plot/bar/BarPlot.svelte +430 -391
- package/dist/plot/bar/BarPlot.svelte.d.ts +2 -0
- package/dist/plot/bar/BarPlotControls.svelte +1 -3
- package/dist/plot/bar/BarPlotControls.svelte.d.ts +4 -6
- package/dist/plot/bar/SpacegroupBarPlot.svelte +29 -36
- package/dist/plot/bar/data.d.ts +5 -4
- package/dist/plot/bar/data.js +2 -1
- package/dist/plot/box/BoxPlot.svelte +301 -209
- package/dist/plot/box/BoxPlot.svelte.d.ts +2 -0
- package/dist/plot/box/Violin.svelte.d.ts +1 -0
- package/dist/plot/box/box-plot.js +1 -1
- package/dist/plot/core/auto-place.js +1 -1
- package/dist/plot/core/axis-utils.d.ts +12 -7
- package/dist/plot/core/axis-utils.js +48 -48
- package/dist/plot/core/components/AxisLabel.svelte +82 -20
- package/dist/plot/core/components/AxisLabel.svelte.d.ts +1 -0
- package/dist/plot/core/components/ColorBar.svelte +101 -106
- package/dist/plot/core/components/ColorScaleSelect.svelte +1 -1
- package/dist/plot/core/components/FillArea.svelte +10 -17
- package/dist/plot/core/components/HierarchyControls.svelte +249 -0
- package/dist/plot/{sunburst/SunburstControls.svelte.d.ts → core/components/HierarchyControls.svelte.d.ts} +12 -8
- package/dist/plot/core/components/InteractiveAxisLabel.svelte +1 -1
- package/dist/plot/core/components/InteractiveAxisLabel.svelte.d.ts +1 -1
- package/dist/plot/core/components/Line.svelte +18 -9
- package/dist/plot/core/components/Line.svelte.d.ts +2 -0
- package/dist/plot/core/components/PlotAxis.svelte +40 -17
- package/dist/plot/core/components/PlotControls.svelte +65 -41
- package/dist/plot/core/components/PlotLegend.svelte +31 -35
- package/dist/plot/core/components/PlotMarginals.svelte +616 -0
- package/dist/plot/core/components/PlotMarginals.svelte.d.ts +15 -0
- package/dist/plot/core/components/PlotTooltip.svelte +1 -1
- package/dist/plot/core/components/PortalSelect.svelte +4 -8
- package/dist/plot/core/components/ReferenceLine.svelte +27 -24
- package/dist/plot/core/components/ReferenceLine3D.svelte +54 -57
- package/dist/plot/core/components/ReferencePlane.svelte +7 -8
- package/dist/plot/core/components/ZeroLines.svelte +4 -8
- package/dist/plot/core/components/ZoomRect.svelte +1 -2
- package/dist/plot/core/components/index.d.ts +1 -0
- package/dist/plot/core/components/index.js +1 -0
- package/dist/plot/core/data-cleaning-signal.d.ts +46 -0
- package/dist/plot/core/data-cleaning-signal.js +467 -0
- package/dist/plot/core/data-cleaning.d.ts +2 -42
- package/dist/plot/core/data-cleaning.js +27 -474
- package/dist/plot/core/data-transform.js +9 -5
- package/dist/plot/core/fill-utils.d.ts +3 -1
- package/dist/plot/core/fill-utils.js +28 -6
- package/dist/plot/core/index.d.ts +1 -0
- package/dist/plot/core/index.js +1 -0
- package/dist/plot/core/interactions.js +0 -2
- package/dist/plot/core/layout.d.ts +3 -1
- package/dist/plot/core/layout.js +22 -13
- package/dist/plot/core/marginals.d.ts +170 -0
- package/dist/plot/core/marginals.js +436 -0
- package/dist/plot/core/reference-line.js +1 -1
- package/dist/plot/core/scales.d.ts +23 -0
- package/dist/plot/core/scales.js +39 -13
- package/dist/plot/core/types/fills.d.ts +101 -0
- package/dist/plot/core/types/fills.js +15 -0
- package/dist/plot/core/types/plot-3d.d.ts +87 -0
- package/dist/plot/core/types/plot-3d.js +4 -0
- package/dist/plot/core/types/reference-lines.d.ts +140 -0
- package/dist/plot/core/types/reference-lines.js +9 -0
- package/dist/plot/core/types.d.ts +29 -324
- package/dist/plot/core/types.js +23 -20
- package/dist/plot/core/utils/hierarchy-chart.d.ts +51 -0
- package/dist/plot/core/utils/hierarchy-chart.js +179 -0
- package/dist/plot/core/utils/hierarchy-labels.d.ts +20 -0
- package/dist/plot/core/utils/hierarchy-labels.js +99 -0
- package/dist/plot/core/utils/label-placement.js +2 -2
- package/dist/plot/histogram/Histogram.svelte +304 -325
- package/dist/plot/histogram/Histogram.svelte.d.ts +7 -7
- package/dist/plot/histogram/HistogramControls.svelte +30 -28
- package/dist/plot/histogram/histogram.d.ts +31 -0
- package/dist/plot/histogram/histogram.js +70 -0
- package/dist/plot/histogram/index.d.ts +1 -0
- package/dist/plot/histogram/index.js +1 -0
- package/dist/plot/index.d.ts +1 -0
- package/dist/plot/index.js +1 -0
- package/dist/plot/sankey/Sankey.svelte +78 -69
- package/dist/plot/sankey/Sankey.svelte.d.ts +2 -2
- package/dist/plot/sankey/SankeyControls.svelte +22 -16
- package/dist/plot/scatter/BinnedScatterPlot.svelte +263 -109
- package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +7 -0
- package/dist/plot/scatter/ElementScatter.svelte +4 -1
- package/dist/plot/scatter/ScatterPlot.svelte +477 -385
- package/dist/plot/scatter/ScatterPlot.svelte.d.ts +2 -0
- package/dist/plot/scatter/ScatterPlotControls.svelte +18 -19
- package/dist/plot/scatter/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/scatter/ScatterPoint.svelte +16 -9
- package/dist/plot/scatter/binned-scatter-types.d.ts +3 -2
- package/dist/plot/scatter/binned-scatter-types.js +6 -1
- package/dist/plot/scatter/scatter-data.d.ts +1 -1
- package/dist/plot/scatter/scatter-data.js +8 -7
- package/dist/plot/scatter-3d/ScatterPlot3D.svelte +25 -34
- package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +8 -5
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +113 -116
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/scatter-3d/Surface3D.svelte +4 -13
- package/dist/plot/sunburst/Sunburst.svelte +251 -329
- package/dist/plot/sunburst/Sunburst.svelte.d.ts +2 -2
- package/dist/plot/sunburst/index.d.ts +1 -1
- package/dist/plot/sunburst/index.js +2 -1
- package/dist/plot/sunburst/render.d.ts +2 -2
- package/dist/plot/sunburst/render.js +47 -22
- package/dist/plot/sunburst/sunburst.d.ts +5 -1
- package/dist/plot/sunburst/sunburst.js +10 -3
- package/dist/plot/treemap/Treemap.svelte +907 -0
- package/dist/plot/treemap/Treemap.svelte.d.ts +84 -0
- package/dist/plot/treemap/index.d.ts +3 -0
- package/dist/plot/treemap/index.js +4 -0
- package/dist/plot/treemap/treemap.d.ts +26 -0
- package/dist/plot/treemap/treemap.js +99 -0
- package/dist/rdf/RdfPlot.svelte +22 -26
- package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
- package/dist/rdf/calc-rdf.js +4 -8
- package/dist/sanitize.js +1 -1
- package/dist/scene/bind-renderer.svelte.js +14 -6
- package/dist/settings.d.ts +19 -9
- package/dist/settings.js +77 -50
- package/dist/spectral/Bands.svelte +223 -230
- package/dist/spectral/BandsAndDos.svelte +11 -9
- package/dist/spectral/BrillouinBandsDos.svelte +19 -27
- package/dist/spectral/Dos.svelte +70 -73
- package/dist/spectral/helpers.d.ts +3 -3
- package/dist/spectral/helpers.js +25 -35
- package/dist/state.svelte.js +5 -18
- package/dist/structure/Arrow.svelte +5 -18
- package/dist/structure/ArrowInstances.svelte +160 -0
- package/dist/structure/ArrowInstances.svelte.d.ts +16 -0
- package/dist/structure/AtomLegend.svelte +115 -75
- package/dist/structure/AtomLegend.svelte.d.ts +1 -1
- package/dist/structure/Bond.svelte +31 -23
- package/dist/structure/CanvasTooltip.svelte +15 -3
- package/dist/structure/CellSelect.svelte +11 -26
- package/dist/structure/Cylinder.svelte +24 -40
- package/dist/structure/InstancedAtoms.svelte +114 -0
- package/dist/structure/InstancedAtoms.svelte.d.ts +15 -0
- package/dist/structure/Lattice.svelte +85 -149
- package/dist/structure/SiteLabels.svelte +122 -0
- package/dist/structure/SiteLabels.svelte.d.ts +18 -0
- package/dist/structure/Structure.svelte +473 -419
- package/dist/structure/Structure.svelte.d.ts +4 -2
- package/dist/structure/StructureCarousel.svelte +630 -0
- package/dist/structure/StructureCarousel.svelte.d.ts +19 -0
- package/dist/structure/StructureControls.svelte +157 -399
- package/dist/structure/StructureControls.svelte.d.ts +1 -1
- package/dist/structure/StructureExportPane.svelte +17 -21
- package/dist/structure/StructureInfoPane.svelte +72 -66
- package/dist/structure/StructureInfoPane.svelte.d.ts +1 -1
- package/dist/structure/StructureScene.svelte +467 -476
- package/dist/structure/StructureScene.svelte.d.ts +3 -1
- package/dist/structure/StructureViewport.svelte +386 -0
- package/dist/structure/StructureViewport.svelte.d.ts +63 -0
- package/dist/structure/atom-label-placement.d.ts +16 -12
- package/dist/structure/atom-label-placement.js +70 -37
- package/dist/structure/atom-properties.d.ts +2 -0
- package/dist/structure/atom-properties.js +104 -38
- package/dist/structure/bonding.d.ts +10 -3
- package/dist/structure/bonding.js +84 -28
- package/dist/structure/edit-history.d.ts +6 -0
- package/dist/structure/edit-history.js +27 -0
- package/dist/structure/export.d.ts +1 -0
- package/dist/structure/export.js +39 -31
- package/dist/structure/format-detect.d.ts +1 -0
- package/dist/structure/format-detect.js +22 -0
- package/dist/structure/geometry.d.ts +9 -0
- package/dist/structure/geometry.js +27 -0
- package/dist/structure/index.d.ts +16 -1
- package/dist/structure/index.js +13 -2
- package/dist/structure/measure.d.ts +1 -1
- package/dist/structure/measure.js +6 -12
- package/dist/structure/parse.d.ts +2 -1
- package/dist/structure/parse.js +243 -222
- package/dist/structure/partial-occupancy.js +4 -2
- package/dist/structure/pbc.js +78 -98
- package/dist/structure/polyhedra.d.ts +0 -1
- package/dist/structure/polyhedra.js +3 -18
- package/dist/structure/supercell.d.ts +1 -2
- package/dist/structure/supercell.js +7 -13
- package/dist/symmetry/SymmetryElements.svelte +11 -15
- package/dist/symmetry/SymmetryStats.svelte +56 -68
- package/dist/symmetry/WyckoffTable.svelte +5 -24
- package/dist/symmetry/cell-transform.js +1 -0
- package/dist/symmetry/index.d.ts +1 -0
- package/dist/symmetry/index.js +8 -2
- package/dist/symmetry/spacegroups.js +3 -2
- package/dist/table/HeatmapTable.svelte +1102 -365
- package/dist/table/HeatmapTable.svelte.d.ts +12 -2
- package/dist/table/ToggleMenu.svelte +5 -12
- package/dist/table/index.d.ts +21 -6
- package/dist/table/index.js +34 -21
- package/dist/theme/ThemeControl.svelte +16 -8
- package/dist/theme/index.d.ts +1 -5
- package/dist/theme/index.js +4 -10
- package/dist/theme/themes.mjs +14 -7
- package/dist/tooltip/KCoords.svelte +4 -1
- package/dist/tooltip/TooltipContent.svelte +2 -5
- package/dist/trajectory/Trajectory.svelte +515 -308
- package/dist/trajectory/Trajectory.svelte.d.ts +1 -1
- package/dist/trajectory/TrajectoryError.svelte +11 -8
- package/dist/trajectory/TrajectoryExportPane.svelte +14 -31
- package/dist/trajectory/TrajectoryInfoPane.svelte +85 -97
- package/dist/trajectory/extract.js +56 -34
- package/dist/trajectory/format-detect.js +11 -9
- package/dist/trajectory/frame-reader.d.ts +2 -0
- package/dist/trajectory/frame-reader.js +33 -29
- package/dist/trajectory/helpers.d.ts +2 -0
- package/dist/trajectory/helpers.js +18 -7
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +3 -0
- package/dist/trajectory/parse/h5-utils.d.ts +13 -0
- package/dist/trajectory/parse/h5-utils.js +109 -0
- package/dist/trajectory/parse/hdf5.d.ts +1 -1
- package/dist/trajectory/parse/hdf5.js +116 -106
- package/dist/trajectory/parse/index.d.ts +2 -3
- package/dist/trajectory/parse/index.js +17 -20
- package/dist/trajectory/parse/lammps.js +8 -12
- package/dist/trajectory/parse/vasp.js +10 -7
- package/dist/trajectory/parse/vaspout-electronic.d.ts +13 -0
- package/dist/trajectory/parse/vaspout-electronic.js +207 -0
- package/dist/trajectory/parse/vaspout-h5.d.ts +5 -0
- package/dist/trajectory/parse/vaspout-h5.js +284 -0
- package/dist/trajectory/parse/xyz.js +11 -7
- package/dist/trajectory/plotting.d.ts +8 -9
- package/dist/trajectory/plotting.js +99 -27
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +7 -0
- package/dist/xrd/XrdPlot.svelte +47 -61
- package/dist/xrd/broadening.js +2 -1
- package/dist/xrd/calc-xrd.js +2 -4
- package/dist/xrd/parse.d.ts +0 -1
- package/dist/xrd/parse.js +56 -73
- package/package.json +61 -33
- package/dist/api/mp.d.ts +0 -6
- package/dist/api/mp.js +0 -22
- package/dist/plot/sunburst/SunburstControls.svelte +0 -200
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
StructureBond,
|
|
26
26
|
} from './'
|
|
27
27
|
import {
|
|
28
|
-
Arrow,
|
|
29
28
|
atomic_radii,
|
|
30
29
|
Cylinder,
|
|
31
30
|
get_all_site_vectors,
|
|
@@ -34,11 +33,11 @@
|
|
|
34
33
|
Lattice,
|
|
35
34
|
VECTOR_PALETTE,
|
|
36
35
|
} from './'
|
|
36
|
+
import ArrowInstances from './ArrowInstances.svelte'
|
|
37
|
+
import InstancedAtoms from './InstancedAtoms.svelte'
|
|
38
|
+
import SiteLabels from './SiteLabels.svelte'
|
|
37
39
|
import type { AtomColorConfig } from './atom-properties'
|
|
38
|
-
import {
|
|
39
|
-
get_orig_site_idx,
|
|
40
|
-
get_property_colors,
|
|
41
|
-
} from './atom-properties'
|
|
40
|
+
import { get_orig_site_idx, get_property_colors } from './atom-properties'
|
|
42
41
|
import type { SymmetryElement } from '../symmetry'
|
|
43
42
|
import { has_visible_symmetry_overlay } from '../symmetry/symmetry-elements'
|
|
44
43
|
import SymmetryElements from '../symmetry/SymmetryElements.svelte'
|
|
@@ -59,9 +58,9 @@
|
|
|
59
58
|
import type { BondEditResult, BondingStrategy, BondKeyTarget } from './bonding'
|
|
60
59
|
import {
|
|
61
60
|
add_or_restore_bond,
|
|
62
|
-
BONDING_STRATEGIES,
|
|
63
61
|
BOND_ORDER_OPTIONS,
|
|
64
62
|
canonicalize_bond_target,
|
|
63
|
+
compute_bonds,
|
|
65
64
|
delete_bond as apply_delete_bond,
|
|
66
65
|
get_bond_key,
|
|
67
66
|
get_bond_render_matrices,
|
|
@@ -70,37 +69,11 @@
|
|
|
70
69
|
set_bond_order as apply_set_bond_order,
|
|
71
70
|
structure_bond_to_bond_pair,
|
|
72
71
|
} from './bonding'
|
|
73
|
-
import {
|
|
74
|
-
|
|
75
|
-
compose_perceived_bonds,
|
|
76
|
-
perceive_bond_orders,
|
|
77
|
-
} from './index'
|
|
78
|
-
import {
|
|
79
|
-
choose_site_label_offset,
|
|
80
|
-
LABEL_OFFSET_EPS,
|
|
81
|
-
make_label_position_calculator,
|
|
82
|
-
} from './atom-label-placement'
|
|
72
|
+
import { CanvasTooltip, compose_perceived_bonds, perceive_bond_orders } from './index'
|
|
73
|
+
import { choose_site_label_offset, LABEL_OFFSET_EPS } from './atom-label-placement'
|
|
83
74
|
import type { PolyhedraColorMode, Polyhedron } from './polyhedra'
|
|
84
75
|
import { compute_polyhedra, merge_polyhedra_buffers } from './polyhedra'
|
|
85
76
|
|
|
86
|
-
type InstancedAtomGroup = {
|
|
87
|
-
element: string
|
|
88
|
-
radius: number
|
|
89
|
-
color: string
|
|
90
|
-
is_image_atom: boolean
|
|
91
|
-
atoms: (typeof atom_data)[number][]
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function instanced_atom_group_key(
|
|
95
|
-
{ element, radius, color, is_image_atom, atoms }: InstancedAtomGroup,
|
|
96
|
-
measure_mode: MeasureMode,
|
|
97
|
-
): string {
|
|
98
|
-
const edit_mode_image = measure_mode === `edit-atoms` && is_image_atom
|
|
99
|
-
return `${element}-${format_num(radius, `.3~`)}-${color}-${
|
|
100
|
-
is_image_atom ? `img` : `base`
|
|
101
|
-
}-${edit_mode_image}-${atoms.length}`
|
|
102
|
-
}
|
|
103
|
-
|
|
104
77
|
type EditableAtomHitTarget = {
|
|
105
78
|
site_idx: number
|
|
106
79
|
position: Vec3
|
|
@@ -133,6 +106,7 @@
|
|
|
133
106
|
same_size_atoms = false,
|
|
134
107
|
camera_position = DEFAULTS.structure.camera_position,
|
|
135
108
|
camera_target = undefined,
|
|
109
|
+
camera_direction = undefined,
|
|
136
110
|
camera_projection = DEFAULTS.structure.camera_projection,
|
|
137
111
|
rotation_damping = DEFAULTS.structure.rotation_damping,
|
|
138
112
|
max_zoom = DEFAULTS.structure.max_zoom,
|
|
@@ -237,6 +211,7 @@
|
|
|
237
211
|
dragging_atoms = $bindable(false),
|
|
238
212
|
volumetric_data = undefined,
|
|
239
213
|
isosurface_settings = DEFAULT_ISOSURFACE_SETTINGS,
|
|
214
|
+
interactive = true,
|
|
240
215
|
}: SceneControlProps & {
|
|
241
216
|
structure?: AnyStructure
|
|
242
217
|
base_structure?: AnyStructure // The original structure without image atoms, used for property color calculation
|
|
@@ -245,6 +220,9 @@
|
|
|
245
220
|
// determined by the atomic radius of the element
|
|
246
221
|
camera_position?: [x: number, y: number, z: number] // initial camera position from which to render the scene
|
|
247
222
|
camera_target?: Vec3 // external orbit-controls target for pan synchronization
|
|
223
|
+
// When set (and camera_position is unset/zero), auto-place the camera along this
|
|
224
|
+
// direction from the structure center (used by the multi-side view for fixed angles)
|
|
225
|
+
camera_direction?: Vec3
|
|
248
226
|
show_atoms?: boolean
|
|
249
227
|
show_bonds?: ShowBonds
|
|
250
228
|
show_site_labels?: boolean
|
|
@@ -287,7 +265,10 @@
|
|
|
287
265
|
// Fractional coords must refer to the SAME cell as the rendered lattice (moyo
|
|
288
266
|
// operations are in the input-cell frame, i.e. the original untransformed cell).
|
|
289
267
|
symmetry_elements?: SymmetryElement[]
|
|
290
|
-
symmetry_elements_props?: Omit<
|
|
268
|
+
symmetry_elements_props?: Omit<
|
|
269
|
+
ComponentProps<typeof SymmetryElements>,
|
|
270
|
+
`elements` | `lattice`
|
|
271
|
+
>
|
|
291
272
|
// Auto-reduce visual clutter while a symmetry-element overlay is visible: hides
|
|
292
273
|
// coordination polyhedra and calculated bonds, and shrinks atoms so axes/planes/
|
|
293
274
|
// centers stay readable. Purely derived — toggling the overlay off restores the
|
|
@@ -337,6 +318,9 @@
|
|
|
337
318
|
dragging_atoms?: boolean // true while TransformControls drag is active (skips expensive recalculations)
|
|
338
319
|
volumetric_data?: VolumetricData // Active volumetric data for isosurface rendering
|
|
339
320
|
isosurface_settings?: IsosurfaceSettings // Isosurface rendering settings
|
|
321
|
+
// When false, render the scene without hover/edit raycast helpers. Used by multi-side
|
|
322
|
+
// view so inactive panes skip interaction-only work while the active pane stays editable.
|
|
323
|
+
interactive?: boolean
|
|
340
324
|
} = $props()
|
|
341
325
|
|
|
342
326
|
const pulse = create_pulse_animation(
|
|
@@ -364,7 +348,6 @@
|
|
|
364
348
|
initial_computed_zoom = stored_initial_zoom
|
|
365
349
|
})
|
|
366
350
|
|
|
367
|
-
let bond_pairs: BondPair[] = $state([])
|
|
368
351
|
let atom_tooltip_active = $state(false)
|
|
369
352
|
let hovered_bond_key = $state<string | null>(null)
|
|
370
353
|
const ATOM_HOVER_CLEAR_DELAY_MS = 200
|
|
@@ -392,20 +375,18 @@
|
|
|
392
375
|
}, ATOM_HOVER_CLEAR_DELAY_MS)
|
|
393
376
|
}
|
|
394
377
|
|
|
395
|
-
const atom_hover_props = (site_idx: number | null
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (!disabled && site_idx != null) schedule_atom_hover_clear(site_idx)
|
|
404
|
-
},
|
|
405
|
-
})
|
|
378
|
+
const atom_hover_props = (site_idx: number | null) =>
|
|
379
|
+
!interactive || site_idx == null
|
|
380
|
+
? {}
|
|
381
|
+
: {
|
|
382
|
+
onpointerenter: () => set_atom_hover(site_idx),
|
|
383
|
+
onpointermove: () => set_atom_hover(site_idx),
|
|
384
|
+
onpointerleave: () => schedule_atom_hover_clear(site_idx),
|
|
385
|
+
}
|
|
406
386
|
|
|
407
387
|
// Cursor style for the canvas, derived from mode and hover state
|
|
408
388
|
let canvas_cursor = $derived.by(() => {
|
|
389
|
+
if (!interactive) return `default`
|
|
409
390
|
if (measure_mode === `edit-atoms` && add_atom_mode) return `crosshair`
|
|
410
391
|
if (measure_mode === `edit-bonds` && hovered_bond_key != null) {
|
|
411
392
|
return bond_edits_enabled ? `pointer` : `not-allowed`
|
|
@@ -419,7 +400,6 @@
|
|
|
419
400
|
if (measure_mode === `edit-atoms`) {
|
|
420
401
|
const site = structure?.sites?.[hovered_idx]
|
|
421
402
|
if (site?.properties?.orig_site_idx != null) return `not-allowed`
|
|
422
|
-
return `pointer`
|
|
423
403
|
}
|
|
424
404
|
return `pointer`
|
|
425
405
|
}
|
|
@@ -474,8 +454,9 @@
|
|
|
474
454
|
): BondKeyTarget {
|
|
475
455
|
const rendered_target = { site_idx_1, site_idx_2, cell_shift }
|
|
476
456
|
const rendered_key = rendered_bond_key_for(rendered_target)
|
|
477
|
-
return
|
|
478
|
-
canonical_bond_target(rendered_target)
|
|
457
|
+
return (
|
|
458
|
+
find_added_bond_by_rendered_key(rendered_key) ?? canonical_bond_target(rendered_target)
|
|
459
|
+
)
|
|
479
460
|
}
|
|
480
461
|
|
|
481
462
|
const is_image_bond_site = (site_idx: number): boolean =>
|
|
@@ -486,9 +467,11 @@
|
|
|
486
467
|
|
|
487
468
|
const can_edit_bond = (bond: BondKeyTarget): boolean => {
|
|
488
469
|
const target = canonical_bond_target(bond)
|
|
489
|
-
return
|
|
470
|
+
return (
|
|
471
|
+
bond_edits_enabled &&
|
|
490
472
|
!is_image_bond_site(target.site_idx_1) &&
|
|
491
473
|
!is_image_bond_site(target.site_idx_2)
|
|
474
|
+
)
|
|
492
475
|
}
|
|
493
476
|
|
|
494
477
|
const format_bond_order = (order: BondOrder | undefined): string =>
|
|
@@ -500,10 +483,12 @@
|
|
|
500
483
|
cell_shift?: Vec3,
|
|
501
484
|
): BondOrder | undefined {
|
|
502
485
|
const key = get_bond_key(site_idx_1, site_idx_2, cell_shift)
|
|
503
|
-
return
|
|
486
|
+
return (
|
|
487
|
+
find_added_bond_by_rendered_key(key)?.order ??
|
|
504
488
|
bond_order_overrides.find((bond) => matches_bond_key(bond, key))?.order ??
|
|
505
489
|
added_bonds.find((bond) => matches_bond_key(bond, key))?.order ??
|
|
506
490
|
filtered_bond_pairs.find((bond) => matches_bond_key(bond, key))?.bond_order
|
|
491
|
+
)
|
|
507
492
|
}
|
|
508
493
|
|
|
509
494
|
const midpoint = (pos_1: Vec3, pos_2: Vec3): Vec3 => [
|
|
@@ -553,9 +538,10 @@
|
|
|
553
538
|
return site_idx
|
|
554
539
|
}
|
|
555
540
|
|
|
556
|
-
const image_site_idx = structure.sites.findIndex(
|
|
557
|
-
candidate_site
|
|
558
|
-
|
|
541
|
+
const image_site_idx = structure.sites.findIndex(
|
|
542
|
+
(candidate_site) =>
|
|
543
|
+
candidate_site.properties?.orig_site_idx === site_idx &&
|
|
544
|
+
matches_world_position(candidate_site),
|
|
559
545
|
)
|
|
560
546
|
return image_site_idx === -1 ? site_idx : image_site_idx
|
|
561
547
|
}
|
|
@@ -590,10 +576,7 @@
|
|
|
590
576
|
|
|
591
577
|
let label_screen_margin = $derived(site_label_size * 10 + site_label_padding)
|
|
592
578
|
|
|
593
|
-
function get_bond_context_menu_position(
|
|
594
|
-
bond: BondPair,
|
|
595
|
-
event?: BondContextMenuEvent,
|
|
596
|
-
): Vec3 {
|
|
579
|
+
function get_bond_context_menu_position(bond: BondPair, event?: BondContextMenuEvent): Vec3 {
|
|
597
580
|
const parent = event?.object?.parent
|
|
598
581
|
if (!event?.point || !parent) return midpoint(bond.pos_1, bond.pos_2)
|
|
599
582
|
|
|
@@ -634,8 +617,10 @@
|
|
|
634
617
|
): BondPair | undefined => {
|
|
635
618
|
const rendered_key = rendered_bond_key_for(target)
|
|
636
619
|
const canonical_key = bond_key_for(canonical_target)
|
|
637
|
-
return
|
|
620
|
+
return (
|
|
621
|
+
filtered_bond_pairs.find((bond) => rendered_bond_key_for(bond) === rendered_key) ??
|
|
638
622
|
filtered_bond_pairs.find((bond) => bond_key_for(bond) === canonical_key)
|
|
623
|
+
)
|
|
639
624
|
}
|
|
640
625
|
|
|
641
626
|
function open_bond_order_menu_for_target(
|
|
@@ -647,6 +632,7 @@
|
|
|
647
632
|
}
|
|
648
633
|
|
|
649
634
|
function add_or_restore_pair(site_idx_1: number, site_idx_2: number) {
|
|
635
|
+
if (!interactive) return // inactive panes must not mutate shared bond state
|
|
650
636
|
const rendered_target = { site_idx_1, site_idx_2 }
|
|
651
637
|
if (!can_edit_bond(rendered_target)) return
|
|
652
638
|
const edit_state = current_bond_edit_state()
|
|
@@ -704,11 +690,7 @@
|
|
|
704
690
|
const target = resolve_bond_edit_target(site_idx_1, site_idx_2, cell_shift)
|
|
705
691
|
if (!can_edit_bond(target)) return
|
|
706
692
|
apply_bond_edit_result(
|
|
707
|
-
apply_delete_bond(
|
|
708
|
-
current_bond_edit_state(),
|
|
709
|
-
target,
|
|
710
|
-
editable_perceived_bond_pairs,
|
|
711
|
-
),
|
|
693
|
+
apply_delete_bond(current_bond_edit_state(), target, editable_perceived_bond_pairs),
|
|
712
694
|
)
|
|
713
695
|
}
|
|
714
696
|
|
|
@@ -722,12 +704,10 @@
|
|
|
722
704
|
// intercept the same native click, only the first intersection should fire.
|
|
723
705
|
// All threlte intersection events from one click share the same nativeEvent ref.
|
|
724
706
|
let last_native_event: Event | null = null
|
|
725
|
-
//
|
|
726
|
-
// back to click. When pointerdown did fire, skip the matching click once.
|
|
707
|
+
// Instanced-atom raycasts do not always emit pointerdown, so edit-bonds also
|
|
708
|
+
// falls back to click. When pointerdown did fire, skip the matching click once.
|
|
727
709
|
let last_edit_bonds_pointerdown_site_idx: number | null = null
|
|
728
|
-
let clear_edit_bonds_pointerdown_site_timeout:
|
|
729
|
-
| ReturnType<typeof setTimeout>
|
|
730
|
-
| null = null
|
|
710
|
+
let clear_edit_bonds_pointerdown_site_timeout: ReturnType<typeof setTimeout> | null = null
|
|
731
711
|
|
|
732
712
|
function remember_edit_bonds_pointerdown_site(site_idx: number) {
|
|
733
713
|
last_edit_bonds_pointerdown_site_idx = site_idx
|
|
@@ -756,30 +736,62 @@
|
|
|
756
736
|
return true
|
|
757
737
|
}
|
|
758
738
|
|
|
759
|
-
//
|
|
760
|
-
//
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
if (
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
if (skip_duplicate_edit_bonds_click(site_idx)) {
|
|
772
|
-
event.stopPropagation()
|
|
773
|
-
return
|
|
774
|
-
}
|
|
739
|
+
// Selection handlers shared by instanced atom meshes and per-site hit targets
|
|
740
|
+
// so the edit-bonds click semantics can't drift between the two paths
|
|
741
|
+
const handle_atom_pointerdown = (site_idx: number, event: PointerEvent) => {
|
|
742
|
+
if (measure_mode !== `edit-bonds` || bond_edit_mode !== `add`) return
|
|
743
|
+
select_edit_bonds_site(site_idx, event)
|
|
744
|
+
}
|
|
745
|
+
const handle_atom_click = (site_idx: number, event: MouseEvent) => {
|
|
746
|
+
if (measure_mode === `edit-bonds`) {
|
|
747
|
+
if (bond_edit_mode !== `add`) return
|
|
748
|
+
if (skip_duplicate_edit_bonds_click(site_idx)) {
|
|
749
|
+
event.stopPropagation()
|
|
750
|
+
return
|
|
775
751
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
}
|
|
752
|
+
}
|
|
753
|
+
toggle_selection(site_idx, event)
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Pointer handlers for an instanced atom mesh: intersection events carry the
|
|
757
|
+
// hit `instanceId`, which indexes into the mesh's `atoms` array. One handler
|
|
758
|
+
// set per mesh instead of one per atom. Inactive grid panes render without
|
|
759
|
+
// raycast handlers; ghosted edit-mode image atoms are non-interactive.
|
|
760
|
+
type InstanceEvent = { instanceId?: number }
|
|
761
|
+
const atom_instance_events = (
|
|
762
|
+
instance_atoms: { site_idx: number }[],
|
|
763
|
+
is_edit_image: boolean,
|
|
764
|
+
) => {
|
|
765
|
+
if (!interactive || is_edit_image) return {}
|
|
766
|
+
const wrap =
|
|
767
|
+
<Event_ extends InstanceEvent>(handler: (site_idx: number, event: Event_) => void) =>
|
|
768
|
+
(event: Event_) => {
|
|
769
|
+
const site_idx = instance_atoms[event.instanceId ?? -1]?.site_idx
|
|
770
|
+
if (site_idx != null) handler(site_idx, event)
|
|
771
|
+
}
|
|
772
|
+
return {
|
|
773
|
+
onpointerenter: wrap(set_atom_hover),
|
|
774
|
+
onpointermove: wrap(set_atom_hover),
|
|
775
|
+
onpointerleave: wrap(schedule_atom_hover_clear),
|
|
776
|
+
onpointerdown: wrap<PointerEvent & InstanceEvent>(handle_atom_pointerdown),
|
|
777
|
+
onclick: wrap<MouseEvent & InstanceEvent>(handle_atom_click),
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Pointer props (hover + select) for per-site hit-target meshes (partial-occupancy
|
|
782
|
+
// sites), with the same interactivity gating as atom_instance_events
|
|
783
|
+
const atom_pointer_props = (site_idx: number, is_edit_image: boolean) =>
|
|
784
|
+
!interactive || is_edit_image
|
|
785
|
+
? {}
|
|
786
|
+
: {
|
|
787
|
+
...atom_hover_props(site_idx),
|
|
788
|
+
onpointerdown: (event: PointerEvent) => handle_atom_pointerdown(site_idx, event),
|
|
789
|
+
onclick: (event: MouseEvent) => handle_atom_click(site_idx, event),
|
|
790
|
+
}
|
|
779
791
|
|
|
780
792
|
function toggle_selection(site_index: number, evt?: Event) {
|
|
781
793
|
evt?.stopPropagation?.()
|
|
782
|
-
const event_with_native = evt as Event & { nativeEvent?: unknown } | undefined
|
|
794
|
+
const event_with_native = evt as (Event & { nativeEvent?: unknown }) | undefined
|
|
783
795
|
const native_event = event_with_native?.nativeEvent ?? evt
|
|
784
796
|
if (native_event instanceof Event) {
|
|
785
797
|
if (native_event === last_native_event) return
|
|
@@ -787,6 +799,9 @@
|
|
|
787
799
|
}
|
|
788
800
|
|
|
789
801
|
if (measure_mode === `edit-bonds`) {
|
|
802
|
+
// Only the active pane edits: inactive panes keep selection/hover overlays visible
|
|
803
|
+
// but must not change selection or add/restore bonds in shared state.
|
|
804
|
+
if (!interactive) return
|
|
790
805
|
if (bond_edit_mode === `delete`) {
|
|
791
806
|
measured_sites = []
|
|
792
807
|
selected_sites = []
|
|
@@ -811,12 +826,16 @@
|
|
|
811
826
|
}
|
|
812
827
|
|
|
813
828
|
if (measure_mode === `edit-atoms`) {
|
|
829
|
+
// Inactive panes don't drive edit-atoms selection (gizmo/add-plane are interactive-gated)
|
|
830
|
+
if (!interactive) return
|
|
814
831
|
// Block image atoms (detected by orig_site_idx property from PBC)
|
|
815
832
|
const site = structure?.sites?.[site_index]
|
|
816
833
|
if (site?.properties?.orig_site_idx != null) return
|
|
817
834
|
|
|
818
835
|
const is_selected = selected_sites.includes(site_index)
|
|
819
|
-
|
|
836
|
+
// threlte dispatches plain objects wrapping the DOM event, so read shift
|
|
837
|
+
// from the extracted native event (evt itself is never a MouseEvent for 3D hits)
|
|
838
|
+
const is_shift = native_event instanceof MouseEvent && native_event.shiftKey
|
|
820
839
|
|
|
821
840
|
// In edit-atoms mode, selected_sites and measured_sites always stay in sync
|
|
822
841
|
let new_sites: number[]
|
|
@@ -866,11 +885,15 @@
|
|
|
866
885
|
$effect(() => {
|
|
867
886
|
const count = structure?.sites?.length ?? 0
|
|
868
887
|
if (count <= 0) {
|
|
869
|
-
measured_sites = []
|
|
888
|
+
if (untrack(() => measured_sites.length) > 0) measured_sites = []
|
|
870
889
|
return
|
|
871
890
|
}
|
|
872
891
|
untrack(() => {
|
|
873
|
-
|
|
892
|
+
// Only reassign when out-of-range indices were dropped: a fresh (equal)
|
|
893
|
+
// array identity on every structure change would ripple through all
|
|
894
|
+
// measured_sites bindings and their dependents each frame of a trajectory.
|
|
895
|
+
const filtered = measured_sites.filter((idx) => idx >= 0 && idx < count)
|
|
896
|
+
if (filtered.length !== measured_sites.length) measured_sites = filtered
|
|
874
897
|
})
|
|
875
898
|
})
|
|
876
899
|
|
|
@@ -882,9 +905,7 @@
|
|
|
882
905
|
$effect.pre(() => {
|
|
883
906
|
hovered_site = structure?.sites?.[hovered_idx ?? -1] ?? null
|
|
884
907
|
})
|
|
885
|
-
let lattice = $derived(
|
|
886
|
-
structure && `lattice` in structure ? structure.lattice : null,
|
|
887
|
-
)
|
|
908
|
+
let lattice = $derived(structure && `lattice` in structure ? structure.lattice : null)
|
|
888
909
|
|
|
889
910
|
let visual_lattice = $derived(
|
|
890
911
|
base_structure && `lattice` in base_structure ? base_structure.lattice : lattice,
|
|
@@ -894,9 +915,11 @@
|
|
|
894
915
|
lattice
|
|
895
916
|
? math.scale(math.add(...lattice.matrix), 0.5)
|
|
896
917
|
: structure
|
|
897
|
-
|
|
898
|
-
|
|
918
|
+
? get_center_of_mass(structure)
|
|
919
|
+
: ([0, 0, 0] as Vec3),
|
|
899
920
|
)
|
|
921
|
+
// Negated target for the inner un-translate group (recomputed only on target change)
|
|
922
|
+
let neg_rotation_target = $derived(math.scale(rotation_target, -1) as Vec3)
|
|
900
923
|
|
|
901
924
|
let structure_size = $derived.by(() => {
|
|
902
925
|
if (lattice) return (lattice.a + lattice.b + lattice.c) / 2
|
|
@@ -913,8 +936,8 @@
|
|
|
913
936
|
// Excludes PBC image atoms (orig_site_idx) so toggling image atoms doesn't affect arrow sizing.
|
|
914
937
|
let char_atom_spacing = $derived.by(() => {
|
|
915
938
|
if (!lattice || !structure?.sites?.length) return structure_size
|
|
916
|
-
const n_real = structure.sites.filter(
|
|
917
|
-
site.properties?.orig_site_idx == null
|
|
939
|
+
const n_real = structure.sites.filter(
|
|
940
|
+
(site) => site.properties?.orig_site_idx == null,
|
|
918
941
|
).length
|
|
919
942
|
return n_real > 0 ? Math.cbrt(lattice.volume / n_real) : structure_size
|
|
920
943
|
})
|
|
@@ -945,9 +968,19 @@
|
|
|
945
968
|
|
|
946
969
|
// Using $state because this is mutated in an effect based on viewport/structure size
|
|
947
970
|
let computed_zoom = $state(untrack(() => initial_zoom))
|
|
971
|
+
// structure_size is read untracked so structure changes don't re-zoom the user's view;
|
|
972
|
+
// zoom only re-frames on a genuine viewport resize. Skip same-value width/height
|
|
973
|
+
// re-fires (a wrapping component can transiently re-emit clientWidth/Height during a
|
|
974
|
+
// structure swap) so they don't sneak in a re-zoom with the new structure_size.
|
|
975
|
+
let last_zoom_dims: [number, number] = [0, 0]
|
|
948
976
|
$effect(() => {
|
|
949
977
|
if (!(width > 0) || !(height > 0)) return
|
|
950
|
-
|
|
978
|
+
if (width === last_zoom_dims[0] && height === last_zoom_dims[1]) return
|
|
979
|
+
last_zoom_dims = [width, height]
|
|
980
|
+
const structure_max_dim = Math.max(
|
|
981
|
+
1,
|
|
982
|
+
untrack(() => structure_size),
|
|
983
|
+
)
|
|
951
984
|
const viewer_min_dim = Math.min(width, height)
|
|
952
985
|
const scale_factor = viewer_min_dim / (structure_max_dim * 50) // 50px per unit
|
|
953
986
|
let new_zoom = initial_zoom * scale_factor
|
|
@@ -956,11 +989,23 @@
|
|
|
956
989
|
computed_zoom = new_zoom
|
|
957
990
|
})
|
|
958
991
|
|
|
959
|
-
$effect.pre(() => {
|
|
992
|
+
$effect.pre(() => {
|
|
993
|
+
// Simple initial camera auto-position: proportional to structure size and fov
|
|
960
994
|
if (camera_position.every((val) => val === 0) && structure) {
|
|
961
995
|
stored_initial_zoom = undefined
|
|
962
996
|
const distance = Math.max(1, structure_size) * (60 / fov)
|
|
963
|
-
|
|
997
|
+
// When a view direction is given (multi-side view), place the camera
|
|
998
|
+
// target-relative along it; otherwise use the default angled position.
|
|
999
|
+
// normalize_vec returns [0,0,0] for an absent or zero-length direction (arrays are
|
|
1000
|
+
// truthy, so a plain `camera_direction ?` check would miss [0,0,0]); treat that as
|
|
1001
|
+
// "no direction" and fall back to the default, since placing the camera on the
|
|
1002
|
+
// rotation target would be a degenerate zero-length view.
|
|
1003
|
+
const view_dir: Vec3 = camera_direction
|
|
1004
|
+
? math.normalize_vec(camera_direction, [0, 0, 0])
|
|
1005
|
+
: [0, 0, 0]
|
|
1006
|
+
camera_position = view_dir.some((val) => val !== 0)
|
|
1007
|
+
? math.add(rotation_target, math.scale(view_dir, distance))
|
|
1008
|
+
: [distance, distance * 0.3, distance * 0.8]
|
|
964
1009
|
}
|
|
965
1010
|
})
|
|
966
1011
|
// Whether a never|always|crystals|molecules setting applies to the current structure
|
|
@@ -984,23 +1029,29 @@
|
|
|
984
1029
|
)
|
|
985
1030
|
// Calculated bonds are hidden in declutter mode (only their cylinders — bond_pairs
|
|
986
1031
|
// stay computed so tooltips and manually added bonds keep working)
|
|
987
|
-
const effective_show_bonds: ShowBonds = $derived(
|
|
988
|
-
declutter_active ? `never` : show_bonds,
|
|
989
|
-
)
|
|
1032
|
+
const effective_show_bonds: ShowBonds = $derived(declutter_active ? `never` : show_bonds)
|
|
990
1033
|
const effective_atom_radius = $derived(declutter_active ? atom_radius * 0.6 : atom_radius)
|
|
991
1034
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1035
|
+
// Derived (not effect + state) so downstream consumers (filtering, polyhedra,
|
|
1036
|
+
// instanced bond buffers) recompute exactly once per structure change instead
|
|
1037
|
+
// of once with stale bonds and again after an effect flush. The mutable cache
|
|
1038
|
+
// lets atom drags freeze the last computed value (recompute happens on release).
|
|
1039
|
+
// Bonds are computed when either bond rendering or polyhedra need them. The
|
|
1040
|
+
// raw/effective mix is deliberate: RAW show_bonds keeps bond_pairs available
|
|
1041
|
+
// during symmetry declutter (cylinders hide via effective_show_bonds in
|
|
1042
|
+
// bonds_to_render, but tooltips + manually added bonds still need the data),
|
|
1043
|
+
// while EFFECTIVE show_polyhedra skips computing bonds whose only consumer —
|
|
1044
|
+
// the polyhedra $derived below, gated on the same effective value — won't run.
|
|
1045
|
+
let last_bond_pairs: BondPair[] = []
|
|
1046
|
+
let bond_pairs: BondPair[] = $derived.by(() => {
|
|
1047
|
+
if (dragging_atoms) return last_bond_pairs
|
|
999
1048
|
const want_bonds = applies_to_structure(show_bonds)
|
|
1000
1049
|
const want_polyhedra = applies_to_structure(effective_show_polyhedra)
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1050
|
+
last_bond_pairs =
|
|
1051
|
+
structure && (want_bonds || want_polyhedra)
|
|
1052
|
+
? compute_bonds(structure, bonding_strategy, bonding_options)
|
|
1053
|
+
: []
|
|
1054
|
+
return last_bond_pairs
|
|
1004
1055
|
})
|
|
1005
1056
|
|
|
1006
1057
|
// Compute property-based colors when not using element coloring
|
|
@@ -1053,21 +1104,22 @@
|
|
|
1053
1104
|
site.properties?.completion_image &&
|
|
1054
1105
|
!applies_to_structure(effective_show_bonds) &&
|
|
1055
1106
|
!applies_to_structure(effective_show_polyhedra)
|
|
1056
|
-
)
|
|
1107
|
+
)
|
|
1108
|
+
return []
|
|
1057
1109
|
|
|
1058
1110
|
// Calculate radius: same_size > site override > element override > default
|
|
1059
1111
|
// All radii scale uniformly with atom_radius for consistent slider behavior
|
|
1060
1112
|
const base_radius = same_size_atoms
|
|
1061
1113
|
? 1
|
|
1062
|
-
: site_radius_overrides?.get(site_idx) ?? calc_weighted_radius(site)
|
|
1114
|
+
: (site_radius_overrides?.get(site_idx) ?? calc_weighted_radius(site))
|
|
1063
1115
|
const radius = base_radius * effective_atom_radius
|
|
1064
1116
|
|
|
1065
1117
|
// Use property color if available (e.g. coordination number, Wyckoff position)
|
|
1066
1118
|
// Otherwise, each species gets its own element color (important for disordered sites)
|
|
1067
1119
|
const site_property_color = property_colors?.colors[orig_idx]
|
|
1068
1120
|
|
|
1069
|
-
const visible_species = site.species.filter(
|
|
1070
|
-
!hidden_elements.has(element)
|
|
1121
|
+
const visible_species = site.species.filter(
|
|
1122
|
+
({ element }) => !hidden_elements.has(element),
|
|
1071
1123
|
)
|
|
1072
1124
|
const slice_geometry = compute_slice_geometry(visible_species)
|
|
1073
1125
|
return slice_geometry.map((slice_data) => {
|
|
@@ -1095,13 +1147,12 @@
|
|
|
1095
1147
|
const is_site_visible = (site_idx: number): boolean => {
|
|
1096
1148
|
if (!structure?.sites) return false
|
|
1097
1149
|
const site = structure.sites[site_idx]
|
|
1098
|
-
const has_visible_element = site?.species.some(
|
|
1099
|
-
!hidden_elements.has(element)
|
|
1150
|
+
const has_visible_element = site?.species.some(
|
|
1151
|
+
({ element }) => !hidden_elements.has(element),
|
|
1100
1152
|
)
|
|
1101
1153
|
const orig_idx = get_orig_site_idx(site, site_idx)
|
|
1102
1154
|
const prop_val = property_colors?.values[orig_idx]
|
|
1103
|
-
const prop_visible = prop_val === undefined ||
|
|
1104
|
-
!hidden_prop_vals.has(prop_val)
|
|
1155
|
+
const prop_visible = prop_val === undefined || !hidden_prop_vals.has(prop_val)
|
|
1105
1156
|
return has_visible_element && prop_visible
|
|
1106
1157
|
}
|
|
1107
1158
|
|
|
@@ -1127,19 +1178,17 @@
|
|
|
1127
1178
|
})
|
|
1128
1179
|
|
|
1129
1180
|
let editable_perceived_bond_pairs = $derived(
|
|
1130
|
-
|
|
1181
|
+
interactive && bond_edits_enabled
|
|
1182
|
+
? perceived_bond_pairs.map((bond) => ({ ...bond, ...canonical_bond_target(bond) }))
|
|
1183
|
+
: [],
|
|
1131
1184
|
)
|
|
1132
1185
|
|
|
1133
1186
|
let filtered_bond_pairs = $derived.by(() => {
|
|
1134
1187
|
if (!structure?.sites) return perceived_bond_pairs
|
|
1135
1188
|
|
|
1136
1189
|
// Build set of removed bond keys for efficient lookup
|
|
1137
|
-
const removed_keys = new Set(
|
|
1138
|
-
|
|
1139
|
-
)
|
|
1140
|
-
const added_keys = new Set(
|
|
1141
|
-
added_bonds.map(bond_key_for),
|
|
1142
|
-
)
|
|
1190
|
+
const removed_keys = new Set(removed_bonds.map(bond_key_for))
|
|
1191
|
+
const added_keys = new Set(added_bonds.map(bond_key_for))
|
|
1143
1192
|
const order_overrides = new Map(
|
|
1144
1193
|
bond_order_overrides.map((bond) => [bond_key_for(bond), bond.order]),
|
|
1145
1194
|
)
|
|
@@ -1177,7 +1226,7 @@
|
|
|
1177
1226
|
})
|
|
1178
1227
|
|
|
1179
1228
|
let editable_bond_pairs = $derived(
|
|
1180
|
-
bond_edits_enabled ? bonds_to_render.filter(can_edit_bond) : [],
|
|
1229
|
+
interactive && bond_edits_enabled ? bonds_to_render.filter(can_edit_bond) : [],
|
|
1181
1230
|
)
|
|
1182
1231
|
|
|
1183
1232
|
// Coordination polyhedra around cation-like centers, derived from the same
|
|
@@ -1186,10 +1235,12 @@
|
|
|
1186
1235
|
// never recompute the hull geometry.
|
|
1187
1236
|
let polyhedra: Polyhedron[] = $derived.by(() => {
|
|
1188
1237
|
if (
|
|
1189
|
-
!structure?.sites ||
|
|
1238
|
+
!structure?.sites ||
|
|
1239
|
+
dragging_atoms ||
|
|
1190
1240
|
!applies_to_structure(effective_show_polyhedra) ||
|
|
1191
1241
|
filtered_bond_pairs.length === 0
|
|
1192
|
-
)
|
|
1242
|
+
)
|
|
1243
|
+
return []
|
|
1193
1244
|
return compute_polyhedra(structure, filtered_bond_pairs, {
|
|
1194
1245
|
min_neighbors: polyhedra_min_neighbors,
|
|
1195
1246
|
max_neighbors: polyhedra_max_neighbors,
|
|
@@ -1203,8 +1254,9 @@
|
|
|
1203
1254
|
const site = structure?.sites[site_idx]
|
|
1204
1255
|
const orig_idx = get_orig_site_idx(site, site_idx)
|
|
1205
1256
|
const element = get_majority_element(site)
|
|
1206
|
-
return
|
|
1207
|
-
(element && colors.element?.[element]) ?? `#808080`
|
|
1257
|
+
return (
|
|
1258
|
+
property_colors?.colors[orig_idx] ?? (element && colors.element?.[element]) ?? `#808080`
|
|
1259
|
+
)
|
|
1208
1260
|
}
|
|
1209
1261
|
|
|
1210
1262
|
// Separate derived so material-only changes (opacity, edge color) don't rebuild
|
|
@@ -1229,6 +1281,7 @@
|
|
|
1229
1281
|
// Publish which elements currently anchor polyhedra (consumed by controls so
|
|
1230
1282
|
// per-element toggles reflect the actual render state incl. spectator hiding)
|
|
1231
1283
|
$effect(() => {
|
|
1284
|
+
if (!interactive) return
|
|
1232
1285
|
const elems = [...new Set(polyhedra.map((poly) => poly.center_element))].sort()
|
|
1233
1286
|
if (elems.join(`,`) !== polyhedra_rendered_elements.join(`,`)) {
|
|
1234
1287
|
polyhedra_rendered_elements = elems
|
|
@@ -1257,23 +1310,23 @@
|
|
|
1257
1310
|
|
|
1258
1311
|
let polyhedra_edge_geometry: BufferGeometry | null = $state(null)
|
|
1259
1312
|
$effect(() => {
|
|
1260
|
-
const geo =
|
|
1261
|
-
|
|
1262
|
-
|
|
1313
|
+
const geo =
|
|
1314
|
+
polyhedra_show_edges && polyhedra_buffers && polyhedra_buffers.edge_count > 0
|
|
1315
|
+
? buffer_geometry({ position: polyhedra_buffers.edge_positions })
|
|
1316
|
+
: null
|
|
1263
1317
|
polyhedra_edge_geometry = geo
|
|
1264
1318
|
return () => geo?.dispose()
|
|
1265
1319
|
})
|
|
1266
1320
|
|
|
1267
1321
|
let smart_site_label_offsets = $derived.by(() => {
|
|
1268
|
-
|
|
1322
|
+
// Plain Maps: built and consumed inside deriveds, so per-entry reactivity
|
|
1323
|
+
// (SvelteMap) would only add signal overhead for potentially thousands of sites
|
|
1324
|
+
const offsets = new Map<number, Vec3>()
|
|
1269
1325
|
if (bonds_to_render.length === 0) return offsets
|
|
1270
1326
|
|
|
1271
|
-
const bond_directions_by_site = new
|
|
1327
|
+
const bond_directions_by_site = new Map<number, Vec3[]>()
|
|
1272
1328
|
const add_bond_direction = (site_idx: number, pos_1: Vec3, pos_2: Vec3) => {
|
|
1273
|
-
const direction = math.normalize_vec(
|
|
1274
|
-
math.subtract(pos_2, pos_1),
|
|
1275
|
-
[0, 0, 0],
|
|
1276
|
-
)
|
|
1329
|
+
const direction = math.normalize_vec(math.subtract(pos_2, pos_1), [0, 0, 0])
|
|
1277
1330
|
if (Math.hypot(...direction) < LABEL_OFFSET_EPS) return
|
|
1278
1331
|
bond_directions_by_site.set(site_idx, [
|
|
1279
1332
|
...(bond_directions_by_site.get(site_idx) ?? []),
|
|
@@ -1312,7 +1365,7 @@
|
|
|
1312
1365
|
const get_majority_color = (site: typeof site_a) => {
|
|
1313
1366
|
if (!site?.species || site.species.length === 0) return bond_color
|
|
1314
1367
|
const majority_species = site.species.reduce((max, spec) =>
|
|
1315
|
-
spec.occu > max.occu ? spec : max
|
|
1368
|
+
spec.occu > max.occu ? spec : max,
|
|
1316
1369
|
)
|
|
1317
1370
|
return colors.element?.[majority_species.element] || bond_color
|
|
1318
1371
|
}
|
|
@@ -1328,7 +1381,7 @@
|
|
|
1328
1381
|
})
|
|
1329
1382
|
|
|
1330
1383
|
let radius_by_site_idx = $derived.by(() => {
|
|
1331
|
-
const map = new
|
|
1384
|
+
const map = new Map<number, number>()
|
|
1332
1385
|
for (const atom of atom_data) {
|
|
1333
1386
|
if (!map.has(atom.site_idx)) map.set(atom.site_idx, atom.radius)
|
|
1334
1387
|
}
|
|
@@ -1340,7 +1393,8 @@
|
|
|
1340
1393
|
// angles. Give each such site one invisible full-sphere hit target so it's as
|
|
1341
1394
|
// reliably hoverable as an ordered atom (single solid sphere). One per site.
|
|
1342
1395
|
let partial_hit_targets = $derived.by(() => {
|
|
1343
|
-
|
|
1396
|
+
if (!interactive) return []
|
|
1397
|
+
const targets = new Map<number, EditableAtomHitTarget & { is_image_atom: boolean }>()
|
|
1344
1398
|
for (const atom of atom_data) {
|
|
1345
1399
|
if (!atom.has_partial_occupancy || targets.has(atom.site_idx)) continue
|
|
1346
1400
|
targets.set(atom.site_idx, {
|
|
@@ -1355,6 +1409,7 @@
|
|
|
1355
1409
|
|
|
1356
1410
|
let editable_atom_hit_targets = $derived.by(() => {
|
|
1357
1411
|
if (
|
|
1412
|
+
!interactive ||
|
|
1358
1413
|
measure_mode !== `edit-bonds` ||
|
|
1359
1414
|
bond_edit_mode !== `add` ||
|
|
1360
1415
|
!bond_edits_enabled
|
|
@@ -1362,7 +1417,7 @@
|
|
|
1362
1417
|
return []
|
|
1363
1418
|
}
|
|
1364
1419
|
|
|
1365
|
-
const targets = new
|
|
1420
|
+
const targets = new Map<number, EditableAtomHitTarget>()
|
|
1366
1421
|
for (const atom of atom_data) {
|
|
1367
1422
|
if (!can_select_bond_site(atom.site_idx)) continue
|
|
1368
1423
|
if (targets.has(atom.site_idx)) continue
|
|
@@ -1378,13 +1433,47 @@
|
|
|
1378
1433
|
// Get radius for a site (for highlight fallback when site is hidden/filtered)
|
|
1379
1434
|
// Checks site_radius_overrides first for consistency with visible atoms
|
|
1380
1435
|
const get_site_radius = (site: Site, site_idx: number | null): number => {
|
|
1381
|
-
const override = site_idx !== null
|
|
1382
|
-
|
|
1383
|
-
: undefined
|
|
1384
|
-
const base_radius = same_size_atoms ? 1 : override ?? calc_weighted_radius(site)
|
|
1436
|
+
const override = site_idx !== null ? site_radius_overrides?.get(site_idx) : undefined
|
|
1437
|
+
const base_radius = same_size_atoms ? 1 : (override ?? calc_weighted_radius(site))
|
|
1385
1438
|
return base_radius * effective_atom_radius
|
|
1386
1439
|
}
|
|
1387
1440
|
|
|
1441
|
+
// Sites to outline with a translucent sphere: hovered + all selected/active sites. Kept
|
|
1442
|
+
// independent of the pulse animation so this list (with its per-site radius lookups) only
|
|
1443
|
+
// rebuilds when highlighted sites change; pulsing opacity is applied per-frame in the
|
|
1444
|
+
// template instead, avoiding a rebuild every frame in every multi-view pane.
|
|
1445
|
+
type HighlightTarget = {
|
|
1446
|
+
kind: `hover` | `selected` | `active`
|
|
1447
|
+
site: Site
|
|
1448
|
+
site_idx: number | null
|
|
1449
|
+
color: string
|
|
1450
|
+
radius: number
|
|
1451
|
+
}
|
|
1452
|
+
let highlight_targets: HighlightTarget[] = $derived.by(() => {
|
|
1453
|
+
const targets: HighlightTarget[] = []
|
|
1454
|
+
const add = (
|
|
1455
|
+
kind: HighlightTarget[`kind`],
|
|
1456
|
+
site: Site | null,
|
|
1457
|
+
site_idx: number | null,
|
|
1458
|
+
color: string,
|
|
1459
|
+
) => {
|
|
1460
|
+
if (!site) return
|
|
1461
|
+
const radius =
|
|
1462
|
+
site_idx !== null
|
|
1463
|
+
? (radius_by_site_idx.get(site_idx) ?? get_site_radius(site, site_idx))
|
|
1464
|
+
: get_site_radius(site, site_idx)
|
|
1465
|
+
targets.push({ kind, site, site_idx, color, radius })
|
|
1466
|
+
}
|
|
1467
|
+
add(`hover`, hovered_site, hovered_idx, `white`)
|
|
1468
|
+
for (const idx of selected_sites ?? []) {
|
|
1469
|
+
add(`selected`, structure?.sites?.[idx] ?? null, idx, selection_highlight_color)
|
|
1470
|
+
}
|
|
1471
|
+
for (const idx of active_sites ?? []) {
|
|
1472
|
+
add(`active`, structure?.sites?.[idx] ?? null, idx, active_highlight_color)
|
|
1473
|
+
}
|
|
1474
|
+
return targets
|
|
1475
|
+
})
|
|
1476
|
+
|
|
1388
1477
|
// Interpolate between spin-down (#3498db blue) and spin-up (#e74c3c red)
|
|
1389
1478
|
// based on the z-component direction of a magnetic vector
|
|
1390
1479
|
function spin_direction_color(vec: Vec3): string {
|
|
@@ -1393,9 +1482,9 @@
|
|
|
1393
1482
|
const red = Math.round(52 + (231 - 52) * z_frac)
|
|
1394
1483
|
const grn = Math.round(152 + (76 - 152) * z_frac)
|
|
1395
1484
|
const blu = Math.round(219 + (60 - 219) * z_frac)
|
|
1396
|
-
return `#${red.toString(16).padStart(2, `0`)}${
|
|
1397
|
-
|
|
1398
|
-
|
|
1485
|
+
return `#${red.toString(16).padStart(2, `0`)}${grn
|
|
1486
|
+
.toString(16)
|
|
1487
|
+
.padStart(2, `0`)}${blu.toString(16).padStart(2, `0`)}`
|
|
1399
1488
|
}
|
|
1400
1489
|
|
|
1401
1490
|
// Build one arrow layer per visible vector key. Auto-scales the longest
|
|
@@ -1415,7 +1504,7 @@
|
|
|
1415
1504
|
let max_mag = 0
|
|
1416
1505
|
const site_vec_maps = structure.sites.map((site, site_idx) => {
|
|
1417
1506
|
if (!is_site_visible(site_idx)) return null
|
|
1418
|
-
const map = new
|
|
1507
|
+
const map = new Map<string, Vec3>()
|
|
1419
1508
|
for (const { key, vec } of get_all_site_vectors(site)) {
|
|
1420
1509
|
map.set(key, vec)
|
|
1421
1510
|
if (active_set.has(key)) {
|
|
@@ -1427,9 +1516,7 @@
|
|
|
1427
1516
|
|
|
1428
1517
|
// When normalize is on, treat all magnitudes as 1 so arrows have equal length
|
|
1429
1518
|
const effective_max = vector_normalize ? 1 : max_mag
|
|
1430
|
-
const auto_scale = effective_max > 1e-10
|
|
1431
|
-
? (char_atom_spacing * 1.8) / effective_max
|
|
1432
|
-
: 1
|
|
1519
|
+
const auto_scale = effective_max > 1e-10 ? (char_atom_spacing * 1.8) / effective_max : 1
|
|
1433
1520
|
const is_single = active_keys.length === 1
|
|
1434
1521
|
const effective_global_scale = auto_scale * vector_scale
|
|
1435
1522
|
|
|
@@ -1439,40 +1526,40 @@
|
|
|
1439
1526
|
// of the visual atom radius (0 = center, 0.5 = halfway to surface).
|
|
1440
1527
|
// get_site_radius() returns the uniform scale applied to SphereGeometry(0.5),
|
|
1441
1528
|
// so visual_radius = get_site_radius() * 0.5.
|
|
1442
|
-
const site_offsets =
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1529
|
+
const site_offsets =
|
|
1530
|
+
vector_origin_gap > 0 && !is_single
|
|
1531
|
+
? structure.sites.map((site, site_idx) => {
|
|
1532
|
+
const vec_map = site_vec_maps[site_idx]
|
|
1533
|
+
if (!vec_map) return null
|
|
1534
|
+
const site_keys = active_keys.filter((key) => vec_map.has(key))
|
|
1535
|
+
const n_keys = site_keys.length
|
|
1536
|
+
if (n_keys <= 1) return null
|
|
1537
|
+
const visual_radius = get_site_radius(site, site_idx) * 0.5
|
|
1538
|
+
const gap_abs = vector_origin_gap * visual_radius
|
|
1539
|
+
let mean: Vec3 = [0, 0, 0]
|
|
1540
|
+
for (const key of site_keys) {
|
|
1541
|
+
const vec = vec_map.get(key)
|
|
1542
|
+
if (vec) mean = math.add(mean, math.normalize_vec(vec)) as Vec3
|
|
1543
|
+
}
|
|
1544
|
+
const mean_dir = math.normalize_vec(mean, [0, 1, 0] as Vec3)
|
|
1545
|
+
const [u_vec, v_vec] = math.compute_in_plane_basis(mean_dir)
|
|
1546
|
+
const offsets = new Map<string, Vec3>()
|
|
1547
|
+
for (const [idx, key] of site_keys.entries()) {
|
|
1548
|
+
const angle = (2 * Math.PI * idx) / n_keys
|
|
1549
|
+
const dx = math.scale(u_vec, gap_abs * Math.cos(angle))
|
|
1550
|
+
const dy = math.scale(v_vec, gap_abs * Math.sin(angle))
|
|
1551
|
+
offsets.set(key, math.add(dx, dy))
|
|
1552
|
+
}
|
|
1553
|
+
return offsets
|
|
1554
|
+
})
|
|
1555
|
+
: null
|
|
1468
1556
|
|
|
1469
1557
|
const mag_interpolator = get_d3_interpolator(vector_color_scale)
|
|
1470
1558
|
|
|
1471
1559
|
return active_keys.map((key, layer_idx) => {
|
|
1472
1560
|
const layer_cfg = vector_configs[key]
|
|
1473
1561
|
const layer_scale = effective_global_scale * (layer_cfg?.scale ?? 1.0)
|
|
1474
|
-
const layer_color = layer_cfg?.color ??
|
|
1475
|
-
VECTOR_PALETTE[layer_idx % VECTOR_PALETTE.length]
|
|
1562
|
+
const layer_color = layer_cfg?.color ?? VECTOR_PALETTE[layer_idx % VECTOR_PALETTE.length]
|
|
1476
1563
|
|
|
1477
1564
|
const arrows = structure.sites
|
|
1478
1565
|
.map((site, site_idx) => {
|
|
@@ -1488,11 +1575,12 @@
|
|
|
1488
1575
|
arrow_color = layer_cfg.color
|
|
1489
1576
|
} else if (!is_single) arrow_color = layer_color
|
|
1490
1577
|
else {
|
|
1491
|
-
const effective_mode =
|
|
1492
|
-
|
|
1493
|
-
? `
|
|
1494
|
-
|
|
1495
|
-
|
|
1578
|
+
const effective_mode =
|
|
1579
|
+
vector_color_mode === `auto`
|
|
1580
|
+
? key.startsWith(`magmom`) || key.startsWith(`spin`)
|
|
1581
|
+
? `spin_direction`
|
|
1582
|
+
: `element`
|
|
1583
|
+
: vector_color_mode
|
|
1496
1584
|
if (effective_mode === `magnitude`) {
|
|
1497
1585
|
const mag = Math.hypot(...vec)
|
|
1498
1586
|
const norm = max_mag > 1e-10 ? mag / max_mag : 0
|
|
@@ -1502,14 +1590,13 @@
|
|
|
1502
1590
|
} else if (effective_mode === `uniform`) {
|
|
1503
1591
|
arrow_color = vector_color
|
|
1504
1592
|
} else {
|
|
1505
|
-
const majority_element =
|
|
1506
|
-
|
|
1507
|
-
spec.occu > max.occu ? spec : max
|
|
1508
|
-
|
|
1509
|
-
|
|
1593
|
+
const majority_element =
|
|
1594
|
+
site.species.length > 0
|
|
1595
|
+
? site.species.reduce((max, spec) => (spec.occu > max.occu ? spec : max))
|
|
1596
|
+
.element
|
|
1597
|
+
: undefined
|
|
1510
1598
|
arrow_color =
|
|
1511
|
-
(majority_element && colors.element?.[majority_element]) ||
|
|
1512
|
-
vector_color
|
|
1599
|
+
(majority_element && colors.element?.[majority_element]) || vector_color
|
|
1513
1600
|
}
|
|
1514
1601
|
}
|
|
1515
1602
|
|
|
@@ -1531,61 +1618,68 @@
|
|
|
1531
1618
|
})
|
|
1532
1619
|
})
|
|
1533
1620
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
(groups[key] = { element, radius, color, is_image_atom, atoms: [] })
|
|
1547
|
-
bucket.atoms.push(atom)
|
|
1548
|
-
return groups
|
|
1549
|
-
},
|
|
1550
|
-
{} as Record<string, InstancedAtomGroup>,
|
|
1551
|
-
),
|
|
1552
|
-
),
|
|
1553
|
-
)
|
|
1621
|
+
// Full-occupancy atoms split into base and PBC-image sets. Each set renders
|
|
1622
|
+
// as ONE InstancedMesh (per-atom color/radius live in instance buffers), so
|
|
1623
|
+
// no per-element grouping is needed. Image atoms get their own mesh because
|
|
1624
|
+
// they ghost (desaturate + translucent) and lose interactivity in edit-atoms mode.
|
|
1625
|
+
let instanced_atom_sets = $derived.by(() => {
|
|
1626
|
+
const base: typeof atom_data = []
|
|
1627
|
+
const image: typeof atom_data = []
|
|
1628
|
+
for (const atom of atom_data) {
|
|
1629
|
+
if (!atom.has_partial_occupancy) (atom.is_image_atom ? image : base).push(atom)
|
|
1630
|
+
}
|
|
1631
|
+
return { base, image }
|
|
1632
|
+
})
|
|
1554
1633
|
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1634
|
+
// One label anchor per visible site (sites can contribute several atom_data
|
|
1635
|
+
// entries, e.g. partial-occupancy wedges — label each site once)
|
|
1636
|
+
let label_entries = $derived.by(() => {
|
|
1637
|
+
if (!show_site_labels && !show_site_indices) return []
|
|
1638
|
+
const seen = new Set<number>()
|
|
1639
|
+
const entries: { site_idx: number; position: Vec3; radius: number }[] = []
|
|
1640
|
+
for (const atom of atom_data) {
|
|
1641
|
+
if (seen.has(atom.site_idx)) continue
|
|
1642
|
+
seen.add(atom.site_idx)
|
|
1643
|
+
entries.push({
|
|
1644
|
+
site_idx: atom.site_idx,
|
|
1645
|
+
position: atom.position,
|
|
1646
|
+
radius: atom.radius,
|
|
1647
|
+
})
|
|
1648
|
+
}
|
|
1649
|
+
return entries
|
|
1650
|
+
})
|
|
1651
|
+
|
|
1652
|
+
// Partial-occupancy atoms render as separate wedge meshes (see template below).
|
|
1653
|
+
// Derived so the filter isn't re-run inline on every render of the atoms group.
|
|
1654
|
+
let partial_occupancy_atoms = $derived(
|
|
1655
|
+
atom_data.filter((atom) => atom.has_partial_occupancy),
|
|
1564
1656
|
)
|
|
1565
1657
|
|
|
1566
|
-
let orbit_controls_props = $derived(
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1658
|
+
let orbit_controls_props = $derived(
|
|
1659
|
+
build_orbit_props({
|
|
1660
|
+
camera_projection,
|
|
1661
|
+
target: camera_target ?? rotation_target,
|
|
1662
|
+
rotate_speed,
|
|
1663
|
+
zoom_speed,
|
|
1664
|
+
zoom_to_cursor,
|
|
1665
|
+
pan_speed,
|
|
1666
|
+
max_zoom,
|
|
1667
|
+
min_zoom,
|
|
1668
|
+
auto_rotate,
|
|
1669
|
+
rotation_damping,
|
|
1670
|
+
set_camera_is_moving: (moving) => (camera_is_moving = moving),
|
|
1671
|
+
// Close hover tooltips + bond context menu while the camera moves. Only hide the
|
|
1672
|
+
// VISIBLE menu (not bond_context_target): clicking a menu button fires this
|
|
1673
|
+
// orbit-controls start handler before the button's own handler runs, which still
|
|
1674
|
+
// needs the target bond to apply the edit (see bond_context_target comment).
|
|
1675
|
+
onstart_extra: () => {
|
|
1676
|
+
cancel_atom_hover_clear()
|
|
1677
|
+
hovered_idx = null
|
|
1678
|
+
hovered_bond_key = null
|
|
1679
|
+
bond_context_menu = null
|
|
1680
|
+
},
|
|
1681
|
+
}),
|
|
1682
|
+
)
|
|
1589
1683
|
|
|
1590
1684
|
let measure_line_color = $derived.by(() => {
|
|
1591
1685
|
if (typeof window === `undefined`) return
|
|
@@ -1595,27 +1689,15 @@
|
|
|
1595
1689
|
})
|
|
1596
1690
|
</script>
|
|
1597
1691
|
|
|
1598
|
-
{#snippet bond_instanced_mesh_snippet(
|
|
1599
|
-
group: ComponentProps<typeof Bond>[`group`],
|
|
1600
|
-
)}
|
|
1692
|
+
{#snippet bond_instanced_mesh_snippet(group: ComponentProps<typeof Bond>[`group`])}
|
|
1601
1693
|
{#key group.instances.length}
|
|
1602
1694
|
<Bond {group} />
|
|
1603
1695
|
{/key}
|
|
1604
1696
|
{/snippet}
|
|
1605
1697
|
|
|
1606
|
-
{#snippet site_label_snippet(
|
|
1698
|
+
{#snippet site_label_snippet(site_idx: number)}
|
|
1607
1699
|
{@const site = structure!.sites[site_idx]}
|
|
1608
|
-
{
|
|
1609
|
-
<extras.HTML
|
|
1610
|
-
center
|
|
1611
|
-
position={position}
|
|
1612
|
-
calculatePosition={make_label_position_calculator(
|
|
1613
|
-
position,
|
|
1614
|
-
() => smart_site_label_offsets.get(site_idx) ?? site_label_offset,
|
|
1615
|
-
visual_radius,
|
|
1616
|
-
label_screen_margin,
|
|
1617
|
-
)}
|
|
1618
|
-
>
|
|
1700
|
+
{#if site}
|
|
1619
1701
|
{#if atom_label}
|
|
1620
1702
|
{@render atom_label({ site, site_idx })}
|
|
1621
1703
|
{:else}
|
|
@@ -1646,10 +1728,7 @@
|
|
|
1646
1728
|
{#if site.species.length === 1}
|
|
1647
1729
|
{site.species[0].element}{#if show_site_indices}-{site_idx + 1}{/if}
|
|
1648
1730
|
{:else}
|
|
1649
|
-
{#each site.species as
|
|
1650
|
-
{ element, occu, oxidation_state }
|
|
1651
|
-
(`${element}-${occu}-${oxidation_state}`)
|
|
1652
|
-
}
|
|
1731
|
+
{#each site.species as { element, occu, oxidation_state } (`${element}-${occu}-${oxidation_state}`)}
|
|
1653
1732
|
{element}<sub>{format_num(occu, `.3~`).replace(`0.`, `.`)}</sub>
|
|
1654
1733
|
{/each}
|
|
1655
1734
|
{#if show_site_indices}-{site_idx + 1}{/if}
|
|
@@ -1659,7 +1738,7 @@
|
|
|
1659
1738
|
{/if}
|
|
1660
1739
|
</button>
|
|
1661
1740
|
{/if}
|
|
1662
|
-
|
|
1741
|
+
{/if}
|
|
1663
1742
|
{/snippet}
|
|
1664
1743
|
|
|
1665
1744
|
<SceneCamera
|
|
@@ -1680,56 +1759,39 @@
|
|
|
1680
1759
|
<!-- Apply manual rotation around center: translate to origin, rotate, translate back -->
|
|
1681
1760
|
<T.Group position={rotation_target}>
|
|
1682
1761
|
<T.Group {rotation}>
|
|
1683
|
-
<T.Group position={
|
|
1762
|
+
<T.Group position={neg_rotation_target}>
|
|
1684
1763
|
{#if show_atoms}
|
|
1685
|
-
<!-- Instanced rendering for full
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
<
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
{
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
scale={atom.radius}
|
|
1705
|
-
{...atom_pointer_props(atom.site_idx, edit_mode_image)}
|
|
1706
|
-
/>
|
|
1707
|
-
{/each}
|
|
1708
|
-
</extras.InstancedMesh>
|
|
1709
|
-
{/each}
|
|
1764
|
+
<!-- Instanced rendering for full-occupancy atoms: one InstancedMesh for
|
|
1765
|
+
base atoms and one for PBC image atoms (which ghost + lose interaction
|
|
1766
|
+
in edit-atoms mode). Pointer events are resolved via raycast instanceId. -->
|
|
1767
|
+
{#if instanced_atom_sets.base.length > 0}
|
|
1768
|
+
<InstancedAtoms
|
|
1769
|
+
atoms={instanced_atom_sets.base}
|
|
1770
|
+
{sphere_segments}
|
|
1771
|
+
{...atom_instance_events(instanced_atom_sets.base, false)}
|
|
1772
|
+
/>
|
|
1773
|
+
{/if}
|
|
1774
|
+
{#if instanced_atom_sets.image.length > 0}
|
|
1775
|
+
{@const edit_mode_image = measure_mode === `edit-atoms`}
|
|
1776
|
+
<InstancedAtoms
|
|
1777
|
+
atoms={instanced_atom_sets.image}
|
|
1778
|
+
{sphere_segments}
|
|
1779
|
+
ghost={edit_mode_image}
|
|
1780
|
+
{...atom_instance_events(instanced_atom_sets.image, edit_mode_image)}
|
|
1781
|
+
/>
|
|
1782
|
+
{/if}
|
|
1710
1783
|
|
|
1711
1784
|
<!-- Regular rendering for partial occupancy atoms -->
|
|
1712
|
-
{#each
|
|
1713
|
-
atom
|
|
1714
|
-
(atom.site_idx + atom.element + atom.occupancy)
|
|
1715
|
-
}
|
|
1785
|
+
{#each partial_occupancy_atoms as atom (atom.site_idx + atom.element + atom.occupancy)}
|
|
1716
1786
|
{@const partial_edit_image = measure_mode === `edit-atoms` && atom.is_image_atom}
|
|
1717
1787
|
{@const ghost_opacity = partial_edit_image ? 0.5 : 1}
|
|
1718
1788
|
<!-- Visual only: pointer interaction handled by the invisible full-sphere
|
|
1719
1789
|
hit targets below (wedge meshes leave gaps at the poles). -->
|
|
1720
1790
|
<T.Group position={atom.position} scale={atom.radius}>
|
|
1721
|
-
{@const partial_color = partial_edit_image
|
|
1722
|
-
? desaturate(atom.color)
|
|
1723
|
-
: atom.color}
|
|
1791
|
+
{@const partial_color = partial_edit_image ? desaturate(atom.color) : atom.color}
|
|
1724
1792
|
<T.Mesh>
|
|
1725
1793
|
<T.SphereGeometry
|
|
1726
|
-
args={[
|
|
1727
|
-
0.5,
|
|
1728
|
-
sphere_segments,
|
|
1729
|
-
sphere_segments,
|
|
1730
|
-
atom.start_phi,
|
|
1731
|
-
atom.phi_length,
|
|
1732
|
-
]}
|
|
1794
|
+
args={[0.5, sphere_segments, sphere_segments, atom.start_phi, atom.phi_length]}
|
|
1733
1795
|
/>
|
|
1734
1796
|
<T.MeshStandardMaterial
|
|
1735
1797
|
color={partial_color}
|
|
@@ -1775,12 +1837,6 @@
|
|
|
1775
1837
|
</T.Mesh>
|
|
1776
1838
|
{/if}
|
|
1777
1839
|
</T.Group>
|
|
1778
|
-
|
|
1779
|
-
<!-- Render label only for the first species of this site to avoid duplicates -->
|
|
1780
|
-
{#if (show_site_labels || show_site_indices) &&
|
|
1781
|
-
atom.element === structure!.sites[atom.site_idx].species[0].element}
|
|
1782
|
-
{@render site_label_snippet(atom.position, atom.site_idx)}
|
|
1783
|
-
{/if}
|
|
1784
1840
|
{/each}
|
|
1785
1841
|
|
|
1786
1842
|
<!-- Invisible full-sphere hit targets for partial-occupancy sites so the
|
|
@@ -1797,23 +1853,31 @@
|
|
|
1797
1853
|
</T.Mesh>
|
|
1798
1854
|
{/each}
|
|
1799
1855
|
|
|
1800
|
-
<!-- Site labels/indices for
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1856
|
+
<!-- Site labels/indices: single overlay for all labels (one DOM container
|
|
1857
|
+
+ one per-frame position pass instead of one threlte <HTML> per label) -->
|
|
1858
|
+
{#if label_entries.length > 0}
|
|
1859
|
+
<SiteLabels
|
|
1860
|
+
entries={label_entries}
|
|
1861
|
+
get_offset={(site_idx) =>
|
|
1862
|
+
smart_site_label_offsets.get(site_idx) ?? site_label_offset}
|
|
1863
|
+
screen_margin={label_screen_margin}
|
|
1864
|
+
>
|
|
1865
|
+
{#snippet label({ site_idx })}
|
|
1866
|
+
{@render site_label_snippet(site_idx)}
|
|
1867
|
+
{/snippet}
|
|
1868
|
+
</SiteLabels>
|
|
1805
1869
|
{/if}
|
|
1806
1870
|
{/if}
|
|
1807
1871
|
|
|
1872
|
+
<!-- Per-site vector arrows (forces, magmoms, ...) as instanced meshes:
|
|
1873
|
+
2 draw calls per layer instead of 2 meshes per site -->
|
|
1808
1874
|
{#each vector_layers as layer (layer.key)}
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
/>
|
|
1816
|
-
{/each}
|
|
1875
|
+
<ArrowInstances
|
|
1876
|
+
arrows={layer.arrows}
|
|
1877
|
+
shaft_radius={eff_shaft_radius}
|
|
1878
|
+
arrow_head_radius={eff_head_radius}
|
|
1879
|
+
arrow_head_length={eff_head_length}
|
|
1880
|
+
/>
|
|
1817
1881
|
{/each}
|
|
1818
1882
|
|
|
1819
1883
|
<!-- Instanced bond rendering with gradient colors -->
|
|
@@ -1850,16 +1914,12 @@
|
|
|
1850
1914
|
{/if}
|
|
1851
1915
|
|
|
1852
1916
|
<!-- Clickable bond hit-test cylinders in edit-bonds mode -->
|
|
1853
|
-
{#if measure_mode === `edit-bonds` && editable_bond_pairs.length > 0}
|
|
1854
|
-
{#each editable_bond_pairs as
|
|
1855
|
-
bond
|
|
1856
|
-
(`bond-hit-${bond_edit_mode}-${rendered_bond_key_for(bond)}`)
|
|
1857
|
-
}
|
|
1917
|
+
{#if interactive && measure_mode === `edit-bonds` && editable_bond_pairs.length > 0}
|
|
1918
|
+
{#each editable_bond_pairs as bond (`bond-hit-${bond_edit_mode}-${rendered_bond_key_for(bond)}`)}
|
|
1858
1919
|
{@const bond_key = rendered_bond_key_for(bond)}
|
|
1859
1920
|
{@const is_hovered = hovered_bond_key === bond_key}
|
|
1860
1921
|
{@const is_delete_mode = bond_edit_mode === `delete`}
|
|
1861
|
-
{@const bond_hit_radius =
|
|
1862
|
-
bond_thickness * (is_delete_mode ? 5 : 1.25)}
|
|
1922
|
+
{@const bond_hit_radius = bond_thickness * (is_delete_mode ? 5 : 1.25)}
|
|
1863
1923
|
{@const bond_hover_radius = bond_thickness * 1.1}
|
|
1864
1924
|
<T.Mesh
|
|
1865
1925
|
matrixAutoUpdate={false}
|
|
@@ -1888,19 +1948,8 @@
|
|
|
1888
1948
|
onpointermove={() => (hovered_bond_key = bond_key)}
|
|
1889
1949
|
onpointerleave={() => (hovered_bond_key = null)}
|
|
1890
1950
|
>
|
|
1891
|
-
<T.CylinderGeometry
|
|
1892
|
-
|
|
1893
|
-
bond_hit_radius,
|
|
1894
|
-
bond_hit_radius,
|
|
1895
|
-
1,
|
|
1896
|
-
6,
|
|
1897
|
-
]}
|
|
1898
|
-
/>
|
|
1899
|
-
<T.MeshBasicMaterial
|
|
1900
|
-
transparent
|
|
1901
|
-
opacity={0}
|
|
1902
|
-
depthWrite={false}
|
|
1903
|
-
/>
|
|
1951
|
+
<T.CylinderGeometry args={[bond_hit_radius, bond_hit_radius, 1, 6]} />
|
|
1952
|
+
<T.MeshBasicMaterial transparent opacity={0} depthWrite={false} />
|
|
1904
1953
|
</T.Mesh>
|
|
1905
1954
|
{#if is_hovered}
|
|
1906
1955
|
<T.Mesh
|
|
@@ -1919,7 +1968,7 @@
|
|
|
1919
1968
|
{/each}
|
|
1920
1969
|
{/if}
|
|
1921
1970
|
|
|
1922
|
-
{#if editable_atom_hit_targets.length > 0}
|
|
1971
|
+
{#if interactive && editable_atom_hit_targets.length > 0}
|
|
1923
1972
|
{#each editable_atom_hit_targets as atom_hit (atom_hit.site_idx)}
|
|
1924
1973
|
<T.Mesh
|
|
1925
1974
|
position={atom_hit.position}
|
|
@@ -1930,16 +1979,12 @@
|
|
|
1930
1979
|
}}
|
|
1931
1980
|
>
|
|
1932
1981
|
<T.SphereGeometry args={[0.5, 12, 12]} />
|
|
1933
|
-
<T.MeshBasicMaterial
|
|
1934
|
-
transparent
|
|
1935
|
-
opacity={0}
|
|
1936
|
-
depthWrite={false}
|
|
1937
|
-
/>
|
|
1982
|
+
<T.MeshBasicMaterial transparent opacity={0} depthWrite={false} />
|
|
1938
1983
|
</T.Mesh>
|
|
1939
1984
|
{/each}
|
|
1940
1985
|
{/if}
|
|
1941
1986
|
|
|
1942
|
-
{#if measure_mode === `edit-bonds` && bond_context_menu}
|
|
1987
|
+
{#if interactive && measure_mode === `edit-bonds` && bond_context_menu}
|
|
1943
1988
|
{@const current_order = get_current_bond_order(
|
|
1944
1989
|
bond_context_menu.site_idx_1,
|
|
1945
1990
|
bond_context_menu.site_idx_2,
|
|
@@ -2003,62 +2048,31 @@
|
|
|
2003
2048
|
</extras.HTML>
|
|
2004
2049
|
{/if}
|
|
2005
2050
|
|
|
2006
|
-
<!-- highlight hovered, active and selected sites
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
color: active_highlight_color,
|
|
2028
|
-
}))),
|
|
2029
|
-
] as
|
|
2030
|
-
entry
|
|
2031
|
-
(`${entry.kind}-${entry.site_idx}`)
|
|
2032
|
-
}
|
|
2033
|
-
{@const { site, opacity, color, kind, site_idx } = entry}
|
|
2034
|
-
{#if site}
|
|
2035
|
-
{@const xyz = site.xyz}
|
|
2036
|
-
{@const highlight_radius = site_idx !== null
|
|
2037
|
-
? radius_by_site_idx.get(site_idx) ?? get_site_radius(site, site_idx)
|
|
2038
|
-
: get_site_radius(site, site_idx)}
|
|
2039
|
-
<T.Mesh
|
|
2040
|
-
position={xyz}
|
|
2041
|
-
scale={1.2 * highlight_radius}
|
|
2042
|
-
oncreate={disable_raycast}
|
|
2043
|
-
>
|
|
2044
|
-
<T.SphereGeometry args={[0.5, 22, 22]} />
|
|
2045
|
-
<T.MeshStandardMaterial
|
|
2046
|
-
{color}
|
|
2047
|
-
transparent
|
|
2048
|
-
{opacity}
|
|
2049
|
-
emissive={color}
|
|
2050
|
-
emissiveIntensity={kind === `selected` || kind === `active` ? 0.7 : 0.2}
|
|
2051
|
-
depthTest={false}
|
|
2052
|
-
depthWrite={false}
|
|
2053
|
-
/>
|
|
2054
|
-
</T.Mesh>
|
|
2055
|
-
{/if}
|
|
2051
|
+
<!-- highlight hovered, active and selected sites. The target list is a pulse-
|
|
2052
|
+
independent $derived; only the opacity reads the per-frame pulse value so the
|
|
2053
|
+
array isn't rebuilt every animation frame. -->
|
|
2054
|
+
{#each highlight_targets as entry (`${entry.kind}-${entry.site_idx}`)}
|
|
2055
|
+
{@const is_pulsing = entry.kind !== `hover`}
|
|
2056
|
+
<T.Mesh
|
|
2057
|
+
position={entry.site.xyz}
|
|
2058
|
+
scale={1.2 * entry.radius}
|
|
2059
|
+
oncreate={disable_raycast}
|
|
2060
|
+
>
|
|
2061
|
+
<T.SphereGeometry args={[0.5, 22, 22]} />
|
|
2062
|
+
<T.MeshStandardMaterial
|
|
2063
|
+
color={entry.color}
|
|
2064
|
+
transparent
|
|
2065
|
+
opacity={is_pulsing ? pulse_opacity : 0.28}
|
|
2066
|
+
emissive={entry.color}
|
|
2067
|
+
emissiveIntensity={is_pulsing ? 0.7 : 0.2}
|
|
2068
|
+
depthTest={false}
|
|
2069
|
+
depthWrite={false}
|
|
2070
|
+
/>
|
|
2071
|
+
</T.Mesh>
|
|
2056
2072
|
{/each}
|
|
2057
2073
|
|
|
2058
2074
|
<!-- selection order labels (1, 2, 3, ...) for measurements and bond editing -->
|
|
2059
|
-
{#if structure?.sites && (measured_sites?.length ?? 0) > 0 &&
|
|
2060
|
-
(measure_mode === `distance` || measure_mode === `angle` ||
|
|
2061
|
-
measure_mode === `edit-bonds`)}
|
|
2075
|
+
{#if structure?.sites && (measured_sites?.length ?? 0) > 0 && (measure_mode === `distance` || measure_mode === `angle` || measure_mode === `edit-bonds`)}
|
|
2062
2076
|
{#each measured_sites as site_index, loop_idx (site_index)}
|
|
2063
2077
|
{@const site = structure.sites[site_index]}
|
|
2064
2078
|
{#if site}
|
|
@@ -2073,24 +2087,18 @@
|
|
|
2073
2087
|
{/if}
|
|
2074
2088
|
|
|
2075
2089
|
<!-- hovered site tooltip -->
|
|
2076
|
-
{#if hovered_site && !camera_is_moving &&
|
|
2077
|
-
|
|
2078
|
-
{@const
|
|
2079
|
-
`, `,
|
|
2080
|
-
)}
|
|
2081
|
-
{@const xyz = hovered_site.xyz.map((val) => format_num(val, float_fmt)).join(
|
|
2082
|
-
`, `,
|
|
2083
|
-
)}
|
|
2090
|
+
{#if hovered_site && !camera_is_moving && (atom_tooltip_active || active_sites.includes(hovered_idx ?? -1))}
|
|
2091
|
+
{@const abc = hovered_site.abc.map((val) => format_num(val, float_fmt)).join(`, `)}
|
|
2092
|
+
{@const xyz = hovered_site.xyz.map((val) => format_num(val, float_fmt)).join(`, `)}
|
|
2084
2093
|
{@const bond_neighbors = (() => {
|
|
2085
2094
|
if (hovered_idx == null || !structure?.sites) return []
|
|
2086
2095
|
return filtered_bond_pairs
|
|
2087
|
-
.filter(
|
|
2088
|
-
bond.site_idx_1 === hovered_idx || bond.site_idx_2 === hovered_idx
|
|
2096
|
+
.filter(
|
|
2097
|
+
(bond) => bond.site_idx_1 === hovered_idx || bond.site_idx_2 === hovered_idx,
|
|
2089
2098
|
)
|
|
2090
2099
|
.map((bond) => {
|
|
2091
|
-
const neighbor_idx =
|
|
2092
|
-
? bond.site_idx_2
|
|
2093
|
-
: bond.site_idx_1
|
|
2100
|
+
const neighbor_idx =
|
|
2101
|
+
bond.site_idx_1 === hovered_idx ? bond.site_idx_2 : bond.site_idx_1
|
|
2094
2102
|
return structure.sites[neighbor_idx]?.species[0]?.element ?? `?`
|
|
2095
2103
|
})
|
|
2096
2104
|
})()}
|
|
@@ -2107,27 +2115,20 @@
|
|
|
2107
2115
|
})()}
|
|
2108
2116
|
{@const tooltip_species =
|
|
2109
2117
|
render_sites.find((rs) => rs.site_idx === hovered_idx)?.site.species ??
|
|
2110
|
-
hovered_site.species ??
|
|
2118
|
+
hovered_site.species ??
|
|
2119
|
+
[]}
|
|
2111
2120
|
<CanvasTooltip position={hovered_site.xyz}>
|
|
2112
2121
|
<!-- Element symbols with occupancies for disordered sites -->
|
|
2113
2122
|
<div class="elements">
|
|
2114
|
-
{#each tooltip_species as
|
|
2115
|
-
{
|
|
2116
|
-
|
|
2117
|
-
(`${element ?? ``}-${occu ?? ``}-${oxi_state ?? ``}-${idx}`)
|
|
2118
|
-
}
|
|
2119
|
-
{@const element_name = element_data.find((elem) =>
|
|
2120
|
-
elem.symbol === element
|
|
2121
|
-
)?.name ??
|
|
2122
|
-
``}
|
|
2123
|
+
{#each tooltip_species as { element, occu, oxidation_state: oxi_state }, idx (`${element ?? ``}-${occu ?? ``}-${oxi_state ?? ``}-${idx}`)}
|
|
2124
|
+
{@const element_name =
|
|
2125
|
+
element_data.find((elem) => elem.symbol === element)?.name ?? ``}
|
|
2123
2126
|
<span class="species">
|
|
2124
|
-
{#if occu !== 1}<span class="occupancy">{
|
|
2125
|
-
format_num(occu, `.3~f`)
|
|
2126
|
-
}</span>{/if}
|
|
2127
|
+
{#if occu !== 1}<span class="occupancy">{format_num(occu, `.3~f`)}</span>{/if}
|
|
2127
2128
|
<strong>
|
|
2128
|
-
{element}{#if oxi_state != null && oxi_state !== 0}<sup
|
|
2129
|
-
oxi_state
|
|
2130
|
-
|
|
2129
|
+
{element}{#if oxi_state != null && oxi_state !== 0}<sup
|
|
2130
|
+
>{Math.abs(oxi_state)}{oxi_state > 0 ? `+` : `−`}</sup
|
|
2131
|
+
>{/if}
|
|
2131
2132
|
</strong>
|
|
2132
2133
|
{#if element_name}<span class="elem-name">{element_name}</span>{/if}
|
|
2133
2134
|
</span>
|
|
@@ -2153,23 +2154,19 @@
|
|
|
2153
2154
|
{/if}
|
|
2154
2155
|
|
|
2155
2156
|
<!-- TransformControls for editing atoms in edit-atoms mode -->
|
|
2156
|
-
{#if measure_mode === `edit-atoms` && selected_sites.length > 0 &&
|
|
2157
|
-
structure?.sites}
|
|
2157
|
+
{#if interactive && measure_mode === `edit-atoms` && selected_sites.length > 0 && structure?.sites}
|
|
2158
2158
|
{@const selected_atoms = selected_sites
|
|
2159
2159
|
.map((idx) => structure?.sites?.[idx])
|
|
2160
2160
|
.filter((site): site is Site => site != null)}
|
|
2161
2161
|
{#if selected_atoms.length > 0}
|
|
2162
2162
|
{@const avg = (dim: number) =>
|
|
2163
|
-
|
|
2164
|
-
|
|
2163
|
+
selected_atoms.reduce((sum, atom) => sum + atom.xyz[dim], 0) /
|
|
2164
|
+
selected_atoms.length}
|
|
2165
2165
|
{@const centroid = [avg(0), avg(1), avg(2)] as Vec3}
|
|
2166
2166
|
<!-- Invisible mesh at centroid for TransformControls to manipulate.
|
|
2167
2167
|
During drag, use frozen_centroid so Svelte doesn't override TransformControls
|
|
2168
2168
|
with the wrapped centroid (which jumps on PBC boundary crossings). -->
|
|
2169
|
-
<T.Mesh
|
|
2170
|
-
position={frozen_centroid ?? centroid}
|
|
2171
|
-
bind:ref={transform_object}
|
|
2172
|
-
>
|
|
2169
|
+
<T.Mesh position={frozen_centroid ?? centroid} bind:ref={transform_object}>
|
|
2173
2170
|
<T.SphereGeometry args={[0.01, 4, 4]} />
|
|
2174
2171
|
<T.MeshBasicMaterial transparent opacity={0} />
|
|
2175
2172
|
</T.Mesh>
|
|
@@ -2208,7 +2205,7 @@
|
|
|
2208
2205
|
|
|
2209
2206
|
<!-- Invisible plane for click-to-place atom in add-atom mode -->
|
|
2210
2207
|
<!-- Uses onBeforeRender to orient normal toward camera so raycasts always hit -->
|
|
2211
|
-
{#if measure_mode === `edit-atoms` && add_atom_mode}
|
|
2208
|
+
{#if interactive && measure_mode === `edit-atoms` && add_atom_mode}
|
|
2212
2209
|
{@const center = rotation_target ?? [0, 0, 0]}
|
|
2213
2210
|
<T.Mesh
|
|
2214
2211
|
position={center}
|
|
@@ -2223,10 +2220,7 @@
|
|
|
2223
2220
|
}}
|
|
2224
2221
|
>
|
|
2225
2222
|
<T.PlaneGeometry
|
|
2226
|
-
args={[
|
|
2227
|
-
Math.max(200, structure_size * 4),
|
|
2228
|
-
Math.max(200, structure_size * 4),
|
|
2229
|
-
]}
|
|
2223
|
+
args={[Math.max(200, structure_size * 4), Math.max(200, structure_size * 4)]}
|
|
2230
2224
|
/>
|
|
2231
2225
|
<T.MeshBasicMaterial transparent opacity={0} side={2} depthWrite={false} />
|
|
2232
2226
|
</T.Mesh>
|
|
@@ -2246,27 +2240,23 @@
|
|
|
2246
2240
|
{@const site_j = structure.sites[idx_j]}
|
|
2247
2241
|
{@const pos_i = site_i.xyz}
|
|
2248
2242
|
{@const pos_j = site_j.xyz}
|
|
2249
|
-
<Cylinder
|
|
2250
|
-
from={pos_i}
|
|
2251
|
-
to={pos_j}
|
|
2252
|
-
thickness={0.12}
|
|
2253
|
-
color={measure_line_color}
|
|
2254
|
-
/>
|
|
2243
|
+
<Cylinder from={pos_i} to={pos_j} thickness={0.12} color={measure_line_color} />
|
|
2255
2244
|
{@const midpoint = [
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2245
|
+
(pos_i[0] + pos_j[0]) / 2,
|
|
2246
|
+
(pos_i[1] + pos_j[1]) / 2,
|
|
2247
|
+
(pos_i[2] + pos_j[2]) / 2,
|
|
2248
|
+
] as Vec3}
|
|
2260
2249
|
{@const direct = math.euclidean_dist(pos_i, pos_j)}
|
|
2261
2250
|
{@const pbc = lattice
|
|
2262
|
-
|
|
2263
|
-
|
|
2251
|
+
? measure.distance_pbc(pos_i, pos_j, lattice.matrix, undefined, lattice.pbc)
|
|
2252
|
+
: direct}
|
|
2264
2253
|
{@const differ = lattice ? Math.abs(pbc - direct) > 1e-6 : false}
|
|
2265
2254
|
<extras.HTML center position={midpoint}>
|
|
2266
2255
|
<span class="measure-label">
|
|
2267
2256
|
{#if differ}
|
|
2268
2257
|
PBC: {format_num(pbc, float_fmt)} Å<br /><small>
|
|
2269
|
-
Direct: {format_num(direct, float_fmt)} Å</small
|
|
2258
|
+
Direct: {format_num(direct, float_fmt)} Å</small
|
|
2259
|
+
>
|
|
2270
2260
|
{:else}
|
|
2271
2261
|
{format_num(pbc, float_fmt)} Å
|
|
2272
2262
|
{/if}
|
|
@@ -2277,19 +2267,20 @@
|
|
|
2277
2267
|
{:else if measure_mode === `angle` && measured_sites.length >= 3}
|
|
2278
2268
|
{#each measured_sites as idx_center (idx_center)}
|
|
2279
2269
|
{@const center = structure.sites[idx_center]}
|
|
2280
|
-
{#each measured_sites.filter((idx) => idx !== idx_center) as
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
}
|
|
2285
|
-
{#each measured_sites.filter((idx) => idx !== idx_center).slice(loop_idx + 1) as
|
|
2286
|
-
idx_b
|
|
2287
|
-
(idx_center + `-` + idx_a + `-` + idx_b)
|
|
2288
|
-
}
|
|
2270
|
+
{#each measured_sites.filter((idx) => idx !== idx_center) as idx_a, loop_idx (idx_center + `-` + idx_a)}
|
|
2271
|
+
{#each measured_sites
|
|
2272
|
+
.filter((idx) => idx !== idx_center)
|
|
2273
|
+
.slice(loop_idx + 1) as idx_b (idx_center + `-` + idx_a + `-` + idx_b)}
|
|
2289
2274
|
{@const site_a = structure.sites[idx_a]}
|
|
2290
2275
|
{@const site_b = structure.sites[idx_b]}
|
|
2291
2276
|
{@const disp = (to: Vec3) =>
|
|
2292
|
-
|
|
2277
|
+
measure.displacement_pbc(
|
|
2278
|
+
center.xyz,
|
|
2279
|
+
to,
|
|
2280
|
+
lattice?.matrix,
|
|
2281
|
+
undefined,
|
|
2282
|
+
lattice?.pbc,
|
|
2283
|
+
)}
|
|
2293
2284
|
{@const v1 = disp(site_a.xyz)}
|
|
2294
2285
|
{@const v2 = disp(site_b.xyz)}
|
|
2295
2286
|
{@const n1 = Math.hypot(v1[0], v1[1], v1[2])}
|