diff --git a/rhino/elemente.py b/rhino/elemente.py index 6bc115d..70ee3f5 100644 --- a/rhino/elemente.py +++ b/rhino/elemente.py @@ -6457,6 +6457,12 @@ class ElementeBridge(panel_base.BaseBridge): # Wendet einen gespeicherten Stil auf die im Patch genannten # raum_outline element_ids an. p = {stilId, ids=[…]}. self._cmd_apply_raum_stil(p) + elif t == "REORDER_RAUM_STILE": + # p = {ids: [stil_id, ...]} in neuer Reihenfolge + self._cmd_reorder_raum_stile(p) + elif t == "DUPLICATE_RAUM_STIL": + # p = {id, newName?} → kopiert Stil mit neuer uuid + Name + self._cmd_duplicate_raum_stil(p) elif t == "OPEN_ELEMENTE_UEBERSICHT": try: import elemente_uebersicht @@ -8601,16 +8607,74 @@ class ElementeBridge(panel_base.BaseBridge): print("[ELEMENTE] Stempel-Stil {} geloescht".format(sid)) self._send_state() + def _cmd_reorder_raum_stile(self, p): + """Setzt die Reihenfolge der Stile auf die uebergebene id-Liste. + Nicht-erwaehnte Stile bleiben am Ende erhalten.""" + doc = Rhino.RhinoDoc.ActiveDoc + if doc is None: return + new_order = p.get("ids") or [] + if not isinstance(new_order, list) or not new_order: return + stile = load_raum_stempel_stile(doc) + by_id = {s.get("id"): s for s in stile if s.get("id")} + ordered = [] + seen = set() + for sid in new_order: + if sid in by_id and sid not in seen: + ordered.append(by_id[sid]); seen.add(sid) + # Reste anhaengen + for s in stile: + sid = s.get("id") + if sid and sid not in seen: + ordered.append(s); seen.add(sid) + save_raum_stempel_stile(doc, ordered) + self._send_state() + + def _cmd_duplicate_raum_stil(self, p): + """Kopiert einen Stil mit neuer uuid. Payload: {id, newName?}.""" + doc = Rhino.RhinoDoc.ActiveDoc + if doc is None: return + sid = (p.get("id") or "").strip() + if not sid: return + stile = load_raum_stempel_stile(doc) + src = next((s for s in stile if s.get("id") == sid), None) + if src is None: return + new_name = (p.get("newName") or "").strip() or ( + "{} (Kopie)".format(src.get("name", "Stil"))) + new_stil = dict(src) + new_stil["id"] = "stil_" + uuid.uuid4().hex[:8] + new_stil["name"] = new_name + # Einfuegen direkt nach Original + try: + i = next(j for j, s in enumerate(stile) if s.get("id") == sid) + stile.insert(i + 1, new_stil) + except StopIteration: + stile.append(new_stil) + save_raum_stempel_stile(doc, stile) + print("[ELEMENTE] Stempel-Stil '{}' dupliziert -> '{}'".format( + src.get("name"), new_name)) + self._send_state() + def _cmd_apply_raum_stil(self, p): - """Wendet einen Stil auf eine Liste von raum_outline-Element-IDs - an. Payload: {stilId, ids: [element_id, ...]}. Schreibt die Stil- - Felder auf die Source-Outlines + triggert Regen pro Raum. + """Wendet einen Stil auf raum_outline-Element-IDs an. Payload: + {stilId, ids: [element_id, ...]}. Wenn ids leer/missing → nimmt + alle aktuell SELEKTIERTEN raum_outline-Objekte (Bulk-Apply). + Schreibt Stil-Felder auf die Source-Outlines + triggert Regen. """ doc = Rhino.RhinoDoc.ActiveDoc if doc is None: return sid = (p.get("stilId") or "").strip() ids = p.get("ids") or [] - if not sid or not ids: return + if not sid: return + # Bulk-Fallback: keine ids im Patch → alle selektierten Raeume + if not ids: + for obj in doc.Objects: + try: + if obj.IsSelected(False) <= 0: continue + m = _read_meta(obj) + if m and m.get("type") == "raum_outline": + ids.append(m["id"]) + except Exception: pass + if not ids: return stile = load_raum_stempel_stile(doc) stil = next((s for s in stile if s.get("id") == sid), None) if stil is None: diff --git a/rhino/rhinopanel.py b/rhino/rhinopanel.py index 0db9463..d209a87 100644 --- a/rhino/rhinopanel.py +++ b/rhino/rhinopanel.py @@ -961,6 +961,14 @@ class EbenenBridge(panel_base.BaseBridge): lib_root = library.library_root() except Exception: lib_items = []; lib_root = "" + # Raumstempel-Stile + Fonts aus elemente fuer den "Raumstile"-Tab + try: + import elemente as _el + raum_stile = _el.load_raum_stempel_stile(doc) + fonts_list = _el._list_system_fonts() + except Exception: + raum_stile = [] + fonts_list = [] params = { "defaults": current.get("defaults", {}), "project": current.get("project", {}), @@ -971,6 +979,8 @@ class EbenenBridge(panel_base.BaseBridge): "linetypes": _list_linetypes_full(doc), "libraryItems": lib_items, "libraryRoot": lib_root, + "raumStempelStile": raum_stile, + "fonts": fonts_list, } def on_save(updated): doc2 = Rhino.RhinoDoc.ActiveDoc @@ -1078,6 +1088,108 @@ class EbenenBridge(panel_base.BaseBridge): self._delete_library_item(p) elif t == "SAVE_SELECTION_AS_LIBRARY": self._save_selection_as_library(p) + elif t == "SAVE_RAUM_STIL": + self._raum_stil_save(p) + elif t == "DELETE_RAUM_STIL": + self._raum_stil_delete(p) + elif t == "DUPLICATE_RAUM_STIL": + self._raum_stil_duplicate(p) + elif t == "REORDER_RAUM_STILE": + self._raum_stil_reorder(p) + + # ---- Raumstempel-Stile (Settings-Tab "Raumstile") ---- + # Dispatcht direkt zu elemente.py — kein Roundtrip via Elemente- + # Bridge noetig. Nach jeder Op wird die neue Liste an's Frontend + # gepushed via STILE_UPDATED. + def _raum_stil_send_updated(self): + try: + import elemente as _el + d = Rhino.RhinoDoc.ActiveDoc + self.send("STILE_UPDATED", + {"raumStempelStile": _el.load_raum_stempel_stile(d)}) + except Exception as ex: + print("[PROJECT-SETTINGS] STILE_UPDATED:", ex) + + def _raum_stil_save(self, p): + try: + import elemente as _el + d = Rhino.RhinoDoc.ActiveDoc + name = (p.get("name") or "").strip() + if not name: return + sid = (p.get("id") or "").strip() or ( + "stil_" + __import__("uuid").uuid4().hex[:8]) + stile = _el.load_raum_stempel_stile(d) + settings = p.get("settings") or {} + new_stil = {"id": sid, "name": name} + for f in _el._RAUM_STIL_FIELDS: + if f in settings: new_stil[f] = settings[f] + found = False + for i, s in enumerate(stile): + if s.get("id") == sid: + stile[i] = new_stil; found = True; break + if not found: stile.append(new_stil) + _el.save_raum_stempel_stile(d, stile) + except Exception as ex: + print("[PROJECT-SETTINGS] raum_stil save:", ex) + self._raum_stil_send_updated() + + def _raum_stil_delete(self, p): + try: + import elemente as _el + d = Rhino.RhinoDoc.ActiveDoc + sid = (p.get("id") or "").strip() + if not sid: return + stile = _el.load_raum_stempel_stile(d) + stile = [s for s in stile if s.get("id") != sid] + _el.save_raum_stempel_stile(d, stile) + except Exception as ex: + print("[PROJECT-SETTINGS] raum_stil delete:", ex) + self._raum_stil_send_updated() + + def _raum_stil_duplicate(self, p): + try: + import elemente as _el + import uuid as _uuid + d = Rhino.RhinoDoc.ActiveDoc + sid = (p.get("id") or "").strip() + if not sid: return + stile = _el.load_raum_stempel_stile(d) + src = next((s for s in stile if s.get("id") == sid), None) + if src is None: return + new_name = (p.get("newName") or "").strip() or ( + "{} (Kopie)".format(src.get("name", "Stil"))) + new_stil = dict(src) + new_stil["id"] = "stil_" + _uuid.uuid4().hex[:8] + new_stil["name"] = new_name + try: + i = next(j for j, s in enumerate(stile) if s.get("id") == sid) + stile.insert(i + 1, new_stil) + except StopIteration: + stile.append(new_stil) + _el.save_raum_stempel_stile(d, stile) + except Exception as ex: + print("[PROJECT-SETTINGS] raum_stil duplicate:", ex) + self._raum_stil_send_updated() + + def _raum_stil_reorder(self, p): + try: + import elemente as _el + d = Rhino.RhinoDoc.ActiveDoc + new_order = p.get("ids") or [] + if not isinstance(new_order, list) or not new_order: return + stile = _el.load_raum_stempel_stile(d) + by_id = {s.get("id"): s for s in stile if s.get("id")} + ordered = []; seen = set() + for i in new_order: + if i in by_id and i not in seen: + ordered.append(by_id[i]); seen.add(i) + for s in stile: + if s.get("id") and s["id"] not in seen: + ordered.append(s); seen.add(s["id"]) + _el.save_raum_stempel_stile(d, ordered) + except Exception as ex: + print("[PROJECT-SETTINGS] raum_stil reorder:", ex) + self._raum_stil_send_updated() def _pick_texture(self, payload): slot = payload.get("slot") or "diffuse" try: diff --git a/src/ElementeApp.jsx b/src/ElementeApp.jsx index db2840a..1679be5 100644 --- a/src/ElementeApp.jsx +++ b/src/ElementeApp.jsx @@ -914,7 +914,10 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fo deleteRaumStil(activeStilId) return } - if (val) applyRaumStil(val, [raum.id]) + // Empty ids → Backend nutzt aktuelle Rhino-Selection (Bulk-Apply auf + // alle selektierten raum_outlines). Inkludiert immer den aktuellen + // Raum (er ist ja gerade in den Properties sichtbar = selektiert). + if (val) applyRaumStil(val, []) } const [name, setName] = useState(raum.name || 'Raum') diff --git a/src/components/ProjectSettingsDialog.jsx b/src/components/ProjectSettingsDialog.jsx index 483c3be..bd48bd8 100644 --- a/src/components/ProjectSettingsDialog.jsx +++ b/src/components/ProjectSettingsDialog.jsx @@ -9,6 +9,7 @@ import { renameHatch, deleteHatch, importHatchFile, listLibraryItems, addLibraryFile, updateLibraryItem, deleteLibraryItem, saveSelectionAsLibrary, + saveRaumStil, deleteRaumStil, duplicateRaumStil, reorderRaumStile, } from '../lib/rhinoBridge' /* Field — Stack-Layout fuer komplexe Inputs (mehrere Felder nebeneinander) */ @@ -646,6 +647,11 @@ export default function ProjectSettingsDialog({ const [libRoot, setLibRoot] = useState(initial.libraryRoot || '') const [selLib, setSelLib] = useState(null) const builtin = initial.builtinMaterials || [] + // Raumstile-State: kommt von initial + wird per STATE-Refresh aktualisiert + // wenn Backend CRUD-Op fertig ist. Drag-Reorder lokal + commit beim Drop. + const [raumStile, setRaumStile] = useState(initial.raumStempelStile || []) + const [dragStilIdx, setDragStilIdx] = useState(null) + const fontsList = initial.fonts || [] // Aktuell ausgewaehltes Material aus Selection ableiten const selectedMat = (() => { @@ -682,6 +688,9 @@ export default function ProjectSettingsDialog({ onMessage('LIBRARY_ERROR', ({ msg }) => { if (msg) alert(msg) }) + onMessage('STILE_UPDATED', ({ raumStempelStile }) => { + if (Array.isArray(raumStempelStile)) setRaumStile(raumStempelStile) + }) }, []) // Backend-File-Picker-Antwort: aktualisiert das Slot im aktuell @@ -756,6 +765,7 @@ export default function ProjectSettingsDialog({ { key: 'linetypes', label: 'Linientypen' }, { key: 'hatches', label: 'Schraffuren' }, { key: 'symbols', label: 'Symbole' }, + { key: 'raumstile', label: 'Raumstile' }, ]} active={tab} onChange={setTab} /> {/* Body */} @@ -1341,6 +1351,116 @@ export default function ProjectSettingsDialog({ )} + + {tab === 'raumstile' && ( +