MoleditPy-linux 2.2.4__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 (58) hide show
  1. moleditpy_linux/__init__.py +17 -0
  2. moleditpy_linux/__main__.py +29 -0
  3. moleditpy_linux/main.py +37 -0
  4. moleditpy_linux/modules/__init__.py +41 -0
  5. moleditpy_linux/modules/about_dialog.py +104 -0
  6. moleditpy_linux/modules/align_plane_dialog.py +293 -0
  7. moleditpy_linux/modules/alignment_dialog.py +273 -0
  8. moleditpy_linux/modules/analysis_window.py +209 -0
  9. moleditpy_linux/modules/angle_dialog.py +440 -0
  10. moleditpy_linux/modules/assets/icon.icns +0 -0
  11. moleditpy_linux/modules/assets/icon.ico +0 -0
  12. moleditpy_linux/modules/assets/icon.png +0 -0
  13. moleditpy_linux/modules/atom_item.py +348 -0
  14. moleditpy_linux/modules/bond_item.py +406 -0
  15. moleditpy_linux/modules/bond_length_dialog.py +380 -0
  16. moleditpy_linux/modules/calculation_worker.py +766 -0
  17. moleditpy_linux/modules/color_settings_dialog.py +321 -0
  18. moleditpy_linux/modules/constants.py +88 -0
  19. moleditpy_linux/modules/constrained_optimization_dialog.py +679 -0
  20. moleditpy_linux/modules/custom_interactor_style.py +749 -0
  21. moleditpy_linux/modules/custom_qt_interactor.py +59 -0
  22. moleditpy_linux/modules/dialog3_d_picking_mixin.py +108 -0
  23. moleditpy_linux/modules/dihedral_dialog.py +443 -0
  24. moleditpy_linux/modules/main_window.py +842 -0
  25. moleditpy_linux/modules/main_window_app_state.py +780 -0
  26. moleditpy_linux/modules/main_window_compute.py +1242 -0
  27. moleditpy_linux/modules/main_window_dialog_manager.py +460 -0
  28. moleditpy_linux/modules/main_window_edit_3d.py +536 -0
  29. moleditpy_linux/modules/main_window_edit_actions.py +1455 -0
  30. moleditpy_linux/modules/main_window_export.py +806 -0
  31. moleditpy_linux/modules/main_window_main_init.py +2006 -0
  32. moleditpy_linux/modules/main_window_molecular_parsers.py +1044 -0
  33. moleditpy_linux/modules/main_window_project_io.py +434 -0
  34. moleditpy_linux/modules/main_window_string_importers.py +275 -0
  35. moleditpy_linux/modules/main_window_ui_manager.py +606 -0
  36. moleditpy_linux/modules/main_window_view_3d.py +1531 -0
  37. moleditpy_linux/modules/main_window_view_loaders.py +355 -0
  38. moleditpy_linux/modules/mirror_dialog.py +122 -0
  39. moleditpy_linux/modules/molecular_data.py +302 -0
  40. moleditpy_linux/modules/molecule_scene.py +2000 -0
  41. moleditpy_linux/modules/move_group_dialog.py +598 -0
  42. moleditpy_linux/modules/periodic_table_dialog.py +84 -0
  43. moleditpy_linux/modules/planarize_dialog.py +221 -0
  44. moleditpy_linux/modules/plugin_interface.py +195 -0
  45. moleditpy_linux/modules/plugin_manager.py +309 -0
  46. moleditpy_linux/modules/plugin_manager_window.py +221 -0
  47. moleditpy_linux/modules/settings_dialog.py +1149 -0
  48. moleditpy_linux/modules/template_preview_item.py +157 -0
  49. moleditpy_linux/modules/template_preview_view.py +74 -0
  50. moleditpy_linux/modules/translation_dialog.py +365 -0
  51. moleditpy_linux/modules/user_template_dialog.py +692 -0
  52. moleditpy_linux/modules/zoomable_view.py +129 -0
  53. moleditpy_linux-2.2.4.dist-info/METADATA +936 -0
  54. moleditpy_linux-2.2.4.dist-info/RECORD +58 -0
  55. moleditpy_linux-2.2.4.dist-info/WHEEL +5 -0
  56. moleditpy_linux-2.2.4.dist-info/entry_points.txt +2 -0
  57. moleditpy_linux-2.2.4.dist-info/licenses/LICENSE +674 -0
  58. moleditpy_linux-2.2.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1149 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ MoleditPy — A Python-based molecular editing software
6
+
7
+ Author: Hiromichi Yokoyama
8
+ License: GPL-3.0 license
9
+ Repo: https://github.com/HiroYokoyama/python_molecular_editor
10
+ DOI: 10.5281/zenodo.17268532
11
+ """
12
+
13
+ from PyQt6.QtWidgets import (
14
+ QDialog, QVBoxLayout, QTabWidget, QWidget, QFormLayout, QPushButton, QHBoxLayout,
15
+ QCheckBox, QComboBox, QLabel, QColorDialog, QSlider, QFrame, QMessageBox
16
+ )
17
+ from PyQt6.QtWidgets import QApplication
18
+ from PyQt6.QtCore import Qt
19
+ from PyQt6.QtGui import QColor
20
+ try:
21
+ from .constants import CPK_COLORS
22
+ except Exception:
23
+ from modules.constants import CPK_COLORS
24
+
25
+
26
+ class SettingsDialog(QDialog):
27
+ def __init__(self, current_settings, parent=None):
28
+ super().__init__(parent)
29
+ self.setWindowTitle("3D View Settings")
30
+ self.setMinimumSize(500, 600)
31
+
32
+ # 親ウィンドウの参照を保存(Apply機能のため)
33
+ self.parent_window = parent
34
+
35
+ # デフォルト設定をクラス内で定義
36
+ # Multi-bond settings are model-specific now (ball_stick, cpk, wireframe, stick)
37
+ self.default_settings = {
38
+ 'background_color': '#919191',
39
+ 'projection_mode': 'Perspective',
40
+ 'lighting_enabled': True,
41
+ 'specular': 0.20,
42
+ 'specular_power': 20,
43
+ 'light_intensity': 1.0,
44
+ 'show_3d_axes': True,
45
+ # Ball and Stick model parameters
46
+ 'ball_stick_atom_scale': 1.0,
47
+ 'ball_stick_bond_radius': 0.1,
48
+ 'ball_stick_resolution': 16,
49
+ # CPK (Space-filling) model parameters
50
+ 'cpk_atom_scale': 1.0,
51
+ 'cpk_resolution': 32,
52
+ # Wireframe model parameters
53
+ 'wireframe_bond_radius': 0.01,
54
+ 'wireframe_resolution': 6,
55
+ # Stick model parameters
56
+ 'stick_bond_radius': 0.15,
57
+ 'stick_resolution': 16,
58
+ # Multiple bond offset parameters (per-model)
59
+ 'ball_stick_double_bond_offset_factor': 2.0,
60
+ 'ball_stick_triple_bond_offset_factor': 2.0,
61
+ 'ball_stick_double_bond_radius_factor': 0.8,
62
+ 'ball_stick_triple_bond_radius_factor': 0.75,
63
+ 'wireframe_double_bond_offset_factor': 3.0,
64
+ 'wireframe_triple_bond_offset_factor': 3.0,
65
+ 'wireframe_double_bond_radius_factor': 0.8,
66
+ 'wireframe_triple_bond_radius_factor': 0.75,
67
+ 'stick_double_bond_offset_factor': 1.5,
68
+ 'stick_triple_bond_offset_factor': 1.0,
69
+ 'stick_double_bond_radius_factor': 0.6,
70
+ 'stick_triple_bond_radius_factor': 0.4,
71
+ 'aromatic_torus_thickness_factor': 0.6,
72
+ # Whether to draw an aromatic circle inside rings in 3D
73
+ 'display_aromatic_circles_3d': False,
74
+ # If True, attempts to be permissive when RDKit raises chemical/sanitization errors
75
+ # during file import (useful for viewing malformed XYZ/MOL files). When enabled,
76
+ # element symbol recognition will be coerced where possible and Chem.SanitizeMol
77
+ # failures will be ignored so the 3D viewer can still display the structure.
78
+ 'skip_chemistry_checks': False,
79
+ # When True, always prompt the user for molecular charge on XYZ import
80
+ # instead of silently trying charge=0 first. Default True to disable
81
+ # the silent 'charge=0' test.
82
+ 'always_ask_charge': False,
83
+ # 3D conversion/optimization defaults
84
+ '3d_conversion_mode': 'fallback',
85
+ 'optimization_method': 'MMFF_RDKIT',
86
+ 'ball_stick_bond_color': '#7F7F7F',
87
+ 'cpk_colors': {},
88
+ # If True, RDKit will attempt to kekulize aromatic systems for 3D display
89
+ # (shows alternating single/double bonds rather than aromatic circles)
90
+ 'display_kekule_3d': False,
91
+ 'ball_stick_use_cpk_bond_color': False,
92
+ }
93
+
94
+ # --- 選択された色を管理する専用のインスタンス変数 ---
95
+ self.current_bg_color = None
96
+
97
+ # --- UI要素の作成 ---
98
+ layout = QVBoxLayout(self)
99
+
100
+ # タブウィジェットを作成
101
+ self.tab_widget = QTabWidget()
102
+ layout.addWidget(self.tab_widget)
103
+
104
+ # Scene設定タブ
105
+ self.create_scene_tab()
106
+
107
+ # Ball and Stick設定タブ
108
+ self.create_ball_stick_tab()
109
+
110
+ # CPK設定タブ
111
+ self.create_cpk_tab()
112
+
113
+ # Wireframe設定タブ
114
+ self.create_wireframe_tab()
115
+
116
+ # Stick設定タブ
117
+ self.create_stick_tab()
118
+
119
+ # Other設定タブ
120
+ self.create_other_tab()
121
+
122
+ # 渡された設定でUIと内部変数を初期化
123
+ self.update_ui_from_settings(current_settings)
124
+
125
+ # Initialize aromatic circle checkbox and torus thickness from settings
126
+ self.aromatic_circle_checkbox.setChecked(current_settings.get('display_aromatic_circles_3d', self.default_settings.get('display_aromatic_circles_3d', False)))
127
+ # Thickness factor is stored as a multiplier (e.g., 1.0), slider uses integer 0-300 representing 0.1x-3.0x
128
+ thickness_factor = current_settings.get('aromatic_torus_thickness_factor', self.default_settings.get('aromatic_torus_thickness_factor', 1.0))
129
+ self.aromatic_torus_thickness_slider.setValue(int(thickness_factor * 100))
130
+ self.aromatic_torus_thickness_label.setText(f"{thickness_factor:.1f}")
131
+
132
+ # --- ボタンの配置 ---
133
+ buttons = QHBoxLayout()
134
+
135
+ # タブごとのリセットボタン
136
+ reset_tab_button = QPushButton("Reset Current Tab")
137
+ reset_tab_button.clicked.connect(self.reset_current_tab)
138
+ reset_tab_button.setToolTip("Reset settings for the currently selected tab only")
139
+ buttons.addWidget(reset_tab_button)
140
+
141
+ # 全体リセットボタン
142
+ reset_all_button = QPushButton("Reset All")
143
+ reset_all_button.clicked.connect(self.reset_all_settings)
144
+ reset_all_button.setToolTip("Reset all settings to defaults")
145
+ buttons.addWidget(reset_all_button)
146
+
147
+ buttons.addStretch(1)
148
+
149
+ # Applyボタンを追加
150
+ apply_button = QPushButton("Apply")
151
+ apply_button.clicked.connect(self.apply_settings)
152
+ apply_button.setToolTip("Apply settings without closing dialog")
153
+ buttons.addWidget(apply_button)
154
+
155
+ ok_button = QPushButton("OK")
156
+ cancel_button = QPushButton("Cancel")
157
+ ok_button.clicked.connect(self.accept)
158
+ cancel_button.clicked.connect(self.reject)
159
+
160
+ buttons.addWidget(ok_button)
161
+ buttons.addWidget(cancel_button)
162
+ layout.addLayout(buttons)
163
+
164
+ def create_scene_tab(self):
165
+ """基本設定タブを作成"""
166
+ scene_widget = QWidget()
167
+ form_layout = QFormLayout(scene_widget)
168
+
169
+ # 1. 背景色
170
+ self.bg_button = QPushButton()
171
+ self.bg_button.setToolTip("Click to select a color")
172
+ self.bg_button.clicked.connect(self.select_color)
173
+ form_layout.addRow("Background Color:", self.bg_button)
174
+
175
+ # 1a. 軸の表示/非表示
176
+ self.axes_checkbox = QCheckBox()
177
+ form_layout.addRow("Show 3D Axes:", self.axes_checkbox)
178
+
179
+ # 2. ライトの有効/無効
180
+ self.light_checkbox = QCheckBox()
181
+ form_layout.addRow("Enable Lighting:", self.light_checkbox)
182
+
183
+ # 光の強さスライダーを追加
184
+ self.intensity_slider = QSlider(Qt.Orientation.Horizontal)
185
+ self.intensity_slider.setRange(0, 200) # 0.0 ~ 2.0 の範囲
186
+ self.intensity_label = QLabel("1.0")
187
+ self.intensity_slider.valueChanged.connect(lambda v: self.intensity_label.setText(f"{v/100:.2f}"))
188
+ intensity_layout = QHBoxLayout()
189
+ intensity_layout.addWidget(self.intensity_slider)
190
+ intensity_layout.addWidget(self.intensity_label)
191
+ form_layout.addRow("Light Intensity:", intensity_layout)
192
+
193
+ # 3. 光沢 (Specular)
194
+ self.specular_slider = QSlider(Qt.Orientation.Horizontal)
195
+ self.specular_slider.setRange(0, 100)
196
+ self.specular_label = QLabel("0.20")
197
+ self.specular_slider.valueChanged.connect(lambda v: self.specular_label.setText(f"{v/100:.2f}"))
198
+ specular_layout = QHBoxLayout()
199
+ specular_layout.addWidget(self.specular_slider)
200
+ specular_layout.addWidget(self.specular_label)
201
+ form_layout.addRow("Shininess (Specular):", specular_layout)
202
+
203
+ # 4. 光沢の強さ (Specular Power)
204
+ self.spec_power_slider = QSlider(Qt.Orientation.Horizontal)
205
+ self.spec_power_slider.setRange(0, 100)
206
+ self.spec_power_label = QLabel("20")
207
+ self.spec_power_slider.valueChanged.connect(lambda v: self.spec_power_label.setText(str(v)))
208
+ spec_power_layout = QHBoxLayout()
209
+ spec_power_layout.addWidget(self.spec_power_slider)
210
+ spec_power_layout.addWidget(self.spec_power_label)
211
+ form_layout.addRow("Shininess Power:", spec_power_layout)
212
+
213
+ # Projection mode (Perspective / Orthographic)
214
+ self.projection_combo = QComboBox()
215
+ self.projection_combo.addItem("Perspective")
216
+ self.projection_combo.addItem("Orthographic")
217
+ self.projection_combo.setToolTip("Choose camera projection mode: Perspective (default) or Orthographic")
218
+ form_layout.addRow("Projection Mode:", self.projection_combo)
219
+
220
+ self.tab_widget.addTab(scene_widget, "Scene")
221
+
222
+ def create_other_tab(self):
223
+ """other設定タブを作成"""
224
+ self.other_widget = QWidget()
225
+ self.other_form_layout = QFormLayout(self.other_widget)
226
+
227
+ # 化学チェックスキップオプション(otherタブに移動)
228
+ self.skip_chem_checks_checkbox = QCheckBox()
229
+ self.skip_chem_checks_checkbox.setToolTip("When enabled, XYZ file import will try to ignore chemical/sanitization errors and allow viewing malformed files.")
230
+ # Immediately persist change to settings when user toggles the checkbox
231
+ try:
232
+ self.skip_chem_checks_checkbox.stateChanged.connect(lambda s: self._on_skip_chem_checks_changed(s))
233
+ except Exception:
234
+ pass
235
+
236
+ # Add the checkbox to the other tab's form
237
+ try:
238
+ self.other_form_layout.addRow("Skip chemistry checks on import XYZ file:", self.skip_chem_checks_checkbox)
239
+ except Exception:
240
+ pass
241
+
242
+ # 3D Kekule display option (under Other) will be added below the
243
+ # 'Always ask molecular charge on import' option so ordering is clear
244
+ # in the UI.
245
+ self.kekule_3d_checkbox = QCheckBox()
246
+ self.kekule_3d_checkbox.setToolTip("When enabled, aromatic bonds will be kekulized in the 3D view (show alternating single/double bonds).")
247
+ # Don't persist kekule state immediately; Apply/OK should commit setting.
248
+ # Always ask charge on XYZ import (skip silent charge=0 test)
249
+ self.always_ask_charge_checkbox = QCheckBox()
250
+ self.always_ask_charge_checkbox.setToolTip("Prompt for overall molecular charge when importing XYZ files instead of silently trying charge=0 first.")
251
+ try:
252
+ self.other_form_layout.addRow("Always ask molecular charge on import XYZ file:", self.always_ask_charge_checkbox)
253
+ except Exception:
254
+ pass
255
+
256
+ # Add separator after Kekule bonds option
257
+ separator = QFrame()
258
+ separator.setFrameShape(QFrame.Shape.HLine)
259
+ separator.setFrameShadow(QFrame.Shadow.Sunken)
260
+ self.other_form_layout.addRow(separator)
261
+
262
+ # Place the Kekulé option after the always-ask-charge option
263
+ try:
264
+ self.other_form_layout.addRow("Display Kekulé bonds in 3D:", self.kekule_3d_checkbox)
265
+ except Exception:
266
+ pass
267
+
268
+
269
+ # Aromatic ring circle display option
270
+ self.aromatic_circle_checkbox = QCheckBox()
271
+ self.aromatic_circle_checkbox.setToolTip("When enabled, aromatic rings will be displayed with a circle inside the ring in 3D view.")
272
+ try:
273
+ self.other_form_layout.addRow("Display aromatic rings as circles in 3D:", self.aromatic_circle_checkbox)
274
+ except Exception:
275
+ pass
276
+
277
+ # Aromatic torus thickness factor
278
+ self.aromatic_torus_thickness_slider = QSlider(Qt.Orientation.Horizontal)
279
+ self.aromatic_torus_thickness_slider.setRange(10, 300) # 0.1x to 3.0x
280
+ self.aromatic_torus_thickness_slider.setValue(60) # Default 0.6x
281
+ self.aromatic_torus_thickness_label = QLabel("0.6")
282
+ self.aromatic_torus_thickness_slider.valueChanged.connect(
283
+ lambda v: self.aromatic_torus_thickness_label.setText(f"{v/100:.1f}")
284
+ )
285
+ thickness_layout = QHBoxLayout()
286
+ thickness_layout.addWidget(self.aromatic_torus_thickness_slider)
287
+ thickness_layout.addWidget(self.aromatic_torus_thickness_label)
288
+ try:
289
+ self.other_form_layout.addRow("Aromatic torus thickness (× bond radius):", thickness_layout)
290
+ except Exception:
291
+ pass
292
+
293
+ # Add Other tab to the tab widget
294
+ self.tab_widget.addTab(self.other_widget, "Other")
295
+
296
+ def refresh_ui(self):
297
+ """Refresh periodic table / BS button visuals using current settings.
298
+
299
+ Called when settings change externally (e.g., Reset All in main settings) so
300
+ the dialog reflects the current stored overrides.
301
+ """
302
+ try:
303
+ # Update element button colors from parent.settings cpks
304
+ overrides = self.parent_window.settings.get('cpk_colors', {}) if self.parent_window and hasattr(self.parent_window, 'settings') else {}
305
+ for s, btn in self.element_buttons.items():
306
+ try:
307
+ override = overrides.get(s)
308
+ q_color = QColor(override) if override else CPK_COLORS.get(s, CPK_COLORS['DEFAULT'])
309
+ brightness = (q_color.red() * 299 + q_color.green() * 587 + q_color.blue() * 114) / 1000
310
+ text_color = 'white' if brightness < 128 else 'black'
311
+ btn.setStyleSheet(f"background-color: {q_color.name()}; color: {text_color}; border: 1px solid #555; font-weight: bold;")
312
+ except Exception:
313
+ pass
314
+ # Update BS color button from parent settings
315
+ try:
316
+ if hasattr(self, 'bs_button') and self.parent_window and hasattr(self.parent_window, 'settings'):
317
+ bs_hex = self.parent_window.settings.get('ball_stick_bond_color', self.parent_window.default_settings.get('ball_stick_bond_color', '#7F7F7F'))
318
+ self.bs_button.setStyleSheet(f"background-color: {bs_hex}; border: 1px solid #888;")
319
+ self.bs_button.setToolTip(bs_hex)
320
+ except Exception:
321
+ pass
322
+ except Exception:
323
+ pass
324
+ # Avoid circular import: import ColorSettingsDialog only inside method using it
325
+
326
+ # NOTE: Multi-bond offset/thickness settings moved to per-model tabs to allow
327
+ # independent configuration for Ball&Stick/CPK/Wireframe/Stick.
328
+
329
+ # 'Other' tab is created in create_other_tab; nothing to do here.
330
+
331
+ def create_ball_stick_tab(self):
332
+ """Ball and Stick設定タブを作成"""
333
+ ball_stick_widget = QWidget()
334
+ form_layout = QFormLayout(ball_stick_widget)
335
+
336
+ info_label = QLabel("Ball & Stick model shows atoms as spheres and bonds as cylinders.")
337
+ info_label.setWordWrap(True)
338
+ info_label.setStyleSheet("color: #666; font-style: italic; margin-top: 10px;")
339
+ form_layout.addRow(info_label)
340
+
341
+ # 原子サイズスケール
342
+ self.bs_atom_scale_slider = QSlider(Qt.Orientation.Horizontal)
343
+ self.bs_atom_scale_slider.setRange(10, 200) # 0.1 ~ 2.0
344
+ self.bs_atom_scale_label = QLabel("1.00")
345
+ self.bs_atom_scale_slider.valueChanged.connect(lambda v: self.bs_atom_scale_label.setText(f"{v/100:.2f}"))
346
+ atom_scale_layout = QHBoxLayout()
347
+ atom_scale_layout.addWidget(self.bs_atom_scale_slider)
348
+ atom_scale_layout.addWidget(self.bs_atom_scale_label)
349
+ form_layout.addRow("Atom Size Scale:", atom_scale_layout)
350
+
351
+ # ボンド半径
352
+ self.bs_bond_radius_slider = QSlider(Qt.Orientation.Horizontal)
353
+ self.bs_bond_radius_slider.setRange(1, 50) # 0.01 ~ 0.5
354
+ self.bs_bond_radius_label = QLabel("0.10")
355
+ self.bs_bond_radius_slider.valueChanged.connect(lambda v: self.bs_bond_radius_label.setText(f"{v/100:.2f}"))
356
+ bond_radius_layout = QHBoxLayout()
357
+ bond_radius_layout.addWidget(self.bs_bond_radius_slider)
358
+ bond_radius_layout.addWidget(self.bs_bond_radius_label)
359
+ form_layout.addRow("Bond Radius:", bond_radius_layout)
360
+
361
+ # --- 区切り線(水平ライン) ---
362
+ line = QFrame()
363
+ line.setFrameShape(QFrame.Shape.HLine)
364
+ line.setFrameShadow(QFrame.Shadow.Sunken)
365
+ form_layout.addRow(line)
366
+
367
+ # --- Per-model multi-bond controls (Ball & Stick) ---
368
+ # 二重/三重結合のオフセット倍率(Ball & Stick)
369
+ self.bs_double_offset_slider = QSlider(Qt.Orientation.Horizontal)
370
+ self.bs_double_offset_slider.setRange(100, 400)
371
+ self.bs_double_offset_label = QLabel("2.00")
372
+ self.bs_double_offset_slider.valueChanged.connect(lambda v: self.bs_double_offset_label.setText(f"{v/100:.2f}"))
373
+ bs_double_offset_layout = QHBoxLayout()
374
+ bs_double_offset_layout.addWidget(self.bs_double_offset_slider)
375
+ bs_double_offset_layout.addWidget(self.bs_double_offset_label)
376
+ form_layout.addRow("Double Bond Offset (Ball & Stick):", bs_double_offset_layout)
377
+
378
+ self.bs_triple_offset_slider = QSlider(Qt.Orientation.Horizontal)
379
+ self.bs_triple_offset_slider.setRange(100, 400)
380
+ self.bs_triple_offset_label = QLabel("2.00")
381
+ self.bs_triple_offset_slider.valueChanged.connect(lambda v: self.bs_triple_offset_label.setText(f"{v/100:.2f}"))
382
+ bs_triple_offset_layout = QHBoxLayout()
383
+ bs_triple_offset_layout.addWidget(self.bs_triple_offset_slider)
384
+ bs_triple_offset_layout.addWidget(self.bs_triple_offset_label)
385
+ form_layout.addRow("Triple Bond Offset (Ball & Stick):", bs_triple_offset_layout)
386
+
387
+ # 半径倍率
388
+ self.bs_double_radius_slider = QSlider(Qt.Orientation.Horizontal)
389
+ self.bs_double_radius_slider.setRange(50, 100)
390
+ self.bs_double_radius_label = QLabel("0.80")
391
+ self.bs_double_radius_slider.valueChanged.connect(lambda v: self.bs_double_radius_label.setText(f"{v/100:.2f}"))
392
+ bs_double_radius_layout = QHBoxLayout()
393
+ bs_double_radius_layout.addWidget(self.bs_double_radius_slider)
394
+ bs_double_radius_layout.addWidget(self.bs_double_radius_label)
395
+ form_layout.addRow("Double Bond Thickness (Ball & Stick):", bs_double_radius_layout)
396
+
397
+ self.bs_triple_radius_slider = QSlider(Qt.Orientation.Horizontal)
398
+ self.bs_triple_radius_slider.setRange(50, 100)
399
+ self.bs_triple_radius_label = QLabel("0.70")
400
+ self.bs_triple_radius_slider.valueChanged.connect(lambda v: self.bs_triple_radius_label.setText(f"{v/100:.2f}"))
401
+ bs_triple_radius_layout = QHBoxLayout()
402
+ bs_triple_radius_layout.addWidget(self.bs_triple_radius_slider)
403
+ bs_triple_radius_layout.addWidget(self.bs_triple_radius_label)
404
+ form_layout.addRow("Triple Bond Thickness (Ball & Stick):", bs_triple_radius_layout)
405
+
406
+ # --- 区切り線(水平ライン) ---
407
+ line = QFrame()
408
+ line.setFrameShape(QFrame.Shape.HLine)
409
+ line.setFrameShadow(QFrame.Shadow.Sunken)
410
+ form_layout.addRow(line)
411
+
412
+ # 解像度
413
+ self.bs_resolution_slider = QSlider(Qt.Orientation.Horizontal)
414
+ self.bs_resolution_slider.setRange(6, 32)
415
+ self.bs_resolution_label = QLabel("16")
416
+ self.bs_resolution_slider.valueChanged.connect(lambda v: self.bs_resolution_label.setText(str(v)))
417
+ resolution_layout = QHBoxLayout()
418
+ resolution_layout.addWidget(self.bs_resolution_slider)
419
+ resolution_layout.addWidget(self.bs_resolution_label)
420
+ form_layout.addRow("Resolution (Quality):", resolution_layout)
421
+
422
+ # --- 区切り線(水平ライン) ---
423
+ line = QFrame()
424
+ line.setFrameShape(QFrame.Shape.HLine)
425
+ line.setFrameShadow(QFrame.Shadow.Sunken)
426
+ form_layout.addRow(line)
427
+
428
+ # --- Ball & Stick bond color ---
429
+ self.bs_bond_color_button = QPushButton()
430
+ self.bs_bond_color_button.setFixedSize(36, 24)
431
+ self.bs_bond_color_button.clicked.connect(self.pick_bs_bond_color)
432
+ self.bs_bond_color_button.setToolTip("Choose the uniform bond color for Ball & Stick model (3D)")
433
+ form_layout.addRow("Ball & Stick bond color:", self.bs_bond_color_button)
434
+
435
+ # Use CPK colors for bonds option
436
+ self.bs_use_cpk_bond_checkbox = QCheckBox()
437
+ self.bs_use_cpk_bond_checkbox.setToolTip("If checked, bonds will be colored using the atom colors (split bonds). If unchecked, a uniform color is used.")
438
+ form_layout.addRow("Use CPK colors for bonds:", self.bs_use_cpk_bond_checkbox)
439
+
440
+ self.tab_widget.addTab(ball_stick_widget, "Ball & Stick")
441
+
442
+ def create_cpk_tab(self):
443
+ """CPK設定タブを作成"""
444
+ cpk_widget = QWidget()
445
+ form_layout = QFormLayout(cpk_widget)
446
+
447
+ info_label = QLabel("CPK model shows atoms as space-filling spheres using van der Waals radii.")
448
+ info_label.setWordWrap(True)
449
+ info_label.setStyleSheet("color: #666; font-style: italic; margin-top: 10px;")
450
+ form_layout.addRow(info_label)
451
+
452
+ # 原子サイズスケール
453
+ self.cpk_atom_scale_slider = QSlider(Qt.Orientation.Horizontal)
454
+ self.cpk_atom_scale_slider.setRange(50, 200) # 0.5 ~ 2.0
455
+ self.cpk_atom_scale_label = QLabel("1.00")
456
+ self.cpk_atom_scale_slider.valueChanged.connect(lambda v: self.cpk_atom_scale_label.setText(f"{v/100:.2f}"))
457
+ atom_scale_layout = QHBoxLayout()
458
+ atom_scale_layout.addWidget(self.cpk_atom_scale_slider)
459
+ atom_scale_layout.addWidget(self.cpk_atom_scale_label)
460
+ form_layout.addRow("Atom Size Scale:", atom_scale_layout)
461
+
462
+ # 解像度
463
+ self.cpk_resolution_slider = QSlider(Qt.Orientation.Horizontal)
464
+ self.cpk_resolution_slider.setRange(8, 64)
465
+ self.cpk_resolution_label = QLabel("32")
466
+ self.cpk_resolution_slider.valueChanged.connect(lambda v: self.cpk_resolution_label.setText(str(v)))
467
+ resolution_layout = QHBoxLayout()
468
+ resolution_layout.addWidget(self.cpk_resolution_slider)
469
+ resolution_layout.addWidget(self.cpk_resolution_label)
470
+ form_layout.addRow("Resolution (Quality):", resolution_layout)
471
+
472
+ self.tab_widget.addTab(cpk_widget, "CPK (Space-filling)")
473
+
474
+ def create_wireframe_tab(self):
475
+ """Wireframe設定タブを作成"""
476
+ wireframe_widget = QWidget()
477
+ form_layout = QFormLayout(wireframe_widget)
478
+
479
+ info_label = QLabel("Wireframe model shows molecular structure with thin lines only.")
480
+ info_label.setWordWrap(True)
481
+ info_label.setStyleSheet("color: #666; font-style: italic; margin-top: 10px;")
482
+ form_layout.addRow(info_label)
483
+
484
+ # ボンド半径
485
+ self.wf_bond_radius_slider = QSlider(Qt.Orientation.Horizontal)
486
+ self.wf_bond_radius_slider.setRange(1, 10) # 0.01 ~ 0.1
487
+ self.wf_bond_radius_label = QLabel("0.01")
488
+ self.wf_bond_radius_slider.valueChanged.connect(lambda v: self.wf_bond_radius_label.setText(f"{v/100:.2f}"))
489
+ bond_radius_layout = QHBoxLayout()
490
+ bond_radius_layout.addWidget(self.wf_bond_radius_slider)
491
+ bond_radius_layout.addWidget(self.wf_bond_radius_label)
492
+ form_layout.addRow("Bond Radius:", bond_radius_layout)
493
+
494
+
495
+ # --- 区切り線(水平ライン) ---
496
+ line = QFrame()
497
+ line.setFrameShape(QFrame.Shape.HLine)
498
+ line.setFrameShadow(QFrame.Shadow.Sunken)
499
+ form_layout.addRow(line)
500
+
501
+ # --- Per-model multi-bond controls (Wireframe) ---
502
+ self.wf_double_offset_slider = QSlider(Qt.Orientation.Horizontal)
503
+ self.wf_double_offset_slider.setRange(100, 400)
504
+ self.wf_double_offset_label = QLabel("2.00")
505
+ self.wf_double_offset_slider.valueChanged.connect(lambda v: self.wf_double_offset_label.setText(f"{v/100:.2f}"))
506
+ wf_double_offset_layout = QHBoxLayout()
507
+ wf_double_offset_layout.addWidget(self.wf_double_offset_slider)
508
+ wf_double_offset_layout.addWidget(self.wf_double_offset_label)
509
+ form_layout.addRow("Double Bond Offset (Wireframe):", wf_double_offset_layout)
510
+
511
+ self.wf_triple_offset_slider = QSlider(Qt.Orientation.Horizontal)
512
+ self.wf_triple_offset_slider.setRange(100, 400)
513
+ self.wf_triple_offset_label = QLabel("2.00")
514
+ self.wf_triple_offset_slider.valueChanged.connect(lambda v: self.wf_triple_offset_label.setText(f"{v/100:.2f}"))
515
+ wf_triple_offset_layout = QHBoxLayout()
516
+ wf_triple_offset_layout.addWidget(self.wf_triple_offset_slider)
517
+ wf_triple_offset_layout.addWidget(self.wf_triple_offset_label)
518
+ form_layout.addRow("Triple Bond Offset (Wireframe):", wf_triple_offset_layout)
519
+
520
+ self.wf_double_radius_slider = QSlider(Qt.Orientation.Horizontal)
521
+ self.wf_double_radius_slider.setRange(50, 100)
522
+ self.wf_double_radius_label = QLabel("0.80")
523
+ self.wf_double_radius_slider.valueChanged.connect(lambda v: self.wf_double_radius_label.setText(f"{v/100:.2f}"))
524
+ wf_double_radius_layout = QHBoxLayout()
525
+ wf_double_radius_layout.addWidget(self.wf_double_radius_slider)
526
+ wf_double_radius_layout.addWidget(self.wf_double_radius_label)
527
+ form_layout.addRow("Double Bond Thickness (Wireframe):", wf_double_radius_layout)
528
+
529
+ self.wf_triple_radius_slider = QSlider(Qt.Orientation.Horizontal)
530
+ self.wf_triple_radius_slider.setRange(50, 100)
531
+ self.wf_triple_radius_label = QLabel("0.70")
532
+ self.wf_triple_radius_slider.valueChanged.connect(lambda v: self.wf_triple_radius_label.setText(f"{v/100:.2f}"))
533
+ wf_triple_radius_layout = QHBoxLayout()
534
+ wf_triple_radius_layout.addWidget(self.wf_triple_radius_slider)
535
+ wf_triple_radius_layout.addWidget(self.wf_triple_radius_label)
536
+ form_layout.addRow("Triple Bond Thickness (Wireframe):", wf_triple_radius_layout)
537
+
538
+ # --- 区切り線(水平ライン) ---
539
+ line = QFrame()
540
+ line.setFrameShape(QFrame.Shape.HLine)
541
+ line.setFrameShadow(QFrame.Shadow.Sunken)
542
+ form_layout.addRow(line)
543
+
544
+ # 解像度
545
+ self.wf_resolution_slider = QSlider(Qt.Orientation.Horizontal)
546
+ self.wf_resolution_slider.setRange(4, 16)
547
+ self.wf_resolution_label = QLabel("6")
548
+ self.wf_resolution_slider.valueChanged.connect(lambda v: self.wf_resolution_label.setText(str(v)))
549
+ resolution_layout = QHBoxLayout()
550
+ resolution_layout.addWidget(self.wf_resolution_slider)
551
+ resolution_layout.addWidget(self.wf_resolution_label)
552
+ form_layout.addRow("Resolution (Quality):", resolution_layout)
553
+
554
+ self.tab_widget.addTab(wireframe_widget, "Wireframe")
555
+
556
+ def create_stick_tab(self):
557
+ """Stick設定タブを作成"""
558
+ stick_widget = QWidget()
559
+ form_layout = QFormLayout(stick_widget)
560
+
561
+ info_label = QLabel("Stick model shows bonds as thick cylinders with atoms as small spheres.")
562
+ info_label.setWordWrap(True)
563
+ info_label.setStyleSheet("color: #666; font-style: italic; margin-top: 10px;")
564
+ form_layout.addRow(info_label)
565
+
566
+ # ボンド半径(原子半径も同じ値を使用)
567
+ self.stick_bond_radius_slider = QSlider(Qt.Orientation.Horizontal)
568
+ self.stick_bond_radius_slider.setRange(5, 50) # 0.05 ~ 0.5
569
+ self.stick_bond_radius_label = QLabel("0.15")
570
+ self.stick_bond_radius_slider.valueChanged.connect(lambda v: self.stick_bond_radius_label.setText(f"{v/100:.2f}"))
571
+ bond_radius_layout = QHBoxLayout()
572
+ bond_radius_layout.addWidget(self.stick_bond_radius_slider)
573
+ bond_radius_layout.addWidget(self.stick_bond_radius_label)
574
+ form_layout.addRow("Bond Radius:", bond_radius_layout)
575
+
576
+ # --- 区切り線(水平ライン) ---
577
+ line = QFrame()
578
+ line.setFrameShape(QFrame.Shape.HLine)
579
+ line.setFrameShadow(QFrame.Shadow.Sunken)
580
+ form_layout.addRow(line)
581
+
582
+ # --- Per-model multi-bond controls (Stick) ---
583
+ self.stick_double_offset_slider = QSlider(Qt.Orientation.Horizontal)
584
+ self.stick_double_offset_slider.setRange(50, 400)
585
+ self.stick_double_offset_label = QLabel("2.00")
586
+ self.stick_double_offset_slider.valueChanged.connect(lambda v: self.stick_double_offset_label.setText(f"{v/100:.2f}"))
587
+ stick_double_offset_layout = QHBoxLayout()
588
+ stick_double_offset_layout.addWidget(self.stick_double_offset_slider)
589
+ stick_double_offset_layout.addWidget(self.stick_double_offset_label)
590
+ form_layout.addRow("Double Bond Offset (Stick):", stick_double_offset_layout)
591
+
592
+ self.stick_triple_offset_slider = QSlider(Qt.Orientation.Horizontal)
593
+ self.stick_triple_offset_slider.setRange(50, 400)
594
+ self.stick_triple_offset_label = QLabel("2.00")
595
+ self.stick_triple_offset_slider.valueChanged.connect(lambda v: self.stick_triple_offset_label.setText(f"{v/100:.2f}"))
596
+ stick_triple_offset_layout = QHBoxLayout()
597
+ stick_triple_offset_layout.addWidget(self.stick_triple_offset_slider)
598
+ stick_triple_offset_layout.addWidget(self.stick_triple_offset_label)
599
+ form_layout.addRow("Triple Bond Offset (Stick):", stick_triple_offset_layout)
600
+
601
+ self.stick_double_radius_slider = QSlider(Qt.Orientation.Horizontal)
602
+ self.stick_double_radius_slider.setRange(20, 100)
603
+ self.stick_double_radius_label = QLabel("0.80")
604
+ self.stick_double_radius_slider.valueChanged.connect(lambda v: self.stick_double_radius_label.setText(f"{v/100:.2f}"))
605
+ stick_double_radius_layout = QHBoxLayout()
606
+ stick_double_radius_layout.addWidget(self.stick_double_radius_slider)
607
+ stick_double_radius_layout.addWidget(self.stick_double_radius_label)
608
+ form_layout.addRow("Double Bond Thickness (Stick):", stick_double_radius_layout)
609
+
610
+ self.stick_triple_radius_slider = QSlider(Qt.Orientation.Horizontal)
611
+ self.stick_triple_radius_slider.setRange(20, 100)
612
+ self.stick_triple_radius_label = QLabel("0.70")
613
+ self.stick_triple_radius_slider.valueChanged.connect(lambda v: self.stick_triple_radius_label.setText(f"{v/100:.2f}"))
614
+ stick_triple_radius_layout = QHBoxLayout()
615
+ stick_triple_radius_layout.addWidget(self.stick_triple_radius_slider)
616
+ stick_triple_radius_layout.addWidget(self.stick_triple_radius_label)
617
+ form_layout.addRow("Triple Bond Thickness (Stick):", stick_triple_radius_layout)
618
+
619
+ # --- 区切り線(水平ライン) ---
620
+ line = QFrame()
621
+ line.setFrameShape(QFrame.Shape.HLine)
622
+ line.setFrameShadow(QFrame.Shadow.Sunken)
623
+ form_layout.addRow(line)
624
+
625
+ # 解像度
626
+ self.stick_resolution_slider = QSlider(Qt.Orientation.Horizontal)
627
+ self.stick_resolution_slider.setRange(6, 32)
628
+ self.stick_resolution_label = QLabel("16")
629
+ self.stick_resolution_slider.valueChanged.connect(lambda v: self.stick_resolution_label.setText(str(v)))
630
+ resolution_layout = QHBoxLayout()
631
+ resolution_layout.addWidget(self.stick_resolution_slider)
632
+ resolution_layout.addWidget(self.stick_resolution_label)
633
+ form_layout.addRow("Resolution (Quality):", resolution_layout)
634
+
635
+
636
+
637
+
638
+ self.tab_widget.addTab(stick_widget, "Stick")
639
+
640
+ def reset_current_tab(self):
641
+ """現在選択されているタブの設定のみをデフォルトに戻す"""
642
+ current_tab_index = self.tab_widget.currentIndex()
643
+ tab_name = self.tab_widget.tabText(current_tab_index)
644
+
645
+ # 各タブの設定項目を定義
646
+ # Note: tab labels must match those added to the QTabWidget ("Scene", "Ball & Stick",
647
+ # "CPK (Space-filling)", "Wireframe", "Stick", "Other"). Use the per-model
648
+ # multi-bond keys present in self.default_settings.
649
+ tab_settings = {
650
+ "Scene": {
651
+ 'background_color': self.default_settings['background_color'],
652
+ 'projection_mode': self.default_settings['projection_mode'],
653
+ 'show_3d_axes': self.default_settings['show_3d_axes'],
654
+ 'lighting_enabled': self.default_settings['lighting_enabled'],
655
+ 'light_intensity': self.default_settings['light_intensity'],
656
+ 'specular': self.default_settings['specular'],
657
+ 'specular_power': self.default_settings['specular_power']
658
+ },
659
+ "Other": {
660
+ # other options
661
+ 'skip_chemistry_checks': self.default_settings.get('skip_chemistry_checks', False),
662
+ 'display_kekule_3d': self.default_settings.get('display_kekule_3d', False),
663
+ 'always_ask_charge': self.default_settings.get('always_ask_charge', False),
664
+ 'display_aromatic_circles_3d': self.default_settings.get('display_aromatic_circles_3d', False),
665
+ 'aromatic_torus_thickness_factor': self.default_settings.get('aromatic_torus_thickness_factor', 0.6),
666
+ },
667
+ "Ball & Stick": {
668
+ 'ball_stick_atom_scale': self.default_settings['ball_stick_atom_scale'],
669
+ 'ball_stick_bond_radius': self.default_settings['ball_stick_bond_radius'],
670
+ 'ball_stick_resolution': self.default_settings['ball_stick_resolution'],
671
+ 'ball_stick_double_bond_offset_factor': self.default_settings.get('ball_stick_double_bond_offset_factor', 2.0),
672
+ 'ball_stick_triple_bond_offset_factor': self.default_settings.get('ball_stick_triple_bond_offset_factor', 2.0),
673
+ 'ball_stick_double_bond_radius_factor': self.default_settings.get('ball_stick_double_bond_radius_factor', 0.8),
674
+ 'ball_stick_triple_bond_radius_factor': self.default_settings.get('ball_stick_triple_bond_radius_factor', 0.75),
675
+ 'ball_stick_use_cpk_bond_color': self.default_settings['ball_stick_use_cpk_bond_color'],
676
+ 'ball_stick_bond_color': self.default_settings.get('ball_stick_bond_color', '#7F7F7F')
677
+ },
678
+ "CPK (Space-filling)": {
679
+ 'cpk_atom_scale': self.default_settings['cpk_atom_scale'],
680
+ 'cpk_resolution': self.default_settings['cpk_resolution'],
681
+ 'cpk_colors': {}
682
+ },
683
+ "Wireframe": {
684
+ 'wireframe_bond_radius': self.default_settings['wireframe_bond_radius'],
685
+ 'wireframe_resolution': self.default_settings['wireframe_resolution'],
686
+ 'wireframe_double_bond_offset_factor': self.default_settings.get('wireframe_double_bond_offset_factor', 3.0),
687
+ 'wireframe_triple_bond_offset_factor': self.default_settings.get('wireframe_triple_bond_offset_factor', 3.0),
688
+ 'wireframe_double_bond_radius_factor': self.default_settings.get('wireframe_double_bond_radius_factor', 0.8),
689
+ 'wireframe_triple_bond_radius_factor': self.default_settings.get('wireframe_triple_bond_radius_factor', 0.75)
690
+ },
691
+ "Stick": {
692
+ 'stick_bond_radius': self.default_settings['stick_bond_radius'],
693
+ 'stick_resolution': self.default_settings['stick_resolution'],
694
+ 'stick_double_bond_offset_factor': self.default_settings.get('stick_double_bond_offset_factor', 1.5),
695
+ 'stick_triple_bond_offset_factor': self.default_settings.get('stick_triple_bond_offset_factor', 1.0),
696
+ 'stick_double_bond_radius_factor': self.default_settings.get('stick_double_bond_radius_factor', 0.6),
697
+ 'stick_triple_bond_radius_factor': self.default_settings.get('stick_triple_bond_radius_factor', 0.4)
698
+ }
699
+ }
700
+
701
+ # 選択されたタブの設定のみを適用
702
+ if tab_name in tab_settings:
703
+ tab_defaults = tab_settings[tab_name]
704
+
705
+ # 現在の設定を取得
706
+ current_settings = self.get_current_ui_settings()
707
+
708
+ # 選択されたタブの項目のみをデフォルト値で更新
709
+ updated_settings = current_settings.copy()
710
+ updated_settings.update(tab_defaults)
711
+
712
+ # UIを更新
713
+ self.update_ui_from_settings(updated_settings)
714
+ # CPK tab: do not change parent/settings immediately; let Apply/OK persist any changes
715
+
716
+ # ユーザーへのフィードバック
717
+ QMessageBox.information(self, "Reset Complete", f"Settings for '{tab_name}' tab have been reset to defaults.")
718
+ else:
719
+ QMessageBox.warning(self, "Error", f"Unknown tab: {tab_name}")
720
+
721
+ def reset_all_settings(self):
722
+ """すべての設定をデフォルトに戻す"""
723
+ reply = QMessageBox.question(
724
+ self,
725
+ "Reset All Settings",
726
+ "Are you sure you want to reset all settings to defaults?",
727
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
728
+ QMessageBox.StandardButton.No
729
+ )
730
+
731
+ if reply == QMessageBox.StandardButton.Yes:
732
+ # Update the dialog UI
733
+ self.update_ui_from_settings(self.default_settings)
734
+
735
+ # Also persist defaults to the application-level settings if parent is available
736
+ try:
737
+ if self.parent_window and hasattr(self.parent_window, 'settings'):
738
+ # Update parent settings and save
739
+ self.parent_window.settings.update(self.default_settings)
740
+ # defer writing to disk; mark dirty so closeEvent will persist
741
+ try:
742
+ self.parent_window.settings_dirty = True
743
+ except Exception:
744
+ pass
745
+ # Also ensure color settings return to defaults and UI reflects them
746
+ try:
747
+ # Remove any CPK overrides to restore defaults
748
+ if 'cpk_colors' in self.parent_window.settings:
749
+ # Reset to defaults (empty override dict)
750
+ self.parent_window.settings['cpk_colors'] = {}
751
+ # 2D bond color is fixed and not part of the settings; do not modify it here
752
+ # Reset 3D Ball & Stick uniform bond color to default
753
+ self.parent_window.settings['ball_stick_bond_color'] = self.default_settings.get('ball_stick_bond_color', '#7F7F7F')
754
+ # Update global CPK colors and reapply 3D settings immediately
755
+ try:
756
+ self.parent_window.update_cpk_colors_from_settings()
757
+ except Exception:
758
+ pass
759
+ try:
760
+ self.parent_window.apply_3d_settings()
761
+ except Exception:
762
+ pass
763
+ # Re-draw current 3D molecule if any
764
+ try:
765
+ if hasattr(self.parent_window, 'current_mol') and self.parent_window.current_mol:
766
+ self.parent_window.draw_molecule_3d(self.parent_window.current_mol)
767
+ except Exception:
768
+ pass
769
+ # Update 2D scene items to reflect color reset
770
+ try:
771
+ if hasattr(self.parent_window, 'scene'):
772
+ for it in self.parent_window.scene.items():
773
+ try:
774
+ if hasattr(it, 'update_style'):
775
+ it.update_style()
776
+ except Exception:
777
+ pass
778
+ except Exception:
779
+ pass
780
+ # Mark settings dirty so they'll be saved on exit
781
+ try:
782
+ self.parent_window.settings_dirty = True
783
+ except Exception:
784
+ pass
785
+ except Exception:
786
+ pass
787
+
788
+ # Refresh parent's optimization and conversion menu/action states
789
+ try:
790
+ # Optimization method
791
+ if hasattr(self.parent_window, 'optimization_method'):
792
+ self.parent_window.optimization_method = self.parent_window.settings.get('optimization_method', 'MMFF_RDKIT')
793
+ if hasattr(self.parent_window, 'opt3d_actions'):
794
+ for k, act in self.parent_window.opt3d_actions.items():
795
+ try:
796
+ act.setChecked(k.upper() == (self.parent_window.optimization_method or '').upper())
797
+ except Exception:
798
+ pass
799
+
800
+ # Conversion mode
801
+ conv_mode = self.parent_window.settings.get('3d_conversion_mode', 'fallback')
802
+ if hasattr(self.parent_window, 'conv_actions'):
803
+ for k, act in self.parent_window.conv_actions.items():
804
+ try:
805
+ act.setChecked(k == conv_mode)
806
+ except Exception:
807
+ pass
808
+ except Exception:
809
+ pass
810
+ except Exception:
811
+ pass
812
+
813
+ QMessageBox.information(self, "Reset Complete", "All settings have been reset to defaults.")
814
+
815
+ def get_current_ui_settings(self):
816
+ """現在のUIから設定値を取得"""
817
+ return {
818
+ 'background_color': self.current_bg_color,
819
+ 'show_3d_axes': self.axes_checkbox.isChecked(),
820
+ 'lighting_enabled': self.light_checkbox.isChecked(),
821
+ 'light_intensity': self.intensity_slider.value() / 100.0,
822
+ 'specular': self.specular_slider.value() / 100.0,
823
+ 'specular_power': self.spec_power_slider.value(),
824
+ # Ball and Stick settings
825
+ 'ball_stick_atom_scale': self.bs_atom_scale_slider.value() / 100.0,
826
+ 'ball_stick_bond_radius': self.bs_bond_radius_slider.value() / 100.0,
827
+ 'ball_stick_resolution': self.bs_resolution_slider.value(),
828
+ # CPK settings
829
+ 'cpk_atom_scale': self.cpk_atom_scale_slider.value() / 100.0,
830
+ 'cpk_resolution': self.cpk_resolution_slider.value(),
831
+ # Wireframe settings
832
+ 'wireframe_bond_radius': self.wf_bond_radius_slider.value() / 100.0,
833
+ 'wireframe_resolution': self.wf_resolution_slider.value(),
834
+ # Stick settings
835
+ 'stick_bond_radius': self.stick_bond_radius_slider.value() / 100.0,
836
+ 'stick_resolution': self.stick_resolution_slider.value(),
837
+ # Multi-bond settings (per-model)
838
+ 'ball_stick_double_bond_offset_factor': self.bs_double_offset_slider.value() / 100.0,
839
+ 'ball_stick_triple_bond_offset_factor': self.bs_triple_offset_slider.value() / 100.0,
840
+ 'ball_stick_double_bond_radius_factor': self.bs_double_radius_slider.value() / 100.0,
841
+ 'ball_stick_triple_bond_radius_factor': self.bs_triple_radius_slider.value() / 100.0,
842
+ 'wireframe_double_bond_offset_factor': self.wf_double_offset_slider.value() / 100.0,
843
+ 'wireframe_triple_bond_offset_factor': self.wf_triple_offset_slider.value() / 100.0,
844
+ 'wireframe_double_bond_radius_factor': self.wf_double_radius_slider.value() / 100.0,
845
+ 'wireframe_triple_bond_radius_factor': self.wf_triple_radius_slider.value() / 100.0,
846
+ 'stick_double_bond_offset_factor': self.stick_double_offset_slider.value() / 100.0,
847
+ 'stick_triple_bond_offset_factor': self.stick_triple_offset_slider.value() / 100.0,
848
+ 'stick_double_bond_radius_factor': self.stick_double_radius_slider.value() / 100.0,
849
+ 'stick_triple_bond_radius_factor': self.stick_triple_radius_slider.value() / 100.0,
850
+ # Projection mode
851
+ 'projection_mode': self.projection_combo.currentText(),
852
+ # Kekule / aromatic / torus settings
853
+ 'display_kekule_3d': self.kekule_3d_checkbox.isChecked(),
854
+ 'display_aromatic_circles_3d': self.aromatic_circle_checkbox.isChecked(),
855
+ 'aromatic_torus_thickness_factor': self.aromatic_torus_thickness_slider.value() / 100.0,
856
+ 'skip_chemistry_checks': self.skip_chem_checks_checkbox.isChecked(),
857
+ 'always_ask_charge': self.always_ask_charge_checkbox.isChecked(),
858
+ # Ball & Stick bond color and use-cpk option
859
+ 'ball_stick_bond_color': getattr(self, 'bs_bond_color', self.default_settings.get('ball_stick_bond_color')),
860
+ 'ball_stick_use_cpk_bond_color': self.bs_use_cpk_bond_checkbox.isChecked(),
861
+ }
862
+
863
+ def reset_to_defaults(self):
864
+ """UIをデフォルト設定に戻す(後方互換性のため残存)"""
865
+ self.reset_all_settings()
866
+
867
+ def update_ui_from_settings(self, settings_dict):
868
+ # 基本設定
869
+ self.current_bg_color = settings_dict.get('background_color', self.default_settings['background_color'])
870
+ self.update_color_button(self.current_bg_color)
871
+ self.axes_checkbox.setChecked(settings_dict.get('show_3d_axes', self.default_settings['show_3d_axes']))
872
+ self.light_checkbox.setChecked(settings_dict.get('lighting_enabled', self.default_settings['lighting_enabled']))
873
+
874
+ # スライダーの値を設定
875
+ intensity_val = int(settings_dict.get('light_intensity', self.default_settings['light_intensity']) * 100)
876
+ self.intensity_slider.setValue(intensity_val)
877
+ self.intensity_label.setText(f"{intensity_val/100:.2f}")
878
+
879
+ specular_val = int(settings_dict.get('specular', self.default_settings['specular']) * 100)
880
+ self.specular_slider.setValue(specular_val)
881
+ self.specular_label.setText(f"{specular_val/100:.2f}")
882
+
883
+ self.spec_power_slider.setValue(settings_dict.get('specular_power', self.default_settings['specular_power']))
884
+ self.spec_power_label.setText(str(settings_dict.get('specular_power', self.default_settings['specular_power'])))
885
+
886
+ # Ball and Stick設定
887
+ bs_atom_scale = int(settings_dict.get('ball_stick_atom_scale', self.default_settings['ball_stick_atom_scale']) * 100)
888
+ self.bs_atom_scale_slider.setValue(bs_atom_scale)
889
+ self.bs_atom_scale_label.setText(f"{bs_atom_scale/100:.2f}")
890
+
891
+ bs_bond_radius = int(settings_dict.get('ball_stick_bond_radius', self.default_settings['ball_stick_bond_radius']) * 100)
892
+ self.bs_bond_radius_slider.setValue(bs_bond_radius)
893
+ self.bs_bond_radius_label.setText(f"{bs_bond_radius/100:.2f}")
894
+
895
+ self.bs_resolution_slider.setValue(settings_dict.get('ball_stick_resolution', self.default_settings['ball_stick_resolution']))
896
+ self.bs_resolution_label.setText(str(settings_dict.get('ball_stick_resolution', self.default_settings['ball_stick_resolution'])))
897
+ # Ball & Stick bond color (uniform gray color for ball-and-stick)
898
+ bs_bond_color = settings_dict.get('ball_stick_bond_color', self.default_settings.get('ball_stick_bond_color', '#7F7F7F'))
899
+ try:
900
+ self.bs_bond_color = QColor(bs_bond_color).name()
901
+ except Exception:
902
+ self.bs_bond_color = self.default_settings.get('ball_stick_bond_color', '#7F7F7F')
903
+ # Ensure color button exists and update its appearance
904
+ try:
905
+ if hasattr(self, 'bs_bond_color_button'):
906
+ self.bs_bond_color_button.setStyleSheet(f"background-color: {self.bs_bond_color}; border: 1px solid #888;")
907
+ try:
908
+ self.bs_bond_color_button.setToolTip(self.bs_bond_color)
909
+ except Exception:
910
+ pass
911
+ except Exception:
912
+ pass
913
+
914
+ # Ball & Stick CPK bond color option
915
+ self.bs_use_cpk_bond_checkbox.setChecked(settings_dict.get('ball_stick_use_cpk_bond_color', self.default_settings['ball_stick_use_cpk_bond_color']))
916
+
917
+ # CPK設定
918
+ cpk_atom_scale = int(settings_dict.get('cpk_atom_scale', self.default_settings['cpk_atom_scale']) * 100)
919
+ self.cpk_atom_scale_slider.setValue(cpk_atom_scale)
920
+ self.cpk_atom_scale_label.setText(f"{cpk_atom_scale/100:.2f}")
921
+
922
+ self.cpk_resolution_slider.setValue(settings_dict.get('cpk_resolution', self.default_settings['cpk_resolution']))
923
+ self.cpk_resolution_label.setText(str(settings_dict.get('cpk_resolution', self.default_settings['cpk_resolution'])))
924
+
925
+ # Wireframe設定
926
+ wf_bond_radius = int(settings_dict.get('wireframe_bond_radius', self.default_settings['wireframe_bond_radius']) * 100)
927
+ self.wf_bond_radius_slider.setValue(wf_bond_radius)
928
+ self.wf_bond_radius_label.setText(f"{wf_bond_radius/100:.2f}")
929
+
930
+ self.wf_resolution_slider.setValue(settings_dict.get('wireframe_resolution', self.default_settings['wireframe_resolution']))
931
+ self.wf_resolution_label.setText(str(settings_dict.get('wireframe_resolution', self.default_settings['wireframe_resolution'])))
932
+
933
+ # Stick設定
934
+ stick_bond_radius = int(settings_dict.get('stick_bond_radius', self.default_settings['stick_bond_radius']) * 100)
935
+ self.stick_bond_radius_slider.setValue(stick_bond_radius)
936
+ self.stick_bond_radius_label.setText(f"{stick_bond_radius/100:.2f}")
937
+
938
+ self.stick_resolution_slider.setValue(settings_dict.get('stick_resolution', self.default_settings['stick_resolution']))
939
+ self.stick_resolution_label.setText(str(settings_dict.get('stick_resolution', self.default_settings['stick_resolution'])))
940
+
941
+ # 多重結合設定(モデル毎) — 後方互換のため既存のグローバルキーがあればフォールバック
942
+ # Ball & Stick
943
+ bs_double_offset = int(settings_dict.get('ball_stick_double_bond_offset_factor', settings_dict.get('double_bond_offset_factor', self.default_settings.get('ball_stick_double_bond_offset_factor', 2.0))) * 100)
944
+ self.bs_double_offset_slider.setValue(bs_double_offset)
945
+ self.bs_double_offset_label.setText(f"{bs_double_offset/100:.2f}")
946
+
947
+ bs_triple_offset = int(settings_dict.get('ball_stick_triple_bond_offset_factor', settings_dict.get('triple_bond_offset_factor', self.default_settings.get('ball_stick_triple_bond_offset_factor', 2.0))) * 100)
948
+ self.bs_triple_offset_slider.setValue(bs_triple_offset)
949
+ self.bs_triple_offset_label.setText(f"{bs_triple_offset/100:.2f}")
950
+
951
+ bs_double_radius = int(settings_dict.get('ball_stick_double_bond_radius_factor', settings_dict.get('double_bond_radius_factor', self.default_settings.get('ball_stick_double_bond_radius_factor', 1.0))) * 100)
952
+ self.bs_double_radius_slider.setValue(bs_double_radius)
953
+ self.bs_double_radius_label.setText(f"{bs_double_radius/100:.2f}")
954
+
955
+ bs_triple_radius = int(settings_dict.get('ball_stick_triple_bond_radius_factor', settings_dict.get('triple_bond_radius_factor', self.default_settings.get('ball_stick_triple_bond_radius_factor', 0.75))) * 100)
956
+ self.bs_triple_radius_slider.setValue(bs_triple_radius)
957
+ self.bs_triple_radius_label.setText(f"{bs_triple_radius/100:.2f}")
958
+
959
+ # Wireframe
960
+ wf_double_offset = int(settings_dict.get('wireframe_double_bond_offset_factor', settings_dict.get('double_bond_offset_factor', self.default_settings.get('wireframe_double_bond_offset_factor', 3.0))) * 100)
961
+ self.wf_double_offset_slider.setValue(wf_double_offset)
962
+ self.wf_double_offset_label.setText(f"{wf_double_offset/100:.2f}")
963
+
964
+ wf_triple_offset = int(settings_dict.get('wireframe_triple_bond_offset_factor', settings_dict.get('triple_bond_offset_factor', self.default_settings.get('wireframe_triple_bond_offset_factor', 3.0))) * 100)
965
+ self.wf_triple_offset_slider.setValue(wf_triple_offset)
966
+ self.wf_triple_offset_label.setText(f"{wf_triple_offset/100:.2f}")
967
+
968
+ wf_double_radius = int(settings_dict.get('wireframe_double_bond_radius_factor', settings_dict.get('double_bond_radius_factor', self.default_settings.get('wireframe_double_bond_radius_factor', 1.0))) * 100)
969
+ self.wf_double_radius_slider.setValue(wf_double_radius)
970
+ self.wf_double_radius_label.setText(f"{wf_double_radius/100:.2f}")
971
+
972
+ wf_triple_radius = int(settings_dict.get('wireframe_triple_bond_radius_factor', settings_dict.get('triple_bond_radius_factor', self.default_settings.get('wireframe_triple_bond_radius_factor', 0.75))) * 100)
973
+ self.wf_triple_radius_slider.setValue(wf_triple_radius)
974
+ self.wf_triple_radius_label.setText(f"{wf_triple_radius/100:.2f}")
975
+
976
+ # Stick
977
+ stick_double_offset = int(settings_dict.get('stick_double_bond_offset_factor', settings_dict.get('double_bond_offset_factor', self.default_settings.get('stick_double_bond_offset_factor', 1.5))) * 100)
978
+ self.stick_double_offset_slider.setValue(stick_double_offset)
979
+ self.stick_double_offset_label.setText(f"{stick_double_offset/100:.2f}")
980
+
981
+ stick_triple_offset = int(settings_dict.get('stick_triple_bond_offset_factor', settings_dict.get('triple_bond_offset_factor', self.default_settings.get('stick_triple_bond_offset_factor', 1.0))) * 100)
982
+ self.stick_triple_offset_slider.setValue(stick_triple_offset)
983
+ self.stick_triple_offset_label.setText(f"{stick_triple_offset/100:.2f}")
984
+
985
+ stick_double_radius = int(settings_dict.get('stick_double_bond_radius_factor', settings_dict.get('double_bond_radius_factor', self.default_settings.get('stick_double_bond_radius_factor', 0.60))) * 100)
986
+ self.stick_double_radius_slider.setValue(stick_double_radius)
987
+ self.stick_double_radius_label.setText(f"{stick_double_radius/100:.2f}")
988
+
989
+ stick_triple_radius = int(settings_dict.get('stick_triple_bond_radius_factor', settings_dict.get('triple_bond_radius_factor', self.default_settings.get('stick_triple_bond_radius_factor', 0.40))) * 100)
990
+ self.stick_triple_radius_slider.setValue(stick_triple_radius)
991
+ self.stick_triple_radius_label.setText(f"{stick_triple_radius/100:.2f}")
992
+
993
+ # Projection mode
994
+ proj_mode = settings_dict.get('projection_mode', self.default_settings.get('projection_mode', 'Perspective'))
995
+ idx = self.projection_combo.findText(proj_mode)
996
+ self.projection_combo.setCurrentIndex(idx if idx != -1 else 0)
997
+ # skip chemistry checks
998
+ self.skip_chem_checks_checkbox.setChecked(settings_dict.get('skip_chemistry_checks', self.default_settings.get('skip_chemistry_checks', False)))
999
+ # kekule setting
1000
+ self.kekule_3d_checkbox.setChecked(settings_dict.get('display_kekule_3d', self.default_settings.get('display_kekule_3d', False)))
1001
+ # always ask for charge on XYZ imports
1002
+ self.always_ask_charge_checkbox.setChecked(settings_dict.get('always_ask_charge', self.default_settings.get('always_ask_charge', False)))
1003
+ # Aromatic ring circle display and torus thickness factor
1004
+ self.aromatic_circle_checkbox.setChecked(settings_dict.get('display_aromatic_circles_3d', self.default_settings.get('display_aromatic_circles_3d', False)))
1005
+ thickness_factor = float(settings_dict.get('aromatic_torus_thickness_factor', self.default_settings.get('aromatic_torus_thickness_factor', 0.6)))
1006
+ try:
1007
+ self.aromatic_torus_thickness_slider.setValue(int(thickness_factor * 100))
1008
+ self.aromatic_torus_thickness_label.setText(f"{thickness_factor:.1f}")
1009
+ except Exception:
1010
+ pass
1011
+
1012
+ def select_color(self):
1013
+ """カラーピッカーを開き、選択された色を内部変数とUIに反映させる"""
1014
+ # 内部変数から現在の色を取得してカラーピッカーを初期化
1015
+ color = QColorDialog.getColor(QColor(self.current_bg_color), self)
1016
+ if color.isValid():
1017
+ # 内部変数を更新
1018
+ self.current_bg_color = color.name()
1019
+ # UIの見た目を更新
1020
+ self.update_color_button(self.current_bg_color)
1021
+
1022
+ def update_color_button(self, color_hex):
1023
+ """ボタンの背景色と境界線を設定する"""
1024
+ self.bg_button.setStyleSheet(f"background-color: {color_hex}; border: 1px solid #888;")
1025
+
1026
+ def get_settings(self):
1027
+ return {
1028
+ 'background_color': self.current_bg_color,
1029
+ 'projection_mode': self.projection_combo.currentText(),
1030
+ 'show_3d_axes': self.axes_checkbox.isChecked(),
1031
+ 'lighting_enabled': self.light_checkbox.isChecked(),
1032
+ 'light_intensity': self.intensity_slider.value() / 100.0,
1033
+ 'specular': self.specular_slider.value() / 100.0,
1034
+ 'specular_power': self.spec_power_slider.value(),
1035
+ # Ball and Stick settings
1036
+ 'ball_stick_atom_scale': self.bs_atom_scale_slider.value() / 100.0,
1037
+ 'ball_stick_bond_radius': self.bs_bond_radius_slider.value() / 100.0,
1038
+ 'ball_stick_resolution': self.bs_resolution_slider.value(),
1039
+ # CPK settings
1040
+ 'cpk_atom_scale': self.cpk_atom_scale_slider.value() / 100.0,
1041
+ 'cpk_resolution': self.cpk_resolution_slider.value(),
1042
+ # Wireframe settings
1043
+ 'wireframe_bond_radius': self.wf_bond_radius_slider.value() / 100.0,
1044
+ 'wireframe_resolution': self.wf_resolution_slider.value(),
1045
+ # Stick settings
1046
+ 'stick_bond_radius': self.stick_bond_radius_slider.value() / 100.0,
1047
+ 'stick_resolution': self.stick_resolution_slider.value(),
1048
+ # Multiple bond offset settings (per-model)
1049
+ 'ball_stick_double_bond_offset_factor': self.bs_double_offset_slider.value() / 100.0,
1050
+ 'ball_stick_triple_bond_offset_factor': self.bs_triple_offset_slider.value() / 100.0,
1051
+ 'ball_stick_double_bond_radius_factor': self.bs_double_radius_slider.value() / 100.0,
1052
+ 'ball_stick_triple_bond_radius_factor': self.bs_triple_radius_slider.value() / 100.0,
1053
+ 'wireframe_double_bond_offset_factor': self.wf_double_offset_slider.value() / 100.0,
1054
+ 'wireframe_triple_bond_offset_factor': self.wf_triple_offset_slider.value() / 100.0,
1055
+ 'wireframe_double_bond_radius_factor': self.wf_double_radius_slider.value() / 100.0,
1056
+ 'wireframe_triple_bond_radius_factor': self.wf_triple_radius_slider.value() / 100.0,
1057
+ 'stick_double_bond_offset_factor': self.stick_double_offset_slider.value() / 100.0,
1058
+ 'stick_triple_bond_offset_factor': self.stick_triple_offset_slider.value() / 100.0,
1059
+ 'stick_double_bond_radius_factor': self.stick_double_radius_slider.value() / 100.0,
1060
+ 'stick_triple_bond_radius_factor': self.stick_triple_radius_slider.value() / 100.0,
1061
+ 'display_kekule_3d': self.kekule_3d_checkbox.isChecked(),
1062
+ 'display_aromatic_circles_3d': self.aromatic_circle_checkbox.isChecked(),
1063
+ 'aromatic_torus_thickness_factor': self.aromatic_torus_thickness_slider.value() / 100.0,
1064
+ 'skip_chemistry_checks': self.skip_chem_checks_checkbox.isChecked(),
1065
+ 'always_ask_charge': self.always_ask_charge_checkbox.isChecked(),
1066
+ # Ball & Stick bond color (3D grey/uniform color)
1067
+ 'ball_stick_bond_color': getattr(self, 'bs_bond_color', self.default_settings.get('ball_stick_bond_color', '#7F7F7F')),
1068
+ 'ball_stick_use_cpk_bond_color': self.bs_use_cpk_bond_checkbox.isChecked(),
1069
+ }
1070
+
1071
+ def pick_bs_bond_color(self):
1072
+ """Open QColorDialog to pick Ball & Stick bond color (3D)."""
1073
+ cur = getattr(self, 'bs_bond_color', self.default_settings.get('ball_stick_bond_color', '#7F7F7F'))
1074
+ color = QColorDialog.getColor(QColor(cur), self)
1075
+ if color.isValid():
1076
+ self.bs_bond_color = color.name()
1077
+ try:
1078
+ self.bs_bond_color_button.setStyleSheet(f"background-color: {self.bs_bond_color}; border: 1px solid #888;")
1079
+ self.bs_bond_color_button.setToolTip(self.bs_bond_color)
1080
+ except Exception:
1081
+ pass
1082
+
1083
+ def apply_settings(self):
1084
+ """設定を適用(ダイアログは開いたまま)"""
1085
+ # 親ウィンドウの設定を更新
1086
+ if self.parent_window:
1087
+ settings = self.get_settings()
1088
+ self.parent_window.settings.update(settings)
1089
+ # Mark settings dirty; persist on exit to avoid frequent disk writes
1090
+ try:
1091
+ self.parent_window.settings_dirty = True
1092
+ except Exception:
1093
+ pass
1094
+ # 3Dビューの設定を適用
1095
+ self.parent_window.apply_3d_settings()
1096
+ # Update CPK colors from settings if present (no-op otherwise)
1097
+ try:
1098
+ self.parent_window.update_cpk_colors_from_settings()
1099
+ except Exception:
1100
+ pass
1101
+ # Refresh any open CPK color dialogs so they update their UI
1102
+ try:
1103
+ for w in QApplication.topLevelWidgets():
1104
+ try:
1105
+ # import locally to avoid circular import
1106
+ try:
1107
+ from .color_settings_dialog import ColorSettingsDialog
1108
+ except Exception:
1109
+ from modules.color_settings_dialog import ColorSettingsDialog
1110
+ if isinstance(w, ColorSettingsDialog):
1111
+ try:
1112
+ w.refresh_ui()
1113
+ except Exception:
1114
+ pass
1115
+ except Exception:
1116
+ pass
1117
+ except Exception:
1118
+ pass
1119
+ # 現在の分子を再描画(設定変更を反映)
1120
+ if hasattr(self.parent_window, 'current_mol') and self.parent_window.current_mol:
1121
+ self.parent_window.draw_molecule_3d(self.parent_window.current_mol)
1122
+ # ステータスバーに適用完了を表示
1123
+ self.parent_window.statusBar().showMessage("Settings applied successfully")
1124
+
1125
+ def _on_skip_chem_checks_changed(self, state):
1126
+ """Handle user toggling of skip chemistry checks: persist and update UI.
1127
+
1128
+ state: Qt.Checked (2) or Qt.Unchecked (0)
1129
+ """
1130
+ try:
1131
+ enabled = bool(state)
1132
+ self.settings['skip_chemistry_checks'] = enabled
1133
+ # mark dirty instead of immediate save
1134
+ try:
1135
+ self.settings_dirty = True
1136
+ except Exception:
1137
+ pass
1138
+ # If skip is enabled, allow Optimize button; otherwise, respect chem_check flags
1139
+
1140
+ except Exception:
1141
+ pass
1142
+
1143
+ # Note: Kekule display is applied only when user clicks Apply/OK.
1144
+
1145
+ def accept(self):
1146
+ """ダイアログの設定を適用してから閉じる"""
1147
+ # apply_settingsを呼び出して設定を適用
1148
+ self.apply_settings()
1149
+ super().accept()