matterviz 0.3.4 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/FilePicker.svelte +1 -1
- package/dist/app.css +7 -0
- package/dist/brillouin/BrillouinZone.svelte +5 -2
- package/dist/brillouin/compute.js +8 -4
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +6 -6
- package/dist/chempot-diagram/async-compute.svelte.js +5 -4
- package/dist/chempot-diagram/chempot-worker.js +2 -2
- package/dist/chempot-diagram/compute.js +16 -16
- package/dist/composition/FormulaFilter.svelte +3 -3
- package/dist/constants.js +2 -8
- package/dist/convex-hull/ConvexHull.svelte +2 -2
- package/dist/convex-hull/ConvexHull2D.svelte +11 -10
- package/dist/convex-hull/ConvexHull3D.svelte +16 -14
- package/dist/convex-hull/ConvexHull4D.svelte +26 -14
- package/dist/convex-hull/ConvexHullControls.svelte +1 -1
- package/dist/convex-hull/ConvexHullInfoPane.svelte +68 -61
- package/dist/convex-hull/ConvexHullStats.svelte +23 -6
- package/dist/convex-hull/GasPressureControls.svelte +3 -3
- package/dist/convex-hull/TemperatureSlider.svelte +1 -1
- package/dist/convex-hull/barycentric-coords.js +2 -2
- package/dist/convex-hull/helpers.js +45 -27
- package/dist/convex-hull/thermodynamics.js +2 -2
- package/dist/element/BohrAtom.svelte +25 -27
- package/dist/element/BohrAtom.svelte.d.ts +2 -2
- package/dist/element/data.d.ts +2 -3
- package/dist/fermi-surface/FermiSurface.svelte +5 -2
- package/dist/fermi-surface/compute.js +3 -3
- package/dist/fermi-surface/parse.js +2 -2
- package/dist/fermi-surface/symmetry.js +1 -1
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +8 -8
- package/dist/icons.d.ts +6 -6
- package/dist/icons.js +6 -6
- package/dist/io/decompress.js +12 -7
- package/dist/io/export.js +20 -16
- package/dist/io/is-binary.js +19 -4
- package/dist/isosurface/parse.js +8 -8
- package/dist/isosurface/types.js +9 -9
- package/dist/layout/InfoTag.svelte +1 -1
- package/dist/layout/json-tree/JsonNode.svelte +1 -0
- package/dist/layout/json-tree/utils.js +2 -1
- package/dist/marching-cubes.js +1 -1
- package/dist/math.js +1 -1
- package/dist/overlays/CopyButton.svelte +45 -0
- package/dist/overlays/CopyButton.svelte.d.ts +8 -0
- package/dist/overlays/InfoPaneCards.svelte +149 -0
- package/dist/overlays/InfoPaneCards.svelte.d.ts +22 -0
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +33 -35
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +2 -2
- package/dist/phase-diagram/PhaseDiagramControls.svelte +27 -29
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +2 -2
- package/dist/phase-diagram/parse.js +3 -3
- package/dist/phase-diagram/svg-to-diagram.js +10 -12
- package/dist/plot/BarPlot.svelte +24 -15
- package/dist/plot/BarPlot.svelte.d.ts +3 -2
- package/dist/plot/FillArea.svelte +2 -3
- package/dist/plot/FillArea.svelte.d.ts +3 -2
- package/dist/plot/Histogram.svelte +37 -19
- package/dist/plot/Line.svelte +2 -3
- package/dist/plot/Line.svelte.d.ts +2 -2
- package/dist/plot/PlotLegend.svelte +79 -8
- package/dist/plot/PlotLegend.svelte.d.ts +4 -0
- package/dist/plot/PortalSelect.svelte +5 -5
- package/dist/plot/ScatterPlot.svelte +47 -33
- package/dist/plot/ScatterPlot.svelte.d.ts +5 -4
- package/dist/plot/ScatterPlot3D.svelte +6 -3
- package/dist/plot/ScatterPoint.svelte +10 -4
- package/dist/plot/ScatterPoint.svelte.d.ts +4 -2
- package/dist/plot/SpacegroupBarPlot.svelte +5 -4
- package/dist/plot/data-cleaning.js +9 -9
- package/dist/plot/index.d.ts +0 -6
- package/dist/plot/scales.d.ts +3 -3
- package/dist/plot/scales.js +29 -29
- package/dist/plot/types.d.ts +5 -9
- package/dist/rdf/calc-rdf.js +1 -1
- package/dist/sanitize.js +22 -15
- package/dist/settings.d.ts +2 -0
- package/dist/settings.js +12 -3
- package/dist/spectral/Bands.svelte +6 -6
- package/dist/spectral/BandsAndDos.svelte +4 -4
- package/dist/spectral/BrillouinBandsDos.svelte +3 -3
- package/dist/spectral/Dos.svelte +2 -2
- package/dist/spectral/helpers.js +1 -1
- package/dist/structure/AtomLegend.svelte +4 -4
- package/dist/structure/AtomLegend.svelte.d.ts +1 -1
- package/dist/structure/Cylinder.svelte +7 -7
- package/dist/structure/Structure.svelte +169 -27
- package/dist/structure/Structure.svelte.d.ts +6 -2
- package/dist/structure/StructureControls.svelte +130 -16
- package/dist/structure/StructureControls.svelte.d.ts +1 -1
- package/dist/structure/StructureInfoPane.svelte +519 -218
- package/dist/structure/StructureInfoPane.svelte.d.ts +2 -1
- package/dist/structure/StructureScene.svelte +399 -68
- package/dist/structure/StructureScene.svelte.d.ts +8 -4
- package/dist/structure/atom-properties.js +3 -1
- package/dist/structure/bond-order-perception.d.ts +13 -0
- package/dist/structure/bond-order-perception.js +367 -0
- package/dist/structure/bonding.d.ts +10 -1
- package/dist/structure/bonding.js +232 -11
- package/dist/structure/export.js +6 -4
- package/dist/structure/index.d.ts +19 -4
- package/dist/structure/index.js +3 -0
- package/dist/structure/label-placement.d.ts +14 -0
- package/dist/structure/label-placement.js +72 -0
- package/dist/structure/parse.d.ts +2 -1
- package/dist/structure/parse.js +25 -36
- package/dist/structure/supercell.js +35 -2
- package/dist/symmetry/SymmetryStats.svelte +1 -1
- package/dist/symmetry/cell-transform.js +15 -1
- package/dist/symmetry/index.js +3 -3
- package/dist/table/HeatmapTable.svelte +3 -3
- package/dist/table/ToggleMenu.svelte +1 -1
- package/dist/trajectory/Trajectory.svelte +2 -2
- package/dist/trajectory/TrajectoryInfoPane.svelte +14 -88
- package/dist/trajectory/extract.js +4 -4
- package/dist/trajectory/frame-reader.js +2 -2
- package/dist/trajectory/parse/ase.js +2 -6
- package/dist/trajectory/parse/hdf5.js +1 -3
- package/dist/trajectory/plotting.js +1 -1
- package/dist/utils.js +1 -1
- package/dist/xrd/calc-xrd.js +1 -1
- package/package.json +22 -37
- package/dist/structure/ferrox-wasm-types.d.ts +0 -46
- package/dist/structure/ferrox-wasm-types.js +0 -18
- package/dist/structure/ferrox-wasm.d.ts +0 -94
- package/dist/structure/ferrox-wasm.js +0 -249
|
@@ -22,7 +22,12 @@
|
|
|
22
22
|
import { DEFAULTS } from '../settings'
|
|
23
23
|
import { sanitize_html } from '../sanitize'
|
|
24
24
|
import { colors } from '../state.svelte'
|
|
25
|
-
import type {
|
|
25
|
+
import type {
|
|
26
|
+
AnyStructure,
|
|
27
|
+
Crystal,
|
|
28
|
+
MeasureMode,
|
|
29
|
+
StructureBond,
|
|
30
|
+
} from './'
|
|
26
31
|
import {
|
|
27
32
|
default_vector_configs,
|
|
28
33
|
get_element_counts,
|
|
@@ -42,7 +47,7 @@
|
|
|
42
47
|
import { Canvas } from '@threlte/core'
|
|
43
48
|
import type { ComponentProps, Snippet } from 'svelte'
|
|
44
49
|
import { untrack } from 'svelte'
|
|
45
|
-
import { click_outside, tooltip } from 'svelte-multiselect'
|
|
50
|
+
import { click_outside, tooltip } from 'svelte-multiselect/attachments'
|
|
46
51
|
import type { HTMLAttributes } from 'svelte/elements'
|
|
47
52
|
import { SvelteMap, SvelteSet } from 'svelte/reactivity'
|
|
48
53
|
import type { Camera, OrthographicCamera, Scene } from 'three'
|
|
@@ -50,6 +55,7 @@
|
|
|
50
55
|
import { get_property_colors } from './atom-properties'
|
|
51
56
|
import AtomLegend from './AtomLegend.svelte'
|
|
52
57
|
import CellSelect from './CellSelect.svelte'
|
|
58
|
+
import { merge_bond_edits } from './bonding'
|
|
53
59
|
import type { StructureHandlerData } from './index'
|
|
54
60
|
import { MAX_SELECTED_SITES } from './measure'
|
|
55
61
|
import { normalize_fractional_coords, parse_any_structure } from './parse'
|
|
@@ -60,11 +66,22 @@
|
|
|
60
66
|
|
|
61
67
|
// Type alias for event handlers to reduce verbosity
|
|
62
68
|
type EventHandler = (data: StructureHandlerData) => void
|
|
69
|
+
type BondEditContext = {
|
|
70
|
+
structure?: AnyStructure
|
|
71
|
+
cell_type: CellType
|
|
72
|
+
supercell_scaling: string
|
|
73
|
+
show_image_atoms: boolean
|
|
74
|
+
}
|
|
75
|
+
type BondEditSnapshot = {
|
|
76
|
+
bonds: StructureBond[] | undefined
|
|
77
|
+
context: BondEditContext
|
|
78
|
+
}
|
|
63
79
|
|
|
64
80
|
// Local reactive state for scene and lattice props. Deeply reactive so nested mutations propagate.
|
|
65
81
|
// Deep-clone to prevent mutations from leaking to global defaults across component instances.
|
|
66
82
|
let scene_props = $state(
|
|
67
83
|
structuredClone(DEFAULTS.structure) as typeof DEFAULTS.structure & {
|
|
84
|
+
active_sites?: number[]
|
|
68
85
|
camera_target?: Vec3
|
|
69
86
|
},
|
|
70
87
|
)
|
|
@@ -79,6 +96,7 @@
|
|
|
79
96
|
|
|
80
97
|
let {
|
|
81
98
|
structure = $bindable(),
|
|
99
|
+
bonds = $bindable(),
|
|
82
100
|
scene_props: scene_props_in = $bindable(),
|
|
83
101
|
lattice_props: lattice_props_in = $bindable(),
|
|
84
102
|
controls_open = $bindable(false),
|
|
@@ -117,6 +135,8 @@
|
|
|
117
135
|
performance_mode = $bindable(`quality`),
|
|
118
136
|
// expose selected site indices for external control/highlighting
|
|
119
137
|
selected_sites = $bindable([]),
|
|
138
|
+
highlighted_sites = $bindable([]),
|
|
139
|
+
hovered_site_idx = $bindable(null),
|
|
120
140
|
// expose measured site indices for overlays/labels
|
|
121
141
|
measured_sites = $bindable([]),
|
|
122
142
|
// expose the displayed structure (with image atoms and supercell) for external use
|
|
@@ -153,10 +173,12 @@
|
|
|
153
173
|
on_fullscreen_change,
|
|
154
174
|
on_camera_move,
|
|
155
175
|
on_camera_reset,
|
|
176
|
+
on_bonds_change,
|
|
156
177
|
...rest
|
|
157
178
|
}:
|
|
158
179
|
& {
|
|
159
180
|
structure?: AnyStructure
|
|
181
|
+
bonds?: StructureBond[]
|
|
160
182
|
scene_props?: ComponentProps<typeof StructureScene>
|
|
161
183
|
/**
|
|
162
184
|
* Controls visibility configuration.
|
|
@@ -199,6 +221,8 @@
|
|
|
199
221
|
performance_mode?: `quality` | `speed`
|
|
200
222
|
// allow parent components to control highlighted/selected site indices
|
|
201
223
|
selected_sites?: number[]
|
|
224
|
+
highlighted_sites?: number[]
|
|
225
|
+
hovered_site_idx?: number | null
|
|
202
226
|
// explicit measured sites for distance/angle overlays
|
|
203
227
|
measured_sites?: number[]
|
|
204
228
|
// expose the displayed structure (with image atoms and/or supercell) for external use
|
|
@@ -236,6 +260,7 @@
|
|
|
236
260
|
on_fullscreen_change?: EventHandler
|
|
237
261
|
on_camera_move?: EventHandler
|
|
238
262
|
on_camera_reset?: EventHandler
|
|
263
|
+
on_bonds_change?: (bonds: StructureBond[] | undefined) => void
|
|
239
264
|
}
|
|
240
265
|
& Omit<ComponentProps<typeof StructureControls>, `children` | `onclose`>
|
|
241
266
|
& Omit<HTMLAttributes<HTMLDivElement>, `children`> = $props()
|
|
@@ -427,8 +452,85 @@
|
|
|
427
452
|
let export_pane_open = $state(false)
|
|
428
453
|
|
|
429
454
|
// Bond customization state
|
|
430
|
-
let added_bonds = $state<[
|
|
431
|
-
let removed_bonds = $state<[
|
|
455
|
+
let added_bonds = $state<StructureBond[]>([])
|
|
456
|
+
let removed_bonds = $state<StructureBond[]>([])
|
|
457
|
+
let bond_order_overrides = $state<StructureBond[]>([])
|
|
458
|
+
let last_emitted_bond_signature = $state<string>()
|
|
459
|
+
let bond_edit_snapshot = $state<BondEditSnapshot>()
|
|
460
|
+
let has_bond_edits = $derived(
|
|
461
|
+
added_bonds.length > 0 || removed_bonds.length > 0 ||
|
|
462
|
+
bond_order_overrides.length > 0,
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
function clear_bond_edits() {
|
|
466
|
+
added_bonds = []
|
|
467
|
+
removed_bonds = []
|
|
468
|
+
bond_order_overrides = []
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function emit_bonds(next_bonds: StructureBond[] | undefined) {
|
|
472
|
+
const signature = next_bonds === undefined ? `undefined` : JSON.stringify(next_bonds)
|
|
473
|
+
if (signature === last_emitted_bond_signature) return
|
|
474
|
+
last_emitted_bond_signature = signature
|
|
475
|
+
bonds = next_bonds
|
|
476
|
+
on_bonds_change?.(next_bonds)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const current_source_bonds = (): StructureBond[] | undefined =>
|
|
480
|
+
bonds ?? structure?.properties?.bonds
|
|
481
|
+
|
|
482
|
+
const current_bond_edit_context = (): BondEditContext => ({
|
|
483
|
+
structure,
|
|
484
|
+
cell_type,
|
|
485
|
+
supercell_scaling,
|
|
486
|
+
show_image_atoms,
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
const bond_edit_context_changed = (
|
|
490
|
+
previous: BondEditContext,
|
|
491
|
+
current: BondEditContext,
|
|
492
|
+
): boolean =>
|
|
493
|
+
previous.structure !== current.structure ||
|
|
494
|
+
previous.cell_type !== current.cell_type ||
|
|
495
|
+
previous.supercell_scaling !== current.supercell_scaling ||
|
|
496
|
+
previous.show_image_atoms !== current.show_image_atoms
|
|
497
|
+
|
|
498
|
+
const resolve_bond_edit_reset_bonds = (
|
|
499
|
+
snapshot: BondEditSnapshot,
|
|
500
|
+
): StructureBond[] | undefined =>
|
|
501
|
+
snapshot.context.structure === structure ? snapshot.bonds : structure?.properties?.bonds
|
|
502
|
+
|
|
503
|
+
$effect(() => {
|
|
504
|
+
const snapshot = bond_edit_snapshot
|
|
505
|
+
if (snapshot === undefined) return
|
|
506
|
+
const context = current_bond_edit_context()
|
|
507
|
+
if (!bond_edit_context_changed(snapshot.context, context)) return
|
|
508
|
+
untrack(() => {
|
|
509
|
+
emit_bonds(resolve_bond_edit_reset_bonds(snapshot))
|
|
510
|
+
clear_bond_edits()
|
|
511
|
+
bond_edit_snapshot = undefined
|
|
512
|
+
})
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
$effect(() => {
|
|
516
|
+
if (!has_bond_edits) {
|
|
517
|
+
if (bond_edit_snapshot === undefined) return
|
|
518
|
+
emit_bonds(resolve_bond_edit_reset_bonds(bond_edit_snapshot))
|
|
519
|
+
bond_edit_snapshot = undefined
|
|
520
|
+
return
|
|
521
|
+
}
|
|
522
|
+
bond_edit_snapshot ??= {
|
|
523
|
+
bonds: current_source_bonds(),
|
|
524
|
+
context: current_bond_edit_context(),
|
|
525
|
+
}
|
|
526
|
+
const edited_bonds = merge_bond_edits(
|
|
527
|
+
bond_edit_snapshot.bonds ?? [],
|
|
528
|
+
added_bonds,
|
|
529
|
+
removed_bonds,
|
|
530
|
+
bond_order_overrides,
|
|
531
|
+
)
|
|
532
|
+
emit_bonds(edited_bonds)
|
|
533
|
+
})
|
|
432
534
|
|
|
433
535
|
// === Edit-atoms mode state ===
|
|
434
536
|
let dragging_atoms = $state(false)
|
|
@@ -530,10 +632,7 @@
|
|
|
530
632
|
if (measure_mode !== `edit-atoms`) return
|
|
531
633
|
untrack(() => {
|
|
532
634
|
// Clear bond edits from edit-bonds mode to avoid stale state
|
|
533
|
-
if (
|
|
534
|
-
added_bonds = []
|
|
535
|
-
removed_bonds = []
|
|
536
|
-
}
|
|
635
|
+
if (has_bond_edits) clear_bond_edits()
|
|
537
636
|
if (cell_type !== `original` && cell_transformed_structure && structure) {
|
|
538
637
|
// Bake the transformed cell: push original to undo, replace structure
|
|
539
638
|
is_internal_edit = true
|
|
@@ -545,6 +644,9 @@
|
|
|
545
644
|
})
|
|
546
645
|
|
|
547
646
|
let controls_config = $derived(normalize_show_controls(show_controls))
|
|
647
|
+
let active_scene_sites = $derived([
|
|
648
|
+
...new SvelteSet([...(scene_props.active_sites ?? []), ...highlighted_sites]),
|
|
649
|
+
])
|
|
548
650
|
|
|
549
651
|
// Normalize structure coordinates: wrap fractional coords to [0,1) and recompute Cartesian
|
|
550
652
|
// This ensures atoms are rendered inside the unit cell regardless of data source
|
|
@@ -553,24 +655,32 @@
|
|
|
553
655
|
return normalize_fractional_coords(structure) as AnyStructure
|
|
554
656
|
})
|
|
555
657
|
|
|
658
|
+
let structure_with_bonds = $derived.by(() => {
|
|
659
|
+
if (!normalized_structure || bonds === undefined) return normalized_structure
|
|
660
|
+
return {
|
|
661
|
+
...normalized_structure,
|
|
662
|
+
properties: { ...normalized_structure.properties, bonds },
|
|
663
|
+
} as AnyStructure
|
|
664
|
+
})
|
|
665
|
+
|
|
556
666
|
// Apply cell type transformation (original, conventional, or primitive)
|
|
557
667
|
// This must happen BEFORE supercell transformation
|
|
558
668
|
let cell_transformed_structure = $derived.by(() => {
|
|
559
669
|
if (
|
|
560
|
-
!
|
|
670
|
+
!structure_with_bonds || !(`lattice` in structure_with_bonds) ||
|
|
561
671
|
cell_type === `original`
|
|
562
672
|
) {
|
|
563
|
-
return
|
|
673
|
+
return structure_with_bonds
|
|
564
674
|
}
|
|
565
675
|
// Cell type transformation requires symmetry data
|
|
566
676
|
if (!sym_data) {
|
|
567
|
-
return
|
|
677
|
+
return structure_with_bonds
|
|
568
678
|
}
|
|
569
679
|
try {
|
|
570
|
-
return transform_cell(
|
|
680
|
+
return transform_cell(structure_with_bonds as Crystal, cell_type, sym_data)
|
|
571
681
|
} catch (error) {
|
|
572
682
|
console.error(`Failed to transform cell to ${cell_type}:`, error)
|
|
573
|
-
return
|
|
683
|
+
return structure_with_bonds
|
|
574
684
|
}
|
|
575
685
|
})
|
|
576
686
|
|
|
@@ -580,6 +690,19 @@
|
|
|
580
690
|
let has_supercell = $derived(
|
|
581
691
|
!!supercell_scaling && ![``, `1x1x1`, `1`].includes(supercell_scaling),
|
|
582
692
|
)
|
|
693
|
+
let bond_edits_enabled = $derived(
|
|
694
|
+
cell_type === `original` && !has_supercell && !supercell_loading,
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
$effect(() => {
|
|
698
|
+
if (measure_mode !== `edit-bonds` || bond_edits_enabled) return
|
|
699
|
+
untrack(() => {
|
|
700
|
+
clear_selection()
|
|
701
|
+
clear_bond_edits()
|
|
702
|
+
measure_mode = `distance`
|
|
703
|
+
show_toast(`Bond editing is only available for the original 1x1x1 cell`)
|
|
704
|
+
})
|
|
705
|
+
})
|
|
583
706
|
|
|
584
707
|
// Tile volumetric data to match supercell when active.
|
|
585
708
|
// Gate on !supercell_loading so the tiled volume and supercell structure update
|
|
@@ -806,17 +929,17 @@
|
|
|
806
929
|
}
|
|
807
930
|
|
|
808
931
|
const emit_file_load_event = (
|
|
809
|
-
|
|
932
|
+
loaded_structure: AnyStructure,
|
|
810
933
|
filename: string,
|
|
811
934
|
content: string | ArrayBuffer,
|
|
812
935
|
) =>
|
|
813
936
|
on_file_load?.({
|
|
814
|
-
structure:
|
|
937
|
+
structure: loaded_structure,
|
|
815
938
|
filename,
|
|
816
939
|
file_size: typeof content === `string`
|
|
817
940
|
? new Blob([content]).size
|
|
818
941
|
: content.byteLength,
|
|
819
|
-
total_atoms:
|
|
942
|
+
total_atoms: loaded_structure.sites?.length || 0,
|
|
820
943
|
})
|
|
821
944
|
|
|
822
945
|
// Try to parse content as a volumetric file, setting both structure and volumetric data.
|
|
@@ -930,8 +1053,7 @@
|
|
|
930
1053
|
}
|
|
931
1054
|
// Clear per-site overrides since indices shifted after deletion
|
|
932
1055
|
if (site_radius_overrides?.size > 0) site_radius_overrides.clear()
|
|
933
|
-
|
|
934
|
-
removed_bonds = []
|
|
1056
|
+
clear_bond_edits()
|
|
935
1057
|
show_toast(`Deleted ${n_deleted} site${n_deleted > 1 ? `s` : ``}`)
|
|
936
1058
|
}
|
|
937
1059
|
return
|
|
@@ -1295,15 +1417,13 @@
|
|
|
1295
1417
|
style="margin-left: -2px"
|
|
1296
1418
|
/>
|
|
1297
1419
|
</button>
|
|
1298
|
-
{#if (measured_sites?.length ?? 0) > 0 ||
|
|
1299
|
-
removed_bonds.length > 0}
|
|
1420
|
+
{#if (measured_sites?.length ?? 0) > 0 || has_bond_edits}
|
|
1300
1421
|
<button
|
|
1301
1422
|
type="button"
|
|
1302
1423
|
aria-label="Reset selection and bond edits"
|
|
1303
1424
|
onclick={() => {
|
|
1304
1425
|
clear_selection()
|
|
1305
|
-
|
|
1306
|
-
removed_bonds = []
|
|
1426
|
+
clear_bond_edits()
|
|
1307
1427
|
}}
|
|
1308
1428
|
>
|
|
1309
1429
|
<Icon icon="Reset" style="margin-left: -4px" />
|
|
@@ -1333,7 +1453,14 @@
|
|
|
1333
1453
|
<button
|
|
1334
1454
|
class="view-mode-option"
|
|
1335
1455
|
class:selected={measure_mode === mode}
|
|
1336
|
-
|
|
1456
|
+
disabled={mode === `edit-bonds` && !bond_edits_enabled}
|
|
1457
|
+
title={mode === `edit-bonds` && !bond_edits_enabled
|
|
1458
|
+
? `Bond editing is only available for the original 1x1x1 cell`
|
|
1459
|
+
: label}
|
|
1460
|
+
onclick={() => {
|
|
1461
|
+
if (mode === `edit-bonds` && !bond_edits_enabled) return
|
|
1462
|
+
;[measure_mode, measure_menu_open] = [mode, false]
|
|
1463
|
+
}}
|
|
1337
1464
|
>
|
|
1338
1465
|
<Icon {icon} style="transform: scale({scale})" />
|
|
1339
1466
|
<span>{@html sanitize_html(label)}</span>
|
|
@@ -1427,7 +1554,9 @@
|
|
|
1427
1554
|
<StructureInfoPane
|
|
1428
1555
|
structure={normalized_structure}
|
|
1429
1556
|
bind:pane_open={info_pane_open}
|
|
1430
|
-
|
|
1557
|
+
bind:highlighted_sites
|
|
1558
|
+
bind:hovered_site_idx
|
|
1559
|
+
bind:selected_sites
|
|
1431
1560
|
{sym_data}
|
|
1432
1561
|
{@attach tooltip({ content: `Structure info pane` })}
|
|
1433
1562
|
/>
|
|
@@ -1508,6 +1637,8 @@
|
|
|
1508
1637
|
{isosurface_settings}
|
|
1509
1638
|
bind:camera_is_moving
|
|
1510
1639
|
bind:selected_sites
|
|
1640
|
+
active_sites={active_scene_sites}
|
|
1641
|
+
bind:hovered_idx={hovered_site_idx}
|
|
1511
1642
|
bind:measured_sites
|
|
1512
1643
|
bind:scene
|
|
1513
1644
|
bind:camera
|
|
@@ -1520,6 +1651,8 @@
|
|
|
1520
1651
|
bind:site_radius_overrides
|
|
1521
1652
|
bind:added_bonds
|
|
1522
1653
|
bind:removed_bonds
|
|
1654
|
+
bind:bond_order_overrides
|
|
1655
|
+
{bond_edits_enabled}
|
|
1523
1656
|
{measure_mode}
|
|
1524
1657
|
{width}
|
|
1525
1658
|
{height}
|
|
@@ -1545,8 +1678,7 @@
|
|
|
1545
1678
|
<div class="edit-toast">{toast_msg}</div>
|
|
1546
1679
|
{/if}
|
|
1547
1680
|
|
|
1548
|
-
{#if measure_mode === `edit-bonds` &&
|
|
1549
|
-
(added_bonds.length > 0 || removed_bonds.length > 0)}
|
|
1681
|
+
{#if measure_mode === `edit-bonds` && has_bond_edits}
|
|
1550
1682
|
<div class="bond-edit-status">
|
|
1551
1683
|
{#if added_bonds.length > 0}
|
|
1552
1684
|
<span class="added">+{added_bonds.length} added</span>
|
|
@@ -1554,6 +1686,9 @@
|
|
|
1554
1686
|
{#if removed_bonds.length > 0}
|
|
1555
1687
|
<span class="removed">-{removed_bonds.length} removed</span>
|
|
1556
1688
|
{/if}
|
|
1689
|
+
{#if bond_order_overrides.length > 0}
|
|
1690
|
+
<span class="changed">{bond_order_overrides.length} ordered</span>
|
|
1691
|
+
{/if}
|
|
1557
1692
|
</div>
|
|
1558
1693
|
{/if}
|
|
1559
1694
|
|
|
@@ -1626,7 +1761,10 @@
|
|
|
1626
1761
|
gap: 4pt;
|
|
1627
1762
|
/* buttons need higher z-index than AtomLegend to make info/controls panes occlude legend */
|
|
1628
1763
|
/* we also need crazy high z-index to make info/control pane occlude threlte/extras' <HTML> elements for site labels */
|
|
1629
|
-
z-index: var(
|
|
1764
|
+
z-index: var(
|
|
1765
|
+
--struct-buttons-z-index,
|
|
1766
|
+
var(--z-index-overlay-controls, 100000000)
|
|
1767
|
+
);
|
|
1630
1768
|
opacity: 0;
|
|
1631
1769
|
pointer-events: none;
|
|
1632
1770
|
transition: opacity 0.2s ease;
|
|
@@ -1792,6 +1930,10 @@
|
|
|
1792
1930
|
color: #f44336;
|
|
1793
1931
|
font-weight: bold;
|
|
1794
1932
|
}
|
|
1933
|
+
.bond-edit-status .changed {
|
|
1934
|
+
color: var(--accent-color, #007acc);
|
|
1935
|
+
font-weight: bold;
|
|
1936
|
+
}
|
|
1795
1937
|
/* CellSelect: position at left of legend, show on hover */
|
|
1796
1938
|
.structure :global(.cell-select) {
|
|
1797
1939
|
order: -1; /* Move to left side of AtomLegend flex container */
|
|
@@ -2,7 +2,7 @@ import type { ShowControlsProp } from '../controls';
|
|
|
2
2
|
import type { ElementSymbol } from '../element';
|
|
3
3
|
import Spinner from '../feedback/Spinner.svelte';
|
|
4
4
|
import type { IsosurfaceSettings, VolumetricData } from '../isosurface/types';
|
|
5
|
-
import type { AnyStructure, MeasureMode } from './';
|
|
5
|
+
import type { AnyStructure, MeasureMode, StructureBond } from './';
|
|
6
6
|
import type { CellType, SymmetrySettings } from '../symmetry';
|
|
7
7
|
import type { MoyoDataset } from '@spglib/moyo-wasm';
|
|
8
8
|
import type { ComponentProps, Snippet } from 'svelte';
|
|
@@ -15,6 +15,7 @@ import StructureScene from './StructureScene.svelte';
|
|
|
15
15
|
type EventHandler = (data: StructureHandlerData) => void;
|
|
16
16
|
type $$ComponentProps = {
|
|
17
17
|
structure?: AnyStructure;
|
|
18
|
+
bonds?: StructureBond[];
|
|
18
19
|
scene_props?: ComponentProps<typeof StructureScene>;
|
|
19
20
|
/**
|
|
20
21
|
* Controls visibility configuration.
|
|
@@ -53,6 +54,8 @@ type $$ComponentProps = {
|
|
|
53
54
|
error_msg?: string;
|
|
54
55
|
performance_mode?: `quality` | `speed`;
|
|
55
56
|
selected_sites?: number[];
|
|
57
|
+
highlighted_sites?: number[];
|
|
58
|
+
hovered_site_idx?: number | null;
|
|
56
59
|
measured_sites?: number[];
|
|
57
60
|
displayed_structure?: AnyStructure;
|
|
58
61
|
hidden_elements?: Set<ElementSymbol>;
|
|
@@ -77,7 +80,8 @@ type $$ComponentProps = {
|
|
|
77
80
|
on_fullscreen_change?: EventHandler;
|
|
78
81
|
on_camera_move?: EventHandler;
|
|
79
82
|
on_camera_reset?: EventHandler;
|
|
83
|
+
on_bonds_change?: (bonds: StructureBond[] | undefined) => void;
|
|
80
84
|
} & Omit<ComponentProps<typeof StructureControls>, `children` | `onclose`> & Omit<HTMLAttributes<HTMLDivElement>, `children`>;
|
|
81
|
-
declare const Structure: import("svelte").Component<$$ComponentProps, {}, "structure" | "height" | "width" | "dragover" | "fullscreen" | "hovered" | "controls_open" | "loading" | "color_scheme" | "background_color" | "background_opacity" | "show_image_atoms" | "info_pane_open" | "wrapper" | "sym_data" | "png_dpi" | "error_msg" | "atom_color_config" | "
|
|
85
|
+
declare const Structure: import("svelte").Component<$$ComponentProps, {}, "structure" | "height" | "width" | "dragover" | "fullscreen" | "hovered" | "controls_open" | "loading" | "color_scheme" | "background_color" | "background_opacity" | "show_image_atoms" | "info_pane_open" | "wrapper" | "sym_data" | "png_dpi" | "error_msg" | "site_radius_overrides" | "atom_color_config" | "lattice_props" | "measure_mode" | "selected_sites" | "measured_sites" | "hidden_elements" | "hidden_prop_vals" | "element_radius_overrides" | "volumetric_data" | "isosurface_settings" | "element_mapping" | "bonds" | "supercell_scaling" | "cell_type" | "active_volume_idx" | "scene_props" | "highlighted_sites" | "hovered_site_idx" | "enable_measure_mode" | "performance_mode" | "displayed_structure" | "symmetry_settings">;
|
|
82
86
|
type Structure = ReturnType<typeof Structure>;
|
|
83
87
|
export default Structure;
|
|
@@ -112,21 +112,91 @@
|
|
|
112
112
|
}
|
|
113
113
|
})
|
|
114
114
|
|
|
115
|
+
const hex_color_pattern = /^#[0-9a-f]{3}([0-9a-f]{3})?$/i
|
|
116
|
+
const color_mix_pattern =
|
|
117
|
+
/^color-mix\(in srgb,\s*(#[0-9a-f]{3}(?:[0-9a-f]{3})?)\s+(\d+(?:\.\d+)?)%,\s*transparent\)$/i
|
|
118
|
+
|
|
119
|
+
const as_hex_color = (color: string | undefined, fallback: string): string =>
|
|
120
|
+
color?.match(hex_color_pattern)?.[0] ?? fallback
|
|
121
|
+
|
|
122
|
+
const parse_label_bg_color = (
|
|
123
|
+
color: string | undefined,
|
|
124
|
+
fallback_hex_color: string,
|
|
125
|
+
fallback_opacity: number,
|
|
126
|
+
): { hex_color: string; opacity: number } => {
|
|
127
|
+
if (color === `transparent`) {
|
|
128
|
+
return { hex_color: fallback_hex_color, opacity: 0 }
|
|
129
|
+
}
|
|
130
|
+
const color_mix = color?.match(color_mix_pattern)
|
|
131
|
+
if (color_mix) {
|
|
132
|
+
const percentage = Math.max(0, Math.min(100, Number(color_mix[2])))
|
|
133
|
+
return {
|
|
134
|
+
hex_color: color_mix[1],
|
|
135
|
+
opacity: percentage / 100,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const hex_color = color?.match(hex_color_pattern)?.[0]
|
|
139
|
+
return hex_color === undefined
|
|
140
|
+
? { hex_color: fallback_hex_color, opacity: fallback_opacity }
|
|
141
|
+
: { hex_color, opacity: 1 }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const default_site_label_color = as_hex_color(
|
|
145
|
+
DEFAULTS.structure.site_label_color,
|
|
146
|
+
`#111111`,
|
|
147
|
+
)
|
|
148
|
+
const default_site_label_bg = parse_label_bg_color(
|
|
149
|
+
DEFAULTS.structure.site_label_bg_color,
|
|
150
|
+
`#000000`,
|
|
151
|
+
0,
|
|
152
|
+
)
|
|
153
|
+
const initial_site_label_bg = parse_label_bg_color(
|
|
154
|
+
scene_props.site_label_bg_color,
|
|
155
|
+
default_site_label_bg.hex_color,
|
|
156
|
+
default_site_label_bg.opacity,
|
|
157
|
+
)
|
|
158
|
+
|
|
115
159
|
// Atom label color management
|
|
116
160
|
let site_label_hex_color = $state(
|
|
117
|
-
scene_props.site_label_color
|
|
118
|
-
)
|
|
119
|
-
let site_label_bg_hex_color = $state(
|
|
120
|
-
scene_props.site_label_bg_color || DEFAULTS.structure.site_label_bg_color,
|
|
161
|
+
as_hex_color(scene_props.site_label_color, default_site_label_color),
|
|
121
162
|
)
|
|
122
|
-
let
|
|
163
|
+
let site_label_bg_hex_color = $state(initial_site_label_bg.hex_color)
|
|
164
|
+
let site_label_background_opacity = $state(initial_site_label_bg.opacity)
|
|
165
|
+
let last_synced_site_label_color = scene_props.site_label_color
|
|
166
|
+
let last_synced_site_label_bg_color = scene_props.site_label_bg_color
|
|
123
167
|
|
|
124
168
|
$effect(() => {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
169
|
+
const external_color_changed =
|
|
170
|
+
scene_props.site_label_color !== last_synced_site_label_color
|
|
171
|
+
const external_bg_changed =
|
|
172
|
+
scene_props.site_label_bg_color !== last_synced_site_label_bg_color
|
|
173
|
+
|
|
174
|
+
if (external_color_changed) {
|
|
175
|
+
site_label_hex_color = as_hex_color(
|
|
176
|
+
scene_props.site_label_color,
|
|
177
|
+
default_site_label_color,
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
if (external_bg_changed) {
|
|
181
|
+
const next_bg = parse_label_bg_color(
|
|
182
|
+
scene_props.site_label_bg_color,
|
|
183
|
+
default_site_label_bg.hex_color,
|
|
184
|
+
default_site_label_bg.opacity,
|
|
185
|
+
)
|
|
186
|
+
site_label_bg_hex_color = next_bg.hex_color
|
|
187
|
+
site_label_background_opacity = next_bg.opacity
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!external_color_changed) scene_props.site_label_color = site_label_hex_color
|
|
191
|
+
if (!external_bg_changed) {
|
|
192
|
+
scene_props.site_label_bg_color =
|
|
193
|
+
`color-mix(in srgb, ${site_label_bg_hex_color} ${
|
|
194
|
+
format_num(site_label_background_opacity, `.1~%`)
|
|
195
|
+
}, transparent)`
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
last_synced_site_label_color = scene_props.site_label_color
|
|
199
|
+
last_synced_site_label_bg_color = scene_props.site_label_bg_color
|
|
130
200
|
})
|
|
131
201
|
|
|
132
202
|
// Ensure site_label_offset is always available
|
|
@@ -638,15 +708,19 @@
|
|
|
638
708
|
scene_props.site_label_size = DEFAULTS.structure.site_label_size
|
|
639
709
|
scene_props.site_label_padding = DEFAULTS.structure.site_label_padding
|
|
640
710
|
scene_props.site_label_offset = [...DEFAULTS.structure.site_label_offset]
|
|
641
|
-
site_label_hex_color =
|
|
642
|
-
site_label_bg_hex_color =
|
|
643
|
-
site_label_background_opacity =
|
|
711
|
+
site_label_hex_color = default_site_label_color
|
|
712
|
+
site_label_bg_hex_color = default_site_label_bg.hex_color
|
|
713
|
+
site_label_background_opacity = default_site_label_bg.opacity
|
|
644
714
|
}}
|
|
645
715
|
>
|
|
646
716
|
<div class="pane-row">
|
|
647
717
|
<label>
|
|
648
718
|
Color
|
|
649
|
-
<input
|
|
719
|
+
<input
|
|
720
|
+
type="color"
|
|
721
|
+
aria-label="Site label color"
|
|
722
|
+
bind:value={site_label_hex_color}
|
|
723
|
+
/>
|
|
650
724
|
</label>
|
|
651
725
|
<label>
|
|
652
726
|
Size
|
|
@@ -662,7 +736,11 @@
|
|
|
662
736
|
<div class="pane-row">
|
|
663
737
|
<label>
|
|
664
738
|
Background
|
|
665
|
-
<input
|
|
739
|
+
<input
|
|
740
|
+
type="color"
|
|
741
|
+
aria-label="Site label background color"
|
|
742
|
+
bind:value={site_label_bg_hex_color}
|
|
743
|
+
/>
|
|
666
744
|
</label>
|
|
667
745
|
<label>
|
|
668
746
|
Opacity
|
|
@@ -671,6 +749,7 @@
|
|
|
671
749
|
min="0"
|
|
672
750
|
max="1"
|
|
673
751
|
step="0.01"
|
|
752
|
+
aria-label="Site label background opacity"
|
|
674
753
|
bind:value={site_label_background_opacity}
|
|
675
754
|
/>
|
|
676
755
|
<input
|
|
@@ -678,6 +757,7 @@
|
|
|
678
757
|
min="0"
|
|
679
758
|
max="1"
|
|
680
759
|
step="0.01"
|
|
760
|
+
aria-label="Site label background opacity slider"
|
|
681
761
|
bind:value={site_label_background_opacity}
|
|
682
762
|
/>
|
|
683
763
|
</label>
|
|
@@ -938,7 +1018,10 @@
|
|
|
938
1018
|
{/if}
|
|
939
1019
|
|
|
940
1020
|
{#if !supercell_input_valid}
|
|
941
|
-
<div
|
|
1021
|
+
<div
|
|
1022
|
+
data-testid="supercell-input-error"
|
|
1023
|
+
style="color: red; font-size: 0.8em; margin-top: 4pt"
|
|
1024
|
+
>
|
|
942
1025
|
Invalid format. Use patterns like "2x2x2", "3x1x2", or "2".
|
|
943
1026
|
</div>
|
|
944
1027
|
{/if}
|
|
@@ -1083,11 +1166,15 @@
|
|
|
1083
1166
|
title="Bonds"
|
|
1084
1167
|
current_values={{
|
|
1085
1168
|
bonding_strategy: scene_props.bonding_strategy,
|
|
1169
|
+
auto_bond_order: scene_props.auto_bond_order,
|
|
1170
|
+
aromatic_display: scene_props.aromatic_display,
|
|
1086
1171
|
bond_color: scene_props.bond_color,
|
|
1087
1172
|
bond_thickness: scene_props.bond_thickness,
|
|
1088
1173
|
}}
|
|
1089
1174
|
on_reset={() => {
|
|
1090
1175
|
scene_props.bonding_strategy = DEFAULTS.structure.bonding_strategy
|
|
1176
|
+
scene_props.auto_bond_order = DEFAULTS.structure.auto_bond_order
|
|
1177
|
+
scene_props.aromatic_display = DEFAULTS.structure.aromatic_display
|
|
1091
1178
|
scene_props.bond_color = DEFAULTS.structure.bond_color
|
|
1092
1179
|
scene_props.bond_thickness = DEFAULTS.structure.bond_thickness
|
|
1093
1180
|
}}
|
|
@@ -1104,6 +1191,33 @@
|
|
|
1104
1191
|
{/each}
|
|
1105
1192
|
</select>
|
|
1106
1193
|
</label>
|
|
1194
|
+
<label
|
|
1195
|
+
style="gap: 6pt"
|
|
1196
|
+
{@attach tooltip({
|
|
1197
|
+
content: SETTINGS_CONFIG.structure.auto_bond_order.description,
|
|
1198
|
+
})}
|
|
1199
|
+
>
|
|
1200
|
+
<input type="checkbox" bind:checked={scene_props.auto_bond_order} />
|
|
1201
|
+
Auto bond order (perceive double/triple/aromatic)
|
|
1202
|
+
</label>
|
|
1203
|
+
{#if scene_props.auto_bond_order}
|
|
1204
|
+
<label
|
|
1205
|
+
{@attach tooltip({
|
|
1206
|
+
content: SETTINGS_CONFIG.structure.aromatic_display.description,
|
|
1207
|
+
})}
|
|
1208
|
+
>
|
|
1209
|
+
Aromatic display <select bind:value={scene_props.aromatic_display}>
|
|
1210
|
+
{#each Object.entries(
|
|
1211
|
+
SETTINGS_CONFIG.structure.aromatic_display.enum ?? {},
|
|
1212
|
+
) as
|
|
1213
|
+
[value, label]
|
|
1214
|
+
(value)
|
|
1215
|
+
}
|
|
1216
|
+
<option {value}>{label}</option>
|
|
1217
|
+
{/each}
|
|
1218
|
+
</select>
|
|
1219
|
+
</label>
|
|
1220
|
+
{/if}
|
|
1107
1221
|
<label>
|
|
1108
1222
|
Color <input type="color" bind:value={scene_props.bond_color} />
|
|
1109
1223
|
</label>
|
|
@@ -26,6 +26,6 @@ type $$ComponentProps = Omit<ComponentProps<typeof DraggablePane>, `children`> &
|
|
|
26
26
|
pane_props?: ComponentProps<typeof DraggablePane>[`pane_props`];
|
|
27
27
|
toggle_props?: ComponentProps<typeof DraggablePane>[`toggle_props`];
|
|
28
28
|
};
|
|
29
|
-
declare const StructureControls: import("svelte").Component<$$ComponentProps, {}, "controls_open" | "color_scheme" | "background_color" | "background_opacity" | "show_image_atoms" | "atom_color_config" | "
|
|
29
|
+
declare const StructureControls: import("svelte").Component<$$ComponentProps, {}, "controls_open" | "color_scheme" | "background_color" | "background_opacity" | "show_image_atoms" | "atom_color_config" | "lattice_props" | "volumetric_data" | "isosurface_settings" | "supercell_scaling" | "cell_type" | "active_volume_idx" | "scene_props" | "supercell_loading">;
|
|
30
30
|
type StructureControls = ReturnType<typeof StructureControls>;
|
|
31
31
|
export default StructureControls;
|