MoleditPy 4.9.1__tar.gz → 4.9.2__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.
Files changed (85) hide show
  1. {moleditpy-4.9.1 → moleditpy-4.9.2}/PKG-INFO +1 -1
  2. {moleditpy-4.9.1 → moleditpy-4.9.2}/pyproject.toml +1 -1
  3. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/MoleditPy.egg-info/PKG-INFO +1 -1
  4. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/core/mol_geometry.py +2 -0
  5. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/core/molecular_data.py +2 -0
  6. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/main.py +2 -0
  7. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/plugins/plugin_interface.py +4 -0
  8. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/plugins/plugin_manager.py +1 -0
  9. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/plugins/plugin_manager_window.py +1 -0
  10. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/about_dialog.py +1 -0
  11. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/align_plane_dialog.py +1 -0
  12. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/alignment_dialog.py +1 -0
  13. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/analysis_window.py +1 -0
  14. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/angle_dialog.py +2 -0
  15. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/app_state.py +2 -0
  16. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/atom_item.py +1 -0
  17. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/atom_picking.py +3 -0
  18. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/base_picking_dialog.py +1 -0
  19. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/bond_item.py +2 -0
  20. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/bond_length_dialog.py +2 -0
  21. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/calculation_worker.py +5 -0
  22. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/chain_mixin.py +1 -0
  23. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/color_settings_dialog.py +1 -0
  24. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/compute_logic.py +10 -0
  25. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/constrained_optimization_dialog.py +5 -0
  26. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/custom_interactor_style.py +12 -0
  27. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/custom_qt_interactor.py +1 -0
  28. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/dialog_3d_picking_mixin.py +2 -0
  29. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/dialog_logic.py +1 -0
  30. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/dihedral_dialog.py +1 -0
  31. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/edit_3d_logic.py +1 -0
  32. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/edit_actions_logic.py +5 -0
  33. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/export_logic.py +1 -0
  34. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/geometry_base_dialog.py +1 -0
  35. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/io_logic.py +4 -0
  36. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/main_window.py +1 -0
  37. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/main_window_init.py +6 -0
  38. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/mirror_dialog.py +1 -0
  39. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/molecular_scene_handler.py +9 -0
  40. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/molecule_scene.py +1 -0
  41. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/move_group_dialog.py +1 -0
  42. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/move_selected_atoms_dialog.py +2 -0
  43. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/periodic_table_dialog.py +1 -0
  44. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/planarize_dialog.py +1 -0
  45. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/plugin_menu_manager.py +6 -0
  46. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/preview_molecule.py +2 -0
  47. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/settings_dialog.py +4 -1
  48. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/settings_tabs/settings_2d_cleanup_tab.py +9 -2
  49. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/settings_tabs/settings_2d_tab.py +8 -2
  50. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/settings_tabs/settings_3d_tabs.py +17 -4
  51. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/settings_tabs/settings_other_tab.py +15 -4
  52. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/settings_tabs/settings_tab_base.py +22 -3
  53. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/string_importers.py +3 -0
  54. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/template_preview_item.py +1 -0
  55. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/template_preview_view.py +1 -0
  56. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/translation_dialog.py +5 -0
  57. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/ui_manager.py +2 -0
  58. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/user_template_dialog.py +1 -0
  59. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/view_3d_logic.py +9 -0
  60. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/zoomable_view.py +1 -0
  61. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/utils/constants.py +1 -0
  62. {moleditpy-4.9.1 → moleditpy-4.9.2}/LICENSE +0 -0
  63. {moleditpy-4.9.1 → moleditpy-4.9.2}/README.md +0 -0
  64. {moleditpy-4.9.1 → moleditpy-4.9.2}/setup.cfg +0 -0
  65. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/MoleditPy.egg-info/SOURCES.txt +0 -0
  66. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/MoleditPy.egg-info/dependency_links.txt +0 -0
  67. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/MoleditPy.egg-info/entry_points.txt +0 -0
  68. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/MoleditPy.egg-info/requires.txt +0 -0
  69. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/MoleditPy.egg-info/top_level.txt +0 -0
  70. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/__init__.py +0 -0
  71. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/__main__.py +0 -0
  72. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/assets/file_icon.ico +0 -0
  73. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/assets/icon.icns +0 -0
  74. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/assets/icon.ico +0 -0
  75. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/assets/icon.png +0 -0
  76. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/core/__init__.py +0 -0
  77. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/plugins/__init__.py +0 -0
  78. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/__init__.py +0 -0
  79. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/ui/settings_tabs/__init__.py +0 -0
  80. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/utils/__init__.py +0 -0
  81. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/utils/default_settings.py +0 -0
  82. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/utils/hit_radius.py +0 -0
  83. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/utils/sip_isdeleted_safe.py +0 -0
  84. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/utils/suppress_log.py +0 -0
  85. {moleditpy-4.9.1 → moleditpy-4.9.2}/src/moleditpy/utils/system_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy
3
- Version: 4.9.1
3
+ Version: 4.9.2
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
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "MoleditPy"
7
7
 
8
- version = "4.9.1"
8
+ version = "4.9.2"
9
9
 
10
10
  license = {file = "LICENSE"}
11
11
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy
3
- Version: 4.9.1
3
+ Version: 4.9.2
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
@@ -723,6 +723,7 @@ def resolve_2d_overlaps(
723
723
  parent = {aid: aid for aid in atom_ids}
724
724
 
725
725
  def find_set(aid: int) -> int:
726
+ """Find representative root for an element in disjoint-set."""
726
727
  # Iterative path compression avoids recursion limits
727
728
  root = aid
728
729
  while parent[root] != root:
@@ -732,6 +733,7 @@ def resolve_2d_overlaps(
732
733
  return root
733
734
 
734
735
  def unite_sets(aid1: int, aid2: int) -> None:
736
+ """Unite two sets in disjoint-set forest."""
735
737
  root1 = find_set(aid1)
736
738
  root2 = find_set(aid2)
737
739
  if root1 != root2:
@@ -126,6 +126,7 @@ class MolecularData:
126
126
  next_atom_id: int
127
127
 
128
128
  def __init__(self) -> None:
129
+ """Initialize MolecularData structure with atoms, bonds, and coordinates."""
129
130
  self.atoms = {}
130
131
  self.bonds = {}
131
132
  self.next_atom_id = 0
@@ -342,6 +343,7 @@ class MolecularData:
342
343
 
343
344
  # Helper: Pick neighbors prioritizing heavy atoms
344
345
  def pick_preferred_neighbor(atom: Chem.Atom, exclude_idx: int) -> Optional[int]:
346
+ """Select preferred neighbor atom for geometry alignment or reference."""
345
347
  for nbr in atom.GetNeighbors():
346
348
  if nbr.GetIdx() == exclude_idx:
347
349
  continue
@@ -77,6 +77,7 @@ class _ErrorDialogHandler(logging.Handler):
77
77
  _DEDUP_WINDOW_S = 10.0
78
78
 
79
79
  def __init__(self, log_path: Optional[str] = None) -> None:
80
+ """Initialize GUI error dialog logging handler with dedup tracking."""
80
81
  super().__init__(level=logging.ERROR)
81
82
  # signature -> monotonic timestamp of its last shown dialog.
82
83
  self._last_shown: Dict[str, float] = {}
@@ -85,6 +86,7 @@ class _ErrorDialogHandler(logging.Handler):
85
86
  self._log_path = log_path
86
87
 
87
88
  def emit(self, record: logging.LogRecord) -> None:
89
+ """Emit error dialog for unhandled error log record with deduplication."""
88
90
  try:
89
91
  if getattr(record, "no_dialog", False):
90
92
  return
@@ -21,6 +21,7 @@ class PluginContext:
21
21
  """
22
22
 
23
23
  def __init__(self, manager: Any, plugin_name: str) -> None:
24
+ """Initialize plugin execution context with host reference."""
24
25
  self._manager = manager
25
26
  self._plugin_name = plugin_name
26
27
 
@@ -206,6 +207,7 @@ class PluginContext:
206
207
 
207
208
  @current_mol.setter
208
209
  def current_mol(self, mol: Any) -> None:
210
+ """Set active RDKit molecule and redraw 3D view."""
209
211
  mw = self.get_main_window()
210
212
  if mw and hasattr(mw, "view_3d_manager"):
211
213
  mw.view_3d_manager.current_mol = mol
@@ -218,6 +220,7 @@ class PluginContext:
218
220
 
219
221
  @current_molecule.setter
220
222
  def current_molecule(self, mol: Any) -> None:
223
+ """Set active molecule via current_mol setter."""
221
224
  self.current_mol = mol
222
225
 
223
226
  @property
@@ -607,6 +610,7 @@ class Plugin3DController:
607
610
  """Helper to manipulate the 3D scene."""
608
611
 
609
612
  def __init__(self, main_window: Any) -> None:
613
+ """Initialize Plugin3DController with main window reference."""
610
614
  self._mw = main_window
611
615
 
612
616
  def _get_v3d(self) -> Optional[Any]:
@@ -76,6 +76,7 @@ class PluginManager:
76
76
  return "N/A"
77
77
 
78
78
  def __init__(self, main_window: Any = None) -> None:
79
+ """Initialize PluginManager with plugin search directories."""
79
80
  self.plugin_dir: str = os.path.join(
80
81
  os.path.expanduser("~"), ".moleditpy", "plugins"
81
82
  )
@@ -37,6 +37,7 @@ class PluginManagerWindow(QDialog):
37
37
  """Dialog for browsing, installing, and removing plugins."""
38
38
 
39
39
  def __init__(self, plugin_manager: Any, parent: Optional[QWidget] = None) -> None:
40
+ """Initialize plugin management dialog window."""
40
41
  super().__init__(parent)
41
42
  self.btn_remove: Any = None
42
43
  self.table: Any = None
@@ -33,6 +33,7 @@ class AboutDialog(QDialog):
33
33
  """Dialog showing application version, icon, and project links."""
34
34
 
35
35
  def __init__(self, main_window: Any, parent: Optional[QWidget] = None) -> None:
36
+ """Initialize About dialog with version and author info."""
36
37
  super().__init__(parent)
37
38
  self.image_label: Any = None
38
39
  self.main_window = main_window
@@ -45,6 +45,7 @@ class AlignPlaneDialog(BasePickingDialog):
45
45
  preselected_atoms: Optional[Sequence[int]] = None,
46
46
  parent: Optional[QWidget] = None,
47
47
  ) -> None:
48
+ """Initialize align plane dialog with target geometry settings."""
48
49
  super().__init__(mol, main_window, parent)
49
50
  self.plane = plane
50
51
  self._selected_atoms = SelectionList()
@@ -49,6 +49,7 @@ class AlignmentDialog(Dialog3DPickingMixin, QDialog):
49
49
  preselected_atoms: Optional[Sequence[int]] = None,
50
50
  parent: Optional[QWidget] = None,
51
51
  ) -> None:
52
+ """Initialize alignment dialog with reference axis settings."""
52
53
  QDialog.__init__(self, parent)
53
54
  Dialog3DPickingMixin.__init__(self)
54
55
  self.mol = mol
@@ -41,6 +41,7 @@ class AnalysisWindow(QDialog):
41
41
  parent: Optional[QWidget] = None,
42
42
  is_xyz_derived: bool = False,
43
43
  ) -> None:
44
+ """Initialize analysis window for molecular properties."""
44
45
  super().__init__(parent)
45
46
  self.mol = mol
46
47
  self.is_xyz_derived = is_xyz_derived # Flag indicating if derived from XYZ
@@ -51,6 +51,7 @@ class AngleDialog(GeometryBaseDialog):
51
51
  preselected_atoms: Optional[Sequence[int]] = None,
52
52
  parent: Optional[QWidget] = None,
53
53
  ) -> None:
54
+ """Initialize bond angle adjustment dialog."""
54
55
  super().__init__(mol, main_window, parent)
55
56
  self._baseline_positions: Any = None
56
57
  self._snapshot_positions = None
@@ -384,6 +385,7 @@ class AngleDialog(GeometryBaseDialog):
384
385
  self._push_undo()
385
386
 
386
387
  def _is_selection_complete(self) -> bool:
388
+ """Check whether required three atoms have been picked."""
387
389
  return (
388
390
  self.atom1_idx is not None
389
391
  and self.atom2_idx is not None
@@ -74,6 +74,7 @@ class StateManager:
74
74
  """Manages serialized undo/redo state and document save state for MainWindow."""
75
75
 
76
76
  def __init__(self, host: MainWindow) -> None:
77
+ """Initialize StateManager with reference to host window."""
77
78
  self.host = host
78
79
  self.data: MolecularData # Dynamically assigned in main_window_init.py
79
80
  self.has_unsaved_changes = False
@@ -139,6 +140,7 @@ class StateManager:
139
140
  file_version_str = loaded_data.get("version", "0.0.0")
140
141
 
141
142
  def parse_v(v_str: str) -> Tuple[int, ...]:
143
+ """Parse version string into tuple of integers."""
142
144
  if not v_str or not isinstance(v_str, str):
143
145
  return (0, 0, 0)
144
146
  try:
@@ -78,6 +78,7 @@ class AtomItem(QGraphicsItem):
78
78
  def __init__(
79
79
  self, atom_id: int, symbol: str, pos: QPointF, charge: int = 0, radical: int = 0
80
80
  ) -> None:
81
+ """Initialize 2D atom graphics item."""
81
82
  super().__init__()
82
83
  self.atom_id: int = atom_id
83
84
  self.symbol: str = symbol
@@ -21,6 +21,7 @@ from ..utils.constants import VDW_DISPLAY_RADII, pt
21
21
 
22
22
 
23
23
  def _world_to_display(renderer: Any, pos: Any) -> Optional[tuple[float, float, float]]:
24
+ """Project 3D world coordinates to 2D display viewport pixels."""
24
25
  try:
25
26
  renderer.SetWorldPoint(float(pos[0]), float(pos[1]), float(pos[2]), 1.0)
26
27
  renderer.WorldToDisplay()
@@ -31,6 +32,7 @@ def _world_to_display(renderer: Any, pos: Any) -> Optional[tuple[float, float, f
31
32
 
32
33
 
33
34
  def _atom_world_radius(view_3d_manager: Any, mol: Any, atom_idx: int) -> float:
35
+ """Calculate effective atom sphere radius in 3D world units."""
34
36
  try:
35
37
  atom = mol.GetAtomWithIdx(int(atom_idx))
36
38
  symbol = atom.GetSymbol()
@@ -68,6 +70,7 @@ def _atom_world_radius(view_3d_manager: Any, mol: Any, atom_idx: int) -> float:
68
70
  def _projected_radius_px(
69
71
  renderer: Any, center: Any, world_radius: float
70
72
  ) -> Optional[float]:
73
+ """Calculate projected atom radius on screen in display pixels."""
71
74
  center_display = _world_to_display(renderer, center)
72
75
  if center_display is None:
73
76
  return None
@@ -57,6 +57,7 @@ class BasePickingDialog(Dialog3DPickingMixin, QDialog):
57
57
  main_window: "MainWindow",
58
58
  parent: Optional[QWidget] = None,
59
59
  ) -> None:
60
+ """Initialize base atom picking dialog."""
60
61
  QDialog.__init__(self, parent)
61
62
  Dialog3DPickingMixin.__init__(self)
62
63
  self.mol = mol
@@ -120,6 +120,7 @@ class BondItem(QGraphicsItem):
120
120
  """Shorten inner ring double bonds based on the ring angle at both atoms."""
121
121
 
122
122
  def angle_between(v1: QPointF, v2: QPointF) -> Optional[float]:
123
+ """Calculate smaller unsigned angle between two 2D vectors in radians."""
123
124
  len1 = math.hypot(v1.x(), v1.y())
124
125
  len2 = math.hypot(v2.x(), v2.y())
125
126
  if len1 <= 1e-6 or len2 <= 1e-6:
@@ -158,6 +159,7 @@ class BondItem(QGraphicsItem):
158
159
  order: int = 1,
159
160
  stereo: int = 0,
160
161
  ) -> None:
162
+ """Initialize 2D bond graphics item between two atoms."""
161
163
  super().__init__()
162
164
  # Validate input parameters
163
165
  if atom1_item is None or atom2_item is None:
@@ -48,6 +48,7 @@ class BondLengthDialog(GeometryBaseDialog):
48
48
  preselected_atoms: Optional[Sequence[int]] = None,
49
49
  parent: Optional[QWidget] = None,
50
50
  ) -> None:
51
+ """Initialize bond length adjustment dialog."""
51
52
  super().__init__(mol, main_window, parent)
52
53
  self._baseline_positions: Any = None
53
54
  self._snapshot_positions = None
@@ -325,6 +326,7 @@ class BondLengthDialog(GeometryBaseDialog):
325
326
  self._push_undo()
326
327
 
327
328
  def _is_selection_complete(self) -> bool:
329
+ """Check whether required two atoms have been picked."""
328
330
  return self.atom1_idx is not None and self.atom2_idx is not None
329
331
 
330
332
  def apply_geometry_update(self, new_distance: float) -> None: # pylint: disable=arguments-renamed
@@ -808,6 +808,7 @@ class CalculationWorker(QObject):
808
808
  start_work = pyqtSignal(str, object)
809
809
 
810
810
  def __init__(self, parent: Optional[QObject] = None) -> None:
811
+ """Initialize background calculation worker thread."""
811
812
  super().__init__(parent)
812
813
  self.halt_ids: Optional[Set[Any]] = None
813
814
  self.halt_all: bool = False
@@ -822,6 +823,7 @@ class CalculationWorker(QObject):
822
823
  w_id = options.get("worker_id")
823
824
 
824
825
  def _check_halted() -> bool:
826
+ """Check if current calculation worker has been requested to halt."""
825
827
  h_ids = getattr(self, "halt_ids", None)
826
828
  if self.halt_all:
827
829
  return True # type: ignore[return-value]
@@ -836,18 +838,21 @@ class CalculationWorker(QObject):
836
838
  # pylint: enable=unsupported-membership-test
837
839
 
838
840
  def _safe_status(msg: str) -> None:
841
+ """Emit calculation status update checking halt state."""
839
842
  if _check_halted():
840
843
  raise WorkerHaltError("Halted")
841
844
  with suppress_log(AttributeError, RuntimeError):
842
845
  self.status_update.emit(msg)
843
846
 
844
847
  def _safe_finished(payload: Any) -> None:
848
+ """Emit calculation finished signal checking halt state."""
845
849
  if _check_halted():
846
850
  raise WorkerHaltError("Halted")
847
851
  with suppress_log(AttributeError, RuntimeError, TypeError):
848
852
  self.finished.emit(payload)
849
853
 
850
854
  def _safe_error(msg: str) -> None:
855
+ """Emit calculation error signal handling graceful halt state."""
851
856
  # If we're already halting, don't raise another error
852
857
  if msg == "Halted":
853
858
  with suppress_log(AttributeError, RuntimeError, TypeError):
@@ -38,6 +38,7 @@ class ChainMixin:
38
38
  template_preview: Any
39
39
 
40
40
  def __init__(self, *args: Any, **kwargs: Any) -> None:
41
+ """Initialize aliphatic chain drawing tool mixin."""
41
42
  self.chain_anchor: Optional[QPointF] = None
42
43
  self.chain_start_atom: Optional[AtomItem] = None
43
44
  self.chain_end_atom: Optional[AtomItem] = None
@@ -37,6 +37,7 @@ class ColorSettingsDialog(QDialog):
37
37
  """
38
38
 
39
39
  def __init__(self, current_settings: Any, parent: Optional[QWidget] = None) -> None:
40
+ """Initialize CPK element color customization dialog."""
40
41
  super().__init__(parent)
41
42
  self.setWindowTitle("CPK Colors")
42
43
  self.parent_window: Any = parent
@@ -48,6 +48,7 @@ class ComputeManager:
48
48
  """Independent manager for molecular computations, ported from MainWindowCompute mixin."""
49
49
 
50
50
  def __init__(self, host: MainWindow) -> None:
51
+ """Initialize ComputeManager with reference to host window."""
51
52
  self._calculating_text_actor: Any = None
52
53
  self.original_atom_properties: Dict[int, int] = {}
53
54
  self.host = host
@@ -180,6 +181,7 @@ class ComputeManager:
180
181
  menu.exec(self.host.init_manager.convert_button.mapToGlobal(pos)) # type: ignore[union-attr]
181
182
 
182
183
  def _trigger_conversion_with_temp_mode(self, mode_key: str) -> None:
184
+ """Trigger 2D to 3D conversion with temporary mode override."""
183
185
  # Store mode as instance attribute so it reaches trigger_conversion
184
186
  # without passing a kwarg — plugins may wrap trigger_conversion without
185
187
  # forwarding **kwargs, which would cause a TypeError.
@@ -203,6 +205,7 @@ class ComputeManager:
203
205
  menu.exec(self.host.init_manager.optimize_3d_button.mapToGlobal(pos)) # type: ignore[union-attr]
204
206
 
205
207
  def _trigger_optimize_with_temp_method(self, method_key: str) -> None:
208
+ """Trigger 3D optimization with temporary method override."""
206
209
  QTimer.singleShot(0, lambda: self.optimize_3d_structure(method_key))
207
210
 
208
211
  def trigger_conversion(
@@ -429,6 +432,7 @@ class ComputeManager:
429
432
  self.host.update_status_message(f"Process completed ({label}).")
430
433
 
431
434
  def _prepare_rdkit_mol_for_conversion(self) -> Optional[Chem.Mol]:
435
+ """Prepare and sanitize RDKit molecule for 3D conversion."""
432
436
  mol = self.host.state_manager.data.to_rdkit_mol(use_2d_stereo=False)
433
437
  if not mol or mol.GetNumAtoms() == 0:
434
438
  self.check_chemistry_problems_fallback()
@@ -457,6 +461,7 @@ class ComputeManager:
457
461
  def _handle_chemistry_problems(
458
462
  self, mol: Chem.Mol, problems: Sequence[Any]
459
463
  ) -> None:
464
+ """Handle chemical sanitization issues before conversion."""
460
465
  self.host.init_manager.scene.clear_all_problem_flags()
461
466
  msg = f"Error: {len(problems)} chemistry problem(s) found (e.g., hypervalency). Fix the 2D layout before converting."
462
467
  self.host.statusBar().showMessage(msg) # type: ignore[union-attr]
@@ -498,6 +503,7 @@ class ComputeManager:
498
503
  return None
499
504
 
500
505
  def _setup_mol_block_for_worker(self, mol: Chem.Mol) -> str:
506
+ """Construct molecule block data for background worker."""
501
507
  ez_block = self._ez_consistent_mol_block()
502
508
  if ez_block:
503
509
  return ez_block
@@ -511,6 +517,7 @@ class ComputeManager:
511
517
  def _start_calculation_worker(
512
518
  self, mol_block: str, options: Dict[str, Any], run_id: int
513
519
  ) -> None:
520
+ """Start background thread calculation worker."""
514
521
  thread = QThread()
515
522
  worker = CalculationWorker()
516
523
  worker.halt_ids = self.halt_ids
@@ -518,6 +525,7 @@ class ComputeManager:
518
525
  worker.status_update.connect(self.host.ui_manager.update_status_bar)
519
526
 
520
527
  def _cleanup() -> None:
528
+ """Clean up calculation worker thread references."""
521
529
  thread.quit()
522
530
  thread.finished.connect(thread.deleteLater)
523
531
  worker.deleteLater()
@@ -525,12 +533,14 @@ class ComputeManager:
525
533
  self._active_calc_threads.remove(thread)
526
534
 
527
535
  def _on_finished(result: Any) -> None:
536
+ """Handle successful background calculation completion."""
528
537
  try:
529
538
  self.on_calculation_finished(result)
530
539
  finally:
531
540
  _cleanup()
532
541
 
533
542
  def _on_error(msg: Any) -> None:
543
+ """Handle background calculation failure."""
534
544
  try:
535
545
  self.on_calculation_error(msg)
536
546
  finally:
@@ -40,6 +40,7 @@ class ConstrainedOptimizationThread(QThread):
40
40
  error_occurred = pyqtSignal(str)
41
41
 
42
42
  def __init__(self, ff: Any, max_iters: int = 20000, parent: Any = None) -> None:
43
+ """Initialize constrained optimization worker thread."""
43
44
  super().__init__(parent)
44
45
  self.ff = ff
45
46
  self.max_iters = max_iters
@@ -57,6 +58,7 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
57
58
  """Dialog for constrained optimization."""
58
59
 
59
60
  def __init__(self, mol: Any, main_window: Any, parent: Any = None) -> None:
61
+ """Initialize constrained force field optimization dialog."""
60
62
  QDialog.__init__(self, parent)
61
63
  Dialog3DPickingMixin.__init__(self)
62
64
  self.mol = mol
@@ -251,6 +253,7 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
251
253
  layout.addLayout(main_buttons)
252
254
 
253
255
  def on_atom_picked(self, atom_idx: int) -> None:
256
+ """Handle atom selection event from 3D viewport."""
254
257
  if atom_idx in self.selected_atoms:
255
258
  self.selected_atoms.remove(atom_idx)
256
259
  else:
@@ -603,6 +606,7 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
603
606
  self.optimize_button.setEnabled(True)
604
607
 
605
608
  def _on_optimization_error(self, err_msg: str) -> None:
609
+ """Handle optimization thread failure callback."""
606
610
  if self._closed:
607
611
  logging.warning(
608
612
  "Constrained optimization failed after dialog close: %s", err_msg
@@ -615,6 +619,7 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
615
619
  QMessageBox.critical(self, "Error", f"Optimization error: {err_msg}")
616
620
 
617
621
  def _on_optimization_finished(self, ff_name: str, conf: Any) -> None:
622
+ """Handle optimization thread completion callback."""
618
623
  if self._closed:
619
624
  logging.info("Discarding constrained optimization result after close.")
620
625
  return
@@ -72,6 +72,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
72
72
  """VTK interactor style extending trackball-camera with 3D atom drag and measurement."""
73
73
 
74
74
  def __init__(self, main_window: Any = None, **kwargs: Any) -> None:
75
+ """Initialize custom PyVista interactor style with mouse handlers."""
75
76
  super().__init__(**kwargs)
76
77
  self.main_window = main_window
77
78
  # Custom state flags
@@ -416,6 +417,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
416
417
  if type(move_group_dialog).__name__ == "MoveSelectedAtomsDialog":
417
418
  # For MoveSelectedAtomsDialog, we toggle ONLY the clicked atom, no BFS!
418
419
  def _deferred_toggle() -> None:
420
+ """Toggle atom selection state in deferred callback."""
419
421
  try:
420
422
  move_group_dialog.on_atom_picked(clicked_atom_idx)
421
423
  except (AttributeError, RuntimeError):
@@ -477,6 +479,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
477
479
  move_group_dialog.selected_atoms.add(clicked_atom_idx)
478
480
 
479
481
  def _deferred_move_group_update() -> None:
482
+ """Update selected group translation in deferred callback."""
480
483
  try:
481
484
  move_group_dialog.show_atom_labels()
482
485
  move_group_dialog.update_display()
@@ -527,6 +530,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
527
530
  if True:
528
531
 
529
532
  def _deferred_measure() -> None:
533
+ """Measure distance between picked atoms in deferred callback."""
530
534
  try:
531
535
  mw.edit_3d_manager.handle_measurement_atom_selection(
532
536
  closest_atom_idx
@@ -846,6 +850,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
846
850
  _rt_mol = mw.view_3d_manager.current_mol
847
851
 
848
852
  def _deferred_rt_atom() -> None:
853
+ """Execute deferred real-time atom drag update."""
849
854
  try:
850
855
  mw.view_3d_manager.draw_molecule_3d(_rt_mol)
851
856
  except (AttributeError, RuntimeError, ValueError, TypeError):
@@ -906,6 +911,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
906
911
  _rt_dlg = move_group_dialog
907
912
 
908
913
  def _deferred_rt_grp() -> None:
914
+ """Execute deferred real-time group translation update."""
909
915
  try:
910
916
  mw.view_3d_manager.draw_molecule_3d(_rt_mol)
911
917
  _rt_dlg.show_atom_labels()
@@ -1054,6 +1060,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
1054
1060
  _rt_dlg = move_group_dialog
1055
1061
 
1056
1062
  def _deferred_rt_rot() -> None:
1063
+ """Execute deferred real-time group rotation update."""
1057
1064
  try:
1058
1065
  mw.view_3d_manager.draw_molecule_3d(_rt_mol)
1059
1066
  _rt_dlg.show_atom_labels()
@@ -1161,6 +1168,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
1161
1168
  _grp_dlg = move_group_dialog
1162
1169
 
1163
1170
  def _deferred_group_redraw() -> None:
1171
+ """Redraw moving group geometry in deferred callback."""
1164
1172
  mw.view_3d_manager.draw_molecule_3d(_grp_mol)
1165
1173
  mw.view_3d_manager.update_chiral_labels()
1166
1174
  _grp_dlg.show_atom_labels()
@@ -1186,6 +1194,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
1186
1194
  if not self._mouse_moved_during_drag and self._mouse_press_pos is not None:
1187
1195
  # Background click: deselect
1188
1196
  def _deferred_clear_move_group() -> None:
1197
+ """Clear active move group selection in deferred callback."""
1189
1198
  try:
1190
1199
  move_group_dialog.group_atoms.clear()
1191
1200
  move_group_dialog.selected_atoms.clear()
@@ -1205,6 +1214,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
1205
1214
  ):
1206
1215
  # Background click -> clear selection
1207
1216
  def _deferred_clear_measurement() -> None:
1217
+ """Clear active measurement lines in deferred callback."""
1208
1218
  try:
1209
1219
  mw.edit_3d_manager.clear_measurement_selection()
1210
1220
  except (AttributeError, RuntimeError):
@@ -1314,6 +1324,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
1314
1324
  _atom_mol = mw.view_3d_manager.current_mol
1315
1325
 
1316
1326
  def _deferred_atom_redraw() -> None:
1327
+ """Redraw moved atom geometry in deferred callback."""
1317
1328
  try:
1318
1329
  mw.view_3d_manager.draw_molecule_3d(_atom_mol)
1319
1330
  except (AttributeError, RuntimeError, ValueError, TypeError):
@@ -1340,6 +1351,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
1340
1351
  ]
1341
1352
 
1342
1353
  def _deferred_updates() -> None:
1354
+ """Process pending deferred viewport updates."""
1343
1355
  for fn in _update_calls:
1344
1356
  try:
1345
1357
  fn()
@@ -32,6 +32,7 @@ class CustomQtInteractor(QtInteractor):
32
32
  main_window: Optional[Any] = None,
33
33
  **kwargs: Any,
34
34
  ) -> None:
35
+ """Initialize embedded PyVista Qt interactor widget."""
35
36
  super().__init__(parent, **kwargs)
36
37
  self.main_window = main_window
37
38
 
@@ -86,6 +86,7 @@ class Dialog3DPickingMixin(_MixinBase):
86
86
  self.main_window.picking_consumed = True
87
87
 
88
88
  def _deferred_pick() -> None:
89
+ """Process deferred atom pick event in 3D viewport."""
89
90
  try:
90
91
  self.on_atom_picked(int(closest_atom_idx))
91
92
  except (AttributeError, RuntimeError):
@@ -140,6 +141,7 @@ class Dialog3DPickingMixin(_MixinBase):
140
141
  if hasattr(self, "clear_selection"):
141
142
 
142
143
  def _deferred_clear() -> None:
144
+ """Clear deferred picking state in 3D viewport."""
143
145
  try:
144
146
  self.clear_selection()
145
147
  except (AttributeError, RuntimeError):
@@ -52,6 +52,7 @@ class DialogManager:
52
52
  """
53
53
 
54
54
  def __init__(self, host: Any) -> None:
55
+ """Initialize DialogManager coordinating application dialogs."""
55
56
  self.host = host
56
57
 
57
58
  def _get_preselected_atoms_3d(self) -> List[int]:
@@ -51,6 +51,7 @@ class DihedralDialog(GeometryBaseDialog):
51
51
  preselected_atoms: Optional[Sequence[int]] = None,
52
52
  parent: Optional[QWidget] = None,
53
53
  ) -> None:
54
+ """Initialize dihedral angle adjustment dialog."""
54
55
  super().__init__(mol, main_window, parent)
55
56
  self._baseline_positions: Any = None
56
57
  self._snapshot_positions = None
@@ -39,6 +39,7 @@ class Edit3DManager:
39
39
  """Independent manager for 3D editing logic, ported from MainWindowEdit3d mixin."""
40
40
 
41
41
  def __init__(self, host: Any) -> None:
42
+ """Initialize 3D geometric transformation and editing manager."""
42
43
  self.host = host
43
44
  # State variables previously held by mixin
44
45
  self.measurement_mode = False
@@ -75,6 +75,7 @@ class Rotate2DDialog(QDialog):
75
75
  def __init__(
76
76
  self, parent: Optional[QWidget] = None, initial_angle: float = 0
77
77
  ) -> None:
78
+ """Initialize 2D structure rotation dialog."""
78
79
  super().__init__(parent)
79
80
  self.setWindowTitle("Rotate 2D")
80
81
  self.setFixedWidth(300)
@@ -122,6 +123,7 @@ class EditActionsManager:
122
123
  """Independent manager for molecular editing actions, ported from MainWindowEditActions mixin."""
123
124
 
124
125
  def __init__(self, host: MainWindow) -> None:
126
+ """Initialize EditActionsManager for clipboard and editing tools."""
125
127
  self.dragged_atom_info = None
126
128
  self.host = host
127
129
  # State variables previously held by mixin
@@ -665,6 +667,7 @@ class EditActionsManager:
665
667
 
666
668
  # Helper: determine bond_stereo for hydrogen
667
669
  def _choose_stereo(i: int) -> int:
670
+ """Determine stereo wedge or dash representation for bond."""
668
671
  # 0: plain, 1: wedge, 2: dash, 3: plain, 4+: all plain
669
672
  if i == 0:
670
673
  return 0
@@ -1122,6 +1125,7 @@ class EditActionsManager:
1122
1125
  problem_map = self._detect_chemistry_problems(mol)
1123
1126
 
1124
1127
  def _ui_closure() -> None:
1128
+ """Execute UI update closure callback."""
1125
1129
  self._apply_ui_h_counts(h_count_map, problem_map, my_token)
1126
1130
 
1127
1131
  try:
@@ -1265,6 +1269,7 @@ class EditActionsManager:
1265
1269
  from moleditpy.core.mol_geometry import resolve_2d_overlaps
1266
1270
 
1267
1271
  def has_bond_check(id1: int, id2: int) -> bool:
1272
+ """Check if bond already exists between atom pair."""
1268
1273
  item1 = self.host.init_manager.scene.atom_items.get(id1)
1269
1274
  item2 = self.host.init_manager.scene.atom_items.get(id2)
1270
1275
  return (
@@ -35,6 +35,7 @@ class ExportManager:
35
35
  """Independent manager for export logic, ported from MainWindowExport mixin."""
36
36
 
37
37
  def __init__(self, host: Any) -> None:
38
+ """Initialize ExportManager for file and scene exports."""
38
39
  self.host = host
39
40
 
40
41
  def _get_default_basename(self) -> str: