MoleditPy-linux 3.2.0__tar.gz → 3.3.1__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-3.2.0 → moleditpy_linux-3.3.1}/PKG-INFO +22 -6
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/README.md +21 -5
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/pyproject.toml +1 -1
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/MoleditPy_linux.egg-info/PKG-INFO +22 -6
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/MoleditPy_linux.egg-info/SOURCES.txt +1 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/__init__.py +2 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/angle_dialog.py +27 -12
- moleditpy_linux-3.3.1/src/moleditpy_linux/ui/atom_picking.py +163 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/bond_length_dialog.py +13 -7
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/calculation_worker.py +3 -2
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/constrained_optimization_dialog.py +80 -4
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/custom_interactor_style.py +180 -151
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/dialog_3d_picking_mixin.py +60 -50
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/dihedral_dialog.py +34 -15
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/edit_3d_logic.py +23 -20
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/export_logic.py +99 -106
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/main_window_init.py +43 -10
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/molecular_scene_handler.py +0 -2
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/move_group_dialog.py +20 -73
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/ui_manager.py +9 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/view_3d_logic.py +131 -109
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/utils/constants.py +31 -1
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/LICENSE +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/setup.cfg +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/MoleditPy_linux.egg-info/dependency_links.txt +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/MoleditPy_linux.egg-info/entry_points.txt +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/MoleditPy_linux.egg-info/requires.txt +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/MoleditPy_linux.egg-info/top_level.txt +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/__main__.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/assets/file_icon.ico +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/assets/icon.icns +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/assets/icon.ico +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/assets/icon.png +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/core/__init__.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/core/mol_geometry.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/core/molecular_data.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/main.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/plugins/__init__.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/plugins/plugin_interface.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/plugins/plugin_manager.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/plugins/plugin_manager_window.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/__init__.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/about_dialog.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/align_plane_dialog.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/alignment_dialog.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/analysis_window.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/app_state.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/atom_item.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/base_picking_dialog.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/bond_item.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/color_settings_dialog.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/compute_logic.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/custom_qt_interactor.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/dialog_logic.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/edit_actions_logic.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/geometry_base_dialog.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/io_logic.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/main_window.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/mirror_dialog.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/molecule_scene.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/periodic_table_dialog.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/planarize_dialog.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/settings_dialog.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/settings_tabs/__init__.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/settings_tabs/settings_2d_tab.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/settings_tabs/settings_3d_tabs.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/settings_tabs/settings_other_tab.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/settings_tabs/settings_tab_base.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/string_importers.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/template_preview_item.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/template_preview_view.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/translation_dialog.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/user_template_dialog.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/zoomable_view.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/utils/__init__.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/utils/default_settings.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/utils/sip_isdeleted_safe.py +0 -0
- {moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/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: 3.
|
|
3
|
+
Version: 3.3.1
|
|
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
|
|
@@ -703,19 +703,19 @@ Dynamic: license-file
|
|
|
703
703
|
|
|
704
704
|
# MoleditPy — A Python Molecular Editor
|
|
705
705
|
|
|
706
|
-
This is the Linux version of MoleditPy. The Open Babel fallback is disabled due to compatibility reasons.
|
|
707
|
-
|
|
708
706
|
[](https://doi.org/10.5281/zenodo.17268532)
|
|
709
707
|
[](https://www.rdkit.org/)
|
|
710
708
|
[](https://badge.fury.io/py/MoleditPy)
|
|
711
709
|
[](https://pypi.org/project/MoleditPy/)
|
|
712
710
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
713
711
|
[](https://github.com/HiroYokoyama/python_molecular_editor/actions)
|
|
714
|
-

|
|
713
|
+

|
|
716
714
|

|
|
717
|
-

|
|
718
716
|
[](https://pepy.tech/projects/moleditpy)
|
|
717
|
+
[](https://pepy.tech/projects/moleditpy)
|
|
718
|
+
[](https://deepwiki.com/HiroYokoyama/python_molecular_editor)
|
|
719
719
|
|
|
720
720
|
[🇯🇵 日本語 (Japanese)](#japanese)
|
|
721
721
|
|
|
@@ -841,6 +841,14 @@ moleditpy
|
|
|
841
841
|
|
|
842
842
|
This project is licensed under the **GNU General Public License v3.0 (GPL-v3)**. See the `LICENSE` file for details.
|
|
843
843
|
|
|
844
|
+
## Citation
|
|
845
|
+
|
|
846
|
+
If you use this software in your work, please cite it as follows:
|
|
847
|
+
|
|
848
|
+
```
|
|
849
|
+
Yokoyama, H. (2026). MoleditPy — A Python-based molecular editing software. Zenodo. https://doi.org/10.5281/zenodo.17268532
|
|
850
|
+
```
|
|
851
|
+
|
|
844
852
|
-----
|
|
845
853
|
|
|
846
854
|
<div id="japanese"></div>
|
|
@@ -964,3 +972,11 @@ moleditpy
|
|
|
964
972
|
## ライセンス
|
|
965
973
|
|
|
966
974
|
このプロジェクトは **GNU General Public License v3.0 (GPL-v3)** のもとで公開されています。詳細は `LICENSE` ファイルを参照してください。
|
|
975
|
+
|
|
976
|
+
## 引用
|
|
977
|
+
|
|
978
|
+
本ソフトウェアを研究で使用される場合は、以下の通り引用を明記してください。
|
|
979
|
+
|
|
980
|
+
```
|
|
981
|
+
Yokoyama, H. (2026). MoleditPy — A Python-based molecular editing software. Zenodo. https://doi.org/10.5281/zenodo.17268532
|
|
982
|
+
```
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# MoleditPy — A Python Molecular Editor
|
|
2
2
|
|
|
3
|
-
This is the Linux version of MoleditPy. The Open Babel fallback is disabled due to compatibility reasons.
|
|
4
|
-
|
|
5
3
|
[](https://doi.org/10.5281/zenodo.17268532)
|
|
6
4
|
[](https://www.rdkit.org/)
|
|
7
5
|
[](https://badge.fury.io/py/MoleditPy)
|
|
8
6
|
[](https://pypi.org/project/MoleditPy/)
|
|
9
7
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
10
8
|
[](https://github.com/HiroYokoyama/python_molecular_editor/actions)
|
|
11
|
-

|
|
10
|
+

|
|
13
11
|

|
|
14
|
-

|
|
15
13
|
[](https://pepy.tech/projects/moleditpy)
|
|
14
|
+
[](https://pepy.tech/projects/moleditpy)
|
|
15
|
+
[](https://deepwiki.com/HiroYokoyama/python_molecular_editor)
|
|
16
16
|
|
|
17
17
|
[🇯🇵 日本語 (Japanese)](#japanese)
|
|
18
18
|
|
|
@@ -138,6 +138,14 @@ moleditpy
|
|
|
138
138
|
|
|
139
139
|
This project is licensed under the **GNU General Public License v3.0 (GPL-v3)**. See the `LICENSE` file for details.
|
|
140
140
|
|
|
141
|
+
## Citation
|
|
142
|
+
|
|
143
|
+
If you use this software in your work, please cite it as follows:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
Yokoyama, H. (2026). MoleditPy — A Python-based molecular editing software. Zenodo. https://doi.org/10.5281/zenodo.17268532
|
|
147
|
+
```
|
|
148
|
+
|
|
141
149
|
-----
|
|
142
150
|
|
|
143
151
|
<div id="japanese"></div>
|
|
@@ -261,3 +269,11 @@ moleditpy
|
|
|
261
269
|
## ライセンス
|
|
262
270
|
|
|
263
271
|
このプロジェクトは **GNU General Public License v3.0 (GPL-v3)** のもとで公開されています。詳細は `LICENSE` ファイルを参照してください。
|
|
272
|
+
|
|
273
|
+
## 引用
|
|
274
|
+
|
|
275
|
+
本ソフトウェアを研究で使用される場合は、以下の通り引用を明記してください。
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
Yokoyama, H. (2026). MoleditPy — A Python-based molecular editing software. Zenodo. https://doi.org/10.5281/zenodo.17268532
|
|
279
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy-linux
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.1
|
|
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
|
|
@@ -703,19 +703,19 @@ Dynamic: license-file
|
|
|
703
703
|
|
|
704
704
|
# MoleditPy — A Python Molecular Editor
|
|
705
705
|
|
|
706
|
-
This is the Linux version of MoleditPy. The Open Babel fallback is disabled due to compatibility reasons.
|
|
707
|
-
|
|
708
706
|
[](https://doi.org/10.5281/zenodo.17268532)
|
|
709
707
|
[](https://www.rdkit.org/)
|
|
710
708
|
[](https://badge.fury.io/py/MoleditPy)
|
|
711
709
|
[](https://pypi.org/project/MoleditPy/)
|
|
712
710
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
713
711
|
[](https://github.com/HiroYokoyama/python_molecular_editor/actions)
|
|
714
|
-

|
|
713
|
+

|
|
716
714
|

|
|
717
|
-

|
|
718
716
|
[](https://pepy.tech/projects/moleditpy)
|
|
717
|
+
[](https://pepy.tech/projects/moleditpy)
|
|
718
|
+
[](https://deepwiki.com/HiroYokoyama/python_molecular_editor)
|
|
719
719
|
|
|
720
720
|
[🇯🇵 日本語 (Japanese)](#japanese)
|
|
721
721
|
|
|
@@ -841,6 +841,14 @@ moleditpy
|
|
|
841
841
|
|
|
842
842
|
This project is licensed under the **GNU General Public License v3.0 (GPL-v3)**. See the `LICENSE` file for details.
|
|
843
843
|
|
|
844
|
+
## Citation
|
|
845
|
+
|
|
846
|
+
If you use this software in your work, please cite it as follows:
|
|
847
|
+
|
|
848
|
+
```
|
|
849
|
+
Yokoyama, H. (2026). MoleditPy — A Python-based molecular editing software. Zenodo. https://doi.org/10.5281/zenodo.17268532
|
|
850
|
+
```
|
|
851
|
+
|
|
844
852
|
-----
|
|
845
853
|
|
|
846
854
|
<div id="japanese"></div>
|
|
@@ -964,3 +972,11 @@ moleditpy
|
|
|
964
972
|
## ライセンス
|
|
965
973
|
|
|
966
974
|
このプロジェクトは **GNU General Public License v3.0 (GPL-v3)** のもとで公開されています。詳細は `LICENSE` ファイルを参照してください。
|
|
975
|
+
|
|
976
|
+
## 引用
|
|
977
|
+
|
|
978
|
+
本ソフトウェアを研究で使用される場合は、以下の通り引用を明記してください。
|
|
979
|
+
|
|
980
|
+
```
|
|
981
|
+
Yokoyama, H. (2026). MoleditPy — A Python-based molecular editing software. Zenodo. https://doi.org/10.5281/zenodo.17268532
|
|
982
|
+
```
|
|
@@ -29,6 +29,7 @@ src/moleditpy_linux/ui/analysis_window.py
|
|
|
29
29
|
src/moleditpy_linux/ui/angle_dialog.py
|
|
30
30
|
src/moleditpy_linux/ui/app_state.py
|
|
31
31
|
src/moleditpy_linux/ui/atom_item.py
|
|
32
|
+
src/moleditpy_linux/ui/atom_picking.py
|
|
32
33
|
src/moleditpy_linux/ui/base_picking_dialog.py
|
|
33
34
|
src/moleditpy_linux/ui/bond_item.py
|
|
34
35
|
src/moleditpy_linux/ui/bond_length_dialog.py
|
|
@@ -168,20 +168,35 @@ class AngleDialog(GeometryBaseDialog):
|
|
|
168
168
|
|
|
169
169
|
def on_atom_picked(self, atom_idx: int) -> None:
|
|
170
170
|
"""Handle atom picking event in the 3D view."""
|
|
171
|
-
if
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
self.atom2_idx =
|
|
175
|
-
elif self.atom3_idx is None:
|
|
176
|
-
self.atom3_idx = atom_idx
|
|
177
|
-
# Take a fresh snapshot immediately upon completing the triad selection
|
|
178
|
-
self._snapshot_positions = self.mol.GetConformer().GetPositions().copy()
|
|
179
|
-
else:
|
|
180
|
-
# Reset and start over
|
|
181
|
-
self.atom1_idx = atom_idx
|
|
182
|
-
self.atom2_idx = None
|
|
171
|
+
# Deselection logic: if already selected, remove and shift down
|
|
172
|
+
if atom_idx == self.atom1_idx:
|
|
173
|
+
self.atom1_idx = self.atom2_idx
|
|
174
|
+
self.atom2_idx = self.atom3_idx
|
|
183
175
|
self.atom3_idx = None
|
|
184
176
|
self._snapshot_positions = None
|
|
177
|
+
elif atom_idx == self.atom2_idx:
|
|
178
|
+
self.atom2_idx = self.atom3_idx
|
|
179
|
+
self.atom3_idx = None
|
|
180
|
+
self._snapshot_positions = None
|
|
181
|
+
elif atom_idx == self.atom3_idx:
|
|
182
|
+
self.atom3_idx = None
|
|
183
|
+
self._snapshot_positions = None
|
|
184
|
+
else:
|
|
185
|
+
# Selection logic
|
|
186
|
+
if self.atom1_idx is None:
|
|
187
|
+
self.atom1_idx = atom_idx
|
|
188
|
+
elif self.atom2_idx is None:
|
|
189
|
+
self.atom2_idx = atom_idx
|
|
190
|
+
elif self.atom3_idx is None:
|
|
191
|
+
self.atom3_idx = atom_idx
|
|
192
|
+
# Take a fresh snapshot immediately upon completing the triad selection
|
|
193
|
+
self._snapshot_positions = self.mol.GetConformer().GetPositions().copy()
|
|
194
|
+
else:
|
|
195
|
+
# Reset and start over
|
|
196
|
+
self.atom1_idx = atom_idx
|
|
197
|
+
self.atom2_idx = None
|
|
198
|
+
self.atom3_idx = None
|
|
199
|
+
self._snapshot_positions = None
|
|
185
200
|
|
|
186
201
|
self.update_display()
|
|
187
202
|
|
|
@@ -0,0 +1,163 @@
|
|
|
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
|
+
from typing import Any, Optional
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from ..utils.constants import VDW_RADII, pt
|
|
21
|
+
except ImportError:
|
|
22
|
+
from moleditpy_linux.utils.constants import VDW_RADII, pt
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _world_to_display(renderer: Any, pos: Any) -> Optional[tuple[float, float, float]]:
|
|
26
|
+
try:
|
|
27
|
+
renderer.SetWorldPoint(float(pos[0]), float(pos[1]), float(pos[2]), 1.0)
|
|
28
|
+
renderer.WorldToDisplay()
|
|
29
|
+
display = renderer.GetDisplayPoint()
|
|
30
|
+
return (float(display[0]), float(display[1]), float(display[2]))
|
|
31
|
+
except (AttributeError, RuntimeError, TypeError, ValueError, IndexError):
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _atom_world_radius(view_3d_manager: Any, mol: Any, atom_idx: int) -> float:
|
|
36
|
+
try:
|
|
37
|
+
atom = mol.GetAtomWithIdx(int(atom_idx))
|
|
38
|
+
symbol = atom.GetSymbol()
|
|
39
|
+
except (AttributeError, RuntimeError, TypeError, ValueError):
|
|
40
|
+
symbol = "C"
|
|
41
|
+
|
|
42
|
+
settings = {}
|
|
43
|
+
try:
|
|
44
|
+
settings = view_3d_manager.host.init_manager.settings
|
|
45
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
style = str(getattr(view_3d_manager, "current_3d_style", "ball_and_stick"))
|
|
49
|
+
style = style.lower().replace(" ", "_")
|
|
50
|
+
|
|
51
|
+
if style == "cpk":
|
|
52
|
+
scale = settings.get("cpk_atom_scale", 1.0)
|
|
53
|
+
try:
|
|
54
|
+
radius = pt.GetRvdw(pt.GetAtomicNumber(symbol))
|
|
55
|
+
return float(radius if radius > 0.1 else 1.5) * float(scale)
|
|
56
|
+
except (AttributeError, RuntimeError, TypeError, ValueError):
|
|
57
|
+
return 1.5 * float(scale)
|
|
58
|
+
|
|
59
|
+
if style == "stick":
|
|
60
|
+
return float(settings.get("stick_bond_radius", 0.15))
|
|
61
|
+
|
|
62
|
+
if style == "wireframe":
|
|
63
|
+
return 0.01
|
|
64
|
+
|
|
65
|
+
scale = settings.get("ball_stick_atom_scale", 1.0)
|
|
66
|
+
return float(VDW_RADII.get(symbol, 0.4)) * float(scale)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _projected_radius_px(
|
|
70
|
+
renderer: Any, center: Any, world_radius: float
|
|
71
|
+
) -> Optional[float]:
|
|
72
|
+
center_display = _world_to_display(renderer, center)
|
|
73
|
+
if center_display is None:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
offsets = (
|
|
77
|
+
(world_radius, 0.0, 0.0),
|
|
78
|
+
(0.0, world_radius, 0.0),
|
|
79
|
+
(0.0, 0.0, world_radius),
|
|
80
|
+
)
|
|
81
|
+
radius_px = 0.0
|
|
82
|
+
for offset in offsets:
|
|
83
|
+
edge = (
|
|
84
|
+
float(center[0]) + offset[0],
|
|
85
|
+
float(center[1]) + offset[1],
|
|
86
|
+
float(center[2]) + offset[2],
|
|
87
|
+
)
|
|
88
|
+
edge_display = _world_to_display(renderer, edge)
|
|
89
|
+
if edge_display is None:
|
|
90
|
+
continue
|
|
91
|
+
radius_px = max(
|
|
92
|
+
radius_px,
|
|
93
|
+
float(
|
|
94
|
+
np.hypot(
|
|
95
|
+
edge_display[0] - center_display[0],
|
|
96
|
+
edge_display[1] - center_display[1],
|
|
97
|
+
)
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return radius_px
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def pick_atom_index_from_screen(
|
|
105
|
+
view_3d_manager: Any,
|
|
106
|
+
click_pos: tuple[int, int],
|
|
107
|
+
mol: Optional[Any] = None,
|
|
108
|
+
padding_px: float = 8.0,
|
|
109
|
+
min_radius_px: float = 14.0,
|
|
110
|
+
max_radius_px: float = 96.0,
|
|
111
|
+
) -> Optional[int]:
|
|
112
|
+
"""Return the atom nearest a screen click without invoking VTK cell picking."""
|
|
113
|
+
try:
|
|
114
|
+
plotter = view_3d_manager.plotter
|
|
115
|
+
renderer = plotter.renderer
|
|
116
|
+
positions = view_3d_manager.atom_positions_3d
|
|
117
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
if positions is None:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
positions_array = np.asarray(positions, dtype=float)
|
|
125
|
+
except (TypeError, ValueError):
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
if positions_array.ndim != 2 or positions_array.shape[1] < 3:
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
if mol is None:
|
|
132
|
+
mol = getattr(view_3d_manager, "current_mol", None)
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
atom_count = int(mol.GetNumAtoms()) if mol is not None else len(positions_array)
|
|
136
|
+
except (AttributeError, RuntimeError, TypeError, ValueError):
|
|
137
|
+
atom_count = len(positions_array)
|
|
138
|
+
|
|
139
|
+
best_idx: Optional[int] = None
|
|
140
|
+
best_score: Optional[tuple[float, float]] = None
|
|
141
|
+
|
|
142
|
+
for atom_idx in range(min(atom_count, len(positions_array))):
|
|
143
|
+
center = positions_array[atom_idx]
|
|
144
|
+
display = _world_to_display(renderer, center)
|
|
145
|
+
if display is None or not np.all(np.isfinite(display[:2])):
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
world_radius = _atom_world_radius(view_3d_manager, mol, atom_idx)
|
|
149
|
+
projected_radius = _projected_radius_px(renderer, center, world_radius)
|
|
150
|
+
hit_radius = max(
|
|
151
|
+
float(min_radius_px),
|
|
152
|
+
min(float(max_radius_px), float(projected_radius or 0.0) + padding_px),
|
|
153
|
+
)
|
|
154
|
+
distance = float(np.hypot(display[0] - click_pos[0], display[1] - click_pos[1]))
|
|
155
|
+
if distance > hit_radius:
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
score = (distance / hit_radius, distance)
|
|
159
|
+
if best_score is None or score < best_score:
|
|
160
|
+
best_idx = atom_idx
|
|
161
|
+
best_score = score
|
|
162
|
+
|
|
163
|
+
return best_idx
|
{moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/bond_length_dialog.py
RENAMED
|
@@ -160,14 +160,20 @@ class BondLengthDialog(GeometryBaseDialog):
|
|
|
160
160
|
|
|
161
161
|
def on_atom_picked(self, atom_idx: int) -> None:
|
|
162
162
|
"""Handle atom picking event in the 3D view."""
|
|
163
|
-
if self.atom1_idx
|
|
164
|
-
self.atom1_idx =
|
|
165
|
-
elif self.atom2_idx is None:
|
|
166
|
-
self.atom2_idx = atom_idx
|
|
167
|
-
else:
|
|
168
|
-
# Reset and start over
|
|
169
|
-
self.atom1_idx = atom_idx
|
|
163
|
+
if atom_idx == self.atom1_idx:
|
|
164
|
+
self.atom1_idx = self.atom2_idx
|
|
170
165
|
self.atom2_idx = None
|
|
166
|
+
elif atom_idx == self.atom2_idx:
|
|
167
|
+
self.atom2_idx = None
|
|
168
|
+
else:
|
|
169
|
+
if self.atom1_idx is None:
|
|
170
|
+
self.atom1_idx = atom_idx
|
|
171
|
+
elif self.atom2_idx is None:
|
|
172
|
+
self.atom2_idx = atom_idx
|
|
173
|
+
else:
|
|
174
|
+
# Reset and start over
|
|
175
|
+
self.atom1_idx = atom_idx
|
|
176
|
+
self.atom2_idx = None
|
|
171
177
|
|
|
172
178
|
self.update_display()
|
|
173
179
|
|
{moleditpy_linux-3.2.0 → moleditpy_linux-3.3.1}/src/moleditpy_linux/ui/calculation_worker.py
RENAMED
|
@@ -16,6 +16,7 @@ import re
|
|
|
16
16
|
import numpy as np
|
|
17
17
|
import sys
|
|
18
18
|
import subprocess
|
|
19
|
+
import time
|
|
19
20
|
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
|
|
20
21
|
|
|
21
22
|
from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot
|
|
@@ -250,6 +251,8 @@ def _iterative_optimize(
|
|
|
250
251
|
chunk = min(chunk_size, max_iters - iters_done)
|
|
251
252
|
res = ff.Minimize(maxIts=chunk)
|
|
252
253
|
iters_done += chunk
|
|
254
|
+
time.sleep(0.001)
|
|
255
|
+
|
|
253
256
|
if res == 0:
|
|
254
257
|
break
|
|
255
258
|
return True
|
|
@@ -286,8 +289,6 @@ def _iterative_optimize_obabel(
|
|
|
286
289
|
if check_halted_cb():
|
|
287
290
|
raise WorkerHaltError("Halted")
|
|
288
291
|
ff.ConjugateGradients(chunk_size)
|
|
289
|
-
import time
|
|
290
|
-
|
|
291
292
|
time.sleep(0.001)
|
|
292
293
|
|
|
293
294
|
ff.GetCoordinates(ob_mol.OBMol)
|
|
@@ -13,7 +13,7 @@ DOI: 10.5281/zenodo.17268532
|
|
|
13
13
|
import logging
|
|
14
14
|
from typing import Any
|
|
15
15
|
|
|
16
|
-
from PyQt6.QtCore import Qt
|
|
16
|
+
from PyQt6.QtCore import Qt, QThread, pyqtSignal
|
|
17
17
|
from PyQt6.QtWidgets import (
|
|
18
18
|
QAbstractItemView,
|
|
19
19
|
QComboBox,
|
|
@@ -33,6 +33,23 @@ from rdkit.Chem import AllChem, rdMolTransforms
|
|
|
33
33
|
from .dialog_3d_picking_mixin import Dialog3DPickingMixin
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
class ConstrainedOptimizationThread(QThread):
|
|
37
|
+
optimization_finished = pyqtSignal()
|
|
38
|
+
error_occurred = pyqtSignal(str)
|
|
39
|
+
|
|
40
|
+
def __init__(self, ff: Any, max_iters: int = 20000, parent: Any = None) -> None:
|
|
41
|
+
super().__init__(parent)
|
|
42
|
+
self.ff = ff
|
|
43
|
+
self.max_iters = max_iters
|
|
44
|
+
|
|
45
|
+
def run(self) -> None:
|
|
46
|
+
try:
|
|
47
|
+
self.ff.Minimize(maxIts=self.max_iters)
|
|
48
|
+
self.optimization_finished.emit()
|
|
49
|
+
except Exception as e:
|
|
50
|
+
self.error_occurred.emit(str(e))
|
|
51
|
+
|
|
52
|
+
|
|
36
53
|
class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
37
54
|
"""Dialog for constrained optimization."""
|
|
38
55
|
|
|
@@ -427,16 +444,34 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
427
444
|
if positions:
|
|
428
445
|
plotter = self.main_window.view_3d_manager.plotter
|
|
429
446
|
if plotter is not None:
|
|
447
|
+
# Save camera position to prevent reset
|
|
448
|
+
try:
|
|
449
|
+
cam = plotter.camera_position
|
|
450
|
+
except (AttributeError, RuntimeError, TypeError) as e:
|
|
451
|
+
logging.debug(f"Could not save camera position: {e}")
|
|
452
|
+
cam = None
|
|
453
|
+
|
|
430
454
|
label_actor = plotter.add_point_labels(
|
|
431
455
|
positions,
|
|
432
456
|
texts,
|
|
433
|
-
point_size=
|
|
457
|
+
point_size=0,
|
|
434
458
|
font_size=12,
|
|
435
459
|
text_color="cyan",
|
|
436
460
|
always_visible=True,
|
|
461
|
+
show_points=False,
|
|
462
|
+
shape="rect",
|
|
463
|
+
shape_color="gray",
|
|
464
|
+
shape_opacity=0.5,
|
|
437
465
|
)
|
|
438
466
|
self.constraint_labels.append(label_actor)
|
|
439
467
|
|
|
468
|
+
# Restore camera position
|
|
469
|
+
if cam is not None:
|
|
470
|
+
try:
|
|
471
|
+
plotter.camera_position = cam
|
|
472
|
+
except (AttributeError, RuntimeError, TypeError) as e:
|
|
473
|
+
logging.debug(f"Could not restore camera position: {e}")
|
|
474
|
+
|
|
440
475
|
def clear_constraint_labels(self) -> None:
|
|
441
476
|
for label_actor in self.constraint_labels:
|
|
442
477
|
try:
|
|
@@ -541,8 +576,30 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
541
576
|
status_bar = self.main_window.statusBar()
|
|
542
577
|
if status_bar is not None:
|
|
543
578
|
status_bar.showMessage(f"Running constrained {ff_name} optimization...")
|
|
544
|
-
ff.Minimize(maxIts=20000)
|
|
545
579
|
|
|
580
|
+
self.optimize_button.setEnabled(False)
|
|
581
|
+
|
|
582
|
+
self._opt_thread = ConstrainedOptimizationThread(ff, 20000, self)
|
|
583
|
+
self._opt_thread.optimization_finished.connect(
|
|
584
|
+
lambda: self._on_optimization_finished(ff_name, conf)
|
|
585
|
+
)
|
|
586
|
+
self._opt_thread.error_occurred.connect(self._on_optimization_error)
|
|
587
|
+
self._opt_thread.start()
|
|
588
|
+
|
|
589
|
+
except Exception as e:
|
|
590
|
+
QMessageBox.critical(self, "Error", f"Failed to start optimization: {e}")
|
|
591
|
+
self.optimize_button.setEnabled(True)
|
|
592
|
+
|
|
593
|
+
def _on_optimization_error(self, err_msg: str) -> None:
|
|
594
|
+
self.optimize_button.setEnabled(True)
|
|
595
|
+
status_bar = self.main_window.statusBar()
|
|
596
|
+
if status_bar is not None:
|
|
597
|
+
status_bar.showMessage(f"Optimization failed: {err_msg}")
|
|
598
|
+
QMessageBox.critical(self, "Error", f"Optimization error: {err_msg}")
|
|
599
|
+
|
|
600
|
+
def _on_optimization_finished(self, ff_name: str, conf: Any) -> None:
|
|
601
|
+
self.optimize_button.setEnabled(True)
|
|
602
|
+
try:
|
|
546
603
|
# Apply optimized coordinates to the main window's numpy array
|
|
547
604
|
cache = self.main_window.view_3d_manager.atom_positions_3d
|
|
548
605
|
for i in range(self.mol.GetNumAtoms()):
|
|
@@ -558,6 +615,7 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
558
615
|
self.main_window.view_3d_manager.draw_molecule_3d(self.mol)
|
|
559
616
|
self.main_window.view_3d_manager.update_chiral_labels()
|
|
560
617
|
self.main_window.edit_actions_manager.push_undo_state()
|
|
618
|
+
status_bar = self.main_window.statusBar()
|
|
561
619
|
if status_bar is not None:
|
|
562
620
|
status_bar.showMessage("Constrained optimization finished.")
|
|
563
621
|
|
|
@@ -680,13 +738,24 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
680
738
|
if positions:
|
|
681
739
|
plotter = self.main_window.view_3d_manager.plotter
|
|
682
740
|
if plotter is not None:
|
|
741
|
+
# Save camera position to prevent reset
|
|
742
|
+
try:
|
|
743
|
+
cam = plotter.camera_position
|
|
744
|
+
except (AttributeError, RuntimeError, TypeError) as e:
|
|
745
|
+
logging.debug(f"Could not save camera position: {e}")
|
|
746
|
+
cam = None
|
|
747
|
+
|
|
683
748
|
label_actor = plotter.add_point_labels(
|
|
684
749
|
positions,
|
|
685
750
|
texts,
|
|
686
|
-
point_size=
|
|
751
|
+
point_size=0,
|
|
687
752
|
font_size=12,
|
|
688
753
|
text_color="yellow",
|
|
689
754
|
always_visible=True,
|
|
755
|
+
show_points=False,
|
|
756
|
+
shape="rect",
|
|
757
|
+
shape_color="gray",
|
|
758
|
+
shape_opacity=0.5,
|
|
690
759
|
)
|
|
691
760
|
# Consider case where add_point_labels returns a list
|
|
692
761
|
if isinstance(label_actor, list):
|
|
@@ -694,6 +763,13 @@ class ConstrainedOptimizationDialog(Dialog3DPickingMixin, QDialog):
|
|
|
694
763
|
else:
|
|
695
764
|
self.selection_labels.append(label_actor)
|
|
696
765
|
|
|
766
|
+
# Restore camera position
|
|
767
|
+
if cam is not None:
|
|
768
|
+
try:
|
|
769
|
+
plotter.camera_position = cam
|
|
770
|
+
except (AttributeError, RuntimeError, TypeError) as e:
|
|
771
|
+
logging.debug(f"Could not restore camera position: {e}")
|
|
772
|
+
|
|
697
773
|
def on_cell_changed(self, row: int, column: int) -> None:
|
|
698
774
|
"""Update internal data when a table cell is edited."""
|
|
699
775
|
|