GF/AGF-Outlines: eigene Layer + minimaler Stempel/UI

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"
This commit is contained in:
2026-05-27 00:24:57 +02:00
parent 2386366566
commit ac7b2f2ee5
2 changed files with 130 additions and 63 deletions
+75 -57
View File
@@ -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. */}
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Stil</span>
<select value={activeStilId}
onChange={(e) => handleStilChange(e.target.value)}
style={{ flex: 1, fontSize: 11 }}
title="Gespeicherter Stempel-Stil — Klick wendet die Vorlage an">
<option value=""> Stil wählen </option>
{stilList.map(s => (
<option key={s.id} value={s.id}>{s.name}</option>
))}
<option disabled></option>
<option value="__save__">+ Aktuelle Settings als Stil speichern</option>
{activeStilId && <option value="__delete__">🗑 Aktiven Stil löschen</option>}
</select>
</div>
{!isFlaeche && (
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Stil</span>
<select value={activeStilId}
onChange={(e) => handleStilChange(e.target.value)}
style={{ flex: 1, fontSize: 11 }}
title="Gespeicherter Stempel-Stil — Klick wendet die Vorlage an">
<option value=""> Stil wählen </option>
{stilList.map(s => (
<option key={s.id} value={s.id}>{s.name}</option>
))}
<option disabled></option>
<option value="__save__">+ Aktuelle Settings als Stil speichern</option>
{activeStilId && <option value="__delete__">🗑 Aktiven Stil löschen</option>}
</select>
</div>
)}
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Geschoss</span>
@@ -1013,30 +1019,35 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fo
</select>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Nummer</span>
<input type="text" value={nummer}
onChange={(e) => 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' }} />
</div>
{/* Nummer + Name nur bei normalen Raeumen (nicht GF/AGF-Flaechen) */}
{!isFlaeche && (
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Nummer</span>
<input type="text" value={nummer}
onChange={(e) => 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' }} />
</div>
)}
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Name</span>
<input type="text" value={name}
onChange={(e) => 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 }} />
</div>
{!isFlaeche && (
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Name</span>
<input type="text" value={name}
onChange={(e) => 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 }} />
</div>
)}
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Typ</span>
@@ -1100,27 +1111,33 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fo
</select>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Funktion</span>
<input type="text" value={funktion}
onChange={(e) => 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 }} />
</div>
{!isFlaeche && (
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Funktion</span>
<input type="text" value={funktion}
onChange={(e) => 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 }} />
</div>
)}
{/* 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. */}
<StempelLayoutBuilder
layout={layout}
availableFields={availableFields}
onChange={(newLayout) => 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 && (
<StempelLayoutBuilder
layout={layout}
availableFields={availableFields}
onChange={(newLayout) => 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,
}}>
<Icon name="info" size={11} style={{ color: 'var(--text-muted)' }} />
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.'}
</div>
<div style={{