From ac7b2f2ee5ef65ab00836bf1899e1f01585d76de Mon Sep 17 00:00:00 2001 From: karim Date: Wed, 27 May 2026 00:24:57 +0200 Subject: [PATCH] GF/AGF-Outlines: eigene Layer + minimaler Stempel/UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Geschossflaeche (GF) und Aussengeschossflaeche (AGF) sind reine Flaechen-Ausweisungs-Outlines — keine Raeume mit Name/Nummer/Funktion. Backend: - Neue Layer-Helpers _layer_path_raum_gf (61_GF) und _layer_path_raum_agf (62_AGF) — eigene Sublayer pro Geschoss, eigene Default-Farben - _layer_path_for_raum_sia(doc, gname, sia): routet sia=gf → GF-Layer, sia=agf → AGF-Layer, sonst RAEUME-Layer (HNF/NNF/VF/FF/leer) - _regenerate_element_body raum_outline-Branch nutzt das Routing → Source-Outline migriert automatisch auf den richtigen Layer - _update_wall raum-Branch: bei SIA-Wechsel (z.B. HNF → GF) auch Layer-Migration - _make_raum_stamp_text: bei sia=gf/agf default-layout override auf [["sia", "area"]] → Stempel zeigt nur "GF 234.5 m²" / "AGF 18.0 m²" ohne Nummer/Name/Funktion Frontend (RaumProperties): - Conditional isFlaeche = sia in (gf, agf) - Versteckt bei isFlaeche: Stil-Picker, Nummer-Input, Name-Input, Funktion-Input, StempelLayoutBuilder - Bleibt sichtbar: Geschoss, SIA-Tag-Selector, Fuellung, Rundung, Skala-Modus, Flaeche/Umfang-Footer - Info-Zeile zeigt bei isFlaeche: "Flaechen-Outline (GF) auf eigenem Layer · Stempel zeigt nur GF + Flaeche" --- rhino/elemente.py | 61 ++++++++++++++++++-- src/ElementeApp.jsx | 132 +++++++++++++++++++++++++------------------- 2 files changed, 130 insertions(+), 63 deletions(-) diff --git a/rhino/elemente.py b/rhino/elemente.py index b774575..4be53cd 100644 --- a/rhino/elemente.py +++ b/rhino/elemente.py @@ -1079,6 +1079,36 @@ def _layer_path_raum(doc, geschoss_name): return "{}::{}".format(geschoss_name, sub) +def _layer_path_raum_gf(doc, geschoss_name): + """Geschossflaeche-Outline — Sublayer 'GF' (Code 61). Separater Layer + weil GF-Outlines die ganze Wand-Topologie umfassen und NICHT zusammen + mit Nutzflaechen-Raeumen visualisiert/exportiert werden sollen.""" + sub = _find_ebene_sublayer_name(doc, ["gf", "geschossfl"], + "61", "GF", + default_color="#a0a0a0", default_lw=0.18) + return "{}::{}".format(geschoss_name, sub) + + +def _layer_path_raum_agf(doc, geschoss_name): + """Aussengeschossflaeche-Outline (Balkone/Terrassen) — Sublayer 'AGF' + (Code 62). Eigener Layer.""" + sub = _find_ebene_sublayer_name(doc, ["agf", "aussen"], + "62", "AGF", + default_color="#90b090", default_lw=0.13) + return "{}::{}".format(geschoss_name, sub) + + +def _layer_path_for_raum_sia(doc, geschoss_name, sia_tag): + """Routet auf den richtigen Layer je nach SIA-Klassifikation. + gf → 61_GF, agf → 62_AGF, alles andere (HNF/NNF/VF/FF/leer) → 60_RAEUME. + Damit kann der User pro Geschoss die Geschossflaechen separat sichtbar/ + unsichtbar schalten oder als eigenes Layout exportieren.""" + s = (sia_tag or "").lower() + if s == "gf": return _layer_path_raum_gf(doc, geschoss_name) + if s == "agf": return _layer_path_raum_agf(doc, geschoss_name) + return _layer_path_raum(doc, geschoss_name) + + def _layer_path_symbole(doc, geschoss_name, variant): """Symbol-Layer (Library-Items). variant = '2d' oder '3d'. Pfad: ::40_SYMBOLE::SYMBOLE_2D bzw. SYMBOLE_3D. @@ -4907,8 +4937,16 @@ def _make_raum_stamp_text(centroid, name, nummer, funktion, area, rundung, return tag if (tag and tag != "—") else None return None - # Layout resolven: explizit gesetzt > show_*-Flags - if layout and isinstance(layout, list) and any(layout): + # Layout resolven: explizit gesetzt > show_*-Flags > GF/AGF-Default + # Sonderfall GF/AGF: das sind reine Flaechen-Outlines (Geschoss- + # flaeche / Aussengeschossflaeche) — kein Raum-Inhalt. Stempel + # zeigt nur 'GF 234.5 m²' bzw. 'AGF 18.0 m²' — keine Nummer/ + # Name/Funktion. + _is_flaeche = (sia_code or "").lower() in ("gf", "agf") + if _is_flaeche and not (layout and isinstance(layout, list) + and any(layout)): + rows = [["sia", "area"]] + elif layout and isinstance(layout, list) and any(layout): rows = layout else: # Aus show_*-Flags ein implizites Layout bauen @@ -6328,9 +6366,15 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name # Raum: Source = geschlossene Outline; Volumes = TextEntity (Stempel) # + optional Brep-Fuellung (SIA-Modus). Geht NICHT durch den # Brep-Replace-Pfad — wird direkt hier emittiert. - layer = _ensure_layer(doc, _layer_path_raum(doc, geschoss_name)) + # Layer-Routing nach SIA-Klassifikation: gf → 61_GF, agf → 62_AGF, + # sonst 60_RAEUME (HNF/NNF/VF/FF/leer). So sind GF-Outlines auf + # eigenem Layer einzeln aus-/einblendbar. + _raum_sia = (meta.get("raum_sia") or "").lower() + layer = _ensure_layer(doc, + _layer_path_for_raum_sia(doc, geschoss_name, _raum_sia)) src_layer = layer - # Source-Outline ggf. auf richtigen Layer migrieren + # Source-Outline ggf. auf richtigen Layer migrieren (z.B. nach + # SIA-Tag-Wechsel HNF → GF) try: if src_layer >= 0 and src_obj.Attributes.LayerIndex != src_layer: new_attrs = src_obj.Attributes.Duplicate() @@ -10659,10 +10703,15 @@ class ElementeBridge(panel_base.BaseBridge): old_modus, r_modus, scale, r_th)) gstart = p.get("geschoss", old_meta["geschoss"]) attrs = axis_obj.Attributes - if gstart != old_meta["geschoss"]: + # Layer-Routing: bei Geschoss-Wechsel ODER SIA-Wechsel (gf/agf + # haben eigene Sublayer) den richtigen Layer setzen. + old_sia = (old_meta.get("raum_sia") or "").lower() + new_sia = (r_sia or "").lower() + if gstart != old_meta["geschoss"] or new_sia != old_sia: gs = _geschoss_by_id(doc, gstart) gn = gs.get("name", "EG") if gs else "EG" - attrs.LayerIndex = _ensure_layer(doc, _layer_path_raum(doc, gn)) + attrs.LayerIndex = _ensure_layer(doc, + _layer_path_for_raum_sia(doc, gn, new_sia)) _attach_meta(attrs, wall_id, "raum_outline", gstart, 0.0, "", "", "mid", raum_name=r_name, diff --git a/src/ElementeApp.jsx b/src/ElementeApp.jsx index 37fe844..d15e850 100644 --- a/src/ElementeApp.jsx +++ b/src/ElementeApp.jsx @@ -937,6 +937,10 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fo // Texthoehe (raum.txtH) + Ausrichtung (raum.align) werden via Oberleiste // gesetzt — kein Local-State noetig, Stil-Speichern liest direkt aus raum. const txtModus = raum.txtModus || 'fix' + // Sonderfall: GF/AGF sind reine Flaechen-Outlines (keine Raum-Inhalte + // wie Name/Nummer/Funktion). UI wird darunter minimiert auf SIA-Tag + + // Skala + Fluessigkeits-Anzeige. + const isFlaeche = (raum.sia === 'gf' || raum.sia === 'agf') useEffect(() => { setName(raum.name || 'Raum') setNummer(raum.nummer || '') @@ -988,21 +992,23 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fo {/* Stempel-Stil Preset-Picker — apply einer gespeicherten Visual-Vorlage auf den Raum. "+ Aktuell speichern" frischt die Liste mit den jetzigen Settings als neuen Stil. Stile sind per-Doc. */} -
- Stil - -
+ {!isFlaeche && ( +
+ Stil + +
+ )}
Geschoss @@ -1013,30 +1019,35 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fo
-
- Nummer - setNummer(e.target.value)} - onBlur={() => { - if (nummer !== (raum.nummer || '')) onUpdate({ nummer }) - }} - onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }} - style={{ flex: 1, fontSize: 11, - fontFamily: 'DM Mono, monospace' }} /> -
+ {/* Nummer + Name nur bei normalen Raeumen (nicht GF/AGF-Flaechen) */} + {!isFlaeche && ( +
+ Nummer + setNummer(e.target.value)} + onBlur={() => { + if (nummer !== (raum.nummer || '')) onUpdate({ nummer }) + }} + onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }} + style={{ flex: 1, fontSize: 11, + fontFamily: 'DM Mono, monospace' }} /> +
+ )} -
- Name - setName(e.target.value)} - onBlur={() => { - const v = (name || 'Raum').trim() - if (v !== raum.name) onUpdate({ name: v }) - else setName(v) - }} - onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }} - style={{ flex: 1, fontSize: 11 }} /> -
+ {!isFlaeche && ( +
+ Name + setName(e.target.value)} + onBlur={() => { + const v = (name || 'Raum').trim() + if (v !== raum.name) onUpdate({ name: v }) + else setName(v) + }} + onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }} + style={{ flex: 1, fontSize: 11 }} /> +
+ )}
Typ @@ -1100,27 +1111,33 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fo
-
- Funktion - setFunktion(e.target.value)} - onBlur={() => { - const v = (funktion || '').trim() - if (v !== (raum.funktion || '')) onUpdate({ funktion: v }) - }} - onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }} - placeholder="z.B. Wohnen, Bad, Büro …" - style={{ flex: 1, fontSize: 11 }} /> -
+ {!isFlaeche && ( +
+ Funktion + setFunktion(e.target.value)} + onBlur={() => { + const v = (funktion || '').trim() + if (v !== (raum.funktion || '')) onUpdate({ funktion: v }) + }} + onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }} + placeholder="z.B. Wohnen, Bad, Büro …" + style={{ flex: 1, fontSize: 11 }} /> +
+ )} {/* Stempel-Layout — Drag-and-Drop. Jede Row ist eine Textzeile, Felder innerhalb einer Row landen in derselben Zeile. Drag zwischen Rows um umzuordnen. Klick auf Field oben fügt es in - eine eigene neue Row hinzu. */} - onUpdate({ layout: newLayout })} /> + eine eigene neue Row hinzu. + Bei GF/AGF-Flaechen wird das ausgeblendet — Stempel zeigt da + automatisch nur den SIA-Tag + Flaeche (z.B. "GF · 234.5 m²"). */} + {!isFlaeche && ( + onUpdate({ layout: newLayout })} /> + )} {/* Hinweis: Typografie (Font/Stil/Höhe) wird in der OBERLEISTE gesetzt — den Stempel im Viewport anklicken. Aenderungen werden @@ -1132,8 +1149,9 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fo display: 'flex', alignItems: 'center', gap: 4, }}> - Typografie (Font/Stil/Höhe): Stempel im Viewport selektieren → - Oberleiste. + {isFlaeche + ? `Flächen-Outline (${(raum.sia || '').toUpperCase()}) auf eigenem Layer · Stempel zeigt nur ${(raum.sia || '').toUpperCase()} + Fläche` + : 'Typografie (Font/Stil/Höhe): Stempel im Viewport selektieren → Oberleiste.'}