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.
Files changed (59) hide show
  1. moleditpy_linux/__init__.py +17 -0
  2. moleditpy_linux/__main__.py +29 -0
  3. moleditpy_linux/main.py +37 -0
  4. moleditpy_linux/modules/__init__.py +41 -0
  5. moleditpy_linux/modules/about_dialog.py +104 -0
  6. moleditpy_linux/modules/align_plane_dialog.py +292 -0
  7. moleditpy_linux/modules/alignment_dialog.py +272 -0
  8. moleditpy_linux/modules/analysis_window.py +209 -0
  9. moleditpy_linux/modules/angle_dialog.py +440 -0
  10. moleditpy_linux/modules/assets/file_icon.ico +0 -0
  11. moleditpy_linux/modules/assets/icon.icns +0 -0
  12. moleditpy_linux/modules/assets/icon.ico +0 -0
  13. moleditpy_linux/modules/assets/icon.png +0 -0
  14. moleditpy_linux/modules/atom_item.py +395 -0
  15. moleditpy_linux/modules/bond_item.py +464 -0
  16. moleditpy_linux/modules/bond_length_dialog.py +380 -0
  17. moleditpy_linux/modules/calculation_worker.py +766 -0
  18. moleditpy_linux/modules/color_settings_dialog.py +321 -0
  19. moleditpy_linux/modules/constants.py +88 -0
  20. moleditpy_linux/modules/constrained_optimization_dialog.py +678 -0
  21. moleditpy_linux/modules/custom_interactor_style.py +749 -0
  22. moleditpy_linux/modules/custom_qt_interactor.py +102 -0
  23. moleditpy_linux/modules/dialog3_d_picking_mixin.py +141 -0
  24. moleditpy_linux/modules/dihedral_dialog.py +443 -0
  25. moleditpy_linux/modules/main_window.py +850 -0
  26. moleditpy_linux/modules/main_window_app_state.py +787 -0
  27. moleditpy_linux/modules/main_window_compute.py +1242 -0
  28. moleditpy_linux/modules/main_window_dialog_manager.py +460 -0
  29. moleditpy_linux/modules/main_window_edit_3d.py +536 -0
  30. moleditpy_linux/modules/main_window_edit_actions.py +1565 -0
  31. moleditpy_linux/modules/main_window_export.py +917 -0
  32. moleditpy_linux/modules/main_window_main_init.py +2100 -0
  33. moleditpy_linux/modules/main_window_molecular_parsers.py +1044 -0
  34. moleditpy_linux/modules/main_window_project_io.py +434 -0
  35. moleditpy_linux/modules/main_window_string_importers.py +275 -0
  36. moleditpy_linux/modules/main_window_ui_manager.py +602 -0
  37. moleditpy_linux/modules/main_window_view_3d.py +1539 -0
  38. moleditpy_linux/modules/main_window_view_loaders.py +355 -0
  39. moleditpy_linux/modules/mirror_dialog.py +122 -0
  40. moleditpy_linux/modules/molecular_data.py +302 -0
  41. moleditpy_linux/modules/molecule_scene.py +2000 -0
  42. moleditpy_linux/modules/move_group_dialog.py +600 -0
  43. moleditpy_linux/modules/periodic_table_dialog.py +84 -0
  44. moleditpy_linux/modules/planarize_dialog.py +220 -0
  45. moleditpy_linux/modules/plugin_interface.py +215 -0
  46. moleditpy_linux/modules/plugin_manager.py +473 -0
  47. moleditpy_linux/modules/plugin_manager_window.py +274 -0
  48. moleditpy_linux/modules/settings_dialog.py +1503 -0
  49. moleditpy_linux/modules/template_preview_item.py +157 -0
  50. moleditpy_linux/modules/template_preview_view.py +74 -0
  51. moleditpy_linux/modules/translation_dialog.py +364 -0
  52. moleditpy_linux/modules/user_template_dialog.py +692 -0
  53. moleditpy_linux/modules/zoomable_view.py +129 -0
  54. moleditpy_linux-2.4.1.dist-info/METADATA +954 -0
  55. moleditpy_linux-2.4.1.dist-info/RECORD +59 -0
  56. moleditpy_linux-2.4.1.dist-info/WHEEL +5 -0
  57. moleditpy_linux-2.4.1.dist-info/entry_points.txt +2 -0
  58. moleditpy_linux-2.4.1.dist-info/licenses/LICENSE +674 -0
  59. 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
+