MoleditPy 3.0.0a2__tar.gz → 3.0.0a4__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-3.0.0a2 → moleditpy-3.0.0a4}/PKG-INFO +3 -2
  2. {moleditpy-3.0.0a2 → moleditpy-3.0.0a4}/README.md +2 -1
  3. {moleditpy-3.0.0a2 → moleditpy-3.0.0a4}/pyproject.toml +1 -1
  4. {moleditpy-3.0.0a2 → moleditpy-3.0.0a4}/src/MoleditPy.egg-info/PKG-INFO +3 -2
  5. moleditpy-3.0.0a4/src/MoleditPy.egg-info/SOURCES.txt +84 -0
  6. moleditpy-3.0.0a4/src/moleditpy/__init__.py +20 -0
  7. moleditpy-3.0.0a4/src/moleditpy/core/__init__.py +0 -0
  8. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/core}/mol_geometry.py +168 -16
  9. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/core}/molecular_data.py +137 -66
  10. {moleditpy-3.0.0a2 → moleditpy-3.0.0a4}/src/moleditpy/main.py +23 -4
  11. moleditpy-3.0.0a4/src/moleditpy/modules/__init__.py +122 -0
  12. {moleditpy-3.0.0a2/src/moleditpy → moleditpy-3.0.0a4/src/moleditpy/plugins}/__init__.py +11 -15
  13. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/plugins}/plugin_manager.py +574 -578
  14. moleditpy-3.0.0a4/src/moleditpy/ui/__init__.py +25 -0
  15. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/about_dialog.py +4 -3
  16. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/align_plane_dialog.py +306 -306
  17. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/alignment_dialog.py +1 -1
  18. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/analysis_window.py +226 -226
  19. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/angle_dialog.py +5 -4
  20. moleditpy-3.0.0a2/src/moleditpy/modules/main_window_app_state.py → moleditpy-3.0.0a4/src/moleditpy/ui/app_state.py +22 -27
  21. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/atom_item.py +38 -29
  22. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/bond_item.py +543 -528
  23. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/bond_length_dialog.py +3 -2
  24. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/calculation_worker.py +970 -970
  25. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/color_settings_dialog.py +404 -404
  26. moleditpy-3.0.0a2/src/moleditpy/modules/main_window_compute.py → moleditpy-3.0.0a4/src/moleditpy/ui/compute_engine.py +4 -11
  27. moleditpy-3.0.0a4/src/moleditpy/ui/compute_logic.py +529 -0
  28. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/constrained_optimization_dialog.py +2 -1
  29. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/custom_interactor_style.py +34 -24
  30. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/custom_qt_interactor.py +97 -97
  31. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/dialog_3d_picking_mixin.py +210 -210
  32. moleditpy-3.0.0a4/src/moleditpy/ui/dialog_logic.py +438 -0
  33. moleditpy-3.0.0a2/src/moleditpy/modules/main_window_dialog_manager.py → moleditpy-3.0.0a4/src/moleditpy/ui/dialog_manager.py +17 -21
  34. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/dihedral_dialog.py +5 -4
  35. moleditpy-3.0.0a2/src/moleditpy/modules/main_window_edit_3d.py → moleditpy-3.0.0a4/src/moleditpy/ui/edit_3d_logic.py +51 -26
  36. moleditpy-3.0.0a2/src/moleditpy/modules/main_window_edit_actions.py → moleditpy-3.0.0a4/src/moleditpy/ui/edit_actions_logic.py +216 -322
  37. moleditpy-3.0.0a2/src/moleditpy/modules/main_window_export.py → moleditpy-3.0.0a4/src/moleditpy/ui/export_logic.py +37 -32
  38. moleditpy-3.0.0a4/src/moleditpy/ui/main_window.py +218 -0
  39. moleditpy-3.0.0a2/src/moleditpy/modules/main_window_main_init.py → moleditpy-3.0.0a4/src/moleditpy/ui/main_window_init.py +58 -72
  40. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/mirror_dialog.py +140 -140
  41. moleditpy-3.0.0a2/src/moleditpy/modules/main_window_molecular_parsers.py → moleditpy-3.0.0a4/src/moleditpy/ui/molecular_parsers.py +592 -594
  42. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/molecular_scene_handler.py +30 -15
  43. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/molecule_scene.py +33 -24
  44. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/move_group_dialog.py +4 -6
  45. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/periodic_table_dialog.py +196 -196
  46. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/planarize_dialog.py +239 -242
  47. moleditpy-3.0.0a2/src/moleditpy/modules/main_window_project_io.py → moleditpy-3.0.0a4/src/moleditpy/ui/project_io.py +9 -15
  48. moleditpy-3.0.0a4/src/moleditpy/ui/settings_dialog.py +229 -0
  49. moleditpy-3.0.0a4/src/moleditpy/ui/settings_tabs/__init__.py +11 -0
  50. moleditpy-3.0.0a4/src/moleditpy/ui/settings_tabs/settings_2d_tab.py +249 -0
  51. moleditpy-3.0.0a4/src/moleditpy/ui/settings_tabs/settings_3d_tabs.py +321 -0
  52. moleditpy-3.0.0a4/src/moleditpy/ui/settings_tabs/settings_other_tab.py +99 -0
  53. moleditpy-3.0.0a4/src/moleditpy/ui/settings_tabs/settings_tab_base.py +33 -0
  54. moleditpy-3.0.0a2/src/moleditpy/modules/__init__.py → moleditpy-3.0.0a4/src/moleditpy/ui/sip_isdeleted_safe.py +35 -41
  55. moleditpy-3.0.0a2/src/moleditpy/modules/main_window_string_importers.py → moleditpy-3.0.0a4/src/moleditpy/ui/string_importers.py +247 -248
  56. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/template_preview_item.py +2 -2
  57. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/template_preview_view.py +85 -85
  58. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/translation_dialog.py +347 -347
  59. moleditpy-3.0.0a2/src/moleditpy/modules/main_window_ui_manager.py → moleditpy-3.0.0a4/src/moleditpy/ui/ui_manager.py +511 -518
  60. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/user_template_dialog.py +712 -712
  61. moleditpy-3.0.0a2/src/moleditpy/modules/main_window_view_3d.py → moleditpy-3.0.0a4/src/moleditpy/ui/view_3d_logic.py +89 -69
  62. moleditpy-3.0.0a2/src/moleditpy/modules/main_window_view_loaders.py → moleditpy-3.0.0a4/src/moleditpy/ui/view_loaders.py +274 -282
  63. moleditpy-3.0.0a4/src/moleditpy/utils/__init__.py +11 -0
  64. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/utils}/constants.py +1 -1
  65. moleditpy-3.0.0a4/src/moleditpy/utils/sip_isdeleted_safe.py +41 -0
  66. moleditpy-3.0.0a2/src/MoleditPy.egg-info/SOURCES.txt +0 -72
  67. moleditpy-3.0.0a2/src/moleditpy/modules/main_window.py +0 -107
  68. moleditpy-3.0.0a2/src/moleditpy/modules/settings_dialog.py +0 -1705
  69. moleditpy-3.0.0a2/src/moleditpy/modules/sip_isdeleted_safe.py +0 -17
  70. {moleditpy-3.0.0a2 → moleditpy-3.0.0a4}/LICENSE +0 -0
  71. {moleditpy-3.0.0a2 → moleditpy-3.0.0a4}/setup.cfg +0 -0
  72. {moleditpy-3.0.0a2 → moleditpy-3.0.0a4}/src/MoleditPy.egg-info/dependency_links.txt +0 -0
  73. {moleditpy-3.0.0a2 → moleditpy-3.0.0a4}/src/MoleditPy.egg-info/entry_points.txt +0 -0
  74. {moleditpy-3.0.0a2 → moleditpy-3.0.0a4}/src/MoleditPy.egg-info/requires.txt +0 -0
  75. {moleditpy-3.0.0a2 → moleditpy-3.0.0a4}/src/MoleditPy.egg-info/top_level.txt +0 -0
  76. {moleditpy-3.0.0a2 → moleditpy-3.0.0a4}/src/moleditpy/__main__.py +0 -0
  77. {moleditpy-3.0.0a2 → moleditpy-3.0.0a4}/src/moleditpy/assets/file_icon.ico +0 -0
  78. {moleditpy-3.0.0a2 → moleditpy-3.0.0a4}/src/moleditpy/assets/icon.icns +0 -0
  79. {moleditpy-3.0.0a2 → moleditpy-3.0.0a4}/src/moleditpy/assets/icon.ico +0 -0
  80. {moleditpy-3.0.0a2 → moleditpy-3.0.0a4}/src/moleditpy/assets/icon.png +0 -0
  81. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/plugins}/plugin_interface.py +0 -0
  82. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/plugins}/plugin_manager_window.py +0 -0
  83. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/ui}/zoomable_view.py +0 -0
  84. {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a4/src/moleditpy/utils}/system_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MoleditPy
3
- Version: 3.0.0a2
3
+ Version: 3.0.0a4
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
@@ -711,7 +711,7 @@ Dynamic: license-file
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
713
  ![Core Logic Coverage](https://img.shields.io/badge/core_logic_coverage-80%25-green)
714
- ![Overall Coverage](https://img.shields.io/badge/coverage-63.83%25-green)
714
+ ![Overall Coverage](https://img.shields.io/badge/coverage-64%25-green)
715
715
  ![GUI Status](https://img.shields.io/badge/GUI-Manually_Verified-blue)
716
716
  ![Pylint Score](https://img.shields.io/badge/pylint-8.93%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)
@@ -834,6 +834,7 @@ moleditpy
834
834
  * **GUI and 2D Drawing (PyQt6):** The editor is built on a `QGraphicsScene`, where custom `AtomItem` and `BondItem` objects are interactively manipulated. The Undo/Redo feature is implemented by serializing the application state.
835
835
  * **Chemical Calculations (RDKit / Open Babel):** RDKit is used to generate molecule objects from 2D data, perform 3D coordinate generation, and calculate properties. Open Babel serves as a fallback for 3D conversion. All heavy computations are run on a separate `QThread` to keep the GUI responsive.
836
836
  * **3D Visualization (PyVista / pyvistaqt):** 3D rendering is achieved by generating PyVista meshes (spheres and cylinders) from RDKit conformer coordinates. A custom `vtkInteractorStyle` enables direct drag-and-drop editing of atoms in the 3D view.
837
+ * **Modular Architecture:** The codebase is organized into dedicated packages for `core` logic, `ui` components, and `utils`. The main application logic is decomposed into reusable mixins, ensuring long-term maintainability and easier verification.
837
838
 
838
839
  ## License
839
840
 
@@ -7,7 +7,7 @@
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
9
  ![Core Logic Coverage](https://img.shields.io/badge/core_logic_coverage-80%25-green)
10
- ![Overall Coverage](https://img.shields.io/badge/coverage-63.83%25-green)
10
+ ![Overall Coverage](https://img.shields.io/badge/coverage-64%25-green)
11
11
  ![GUI Status](https://img.shields.io/badge/GUI-Manually_Verified-blue)
12
12
  ![Pylint Score](https://img.shields.io/badge/pylint-8.93%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)
@@ -130,6 +130,7 @@ moleditpy
130
130
  * **GUI and 2D Drawing (PyQt6):** The editor is built on a `QGraphicsScene`, where custom `AtomItem` and `BondItem` objects are interactively manipulated. The Undo/Redo feature is implemented by serializing the application state.
131
131
  * **Chemical Calculations (RDKit / Open Babel):** RDKit is used to generate molecule objects from 2D data, perform 3D coordinate generation, and calculate properties. Open Babel serves as a fallback for 3D conversion. All heavy computations are run on a separate `QThread` to keep the GUI responsive.
132
132
  * **3D Visualization (PyVista / pyvistaqt):** 3D rendering is achieved by generating PyVista meshes (spheres and cylinders) from RDKit conformer coordinates. A custom `vtkInteractorStyle` enables direct drag-and-drop editing of atoms in the 3D view.
133
+ * **Modular Architecture:** The codebase is organized into dedicated packages for `core` logic, `ui` components, and `utils`. The main application logic is decomposed into reusable mixins, ensuring long-term maintainability and easier verification.
133
134
 
134
135
  ## License
135
136
 
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "MoleditPy"
7
7
 
8
- version = "3.0.0a2"
8
+ version = "3.0.0a4"
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.0a2
3
+ Version: 3.0.0a4
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
@@ -711,7 +711,7 @@ Dynamic: license-file
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
713
  ![Core Logic Coverage](https://img.shields.io/badge/core_logic_coverage-80%25-green)
714
- ![Overall Coverage](https://img.shields.io/badge/coverage-63.83%25-green)
714
+ ![Overall Coverage](https://img.shields.io/badge/coverage-64%25-green)
715
715
  ![GUI Status](https://img.shields.io/badge/GUI-Manually_Verified-blue)
716
716
  ![Pylint Score](https://img.shields.io/badge/pylint-8.93%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)
@@ -834,6 +834,7 @@ moleditpy
834
834
  * **GUI and 2D Drawing (PyQt6):** The editor is built on a `QGraphicsScene`, where custom `AtomItem` and `BondItem` objects are interactively manipulated. The Undo/Redo feature is implemented by serializing the application state.
835
835
  * **Chemical Calculations (RDKit / Open Babel):** RDKit is used to generate molecule objects from 2D data, perform 3D coordinate generation, and calculate properties. Open Babel serves as a fallback for 3D conversion. All heavy computations are run on a separate `QThread` to keep the GUI responsive.
836
836
  * **3D Visualization (PyVista / pyvistaqt):** 3D rendering is achieved by generating PyVista meshes (spheres and cylinders) from RDKit conformer coordinates. A custom `vtkInteractorStyle` enables direct drag-and-drop editing of atoms in the 3D view.
837
+ * **Modular Architecture:** The codebase is organized into dedicated packages for `core` logic, `ui` components, and `utils`. The main application logic is decomposed into reusable mixins, ensuring long-term maintainability and easier verification.
837
838
 
838
839
  ## License
839
840
 
@@ -0,0 +1,84 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/MoleditPy.egg-info/PKG-INFO
5
+ src/MoleditPy.egg-info/SOURCES.txt
6
+ src/MoleditPy.egg-info/dependency_links.txt
7
+ src/MoleditPy.egg-info/entry_points.txt
8
+ src/MoleditPy.egg-info/requires.txt
9
+ src/MoleditPy.egg-info/top_level.txt
10
+ src/moleditpy/__init__.py
11
+ src/moleditpy/__main__.py
12
+ src/moleditpy/main.py
13
+ src/moleditpy.egg-info/PKG-INFO
14
+ src/moleditpy.egg-info/SOURCES.txt
15
+ src/moleditpy.egg-info/dependency_links.txt
16
+ src/moleditpy.egg-info/entry_points.txt
17
+ src/moleditpy.egg-info/requires.txt
18
+ src/moleditpy.egg-info/top_level.txt
19
+ src/moleditpy/assets/file_icon.ico
20
+ src/moleditpy/assets/icon.icns
21
+ src/moleditpy/assets/icon.ico
22
+ src/moleditpy/assets/icon.png
23
+ src/moleditpy/core/__init__.py
24
+ src/moleditpy/core/mol_geometry.py
25
+ src/moleditpy/core/molecular_data.py
26
+ src/moleditpy/modules/__init__.py
27
+ src/moleditpy/plugins/__init__.py
28
+ src/moleditpy/plugins/plugin_interface.py
29
+ src/moleditpy/plugins/plugin_manager.py
30
+ src/moleditpy/plugins/plugin_manager_window.py
31
+ src/moleditpy/ui/__init__.py
32
+ src/moleditpy/ui/about_dialog.py
33
+ src/moleditpy/ui/align_plane_dialog.py
34
+ src/moleditpy/ui/alignment_dialog.py
35
+ src/moleditpy/ui/analysis_window.py
36
+ src/moleditpy/ui/angle_dialog.py
37
+ src/moleditpy/ui/app_state.py
38
+ src/moleditpy/ui/atom_item.py
39
+ src/moleditpy/ui/bond_item.py
40
+ src/moleditpy/ui/bond_length_dialog.py
41
+ src/moleditpy/ui/calculation_worker.py
42
+ src/moleditpy/ui/color_settings_dialog.py
43
+ src/moleditpy/ui/compute_engine.py
44
+ src/moleditpy/ui/compute_logic.py
45
+ src/moleditpy/ui/constrained_optimization_dialog.py
46
+ src/moleditpy/ui/custom_interactor_style.py
47
+ src/moleditpy/ui/custom_qt_interactor.py
48
+ src/moleditpy/ui/dialog_3d_picking_mixin.py
49
+ src/moleditpy/ui/dialog_logic.py
50
+ src/moleditpy/ui/dialog_manager.py
51
+ src/moleditpy/ui/dihedral_dialog.py
52
+ src/moleditpy/ui/edit_3d_logic.py
53
+ src/moleditpy/ui/edit_actions_logic.py
54
+ src/moleditpy/ui/export_logic.py
55
+ src/moleditpy/ui/main_window.py
56
+ src/moleditpy/ui/main_window_init.py
57
+ src/moleditpy/ui/mirror_dialog.py
58
+ src/moleditpy/ui/molecular_parsers.py
59
+ src/moleditpy/ui/molecular_scene_handler.py
60
+ src/moleditpy/ui/molecule_scene.py
61
+ src/moleditpy/ui/move_group_dialog.py
62
+ src/moleditpy/ui/periodic_table_dialog.py
63
+ src/moleditpy/ui/planarize_dialog.py
64
+ src/moleditpy/ui/project_io.py
65
+ src/moleditpy/ui/settings_dialog.py
66
+ src/moleditpy/ui/sip_isdeleted_safe.py
67
+ src/moleditpy/ui/string_importers.py
68
+ src/moleditpy/ui/template_preview_item.py
69
+ src/moleditpy/ui/template_preview_view.py
70
+ src/moleditpy/ui/translation_dialog.py
71
+ src/moleditpy/ui/ui_manager.py
72
+ src/moleditpy/ui/user_template_dialog.py
73
+ src/moleditpy/ui/view_3d_logic.py
74
+ src/moleditpy/ui/view_loaders.py
75
+ src/moleditpy/ui/zoomable_view.py
76
+ src/moleditpy/ui/settings_tabs/__init__.py
77
+ src/moleditpy/ui/settings_tabs/settings_2d_tab.py
78
+ src/moleditpy/ui/settings_tabs/settings_3d_tabs.py
79
+ src/moleditpy/ui/settings_tabs/settings_other_tab.py
80
+ src/moleditpy/ui/settings_tabs/settings_tab_base.py
81
+ src/moleditpy/utils/__init__.py
82
+ src/moleditpy/utils/constants.py
83
+ src/moleditpy/utils/sip_isdeleted_safe.py
84
+ src/moleditpy/utils/system_utils.py
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ MoleditPy — A Python-based molecular editing software
6
+
7
+ Author: Hiromichi Yokoyama
8
+ License: GPL-3.0 license
9
+ Repo: https://github.com/HiroYokoyama/python_molecular_editor
10
+ DOI: 10.5281/zenodo.17268532
11
+ """
12
+
13
+ """Top-level package for moleditpy."""
14
+
15
+ import importlib.util
16
+
17
+ try:
18
+ OBABEL_AVAILABLE = importlib.util.find_spec("openbabel") is not None
19
+ except ImportError:
20
+ OBABEL_AVAILABLE = False
File without changes
@@ -10,12 +10,11 @@ Repo: https://github.com/HiroYokoyama/python_molecular_editor
10
10
  DOI: 10.5281/zenodo.17268532
11
11
  """
12
12
 
13
- """Pure-logic molecular-geometry helpers.
14
-
15
- This module is intentionally free of any GUI imports so that it can
16
- be unit-tested in isolation and shared across dialogs and main-window
17
- submodules without circular dependencies.
18
- """
13
+ from __future__ import annotations
14
+ import math
15
+ import logging
16
+ from collections import deque
17
+ from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union
19
18
 
20
19
  import numpy as np
21
20
 
@@ -23,8 +22,7 @@ import numpy as np
23
22
  # Primitive geometry helpers
24
23
  # ------------------------------------------------------------------
25
24
 
26
-
27
- def calc_distance(pos1, pos2) -> float:
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:
28
26
  """Return the Euclidean distance between two 3-D positions.
29
27
 
30
28
  Parameters
@@ -42,7 +40,11 @@ def calc_distance(pos1, pos2) -> float:
42
40
  )
43
41
 
44
42
 
45
- def calc_angle_deg(pos1, pos2_vertex, pos3) -> float:
43
+ def calc_angle_deg(
44
+ pos1: Union[np.ndarray, Tuple[float, float, float], List[float]],
45
+ pos2_vertex: Union[np.ndarray, Tuple[float, float, float], List[float]],
46
+ pos3: Union[np.ndarray, Tuple[float, float, float], List[float]],
47
+ ) -> float:
46
48
  """Return the angle pos1–pos2_vertex–pos3 in degrees.
47
49
 
48
50
  The angle is measured at *pos2_vertex* and is always in [0, 180].
@@ -76,7 +78,7 @@ def calc_angle_deg(pos1, pos2_vertex, pos3) -> float:
76
78
  # ------------------------------------------------------------------
77
79
 
78
80
 
79
- def get_connected_group(mol, start_atom, exclude=None):
81
+ def get_connected_group(mol: Any, start_atom: int, exclude: Optional[int] = None) -> Set[int]:
80
82
  """Return the set of atom indices reachable from *start_atom*
81
83
  without passing through *exclude*.
82
84
 
@@ -122,7 +124,7 @@ def get_connected_group(mol, start_atom, exclude=None):
122
124
  # ------------------------------------------------------------------
123
125
 
124
126
 
125
- def rodrigues_rotate(v, axis, angle):
127
+ def rodrigues_rotate(v: np.ndarray, axis: np.ndarray, angle: float) -> np.ndarray:
126
128
  """Rotate vector *v* around a unit *axis* by *angle* radians.
127
129
 
128
130
  Implements Rodrigues' rotation formula:
@@ -149,8 +151,13 @@ def rodrigues_rotate(v, axis, angle):
149
151
 
150
152
 
151
153
  def adjust_bond_angle(
152
- positions, idx_a, idx_b, idx_c, target_angle_deg, atom_indices_to_move
153
- ):
154
+ positions: np.ndarray,
155
+ idx_a: int,
156
+ idx_b: int,
157
+ idx_c: int,
158
+ target_angle_deg: float,
159
+ atom_indices_to_move: Iterable[int],
160
+ ) -> float:
154
161
  """Adjust the A–B–C bond angle to *target_angle_deg* using a
155
162
  difference-based rotation.
156
163
 
@@ -242,7 +249,7 @@ def adjust_bond_angle(
242
249
  # ------------------------------------------------------------------
243
250
 
244
251
 
245
- def calculate_dihedral(positions, i1, i2, i3, i4):
252
+ def calculate_dihedral(positions: Any, i1: int, i2: int, i3: int, i4: int) -> float:
246
253
  """Compute the dihedral angle defined by four atom indices.
247
254
 
248
255
  Parameters
@@ -312,7 +319,7 @@ _VALENCE_LIMITS = {
312
319
  }
313
320
 
314
321
 
315
- def is_problematic_valence(symbol, bond_count, charge=0):
322
+ def is_problematic_valence(symbol: str, bond_count: Union[int, float], charge: int = 0) -> bool:
316
323
  """Return ``True`` if the atom's total bond order exceeds its
317
324
  typical maximum valence.
318
325
 
@@ -404,7 +411,7 @@ def inject_ez_stereo_to_mol_block(mol_block, rdkit_mol, bonds_data):
404
411
  return "\n".join(mol_lines)
405
412
 
406
413
 
407
- def identify_valence_problems(atoms_data, bonds_data):
414
+ def identify_valence_problems(atoms_data: Dict[int, Any], bonds_data: Dict[Tuple[int, int], Any]) -> List[int]:
408
415
  """Identify atoms with problematic valence.
409
416
 
410
417
  Parameters
@@ -437,3 +444,148 @@ def identify_valence_problems(atoms_data, bonds_data):
437
444
  problem_atom_ids.append(atom_id)
438
445
 
439
446
  return problem_atom_ids
447
+
448
+
449
+ def optimize_2d_coords(mol: Any) -> Dict[int, Tuple[float, float]]:
450
+ """Generate 2D coordinates using RDKit and return a map of (x, y) tuples."""
451
+ from rdkit.Chem import AllChem
452
+
453
+ AllChem.Compute2DCoords(mol)
454
+ conf = mol.GetConformer()
455
+ new_positions = {}
456
+ for rdkit_atom in mol.GetAtoms():
457
+ if rdkit_atom.HasProp("_original_atom_id"):
458
+ original_id = rdkit_atom.GetIntProp("_original_atom_id")
459
+ pos = conf.GetAtomPosition(rdkit_atom.GetIdx())
460
+ new_positions[original_id] = (pos.x, pos.y)
461
+ return new_positions
462
+
463
+
464
+ def calculate_best_fit_plane_projection(centered_positions, normal, centroid):
465
+ """Project centered points orthogonally onto the plane defined by normal and centroid."""
466
+ projections = centered_positions - np.outer(
467
+ np.dot(centered_positions, normal), normal
468
+ )
469
+ return projections + centroid
470
+
471
+
472
+ def rotate_2d_points(points_map, center_x, center_y, angle_degrees):
473
+ """Rotate 2D points (atom_id -> (x, y)) around a center."""
474
+ rad = math.radians(angle_degrees)
475
+ cos_a = math.cos(rad)
476
+ sin_a = math.sin(rad)
477
+ new_positions = {}
478
+ for atom_id, (x, y) in points_map.items():
479
+ dx = x - center_x
480
+ dy = y - center_y
481
+ new_dx = dx * cos_a - dy * sin_a
482
+ new_dy = dx * sin_a + dy * cos_a
483
+ new_positions[atom_id] = (center_x + new_dx, center_y + new_dy)
484
+ return new_positions
485
+
486
+
487
+ def resolve_2d_overlaps(
488
+ atom_ids,
489
+ positions_map,
490
+ adjacency_list,
491
+ overlap_threshold=0.5,
492
+ move_distance=20,
493
+ has_bond_check_func=None,
494
+ ):
495
+ """Detect and resolve overlapping atom groups in 2D.
496
+
497
+ Returns list of (atom_ids_set, translation_vector_tuple).
498
+ """
499
+ overlapping_pairs = []
500
+ ids_list = list(atom_ids)
501
+ for i in range(len(ids_list)):
502
+ for j in range(i + 1, len(ids_list)):
503
+ id1 = ids_list[i]
504
+ id2 = ids_list[j]
505
+
506
+ # Skip directly bonded pairs
507
+ if has_bond_check_func and has_bond_check_func(id1, id2):
508
+ continue
509
+
510
+ p1 = positions_map[id1]
511
+ p2 = positions_map[id2]
512
+ dist = math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
513
+ if dist < overlap_threshold:
514
+ overlapping_pairs.append((id1, id2))
515
+
516
+ if not overlapping_pairs:
517
+ return []
518
+
519
+ # Union-Find for overlap groups
520
+ parent = {aid: aid for aid in atom_ids}
521
+
522
+ def find_set(aid):
523
+ if parent[aid] == aid:
524
+ return aid
525
+ parent[aid] = find_set(parent[aid])
526
+ return parent[aid]
527
+
528
+ def unite_sets(aid1, aid2):
529
+ root1 = find_set(aid1)
530
+ root2 = find_set(aid2)
531
+ if root1 != root2:
532
+ parent[root2] = root1
533
+
534
+ for id1, id2 in overlapping_pairs:
535
+ unite_sets(id1, id2)
536
+
537
+ groups_by_root = {}
538
+ for aid in atom_ids:
539
+ root = find_set(aid)
540
+ groups_by_root.setdefault(root, []).append(aid)
541
+
542
+ move_operations = []
543
+ processed_roots = set()
544
+
545
+ for root_id, group_ids in groups_by_root.items():
546
+ if root_id in processed_roots or len(group_ids) < 2:
547
+ continue
548
+ processed_roots.add(root_id)
549
+
550
+ # Split into fragments via BFS
551
+ fragments = []
552
+ visited = set()
553
+ group_set = set(group_ids)
554
+ for aid in group_ids:
555
+ if aid not in visited:
556
+ frag = set()
557
+ q = deque([aid])
558
+ visited.add(aid)
559
+ frag.add(aid)
560
+ while q:
561
+ curr = q.popleft()
562
+ for neighbor in adjacency_list.get(curr, []):
563
+ if neighbor in group_set and neighbor not in visited:
564
+ visited.add(neighbor)
565
+ frag.add(neighbor)
566
+ q.append(neighbor)
567
+ fragments.append(frag)
568
+
569
+ if len(fragments) < 2:
570
+ continue
571
+
572
+ # Find representative pair
573
+ rep_id1, rep_id2 = None, None
574
+ for i1, i2 in overlapping_pairs:
575
+ if find_set(i1) == root_id:
576
+ rep_id1, rep_id2 = i1, i2
577
+ break
578
+
579
+ if rep_id1 is None:
580
+ continue
581
+
582
+ frag1 = next((f for f in fragments if rep_id1 in f), None)
583
+ frag2 = next((f for f in fragments if rep_id2 in f), None)
584
+ if frag1 is None or frag2 is None or frag1 == frag2:
585
+ continue
586
+
587
+ ids_to_move = frag1 if rep_id1 > rep_id2 else frag2
588
+ # Move vector (-move_distance, move_distance)
589
+ move_operations.append((ids_to_move, (-move_distance, move_distance)))
590
+
591
+ return move_operations