#! python 3 # -*- coding: utf-8 -*- """ overrides_panel.py OVERRIDES-Panel: Rule-Editor fuer grafische Overrides. Liest/schreibt rhino/overrides.py-Engine. """ import os import sys import uuid import Rhino 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 import overrides PANEL_GUID_STR = "8f2a7b6c-9f3a-4f4d-e6f7-08192a3c4d62" def _list_layer_names(doc): out = [] try: for layer in doc.Layers: if layer is None or layer.IsDeleted: continue try: out.append({"name": layer.Name, "fullPath": layer.FullPath or layer.Name}) except Exception: pass except Exception: pass return out def _list_linetypes(doc): out = [] try: for lt in doc.Linetypes: try: if lt.Name and not lt.IsDeleted: out.append(lt.Name) except Exception: pass except Exception: pass return out def _list_hatch_patterns(doc): out = [] try: for i in range(doc.HatchPatterns.Count): try: hp = doc.HatchPatterns[i] if hp and hp.Name: out.append(hp.Name) except Exception: pass except Exception: pass return out def _payload(doc): cfg = overrides.load_config(doc) return { "enabled": bool(cfg.get("enabled")), "rules": cfg.get("rules") or [], "layers": _list_layer_names(doc), "linetypes": _list_linetypes(doc), "hatchPatterns": _list_hatch_patterns(doc), "presets": overrides.list_presets(), "activePreset": cfg.get("activePreset"), } class OverridesBridge(panel_base.BaseBridge): def __init__(self): panel_base.BaseBridge.__init__(self, "overrides") def _on_ready(self): self._send_state() def _send_state(self): doc = Rhino.RhinoDoc.ActiveDoc self.send("STATE", _payload(doc)) # Oberleiste mit-informieren: deren Topbar zeigt # Toggle + Preset-Dropdown, das vom selben State abhaengt. # Cache invalidieren, dann force-send, sonst sieht die Topbar # neue Rules/Presets erst beim naechsten Toggle. try: b = sc.sticky.get("oberleiste_bridge") if b is not None: b._cached_overrides = None b._send_state(force=True) except Exception as ex: print("[OVERRIDES] notify oberleiste:", ex) 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 = {} doc = Rhino.RhinoDoc.ActiveDoc if t == "READY" or t == "REQUEST_STATE": self._on_ready() elif t == "SET_ENABLED": overrides.set_enabled(doc, bool(p.get("enabled"))) self._send_state() elif t == "ADD_RULE": cfg = overrides.load_config(doc) rule = p.get("rule") or {} if not rule.get("id"): rule["id"] = "rule_" + uuid.uuid4().hex[:8] rule.setdefault("name", "Neue Regel") rule.setdefault("enabled", True) rule.setdefault("condition", {"type": "layer_name", "operator": "equals", "value": ""}) rule.setdefault("actions", {}) # Neue Regel oben einfuegen (= hoechste Prioritaet) rules = cfg.get("rules") or [] rules.insert(0, rule) overrides.update_rules(doc, rules, cfg.get("enabled")) self._send_state() elif t == "UPDATE_RULE": cfg = overrides.load_config(doc) rid = p.get("id") patch = p.get("rule") or {} rules = cfg.get("rules") or [] for i, r in enumerate(rules): if r.get("id") == rid: rules[i] = patch break overrides.update_rules(doc, rules, cfg.get("enabled")) self._send_state() elif t == "DELETE_RULE": cfg = overrides.load_config(doc) rid = p.get("id") rules = [r for r in (cfg.get("rules") or []) if r.get("id") != rid] overrides.update_rules(doc, rules, cfg.get("enabled")) self._send_state() elif t == "REORDER_RULES": cfg = overrides.load_config(doc) order = p.get("order") or [] by_id = {r.get("id"): r for r in (cfg.get("rules") or [])} new_rules = [by_id[i] for i in order if i in by_id] # Verbleibende (falls Liste inkonsistent) hinten anhaengen for r in (cfg.get("rules") or []): if r not in new_rules: new_rules.append(r) overrides.update_rules(doc, new_rules, cfg.get("enabled")) self._send_state() elif t == "DUPLICATE_RULE": cfg = overrides.load_config(doc) rid = p.get("id") rules = cfg.get("rules") or [] for i, r in enumerate(rules): if r.get("id") == rid: import json clone = json.loads(json.dumps(r, ensure_ascii=False)) clone["id"] = "rule_" + uuid.uuid4().hex[:8] clone["name"] = (r.get("name", "Regel") + " Kopie") rules.insert(i + 1, clone) break overrides.update_rules(doc, rules, cfg.get("enabled")) self._send_state() elif t == "REAPPLY": if overrides.load_config(doc).get("enabled"): overrides.restore_all(doc) overrides.apply_all(doc) self._send_state() elif t == "CLEAR_RULES": # Alle Regeln entfernen und activePreset clearen — wird vom # Topbar/Kombination-Dropdown beim Wechsel auf "— neu / keine —" # gefeuert, damit der Editor wirklich leer ist. cfg = overrides.load_config(doc) overrides.update_rules(doc, [], cfg.get("enabled")) self._send_state() # --- Presets (cross-doc) ---------------------------------------- elif t == "SAVE_PRESET": name = (p.get("name") or "").strip() if name: cfg = overrides.load_config(doc) overrides.save_preset(name, cfg.get("rules") or []) self._send_state() elif t == "LOAD_PRESET": name = (p.get("name") or "").strip() mode = p.get("mode") or "replace" # 'replace' oder 'append' if mode == "replace": # set_active_preset macht alles richtig: Rules ersetzen, # activePreset = name, ggf. neu anwenden. overrides.set_active_preset(doc, name) else: # Append-Mode: bestehende + Preset-Rules. activePreset wird # in update_rules auf None gesetzt — passt, weil's eine # Mischung ist, kein einzelnes Preset mehr. rules = overrides.load_preset(name) if rules is not None: cfg = overrides.load_config(doc) new_rules = list(cfg.get("rules") or []) + list(rules) overrides.update_rules(doc, new_rules, cfg.get("enabled")) self._send_state() elif t == "DELETE_PRESET": name = (p.get("name") or "").strip() if name: overrides.delete_preset(name) self._send_state() def _bridge_factory(): b = OverridesBridge() try: overrides.install_listeners() except Exception as ex: print("[OVERRIDES] install_listeners:", ex) # Bridge im sticky ablegen, damit andere Panels (z.B. Oberleiste) sie # bei Cross-Panel-Updates erreichen koennen. sc.sticky["overrides_bridge"] = b return b panel_base.register_and_open("overrides", "OVERRIDES", PANEL_GUID_STR, _bridge_factory, icon_spec=("tune", "#b5621e"), min_size=(720, 560))