MoleditPy 2.2.0a0__py3-none-any.whl → 2.2.0a1__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 +73 -103
- moleditpy/modules/plugin_manager.py +10 -0
- moleditpy/plugins/Analysis/ms_spectrum_neo.py +919 -0
- moleditpy/plugins/File/animated_xyz_giffer.py +583 -0
- moleditpy/plugins/File/cube_viewer.py +689 -0
- moleditpy/plugins/File/gaussian_fchk_freq_analyzer.py +1148 -0
- moleditpy/plugins/File/mapped_cube_viewer.py +552 -0
- moleditpy/plugins/File/orca_out_freq_analyzer.py +1226 -0
- moleditpy/plugins/File/paste_xyz.py +336 -0
- moleditpy/plugins/Input Generator/gaussian_input_generator_neo.py +930 -0
- moleditpy/plugins/Input Generator/orca_input_generator_neo.py +1028 -0
- moleditpy/plugins/Input Generator/orca_xyz2inp_gui.py +286 -0
- moleditpy/plugins/Optimization/all-trans_optimizer.py +65 -0
- moleditpy/plugins/Optimization/complex_molecule_untangler.py +268 -0
- moleditpy/plugins/Optimization/conf_search.py +224 -0
- moleditpy/plugins/Utility/atom_colorizer.py +547 -0
- moleditpy/plugins/Utility/console.py +163 -0
- moleditpy/plugins/Utility/pubchem_ressolver.py +244 -0
- moleditpy/plugins/Utility/vdw_radii_overlay.py +303 -0
- {moleditpy-2.2.0a0.dist-info → moleditpy-2.2.0a1.dist-info}/METADATA +1 -1
- {moleditpy-2.2.0a0.dist-info → moleditpy-2.2.0a1.dist-info}/RECORD +26 -9
- {moleditpy-2.2.0a0.dist-info → moleditpy-2.2.0a1.dist-info}/WHEEL +0 -0
- {moleditpy-2.2.0a0.dist-info → moleditpy-2.2.0a1.dist-info}/entry_points.txt +0 -0
- {moleditpy-2.2.0a0.dist-info → moleditpy-2.2.0a1.dist-info}/licenses/LICENSE +0 -0
- {moleditpy-2.2.0a0.dist-info → moleditpy-2.2.0a1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
from PyQt6.QtWidgets import (
|
|
2
|
+
QDialog, QVBoxLayout, QHBoxLayout, QTableWidget,
|
|
3
|
+
QTableWidgetItem, QPushButton, QMessageBox, QLabel, QHeaderView, QAbstractItemView,
|
|
4
|
+
QApplication, QComboBox
|
|
5
|
+
)
|
|
6
|
+
from PyQt6.QtCore import Qt
|
|
7
|
+
from rdkit import Chem
|
|
8
|
+
from rdkit.Chem import AllChem
|
|
9
|
+
import copy
|
|
10
|
+
|
|
11
|
+
PLUGIN_NAME = "Conformational Search"
|
|
12
|
+
__version__="2025.12.25"
|
|
13
|
+
__author__="HiroYokoyama"
|
|
14
|
+
|
|
15
|
+
class ConformerSearchDialog(QDialog):
|
|
16
|
+
def __init__(self, main_window, parent=None):
|
|
17
|
+
super().__init__(parent)
|
|
18
|
+
self.main_window = main_window
|
|
19
|
+
self.setWindowTitle("Conformational Search & Preview")
|
|
20
|
+
self.resize(400, 500)
|
|
21
|
+
|
|
22
|
+
# メインウィンドウの分子への参照
|
|
23
|
+
self.target_mol = getattr(main_window, "current_mol", None)
|
|
24
|
+
|
|
25
|
+
# 計算用の一時的な分子(オリジナルを汚染しないため)
|
|
26
|
+
self.temp_mol = None
|
|
27
|
+
# 生成された配座データのリスト [(Energy, ConformerID), ...]
|
|
28
|
+
self.conformer_data = []
|
|
29
|
+
|
|
30
|
+
self.init_ui()
|
|
31
|
+
|
|
32
|
+
def init_ui(self):
|
|
33
|
+
layout = QVBoxLayout(self)
|
|
34
|
+
|
|
35
|
+
# 説明ラベル
|
|
36
|
+
self.lbl_info = QLabel("Click 'Run Search' to generate conformers.\nSelect a row to preview.")
|
|
37
|
+
layout.addWidget(self.lbl_info)
|
|
38
|
+
|
|
39
|
+
# Force Field Selection
|
|
40
|
+
hbox_ff = QHBoxLayout()
|
|
41
|
+
hbox_ff.addWidget(QLabel("Force Field:"))
|
|
42
|
+
self.combo_ff = QComboBox()
|
|
43
|
+
self.combo_ff.addItems(["MMFF94", "UFF"])
|
|
44
|
+
hbox_ff.addWidget(self.combo_ff)
|
|
45
|
+
hbox_ff.addStretch()
|
|
46
|
+
layout.addLayout(hbox_ff)
|
|
47
|
+
|
|
48
|
+
# Set default based on main window setting
|
|
49
|
+
default_method = getattr(self.main_window, "optimization_method", "MMFF_RDKIT")
|
|
50
|
+
if default_method:
|
|
51
|
+
default_method = default_method.upper()
|
|
52
|
+
if "UFF" in default_method:
|
|
53
|
+
self.combo_ff.setCurrentText("UFF")
|
|
54
|
+
else:
|
|
55
|
+
self.combo_ff.setCurrentText("MMFF94")
|
|
56
|
+
|
|
57
|
+
# 結果表示用テーブル
|
|
58
|
+
self.table = QTableWidget()
|
|
59
|
+
self.table.setColumnCount(2)
|
|
60
|
+
self.table.setHorizontalHeaderLabels(["Rank", "Energy (kcal/mol)"])
|
|
61
|
+
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
|
62
|
+
self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
|
63
|
+
self.table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
|
64
|
+
self.table.itemClicked.connect(self.preview_conformer)
|
|
65
|
+
layout.addWidget(self.table)
|
|
66
|
+
|
|
67
|
+
# ボタンエリア
|
|
68
|
+
btn_layout = QHBoxLayout()
|
|
69
|
+
self.btn_run = QPushButton("Run Search")
|
|
70
|
+
self.btn_run.clicked.connect(self.run_search)
|
|
71
|
+
|
|
72
|
+
self.btn_close = QPushButton("Close")
|
|
73
|
+
self.btn_close.clicked.connect(self.accept) # 閉じる(現在のプレビュー状態で確定)
|
|
74
|
+
|
|
75
|
+
btn_layout.addWidget(self.btn_run)
|
|
76
|
+
btn_layout.addWidget(self.btn_close)
|
|
77
|
+
layout.addLayout(btn_layout)
|
|
78
|
+
|
|
79
|
+
def accept(self):
|
|
80
|
+
# Push undo state when closing the dialog (confirming the selection)
|
|
81
|
+
if hasattr(self.main_window, "push_undo_state"):
|
|
82
|
+
self.main_window.push_undo_state()
|
|
83
|
+
super().accept()
|
|
84
|
+
|
|
85
|
+
def run_search(self):
|
|
86
|
+
if not self.target_mol:
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
self.btn_run.setEnabled(False)
|
|
90
|
+
self.lbl_info.setText("Running conformational search... please wait.")
|
|
91
|
+
QApplication.processEvents()
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
# 計算用に分子を複製(水素が付加されていることを推奨)
|
|
95
|
+
mol_calc = copy.deepcopy(self.target_mol)
|
|
96
|
+
|
|
97
|
+
# 1. 配座生成 (ETKDGv3)
|
|
98
|
+
params = AllChem.ETKDGv3()
|
|
99
|
+
params.useSmallRingTorsions = True
|
|
100
|
+
cids = AllChem.EmbedMultipleConfs(mol_calc, numConfs=30, params=params)
|
|
101
|
+
|
|
102
|
+
if not cids:
|
|
103
|
+
QMessageBox.warning(self, PLUGIN_NAME, "Failed to generate conformers.")
|
|
104
|
+
self.lbl_info.setText("Failed.")
|
|
105
|
+
self.btn_run.setEnabled(True)
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
# 2. 構造最適化とエネルギー計算
|
|
109
|
+
results = []
|
|
110
|
+
selected_ff = self.combo_ff.currentText()
|
|
111
|
+
|
|
112
|
+
for i, cid in enumerate(cids):
|
|
113
|
+
energy = None
|
|
114
|
+
|
|
115
|
+
if selected_ff == "MMFF94":
|
|
116
|
+
# MMFF94 Optimize
|
|
117
|
+
if AllChem.MMFFOptimizeMolecule(mol_calc, confId=cid) != -1:
|
|
118
|
+
# Calculate Energy
|
|
119
|
+
prop = AllChem.MMFFGetMoleculeProperties(mol_calc)
|
|
120
|
+
if prop:
|
|
121
|
+
ff = AllChem.MMFFGetMoleculeForceField(mol_calc, prop, confId=cid)
|
|
122
|
+
if ff:
|
|
123
|
+
energy = ff.CalcEnergy()
|
|
124
|
+
|
|
125
|
+
elif selected_ff == "UFF":
|
|
126
|
+
# UFF Optimize
|
|
127
|
+
if AllChem.UFFOptimizeMolecule(mol_calc, confId=cid) != -1:
|
|
128
|
+
# Calculate Energy
|
|
129
|
+
ff = AllChem.UFFGetMoleculeForceField(mol_calc, confId=cid)
|
|
130
|
+
if ff:
|
|
131
|
+
energy = ff.CalcEnergy()
|
|
132
|
+
|
|
133
|
+
if energy is not None:
|
|
134
|
+
results.append((energy, cid))
|
|
135
|
+
|
|
136
|
+
# UIの応答性を維持
|
|
137
|
+
if i % 5 == 0:
|
|
138
|
+
QApplication.processEvents()
|
|
139
|
+
|
|
140
|
+
if not results:
|
|
141
|
+
QMessageBox.warning(self, PLUGIN_NAME, f"Optimization failed with {selected_ff}.")
|
|
142
|
+
self.btn_run.setEnabled(True)
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
# エネルギーが低い順にソート
|
|
146
|
+
results.sort(key=lambda x: x[0])
|
|
147
|
+
|
|
148
|
+
# データを保持
|
|
149
|
+
self.temp_mol = mol_calc
|
|
150
|
+
self.conformer_data = results
|
|
151
|
+
|
|
152
|
+
# テーブル更新
|
|
153
|
+
self.update_table()
|
|
154
|
+
self.lbl_info.setText(f"Found {len(results)} conformers ({selected_ff}).")
|
|
155
|
+
|
|
156
|
+
except Exception as e:
|
|
157
|
+
QMessageBox.critical(self, PLUGIN_NAME, f"Error during search: {str(e)}")
|
|
158
|
+
self.lbl_info.setText("Error occurred.")
|
|
159
|
+
finally:
|
|
160
|
+
self.btn_run.setEnabled(True)
|
|
161
|
+
|
|
162
|
+
def update_table(self):
|
|
163
|
+
self.table.setRowCount(0)
|
|
164
|
+
# base_energy = self.conformer_data[0][0] if self.conformer_data else 0
|
|
165
|
+
|
|
166
|
+
for rank, (energy, cid) in enumerate(self.conformer_data):
|
|
167
|
+
row_idx = self.table.rowCount()
|
|
168
|
+
self.table.insertRow(row_idx)
|
|
169
|
+
|
|
170
|
+
# Rank
|
|
171
|
+
self.table.setItem(row_idx, 0, QTableWidgetItem(str(rank + 1)))
|
|
172
|
+
|
|
173
|
+
# Energy
|
|
174
|
+
energy_str = f"{energy:.4f}"
|
|
175
|
+
self.table.setItem(row_idx, 1, QTableWidgetItem(energy_str))
|
|
176
|
+
|
|
177
|
+
# 隠しデータとしてConformer IDを持たせる
|
|
178
|
+
self.table.item(row_idx, 0).setData(Qt.ItemDataRole.UserRole, cid)
|
|
179
|
+
|
|
180
|
+
def preview_conformer(self, item):
|
|
181
|
+
"""リスト選択時にメインウィンドウの表示を更新"""
|
|
182
|
+
if not self.temp_mol or not self.target_mol:
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
row = item.row()
|
|
186
|
+
# Rankカラム(0)にCIDを埋め込んでいるので取得
|
|
187
|
+
cid = self.table.item(row, 0).data(Qt.ItemDataRole.UserRole)
|
|
188
|
+
|
|
189
|
+
# 選択された配座の座標を取得
|
|
190
|
+
source_conf = self.temp_mol.GetConformer(cid)
|
|
191
|
+
target_conf = self.target_mol.GetConformer() # 現在の表示用Conformer
|
|
192
|
+
|
|
193
|
+
# 座標のコピー
|
|
194
|
+
for i in range(self.target_mol.GetNumAtoms()):
|
|
195
|
+
pos = source_conf.GetAtomPosition(i)
|
|
196
|
+
target_conf.SetAtomPosition(i, pos)
|
|
197
|
+
|
|
198
|
+
# ビューの更新(ユーザー提供コードのロジックに従う)
|
|
199
|
+
if hasattr(self.main_window, "draw_molecule_3d"):
|
|
200
|
+
self.main_window.draw_molecule_3d(self.target_mol)
|
|
201
|
+
elif hasattr(self.main_window, "update_view"):
|
|
202
|
+
self.main_window.update_view()
|
|
203
|
+
elif hasattr(self.main_window, "gl_widget"):
|
|
204
|
+
# GLWidgetのリフレッシュ
|
|
205
|
+
getattr(self.main_window.gl_widget, "update", lambda: None)()
|
|
206
|
+
|
|
207
|
+
def run(mw):
|
|
208
|
+
mol = getattr(mw, "current_mol", None)
|
|
209
|
+
if not mol:
|
|
210
|
+
QMessageBox.warning(mw, PLUGIN_NAME, "No molecule loaded.")
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
# 既存のダイアログがあればアクティブにする
|
|
214
|
+
if hasattr(mw, "_conformer_search_dialog") and mw._conformer_search_dialog.isVisible():
|
|
215
|
+
mw._conformer_search_dialog.raise_()
|
|
216
|
+
mw._conformer_search_dialog.activateWindow()
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
dialog = ConformerSearchDialog(mw, parent=mw)
|
|
220
|
+
# 参照を保持してGCを防ぐ
|
|
221
|
+
mw._conformer_search_dialog = dialog
|
|
222
|
+
dialog.show() # モーダルではなくModeless(非ブロック)で表示
|
|
223
|
+
|
|
224
|
+
# initialize removed as it only registered the menu action
|