MoleditPy 3.0.0a5__tar.gz → 3.0.0a6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/PKG-INFO +4 -4
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/README.md +3 -3
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/pyproject.toml +1 -1
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/MoleditPy.egg-info/PKG-INFO +4 -4
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/core/mol_geometry.py +14 -5
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/core/molecular_data.py +14 -4
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/main.py +0 -1
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/plugins/plugin_interface.py +80 -1
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/plugins/plugin_manager.py +38 -11
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/align_plane_dialog.py +1 -5
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/alignment_dialog.py +1 -4
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/angle_dialog.py +2 -4
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/app_state.py +109 -36
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/atom_item.py +24 -15
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/base_picking_dialog.py +11 -4
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/bond_item.py +71 -57
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/bond_length_dialog.py +21 -26
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/calculation_worker.py +2 -2
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/color_settings_dialog.py +37 -18
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/compute_logic.py +90 -42
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/constrained_optimization_dialog.py +24 -7
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/custom_interactor_style.py +69 -23
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/custom_qt_interactor.py +2 -2
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/dialog_3d_picking_mixin.py +20 -6
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/dialog_logic.py +75 -21
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/dihedral_dialog.py +31 -18
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/edit_3d_logic.py +38 -13
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/edit_actions_logic.py +111 -39
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/export_logic.py +73 -23
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/geometry_base_dialog.py +11 -9
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/io_logic.py +299 -110
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/main_window.py +4 -2
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/main_window_init.py +291 -101
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/molecular_scene_handler.py +44 -14
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/molecule_scene.py +35 -14
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/move_group_dialog.py +83 -27
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/planarize_dialog.py +2 -4
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/settings_dialog.py +16 -8
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/settings_tabs/settings_2d_tab.py +2 -3
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/settings_tabs/settings_3d_tabs.py +15 -5
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/settings_tabs/settings_other_tab.py +3 -3
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/string_importers.py +7 -4
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/translation_dialog.py +5 -6
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/ui_manager.py +71 -36
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/user_template_dialog.py +22 -7
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/view_3d_logic.py +224 -78
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/utils/constants.py +1 -1
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/utils/default_settings.py +0 -9
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/LICENSE +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/setup.cfg +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/MoleditPy.egg-info/SOURCES.txt +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/MoleditPy.egg-info/dependency_links.txt +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/MoleditPy.egg-info/entry_points.txt +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/MoleditPy.egg-info/requires.txt +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/MoleditPy.egg-info/top_level.txt +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/__init__.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/__main__.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/assets/file_icon.ico +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/assets/icon.icns +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/assets/icon.ico +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/assets/icon.png +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/core/__init__.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/plugins/__init__.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/plugins/plugin_manager_window.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/__init__.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/about_dialog.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/analysis_window.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/mirror_dialog.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/periodic_table_dialog.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/settings_tabs/__init__.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/settings_tabs/settings_tab_base.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/sip_isdeleted_safe.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/template_preview_item.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/template_preview_view.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/ui/zoomable_view.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/utils/__init__.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/utils/sip_isdeleted_safe.py +0 -0
- {moleditpy-3.0.0a5 → moleditpy-3.0.0a6}/src/moleditpy/utils/system_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.0a6
|
|
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
|
|
@@ -710,10 +710,10 @@ Dynamic: license-file
|
|
|
710
710
|
[](https://pypi.org/project/MoleditPy/)
|
|
711
711
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
712
712
|
[](https://github.com/HiroYokoyama/python_molecular_editor/actions)
|
|
713
|
-

|
|
714
|
+

|
|
715
715
|

|
|
716
|
-

|
|
717
717
|
[](https://pepy.tech/projects/moleditpy)
|
|
718
718
|
|
|
719
719
|
[🇯🇵 日本語 (Japanese)](#japanese)
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
[](https://pypi.org/project/MoleditPy/)
|
|
7
7
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
8
8
|
[](https://github.com/HiroYokoyama/python_molecular_editor/actions)
|
|
9
|
-

|
|
10
|
+

|
|
11
11
|

|
|
12
|
-

|
|
13
13
|
[](https://pepy.tech/projects/moleditpy)
|
|
14
14
|
|
|
15
15
|
[🇯🇵 日本語 (Japanese)](#japanese)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.0a6
|
|
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
|
|
@@ -710,10 +710,10 @@ Dynamic: license-file
|
|
|
710
710
|
[](https://pypi.org/project/MoleditPy/)
|
|
711
711
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
712
712
|
[](https://github.com/HiroYokoyama/python_molecular_editor/actions)
|
|
713
|
-

|
|
714
|
+

|
|
715
715
|

|
|
716
|
-

|
|
717
717
|
[](https://pepy.tech/projects/moleditpy)
|
|
718
718
|
|
|
719
719
|
[🇯🇵 日本語 (Japanese)](#japanese)
|
|
@@ -12,7 +12,6 @@ DOI: 10.5281/zenodo.17268532
|
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
import math
|
|
15
|
-
import logging
|
|
16
15
|
from collections import deque
|
|
17
16
|
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union
|
|
18
17
|
|
|
@@ -22,7 +21,11 @@ import numpy as np
|
|
|
22
21
|
# Primitive geometry helpers
|
|
23
22
|
# ------------------------------------------------------------------
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
|
|
25
|
+
def calc_distance(
|
|
26
|
+
pos1: Union[np.ndarray, Tuple[float, float, float], List[float]],
|
|
27
|
+
pos2: Union[np.ndarray, Tuple[float, float, float], List[float]],
|
|
28
|
+
) -> float:
|
|
26
29
|
"""Return the Euclidean distance between two 3-D positions.
|
|
27
30
|
|
|
28
31
|
Parameters
|
|
@@ -78,7 +81,9 @@ def calc_angle_deg(
|
|
|
78
81
|
# ------------------------------------------------------------------
|
|
79
82
|
|
|
80
83
|
|
|
81
|
-
def get_connected_group(
|
|
84
|
+
def get_connected_group(
|
|
85
|
+
mol: Any, start_atom: int, exclude: Optional[int] = None
|
|
86
|
+
) -> Set[int]:
|
|
82
87
|
"""Return the set of atom indices reachable from *start_atom*
|
|
83
88
|
without passing through *exclude*.
|
|
84
89
|
|
|
@@ -383,7 +388,9 @@ _VALENCE_LIMITS = {
|
|
|
383
388
|
}
|
|
384
389
|
|
|
385
390
|
|
|
386
|
-
def is_problematic_valence(
|
|
391
|
+
def is_problematic_valence(
|
|
392
|
+
symbol: str, bond_count: Union[int, float], charge: int = 0
|
|
393
|
+
) -> bool:
|
|
387
394
|
"""Return ``True`` if the atom's total bond order exceeds its
|
|
388
395
|
typical maximum valence.
|
|
389
396
|
|
|
@@ -475,7 +482,9 @@ def inject_ez_stereo_to_mol_block(mol_block, rdkit_mol, bonds_data):
|
|
|
475
482
|
return "\n".join(mol_lines)
|
|
476
483
|
|
|
477
484
|
|
|
478
|
-
def identify_valence_problems(
|
|
485
|
+
def identify_valence_problems(
|
|
486
|
+
atoms_data: Dict[int, Any], bonds_data: Dict[Tuple[int, int], Any]
|
|
487
|
+
) -> List[int]:
|
|
479
488
|
"""Identify atoms with problematic valence.
|
|
480
489
|
|
|
481
490
|
Parameters
|
|
@@ -12,7 +12,7 @@ DOI: 10.5281/zenodo.17268532
|
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
import logging
|
|
15
|
-
from typing import Any, Dict, List, Optional,
|
|
15
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
16
16
|
from rdkit import Chem
|
|
17
17
|
|
|
18
18
|
try:
|
|
@@ -43,7 +43,13 @@ class MolecularData:
|
|
|
43
43
|
self._next_atom_id = 0
|
|
44
44
|
self.adjacency_list = {}
|
|
45
45
|
|
|
46
|
-
def add_atom(
|
|
46
|
+
def add_atom(
|
|
47
|
+
self,
|
|
48
|
+
symbol: str,
|
|
49
|
+
pos: Union[Any, Tuple[float, float]],
|
|
50
|
+
charge: int = 0,
|
|
51
|
+
radical: int = 0,
|
|
52
|
+
) -> int:
|
|
47
53
|
atom_id = self._next_atom_id
|
|
48
54
|
# Internalize position as raw floats to decouple from UI types (QPointF)
|
|
49
55
|
if hasattr(pos, "x") and hasattr(pos, "y"):
|
|
@@ -72,7 +78,9 @@ class MolecularData:
|
|
|
72
78
|
else:
|
|
73
79
|
self.atoms[atom_id]["pos"] = PointTuple((float(pos[0]), float(pos[1])))
|
|
74
80
|
|
|
75
|
-
def add_bond(
|
|
81
|
+
def add_bond(
|
|
82
|
+
self, id1: int, id2: int, order: Union[int, float] = 1, stereo: int = 0
|
|
83
|
+
) -> Tuple[Tuple[int, int], str]:
|
|
76
84
|
# For stereo bonds, do not sort because ID order determines direction.
|
|
77
85
|
# For non-stereo bonds, sort to normalize the key.
|
|
78
86
|
if stereo == 0:
|
|
@@ -457,7 +465,9 @@ class MolecularData:
|
|
|
457
465
|
mol_block += "M END\n"
|
|
458
466
|
return mol_block
|
|
459
467
|
|
|
460
|
-
def to_template_dict(
|
|
468
|
+
def to_template_dict(
|
|
469
|
+
self, name: str, version: str = "1.0", application_version: str = ""
|
|
470
|
+
) -> Dict[str, Any]:
|
|
461
471
|
"""Convert current structure to a dictionary for template storage."""
|
|
462
472
|
import datetime
|
|
463
473
|
|
|
@@ -45,6 +45,50 @@ class PluginContext:
|
|
|
45
45
|
self._plugin_name, path, callback, text, icon, shortcut
|
|
46
46
|
)
|
|
47
47
|
|
|
48
|
+
def register_menu_action(
|
|
49
|
+
self,
|
|
50
|
+
path: str,
|
|
51
|
+
text_or_callback: Union[str, Callable],
|
|
52
|
+
callback: Optional[Callable] = None,
|
|
53
|
+
icon: Optional[str] = None,
|
|
54
|
+
shortcut: Optional[str] = None,
|
|
55
|
+
):
|
|
56
|
+
"""Backward-compatible alias for add_menu_action.
|
|
57
|
+
Supports old 3-arg style: register_menu_action(path, text, callback).
|
|
58
|
+
"""
|
|
59
|
+
if callable(text_or_callback):
|
|
60
|
+
# New style: (path, callback, ...)
|
|
61
|
+
self.add_menu_action(path, text_or_callback, None, icon, shortcut)
|
|
62
|
+
else:
|
|
63
|
+
# Old style: (path, text, callback)
|
|
64
|
+
self.add_menu_action(path, callback, text_or_callback, icon, shortcut)
|
|
65
|
+
|
|
66
|
+
def add_plugin_menu(
|
|
67
|
+
self,
|
|
68
|
+
path: str,
|
|
69
|
+
callback: Callable,
|
|
70
|
+
text: Optional[str] = None,
|
|
71
|
+
icon: Optional[str] = None,
|
|
72
|
+
shortcut: Optional[str] = None,
|
|
73
|
+
):
|
|
74
|
+
"""
|
|
75
|
+
Register an action nested inside the Plugin menu.
|
|
76
|
+
|
|
77
|
+
Equivalent to add_menu_action("Plugin/<path>", ...).
|
|
78
|
+
Use this instead of add_menu_action when you want your plugin to appear
|
|
79
|
+
as a nested folder inside the Plugin menu rather than as a top-level menu.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
path: Sub-path within the Plugin menu, e.g. "Utility/My Tool"
|
|
83
|
+
results in Plugin > Utility > My Tool.
|
|
84
|
+
callback: Function to call when triggered.
|
|
85
|
+
text: Label override (defaults to last part of path).
|
|
86
|
+
icon: Path to icon (optional).
|
|
87
|
+
shortcut: Keyboard shortcut (optional).
|
|
88
|
+
"""
|
|
89
|
+
full_path = f"Plugin/{path.lstrip('/')}"
|
|
90
|
+
self.add_menu_action(full_path, callback, text, icon, shortcut)
|
|
91
|
+
|
|
48
92
|
def add_toolbar_action(
|
|
49
93
|
self,
|
|
50
94
|
callback: Callable,
|
|
@@ -84,7 +128,7 @@ class PluginContext:
|
|
|
84
128
|
def push_undo_checkpoint(self) -> None:
|
|
85
129
|
"""
|
|
86
130
|
Create an undo checkpoint for the current state.
|
|
87
|
-
Call this AFTER making modifications to the molecule to ensure the
|
|
131
|
+
Call this AFTER making modifications to the molecule to ensure the
|
|
88
132
|
new state is saved to the undo history.
|
|
89
133
|
"""
|
|
90
134
|
self._manager.push_undo_checkpoint()
|
|
@@ -265,6 +309,41 @@ class PluginContext:
|
|
|
265
309
|
"""
|
|
266
310
|
self._manager.register_document_reset_handler(self._plugin_name, callback)
|
|
267
311
|
|
|
312
|
+
def get_setting(self, key: str, default: Any = None) -> Any:
|
|
313
|
+
"""
|
|
314
|
+
Get a plugin-specific persistent setting.
|
|
315
|
+
|
|
316
|
+
Settings are stored in the app settings dict under 'plugin.<plugin_name>.<key>'
|
|
317
|
+
and are persisted across sessions with the application settings.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
key: Setting key name.
|
|
321
|
+
default: Value to return if the setting is not found.
|
|
322
|
+
"""
|
|
323
|
+
mw = self.get_main_window()
|
|
324
|
+
if mw and hasattr(mw, "init_manager") and hasattr(mw.init_manager, "settings"):
|
|
325
|
+
namespaced = f"plugin.{self._plugin_name}.{key}"
|
|
326
|
+
return mw.init_manager.settings.get(namespaced, default)
|
|
327
|
+
return default
|
|
328
|
+
|
|
329
|
+
def set_setting(self, key: str, value: Any) -> None:
|
|
330
|
+
"""
|
|
331
|
+
Save a plugin-specific persistent setting.
|
|
332
|
+
|
|
333
|
+
Settings are stored in the app settings dict under 'plugin.<plugin_name>.<key>'
|
|
334
|
+
and are saved when the application saves its settings.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
key: Setting key name.
|
|
338
|
+
value: Value to store (must be JSON-serializable).
|
|
339
|
+
"""
|
|
340
|
+
mw = self.get_main_window()
|
|
341
|
+
if mw and hasattr(mw, "init_manager") and hasattr(mw.init_manager, "settings"):
|
|
342
|
+
namespaced = f"plugin.{self._plugin_name}.{key}"
|
|
343
|
+
mw.init_manager.settings[namespaced] = value
|
|
344
|
+
if hasattr(mw.init_manager, "settings_dirty"):
|
|
345
|
+
mw.init_manager.settings_dirty = True
|
|
346
|
+
|
|
268
347
|
|
|
269
348
|
class Plugin3DController:
|
|
270
349
|
"""Helper to manipulate the 3D scene."""
|
|
@@ -18,7 +18,7 @@ import os
|
|
|
18
18
|
import shutil
|
|
19
19
|
import sys
|
|
20
20
|
import zipfile
|
|
21
|
-
from typing import Any, Callable, Dict, List, Optional,
|
|
21
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
22
22
|
|
|
23
23
|
from PyQt6.QtCore import QUrl
|
|
24
24
|
from PyQt6.QtGui import QDesktopServices
|
|
@@ -30,9 +30,12 @@ except ImportError:
|
|
|
30
30
|
# Fallback if running as script
|
|
31
31
|
from moleditpy.plugins.plugin_interface import PluginContext
|
|
32
32
|
|
|
33
|
+
|
|
33
34
|
class PluginManager:
|
|
34
35
|
def __init__(self, main_window: Any = None) -> None:
|
|
35
|
-
self.plugin_dir: str = os.path.join(
|
|
36
|
+
self.plugin_dir: str = os.path.join(
|
|
37
|
+
os.path.expanduser("~"), ".moleditpy", "plugins"
|
|
38
|
+
)
|
|
36
39
|
self.plugins: List[Dict[str, Any]] = [] # List of dicts
|
|
37
40
|
self.main_window: Any = main_window
|
|
38
41
|
|
|
@@ -50,7 +53,9 @@ class PluginManager:
|
|
|
50
53
|
self.load_handlers: Dict[str, Callable] = {}
|
|
51
54
|
self.custom_3d_styles: Dict[str, Dict[str, Any]] = {}
|
|
52
55
|
self.document_reset_handlers: List[Dict[str, Any]] = []
|
|
53
|
-
self.plugin_windows: Dict[
|
|
56
|
+
self.plugin_windows: Dict[
|
|
57
|
+
str, Dict[str, Any]
|
|
58
|
+
] = {} # Map of plugin_name -> {window_id -> window}
|
|
54
59
|
|
|
55
60
|
def get_main_window(self) -> Any:
|
|
56
61
|
return self.main_window
|
|
@@ -235,7 +240,9 @@ class PluginManager:
|
|
|
235
240
|
|
|
236
241
|
return self.plugins
|
|
237
242
|
|
|
238
|
-
def _load_single_plugin(
|
|
243
|
+
def _load_single_plugin(
|
|
244
|
+
self, filepath: str, module_name: str, category: str
|
|
245
|
+
) -> None:
|
|
239
246
|
"""Common loading logic for both single-file and package plugins."""
|
|
240
247
|
try:
|
|
241
248
|
# Ensure unique module name by including category path
|
|
@@ -371,7 +378,15 @@ class PluginManager:
|
|
|
371
378
|
)
|
|
372
379
|
|
|
373
380
|
# --- Registration Callbacks ---
|
|
374
|
-
def register_menu_action(
|
|
381
|
+
def register_menu_action(
|
|
382
|
+
self,
|
|
383
|
+
plugin_name: str,
|
|
384
|
+
path: str,
|
|
385
|
+
callback: Callable,
|
|
386
|
+
text: str,
|
|
387
|
+
icon: str,
|
|
388
|
+
shortcut: str,
|
|
389
|
+
) -> None:
|
|
375
390
|
self.menu_actions.append(
|
|
376
391
|
{
|
|
377
392
|
"plugin": plugin_name,
|
|
@@ -383,7 +398,9 @@ class PluginManager:
|
|
|
383
398
|
}
|
|
384
399
|
)
|
|
385
400
|
|
|
386
|
-
def register_toolbar_action(
|
|
401
|
+
def register_toolbar_action(
|
|
402
|
+
self, plugin_name: str, callback: Callable, text: str, icon: str, tooltip: str
|
|
403
|
+
) -> None:
|
|
387
404
|
self.toolbar_actions.append(
|
|
388
405
|
{
|
|
389
406
|
"plugin": plugin_name,
|
|
@@ -394,7 +411,9 @@ class PluginManager:
|
|
|
394
411
|
}
|
|
395
412
|
)
|
|
396
413
|
|
|
397
|
-
def register_drop_handler(
|
|
414
|
+
def register_drop_handler(
|
|
415
|
+
self, plugin_name: str, callback: Callable, priority: int
|
|
416
|
+
) -> None:
|
|
398
417
|
self.drop_handlers.append(
|
|
399
418
|
{"priority": priority, "plugin": plugin_name, "callback": callback}
|
|
400
419
|
)
|
|
@@ -470,12 +489,20 @@ class PluginManager:
|
|
|
470
489
|
|
|
471
490
|
def refresh_3d_view(self) -> None:
|
|
472
491
|
"""Force a re-render of the 3D scene."""
|
|
473
|
-
if
|
|
492
|
+
if (
|
|
493
|
+
self.main_window
|
|
494
|
+
and hasattr(self.main_window, "plotter")
|
|
495
|
+
and self.main_window.plotter
|
|
496
|
+
):
|
|
474
497
|
self.main_window.plotter.render()
|
|
475
498
|
|
|
476
499
|
def reset_3d_camera(self) -> None:
|
|
477
500
|
"""Reset the 3D camera to fit the current molecule."""
|
|
478
|
-
if
|
|
501
|
+
if (
|
|
502
|
+
self.main_window
|
|
503
|
+
and hasattr(self.main_window, "plotter")
|
|
504
|
+
and self.main_window.plotter
|
|
505
|
+
):
|
|
479
506
|
self.main_window.plotter.reset_camera()
|
|
480
507
|
self.main_window.plotter.render()
|
|
481
508
|
|
|
@@ -492,7 +519,7 @@ class PluginManager:
|
|
|
492
519
|
# In MoleditPy, atoms in the scene are AtomItem objects which have an 'atom_id'.
|
|
493
520
|
# These atom_ids map to entries in state_manager.data.atoms.
|
|
494
521
|
# RDKit molecule atoms have an '_original_atom_id' property.
|
|
495
|
-
|
|
522
|
+
|
|
496
523
|
scene = getattr(self.main_window, "scene", None)
|
|
497
524
|
if scene:
|
|
498
525
|
selected_items = scene.selectedItems()
|
|
@@ -501,7 +528,7 @@ class PluginManager:
|
|
|
501
528
|
# Relying on duck-typing for AtomItem
|
|
502
529
|
if hasattr(item, "atom_id"):
|
|
503
530
|
selected_atom_ids.add(item.atom_id)
|
|
504
|
-
|
|
531
|
+
|
|
505
532
|
# Now map these editor IDs to RDKit indices
|
|
506
533
|
mol = getattr(self.main_window, "current_mol", None)
|
|
507
534
|
if mol and selected_atom_ids:
|
|
@@ -10,18 +10,14 @@ Repo: https://github.com/HiroYokoyama/python_molecular_editor
|
|
|
10
10
|
DOI: 10.5281/zenodo.17268532
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
import logging
|
|
14
13
|
import numpy as np
|
|
15
|
-
from PyQt6.QtCore import Qt
|
|
16
14
|
from PyQt6.QtWidgets import (
|
|
17
15
|
QHBoxLayout,
|
|
18
16
|
QLabel,
|
|
19
17
|
QMessageBox,
|
|
20
18
|
QPushButton,
|
|
21
19
|
QVBoxLayout,
|
|
22
|
-
QWidget,
|
|
23
20
|
)
|
|
24
|
-
from rdkit import Geometry
|
|
25
21
|
|
|
26
22
|
try:
|
|
27
23
|
from .base_picking_dialog import BasePickingDialog
|
|
@@ -122,7 +118,7 @@ class AlignPlaneDialog(BasePickingDialog):
|
|
|
122
118
|
# fallback to main_window data map
|
|
123
119
|
self.selected_atoms = (
|
|
124
120
|
set(self.main_window.state_manager.data.atoms.keys())
|
|
125
|
-
if hasattr(self.main_window.state_manager,
|
|
121
|
+
if hasattr(self.main_window.state_manager, "data")
|
|
126
122
|
else set()
|
|
127
123
|
)
|
|
128
124
|
|
|
@@ -10,7 +10,6 @@ Repo: https://github.com/HiroYokoyama/python_molecular_editor
|
|
|
10
10
|
DOI: 10.5281/zenodo.17268532
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
import logging
|
|
14
13
|
import numpy as np
|
|
15
14
|
from PyQt6.QtWidgets import (
|
|
16
15
|
QDialog,
|
|
@@ -128,9 +127,7 @@ class AlignmentDialog(Dialog3DPickingMixin, QDialog):
|
|
|
128
127
|
elif len(self.selected_atoms) == 1:
|
|
129
128
|
selected_list = list(self.selected_atoms)
|
|
130
129
|
atom = self.mol.GetAtomWithIdx(selected_list[0])
|
|
131
|
-
self.selection_label.setText(
|
|
132
|
-
f"Selected 1 atom: {atom.GetSymbol()}"
|
|
133
|
-
)
|
|
130
|
+
self.selection_label.setText(f"Selected 1 atom: {atom.GetSymbol()}")
|
|
134
131
|
self.apply_button.setEnabled(False)
|
|
135
132
|
elif len(self.selected_atoms) == 2:
|
|
136
133
|
selected_list = sorted(list(self.selected_atoms))
|
|
@@ -10,7 +10,6 @@ Repo: https://github.com/HiroYokoyama/python_molecular_editor
|
|
|
10
10
|
DOI: 10.5281/zenodo.17268532
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
import logging
|
|
14
13
|
from PyQt6.QtWidgets import (
|
|
15
14
|
QHBoxLayout,
|
|
16
15
|
QLabel,
|
|
@@ -23,7 +22,6 @@ from PyQt6.QtWidgets import (
|
|
|
23
22
|
QMessageBox,
|
|
24
23
|
)
|
|
25
24
|
from PyQt6.QtCore import Qt
|
|
26
|
-
from rdkit import Geometry
|
|
27
25
|
|
|
28
26
|
try:
|
|
29
27
|
from .geometry_base_dialog import GeometryBaseDialog
|
|
@@ -91,7 +89,7 @@ class AngleDialog(GeometryBaseDialog):
|
|
|
91
89
|
self.angle_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
|
|
92
90
|
self.angle_slider.setTickInterval(45)
|
|
93
91
|
self.angle_slider.setEnabled(False)
|
|
94
|
-
|
|
92
|
+
|
|
95
93
|
# Connect to base class real-time handlers
|
|
96
94
|
self.angle_slider.sliderPressed.connect(self.on_slider_pressed)
|
|
97
95
|
self.angle_slider.sliderMoved.connect(
|
|
@@ -101,7 +99,7 @@ class AngleDialog(GeometryBaseDialog):
|
|
|
101
99
|
self.angle_slider.valueChanged.connect(
|
|
102
100
|
lambda v: self.on_slider_value_changed_click(v, self.angle_input, 1.0)
|
|
103
101
|
)
|
|
104
|
-
|
|
102
|
+
|
|
105
103
|
layout.addLayout(angle_layout)
|
|
106
104
|
layout.addWidget(self.angle_slider)
|
|
107
105
|
|