Source code for wolfhece.plugins.factory

"""Pre-built companion classes for common interactive-selection patterns.

All four classes are ready to use as-is or to subclass.  Override the
``COLOR_*`` / ``*_FRACTION`` class attributes to change the visual style
without writing any method code.

Quick-start
-----------
::

    from wolfhece._plugin_factory import point_picker, polyline, multi_polyline, polygon

    # One-liner: create + register + activate
    comp = point_picker(viewer)
    # … right-click on the map …
    print(comp.points)
    comp.destroy()

Interaction summary
-------------------

+-------------------------+------------------+-------------------+------------------+
| Companion               | Add vertex       | Finish / accept   | Cancel / stop    |
+=========================+==================+===================+==================+
| PointPickerCompanion    | Right-click      | (already done)    | Esc or stop()    |
+-------------------------+------------------+-------------------+------------------+
| PolylineCompanion       | Right-click      | Enter (≥ 2 pts)   | Esc (discard)    |
+-------------------------+------------------+-------------------+------------------+
| MultiPolylineCompanion  | Right-click      | Enter (≥ 2 pts)   | Esc stops action |
+-------------------------+------------------+-------------------+------------------+
| PolygonCompanion        | Right-click      | Enter (≥ 3 pts)   | Esc (discard)    |
+-------------------------+------------------+-------------------+------------------+

All companions:

* ``left-click`` selects the nearest already-placed vertex (PointPickerCompanion only)
* ``Ctrl+Z`` undoes the last vertex (PointPickerCompanion only)
* ``comp.stop()``  deactivates the action; collected data is preserved
* ``comp.destroy()``  deactivates + unregisters all handlers
"""
from __future__ import annotations

import logging
from dataclasses import dataclass, field
from typing import TYPE_CHECKING

_logger = logging.getLogger(__name__)

import numpy as np

from .abc import AbstractCompanionModel, AbstractUICompanion, ActionSpec
from .types import Keys
from .._viewer_plugin_handlers import MouseContext, KeyboardSnapshot

if TYPE_CHECKING:
    from ..PyDraw import WolfMapViewer

__all__ = [
    'PointPickerCompanion',
    'PolylineCompanion',
    'MultiPolylineCompanion',
    'MultiPolylineZonesCompanion',
    'PolygonCompanion',
    'point_picker',
    'polyline',
    'multi_polyline',
    'multi_polyline_zones',
    'polygon',
]

@dataclass
class PointPickerModel(AbstractCompanionModel):
    points: list[tuple[float, float]] = field(default_factory=list)
    selected: int = -1

    def reset(self) -> None:
        self.points.clear()
        self.selected = -1


@dataclass
class PolylineModel(AbstractCompanionModel):
    vertices: list[tuple[float, float]] = field(default_factory=list)
    finished: bool = False

    def reset(self) -> None:
        self.vertices.clear()
        self.finished = False


@dataclass
class MultiPolylineModel(AbstractCompanionModel):
    polylines: list[list[tuple[float, float]]] = field(default_factory=list)
    current: list[tuple[float, float]] = field(default_factory=list)

    def reset(self) -> None:
        self.polylines.clear()
        self.current.clear()


@dataclass
class MultiPolylineZonesModel(AbstractCompanionModel):
    zones: 'Zones | None' = None  # type: ignore[name-defined]
    current: list[tuple[float, float]] = field(default_factory=list)

    def reset(self) -> None:
        self.zones = None
        self.current.clear()


@dataclass
class PolygonModel(AbstractCompanionModel):
    polygons: list[list[tuple[float, float]]] = field(default_factory=list)
    current: list[tuple[float, float]] = field(default_factory=list)

    def reset(self) -> None:
        self.polygons.clear()
        self.current.clear()


# ---------------------------------------------------------------------------
# PointPickerCompanion
# ---------------------------------------------------------------------------

[docs] class PointPickerCompanion(AbstractUICompanion): """Collect isolated (x, y) points via right-click. Interaction ----------- * **Right-click** — add a point at the snapped cursor position * **Left-click** — select the nearest point (highlighted in gold) * **Ctrl+Z** — remove the last point * **Esc** — deactivate the action; collected points are preserved Attributes ---------- points : list[tuple[float, float]] Collected world-coordinate pairs (snapped). selected : int Index of the highlighted point (-1 = none). Customisation ------------- Override the class attributes to change colours/size without subclassing:: class MyPicker(PointPickerCompanion): COLOR_NORMAL = (0.0, 0.6, 1.0, 1.0) # blue COLOR_SELECTED = (1.0, 1.0, 0.0, 1.0) # yellow CROSS_FRACTION = 0.015 """ #: RGBA colour for unselected points.
[docs] COLOR_NORMAL: tuple = (1.0, 0.0, 0.0, 1.0)
#: RGBA colour for the selected point.
[docs] COLOR_SELECTED: tuple = (1.0, 0.8, 0.0, 1.0)
#: Cross arm-length as a fraction of viewport width.
[docs] CROSS_FRACTION: float = 0.01
[docs] def create_model(self) -> PointPickerModel: return PointPickerModel()
@property
[docs] def points(self) -> list[tuple[float, float]]: return self.model.points # type: ignore[union-attr]
@points.setter def points(self, value: list[tuple[float, float]]) -> None: self.model.points = value # type: ignore[union-attr] @property
[docs] def selected(self) -> int: return self.model.selected # type: ignore[union-attr]
@selected.setter def selected(self, value: int) -> None: self.model.selected = value # type: ignore[union-attr]
[docs] def actions_spec(self): return [ ActionSpec( 'pick', rdown=self._rdown, ldown=self._ldown, key=self._key, paint=self._paint, primary=True, start_message='Right-click: add | Left-click: select nearest | Ctrl+Z: undo | Esc: stop', ), ]
[docs] def start(self) -> None: """Activate the point-picker action.""" self.proxy.start_action( 'pick', 'Right-click: add | Left-click: select nearest | Ctrl+Z: undo | Esc: stop', )
# -- handlers ------------------------------------------------------------
[docs] def _rdown(self, ctx: MouseContext) -> None: self.points.append((ctx.x_snap, ctx.y_snap)) self.selected = len(self.points) - 1 self.proxy.force_redraw()
[docs] def _ldown(self, ctx: MouseContext) -> None: if not self.points: return pts = np.array(self.points) dists = np.hypot(pts[:, 0] - ctx.x, pts[:, 1] - ctx.y) self.selected = int(np.argmin(dists)) self.proxy.force_redraw()
[docs] def _key(self, kb: KeyboardSnapshot) -> bool: if kb.key_code == Keys.ESCAPE: self.stop() return True if kb.ctrl and kb.key_code == ord('Z'): if self.points: self.points.pop() if self.selected >= len(self.points): self.selected = len(self.points) - 1 self.proxy.force_redraw() return True return False
[docs] def _paint(self) -> None: self.proxy.draw_crosses( self.points, self.proxy.viewport_fraction(self.CROSS_FRACTION), color=self.COLOR_NORMAL, selected_idx=self.selected, selected_color=self.COLOR_SELECTED, )
# --------------------------------------------------------------------------- # PolylineCompanion # ---------------------------------------------------------------------------
[docs] class PolylineCompanion(AbstractUICompanion): """Record a single polyline vertex by vertex. Interaction ----------- * **Right-click** — append a vertex * **Enter / Return** — finalise the polyline (requires ≥ 2 vertices) * **Esc** — discard all vertices and deactivate Attributes ---------- vertices : list[tuple[float, float]] Ordered vertices of the recorded polyline. Empty after a cancelled interaction, populated after a successful Enter. finished : bool ``True`` once Enter has been pressed with ≥ 2 vertices. Customisation ------------- Override ``COLOR_LINE``, ``COLOR_VERTEX``, ``CROSS_FRACTION``, ``LINE_WIDTH`` at the class level. """ #: RGBA colour for the line segments.
[docs] COLOR_LINE: tuple = (0.0, 0.5, 1.0, 1.0)
#: RGBA colour for the vertex markers.
[docs] COLOR_VERTEX: tuple = (1.0, 1.0, 1.0, 1.0)
#: Vertex cross size as a fraction of viewport width.
[docs] CROSS_FRACTION: float = 0.008
#: OpenGL line width in pixels.
[docs] LINE_WIDTH: float = 2.0
[docs] def create_model(self) -> PolylineModel: return PolylineModel()
@property
[docs] def vertices(self) -> list[tuple[float, float]]: return self.model.vertices # type: ignore[union-attr]
@vertices.setter def vertices(self, value: list[tuple[float, float]]) -> None: self.model.vertices = value # type: ignore[union-attr] @property
[docs] def finished(self) -> bool: return self.model.finished # type: ignore[union-attr]
@finished.setter def finished(self, value: bool) -> None: self.model.finished = value # type: ignore[union-attr]
[docs] def actions_spec(self): return [ ActionSpec( 'line', rdown=self._rdown, key=self._key, paint=self._paint, primary=True, start_message='Right-click: add vertex | Enter: finalise (≥2 pts) | Esc: cancel', ), ]
[docs] def start(self) -> None: """Reset state and activate the polyline-recording action.""" self.vertices.clear() self.finished = False self.proxy.start_action( 'line', 'Right-click: add vertex | Enter: finalise (≥2 pts) | Esc: cancel', )
# -- handlers ------------------------------------------------------------
[docs] def _rdown(self, ctx: MouseContext) -> None: self.vertices.append((ctx.x_snap, ctx.y_snap)) self.proxy.force_redraw()
[docs] def _key(self, kb: KeyboardSnapshot) -> bool: if kb.key_code == Keys.RETURN: if len(self.vertices) >= 2: self.finished = True else: self.proxy.set_status('Need at least 2 vertices to finalise.') self.stop() return True if kb.key_code == Keys.ESCAPE: self.vertices.clear() self.finished = False self.stop() return True return False
[docs] def _paint(self) -> None: if len(self.vertices) >= 2: self.proxy.draw_polyline( self.vertices, self.COLOR_LINE, line_width=self.LINE_WIDTH, ) half = self.proxy.viewport_fraction(self.CROSS_FRACTION) self.proxy.draw_crosses(self.vertices, half, color=self.COLOR_VERTEX)
# --------------------------------------------------------------------------- # MultiPolylineCompanion # ---------------------------------------------------------------------------
[docs] class MultiPolylineCompanion(AbstractUICompanion): """Record multiple polylines in a single session. Interaction ----------- * **Right-click** — append a vertex to the current in-progress line * **Enter / Return** — finalise the current line (≥ 2 vertices) and start a new one * **Esc** — discard the current in-progress line and deactivate; already-finalised lines are preserved Attributes ---------- polylines : list[list[tuple[float, float]]] All finalised polylines. current : list[tuple[float, float]] Vertices of the line currently being drawn. """ #: RGBA colour for finalised lines.
[docs] COLOR_DONE: tuple = (0.2, 0.6, 1.0, 1.0)
#: RGBA colour for the in-progress line.
[docs] COLOR_CURRENT: tuple = (1.0, 0.5, 0.0, 1.0)
#: RGBA colour for vertex markers.
[docs] COLOR_VERTEX: tuple = (1.0, 1.0, 1.0, 0.8)
#: Vertex cross size as a fraction of viewport width.
[docs] CROSS_FRACTION: float = 0.007
#: OpenGL line width in pixels.
[docs] LINE_WIDTH: float = 1.5
[docs] def create_model(self) -> MultiPolylineModel: return MultiPolylineModel()
@property
[docs] def polylines(self) -> list[list[tuple[float, float]]]: return self.model.polylines # type: ignore[union-attr]
@polylines.setter def polylines(self, value: list[list[tuple[float, float]]]) -> None: self.model.polylines = value # type: ignore[union-attr] @property
[docs] def current(self) -> list[tuple[float, float]]: return self.model.current # type: ignore[union-attr]
@current.setter def current(self, value: list[tuple[float, float]]) -> None: self.model.current = value # type: ignore[union-attr]
[docs] def actions_spec(self): return [ ActionSpec( 'lines', rdown=self._rdown, key=self._key, paint=self._paint, primary=True, start_message='Right-click: vertex | Enter: next line | Esc: finish session', ), ]
[docs] def start(self) -> None: """Reset state and activate the multi-polyline recording action.""" self.polylines.clear() self.current.clear() self.proxy.start_action( 'lines', 'Right-click: vertex | Enter: next line | Esc: finish session', )
# -- handlers ------------------------------------------------------------
[docs] def _rdown(self, ctx: MouseContext) -> None: self.current.append((ctx.x_snap, ctx.y_snap)) self.proxy.force_redraw()
[docs] def _key(self, kb: KeyboardSnapshot) -> bool: if kb.key_code == Keys.RETURN: if len(self.current) >= 2: self.polylines.append(list(self.current)) self.current.clear() self.proxy.force_redraw() n = len(self.polylines) self.proxy.set_status( f'Line {n} recorded — right-click to start line {n + 1} | Esc: finish' ) else: self.proxy.set_status('Need at least 2 vertices — keep clicking.') return True if kb.key_code == Keys.ESCAPE: self.current.clear() self.proxy.force_redraw() self.stop() return True return False
[docs] def _paint(self) -> None: for line in self.polylines: if len(line) >= 2: self.proxy.draw_polyline(line, self.COLOR_DONE, line_width=self.LINE_WIDTH) if len(self.current) >= 2: self.proxy.draw_polyline(self.current, self.COLOR_CURRENT, line_width=self.LINE_WIDTH) half = self.proxy.viewport_fraction(self.CROSS_FRACTION) all_pts = [p for line in self.polylines for p in line] + self.current if all_pts: self.proxy.draw_crosses(all_pts, half, color=self.COLOR_VERTEX)
# --------------------------------------------------------------------------- # MultiPolylineZonesCompanion # ---------------------------------------------------------------------------
[docs] class MultiPolylineZonesCompanion(AbstractUICompanion): """Record multiple polylines and store them in a :class:`~wolfhece.pyvertexvectors.Zones` object. Behaves like :class:`MultiPolylineCompanion` for the interaction, but instead of keeping the results in a plain Python list each accepted polyline is immediately written into a ``Zones`` → ``zone`` → ``vector`` hierarchy and — when *auto_attach* is enabled — added to the viewer so that it appears in the layers panel and can be exported. Interaction ----------- * **Right-click** — append a vertex to the current in-progress line * **Enter / Return** — finalise the current line (≥ 2 vertices); it is added to the :attr:`zones` object as a new ``zone`` * **Esc** — discard the current in-progress vertices and stop the action; already-finalised lines in :attr:`zones` are preserved Attributes ---------- zones : Zones | None The backing :class:`~wolfhece.pyvertexvectors.Zones` object. ``None`` until the first line is accepted. Once created it is the same object that is attached to the viewer (when *auto_attach* is ``True``). current : list[tuple[float, float]] Vertices of the line currently being digitised. Parameters ---------- zones_id : str Identifier string for the :class:`~wolfhece.pyvertexvectors.Zones` object. Defaults to ``'multi_polyline'``. auto_attach : bool When ``True`` (default) the :class:`~wolfhece.pyvertexvectors.Zones` object is added to the viewer via ``viewer.add_object('vector', …)`` on the first accepted polyline. zone_name_prefix : str Prefix for the generated zone names (``zone_001``, ``zone_002``, …). Customisation ------------- Override the class attributes to change colours/line width:: class MyZonesCompanion(MultiPolylineZonesCompanion): COLOR_DONE = (0.9, 0.2, 0.2, 1.0) COLOR_CURRENT = (1.0, 0.8, 0.0, 1.0) """ #: RGBA colour for lines that have already been accepted into the Zones.
[docs] COLOR_DONE: tuple = (0.2, 0.6, 1.0, 1.0)
#: RGBA colour for the line currently being digitised.
[docs] COLOR_CURRENT: tuple = (1.0, 0.5, 0.0, 1.0)
#: RGBA colour for vertex cross markers.
[docs] COLOR_VERTEX: tuple = (1.0, 1.0, 1.0, 0.8)
#: Vertex cross size as a fraction of viewport width.
[docs] CROSS_FRACTION: float = 0.007
#: OpenGL line width in pixels.
[docs] LINE_WIDTH: float = 1.5
def __init__( self, *, zones_id: str = 'multi_polyline', auto_attach: bool = True, zone_name_prefix: str = 'zone', ) -> None:
[docs] self._zones_id = zones_id
[docs] self._auto_attach = auto_attach
[docs] self._zone_name_prefix = zone_name_prefix
[docs] def create_model(self) -> MultiPolylineZonesModel: return MultiPolylineZonesModel()
@property
[docs] def zones(self) -> 'Zones | None': # type: ignore[name-defined] return self.model.zones # type: ignore[union-attr]
@zones.setter def zones(self, value: 'Zones | None') -> None: # type: ignore[name-defined] self.model.zones = value # type: ignore[union-attr] @property
[docs] def current(self) -> list[tuple[float, float]]: return self.model.current # type: ignore[union-attr]
@current.setter def current(self, value: list[tuple[float, float]]) -> None: self.model.current = value # type: ignore[union-attr] # -- AbstractUICompanion interface ---------------------------------------
[docs] def actions_spec(self): return [ ActionSpec( 'mplz', rdown=self._rdown, key=self._key, paint=self._paint, primary=True, start_message='Right-click: vertex | Enter: accept line (≥2 pts) | Esc: stop', ), ]
[docs] def start(self) -> None: """Activate the multi-polyline action. Only *current* is cleared; any already-accepted lines in :attr:`zones` are preserved so that the session can be resumed. """ self.current.clear() self.proxy.start_action( 'mplz', 'Right-click: vertex | Enter: accept line (≥2 pts) | Esc: stop', )
# -- public API ----------------------------------------------------------
[docs] def attach_zones(self, id: str | None = None) -> 'Zones | None': # type: ignore[name-defined] """Manually add :attr:`zones` to the viewer. This is a no-op when *auto_attach* is ``True`` (attachment already happened) or when :attr:`zones` is ``None`` (no line accepted yet). :param id: Override the id used when registering with the viewer (defaults to the *zones_id* given at construction time). :return: The :class:`~wolfhece.pyvertexvectors.Zones` object, or ``None`` if nothing has been digitised yet. """ if self.zones is None: return None oid = id if id is not None else self._zones_id try: self.proxy._viewer.add_object('vector', newobj=self.zones, ToCheck=True, id=oid) except Exception as exc: _logger.warning("attach_zones: could not add object to viewer: %s", exc) return self.zones
[docs] def clear_zones(self) -> None: """Discard all accepted lines and reset :attr:`zones` to ``None``. This does *not* remove the object from the viewer if it was already attached — call ``viewer.get_obj_from_id(…)`` and remove it manually if needed. """ self.zones = None self.current.clear() self.proxy.force_redraw()
# -- private helpers -----------------------------------------------------
[docs] def _ensure_zones(self) -> 'Zones': # type: ignore[name-defined] """Return the Zones object, creating (and optionally attaching) it on the first call.""" if self.zones is None: from ..pyvertexvectors import Zones self.zones = Zones(idx=self._zones_id, mapviewer=self.proxy._viewer) if self._auto_attach: try: self.proxy._viewer.add_object( 'vector', newobj=self.zones, ToCheck=True, id=self._zones_id, ) except Exception as exc: _logger.warning( "_ensure_zones: could not attach Zones to viewer: %s", exc ) return self.zones
[docs] def _finalise_current(self) -> None: """Move *current* vertices into a new zone+vector inside :attr:`zones`.""" if len(self.current) < 2: return zones_obj = self._ensure_zones() n = zones_obj.nbzones + 1 zone_name = f'{self._zone_name_prefix}_{n:03d}' z = zones_obj._make_zone(name=zone_name, parent=zones_obj) v = zones_obj._make_vector( name=f'line_{n:03d}', parentzone=z, fromlist=list(self.current), ) z.add_vector(v, forceparent=True) zones_obj.add_zone(z, forceparent=True) try: zones_obj.find_minmax(update=True) except Exception: pass self.current.clear() self.proxy.force_redraw()
# -- event handlers ------------------------------------------------------
[docs] def _rdown(self, ctx: MouseContext) -> None: self.current.append((ctx.x_snap, ctx.y_snap)) self.proxy.force_redraw()
[docs] def _key(self, kb: KeyboardSnapshot) -> bool: if kb.key_code == Keys.RETURN: if len(self.current) >= 2: self._finalise_current() n = self.zones.nbzones if self.zones is not None else 0 self.proxy.set_status( f'Line {n} accepted — right-click to start line {n + 1} | Esc: stop' ) else: self.proxy.set_status('Need at least 2 vertices — keep clicking.') return True if kb.key_code == Keys.ESCAPE: self.current.clear() self.proxy.force_redraw() self.stop() return True return False
[docs] def _paint(self) -> None: # Already-accepted lines — draw from the Zones object so the overlay # stays in sync even if the viewer hasn't refreshed its own layer yet. if self.zones is not None: for z in self.zones.myzones: for v in z.myvectors: pts = [(vert.x, vert.y) for vert in v.myvertices] if len(pts) >= 2: self.proxy.draw_polyline( pts, self.COLOR_DONE, line_width=self.LINE_WIDTH, ) # In-progress line if len(self.current) >= 2: self.proxy.draw_polyline( self.current, self.COLOR_CURRENT, line_width=self.LINE_WIDTH, ) # Vertex markers half = self.proxy.viewport_fraction(self.CROSS_FRACTION) accepted_pts = [ (vert.x, vert.y) for z in (self.zones.myzones if self.zones is not None else []) for v in z.myvectors for vert in v.myvertices ] all_pts = accepted_pts + self.current if all_pts: self.proxy.draw_crosses(all_pts, half, color=self.COLOR_VERTEX)
# --------------------------------------------------------------------------- # PolygonCompanion # ---------------------------------------------------------------------------
[docs] class PolygonCompanion(AbstractUICompanion): """Record one or more closed polygons. Interaction ----------- * **Right-click** — append a vertex to the current polygon * **Enter / Return** — close and finalise the current polygon (≥ 3 vertices) * **Esc** — discard the current in-progress polygon; finalised polygons are kept Attributes ---------- polygons : list[list[tuple[float, float]]] All finalised polygons. The closing segment (last → first vertex) is implicit — it is drawn but *not* stored as a duplicate vertex. current : list[tuple[float, float]] Vertices of the polygon currently being drawn. """ #: RGBA colour for finalised (closed) polygons.
[docs] COLOR_DONE: tuple = (0.0, 0.8, 0.2, 1.0)
#: RGBA colour for the in-progress polygon.
[docs] COLOR_CURRENT: tuple = (1.0, 0.5, 0.0, 1.0)
#: RGBA colour for vertex markers.
[docs] COLOR_VERTEX: tuple = (1.0, 1.0, 1.0, 0.8)
#: Vertex cross size as a fraction of viewport width.
[docs] CROSS_FRACTION: float = 0.007
#: OpenGL line width in pixels.
[docs] LINE_WIDTH: float = 1.5
[docs] def create_model(self) -> PolygonModel: return PolygonModel()
@property
[docs] def polygons(self) -> list[list[tuple[float, float]]]: return self.model.polygons # type: ignore[union-attr]
@polygons.setter def polygons(self, value: list[list[tuple[float, float]]]) -> None: self.model.polygons = value # type: ignore[union-attr] @property
[docs] def current(self) -> list[tuple[float, float]]: return self.model.current # type: ignore[union-attr]
@current.setter def current(self, value: list[tuple[float, float]]) -> None: self.model.current = value # type: ignore[union-attr]
[docs] def actions_spec(self): return [ ActionSpec( 'poly', rdown=self._rdown, key=self._key, paint=self._paint, primary=True, start_message='Right-click: vertex | Enter: close polygon (≥3 pts) | Esc: cancel current', ), ]
[docs] def start(self) -> None: """Reset state and activate the polygon-recording action.""" self.polygons.clear() self.current.clear() self.proxy.start_action( 'poly', 'Right-click: vertex | Enter: close polygon (≥3 pts) | Esc: cancel current', )
# -- handlers ------------------------------------------------------------
[docs] def _rdown(self, ctx: MouseContext) -> None: self.current.append((ctx.x_snap, ctx.y_snap)) self.proxy.force_redraw()
[docs] def _key(self, kb: KeyboardSnapshot) -> bool: if kb.key_code == Keys.RETURN: if len(self.current) >= 3: self.polygons.append(list(self.current)) self.current.clear() self.proxy.force_redraw() n = len(self.polygons) self.proxy.set_status( f'Polygon {n} closed — right-click to start polygon {n + 1} | Esc: stop' ) else: self.proxy.set_status('Need at least 3 vertices to close a polygon.') return True if kb.key_code == Keys.ESCAPE: self.current.clear() self.proxy.force_redraw() return True return False
[docs] def _paint(self) -> None: for poly in self.polygons: if len(poly) >= 3: self.proxy.draw_polyline( poly, self.COLOR_DONE, closed=True, line_width=self.LINE_WIDTH, ) if len(self.current) >= 2: self.proxy.draw_polyline(self.current, self.COLOR_CURRENT, line_width=self.LINE_WIDTH) half = self.proxy.viewport_fraction(self.CROSS_FRACTION) all_pts = [p for poly in self.polygons for p in poly] + self.current if all_pts: self.proxy.draw_crosses(all_pts, half, color=self.COLOR_VERTEX)
# --------------------------------------------------------------------------- # Convenience factory functions # ---------------------------------------------------------------------------
[docs] def point_picker(viewer: 'WolfMapViewer', **kwargs) -> PointPickerCompanion: """Create, register and activate a :class:`PointPickerCompanion`. :param viewer: The :class:`~wolfhece.PyDraw.WolfMapViewer` instance. :param kwargs: Forwarded to :class:`PointPickerCompanion` constructor (e.g. ``namespace=``, ``dialogs=``). :return: The activated companion. """ c = PointPickerCompanion(**kwargs) viewer.attach_companion(c) c.start() return c
[docs] def polyline(viewer: 'WolfMapViewer', **kwargs) -> PolylineCompanion: """Create, register and activate a :class:`PolylineCompanion`. :param viewer: The :class:`~wolfhece.PyDraw.WolfMapViewer` instance. :param kwargs: Forwarded to the constructor. :return: The activated companion. """ c = PolylineCompanion(**kwargs) viewer.attach_companion(c) c.start() return c
[docs] def multi_polyline(viewer: 'WolfMapViewer', **kwargs) -> MultiPolylineCompanion: """Create, register and activate a :class:`MultiPolylineCompanion`. :param viewer: The :class:`~wolfhece.PyDraw.WolfMapViewer` instance. :param kwargs: Forwarded to the constructor. :return: The activated companion. """ c = MultiPolylineCompanion(**kwargs) viewer.attach_companion(c) c.start() return c
[docs] def multi_polyline_zones( viewer: 'WolfMapViewer', zones_id: str = 'multi_polyline', auto_attach: bool = True, **kwargs, ) -> 'MultiPolylineZonesCompanion': """Create, register and activate a :class:`MultiPolylineZonesCompanion`. :param viewer: The :class:`~wolfhece.PyDraw.WolfMapViewer` instance. :param zones_id: Identifier of the :class:`~wolfhece.pyvertexvectors.Zones` object that will be added to the viewer. :param auto_attach: When ``True`` (default) the :class:`~wolfhece.pyvertexvectors.Zones` object is added to the viewer automatically on the first accepted polyline. :param kwargs: Forwarded to the constructor. :return: The activated companion. """ c = MultiPolylineZonesCompanion(zones_id=zones_id, auto_attach=auto_attach, **kwargs) viewer.attach_companion(c) c.start() return c
[docs] def polygon(viewer: 'WolfMapViewer', **kwargs) -> PolygonCompanion: """Create, register and activate a :class:`PolygonCompanion`. :param viewer: The :class:`~wolfhece.PyDraw.WolfMapViewer` instance. :param kwargs: Forwarded to the constructor. :return: The activated companion. """ c = PolygonCompanion(**kwargs) viewer.attach_companion(c) c.start() return c