matterviz 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EmptyState.svelte +10 -4
- package/dist/FilePicker.svelte +48 -60
- package/dist/Icon.svelte +7 -4
- package/dist/MillerIndexInput.svelte +2 -2
- package/dist/api/optimade.js +10 -7
- package/dist/app.css +68 -0
- package/dist/brillouin/BrillouinZone.svelte +90 -91
- package/dist/brillouin/BrillouinZone.svelte.d.ts +5 -7
- package/dist/brillouin/BrillouinZoneControls.svelte +3 -6
- package/dist/brillouin/BrillouinZoneExportPane.svelte +18 -14
- package/dist/brillouin/BrillouinZoneInfoPane.svelte +6 -10
- package/dist/brillouin/BrillouinZoneScene.svelte +46 -68
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/brillouin/compute.js +10 -16
- package/dist/brillouin/geometry.js +2 -2
- package/dist/chempot-diagram/ChemPotDiagram.svelte +48 -50
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +61 -169
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +559 -755
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +1 -1
- package/dist/chempot-diagram/async-compute.svelte.js +13 -14
- package/dist/chempot-diagram/color.d.ts +21 -1
- package/dist/chempot-diagram/color.js +70 -1
- package/dist/chempot-diagram/compute.d.ts +7 -0
- package/dist/chempot-diagram/compute.js +85 -26
- package/dist/chempot-diagram/controls-state.svelte.js +1 -1
- package/dist/chempot-diagram/export.js +1 -6
- package/dist/chempot-diagram/temperature.js +9 -10
- package/dist/colors/index.d.ts +0 -4
- package/dist/colors/index.js +19 -20
- package/dist/composition/BarChart.svelte +30 -26
- package/dist/composition/BubbleChart.svelte +17 -17
- package/dist/composition/Composition.svelte +10 -19
- package/dist/composition/Formula.svelte +20 -27
- package/dist/composition/FormulaFilter.svelte +115 -184
- package/dist/composition/PieChart.svelte +55 -41
- package/dist/composition/format.js +7 -9
- package/dist/composition/index.d.ts +2 -0
- package/dist/composition/index.js +14 -0
- package/dist/composition/parse.d.ts +2 -2
- package/dist/composition/parse.js +61 -115
- package/dist/constants.d.ts +0 -1
- package/dist/constants.js +17 -26
- package/dist/convex-hull/ConvexHull.svelte +12 -15
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +93 -94
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +127 -186
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +131 -180
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullChrome.svelte +36 -40
- package/dist/convex-hull/ConvexHullControls.svelte +46 -48
- package/dist/convex-hull/ConvexHullStats.svelte +193 -195
- package/dist/convex-hull/ConvexHullTooltip.svelte +5 -12
- package/dist/convex-hull/GasPressureControls.svelte +5 -12
- package/dist/convex-hull/StructurePopup.svelte +18 -22
- package/dist/convex-hull/StructurePopup.svelte.d.ts +2 -0
- package/dist/convex-hull/TemperatureSlider.svelte +4 -10
- package/dist/convex-hull/canvas-interactions.svelte.js +2 -1
- package/dist/convex-hull/gas-thermodynamics.js +21 -53
- package/dist/convex-hull/helpers.d.ts +8 -10
- package/dist/convex-hull/helpers.js +37 -43
- package/dist/convex-hull/hull-state.svelte.d.ts +1 -1
- package/dist/convex-hull/thermodynamics.js +37 -44
- package/dist/coordination/CoordinationBarPlot.svelte +49 -53
- package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
- package/dist/coordination/calc-coordination.d.ts +1 -1
- package/dist/coordination/calc-coordination.js +28 -25
- package/dist/element/BohrAtom.svelte +5 -9
- 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 +44 -67
- package/dist/element/Nucleus.svelte +4 -2
- package/dist/element/data.d.ts +3 -2
- package/dist/element/data.js +5 -1
- package/dist/element/helpers.d.ts +4 -0
- package/dist/element/helpers.js +18 -0
- package/dist/element/index.d.ts +2 -5
- package/dist/element/index.js +4 -19
- package/dist/feedback/DragOverlay.svelte +5 -5
- package/dist/feedback/DragOverlay.svelte.d.ts +2 -2
- package/dist/feedback/StatusMessage.svelte +7 -4
- package/dist/fermi-surface/FermiSlice.svelte +21 -18
- package/dist/fermi-surface/FermiSurface.svelte +35 -44
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +7 -13
- package/dist/fermi-surface/FermiSurfaceControls.svelte +6 -20
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +64 -136
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceTooltip.svelte +9 -6
- package/dist/fermi-surface/compute.js +25 -58
- package/dist/fermi-surface/parse.js +54 -88
- package/dist/file-viewer/JsonBrowser.svelte +1043 -0
- package/dist/file-viewer/JsonBrowser.svelte.d.ts +9 -0
- package/dist/file-viewer/PlotPanel.svelte +350 -0
- package/dist/file-viewer/PlotPanel.svelte.d.ts +9 -0
- package/dist/file-viewer/TrajectoryWithDos.svelte +30 -0
- package/dist/file-viewer/TrajectoryWithDos.svelte.d.ts +10 -0
- package/dist/file-viewer/detect.d.ts +11 -0
- package/dist/file-viewer/detect.js +381 -0
- package/dist/file-viewer/eligibility.d.ts +4 -0
- package/dist/file-viewer/eligibility.js +53 -0
- package/dist/file-viewer/host-protocol.d.ts +50 -0
- package/dist/file-viewer/host-transfer.d.ts +25 -0
- package/dist/file-viewer/host-transfer.js +42 -0
- package/dist/file-viewer/index.d.ts +5 -0
- package/dist/file-viewer/index.js +5 -0
- package/dist/file-viewer/main.d.ts +44 -0
- package/dist/file-viewer/main.js +535 -0
- package/dist/file-viewer/parse.d.ts +9 -0
- package/dist/file-viewer/parse.js +170 -0
- package/dist/file-viewer/plot-utils.d.ts +24 -0
- package/dist/file-viewer/plot-utils.js +191 -0
- package/dist/file-viewer/types.d.ts +4 -0
- package/dist/file-viewer/types.js +5 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +153 -219
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +2 -2
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +9 -7
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
- package/dist/heatmap-matrix/index.d.ts +5 -0
- package/dist/icons.d.ts +4 -0
- package/dist/icons.js +4 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/io/ExportPane.svelte +11 -7
- package/dist/io/decompress.d.ts +4 -1
- package/dist/io/decompress.js +24 -36
- package/dist/io/export.d.ts +11 -4
- package/dist/io/export.js +75 -19
- package/dist/io/fetch.d.ts +0 -3
- package/dist/io/fetch.js +0 -11
- package/dist/io/file-drop.js +60 -35
- package/dist/io/index.d.ts +0 -1
- package/dist/io/index.js +2 -9
- package/dist/io/is-binary.d.ts +9 -0
- package/dist/io/is-binary.js +45 -1
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +40 -40
- package/dist/isosurface/Isosurface.svelte +700 -215
- package/dist/isosurface/Isosurface.svelte.d.ts +7 -1
- package/dist/isosurface/IsosurfaceControls.svelte +590 -116
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +1 -1
- package/dist/isosurface/VolumeSlice.svelte +224 -0
- package/dist/isosurface/VolumeSlice.svelte.d.ts +28 -0
- package/dist/isosurface/coloring.d.ts +20 -0
- package/dist/isosurface/coloring.js +118 -0
- package/dist/isosurface/geometry-worker-types.d.ts +38 -0
- package/dist/isosurface/geometry.worker.d.ts +1 -0
- package/dist/isosurface/geometry.worker.js +59 -0
- package/dist/isosurface/grid.d.ts +10 -0
- package/dist/isosurface/grid.js +31 -0
- package/dist/isosurface/index.d.ts +4 -0
- package/dist/isosurface/index.js +4 -0
- package/dist/isosurface/parse-vaspwave.d.ts +3 -0
- package/dist/isosurface/parse-vaspwave.js +138 -0
- package/dist/isosurface/parse.js +6 -6
- package/dist/isosurface/profile.d.ts +12 -0
- package/dist/isosurface/profile.js +19 -0
- package/dist/isosurface/sampling.d.ts +34 -0
- package/dist/isosurface/sampling.js +408 -0
- package/dist/isosurface/slice-rendering.d.ts +13 -0
- package/dist/isosurface/slice-rendering.js +79 -0
- package/dist/isosurface/slice.d.ts +22 -2
- package/dist/isosurface/slice.js +200 -116
- package/dist/isosurface/types.d.ts +24 -1
- package/dist/isosurface/types.js +120 -8
- package/dist/json-path.d.ts +4 -0
- package/dist/json-path.js +89 -0
- package/dist/labels.d.ts +6 -2
- package/dist/labels.js +44 -14
- package/dist/layout/FullscreenButton.svelte +2 -1
- package/dist/layout/FullscreenToggle.svelte +10 -4
- package/dist/layout/InfoCard.svelte +8 -21
- 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 +57 -62
- package/dist/layout/SettingsSection.svelte.d.ts +6 -7
- package/dist/layout/SubpageGrid.svelte +3 -1
- package/dist/layout/ViewerChrome.svelte +16 -11
- package/dist/layout/ViewerChrome.svelte.d.ts +2 -1
- package/dist/layout/fullscreen.js +3 -5
- package/dist/layout/fullscreen.svelte.js +1 -2
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/index.js +1 -0
- package/dist/layout/json-tree/JsonNode.svelte +22 -59
- package/dist/layout/json-tree/JsonTree.svelte +101 -172
- package/dist/layout/json-tree/JsonValue.svelte +9 -32
- package/dist/layout/json-tree/utils.d.ts +0 -3
- package/dist/layout/json-tree/utils.js +50 -188
- package/dist/marching-cubes.d.ts +8 -1
- package/dist/marching-cubes.js +115 -48
- package/dist/math.d.ts +5 -2
- package/dist/math.js +48 -38
- package/dist/overlays/ContextMenu.svelte +19 -34
- package/dist/overlays/CopyButton.svelte +11 -5
- package/dist/overlays/DragControlTab.svelte +6 -1
- package/dist/overlays/DraggablePane.svelte +110 -74
- package/dist/overlays/DraggablePane.svelte.d.ts +2 -1
- package/dist/overlays/GlassChip.svelte +32 -0
- package/dist/overlays/GlassChip.svelte.d.ts +8 -0
- package/dist/overlays/InfoPaneCards.svelte +11 -23
- package/dist/overlays/index.d.ts +7 -0
- package/dist/overlays/index.js +26 -0
- package/dist/overlays/portal.d.ts +2 -0
- package/dist/overlays/portal.js +14 -0
- package/dist/periodic-table/PeriodicTable.svelte +113 -145
- package/dist/periodic-table/PeriodicTable.svelte.d.ts +5 -3
- package/dist/periodic-table/PeriodicTableControls.svelte +37 -115
- package/dist/periodic-table/TableInset.svelte +9 -4
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +68 -105
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -8
- package/dist/phase-diagram/PhaseDiagramControls.svelte +70 -94
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +1 -1
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +2 -1
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +12 -12
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +54 -58
- package/dist/phase-diagram/TdbInfoPanel.svelte +21 -15
- package/dist/phase-diagram/build-diagram.js +4 -9
- package/dist/phase-diagram/diagram-input.d.ts +1 -2
- package/dist/phase-diagram/parse.js +15 -15
- package/dist/phase-diagram/svg-to-diagram.js +28 -27
- package/dist/phase-diagram/utils.js +2 -3
- package/dist/plot/bar/BarPlot.svelte +461 -426
- package/dist/plot/bar/BarPlot.svelte.d.ts +2 -0
- package/dist/plot/bar/BarPlotControls.svelte +1 -3
- package/dist/plot/bar/BarPlotControls.svelte.d.ts +4 -6
- package/dist/plot/bar/SpacegroupBarPlot.svelte +46 -77
- package/dist/plot/bar/data.d.ts +5 -4
- package/dist/plot/bar/data.js +2 -1
- package/dist/plot/box/BoxPlot.svelte +322 -226
- package/dist/plot/box/BoxPlot.svelte.d.ts +2 -0
- package/dist/plot/box/Violin.svelte.d.ts +1 -0
- package/dist/plot/box/box-plot.js +1 -1
- package/dist/plot/core/auto-place.d.ts +1 -1
- package/dist/plot/core/auto-place.js +2 -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 +101 -106
- package/dist/plot/core/components/ColorScaleSelect.svelte +1 -1
- package/dist/plot/core/components/FillArea.svelte +10 -17
- package/dist/plot/core/components/HierarchyControls.svelte +249 -0
- package/dist/plot/{sunburst/SunburstControls.svelte.d.ts → core/components/HierarchyControls.svelte.d.ts} +12 -8
- package/dist/plot/core/components/InteractiveAxisLabel.svelte +1 -1
- package/dist/plot/core/components/InteractiveAxisLabel.svelte.d.ts +1 -1
- package/dist/plot/core/components/Line.svelte +18 -9
- package/dist/plot/core/components/Line.svelte.d.ts +2 -0
- package/dist/plot/core/components/PlotAxis.svelte +40 -17
- package/dist/plot/core/components/PlotControls.svelte +65 -41
- package/dist/plot/core/components/PlotLegend.svelte +31 -35
- package/dist/plot/core/components/PlotMarginals.svelte +616 -0
- package/dist/plot/core/components/PlotMarginals.svelte.d.ts +15 -0
- package/dist/plot/core/components/PlotTooltip.svelte +1 -1
- package/dist/plot/core/components/PortalSelect.svelte +4 -8
- package/dist/plot/core/components/ReferenceLine.svelte +27 -24
- package/dist/plot/core/components/ReferenceLine3D.svelte +54 -57
- package/dist/plot/core/components/ReferencePlane.svelte +7 -8
- package/dist/plot/core/components/ZeroLines.svelte +4 -8
- package/dist/plot/core/components/ZoomRect.svelte +1 -2
- package/dist/plot/core/components/index.d.ts +1 -0
- package/dist/plot/core/components/index.js +1 -0
- package/dist/plot/core/data-cleaning-signal.d.ts +46 -0
- package/dist/plot/core/data-cleaning-signal.js +467 -0
- package/dist/plot/core/data-cleaning.d.ts +2 -42
- package/dist/plot/core/data-cleaning.js +27 -474
- package/dist/plot/core/data-transform.d.ts +1 -2
- package/dist/plot/core/data-transform.js +9 -15
- package/dist/plot/core/fill-utils.d.ts +3 -1
- package/dist/plot/core/fill-utils.js +28 -6
- package/dist/plot/core/index.d.ts +1 -0
- package/dist/plot/core/index.js +1 -0
- package/dist/plot/core/interactions.js +0 -2
- package/dist/plot/core/layout.d.ts +15 -6
- package/dist/plot/core/layout.js +57 -38
- 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 +2 -4
- package/dist/plot/core/pan-zoom.svelte.js +34 -17
- package/dist/plot/core/placed-tween.svelte.d.ts +3 -0
- package/dist/plot/core/placed-tween.svelte.js +68 -21
- package/dist/plot/core/reference-line.js +1 -1
- package/dist/plot/core/scales.d.ts +23 -0
- package/dist/plot/core/scales.js +39 -13
- package/dist/plot/core/svg.js +0 -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 +29 -324
- package/dist/plot/core/types.js +23 -20
- package/dist/plot/core/utils/hierarchy-chart.d.ts +52 -0
- package/dist/plot/core/utils/hierarchy-chart.js +191 -0
- package/dist/plot/core/utils/hierarchy-labels.d.ts +20 -0
- package/dist/plot/core/utils/hierarchy-labels.js +100 -0
- package/dist/plot/core/utils/label-placement.js +2 -2
- package/dist/plot/histogram/Histogram.svelte +314 -340
- package/dist/plot/histogram/Histogram.svelte.d.ts +7 -7
- package/dist/plot/histogram/HistogramControls.svelte +30 -28
- package/dist/plot/histogram/histogram.d.ts +31 -0
- package/dist/plot/histogram/histogram.js +70 -0
- package/dist/plot/histogram/index.d.ts +1 -0
- package/dist/plot/histogram/index.js +1 -0
- package/dist/plot/index.d.ts +1 -0
- package/dist/plot/index.js +1 -0
- package/dist/plot/sankey/Sankey.svelte +78 -69
- package/dist/plot/sankey/Sankey.svelte.d.ts +2 -2
- package/dist/plot/sankey/SankeyControls.svelte +22 -16
- package/dist/plot/sankey/sankey.js +13 -9
- package/dist/plot/scatter/BinnedScatterPlot.svelte +311 -141
- package/dist/plot/scatter/BinnedScatterPlot.svelte.d.ts +7 -0
- package/dist/plot/scatter/ElementScatter.svelte +4 -1
- package/dist/plot/scatter/ScatterPlot.svelte +517 -410
- package/dist/plot/scatter/ScatterPlot.svelte.d.ts +2 -0
- package/dist/plot/scatter/ScatterPlotControls.svelte +18 -19
- package/dist/plot/scatter/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/scatter/ScatterPoint.svelte +16 -9
- package/dist/plot/scatter/adaptive-density.d.ts +1 -1
- package/dist/plot/scatter/adaptive-density.js +36 -20
- package/dist/plot/scatter/binned-scatter-types.d.ts +3 -2
- package/dist/plot/scatter/binned-scatter-types.js +6 -1
- package/dist/plot/scatter/scatter-data.d.ts +1 -1
- package/dist/plot/scatter/scatter-data.js +8 -7
- package/dist/plot/scatter-3d/ScatterPlot3D.svelte +25 -34
- package/dist/plot/scatter-3d/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/scatter-3d/ScatterPlot3DControls.svelte +8 -5
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte +113 -116
- package/dist/plot/scatter-3d/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/scatter-3d/Surface3D.svelte +4 -13
- package/dist/plot/sunburst/Sunburst.svelte +269 -347
- package/dist/plot/sunburst/Sunburst.svelte.d.ts +2 -2
- package/dist/plot/sunburst/index.d.ts +1 -1
- package/dist/plot/sunburst/index.js +2 -1
- package/dist/plot/sunburst/render.d.ts +2 -2
- package/dist/plot/sunburst/render.js +47 -22
- package/dist/plot/sunburst/sunburst.d.ts +5 -1
- package/dist/plot/sunburst/sunburst.js +10 -3
- package/dist/plot/treemap/Treemap.svelte +1022 -0
- package/dist/plot/treemap/Treemap.svelte.d.ts +90 -0
- package/dist/plot/treemap/index.d.ts +4 -0
- package/dist/plot/treemap/index.js +4 -0
- package/dist/plot/treemap/labels.d.ts +38 -0
- package/dist/plot/treemap/labels.js +80 -0
- package/dist/plot/treemap/treemap.d.ts +28 -0
- package/dist/plot/treemap/treemap.js +102 -0
- package/dist/rdf/RdfPlot.svelte +37 -59
- package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
- package/dist/rdf/calc-rdf.js +63 -52
- package/dist/rdf/index.d.ts +0 -1
- package/dist/sanitize.js +85 -27
- package/dist/scene/SceneCamera.svelte +1 -1
- package/dist/scene/SceneCamera.svelte.d.ts +1 -1
- package/dist/scene/bind-renderer.svelte.js +14 -6
- package/dist/scene/index.d.ts +1 -2
- package/dist/scene/index.js +1 -1
- package/dist/scene/{props.js → props.svelte.js} +15 -1
- package/dist/settings.d.ts +19 -9
- package/dist/settings.js +229 -525
- package/dist/spectral/Bands.svelte +207 -221
- package/dist/spectral/BandsAndDos.svelte +11 -9
- package/dist/spectral/BrillouinBandsDos.svelte +19 -42
- package/dist/spectral/Dos.svelte +92 -93
- package/dist/spectral/helpers.d.ts +3 -3
- package/dist/spectral/helpers.js +65 -140
- package/dist/state.svelte.js +5 -18
- package/dist/structure/Arrow.svelte +5 -18
- package/dist/structure/ArrowInstances.svelte +160 -0
- package/dist/structure/ArrowInstances.svelte.d.ts +16 -0
- package/dist/structure/AtomLegend.svelte +120 -85
- package/dist/structure/AtomLegend.svelte.d.ts +2 -3
- package/dist/structure/Bond.svelte +31 -23
- package/dist/structure/CanvasTooltip.svelte +15 -3
- package/dist/structure/CellSelect.svelte +45 -35
- 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 +636 -504
- package/dist/structure/Structure.svelte.d.ts +4 -2
- package/dist/structure/StructureCarousel.svelte +637 -0
- package/dist/structure/StructureCarousel.svelte.d.ts +19 -0
- package/dist/structure/StructureControls.svelte +165 -425
- package/dist/structure/StructureControls.svelte.d.ts +1 -1
- package/dist/structure/StructureExportPane.svelte +17 -21
- package/dist/structure/StructureInfoPane.svelte +128 -133
- package/dist/structure/StructureInfoPane.svelte.d.ts +1 -1
- package/dist/structure/StructureScene.svelte +535 -578
- package/dist/structure/StructureScene.svelte.d.ts +6 -2
- package/dist/structure/StructureViewport.svelte +451 -0
- package/dist/structure/StructureViewport.svelte.d.ts +65 -0
- package/dist/structure/atom-label-placement.d.ts +16 -12
- package/dist/structure/atom-label-placement.js +70 -37
- package/dist/structure/atom-properties.d.ts +2 -0
- package/dist/structure/atom-properties.js +105 -38
- package/dist/structure/bond-order-perception.js +3 -8
- package/dist/structure/bonding.d.ts +9 -3
- package/dist/structure/bonding.js +95 -46
- package/dist/structure/edit-history.d.ts +6 -0
- package/dist/structure/edit-history.js +27 -0
- package/dist/structure/export.d.ts +1 -0
- package/dist/structure/export.js +89 -105
- package/dist/structure/format-detect.d.ts +1 -0
- package/dist/structure/format-detect.js +20 -0
- package/dist/structure/geometry.d.ts +9 -0
- package/dist/structure/geometry.js +27 -0
- package/dist/structure/index.d.ts +16 -1
- package/dist/structure/index.js +15 -10
- package/dist/structure/measure.d.ts +1 -1
- package/dist/structure/measure.js +6 -12
- package/dist/structure/parse.d.ts +2 -1
- package/dist/structure/parse.js +272 -313
- package/dist/structure/partial-occupancy.js +10 -5
- package/dist/structure/pbc.js +85 -107
- package/dist/structure/polyhedra.d.ts +0 -1
- package/dist/structure/polyhedra.js +7 -21
- package/dist/structure/prop-groups.d.ts +85 -0
- package/dist/structure/prop-groups.js +18 -0
- package/dist/structure/supercell.d.ts +1 -2
- package/dist/structure/supercell.js +7 -13
- package/dist/symmetry/SymmetryElements.svelte +11 -15
- package/dist/symmetry/SymmetryStats.svelte +55 -71
- package/dist/symmetry/WyckoffTable.svelte +10 -28
- package/dist/symmetry/cell-transform.js +6 -14
- package/dist/symmetry/index.d.ts +1 -0
- package/dist/symmetry/index.js +13 -10
- package/dist/symmetry/spacegroups.js +3 -2
- package/dist/table/HeatmapTable.svelte +1142 -447
- package/dist/table/HeatmapTable.svelte.d.ts +12 -2
- package/dist/table/ToggleMenu.svelte +168 -53
- package/dist/table/ToggleMenu.svelte.d.ts +1 -1
- 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/embedded.d.ts +18 -0
- package/dist/theme/embedded.js +206 -0
- package/dist/theme/index.d.ts +5 -5
- package/dist/theme/index.js +5 -12
- package/dist/theme/themes.mjs +44 -66
- package/dist/tooltip/KCoords.svelte +4 -1
- package/dist/tooltip/TooltipContent.svelte +3 -6
- package/dist/tooltip/TooltipContent.svelte.d.ts +1 -1
- package/dist/tooltip/index.d.ts +8 -1
- package/dist/trajectory/Trajectory.svelte +613 -386
- package/dist/trajectory/Trajectory.svelte.d.ts +1 -1
- package/dist/trajectory/TrajectoryError.svelte +12 -74
- package/dist/trajectory/TrajectoryExportPane.svelte +14 -32
- package/dist/trajectory/TrajectoryInfoPane.svelte +127 -164
- package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +2 -1
- package/dist/trajectory/extract.js +61 -49
- package/dist/trajectory/format-detect.d.ts +2 -0
- package/dist/trajectory/format-detect.js +17 -13
- package/dist/trajectory/frame-reader.d.ts +2 -0
- package/dist/trajectory/frame-reader.js +35 -32
- package/dist/trajectory/helpers.d.ts +2 -0
- package/dist/trajectory/helpers.js +34 -56
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +5 -8
- package/dist/trajectory/parse/ase.js +1 -1
- 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 +6 -6
- package/dist/trajectory/parse/index.js +25 -33
- package/dist/trajectory/parse/lammps.js +8 -12
- package/dist/trajectory/parse/vasp.js +10 -7
- package/dist/trajectory/parse/vaspout-electronic.d.ts +13 -0
- package/dist/trajectory/parse/vaspout-electronic.js +207 -0
- package/dist/trajectory/parse/vaspout-h5.d.ts +4 -0
- package/dist/trajectory/parse/vaspout-h5.js +281 -0
- package/dist/trajectory/parse/xyz.js +59 -9
- package/dist/trajectory/plotting.d.ts +8 -9
- package/dist/trajectory/plotting.js +113 -64
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +7 -0
- package/dist/xrd/XrdPlot.svelte +79 -118
- package/dist/xrd/broadening.js +2 -1
- package/dist/xrd/calc-xrd.js +24 -62
- package/dist/xrd/parse.d.ts +0 -1
- package/dist/xrd/parse.js +69 -95
- package/package.json +94 -34
- package/readme.md +6 -5
- package/dist/api/mp.d.ts +0 -6
- package/dist/api/mp.js +0 -22
- package/dist/plot/sunburst/SunburstControls.svelte +0 -200
- package/dist/scene/types.d.ts +0 -26
- package/dist/tooltip/types.d.ts +0 -8
- package/dist/trajectory/constants.d.ts +0 -6
- package/dist/trajectory/constants.js +0 -11
- /package/dist/{scene/types.js → file-viewer/host-protocol.js} +0 -0
- /package/dist/{tooltip/types.js → isosurface/geometry-worker-types.js} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { D3InterpolateName } from '../colors'
|
|
3
|
-
import { get_d3_interpolator } from '../colors'
|
|
3
|
+
import { get_d3_interpolator, is_dark_mode, watch_dark_mode } from '../colors'
|
|
4
4
|
import type { ElementSymbol } from '../element'
|
|
5
|
-
import {
|
|
5
|
+
import { element_by_symbol } from '../element'
|
|
6
6
|
import Isosurface from '../isosurface/Isosurface.svelte'
|
|
7
7
|
import type { IsosurfaceSettings, VolumetricData } from '../isosurface/types'
|
|
8
8
|
import { DEFAULT_ISOSURFACE_SETTINGS } from '../isosurface/types'
|
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
StructureBond,
|
|
26
26
|
} from './'
|
|
27
27
|
import {
|
|
28
|
-
Arrow,
|
|
29
28
|
atomic_radii,
|
|
30
29
|
Cylinder,
|
|
31
30
|
get_all_site_vectors,
|
|
@@ -34,11 +33,11 @@
|
|
|
34
33
|
Lattice,
|
|
35
34
|
VECTOR_PALETTE,
|
|
36
35
|
} from './'
|
|
36
|
+
import ArrowInstances from './ArrowInstances.svelte'
|
|
37
|
+
import InstancedAtoms from './InstancedAtoms.svelte'
|
|
38
|
+
import SiteLabels from './SiteLabels.svelte'
|
|
37
39
|
import type { AtomColorConfig } from './atom-properties'
|
|
38
|
-
import {
|
|
39
|
-
get_orig_site_idx,
|
|
40
|
-
get_property_colors,
|
|
41
|
-
} from './atom-properties'
|
|
40
|
+
import { get_orig_site_idx, get_property_colors } from './atom-properties'
|
|
42
41
|
import type { SymmetryElement } from '../symmetry'
|
|
43
42
|
import { has_visible_symmetry_overlay } from '../symmetry/symmetry-elements'
|
|
44
43
|
import SymmetryElements from '../symmetry/SymmetryElements.svelte'
|
|
@@ -59,9 +58,9 @@
|
|
|
59
58
|
import type { BondEditResult, BondingStrategy, BondKeyTarget } from './bonding'
|
|
60
59
|
import {
|
|
61
60
|
add_or_restore_bond,
|
|
62
|
-
BONDING_STRATEGIES,
|
|
63
61
|
BOND_ORDER_OPTIONS,
|
|
64
62
|
canonicalize_bond_target,
|
|
63
|
+
compute_bonds,
|
|
65
64
|
delete_bond as apply_delete_bond,
|
|
66
65
|
get_bond_key,
|
|
67
66
|
get_bond_render_matrices,
|
|
@@ -70,37 +69,11 @@
|
|
|
70
69
|
set_bond_order as apply_set_bond_order,
|
|
71
70
|
structure_bond_to_bond_pair,
|
|
72
71
|
} from './bonding'
|
|
73
|
-
import {
|
|
74
|
-
|
|
75
|
-
compose_perceived_bonds,
|
|
76
|
-
perceive_bond_orders,
|
|
77
|
-
} from './index'
|
|
78
|
-
import {
|
|
79
|
-
choose_site_label_offset,
|
|
80
|
-
LABEL_OFFSET_EPS,
|
|
81
|
-
make_label_position_calculator,
|
|
82
|
-
} from './atom-label-placement'
|
|
72
|
+
import { CanvasTooltip, compose_perceived_bonds, perceive_bond_orders } from './index'
|
|
73
|
+
import { choose_site_label_offset, LABEL_OFFSET_EPS } from './atom-label-placement'
|
|
83
74
|
import type { PolyhedraColorMode, Polyhedron } from './polyhedra'
|
|
84
75
|
import { compute_polyhedra, merge_polyhedra_buffers } from './polyhedra'
|
|
85
76
|
|
|
86
|
-
type InstancedAtomGroup = {
|
|
87
|
-
element: string
|
|
88
|
-
radius: number
|
|
89
|
-
color: string
|
|
90
|
-
is_image_atom: boolean
|
|
91
|
-
atoms: (typeof atom_data)[number][]
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function instanced_atom_group_key(
|
|
95
|
-
{ element, radius, color, is_image_atom, atoms }: InstancedAtomGroup,
|
|
96
|
-
measure_mode: MeasureMode,
|
|
97
|
-
): string {
|
|
98
|
-
const edit_mode_image = measure_mode === `edit-atoms` && is_image_atom
|
|
99
|
-
return `${element}-${format_num(radius, `.3~`)}-${color}-${
|
|
100
|
-
is_image_atom ? `img` : `base`
|
|
101
|
-
}-${edit_mode_image}-${atoms.length}`
|
|
102
|
-
}
|
|
103
|
-
|
|
104
77
|
type EditableAtomHitTarget = {
|
|
105
78
|
site_idx: number
|
|
106
79
|
position: Vec3
|
|
@@ -133,6 +106,7 @@
|
|
|
133
106
|
same_size_atoms = false,
|
|
134
107
|
camera_position = DEFAULTS.structure.camera_position,
|
|
135
108
|
camera_target = undefined,
|
|
109
|
+
camera_direction = undefined,
|
|
136
110
|
camera_projection = DEFAULTS.structure.camera_projection,
|
|
137
111
|
rotation_damping = DEFAULTS.structure.rotation_damping,
|
|
138
112
|
max_zoom = DEFAULTS.structure.max_zoom,
|
|
@@ -237,14 +211,19 @@
|
|
|
237
211
|
dragging_atoms = $bindable(false),
|
|
238
212
|
volumetric_data = undefined,
|
|
239
213
|
isosurface_settings = DEFAULT_ISOSURFACE_SETTINGS,
|
|
214
|
+
active_volume_idx = 0,
|
|
215
|
+
volume_scaling = [1, 1, 1],
|
|
216
|
+
interactive = true,
|
|
240
217
|
}: SceneControlProps & {
|
|
241
218
|
structure?: AnyStructure
|
|
242
219
|
base_structure?: AnyStructure // The original structure without image atoms, used for property color calculation
|
|
243
220
|
atom_radius?: number // scale factor for atomic radii
|
|
244
|
-
same_size_atoms?: boolean //
|
|
245
|
-
// determined by the atomic radius of the element
|
|
221
|
+
same_size_atoms?: boolean // uniform radius for all atoms (else per-element atomic radii)
|
|
246
222
|
camera_position?: [x: number, y: number, z: number] // initial camera position from which to render the scene
|
|
247
223
|
camera_target?: Vec3 // external orbit-controls target for pan synchronization
|
|
224
|
+
// When set (and camera_position is unset/zero), auto-place the camera along this
|
|
225
|
+
// direction from the structure center (used by the multi-side view for fixed angles)
|
|
226
|
+
camera_direction?: Vec3
|
|
248
227
|
show_atoms?: boolean
|
|
249
228
|
show_bonds?: ShowBonds
|
|
250
229
|
show_site_labels?: boolean
|
|
@@ -287,7 +266,10 @@
|
|
|
287
266
|
// Fractional coords must refer to the SAME cell as the rendered lattice (moyo
|
|
288
267
|
// operations are in the input-cell frame, i.e. the original untransformed cell).
|
|
289
268
|
symmetry_elements?: SymmetryElement[]
|
|
290
|
-
symmetry_elements_props?: Omit<
|
|
269
|
+
symmetry_elements_props?: Omit<
|
|
270
|
+
ComponentProps<typeof SymmetryElements>,
|
|
271
|
+
`elements` | `lattice`
|
|
272
|
+
>
|
|
291
273
|
// Auto-reduce visual clutter while a symmetry-element overlay is visible: hides
|
|
292
274
|
// coordination polyhedra and calculated bonds, and shrinks atoms so axes/planes/
|
|
293
275
|
// centers stay readable. Purely derived — toggling the overlay off restores the
|
|
@@ -335,8 +317,15 @@
|
|
|
335
317
|
add_element?: ElementSymbol // element to add when clicking in add-atom mode
|
|
336
318
|
cursor?: string // cursor style for the 3D canvas
|
|
337
319
|
dragging_atoms?: boolean // true while TransformControls drag is active (skips expensive recalculations)
|
|
338
|
-
|
|
320
|
+
// Loaded volumetric datasets for isosurface rendering (single volume accepted
|
|
321
|
+
// for backwards compatibility)
|
|
322
|
+
volumetric_data?: VolumetricData | VolumetricData[]
|
|
339
323
|
isosurface_settings?: IsosurfaceSettings // Isosurface rendering settings
|
|
324
|
+
active_volume_idx?: number // Volume implicit single-isovalue settings apply to
|
|
325
|
+
volume_scaling?: Vec3 // Supercell tiling applied to isosurface geometry
|
|
326
|
+
// When false, render the scene without hover/edit raycast helpers. Used by multi-side
|
|
327
|
+
// view so inactive panes skip interaction-only work while the active pane stays editable.
|
|
328
|
+
interactive?: boolean
|
|
340
329
|
} = $props()
|
|
341
330
|
|
|
342
331
|
const pulse = create_pulse_animation(
|
|
@@ -344,6 +333,8 @@
|
|
|
344
333
|
{ step: 0.015, frequency: 5 },
|
|
345
334
|
)
|
|
346
335
|
let pulse_opacity = $derived(0.15 + 0.25 * pulse.unit)
|
|
336
|
+
let dark_mode = $state(is_dark_mode())
|
|
337
|
+
$effect(() => watch_dark_mode((dark) => (dark_mode = dark)))
|
|
347
338
|
|
|
348
339
|
bind_renderer((threlte_scene, threlte_camera) => {
|
|
349
340
|
scene = threlte_scene
|
|
@@ -364,7 +355,6 @@
|
|
|
364
355
|
initial_computed_zoom = stored_initial_zoom
|
|
365
356
|
})
|
|
366
357
|
|
|
367
|
-
let bond_pairs: BondPair[] = $state([])
|
|
368
358
|
let atom_tooltip_active = $state(false)
|
|
369
359
|
let hovered_bond_key = $state<string | null>(null)
|
|
370
360
|
const ATOM_HOVER_CLEAR_DELAY_MS = 200
|
|
@@ -392,20 +382,18 @@
|
|
|
392
382
|
}, ATOM_HOVER_CLEAR_DELAY_MS)
|
|
393
383
|
}
|
|
394
384
|
|
|
395
|
-
const atom_hover_props = (site_idx: number | null
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (!disabled && site_idx != null) schedule_atom_hover_clear(site_idx)
|
|
404
|
-
},
|
|
405
|
-
})
|
|
385
|
+
const atom_hover_props = (site_idx: number | null) =>
|
|
386
|
+
!interactive || site_idx == null
|
|
387
|
+
? {}
|
|
388
|
+
: {
|
|
389
|
+
onpointerenter: () => set_atom_hover(site_idx),
|
|
390
|
+
onpointermove: () => set_atom_hover(site_idx),
|
|
391
|
+
onpointerleave: () => schedule_atom_hover_clear(site_idx),
|
|
392
|
+
}
|
|
406
393
|
|
|
407
394
|
// Cursor style for the canvas, derived from mode and hover state
|
|
408
395
|
let canvas_cursor = $derived.by(() => {
|
|
396
|
+
if (!interactive) return `default`
|
|
409
397
|
if (measure_mode === `edit-atoms` && add_atom_mode) return `crosshair`
|
|
410
398
|
if (measure_mode === `edit-bonds` && hovered_bond_key != null) {
|
|
411
399
|
return bond_edits_enabled ? `pointer` : `not-allowed`
|
|
@@ -419,7 +407,6 @@
|
|
|
419
407
|
if (measure_mode === `edit-atoms`) {
|
|
420
408
|
const site = structure?.sites?.[hovered_idx]
|
|
421
409
|
if (site?.properties?.orig_site_idx != null) return `not-allowed`
|
|
422
|
-
return `pointer`
|
|
423
410
|
}
|
|
424
411
|
return `pointer`
|
|
425
412
|
}
|
|
@@ -451,6 +438,22 @@
|
|
|
451
438
|
bond_context_target = null
|
|
452
439
|
}
|
|
453
440
|
|
|
441
|
+
// Shared handlers for bond context-menu buttons: act on pointerdown (a click would
|
|
442
|
+
// arrive after orbit-controls' start handler already closed the menu) or Enter/Space
|
|
443
|
+
const menu_action_props = (action: () => void) => {
|
|
444
|
+
const run = (event: Event) => {
|
|
445
|
+
event.preventDefault()
|
|
446
|
+
event.stopPropagation()
|
|
447
|
+
action()
|
|
448
|
+
}
|
|
449
|
+
return {
|
|
450
|
+
onpointerdown: run,
|
|
451
|
+
onkeydown: (event: KeyboardEvent) => {
|
|
452
|
+
if (event.key === `Enter` || event.key === ` `) run(event)
|
|
453
|
+
},
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
454
457
|
const canonical_bond_target = (bond: BondKeyTarget): BondKeyTarget =>
|
|
455
458
|
canonicalize_bond_target(bond, structure?.sites)
|
|
456
459
|
|
|
@@ -474,8 +477,9 @@
|
|
|
474
477
|
): BondKeyTarget {
|
|
475
478
|
const rendered_target = { site_idx_1, site_idx_2, cell_shift }
|
|
476
479
|
const rendered_key = rendered_bond_key_for(rendered_target)
|
|
477
|
-
return
|
|
478
|
-
canonical_bond_target(rendered_target)
|
|
480
|
+
return (
|
|
481
|
+
find_added_bond_by_rendered_key(rendered_key) ?? canonical_bond_target(rendered_target)
|
|
482
|
+
)
|
|
479
483
|
}
|
|
480
484
|
|
|
481
485
|
const is_image_bond_site = (site_idx: number): boolean =>
|
|
@@ -486,9 +490,11 @@
|
|
|
486
490
|
|
|
487
491
|
const can_edit_bond = (bond: BondKeyTarget): boolean => {
|
|
488
492
|
const target = canonical_bond_target(bond)
|
|
489
|
-
return
|
|
493
|
+
return (
|
|
494
|
+
bond_edits_enabled &&
|
|
490
495
|
!is_image_bond_site(target.site_idx_1) &&
|
|
491
496
|
!is_image_bond_site(target.site_idx_2)
|
|
497
|
+
)
|
|
492
498
|
}
|
|
493
499
|
|
|
494
500
|
const format_bond_order = (order: BondOrder | undefined): string =>
|
|
@@ -500,10 +506,12 @@
|
|
|
500
506
|
cell_shift?: Vec3,
|
|
501
507
|
): BondOrder | undefined {
|
|
502
508
|
const key = get_bond_key(site_idx_1, site_idx_2, cell_shift)
|
|
503
|
-
return
|
|
509
|
+
return (
|
|
510
|
+
find_added_bond_by_rendered_key(key)?.order ??
|
|
504
511
|
bond_order_overrides.find((bond) => matches_bond_key(bond, key))?.order ??
|
|
505
512
|
added_bonds.find((bond) => matches_bond_key(bond, key))?.order ??
|
|
506
513
|
filtered_bond_pairs.find((bond) => matches_bond_key(bond, key))?.bond_order
|
|
514
|
+
)
|
|
507
515
|
}
|
|
508
516
|
|
|
509
517
|
const midpoint = (pos_1: Vec3, pos_2: Vec3): Vec3 => [
|
|
@@ -515,20 +523,14 @@
|
|
|
515
523
|
const BOND_ENDPOINT_HIT_FRACTION = 0.3
|
|
516
524
|
const BOND_ENDPOINT_SITE_MATCH_TOLERANCE = 1e-6
|
|
517
525
|
const EDITABLE_ATOM_HIT_RADIUS_SCALE = 1.15
|
|
518
|
-
const skip_raycast = (): void => undefined
|
|
519
526
|
|
|
520
527
|
function apply_bond_transform(mesh: Mesh, bond: BondPair): void {
|
|
521
528
|
mesh.matrix.fromArray(bond.transform_matrix)
|
|
522
529
|
mesh.matrixWorldNeedsUpdate = true
|
|
523
530
|
}
|
|
524
531
|
|
|
525
|
-
function apply_non_raycastable_bond_hit_transform(mesh: Mesh, bond: BondPair): void {
|
|
526
|
-
apply_bond_transform(mesh, bond)
|
|
527
|
-
disable_raycast(mesh)
|
|
528
|
-
}
|
|
529
|
-
|
|
530
532
|
function disable_raycast(mesh: Mesh): void {
|
|
531
|
-
mesh.raycast =
|
|
533
|
+
mesh.raycast = () => undefined
|
|
532
534
|
}
|
|
533
535
|
|
|
534
536
|
function site_world_position(parent: Object3D, site: Site): Vector3 {
|
|
@@ -553,9 +555,10 @@
|
|
|
553
555
|
return site_idx
|
|
554
556
|
}
|
|
555
557
|
|
|
556
|
-
const image_site_idx = structure.sites.findIndex(
|
|
557
|
-
candidate_site
|
|
558
|
-
|
|
558
|
+
const image_site_idx = structure.sites.findIndex(
|
|
559
|
+
(candidate_site) =>
|
|
560
|
+
candidate_site.properties?.orig_site_idx === site_idx &&
|
|
561
|
+
matches_world_position(candidate_site),
|
|
559
562
|
)
|
|
560
563
|
return image_site_idx === -1 ? site_idx : image_site_idx
|
|
561
564
|
}
|
|
@@ -590,10 +593,7 @@
|
|
|
590
593
|
|
|
591
594
|
let label_screen_margin = $derived(site_label_size * 10 + site_label_padding)
|
|
592
595
|
|
|
593
|
-
function get_bond_context_menu_position(
|
|
594
|
-
bond: BondPair,
|
|
595
|
-
event?: BondContextMenuEvent,
|
|
596
|
-
): Vec3 {
|
|
596
|
+
function get_bond_context_menu_position(bond: BondPair, event?: BondContextMenuEvent): Vec3 {
|
|
597
597
|
const parent = event?.object?.parent
|
|
598
598
|
if (!event?.point || !parent) return midpoint(bond.pos_1, bond.pos_2)
|
|
599
599
|
|
|
@@ -634,8 +634,10 @@
|
|
|
634
634
|
): BondPair | undefined => {
|
|
635
635
|
const rendered_key = rendered_bond_key_for(target)
|
|
636
636
|
const canonical_key = bond_key_for(canonical_target)
|
|
637
|
-
return
|
|
637
|
+
return (
|
|
638
|
+
filtered_bond_pairs.find((bond) => rendered_bond_key_for(bond) === rendered_key) ??
|
|
638
639
|
filtered_bond_pairs.find((bond) => bond_key_for(bond) === canonical_key)
|
|
640
|
+
)
|
|
639
641
|
}
|
|
640
642
|
|
|
641
643
|
function open_bond_order_menu_for_target(
|
|
@@ -647,6 +649,7 @@
|
|
|
647
649
|
}
|
|
648
650
|
|
|
649
651
|
function add_or_restore_pair(site_idx_1: number, site_idx_2: number) {
|
|
652
|
+
if (!interactive) return // inactive panes must not mutate shared bond state
|
|
650
653
|
const rendered_target = { site_idx_1, site_idx_2 }
|
|
651
654
|
if (!can_edit_bond(rendered_target)) return
|
|
652
655
|
const edit_state = current_bond_edit_state()
|
|
@@ -704,11 +707,7 @@
|
|
|
704
707
|
const target = resolve_bond_edit_target(site_idx_1, site_idx_2, cell_shift)
|
|
705
708
|
if (!can_edit_bond(target)) return
|
|
706
709
|
apply_bond_edit_result(
|
|
707
|
-
apply_delete_bond(
|
|
708
|
-
current_bond_edit_state(),
|
|
709
|
-
target,
|
|
710
|
-
editable_perceived_bond_pairs,
|
|
711
|
-
),
|
|
710
|
+
apply_delete_bond(current_bond_edit_state(), target, editable_perceived_bond_pairs),
|
|
712
711
|
)
|
|
713
712
|
}
|
|
714
713
|
|
|
@@ -722,12 +721,10 @@
|
|
|
722
721
|
// intercept the same native click, only the first intersection should fire.
|
|
723
722
|
// All threlte intersection events from one click share the same nativeEvent ref.
|
|
724
723
|
let last_native_event: Event | null = null
|
|
725
|
-
//
|
|
726
|
-
// back to click. When pointerdown did fire, skip the matching click once.
|
|
724
|
+
// Instanced-atom raycasts do not always emit pointerdown, so edit-bonds also
|
|
725
|
+
// falls back to click. When pointerdown did fire, skip the matching click once.
|
|
727
726
|
let last_edit_bonds_pointerdown_site_idx: number | null = null
|
|
728
|
-
let clear_edit_bonds_pointerdown_site_timeout:
|
|
729
|
-
| ReturnType<typeof setTimeout>
|
|
730
|
-
| null = null
|
|
727
|
+
let clear_edit_bonds_pointerdown_site_timeout: ReturnType<typeof setTimeout> | null = null
|
|
731
728
|
|
|
732
729
|
function remember_edit_bonds_pointerdown_site(site_idx: number) {
|
|
733
730
|
last_edit_bonds_pointerdown_site_idx = site_idx
|
|
@@ -756,30 +753,62 @@
|
|
|
756
753
|
return true
|
|
757
754
|
}
|
|
758
755
|
|
|
759
|
-
//
|
|
760
|
-
//
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
if (
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
if (skip_duplicate_edit_bonds_click(site_idx)) {
|
|
772
|
-
event.stopPropagation()
|
|
773
|
-
return
|
|
774
|
-
}
|
|
756
|
+
// Selection handlers shared by instanced atom meshes and per-site hit targets
|
|
757
|
+
// so the edit-bonds click semantics can't drift between the two paths
|
|
758
|
+
const handle_atom_pointerdown = (site_idx: number, event: PointerEvent) => {
|
|
759
|
+
if (measure_mode !== `edit-bonds` || bond_edit_mode !== `add`) return
|
|
760
|
+
select_edit_bonds_site(site_idx, event)
|
|
761
|
+
}
|
|
762
|
+
const handle_atom_click = (site_idx: number, event: MouseEvent) => {
|
|
763
|
+
if (measure_mode === `edit-bonds`) {
|
|
764
|
+
if (bond_edit_mode !== `add`) return
|
|
765
|
+
if (skip_duplicate_edit_bonds_click(site_idx)) {
|
|
766
|
+
event.stopPropagation()
|
|
767
|
+
return
|
|
775
768
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
}
|
|
769
|
+
}
|
|
770
|
+
toggle_selection(site_idx, event)
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// Pointer handlers for an instanced atom mesh: intersection events carry the
|
|
774
|
+
// hit `instanceId`, which indexes into the mesh's `atoms` array. One handler
|
|
775
|
+
// set per mesh instead of one per atom. Inactive grid panes render without
|
|
776
|
+
// raycast handlers; ghosted edit-mode image atoms are non-interactive.
|
|
777
|
+
type InstanceEvent = { instanceId?: number }
|
|
778
|
+
const atom_instance_events = (
|
|
779
|
+
instance_atoms: { site_idx: number }[],
|
|
780
|
+
is_edit_image: boolean,
|
|
781
|
+
) => {
|
|
782
|
+
if (!interactive || is_edit_image) return {}
|
|
783
|
+
const wrap =
|
|
784
|
+
<Event_ extends InstanceEvent>(handler: (site_idx: number, event: Event_) => void) =>
|
|
785
|
+
(event: Event_) => {
|
|
786
|
+
const site_idx = instance_atoms[event.instanceId ?? -1]?.site_idx
|
|
787
|
+
if (site_idx != null) handler(site_idx, event)
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
onpointerenter: wrap(set_atom_hover),
|
|
791
|
+
onpointermove: wrap(set_atom_hover),
|
|
792
|
+
onpointerleave: wrap(schedule_atom_hover_clear),
|
|
793
|
+
onpointerdown: wrap<PointerEvent & InstanceEvent>(handle_atom_pointerdown),
|
|
794
|
+
onclick: wrap<MouseEvent & InstanceEvent>(handle_atom_click),
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Pointer props (hover + select) for per-site hit-target meshes (partial-occupancy
|
|
799
|
+
// sites), with the same interactivity gating as atom_instance_events
|
|
800
|
+
const atom_pointer_props = (site_idx: number, is_edit_image: boolean) =>
|
|
801
|
+
!interactive || is_edit_image
|
|
802
|
+
? {}
|
|
803
|
+
: {
|
|
804
|
+
...atom_hover_props(site_idx),
|
|
805
|
+
onpointerdown: (event: PointerEvent) => handle_atom_pointerdown(site_idx, event),
|
|
806
|
+
onclick: (event: MouseEvent) => handle_atom_click(site_idx, event),
|
|
807
|
+
}
|
|
779
808
|
|
|
780
809
|
function toggle_selection(site_index: number, evt?: Event) {
|
|
781
810
|
evt?.stopPropagation?.()
|
|
782
|
-
const event_with_native = evt as Event & { nativeEvent?: unknown } | undefined
|
|
811
|
+
const event_with_native = evt as (Event & { nativeEvent?: unknown }) | undefined
|
|
783
812
|
const native_event = event_with_native?.nativeEvent ?? evt
|
|
784
813
|
if (native_event instanceof Event) {
|
|
785
814
|
if (native_event === last_native_event) return
|
|
@@ -787,6 +816,9 @@
|
|
|
787
816
|
}
|
|
788
817
|
|
|
789
818
|
if (measure_mode === `edit-bonds`) {
|
|
819
|
+
// Only the active pane edits: inactive panes keep selection/hover overlays visible
|
|
820
|
+
// but must not change selection or add/restore bonds in shared state.
|
|
821
|
+
if (!interactive) return
|
|
790
822
|
if (bond_edit_mode === `delete`) {
|
|
791
823
|
measured_sites = []
|
|
792
824
|
selected_sites = []
|
|
@@ -811,12 +843,16 @@
|
|
|
811
843
|
}
|
|
812
844
|
|
|
813
845
|
if (measure_mode === `edit-atoms`) {
|
|
846
|
+
// Inactive panes don't drive edit-atoms selection (gizmo/add-plane are interactive-gated)
|
|
847
|
+
if (!interactive) return
|
|
814
848
|
// Block image atoms (detected by orig_site_idx property from PBC)
|
|
815
849
|
const site = structure?.sites?.[site_index]
|
|
816
850
|
if (site?.properties?.orig_site_idx != null) return
|
|
817
851
|
|
|
818
852
|
const is_selected = selected_sites.includes(site_index)
|
|
819
|
-
|
|
853
|
+
// threlte dispatches plain objects wrapping the DOM event, so read shift
|
|
854
|
+
// from the extracted native event (evt itself is never a MouseEvent for 3D hits)
|
|
855
|
+
const is_shift = native_event instanceof MouseEvent && native_event.shiftKey
|
|
820
856
|
|
|
821
857
|
// In edit-atoms mode, selected_sites and measured_sites always stay in sync
|
|
822
858
|
let new_sites: number[]
|
|
@@ -866,11 +902,15 @@
|
|
|
866
902
|
$effect(() => {
|
|
867
903
|
const count = structure?.sites?.length ?? 0
|
|
868
904
|
if (count <= 0) {
|
|
869
|
-
measured_sites = []
|
|
905
|
+
if (untrack(() => measured_sites.length) > 0) measured_sites = []
|
|
870
906
|
return
|
|
871
907
|
}
|
|
872
908
|
untrack(() => {
|
|
873
|
-
|
|
909
|
+
// Only reassign when out-of-range indices were dropped: a fresh (equal)
|
|
910
|
+
// array identity on every structure change would ripple through all
|
|
911
|
+
// measured_sites bindings and their dependents each frame of a trajectory.
|
|
912
|
+
const filtered = measured_sites.filter((idx) => idx >= 0 && idx < count)
|
|
913
|
+
if (filtered.length !== measured_sites.length) measured_sites = filtered
|
|
874
914
|
})
|
|
875
915
|
})
|
|
876
916
|
|
|
@@ -882,9 +922,7 @@
|
|
|
882
922
|
$effect.pre(() => {
|
|
883
923
|
hovered_site = structure?.sites?.[hovered_idx ?? -1] ?? null
|
|
884
924
|
})
|
|
885
|
-
let lattice = $derived(
|
|
886
|
-
structure && `lattice` in structure ? structure.lattice : null,
|
|
887
|
-
)
|
|
925
|
+
let lattice = $derived(structure && `lattice` in structure ? structure.lattice : null)
|
|
888
926
|
|
|
889
927
|
let visual_lattice = $derived(
|
|
890
928
|
base_structure && `lattice` in base_structure ? base_structure.lattice : lattice,
|
|
@@ -894,9 +932,11 @@
|
|
|
894
932
|
lattice
|
|
895
933
|
? math.scale(math.add(...lattice.matrix), 0.5)
|
|
896
934
|
: structure
|
|
897
|
-
|
|
898
|
-
|
|
935
|
+
? get_center_of_mass(structure)
|
|
936
|
+
: ([0, 0, 0] as Vec3),
|
|
899
937
|
)
|
|
938
|
+
// Negated target for the inner un-translate group (recomputed only on target change)
|
|
939
|
+
let neg_rotation_target = $derived(math.scale(rotation_target, -1) as Vec3)
|
|
900
940
|
|
|
901
941
|
let structure_size = $derived.by(() => {
|
|
902
942
|
if (lattice) return (lattice.a + lattice.b + lattice.c) / 2
|
|
@@ -913,8 +953,8 @@
|
|
|
913
953
|
// Excludes PBC image atoms (orig_site_idx) so toggling image atoms doesn't affect arrow sizing.
|
|
914
954
|
let char_atom_spacing = $derived.by(() => {
|
|
915
955
|
if (!lattice || !structure?.sites?.length) return structure_size
|
|
916
|
-
const n_real = structure.sites.filter(
|
|
917
|
-
site.properties?.orig_site_idx == null
|
|
956
|
+
const n_real = structure.sites.filter(
|
|
957
|
+
(site) => site.properties?.orig_site_idx == null,
|
|
918
958
|
).length
|
|
919
959
|
return n_real > 0 ? Math.cbrt(lattice.volume / n_real) : structure_size
|
|
920
960
|
})
|
|
@@ -945,9 +985,19 @@
|
|
|
945
985
|
|
|
946
986
|
// Using $state because this is mutated in an effect based on viewport/structure size
|
|
947
987
|
let computed_zoom = $state(untrack(() => initial_zoom))
|
|
988
|
+
// structure_size is read untracked so structure changes don't re-zoom the user's view;
|
|
989
|
+
// zoom only re-frames on a genuine viewport resize. Skip same-value width/height
|
|
990
|
+
// re-fires (a wrapping component can transiently re-emit clientWidth/Height during a
|
|
991
|
+
// structure swap) so they don't sneak in a re-zoom with the new structure_size.
|
|
992
|
+
let last_zoom_dims: [number, number] = [0, 0]
|
|
948
993
|
$effect(() => {
|
|
949
994
|
if (!(width > 0) || !(height > 0)) return
|
|
950
|
-
|
|
995
|
+
if (width === last_zoom_dims[0] && height === last_zoom_dims[1]) return
|
|
996
|
+
last_zoom_dims = [width, height]
|
|
997
|
+
const structure_max_dim = Math.max(
|
|
998
|
+
1,
|
|
999
|
+
untrack(() => structure_size),
|
|
1000
|
+
)
|
|
951
1001
|
const viewer_min_dim = Math.min(width, height)
|
|
952
1002
|
const scale_factor = viewer_min_dim / (structure_max_dim * 50) // 50px per unit
|
|
953
1003
|
let new_zoom = initial_zoom * scale_factor
|
|
@@ -956,11 +1006,23 @@
|
|
|
956
1006
|
computed_zoom = new_zoom
|
|
957
1007
|
})
|
|
958
1008
|
|
|
959
|
-
$effect.pre(() => {
|
|
1009
|
+
$effect.pre(() => {
|
|
1010
|
+
// Simple initial camera auto-position: proportional to structure size and fov
|
|
960
1011
|
if (camera_position.every((val) => val === 0) && structure) {
|
|
961
1012
|
stored_initial_zoom = undefined
|
|
962
1013
|
const distance = Math.max(1, structure_size) * (60 / fov)
|
|
963
|
-
|
|
1014
|
+
// When a view direction is given (multi-side view), place the camera
|
|
1015
|
+
// target-relative along it; otherwise use the default angled position.
|
|
1016
|
+
// normalize_vec returns [0,0,0] for an absent or zero-length direction (arrays are
|
|
1017
|
+
// truthy, so a plain `camera_direction ?` check would miss [0,0,0]); treat that as
|
|
1018
|
+
// "no direction" and fall back to the default, since placing the camera on the
|
|
1019
|
+
// rotation target would be a degenerate zero-length view.
|
|
1020
|
+
const view_dir: Vec3 = camera_direction
|
|
1021
|
+
? math.normalize_vec(camera_direction, [0, 0, 0])
|
|
1022
|
+
: [0, 0, 0]
|
|
1023
|
+
camera_position = view_dir.some((val) => val !== 0)
|
|
1024
|
+
? math.add(rotation_target, math.scale(view_dir, distance))
|
|
1025
|
+
: [distance, distance * 0.3, distance * 0.8]
|
|
964
1026
|
}
|
|
965
1027
|
})
|
|
966
1028
|
// Whether a never|always|crystals|molecules setting applies to the current structure
|
|
@@ -984,23 +1046,29 @@
|
|
|
984
1046
|
)
|
|
985
1047
|
// Calculated bonds are hidden in declutter mode (only their cylinders — bond_pairs
|
|
986
1048
|
// stay computed so tooltips and manually added bonds keep working)
|
|
987
|
-
const effective_show_bonds: ShowBonds = $derived(
|
|
988
|
-
declutter_active ? `never` : show_bonds,
|
|
989
|
-
)
|
|
1049
|
+
const effective_show_bonds: ShowBonds = $derived(declutter_active ? `never` : show_bonds)
|
|
990
1050
|
const effective_atom_radius = $derived(declutter_active ? atom_radius * 0.6 : atom_radius)
|
|
991
1051
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1052
|
+
// Derived (not effect + state) so downstream consumers (filtering, polyhedra,
|
|
1053
|
+
// instanced bond buffers) recompute exactly once per structure change instead
|
|
1054
|
+
// of once with stale bonds and again after an effect flush. The mutable cache
|
|
1055
|
+
// lets atom drags freeze the last computed value (recompute happens on release).
|
|
1056
|
+
// Bonds are computed when either bond rendering or polyhedra need them. The
|
|
1057
|
+
// raw/effective mix is deliberate: RAW show_bonds keeps bond_pairs available
|
|
1058
|
+
// during symmetry declutter (cylinders hide via effective_show_bonds in
|
|
1059
|
+
// bonds_to_render, but tooltips + manually added bonds still need the data),
|
|
1060
|
+
// while EFFECTIVE show_polyhedra skips computing bonds whose only consumer —
|
|
1061
|
+
// the polyhedra $derived below, gated on the same effective value — won't run.
|
|
1062
|
+
let last_bond_pairs: BondPair[] = []
|
|
1063
|
+
let bond_pairs: BondPair[] = $derived.by(() => {
|
|
1064
|
+
if (dragging_atoms) return last_bond_pairs
|
|
999
1065
|
const want_bonds = applies_to_structure(show_bonds)
|
|
1000
1066
|
const want_polyhedra = applies_to_structure(effective_show_polyhedra)
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1067
|
+
last_bond_pairs =
|
|
1068
|
+
structure && (want_bonds || want_polyhedra)
|
|
1069
|
+
? compute_bonds(structure, bonding_strategy, bonding_options)
|
|
1070
|
+
: []
|
|
1071
|
+
return last_bond_pairs
|
|
1004
1072
|
})
|
|
1005
1073
|
|
|
1006
1074
|
// Compute property-based colors when not using element coloring
|
|
@@ -1053,21 +1121,22 @@
|
|
|
1053
1121
|
site.properties?.completion_image &&
|
|
1054
1122
|
!applies_to_structure(effective_show_bonds) &&
|
|
1055
1123
|
!applies_to_structure(effective_show_polyhedra)
|
|
1056
|
-
)
|
|
1124
|
+
)
|
|
1125
|
+
return []
|
|
1057
1126
|
|
|
1058
1127
|
// Calculate radius: same_size > site override > element override > default
|
|
1059
1128
|
// All radii scale uniformly with atom_radius for consistent slider behavior
|
|
1060
1129
|
const base_radius = same_size_atoms
|
|
1061
1130
|
? 1
|
|
1062
|
-
: site_radius_overrides?.get(site_idx) ?? calc_weighted_radius(site)
|
|
1131
|
+
: (site_radius_overrides?.get(site_idx) ?? calc_weighted_radius(site))
|
|
1063
1132
|
const radius = base_radius * effective_atom_radius
|
|
1064
1133
|
|
|
1065
1134
|
// Use property color if available (e.g. coordination number, Wyckoff position)
|
|
1066
1135
|
// Otherwise, each species gets its own element color (important for disordered sites)
|
|
1067
1136
|
const site_property_color = property_colors?.colors[orig_idx]
|
|
1068
1137
|
|
|
1069
|
-
const visible_species = site.species.filter(
|
|
1070
|
-
!hidden_elements.has(element)
|
|
1138
|
+
const visible_species = site.species.filter(
|
|
1139
|
+
({ element }) => !hidden_elements.has(element),
|
|
1071
1140
|
)
|
|
1072
1141
|
const slice_geometry = compute_slice_geometry(visible_species)
|
|
1073
1142
|
return slice_geometry.map((slice_data) => {
|
|
@@ -1095,13 +1164,12 @@
|
|
|
1095
1164
|
const is_site_visible = (site_idx: number): boolean => {
|
|
1096
1165
|
if (!structure?.sites) return false
|
|
1097
1166
|
const site = structure.sites[site_idx]
|
|
1098
|
-
const has_visible_element = site?.species.some(
|
|
1099
|
-
!hidden_elements.has(element)
|
|
1167
|
+
const has_visible_element = site?.species.some(
|
|
1168
|
+
({ element }) => !hidden_elements.has(element),
|
|
1100
1169
|
)
|
|
1101
1170
|
const orig_idx = get_orig_site_idx(site, site_idx)
|
|
1102
1171
|
const prop_val = property_colors?.values[orig_idx]
|
|
1103
|
-
const prop_visible = prop_val === undefined ||
|
|
1104
|
-
!hidden_prop_vals.has(prop_val)
|
|
1172
|
+
const prop_visible = prop_val === undefined || !hidden_prop_vals.has(prop_val)
|
|
1105
1173
|
return has_visible_element && prop_visible
|
|
1106
1174
|
}
|
|
1107
1175
|
|
|
@@ -1127,19 +1195,17 @@
|
|
|
1127
1195
|
})
|
|
1128
1196
|
|
|
1129
1197
|
let editable_perceived_bond_pairs = $derived(
|
|
1130
|
-
|
|
1198
|
+
interactive && bond_edits_enabled
|
|
1199
|
+
? perceived_bond_pairs.map((bond) => ({ ...bond, ...canonical_bond_target(bond) }))
|
|
1200
|
+
: [],
|
|
1131
1201
|
)
|
|
1132
1202
|
|
|
1133
1203
|
let filtered_bond_pairs = $derived.by(() => {
|
|
1134
1204
|
if (!structure?.sites) return perceived_bond_pairs
|
|
1135
1205
|
|
|
1136
1206
|
// Build set of removed bond keys for efficient lookup
|
|
1137
|
-
const removed_keys = new Set(
|
|
1138
|
-
|
|
1139
|
-
)
|
|
1140
|
-
const added_keys = new Set(
|
|
1141
|
-
added_bonds.map(bond_key_for),
|
|
1142
|
-
)
|
|
1207
|
+
const removed_keys = new Set(removed_bonds.map(bond_key_for))
|
|
1208
|
+
const added_keys = new Set(added_bonds.map(bond_key_for))
|
|
1143
1209
|
const order_overrides = new Map(
|
|
1144
1210
|
bond_order_overrides.map((bond) => [bond_key_for(bond), bond.order]),
|
|
1145
1211
|
)
|
|
@@ -1177,7 +1243,7 @@
|
|
|
1177
1243
|
})
|
|
1178
1244
|
|
|
1179
1245
|
let editable_bond_pairs = $derived(
|
|
1180
|
-
bond_edits_enabled ? bonds_to_render.filter(can_edit_bond) : [],
|
|
1246
|
+
interactive && bond_edits_enabled ? bonds_to_render.filter(can_edit_bond) : [],
|
|
1181
1247
|
)
|
|
1182
1248
|
|
|
1183
1249
|
// Coordination polyhedra around cation-like centers, derived from the same
|
|
@@ -1186,10 +1252,12 @@
|
|
|
1186
1252
|
// never recompute the hull geometry.
|
|
1187
1253
|
let polyhedra: Polyhedron[] = $derived.by(() => {
|
|
1188
1254
|
if (
|
|
1189
|
-
!structure?.sites ||
|
|
1255
|
+
!structure?.sites ||
|
|
1256
|
+
dragging_atoms ||
|
|
1190
1257
|
!applies_to_structure(effective_show_polyhedra) ||
|
|
1191
1258
|
filtered_bond_pairs.length === 0
|
|
1192
|
-
)
|
|
1259
|
+
)
|
|
1260
|
+
return []
|
|
1193
1261
|
return compute_polyhedra(structure, filtered_bond_pairs, {
|
|
1194
1262
|
min_neighbors: polyhedra_min_neighbors,
|
|
1195
1263
|
max_neighbors: polyhedra_max_neighbors,
|
|
@@ -1203,8 +1271,9 @@
|
|
|
1203
1271
|
const site = structure?.sites[site_idx]
|
|
1204
1272
|
const orig_idx = get_orig_site_idx(site, site_idx)
|
|
1205
1273
|
const element = get_majority_element(site)
|
|
1206
|
-
return
|
|
1207
|
-
(element && colors.element?.[element]) ?? `#808080`
|
|
1274
|
+
return (
|
|
1275
|
+
property_colors?.colors[orig_idx] ?? (element && colors.element?.[element]) ?? `#808080`
|
|
1276
|
+
)
|
|
1208
1277
|
}
|
|
1209
1278
|
|
|
1210
1279
|
// Separate derived so material-only changes (opacity, edge color) don't rebuild
|
|
@@ -1229,6 +1298,7 @@
|
|
|
1229
1298
|
// Publish which elements currently anchor polyhedra (consumed by controls so
|
|
1230
1299
|
// per-element toggles reflect the actual render state incl. spectator hiding)
|
|
1231
1300
|
$effect(() => {
|
|
1301
|
+
if (!interactive) return
|
|
1232
1302
|
const elems = [...new Set(polyhedra.map((poly) => poly.center_element))].sort()
|
|
1233
1303
|
if (elems.join(`,`) !== polyhedra_rendered_elements.join(`,`)) {
|
|
1234
1304
|
polyhedra_rendered_elements = elems
|
|
@@ -1257,23 +1327,23 @@
|
|
|
1257
1327
|
|
|
1258
1328
|
let polyhedra_edge_geometry: BufferGeometry | null = $state(null)
|
|
1259
1329
|
$effect(() => {
|
|
1260
|
-
const geo =
|
|
1261
|
-
|
|
1262
|
-
|
|
1330
|
+
const geo =
|
|
1331
|
+
polyhedra_show_edges && polyhedra_buffers && polyhedra_buffers.edge_count > 0
|
|
1332
|
+
? buffer_geometry({ position: polyhedra_buffers.edge_positions })
|
|
1333
|
+
: null
|
|
1263
1334
|
polyhedra_edge_geometry = geo
|
|
1264
1335
|
return () => geo?.dispose()
|
|
1265
1336
|
})
|
|
1266
1337
|
|
|
1267
1338
|
let smart_site_label_offsets = $derived.by(() => {
|
|
1268
|
-
|
|
1339
|
+
// Plain Maps: built and consumed inside deriveds, so per-entry reactivity
|
|
1340
|
+
// (SvelteMap) would only add signal overhead for potentially thousands of sites
|
|
1341
|
+
const offsets = new Map<number, Vec3>()
|
|
1269
1342
|
if (bonds_to_render.length === 0) return offsets
|
|
1270
1343
|
|
|
1271
|
-
const bond_directions_by_site = new
|
|
1344
|
+
const bond_directions_by_site = new Map<number, Vec3[]>()
|
|
1272
1345
|
const add_bond_direction = (site_idx: number, pos_1: Vec3, pos_2: Vec3) => {
|
|
1273
|
-
const direction = math.normalize_vec(
|
|
1274
|
-
math.subtract(pos_2, pos_1),
|
|
1275
|
-
[0, 0, 0],
|
|
1276
|
-
)
|
|
1346
|
+
const direction = math.normalize_vec(math.subtract(pos_2, pos_1), [0, 0, 0])
|
|
1277
1347
|
if (Math.hypot(...direction) < LABEL_OFFSET_EPS) return
|
|
1278
1348
|
bond_directions_by_site.set(site_idx, [
|
|
1279
1349
|
...(bond_directions_by_site.get(site_idx) ?? []),
|
|
@@ -1312,7 +1382,7 @@
|
|
|
1312
1382
|
const get_majority_color = (site: typeof site_a) => {
|
|
1313
1383
|
if (!site?.species || site.species.length === 0) return bond_color
|
|
1314
1384
|
const majority_species = site.species.reduce((max, spec) =>
|
|
1315
|
-
spec.occu > max.occu ? spec : max
|
|
1385
|
+
spec.occu > max.occu ? spec : max,
|
|
1316
1386
|
)
|
|
1317
1387
|
return colors.element?.[majority_species.element] || bond_color
|
|
1318
1388
|
}
|
|
@@ -1328,7 +1398,7 @@
|
|
|
1328
1398
|
})
|
|
1329
1399
|
|
|
1330
1400
|
let radius_by_site_idx = $derived.by(() => {
|
|
1331
|
-
const map = new
|
|
1401
|
+
const map = new Map<number, number>()
|
|
1332
1402
|
for (const atom of atom_data) {
|
|
1333
1403
|
if (!map.has(atom.site_idx)) map.set(atom.site_idx, atom.radius)
|
|
1334
1404
|
}
|
|
@@ -1340,7 +1410,8 @@
|
|
|
1340
1410
|
// angles. Give each such site one invisible full-sphere hit target so it's as
|
|
1341
1411
|
// reliably hoverable as an ordered atom (single solid sphere). One per site.
|
|
1342
1412
|
let partial_hit_targets = $derived.by(() => {
|
|
1343
|
-
|
|
1413
|
+
if (!interactive) return []
|
|
1414
|
+
const targets = new Map<number, EditableAtomHitTarget & { is_image_atom: boolean }>()
|
|
1344
1415
|
for (const atom of atom_data) {
|
|
1345
1416
|
if (!atom.has_partial_occupancy || targets.has(atom.site_idx)) continue
|
|
1346
1417
|
targets.set(atom.site_idx, {
|
|
@@ -1355,6 +1426,7 @@
|
|
|
1355
1426
|
|
|
1356
1427
|
let editable_atom_hit_targets = $derived.by(() => {
|
|
1357
1428
|
if (
|
|
1429
|
+
!interactive ||
|
|
1358
1430
|
measure_mode !== `edit-bonds` ||
|
|
1359
1431
|
bond_edit_mode !== `add` ||
|
|
1360
1432
|
!bond_edits_enabled
|
|
@@ -1362,7 +1434,7 @@
|
|
|
1362
1434
|
return []
|
|
1363
1435
|
}
|
|
1364
1436
|
|
|
1365
|
-
const targets = new
|
|
1437
|
+
const targets = new Map<number, EditableAtomHitTarget>()
|
|
1366
1438
|
for (const atom of atom_data) {
|
|
1367
1439
|
if (!can_select_bond_site(atom.site_idx)) continue
|
|
1368
1440
|
if (targets.has(atom.site_idx)) continue
|
|
@@ -1378,13 +1450,47 @@
|
|
|
1378
1450
|
// Get radius for a site (for highlight fallback when site is hidden/filtered)
|
|
1379
1451
|
// Checks site_radius_overrides first for consistency with visible atoms
|
|
1380
1452
|
const get_site_radius = (site: Site, site_idx: number | null): number => {
|
|
1381
|
-
const override = site_idx !== null
|
|
1382
|
-
|
|
1383
|
-
: undefined
|
|
1384
|
-
const base_radius = same_size_atoms ? 1 : override ?? calc_weighted_radius(site)
|
|
1453
|
+
const override = site_idx !== null ? site_radius_overrides?.get(site_idx) : undefined
|
|
1454
|
+
const base_radius = same_size_atoms ? 1 : (override ?? calc_weighted_radius(site))
|
|
1385
1455
|
return base_radius * effective_atom_radius
|
|
1386
1456
|
}
|
|
1387
1457
|
|
|
1458
|
+
// Sites to outline with a translucent sphere: hovered + all selected/active sites. Kept
|
|
1459
|
+
// independent of the pulse animation so this list (with its per-site radius lookups) only
|
|
1460
|
+
// rebuilds when highlighted sites change; pulsing opacity is applied per-frame in the
|
|
1461
|
+
// template instead, avoiding a rebuild every frame in every multi-view pane.
|
|
1462
|
+
type HighlightTarget = {
|
|
1463
|
+
kind: `hover` | `selected` | `active`
|
|
1464
|
+
site: Site
|
|
1465
|
+
site_idx: number | null
|
|
1466
|
+
color: string
|
|
1467
|
+
radius: number
|
|
1468
|
+
}
|
|
1469
|
+
let highlight_targets: HighlightTarget[] = $derived.by(() => {
|
|
1470
|
+
const targets: HighlightTarget[] = []
|
|
1471
|
+
const add = (
|
|
1472
|
+
kind: HighlightTarget[`kind`],
|
|
1473
|
+
site: Site | null,
|
|
1474
|
+
site_idx: number | null,
|
|
1475
|
+
color: string,
|
|
1476
|
+
) => {
|
|
1477
|
+
if (!site) return
|
|
1478
|
+
const radius =
|
|
1479
|
+
site_idx !== null
|
|
1480
|
+
? (radius_by_site_idx.get(site_idx) ?? get_site_radius(site, site_idx))
|
|
1481
|
+
: get_site_radius(site, site_idx)
|
|
1482
|
+
targets.push({ kind, site, site_idx, color, radius })
|
|
1483
|
+
}
|
|
1484
|
+
add(`hover`, hovered_site, hovered_idx, dark_mode ? `white` : `#333`)
|
|
1485
|
+
for (const idx of selected_sites ?? []) {
|
|
1486
|
+
add(`selected`, structure?.sites?.[idx] ?? null, idx, selection_highlight_color)
|
|
1487
|
+
}
|
|
1488
|
+
for (const idx of active_sites ?? []) {
|
|
1489
|
+
add(`active`, structure?.sites?.[idx] ?? null, idx, active_highlight_color)
|
|
1490
|
+
}
|
|
1491
|
+
return targets
|
|
1492
|
+
})
|
|
1493
|
+
|
|
1388
1494
|
// Interpolate between spin-down (#3498db blue) and spin-up (#e74c3c red)
|
|
1389
1495
|
// based on the z-component direction of a magnetic vector
|
|
1390
1496
|
function spin_direction_color(vec: Vec3): string {
|
|
@@ -1393,9 +1499,9 @@
|
|
|
1393
1499
|
const red = Math.round(52 + (231 - 52) * z_frac)
|
|
1394
1500
|
const grn = Math.round(152 + (76 - 152) * z_frac)
|
|
1395
1501
|
const blu = Math.round(219 + (60 - 219) * z_frac)
|
|
1396
|
-
return `#${red.toString(16).padStart(2, `0`)}${
|
|
1397
|
-
|
|
1398
|
-
|
|
1502
|
+
return `#${red.toString(16).padStart(2, `0`)}${grn
|
|
1503
|
+
.toString(16)
|
|
1504
|
+
.padStart(2, `0`)}${blu.toString(16).padStart(2, `0`)}`
|
|
1399
1505
|
}
|
|
1400
1506
|
|
|
1401
1507
|
// Build one arrow layer per visible vector key. Auto-scales the longest
|
|
@@ -1415,7 +1521,7 @@
|
|
|
1415
1521
|
let max_mag = 0
|
|
1416
1522
|
const site_vec_maps = structure.sites.map((site, site_idx) => {
|
|
1417
1523
|
if (!is_site_visible(site_idx)) return null
|
|
1418
|
-
const map = new
|
|
1524
|
+
const map = new Map<string, Vec3>()
|
|
1419
1525
|
for (const { key, vec } of get_all_site_vectors(site)) {
|
|
1420
1526
|
map.set(key, vec)
|
|
1421
1527
|
if (active_set.has(key)) {
|
|
@@ -1427,9 +1533,7 @@
|
|
|
1427
1533
|
|
|
1428
1534
|
// When normalize is on, treat all magnitudes as 1 so arrows have equal length
|
|
1429
1535
|
const effective_max = vector_normalize ? 1 : max_mag
|
|
1430
|
-
const auto_scale = effective_max > 1e-10
|
|
1431
|
-
? (char_atom_spacing * 1.8) / effective_max
|
|
1432
|
-
: 1
|
|
1536
|
+
const auto_scale = effective_max > 1e-10 ? (char_atom_spacing * 1.8) / effective_max : 1
|
|
1433
1537
|
const is_single = active_keys.length === 1
|
|
1434
1538
|
const effective_global_scale = auto_scale * vector_scale
|
|
1435
1539
|
|
|
@@ -1439,40 +1543,40 @@
|
|
|
1439
1543
|
// of the visual atom radius (0 = center, 0.5 = halfway to surface).
|
|
1440
1544
|
// get_site_radius() returns the uniform scale applied to SphereGeometry(0.5),
|
|
1441
1545
|
// so visual_radius = get_site_radius() * 0.5.
|
|
1442
|
-
const site_offsets =
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1546
|
+
const site_offsets =
|
|
1547
|
+
vector_origin_gap > 0 && !is_single
|
|
1548
|
+
? structure.sites.map((site, site_idx) => {
|
|
1549
|
+
const vec_map = site_vec_maps[site_idx]
|
|
1550
|
+
if (!vec_map) return null
|
|
1551
|
+
const site_keys = active_keys.filter((key) => vec_map.has(key))
|
|
1552
|
+
const n_keys = site_keys.length
|
|
1553
|
+
if (n_keys <= 1) return null
|
|
1554
|
+
const visual_radius = get_site_radius(site, site_idx) * 0.5
|
|
1555
|
+
const gap_abs = vector_origin_gap * visual_radius
|
|
1556
|
+
let mean: Vec3 = [0, 0, 0]
|
|
1557
|
+
for (const key of site_keys) {
|
|
1558
|
+
const vec = vec_map.get(key)
|
|
1559
|
+
if (vec) mean = math.add(mean, math.normalize_vec(vec)) as Vec3
|
|
1560
|
+
}
|
|
1561
|
+
const mean_dir = math.normalize_vec(mean, [0, 1, 0] as Vec3)
|
|
1562
|
+
const [u_vec, v_vec] = math.compute_in_plane_basis(mean_dir)
|
|
1563
|
+
const offsets = new Map<string, Vec3>()
|
|
1564
|
+
for (const [idx, key] of site_keys.entries()) {
|
|
1565
|
+
const angle = (2 * Math.PI * idx) / n_keys
|
|
1566
|
+
const dx = math.scale(u_vec, gap_abs * Math.cos(angle))
|
|
1567
|
+
const dy = math.scale(v_vec, gap_abs * Math.sin(angle))
|
|
1568
|
+
offsets.set(key, math.add(dx, dy))
|
|
1569
|
+
}
|
|
1570
|
+
return offsets
|
|
1571
|
+
})
|
|
1572
|
+
: null
|
|
1468
1573
|
|
|
1469
1574
|
const mag_interpolator = get_d3_interpolator(vector_color_scale)
|
|
1470
1575
|
|
|
1471
1576
|
return active_keys.map((key, layer_idx) => {
|
|
1472
1577
|
const layer_cfg = vector_configs[key]
|
|
1473
1578
|
const layer_scale = effective_global_scale * (layer_cfg?.scale ?? 1.0)
|
|
1474
|
-
const layer_color = layer_cfg?.color ??
|
|
1475
|
-
VECTOR_PALETTE[layer_idx % VECTOR_PALETTE.length]
|
|
1579
|
+
const layer_color = layer_cfg?.color ?? VECTOR_PALETTE[layer_idx % VECTOR_PALETTE.length]
|
|
1476
1580
|
|
|
1477
1581
|
const arrows = structure.sites
|
|
1478
1582
|
.map((site, site_idx) => {
|
|
@@ -1488,11 +1592,12 @@
|
|
|
1488
1592
|
arrow_color = layer_cfg.color
|
|
1489
1593
|
} else if (!is_single) arrow_color = layer_color
|
|
1490
1594
|
else {
|
|
1491
|
-
const effective_mode =
|
|
1492
|
-
|
|
1493
|
-
? `
|
|
1494
|
-
|
|
1495
|
-
|
|
1595
|
+
const effective_mode =
|
|
1596
|
+
vector_color_mode === `auto`
|
|
1597
|
+
? key.startsWith(`magmom`) || key.startsWith(`spin`)
|
|
1598
|
+
? `spin_direction`
|
|
1599
|
+
: `element`
|
|
1600
|
+
: vector_color_mode
|
|
1496
1601
|
if (effective_mode === `magnitude`) {
|
|
1497
1602
|
const mag = Math.hypot(...vec)
|
|
1498
1603
|
const norm = max_mag > 1e-10 ? mag / max_mag : 0
|
|
@@ -1502,14 +1607,13 @@
|
|
|
1502
1607
|
} else if (effective_mode === `uniform`) {
|
|
1503
1608
|
arrow_color = vector_color
|
|
1504
1609
|
} else {
|
|
1505
|
-
const majority_element =
|
|
1506
|
-
|
|
1507
|
-
spec.occu > max.occu ? spec : max
|
|
1508
|
-
|
|
1509
|
-
|
|
1610
|
+
const majority_element =
|
|
1611
|
+
site.species.length > 0
|
|
1612
|
+
? site.species.reduce((max, spec) => (spec.occu > max.occu ? spec : max))
|
|
1613
|
+
.element
|
|
1614
|
+
: undefined
|
|
1510
1615
|
arrow_color =
|
|
1511
|
-
(majority_element && colors.element?.[majority_element]) ||
|
|
1512
|
-
vector_color
|
|
1616
|
+
(majority_element && colors.element?.[majority_element]) || vector_color
|
|
1513
1617
|
}
|
|
1514
1618
|
}
|
|
1515
1619
|
|
|
@@ -1531,61 +1635,68 @@
|
|
|
1531
1635
|
})
|
|
1532
1636
|
})
|
|
1533
1637
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
(groups[key] = { element, radius, color, is_image_atom, atoms: [] })
|
|
1547
|
-
bucket.atoms.push(atom)
|
|
1548
|
-
return groups
|
|
1549
|
-
},
|
|
1550
|
-
{} as Record<string, InstancedAtomGroup>,
|
|
1551
|
-
),
|
|
1552
|
-
),
|
|
1553
|
-
)
|
|
1638
|
+
// Full-occupancy atoms split into base and PBC-image sets. Each set renders
|
|
1639
|
+
// as ONE InstancedMesh (per-atom color/radius live in instance buffers), so
|
|
1640
|
+
// no per-element grouping is needed. Image atoms get their own mesh because
|
|
1641
|
+
// they ghost (desaturate + translucent) and lose interactivity in edit-atoms mode.
|
|
1642
|
+
let instanced_atom_sets = $derived.by(() => {
|
|
1643
|
+
const base: typeof atom_data = []
|
|
1644
|
+
const image: typeof atom_data = []
|
|
1645
|
+
for (const atom of atom_data) {
|
|
1646
|
+
if (!atom.has_partial_occupancy) (atom.is_image_atom ? image : base).push(atom)
|
|
1647
|
+
}
|
|
1648
|
+
return { base, image }
|
|
1649
|
+
})
|
|
1554
1650
|
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1651
|
+
// One label anchor per visible site (sites can contribute several atom_data
|
|
1652
|
+
// entries, e.g. partial-occupancy wedges — label each site once)
|
|
1653
|
+
let label_entries = $derived.by(() => {
|
|
1654
|
+
if (!show_site_labels && !show_site_indices) return []
|
|
1655
|
+
const seen = new Set<number>()
|
|
1656
|
+
const entries: { site_idx: number; position: Vec3; radius: number }[] = []
|
|
1657
|
+
for (const atom of atom_data) {
|
|
1658
|
+
if (seen.has(atom.site_idx)) continue
|
|
1659
|
+
seen.add(atom.site_idx)
|
|
1660
|
+
entries.push({
|
|
1661
|
+
site_idx: atom.site_idx,
|
|
1662
|
+
position: atom.position,
|
|
1663
|
+
radius: atom.radius,
|
|
1664
|
+
})
|
|
1665
|
+
}
|
|
1666
|
+
return entries
|
|
1667
|
+
})
|
|
1668
|
+
|
|
1669
|
+
// Partial-occupancy atoms render as separate wedge meshes (see template below).
|
|
1670
|
+
// Derived so the filter isn't re-run inline on every render of the atoms group.
|
|
1671
|
+
let partial_occupancy_atoms = $derived(
|
|
1672
|
+
atom_data.filter((atom) => atom.has_partial_occupancy),
|
|
1564
1673
|
)
|
|
1565
1674
|
|
|
1566
|
-
let orbit_controls_props = $derived(
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1675
|
+
let orbit_controls_props = $derived(
|
|
1676
|
+
build_orbit_props({
|
|
1677
|
+
camera_projection,
|
|
1678
|
+
target: camera_target ?? rotation_target,
|
|
1679
|
+
rotate_speed,
|
|
1680
|
+
zoom_speed,
|
|
1681
|
+
zoom_to_cursor,
|
|
1682
|
+
pan_speed,
|
|
1683
|
+
max_zoom,
|
|
1684
|
+
min_zoom,
|
|
1685
|
+
auto_rotate,
|
|
1686
|
+
rotation_damping,
|
|
1687
|
+
set_camera_is_moving: (moving) => (camera_is_moving = moving),
|
|
1688
|
+
// Close hover tooltips + bond context menu while the camera moves. Only hide the
|
|
1689
|
+
// VISIBLE menu (not bond_context_target): clicking a menu button fires this
|
|
1690
|
+
// orbit-controls start handler before the button's own handler runs, which still
|
|
1691
|
+
// needs the target bond to apply the edit (see bond_context_target comment).
|
|
1692
|
+
onstart_extra: () => {
|
|
1693
|
+
cancel_atom_hover_clear()
|
|
1694
|
+
hovered_idx = null
|
|
1695
|
+
hovered_bond_key = null
|
|
1696
|
+
bond_context_menu = null
|
|
1697
|
+
},
|
|
1698
|
+
}),
|
|
1699
|
+
)
|
|
1589
1700
|
|
|
1590
1701
|
let measure_line_color = $derived.by(() => {
|
|
1591
1702
|
if (typeof window === `undefined`) return
|
|
@@ -1595,27 +1706,9 @@
|
|
|
1595
1706
|
})
|
|
1596
1707
|
</script>
|
|
1597
1708
|
|
|
1598
|
-
{#snippet
|
|
1599
|
-
group: ComponentProps<typeof Bond>[`group`],
|
|
1600
|
-
)}
|
|
1601
|
-
{#key group.instances.length}
|
|
1602
|
-
<Bond {group} />
|
|
1603
|
-
{/key}
|
|
1604
|
-
{/snippet}
|
|
1605
|
-
|
|
1606
|
-
{#snippet site_label_snippet(position: Vec3, site_idx: number)}
|
|
1709
|
+
{#snippet site_label_snippet(site_idx: number)}
|
|
1607
1710
|
{@const site = structure!.sites[site_idx]}
|
|
1608
|
-
{
|
|
1609
|
-
<extras.HTML
|
|
1610
|
-
center
|
|
1611
|
-
position={position}
|
|
1612
|
-
calculatePosition={make_label_position_calculator(
|
|
1613
|
-
position,
|
|
1614
|
-
() => smart_site_label_offsets.get(site_idx) ?? site_label_offset,
|
|
1615
|
-
visual_radius,
|
|
1616
|
-
label_screen_margin,
|
|
1617
|
-
)}
|
|
1618
|
-
>
|
|
1711
|
+
{#if site}
|
|
1619
1712
|
{#if atom_label}
|
|
1620
1713
|
{@render atom_label({ site, site_idx })}
|
|
1621
1714
|
{:else}
|
|
@@ -1646,10 +1739,7 @@
|
|
|
1646
1739
|
{#if site.species.length === 1}
|
|
1647
1740
|
{site.species[0].element}{#if show_site_indices}-{site_idx + 1}{/if}
|
|
1648
1741
|
{:else}
|
|
1649
|
-
{#each site.species as
|
|
1650
|
-
{ element, occu, oxidation_state }
|
|
1651
|
-
(`${element}-${occu}-${oxidation_state}`)
|
|
1652
|
-
}
|
|
1742
|
+
{#each site.species as { element, occu, oxidation_state } (`${element}-${occu}-${oxidation_state}`)}
|
|
1653
1743
|
{element}<sub>{format_num(occu, `.3~`).replace(`0.`, `.`)}</sub>
|
|
1654
1744
|
{/each}
|
|
1655
1745
|
{#if show_site_indices}-{site_idx + 1}{/if}
|
|
@@ -1659,7 +1749,7 @@
|
|
|
1659
1749
|
{/if}
|
|
1660
1750
|
</button>
|
|
1661
1751
|
{/if}
|
|
1662
|
-
|
|
1752
|
+
{/if}
|
|
1663
1753
|
{/snippet}
|
|
1664
1754
|
|
|
1665
1755
|
<SceneCamera
|
|
@@ -1680,56 +1770,39 @@
|
|
|
1680
1770
|
<!-- Apply manual rotation around center: translate to origin, rotate, translate back -->
|
|
1681
1771
|
<T.Group position={rotation_target}>
|
|
1682
1772
|
<T.Group {rotation}>
|
|
1683
|
-
<T.Group position={
|
|
1773
|
+
<T.Group position={neg_rotation_target}>
|
|
1684
1774
|
{#if show_atoms}
|
|
1685
|
-
<!-- Instanced rendering for full
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
<
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
{
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
scale={atom.radius}
|
|
1705
|
-
{...atom_pointer_props(atom.site_idx, edit_mode_image)}
|
|
1706
|
-
/>
|
|
1707
|
-
{/each}
|
|
1708
|
-
</extras.InstancedMesh>
|
|
1709
|
-
{/each}
|
|
1775
|
+
<!-- Instanced rendering for full-occupancy atoms: one InstancedMesh for
|
|
1776
|
+
base atoms and one for PBC image atoms (which ghost + lose interaction
|
|
1777
|
+
in edit-atoms mode). Pointer events are resolved via raycast instanceId. -->
|
|
1778
|
+
{#if instanced_atom_sets.base.length > 0}
|
|
1779
|
+
<InstancedAtoms
|
|
1780
|
+
atoms={instanced_atom_sets.base}
|
|
1781
|
+
{sphere_segments}
|
|
1782
|
+
{...atom_instance_events(instanced_atom_sets.base, false)}
|
|
1783
|
+
/>
|
|
1784
|
+
{/if}
|
|
1785
|
+
{#if instanced_atom_sets.image.length > 0}
|
|
1786
|
+
{@const edit_mode_image = measure_mode === `edit-atoms`}
|
|
1787
|
+
<InstancedAtoms
|
|
1788
|
+
atoms={instanced_atom_sets.image}
|
|
1789
|
+
{sphere_segments}
|
|
1790
|
+
ghost={edit_mode_image}
|
|
1791
|
+
{...atom_instance_events(instanced_atom_sets.image, edit_mode_image)}
|
|
1792
|
+
/>
|
|
1793
|
+
{/if}
|
|
1710
1794
|
|
|
1711
1795
|
<!-- Regular rendering for partial occupancy atoms -->
|
|
1712
|
-
{#each
|
|
1713
|
-
atom
|
|
1714
|
-
(atom.site_idx + atom.element + atom.occupancy)
|
|
1715
|
-
}
|
|
1796
|
+
{#each partial_occupancy_atoms as atom (atom.site_idx + atom.element + atom.occupancy)}
|
|
1716
1797
|
{@const partial_edit_image = measure_mode === `edit-atoms` && atom.is_image_atom}
|
|
1717
1798
|
{@const ghost_opacity = partial_edit_image ? 0.5 : 1}
|
|
1718
1799
|
<!-- Visual only: pointer interaction handled by the invisible full-sphere
|
|
1719
1800
|
hit targets below (wedge meshes leave gaps at the poles). -->
|
|
1720
1801
|
<T.Group position={atom.position} scale={atom.radius}>
|
|
1721
|
-
{@const partial_color = partial_edit_image
|
|
1722
|
-
? desaturate(atom.color)
|
|
1723
|
-
: atom.color}
|
|
1802
|
+
{@const partial_color = partial_edit_image ? desaturate(atom.color) : atom.color}
|
|
1724
1803
|
<T.Mesh>
|
|
1725
1804
|
<T.SphereGeometry
|
|
1726
|
-
args={[
|
|
1727
|
-
0.5,
|
|
1728
|
-
sphere_segments,
|
|
1729
|
-
sphere_segments,
|
|
1730
|
-
atom.start_phi,
|
|
1731
|
-
atom.phi_length,
|
|
1732
|
-
]}
|
|
1805
|
+
args={[0.5, sphere_segments, sphere_segments, atom.start_phi, atom.phi_length]}
|
|
1733
1806
|
/>
|
|
1734
1807
|
<T.MeshStandardMaterial
|
|
1735
1808
|
color={partial_color}
|
|
@@ -1738,49 +1811,28 @@
|
|
|
1738
1811
|
/>
|
|
1739
1812
|
</T.Mesh>
|
|
1740
1813
|
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
args={[
|
|
1763
|
-
0.5,
|
|
1764
|
-
sphere_segments,
|
|
1765
|
-
PARTIAL_OCCUPANCY_CAP_ARC.end_cap_arc_start,
|
|
1766
|
-
PARTIAL_OCCUPANCY_CAP_ARC.arc_length,
|
|
1767
|
-
]}
|
|
1768
|
-
/>
|
|
1769
|
-
<T.MeshStandardMaterial
|
|
1770
|
-
color={partial_color}
|
|
1771
|
-
side={2}
|
|
1772
|
-
opacity={ghost_opacity}
|
|
1773
|
-
transparent={partial_edit_image}
|
|
1774
|
-
/>
|
|
1775
|
-
</T.Mesh>
|
|
1776
|
-
{/if}
|
|
1814
|
+
<!-- Flat caps closing the wedge at its start/end azimuthal angles -->
|
|
1815
|
+
{#each [[atom.render_start_cap, atom.start_phi, PARTIAL_OCCUPANCY_CAP_ARC.start_cap_arc_start], [atom.render_end_cap, atom.end_phi, PARTIAL_OCCUPANCY_CAP_ARC.end_cap_arc_start]] as const as [render_cap, phi, arc_start], cap_idx (cap_idx)}
|
|
1816
|
+
{#if render_cap}
|
|
1817
|
+
<T.Mesh rotation={[0, phi, 0]}>
|
|
1818
|
+
<T.CircleGeometry
|
|
1819
|
+
args={[
|
|
1820
|
+
0.5,
|
|
1821
|
+
sphere_segments,
|
|
1822
|
+
arc_start,
|
|
1823
|
+
PARTIAL_OCCUPANCY_CAP_ARC.arc_length,
|
|
1824
|
+
]}
|
|
1825
|
+
/>
|
|
1826
|
+
<T.MeshStandardMaterial
|
|
1827
|
+
color={partial_color}
|
|
1828
|
+
side={2}
|
|
1829
|
+
opacity={ghost_opacity}
|
|
1830
|
+
transparent={partial_edit_image}
|
|
1831
|
+
/>
|
|
1832
|
+
</T.Mesh>
|
|
1833
|
+
{/if}
|
|
1834
|
+
{/each}
|
|
1777
1835
|
</T.Group>
|
|
1778
|
-
|
|
1779
|
-
<!-- Render label only for the first species of this site to avoid duplicates -->
|
|
1780
|
-
{#if (show_site_labels || show_site_indices) &&
|
|
1781
|
-
atom.element === structure!.sites[atom.site_idx].species[0].element}
|
|
1782
|
-
{@render site_label_snippet(atom.position, atom.site_idx)}
|
|
1783
|
-
{/if}
|
|
1784
1836
|
{/each}
|
|
1785
1837
|
|
|
1786
1838
|
<!-- Invisible full-sphere hit targets for partial-occupancy sites so the
|
|
@@ -1797,31 +1849,39 @@
|
|
|
1797
1849
|
</T.Mesh>
|
|
1798
1850
|
{/each}
|
|
1799
1851
|
|
|
1800
|
-
<!-- Site labels/indices for
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1852
|
+
<!-- Site labels/indices: single overlay for all labels (one DOM container
|
|
1853
|
+
+ one per-frame position pass instead of one threlte <HTML> per label) -->
|
|
1854
|
+
{#if label_entries.length > 0}
|
|
1855
|
+
<SiteLabels
|
|
1856
|
+
entries={label_entries}
|
|
1857
|
+
get_offset={(site_idx) =>
|
|
1858
|
+
smart_site_label_offsets.get(site_idx) ?? site_label_offset}
|
|
1859
|
+
screen_margin={label_screen_margin}
|
|
1860
|
+
>
|
|
1861
|
+
{#snippet label({ site_idx })}
|
|
1862
|
+
{@render site_label_snippet(site_idx)}
|
|
1863
|
+
{/snippet}
|
|
1864
|
+
</SiteLabels>
|
|
1805
1865
|
{/if}
|
|
1806
1866
|
{/if}
|
|
1807
1867
|
|
|
1868
|
+
<!-- Per-site vector arrows (forces, magmoms, ...) as instanced meshes:
|
|
1869
|
+
2 draw calls per layer instead of 2 meshes per site -->
|
|
1808
1870
|
{#each vector_layers as layer (layer.key)}
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
/>
|
|
1816
|
-
{/each}
|
|
1871
|
+
<ArrowInstances
|
|
1872
|
+
arrows={layer.arrows}
|
|
1873
|
+
shaft_radius={eff_shaft_radius}
|
|
1874
|
+
arrow_head_radius={eff_head_radius}
|
|
1875
|
+
arrow_head_length={eff_head_length}
|
|
1876
|
+
/>
|
|
1817
1877
|
{/each}
|
|
1818
1878
|
|
|
1819
1879
|
<!-- Instanced bond rendering with gradient colors -->
|
|
1820
|
-
{#
|
|
1821
|
-
{#
|
|
1822
|
-
{
|
|
1823
|
-
{/
|
|
1824
|
-
{/
|
|
1880
|
+
{#each instanced_bond_groups as group (group.thickness + group.instances.length)}
|
|
1881
|
+
{#key group.instances.length}
|
|
1882
|
+
<Bond {group} />
|
|
1883
|
+
{/key}
|
|
1884
|
+
{/each}
|
|
1825
1885
|
|
|
1826
1886
|
<!-- Coordination polyhedra: all faces in one merged mesh, edges in one
|
|
1827
1887
|
LineSegments (1-2 draw calls regardless of supercell size) -->
|
|
@@ -1850,16 +1910,12 @@
|
|
|
1850
1910
|
{/if}
|
|
1851
1911
|
|
|
1852
1912
|
<!-- Clickable bond hit-test cylinders in edit-bonds mode -->
|
|
1853
|
-
{#if measure_mode === `edit-bonds` && editable_bond_pairs.length > 0}
|
|
1854
|
-
{#each editable_bond_pairs as
|
|
1855
|
-
bond
|
|
1856
|
-
(`bond-hit-${bond_edit_mode}-${rendered_bond_key_for(bond)}`)
|
|
1857
|
-
}
|
|
1913
|
+
{#if interactive && measure_mode === `edit-bonds` && editable_bond_pairs.length > 0}
|
|
1914
|
+
{#each editable_bond_pairs as bond (`bond-hit-${bond_edit_mode}-${rendered_bond_key_for(bond)}`)}
|
|
1858
1915
|
{@const bond_key = rendered_bond_key_for(bond)}
|
|
1859
1916
|
{@const is_hovered = hovered_bond_key === bond_key}
|
|
1860
1917
|
{@const is_delete_mode = bond_edit_mode === `delete`}
|
|
1861
|
-
{@const bond_hit_radius =
|
|
1862
|
-
bond_thickness * (is_delete_mode ? 5 : 1.25)}
|
|
1918
|
+
{@const bond_hit_radius = bond_thickness * (is_delete_mode ? 5 : 1.25)}
|
|
1863
1919
|
{@const bond_hover_radius = bond_thickness * 1.1}
|
|
1864
1920
|
<T.Mesh
|
|
1865
1921
|
matrixAutoUpdate={false}
|
|
@@ -1888,24 +1944,16 @@
|
|
|
1888
1944
|
onpointermove={() => (hovered_bond_key = bond_key)}
|
|
1889
1945
|
onpointerleave={() => (hovered_bond_key = null)}
|
|
1890
1946
|
>
|
|
1891
|
-
<T.CylinderGeometry
|
|
1892
|
-
|
|
1893
|
-
bond_hit_radius,
|
|
1894
|
-
bond_hit_radius,
|
|
1895
|
-
1,
|
|
1896
|
-
6,
|
|
1897
|
-
]}
|
|
1898
|
-
/>
|
|
1899
|
-
<T.MeshBasicMaterial
|
|
1900
|
-
transparent
|
|
1901
|
-
opacity={0}
|
|
1902
|
-
depthWrite={false}
|
|
1903
|
-
/>
|
|
1947
|
+
<T.CylinderGeometry args={[bond_hit_radius, bond_hit_radius, 1, 6]} />
|
|
1948
|
+
<T.MeshBasicMaterial transparent opacity={0} depthWrite={false} />
|
|
1904
1949
|
</T.Mesh>
|
|
1905
1950
|
{#if is_hovered}
|
|
1906
1951
|
<T.Mesh
|
|
1907
1952
|
matrixAutoUpdate={false}
|
|
1908
|
-
oncreate={(ref) =>
|
|
1953
|
+
oncreate={(ref) => {
|
|
1954
|
+
apply_bond_transform(ref, bond)
|
|
1955
|
+
disable_raycast(ref)
|
|
1956
|
+
}}
|
|
1909
1957
|
>
|
|
1910
1958
|
<T.CylinderGeometry args={[bond_hover_radius, bond_hover_radius, 1, 6]} />
|
|
1911
1959
|
<T.MeshBasicMaterial
|
|
@@ -1919,7 +1967,7 @@
|
|
|
1919
1967
|
{/each}
|
|
1920
1968
|
{/if}
|
|
1921
1969
|
|
|
1922
|
-
{#if editable_atom_hit_targets.length > 0}
|
|
1970
|
+
{#if interactive && editable_atom_hit_targets.length > 0}
|
|
1923
1971
|
{#each editable_atom_hit_targets as atom_hit (atom_hit.site_idx)}
|
|
1924
1972
|
<T.Mesh
|
|
1925
1973
|
position={atom_hit.position}
|
|
@@ -1930,16 +1978,12 @@
|
|
|
1930
1978
|
}}
|
|
1931
1979
|
>
|
|
1932
1980
|
<T.SphereGeometry args={[0.5, 12, 12]} />
|
|
1933
|
-
<T.MeshBasicMaterial
|
|
1934
|
-
transparent
|
|
1935
|
-
opacity={0}
|
|
1936
|
-
depthWrite={false}
|
|
1937
|
-
/>
|
|
1981
|
+
<T.MeshBasicMaterial transparent opacity={0} depthWrite={false} />
|
|
1938
1982
|
</T.Mesh>
|
|
1939
1983
|
{/each}
|
|
1940
1984
|
{/if}
|
|
1941
1985
|
|
|
1942
|
-
{#if measure_mode === `edit-bonds` && bond_context_menu}
|
|
1986
|
+
{#if interactive && measure_mode === `edit-bonds` && bond_context_menu}
|
|
1943
1987
|
{@const current_order = get_current_bond_order(
|
|
1944
1988
|
bond_context_menu.site_idx_1,
|
|
1945
1989
|
bond_context_menu.site_idx_2,
|
|
@@ -1951,114 +1995,46 @@
|
|
|
1951
1995
|
{#each BOND_ORDER_OPTIONS as { order, label } (label)}
|
|
1952
1996
|
<button
|
|
1953
1997
|
type="button"
|
|
1954
|
-
|
|
1955
|
-
event.preventDefault()
|
|
1956
|
-
event.stopPropagation()
|
|
1957
|
-
set_context_bond_order(order)
|
|
1958
|
-
}}
|
|
1959
|
-
onkeydown={(event: KeyboardEvent) => {
|
|
1960
|
-
if (event.key !== `Enter` && event.key !== ` `) return
|
|
1961
|
-
event.preventDefault()
|
|
1962
|
-
event.stopPropagation()
|
|
1963
|
-
set_context_bond_order(order)
|
|
1964
|
-
}}
|
|
1998
|
+
{...menu_action_props(() => set_context_bond_order(order))}
|
|
1965
1999
|
>
|
|
1966
2000
|
{label}
|
|
1967
2001
|
</button>
|
|
1968
2002
|
{/each}
|
|
1969
|
-
<button
|
|
1970
|
-
type="button"
|
|
1971
|
-
class="remove"
|
|
1972
|
-
onpointerdown={(event: PointerEvent) => {
|
|
1973
|
-
event.preventDefault()
|
|
1974
|
-
event.stopPropagation()
|
|
1975
|
-
remove_context_bond()
|
|
1976
|
-
}}
|
|
1977
|
-
onkeydown={(event: KeyboardEvent) => {
|
|
1978
|
-
if (event.key !== `Enter` && event.key !== ` `) return
|
|
1979
|
-
event.preventDefault()
|
|
1980
|
-
event.stopPropagation()
|
|
1981
|
-
remove_context_bond()
|
|
1982
|
-
}}
|
|
1983
|
-
>
|
|
2003
|
+
<button type="button" class="remove" {...menu_action_props(remove_context_bond)}>
|
|
1984
2004
|
Remove
|
|
1985
2005
|
</button>
|
|
1986
|
-
<button
|
|
1987
|
-
type="button"
|
|
1988
|
-
onpointerdown={(event: PointerEvent) => {
|
|
1989
|
-
event.preventDefault()
|
|
1990
|
-
event.stopPropagation()
|
|
1991
|
-
close_bond_context_menu()
|
|
1992
|
-
}}
|
|
1993
|
-
onkeydown={(event: KeyboardEvent) => {
|
|
1994
|
-
if (event.key !== `Enter` && event.key !== ` `) return
|
|
1995
|
-
event.preventDefault()
|
|
1996
|
-
event.stopPropagation()
|
|
1997
|
-
close_bond_context_menu()
|
|
1998
|
-
}}
|
|
1999
|
-
>
|
|
2006
|
+
<button type="button" {...menu_action_props(close_bond_context_menu)}>
|
|
2000
2007
|
Close
|
|
2001
2008
|
</button>
|
|
2002
2009
|
</div>
|
|
2003
2010
|
</extras.HTML>
|
|
2004
2011
|
{/if}
|
|
2005
2012
|
|
|
2006
|
-
<!-- highlight hovered, active and selected sites
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
color: active_highlight_color,
|
|
2028
|
-
}))),
|
|
2029
|
-
] as
|
|
2030
|
-
entry
|
|
2031
|
-
(`${entry.kind}-${entry.site_idx}`)
|
|
2032
|
-
}
|
|
2033
|
-
{@const { site, opacity, color, kind, site_idx } = entry}
|
|
2034
|
-
{#if site}
|
|
2035
|
-
{@const xyz = site.xyz}
|
|
2036
|
-
{@const highlight_radius = site_idx !== null
|
|
2037
|
-
? radius_by_site_idx.get(site_idx) ?? get_site_radius(site, site_idx)
|
|
2038
|
-
: get_site_radius(site, site_idx)}
|
|
2039
|
-
<T.Mesh
|
|
2040
|
-
position={xyz}
|
|
2041
|
-
scale={1.2 * highlight_radius}
|
|
2042
|
-
oncreate={disable_raycast}
|
|
2043
|
-
>
|
|
2044
|
-
<T.SphereGeometry args={[0.5, 22, 22]} />
|
|
2045
|
-
<T.MeshStandardMaterial
|
|
2046
|
-
{color}
|
|
2047
|
-
transparent
|
|
2048
|
-
{opacity}
|
|
2049
|
-
emissive={color}
|
|
2050
|
-
emissiveIntensity={kind === `selected` || kind === `active` ? 0.7 : 0.2}
|
|
2051
|
-
depthTest={false}
|
|
2052
|
-
depthWrite={false}
|
|
2053
|
-
/>
|
|
2054
|
-
</T.Mesh>
|
|
2055
|
-
{/if}
|
|
2013
|
+
<!-- highlight hovered, active and selected sites. The target list is a pulse-
|
|
2014
|
+
independent $derived; only the opacity reads the per-frame pulse value so the
|
|
2015
|
+
array isn't rebuilt every animation frame. -->
|
|
2016
|
+
{#each highlight_targets as entry (`${entry.kind}-${entry.site_idx}`)}
|
|
2017
|
+
{@const is_pulsing = entry.kind !== `hover`}
|
|
2018
|
+
<T.Mesh
|
|
2019
|
+
position={entry.site.xyz}
|
|
2020
|
+
scale={1.2 * entry.radius}
|
|
2021
|
+
oncreate={disable_raycast}
|
|
2022
|
+
>
|
|
2023
|
+
<T.SphereGeometry args={[0.5, 22, 22]} />
|
|
2024
|
+
<T.MeshStandardMaterial
|
|
2025
|
+
color={entry.color}
|
|
2026
|
+
transparent
|
|
2027
|
+
opacity={is_pulsing ? pulse_opacity : 0.28}
|
|
2028
|
+
emissive={entry.color}
|
|
2029
|
+
emissiveIntensity={is_pulsing ? 0.7 : 0.2}
|
|
2030
|
+
depthTest={false}
|
|
2031
|
+
depthWrite={false}
|
|
2032
|
+
/>
|
|
2033
|
+
</T.Mesh>
|
|
2056
2034
|
{/each}
|
|
2057
2035
|
|
|
2058
2036
|
<!-- selection order labels (1, 2, 3, ...) for measurements and bond editing -->
|
|
2059
|
-
{#if structure?.sites && (measured_sites?.length ?? 0) > 0 &&
|
|
2060
|
-
(measure_mode === `distance` || measure_mode === `angle` ||
|
|
2061
|
-
measure_mode === `edit-bonds`)}
|
|
2037
|
+
{#if structure?.sites && (measured_sites?.length ?? 0) > 0 && (measure_mode === `distance` || measure_mode === `angle` || measure_mode === `edit-bonds`)}
|
|
2062
2038
|
{#each measured_sites as site_index, loop_idx (site_index)}
|
|
2063
2039
|
{@const site = structure.sites[site_index]}
|
|
2064
2040
|
{#if site}
|
|
@@ -2073,24 +2049,18 @@
|
|
|
2073
2049
|
{/if}
|
|
2074
2050
|
|
|
2075
2051
|
<!-- hovered site tooltip -->
|
|
2076
|
-
{#if hovered_site && !camera_is_moving &&
|
|
2077
|
-
|
|
2078
|
-
{@const
|
|
2079
|
-
`, `,
|
|
2080
|
-
)}
|
|
2081
|
-
{@const xyz = hovered_site.xyz.map((val) => format_num(val, float_fmt)).join(
|
|
2082
|
-
`, `,
|
|
2083
|
-
)}
|
|
2052
|
+
{#if hovered_site && !camera_is_moving && (atom_tooltip_active || active_sites.includes(hovered_idx ?? -1))}
|
|
2053
|
+
{@const abc = hovered_site.abc.map((val) => format_num(val, float_fmt)).join(`, `)}
|
|
2054
|
+
{@const xyz = hovered_site.xyz.map((val) => format_num(val, float_fmt)).join(`, `)}
|
|
2084
2055
|
{@const bond_neighbors = (() => {
|
|
2085
2056
|
if (hovered_idx == null || !structure?.sites) return []
|
|
2086
2057
|
return filtered_bond_pairs
|
|
2087
|
-
.filter(
|
|
2088
|
-
bond.site_idx_1 === hovered_idx || bond.site_idx_2 === hovered_idx
|
|
2058
|
+
.filter(
|
|
2059
|
+
(bond) => bond.site_idx_1 === hovered_idx || bond.site_idx_2 === hovered_idx,
|
|
2089
2060
|
)
|
|
2090
2061
|
.map((bond) => {
|
|
2091
|
-
const neighbor_idx =
|
|
2092
|
-
? bond.site_idx_2
|
|
2093
|
-
: bond.site_idx_1
|
|
2062
|
+
const neighbor_idx =
|
|
2063
|
+
bond.site_idx_1 === hovered_idx ? bond.site_idx_2 : bond.site_idx_1
|
|
2094
2064
|
return structure.sites[neighbor_idx]?.species[0]?.element ?? `?`
|
|
2095
2065
|
})
|
|
2096
2066
|
})()}
|
|
@@ -2107,27 +2077,20 @@
|
|
|
2107
2077
|
})()}
|
|
2108
2078
|
{@const tooltip_species =
|
|
2109
2079
|
render_sites.find((rs) => rs.site_idx === hovered_idx)?.site.species ??
|
|
2110
|
-
hovered_site.species ??
|
|
2080
|
+
hovered_site.species ??
|
|
2081
|
+
[]}
|
|
2111
2082
|
<CanvasTooltip position={hovered_site.xyz}>
|
|
2112
2083
|
<!-- Element symbols with occupancies for disordered sites -->
|
|
2113
2084
|
<div class="elements">
|
|
2114
|
-
{#each tooltip_species as
|
|
2115
|
-
{
|
|
2116
|
-
|
|
2117
|
-
(`${element ?? ``}-${occu ?? ``}-${oxi_state ?? ``}-${idx}`)
|
|
2118
|
-
}
|
|
2119
|
-
{@const element_name = element_data.find((elem) =>
|
|
2120
|
-
elem.symbol === element
|
|
2121
|
-
)?.name ??
|
|
2122
|
-
``}
|
|
2085
|
+
{#each tooltip_species as { element, occu, oxidation_state: oxi_state }, idx (`${element ?? ``}-${occu ?? ``}-${oxi_state ?? ``}-${idx}`)}
|
|
2086
|
+
{@const element_name =
|
|
2087
|
+
element_by_symbol.get(element as ElementSymbol)?.name ?? ``}
|
|
2123
2088
|
<span class="species">
|
|
2124
|
-
{#if occu !== 1}<span class="occupancy">{
|
|
2125
|
-
format_num(occu, `.3~f`)
|
|
2126
|
-
}</span>{/if}
|
|
2089
|
+
{#if occu !== 1}<span class="occupancy">{format_num(occu, `.3~f`)}</span>{/if}
|
|
2127
2090
|
<strong>
|
|
2128
|
-
{element}{#if oxi_state != null && oxi_state !== 0}<sup
|
|
2129
|
-
oxi_state
|
|
2130
|
-
|
|
2091
|
+
{element}{#if oxi_state != null && oxi_state !== 0}<sup
|
|
2092
|
+
>{Math.abs(oxi_state)}{oxi_state > 0 ? `+` : `−`}</sup
|
|
2093
|
+
>{/if}
|
|
2131
2094
|
</strong>
|
|
2132
2095
|
{#if element_name}<span class="elem-name">{element_name}</span>{/if}
|
|
2133
2096
|
</span>
|
|
@@ -2153,23 +2116,19 @@
|
|
|
2153
2116
|
{/if}
|
|
2154
2117
|
|
|
2155
2118
|
<!-- TransformControls for editing atoms in edit-atoms mode -->
|
|
2156
|
-
{#if measure_mode === `edit-atoms` && selected_sites.length > 0 &&
|
|
2157
|
-
structure?.sites}
|
|
2119
|
+
{#if interactive && measure_mode === `edit-atoms` && selected_sites.length > 0 && structure?.sites}
|
|
2158
2120
|
{@const selected_atoms = selected_sites
|
|
2159
2121
|
.map((idx) => structure?.sites?.[idx])
|
|
2160
2122
|
.filter((site): site is Site => site != null)}
|
|
2161
2123
|
{#if selected_atoms.length > 0}
|
|
2162
2124
|
{@const avg = (dim: number) =>
|
|
2163
|
-
|
|
2164
|
-
|
|
2125
|
+
selected_atoms.reduce((sum, atom) => sum + atom.xyz[dim], 0) /
|
|
2126
|
+
selected_atoms.length}
|
|
2165
2127
|
{@const centroid = [avg(0), avg(1), avg(2)] as Vec3}
|
|
2166
2128
|
<!-- Invisible mesh at centroid for TransformControls to manipulate.
|
|
2167
2129
|
During drag, use frozen_centroid so Svelte doesn't override TransformControls
|
|
2168
2130
|
with the wrapped centroid (which jumps on PBC boundary crossings). -->
|
|
2169
|
-
<T.Mesh
|
|
2170
|
-
position={frozen_centroid ?? centroid}
|
|
2171
|
-
bind:ref={transform_object}
|
|
2172
|
-
>
|
|
2131
|
+
<T.Mesh position={frozen_centroid ?? centroid} bind:ref={transform_object}>
|
|
2173
2132
|
<T.SphereGeometry args={[0.01, 4, 4]} />
|
|
2174
2133
|
<T.MeshBasicMaterial transparent opacity={0} />
|
|
2175
2134
|
</T.Mesh>
|
|
@@ -2208,7 +2167,7 @@
|
|
|
2208
2167
|
|
|
2209
2168
|
<!-- Invisible plane for click-to-place atom in add-atom mode -->
|
|
2210
2169
|
<!-- Uses onBeforeRender to orient normal toward camera so raycasts always hit -->
|
|
2211
|
-
{#if measure_mode === `edit-atoms` && add_atom_mode}
|
|
2170
|
+
{#if interactive && measure_mode === `edit-atoms` && add_atom_mode}
|
|
2212
2171
|
{@const center = rotation_target ?? [0, 0, 0]}
|
|
2213
2172
|
<T.Mesh
|
|
2214
2173
|
position={center}
|
|
@@ -2223,10 +2182,7 @@
|
|
|
2223
2182
|
}}
|
|
2224
2183
|
>
|
|
2225
2184
|
<T.PlaneGeometry
|
|
2226
|
-
args={[
|
|
2227
|
-
Math.max(200, structure_size * 4),
|
|
2228
|
-
Math.max(200, structure_size * 4),
|
|
2229
|
-
]}
|
|
2185
|
+
args={[Math.max(200, structure_size * 4), Math.max(200, structure_size * 4)]}
|
|
2230
2186
|
/>
|
|
2231
2187
|
<T.MeshBasicMaterial transparent opacity={0} side={2} depthWrite={false} />
|
|
2232
2188
|
</T.Mesh>
|
|
@@ -2234,7 +2190,15 @@
|
|
|
2234
2190
|
|
|
2235
2191
|
<!-- Isosurface rendering from volumetric data (CHGCAR, .cube files) -->
|
|
2236
2192
|
{#if volumetric_data && isosurface_settings}
|
|
2237
|
-
|
|
2193
|
+
{@const volume_list = Array.isArray(volumetric_data)
|
|
2194
|
+
? volumetric_data
|
|
2195
|
+
: [volumetric_data]}
|
|
2196
|
+
<Isosurface
|
|
2197
|
+
volumes={volume_list}
|
|
2198
|
+
settings={isosurface_settings}
|
|
2199
|
+
{active_volume_idx}
|
|
2200
|
+
tiling={volume_scaling}
|
|
2201
|
+
/>
|
|
2238
2202
|
{/if}
|
|
2239
2203
|
|
|
2240
2204
|
<!-- Measurement overlays for measured sites -->
|
|
@@ -2246,27 +2210,19 @@
|
|
|
2246
2210
|
{@const site_j = structure.sites[idx_j]}
|
|
2247
2211
|
{@const pos_i = site_i.xyz}
|
|
2248
2212
|
{@const pos_j = site_j.xyz}
|
|
2249
|
-
<Cylinder
|
|
2250
|
-
|
|
2251
|
-
to={pos_j}
|
|
2252
|
-
thickness={0.12}
|
|
2253
|
-
color={measure_line_color}
|
|
2254
|
-
/>
|
|
2255
|
-
{@const midpoint = [
|
|
2256
|
-
(pos_i[0] + pos_j[0]) / 2,
|
|
2257
|
-
(pos_i[1] + pos_j[1]) / 2,
|
|
2258
|
-
(pos_i[2] + pos_j[2]) / 2,
|
|
2259
|
-
] as Vec3}
|
|
2213
|
+
<Cylinder from={pos_i} to={pos_j} thickness={0.12} color={measure_line_color} />
|
|
2214
|
+
{@const mid_pos = midpoint(pos_i, pos_j)}
|
|
2260
2215
|
{@const direct = math.euclidean_dist(pos_i, pos_j)}
|
|
2261
2216
|
{@const pbc = lattice
|
|
2262
|
-
|
|
2263
|
-
|
|
2217
|
+
? measure.distance_pbc(pos_i, pos_j, lattice.matrix, undefined, lattice.pbc)
|
|
2218
|
+
: direct}
|
|
2264
2219
|
{@const differ = lattice ? Math.abs(pbc - direct) > 1e-6 : false}
|
|
2265
|
-
<extras.HTML center position={
|
|
2220
|
+
<extras.HTML center position={mid_pos}>
|
|
2266
2221
|
<span class="measure-label">
|
|
2267
2222
|
{#if differ}
|
|
2268
2223
|
PBC: {format_num(pbc, float_fmt)} Å<br /><small>
|
|
2269
|
-
Direct: {format_num(direct, float_fmt)} Å</small
|
|
2224
|
+
Direct: {format_num(direct, float_fmt)} Å</small
|
|
2225
|
+
>
|
|
2270
2226
|
{:else}
|
|
2271
2227
|
{format_num(pbc, float_fmt)} Å
|
|
2272
2228
|
{/if}
|
|
@@ -2277,19 +2233,20 @@
|
|
|
2277
2233
|
{:else if measure_mode === `angle` && measured_sites.length >= 3}
|
|
2278
2234
|
{#each measured_sites as idx_center (idx_center)}
|
|
2279
2235
|
{@const center = structure.sites[idx_center]}
|
|
2280
|
-
{#each measured_sites.filter((idx) => idx !== idx_center) as
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
}
|
|
2285
|
-
{#each measured_sites.filter((idx) => idx !== idx_center).slice(loop_idx + 1) as
|
|
2286
|
-
idx_b
|
|
2287
|
-
(idx_center + `-` + idx_a + `-` + idx_b)
|
|
2288
|
-
}
|
|
2236
|
+
{#each measured_sites.filter((idx) => idx !== idx_center) as idx_a, loop_idx (idx_center + `-` + idx_a)}
|
|
2237
|
+
{#each measured_sites
|
|
2238
|
+
.filter((idx) => idx !== idx_center)
|
|
2239
|
+
.slice(loop_idx + 1) as idx_b (idx_center + `-` + idx_a + `-` + idx_b)}
|
|
2289
2240
|
{@const site_a = structure.sites[idx_a]}
|
|
2290
2241
|
{@const site_b = structure.sites[idx_b]}
|
|
2291
2242
|
{@const disp = (to: Vec3) =>
|
|
2292
|
-
|
|
2243
|
+
measure.displacement_pbc(
|
|
2244
|
+
center.xyz,
|
|
2245
|
+
to,
|
|
2246
|
+
lattice?.matrix,
|
|
2247
|
+
undefined,
|
|
2248
|
+
lattice?.pbc,
|
|
2249
|
+
)}
|
|
2293
2250
|
{@const v1 = disp(site_a.xyz)}
|
|
2294
2251
|
{@const v2 = disp(site_b.xyz)}
|
|
2295
2252
|
{@const n1 = Math.hypot(v1[0], v1[1], v1[2])}
|