diff --git a/rhino/overrides.py b/rhino/overrides.py index 9e73b85..b4f7fae 100644 --- a/rhino/overrides.py +++ b/rhino/overrides.py @@ -54,6 +54,9 @@ _STORE_KEY = "dossier_overrides" # Globale Presets (cross-doc) — Datei im User-Home _PRESETS_DIR = os.path.expanduser("~/Library/Application Support/RhinoPanel") _PRESETS_PATH = os.path.join(_PRESETS_DIR, "override_presets.json") +# Rule-Templates: einzelne wiederverwendbare Regeln (cross-doc). Andere +# Datei damit User Combo-Presets und Einzel-Templates separat verwalten kann. +_RULE_TPL_PATH = os.path.join(_PRESETS_DIR, "override_rule_templates.json") # UserString-Keys fuer Original-Backups (pro Objekt) _ORIG_COLOR_SRC = "dossier_or_csrc" @@ -183,6 +186,72 @@ def delete_preset(name): return _write_presets_file(new) +# --- Rule-Templates: einzelne wiederverwendbare Regeln (cross-doc) ---------- + +def _read_rule_templates(): + if not os.path.isfile(_RULE_TPL_PATH): return [] + try: + with open(_RULE_TPL_PATH, "rb") as f: + data = json.loads(f.read().decode("utf-8")) + if isinstance(data, list): return data + if isinstance(data, dict) and "templates" in data: + return data.get("templates") or [] + except Exception as ex: + print("[OVERRIDES] read_rule_templates:", ex) + return [] + + +def _write_rule_templates(templates): + try: + if not os.path.isdir(_PRESETS_DIR): + os.makedirs(_PRESETS_DIR) + with open(_RULE_TPL_PATH, "wb") as f: + f.write(json.dumps(templates or [], ensure_ascii=False, indent=2).encode("utf-8")) + return True + except Exception as ex: + print("[OVERRIDES] write_rule_templates:", ex) + return False + + +def list_rule_templates(): + """Liefert Liste von {name, rule} fuer alle gespeicherten Templates.""" + out = [] + for t in _read_rule_templates(): + if not isinstance(t, dict): continue + out.append({"name": t.get("name", "(ohne Name)"), + "rule": t.get("rule") or {}}) + return out + + +def save_rule_template(name, rule): + """Speichert/ueberschreibt eine Regel als Template unter name.""" + if not name or not isinstance(name, str): return False + name = name.strip() + if not name or not isinstance(rule, dict): return False + templates = _read_rule_templates() + for i, t in enumerate(templates): + if isinstance(t, dict) and t.get("name") == name: + templates[i] = {"name": name, "rule": rule} + return _write_rule_templates(templates) + templates.append({"name": name, "rule": rule}) + return _write_rule_templates(templates) + + +def load_rule_template(name): + """Liefert die Rule eines Templates oder None.""" + for t in _read_rule_templates(): + if isinstance(t, dict) and t.get("name") == name: + return json.loads(json.dumps(t.get("rule") or {})) + return None + + +def delete_rule_template(name): + templates = _read_rule_templates() + new = [t for t in templates if not (isinstance(t, dict) and t.get("name") == name)] + if len(new) == len(templates): return False + return _write_rule_templates(new) + + def set_active_preset(doc, name): """Aktiviert ein gespeichertes Preset: kopiert dessen Rules ins Doc-Config und markiert es als activePreset. Wenn name leer/None: aktives Preset diff --git a/rhino/overrides_panel.py b/rhino/overrides_panel.py index 5abae85..31ce4eb 100644 --- a/rhino/overrides_panel.py +++ b/rhino/overrides_panel.py @@ -67,13 +67,14 @@ def _list_hatch_patterns(doc): 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"), + "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"), + "ruleTemplates": overrides.list_rule_templates(), } @@ -210,6 +211,36 @@ class OverridesBridge(panel_base.BaseBridge): overrides.delete_preset(name) self._send_state() + # --- Rule-Templates (cross-doc, einzelne Regeln) ---------------- + elif t == "SAVE_RULE_TEMPLATE": + name = (p.get("name") or "").strip() + rule = p.get("rule") or {} + if name and isinstance(rule, dict): + # ID/Name aus dem Template-Rule herausschneiden (template hat + # eigenen Namen, ID wird beim Insert neu generiert) + clean = {k: v for k, v in rule.items() if k not in ("id",)} + overrides.save_rule_template(name, clean) + self._send_state() + elif t == "DELETE_RULE_TEMPLATE": + name = (p.get("name") or "").strip() + if name: + overrides.delete_rule_template(name) + self._send_state() + elif t == "ADD_FROM_TEMPLATE": + name = (p.get("name") or "").strip() + if not name: return + tpl = overrides.load_rule_template(name) + if not tpl: return + cfg = overrides.load_config(doc) + new_rule = dict(tpl) + new_rule["id"] = "rule_" + uuid.uuid4().hex[:8] + new_rule.setdefault("enabled", True) + new_rule.setdefault("name", name) + rules = cfg.get("rules") or [] + rules.insert(0, new_rule) + overrides.update_rules(doc, rules, cfg.get("enabled")) + self._send_state() + def _ensure_listeners_once(): """Overrides-Listener nur EINMAL global installieren (statt bei jedem diff --git a/src/OverridesApp.jsx b/src/OverridesApp.jsx index 6c0d80f..f0ca45b 100644 --- a/src/OverridesApp.jsx +++ b/src/OverridesApp.jsx @@ -6,6 +6,7 @@ import { setOverridesEnabled, addRule, updateRule, deleteRule, reorderRules, duplicateRule, reapplyOverrides, clearOverrideRules, savePreset, loadPreset, deletePreset, + saveRuleTemplate, addFromTemplate, deleteRuleTemplate, } from './lib/rhinoBridge' const COND_TYPES = [ @@ -383,9 +384,10 @@ function RuleCard({ rule, index, total, layers, linetypes, hatchPatterns, onPatc export default function OverridesApp() { const [state, setState] = useState({ enabled: false, rules: [], layers: [], linetypes: [], hatchPatterns: [], presets: [], - activePreset: null, + activePreset: null, ruleTemplates: [], }) const [selectedPreset, setSelectedPreset] = useState('') + const [selectedTemplate, setSelectedTemplate] = useState('') const [ctxMenu, setCtxMenu] = useState(null) // {x, y, ruleId} useEffect(() => { @@ -435,6 +437,14 @@ export default function OverridesApp() { { label: 'Duplizieren', icon: 'content_copy', onClick: () => duplicateRule(ruleId) }, + { label: 'Als Vorlage speichern…', + icon: 'bookmark_add', + onClick: () => { + const def = rule.name || 'Vorlage' + const name = window.prompt('Name für Vorlage:', def) + if (!name || !name.trim()) return + saveRuleTemplate(name.trim(), rule) + } }, { divider: true }, { label: 'Löschen', icon: 'delete', danger: true, @@ -454,30 +464,10 @@ export default function OverridesApp() { background: 'var(--bg-base)', boxSizing: 'border-box', }}> - {/* Header — globaler Toggle + Refresh + FAB neue Regel */} -
- - - -
- - {/* Override-Kombinationen — Dropdown plus kontextabhaengiger Save. */} + {/* Override-Kombinationen — Dropdown plus kontextabhaengiger Save. + Globaler AN/AUS-Toggle ist jetzt in der Oberleiste, hier ueber- + fluessig. Reapply-Button raus: Backend re-applied automatisch + bei jeder Aenderung. */}
+ {/* Neue Regel: leere Regel ODER aus Vorlage. */} +
+ + +
+
Regeln sind additiv. Bei Konflikt gewinnt die oberste.
diff --git a/src/lib/rhinoBridge.js b/src/lib/rhinoBridge.js index 52f1401..907c18f 100644 --- a/src/lib/rhinoBridge.js +++ b/src/lib/rhinoBridge.js @@ -192,6 +192,10 @@ export function clearOverrideRules() { send('CLEAR_RULES', {}) } export function savePreset(name) { send('SAVE_PRESET', { name }) } export function loadPreset(name, mode) { send('LOAD_PRESET', { name, mode: mode || 'replace' }) } export function deletePreset(name) { send('DELETE_PRESET', { name }) } +// Rule-Templates: einzelne Regel speichern/anwenden/loeschen +export function saveRuleTemplate(name, rule) { send('SAVE_RULE_TEMPLATE', { name, rule }) } +export function addFromTemplate(name) { send('ADD_FROM_TEMPLATE', { name }) } +export function deleteRuleTemplate(name) { send('DELETE_RULE_TEMPLATE', { name }) } // --- Dimensionen-Panel --- export function setRefPoint(x, y, z) { send('SET_REF_POINT', { x, y, z }) }