MoleditPy-linux 3.5.2__tar.gz → 3.6.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.5.2 → moleditpy_linux-3.6.1}/PKG-INFO +3 -3
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/README.md +2 -2
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/pyproject.toml +1 -1
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/MoleditPy_linux.egg-info/PKG-INFO +3 -3
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/MoleditPy_linux.egg-info/SOURCES.txt +1 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/__init__.py +3 -4
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/main.py +10 -0
- moleditpy_linux-3.6.1/src/moleditpy_linux/ui/atom_picking.py +310 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/constrained_optimization_dialog.py +0 -3
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/custom_interactor_style.py +64 -16
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/dialog_logic.py +61 -13
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/edit_3d_logic.py +16 -20
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/edit_actions_logic.py +3 -3
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/main_window_init.py +8 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/molecular_scene_handler.py +129 -36
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/molecule_scene.py +38 -26
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/move_group_dialog.py +27 -17
- moleditpy_linux-3.6.1/src/moleditpy_linux/ui/move_selected_atoms_dialog.py +640 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/settings_tabs/settings_2d_tab.py +27 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/settings_tabs/settings_3d_tabs.py +3 -1
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/settings_tabs/settings_other_tab.py +18 -5
- moleditpy_linux-3.6.1/src/moleditpy_linux/ui/settings_tabs/settings_tab_base.py +107 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/ui_manager.py +1 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/utils/default_settings.py +2 -1
- moleditpy_linux-3.5.2/src/moleditpy_linux/ui/atom_picking.py +0 -163
- moleditpy_linux-3.5.2/src/moleditpy_linux/ui/settings_tabs/settings_tab_base.py +0 -70
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/LICENSE +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/setup.cfg +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/MoleditPy_linux.egg-info/dependency_links.txt +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/MoleditPy_linux.egg-info/entry_points.txt +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/MoleditPy_linux.egg-info/requires.txt +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/MoleditPy_linux.egg-info/top_level.txt +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/__main__.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/assets/file_icon.ico +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/assets/icon.icns +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/assets/icon.ico +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/assets/icon.png +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/core/__init__.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/core/mol_geometry.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/core/molecular_data.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/plugins/__init__.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/plugins/plugin_interface.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/plugins/plugin_manager.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/plugins/plugin_manager_window.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/__init__.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/about_dialog.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/align_plane_dialog.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/alignment_dialog.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/analysis_window.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/angle_dialog.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/app_state.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/atom_item.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/base_picking_dialog.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/bond_item.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/bond_length_dialog.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/calculation_worker.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/color_settings_dialog.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/compute_logic.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/custom_qt_interactor.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/dialog_3d_picking_mixin.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/dihedral_dialog.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/export_logic.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/geometry_base_dialog.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/io_logic.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/main_window.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/mirror_dialog.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/periodic_table_dialog.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/planarize_dialog.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/settings_dialog.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/settings_tabs/__init__.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/string_importers.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/template_preview_item.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/template_preview_view.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/translation_dialog.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/user_template_dialog.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/view_3d_logic.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/zoomable_view.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/utils/__init__.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/utils/constants.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/utils/sip_isdeleted_safe.py +0 -0
- {moleditpy_linux-3.5.2 → moleditpy_linux-3.6.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.6.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
|
|
@@ -804,7 +804,7 @@ For detailed instructions, please refer to the project [Wiki](https://github.com
|
|
|
804
804
|
After installation, run this command to create the shortcut in your application menu (e.g., Start Menu or Applications folder).
|
|
805
805
|
|
|
806
806
|
```bash
|
|
807
|
-
|
|
807
|
+
python -m moleditpy_installer
|
|
808
808
|
```
|
|
809
809
|
|
|
810
810
|
#### Running the Application
|
|
@@ -937,7 +937,7 @@ Yokoyama, H. (2026). MoleditPy — A Python-based molecular editing software. Ze
|
|
|
937
937
|
インストール後、このコマンドを実行すると、アプリケーションメニュー(スタートメニューやアプリケーションフォルダなど)にショートカットが作成されます。
|
|
938
938
|
|
|
939
939
|
```bash
|
|
940
|
-
|
|
940
|
+
python -m moleditpy_installer
|
|
941
941
|
```
|
|
942
942
|
|
|
943
943
|
#### アプリケーションの起動
|
|
@@ -101,7 +101,7 @@ For detailed instructions, please refer to the project [Wiki](https://github.com
|
|
|
101
101
|
After installation, run this command to create the shortcut in your application menu (e.g., Start Menu or Applications folder).
|
|
102
102
|
|
|
103
103
|
```bash
|
|
104
|
-
|
|
104
|
+
python -m moleditpy_installer
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
#### Running the Application
|
|
@@ -234,7 +234,7 @@ Yokoyama, H. (2026). MoleditPy — A Python-based molecular editing software. Ze
|
|
|
234
234
|
インストール後、このコマンドを実行すると、アプリケーションメニュー(スタートメニューやアプリケーションフォルダなど)にショートカットが作成されます。
|
|
235
235
|
|
|
236
236
|
```bash
|
|
237
|
-
|
|
237
|
+
python -m moleditpy_installer
|
|
238
238
|
```
|
|
239
239
|
|
|
240
240
|
#### アプリケーションの起動
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy-linux
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.6.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
|
|
@@ -804,7 +804,7 @@ For detailed instructions, please refer to the project [Wiki](https://github.com
|
|
|
804
804
|
After installation, run this command to create the shortcut in your application menu (e.g., Start Menu or Applications folder).
|
|
805
805
|
|
|
806
806
|
```bash
|
|
807
|
-
|
|
807
|
+
python -m moleditpy_installer
|
|
808
808
|
```
|
|
809
809
|
|
|
810
810
|
#### Running the Application
|
|
@@ -937,7 +937,7 @@ Yokoyama, H. (2026). MoleditPy — A Python-based molecular editing software. Ze
|
|
|
937
937
|
インストール後、このコマンドを実行すると、アプリケーションメニュー(スタートメニューやアプリケーションフォルダなど)にショートカットが作成されます。
|
|
938
938
|
|
|
939
939
|
```bash
|
|
940
|
-
|
|
940
|
+
python -m moleditpy_installer
|
|
941
941
|
```
|
|
942
942
|
|
|
943
943
|
#### アプリケーションの起動
|
|
@@ -53,6 +53,7 @@ src/moleditpy_linux/ui/mirror_dialog.py
|
|
|
53
53
|
src/moleditpy_linux/ui/molecular_scene_handler.py
|
|
54
54
|
src/moleditpy_linux/ui/molecule_scene.py
|
|
55
55
|
src/moleditpy_linux/ui/move_group_dialog.py
|
|
56
|
+
src/moleditpy_linux/ui/move_selected_atoms_dialog.py
|
|
56
57
|
src/moleditpy_linux/ui/periodic_table_dialog.py
|
|
57
58
|
src/moleditpy_linux/ui/planarize_dialog.py
|
|
58
59
|
src/moleditpy_linux/ui/settings_dialog.py
|
|
@@ -8,15 +8,14 @@ Author: Hiromichi Yokoyama
|
|
|
8
8
|
License: GPL-3.0 license
|
|
9
9
|
Repo: https://github.com/HiroYokoyama/python_molecular_editor
|
|
10
10
|
DOI: 10.5281/zenodo.17268532
|
|
11
|
-
"""
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
Top-level package for moleditpy_linux.
|
|
13
|
+
"""
|
|
14
14
|
|
|
15
15
|
import importlib.util
|
|
16
|
+
from .utils.constants import VERSION as __version__ # noqa: F401
|
|
16
17
|
|
|
17
18
|
try:
|
|
18
19
|
OBABEL_AVAILABLE = False
|
|
19
20
|
except ImportError:
|
|
20
21
|
OBABEL_AVAILABLE = False
|
|
21
|
-
|
|
22
|
-
from .utils.constants import VERSION as __version__ # noqa: F401
|
|
@@ -148,4 +148,14 @@ def main() -> None:
|
|
|
148
148
|
app = QApplication([sys.argv[0]] + remaining)
|
|
149
149
|
window = MainWindow(initial_file=args.file, safe_mode=args.safe)
|
|
150
150
|
window.show()
|
|
151
|
+
|
|
152
|
+
# Force Windows to refresh taskbar/titlebar icon after event loop starts
|
|
153
|
+
if sys.platform == "win32":
|
|
154
|
+
try:
|
|
155
|
+
from PyQt6.QtCore import QTimer
|
|
156
|
+
|
|
157
|
+
QTimer.singleShot(100, lambda: window.setWindowIcon(window.windowIcon()))
|
|
158
|
+
except Exception:
|
|
159
|
+
pass
|
|
160
|
+
|
|
151
161
|
sys.exit(app.exec())
|
|
@@ -0,0 +1,310 @@
|
|
|
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
|
+
import logging
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from ..utils.constants import VDW_RADII, pt
|
|
22
|
+
except ImportError:
|
|
23
|
+
from moleditpy_linux.utils.constants import VDW_RADII, pt
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _world_to_display(renderer: Any, pos: Any) -> Optional[tuple[float, float, float]]:
|
|
27
|
+
try:
|
|
28
|
+
renderer.SetWorldPoint(float(pos[0]), float(pos[1]), float(pos[2]), 1.0)
|
|
29
|
+
renderer.WorldToDisplay()
|
|
30
|
+
display = renderer.GetDisplayPoint()
|
|
31
|
+
return (float(display[0]), float(display[1]), float(display[2]))
|
|
32
|
+
except (AttributeError, RuntimeError, TypeError, ValueError, IndexError):
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _atom_world_radius(view_3d_manager: Any, mol: Any, atom_idx: int) -> float:
|
|
37
|
+
try:
|
|
38
|
+
atom = mol.GetAtomWithIdx(int(atom_idx))
|
|
39
|
+
symbol = atom.GetSymbol()
|
|
40
|
+
except (AttributeError, RuntimeError, TypeError, ValueError):
|
|
41
|
+
symbol = "C"
|
|
42
|
+
|
|
43
|
+
settings = {}
|
|
44
|
+
try:
|
|
45
|
+
settings = view_3d_manager.host.init_manager.settings
|
|
46
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
style = str(getattr(view_3d_manager, "current_3d_style", "ball_and_stick"))
|
|
50
|
+
style = style.lower().replace(" ", "_")
|
|
51
|
+
|
|
52
|
+
if style == "cpk":
|
|
53
|
+
scale = settings.get("cpk_atom_scale", 1.0)
|
|
54
|
+
try:
|
|
55
|
+
radius = pt.GetRvdw(pt.GetAtomicNumber(symbol))
|
|
56
|
+
return float(radius if radius > 0.1 else 1.5) * float(scale)
|
|
57
|
+
except (AttributeError, RuntimeError, TypeError, ValueError):
|
|
58
|
+
return 1.5 * float(scale)
|
|
59
|
+
|
|
60
|
+
if style == "stick":
|
|
61
|
+
return float(settings.get("stick_bond_radius", 0.15))
|
|
62
|
+
|
|
63
|
+
if style == "wireframe":
|
|
64
|
+
return 0.01
|
|
65
|
+
|
|
66
|
+
scale = settings.get("ball_stick_atom_scale", 1.0)
|
|
67
|
+
return float(VDW_RADII.get(symbol, 0.4)) * float(scale)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _projected_radius_px(
|
|
71
|
+
renderer: Any, center: Any, world_radius: float
|
|
72
|
+
) -> Optional[float]:
|
|
73
|
+
center_display = _world_to_display(renderer, center)
|
|
74
|
+
if center_display is None:
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
offsets = (
|
|
78
|
+
(world_radius, 0.0, 0.0),
|
|
79
|
+
(0.0, world_radius, 0.0),
|
|
80
|
+
(0.0, 0.0, world_radius),
|
|
81
|
+
)
|
|
82
|
+
radius_px = 0.0
|
|
83
|
+
for offset in offsets:
|
|
84
|
+
edge = (
|
|
85
|
+
float(center[0]) + offset[0],
|
|
86
|
+
float(center[1]) + offset[1],
|
|
87
|
+
float(center[2]) + offset[2],
|
|
88
|
+
)
|
|
89
|
+
edge_display = _world_to_display(renderer, edge)
|
|
90
|
+
if edge_display is None:
|
|
91
|
+
continue
|
|
92
|
+
radius_px = max(
|
|
93
|
+
radius_px,
|
|
94
|
+
float(
|
|
95
|
+
np.hypot(
|
|
96
|
+
edge_display[0] - center_display[0],
|
|
97
|
+
edge_display[1] - center_display[1],
|
|
98
|
+
)
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return radius_px
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def pick_atom_index_from_screen_sequential(
|
|
106
|
+
view_3d_manager: Any,
|
|
107
|
+
click_pos: tuple[int, int],
|
|
108
|
+
mol: Optional[Any] = None,
|
|
109
|
+
padding_px: float = 8.0,
|
|
110
|
+
min_radius_px: float = 14.0,
|
|
111
|
+
max_radius_px: float = 96.0,
|
|
112
|
+
) -> Optional[int]:
|
|
113
|
+
"""Return the atom nearest a screen click without invoking VTK cell picking."""
|
|
114
|
+
try:
|
|
115
|
+
plotter = view_3d_manager.plotter
|
|
116
|
+
renderer = plotter.renderer
|
|
117
|
+
positions = view_3d_manager.atom_positions_3d
|
|
118
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
if positions is None:
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
positions_array = np.asarray(positions, dtype=float)
|
|
126
|
+
except (TypeError, ValueError):
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
if positions_array.ndim != 2 or positions_array.shape[1] < 3:
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
if mol is None:
|
|
133
|
+
mol = getattr(view_3d_manager, "current_mol", None)
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
atom_count = int(mol.GetNumAtoms()) if mol is not None else len(positions_array)
|
|
137
|
+
except (AttributeError, RuntimeError, TypeError, ValueError):
|
|
138
|
+
atom_count = len(positions_array)
|
|
139
|
+
|
|
140
|
+
best_idx: Optional[int] = None
|
|
141
|
+
best_score: Optional[tuple[float, float]] = None
|
|
142
|
+
|
|
143
|
+
for atom_idx in range(min(atom_count, len(positions_array))):
|
|
144
|
+
center = positions_array[atom_idx]
|
|
145
|
+
display = _world_to_display(renderer, center)
|
|
146
|
+
if display is None or not np.all(np.isfinite(display[:2])):
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
world_radius = _atom_world_radius(view_3d_manager, mol, atom_idx)
|
|
150
|
+
projected_radius = _projected_radius_px(renderer, center, world_radius)
|
|
151
|
+
hit_radius = max(
|
|
152
|
+
float(min_radius_px),
|
|
153
|
+
min(float(max_radius_px), float(projected_radius or 0.0) + padding_px),
|
|
154
|
+
)
|
|
155
|
+
distance = float(np.hypot(display[0] - click_pos[0], display[1] - click_pos[1]))
|
|
156
|
+
if distance > hit_radius:
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
score = (distance / hit_radius, distance)
|
|
160
|
+
if best_score is None or score < best_score:
|
|
161
|
+
best_idx = atom_idx
|
|
162
|
+
best_score = score
|
|
163
|
+
|
|
164
|
+
return best_idx
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def pick_atom_index_from_screen_vectorized(
|
|
168
|
+
view_3d_manager: Any,
|
|
169
|
+
click_pos: tuple[int, int],
|
|
170
|
+
mol: Optional[Any] = None,
|
|
171
|
+
padding_px: float = 8.0,
|
|
172
|
+
min_radius_px: float = 14.0,
|
|
173
|
+
max_radius_px: float = 96.0,
|
|
174
|
+
) -> Optional[int]:
|
|
175
|
+
"""
|
|
176
|
+
Vectorized atom picking using the camera's projection matrix.
|
|
177
|
+
Eliminates the O(N) Python loop and VTK C++ boundary calls.
|
|
178
|
+
"""
|
|
179
|
+
try:
|
|
180
|
+
plotter = view_3d_manager.plotter
|
|
181
|
+
renderer = plotter.renderer
|
|
182
|
+
positions = view_3d_manager.atom_positions_3d
|
|
183
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
if positions is None or len(positions) == 0:
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
positions_array = np.asarray(positions, dtype=float) # Shape: (N, 3)
|
|
191
|
+
except (TypeError, ValueError):
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
if positions_array.ndim != 2 or positions_array.shape[1] < 3:
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
if mol is None:
|
|
198
|
+
mol = getattr(view_3d_manager, "current_mol", None)
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
atom_count = int(mol.GetNumAtoms()) if mol is not None else len(positions_array)
|
|
202
|
+
except (AttributeError, RuntimeError, TypeError, ValueError):
|
|
203
|
+
atom_count = len(positions_array)
|
|
204
|
+
|
|
205
|
+
active_atoms = min(atom_count, len(positions_array))
|
|
206
|
+
if active_atoms == 0:
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
positions_array = positions_array[:active_atoms]
|
|
210
|
+
|
|
211
|
+
# 1. Retrieve the View-Projection (Composite) Matrix from the active camera
|
|
212
|
+
try:
|
|
213
|
+
camera = renderer.GetActiveCamera()
|
|
214
|
+
aspect_ratio = renderer.GetTiledAspectRatio()
|
|
215
|
+
# Get the 4x4 composite projection matrix (vtkMatrix4x4)
|
|
216
|
+
vtk_matrix = camera.GetCompositeProjectionTransformMatrix(aspect_ratio, -1, 1)
|
|
217
|
+
|
|
218
|
+
# Convert vtkMatrix4x4 to a NumPy 4x4 array
|
|
219
|
+
matrix = np.zeros((4, 4))
|
|
220
|
+
for i in range(4):
|
|
221
|
+
for j in range(4):
|
|
222
|
+
matrix[i, j] = vtk_matrix.GetElement(i, j)
|
|
223
|
+
except Exception:
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
# 2. Convert all N world coordinates to homogeneous coordinates (N, 4)
|
|
227
|
+
homogeneous_coords = np.hstack([positions_array, np.ones((active_atoms, 1))])
|
|
228
|
+
|
|
229
|
+
# 3. Apply Matrix Multiplication: (N, 4) x (4, 4).T -> Clip Space Coordinates
|
|
230
|
+
clip_coords = homogeneous_coords @ matrix.T
|
|
231
|
+
|
|
232
|
+
# 4. Perform Perspective Divide -> Normalized Device Coordinates (NDC)
|
|
233
|
+
w = clip_coords[:, 3:4]
|
|
234
|
+
w_copy = np.copy(w)
|
|
235
|
+
w_copy[np.abs(w_copy) < 1e-5] = 1.0
|
|
236
|
+
ndc_coords = clip_coords[:, :3] / w_copy
|
|
237
|
+
|
|
238
|
+
# 5. Transform NDC to Screen/Display Coordinates
|
|
239
|
+
try:
|
|
240
|
+
size = renderer.GetSize() # (width, height)
|
|
241
|
+
except Exception:
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
# VTK display space coordinates: X: [0, W], Y: [0, H]
|
|
245
|
+
display_coords = np.zeros((active_atoms, 2))
|
|
246
|
+
display_coords[:, 0] = (ndc_coords[:, 0] + 1.0) * 0.5 * size[0]
|
|
247
|
+
display_coords[:, 1] = (ndc_coords[:, 1] + 1.0) * 0.5 * size[1]
|
|
248
|
+
|
|
249
|
+
# 6. Vectorized Distance and Hit Radius Calculation
|
|
250
|
+
dx = display_coords[:, 0] - click_pos[0]
|
|
251
|
+
dy = display_coords[:, 1] - click_pos[1]
|
|
252
|
+
distances = np.hypot(dx, dy)
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
if camera.GetParallelProjection():
|
|
256
|
+
pixel_scale = size[1] / (2.0 * camera.GetParallelScale())
|
|
257
|
+
else:
|
|
258
|
+
view_angle_rad = np.radians(camera.GetViewAngle())
|
|
259
|
+
pixel_scale = size[1] / (
|
|
260
|
+
2.0 * np.abs(w.flatten()) * np.tan(view_angle_rad / 2.0)
|
|
261
|
+
)
|
|
262
|
+
except Exception:
|
|
263
|
+
pixel_scale = 20.0 # Safe fallback scale
|
|
264
|
+
|
|
265
|
+
# Pre-calculate world radii for all atoms
|
|
266
|
+
world_radii = np.array(
|
|
267
|
+
[_atom_world_radius(view_3d_manager, mol, idx) for idx in range(active_atoms)]
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
projected_radii = world_radii * pixel_scale
|
|
271
|
+
hit_radii = np.maximum(
|
|
272
|
+
min_radius_px, np.minimum(max_radius_px, projected_radii + padding_px)
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# 7. Mask out atoms that are further than their hit radius
|
|
276
|
+
valid_mask = distances <= hit_radii
|
|
277
|
+
if not np.any(valid_mask):
|
|
278
|
+
return None
|
|
279
|
+
|
|
280
|
+
# 8. Score calculation and find the best index
|
|
281
|
+
# Tie-breaking logic: (ratio) + (distances * 1e-8)
|
|
282
|
+
scores = (distances / hit_radii) + (distances * 1e-8)
|
|
283
|
+
|
|
284
|
+
scores[~valid_mask] = np.inf
|
|
285
|
+
best_idx = int(np.argmin(scores))
|
|
286
|
+
|
|
287
|
+
return best_idx if scores[best_idx] != np.inf else None
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def pick_atom_index_from_screen(
|
|
291
|
+
view_3d_manager: Any,
|
|
292
|
+
click_pos: tuple[int, int],
|
|
293
|
+
mol: Optional[Any] = None,
|
|
294
|
+
padding_px: float = 8.0,
|
|
295
|
+
min_radius_px: float = 14.0,
|
|
296
|
+
max_radius_px: float = 96.0,
|
|
297
|
+
) -> Optional[int]:
|
|
298
|
+
"""Return the atom nearest a screen click, trying vectorized first, falling back to sequential."""
|
|
299
|
+
try:
|
|
300
|
+
best_idx = pick_atom_index_from_screen_vectorized(
|
|
301
|
+
view_3d_manager, click_pos, mol, padding_px, min_radius_px, max_radius_px
|
|
302
|
+
)
|
|
303
|
+
if best_idx is not None:
|
|
304
|
+
return best_idx
|
|
305
|
+
except Exception as e:
|
|
306
|
+
logging.debug("Vectorized picking failed, falling back to sequential: %s", e)
|
|
307
|
+
|
|
308
|
+
return pick_atom_index_from_screen_sequential(
|
|
309
|
+
view_3d_manager, click_pos, mol, padding_px, min_radius_px, max_radius_px
|
|
310
|
+
)
|
{moleditpy_linux-3.5.2 → moleditpy_linux-3.6.1}/src/moleditpy_linux/ui/custom_interactor_style.py
RENAMED
|
@@ -22,10 +22,8 @@ from vtkmodules.vtkInteractionStyle import vtkInteractorStyleTrackballCamera #
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
try:
|
|
25
|
-
from .move_group_dialog import MoveGroupDialog
|
|
26
25
|
from .atom_picking import pick_atom_index_from_screen
|
|
27
26
|
except ImportError:
|
|
28
|
-
from moleditpy_linux.ui.move_group_dialog import MoveGroupDialog
|
|
29
27
|
from moleditpy_linux.ui.atom_picking import pick_atom_index_from_screen
|
|
30
28
|
|
|
31
29
|
from rdkit import Geometry
|
|
@@ -107,10 +105,15 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
107
105
|
self._mouse_press_pos = None
|
|
108
106
|
|
|
109
107
|
# Check Move Group dialog
|
|
108
|
+
# Check Move Group or Move Selected Atoms dialog
|
|
110
109
|
move_group_dialog = None
|
|
111
110
|
for widget in QApplication.topLevelWidgets():
|
|
112
111
|
try:
|
|
113
|
-
if
|
|
112
|
+
if (
|
|
113
|
+
type(widget).__name__
|
|
114
|
+
in ("MoveGroupDialog", "MoveSelectedAtomsDialog")
|
|
115
|
+
and widget.isVisible()
|
|
116
|
+
):
|
|
114
117
|
move_group_dialog = widget
|
|
115
118
|
break
|
|
116
119
|
except (AttributeError, RuntimeError, TypeError):
|
|
@@ -147,6 +150,20 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
147
150
|
self._suppress_next_left_button_up = True
|
|
148
151
|
return # Disable camera rotation
|
|
149
152
|
else:
|
|
153
|
+
if type(move_group_dialog).__name__ == "MoveSelectedAtomsDialog":
|
|
154
|
+
# For MoveSelectedAtomsDialog, we toggle ONLY the clicked atom, no BFS!
|
|
155
|
+
def _deferred_toggle(
|
|
156
|
+
idx=clicked_atom_idx, dlg=move_group_dialog
|
|
157
|
+
):
|
|
158
|
+
try:
|
|
159
|
+
dlg.on_atom_picked(idx)
|
|
160
|
+
except (AttributeError, RuntimeError):
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
QTimer.singleShot(0, _deferred_toggle)
|
|
164
|
+
self._suppress_next_left_button_up = True
|
|
165
|
+
return
|
|
166
|
+
|
|
150
167
|
# Clicked outside group - Search connected component
|
|
151
168
|
visited = set()
|
|
152
169
|
queue = [clicked_atom_idx]
|
|
@@ -172,8 +189,14 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
172
189
|
|
|
173
190
|
# Multi-selection with Ctrl
|
|
174
191
|
is_ctrl_pressed = bool(
|
|
175
|
-
|
|
176
|
-
|
|
192
|
+
(
|
|
193
|
+
QApplication.keyboardModifiers()
|
|
194
|
+
& Qt.KeyboardModifier.ControlModifier
|
|
195
|
+
)
|
|
196
|
+
or (
|
|
197
|
+
self.GetInteractor()
|
|
198
|
+
and self.GetInteractor().GetControlKey()
|
|
199
|
+
)
|
|
177
200
|
)
|
|
178
201
|
|
|
179
202
|
if is_ctrl_pressed:
|
|
@@ -208,8 +231,10 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
208
231
|
super(CustomInteractorStyle, self).OnLeftButtonDown()
|
|
209
232
|
return
|
|
210
233
|
|
|
234
|
+
interactor = self.GetInteractor()
|
|
211
235
|
is_temp_mode = bool(
|
|
212
|
-
QApplication.keyboardModifiers() & Qt.KeyboardModifier.AltModifier
|
|
236
|
+
(QApplication.keyboardModifiers() & Qt.KeyboardModifier.AltModifier)
|
|
237
|
+
or (interactor and interactor.GetAltKey())
|
|
213
238
|
)
|
|
214
239
|
is_edit_active = mw.edit_3d_manager.is_3d_edit_mode or is_temp_mode
|
|
215
240
|
|
|
@@ -293,11 +318,15 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
293
318
|
"""
|
|
294
319
|
mw = self.main_window
|
|
295
320
|
|
|
296
|
-
# Check if Move Group dialog is open
|
|
321
|
+
# Check if Move Group dialog or Move Selected Atoms dialog is open
|
|
297
322
|
move_group_dialog = None
|
|
298
323
|
try:
|
|
299
324
|
for widget in QApplication.topLevelWidgets():
|
|
300
|
-
if
|
|
325
|
+
if (
|
|
326
|
+
type(widget).__name__
|
|
327
|
+
in ("MoveGroupDialog", "MoveSelectedAtomsDialog")
|
|
328
|
+
and widget.isVisible()
|
|
329
|
+
):
|
|
301
330
|
move_group_dialog = widget
|
|
302
331
|
break
|
|
303
332
|
except (AttributeError, RuntimeError, TypeError) as e:
|
|
@@ -350,11 +379,15 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
350
379
|
"""
|
|
351
380
|
mw = self.main_window
|
|
352
381
|
|
|
353
|
-
# Move Group drag handling
|
|
382
|
+
# Move Group / Selected Atoms drag handling
|
|
354
383
|
move_group_dialog = None
|
|
355
384
|
try:
|
|
356
385
|
for widget in QApplication.topLevelWidgets():
|
|
357
|
-
if
|
|
386
|
+
if (
|
|
387
|
+
type(widget).__name__
|
|
388
|
+
in ("MoveGroupDialog", "MoveSelectedAtomsDialog")
|
|
389
|
+
and widget.isVisible()
|
|
390
|
+
):
|
|
358
391
|
move_group_dialog = widget
|
|
359
392
|
break
|
|
360
393
|
except (AttributeError, RuntimeError, TypeError):
|
|
@@ -372,7 +405,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
372
405
|
dx = current_pos[0] - move_group_dialog._drag_start_pos[0]
|
|
373
406
|
dy = current_pos[1] - move_group_dialog._drag_start_pos[1]
|
|
374
407
|
|
|
375
|
-
if abs(dx) >
|
|
408
|
+
if abs(dx) > 5 or abs(dy) > 5:
|
|
376
409
|
move_group_dialog._mouse_moved = True
|
|
377
410
|
|
|
378
411
|
return # Disable camera rotation
|
|
@@ -390,7 +423,7 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
390
423
|
dx = current_pos[0] - move_group_dialog._rotation_start_pos[0]
|
|
391
424
|
dy = current_pos[1] - move_group_dialog._rotation_start_pos[1]
|
|
392
425
|
|
|
393
|
-
if abs(dx) >
|
|
426
|
+
if abs(dx) > 5 or abs(dy) > 5:
|
|
394
427
|
move_group_dialog._rotation_mouse_moved = True
|
|
395
428
|
|
|
396
429
|
return # Disable camera rotation
|
|
@@ -442,11 +475,15 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
442
475
|
"""
|
|
443
476
|
mw = self.main_window
|
|
444
477
|
|
|
445
|
-
# Finalize Move Group drag
|
|
478
|
+
# Finalize Move Group / Selected Atoms drag
|
|
446
479
|
move_group_dialog = None
|
|
447
480
|
try:
|
|
448
481
|
for widget in QApplication.topLevelWidgets():
|
|
449
|
-
if
|
|
482
|
+
if (
|
|
483
|
+
type(widget).__name__
|
|
484
|
+
in ("MoveGroupDialog", "MoveSelectedAtomsDialog")
|
|
485
|
+
and widget.isVisible()
|
|
486
|
+
):
|
|
450
487
|
move_group_dialog = widget
|
|
451
488
|
break
|
|
452
489
|
except (AttributeError, RuntimeError, TypeError):
|
|
@@ -456,6 +493,13 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
456
493
|
if getattr(
|
|
457
494
|
move_group_dialog, "_is_dragging_group_vtk", False
|
|
458
495
|
) and not getattr(move_group_dialog, "_mouse_moved", False):
|
|
496
|
+
# No drag = click only -> toggle
|
|
497
|
+
clicked_atom = getattr(move_group_dialog, "_drag_atom_idx", None)
|
|
498
|
+
if clicked_atom is not None:
|
|
499
|
+
try:
|
|
500
|
+
move_group_dialog.on_atom_picked(clicked_atom)
|
|
501
|
+
except (AttributeError, RuntimeError, TypeError, ValueError) as e:
|
|
502
|
+
logging.error(f"Error in toggle: {e}")
|
|
459
503
|
# Reset if multi-clicked without drag
|
|
460
504
|
move_group_dialog._is_dragging_group_vtk = False
|
|
461
505
|
move_group_dialog._drag_start_pos = None
|
|
@@ -749,11 +793,15 @@ class CustomInteractorStyle(vtkInteractorStyleTrackballCamera):
|
|
|
749
793
|
"""
|
|
750
794
|
mw = self.main_window
|
|
751
795
|
|
|
752
|
-
# Finalize Move Group rotation
|
|
796
|
+
# Finalize Move Group / Selected Atoms rotation
|
|
753
797
|
move_group_dialog = None
|
|
754
798
|
try:
|
|
755
799
|
for widget in QApplication.topLevelWidgets():
|
|
756
|
-
if
|
|
800
|
+
if (
|
|
801
|
+
type(widget).__name__
|
|
802
|
+
in ("MoveGroupDialog", "MoveSelectedAtomsDialog")
|
|
803
|
+
and widget.isVisible()
|
|
804
|
+
):
|
|
757
805
|
move_group_dialog = widget
|
|
758
806
|
break
|
|
759
807
|
except (AttributeError, RuntimeError, TypeError):
|