MoleditPy-linux 4.8.2__tar.gz → 4.8.3__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 (84) hide show
  1. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/PKG-INFO +1 -1
  2. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/pyproject.toml +1 -1
  3. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/MoleditPy_linux.egg-info/PKG-INFO +1 -1
  4. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/core/mol_geometry.py +2 -2
  5. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/core/molecular_data.py +1 -1
  6. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/main.py +2 -2
  7. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/plugins/plugin_interface.py +12 -10
  8. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/plugins/plugin_manager.py +30 -15
  9. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/analysis_window.py +8 -5
  10. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/app_state.py +13 -7
  11. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/base_picking_dialog.py +2 -2
  12. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/calculation_worker.py +34 -26
  13. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/compute_logic.py +23 -6
  14. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/constrained_optimization_dialog.py +7 -5
  15. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/custom_interactor_style.py +4 -1
  16. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/custom_qt_interactor.py +5 -0
  17. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/dialog_3d_picking_mixin.py +8 -1
  18. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/edit_actions_logic.py +22 -4
  19. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/export_logic.py +2 -2
  20. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/io_logic.py +27 -8
  21. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/main_window_init.py +1 -1
  22. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/molecular_scene_handler.py +4 -4
  23. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/molecule_scene.py +6 -2
  24. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/move_group_dialog.py +3 -3
  25. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/move_selected_atoms_dialog.py +3 -3
  26. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/planarize_dialog.py +1 -1
  27. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/plugin_menu_manager.py +3 -1
  28. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/string_importers.py +7 -7
  29. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/template_preview_item.py +2 -2
  30. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/translation_dialog.py +14 -2
  31. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/view_3d_logic.py +20 -20
  32. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/LICENSE +0 -0
  33. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/README.md +0 -0
  34. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/setup.cfg +0 -0
  35. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/MoleditPy_linux.egg-info/SOURCES.txt +0 -0
  36. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/MoleditPy_linux.egg-info/dependency_links.txt +0 -0
  37. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/MoleditPy_linux.egg-info/entry_points.txt +0 -0
  38. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/MoleditPy_linux.egg-info/requires.txt +0 -0
  39. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/MoleditPy_linux.egg-info/top_level.txt +0 -0
  40. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/__init__.py +0 -0
  41. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/__main__.py +0 -0
  42. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/assets/file_icon.ico +0 -0
  43. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/assets/icon.icns +0 -0
  44. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/assets/icon.ico +0 -0
  45. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/assets/icon.png +0 -0
  46. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/core/__init__.py +0 -0
  47. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/plugins/__init__.py +0 -0
  48. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/plugins/plugin_manager_window.py +0 -0
  49. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/__init__.py +0 -0
  50. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/about_dialog.py +0 -0
  51. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/align_plane_dialog.py +0 -0
  52. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/alignment_dialog.py +0 -0
  53. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/angle_dialog.py +0 -0
  54. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/atom_item.py +0 -0
  55. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/atom_picking.py +0 -0
  56. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/bond_item.py +0 -0
  57. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/bond_length_dialog.py +0 -0
  58. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/chain_mixin.py +0 -0
  59. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/color_settings_dialog.py +0 -0
  60. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/dialog_logic.py +0 -0
  61. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/dihedral_dialog.py +0 -0
  62. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/edit_3d_logic.py +0 -0
  63. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/geometry_base_dialog.py +0 -0
  64. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/main_window.py +0 -0
  65. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/mirror_dialog.py +0 -0
  66. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/periodic_table_dialog.py +0 -0
  67. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/preview_molecule.py +0 -0
  68. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/settings_dialog.py +0 -0
  69. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/settings_tabs/__init__.py +0 -0
  70. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/settings_tabs/settings_2d_tab.py +0 -0
  71. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/settings_tabs/settings_3d_tabs.py +0 -0
  72. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/settings_tabs/settings_other_tab.py +0 -0
  73. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/settings_tabs/settings_tab_base.py +0 -0
  74. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/template_preview_view.py +0 -0
  75. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/ui_manager.py +0 -0
  76. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/user_template_dialog.py +0 -0
  77. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/ui/zoomable_view.py +0 -0
  78. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/utils/__init__.py +0 -0
  79. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/utils/constants.py +0 -0
  80. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/utils/default_settings.py +0 -0
  81. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/utils/hit_radius.py +0 -0
  82. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/utils/sip_isdeleted_safe.py +0 -0
  83. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/src/moleditpy_linux/utils/suppress_log.py +0 -0
  84. {moleditpy_linux-4.8.2 → moleditpy_linux-4.8.3}/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.8.3
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-linux"
7
7
 
8
- version = "4.8.2"
8
+ version = "4.8.3"
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.8.3
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
@@ -593,9 +593,9 @@ def _identify_problems_rdkit(
593
593
 
594
594
  def optimize_2d_coords(mol: Chem.Mol) -> Dict[int, Tuple[float, float]]:
595
595
  """Generate 2D coordinates using RDKit and return a map of (x, y) tuples."""
596
- from rdkit.Chem import AllChem
596
+ from rdkit.Chem import rdDepictor
597
597
 
598
- AllChem.Compute2DCoords(mol)
598
+ rdDepictor.Compute2DCoords(mol)
599
599
  conf = mol.GetConformer()
600
600
  new_positions = {}
601
601
  for rdkit_atom in mol.GetAtoms():
@@ -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:
@@ -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:
@@ -17,13 +17,13 @@ import re
17
17
  import numpy as np
18
18
  import sys
19
19
  import subprocess
20
- from typing import Any, Callable, Dict, Optional, Set, Tuple
20
+ from typing import Any, Callable, Dict, List, Optional, Set, Tuple
21
21
 
22
22
  from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot
23
23
 
24
24
  # RDKit
25
25
  from rdkit import Chem
26
- from rdkit.Chem import AllChem, rdGeometry
26
+ from rdkit.Chem import rdDistGeom, rdForceFieldHelpers, rdGeometry
27
27
  from rdkit.DistanceGeometry import DoTriangleSmoothing
28
28
 
29
29
  from ..utils.constants import DEFAULT_BOND_LENGTH_ANGSTROM
@@ -235,14 +235,16 @@ def _iterative_optimize(
235
235
  )
236
236
  ff = None
237
237
  if method == "UFF":
238
- ff = AllChem.UFFGetMoleculeForceField(
238
+ ff = rdForceFieldHelpers.UFFGetMoleculeForceField(
239
239
  mol, confId=0, ignoreInterfragInteractions=ignore_interfrag
240
240
  )
241
241
  elif "MMFF94" in method:
242
242
  mmff_variant = "MMFF94" if method == "MMFF94" else "MMFF94s"
243
- props = AllChem.MMFFGetMoleculeProperties(mol, mmffVariant=mmff_variant)
243
+ props = rdForceFieldHelpers.MMFFGetMoleculeProperties(
244
+ mol, mmffVariant=mmff_variant
245
+ )
244
246
  if props:
245
- ff = AllChem.MMFFGetMoleculeForceField(
247
+ ff = rdForceFieldHelpers.MMFFGetMoleculeForceField(
246
248
  mol, props, confId=0, ignoreInterfragInteractions=ignore_interfrag
247
249
  )
248
250
 
@@ -378,8 +380,10 @@ _DIRECT_WEDGE_TILT = math.radians(35.0)
378
380
 
379
381
 
380
382
  def _rescale_2d_layout(
381
- coords: list, mol: Chem.Mol, _safe_status: Callable[[str], None]
382
- ) -> list:
383
+ coords: List[Tuple[float, float, float]],
384
+ mol: Chem.Mol,
385
+ _safe_status: Callable[[str], None],
386
+ ) -> List[Tuple[float, float, float]]:
383
387
  """Scale parsed 2D coordinates so the median bond is a chemically sane length."""
384
388
  lengths = []
385
389
  for bond in mol.GetBonds():
@@ -419,8 +423,12 @@ def _perform_direct_conversion(
419
423
  options: Optional[Dict[str, Any]],
420
424
  _check_halted: Callable[[], bool],
421
425
  _safe_status: Callable[[str], None],
422
- ) -> Optional[Chem.Mol]:
423
- """Direct 3D conversion using 2D coordinates and adding missing H without embedding."""
426
+ ) -> Chem.Mol:
427
+ """Direct 3D conversion using 2D coordinates and adding missing H without embedding.
428
+
429
+ Returns the same molecule it was given: the conversion mutates ``mol`` in
430
+ place and never rebinds it, so the result is never None.
431
+ """
424
432
  parsed_coords, stereo_dirs = [], []
425
433
  # Best-effort attempt to parse 2D coordinates from MOL block for direct conversion.
426
434
  # We suppress AttributeError/RuntimeError if the block is malformed or RDKit fails internally.
@@ -724,10 +732,10 @@ print(ob_mol.write("mol"))
724
732
  f"Subprocess crashed or failed. Error: {result.stderr.strip()}"
725
733
  )
726
734
  out_mol_block = result.stdout
727
- except subprocess.TimeoutExpired:
728
- raise RuntimeError("Open Babel make3D() timed out.")
735
+ except subprocess.TimeoutExpired as exc:
736
+ raise RuntimeError("Open Babel make3D() timed out.") from exc
729
737
  except Exception as e:
730
- raise RuntimeError(f"Open Babel isolated execution failed: {e}")
738
+ raise RuntimeError(f"Open Babel isolated execution failed: {e}") from e
731
739
 
732
740
  rd_mol = Chem.MolFromMolBlock(out_mol_block, removeHs=False)
733
741
  if not rd_mol:
@@ -786,7 +794,7 @@ print(ob_mol.write("mol"))
786
794
  raise
787
795
  except (AttributeError, RuntimeError, ValueError, TypeError) as e:
788
796
  if mode == "obabel":
789
- raise RuntimeError(f"Open Babel conversion failed: {e}")
797
+ raise RuntimeError(f"Open Babel conversion failed: {e}") from e
790
798
  _safe_status(f"Open Babel failed: {e}. Falling back...")
791
799
  return False
792
800
 
@@ -953,11 +961,15 @@ class CalculationWorker(QObject):
953
961
  _safe_finished = helpers["finished"]
954
962
  w_id = options.get("worker_id")
955
963
 
956
- params = AllChem.ETKDGv2()
957
- params.randomSeed = 42
958
- params.useExpTorsionAnglePrefs = params.useBasicKnowledge = (
959
- params.enforceChirality
960
- ) = True
964
+ # rdkit's stub declares these EmbedParameters properties with bare
965
+ # `*args, **kwargs` and no annotations, so every assignment through them
966
+ # is reported as a type error. Unchained here so each one can carry the
967
+ # narrowest possible ignore.
968
+ params = rdDistGeom.ETKDGv2()
969
+ params.randomSeed = 42 # type: ignore[assignment]
970
+ params.useExpTorsionAnglePrefs = True # type: ignore[assignment]
971
+ params.useBasicKnowledge = True # type: ignore[assignment]
972
+ params.enforceChirality = True # type: ignore[assignment]
961
973
 
962
974
  # Track existing/explicit stereo to restore after embedding
963
975
  orig_stereo = []
@@ -980,22 +992,18 @@ class CalculationWorker(QObject):
980
992
 
981
993
  conf_id = -1
982
994
  with suppress_log(RuntimeError, ValueError):
983
- conf_id = AllChem.EmbedMolecule(mol, params)
995
+ conf_id = rdDistGeom.EmbedMolecule(mol, params)
984
996
 
985
997
  # Fallback embedding strategies
986
998
  if conf_id == -1:
987
999
  with suppress_log(AttributeError, RuntimeError, ValueError, TypeError):
988
- bm = AllChem.GetMoleculeBoundsMatrix(mol)
989
- for b_idx, s, satoms in orig_stereo:
1000
+ bm = rdDistGeom.GetMoleculeBoundsMatrix(mol)
1001
+ for _b_idx, s, satoms in orig_stereo:
990
1002
  if len(satoms) == 2:
991
1003
  t = 3.0 if s == Chem.BondStereo.STEREOZ else 5.0
992
1004
  bm[satoms[0]][satoms[1]] = bm[satoms[1]][satoms[0]] = t
993
1005
  DoTriangleSmoothing(bm)
994
- conf_id = AllChem.EmbedMolecule(mol, bm, params)
995
-
996
- if conf_id == -1:
997
- with suppress_log(AttributeError, RuntimeError, ValueError, TypeError):
998
- conf_id = AllChem.EmbedMolecule(mol, AllChem.ETKDGv2(randomSeed=42))
1006
+ conf_id = rdDistGeom.EmbedMolecule(mol, bm, params)
999
1007
 
1000
1008
  if conf_id == -1:
1001
1009
  return False
@@ -13,13 +13,23 @@ DOI: 10.5281/zenodo.17268532
13
13
  from __future__ import annotations
14
14
  import logging
15
15
  from ..utils.suppress_log import suppress_log
16
- from typing import Any, Dict, List, Optional, Set, Tuple, Union, TYPE_CHECKING
16
+ from typing import (
17
+ Any,
18
+ Dict,
19
+ List,
20
+ Optional,
21
+ Sequence,
22
+ Set,
23
+ Tuple,
24
+ Union,
25
+ TYPE_CHECKING,
26
+ )
17
27
 
18
28
  from PyQt6.QtCore import QThread, QTimer, QPoint
19
29
  from PyQt6.QtGui import QAction, QColor
20
30
  from PyQt6.QtWidgets import QMenu, QMessageBox, QWidget
21
31
  from rdkit import Chem
22
- from rdkit.Chem import AllChem
32
+ from rdkit.Chem import rdDepictor
23
33
 
24
34
  from . import OBABEL_AVAILABLE
25
35
 
@@ -387,6 +397,11 @@ class ComputeManager:
387
397
  """
388
398
  mol = self.host.view_3d_manager.current_mol
389
399
  label = entry.get("label", method)
400
+ if mol is None:
401
+ self.host.update_status_message(
402
+ f"Optimization with {label} needs a 3D structure."
403
+ )
404
+ return
390
405
  self.host.update_status_message(f"Optimizing 3D structure ({label})...")
391
406
  try:
392
407
  success = bool(entry["callback"](mol))
@@ -410,7 +425,7 @@ class ComputeManager:
410
425
  self._refresh_ui_state()
411
426
  self.host.edit_actions_manager.push_undo_state()
412
427
  if self.host.view_3d_manager.plotter:
413
- self.host.view_3d_manager.plotter.reset_camera()
428
+ self.host.view_3d_manager.plotter.reset_camera() # type: ignore[call-arg]
414
429
  self.host.update_status_message(f"Process completed ({label}).")
415
430
 
416
431
  def _prepare_rdkit_mol_for_conversion(self) -> Optional[Chem.Mol]:
@@ -439,7 +454,9 @@ class ComputeManager:
439
454
  return None
440
455
  return mol
441
456
 
442
- def _handle_chemistry_problems(self, mol: Chem.Mol, problems: List[Any]) -> None:
457
+ def _handle_chemistry_problems(
458
+ self, mol: Chem.Mol, problems: Sequence[Any]
459
+ ) -> None:
443
460
  self.host.init_manager.scene.clear_all_problem_flags()
444
461
  msg = f"Error: {len(problems)} chemistry problem(s) found (e.g., hypervalency). Fix the 2D layout before converting."
445
462
  self.host.statusBar().showMessage(msg) # type: ignore[union-attr]
@@ -474,7 +491,7 @@ class ComputeManager:
474
491
  # 3D generation; the 2D canvas keeps the user's own layout.
475
492
  planar = Chem.Mol(stereo_mol)
476
493
  planar.RemoveAllConformers()
477
- AllChem.Compute2DCoords(planar)
494
+ rdDepictor.Compute2DCoords(planar)
478
495
  return str(Chem.MolToMolBlock(planar, includeStereo=True))
479
496
  except (RuntimeError, ValueError) as e:
480
497
  logging.warning("E/Z-consistent 2D layout failed: %s", e)
@@ -556,7 +573,7 @@ class ComputeManager:
556
573
  self._refresh_ui_state()
557
574
  self.host.edit_actions_manager.push_undo_state()
558
575
  if self.host.view_3d_manager.plotter:
559
- self.host.view_3d_manager.plotter.reset_camera()
576
+ self.host.view_3d_manager.plotter.reset_camera() # type: ignore[call-arg]
560
577
 
561
578
  # Record the successful optimization method from mol property or current setting
562
579
  try:
@@ -28,7 +28,7 @@ from PyQt6.QtWidgets import (
28
28
  QTableWidgetItem,
29
29
  QVBoxLayout,
30
30
  )
31
- from rdkit.Chem import AllChem, rdMolTransforms
31
+ from rdkit.Chem import rdForceFieldHelpers, rdMolTransforms
32
32
 
33
33
  from .dialog_3d_picking_mixin import Dialog3DPickingMixin
34
34
 
@@ -378,7 +378,7 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
378
378
  def remove_constraint(self) -> None:
379
379
  """Delete the selected constraint rows from the table."""
380
380
  selected_rows = sorted(
381
- list(set(index.row() for index in self.constraint_table.selectedIndexes())),
381
+ {index.row() for index in self.constraint_table.selectedIndexes()},
382
382
  reverse=True,
383
383
  )
384
384
  if not selected_rows:
@@ -511,8 +511,10 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
511
511
  "optimize_intermolecular_interaction_rdkit", True
512
512
  )
513
513
  if ff_name.startswith("MMFF"):
514
- props = AllChem.MMFFGetMoleculeProperties(self.mol, mmffVariant=ff_name)
515
- ff = AllChem.MMFFGetMoleculeForceField(
514
+ props = rdForceFieldHelpers.MMFFGetMoleculeProperties(
515
+ self.mol, mmffVariant=ff_name
516
+ )
517
+ ff = rdForceFieldHelpers.MMFFGetMoleculeForceField(
516
518
  self.mol,
517
519
  props,
518
520
  confId=0,
@@ -522,7 +524,7 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
522
524
  add_angle_constraint = ff.MMFFAddAngleConstraint
523
525
  add_torsion_constraint = ff.MMFFAddTorsionConstraint
524
526
  else: # UFF
525
- ff = AllChem.UFFGetMoleculeForceField(
527
+ ff = rdForceFieldHelpers.UFFGetMoleculeForceField(
526
528
  self.mol, confId=0, ignoreInterfragInteractions=ignore_interfrag
527
529
  )
528
530
  add_dist_constraint = ff.UFFAddDistanceConstraint
@@ -923,7 +923,10 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
923
923
  logging.debug("Suppressed non-critical error", exc_info=True)
924
924
 
925
925
  def _compute_delta_rotation_matrix(
926
- self, renderer: Any, start_pos: tuple, current_pos: tuple
926
+ self,
927
+ renderer: Any,
928
+ start_pos: Tuple[float, float],
929
+ current_pos: Tuple[float, float],
927
930
  ) -> Optional[np.ndarray]:
928
931
  """Compute 3D rotation matrix from screen mouse displacement (dx, dy)."""
929
932
  try: