MoleditPy 2.2.0a1__py3-none-any.whl → 2.2.0a3__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/modules/constants.py +1 -1
- moleditpy/modules/main_window_main_init.py +31 -13
- moleditpy/modules/main_window_ui_manager.py +21 -2
- moleditpy/modules/plugin_interface.py +1 -10
- moleditpy/modules/plugin_manager.py +0 -3
- {moleditpy-2.2.0a1.dist-info → moleditpy-2.2.0a3.dist-info}/METADATA +1 -1
- {moleditpy-2.2.0a1.dist-info → moleditpy-2.2.0a3.dist-info}/RECORD +11 -28
- moleditpy/plugins/Analysis/ms_spectrum_neo.py +0 -919
- moleditpy/plugins/File/animated_xyz_giffer.py +0 -583
- moleditpy/plugins/File/cube_viewer.py +0 -689
- moleditpy/plugins/File/gaussian_fchk_freq_analyzer.py +0 -1148
- moleditpy/plugins/File/mapped_cube_viewer.py +0 -552
- moleditpy/plugins/File/orca_out_freq_analyzer.py +0 -1226
- moleditpy/plugins/File/paste_xyz.py +0 -336
- moleditpy/plugins/Input Generator/gaussian_input_generator_neo.py +0 -930
- moleditpy/plugins/Input Generator/orca_input_generator_neo.py +0 -1028
- moleditpy/plugins/Input Generator/orca_xyz2inp_gui.py +0 -286
- moleditpy/plugins/Optimization/all-trans_optimizer.py +0 -65
- moleditpy/plugins/Optimization/complex_molecule_untangler.py +0 -268
- moleditpy/plugins/Optimization/conf_search.py +0 -224
- moleditpy/plugins/Utility/atom_colorizer.py +0 -547
- moleditpy/plugins/Utility/console.py +0 -163
- moleditpy/plugins/Utility/pubchem_ressolver.py +0 -244
- moleditpy/plugins/Utility/vdw_radii_overlay.py +0 -303
- {moleditpy-2.2.0a1.dist-info → moleditpy-2.2.0a3.dist-info}/WHEEL +0 -0
- {moleditpy-2.2.0a1.dist-info → moleditpy-2.2.0a3.dist-info}/entry_points.txt +0 -0
- {moleditpy-2.2.0a1.dist-info → moleditpy-2.2.0a3.dist-info}/licenses/LICENSE +0 -0
- {moleditpy-2.2.0a1.dist-info → moleditpy-2.2.0a3.dist-info}/top_level.txt +0 -0
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import traceback
|
|
3
|
-
import io
|
|
4
|
-
import contextlib
|
|
5
|
-
from PyQt6.QtWidgets import (
|
|
6
|
-
QDialog, QVBoxLayout, QTextEdit, QPushButton, QHBoxLayout,
|
|
7
|
-
QMessageBox, QLabel, QLineEdit, QDialogButtonBox, QInputDialog
|
|
8
|
-
)
|
|
9
|
-
from PyQt6.QtCore import Qt
|
|
10
|
-
try:
|
|
11
|
-
from rdkit import Chem
|
|
12
|
-
from rdkit.Chem import rdGeometry, AllChem
|
|
13
|
-
except ImportError:
|
|
14
|
-
Chem = None
|
|
15
|
-
|
|
16
|
-
PLUGIN_NAME = "Paste XYZ"
|
|
17
|
-
__version__="2025.12.25"
|
|
18
|
-
__author__="HiroYokoyama"
|
|
19
|
-
|
|
20
|
-
class PasteXYZDialog(QDialog):
|
|
21
|
-
def __init__(self, parent=None):
|
|
22
|
-
super().__init__(parent)
|
|
23
|
-
self.setWindowTitle("Paste XYZ")
|
|
24
|
-
self.resize(600, 400)
|
|
25
|
-
self.layout = QVBoxLayout(self)
|
|
26
|
-
|
|
27
|
-
info_label = QLabel("Paste XYZ coordinates below (Header/Atom count is IGNORED).")
|
|
28
|
-
self.layout.addWidget(info_label)
|
|
29
|
-
|
|
30
|
-
self.text_edit = QTextEdit()
|
|
31
|
-
self.text_edit.setPlaceholderText("Paste XYZ data here...\nExample:\nC 0.0 0.0 0.0\nH 1.0 0.0 0.0\n...")
|
|
32
|
-
self.layout.addWidget(self.text_edit)
|
|
33
|
-
|
|
34
|
-
btn_layout = QHBoxLayout()
|
|
35
|
-
self.load_btn = QPushButton("Load")
|
|
36
|
-
self.cancel_btn = QPushButton("Cancel")
|
|
37
|
-
btn_layout.addStretch()
|
|
38
|
-
btn_layout.addWidget(self.load_btn)
|
|
39
|
-
btn_layout.addWidget(self.cancel_btn)
|
|
40
|
-
self.layout.addLayout(btn_layout)
|
|
41
|
-
|
|
42
|
-
self.load_btn.clicked.connect(self.accept)
|
|
43
|
-
self.cancel_btn.clicked.connect(self.reject)
|
|
44
|
-
|
|
45
|
-
def get_data(self):
|
|
46
|
-
return self.text_edit.toPlainText()
|
|
47
|
-
|
|
48
|
-
def run(mw):
|
|
49
|
-
if Chem is None:
|
|
50
|
-
QMessageBox.critical(mw, "Error", "RDKit is not available.")
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
dialog = PasteXYZDialog(mw)
|
|
54
|
-
if dialog.exec() == QDialog.Accepted:
|
|
55
|
-
xyz_text = dialog.get_data()
|
|
56
|
-
if not xyz_text.strip():
|
|
57
|
-
return
|
|
58
|
-
|
|
59
|
-
try:
|
|
60
|
-
# Robust Parsing Logic: Scan for 'Symbol X Y Z' lines
|
|
61
|
-
atoms_data = []
|
|
62
|
-
lines = xyz_text.splitlines()
|
|
63
|
-
|
|
64
|
-
for line in lines:
|
|
65
|
-
parts = line.split()
|
|
66
|
-
if len(parts) >= 4:
|
|
67
|
-
# Check if last 3 are standard floats (coordinates)
|
|
68
|
-
try:
|
|
69
|
-
x = float(parts[1])
|
|
70
|
-
y = float(parts[2])
|
|
71
|
-
z = float(parts[3])
|
|
72
|
-
symbol = parts[0]
|
|
73
|
-
# Verify symbol is likely an element (alpha)
|
|
74
|
-
if not symbol[0].isalpha():
|
|
75
|
-
# Maybe it's 'Index Symbol X Y Z' or something else?
|
|
76
|
-
# Strict requirement: "Header/Atom count ignored", assume Paste is pure or standard XYZ
|
|
77
|
-
# If line starts with a number, it's likely count or index, SKIP.
|
|
78
|
-
# Standard XYZ atom lines start with Symbol.
|
|
79
|
-
continue
|
|
80
|
-
|
|
81
|
-
atoms_data.append((symbol, x, y, z))
|
|
82
|
-
except ValueError:
|
|
83
|
-
# Not coordinates, skip (likely header or title)
|
|
84
|
-
continue
|
|
85
|
-
|
|
86
|
-
if not atoms_data:
|
|
87
|
-
QMessageBox.warning(mw, "Paste XYZ", "No valid coordinate lines found.\nExpected format: Symbol X Y Z")
|
|
88
|
-
return
|
|
89
|
-
|
|
90
|
-
# Clean workspace
|
|
91
|
-
if hasattr(mw, 'clear_all'):
|
|
92
|
-
mw.clear_all()
|
|
93
|
-
elif hasattr(mw, 'plotter'):
|
|
94
|
-
mw.plotter.clear()
|
|
95
|
-
|
|
96
|
-
# Create RWMol and add atoms/conformers
|
|
97
|
-
mol = Chem.RWMol()
|
|
98
|
-
for i, (symbol, x, y, z) in enumerate(atoms_data):
|
|
99
|
-
atom = Chem.Atom(symbol)
|
|
100
|
-
atom.SetIntProp("xyz_unique_id", i)
|
|
101
|
-
mol.AddAtom(atom)
|
|
102
|
-
|
|
103
|
-
conf = Chem.Conformer(len(atoms_data))
|
|
104
|
-
for i, (symbol, x, y, z) in enumerate(atoms_data):
|
|
105
|
-
conf.SetAtomPosition(i, rdGeometry.Point3D(x, y, z))
|
|
106
|
-
mol.AddConformer(conf)
|
|
107
|
-
|
|
108
|
-
# --- Chemistry Check Logic (Adapted from main_window_molecular_parsers.py) ---
|
|
109
|
-
|
|
110
|
-
# Helper: prompt for charge
|
|
111
|
-
def prompt_for_charge():
|
|
112
|
-
try:
|
|
113
|
-
dialog = QDialog(mw)
|
|
114
|
-
dialog.setWindowTitle("Import XYZ Charge")
|
|
115
|
-
layout = QVBoxLayout(dialog)
|
|
116
|
-
label = QLabel("Enter total molecular charge:")
|
|
117
|
-
line_edit = QLineEdit(dialog)
|
|
118
|
-
line_edit.setText("")
|
|
119
|
-
btn_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, parent=dialog)
|
|
120
|
-
skip_btn = QPushButton("Skip chemistry", dialog)
|
|
121
|
-
hl = QHBoxLayout()
|
|
122
|
-
hl.addWidget(btn_box)
|
|
123
|
-
hl.addWidget(skip_btn)
|
|
124
|
-
layout.addWidget(label)
|
|
125
|
-
layout.addWidget(line_edit)
|
|
126
|
-
layout.addLayout(hl)
|
|
127
|
-
|
|
128
|
-
result = {"accepted": False, "skip": False}
|
|
129
|
-
def on_ok():
|
|
130
|
-
result["accepted"] = True
|
|
131
|
-
dialog.accept()
|
|
132
|
-
def on_cancel():
|
|
133
|
-
dialog.reject()
|
|
134
|
-
def on_skip():
|
|
135
|
-
result["skip"] = True
|
|
136
|
-
dialog.accept()
|
|
137
|
-
|
|
138
|
-
try:
|
|
139
|
-
btn_box.button(QDialogButtonBox.Ok).clicked.connect(on_ok)
|
|
140
|
-
btn_box.button(QDialogButtonBox.Cancel).clicked.connect(on_cancel)
|
|
141
|
-
except Exception:
|
|
142
|
-
btn_box.accepted.connect(on_ok)
|
|
143
|
-
btn_box.rejected.connect(on_cancel)
|
|
144
|
-
skip_btn.clicked.connect(on_skip)
|
|
145
|
-
|
|
146
|
-
if dialog.exec() != QDialog.Accepted:
|
|
147
|
-
return None, False, False
|
|
148
|
-
if result["skip"]:
|
|
149
|
-
return 0, True, True
|
|
150
|
-
if not result["accepted"]:
|
|
151
|
-
return None, False, False
|
|
152
|
-
|
|
153
|
-
charge_text = line_edit.text()
|
|
154
|
-
except Exception:
|
|
155
|
-
# Fallback to simple input
|
|
156
|
-
try:
|
|
157
|
-
charge_text, ok = QInputDialog.getText(mw, "Import XYZ Charge", "Enter total molecular charge:", text="0")
|
|
158
|
-
if not ok: return None, False, False
|
|
159
|
-
except Exception:
|
|
160
|
-
return 0, True, False
|
|
161
|
-
|
|
162
|
-
try:
|
|
163
|
-
return int(str(charge_text).strip()), True, False
|
|
164
|
-
except Exception:
|
|
165
|
-
try:
|
|
166
|
-
return int(float(str(charge_text).strip())), True, False
|
|
167
|
-
except Exception:
|
|
168
|
-
return 0, True, False
|
|
169
|
-
|
|
170
|
-
# Inner helper: process with charge
|
|
171
|
-
def _process_with_charge(charge_val):
|
|
172
|
-
buf = io.StringIO()
|
|
173
|
-
used_rd_determine = False
|
|
174
|
-
mol_to_finalize = None
|
|
175
|
-
with contextlib.redirect_stderr(buf):
|
|
176
|
-
try:
|
|
177
|
-
from rdkit.Chem import rdDetermineBonds
|
|
178
|
-
try:
|
|
179
|
-
# Try to copy mol
|
|
180
|
-
try:
|
|
181
|
-
mol_candidate = Chem.RWMol(Chem.Mol(mol))
|
|
182
|
-
except Exception:
|
|
183
|
-
mol_candidate = Chem.RWMol(mol)
|
|
184
|
-
|
|
185
|
-
rdDetermineBonds.DetermineBonds(mol_candidate, charge=charge_val)
|
|
186
|
-
mol_to_finalize = mol_candidate
|
|
187
|
-
used_rd_determine = True
|
|
188
|
-
except Exception:
|
|
189
|
-
# DetermineBonds failed
|
|
190
|
-
raise RuntimeError("DetermineBondsFailed")
|
|
191
|
-
except RuntimeError:
|
|
192
|
-
raise
|
|
193
|
-
except Exception:
|
|
194
|
-
used_rd_determine = False
|
|
195
|
-
mol_to_finalize = mol
|
|
196
|
-
|
|
197
|
-
if not used_rd_determine:
|
|
198
|
-
# Fallback to distance based
|
|
199
|
-
if hasattr(mw, 'estimate_bonds_from_distances'):
|
|
200
|
-
mw.estimate_bonds_from_distances(mol_to_finalize)
|
|
201
|
-
|
|
202
|
-
try:
|
|
203
|
-
candidate_mol = mol_to_finalize.GetMol()
|
|
204
|
-
except Exception:
|
|
205
|
-
candidate_mol = None
|
|
206
|
-
|
|
207
|
-
if candidate_mol is None:
|
|
208
|
-
# Salvage
|
|
209
|
-
try:
|
|
210
|
-
candidate_mol = mol.GetMol()
|
|
211
|
-
except Exception:
|
|
212
|
-
candidate_mol = None
|
|
213
|
-
|
|
214
|
-
if candidate_mol is None:
|
|
215
|
-
raise ValueError("Failed to create valid molecule object")
|
|
216
|
-
|
|
217
|
-
# Attach charge prop
|
|
218
|
-
try:
|
|
219
|
-
candidate_mol.SetIntProp("_xyz_charge", int(charge_val))
|
|
220
|
-
except Exception:
|
|
221
|
-
pass
|
|
222
|
-
|
|
223
|
-
# Apply chem check flags (if available in main_window)
|
|
224
|
-
if hasattr(mw, '_apply_chem_check_and_set_flags'):
|
|
225
|
-
mw._apply_chem_check_and_set_flags(candidate_mol, source_desc='PasteXYZ')
|
|
226
|
-
|
|
227
|
-
return candidate_mol
|
|
228
|
-
|
|
229
|
-
# Main Logic Loop
|
|
230
|
-
final_mol = None
|
|
231
|
-
|
|
232
|
-
# Check settings if available (defaulting to safe behavior)
|
|
233
|
-
settings = getattr(mw, 'settings', {})
|
|
234
|
-
always_ask = bool(settings.get('always_ask_charge', False))
|
|
235
|
-
skip_checks_global = bool(settings.get('skip_chemistry_checks', False))
|
|
236
|
-
|
|
237
|
-
if skip_checks_global:
|
|
238
|
-
# Skip path
|
|
239
|
-
if hasattr(mw, 'estimate_bonds_from_distances'):
|
|
240
|
-
try: mw.estimate_bonds_from_distances(mol)
|
|
241
|
-
except: pass
|
|
242
|
-
try:
|
|
243
|
-
final_mol = mol.GetMol()
|
|
244
|
-
final_mol.SetIntProp("_xyz_skip_checks", 1)
|
|
245
|
-
# Disable optimization for this mol
|
|
246
|
-
mw.current_mol = final_mol
|
|
247
|
-
mw.is_xyz_derived = True
|
|
248
|
-
except: pass
|
|
249
|
-
else:
|
|
250
|
-
# Normal path
|
|
251
|
-
try:
|
|
252
|
-
if not always_ask:
|
|
253
|
-
try:
|
|
254
|
-
final_mol = _process_with_charge(0)
|
|
255
|
-
except RuntimeError:
|
|
256
|
-
# DetermineBonds failed for 0, loop prompt
|
|
257
|
-
while True:
|
|
258
|
-
charge_val, ok, skip_flag = prompt_for_charge()
|
|
259
|
-
if not ok: return # User cancel
|
|
260
|
-
if skip_flag:
|
|
261
|
-
# User skipped
|
|
262
|
-
if hasattr(mw, 'estimate_bonds_from_distances'):
|
|
263
|
-
try: mw.estimate_bonds_from_distances(mol)
|
|
264
|
-
except: pass
|
|
265
|
-
try:
|
|
266
|
-
final_mol = mol.GetMol()
|
|
267
|
-
final_mol.SetIntProp("_xyz_skip_checks", 1)
|
|
268
|
-
except: pass
|
|
269
|
-
break
|
|
270
|
-
|
|
271
|
-
try:
|
|
272
|
-
final_mol = _process_with_charge(charge_val)
|
|
273
|
-
break
|
|
274
|
-
except RuntimeError:
|
|
275
|
-
mw.statusBar().showMessage("DetermineBonds failed for that charge...")
|
|
276
|
-
continue
|
|
277
|
-
except Exception:
|
|
278
|
-
# Other error, try salvage if skip checks allowed? No, here we just continue or break
|
|
279
|
-
continue
|
|
280
|
-
else:
|
|
281
|
-
# Always ask
|
|
282
|
-
while True:
|
|
283
|
-
charge_val, ok, skip_flag = prompt_for_charge()
|
|
284
|
-
if not ok: return
|
|
285
|
-
if skip_flag:
|
|
286
|
-
if hasattr(mw, 'estimate_bonds_from_distances'):
|
|
287
|
-
try: mw.estimate_bonds_from_distances(mol)
|
|
288
|
-
except: pass
|
|
289
|
-
try:
|
|
290
|
-
final_mol = mol.GetMol()
|
|
291
|
-
final_mol.SetIntProp("_xyz_skip_checks", 1)
|
|
292
|
-
except: pass
|
|
293
|
-
break
|
|
294
|
-
try:
|
|
295
|
-
final_mol = _process_with_charge(charge_val)
|
|
296
|
-
break
|
|
297
|
-
except RuntimeError:
|
|
298
|
-
mw.statusBar().showMessage("DetermineBonds failed...")
|
|
299
|
-
continue
|
|
300
|
-
except Exception:
|
|
301
|
-
continue
|
|
302
|
-
except Exception:
|
|
303
|
-
# Any other unhandled fallback
|
|
304
|
-
pass
|
|
305
|
-
|
|
306
|
-
# If failed to get final_mol, fallback to raw
|
|
307
|
-
if final_mol is None:
|
|
308
|
-
if hasattr(mw, 'estimate_bonds_from_distances'):
|
|
309
|
-
try: mw.estimate_bonds_from_distances(mol)
|
|
310
|
-
except: pass
|
|
311
|
-
try: final_mol = mol.GetMol()
|
|
312
|
-
except: pass
|
|
313
|
-
|
|
314
|
-
if final_mol:
|
|
315
|
-
# We need RWMol for editing
|
|
316
|
-
rw_mol = Chem.RWMol(final_mol)
|
|
317
|
-
mw.current_mol = rw_mol
|
|
318
|
-
mw.current_file_path = None
|
|
319
|
-
mw.has_unsaved_changes = True
|
|
320
|
-
|
|
321
|
-
if hasattr(mw, 'update_window_title'): mw.update_window_title()
|
|
322
|
-
if hasattr(mw, 'reset_undo_stack'): mw.reset_undo_stack()
|
|
323
|
-
if hasattr(mw, 'draw_molecule_3d'): mw.draw_molecule_3d(rw_mol)
|
|
324
|
-
if hasattr(mw, 'fit_to_view'): mw.fit_to_view()
|
|
325
|
-
if hasattr(mw, 'statusBar'): mw.statusBar().showMessage(f"Loaded {len(atoms_data)} atoms from clipboard data.")
|
|
326
|
-
|
|
327
|
-
# Enter 3D only mode as requested
|
|
328
|
-
if hasattr(mw, '_enter_3d_viewer_ui_mode'):
|
|
329
|
-
try:
|
|
330
|
-
mw._enter_3d_viewer_ui_mode()
|
|
331
|
-
except Exception as e:
|
|
332
|
-
print(f"Could not switch to 3D mode: {e}")
|
|
333
|
-
|
|
334
|
-
except Exception as e:
|
|
335
|
-
traceback.print_exc()
|
|
336
|
-
QMessageBox.critical(mw, "Error", f"Failed to parse or load data:\n{e}")
|