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 - Hatch hat _FILL_OWNER_KEY (= curve_id) → Curve um den gleichen
Vektor mit-translaten (User hat Hatch alleine verschoben). 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("_dossier_undo_active"): return
if sc.sticky.get("_elemente_regen_busy"): return if sc.sticky.get("_elemente_regen_busy"): return
new_obj = args.NewRhinoObject new_obj = args.NewRhinoObject
if new_obj is None or new_obj.Id in _processing: if new_obj is None or new_obj.Id in _processing:
return return
a = new_obj.Attributes 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. # Reverse-Direction: Hatch verschoben/rotiert/skaliert → Curve mitnehmen.
# Wir nehmen die Outer-Boundary direkt aus der (bereits transformed) # Wir nehmen die Outer-Boundary direkt aus der (bereits transformed)
# Hatch — funktioniert fuer Move, Rotate, Scale, beliebige Transforms. # 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 eine Curve geloescht wird, ihre gekoppelte Hatch mitloeschen.
Wenn umgekehrt eine Hatch direkt geloescht wird, den Verweis auf der Wenn umgekehrt eine Hatch direkt geloescht wird, den Verweis auf der
Curve aufraeumen damit beim naechsten Toggle keine Geister-Referenz steht.""" 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("_dossier_undo_active"): return
if sc.sticky.get("_elemente_regen_busy"): return if sc.sticky.get("_elemente_regen_busy"): return
obj = args.TheObject obj = args.TheObject
@@ -1486,7 +1490,10 @@ def _install_selection_listener(bridge):
return return
# Schneller Bail-out: ohne Hatch-UserString interessiert uns das # 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: try:
hatch_id_str = attrs.GetUserString(_FILL_KEY) hatch_id_str = attrs.GetUserString(_FILL_KEY)
except Exception: except Exception:
@@ -1551,10 +1558,6 @@ def _install_selection_listener(bridge):
- Wenn das Objekt eben gerade als Teil eines Drag/Move geloescht wurde, - Wenn das Objekt eben gerade als Teil eines Drag/Move geloescht wurde,
stellen wir die Hatch mit den gemerkten Metadaten wieder her. stellen wir die Hatch mit den gemerkten Metadaten wieder her.
- Sonst pruefen wir ob die Ebene ein Auto-Fill konfiguriert hat.""" - 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("_dossier_undo_active"): return
if sc.sticky.get("_elemente_regen_busy"): return if sc.sticky.get("_elemente_regen_busy"): return
obj = args.TheObject obj = args.TheObject
@@ -1565,6 +1568,9 @@ def _install_selection_listener(bridge):
doc = Rhino.RhinoDoc.ActiveDoc doc = Rhino.RhinoDoc.ActiveDoc
# 1) Drag-Recovery: Hatch-Metadaten wurden gerade in on_delete gespeichert? # 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) pending = _take_pending_hatch(obj.Id)
if pending is not None: if pending is not None:
try: try:
@@ -1580,7 +1586,10 @@ def _install_selection_listener(bridge):
except Exception: pass except Exception: pass
return 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: try:
ok = _apply_ebene_fill(doc, obj) ok = _apply_ebene_fill(doc, obj)
except Exception as ex: except Exception as ex: