moleditpy 2.8.0a4__tar.gz → 2.8.1__tar.gz
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-2.8.0a4 → moleditpy-2.8.1}/PKG-INFO +3 -3
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/README.md +2 -2
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/pyproject.toml +1 -1
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/MoleditPy.egg-info/PKG-INFO +3 -3
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/angle_dialog.py +21 -8
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/bond_length_dialog.py +3 -6
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/calculation_worker.py +55 -34
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/constants.py +1 -1
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/dihedral_dialog.py +3 -6
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/main_window_compute.py +9 -2
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/main_window_main_init.py +4 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/LICENSE +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/setup.cfg +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/MoleditPy.egg-info/SOURCES.txt +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/MoleditPy.egg-info/dependency_links.txt +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/MoleditPy.egg-info/entry_points.txt +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/MoleditPy.egg-info/requires.txt +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/MoleditPy.egg-info/top_level.txt +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/__init__.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/__main__.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/assets/file_icon.ico +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/assets/icon.icns +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/assets/icon.ico +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/assets/icon.png +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/main.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/__init__.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/about_dialog.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/align_plane_dialog.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/alignment_dialog.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/analysis_window.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/atom_item.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/bond_item.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/color_settings_dialog.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/constrained_optimization_dialog.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/custom_interactor_style.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/custom_qt_interactor.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/dialog3_d_picking_mixin.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/main_window.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/main_window_app_state.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/main_window_dialog_manager.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/main_window_edit_3d.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/main_window_edit_actions.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/main_window_export.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/main_window_molecular_parsers.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/main_window_project_io.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/main_window_string_importers.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/main_window_ui_manager.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/main_window_view_3d.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/main_window_view_loaders.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/mirror_dialog.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/mol_geometry.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/molecular_data.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/molecule_scene.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/move_group_dialog.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/periodic_table_dialog.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/planarize_dialog.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/plugin_interface.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/plugin_manager.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/plugin_manager_window.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/settings_dialog.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/sip_isdeleted_safe.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/template_preview_item.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/template_preview_view.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/translation_dialog.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/user_template_dialog.py +0 -0
- {moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/zoomable_view.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy
|
|
3
|
-
Version: 2.8.
|
|
3
|
+
Version: 2.8.1
|
|
4
4
|
Summary: A cross-platform, simple, and intuitive molecular structure editor built in Python. It allows 2D molecular drawing and 3D structure visualization. It supports exporting structure files for input to DFT calculation software.
|
|
5
5
|
Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -710,10 +710,10 @@ Dynamic: license-file
|
|
|
710
710
|
[](https://pypi.org/project/MoleditPy/)
|
|
711
711
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
712
712
|
[](https://github.com/HiroYokoyama/python_molecular_editor/actions)
|
|
713
|
-

|
|
714
714
|

|
|
715
715
|

|
|
716
|
-

|
|
717
717
|
[](https://pepy.tech/projects/moleditpy)
|
|
718
718
|
|
|
719
719
|
[🇯🇵 日本語 (Japanese)](#japanese)
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
[](https://pypi.org/project/MoleditPy/)
|
|
7
7
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
8
8
|
[](https://github.com/HiroYokoyama/python_molecular_editor/actions)
|
|
9
|
-

|
|
10
10
|

|
|
11
11
|

|
|
12
|
-

|
|
13
13
|
[](https://pepy.tech/projects/moleditpy)
|
|
14
14
|
|
|
15
15
|
[🇯🇵 日本語 (Japanese)](#japanese)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy
|
|
3
|
-
Version: 2.8.
|
|
3
|
+
Version: 2.8.1
|
|
4
4
|
Summary: A cross-platform, simple, and intuitive molecular structure editor built in Python. It allows 2D molecular drawing and 3D structure visualization. It supports exporting structure files for input to DFT calculation software.
|
|
5
5
|
Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -710,10 +710,10 @@ Dynamic: license-file
|
|
|
710
710
|
[](https://pypi.org/project/MoleditPy/)
|
|
711
711
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
712
712
|
[](https://github.com/HiroYokoyama/python_molecular_editor/actions)
|
|
713
|
-

|
|
714
714
|

|
|
715
715
|

|
|
716
|
-

|
|
717
717
|
[](https://pepy.tech/projects/moleditpy)
|
|
718
718
|
|
|
719
719
|
[🇯🇵 日本語 (Japanese)](#japanese)
|
|
@@ -152,11 +152,15 @@ class AngleDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
152
152
|
self.atom2_idx = atom_idx
|
|
153
153
|
elif self.atom3_idx is None:
|
|
154
154
|
self.atom3_idx = atom_idx
|
|
155
|
+
# Take a fresh snapshot immediately upon completing the triad selection
|
|
156
|
+
# This locks in the "original" initial geometry the user started modifying from
|
|
157
|
+
self._snapshot_positions = self.mol.GetConformer().GetPositions().copy()
|
|
155
158
|
else:
|
|
156
159
|
# Reset and start over
|
|
157
160
|
self.atom1_idx = atom_idx
|
|
158
161
|
self.atom2_idx = None
|
|
159
162
|
self.atom3_idx = None
|
|
163
|
+
self._snapshot_positions = None
|
|
160
164
|
|
|
161
165
|
# 原子ラベルを表示
|
|
162
166
|
self.show_atom_labels()
|
|
@@ -177,12 +181,6 @@ class AngleDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
177
181
|
self.disable_picking()
|
|
178
182
|
super().closeEvent(event)
|
|
179
183
|
|
|
180
|
-
def reject(self):
|
|
181
|
-
"""キャンセル時の処理"""
|
|
182
|
-
self.clear_atom_labels()
|
|
183
|
-
self.disable_picking()
|
|
184
|
-
super().reject()
|
|
185
|
-
|
|
186
184
|
def accept(self):
|
|
187
185
|
"""OK時の処理"""
|
|
188
186
|
self.clear_atom_labels()
|
|
@@ -194,6 +192,7 @@ class AngleDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
194
192
|
self.atom1_idx = None
|
|
195
193
|
self.atom2_idx = None # vertex atom
|
|
196
194
|
self.atom3_idx = None
|
|
195
|
+
self._snapshot_positions = None
|
|
197
196
|
self.clear_selection_labels()
|
|
198
197
|
self.update_display()
|
|
199
198
|
|
|
@@ -215,6 +214,7 @@ class AngleDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
215
214
|
self.selection_label.setText("No atoms selected")
|
|
216
215
|
self.angle_label.setText("")
|
|
217
216
|
self.apply_button.setEnabled(False)
|
|
217
|
+
self._snapshot_positions = None
|
|
218
218
|
# Clear angle input when no selection
|
|
219
219
|
try:
|
|
220
220
|
self.angle_input.blockSignals(True)
|
|
@@ -235,6 +235,7 @@ class AngleDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
235
235
|
)
|
|
236
236
|
self.angle_label.setText("")
|
|
237
237
|
self.apply_button.setEnabled(False)
|
|
238
|
+
self._snapshot_positions = None
|
|
238
239
|
# ラベル追加
|
|
239
240
|
self.add_selection_label(self.atom1_idx, "1")
|
|
240
241
|
# Clear angle input while selection is incomplete
|
|
@@ -258,6 +259,7 @@ class AngleDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
258
259
|
)
|
|
259
260
|
self.angle_label.setText("")
|
|
260
261
|
self.apply_button.setEnabled(False)
|
|
262
|
+
self._snapshot_positions = None
|
|
261
263
|
# ラベル追加
|
|
262
264
|
self.add_selection_label(self.atom1_idx, "1")
|
|
263
265
|
self.add_selection_label(self.atom2_idx, "2(vertex)")
|
|
@@ -340,7 +342,9 @@ class AngleDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
340
342
|
self._slider_dragging = True
|
|
341
343
|
self.main_window.push_undo_state()
|
|
342
344
|
# Snapshot positions so the rotation axis stays stable during drag
|
|
343
|
-
|
|
345
|
+
# Only take snapshot if one doesn't exist to preserve directional info
|
|
346
|
+
if getattr(self, '_snapshot_positions', None) is None:
|
|
347
|
+
self._snapshot_positions = self.mol.GetConformer().GetPositions().copy()
|
|
344
348
|
|
|
345
349
|
def on_slider_moved(self, value):
|
|
346
350
|
"""Update geometry in real-time while dragging."""
|
|
@@ -356,7 +360,8 @@ class AngleDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
356
360
|
def on_slider_released(self):
|
|
357
361
|
"""Finalize slider dragging."""
|
|
358
362
|
self._slider_dragging = False
|
|
359
|
-
|
|
363
|
+
# Do NOT clear snapshot here. Keep it to preserve the turning direction
|
|
364
|
+
# even if they approach ±180°. It gets cleared when selection is changed.
|
|
360
365
|
self.main_window.draw_molecule_3d(self.mol)
|
|
361
366
|
self.main_window.update_chiral_labels()
|
|
362
367
|
|
|
@@ -367,6 +372,11 @@ class AngleDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
367
372
|
if self.atom1_idx is None or self.atom2_idx is None or self.atom3_idx is None:
|
|
368
373
|
return
|
|
369
374
|
self.main_window.push_undo_state()
|
|
375
|
+
|
|
376
|
+
# Ensure we have a snapshot for click-to-position as well to maintain direction
|
|
377
|
+
if getattr(self, '_snapshot_positions', None) is None:
|
|
378
|
+
self._snapshot_positions = self.mol.GetConformer().GetPositions().copy()
|
|
379
|
+
|
|
370
380
|
self.angle_input.blockSignals(True)
|
|
371
381
|
self.angle_input.setText(f"{value}")
|
|
372
382
|
self.angle_input.blockSignals(False)
|
|
@@ -468,6 +478,9 @@ class AngleDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
468
478
|
self.main_window.draw_molecule_3d(self.mol)
|
|
469
479
|
|
|
470
480
|
def reject(self):
|
|
481
|
+
"""キャンセル時の処理"""
|
|
482
|
+
self.clear_atom_labels()
|
|
483
|
+
self.disable_picking()
|
|
471
484
|
super().reject()
|
|
472
485
|
try:
|
|
473
486
|
if self.main_window.current_mol:
|
|
@@ -168,12 +168,6 @@ class BondLengthDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
168
168
|
self.disable_picking()
|
|
169
169
|
super().closeEvent(event)
|
|
170
170
|
|
|
171
|
-
def reject(self):
|
|
172
|
-
"""キャンセル時の処理"""
|
|
173
|
-
self.clear_atom_labels()
|
|
174
|
-
self.disable_picking()
|
|
175
|
-
super().reject()
|
|
176
|
-
|
|
177
171
|
def accept(self):
|
|
178
172
|
"""OK時の処理"""
|
|
179
173
|
self.clear_atom_labels()
|
|
@@ -420,6 +414,9 @@ class BondLengthDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
420
414
|
self.main_window.draw_molecule_3d(self.mol)
|
|
421
415
|
|
|
422
416
|
def reject(self):
|
|
417
|
+
"""キャンセル時の処理"""
|
|
418
|
+
self.clear_atom_labels()
|
|
419
|
+
self.disable_picking()
|
|
423
420
|
super().reject()
|
|
424
421
|
try:
|
|
425
422
|
if self.main_window.current_mol:
|
|
@@ -204,15 +204,15 @@ def _iterative_optimize(mol, method, check_halted_cb, safe_status_cb, max_iters=
|
|
|
204
204
|
mmff_variant = "MMFF94" if method == "MMFF94" else "MMFF94s"
|
|
205
205
|
props = AllChem.MMFFGetMoleculeProperties(mol, mmffVariant=mmff_variant)
|
|
206
206
|
if props is None:
|
|
207
|
-
|
|
207
|
+
raise ValueError(f"Failed to generate MMFF properties for variant {mmff_variant}.")
|
|
208
208
|
ff = AllChem.MMFFGetMoleculeForceField(mol, props, confId=0)
|
|
209
209
|
elif method == "UFF":
|
|
210
210
|
ff = AllChem.UFFGetMoleculeForceField(mol, confId=0)
|
|
211
211
|
else:
|
|
212
|
-
|
|
212
|
+
raise ValueError(f"Unknown optimization method: {method}")
|
|
213
213
|
|
|
214
214
|
if ff is None:
|
|
215
|
-
|
|
215
|
+
raise ValueError(f"Failed to setup force field for method: {method}")
|
|
216
216
|
|
|
217
217
|
ff.Initialize()
|
|
218
218
|
iters_done = 0
|
|
@@ -238,8 +238,7 @@ def _iterative_optimize_obabel(mol, method, check_halted_cb, safe_status_cb, max
|
|
|
238
238
|
"""Perform force field optimization using OpenBabel in chunks to avoid UI freezing."""
|
|
239
239
|
try:
|
|
240
240
|
if not OBABEL_AVAILABLE or pybel is None:
|
|
241
|
-
|
|
242
|
-
return False
|
|
241
|
+
raise RuntimeError("OpenBabel is not available.")
|
|
243
242
|
|
|
244
243
|
ff_name = method.lower()
|
|
245
244
|
|
|
@@ -250,8 +249,7 @@ def _iterative_optimize_obabel(mol, method, check_halted_cb, safe_status_cb, max
|
|
|
250
249
|
# Set up forcefield
|
|
251
250
|
ff = pybel.ob.OBForceField.FindForceField(ff_name)
|
|
252
251
|
if not ff:
|
|
253
|
-
|
|
254
|
-
return False
|
|
252
|
+
raise ValueError(f"Forcefield '{ff_name}' not found in OpenBabel.")
|
|
255
253
|
|
|
256
254
|
if not ff.Setup(ob_mol.OBMol):
|
|
257
255
|
error_msg = f"Failed to load parameters for '{ff_name}'."
|
|
@@ -960,7 +958,11 @@ class CalculationWorker(QObject):
|
|
|
960
958
|
_safe_status(f"Applying force field optimization ({method_key} / OpenBabel)...")
|
|
961
959
|
opt_success = _iterative_optimize_obabel(mol, method_key, _check_halted, _safe_status)
|
|
962
960
|
if not opt_success:
|
|
963
|
-
|
|
961
|
+
_safe_status(f"Warning: Optimization with {opt_method} failed. Using unoptimized structure.")
|
|
962
|
+
try:
|
|
963
|
+
mol.ClearProp("_pme_optimization_method")
|
|
964
|
+
except Exception:
|
|
965
|
+
pass
|
|
964
966
|
else: # RDKit backend
|
|
965
967
|
try:
|
|
966
968
|
# Add property for UI feedback
|
|
@@ -970,7 +972,11 @@ class CalculationWorker(QObject):
|
|
|
970
972
|
_safe_status(f"Applying force field optimization ({method_key} / RDKit)...")
|
|
971
973
|
opt_success = _iterative_optimize(mol, method_key, _check_halted, _safe_status)
|
|
972
974
|
if not opt_success:
|
|
973
|
-
|
|
975
|
+
_safe_status(f"Warning: Optimization with {opt_method} failed. Using unoptimized structure.")
|
|
976
|
+
try:
|
|
977
|
+
mol.ClearProp("_pme_optimization_method")
|
|
978
|
+
except Exception:
|
|
979
|
+
pass
|
|
974
980
|
|
|
975
981
|
if _check_halted():
|
|
976
982
|
raise WorkerHaltError("Halted")
|
|
@@ -1163,14 +1169,14 @@ class CalculationWorker(QObject):
|
|
|
1163
1169
|
if backend == "OBABEL":
|
|
1164
1170
|
try:
|
|
1165
1171
|
mol.SetProp("_pme_optimization_method", opt_method_raw)
|
|
1166
|
-
except Exception:
|
|
1172
|
+
except Exception: # pragma: no cover
|
|
1167
1173
|
pass
|
|
1168
1174
|
_safe_status(f"Optimizing with OpenBabel ({method_key})...")
|
|
1169
1175
|
opt_success = _iterative_optimize_obabel(mol, method_key, _check_halted, _safe_status)
|
|
1170
1176
|
else:
|
|
1171
1177
|
try:
|
|
1172
1178
|
mol.SetProp("_pme_optimization_method", opt_method_raw)
|
|
1173
|
-
except Exception:
|
|
1179
|
+
except Exception: # pragma: no cover
|
|
1174
1180
|
pass
|
|
1175
1181
|
_safe_status(f"Optimizing with RDKit ({method_key})...")
|
|
1176
1182
|
opt_success = _iterative_optimize(mol, method_key, _check_halted, _safe_status)
|
|
@@ -1180,31 +1186,39 @@ class CalculationWorker(QObject):
|
|
|
1180
1186
|
except WorkerHaltError:
|
|
1181
1187
|
raise
|
|
1182
1188
|
except Exception as opt_err:
|
|
1183
|
-
|
|
1184
|
-
|
|
1189
|
+
if conversion_mode == "rdkit":
|
|
1190
|
+
raise Exception(f"Optimization with {opt_method_raw} failed: {opt_err}")
|
|
1191
|
+
_safe_status(f"Optimization failed: {opt_err}. Falling back...") # pragma: no cover
|
|
1192
|
+
try: # pragma: no cover
|
|
1193
|
+
mol.ClearProp("_pme_optimization_method")
|
|
1194
|
+
except Exception: # pragma: no cover
|
|
1195
|
+
pass
|
|
1196
|
+
# Allow fallback to proceed instead of crashing
|
|
1197
|
+
conf_id = -1 # pragma: no cover
|
|
1185
1198
|
|
|
1186
1199
|
# CRITICAL: Restore stereochemistry again after optimization (explicit labels priority)
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1200
|
+
if conf_id != -1:
|
|
1201
|
+
for bond_idx, stereo, stereo_atoms in original_stereo_info:
|
|
1202
|
+
bond = mol.GetBondWithIdx(bond_idx)
|
|
1203
|
+
if len(stereo_atoms) == 2:
|
|
1204
|
+
bond.SetStereoAtoms(stereo_atoms[0], stereo_atoms[1])
|
|
1205
|
+
bond.SetStereo(stereo)
|
|
1192
1206
|
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1207
|
+
# CRITICAL: Check for halt *before* emitting finished signal
|
|
1208
|
+
if _check_halted():
|
|
1209
|
+
raise WorkerHaltError("Halted")
|
|
1196
1210
|
|
|
1197
|
-
|
|
1198
|
-
|
|
1211
|
+
# Collision avoidance
|
|
1212
|
+
_adjust_collision_avoidance(mol, _check_halted, _safe_status)
|
|
1199
1213
|
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1214
|
+
try:
|
|
1215
|
+
_safe_finished((worker_id, mol))
|
|
1216
|
+
except WorkerHaltError:
|
|
1217
|
+
raise
|
|
1218
|
+
except Exception:
|
|
1219
|
+
_safe_finished(mol)
|
|
1220
|
+
_safe_status("RDKit 3D conversion succeeded.")
|
|
1221
|
+
return
|
|
1208
1222
|
|
|
1209
1223
|
# If RDKit did not produce a conf and OBabel is allowed, try Open Babel
|
|
1210
1224
|
if conf_id == -1 and conversion_mode in ("fallback", "obabel"):
|
|
@@ -1260,12 +1274,19 @@ class CalculationWorker(QObject):
|
|
|
1260
1274
|
opt_success = _iterative_optimize(rd_mol, method_key, _check_halted, _safe_status)
|
|
1261
1275
|
|
|
1262
1276
|
if not opt_success:
|
|
1263
|
-
|
|
1277
|
+
_safe_status(f"Warning: Optimization with {opt_method_raw} failed. Using unoptimized structure.")
|
|
1278
|
+
try:
|
|
1279
|
+
rd_mol.ClearProp("_pme_optimization_method")
|
|
1280
|
+
except Exception:
|
|
1281
|
+
pass
|
|
1264
1282
|
except WorkerHaltError:
|
|
1265
1283
|
raise
|
|
1266
1284
|
except Exception as opt_err:
|
|
1267
|
-
_safe_status(f"Optimization failed: {opt_err}")
|
|
1268
|
-
|
|
1285
|
+
_safe_status(f"Warning: Optimization failed: {opt_err}. Using unoptimized structure.")
|
|
1286
|
+
try:
|
|
1287
|
+
rd_mol.ClearProp("_pme_optimization_method")
|
|
1288
|
+
except Exception:
|
|
1289
|
+
pass
|
|
1269
1290
|
|
|
1270
1291
|
_safe_status(
|
|
1271
1292
|
"Open Babel embedding succeeded. Warning: Conformation accuracy may be limited."
|
|
@@ -182,12 +182,6 @@ class DihedralDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
182
182
|
self.disable_picking()
|
|
183
183
|
super().closeEvent(event)
|
|
184
184
|
|
|
185
|
-
def reject(self):
|
|
186
|
-
"""キャンセル時の処理"""
|
|
187
|
-
self.clear_atom_labels()
|
|
188
|
-
self.disable_picking()
|
|
189
|
-
super().reject()
|
|
190
|
-
|
|
191
185
|
def accept(self):
|
|
192
186
|
"""OK時の処理"""
|
|
193
187
|
self.clear_atom_labels()
|
|
@@ -511,6 +505,9 @@ class DihedralDialog(Dialog3DPickingMixin, QDialog): # pragma: no cover
|
|
|
511
505
|
self.main_window.draw_molecule_3d(self.mol)
|
|
512
506
|
|
|
513
507
|
def reject(self):
|
|
508
|
+
"""キャンセル時の処理"""
|
|
509
|
+
self.clear_atom_labels()
|
|
510
|
+
self.disable_picking()
|
|
514
511
|
super().reject()
|
|
515
512
|
try:
|
|
516
513
|
if self.main_window.current_mol:
|
|
@@ -51,6 +51,9 @@ except Exception:
|
|
|
51
51
|
# --- クラス定義 ---
|
|
52
52
|
class MainWindowCompute(object):
|
|
53
53
|
"""main_window.py から分離された機能クラス"""
|
|
54
|
+
|
|
55
|
+
# Default initial state
|
|
56
|
+
last_successful_optimization_method = None
|
|
54
57
|
|
|
55
58
|
def set_optimization_method(self, method_name): # pragma: no cover
|
|
56
59
|
"""Set preferred 3D optimization method and persist to settings.
|
|
@@ -67,6 +70,7 @@ class MainWindowCompute(object):
|
|
|
67
70
|
"UFF_RDKIT",
|
|
68
71
|
"UFF_OBABEL",
|
|
69
72
|
"GAFF_OBABEL",
|
|
73
|
+
"MMFF94s_OBABEL",
|
|
70
74
|
"MMFF94_OBABEL",
|
|
71
75
|
"GHEMICAL_OBABEL",
|
|
72
76
|
)
|
|
@@ -166,6 +170,7 @@ class MainWindowCompute(object):
|
|
|
166
170
|
("MMFF94s (RDKit)", "MMFF_RDKIT"),
|
|
167
171
|
("MMFF94 (RDKit)", "MMFF94_RDKIT"),
|
|
168
172
|
("UFF (RDKit)", "UFF_RDKIT"),
|
|
173
|
+
("MMFF94s (Open Babel)", "MMFF94s_OBABEL"),
|
|
169
174
|
("MMFF94 (Open Babel)", "MMFF94_OBABEL"),
|
|
170
175
|
("UFF (Open Babel)", "UFF_OBABEL"),
|
|
171
176
|
("GAFF (Open Babel)", "GAFF_OBABEL"),
|
|
@@ -1022,8 +1027,8 @@ class MainWindowCompute(object):
|
|
|
1022
1027
|
except Exception: # pragma: no cover
|
|
1023
1028
|
import traceback
|
|
1024
1029
|
traceback.print_exc()
|
|
1025
|
-
|
|
1026
|
-
|
|
1030
|
+
# If the property is not on the molecule, it means optimization failed or was bypassed.
|
|
1031
|
+
# We explicitly do not fall back to self.optimization_method.
|
|
1027
1032
|
# Store the optimization method using its user-friendly UI label if available.
|
|
1028
1033
|
if opt_method:
|
|
1029
1034
|
try:
|
|
@@ -1033,6 +1038,8 @@ class MainWindowCompute(object):
|
|
|
1033
1038
|
self.last_successful_optimization_method = label
|
|
1034
1039
|
except Exception:
|
|
1035
1040
|
self.last_successful_optimization_method = opt_method
|
|
1041
|
+
else:
|
|
1042
|
+
self.last_successful_optimization_method = None
|
|
1036
1043
|
except Exception:
|
|
1037
1044
|
# non-fatal
|
|
1038
1045
|
pass
|
|
@@ -1458,6 +1458,7 @@ class MainWindowMainInit(object):
|
|
|
1458
1458
|
("MMFF94s (RDKit)", "MMFF_RDKIT"),
|
|
1459
1459
|
("MMFF94 (RDKit)", "MMFF94_RDKIT"),
|
|
1460
1460
|
("UFF (RDKit)", "UFF_RDKIT"),
|
|
1461
|
+
("MMFF94s (Open Babel)", "MMFF94s_OBABEL"),
|
|
1461
1462
|
("MMFF94 (Open Babel)", "MMFF94_OBABEL"),
|
|
1462
1463
|
("UFF (Open Babel)", "UFF_OBABEL"),
|
|
1463
1464
|
("GAFF (Open Babel)", "GAFF_OBABEL"),
|
|
@@ -1484,6 +1485,9 @@ class MainWindowMainInit(object):
|
|
|
1484
1485
|
import traceback
|
|
1485
1486
|
traceback.print_exc()
|
|
1486
1487
|
|
|
1488
|
+
# If Open Babel is not available, disable Open Babel-based optimization methods
|
|
1489
|
+
if key.endswith("_OBABEL") and not OBABEL_AVAILABLE:
|
|
1490
|
+
action.setEnabled(False)
|
|
1487
1491
|
action.triggered.connect(
|
|
1488
1492
|
lambda checked, m=key: self.set_optimization_method(m)
|
|
1489
1493
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/constrained_optimization_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy-2.8.0a4 → moleditpy-2.8.1}/src/moleditpy/modules/main_window_molecular_parsers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|