moleditpy 0.3.4__tar.gz → 0.3.6__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: 0.3.4
3
+ Version: 0.3.6
4
4
  Summary: A program to edit molecular structures using Python.
5
5
  Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/HiroYokoyama/python_molecular_editor
@@ -33,6 +33,8 @@ A cross-platform, simple, and intuitive molecular structure editor built in Pyth
33
33
  ライセンス: Apache-2.0
34
34
  リポジトリ: [https://github.com/HiroYokoyama/python\_molecular\_editor](https://github.com/HiroYokoyama/python_molecular_editor)
35
35
 
36
+ ![](img/screenshot.png)
37
+
36
38
  -----
37
39
 
38
40
  ## 概要
@@ -9,6 +9,8 @@ A cross-platform, simple, and intuitive molecular structure editor built in Pyth
9
9
  ライセンス: Apache-2.0
10
10
  リポジトリ: [https://github.com/HiroYokoyama/python\_molecular\_editor](https://github.com/HiroYokoyama/python_molecular_editor)
11
11
 
12
+ ![](img/screenshot.png)
13
+
12
14
  -----
13
15
 
14
16
  ## 概要
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "moleditpy"
7
7
 
8
- version = "0.3.4"
8
+ version = "0.3.6"
9
9
 
10
10
  authors = [
11
11
  { name="HiroYokoyama", email="titech.yoko.hiro@gmail.com" },
@@ -1332,7 +1332,79 @@ class MoleculeScene(QGraphicsScene):
1332
1332
  (b.atom1 is atom2 and b.atom2 is atom1):
1333
1333
  return b
1334
1334
  return None
1335
-
1335
+
1336
+
1337
+ class ZoomableView(QGraphicsView):
1338
+ """ マウスホイールでのズームと中ボタンでのパン機能を追加したQGraphicsView """
1339
+ def __init__(self, scene, parent=None):
1340
+ super().__init__(scene, parent)
1341
+ self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
1342
+ self.setResizeAnchor(QGraphicsView.ViewportAnchor.AnchorViewCenter)
1343
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
1344
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
1345
+ self.setDragMode(QGraphicsView.DragMode.NoDrag) # デフォルトはドラッグなし
1346
+
1347
+ self._is_panning = False
1348
+ self._pan_start_pos = QPointF()
1349
+
1350
+ def wheelEvent(self, event):
1351
+ """ マウスホイールを回した際のイベント """
1352
+ if event.modifiers() & Qt.KeyboardModifier.ControlModifier:
1353
+ zoom_in_factor = 1.1
1354
+ zoom_out_factor = 1 / zoom_in_factor
1355
+
1356
+ transform = self.transform()
1357
+ current_scale = transform.m11()
1358
+ min_scale, max_scale = 0.05, 20.0
1359
+
1360
+ if event.angleDelta().y() > 0:
1361
+ if max_scale > current_scale:
1362
+ self.scale(zoom_in_factor, zoom_in_factor)
1363
+ else:
1364
+ if min_scale < current_scale:
1365
+ self.scale(zoom_out_factor, zoom_out_factor)
1366
+
1367
+ event.accept()
1368
+ else:
1369
+ super().wheelEvent(event)
1370
+
1371
+ def mousePressEvent(self, event):
1372
+ """ 中ボタンが押されたらパン(視点移動)モードを開始 """
1373
+ if event.button() == Qt.MouseButton.MiddleButton:
1374
+ self._is_panning = True
1375
+ self._pan_start_pos = event.pos()
1376
+ self.setCursor(Qt.CursorShape.SizeAllCursor)
1377
+ event.accept()
1378
+ else:
1379
+ super().mousePressEvent(event)
1380
+
1381
+ def mouseMoveEvent(self, event):
1382
+ """ パンモード中にマウスを動かしたら、その分だけ視点を移動させる """
1383
+ if self._is_panning:
1384
+ delta = self.mapToScene(event.pos()) - self.mapToScene(self._pan_start_pos)
1385
+ self.translate(delta.x(), delta.y())
1386
+ self._pan_start_pos = event.pos()
1387
+ event.accept()
1388
+ else:
1389
+ super().mouseMoveEvent(event)
1390
+
1391
+ def mouseReleaseEvent(self, event):
1392
+ """ 中ボタンを離したらパンモードを終了 """
1393
+ if event.button() == Qt.MouseButton.MiddleButton:
1394
+ self._is_panning = False
1395
+ # 現在の描画モードに応じたカーソルに戻す
1396
+ current_mode = self.scene().mode if self.scene() else 'select'
1397
+ if current_mode == 'select':
1398
+ self.setCursor(Qt.CursorShape.ArrowCursor)
1399
+ elif current_mode.startswith(('atom', 'bond', 'template')):
1400
+ self.setCursor(Qt.CursorShape.CrossCursor)
1401
+ elif current_mode.startswith(('charge', 'radical')):
1402
+ self.setCursor(Qt.CursorShape.PointingHandCursor)
1403
+ else:
1404
+ self.setCursor(Qt.CursorShape.ArrowCursor)
1405
+ event.accept()
1406
+ else:
1407
+ super().mouseReleaseEvent(event)
1336
1408
 
1337
1409
  class CalculationWorker(QObject):
1338
1410
  # --- 変更点1: 新しいシグナルを追加 ---
@@ -1559,7 +1631,7 @@ class MainWindow(QMainWindow):
1559
1631
  self.scene.setSceneRect(-4000,-4000,4000,4000)
1560
1632
  self.scene.setBackgroundBrush(QColor("#FFFFFF"))
1561
1633
 
1562
- self.view_2d=QGraphicsView(self.scene)
1634
+ self.view_2d=ZoomableView(self.scene)
1563
1635
  self.view_2d.setRenderHint(QPainter.RenderHint.Antialiasing)
1564
1636
  self.view_2d.setSizePolicy(
1565
1637
  QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
@@ -1830,7 +1902,7 @@ class MainWindow(QMainWindow):
1830
1902
  menu_bar = self.menuBar()
1831
1903
 
1832
1904
  file_menu = menu_bar.addMenu("&File")
1833
- load_mol_action = QAction("Open MOL/SDF...", self); load_mol_action.triggered.connect(self.load_mol_file)
1905
+ load_mol_action = QAction("Import MOL...", self); load_mol_action.triggered.connect(self.load_mol_file)
1834
1906
  file_menu.addAction(load_mol_action)
1835
1907
  file_menu.addSeparator()
1836
1908
  save_mol_action = QAction("Save 2D as MOL...", self); save_mol_action.triggered.connect(self.save_as_mol)
@@ -1900,6 +1972,29 @@ class MainWindow(QMainWindow):
1900
1972
  clear_all_action.triggered.connect(self.clear_all); edit_menu.addAction(clear_all_action)
1901
1973
 
1902
1974
  view_menu = menu_bar.addMenu("&View")
1975
+
1976
+ zoom_in_action = QAction("Zoom In", self)
1977
+ zoom_in_action.setShortcut(QKeySequence.StandardKey.ZoomIn) # Ctrl +
1978
+ zoom_in_action.triggered.connect(self.zoom_in)
1979
+ view_menu.addAction(zoom_in_action)
1980
+
1981
+ zoom_out_action = QAction("Zoom Out", self)
1982
+ zoom_out_action.setShortcut(QKeySequence.StandardKey.ZoomOut) # Ctrl -
1983
+ zoom_out_action.triggered.connect(self.zoom_out)
1984
+ view_menu.addAction(zoom_out_action)
1985
+
1986
+ reset_zoom_action = QAction("Reset Zoom", self)
1987
+ reset_zoom_action.setShortcut(QKeySequence("Ctrl+0"))
1988
+ reset_zoom_action.triggered.connect(self.reset_zoom)
1989
+ view_menu.addAction(reset_zoom_action)
1990
+
1991
+ fit_action = QAction("Fit to View", self)
1992
+ fit_action.setShortcut(QKeySequence("Ctrl+9"))
1993
+ fit_action.triggered.connect(self.fit_to_view)
1994
+ view_menu.addAction(fit_action)
1995
+
1996
+ view_menu.addSeparator()
1997
+
1903
1998
  self.toggle_chiral_action = QAction("Show Chiral Labels", self, checkable=True)
1904
1999
  self.toggle_chiral_action.setChecked(self.show_chiral_labels)
1905
2000
  self.toggle_chiral_action.triggered.connect(self.toggle_chiral_labels_display)
@@ -2362,33 +2457,50 @@ class MainWindow(QMainWindow):
2362
2457
  if push_to_undo:
2363
2458
  self.push_undo_state()
2364
2459
 
2365
-
2366
2460
  def load_mol_file(self):
2367
2461
  options = QFileDialog.Option.DontUseNativeDialog
2368
- file_path, _ = QFileDialog.getOpenFileName(self, "Open MOL/SDF File", "", "Chemical Files (*.mol *.sdf);;All Files (*)", options=options)
2462
+ file_path, _ = QFileDialog.getOpenFileName(self, "Import MOL File", "", "Chemical Files (*.mol *.sdf);;All Files (*)", options=options)
2369
2463
  if not file_path: return
2370
2464
  try:
2371
- suppl = Chem.SDMolSupplier(file_path, removeHs=False)
2465
+ suppl = Chem.SDMolSupplier(file_path, removeHs=True)
2372
2466
  mol = next(suppl, None)
2373
2467
  if mol is None: raise ValueError("Failed to read molecule from file.")
2468
+
2469
+ Chem.Kekulize(mol)
2470
+
2374
2471
  self.clear_2d_editor(push_to_undo=False)
2375
2472
  self.current_mol = None; self.plotter.clear(); self.analysis_action.setEnabled(False)
2376
2473
 
2377
- if mol.GetNumConformers() == 0: AllChem.Compute2DCoords(mol)
2474
+ if mol.GetNumConformers() == 0:
2475
+ AllChem.Compute2DCoords(mol)
2378
2476
 
2379
2477
  conf = mol.GetConformer(); SCALE_FACTOR = 50.0
2380
- rdkit_idx_to_my_id = {}
2478
+
2479
+ view_center = self.view_2d.mapToScene(self.view_2d.viewport().rect().center())
2480
+
2381
2481
  positions = [conf.GetAtomPosition(i) for i in range(mol.GetNumAtoms())]
2382
- center_x = sum(p.x for p in positions)/len(positions) if positions else 0
2383
- center_y = sum(p.y for p in positions)/len(positions) if positions else 0
2482
+ if positions:
2483
+ mol_center_x = sum(p.x for p in positions) / len(positions)
2484
+ mol_center_y = sum(p.y for p in positions) / len(positions)
2485
+ else:
2486
+ mol_center_x, mol_center_y = 0.0, 0.0
2487
+
2488
+ rdkit_idx_to_my_id = {}
2384
2489
  for i in range(mol.GetNumAtoms()):
2385
- atom = mol.GetAtomWithIdx(i); pos = conf.GetAtomPosition(i)
2490
+ atom = mol.GetAtomWithIdx(i)
2491
+ pos = conf.GetAtomPosition(i)
2386
2492
  charge = atom.GetFormalCharge()
2387
- scene_x=(pos.x-center_x)*SCALE_FACTOR; scene_y=-(pos.y-center_y)*SCALE_FACTOR
2493
+
2494
+ relative_x = pos.x - mol_center_x
2495
+ relative_y = pos.y - mol_center_y
2496
+
2497
+ scene_x = (relative_x * SCALE_FACTOR) + view_center.x()
2498
+ scene_y = (-relative_y * SCALE_FACTOR) + view_center.y()
2499
+
2388
2500
  atom_id = self.scene.create_atom(atom.GetSymbol(), QPointF(scene_x, scene_y), charge=charge)
2389
2501
  rdkit_idx_to_my_id[i] = atom_id
2502
+
2390
2503
  for bond in mol.GetBonds():
2391
- # (以降のBond読み込み部分は変更なし)
2392
2504
  b_idx,e_idx=bond.GetBeginAtomIdx(),bond.GetEndAtomIdx()
2393
2505
  b_type = bond.GetBondTypeAsDouble(); b_dir = bond.GetBondDir()
2394
2506
  stereo = 0
@@ -2796,14 +2908,18 @@ class MainWindow(QMainWindow):
2796
2908
  self.statusBar().showMessage(f"3D chiral label drawing error: {e}")
2797
2909
 
2798
2910
 
2911
+ # MainWindow クラス内 (約2800行目付近)
2912
+
2799
2913
  def toggle_chiral_labels_display(self, checked):
2800
2914
  """Viewメニューのアクションに応じてキラルラベル表示を切り替える"""
2801
2915
  self.show_chiral_labels = checked
2916
+
2917
+ if self.current_mol:
2918
+ self.draw_molecule_3d(self.current_mol)
2919
+
2802
2920
  if checked:
2803
- draw_molecule_3d()
2804
2921
  self.statusBar().showMessage("Chiral labels: will be (re)computed after Convert→3D.")
2805
2922
  else:
2806
- draw_molecule_3d()
2807
2923
  self.statusBar().showMessage("Chiral labels disabled.")
2808
2924
 
2809
2925
 
@@ -2892,6 +3008,43 @@ class MainWindow(QMainWindow):
2892
3008
  else:
2893
3009
  event.ignore()
2894
3010
 
3011
+ def zoom_in(self):
3012
+ """ ビューを 20% 拡大する """
3013
+ self.view_2d.scale(1.2, 1.2)
3014
+
3015
+ def zoom_out(self):
3016
+ """ ビューを 20% 縮小する """
3017
+ self.view_2d.scale(1/1.2, 1/1.2)
3018
+
3019
+ def reset_zoom(self):
3020
+ """ ビューの拡大率をデフォルト (50%) にリセットする """
3021
+ transform = QTransform()
3022
+ transform.scale(0.5, 0.5)
3023
+ self.view_2d.setTransform(transform)
3024
+
3025
+ def fit_to_view(self):
3026
+ """ シーン上のすべてのアイテムがビューに収まるように調整する """
3027
+ if not self.scene.items():
3028
+ self.reset_zoom()
3029
+ return
3030
+
3031
+ bounds = self.scene.itemsBoundingRect()
3032
+ visible_items_rect = QRectF()
3033
+ for item in self.scene.items():
3034
+ if item.isVisible() and not isinstance(item, TemplatePreviewItem):
3035
+ if visible_items_rect.isEmpty():
3036
+ visible_items_rect = item.sceneBoundingRect()
3037
+ else:
3038
+ visible_items_rect = visible_items_rect.united(item.sceneBoundingRect())
3039
+
3040
+ if not visible_items_rect.isEmpty():
3041
+ self.view_2d.fitInView(visible_items_rect, Qt.AspectRatioMode.KeepAspectRatio)
3042
+ self.view_2d.scale(0.6, 0.6)
3043
+ else:
3044
+ self.reset_zoom()
3045
+
3046
+
2895
3047
  # --- Application Execution ---
2896
3048
  if __name__ == '__main__':
2897
3049
  main()
3050
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moleditpy
3
- Version: 0.3.4
3
+ Version: 0.3.6
4
4
  Summary: A program to edit molecular structures using Python.
5
5
  Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/HiroYokoyama/python_molecular_editor
@@ -33,6 +33,8 @@ A cross-platform, simple, and intuitive molecular structure editor built in Pyth
33
33
  ライセンス: Apache-2.0
34
34
  リポジトリ: [https://github.com/HiroYokoyama/python\_molecular\_editor](https://github.com/HiroYokoyama/python_molecular_editor)
35
35
 
36
+ ![](img/screenshot.png)
37
+
36
38
  -----
37
39
 
38
40
  ## 概要
File without changes