moleditpy 1.2.6__tar.gz → 1.3.1__tar.gz
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-1.2.6 → moleditpy-1.3.1}/PKG-INFO +12 -3
- {moleditpy-1.2.6 → moleditpy-1.3.1}/README.md +8 -1
- {moleditpy-1.2.6 → moleditpy-1.3.1}/pyproject.toml +4 -2
- {moleditpy-1.2.6 → moleditpy-1.3.1}/src/moleditpy/main.py +216 -35
- {moleditpy-1.2.6 → moleditpy-1.3.1}/src/moleditpy.egg-info/PKG-INFO +12 -3
- {moleditpy-1.2.6 → moleditpy-1.3.1}/setup.cfg +0 -0
- {moleditpy-1.2.6 → moleditpy-1.3.1}/src/moleditpy/__init__.py +0 -0
- {moleditpy-1.2.6 → moleditpy-1.3.1}/src/moleditpy/assets/icon.ico +0 -0
- {moleditpy-1.2.6 → moleditpy-1.3.1}/src/moleditpy/assets/icon.png +0 -0
- {moleditpy-1.2.6 → moleditpy-1.3.1}/src/moleditpy.egg-info/SOURCES.txt +0 -0
- {moleditpy-1.2.6 → moleditpy-1.3.1}/src/moleditpy.egg-info/dependency_links.txt +0 -0
- {moleditpy-1.2.6 → moleditpy-1.3.1}/src/moleditpy.egg-info/entry_points.txt +0 -0
- {moleditpy-1.2.6 → moleditpy-1.3.1}/src/moleditpy.egg-info/requires.txt +0 -0
- {moleditpy-1.2.6 → moleditpy-1.3.1}/src/moleditpy.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: moleditpy
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: A cross-platform, simple, and intuitive molecular structure editor built in Python. It allows 2D molecular drawing and 3D structure visualization. It supports exporting structure files for input to DFT calculation software.
|
|
5
5
|
Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/HiroYokoyama/python_molecular_editor
|
|
@@ -13,8 +13,10 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.9
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
18
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
-
Requires-Python: >=3.
|
|
19
|
+
Requires-Python: >=3.8
|
|
18
20
|
Description-Content-Type: text/markdown
|
|
19
21
|
Requires-Dist: PyQt6
|
|
20
22
|
Requires-Dist: rdkit
|
|
@@ -34,7 +36,6 @@ A cross-platform, simple, and intuitive molecular structure editor built in Pyth
|
|
|
34
36
|
**License**: Apache-2.0
|
|
35
37
|
**Repository**: [https://github.com/HiroYokoyama/python\_molecular\_editor](https://github.com/HiroYokoyama/python_molecular_editor)
|
|
36
38
|
|
|
37
|
-
-----
|
|
38
39
|
|
|
39
40
|
Pythonで構築された、クロスプラットフォームかつシンプルで直感的な分子構造エディターです。2Dでの分子描画と3D構造可視化ができます。構造ファイルのエクスポートをサポートし、さらにインタラクティブな3D編集機能で目的の配座を直感的に作成できるため、**DFT計算のインプット作成に最適です**。
|
|
40
41
|
|
|
@@ -169,6 +170,10 @@ This application is a tool for easily drawing molecular structures and visually
|
|
|
169
170
|
|
|
170
171
|
## Execution and Installation
|
|
171
172
|
|
|
173
|
+
For more details, please refer to the [Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki).
|
|
174
|
+
|
|
175
|
+
The [Docker version](https://github.com/HiroYokoyama/python_molecular_editor_docker) is also available.
|
|
176
|
+
|
|
172
177
|
#### Requirements
|
|
173
178
|
|
|
174
179
|
`PyQt6`, `RDKit`, `NumPy`, `PyVista`, `pyvistaqt`, `openbabel`
|
|
@@ -193,6 +198,10 @@ moleditpy
|
|
|
193
198
|
|
|
194
199
|
## 実行とインストール
|
|
195
200
|
|
|
201
|
+
詳細については、[Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki)を確認してください。
|
|
202
|
+
|
|
203
|
+
[Docker版](https://github.com/HiroYokoyama/python_molecular_editor_docker)もあります。
|
|
204
|
+
|
|
196
205
|
#### 必要ライブラリ
|
|
197
206
|
|
|
198
207
|
`PyQt6`, `RDKit`, `NumPy`, `PyVista`, `pyvistaqt`, `openbabel`
|
|
@@ -9,7 +9,6 @@ A cross-platform, simple, and intuitive molecular structure editor built in Pyth
|
|
|
9
9
|
**License**: Apache-2.0
|
|
10
10
|
**Repository**: [https://github.com/HiroYokoyama/python\_molecular\_editor](https://github.com/HiroYokoyama/python_molecular_editor)
|
|
11
11
|
|
|
12
|
-
-----
|
|
13
12
|
|
|
14
13
|
Pythonで構築された、クロスプラットフォームかつシンプルで直感的な分子構造エディターです。2Dでの分子描画と3D構造可視化ができます。構造ファイルのエクスポートをサポートし、さらにインタラクティブな3D編集機能で目的の配座を直感的に作成できるため、**DFT計算のインプット作成に最適です**。
|
|
15
14
|
|
|
@@ -144,6 +143,10 @@ This application is a tool for easily drawing molecular structures and visually
|
|
|
144
143
|
|
|
145
144
|
## Execution and Installation
|
|
146
145
|
|
|
146
|
+
For more details, please refer to the [Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki).
|
|
147
|
+
|
|
148
|
+
The [Docker version](https://github.com/HiroYokoyama/python_molecular_editor_docker) is also available.
|
|
149
|
+
|
|
147
150
|
#### Requirements
|
|
148
151
|
|
|
149
152
|
`PyQt6`, `RDKit`, `NumPy`, `PyVista`, `pyvistaqt`, `openbabel`
|
|
@@ -168,6 +171,10 @@ moleditpy
|
|
|
168
171
|
|
|
169
172
|
## 実行とインストール
|
|
170
173
|
|
|
174
|
+
詳細については、[Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki)を確認してください。
|
|
175
|
+
|
|
176
|
+
[Docker版](https://github.com/HiroYokoyama/python_molecular_editor_docker)もあります。
|
|
177
|
+
|
|
171
178
|
#### 必要ライブラリ
|
|
172
179
|
|
|
173
180
|
`PyQt6`, `RDKit`, `NumPy`, `PyVista`, `pyvistaqt`, `openbabel`
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "moleditpy"
|
|
7
7
|
|
|
8
|
-
version = "1.
|
|
8
|
+
version = "1.3.1"
|
|
9
9
|
|
|
10
10
|
authors = [
|
|
11
11
|
{ name="HiroYokoyama", email="titech.yoko.hiro@gmail.com" },
|
|
@@ -15,7 +15,7 @@ description = "A cross-platform, simple, and intuitive molecular structure edito
|
|
|
15
15
|
|
|
16
16
|
readme = "README.md"
|
|
17
17
|
|
|
18
|
-
requires-python = ">=3.
|
|
18
|
+
requires-python = ">=3.8"
|
|
19
19
|
|
|
20
20
|
classifiers = [
|
|
21
21
|
"Programming Language :: Python :: 3",
|
|
@@ -26,6 +26,8 @@ classifiers = [
|
|
|
26
26
|
"Programming Language :: Python :: 3.9",
|
|
27
27
|
"Programming Language :: Python :: 3.10",
|
|
28
28
|
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Programming Language :: Python :: 3.13",
|
|
29
31
|
"License :: OSI Approved :: Apache Software License"
|
|
30
32
|
]
|
|
31
33
|
|
|
@@ -11,7 +11,7 @@ DOI 10.5281/zenodo.17268532
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
#Version
|
|
14
|
-
VERSION = '1.
|
|
14
|
+
VERSION = '1.3.1'
|
|
15
15
|
|
|
16
16
|
print("-----------------------------------------------------")
|
|
17
17
|
print("MoleditPy — A Python-based molecular editing software")
|
|
@@ -123,10 +123,15 @@ class MolecularData:
|
|
|
123
123
|
return atom_id
|
|
124
124
|
|
|
125
125
|
def add_bond(self, id1, id2, order=1, stereo=0):
|
|
126
|
-
|
|
126
|
+
# 立体結合の場合、IDの順序は方向性を意味するため、ソートしない。
|
|
127
|
+
# 非立体結合の場合は、キーを正規化するためにソートする。
|
|
128
|
+
if stereo == 0:
|
|
129
|
+
if id1 > id2: id1, id2 = id2, id1
|
|
130
|
+
|
|
127
131
|
bond_data = {'order': order, 'stereo': stereo, 'item': None}
|
|
128
132
|
|
|
129
|
-
|
|
133
|
+
# 逆方向のキーも考慮して、新規結合かどうかをチェック
|
|
134
|
+
is_new_bond = (id1, id2) not in self.bonds and (id2, id1) not in self.bonds
|
|
130
135
|
if is_new_bond:
|
|
131
136
|
if id1 in self.adjacency_list and id2 in self.adjacency_list:
|
|
132
137
|
self.adjacency_list[id1].append(id2)
|
|
@@ -139,7 +144,6 @@ class MolecularData:
|
|
|
139
144
|
self.bonds[(id1, id2)] = bond_data
|
|
140
145
|
return (id1, id2), 'created'
|
|
141
146
|
|
|
142
|
-
|
|
143
147
|
def remove_atom(self, atom_id):
|
|
144
148
|
if atom_id in self.atoms:
|
|
145
149
|
# Safely get neighbors before deleting the atom's own entry
|
|
@@ -158,13 +162,19 @@ class MolecularData:
|
|
|
158
162
|
del self.bonds[key]
|
|
159
163
|
|
|
160
164
|
def remove_bond(self, id1, id2):
|
|
161
|
-
|
|
165
|
+
# 方向性のある立体結合(順方向/逆方向)と、正規化された非立体結合のキーを探す
|
|
166
|
+
key_to_remove = None
|
|
162
167
|
if (id1, id2) in self.bonds:
|
|
168
|
+
key_to_remove = (id1, id2)
|
|
169
|
+
elif (id2, id1) in self.bonds:
|
|
170
|
+
key_to_remove = (id2, id1)
|
|
171
|
+
|
|
172
|
+
if key_to_remove:
|
|
163
173
|
if id1 in self.adjacency_list and id2 in self.adjacency_list[id1]:
|
|
164
174
|
self.adjacency_list[id1].remove(id2)
|
|
165
175
|
if id2 in self.adjacency_list and id1 in self.adjacency_list[id2]:
|
|
166
176
|
self.adjacency_list[id2].remove(id1)
|
|
167
|
-
del self.bonds[
|
|
177
|
+
del self.bonds[key_to_remove]
|
|
168
178
|
|
|
169
179
|
def to_mol_block(self):
|
|
170
180
|
try:
|
|
@@ -245,11 +255,9 @@ class MolecularData:
|
|
|
245
255
|
elif bond_data['stereo'] == 2: # Dash
|
|
246
256
|
bond.SetBondDir(Chem.BondDir.BEGINDASH)
|
|
247
257
|
|
|
258
|
+
mol.UpdatePropertyCache(strict=False)
|
|
259
|
+
|
|
248
260
|
mol = mol.GetMol()
|
|
249
|
-
try:
|
|
250
|
-
Chem.SanitizeMol(mol)
|
|
251
|
-
except Exception:
|
|
252
|
-
pass
|
|
253
261
|
return mol
|
|
254
262
|
|
|
255
263
|
class AtomItem(QGraphicsItem):
|
|
@@ -271,7 +279,10 @@ class AtomItem(QGraphicsItem):
|
|
|
271
279
|
|
|
272
280
|
hydrogen_part = ""
|
|
273
281
|
if self.implicit_h_count > 0:
|
|
274
|
-
is_skeletal_carbon = (self.symbol == 'C' and
|
|
282
|
+
is_skeletal_carbon = (self.symbol == 'C' and
|
|
283
|
+
self.charge == 0 and
|
|
284
|
+
self.radical == 0 and
|
|
285
|
+
len(self.bonds) > 0)
|
|
275
286
|
if not is_skeletal_carbon:
|
|
276
287
|
hydrogen_part = "H"
|
|
277
288
|
if self.implicit_h_count > 1:
|
|
@@ -364,7 +375,10 @@ class AtomItem(QGraphicsItem):
|
|
|
364
375
|
# --- 水素部分のテキストを作成 ---
|
|
365
376
|
hydrogen_part = ""
|
|
366
377
|
if self.implicit_h_count > 0:
|
|
367
|
-
is_skeletal_carbon = (self.symbol == 'C' and
|
|
378
|
+
is_skeletal_carbon = (self.symbol == 'C' and
|
|
379
|
+
self.charge == 0 and
|
|
380
|
+
self.radical == 0 and
|
|
381
|
+
len(self.bonds) > 0)
|
|
368
382
|
if not is_skeletal_carbon:
|
|
369
383
|
hydrogen_part = "H"
|
|
370
384
|
if self.implicit_h_count > 1:
|
|
@@ -738,8 +752,8 @@ class MoleculeScene(QGraphicsScene):
|
|
|
738
752
|
self.data.atoms[item.atom_id]['charge'] = 0
|
|
739
753
|
item.update_style()
|
|
740
754
|
data_changed = True
|
|
741
|
-
#
|
|
742
|
-
elif not self.mode.startswith('template'):
|
|
755
|
+
# 上記以外のモード(テンプレート、電荷、ラジカルを除く)では原子を削除
|
|
756
|
+
elif not self.mode.startswith(('template', 'charge', 'radical')):
|
|
743
757
|
data_changed = self.delete_items({item})
|
|
744
758
|
|
|
745
759
|
elif isinstance(item, BondItem):
|
|
@@ -845,6 +859,9 @@ class MoleculeScene(QGraphicsScene):
|
|
|
845
859
|
self.data.atoms[atom.atom_id]['radical'] = atom.radical
|
|
846
860
|
atom.update_style()
|
|
847
861
|
self.data_changed_in_event = True
|
|
862
|
+
self.start_atom=None; self.start_pos = None; self.press_pos = None
|
|
863
|
+
if self.data_changed_in_event: self.window.push_undo_state()
|
|
864
|
+
return
|
|
848
865
|
elif (self.mode == 'charge_plus' or self.mode == 'charge_minus') and is_click and isinstance(released_item, AtomItem):
|
|
849
866
|
atom = released_item
|
|
850
867
|
atom.prepareGeometryChange()
|
|
@@ -853,18 +870,44 @@ class MoleculeScene(QGraphicsScene):
|
|
|
853
870
|
self.data.atoms[atom.atom_id]['charge'] = atom.charge
|
|
854
871
|
atom.update_style()
|
|
855
872
|
self.data_changed_in_event = True
|
|
873
|
+
self.start_atom=None; self.start_pos = None; self.press_pos = None
|
|
874
|
+
if self.data_changed_in_event: self.window.push_undo_state()
|
|
875
|
+
return
|
|
856
876
|
|
|
857
|
-
# 2. 既存結合の様式更新処理 (全ての結合モードで動作)
|
|
858
877
|
elif self.mode.startswith('bond') and is_click and isinstance(released_item, BondItem):
|
|
859
|
-
b = released_item
|
|
860
|
-
|
|
861
|
-
b.order
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
878
|
+
b = released_item
|
|
879
|
+
|
|
880
|
+
if self.bond_stereo != 0 and b.order == self.bond_order and b.stereo == self.bond_stereo:
|
|
881
|
+
# 方向性を反転させる
|
|
882
|
+
old_id1, old_id2 = b.atom1.atom_id, b.atom2.atom_id
|
|
883
|
+
|
|
884
|
+
# 1. 古い方向の結合をデータから削除
|
|
885
|
+
self.data.remove_bond(old_id1, old_id2)
|
|
886
|
+
|
|
887
|
+
# 2. 逆方向で結合をデータに再追加
|
|
888
|
+
new_key, _ = self.data.add_bond(old_id2, old_id1, self.bond_order, self.bond_stereo)
|
|
889
|
+
|
|
890
|
+
# 3. BondItemの原子参照を入れ替え、新しいデータと関連付ける
|
|
891
|
+
b.atom1, b.atom2 = b.atom2, b.atom1
|
|
892
|
+
self.data.bonds[new_key]['item'] = b
|
|
893
|
+
|
|
894
|
+
# 4. 見た目を更新
|
|
895
|
+
b.update_position()
|
|
896
|
+
|
|
897
|
+
else:
|
|
898
|
+
# 既存の結合を一度削除
|
|
899
|
+
self.data.remove_bond(b.atom1.atom_id, b.atom2.atom_id)
|
|
900
|
+
|
|
901
|
+
# BondItemが記憶している方向(b.atom1 -> b.atom2)で、新しい結合様式を再作成
|
|
902
|
+
# これにより、修正済みのadd_bondが呼ばれ、正しい方向で保存される
|
|
903
|
+
new_key, _ = self.data.add_bond(b.atom1.atom_id, b.atom2.atom_id, self.bond_order, self.bond_stereo)
|
|
904
|
+
|
|
905
|
+
# BondItemの見た目とデータ参照を更新
|
|
906
|
+
b.order = self.bond_order
|
|
907
|
+
b.stereo = self.bond_stereo
|
|
908
|
+
self.data.bonds[new_key]['item'] = b
|
|
909
|
+
b.update()
|
|
910
|
+
|
|
868
911
|
self.clearSelection()
|
|
869
912
|
self.data_changed_in_event = True
|
|
870
913
|
|
|
@@ -940,6 +983,30 @@ class MoleculeScene(QGraphicsScene):
|
|
|
940
983
|
self.template_context = {}
|
|
941
984
|
if self.data_changed_in_event: self.window.push_undo_state()
|
|
942
985
|
|
|
986
|
+
def mouseDoubleClickEvent(self, event):
|
|
987
|
+
"""ダブルクリックイベントを処理する"""
|
|
988
|
+
item = self.itemAt(event.scenePos(), self.views()[0].transform())
|
|
989
|
+
|
|
990
|
+
if self.mode in ['charge_plus', 'charge_minus', 'radical'] and isinstance(item, AtomItem):
|
|
991
|
+
if self.mode == 'radical':
|
|
992
|
+
item.prepareGeometryChange()
|
|
993
|
+
item.radical = (item.radical + 1) % 3
|
|
994
|
+
self.data.atoms[item.atom_id]['radical'] = item.radical
|
|
995
|
+
item.update_style()
|
|
996
|
+
else:
|
|
997
|
+
item.prepareGeometryChange()
|
|
998
|
+
delta = 1 if self.mode == 'charge_plus' else -1
|
|
999
|
+
item.charge += delta
|
|
1000
|
+
self.data.atoms[item.atom_id]['charge'] = item.charge
|
|
1001
|
+
item.update_style()
|
|
1002
|
+
|
|
1003
|
+
self.window.push_undo_state()
|
|
1004
|
+
|
|
1005
|
+
event.accept()
|
|
1006
|
+
return
|
|
1007
|
+
|
|
1008
|
+
super().mouseDoubleClickEvent(event)
|
|
1009
|
+
|
|
943
1010
|
def create_atom(self, symbol, pos, charge=0, radical=0):
|
|
944
1011
|
atom_id = self.data.add_atom(symbol, pos, charge=charge, radical=radical)
|
|
945
1012
|
atom_item = AtomItem(atom_id, symbol, pos, charge=charge, radical=radical)
|
|
@@ -2199,9 +2266,23 @@ class MainWindow(QMainWindow):
|
|
|
2199
2266
|
self.init_ui()
|
|
2200
2267
|
self.init_worker_thread()
|
|
2201
2268
|
self._setup_3d_picker()
|
|
2269
|
+
|
|
2270
|
+
# --- RDKit初回実行コストの事前読み込み(ウォームアップ)---
|
|
2271
|
+
try:
|
|
2272
|
+
# Create a molecule with a variety of common atoms to ensure
|
|
2273
|
+
# the valence/H-count machinery is fully initialized.
|
|
2274
|
+
warmup_smiles = "OC(N)C(S)P"
|
|
2275
|
+
warmup_mol = Chem.MolFromSmiles(warmup_smiles)
|
|
2276
|
+
if warmup_mol:
|
|
2277
|
+
for atom in warmup_mol.GetAtoms():
|
|
2278
|
+
atom.GetNumImplicitHs()
|
|
2279
|
+
except Exception as e:
|
|
2280
|
+
print(f"RDKit warm-up failed: {e}")
|
|
2281
|
+
|
|
2202
2282
|
self.reset_undo_stack()
|
|
2203
2283
|
self.scene.selectionChanged.connect(self.update_edit_menu_actions)
|
|
2204
2284
|
QApplication.clipboard().dataChanged.connect(self.update_edit_menu_actions)
|
|
2285
|
+
|
|
2205
2286
|
self.update_edit_menu_actions()
|
|
2206
2287
|
|
|
2207
2288
|
if initial_file:
|
|
@@ -2550,11 +2631,14 @@ class MainWindow(QMainWindow):
|
|
|
2550
2631
|
menu_bar = self.menuBar()
|
|
2551
2632
|
|
|
2552
2633
|
file_menu = menu_bar.addMenu("&File")
|
|
2634
|
+
load_mol_action = QAction("Import MOL...", self); load_mol_action.triggered.connect(self.load_mol_file)
|
|
2635
|
+
file_menu.addAction(load_mol_action)
|
|
2553
2636
|
import_smiles_action = QAction("Import SMILES...", self)
|
|
2554
2637
|
import_smiles_action.triggered.connect(self.import_smiles_dialog)
|
|
2555
2638
|
file_menu.addAction(import_smiles_action)
|
|
2556
|
-
|
|
2557
|
-
|
|
2639
|
+
import_inchi_action = QAction("Import InChI...", self)
|
|
2640
|
+
import_inchi_action.triggered.connect(self.import_inchi_dialog)
|
|
2641
|
+
file_menu.addAction(import_inchi_action)
|
|
2558
2642
|
file_menu.addSeparator()
|
|
2559
2643
|
save_mol_action = QAction("Save 2D as MOL...", self); save_mol_action.triggered.connect(self.save_as_mol)
|
|
2560
2644
|
file_menu.addAction(save_mol_action)
|
|
@@ -2929,7 +3013,22 @@ class MainWindow(QMainWindow):
|
|
|
2929
3013
|
|
|
2930
3014
|
mol_block = Chem.MolToMolBlock(mol, includeStereo=True)
|
|
2931
3015
|
self.convert_button.setEnabled(False)
|
|
3016
|
+
self.cleanup_button.setEnabled(False)
|
|
3017
|
+
self.optimize_3d_button.setEnabled(False)
|
|
3018
|
+
self.export_button.setEnabled(False)
|
|
3019
|
+
self.analysis_action.setEnabled(False)
|
|
3020
|
+
self.edit_3d_action.setEnabled(False)
|
|
2932
3021
|
self.statusBar().showMessage("Calculating 3D structure...")
|
|
3022
|
+
self.plotter.clear()
|
|
3023
|
+
text_actor = self.plotter.add_text(
|
|
3024
|
+
"Calculating...",
|
|
3025
|
+
position='lower_left',
|
|
3026
|
+
font_size=15,
|
|
3027
|
+
color='gray',
|
|
3028
|
+
name='calculating_text'
|
|
3029
|
+
)
|
|
3030
|
+
text_actor.GetTextProperty().SetOpacity(0.5)
|
|
3031
|
+
self.plotter.render()
|
|
2933
3032
|
self.start_calculation.emit(mol_block)
|
|
2934
3033
|
|
|
2935
3034
|
self.view_2d.setFocus()
|
|
@@ -2980,14 +3079,17 @@ class MainWindow(QMainWindow):
|
|
|
2980
3079
|
self.analysis_action.setEnabled(True)
|
|
2981
3080
|
self.push_undo_state()
|
|
2982
3081
|
self.view_2d.setFocus()
|
|
3082
|
+
self.cleanup_button.setEnabled(True)
|
|
2983
3083
|
self.optimize_3d_button.setEnabled(True)
|
|
2984
3084
|
self.export_button.setEnabled(True)
|
|
2985
3085
|
self.edit_3d_action.setEnabled(True)
|
|
2986
3086
|
self.plotter.reset_camera()
|
|
2987
3087
|
|
|
2988
3088
|
def on_calculation_error(self, error_message):
|
|
3089
|
+
self.plotter.clear()
|
|
2989
3090
|
self.dragged_atom_info = None
|
|
2990
3091
|
self.statusBar().showMessage(f"Error: {error_message}")
|
|
3092
|
+
self.cleanup_button.setEnabled(True)
|
|
2991
3093
|
self.convert_button.setEnabled(True)
|
|
2992
3094
|
self.analysis_action.setEnabled(False)
|
|
2993
3095
|
self.edit_3d_action.setEnabled(False)
|
|
@@ -3231,8 +3333,6 @@ class MainWindow(QMainWindow):
|
|
|
3231
3333
|
atom_data['item'].implicit_h_count = 0
|
|
3232
3334
|
atom_data['item'].update()
|
|
3233
3335
|
return
|
|
3234
|
-
|
|
3235
|
-
mol.UpdatePropertyCache(strict=False)
|
|
3236
3336
|
|
|
3237
3337
|
items_to_update = []
|
|
3238
3338
|
for atom in mol.GetAtoms():
|
|
@@ -3261,11 +3361,21 @@ class MainWindow(QMainWindow):
|
|
|
3261
3361
|
if ok and smiles:
|
|
3262
3362
|
self.load_from_smiles(smiles)
|
|
3263
3363
|
|
|
3364
|
+
def import_inchi_dialog(self):
|
|
3365
|
+
"""ユーザーにInChI文字列の入力を促すダイアログを表示する"""
|
|
3366
|
+
inchi, ok = QInputDialog.getText(self, "Import InChI", "Enter InChI string:")
|
|
3367
|
+
if ok and inchi:
|
|
3368
|
+
self.load_from_inchi(inchi)
|
|
3369
|
+
|
|
3264
3370
|
def load_from_smiles(self, smiles_string):
|
|
3265
3371
|
"""SMILES文字列から分子を読み込み、2Dエディタに表示する"""
|
|
3266
3372
|
try:
|
|
3267
|
-
|
|
3373
|
+
cleaned_smiles = smiles_string.strip()
|
|
3374
|
+
|
|
3375
|
+
mol = Chem.MolFromSmiles(cleaned_smiles)
|
|
3268
3376
|
if mol is None:
|
|
3377
|
+
if not cleaned_smiles:
|
|
3378
|
+
raise ValueError("SMILES string was empty.")
|
|
3269
3379
|
raise ValueError("Invalid SMILES string.")
|
|
3270
3380
|
|
|
3271
3381
|
AllChem.Compute2DCoords(mol)
|
|
@@ -3326,6 +3436,75 @@ class MainWindow(QMainWindow):
|
|
|
3326
3436
|
except Exception as e:
|
|
3327
3437
|
self.statusBar().showMessage(f"Error loading from SMILES: {e}")
|
|
3328
3438
|
|
|
3439
|
+
def load_from_inchi(self, inchi_string):
|
|
3440
|
+
"""InChI文字列から分子を読み込み、2Dエディタに表示する"""
|
|
3441
|
+
try:
|
|
3442
|
+
cleaned_inchi = inchi_string.strip()
|
|
3443
|
+
|
|
3444
|
+
mol = Chem.MolFromInchi(cleaned_inchi)
|
|
3445
|
+
if mol is None:
|
|
3446
|
+
if not cleaned_inchi:
|
|
3447
|
+
raise ValueError("InChI string was empty.")
|
|
3448
|
+
raise ValueError("Invalid InChI string.")
|
|
3449
|
+
|
|
3450
|
+
AllChem.Compute2DCoords(mol)
|
|
3451
|
+
Chem.Kekulize(mol)
|
|
3452
|
+
|
|
3453
|
+
AllChem.AssignStereochemistry(mol, cleanIt=True, force=True)
|
|
3454
|
+
conf = mol.GetConformer()
|
|
3455
|
+
AllChem.WedgeMolBonds(mol, conf)
|
|
3456
|
+
|
|
3457
|
+
self.clear_2d_editor(push_to_undo=False)
|
|
3458
|
+
self.current_mol = None
|
|
3459
|
+
self.plotter.clear()
|
|
3460
|
+
self.analysis_action.setEnabled(False)
|
|
3461
|
+
|
|
3462
|
+
conf = mol.GetConformer()
|
|
3463
|
+
SCALE_FACTOR = 50.0
|
|
3464
|
+
|
|
3465
|
+
view_center = self.view_2d.mapToScene(self.view_2d.viewport().rect().center())
|
|
3466
|
+
positions = [conf.GetAtomPosition(i) for i in range(mol.GetNumAtoms())]
|
|
3467
|
+
mol_center_x = sum(p.x for p in positions) / len(positions) if positions else 0.0
|
|
3468
|
+
mol_center_y = sum(p.y for p in positions) / len(positions) if positions else 0.0
|
|
3469
|
+
|
|
3470
|
+
rdkit_idx_to_my_id = {}
|
|
3471
|
+
for i in range(mol.GetNumAtoms()):
|
|
3472
|
+
atom = mol.GetAtomWithIdx(i)
|
|
3473
|
+
pos = conf.GetAtomPosition(i)
|
|
3474
|
+
charge = atom.GetFormalCharge()
|
|
3475
|
+
|
|
3476
|
+
relative_x = pos.x - mol_center_x
|
|
3477
|
+
relative_y = pos.y - mol_center_y
|
|
3478
|
+
|
|
3479
|
+
scene_x = (relative_x * SCALE_FACTOR) + view_center.x()
|
|
3480
|
+
scene_y = (-relative_y * SCALE_FACTOR) + view_center.y()
|
|
3481
|
+
|
|
3482
|
+
atom_id = self.scene.create_atom(atom.GetSymbol(), QPointF(scene_x, scene_y), charge=charge)
|
|
3483
|
+
rdkit_idx_to_my_id[i] = atom_id
|
|
3484
|
+
|
|
3485
|
+
for bond in mol.GetBonds():
|
|
3486
|
+
b_idx, e_idx = bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()
|
|
3487
|
+
b_type = bond.GetBondTypeAsDouble()
|
|
3488
|
+
b_dir = bond.GetBondDir()
|
|
3489
|
+
stereo = 0
|
|
3490
|
+
if b_dir == Chem.BondDir.BEGINWEDGE:
|
|
3491
|
+
stereo = 1 # Wedge
|
|
3492
|
+
elif b_dir == Chem.BondDir.BEGINDASH:
|
|
3493
|
+
stereo = 2 # Dash
|
|
3494
|
+
|
|
3495
|
+
if b_idx in rdkit_idx_to_my_id and e_idx in rdkit_idx_to_my_id:
|
|
3496
|
+
a1_id, a2_id = rdkit_idx_to_my_id[b_idx], rdkit_idx_to_my_id[e_idx]
|
|
3497
|
+
a1_item = self.data.atoms[a1_id]['item']
|
|
3498
|
+
a2_item = self.data.atoms[a2_id]['item']
|
|
3499
|
+
|
|
3500
|
+
self.scene.create_bond(a1_item, a2_item, bond_order=int(b_type), bond_stereo=stereo)
|
|
3501
|
+
|
|
3502
|
+
self.statusBar().showMessage(f"Successfully loaded from InChI.")
|
|
3503
|
+
self.reset_undo_stack()
|
|
3504
|
+
QTimer.singleShot(0, self.fit_to_view)
|
|
3505
|
+
except Exception as e:
|
|
3506
|
+
self.statusBar().showMessage(f"Error loading from InChI: {e}")
|
|
3507
|
+
|
|
3329
3508
|
def load_mol_file(self):
|
|
3330
3509
|
options = QFileDialog.Option.DontUseNativeDialog
|
|
3331
3510
|
file_path, _ = QFileDialog.getOpenFileName(self, "Import MOL File", "", "Chemical Files (*.mol *.sdf);;All Files (*)", options=options)
|
|
@@ -3341,13 +3520,15 @@ class MainWindow(QMainWindow):
|
|
|
3341
3520
|
self.clear_2d_editor(push_to_undo=False)
|
|
3342
3521
|
self.current_mol = None; self.plotter.clear(); self.analysis_action.setEnabled(False)
|
|
3343
3522
|
|
|
3523
|
+
# 1. 座標がなければ2D座標を生成する
|
|
3344
3524
|
if mol.GetNumConformers() == 0:
|
|
3345
|
-
# 座標情報がないMOLファイルの場合、座標を生成し、
|
|
3346
|
-
# ファイルから読み取った立体情報を元に、くさび結合を再設定する
|
|
3347
3525
|
AllChem.Compute2DCoords(mol)
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3526
|
+
|
|
3527
|
+
# 2. 座標の有無にかかわらず、常に立体化学を割り当て、2D表示用にくさび結合を設定する
|
|
3528
|
+
# これにより、3D座標を持つMOLファイルからでも正しく2Dの立体表現が生成される
|
|
3529
|
+
AllChem.AssignStereochemistry(mol, cleanIt=True, force=True)
|
|
3530
|
+
conf = mol.GetConformer()
|
|
3531
|
+
AllChem.WedgeMolBonds(mol, conf)
|
|
3351
3532
|
|
|
3352
3533
|
conf = mol.GetConformer()
|
|
3353
3534
|
|
|
@@ -3464,7 +3645,7 @@ class MainWindow(QMainWindow):
|
|
|
3464
3645
|
try:
|
|
3465
3646
|
conf=self.current_mol.GetConformer(); num_atoms=self.current_mol.GetNumAtoms()
|
|
3466
3647
|
xyz_lines=[str(num_atoms)]; smiles=Chem.MolToSmiles(Chem.RemoveHs(self.current_mol))
|
|
3467
|
-
xyz_lines.append(f"Generated by MoleditPy Ver. {VERSION}
|
|
3648
|
+
xyz_lines.append(f"Generated by MoleditPy Ver. {VERSION}")
|
|
3468
3649
|
for i in range(num_atoms):
|
|
3469
3650
|
pos=conf.GetAtomPosition(i); symbol=self.current_mol.GetAtomWithIdx(i).GetSymbol()
|
|
3470
3651
|
xyz_lines.append(f"{symbol} {pos.x:.6f} {pos.y:.6f} {pos.z:.6f}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: moleditpy
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: A cross-platform, simple, and intuitive molecular structure editor built in Python. It allows 2D molecular drawing and 3D structure visualization. It supports exporting structure files for input to DFT calculation software.
|
|
5
5
|
Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/HiroYokoyama/python_molecular_editor
|
|
@@ -13,8 +13,10 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.9
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
18
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
-
Requires-Python: >=3.
|
|
19
|
+
Requires-Python: >=3.8
|
|
18
20
|
Description-Content-Type: text/markdown
|
|
19
21
|
Requires-Dist: PyQt6
|
|
20
22
|
Requires-Dist: rdkit
|
|
@@ -34,7 +36,6 @@ A cross-platform, simple, and intuitive molecular structure editor built in Pyth
|
|
|
34
36
|
**License**: Apache-2.0
|
|
35
37
|
**Repository**: [https://github.com/HiroYokoyama/python\_molecular\_editor](https://github.com/HiroYokoyama/python_molecular_editor)
|
|
36
38
|
|
|
37
|
-
-----
|
|
38
39
|
|
|
39
40
|
Pythonで構築された、クロスプラットフォームかつシンプルで直感的な分子構造エディターです。2Dでの分子描画と3D構造可視化ができます。構造ファイルのエクスポートをサポートし、さらにインタラクティブな3D編集機能で目的の配座を直感的に作成できるため、**DFT計算のインプット作成に最適です**。
|
|
40
41
|
|
|
@@ -169,6 +170,10 @@ This application is a tool for easily drawing molecular structures and visually
|
|
|
169
170
|
|
|
170
171
|
## Execution and Installation
|
|
171
172
|
|
|
173
|
+
For more details, please refer to the [Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki).
|
|
174
|
+
|
|
175
|
+
The [Docker version](https://github.com/HiroYokoyama/python_molecular_editor_docker) is also available.
|
|
176
|
+
|
|
172
177
|
#### Requirements
|
|
173
178
|
|
|
174
179
|
`PyQt6`, `RDKit`, `NumPy`, `PyVista`, `pyvistaqt`, `openbabel`
|
|
@@ -193,6 +198,10 @@ moleditpy
|
|
|
193
198
|
|
|
194
199
|
## 実行とインストール
|
|
195
200
|
|
|
201
|
+
詳細については、[Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki)を確認してください。
|
|
202
|
+
|
|
203
|
+
[Docker版](https://github.com/HiroYokoyama/python_molecular_editor_docker)もあります。
|
|
204
|
+
|
|
196
205
|
#### 必要ライブラリ
|
|
197
206
|
|
|
198
207
|
`PyQt6`, `RDKit`, `NumPy`, `PyVista`, `pyvistaqt`, `openbabel`
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|