MoleditPy 3.0.0a4__tar.gz → 3.0.0a5__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.0a4 → moleditpy-3.0.0a5}/PKG-INFO +1 -1
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/pyproject.toml +1 -1
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/MoleditPy.egg-info/PKG-INFO +1 -1
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/MoleditPy.egg-info/SOURCES.txt +4 -6
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/core/mol_geometry.py +64 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/core/molecular_data.py +1 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/plugins/plugin_interface.py +112 -21
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/plugins/plugin_manager.py +77 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/plugins/plugin_manager_window.py +313 -313
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/about_dialog.py +2 -2
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/align_plane_dialog.py +20 -94
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/alignment_dialog.py +8 -7
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/angle_dialog.py +406 -516
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/app_state.py +172 -283
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/atom_item.py +6 -5
- moleditpy-3.0.0a5/src/moleditpy/ui/base_picking_dialog.py +119 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/bond_item.py +33 -61
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/bond_length_dialog.py +356 -456
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/calculation_worker.py +32 -18
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/color_settings_dialog.py +66 -35
- moleditpy-3.0.0a5/src/moleditpy/ui/compute_logic.py +625 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/constrained_optimization_dialog.py +26 -26
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/custom_interactor_style.py +83 -101
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/custom_qt_interactor.py +4 -4
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/dialog_3d_picking_mixin.py +39 -34
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/dialog_logic.py +121 -135
- moleditpy-3.0.0a5/src/moleditpy/ui/dihedral_dialog.py +393 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/edit_3d_logic.py +47 -48
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/edit_actions_logic.py +312 -153
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/export_logic.py +106 -144
- moleditpy-3.0.0a5/src/moleditpy/ui/geometry_base_dialog.py +111 -0
- moleditpy-3.0.0a5/src/moleditpy/ui/io_logic.py +835 -0
- moleditpy-3.0.0a5/src/moleditpy/ui/main_window.py +97 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/main_window_init.py +507 -521
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/mirror_dialog.py +3 -3
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/molecular_scene_handler.py +43 -33
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/molecule_scene.py +51 -19
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/move_group_dialog.py +123 -259
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/planarize_dialog.py +22 -65
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/settings_dialog.py +33 -66
- {moleditpy-3.0.0a4/src/moleditpy/utils → moleditpy-3.0.0a5/src/moleditpy/ui/settings_tabs}/__init__.py +11 -11
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/settings_tabs/settings_2d_tab.py +6 -34
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/settings_tabs/settings_3d_tabs.py +20 -75
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/settings_tabs/settings_other_tab.py +18 -6
- moleditpy-3.0.0a5/src/moleditpy/ui/settings_tabs/settings_tab_base.py +63 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/string_importers.py +52 -45
- moleditpy-3.0.0a5/src/moleditpy/ui/translation_dialog.py +194 -0
- moleditpy-3.0.0a5/src/moleditpy/ui/ui_manager.py +567 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/user_template_dialog.py +30 -33
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/view_3d_logic.py +271 -196
- {moleditpy-3.0.0a4/src/moleditpy/plugins → moleditpy-3.0.0a5/src/moleditpy/utils}/__init__.py +11 -11
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/utils/constants.py +1 -1
- moleditpy-3.0.0a5/src/moleditpy/utils/default_settings.py +94 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/utils/sip_isdeleted_safe.py +41 -41
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/utils/system_utils.py +71 -71
- moleditpy-3.0.0a4/src/moleditpy/modules/__init__.py +0 -122
- moleditpy-3.0.0a4/src/moleditpy/ui/compute_engine.py +0 -880
- moleditpy-3.0.0a4/src/moleditpy/ui/compute_logic.py +0 -529
- moleditpy-3.0.0a4/src/moleditpy/ui/dialog_manager.py +0 -464
- moleditpy-3.0.0a4/src/moleditpy/ui/dihedral_dialog.py +0 -545
- moleditpy-3.0.0a4/src/moleditpy/ui/main_window.py +0 -218
- moleditpy-3.0.0a4/src/moleditpy/ui/molecular_parsers.py +0 -592
- moleditpy-3.0.0a4/src/moleditpy/ui/project_io.py +0 -387
- moleditpy-3.0.0a4/src/moleditpy/ui/settings_tabs/settings_tab_base.py +0 -33
- moleditpy-3.0.0a4/src/moleditpy/ui/translation_dialog.py +0 -347
- moleditpy-3.0.0a4/src/moleditpy/ui/ui_manager.py +0 -511
- moleditpy-3.0.0a4/src/moleditpy/ui/view_loaders.py +0 -274
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/LICENSE +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/README.md +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/setup.cfg +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/MoleditPy.egg-info/dependency_links.txt +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/MoleditPy.egg-info/entry_points.txt +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/MoleditPy.egg-info/requires.txt +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/MoleditPy.egg-info/top_level.txt +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/__init__.py +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/__main__.py +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/assets/file_icon.ico +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/assets/icon.icns +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/assets/icon.ico +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/assets/icon.png +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/core/__init__.py +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/main.py +0 -0
- {moleditpy-3.0.0a4/src/moleditpy/ui/settings_tabs → moleditpy-3.0.0a5/src/moleditpy/plugins}/__init__.py +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/__init__.py +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/analysis_window.py +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/periodic_table_dialog.py +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/sip_isdeleted_safe.py +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/template_preview_item.py +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/template_preview_view.py +0 -0
- {moleditpy-3.0.0a4 → moleditpy-3.0.0a5}/src/moleditpy/ui/zoomable_view.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.0a5
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MoleditPy
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.0a5
|
|
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
|
|
@@ -23,7 +23,6 @@ src/moleditpy/assets/icon.png
|
|
|
23
23
|
src/moleditpy/core/__init__.py
|
|
24
24
|
src/moleditpy/core/mol_geometry.py
|
|
25
25
|
src/moleditpy/core/molecular_data.py
|
|
26
|
-
src/moleditpy/modules/__init__.py
|
|
27
26
|
src/moleditpy/plugins/__init__.py
|
|
28
27
|
src/moleditpy/plugins/plugin_interface.py
|
|
29
28
|
src/moleditpy/plugins/plugin_manager.py
|
|
@@ -36,32 +35,31 @@ src/moleditpy/ui/analysis_window.py
|
|
|
36
35
|
src/moleditpy/ui/angle_dialog.py
|
|
37
36
|
src/moleditpy/ui/app_state.py
|
|
38
37
|
src/moleditpy/ui/atom_item.py
|
|
38
|
+
src/moleditpy/ui/base_picking_dialog.py
|
|
39
39
|
src/moleditpy/ui/bond_item.py
|
|
40
40
|
src/moleditpy/ui/bond_length_dialog.py
|
|
41
41
|
src/moleditpy/ui/calculation_worker.py
|
|
42
42
|
src/moleditpy/ui/color_settings_dialog.py
|
|
43
|
-
src/moleditpy/ui/compute_engine.py
|
|
44
43
|
src/moleditpy/ui/compute_logic.py
|
|
45
44
|
src/moleditpy/ui/constrained_optimization_dialog.py
|
|
46
45
|
src/moleditpy/ui/custom_interactor_style.py
|
|
47
46
|
src/moleditpy/ui/custom_qt_interactor.py
|
|
48
47
|
src/moleditpy/ui/dialog_3d_picking_mixin.py
|
|
49
48
|
src/moleditpy/ui/dialog_logic.py
|
|
50
|
-
src/moleditpy/ui/dialog_manager.py
|
|
51
49
|
src/moleditpy/ui/dihedral_dialog.py
|
|
52
50
|
src/moleditpy/ui/edit_3d_logic.py
|
|
53
51
|
src/moleditpy/ui/edit_actions_logic.py
|
|
54
52
|
src/moleditpy/ui/export_logic.py
|
|
53
|
+
src/moleditpy/ui/geometry_base_dialog.py
|
|
54
|
+
src/moleditpy/ui/io_logic.py
|
|
55
55
|
src/moleditpy/ui/main_window.py
|
|
56
56
|
src/moleditpy/ui/main_window_init.py
|
|
57
57
|
src/moleditpy/ui/mirror_dialog.py
|
|
58
|
-
src/moleditpy/ui/molecular_parsers.py
|
|
59
58
|
src/moleditpy/ui/molecular_scene_handler.py
|
|
60
59
|
src/moleditpy/ui/molecule_scene.py
|
|
61
60
|
src/moleditpy/ui/move_group_dialog.py
|
|
62
61
|
src/moleditpy/ui/periodic_table_dialog.py
|
|
63
62
|
src/moleditpy/ui/planarize_dialog.py
|
|
64
|
-
src/moleditpy/ui/project_io.py
|
|
65
63
|
src/moleditpy/ui/settings_dialog.py
|
|
66
64
|
src/moleditpy/ui/sip_isdeleted_safe.py
|
|
67
65
|
src/moleditpy/ui/string_importers.py
|
|
@@ -71,7 +69,6 @@ src/moleditpy/ui/translation_dialog.py
|
|
|
71
69
|
src/moleditpy/ui/ui_manager.py
|
|
72
70
|
src/moleditpy/ui/user_template_dialog.py
|
|
73
71
|
src/moleditpy/ui/view_3d_logic.py
|
|
74
|
-
src/moleditpy/ui/view_loaders.py
|
|
75
72
|
src/moleditpy/ui/zoomable_view.py
|
|
76
73
|
src/moleditpy/ui/settings_tabs/__init__.py
|
|
77
74
|
src/moleditpy/ui/settings_tabs/settings_2d_tab.py
|
|
@@ -80,5 +77,6 @@ src/moleditpy/ui/settings_tabs/settings_other_tab.py
|
|
|
80
77
|
src/moleditpy/ui/settings_tabs/settings_tab_base.py
|
|
81
78
|
src/moleditpy/utils/__init__.py
|
|
82
79
|
src/moleditpy/utils/constants.py
|
|
80
|
+
src/moleditpy/utils/default_settings.py
|
|
83
81
|
src/moleditpy/utils/sip_isdeleted_safe.py
|
|
84
82
|
src/moleditpy/utils/system_utils.py
|
|
@@ -300,6 +300,70 @@ def calculate_dihedral(positions: Any, i1: int, i2: int, i3: int, i4: int) -> fl
|
|
|
300
300
|
return float(np.degrees(angle_rad))
|
|
301
301
|
|
|
302
302
|
|
|
303
|
+
def adjust_dihedral(
|
|
304
|
+
positions: np.ndarray,
|
|
305
|
+
i1: int,
|
|
306
|
+
i2: int,
|
|
307
|
+
i3: int,
|
|
308
|
+
i4: int,
|
|
309
|
+
target_dihedral_deg: float,
|
|
310
|
+
atom_indices_to_move: Iterable[int],
|
|
311
|
+
) -> float:
|
|
312
|
+
"""Adjust the dihedral angle defined by i1-i2-i3-i4 to target_dihedral_deg.
|
|
313
|
+
The rotation is performed around the i2-i3 bond axis.
|
|
314
|
+
|
|
315
|
+
Parameters
|
|
316
|
+
----------
|
|
317
|
+
positions : ndarray, shape (N, 3)
|
|
318
|
+
Atom coordinates. **Modified in-place.**
|
|
319
|
+
i1, i2, i3, i4 : int
|
|
320
|
+
Atom indices defining the dihedral.
|
|
321
|
+
target_dihedral_deg : float
|
|
322
|
+
Target dihedral angle in degrees.
|
|
323
|
+
atom_indices_to_move : iterable of int
|
|
324
|
+
Indices of atoms to be rotated.
|
|
325
|
+
|
|
326
|
+
Returns
|
|
327
|
+
-------
|
|
328
|
+
float
|
|
329
|
+
Applied rotation in radians.
|
|
330
|
+
"""
|
|
331
|
+
# Current dihedral
|
|
332
|
+
current_dihedral = calculate_dihedral(positions, i1, i2, i3, i4)
|
|
333
|
+
|
|
334
|
+
# Rotation angle needed
|
|
335
|
+
delta_deg = target_dihedral_deg - current_dihedral
|
|
336
|
+
|
|
337
|
+
# Shortest rotation path
|
|
338
|
+
if delta_deg > 180:
|
|
339
|
+
delta_deg -= 360
|
|
340
|
+
elif delta_deg < -180:
|
|
341
|
+
delta_deg += 360
|
|
342
|
+
|
|
343
|
+
delta_rad = np.radians(delta_deg)
|
|
344
|
+
|
|
345
|
+
if abs(delta_rad) < 1e-9:
|
|
346
|
+
return 0.0
|
|
347
|
+
|
|
348
|
+
# Rotation axis (i2 -> i3)
|
|
349
|
+
pos2 = positions[i2]
|
|
350
|
+
pos3 = positions[i3]
|
|
351
|
+
axis = pos3 - pos2
|
|
352
|
+
axis_norm = np.linalg.norm(axis)
|
|
353
|
+
|
|
354
|
+
if axis_norm < 1e-12:
|
|
355
|
+
return 0.0
|
|
356
|
+
|
|
357
|
+
axis_unit = axis / axis_norm
|
|
358
|
+
|
|
359
|
+
# Rotate each movable atom
|
|
360
|
+
for idx in atom_indices_to_move:
|
|
361
|
+
rel = positions[idx] - pos2
|
|
362
|
+
positions[idx] = pos2 + rodrigues_rotate(rel, axis_unit, delta_rad)
|
|
363
|
+
|
|
364
|
+
return float(delta_rad)
|
|
365
|
+
|
|
366
|
+
|
|
303
367
|
# ------------------------------------------------------------------
|
|
304
368
|
# Valence sanity check
|
|
305
369
|
# ------------------------------------------------------------------
|
|
@@ -154,6 +154,7 @@ class MolecularData:
|
|
|
154
154
|
atom.SetFormalCharge(data.get("charge", 0))
|
|
155
155
|
atom.SetNumRadicalElectrons(data.get("radical", 0))
|
|
156
156
|
atom.SetIntProp("_original_atom_id", atom_id)
|
|
157
|
+
atom.SetNoImplicit(False) # Allow RDKit to perceive implicit valence
|
|
157
158
|
idx = mol.AddAtom(atom)
|
|
158
159
|
atom_id_to_idx_map[atom_id] = idx
|
|
159
160
|
|
|
@@ -10,7 +10,7 @@ Repo: https://github.com/HiroYokoyama/python_molecular_editor
|
|
|
10
10
|
DOI: 10.5281/zenodo.17268532
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
from typing import Any, Callable, Optional
|
|
13
|
+
from typing import Any, Callable, List, Optional, Union
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class PluginContext:
|
|
@@ -75,6 +75,53 @@ class PluginContext:
|
|
|
75
75
|
"""
|
|
76
76
|
return Plugin3DController(self._manager.get_main_window())
|
|
77
77
|
|
|
78
|
+
def show_status_message(self, message: str, timeout: int = 3000) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Display a message in the application status bar.
|
|
81
|
+
"""
|
|
82
|
+
self._manager.show_status_message(message, timeout)
|
|
83
|
+
|
|
84
|
+
def push_undo_checkpoint(self) -> None:
|
|
85
|
+
"""
|
|
86
|
+
Create an undo checkpoint for the current state.
|
|
87
|
+
Call this AFTER making modifications to the molecule to ensure the
|
|
88
|
+
new state is saved to the undo history.
|
|
89
|
+
"""
|
|
90
|
+
self._manager.push_undo_checkpoint()
|
|
91
|
+
|
|
92
|
+
def refresh_3d_view(self) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Force a refresh (re-render) of the 3D scene.
|
|
95
|
+
"""
|
|
96
|
+
self._manager.refresh_3d_view()
|
|
97
|
+
|
|
98
|
+
def reset_3d_camera(self) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Resets the 3D camera to fit the current molecule.
|
|
101
|
+
"""
|
|
102
|
+
self._manager.reset_3d_camera()
|
|
103
|
+
|
|
104
|
+
def get_selected_atom_indices(self) -> List[int]:
|
|
105
|
+
"""
|
|
106
|
+
Returns a list of RDKit atom indices currently selected in the 2D or 3D view.
|
|
107
|
+
Note: RDKit indices are returned, which map to the current_mol.
|
|
108
|
+
"""
|
|
109
|
+
return self._manager.get_selected_atom_indices()
|
|
110
|
+
|
|
111
|
+
def register_window(self, window_id: str, window: Any) -> None:
|
|
112
|
+
"""
|
|
113
|
+
Register a custom plugin window/dialog with the application.
|
|
114
|
+
This allows the application to manage the window lifecycle.
|
|
115
|
+
Windows are namespaced by the plugin name automatically.
|
|
116
|
+
"""
|
|
117
|
+
self._manager.register_window(self._plugin_name, window_id, window)
|
|
118
|
+
|
|
119
|
+
def get_window(self, window_id: str) -> Optional[Any]:
|
|
120
|
+
"""
|
|
121
|
+
Retrieve a previously registered window by its ID.
|
|
122
|
+
"""
|
|
123
|
+
return self._manager.get_window(self._plugin_name, window_id)
|
|
124
|
+
|
|
78
125
|
def get_main_window(self) -> Any:
|
|
79
126
|
"""
|
|
80
127
|
Returns the raw MainWindow instance.
|
|
@@ -83,22 +130,45 @@ class PluginContext:
|
|
|
83
130
|
return self._manager.get_main_window()
|
|
84
131
|
|
|
85
132
|
@property
|
|
86
|
-
def
|
|
133
|
+
def current_mol(self) -> Any:
|
|
87
134
|
"""
|
|
88
|
-
Get or set the current molecule (RDKit Mol object).
|
|
135
|
+
Get or set the current molecule (RDKit Mol object). Shortcut for current_molecule.
|
|
89
136
|
"""
|
|
90
|
-
mw = self.
|
|
137
|
+
mw = self.get_main_window()
|
|
138
|
+
return mw.current_mol if mw else None
|
|
139
|
+
|
|
140
|
+
@current_mol.setter
|
|
141
|
+
def current_mol(self, mol: Any):
|
|
142
|
+
mw = self.get_main_window()
|
|
91
143
|
if mw:
|
|
92
|
-
|
|
93
|
-
|
|
144
|
+
mw.current_mol = mol
|
|
145
|
+
if hasattr(mw.view_3d_manager, "draw_molecule_3d"):
|
|
146
|
+
mw.view_3d_manager.draw_molecule_3d(mol)
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def current_molecule(self) -> Any:
|
|
150
|
+
"""Alias for current_mol for backward compatibility."""
|
|
151
|
+
return self.current_mol
|
|
94
152
|
|
|
95
153
|
@current_molecule.setter
|
|
96
154
|
def current_molecule(self, mol: Any):
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
155
|
+
self.current_mol = mol
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def plotter(self) -> Any:
|
|
159
|
+
"""
|
|
160
|
+
Returns the PyVista plotter from the MainWindow.
|
|
161
|
+
"""
|
|
162
|
+
mw = self.get_main_window()
|
|
163
|
+
return mw.plotter if mw else None
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def scene(self) -> Any:
|
|
167
|
+
"""
|
|
168
|
+
Returns the 2D MoleculeScene from the MainWindow.
|
|
169
|
+
"""
|
|
170
|
+
mw = self.get_main_window()
|
|
171
|
+
return mw.scene if mw else None
|
|
102
172
|
|
|
103
173
|
def add_export_action(self, label: str, callback: Callable):
|
|
104
174
|
"""
|
|
@@ -202,6 +272,10 @@ class Plugin3DController:
|
|
|
202
272
|
def __init__(self, main_window):
|
|
203
273
|
self._mw = main_window
|
|
204
274
|
|
|
275
|
+
def _get_v3d(self):
|
|
276
|
+
"""Helper to get the 3D manager."""
|
|
277
|
+
return getattr(self._mw, "view_3d_manager", None)
|
|
278
|
+
|
|
205
279
|
def set_atom_color(self, atom_index: int, color_hex: str):
|
|
206
280
|
"""
|
|
207
281
|
Set the color of a specific atom in the 3D view.
|
|
@@ -209,11 +283,11 @@ class Plugin3DController:
|
|
|
209
283
|
atom_index: RDKit atom index.
|
|
210
284
|
color_hex: Hex string e.g., "#FF0000".
|
|
211
285
|
"""
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
)
|
|
216
|
-
|
|
286
|
+
v3d = self._get_v3d()
|
|
287
|
+
if v3d:
|
|
288
|
+
v3d.update_atom_color_override(atom_index, color_hex)
|
|
289
|
+
if hasattr(self._mw, "plotter") and self._mw.plotter:
|
|
290
|
+
self._mw.plotter.render()
|
|
217
291
|
|
|
218
292
|
def set_bond_color(self, bond_index: int, color_hex: str):
|
|
219
293
|
"""
|
|
@@ -223,8 +297,25 @@ class Plugin3DController:
|
|
|
223
297
|
bond_index: RDKit bond index.
|
|
224
298
|
color_hex: Hex string e.g., "#00FF00".
|
|
225
299
|
"""
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
)
|
|
230
|
-
|
|
300
|
+
v3d = self._get_v3d()
|
|
301
|
+
if v3d:
|
|
302
|
+
v3d.update_bond_color_override(bond_index, color_hex)
|
|
303
|
+
if hasattr(self._mw, "plotter") and self._mw.plotter:
|
|
304
|
+
self._mw.plotter.render()
|
|
305
|
+
|
|
306
|
+
def set_bond_color_by_atoms(self, atom_idx1: int, atom_idx2: int, color_hex: str):
|
|
307
|
+
"""
|
|
308
|
+
Set the color of the bond between two atoms.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
atom_idx1: First RDKit atom index.
|
|
312
|
+
atom_idx2: Second RDKit atom index.
|
|
313
|
+
color_hex: Hex string e.g., "#00FF00".
|
|
314
|
+
"""
|
|
315
|
+
mol = getattr(self._mw, "current_mol", None)
|
|
316
|
+
if not mol:
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
bond = mol.GetBondBetweenAtoms(atom_idx1, atom_idx2)
|
|
320
|
+
if bond:
|
|
321
|
+
self.set_bond_color(bond.GetIdx(), color_hex)
|
|
@@ -50,6 +50,7 @@ class PluginManager:
|
|
|
50
50
|
self.load_handlers: Dict[str, Callable] = {}
|
|
51
51
|
self.custom_3d_styles: Dict[str, Dict[str, Any]] = {}
|
|
52
52
|
self.document_reset_handlers: List[Dict[str, Any]] = []
|
|
53
|
+
self.plugin_windows: Dict[str, Dict[str, Any]] = {} # Map of plugin_name -> {window_id -> window}
|
|
53
54
|
|
|
54
55
|
def get_main_window(self) -> Any:
|
|
55
56
|
return self.main_window
|
|
@@ -454,6 +455,82 @@ class PluginManager:
|
|
|
454
455
|
{"plugin": plugin_name, "callback": callback}
|
|
455
456
|
)
|
|
456
457
|
|
|
458
|
+
# --- New API Implementation ---
|
|
459
|
+
def show_status_message(self, message: str, timeout: int = 3000) -> None:
|
|
460
|
+
"""Display a message in the MainWindow status bar."""
|
|
461
|
+
if self.main_window and hasattr(self.main_window, "statusBar"):
|
|
462
|
+
status_bar = self.main_window.statusBar()
|
|
463
|
+
if status_bar:
|
|
464
|
+
status_bar.showMessage(message, timeout)
|
|
465
|
+
|
|
466
|
+
def push_undo_checkpoint(self) -> None:
|
|
467
|
+
"""Triggers an undo checkpoint in the application state."""
|
|
468
|
+
if self.main_window and hasattr(self.main_window, "state_manager"):
|
|
469
|
+
self.main_window.state_manager.push_undo_state()
|
|
470
|
+
|
|
471
|
+
def refresh_3d_view(self) -> None:
|
|
472
|
+
"""Force a re-render of the 3D scene."""
|
|
473
|
+
if self.main_window and hasattr(self.main_window, "plotter") and self.main_window.plotter:
|
|
474
|
+
self.main_window.plotter.render()
|
|
475
|
+
|
|
476
|
+
def reset_3d_camera(self) -> None:
|
|
477
|
+
"""Reset the 3D camera to fit the current molecule."""
|
|
478
|
+
if self.main_window and hasattr(self.main_window, "plotter") and self.main_window.plotter:
|
|
479
|
+
self.main_window.plotter.reset_camera()
|
|
480
|
+
self.main_window.plotter.render()
|
|
481
|
+
|
|
482
|
+
def get_selected_atom_indices(self) -> List[int]:
|
|
483
|
+
"""Retrieve RDKit atom indices currently selected in the 2D scene."""
|
|
484
|
+
selected_indices = []
|
|
485
|
+
if not self.main_window:
|
|
486
|
+
return []
|
|
487
|
+
|
|
488
|
+
# Check 2D selection
|
|
489
|
+
try:
|
|
490
|
+
from .plugin_interface import PluginContext
|
|
491
|
+
# We need to access the scene items.
|
|
492
|
+
# In MoleditPy, atoms in the scene are AtomItem objects which have an 'atom_id'.
|
|
493
|
+
# These atom_ids map to entries in state_manager.data.atoms.
|
|
494
|
+
# RDKit molecule atoms have an '_original_atom_id' property.
|
|
495
|
+
|
|
496
|
+
scene = getattr(self.main_window, "scene", None)
|
|
497
|
+
if scene:
|
|
498
|
+
selected_items = scene.selectedItems()
|
|
499
|
+
selected_atom_ids = set()
|
|
500
|
+
for item in selected_items:
|
|
501
|
+
# Relying on duck-typing for AtomItem
|
|
502
|
+
if hasattr(item, "atom_id"):
|
|
503
|
+
selected_atom_ids.add(item.atom_id)
|
|
504
|
+
|
|
505
|
+
# Now map these editor IDs to RDKit indices
|
|
506
|
+
mol = getattr(self.main_window, "current_mol", None)
|
|
507
|
+
if mol and selected_atom_ids:
|
|
508
|
+
for i in range(mol.GetNumAtoms()):
|
|
509
|
+
atom = mol.GetAtomWithIdx(i)
|
|
510
|
+
if atom.HasProp("_original_atom_id"):
|
|
511
|
+
try:
|
|
512
|
+
orig_id = atom.GetIntProp("_original_atom_id")
|
|
513
|
+
if orig_id in selected_atom_ids:
|
|
514
|
+
selected_indices.append(i)
|
|
515
|
+
except (RuntimeError, ValueError, TypeError):
|
|
516
|
+
continue
|
|
517
|
+
except ImportError:
|
|
518
|
+
pass
|
|
519
|
+
except Exception as e:
|
|
520
|
+
logging.error(f"Error retrieving selected atom indices: {e}")
|
|
521
|
+
|
|
522
|
+
return selected_indices
|
|
523
|
+
|
|
524
|
+
def register_window(self, plugin_name: str, window_id: str, window: Any) -> None:
|
|
525
|
+
"""Register a plugin window to keep it alive and manageable."""
|
|
526
|
+
if plugin_name not in self.plugin_windows:
|
|
527
|
+
self.plugin_windows[plugin_name] = {}
|
|
528
|
+
self.plugin_windows[plugin_name][window_id] = window
|
|
529
|
+
|
|
530
|
+
def get_window(self, plugin_name: str, window_id: str) -> Optional[Any]:
|
|
531
|
+
"""Retrieve a registered plugin window."""
|
|
532
|
+
return self.plugin_windows.get(plugin_name, {}).get(window_id)
|
|
533
|
+
|
|
457
534
|
def invoke_document_reset_handlers(self) -> None:
|
|
458
535
|
"""Call all registered document reset handlers."""
|
|
459
536
|
for handler in self.document_reset_handlers:
|