Ebenen-Split: FIRST_RUN-Race + Slice-Authoritaet fixen

Symptom: Nach Split funktionierten die Ebenen nicht und Container
erschienen doppelt.

Two bugs:

1. FIRST_RUN-Detection war global statt mode-aware:
   - Beim ersten Plugin-Start war doc.Strings leer.
   - Bridge A (z.B. Zeichnungsebenen) sah leer → sendete FIRST_RUN
     → React schickte APPLY mit `z=INITIAL_Z, e=[]`.
   - Backend speicherte `[]` fuer dossier_ebenen → fortan war
     doc.Strings nicht mehr leer.
   - Bridge B (Ebenen) sah dossier_ebenen vorhanden ("[]") → sendete
     STATE_SYNC mit leerer Liste statt FIRST_RUN → React-App
     ueberschrieb INITIAL_EBENEN mit `[]` → leere UI.
   Fix: `_on_ready` prueft jetzt mode-spezifisch ob SEINE Slice in
   doc.Strings ist. "ebenen"-Mode schaut auf dossier_ebenen,
   "zeichnungsebenen" auf dossier_zeichnungsebenen.

2. APPLY ueberschrieb fremde Slice mit Fallback-`[]`:
   - Wenn nur eine Panel-Slice im Payload kam, las Backend die
     andere aus doc.Strings (= leer beim ersten Mal) und schrieb
     dann *beide* Slices, davon eine als `[]`.
   - Naechstes READY sah die `[]` → STATE_SYNC statt FIRST_RUN →
     Daten weg.
   Fix: `_apply` bekommt `save_z`/`save_e` Flags. Jedes Panel ist
   autoritativ fuer SEINE Slice. APPLY aus dem Ebenen-Panel
   speichert NUR dossier_ebenen (save_z=False), aus Zeichnungs-
   ebenen NUR dossier_zeichnungsebenen.

Effekt: Wenn Ebenen-Panel zuerst lädt → speichert ebenen, lässt z
unangetastet → Zeichnungsebenen-Panel sieht z fehlt → bekommt
FIRST_RUN → schickt INITIAL_Z → speichert z. Symmetrisch wenn
Zeichnungsebenen zuerst lädt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-18 23:50:21 +02:00
parent a458b4c47d
commit e96de793a9
+23 -14
View File
@@ -117,7 +117,12 @@ class EbenenBridge(panel_base.BaseBridge):
doc = Rhino.RhinoDoc.ActiveDoc
z_raw = doc.Strings.GetValue("dossier_zeichnungsebenen")
e_raw = doc.Strings.GetValue("dossier_ebenen")
if z_raw or e_raw:
# FIRST_RUN-Entscheidung ist MODE-AWARE: jedes Panel sendet FIRST_RUN
# wenn SEINE Slice in doc.Strings fehlt. Sonst race-conditiont das
# erste APPLY (das nur eine Slice schreibt) und das andere Panel
# kriegt STATE_SYNC mit leerer Slice → leere UI.
my_slice_present = (e_raw if self._mode == "ebenen" else z_raw)
if my_slice_present:
try:
z = json.loads(z_raw) if z_raw else None
e = json.loads(e_raw) if e_raw else None
@@ -155,18 +160,20 @@ class EbenenBridge(panel_base.BaseBridge):
if t == "READY":
self._on_ready()
elif t == "APPLY":
# Beide Panels koennen APPLY schicken. Wenn nur eine Slice
# gesendet wird, fehlende aus doc.Strings nachladen damit
# build_layers nicht mit leerer Liste arbeitet.
z_payload = p.get("zeichnungsebenen")
e_payload = p.get("ebenen")
if not z_payload:
z_raw = doc.Strings.GetValue("dossier_zeichnungsebenen")
z_payload = json.loads(z_raw) if z_raw else []
if not e_payload:
# Jedes Panel ist autoritativ fuer SEINE Slice; die andere kommt
# aus doc.Strings (Fallback) damit build_layers nicht mit leerer
# Liste arbeitet. So ueberschreiben wir nicht versehentlich die
# andere Panel-Slice mit "[]" bei Split-APPLYs.
if self._mode == "zeichnungsebenen":
z_payload = p.get("zeichnungsebenen") or []
e_raw = doc.Strings.GetValue("dossier_ebenen")
e_payload = json.loads(e_raw) if e_raw else []
self._apply(z_payload, e_payload)
self._apply(z_payload, e_payload, save_z=True, save_e=False)
else:
e_payload = p.get("ebenen") or []
z_raw = doc.Strings.GetValue("dossier_zeichnungsebenen")
z_payload = json.loads(z_raw) if z_raw else []
self._apply(z_payload, e_payload, save_z=False, save_e=True)
elif t == "LAYER_STYLE":
layer_builder.update_layer_style(doc, p["code"], p.get("color"), p.get("lw"))
if p.get("color") is not None:
@@ -210,10 +217,10 @@ class EbenenBridge(panel_base.BaseBridge):
# ---- Helpers ----
def _apply(self, zeichnungsebenen, ebenen):
print("[EBENEN] _apply START z={} e={}".format(
def _apply(self, zeichnungsebenen, ebenen, save_z=True, save_e=True):
print("[EBENEN] _apply START z={} e={} (save_z={} save_e={})".format(
len(zeichnungsebenen) if zeichnungsebenen else 0,
len(ebenen) if ebenen else 0))
len(ebenen) if ebenen else 0, save_z, save_e))
doc = Rhino.RhinoDoc.ActiveDoc
# Vor dem Schreiben: alten Fill-Stand snapshotten, damit wir hinterher
@@ -260,7 +267,9 @@ class EbenenBridge(panel_base.BaseBridge):
z_json = json.dumps(zeichnungsebenen, ensure_ascii=False)
e_json = json.dumps(ebenen, ensure_ascii=False)
print("[EBENEN] _apply: SetString ...")
if save_z:
doc.Strings.SetString("dossier_zeichnungsebenen", z_json)
if save_e:
doc.Strings.SetString("dossier_ebenen", e_json)
# Smart-Elemente (Waende) regenerieren — Geschoss-Hoehen/OKFF
# haben sich evtl. geaendert, gebundene Waende muessen neu