moleditpy 3.0.0a2__tar.gz → 3.0.0a3__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.
- {moleditpy-3.0.0a2 → moleditpy-3.0.0a3}/PKG-INFO +3 -2
- {moleditpy-3.0.0a2 → moleditpy-3.0.0a3}/README.md +2 -1
- {moleditpy-3.0.0a2 → moleditpy-3.0.0a3}/pyproject.toml +1 -1
- {moleditpy-3.0.0a2 → moleditpy-3.0.0a3}/src/MoleditPy.egg-info/PKG-INFO +3 -2
- moleditpy-3.0.0a3/src/MoleditPy.egg-info/SOURCES.txt +86 -0
- moleditpy-3.0.0a3/src/moleditpy/__init__.py +20 -0
- moleditpy-3.0.0a3/src/moleditpy/core/__init__.py +0 -0
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/core}/mol_geometry.py +147 -0
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/core}/molecular_data.py +119 -55
- {moleditpy-3.0.0a2 → moleditpy-3.0.0a3}/src/moleditpy/main.py +23 -4
- moleditpy-3.0.0a3/src/moleditpy/modules/__init__.py +122 -0
- {moleditpy-3.0.0a2/src/moleditpy → moleditpy-3.0.0a3/src/moleditpy/plugins}/__init__.py +11 -15
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/plugins}/plugin_manager.py +573 -578
- moleditpy-3.0.0a3/src/moleditpy/ui/__init__.py +25 -0
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/about_dialog.py +4 -3
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/align_plane_dialog.py +306 -306
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/alignment_dialog.py +1 -1
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/analysis_window.py +226 -226
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/angle_dialog.py +5 -4
- moleditpy-3.0.0a2/src/moleditpy/modules/main_window_app_state.py → moleditpy-3.0.0a3/src/moleditpy/ui/app_state.py +22 -27
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/atom_item.py +4 -2
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/bond_item.py +535 -528
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/bond_length_dialog.py +3 -2
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/calculation_worker.py +970 -970
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/color_settings_dialog.py +404 -404
- moleditpy-3.0.0a2/src/moleditpy/modules/main_window_compute.py → moleditpy-3.0.0a3/src/moleditpy/ui/compute_engine.py +4 -11
- moleditpy-3.0.0a3/src/moleditpy/ui/compute_logic.py +526 -0
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/constrained_optimization_dialog.py +2 -1
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/custom_interactor_style.py +34 -24
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/custom_qt_interactor.py +97 -97
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/dialog_3d_picking_mixin.py +210 -210
- moleditpy-3.0.0a3/src/moleditpy/ui/dialog_logic.py +438 -0
- moleditpy-3.0.0a2/src/moleditpy/modules/main_window_dialog_manager.py → moleditpy-3.0.0a3/src/moleditpy/ui/dialog_manager.py +17 -21
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/dihedral_dialog.py +5 -4
- moleditpy-3.0.0a2/src/moleditpy/modules/main_window_edit_3d.py → moleditpy-3.0.0a3/src/moleditpy/ui/edit_3d.py +2 -10
- moleditpy-3.0.0a3/src/moleditpy/ui/edit_3d_logic.py +477 -0
- moleditpy-3.0.0a2/src/moleditpy/modules/main_window_edit_actions.py → moleditpy-3.0.0a3/src/moleditpy/ui/edit_actions.py +5 -11
- moleditpy-3.0.0a3/src/moleditpy/ui/edit_actions_logic.py +1330 -0
- moleditpy-3.0.0a2/src/moleditpy/modules/main_window_export.py → moleditpy-3.0.0a3/src/moleditpy/ui/export_logic.py +12 -10
- moleditpy-3.0.0a3/src/moleditpy/ui/main_window.py +218 -0
- moleditpy-3.0.0a2/src/moleditpy/modules/main_window_main_init.py → moleditpy-3.0.0a3/src/moleditpy/ui/main_window_init.py +58 -72
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/mirror_dialog.py +140 -140
- moleditpy-3.0.0a2/src/moleditpy/modules/main_window_molecular_parsers.py → moleditpy-3.0.0a3/src/moleditpy/ui/molecular_parsers.py +587 -594
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/molecular_scene_handler.py +10 -6
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/molecule_scene.py +12 -10
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/move_group_dialog.py +4 -6
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/periodic_table_dialog.py +196 -196
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/planarize_dialog.py +239 -242
- moleditpy-3.0.0a2/src/moleditpy/modules/main_window_project_io.py → moleditpy-3.0.0a3/src/moleditpy/ui/project_io.py +1 -8
- moleditpy-3.0.0a3/src/moleditpy/ui/settings_dialog.py +229 -0
- moleditpy-3.0.0a3/src/moleditpy/ui/settings_tabs/__init__.py +11 -0
- moleditpy-3.0.0a3/src/moleditpy/ui/settings_tabs/settings_2d_tab.py +249 -0
- moleditpy-3.0.0a3/src/moleditpy/ui/settings_tabs/settings_3d_tabs.py +321 -0
- moleditpy-3.0.0a3/src/moleditpy/ui/settings_tabs/settings_other_tab.py +99 -0
- moleditpy-3.0.0a3/src/moleditpy/ui/settings_tabs/settings_tab_base.py +33 -0
- moleditpy-3.0.0a2/src/moleditpy/modules/__init__.py → moleditpy-3.0.0a3/src/moleditpy/ui/sip_isdeleted_safe.py +35 -41
- moleditpy-3.0.0a2/src/moleditpy/modules/main_window_string_importers.py → moleditpy-3.0.0a3/src/moleditpy/ui/string_importers.py +242 -248
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/template_preview_item.py +2 -2
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/template_preview_view.py +85 -85
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/translation_dialog.py +347 -347
- moleditpy-3.0.0a2/src/moleditpy/modules/main_window_ui_manager.py → moleditpy-3.0.0a3/src/moleditpy/ui/ui_manager.py +511 -518
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/user_template_dialog.py +712 -712
- moleditpy-3.0.0a2/src/moleditpy/modules/main_window_view_3d.py → moleditpy-3.0.0a3/src/moleditpy/ui/view_3d_logic.py +45 -45
- moleditpy-3.0.0a2/src/moleditpy/modules/main_window_view_loaders.py → moleditpy-3.0.0a3/src/moleditpy/ui/view_loaders.py +274 -282
- moleditpy-3.0.0a3/src/moleditpy/utils/__init__.py +11 -0
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/utils}/constants.py +1 -1
- moleditpy-3.0.0a3/src/moleditpy/utils/sip_isdeleted_safe.py +41 -0
- moleditpy-3.0.0a2/src/MoleditPy.egg-info/SOURCES.txt +0 -72
- moleditpy-3.0.0a2/src/moleditpy/modules/main_window.py +0 -107
- moleditpy-3.0.0a2/src/moleditpy/modules/settings_dialog.py +0 -1705
- moleditpy-3.0.0a2/src/moleditpy/modules/sip_isdeleted_safe.py +0 -17
- {moleditpy-3.0.0a2 → moleditpy-3.0.0a3}/LICENSE +0 -0
- {moleditpy-3.0.0a2 → moleditpy-3.0.0a3}/setup.cfg +0 -0
- {moleditpy-3.0.0a2 → moleditpy-3.0.0a3}/src/MoleditPy.egg-info/dependency_links.txt +0 -0
- {moleditpy-3.0.0a2 → moleditpy-3.0.0a3}/src/MoleditPy.egg-info/entry_points.txt +0 -0
- {moleditpy-3.0.0a2 → moleditpy-3.0.0a3}/src/MoleditPy.egg-info/requires.txt +0 -0
- {moleditpy-3.0.0a2 → moleditpy-3.0.0a3}/src/MoleditPy.egg-info/top_level.txt +0 -0
- {moleditpy-3.0.0a2 → moleditpy-3.0.0a3}/src/moleditpy/__main__.py +0 -0
- {moleditpy-3.0.0a2 → moleditpy-3.0.0a3}/src/moleditpy/assets/file_icon.ico +0 -0
- {moleditpy-3.0.0a2 → moleditpy-3.0.0a3}/src/moleditpy/assets/icon.icns +0 -0
- {moleditpy-3.0.0a2 → moleditpy-3.0.0a3}/src/moleditpy/assets/icon.ico +0 -0
- {moleditpy-3.0.0a2 → moleditpy-3.0.0a3}/src/moleditpy/assets/icon.png +0 -0
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/plugins}/plugin_interface.py +0 -0
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/plugins}/plugin_manager_window.py +0 -0
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/ui}/zoomable_view.py +0 -0
- {moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/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.
|
|
3
|
+
Version: 3.0.0a3
|
|
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
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
712
712
|
[](https://github.com/HiroYokoyama/python_molecular_editor/actions)
|
|
713
713
|

|
|
714
|
-

|
|
715
715
|

|
|
716
716
|

|
|
717
717
|
[](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
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
8
8
|
[](https://github.com/HiroYokoyama/python_molecular_editor/actions)
|
|
9
9
|

|
|
10
|
-

|
|
11
11
|

|
|
12
12
|

|
|
13
13
|
[](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
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.0a3
|
|
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
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
712
712
|
[](https://github.com/HiroYokoyama/python_molecular_editor/actions)
|
|
713
713
|

|
|
714
|
-

|
|
715
715
|

|
|
716
716
|

|
|
717
717
|
[](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,86 @@
|
|
|
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.py
|
|
53
|
+
src/moleditpy/ui/edit_3d_logic.py
|
|
54
|
+
src/moleditpy/ui/edit_actions.py
|
|
55
|
+
src/moleditpy/ui/edit_actions_logic.py
|
|
56
|
+
src/moleditpy/ui/export_logic.py
|
|
57
|
+
src/moleditpy/ui/main_window.py
|
|
58
|
+
src/moleditpy/ui/main_window_init.py
|
|
59
|
+
src/moleditpy/ui/mirror_dialog.py
|
|
60
|
+
src/moleditpy/ui/molecular_parsers.py
|
|
61
|
+
src/moleditpy/ui/molecular_scene_handler.py
|
|
62
|
+
src/moleditpy/ui/molecule_scene.py
|
|
63
|
+
src/moleditpy/ui/move_group_dialog.py
|
|
64
|
+
src/moleditpy/ui/periodic_table_dialog.py
|
|
65
|
+
src/moleditpy/ui/planarize_dialog.py
|
|
66
|
+
src/moleditpy/ui/project_io.py
|
|
67
|
+
src/moleditpy/ui/settings_dialog.py
|
|
68
|
+
src/moleditpy/ui/sip_isdeleted_safe.py
|
|
69
|
+
src/moleditpy/ui/string_importers.py
|
|
70
|
+
src/moleditpy/ui/template_preview_item.py
|
|
71
|
+
src/moleditpy/ui/template_preview_view.py
|
|
72
|
+
src/moleditpy/ui/translation_dialog.py
|
|
73
|
+
src/moleditpy/ui/ui_manager.py
|
|
74
|
+
src/moleditpy/ui/user_template_dialog.py
|
|
75
|
+
src/moleditpy/ui/view_3d_logic.py
|
|
76
|
+
src/moleditpy/ui/view_loaders.py
|
|
77
|
+
src/moleditpy/ui/zoomable_view.py
|
|
78
|
+
src/moleditpy/ui/settings_tabs/__init__.py
|
|
79
|
+
src/moleditpy/ui/settings_tabs/settings_2d_tab.py
|
|
80
|
+
src/moleditpy/ui/settings_tabs/settings_3d_tabs.py
|
|
81
|
+
src/moleditpy/ui/settings_tabs/settings_other_tab.py
|
|
82
|
+
src/moleditpy/ui/settings_tabs/settings_tab_base.py
|
|
83
|
+
src/moleditpy/utils/__init__.py
|
|
84
|
+
src/moleditpy/utils/constants.py
|
|
85
|
+
src/moleditpy/utils/sip_isdeleted_safe.py
|
|
86
|
+
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
|
{moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/core}/mol_geometry.py
RENAMED
|
@@ -18,6 +18,8 @@ submodules without circular dependencies.
|
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
import numpy as np
|
|
21
|
+
import math
|
|
22
|
+
from collections import deque
|
|
21
23
|
|
|
22
24
|
# ------------------------------------------------------------------
|
|
23
25
|
# Primitive geometry helpers
|
|
@@ -437,3 +439,148 @@ def identify_valence_problems(atoms_data, bonds_data):
|
|
|
437
439
|
problem_atom_ids.append(atom_id)
|
|
438
440
|
|
|
439
441
|
return problem_atom_ids
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def optimize_2d_coords(mol):
|
|
445
|
+
"""Generate 2D coordinates using RDKit and return a map of (x, y) tuples."""
|
|
446
|
+
from rdkit.Chem import AllChem
|
|
447
|
+
|
|
448
|
+
AllChem.Compute2DCoords(mol)
|
|
449
|
+
conf = mol.GetConformer()
|
|
450
|
+
new_positions = {}
|
|
451
|
+
for rdkit_atom in mol.GetAtoms():
|
|
452
|
+
if rdkit_atom.HasProp("_original_atom_id"):
|
|
453
|
+
original_id = rdkit_atom.GetIntProp("_original_atom_id")
|
|
454
|
+
pos = conf.GetAtomPosition(rdkit_atom.GetIdx())
|
|
455
|
+
new_positions[original_id] = (pos.x, pos.y)
|
|
456
|
+
return new_positions
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def calculate_best_fit_plane_projection(centered_positions, normal, centroid):
|
|
460
|
+
"""Project centered points orthogonally onto the plane defined by normal and centroid."""
|
|
461
|
+
projections = centered_positions - np.outer(
|
|
462
|
+
np.dot(centered_positions, normal), normal
|
|
463
|
+
)
|
|
464
|
+
return projections + centroid
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def rotate_2d_points(points_map, center_x, center_y, angle_degrees):
|
|
468
|
+
"""Rotate 2D points (atom_id -> (x, y)) around a center."""
|
|
469
|
+
rad = math.radians(angle_degrees)
|
|
470
|
+
cos_a = math.cos(rad)
|
|
471
|
+
sin_a = math.sin(rad)
|
|
472
|
+
new_positions = {}
|
|
473
|
+
for atom_id, (x, y) in points_map.items():
|
|
474
|
+
dx = x - center_x
|
|
475
|
+
dy = y - center_y
|
|
476
|
+
new_dx = dx * cos_a - dy * sin_a
|
|
477
|
+
new_dy = dx * sin_a + dy * cos_a
|
|
478
|
+
new_positions[atom_id] = (center_x + new_dx, center_y + new_dy)
|
|
479
|
+
return new_positions
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def resolve_2d_overlaps(
|
|
483
|
+
atom_ids,
|
|
484
|
+
positions_map,
|
|
485
|
+
adjacency_list,
|
|
486
|
+
overlap_threshold=0.5,
|
|
487
|
+
move_distance=20,
|
|
488
|
+
has_bond_check_func=None,
|
|
489
|
+
):
|
|
490
|
+
"""Detect and resolve overlapping atom groups in 2D.
|
|
491
|
+
|
|
492
|
+
Returns list of (atom_ids_set, translation_vector_tuple).
|
|
493
|
+
"""
|
|
494
|
+
overlapping_pairs = []
|
|
495
|
+
ids_list = list(atom_ids)
|
|
496
|
+
for i in range(len(ids_list)):
|
|
497
|
+
for j in range(i + 1, len(ids_list)):
|
|
498
|
+
id1 = ids_list[i]
|
|
499
|
+
id2 = ids_list[j]
|
|
500
|
+
|
|
501
|
+
# Skip directly bonded pairs
|
|
502
|
+
if has_bond_check_func and has_bond_check_func(id1, id2):
|
|
503
|
+
continue
|
|
504
|
+
|
|
505
|
+
p1 = positions_map[id1]
|
|
506
|
+
p2 = positions_map[id2]
|
|
507
|
+
dist = math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
|
|
508
|
+
if dist < overlap_threshold:
|
|
509
|
+
overlapping_pairs.append((id1, id2))
|
|
510
|
+
|
|
511
|
+
if not overlapping_pairs:
|
|
512
|
+
return []
|
|
513
|
+
|
|
514
|
+
# Union-Find for overlap groups
|
|
515
|
+
parent = {aid: aid for aid in atom_ids}
|
|
516
|
+
|
|
517
|
+
def find_set(aid):
|
|
518
|
+
if parent[aid] == aid:
|
|
519
|
+
return aid
|
|
520
|
+
parent[aid] = find_set(parent[aid])
|
|
521
|
+
return parent[aid]
|
|
522
|
+
|
|
523
|
+
def unite_sets(aid1, aid2):
|
|
524
|
+
root1 = find_set(aid1)
|
|
525
|
+
root2 = find_set(aid2)
|
|
526
|
+
if root1 != root2:
|
|
527
|
+
parent[root2] = root1
|
|
528
|
+
|
|
529
|
+
for id1, id2 in overlapping_pairs:
|
|
530
|
+
unite_sets(id1, id2)
|
|
531
|
+
|
|
532
|
+
groups_by_root = {}
|
|
533
|
+
for aid in atom_ids:
|
|
534
|
+
root = find_set(aid)
|
|
535
|
+
groups_by_root.setdefault(root, []).append(aid)
|
|
536
|
+
|
|
537
|
+
move_operations = []
|
|
538
|
+
processed_roots = set()
|
|
539
|
+
|
|
540
|
+
for root_id, group_ids in groups_by_root.items():
|
|
541
|
+
if root_id in processed_roots or len(group_ids) < 2:
|
|
542
|
+
continue
|
|
543
|
+
processed_roots.add(root_id)
|
|
544
|
+
|
|
545
|
+
# Split into fragments via BFS
|
|
546
|
+
fragments = []
|
|
547
|
+
visited = set()
|
|
548
|
+
group_set = set(group_ids)
|
|
549
|
+
for aid in group_ids:
|
|
550
|
+
if aid not in visited:
|
|
551
|
+
frag = set()
|
|
552
|
+
q = deque([aid])
|
|
553
|
+
visited.add(aid)
|
|
554
|
+
frag.add(aid)
|
|
555
|
+
while q:
|
|
556
|
+
curr = q.popleft()
|
|
557
|
+
for neighbor in adjacency_list.get(curr, []):
|
|
558
|
+
if neighbor in group_set and neighbor not in visited:
|
|
559
|
+
visited.add(neighbor)
|
|
560
|
+
frag.add(neighbor)
|
|
561
|
+
q.append(neighbor)
|
|
562
|
+
fragments.append(frag)
|
|
563
|
+
|
|
564
|
+
if len(fragments) < 2:
|
|
565
|
+
continue
|
|
566
|
+
|
|
567
|
+
# Find representative pair
|
|
568
|
+
rep_id1, rep_id2 = None, None
|
|
569
|
+
for i1, i2 in overlapping_pairs:
|
|
570
|
+
if find_set(i1) == root_id:
|
|
571
|
+
rep_id1, rep_id2 = i1, i2
|
|
572
|
+
break
|
|
573
|
+
|
|
574
|
+
if not rep_id1:
|
|
575
|
+
continue
|
|
576
|
+
|
|
577
|
+
frag1 = next((f for f in fragments if rep_id1 in f), None)
|
|
578
|
+
frag2 = next((f for f in fragments if rep_id2 in f), None)
|
|
579
|
+
if not frag1 or not frag2 or frag1 == frag2:
|
|
580
|
+
continue
|
|
581
|
+
|
|
582
|
+
ids_to_move = frag1 if rep_id1 > rep_id2 else frag2
|
|
583
|
+
# Move vector (-move_distance, move_distance)
|
|
584
|
+
move_operations.append((ids_to_move, (-move_distance, move_distance)))
|
|
585
|
+
|
|
586
|
+
return move_operations
|
{moleditpy-3.0.0a2/src/moleditpy/modules → moleditpy-3.0.0a3/src/moleditpy/core}/molecular_data.py
RENAMED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
#!/usr/bin/env python3
|
|
3
2
|
# -*- coding: utf-8 -*-
|
|
4
3
|
|
|
@@ -11,13 +10,23 @@ Repo: https://github.com/HiroYokoyama/python_molecular_editor
|
|
|
11
10
|
DOI: 10.5281/zenodo.17268532
|
|
12
11
|
"""
|
|
13
12
|
|
|
13
|
+
import logging
|
|
14
14
|
from rdkit import Chem
|
|
15
|
-
from PyQt6.QtCore import QPointF
|
|
16
15
|
|
|
17
16
|
try:
|
|
18
|
-
from .constants import ANGSTROM_PER_PIXEL
|
|
17
|
+
from ..utils.constants import ANGSTROM_PER_PIXEL
|
|
19
18
|
except ImportError:
|
|
20
|
-
from
|
|
19
|
+
from moleditpy.utils.constants import ANGSTROM_PER_PIXEL
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PointTuple(tuple):
|
|
23
|
+
"""Backward-compatible tuple that allows .x() and .y() access like QPointF."""
|
|
24
|
+
|
|
25
|
+
def x(self):
|
|
26
|
+
return self[0]
|
|
27
|
+
|
|
28
|
+
def y(self):
|
|
29
|
+
return self[1]
|
|
21
30
|
|
|
22
31
|
|
|
23
32
|
class MolecularData:
|
|
@@ -29,9 +38,15 @@ class MolecularData:
|
|
|
29
38
|
|
|
30
39
|
def add_atom(self, symbol, pos, charge=0, radical=0):
|
|
31
40
|
atom_id = self._next_atom_id
|
|
41
|
+
# Internalize position as raw floats to decouple from UI types (QPointF)
|
|
42
|
+
if hasattr(pos, "x") and hasattr(pos, "y"):
|
|
43
|
+
raw_pos = PointTuple((float(pos.x()), float(pos.y())))
|
|
44
|
+
else:
|
|
45
|
+
raw_pos = PointTuple((float(pos[0]), float(pos[1])))
|
|
46
|
+
|
|
32
47
|
self.atoms[atom_id] = {
|
|
33
48
|
"symbol": symbol,
|
|
34
|
-
"pos":
|
|
49
|
+
"pos": raw_pos,
|
|
35
50
|
"item": None,
|
|
36
51
|
"charge": charge,
|
|
37
52
|
"radical": radical,
|
|
@@ -40,6 +55,16 @@ class MolecularData:
|
|
|
40
55
|
self._next_atom_id += 1
|
|
41
56
|
return atom_id
|
|
42
57
|
|
|
58
|
+
def set_atom_pos(self, atom_id, pos):
|
|
59
|
+
"""Update atom position using raw floats or QPointF."""
|
|
60
|
+
if atom_id in self.atoms:
|
|
61
|
+
if hasattr(pos, "x") and hasattr(pos, "y"):
|
|
62
|
+
self.atoms[atom_id]["pos"] = PointTuple(
|
|
63
|
+
(float(pos.x()), float(pos.y()))
|
|
64
|
+
)
|
|
65
|
+
else:
|
|
66
|
+
self.atoms[atom_id]["pos"] = PointTuple((float(pos[0]), float(pos[1])))
|
|
67
|
+
|
|
43
68
|
def add_bond(self, id1, id2, order=1, stereo=0):
|
|
44
69
|
# For stereo bonds, do not sort because ID order determines direction.
|
|
45
70
|
# For non-stereo bonds, sort to normalize the key.
|
|
@@ -68,16 +93,11 @@ class MolecularData:
|
|
|
68
93
|
# Safely get neighbors before deleting the atom's own entry
|
|
69
94
|
neighbors = self.adjacency_list.get(atom_id, [])
|
|
70
95
|
for neighbor_id in neighbors:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
)
|
|
76
|
-
self.adjacency_list[neighbor_id].remove(atom_id)
|
|
77
|
-
except (ValueError, KeyError, TypeError) as e:
|
|
78
|
-
logging.debug(
|
|
79
|
-
f"Suppressed exception: {e}"
|
|
80
|
-
) # Ignore adjacency list inconsistencies during atom removal
|
|
96
|
+
if (
|
|
97
|
+
neighbor_id in self.adjacency_list
|
|
98
|
+
and atom_id in self.adjacency_list[neighbor_id]
|
|
99
|
+
):
|
|
100
|
+
self.adjacency_list[neighbor_id].remove(atom_id)
|
|
81
101
|
|
|
82
102
|
# Now, safely delete the atom's own entry from the adjacency list
|
|
83
103
|
if atom_id in self.adjacency_list:
|
|
@@ -87,14 +107,9 @@ class MolecularData:
|
|
|
87
107
|
del self.atoms[atom_id]
|
|
88
108
|
|
|
89
109
|
# Remove bonds involving this atom
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
del self.bonds[key]
|
|
94
|
-
except (RuntimeError, KeyError) as e:
|
|
95
|
-
logging.debug(
|
|
96
|
-
f"Suppressed exception: {e}"
|
|
97
|
-
) # Ignore mutation issues during batch bond removal
|
|
110
|
+
bonds_to_remove = [key for key in self.bonds if atom_id in key]
|
|
111
|
+
for key in bonds_to_remove:
|
|
112
|
+
self.bonds.pop(key, None)
|
|
98
113
|
|
|
99
114
|
def remove_bond(self, id1, id2):
|
|
100
115
|
# Look for directional stereo bonds (forward/reverse) and normalized non-stereo bond keys.
|
|
@@ -105,16 +120,11 @@ class MolecularData:
|
|
|
105
120
|
key_to_remove = (id2, id1)
|
|
106
121
|
|
|
107
122
|
if key_to_remove:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
del self.bonds[key_to_remove]
|
|
114
|
-
except (ValueError, KeyError) as e:
|
|
115
|
-
logging.debug(
|
|
116
|
-
f"Suppressed exception: {e}"
|
|
117
|
-
) # Ignore if bond already removed or inconsistent
|
|
123
|
+
if id1 in self.adjacency_list and id2 in self.adjacency_list[id1]:
|
|
124
|
+
self.adjacency_list[id1].remove(id2)
|
|
125
|
+
if id2 in self.adjacency_list and id1 in self.adjacency_list[id2]:
|
|
126
|
+
self.adjacency_list[id2].remove(id1)
|
|
127
|
+
self.bonds.pop(key_to_remove, None)
|
|
118
128
|
|
|
119
129
|
def to_rdkit_mol(self, use_2d_stereo=True):
|
|
120
130
|
"""
|
|
@@ -125,7 +135,7 @@ class MolecularData:
|
|
|
125
135
|
return None
|
|
126
136
|
mol = Chem.RWMol()
|
|
127
137
|
|
|
128
|
-
#
|
|
138
|
+
# atoms
|
|
129
139
|
atom_id_to_idx_map = {}
|
|
130
140
|
for atom_id, data in self.atoms.items():
|
|
131
141
|
try:
|
|
@@ -140,7 +150,7 @@ class MolecularData:
|
|
|
140
150
|
idx = mol.AddAtom(atom)
|
|
141
151
|
atom_id_to_idx_map[atom_id] = idx
|
|
142
152
|
|
|
143
|
-
#
|
|
153
|
+
# save bonds & stereo info (label info is kept here)
|
|
144
154
|
bond_stereo_info = {} # bond_idx -> {'type': int, 'atom_ids': (id1,id2), 'bond_data': bond_data}
|
|
145
155
|
for (id1, id2), bond_data in self.bonds.items():
|
|
146
156
|
if id1 not in atom_id_to_idx_map or id2 not in atom_id_to_idx_map:
|
|
@@ -165,7 +175,7 @@ class MolecularData:
|
|
|
165
175
|
"bond_data": bond_data,
|
|
166
176
|
}
|
|
167
177
|
|
|
168
|
-
#
|
|
178
|
+
# sanitize
|
|
169
179
|
final_mol = mol.GetMol()
|
|
170
180
|
try:
|
|
171
181
|
Chem.SanitizeMol(final_mol)
|
|
@@ -173,7 +183,7 @@ class MolecularData:
|
|
|
173
183
|
# Sanitization failure: return None to trigger manual MOL block fallback
|
|
174
184
|
return None
|
|
175
185
|
|
|
176
|
-
#
|
|
186
|
+
# add 2D conformer
|
|
177
187
|
# Convert from scene pixels to angstroms when creating RDKit conformer.
|
|
178
188
|
conf = Chem.Conformer(final_mol.GetNumAtoms())
|
|
179
189
|
conf.Set3D(False)
|
|
@@ -182,14 +192,19 @@ class MolecularData:
|
|
|
182
192
|
idx = atom_id_to_idx_map[atom_id]
|
|
183
193
|
pos = data.get("pos")
|
|
184
194
|
if pos:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
195
|
+
# pos may be a tuple (x, y) or a QPointF (from old state deserialization)
|
|
196
|
+
if hasattr(pos, "x") and hasattr(pos, "y"):
|
|
197
|
+
ax = pos.x() * ANGSTROM_PER_PIXEL
|
|
198
|
+
ay = -pos.y() * ANGSTROM_PER_PIXEL
|
|
199
|
+
elif isinstance(pos, (list, tuple)) and len(pos) >= 2:
|
|
200
|
+
ax = pos[0] * ANGSTROM_PER_PIXEL
|
|
201
|
+
ay = -pos[1] * ANGSTROM_PER_PIXEL
|
|
202
|
+
else:
|
|
203
|
+
continue
|
|
189
204
|
conf.SetAtomPosition(idx, (ax, ay, 0.0))
|
|
190
205
|
final_mol.AddConformer(conf)
|
|
191
206
|
|
|
192
|
-
#
|
|
207
|
+
# Stereochemistry setting prioritizing E/Z labels ---
|
|
193
208
|
# First, record bonds with E/Z labels
|
|
194
209
|
ez_labeled_bonds = set()
|
|
195
210
|
for bond_idx, info in bond_stereo_info.items():
|
|
@@ -223,7 +238,7 @@ class MolecularData:
|
|
|
223
238
|
return nbr.GetIdx()
|
|
224
239
|
return None
|
|
225
240
|
|
|
226
|
-
#
|
|
241
|
+
# Overwrite based on labels (E/Z has highest priority) ---
|
|
227
242
|
for bond_idx, info in bond_stereo_info.items():
|
|
228
243
|
stereo_type = info["type"]
|
|
229
244
|
bond = final_mol.GetBondWithIdx(bond_idx)
|
|
@@ -280,7 +295,7 @@ class MolecularData:
|
|
|
280
295
|
if b2 is not None:
|
|
281
296
|
b2.SetBondDir(Chem.BondDir.NONE)
|
|
282
297
|
|
|
283
|
-
#
|
|
298
|
+
# Finalization (cache update + stereochemistry reassignment)
|
|
284
299
|
final_mol.UpdatePropertyCache(strict=False)
|
|
285
300
|
|
|
286
301
|
# During 3D conversion (use_2d_stereo=False), apply force=True if E/Z labels exist
|
|
@@ -347,18 +362,17 @@ class MolecularData:
|
|
|
347
362
|
positions = []
|
|
348
363
|
for aidx in a_ring:
|
|
349
364
|
item = rdkit_idx_to_item.get(aidx)
|
|
350
|
-
if item:
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
logging.debug(f"Suppressed exception: {e}")
|
|
365
|
+
if item and hasattr(item, "pos"):
|
|
366
|
+
pos = item.pos()
|
|
367
|
+
if pos is not None:
|
|
368
|
+
positions.append(pos)
|
|
355
369
|
|
|
356
370
|
if not positions:
|
|
357
371
|
continue
|
|
358
372
|
|
|
359
373
|
center_x = sum(p.x() for p in positions) / len(positions)
|
|
360
374
|
center_y = sum(p.y() for p in positions) / len(positions)
|
|
361
|
-
ring_center =
|
|
375
|
+
ring_center = (center_x, center_y) # Use tuple (x, y) instead of QPointF
|
|
362
376
|
|
|
363
377
|
# Update all bonds in this ring
|
|
364
378
|
for bidx in b_ring:
|
|
@@ -378,9 +392,9 @@ class MolecularData:
|
|
|
378
392
|
try:
|
|
379
393
|
return Chem.MolToMolBlock(mol, includeStereo=True)
|
|
380
394
|
except (RuntimeError, ValueError, TypeError) as e:
|
|
381
|
-
logging.
|
|
382
|
-
f"
|
|
383
|
-
) #
|
|
395
|
+
logging.warning(
|
|
396
|
+
f"RDKit MolBlock generation failed: {e}"
|
|
397
|
+
) # Fallback to manual MolBlock generation if RDKit fails
|
|
384
398
|
|
|
385
399
|
if not self.atoms:
|
|
386
400
|
return None
|
|
@@ -390,8 +404,17 @@ class MolecularData:
|
|
|
390
404
|
mol_block += f"{num_atoms:3d}{num_bonds:3d} 0 0 0 0 0 0 0 0999 V2000\n"
|
|
391
405
|
for old_id, atom in self.atoms.items():
|
|
392
406
|
# Convert scene pixel coordinates to angstroms when emitting MOL block
|
|
393
|
-
|
|
394
|
-
|
|
407
|
+
pos = atom.get("pos")
|
|
408
|
+
if not pos:
|
|
409
|
+
continue
|
|
410
|
+
|
|
411
|
+
if hasattr(pos, "x") and hasattr(pos, "y"):
|
|
412
|
+
x_px, y_px = pos.x(), -pos.y()
|
|
413
|
+
elif isinstance(pos, (list, tuple)) and len(pos) >= 2:
|
|
414
|
+
x_px, y_px = pos[0], -pos[1]
|
|
415
|
+
else:
|
|
416
|
+
continue
|
|
417
|
+
|
|
395
418
|
x, y = x_px * ANGSTROM_PER_PIXEL, y_px * ANGSTROM_PER_PIXEL
|
|
396
419
|
z, symbol = 0.0, atom["symbol"]
|
|
397
420
|
charge = atom.get("charge", 0)
|
|
@@ -425,3 +448,44 @@ class MolecularData:
|
|
|
425
448
|
|
|
426
449
|
mol_block += "M END\n"
|
|
427
450
|
return mol_block
|
|
451
|
+
|
|
452
|
+
def to_template_dict(self, name, version="1.0", application_version=""):
|
|
453
|
+
"""Convert current structure to a dictionary for template storage."""
|
|
454
|
+
import datetime
|
|
455
|
+
|
|
456
|
+
atoms_data = []
|
|
457
|
+
for atom_id, atom_info in self.atoms.items():
|
|
458
|
+
pos = atom_info["pos"]
|
|
459
|
+
# pos is guaranteed to be a tuple (x, y) due to internal caching in add_atom/set_atom_pos
|
|
460
|
+
atoms_data.append(
|
|
461
|
+
{
|
|
462
|
+
"id": atom_id,
|
|
463
|
+
"symbol": atom_info["symbol"],
|
|
464
|
+
"x": pos[0],
|
|
465
|
+
"y": pos[1],
|
|
466
|
+
"charge": atom_info.get("charge", 0),
|
|
467
|
+
"radical": atom_info.get("radical", 0),
|
|
468
|
+
}
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
bonds_data = []
|
|
472
|
+
for (id1, id2), bond_info in self.bonds.items():
|
|
473
|
+
bonds_data.append(
|
|
474
|
+
{
|
|
475
|
+
"atom1": id1,
|
|
476
|
+
"atom2": id2,
|
|
477
|
+
"order": bond_info["order"],
|
|
478
|
+
"stereo": bond_info.get("stereo", 0),
|
|
479
|
+
}
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
return {
|
|
483
|
+
"format": "PME Template",
|
|
484
|
+
"version": version,
|
|
485
|
+
"application": "MoleditPy",
|
|
486
|
+
"application_version": application_version,
|
|
487
|
+
"name": name,
|
|
488
|
+
"created": str(datetime.datetime.now()),
|
|
489
|
+
"atoms": atoms_data,
|
|
490
|
+
"bonds": bonds_data,
|
|
491
|
+
}
|