Hatch↔Curve-Sync: User-Transform-Skip war zu aggressiv

Symptom: Beim Verschieben einer Polylinie ging die gekoppelte Hatch
nicht mehr mit; umgekehrt zog die Hatch die Curve auch nicht mehr
mit. Die bidirektionale Hatch-Curve-Kopplung war kaputt.

Ursache: ich hatte vor einiger Zeit in gestaltungs on_replace,
on_delete und on_add jeweils einen `_dossier_user_transform_active`-
Skip eingebaut um waehrend elemente-Moves die Listener stumm zu
halten (Performance). Damit wurden aber auch die Hatch-Coupling-
Updates fuer normale Polylinien geblockt — die laufen ja gerade
genau dann, wenn der User einen `_Move` macht.

Fix: Skip-Logik selektiver machen:

- on_replace: Skip ENTFERNT. Stattdessen ein frueher Bail-out wenn
  das Objekt weder _FILL_KEY noch _FILL_OWNER_KEY hat → Wand-Sub-
  Volumen werden immer noch nicht angepackt, aber Hatch-gekoppelte
  Polylinien laufen durch (auch waehrend _Move).
- on_delete: Skip ENTFERNT. Der vorhandene Bail-out auf "kein Hatch-
  UserString" filtert dossier-Sub-Volumen weiterhin raus. Hatch-
  gekoppelte Curves machen Cascade-Delete + Pending-Save fuer die
  Recovery in on_add.
- on_add: zwei Phasen — Drag-Recovery (Phase 1, IMMER) und Auto-Fill
  (Phase 2, nur ausserhalb User-Transform). So funktioniert die
  Delete+Add-Recovery von Rhinos Move waehrend Auto-Fill nicht
  versehentlich neue Hatches fuer Wand-Volumen anlegt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 00:54:51 +02:00
parent fcbc97b608
commit d6c09d22f7
+23 -14
View File
@@ -1387,16 +1387,24 @@ def _install_selection_listener(bridge):
- Hatch hat _FILL_OWNER_KEY (= curve_id) → Curve um den gleichen
Vektor mit-translaten (User hat Hatch alleine verschoben).
"""
# Waehrend User-Transform-Command: elemente uebernimmt die Geometrie-
# Synchronisation. Hatch-Re-Create laeuft hier sowieso ins Leere weil
# Rhino bei Move Delete+Add statt Replace feuert.
if sc.sticky.get("_dossier_user_transform_active"): return
if sc.sticky.get("_dossier_undo_active"): return
if sc.sticky.get("_elemente_regen_busy"): return
new_obj = args.NewRhinoObject
if new_obj is None or new_obj.Id in _processing:
return
a = new_obj.Attributes
# Frueher Bail-out wenn das Objekt KEIN Hatch-Coupling hat — irrelevant.
# Erst NACH dieser Pruefung das user-transform-Skip, sonst wuerde die
# Hatch-Sync beim Verschieben einer normalen Polylinie nicht laufen
# (war der Fall nach dem Split: Hatch ging bei _Move nicht mehr mit).
hatch_id_str_quick = a.GetUserString(_FILL_KEY)
owner_id_str_quick = a.GetUserString(_FILL_OWNER_KEY)
if not hatch_id_str_quick and not owner_id_str_quick:
return
# Dossier-eigene Sources (wand_axis etc.) haben weder _FILL_KEY noch
# _FILL_OWNER_KEY — die werden hier oben rausgekickt. User-Transform-
# Skip war eigentlich nur fuer Dossier-Elemente gedacht; reine Hatch-
# gekoppelte Polylinien brauchen die Sync auch waehrend _Move.
# Reverse-Direction: Hatch verschoben/rotiert/skaliert → Curve mitnehmen.
# Wir nehmen die Outer-Boundary direkt aus der (bereits transformed)
# Hatch — funktioniert fuer Move, Rotate, Scale, beliebige Transforms.
@@ -1470,10 +1478,6 @@ def _install_selection_listener(bridge):
"""Wenn eine Curve geloescht wird, ihre gekoppelte Hatch mitloeschen.
Wenn umgekehrt eine Hatch direkt geloescht wird, den Verweis auf der
Curve aufraeumen damit beim naechsten Toggle keine Geister-Referenz steht."""
# Waehrend User-Transform-Command: Rhino feuert Delete+Add fuer
# transformierte Objekte. Curve→Hatch-Cascade hier wuerde die Hatch
# killen obwohl sie gleich wieder benoetigt wird.
if sc.sticky.get("_dossier_user_transform_active"): return
if sc.sticky.get("_dossier_undo_active"): return
if sc.sticky.get("_elemente_regen_busy"): return
obj = args.TheObject
@@ -1486,7 +1490,10 @@ def _install_selection_listener(bridge):
return
# Schneller Bail-out: ohne Hatch-UserString interessiert uns das
# Event nicht. Vermeidet Print-Spam fuer Wand-Sub-Volumen etc.
# Event nicht. Vermeidet Print-Spam fuer Wand-Sub-Volumen etc. UND
# filtert den user-transform-Pfad: nur Hatch-gekoppelte Objekte
# brauchen die Sync (= Cascade-Delete + pending-Save fuer Recovery).
# Wand-Volumen werden nicht beruehrt.
try:
hatch_id_str = attrs.GetUserString(_FILL_KEY)
except Exception:
@@ -1551,10 +1558,6 @@ def _install_selection_listener(bridge):
- Wenn das Objekt eben gerade als Teil eines Drag/Move geloescht wurde,
stellen wir die Hatch mit den gemerkten Metadaten wieder her.
- Sonst pruefen wir ob die Ebene ein Auto-Fill konfiguriert hat."""
# Waehrend User-Transform-Command: elemente uebernimmt Geometrie-Sync.
# Auto-Fill hier wuerde unnoetige Hatches erzeugen weil das Objekt
# bereits eine geerbte Fill-UserString hat (vom Delete+Add im Move).
if sc.sticky.get("_dossier_user_transform_active"): return
if sc.sticky.get("_dossier_undo_active"): return
if sc.sticky.get("_elemente_regen_busy"): return
obj = args.TheObject
@@ -1565,6 +1568,9 @@ def _install_selection_listener(bridge):
doc = Rhino.RhinoDoc.ActiveDoc
# 1) Drag-Recovery: Hatch-Metadaten wurden gerade in on_delete gespeichert?
# MUSS auch waehrend User-Transform laufen (= das ist gerade DER Fall:
# Rhino's Move feuert Delete+Add, on_delete hat pending gespeichert,
# jetzt muss on_add die Hatch wiederherstellen).
pending = _take_pending_hatch(obj.Id)
if pending is not None:
try:
@@ -1580,7 +1586,10 @@ def _install_selection_listener(bridge):
except Exception: pass
return
# 2) Auto-Fill aus Ebenen-Definition
# 2) Auto-Fill aus Ebenen-Definition — nur ausserhalb User-Transform.
# Waehrend Move/Rotate werden Sub-Volumen erzeugt die kein Auto-Fill
# brauchen, und elemente uebernimmt die Coupling.
if sc.sticky.get("_dossier_user_transform_active"): return
try:
ok = _apply_ebene_fill(doc, obj)
except Exception as ex: