wolfhece.plugins.viewer_proxy

Technical runtime proxy used by companion plugins.

This proxy sits between companions and the viewer API. Companion code should prefer accessing viewer facilities through this object.

Temporary migration escape hatch

The underlying viewer remains reachable through the private _viewer attribute of this proxy so legacy companions can still access raw viewer features while dedicated helper methods are progressively added.

Module Contents

class wolfhece.plugins.viewer_proxy.ViewerProxy(name: str, viewer: WolfMapViewer | None = None, dialogs: wolfhece.dialog_provider.DialogProvider | None = None, namespace: str = '')[source]

Bridge object that hosts technical viewer/wx/dialog/OpenGL routines.

Companion subclasses can focus on business logic while this proxy owns the low-level plumbing and viewer integration details.

The proxy is the sole owner of both the viewer reference and the dialog provider. Companion code should access the viewer only through self.proxy._viewer (escape hatch) or dedicated helper methods.

_name: str[source]
_viewer: WolfMapViewer | None = None[source]
_dialogs: wolfhece.dialog_provider.DialogProvider = None[source]
_namespace: str = ''[source]
_menu: wx.Menu | None = None[source]
_menu_host: str = 'top_level'[source]
_menu_parent: wx.Menu | None = None[source]
_menu_parent_item: wx.MenuItem | None = None[source]
_registered_action_ids: list[str] = [][source]
_appended_items: list[tuple[wx.Menu, wx.MenuItem]] = [][source]
_multistep_specs: dict[str, wolfhece.plugins.types.MultiStepSpec][source]
_multistep_step_index: dict[str, int][source]
_active_multistep_action_id: str | None = None[source]
attach(viewer: wolfhece.PyDraw.WolfMapViewer, dialogs: wolfhece.dialog_provider.DialogProvider | None = None) None[source]

Attach (or re-attach) the proxy to a viewer after construction.

Useful when a companion is created without a viewer and the viewer becomes available later:

companion = MyCompanion()
...
companion.runtime.attach(viewer)
companion.runtime.activate()
Parameters:
  • viewer – The WolfMapViewer to bind.

  • dialogs – Optional dialog provider; when omitted the current provider is kept (or a default one is used if none was set yet).

Raises:

RuntimeError – If a different viewer is already attached.

detach() None[source]

Detach the proxy from the current viewer.

After calling this the companion is inert: all viewer-dependent helpers will fail until attach() is called again. The dialog provider is kept so that standalone dialogs (e.g. file pickers) still work.

_proxy_registry_key() str[source]

Return the key used to de-duplicate proxy instances on a viewer.

_get_proxy_registry() dict[str, ViewerProxy][source]

Return viewer-scoped proxy registry, creating it if needed.

_replace_previous_proxy_instance() None[source]

Replace a previous attached proxy with the same identity.

Notebook cells are often re-executed, creating a new companion instance while the old one is still attached. We proactively destroy and detach the previous proxy to keep registration/menu state idempotent.

action_id(local_id: str) str[source]

Return '{namespace}.{local_id}' — the companion’s namespaced action id.

Guarantees uniqueness across companions that independently pick the same local_id. Use this instead of building the string manually:

self.proxy.register_action('pick', ldown=self._ldown)
Parameters:

local_id – Short, human-readable name (e.g. 'pick', 'place wall').

Returns:

'{namespace}.{local_id}' — e.g. 'mycompanion.pick'.

build_menu(title: str, items: list[wolfhece.plugins.types.MenuEntry], *, host: str = 'top_level', companions_root_label: str | None = None) None[source]

Build a companion menu either as top-level or under “Companions”.

Menu item callbacks are always called with a synthetic MouseContext instead of a raw wx event. This keeps companion logic framework-agnostic.

_menu_registry_key(*, title: str, host: str) tuple[str, str, str][source]

Return a stable key for de-duplicating companion menus per viewer.

static _safe_isinstance(obj: object, type_or_tuple: object) bool[source]

Like isinstance(), but tolerant when test doubles replace wx classes.

_get_companion_menu_registry() dict[tuple[str, str, str], dict][source]

Return viewer-scoped companion menu registry, creating it if needed.

_replace_existing_companion_menu(*, title: str, host: str) None[source]

Remove an existing menu built by a previous companion instance.

This makes notebook re-execution idempotent: rebuilding a companion with the same identity (host + namespace + title) replaces the old menu instead of appending duplicates.

_register_companion_menu(*, title: str, host: str) None[source]

Store bookkeeping for later de-duplication and safe teardown.

_ensure_companions_root_menu(label: str) wx.Menu[source]

Return the shared top-level root menu used by companions.

_cleanup_companions_root_menu_if_empty() None[source]

Remove the shared companions root when no child submenus remain.

fill_wx_menu(menu: wx.Menu, items: list[wolfhece.plugins.types.MenuEntry]) None[source]

Populate a wx menu from declarative entries.

The proxy intercepts wx.EVT_MENU and forwards a synthetic mouse context to handlers.

_wrap_menu_handler(handler: Callable[Ellipsis, None]) Callable[source]

Adapt a wx menu callback to a MouseContext callback.

Companion handlers stay independent from wx by receiving a synthetic MouseContext.

_menu_event_to_mouse_context(event) wolfhece._viewer_plugin_handlers.MouseContext[source]

Build a synthetic MouseContext from a wx menu event.

Strategy: 1. Reuse the viewer’s latest cached mouse context when available. 2. Fall back to a neutral origin context when no cache exists.

append_to_menu(menu: wx.Menu, items: list[wolfhece.plugins.types.MenuEntry]) None[source]

Append declarative entries to an existing wx menu.

The proxy records the appended items so menu_destroy() can later remove them cleanly.

append_to_create_menu(items: list[wolfhece.plugins.types.MenuEntry]) None[source]

Append entries to the viewer’s create-object menu.

append_to_add_menu(items: list[wolfhece.plugins.types.MenuEntry]) None[source]

Append entries to the viewer’s add-object menu.

_require_viewer() wolfhece.PyDraw.WolfMapViewer[source]

Return the attached viewer or raise when the proxy is detached.

Raises:

RuntimeError – If no viewer is currently attached.

property active_array[source]

Return the viewer’s active array, or None when unset.

property active_matrix[source]

Alias for active_array.

The viewer code historically uses “array” while some companions and docs still talk about matrices.

property active_zones[source]

Return the viewer’s active zones, or None when unset.

property active_triangulation[source]

Return the viewer’s active triangulation, or None when unset.

get_array(id: str | None = None)[source]

Return the active array or a named array from the viewer.

get_matrix(id: str | None = None)[source]

Alias for get_array().

Kept for companions / docs that still use the matrix wording.

get_zones(id: str | None = None)[source]

Return the active zones or a named zones object from the viewer.

get_triangulation(id: str | None = None)[source]

Return the active triangulation or a named triangulation.

add_array(newobj=None, *, filename: str = '', ToCheck: bool = True, id: str = '') int[source]

Add an array to the viewer and return the viewer’s add result.

add_matrix(newobj=None, *, filename: str = '', ToCheck: bool = True, id: str = '') int[source]

Alias for add_array().

add_zones(newobj=None, *, filename: str = '', ToCheck: bool = True, id: str = '') int[source]

Add a zones object to the viewer and return the add result.

add_triangulation(newobj=None, *, filename: str = '', ToCheck: bool = True, id: str = '') int[source]

Add a triangulation to the viewer and return the add result.

destroy() None[source]

Unregister actions, remove menus, and detach the proxy.

This is the main teardown hook used by the plugin manager and by the notebook re-execution cleanup path.

menu_destroy() None[source]

Remove menus and appended items that were registered by the proxy.

_resolve_action_id(action_id: str | ActionKind) str[source]

Return a fully-qualified (namespaced) action id string.

If action_id is an ActionKind enum it is converted to its value. If the resulting string contains no '.', it is treated as a local id and the companion namespace is prepended automatically:

rt._resolve_action_id('pick')          # → 'mycompanion.pick'
rt._resolve_action_id('mycompanion.pick')  # → 'mycompanion.pick'
rt._resolve_action_id(ActionKind.SCULPT)   # → 'sculpt' (enum value kept)

This means companion code can simply write self.proxy.register_action('pick', …) without a preceding self.proxy.action_id('pick') call.

resolve_action_id(action_id: str | ActionKind) str[source]

Public resolver for local/namespaced action ids.

Declarative objects (e.g. MultiStepAction) should use this method instead of duplicating namespacing logic.

static _accepts_n_or_more_positional(fn: Callable, n: int) bool[source]

Return True when fn can consume at least n positional args.

_adapt_mouse_handler(handler: Callable | None) Callable | None[source]

Adapt mouse handlers to support both (viewer, ctx) and (ctx).

_adapt_key_handler(handler: Callable | None) Callable | None[source]

Adapt key handlers to support both (viewer, kb) and (kb).

_adapt_paint_handler(handler: Callable | None) Callable | None[source]

Adapt paint handlers to support both (viewer) and ().

register_action(action_id: str | wolfhece._action_kind.ActionKind, *, rdown: Callable | None = None, motion: Callable | None = None, ldown: Callable | None = None, key: Callable | None = None, paint: Callable | None = None, overload: bool = False) None[source]

Register interactive handlers for an action id.

Supported callback signatures:

  • rdown/motion/ldown: (viewer, ctx) or (ctx)

  • key: (viewer, kb) or (kb)

  • paint: (viewer) or ()

This allows companion code to ignore direct viewer access and rely on self.proxy for viewer interactions.

register_actions(specs: Iterable[ActionSpec | MultiStepSpec]) None[source]

Register a declarative batch of ActionSpec and/or MultiStepSpec.

register_multistep_action(spec: wolfhece.plugins.types.MultiStepSpec) None[source]

Register one declarative multi-step action with runtime step dispatch.

_current_multistep_step(action_id: str)[source]

Return the currently active step object for a multi-step action.

_coerce_transition(result) object[source]

Normalize a handler return value into a StepTransition.

_apply_multistep_transition(action_id: str, result) None[source]

Apply a handler-driven transition to a multi-step action.

_advance_multistep(action_id: str) None[source]

Advance a multi-step action to its next step when possible.

_finish_multistep(action_id: str) None[source]

Finish a multi-step action and reset its runtime state.

_cancel_multistep(action_id: str) None[source]

Cancel a multi-step action and clear its runtime state.

_build_multistep_mouse_dispatch(action_id: str, event_name: str) Callable[source]

Build a mouse-event dispatcher for one step-driven action.

_build_multistep_key_dispatch(action_id: str) Callable[source]

Build a keyboard-event dispatcher for one step-driven action.

_build_multistep_paint_dispatch(action_id: str) Callable[source]

Build a paint dispatcher for one step-driven action.

unregister_action(action_id: str | wolfhece._action_kind.ActionKind) None[source]

Unregister a previously registered action id.

start_action(action_id: str | wolfhece._action_kind.ActionKind, message: str = '') None[source]

Start an interaction on the viewer, honoring multi-step actions.

end_action(message: str = '') None[source]

End the current viewer interaction.

require_active_array() bool[source]

Warn and return False when no active array is selected.

static require(condition: bool, warning: str) bool[source]

Emit a warning and return the truth value of condition.

show_error(message: str, title: str = '') None[source]

Show an error dialog.

Parameters:
  • message – Message body shown to the user.

  • title – Optional dialog title.

show_info(message: str, title: str = '') None[source]

Show an informational dialog.

confirm(message: str, title: str = '', default: str = 'yes') bool[source]

Ask the user for a yes/no style confirmation.

ask_text(message: str, title: str = '', default: str = '') str | None[source]

Ask the user for free-form text.

ask_float(message: str, title: str = '', default: float | str = '') float | None[source]

Ask the user for a floating-point value.

ask_integer(message: str, title: str = '', prompt: str = '', default: int = 0, min_value: int = 0, max_value: int = 0) int | None[source]

Ask the user for an integer value.

ask_single_choice(message: str, title: str, choices: list[str], preselected: int | None = None) str | None[source]

Ask the user to pick one value from a finite list of choices.

ask_file_open(message: str, wildcard: str = 'all (*.*)|*.*', default_path: str = '') str | None[source]

Open a file-selection dialog and return the chosen path.

ask_file_save(message: str, wildcard: str = 'all (*.*)|*.*', default_path: str = '', default_file: str = '') str | None[source]

Open a save-file dialog and return the chosen path.

ask_directory(message: str, default_path: str = '') str | None[source]

Open a directory picker and return the chosen path.

run_with_progress(title: str, steps: list[tuple[int, str, Callable]]) None[source]

Run a list of callables while updating a progress dialog.

set_status(message: str) None[source]

Set the viewer status bar text.

force_redraw() None[source]

Trigger a repaint of the viewer canvas.

viewport_fraction(fraction: float = 0.008) float[source]

Return a fraction of the current viewport width.

static draw_crosses(points: Iterable[tuple[float, float]], half_size: float, color: tuple[float, float, float, float] = (1.0, 0.0, 0.0, 1.0), *, selected_idx: int = -1, selected_color: tuple[float, float, float, float] = (1.0, 0.8, 0.0, 1.0), line_width: float = 2.0) None[source]

Draw cross markers at the given points using OpenGL primitives.

static draw_polyline(points: Iterable[tuple[float, float]], color: tuple[float, float, float, float] = (0.0, 0.5, 1.0, 1.0), *, closed: bool = False, line_width: float = 1.5) None[source]

Draw a polyline or closed loop using OpenGL primitives.

static draw_segments(segments: Iterable[tuple[float, float, float, float]], color: tuple[float, float, float, float] = (1.0, 1.0, 0.0, 1.0), *, line_width: float = 1.5) None[source]

Draw independent line segments using OpenGL primitives.

static draw_points(points: Iterable[tuple[float, float]], color: tuple[float, float, float, float] = (1.0, 0.0, 0.0, 1.0), *, point_size: float = 4.0) None[source]

Draw points using OpenGL point primitives.