From e96de793a946893853fb0fdd237681733493af9f Mon Sep 17 00:00:00 2001 From: karim Date: Mon, 18 May 2026 23:50:21 +0200 Subject: [PATCH] Ebenen-Split: FIRST_RUN-Race + Slice-Authoritaet fixen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- rhino/rhinopanel.py | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/rhino/rhinopanel.py b/rhino/rhinopanel.py index 2ab4475..960dc50 100644 --- a/rhino/rhinopanel.py +++ b/rhino/rhinopanel.py @@ -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,8 +267,10 @@ 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 ...") - doc.Strings.SetString("dossier_zeichnungsebenen", z_json) - doc.Strings.SetString("dossier_ebenen", e_json) + 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 # extrudiert werden. Best-effort, faengt jeden Fehler ab.