MoleditPy 3.0.0a4__tar.gz → 3.0.0a6__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 (91) hide show
  1. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/PKG-INFO +4 -4
  2. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/README.md +3 -3
  3. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/pyproject.toml +1 -1
  4. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/MoleditPy.egg-info/PKG-INFO +4 -4
  5. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/MoleditPy.egg-info/SOURCES.txt +4 -6
  6. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/core/mol_geometry.py +78 -5
  7. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/core/molecular_data.py +15 -4
  8. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/main.py +0 -1
  9. moleditpy-3.0.0a6/src/moleditpy/plugins/plugin_interface.py +400 -0
  10. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/plugins/plugin_manager.py +110 -6
  11. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/plugins/plugin_manager_window.py +313 -313
  12. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/about_dialog.py +2 -2
  13. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/align_plane_dialog.py +18 -96
  14. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/alignment_dialog.py +7 -9
  15. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/angle_dialog.py +404 -516
  16. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/app_state.py +245 -283
  17. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/atom_item.py +24 -14
  18. moleditpy-3.0.0a6/src/moleditpy/ui/base_picking_dialog.py +126 -0
  19. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/bond_item.py +73 -87
  20. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/bond_length_dialog.py +351 -456
  21. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/calculation_worker.py +32 -18
  22. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/color_settings_dialog.py +81 -31
  23. moleditpy-3.0.0a6/src/moleditpy/ui/compute_logic.py +673 -0
  24. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/constrained_optimization_dialog.py +43 -26
  25. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/custom_interactor_style.py +129 -101
  26. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/custom_qt_interactor.py +4 -4
  27. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/dialog_3d_picking_mixin.py +53 -34
  28. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/dialog_logic.py +176 -136
  29. moleditpy-3.0.0a6/src/moleditpy/ui/dihedral_dialog.py +406 -0
  30. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/edit_3d_logic.py +70 -46
  31. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/edit_actions_logic.py +382 -151
  32. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/export_logic.py +156 -144
  33. moleditpy-3.0.0a6/src/moleditpy/ui/geometry_base_dialog.py +113 -0
  34. moleditpy-3.0.0a6/src/moleditpy/ui/io_logic.py +1024 -0
  35. moleditpy-3.0.0a6/src/moleditpy/ui/main_window.py +99 -0
  36. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/main_window_init.py +696 -520
  37. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/mirror_dialog.py +3 -3
  38. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/molecular_scene_handler.py +75 -35
  39. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/molecule_scene.py +74 -21
  40. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/move_group_dialog.py +131 -211
  41. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/planarize_dialog.py +21 -66
  42. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/settings_dialog.py +41 -66
  43. {moleditpy-3.0.0a4/src/moleditpy/utils → moleditpy-3.0.0a6/src/moleditpy/ui/settings_tabs}/__init__.py +11 -11
  44. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/settings_tabs/settings_2d_tab.py +7 -36
  45. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/settings_tabs/settings_3d_tabs.py +28 -73
  46. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/settings_tabs/settings_other_tab.py +18 -6
  47. moleditpy-3.0.0a6/src/moleditpy/ui/settings_tabs/settings_tab_base.py +63 -0
  48. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/string_importers.py +56 -46
  49. moleditpy-3.0.0a6/src/moleditpy/ui/translation_dialog.py +193 -0
  50. moleditpy-3.0.0a6/src/moleditpy/ui/ui_manager.py +602 -0
  51. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/user_template_dialog.py +45 -33
  52. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/view_3d_logic.py +429 -208
  53. {moleditpy-3.0.0a4/src/moleditpy/plugins → moleditpy-3.0.0a6/src/moleditpy/utils}/__init__.py +11 -11
  54. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/utils/constants.py +1 -1
  55. moleditpy-3.0.0a6/src/moleditpy/utils/default_settings.py +85 -0
  56. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/utils/sip_isdeleted_safe.py +41 -41
  57. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/utils/system_utils.py +71 -71
  58. moleditpy-3.0.0a4/src/moleditpy/modules/__init__.py +0 -122
  59. moleditpy-3.0.0a4/src/moleditpy/plugins/plugin_interface.py +0 -230
  60. moleditpy-3.0.0a4/src/moleditpy/ui/compute_engine.py +0 -880
  61. moleditpy-3.0.0a4/src/moleditpy/ui/compute_logic.py +0 -529
  62. moleditpy-3.0.0a4/src/moleditpy/ui/dialog_manager.py +0 -464
  63. moleditpy-3.0.0a4/src/moleditpy/ui/dihedral_dialog.py +0 -545
  64. moleditpy-3.0.0a4/src/moleditpy/ui/main_window.py +0 -218
  65. moleditpy-3.0.0a4/src/moleditpy/ui/molecular_parsers.py +0 -592
  66. moleditpy-3.0.0a4/src/moleditpy/ui/project_io.py +0 -387
  67. moleditpy-3.0.0a4/src/moleditpy/ui/settings_tabs/settings_tab_base.py +0 -33
  68. moleditpy-3.0.0a4/src/moleditpy/ui/translation_dialog.py +0 -347
  69. moleditpy-3.0.0a4/src/moleditpy/ui/ui_manager.py +0 -511
  70. moleditpy-3.0.0a4/src/moleditpy/ui/view_loaders.py +0 -274
  71. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/LICENSE +0 -0
  72. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/setup.cfg +0 -0
  73. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/MoleditPy.egg-info/dependency_links.txt +0 -0
  74. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/MoleditPy.egg-info/entry_points.txt +0 -0
  75. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/MoleditPy.egg-info/requires.txt +0 -0
  76. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/MoleditPy.egg-info/top_level.txt +0 -0
  77. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/__init__.py +0 -0
  78. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/__main__.py +0 -0
  79. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/assets/file_icon.ico +0 -0
  80. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/assets/icon.icns +0 -0
  81. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/assets/icon.ico +0 -0
  82. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/assets/icon.png +0 -0
  83. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/core/__init__.py +0 -0
  84. {moleditpy-3.0.0a4/src/moleditpy/ui/settings_tabs → moleditpy-3.0.0a6/src/moleditpy/plugins}/__init__.py +0 -0
  85. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/__init__.py +0 -0
  86. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/analysis_window.py +0 -0
  87. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/periodic_table_dialog.py +0 -0
  88. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/sip_isdeleted_safe.py +0 -0
  89. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/template_preview_item.py +0 -0
  90. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/template_preview_view.py +0 -0
  91. {moleditpy-3.0.0a4 → moleditpy-3.0.0a6}/src/moleditpy/ui/zoomable_view.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy
3
- Version: 3.0.0a4
3
+ Version: 3.0.0a6
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
@@ -710,10 +710,10 @@ Dynamic: license-file
710
710
  [![Python Versions](https://img.shields.io/pypi/pyversions/MoleditPy.svg)](https://pypi.org/project/MoleditPy/)
711
711
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
712
712
  [![Build Status](https://github.com/HiroYokoyama/python_molecular_editor/actions/workflows/tests.yml/badge.svg)](https://github.com/HiroYokoyama/python_molecular_editor/actions)
713
- ![Core Logic Coverage](https://img.shields.io/badge/core_logic_coverage-80%25-green)
714
- ![Overall Coverage](https://img.shields.io/badge/coverage-64%25-green)
713
+ ![Core Logic Coverage](https://img.shields.io/badge/core_logic_coverage-75%25-green)
714
+ ![Overall Coverage](https://img.shields.io/badge/coverage-67%25-green)
715
715
  ![GUI Status](https://img.shields.io/badge/GUI-Manually_Verified-blue)
716
- ![Pylint Score](https://img.shields.io/badge/pylint-8.93%2F10-brightgreen)
716
+ ![Pylint Score](https://img.shields.io/badge/pylint-9%2F10-brightgreen)
717
717
  [![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)
718
718
 
719
719
  [🇯🇵 日本語 (Japanese)](#japanese)
@@ -6,10 +6,10 @@
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
- ![Core Logic Coverage](https://img.shields.io/badge/core_logic_coverage-80%25-green)
10
- ![Overall Coverage](https://img.shields.io/badge/coverage-64%25-green)
9
+ ![Core Logic Coverage](https://img.shields.io/badge/core_logic_coverage-75%25-green)
10
+ ![Overall Coverage](https://img.shields.io/badge/coverage-67%25-green)
11
11
  ![GUI Status](https://img.shields.io/badge/GUI-Manually_Verified-blue)
12
- ![Pylint Score](https://img.shields.io/badge/pylint-8.93%2F10-brightgreen)
12
+ ![Pylint Score](https://img.shields.io/badge/pylint-9%2F10-brightgreen)
13
13
  [![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)
14
14
 
15
15
  [🇯🇵 日本語 (Japanese)](#japanese)
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "MoleditPy"
7
7
 
8
- version = "3.0.0a4"
8
+ version = "3.0.0a6"
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: 3.0.0a4
3
+ Version: 3.0.0a6
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
@@ -710,10 +710,10 @@ Dynamic: license-file
710
710
  [![Python Versions](https://img.shields.io/pypi/pyversions/MoleditPy.svg)](https://pypi.org/project/MoleditPy/)
711
711
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
712
712
  [![Build Status](https://github.com/HiroYokoyama/python_molecular_editor/actions/workflows/tests.yml/badge.svg)](https://github.com/HiroYokoyama/python_molecular_editor/actions)
713
- ![Core Logic Coverage](https://img.shields.io/badge/core_logic_coverage-80%25-green)
714
- ![Overall Coverage](https://img.shields.io/badge/coverage-64%25-green)
713
+ ![Core Logic Coverage](https://img.shields.io/badge/core_logic_coverage-75%25-green)
714
+ ![Overall Coverage](https://img.shields.io/badge/coverage-67%25-green)
715
715
  ![GUI Status](https://img.shields.io/badge/GUI-Manually_Verified-blue)
716
- ![Pylint Score](https://img.shields.io/badge/pylint-8.93%2F10-brightgreen)
716
+ ![Pylint Score](https://img.shields.io/badge/pylint-9%2F10-brightgreen)
717
717
  [![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)
718
718
 
719
719
  [🇯🇵 日本語 (Japanese)](#japanese)
@@ -23,7 +23,6 @@ src/moleditpy/assets/icon.png
23
23
  src/moleditpy/core/__init__.py
24
24
  src/moleditpy/core/mol_geometry.py
25
25
  src/moleditpy/core/molecular_data.py
26
- src/moleditpy/modules/__init__.py
27
26
  src/moleditpy/plugins/__init__.py
28
27
  src/moleditpy/plugins/plugin_interface.py
29
28
  src/moleditpy/plugins/plugin_manager.py
@@ -36,32 +35,31 @@ src/moleditpy/ui/analysis_window.py
36
35
  src/moleditpy/ui/angle_dialog.py
37
36
  src/moleditpy/ui/app_state.py
38
37
  src/moleditpy/ui/atom_item.py
38
+ src/moleditpy/ui/base_picking_dialog.py
39
39
  src/moleditpy/ui/bond_item.py
40
40
  src/moleditpy/ui/bond_length_dialog.py
41
41
  src/moleditpy/ui/calculation_worker.py
42
42
  src/moleditpy/ui/color_settings_dialog.py
43
- src/moleditpy/ui/compute_engine.py
44
43
  src/moleditpy/ui/compute_logic.py
45
44
  src/moleditpy/ui/constrained_optimization_dialog.py
46
45
  src/moleditpy/ui/custom_interactor_style.py
47
46
  src/moleditpy/ui/custom_qt_interactor.py
48
47
  src/moleditpy/ui/dialog_3d_picking_mixin.py
49
48
  src/moleditpy/ui/dialog_logic.py
50
- src/moleditpy/ui/dialog_manager.py
51
49
  src/moleditpy/ui/dihedral_dialog.py
52
50
  src/moleditpy/ui/edit_3d_logic.py
53
51
  src/moleditpy/ui/edit_actions_logic.py
54
52
  src/moleditpy/ui/export_logic.py
53
+ src/moleditpy/ui/geometry_base_dialog.py
54
+ src/moleditpy/ui/io_logic.py
55
55
  src/moleditpy/ui/main_window.py
56
56
  src/moleditpy/ui/main_window_init.py
57
57
  src/moleditpy/ui/mirror_dialog.py
58
- src/moleditpy/ui/molecular_parsers.py
59
58
  src/moleditpy/ui/molecular_scene_handler.py
60
59
  src/moleditpy/ui/molecule_scene.py
61
60
  src/moleditpy/ui/move_group_dialog.py
62
61
  src/moleditpy/ui/periodic_table_dialog.py
63
62
  src/moleditpy/ui/planarize_dialog.py
64
- src/moleditpy/ui/project_io.py
65
63
  src/moleditpy/ui/settings_dialog.py
66
64
  src/moleditpy/ui/sip_isdeleted_safe.py
67
65
  src/moleditpy/ui/string_importers.py
@@ -71,7 +69,6 @@ src/moleditpy/ui/translation_dialog.py
71
69
  src/moleditpy/ui/ui_manager.py
72
70
  src/moleditpy/ui/user_template_dialog.py
73
71
  src/moleditpy/ui/view_3d_logic.py
74
- src/moleditpy/ui/view_loaders.py
75
72
  src/moleditpy/ui/zoomable_view.py
76
73
  src/moleditpy/ui/settings_tabs/__init__.py
77
74
  src/moleditpy/ui/settings_tabs/settings_2d_tab.py
@@ -80,5 +77,6 @@ src/moleditpy/ui/settings_tabs/settings_other_tab.py
80
77
  src/moleditpy/ui/settings_tabs/settings_tab_base.py
81
78
  src/moleditpy/utils/__init__.py
82
79
  src/moleditpy/utils/constants.py
80
+ src/moleditpy/utils/default_settings.py
83
81
  src/moleditpy/utils/sip_isdeleted_safe.py
84
82
  src/moleditpy/utils/system_utils.py
@@ -12,7 +12,6 @@ DOI: 10.5281/zenodo.17268532
12
12
 
13
13
  from __future__ import annotations
14
14
  import math
15
- import logging
16
15
  from collections import deque
17
16
  from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union
18
17
 
@@ -22,7 +21,11 @@ import numpy as np
22
21
  # Primitive geometry helpers
23
22
  # ------------------------------------------------------------------
24
23
 
25
- def calc_distance(pos1: Union[np.ndarray, Tuple[float, float, float], List[float]], pos2: Union[np.ndarray, Tuple[float, float, float], List[float]]) -> float:
24
+
25
+ def calc_distance(
26
+ pos1: Union[np.ndarray, Tuple[float, float, float], List[float]],
27
+ pos2: Union[np.ndarray, Tuple[float, float, float], List[float]],
28
+ ) -> float:
26
29
  """Return the Euclidean distance between two 3-D positions.
27
30
 
28
31
  Parameters
@@ -78,7 +81,9 @@ def calc_angle_deg(
78
81
  # ------------------------------------------------------------------
79
82
 
80
83
 
81
- def get_connected_group(mol: Any, start_atom: int, exclude: Optional[int] = None) -> Set[int]:
84
+ def get_connected_group(
85
+ mol: Any, start_atom: int, exclude: Optional[int] = None
86
+ ) -> Set[int]:
82
87
  """Return the set of atom indices reachable from *start_atom*
83
88
  without passing through *exclude*.
84
89
 
@@ -300,6 +305,70 @@ def calculate_dihedral(positions: Any, i1: int, i2: int, i3: int, i4: int) -> fl
300
305
  return float(np.degrees(angle_rad))
301
306
 
302
307
 
308
+ def adjust_dihedral(
309
+ positions: np.ndarray,
310
+ i1: int,
311
+ i2: int,
312
+ i3: int,
313
+ i4: int,
314
+ target_dihedral_deg: float,
315
+ atom_indices_to_move: Iterable[int],
316
+ ) -> float:
317
+ """Adjust the dihedral angle defined by i1-i2-i3-i4 to target_dihedral_deg.
318
+ The rotation is performed around the i2-i3 bond axis.
319
+
320
+ Parameters
321
+ ----------
322
+ positions : ndarray, shape (N, 3)
323
+ Atom coordinates. **Modified in-place.**
324
+ i1, i2, i3, i4 : int
325
+ Atom indices defining the dihedral.
326
+ target_dihedral_deg : float
327
+ Target dihedral angle in degrees.
328
+ atom_indices_to_move : iterable of int
329
+ Indices of atoms to be rotated.
330
+
331
+ Returns
332
+ -------
333
+ float
334
+ Applied rotation in radians.
335
+ """
336
+ # Current dihedral
337
+ current_dihedral = calculate_dihedral(positions, i1, i2, i3, i4)
338
+
339
+ # Rotation angle needed
340
+ delta_deg = target_dihedral_deg - current_dihedral
341
+
342
+ # Shortest rotation path
343
+ if delta_deg > 180:
344
+ delta_deg -= 360
345
+ elif delta_deg < -180:
346
+ delta_deg += 360
347
+
348
+ delta_rad = np.radians(delta_deg)
349
+
350
+ if abs(delta_rad) < 1e-9:
351
+ return 0.0
352
+
353
+ # Rotation axis (i2 -> i3)
354
+ pos2 = positions[i2]
355
+ pos3 = positions[i3]
356
+ axis = pos3 - pos2
357
+ axis_norm = np.linalg.norm(axis)
358
+
359
+ if axis_norm < 1e-12:
360
+ return 0.0
361
+
362
+ axis_unit = axis / axis_norm
363
+
364
+ # Rotate each movable atom
365
+ for idx in atom_indices_to_move:
366
+ rel = positions[idx] - pos2
367
+ positions[idx] = pos2 + rodrigues_rotate(rel, axis_unit, delta_rad)
368
+
369
+ return float(delta_rad)
370
+
371
+
303
372
  # ------------------------------------------------------------------
304
373
  # Valence sanity check
305
374
  # ------------------------------------------------------------------
@@ -319,7 +388,9 @@ _VALENCE_LIMITS = {
319
388
  }
320
389
 
321
390
 
322
- def is_problematic_valence(symbol: str, bond_count: Union[int, float], charge: int = 0) -> bool:
391
+ def is_problematic_valence(
392
+ symbol: str, bond_count: Union[int, float], charge: int = 0
393
+ ) -> bool:
323
394
  """Return ``True`` if the atom's total bond order exceeds its
324
395
  typical maximum valence.
325
396
 
@@ -411,7 +482,9 @@ def inject_ez_stereo_to_mol_block(mol_block, rdkit_mol, bonds_data):
411
482
  return "\n".join(mol_lines)
412
483
 
413
484
 
414
- def identify_valence_problems(atoms_data: Dict[int, Any], bonds_data: Dict[Tuple[int, int], Any]) -> List[int]:
485
+ def identify_valence_problems(
486
+ atoms_data: Dict[int, Any], bonds_data: Dict[Tuple[int, int], Any]
487
+ ) -> List[int]:
415
488
  """Identify atoms with problematic valence.
416
489
 
417
490
  Parameters
@@ -12,7 +12,7 @@ DOI: 10.5281/zenodo.17268532
12
12
 
13
13
  from __future__ import annotations
14
14
  import logging
15
- from typing import Any, Dict, List, Optional, Set, Tuple, Union
15
+ from typing import Any, Dict, List, Optional, Tuple, Union
16
16
  from rdkit import Chem
17
17
 
18
18
  try:
@@ -43,7 +43,13 @@ class MolecularData:
43
43
  self._next_atom_id = 0
44
44
  self.adjacency_list = {}
45
45
 
46
- def add_atom(self, symbol: str, pos: Union[Any, Tuple[float, float]], charge: int = 0, radical: int = 0) -> int:
46
+ def add_atom(
47
+ self,
48
+ symbol: str,
49
+ pos: Union[Any, Tuple[float, float]],
50
+ charge: int = 0,
51
+ radical: int = 0,
52
+ ) -> int:
47
53
  atom_id = self._next_atom_id
48
54
  # Internalize position as raw floats to decouple from UI types (QPointF)
49
55
  if hasattr(pos, "x") and hasattr(pos, "y"):
@@ -72,7 +78,9 @@ class MolecularData:
72
78
  else:
73
79
  self.atoms[atom_id]["pos"] = PointTuple((float(pos[0]), float(pos[1])))
74
80
 
75
- def add_bond(self, id1: int, id2: int, order: Union[int, float] = 1, stereo: int = 0) -> Tuple[Tuple[int, int], str]:
81
+ def add_bond(
82
+ self, id1: int, id2: int, order: Union[int, float] = 1, stereo: int = 0
83
+ ) -> Tuple[Tuple[int, int], str]:
76
84
  # For stereo bonds, do not sort because ID order determines direction.
77
85
  # For non-stereo bonds, sort to normalize the key.
78
86
  if stereo == 0:
@@ -154,6 +162,7 @@ class MolecularData:
154
162
  atom.SetFormalCharge(data.get("charge", 0))
155
163
  atom.SetNumRadicalElectrons(data.get("radical", 0))
156
164
  atom.SetIntProp("_original_atom_id", atom_id)
165
+ atom.SetNoImplicit(False) # Allow RDKit to perceive implicit valence
157
166
  idx = mol.AddAtom(atom)
158
167
  atom_id_to_idx_map[atom_id] = idx
159
168
 
@@ -456,7 +465,9 @@ class MolecularData:
456
465
  mol_block += "M END\n"
457
466
  return mol_block
458
467
 
459
- def to_template_dict(self, name: str, version: str = "1.0", application_version: str = "") -> Dict[str, Any]:
468
+ def to_template_dict(
469
+ self, name: str, version: str = "1.0", application_version: str = ""
470
+ ) -> Dict[str, Any]:
460
471
  """Convert current structure to a dictionary for template storage."""
461
472
  import datetime
462
473
 
@@ -50,7 +50,6 @@ def setup_logging():
50
50
  sys.excepthook = handle_exception
51
51
 
52
52
 
53
-
54
53
  def main():
55
54
  # Setup logging as early as possible
56
55
  setup_logging()