Companion factory — sélection interactive de géométries
Ce notebook présente cinq compagnons préconfigurés disponibles dans wolfhece._companion_factory. Chacun répond à un besoin courant de saisie interactive sur la carte.
Compagnon |
Résultat |
Cas d’usage typique |
|---|---|---|
|
|
Saisie de points de contrôle, nœuds d’un maillage |
|
|
Profil en long, tracé d’axe, chemin d’écoulement |
|
|
Réseau de drains, contours de sections |
|
|
Idem, mais stockage dans une couche vectorielle réutilisable |
|
|
Zones d’inondation, bassins versants |
Interaction commune à tous
Touche / action |
Effet |
|---|---|
Clic droit |
Ajoute un sommet |
Entrée |
Valide la géométrie en cours (≥ 2 pts pour lignes, ≥ 3 pour polygones) |
Esc |
Annule / arrête |
|
Désactive l’action (données conservées) |
|
Désactive + désenregistre tous les handlers |
Usage minimal (one-liner)
from wolfhece._companion_factory import point_picker
comp = point_picker(viewer) # crée, enregistre ET active en une ligne
# … clic droit sur la carte …
print(comp.points)
comp.destroy()
1 — Démarrage wx
[1]:
import sys
%gui wx
2 — Créer le viewer
[2]:
from wolfhece.PyDraw import WolfMapViewer
viewer = WolfMapViewer(None, title="Companion factory", w=1200, h=800)
viewer.Show()
INFO:root:Importing wolfhece modules
INFO:root:wolfhece modules imported
[2]:
False
3 — Importer la factory
[3]:
from wolfhece._companion_factory import (
PointPickerCompanion,
PolylineCompanion,
MultiPolylineCompanion,
MultiPolylineZonesCompanion,
PolygonCompanion,
point_picker,
polyline,
multi_polyline,
multi_polyline_zones,
polygon,
)
print('Factory importée.')
Factory importée.
A — PointPickerCompanion : sélection de points isolés
Données disponibles après interaction : comp.points (liste de (x, y)).
[4]:
# One-liner : crée + enregistre + active
comp_pts = point_picker(viewer)
print(comp_pts)
INFO:root:ACTION : Right-click: add | Left-click: select nearest | Ctrl+Z: undo | Esc: stop
<PointPickerCompanion ns='pointpickercompanion' menu=not built actions=[pointpickercompanion.pick]>
[5]:
# Inspecter les points collectés (exécutez cette cellule à tout moment)
print(f"{len(comp_pts.points)} point(s) | sélectionné : {comp_pts.selected}")
for i, (x, y) in enumerate(comp_pts.points, 1):
flag = ' ←' if i - 1 == comp_pts.selected else ''
print(f" {i:3d}. X={x:.3f} Y={y:.3f}{flag}")
5 point(s) | sélectionné : 4
1. X=4.767 Y=25.779
2. X=23.782 Y=27.803
3. X=11.531 Y=20.346
4. X=27.510 Y=15.446
5. X=33.795 Y=28.229 ←
[6]:
# Convertir en tableau numpy pour traitement
import numpy as np
if comp_pts.points:
pts_array = np.array(comp_pts.points) # shape (N, 2)
print(f"Shape : {pts_array.shape}")
print(pts_array)
Shape : (5, 2)
[[ 4.76725146 25.7788574 ]
[23.78155646 27.80278902]
[11.53144399 20.34619883]
[27.50985155 15.44615385]
[33.79469186 28.22887989]]
[7]:
comp_pts.stop()
comp_pts.destroy()
print('PointPickerCompanion détruit.')
INFO:root:ACTION :
PointPickerCompanion détruit.
B — PolylineCompanion : saisie d’une polyligne
Données disponibles après Enter : comp.vertices (liste ordonnée de sommets), comp.finished (True).
[8]:
comp_line = polyline(viewer)
print(comp_line)
INFO:root:ACTION : Right-click: add vertex | Enter: finalise (≥2 pts) | Esc: cancel
<PolylineCompanion ns='polylinecompanion' menu=not built actions=[polylinecompanion.line]>
[9]:
print(f"finished={comp_line.finished} | {len(comp_line.vertices)} sommet(s)")
for i, (x, y) in enumerate(comp_line.vertices, 1):
print(f" {i:3d}. X={x:.3f} Y={y:.3f}")
finished=True | 10 sommet(s)
1. X=-3.009 Y=15.606
2. X=-3.435 Y=30.093
3. X=9.188 Y=31.212
4. X=22.397 Y=30.945
5. X=22.823 Y=23.861
6. X=7.910 Y=14.115
7. X=19.574 Y=10.546
8. X=31.291 Y=17.257
9. X=34.008 Y=35.472
10. X=10.573 Y=37.177
[10]:
# Calculer la longueur totale de la polyligne
if comp_line.finished and len(comp_line.vertices) >= 2:
import numpy as np
v = np.array(comp_line.vertices)
segments = np.hypot(np.diff(v[:, 0]), np.diff(v[:, 1]))
print(f"Longueur totale : {segments.sum():.3f} m")
print(f"Nb segments : {len(segments)}")
Longueur totale : 132.904 m
Nb segments : 9
[11]:
comp_line.destroy()
print('PolylineCompanion détruit.')
PolylineCompanion détruit.
C — MultiPolylineCompanion : plusieurs polylignes en une session
Couleurs : lignes finalisées en bleu, ligne en cours en orange.
Données : comp.polylines (liste de listes de sommets).
[ ]:
comp_lines = multi_polyline(viewer)
print(comp_lines)
[ ]:
print(f"{len(comp_lines.polylines)} polyligne(s) finalisée(s)")
for j, line in enumerate(comp_lines.polylines, 1):
print(f" Ligne {j} : {len(line)} sommets")
for i, (x, y) in enumerate(line, 1):
print(f" {i:3d}. X={x:.3f} Y={y:.3f}")
[ ]:
comp_lines.destroy()
print('MultiPolylineCompanion détruit.')
D — MultiPolylineZonesCompanion : polylignes dans un objet Zones
Identique à MultiPolylineCompanion pour l’interaction, mais chaque polyligne acceptée est immédiatement stockée dans un objet `Zones <../../api/wolfhece.pyvertexvectors.rst>`__ — hiérarchie Zones → zone → vector — et, par défaut, ajoutée automatiquement au viewer comme couche vectorielle.
comp.zones.Données : comp.zones (objet Zones, None avant la première acceptation).
Avantage clé : l’objet
Zonespeut être exporté, manipulé, ou transmis à d’autres outils wolfhece directement sans conversion.
[4]:
# One-liner : crée, enregistre et active
# Par défaut auto_attach=True → le Zones est ajouté au viewer à la 1re ligne acceptée
comp_zones = multi_polyline_zones(viewer, zones_id='demo_zones')
print(comp_zones)
INFO:root:ACTION : Right-click: vertex | Enter: accept line (≥2 pts) | Esc: stop
<MultiPolylineZonesCompanion ns='multipolylinezonescompanion' menu=not built actions=[multipolylinezonescompanion.mplz]>
[5]:
# Inspecter les lignes dans le Zones (exécutez à tout moment)
if comp_zones.zones is not None:
z_obj = comp_zones.zones
print(f"{z_obj.nbzones} zone(s) enregistrée(s)")
for j, z in enumerate(z_obj.myzones, 1):
for v in z.myvectors:
pts = [(vert.x, vert.y) for vert in v.myvertices]
print(f" Zone {j} '{z.myname}' — {len(pts)} sommet(s)")
for i, (x, y) in enumerate(pts, 1):
print(f" {i:3d}. X={x:.3f} Y={y:.3f}")
else:
print("Aucune ligne acceptée pour l'instant (appuyez sur Entrée après ≥ 2 clics).")
1 zone(s) enregistrée(s)
Zone 1 'zone_001' — 10 sommet(s)
1. X=1.625 Y=24.820
2. X=9.614 Y=28.122
3. X=18.935 Y=28.442
4. X=25.113 Y=24.501
5. X=23.995 Y=21.571
6. X=12.756 Y=20.666
7. X=16.964 Y=15.926
8. X=28.362 Y=13.475
9. X=33.582 Y=29.827
10. X=18.242 Y=34.301
[6]:
# Convertir le Zones en tableau numpy (toutes les lignes concaténées)
import numpy as np
if comp_zones.zones is not None:
all_pts = [
(vert.x, vert.y)
for z in comp_zones.zones.myzones
for v in z.myvectors
for vert in v.myvertices
]
if all_pts:
arr = np.array(all_pts)
print(f"Shape : {arr.shape}")
print(arr)
Shape : (10, 2)
[[ 1.62483131 24.82015295]
[ 9.61403509 28.12235717]
[18.93477283 28.44192533]
[25.11309042 24.5005848 ]
[23.99460189 21.57121008]
[12.75645524 20.66576698]
[16.96410256 15.92550607]
[28.36203329 13.47548358]
[33.58164642 29.82672065]
[18.24237517 34.30067476]]
[7]:
comp_zones.destroy()
print('MultiPolylineZonesCompanion détruit.')
MultiPolylineZonesCompanion détruit.
E — PolygonCompanion : saisie de polygones fermés
Couleurs : polygones validés en vert, polygone en cours en orange.
Données : comp.polygons (liste de listes ; le segment de fermeture dernier→premier est implicite).
[ ]:
comp_poly = polygon(viewer)
print(comp_poly)
[ ]:
print(f"{len(comp_poly.polygons)} polygone(s) finalisé(s)")
for j, poly in enumerate(comp_poly.polygons, 1):
print(f" Polygone {j} : {len(poly)} sommets")
for i, (x, y) in enumerate(poly, 1):
print(f" {i:3d}. X={x:.3f} Y={y:.3f}")
[ ]:
# Calculer l'aire de chaque polygone (formule de Shoelace)
import numpy as np
for j, poly in enumerate(comp_poly.polygons, 1):
v = np.array(poly)
x, y = v[:, 0], v[:, 1]
area = 0.5 * abs(np.dot(x, np.roll(y, -1)) - np.dot(y, np.roll(x, -1)))
print(f"Polygone {j} : aire = {area:.3f} m²")
[ ]:
comp_poly.destroy()
print('PolygonCompanion détruit.')
F — Personnalisation : surcharger les attributs de classe
Les couleurs et tailles sont des attributs de classe — on les modifie sans réécrire aucune méthode.
[ ]:
class BluePicker(PointPickerCompanion):
"""PointPickerCompanion avec des croix bleues et des marqueurs plus grands."""
COLOR_NORMAL = (0.1, 0.4, 1.0, 1.0) # bleu
COLOR_SELECTED = (1.0, 1.0, 0.0, 1.0) # jaune
CROSS_FRACTION = 0.018 # croix plus grandes
class ThickPolyline(PolylineCompanion):
"""PolylineCompanion en rouge épais."""
COLOR_LINE = (1.0, 0.1, 0.1, 1.0) # rouge
LINE_WIDTH = 3.5
# Utilisation identique aux classes de base :
comp_blue = BluePicker(viewer)
comp_blue.start()
print(comp_blue)
# Pour nettoyer avant la prochaine démonstration :
comp_blue.destroy()
G — Extension : ajouter un comportement au clic
Pour ajouter de la logique sans réécrire toute la classe, on surcharge uniquement le handler privé correspondant.
[ ]:
class AnnotatedPicker(PointPickerCompanion):
"""Affiche un label numéroté dans la barre de statut après chaque clic."""
def _rdown(self, viewer, ctx):
super()._rdown(viewer, ctx) # comportement de base (ajoute le point)
n = len(self.points)
self._set_status(f"Point #{n} X={ctx.x_snap:.3f} Y={ctx.y_snap:.3f}")
comp_ann = AnnotatedPicker(viewer)
comp_ann.start()
print("Cliquez sur la carte — la barre de statut s'actualise à chaque point.")
# Nettoyage :
# comp_ann.destroy()
[ ]:
comp_ann.destroy()
print('Nettoyage terminé.')
Récapitulatif — référence rapide
from wolfhece._companion_factory import (
point_picker, # → comp.points : list[(x, y)]
polyline, # → comp.vertices : list[(x, y)], comp.finished : bool
multi_polyline, # → comp.polylines : list[list[(x, y)]]
multi_polyline_zones, # → comp.zones : Zones (hiérarchie Zones→zone→vector)
polygon, # → comp.polygons : list[list[(x, y)]]
)
# Démarrage
comp = multi_polyline_zones(viewer, zones_id='mon_tracé', auto_attach=True)
# Inspection
print(comp.zones) # objet Zones (None avant la 1re ligne acceptée)
print(comp.zones.nbzones) # nombre de zones enregistrées
# Arrêt
comp.stop() # désactive, données conservées
comp.destroy() # désactive + désenregistre
# Attachement manuel (quand auto_attach=False)
comp2 = multi_polyline_zones(viewer, auto_attach=False)
# … interaction …
comp2.attach_zones() # ajoute le Zones au viewer
# Remise à zéro
comp.clear_zones() # vide le Zones et remet zones=None
Personnalisation sans réécriture
Attribut de classe |
Type |
Rôle |
|---|---|---|
|
|
Couleur des éléments non sélectionnés |
|
|
Couleur de l’élément sélectionné |
|
|
Couleurs de trait |
|
|
Taille des marqueurs (fraction de la largeur visible) |
|
|
Épaisseur du trait OpenGL (pixels) |
Pour aller plus loin
Objectif |
Ressource |
|---|---|
Comprendre la structure de base |
|
Écrire un compagnon complet avec menu |
|
Tous les hooks (motion, key, paint) |
|
Superposer deux compagnons |
|