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,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
+