MoleditPy 2.2.0a2__py3-none-any.whl → 2.2.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/modules/constants.py +1 -1
- moleditpy/modules/main_window_main_init.py +32 -14
- moleditpy/modules/plugin_interface.py +1 -10
- moleditpy/modules/plugin_manager.py +0 -3
- {moleditpy-2.2.0a2.dist-info → moleditpy-2.2.1.dist-info}/METADATA +1 -1
- {moleditpy-2.2.0a2.dist-info → moleditpy-2.2.1.dist-info}/RECORD +10 -27
- 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 -262
- moleditpy/plugins/Utility/console.py +0 -163
- moleditpy/plugins/Utility/pubchem_ressolver.py +0 -244
- moleditpy/plugins/Utility/vdw_radii_overlay.py +0 -432
- {moleditpy-2.2.0a2.dist-info → moleditpy-2.2.1.dist-info}/WHEEL +0 -0
- {moleditpy-2.2.0a2.dist-info → moleditpy-2.2.1.dist-info}/entry_points.txt +0 -0
- {moleditpy-2.2.0a2.dist-info → moleditpy-2.2.1.dist-info}/licenses/LICENSE +0 -0
- {moleditpy-2.2.0a2.dist-info → moleditpy-2.2.1.dist-info}/top_level.txt +0 -0
|
@@ -1,1028 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
import os
|
|
3
|
-
from PyQt6.QtWidgets import (QMessageBox, QDialog, QVBoxLayout, QLabel,
|
|
4
|
-
QLineEdit, QSpinBox, QPushButton, QFileDialog,
|
|
5
|
-
QFormLayout, QGroupBox, QHBoxLayout, QComboBox, QTextEdit,
|
|
6
|
-
QTabWidget, QCheckBox, QWidget, QScrollArea, QMenu)
|
|
7
|
-
from PyQt6.QtGui import QPalette, QColor, QAction, QFont
|
|
8
|
-
from PyQt6.QtCore import Qt
|
|
9
|
-
from rdkit import Chem
|
|
10
|
-
from rdkit.Chem import rdMolTransforms
|
|
11
|
-
import json
|
|
12
|
-
|
|
13
|
-
__version__="2025.12.18"
|
|
14
|
-
__author__="HiroYokoyama"
|
|
15
|
-
PLUGIN_NAME = "ORCA Input Generator Neo"
|
|
16
|
-
SETTINGS_FILE = os.path.join(os.path.dirname(__file__), "orca_input_generator_neo.json")
|
|
17
|
-
|
|
18
|
-
class OrcaKeywordBuilderDialog(QDialog):
|
|
19
|
-
"""
|
|
20
|
-
Dialog to construct the ORCA Job Route line.
|
|
21
|
-
"""
|
|
22
|
-
def __init__(self, parent=None, current_route=""):
|
|
23
|
-
super().__init__(parent)
|
|
24
|
-
self.setWindowTitle("ORCA Keyword Builder")
|
|
25
|
-
self.resize(700, 600)
|
|
26
|
-
self.ui_ready = False
|
|
27
|
-
self.current_route = current_route
|
|
28
|
-
self.setup_ui()
|
|
29
|
-
self.parse_route(current_route)
|
|
30
|
-
|
|
31
|
-
def setup_ui(self):
|
|
32
|
-
layout = QVBoxLayout()
|
|
33
|
-
|
|
34
|
-
self.tabs = QTabWidget()
|
|
35
|
-
|
|
36
|
-
# --- Tab 1: Method & Basis ---
|
|
37
|
-
self.tab_method = QWidget()
|
|
38
|
-
self.setup_method_tab()
|
|
39
|
-
self.tabs.addTab(self.tab_method, "Method/Basis")
|
|
40
|
-
|
|
41
|
-
# --- Tab 2: Job Type ---
|
|
42
|
-
self.tab_job = QWidget()
|
|
43
|
-
self.setup_job_tab()
|
|
44
|
-
self.tabs.addTab(self.tab_job, "Job Type")
|
|
45
|
-
|
|
46
|
-
# --- Tab 3: Solvation & Disp ---
|
|
47
|
-
self.tab_solvation = QWidget()
|
|
48
|
-
self.setup_solvation_tab()
|
|
49
|
-
self.tabs.addTab(self.tab_solvation, "Solvation/Dispersion")
|
|
50
|
-
|
|
51
|
-
# --- Tab 4: Properties ---
|
|
52
|
-
self.tab_props = QWidget()
|
|
53
|
-
self.setup_props_tab()
|
|
54
|
-
self.tabs.addTab(self.tab_props, "Properties")
|
|
55
|
-
|
|
56
|
-
layout.addWidget(self.tabs)
|
|
57
|
-
|
|
58
|
-
# --- Preview ---
|
|
59
|
-
preview_group = QGroupBox("Keyword Preview")
|
|
60
|
-
preview_layout = QVBoxLayout()
|
|
61
|
-
self.preview_label = QLabel()
|
|
62
|
-
self.preview_label.setWordWrap(True)
|
|
63
|
-
self.preview_label.setStyleSheet("font-weight: bold; color: blue; font-size: 14px;")
|
|
64
|
-
preview_layout.addWidget(self.preview_label)
|
|
65
|
-
preview_group.setLayout(preview_layout)
|
|
66
|
-
layout.addWidget(preview_group)
|
|
67
|
-
|
|
68
|
-
# --- Buttons ---
|
|
69
|
-
btn_layout = QHBoxLayout()
|
|
70
|
-
self.btn_ok = QPushButton("Apply to Job")
|
|
71
|
-
self.btn_ok.clicked.connect(self.accept)
|
|
72
|
-
self.btn_cancel = QPushButton("Cancel")
|
|
73
|
-
self.btn_cancel.clicked.connect(self.reject)
|
|
74
|
-
btn_layout.addStretch()
|
|
75
|
-
btn_layout.addWidget(self.btn_ok)
|
|
76
|
-
btn_layout.addWidget(self.btn_cancel)
|
|
77
|
-
layout.addLayout(btn_layout)
|
|
78
|
-
|
|
79
|
-
self.setLayout(layout)
|
|
80
|
-
|
|
81
|
-
self.connect_signals()
|
|
82
|
-
self.ui_ready = True
|
|
83
|
-
self.update_ui_state() # Initial UI state update
|
|
84
|
-
self.update_preview()
|
|
85
|
-
|
|
86
|
-
def setup_method_tab(self):
|
|
87
|
-
layout = QFormLayout()
|
|
88
|
-
|
|
89
|
-
# Method Type
|
|
90
|
-
self.method_type = QComboBox()
|
|
91
|
-
self.method_type.addItems([
|
|
92
|
-
"DFT (GGA/Hybrid/Meta)",
|
|
93
|
-
"DFT (Range-Separated)",
|
|
94
|
-
"DFT (Double Hybrid)",
|
|
95
|
-
"Wavefunction (HF/MP2)",
|
|
96
|
-
"Wavefunction (Coupled Cluster)",
|
|
97
|
-
"Wavefunction (Multireference)",
|
|
98
|
-
"Semi-Empirical"
|
|
99
|
-
])
|
|
100
|
-
self.method_type.currentIndexChanged.connect(self.update_method_list)
|
|
101
|
-
layout.addRow("Method Type:", self.method_type)
|
|
102
|
-
|
|
103
|
-
# Method Name
|
|
104
|
-
self.method_name = QComboBox()
|
|
105
|
-
self.update_method_list()
|
|
106
|
-
layout.addRow("Method:", self.method_name)
|
|
107
|
-
|
|
108
|
-
# Basis Set
|
|
109
|
-
self.basis_set = QComboBox()
|
|
110
|
-
basis_groups = [
|
|
111
|
-
"--- Karlsruhe (Def2) ---",
|
|
112
|
-
"def2-SV(P)", "def2-SVP", "def2-TZVP", "def2-QZVP",
|
|
113
|
-
"ma-def2-SVP", "ma-def2-TZVP", "ma-def2-QZVP",
|
|
114
|
-
"def2-TZVPP", "def2-QZVPP",
|
|
115
|
-
"--- Dunning (cc-pV) ---",
|
|
116
|
-
"cc-pVDZ", "cc-pVTZ", "cc-pVQZ", "cc-pV5Z",
|
|
117
|
-
"aug-cc-pVDZ", "aug-cc-pVTZ", "aug-cc-pVQZ", "aug-cc-pV5Z",
|
|
118
|
-
"--- Pople ---",
|
|
119
|
-
"6-31G", "6-31G*", "6-311G", "6-311G*", "6-311G**",
|
|
120
|
-
"6-31+G*", "6-311+G**", "6-31++G**",
|
|
121
|
-
"--- Jensen (pc) ---",
|
|
122
|
-
"pc-0", "pc-1", "pc-2", "pc-3", "aug-pc-1", "aug-pc-2",
|
|
123
|
-
"--- Other ---",
|
|
124
|
-
"EPR-II", "EPR-III", "IGLO-II", "IGLO-III"
|
|
125
|
-
]
|
|
126
|
-
self.basis_set.addItems(basis_groups)
|
|
127
|
-
self.basis_set.setCurrentText("def2-SVP")
|
|
128
|
-
layout.addRow("Basis Set:", self.basis_set)
|
|
129
|
-
|
|
130
|
-
# Auxiliary Basis (RI/RIJCOSX)
|
|
131
|
-
self.aux_basis = QComboBox()
|
|
132
|
-
self.aux_basis.addItems([
|
|
133
|
-
"Auto (Def2/J, etc)", "None", "Def2/J", "Def2/JK",
|
|
134
|
-
"AutoAux", "NoAux"
|
|
135
|
-
])
|
|
136
|
-
layout.addRow("Aux Basis (RI):", self.aux_basis)
|
|
137
|
-
|
|
138
|
-
self.tab_method.setLayout(layout)
|
|
139
|
-
|
|
140
|
-
def update_method_list(self):
|
|
141
|
-
mtype = self.method_type.currentText()
|
|
142
|
-
self.method_name.blockSignals(True)
|
|
143
|
-
self.method_name.clear()
|
|
144
|
-
|
|
145
|
-
if "GGA/Hybrid" in mtype:
|
|
146
|
-
self.method_name.addItems([
|
|
147
|
-
"B3LYP", "PBE0", "PBE", "BP86", "BLYP", "PW91",
|
|
148
|
-
"TPSSh", "TPSS", "SCAN", "r2SCAN-3c", "B97-3c", "PBEh-3c", # 3c系を追加
|
|
149
|
-
"M06", "M06-2X", "M06-HF", "M06-L",
|
|
150
|
-
"X3LYP", "O3LYP", "B3PW91", "BHandHLYP" # BH&HLYPをBHandHLYPに修正
|
|
151
|
-
])
|
|
152
|
-
elif "Range-Separated" in mtype:
|
|
153
|
-
self.method_name.addItems([
|
|
154
|
-
"wB97X-D3", "wB97X-V", "wB97M-V", "wB97X", "wB97",
|
|
155
|
-
"CAM-B3LYP", "LC-wPBE", "wPBE", "wPBEh"
|
|
156
|
-
])
|
|
157
|
-
elif "Double Hybrid" in mtype:
|
|
158
|
-
self.method_name.addItems([
|
|
159
|
-
"B2PLYP", "mPW2PLYP", "B2GP-PLYP", "DSD-PBEP86", "DSD-BLYP",
|
|
160
|
-
"PTPSS-D3", "PWPB95"
|
|
161
|
-
])
|
|
162
|
-
elif "Wavefunction (HF/MP2)" in mtype:
|
|
163
|
-
self.method_name.addItems([
|
|
164
|
-
"HF", "HF-3c", "UHF", "ROHF", # HF-3cを追加
|
|
165
|
-
"MP2", "RI-MP2", "SCS-MP2", "OO-RI-MP2"
|
|
166
|
-
])
|
|
167
|
-
elif "Coupled Cluster" in mtype:
|
|
168
|
-
self.method_name.addItems([
|
|
169
|
-
"DLPNO-CCSD(T)", "DLPNO-CCSD(T1)", "DLPNO-CCSD",
|
|
170
|
-
"CCSD(T)", "CCSD", "QCISD(T)", "CEPA/1"
|
|
171
|
-
])
|
|
172
|
-
elif "Multireference" in mtype:
|
|
173
|
-
self.method_name.addItems([
|
|
174
|
-
"CASSCF", "NEVPT2", "DLPNO-NEVPT2", "MRCI"
|
|
175
|
-
])
|
|
176
|
-
elif "Semi-Empirical" in mtype:
|
|
177
|
-
self.method_name.addItems(["XT", "GFN1-xTB", "GFN2-xTB", "PM3", "AM1", "ZINDO/S", "PM6", "MNDO"])
|
|
178
|
-
else:
|
|
179
|
-
self.method_name.addItem("Custom")
|
|
180
|
-
|
|
181
|
-
self.method_name.blockSignals(False)
|
|
182
|
-
self.update_ui_state() # Update visibility when method type changes
|
|
183
|
-
self.update_preview()
|
|
184
|
-
|
|
185
|
-
def setup_job_tab(self):
|
|
186
|
-
layout = QVBoxLayout()
|
|
187
|
-
|
|
188
|
-
self.job_type = QComboBox()
|
|
189
|
-
self.job_type.addItems([
|
|
190
|
-
"Optimization + Freq (Opt Freq)",
|
|
191
|
-
"Optimization Only (Opt)",
|
|
192
|
-
"Frequency Only (Freq)",
|
|
193
|
-
"Single Point Energy (SP)",
|
|
194
|
-
"Scan (Relaxed Surface)",
|
|
195
|
-
"Transition State Opt (OptTS)",
|
|
196
|
-
"Gradient",
|
|
197
|
-
"Hessian"
|
|
198
|
-
])
|
|
199
|
-
layout.addWidget(QLabel("Job Task:"))
|
|
200
|
-
layout.addWidget(self.job_type)
|
|
201
|
-
self.job_type.currentIndexChanged.connect(self.update_ui_state)
|
|
202
|
-
|
|
203
|
-
# Opt Options
|
|
204
|
-
self.opt_group = QGroupBox("Optimization Options")
|
|
205
|
-
opt_layout = QHBoxLayout()
|
|
206
|
-
self.opt_tight = QCheckBox("TightOpt")
|
|
207
|
-
self.opt_loose = QCheckBox("LooseOpt")
|
|
208
|
-
self.opt_calcfc = QCheckBox("CalcFC")
|
|
209
|
-
self.opt_ts_mode = QCheckBox("CalcHess (for TS)")
|
|
210
|
-
opt_layout.addWidget(self.opt_tight)
|
|
211
|
-
opt_layout.addWidget(self.opt_loose)
|
|
212
|
-
opt_layout.addWidget(self.opt_calcfc)
|
|
213
|
-
opt_layout.addWidget(self.opt_ts_mode)
|
|
214
|
-
self.opt_group.setLayout(opt_layout)
|
|
215
|
-
layout.addWidget(self.opt_group)
|
|
216
|
-
|
|
217
|
-
# Freq Options
|
|
218
|
-
self.freq_group = QGroupBox("Freq Options")
|
|
219
|
-
freq_layout = QHBoxLayout()
|
|
220
|
-
self.freq_num = QCheckBox("NumFreq")
|
|
221
|
-
self.freq_raman = QCheckBox("Raman")
|
|
222
|
-
freq_layout.addWidget(self.freq_num)
|
|
223
|
-
freq_layout.addWidget(self.freq_raman)
|
|
224
|
-
self.freq_group.setLayout(freq_layout)
|
|
225
|
-
layout.addWidget(self.freq_group)
|
|
226
|
-
|
|
227
|
-
layout.addStretch()
|
|
228
|
-
self.tab_job.setLayout(layout)
|
|
229
|
-
|
|
230
|
-
def setup_solvation_tab(self):
|
|
231
|
-
layout = QFormLayout()
|
|
232
|
-
|
|
233
|
-
self.solv_model = QComboBox()
|
|
234
|
-
self.solv_model.addItems(["None", "CPCM", "SMD", "IEFPCM", "CPC(Water) (Short)"])
|
|
235
|
-
self.solv_model.currentIndexChanged.connect(self.update_ui_state)
|
|
236
|
-
layout.addRow("Solvation Model:", self.solv_model)
|
|
237
|
-
|
|
238
|
-
self.solvent = QComboBox()
|
|
239
|
-
solvents = [
|
|
240
|
-
"Water", "Acetonitrile", "Methanol", "Ethanol",
|
|
241
|
-
"Chloroform", "Dichloromethane", "Toluene",
|
|
242
|
-
"THF", "DMSO", "Cyclohexane", "Benzene", "Acetone",
|
|
243
|
-
"CCl4", "DMF", "HMPA", "Pyridine"
|
|
244
|
-
]
|
|
245
|
-
self.solvent.addItems(solvents)
|
|
246
|
-
layout.addRow("Solvent:", self.solvent)
|
|
247
|
-
|
|
248
|
-
layout.addRow(QLabel(" "))
|
|
249
|
-
|
|
250
|
-
self.dispersion = QComboBox()
|
|
251
|
-
self.dispersion.addItems(["None", "D3BJ", "D3Zero", "D4", "D2", "NL"])
|
|
252
|
-
layout.addRow("Dispersion Correction:", self.dispersion)
|
|
253
|
-
|
|
254
|
-
self.tab_solvation.setLayout(layout)
|
|
255
|
-
|
|
256
|
-
def setup_props_tab(self):
|
|
257
|
-
layout = QFormLayout()
|
|
258
|
-
|
|
259
|
-
self.rijcosx = QCheckBox("RIJCOSX / RI approximation")
|
|
260
|
-
self.rijcosx.setChecked(True)
|
|
261
|
-
layout.addRow(self.rijcosx)
|
|
262
|
-
|
|
263
|
-
self.grid_combo = QComboBox()
|
|
264
|
-
self.grid_combo.addItems(["Default", "DefGrid2", "DefGrid3", "Grid4", "Grid5", "Grid6", "NoGrid"])
|
|
265
|
-
layout.addRow("Grid:", self.grid_combo)
|
|
266
|
-
|
|
267
|
-
self.scf_conv = QComboBox()
|
|
268
|
-
self.scf_conv.addItems(["Default", "LooseSCF", "TightSCF", "VeryTightSCF"])
|
|
269
|
-
layout.addRow("SCF Convergence:", self.scf_conv)
|
|
270
|
-
|
|
271
|
-
# NBO
|
|
272
|
-
self.pop_nbo = QCheckBox("NBO Analysis (! NBO)")
|
|
273
|
-
layout.addRow(self.pop_nbo)
|
|
274
|
-
|
|
275
|
-
self.tab_props.setLayout(layout)
|
|
276
|
-
|
|
277
|
-
def connect_signals(self):
|
|
278
|
-
widgets = [
|
|
279
|
-
self.method_type, self.method_name, self.basis_set, self.aux_basis,
|
|
280
|
-
self.job_type, self.opt_tight, self.opt_loose, self.opt_calcfc, self.opt_ts_mode,
|
|
281
|
-
self.freq_num, self.freq_raman,
|
|
282
|
-
self.solv_model, self.solvent, self.dispersion,
|
|
283
|
-
self.rijcosx, self.grid_combo, self.scf_conv, self.pop_nbo
|
|
284
|
-
]
|
|
285
|
-
for w in widgets:
|
|
286
|
-
if isinstance(w, QComboBox):
|
|
287
|
-
w.currentIndexChanged.connect(self.update_preview)
|
|
288
|
-
elif isinstance(w, QCheckBox):
|
|
289
|
-
w.toggled.connect(self.update_preview)
|
|
290
|
-
elif isinstance(w, QSpinBox):
|
|
291
|
-
w.valueChanged.connect(self.update_preview)
|
|
292
|
-
|
|
293
|
-
def update_ui_state(self):
|
|
294
|
-
"""Update usability of widgets based on current selection."""
|
|
295
|
-
if not getattr(self, 'ui_ready', False): return
|
|
296
|
-
|
|
297
|
-
# 1. Method Dependent
|
|
298
|
-
mtype = self.method_type.currentText()
|
|
299
|
-
is_semi = "Semi-Empirical" in mtype
|
|
300
|
-
|
|
301
|
-
# Disable Basis Set & Aux Basis for Semi-Empirical
|
|
302
|
-
self.basis_set.setEnabled(not is_semi)
|
|
303
|
-
self.aux_basis.setEnabled(not is_semi)
|
|
304
|
-
|
|
305
|
-
# Handling RI / RIJCOSX
|
|
306
|
-
if is_semi:
|
|
307
|
-
self.rijcosx.setEnabled(False)
|
|
308
|
-
self.rijcosx.setChecked(False)
|
|
309
|
-
self.rijcosx.setText("RIJCOSX (N/A)")
|
|
310
|
-
else:
|
|
311
|
-
self.rijcosx.setEnabled(True)
|
|
312
|
-
if "Wavefunction" in mtype:
|
|
313
|
-
self.rijcosx.setText("RI Approximation (! RI ...)")
|
|
314
|
-
else:
|
|
315
|
-
self.rijcosx.setText("RIJCOSX (Speed up Hybrid DFT)")
|
|
316
|
-
|
|
317
|
-
# 2. Solvation
|
|
318
|
-
solv = self.solv_model.currentText()
|
|
319
|
-
is_solvated = solv != "None"
|
|
320
|
-
self.solvent.setEnabled(is_solvated)
|
|
321
|
-
if "CPC(Water)" in solv:
|
|
322
|
-
self.solvent.setEnabled(False) # Water is implied
|
|
323
|
-
|
|
324
|
-
# 3. Job Type
|
|
325
|
-
job_txt = self.job_type.currentText()
|
|
326
|
-
is_opt = "Opt" in job_txt or "Scan" in job_txt
|
|
327
|
-
is_freq = "Freq" in job_txt
|
|
328
|
-
|
|
329
|
-
self.opt_group.setVisible(is_opt)
|
|
330
|
-
self.freq_group.setVisible(is_freq)
|
|
331
|
-
|
|
332
|
-
# 4. TD-DFT (Removed from Route Builder, handled via blocks)
|
|
333
|
-
|
|
334
|
-
def update_preview(self):
|
|
335
|
-
if not getattr(self, 'ui_ready', False):
|
|
336
|
-
return
|
|
337
|
-
|
|
338
|
-
route_parts = ["!"]
|
|
339
|
-
|
|
340
|
-
# Method / Basis
|
|
341
|
-
method = self.method_name.currentText()
|
|
342
|
-
basis = self.basis_set.currentText()
|
|
343
|
-
|
|
344
|
-
# 3c methods usually don't need basis set
|
|
345
|
-
mtype = self.method_type.currentText()
|
|
346
|
-
if "Semi-Empirical" in mtype:
|
|
347
|
-
route_parts.append(method)
|
|
348
|
-
elif "3c" in method:
|
|
349
|
-
route_parts.append(method)
|
|
350
|
-
else:
|
|
351
|
-
route_parts.append(method)
|
|
352
|
-
route_parts.append(basis)
|
|
353
|
-
|
|
354
|
-
# RIJCOSX / RI
|
|
355
|
-
if self.rijcosx.isEnabled() and self.rijcosx.isChecked():
|
|
356
|
-
if "Wavefunction" in mtype:
|
|
357
|
-
route_parts.append("RI")
|
|
358
|
-
else:
|
|
359
|
-
route_parts.append("RIJCOSX")
|
|
360
|
-
|
|
361
|
-
aux = self.aux_basis.currentText()
|
|
362
|
-
if "Def2/J" in aux: route_parts.append("Def2/J")
|
|
363
|
-
elif "Def2/JK" in aux: route_parts.append("Def2/JK")
|
|
364
|
-
|
|
365
|
-
# Job Type
|
|
366
|
-
job_txt = self.job_type.currentText()
|
|
367
|
-
if "Opt Freq" in job_txt: route_parts.extend(["Opt", "Freq"])
|
|
368
|
-
elif "Opt Only" in job_txt: route_parts.append("Opt")
|
|
369
|
-
elif "OptTS" in job_txt: route_parts.append("OptTS")
|
|
370
|
-
elif "Freq Only" in job_txt: route_parts.append("Freq")
|
|
371
|
-
elif "Scan" in job_txt: route_parts.append("Scan")
|
|
372
|
-
elif "Gradient" in job_txt: route_parts.append("Gradient")
|
|
373
|
-
elif "Hessian" in job_txt: route_parts.append("Hessian")
|
|
374
|
-
elif "SP" in job_txt: pass # No keyword
|
|
375
|
-
|
|
376
|
-
# Opt Options
|
|
377
|
-
if self.opt_group.isVisible():
|
|
378
|
-
if self.opt_tight.isChecked(): route_parts.append("TightOpt")
|
|
379
|
-
if self.opt_loose.isChecked(): route_parts.append("LooseOpt")
|
|
380
|
-
if self.opt_calcfc.isChecked(): route_parts.append("CalcFC")
|
|
381
|
-
if self.opt_ts_mode.isChecked(): route_parts.append("CalcHess")
|
|
382
|
-
|
|
383
|
-
# Freq Options
|
|
384
|
-
if self.freq_group.isVisible():
|
|
385
|
-
if self.freq_num.isChecked(): route_parts.append("NumFreq")
|
|
386
|
-
|
|
387
|
-
# Solvation
|
|
388
|
-
solv = self.solv_model.currentText()
|
|
389
|
-
if solv != "None":
|
|
390
|
-
if "CPC(Water)" in solv:
|
|
391
|
-
route_parts.append("CPC(Water)")
|
|
392
|
-
else:
|
|
393
|
-
solvent = self.solvent.currentText()
|
|
394
|
-
if "CPCM" == solv:
|
|
395
|
-
route_parts.append(f"CPCM({solvent})")
|
|
396
|
-
elif "SMD" == solv:
|
|
397
|
-
route_parts.append(f"CPCM({solvent})")
|
|
398
|
-
route_parts.append("SMD")
|
|
399
|
-
elif "IEFPCM" == solv:
|
|
400
|
-
route_parts.append(f"CPCM({solvent})")
|
|
401
|
-
|
|
402
|
-
# Dispersion
|
|
403
|
-
disp = self.dispersion.currentText()
|
|
404
|
-
if disp != "None":
|
|
405
|
-
route_parts.append(disp)
|
|
406
|
-
|
|
407
|
-
# SCF / Grid
|
|
408
|
-
scf = self.scf_conv.currentText()
|
|
409
|
-
if scf != "Default": route_parts.append(scf)
|
|
410
|
-
|
|
411
|
-
grid = self.grid_combo.currentText()
|
|
412
|
-
if grid != "Default": route_parts.append(grid)
|
|
413
|
-
|
|
414
|
-
# NBO
|
|
415
|
-
if self.pop_nbo.isChecked(): route_parts.append("NBO")
|
|
416
|
-
|
|
417
|
-
self.preview_str = " ".join(route_parts)
|
|
418
|
-
self.preview_label.setText(self.preview_str)
|
|
419
|
-
|
|
420
|
-
def get_route(self):
|
|
421
|
-
return self.preview_str
|
|
422
|
-
|
|
423
|
-
def parse_route(self, route):
|
|
424
|
-
pass
|
|
425
|
-
|
|
426
|
-
class OrcaSetupDialogNeo(QDialog):
|
|
427
|
-
"""
|
|
428
|
-
ORCA Input Generator Neo
|
|
429
|
-
"""
|
|
430
|
-
def __init__(self, parent=None, mol=None, filename=None):
|
|
431
|
-
super().__init__(parent)
|
|
432
|
-
self.setWindowTitle(PLUGIN_NAME)
|
|
433
|
-
self.resize(600, 700)
|
|
434
|
-
self.mol = mol
|
|
435
|
-
self.filename = filename
|
|
436
|
-
self.setup_ui()
|
|
437
|
-
self.load_presets_from_file()
|
|
438
|
-
self.calc_initial_charge_mult()
|
|
439
|
-
|
|
440
|
-
def setup_ui(self):
|
|
441
|
-
main_layout = QVBoxLayout()
|
|
442
|
-
|
|
443
|
-
# --- 0. Preset Management ---
|
|
444
|
-
preset_group = QGroupBox("Preset Management")
|
|
445
|
-
preset_layout = QHBoxLayout()
|
|
446
|
-
|
|
447
|
-
self.preset_combo = QComboBox()
|
|
448
|
-
self.preset_combo.currentIndexChanged.connect(self.apply_selected_preset)
|
|
449
|
-
preset_layout.addWidget(QLabel("Preset:"))
|
|
450
|
-
preset_layout.addWidget(self.preset_combo, 1)
|
|
451
|
-
|
|
452
|
-
self.btn_save_preset = QPushButton("Save New...")
|
|
453
|
-
self.btn_save_preset.clicked.connect(self.save_preset_dialog)
|
|
454
|
-
preset_layout.addWidget(self.btn_save_preset)
|
|
455
|
-
|
|
456
|
-
self.btn_del_preset = QPushButton("Delete")
|
|
457
|
-
self.btn_del_preset.clicked.connect(self.delete_preset)
|
|
458
|
-
preset_layout.addWidget(self.btn_del_preset)
|
|
459
|
-
|
|
460
|
-
preset_group.setLayout(preset_layout)
|
|
461
|
-
main_layout.addWidget(preset_group)
|
|
462
|
-
|
|
463
|
-
# --- 1. Resource Configuration ---
|
|
464
|
-
res_group = QGroupBox("Resources (%pal, %maxcore)")
|
|
465
|
-
res_layout = QFormLayout()
|
|
466
|
-
|
|
467
|
-
# NProcs
|
|
468
|
-
self.nproc_spin = QSpinBox()
|
|
469
|
-
self.nproc_spin.setRange(1, 128)
|
|
470
|
-
self.nproc_spin.setValue(4)
|
|
471
|
-
res_layout.addRow("Number of Processors (nprocs):", self.nproc_spin)
|
|
472
|
-
|
|
473
|
-
# Memory
|
|
474
|
-
self.mem_spin = QSpinBox()
|
|
475
|
-
self.mem_spin.setRange(100, 999999)
|
|
476
|
-
self.mem_spin.setValue(2000)
|
|
477
|
-
self.mem_spin.setSuffix(" MB")
|
|
478
|
-
res_layout.addRow("Memory per Core (MaxCore):", self.mem_spin)
|
|
479
|
-
|
|
480
|
-
res_group.setLayout(res_layout)
|
|
481
|
-
main_layout.addWidget(res_group)
|
|
482
|
-
|
|
483
|
-
# --- 2. Simple Input Line ---
|
|
484
|
-
kw_group = QGroupBox("Simple Input Line (!)")
|
|
485
|
-
kw_layout = QVBoxLayout()
|
|
486
|
-
|
|
487
|
-
kw_h_layout = QHBoxLayout()
|
|
488
|
-
self.keywords_edit = QLineEdit("! B3LYP def2-SVP Opt Freq")
|
|
489
|
-
self.btn_route = QPushButton("Keyword Builder...")
|
|
490
|
-
self.btn_route.clicked.connect(self.open_keyword_builder)
|
|
491
|
-
kw_h_layout.addWidget(self.keywords_edit)
|
|
492
|
-
kw_h_layout.addWidget(self.btn_route)
|
|
493
|
-
|
|
494
|
-
kw_layout.addWidget(QLabel("Keywords (starts with !):"))
|
|
495
|
-
kw_layout.addLayout(kw_h_layout)
|
|
496
|
-
|
|
497
|
-
# Comment
|
|
498
|
-
self.comment_edit = QLineEdit("Generated by MoleditPy ORCA Neo")
|
|
499
|
-
kw_layout.addWidget(QLabel("Comment (# ...):"))
|
|
500
|
-
kw_layout.addWidget(self.comment_edit)
|
|
501
|
-
|
|
502
|
-
kw_group.setLayout(kw_layout)
|
|
503
|
-
main_layout.addWidget(kw_group)
|
|
504
|
-
|
|
505
|
-
# --- 3. Molecular State ---
|
|
506
|
-
mol_group = QGroupBox("Molecular Specification")
|
|
507
|
-
mol_layout = QHBoxLayout()
|
|
508
|
-
|
|
509
|
-
self.charge_spin = QSpinBox()
|
|
510
|
-
self.charge_spin.setRange(-10, 10)
|
|
511
|
-
self.charge_spin.valueChanged.connect(self.validate_charge_mult)
|
|
512
|
-
|
|
513
|
-
self.mult_spin = QSpinBox()
|
|
514
|
-
self.mult_spin.setRange(1, 10)
|
|
515
|
-
self.mult_spin.valueChanged.connect(self.validate_charge_mult)
|
|
516
|
-
|
|
517
|
-
mol_layout.addWidget(QLabel("Charge:"))
|
|
518
|
-
mol_layout.addWidget(self.charge_spin)
|
|
519
|
-
mol_layout.addWidget(QLabel("Multiplicity:"))
|
|
520
|
-
mol_layout.addWidget(self.mult_spin)
|
|
521
|
-
|
|
522
|
-
self.default_palette = self.charge_spin.palette()
|
|
523
|
-
|
|
524
|
-
mol_group.setLayout(mol_layout)
|
|
525
|
-
main_layout.addWidget(mol_group)
|
|
526
|
-
|
|
527
|
-
# --- 3b. Coordinate Format ---
|
|
528
|
-
coord_group = QGroupBox("Coordinate Format")
|
|
529
|
-
coord_layout = QHBoxLayout()
|
|
530
|
-
self.coord_format_combo = QComboBox()
|
|
531
|
-
self.coord_format_combo.addItems(["Cartesian (XYZ)", "Internal (Z-Matrix / * int)", "Internal (Z-Matrix / * gzmt)"])
|
|
532
|
-
self.coord_format_combo.setCurrentIndex(0)
|
|
533
|
-
self.coord_format_combo.setEnabled(True)
|
|
534
|
-
coord_layout.addWidget(QLabel("Format:"))
|
|
535
|
-
coord_layout.addWidget(self.coord_format_combo)
|
|
536
|
-
coord_group.setLayout(coord_layout)
|
|
537
|
-
main_layout.addWidget(coord_group)
|
|
538
|
-
|
|
539
|
-
# --- 4. Advanced/Blocks ---
|
|
540
|
-
adv_group = QGroupBox("Advanced Blocks (Pre/Post Coordinates)")
|
|
541
|
-
adv_layout = QVBoxLayout()
|
|
542
|
-
|
|
543
|
-
# Block Helper
|
|
544
|
-
blk_h_layout = QHBoxLayout()
|
|
545
|
-
blk_h_layout.addWidget(QLabel("Block Helper:"))
|
|
546
|
-
|
|
547
|
-
self.block_combo = QComboBox()
|
|
548
|
-
self.block_combo.addItems([
|
|
549
|
-
"Select Block to Insert...",
|
|
550
|
-
"%scf ... end",
|
|
551
|
-
"%output ... end",
|
|
552
|
-
"%geom ... end",
|
|
553
|
-
"%elprop ... end",
|
|
554
|
-
"%plots ... end",
|
|
555
|
-
"%tddft ... end",
|
|
556
|
-
"%cis ... end",
|
|
557
|
-
"%mrci ... end",
|
|
558
|
-
"%casscf ... end",
|
|
559
|
-
"%eprnmr ... end (Post)",
|
|
560
|
-
])
|
|
561
|
-
blk_h_layout.addWidget(self.block_combo, 1)
|
|
562
|
-
|
|
563
|
-
self.btn_insert_block = QPushButton("Insert")
|
|
564
|
-
self.btn_insert_block.clicked.connect(self.insert_block_template)
|
|
565
|
-
blk_h_layout.addWidget(self.btn_insert_block)
|
|
566
|
-
|
|
567
|
-
adv_layout.addLayout(blk_h_layout)
|
|
568
|
-
|
|
569
|
-
# Tabs for Pre/Post
|
|
570
|
-
self.adv_tabs = QTabWidget()
|
|
571
|
-
|
|
572
|
-
self.adv_edit = QTextEdit()
|
|
573
|
-
self.adv_edit.setPlaceholderText("Pre-Coordinate Blocks\nExample:\n%scf\n MaxIter 100\nend")
|
|
574
|
-
self.adv_tabs.addTab(self.adv_edit, "Pre-Coordinate")
|
|
575
|
-
|
|
576
|
-
self.post_adv_edit = QTextEdit()
|
|
577
|
-
self.post_adv_edit.setPlaceholderText("Post-Coordinate Blocks")
|
|
578
|
-
self.adv_tabs.addTab(self.post_adv_edit, "Post-Coordinate")
|
|
579
|
-
|
|
580
|
-
adv_layout.addWidget(self.adv_tabs)
|
|
581
|
-
|
|
582
|
-
adv_group.setLayout(adv_layout)
|
|
583
|
-
main_layout.addWidget(adv_group)
|
|
584
|
-
|
|
585
|
-
# --- Save/Preview Buttons ---
|
|
586
|
-
btn_box = QHBoxLayout()
|
|
587
|
-
|
|
588
|
-
self.btn_preview = QPushButton("Preview Input")
|
|
589
|
-
self.btn_preview.clicked.connect(self.preview_file)
|
|
590
|
-
btn_box.addWidget(self.btn_preview)
|
|
591
|
-
|
|
592
|
-
self.save_btn = QPushButton("Save Input File...")
|
|
593
|
-
self.save_btn.clicked.connect(self.save_file)
|
|
594
|
-
self.save_btn.setStyleSheet("font-weight: bold; padding: 5px;")
|
|
595
|
-
btn_box.addWidget(self.save_btn)
|
|
596
|
-
|
|
597
|
-
main_layout.addLayout(btn_box)
|
|
598
|
-
|
|
599
|
-
self.setLayout(main_layout)
|
|
600
|
-
|
|
601
|
-
def insert_block_template(self):
|
|
602
|
-
txt = self.block_combo.currentText()
|
|
603
|
-
if "Select" in txt: return
|
|
604
|
-
|
|
605
|
-
template = ""
|
|
606
|
-
if "%scf" in txt:
|
|
607
|
-
template = "%scf\n MaxIter 125\nend\n"
|
|
608
|
-
elif "%output" in txt:
|
|
609
|
-
template = "%output\n PrintLevel Normal\nend\n"
|
|
610
|
-
elif "%geom" in txt:
|
|
611
|
-
template = "%geom\n MaxIter 100\nend\n"
|
|
612
|
-
elif "%elprop" in txt:
|
|
613
|
-
template = "%elprop\n Dipole True\n Quadrupole True\nend\n"
|
|
614
|
-
elif "%plots" in txt:
|
|
615
|
-
template = "%plots\n Format Gaussian_Cube\nend\n"
|
|
616
|
-
elif "%tddft" in txt:
|
|
617
|
-
template = (
|
|
618
|
-
"%tddft\n"
|
|
619
|
-
" NRoots 10 # Number of excited states\n"
|
|
620
|
-
" MaxDim 10 # Max dimension of expansion space\n"
|
|
621
|
-
" TDA true # Tamm-Dancoff Approximation (true/false)\n"
|
|
622
|
-
" IRoot 1 # State of interest for gradient properties\n"
|
|
623
|
-
" Triplets true # Calculate triplet states\n"
|
|
624
|
-
" Singlets true # Calculate singlet states\n"
|
|
625
|
-
" DoQuad true # Compute quadrupole intensities\n"
|
|
626
|
-
"end\n"
|
|
627
|
-
)
|
|
628
|
-
elif "%cis" in txt:
|
|
629
|
-
template = "%cis\n NRoots 10\nend\n"
|
|
630
|
-
elif "%mrci" in txt:
|
|
631
|
-
template = "%mrci\n NewBlocks 1 1\nend\n"
|
|
632
|
-
elif "%casscf" in txt:
|
|
633
|
-
template = "%casscf\n Nel 2\n Norb 2\n Mult 1\nend\n"
|
|
634
|
-
elif "%eprnmr" in txt:
|
|
635
|
-
template = "%eprnmr\n nuclei = all h {shift, ssall}\nend\n"
|
|
636
|
-
# Switch to Post-Coordinate tab automatically
|
|
637
|
-
self.adv_tabs.setCurrentWidget(self.post_adv_edit)
|
|
638
|
-
|
|
639
|
-
current_widget = self.adv_tabs.currentWidget()
|
|
640
|
-
if isinstance(current_widget, QTextEdit):
|
|
641
|
-
cursor = current_widget.textCursor()
|
|
642
|
-
cursor.insertText(template)
|
|
643
|
-
|
|
644
|
-
def open_keyword_builder(self):
|
|
645
|
-
dialog = OrcaKeywordBuilderDialog(self, self.keywords_edit.text())
|
|
646
|
-
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
647
|
-
self.keywords_edit.setText(dialog.get_route())
|
|
648
|
-
|
|
649
|
-
def get_coords_lines(self):
|
|
650
|
-
if not self.mol: return []
|
|
651
|
-
lines = []
|
|
652
|
-
try:
|
|
653
|
-
conf = self.mol.GetConformer()
|
|
654
|
-
for i in range(self.mol.GetNumAtoms()):
|
|
655
|
-
pos = conf.GetAtomPosition(i)
|
|
656
|
-
atom = self.mol.GetAtomWithIdx(i)
|
|
657
|
-
lines.append(f" {atom.GetSymbol(): <4} {pos.x: >12.6f} {pos.y: >12.6f} {pos.z: >12.6f}")
|
|
658
|
-
except Exception as e:
|
|
659
|
-
return [f"# Error: {e}"]
|
|
660
|
-
return lines
|
|
661
|
-
|
|
662
|
-
def _build_zmatrix_data(self):
|
|
663
|
-
"""Helper to build Z-Matrix connectivity and values."""
|
|
664
|
-
if not self.mol: return None
|
|
665
|
-
try:
|
|
666
|
-
atoms = list(self.mol.GetAtoms())
|
|
667
|
-
conf = self.mol.GetConformer()
|
|
668
|
-
|
|
669
|
-
def get_dist(i, j): return rdMolTransforms.GetBondLength(conf, i, j)
|
|
670
|
-
def get_angle(i, j, k): return rdMolTransforms.GetAngleDeg(conf, i, j, k)
|
|
671
|
-
def get_dihedral(i, j, k, l): return rdMolTransforms.GetDihedralDeg(conf, i, j, k, l)
|
|
672
|
-
|
|
673
|
-
defined = []
|
|
674
|
-
z_data = [] # List of dicts for each atom
|
|
675
|
-
|
|
676
|
-
for i, atom in enumerate(atoms):
|
|
677
|
-
symbol = atom.GetSymbol()
|
|
678
|
-
|
|
679
|
-
# Atom 0
|
|
680
|
-
if i == 0:
|
|
681
|
-
z_data.append({"symbol": symbol, "refs": []})
|
|
682
|
-
defined.append(i)
|
|
683
|
-
continue
|
|
684
|
-
|
|
685
|
-
# Find neighbors in defined set
|
|
686
|
-
current_idx = atom.GetIdx()
|
|
687
|
-
neighbors = [n.GetIdx() for n in atom.GetNeighbors()]
|
|
688
|
-
candidates = [n for n in neighbors if n in defined]
|
|
689
|
-
if not candidates: candidates = defined[:] # Fallback
|
|
690
|
-
|
|
691
|
-
refs = []
|
|
692
|
-
# Ref 1 (Distance)
|
|
693
|
-
if candidates: refs.append(candidates[-1])
|
|
694
|
-
else: refs.append(0)
|
|
695
|
-
|
|
696
|
-
# Ref 2 (Angle)
|
|
697
|
-
candidates_2 = [x for x in defined if x != refs[0]]
|
|
698
|
-
if candidates_2: refs.append(candidates_2[-1])
|
|
699
|
-
|
|
700
|
-
# Ref 3 (Dihedral)
|
|
701
|
-
candidates_3 = [x for x in defined if x not in refs]
|
|
702
|
-
if candidates_3: refs.append(candidates_3[-1])
|
|
703
|
-
|
|
704
|
-
# Calculate Values
|
|
705
|
-
row = {"symbol": symbol, "refs": [], "values": []}
|
|
706
|
-
|
|
707
|
-
if len(refs) >= 1:
|
|
708
|
-
row["refs"].append(refs[0]) # 0-based index for calculation
|
|
709
|
-
row["values"].append(get_dist(i, refs[0]))
|
|
710
|
-
|
|
711
|
-
if len(refs) >= 2:
|
|
712
|
-
row["refs"].append(refs[1])
|
|
713
|
-
row["values"].append(get_angle(i, refs[0], refs[1]))
|
|
714
|
-
|
|
715
|
-
if len(refs) >= 3:
|
|
716
|
-
row["refs"].append(refs[2])
|
|
717
|
-
row["values"].append(get_dihedral(i, refs[0], refs[1], refs[2]))
|
|
718
|
-
|
|
719
|
-
z_data.append(row)
|
|
720
|
-
defined.append(i)
|
|
721
|
-
|
|
722
|
-
return z_data
|
|
723
|
-
except Exception as e:
|
|
724
|
-
raise e
|
|
725
|
-
|
|
726
|
-
def get_zmatrix_standard_lines(self):
|
|
727
|
-
"""
|
|
728
|
-
Generates * int style lines.
|
|
729
|
-
Format: Symbol Ref1 Ref2 Ref3 R Angle Dihed
|
|
730
|
-
Refs are 1-based integers. Missing refs/values are 0/0.0.
|
|
731
|
-
"""
|
|
732
|
-
try:
|
|
733
|
-
data = self._build_zmatrix_data()
|
|
734
|
-
if not data: return []
|
|
735
|
-
|
|
736
|
-
lines = []
|
|
737
|
-
for i, row in enumerate(data):
|
|
738
|
-
symbol = row["symbol"]
|
|
739
|
-
|
|
740
|
-
# Prepare 3 refs (1-based) and 3 values
|
|
741
|
-
refs_out = [0, 0, 0]
|
|
742
|
-
vals_out = [0.0, 0.0, 0.0]
|
|
743
|
-
|
|
744
|
-
current_refs = row["refs"]
|
|
745
|
-
current_vals = row.get("values", [])
|
|
746
|
-
|
|
747
|
-
for k in range(min(3, len(current_refs))):
|
|
748
|
-
refs_out[k] = current_refs[k] + 1 # Convert to 1-based
|
|
749
|
-
vals_out[k] = current_vals[k]
|
|
750
|
-
|
|
751
|
-
line = f" {symbol: <3} {refs_out[0]: >3} {refs_out[1]: >3} {refs_out[2]: >3} " \
|
|
752
|
-
f"{vals_out[0]: >10.6f} {vals_out[1]: >10.6f} {vals_out[2]: >10.6f}"
|
|
753
|
-
lines.append(line)
|
|
754
|
-
return lines
|
|
755
|
-
except Exception as e:
|
|
756
|
-
return [f"# Error generating Standard Z-Matrix: {e}"]
|
|
757
|
-
|
|
758
|
-
def get_zmatrix_gzmt_lines(self):
|
|
759
|
-
"""
|
|
760
|
-
Generates * gzmt style lines (Compact).
|
|
761
|
-
Format: Symbol Ref1 R Ref2 Angle Ref3 Dihed
|
|
762
|
-
Refs are 1-based.
|
|
763
|
-
"""
|
|
764
|
-
try:
|
|
765
|
-
data = self._build_zmatrix_data()
|
|
766
|
-
if not data: return []
|
|
767
|
-
|
|
768
|
-
lines = []
|
|
769
|
-
for i, row in enumerate(data):
|
|
770
|
-
symbol = row["symbol"]
|
|
771
|
-
line = f" {symbol: <3}"
|
|
772
|
-
|
|
773
|
-
current_refs = row["refs"]
|
|
774
|
-
current_vals = row.get("values", [])
|
|
775
|
-
|
|
776
|
-
# Z-Matrix logic:
|
|
777
|
-
# Atom 1: Symbol
|
|
778
|
-
# Atom 2: Symbol Ref1 R
|
|
779
|
-
# Atom 3: Symbol Ref1 R Ref2 A
|
|
780
|
-
# Atom 4: Symbol Ref1 R Ref2 A Ref3 D
|
|
781
|
-
|
|
782
|
-
if i == 0:
|
|
783
|
-
pass
|
|
784
|
-
else:
|
|
785
|
-
count = len(current_refs)
|
|
786
|
-
if count >= 1:
|
|
787
|
-
line += f" {current_refs[0] + 1: >3} {current_vals[0]: .6f}"
|
|
788
|
-
if count >= 2:
|
|
789
|
-
line += f" {current_refs[1] + 1: >3} {current_vals[1]: .6f}"
|
|
790
|
-
if count >= 3:
|
|
791
|
-
line += f" {current_refs[2] + 1: >3} {current_vals[2]: .6f}"
|
|
792
|
-
|
|
793
|
-
lines.append(line)
|
|
794
|
-
return lines
|
|
795
|
-
except Exception as e:
|
|
796
|
-
return [f"# Error generating GZMT Z-Matrix: {e}"]
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
def generate_input_content(self):
|
|
800
|
-
"""Generates the full content of the input file as a string."""
|
|
801
|
-
content = []
|
|
802
|
-
|
|
803
|
-
comment = self.comment_edit.text().strip()
|
|
804
|
-
if comment:
|
|
805
|
-
content.append(f"# {comment}")
|
|
806
|
-
|
|
807
|
-
# Resources
|
|
808
|
-
nprocs = self.nproc_spin.value()
|
|
809
|
-
if nprocs > 1:
|
|
810
|
-
content.append(f"%pal nprocs {nprocs} end")
|
|
811
|
-
content.append(f"%maxcore {self.mem_spin.value()}\n")
|
|
812
|
-
|
|
813
|
-
# Keywords
|
|
814
|
-
kw = self.keywords_edit.text().strip()
|
|
815
|
-
if not kw.startswith("!"): kw = "! " + kw
|
|
816
|
-
content.append(f"{kw}\n")
|
|
817
|
-
|
|
818
|
-
# Advanced Blocks
|
|
819
|
-
adv = self.adv_edit.toPlainText().strip()
|
|
820
|
-
if adv:
|
|
821
|
-
content.append(f"{adv}\n")
|
|
822
|
-
|
|
823
|
-
# Coordinates
|
|
824
|
-
is_cartesian = "Cartesian" in self.coord_format_combo.currentText()
|
|
825
|
-
coord_lines = self.get_coords_lines()
|
|
826
|
-
|
|
827
|
-
if is_cartesian:
|
|
828
|
-
content.append(f"* xyz {self.charge_spin.value()} {self.mult_spin.value()}")
|
|
829
|
-
content.extend(coord_lines)
|
|
830
|
-
content.append("*")
|
|
831
|
-
else:
|
|
832
|
-
# Z-Matrix
|
|
833
|
-
is_gzmt = "gzmt" in self.coord_format_combo.currentText()
|
|
834
|
-
|
|
835
|
-
if is_gzmt:
|
|
836
|
-
zmat_lines = self.get_zmatrix_gzmt_lines()
|
|
837
|
-
header = "* gzmt"
|
|
838
|
-
else:
|
|
839
|
-
zmat_lines = self.get_zmatrix_standard_lines()
|
|
840
|
-
header = "* int"
|
|
841
|
-
|
|
842
|
-
if any("Error" in line for line in zmat_lines):
|
|
843
|
-
content.append(f"# ERROR: Z-Matrix generation failed.")
|
|
844
|
-
content.append(f"* xyz {self.charge_spin.value()} {self.mult_spin.value()}")
|
|
845
|
-
content.extend(self.get_coords_lines())
|
|
846
|
-
content.append("*")
|
|
847
|
-
else:
|
|
848
|
-
content.append(f"{header} {self.charge_spin.value()} {self.mult_spin.value()}")
|
|
849
|
-
content.extend(zmat_lines)
|
|
850
|
-
content.append("*")
|
|
851
|
-
|
|
852
|
-
# Post-Coordinate Blocks
|
|
853
|
-
adv_post = self.post_adv_edit.toPlainText().strip()
|
|
854
|
-
if adv_post:
|
|
855
|
-
content.append(f"\n{adv_post}")
|
|
856
|
-
|
|
857
|
-
return "\n".join(content)
|
|
858
|
-
|
|
859
|
-
def preview_file(self):
|
|
860
|
-
content = self.generate_input_content()
|
|
861
|
-
|
|
862
|
-
d = QDialog(self)
|
|
863
|
-
d.setWindowTitle("Preview Input - ORCA Neo")
|
|
864
|
-
d.resize(600, 500)
|
|
865
|
-
l = QVBoxLayout()
|
|
866
|
-
t = QTextEdit()
|
|
867
|
-
t.setPlainText(content)
|
|
868
|
-
t.setReadOnly(True)
|
|
869
|
-
t.setFont(QFont("Courier New", 10))
|
|
870
|
-
l.addWidget(t)
|
|
871
|
-
|
|
872
|
-
btn = QPushButton("Close")
|
|
873
|
-
btn.clicked.connect(d.accept)
|
|
874
|
-
l.addWidget(btn)
|
|
875
|
-
d.setLayout(l)
|
|
876
|
-
d.exec()
|
|
877
|
-
|
|
878
|
-
def save_file(self):
|
|
879
|
-
# Validation Check (e.g. Z-Matrix Error)
|
|
880
|
-
# We can re-check here or just trust generate_input_content
|
|
881
|
-
|
|
882
|
-
default_name = ""
|
|
883
|
-
if self.filename:
|
|
884
|
-
base, _ = os.path.splitext(self.filename)
|
|
885
|
-
default_name = base + ".inp"
|
|
886
|
-
|
|
887
|
-
file_path, _ = QFileDialog.getSaveFileName(
|
|
888
|
-
self, "Save ORCA Input", default_name, "ORCA Input (*.inp);;All Files (*)"
|
|
889
|
-
)
|
|
890
|
-
|
|
891
|
-
if file_path:
|
|
892
|
-
try:
|
|
893
|
-
content = self.generate_input_content()
|
|
894
|
-
with open(file_path, 'w', encoding='utf-8') as f:
|
|
895
|
-
f.write(content)
|
|
896
|
-
|
|
897
|
-
QMessageBox.information(self, "Success", f"File saved:\n{file_path}")
|
|
898
|
-
self.accept()
|
|
899
|
-
except Exception as e:
|
|
900
|
-
QMessageBox.critical(self, "Error", f"Failed to save file:\n{e}")
|
|
901
|
-
|
|
902
|
-
# --- Preset Management (Similar to Gaussian Neo) ---
|
|
903
|
-
def load_presets_from_file(self):
|
|
904
|
-
self.presets_data = {}
|
|
905
|
-
if os.path.exists(SETTINGS_FILE):
|
|
906
|
-
try:
|
|
907
|
-
with open(SETTINGS_FILE, 'r', encoding='utf-8') as f:
|
|
908
|
-
self.presets_data = json.load(f)
|
|
909
|
-
except Exception as e:
|
|
910
|
-
print(f"Error loading presets: {e}")
|
|
911
|
-
|
|
912
|
-
if "Default" not in self.presets_data:
|
|
913
|
-
self.presets_data["Default"] = {
|
|
914
|
-
"nproc": 4, "maxcore": 2000,
|
|
915
|
-
"route": "! B3LYP def2-SVP Opt Freq", "adv": "", "adv_post": ""
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
self.update_preset_combo()
|
|
919
|
-
|
|
920
|
-
def update_preset_combo(self):
|
|
921
|
-
current = self.preset_combo.currentText()
|
|
922
|
-
self.preset_combo.blockSignals(True)
|
|
923
|
-
self.preset_combo.clear()
|
|
924
|
-
self.preset_combo.addItems(sorted(self.presets_data.keys()))
|
|
925
|
-
|
|
926
|
-
if current in self.presets_data:
|
|
927
|
-
self.preset_combo.setCurrentText(current)
|
|
928
|
-
elif "Default" in self.presets_data:
|
|
929
|
-
self.preset_combo.setCurrentText("Default")
|
|
930
|
-
|
|
931
|
-
self.preset_combo.blockSignals(False)
|
|
932
|
-
self.apply_selected_preset()
|
|
933
|
-
|
|
934
|
-
def apply_selected_preset(self):
|
|
935
|
-
name = self.preset_combo.currentText()
|
|
936
|
-
if name not in self.presets_data: return
|
|
937
|
-
data = self.presets_data[name]
|
|
938
|
-
|
|
939
|
-
self.nproc_spin.setValue(data.get("nproc", 4))
|
|
940
|
-
self.mem_spin.setValue(data.get("maxcore", 2000))
|
|
941
|
-
self.keywords_edit.setText(data.get("route", "! B3LYP def2-SVP Opt Freq"))
|
|
942
|
-
self.adv_edit.setPlainText(data.get("adv", ""))
|
|
943
|
-
self.post_adv_edit.setPlainText(data.get("adv_post", ""))
|
|
944
|
-
|
|
945
|
-
def save_preset_dialog(self):
|
|
946
|
-
name, ok = QInputDialog.getText(self, "Save Preset", "Preset Name:")
|
|
947
|
-
if ok and name:
|
|
948
|
-
self.presets_data[name] = {
|
|
949
|
-
"nproc": self.nproc_spin.value(),
|
|
950
|
-
"maxcore": self.mem_spin.value(),
|
|
951
|
-
"route": self.keywords_edit.text(),
|
|
952
|
-
"adv": self.adv_edit.toPlainText(),
|
|
953
|
-
"adv_post": self.post_adv_edit.toPlainText()
|
|
954
|
-
}
|
|
955
|
-
self.save_presets_to_file()
|
|
956
|
-
self.update_preset_combo()
|
|
957
|
-
self.preset_combo.setCurrentText(name)
|
|
958
|
-
|
|
959
|
-
def delete_preset(self):
|
|
960
|
-
name = self.preset_combo.currentText()
|
|
961
|
-
if name == "Default":
|
|
962
|
-
QMessageBox.warning(self, "Warning", "Cannot delete Default preset.")
|
|
963
|
-
return
|
|
964
|
-
|
|
965
|
-
confirm = QMessageBox.question(self, "Confirm", f"Delete preset '{name}'?")
|
|
966
|
-
if confirm == QMessageBox.StandardButton.Yes:
|
|
967
|
-
del self.presets_data[name]
|
|
968
|
-
self.save_presets_to_file()
|
|
969
|
-
self.update_preset_combo()
|
|
970
|
-
|
|
971
|
-
def save_presets_to_file(self):
|
|
972
|
-
try:
|
|
973
|
-
with open(SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
|
974
|
-
json.dump(self.presets_data, f, indent=4)
|
|
975
|
-
except Exception as e:
|
|
976
|
-
QMessageBox.warning(self, "Error", f"Failed to save presets: {e}")
|
|
977
|
-
|
|
978
|
-
# --- Charge/Mult Logic ---
|
|
979
|
-
def calc_initial_charge_mult(self):
|
|
980
|
-
if not self.mol: return
|
|
981
|
-
try:
|
|
982
|
-
try: charge = Chem.GetFormalCharge(self.mol)
|
|
983
|
-
except: charge = 0
|
|
984
|
-
|
|
985
|
-
num_radical = sum(atom.GetNumRadicalElectrons() for atom in self.mol.GetAtoms())
|
|
986
|
-
mult = num_radical + 1
|
|
987
|
-
|
|
988
|
-
self.charge_spin.setValue(int(charge))
|
|
989
|
-
self.mult_spin.setValue(int(mult))
|
|
990
|
-
self.validate_charge_mult()
|
|
991
|
-
except Exception: pass
|
|
992
|
-
|
|
993
|
-
def validate_charge_mult(self):
|
|
994
|
-
if not self.mol: return
|
|
995
|
-
try:
|
|
996
|
-
charge = self.charge_spin.value()
|
|
997
|
-
mult = self.mult_spin.value()
|
|
998
|
-
|
|
999
|
-
total_protons = sum(atom.GetAtomicNum() for atom in self.mol.GetAtoms())
|
|
1000
|
-
total_electrons = total_protons - charge
|
|
1001
|
-
|
|
1002
|
-
is_valid = (total_electrons % 2 == 0 and mult % 2 != 0) or \
|
|
1003
|
-
(total_electrons % 2 != 0 and mult % 2 == 0)
|
|
1004
|
-
|
|
1005
|
-
if is_valid:
|
|
1006
|
-
self.charge_spin.setPalette(self.default_palette)
|
|
1007
|
-
self.mult_spin.setPalette(self.default_palette)
|
|
1008
|
-
else:
|
|
1009
|
-
p = self.charge_spin.palette()
|
|
1010
|
-
p.setColor(QPalette.ColorRole.Base, QColor("#FFDDDD"))
|
|
1011
|
-
p.setColor(QPalette.ColorRole.Text, Qt.GlobalColor.red)
|
|
1012
|
-
self.charge_spin.setPalette(p)
|
|
1013
|
-
self.mult_spin.setPalette(p)
|
|
1014
|
-
except: pass
|
|
1015
|
-
|
|
1016
|
-
from PyQt6.QtWidgets import QInputDialog
|
|
1017
|
-
|
|
1018
|
-
def run(mw):
|
|
1019
|
-
mol = getattr(mw, 'current_mol', None)
|
|
1020
|
-
if not mol:
|
|
1021
|
-
QMessageBox.warning(mw, PLUGIN_NAME, "No molecule loaded.")
|
|
1022
|
-
return
|
|
1023
|
-
|
|
1024
|
-
filename = getattr(mw, 'current_file_path', None)
|
|
1025
|
-
dialog = OrcaSetupDialogNeo(parent=mw, mol=mol, filename=filename)
|
|
1026
|
-
dialog.exec()
|
|
1027
|
-
|
|
1028
|
-
# initialize removed as it only registered the menu action
|