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,275 @@
|
|
|
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
|
+
main_window_string_importers.py
|
|
15
|
+
MainWindow (main_window.py) から分離されたモジュール
|
|
16
|
+
機能クラス: MainWindowStringImporters
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
import traceback
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# RDKit imports (explicit to satisfy flake8 and used features)
|
|
24
|
+
from rdkit import Chem
|
|
25
|
+
from rdkit.Chem import AllChem
|
|
26
|
+
try:
|
|
27
|
+
pass
|
|
28
|
+
except Exception:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
# PyQt6 Modules
|
|
32
|
+
from PyQt6.QtWidgets import (
|
|
33
|
+
QInputDialog
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
from PyQt6.QtCore import (
|
|
39
|
+
QPointF, QTimer
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Use centralized Open Babel availability from package-level __init__
|
|
44
|
+
# Use per-package modules availability (local __init__).
|
|
45
|
+
try:
|
|
46
|
+
from . import OBABEL_AVAILABLE
|
|
47
|
+
except Exception:
|
|
48
|
+
from modules import OBABEL_AVAILABLE
|
|
49
|
+
# Only import pybel on demand — `moleditpy` itself doesn't expose `pybel`.
|
|
50
|
+
if OBABEL_AVAILABLE:
|
|
51
|
+
try:
|
|
52
|
+
from openbabel import pybel
|
|
53
|
+
except Exception:
|
|
54
|
+
# If import fails here, disable OBABEL locally; avoid raising
|
|
55
|
+
pybel = None
|
|
56
|
+
OBABEL_AVAILABLE = False
|
|
57
|
+
print("Warning: openbabel.pybel not available. Open Babel fallback and OBabel-based options will be disabled.")
|
|
58
|
+
else:
|
|
59
|
+
pybel = None
|
|
60
|
+
|
|
61
|
+
# Optional SIP helper: on some PyQt6 builds sip.isdeleted is available and
|
|
62
|
+
# allows safely detecting C++ wrapper objects that have been deleted. Import
|
|
63
|
+
# it once at module import time and expose a small, robust wrapper so callers
|
|
64
|
+
# can avoid re-importing sip repeatedly and so we centralize exception
|
|
65
|
+
# handling (this reduces crash risk during teardown and deletion operations).
|
|
66
|
+
try:
|
|
67
|
+
import sip as _sip # type: ignore
|
|
68
|
+
_sip_isdeleted = getattr(_sip, 'isdeleted', None)
|
|
69
|
+
except Exception:
|
|
70
|
+
_sip = None
|
|
71
|
+
_sip_isdeleted = None
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
# package relative imports (preferred when running as `python -m moleditpy`)
|
|
75
|
+
pass
|
|
76
|
+
except Exception:
|
|
77
|
+
# Fallback to absolute imports for script-style execution
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# --- クラス定義 ---
|
|
82
|
+
class MainWindowStringImporters(object):
|
|
83
|
+
""" main_window.py から分離された機能クラス """
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def import_smiles_dialog(self):
|
|
87
|
+
"""ユーザーにSMILES文字列の入力を促すダイアログを表示する"""
|
|
88
|
+
smiles, ok = QInputDialog.getText(self, "Import SMILES", "Enter SMILES string:")
|
|
89
|
+
if ok and smiles:
|
|
90
|
+
self.load_from_smiles(smiles)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def import_inchi_dialog(self):
|
|
95
|
+
"""ユーザーにInChI文字列の入力を促すダイアログを表示する"""
|
|
96
|
+
inchi, ok = QInputDialog.getText(self, "Import InChI", "Enter InChI string:")
|
|
97
|
+
if ok and inchi:
|
|
98
|
+
self.load_from_inchi(inchi)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def load_from_smiles(self, smiles_string):
|
|
103
|
+
"""SMILES文字列から分子を読み込み、2Dエディタに表示する"""
|
|
104
|
+
try:
|
|
105
|
+
if not self.check_unsaved_changes():
|
|
106
|
+
return # ユーザーがキャンセルした場合は何もしない
|
|
107
|
+
|
|
108
|
+
cleaned_smiles = smiles_string.strip()
|
|
109
|
+
|
|
110
|
+
mol = Chem.MolFromSmiles(cleaned_smiles)
|
|
111
|
+
if mol is None:
|
|
112
|
+
if not cleaned_smiles:
|
|
113
|
+
raise ValueError("SMILES string was empty.")
|
|
114
|
+
raise ValueError("Invalid SMILES string.")
|
|
115
|
+
|
|
116
|
+
AllChem.Compute2DCoords(mol)
|
|
117
|
+
Chem.Kekulize(mol)
|
|
118
|
+
|
|
119
|
+
AllChem.AssignStereochemistry(mol, cleanIt=True, force=True)
|
|
120
|
+
conf = mol.GetConformer()
|
|
121
|
+
AllChem.WedgeMolBonds(mol, conf)
|
|
122
|
+
|
|
123
|
+
self.restore_ui_for_editing()
|
|
124
|
+
self.clear_2d_editor(push_to_undo=False)
|
|
125
|
+
self.current_mol = None
|
|
126
|
+
self.plotter.clear()
|
|
127
|
+
self.analysis_action.setEnabled(False)
|
|
128
|
+
|
|
129
|
+
conf = mol.GetConformer()
|
|
130
|
+
SCALE_FACTOR = 50.0
|
|
131
|
+
|
|
132
|
+
view_center = self.view_2d.mapToScene(self.view_2d.viewport().rect().center())
|
|
133
|
+
positions = [conf.GetAtomPosition(i) for i in range(mol.GetNumAtoms())]
|
|
134
|
+
mol_center_x = sum(p.x for p in positions) / len(positions) if positions else 0.0
|
|
135
|
+
mol_center_y = sum(p.y for p in positions) / len(positions) if positions else 0.0
|
|
136
|
+
|
|
137
|
+
rdkit_idx_to_my_id = {}
|
|
138
|
+
for i in range(mol.GetNumAtoms()):
|
|
139
|
+
atom = mol.GetAtomWithIdx(i)
|
|
140
|
+
pos = conf.GetAtomPosition(i)
|
|
141
|
+
charge = atom.GetFormalCharge()
|
|
142
|
+
|
|
143
|
+
relative_x = pos.x - mol_center_x
|
|
144
|
+
relative_y = pos.y - mol_center_y
|
|
145
|
+
|
|
146
|
+
scene_x = (relative_x * SCALE_FACTOR) + view_center.x()
|
|
147
|
+
scene_y = (-relative_y * SCALE_FACTOR) + view_center.y()
|
|
148
|
+
|
|
149
|
+
atom_id = self.scene.create_atom(atom.GetSymbol(), QPointF(scene_x, scene_y), charge=charge)
|
|
150
|
+
rdkit_idx_to_my_id[i] = atom_id
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
for bond in mol.GetBonds():
|
|
154
|
+
b_idx, e_idx = bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()
|
|
155
|
+
b_type = bond.GetBondTypeAsDouble()
|
|
156
|
+
b_dir = bond.GetBondDir()
|
|
157
|
+
stereo = 0
|
|
158
|
+
# 単結合の立体
|
|
159
|
+
if b_dir == Chem.BondDir.BEGINWEDGE:
|
|
160
|
+
stereo = 1 # Wedge
|
|
161
|
+
elif b_dir == Chem.BondDir.BEGINDASH:
|
|
162
|
+
stereo = 2 # Dash
|
|
163
|
+
# 二重結合のE/Z
|
|
164
|
+
if bond.GetBondType() == Chem.BondType.DOUBLE:
|
|
165
|
+
if bond.GetStereo() == Chem.BondStereo.STEREOZ:
|
|
166
|
+
stereo = 3 # Z
|
|
167
|
+
elif bond.GetStereo() == Chem.BondStereo.STEREOE:
|
|
168
|
+
stereo = 4 # E
|
|
169
|
+
|
|
170
|
+
if b_idx in rdkit_idx_to_my_id and e_idx in rdkit_idx_to_my_id:
|
|
171
|
+
a1_id, a2_id = rdkit_idx_to_my_id[b_idx], rdkit_idx_to_my_id[e_idx]
|
|
172
|
+
a1_item = self.data.atoms[a1_id]['item']
|
|
173
|
+
a2_item = self.data.atoms[a2_id]['item']
|
|
174
|
+
self.scene.create_bond(a1_item, a2_item, bond_order=int(b_type), bond_stereo=stereo)
|
|
175
|
+
|
|
176
|
+
self.statusBar().showMessage("Successfully loaded from SMILES.")
|
|
177
|
+
self.reset_undo_stack()
|
|
178
|
+
self.has_unsaved_changes = False
|
|
179
|
+
self.update_window_title()
|
|
180
|
+
QTimer.singleShot(0, self.fit_to_view)
|
|
181
|
+
|
|
182
|
+
except ValueError as e:
|
|
183
|
+
self.statusBar().showMessage(f"Invalid SMILES: {e}")
|
|
184
|
+
except Exception as e:
|
|
185
|
+
self.statusBar().showMessage(f"Error loading from SMILES: {e}")
|
|
186
|
+
|
|
187
|
+
traceback.print_exc()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def load_from_inchi(self, inchi_string):
|
|
192
|
+
"""InChI文字列から分子を読み込み、2Dエディタに表示する"""
|
|
193
|
+
try:
|
|
194
|
+
if not self.check_unsaved_changes():
|
|
195
|
+
return # ユーザーがキャンセルした場合は何もしない
|
|
196
|
+
cleaned_inchi = inchi_string.strip()
|
|
197
|
+
|
|
198
|
+
mol = Chem.MolFromInchi(cleaned_inchi)
|
|
199
|
+
if mol is None:
|
|
200
|
+
if not cleaned_inchi:
|
|
201
|
+
raise ValueError("InChI string was empty.")
|
|
202
|
+
raise ValueError("Invalid InChI string.")
|
|
203
|
+
|
|
204
|
+
AllChem.Compute2DCoords(mol)
|
|
205
|
+
Chem.Kekulize(mol)
|
|
206
|
+
|
|
207
|
+
AllChem.AssignStereochemistry(mol, cleanIt=True, force=True)
|
|
208
|
+
conf = mol.GetConformer()
|
|
209
|
+
AllChem.WedgeMolBonds(mol, conf)
|
|
210
|
+
|
|
211
|
+
self.restore_ui_for_editing()
|
|
212
|
+
self.clear_2d_editor(push_to_undo=False)
|
|
213
|
+
self.current_mol = None
|
|
214
|
+
self.plotter.clear()
|
|
215
|
+
self.analysis_action.setEnabled(False)
|
|
216
|
+
|
|
217
|
+
conf = mol.GetConformer()
|
|
218
|
+
SCALE_FACTOR = 50.0
|
|
219
|
+
|
|
220
|
+
view_center = self.view_2d.mapToScene(self.view_2d.viewport().rect().center())
|
|
221
|
+
positions = [conf.GetAtomPosition(i) for i in range(mol.GetNumAtoms())]
|
|
222
|
+
mol_center_x = sum(p.x for p in positions) / len(positions) if positions else 0.0
|
|
223
|
+
mol_center_y = sum(p.y for p in positions) / len(positions) if positions else 0.0
|
|
224
|
+
|
|
225
|
+
rdkit_idx_to_my_id = {}
|
|
226
|
+
for i in range(mol.GetNumAtoms()):
|
|
227
|
+
atom = mol.GetAtomWithIdx(i)
|
|
228
|
+
pos = conf.GetAtomPosition(i)
|
|
229
|
+
charge = atom.GetFormalCharge()
|
|
230
|
+
|
|
231
|
+
relative_x = pos.x - mol_center_x
|
|
232
|
+
relative_y = pos.y - mol_center_y
|
|
233
|
+
|
|
234
|
+
scene_x = (relative_x * SCALE_FACTOR) + view_center.x()
|
|
235
|
+
scene_y = (-relative_y * SCALE_FACTOR) + view_center.y()
|
|
236
|
+
|
|
237
|
+
atom_id = self.scene.create_atom(atom.GetSymbol(), QPointF(scene_x, scene_y), charge=charge)
|
|
238
|
+
rdkit_idx_to_my_id[i] = atom_id
|
|
239
|
+
|
|
240
|
+
for bond in mol.GetBonds():
|
|
241
|
+
b_idx, e_idx = bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()
|
|
242
|
+
b_type = bond.GetBondTypeAsDouble()
|
|
243
|
+
b_dir = bond.GetBondDir()
|
|
244
|
+
stereo = 0
|
|
245
|
+
# 単結合の立体
|
|
246
|
+
if b_dir == Chem.BondDir.BEGINWEDGE:
|
|
247
|
+
stereo = 1 # Wedge
|
|
248
|
+
elif b_dir == Chem.BondDir.BEGINDASH:
|
|
249
|
+
stereo = 2 # Dash
|
|
250
|
+
# 二重結合のE/Z
|
|
251
|
+
if bond.GetBondType() == Chem.BondType.DOUBLE:
|
|
252
|
+
if bond.GetStereo() == Chem.BondStereo.STEREOZ:
|
|
253
|
+
stereo = 3 # Z
|
|
254
|
+
elif bond.GetStereo() == Chem.BondStereo.STEREOE:
|
|
255
|
+
stereo = 4 # E
|
|
256
|
+
|
|
257
|
+
if b_idx in rdkit_idx_to_my_id and e_idx in rdkit_idx_to_my_id:
|
|
258
|
+
a1_id, a2_id = rdkit_idx_to_my_id[b_idx], rdkit_idx_to_my_id[e_idx]
|
|
259
|
+
a1_item = self.data.atoms[a1_id]['item']
|
|
260
|
+
a2_item = self.data.atoms[a2_id]['item']
|
|
261
|
+
self.scene.create_bond(a1_item, a2_item, bond_order=int(b_type), bond_stereo=stereo)
|
|
262
|
+
|
|
263
|
+
self.statusBar().showMessage("Successfully loaded from InChI.")
|
|
264
|
+
self.reset_undo_stack()
|
|
265
|
+
self.has_unsaved_changes = False
|
|
266
|
+
self.update_window_title()
|
|
267
|
+
QTimer.singleShot(0, self.fit_to_view)
|
|
268
|
+
|
|
269
|
+
except ValueError as e:
|
|
270
|
+
self.statusBar().showMessage(f"Invalid InChI: {e}")
|
|
271
|
+
except Exception as e:
|
|
272
|
+
self.statusBar().showMessage(f"Error loading from InChI: {e}")
|
|
273
|
+
|
|
274
|
+
traceback.print_exc()
|
|
275
|
+
|