MoleditPy-linux 4.7.1__tar.gz → 4.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/PKG-INFO +3 -3
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/README.md +2 -2
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/pyproject.toml +1 -1
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/MoleditPy_linux.egg-info/PKG-INFO +3 -3
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/MoleditPy_linux.egg-info/SOURCES.txt +1 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/about_dialog.py +4 -2
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/dialog_logic.py +2 -20
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/export_logic.py +6 -4
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/molecular_scene_handler.py +57 -2
- moleditpy_linux-4.8.0/src/moleditpy_linux/ui/preview_molecule.py +204 -0
- moleditpy_linux-4.8.0/src/moleditpy_linux/ui/template_preview_item.py +406 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/template_preview_view.py +2 -2
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/ui_manager.py +18 -27
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/user_template_dialog.py +70 -198
- moleditpy_linux-4.7.1/src/moleditpy_linux/ui/template_preview_item.py +0 -251
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/LICENSE +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/setup.cfg +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/MoleditPy_linux.egg-info/dependency_links.txt +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/MoleditPy_linux.egg-info/entry_points.txt +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/MoleditPy_linux.egg-info/requires.txt +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/MoleditPy_linux.egg-info/top_level.txt +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/__init__.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/__main__.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/assets/file_icon.ico +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/assets/icon.icns +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/assets/icon.ico +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/assets/icon.png +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/core/__init__.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/core/mol_geometry.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/core/molecular_data.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/main.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/plugins/__init__.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/plugins/plugin_interface.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/plugins/plugin_manager.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/plugins/plugin_manager_window.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/__init__.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/align_plane_dialog.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/alignment_dialog.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/analysis_window.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/angle_dialog.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/app_state.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/atom_item.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/atom_picking.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/base_picking_dialog.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/bond_item.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/bond_length_dialog.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/calculation_worker.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/chain_mixin.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/color_settings_dialog.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/compute_logic.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/constrained_optimization_dialog.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/custom_interactor_style.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/custom_qt_interactor.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/dialog_3d_picking_mixin.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/dihedral_dialog.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/edit_3d_logic.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/edit_actions_logic.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/geometry_base_dialog.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/io_logic.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/main_window.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/main_window_init.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/mirror_dialog.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/molecule_scene.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/move_group_dialog.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/move_selected_atoms_dialog.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/periodic_table_dialog.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/planarize_dialog.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/plugin_menu_manager.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/settings_dialog.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/settings_tabs/__init__.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/settings_tabs/settings_2d_tab.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/settings_tabs/settings_3d_tabs.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/settings_tabs/settings_other_tab.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/settings_tabs/settings_tab_base.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/string_importers.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/translation_dialog.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/view_3d_logic.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/zoomable_view.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/utils/__init__.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/utils/constants.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/utils/default_settings.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/utils/hit_radius.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/utils/sip_isdeleted_safe.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/utils/suppress_log.py +0 -0
- {moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/utils/system_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy-linux
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.8.0
|
|
4
4
|
Summary: A cross-platform, simple, and intuitive molecular structure editor built in Python. It allows 2D molecular drawing and 3D structure visualization. It supports exporting structure files for input to DFT calculation software.
|
|
5
5
|
Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -786,7 +786,7 @@ This application combines a modern GUI built with **PyQt6**, powerful cheminform
|
|
|
786
786
|
|
|
787
787
|
## Installation and Execution
|
|
788
788
|
|
|
789
|
-
For detailed instructions, please refer to the project [Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki). A [Docker version](https://github.com/HiroYokoyama/python_molecular_editor_docker) is also available. A [Windows installer](https://hiroyokoyama.github.io/python_molecular_editor/windows-installer/windows_installer)
|
|
789
|
+
For detailed instructions, please refer to the project [Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki). A [Docker version](https://github.com/HiroYokoyama/python_molecular_editor_docker) is also available. A [Windows installer](https://hiroyokoyama.github.io/python_molecular_editor/windows-installer/windows_installer) and a [macOS app bundle](https://hiroyokoyama.github.io/python_molecular_editor/macos-installer/macos_installer) are also distributed.
|
|
790
790
|
|
|
791
791
|
#### Requirements
|
|
792
792
|
|
|
@@ -922,7 +922,7 @@ Additionally, please cite the plugins you used.
|
|
|
922
922
|
|
|
923
923
|
## インストールと実行
|
|
924
924
|
|
|
925
|
-
詳細な手順については、プロジェクトの[Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki)を参照してください。[Docker版](https://github.com/HiroYokoyama/python_molecular_editor_docker)も利用可能です。[Windows向けインストーラー](https://hiroyokoyama.github.io/python_molecular_editor/windows-installer/windows_installer-jp)も使用できます。
|
|
925
|
+
詳細な手順については、プロジェクトの[Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki)を参照してください。[Docker版](https://github.com/HiroYokoyama/python_molecular_editor_docker)も利用可能です。[Windows向けインストーラー](https://hiroyokoyama.github.io/python_molecular_editor/windows-installer/windows_installer-jp)や[macOSアプリバンドル](https://hiroyokoyama.github.io/python_molecular_editor/macos-installer/macos_installer-jp)も使用できます。
|
|
926
926
|
|
|
927
927
|
#### 必要ライブラリ
|
|
928
928
|
|
|
@@ -83,7 +83,7 @@ This application combines a modern GUI built with **PyQt6**, powerful cheminform
|
|
|
83
83
|
|
|
84
84
|
## Installation and Execution
|
|
85
85
|
|
|
86
|
-
For detailed instructions, please refer to the project [Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki). A [Docker version](https://github.com/HiroYokoyama/python_molecular_editor_docker) is also available. A [Windows installer](https://hiroyokoyama.github.io/python_molecular_editor/windows-installer/windows_installer)
|
|
86
|
+
For detailed instructions, please refer to the project [Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki). A [Docker version](https://github.com/HiroYokoyama/python_molecular_editor_docker) is also available. A [Windows installer](https://hiroyokoyama.github.io/python_molecular_editor/windows-installer/windows_installer) and a [macOS app bundle](https://hiroyokoyama.github.io/python_molecular_editor/macos-installer/macos_installer) are also distributed.
|
|
87
87
|
|
|
88
88
|
#### Requirements
|
|
89
89
|
|
|
@@ -219,7 +219,7 @@ Additionally, please cite the plugins you used.
|
|
|
219
219
|
|
|
220
220
|
## インストールと実行
|
|
221
221
|
|
|
222
|
-
詳細な手順については、プロジェクトの[Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki)を参照してください。[Docker版](https://github.com/HiroYokoyama/python_molecular_editor_docker)も利用可能です。[Windows向けインストーラー](https://hiroyokoyama.github.io/python_molecular_editor/windows-installer/windows_installer-jp)も使用できます。
|
|
222
|
+
詳細な手順については、プロジェクトの[Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki)を参照してください。[Docker版](https://github.com/HiroYokoyama/python_molecular_editor_docker)も利用可能です。[Windows向けインストーラー](https://hiroyokoyama.github.io/python_molecular_editor/windows-installer/windows_installer-jp)や[macOSアプリバンドル](https://hiroyokoyama.github.io/python_molecular_editor/macos-installer/macos_installer-jp)も使用できます。
|
|
223
223
|
|
|
224
224
|
#### 必要ライブラリ
|
|
225
225
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy-linux
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.8.0
|
|
4
4
|
Summary: A cross-platform, simple, and intuitive molecular structure editor built in Python. It allows 2D molecular drawing and 3D structure visualization. It supports exporting structure files for input to DFT calculation software.
|
|
5
5
|
Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -786,7 +786,7 @@ This application combines a modern GUI built with **PyQt6**, powerful cheminform
|
|
|
786
786
|
|
|
787
787
|
## Installation and Execution
|
|
788
788
|
|
|
789
|
-
For detailed instructions, please refer to the project [Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki). A [Docker version](https://github.com/HiroYokoyama/python_molecular_editor_docker) is also available. A [Windows installer](https://hiroyokoyama.github.io/python_molecular_editor/windows-installer/windows_installer)
|
|
789
|
+
For detailed instructions, please refer to the project [Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki). A [Docker version](https://github.com/HiroYokoyama/python_molecular_editor_docker) is also available. A [Windows installer](https://hiroyokoyama.github.io/python_molecular_editor/windows-installer/windows_installer) and a [macOS app bundle](https://hiroyokoyama.github.io/python_molecular_editor/macos-installer/macos_installer) are also distributed.
|
|
790
790
|
|
|
791
791
|
#### Requirements
|
|
792
792
|
|
|
@@ -922,7 +922,7 @@ Additionally, please cite the plugins you used.
|
|
|
922
922
|
|
|
923
923
|
## インストールと実行
|
|
924
924
|
|
|
925
|
-
詳細な手順については、プロジェクトの[Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki)を参照してください。[Docker版](https://github.com/HiroYokoyama/python_molecular_editor_docker)も利用可能です。[Windows向けインストーラー](https://hiroyokoyama.github.io/python_molecular_editor/windows-installer/windows_installer-jp)も使用できます。
|
|
925
|
+
詳細な手順については、プロジェクトの[Wiki](https://github.com/HiroYokoyama/python_molecular_editor/wiki)を参照してください。[Docker版](https://github.com/HiroYokoyama/python_molecular_editor_docker)も利用可能です。[Windows向けインストーラー](https://hiroyokoyama.github.io/python_molecular_editor/windows-installer/windows_installer-jp)や[macOSアプリバンドル](https://hiroyokoyama.github.io/python_molecular_editor/macos-installer/macos_installer-jp)も使用できます。
|
|
926
926
|
|
|
927
927
|
#### 必要ライブラリ
|
|
928
928
|
|
|
@@ -58,6 +58,7 @@ src/moleditpy_linux/ui/move_selected_atoms_dialog.py
|
|
|
58
58
|
src/moleditpy_linux/ui/periodic_table_dialog.py
|
|
59
59
|
src/moleditpy_linux/ui/planarize_dialog.py
|
|
60
60
|
src/moleditpy_linux/ui/plugin_menu_manager.py
|
|
61
|
+
src/moleditpy_linux/ui/preview_molecule.py
|
|
61
62
|
src/moleditpy_linux/ui/settings_dialog.py
|
|
62
63
|
src/moleditpy_linux/ui/string_importers.py
|
|
63
64
|
src/moleditpy_linux/ui/template_preview_item.py
|
|
@@ -100,15 +100,16 @@ class AboutDialog(QDialog):
|
|
|
100
100
|
layout.addLayout(button_layout)
|
|
101
101
|
|
|
102
102
|
def image_clicked(self, event: QMouseEvent) -> None:
|
|
103
|
-
"""
|
|
103
|
+
"""Clear all, load bipyrimidine from SMILES, and build it in 3D."""
|
|
104
104
|
# Clear the current scene
|
|
105
105
|
self.main_window.edit_actions_manager.clear_all()
|
|
106
106
|
|
|
107
107
|
bipyrimidine_smiles = "C1=CN=C(N=C1)C2=NC=CC=N2"
|
|
108
108
|
self.main_window.string_importer_manager.load_from_smiles(bipyrimidine_smiles)
|
|
109
109
|
|
|
110
|
-
# Close the dialog
|
|
110
|
+
# Close the dialog first: the conversion puts up its own
|
|
111
111
|
self.accept()
|
|
112
|
+
self.main_window.compute_manager.trigger_conversion()
|
|
112
113
|
|
|
113
114
|
def image_mouse_press_event(self, event: QMouseEvent) -> None:
|
|
114
115
|
"""Handle mouse press on the image: trigger easter egg only for right-click."""
|
|
@@ -118,6 +119,7 @@ class AboutDialog(QDialog):
|
|
|
118
119
|
else:
|
|
119
120
|
event.ignore()
|
|
120
121
|
except (AttributeError, RuntimeError, ValueError, TypeError):
|
|
122
|
+
logging.warning("Image click handling failed", exc_info=True)
|
|
121
123
|
try:
|
|
122
124
|
event.ignore()
|
|
123
125
|
except (AttributeError, RuntimeError, ValueError, TypeError) as e:
|
|
@@ -114,29 +114,11 @@ class DialogManager:
|
|
|
114
114
|
_template_dialog.activateWindow()
|
|
115
115
|
return
|
|
116
116
|
|
|
117
|
-
# Create new dialog
|
|
117
|
+
# Create new dialog. Picking a template arms it right away, and closing
|
|
118
|
+
# the dialog cleans the mode up, so there is nothing to do on finish.
|
|
118
119
|
self.host.template_dialog = UserTemplateDialog(self.host, self.host)
|
|
119
120
|
self.host.template_dialog.show()
|
|
120
121
|
|
|
121
|
-
# Activate if a template is selected after dialog is closed
|
|
122
|
-
def on_dialog_finished() -> None:
|
|
123
|
-
if self.host.template_dialog.selected_template:
|
|
124
|
-
template_name = self.host.template_dialog.selected_template.get(
|
|
125
|
-
"name", "user_template"
|
|
126
|
-
)
|
|
127
|
-
mode_name = f"template_user_{template_name}"
|
|
128
|
-
|
|
129
|
-
# Store template data for the scene to use
|
|
130
|
-
self.host.set_scene_user_template_data(
|
|
131
|
-
self.host.template_dialog.selected_template
|
|
132
|
-
)
|
|
133
|
-
self.host.ui_manager.set_mode(mode_name)
|
|
134
|
-
|
|
135
|
-
# Update status
|
|
136
|
-
self.host.update_status_message(f"Template mode: {template_name}")
|
|
137
|
-
|
|
138
|
-
self.host.template_dialog.finished.connect(on_dialog_finished)
|
|
139
|
-
|
|
140
122
|
def save_2d_as_template(self) -> None:
|
|
141
123
|
"""Save current 2D structure as a template"""
|
|
142
124
|
if not self.host.state_manager.data.atoms:
|
|
@@ -136,7 +136,7 @@ class ExportManager:
|
|
|
136
136
|
"""
|
|
137
137
|
try:
|
|
138
138
|
# Create MTL file
|
|
139
|
-
with open(mtl_path, "w") as mtl_file:
|
|
139
|
+
with open(mtl_path, "w", encoding="utf-8") as mtl_file:
|
|
140
140
|
mtl_file.write(f"# Material file for {os.path.basename(obj_path)}\n")
|
|
141
141
|
mtl_file.write("# Generated with individual object colors\n\n")
|
|
142
142
|
|
|
@@ -157,7 +157,7 @@ class ExportManager:
|
|
|
157
157
|
mtl_file.write("\n")
|
|
158
158
|
|
|
159
159
|
# Create OBJ file
|
|
160
|
-
with open(obj_path, "w") as obj_file:
|
|
160
|
+
with open(obj_path, "w", encoding="utf-8") as obj_file:
|
|
161
161
|
obj_file.write("# OBJ file with multiple materials\n")
|
|
162
162
|
obj_file.write("# Generated with individual object colors\n")
|
|
163
163
|
obj_file.write(f"mtllib {os.path.basename(mtl_path)}\n\n")
|
|
@@ -225,8 +225,10 @@ class ExportManager:
|
|
|
225
225
|
vertex_offset += mesh.n_points
|
|
226
226
|
obj_file.write("\n")
|
|
227
227
|
|
|
228
|
-
except (AttributeError, RuntimeError, ValueError) as e:
|
|
229
|
-
|
|
228
|
+
except (AttributeError, OSError, RuntimeError, ValueError) as e:
|
|
229
|
+
# ValueError, not Exception: export_obj_mtl catches this to report
|
|
230
|
+
# the failure in the status bar.
|
|
231
|
+
raise ValueError(f"Failed to create multi-material OBJ: {e}") from e
|
|
230
232
|
|
|
231
233
|
def export_color_stl(self) -> None:
|
|
232
234
|
"""Export as Color STL."""
|
{moleditpy_linux-4.7.1 → moleditpy_linux-4.8.0}/src/moleditpy_linux/ui/molecular_scene_handler.py
RENAMED
|
@@ -448,6 +448,7 @@ class TemplateMixin:
|
|
|
448
448
|
self.template_context["bonds_info"] = bonds_info
|
|
449
449
|
|
|
450
450
|
# Snap individual preview vertices to nearby atoms to reflect template fusing visually
|
|
451
|
+
vertex_atoms: List[Optional[AtomItem]] = [None] * len(points)
|
|
451
452
|
if self.get_setting("template_fusing_enabled_2d", True) and not alt_pressed:
|
|
452
453
|
fuse_dist = self.get_setting("template_fusing_distance_2d", 7.0)
|
|
453
454
|
mapped_atoms = set(self.template_context.get("items", []))
|
|
@@ -468,6 +469,7 @@ class TemplateMixin:
|
|
|
468
469
|
if best_idx != -1 and best_d <= click_map_threshold:
|
|
469
470
|
points[best_idx] = ex_pos
|
|
470
471
|
used_indices.add(best_idx)
|
|
472
|
+
vertex_atoms[best_idx] = ex_item
|
|
471
473
|
except (AttributeError, TypeError, IndexError):
|
|
472
474
|
# Safe defensive fallback catching AttributeError, TypeError, IndexError
|
|
473
475
|
logging.debug("Suppressed non-critical error", exc_info=True)
|
|
@@ -497,8 +499,22 @@ class TemplateMixin:
|
|
|
497
499
|
if nearby and best_d <= fuse_dist:
|
|
498
500
|
points[i] = nearby.pos()
|
|
499
501
|
mapped_atoms.add(nearby)
|
|
502
|
+
vertex_atoms[i] = nearby
|
|
503
|
+
|
|
504
|
+
# add_molecule_fragment rotates a fused aromatic ring to match the
|
|
505
|
+
# bonds already there; the preview shows the same alternation. It
|
|
506
|
+
# rotates the placement copy itself, so template_context keeps the
|
|
507
|
+
# unrotated orders.
|
|
508
|
+
preview_bonds = bonds_info
|
|
509
|
+
if is_aromatic and n == 6:
|
|
510
|
+
rotation = self._calculate_6ring_rotation(n, bonds_info, vertex_atoms)
|
|
511
|
+
orders = [order for (_, _, order) in bonds_info]
|
|
512
|
+
preview_bonds = [
|
|
513
|
+
(i, j, orders[(m + rotation) % n])
|
|
514
|
+
for m, (i, j, _) in enumerate(bonds_info)
|
|
515
|
+
]
|
|
500
516
|
|
|
501
|
-
self.template_preview.set_geometry(points, is_aromatic)
|
|
517
|
+
self.template_preview.set_geometry(points, is_aromatic, preview_bonds)
|
|
502
518
|
|
|
503
519
|
self.template_preview.show()
|
|
504
520
|
if self.views():
|
|
@@ -555,6 +571,44 @@ class TemplateMixin:
|
|
|
555
571
|
points.append(current_p)
|
|
556
572
|
return points
|
|
557
573
|
|
|
574
|
+
def _apply_template_atom_to_existing(
|
|
575
|
+
self, atom_item: Any, atom_data: Dict[str, Any]
|
|
576
|
+
) -> None:
|
|
577
|
+
"""Overwrite an existing atom with a user template atom's element/charge/radical."""
|
|
578
|
+
symbol = atom_data.get("symbol", "C")
|
|
579
|
+
# A hand-written template may carry nulls here
|
|
580
|
+
charge = int(atom_data.get("charge") or 0)
|
|
581
|
+
radical = int(atom_data.get("radical") or 0)
|
|
582
|
+
|
|
583
|
+
if (
|
|
584
|
+
atom_item.symbol == symbol
|
|
585
|
+
and atom_item.charge == charge
|
|
586
|
+
and atom_item.radical == radical
|
|
587
|
+
):
|
|
588
|
+
return
|
|
589
|
+
|
|
590
|
+
try:
|
|
591
|
+
atom_item.prepareGeometryChange()
|
|
592
|
+
atom_item.symbol = symbol
|
|
593
|
+
atom_item.charge = charge
|
|
594
|
+
atom_item.radical = radical
|
|
595
|
+
|
|
596
|
+
record = self.data.atoms.get(atom_item.atom_id)
|
|
597
|
+
if record is not None:
|
|
598
|
+
record["symbol"] = symbol
|
|
599
|
+
record["charge"] = charge
|
|
600
|
+
record["radical"] = radical
|
|
601
|
+
|
|
602
|
+
# A bonded carbon is drawn as a bare vertex; the new element needs a label
|
|
603
|
+
atom_item.update_style()
|
|
604
|
+
for bond in atom_item.bonds:
|
|
605
|
+
bond.update()
|
|
606
|
+
other = bond.atom1 if bond.atom2 is atom_item else bond.atom2
|
|
607
|
+
if other is not None:
|
|
608
|
+
other.update_style()
|
|
609
|
+
except (AttributeError, RuntimeError, KeyError, TypeError) as e:
|
|
610
|
+
logging.warning(f"Error applying template atom to existing atom: {e}")
|
|
611
|
+
|
|
558
612
|
def add_user_template_fragment(self, context: Dict[str, Any]) -> None:
|
|
559
613
|
"""Place user template fragment"""
|
|
560
614
|
points = context.get("points", [])
|
|
@@ -569,9 +623,10 @@ class TemplateMixin:
|
|
|
569
623
|
atom_id_map = {} # template id -> scene atom id
|
|
570
624
|
|
|
571
625
|
for i, (pos, atom_data) in enumerate(zip(points, atoms_data)):
|
|
572
|
-
#
|
|
626
|
+
# Reuse the clicked atom for the template's first atom, overwriting its element
|
|
573
627
|
if i == 0 and attachment_atom:
|
|
574
628
|
atom_id_map[atom_data["id"]] = attachment_atom.atom_id
|
|
629
|
+
self._apply_template_atom_to_existing(attachment_atom, atom_data)
|
|
575
630
|
continue
|
|
576
631
|
|
|
577
632
|
symbol = atom_data.get("symbol", "C")
|
|
@@ -0,0 +1,204 @@
|
|
|
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
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from typing import Any, Dict, List, Sequence, Tuple
|
|
17
|
+
|
|
18
|
+
from PyQt6.QtCore import QPointF, QRectF
|
|
19
|
+
from PyQt6.QtWidgets import QGraphicsItem, QGraphicsScene
|
|
20
|
+
|
|
21
|
+
from .atom_item import AtomItem
|
|
22
|
+
from .bond_item import BondItem
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PreviewScene(QGraphicsScene):
|
|
26
|
+
"""Scene for preview-only atom/bond items.
|
|
27
|
+
|
|
28
|
+
AtomItem and BondItem read their font, colour and spacing through
|
|
29
|
+
scene().get_setting(), so any scene holding them has to answer it. ``host``
|
|
30
|
+
is the editor scene, so previews follow the user's 2D settings.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, host: Any = None) -> None:
|
|
34
|
+
super().__init__()
|
|
35
|
+
self.host: Any = host
|
|
36
|
+
|
|
37
|
+
def get_setting(self, key: str, default: Any = None) -> Any:
|
|
38
|
+
"""Forward a settings lookup to the editor scene."""
|
|
39
|
+
host = self.host
|
|
40
|
+
if host is None or not hasattr(host, "get_setting"):
|
|
41
|
+
return default
|
|
42
|
+
try:
|
|
43
|
+
return host.get_setting(key, default)
|
|
44
|
+
except (AttributeError, RuntimeError, TypeError, ValueError):
|
|
45
|
+
return default
|
|
46
|
+
|
|
47
|
+
def views(self) -> List[Any]:
|
|
48
|
+
"""Report the editor's views so zoom-dependent radii match the real items."""
|
|
49
|
+
host = self.host
|
|
50
|
+
if host is not None and hasattr(host, "views"):
|
|
51
|
+
try:
|
|
52
|
+
return list(host.views())
|
|
53
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
54
|
+
return []
|
|
55
|
+
return list(super().views())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def ensure_preview_settings(scene: Any, host: Any = None) -> None:
|
|
59
|
+
"""Give a plain QGraphicsScene the get_setting() the preview items call."""
|
|
60
|
+
if hasattr(scene, "get_setting"):
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
def get_setting(key: str, default: Any = None) -> Any:
|
|
64
|
+
if host is None or not hasattr(host, "get_setting"):
|
|
65
|
+
return default
|
|
66
|
+
try:
|
|
67
|
+
# The host outlives the preview normally, but a closed editor would
|
|
68
|
+
# otherwise raise out of paint()
|
|
69
|
+
return host.get_setting(key, default)
|
|
70
|
+
except (AttributeError, RuntimeError, TypeError, ValueError):
|
|
71
|
+
return default
|
|
72
|
+
|
|
73
|
+
scene.get_setting = get_setting
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def build_preview_items(
|
|
77
|
+
atoms: Sequence[Dict[str, Any]],
|
|
78
|
+
bonds: Sequence[Sequence[int]],
|
|
79
|
+
) -> Tuple[List[AtomItem], List[BondItem], Dict[int, Tuple[int, ...]]]:
|
|
80
|
+
"""Build the atom/bond items that draw a template the way the editor draws it.
|
|
81
|
+
|
|
82
|
+
``atoms`` entries need ``pos`` plus the usual ``symbol``/``charge``/``radical``;
|
|
83
|
+
``bonds`` are ``(index1, index2[, order[, stereo]])``. The items are returned
|
|
84
|
+
unparented — the caller decides which scene they belong to — together with the
|
|
85
|
+
ring each bond belongs to, so a moved preview can recompute its ring centres.
|
|
86
|
+
"""
|
|
87
|
+
atom_items: List[AtomItem] = []
|
|
88
|
+
for i, atom_data in enumerate(atoms):
|
|
89
|
+
item = AtomItem(
|
|
90
|
+
i,
|
|
91
|
+
atom_data.get("symbol", "C"),
|
|
92
|
+
QPointF(atom_data.get("pos", QPointF())),
|
|
93
|
+
charge=int(atom_data.get("charge", 0) or 0),
|
|
94
|
+
radical=int(atom_data.get("radical", 0) or 0),
|
|
95
|
+
)
|
|
96
|
+
item.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, False)
|
|
97
|
+
item.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, False)
|
|
98
|
+
item.setAcceptHoverEvents(False)
|
|
99
|
+
atom_items.append(item)
|
|
100
|
+
|
|
101
|
+
bond_items: List[BondItem] = []
|
|
102
|
+
seen_pairs: set[Tuple[int, int]] = set()
|
|
103
|
+
for bond in bonds:
|
|
104
|
+
if len(bond) < 2:
|
|
105
|
+
continue
|
|
106
|
+
i, j = bond[0], bond[1]
|
|
107
|
+
if not isinstance(i, int) or not isinstance(j, int):
|
|
108
|
+
continue
|
|
109
|
+
if not (0 <= i < len(atom_items) and 0 <= j < len(atom_items)) or i == j:
|
|
110
|
+
continue
|
|
111
|
+
# A repeated pair would make RDKit reject the whole preview molecule
|
|
112
|
+
if (min(i, j), max(i, j)) in seen_pairs:
|
|
113
|
+
continue
|
|
114
|
+
seen_pairs.add((min(i, j), max(i, j)))
|
|
115
|
+
order = int(bond[2]) if len(bond) > 2 else 1
|
|
116
|
+
stereo = int(bond[3]) if len(bond) > 3 else 0
|
|
117
|
+
atom1, atom2 = atom_items[i], atom_items[j]
|
|
118
|
+
bond_item = BondItem(atom1, atom2, order, stereo)
|
|
119
|
+
bond_item.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable, False)
|
|
120
|
+
bond_item.setAcceptHoverEvents(False)
|
|
121
|
+
# create_bond() does this too; without it carbons never turn invisible
|
|
122
|
+
atom1.bonds.append(bond_item)
|
|
123
|
+
atom2.bonds.append(bond_item)
|
|
124
|
+
bond_items.append(bond_item)
|
|
125
|
+
|
|
126
|
+
ring_atoms = apply_preview_topology(atom_items, bond_items)
|
|
127
|
+
for item in atom_items:
|
|
128
|
+
item.update_style()
|
|
129
|
+
return atom_items, bond_items, ring_atoms
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def preview_content_rect(
|
|
133
|
+
atom_items: Sequence[AtomItem], bond_items: Sequence[BondItem]
|
|
134
|
+
) -> QRectF:
|
|
135
|
+
"""Return the rect the preview actually draws into, without any hit padding."""
|
|
136
|
+
rect = QRectF()
|
|
137
|
+
for atom in atom_items:
|
|
138
|
+
rect = rect.united(atom.visual_rect().translated(atom.pos()))
|
|
139
|
+
for bond in bond_items:
|
|
140
|
+
line = bond.get_line_in_local_coords()
|
|
141
|
+
rect = rect.united(
|
|
142
|
+
QRectF(line.p1(), line.p2()).normalized().translated(bond.pos())
|
|
143
|
+
)
|
|
144
|
+
return rect
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def apply_preview_topology(
|
|
148
|
+
atom_items: Sequence[AtomItem], bond_items: Sequence[BondItem]
|
|
149
|
+
) -> Dict[int, Tuple[int, ...]]:
|
|
150
|
+
"""Fill in implicit hydrogens and ring info for the preview.
|
|
151
|
+
|
|
152
|
+
Deliberately no sanitization: a preview must draw whatever the template says,
|
|
153
|
+
so the valence is never checked and an odd one still gets its H count.
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
from rdkit import Chem # pylint: disable=import-outside-toplevel
|
|
157
|
+
|
|
158
|
+
bond_types = {
|
|
159
|
+
1: Chem.BondType.SINGLE,
|
|
160
|
+
2: Chem.BondType.DOUBLE,
|
|
161
|
+
3: Chem.BondType.TRIPLE,
|
|
162
|
+
4: Chem.BondType.AROMATIC,
|
|
163
|
+
}
|
|
164
|
+
mol = Chem.RWMol()
|
|
165
|
+
for item in atom_items:
|
|
166
|
+
atom = Chem.Atom(item.symbol)
|
|
167
|
+
atom.SetFormalCharge(item.charge)
|
|
168
|
+
atom.SetNumRadicalElectrons(item.radical)
|
|
169
|
+
mol.AddAtom(atom)
|
|
170
|
+
index_of = {id(item): i for i, item in enumerate(atom_items)}
|
|
171
|
+
for bond in bond_items:
|
|
172
|
+
mol.AddBond(
|
|
173
|
+
index_of[id(bond.atom1)],
|
|
174
|
+
index_of[id(bond.atom2)],
|
|
175
|
+
bond_types.get(bond.order, Chem.BondType.SINGLE),
|
|
176
|
+
)
|
|
177
|
+
mol.UpdatePropertyCache(strict=False)
|
|
178
|
+
Chem.FastFindRings(mol)
|
|
179
|
+
except (ValueError, RuntimeError, TypeError, AttributeError, ImportError):
|
|
180
|
+
logging.debug("Preview topology unavailable", exc_info=True)
|
|
181
|
+
return {}
|
|
182
|
+
|
|
183
|
+
for item, atom in zip(atom_items, mol.GetAtoms()):
|
|
184
|
+
item.implicit_h_count = atom.GetTotalNumHs()
|
|
185
|
+
|
|
186
|
+
ring_info = mol.GetRingInfo()
|
|
187
|
+
ring_atoms: Dict[int, Tuple[int, ...]] = {}
|
|
188
|
+
best_ring_size: Dict[int, int] = {}
|
|
189
|
+
for atom_ring, bond_ring in zip(ring_info.AtomRings(), ring_info.BondRings()):
|
|
190
|
+
positions = [atom_items[idx].pos() for idx in atom_ring]
|
|
191
|
+
center = (
|
|
192
|
+
sum(p.x() for p in positions) / len(positions),
|
|
193
|
+
sum(p.y() for p in positions) / len(positions),
|
|
194
|
+
)
|
|
195
|
+
for bond_idx in bond_ring:
|
|
196
|
+
if bond_idx >= len(bond_items):
|
|
197
|
+
continue
|
|
198
|
+
bond = bond_items[bond_idx]
|
|
199
|
+
bond.is_in_ring = True
|
|
200
|
+
if best_ring_size.get(bond_idx, len(atom_items) + 1) > len(atom_ring):
|
|
201
|
+
bond.ring_center = center
|
|
202
|
+
best_ring_size[bond_idx] = len(atom_ring)
|
|
203
|
+
ring_atoms[bond_idx] = tuple(atom_ring)
|
|
204
|
+
return ring_atoms
|