{ "cells": [ { "cell_type": "markdown", "id": "7fe80add", "metadata": {}, "source": [ "# Companion sans menu — exemple minimal\n", "\n", "Ce notebook montre le compagnon **le plus court possible** :\n", "\n", "- **Aucun menu** — pas de `menu_build()` à écrire\n", "- **Aucun import OpenGL** — le dessin est géré par les helpers de la classe de base\n", "- **Une seule responsabilité** : clic droit → ajoute un point, qui est immédiatement dessiné sur la carte\n", "\n", "La classe entière tient en une vingtaine de lignes.\n", "\n", "## Pourquoi se passer de menu ?\n", "\n", "Depuis le notebook, les actions se déclenchent par code (`comp.start()`, `comp.stop()`). \n", "Un menu n'est utile que si l'utilisateur final doit déclencher l'action *depuis l'interface graphique*. \n", "Pour une session d'exploration ou de débogage, il est inutile.\n", "\n", "## Ce que fait `AbstractCompanion` pour vous\n", "\n", "| Méthode héritée | Rôle |\n", "|---|---|\n", "| `self._action_id('x')` | Construit un identifiant namespaced sans collision |\n", "| `self._register_action(id, rdown=..., paint=...)` | Enregistre les handlers sur le viewer |\n", "| `self._start_action(id, hint)` | Active l'action et affiche l'indice dans la barre de statut |\n", "| `self._end_action()` | Désactive l'action |\n", "| `self._force_redraw()` | Force un rafraîchissement du viewer (`viewer.Paint()`) |\n", "| `self._viewport_fraction(f)` | Renvoie `f × largeur_visible` — taille qui suit le zoom |\n", "| `self._draw_crosses(points, half_size)` | Dessine des croix aux positions données |\n", "| `self.destroy()` | Désenregistre toutes les actions d'un seul appel |" ] }, { "cell_type": "markdown", "id": "9e0e46aa", "metadata": {}, "source": [ "## 1 — Démarrage wx" ] }, { "cell_type": "code", "execution_count": 1, "id": "6f4bd835", "metadata": {}, "outputs": [], "source": [ "import sys\n", "%gui wx" ] }, { "cell_type": "markdown", "id": "f51fc2ae", "metadata": {}, "source": [ "## 2 — Créer le viewer" ] }, { "cell_type": "code", "execution_count": 2, "id": "c9c3de1e", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO:root:Importing wolfhece modules\n", "INFO:root:wolfhece modules imported\n" ] }, { "data": { "text/plain": [ "False" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from wolfhece.PyDraw import WolfMapViewer\n", "\n", "viewer = WolfMapViewer(None, title=\"Companion sans menu\", w=1200, h=800)\n", "viewer.Show()" ] }, { "cell_type": "markdown", "id": "d07048cb", "metadata": {}, "source": [ "## 3 — La classe compagnon\n", "\n", "Seule obligation : implémenter `start()` (méthode abstraite de `AbstractCompanion`). \n", "Tout le reste est optionnel." ] }, { "cell_type": "code", "execution_count": null, "id": "decbe82b", "metadata": {}, "outputs": [], "source": [ "from wolfhece._menu_companion_abc import AbstractCompanion, MouseContext\n", "\n", "\n", "class DotCompanion(AbstractCompanion):\n", " \"\"\"Collecte des clics droits et les dessine sur la carte.\n", "\n", " Pas de menu, pas d'import OpenGL — le compagnon minimal.\n", " \"\"\"\n", "\n", " def __init__(self, viewer):\n", " super().__init__(viewer)\n", " #: Points collectés — lisibles depuis le notebook.\n", " self.points: list[tuple[float, float]] = []\n", "\n", " # ── Méthode abstraite obligatoire ─────────────────────────────────────\n", " def start(self) -> None:\n", " \"\"\"Enregistre les handlers et active l'action.\"\"\"\n", " self._register_action(\n", " self._action_id('pick'),\n", " rdown=self._rdown,\n", " paint=self._paint,\n", " )\n", " self._start_action(self._action_id('pick'), 'Clic droit pour ajouter un point…')\n", "\n", " # ── stop() est hérité : appelle self._end_action() par défaut ─────────\n", "\n", " # ── Handlers privés ───────────────────────────────────────────────────\n", " def _rdown(self, viewer, ctx: MouseContext) -> None:\n", " \"\"\"Enregistre le point et redessine.\"\"\"\n", " self.points.append((ctx.x_snap, ctx.y_snap))\n", " print(f\"#{len(self.points):3d} X={ctx.x_snap:.3f} Y={ctx.y_snap:.3f}\")\n", " self._force_redraw()\n", "\n", " def _paint(self, viewer) -> None:\n", " \"\"\"Dessine une croix à chaque point collecté.\"\"\"\n", " self._draw_crosses(self.points, self._viewport_fraction(0.01))" ] }, { "cell_type": "markdown", "id": "57322406", "metadata": {}, "source": [ "## 4 — Instancier et démarrer\n", "\n", "Pas de `menu_build()` à appeler — on démarre directement." ] }, { "cell_type": "code", "execution_count": 4, "id": "833019dc", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO:root:ACTION : Clic droit pour ajouter un point…\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "Action active — clic droit sur la carte pour ajouter des points.\n" ] } ], "source": [ "comp = DotCompanion(viewer)\n", "comp.start()\n", "\n", "print(comp)\n", "print(\"Action active — clic droit sur la carte pour ajouter des points.\")" ] }, { "cell_type": "markdown", "id": "f99dd1b0", "metadata": {}, "source": [ "## 5 — Inspecter les points collectés" ] }, { "cell_type": "code", "execution_count": 5, "id": "8a3f9339", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4 point(s) :\n", " 1. X=5.353 Y=27.536\n", " 2. X=26.764 Y=29.880\n", " 3. X=7.271 Y=16.884\n", " 4. X=30.492 Y=14.328\n" ] } ], "source": [ "print(f\"{len(comp.points)} point(s) :\")\n", "for i, (x, y) in enumerate(comp.points, 1):\n", " print(f\" {i:3d}. X={x:.3f} Y={y:.3f}\")" ] }, { "cell_type": "markdown", "id": "2cade5ae", "metadata": {}, "source": [ "## 6 — Arrêter et nettoyer" ] }, { "cell_type": "code", "execution_count": 6, "id": "c98b2821", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO:root:ACTION : \n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Compagnon détruit.\n", "\n" ] } ], "source": [ "comp.stop() # désactive l'action (points conservés)\n", "comp.destroy() # désenregistre tous les handlers\n", "\n", "print(\"Compagnon détruit.\")\n", "print(comp)" ] }, { "cell_type": "markdown", "id": "b1631503", "metadata": {}, "source": [ "## Récapitulatif — classe complète\n", "\n", "```python\n", "from wolfhece._menu_companion_abc import AbstractCompanion\n", "from wolfhece._viewer_plugin_handlers import MouseContext\n", "\n", "\n", "class DotCompanion(AbstractCompanion):\n", "\n", " def __init__(self, viewer):\n", " super().__init__(viewer)\n", " self.points: list[tuple[float, float]] = []\n", "\n", " def start(self) -> None:\n", " self._register_action(\n", " self._action_id('pick'),\n", " rdown=self._rdown,\n", " paint=self._paint,\n", " )\n", " self._start_action(self._action_id('pick'), 'Clic droit pour ajouter un point…')\n", "\n", " def _rdown(self, viewer, ctx: MouseContext) -> None:\n", " self.points.append((ctx.x_snap, ctx.y_snap))\n", " self._force_redraw()\n", "\n", " def _paint(self, viewer) -> None:\n", " self._draw_crosses(self.points, self._viewport_fraction(0.01))\n", "```\n", "\n", "### Prochaines étapes\n", "\n", "| Pour ajouter… | Voir… |\n", "|---|---|\n", "| Un menu contextuel | `plugin_companion_example.ipynb` |\n", "| Sélection, undo, raccourcis clavier, hook paint complet | `plugin_companion_complete.ipynb` |\n", "| Superposition temporaire d'un second compagnon | `plugin_companion_overload.ipynb` |" ] } ], "metadata": { "kernelspec": { "display_name": "python311", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.9" } }, "nbformat": 4, "nbformat_minor": 5 }