matterviz 0.1.12 → 0.1.13

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.
Files changed (195) hide show
  1. package/dist/ContextMenu.svelte +1 -1
  2. package/dist/ContextMenu.svelte.d.ts +3 -3
  3. package/dist/DraggablePane.svelte +10 -5
  4. package/dist/DraggablePane.svelte.d.ts +3 -3
  5. package/dist/FilePicker.svelte.d.ts +3 -3
  6. package/dist/Icon.svelte.d.ts +3 -3
  7. package/dist/Nav.svelte +76 -31
  8. package/dist/SettingsSection.svelte.d.ts +3 -3
  9. package/dist/Spinner.svelte +18 -29
  10. package/dist/Spinner.svelte.d.ts +3 -3
  11. package/dist/app.css +4 -0
  12. package/dist/bands/Bands.svelte +216 -0
  13. package/dist/bands/Bands.svelte.d.ts +16 -0
  14. package/dist/bands/BandsAndDos.svelte +26 -0
  15. package/dist/bands/BandsAndDos.svelte.d.ts +16 -0
  16. package/dist/bands/Dos.svelte +124 -0
  17. package/dist/bands/Dos.svelte.d.ts +18 -0
  18. package/dist/bands/helpers.d.ts +11 -0
  19. package/dist/bands/helpers.js +195 -0
  20. package/dist/bands/index.d.ts +5 -0
  21. package/dist/bands/index.js +6 -0
  22. package/dist/bands/types.d.ts +54 -0
  23. package/dist/bands/types.js +2 -0
  24. package/dist/brillouin/BrillouinZone.svelte +373 -0
  25. package/dist/brillouin/BrillouinZone.svelte.d.ts +57 -0
  26. package/dist/brillouin/BrillouinZoneControls.svelte +88 -0
  27. package/dist/brillouin/BrillouinZoneControls.svelte.d.ts +14 -0
  28. package/dist/brillouin/BrillouinZoneExportPane.svelte +129 -0
  29. package/dist/brillouin/BrillouinZoneExportPane.svelte.d.ts +15 -0
  30. package/dist/brillouin/BrillouinZoneInfoPane.svelte +115 -0
  31. package/dist/brillouin/BrillouinZoneInfoPane.svelte.d.ts +13 -0
  32. package/dist/brillouin/BrillouinZoneScene.svelte +180 -0
  33. package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +34 -0
  34. package/dist/brillouin/compute.d.ts +7 -0
  35. package/dist/brillouin/compute.js +215 -0
  36. package/dist/brillouin/index.d.ts +7 -0
  37. package/dist/brillouin/index.js +7 -0
  38. package/dist/brillouin/types.d.ts +23 -0
  39. package/dist/brillouin/types.js +1 -0
  40. package/dist/composition/BarChart.svelte.d.ts +3 -3
  41. package/dist/composition/BubbleChart.svelte +7 -6
  42. package/dist/composition/BubbleChart.svelte.d.ts +3 -3
  43. package/dist/composition/Composition.svelte.d.ts +3 -3
  44. package/dist/composition/PieChart.svelte +1 -1
  45. package/dist/composition/PieChart.svelte.d.ts +3 -3
  46. package/dist/composition/parse.d.ts +3 -3
  47. package/dist/coordination/CoordinationBarPlot.svelte +241 -0
  48. package/dist/coordination/CoordinationBarPlot.svelte.d.ts +29 -0
  49. package/dist/coordination/calc-coordination.d.ts +15 -0
  50. package/dist/coordination/calc-coordination.js +66 -0
  51. package/dist/coordination/index.d.ts +8 -0
  52. package/dist/coordination/index.js +7 -0
  53. package/dist/element/BohrAtom.svelte.d.ts +3 -3
  54. package/dist/element/ElementHeading.svelte.d.ts +3 -3
  55. package/dist/element/ElementPhoto.svelte.d.ts +3 -3
  56. package/dist/element/ElementStats.svelte.d.ts +3 -3
  57. package/dist/element/ElementTile.svelte +1 -1
  58. package/dist/element/ElementTile.svelte.d.ts +4 -4
  59. package/dist/element/Nucleus.svelte.d.ts +3 -3
  60. package/dist/icons.d.ts +4 -0
  61. package/dist/icons.js +4 -0
  62. package/dist/index.d.ts +5 -1
  63. package/dist/index.js +5 -1
  64. package/dist/io/export.d.ts +1 -6
  65. package/dist/io/export.js +12 -12
  66. package/dist/labels.d.ts +19 -0
  67. package/dist/labels.js +59 -0
  68. package/dist/math.d.ts +7 -2
  69. package/dist/math.js +24 -12
  70. package/dist/periodic-table/PeriodicTable.svelte.d.ts +3 -3
  71. package/dist/periodic-table/PeriodicTableControls.svelte.d.ts +3 -3
  72. package/dist/periodic-table/PropertySelect.svelte +1 -1
  73. package/dist/periodic-table/PropertySelect.svelte.d.ts +5 -4
  74. package/dist/periodic-table/TableInset.svelte.d.ts +3 -3
  75. package/dist/phase-diagram/PhaseDiagram2D.svelte +44 -107
  76. package/dist/phase-diagram/PhaseDiagram2D.svelte.d.ts +7 -27
  77. package/dist/phase-diagram/PhaseDiagram3D.svelte +67 -170
  78. package/dist/phase-diagram/PhaseDiagram3D.svelte.d.ts +7 -31
  79. package/dist/phase-diagram/PhaseDiagram4D.svelte +87 -145
  80. package/dist/phase-diagram/PhaseDiagram4D.svelte.d.ts +5 -28
  81. package/dist/phase-diagram/PhaseDiagramControls.svelte +9 -9
  82. package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +6 -6
  83. package/dist/phase-diagram/PhaseDiagramInfoPane.svelte +4 -4
  84. package/dist/phase-diagram/PhaseDiagramInfoPane.svelte.d.ts +5 -5
  85. package/dist/phase-diagram/PhaseDiagramStats.svelte +9 -16
  86. package/dist/phase-diagram/PhaseDiagramStats.svelte.d.ts +3 -3
  87. package/dist/phase-diagram/StructurePopup.svelte.d.ts +3 -3
  88. package/dist/phase-diagram/barycentric-coords.js +13 -24
  89. package/dist/phase-diagram/helpers.d.ts +12 -14
  90. package/dist/phase-diagram/helpers.js +70 -23
  91. package/dist/phase-diagram/index.d.ts +58 -0
  92. package/dist/phase-diagram/index.js +44 -0
  93. package/dist/phase-diagram/thermodynamics.js +2 -3
  94. package/dist/phase-diagram/types.d.ts +3 -8
  95. package/dist/plot/BarPlot.svelte +474 -240
  96. package/dist/plot/BarPlot.svelte.d.ts +16 -37
  97. package/dist/plot/BarPlotControls.svelte +33 -201
  98. package/dist/plot/BarPlotControls.svelte.d.ts +11 -22
  99. package/dist/plot/ColorBar.svelte.d.ts +3 -3
  100. package/dist/plot/ColorScaleSelect.svelte +1 -1
  101. package/dist/plot/ColorScaleSelect.svelte.d.ts +3 -3
  102. package/dist/plot/ElementScatter.svelte +5 -5
  103. package/dist/plot/ElementScatter.svelte.d.ts +8 -8
  104. package/dist/plot/Histogram.svelte +318 -170
  105. package/dist/plot/Histogram.svelte.d.ts +21 -41
  106. package/dist/plot/HistogramControls.svelte +115 -347
  107. package/dist/plot/HistogramControls.svelte.d.ts +13 -28
  108. package/dist/plot/Line.svelte +1 -1
  109. package/dist/plot/Line.svelte.d.ts +4 -4
  110. package/dist/plot/PlotControls.svelte +280 -0
  111. package/dist/plot/PlotControls.svelte.d.ts +4 -0
  112. package/dist/plot/PlotLegend.svelte +9 -24
  113. package/dist/plot/PlotLegend.svelte.d.ts +3 -3
  114. package/dist/plot/ScatterPlot.svelte +536 -571
  115. package/dist/plot/ScatterPlot.svelte.d.ts +18 -90
  116. package/dist/plot/ScatterPlotControls.svelte +108 -316
  117. package/dist/plot/ScatterPlotControls.svelte.d.ts +11 -38
  118. package/dist/plot/ScatterPoint.svelte +1 -1
  119. package/dist/plot/ScatterPoint.svelte.d.ts +5 -5
  120. package/dist/plot/SpacegroupBarPlot.svelte +235 -0
  121. package/dist/plot/SpacegroupBarPlot.svelte.d.ts +9 -0
  122. package/dist/plot/data-transform.d.ts +2 -2
  123. package/dist/plot/data-transform.js +1 -5
  124. package/dist/plot/index.d.ts +12 -200
  125. package/dist/plot/index.js +14 -6
  126. package/dist/plot/layout.d.ts +10 -16
  127. package/dist/plot/layout.js +40 -25
  128. package/dist/plot/scales.d.ts +7 -8
  129. package/dist/plot/scales.js +22 -7
  130. package/dist/plot/types.d.ts +303 -0
  131. package/dist/plot/types.js +1 -0
  132. package/dist/rdf/RdfPlot.svelte +156 -0
  133. package/dist/rdf/RdfPlot.svelte.d.ts +20 -0
  134. package/dist/rdf/calc-rdf.d.ts +4 -0
  135. package/dist/rdf/calc-rdf.js +107 -0
  136. package/dist/rdf/index.d.ts +22 -0
  137. package/dist/rdf/index.js +2 -0
  138. package/dist/settings.d.ts +86 -75
  139. package/dist/settings.js +257 -155
  140. package/dist/structure/Bond.svelte +126 -110
  141. package/dist/structure/Bond.svelte.d.ts +7 -20
  142. package/dist/structure/CanvasTooltip.svelte.d.ts +4 -4
  143. package/dist/structure/Cylinder.svelte +29 -0
  144. package/dist/structure/Cylinder.svelte.d.ts +10 -0
  145. package/dist/structure/Lattice.svelte +1 -2
  146. package/dist/structure/Lattice.svelte.d.ts +3 -3
  147. package/dist/structure/Structure.svelte +23 -8
  148. package/dist/structure/Structure.svelte.d.ts +10 -7
  149. package/dist/structure/StructureControls.svelte +25 -134
  150. package/dist/structure/StructureControls.svelte.d.ts +4 -9
  151. package/dist/structure/StructureExportPane.svelte +188 -0
  152. package/dist/structure/StructureExportPane.svelte.d.ts +17 -0
  153. package/dist/structure/StructureInfoPane.svelte.d.ts +3 -3
  154. package/dist/structure/StructureLegend.svelte +91 -27
  155. package/dist/structure/StructureLegend.svelte.d.ts +5 -4
  156. package/dist/structure/StructureScene.svelte +93 -100
  157. package/dist/structure/StructureScene.svelte.d.ts +15 -12
  158. package/dist/structure/Vector.svelte +19 -25
  159. package/dist/structure/Vector.svelte.d.ts +3 -3
  160. package/dist/structure/bonding.d.ts +16 -14
  161. package/dist/structure/bonding.js +207 -216
  162. package/dist/structure/export.d.ts +3 -0
  163. package/dist/structure/export.js +120 -0
  164. package/dist/structure/index.d.ts +15 -1
  165. package/dist/structure/index.js +2 -0
  166. package/dist/symmetry/WyckoffTable.svelte.d.ts +3 -3
  167. package/dist/symmetry/index.d.ts +1 -0
  168. package/dist/symmetry/index.js +1 -0
  169. package/dist/symmetry/spacegroups.d.ts +9 -0
  170. package/dist/symmetry/spacegroups.js +397 -0
  171. package/dist/theme/ThemeControl.svelte.d.ts +3 -3
  172. package/dist/theme/themes.js +2 -16
  173. package/dist/trajectory/Trajectory.svelte +52 -78
  174. package/dist/trajectory/Trajectory.svelte.d.ts +5 -5
  175. package/dist/trajectory/TrajectoryError.svelte.d.ts +3 -3
  176. package/dist/trajectory/TrajectoryExportPane.svelte +1 -1
  177. package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +5 -5
  178. package/dist/trajectory/TrajectoryInfoPane.svelte +1 -1
  179. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +3 -3
  180. package/dist/trajectory/extract.js +2 -1
  181. package/dist/trajectory/index.d.ts +1 -1
  182. package/dist/trajectory/index.js +1 -1
  183. package/dist/trajectory/parse.js +21 -14
  184. package/dist/utils.d.ts +1 -0
  185. package/dist/utils.js +17 -0
  186. package/dist/xrd/XrdPlot.svelte +14 -11
  187. package/dist/xrd/XrdPlot.svelte.d.ts +6 -5
  188. package/package.json +20 -12
  189. package/readme.md +1 -1
  190. package/dist/material/MaterialCard.svelte +0 -123
  191. package/dist/material/MaterialCard.svelte.d.ts +0 -9
  192. package/dist/material/index.d.ts +0 -1
  193. package/dist/material/index.js +0 -1
  194. package/dist/plot/formatting.d.ts +0 -7
  195. package/dist/plot/formatting.js +0 -53
@@ -3,18 +3,17 @@ import { format_num } from '../labels';
3
3
  import * as math from '../math';
4
4
  import { DEFAULTS } from '../settings';
5
5
  import { colors } from '../state.svelte';
6
- import { Bond, get_center_of_mass, Lattice, Vector } from './';
6
+ import { Cylinder, get_center_of_mass, Lattice, Vector } from './';
7
7
  import { angle_between_vectors, displacement_pbc, distance_pbc, MAX_SELECTED_SITES, } from './measure';
8
- import { T } from '@threlte/core';
9
- import * as Extras from '@threlte/extras';
8
+ import { T, useThrelte } from '@threlte/core';
9
+ import * as extras from '@threlte/extras';
10
10
  import { untrack } from 'svelte';
11
11
  import { SvelteMap } from 'svelte/reactivity';
12
+ import Bond from './Bond.svelte';
12
13
  import { BONDING_STRATEGIES } from './bonding';
13
14
  import { CanvasTooltip } from './index';
14
- // Add pulsating animation for selected sites
15
15
  let pulse_time = $state(0);
16
16
  let pulse_opacity = $derived(0.15 + 0.25 * Math.sin(pulse_time * 5));
17
- // Update pulse time for animation
18
17
  $effect(() => {
19
18
  if (!selected_sites?.length && !active_sites?.length)
20
19
  return;
@@ -31,15 +30,18 @@ $effect(() => {
31
30
  frame_id = requestAnimationFrame(animate);
32
31
  return () => cancelAnimationFrame(frame_id);
33
32
  });
34
- let { structure = undefined, atom_radius = DEFAULTS.structure.atom_radius, same_size_atoms = false, camera_position = DEFAULTS.structure.camera_position, camera_projection = DEFAULTS.structure.camera_projection, rotation_damping = DEFAULTS.structure.rotation_damping, max_zoom = DEFAULTS.structure.max_zoom, min_zoom = DEFAULTS.structure.min_zoom, zoom_speed = DEFAULTS.structure.zoom_speed, pan_speed = DEFAULTS.structure.pan_speed, show_atoms = DEFAULTS.structure.show_atoms, show_bonds = DEFAULTS.structure.show_bonds, show_site_labels = DEFAULTS.structure.show_site_labels, show_site_indices = DEFAULTS.structure.show_site_indices, site_label_size = DEFAULTS.structure.site_label_size, site_label_offset = $bindable(DEFAULTS.structure.site_label_offset), site_label_bg_color = `color-mix(in srgb, #000000 0%, transparent)`, site_label_color = `#ffffff`, site_label_padding = 3, show_force_vectors = DEFAULTS.structure.show_force_vectors, force_scale = DEFAULTS.structure.force_scale, force_color = DEFAULTS.structure.force_color, gizmo = DEFAULTS.structure.show_gizmo, hovered_idx = $bindable(null), hovered_site = $bindable(null), float_fmt = `.3~f`, auto_rotate = DEFAULTS.structure.auto_rotate, bond_thickness = DEFAULTS.structure.bond_thickness, bond_color = DEFAULTS.structure.bond_color, bonding_strategy = DEFAULTS.structure.bonding_strategy, bonding_options = {}, fov = DEFAULTS.structure.fov, initial_zoom = DEFAULTS.structure.initial_zoom, ambient_light = DEFAULTS.structure.ambient_light, directional_light = DEFAULTS.structure.directional_light, sphere_segments = DEFAULTS.structure.sphere_segments, lattice_props = {}, atom_label, camera_is_moving = $bindable(false), orbit_controls = $bindable(undefined), width = 0, height = 0, measure_mode = `distance`, selected_sites = $bindable([]), measured_sites = $bindable([]), selection_highlight_color = `#6cf0ff`,
33
+ let { structure = undefined, atom_radius = DEFAULTS.structure.atom_radius, same_size_atoms = false, camera_position = DEFAULTS.structure.camera_position, camera_projection = DEFAULTS.structure.camera_projection, rotation_damping = DEFAULTS.structure.rotation_damping, max_zoom = DEFAULTS.structure.max_zoom, min_zoom = DEFAULTS.structure.min_zoom, zoom_speed = DEFAULTS.structure.zoom_speed, pan_speed = DEFAULTS.structure.pan_speed, show_atoms = DEFAULTS.structure.show_atoms, show_bonds = DEFAULTS.structure.show_bonds, show_site_labels = DEFAULTS.structure.show_site_labels, show_site_indices = DEFAULTS.structure.show_site_indices, site_label_size = DEFAULTS.structure.site_label_size, site_label_offset = $bindable(DEFAULTS.structure.site_label_offset), site_label_bg_color = `color-mix(in srgb, #000000 0%, transparent)`, site_label_color = `#ffffff`, site_label_padding = 3, show_force_vectors = DEFAULTS.structure.show_force_vectors, force_scale = DEFAULTS.structure.force_scale, force_color = DEFAULTS.structure.force_color, gizmo = DEFAULTS.structure.show_gizmo, hovered_idx = $bindable(null), hovered_site = $bindable(null), float_fmt = `.3~f`, auto_rotate = DEFAULTS.structure.auto_rotate, bond_thickness = DEFAULTS.structure.bond_thickness, bond_color = DEFAULTS.structure.bond_color, bonding_strategy = DEFAULTS.structure.bonding_strategy, bonding_options = {}, fov = DEFAULTS.structure.fov, initial_zoom = DEFAULTS.structure.initial_zoom, ambient_light = DEFAULTS.structure.ambient_light, directional_light = DEFAULTS.structure.directional_light, sphere_segments = DEFAULTS.structure.sphere_segments, lattice_props = {}, atom_label, camera_is_moving = $bindable(false), width = 0, height = 0, measure_mode = `distance`, selected_sites = $bindable([]), measured_sites = $bindable([]), selection_highlight_color = `#6cf0ff`,
35
34
  // Active highlight group with different color
36
- active_sites = $bindable([]), active_highlight_color = `var(--struct-active-highlight-color, #2563eb)`, rotation = DEFAULTS.structure.rotation, } = $props();
35
+ active_sites = $bindable([]), active_highlight_color = `var(--struct-active-highlight-color, #2563eb)`, rotation = DEFAULTS.structure.rotation, scene = $bindable(undefined), camera = $bindable(undefined), hidden_elements = $bindable(new Set()), } = $props();
36
+ const threlte = useThrelte();
37
+ $effect(() => {
38
+ scene = threlte.scene;
39
+ camera = threlte.camera.current;
40
+ });
37
41
  let bond_pairs = $state([]);
38
42
  let active_tooltip = $state(null);
39
- let hovered_bond_data = $state(null);
40
43
  function toggle_selection(site_index, evt) {
41
44
  evt?.stopPropagation?.();
42
- // Check if adding this site would exceed the soft cap
43
45
  if (!measured_sites.includes(site_index) &&
44
46
  measured_sites.length >= MAX_SELECTED_SITES) {
45
47
  console.warn(`Selection size limit reached (${MAX_SELECTED_SITES}). Deselect some sites first.`);
@@ -48,12 +50,10 @@ function toggle_selection(site_index, evt) {
48
50
  measured_sites = measured_sites.includes(site_index)
49
51
  ? measured_sites.filter((idx) => idx !== site_index)
50
52
  : [...measured_sites, site_index];
51
- // keep selected_sites in sync with user clicks
52
53
  selected_sites = selected_sites.includes(site_index)
53
54
  ? selected_sites.filter((idx) => idx !== site_index)
54
55
  : [...selected_sites, site_index];
55
56
  }
56
- // Keep measured site selection valid across structure changes (new structure might have fewer sites)
57
57
  $effect(() => {
58
58
  const count = structure?.sites?.length ?? 0;
59
59
  if (count <= 0) {
@@ -64,25 +64,21 @@ $effect(() => {
64
64
  measured_sites = measured_sites.filter((idx) => idx >= 0 && idx < count);
65
65
  });
66
66
  });
67
- Extras.interactivity();
67
+ extras.interactivity();
68
68
  $effect.pre(() => {
69
69
  hovered_site = structure?.sites?.[hovered_idx ?? -1] ?? null;
70
70
  });
71
71
  let lattice = $derived(structure && `lattice` in structure ? structure.lattice : null);
72
- // Get rotation target: cell center for crystalline structures, center of mass for molecular systems
73
72
  let rotation_target = $derived(lattice
74
73
  ? math.scale(math.add(...lattice.matrix), 0.5)
75
74
  : structure
76
75
  ? get_center_of_mass(structure)
77
76
  : [0, 0, 0]);
78
- // Calculate structure size for camera setup
79
77
  let structure_size = $derived(lattice ? (lattice.a + lattice.b + lattice.c) / 2 : 10);
80
- // Persisted zoom; recompute only on viewport changes
81
78
  let computed_zoom = $state(initial_zoom);
82
79
  $effect(() => {
83
80
  if (!(width > 0) || !(height > 0))
84
81
  return;
85
- // Avoid depending on structure/structure_size so trajectories don't retrigger zoom
86
82
  const structure_max_dim = Math.max(1, untrack(() => structure_size));
87
83
  const viewer_min_dim = Math.min(width, height);
88
84
  const scale_factor = viewer_min_dim / (structure_max_dim * 50); // 50px per unit
@@ -94,13 +90,12 @@ $effect(() => {
94
90
  computed_zoom = new_zoom;
95
91
  });
96
92
  $effect.pre(() => {
97
- // Simple initial camera auto-position: proportional to structure size and fov
98
93
  if (camera_position.every((v) => v === 0) && structure) {
99
94
  const distance = Math.max(1, structure_size) * (60 / fov);
100
95
  camera_position = [distance, distance * 0.3, distance * 0.8];
101
96
  }
102
97
  });
103
- $effect.pre(() => {
98
+ $effect(() => {
104
99
  if (structure && show_bonds !== `never`) {
105
100
  // Determine if we should show bonds based on the setting and structure type
106
101
  const should_show_bonds = show_bonds === `always` ||
@@ -115,23 +110,15 @@ $effect.pre(() => {
115
110
  else
116
111
  bond_pairs = [];
117
112
  });
118
- // Update orbit controls when switching between camera projections to ensure proper centering
119
- $effect(() => {
120
- if (orbit_controls && camera_projection) {
121
- queueMicrotask(() => {
122
- orbit_controls.target.set(...rotation_target); // Structure is positioned with rotation_target as the center
123
- orbit_controls.update();
124
- });
125
- }
126
- });
127
113
  let atom_data = $derived.by(() => {
128
114
  if (!show_atoms || !structure?.sites)
129
115
  return [];
130
- // Build atom data with partial occupancy handling
131
116
  return structure.sites.flatMap((site, site_idx) => {
132
117
  const radius = same_size_atoms ? atom_radius : site.species.reduce((sum, spec) => sum + spec.occu * (atomic_radii[spec.element] ?? 1), 0) * atom_radius;
133
118
  let start_angle = 0;
134
- return site.species.map(({ element, occu }) => ({
119
+ return site.species
120
+ .filter(({ element }) => !hidden_elements?.has(element))
121
+ .map(({ element, occu }) => ({
135
122
  site_idx,
136
123
  element,
137
124
  occupancy: occu,
@@ -144,6 +131,42 @@ let atom_data = $derived.by(() => {
144
131
  }));
145
132
  });
146
133
  });
134
+ let filtered_bond_pairs = $derived.by(() => {
135
+ if (!structure?.sites || hidden_elements.size === 0)
136
+ return bond_pairs;
137
+ return bond_pairs.filter((bond) => {
138
+ const site_1 = structure.sites[bond.site_idx_1];
139
+ const site_2 = structure.sites[bond.site_idx_2];
140
+ const site_1_visible = site_1?.species.some(({ element }) => !hidden_elements.has(element));
141
+ const site_2_visible = site_2?.species.some(({ element }) => !hidden_elements.has(element));
142
+ return site_1_visible && site_2_visible;
143
+ });
144
+ });
145
+ let instanced_bond_groups = $derived.by(() => {
146
+ if (!structure?.sites || filtered_bond_pairs.length === 0)
147
+ return [];
148
+ const group = {
149
+ thickness: bond_thickness,
150
+ ambient_light,
151
+ directional_light,
152
+ instances: [],
153
+ };
154
+ for (const bond_data of filtered_bond_pairs) {
155
+ const site_a = structure.sites[bond_data.site_idx_1];
156
+ const site_b = structure.sites[bond_data.site_idx_2];
157
+ const get_majority_color = (site) => {
158
+ if (!site?.species || site.species.length === 0)
159
+ return bond_color;
160
+ const majority_species = site.species.reduce((max, spec) => spec.occu > max.occu ? spec : max);
161
+ return colors.element?.[majority_species.element] || bond_color;
162
+ };
163
+ const color_start = get_majority_color(site_a);
164
+ const color_end = get_majority_color(site_b);
165
+ const instance = { matrix: bond_data.transform_matrix, color_start, color_end };
166
+ group.instances.push(instance);
167
+ }
168
+ return group.instances.length > 0 ? [group] : [];
169
+ });
147
170
  let radius_by_site_idx = $derived.by(() => {
148
171
  const map = new SvelteMap();
149
172
  for (const atom of atom_data) {
@@ -179,7 +202,6 @@ let instanced_atom_groups = $derived(Object.values(atom_data
179
202
  bucket.atoms.push(atom);
180
203
  return groups;
181
204
  }, {})));
182
- // Pre-calculate unique site atoms for labeling
183
205
  let unique_instanced_atoms = $derived(Object.values(instanced_atom_groups
184
206
  .flatMap((group) => group.atoms)
185
207
  .reduce((acc, atom) => {
@@ -209,7 +231,6 @@ let gizmo_props = $derived.by(() => {
209
231
  };
210
232
  });
211
233
  let orbit_controls_props = $derived({
212
- 'bind:ref': orbit_controls,
213
234
  position: [0, 0, 0],
214
235
  enableZoom: zoom_speed > 0,
215
236
  zoomSpeed: camera_projection === `orthographic` ? zoom_speed * 2 : zoom_speed,
@@ -230,7 +251,6 @@ let orbit_controls_props = $derived({
230
251
  camera_is_moving = false;
231
252
  },
232
253
  });
233
- // Theme-aware measurement line color from CSS variable
234
254
  let measure_line_color = $derived.by(() => {
235
255
  if (typeof window === `undefined`)
236
256
  return;
@@ -240,10 +260,18 @@ let measure_line_color = $derived.by(() => {
240
260
  });
241
261
  </script>
242
262
 
263
+ {#snippet bond_instanced_mesh_snippet(
264
+ group: ComponentProps<typeof Bond>[`group`],
265
+ )}
266
+ {#key group.instances.length}
267
+ <Bond {group} />
268
+ {/key}
269
+ {/snippet}
270
+
243
271
  {#snippet site_label_snippet(position: Vec3, site_idx: number)}
244
272
  {@const site = structure!.sites[site_idx]}
245
273
  {@const pos = math.add(position, site_label_offset)}
246
- <Extras.HTML center position={pos}>
274
+ <extras.HTML center position={pos}>
247
275
  {#if atom_label}
248
276
  {@render atom_label(site, site_idx)}
249
277
  {:else}
@@ -281,14 +309,14 @@ let measure_line_color = $derived.by(() => {
281
309
  {/if}
282
310
  </span>
283
311
  {/if}
284
- </Extras.HTML>
312
+ </extras.HTML>
285
313
  {/snippet}
286
314
 
287
315
  {#if camera_projection === `perspective`}
288
316
  <T.PerspectiveCamera makeDefault position={camera_position} {fov}>
289
- <Extras.OrbitControls {...orbit_controls_props}>
290
- {#if gizmo}<Extras.Gizmo {...gizmo_props} />{/if}
291
- </Extras.OrbitControls>
317
+ <extras.OrbitControls {...orbit_controls_props}>
318
+ {#if gizmo}<extras.Gizmo {...gizmo_props} />{/if}
319
+ </extras.OrbitControls>
292
320
  </T.PerspectiveCamera>
293
321
  {:else}
294
322
  <T.OrthographicCamera
@@ -297,9 +325,9 @@ let measure_line_color = $derived.by(() => {
297
325
  zoom={computed_zoom}
298
326
  near={-100}
299
327
  >
300
- <Extras.OrbitControls {...orbit_controls_props}>
301
- {#if gizmo}<Extras.Gizmo {...gizmo_props} />{/if}
302
- </Extras.OrbitControls>
328
+ <extras.OrbitControls {...orbit_controls_props}>
329
+ {#if gizmo}<extras.Gizmo {...gizmo_props} />{/if}
330
+ </extras.OrbitControls>
303
331
  </T.OrthographicCamera>
304
332
  {/if}
305
333
 
@@ -313,20 +341,19 @@ let measure_line_color = $derived.by(() => {
313
341
  {#if show_atoms}
314
342
  <!-- Instanced rendering for full occupancy atoms -->
315
343
  {#each instanced_atom_groups as group (group.element + group.radius)}
316
- <Extras.InstancedMesh
344
+ <extras.InstancedMesh
317
345
  key="{group.element}-{group.radius}-{group.atoms.length}"
318
346
  range={group.atoms.length}
319
347
  >
320
348
  <T.SphereGeometry args={[0.5, sphere_segments, sphere_segments]} />
321
349
  <T.MeshStandardMaterial color={group.color} />
322
350
  {#each group.atoms as atom (atom.site_idx)}
323
- <Extras.Instance
351
+ <extras.Instance
324
352
  position={atom.position}
325
353
  scale={atom.radius}
326
354
  onpointerenter={() => {
327
355
  hovered_idx = atom.site_idx
328
356
  active_tooltip = `atom`
329
- hovered_bond_data = null
330
357
  }}
331
358
  onpointerleave={() => {
332
359
  hovered_idx = null
@@ -338,7 +365,7 @@ let measure_line_color = $derived.by(() => {
338
365
  }}
339
366
  />
340
367
  {/each}
341
- </Extras.InstancedMesh>
368
+ </extras.InstancedMesh>
342
369
  {/each}
343
370
 
344
371
  <!-- Regular rendering for partial occupancy atoms -->
@@ -352,7 +379,6 @@ let measure_line_color = $derived.by(() => {
352
379
  onpointerenter={() => {
353
380
  hovered_idx = atom.site_idx
354
381
  active_tooltip = `atom`
355
- hovered_bond_data = null
356
382
  }}
357
383
  onpointerleave={() => {
358
384
  hovered_idx = null
@@ -409,34 +435,10 @@ let measure_line_color = $derived.by(() => {
409
435
  {/each}
410
436
  {/if}
411
437
 
412
- {#if bond_pairs.length > 0}
413
- {#each bond_pairs as bond_data (JSON.stringify(bond_data))}
414
- {@const site_a = structure?.sites[bond_data.site_idx_1]}
415
- {@const site_b = structure?.sites[bond_data.site_idx_2]}
416
- {@const get_majority_color = (site: typeof site_a) => {
417
- if (!site?.species || site.species.length === 0) return bond_color
418
- // Find species with highest occupancy
419
- const majority_species = site.species.reduce((max, spec) =>
420
- spec.occu > max.occu ? spec : max
421
- )
422
- return colors.element?.[majority_species.element] || bond_color
423
- }}
424
- {@const from_color = get_majority_color(site_a)}
425
- {@const to_color = get_majority_color(site_b)}
426
- <Bond
427
- from={bond_data.pos_1}
428
- to={bond_data.pos_2}
429
- thickness={bond_thickness}
430
- {from_color}
431
- {to_color}
432
- color={bond_color}
433
- {bond_data}
434
- {bonding_strategy}
435
- {active_tooltip}
436
- {hovered_bond_data}
437
- onbondhover={(data: BondPair | null) => hovered_bond_data = data}
438
- ontooltipchange={(type: `atom` | `bond` | null) => active_tooltip = type}
439
- />
438
+ <!-- Instanced bond rendering with gradient colors -->
439
+ {#if instanced_bond_groups.length > 0}
440
+ {#each instanced_bond_groups as group (group.thickness + group.instances.length)}
441
+ {@render bond_instanced_mesh_snippet(group)}
440
442
  {/each}
441
443
  {/if}
442
444
 
@@ -504,9 +506,9 @@ let measure_line_color = $derived.by(() => {
504
506
  <!-- shift selected site labels down to avoid overlapping regular site labels-->
505
507
  {@const selection_offset = math.add(site_label_offset, [0, -0.5, 0])}
506
508
  {@const pos = math.add(site.xyz, selection_offset) as Vec3}
507
- <Extras.HTML center position={pos}>
509
+ <extras.HTML center position={pos}>
508
510
  <span class="selection-label">{loop_idx + 1}</span>
509
- </Extras.HTML>
511
+ </extras.HTML>
510
512
  {/if}
511
513
  {/each}
512
514
  {/if}
@@ -558,7 +560,12 @@ let measure_line_color = $derived.by(() => {
558
560
  {@const site_j = structure.sites[idx_j]}
559
561
  {@const pos_i = site_i.xyz}
560
562
  {@const pos_j = site_j.xyz}
561
- <Bond from={pos_i} to={pos_j} thickness={0.12} color={measure_line_color} />
563
+ <Cylinder
564
+ from={pos_i}
565
+ to={pos_j}
566
+ thickness={0.12}
567
+ color={measure_line_color}
568
+ />
562
569
  {@const midpoint = [
563
570
  (pos_i[0] + pos_j[0]) / 2,
564
571
  (pos_i[1] + pos_j[1]) / 2,
@@ -567,7 +574,7 @@ let measure_line_color = $derived.by(() => {
567
574
  {@const direct = math.euclidean_dist(pos_i, pos_j)}
568
575
  {@const pbc = lattice ? distance_pbc(pos_i, pos_j, lattice.matrix) : direct}
569
576
  {@const differ = lattice ? Math.abs(pbc - direct) > 1e-6 : false}
570
- <Extras.HTML center position={midpoint}>
577
+ <extras.HTML center position={midpoint}>
571
578
  <span class="measure-label">
572
579
  {#if differ}
573
580
  PBC: {format_num(pbc, float_fmt)} Å<br /><small>
@@ -576,7 +583,7 @@ let measure_line_color = $derived.by(() => {
576
583
  {format_num(pbc, float_fmt)} Å
577
584
  {/if}
578
585
  </span>
579
- </Extras.HTML>
586
+ </extras.HTML>
580
587
  {/each}
581
588
  {/each}
582
589
  {:else if measure_mode === `angle` && measured_sites.length >= 3}
@@ -600,39 +607,25 @@ let measure_line_color = $derived.by(() => {
600
607
  {@const angle_deg = angle_between_vectors(v1, v2, `degrees`)}
601
608
  {#if n1 > math.EPS && n2 > math.EPS}
602
609
  <!-- draw rays from center to the two sites -->
603
- <Bond
610
+ <Cylinder
604
611
  from={center.xyz}
605
612
  to={site_a.xyz}
606
613
  thickness={0.05}
607
614
  color={measure_line_color}
608
615
  />
609
- <Bond
616
+ <Cylinder
610
617
  from={center.xyz}
611
618
  to={site_b.xyz}
612
619
  thickness={0.05}
613
620
  color={measure_line_color}
614
621
  />
615
- {@const bisector = [
616
- v1[0] / n1 + v2[0] / n2,
617
- v1[1] / n1 + v2[1] / n2,
618
- v1[2] / n1 + v2[2] / n2,
619
- ] as Vec3}
620
- {@const bis_norm =
621
- Math.sqrt(bisector[0] ** 2 + bisector[1] ** 2 + bisector[2] ** 2) ||
622
- 1}
623
- {@const offset_dir = [
624
- bisector[0] / bis_norm,
625
- bisector[1] / bis_norm,
626
- bisector[2] / bis_norm,
627
- ] as Vec3}
628
- {@const label_pos = [
629
- center.xyz[0] + offset_dir[0] * 0.6,
630
- center.xyz[1] + offset_dir[1] * 0.6,
631
- center.xyz[2] + offset_dir[2] * 0.6,
632
- ] as Vec3}
633
- <Extras.HTML center position={label_pos}>
622
+ {@const bisector = math.add(math.scale(v1, 1 / n1), math.scale(v2, 1 / n2))}
623
+ {@const bis_norm = Math.hypot(...bisector) || 1}
624
+ {@const offset_dir = math.scale(bisector, 1 / bis_norm)}
625
+ {@const label_pos = math.add(center.xyz, math.scale(offset_dir, 0.6))}
626
+ <extras.HTML center position={label_pos}>
634
627
  <span class="measure-label">{format_num(angle_deg, float_fmt)}°</span>
635
- </Extras.HTML>
628
+ </extras.HTML>
636
629
  {/if}
637
630
  {/each}
638
631
  {/each}
@@ -1,19 +1,20 @@
1
- import type { AnyStructure, Site, Vec3 } from '..';
2
- import { type ShowBonds } from '../settings';
1
+ import type { AnyStructure, ElementSymbol, Site, Vec3 } from '..';
2
+ import { type CameraProjection, type ShowBonds } from '../settings';
3
3
  import { Lattice } from './';
4
- import * as Extras from '@threlte/extras';
4
+ import * as extras from '@threlte/extras';
5
5
  import type { ComponentProps } from 'svelte';
6
6
  import { type Snippet } from 'svelte';
7
+ import type { Camera, Scene } from 'three';
7
8
  import { type BondingStrategy } from './bonding';
8
- interface Props {
9
- structure?: AnyStructure | undefined;
9
+ type $$ComponentProps = {
10
+ structure?: AnyStructure;
10
11
  atom_radius?: number;
11
12
  same_size_atoms?: boolean;
12
13
  camera_position?: [x: number, y: number, z: number];
13
- camera_projection?: `perspective` | `orthographic`;
14
+ camera_projection?: CameraProjection;
14
15
  rotation_damping?: number;
15
- max_zoom?: number | undefined;
16
- min_zoom?: number | undefined;
16
+ max_zoom?: number;
17
+ min_zoom?: number;
17
18
  zoom_speed?: number;
18
19
  pan_speed?: number;
19
20
  show_atoms?: boolean;
@@ -23,7 +24,7 @@ interface Props {
23
24
  show_force_vectors?: boolean;
24
25
  force_scale?: number;
25
26
  force_color?: string;
26
- gizmo?: boolean | ComponentProps<typeof Extras.Gizmo>;
27
+ gizmo?: boolean | ComponentProps<typeof extras.Gizmo>;
27
28
  hovered_idx?: number | null;
28
29
  hovered_site?: Site | null;
29
30
  float_fmt?: string;
@@ -45,7 +46,6 @@ interface Props {
45
46
  site_label_color?: string;
46
47
  site_label_padding?: number;
47
48
  camera_is_moving?: boolean;
48
- orbit_controls?: ComponentProps<typeof Extras.OrbitControls>[`ref`];
49
49
  width?: number;
50
50
  height?: number;
51
51
  measure_mode?: `distance` | `angle`;
@@ -55,7 +55,10 @@ interface Props {
55
55
  active_sites?: number[];
56
56
  active_highlight_color?: string;
57
57
  rotation?: Vec3;
58
- }
59
- declare const StructureScene: import("svelte").Component<Props, {}, "site_label_offset" | "selected_sites" | "measured_sites" | "hovered_idx" | "hovered_site" | "camera_is_moving" | "orbit_controls" | "active_sites">;
58
+ scene?: Scene;
59
+ camera?: Camera;
60
+ hidden_elements?: Set<ElementSymbol>;
61
+ };
62
+ declare const StructureScene: import("svelte").Component<$$ComponentProps, {}, "camera" | "site_label_offset" | "scene" | "selected_sites" | "measured_sites" | "hidden_elements" | "hovered_idx" | "hovered_site" | "camera_is_moving" | "active_sites">;
60
63
  type StructureScene = ReturnType<typeof StructureScene>;
61
64
  export default StructureScene;
@@ -3,37 +3,31 @@ import { DEFAULTS } from '../settings';
3
3
  import { T } from '@threlte/core';
4
4
  import { Euler, Quaternion, Vector3 } from 'three';
5
5
  let { position, vector, scale = DEFAULTS.structure.force_scale, color = DEFAULTS.structure.force_color, shaft_radius = DEFAULTS.structure.force_shaft_radius, arrow_head_radius = DEFAULTS.structure.force_arrow_head_radius, arrow_head_length = DEFAULTS.structure.force_arrow_head_length, ...rest } = $props();
6
- // Calculate vector magnitude and normalized direction
7
- let vector_magnitude = $derived(Math.hypot(...vector));
8
- let vector_direction = $derived(vector_magnitude > math.EPS
9
- ? math.scale(vector, 1 / vector_magnitude)
10
- : [0, 1, 0]);
11
- // Scaled vector length
12
- let vector_length = $derived(vector_magnitude * scale);
13
- let shaft_length = $derived(Math.max(0, vector_length - arrow_head_length * 0.5));
14
- // Calculate positions using math helpers
15
- let shaft_center = $derived(math.add(position, math.scale(vector_direction, shaft_length * 0.5)));
16
- let arrow_head_position = $derived(math.add(position, math.scale(vector_direction, shaft_length + arrow_head_length * 0.5)));
17
- // Calculate rotation to align Y-axis with vector direction
18
- let rotation = $derived.by(() => {
19
- if (vector_magnitude < math.EPS)
20
- return [0, 0, 0]; // Handle zero vector
21
- const quaternion = new Quaternion().setFromUnitVectors(new Vector3(0, 1, 0), new Vector3(...vector_direction).normalize());
22
- const euler = new Euler().setFromQuaternion(quaternion);
23
- return euler.toArray().slice(0, 3);
6
+ const mag = $derived(Math.hypot(...vector));
7
+ const dir = $derived(mag > math.EPS ? math.scale(vector, 1 / mag) : [0, 1, 0]);
8
+ const vec_len = $derived(mag * scale);
9
+ const head_len = $derived(arrow_head_length < 0 ? vec_len * -arrow_head_length : arrow_head_length);
10
+ const shaft_len = $derived(Math.max(0, vec_len - head_len * 0.5));
11
+ const shaft_r = $derived(shaft_radius < 0 ? shaft_len * -shaft_radius : shaft_radius);
12
+ const head_r = $derived(arrow_head_radius < 0 ? shaft_len * -arrow_head_radius : arrow_head_radius);
13
+ const shaft_pos = $derived(math.add(position, math.scale(dir, shaft_len * 0.5)));
14
+ const head_pos = $derived(math.add(position, math.scale(dir, shaft_len + head_len * 0.5)));
15
+ const rotation = $derived.by(() => {
16
+ if (mag < math.EPS)
17
+ return [0, 0, 0];
18
+ const quat = new Quaternion().setFromUnitVectors(new Vector3(0, 1, 0), new Vector3(...dir));
19
+ return new Euler().setFromQuaternion(quat).toArray().slice(0, 3);
24
20
  });
25
21
  </script>
26
22
 
27
- <!-- Vector shaft (cylinder) -->
28
- {#if shaft_length > 0.01}
29
- <T.Mesh {...rest} position={shaft_center} {rotation}>
30
- <T.CylinderGeometry args={[shaft_radius, shaft_radius, shaft_length, 12]} />
23
+ {#if shaft_len > 0.01}
24
+ <T.Mesh {...rest} position={shaft_pos} {rotation}>
25
+ <T.CylinderGeometry args={[shaft_r, shaft_r, shaft_len, 12]} />
31
26
  <T.MeshStandardMaterial {color} />
32
27
  </T.Mesh>
33
28
  {/if}
34
29
 
35
- <!-- Arrow head (cone) -->
36
- <T.Mesh {...rest} position={arrow_head_position} {rotation}>
37
- <T.ConeGeometry args={[arrow_head_radius, arrow_head_length, 12]} />
30
+ <T.Mesh {...rest} position={head_pos} {rotation}>
31
+ <T.ConeGeometry args={[head_r, head_len, 12]} />
38
32
  <T.MeshStandardMaterial {color} />
39
33
  </T.Mesh>
@@ -1,7 +1,7 @@
1
1
  import type { Vec3 } from '..';
2
2
  import { T } from '@threlte/core';
3
3
  import type { ComponentProps } from 'svelte';
4
- interface Props extends ComponentProps<typeof T.Mesh> {
4
+ type $$ComponentProps = ComponentProps<typeof T.Mesh> & {
5
5
  position: Vec3;
6
6
  vector: Vec3;
7
7
  scale?: number;
@@ -9,7 +9,7 @@ interface Props extends ComponentProps<typeof T.Mesh> {
9
9
  shaft_radius?: number;
10
10
  arrow_head_radius?: number;
11
11
  arrow_head_length?: number;
12
- }
13
- declare const Vector: import("svelte").Component<Props, {}, "">;
12
+ };
13
+ declare const Vector: import("svelte").Component<$$ComponentProps, {}, "">;
14
14
  type Vector = ReturnType<typeof Vector>;
15
15
  export default Vector;
@@ -1,23 +1,18 @@
1
1
  import type { AnyStructure, BondPair } from '..';
2
2
  export declare const BONDING_STRATEGIES: {
3
- readonly max_dist: typeof max_dist;
4
- readonly nearest_neighbor: typeof nearest_neighbor;
5
3
  readonly electroneg_ratio: typeof electroneg_ratio;
4
+ readonly solid_angle: typeof solid_angle;
6
5
  };
7
6
  export type BondingStrategy = keyof typeof BONDING_STRATEGIES;
8
7
  export type BondingAlgo = (typeof BONDING_STRATEGIES)[BondingStrategy];
9
- export declare function max_dist(structure: AnyStructure, { max_distance_ratio, min_bond_dist, same_species_penalty }?: {
10
- max_distance_ratio?: number | undefined;
11
- min_bond_dist?: number | undefined;
12
- same_species_penalty?: number | undefined;
13
- }): BondPair[];
14
- export declare function nearest_neighbor(structure: AnyStructure, { max_neighbors, tolerance_factor, min_bond_dist, same_species_penalty, }?: {
15
- max_neighbors?: number | undefined;
16
- tolerance_factor?: number | undefined;
17
- min_bond_dist?: number | undefined;
18
- same_species_penalty?: number | undefined;
19
- }): BondPair[];
20
- export declare function electroneg_ratio(structure: AnyStructure, { electronegativity_threshold, max_distance_ratio, min_bond_dist, metal_metal_penalty, metal_nonmetal_bonus, similar_electronegativity_bonus, same_species_penalty, strength_threshold, }?: {
8
+ export declare function electroneg_ratio(structure: AnyStructure, { electronegativity_threshold, // Max electronegativity difference for bonding
9
+ max_distance_ratio, // Max distance as multiple of sum of covalent radii
10
+ min_bond_dist, // Minimum bond distance in Angstroms
11
+ metal_metal_penalty, // Strength penalty for metal-metal bonds
12
+ metal_nonmetal_bonus, // Strength bonus for metal-nonmetal bonds
13
+ similar_electronegativity_bonus, // Bonus for similar electronegativity
14
+ same_species_penalty, // Penalty for bonds between same element
15
+ strength_threshold, }?: {
21
16
  electronegativity_threshold?: number | undefined;
22
17
  max_distance_ratio?: number | undefined;
23
18
  min_bond_dist?: number | undefined;
@@ -27,3 +22,10 @@ export declare function electroneg_ratio(structure: AnyStructure, { electronegat
27
22
  same_species_penalty?: number | undefined;
28
23
  strength_threshold?: number | undefined;
29
24
  }): BondPair[];
25
+ export declare function solid_angle(structure: AnyStructure, { min_solid_angle, min_face_area, max_distance, min_bond_dist, strength_threshold, }?: {
26
+ min_solid_angle?: number | undefined;
27
+ min_face_area?: number | undefined;
28
+ max_distance?: number | undefined;
29
+ min_bond_dist?: number | undefined;
30
+ strength_threshold?: number | undefined;
31
+ }): BondPair[];