matterviz 0.4.0 → 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 +132 -219
- package/dist/brillouin/BrillouinZone.svelte.d.ts +9 -20
- package/dist/brillouin/BrillouinZoneControls.svelte +3 -6
- package/dist/brillouin/BrillouinZoneExportPane.svelte +47 -96
- package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +15 -42
- package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +2 -3
- package/dist/brillouin/BrillouinZoneScene.svelte +63 -230
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +3 -23
- package/dist/brillouin/ReciprocalVectors.svelte +39 -0
- package/dist/brillouin/ReciprocalVectors.svelte.d.ts +9 -0
- package/dist/brillouin/compute.d.ts +2 -0
- package/dist/brillouin/compute.js +80 -77
- package/dist/brillouin/geometry.d.ts +8 -0
- package/dist/brillouin/geometry.js +57 -0
- package/dist/brillouin/index.d.ts +2 -0
- package/dist/brillouin/index.js +2 -0
- package/dist/brillouin/types.d.ts +2 -2
- package/dist/chempot-diagram/ChemPotDiagram.svelte +33 -38
- package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +143 -246
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +4 -1
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +652 -976
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +7 -1
- package/dist/chempot-diagram/async-compute.svelte.js +1 -1
- package/dist/chempot-diagram/color.d.ts +3 -6
- package/dist/chempot-diagram/color.js +5 -5
- package/dist/chempot-diagram/compute.d.ts +3 -3
- package/dist/chempot-diagram/compute.js +3 -1
- package/dist/chempot-diagram/controls-state.svelte.d.ts +10 -0
- package/dist/chempot-diagram/controls-state.svelte.js +42 -0
- package/dist/chempot-diagram/export.d.ts +47 -0
- package/dist/chempot-diagram/export.js +133 -0
- package/dist/chempot-diagram/index.d.ts +1 -0
- package/dist/chempot-diagram/index.js +1 -0
- package/dist/chempot-diagram/pointer.d.ts +0 -10
- package/dist/chempot-diagram/pointer.js +4 -4
- package/dist/chempot-diagram/types.d.ts +3 -3
- package/dist/colors/index.d.ts +0 -4
- package/dist/colors/index.js +21 -22
- 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 +96 -98
- package/dist/composition/PieChart.svelte +60 -46
- package/dist/composition/chem-sys.js +3 -2
- package/dist/composition/format.js +4 -3
- package/dist/composition/index.d.ts +2 -0
- package/dist/composition/index.js +14 -0
- package/dist/composition/parse.d.ts +2 -3
- package/dist/composition/parse.js +27 -28
- package/dist/constants.js +11 -9
- package/dist/controls.d.ts +1 -0
- package/dist/controls.js +0 -1
- package/dist/convex-hull/ConvexHull.svelte +20 -25
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -4
- package/dist/convex-hull/ConvexHull2D.svelte +176 -256
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +269 -804
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +236 -755
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullChrome.svelte +264 -0
- package/dist/convex-hull/ConvexHullChrome.svelte.d.ts +30 -0
- package/dist/convex-hull/ConvexHullControls.svelte +128 -49
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +7 -6
- package/dist/convex-hull/ConvexHullInfoPane.svelte +18 -5
- package/dist/convex-hull/ConvexHullInfoPane.svelte.d.ts +6 -5
- package/dist/convex-hull/ConvexHullStats.svelte +210 -351
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +3 -1
- package/dist/convex-hull/ConvexHullTooltip.svelte +16 -14
- package/dist/convex-hull/ConvexHullTooltip.svelte.d.ts +2 -1
- 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/barycentric-coords.d.ts +2 -4
- package/dist/convex-hull/barycentric-coords.js +6 -33
- package/dist/convex-hull/canvas-interactions.svelte.d.ts +79 -0
- package/dist/convex-hull/canvas-interactions.svelte.js +279 -0
- package/dist/convex-hull/helpers.d.ts +47 -17
- package/dist/convex-hull/helpers.js +186 -101
- package/dist/convex-hull/hull-state.svelte.d.ts +44 -0
- package/dist/convex-hull/hull-state.svelte.js +124 -0
- package/dist/convex-hull/index.d.ts +9 -7
- package/dist/convex-hull/index.js +7 -2
- package/dist/convex-hull/thermodynamics.js +109 -937
- package/dist/convex-hull/types.d.ts +12 -4
- package/dist/convex-hull/types.js +12 -0
- package/dist/coordination/CoordinationBarPlot.svelte +41 -43
- 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 +6 -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/ElementTile.svelte.d.ts +1 -1
- 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 -0
- package/dist/element/index.js +3 -0
- package/dist/feedback/DragOverlay.svelte +5 -3
- package/dist/feedback/DragOverlay.svelte.d.ts +2 -1
- package/dist/feedback/StatusMessage.svelte +20 -7
- package/dist/fermi-surface/FermiSlice.svelte +21 -18
- package/dist/fermi-surface/FermiSurface.svelte +78 -165
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +9 -20
- package/dist/fermi-surface/FermiSurfaceControls.svelte +6 -20
- package/dist/fermi-surface/FermiSurfaceScene.svelte +93 -272
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +3 -23
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte +9 -6
- package/dist/fermi-surface/compute.js +14 -13
- package/dist/fermi-surface/export.js +4 -15
- package/dist/fermi-surface/index.d.ts +0 -1
- package/dist/fermi-surface/index.js +0 -1
- package/dist/fermi-surface/parse.d.ts +1 -1
- package/dist/fermi-surface/parse.js +84 -97
- package/dist/fermi-surface/types.d.ts +2 -2
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +161 -185
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +6 -5
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +12 -9
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +5 -5
- package/dist/heatmap-matrix/index.d.ts +8 -2
- package/dist/icons.d.ts +4 -0
- package/dist/icons.js +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/io/ExportPane.svelte +170 -0
- package/dist/io/ExportPane.svelte.d.ts +17 -0
- package/dist/io/decompress.d.ts +2 -1
- package/dist/io/decompress.js +22 -36
- package/dist/io/export.d.ts +9 -1
- package/dist/io/export.js +37 -29
- package/dist/io/fetch.d.ts +2 -4
- package/dist/io/fetch.js +0 -11
- package/dist/io/file-drop.d.ts +7 -0
- package/dist/io/file-drop.js +13 -0
- package/dist/io/index.d.ts +1 -0
- package/dist/io/index.js +3 -0
- package/dist/io/is-binary.d.ts +9 -0
- package/dist/io/is-binary.js +45 -1
- package/dist/io/types.d.ts +13 -0
- 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 +50 -48
- package/dist/labels.d.ts +6 -2
- package/dist/labels.js +44 -14
- package/dist/layout/FullscreenButton.svelte +34 -0
- package/dist/layout/FullscreenButton.svelte.d.ts +10 -0
- package/dist/layout/FullscreenToggle.svelte +12 -12
- 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 +115 -0
- package/dist/layout/ViewerChrome.svelte.d.ts +18 -0
- package/dist/layout/fullscreen.d.ts +4 -0
- package/dist/layout/fullscreen.js +3 -5
- package/dist/layout/fullscreen.svelte.d.ts +8 -0
- package/dist/layout/fullscreen.svelte.js +36 -0
- package/dist/layout/index.d.ts +4 -0
- package/dist/layout/index.js +4 -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 +9 -3
- package/dist/math.js +37 -21
- 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 +11 -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 +115 -146
- 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 +73 -96
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +4 -3
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +4 -2
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +2 -3
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +49 -134
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +3 -4
- 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/colors.js +1 -1
- package/dist/phase-diagram/diagram-input.d.ts +1 -2
- package/dist/phase-diagram/parse.d.ts +2 -1
- 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 +494 -692
- package/dist/plot/bar/BarPlot.svelte.d.ts +9 -15
- package/dist/plot/bar/BarPlotControls.svelte +1 -3
- package/dist/plot/bar/BarPlotControls.svelte.d.ts +5 -7
- package/dist/plot/bar/SpacegroupBarPlot.svelte +31 -37
- package/dist/plot/bar/data.d.ts +5 -4
- package/dist/plot/bar/data.js +2 -1
- package/dist/plot/box/BoxPlot.svelte +361 -439
- package/dist/plot/box/BoxPlot.svelte.d.ts +6 -3
- package/dist/plot/box/BoxPlotControls.svelte.d.ts +1 -1
- package/dist/plot/box/Violin.svelte.d.ts +2 -1
- package/dist/plot/box/box-plot.d.ts +3 -2
- package/dist/plot/box/box-plot.js +6 -3
- package/dist/plot/box/kde.d.ts +2 -1
- package/dist/plot/box/kde.js +4 -4
- package/dist/plot/core/auto-place.d.ts +1 -1
- package/dist/plot/core/auto-place.js +5 -2
- 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 +106 -111
- package/dist/plot/core/components/ColorBar.svelte.d.ts +5 -4
- 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 +21 -11
- package/dist/plot/core/components/Line.svelte.d.ts +5 -2
- package/dist/plot/core/components/PlotAxis.svelte +42 -18
- package/dist/plot/core/components/PlotAxis.svelte.d.ts +2 -1
- package/dist/plot/core/components/PlotControls.svelte +65 -41
- package/dist/plot/core/components/PlotControls.svelte.d.ts +1 -1
- 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 +56 -59
- package/dist/plot/core/components/ReferenceLine3D.svelte.d.ts +4 -4
- package/dist/plot/core/components/ReferencePlane.svelte +9 -10
- package/dist/plot/core/components/ReferencePlane.svelte.d.ts +4 -4
- 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 +29 -476
- package/dist/plot/core/data-transform.js +9 -5
- package/dist/plot/core/fill-utils.d.ts +7 -4
- package/dist/plot/core/fill-utils.js +34 -9
- package/dist/plot/core/index.d.ts +1 -0
- package/dist/plot/core/index.js +1 -0
- package/dist/plot/core/interactions.d.ts +5 -1
- package/dist/plot/core/interactions.js +14 -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/pan-zoom.svelte.d.ts +35 -0
- package/dist/plot/core/pan-zoom.svelte.js +221 -0
- package/dist/plot/core/placed-tween.svelte.d.ts +21 -0
- package/dist/plot/core/placed-tween.svelte.js +68 -0
- package/dist/plot/core/reference-line.d.ts +10 -10
- package/dist/plot/core/reference-line.js +7 -7
- package/dist/plot/core/scales.d.ts +40 -25
- package/dist/plot/core/scales.js +49 -21
- package/dist/plot/core/svg.d.ts +2 -1
- 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 +47 -331
- 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.d.ts +1 -1
- package/dist/plot/core/utils/label-placement.js +5 -5
- package/dist/plot/core/utils.d.ts +2 -1
- package/dist/plot/histogram/Histogram.svelte +373 -631
- package/dist/plot/histogram/Histogram.svelte.d.ts +7 -7
- package/dist/plot/histogram/HistogramControls.svelte +30 -28
- package/dist/plot/histogram/HistogramControls.svelte.d.ts +1 -1
- 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 +79 -73
- package/dist/plot/sankey/Sankey.svelte.d.ts +3 -3
- package/dist/plot/sankey/SankeyControls.svelte +22 -16
- package/dist/plot/sankey/sankey.js +3 -1
- package/dist/plot/scatter/BinnedScatterPlot.svelte +264 -112
- package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +11 -4
- package/dist/plot/scatter/ElementScatter.svelte +4 -1
- package/dist/plot/scatter/ScatterPlot.svelte +623 -821
- package/dist/plot/scatter/ScatterPlot.svelte.d.ts +9 -15
- 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 +6 -12
- package/dist/plot/scatter/binned-scatter-types.js +6 -1
- package/dist/plot/scatter/index.d.ts +1 -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 +40 -60
- package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +6 -14
- package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +17 -15
- package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte.d.ts +5 -5
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +232 -234
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +5 -14
- package/dist/plot/scatter-3d/Surface3D.svelte +9 -17
- package/dist/plot/scatter-3d/Surface3D.svelte.d.ts +4 -3
- package/dist/plot/sunburst/Sunburst.svelte +256 -338
- package/dist/plot/sunburst/Sunburst.svelte.d.ts +4 -3
- 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 +14 -4
- 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 +14 -3
- package/dist/scene/SceneCamera.svelte +62 -0
- package/dist/scene/SceneCamera.svelte.d.ts +19 -0
- package/dist/scene/bind-renderer.svelte.d.ts +2 -0
- package/dist/scene/bind-renderer.svelte.js +22 -0
- package/dist/scene/index.d.ts +4 -0
- package/dist/scene/index.js +5 -0
- package/dist/scene/props.js +52 -0
- package/dist/scene/types.d.ts +26 -0
- package/dist/scene/types.js +1 -0
- package/dist/settings.d.ts +33 -11
- package/dist/settings.js +136 -51
- package/dist/spectral/Bands.svelte +231 -237
- package/dist/spectral/Bands.svelte.d.ts +3 -2
- package/dist/spectral/BandsAndDos.svelte +29 -29
- package/dist/spectral/BrillouinBandsDos.svelte +21 -29
- package/dist/spectral/Dos.svelte +75 -77
- package/dist/spectral/Dos.svelte.d.ts +2 -1
- package/dist/spectral/helpers.d.ts +9 -9
- package/dist/spectral/helpers.js +66 -70
- package/dist/state.svelte.d.ts +0 -7
- package/dist/state.svelte.js +5 -24
- package/dist/structure/Arrow.svelte +6 -21
- 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 +16 -3
- package/dist/structure/CellSelect.svelte +22 -29
- package/dist/structure/CellSelect.svelte.d.ts +2 -1
- 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 +725 -735
- package/dist/structure/Structure.svelte.d.ts +8 -16
- package/dist/structure/StructureCarousel.svelte +630 -0
- package/dist/structure/StructureCarousel.svelte.d.ts +19 -0
- package/dist/structure/StructureControls.svelte +298 -325
- package/dist/structure/StructureControls.svelte.d.ts +5 -3
- package/dist/structure/StructureExportPane.svelte +62 -168
- package/dist/structure/StructureExportPane.svelte.d.ts +4 -5
- package/dist/structure/StructureInfoPane.svelte +77 -69
- package/dist/structure/StructureInfoPane.svelte.d.ts +6 -6
- package/dist/structure/StructureScene.svelte +758 -600
- package/dist/structure/StructureScene.svelte.d.ts +24 -20
- package/dist/structure/StructureViewport.svelte +386 -0
- package/dist/structure/StructureViewport.svelte.d.ts +63 -0
- package/dist/structure/atom-label-placement.d.ts +18 -0
- package/dist/structure/atom-label-placement.js +115 -0
- package/dist/structure/atom-properties.d.ts +3 -1
- package/dist/structure/atom-properties.js +115 -54
- package/dist/structure/bond-order-perception.js +2 -4
- package/dist/structure/bonding.d.ts +12 -2
- package/dist/structure/bonding.js +172 -73
- package/dist/structure/edit-history.d.ts +6 -0
- package/dist/structure/edit-history.js +27 -0
- package/dist/structure/export.d.ts +25 -4
- package/dist/structure/export.js +103 -153
- 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 +18 -1
- package/dist/structure/index.js +15 -2
- package/dist/structure/measure.d.ts +1 -1
- package/dist/structure/measure.js +6 -12
- package/dist/structure/parse.d.ts +5 -3
- package/dist/structure/parse.js +551 -567
- package/dist/structure/partial-occupancy.d.ts +0 -1
- package/dist/structure/partial-occupancy.js +5 -3
- package/dist/structure/pbc.d.ts +1 -1
- package/dist/structure/pbc.js +204 -51
- package/dist/structure/polyhedra.d.ts +40 -0
- package/dist/structure/polyhedra.js +587 -0
- package/dist/structure/site.d.ts +4 -0
- package/dist/structure/site.js +1 -0
- package/dist/structure/supercell.d.ts +1 -2
- package/dist/structure/supercell.js +10 -15
- package/dist/structure/validation.js +5 -6
- package/dist/symmetry/SymmetryElementControls.svelte +69 -0
- package/dist/symmetry/SymmetryElementControls.svelte.d.ts +9 -0
- package/dist/symmetry/SymmetryElements.svelte +350 -0
- package/dist/symmetry/SymmetryElements.svelte.d.ts +24 -0
- package/dist/symmetry/SymmetryStats.svelte +142 -49
- package/dist/symmetry/WyckoffTable.svelte +58 -16
- package/dist/symmetry/WyckoffTable.svelte.d.ts +3 -0
- package/dist/symmetry/cell-transform.js +8 -14
- package/dist/symmetry/index.d.ts +15 -4
- package/dist/symmetry/index.js +308 -81
- package/dist/symmetry/spacegroups.d.ts +5 -1
- package/dist/symmetry/spacegroups.js +18 -3
- package/dist/symmetry/symmetry-elements.d.ts +33 -0
- package/dist/symmetry/symmetry-elements.js +521 -0
- package/dist/symmetry/wyckoff-db.d.ts +9 -0
- package/dist/symmetry/wyckoff-db.js +87 -0
- package/dist/table/HeatmapTable.svelte +1104 -378
- 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 +556 -352
- package/dist/trajectory/Trajectory.svelte.d.ts +10 -22
- package/dist/trajectory/TrajectoryError.svelte +11 -8
- package/dist/trajectory/TrajectoryExportPane.svelte +28 -54
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +4 -5
- package/dist/trajectory/TrajectoryInfoPane.svelte +88 -99
- package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +3 -2
- package/dist/trajectory/constants.js +6 -2
- package/dist/trajectory/extract.js +73 -71
- package/dist/trajectory/format-detect.d.ts +0 -1
- package/dist/trajectory/format-detect.js +13 -17
- package/dist/trajectory/frame-reader.d.ts +2 -1
- package/dist/trajectory/frame-reader.js +74 -136
- package/dist/trajectory/helpers.d.ts +12 -2
- package/dist/trajectory/helpers.js +68 -37
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +3 -0
- package/dist/trajectory/parse/ase.d.ts +9 -1
- package/dist/trajectory/parse/ase.js +47 -32
- package/dist/trajectory/parse/diagnostics.d.ts +3 -0
- package/dist/trajectory/parse/diagnostics.js +14 -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 +3 -4
- package/dist/trajectory/parse/index.js +68 -119
- package/dist/trajectory/parse/lammps.d.ts +0 -2
- package/dist/trajectory/parse/lammps.js +14 -16
- package/dist/trajectory/parse/pymatgen.d.ts +2 -0
- package/dist/trajectory/parse/pymatgen.js +74 -0
- package/dist/trajectory/parse/vasp.js +13 -9
- 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.d.ts +9 -21
- package/dist/trajectory/parse/xyz.js +38 -39
- package/dist/trajectory/plotting.d.ts +8 -10
- package/dist/trajectory/plotting.js +102 -127
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +8 -1
- package/dist/xrd/XrdPlot.svelte +56 -85
- package/dist/xrd/broadening.d.ts +2 -1
- package/dist/xrd/broadening.js +2 -1
- package/dist/xrd/calc-xrd.js +8 -15
- package/dist/xrd/index.d.ts +2 -2
- package/dist/xrd/parse.d.ts +0 -1
- package/dist/xrd/parse.js +56 -73
- package/package.json +77 -36
- package/dist/api/mp.d.ts +0 -6
- package/dist/api/mp.js +0 -22
- package/dist/element/data.json +0 -11864
- package/dist/fermi-surface/marching-cubes.d.ts +0 -2
- package/dist/fermi-surface/marching-cubes.js +0 -2
- package/dist/plot/core/hover-lock.svelte.d.ts +0 -14
- package/dist/plot/core/hover-lock.svelte.js +0 -45
- package/dist/plot/sunburst/SunburstControls.svelte +0 -200
- package/dist/structure/label-placement.d.ts +0 -14
- package/dist/structure/label-placement.js +0 -72
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { D3InterpolateName } from '../colors'
|
|
3
|
-
import {
|
|
3
|
+
import { get_d3_interpolator } from '../colors'
|
|
4
4
|
import type { ElementSymbol } from '../element'
|
|
5
5
|
import { element_data } from '../element'
|
|
6
6
|
import Isosurface from '../isosurface/Isosurface.svelte'
|
|
@@ -9,12 +9,9 @@
|
|
|
9
9
|
import { format_num } from '../labels'
|
|
10
10
|
import type { Vec3 } from '../math'
|
|
11
11
|
import * as math from '../math'
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
VectorColorMode,
|
|
16
|
-
VectorLayerConfig,
|
|
17
|
-
} from '../settings'
|
|
12
|
+
import { bind_renderer, build_orbit_props, SceneCamera } from '../scene'
|
|
13
|
+
import type { SceneControlProps } from '../scene'
|
|
14
|
+
import type { ShowBonds, VectorColorMode, VectorLayerConfig } from '../settings'
|
|
18
15
|
import { DEFAULTS } from '../settings'
|
|
19
16
|
import { create_pulse_animation } from '../effects.svelte'
|
|
20
17
|
import { colors } from '../state.svelte'
|
|
@@ -28,7 +25,6 @@
|
|
|
28
25
|
StructureBond,
|
|
29
26
|
} from './'
|
|
30
27
|
import {
|
|
31
|
-
Arrow,
|
|
32
28
|
atomic_radii,
|
|
33
29
|
Cylinder,
|
|
34
30
|
get_all_site_vectors,
|
|
@@ -37,11 +33,14 @@
|
|
|
37
33
|
Lattice,
|
|
38
34
|
VECTOR_PALETTE,
|
|
39
35
|
} from './'
|
|
36
|
+
import ArrowInstances from './ArrowInstances.svelte'
|
|
37
|
+
import InstancedAtoms from './InstancedAtoms.svelte'
|
|
38
|
+
import SiteLabels from './SiteLabels.svelte'
|
|
40
39
|
import type { AtomColorConfig } from './atom-properties'
|
|
41
|
-
import {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
import { get_orig_site_idx, get_property_colors } from './atom-properties'
|
|
41
|
+
import type { SymmetryElement } from '../symmetry'
|
|
42
|
+
import { has_visible_symmetry_overlay } from '../symmetry/symmetry-elements'
|
|
43
|
+
import SymmetryElements from '../symmetry/SymmetryElements.svelte'
|
|
45
44
|
import * as measure from './measure'
|
|
46
45
|
import {
|
|
47
46
|
compute_slice_geometry,
|
|
@@ -49,53 +48,31 @@
|
|
|
49
48
|
PARTIAL_OCCUPANCY_CAP_ARC,
|
|
50
49
|
} from './partial-occupancy'
|
|
51
50
|
import type { MoyoDataset } from '@spglib/moyo-wasm'
|
|
52
|
-
import { T
|
|
51
|
+
import { T } from '@threlte/core'
|
|
53
52
|
import * as extras from '@threlte/extras'
|
|
54
53
|
import { type ComponentProps, type Snippet, untrack } from 'svelte'
|
|
55
54
|
import { SvelteMap, SvelteSet } from 'svelte/reactivity'
|
|
56
|
-
import {
|
|
55
|
+
import { BufferAttribute, BufferGeometry, Color, DoubleSide, Vector3 } from 'three'
|
|
56
|
+
import type { Mesh, Object3D } from 'three'
|
|
57
57
|
import Bond from './Bond.svelte'
|
|
58
58
|
import type { BondEditResult, BondingStrategy, BondKeyTarget } from './bonding'
|
|
59
59
|
import {
|
|
60
60
|
add_or_restore_bond,
|
|
61
|
-
BONDING_STRATEGIES,
|
|
62
61
|
BOND_ORDER_OPTIONS,
|
|
63
62
|
canonicalize_bond_target,
|
|
63
|
+
compute_bonds,
|
|
64
64
|
delete_bond as apply_delete_bond,
|
|
65
65
|
get_bond_key,
|
|
66
66
|
get_bond_render_matrices,
|
|
67
67
|
get_explicit_bond_metadata,
|
|
68
|
+
get_majority_element,
|
|
68
69
|
set_bond_order as apply_set_bond_order,
|
|
69
70
|
structure_bond_to_bond_pair,
|
|
70
71
|
} from './bonding'
|
|
71
|
-
import {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
} from './index'
|
|
76
|
-
import {
|
|
77
|
-
choose_site_label_offset,
|
|
78
|
-
LABEL_OFFSET_EPS,
|
|
79
|
-
make_label_position_calculator,
|
|
80
|
-
} from './label-placement'
|
|
81
|
-
|
|
82
|
-
type InstancedAtomGroup = {
|
|
83
|
-
element: string
|
|
84
|
-
radius: number
|
|
85
|
-
color: string
|
|
86
|
-
is_image_atom: boolean
|
|
87
|
-
atoms: (typeof atom_data)[number][]
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function instanced_atom_group_key(
|
|
91
|
-
{ element, radius, color, is_image_atom, atoms }: InstancedAtomGroup,
|
|
92
|
-
measure_mode: MeasureMode,
|
|
93
|
-
): string {
|
|
94
|
-
const edit_mode_image = measure_mode === `edit-atoms` && is_image_atom
|
|
95
|
-
return `${element}-${format_num(radius, `.3~`)}-${color}-${
|
|
96
|
-
is_image_atom ? `img` : `base`
|
|
97
|
-
}-${edit_mode_image}-${atoms.length}`
|
|
98
|
-
}
|
|
72
|
+
import { CanvasTooltip, compose_perceived_bonds, perceive_bond_orders } from './index'
|
|
73
|
+
import { choose_site_label_offset, LABEL_OFFSET_EPS } from './atom-label-placement'
|
|
74
|
+
import type { PolyhedraColorMode, Polyhedron } from './polyhedra'
|
|
75
|
+
import { compute_polyhedra, merge_polyhedra_buffers } from './polyhedra'
|
|
99
76
|
|
|
100
77
|
type EditableAtomHitTarget = {
|
|
101
78
|
site_idx: number
|
|
@@ -129,6 +106,7 @@
|
|
|
129
106
|
same_size_atoms = false,
|
|
130
107
|
camera_position = DEFAULTS.structure.camera_position,
|
|
131
108
|
camera_target = undefined,
|
|
109
|
+
camera_direction = undefined,
|
|
132
110
|
camera_projection = DEFAULTS.structure.camera_projection,
|
|
133
111
|
rotation_damping = DEFAULTS.structure.rotation_damping,
|
|
134
112
|
max_zoom = DEFAULTS.structure.max_zoom,
|
|
@@ -168,12 +146,27 @@
|
|
|
168
146
|
auto_bond_order = DEFAULTS.structure.auto_bond_order,
|
|
169
147
|
aromatic_display = DEFAULTS.structure.aromatic_display,
|
|
170
148
|
bonding_options = {},
|
|
149
|
+
show_polyhedra = DEFAULTS.structure.show_polyhedra,
|
|
150
|
+
polyhedra_opacity = DEFAULTS.structure.polyhedra_opacity,
|
|
151
|
+
polyhedra_show_edges = DEFAULTS.structure.polyhedra_show_edges,
|
|
152
|
+
polyhedra_edge_color = DEFAULTS.structure.polyhedra_edge_color,
|
|
153
|
+
polyhedra_color_mode = DEFAULTS.structure.polyhedra_color_mode,
|
|
154
|
+
polyhedra_color = DEFAULTS.structure.polyhedra_color,
|
|
155
|
+
polyhedra_hide_center_atoms = DEFAULTS.structure.polyhedra_hide_center_atoms,
|
|
156
|
+
polyhedra_min_neighbors = DEFAULTS.structure.polyhedra_min_neighbors,
|
|
157
|
+
polyhedra_max_neighbors = DEFAULTS.structure.polyhedra_max_neighbors,
|
|
158
|
+
polyhedra_excluded_elements = DEFAULTS.structure.polyhedra_excluded_elements,
|
|
159
|
+
polyhedra_included_elements = DEFAULTS.structure.polyhedra_included_elements,
|
|
160
|
+
polyhedra_rendered_elements = $bindable<string[]>([]),
|
|
171
161
|
fov = DEFAULTS.structure.fov,
|
|
172
162
|
initial_zoom = DEFAULTS.structure.initial_zoom,
|
|
173
163
|
ambient_light = DEFAULTS.structure.ambient_light,
|
|
174
164
|
directional_light = DEFAULTS.structure.directional_light,
|
|
175
165
|
sphere_segments = DEFAULTS.structure.sphere_segments,
|
|
176
166
|
lattice_props = {},
|
|
167
|
+
symmetry_elements = [],
|
|
168
|
+
symmetry_elements_props = {},
|
|
169
|
+
symmetry_declutter = true,
|
|
177
170
|
atom_label,
|
|
178
171
|
camera_is_moving = $bindable(false),
|
|
179
172
|
width = 0,
|
|
@@ -218,7 +211,8 @@
|
|
|
218
211
|
dragging_atoms = $bindable(false),
|
|
219
212
|
volumetric_data = undefined,
|
|
220
213
|
isosurface_settings = DEFAULT_ISOSURFACE_SETTINGS,
|
|
221
|
-
|
|
214
|
+
interactive = true,
|
|
215
|
+
}: SceneControlProps & {
|
|
222
216
|
structure?: AnyStructure
|
|
223
217
|
base_structure?: AnyStructure // The original structure without image atoms, used for property color calculation
|
|
224
218
|
atom_radius?: number // scale factor for atomic radii
|
|
@@ -226,15 +220,9 @@
|
|
|
226
220
|
// determined by the atomic radius of the element
|
|
227
221
|
camera_position?: [x: number, y: number, z: number] // initial camera position from which to render the scene
|
|
228
222
|
camera_target?: Vec3 // external orbit-controls target for pan synchronization
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
max_zoom?: number
|
|
233
|
-
min_zoom?: number
|
|
234
|
-
rotate_speed?: number // rotation speed. set to 0 to disable rotation.
|
|
235
|
-
zoom_speed?: number // zoom speed. set to 0 to disable zooming.
|
|
236
|
-
pan_speed?: number // pan speed. set to 0 to disable panning.
|
|
237
|
-
zoom_to_cursor?: boolean // zoom toward cursor position instead of scene center
|
|
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
|
|
238
226
|
show_atoms?: boolean
|
|
239
227
|
show_bonds?: ShowBonds
|
|
240
228
|
show_site_labels?: boolean
|
|
@@ -250,30 +238,49 @@
|
|
|
250
238
|
vector_shaft_radius?: number
|
|
251
239
|
vector_arrow_head_radius?: number
|
|
252
240
|
vector_arrow_head_length?: number
|
|
253
|
-
gizmo?: boolean | ComponentProps<typeof extras.Gizmo>
|
|
254
241
|
hovered_idx?: number | null
|
|
255
242
|
hovered_site?: Site | null
|
|
256
243
|
float_fmt?: string
|
|
257
|
-
auto_rotate?: number
|
|
258
|
-
initial_zoom?: number
|
|
259
244
|
bond_thickness?: number
|
|
260
245
|
bond_color?: string
|
|
261
246
|
bonding_strategy?: BondingStrategy
|
|
262
247
|
auto_bond_order?: boolean
|
|
263
248
|
aromatic_display?: `aromatic` | `kekule`
|
|
264
249
|
bonding_options?: Record<string, unknown>
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
250
|
+
show_polyhedra?: ShowBonds // when to render coordination polyhedra
|
|
251
|
+
polyhedra_opacity?: number
|
|
252
|
+
polyhedra_show_edges?: boolean
|
|
253
|
+
polyhedra_edge_color?: string
|
|
254
|
+
polyhedra_color_mode?: PolyhedraColorMode
|
|
255
|
+
polyhedra_color?: string // custom color used when polyhedra_color_mode is 'uniform'
|
|
256
|
+
polyhedra_hide_center_atoms?: boolean
|
|
257
|
+
polyhedra_min_neighbors?: number // min coordination number to form a polyhedron
|
|
258
|
+
polyhedra_max_neighbors?: number // max CN - skips e.g. CN-12 cuboctahedra
|
|
259
|
+
polyhedra_excluded_elements?: readonly string[] // elements never used as polyhedra centers
|
|
260
|
+
polyhedra_included_elements?: readonly string[] // force-include (bypasses spectator hiding)
|
|
261
|
+
polyhedra_rendered_elements?: string[] // (output) elements that currently have polyhedra
|
|
268
262
|
sphere_segments?: number
|
|
269
263
|
lattice_props?: ComponentProps<typeof Lattice>
|
|
264
|
+
// Symmetry elements (from symmetry_elements_from_ops) to overlay on the structure.
|
|
265
|
+
// Fractional coords must refer to the SAME cell as the rendered lattice (moyo
|
|
266
|
+
// operations are in the input-cell frame, i.e. the original untransformed cell).
|
|
267
|
+
symmetry_elements?: SymmetryElement[]
|
|
268
|
+
symmetry_elements_props?: Omit<
|
|
269
|
+
ComponentProps<typeof SymmetryElements>,
|
|
270
|
+
`elements` | `lattice`
|
|
271
|
+
>
|
|
272
|
+
// Auto-reduce visual clutter while a symmetry-element overlay is visible: hides
|
|
273
|
+
// coordination polyhedra and calculated bonds, and shrinks atoms so axes/planes/
|
|
274
|
+
// centers stay readable. Purely derived — toggling the overlay off restores the
|
|
275
|
+
// configured appearance.
|
|
276
|
+
symmetry_declutter?: boolean
|
|
270
277
|
atom_label?: Snippet<[{ site: Site; site_idx: number }]>
|
|
271
278
|
site_label_size?: number
|
|
272
279
|
site_label_offset?: Vec3
|
|
273
280
|
site_label_bg_color?: string
|
|
274
281
|
site_label_color?: string
|
|
275
282
|
site_label_padding?: number
|
|
276
|
-
camera_is_moving?: boolean //
|
|
283
|
+
camera_is_moving?: boolean // bindable: true while orbit controls are active
|
|
277
284
|
width?: number // Viewer dimensions for responsive zoom
|
|
278
285
|
height?: number
|
|
279
286
|
// measurement props
|
|
@@ -291,9 +298,6 @@
|
|
|
291
298
|
active_sites?: number[]
|
|
292
299
|
active_highlight_color?: string
|
|
293
300
|
rotation?: Vec3 // rotation control prop
|
|
294
|
-
// Expose scene and camera for external use (e.g. export pane)
|
|
295
|
-
scene?: Scene
|
|
296
|
-
camera?: Camera
|
|
297
301
|
orbit_controls?: ComponentProps<typeof extras.OrbitControls>[`ref`] // OrbitControls instance
|
|
298
302
|
rotation_target_ref?: Vec3 // Expose rotation target for reset
|
|
299
303
|
initial_computed_zoom?: number // Expose initial zoom for reset
|
|
@@ -314,6 +318,9 @@
|
|
|
314
318
|
dragging_atoms?: boolean // true while TransformControls drag is active (skips expensive recalculations)
|
|
315
319
|
volumetric_data?: VolumetricData // Active volumetric data for isosurface rendering
|
|
316
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
|
|
317
324
|
} = $props()
|
|
318
325
|
|
|
319
326
|
const pulse = create_pulse_animation(
|
|
@@ -322,13 +329,9 @@
|
|
|
322
329
|
)
|
|
323
330
|
let pulse_opacity = $derived(0.15 + 0.25 * pulse.unit)
|
|
324
331
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
camera = threlte.camera.current
|
|
329
|
-
if (threlte.renderer) {
|
|
330
|
-
Object.assign(threlte.renderer.domElement, { __renderer: threlte.renderer })
|
|
331
|
-
}
|
|
332
|
+
bind_renderer((threlte_scene, threlte_camera) => {
|
|
333
|
+
scene = threlte_scene
|
|
334
|
+
camera = threlte_camera
|
|
332
335
|
})
|
|
333
336
|
|
|
334
337
|
// Expose rotation target for external reset
|
|
@@ -345,7 +348,6 @@
|
|
|
345
348
|
initial_computed_zoom = stored_initial_zoom
|
|
346
349
|
})
|
|
347
350
|
|
|
348
|
-
let bond_pairs: BondPair[] = $state([])
|
|
349
351
|
let atom_tooltip_active = $state(false)
|
|
350
352
|
let hovered_bond_key = $state<string | null>(null)
|
|
351
353
|
const ATOM_HOVER_CLEAR_DELAY_MS = 200
|
|
@@ -373,20 +375,18 @@
|
|
|
373
375
|
}, ATOM_HOVER_CLEAR_DELAY_MS)
|
|
374
376
|
}
|
|
375
377
|
|
|
376
|
-
const atom_hover_props = (site_idx: number | null
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if (!disabled && site_idx != null) schedule_atom_hover_clear(site_idx)
|
|
385
|
-
},
|
|
386
|
-
})
|
|
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
|
+
}
|
|
387
386
|
|
|
388
387
|
// Cursor style for the canvas, derived from mode and hover state
|
|
389
388
|
let canvas_cursor = $derived.by(() => {
|
|
389
|
+
if (!interactive) return `default`
|
|
390
390
|
if (measure_mode === `edit-atoms` && add_atom_mode) return `crosshair`
|
|
391
391
|
if (measure_mode === `edit-bonds` && hovered_bond_key != null) {
|
|
392
392
|
return bond_edits_enabled ? `pointer` : `not-allowed`
|
|
@@ -400,7 +400,6 @@
|
|
|
400
400
|
if (measure_mode === `edit-atoms`) {
|
|
401
401
|
const site = structure?.sites?.[hovered_idx]
|
|
402
402
|
if (site?.properties?.orig_site_idx != null) return `not-allowed`
|
|
403
|
-
return `pointer`
|
|
404
403
|
}
|
|
405
404
|
return `pointer`
|
|
406
405
|
}
|
|
@@ -455,8 +454,9 @@
|
|
|
455
454
|
): BondKeyTarget {
|
|
456
455
|
const rendered_target = { site_idx_1, site_idx_2, cell_shift }
|
|
457
456
|
const rendered_key = rendered_bond_key_for(rendered_target)
|
|
458
|
-
return
|
|
459
|
-
canonical_bond_target(rendered_target)
|
|
457
|
+
return (
|
|
458
|
+
find_added_bond_by_rendered_key(rendered_key) ?? canonical_bond_target(rendered_target)
|
|
459
|
+
)
|
|
460
460
|
}
|
|
461
461
|
|
|
462
462
|
const is_image_bond_site = (site_idx: number): boolean =>
|
|
@@ -467,9 +467,11 @@
|
|
|
467
467
|
|
|
468
468
|
const can_edit_bond = (bond: BondKeyTarget): boolean => {
|
|
469
469
|
const target = canonical_bond_target(bond)
|
|
470
|
-
return
|
|
470
|
+
return (
|
|
471
|
+
bond_edits_enabled &&
|
|
471
472
|
!is_image_bond_site(target.site_idx_1) &&
|
|
472
473
|
!is_image_bond_site(target.site_idx_2)
|
|
474
|
+
)
|
|
473
475
|
}
|
|
474
476
|
|
|
475
477
|
const format_bond_order = (order: BondOrder | undefined): string =>
|
|
@@ -481,10 +483,12 @@
|
|
|
481
483
|
cell_shift?: Vec3,
|
|
482
484
|
): BondOrder | undefined {
|
|
483
485
|
const key = get_bond_key(site_idx_1, site_idx_2, cell_shift)
|
|
484
|
-
return
|
|
486
|
+
return (
|
|
487
|
+
find_added_bond_by_rendered_key(key)?.order ??
|
|
485
488
|
bond_order_overrides.find((bond) => matches_bond_key(bond, key))?.order ??
|
|
486
489
|
added_bonds.find((bond) => matches_bond_key(bond, key))?.order ??
|
|
487
490
|
filtered_bond_pairs.find((bond) => matches_bond_key(bond, key))?.bond_order
|
|
491
|
+
)
|
|
488
492
|
}
|
|
489
493
|
|
|
490
494
|
const midpoint = (pos_1: Vec3, pos_2: Vec3): Vec3 => [
|
|
@@ -534,9 +538,10 @@
|
|
|
534
538
|
return site_idx
|
|
535
539
|
}
|
|
536
540
|
|
|
537
|
-
const image_site_idx = structure.sites.findIndex(
|
|
538
|
-
candidate_site
|
|
539
|
-
|
|
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),
|
|
540
545
|
)
|
|
541
546
|
return image_site_idx === -1 ? site_idx : image_site_idx
|
|
542
547
|
}
|
|
@@ -571,10 +576,7 @@
|
|
|
571
576
|
|
|
572
577
|
let label_screen_margin = $derived(site_label_size * 10 + site_label_padding)
|
|
573
578
|
|
|
574
|
-
function get_bond_context_menu_position(
|
|
575
|
-
bond: BondPair,
|
|
576
|
-
event?: BondContextMenuEvent,
|
|
577
|
-
): Vec3 {
|
|
579
|
+
function get_bond_context_menu_position(bond: BondPair, event?: BondContextMenuEvent): Vec3 {
|
|
578
580
|
const parent = event?.object?.parent
|
|
579
581
|
if (!event?.point || !parent) return midpoint(bond.pos_1, bond.pos_2)
|
|
580
582
|
|
|
@@ -615,8 +617,10 @@
|
|
|
615
617
|
): BondPair | undefined => {
|
|
616
618
|
const rendered_key = rendered_bond_key_for(target)
|
|
617
619
|
const canonical_key = bond_key_for(canonical_target)
|
|
618
|
-
return
|
|
620
|
+
return (
|
|
621
|
+
filtered_bond_pairs.find((bond) => rendered_bond_key_for(bond) === rendered_key) ??
|
|
619
622
|
filtered_bond_pairs.find((bond) => bond_key_for(bond) === canonical_key)
|
|
623
|
+
)
|
|
620
624
|
}
|
|
621
625
|
|
|
622
626
|
function open_bond_order_menu_for_target(
|
|
@@ -628,6 +632,7 @@
|
|
|
628
632
|
}
|
|
629
633
|
|
|
630
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
|
|
631
636
|
const rendered_target = { site_idx_1, site_idx_2 }
|
|
632
637
|
if (!can_edit_bond(rendered_target)) return
|
|
633
638
|
const edit_state = current_bond_edit_state()
|
|
@@ -685,11 +690,7 @@
|
|
|
685
690
|
const target = resolve_bond_edit_target(site_idx_1, site_idx_2, cell_shift)
|
|
686
691
|
if (!can_edit_bond(target)) return
|
|
687
692
|
apply_bond_edit_result(
|
|
688
|
-
apply_delete_bond(
|
|
689
|
-
current_bond_edit_state(),
|
|
690
|
-
target,
|
|
691
|
-
editable_perceived_bond_pairs,
|
|
692
|
-
),
|
|
693
|
+
apply_delete_bond(current_bond_edit_state(), target, editable_perceived_bond_pairs),
|
|
693
694
|
)
|
|
694
695
|
}
|
|
695
696
|
|
|
@@ -703,12 +704,10 @@
|
|
|
703
704
|
// intercept the same native click, only the first intersection should fire.
|
|
704
705
|
// All threlte intersection events from one click share the same nativeEvent ref.
|
|
705
706
|
let last_native_event: Event | null = null
|
|
706
|
-
//
|
|
707
|
-
// 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.
|
|
708
709
|
let last_edit_bonds_pointerdown_site_idx: number | null = null
|
|
709
|
-
let clear_edit_bonds_pointerdown_site_timeout:
|
|
710
|
-
| ReturnType<typeof setTimeout>
|
|
711
|
-
| null = null
|
|
710
|
+
let clear_edit_bonds_pointerdown_site_timeout: ReturnType<typeof setTimeout> | null = null
|
|
712
711
|
|
|
713
712
|
function remember_edit_bonds_pointerdown_site(site_idx: number) {
|
|
714
713
|
last_edit_bonds_pointerdown_site_idx = site_idx
|
|
@@ -737,9 +736,62 @@
|
|
|
737
736
|
return true
|
|
738
737
|
}
|
|
739
738
|
|
|
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
|
|
751
|
+
}
|
|
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
|
+
}
|
|
791
|
+
|
|
740
792
|
function toggle_selection(site_index: number, evt?: Event) {
|
|
741
793
|
evt?.stopPropagation?.()
|
|
742
|
-
const event_with_native = evt as Event & { nativeEvent?: unknown } | undefined
|
|
794
|
+
const event_with_native = evt as (Event & { nativeEvent?: unknown }) | undefined
|
|
743
795
|
const native_event = event_with_native?.nativeEvent ?? evt
|
|
744
796
|
if (native_event instanceof Event) {
|
|
745
797
|
if (native_event === last_native_event) return
|
|
@@ -747,6 +799,9 @@
|
|
|
747
799
|
}
|
|
748
800
|
|
|
749
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
|
|
750
805
|
if (bond_edit_mode === `delete`) {
|
|
751
806
|
measured_sites = []
|
|
752
807
|
selected_sites = []
|
|
@@ -771,12 +826,16 @@
|
|
|
771
826
|
}
|
|
772
827
|
|
|
773
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
|
|
774
831
|
// Block image atoms (detected by orig_site_idx property from PBC)
|
|
775
832
|
const site = structure?.sites?.[site_index]
|
|
776
833
|
if (site?.properties?.orig_site_idx != null) return
|
|
777
834
|
|
|
778
835
|
const is_selected = selected_sites.includes(site_index)
|
|
779
|
-
|
|
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
|
|
780
839
|
|
|
781
840
|
// In edit-atoms mode, selected_sites and measured_sites always stay in sync
|
|
782
841
|
let new_sites: number[]
|
|
@@ -826,11 +885,15 @@
|
|
|
826
885
|
$effect(() => {
|
|
827
886
|
const count = structure?.sites?.length ?? 0
|
|
828
887
|
if (count <= 0) {
|
|
829
|
-
measured_sites = []
|
|
888
|
+
if (untrack(() => measured_sites.length) > 0) measured_sites = []
|
|
830
889
|
return
|
|
831
890
|
}
|
|
832
891
|
untrack(() => {
|
|
833
|
-
|
|
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
|
|
834
897
|
})
|
|
835
898
|
})
|
|
836
899
|
|
|
@@ -842,9 +905,7 @@
|
|
|
842
905
|
$effect.pre(() => {
|
|
843
906
|
hovered_site = structure?.sites?.[hovered_idx ?? -1] ?? null
|
|
844
907
|
})
|
|
845
|
-
let lattice = $derived(
|
|
846
|
-
structure && `lattice` in structure ? structure.lattice : null,
|
|
847
|
-
)
|
|
908
|
+
let lattice = $derived(structure && `lattice` in structure ? structure.lattice : null)
|
|
848
909
|
|
|
849
910
|
let visual_lattice = $derived(
|
|
850
911
|
base_structure && `lattice` in base_structure ? base_structure.lattice : lattice,
|
|
@@ -852,11 +913,13 @@
|
|
|
852
913
|
|
|
853
914
|
let rotation_target = $derived(
|
|
854
915
|
lattice
|
|
855
|
-
?
|
|
916
|
+
? math.scale(math.add(...lattice.matrix), 0.5)
|
|
856
917
|
: structure
|
|
857
|
-
|
|
858
|
-
|
|
918
|
+
? get_center_of_mass(structure)
|
|
919
|
+
: ([0, 0, 0] as Vec3),
|
|
859
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)
|
|
860
923
|
|
|
861
924
|
let structure_size = $derived.by(() => {
|
|
862
925
|
if (lattice) return (lattice.a + lattice.b + lattice.c) / 2
|
|
@@ -873,8 +936,8 @@
|
|
|
873
936
|
// Excludes PBC image atoms (orig_site_idx) so toggling image atoms doesn't affect arrow sizing.
|
|
874
937
|
let char_atom_spacing = $derived.by(() => {
|
|
875
938
|
if (!lattice || !structure?.sites?.length) return structure_size
|
|
876
|
-
const n_real = structure.sites.filter(
|
|
877
|
-
site.properties?.orig_site_idx == null
|
|
939
|
+
const n_real = structure.sites.filter(
|
|
940
|
+
(site) => site.properties?.orig_site_idx == null,
|
|
878
941
|
).length
|
|
879
942
|
return n_real > 0 ? Math.cbrt(lattice.volume / n_real) : structure_size
|
|
880
943
|
})
|
|
@@ -905,9 +968,19 @@
|
|
|
905
968
|
|
|
906
969
|
// Using $state because this is mutated in an effect based on viewport/structure size
|
|
907
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]
|
|
908
976
|
$effect(() => {
|
|
909
977
|
if (!(width > 0) || !(height > 0)) return
|
|
910
|
-
|
|
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
|
+
)
|
|
911
984
|
const viewer_min_dim = Math.min(width, height)
|
|
912
985
|
const scale_factor = viewer_min_dim / (structure_max_dim * 50) // 50px per unit
|
|
913
986
|
let new_zoom = initial_zoom * scale_factor
|
|
@@ -916,24 +989,69 @@
|
|
|
916
989
|
computed_zoom = new_zoom
|
|
917
990
|
})
|
|
918
991
|
|
|
919
|
-
$effect.pre(() => {
|
|
992
|
+
$effect.pre(() => {
|
|
993
|
+
// Simple initial camera auto-position: proportional to structure size and fov
|
|
920
994
|
if (camera_position.every((val) => val === 0) && structure) {
|
|
921
995
|
stored_initial_zoom = undefined
|
|
922
996
|
const distance = Math.max(1, structure_size) * (60 / fov)
|
|
923
|
-
|
|
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]
|
|
924
1009
|
}
|
|
925
1010
|
})
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1011
|
+
// Whether a never|always|crystals|molecules setting applies to the current structure
|
|
1012
|
+
const applies_to_structure = (when: ShowBonds): boolean =>
|
|
1013
|
+
when === `always` ||
|
|
1014
|
+
(when === `crystals` && Boolean(lattice)) ||
|
|
1015
|
+
(when === `molecules` && !lattice)
|
|
1016
|
+
|
|
1017
|
+
// Declutter while a symmetry-element overlay actually draws something (elements present
|
|
1018
|
+
// AND an enabled kind among them): hide coordination polyhedra/bonds and shrink atoms so
|
|
1019
|
+
// axes/planes/centers stay readable. Gating on visibility (not just `symmetry_elements`)
|
|
1020
|
+
// avoids hiding everything when nothing renders in its place — e.g. an inversion-only
|
|
1021
|
+
// cell under the rotation-only default. Pure derived overrides: the configured
|
|
1022
|
+
// appearance returns untouched the moment the overlay goes away.
|
|
1023
|
+
const declutter_active = $derived(
|
|
1024
|
+
symmetry_declutter &&
|
|
1025
|
+
has_visible_symmetry_overlay(symmetry_elements, symmetry_elements_props.show_kinds),
|
|
1026
|
+
)
|
|
1027
|
+
const effective_show_polyhedra: ShowBonds = $derived(
|
|
1028
|
+
declutter_active ? `never` : show_polyhedra,
|
|
1029
|
+
)
|
|
1030
|
+
// Calculated bonds are hidden in declutter mode (only their cylinders — bond_pairs
|
|
1031
|
+
// stay computed so tooltips and manually added bonds keep working)
|
|
1032
|
+
const effective_show_bonds: ShowBonds = $derived(declutter_active ? `never` : show_bonds)
|
|
1033
|
+
const effective_atom_radius = $derived(declutter_active ? atom_radius * 0.6 : atom_radius)
|
|
1034
|
+
|
|
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
|
|
1048
|
+
const want_bonds = applies_to_structure(show_bonds)
|
|
1049
|
+
const want_polyhedra = applies_to_structure(effective_show_polyhedra)
|
|
1050
|
+
last_bond_pairs =
|
|
1051
|
+
structure && (want_bonds || want_polyhedra)
|
|
1052
|
+
? compute_bonds(structure, bonding_strategy, bonding_options)
|
|
1053
|
+
: []
|
|
1054
|
+
return last_bond_pairs
|
|
937
1055
|
})
|
|
938
1056
|
|
|
939
1057
|
// Compute property-based colors when not using element coloring
|
|
@@ -957,9 +1075,16 @@
|
|
|
957
1075
|
return total_occu > 0 ? weighted_sum / total_occu : 1
|
|
958
1076
|
}
|
|
959
1077
|
|
|
1078
|
+
// Disordered sites are often stored as separate split sites (one species each)
|
|
1079
|
+
// at the same position; merge_split_partial_sites groups them into one render
|
|
1080
|
+
// site whose `species` holds every element. Shared by atom rendering and the
|
|
1081
|
+
// hover tooltip so it lists all elements, not just the majority one.
|
|
1082
|
+
let render_sites = $derived(
|
|
1083
|
+
structure?.sites ? merge_split_partial_sites(structure.sites, hidden_elements) : [],
|
|
1084
|
+
)
|
|
1085
|
+
|
|
960
1086
|
let atom_data = $derived.by(() => {
|
|
961
|
-
if (!show_atoms
|
|
962
|
-
const render_sites = merge_split_partial_sites(structure.sites, hidden_elements)
|
|
1087
|
+
if (!show_atoms) return []
|
|
963
1088
|
return render_sites.flatMap(({ site_idx, site, is_image_atom }) => {
|
|
964
1089
|
const orig_idx = get_orig_site_idx(site, site_idx)
|
|
965
1090
|
|
|
@@ -967,19 +1092,34 @@
|
|
|
967
1092
|
const prop_val = property_colors?.values[orig_idx]
|
|
968
1093
|
if (prop_val !== undefined && hidden_prop_vals.has(prop_val)) return []
|
|
969
1094
|
|
|
1095
|
+
// Optionally hide atoms at the center of a rendered polyhedron
|
|
1096
|
+
if (polyhedra_hide_center_atoms && polyhedra_center_site_idxs.has(site_idx)) {
|
|
1097
|
+
return []
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// Phase-2 PBC images exist only to complete bonds/coordination polyhedra at
|
|
1101
|
+
// cell faces. When neither renders (polyhedra toggled off, symmetry declutter,
|
|
1102
|
+
// …) they'd float disconnected outside the cell — hide them.
|
|
1103
|
+
if (
|
|
1104
|
+
site.properties?.completion_image &&
|
|
1105
|
+
!applies_to_structure(effective_show_bonds) &&
|
|
1106
|
+
!applies_to_structure(effective_show_polyhedra)
|
|
1107
|
+
)
|
|
1108
|
+
return []
|
|
1109
|
+
|
|
970
1110
|
// Calculate radius: same_size > site override > element override > default
|
|
971
1111
|
// All radii scale uniformly with atom_radius for consistent slider behavior
|
|
972
1112
|
const base_radius = same_size_atoms
|
|
973
1113
|
? 1
|
|
974
|
-
: site_radius_overrides?.get(site_idx) ?? calc_weighted_radius(site)
|
|
975
|
-
const radius = base_radius *
|
|
1114
|
+
: (site_radius_overrides?.get(site_idx) ?? calc_weighted_radius(site))
|
|
1115
|
+
const radius = base_radius * effective_atom_radius
|
|
976
1116
|
|
|
977
1117
|
// Use property color if available (e.g. coordination number, Wyckoff position)
|
|
978
1118
|
// Otherwise, each species gets its own element color (important for disordered sites)
|
|
979
1119
|
const site_property_color = property_colors?.colors[orig_idx]
|
|
980
1120
|
|
|
981
|
-
const visible_species = site.species.filter(
|
|
982
|
-
!hidden_elements.has(element)
|
|
1121
|
+
const visible_species = site.species.filter(
|
|
1122
|
+
({ element }) => !hidden_elements.has(element),
|
|
983
1123
|
)
|
|
984
1124
|
const slice_geometry = compute_slice_geometry(visible_species)
|
|
985
1125
|
return slice_geometry.map((slice_data) => {
|
|
@@ -1007,13 +1147,12 @@
|
|
|
1007
1147
|
const is_site_visible = (site_idx: number): boolean => {
|
|
1008
1148
|
if (!structure?.sites) return false
|
|
1009
1149
|
const site = structure.sites[site_idx]
|
|
1010
|
-
const has_visible_element = site?.species.some(
|
|
1011
|
-
!hidden_elements.has(element)
|
|
1150
|
+
const has_visible_element = site?.species.some(
|
|
1151
|
+
({ element }) => !hidden_elements.has(element),
|
|
1012
1152
|
)
|
|
1013
1153
|
const orig_idx = get_orig_site_idx(site, site_idx)
|
|
1014
1154
|
const prop_val = property_colors?.values[orig_idx]
|
|
1015
|
-
const prop_visible = prop_val === undefined ||
|
|
1016
|
-
!hidden_prop_vals.has(prop_val)
|
|
1155
|
+
const prop_visible = prop_val === undefined || !hidden_prop_vals.has(prop_val)
|
|
1017
1156
|
return has_visible_element && prop_visible
|
|
1018
1157
|
}
|
|
1019
1158
|
|
|
@@ -1039,19 +1178,17 @@
|
|
|
1039
1178
|
})
|
|
1040
1179
|
|
|
1041
1180
|
let editable_perceived_bond_pairs = $derived(
|
|
1042
|
-
|
|
1181
|
+
interactive && bond_edits_enabled
|
|
1182
|
+
? perceived_bond_pairs.map((bond) => ({ ...bond, ...canonical_bond_target(bond) }))
|
|
1183
|
+
: [],
|
|
1043
1184
|
)
|
|
1044
1185
|
|
|
1045
1186
|
let filtered_bond_pairs = $derived.by(() => {
|
|
1046
1187
|
if (!structure?.sites) return perceived_bond_pairs
|
|
1047
1188
|
|
|
1048
1189
|
// Build set of removed bond keys for efficient lookup
|
|
1049
|
-
const removed_keys = new Set(
|
|
1050
|
-
|
|
1051
|
-
)
|
|
1052
|
-
const added_keys = new Set(
|
|
1053
|
-
added_bonds.map(bond_key_for),
|
|
1054
|
-
)
|
|
1190
|
+
const removed_keys = new Set(removed_bonds.map(bond_key_for))
|
|
1191
|
+
const added_keys = new Set(added_bonds.map(bond_key_for))
|
|
1055
1192
|
const order_overrides = new Map(
|
|
1056
1193
|
bond_order_overrides.map((bond) => [bond_key_for(bond), bond.order]),
|
|
1057
1194
|
)
|
|
@@ -1079,20 +1216,117 @@
|
|
|
1079
1216
|
return [...calculated, ...added]
|
|
1080
1217
|
})
|
|
1081
1218
|
|
|
1219
|
+
// Bonds drawn as cylinders. When show_bonds doesn't apply, calculated bonds are
|
|
1220
|
+
// hidden but manually added bonds stay visible (bond_pairs may still be computed
|
|
1221
|
+
// for polyhedra, so this can't rely on bond_pairs being empty).
|
|
1222
|
+
let bonds_to_render = $derived.by(() => {
|
|
1223
|
+
if (applies_to_structure(effective_show_bonds)) return filtered_bond_pairs
|
|
1224
|
+
const added_keys = new Set(added_bonds.map(bond_key_for))
|
|
1225
|
+
return filtered_bond_pairs.filter((bond) => added_keys.has(bond_key_for(bond)))
|
|
1226
|
+
})
|
|
1227
|
+
|
|
1082
1228
|
let editable_bond_pairs = $derived(
|
|
1083
|
-
bond_edits_enabled ?
|
|
1229
|
+
interactive && bond_edits_enabled ? bonds_to_render.filter(can_edit_bond) : [],
|
|
1084
1230
|
)
|
|
1085
1231
|
|
|
1232
|
+
// Coordination polyhedra around cation-like centers, derived from the same
|
|
1233
|
+
// (edited, filtered) bond graph as rendered bonds so the two never disagree.
|
|
1234
|
+
// Colors are resolved in polyhedra_buffers below, so color-scheme/mode changes
|
|
1235
|
+
// never recompute the hull geometry.
|
|
1236
|
+
let polyhedra: Polyhedron[] = $derived.by(() => {
|
|
1237
|
+
if (
|
|
1238
|
+
!structure?.sites ||
|
|
1239
|
+
dragging_atoms ||
|
|
1240
|
+
!applies_to_structure(effective_show_polyhedra) ||
|
|
1241
|
+
filtered_bond_pairs.length === 0
|
|
1242
|
+
)
|
|
1243
|
+
return []
|
|
1244
|
+
return compute_polyhedra(structure, filtered_bond_pairs, {
|
|
1245
|
+
min_neighbors: polyhedra_min_neighbors,
|
|
1246
|
+
max_neighbors: polyhedra_max_neighbors,
|
|
1247
|
+
excluded_center_elements: polyhedra_excluded_elements,
|
|
1248
|
+
included_center_elements: polyhedra_included_elements,
|
|
1249
|
+
})
|
|
1250
|
+
})
|
|
1251
|
+
|
|
1252
|
+
// Color of a site: property color (coordination/Wyckoff modes) or element color
|
|
1253
|
+
const polyhedra_site_color = (site_idx: number): string => {
|
|
1254
|
+
const site = structure?.sites[site_idx]
|
|
1255
|
+
const orig_idx = get_orig_site_idx(site, site_idx)
|
|
1256
|
+
const element = get_majority_element(site)
|
|
1257
|
+
return (
|
|
1258
|
+
property_colors?.colors[orig_idx] ?? (element && colors.element?.[element]) ?? `#808080`
|
|
1259
|
+
)
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// Separate derived so material-only changes (opacity, edge color) don't rebuild
|
|
1263
|
+
// buffers and color changes don't rebuild hulls
|
|
1264
|
+
let polyhedra_buffers = $derived.by(() => {
|
|
1265
|
+
if (polyhedra.length === 0) return null
|
|
1266
|
+
const get_vertex_color = (poly: Polyhedron, vertex_idx: number): string => {
|
|
1267
|
+
if (polyhedra_color_mode === `uniform`) return polyhedra_color
|
|
1268
|
+
if (polyhedra_color_mode === `center`) {
|
|
1269
|
+
return polyhedra_site_color(poly.center_site_idx)
|
|
1270
|
+
}
|
|
1271
|
+
// 'vertex' (default): each corner takes the color of the atom that forms it
|
|
1272
|
+
return polyhedra_site_color(poly.vertex_site_idxs[vertex_idx])
|
|
1273
|
+
}
|
|
1274
|
+
return merge_polyhedra_buffers(polyhedra, get_vertex_color)
|
|
1275
|
+
})
|
|
1276
|
+
|
|
1277
|
+
let polyhedra_center_site_idxs = $derived(
|
|
1278
|
+
new Set(polyhedra.map((poly) => poly.center_site_idx)),
|
|
1279
|
+
)
|
|
1280
|
+
|
|
1281
|
+
// Publish which elements currently anchor polyhedra (consumed by controls so
|
|
1282
|
+
// per-element toggles reflect the actual render state incl. spectator hiding)
|
|
1283
|
+
$effect(() => {
|
|
1284
|
+
if (!interactive) return
|
|
1285
|
+
const elems = [...new Set(polyhedra.map((poly) => poly.center_element))].sort()
|
|
1286
|
+
if (elems.join(`,`) !== polyhedra_rendered_elements.join(`,`)) {
|
|
1287
|
+
polyhedra_rendered_elements = elems
|
|
1288
|
+
}
|
|
1289
|
+
})
|
|
1290
|
+
|
|
1291
|
+
// Geometries with proper disposal on dependency change (same pattern as ReferencePlane)
|
|
1292
|
+
const buffer_geometry = (attrs: Record<string, Float32Array>): BufferGeometry => {
|
|
1293
|
+
const geo = new BufferGeometry()
|
|
1294
|
+
for (const [name, array] of Object.entries(attrs)) {
|
|
1295
|
+
geo.setAttribute(name, new BufferAttribute(array, 3))
|
|
1296
|
+
}
|
|
1297
|
+
return geo
|
|
1298
|
+
}
|
|
1299
|
+
let polyhedra_geometry: BufferGeometry | null = $state(null)
|
|
1300
|
+
$effect(() => {
|
|
1301
|
+
let geo: BufferGeometry | null = null
|
|
1302
|
+
if (polyhedra_buffers && polyhedra_buffers.triangle_count > 0) {
|
|
1303
|
+
const { positions: position, colors: color } = polyhedra_buffers
|
|
1304
|
+
geo = buffer_geometry({ position, color })
|
|
1305
|
+
geo.computeVertexNormals() // non-indexed -> per-face normals (flat shading)
|
|
1306
|
+
}
|
|
1307
|
+
polyhedra_geometry = geo
|
|
1308
|
+
return () => geo?.dispose()
|
|
1309
|
+
})
|
|
1310
|
+
|
|
1311
|
+
let polyhedra_edge_geometry: BufferGeometry | null = $state(null)
|
|
1312
|
+
$effect(() => {
|
|
1313
|
+
const geo =
|
|
1314
|
+
polyhedra_show_edges && polyhedra_buffers && polyhedra_buffers.edge_count > 0
|
|
1315
|
+
? buffer_geometry({ position: polyhedra_buffers.edge_positions })
|
|
1316
|
+
: null
|
|
1317
|
+
polyhedra_edge_geometry = geo
|
|
1318
|
+
return () => geo?.dispose()
|
|
1319
|
+
})
|
|
1320
|
+
|
|
1086
1321
|
let smart_site_label_offsets = $derived.by(() => {
|
|
1087
|
-
|
|
1088
|
-
|
|
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>()
|
|
1325
|
+
if (bonds_to_render.length === 0) return offsets
|
|
1089
1326
|
|
|
1090
|
-
const bond_directions_by_site = new
|
|
1327
|
+
const bond_directions_by_site = new Map<number, Vec3[]>()
|
|
1091
1328
|
const add_bond_direction = (site_idx: number, pos_1: Vec3, pos_2: Vec3) => {
|
|
1092
|
-
const direction = math.normalize_vec(
|
|
1093
|
-
math.subtract(pos_2, pos_1),
|
|
1094
|
-
[0, 0, 0],
|
|
1095
|
-
)
|
|
1329
|
+
const direction = math.normalize_vec(math.subtract(pos_2, pos_1), [0, 0, 0])
|
|
1096
1330
|
if (Math.hypot(...direction) < LABEL_OFFSET_EPS) return
|
|
1097
1331
|
bond_directions_by_site.set(site_idx, [
|
|
1098
1332
|
...(bond_directions_by_site.get(site_idx) ?? []),
|
|
@@ -1100,7 +1334,7 @@
|
|
|
1100
1334
|
])
|
|
1101
1335
|
}
|
|
1102
1336
|
|
|
1103
|
-
for (const { site_idx_1, site_idx_2, pos_1, pos_2 } of
|
|
1337
|
+
for (const { site_idx_1, site_idx_2, pos_1, pos_2 } of bonds_to_render) {
|
|
1104
1338
|
add_bond_direction(site_idx_1, pos_1, pos_2)
|
|
1105
1339
|
add_bond_direction(site_idx_2, pos_2, pos_1)
|
|
1106
1340
|
}
|
|
@@ -1111,7 +1345,7 @@
|
|
|
1111
1345
|
})
|
|
1112
1346
|
|
|
1113
1347
|
let instanced_bond_groups = $derived.by(() => {
|
|
1114
|
-
if (!structure?.sites ||
|
|
1348
|
+
if (!structure?.sites || bonds_to_render.length === 0) return []
|
|
1115
1349
|
|
|
1116
1350
|
const group = {
|
|
1117
1351
|
thickness: bond_thickness,
|
|
@@ -1124,14 +1358,14 @@
|
|
|
1124
1358
|
}[],
|
|
1125
1359
|
}
|
|
1126
1360
|
|
|
1127
|
-
for (const bond_data of
|
|
1361
|
+
for (const bond_data of bonds_to_render) {
|
|
1128
1362
|
const site_a = structure.sites[bond_data.site_idx_1]
|
|
1129
1363
|
const site_b = structure.sites[bond_data.site_idx_2]
|
|
1130
1364
|
|
|
1131
1365
|
const get_majority_color = (site: typeof site_a) => {
|
|
1132
1366
|
if (!site?.species || site.species.length === 0) return bond_color
|
|
1133
1367
|
const majority_species = site.species.reduce((max, spec) =>
|
|
1134
|
-
spec.occu > max.occu ? spec : max
|
|
1368
|
+
spec.occu > max.occu ? spec : max,
|
|
1135
1369
|
)
|
|
1136
1370
|
return colors.element?.[majority_species.element] || bond_color
|
|
1137
1371
|
}
|
|
@@ -1147,15 +1381,35 @@
|
|
|
1147
1381
|
})
|
|
1148
1382
|
|
|
1149
1383
|
let radius_by_site_idx = $derived.by(() => {
|
|
1150
|
-
const map = new
|
|
1384
|
+
const map = new Map<number, number>()
|
|
1151
1385
|
for (const atom of atom_data) {
|
|
1152
1386
|
if (!map.has(atom.site_idx)) map.set(atom.site_idx, atom.radius)
|
|
1153
1387
|
}
|
|
1154
1388
|
return map
|
|
1155
1389
|
})
|
|
1156
1390
|
|
|
1391
|
+
// Partial-occupancy atoms render as separate wedge (lune) meshes that converge
|
|
1392
|
+
// to a point at the sphere's poles, leaving the ball hard to hover from some
|
|
1393
|
+
// angles. Give each such site one invisible full-sphere hit target so it's as
|
|
1394
|
+
// reliably hoverable as an ordered atom (single solid sphere). One per site.
|
|
1395
|
+
let partial_hit_targets = $derived.by(() => {
|
|
1396
|
+
if (!interactive) return []
|
|
1397
|
+
const targets = new Map<number, EditableAtomHitTarget & { is_image_atom: boolean }>()
|
|
1398
|
+
for (const atom of atom_data) {
|
|
1399
|
+
if (!atom.has_partial_occupancy || targets.has(atom.site_idx)) continue
|
|
1400
|
+
targets.set(atom.site_idx, {
|
|
1401
|
+
site_idx: atom.site_idx,
|
|
1402
|
+
position: atom.position,
|
|
1403
|
+
radius: atom.radius,
|
|
1404
|
+
is_image_atom: atom.is_image_atom,
|
|
1405
|
+
})
|
|
1406
|
+
}
|
|
1407
|
+
return [...targets.values()]
|
|
1408
|
+
})
|
|
1409
|
+
|
|
1157
1410
|
let editable_atom_hit_targets = $derived.by(() => {
|
|
1158
1411
|
if (
|
|
1412
|
+
!interactive ||
|
|
1159
1413
|
measure_mode !== `edit-bonds` ||
|
|
1160
1414
|
bond_edit_mode !== `add` ||
|
|
1161
1415
|
!bond_edits_enabled
|
|
@@ -1163,7 +1417,7 @@
|
|
|
1163
1417
|
return []
|
|
1164
1418
|
}
|
|
1165
1419
|
|
|
1166
|
-
const targets = new
|
|
1420
|
+
const targets = new Map<number, EditableAtomHitTarget>()
|
|
1167
1421
|
for (const atom of atom_data) {
|
|
1168
1422
|
if (!can_select_bond_site(atom.site_idx)) continue
|
|
1169
1423
|
if (targets.has(atom.site_idx)) continue
|
|
@@ -1179,13 +1433,47 @@
|
|
|
1179
1433
|
// Get radius for a site (for highlight fallback when site is hidden/filtered)
|
|
1180
1434
|
// Checks site_radius_overrides first for consistency with visible atoms
|
|
1181
1435
|
const get_site_radius = (site: Site, site_idx: number | null): number => {
|
|
1182
|
-
const override = site_idx !== null
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
const base_radius = same_size_atoms ? 1 : override ?? calc_weighted_radius(site)
|
|
1186
|
-
return base_radius * atom_radius
|
|
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))
|
|
1438
|
+
return base_radius * effective_atom_radius
|
|
1187
1439
|
}
|
|
1188
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
|
+
|
|
1189
1477
|
// Interpolate between spin-down (#3498db blue) and spin-up (#e74c3c red)
|
|
1190
1478
|
// based on the z-component direction of a magnetic vector
|
|
1191
1479
|
function spin_direction_color(vec: Vec3): string {
|
|
@@ -1194,9 +1482,9 @@
|
|
|
1194
1482
|
const red = Math.round(52 + (231 - 52) * z_frac)
|
|
1195
1483
|
const grn = Math.round(152 + (76 - 152) * z_frac)
|
|
1196
1484
|
const blu = Math.round(219 + (60 - 219) * z_frac)
|
|
1197
|
-
return `#${red.toString(16).padStart(2, `0`)}${
|
|
1198
|
-
|
|
1199
|
-
|
|
1485
|
+
return `#${red.toString(16).padStart(2, `0`)}${grn
|
|
1486
|
+
.toString(16)
|
|
1487
|
+
.padStart(2, `0`)}${blu.toString(16).padStart(2, `0`)}`
|
|
1200
1488
|
}
|
|
1201
1489
|
|
|
1202
1490
|
// Build one arrow layer per visible vector key. Auto-scales the longest
|
|
@@ -1216,7 +1504,7 @@
|
|
|
1216
1504
|
let max_mag = 0
|
|
1217
1505
|
const site_vec_maps = structure.sites.map((site, site_idx) => {
|
|
1218
1506
|
if (!is_site_visible(site_idx)) return null
|
|
1219
|
-
const map = new
|
|
1507
|
+
const map = new Map<string, Vec3>()
|
|
1220
1508
|
for (const { key, vec } of get_all_site_vectors(site)) {
|
|
1221
1509
|
map.set(key, vec)
|
|
1222
1510
|
if (active_set.has(key)) {
|
|
@@ -1228,9 +1516,7 @@
|
|
|
1228
1516
|
|
|
1229
1517
|
// When normalize is on, treat all magnitudes as 1 so arrows have equal length
|
|
1230
1518
|
const effective_max = vector_normalize ? 1 : max_mag
|
|
1231
|
-
const auto_scale = effective_max > 1e-10
|
|
1232
|
-
? (char_atom_spacing * 1.8) / effective_max
|
|
1233
|
-
: 1
|
|
1519
|
+
const auto_scale = effective_max > 1e-10 ? (char_atom_spacing * 1.8) / effective_max : 1
|
|
1234
1520
|
const is_single = active_keys.length === 1
|
|
1235
1521
|
const effective_global_scale = auto_scale * vector_scale
|
|
1236
1522
|
|
|
@@ -1240,40 +1526,40 @@
|
|
|
1240
1526
|
// of the visual atom radius (0 = center, 0.5 = halfway to surface).
|
|
1241
1527
|
// get_site_radius() returns the uniform scale applied to SphereGeometry(0.5),
|
|
1242
1528
|
// so visual_radius = get_site_radius() * 0.5.
|
|
1243
|
-
const site_offsets =
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
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
|
|
1269
1556
|
|
|
1270
1557
|
const mag_interpolator = get_d3_interpolator(vector_color_scale)
|
|
1271
1558
|
|
|
1272
1559
|
return active_keys.map((key, layer_idx) => {
|
|
1273
1560
|
const layer_cfg = vector_configs[key]
|
|
1274
1561
|
const layer_scale = effective_global_scale * (layer_cfg?.scale ?? 1.0)
|
|
1275
|
-
const layer_color = layer_cfg?.color ??
|
|
1276
|
-
VECTOR_PALETTE[layer_idx % VECTOR_PALETTE.length]
|
|
1562
|
+
const layer_color = layer_cfg?.color ?? VECTOR_PALETTE[layer_idx % VECTOR_PALETTE.length]
|
|
1277
1563
|
|
|
1278
1564
|
const arrows = structure.sites
|
|
1279
1565
|
.map((site, site_idx) => {
|
|
@@ -1289,11 +1575,12 @@
|
|
|
1289
1575
|
arrow_color = layer_cfg.color
|
|
1290
1576
|
} else if (!is_single) arrow_color = layer_color
|
|
1291
1577
|
else {
|
|
1292
|
-
const effective_mode =
|
|
1293
|
-
|
|
1294
|
-
? `
|
|
1295
|
-
|
|
1296
|
-
|
|
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
|
|
1297
1584
|
if (effective_mode === `magnitude`) {
|
|
1298
1585
|
const mag = Math.hypot(...vec)
|
|
1299
1586
|
const norm = max_mag > 1e-10 ? mag / max_mag : 0
|
|
@@ -1303,19 +1590,18 @@
|
|
|
1303
1590
|
} else if (effective_mode === `uniform`) {
|
|
1304
1591
|
arrow_color = vector_color
|
|
1305
1592
|
} else {
|
|
1306
|
-
const majority_element =
|
|
1307
|
-
|
|
1308
|
-
spec.occu > max.occu ? spec : max
|
|
1309
|
-
|
|
1310
|
-
|
|
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
|
|
1311
1598
|
arrow_color =
|
|
1312
|
-
(majority_element && colors.element?.[majority_element]) ||
|
|
1313
|
-
vector_color
|
|
1599
|
+
(majority_element && colors.element?.[majority_element]) || vector_color
|
|
1314
1600
|
}
|
|
1315
1601
|
}
|
|
1316
1602
|
|
|
1317
1603
|
const offset = site_offsets?.[site_idx]?.get(key)
|
|
1318
|
-
const position = offset ? math.add(site.xyz, offset)
|
|
1604
|
+
const position = offset ? math.add(site.xyz, offset) : site.xyz
|
|
1319
1605
|
const arrow_vec = vector_normalize ? math.normalize_vec(vec) : vec
|
|
1320
1606
|
|
|
1321
1607
|
return {
|
|
@@ -1332,90 +1618,69 @@
|
|
|
1332
1618
|
})
|
|
1333
1619
|
})
|
|
1334
1620
|
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
is_image_atom ? `img` : `base`
|
|
1345
|
-
}`
|
|
1346
|
-
const bucket = groups[key] ||
|
|
1347
|
-
(groups[key] = { element, radius, color, is_image_atom, atoms: [] })
|
|
1348
|
-
bucket.atoms.push(atom)
|
|
1349
|
-
return groups
|
|
1350
|
-
},
|
|
1351
|
-
{} as Record<string, InstancedAtomGroup>,
|
|
1352
|
-
),
|
|
1353
|
-
),
|
|
1354
|
-
)
|
|
1355
|
-
|
|
1356
|
-
let unique_instanced_atoms = $derived(
|
|
1357
|
-
Object.values(
|
|
1358
|
-
instanced_atom_groups
|
|
1359
|
-
.flatMap((group) => group.atoms)
|
|
1360
|
-
.reduce((acc, atom) => {
|
|
1361
|
-
acc[atom.site_idx] = atom
|
|
1362
|
-
return acc
|
|
1363
|
-
}, {} as Record<number, (typeof atom_data)[number]>),
|
|
1364
|
-
),
|
|
1365
|
-
)
|
|
1366
|
-
|
|
1367
|
-
let gizmo_props = $derived.by(() => {
|
|
1368
|
-
const axis_options = Object.fromEntries(
|
|
1369
|
-
[...AXIS_COLORS, ...NEG_AXIS_COLORS].map(([axis, color, hover_color]) => [
|
|
1370
|
-
axis,
|
|
1371
|
-
{
|
|
1372
|
-
color,
|
|
1373
|
-
labelColor: `#111`,
|
|
1374
|
-
opacity: axis.startsWith(`n`) ? 0.9 : 0.8,
|
|
1375
|
-
hover: {
|
|
1376
|
-
color: hover_color,
|
|
1377
|
-
labelColor: `#222222`,
|
|
1378
|
-
opacity: axis.startsWith(`n`) ? 1 : 0.9,
|
|
1379
|
-
},
|
|
1380
|
-
},
|
|
1381
|
-
]),
|
|
1382
|
-
)
|
|
1383
|
-
return {
|
|
1384
|
-
background: { enabled: false },
|
|
1385
|
-
className: `responsive-gizmo`,
|
|
1386
|
-
...axis_options,
|
|
1387
|
-
...(typeof gizmo === `boolean` ? {} : gizmo),
|
|
1388
|
-
offset: { left: 5, bottom: 5 },
|
|
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)
|
|
1389
1630
|
}
|
|
1631
|
+
return { base, image }
|
|
1390
1632
|
})
|
|
1391
1633
|
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
onstart: () => {
|
|
1409
|
-
camera_is_moving = true
|
|
1410
|
-
cancel_atom_hover_clear()
|
|
1411
|
-
hovered_idx = null
|
|
1412
|
-
bond_context_menu = null
|
|
1413
|
-
},
|
|
1414
|
-
onend: () => {
|
|
1415
|
-
camera_is_moving = false
|
|
1416
|
-
},
|
|
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
|
|
1417
1650
|
})
|
|
1418
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),
|
|
1656
|
+
)
|
|
1657
|
+
|
|
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
|
+
)
|
|
1683
|
+
|
|
1419
1684
|
let measure_line_color = $derived.by(() => {
|
|
1420
1685
|
if (typeof window === `undefined`) return
|
|
1421
1686
|
const root_styles = getComputedStyle(document.documentElement)
|
|
@@ -1424,27 +1689,15 @@
|
|
|
1424
1689
|
})
|
|
1425
1690
|
</script>
|
|
1426
1691
|
|
|
1427
|
-
{#snippet bond_instanced_mesh_snippet(
|
|
1428
|
-
group: ComponentProps<typeof Bond>[`group`],
|
|
1429
|
-
)}
|
|
1692
|
+
{#snippet bond_instanced_mesh_snippet(group: ComponentProps<typeof Bond>[`group`])}
|
|
1430
1693
|
{#key group.instances.length}
|
|
1431
1694
|
<Bond {group} />
|
|
1432
1695
|
{/key}
|
|
1433
1696
|
{/snippet}
|
|
1434
1697
|
|
|
1435
|
-
{#snippet site_label_snippet(
|
|
1698
|
+
{#snippet site_label_snippet(site_idx: number)}
|
|
1436
1699
|
{@const site = structure!.sites[site_idx]}
|
|
1437
|
-
{
|
|
1438
|
-
<extras.HTML
|
|
1439
|
-
center
|
|
1440
|
-
position={position}
|
|
1441
|
-
calculatePosition={make_label_position_calculator(
|
|
1442
|
-
position,
|
|
1443
|
-
() => smart_site_label_offsets.get(site_idx) ?? site_label_offset,
|
|
1444
|
-
visual_radius,
|
|
1445
|
-
label_screen_margin,
|
|
1446
|
-
)}
|
|
1447
|
-
>
|
|
1700
|
+
{#if site}
|
|
1448
1701
|
{#if atom_label}
|
|
1449
1702
|
{@render atom_label({ site, site_idx })}
|
|
1450
1703
|
{:else}
|
|
@@ -1475,10 +1728,7 @@
|
|
|
1475
1728
|
{#if site.species.length === 1}
|
|
1476
1729
|
{site.species[0].element}{#if show_site_indices}-{site_idx + 1}{/if}
|
|
1477
1730
|
{:else}
|
|
1478
|
-
{#each site.species as
|
|
1479
|
-
{ element, occu, oxidation_state }
|
|
1480
|
-
(`${element}-${occu}-${oxidation_state}`)
|
|
1481
|
-
}
|
|
1731
|
+
{#each site.species as { element, occu, oxidation_state } (`${element}-${occu}-${oxidation_state}`)}
|
|
1482
1732
|
{element}<sub>{format_num(occu, `.3~`).replace(`0.`, `.`)}</sub>
|
|
1483
1733
|
{/each}
|
|
1484
1734
|
{#if show_site_indices}-{site_idx + 1}{/if}
|
|
@@ -1488,34 +1738,20 @@
|
|
|
1488
1738
|
{/if}
|
|
1489
1739
|
</button>
|
|
1490
1740
|
{/if}
|
|
1491
|
-
|
|
1741
|
+
{/if}
|
|
1492
1742
|
{/snippet}
|
|
1493
1743
|
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
</T.PerspectiveCamera>
|
|
1506
|
-
{:else}
|
|
1507
|
-
<T.OrthographicCamera
|
|
1508
|
-
makeDefault
|
|
1509
|
-
position={camera_position}
|
|
1510
|
-
zoom={computed_zoom}
|
|
1511
|
-
near={-100}
|
|
1512
|
-
far={camera_far}
|
|
1513
|
-
>
|
|
1514
|
-
<extras.OrbitControls bind:ref={orbit_controls} {...orbit_controls_props}>
|
|
1515
|
-
{#if gizmo}<extras.Gizmo {...gizmo_props} />{/if}
|
|
1516
|
-
</extras.OrbitControls>
|
|
1517
|
-
</T.OrthographicCamera>
|
|
1518
|
-
{/if}
|
|
1744
|
+
<SceneCamera
|
|
1745
|
+
{camera_projection}
|
|
1746
|
+
position={camera_position}
|
|
1747
|
+
{fov}
|
|
1748
|
+
zoom={computed_zoom}
|
|
1749
|
+
near={camera_near}
|
|
1750
|
+
far={camera_far}
|
|
1751
|
+
orbit_props={orbit_controls_props}
|
|
1752
|
+
{gizmo}
|
|
1753
|
+
bind:orbit_controls
|
|
1754
|
+
/>
|
|
1519
1755
|
|
|
1520
1756
|
<T.DirectionalLight position={[3, 10, 10]} intensity={directional_light} />
|
|
1521
1757
|
<T.AmbientLight intensity={ambient_light} />
|
|
@@ -1523,100 +1759,39 @@
|
|
|
1523
1759
|
<!-- Apply manual rotation around center: translate to origin, rotate, translate back -->
|
|
1524
1760
|
<T.Group position={rotation_target}>
|
|
1525
1761
|
<T.Group {rotation}>
|
|
1526
|
-
<T.Group position={
|
|
1762
|
+
<T.Group position={neg_rotation_target}>
|
|
1527
1763
|
{#if show_atoms}
|
|
1528
|
-
<!-- Instanced rendering for full
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
<
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
{
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
scale={atom.radius}
|
|
1548
|
-
{...atom_hover_props(atom.site_idx, edit_mode_image)}
|
|
1549
|
-
onpointerdown={(event: PointerEvent) => {
|
|
1550
|
-
if (
|
|
1551
|
-
edit_mode_image ||
|
|
1552
|
-
measure_mode !== `edit-bonds` ||
|
|
1553
|
-
bond_edit_mode !== `add`
|
|
1554
|
-
) {
|
|
1555
|
-
return
|
|
1556
|
-
}
|
|
1557
|
-
select_edit_bonds_site(atom.site_idx, event)
|
|
1558
|
-
}}
|
|
1559
|
-
onclick={(event: MouseEvent) => {
|
|
1560
|
-
if (edit_mode_image) return
|
|
1561
|
-
if (measure_mode === `edit-bonds`) {
|
|
1562
|
-
if (bond_edit_mode !== `add`) return
|
|
1563
|
-
if (skip_duplicate_edit_bonds_click(atom.site_idx)) {
|
|
1564
|
-
event.stopPropagation()
|
|
1565
|
-
return
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
toggle_selection(atom.site_idx, event)
|
|
1569
|
-
}}
|
|
1570
|
-
/>
|
|
1571
|
-
{/each}
|
|
1572
|
-
</extras.InstancedMesh>
|
|
1573
|
-
{/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}
|
|
1574
1783
|
|
|
1575
1784
|
<!-- Regular rendering for partial occupancy atoms -->
|
|
1576
|
-
{#each
|
|
1577
|
-
atom
|
|
1578
|
-
(atom.site_idx + atom.element + atom.occupancy)
|
|
1579
|
-
}
|
|
1785
|
+
{#each partial_occupancy_atoms as atom (atom.site_idx + atom.element + atom.occupancy)}
|
|
1580
1786
|
{@const partial_edit_image = measure_mode === `edit-atoms` && atom.is_image_atom}
|
|
1581
1787
|
{@const ghost_opacity = partial_edit_image ? 0.5 : 1}
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
{
|
|
1586
|
-
onpointerdown={(event: PointerEvent) => {
|
|
1587
|
-
if (
|
|
1588
|
-
partial_edit_image ||
|
|
1589
|
-
measure_mode !== `edit-bonds` ||
|
|
1590
|
-
bond_edit_mode !== `add`
|
|
1591
|
-
) {
|
|
1592
|
-
return
|
|
1593
|
-
}
|
|
1594
|
-
select_edit_bonds_site(atom.site_idx, event)
|
|
1595
|
-
}}
|
|
1596
|
-
onclick={(event: MouseEvent) => {
|
|
1597
|
-
if (partial_edit_image) return
|
|
1598
|
-
if (measure_mode === `edit-bonds`) {
|
|
1599
|
-
if (bond_edit_mode !== `add`) return
|
|
1600
|
-
if (skip_duplicate_edit_bonds_click(atom.site_idx)) {
|
|
1601
|
-
event.stopPropagation()
|
|
1602
|
-
return
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
toggle_selection(atom.site_idx, event)
|
|
1606
|
-
}}
|
|
1607
|
-
>
|
|
1608
|
-
{@const partial_color = partial_edit_image
|
|
1609
|
-
? desaturate(atom.color)
|
|
1610
|
-
: atom.color}
|
|
1788
|
+
<!-- Visual only: pointer interaction handled by the invisible full-sphere
|
|
1789
|
+
hit targets below (wedge meshes leave gaps at the poles). -->
|
|
1790
|
+
<T.Group position={atom.position} scale={atom.radius}>
|
|
1791
|
+
{@const partial_color = partial_edit_image ? desaturate(atom.color) : atom.color}
|
|
1611
1792
|
<T.Mesh>
|
|
1612
1793
|
<T.SphereGeometry
|
|
1613
|
-
args={[
|
|
1614
|
-
0.5,
|
|
1615
|
-
sphere_segments,
|
|
1616
|
-
sphere_segments,
|
|
1617
|
-
atom.start_phi,
|
|
1618
|
-
atom.phi_length,
|
|
1619
|
-
]}
|
|
1794
|
+
args={[0.5, sphere_segments, sphere_segments, atom.start_phi, atom.phi_length]}
|
|
1620
1795
|
/>
|
|
1621
1796
|
<T.MeshStandardMaterial
|
|
1622
1797
|
color={partial_color}
|
|
@@ -1625,7 +1800,7 @@
|
|
|
1625
1800
|
/>
|
|
1626
1801
|
</T.Mesh>
|
|
1627
1802
|
|
|
1628
|
-
{#if atom.
|
|
1803
|
+
{#if atom.render_start_cap}
|
|
1629
1804
|
<T.Mesh rotation={[0, atom.start_phi, 0]}>
|
|
1630
1805
|
<T.CircleGeometry
|
|
1631
1806
|
args={[
|
|
@@ -1643,7 +1818,7 @@
|
|
|
1643
1818
|
/>
|
|
1644
1819
|
</T.Mesh>
|
|
1645
1820
|
{/if}
|
|
1646
|
-
{#if atom.
|
|
1821
|
+
{#if atom.render_end_cap}
|
|
1647
1822
|
<T.Mesh rotation={[0, atom.end_phi, 0]}>
|
|
1648
1823
|
<T.CircleGeometry
|
|
1649
1824
|
args={[
|
|
@@ -1662,31 +1837,47 @@
|
|
|
1662
1837
|
</T.Mesh>
|
|
1663
1838
|
{/if}
|
|
1664
1839
|
</T.Group>
|
|
1840
|
+
{/each}
|
|
1665
1841
|
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1842
|
+
<!-- Invisible full-sphere hit targets for partial-occupancy sites so the
|
|
1843
|
+
whole ball is hoverable/clickable (wedge meshes leave gaps at the poles). -->
|
|
1844
|
+
{#each partial_hit_targets as hit (hit.site_idx)}
|
|
1845
|
+
{@const hit_edit_image = measure_mode === `edit-atoms` && hit.is_image_atom}
|
|
1846
|
+
<T.Mesh
|
|
1847
|
+
position={hit.position}
|
|
1848
|
+
scale={hit.radius}
|
|
1849
|
+
{...atom_pointer_props(hit.site_idx, hit_edit_image)}
|
|
1850
|
+
>
|
|
1851
|
+
<T.SphereGeometry args={[0.5, sphere_segments, sphere_segments]} />
|
|
1852
|
+
<T.MeshBasicMaterial transparent opacity={0} depthWrite={false} />
|
|
1853
|
+
</T.Mesh>
|
|
1671
1854
|
{/each}
|
|
1672
1855
|
|
|
1673
|
-
<!-- Site labels/indices for
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
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>
|
|
1678
1869
|
{/if}
|
|
1679
1870
|
{/if}
|
|
1680
1871
|
|
|
1872
|
+
<!-- Per-site vector arrows (forces, magmoms, ...) as instanced meshes:
|
|
1873
|
+
2 draw calls per layer instead of 2 meshes per site -->
|
|
1681
1874
|
{#each vector_layers as layer (layer.key)}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
/>
|
|
1689
|
-
{/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
|
+
/>
|
|
1690
1881
|
{/each}
|
|
1691
1882
|
|
|
1692
1883
|
<!-- Instanced bond rendering with gradient colors -->
|
|
@@ -1696,17 +1887,39 @@
|
|
|
1696
1887
|
{/each}
|
|
1697
1888
|
{/if}
|
|
1698
1889
|
|
|
1890
|
+
<!-- Coordination polyhedra: all faces in one merged mesh, edges in one
|
|
1891
|
+
LineSegments (1-2 draw calls regardless of supercell size) -->
|
|
1892
|
+
{#if polyhedra_geometry}
|
|
1893
|
+
<T.Mesh geometry={polyhedra_geometry} frustumCulled={false} raycast={() => null}>
|
|
1894
|
+
<!-- depthWrite when mostly opaque: VESTA-like occlusion between polyhedra;
|
|
1895
|
+
fully translucent settings fall back to see-through blending -->
|
|
1896
|
+
<T.MeshStandardMaterial
|
|
1897
|
+
vertexColors
|
|
1898
|
+
transparent={polyhedra_opacity < 1}
|
|
1899
|
+
opacity={polyhedra_opacity}
|
|
1900
|
+
side={DoubleSide}
|
|
1901
|
+
depthWrite={polyhedra_opacity >= 0.65}
|
|
1902
|
+
flatShading
|
|
1903
|
+
/>
|
|
1904
|
+
</T.Mesh>
|
|
1905
|
+
{#if polyhedra_edge_geometry}
|
|
1906
|
+
<T.LineSegments
|
|
1907
|
+
geometry={polyhedra_edge_geometry}
|
|
1908
|
+
frustumCulled={false}
|
|
1909
|
+
raycast={() => null}
|
|
1910
|
+
>
|
|
1911
|
+
<T.LineBasicMaterial color={polyhedra_edge_color} />
|
|
1912
|
+
</T.LineSegments>
|
|
1913
|
+
{/if}
|
|
1914
|
+
{/if}
|
|
1915
|
+
|
|
1699
1916
|
<!-- Clickable bond hit-test cylinders in edit-bonds mode -->
|
|
1700
|
-
{#if measure_mode === `edit-bonds` && editable_bond_pairs.length > 0}
|
|
1701
|
-
{#each editable_bond_pairs as
|
|
1702
|
-
bond
|
|
1703
|
-
(`bond-hit-${bond_edit_mode}-${rendered_bond_key_for(bond)}`)
|
|
1704
|
-
}
|
|
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)}`)}
|
|
1705
1919
|
{@const bond_key = rendered_bond_key_for(bond)}
|
|
1706
1920
|
{@const is_hovered = hovered_bond_key === bond_key}
|
|
1707
1921
|
{@const is_delete_mode = bond_edit_mode === `delete`}
|
|
1708
|
-
{@const bond_hit_radius =
|
|
1709
|
-
bond_thickness * (is_delete_mode ? 5 : 1.25)}
|
|
1922
|
+
{@const bond_hit_radius = bond_thickness * (is_delete_mode ? 5 : 1.25)}
|
|
1710
1923
|
{@const bond_hover_radius = bond_thickness * 1.1}
|
|
1711
1924
|
<T.Mesh
|
|
1712
1925
|
matrixAutoUpdate={false}
|
|
@@ -1735,19 +1948,8 @@
|
|
|
1735
1948
|
onpointermove={() => (hovered_bond_key = bond_key)}
|
|
1736
1949
|
onpointerleave={() => (hovered_bond_key = null)}
|
|
1737
1950
|
>
|
|
1738
|
-
<T.CylinderGeometry
|
|
1739
|
-
|
|
1740
|
-
bond_hit_radius,
|
|
1741
|
-
bond_hit_radius,
|
|
1742
|
-
1,
|
|
1743
|
-
6,
|
|
1744
|
-
]}
|
|
1745
|
-
/>
|
|
1746
|
-
<T.MeshBasicMaterial
|
|
1747
|
-
transparent
|
|
1748
|
-
opacity={0}
|
|
1749
|
-
depthWrite={false}
|
|
1750
|
-
/>
|
|
1951
|
+
<T.CylinderGeometry args={[bond_hit_radius, bond_hit_radius, 1, 6]} />
|
|
1952
|
+
<T.MeshBasicMaterial transparent opacity={0} depthWrite={false} />
|
|
1751
1953
|
</T.Mesh>
|
|
1752
1954
|
{#if is_hovered}
|
|
1753
1955
|
<T.Mesh
|
|
@@ -1766,7 +1968,7 @@
|
|
|
1766
1968
|
{/each}
|
|
1767
1969
|
{/if}
|
|
1768
1970
|
|
|
1769
|
-
{#if editable_atom_hit_targets.length > 0}
|
|
1971
|
+
{#if interactive && editable_atom_hit_targets.length > 0}
|
|
1770
1972
|
{#each editable_atom_hit_targets as atom_hit (atom_hit.site_idx)}
|
|
1771
1973
|
<T.Mesh
|
|
1772
1974
|
position={atom_hit.position}
|
|
@@ -1777,16 +1979,12 @@
|
|
|
1777
1979
|
}}
|
|
1778
1980
|
>
|
|
1779
1981
|
<T.SphereGeometry args={[0.5, 12, 12]} />
|
|
1780
|
-
<T.MeshBasicMaterial
|
|
1781
|
-
transparent
|
|
1782
|
-
opacity={0}
|
|
1783
|
-
depthWrite={false}
|
|
1784
|
-
/>
|
|
1982
|
+
<T.MeshBasicMaterial transparent opacity={0} depthWrite={false} />
|
|
1785
1983
|
</T.Mesh>
|
|
1786
1984
|
{/each}
|
|
1787
1985
|
{/if}
|
|
1788
1986
|
|
|
1789
|
-
{#if measure_mode === `edit-bonds` && bond_context_menu}
|
|
1987
|
+
{#if interactive && measure_mode === `edit-bonds` && bond_context_menu}
|
|
1790
1988
|
{@const current_order = get_current_bond_order(
|
|
1791
1989
|
bond_context_menu.site_idx_1,
|
|
1792
1990
|
bond_context_menu.site_idx_2,
|
|
@@ -1850,68 +2048,37 @@
|
|
|
1850
2048
|
</extras.HTML>
|
|
1851
2049
|
{/if}
|
|
1852
2050
|
|
|
1853
|
-
<!-- highlight hovered, active and selected sites
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
color: active_highlight_color,
|
|
1875
|
-
}))),
|
|
1876
|
-
] as
|
|
1877
|
-
entry
|
|
1878
|
-
(`${entry.kind}-${entry.site_idx}`)
|
|
1879
|
-
}
|
|
1880
|
-
{@const { site, opacity, color, kind, site_idx } = entry}
|
|
1881
|
-
{#if site}
|
|
1882
|
-
{@const xyz = site.xyz}
|
|
1883
|
-
{@const highlight_radius = site_idx !== null
|
|
1884
|
-
? radius_by_site_idx.get(site_idx) ?? get_site_radius(site, site_idx)
|
|
1885
|
-
: get_site_radius(site, site_idx)}
|
|
1886
|
-
<T.Mesh
|
|
1887
|
-
position={xyz}
|
|
1888
|
-
scale={1.2 * highlight_radius}
|
|
1889
|
-
oncreate={disable_raycast}
|
|
1890
|
-
>
|
|
1891
|
-
<T.SphereGeometry args={[0.5, 22, 22]} />
|
|
1892
|
-
<T.MeshStandardMaterial
|
|
1893
|
-
{color}
|
|
1894
|
-
transparent
|
|
1895
|
-
{opacity}
|
|
1896
|
-
emissive={color}
|
|
1897
|
-
emissiveIntensity={kind === `selected` || kind === `active` ? 0.7 : 0.2}
|
|
1898
|
-
depthTest={false}
|
|
1899
|
-
depthWrite={false}
|
|
1900
|
-
/>
|
|
1901
|
-
</T.Mesh>
|
|
1902
|
-
{/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>
|
|
1903
2072
|
{/each}
|
|
1904
2073
|
|
|
1905
2074
|
<!-- selection order labels (1, 2, 3, ...) for measurements and bond editing -->
|
|
1906
|
-
{#if structure?.sites && (measured_sites?.length ?? 0) > 0 &&
|
|
1907
|
-
(measure_mode === `distance` || measure_mode === `angle` ||
|
|
1908
|
-
measure_mode === `edit-bonds`)}
|
|
2075
|
+
{#if structure?.sites && (measured_sites?.length ?? 0) > 0 && (measure_mode === `distance` || measure_mode === `angle` || measure_mode === `edit-bonds`)}
|
|
1909
2076
|
{#each measured_sites as site_index, loop_idx (site_index)}
|
|
1910
2077
|
{@const site = structure.sites[site_index]}
|
|
1911
2078
|
{#if site}
|
|
1912
2079
|
<!-- shift selected site labels down to avoid overlapping regular site labels-->
|
|
1913
|
-
{@const selection_offset = math.add(site_label_offset, [0, -0.5, 0])}
|
|
1914
|
-
{@const pos = math.add(site.xyz, selection_offset)
|
|
2080
|
+
{@const selection_offset = math.add<Vec3>(site_label_offset, [0, -0.5, 0])}
|
|
2081
|
+
{@const pos = math.add(site.xyz, selection_offset)}
|
|
1915
2082
|
<extras.HTML center position={pos}>
|
|
1916
2083
|
<span class="selection-label">{loop_idx + 1}</span>
|
|
1917
2084
|
</extras.HTML>
|
|
@@ -1920,24 +2087,18 @@
|
|
|
1920
2087
|
{/if}
|
|
1921
2088
|
|
|
1922
2089
|
<!-- hovered site tooltip -->
|
|
1923
|
-
{#if hovered_site && !camera_is_moving &&
|
|
1924
|
-
|
|
1925
|
-
{@const
|
|
1926
|
-
`, `,
|
|
1927
|
-
)}
|
|
1928
|
-
{@const xyz = hovered_site.xyz.map((val) => format_num(val, float_fmt)).join(
|
|
1929
|
-
`, `,
|
|
1930
|
-
)}
|
|
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(`, `)}
|
|
1931
2093
|
{@const bond_neighbors = (() => {
|
|
1932
2094
|
if (hovered_idx == null || !structure?.sites) return []
|
|
1933
2095
|
return filtered_bond_pairs
|
|
1934
|
-
.filter(
|
|
1935
|
-
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,
|
|
1936
2098
|
)
|
|
1937
2099
|
.map((bond) => {
|
|
1938
|
-
const neighbor_idx =
|
|
1939
|
-
? bond.site_idx_2
|
|
1940
|
-
: bond.site_idx_1
|
|
2100
|
+
const neighbor_idx =
|
|
2101
|
+
bond.site_idx_1 === hovered_idx ? bond.site_idx_2 : bond.site_idx_1
|
|
1941
2102
|
return structure.sites[neighbor_idx]?.species[0]?.element ?? `?`
|
|
1942
2103
|
})
|
|
1943
2104
|
})()}
|
|
@@ -1952,28 +2113,25 @@
|
|
|
1952
2113
|
.map(([elem, count]) => `${elem}: ${count}`)
|
|
1953
2114
|
return ` (${parts.join(`, `)})`
|
|
1954
2115
|
})()}
|
|
2116
|
+
{@const tooltip_species =
|
|
2117
|
+
render_sites.find((rs) => rs.site_idx === hovered_idx)?.site.species ??
|
|
2118
|
+
hovered_site.species ??
|
|
2119
|
+
[]}
|
|
1955
2120
|
<CanvasTooltip position={hovered_site.xyz}>
|
|
1956
2121
|
<!-- Element symbols with occupancies for disordered sites -->
|
|
1957
2122
|
<div class="elements">
|
|
1958
|
-
{#each
|
|
1959
|
-
{
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
}</span>{/if}
|
|
1971
|
-
<strong>
|
|
1972
|
-
{element}{#if oxi_state != null && oxi_state !== 0}<sup>{Math.abs(
|
|
1973
|
-
oxi_state,
|
|
1974
|
-
)}{oxi_state > 0 ? `+` : `−`}</sup>{/if}
|
|
1975
|
-
</strong>
|
|
1976
|
-
{#if element_name}<span class="elem-name">{element_name}</span>{/if}
|
|
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 ?? ``}
|
|
2126
|
+
<span class="species">
|
|
2127
|
+
{#if occu !== 1}<span class="occupancy">{format_num(occu, `.3~f`)}</span>{/if}
|
|
2128
|
+
<strong>
|
|
2129
|
+
{element}{#if oxi_state != null && oxi_state !== 0}<sup
|
|
2130
|
+
>{Math.abs(oxi_state)}{oxi_state > 0 ? `+` : `−`}</sup
|
|
2131
|
+
>{/if}
|
|
2132
|
+
</strong>
|
|
2133
|
+
{#if element_name}<span class="elem-name">{element_name}</span>{/if}
|
|
2134
|
+
</span>
|
|
1977
2135
|
{/each}
|
|
1978
2136
|
</div>
|
|
1979
2137
|
<div class="coordinates fractional">abc: ({abc})</div>
|
|
@@ -1986,26 +2144,29 @@
|
|
|
1986
2144
|
|
|
1987
2145
|
{#if visual_lattice}
|
|
1988
2146
|
<Lattice matrix={visual_lattice.matrix} {...lattice_props} />
|
|
2147
|
+
{#if symmetry_elements.length > 0}
|
|
2148
|
+
<SymmetryElements
|
|
2149
|
+
elements={symmetry_elements}
|
|
2150
|
+
lattice={visual_lattice.matrix}
|
|
2151
|
+
{...symmetry_elements_props}
|
|
2152
|
+
/>
|
|
2153
|
+
{/if}
|
|
1989
2154
|
{/if}
|
|
1990
2155
|
|
|
1991
2156
|
<!-- TransformControls for editing atoms in edit-atoms mode -->
|
|
1992
|
-
{#if measure_mode === `edit-atoms` && selected_sites.length > 0 &&
|
|
1993
|
-
structure?.sites}
|
|
2157
|
+
{#if interactive && measure_mode === `edit-atoms` && selected_sites.length > 0 && structure?.sites}
|
|
1994
2158
|
{@const selected_atoms = selected_sites
|
|
1995
2159
|
.map((idx) => structure?.sites?.[idx])
|
|
1996
2160
|
.filter((site): site is Site => site != null)}
|
|
1997
2161
|
{#if selected_atoms.length > 0}
|
|
1998
2162
|
{@const avg = (dim: number) =>
|
|
1999
|
-
|
|
2000
|
-
|
|
2163
|
+
selected_atoms.reduce((sum, atom) => sum + atom.xyz[dim], 0) /
|
|
2164
|
+
selected_atoms.length}
|
|
2001
2165
|
{@const centroid = [avg(0), avg(1), avg(2)] as Vec3}
|
|
2002
2166
|
<!-- Invisible mesh at centroid for TransformControls to manipulate.
|
|
2003
2167
|
During drag, use frozen_centroid so Svelte doesn't override TransformControls
|
|
2004
2168
|
with the wrapped centroid (which jumps on PBC boundary crossings). -->
|
|
2005
|
-
<T.Mesh
|
|
2006
|
-
position={frozen_centroid ?? centroid}
|
|
2007
|
-
bind:ref={transform_object}
|
|
2008
|
-
>
|
|
2169
|
+
<T.Mesh position={frozen_centroid ?? centroid} bind:ref={transform_object}>
|
|
2009
2170
|
<T.SphereGeometry args={[0.01, 4, 4]} />
|
|
2010
2171
|
<T.MeshBasicMaterial transparent opacity={0} />
|
|
2011
2172
|
</T.Mesh>
|
|
@@ -2044,7 +2205,7 @@
|
|
|
2044
2205
|
|
|
2045
2206
|
<!-- Invisible plane for click-to-place atom in add-atom mode -->
|
|
2046
2207
|
<!-- Uses onBeforeRender to orient normal toward camera so raycasts always hit -->
|
|
2047
|
-
{#if measure_mode === `edit-atoms` && add_atom_mode}
|
|
2208
|
+
{#if interactive && measure_mode === `edit-atoms` && add_atom_mode}
|
|
2048
2209
|
{@const center = rotation_target ?? [0, 0, 0]}
|
|
2049
2210
|
<T.Mesh
|
|
2050
2211
|
position={center}
|
|
@@ -2059,10 +2220,7 @@
|
|
|
2059
2220
|
}}
|
|
2060
2221
|
>
|
|
2061
2222
|
<T.PlaneGeometry
|
|
2062
|
-
args={[
|
|
2063
|
-
Math.max(200, structure_size * 4),
|
|
2064
|
-
Math.max(200, structure_size * 4),
|
|
2065
|
-
]}
|
|
2223
|
+
args={[Math.max(200, structure_size * 4), Math.max(200, structure_size * 4)]}
|
|
2066
2224
|
/>
|
|
2067
2225
|
<T.MeshBasicMaterial transparent opacity={0} side={2} depthWrite={false} />
|
|
2068
2226
|
</T.Mesh>
|
|
@@ -2082,27 +2240,23 @@
|
|
|
2082
2240
|
{@const site_j = structure.sites[idx_j]}
|
|
2083
2241
|
{@const pos_i = site_i.xyz}
|
|
2084
2242
|
{@const pos_j = site_j.xyz}
|
|
2085
|
-
<Cylinder
|
|
2086
|
-
from={pos_i}
|
|
2087
|
-
to={pos_j}
|
|
2088
|
-
thickness={0.12}
|
|
2089
|
-
color={measure_line_color}
|
|
2090
|
-
/>
|
|
2243
|
+
<Cylinder from={pos_i} to={pos_j} thickness={0.12} color={measure_line_color} />
|
|
2091
2244
|
{@const midpoint = [
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
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}
|
|
2096
2249
|
{@const direct = math.euclidean_dist(pos_i, pos_j)}
|
|
2097
2250
|
{@const pbc = lattice
|
|
2098
|
-
|
|
2099
|
-
|
|
2251
|
+
? measure.distance_pbc(pos_i, pos_j, lattice.matrix, undefined, lattice.pbc)
|
|
2252
|
+
: direct}
|
|
2100
2253
|
{@const differ = lattice ? Math.abs(pbc - direct) > 1e-6 : false}
|
|
2101
2254
|
<extras.HTML center position={midpoint}>
|
|
2102
2255
|
<span class="measure-label">
|
|
2103
2256
|
{#if differ}
|
|
2104
2257
|
PBC: {format_num(pbc, float_fmt)} Å<br /><small>
|
|
2105
|
-
Direct: {format_num(direct, float_fmt)} Å</small
|
|
2258
|
+
Direct: {format_num(direct, float_fmt)} Å</small
|
|
2259
|
+
>
|
|
2106
2260
|
{:else}
|
|
2107
2261
|
{format_num(pbc, float_fmt)} Å
|
|
2108
2262
|
{/if}
|
|
@@ -2113,19 +2267,20 @@
|
|
|
2113
2267
|
{:else if measure_mode === `angle` && measured_sites.length >= 3}
|
|
2114
2268
|
{#each measured_sites as idx_center (idx_center)}
|
|
2115
2269
|
{@const center = structure.sites[idx_center]}
|
|
2116
|
-
{#each measured_sites.filter((idx) => idx !== idx_center) as
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
}
|
|
2121
|
-
{#each measured_sites.filter((idx) => idx !== idx_center).slice(loop_idx + 1) as
|
|
2122
|
-
idx_b
|
|
2123
|
-
(idx_center + `-` + idx_a + `-` + idx_b)
|
|
2124
|
-
}
|
|
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)}
|
|
2125
2274
|
{@const site_a = structure.sites[idx_a]}
|
|
2126
2275
|
{@const site_b = structure.sites[idx_b]}
|
|
2127
2276
|
{@const disp = (to: Vec3) =>
|
|
2128
|
-
|
|
2277
|
+
measure.displacement_pbc(
|
|
2278
|
+
center.xyz,
|
|
2279
|
+
to,
|
|
2280
|
+
lattice?.matrix,
|
|
2281
|
+
undefined,
|
|
2282
|
+
lattice?.pbc,
|
|
2283
|
+
)}
|
|
2129
2284
|
{@const v1 = disp(site_a.xyz)}
|
|
2130
2285
|
{@const v2 = disp(site_b.xyz)}
|
|
2131
2286
|
{@const n1 = Math.hypot(v1[0], v1[1], v1[2])}
|
|
@@ -2163,10 +2318,6 @@
|
|
|
2163
2318
|
</T.Group>
|
|
2164
2319
|
|
|
2165
2320
|
<style>
|
|
2166
|
-
:global(.structure .responsive-gizmo) {
|
|
2167
|
-
width: clamp(70px, 18cqmin, 100px) !important;
|
|
2168
|
-
height: clamp(70px, 18cqmin, 100px) !important;
|
|
2169
|
-
}
|
|
2170
2321
|
.atom-label {
|
|
2171
2322
|
background: var(--struct-atom-label-bg, rgba(0, 0, 0, 0.1));
|
|
2172
2323
|
border: 0;
|
|
@@ -2180,6 +2331,13 @@
|
|
|
2180
2331
|
.elements {
|
|
2181
2332
|
margin-bottom: var(--canvas-tooltip-elements-margin);
|
|
2182
2333
|
}
|
|
2334
|
+
.species {
|
|
2335
|
+
display: inline-block;
|
|
2336
|
+
white-space: nowrap;
|
|
2337
|
+
&:not(:first-child) {
|
|
2338
|
+
margin-left: var(--canvas-tooltip-species-gap, 0.5em);
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2183
2341
|
.occupancy {
|
|
2184
2342
|
font-size: var(--canvas-tooltip-occu-font-size);
|
|
2185
2343
|
opacity: var(--canvas-tooltip-occu-opacity);
|