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.
Files changed (28) hide show
  1. moleditpy/modules/constants.py +1 -1
  2. moleditpy/modules/main_window_main_init.py +31 -13
  3. moleditpy/modules/main_window_ui_manager.py +21 -2
  4. moleditpy/modules/plugin_interface.py +1 -10
  5. moleditpy/modules/plugin_manager.py +0 -3
  6. {moleditpy-2.2.0a1.dist-info → moleditpy-2.2.0a3.dist-info}/METADATA +1 -1
  7. {moleditpy-2.2.0a1.dist-info → moleditpy-2.2.0a3.dist-info}/RECORD +11 -28
  8. moleditpy/plugins/Analysis/ms_spectrum_neo.py +0 -919
  9. moleditpy/plugins/File/animated_xyz_giffer.py +0 -583
  10. moleditpy/plugins/File/cube_viewer.py +0 -689
  11. moleditpy/plugins/File/gaussian_fchk_freq_analyzer.py +0 -1148
  12. moleditpy/plugins/File/mapped_cube_viewer.py +0 -552
  13. moleditpy/plugins/File/orca_out_freq_analyzer.py +0 -1226
  14. moleditpy/plugins/File/paste_xyz.py +0 -336
  15. moleditpy/plugins/Input Generator/gaussian_input_generator_neo.py +0 -930
  16. moleditpy/plugins/Input Generator/orca_input_generator_neo.py +0 -1028
  17. moleditpy/plugins/Input Generator/orca_xyz2inp_gui.py +0 -286
  18. moleditpy/plugins/Optimization/all-trans_optimizer.py +0 -65
  19. moleditpy/plugins/Optimization/complex_molecule_untangler.py +0 -268
  20. moleditpy/plugins/Optimization/conf_search.py +0 -224
  21. moleditpy/plugins/Utility/atom_colorizer.py +0 -547
  22. moleditpy/plugins/Utility/console.py +0 -163
  23. moleditpy/plugins/Utility/pubchem_ressolver.py +0 -244
  24. moleditpy/plugins/Utility/vdw_radii_overlay.py +0 -303
  25. {moleditpy-2.2.0a1.dist-info → moleditpy-2.2.0a3.dist-info}/WHEEL +0 -0
  26. {moleditpy-2.2.0a1.dist-info → moleditpy-2.2.0a3.dist-info}/entry_points.txt +0 -0
  27. {moleditpy-2.2.0a1.dist-info → moleditpy-2.2.0a3.dist-info}/licenses/LICENSE +0 -0
  28. {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}")