diff --git a/rhino/panel_base.py b/rhino/panel_base.py index d3ce67e..7e21191 100644 --- a/rhino/panel_base.py +++ b/rhino/panel_base.py @@ -198,8 +198,12 @@ class BaseBridge(object): # --- HTML laden ------------------------------------------------------------- -def load_inline(wv, mode): - """Laedt dist/index.html inline und injiziert window.PANEL_MODE.""" +def load_inline(wv, mode, params=None): + """Laedt dist/index.html inline und injiziert window.PANEL_MODE. + + `params` (optional dict): wird als `window.PANEL_PARAMS` injiziert. Wird + von Satelliten-Fenstern (z.B. Settings-Dialoge) verwendet um initial- + State an die React-App zu uebergeben.""" if not os.path.exists(_DIST): print("[{}] dist nicht gefunden".format(mode.upper())) return @@ -207,7 +211,14 @@ def load_inline(wv, mode): with open(_DIST, "rb") as f: html = f.read().decode("utf-8") - mode_script = ''.format(mode) + parts = ['') + mode_script = ''.join(parts) if "" in html: html = html.replace("", mode_script + "") else: @@ -267,6 +278,92 @@ def attach_webview(panel, bridge, mode): Rhino.RhinoApp.Idle += on_idle +# --- Satelliten-Fenster (echtes Rhino-Fenster mit eingebetteter WebView) ---- + +def open_satellite_window(mode, params=None, title=None, size=(420, 560), + on_save=None, on_cancel=None): + """Oeffnet ein echtes Rhino-Fenster (Eto.Form) mit eingebetteter WebView. + Die WebView laedt die React-App mit dem gegebenen `mode` und `params`. + + Die React-App sendet via Bridge `SAVE`/`CANCEL`-Messages. Wir rufen + dann die jeweilige Callback-Funktion auf (mit dem Save-Payload) und + schliessen das Fenster. + + Returns die Form-Instance (User kann sie speichern um sie spaeter + programmatisch zu schliessen).""" + + form = forms.Form() + if title is None: title = mode.replace('_', ' ').title() + form.Title = title + try: + form.ClientSize = drawing.Size(int(size[0]), int(size[1])) + except Exception: pass + form.Resizable = True + form.Topmost = False + + wv = forms.WebView() + + # Inline-Bridge fuer Satelliten-Fenster: handle SAVE/CANCEL, schliesse + # bei beiden das Fenster. + class _SatelliteBridge(BaseBridge): + def __init__(self): + BaseBridge.__init__(self, mode) + def handle(self, data): + t = data.get("type", "") + p = data.get("payload") or {} + if t == "READY": + # React liest PANEL_PARAMS direkt vom window-Object — wir + # muessen also nichts mehr aktiv senden. + pass + elif t == "SAVE": + if on_save is not None: + try: on_save(p) + except Exception as ex: + print("[{}] on_save: {}".format(mode.upper(), ex)) + try: form.Close() + except Exception: pass + elif t == "CANCEL": + if on_cancel is not None: + try: on_cancel() + except Exception: pass + try: form.Close() + except Exception: pass + + bridge = _SatelliteBridge() + bridge.set_webview(wv) + + def on_title_(s, e): + title_str = e.Title or "" + if not title_str.startswith("RHINOMSG::"): + return + try: + bridge.handle_raw(title_str[10:]) + except Exception as ex: + print("[{}] Message-Fehler: {}".format(mode.upper(), ex)) + finally: + try: + wv.ExecuteScript("document.title='{}';".format(mode.upper())) + except Exception: + pass + + def on_loaded(s, e): + try: wv.ExecuteScript("window.RHINO_MODE=true;") + except Exception: pass + + wv.DocumentTitleChanged += on_title_ + wv.DocumentLoaded += on_loaded + + form.Content = wv + form.Show() + # HTML nach Show() laden — sonst ist die WebView eventuell noch nicht + # gerendert und die JS-Bridge initialisiert sich seltsam. + try: + load_inline(wv, mode, params=params) + except Exception as ex: + print("[{}] Inline-Fehler: {}".format(mode.upper(), ex)) + return form + + # --- Dynamic .NET Type ------------------------------------------------------ def create_dockable_type(guid_str, type_name, assembly_name): diff --git a/rhino/rhinopanel.py b/rhino/rhinopanel.py index 4d0d5cd..8a8a2c2 100644 --- a/rhino/rhinopanel.py +++ b/rhino/rhinopanel.py @@ -216,9 +216,89 @@ class EbenenBridge(panel_base.BaseBridge): elif t == "DELETE_PRESET": self._delete_preset(p.get("name") or "") self._send_combination() + elif t == "OPEN_GESCHOSS_SETTINGS": + self._open_geschoss_settings(p.get("geschoss") or {}) + elif t == "OPEN_EBENEN_SETTINGS": + self._open_ebenen_settings(p.get("ebene") or {}, + p.get("hatchPatterns") or []) # ---- Helpers ---- + def _open_geschoss_settings(self, geschoss): + """Oeffnet ein echtes Rhino-Fenster (Eto.Form mit WebView) mit dem + GeschossSettingsDialog. Save updated den Eintrag in doc.Strings + + triggert Cross-Panel-Sync.""" + if not isinstance(geschoss, dict) or not geschoss.get("id"): + print("[EBENEN] open_geschoss_settings: kein Geschoss-Payload") + return + gid = geschoss["id"] + def on_save(updated): + doc = Rhino.RhinoDoc.ActiveDoc + if doc is None: return + z_raw = doc.Strings.GetValue("dossier_zeichnungsebenen") + if not z_raw: + print("[EBENEN] save_geschoss: kein z-Store"); return + try: + z_list = json.loads(z_raw) + except Exception as ex: + print("[EBENEN] save_geschoss JSON:", ex); return + replaced = False + for i, z in enumerate(z_list): + if isinstance(z, dict) and z.get("id") == gid: + z_list[i] = updated + replaced = True + break + if not replaced: + print("[EBENEN] save_geschoss: id {} nicht gefunden".format(gid)) + return + # Build_layers + Save via _apply (durchlaeuft ohne save_e) + e_raw = doc.Strings.GetValue("dossier_ebenen") + try: e_list = json.loads(e_raw) if e_raw else [] + except Exception: e_list = [] + self._apply(z_list, e_list, save_z=True, save_e=False) + panel_base.open_satellite_window( + "geschoss_settings", + params=geschoss, + title="Zeichnungsebene: {}".format(geschoss.get("name", "")), + size=(380, 540), + on_save=on_save) + + def _open_ebenen_settings(self, ebene, hatch_patterns): + """Oeffnet ein echtes Rhino-Fenster mit dem EbenenSettingsDialog.""" + if not isinstance(ebene, dict) or not ebene.get("code"): + print("[EBENEN] open_ebenen_settings: kein Ebene-Payload") + return + old_code = ebene["code"] + def on_save(updated): + doc = Rhino.RhinoDoc.ActiveDoc + if doc is None: return + e_raw = doc.Strings.GetValue("dossier_ebenen") + if not e_raw: + print("[EBENEN] save_ebene: kein e-Store"); return + try: + e_list = json.loads(e_raw) + except Exception as ex: + print("[EBENEN] save_ebene JSON:", ex); return + replaced = False + for i, e in enumerate(e_list): + if isinstance(e, dict) and e.get("code") == old_code: + e_list[i] = updated + replaced = True + break + if not replaced: + print("[EBENEN] save_ebene: code {} nicht gefunden".format(old_code)) + return + z_raw = doc.Strings.GetValue("dossier_zeichnungsebenen") + try: z_list = json.loads(z_raw) if z_raw else [] + except Exception: z_list = [] + self._apply(z_list, e_list, save_z=False, save_e=True) + panel_base.open_satellite_window( + "ebenen_settings", + params={"ebene": ebene, "hatchPatterns": hatch_patterns}, + title="Ebene: {}_{}".format(ebene.get("code", ""), ebene.get("name", "")), + size=(420, 600), + on_save=on_save) + 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, diff --git a/src/EbenenSettingsApp.jsx b/src/EbenenSettingsApp.jsx new file mode 100644 index 0000000..21a8bf1 --- /dev/null +++ b/src/EbenenSettingsApp.jsx @@ -0,0 +1,42 @@ +import { useEffect } from 'react' +import EbenenSettingsDialog from './components/EbenenSettingsDialog' +import { notifyReady } from './lib/rhinoBridge' + +function bridgeSend(type, payload = {}) { + if (!window.RHINO_MODE) { console.log('[Bridge] →', type, payload); return } + const json = JSON.stringify({ type, payload }) + document.title = 'RHINOMSG::' + json +} + +export default function EbenenSettingsApp() { + const initial = (typeof window !== 'undefined' && window.PANEL_PARAMS) || {} + + useEffect(() => { + notifyReady() + const blockContext = (ev) => ev.preventDefault() + document.addEventListener('contextmenu', blockContext) + return () => document.removeEventListener('contextmenu', blockContext) + }, []) + + const ebene = initial.ebene || initial + const hatchPatterns = initial.hatchPatterns || ['Solid'] + + if (!ebene || typeof ebene !== 'object' || !ebene.code) { + return