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,163 +0,0 @@
1
- import sys
2
- import io
3
- import code
4
- import traceback
5
- from contextlib import redirect_stdout, redirect_stderr
6
- from PyQt6.QtWidgets import (
7
- QDialog, QVBoxLayout, QTextEdit, QLineEdit,
8
- QPushButton, QLabel, QWidget
9
- )
10
- from PyQt6.QtGui import QFont, QColor
11
- from PyQt6.QtCore import Qt
12
- import rdkit.Chem as Chem
13
-
14
- __version__="2025.12.25"
15
- __author__="HiroYokoyama"
16
- PLUGIN_NAME = "Python Console"
17
-
18
- class HistoryLineEdit(QLineEdit):
19
- def __init__(self, parent=None):
20
- super().__init__(parent)
21
- self.history = []
22
- self.history_index = 0
23
-
24
- def append_history(self, text):
25
- if text and (not self.history or self.history[-1] != text):
26
- self.history.append(text)
27
- self.history_index = len(self.history)
28
-
29
- def keyPressEvent(self, event):
30
- if event.key() == Qt.Key.Key_Up:
31
- if self.history_index > 0:
32
- self.history_index -= 1
33
- self.setText(self.history[self.history_index])
34
- elif event.key() == Qt.Key.Key_Down:
35
- if self.history_index < len(self.history) - 1:
36
- self.history_index += 1
37
- self.setText(self.history[self.history_index])
38
- else:
39
- self.history_index = len(self.history)
40
- self.clear()
41
- else:
42
- super().keyPressEvent(event)
43
-
44
- class PythonConsoleDialog(QDialog):
45
- def __init__(self, main_window):
46
- super().__init__(main_window)
47
- self.main_window = main_window
48
- self.setWindowTitle("MoleditPy Python Console")
49
- self.resize(600, 400)
50
-
51
- # UI Setup
52
- layout = QVBoxLayout()
53
-
54
- # Output Area (Log)
55
- self.output_area = QTextEdit()
56
- self.output_area.setReadOnly(True)
57
- self.output_area.setStyleSheet("background-color: #1e1e1e; color: #dcdcdc;")
58
- self.output_area.setFont(QFont("Consolas", 10))
59
- layout.addWidget(self.output_area)
60
-
61
- # Input Area with History
62
- self.input_line = HistoryLineEdit()
63
- self.input_line.setPlaceholderText("Enter Python code...")
64
- self.input_line.setStyleSheet("background-color: #2d2d2d; color: #ffffff; border: 1px solid #3e3e3e;")
65
- self.input_line.setFont(QFont("Consolas", 10))
66
- self.input_line.returnPressed.connect(self.run_code)
67
- layout.addWidget(self.input_line)
68
-
69
- # Help Label
70
- help_text = QLabel("Available vars: 'mw' (MainWindow), 'mol' (current_mol), 'Chem' (rdkit.Chem)")
71
- help_text.setStyleSheet("color: gray; font-size: 10px;")
72
- layout.addWidget(help_text)
73
-
74
- self.setLayout(layout)
75
-
76
- # Initialize execution environment (namespace)
77
- self.local_scope = {
78
- 'mw': self.main_window,
79
- 'Chem': Chem,
80
- 'mol': self._get_best_mol(),
81
- }
82
-
83
- # Initialize Interpreter
84
- self.interpreter = code.InteractiveInterpreter(self.local_scope)
85
-
86
- self.append_output("MoleditPy Console Ready.")
87
- self.append_output(">>> Type commands and press Enter.")
88
-
89
- def _get_best_mol(self):
90
- """Helper to get the most relevant RDKit molecule object.
91
- """
92
- mol = getattr(self.main_window, 'current_mol', None)
93
-
94
- return mol
95
-
96
- def append_output(self, text, color=None):
97
- if color:
98
- self.output_area.append(f"<span style='color: {color};'>{text}</span>")
99
- else:
100
- self.output_area.append(text)
101
-
102
- def run_code(self):
103
- command = self.input_line.text()
104
- if not command:
105
- return
106
-
107
- # Handle History
108
- self.input_line.append_history(command)
109
- self.input_line.clear()
110
-
111
- # Display Input
112
- self.append_output(f">>> {command}", color="#4CAF50")
113
-
114
- # Sync variables
115
- self.local_scope['mol'] = self._get_best_mol()
116
-
117
- if self.local_scope['mol'] is None:
118
- # Optional: warn user if they try to use 'mol' and it's still None
119
- # but only if 'mol' appears in command to avoid spam
120
- if 'mol' in command:
121
- print("Warning: 'mol' is None (no valid 2D or 3D structure found).")
122
-
123
- # Capture Output
124
- stdout_capture = io.StringIO()
125
- stderr_capture = io.StringIO()
126
-
127
- try:
128
- with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture):
129
- # runsource handles compilation and syntax errors.
130
- # It returns True if more input is needed (incomplete code), which we can handle or just report.
131
- more = self.interpreter.runsource(command, "<console>", "single")
132
-
133
- if more:
134
- print("(Incomplete input - multiline not fully supported yet)")
135
- except Exception:
136
- # This catches errors OUTSIDE runsource's internal handling if any
137
- traceback.print_exc(file=stderr_capture)
138
-
139
- # Process Captured Output
140
- out_str = stdout_capture.getvalue()
141
- err_str = stderr_capture.getvalue()
142
-
143
- if out_str:
144
- self.output_area.append(out_str.strip())
145
- if err_str:
146
- self.append_output(err_str.strip(), color="#FF5252")
147
-
148
- # Refresh UI if needed
149
- # If the user modified 'mol', we might want to push it back?
150
- # For now, read-only assumption for simpler integration,
151
- # but if they modify 'mw.data' directly, we might need a refresh.
152
- pass
153
-
154
- # Plugin Entry Point
155
- def run(mw):
156
- if not hasattr(mw, 'python_console_dialog'):
157
- mw.python_console_dialog = PythonConsoleDialog(mw)
158
-
159
- mw.python_console_dialog.show()
160
- mw.python_console_dialog.raise_()
161
- mw.python_console_dialog.activateWindow()
162
-
163
- # initialize removed as it only registered the menu action
@@ -1,244 +0,0 @@
1
- import sys
2
- import requests # API通信に必要 (pip install requests)
3
- from PyQt6.QtWidgets import (
4
- QDialog, QVBoxLayout, QHBoxLayout, QTableWidget,
5
- QTableWidgetItem, QPushButton, QMessageBox, QLabel, QHeaderView,
6
- QAbstractItemView, QApplication, QLineEdit, QComboBox
7
- )
8
- from PyQt6.QtCore import Qt, QPointF
9
- from rdkit import Chem
10
- from rdkit.Chem import AllChem
11
-
12
- __version__="2025.12.25"
13
- __author__="HiroYokoyama"
14
- PLUGIN_NAME = "PubChem Name Resolver"
15
-
16
- class MoleculeResolverDialog(QDialog):
17
- def __init__(self, main_window, parent=None):
18
- super().__init__(parent)
19
- self.main_window = main_window
20
- self.setWindowTitle("PubChem Name Resolver")
21
- self.resize(500, 600)
22
-
23
- # 取得した候補データのリスト
24
- # 各要素は辞書: {'name': str, 'smiles': str, 'formula': str}
25
- self.candidates_data = []
26
-
27
- # 生成されたRDKit分子オブジェクト(一時保存)
28
- self.generated_mol = None
29
-
30
- self.init_ui()
31
-
32
- def init_ui(self):
33
- layout = QVBoxLayout(self)
34
-
35
- # --- 入力エリア ---
36
- input_layout = QHBoxLayout()
37
-
38
- self.combo_type = QComboBox()
39
- self.combo_type.addItems(["Auto (Name/CAS)", "SMILES"])
40
- input_layout.addWidget(self.combo_type)
41
-
42
- self.line_input = QLineEdit()
43
- self.line_input.setPlaceholderText("Enter Name or SMILES...")
44
- self.line_input.returnPressed.connect(self.run_search) # Enterキーで検索
45
- input_layout.addWidget(self.line_input)
46
-
47
- self.btn_search = QPushButton("Search Online")
48
- self.btn_search.clicked.connect(self.run_search)
49
- input_layout.addWidget(self.btn_search)
50
-
51
- layout.addLayout(input_layout)
52
-
53
- # --- 説明ラベル ---
54
- self.lbl_info = QLabel("Enter a chemical identifier and click Search.")
55
- layout.addWidget(self.lbl_info)
56
-
57
- # --- 結果表示用テーブル ---
58
- self.table = QTableWidget()
59
- self.table.setColumnCount(3)
60
- self.table.setHorizontalHeaderLabels(["Name/Synonym", "Formula", "SMILES"])
61
- # ヘッダー調整
62
- header = self.table.horizontalHeader()
63
- header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
64
- header.setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents)
65
- header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
66
-
67
- self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
68
- self.table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
69
- # 選択変更時にロードボタンを有効化するなどの処理を入れるならここ
70
- layout.addWidget(self.table)
71
-
72
- # --- ボタンエリア ---
73
- btn_layout = QHBoxLayout()
74
-
75
- self.btn_load = QPushButton("Load to 2D Editor")
76
- self.btn_load.clicked.connect(self.load_molecule)
77
-
78
- self.btn_close = QPushButton("Close")
79
- self.btn_close.clicked.connect(self.close)
80
-
81
- btn_layout.addWidget(self.btn_load)
82
- btn_layout.addWidget(self.btn_close)
83
- layout.addLayout(btn_layout)
84
-
85
- def run_search(self):
86
- query = self.line_input.text().strip()
87
- if not query:
88
- return
89
-
90
- self.lbl_info.setText("Searching PubChem... please wait.")
91
- self.btn_search.setEnabled(False)
92
- self.table.setRowCount(0)
93
- QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
94
- QApplication.processEvents()
95
-
96
- results = []
97
- error_msg = None
98
- network_error = False
99
-
100
- try:
101
- search_type = self.combo_type.currentText()
102
-
103
- if search_type == "SMILES":
104
- # SMILESの場合は直接リストに追加(検証含む)
105
- mol = Chem.MolFromSmiles(query)
106
- if mol:
107
- results.append({
108
- 'name': 'User Input SMILES',
109
- 'smiles': query,
110
- 'formula': Chem.rdMolDescriptors.CalcMolFormula(mol)
111
- })
112
- else:
113
- raise ValueError("Invalid SMILES string.")
114
-
115
- else: # Auto (PubChem API)
116
- # PUG REST APIを使用して検索
117
- # プロパティとしてSMILESと分子式を取得
118
- # CanonicalSMILESも取得してフォールバックに使用
119
- url = f"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/{query}/property/IsomericSMILES,CanonicalSMILES,MolecularFormula,Title/JSON"
120
-
121
- response = requests.get(url, timeout=10)
122
-
123
- if response.status_code == 200:
124
- data = response.json()
125
- props = data.get('PropertyTable', {}).get('Properties', [])
126
- for p in props:
127
- # SMILESの取得(Isomericを優先、なければCanonical、それでもなければキー検索)
128
- smiles = p.get('IsomericSMILES')
129
- if not smiles:
130
- smiles = p.get('CanonicalSMILES')
131
-
132
- # まだ取得できていない場合、キー名に'SMILES'が含まれるものを探す
133
- if not smiles:
134
- for k in p.keys():
135
- if 'SMILES' in k:
136
- smiles = p[k]
137
- if smiles:
138
- break
139
-
140
- if not smiles:
141
- smiles = "" # 見つからない場合
142
-
143
- results.append({
144
- 'name': p.get('Title', query),
145
- 'smiles': smiles,
146
- 'formula': p.get('MolecularFormula', '')
147
- })
148
- else:
149
- # found nothing or error status
150
- pass
151
-
152
- except requests.exceptions.RequestException:
153
- network_error = True
154
- except Exception as e:
155
- error_msg = str(e)
156
- finally:
157
- QApplication.restoreOverrideCursor()
158
- self.btn_search.setEnabled(True)
159
-
160
- # UI updates after cursor restore
161
- if network_error:
162
- QMessageBox.critical(self, PLUGIN_NAME, "Network error. Please check your internet connection.")
163
- self.lbl_info.setText("Network error.")
164
- elif error_msg:
165
- QMessageBox.critical(self, PLUGIN_NAME, f"Error: {error_msg}")
166
- self.lbl_info.setText("Error occurred.")
167
- elif not results and 'response' in locals() and response.status_code != 200:
168
- self.lbl_info.setText("Not found in PubChem search.")
169
- else:
170
- # データを保持
171
- self.candidates_data = results
172
- self.update_table()
173
-
174
- if results:
175
- self.lbl_info.setText(f"Found {len(results)} candidates. Select one and click Load to 2D Editor.")
176
- else:
177
- self.lbl_info.setText("No results found.")
178
-
179
- def update_table(self):
180
- self.table.setRowCount(0)
181
-
182
- for i, data in enumerate(self.candidates_data):
183
- row_idx = self.table.rowCount()
184
- self.table.insertRow(row_idx)
185
-
186
- self.table.setItem(row_idx, 0, QTableWidgetItem(str(data['name'])))
187
- self.table.setItem(row_idx, 1, QTableWidgetItem(str(data['formula'])))
188
- self.table.setItem(row_idx, 2, QTableWidgetItem(str(data['smiles'])))
189
-
190
- def load_molecule(self):
191
- """選択された行のSMILESから2D構造を生成し、メインウィンドウのエディタに入れる"""
192
- selected_items = self.table.selectedItems()
193
- if not selected_items:
194
- QMessageBox.warning(self, PLUGIN_NAME, "Please select a molecule from the list.")
195
- return
196
-
197
- row = selected_items[0].row()
198
- smiles = self.candidates_data[row]['smiles']
199
- name = self.candidates_data[row]['name']
200
-
201
- if not smiles:
202
- QMessageBox.warning(self, PLUGIN_NAME, "No SMILES data available for this entry.")
203
- return
204
-
205
- self.lbl_info.setText("Loading into 2D Editor...")
206
- QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
207
- QApplication.processEvents()
208
-
209
- success = False
210
- error_msg = None
211
-
212
- try:
213
- # メインウィンドウのSMILES読み込み機能を使用
214
- if hasattr(self.main_window, "load_from_smiles"):
215
- self.main_window.load_from_smiles(smiles)
216
- success = True
217
- else:
218
- error_msg = "Main window does not support 'load_from_smiles'."
219
-
220
- except Exception as e:
221
- error_msg = str(e)
222
-
223
- finally:
224
- QApplication.restoreOverrideCursor()
225
-
226
- if success:
227
- self.lbl_info.setText(f"Loaded: {name}")
228
- QMessageBox.information(self, PLUGIN_NAME, f"Successfully loaded: {name}")
229
- self.accept() # ダイアログを閉じる
230
- elif error_msg:
231
- QMessageBox.critical(self, PLUGIN_NAME, f"Error: {error_msg}")
232
- self.lbl_info.setText("Load failed.")
233
-
234
- def run(mw):
235
- if hasattr(mw, "_molecule_resolver_dialog") and mw._molecule_resolver_dialog.isVisible():
236
- mw._molecule_resolver_dialog.raise_()
237
- mw._molecule_resolver_dialog.activateWindow()
238
- return
239
-
240
- dialog = MoleculeResolverDialog(mw, parent=mw)
241
- mw._molecule_resolver_dialog = dialog
242
- dialog.show()
243
-
244
- # initialize removed as it only registered the menu action