wolfhece.plugins.abc ==================== .. py:module:: wolfhece.plugins.abc .. autoapi-nested-parse:: Abstract companion base class. This module intentionally focuses on :class:`AbstractUICompanion`. Related helpers now live in dedicated modules: - :mod:`wolfhece.plugins.types` for declarative menu/action dataclasses, - :mod:`wolfhece.plugins.state` for resettable state containers, - :mod:`wolfhece.plugins.actions` for interactive action helpers. For backward compatibility, their main symbols are re-exported here. Module Contents --------------- .. py:class:: AbstractCompanionModel Bases: :py:obj:`abc.ABC` .. autoapi-inheritance-diagram:: wolfhece.plugins.abc.AbstractCompanionModel :parts: 1 :private-bases: Pure business model contract for companion plugins. Keep all business state and domain transitions in a model class that does not depend on wx/viewer/OpenGL APIs. The UI companion can then compose this model and stay focused on event wiring and rendering. This default contract is intentionally lightweight to support plugins that only need simple dataclasses. Override :meth:`reset` when your model must clear transient state. .. py:method:: reset() -> None Reset transient domain state (no-op by default). .. py:class:: AbstractUICompanion Bases: :py:obj:`abc.ABC` .. autoapi-inheritance-diagram:: wolfhece.plugins.abc.AbstractUICompanion :parts: 1 :private-bases: Abstract base class for companion objects that integrate with WolfMapViewer. Viewer access policy -------------------- Companion code should use :attr:`proxy` as the primary entry point for viewer integration (actions, menus, dialogs, redraw, OpenGL helpers). During the migration period, direct viewer access is still possible via the private bridge attribute ``self.proxy._viewer`` for advanced cases where no dedicated helper exists yet. A *companion* is an object that: 1. Owns the transient state needed by its interactive actions. 2. Registers (and later cleans up) custom mouse/keyboard handlers in the viewer's action dispatch tables. 3. Optionally appends its own ``wx.Menu`` to the viewer's menu bar (declare it through :meth:`menu_spec`). Subclasses **must** implement :meth:`start`. All other methods are either concrete helpers or optional overrides. In particular, :meth:`menu_spec` has a no-op default — companions that interact purely via mouse/keyboard events do not need a menu. .. py:attribute:: proxy :type: wolfhece.plugins.viewer_proxy.ViewerProxy .. py:attribute:: model :type: AbstractCompanionModel | None .. py:method:: create_model() -> AbstractCompanionModel | None Create the companion's optional pure business model. Override this to keep domain logic independent from UI concerns while staying in the same plugin module (companion + model in one file). Default implementation returns ``None``. .. py:method:: get_namespace() -> str Return the namespace string used to prefix action ids. The default is the companion's class name lowercased. Override in a subclass to choose a different prefix:: class MyCompanion(AbstractUICompanion): def get_namespace(self) -> str: return 'myfeature' .. py:method:: configure() Configure the companion's runtime behaviour. .. py:method:: menu_spec() -> tuple[str, list[MenuEntry]] | None Declare the companion's menu as a ``(title, items)`` pair. Override this to add a menu without writing wx code:: def menu_spec(self) -> tuple[str, list[MenuEntry]] | None: return (_('My Tool'), [ MenuItem(_('Run'), self._on_run, _('Execute the tool')), SEPARATOR, SubMenuSpec(_('Settings'), [ MenuItem(_('Configure…'), self._on_configure), ]), ]) Return ``None`` (the default) when no top-level menu is needed. Called automatically by :meth:`build`. Do not call directly. .. py:method:: menu_host() -> str Return where this companion menu should be hosted. Supported values are: - ``'companions_root'``: create a submenu under the shared viewer menu "Companions" (default). - ``'top_level'``: create a direct top-level menu entry in the viewer menubar. Override this in companions that must remain top-level. .. py:method:: actions_spec() -> Iterable[ActionSpec | MultiStepSpec] | None Declare interactive handlers to register with the viewer. Override this method declaratively by returning :class:`ActionSpec` and/or :class:`MultiStepSpec` rows. The proxy performs registration and handler adaptation:: def actions_spec(self): return [ ActionSpec(self._pick.action_id, ldown=self._ldown, key=self._key), MultiStepSpec( 'wall', steps=[ StepSpec(hint='Pick first point', ldown=self._step1), StepSpec(hint='Pick second point', ldown=self._step2), ], ), ] For :class:`MultiStepSpec`, handlers can return :class:`StepTransition` values (or equivalent strings) to drive the runtime state machine. Return ``None`` (or an empty iterable) when there is no action to register. Called automatically by :meth:`build`. Do not call directly. .. py:method:: start() -> None Activate the companion's primary interactive action. Default behaviour is declarative and driven by :meth:`actions_spec`: - select the :class:`ActionSpec` marked ``primary=True``, - otherwise fall back to the first declared :class:`ActionSpec`, - call :meth:`ViewerProxy.start_action` with its ``action_id`` and optional ``start_message``. Override this method when startup needs additional business logic. :raises RuntimeError: If no actions are declared or more than one :class:`ActionSpec` is marked primary. .. py:method:: stop() -> None Deactivate the companion's primary interactive action. The default implementation calls :meth:`proxy.end_action`. Override to add post-processing (e.g. printing a summary):: def stop(self) -> None: self.proxy.end_action() print(f"{len(self.points)} item(s) recorded.") .. py:method:: build() -> None Build and attach the companion's menu to the viewer menubar. The default implementation calls :meth:`menu_spec` to build any declared menu, then :meth:`actions_spec` to wire up interactive handlers. Override this method directly only for advanced cases that the two hooks cannot express. **Preferred approach** — override :meth:`menu_spec` and/or :meth:`actions_spec`:: def menu_spec(self) -> tuple[str, list[MenuEntry]] | None: return (_('My Feature'), [ MenuItem(_('Do something'), self._on_do), SEPARATOR, SubMenuSpec(_('Tools'), [ MenuItem(_('Export'), self._on_export), ]), ]) def actions_spec(self): return [ ActionSpec(self._pick.action_id, ldown=self._ldown), ] **Advanced — full wx control** (bypass both hooks):: def build(self) -> None: if self.proxy._menu is not None: return import wx self.proxy._menu = wx.Menu() ... self.proxy.register_action(...) Implementations **must** be idempotent. .. py:method:: destroy() -> None Unregister all custom actions, remove the companion's menu, and release viewer resources. Call this when the companion is no longer needed. The method is safe to call multiple times. Override this method to add companion-specific teardown:: def destroy(self) -> None: # custom pre-teardown ... super().destroy() :raises RuntimeError: Never — all wx errors are caught and logged. .. py:method:: _iter_action_specs() -> Iterable[ActionSpec] Yield action specs normalized to :class:`ActionSpec`. ``MultiStepSpec`` entries are normalized to a synthetic :class:`ActionSpec` for primary-action selection logic. .. py:method:: _primary_action_spec() -> ActionSpec | None Return the action spec used by the default :meth:`start` method. Selection rules: 1. If one spec has ``primary=True``, use it. 2. If none are marked primary, use the first declared spec. 3. If multiple specs are marked primary, raise ``RuntimeError``.