MoleditPy 1.16.3__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.
Files changed (54) hide show
  1. moleditpy/__init__.py +4 -0
  2. moleditpy/__main__.py +29 -0
  3. moleditpy/main.py +37 -0
  4. moleditpy/modules/__init__.py +36 -0
  5. moleditpy/modules/about_dialog.py +92 -0
  6. moleditpy/modules/align_plane_dialog.py +281 -0
  7. moleditpy/modules/alignment_dialog.py +261 -0
  8. moleditpy/modules/analysis_window.py +197 -0
  9. moleditpy/modules/angle_dialog.py +428 -0
  10. moleditpy/modules/assets/icon.icns +0 -0
  11. moleditpy/modules/assets/icon.ico +0 -0
  12. moleditpy/modules/assets/icon.png +0 -0
  13. moleditpy/modules/atom_item.py +336 -0
  14. moleditpy/modules/bond_item.py +303 -0
  15. moleditpy/modules/bond_length_dialog.py +368 -0
  16. moleditpy/modules/calculation_worker.py +754 -0
  17. moleditpy/modules/color_settings_dialog.py +309 -0
  18. moleditpy/modules/constants.py +76 -0
  19. moleditpy/modules/constrained_optimization_dialog.py +667 -0
  20. moleditpy/modules/custom_interactor_style.py +737 -0
  21. moleditpy/modules/custom_qt_interactor.py +49 -0
  22. moleditpy/modules/dialog3_d_picking_mixin.py +96 -0
  23. moleditpy/modules/dihedral_dialog.py +431 -0
  24. moleditpy/modules/main_window.py +830 -0
  25. moleditpy/modules/main_window_app_state.py +747 -0
  26. moleditpy/modules/main_window_compute.py +1203 -0
  27. moleditpy/modules/main_window_dialog_manager.py +454 -0
  28. moleditpy/modules/main_window_edit_3d.py +531 -0
  29. moleditpy/modules/main_window_edit_actions.py +1449 -0
  30. moleditpy/modules/main_window_export.py +744 -0
  31. moleditpy/modules/main_window_main_init.py +1668 -0
  32. moleditpy/modules/main_window_molecular_parsers.py +1037 -0
  33. moleditpy/modules/main_window_project_io.py +429 -0
  34. moleditpy/modules/main_window_string_importers.py +270 -0
  35. moleditpy/modules/main_window_ui_manager.py +567 -0
  36. moleditpy/modules/main_window_view_3d.py +1211 -0
  37. moleditpy/modules/main_window_view_loaders.py +350 -0
  38. moleditpy/modules/mirror_dialog.py +110 -0
  39. moleditpy/modules/molecular_data.py +290 -0
  40. moleditpy/modules/molecule_scene.py +1964 -0
  41. moleditpy/modules/move_group_dialog.py +586 -0
  42. moleditpy/modules/periodic_table_dialog.py +72 -0
  43. moleditpy/modules/planarize_dialog.py +209 -0
  44. moleditpy/modules/settings_dialog.py +1071 -0
  45. moleditpy/modules/template_preview_item.py +148 -0
  46. moleditpy/modules/template_preview_view.py +62 -0
  47. moleditpy/modules/translation_dialog.py +353 -0
  48. moleditpy/modules/user_template_dialog.py +621 -0
  49. moleditpy/modules/zoomable_view.py +98 -0
  50. moleditpy-1.16.3.dist-info/METADATA +274 -0
  51. moleditpy-1.16.3.dist-info/RECORD +54 -0
  52. moleditpy-1.16.3.dist-info/WHEEL +5 -0
  53. moleditpy-1.16.3.dist-info/entry_points.txt +2 -0
  54. moleditpy-1.16.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,309 @@
1
+ from PyQt6.QtWidgets import (
2
+ QDialog, QVBoxLayout, QGridLayout, QPushButton, QHBoxLayout, QLabel,
3
+ QApplication, QColorDialog
4
+ )
5
+ from PyQt6.QtGui import QColor
6
+ try:
7
+ from .constants import CPK_COLORS, DEFAULT_CPK_COLORS
8
+ except Exception:
9
+ from modules.constants import CPK_COLORS, DEFAULT_CPK_COLORS
10
+
11
+ class ColorSettingsDialog(QDialog):
12
+ """Dialog to customize CPK element colors.
13
+
14
+ - Click an element to pick a new color for the element (CPK colors).
15
+ - Reset All button to restore defaults for everything.
16
+ """
17
+ def __init__(self, current_settings, parent=None):
18
+ super().__init__(parent)
19
+ self.setWindowTitle("CPK Colors")
20
+ self.parent_window = parent
21
+ self.current_settings = current_settings or {}
22
+
23
+ self.changed_cpk = {} # symbol -> hex
24
+ self._reset_all_flag = False
25
+
26
+ layout = QVBoxLayout(self)
27
+
28
+ # Color picking for CPK is available in the periodic table and CPK dialog
29
+
30
+ # Periodic table grid (buttons like PeriodicTableDialog)
31
+ grid = QGridLayout()
32
+ self.element_buttons = {}
33
+ elements = [
34
+ ('H',1,1), ('He',1,18),
35
+ ('Li',2,1), ('Be',2,2), ('B',2,13), ('C',2,14), ('N',2,15), ('O',2,16), ('F',2,17), ('Ne',2,18),
36
+ ('Na',3,1), ('Mg',3,2), ('Al',3,13), ('Si',3,14), ('P',3,15), ('S',3,16), ('Cl',3,17), ('Ar',3,18),
37
+ ('K',4,1), ('Ca',4,2), ('Sc',4,3), ('Ti',4,4), ('V',4,5), ('Cr',4,6), ('Mn',4,7), ('Fe',4,8),
38
+ ('Co',4,9), ('Ni',4,10), ('Cu',4,11), ('Zn',4,12), ('Ga',4,13), ('Ge',4,14), ('As',4,15), ('Se',4,16),
39
+ ('Br',4,17), ('Kr',4,18),
40
+ ('Rb',5,1), ('Sr',5,2), ('Y',5,3), ('Zr',5,4), ('Nb',5,5), ('Mo',5,6), ('Tc',5,7), ('Ru',5,8),
41
+ ('Rh',5,9), ('Pd',5,10), ('Ag',5,11), ('Cd',5,12), ('In',5,13), ('Sn',5,14), ('Sb',5,15), ('Te',5,16),
42
+ ('I',5,17), ('Xe',5,18),
43
+ ('Cs',6,1), ('Ba',6,2), ('Hf',6,4), ('Ta',6,5), ('W',6,6), ('Re',6,7), ('Os',6,8),
44
+ ('Ir',6,9), ('Pt',6,10), ('Au',6,11), ('Hg',6,12), ('Tl',6,13), ('Pb',6,14), ('Bi',6,15), ('Po',6,16),
45
+ ('At',6,17), ('Rn',6,18),
46
+ ('Fr',7,1), ('Ra',7,2), ('Rf',7,4), ('Db',7,5), ('Sg',7,6), ('Bh',7,7), ('Hs',7,8),
47
+ ('Mt',7,9), ('Ds',7,10), ('Rg',7,11), ('Cn',7,12), ('Nh',7,13), ('Fl',7,14), ('Mc',7,15), ('Lv',7,16),
48
+ ('Ts',7,17), ('Og',7,18),
49
+ ('La',8,3), ('Ce',8,4), ('Pr',8,5), ('Nd',8,6), ('Pm',8,7), ('Sm',8,8), ('Eu',8,9), ('Gd',8,10), ('Tb',8,11),
50
+ ('Dy',8,12), ('Ho',8,13), ('Er',8,14), ('Tm',8,15), ('Yb',8,16), ('Lu',8,17),
51
+ ('Ac',9,3), ('Th',9,4), ('Pa',9,5), ('U',9,6), ('Np',9,7), ('Pu',9,8), ('Am',9,9), ('Cm',9,10), ('Bk',9,11),
52
+ ('Cf',9,12), ('Es',9,13), ('Fm',9,14), ('Md',9,15), ('No',9,16), ('Lr',9,17),
53
+ ]
54
+
55
+ for symbol, row, col in elements:
56
+ b = QPushButton(symbol)
57
+ b.setFixedSize(40, 40)
58
+ # Choose override color (if present) else default CPK color
59
+ override = self.current_settings.get('cpk_colors', {}).get(symbol)
60
+ if override:
61
+ q_color = QColor(override)
62
+ else:
63
+ q_color = CPK_COLORS.get(symbol, CPK_COLORS['DEFAULT'])
64
+
65
+ brightness = (q_color.red() * 299 + q_color.green() * 587 + q_color.blue() * 114) / 1000
66
+ text_color = 'white' if brightness < 128 else 'black'
67
+ b.setStyleSheet(f"background-color: {q_color.name()}; color: {text_color}; border: 1px solid #555; font-weight: bold;")
68
+ b.clicked.connect(self.on_element_clicked)
69
+ grid.addWidget(b, row, col)
70
+ self.element_buttons[symbol] = b
71
+
72
+ layout.addLayout(grid)
73
+
74
+ # Ball & Stick bond color (3D) picker - placed near the periodic table for CPK settings
75
+ self.changed_bs_color = None
76
+ try:
77
+ bs_h = QHBoxLayout()
78
+ bs_label = QLabel("Ball & Stick bond color:")
79
+ self.bs_button = QPushButton()
80
+ self.bs_button.setFixedSize(36, 24)
81
+ # initialize from current settings (if provided)
82
+ try:
83
+ cur_bs = self.current_settings.get('ball_stick_bond_color') if self.current_settings else None
84
+ except Exception:
85
+ cur_bs = None
86
+ if not cur_bs and self.parent_window and hasattr(self.parent_window, 'settings'):
87
+ cur_bs = self.parent_window.settings.get('ball_stick_bond_color', '#7F7F7F')
88
+ try:
89
+ self.bs_button.setStyleSheet(f"background-color: {cur_bs}; border: 1px solid #888;")
90
+ self.bs_button.setToolTip(cur_bs)
91
+ except Exception:
92
+ pass
93
+ self.bs_button.clicked.connect(self.pick_bs_bond_color)
94
+ bs_h.addWidget(bs_label)
95
+ bs_h.addWidget(self.bs_button)
96
+ bs_h.addStretch(1)
97
+ layout.addLayout(bs_h)
98
+ except Exception:
99
+ pass
100
+
101
+ # Reset button and action buttons
102
+ h = QHBoxLayout()
103
+ reset_button = QPushButton("Reset All")
104
+ reset_button.clicked.connect(self.reset_all)
105
+ h.addWidget(reset_button)
106
+ h.addStretch(1)
107
+ apply_button = QPushButton("Apply")
108
+ apply_button.clicked.connect(self.apply_changes)
109
+ ok_button = QPushButton("OK")
110
+ ok_button.clicked.connect(self.accept)
111
+ cancel_button = QPushButton("Cancel")
112
+ cancel_button.clicked.connect(self.reject)
113
+
114
+ h.addWidget(apply_button); h.addWidget(ok_button); h.addWidget(cancel_button)
115
+ layout.addLayout(h)
116
+
117
+ # initialize
118
+ # No 2D bond color control here
119
+
120
+ # 2D bond color picker removed — 2D bond color is fixed and not configurable here
121
+
122
+ def on_element_clicked(self):
123
+ b = self.sender()
124
+ symbol = b.text()
125
+ # get current color (override if exists else default)
126
+ cur = self.current_settings.get('cpk_colors', {}).get(symbol)
127
+ if not cur:
128
+ cur = CPK_COLORS.get(symbol, CPK_COLORS['DEFAULT']).name()
129
+ color = QColorDialog.getColor(QColor(cur), self)
130
+ if color.isValid():
131
+ self.changed_cpk[symbol] = color.name()
132
+ # Update button appearance
133
+ brightness = (color.red() * 299 + color.green() * 587 + color.blue() * 114) / 1000
134
+ text_color = 'white' if brightness < 128 else 'black'
135
+ b.setStyleSheet(f"background-color: {color.name()}; color: {text_color}; border: 1px solid #555; font-weight: bold;")
136
+
137
+ def reset_all(self):
138
+ # Clear overrides
139
+ self.changed_cpk = {}
140
+ self._reset_all_flag = True
141
+
142
+ # 1. B&S結合色もリセット対象(デフォルト値)に設定
143
+ try:
144
+ self.changed_bs_color = self.parent_window.default_settings.get('ball_stick_bond_color', '#7F7F7F') if hasattr(self.parent_window, 'default_settings') else '#7F7F7F'
145
+ except Exception:
146
+ self.changed_bs_color = '#7F7F7F'
147
+
148
+ # 2. ダイアログ内のCPKボタンの表示をデフォルトに戻す
149
+ for s, btn in self.element_buttons.items():
150
+ q_color = DEFAULT_CPK_COLORS.get(s, DEFAULT_CPK_COLORS['DEFAULT'])
151
+ brightness = (q_color.red() * 299 + q_color.green() * 587 + q_color.blue() * 114) / 1000
152
+ text_color = 'white' if brightness < 128 else 'black'
153
+ btn.setStyleSheet(f"background-color: {q_color.name()}; color: {text_color}; border: 1px solid #555; font-weight: bold;")
154
+
155
+ # 3. 3Dプレビューを更新する L.3337〜L.3386 の try...finally ブロックは削除
156
+
157
+ # 4. ダイアログ内のB&S結合色ボタンの表示をデフォルトに戻す
158
+ try:
159
+ if hasattr(self, 'bs_button'):
160
+ # self.changed_bs_color に設定したデフォルト値を反映
161
+ hexv = self.changed_bs_color
162
+ self.bs_button.setStyleSheet(f"background-color: {hexv}; border: 1px solid #888;")
163
+ self.bs_button.setToolTip(hexv)
164
+ except Exception:
165
+ pass
166
+
167
+ def apply_changes(self):
168
+ # Persist only changed keys
169
+ if self.parent_window:
170
+ if self._reset_all_flag:
171
+ # Remove any cpk overrides
172
+ try:
173
+ if 'cpk_colors' in self.parent_window.settings:
174
+ del self.parent_window.settings['cpk_colors']
175
+ except Exception:
176
+ pass
177
+ if self.changed_cpk:
178
+ # Merge with existing overrides
179
+ cdict = self.parent_window.settings.get('cpk_colors', {}).copy()
180
+ cdict.update(self.changed_cpk)
181
+ self.parent_window.settings['cpk_colors'] = cdict
182
+ self.parent_window.settings_dirty = True
183
+ # After changing settings, update global CPK color map and refresh views
184
+ try:
185
+ self.parent_window.update_cpk_colors_from_settings()
186
+ except Exception:
187
+ pass
188
+ try:
189
+ self.parent_window.apply_3d_settings(redraw=False)
190
+ except Exception:
191
+ pass
192
+ try:
193
+ if hasattr(self.parent_window, 'current_mol') and self.parent_window.current_mol:
194
+ self.parent_window.draw_molecule_3d(self.parent_window.current_mol)
195
+ except Exception:
196
+ pass
197
+ # update 2D scene objects
198
+ try:
199
+ if hasattr(self.parent_window, 'scene'):
200
+ for it in self.parent_window.scene.items():
201
+ try:
202
+ if hasattr(it, 'update_style'):
203
+ it.update_style()
204
+ except Exception:
205
+ pass
206
+ except Exception:
207
+ pass
208
+ # update periodic table button styles in the dialog to reflect any overrides
209
+ try:
210
+ for s, btn in self.element_buttons.items():
211
+ try:
212
+ q_color = QColor(self.parent_window.settings.get('cpk_colors', {}).get(s, CPK_COLORS.get(s, CPK_COLORS['DEFAULT']).name()))
213
+ brightness = (q_color.red() * 299 + q_color.green() * 587 + q_color.blue() * 114) / 1000
214
+ text_color = 'white' if brightness < 128 else 'black'
215
+ btn.setStyleSheet(f"background-color: {q_color.name()}; color: {text_color}; border: 1px solid #555; font-weight: bold;")
216
+ except Exception:
217
+ pass
218
+ except Exception:
219
+ pass
220
+ # Refresh any open SettingsDialog instances so the ball & stick color preview updates
221
+ try:
222
+ # Avoid circular import at module level; import SettingsDialog on demand
223
+ try:
224
+ from .settings_dialog import SettingsDialog
225
+ except Exception:
226
+ from modules.settings_dialog import SettingsDialog
227
+
228
+ for w in QApplication.topLevelWidgets():
229
+ try:
230
+ if isinstance(w, SettingsDialog):
231
+ try:
232
+ w.update_ui_from_settings(self.parent_window.settings)
233
+ except Exception:
234
+ pass
235
+ except Exception:
236
+ pass
237
+ except Exception:
238
+ pass
239
+ # Persist changed Ball & Stick color if the user changed it from the CPK dialog
240
+ if getattr(self, 'changed_bs_color', None):
241
+ try:
242
+ self.parent_window.settings['ball_stick_bond_color'] = self.changed_bs_color
243
+ try:
244
+ self.parent_window.settings_dirty = True
245
+ except Exception:
246
+ pass
247
+ # After changing ball-stick color, ensure 3D view updates
248
+ try:
249
+ self.parent_window.apply_3d_settings()
250
+ except Exception:
251
+ pass
252
+ try:
253
+ if hasattr(self.parent_window, 'current_mol') and self.parent_window.current_mol:
254
+ self.parent_window.draw_molecule_3d(self.parent_window.current_mol)
255
+ except Exception:
256
+ pass
257
+ except Exception:
258
+ pass
259
+ # Removed 2D bond color control — nothing to persist here
260
+ elif self._reset_all_flag:
261
+ # Reset Ball & Stick 3D bond color to default
262
+ try:
263
+ # Use a stable default instead of relying on parent_window.default_settings
264
+ self.parent_window.settings['ball_stick_bond_color'] = '#7F7F7F'
265
+ try:
266
+ self.parent_window.settings_dirty = True
267
+ except Exception:
268
+ pass
269
+ except Exception:
270
+ pass
271
+ self.parent_window.update_cpk_colors_from_settings()
272
+ self.parent_window.apply_3d_settings()
273
+ if hasattr(self.parent_window, 'current_mol') and self.parent_window.current_mol:
274
+ self.parent_window.draw_molecule_3d(self.parent_window.current_mol)
275
+
276
+ # update 2D scene
277
+ try:
278
+ if hasattr(self.parent_window, 'scene'):
279
+ for it in self.parent_window.scene.items():
280
+ try:
281
+ # AtomItem.update_style uses CPK_COLORS map
282
+ if hasattr(it, 'update_style'):
283
+ it.update_style()
284
+ except Exception:
285
+ pass
286
+ except Exception:
287
+ pass
288
+
289
+ def accept(self):
290
+ self.apply_changes()
291
+ super().accept()
292
+
293
+ def pick_bs_bond_color(self):
294
+ """Pick Ball & Stick 3D bond color from the CPK dialog and update preview immediately."""
295
+ try:
296
+ cur = getattr(self, 'changed_bs_color', None) or (self.current_settings.get('ball_stick_bond_color') if self.current_settings else None)
297
+ except Exception:
298
+ cur = None
299
+ if not cur and self.parent_window and hasattr(self.parent_window, 'settings'):
300
+ cur = self.parent_window.settings.get('ball_stick_bond_color', '#7F7F7F')
301
+ color = QColorDialog.getColor(QColor(cur), self)
302
+ if color.isValid():
303
+ hexv = color.name()
304
+ self.changed_bs_color = hexv
305
+ try:
306
+ self.bs_button.setStyleSheet(f"background-color: {hexv}; border: 1px solid #888;")
307
+ self.bs_button.setToolTip(hexv)
308
+ except Exception:
309
+ pass
@@ -0,0 +1,76 @@
1
+ # --- Constants ---
2
+
3
+ from PyQt6.QtGui import QFont, QColor
4
+ from rdkit import Chem
5
+
6
+ #Version
7
+ VERSION = '1.16.3'
8
+
9
+ ATOM_RADIUS = 18
10
+ BOND_OFFSET = 3.5
11
+ DEFAULT_BOND_LENGTH = 75 # テンプレートで使用する標準結合長
12
+ CLIPBOARD_MIME_TYPE = "application/x-moleditpy-fragment"
13
+
14
+ # Physical bond length (approximate) used to convert scene pixels to angstroms.
15
+ # DEFAULT_BOND_LENGTH is the length in pixels used in the editor UI for a typical bond.
16
+ # Many molecular file formats expect coordinates in angstroms; use ~1.5 Å as a typical single-bond length.
17
+ DEFAULT_BOND_LENGTH_ANGSTROM = 1.5
18
+ # Multiply pixel coordinates by this to get angstroms: ANGSTROM_PER_PIXEL = 1.5Å / DEFAULT_BOND_LENGTH(px)
19
+ ANGSTROM_PER_PIXEL = DEFAULT_BOND_LENGTH_ANGSTROM / DEFAULT_BOND_LENGTH
20
+
21
+ # UI / drawing / behavior constants (centralized for maintainability)
22
+ FONT_FAMILY = "Arial"
23
+ FONT_SIZE_LARGE = 20
24
+ FONT_SIZE_SMALL = 12
25
+ FONT_WEIGHT_BOLD = QFont.Weight.Bold
26
+
27
+ # Hit / visual sizes (in pixels at scale=1)
28
+ DESIRED_ATOM_PIXEL_RADIUS = 15.0
29
+ DESIRED_BOND_PIXEL_WIDTH = 18.0
30
+
31
+ # Bond/EZ label
32
+ EZ_LABEL_TEXT_OUTLINE = 2.5
33
+ EZ_LABEL_MARGIN = 16
34
+ EZ_LABEL_BOX_SIZE = 28
35
+
36
+ # Interaction thresholds
37
+ SNAP_DISTANCE = 14.0
38
+ SUM_TOLERANCE = 5.0
39
+
40
+ # Misc drawing
41
+ NUM_DASHES = 8
42
+ HOVER_PEN_WIDTH = 8
43
+
44
+ CPK_COLORS = {
45
+ 'H': QColor('#FFFFFF'), 'C': QColor('#222222'), 'N': QColor('#3377FF'), 'O': QColor('#FF3333'), 'F': QColor('#99E6E6'),
46
+ 'Cl': QColor('#33FF33'), 'Br': QColor('#A52A2A'), 'I': QColor('#9400D3'), 'S': QColor('#FFC000'), 'P': QColor('#FF8000'),
47
+ 'Si': QColor('#DAA520'), 'B': QColor('#FA8072'), 'He': QColor('#D9FFFF'), 'Ne': QColor('#B3E3F5'), 'Ar': QColor('#80D1E3'),
48
+ 'Kr': QColor('#5CACC8'), 'Xe': QColor('#429EB0'), 'Rn': QColor('#298FA2'), 'Li': QColor('#CC80FF'), 'Na': QColor('#AB5CF2'),
49
+ 'K': QColor('#8F44D7'), 'Rb': QColor('#702EBC'), 'Cs': QColor('#561B9E'), 'Fr': QColor('#421384'), 'Be': QColor('#C2FF00'),
50
+ 'Mg': QColor('#8AFF00'), 'Ca': QColor('#3DFF00'), 'Sr': QColor('#00FF00'), 'Ba': QColor('#00E600'), 'Ra': QColor('#00B800'),
51
+ 'Sc': QColor('#E6E6E6'), 'Ti': QColor('#BFC2C7'), 'V': QColor('#A6A6AB'), 'Cr': QColor('#8A99C7'), 'Mn': QColor('#9C7AC7'),
52
+ 'Fe': QColor('#E06633'), 'Co': QColor('#F090A0'), 'Ni': QColor('#50D050'), 'Cu': QColor('#C88033'), 'Zn': QColor('#7D80B0'),
53
+ 'Ga': QColor('#C28F8F'), 'Ge': QColor('#668F8F'), 'As': QColor('#BD80E3'), 'Se': QColor('#FFA100'), 'Tc': QColor('#3B9E9E'),
54
+ 'Ru': QColor('#248F8F'), 'Rh': QColor('#0A7D8F'), 'Pd': QColor('#006985'), 'Ag': QColor('#C0C0C0'), 'Cd': QColor('#FFD700'),
55
+ 'In': QColor('#A67573'), 'Sn': QColor('#668080'), 'Sb': QColor('#9E63B5'), 'Te': QColor('#D47A00'), 'La': QColor('#70D4FF'),
56
+ 'Ce': QColor('#FFFFC7'), 'Pr': QColor('#D9FFC7'), 'Nd': QColor('#C7FFC7'), 'Pm': QColor('#A3FFC7'), 'Sm': QColor('#8FFFC7'),
57
+ 'Eu': QColor('#61FFC7'), 'Gd': QColor('#45FFC7'), 'Tb': QColor('#30FFC7'), 'Dy': QColor('#1FFFC7'), 'Ho': QColor('#00FF9C'),
58
+ 'Er': QColor('#00E675'), 'Tm': QColor('#00D452'), 'Yb': QColor('#00BF38'), 'Lu': QColor('#00AB24'), 'Hf': QColor('#4DC2FF'),
59
+ 'Ta': QColor('#4DA6FF'), 'W': QColor('#2194D6'), 'Re': QColor('#267DAB'), 'Os': QColor('#266696'), 'Ir': QColor('#175487'),
60
+ 'Pt': QColor('#D0D0E0'), 'Au': QColor('#FFD123'), 'Hg': QColor('#B8B8D0'), 'Tl': QColor('#A6544D'), 'Pb': QColor('#575961'),
61
+ 'Bi': QColor('#9E4FB5'), 'Po': QColor('#AB5C00'), 'At': QColor('#754F45'), 'Ac': QColor('#70ABFA'), 'Th': QColor('#00BAFF'),
62
+ 'Pa': QColor('#00A1FF'), 'U': QColor('#008FFF'), 'Np': QColor('#0080FF'), 'Pu': QColor('#006BFF'), 'Am': QColor('#545CF2'),
63
+ 'Cm': QColor('#785CE3'), 'Bk': QColor('#8A4FE3'), 'Cf': QColor('#A136D4'), 'Es': QColor('#B31FD4'), 'Fm': QColor('#B31FBA'),
64
+ 'Md': QColor('#B30DA6'), 'No': QColor('#BD0D87'), 'Lr': QColor('#C70066'), 'Al': QColor('#B3A68F'), 'Y': QColor('#99FFFF'),
65
+ 'Zr': QColor('#7EE7E7'), 'Nb': QColor('#68CFCE'), 'Mo': QColor('#52B7B7'), 'DEFAULT': QColor('#FF1493') # Pink fallback
66
+ }
67
+ CPK_COLORS_PV = {
68
+ k: [c.redF(), c.greenF(), c.blueF()] for k, c in CPK_COLORS.items()
69
+ }
70
+
71
+ # Keep a copy of the original default map so we can restore it when user resets
72
+ DEFAULT_CPK_COLORS = {k: QColor(v) if not isinstance(v, QColor) else v for k, v in CPK_COLORS.items()}
73
+
74
+ pt = Chem.GetPeriodicTable()
75
+ VDW_RADII = {pt.GetElementSymbol(i): pt.GetRvdw(i) * 0.3 for i in range(1, 119)}
76
+