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,460 @@
|
|
|
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
|
+
|
|
14
|
+
"""
|
|
15
|
+
main_window_dialog_manager.py
|
|
16
|
+
MainWindow (main_window.py) から分離されたモジュール
|
|
17
|
+
機能クラス: MainWindowDialogManager
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
import json
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# RDKit imports (explicit to satisfy flake8 and used features)
|
|
26
|
+
try:
|
|
27
|
+
pass
|
|
28
|
+
except Exception:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
# PyQt6 Modules
|
|
32
|
+
from PyQt6.QtWidgets import (
|
|
33
|
+
QMessageBox,
|
|
34
|
+
QInputDialog
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
from PyQt6.QtCore import (
|
|
40
|
+
QDateTime
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Use centralized Open Babel availability from package-level __init__
|
|
45
|
+
# Use per-package modules availability (local __init__).
|
|
46
|
+
try:
|
|
47
|
+
from . import OBABEL_AVAILABLE
|
|
48
|
+
except Exception:
|
|
49
|
+
from modules import OBABEL_AVAILABLE
|
|
50
|
+
# Only import pybel on demand — `moleditpy` itself doesn't expose `pybel`.
|
|
51
|
+
if OBABEL_AVAILABLE:
|
|
52
|
+
try:
|
|
53
|
+
from openbabel import pybel
|
|
54
|
+
except Exception:
|
|
55
|
+
# If import fails here, disable OBABEL locally; avoid raising
|
|
56
|
+
pybel = None
|
|
57
|
+
OBABEL_AVAILABLE = False
|
|
58
|
+
print("Warning: openbabel.pybel not available. Open Babel fallback and OBabel-based options will be disabled.")
|
|
59
|
+
else:
|
|
60
|
+
pybel = None
|
|
61
|
+
|
|
62
|
+
# Optional SIP helper: on some PyQt6 builds sip.isdeleted is available and
|
|
63
|
+
# allows safely detecting C++ wrapper objects that have been deleted. Import
|
|
64
|
+
# it once at module import time and expose a small, robust wrapper so callers
|
|
65
|
+
# can avoid re-importing sip repeatedly and so we centralize exception
|
|
66
|
+
# handling (this reduces crash risk during teardown and deletion operations).
|
|
67
|
+
try:
|
|
68
|
+
import sip as _sip # type: ignore
|
|
69
|
+
_sip_isdeleted = getattr(_sip, 'isdeleted', None)
|
|
70
|
+
except Exception:
|
|
71
|
+
_sip = None
|
|
72
|
+
_sip_isdeleted = None
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
# package relative imports (preferred when running as `python -m moleditpy`)
|
|
76
|
+
from .constants import VERSION
|
|
77
|
+
from .user_template_dialog import UserTemplateDialog
|
|
78
|
+
from .about_dialog import AboutDialog
|
|
79
|
+
from .translation_dialog import TranslationDialog
|
|
80
|
+
from .mirror_dialog import MirrorDialog
|
|
81
|
+
from .move_group_dialog import MoveGroupDialog
|
|
82
|
+
from .align_plane_dialog import AlignPlaneDialog
|
|
83
|
+
from .planarize_dialog import PlanarizeDialog
|
|
84
|
+
from .alignment_dialog import AlignmentDialog
|
|
85
|
+
from .periodic_table_dialog import PeriodicTableDialog
|
|
86
|
+
from .analysis_window import AnalysisWindow
|
|
87
|
+
from .bond_length_dialog import BondLengthDialog
|
|
88
|
+
from .angle_dialog import AngleDialog
|
|
89
|
+
from .dihedral_dialog import DihedralDialog
|
|
90
|
+
from .constrained_optimization_dialog import ConstrainedOptimizationDialog
|
|
91
|
+
except Exception:
|
|
92
|
+
# Fallback to absolute imports for script-style execution
|
|
93
|
+
from modules.constants import VERSION
|
|
94
|
+
from modules.user_template_dialog import UserTemplateDialog
|
|
95
|
+
from modules.about_dialog import AboutDialog
|
|
96
|
+
from modules.translation_dialog import TranslationDialog
|
|
97
|
+
from modules.mirror_dialog import MirrorDialog
|
|
98
|
+
from modules.move_group_dialog import MoveGroupDialog
|
|
99
|
+
from modules.align_plane_dialog import AlignPlaneDialog
|
|
100
|
+
from modules.planarize_dialog import PlanarizeDialog
|
|
101
|
+
from modules.alignment_dialog import AlignmentDialog
|
|
102
|
+
from modules.periodic_table_dialog import PeriodicTableDialog
|
|
103
|
+
from modules.analysis_window import AnalysisWindow
|
|
104
|
+
from modules.bond_length_dialog import BondLengthDialog
|
|
105
|
+
from modules.angle_dialog import AngleDialog
|
|
106
|
+
from modules.dihedral_dialog import DihedralDialog
|
|
107
|
+
from modules.constrained_optimization_dialog import ConstrainedOptimizationDialog
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# --- クラス定義 ---
|
|
111
|
+
class MainWindowDialogManager(object):
|
|
112
|
+
""" main_window.py から分離された機能クラス """
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def show_about_dialog(self):
|
|
116
|
+
"""Show the custom About dialog with Easter egg functionality"""
|
|
117
|
+
dialog = AboutDialog(self, self)
|
|
118
|
+
dialog.exec()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def open_periodic_table_dialog(self):
|
|
123
|
+
dialog=PeriodicTableDialog(self); dialog.element_selected.connect(self.set_atom_from_periodic_table)
|
|
124
|
+
checked_action=self.tool_group.checkedAction()
|
|
125
|
+
if checked_action: self.tool_group.setExclusive(False); checked_action.setChecked(False); self.tool_group.setExclusive(True)
|
|
126
|
+
dialog.exec()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def open_analysis_window(self):
|
|
131
|
+
if self.current_mol:
|
|
132
|
+
dialog = AnalysisWindow(self.current_mol, self, is_xyz_derived=self.is_xyz_derived)
|
|
133
|
+
dialog.exec()
|
|
134
|
+
else:
|
|
135
|
+
self.statusBar().showMessage("Please generate a 3D structure first to show analysis.")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def open_template_dialog(self):
|
|
140
|
+
"""テンプレートダイアログを開く"""
|
|
141
|
+
dialog = UserTemplateDialog(self, self)
|
|
142
|
+
dialog.exec()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def open_template_dialog_and_activate(self):
|
|
147
|
+
"""テンプレートダイアログを開き、テンプレートがメイン画面で使用できるようにする"""
|
|
148
|
+
# 既存のダイアログがあるかチェック
|
|
149
|
+
if hasattr(self, '_template_dialog') and self._template_dialog and not self._template_dialog.isHidden():
|
|
150
|
+
# 既存のダイアログを前面に表示
|
|
151
|
+
self._template_dialog.raise_()
|
|
152
|
+
self._template_dialog.activateWindow()
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
# 新しいダイアログを作成
|
|
156
|
+
self._template_dialog = UserTemplateDialog(self, self)
|
|
157
|
+
self._template_dialog.show() # モードレスで表示
|
|
158
|
+
|
|
159
|
+
# ダイアログが閉じられた後、テンプレートが選択されていればアクティブ化
|
|
160
|
+
def on_dialog_finished():
|
|
161
|
+
if hasattr(self._template_dialog, 'selected_template') and self._template_dialog.selected_template:
|
|
162
|
+
template_name = self._template_dialog.selected_template.get('name', 'user_template')
|
|
163
|
+
mode_name = f"template_user_{template_name}"
|
|
164
|
+
|
|
165
|
+
# Store template data for the scene to use
|
|
166
|
+
self.scene.user_template_data = self._template_dialog.selected_template
|
|
167
|
+
self.set_mode(mode_name)
|
|
168
|
+
|
|
169
|
+
# Update status
|
|
170
|
+
self.statusBar().showMessage(f"Template mode: {template_name}")
|
|
171
|
+
|
|
172
|
+
self._template_dialog.finished.connect(on_dialog_finished)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def save_2d_as_template(self):
|
|
177
|
+
"""現在の2D構造をテンプレートとして保存"""
|
|
178
|
+
if not self.data.atoms:
|
|
179
|
+
QMessageBox.warning(self, "Warning", "No structure to save as template.")
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
# Get template name
|
|
183
|
+
name, ok = QInputDialog.getText(self, "Save Template", "Enter template name:")
|
|
184
|
+
if not ok or not name.strip():
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
name = name.strip()
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
# Template directory
|
|
191
|
+
template_dir = os.path.join(self.settings_dir, 'user-templates')
|
|
192
|
+
if not os.path.exists(template_dir):
|
|
193
|
+
os.makedirs(template_dir)
|
|
194
|
+
|
|
195
|
+
# Convert current structure to template format
|
|
196
|
+
atoms_data = []
|
|
197
|
+
bonds_data = []
|
|
198
|
+
|
|
199
|
+
# Convert atoms
|
|
200
|
+
for atom_id, atom_info in self.data.atoms.items():
|
|
201
|
+
pos = atom_info['pos']
|
|
202
|
+
atoms_data.append({
|
|
203
|
+
'id': atom_id,
|
|
204
|
+
'symbol': atom_info['symbol'],
|
|
205
|
+
'x': pos.x(),
|
|
206
|
+
'y': pos.y(),
|
|
207
|
+
'charge': atom_info.get('charge', 0),
|
|
208
|
+
'radical': atom_info.get('radical', 0)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
# Convert bonds
|
|
212
|
+
for (atom1_id, atom2_id), bond_info in self.data.bonds.items():
|
|
213
|
+
bonds_data.append({
|
|
214
|
+
'atom1': atom1_id,
|
|
215
|
+
'atom2': atom2_id,
|
|
216
|
+
'order': bond_info['order'],
|
|
217
|
+
'stereo': bond_info.get('stereo', 0)
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
# Create template data
|
|
221
|
+
template_data = {
|
|
222
|
+
'format': "PME Template",
|
|
223
|
+
'version': "1.0",
|
|
224
|
+
'application': "MoleditPy",
|
|
225
|
+
'application_version': VERSION,
|
|
226
|
+
'name': name,
|
|
227
|
+
'created': str(QDateTime.currentDateTime().toString()),
|
|
228
|
+
'atoms': atoms_data,
|
|
229
|
+
'bonds': bonds_data
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
# Save to file
|
|
233
|
+
filename = f"{name.replace(' ', '_')}.pmetmplt"
|
|
234
|
+
filepath = os.path.join(template_dir, filename)
|
|
235
|
+
|
|
236
|
+
if os.path.exists(filepath):
|
|
237
|
+
reply = QMessageBox.question(
|
|
238
|
+
self, "Overwrite Template",
|
|
239
|
+
f"Template '{name}' already exists. Overwrite?",
|
|
240
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
241
|
+
)
|
|
242
|
+
if reply != QMessageBox.StandardButton.Yes:
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
246
|
+
json.dump(template_data, f, indent=2, ensure_ascii=False)
|
|
247
|
+
|
|
248
|
+
# Mark as saved (no unsaved changes for this operation)
|
|
249
|
+
self.has_unsaved_changes = False
|
|
250
|
+
self.update_window_title()
|
|
251
|
+
|
|
252
|
+
QMessageBox.information(self, "Success", f"Template '{name}' saved successfully.")
|
|
253
|
+
|
|
254
|
+
except Exception as e:
|
|
255
|
+
QMessageBox.critical(self, "Error", f"Failed to save template: {str(e)}")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def open_translation_dialog(self):
|
|
260
|
+
"""平行移動ダイアログを開く"""
|
|
261
|
+
# 測定モードを無効化
|
|
262
|
+
if self.measurement_mode:
|
|
263
|
+
self.measurement_action.setChecked(False)
|
|
264
|
+
self.toggle_measurement_mode(False)
|
|
265
|
+
|
|
266
|
+
dialog = TranslationDialog(self.current_mol, self, parent=self)
|
|
267
|
+
self.active_3d_dialogs.append(dialog) # 参照を保持
|
|
268
|
+
dialog.show() # execではなくshowを使用してモードレス表示
|
|
269
|
+
dialog.accepted.connect(lambda: self.statusBar().showMessage("Translation applied."))
|
|
270
|
+
dialog.accepted.connect(self.push_undo_state)
|
|
271
|
+
dialog.finished.connect(lambda: self.remove_dialog_from_list(dialog)) # ダイアログが閉じられた時にリストから削除
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def open_move_group_dialog(self):
|
|
276
|
+
"""Move Groupダイアログを開く"""
|
|
277
|
+
# 測定モードを無効化
|
|
278
|
+
if self.measurement_mode:
|
|
279
|
+
self.measurement_action.setChecked(False)
|
|
280
|
+
self.toggle_measurement_mode(False)
|
|
281
|
+
|
|
282
|
+
dialog = MoveGroupDialog(self.current_mol, self, parent=self)
|
|
283
|
+
self.active_3d_dialogs.append(dialog)
|
|
284
|
+
dialog.show()
|
|
285
|
+
dialog.accepted.connect(lambda: self.statusBar().showMessage("Group transformation applied."))
|
|
286
|
+
dialog.accepted.connect(self.push_undo_state)
|
|
287
|
+
dialog.finished.connect(lambda: self.remove_dialog_from_list(dialog))
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def open_align_plane_dialog(self, plane):
|
|
292
|
+
"""alignダイアログを開く"""
|
|
293
|
+
# 事前選択された原子を取得(測定モード無効化前に)
|
|
294
|
+
preselected_atoms = []
|
|
295
|
+
if hasattr(self, 'selected_atoms_3d') and self.selected_atoms_3d:
|
|
296
|
+
preselected_atoms = list(self.selected_atoms_3d)
|
|
297
|
+
elif hasattr(self, 'selected_atoms_for_measurement') and self.selected_atoms_for_measurement:
|
|
298
|
+
preselected_atoms = list(self.selected_atoms_for_measurement)
|
|
299
|
+
|
|
300
|
+
# 測定モードを無効化
|
|
301
|
+
if self.measurement_mode:
|
|
302
|
+
self.measurement_action.setChecked(False)
|
|
303
|
+
self.toggle_measurement_mode(False)
|
|
304
|
+
|
|
305
|
+
dialog = AlignPlaneDialog(self.current_mol, self, plane, preselected_atoms, parent=self)
|
|
306
|
+
self.active_3d_dialogs.append(dialog) # 参照を保持
|
|
307
|
+
dialog.show() # execではなくshowを使用してモードレス表示
|
|
308
|
+
dialog.accepted.connect(lambda: self.statusBar().showMessage(f"Atoms alignd to {plane.upper()} plane."))
|
|
309
|
+
dialog.accepted.connect(self.push_undo_state)
|
|
310
|
+
dialog.finished.connect(lambda: self.remove_dialog_from_list(dialog)) # ダイアログが閉じられた時にリストから削除
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def open_planarize_dialog(self, plane=None):
|
|
315
|
+
"""選択原子群を最適平面へ投影するダイアログを開く"""
|
|
316
|
+
# 事前選択された原子を取得(測定モード無効化前に)
|
|
317
|
+
preselected_atoms = []
|
|
318
|
+
if hasattr(self, 'selected_atoms_3d') and self.selected_atoms_3d:
|
|
319
|
+
preselected_atoms = list(self.selected_atoms_3d)
|
|
320
|
+
elif hasattr(self, 'selected_atoms_for_measurement') and self.selected_atoms_for_measurement:
|
|
321
|
+
preselected_atoms = list(self.selected_atoms_for_measurement)
|
|
322
|
+
|
|
323
|
+
# 測定モードを無効化
|
|
324
|
+
if self.measurement_mode:
|
|
325
|
+
self.measurement_action.setChecked(False)
|
|
326
|
+
self.toggle_measurement_mode(False)
|
|
327
|
+
|
|
328
|
+
dialog = PlanarizeDialog(self.current_mol, self, preselected_atoms, parent=self)
|
|
329
|
+
self.active_3d_dialogs.append(dialog)
|
|
330
|
+
dialog.show()
|
|
331
|
+
dialog.accepted.connect(lambda: self.statusBar().showMessage("Selection planarized to best-fit plane."))
|
|
332
|
+
dialog.accepted.connect(self.push_undo_state)
|
|
333
|
+
dialog.finished.connect(lambda: self.remove_dialog_from_list(dialog))
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def open_alignment_dialog(self, axis):
|
|
338
|
+
"""アライメントダイアログを開く"""
|
|
339
|
+
# 事前選択された原子を取得(測定モード無効化前に)
|
|
340
|
+
preselected_atoms = []
|
|
341
|
+
if hasattr(self, 'selected_atoms_3d') and self.selected_atoms_3d:
|
|
342
|
+
preselected_atoms = list(self.selected_atoms_3d)
|
|
343
|
+
elif hasattr(self, 'selected_atoms_for_measurement') and self.selected_atoms_for_measurement:
|
|
344
|
+
preselected_atoms = list(self.selected_atoms_for_measurement)
|
|
345
|
+
|
|
346
|
+
# 測定モードを無効化
|
|
347
|
+
if self.measurement_mode:
|
|
348
|
+
self.measurement_action.setChecked(False)
|
|
349
|
+
self.toggle_measurement_mode(False)
|
|
350
|
+
|
|
351
|
+
dialog = AlignmentDialog(self.current_mol, self, axis, preselected_atoms, parent=self)
|
|
352
|
+
self.active_3d_dialogs.append(dialog) # 参照を保持
|
|
353
|
+
dialog.show() # execではなくshowを使用してモードレス表示
|
|
354
|
+
dialog.accepted.connect(lambda: self.statusBar().showMessage(f"Atoms aligned to {axis.upper()}-axis."))
|
|
355
|
+
dialog.accepted.connect(self.push_undo_state)
|
|
356
|
+
dialog.finished.connect(lambda: self.remove_dialog_from_list(dialog)) # ダイアログが閉じられた時にリストから削除
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def open_bond_length_dialog(self):
|
|
361
|
+
"""結合長変換ダイアログを開く"""
|
|
362
|
+
# 事前選択された原子を取得(測定モード無効化前に)
|
|
363
|
+
preselected_atoms = []
|
|
364
|
+
if hasattr(self, 'selected_atoms_3d') and self.selected_atoms_3d:
|
|
365
|
+
preselected_atoms = list(self.selected_atoms_3d)
|
|
366
|
+
elif hasattr(self, 'selected_atoms_for_measurement') and self.selected_atoms_for_measurement:
|
|
367
|
+
preselected_atoms = list(self.selected_atoms_for_measurement)
|
|
368
|
+
|
|
369
|
+
# 測定モードを無効化
|
|
370
|
+
if self.measurement_mode:
|
|
371
|
+
self.measurement_action.setChecked(False)
|
|
372
|
+
self.toggle_measurement_mode(False)
|
|
373
|
+
|
|
374
|
+
dialog = BondLengthDialog(self.current_mol, self, preselected_atoms, parent=self)
|
|
375
|
+
self.active_3d_dialogs.append(dialog) # 参照を保持
|
|
376
|
+
dialog.show() # execではなくshowを使用してモードレス表示
|
|
377
|
+
dialog.accepted.connect(lambda: self.statusBar().showMessage("Bond length adjusted."))
|
|
378
|
+
dialog.accepted.connect(self.push_undo_state)
|
|
379
|
+
dialog.finished.connect(lambda: self.remove_dialog_from_list(dialog)) # ダイアログが閉じられた時にリストから削除
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def open_angle_dialog(self):
|
|
384
|
+
"""角度変換ダイアログを開く"""
|
|
385
|
+
# 事前選択された原子を取得(測定モード無効化前に)
|
|
386
|
+
preselected_atoms = []
|
|
387
|
+
if hasattr(self, 'selected_atoms_3d') and self.selected_atoms_3d:
|
|
388
|
+
preselected_atoms = list(self.selected_atoms_3d)
|
|
389
|
+
elif hasattr(self, 'selected_atoms_for_measurement') and self.selected_atoms_for_measurement:
|
|
390
|
+
preselected_atoms = list(self.selected_atoms_for_measurement)
|
|
391
|
+
|
|
392
|
+
# 測定モードを無効化
|
|
393
|
+
if self.measurement_mode:
|
|
394
|
+
self.measurement_action.setChecked(False)
|
|
395
|
+
self.toggle_measurement_mode(False)
|
|
396
|
+
|
|
397
|
+
dialog = AngleDialog(self.current_mol, self, preselected_atoms, parent=self)
|
|
398
|
+
self.active_3d_dialogs.append(dialog) # 参照を保持
|
|
399
|
+
dialog.show() # execではなくshowを使用してモードレス表示
|
|
400
|
+
dialog.accepted.connect(lambda: self.statusBar().showMessage("Angle adjusted."))
|
|
401
|
+
dialog.accepted.connect(self.push_undo_state)
|
|
402
|
+
dialog.finished.connect(lambda: self.remove_dialog_from_list(dialog)) # ダイアログが閉じられた時にリストから削除
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def open_dihedral_dialog(self):
|
|
407
|
+
"""二面角変換ダイアログを開く"""
|
|
408
|
+
# 事前選択された原子を取得(測定モード無効化前に)
|
|
409
|
+
preselected_atoms = []
|
|
410
|
+
if hasattr(self, 'selected_atoms_3d') and self.selected_atoms_3d:
|
|
411
|
+
preselected_atoms = list(self.selected_atoms_3d)
|
|
412
|
+
elif hasattr(self, 'selected_atoms_for_measurement') and self.selected_atoms_for_measurement:
|
|
413
|
+
preselected_atoms = list(self.selected_atoms_for_measurement)
|
|
414
|
+
|
|
415
|
+
# 測定モードを無効化
|
|
416
|
+
if self.measurement_mode:
|
|
417
|
+
self.measurement_action.setChecked(False)
|
|
418
|
+
self.toggle_measurement_mode(False)
|
|
419
|
+
|
|
420
|
+
dialog = DihedralDialog(self.current_mol, self, preselected_atoms, parent=self)
|
|
421
|
+
self.active_3d_dialogs.append(dialog) # 参照を保持
|
|
422
|
+
dialog.show() # execではなくshowを使用してモードレス表示
|
|
423
|
+
dialog.accepted.connect(lambda: self.statusBar().showMessage("Dihedral angle adjusted."))
|
|
424
|
+
dialog.accepted.connect(self.push_undo_state)
|
|
425
|
+
dialog.finished.connect(lambda: self.remove_dialog_from_list(dialog)) # ダイアログが閉じられた時にリストから削除
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def open_mirror_dialog(self):
|
|
430
|
+
"""ミラー機能ダイアログを開く"""
|
|
431
|
+
if not self.current_mol:
|
|
432
|
+
self.statusBar().showMessage("No 3D molecule loaded.")
|
|
433
|
+
return
|
|
434
|
+
|
|
435
|
+
# 測定モードを無効化
|
|
436
|
+
if self.measurement_mode:
|
|
437
|
+
self.measurement_action.setChecked(False)
|
|
438
|
+
self.toggle_measurement_mode(False)
|
|
439
|
+
|
|
440
|
+
dialog = MirrorDialog(self.current_mol, self)
|
|
441
|
+
dialog.exec() # モーダルダイアログとして表示
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def open_constrained_optimization_dialog(self):
|
|
446
|
+
"""制約付き最適化ダイアログを開く"""
|
|
447
|
+
if not self.current_mol:
|
|
448
|
+
self.statusBar().showMessage("No 3D molecule loaded.")
|
|
449
|
+
return
|
|
450
|
+
|
|
451
|
+
# 測定モードを無効化
|
|
452
|
+
if self.measurement_mode:
|
|
453
|
+
self.measurement_action.setChecked(False)
|
|
454
|
+
self.toggle_measurement_mode(False)
|
|
455
|
+
|
|
456
|
+
dialog = ConstrainedOptimizationDialog(self.current_mol, self, parent=self)
|
|
457
|
+
self.active_3d_dialogs.append(dialog) # 参照を保持
|
|
458
|
+
dialog.show() # モードレス表示
|
|
459
|
+
dialog.finished.connect(lambda: self.remove_dialog_from_list(dialog))
|
|
460
|
+
|