molbuilder 1.0.0__py3-none-any.whl → 1.1.0__py3-none-any.whl
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.
- molbuilder/__init__.py +1 -1
- molbuilder/cli/demos.py +73 -1
- molbuilder/cli/menu.py +2 -0
- molbuilder/dynamics/__init__.py +49 -0
- molbuilder/dynamics/forcefield.py +607 -0
- molbuilder/dynamics/integrator.py +275 -0
- molbuilder/dynamics/mechanism_choreography.py +216 -0
- molbuilder/dynamics/mechanisms.py +552 -0
- molbuilder/dynamics/simulation.py +209 -0
- molbuilder/dynamics/trajectory.py +215 -0
- molbuilder/gui/app.py +114 -0
- molbuilder/visualization/__init__.py +2 -1
- molbuilder/visualization/electron_density_viz.py +246 -0
- molbuilder/visualization/interaction_controls.py +211 -0
- molbuilder/visualization/interaction_viz.py +615 -0
- molbuilder/visualization/theme.py +7 -0
- {molbuilder-1.0.0.dist-info → molbuilder-1.1.0.dist-info}/METADATA +1 -1
- {molbuilder-1.0.0.dist-info → molbuilder-1.1.0.dist-info}/RECORD +22 -12
- {molbuilder-1.0.0.dist-info → molbuilder-1.1.0.dist-info}/WHEEL +0 -0
- {molbuilder-1.0.0.dist-info → molbuilder-1.1.0.dist-info}/entry_points.txt +0 -0
- {molbuilder-1.0.0.dist-info → molbuilder-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {molbuilder-1.0.0.dist-info → molbuilder-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""Electron cloud rendering for bond events.
|
|
2
|
+
|
|
3
|
+
Uses an LCAO (Linear Combination of Atomic Orbitals) approximation to
|
|
4
|
+
visualize electron density during bond formation and breaking. The
|
|
5
|
+
bonding orbital is modelled as:
|
|
6
|
+
|
|
7
|
+
psi_bond = c_a * phi_a(r - R_a) + c_b * phi_b(r - R_b)
|
|
8
|
+
|
|
9
|
+
where phi are hydrogen-like orbitals evaluated with Slater effective
|
|
10
|
+
nuclear charges, and c_a, c_b are mixing coefficients determined by
|
|
11
|
+
the bond order.
|
|
12
|
+
|
|
13
|
+
Reuses radial_wavefunction, real_spherical_harmonic, and
|
|
14
|
+
wavefunction_real from molbuilder.atomic.wavefunctions.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import math
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
from molbuilder.core.constants import BOHR_RADIUS_M
|
|
24
|
+
from molbuilder.core.elements import SYMBOL_TO_Z
|
|
25
|
+
from molbuilder.visualization.theme import ELECTRON_CLOUD_COLOR
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Bohr radius in Angstroms
|
|
29
|
+
_A0_ANGSTROM = BOHR_RADIUS_M * 1e10 # ~0.529 A
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _hex_to_rgba(hex_color: str, alpha: float = 0.3) -> tuple:
|
|
33
|
+
"""Convert hex color string to RGBA tuple (0-1 scale)."""
|
|
34
|
+
h = hex_color.lstrip("#")
|
|
35
|
+
r, g, b = (int(h[i:i+2], 16) / 255.0 for i in (0, 2, 4))
|
|
36
|
+
return (r, g, b, alpha)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _slater_zeff_simple(Z: int) -> float:
|
|
40
|
+
"""Simplified Slater Z_eff for outermost valence s/p orbital.
|
|
41
|
+
|
|
42
|
+
Uses a rough approximation: Z_eff ~ Z - S where S is the total
|
|
43
|
+
screening. For quick rendering this is sufficient.
|
|
44
|
+
"""
|
|
45
|
+
# Very simplified: use known values for common elements
|
|
46
|
+
_ZEFF = {
|
|
47
|
+
1: 1.0, 6: 3.25, 7: 3.9, 8: 4.55, 9: 5.2,
|
|
48
|
+
15: 4.8, 16: 5.45, 17: 6.1, 35: 9.0, 53: 12.0,
|
|
49
|
+
}
|
|
50
|
+
return _ZEFF.get(Z, max(1.0, Z * 0.3))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _valence_nl(Z: int) -> tuple[int, int]:
|
|
54
|
+
"""Return (n, l) for the outermost valence orbital."""
|
|
55
|
+
if Z <= 2:
|
|
56
|
+
return (1, 0)
|
|
57
|
+
if Z <= 10:
|
|
58
|
+
if Z <= 4:
|
|
59
|
+
return (2, 0)
|
|
60
|
+
return (2, 1)
|
|
61
|
+
if Z <= 18:
|
|
62
|
+
if Z <= 12:
|
|
63
|
+
return (3, 0)
|
|
64
|
+
return (3, 1)
|
|
65
|
+
if Z <= 36:
|
|
66
|
+
return (4, 0)
|
|
67
|
+
return (5, 0)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ElectronDensityRenderer:
|
|
71
|
+
"""Renders electron density clouds using Monte Carlo point sampling.
|
|
72
|
+
|
|
73
|
+
Generates scatter points weighted by the LCAO bonding orbital
|
|
74
|
+
probability density, producing a visual representation of the
|
|
75
|
+
electron cloud during bond events.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
n_points : int
|
|
80
|
+
Number of scatter points to generate per bond. More points
|
|
81
|
+
give smoother clouds but slower rendering.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(self, n_points: int = 5000):
|
|
85
|
+
self.n_points = n_points
|
|
86
|
+
self._rng = np.random.default_rng(42)
|
|
87
|
+
|
|
88
|
+
def compute_bond_density(self, pos_a: np.ndarray, pos_b: np.ndarray,
|
|
89
|
+
z_a: int, z_b: int,
|
|
90
|
+
bond_order: float = 1.0,
|
|
91
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
92
|
+
"""Compute electron cloud scatter points for a bond.
|
|
93
|
+
|
|
94
|
+
Uses rejection sampling: generates random points in the bonding
|
|
95
|
+
region and accepts them with probability proportional to
|
|
96
|
+
|psi_bond|^2.
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
pos_a, pos_b : ndarray of shape (3,)
|
|
101
|
+
Atomic positions in Angstroms.
|
|
102
|
+
z_a, z_b : int
|
|
103
|
+
Atomic numbers.
|
|
104
|
+
bond_order : float
|
|
105
|
+
Fractional bond order (0 to 3). Controls cloud density.
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
points : ndarray of shape (n_accepted, 3)
|
|
110
|
+
Accepted scatter point positions.
|
|
111
|
+
colors : ndarray of shape (n_accepted, 4)
|
|
112
|
+
RGBA colors for each point.
|
|
113
|
+
"""
|
|
114
|
+
if bond_order < 0.05:
|
|
115
|
+
return np.empty((0, 3)), np.empty((0, 4))
|
|
116
|
+
|
|
117
|
+
midpoint = 0.5 * (pos_a + pos_b)
|
|
118
|
+
bond_vec = pos_b - pos_a
|
|
119
|
+
bond_len = np.linalg.norm(bond_vec)
|
|
120
|
+
if bond_len < 1e-6:
|
|
121
|
+
return np.empty((0, 3)), np.empty((0, 4))
|
|
122
|
+
|
|
123
|
+
# Effective nuclear charges
|
|
124
|
+
zeff_a = _slater_zeff_simple(z_a)
|
|
125
|
+
zeff_b = _slater_zeff_simple(z_b)
|
|
126
|
+
|
|
127
|
+
# Valence orbital quantum numbers
|
|
128
|
+
n_a, l_a = _valence_nl(z_a)
|
|
129
|
+
n_b, l_b = _valence_nl(z_b)
|
|
130
|
+
|
|
131
|
+
# Sampling region: ellipsoid around the bond
|
|
132
|
+
spread = max(bond_len * 0.8, 1.5)
|
|
133
|
+
|
|
134
|
+
# Generate candidate points
|
|
135
|
+
pts = self._rng.normal(0, spread * 0.4, (self.n_points, 3))
|
|
136
|
+
pts += midpoint
|
|
137
|
+
|
|
138
|
+
# Evaluate approximate orbital values at each point
|
|
139
|
+
# Use simplified 1s-like orbitals for speed
|
|
140
|
+
r_a = np.linalg.norm(pts - pos_a, axis=1)
|
|
141
|
+
r_b = np.linalg.norm(pts - pos_b, axis=1)
|
|
142
|
+
|
|
143
|
+
# Slater-type orbital: phi ~ r^(n-1) * exp(-zeff * r / (n * a0))
|
|
144
|
+
decay_a = zeff_a / (n_a * _A0_ANGSTROM)
|
|
145
|
+
decay_b = zeff_b / (n_b * _A0_ANGSTROM)
|
|
146
|
+
|
|
147
|
+
phi_a = np.exp(-decay_a * r_a)
|
|
148
|
+
phi_b = np.exp(-decay_b * r_b)
|
|
149
|
+
|
|
150
|
+
# LCAO bonding combination
|
|
151
|
+
c_a = 1.0 / math.sqrt(2.0)
|
|
152
|
+
c_b = 1.0 / math.sqrt(2.0)
|
|
153
|
+
psi_bond = c_a * phi_a + c_b * phi_b
|
|
154
|
+
density = psi_bond ** 2
|
|
155
|
+
|
|
156
|
+
# Normalize and apply bond order scaling
|
|
157
|
+
max_dens = np.max(density)
|
|
158
|
+
if max_dens < 1e-30:
|
|
159
|
+
return np.empty((0, 3)), np.empty((0, 4))
|
|
160
|
+
prob = density / max_dens * min(bond_order, 1.5)
|
|
161
|
+
|
|
162
|
+
# Rejection sampling
|
|
163
|
+
rng_vals = self._rng.uniform(0, 1, self.n_points)
|
|
164
|
+
accept = rng_vals < prob
|
|
165
|
+
|
|
166
|
+
accepted_pts = pts[accept]
|
|
167
|
+
n_accepted = len(accepted_pts)
|
|
168
|
+
|
|
169
|
+
if n_accepted == 0:
|
|
170
|
+
return np.empty((0, 3)), np.empty((0, 4))
|
|
171
|
+
|
|
172
|
+
# Colors: base color with alpha proportional to density
|
|
173
|
+
base_rgba = _hex_to_rgba(ELECTRON_CLOUD_COLOR, 0.4)
|
|
174
|
+
colors = np.tile(base_rgba, (n_accepted, 1))
|
|
175
|
+
# Modulate alpha by density
|
|
176
|
+
accepted_density = density[accept]
|
|
177
|
+
alpha_scale = accepted_density / max_dens
|
|
178
|
+
colors[:, 3] = 0.1 + 0.4 * alpha_scale * min(bond_order, 1.5)
|
|
179
|
+
|
|
180
|
+
return accepted_pts, colors
|
|
181
|
+
|
|
182
|
+
def render_on_axis(self, ax, pos_a: np.ndarray, pos_b: np.ndarray,
|
|
183
|
+
z_a: int, z_b: int,
|
|
184
|
+
bond_order: float = 1.0,
|
|
185
|
+
point_size: float = 2.0):
|
|
186
|
+
"""Draw electron density cloud on a matplotlib 3D axis.
|
|
187
|
+
|
|
188
|
+
Parameters
|
|
189
|
+
----------
|
|
190
|
+
ax : Axes3D
|
|
191
|
+
Matplotlib 3D axis to draw on.
|
|
192
|
+
pos_a, pos_b : ndarray of shape (3,)
|
|
193
|
+
Atomic positions in Angstroms.
|
|
194
|
+
z_a, z_b : int
|
|
195
|
+
Atomic numbers.
|
|
196
|
+
bond_order : float
|
|
197
|
+
Fractional bond order.
|
|
198
|
+
point_size : float
|
|
199
|
+
Size of scatter points.
|
|
200
|
+
"""
|
|
201
|
+
points, colors = self.compute_bond_density(
|
|
202
|
+
pos_a, pos_b, z_a, z_b, bond_order)
|
|
203
|
+
if len(points) == 0:
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
ax.scatter(
|
|
207
|
+
points[:, 0], points[:, 1], points[:, 2],
|
|
208
|
+
c=colors,
|
|
209
|
+
s=point_size,
|
|
210
|
+
alpha=0.3,
|
|
211
|
+
depthshade=True,
|
|
212
|
+
edgecolors="none",
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def render_on_axis_2d(self, ax, pos_a: np.ndarray, pos_b: np.ndarray,
|
|
216
|
+
z_a: int, z_b: int,
|
|
217
|
+
bond_order: float = 1.0,
|
|
218
|
+
point_size: float = 2.0):
|
|
219
|
+
"""Draw electron density cloud on a 2D matplotlib axis.
|
|
220
|
+
|
|
221
|
+
Uses the first two coordinates (x, y) for 2D projection.
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
ax : Axes
|
|
226
|
+
Matplotlib 2D axis.
|
|
227
|
+
pos_a, pos_b : ndarray
|
|
228
|
+
Atomic positions.
|
|
229
|
+
z_a, z_b : int
|
|
230
|
+
Atomic numbers.
|
|
231
|
+
bond_order : float
|
|
232
|
+
Fractional bond order.
|
|
233
|
+
point_size : float
|
|
234
|
+
Scatter point size.
|
|
235
|
+
"""
|
|
236
|
+
points, colors = self.compute_bond_density(
|
|
237
|
+
pos_a, pos_b, z_a, z_b, bond_order)
|
|
238
|
+
if len(points) == 0:
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
ax.scatter(
|
|
242
|
+
points[:, 0], points[:, 1],
|
|
243
|
+
c=colors,
|
|
244
|
+
s=point_size,
|
|
245
|
+
edgecolors="none",
|
|
246
|
+
)
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Playback controls for the interaction visualizer.
|
|
2
|
+
|
|
3
|
+
Provides keyboard bindings for interactive animation control and an
|
|
4
|
+
optional tkinter panel for GUI embedding.
|
|
5
|
+
|
|
6
|
+
Keyboard bindings:
|
|
7
|
+
Space : Play / Pause
|
|
8
|
+
Right : Step forward
|
|
9
|
+
Left : Step backward
|
|
10
|
+
Up : Increase speed (2x)
|
|
11
|
+
Down : Decrease speed (0.5x)
|
|
12
|
+
E : Toggle electron density
|
|
13
|
+
L : Toggle labels
|
|
14
|
+
R : Reset to beginning
|
|
15
|
+
Q / Esc : Close
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import TYPE_CHECKING
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from molbuilder.visualization.interaction_viz import InteractionVisualizer
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PlaybackController:
|
|
27
|
+
"""Keyboard-driven playback controller for InteractionVisualizer.
|
|
28
|
+
|
|
29
|
+
Connects to matplotlib key_press_event to provide interactive
|
|
30
|
+
playback controls.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
visualizer : InteractionVisualizer
|
|
35
|
+
The visualizer to control.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, visualizer: InteractionVisualizer):
|
|
39
|
+
self.viz = visualizer
|
|
40
|
+
self._speed_multiplier = 1.0
|
|
41
|
+
self._current_frame = 0
|
|
42
|
+
self._connected = False
|
|
43
|
+
|
|
44
|
+
def connect(self):
|
|
45
|
+
"""Connect keyboard event handlers to the visualizer's figure."""
|
|
46
|
+
if self.viz.fig is None:
|
|
47
|
+
return
|
|
48
|
+
self.viz.fig.canvas.mpl_connect("key_press_event", self._on_key)
|
|
49
|
+
self._connected = True
|
|
50
|
+
|
|
51
|
+
def _on_key(self, event):
|
|
52
|
+
"""Handle a key press event."""
|
|
53
|
+
if event.key == " ":
|
|
54
|
+
self._toggle_pause()
|
|
55
|
+
elif event.key == "right":
|
|
56
|
+
self._step_forward()
|
|
57
|
+
elif event.key == "left":
|
|
58
|
+
self._step_backward()
|
|
59
|
+
elif event.key == "up":
|
|
60
|
+
self._speed_up()
|
|
61
|
+
elif event.key == "down":
|
|
62
|
+
self._slow_down()
|
|
63
|
+
elif event.key == "e":
|
|
64
|
+
self._toggle_electron_density()
|
|
65
|
+
elif event.key == "l":
|
|
66
|
+
self._toggle_labels()
|
|
67
|
+
elif event.key == "r":
|
|
68
|
+
self._reset()
|
|
69
|
+
elif event.key in ("q", "escape"):
|
|
70
|
+
self._close()
|
|
71
|
+
|
|
72
|
+
def _toggle_pause(self):
|
|
73
|
+
"""Toggle play/pause."""
|
|
74
|
+
anim = self.viz._anim
|
|
75
|
+
if anim is None:
|
|
76
|
+
return
|
|
77
|
+
if self.viz._paused:
|
|
78
|
+
anim.resume()
|
|
79
|
+
self.viz._paused = False
|
|
80
|
+
else:
|
|
81
|
+
anim.pause()
|
|
82
|
+
self.viz._paused = True
|
|
83
|
+
|
|
84
|
+
def _step_forward(self):
|
|
85
|
+
"""Step one frame forward (pauses first)."""
|
|
86
|
+
anim = self.viz._anim
|
|
87
|
+
if anim is None:
|
|
88
|
+
return
|
|
89
|
+
if not self.viz._paused:
|
|
90
|
+
anim.pause()
|
|
91
|
+
self.viz._paused = True
|
|
92
|
+
self._current_frame = min(
|
|
93
|
+
self._current_frame + 1, self.viz.n_frames - 1)
|
|
94
|
+
self.viz._render_frame(self._current_frame)
|
|
95
|
+
self.viz.fig.canvas.draw_idle()
|
|
96
|
+
|
|
97
|
+
def _step_backward(self):
|
|
98
|
+
"""Step one frame backward."""
|
|
99
|
+
anim = self.viz._anim
|
|
100
|
+
if anim is None:
|
|
101
|
+
return
|
|
102
|
+
if not self.viz._paused:
|
|
103
|
+
anim.pause()
|
|
104
|
+
self.viz._paused = True
|
|
105
|
+
self._current_frame = max(self._current_frame - 1, 0)
|
|
106
|
+
self.viz._render_frame(self._current_frame)
|
|
107
|
+
self.viz.fig.canvas.draw_idle()
|
|
108
|
+
|
|
109
|
+
def _speed_up(self):
|
|
110
|
+
"""Double the playback speed."""
|
|
111
|
+
self._speed_multiplier *= 2.0
|
|
112
|
+
anim = self.viz._anim
|
|
113
|
+
if anim is not None:
|
|
114
|
+
new_interval = max(
|
|
115
|
+
1, int(1000 / (self.viz.config.fps * self._speed_multiplier)))
|
|
116
|
+
anim.event_source.interval = new_interval
|
|
117
|
+
|
|
118
|
+
def _slow_down(self):
|
|
119
|
+
"""Halve the playback speed."""
|
|
120
|
+
self._speed_multiplier *= 0.5
|
|
121
|
+
anim = self.viz._anim
|
|
122
|
+
if anim is not None:
|
|
123
|
+
new_interval = int(
|
|
124
|
+
1000 / (self.viz.config.fps * self._speed_multiplier))
|
|
125
|
+
anim.event_source.interval = new_interval
|
|
126
|
+
|
|
127
|
+
def _toggle_electron_density(self):
|
|
128
|
+
"""Toggle electron density cloud rendering."""
|
|
129
|
+
self.viz.config.show_electron_density = (
|
|
130
|
+
not self.viz.config.show_electron_density)
|
|
131
|
+
|
|
132
|
+
def _toggle_labels(self):
|
|
133
|
+
"""Toggle time and energy labels."""
|
|
134
|
+
self.viz.config.show_time_label = not self.viz.config.show_time_label
|
|
135
|
+
self.viz.config.show_energy_bar = not self.viz.config.show_energy_bar
|
|
136
|
+
|
|
137
|
+
def _reset(self):
|
|
138
|
+
"""Reset to the first frame."""
|
|
139
|
+
self._current_frame = 0
|
|
140
|
+
if self.viz._paused:
|
|
141
|
+
self.viz._render_frame(0)
|
|
142
|
+
self.viz.fig.canvas.draw_idle()
|
|
143
|
+
|
|
144
|
+
def _close(self):
|
|
145
|
+
"""Close the figure."""
|
|
146
|
+
import matplotlib.pyplot as plt
|
|
147
|
+
plt.close(self.viz.fig)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def create_tkinter_panel(parent, visualizer: InteractionVisualizer):
|
|
151
|
+
"""Create an optional tkinter control panel for GUI embedding.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
parent : tk.Widget
|
|
156
|
+
Parent tkinter widget.
|
|
157
|
+
visualizer : InteractionVisualizer
|
|
158
|
+
The visualizer to control.
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
tk.Frame
|
|
163
|
+
The control panel frame.
|
|
164
|
+
"""
|
|
165
|
+
import tkinter as tk
|
|
166
|
+
from tkinter import ttk
|
|
167
|
+
|
|
168
|
+
controller = PlaybackController(visualizer)
|
|
169
|
+
|
|
170
|
+
frame = ttk.Frame(parent)
|
|
171
|
+
|
|
172
|
+
play_btn = ttk.Button(
|
|
173
|
+
frame, text="Play/Pause",
|
|
174
|
+
command=controller._toggle_pause)
|
|
175
|
+
play_btn.pack(side="left", padx=2)
|
|
176
|
+
|
|
177
|
+
step_back_btn = ttk.Button(
|
|
178
|
+
frame, text="<<",
|
|
179
|
+
command=controller._step_backward)
|
|
180
|
+
step_back_btn.pack(side="left", padx=2)
|
|
181
|
+
|
|
182
|
+
step_fwd_btn = ttk.Button(
|
|
183
|
+
frame, text=">>",
|
|
184
|
+
command=controller._step_forward)
|
|
185
|
+
step_fwd_btn.pack(side="left", padx=2)
|
|
186
|
+
|
|
187
|
+
slower_btn = ttk.Button(
|
|
188
|
+
frame, text="Slower",
|
|
189
|
+
command=controller._slow_down)
|
|
190
|
+
slower_btn.pack(side="left", padx=2)
|
|
191
|
+
|
|
192
|
+
faster_btn = ttk.Button(
|
|
193
|
+
frame, text="Faster",
|
|
194
|
+
command=controller._speed_up)
|
|
195
|
+
faster_btn.pack(side="left", padx=2)
|
|
196
|
+
|
|
197
|
+
edensity_var = tk.BooleanVar(value=visualizer.config.show_electron_density)
|
|
198
|
+
edensity_cb = ttk.Checkbutton(
|
|
199
|
+
frame, text="e- Density",
|
|
200
|
+
variable=edensity_var,
|
|
201
|
+
command=controller._toggle_electron_density)
|
|
202
|
+
edensity_cb.pack(side="left", padx=2)
|
|
203
|
+
|
|
204
|
+
labels_var = tk.BooleanVar(value=visualizer.config.show_time_label)
|
|
205
|
+
labels_cb = ttk.Checkbutton(
|
|
206
|
+
frame, text="Labels",
|
|
207
|
+
variable=labels_var,
|
|
208
|
+
command=controller._toggle_labels)
|
|
209
|
+
labels_cb.pack(side="left", padx=2)
|
|
210
|
+
|
|
211
|
+
return frame
|