moleditpy 1.1.7__tar.gz → 1.2.0__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.1.7
3
+ Version: 1.2.0
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
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "moleditpy"
7
7
 
8
- version = "1.1.7"
8
+ version = "1.2.0"
9
9
 
10
10
  authors = [
11
11
  { name="HiroYokoyama", email="titech.yoko.hiro@gmail.com" },
@@ -11,7 +11,11 @@ DOI 10.5281/zenodo.17268532
11
11
  """
12
12
 
13
13
  #Version
14
- VERSION = '1.1.7'
14
+ VERSION = '1.2.0'
15
+
16
+ print("-----------------------------------------------------")
17
+ print("MoleditPy — A Python-based molecular editing software")
18
+ print("-----------------------------------------------------\n")
15
19
 
16
20
  import sys
17
21
  import numpy as np
@@ -31,7 +35,7 @@ from PyQt6.QtWidgets import (
31
35
  QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
32
36
  QPushButton, QSplitter, QGraphicsView, QGraphicsScene, QGraphicsItem,
33
37
  QToolBar, QStatusBar, QGraphicsTextItem, QGraphicsLineItem, QDialog, QGridLayout,
34
- QFileDialog, QSizePolicy, QLabel, QLineEdit, QToolButton, QMenu, QMessageBox
38
+ QFileDialog, QSizePolicy, QLabel, QLineEdit, QToolButton, QMenu, QMessageBox, QInputDialog
35
39
  )
36
40
  from PyQt6.QtGui import (
37
41
  QPen, QBrush, QColor, QPainter, QAction, QActionGroup, QFont, QPolygonF,
@@ -190,7 +194,15 @@ class MolecularData:
190
194
 
191
195
  for (id1, id2), bond in self.bonds.items():
192
196
  idx1, idx2, order = atom_map[id1] + 1, atom_map[id2] + 1, bond['order']
193
- mol_block += f"{idx1:3d}{idx2:3d}{order:3d} 0 0 0 0\n"
197
+ stereo_code = 0
198
+ bond_stereo = bond.get('stereo', 0)
199
+ if bond_stereo == 1:
200
+ stereo_code = 1
201
+ elif bond_stereo == 2:
202
+ stereo_code = 6
203
+
204
+ mol_block += f"{idx1:3d}{idx2:3d}{order:3d}{stereo_code:3d} 0 0 0\n"
205
+
194
206
  mol_block += "M END\n"
195
207
  return mol_block
196
208
 
@@ -2077,6 +2089,7 @@ class MainWindow(QMainWindow):
2077
2089
  # 1. 右パネル全体は「垂直」レイアウトにする
2078
2090
  right_layout = QVBoxLayout(right_pane)
2079
2091
  self.plotter = QtInteractor(right_pane)
2092
+ self.plotter.setAcceptDrops(False)
2080
2093
  self.plotter.setSizePolicy(
2081
2094
  QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
2082
2095
  )
@@ -2363,6 +2376,9 @@ class MainWindow(QMainWindow):
2363
2376
  menu_bar = self.menuBar()
2364
2377
 
2365
2378
  file_menu = menu_bar.addMenu("&File")
2379
+ import_smiles_action = QAction("Import SMILES...", self)
2380
+ import_smiles_action.triggered.connect(self.import_smiles_dialog)
2381
+ file_menu.addAction(import_smiles_action)
2366
2382
  load_mol_action = QAction("Import MOL...", self); load_mol_action.triggered.connect(self.load_mol_file)
2367
2383
  file_menu.addAction(load_mol_action)
2368
2384
  file_menu.addSeparator()
@@ -3019,13 +3035,84 @@ class MainWindow(QMainWindow):
3019
3035
  if push_to_undo:
3020
3036
  self.push_undo_state()
3021
3037
 
3038
+ def import_smiles_dialog(self):
3039
+ """ユーザーにSMILES文字列の入力を促すダイアログを表示する"""
3040
+ smiles, ok = QInputDialog.getText(self, "Import SMILES", "Enter SMILES string:")
3041
+ if ok and smiles:
3042
+ self.load_from_smiles(smiles)
3043
+
3044
+ def load_from_smiles(self, smiles_string):
3045
+ """SMILES文字列から分子を読み込み、2Dエディタに表示する"""
3046
+ try:
3047
+ mol = Chem.MolFromSmiles(smiles_string)
3048
+ if mol is None:
3049
+ raise ValueError("Invalid SMILES string.")
3050
+
3051
+ AllChem.Compute2DCoords(mol)
3052
+ Chem.Kekulize(mol)
3053
+
3054
+ AllChem.AssignStereochemistry(mol, cleanIt=True, force=True)
3055
+ conf = mol.GetConformer()
3056
+ AllChem.WedgeMolBonds(mol, conf)
3057
+
3058
+ self.clear_2d_editor(push_to_undo=False)
3059
+ self.current_mol = None
3060
+ self.plotter.clear()
3061
+ self.analysis_action.setEnabled(False)
3062
+
3063
+ conf = mol.GetConformer()
3064
+ SCALE_FACTOR = 50.0
3065
+
3066
+ view_center = self.view_2d.mapToScene(self.view_2d.viewport().rect().center())
3067
+ positions = [conf.GetAtomPosition(i) for i in range(mol.GetNumAtoms())]
3068
+ mol_center_x = sum(p.x for p in positions) / len(positions) if positions else 0.0
3069
+ mol_center_y = sum(p.y for p in positions) / len(positions) if positions else 0.0
3070
+
3071
+ rdkit_idx_to_my_id = {}
3072
+ for i in range(mol.GetNumAtoms()):
3073
+ atom = mol.GetAtomWithIdx(i)
3074
+ pos = conf.GetAtomPosition(i)
3075
+ charge = atom.GetFormalCharge()
3076
+
3077
+ relative_x = pos.x - mol_center_x
3078
+ relative_y = pos.y - mol_center_y
3079
+
3080
+ scene_x = (relative_x * SCALE_FACTOR) + view_center.x()
3081
+ scene_y = (-relative_y * SCALE_FACTOR) + view_center.y()
3082
+
3083
+ atom_id = self.scene.create_atom(atom.GetSymbol(), QPointF(scene_x, scene_y), charge=charge)
3084
+ rdkit_idx_to_my_id[i] = atom_id
3085
+
3086
+ for bond in mol.GetBonds():
3087
+ b_idx, e_idx = bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()
3088
+ b_type = bond.GetBondTypeAsDouble()
3089
+ b_dir = bond.GetBondDir()
3090
+ stereo = 0
3091
+ if b_dir == Chem.BondDir.BEGINWEDGE:
3092
+ stereo = 1 # Wedge
3093
+ elif b_dir == Chem.BondDir.BEGINDASH:
3094
+ stereo = 2 # Dash
3095
+
3096
+ if b_idx in rdkit_idx_to_my_id and e_idx in rdkit_idx_to_my_id:
3097
+ a1_id, a2_id = rdkit_idx_to_my_id[b_idx], rdkit_idx_to_my_id[e_idx]
3098
+ a1_item = self.data.atoms[a1_id]['item']
3099
+ a2_item = self.data.atoms[a2_id]['item']
3100
+
3101
+ self.scene.create_bond(a1_item, a2_item, bond_order=int(b_type), bond_stereo=stereo)
3102
+
3103
+ self.statusBar().showMessage(f"Successfully loaded from SMILES.")
3104
+ self.reset_undo_stack()
3105
+ self.fit_to_view()
3106
+ except Exception as e:
3107
+ self.statusBar().showMessage(f"Error loading from SMILES: {e}")
3108
+
3022
3109
  def load_mol_file(self):
3023
3110
  options = QFileDialog.Option.DontUseNativeDialog
3024
3111
  file_path, _ = QFileDialog.getOpenFileName(self, "Import MOL File", "", "Chemical Files (*.mol *.sdf);;All Files (*)", options=options)
3025
3112
  if not file_path: return
3026
3113
  try:
3027
3114
  self.dragged_atom_info = None
3028
- suppl = Chem.SDMolSupplier(file_path, removeHs=True)
3115
+ suppl = Chem.SDMolSupplier(file_path, removeHs=False)
3029
3116
  mol = next(suppl, None)
3030
3117
  if mol is None: raise ValueError("Failed to read molecule from file.")
3031
3118
 
@@ -3035,9 +3122,16 @@ class MainWindow(QMainWindow):
3035
3122
  self.current_mol = None; self.plotter.clear(); self.analysis_action.setEnabled(False)
3036
3123
 
3037
3124
  if mol.GetNumConformers() == 0:
3125
+ # 座標情報がないMOLファイルの場合、座標を生成し、
3126
+ # ファイルから読み取った立体情報を元に、くさび結合を再設定する
3038
3127
  AllChem.Compute2DCoords(mol)
3128
+ AllChem.AssignStereochemistry(mol, cleanIt=True, force=True)
3129
+ conf_for_wedge = mol.GetConformer()
3130
+ AllChem.WedgeMolBonds(mol, conf_for_wedge)
3131
+
3132
+ conf = mol.GetConformer()
3039
3133
 
3040
- conf = mol.GetConformer(); SCALE_FACTOR = 50.0
3134
+ SCALE_FACTOR = 50.0
3041
3135
 
3042
3136
  view_center = self.view_2d.mapToScene(self.view_2d.viewport().rect().center())
3043
3137
 
@@ -3075,6 +3169,7 @@ class MainWindow(QMainWindow):
3075
3169
  self.scene.create_bond(a1_item, a2_item, bond_order=int(b_type), bond_stereo=stereo)
3076
3170
 
3077
3171
  self.statusBar().showMessage(f"Successfully loaded {file_path}")
3172
+ self.fit_to_view()
3078
3173
  self.reset_undo_stack()
3079
3174
  except Exception as e: self.statusBar().showMessage(f"Error loading file: {e}")
3080
3175
 
@@ -3152,7 +3247,7 @@ class MainWindow(QMainWindow):
3152
3247
  for i in range(num_atoms):
3153
3248
  pos=conf.GetAtomPosition(i); symbol=self.current_mol.GetAtomWithIdx(i).GetSymbol()
3154
3249
  xyz_lines.append(f"{symbol} {pos.x:.6f} {pos.y:.6f} {pos.z:.6f}")
3155
- with open(file_path,'w') as f: f.write("\n".join(xyz_lines))
3250
+ with open(file_path,'w') as f: f.write("\n".join(xyz_lines) + "\n")
3156
3251
  self.statusBar().showMessage(f"Successfully saved to {file_path}")
3157
3252
  except Exception as e: self.statusBar().showMessage(f"Error saving file: {e}")
3158
3253
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moleditpy
3
- Version: 1.1.7
3
+ Version: 1.2.0
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
File without changes
File without changes