MoleditPy 3.3.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-3.3.0 → moleditpy-3.3.1}/PKG-INFO +20 -2
- {moleditpy-3.3.0 → moleditpy-3.3.1}/README.md +19 -1
- {moleditpy-3.3.0 → moleditpy-3.3.1}/pyproject.toml +1 -1
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/MoleditPy.egg-info/PKG-INFO +20 -2
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/__init__.py +2 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/atom_picking.py +163 -163
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/view_3d_logic.py +1 -1
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/utils/constants.py +31 -1
- {moleditpy-3.3.0 → moleditpy-3.3.1}/LICENSE +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/setup.cfg +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/MoleditPy.egg-info/SOURCES.txt +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/MoleditPy.egg-info/dependency_links.txt +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/MoleditPy.egg-info/entry_points.txt +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/MoleditPy.egg-info/requires.txt +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/MoleditPy.egg-info/top_level.txt +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/__main__.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/assets/file_icon.ico +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/assets/icon.icns +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/assets/icon.ico +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/assets/icon.png +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/core/__init__.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/core/mol_geometry.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/core/molecular_data.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/main.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/plugins/__init__.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/plugins/plugin_interface.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/plugins/plugin_manager.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/plugins/plugin_manager_window.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/__init__.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/about_dialog.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/align_plane_dialog.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/alignment_dialog.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/analysis_window.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/angle_dialog.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/app_state.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/atom_item.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/base_picking_dialog.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/bond_item.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/bond_length_dialog.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/calculation_worker.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/color_settings_dialog.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/compute_logic.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/constrained_optimization_dialog.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/custom_interactor_style.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/custom_qt_interactor.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/dialog_3d_picking_mixin.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/dialog_logic.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/dihedral_dialog.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/edit_3d_logic.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/edit_actions_logic.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/export_logic.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/geometry_base_dialog.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/io_logic.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/main_window.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/main_window_init.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/mirror_dialog.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/molecular_scene_handler.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/molecule_scene.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/move_group_dialog.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/periodic_table_dialog.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/planarize_dialog.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/settings_dialog.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/settings_tabs/__init__.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/settings_tabs/settings_2d_tab.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/settings_tabs/settings_3d_tabs.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/settings_tabs/settings_other_tab.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/settings_tabs/settings_tab_base.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/string_importers.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/template_preview_item.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/template_preview_view.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/translation_dialog.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/ui_manager.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/user_template_dialog.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/ui/zoomable_view.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/utils/__init__.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/utils/default_settings.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/src/moleditpy/utils/sip_isdeleted_safe.py +0 -0
- {moleditpy-3.3.0 → moleditpy-3.3.1}/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.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
|
|
@@ -713,8 +713,10 @@ Dynamic: license-file
|
|
|
713
713
|

|
|
714
714
|

|
|
715
715
|

|
|
716
|
-

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

|
|
10
10
|

|
|
11
11
|

|
|
12
|
-

|
|
13
13
|
[](https://pepy.tech/projects/moleditpy)
|
|
14
|
+
[](https://pepy.tech/projects/moleditpy)
|
|
15
|
+
[](https://deepwiki.com/HiroYokoyama/python_molecular_editor)
|
|
14
16
|
|
|
15
17
|
[🇯🇵 日本語 (Japanese)](#japanese)
|
|
16
18
|
|
|
@@ -136,6 +138,14 @@ moleditpy
|
|
|
136
138
|
|
|
137
139
|
This project is licensed under the **GNU General Public License v3.0 (GPL-v3)**. See the `LICENSE` file for details.
|
|
138
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
|
+
|
|
139
149
|
-----
|
|
140
150
|
|
|
141
151
|
<div id="japanese"></div>
|
|
@@ -259,3 +269,11 @@ moleditpy
|
|
|
259
269
|
## ライセンス
|
|
260
270
|
|
|
261
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
|
|
3
|
-
Version: 3.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
|
|
@@ -713,8 +713,10 @@ Dynamic: license-file
|
|
|
713
713
|

|
|
714
714
|

|
|
715
715
|

|
|
716
|
-

|
|
717
717
|
[](https://pepy.tech/projects/moleditpy)
|
|
718
|
+
[](https://pepy.tech/projects/moleditpy)
|
|
719
|
+
[](https://deepwiki.com/HiroYokoyama/python_molecular_editor)
|
|
718
720
|
|
|
719
721
|
[🇯🇵 日本語 (Japanese)](#japanese)
|
|
720
722
|
|
|
@@ -840,6 +842,14 @@ moleditpy
|
|
|
840
842
|
|
|
841
843
|
This project is licensed under the **GNU General Public License v3.0 (GPL-v3)**. See the `LICENSE` file for details.
|
|
842
844
|
|
|
845
|
+
## Citation
|
|
846
|
+
|
|
847
|
+
If you use this software in your work, please cite it as follows:
|
|
848
|
+
|
|
849
|
+
```
|
|
850
|
+
Yokoyama, H. (2026). MoleditPy — A Python-based molecular editing software. Zenodo. https://doi.org/10.5281/zenodo.17268532
|
|
851
|
+
```
|
|
852
|
+
|
|
843
853
|
-----
|
|
844
854
|
|
|
845
855
|
<div id="japanese"></div>
|
|
@@ -963,3 +973,11 @@ moleditpy
|
|
|
963
973
|
## ライセンス
|
|
964
974
|
|
|
965
975
|
このプロジェクトは **GNU General Public License v3.0 (GPL-v3)** のもとで公開されています。詳細は `LICENSE` ファイルを参照してください。
|
|
976
|
+
|
|
977
|
+
## 引用
|
|
978
|
+
|
|
979
|
+
本ソフトウェアを研究で使用される場合は、以下の通り引用を明記してください。
|
|
980
|
+
|
|
981
|
+
```
|
|
982
|
+
Yokoyama, H. (2026). MoleditPy — A Python-based molecular editing software. Zenodo. https://doi.org/10.5281/zenodo.17268532
|
|
983
|
+
```
|
|
@@ -1,163 +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.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
|
|
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.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
|
|
@@ -1632,7 +1632,7 @@ class View3DManager:
|
|
|
1632
1632
|
|
|
1633
1633
|
# Color definitions (dark blue/green/red)
|
|
1634
1634
|
rdkit_color = "#003366" # Dark blue
|
|
1635
|
-
id_color = "#
|
|
1635
|
+
id_color = "#009000" # Green
|
|
1636
1636
|
xyz_color = "#8B0000" # Dark red
|
|
1637
1637
|
other_color = "black"
|
|
1638
1638
|
|
|
@@ -12,11 +12,41 @@ DOI: 10.5281/zenodo.17268532
|
|
|
12
12
|
|
|
13
13
|
# --- Constants ---
|
|
14
14
|
|
|
15
|
+
import os
|
|
15
16
|
from PyQt6.QtGui import QColor, QFont
|
|
16
17
|
from rdkit import Chem
|
|
17
18
|
|
|
19
|
+
|
|
20
|
+
def _get_version():
|
|
21
|
+
try:
|
|
22
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
return version("MoleditPy")
|
|
26
|
+
except PackageNotFoundError:
|
|
27
|
+
pass
|
|
28
|
+
except ImportError:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
# Fallback: Parse pyproject.toml directly
|
|
33
|
+
current_dir = os.path.abspath(os.path.dirname(__file__))
|
|
34
|
+
for _ in range(5):
|
|
35
|
+
pyproject_path = os.path.join(current_dir, "pyproject.toml")
|
|
36
|
+
if os.path.exists(pyproject_path):
|
|
37
|
+
with open(pyproject_path, "r", encoding="utf-8") as f:
|
|
38
|
+
for line in f:
|
|
39
|
+
if line.strip().startswith("version ="):
|
|
40
|
+
return line.split("=")[1].strip().strip('"').strip("'")
|
|
41
|
+
current_dir = os.path.dirname(current_dir)
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
return "Unknown"
|
|
46
|
+
|
|
47
|
+
|
|
18
48
|
# Version
|
|
19
|
-
VERSION =
|
|
49
|
+
VERSION = _get_version()
|
|
20
50
|
|
|
21
51
|
ATOM_RADIUS = 18
|
|
22
52
|
BOND_OFFSET = 3.5
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|