MoleditPy 2.1.1__py3-none-any.whl → 2.2.0a0__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/modules/constants.py +1 -1
- moleditpy/modules/main_window.py +8 -0
- moleditpy/modules/main_window_app_state.py +24 -0
- moleditpy/modules/main_window_compute.py +27 -0
- moleditpy/modules/main_window_main_init.py +274 -56
- moleditpy/modules/main_window_ui_manager.py +12 -1
- moleditpy/modules/main_window_view_3d.py +122 -35
- moleditpy/modules/molecule_scene.py +6 -14
- moleditpy/modules/plugin_interface.py +204 -0
- moleditpy/modules/plugin_manager.py +220 -38
- moleditpy/modules/plugin_manager_window.py +218 -0
- moleditpy/modules/template_preview_item.py +3 -6
- moleditpy/modules/user_template_dialog.py +59 -1
- {moleditpy-2.1.1.dist-info → moleditpy-2.2.0a0.dist-info}/METADATA +1 -1
- {moleditpy-2.1.1.dist-info → moleditpy-2.2.0a0.dist-info}/RECORD +19 -17
- {moleditpy-2.1.1.dist-info → moleditpy-2.2.0a0.dist-info}/WHEEL +0 -0
- {moleditpy-2.1.1.dist-info → moleditpy-2.2.0a0.dist-info}/entry_points.txt +0 -0
- {moleditpy-2.1.1.dist-info → moleditpy-2.2.0a0.dist-info}/licenses/LICENSE +0 -0
- {moleditpy-2.1.1.dist-info → moleditpy-2.2.0a0.dist-info}/top_level.txt +0 -0
|
@@ -120,8 +120,25 @@ class MainWindowView3d(object):
|
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
def draw_molecule_3d(self, mol):
|
|
123
|
+
"""Dispatch to custom style or standard drawing."""
|
|
124
|
+
mw = self.mw if hasattr(self, 'mw') else self
|
|
125
|
+
|
|
126
|
+
if hasattr(mw, 'plugin_manager') and hasattr(mw.plugin_manager, 'custom_3d_styles'):
|
|
127
|
+
if hasattr(self, 'current_3d_style') and self.current_3d_style in mw.plugin_manager.custom_3d_styles:
|
|
128
|
+
handler = mw.plugin_manager.custom_3d_styles[self.current_3d_style]['callback']
|
|
129
|
+
try:
|
|
130
|
+
handler(mw, mol)
|
|
131
|
+
return
|
|
132
|
+
except Exception as e:
|
|
133
|
+
logging.error(f"Error in custom 3d style '{self.current_3d_style}': {e}")
|
|
134
|
+
|
|
135
|
+
self.draw_standard_3d_style(mol)
|
|
136
|
+
|
|
137
|
+
def draw_standard_3d_style(self, mol, style_override=None):
|
|
123
138
|
"""3D 分子を描画し、軸アクターの参照をクリアする(軸の再制御は apply_3d_settings に任せる)"""
|
|
124
139
|
|
|
140
|
+
current_style = style_override if style_override else self.current_3d_style
|
|
141
|
+
|
|
125
142
|
# 測定選択をクリア(分子が変更されたため)
|
|
126
143
|
if hasattr(self, 'measurement_mode'):
|
|
127
144
|
self.clear_measurement_selection()
|
|
@@ -192,16 +209,26 @@ class MainWindowView3d(object):
|
|
|
192
209
|
sym = [a.GetSymbol() for a in mol_to_draw.GetAtoms()]
|
|
193
210
|
col = np.array([CPK_COLORS_PV.get(s, [0.5, 0.5, 0.5]) for s in sym])
|
|
194
211
|
|
|
212
|
+
# Apply plugin color overrides
|
|
213
|
+
if hasattr(self, '_plugin_color_overrides') and self._plugin_color_overrides:
|
|
214
|
+
for atom_idx, hex_color in self._plugin_color_overrides.items():
|
|
215
|
+
if 0 <= atom_idx < len(col):
|
|
216
|
+
try:
|
|
217
|
+
c = QColor(hex_color)
|
|
218
|
+
col[atom_idx] = [c.redF(), c.greenF(), c.blueF()]
|
|
219
|
+
except Exception:
|
|
220
|
+
pass
|
|
221
|
+
|
|
195
222
|
# スタイルに応じて原子の半径を設定(設定から読み込み)
|
|
196
|
-
if
|
|
223
|
+
if current_style == 'cpk':
|
|
197
224
|
atom_scale = self.settings.get('cpk_atom_scale', 1.0)
|
|
198
225
|
resolution = self.settings.get('cpk_resolution', 32)
|
|
199
226
|
rad = np.array([pt.GetRvdw(pt.GetAtomicNumber(s)) * atom_scale for s in sym])
|
|
200
|
-
elif
|
|
227
|
+
elif current_style == 'wireframe':
|
|
201
228
|
# Wireframeでは原子を描画しないので、この設定は実際には使用されない
|
|
202
229
|
resolution = self.settings.get('wireframe_resolution', 6)
|
|
203
230
|
rad = np.array([0.01 for s in sym]) # 極小値(使用されない)
|
|
204
|
-
elif
|
|
231
|
+
elif current_style == 'stick':
|
|
205
232
|
atom_radius = self.settings.get('stick_bond_radius', 0.15) # Use bond radius for atoms
|
|
206
233
|
resolution = self.settings.get('stick_resolution', 16)
|
|
207
234
|
rad = np.array([atom_radius for s in sym])
|
|
@@ -223,9 +250,9 @@ class MainWindowView3d(object):
|
|
|
223
250
|
)
|
|
224
251
|
|
|
225
252
|
# Wireframeスタイルの場合は原子を描画しない
|
|
226
|
-
if
|
|
253
|
+
if current_style != 'wireframe':
|
|
227
254
|
# Stickモードで末端二重結合・三重結合の原子を分裂させるための処理
|
|
228
|
-
if
|
|
255
|
+
if current_style == 'stick':
|
|
229
256
|
# 末端原子(次数1)で多重結合を持つものを検出
|
|
230
257
|
split_atoms = [] # (atom_idx, bond_order, offset_vecs)
|
|
231
258
|
skip_atoms = set() # スキップする原子のインデックス
|
|
@@ -354,12 +381,12 @@ class MainWindowView3d(object):
|
|
|
354
381
|
|
|
355
382
|
|
|
356
383
|
# ボンドの描画(ball_and_stick、wireframe、stickで描画)
|
|
357
|
-
if
|
|
384
|
+
if current_style in ['ball_and_stick', 'wireframe', 'stick']:
|
|
358
385
|
# スタイルに応じてボンドの太さと解像度を設定(設定から読み込み)
|
|
359
|
-
if
|
|
386
|
+
if current_style == 'wireframe':
|
|
360
387
|
cyl_radius = self.settings.get('wireframe_bond_radius', 0.01)
|
|
361
388
|
bond_resolution = self.settings.get('wireframe_resolution', 6)
|
|
362
|
-
elif
|
|
389
|
+
elif current_style == 'stick':
|
|
363
390
|
cyl_radius = self.settings.get('stick_bond_radius', 0.15)
|
|
364
391
|
bond_resolution = self.settings.get('stick_resolution', 16)
|
|
365
392
|
else: # ball_and_stick
|
|
@@ -368,7 +395,7 @@ class MainWindowView3d(object):
|
|
|
368
395
|
|
|
369
396
|
# Ball and Stick用の共通色
|
|
370
397
|
bs_bond_rgb = [127, 127, 127]
|
|
371
|
-
if
|
|
398
|
+
if current_style == 'ball_and_stick':
|
|
372
399
|
try:
|
|
373
400
|
bs_hex = self.settings.get('ball_stick_bond_color', '#7F7F7F')
|
|
374
401
|
q = QColor(bs_hex)
|
|
@@ -401,6 +428,28 @@ class MainWindowView3d(object):
|
|
|
401
428
|
begin_color_rgb = [int(c * 255) for c in begin_color]
|
|
402
429
|
end_color_rgb = [int(c * 255) for c in end_color]
|
|
403
430
|
|
|
431
|
+
# Check for plugin override
|
|
432
|
+
bond_idx = bond.GetIdx()
|
|
433
|
+
# Override handling: if set, force both ends and uniform color to this value
|
|
434
|
+
if hasattr(self, '_plugin_bond_color_overrides') and bond_idx in self._plugin_bond_color_overrides:
|
|
435
|
+
try:
|
|
436
|
+
# Expecting hex string
|
|
437
|
+
hex_c = self._plugin_bond_color_overrides[bond_idx]
|
|
438
|
+
c_obj = QColor(hex_c)
|
|
439
|
+
ov_rgb = [c_obj.red(), c_obj.green(), c_obj.blue()]
|
|
440
|
+
begin_color_rgb = ov_rgb
|
|
441
|
+
end_color_rgb = ov_rgb
|
|
442
|
+
# Also override uniform color in case style uses it
|
|
443
|
+
# We need to use a local variable for this iteration instead of the global bs_bond_rgb
|
|
444
|
+
# But wait, bs_bond_rgb is defined outside loop.
|
|
445
|
+
# We can define local_bs_bond_rgb
|
|
446
|
+
except Exception:
|
|
447
|
+
pass
|
|
448
|
+
|
|
449
|
+
# Determine effective uniform color for this bond
|
|
450
|
+
local_bs_bond_rgb = begin_color_rgb if (hasattr(self, '_plugin_bond_color_overrides') and bond_idx in self._plugin_bond_color_overrides) else bs_bond_rgb
|
|
451
|
+
|
|
452
|
+
|
|
404
453
|
# セグメント追加用ヘルパー関数
|
|
405
454
|
def add_segment(p1, p2, radius, color_rgb):
|
|
406
455
|
nonlocal current_point_idx
|
|
@@ -416,14 +465,20 @@ class MainWindowView3d(object):
|
|
|
416
465
|
|
|
417
466
|
# Get CPK bond color setting once for all bond types
|
|
418
467
|
use_cpk_bond = self.settings.get('ball_stick_use_cpk_bond_color', False)
|
|
468
|
+
# If overwritten, treat as if we want to show that color (effectively behave like CPK_Split but with same color, or Uniform).
|
|
469
|
+
# To be robust, if overwritten, we can force "use_cpk_bond" logic but with our same colors?
|
|
470
|
+
# Actually, if overridden, we probably want the whole bond to be that color.
|
|
471
|
+
|
|
472
|
+
is_overridden = hasattr(self, '_plugin_bond_color_overrides') and bond_idx in self._plugin_bond_color_overrides
|
|
419
473
|
|
|
420
474
|
if bt == Chem.rdchem.BondType.SINGLE or bt == Chem.rdchem.BondType.AROMATIC:
|
|
421
|
-
if
|
|
422
|
-
# 単一セグメント (Uniform color)
|
|
423
|
-
add_segment(sp, ep, cyl_radius,
|
|
424
|
-
self._3d_color_map[f'bond_{bond_counter}'] =
|
|
475
|
+
if current_style == 'ball_and_stick' and not use_cpk_bond and not is_overridden:
|
|
476
|
+
# 単一セグメント (Uniform color) - Default behavior
|
|
477
|
+
add_segment(sp, ep, cyl_radius, local_bs_bond_rgb)
|
|
478
|
+
self._3d_color_map[f'bond_{bond_counter}'] = local_bs_bond_rgb
|
|
425
479
|
else:
|
|
426
|
-
# 分割セグメント (CPK split colors)
|
|
480
|
+
# 分割セグメント (CPK split colors OR Overridden uniform)
|
|
481
|
+
# If overridden, begin/end are same, so this produces a uniform looking bond split in middle
|
|
427
482
|
mid_point = (sp + ep) / 2
|
|
428
483
|
add_segment(sp, mid_point, cyl_radius, begin_color_rgb)
|
|
429
484
|
add_segment(mid_point, ep, cyl_radius, end_color_rgb)
|
|
@@ -434,13 +489,13 @@ class MainWindowView3d(object):
|
|
|
434
489
|
# 多重結合のパラメータ計算
|
|
435
490
|
v1 = d / h
|
|
436
491
|
# モデルごとの半径ファクターを適用
|
|
437
|
-
if
|
|
492
|
+
if current_style == 'ball_and_stick':
|
|
438
493
|
double_radius_factor = self.settings.get('ball_stick_double_bond_radius_factor', 0.8)
|
|
439
494
|
triple_radius_factor = self.settings.get('ball_stick_triple_bond_radius_factor', 0.75)
|
|
440
|
-
elif
|
|
495
|
+
elif current_style == 'wireframe':
|
|
441
496
|
double_radius_factor = self.settings.get('wireframe_double_bond_radius_factor', 0.8)
|
|
442
497
|
triple_radius_factor = self.settings.get('wireframe_triple_bond_radius_factor', 0.75)
|
|
443
|
-
elif
|
|
498
|
+
elif current_style == 'stick':
|
|
444
499
|
double_radius_factor = self.settings.get('stick_double_bond_radius_factor', 0.60)
|
|
445
500
|
triple_radius_factor = self.settings.get('stick_triple_bond_radius_factor', 0.40)
|
|
446
501
|
else:
|
|
@@ -448,13 +503,13 @@ class MainWindowView3d(object):
|
|
|
448
503
|
triple_radius_factor = 0.75
|
|
449
504
|
|
|
450
505
|
# 設定からオフセットファクターを取得(モデルごと)
|
|
451
|
-
if
|
|
506
|
+
if current_style == 'ball_and_stick':
|
|
452
507
|
double_offset_factor = self.settings.get('ball_stick_double_bond_offset_factor', 2.0)
|
|
453
508
|
triple_offset_factor = self.settings.get('ball_stick_triple_bond_offset_factor', 2.0)
|
|
454
|
-
elif
|
|
509
|
+
elif current_style == 'wireframe':
|
|
455
510
|
double_offset_factor = self.settings.get('wireframe_double_bond_offset_factor', 3.0)
|
|
456
511
|
triple_offset_factor = self.settings.get('wireframe_triple_bond_offset_factor', 3.0)
|
|
457
|
-
elif
|
|
512
|
+
elif current_style == 'stick':
|
|
458
513
|
double_offset_factor = self.settings.get('stick_double_bond_offset_factor', 1.5)
|
|
459
514
|
triple_offset_factor = self.settings.get('stick_triple_bond_offset_factor', 1.0)
|
|
460
515
|
else:
|
|
@@ -471,11 +526,11 @@ class MainWindowView3d(object):
|
|
|
471
526
|
p2_start = sp - off_dir * (s_double / 2)
|
|
472
527
|
p2_end = ep - off_dir * (s_double / 2)
|
|
473
528
|
|
|
474
|
-
if
|
|
475
|
-
add_segment(p1_start, p1_end, r,
|
|
476
|
-
add_segment(p2_start, p2_end, r,
|
|
477
|
-
self._3d_color_map[f'bond_{bond_counter}_1'] =
|
|
478
|
-
self._3d_color_map[f'bond_{bond_counter}_2'] =
|
|
529
|
+
if current_style == 'ball_and_stick' and not use_cpk_bond and not is_overridden:
|
|
530
|
+
add_segment(p1_start, p1_end, r, local_bs_bond_rgb)
|
|
531
|
+
add_segment(p2_start, p2_end, r, local_bs_bond_rgb)
|
|
532
|
+
self._3d_color_map[f'bond_{bond_counter}_1'] = local_bs_bond_rgb
|
|
533
|
+
self._3d_color_map[f'bond_{bond_counter}_2'] = local_bs_bond_rgb
|
|
479
534
|
else:
|
|
480
535
|
mid1 = (p1_start + p1_end) / 2
|
|
481
536
|
mid2 = (p2_start + p2_end) / 2
|
|
@@ -497,9 +552,9 @@ class MainWindowView3d(object):
|
|
|
497
552
|
s_triple = cyl_radius * triple_offset_factor
|
|
498
553
|
|
|
499
554
|
# Center
|
|
500
|
-
if
|
|
501
|
-
add_segment(sp, ep, r,
|
|
502
|
-
self._3d_color_map[f'bond_{bond_counter}_1'] =
|
|
555
|
+
if current_style == 'ball_and_stick' and not use_cpk_bond and not is_overridden:
|
|
556
|
+
add_segment(sp, ep, r, local_bs_bond_rgb)
|
|
557
|
+
self._3d_color_map[f'bond_{bond_counter}_1'] = local_bs_bond_rgb
|
|
503
558
|
else:
|
|
504
559
|
mid = (sp + ep) / 2
|
|
505
560
|
add_segment(sp, mid, r, begin_color_rgb)
|
|
@@ -513,10 +568,10 @@ class MainWindowView3d(object):
|
|
|
513
568
|
p_start = sp + offset
|
|
514
569
|
p_end = ep + offset
|
|
515
570
|
|
|
516
|
-
if
|
|
517
|
-
add_segment(p_start, p_end, r,
|
|
571
|
+
if current_style == 'ball_and_stick' and not use_cpk_bond and not is_overridden:
|
|
572
|
+
add_segment(p_start, p_end, r, local_bs_bond_rgb)
|
|
518
573
|
suffix = '_2' if sign == 1 else '_3'
|
|
519
|
-
self._3d_color_map[f'bond_{bond_counter}{suffix}'] =
|
|
574
|
+
self._3d_color_map[f'bond_{bond_counter}{suffix}'] = local_bs_bond_rgb
|
|
520
575
|
else:
|
|
521
576
|
mid = (p_start + p_end) / 2
|
|
522
577
|
add_segment(p_start, mid, r, begin_color_rgb)
|
|
@@ -590,11 +645,11 @@ class MainWindowView3d(object):
|
|
|
590
645
|
ring_radius = np.mean(distances) * 0.55 # Slightly smaller
|
|
591
646
|
|
|
592
647
|
# Get bond radius from current style settings for torus thickness
|
|
593
|
-
if
|
|
648
|
+
if current_style == 'stick':
|
|
594
649
|
bond_radius = self.settings.get('stick_bond_radius', 0.15)
|
|
595
|
-
elif
|
|
650
|
+
elif current_style == 'ball_and_stick':
|
|
596
651
|
bond_radius = self.settings.get('ball_stick_bond_radius', 0.1)
|
|
597
|
-
elif
|
|
652
|
+
elif current_style == 'wireframe':
|
|
598
653
|
bond_radius = self.settings.get('wireframe_bond_radius', 0.01)
|
|
599
654
|
else:
|
|
600
655
|
bond_radius = 0.1 # Default
|
|
@@ -635,7 +690,7 @@ class MainWindowView3d(object):
|
|
|
635
690
|
atom_symbols = [mol_to_draw.GetAtomWithIdx(idx).GetSymbol() for idx in ring]
|
|
636
691
|
most_common_symbol = Counter(atom_symbols).most_common(1)[0][0] if atom_symbols else None
|
|
637
692
|
|
|
638
|
-
if
|
|
693
|
+
if current_style == 'ball_and_stick':
|
|
639
694
|
# Check if using CPK bond colors
|
|
640
695
|
use_cpk = self.settings.get('ball_stick_use_cpk_bond_color', False)
|
|
641
696
|
if use_cpk:
|
|
@@ -1446,3 +1501,35 @@ class MainWindowView3d(object):
|
|
|
1446
1501
|
|
|
1447
1502
|
|
|
1448
1503
|
|
|
1504
|
+
|
|
1505
|
+
def update_bond_color_override(self, bond_idx, hex_color):
|
|
1506
|
+
"""Plugin API helper to override bond color."""
|
|
1507
|
+
if not hasattr(self, '_plugin_bond_color_overrides'):
|
|
1508
|
+
self._plugin_bond_color_overrides = {}
|
|
1509
|
+
|
|
1510
|
+
if hex_color is None:
|
|
1511
|
+
if bond_idx in self._plugin_bond_color_overrides:
|
|
1512
|
+
del self._plugin_bond_color_overrides[bond_idx]
|
|
1513
|
+
else:
|
|
1514
|
+
self._plugin_bond_color_overrides[bond_idx] = hex_color
|
|
1515
|
+
|
|
1516
|
+
if self.current_mol:
|
|
1517
|
+
self.draw_molecule_3d(self.current_mol)
|
|
1518
|
+
|
|
1519
|
+
def update_atom_color_override(self, atom_index, color_hex):
|
|
1520
|
+
"""Plugin helper to update specific atom color override."""
|
|
1521
|
+
if not hasattr(self, '_plugin_color_overrides'):
|
|
1522
|
+
self._plugin_color_overrides = {}
|
|
1523
|
+
|
|
1524
|
+
if color_hex is None:
|
|
1525
|
+
if atom_index in self._plugin_color_overrides:
|
|
1526
|
+
del self._plugin_color_overrides[atom_index]
|
|
1527
|
+
else:
|
|
1528
|
+
self._plugin_color_overrides[atom_index] = color_hex
|
|
1529
|
+
|
|
1530
|
+
if self.current_mol:
|
|
1531
|
+
self.draw_molecule_3d(self.current_mol)
|
|
1532
|
+
|
|
1533
|
+
|
|
1534
|
+
|
|
1535
|
+
|
|
@@ -1495,24 +1495,16 @@ class MoleculeScene(QGraphicsScene):
|
|
|
1495
1495
|
'atoms_data': atoms,
|
|
1496
1496
|
'attachment_atom': attachment_atom,
|
|
1497
1497
|
}
|
|
1498
|
-
# 既存のプレビューアイテムを一旦クリア
|
|
1498
|
+
# 既存のプレビューアイテムを一旦クリア (レガシーな線画描画の消去)
|
|
1499
1499
|
for item in list(self.items()):
|
|
1500
1500
|
if isinstance(item, QGraphicsLineItem) and getattr(item, '_is_template_preview', False):
|
|
1501
1501
|
self.removeItem(item)
|
|
1502
1502
|
|
|
1503
|
-
#
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
# stereo = bond_info[3] if len(bond_info) > 3 else 0
|
|
1509
|
-
if i < len(points) and j < len(points):
|
|
1510
|
-
line = QGraphicsLineItem(QLineF(points[i], points[j]))
|
|
1511
|
-
pen = QPen(Qt.black, 2 if order == 2 else 1)
|
|
1512
|
-
line.setPen(pen)
|
|
1513
|
-
line._is_template_preview = True # フラグで区別
|
|
1514
|
-
self.addItem(line)
|
|
1515
|
-
# Never access self.data.atoms here for preview-only atoms
|
|
1503
|
+
# TemplatePreviewItemを使用して高機能なプレビューを描画
|
|
1504
|
+
self.template_preview.set_user_template_geometry(points, bonds_info, atoms)
|
|
1505
|
+
self.template_preview.show()
|
|
1506
|
+
if self.views():
|
|
1507
|
+
self.views()[0].viewport().update()
|
|
1516
1508
|
|
|
1517
1509
|
def leaveEvent(self, event):
|
|
1518
1510
|
self.template_preview.hide(); super().leaveEvent(event)
|
|
@@ -0,0 +1,204 @@
|
|
|
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
|
+
def add_export_action(self, label: str, callback: Callable):
|
|
69
|
+
"""
|
|
70
|
+
Register a custom export action.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
label: Text to display in the Export menu (e.g., "Export as MyFormat...").
|
|
74
|
+
callback: Function to call when triggered.
|
|
75
|
+
"""
|
|
76
|
+
self._manager.register_export_action(self._plugin_name, label, callback)
|
|
77
|
+
|
|
78
|
+
def register_optimization_method(self, method_name: str, callback: Callable[[Any], bool]):
|
|
79
|
+
"""
|
|
80
|
+
Register a custom 3D optimization method.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
method_name: Name of the method to display in 3D Optimization menu.
|
|
84
|
+
callback: Function taking (rdkit_mol) -> bool (success).
|
|
85
|
+
Modifies the molecule in-place.
|
|
86
|
+
"""
|
|
87
|
+
self._manager.register_optimization_method(self._plugin_name, method_name, callback)
|
|
88
|
+
|
|
89
|
+
def register_file_opener(self, extension: str, callback: Callable[[str], None]):
|
|
90
|
+
"""
|
|
91
|
+
Register a handler for opening a specific file extension.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
extension: File extension including dot, e.g. ".xyz".
|
|
95
|
+
callback: Function taking (file_path) -> None.
|
|
96
|
+
Should load the file into the main window.
|
|
97
|
+
"""
|
|
98
|
+
self._manager.register_file_opener(self._plugin_name, extension, callback)
|
|
99
|
+
|
|
100
|
+
def register_file_opener(self, extension: str, callback: Callable[[str], None]):
|
|
101
|
+
"""
|
|
102
|
+
Register a handler for opening a specific file extension.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
extension: File extension including dot, e.g. ".xyz".
|
|
106
|
+
callback: Function taking (file_path) -> None.
|
|
107
|
+
Should load the file into the main window.
|
|
108
|
+
"""
|
|
109
|
+
self._manager.register_file_opener(self._plugin_name, extension, callback)
|
|
110
|
+
|
|
111
|
+
def add_analysis_tool(self, label: str, callback: Callable):
|
|
112
|
+
"""
|
|
113
|
+
Register a tool in the Analysis menu.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
label: Text to display in the menu.
|
|
117
|
+
callback: Function to contact when triggered.
|
|
118
|
+
"""
|
|
119
|
+
self._manager.register_analysis_tool(self._plugin_name, label, callback)
|
|
120
|
+
|
|
121
|
+
def register_save_handler(self, callback: Callable[[], dict]):
|
|
122
|
+
"""
|
|
123
|
+
Register a callback to save state into the project file.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
callback: Function returning a dict of serializable data.
|
|
127
|
+
"""
|
|
128
|
+
self._manager.register_save_handler(self._plugin_name, callback)
|
|
129
|
+
|
|
130
|
+
def register_load_handler(self, callback: Callable[[dict], None]):
|
|
131
|
+
"""
|
|
132
|
+
Register a callback to restore state from the project file.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
callback: Function receiving the dict of saved data.
|
|
136
|
+
"""
|
|
137
|
+
def register_load_handler(self, callback: Callable[[dict], None]):
|
|
138
|
+
"""
|
|
139
|
+
Register a callback to restore state from the project file.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
callback: Function receiving the dict of saved data.
|
|
143
|
+
"""
|
|
144
|
+
self._manager.register_load_handler(self._plugin_name, callback)
|
|
145
|
+
|
|
146
|
+
def register_3d_context_menu(self, callback: Callable, label: str):
|
|
147
|
+
"""Deprecated: This method does nothing. Kept for backward compatibility."""
|
|
148
|
+
print(f"Warning: Plugin '{self._plugin_name}' uses deprecated 'register_3d_context_menu'. This API has been removed.")
|
|
149
|
+
|
|
150
|
+
def register_3d_style(self, style_name: str, callback: Callable[[Any, Any], None]):
|
|
151
|
+
"""
|
|
152
|
+
Register a custom 3D rendering style.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
style_name: Name of the style (must be unique).
|
|
156
|
+
callback: Function taking (main_window, mol) -> None.
|
|
157
|
+
Should fully handle drawing the molecule in the 3D view.
|
|
158
|
+
"""
|
|
159
|
+
self._manager.register_3d_style(self._plugin_name, style_name, callback)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def add_panel_button(self, text: str, callback: Callable, panel: str = "right"):
|
|
163
|
+
"""
|
|
164
|
+
Add a button to the bottom control panel.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
text: Label on the button.
|
|
168
|
+
callback: Function to call on click.
|
|
169
|
+
panel: "left" (2D Editor) or "right" (3D Viewer). Default "right".
|
|
170
|
+
"""
|
|
171
|
+
self._manager.register_panel_button(self._plugin_name, text, callback, panel)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class Plugin3DController:
|
|
176
|
+
"""Helper to manipulate the 3D scene."""
|
|
177
|
+
def __init__(self, main_window):
|
|
178
|
+
self._mw = main_window
|
|
179
|
+
|
|
180
|
+
def set_atom_color(self, atom_index: int, color_hex: str):
|
|
181
|
+
"""
|
|
182
|
+
Set the color of a specific atom in the 3D view.
|
|
183
|
+
Args:
|
|
184
|
+
atom_index: RDKit atom index.
|
|
185
|
+
color_hex: Hex string e.g., "#FF0000".
|
|
186
|
+
"""
|
|
187
|
+
# This will need to hook into the actual 3D view logic
|
|
188
|
+
if hasattr(self._mw, 'main_window_view_3d'):
|
|
189
|
+
# Logic to update color map and trigger redraw
|
|
190
|
+
# For now we can assume we might need to expose a method in view_3d
|
|
191
|
+
self._mw.main_window_view_3d.update_atom_color_override(atom_index, color_hex)
|
|
192
|
+
self._mw.plotter.render()
|
|
193
|
+
|
|
194
|
+
def set_bond_color(self, bond_index: int, color_hex: str):
|
|
195
|
+
"""
|
|
196
|
+
Set the color of a specific bond in the 3D view.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
bond_index: RDKit bond index.
|
|
200
|
+
color_hex: Hex string e.g., "#00FF00".
|
|
201
|
+
"""
|
|
202
|
+
if hasattr(self._mw, 'main_window_view_3d'):
|
|
203
|
+
self._mw.main_window_view_3d.update_bond_color_override(bond_index, color_hex)
|
|
204
|
+
self._mw.plotter.render()
|