MoleditPy-linux 2.4.1__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.
- moleditpy_linux/__init__.py +17 -0
- moleditpy_linux/__main__.py +29 -0
- moleditpy_linux/main.py +37 -0
- moleditpy_linux/modules/__init__.py +41 -0
- moleditpy_linux/modules/about_dialog.py +104 -0
- moleditpy_linux/modules/align_plane_dialog.py +292 -0
- moleditpy_linux/modules/alignment_dialog.py +272 -0
- moleditpy_linux/modules/analysis_window.py +209 -0
- moleditpy_linux/modules/angle_dialog.py +440 -0
- moleditpy_linux/modules/assets/file_icon.ico +0 -0
- moleditpy_linux/modules/assets/icon.icns +0 -0
- moleditpy_linux/modules/assets/icon.ico +0 -0
- moleditpy_linux/modules/assets/icon.png +0 -0
- moleditpy_linux/modules/atom_item.py +395 -0
- moleditpy_linux/modules/bond_item.py +464 -0
- moleditpy_linux/modules/bond_length_dialog.py +380 -0
- moleditpy_linux/modules/calculation_worker.py +766 -0
- moleditpy_linux/modules/color_settings_dialog.py +321 -0
- moleditpy_linux/modules/constants.py +88 -0
- moleditpy_linux/modules/constrained_optimization_dialog.py +678 -0
- moleditpy_linux/modules/custom_interactor_style.py +749 -0
- moleditpy_linux/modules/custom_qt_interactor.py +102 -0
- moleditpy_linux/modules/dialog3_d_picking_mixin.py +141 -0
- moleditpy_linux/modules/dihedral_dialog.py +443 -0
- moleditpy_linux/modules/main_window.py +850 -0
- moleditpy_linux/modules/main_window_app_state.py +787 -0
- moleditpy_linux/modules/main_window_compute.py +1242 -0
- moleditpy_linux/modules/main_window_dialog_manager.py +460 -0
- moleditpy_linux/modules/main_window_edit_3d.py +536 -0
- moleditpy_linux/modules/main_window_edit_actions.py +1565 -0
- moleditpy_linux/modules/main_window_export.py +917 -0
- moleditpy_linux/modules/main_window_main_init.py +2100 -0
- moleditpy_linux/modules/main_window_molecular_parsers.py +1044 -0
- moleditpy_linux/modules/main_window_project_io.py +434 -0
- moleditpy_linux/modules/main_window_string_importers.py +275 -0
- moleditpy_linux/modules/main_window_ui_manager.py +602 -0
- moleditpy_linux/modules/main_window_view_3d.py +1539 -0
- moleditpy_linux/modules/main_window_view_loaders.py +355 -0
- moleditpy_linux/modules/mirror_dialog.py +122 -0
- moleditpy_linux/modules/molecular_data.py +302 -0
- moleditpy_linux/modules/molecule_scene.py +2000 -0
- moleditpy_linux/modules/move_group_dialog.py +600 -0
- moleditpy_linux/modules/periodic_table_dialog.py +84 -0
- moleditpy_linux/modules/planarize_dialog.py +220 -0
- moleditpy_linux/modules/plugin_interface.py +215 -0
- moleditpy_linux/modules/plugin_manager.py +473 -0
- moleditpy_linux/modules/plugin_manager_window.py +274 -0
- moleditpy_linux/modules/settings_dialog.py +1503 -0
- moleditpy_linux/modules/template_preview_item.py +157 -0
- moleditpy_linux/modules/template_preview_view.py +74 -0
- moleditpy_linux/modules/translation_dialog.py +364 -0
- moleditpy_linux/modules/user_template_dialog.py +692 -0
- moleditpy_linux/modules/zoomable_view.py +129 -0
- moleditpy_linux-2.4.1.dist-info/METADATA +954 -0
- moleditpy_linux-2.4.1.dist-info/RECORD +59 -0
- moleditpy_linux-2.4.1.dist-info/WHEEL +5 -0
- moleditpy_linux-2.4.1.dist-info/entry_points.txt +2 -0
- moleditpy_linux-2.4.1.dist-info/licenses/LICENSE +674 -0
- moleditpy_linux-2.4.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1503 @@
|
|
|
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("Settings")
|
|
30
|
+
self.setMinimumSize(700, 800)
|
|
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
|
+
# --- 2D Settings Defaults ---
|
|
94
|
+
'bond_width_2d': 2.0,
|
|
95
|
+
'bond_spacing_double_2d': 3.5,
|
|
96
|
+
'bond_spacing_triple_2d': 3.5,
|
|
97
|
+
'atom_font_size_2d': 20,
|
|
98
|
+
'background_color_2d': '#FFFFFF',
|
|
99
|
+
'bond_color_2d': '#222222', # Almost black
|
|
100
|
+
'atom_use_bond_color_2d': False,
|
|
101
|
+
'bond_cap_style_2d': 'Round',
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# --- 選択された色を管理する専用のインスタンス変数 ---
|
|
105
|
+
self.current_bg_color = None
|
|
106
|
+
|
|
107
|
+
# --- UI要素の作成 ---
|
|
108
|
+
layout = QVBoxLayout(self)
|
|
109
|
+
|
|
110
|
+
# タブウィジェットを作成
|
|
111
|
+
self.tab_widget = QTabWidget()
|
|
112
|
+
layout.addWidget(self.tab_widget)
|
|
113
|
+
|
|
114
|
+
# 2D Settings Tab (First)
|
|
115
|
+
self.create_2d_settings_tab()
|
|
116
|
+
|
|
117
|
+
# Scene設定タブ
|
|
118
|
+
self.create_scene_tab()
|
|
119
|
+
|
|
120
|
+
# Ball and Stick設定タブ
|
|
121
|
+
self.create_ball_stick_tab()
|
|
122
|
+
|
|
123
|
+
# CPK設定タブ
|
|
124
|
+
self.create_cpk_tab()
|
|
125
|
+
|
|
126
|
+
# Wireframe設定タブ
|
|
127
|
+
self.create_wireframe_tab()
|
|
128
|
+
|
|
129
|
+
# Stick設定タブ
|
|
130
|
+
self.create_stick_tab()
|
|
131
|
+
|
|
132
|
+
# Other設定タブ
|
|
133
|
+
self.create_other_tab()
|
|
134
|
+
|
|
135
|
+
# 渡された設定でUIと内部変数を初期化
|
|
136
|
+
self.update_ui_from_settings(current_settings)
|
|
137
|
+
|
|
138
|
+
# Initialize aromatic circle checkbox and torus thickness from settings
|
|
139
|
+
self.aromatic_circle_checkbox.setChecked(current_settings.get('display_aromatic_circles_3d', self.default_settings.get('display_aromatic_circles_3d', False)))
|
|
140
|
+
# Thickness factor is stored as a multiplier (e.g., 1.0), slider uses integer 0-300 representing 0.1x-3.0x
|
|
141
|
+
thickness_factor = current_settings.get('aromatic_torus_thickness_factor', self.default_settings.get('aromatic_torus_thickness_factor', 1.0))
|
|
142
|
+
self.aromatic_torus_thickness_slider.setValue(int(thickness_factor * 100))
|
|
143
|
+
self.aromatic_torus_thickness_label.setText(f"{thickness_factor:.1f}")
|
|
144
|
+
|
|
145
|
+
# Initialize internal 2D colors
|
|
146
|
+
self.current_bg_color_2d = current_settings.get('background_color_2d', self.default_settings['background_color_2d'])
|
|
147
|
+
self.current_bond_color_2d = current_settings.get('bond_color_2d', self.default_settings['bond_color_2d'])
|
|
148
|
+
|
|
149
|
+
# Apply initial 2D button styles
|
|
150
|
+
self.update_2d_color_buttons()
|
|
151
|
+
|
|
152
|
+
# --- ボタンの配置 ---
|
|
153
|
+
buttons = QHBoxLayout()
|
|
154
|
+
|
|
155
|
+
# タブごとのリセットボタン
|
|
156
|
+
reset_tab_button = QPushButton("Reset Current Tab")
|
|
157
|
+
reset_tab_button.clicked.connect(self.reset_current_tab)
|
|
158
|
+
reset_tab_button.setToolTip("Reset settings for the currently selected tab only")
|
|
159
|
+
buttons.addWidget(reset_tab_button)
|
|
160
|
+
|
|
161
|
+
# 全体リセットボタン
|
|
162
|
+
reset_all_button = QPushButton("Reset All")
|
|
163
|
+
reset_all_button.clicked.connect(self.reset_all_settings)
|
|
164
|
+
reset_all_button.setToolTip("Reset all settings to defaults")
|
|
165
|
+
buttons.addWidget(reset_all_button)
|
|
166
|
+
|
|
167
|
+
buttons.addStretch(1)
|
|
168
|
+
|
|
169
|
+
# Applyボタンを追加
|
|
170
|
+
apply_button = QPushButton("Apply")
|
|
171
|
+
apply_button.clicked.connect(self.apply_settings)
|
|
172
|
+
apply_button.setToolTip("Apply settings without closing dialog")
|
|
173
|
+
buttons.addWidget(apply_button)
|
|
174
|
+
|
|
175
|
+
ok_button = QPushButton("OK")
|
|
176
|
+
cancel_button = QPushButton("Cancel")
|
|
177
|
+
ok_button.clicked.connect(self.accept)
|
|
178
|
+
cancel_button.clicked.connect(self.reject)
|
|
179
|
+
|
|
180
|
+
buttons.addWidget(ok_button)
|
|
181
|
+
buttons.addWidget(cancel_button)
|
|
182
|
+
layout.addLayout(buttons)
|
|
183
|
+
|
|
184
|
+
def create_2d_settings_tab(self):
|
|
185
|
+
"""2D Settings Tab"""
|
|
186
|
+
widget = QWidget()
|
|
187
|
+
form_layout = QFormLayout(widget)
|
|
188
|
+
|
|
189
|
+
# --- View Settings ---
|
|
190
|
+
form_layout.addRow(QLabel("<b>View Appearance</b>"))
|
|
191
|
+
|
|
192
|
+
# Background Color
|
|
193
|
+
self.bg_color_2d_button = QPushButton()
|
|
194
|
+
self.bg_color_2d_button.setFixedSize(60, 24)
|
|
195
|
+
self.bg_color_2d_button.clicked.connect(self.pick_bg_color_2d)
|
|
196
|
+
form_layout.addRow("Background Color:", self.bg_color_2d_button)
|
|
197
|
+
|
|
198
|
+
line = QFrame()
|
|
199
|
+
line.setFrameShape(QFrame.Shape.HLine)
|
|
200
|
+
line.setFrameShadow(QFrame.Shadow.Sunken)
|
|
201
|
+
form_layout.addRow(line)
|
|
202
|
+
|
|
203
|
+
# --- Bond Settings ---
|
|
204
|
+
form_layout.addRow(QLabel("<b>Bond Settings</b>"))
|
|
205
|
+
|
|
206
|
+
# Bond Color
|
|
207
|
+
self.bond_color_2d_button = QPushButton()
|
|
208
|
+
self.bond_color_2d_button.setFixedSize(60, 24)
|
|
209
|
+
self.bond_color_2d_button.clicked.connect(self.pick_bond_color_2d)
|
|
210
|
+
form_layout.addRow("Bond Color:", self.bond_color_2d_button)
|
|
211
|
+
|
|
212
|
+
# Bond Width
|
|
213
|
+
self.bond_width_2d_slider = QSlider(Qt.Orientation.Horizontal)
|
|
214
|
+
self.bond_width_2d_slider.setRange(10, 200) # 1.0 - 20.0
|
|
215
|
+
self.bond_width_2d_label = QLabel("2.0")
|
|
216
|
+
self.bond_width_2d_slider.valueChanged.connect(lambda v: self.bond_width_2d_label.setText(f"{v/10:.1f}"))
|
|
217
|
+
bw_layout = QHBoxLayout()
|
|
218
|
+
bw_layout.addWidget(self.bond_width_2d_slider)
|
|
219
|
+
bw_layout.addWidget(self.bond_width_2d_label)
|
|
220
|
+
form_layout.addRow("Bond Width:", bw_layout)
|
|
221
|
+
|
|
222
|
+
# Double Bond Spacing
|
|
223
|
+
self.bond_spacing_double_2d_slider = QSlider(Qt.Orientation.Horizontal)
|
|
224
|
+
self.bond_spacing_double_2d_slider.setRange(10, 200) # 1.0 - 20.0
|
|
225
|
+
self.bond_spacing_double_2d_label = QLabel("3.5")
|
|
226
|
+
self.bond_spacing_double_2d_slider.valueChanged.connect(lambda v: self.bond_spacing_double_2d_label.setText(f"{v/10:.1f}"))
|
|
227
|
+
bsd_layout = QHBoxLayout()
|
|
228
|
+
bsd_layout.addWidget(self.bond_spacing_double_2d_slider)
|
|
229
|
+
bsd_layout.addWidget(self.bond_spacing_double_2d_label)
|
|
230
|
+
form_layout.addRow("Double Bond Spacing:", bsd_layout)
|
|
231
|
+
|
|
232
|
+
# Triple Bond Spacing
|
|
233
|
+
self.bond_spacing_triple_2d_slider = QSlider(Qt.Orientation.Horizontal)
|
|
234
|
+
self.bond_spacing_triple_2d_slider.setRange(10, 200) # 1.0 - 20.0
|
|
235
|
+
self.bond_spacing_triple_2d_label = QLabel("3.5")
|
|
236
|
+
self.bond_spacing_triple_2d_slider.valueChanged.connect(lambda v: self.bond_spacing_triple_2d_label.setText(f"{v/10:.1f}"))
|
|
237
|
+
bst_layout = QHBoxLayout()
|
|
238
|
+
bst_layout.addWidget(self.bond_spacing_triple_2d_slider)
|
|
239
|
+
bst_layout.addWidget(self.bond_spacing_triple_2d_label)
|
|
240
|
+
form_layout.addRow("Triple Bond Spacing:", bst_layout)
|
|
241
|
+
|
|
242
|
+
# Bond Cap Style
|
|
243
|
+
self.bond_cap_style_2d_combo = QComboBox()
|
|
244
|
+
self.bond_cap_style_2d_combo.addItems(['Round', 'Flat', 'Square'])
|
|
245
|
+
form_layout.addRow("Bond Cap Style:", self.bond_cap_style_2d_combo)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
line2 = QFrame()
|
|
249
|
+
line2.setFrameShape(QFrame.Shape.HLine)
|
|
250
|
+
line2.setFrameShadow(QFrame.Shadow.Sunken)
|
|
251
|
+
form_layout.addRow(line2)
|
|
252
|
+
|
|
253
|
+
# --- Atom Settings ---
|
|
254
|
+
form_layout.addRow(QLabel("<b>Atom Settings</b>"))
|
|
255
|
+
|
|
256
|
+
# Font Size
|
|
257
|
+
self.atom_font_size_2d_slider = QSlider(Qt.Orientation.Horizontal)
|
|
258
|
+
self.atom_font_size_2d_slider.setRange(8, 72)
|
|
259
|
+
self.atom_font_size_2d_label = QLabel("20")
|
|
260
|
+
self.atom_font_size_2d_slider.valueChanged.connect(lambda v: self.atom_font_size_2d_label.setText(str(v)))
|
|
261
|
+
fs_layout = QHBoxLayout()
|
|
262
|
+
fs_layout.addWidget(self.atom_font_size_2d_slider)
|
|
263
|
+
fs_layout.addWidget(self.atom_font_size_2d_label)
|
|
264
|
+
form_layout.addRow("Atom Label Font Size:", fs_layout)
|
|
265
|
+
|
|
266
|
+
# Use Bond Color Checkbox
|
|
267
|
+
self.atom_use_bond_color_2d_checkbox = QCheckBox()
|
|
268
|
+
self.atom_use_bond_color_2d_checkbox.setToolTip("If checked, atoms will use the unified Bond Color instead of element-specific colors (CPK).")
|
|
269
|
+
form_layout.addRow("Use Bond Color for Atoms:", self.atom_use_bond_color_2d_checkbox)
|
|
270
|
+
|
|
271
|
+
self.tab_widget.addTab(widget, "2D Settings")
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# Initialize internal variable for combo updates
|
|
276
|
+
# (Usually done in update_ui_from_settings, but we add direct access here for simple binding if needed)
|
|
277
|
+
# We rely on update_ui_from_settings to set current selection.
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def pick_bg_color_2d(self):
|
|
281
|
+
color = QColorDialog.getColor(QColor(self.current_bg_color_2d), self, "Select 2D Background Color")
|
|
282
|
+
if color.isValid():
|
|
283
|
+
self.current_bg_color_2d = color.name()
|
|
284
|
+
self.update_2d_color_buttons()
|
|
285
|
+
|
|
286
|
+
def pick_bond_color_2d(self):
|
|
287
|
+
color = QColorDialog.getColor(QColor(self.current_bond_color_2d), self, "Select 2D Bond Color")
|
|
288
|
+
if color.isValid():
|
|
289
|
+
self.current_bond_color_2d = color.name()
|
|
290
|
+
self.update_2d_color_buttons()
|
|
291
|
+
|
|
292
|
+
def update_2d_color_buttons(self):
|
|
293
|
+
try:
|
|
294
|
+
self.bg_color_2d_button.setStyleSheet(f"background-color: {self.current_bg_color_2d}; border: 1px solid #888;")
|
|
295
|
+
self.bond_color_2d_button.setStyleSheet(f"background-color: {self.current_bond_color_2d}; border: 1px solid #888;")
|
|
296
|
+
except Exception:
|
|
297
|
+
pass
|
|
298
|
+
|
|
299
|
+
def create_scene_tab(self):
|
|
300
|
+
"""基本設定タブを作成"""
|
|
301
|
+
scene_widget = QWidget()
|
|
302
|
+
form_layout = QFormLayout(scene_widget)
|
|
303
|
+
|
|
304
|
+
# 1. 背景色
|
|
305
|
+
self.bg_button = QPushButton()
|
|
306
|
+
self.bg_button.setToolTip("Click to select a color")
|
|
307
|
+
self.bg_button.clicked.connect(self.select_color)
|
|
308
|
+
form_layout.addRow("Background Color:", self.bg_button)
|
|
309
|
+
|
|
310
|
+
# 1a. 軸の表示/非表示
|
|
311
|
+
self.axes_checkbox = QCheckBox()
|
|
312
|
+
form_layout.addRow("Show 3D Axes:", self.axes_checkbox)
|
|
313
|
+
|
|
314
|
+
# 2. ライトの有効/無効
|
|
315
|
+
self.light_checkbox = QCheckBox()
|
|
316
|
+
form_layout.addRow("Enable Lighting:", self.light_checkbox)
|
|
317
|
+
|
|
318
|
+
# 光の強さスライダーを追加
|
|
319
|
+
self.intensity_slider = QSlider(Qt.Orientation.Horizontal)
|
|
320
|
+
self.intensity_slider.setRange(0, 200) # 0.0 ~ 2.0 の範囲
|
|
321
|
+
self.intensity_label = QLabel("1.0")
|
|
322
|
+
self.intensity_slider.valueChanged.connect(lambda v: self.intensity_label.setText(f"{v/100:.2f}"))
|
|
323
|
+
intensity_layout = QHBoxLayout()
|
|
324
|
+
intensity_layout.addWidget(self.intensity_slider)
|
|
325
|
+
intensity_layout.addWidget(self.intensity_label)
|
|
326
|
+
form_layout.addRow("Light Intensity:", intensity_layout)
|
|
327
|
+
|
|
328
|
+
# 3. 光沢 (Specular)
|
|
329
|
+
self.specular_slider = QSlider(Qt.Orientation.Horizontal)
|
|
330
|
+
self.specular_slider.setRange(0, 100)
|
|
331
|
+
self.specular_label = QLabel("0.20")
|
|
332
|
+
self.specular_slider.valueChanged.connect(lambda v: self.specular_label.setText(f"{v/100:.2f}"))
|
|
333
|
+
specular_layout = QHBoxLayout()
|
|
334
|
+
specular_layout.addWidget(self.specular_slider)
|
|
335
|
+
specular_layout.addWidget(self.specular_label)
|
|
336
|
+
form_layout.addRow("Shininess (Specular):", specular_layout)
|
|
337
|
+
|
|
338
|
+
# 4. 光沢の強さ (Specular Power)
|
|
339
|
+
self.spec_power_slider = QSlider(Qt.Orientation.Horizontal)
|
|
340
|
+
self.spec_power_slider.setRange(0, 100)
|
|
341
|
+
self.spec_power_label = QLabel("20")
|
|
342
|
+
self.spec_power_slider.valueChanged.connect(lambda v: self.spec_power_label.setText(str(v)))
|
|
343
|
+
spec_power_layout = QHBoxLayout()
|
|
344
|
+
spec_power_layout.addWidget(self.spec_power_slider)
|
|
345
|
+
spec_power_layout.addWidget(self.spec_power_label)
|
|
346
|
+
form_layout.addRow("Shininess Power:", spec_power_layout)
|
|
347
|
+
|
|
348
|
+
# Projection mode (Perspective / Orthographic)
|
|
349
|
+
self.projection_combo = QComboBox()
|
|
350
|
+
self.projection_combo.addItem("Perspective")
|
|
351
|
+
self.projection_combo.addItem("Orthographic")
|
|
352
|
+
self.projection_combo.setToolTip("Choose camera projection mode: Perspective (default) or Orthographic")
|
|
353
|
+
form_layout.addRow("Projection Mode:", self.projection_combo)
|
|
354
|
+
|
|
355
|
+
self.tab_widget.addTab(scene_widget, "3D Scene")
|
|
356
|
+
|
|
357
|
+
def create_other_tab(self):
|
|
358
|
+
"""other設定タブを作成"""
|
|
359
|
+
self.other_widget = QWidget()
|
|
360
|
+
self.other_form_layout = QFormLayout(self.other_widget)
|
|
361
|
+
|
|
362
|
+
# 化学チェックスキップオプション(otherタブに移動)
|
|
363
|
+
self.skip_chem_checks_checkbox = QCheckBox()
|
|
364
|
+
self.skip_chem_checks_checkbox.setToolTip("When enabled, XYZ file import will try to ignore chemical/sanitization errors and allow viewing malformed files.")
|
|
365
|
+
# Immediately persist change to settings when user toggles the checkbox
|
|
366
|
+
try:
|
|
367
|
+
self.skip_chem_checks_checkbox.stateChanged.connect(lambda s: self._on_skip_chem_checks_changed(s))
|
|
368
|
+
except Exception:
|
|
369
|
+
pass
|
|
370
|
+
|
|
371
|
+
# Add the checkbox to the other tab's form
|
|
372
|
+
try:
|
|
373
|
+
self.other_form_layout.addRow("Skip chemistry checks on import XYZ file:", self.skip_chem_checks_checkbox)
|
|
374
|
+
except Exception:
|
|
375
|
+
pass
|
|
376
|
+
|
|
377
|
+
# 3D Kekule display option (under Other) will be added below the
|
|
378
|
+
# 'Always ask molecular charge on import' option so ordering is clear
|
|
379
|
+
# in the UI.
|
|
380
|
+
self.kekule_3d_checkbox = QCheckBox()
|
|
381
|
+
self.kekule_3d_checkbox.setToolTip("When enabled, aromatic bonds will be kekulized in the 3D view (show alternating single/double bonds).")
|
|
382
|
+
# Don't persist kekule state immediately; Apply/OK should commit setting.
|
|
383
|
+
# Always ask charge on XYZ import (skip silent charge=0 test)
|
|
384
|
+
self.always_ask_charge_checkbox = QCheckBox()
|
|
385
|
+
self.always_ask_charge_checkbox.setToolTip("Prompt for overall molecular charge when importing XYZ files instead of silently trying charge=0 first.")
|
|
386
|
+
try:
|
|
387
|
+
self.other_form_layout.addRow("Always ask molecular charge on import XYZ file:", self.always_ask_charge_checkbox)
|
|
388
|
+
except Exception:
|
|
389
|
+
pass
|
|
390
|
+
|
|
391
|
+
# Add separator after Kekule bonds option
|
|
392
|
+
separator = QFrame()
|
|
393
|
+
separator.setFrameShape(QFrame.Shape.HLine)
|
|
394
|
+
separator.setFrameShadow(QFrame.Shadow.Sunken)
|
|
395
|
+
self.other_form_layout.addRow(separator)
|
|
396
|
+
|
|
397
|
+
# Place the Kekulé option after the always-ask-charge option
|
|
398
|
+
try:
|
|
399
|
+
self.other_form_layout.addRow("Display Kekulé bonds in 3D:", self.kekule_3d_checkbox)
|
|
400
|
+
except Exception:
|
|
401
|
+
pass
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
# Aromatic ring circle display option
|
|
405
|
+
self.aromatic_circle_checkbox = QCheckBox()
|
|
406
|
+
self.aromatic_circle_checkbox.setToolTip("When enabled, aromatic rings will be displayed with a circle inside the ring in 3D view.")
|
|
407
|
+
try:
|
|
408
|
+
self.other_form_layout.addRow("Display aromatic rings as circles in 3D:", self.aromatic_circle_checkbox)
|
|
409
|
+
except Exception:
|
|
410
|
+
pass
|
|
411
|
+
|
|
412
|
+
# Aromatic torus thickness factor
|
|
413
|
+
self.aromatic_torus_thickness_slider = QSlider(Qt.Orientation.Horizontal)
|
|
414
|
+
self.aromatic_torus_thickness_slider.setRange(10, 300) # 0.1x to 3.0x
|
|
415
|
+
self.aromatic_torus_thickness_slider.setValue(60) # Default 0.6x
|
|
416
|
+
self.aromatic_torus_thickness_label = QLabel("0.6")
|
|
417
|
+
self.aromatic_torus_thickness_slider.valueChanged.connect(
|
|
418
|
+
lambda v: self.aromatic_torus_thickness_label.setText(f"{v/100:.1f}")
|
|
419
|
+
)
|
|
420
|
+
thickness_layout = QHBoxLayout()
|
|
421
|
+
thickness_layout.addWidget(self.aromatic_torus_thickness_slider)
|
|
422
|
+
thickness_layout.addWidget(self.aromatic_torus_thickness_label)
|
|
423
|
+
try:
|
|
424
|
+
self.other_form_layout.addRow("Aromatic torus thickness (× bond radius):", thickness_layout)
|
|
425
|
+
except Exception:
|
|
426
|
+
pass
|
|
427
|
+
|
|
428
|
+
# Add Other tab to the tab widget
|
|
429
|
+
self.tab_widget.addTab(self.other_widget, "Other")
|
|
430
|
+
|
|
431
|
+
def refresh_ui(self):
|
|
432
|
+
"""Refresh periodic table / BS button visuals using current settings.
|
|
433
|
+
|
|
434
|
+
Called when settings change externally (e.g., Reset All in main settings) so
|
|
435
|
+
the dialog reflects the current stored overrides.
|
|
436
|
+
"""
|
|
437
|
+
try:
|
|
438
|
+
# Update element button colors from parent.settings cpks
|
|
439
|
+
overrides = self.parent_window.settings.get('cpk_colors', {}) if self.parent_window and hasattr(self.parent_window, 'settings') else {}
|
|
440
|
+
for s, btn in self.element_buttons.items():
|
|
441
|
+
try:
|
|
442
|
+
override = overrides.get(s)
|
|
443
|
+
q_color = QColor(override) if override else CPK_COLORS.get(s, CPK_COLORS['DEFAULT'])
|
|
444
|
+
brightness = (q_color.red() * 299 + q_color.green() * 587 + q_color.blue() * 114) / 1000
|
|
445
|
+
text_color = 'white' if brightness < 128 else 'black'
|
|
446
|
+
btn.setStyleSheet(f"background-color: {q_color.name()}; color: {text_color}; border: 1px solid #555; font-weight: bold;")
|
|
447
|
+
except Exception:
|
|
448
|
+
pass
|
|
449
|
+
# Update BS color button from parent settings
|
|
450
|
+
try:
|
|
451
|
+
if hasattr(self, 'bs_button') and self.parent_window and hasattr(self.parent_window, 'settings'):
|
|
452
|
+
bs_hex = self.parent_window.settings.get('ball_stick_bond_color', self.parent_window.default_settings.get('ball_stick_bond_color', '#7F7F7F'))
|
|
453
|
+
self.bs_button.setStyleSheet(f"background-color: {bs_hex}; border: 1px solid #888;")
|
|
454
|
+
self.bs_button.setToolTip(bs_hex)
|
|
455
|
+
except Exception:
|
|
456
|
+
pass
|
|
457
|
+
except Exception:
|
|
458
|
+
pass
|
|
459
|
+
# Avoid circular import: import ColorSettingsDialog only inside method using it
|
|
460
|
+
|
|
461
|
+
# NOTE: Multi-bond offset/thickness settings moved to per-model tabs to allow
|
|
462
|
+
# independent configuration for Ball&Stick/CPK/Wireframe/Stick.
|
|
463
|
+
|
|
464
|
+
# 'Other' tab is created in create_other_tab; nothing to do here.
|
|
465
|
+
|
|
466
|
+
def create_ball_stick_tab(self):
|
|
467
|
+
"""Ball and Stick設定タブを作成"""
|
|
468
|
+
ball_stick_widget = QWidget()
|
|
469
|
+
form_layout = QFormLayout(ball_stick_widget)
|
|
470
|
+
|
|
471
|
+
info_label = QLabel("Ball & Stick model shows atoms as spheres and bonds as cylinders.")
|
|
472
|
+
info_label.setWordWrap(True)
|
|
473
|
+
info_label.setStyleSheet("color: #666; font-style: italic; margin-top: 10px;")
|
|
474
|
+
form_layout.addRow(info_label)
|
|
475
|
+
|
|
476
|
+
# 原子サイズスケール
|
|
477
|
+
self.bs_atom_scale_slider = QSlider(Qt.Orientation.Horizontal)
|
|
478
|
+
self.bs_atom_scale_slider.setRange(10, 200) # 0.1 ~ 2.0
|
|
479
|
+
self.bs_atom_scale_label = QLabel("1.00")
|
|
480
|
+
self.bs_atom_scale_slider.valueChanged.connect(lambda v: self.bs_atom_scale_label.setText(f"{v/100:.2f}"))
|
|
481
|
+
atom_scale_layout = QHBoxLayout()
|
|
482
|
+
atom_scale_layout.addWidget(self.bs_atom_scale_slider)
|
|
483
|
+
atom_scale_layout.addWidget(self.bs_atom_scale_label)
|
|
484
|
+
form_layout.addRow("Atom Size Scale:", atom_scale_layout)
|
|
485
|
+
|
|
486
|
+
# ボンド半径
|
|
487
|
+
self.bs_bond_radius_slider = QSlider(Qt.Orientation.Horizontal)
|
|
488
|
+
self.bs_bond_radius_slider.setRange(1, 50) # 0.01 ~ 0.5
|
|
489
|
+
self.bs_bond_radius_label = QLabel("0.10")
|
|
490
|
+
self.bs_bond_radius_slider.valueChanged.connect(lambda v: self.bs_bond_radius_label.setText(f"{v/100:.2f}"))
|
|
491
|
+
bond_radius_layout = QHBoxLayout()
|
|
492
|
+
bond_radius_layout.addWidget(self.bs_bond_radius_slider)
|
|
493
|
+
bond_radius_layout.addWidget(self.bs_bond_radius_label)
|
|
494
|
+
form_layout.addRow("Bond Radius:", bond_radius_layout)
|
|
495
|
+
|
|
496
|
+
# --- 区切り線(水平ライン) ---
|
|
497
|
+
line = QFrame()
|
|
498
|
+
line.setFrameShape(QFrame.Shape.HLine)
|
|
499
|
+
line.setFrameShadow(QFrame.Shadow.Sunken)
|
|
500
|
+
form_layout.addRow(line)
|
|
501
|
+
|
|
502
|
+
# --- Per-model multi-bond controls (Ball & Stick) ---
|
|
503
|
+
# 二重/三重結合のオフセット倍率(Ball & Stick)
|
|
504
|
+
self.bs_double_offset_slider = QSlider(Qt.Orientation.Horizontal)
|
|
505
|
+
self.bs_double_offset_slider.setRange(100, 400)
|
|
506
|
+
self.bs_double_offset_label = QLabel("2.00")
|
|
507
|
+
self.bs_double_offset_slider.valueChanged.connect(lambda v: self.bs_double_offset_label.setText(f"{v/100:.2f}"))
|
|
508
|
+
bs_double_offset_layout = QHBoxLayout()
|
|
509
|
+
bs_double_offset_layout.addWidget(self.bs_double_offset_slider)
|
|
510
|
+
bs_double_offset_layout.addWidget(self.bs_double_offset_label)
|
|
511
|
+
form_layout.addRow("Double Bond Offset (Ball & Stick):", bs_double_offset_layout)
|
|
512
|
+
|
|
513
|
+
self.bs_triple_offset_slider = QSlider(Qt.Orientation.Horizontal)
|
|
514
|
+
self.bs_triple_offset_slider.setRange(100, 400)
|
|
515
|
+
self.bs_triple_offset_label = QLabel("2.00")
|
|
516
|
+
self.bs_triple_offset_slider.valueChanged.connect(lambda v: self.bs_triple_offset_label.setText(f"{v/100:.2f}"))
|
|
517
|
+
bs_triple_offset_layout = QHBoxLayout()
|
|
518
|
+
bs_triple_offset_layout.addWidget(self.bs_triple_offset_slider)
|
|
519
|
+
bs_triple_offset_layout.addWidget(self.bs_triple_offset_label)
|
|
520
|
+
form_layout.addRow("Triple Bond Offset (Ball & Stick):", bs_triple_offset_layout)
|
|
521
|
+
|
|
522
|
+
# 半径倍率
|
|
523
|
+
self.bs_double_radius_slider = QSlider(Qt.Orientation.Horizontal)
|
|
524
|
+
self.bs_double_radius_slider.setRange(50, 100)
|
|
525
|
+
self.bs_double_radius_label = QLabel("0.80")
|
|
526
|
+
self.bs_double_radius_slider.valueChanged.connect(lambda v: self.bs_double_radius_label.setText(f"{v/100:.2f}"))
|
|
527
|
+
bs_double_radius_layout = QHBoxLayout()
|
|
528
|
+
bs_double_radius_layout.addWidget(self.bs_double_radius_slider)
|
|
529
|
+
bs_double_radius_layout.addWidget(self.bs_double_radius_label)
|
|
530
|
+
form_layout.addRow("Double Bond Thickness (Ball & Stick):", bs_double_radius_layout)
|
|
531
|
+
|
|
532
|
+
self.bs_triple_radius_slider = QSlider(Qt.Orientation.Horizontal)
|
|
533
|
+
self.bs_triple_radius_slider.setRange(50, 100)
|
|
534
|
+
self.bs_triple_radius_label = QLabel("0.70")
|
|
535
|
+
self.bs_triple_radius_slider.valueChanged.connect(lambda v: self.bs_triple_radius_label.setText(f"{v/100:.2f}"))
|
|
536
|
+
bs_triple_radius_layout = QHBoxLayout()
|
|
537
|
+
bs_triple_radius_layout.addWidget(self.bs_triple_radius_slider)
|
|
538
|
+
bs_triple_radius_layout.addWidget(self.bs_triple_radius_label)
|
|
539
|
+
form_layout.addRow("Triple Bond Thickness (Ball & Stick):", bs_triple_radius_layout)
|
|
540
|
+
|
|
541
|
+
# --- 区切り線(水平ライン) ---
|
|
542
|
+
line = QFrame()
|
|
543
|
+
line.setFrameShape(QFrame.Shape.HLine)
|
|
544
|
+
line.setFrameShadow(QFrame.Shadow.Sunken)
|
|
545
|
+
form_layout.addRow(line)
|
|
546
|
+
|
|
547
|
+
# 解像度
|
|
548
|
+
self.bs_resolution_slider = QSlider(Qt.Orientation.Horizontal)
|
|
549
|
+
self.bs_resolution_slider.setRange(6, 32)
|
|
550
|
+
self.bs_resolution_label = QLabel("16")
|
|
551
|
+
self.bs_resolution_slider.valueChanged.connect(lambda v: self.bs_resolution_label.setText(str(v)))
|
|
552
|
+
resolution_layout = QHBoxLayout()
|
|
553
|
+
resolution_layout.addWidget(self.bs_resolution_slider)
|
|
554
|
+
resolution_layout.addWidget(self.bs_resolution_label)
|
|
555
|
+
form_layout.addRow("Resolution (Quality):", resolution_layout)
|
|
556
|
+
|
|
557
|
+
# --- 区切り線(水平ライン) ---
|
|
558
|
+
line = QFrame()
|
|
559
|
+
line.setFrameShape(QFrame.Shape.HLine)
|
|
560
|
+
line.setFrameShadow(QFrame.Shadow.Sunken)
|
|
561
|
+
form_layout.addRow(line)
|
|
562
|
+
|
|
563
|
+
# --- Ball & Stick bond color ---
|
|
564
|
+
self.bs_bond_color_button = QPushButton()
|
|
565
|
+
self.bs_bond_color_button.setFixedSize(36, 24)
|
|
566
|
+
self.bs_bond_color_button.clicked.connect(self.pick_bs_bond_color)
|
|
567
|
+
self.bs_bond_color_button.setToolTip("Choose the uniform bond color for Ball & Stick model (3D)")
|
|
568
|
+
form_layout.addRow("Ball & Stick bond color:", self.bs_bond_color_button)
|
|
569
|
+
|
|
570
|
+
# Use CPK colors for bonds option
|
|
571
|
+
self.bs_use_cpk_bond_checkbox = QCheckBox()
|
|
572
|
+
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.")
|
|
573
|
+
form_layout.addRow("Use CPK colors for bonds:", self.bs_use_cpk_bond_checkbox)
|
|
574
|
+
|
|
575
|
+
self.tab_widget.addTab(ball_stick_widget, "Ball & Stick")
|
|
576
|
+
|
|
577
|
+
def create_cpk_tab(self):
|
|
578
|
+
"""CPK設定タブを作成"""
|
|
579
|
+
cpk_widget = QWidget()
|
|
580
|
+
form_layout = QFormLayout(cpk_widget)
|
|
581
|
+
|
|
582
|
+
info_label = QLabel("CPK model shows atoms as space-filling spheres using van der Waals radii.")
|
|
583
|
+
info_label.setWordWrap(True)
|
|
584
|
+
info_label.setStyleSheet("color: #666; font-style: italic; margin-top: 10px;")
|
|
585
|
+
form_layout.addRow(info_label)
|
|
586
|
+
|
|
587
|
+
# 原子サイズスケール
|
|
588
|
+
self.cpk_atom_scale_slider = QSlider(Qt.Orientation.Horizontal)
|
|
589
|
+
self.cpk_atom_scale_slider.setRange(50, 200) # 0.5 ~ 2.0
|
|
590
|
+
self.cpk_atom_scale_label = QLabel("1.00")
|
|
591
|
+
self.cpk_atom_scale_slider.valueChanged.connect(lambda v: self.cpk_atom_scale_label.setText(f"{v/100:.2f}"))
|
|
592
|
+
atom_scale_layout = QHBoxLayout()
|
|
593
|
+
atom_scale_layout.addWidget(self.cpk_atom_scale_slider)
|
|
594
|
+
atom_scale_layout.addWidget(self.cpk_atom_scale_label)
|
|
595
|
+
form_layout.addRow("Atom Size Scale:", atom_scale_layout)
|
|
596
|
+
|
|
597
|
+
# 解像度
|
|
598
|
+
self.cpk_resolution_slider = QSlider(Qt.Orientation.Horizontal)
|
|
599
|
+
self.cpk_resolution_slider.setRange(8, 64)
|
|
600
|
+
self.cpk_resolution_label = QLabel("32")
|
|
601
|
+
self.cpk_resolution_slider.valueChanged.connect(lambda v: self.cpk_resolution_label.setText(str(v)))
|
|
602
|
+
resolution_layout = QHBoxLayout()
|
|
603
|
+
resolution_layout.addWidget(self.cpk_resolution_slider)
|
|
604
|
+
resolution_layout.addWidget(self.cpk_resolution_label)
|
|
605
|
+
form_layout.addRow("Resolution (Quality):", resolution_layout)
|
|
606
|
+
|
|
607
|
+
self.tab_widget.addTab(cpk_widget, "CPK (Space-filling)")
|
|
608
|
+
|
|
609
|
+
def create_wireframe_tab(self):
|
|
610
|
+
"""Wireframe設定タブを作成"""
|
|
611
|
+
wireframe_widget = QWidget()
|
|
612
|
+
form_layout = QFormLayout(wireframe_widget)
|
|
613
|
+
|
|
614
|
+
info_label = QLabel("Wireframe model shows molecular structure with thin lines only.")
|
|
615
|
+
info_label.setWordWrap(True)
|
|
616
|
+
info_label.setStyleSheet("color: #666; font-style: italic; margin-top: 10px;")
|
|
617
|
+
form_layout.addRow(info_label)
|
|
618
|
+
|
|
619
|
+
# ボンド半径
|
|
620
|
+
self.wf_bond_radius_slider = QSlider(Qt.Orientation.Horizontal)
|
|
621
|
+
self.wf_bond_radius_slider.setRange(1, 10) # 0.01 ~ 0.1
|
|
622
|
+
self.wf_bond_radius_label = QLabel("0.01")
|
|
623
|
+
self.wf_bond_radius_slider.valueChanged.connect(lambda v: self.wf_bond_radius_label.setText(f"{v/100:.2f}"))
|
|
624
|
+
bond_radius_layout = QHBoxLayout()
|
|
625
|
+
bond_radius_layout.addWidget(self.wf_bond_radius_slider)
|
|
626
|
+
bond_radius_layout.addWidget(self.wf_bond_radius_label)
|
|
627
|
+
form_layout.addRow("Bond Radius:", bond_radius_layout)
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
# --- 区切り線(水平ライン) ---
|
|
631
|
+
line = QFrame()
|
|
632
|
+
line.setFrameShape(QFrame.Shape.HLine)
|
|
633
|
+
line.setFrameShadow(QFrame.Shadow.Sunken)
|
|
634
|
+
form_layout.addRow(line)
|
|
635
|
+
|
|
636
|
+
# --- Per-model multi-bond controls (Wireframe) ---
|
|
637
|
+
self.wf_double_offset_slider = QSlider(Qt.Orientation.Horizontal)
|
|
638
|
+
self.wf_double_offset_slider.setRange(100, 400)
|
|
639
|
+
self.wf_double_offset_label = QLabel("2.00")
|
|
640
|
+
self.wf_double_offset_slider.valueChanged.connect(lambda v: self.wf_double_offset_label.setText(f"{v/100:.2f}"))
|
|
641
|
+
wf_double_offset_layout = QHBoxLayout()
|
|
642
|
+
wf_double_offset_layout.addWidget(self.wf_double_offset_slider)
|
|
643
|
+
wf_double_offset_layout.addWidget(self.wf_double_offset_label)
|
|
644
|
+
form_layout.addRow("Double Bond Offset (Wireframe):", wf_double_offset_layout)
|
|
645
|
+
|
|
646
|
+
self.wf_triple_offset_slider = QSlider(Qt.Orientation.Horizontal)
|
|
647
|
+
self.wf_triple_offset_slider.setRange(100, 400)
|
|
648
|
+
self.wf_triple_offset_label = QLabel("2.00")
|
|
649
|
+
self.wf_triple_offset_slider.valueChanged.connect(lambda v: self.wf_triple_offset_label.setText(f"{v/100:.2f}"))
|
|
650
|
+
wf_triple_offset_layout = QHBoxLayout()
|
|
651
|
+
wf_triple_offset_layout.addWidget(self.wf_triple_offset_slider)
|
|
652
|
+
wf_triple_offset_layout.addWidget(self.wf_triple_offset_label)
|
|
653
|
+
form_layout.addRow("Triple Bond Offset (Wireframe):", wf_triple_offset_layout)
|
|
654
|
+
|
|
655
|
+
self.wf_double_radius_slider = QSlider(Qt.Orientation.Horizontal)
|
|
656
|
+
self.wf_double_radius_slider.setRange(50, 100)
|
|
657
|
+
self.wf_double_radius_label = QLabel("0.80")
|
|
658
|
+
self.wf_double_radius_slider.valueChanged.connect(lambda v: self.wf_double_radius_label.setText(f"{v/100:.2f}"))
|
|
659
|
+
wf_double_radius_layout = QHBoxLayout()
|
|
660
|
+
wf_double_radius_layout.addWidget(self.wf_double_radius_slider)
|
|
661
|
+
wf_double_radius_layout.addWidget(self.wf_double_radius_label)
|
|
662
|
+
form_layout.addRow("Double Bond Thickness (Wireframe):", wf_double_radius_layout)
|
|
663
|
+
|
|
664
|
+
self.wf_triple_radius_slider = QSlider(Qt.Orientation.Horizontal)
|
|
665
|
+
self.wf_triple_radius_slider.setRange(50, 100)
|
|
666
|
+
self.wf_triple_radius_label = QLabel("0.70")
|
|
667
|
+
self.wf_triple_radius_slider.valueChanged.connect(lambda v: self.wf_triple_radius_label.setText(f"{v/100:.2f}"))
|
|
668
|
+
wf_triple_radius_layout = QHBoxLayout()
|
|
669
|
+
wf_triple_radius_layout.addWidget(self.wf_triple_radius_slider)
|
|
670
|
+
wf_triple_radius_layout.addWidget(self.wf_triple_radius_label)
|
|
671
|
+
form_layout.addRow("Triple Bond Thickness (Wireframe):", wf_triple_radius_layout)
|
|
672
|
+
|
|
673
|
+
# --- 区切り線(水平ライン) ---
|
|
674
|
+
line = QFrame()
|
|
675
|
+
line.setFrameShape(QFrame.Shape.HLine)
|
|
676
|
+
line.setFrameShadow(QFrame.Shadow.Sunken)
|
|
677
|
+
form_layout.addRow(line)
|
|
678
|
+
|
|
679
|
+
# 解像度
|
|
680
|
+
self.wf_resolution_slider = QSlider(Qt.Orientation.Horizontal)
|
|
681
|
+
self.wf_resolution_slider.setRange(4, 16)
|
|
682
|
+
self.wf_resolution_label = QLabel("6")
|
|
683
|
+
self.wf_resolution_slider.valueChanged.connect(lambda v: self.wf_resolution_label.setText(str(v)))
|
|
684
|
+
resolution_layout = QHBoxLayout()
|
|
685
|
+
resolution_layout.addWidget(self.wf_resolution_slider)
|
|
686
|
+
resolution_layout.addWidget(self.wf_resolution_label)
|
|
687
|
+
form_layout.addRow("Resolution (Quality):", resolution_layout)
|
|
688
|
+
|
|
689
|
+
self.tab_widget.addTab(wireframe_widget, "Wireframe")
|
|
690
|
+
|
|
691
|
+
def create_stick_tab(self):
|
|
692
|
+
"""Stick設定タブを作成"""
|
|
693
|
+
stick_widget = QWidget()
|
|
694
|
+
form_layout = QFormLayout(stick_widget)
|
|
695
|
+
|
|
696
|
+
info_label = QLabel("Stick model shows bonds as thick cylinders with atoms as small spheres.")
|
|
697
|
+
info_label.setWordWrap(True)
|
|
698
|
+
info_label.setStyleSheet("color: #666; font-style: italic; margin-top: 10px;")
|
|
699
|
+
form_layout.addRow(info_label)
|
|
700
|
+
|
|
701
|
+
# ボンド半径(原子半径も同じ値を使用)
|
|
702
|
+
self.stick_bond_radius_slider = QSlider(Qt.Orientation.Horizontal)
|
|
703
|
+
self.stick_bond_radius_slider.setRange(5, 50) # 0.05 ~ 0.5
|
|
704
|
+
self.stick_bond_radius_label = QLabel("0.15")
|
|
705
|
+
self.stick_bond_radius_slider.valueChanged.connect(lambda v: self.stick_bond_radius_label.setText(f"{v/100:.2f}"))
|
|
706
|
+
bond_radius_layout = QHBoxLayout()
|
|
707
|
+
bond_radius_layout.addWidget(self.stick_bond_radius_slider)
|
|
708
|
+
bond_radius_layout.addWidget(self.stick_bond_radius_label)
|
|
709
|
+
form_layout.addRow("Bond Radius:", bond_radius_layout)
|
|
710
|
+
|
|
711
|
+
# --- 区切り線(水平ライン) ---
|
|
712
|
+
line = QFrame()
|
|
713
|
+
line.setFrameShape(QFrame.Shape.HLine)
|
|
714
|
+
line.setFrameShadow(QFrame.Shadow.Sunken)
|
|
715
|
+
form_layout.addRow(line)
|
|
716
|
+
|
|
717
|
+
# --- Per-model multi-bond controls (Stick) ---
|
|
718
|
+
self.stick_double_offset_slider = QSlider(Qt.Orientation.Horizontal)
|
|
719
|
+
self.stick_double_offset_slider.setRange(50, 400)
|
|
720
|
+
self.stick_double_offset_label = QLabel("2.00")
|
|
721
|
+
self.stick_double_offset_slider.valueChanged.connect(lambda v: self.stick_double_offset_label.setText(f"{v/100:.2f}"))
|
|
722
|
+
stick_double_offset_layout = QHBoxLayout()
|
|
723
|
+
stick_double_offset_layout.addWidget(self.stick_double_offset_slider)
|
|
724
|
+
stick_double_offset_layout.addWidget(self.stick_double_offset_label)
|
|
725
|
+
form_layout.addRow("Double Bond Offset (Stick):", stick_double_offset_layout)
|
|
726
|
+
|
|
727
|
+
self.stick_triple_offset_slider = QSlider(Qt.Orientation.Horizontal)
|
|
728
|
+
self.stick_triple_offset_slider.setRange(50, 400)
|
|
729
|
+
self.stick_triple_offset_label = QLabel("2.00")
|
|
730
|
+
self.stick_triple_offset_slider.valueChanged.connect(lambda v: self.stick_triple_offset_label.setText(f"{v/100:.2f}"))
|
|
731
|
+
stick_triple_offset_layout = QHBoxLayout()
|
|
732
|
+
stick_triple_offset_layout.addWidget(self.stick_triple_offset_slider)
|
|
733
|
+
stick_triple_offset_layout.addWidget(self.stick_triple_offset_label)
|
|
734
|
+
form_layout.addRow("Triple Bond Offset (Stick):", stick_triple_offset_layout)
|
|
735
|
+
|
|
736
|
+
self.stick_double_radius_slider = QSlider(Qt.Orientation.Horizontal)
|
|
737
|
+
self.stick_double_radius_slider.setRange(20, 100)
|
|
738
|
+
self.stick_double_radius_label = QLabel("0.80")
|
|
739
|
+
self.stick_double_radius_slider.valueChanged.connect(lambda v: self.stick_double_radius_label.setText(f"{v/100:.2f}"))
|
|
740
|
+
stick_double_radius_layout = QHBoxLayout()
|
|
741
|
+
stick_double_radius_layout.addWidget(self.stick_double_radius_slider)
|
|
742
|
+
stick_double_radius_layout.addWidget(self.stick_double_radius_label)
|
|
743
|
+
form_layout.addRow("Double Bond Thickness (Stick):", stick_double_radius_layout)
|
|
744
|
+
|
|
745
|
+
self.stick_triple_radius_slider = QSlider(Qt.Orientation.Horizontal)
|
|
746
|
+
self.stick_triple_radius_slider.setRange(20, 100)
|
|
747
|
+
self.stick_triple_radius_label = QLabel("0.70")
|
|
748
|
+
self.stick_triple_radius_slider.valueChanged.connect(lambda v: self.stick_triple_radius_label.setText(f"{v/100:.2f}"))
|
|
749
|
+
stick_triple_radius_layout = QHBoxLayout()
|
|
750
|
+
stick_triple_radius_layout.addWidget(self.stick_triple_radius_slider)
|
|
751
|
+
stick_triple_radius_layout.addWidget(self.stick_triple_radius_label)
|
|
752
|
+
form_layout.addRow("Triple Bond Thickness (Stick):", stick_triple_radius_layout)
|
|
753
|
+
|
|
754
|
+
# --- 区切り線(水平ライン) ---
|
|
755
|
+
line = QFrame()
|
|
756
|
+
line.setFrameShape(QFrame.Shape.HLine)
|
|
757
|
+
line.setFrameShadow(QFrame.Shadow.Sunken)
|
|
758
|
+
form_layout.addRow(line)
|
|
759
|
+
|
|
760
|
+
# 解像度
|
|
761
|
+
self.stick_resolution_slider = QSlider(Qt.Orientation.Horizontal)
|
|
762
|
+
self.stick_resolution_slider.setRange(6, 32)
|
|
763
|
+
self.stick_resolution_label = QLabel("16")
|
|
764
|
+
self.stick_resolution_slider.valueChanged.connect(lambda v: self.stick_resolution_label.setText(str(v)))
|
|
765
|
+
resolution_layout = QHBoxLayout()
|
|
766
|
+
resolution_layout.addWidget(self.stick_resolution_slider)
|
|
767
|
+
resolution_layout.addWidget(self.stick_resolution_label)
|
|
768
|
+
form_layout.addRow("Resolution (Quality):", resolution_layout)
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
self.tab_widget.addTab(stick_widget, "Stick")
|
|
774
|
+
|
|
775
|
+
def reset_current_tab(self):
|
|
776
|
+
"""現在選択されているタブの設定のみをデフォルトに戻す"""
|
|
777
|
+
current_tab_index = self.tab_widget.currentIndex()
|
|
778
|
+
tab_name = self.tab_widget.tabText(current_tab_index)
|
|
779
|
+
|
|
780
|
+
# 各タブの設定項目を定義
|
|
781
|
+
# Each tab settings
|
|
782
|
+
# Note: tab labels must match those added to the QTabWidget ("2D Settings", "Scene", "Ball & Stick", ...
|
|
783
|
+
# "CPK (Space-filling)", "Wireframe", "Stick", "Other"). Use the per-model
|
|
784
|
+
# multi-bond keys present in self.default_settings.
|
|
785
|
+
tab_settings = {
|
|
786
|
+
"2D Settings": {
|
|
787
|
+
'bond_width_2d': self.default_settings['bond_width_2d'],
|
|
788
|
+
'bond_spacing_double_2d': self.default_settings['bond_spacing_double_2d'],
|
|
789
|
+
'bond_spacing_triple_2d': self.default_settings['bond_spacing_triple_2d'],
|
|
790
|
+
'atom_font_size_2d': self.default_settings['atom_font_size_2d'],
|
|
791
|
+
'background_color_2d': self.default_settings['background_color_2d'],
|
|
792
|
+
'bond_color_2d': self.default_settings['bond_color_2d'],
|
|
793
|
+
'atom_use_bond_color_2d': self.default_settings['atom_use_bond_color_2d'],
|
|
794
|
+
'bond_cap_style_2d': self.default_settings['bond_cap_style_2d']
|
|
795
|
+
},
|
|
796
|
+
"3D Scene": {
|
|
797
|
+
'background_color': self.default_settings['background_color'],
|
|
798
|
+
'projection_mode': self.default_settings['projection_mode'],
|
|
799
|
+
'show_3d_axes': self.default_settings['show_3d_axes'],
|
|
800
|
+
'lighting_enabled': self.default_settings['lighting_enabled'],
|
|
801
|
+
'light_intensity': self.default_settings['light_intensity'],
|
|
802
|
+
'specular': self.default_settings['specular'],
|
|
803
|
+
'specular_power': self.default_settings['specular_power']
|
|
804
|
+
},
|
|
805
|
+
"Other": {
|
|
806
|
+
# other options
|
|
807
|
+
'skip_chemistry_checks': self.default_settings.get('skip_chemistry_checks', False),
|
|
808
|
+
'display_kekule_3d': self.default_settings.get('display_kekule_3d', False),
|
|
809
|
+
'always_ask_charge': self.default_settings.get('always_ask_charge', False),
|
|
810
|
+
'display_aromatic_circles_3d': self.default_settings.get('display_aromatic_circles_3d', False),
|
|
811
|
+
'aromatic_torus_thickness_factor': self.default_settings.get('aromatic_torus_thickness_factor', 0.6),
|
|
812
|
+
},
|
|
813
|
+
"Ball & Stick": {
|
|
814
|
+
'ball_stick_atom_scale': self.default_settings['ball_stick_atom_scale'],
|
|
815
|
+
'ball_stick_bond_radius': self.default_settings['ball_stick_bond_radius'],
|
|
816
|
+
'ball_stick_resolution': self.default_settings['ball_stick_resolution'],
|
|
817
|
+
'ball_stick_double_bond_offset_factor': self.default_settings.get('ball_stick_double_bond_offset_factor', 2.0),
|
|
818
|
+
'ball_stick_triple_bond_offset_factor': self.default_settings.get('ball_stick_triple_bond_offset_factor', 2.0),
|
|
819
|
+
'ball_stick_double_bond_radius_factor': self.default_settings.get('ball_stick_double_bond_radius_factor', 0.8),
|
|
820
|
+
'ball_stick_triple_bond_radius_factor': self.default_settings.get('ball_stick_triple_bond_radius_factor', 0.75),
|
|
821
|
+
'ball_stick_use_cpk_bond_color': self.default_settings['ball_stick_use_cpk_bond_color'],
|
|
822
|
+
'ball_stick_bond_color': self.default_settings.get('ball_stick_bond_color', '#7F7F7F')
|
|
823
|
+
},
|
|
824
|
+
"CPK (Space-filling)": {
|
|
825
|
+
'cpk_atom_scale': self.default_settings['cpk_atom_scale'],
|
|
826
|
+
'cpk_resolution': self.default_settings['cpk_resolution'],
|
|
827
|
+
'cpk_colors': {}
|
|
828
|
+
},
|
|
829
|
+
"Wireframe": {
|
|
830
|
+
'wireframe_bond_radius': self.default_settings['wireframe_bond_radius'],
|
|
831
|
+
'wireframe_resolution': self.default_settings['wireframe_resolution'],
|
|
832
|
+
'wireframe_double_bond_offset_factor': self.default_settings.get('wireframe_double_bond_offset_factor', 3.0),
|
|
833
|
+
'wireframe_triple_bond_offset_factor': self.default_settings.get('wireframe_triple_bond_offset_factor', 3.0),
|
|
834
|
+
'wireframe_double_bond_radius_factor': self.default_settings.get('wireframe_double_bond_radius_factor', 0.8),
|
|
835
|
+
'wireframe_triple_bond_radius_factor': self.default_settings.get('wireframe_triple_bond_radius_factor', 0.75)
|
|
836
|
+
},
|
|
837
|
+
"Stick": {
|
|
838
|
+
'stick_bond_radius': self.default_settings['stick_bond_radius'],
|
|
839
|
+
'stick_resolution': self.default_settings['stick_resolution'],
|
|
840
|
+
'stick_double_bond_offset_factor': self.default_settings.get('stick_double_bond_offset_factor', 1.5),
|
|
841
|
+
'stick_triple_bond_offset_factor': self.default_settings.get('stick_triple_bond_offset_factor', 1.0),
|
|
842
|
+
'stick_double_bond_radius_factor': self.default_settings.get('stick_double_bond_radius_factor', 0.6),
|
|
843
|
+
'stick_triple_bond_radius_factor': self.default_settings.get('stick_triple_bond_radius_factor', 0.4)
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
# 選択されたタブの設定のみを適用
|
|
848
|
+
if tab_name in tab_settings:
|
|
849
|
+
tab_defaults = tab_settings[tab_name]
|
|
850
|
+
|
|
851
|
+
# 現在の設定を取得
|
|
852
|
+
current_settings = self.get_current_ui_settings()
|
|
853
|
+
|
|
854
|
+
# 選択されたタブの項目のみをデフォルト値で更新
|
|
855
|
+
updated_settings = current_settings.copy()
|
|
856
|
+
updated_settings.update(tab_defaults)
|
|
857
|
+
|
|
858
|
+
# UIを更新
|
|
859
|
+
self.update_ui_from_settings(updated_settings)
|
|
860
|
+
|
|
861
|
+
# If 2D settings were reset, update internal color variables too
|
|
862
|
+
if tab_name == "2D Settings":
|
|
863
|
+
self.current_bg_color_2d = updated_settings.get('background_color_2d', self.default_settings['background_color_2d'])
|
|
864
|
+
self.current_bond_color_2d = updated_settings.get('bond_color_2d', self.default_settings['bond_color_2d'])
|
|
865
|
+
self.update_2d_color_buttons()
|
|
866
|
+
|
|
867
|
+
# CPK tab: do not change parent/settings immediately; let Apply/OK persist any changes
|
|
868
|
+
|
|
869
|
+
# ユーザーへのフィードバック
|
|
870
|
+
QMessageBox.information(self, "Reset Complete", f"Settings for '{tab_name}' tab have been reset to defaults.")
|
|
871
|
+
else:
|
|
872
|
+
QMessageBox.warning(self, "Error", f"Unknown tab: {tab_name}")
|
|
873
|
+
|
|
874
|
+
def reset_all_settings(self):
|
|
875
|
+
"""すべての設定をデフォルトに戻す"""
|
|
876
|
+
reply = QMessageBox.question(
|
|
877
|
+
self,
|
|
878
|
+
"Reset All Settings",
|
|
879
|
+
"Are you sure you want to reset all settings to defaults?",
|
|
880
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
881
|
+
QMessageBox.StandardButton.No
|
|
882
|
+
)
|
|
883
|
+
|
|
884
|
+
if reply == QMessageBox.StandardButton.Yes:
|
|
885
|
+
# Update the dialog UI
|
|
886
|
+
self.update_ui_from_settings(self.default_settings)
|
|
887
|
+
|
|
888
|
+
# Also persist defaults to the application-level settings if parent is available
|
|
889
|
+
try:
|
|
890
|
+
if self.parent_window and hasattr(self.parent_window, 'settings'):
|
|
891
|
+
# Update parent settings and save
|
|
892
|
+
self.parent_window.settings.update(self.default_settings)
|
|
893
|
+
# defer writing to disk; mark dirty so closeEvent will persist
|
|
894
|
+
try:
|
|
895
|
+
self.parent_window.settings_dirty = True
|
|
896
|
+
except Exception:
|
|
897
|
+
pass
|
|
898
|
+
# Also ensure color settings return to defaults and UI reflects them
|
|
899
|
+
try:
|
|
900
|
+
# Remove any CPK overrides to restore defaults
|
|
901
|
+
if 'cpk_colors' in self.parent_window.settings:
|
|
902
|
+
# Reset to defaults (empty override dict)
|
|
903
|
+
self.parent_window.settings['cpk_colors'] = {}
|
|
904
|
+
|
|
905
|
+
# Reset 2D Settings (colors need manual refresh in parent scene/view usually handled by apply)
|
|
906
|
+
|
|
907
|
+
# Reset 3D Ball & Stick uniform bond color to default
|
|
908
|
+
self.parent_window.settings['ball_stick_bond_color'] = self.default_settings.get('ball_stick_bond_color', '#7F7F7F')
|
|
909
|
+
# Update global CPK colors and reapply 3D settings immediately
|
|
910
|
+
try:
|
|
911
|
+
self.parent_window.update_cpk_colors_from_settings()
|
|
912
|
+
except Exception:
|
|
913
|
+
pass
|
|
914
|
+
try:
|
|
915
|
+
self.parent_window.apply_3d_settings()
|
|
916
|
+
except Exception:
|
|
917
|
+
pass
|
|
918
|
+
# Re-draw current 3D molecule if any
|
|
919
|
+
try:
|
|
920
|
+
if hasattr(self.parent_window, 'current_mol') and self.parent_window.current_mol:
|
|
921
|
+
self.parent_window.draw_molecule_3d(self.parent_window.current_mol)
|
|
922
|
+
except Exception:
|
|
923
|
+
pass
|
|
924
|
+
# Update 2D scene items to reflect color reset
|
|
925
|
+
try:
|
|
926
|
+
if hasattr(self.parent_window, 'scene'):
|
|
927
|
+
for it in self.parent_window.scene.items():
|
|
928
|
+
try:
|
|
929
|
+
if hasattr(it, 'update_style'):
|
|
930
|
+
it.update_style()
|
|
931
|
+
except Exception:
|
|
932
|
+
pass
|
|
933
|
+
except Exception:
|
|
934
|
+
pass
|
|
935
|
+
# Mark settings dirty so they'll be saved on exit
|
|
936
|
+
try:
|
|
937
|
+
self.parent_window.settings_dirty = True
|
|
938
|
+
except Exception:
|
|
939
|
+
pass
|
|
940
|
+
except Exception:
|
|
941
|
+
pass
|
|
942
|
+
|
|
943
|
+
# Refresh parent's optimization and conversion menu/action states
|
|
944
|
+
try:
|
|
945
|
+
# Optimization method
|
|
946
|
+
if hasattr(self.parent_window, 'optimization_method'):
|
|
947
|
+
self.parent_window.optimization_method = self.parent_window.settings.get('optimization_method', 'MMFF_RDKIT')
|
|
948
|
+
if hasattr(self.parent_window, 'opt3d_actions'):
|
|
949
|
+
for k, act in self.parent_window.opt3d_actions.items():
|
|
950
|
+
try:
|
|
951
|
+
act.setChecked(k.upper() == (self.parent_window.optimization_method or '').upper())
|
|
952
|
+
except Exception:
|
|
953
|
+
pass
|
|
954
|
+
|
|
955
|
+
# Conversion mode
|
|
956
|
+
conv_mode = self.parent_window.settings.get('3d_conversion_mode', 'fallback')
|
|
957
|
+
if hasattr(self.parent_window, 'conv_actions'):
|
|
958
|
+
for k, act in self.parent_window.conv_actions.items():
|
|
959
|
+
try:
|
|
960
|
+
act.setChecked(k == conv_mode)
|
|
961
|
+
except Exception:
|
|
962
|
+
pass
|
|
963
|
+
except Exception:
|
|
964
|
+
pass
|
|
965
|
+
except Exception:
|
|
966
|
+
pass
|
|
967
|
+
|
|
968
|
+
QMessageBox.information(self, "Reset Complete", "All settings have been reset to defaults.")
|
|
969
|
+
|
|
970
|
+
def get_current_ui_settings(self):
|
|
971
|
+
"""現在のUIから設定値を取得"""
|
|
972
|
+
return {
|
|
973
|
+
'background_color': self.current_bg_color,
|
|
974
|
+
'show_3d_axes': self.axes_checkbox.isChecked(),
|
|
975
|
+
'lighting_enabled': self.light_checkbox.isChecked(),
|
|
976
|
+
'light_intensity': self.intensity_slider.value() / 100.0,
|
|
977
|
+
'specular': self.specular_slider.value() / 100.0,
|
|
978
|
+
'specular_power': self.spec_power_slider.value(),
|
|
979
|
+
# Ball and Stick settings
|
|
980
|
+
'ball_stick_atom_scale': self.bs_atom_scale_slider.value() / 100.0,
|
|
981
|
+
'ball_stick_bond_radius': self.bs_bond_radius_slider.value() / 100.0,
|
|
982
|
+
'ball_stick_resolution': self.bs_resolution_slider.value(),
|
|
983
|
+
# CPK settings
|
|
984
|
+
'cpk_atom_scale': self.cpk_atom_scale_slider.value() / 100.0,
|
|
985
|
+
'cpk_resolution': self.cpk_resolution_slider.value(),
|
|
986
|
+
# Wireframe settings
|
|
987
|
+
'wireframe_bond_radius': self.wf_bond_radius_slider.value() / 100.0,
|
|
988
|
+
'wireframe_resolution': self.wf_resolution_slider.value(),
|
|
989
|
+
# Stick settings
|
|
990
|
+
'stick_bond_radius': self.stick_bond_radius_slider.value() / 100.0,
|
|
991
|
+
'stick_resolution': self.stick_resolution_slider.value(),
|
|
992
|
+
# Multi-bond settings (per-model)
|
|
993
|
+
'ball_stick_double_bond_offset_factor': self.bs_double_offset_slider.value() / 100.0,
|
|
994
|
+
'ball_stick_triple_bond_offset_factor': self.bs_triple_offset_slider.value() / 100.0,
|
|
995
|
+
'ball_stick_double_bond_radius_factor': self.bs_double_radius_slider.value() / 100.0,
|
|
996
|
+
'ball_stick_triple_bond_radius_factor': self.bs_triple_radius_slider.value() / 100.0,
|
|
997
|
+
'wireframe_double_bond_offset_factor': self.wf_double_offset_slider.value() / 100.0,
|
|
998
|
+
'wireframe_triple_bond_offset_factor': self.wf_triple_offset_slider.value() / 100.0,
|
|
999
|
+
'wireframe_double_bond_radius_factor': self.wf_double_radius_slider.value() / 100.0,
|
|
1000
|
+
'wireframe_triple_bond_radius_factor': self.wf_triple_radius_slider.value() / 100.0,
|
|
1001
|
+
'stick_double_bond_offset_factor': self.stick_double_offset_slider.value() / 100.0,
|
|
1002
|
+
'stick_triple_bond_offset_factor': self.stick_triple_offset_slider.value() / 100.0,
|
|
1003
|
+
'stick_double_bond_radius_factor': self.stick_double_radius_slider.value() / 100.0,
|
|
1004
|
+
'stick_triple_bond_radius_factor': self.stick_triple_radius_slider.value() / 100.0,
|
|
1005
|
+
# Projection mode
|
|
1006
|
+
'projection_mode': self.projection_combo.currentText(),
|
|
1007
|
+
# Kekule / aromatic / torus settings
|
|
1008
|
+
'display_kekule_3d': self.kekule_3d_checkbox.isChecked(),
|
|
1009
|
+
'display_aromatic_circles_3d': self.aromatic_circle_checkbox.isChecked(),
|
|
1010
|
+
'aromatic_torus_thickness_factor': self.aromatic_torus_thickness_slider.value() / 100.0,
|
|
1011
|
+
'skip_chemistry_checks': self.skip_chem_checks_checkbox.isChecked(),
|
|
1012
|
+
'always_ask_charge': self.always_ask_charge_checkbox.isChecked(),
|
|
1013
|
+
# Ball & Stick bond color and use-cpk option
|
|
1014
|
+
'ball_stick_bond_color': getattr(self, 'bs_bond_color', self.default_settings.get('ball_stick_bond_color')),
|
|
1015
|
+
'ball_stick_use_cpk_bond_color': self.bs_use_cpk_bond_checkbox.isChecked(),
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
def reset_to_defaults(self):
|
|
1019
|
+
"""UIをデフォルト設定に戻す(後方互換性のため残存)"""
|
|
1020
|
+
self.reset_all_settings()
|
|
1021
|
+
|
|
1022
|
+
def update_ui_from_settings(self, settings_dict):
|
|
1023
|
+
# 基本設定
|
|
1024
|
+
self.current_bg_color = settings_dict.get('background_color', self.default_settings['background_color'])
|
|
1025
|
+
self.update_color_button(self.current_bg_color)
|
|
1026
|
+
self.axes_checkbox.setChecked(settings_dict.get('show_3d_axes', self.default_settings['show_3d_axes']))
|
|
1027
|
+
self.light_checkbox.setChecked(settings_dict.get('lighting_enabled', self.default_settings['lighting_enabled']))
|
|
1028
|
+
|
|
1029
|
+
# スライダーの値を設定
|
|
1030
|
+
intensity_val = int(settings_dict.get('light_intensity', self.default_settings['light_intensity']) * 100)
|
|
1031
|
+
self.intensity_slider.setValue(intensity_val)
|
|
1032
|
+
self.intensity_label.setText(f"{intensity_val/100:.2f}")
|
|
1033
|
+
|
|
1034
|
+
specular_val = int(settings_dict.get('specular', self.default_settings['specular']) * 100)
|
|
1035
|
+
self.specular_slider.setValue(specular_val)
|
|
1036
|
+
self.specular_label.setText(f"{specular_val/100:.2f}")
|
|
1037
|
+
|
|
1038
|
+
self.spec_power_slider.setValue(settings_dict.get('specular_power', self.default_settings['specular_power']))
|
|
1039
|
+
self.spec_power_label.setText(str(settings_dict.get('specular_power', self.default_settings['specular_power'])))
|
|
1040
|
+
|
|
1041
|
+
# Ball and Stick設定
|
|
1042
|
+
bs_atom_scale = int(settings_dict.get('ball_stick_atom_scale', self.default_settings['ball_stick_atom_scale']) * 100)
|
|
1043
|
+
self.bs_atom_scale_slider.setValue(bs_atom_scale)
|
|
1044
|
+
self.bs_atom_scale_label.setText(f"{bs_atom_scale/100:.2f}")
|
|
1045
|
+
|
|
1046
|
+
bs_bond_radius = int(settings_dict.get('ball_stick_bond_radius', self.default_settings['ball_stick_bond_radius']) * 100)
|
|
1047
|
+
self.bs_bond_radius_slider.setValue(bs_bond_radius)
|
|
1048
|
+
self.bs_bond_radius_label.setText(f"{bs_bond_radius/100:.2f}")
|
|
1049
|
+
|
|
1050
|
+
self.bs_resolution_slider.setValue(settings_dict.get('ball_stick_resolution', self.default_settings['ball_stick_resolution']))
|
|
1051
|
+
self.bs_resolution_label.setText(str(settings_dict.get('ball_stick_resolution', self.default_settings['ball_stick_resolution'])))
|
|
1052
|
+
# Ball & Stick bond color (uniform gray color for ball-and-stick)
|
|
1053
|
+
bs_bond_color = settings_dict.get('ball_stick_bond_color', self.default_settings.get('ball_stick_bond_color', '#7F7F7F'))
|
|
1054
|
+
try:
|
|
1055
|
+
self.bs_bond_color = QColor(bs_bond_color).name()
|
|
1056
|
+
except Exception:
|
|
1057
|
+
self.bs_bond_color = self.default_settings.get('ball_stick_bond_color', '#7F7F7F')
|
|
1058
|
+
# Ensure color button exists and update its appearance
|
|
1059
|
+
try:
|
|
1060
|
+
if hasattr(self, 'bs_bond_color_button'):
|
|
1061
|
+
self.bs_bond_color_button.setStyleSheet(f"background-color: {self.bs_bond_color}; border: 1px solid #888;")
|
|
1062
|
+
try:
|
|
1063
|
+
self.bs_bond_color_button.setToolTip(self.bs_bond_color)
|
|
1064
|
+
except Exception:
|
|
1065
|
+
pass
|
|
1066
|
+
except Exception:
|
|
1067
|
+
pass
|
|
1068
|
+
|
|
1069
|
+
# Ball & Stick CPK bond color option
|
|
1070
|
+
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']))
|
|
1071
|
+
|
|
1072
|
+
# CPK設定
|
|
1073
|
+
cpk_atom_scale = int(settings_dict.get('cpk_atom_scale', self.default_settings['cpk_atom_scale']) * 100)
|
|
1074
|
+
self.cpk_atom_scale_slider.setValue(cpk_atom_scale)
|
|
1075
|
+
self.cpk_atom_scale_label.setText(f"{cpk_atom_scale/100:.2f}")
|
|
1076
|
+
|
|
1077
|
+
self.cpk_resolution_slider.setValue(settings_dict.get('cpk_resolution', self.default_settings['cpk_resolution']))
|
|
1078
|
+
self.cpk_resolution_label.setText(str(settings_dict.get('cpk_resolution', self.default_settings['cpk_resolution'])))
|
|
1079
|
+
|
|
1080
|
+
# Wireframe設定
|
|
1081
|
+
wf_bond_radius = int(settings_dict.get('wireframe_bond_radius', self.default_settings['wireframe_bond_radius']) * 100)
|
|
1082
|
+
self.wf_bond_radius_slider.setValue(wf_bond_radius)
|
|
1083
|
+
self.wf_bond_radius_label.setText(f"{wf_bond_radius/100:.2f}")
|
|
1084
|
+
|
|
1085
|
+
self.wf_resolution_slider.setValue(settings_dict.get('wireframe_resolution', self.default_settings['wireframe_resolution']))
|
|
1086
|
+
self.wf_resolution_label.setText(str(settings_dict.get('wireframe_resolution', self.default_settings['wireframe_resolution'])))
|
|
1087
|
+
|
|
1088
|
+
# Stick設定
|
|
1089
|
+
stick_bond_radius = int(settings_dict.get('stick_bond_radius', self.default_settings['stick_bond_radius']) * 100)
|
|
1090
|
+
self.stick_bond_radius_slider.setValue(stick_bond_radius)
|
|
1091
|
+
self.stick_bond_radius_label.setText(f"{stick_bond_radius/100:.2f}")
|
|
1092
|
+
|
|
1093
|
+
self.stick_resolution_slider.setValue(settings_dict.get('stick_resolution', self.default_settings['stick_resolution']))
|
|
1094
|
+
self.stick_resolution_label.setText(str(settings_dict.get('stick_resolution', self.default_settings['stick_resolution'])))
|
|
1095
|
+
|
|
1096
|
+
# 多重結合設定(モデル毎) — 後方互換のため既存のグローバルキーがあればフォールバック
|
|
1097
|
+
# Ball & Stick
|
|
1098
|
+
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)
|
|
1099
|
+
self.bs_double_offset_slider.setValue(bs_double_offset)
|
|
1100
|
+
self.bs_double_offset_label.setText(f"{bs_double_offset/100:.2f}")
|
|
1101
|
+
|
|
1102
|
+
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)
|
|
1103
|
+
self.bs_triple_offset_slider.setValue(bs_triple_offset)
|
|
1104
|
+
self.bs_triple_offset_label.setText(f"{bs_triple_offset/100:.2f}")
|
|
1105
|
+
|
|
1106
|
+
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)
|
|
1107
|
+
self.bs_double_radius_slider.setValue(bs_double_radius)
|
|
1108
|
+
self.bs_double_radius_label.setText(f"{bs_double_radius/100:.2f}")
|
|
1109
|
+
|
|
1110
|
+
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)
|
|
1111
|
+
self.bs_triple_radius_slider.setValue(bs_triple_radius)
|
|
1112
|
+
self.bs_triple_radius_label.setText(f"{bs_triple_radius/100:.2f}")
|
|
1113
|
+
|
|
1114
|
+
# Wireframe
|
|
1115
|
+
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)
|
|
1116
|
+
self.wf_double_offset_slider.setValue(wf_double_offset)
|
|
1117
|
+
self.wf_double_offset_label.setText(f"{wf_double_offset/100:.2f}")
|
|
1118
|
+
|
|
1119
|
+
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)
|
|
1120
|
+
self.wf_triple_offset_slider.setValue(wf_triple_offset)
|
|
1121
|
+
self.wf_triple_offset_label.setText(f"{wf_triple_offset/100:.2f}")
|
|
1122
|
+
|
|
1123
|
+
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)
|
|
1124
|
+
self.wf_double_radius_slider.setValue(wf_double_radius)
|
|
1125
|
+
self.wf_double_radius_label.setText(f"{wf_double_radius/100:.2f}")
|
|
1126
|
+
|
|
1127
|
+
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)
|
|
1128
|
+
self.wf_triple_radius_slider.setValue(wf_triple_radius)
|
|
1129
|
+
self.wf_triple_radius_label.setText(f"{wf_triple_radius/100:.2f}")
|
|
1130
|
+
|
|
1131
|
+
# Stick
|
|
1132
|
+
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)
|
|
1133
|
+
self.stick_double_offset_slider.setValue(stick_double_offset)
|
|
1134
|
+
self.stick_double_offset_label.setText(f"{stick_double_offset/100:.2f}")
|
|
1135
|
+
|
|
1136
|
+
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)
|
|
1137
|
+
self.stick_triple_offset_slider.setValue(stick_triple_offset)
|
|
1138
|
+
self.stick_triple_offset_label.setText(f"{stick_triple_offset/100:.2f}")
|
|
1139
|
+
|
|
1140
|
+
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)
|
|
1141
|
+
self.stick_double_radius_slider.setValue(stick_double_radius)
|
|
1142
|
+
self.stick_double_radius_label.setText(f"{stick_double_radius/100:.2f}")
|
|
1143
|
+
|
|
1144
|
+
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)
|
|
1145
|
+
self.stick_triple_radius_slider.setValue(stick_triple_radius)
|
|
1146
|
+
self.stick_triple_radius_label.setText(f"{stick_triple_radius/100:.2f}")
|
|
1147
|
+
|
|
1148
|
+
# Projection mode
|
|
1149
|
+
proj_mode = settings_dict.get('projection_mode', self.default_settings.get('projection_mode', 'Perspective'))
|
|
1150
|
+
idx = self.projection_combo.findText(proj_mode)
|
|
1151
|
+
self.projection_combo.setCurrentIndex(idx if idx != -1 else 0)
|
|
1152
|
+
# skip chemistry checks
|
|
1153
|
+
self.skip_chem_checks_checkbox.setChecked(settings_dict.get('skip_chemistry_checks', self.default_settings.get('skip_chemistry_checks', False)))
|
|
1154
|
+
# kekule setting
|
|
1155
|
+
self.kekule_3d_checkbox.setChecked(settings_dict.get('display_kekule_3d', self.default_settings.get('display_kekule_3d', False)))
|
|
1156
|
+
# always ask for charge on XYZ imports
|
|
1157
|
+
self.always_ask_charge_checkbox.setChecked(settings_dict.get('always_ask_charge', self.default_settings.get('always_ask_charge', False)))
|
|
1158
|
+
# Aromatic ring circle display and torus thickness factor
|
|
1159
|
+
self.aromatic_circle_checkbox.setChecked(settings_dict.get('display_aromatic_circles_3d', self.default_settings.get('display_aromatic_circles_3d', False)))
|
|
1160
|
+
thickness_factor = float(settings_dict.get('aromatic_torus_thickness_factor', self.default_settings.get('aromatic_torus_thickness_factor', 0.6)))
|
|
1161
|
+
try:
|
|
1162
|
+
self.aromatic_torus_thickness_slider.setValue(int(thickness_factor * 100))
|
|
1163
|
+
self.aromatic_torus_thickness_label.setText(f"{thickness_factor:.1f}")
|
|
1164
|
+
except Exception:
|
|
1165
|
+
pass
|
|
1166
|
+
|
|
1167
|
+
def select_color(self):
|
|
1168
|
+
"""カラーピッカーを開き、選択された色を内部変数とUIに反映させる"""
|
|
1169
|
+
# 内部変数から現在の色を取得してカラーピッカーを初期化
|
|
1170
|
+
color = QColorDialog.getColor(QColor(self.current_bg_color), self)
|
|
1171
|
+
if color.isValid():
|
|
1172
|
+
# 内部変数を更新
|
|
1173
|
+
self.current_bg_color = color.name()
|
|
1174
|
+
# UIの見た目を更新
|
|
1175
|
+
self.update_color_button(self.current_bg_color)
|
|
1176
|
+
|
|
1177
|
+
def update_color_button(self, color_hex):
|
|
1178
|
+
"""ボタンの背景色と境界線を設定する"""
|
|
1179
|
+
self.bg_button.setStyleSheet(f"background-color: {color_hex}; border: 1px solid #888;")
|
|
1180
|
+
|
|
1181
|
+
def get_settings(self):
|
|
1182
|
+
return {
|
|
1183
|
+
'background_color': self.current_bg_color,
|
|
1184
|
+
'projection_mode': self.projection_combo.currentText(),
|
|
1185
|
+
'show_3d_axes': self.axes_checkbox.isChecked(),
|
|
1186
|
+
'lighting_enabled': self.light_checkbox.isChecked(),
|
|
1187
|
+
'light_intensity': self.intensity_slider.value() / 100.0,
|
|
1188
|
+
'specular': self.specular_slider.value() / 100.0,
|
|
1189
|
+
'specular_power': self.spec_power_slider.value(),
|
|
1190
|
+
# Ball and Stick settings
|
|
1191
|
+
'ball_stick_atom_scale': self.bs_atom_scale_slider.value() / 100.0,
|
|
1192
|
+
'ball_stick_bond_radius': self.bs_bond_radius_slider.value() / 100.0,
|
|
1193
|
+
'ball_stick_resolution': self.bs_resolution_slider.value(),
|
|
1194
|
+
# CPK settings
|
|
1195
|
+
'cpk_atom_scale': self.cpk_atom_scale_slider.value() / 100.0,
|
|
1196
|
+
'cpk_resolution': self.cpk_resolution_slider.value(),
|
|
1197
|
+
# Wireframe settings
|
|
1198
|
+
'wireframe_bond_radius': self.wf_bond_radius_slider.value() / 100.0,
|
|
1199
|
+
'wireframe_resolution': self.wf_resolution_slider.value(),
|
|
1200
|
+
# Stick settings
|
|
1201
|
+
'stick_bond_radius': self.stick_bond_radius_slider.value() / 100.0,
|
|
1202
|
+
'stick_resolution': self.stick_resolution_slider.value(),
|
|
1203
|
+
# Multiple bond offset settings (per-model)
|
|
1204
|
+
'ball_stick_double_bond_offset_factor': self.bs_double_offset_slider.value() / 100.0,
|
|
1205
|
+
'ball_stick_triple_bond_offset_factor': self.bs_triple_offset_slider.value() / 100.0,
|
|
1206
|
+
'ball_stick_double_bond_radius_factor': self.bs_double_radius_slider.value() / 100.0,
|
|
1207
|
+
'ball_stick_triple_bond_radius_factor': self.bs_triple_radius_slider.value() / 100.0,
|
|
1208
|
+
'wireframe_double_bond_offset_factor': self.wf_double_offset_slider.value() / 100.0,
|
|
1209
|
+
'wireframe_triple_bond_offset_factor': self.wf_triple_offset_slider.value() / 100.0,
|
|
1210
|
+
'wireframe_double_bond_radius_factor': self.wf_double_radius_slider.value() / 100.0,
|
|
1211
|
+
'wireframe_triple_bond_radius_factor': self.wf_triple_radius_slider.value() / 100.0,
|
|
1212
|
+
'stick_double_bond_offset_factor': self.stick_double_offset_slider.value() / 100.0,
|
|
1213
|
+
'stick_triple_bond_offset_factor': self.stick_triple_offset_slider.value() / 100.0,
|
|
1214
|
+
'stick_double_bond_radius_factor': self.stick_double_radius_slider.value() / 100.0,
|
|
1215
|
+
'stick_triple_bond_radius_factor': self.stick_triple_radius_slider.value() / 100.0,
|
|
1216
|
+
'display_kekule_3d': self.kekule_3d_checkbox.isChecked(),
|
|
1217
|
+
'display_aromatic_circles_3d': self.aromatic_circle_checkbox.isChecked(),
|
|
1218
|
+
'aromatic_torus_thickness_factor': self.aromatic_torus_thickness_slider.value() / 100.0,
|
|
1219
|
+
'skip_chemistry_checks': self.skip_chem_checks_checkbox.isChecked(),
|
|
1220
|
+
'always_ask_charge': self.always_ask_charge_checkbox.isChecked(),
|
|
1221
|
+
'ball_stick_bond_color': getattr(self, 'bs_bond_color', self.default_settings.get('ball_stick_bond_color', '#7F7F7F')),
|
|
1222
|
+
'ball_stick_use_cpk_bond_color': self.bs_use_cpk_bond_checkbox.isChecked(),
|
|
1223
|
+
# 2D Settings
|
|
1224
|
+
'bond_width_2d': self.bond_width_2d_slider.value() / 10.0,
|
|
1225
|
+
'bond_spacing_double_2d': self.bond_spacing_double_2d_slider.value() / 10.0,
|
|
1226
|
+
'bond_spacing_triple_2d': self.bond_spacing_triple_2d_slider.value() / 10.0,
|
|
1227
|
+
'atom_font_size_2d': self.atom_font_size_2d_slider.value(),
|
|
1228
|
+
'background_color_2d': self.current_bg_color_2d,
|
|
1229
|
+
'bond_color_2d': self.current_bond_color_2d,
|
|
1230
|
+
'atom_use_bond_color_2d': self.atom_use_bond_color_2d_checkbox.isChecked(),
|
|
1231
|
+
'bond_cap_style_2d': self.bond_cap_style_2d_combo.currentText(),
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
def pick_bs_bond_color(self):
|
|
1235
|
+
"""Open QColorDialog to pick Ball & Stick bond color (3D)."""
|
|
1236
|
+
cur = getattr(self, 'bs_bond_color', self.default_settings.get('ball_stick_bond_color', '#7F7F7F'))
|
|
1237
|
+
color = QColorDialog.getColor(QColor(cur), self)
|
|
1238
|
+
if color.isValid():
|
|
1239
|
+
self.bs_bond_color = color.name()
|
|
1240
|
+
try:
|
|
1241
|
+
self.bs_bond_color_button.setStyleSheet(f"background-color: {self.bs_bond_color}; border: 1px solid #888;")
|
|
1242
|
+
self.bs_bond_color_button.setToolTip(self.bs_bond_color)
|
|
1243
|
+
except Exception:
|
|
1244
|
+
pass
|
|
1245
|
+
|
|
1246
|
+
def apply_settings(self):
|
|
1247
|
+
"""設定を適用(ダイアログは開いたまま)"""
|
|
1248
|
+
# 親ウィンドウの設定を更新
|
|
1249
|
+
if self.parent_window:
|
|
1250
|
+
settings = self.get_settings()
|
|
1251
|
+
self.parent_window.settings.update(settings)
|
|
1252
|
+
# Mark settings dirty; persist on exit to avoid frequent disk writes
|
|
1253
|
+
try:
|
|
1254
|
+
self.parent_window.settings_dirty = True
|
|
1255
|
+
except Exception:
|
|
1256
|
+
pass
|
|
1257
|
+
# 3Dビューの設定を適用
|
|
1258
|
+
self.parent_window.apply_3d_settings()
|
|
1259
|
+
# Update CPK colors from settings if present (no-op otherwise)
|
|
1260
|
+
try:
|
|
1261
|
+
self.parent_window.update_cpk_colors_from_settings()
|
|
1262
|
+
except Exception:
|
|
1263
|
+
pass
|
|
1264
|
+
# Refresh any open CPK color dialogs so they update their UI
|
|
1265
|
+
try:
|
|
1266
|
+
for w in QApplication.topLevelWidgets():
|
|
1267
|
+
try:
|
|
1268
|
+
# import locally to avoid circular import
|
|
1269
|
+
try:
|
|
1270
|
+
from .color_settings_dialog import ColorSettingsDialog
|
|
1271
|
+
except Exception:
|
|
1272
|
+
from modules.color_settings_dialog import ColorSettingsDialog
|
|
1273
|
+
if isinstance(w, ColorSettingsDialog):
|
|
1274
|
+
try:
|
|
1275
|
+
w.refresh_ui()
|
|
1276
|
+
except Exception:
|
|
1277
|
+
pass
|
|
1278
|
+
except Exception:
|
|
1279
|
+
pass
|
|
1280
|
+
except Exception:
|
|
1281
|
+
pass
|
|
1282
|
+
# 現在の分子を再描画(設定変更を反映)
|
|
1283
|
+
if hasattr(self.parent_window, 'current_mol') and self.parent_window.current_mol:
|
|
1284
|
+
self.parent_window.draw_molecule_3d(self.parent_window.current_mol)
|
|
1285
|
+
|
|
1286
|
+
# 2Dビューの設定適用 (背景色、結合スタイルなど)
|
|
1287
|
+
# update_style() will read the new settings from parent_window.settings
|
|
1288
|
+
try:
|
|
1289
|
+
if hasattr(self.parent_window, 'scene') and self.parent_window.scene:
|
|
1290
|
+
# Update Background
|
|
1291
|
+
bg_col_2d = self.parent_window.settings.get('background_color_2d', '#FFFFFF')
|
|
1292
|
+
self.parent_window.scene.setBackgroundBrush(QColor(bg_col_2d))
|
|
1293
|
+
|
|
1294
|
+
# Update Items
|
|
1295
|
+
for item in self.parent_window.scene.items():
|
|
1296
|
+
if hasattr(item, 'update_style'):
|
|
1297
|
+
item.update_style()
|
|
1298
|
+
elif hasattr(item, 'update'):
|
|
1299
|
+
item.update()
|
|
1300
|
+
|
|
1301
|
+
if hasattr(self.parent_window.view_2d, 'viewport'):
|
|
1302
|
+
self.parent_window.view_2d.viewport().update()
|
|
1303
|
+
except Exception:
|
|
1304
|
+
pass
|
|
1305
|
+
|
|
1306
|
+
# ステータスバーに適用完了を表示
|
|
1307
|
+
self.parent_window.statusBar().showMessage("Settings applied successfully")
|
|
1308
|
+
|
|
1309
|
+
def _on_skip_chem_checks_changed(self, state):
|
|
1310
|
+
"""Handle user toggling of skip chemistry checks: persist and update UI.
|
|
1311
|
+
|
|
1312
|
+
state: Qt.Checked (2) or Qt.Unchecked (0)
|
|
1313
|
+
"""
|
|
1314
|
+
try:
|
|
1315
|
+
enabled = bool(state)
|
|
1316
|
+
self.settings['skip_chemistry_checks'] = enabled
|
|
1317
|
+
# mark dirty instead of immediate save
|
|
1318
|
+
try:
|
|
1319
|
+
self.settings_dirty = True
|
|
1320
|
+
except Exception:
|
|
1321
|
+
pass
|
|
1322
|
+
# If skip is enabled, allow Optimize button; otherwise, respect chem_check flags
|
|
1323
|
+
|
|
1324
|
+
except Exception:
|
|
1325
|
+
pass
|
|
1326
|
+
|
|
1327
|
+
# Note: Kekule display is applied only when user clicks Apply/OK.
|
|
1328
|
+
|
|
1329
|
+
def accept(self):
|
|
1330
|
+
"""ダイアログの設定を適用してから閉じる"""
|
|
1331
|
+
# apply_settingsを呼び出して設定を適用
|
|
1332
|
+
self.apply_settings()
|
|
1333
|
+
super().accept()
|
|
1334
|
+
|
|
1335
|
+
def update_ui_from_settings(self, settings_dict):
|
|
1336
|
+
"""設定辞書に基づいてUIを更新"""
|
|
1337
|
+
# 1. Scene settings
|
|
1338
|
+
self.current_bg_color = settings_dict.get('background_color', self.default_settings['background_color'])
|
|
1339
|
+
self.update_color_button(self.current_bg_color)
|
|
1340
|
+
|
|
1341
|
+
self.axes_checkbox.setChecked(settings_dict.get('show_3d_axes', self.default_settings['show_3d_axes']))
|
|
1342
|
+
self.light_checkbox.setChecked(settings_dict.get('lighting_enabled', self.default_settings['lighting_enabled']))
|
|
1343
|
+
|
|
1344
|
+
intensity = settings_dict.get('light_intensity', self.default_settings['light_intensity'])
|
|
1345
|
+
self.intensity_slider.setValue(int(intensity * 100))
|
|
1346
|
+
self.intensity_label.setText(f"{intensity:.2f}")
|
|
1347
|
+
|
|
1348
|
+
specular = settings_dict.get('specular', self.default_settings['specular'])
|
|
1349
|
+
self.specular_slider.setValue(int(specular * 100))
|
|
1350
|
+
self.specular_label.setText(f"{specular:.2f}")
|
|
1351
|
+
|
|
1352
|
+
spec_power = settings_dict.get('specular_power', self.default_settings['specular_power'])
|
|
1353
|
+
self.spec_power_slider.setValue(int(spec_power))
|
|
1354
|
+
self.spec_power_label.setText(str(spec_power))
|
|
1355
|
+
|
|
1356
|
+
proj_mode = settings_dict.get('projection_mode', self.default_settings['projection_mode'])
|
|
1357
|
+
idx = self.projection_combo.findText(proj_mode)
|
|
1358
|
+
if idx >= 0:
|
|
1359
|
+
self.projection_combo.setCurrentIndex(idx)
|
|
1360
|
+
|
|
1361
|
+
# 2. Ball & Stick settings
|
|
1362
|
+
bs_atom_scale = settings_dict.get('ball_stick_atom_scale', self.default_settings['ball_stick_atom_scale'])
|
|
1363
|
+
self.bs_atom_scale_slider.setValue(int(bs_atom_scale * 100))
|
|
1364
|
+
self.bs_atom_scale_label.setText(f"{bs_atom_scale:.2f}")
|
|
1365
|
+
|
|
1366
|
+
bs_bond_radius = settings_dict.get('ball_stick_bond_radius', self.default_settings['ball_stick_bond_radius'])
|
|
1367
|
+
self.bs_bond_radius_slider.setValue(int(bs_bond_radius * 100))
|
|
1368
|
+
self.bs_bond_radius_label.setText(f"{bs_bond_radius:.2f}")
|
|
1369
|
+
|
|
1370
|
+
# Multi-bond offsets (Ball & Stick)
|
|
1371
|
+
bs_db_offset = settings_dict.get('ball_stick_double_bond_offset_factor', self.default_settings.get('ball_stick_double_bond_offset_factor', 2.0))
|
|
1372
|
+
self.bs_double_offset_slider.setValue(int(bs_db_offset * 100))
|
|
1373
|
+
self.bs_double_offset_label.setText(f"{bs_db_offset:.2f}")
|
|
1374
|
+
|
|
1375
|
+
bs_tr_offset = settings_dict.get('ball_stick_triple_bond_offset_factor', self.default_settings.get('ball_stick_triple_bond_offset_factor', 2.0))
|
|
1376
|
+
self.bs_triple_offset_slider.setValue(int(bs_tr_offset * 100))
|
|
1377
|
+
self.bs_triple_offset_label.setText(f"{bs_tr_offset:.2f}")
|
|
1378
|
+
|
|
1379
|
+
bs_db_rad = settings_dict.get('ball_stick_double_bond_radius_factor', self.default_settings.get('ball_stick_double_bond_radius_factor', 0.8))
|
|
1380
|
+
self.bs_double_radius_slider.setValue(int(bs_db_rad * 100))
|
|
1381
|
+
self.bs_double_radius_label.setText(f"{bs_db_rad:.2f}")
|
|
1382
|
+
|
|
1383
|
+
bs_tr_rad = settings_dict.get('ball_stick_triple_bond_radius_factor', self.default_settings.get('ball_stick_triple_bond_radius_factor', 0.75))
|
|
1384
|
+
self.bs_triple_radius_slider.setValue(int(bs_tr_rad * 100))
|
|
1385
|
+
self.bs_triple_radius_label.setText(f"{bs_tr_rad:.2f}")
|
|
1386
|
+
|
|
1387
|
+
bs_res = settings_dict.get('ball_stick_resolution', self.default_settings['ball_stick_resolution'])
|
|
1388
|
+
self.bs_resolution_slider.setValue(int(bs_res))
|
|
1389
|
+
self.bs_resolution_label.setText(str(bs_res))
|
|
1390
|
+
|
|
1391
|
+
# Ball & Stick bond color
|
|
1392
|
+
self.bs_bond_color = settings_dict.get('ball_stick_bond_color', self.default_settings.get('ball_stick_bond_color', '#7F7F7F'))
|
|
1393
|
+
try:
|
|
1394
|
+
self.bs_bond_color_button.setStyleSheet(f"background-color: {self.bs_bond_color}; border: 1px solid #888;")
|
|
1395
|
+
self.bs_bond_color_button.setToolTip(self.bs_bond_color)
|
|
1396
|
+
except Exception:
|
|
1397
|
+
pass
|
|
1398
|
+
|
|
1399
|
+
self.bs_use_cpk_bond_checkbox.setChecked(settings_dict.get('ball_stick_use_cpk_bond_color', self.default_settings.get('ball_stick_use_cpk_bond_color', False)))
|
|
1400
|
+
|
|
1401
|
+
# 3. CPK settings
|
|
1402
|
+
cpk_atom_scale = settings_dict.get('cpk_atom_scale', self.default_settings['cpk_atom_scale'])
|
|
1403
|
+
self.cpk_atom_scale_slider.setValue(int(cpk_atom_scale * 100))
|
|
1404
|
+
self.cpk_atom_scale_label.setText(f"{cpk_atom_scale:.2f}")
|
|
1405
|
+
|
|
1406
|
+
cpk_res = settings_dict.get('cpk_resolution', self.default_settings['cpk_resolution'])
|
|
1407
|
+
self.cpk_resolution_slider.setValue(int(cpk_res))
|
|
1408
|
+
self.cpk_resolution_label.setText(str(cpk_res))
|
|
1409
|
+
|
|
1410
|
+
# 4. Wireframe settings
|
|
1411
|
+
wf_bond_radius = settings_dict.get('wireframe_bond_radius', self.default_settings['wireframe_bond_radius'])
|
|
1412
|
+
self.wf_bond_radius_slider.setValue(int(wf_bond_radius * 100))
|
|
1413
|
+
self.wf_bond_radius_label.setText(f"{wf_bond_radius:.2f}")
|
|
1414
|
+
|
|
1415
|
+
wf_res = settings_dict.get('wireframe_resolution', self.default_settings['wireframe_resolution'])
|
|
1416
|
+
self.wf_resolution_slider.setValue(int(wf_res))
|
|
1417
|
+
self.wf_resolution_label.setText(str(wf_res))
|
|
1418
|
+
|
|
1419
|
+
# Multi-bond offsets (Wireframe)
|
|
1420
|
+
wf_db_offset = settings_dict.get('wireframe_double_bond_offset_factor', self.default_settings.get('wireframe_double_bond_offset_factor', 3.0))
|
|
1421
|
+
self.wf_double_offset_slider.setValue(int(wf_db_offset * 100))
|
|
1422
|
+
self.wf_double_offset_label.setText(f"{wf_db_offset:.2f}")
|
|
1423
|
+
|
|
1424
|
+
wf_tr_offset = settings_dict.get('wireframe_triple_bond_offset_factor', self.default_settings.get('wireframe_triple_bond_offset_factor', 3.0))
|
|
1425
|
+
self.wf_triple_offset_slider.setValue(int(wf_tr_offset * 100))
|
|
1426
|
+
self.wf_triple_offset_label.setText(f"{wf_tr_offset:.2f}")
|
|
1427
|
+
|
|
1428
|
+
wf_db_rad = settings_dict.get('wireframe_double_bond_radius_factor', self.default_settings.get('wireframe_double_bond_radius_factor', 0.8))
|
|
1429
|
+
self.wf_double_radius_slider.setValue(int(wf_db_rad * 100))
|
|
1430
|
+
self.wf_double_radius_label.setText(f"{wf_db_rad:.2f}")
|
|
1431
|
+
|
|
1432
|
+
wf_tr_rad = settings_dict.get('wireframe_triple_bond_radius_factor', self.default_settings.get('wireframe_triple_bond_radius_factor', 0.75))
|
|
1433
|
+
self.wf_triple_radius_slider.setValue(int(wf_tr_rad * 100))
|
|
1434
|
+
self.wf_triple_radius_label.setText(f"{wf_tr_rad:.2f}")
|
|
1435
|
+
|
|
1436
|
+
# 5. Stick settings
|
|
1437
|
+
stick_bond_radius = settings_dict.get('stick_bond_radius', self.default_settings['stick_bond_radius'])
|
|
1438
|
+
self.stick_bond_radius_slider.setValue(int(stick_bond_radius * 100))
|
|
1439
|
+
self.stick_bond_radius_label.setText(f"{stick_bond_radius:.2f}")
|
|
1440
|
+
|
|
1441
|
+
stick_res = settings_dict.get('stick_resolution', self.default_settings['stick_resolution'])
|
|
1442
|
+
self.stick_resolution_slider.setValue(int(stick_res))
|
|
1443
|
+
self.stick_resolution_label.setText(str(stick_res))
|
|
1444
|
+
|
|
1445
|
+
# Multi-bond offsets (Stick)
|
|
1446
|
+
stick_db_offset = settings_dict.get('stick_double_bond_offset_factor', self.default_settings.get('stick_double_bond_offset_factor', 1.5))
|
|
1447
|
+
self.stick_double_offset_slider.setValue(int(stick_db_offset * 100))
|
|
1448
|
+
self.stick_double_offset_label.setText(f"{stick_db_offset:.2f}")
|
|
1449
|
+
|
|
1450
|
+
stick_tr_offset = settings_dict.get('stick_triple_bond_offset_factor', self.default_settings.get('stick_triple_bond_offset_factor', 1.0))
|
|
1451
|
+
self.stick_triple_offset_slider.setValue(int(stick_tr_offset * 100))
|
|
1452
|
+
self.stick_triple_offset_label.setText(f"{stick_tr_offset:.2f}")
|
|
1453
|
+
|
|
1454
|
+
stick_db_rad = settings_dict.get('stick_double_bond_radius_factor', self.default_settings.get('stick_double_bond_radius_factor', 0.6))
|
|
1455
|
+
self.stick_double_radius_slider.setValue(int(stick_db_rad * 100))
|
|
1456
|
+
self.stick_double_radius_label.setText(f"{stick_db_rad:.2f}")
|
|
1457
|
+
|
|
1458
|
+
stick_tr_rad = settings_dict.get('stick_triple_bond_radius_factor', self.default_settings.get('stick_triple_bond_radius_factor', 0.4))
|
|
1459
|
+
self.stick_triple_radius_slider.setValue(int(stick_tr_rad * 100))
|
|
1460
|
+
self.stick_triple_radius_label.setText(f"{stick_tr_rad:.2f}")
|
|
1461
|
+
|
|
1462
|
+
# 6. Other settings
|
|
1463
|
+
self.skip_chem_checks_checkbox.setChecked(settings_dict.get('skip_chemistry_checks', self.default_settings.get('skip_chemistry_checks', False)))
|
|
1464
|
+
self.kekule_3d_checkbox.setChecked(settings_dict.get('display_kekule_3d', self.default_settings.get('display_kekule_3d', False)))
|
|
1465
|
+
# always ask for charge on XYZ imports
|
|
1466
|
+
self.always_ask_charge_checkbox.setChecked(settings_dict.get('always_ask_charge', self.default_settings.get('always_ask_charge', False)))
|
|
1467
|
+
# Aromatic ring circle display and torus thickness factor
|
|
1468
|
+
self.aromatic_circle_checkbox.setChecked(settings_dict.get('display_aromatic_circles_3d', self.default_settings.get('display_aromatic_circles_3d', False)))
|
|
1469
|
+
thickness_factor = float(settings_dict.get('aromatic_torus_thickness_factor', self.default_settings.get('aromatic_torus_thickness_factor', 0.6)))
|
|
1470
|
+
try:
|
|
1471
|
+
self.aromatic_torus_thickness_slider.setValue(int(thickness_factor * 100))
|
|
1472
|
+
self.aromatic_torus_thickness_label.setText(f"{thickness_factor:.1f}")
|
|
1473
|
+
except Exception:
|
|
1474
|
+
pass
|
|
1475
|
+
|
|
1476
|
+
# 7. 2D Settings
|
|
1477
|
+
bw_2d = settings_dict.get('bond_width_2d', self.default_settings['bond_width_2d'])
|
|
1478
|
+
self.bond_width_2d_slider.setValue(int(bw_2d * 10))
|
|
1479
|
+
self.bond_width_2d_label.setText(f"{bw_2d:.1f}")
|
|
1480
|
+
|
|
1481
|
+
bsd_2d = settings_dict.get('bond_spacing_double_2d', self.default_settings['bond_spacing_double_2d'])
|
|
1482
|
+
self.bond_spacing_double_2d_slider.setValue(int(bsd_2d * 10))
|
|
1483
|
+
self.bond_spacing_double_2d_label.setText(f"{bsd_2d:.1f}")
|
|
1484
|
+
|
|
1485
|
+
bst_2d = settings_dict.get('bond_spacing_triple_2d', self.default_settings['bond_spacing_triple_2d'])
|
|
1486
|
+
self.bond_spacing_triple_2d_slider.setValue(int(bst_2d * 10))
|
|
1487
|
+
self.bond_spacing_triple_2d_label.setText(f"{bst_2d:.1f}")
|
|
1488
|
+
|
|
1489
|
+
fs_2d = settings_dict.get('atom_font_size_2d', self.default_settings['atom_font_size_2d'])
|
|
1490
|
+
self.atom_font_size_2d_slider.setValue(int(fs_2d))
|
|
1491
|
+
self.atom_font_size_2d_label.setText(str(fs_2d))
|
|
1492
|
+
|
|
1493
|
+
self.atom_use_bond_color_2d_checkbox.setChecked(settings_dict.get('atom_use_bond_color_2d', self.default_settings.get('atom_use_bond_color_2d', False)))
|
|
1494
|
+
|
|
1495
|
+
self.current_bg_color_2d = settings_dict.get('background_color_2d', self.default_settings['background_color_2d'])
|
|
1496
|
+
|
|
1497
|
+
# Load Bond Cap Style
|
|
1498
|
+
cap_style = settings_dict.get('bond_cap_style_2d', self.default_settings.get('bond_cap_style_2d', 'Round'))
|
|
1499
|
+
index = self.bond_cap_style_2d_combo.findText(cap_style)
|
|
1500
|
+
if index >= 0:
|
|
1501
|
+
self.bond_cap_style_2d_combo.setCurrentIndex(index)
|
|
1502
|
+
self.current_bond_color_2d = settings_dict.get('bond_color_2d', self.default_settings['bond_color_2d'])
|
|
1503
|
+
self.update_2d_color_buttons()
|