MoleditPy-linux 2.4.1__py3-none-any.whl
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/__init__.py +17 -0
- moleditpy_linux/__main__.py +29 -0
- moleditpy_linux/main.py +37 -0
- moleditpy_linux/modules/__init__.py +41 -0
- moleditpy_linux/modules/about_dialog.py +104 -0
- moleditpy_linux/modules/align_plane_dialog.py +292 -0
- moleditpy_linux/modules/alignment_dialog.py +272 -0
- moleditpy_linux/modules/analysis_window.py +209 -0
- moleditpy_linux/modules/angle_dialog.py +440 -0
- moleditpy_linux/modules/assets/file_icon.ico +0 -0
- moleditpy_linux/modules/assets/icon.icns +0 -0
- moleditpy_linux/modules/assets/icon.ico +0 -0
- moleditpy_linux/modules/assets/icon.png +0 -0
- moleditpy_linux/modules/atom_item.py +395 -0
- moleditpy_linux/modules/bond_item.py +464 -0
- moleditpy_linux/modules/bond_length_dialog.py +380 -0
- moleditpy_linux/modules/calculation_worker.py +766 -0
- moleditpy_linux/modules/color_settings_dialog.py +321 -0
- moleditpy_linux/modules/constants.py +88 -0
- moleditpy_linux/modules/constrained_optimization_dialog.py +678 -0
- moleditpy_linux/modules/custom_interactor_style.py +749 -0
- moleditpy_linux/modules/custom_qt_interactor.py +102 -0
- moleditpy_linux/modules/dialog3_d_picking_mixin.py +141 -0
- moleditpy_linux/modules/dihedral_dialog.py +443 -0
- moleditpy_linux/modules/main_window.py +850 -0
- moleditpy_linux/modules/main_window_app_state.py +787 -0
- moleditpy_linux/modules/main_window_compute.py +1242 -0
- moleditpy_linux/modules/main_window_dialog_manager.py +460 -0
- moleditpy_linux/modules/main_window_edit_3d.py +536 -0
- moleditpy_linux/modules/main_window_edit_actions.py +1565 -0
- moleditpy_linux/modules/main_window_export.py +917 -0
- moleditpy_linux/modules/main_window_main_init.py +2100 -0
- moleditpy_linux/modules/main_window_molecular_parsers.py +1044 -0
- moleditpy_linux/modules/main_window_project_io.py +434 -0
- moleditpy_linux/modules/main_window_string_importers.py +275 -0
- moleditpy_linux/modules/main_window_ui_manager.py +602 -0
- moleditpy_linux/modules/main_window_view_3d.py +1539 -0
- moleditpy_linux/modules/main_window_view_loaders.py +355 -0
- moleditpy_linux/modules/mirror_dialog.py +122 -0
- moleditpy_linux/modules/molecular_data.py +302 -0
- moleditpy_linux/modules/molecule_scene.py +2000 -0
- moleditpy_linux/modules/move_group_dialog.py +600 -0
- moleditpy_linux/modules/periodic_table_dialog.py +84 -0
- moleditpy_linux/modules/planarize_dialog.py +220 -0
- moleditpy_linux/modules/plugin_interface.py +215 -0
- moleditpy_linux/modules/plugin_manager.py +473 -0
- moleditpy_linux/modules/plugin_manager_window.py +274 -0
- moleditpy_linux/modules/settings_dialog.py +1503 -0
- moleditpy_linux/modules/template_preview_item.py +157 -0
- moleditpy_linux/modules/template_preview_view.py +74 -0
- moleditpy_linux/modules/translation_dialog.py +364 -0
- moleditpy_linux/modules/user_template_dialog.py +692 -0
- moleditpy_linux/modules/zoomable_view.py +129 -0
- moleditpy_linux-2.4.1.dist-info/METADATA +954 -0
- moleditpy_linux-2.4.1.dist-info/RECORD +59 -0
- moleditpy_linux-2.4.1.dist-info/WHEEL +5 -0
- moleditpy_linux-2.4.1.dist-info/entry_points.txt +2 -0
- moleditpy_linux-2.4.1.dist-info/licenses/LICENSE +674 -0
- moleditpy_linux-2.4.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,220 @@
|
|
|
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 PyQt6.QtWidgets import (
|
|
14
|
+
QDialog, QVBoxLayout, QLabel, QHBoxLayout, QPushButton, QMessageBox
|
|
15
|
+
)
|
|
16
|
+
from PyQt6.QtCore import Qt
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from .dialog3_d_picking_mixin import Dialog3DPickingMixin
|
|
21
|
+
except Exception:
|
|
22
|
+
from modules.dialog3_d_picking_mixin import Dialog3DPickingMixin
|
|
23
|
+
|
|
24
|
+
class PlanarizeDialog(Dialog3DPickingMixin, QDialog):
|
|
25
|
+
|
|
26
|
+
"""選択原子群を最適フィット平面へ投影して planarize するダイアログ
|
|
27
|
+
AlignPlane を参考にした選択UIを持ち、Apply ボタンで選択原子を平面へ直交射影する。
|
|
28
|
+
"""
|
|
29
|
+
def __init__(self, mol, main_window, preselected_atoms=None, parent=None):
|
|
30
|
+
QDialog.__init__(self, parent)
|
|
31
|
+
Dialog3DPickingMixin.__init__(self)
|
|
32
|
+
self.mol = mol
|
|
33
|
+
self.main_window = main_window
|
|
34
|
+
self.selected_atoms = set()
|
|
35
|
+
|
|
36
|
+
if preselected_atoms:
|
|
37
|
+
# 事前選択された原子を追加
|
|
38
|
+
self.selected_atoms.update(preselected_atoms)
|
|
39
|
+
|
|
40
|
+
self.init_ui()
|
|
41
|
+
|
|
42
|
+
if self.selected_atoms:
|
|
43
|
+
self.show_atom_labels()
|
|
44
|
+
self.update_display()
|
|
45
|
+
|
|
46
|
+
def init_ui(self):
|
|
47
|
+
self.setWindowTitle("Planarize")
|
|
48
|
+
self.setModal(False)
|
|
49
|
+
layout = QVBoxLayout(self)
|
|
50
|
+
|
|
51
|
+
instruction_label = QLabel("Click atoms in the 3D view to select them for planarization (minimum 3 required).")
|
|
52
|
+
instruction_label.setWordWrap(True)
|
|
53
|
+
layout.addWidget(instruction_label)
|
|
54
|
+
|
|
55
|
+
self.selection_label = QLabel("No atoms selected")
|
|
56
|
+
layout.addWidget(self.selection_label)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
button_layout = QHBoxLayout()
|
|
60
|
+
self.clear_button = QPushButton("Clear Selection")
|
|
61
|
+
self.clear_button.clicked.connect(self.clear_selection)
|
|
62
|
+
button_layout.addWidget(self.clear_button)
|
|
63
|
+
|
|
64
|
+
# Select All Atoms ボタンを追加
|
|
65
|
+
self.select_all_button = QPushButton("Select All Atoms")
|
|
66
|
+
self.select_all_button.setToolTip("Select all atoms in the molecule for planarization")
|
|
67
|
+
self.select_all_button.clicked.connect(self.select_all_atoms)
|
|
68
|
+
button_layout.addWidget(self.select_all_button)
|
|
69
|
+
|
|
70
|
+
self.apply_button = QPushButton("Apply planarize")
|
|
71
|
+
self.apply_button.clicked.connect(self.apply_planarize)
|
|
72
|
+
self.apply_button.setEnabled(False)
|
|
73
|
+
button_layout.addWidget(self.apply_button)
|
|
74
|
+
|
|
75
|
+
close_button = QPushButton("Close")
|
|
76
|
+
close_button.clicked.connect(self.reject)
|
|
77
|
+
button_layout.addWidget(close_button)
|
|
78
|
+
|
|
79
|
+
button_layout.addStretch()
|
|
80
|
+
|
|
81
|
+
layout.addLayout(button_layout)
|
|
82
|
+
|
|
83
|
+
# enable picking
|
|
84
|
+
self.picker_connection = None
|
|
85
|
+
self.enable_picking()
|
|
86
|
+
|
|
87
|
+
def on_atom_picked(self, atom_idx):
|
|
88
|
+
if atom_idx in self.selected_atoms:
|
|
89
|
+
self.selected_atoms.remove(atom_idx)
|
|
90
|
+
else:
|
|
91
|
+
self.selected_atoms.add(atom_idx)
|
|
92
|
+
self.show_atom_labels()
|
|
93
|
+
self.update_display()
|
|
94
|
+
|
|
95
|
+
def clear_selection(self):
|
|
96
|
+
self.selected_atoms.clear()
|
|
97
|
+
self.clear_atom_labels()
|
|
98
|
+
self.update_display()
|
|
99
|
+
|
|
100
|
+
def update_display(self):
|
|
101
|
+
count = len(self.selected_atoms)
|
|
102
|
+
if count == 0:
|
|
103
|
+
self.selection_label.setText("Click atoms to select for planarize (minimum 3 required)")
|
|
104
|
+
self.apply_button.setEnabled(False)
|
|
105
|
+
else:
|
|
106
|
+
atom_list = sorted(self.selected_atoms)
|
|
107
|
+
atom_display = []
|
|
108
|
+
for i, atom_idx in enumerate(atom_list):
|
|
109
|
+
symbol = self.mol.GetAtomWithIdx(atom_idx).GetSymbol()
|
|
110
|
+
atom_display.append(f"#{i+1}: {symbol}({atom_idx})")
|
|
111
|
+
self.selection_label.setText(f"Selected {count} atoms: {', '.join(atom_display)}")
|
|
112
|
+
self.apply_button.setEnabled(count >= 3)
|
|
113
|
+
|
|
114
|
+
def select_all_atoms(self):
|
|
115
|
+
"""Select all atoms in the current molecule (or fallback) and update labels/UI."""
|
|
116
|
+
try:
|
|
117
|
+
# Prefer RDKit molecule if available
|
|
118
|
+
if hasattr(self, 'mol') and self.mol is not None:
|
|
119
|
+
try:
|
|
120
|
+
n = self.mol.GetNumAtoms()
|
|
121
|
+
# create a set of indices [0..n-1]
|
|
122
|
+
self.selected_atoms = set(range(n))
|
|
123
|
+
except Exception:
|
|
124
|
+
# fallback to main_window data map
|
|
125
|
+
self.selected_atoms = set(self.main_window.data.atoms.keys()) if hasattr(self.main_window, 'data') else set()
|
|
126
|
+
else:
|
|
127
|
+
# fallback to main_window data map
|
|
128
|
+
self.selected_atoms = set(self.main_window.data.atoms.keys()) if hasattr(self.main_window, 'data') else set()
|
|
129
|
+
|
|
130
|
+
# Update labels and display
|
|
131
|
+
self.show_atom_labels()
|
|
132
|
+
self.update_display()
|
|
133
|
+
|
|
134
|
+
except Exception as e:
|
|
135
|
+
QMessageBox.warning(self, "Warning", f"Failed to select all atoms: {e}")
|
|
136
|
+
|
|
137
|
+
def show_atom_labels(self):
|
|
138
|
+
self.clear_atom_labels()
|
|
139
|
+
if not hasattr(self, 'selection_labels'):
|
|
140
|
+
self.selection_labels = []
|
|
141
|
+
if self.selected_atoms:
|
|
142
|
+
for i, atom_idx in enumerate(sorted(self.selected_atoms)):
|
|
143
|
+
pos = self.main_window.atom_positions_3d[atom_idx]
|
|
144
|
+
label_text = f"#{i+1}"
|
|
145
|
+
label_actor = self.main_window.plotter.add_point_labels(
|
|
146
|
+
[pos], [label_text],
|
|
147
|
+
point_size=20,
|
|
148
|
+
font_size=12,
|
|
149
|
+
text_color='cyan',
|
|
150
|
+
always_visible=True
|
|
151
|
+
)
|
|
152
|
+
self.selection_labels.append(label_actor)
|
|
153
|
+
|
|
154
|
+
def clear_atom_labels(self):
|
|
155
|
+
if hasattr(self, 'selection_labels'):
|
|
156
|
+
for label_actor in self.selection_labels:
|
|
157
|
+
try:
|
|
158
|
+
self.main_window.plotter.remove_actor(label_actor)
|
|
159
|
+
except Exception:
|
|
160
|
+
pass
|
|
161
|
+
self.selection_labels = []
|
|
162
|
+
|
|
163
|
+
def apply_planarize(self):
|
|
164
|
+
if not self.selected_atoms or len(self.selected_atoms) < 3:
|
|
165
|
+
QMessageBox.warning(self, "Warning", "Please select at least 3 atoms for planarize.")
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
selected_indices = list(sorted(self.selected_atoms))
|
|
170
|
+
selected_positions = self.main_window.atom_positions_3d[selected_indices].copy()
|
|
171
|
+
|
|
172
|
+
centroid = np.mean(selected_positions, axis=0)
|
|
173
|
+
centered_positions = selected_positions - centroid
|
|
174
|
+
|
|
175
|
+
# SVDによる最小二乗平面の法線取得
|
|
176
|
+
u, s, vh = np.linalg.svd(centered_positions, full_matrices=False)
|
|
177
|
+
normal = vh[-1]
|
|
178
|
+
norm = np.linalg.norm(normal)
|
|
179
|
+
if norm == 0:
|
|
180
|
+
QMessageBox.warning(self, "Warning", "Cannot determine fit plane (degenerate positions).")
|
|
181
|
+
return
|
|
182
|
+
normal = normal / norm
|
|
183
|
+
|
|
184
|
+
# 各点を重心を通る平面へ直交射影
|
|
185
|
+
projections = centered_positions - np.outer(np.dot(centered_positions, normal), normal)
|
|
186
|
+
new_positions = projections + centroid
|
|
187
|
+
|
|
188
|
+
# 分子座標を更新
|
|
189
|
+
conf = self.mol.GetConformer()
|
|
190
|
+
for i, new_pos in zip(selected_indices, new_positions):
|
|
191
|
+
conf.SetAtomPosition(int(i), new_pos.tolist())
|
|
192
|
+
self.main_window.atom_positions_3d[int(i)] = new_pos
|
|
193
|
+
|
|
194
|
+
# 3Dビュー更新
|
|
195
|
+
self.main_window.draw_molecule_3d(self.mol)
|
|
196
|
+
self.main_window.update_chiral_labels()
|
|
197
|
+
self.main_window.push_undo_state()
|
|
198
|
+
|
|
199
|
+
QMessageBox.information(self, "Success", f"Planarized {len(selected_indices)} atoms to best-fit plane.")
|
|
200
|
+
|
|
201
|
+
except Exception as e:
|
|
202
|
+
QMessageBox.critical(self, "Error", f"Failed to planarize: {e}")
|
|
203
|
+
|
|
204
|
+
def closeEvent(self, event):
|
|
205
|
+
"""ダイアログが閉じられる時の処理"""
|
|
206
|
+
self.clear_atom_labels()
|
|
207
|
+
self.disable_picking()
|
|
208
|
+
super().closeEvent(event)
|
|
209
|
+
|
|
210
|
+
def reject(self):
|
|
211
|
+
"""キャンセル時の処理"""
|
|
212
|
+
self.clear_atom_labels()
|
|
213
|
+
self.disable_picking()
|
|
214
|
+
super().reject()
|
|
215
|
+
|
|
216
|
+
def accept(self):
|
|
217
|
+
"""OK時の処理"""
|
|
218
|
+
self.clear_atom_labels()
|
|
219
|
+
self.disable_picking()
|
|
220
|
+
super().accept()
|
|
@@ -0,0 +1,215 @@
|
|
|
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 typing import Callable, Optional, Any
|
|
14
|
+
|
|
15
|
+
class PluginContext:
|
|
16
|
+
"""
|
|
17
|
+
PluginContext provides a safe interface for plugins to interact with the application.
|
|
18
|
+
It is passed to the `initialize(context)` function of the plugin.
|
|
19
|
+
"""
|
|
20
|
+
def __init__(self, manager, plugin_name: str):
|
|
21
|
+
self._manager = manager
|
|
22
|
+
self._plugin_name = plugin_name
|
|
23
|
+
|
|
24
|
+
def add_menu_action(self, path: str, callback: Callable, text: Optional[str] = None, icon: Optional[str] = None, shortcut: Optional[str] = None):
|
|
25
|
+
"""
|
|
26
|
+
Register a menu action.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
path: Menu path, e.g., "File/Import", "Edit", or "MyPlugin" (top level).
|
|
30
|
+
callback: Function to call when triggered.
|
|
31
|
+
text: Label for the action (defaults to last part of path if None).
|
|
32
|
+
icon: Path to icon or icon name (optional).
|
|
33
|
+
shortcut: Keyboard shortcut (optional).
|
|
34
|
+
"""
|
|
35
|
+
self._manager.register_menu_action(self._plugin_name, path, callback, text, icon, shortcut)
|
|
36
|
+
|
|
37
|
+
def add_toolbar_action(self, callback: Callable, text: str, icon: Optional[str] = None, tooltip: Optional[str] = None):
|
|
38
|
+
"""
|
|
39
|
+
Register a toolbar action.
|
|
40
|
+
"""
|
|
41
|
+
self._manager.register_toolbar_action(self._plugin_name, callback, text, icon, tooltip)
|
|
42
|
+
|
|
43
|
+
def register_drop_handler(self, callback: Callable[[str], bool], priority: int = 0):
|
|
44
|
+
"""
|
|
45
|
+
Register a handler for file drops.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
callback: Function taking (file_path) -> bool. Returns True if handled.
|
|
49
|
+
priority: Higher priority handlers are tried first.
|
|
50
|
+
"""
|
|
51
|
+
self._manager.register_drop_handler(self._plugin_name, callback, priority)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_3d_controller(self) -> 'Plugin3DController':
|
|
56
|
+
"""
|
|
57
|
+
Returns a controller to manipulate the 3D scene (e.g. colors).
|
|
58
|
+
"""
|
|
59
|
+
return Plugin3DController(self._manager.get_main_window())
|
|
60
|
+
|
|
61
|
+
def get_main_window(self) -> Any:
|
|
62
|
+
"""
|
|
63
|
+
Returns the raw MainWindow instance.
|
|
64
|
+
Use with caution; prefer specific methods when available.
|
|
65
|
+
"""
|
|
66
|
+
return self._manager.get_main_window()
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def current_molecule(self) -> Any:
|
|
70
|
+
"""
|
|
71
|
+
Get or set the current molecule (RDKit Mol object).
|
|
72
|
+
"""
|
|
73
|
+
mw = self._manager.get_main_window()
|
|
74
|
+
if mw:
|
|
75
|
+
return mw.current_mol
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
@current_molecule.setter
|
|
79
|
+
def current_molecule(self, mol: Any):
|
|
80
|
+
mw = self._manager.get_main_window()
|
|
81
|
+
if mw:
|
|
82
|
+
mw.current_mol = mol
|
|
83
|
+
if hasattr(mw, 'draw_molecule_3d'):
|
|
84
|
+
mw.draw_molecule_3d(mol)
|
|
85
|
+
|
|
86
|
+
def add_export_action(self, label: str, callback: Callable):
|
|
87
|
+
"""
|
|
88
|
+
Register a custom export action.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
label: Text to display in the Export menu (e.g., "Export as MyFormat...").
|
|
92
|
+
callback: Function to call when triggered.
|
|
93
|
+
"""
|
|
94
|
+
self._manager.register_export_action(self._plugin_name, label, callback)
|
|
95
|
+
|
|
96
|
+
def register_optimization_method(self, method_name: str, callback: Callable[[Any], bool]):
|
|
97
|
+
"""
|
|
98
|
+
Register a custom 3D optimization method.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
method_name: Name of the method to display in 3D Optimization menu.
|
|
102
|
+
callback: Function taking (rdkit_mol) -> bool (success).
|
|
103
|
+
Modifies the molecule in-place.
|
|
104
|
+
"""
|
|
105
|
+
self._manager.register_optimization_method(self._plugin_name, method_name, callback)
|
|
106
|
+
|
|
107
|
+
def register_file_opener(self, extension: str, callback: Callable[[str], None], priority: int = 0):
|
|
108
|
+
"""
|
|
109
|
+
Register a handler for opening a specific file extension.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
extension: File extension including dot, e.g. ".xyz".
|
|
113
|
+
callback: Function taking (file_path) -> None.
|
|
114
|
+
Should load the file into the main window.
|
|
115
|
+
priority: Higher priority handlers are tried first (default 0).
|
|
116
|
+
"""
|
|
117
|
+
self._manager.register_file_opener(self._plugin_name, extension, callback, priority)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def add_analysis_tool(self, label: str, callback: Callable):
|
|
122
|
+
"""
|
|
123
|
+
Register a tool in the Analysis menu.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
label: Text to display in the menu.
|
|
127
|
+
callback: Function to contact when triggered.
|
|
128
|
+
"""
|
|
129
|
+
self._manager.register_analysis_tool(self._plugin_name, label, callback)
|
|
130
|
+
|
|
131
|
+
def register_save_handler(self, callback: Callable[[], dict]):
|
|
132
|
+
"""
|
|
133
|
+
Register a callback to save state into the project file.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
callback: Function returning a dict of serializable data.
|
|
137
|
+
"""
|
|
138
|
+
self._manager.register_save_handler(self._plugin_name, callback)
|
|
139
|
+
|
|
140
|
+
def register_load_handler(self, callback: Callable[[dict], None]):
|
|
141
|
+
"""
|
|
142
|
+
Register a callback to restore state from the project file.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
callback: Function receiving the dict of saved data.
|
|
146
|
+
"""
|
|
147
|
+
def register_load_handler(self, callback: Callable[[dict], None]):
|
|
148
|
+
"""
|
|
149
|
+
Register a callback to restore state from the project file.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
callback: Function receiving the dict of saved data.
|
|
153
|
+
"""
|
|
154
|
+
self._manager.register_load_handler(self._plugin_name, callback)
|
|
155
|
+
|
|
156
|
+
def register_3d_context_menu(self, callback: Callable, label: str):
|
|
157
|
+
"""Deprecated: This method does nothing. Kept for backward compatibility."""
|
|
158
|
+
print(f"Warning: Plugin '{self._plugin_name}' uses deprecated 'register_3d_context_menu'. This API has been removed.")
|
|
159
|
+
|
|
160
|
+
def register_3d_style(self, style_name: str, callback: Callable[[Any, Any], None]):
|
|
161
|
+
"""
|
|
162
|
+
Register a custom 3D rendering style.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
style_name: Name of the style (must be unique).
|
|
166
|
+
callback: Function taking (main_window, mol) -> None.
|
|
167
|
+
Should fully handle drawing the molecule in the 3D view.
|
|
168
|
+
"""
|
|
169
|
+
self._manager.register_3d_style(self._plugin_name, style_name, callback)
|
|
170
|
+
|
|
171
|
+
def register_document_reset_handler(self, callback: Callable[[], None]):
|
|
172
|
+
"""
|
|
173
|
+
Register a callback to be called when a new document is created (File→New).
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
callback: Function with no arguments that resets plugin state.
|
|
177
|
+
"""
|
|
178
|
+
self._manager.register_document_reset_handler(self._plugin_name, callback)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class Plugin3DController:
|
|
187
|
+
"""Helper to manipulate the 3D scene."""
|
|
188
|
+
def __init__(self, main_window):
|
|
189
|
+
self._mw = main_window
|
|
190
|
+
|
|
191
|
+
def set_atom_color(self, atom_index: int, color_hex: str):
|
|
192
|
+
"""
|
|
193
|
+
Set the color of a specific atom in the 3D view.
|
|
194
|
+
Args:
|
|
195
|
+
atom_index: RDKit atom index.
|
|
196
|
+
color_hex: Hex string e.g., "#FF0000".
|
|
197
|
+
"""
|
|
198
|
+
# This will need to hook into the actual 3D view logic
|
|
199
|
+
if hasattr(self._mw, 'main_window_view_3d'):
|
|
200
|
+
# Logic to update color map and trigger redraw
|
|
201
|
+
# For now we can assume we might need to expose a method in view_3d
|
|
202
|
+
self._mw.main_window_view_3d.update_atom_color_override(atom_index, color_hex)
|
|
203
|
+
self._mw.plotter.render()
|
|
204
|
+
|
|
205
|
+
def set_bond_color(self, bond_index: int, color_hex: str):
|
|
206
|
+
"""
|
|
207
|
+
Set the color of a specific bond in the 3D view.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
bond_index: RDKit bond index.
|
|
211
|
+
color_hex: Hex string e.g., "#00FF00".
|
|
212
|
+
"""
|
|
213
|
+
if hasattr(self._mw, 'main_window_view_3d'):
|
|
214
|
+
self._mw.main_window_view_3d.update_bond_color_override(bond_index, color_hex)
|
|
215
|
+
self._mw.plotter.render()
|