Stempel-Element: SIA-Bilanz als platzierbares Viewport-Objekt
Neuer Element-Type "stempel" — TextEntity die automatisch eine SIA-416
Bilanz aggregiert und im Plan platziert wird. Re-rendert sich live wenn
sich Raeume im Scope aendern.
Backend (elemente.py):
- Neue SIA-Tags: GF (Geschossflaeche), AGF (Aussengeschossflaeche)
mit eigenen Labels + Pastell-Farben in _SIA_COLORS_HEX
- "stempel" als SOURCE_TYPE; eigene UserStrings:
- stempel_scope: "total" | "geschoss:<gid>"
- stempel_txt_h, stempel_font, stempel_bold, stempel_italic
- compute_sia_bilanz(doc, scope): aggregiert nach SIA-Tags, liefert
HNF/NNF/VF/FF/GF/AGF + abgeleitet NF/NGF/count + Scope-Label
- _format_bilanz_lines: kompakte Stempel-Textzeilen ("HNF 120.5 m²"),
Trennlinien + nur Kategorien > 0
- _make_stempel_text: TextEntity-Builder mit Header "Nutzflächen · {Scope}"
- _regenerate_element_body "stempel"-Branch: in-place Replace mit
aktualisiertem Text (Position bleibt aus alter Geometrie)
- _regenerate_stempel_for_geschoss: regennt alle Stempel im selben
Geschoss + alle "total"-Stempel
- Auto-Cascade: raum_outline-Regen setzt sticky-marker; nach REGEN_BUSY-
Release wird _regenerate_stempel_for_geschoss aufgerufen
- _cmd_create_stempel: GetPoint im Viewport, layer = aktives Geschoss
Raum-Sublayer, default-Scope = aktives Geschoss
- _update_wall "stempel"-Branch: scope/txtH/font/bold/italic via patch
- elemente_uebersicht: SIA-Bilanz um gf/agf/ngf erweitert; "stempel"
als KIND-Eintrag
Frontend:
- createStempel-Bridge-Export
- "Stempel"-Pill-Button in der "Raeume"-PillGroup
- StempelProperties-Component: Scope-Dropdown (Total + alle Geschosse),
Bilanz-Vorschau mit Hervorhebung der NF (Accent-Farbe)
- KIND_META + RAUM_SIA_KINDS um GF/AGF erweitert
Workflow: Pill "Stempel" klicken → Punkt im Viewport → Stempel erscheint
mit Header "Nutzflächen · EG" + Bilanz. Properties: Scope auf Total
umstellen oder anderes Geschoss waehlen. Neue Raeume taggen mit SIA-
Tag → Stempel aktualisiert sich automatisch.
This commit is contained in:
+103
-1
@@ -7,7 +7,7 @@ import {
|
||||
onMessage, notifyReady,
|
||||
createWall, createDecke, createDach,
|
||||
createFenster, createTuer, createAussparung, createTreppe,
|
||||
createStuetze, createTraeger, createRaum,
|
||||
createStuetze, createTraeger, createRaum, createStempel,
|
||||
openSwisstopo, openSwisstopoDialog, openOsmDialog,
|
||||
updateElement, deleteElement, openElementeUebersicht, openElementeProperties,
|
||||
saveOeffStyle, deleteOeffStyle,
|
||||
@@ -178,6 +178,7 @@ const KIND_META = {
|
||||
stuetze: { icon: 'square_foot', label: 'Stütze', color: '#5fa896' },
|
||||
traeger: { icon: 'horizontal_rule', label: 'Träger', color: '#7fc8a8' },
|
||||
raum: { icon: 'crop_free', label: 'Raum', color: '#a0a8b0' },
|
||||
stempel: { icon: 'receipt_long', label: 'Stempel', color: '#5fa896' },
|
||||
aussparung: { icon: 'rectangle', label: 'Aussparung', color: '#9090a0' },
|
||||
}
|
||||
|
||||
@@ -193,6 +194,8 @@ const RAUM_SIA_KINDS = [
|
||||
{ code: 'nnf', label: 'NNF', color: '#e8c498', hint: 'Nebennutzfläche' },
|
||||
{ code: 'vf', label: 'VF', color: '#e8d878', hint: 'Verkehrsfläche' },
|
||||
{ code: 'ff', label: 'FF', color: '#a8c8e0', hint: 'Funktionsfläche' },
|
||||
{ code: 'gf', label: 'GF', color: '#d0d0d0', hint: 'Geschossfläche (gross — umfasst das ganze Geschoss)' },
|
||||
{ code: 'agf', label: 'AGF', color: '#c0d8c0', hint: 'Aussengeschossfläche (Balkone, Terrassen)' },
|
||||
]
|
||||
|
||||
const PROFIL_META = {
|
||||
@@ -479,6 +482,11 @@ function NeuesElementSection({ noGeschoss, activeName, elementsCount }) {
|
||||
'Outline zeichnen · Stempel zeigt Name + Fläche'}
|
||||
disabled={dis}
|
||||
onClick={() => createRaum({})} />
|
||||
<PillButton icon="receipt_long" label="Stempel"
|
||||
hint={dis ? baseHint('Stempel') :
|
||||
'SIA-Bilanz-Stempel platzieren · Default = aktives Geschoss · Properties: Total/Geschoss umstellen'}
|
||||
disabled={dis}
|
||||
onClick={() => createStempel({})} />
|
||||
</PillGroup>
|
||||
|
||||
<PillGroup label="Library">
|
||||
@@ -529,6 +537,9 @@ export function PropertiesView({ selected, geschosse, materials, hatchPatterns,
|
||||
hatchPatterns={hatchPatterns} fonts={fonts || []}
|
||||
raumStempelStile={raumStempelStile || []}
|
||||
onUpdate={upd} onDelete={del('Raum')} />
|
||||
if (selected.kind === 'stempel')
|
||||
return <StempelProperties stempel={selected} geschosse={geschosse}
|
||||
onUpdate={upd} onDelete={del('Stempel')} />
|
||||
|
||||
if (selected.kind === 'aussparung')
|
||||
return <AussparungProperties aussp={selected} onDelete={del('Aussparung')} />
|
||||
@@ -1139,6 +1150,97 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fo
|
||||
)
|
||||
}
|
||||
|
||||
function StempelProperties({ stempel, geschosse, onUpdate, onDelete }) {
|
||||
const scope = stempel.scope || 'total'
|
||||
const bilanz = stempel.bilanz || {}
|
||||
const rows = [
|
||||
{ key: 'hnf', label: 'HNF', val: bilanz.hnf },
|
||||
{ key: 'nnf', label: 'NNF', val: bilanz.nnf },
|
||||
{ key: 'nf', label: 'NF', val: bilanz.nf, accent: true,
|
||||
hint: 'Nutzfläche = HNF + NNF' },
|
||||
{ key: 'vf', label: 'VF', val: bilanz.vf },
|
||||
{ key: 'ff', label: 'FF', val: bilanz.ff },
|
||||
{ key: 'ngf', label: 'NGF', val: bilanz.ngf,
|
||||
hint: 'Nettogeschossfläche = NF + VF + FF' },
|
||||
{ key: 'gf', label: 'GF', val: bilanz.gf },
|
||||
{ key: 'agf', label: 'AGF', val: bilanz.agf },
|
||||
].filter(r => r.val && r.val > 0)
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
padding: 10, marginBottom: 8,
|
||||
background: 'var(--bg-section)',
|
||||
borderRadius: 'var(--r-lg)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Icon name="receipt_long" size={13}
|
||||
style={{ color: 'var(--accent)', marginRight: 6 }} />
|
||||
<span style={{ ...labelXs, flex: 1, color: 'var(--accent)' }}>
|
||||
Stempel · {stempel.scopeLabel || '—'}
|
||||
</span>
|
||||
<button className="btn-icon-sm btn-icon-danger" onClick={onDelete}
|
||||
title="Löschen">
|
||||
<Icon name="delete" size={12} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Scope</span>
|
||||
<select value={scope}
|
||||
onChange={(e) => onUpdate({ scope: e.target.value })}
|
||||
title="Welche Räume werden im Stempel aggregiert"
|
||||
style={{ flex: 1, fontSize: 11 }}>
|
||||
<option value="total">Total · alle Geschosse</option>
|
||||
{geschosse.filter(g => g.id !== '__keingeschoss__').map(g => (
|
||||
<option key={g.id} value={`geschoss:${g.id}`}>
|
||||
Geschoss · {g.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
paddingTop: 6, borderTop: '1px dashed var(--border)',
|
||||
fontSize: 10, fontFamily: 'DM Mono, monospace',
|
||||
color: 'var(--text-muted)',
|
||||
}}>
|
||||
<div style={{ fontSize: 9, color: 'var(--text-muted)',
|
||||
letterSpacing: '0.06em', textTransform: 'uppercase',
|
||||
marginBottom: 4 }}>
|
||||
Bilanz · {bilanz.count || 0} Räume klassifiziert
|
||||
</div>
|
||||
{rows.length === 0 ? (
|
||||
<div style={{ fontSize: 10, color: 'var(--text-muted)',
|
||||
fontStyle: 'italic', padding: '4px 0' }}>
|
||||
Keine klassifizierten Räume im Scope.
|
||||
Räume mit SIA-Tag (HNF/NNF/VF/FF/GF/AGF) erscheinen hier.
|
||||
</div>
|
||||
) : rows.map(r => (
|
||||
<div key={r.key} title={r.hint || ''}
|
||||
style={{ display: 'flex', justifyContent: 'space-between',
|
||||
padding: '2px 0',
|
||||
color: r.accent ? 'var(--accent)' : 'inherit',
|
||||
fontWeight: r.accent ? 600 : 400 }}>
|
||||
<span>{r.label}</span>
|
||||
<span>{r.val.toFixed(1)} m²</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
fontSize: 9, color: 'var(--text-muted)',
|
||||
paddingTop: 4, borderTop: '1px dashed var(--border)',
|
||||
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.
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function AussparungProperties({ aussp, onDelete }) {
|
||||
return (
|
||||
<div style={{
|
||||
|
||||
@@ -10,7 +10,7 @@ import { onMessage, notifyReady, send } from './lib/rhinoBridge'
|
||||
|
||||
const KIND_ORDER = [
|
||||
'wand', 'decke', 'dach', 'fenster', 'tuer', 'aussparung',
|
||||
'treppe', 'stuetze', 'traeger', 'raum',
|
||||
'treppe', 'stuetze', 'traeger', 'raum', 'stempel',
|
||||
]
|
||||
|
||||
const KIND_META = {
|
||||
@@ -24,6 +24,7 @@ const KIND_META = {
|
||||
stuetze: { icon: 'square_foot', label: 'Stützen', color: '#c87050' },
|
||||
traeger: { icon: 'horizontal_rule', label: 'Träger', color: '#a87858' },
|
||||
raum: { icon: 'crop_free', label: 'Räume', color: '#5fa896' },
|
||||
stempel: { icon: 'receipt_long', label: 'Stempel', color: '#5fa896' },
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -379,6 +379,7 @@ export function createTreppe(p) { send('CREATE_TREPPE', p || {}) }
|
||||
export function createStuetze(p) { send('CREATE_STUETZE', p || {}) }
|
||||
export function createTraeger(p) { send('CREATE_TRAEGER', p || {}) }
|
||||
export function createRaum(p) { send('CREATE_RAUM', p || {}) }
|
||||
export function createStempel(p) { send('CREATE_STEMPEL', p || {}) }
|
||||
export function exportRaeume() { send('EXPORT_RAEUME', {}) }
|
||||
// Library-Symbol/Object — Picker im Elemente-Panel
|
||||
export function listLibrary() { send('LIST_LIBRARY', {}) }
|
||||
|
||||
Reference in New Issue
Block a user