MoleditPy-linux 4.8.2__tar.gz → 4.9.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.
Files changed (85) hide show
  1. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/PKG-INFO +2 -2
  2. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/README.md +1 -1
  3. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/pyproject.toml +1 -1
  4. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/MoleditPy_linux.egg-info/PKG-INFO +2 -2
  5. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/MoleditPy_linux.egg-info/SOURCES.txt +1 -0
  6. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/core/mol_geometry.py +54 -3
  7. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/core/molecular_data.py +1 -1
  8. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/main.py +2 -2
  9. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/plugins/plugin_interface.py +12 -10
  10. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/plugins/plugin_manager.py +30 -15
  11. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/analysis_window.py +8 -5
  12. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/app_state.py +13 -7
  13. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/atom_item.py +7 -7
  14. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/base_picking_dialog.py +2 -2
  15. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/bond_item.py +5 -5
  16. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/calculation_worker.py +34 -26
  17. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/compute_logic.py +23 -6
  18. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/constrained_optimization_dialog.py +7 -5
  19. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/custom_interactor_style.py +4 -1
  20. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/custom_qt_interactor.py +5 -0
  21. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/dialog_3d_picking_mixin.py +8 -1
  22. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/edit_actions_logic.py +42 -5
  23. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/export_logic.py +2 -2
  24. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/io_logic.py +27 -8
  25. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/main_window_init.py +2 -2
  26. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/molecular_scene_handler.py +6 -6
  27. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/molecule_scene.py +6 -2
  28. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/move_group_dialog.py +3 -3
  29. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/move_selected_atoms_dialog.py +3 -3
  30. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/planarize_dialog.py +1 -1
  31. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/plugin_menu_manager.py +3 -1
  32. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/settings_dialog.py +4 -0
  33. moleditpy_linux-4.9.0/src/moleditpy_linux/ui/settings_tabs/settings_2d_cleanup_tab.py +146 -0
  34. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/settings_tabs/settings_2d_tab.py +6 -5
  35. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/string_importers.py +7 -7
  36. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/template_preview_item.py +2 -2
  37. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/translation_dialog.py +14 -2
  38. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/view_3d_logic.py +20 -20
  39. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/utils/default_settings.py +11 -6
  40. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/LICENSE +0 -0
  41. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/setup.cfg +0 -0
  42. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/MoleditPy_linux.egg-info/dependency_links.txt +0 -0
  43. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/MoleditPy_linux.egg-info/entry_points.txt +0 -0
  44. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/MoleditPy_linux.egg-info/requires.txt +0 -0
  45. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/MoleditPy_linux.egg-info/top_level.txt +0 -0
  46. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/__init__.py +0 -0
  47. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/__main__.py +0 -0
  48. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/assets/file_icon.ico +0 -0
  49. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/assets/icon.icns +0 -0
  50. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/assets/icon.ico +0 -0
  51. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/assets/icon.png +0 -0
  52. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/core/__init__.py +0 -0
  53. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/plugins/__init__.py +0 -0
  54. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/plugins/plugin_manager_window.py +0 -0
  55. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/__init__.py +0 -0
  56. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/about_dialog.py +0 -0
  57. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/align_plane_dialog.py +0 -0
  58. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/alignment_dialog.py +0 -0
  59. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/angle_dialog.py +0 -0
  60. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/atom_picking.py +0 -0
  61. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/bond_length_dialog.py +0 -0
  62. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/chain_mixin.py +0 -0
  63. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/color_settings_dialog.py +0 -0
  64. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/dialog_logic.py +0 -0
  65. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/dihedral_dialog.py +0 -0
  66. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/edit_3d_logic.py +0 -0
  67. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/geometry_base_dialog.py +0 -0
  68. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/main_window.py +0 -0
  69. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/mirror_dialog.py +0 -0
  70. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/periodic_table_dialog.py +0 -0
  71. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/preview_molecule.py +0 -0
  72. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/settings_tabs/__init__.py +0 -0
  73. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/settings_tabs/settings_3d_tabs.py +0 -0
  74. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/settings_tabs/settings_other_tab.py +0 -0
  75. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/settings_tabs/settings_tab_base.py +0 -0
  76. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/template_preview_view.py +0 -0
  77. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/ui_manager.py +0 -0
  78. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/user_template_dialog.py +0 -0
  79. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/ui/zoomable_view.py +0 -0
  80. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/utils/__init__.py +0 -0
  81. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/utils/constants.py +0 -0
  82. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/utils/hit_radius.py +0 -0
  83. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/utils/sip_isdeleted_safe.py +0 -0
  84. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/utils/suppress_log.py +0 -0
  85. {moleditpy_linux-4.8.2 → moleditpy_linux-4.9.0}/src/moleditpy_linux/utils/system_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy-linux
3
- Version: 4.8.2
3
+ Version: 4.9.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
@@ -709,7 +709,7 @@ Dynamic: license-file
709
709
  [![Python Versions](https://img.shields.io/pypi/pyversions/MoleditPy.svg)](https://pypi.org/project/MoleditPy/)
710
710
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
711
711
  [![Build Status](https://github.com/HiroYokoyama/python_molecular_editor/actions/workflows/tests.yml/badge.svg)](https://github.com/HiroYokoyama/python_molecular_editor/actions)
712
- [![Overall Coverage](https://img.shields.io/badge/coverage->80%25-green)](https://github.com/HiroYokoyama/python_molecular_editor/blob/main/tests/coverage_report.md)
712
+ [![codecov](https://codecov.io/gh/HiroYokoyama/python_molecular_editor/graph/badge.svg)](https://codecov.io/gh/HiroYokoyama/python_molecular_editor)
713
713
  [![GUI Status](https://img.shields.io/badge/GUI-Manually_Verified-blue)](https://github.com/HiroYokoyama/python_molecular_editor/blob/main/tests/MANUAL_CHECKLIST.md)
714
714
  [![Pylint Score](https://img.shields.io/badge/pylint->9%2F10-brightgreen)](https://github.com/HiroYokoyama/python_molecular_editor/blob/main/tests/pylint-score.txt)
715
715
  [![PyPI Downloads](https://static.pepy.tech/personalized-badge/moleditpy?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/moleditpy)
@@ -6,7 +6,7 @@
6
6
  [![Python Versions](https://img.shields.io/pypi/pyversions/MoleditPy.svg)](https://pypi.org/project/MoleditPy/)
7
7
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
8
8
  [![Build Status](https://github.com/HiroYokoyama/python_molecular_editor/actions/workflows/tests.yml/badge.svg)](https://github.com/HiroYokoyama/python_molecular_editor/actions)
9
- [![Overall Coverage](https://img.shields.io/badge/coverage->80%25-green)](https://github.com/HiroYokoyama/python_molecular_editor/blob/main/tests/coverage_report.md)
9
+ [![codecov](https://codecov.io/gh/HiroYokoyama/python_molecular_editor/graph/badge.svg)](https://codecov.io/gh/HiroYokoyama/python_molecular_editor)
10
10
  [![GUI Status](https://img.shields.io/badge/GUI-Manually_Verified-blue)](https://github.com/HiroYokoyama/python_molecular_editor/blob/main/tests/MANUAL_CHECKLIST.md)
11
11
  [![Pylint Score](https://img.shields.io/badge/pylint->9%2F10-brightgreen)](https://github.com/HiroYokoyama/python_molecular_editor/blob/main/tests/pylint-score.txt)
12
12
  [![PyPI Downloads](https://static.pepy.tech/personalized-badge/moleditpy?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/moleditpy)
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "MoleditPy-linux"
7
7
 
8
- version = "4.8.2"
8
+ version = "4.9.0"
9
9
 
10
10
  license = {file = "LICENSE"}
11
11
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy-linux
3
- Version: 4.8.2
3
+ Version: 4.9.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
@@ -709,7 +709,7 @@ Dynamic: license-file
709
709
  [![Python Versions](https://img.shields.io/pypi/pyversions/MoleditPy.svg)](https://pypi.org/project/MoleditPy/)
710
710
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
711
711
  [![Build Status](https://github.com/HiroYokoyama/python_molecular_editor/actions/workflows/tests.yml/badge.svg)](https://github.com/HiroYokoyama/python_molecular_editor/actions)
712
- [![Overall Coverage](https://img.shields.io/badge/coverage->80%25-green)](https://github.com/HiroYokoyama/python_molecular_editor/blob/main/tests/coverage_report.md)
712
+ [![codecov](https://codecov.io/gh/HiroYokoyama/python_molecular_editor/graph/badge.svg)](https://codecov.io/gh/HiroYokoyama/python_molecular_editor)
713
713
  [![GUI Status](https://img.shields.io/badge/GUI-Manually_Verified-blue)](https://github.com/HiroYokoyama/python_molecular_editor/blob/main/tests/MANUAL_CHECKLIST.md)
714
714
  [![Pylint Score](https://img.shields.io/badge/pylint->9%2F10-brightgreen)](https://github.com/HiroYokoyama/python_molecular_editor/blob/main/tests/pylint-score.txt)
715
715
  [![PyPI Downloads](https://static.pepy.tech/personalized-badge/moleditpy?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/moleditpy)
@@ -69,6 +69,7 @@ src/moleditpy_linux/ui/user_template_dialog.py
69
69
  src/moleditpy_linux/ui/view_3d_logic.py
70
70
  src/moleditpy_linux/ui/zoomable_view.py
71
71
  src/moleditpy_linux/ui/settings_tabs/__init__.py
72
+ src/moleditpy_linux/ui/settings_tabs/settings_2d_cleanup_tab.py
72
73
  src/moleditpy_linux/ui/settings_tabs/settings_2d_tab.py
73
74
  src/moleditpy_linux/ui/settings_tabs/settings_3d_tabs.py
74
75
  src/moleditpy_linux/ui/settings_tabs/settings_other_tab.py
@@ -591,12 +591,63 @@ def _identify_problems_rdkit(
591
591
  return None
592
592
 
593
593
 
594
- def optimize_2d_coords(mol: Chem.Mol) -> Dict[int, Tuple[float, float]]:
594
+ _TARGET_BOND_LENGTH = 1.5 # matches RDKit's default (non-CoordGen) bond length
595
+
596
+
597
+ def _normalize_bond_length(mol: Chem.Mol, target_length: float) -> None:
598
+ """Rescale a molecule's 2D conformer so its average bond length matches target_length."""
599
+ if mol.GetNumBonds() == 0:
600
+ return
601
+ conf = mol.GetConformer()
602
+ total_len = 0.0
603
+ for bond in mol.GetBonds():
604
+ p1 = conf.GetAtomPosition(bond.GetBeginAtomIdx())
605
+ p2 = conf.GetAtomPosition(bond.GetEndAtomIdx())
606
+ total_len += ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5
607
+ avg_len = total_len / mol.GetNumBonds()
608
+ if avg_len <= 1e-6:
609
+ return
610
+ factor = target_length / avg_len
611
+ for i in range(mol.GetNumAtoms()):
612
+ pos = conf.GetAtomPosition(i)
613
+ conf.SetAtomPosition(i, (pos.x * factor, pos.y * factor, pos.z))
614
+
615
+
616
+ def optimize_2d_coords(
617
+ mol: Chem.Mol,
618
+ prefer_coordgen: bool = False,
619
+ canonical_orientation: bool = True,
620
+ use_ring_templates: bool = False,
621
+ straighten_bonds: bool = False,
622
+ avoid_clashes: bool = False,
623
+ ) -> Dict[int, Tuple[float, float]]:
595
624
  """Generate 2D coordinates using RDKit and return a map of (x, y) tuples."""
596
- from rdkit.Chem import AllChem
625
+ from rdkit.Chem import rdDepictor
597
626
 
598
- AllChem.Compute2DCoords(mol)
627
+ sample_kwargs = (
628
+ {"nFlipsPerSample": 3, "nSample": 200, "sampleSeed": 0xF00D}
629
+ if avoid_clashes
630
+ else {}
631
+ )
632
+
633
+ rdDepictor.SetPreferCoordGen(prefer_coordgen)
634
+ try:
635
+ rdDepictor.Compute2DCoords(
636
+ mol,
637
+ canonOrient=canonical_orientation,
638
+ useRingTemplates=use_ring_templates,
639
+ **sample_kwargs, # type: ignore[arg-type] # RDKit's generated overload differs by platform.
640
+ )
641
+ if straighten_bonds:
642
+ rdDepictor.StraightenDepiction(mol)
643
+ finally:
644
+ rdDepictor.SetPreferCoordGen(False)
645
+ # CoordGen and the default generator use different internal bond-length
646
+ # conventions (~1.0 vs ~1.5), so normalize here to keep the on-screen
647
+ # scale consistent regardless of which algorithm produced the layout.
648
+ _normalize_bond_length(mol, _TARGET_BOND_LENGTH)
599
649
  conf = mol.GetConformer()
650
+
600
651
  new_positions = {}
601
652
  for rdkit_atom in mol.GetAtoms():
602
653
  if rdkit_atom.HasProp("_original_atom_id"):
@@ -18,7 +18,7 @@ from rdkit import Chem
18
18
  from ..utils.constants import ANGSTROM_PER_PIXEL
19
19
 
20
20
 
21
- class PointTuple(tuple):
21
+ class PointTuple(Tuple[float, ...]):
22
22
  """Backward-compatible tuple that allows .x() and .y() access like QPointF."""
23
23
 
24
24
  def x(self) -> float:
@@ -20,7 +20,7 @@ import sys
20
20
  import argparse
21
21
  import time
22
22
  import traceback
23
- from typing import Any, Dict, Optional
23
+ from typing import Any, Dict, Optional, Set
24
24
 
25
25
  from .utils.constants import VERSION
26
26
 
@@ -81,7 +81,7 @@ class _ErrorDialogHandler(logging.Handler):
81
81
  # signature -> monotonic timestamp of its last shown dialog.
82
82
  self._last_shown: Dict[str, float] = {}
83
83
  # Held so a non-blocking box is not garbage-collected before it closes.
84
- self._open_boxes: set = set()
84
+ self._open_boxes: Set[QMessageBox] = set()
85
85
  self._log_path = log_path
86
86
 
87
87
  def emit(self, record: logging.LogRecord) -> None:
@@ -27,7 +27,7 @@ class PluginContext:
27
27
  def add_menu_action(
28
28
  self,
29
29
  path: str,
30
- callback: Callable,
30
+ callback: Callable[..., Any],
31
31
  text: Optional[str] = None,
32
32
  icon: Optional[str] = None,
33
33
  shortcut: Optional[str] = None,
@@ -57,8 +57,8 @@ class PluginContext:
57
57
  def register_menu_action(
58
58
  self,
59
59
  path: str,
60
- text_or_callback: Union[str, Callable],
61
- callback: Optional[Callable] = None,
60
+ text_or_callback: Union[str, Callable[..., Any]],
61
+ callback: Optional[Callable[..., Any]] = None,
62
62
  icon: Optional[str] = None,
63
63
  shortcut: Optional[str] = None,
64
64
  ) -> None:
@@ -76,7 +76,7 @@ class PluginContext:
76
76
  def add_plugin_menu(
77
77
  self,
78
78
  path: str,
79
- callback: Callable,
79
+ callback: Callable[..., Any],
80
80
  text: Optional[str] = None,
81
81
  icon: Optional[str] = None,
82
82
  shortcut: Optional[str] = None,
@@ -103,7 +103,7 @@ class PluginContext:
103
103
 
104
104
  def add_toolbar_action(
105
105
  self,
106
- callback: Callable,
106
+ callback: Callable[..., Any],
107
107
  text: str,
108
108
  icon: Optional[str] = None,
109
109
  tooltip: Optional[str] = None,
@@ -262,7 +262,7 @@ class PluginContext:
262
262
  if mw and hasattr(mw, "view_3d_manager") and mw.view_3d_manager.plotter:
263
263
  mw.view_3d_manager.plotter.reset_camera()
264
264
 
265
- def add_export_action(self, label: str, callback: Callable) -> None:
265
+ def add_export_action(self, label: str, callback: Callable[..., Any]) -> None:
266
266
  """
267
267
  Register a custom export action.
268
268
 
@@ -303,7 +303,7 @@ class PluginContext:
303
303
  self._plugin_name, extension, callback, priority
304
304
  )
305
305
 
306
- def add_analysis_tool(self, label: str, callback: Callable) -> None:
306
+ def add_analysis_tool(self, label: str, callback: Callable[..., Any]) -> None:
307
307
  """
308
308
  Register a tool in the Analysis menu.
309
309
 
@@ -313,7 +313,7 @@ class PluginContext:
313
313
  """
314
314
  self._manager.register_analysis_tool(self._plugin_name, label, callback)
315
315
 
316
- def register_save_handler(self, callback: Callable[[], dict]) -> None:
316
+ def register_save_handler(self, callback: Callable[[], Dict[str, Any]]) -> None:
317
317
  """
318
318
  Register a callback to save state into the project file.
319
319
 
@@ -322,7 +322,7 @@ class PluginContext:
322
322
  """
323
323
  self._manager.register_save_handler(self._plugin_name, callback)
324
324
 
325
- def register_load_handler(self, callback: Callable[[dict], None]) -> None:
325
+ def register_load_handler(self, callback: Callable[[Dict[str, Any]], None]) -> None:
326
326
  """
327
327
  Register a callback to restore state from the project file.
328
328
 
@@ -331,7 +331,9 @@ class PluginContext:
331
331
  """
332
332
  self._manager.register_load_handler(self._plugin_name, callback)
333
333
 
334
- def register_3d_context_menu(self, callback: Callable, label: str) -> None:
334
+ def register_3d_context_menu(
335
+ self, callback: Callable[..., Any], label: str
336
+ ) -> None:
335
337
  """Deprecated: This method does nothing. Kept for backward compatibility."""
336
338
  import warnings
337
339
 
@@ -92,8 +92,8 @@ class PluginManager:
92
92
  self.optimization_methods: Dict[str, Dict[str, Any]] = {}
93
93
  self.file_openers: Dict[str, List[Dict[str, Any]]] = {}
94
94
  self.analysis_tools: List[Dict[str, Any]] = []
95
- self.save_handlers: Dict[str, Callable] = {}
96
- self.load_handlers: Dict[str, Callable] = {}
95
+ self.save_handlers: Dict[str, Callable[..., Any]] = {}
96
+ self.load_handlers: Dict[str, Callable[..., Any]] = {}
97
97
  self.custom_3d_styles: Dict[str, Dict[str, Any]] = {}
98
98
  self.document_reset_handlers: List[Dict[str, Any]] = []
99
99
  self.atom_drag_handlers: List[Dict[str, Any]] = []
@@ -447,7 +447,7 @@ class PluginManager:
447
447
  self,
448
448
  plugin_name: str,
449
449
  path: str,
450
- callback: Callable,
450
+ callback: Callable[..., Any],
451
451
  text: str,
452
452
  icon: str,
453
453
  shortcut: str,
@@ -471,7 +471,12 @@ class PluginManager:
471
471
  )
472
472
 
473
473
  def register_toolbar_action(
474
- self, plugin_name: str, callback: Callable, text: str, icon: str, tooltip: str
474
+ self,
475
+ plugin_name: str,
476
+ callback: Callable[..., Any],
477
+ text: str,
478
+ icon: str,
479
+ tooltip: str,
475
480
  ) -> None:
476
481
  """Register a plugin toolbar button with its callback and display metadata."""
477
482
  self.toolbar_actions.append(
@@ -485,7 +490,7 @@ class PluginManager:
485
490
  )
486
491
 
487
492
  def register_drop_handler(
488
- self, plugin_name: str, callback: Callable, priority: int
493
+ self, plugin_name: str, callback: Callable[..., Any], priority: int
489
494
  ) -> None:
490
495
  """Register a drag-and-drop handler with a given priority."""
491
496
  self.drop_handlers.append(
@@ -495,7 +500,7 @@ class PluginManager:
495
500
  self.drop_handlers.sort(key=lambda x: x["priority"], reverse=True)
496
501
 
497
502
  def register_export_action(
498
- self, plugin_name: str, label: str, callback: Callable
503
+ self, plugin_name: str, label: str, callback: Callable[..., Any]
499
504
  ) -> None:
500
505
  """Register a plugin export action with its label and callback."""
501
506
  self.export_actions.append(
@@ -503,7 +508,7 @@ class PluginManager:
503
508
  )
504
509
 
505
510
  def register_optimization_method(
506
- self, plugin_name: str, method_name: str, callback: Callable
511
+ self, plugin_name: str, method_name: str, callback: Callable[..., Any]
507
512
  ) -> None:
508
513
  """Register a named 3D optimization method provided by a plugin."""
509
514
  method_key = method_name.upper()
@@ -520,7 +525,11 @@ class PluginManager:
520
525
  )
521
526
 
522
527
  def register_file_opener(
523
- self, plugin_name: str, extension: str, callback: Callable, priority: int = 0
528
+ self,
529
+ plugin_name: str,
530
+ extension: str,
531
+ callback: Callable[..., Any],
532
+ priority: int = 0,
524
533
  ) -> None:
525
534
  """Register a file-type handler for the given extension."""
526
535
  # Normalize extension to lowercase
@@ -540,7 +549,7 @@ class PluginManager:
540
549
 
541
550
  # Analysis Tools registration
542
551
  def register_analysis_tool(
543
- self, plugin_name: str, label: str, callback: Callable
552
+ self, plugin_name: str, label: str, callback: Callable[..., Any]
544
553
  ) -> None:
545
554
  """Register a plugin analysis tool with its label and callback."""
546
555
  self.analysis_tools.append(
@@ -548,16 +557,20 @@ class PluginManager:
548
557
  )
549
558
 
550
559
  # State Persistence registration
551
- def register_save_handler(self, plugin_name: str, callback: Callable) -> None:
560
+ def register_save_handler(
561
+ self, plugin_name: str, callback: Callable[..., Any]
562
+ ) -> None:
552
563
  """Register a plugin session-save callback."""
553
564
  self.save_handlers[plugin_name] = callback
554
565
 
555
- def register_load_handler(self, plugin_name: str, callback: Callable) -> None:
566
+ def register_load_handler(
567
+ self, plugin_name: str, callback: Callable[..., Any]
568
+ ) -> None:
556
569
  """Register a plugin session-load callback."""
557
570
  self.load_handlers[plugin_name] = callback
558
571
 
559
572
  def register_3d_style(
560
- self, plugin_name: str, style_name: str, callback: Callable
573
+ self, plugin_name: str, style_name: str, callback: Callable[..., Any]
561
574
  ) -> None:
562
575
  """Register a named custom 3D rendering style."""
563
576
  self.custom_3d_styles[style_name] = {
@@ -566,14 +579,16 @@ class PluginManager:
566
579
  }
567
580
 
568
581
  def register_document_reset_handler(
569
- self, plugin_name: str, callback: Callable
582
+ self, plugin_name: str, callback: Callable[..., Any]
570
583
  ) -> None:
571
584
  """Register callback to be invoked when a new document is created."""
572
585
  self.document_reset_handlers.append(
573
586
  {"plugin": plugin_name, "callback": callback}
574
587
  )
575
588
 
576
- def register_atom_drag_handler(self, plugin_name: str, callback: Callable) -> None:
589
+ def register_atom_drag_handler(
590
+ self, plugin_name: str, callback: Callable[..., Any]
591
+ ) -> None:
577
592
  """Register a handler called during 3D atom/group dragging."""
578
593
  self.atom_drag_handlers.append({"plugin": plugin_name, "callback": callback})
579
594
 
@@ -676,7 +691,7 @@ class PluginManager:
676
691
  self,
677
692
  event_type: str,
678
693
  atom_indices: List[int],
679
- positions: Dict[int, tuple],
694
+ positions: Dict[int, Tuple[float, ...]],
680
695
  ) -> None:
681
696
  """Call all registered atom drag handlers."""
682
697
  for handler in self.atom_drag_handlers:
@@ -75,7 +75,7 @@ class AnalysisWindow(QDialog):
75
75
  total_atoms = len(xyz_atoms)
76
76
  num_heavy_atoms = 0
77
77
 
78
- for symbol, x, y, z in xyz_atoms:
78
+ for symbol, _x, _y, _z in xyz_atoms:
79
79
  atom_counts[symbol] = atom_counts.get(symbol, 0) + 1
80
80
  if symbol != "H": # Non-hydrogen
81
81
  num_heavy_atoms += 1
@@ -154,12 +154,15 @@ class AnalysisWindow(QDialog):
154
154
 
155
155
  # Calculate various properties
156
156
  mol_formula = rdMolDescriptors.CalcMolFormula(self.mol)
157
- mol_wt = Descriptors.MolWt(self.mol)
158
- exact_mw = Descriptors.ExactMolWt(self.mol)
157
+ # Descriptors.MolWt is generated at import time from rdkit's
158
+ # descriptor table, so no stub can declare it; the rest have
159
+ # direct rdMolDescriptors equivalents used elsewhere here.
160
+ mol_wt = Descriptors.MolWt(self.mol) # type: ignore[attr-defined]
161
+ exact_mw = rdMolDescriptors.CalcExactMolWt(self.mol)
159
162
  num_heavy_atoms = self.mol.GetNumHeavyAtoms()
160
163
  num_rings = rdMolDescriptors.CalcNumRings(self.mol)
161
- log_p = Descriptors.MolLogP(self.mol)
162
- tpsa = Descriptors.TPSA(self.mol)
164
+ log_p = rdMolDescriptors.CalcCrippenDescriptors(self.mol)[0]
165
+ tpsa = rdMolDescriptors.CalcTPSA(self.mol)
163
166
  num_h_donors = rdMolDescriptors.CalcNumHBD(self.mol)
164
167
  num_h_acceptors = rdMolDescriptors.CalcNumHBA(self.mol)
165
168
 
@@ -19,7 +19,7 @@ import copy
19
19
  import logging
20
20
  import os
21
21
  from ..utils.suppress_log import suppress_log
22
- from typing import Any, Dict, Optional, Tuple
22
+ from typing import Any, Dict, List, Optional, Tuple
23
23
 
24
24
 
25
25
  import numpy as np
@@ -43,7 +43,7 @@ if TYPE_CHECKING:
43
43
 
44
44
 
45
45
  # --- Class Definition ---
46
- def _serialize_constraints(constraints: list) -> list:
46
+ def _serialize_constraints(constraints: List[Any]) -> List[List[Any]]:
47
47
  """Convert internal constraint tuples to JSON-serializable lists."""
48
48
  result = []
49
49
  for const in constraints:
@@ -54,7 +54,7 @@ def _serialize_constraints(constraints: list) -> list:
54
54
  return result
55
55
 
56
56
 
57
- def _deserialize_constraints(raw: list) -> list:
57
+ def _deserialize_constraints(raw: List[Any]) -> List[Tuple[Any, ...]]:
58
58
  """Convert JSON-loaded constraint lists back to internal tuples."""
59
59
  result = []
60
60
  for const in raw:
@@ -228,7 +228,7 @@ class StateManager:
228
228
  if self.host.view_3d_manager.plotter and not getattr(
229
229
  self.host, "is_restoring_state", False
230
230
  ):
231
- self.host.view_3d_manager.plotter.reset_camera()
231
+ self.host.view_3d_manager.plotter.reset_camera() # type: ignore[call-arg]
232
232
 
233
233
  self.host.ui_manager.enable_3d_features(True)
234
234
  self.host.view_3d_manager.setup_3d_hover()
@@ -481,7 +481,9 @@ class StateManager:
481
481
  json_data["molecular_info"] = {
482
482
  "num_atoms": self.host.view_3d_manager.current_mol.GetNumAtoms(),
483
483
  "num_bonds": self.host.view_3d_manager.current_mol.GetNumBonds(),
484
- "molecular_weight": Descriptors.MolWt(
484
+ # Generated at import time from rdkit's descriptor
485
+ # table, so no stub can declare it.
486
+ "molecular_weight": Descriptors.MolWt( # type: ignore[attr-defined]
485
487
  self.host.view_3d_manager.current_mol
486
488
  ),
487
489
  "formula": rdMolDescriptors.CalcMolFormula(
@@ -620,7 +622,11 @@ class StateManager:
620
622
  mol_base64 = structure_3d.get("mol_binary_base64")
621
623
  if mol_base64:
622
624
  mol_binary = base64.b64decode(mol_base64.encode("ascii"))
623
- self.host.set_current_molecule(Chem.Mol(mol_binary))
625
+ # Chem.Mol accepts a pickled binary block at runtime;
626
+ # the stub only overloads Mol/str.
627
+ self.host.set_current_molecule(
628
+ Chem.Mol(mol_binary) # type: ignore[call-overload]
629
+ )
624
630
  if self.host.view_3d_manager.current_mol:
625
631
  # Set 3D coordinates
626
632
  if self.host.view_3d_manager.current_mol.GetNumConformers() > 0:
@@ -686,7 +692,7 @@ class StateManager:
686
692
  self.host.set_is_2d_editable(True)
687
693
 
688
694
  if self.host.view_3d_manager.plotter:
689
- self.host.view_3d_manager.plotter.reset_camera()
695
+ self.host.view_3d_manager.plotter.reset_camera() # type: ignore[call-arg]
690
696
 
691
697
  # Enable 3D-related UI
692
698
  try:
@@ -95,18 +95,18 @@ class AtomItem(QGraphicsItem):
95
95
  | QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges
96
96
  )
97
97
  self.setZValue(1)
98
+ self.font: QFont = QFont(FONT_FAMILY, 22, FONT_WEIGHT_BOLD)
98
99
  self.update_style()
99
100
  self.setAcceptHoverEvents(True)
100
101
  self.hovered: bool = False
101
102
  self.has_problem: bool = False
102
103
  self.is_visible: bool = True
103
- self.font: QFont = QFont(FONT_FAMILY, 20, FONT_WEIGHT_BOLD)
104
104
 
105
105
  def update_style(self) -> None:
106
106
  """Refresh font, color, and visibility based on current scene settings."""
107
107
  if sip_isdeleted_safe(self):
108
108
  return
109
- font_size = 20
109
+ font_size = 22
110
110
  font_family = FONT_FAMILY
111
111
  font_bold = True
112
112
  font_italic = False
@@ -114,7 +114,7 @@ class AtomItem(QGraphicsItem):
114
114
 
115
115
  scene: Any = self.scene()
116
116
  if scene is not None:
117
- font_size = scene.get_setting("atom_font_size_2d", 20)
117
+ font_size = scene.get_setting("atom_font_size_2d", 22)
118
118
  font_family = scene.get_setting("atom_font_family_2d", FONT_FAMILY)
119
119
  font_bold = scene.get_setting("atom_font_bold_2d", True)
120
120
  font_italic = scene.get_setting("atom_font_italic_2d", False)
@@ -136,14 +136,14 @@ class AtomItem(QGraphicsItem):
136
136
 
137
137
  def visual_rect(self) -> QRectF:
138
138
  """Return the rectangle the atom draws into; highlights use this."""
139
- font_size = 20
139
+ font_size = 22
140
140
  font_family = FONT_FAMILY
141
141
  font_bold = True
142
142
  font_italic = False
143
143
  font_underline = False
144
144
  scene: Any = self.scene()
145
145
  if scene is not None:
146
- font_size = scene.get_setting("atom_font_size_2d", 20)
146
+ font_size = scene.get_setting("atom_font_size_2d", 22)
147
147
  font_family = scene.get_setting("atom_font_family_2d", FONT_FAMILY)
148
148
  font_bold = scene.get_setting("atom_font_bold_2d", True)
149
149
  font_italic = scene.get_setting("atom_font_italic_2d", False)
@@ -265,14 +265,14 @@ class AtomItem(QGraphicsItem):
265
265
  if not self.is_visible:
266
266
  return path
267
267
 
268
- font_size = 20
268
+ font_size = 22
269
269
  font_family = FONT_FAMILY
270
270
  font_bold = True
271
271
  font_italic = False
272
272
  font_underline = False
273
273
  scene: Any = self.scene()
274
274
  if scene is not None:
275
- font_size = scene.get_setting("atom_font_size_2d", 20)
275
+ font_size = scene.get_setting("atom_font_size_2d", 22)
276
276
  font_family = scene.get_setting("atom_font_family_2d", FONT_FAMILY)
277
277
  font_bold = scene.get_setting("atom_font_bold_2d", True)
278
278
  font_italic = scene.get_setting("atom_font_italic_2d", False)
@@ -11,7 +11,7 @@ DOI: 10.5281/zenodo.17268532
11
11
  """
12
12
 
13
13
  import logging
14
- from typing import TYPE_CHECKING, Any, Optional, Union
14
+ from typing import Any, List, Optional, Union, TYPE_CHECKING
15
15
 
16
16
  import numpy as np
17
17
  from PyQt6.QtCore import Qt, QTimer
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
25
25
  from .main_window import MainWindow
26
26
 
27
27
 
28
- class SelectionList(list):
28
+ class SelectionList(List[int]):
29
29
  """Order-preserving list that compares equal to sets/lists/tuples of same elements."""
30
30
 
31
31
  def __eq__(self, other: object) -> bool:
@@ -254,7 +254,7 @@ class BondItem(QGraphicsItem):
254
254
  else "bond_spacing_double_2d"
255
255
  )
256
256
  bond_offset = scene.get_setting(key, 3.5)
257
- wedge_width = scene.get_setting("bond_wedge_width_2d", 6.0)
257
+ wedge_width = scene.get_setting("bond_wedge_width_2d", 8.0)
258
258
 
259
259
  # Qt prunes clicks whose shape() escapes boundingRect().
260
260
  extra = (getattr(self, "order", 1) - 1) * bond_offset + 50 + wedge_width
@@ -270,7 +270,7 @@ class BondItem(QGraphicsItem):
270
270
  font_size = 20
271
271
  font_family = FONT_FAMILY
272
272
  if scene is not None:
273
- font_size = scene.get_setting("atom_font_size_2d", 20)
273
+ font_size = scene.get_setting("atom_font_size_2d", 22)
274
274
  font_family = scene.get_setting("atom_font_family_2d", FONT_FAMILY)
275
275
 
276
276
  font = QFont(font_family, font_size, FONT_WEIGHT_BOLD)
@@ -363,11 +363,11 @@ class BondItem(QGraphicsItem):
363
363
  bond_color = QColor(color_hex)
364
364
 
365
365
  # Width and Cap Style
366
- bond_width = scene.get_setting("bond_width_2d", 2.0)
366
+ bond_width = scene.get_setting("bond_width_2d", 3.0)
367
367
  cap_style_str = scene.get_setting("bond_cap_style_2d", "Round")
368
368
 
369
369
  # Wedge/Dash settings
370
- wedge_width_half = scene.get_setting("bond_wedge_width_2d", 6.0)
370
+ wedge_width_half = scene.get_setting("bond_wedge_width_2d", 8.0)
371
371
  num_dashes = int(scene.get_setting("bond_dash_count_2d", 8))
372
372
 
373
373
  # Cap Style logic
@@ -531,7 +531,7 @@ class BondItem(QGraphicsItem):
531
531
  font_family = FONT_FAMILY
532
532
  _sc: Any = self.scene()
533
533
  if _sc is not None:
534
- font_size = _sc.get_setting("atom_font_size_2d", 20)
534
+ font_size = _sc.get_setting("atom_font_size_2d", 22)
535
535
  font_family = _sc.get_setting(
536
536
  "atom_font_family_2d", FONT_FAMILY
537
537
  )