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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moleditpy
3
- Version: 1.2.6
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.6
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.2.6"
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.6"
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.2.6'
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
- if id1 > id2: id1, id2 = id2, id1
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
- is_new_bond = (id1, id2) not in self.bonds
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
- if id1 > id2: id1, id2 = id2, id1
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[(id1, id2)]
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 len(self.bonds) > 0)
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 len(self.bonds) > 0)
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 = self.bond_order
862
- b.stereo = self.bond_stereo
863
- id1, id2 = b.atom1.atom_id, b.atom2.atom_id
864
- if id1 > id2: id1, id2 = id2, id1
865
- self.data.bonds[(id1,id2)]['order'] = self.bond_order
866
- self.data.bonds[(id1,id2)]['stereo'] = self.bond_stereo
867
- b.update()
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
- load_mol_action = QAction("Import MOL...", self); load_mol_action.triggered.connect(self.load_mol_file)
2557
- file_menu.addAction(load_mol_action)
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
- mol = Chem.MolFromSmiles(smiles_string)
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
- AllChem.AssignStereochemistry(mol, cleanIt=True, force=True)
3349
- conf_for_wedge = mol.GetConformer()
3350
- AllChem.WedgeMolBonds(mol, conf_for_wedge)
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} SMILES: {smiles}")
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.2.6
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.6
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