MoleditPy 1.18.0__py3-none-any.whl → 2.0.0__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.
@@ -19,6 +19,7 @@ MainWindow (main_window.py) から分離されたモジュール
19
19
 
20
20
  import numpy as np
21
21
  import vtk
22
+ import logging
22
23
 
23
24
 
24
25
  # RDKit imports (explicit to satisfy flake8 and used features)
@@ -58,7 +59,7 @@ if OBABEL_AVAILABLE:
58
59
  # If import fails here, disable OBABEL locally; avoid raising
59
60
  pybel = None
60
61
  OBABEL_AVAILABLE = False
61
- print("Warning: openbabel.pybel not available. Open Babel fallback and OBabel-based options will be disabled.")
62
+ logging.warning("Warning: openbabel.pybel not available. Open Babel fallback and OBabel-based options will be disabled.")
62
63
  else:
63
64
  pybel = None
64
65
 
@@ -201,7 +202,7 @@ class MainWindowView3d(object):
201
202
  resolution = self.settings.get('wireframe_resolution', 6)
202
203
  rad = np.array([0.01 for s in sym]) # 極小値(使用されない)
203
204
  elif self.current_3d_style == 'stick':
204
- atom_radius = self.settings.get('stick_atom_radius', 0.15)
205
+ atom_radius = self.settings.get('stick_bond_radius', 0.15) # Use bond radius for atoms
205
206
  resolution = self.settings.get('stick_resolution', 16)
206
207
  rad = np.array([atom_radius for s in sym])
207
208
  else: # ball_and_stick
@@ -278,7 +279,7 @@ class MainWindowView3d(object):
278
279
 
279
280
  # 結合描画と同じ計算
280
281
  sphere_radius = cyl_radius * radius_factor
281
- except:
282
+ except Exception:
282
283
  sphere_radius = 0.09 # デフォルト値
283
284
  offset_distance = 0.15 # デフォルト値
284
285
 
@@ -437,7 +438,7 @@ class MainWindowView3d(object):
437
438
  double_radius_factor = self.settings.get('ball_stick_double_bond_radius_factor', 0.8)
438
439
  triple_radius_factor = self.settings.get('ball_stick_triple_bond_radius_factor', 0.75)
439
440
  elif self.current_3d_style == 'wireframe':
440
- double_radius_factor = self.settings.get('wireframe_double_bond_radius_factor', 1.0)
441
+ double_radius_factor = self.settings.get('wireframe_double_bond_radius_factor', 0.8)
441
442
  triple_radius_factor = self.settings.get('wireframe_triple_bond_radius_factor', 0.75)
442
443
  elif self.current_3d_style == 'stick':
443
444
  double_radius_factor = self.settings.get('stick_double_bond_radius_factor', 0.60)
@@ -660,7 +661,7 @@ class MainWindowView3d(object):
660
661
  self.plotter.add_mesh(circle_line, color=torus_color, **mesh_props)
661
662
 
662
663
  except Exception as e:
663
- print(f"Error rendering aromatic circles: {e}")
664
+ logging.error(f"Error rendering aromatic circles: {e}")
664
665
 
665
666
  if getattr(self, 'show_chiral_labels', False):
666
667
  try:
@@ -820,7 +821,7 @@ class MainWindowView3d(object):
820
821
  try:
821
822
  # 既存のE/Zラベルを削除
822
823
  self.plotter.remove_actor('ez_labels')
823
- except:
824
+ except Exception:
824
825
  pass
825
826
 
826
827
  pts, labels = [], []
@@ -837,7 +838,7 @@ class MainWindowView3d(object):
837
838
  # 3D座標からステレオ化学を再計算 (molに対して行う)
838
839
  # これにより、2Dでの描画状態に関わらず、現在の3D座標に基づいたE/Z判定が行われる
839
840
  Chem.AssignStereochemistry(mol, cleanIt=True, force=True, flagPossibleStereoCenters=True)
840
- except:
841
+ except Exception:
841
842
  pass
842
843
 
843
844
  for bond in mol.GetBonds():
@@ -1171,7 +1172,7 @@ class MainWindowView3d(object):
1171
1172
  for nm in self.atom_label_legend_names:
1172
1173
  try:
1173
1174
  self.plotter.remove_actor(nm)
1174
- except:
1175
+ except Exception:
1175
1176
  pass
1176
1177
  self.atom_label_legend_names = []
1177
1178
 
@@ -1237,12 +1238,12 @@ class MainWindowView3d(object):
1237
1238
  for a in list(self.current_atom_info_labels):
1238
1239
  try:
1239
1240
  self.plotter.remove_actor(a)
1240
- except:
1241
+ except Exception:
1241
1242
  pass
1242
1243
  else:
1243
1244
  try:
1244
1245
  self.plotter.remove_actor(self.current_atom_info_labels)
1245
- except:
1246
+ except Exception:
1246
1247
  pass
1247
1248
  except Exception:
1248
1249
  pass
@@ -1255,7 +1256,7 @@ class MainWindowView3d(object):
1255
1256
  for nm in list(self.atom_label_legend_names):
1256
1257
  try:
1257
1258
  self.plotter.remove_actor(nm)
1258
- except:
1259
+ except Exception:
1259
1260
  pass
1260
1261
  except Exception:
1261
1262
  pass
@@ -1401,7 +1402,7 @@ class MainWindowView3d(object):
1401
1402
  if renderer and hasattr(renderer, 'SetNumberOfLayers'):
1402
1403
  try:
1403
1404
  renderer.SetNumberOfLayers(2) # レイヤー0:3Dオブジェクト、レイヤー1:2Dオーバーレイ
1404
- except:
1405
+ except Exception:
1405
1406
  pass # PyVistaのバージョンによってはサポートされていない場合がある
1406
1407
 
1407
1408
  # --- 3D軸ウィジェットの設定 ---
@@ -317,7 +317,7 @@ class MainWindowViewLoaders(object):
317
317
  # Keep mol as-is (may lack conformer); downstream code checks for conformers
318
318
  else:
319
319
  raise
320
- except:
320
+ except Exception:
321
321
  self.statusBar().showMessage("Failed to generate 3D coordinates")
322
322
  return
323
323
 
@@ -11,6 +11,7 @@ DOI: 10.5281/zenodo.17268532
11
11
  """
12
12
 
13
13
  import traceback
14
+ import logging
14
15
 
15
16
  from PyQt6.QtWidgets import (
16
17
  QApplication, QGraphicsScene, QGraphicsItem,
@@ -114,6 +115,15 @@ class MoleculeScene(QGraphicsScene):
114
115
  }
115
116
  self.reinitialize_items()
116
117
 
118
+
119
+ def update_all_items(self):
120
+ """全てのアイテムを強制的に再描画する"""
121
+ for item in self.items():
122
+ if isinstance(item, (AtomItem, BondItem)):
123
+ item.update()
124
+ if self.views():
125
+ self.views()[0].viewport().update()
126
+
117
127
  def reinitialize_items(self):
118
128
  self.template_preview = TemplatePreviewItem(); self.addItem(self.template_preview)
119
129
  self.template_preview.hide(); self.template_preview_points = []; self.template_context = {}
@@ -184,6 +194,7 @@ class MoleculeScene(QGraphicsScene):
184
194
  # Delete the entire rectangular selection
185
195
  data_changed = self.delete_items(set(selected_items))
186
196
  if data_changed:
197
+ self.update_all_items()
187
198
  self.window.push_undo_state()
188
199
  self.press_pos = None
189
200
  event.accept()
@@ -203,11 +214,10 @@ class MoleculeScene(QGraphicsScene):
203
214
  self.window.push_undo_state()
204
215
  data_changed = False # ここでundo済みなので以降で積まない
205
216
  except Exception as e:
206
- print(f"Error clearing E/Z label: {e}")
207
-
208
- traceback.print_exc()
217
+ logging.error(f"Error clearing E/Z label: {e}", exc_info=True)
209
218
  if hasattr(self.window, 'statusBar'):
210
219
  self.window.statusBar().showMessage(f"Error clearing E/Z label: {e}", 5000)
220
+ self.update_all_items() # エラー時も整合性維持のため再描画
211
221
  # AtomItemは何もしない
212
222
  # --- 通常の処理 ---
213
223
  elif isinstance(item, AtomItem):
@@ -234,6 +244,7 @@ class MoleculeScene(QGraphicsScene):
234
244
  data_changed = self.delete_items({item})
235
245
 
236
246
  if data_changed:
247
+ self.update_all_items()
237
248
  self.window.push_undo_state()
238
249
  self.press_pos = None
239
250
  event.accept()
@@ -343,7 +354,9 @@ class MoleculeScene(QGraphicsScene):
343
354
  self.data_changed_in_event = True
344
355
  # イベント処理をここで完了させ、下のアイテムが選択されるのを防ぐ
345
356
  self.start_atom=None; self.start_pos = None; self.press_pos = None
346
- if self.data_changed_in_event: self.window.push_undo_state()
357
+ if self.data_changed_in_event:
358
+ self.update_all_items()
359
+ self.window.push_undo_state()
347
360
  return
348
361
 
349
362
  released_item = self.itemAt(end_pos, self.views()[0].transform())
@@ -385,13 +398,13 @@ class MoleculeScene(QGraphicsScene):
385
398
  else: # current_stereo == 4
386
399
  new_stereo = 0 # E -> None
387
400
  self.update_bond_stereo(b, new_stereo)
401
+ self.update_all_items() # 強制再描画
388
402
  self.window.push_undo_state() # ここでUndo stackに積む
389
403
  except Exception as e:
390
- print(f"Error in E/Z stereo toggle: {e}")
391
-
392
- traceback.print_exc()
404
+ logging.error(f"Error in E/Z stereo toggle: {e}", exc_info=True)
393
405
  if hasattr(self.window, 'statusBar'):
394
406
  self.window.statusBar().showMessage(f"Error changing E/Z stereochemistry: {e}", 5000)
407
+ self.update_all_items() # エラー時も整合性維持のため再描画
395
408
  return # この後の処理は行わない
396
409
  elif self.bond_stereo != 0 and b.order == self.bond_order and b.stereo == self.bond_stereo:
397
410
  # 方向性を反転させる
@@ -491,6 +504,10 @@ class MoleculeScene(QGraphicsScene):
491
504
  # 原子移動後に測定ラベルの位置を更新
492
505
  self.window.update_2d_measurement_labels()
493
506
  if self.views(): self.views()[0].viewport().update()
507
+
508
+ if self.data_changed_in_event:
509
+ self.update_all_items()
510
+
494
511
  self.start_atom=None; self.start_pos = None; self.press_pos = None; self.temp_line = None
495
512
  self.template_context = {}
496
513
  # Clear user template data when switching modes
@@ -515,6 +532,7 @@ class MoleculeScene(QGraphicsScene):
515
532
  self.data.atoms[item.atom_id]['charge'] = item.charge
516
533
  item.update_style()
517
534
 
535
+ self.update_all_items()
518
536
  self.window.push_undo_state()
519
537
 
520
538
  event.accept()
@@ -610,7 +628,7 @@ class MoleculeScene(QGraphicsScene):
610
628
  def create_bond(self, start_atom, end_atom, bond_order=None, bond_stereo=None):
611
629
  try:
612
630
  if start_atom is None or end_atom is None:
613
- print("Error: Cannot create bond with None atoms")
631
+ logging.error("Error: Cannot create bond with None atoms")
614
632
  return
615
633
 
616
634
  exist_b = self.find_bond_between(start_atom, end_atom)
@@ -637,9 +655,8 @@ class MoleculeScene(QGraphicsScene):
637
655
  end_atom.update_style()
638
656
 
639
657
  except Exception as e:
640
- print(f"Error creating bond: {e}")
641
-
642
- traceback.print_exc()
658
+ logging.error(f"Error creating bond: {e}", exc_info=True)
659
+ self.update_all_items() # エラーリカバリー
643
660
 
644
661
  def add_molecule_fragment(self, points, bonds_info, existing_items=None, symbol='C'):
645
662
  """
@@ -1270,6 +1287,7 @@ class MoleculeScene(QGraphicsScene):
1270
1287
  print(f"Error during delete_items operation: {e}")
1271
1288
 
1272
1289
  traceback.print_exc()
1290
+ self.update_all_items() # エラーリカバリー
1273
1291
  return False
1274
1292
  def purge_deleted_items(self):
1275
1293
  """Purge and release any held deleted-wrapper references.
@@ -1541,6 +1559,7 @@ class MoleculeScene(QGraphicsScene):
1541
1559
 
1542
1560
  # 計算した情報を使って、その場にフラグメントを追加
1543
1561
  self.add_molecule_fragment(points, bonds_info, existing_items=existing_items)
1562
+ self.update_all_items()
1544
1563
  self.window.push_undo_state()
1545
1564
 
1546
1565
  # --- 動作2: カーソルが空白領域にある場合 (モード切替) ---
@@ -1566,6 +1585,7 @@ class MoleculeScene(QGraphicsScene):
1566
1585
  atom.radical = (atom.radical + 1) % 3
1567
1586
  self.data.atoms[atom.atom_id]['radical'] = atom.radical
1568
1587
  atom.update_style()
1588
+ self.update_all_items()
1569
1589
  self.window.push_undo_state()
1570
1590
  event.accept()
1571
1591
  return
@@ -1586,6 +1606,7 @@ class MoleculeScene(QGraphicsScene):
1586
1606
  atom.charge += delta
1587
1607
  self.data.atoms[atom.atom_id]['charge'] = atom.charge
1588
1608
  atom.update_style()
1609
+ self.update_all_items()
1589
1610
  self.window.push_undo_state()
1590
1611
  event.accept()
1591
1612
  return
@@ -1615,6 +1636,7 @@ class MoleculeScene(QGraphicsScene):
1615
1636
  for atom in atoms_to_update:
1616
1637
  atom.update_style()
1617
1638
 
1639
+ self.update_all_items()
1618
1640
  self.window.push_undo_state()
1619
1641
  event.accept()
1620
1642
  return
@@ -1668,9 +1690,9 @@ class MoleculeScene(QGraphicsScene):
1668
1690
  elif key == Qt.Key.Key_1 and (bond.order != 1 or bond.stereo != 0):
1669
1691
  bond.order = 1; bond.stereo = 0
1670
1692
  elif key == Qt.Key.Key_2 and (bond.order != 2 or bond.stereo != 0):
1671
- bond.order = 2; bond.stereo = 0; needs_update = True
1693
+ bond.order = 2; bond.stereo = 0
1672
1694
  elif key == Qt.Key.Key_3 and bond.order != 3:
1673
- bond.order = 3; bond.stereo = 0; needs_update = True
1695
+ bond.order = 3; bond.stereo = 0
1674
1696
 
1675
1697
  # 4. 実際に変更があった場合のみデータモデルを更新
1676
1698
  if old_order != bond.order or old_stereo != bond.stereo:
@@ -1693,6 +1715,7 @@ class MoleculeScene(QGraphicsScene):
1693
1715
  bond.update()
1694
1716
 
1695
1717
  if any_bond_changed:
1718
+ self.update_all_items()
1696
1719
  self.window.push_undo_state()
1697
1720
 
1698
1721
  if key in [Qt.Key.Key_1, Qt.Key.Key_2, Qt.Key.Key_3, Qt.Key.Key_W, Qt.Key.Key_D]:
@@ -1702,11 +1725,13 @@ class MoleculeScene(QGraphicsScene):
1702
1725
  if isinstance(self.hovered_item, BondItem) and self.hovered_item.order == 2:
1703
1726
  if event.key() == Qt.Key.Key_Z:
1704
1727
  self.update_bond_stereo(self.hovered_item, 3) # Z-isomer
1728
+ self.update_all_items()
1705
1729
  self.window.push_undo_state()
1706
1730
  event.accept()
1707
1731
  return
1708
1732
  elif event.key() == Qt.Key.Key_E:
1709
1733
  self.update_bond_stereo(self.hovered_item, 4) # E-isomer
1734
+ self.update_all_items()
1710
1735
  self.window.push_undo_state()
1711
1736
  event.accept()
1712
1737
  return
@@ -1814,6 +1839,7 @@ class MoleculeScene(QGraphicsScene):
1814
1839
  self.create_bond(start_atom, new_atom_item, bond_order=target_order, bond_stereo=0)
1815
1840
 
1816
1841
  self.clearSelection()
1842
+ self.update_all_items()
1817
1843
  self.window.push_undo_state()
1818
1844
  event.accept()
1819
1845
  return
@@ -1844,6 +1870,7 @@ class MoleculeScene(QGraphicsScene):
1844
1870
  items_to_process.add(item_at_cursor)
1845
1871
 
1846
1872
  if self.delete_items(items_to_process):
1873
+ self.update_all_items()
1847
1874
  self.window.push_undo_state()
1848
1875
  self.window.statusBar().showMessage("Deleted selected items.")
1849
1876
 
@@ -1978,3 +2005,4 @@ class MoleculeScene(QGraphicsScene):
1978
2005
  traceback.print_exc()
1979
2006
  if hasattr(self.window, 'statusBar'):
1980
2007
  self.window.statusBar().showMessage(f"Error updating bond stereochemistry: {e}", 5000)
2008
+ self.update_all_items() # エラーリカバリー
@@ -157,7 +157,7 @@ class PlanarizeDialog(Dialog3DPickingMixin, QDialog):
157
157
  for label_actor in self.selection_labels:
158
158
  try:
159
159
  self.main_window.plotter.remove_actor(label_actor)
160
- except:
160
+ except Exception:
161
161
  pass
162
162
  self.selection_labels = []
163
163
 
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ plugin_manager.py
6
+ Manages discovery, loading, and execution of external plugins.
7
+ """
8
+
9
+ import os
10
+ import sys
11
+ import importlib.util
12
+ import traceback
13
+ from PyQt6.QtGui import QDesktopServices
14
+ from PyQt6.QtCore import QUrl
15
+ from PyQt6.QtWidgets import QMessageBox
16
+
17
+ class PluginManager:
18
+ def __init__(self):
19
+ self.plugin_dir = os.path.join(os.path.expanduser('~'), '.moleditpy', 'plugins')
20
+ self.plugins = [] # List of {"name": str, "module": module_obj}
21
+
22
+ def ensure_plugin_dir(self):
23
+ """Creates the plugin directory if it creates doesn't exist."""
24
+ if not os.path.exists(self.plugin_dir):
25
+ try:
26
+ os.makedirs(self.plugin_dir)
27
+ except OSError as e:
28
+ print(f"Error creating plugin directory: {e}")
29
+
30
+ def open_plugin_folder(self):
31
+ """Opens the plugin directory in the OS file explorer."""
32
+ self.ensure_plugin_dir()
33
+ QDesktopServices.openUrl(QUrl.fromLocalFile(self.plugin_dir))
34
+
35
+ def discover_plugins(self, parent=None):
36
+ """
37
+ Scans the plugin directory for .py files and attempts to import them.
38
+ Returns a list of valid loaded plugins.
39
+ """
40
+ self.ensure_plugin_dir()
41
+ self.plugins = []
42
+
43
+ if not os.path.exists(self.plugin_dir):
44
+ return []
45
+
46
+ for filename in os.listdir(self.plugin_dir):
47
+ if filename.endswith(".py") and not filename.startswith("__"):
48
+ filepath = os.path.join(self.plugin_dir, filename)
49
+ try:
50
+ # Dynamically import the module
51
+ spec = importlib.util.spec_from_file_location(filename[:-3], filepath)
52
+ if spec and spec.loader:
53
+ module = importlib.util.module_from_spec(spec)
54
+ sys.modules[spec.name] = module # helper for relative imports if needed
55
+ spec.loader.exec_module(module)
56
+
57
+ # Check for required attributes
58
+ plugin_name = getattr(module, 'PLUGIN_NAME', filename[:-3])
59
+
60
+ # Validate that it has a run function
61
+ if hasattr(module, 'run') and callable(module.run):
62
+ self.plugins.append({
63
+ 'name': plugin_name,
64
+ 'module': module
65
+ })
66
+ else:
67
+ print(f"Plugin {filename} skipped: Missing 'run(main_window)' function.")
68
+ except Exception as e:
69
+ # Robust error handling with user notification
70
+ msg = f"Failed to load plugin {filename}:\n{e}"
71
+ print(msg)
72
+ traceback.print_exc()
73
+ if parent:
74
+ QMessageBox.warning(parent, "Plugin Load Error", msg)
75
+
76
+ return self.plugins
77
+
78
+ def run_plugin(self, module, main_window):
79
+ """Executes the plugin's run method."""
80
+ try:
81
+ module.run(main_window)
82
+ except Exception as e:
83
+ QMessageBox.critical(main_window, "Plugin Error", f"Error running plugin '{getattr(module, 'PLUGIN_NAME', 'Unknown')}':\n{e}")
84
+ traceback.print_exc()
85
+
@@ -53,7 +53,6 @@ class SettingsDialog(QDialog):
53
53
  'wireframe_bond_radius': 0.01,
54
54
  'wireframe_resolution': 6,
55
55
  # Stick model parameters
56
- 'stick_atom_radius': 0.15,
57
56
  'stick_bond_radius': 0.15,
58
57
  'stick_resolution': 16,
59
58
  # Multiple bond offset parameters (per-model)
@@ -278,8 +277,8 @@ class SettingsDialog(QDialog):
278
277
  # Aromatic torus thickness factor
279
278
  self.aromatic_torus_thickness_slider = QSlider(Qt.Orientation.Horizontal)
280
279
  self.aromatic_torus_thickness_slider.setRange(10, 300) # 0.1x to 3.0x
281
- self.aromatic_torus_thickness_slider.setValue(100) # Default 1.0x
282
- self.aromatic_torus_thickness_label = QLabel("1.0")
280
+ self.aromatic_torus_thickness_slider.setValue(60) # Default 0.6x
281
+ self.aromatic_torus_thickness_label = QLabel("0.6")
283
282
  self.aromatic_torus_thickness_slider.valueChanged.connect(
284
283
  lambda v: self.aromatic_torus_thickness_label.setText(f"{v/100:.1f}")
285
284
  )
@@ -564,17 +563,7 @@ class SettingsDialog(QDialog):
564
563
  info_label.setStyleSheet("color: #666; font-style: italic; margin-top: 10px;")
565
564
  form_layout.addRow(info_label)
566
565
 
567
- # 原子半径
568
- self.stick_atom_radius_slider = QSlider(Qt.Orientation.Horizontal)
569
- self.stick_atom_radius_slider.setRange(5, 50) # 0.05 ~ 0.5
570
- self.stick_atom_radius_label = QLabel("0.15")
571
- self.stick_atom_radius_slider.valueChanged.connect(lambda v: self.stick_atom_radius_label.setText(f"{v/100:.2f}"))
572
- atom_radius_layout = QHBoxLayout()
573
- atom_radius_layout.addWidget(self.stick_atom_radius_slider)
574
- atom_radius_layout.addWidget(self.stick_atom_radius_label)
575
- form_layout.addRow("Atom Radius:", atom_radius_layout)
576
-
577
- # ボンド半径
566
+ # ボンド半径(原子半径も同じ値を使用)
578
567
  self.stick_bond_radius_slider = QSlider(Qt.Orientation.Horizontal)
579
568
  self.stick_bond_radius_slider.setRange(5, 50) # 0.05 ~ 0.5
580
569
  self.stick_bond_radius_label = QLabel("0.15")
@@ -673,7 +662,7 @@ class SettingsDialog(QDialog):
673
662
  'display_kekule_3d': self.default_settings.get('display_kekule_3d', False),
674
663
  'always_ask_charge': self.default_settings.get('always_ask_charge', False),
675
664
  'display_aromatic_circles_3d': self.default_settings.get('display_aromatic_circles_3d', False),
676
- 'aromatic_torus_thickness_factor': self.default_settings.get('aromatic_torus_thickness_factor', 1.0),
665
+ 'aromatic_torus_thickness_factor': self.default_settings.get('aromatic_torus_thickness_factor', 0.6),
677
666
  },
678
667
  "Ball & Stick": {
679
668
  'ball_stick_atom_scale': self.default_settings['ball_stick_atom_scale'],
@@ -700,7 +689,6 @@ class SettingsDialog(QDialog):
700
689
  'wireframe_triple_bond_radius_factor': self.default_settings.get('wireframe_triple_bond_radius_factor', 0.75)
701
690
  },
702
691
  "Stick": {
703
- 'stick_atom_radius': self.default_settings['stick_atom_radius'],
704
692
  'stick_bond_radius': self.default_settings['stick_bond_radius'],
705
693
  'stick_resolution': self.default_settings['stick_resolution'],
706
694
  'stick_double_bond_offset_factor': self.default_settings.get('stick_double_bond_offset_factor', 1.5),
@@ -844,7 +832,6 @@ class SettingsDialog(QDialog):
844
832
  'wireframe_bond_radius': self.wf_bond_radius_slider.value() / 100.0,
845
833
  'wireframe_resolution': self.wf_resolution_slider.value(),
846
834
  # Stick settings
847
- 'stick_atom_radius': self.stick_atom_radius_slider.value() / 100.0,
848
835
  'stick_bond_radius': self.stick_bond_radius_slider.value() / 100.0,
849
836
  'stick_resolution': self.stick_resolution_slider.value(),
850
837
  # Multi-bond settings (per-model)
@@ -944,10 +931,6 @@ class SettingsDialog(QDialog):
944
931
  self.wf_resolution_label.setText(str(settings_dict.get('wireframe_resolution', self.default_settings['wireframe_resolution'])))
945
932
 
946
933
  # Stick設定
947
- stick_atom_radius = int(settings_dict.get('stick_atom_radius', self.default_settings['stick_atom_radius']) * 100)
948
- self.stick_atom_radius_slider.setValue(stick_atom_radius)
949
- self.stick_atom_radius_label.setText(f"{stick_atom_radius/100:.2f}")
950
-
951
934
  stick_bond_radius = int(settings_dict.get('stick_bond_radius', self.default_settings['stick_bond_radius']) * 100)
952
935
  self.stick_bond_radius_slider.setValue(stick_bond_radius)
953
936
  self.stick_bond_radius_label.setText(f"{stick_bond_radius/100:.2f}")
@@ -1019,7 +1002,7 @@ class SettingsDialog(QDialog):
1019
1002
  self.always_ask_charge_checkbox.setChecked(settings_dict.get('always_ask_charge', self.default_settings.get('always_ask_charge', False)))
1020
1003
  # Aromatic ring circle display and torus thickness factor
1021
1004
  self.aromatic_circle_checkbox.setChecked(settings_dict.get('display_aromatic_circles_3d', self.default_settings.get('display_aromatic_circles_3d', False)))
1022
- thickness_factor = float(settings_dict.get('aromatic_torus_thickness_factor', self.default_settings.get('aromatic_torus_thickness_factor', 1.0)))
1005
+ thickness_factor = float(settings_dict.get('aromatic_torus_thickness_factor', self.default_settings.get('aromatic_torus_thickness_factor', 0.6)))
1023
1006
  try:
1024
1007
  self.aromatic_torus_thickness_slider.setValue(int(thickness_factor * 100))
1025
1008
  self.aromatic_torus_thickness_label.setText(f"{thickness_factor:.1f}")
@@ -1060,7 +1043,6 @@ class SettingsDialog(QDialog):
1060
1043
  'wireframe_bond_radius': self.wf_bond_radius_slider.value() / 100.0,
1061
1044
  'wireframe_resolution': self.wf_resolution_slider.value(),
1062
1045
  # Stick settings
1063
- 'stick_atom_radius': self.stick_atom_radius_slider.value() / 100.0,
1064
1046
  'stick_bond_radius': self.stick_bond_radius_slider.value() / 100.0,
1065
1047
  'stick_resolution': self.stick_resolution_slider.value(),
1066
1048
  # Multiple bond offset settings (per-model)
@@ -23,6 +23,7 @@ except Exception:
23
23
  from modules.constants import VERSION, CPK_COLORS
24
24
  import os
25
25
  import json
26
+ import logging
26
27
 
27
28
  class UserTemplateDialog(QDialog):
28
29
  """ユーザーテンプレート管理ダイアログ"""
@@ -106,7 +107,7 @@ class UserTemplateDialog(QDialog):
106
107
  elif hasattr(child, 'refit_view'):
107
108
  child.refit_view()
108
109
  except Exception as e:
109
- print(f"Warning: Failed to refit template previews: {e}")
110
+ logging.warning(f"Warning: Failed to refit template previews: {e}")
110
111
 
111
112
  def showEvent(self, event):
112
113
  """ダイアログ表示時にプレビューを適切にフィット"""
@@ -136,7 +137,7 @@ class UserTemplateDialog(QDialog):
136
137
  template_data['filepath'] = filepath
137
138
  self.user_templates.append(template_data)
138
139
  except Exception as e:
139
- print(f"Error loading user templates: {e}")
140
+ logging.error(f"Error loading user templates: {e}")
140
141
 
141
142
  self.update_template_grid()
142
143
 
@@ -146,7 +147,7 @@ class UserTemplateDialog(QDialog):
146
147
  with open(filepath, 'r', encoding='utf-8') as f:
147
148
  return json.load(f)
148
149
  except Exception as e:
149
- print(f"Error loading template file {filepath}: {e}")
150
+ logging.error(f"Error loading template file {filepath}: {e}")
150
151
  return None
151
152
 
152
153
  def save_template_file(self, filepath, template_data):
@@ -156,7 +157,7 @@ class UserTemplateDialog(QDialog):
156
157
  json.dump(template_data, f, indent=2, ensure_ascii=False)
157
158
  return True
158
159
  except Exception as e:
159
- print(f"Error saving template file {filepath}: {e}")
160
+ logging.error(f"Error saving template file {filepath}: {e}")
160
161
  return False
161
162
 
162
163
  def update_template_grid(self):
@@ -253,7 +254,7 @@ class UserTemplateDialog(QDialog):
253
254
  if view and not rect.isEmpty():
254
255
  view.fitInView(rect, Qt.AspectRatioMode.KeepAspectRatio)
255
256
  except Exception as e:
256
- print(f"Warning: Failed to fit preview view: {e}")
257
+ logging.warning(f"Warning: Failed to fit preview view: {e}")
257
258
 
258
259
  def draw_template_preview(self, scene, template_data, view_size=None):
259
260
  """テンプレートプレビューを描画 - fitInView縮小率に基づく動的スケーリング"""
@@ -303,7 +304,7 @@ class UserTemplateDialog(QDialog):
303
304
  scale_factor = 4.0
304
305
 
305
306
  # Debug info (can be removed in production)
306
- # print(f"Mol size: {mol_size:.1f}, Fit scale: {fit_scale:.3f}, Scale factor: {scale_factor:.2f}")
307
+ # logging.debug(f"Mol size: {mol_size:.1f}, Fit scale: {fit_scale:.3f}, Scale factor: {scale_factor:.2f}")
307
308
  else:
308
309
  scale_factor = 1.0
309
310
 
@@ -481,7 +482,7 @@ class UserTemplateDialog(QDialog):
481
482
  except Exception:
482
483
  pass
483
484
  except Exception as e:
484
- print(f"Warning: Failed to switch main window to template mode: {e}")
485
+ logging.warning(f"Warning: Failed to switch main window to template mode: {e}")
485
486
 
486
487
  def use_template(self, template_data):
487
488
  """テンプレートを使用(エディタに適用)"""
@@ -518,7 +519,7 @@ class UserTemplateDialog(QDialog):
518
519
  # Mark selected and keep dialog open
519
520
  self.selected_template = template_data
520
521
  except Exception as e:
521
- print(f"Warning: Failed to switch main window to template mode: {e}")
522
+ logging.warning(f"Warning: Failed to switch main window to template mode: {e}")
522
523
 
523
524
  # Don't close dialog - keep it open for easy template switching
524
525
  # self.accept()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy
3
- Version: 1.18.0
3
+ Version: 2.0.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
  License: GNU GENERAL PUBLIC LICENSE
@@ -684,7 +684,6 @@ Classifier: Programming Language :: Python :: 3
684
684
  Classifier: Operating System :: OS Independent
685
685
  Classifier: Intended Audience :: Science/Research
686
686
  Classifier: Topic :: Scientific/Engineering :: Chemistry
687
- Classifier: Programming Language :: Python :: 3.8
688
687
  Classifier: Programming Language :: Python :: 3.9
689
688
  Classifier: Programming Language :: Python :: 3.10
690
689
  Classifier: Programming Language :: Python :: 3.11
@@ -760,6 +759,7 @@ This application combines a modern GUI built with **PyQt6**, powerful cheminform
760
759
  * Import structures from **MOL/SDF** files or **SMILES** strings.
761
760
  * Export 3D structures to **MOL** or **XYZ** formats, which are compatible with most DFT calculation software.
762
761
  * Export 2D and 3D views as high-resolution PNG images.
762
+ * **Plugin System:** Extend functionality with Python scripts. Place custom scripts in `~/.moleditpy/plugins` to add new features to the "Plugin" menu.
763
763
 
764
764
  ## Installation and Execution
765
765
 
@@ -874,6 +874,7 @@ This project is licensed under the **GNU General Public License v3.0 (GPL-v3)**.
874
874
  * **MOL/SDF**ファイルや**SMILES**文字列から構造をインポートできます。
875
875
  * 3D構造を**MOL**または**XYZ**形式でエクスポートでき、これらは多くのDFT計算ソフトウェアと互換性があります。
876
876
  * 2Dおよび3Dビューを高解像度のPNG画像としてエクスポートできます。
877
+ * **プラグインシステム:** Pythonスクリプトで機能を拡張できます。`~/.moleditpy/plugins` にスクリプトを配置することで、「Plugin」メニューに独自の機能を追加できます。
877
878
 
878
879
  ## インストールと実行
879
880