Action plugin — complete reference
This notebook demonstrates all five event hooks available in the per-instance plugin system:
Hook |
Event |
Signature |
|---|---|---|
|
Right mouse-button press |
|
|
Mouse motion (any button) |
|
|
Left mouse-button press |
|
|
Key press |
|
|
OpenGL paint (after data, before UI) |
|
The plugin built here lets the user:
Right-click → add a marker point
Left-click → select / highlight the nearest previously added point
Mouse motion → show live coordinates in the status bar
Ctrl+Z → undo last marker
Ctrl+C → clear all markers
Paint hook → draw crosses on every marker in OpenGL
1 — wx startup
Important: always use%gui wxin a notebook — neverwx.App().wx.App()captures the event loop and blocks the kernel.
[1]:
import sys
%gui wx
2 — Create the MapViewer
[2]:
from wolfhece.PyDraw import WolfMapViewer
viewer = WolfMapViewer(None, title="Plugin complete demo", w=1200, h=800)
viewer.Show()
INFO:root:Importing wolfhece modules
INFO:root:wolfhece modules imported
INFO:wolfhece.tablet_wintab:WinTab : contexte ouvert sur HWND=0x20906F6 (pressure_max=32767)
[2]:
False
3 — Plugin state
All mutable state lives in plain Python objects — accessible from anywhere in the notebook.
[3]:
import numpy as np
from wolfhece._viewer_plugin_handlers import MouseContext, KeyboardSnapshot
# ── Persistent state ──────────────────────────────────────────────────────
markers: list[tuple[float, float]] = [] # list of (x, y) world positions
selected_idx: list[int] = [-1] # mutable box for the selected marker index
ACTION_ID = 'mark points' # action string — arbitrary lowercase
print(f"State initialised — action id: '{ACTION_ID}'")
State initialised — action id: 'mark points'
4 — Handler definitions
4a — Right-click: add a marker
[4]:
def _rdown_add_marker(v: WolfMapViewer, ctx: MouseContext) -> None:
"""Right-click adds a marker at the snapped cursor position."""
markers.append((ctx.x_snap, ctx.y_snap))
n = len(markers)
print(f"[Marker #{n}] X={ctx.x_snap:.3f} Y={ctx.y_snap:.3f}")
v.Paint() # force immediate redraw to show the new cross
4b — Left-click: select nearest marker
[5]:
def _ldown_select_nearest(v: WolfMapViewer, ctx: MouseContext) -> None:
"""Left-click selects the marker closest to the cursor."""
if not markers:
return
pts = np.array(markers)
dists = np.hypot(pts[:, 0] - ctx.x, pts[:, 1] - ctx.y)
idx = int(np.argmin(dists))
selected_idx[0] = idx
x, y = markers[idx]
print(f"Selected marker #{idx + 1} X={x:.3f} Y={y:.3f} (dist={dists[idx]:.1f})")
v.Paint()
4c — Mouse motion: live coordinates in status bar
[6]:
def _motion_show_coords(v: WolfMapViewer, ctx: MouseContext) -> None:
"""Display cursor world-coordinates in the viewer status bar."""
v.set_statusbar_text(f"X={ctx.x:.2f} Y={ctx.y:.2f} | {len(markers)} marker(s)")
4d — Key handler: Ctrl+Z undo, Ctrl+C clear
The key handler must return True to consume the event (prevent the default viewer behaviour).
[7]:
import wx
def _key_handler(v: WolfMapViewer, kb: KeyboardSnapshot) -> bool:
"""Ctrl+Z = undo last marker. Ctrl+C = clear all."""
if not kb.ctrl:
return False # let default processing handle non-ctrl keys
if kb.key_code == ord('Z'):
if markers:
removed = markers.pop()
if selected_idx[0] >= len(markers):
selected_idx[0] = len(markers) - 1
print(f"Undo — removed marker at X={removed[0]:.3f} Y={removed[1]:.3f}")
v.Paint()
return True # consumed
if kb.key_code == ord('C'):
markers.clear()
selected_idx[0] = -1
print("All markers cleared.")
v.Paint()
return True # consumed
return False # not our key — let default process it
4e — Paint hook: draw markers in OpenGL
_plotting()) but before the UI overlays (toolbar, palette, etc.).OpenGL.GL calls — the projection matrix is already set to world coordinates.[8]:
from OpenGL.GL import (
glBegin, glEnd, glVertex2f, glColor4f, glLineWidth,
GL_LINES,
)
# Cross size as a fraction of the visible width
CROSS_FRACTION = 0.008
def _paint_markers(v: WolfMapViewer) -> None:
"""Draw a cross at every marker; selected marker is highlighted."""
if not markers:
return
half = (v.xmax - v.xmin) * CROSS_FRACTION
glLineWidth(2.0)
glBegin(GL_LINES)
for i, (mx, my) in enumerate(markers):
if i == selected_idx[0]:
glColor4f(1.0, 0.8, 0.0, 1.0) # gold = selected
else:
glColor4f(1.0, 0.0, 0.0, 1.0) # red = normal
glVertex2f(mx - half, my); glVertex2f(mx + half, my) # horizontal bar
glVertex2f(mx, my - half); glVertex2f(mx, my + half) # vertical bar
glEnd()
glLineWidth(1.0)
5 — Register and activate
[9]:
viewer.register_action(
ACTION_ID,
rdown_handler = _rdown_add_marker,
ldown_handler = _ldown_select_nearest,
motion_handler = _motion_show_coords,
key_handler = _key_handler,
paint_handler = _paint_markers,
)
viewer.start_action(
ACTION_ID,
'Right-click: add marker | Left-click: select nearest | Ctrl+Z: undo | Ctrl+C: clear'
)
print(f"Action '{ACTION_ID}' registered and active.")
INFO:root:ACTION : Right-click: add marker | Left-click: select nearest | Ctrl+Z: undo | Ctrl+C: clear
Action 'mark points' registered and active.
[Marker #1] X=2.956 Y=20.453
[Marker #2] X=22.024 Y=25.672
Selected marker #1 X=2.956 Y=20.453 (dist=0.2)
Selected marker #2 X=22.024 Y=25.672 (dist=0.4)
Selected marker #1 X=2.956 Y=20.453 (dist=0.4)
6 — Inspect markers
[10]:
print(f"{len(markers)} marker(s) | selected index: {selected_idx[0]}")
for i, (x, y) in enumerate(markers):
flag = " ← selected" if i == selected_idx[0] else ""
print(f" {i+1:3d}. X={x:.3f} Y={y:.3f}{flag}")
2 marker(s) | selected index: 0
1. X=2.956 Y=20.453 ← selected
2. X=22.024 Y=25.672
7 — Export markers as numpy array
[11]:
if markers:
pts = np.array(markers, dtype=float)
print(f"Shape: {pts.shape}")
print(pts)
else:
print("No markers yet.")
Shape: (2, 2)
[[ 2.95636527 20.45272155]
[22.02393162 25.67233468]]
8 — Deactivate and clean up
[12]:
viewer.end_action('End mark points')
viewer.unregister_action(ACTION_ID)
print("Plugin deactivated and unregistered.")
INFO:root:ACTION : End mark points
Plugin deactivated and unregistered.
Appendix — Available hooks summary
viewer.register_action(
'my action',
# (viewer, MouseContext) -> None
rdown_handler = ..., # right mouse-button pressed
motion_handler = ..., # mouse moved (any button state)
ldown_handler = ..., # left mouse-button pressed
# (viewer, KeyboardSnapshot) -> bool
# Return True to consume the key event (prevents viewer default).
key_handler = ...,
# (viewer) -> None — raw OpenGL, world-coordinate projection active
# Called: after all _plotting() layers, before sculpt cursor + UI overlays.
paint_handler = ...,
)
MouseContext attributes
Attribute |
Type |
Description |
|---|---|---|
|
|
Raw world coordinates |
|
|
Grid-snapped world coordinates |
|
|
Screen pixel coordinates |
|
|
Keyboard modifiers |
|
|
Button states during motion |
|
|
Stylus pressure |
KeyboardSnapshot attributes
Attribute |
Type |
Description |
|---|---|---|
|
|
|
|
|
Modifiers |
|
|
|
|
|
All non-modifier keys currently held (polled) |