#! python 3 # -*- coding: utf-8 -*- """ layouts.py LAYOUTS-Panel: Layout-Pages erstellen + Details mit Ausschnitten bestuecken. Phase 1 — Snapshot-Mode: Ausschnitt wird beim Zuweisen auf das Detail angewendet, Re-Sync per Knopf. Live-Link und Masterlayouts kommen spaeter. """ import os import sys import json import Rhino import Rhino.Geometry as rg import System import scriptcontext as sc _HERE = os.path.dirname(os.path.abspath(__file__)) if _HERE not in sys.path: sys.path.insert(0, _HERE) import panel_base PANEL_GUID_STR = "4e5d6c7b-8a9f-4e3d-c4b5-a6b7c8d9e0f1" # UserString-Key auf jedem Detail — speichert die Ausschnitt-Bindung. _BIND_KEY = "dossier_bound_ausschnitt" # Doc-Strings fuer Layout-Folder-Organisation (analog Ausschnitte-Ordner). _FOLDER_LIST_KEY = "dossier_layout_folders" # JSON-Array: ["A","B",...] _FOLDER_MAP_KEY = "dossier_layout_folder_map" # JSON-Dict: {pageId: "A"} # Vordefinierte Papierformate in Millimetern (Welt-Einheit wird beim Erstellen # umgerechnet, falls das Doc nicht auf mm steht). PAPER_SIZES_MM = { "A0": (841, 1189), "A1": (594, 841), "A2": (420, 594), "A3": (297, 420), "A4": (210, 297), "Letter": (216, 279), } def _page_unit_system(doc): """Rhino hat ein eigenes Unit-System fuer Layouts (PageUnitSystem), das sich vom Modell-Unit-System unterscheiden kann (z.B. Modell in Meter, Plan in Millimeter — Standard bei Architektur). AddPageView und SetPageSize erwarten Werte in PageUnitSystem, NICHT in ModelUnitSystem.""" try: return doc.PageUnitSystem except Exception: return doc.ModelUnitSystem def _mm_to_page(doc): """Faktor: mm -> Page-Unit. Wird fuer AddPageView/SetPageSize benutzt.""" try: return Rhino.RhinoMath.UnitScale(Rhino.UnitSystem.Millimeters, _page_unit_system(doc)) except Exception: return 1.0 def _page_to_mm(doc): """Faktor: Page-Unit -> mm. Wird beim PDF-Export gebraucht.""" try: return Rhino.RhinoMath.UnitScale(_page_unit_system(doc), Rhino.UnitSystem.Millimeters) except Exception: return 1.0 def _page_size_in_doc(doc, fmt, landscape): """Liefert (width, height) in Page-Units.""" if fmt not in PAPER_SIZES_MM: return None w_mm, h_mm = PAPER_SIZES_MM[fmt] if landscape: w_mm, h_mm = h_mm, w_mm f = _mm_to_page(doc) return (w_mm * f, h_mm * f) def _load_folder_list(doc): """Liefert die Liste explizit angelegter Ordner (Reihenfolge bleibt).""" raw = doc.Strings.GetValue(_FOLDER_LIST_KEY) if not raw: return [] try: data = json.loads(raw) return [n for n in data if isinstance(n, str) and n] except Exception: return [] def _save_folder_list(doc, names): try: doc.Strings.SetString(_FOLDER_LIST_KEY, json.dumps(list(names), ensure_ascii=False)) except Exception as ex: print("[LAYOUTS] _save_folder_list:", ex) def _load_folder_map(doc): """page_id -> folder_name.""" raw = doc.Strings.GetValue(_FOLDER_MAP_KEY) if not raw: return {} try: data = json.loads(raw) if isinstance(data, dict): return {k: v for k, v in data.items() if isinstance(v, str) and v} except Exception: pass return {} def _save_folder_map(doc, m): try: doc.Strings.SetString(_FOLDER_MAP_KEY, json.dumps(m, ensure_ascii=False)) except Exception as ex: print("[LAYOUTS] _save_folder_map:", ex) def _list_layouts(doc): """Liefert dict-Liste aller PageView-Layouts inkl. Ordner-Zuweisung. Groessen werden ZUSAETZLICH in mm geliefert, damit das Frontend ohne Unit-Kenntnis formatieren kann.""" fmap = _load_folder_map(doc) pu_to_mm = _page_to_mm(doc) out = [] for v in doc.Views: if isinstance(v, Rhino.Display.RhinoPageView): try: pid = str(v.MainViewport.Id) w = float(v.PageWidth) h = float(v.PageHeight) out.append({ "id": pid, "name": v.PageName or "", "width": w, "height": h, "widthMm": w * pu_to_mm, "heightMm": h * pu_to_mm, "detailCount": len(v.GetDetailViews() or []), "folder": fmap.get(pid, ""), }) except Exception as ex: print("[LAYOUTS] list_layouts:", ex) return out def _find_page_by_id(doc, page_id): """page_id ist die MainViewport.Id (str). Liefert RhinoPageView oder None.""" for v in doc.Views: if isinstance(v, Rhino.Display.RhinoPageView): try: if str(v.MainViewport.Id) == page_id: return v except Exception: pass return None def _find_detail_by_id(doc, page, detail_id): """Detail-Object auf einer Page anhand seiner Detail-ID.""" try: for d in page.GetDetailViews(): if str(d.Id) == detail_id: return d except Exception as ex: print("[LAYOUTS] find_detail:", ex) return None def _get_detail_binding(detail): """Liest gebundene Ausschnitt-ID aus dem Detail-UserString.""" try: v = detail.Attributes.GetUserString(_BIND_KEY) return v if v else None except Exception: return None def _set_detail_binding(detail, snap_id): """Schreibt/loescht die Ausschnitt-ID auf das Detail-UserString.""" try: if snap_id: detail.Attributes.SetUserString(_BIND_KEY, snap_id) else: detail.Attributes.SetUserString(_BIND_KEY, "") # leerer String = "kein Binding" detail.CommitChanges() return True except Exception as ex: print("[LAYOUTS] set_binding:", ex) return False def _detail_dict(detail, snap_lookup): """Serialisiert ein Detail fuer das Frontend.""" bbox = detail.Geometry.GetBoundingBox(True) # in Welt-Koordinaten der Page bound_id = _get_detail_binding(detail) bound_name = (snap_lookup.get(bound_id, {}) or {}).get("name") if bound_id else None try: vp_name = detail.Viewport.Name except Exception: vp_name = "" return { "id": str(detail.Id), "name": vp_name, "x": float(bbox.Min.X), "y": float(bbox.Min.Y), "width": float(bbox.Max.X - bbox.Min.X), "height": float(bbox.Max.Y - bbox.Min.Y), "boundAusschnitt": bound_id, "boundAusschnittName": bound_name, } def _snap_lookup(doc): """Map snap_id -> snap dict. Wird fuer Detail-Display gebraucht.""" out = {} try: raw = doc.Strings.GetValue("dossier_ausschnitte") if raw: data = json.loads(raw) if isinstance(data, list): for s in data: if isinstance(s, dict) and s.get("id"): out[s["id"]] = s except Exception: pass return out def _slim_snaps(doc): """Schlanke Liste von Snapshots fuer Frontend-Dropdown.""" out = [] try: raw = doc.Strings.GetValue("dossier_ausschnitte") if not raw: return [] data = json.loads(raw) if not isinstance(data, list): return [] for s in data: if isinstance(s, dict) and s.get("id"): out.append({ "id": s.get("id"), "name": s.get("name"), "folder": s.get("folder", ""), "scale": s.get("scale", ""), }) except Exception: pass return out # --- Bridge ----------------------------------------------------------------- class LayoutsBridge(panel_base.BaseBridge): def __init__(self): panel_base.BaseBridge.__init__(self, "layouts") def _on_ready(self): self._send_state() def handle(self, data): if not isinstance(data, dict): return t = data.get("type", "") p = data.get("payload") or {} if not isinstance(p, dict): p = {} if t == "READY": self._on_ready() elif t == "LIST": self._send_state() elif t == "NEW_LAYOUT": self._new_layout(p) elif t == "DELETE_LAYOUT": self._delete_layout(p.get("id")) elif t == "RENAME_LAYOUT": self._rename_layout(p.get("id"), p.get("name")) elif t == "SET_PAGE_SIZE": self._set_page_size(p) elif t == "ACTIVATE_LAYOUT": self._activate_layout(p.get("id")) elif t == "EXPORT_PDF": self._export_pdf(p) elif t == "ADD_DETAIL": self._add_detail(p) elif t == "DELETE_DETAIL": self._delete_detail(p.get("pageId"), p.get("detailId")) elif t == "BIND_AUSSCHNITT": self._bind_ausschnitt(p) elif t == "SYNC_DETAIL": self._sync_detail(p.get("pageId"), p.get("detailId")) elif t == "SYNC_LAYOUT": self._sync_layout(p.get("id")) # Ordner-Management elif t == "ADD_FOLDER": self._add_folder(p.get("name")) elif t == "REMOVE_FOLDER": self._remove_folder(p.get("name")) elif t == "SET_FOLDER": self._set_folder(p.get("id"), p.get("folder") or "") elif t == "OPEN_LAYOUT_DIALOG": self._open_layout_dialog(p) # --- State-Snapshot ----------------------------------------------------- def _send_state(self): doc = Rhino.RhinoDoc.ActiveDoc if doc is None: self.send("STATE", {"layouts": [], "snapshots": [], "details": {}, "folders": []}) return layouts = _list_layouts(doc) snaps = _slim_snaps(doc) snap_lookup = _snap_lookup(doc) # Ordner: explizite Liste + alle in Layouts referenzierten explicit_folders = _load_folder_list(doc) for l in layouts: f = l.get("folder") if f and f not in explicit_folders: explicit_folders.append(f) # Pro Layout die Details mitgeben details = {} for v in doc.Views: if isinstance(v, Rhino.Display.RhinoPageView): try: pid = str(v.MainViewport.Id) details[pid] = [_detail_dict(d, snap_lookup) for d in v.GetDetailViews()] except Exception as ex: print("[LAYOUTS] details for page:", ex) self.send("STATE", { "layouts": layouts, "snapshots": snaps, "details": details, "folders": explicit_folders, }) # --- Ordner ------------------------------------------------------------- def _add_folder(self, name): if not name: return name = name.strip() if not name: return doc = Rhino.RhinoDoc.ActiveDoc folders = _load_folder_list(doc) if name not in folders: folders.append(name) _save_folder_list(doc, folders) self._send_state() def _remove_folder(self, name): if not name: return doc = Rhino.RhinoDoc.ActiveDoc folders = [f for f in _load_folder_list(doc) if f != name] _save_folder_list(doc, folders) # Layouts aus diesem Ordner herausnehmen (zurueck auf Root) m = _load_folder_map(doc) m = {k: v for k, v in m.items() if v != name} _save_folder_map(doc, m) self._send_state() def _set_folder(self, page_id, folder): if not page_id: return doc = Rhino.RhinoDoc.ActiveDoc m = _load_folder_map(doc) if folder: m[page_id] = folder # Sicherstellen dass der Ordner-Name in der expliziten Liste ist folders = _load_folder_list(doc) if folder not in folders: folders.append(folder) _save_folder_list(doc, folders) else: if page_id in m: del m[page_id] _save_folder_map(doc, m) self._send_state() # --- Layouts ------------------------------------------------------------ def _resolve_size(self, doc, p): """Bestimmt (w, h) in Page-Units aus Payload — Format-Name ODER customWidth/customHeight in mm.""" fmt = p.get("format") if fmt == "custom": try: wmm = float(p.get("customWidth")) hmm = float(p.get("customHeight")) except Exception: return None if wmm <= 0 or hmm <= 0: return None f = _mm_to_page(doc) return (wmm * f, hmm * f) if fmt in PAPER_SIZES_MM: return _page_size_in_doc(doc, fmt, bool(p.get("landscape", True))) return None def _new_layout(self, p): doc = Rhino.RhinoDoc.ActiveDoc if doc is None: return name = (p.get("name") or "").strip() size = self._resolve_size(doc, p) if size is None: print("[LAYOUTS] ungueltige Groesse:", p); return w, h = size if not name: name = "Layout {}".format(len(_list_layouts(doc)) + 1) try: page = doc.Views.AddPageView(name, w, h) if page is None: print("[LAYOUTS] AddPageView fehlgeschlagen"); return print("[LAYOUTS] '{}' angelegt ({}x{})".format(name, w, h)) except Exception as ex: print("[LAYOUTS] AddPageView Fehler:", ex) self._send_state() def _set_page_size(self, p): """Aendert die Groesse einer bestehenden Layout-Seite via RhinoPageView.SetPageSize (Rhino 8). Faellt zurueck auf Property- Setter, falls die Methode nicht existiert.""" doc = Rhino.RhinoDoc.ActiveDoc page = _find_page_by_id(doc, p.get("id")) if page is None: return size = self._resolve_size(doc, p) if size is None: print("[LAYOUTS] ungueltige Groesse fuer set_page_size:", p); return w, h = size done = False # 1) SetPageSize(double, double) — Rhino 8 if hasattr(page, "SetPageSize"): try: page.SetPageSize(float(w), float(h)) done = True print("[LAYOUTS] SetPageSize -> {}x{}".format(w, h)) except Exception as ex: print("[LAYOUTS] SetPageSize fehlgeschlagen:", ex) # 2) Fallback: Properties (haengt von Rhino-Version ab) if not done: try: page.PageWidth = float(w) page.PageHeight = float(h) done = True print("[LAYOUTS] PageWidth/Height-Properties -> {}x{}".format(w, h)) except Exception as ex: print("[LAYOUTS] Property-Setter fehlgeschlagen:", ex) if not done: print("[LAYOUTS] Konnte Seiten-Groesse nicht setzen — bitte ueber Rhinos Layout-Dialog aendern") try: page.Redraw() except Exception: pass self._send_state() def _export_pdf(self, p): """Exportiert Layouts als ein gemeinsames PDF. Akzeptiert: - "ids": Liste von Layout-IDs (Multi-Export) - "id": einzelne Layout-ID - sonst alle Layouts. Save-Dialog via Eto.Forms, Inhalt via FilePdf API mit EXPLIZITER Pixel-Groesse aus Page-Dimensionen (sonst wird's ein Mini-Bildchen).""" doc = Rhino.RhinoDoc.ActiveDoc if doc is None: return ids = p.get("ids") if not ids and p.get("id"): ids = [p.get("id")] dpi = float(p.get("dpi") or 300) targets = [] if ids: for pid in ids: page = _find_page_by_id(doc, pid) if page is not None: targets.append(page) else: for v in doc.Views: if isinstance(v, Rhino.Display.RhinoPageView): targets.append(v) if not targets: print("[LAYOUTS] kein Layout zu exportieren"); return path = self._pick_save_path(doc, targets) if not path: print("[LAYOUTS] Export abgebrochen") return # PageWidth/PageHeight sind in Page-Units — fuer die Pixel-Berechnung # rechnen wir in mm um (1 inch = 25.4 mm). mm_per_pu = _page_to_mm(doc) # Remember current active view to restore afterwards prev_view = None try: prev_view = doc.Views.ActiveView except Exception: pass try: pdf = Rhino.FileIO.FilePdf.Create() n_added = 0 for page in targets: try: # Page muss kurz aktiv sein — sonst rendert ViewCapture # leer (vor allem auf macOS). Kurzer Idle-Wait gibt dem # Renderer Zeit, sonst kommt's beim ERSTEN Page-Wechsel # zu Race-Bedingungen. try: page.SetPageAsActive() doc.Views.ActiveView = page page.Redraw() doc.Views.Redraw() Rhino.RhinoApp.Wait() # einen Idle-Tick verarbeiten except Exception: pass # Page-Size in mm -> Pixel @dpi w_mm = float(page.PageWidth) * mm_per_pu h_mm = float(page.PageHeight) * mm_per_pu if w_mm <= 0 or h_mm <= 0: print("[LAYOUTS] Page '{}' hat ungueltige Groesse: {}x{} mm".format( page.PageName, w_mm, h_mm)) continue px_w = max(1, int(round(w_mm / 25.4 * dpi))) px_h = max(1, int(round(h_mm / 25.4 * dpi))) # Settings mit expliziter Groesse — die Drei-Argument- # Variante ist die zuverlaessige fuer PDF-Export. settings = None try: size = System.Drawing.Size(px_w, px_h) settings = Rhino.Display.ViewCaptureSettings(page, size, dpi) except Exception as ex: print("[LAYOUTS] 3-arg Settings:", ex) if settings is None: settings = Rhino.Display.ViewCaptureSettings(page, dpi) # Vector-Output — sonst wird's gerastert und klein try: settings.RasterMode = False except Exception: pass pdf.AddPage(settings) n_added += 1 print("[LAYOUTS] add page '{}': {}x{}mm -> {}x{}px".format( page.PageName, w_mm, h_mm, px_w, px_h)) except Exception as ex: print("[LAYOUTS] add_page '{}': {}".format(page.PageName, ex)) if n_added == 0: print("[LAYOUTS] Keine Seiten konnten hinzugefuegt werden") else: pdf.Write(path) print("[LAYOUTS] PDF geschrieben: {} ({} Seite(n))".format(path, n_added)) except Exception as ex: print("[LAYOUTS] PDF-Export fehlgeschlagen:", ex) finally: # Vorherige View wieder aktivieren if prev_view is not None: try: doc.Views.ActiveView = prev_view except Exception: pass def _pick_save_path(self, doc, targets): """Eto.Forms SaveFileDialog — Default: Doc-Folder + erster Layout-Name.""" try: import Eto.Forms as forms dlg = forms.SaveFileDialog() dlg.Filters.Add(forms.FileFilter("PDF", ".pdf")) try: dlg.CurrentFilterIndex = 0 except Exception: pass # Default-Filename — Layout-Name oder Doc-Name if len(targets) == 1: base = targets[0].PageName or "Layout" else: base = "Layouts" if doc.Path: base = os.path.splitext(os.path.basename(doc.Path))[0] + "_Layouts" dlg.FileName = "{}.pdf".format(base) # Default-Folder — neben der .3dm wenn vorhanden if doc.Path: try: dlg.Directory = System.Uri(os.path.dirname(doc.Path)) except Exception: pass try: import Rhino.UI as RhinoUI parent = RhinoUI.RhinoEtoApp.MainWindow except Exception: parent = None result = dlg.ShowDialog(parent) if str(result) != "Ok": return None path = dlg.FileName if path and not path.lower().endswith(".pdf"): path += ".pdf" return path except Exception as ex: print("[LAYOUTS] SaveFileDialog:", ex) return None def _delete_layout(self, page_id): doc = Rhino.RhinoDoc.ActiveDoc page = _find_page_by_id(doc, page_id) if page is None: print("[LAYOUTS] delete: page not found", page_id); return name = page.PageName # Andere View aktivieren — Rhino verweigert oft, die aktive Page zu loeschen try: for v in doc.Views: if v is not page and not isinstance(v, Rhino.Display.RhinoPageView): doc.Views.ActiveView = v break else: # Nur PageViews da — irgendeine andere aktivieren for v in doc.Views: if v is not page: doc.Views.ActiveView = v break except Exception as ex: print("[LAYOUTS] activate other view:", ex) done = False # Methode 1: doc.Views.Remove try: r = doc.Views.Remove(page) print("[LAYOUTS] doc.Views.Remove returned:", r) # Verify if _find_page_by_id(doc, page_id) is None: done = True except Exception as ex: print("[LAYOUTS] doc.Views.Remove failed:", ex) # Methode 2: Close + Delete via RunScript-Fallback if not done: try: Rhino.RhinoApp.RunScript('_-CommandHistory _Hide _Enter', False) except Exception: pass try: # Rhino 8 hat _-Layout _Delete Rhino.RhinoApp.RunScript('_-Layout _Delete "{}" _Enter'.format(name), False) if _find_page_by_id(doc, page_id) is None: done = True print("[LAYOUTS] geloescht via _-Layout _Delete") except Exception as ex: print("[LAYOUTS] _-Layout _Delete failed:", ex) if not done: print("[LAYOUTS] Konnte Layout '{}' nicht loeschen — bitte manuell ueber Layout-Tab".format(name)) # Folder-Mapping aufraeumen try: m = _load_folder_map(doc) if page_id in m: del m[page_id] _save_folder_map(doc, m) except Exception: pass self._send_state() def _rename_layout(self, page_id, name): doc = Rhino.RhinoDoc.ActiveDoc page = _find_page_by_id(doc, page_id) if page is None or not name: return try: page.PageName = name.strip() except Exception as ex: print("[LAYOUTS] Rename page:", ex) self._send_state() def _activate_layout(self, page_id): doc = Rhino.RhinoDoc.ActiveDoc page = _find_page_by_id(doc, page_id) if page is None: return try: page.SetPageAsActive() doc.Views.ActiveView = page except Exception as ex: print("[LAYOUTS] activate page:", ex) # --- Details ------------------------------------------------------------ def _add_detail(self, p): """Neues Detail auf einer Seite anlegen. Optional gleich an einen Ausschnitt binden (= Snapshot anwenden).""" doc = Rhino.RhinoDoc.ActiveDoc page = _find_page_by_id(doc, p.get("pageId")) if page is None: return # Bounding-Box auf der Seite: default 80% der Seitenflaeche, zentriert try: pw = page.PageWidth ph = page.PageHeight margin_x = pw * 0.1 margin_y = ph * 0.1 c0 = rg.Point2d(margin_x, margin_y) c1 = rg.Point2d(pw - margin_x, ph - margin_y) # Erlaubt Override per Payload for k_min, default in (("x", margin_x), ("y", margin_y)): v = p.get(k_min) if isinstance(v, (int, float)): if k_min == "x": c0 = rg.Point2d(float(v), c0.Y) if k_min == "y": c0 = rg.Point2d(c0.X, float(v)) for k_max, default in (("x2", pw - margin_x), ("y2", ph - margin_y)): v = p.get(k_max) if isinstance(v, (int, float)): if k_max == "x2": c1 = rg.Point2d(float(v), c1.Y) if k_max == "y2": c1 = rg.Point2d(c1.X, float(v)) proj = Rhino.Display.DefinedViewportProjection.Top detail = page.AddDetailView("Detail", c0, c1, proj) if detail is None: print("[LAYOUTS] AddDetailView gab None"); return page.Redraw() # Optional Ausschnitt binden + anwenden snap_id = p.get("ausschnittId") if snap_id: _set_detail_binding(detail, snap_id) try: import ausschnitte ausschnitte.apply_snapshot_to_detail(doc, detail, snap_id) except Exception as ex: print("[LAYOUTS] initial apply:", ex) except Exception as ex: print("[LAYOUTS] AddDetailView:", ex) self._send_state() def _delete_detail(self, page_id, detail_id): doc = Rhino.RhinoDoc.ActiveDoc page = _find_page_by_id(doc, page_id) if page is None: return detail = _find_detail_by_id(doc, page, detail_id) if detail is None: return try: doc.Objects.Delete(detail.Id, True) page.Redraw() except Exception as ex: print("[LAYOUTS] Delete detail:", ex) self._send_state() def _bind_ausschnitt(self, p): """Setzt die Binding und wendet den Ausschnitt sofort an (Snapshot-Mode).""" doc = Rhino.RhinoDoc.ActiveDoc page = _find_page_by_id(doc, p.get("pageId")) if page is None: return detail = _find_detail_by_id(doc, page, p.get("detailId")) if detail is None: return snap_id = p.get("ausschnittId") or None _set_detail_binding(detail, snap_id) if snap_id: try: import ausschnitte ausschnitte.apply_snapshot_to_detail(doc, detail, snap_id) except Exception as ex: print("[LAYOUTS] apply on bind:", ex) self._send_state() def _sync_detail(self, page_id, detail_id): doc = Rhino.RhinoDoc.ActiveDoc page = _find_page_by_id(doc, page_id) if page is None: return detail = _find_detail_by_id(doc, page, detail_id) if detail is None: return snap_id = _get_detail_binding(detail) if not snap_id: print("[LAYOUTS] sync: kein Binding auf diesem Detail") return try: import ausschnitte ausschnitte.apply_snapshot_to_detail(doc, detail, snap_id) except Exception as ex: print("[LAYOUTS] sync:", ex) self._send_state() def _sync_layout(self, page_id): """Alle Details der Page mit ihren Bindings re-applien.""" doc = Rhino.RhinoDoc.ActiveDoc page = _find_page_by_id(doc, page_id) if page is None: return try: import ausschnitte for d in page.GetDetailViews(): snap_id = _get_detail_binding(d) if snap_id: ausschnitte.apply_snapshot_to_detail(doc, d, snap_id) page.Redraw() except Exception as ex: print("[LAYOUTS] sync layout:", ex) self._send_state() def _open_layout_dialog(self, p): """Oeffnet ein Satelliten-Fenster mit dem Layout-Erstellen/Bearbeiten Dialog. mode = 'new' | 'edit'. Bei 'edit' wird `layout` (id, name, width, height) mitgeschickt.""" outer = self mode = (p.get("mode") or "new") layout = p.get("layout") or None bridge_holder = {"form": None} def _apply(payload): if mode == "new": outer._new_layout({ "name": payload.get("name") or "", "format": payload.get("format") or "A3", "landscape": bool(payload.get("landscape", True)), "customWidth": payload.get("customWidth"), "customHeight": payload.get("customHeight"), }) elif mode == "edit" and layout and layout.get("id"): outer._set_page_size({ "id": layout.get("id"), "format": payload.get("format") or "A3", "landscape": bool(payload.get("landscape", True)), "customWidth": payload.get("customWidth"), "customHeight": payload.get("customHeight"), }) class _LayoutDialogBridge(panel_base.BaseBridge): def __init__(self): panel_base.BaseBridge.__init__(self, "layout_dialog") def _on_ready(self): self.send("LAYOUT_DIALOG_STATE", { "mode": mode, "layout": layout, }) def handle(self, data): if not isinstance(data, dict): return t = data.get("type", "") pp = data.get("payload") or {} if t == "READY": self._on_ready() elif t == "SAVE": _apply(pp) try: f = bridge_holder.get("form") if f is not None: f.Close() except Exception: pass elif t == "CANCEL": try: f = bridge_holder.get("form") if f is not None: f.Close() except Exception: pass b = _LayoutDialogBridge() title = "Neues Layout" if mode == "new" else "Papierformat: {}".format( (layout or {}).get("name", "")) bridge_holder["form"] = panel_base.open_satellite_window( "layout_dialog", params={"mode": mode, "layout": layout}, title=title, size=(440, 380), bridge=b) def _bridge_factory(): b = LayoutsBridge() sc.sticky["layouts_bridge"] = b return b panel_base.register_and_open("layouts", "Layouts", PANEL_GUID_STR, _bridge_factory, icon_spec=("view_quilt", "#7a5fa8"))