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:
+326
-3
@@ -408,18 +408,23 @@ def _resolve_raum_rundung(meta, doc=None):
|
|||||||
|
|
||||||
|
|
||||||
_RAUM_ALIGN = ("links", "mid", "rechts")
|
_RAUM_ALIGN = ("links", "mid", "rechts")
|
||||||
_RAUM_SIA_KINDS = ("", "hnf", "nnf", "vf", "ff")
|
_RAUM_SIA_KINDS = ("", "hnf", "nnf", "vf", "ff", "gf", "agf")
|
||||||
_RAUM_FUNKTIONEN = (
|
_RAUM_FUNKTIONEN = (
|
||||||
"wohnen", "schlafen", "bad", "kueche", "essen", "flur", "diele",
|
"wohnen", "schlafen", "bad", "kueche", "essen", "flur", "diele",
|
||||||
"buero", "atelier", "lager", "technik", "balkon", "terrasse",
|
"buero", "atelier", "lager", "technik", "balkon", "terrasse",
|
||||||
"sonstiges",
|
"sonstiges",
|
||||||
)
|
)
|
||||||
# SIA-416 Farbpalette nach CH-Buero-Konvention (helle, kraeftige Pastelltoene).
|
# SIA-416 Farbpalette nach CH-Buero-Konvention (helle, kraeftige Pastelltoene).
|
||||||
|
# GF/AGF: dezente Grau-Toene weil sie meist als Hintergrund-Outline um andere
|
||||||
|
# klassifizierte Raeume gezeichnet werden (Doppelung wird vom Stempel
|
||||||
|
# erkannt: GF/AGF separat aggregiert).
|
||||||
_SIA_COLORS_HEX = {
|
_SIA_COLORS_HEX = {
|
||||||
"hnf": "#e8a8a8", # Hauptnutzflaeche — Rot
|
"hnf": "#e8a8a8", # Hauptnutzflaeche — Rot
|
||||||
"nnf": "#e8c498", # Nebennutzflaeche — Orange
|
"nnf": "#e8c498", # Nebennutzflaeche — Orange
|
||||||
"vf": "#e8d878", # Verkehrsflaeche — Gelb
|
"vf": "#e8d878", # Verkehrsflaeche — Gelb
|
||||||
"ff": "#a8c8e0", # Funktionsflaeche — Hellblau
|
"ff": "#a8c8e0", # Funktionsflaeche — Hellblau
|
||||||
|
"gf": "#d0d0d0", # Geschossflaeche (gross) — Grau
|
||||||
|
"agf": "#c0d8c0", # Aussengeschossflaeche — Hellgruen
|
||||||
}
|
}
|
||||||
_SIA_LABELS = {
|
_SIA_LABELS = {
|
||||||
"": "—",
|
"": "—",
|
||||||
@@ -427,6 +432,8 @@ _SIA_LABELS = {
|
|||||||
"nnf": "NNF",
|
"nnf": "NNF",
|
||||||
"vf": "VF",
|
"vf": "VF",
|
||||||
"ff": "FF",
|
"ff": "FF",
|
||||||
|
"gf": "GF",
|
||||||
|
"agf": "AGF",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cross-Doc Preset-Name fuer Override-Engine. Steuert auch das siaFillMode-
|
# Cross-Doc Preset-Name fuer Override-Engine. Steuert auch das siaFillMode-
|
||||||
@@ -2676,6 +2683,8 @@ def _attach_meta(obj_attrs, wall_id, type_, geschoss, dicke, uk_over, ok_over,
|
|||||||
raum_show_sia=None,
|
raum_show_sia=None,
|
||||||
raum_stamp_dx=None, raum_stamp_dy=None,
|
raum_stamp_dx=None, raum_stamp_dy=None,
|
||||||
raum_layout=None, raum_txt_modus=None,
|
raum_layout=None, raum_txt_modus=None,
|
||||||
|
stempel_scope=None, stempel_txt_h=None,
|
||||||
|
stempel_font=None, stempel_bold=None, stempel_italic=None,
|
||||||
wand_layered=None, wand_layers=None, wand_layer_idx=None,
|
wand_layered=None, wand_layers=None, wand_layer_idx=None,
|
||||||
wand_chain_members=None,
|
wand_chain_members=None,
|
||||||
aussp_parent=None):
|
aussp_parent=None):
|
||||||
@@ -2880,6 +2889,21 @@ def _attach_meta(obj_attrs, wall_id, type_, geschoss, dicke, uk_over, ok_over,
|
|||||||
print("[ELEMENTE] raum_layout set:", ex)
|
print("[ELEMENTE] raum_layout set:", ex)
|
||||||
if raum_txt_modus is not None and raum_txt_modus in ("fix", "masstab"):
|
if raum_txt_modus is not None and raum_txt_modus in ("fix", "masstab"):
|
||||||
obj_attrs.SetUserString(_KEY_RAUM_TXT_MODUS, raum_txt_modus)
|
obj_attrs.SetUserString(_KEY_RAUM_TXT_MODUS, raum_txt_modus)
|
||||||
|
# Stempel-Felder
|
||||||
|
if stempel_scope is not None:
|
||||||
|
obj_attrs.SetUserString(_KEY_STEMPEL_SCOPE, str(stempel_scope))
|
||||||
|
if stempel_txt_h is not None:
|
||||||
|
try: obj_attrs.SetUserString(_KEY_STEMPEL_TXT_H,
|
||||||
|
"{:.4f}".format(float(stempel_txt_h)))
|
||||||
|
except Exception: pass
|
||||||
|
if stempel_font is not None:
|
||||||
|
obj_attrs.SetUserString(_KEY_STEMPEL_FONT, str(stempel_font))
|
||||||
|
if stempel_bold is not None:
|
||||||
|
obj_attrs.SetUserString(_KEY_STEMPEL_BOLD,
|
||||||
|
"1" if bool(stempel_bold) else "0")
|
||||||
|
if stempel_italic is not None:
|
||||||
|
obj_attrs.SetUserString(_KEY_STEMPEL_ITAL,
|
||||||
|
"1" if bool(stempel_italic) else "0")
|
||||||
# Wand-Schichten
|
# Wand-Schichten
|
||||||
if wand_layered is not None:
|
if wand_layered is not None:
|
||||||
obj_attrs.SetUserString(_KEY_WAND_LAYERED,
|
obj_attrs.SetUserString(_KEY_WAND_LAYERED,
|
||||||
@@ -3080,6 +3104,13 @@ def _read_meta(obj):
|
|||||||
if r_txt_modus not in ("fix", "masstab"): r_txt_modus = "fix"
|
if r_txt_modus not in ("fix", "masstab"): r_txt_modus = "fix"
|
||||||
# Aktiver Stempel-Stil (id des zuletzt applizierten Stils)
|
# Aktiver Stempel-Stil (id des zuletzt applizierten Stils)
|
||||||
r_stil_id = a.GetUserString(_KEY_RAUM_STIL_ID) or ""
|
r_stil_id = a.GetUserString(_KEY_RAUM_STIL_ID) or ""
|
||||||
|
# Stempel-Felder
|
||||||
|
st_scope = a.GetUserString(_KEY_STEMPEL_SCOPE) or "total"
|
||||||
|
try: st_th = float(a.GetUserString(_KEY_STEMPEL_TXT_H) or "0.20")
|
||||||
|
except Exception: st_th = 0.20
|
||||||
|
st_font = a.GetUserString(_KEY_STEMPEL_FONT) or ""
|
||||||
|
st_bold = (a.GetUserString(_KEY_STEMPEL_BOLD) == "1")
|
||||||
|
st_ital = (a.GetUserString(_KEY_STEMPEL_ITAL) == "1")
|
||||||
# Field-Layout — parsed JSON list of rows
|
# Field-Layout — parsed JSON list of rows
|
||||||
r_layout_raw = a.GetUserString(_KEY_RAUM_LAYOUT) or ""
|
r_layout_raw = a.GetUserString(_KEY_RAUM_LAYOUT) or ""
|
||||||
r_layout = []
|
r_layout = []
|
||||||
@@ -3202,6 +3233,11 @@ def _read_meta(obj):
|
|||||||
"raum_layout": r_layout,
|
"raum_layout": r_layout,
|
||||||
"raum_txt_modus": r_txt_modus,
|
"raum_txt_modus": r_txt_modus,
|
||||||
"raum_stil_id": r_stil_id,
|
"raum_stil_id": r_stil_id,
|
||||||
|
"stempel_scope": st_scope,
|
||||||
|
"stempel_txt_h": st_th,
|
||||||
|
"stempel_font": st_font,
|
||||||
|
"stempel_bold": st_bold,
|
||||||
|
"stempel_italic": st_ital,
|
||||||
"wand_layered": w_layered,
|
"wand_layered": w_layered,
|
||||||
"wand_layers": w_layers,
|
"wand_layers": w_layers,
|
||||||
"wand_layer_idx": w_layer_idx,
|
"wand_layer_idx": w_layer_idx,
|
||||||
@@ -3808,11 +3844,24 @@ def _make_oeffnung_pieces(axis_curve, point_on_axis, wall_dicke, oeff_meta, base
|
|||||||
SOURCE_TYPES = ("wand_axis", "decke_outline", "dach_outline",
|
SOURCE_TYPES = ("wand_axis", "decke_outline", "dach_outline",
|
||||||
"oeffnung_point", "treppe_axis",
|
"oeffnung_point", "treppe_axis",
|
||||||
"stuetze_point", "traeger_axis",
|
"stuetze_point", "traeger_axis",
|
||||||
"raum_outline", "decke_aussparung_outline")
|
"raum_outline", "decke_aussparung_outline",
|
||||||
|
# Stempel: SIA-Bilanz-TextEntity. Source-only (kein Volume),
|
||||||
|
# die TextEntity selber IST die Source-Geometrie. Regennt
|
||||||
|
# automatisch wenn raeume im Scope sich aendern.
|
||||||
|
"stempel")
|
||||||
VOLUME_TYPES = ("wand_volume", "decke_volume", "dach_volume",
|
VOLUME_TYPES = ("wand_volume", "decke_volume", "dach_volume",
|
||||||
"oeffnung_volume", "oeffnung_swing", "oeffnung_sturz",
|
"oeffnung_volume", "oeffnung_swing", "oeffnung_sturz",
|
||||||
"treppe_volume", "stuetze_volume", "traeger_volume",
|
"treppe_volume", "stuetze_volume", "traeger_volume",
|
||||||
"raum_stamp", "raum_fill")
|
"raum_stamp", "raum_fill")
|
||||||
|
|
||||||
|
# Stempel-Scope-Werte:
|
||||||
|
# "total" → alle Raeume im Doc
|
||||||
|
# "geschoss:<id>" → nur Raeume im genannten Geschoss
|
||||||
|
_KEY_STEMPEL_SCOPE = "dossier_stempel_scope"
|
||||||
|
_KEY_STEMPEL_TXT_H = "dossier_stempel_txt_h" # Texthoehe in m
|
||||||
|
_KEY_STEMPEL_FONT = "dossier_stempel_font"
|
||||||
|
_KEY_STEMPEL_BOLD = "dossier_stempel_bold"
|
||||||
|
_KEY_STEMPEL_ITAL = "dossier_stempel_italic"
|
||||||
# Oeffnungs-Cutout: Boolean-Difference aus Wand. Zusaetzlich kriegt die
|
# Oeffnungs-Cutout: Boolean-Difference aus Wand. Zusaetzlich kriegt die
|
||||||
# Oeffnung ihr eigenes Volumen (Rahmen + Sims + Glas) als Sub-Element.
|
# Oeffnung ihr eigenes Volumen (Rahmen + Sims + Glas) als Sub-Element.
|
||||||
|
|
||||||
@@ -4938,6 +4987,144 @@ def _make_raum_stamp_text(centroid, name, nummer, funktion, area, rundung,
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def compute_sia_bilanz(doc, scope="total"):
|
||||||
|
"""Aggregiert raum_outline-Flaechen nach raum_sia-Klassifikation.
|
||||||
|
scope = "total" → alle Raeume
|
||||||
|
scope = "geschoss:<id>" → nur Raeume mit raum_geschoss == id
|
||||||
|
Liefert dict {hnf, nnf, vf, ff, gf, agf, nf, ngf, count, scope, geschossName}.
|
||||||
|
NF = HNF + NNF (Nutzflaeche). NGF = NF + VF + FF (Nettogeschossflaeche
|
||||||
|
laut SIA 416).
|
||||||
|
"""
|
||||||
|
out = {"hnf": 0.0, "nnf": 0.0, "vf": 0.0, "ff": 0.0,
|
||||||
|
"gf": 0.0, "agf": 0.0, "count": 0,
|
||||||
|
"scope": scope, "geschossName": ""}
|
||||||
|
if doc is None: return out
|
||||||
|
target_gid = None
|
||||||
|
if isinstance(scope, str) and scope.startswith("geschoss:"):
|
||||||
|
target_gid = scope.split(":", 1)[1]
|
||||||
|
g = _geschoss_by_id(doc, target_gid)
|
||||||
|
if g: out["geschossName"] = g.get("name") or ""
|
||||||
|
for obj in doc.Objects:
|
||||||
|
try:
|
||||||
|
m = _read_meta(obj)
|
||||||
|
if not m or m.get("type") != "raum_outline": continue
|
||||||
|
if target_gid is not None and m.get("geschoss") != target_gid:
|
||||||
|
continue
|
||||||
|
try: area, _, _ = _raum_amp(obj.Geometry)
|
||||||
|
except Exception: continue
|
||||||
|
if not area or area <= 0: continue
|
||||||
|
sia = (m.get("raum_sia") or "").lower()
|
||||||
|
if sia not in ("hnf", "nnf", "vf", "ff", "gf", "agf"): continue
|
||||||
|
out[sia] += float(area)
|
||||||
|
out["count"] += 1
|
||||||
|
except Exception: pass
|
||||||
|
out["nf"] = out["hnf"] + out["nnf"]
|
||||||
|
out["ngf"] = out["nf"] + out["vf"] + out["ff"]
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _format_bilanz_lines(bilanz, rundung="0.1"):
|
||||||
|
"""Baut die Stempel-Textzeilen aus einer Bilanz. Zeigt nur Kategorien
|
||||||
|
mit Flaeche > 0. Format kompakt: 'HNF 120.5 m²'."""
|
||||||
|
lines = []
|
||||||
|
def _row(label, val):
|
||||||
|
return "{} {} m²".format(label.ljust(4), _format_area(val, rundung))
|
||||||
|
# Nutzflaechen-Block
|
||||||
|
has_nf = bilanz["hnf"] > 0 or bilanz["nnf"] > 0
|
||||||
|
if bilanz["hnf"] > 0: lines.append(_row("HNF", bilanz["hnf"]))
|
||||||
|
if bilanz["nnf"] > 0: lines.append(_row("NNF", bilanz["nnf"]))
|
||||||
|
if has_nf:
|
||||||
|
lines.append("────────────")
|
||||||
|
lines.append(_row("NF", bilanz["nf"]))
|
||||||
|
# VF/FF
|
||||||
|
if bilanz["vf"] > 0: lines.append(_row("VF", bilanz["vf"]))
|
||||||
|
if bilanz["ff"] > 0: lines.append(_row("FF", bilanz["ff"]))
|
||||||
|
# NGF wenn was zusammenkommt
|
||||||
|
if bilanz["ngf"] > 0 and (bilanz["vf"] > 0 or bilanz["ff"] > 0):
|
||||||
|
lines.append("────────────")
|
||||||
|
lines.append(_row("NGF", bilanz["ngf"]))
|
||||||
|
# Gross-Flaechen separat
|
||||||
|
if bilanz["gf"] > 0 or bilanz["agf"] > 0:
|
||||||
|
lines.append("────────────")
|
||||||
|
if bilanz["gf"] > 0: lines.append(_row("GF", bilanz["gf"]))
|
||||||
|
if bilanz["agf"] > 0: lines.append(_row("AGF", bilanz["agf"]))
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def _make_stempel_text(pos, scope, doc, text_height=0.20, z=0.0,
|
||||||
|
font=None, bold=False, italic=False):
|
||||||
|
"""Baut die Stempel-TextEntity fuer einen Scope ("total" oder
|
||||||
|
"geschoss:<id>"). Header = "Nutzflächen · {Scope-Label}"."""
|
||||||
|
try:
|
||||||
|
bilanz = compute_sia_bilanz(doc, scope)
|
||||||
|
if scope == "total":
|
||||||
|
scope_label = "Total"
|
||||||
|
elif bilanz["geschossName"]:
|
||||||
|
scope_label = bilanz["geschossName"]
|
||||||
|
else:
|
||||||
|
scope_label = "—"
|
||||||
|
header = "Nutzflächen · {}".format(scope_label)
|
||||||
|
body = _format_bilanz_lines(bilanz)
|
||||||
|
if not body:
|
||||||
|
body = ["(keine klassifizierten Räume)"]
|
||||||
|
text = "\n".join([header, "════════════"] + body)
|
||||||
|
te = rg.TextEntity()
|
||||||
|
te.Text = text
|
||||||
|
plane = rg.Plane(rg.Point3d(pos.X, pos.Y, float(z)),
|
||||||
|
rg.Vector3d.ZAxis)
|
||||||
|
te.Plane = plane
|
||||||
|
try: te.TextHeight = float(text_height)
|
||||||
|
except Exception: te.TextHeight = 0.20
|
||||||
|
try:
|
||||||
|
te.Justification = rg.TextJustification.MiddleLeft
|
||||||
|
except Exception: pass
|
||||||
|
# Font/Style optional
|
||||||
|
if doc is not None and font:
|
||||||
|
try:
|
||||||
|
f = Rhino.DocObjects.Font.FromQuartetProperties(
|
||||||
|
str(font), bool(bold), bool(italic))
|
||||||
|
if f is not None:
|
||||||
|
idx = doc.Fonts.FindOrCreate(f.QuartetName,
|
||||||
|
bool(bold), bool(italic))
|
||||||
|
if idx >= 0:
|
||||||
|
try: te.FontIndex = idx
|
||||||
|
except Exception: pass
|
||||||
|
except Exception: pass
|
||||||
|
return te
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] _make_stempel_text:", ex)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _regenerate_stempel_for_geschoss(doc, geschoss_id):
|
||||||
|
"""Regennt alle Stempel die diesen Geschoss-Scope (oder 'total') haben.
|
||||||
|
Wird nach jedem raum_outline-Regen aufgerufen damit Bilanzen aktuell
|
||||||
|
bleiben. Idempotent + safe gegen Endless-Loops via _REGEN_BUSY-Check."""
|
||||||
|
if doc is None: return
|
||||||
|
if sc.sticky.get(_REGEN_BUSY): return
|
||||||
|
target_scopes = {"total"}
|
||||||
|
if geschoss_id:
|
||||||
|
target_scopes.add("geschoss:{}".format(geschoss_id))
|
||||||
|
ids = []
|
||||||
|
for obj in doc.Objects:
|
||||||
|
try:
|
||||||
|
m = _read_meta(obj)
|
||||||
|
if not m or m.get("type") != "stempel": continue
|
||||||
|
if m.get("stempel_scope") in target_scopes:
|
||||||
|
ids.append(m["id"])
|
||||||
|
except Exception: pass
|
||||||
|
if not ids: return
|
||||||
|
_was = sc.sticky.get(_REGEN_BUSY, False)
|
||||||
|
sc.sticky[_REGEN_BUSY] = True
|
||||||
|
try:
|
||||||
|
for sid in ids:
|
||||||
|
try: _regenerate_element(doc, sid)
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] stempel-regen", sid, ":", ex)
|
||||||
|
finally:
|
||||||
|
sc.sticky[_REGEN_BUSY] = _was
|
||||||
|
|
||||||
|
|
||||||
def _make_raum_hatch(outline_curve, z_uk, doc, pattern_name="Solid"):
|
def _make_raum_hatch(outline_curve, z_uk, doc, pattern_name="Solid"):
|
||||||
"""Erzeugt einen Hatch unter der Raum-Outline am Z=z_uk + 1 mm mit
|
"""Erzeugt einen Hatch unter der Raum-Outline am Z=z_uk + 1 mm mit
|
||||||
gegebenem Pattern. Color = ByObject (default hell). Override-System
|
gegebenem Pattern. Color = ByObject (default hell). Override-System
|
||||||
@@ -5572,10 +5759,22 @@ def _regenerate_element(doc, element_id):
|
|||||||
_was_busy = sc.sticky.get(_REGEN_BUSY, False)
|
_was_busy = sc.sticky.get(_REGEN_BUSY, False)
|
||||||
sc.sticky[_REGEN_BUSY] = True
|
sc.sticky[_REGEN_BUSY] = True
|
||||||
try:
|
try:
|
||||||
return _regenerate_element_body(doc, element_id, src_obj, meta,
|
result = _regenerate_element_body(doc, element_id, src_obj, meta,
|
||||||
geom, geschoss_name)
|
geom, geschoss_name)
|
||||||
finally:
|
finally:
|
||||||
sc.sticky[_REGEN_BUSY] = _was_busy
|
sc.sticky[_REGEN_BUSY] = _was_busy
|
||||||
|
# Wenn ein raum_outline-Regen einen Stempel-Dirty-Marker gesetzt hat
|
||||||
|
# (oder generell raeumlich-Aenderungen), Stempel im selben Geschoss
|
||||||
|
# (+ Total) neu rechnen. Lauft AUSSERHALB des REGEN_BUSY-Blocks damit
|
||||||
|
# die Stempel selber regen koennen.
|
||||||
|
try:
|
||||||
|
dirty_gid = sc.sticky.get("_dossier_stempel_dirty")
|
||||||
|
if dirty_gid is not None:
|
||||||
|
sc.sticky["_dossier_stempel_dirty"] = None
|
||||||
|
_regenerate_stempel_for_geschoss(doc, dirty_gid)
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] stempel-cascade:", ex)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name):
|
def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name):
|
||||||
@@ -6321,6 +6520,40 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
|||||||
raum_txt_modus=meta.get("raum_txt_modus"))
|
raum_txt_modus=meta.get("raum_txt_modus"))
|
||||||
try: doc.Objects.AddText(te, attrs)
|
try: doc.Objects.AddText(te, attrs)
|
||||||
except Exception as ex: print("[ELEMENTE] Raum AddText:", ex)
|
except Exception as ex: print("[ELEMENTE] Raum AddText:", ex)
|
||||||
|
# Stempel im selben Geschoss + Total-Stempel regennen, damit ihre
|
||||||
|
# SIA-Bilanz sich aktualisiert. Deferred via Sticky-Marker damit
|
||||||
|
# das nicht im aktuellen _REGEN_BUSY-Zyklus laeuft.
|
||||||
|
try:
|
||||||
|
sc.sticky["_dossier_stempel_dirty"] = (
|
||||||
|
meta.get("geschoss") or "")
|
||||||
|
except Exception: pass
|
||||||
|
return True
|
||||||
|
elif meta["type"] == "stempel":
|
||||||
|
# Stempel re-renderet sich SELBER (kein separates Volume) — wir
|
||||||
|
# ersetzen die TextEntity in-place. Position bleibt aus der alten
|
||||||
|
# Geometrie erhalten.
|
||||||
|
if not isinstance(geom, rg.TextEntity):
|
||||||
|
print("[ELEMENTE] stempel regen: geom ist keine TextEntity")
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
pos = geom.Plane.Origin
|
||||||
|
except Exception:
|
||||||
|
pos = rg.Point3d(0, 0, 0)
|
||||||
|
scope = meta.get("stempel_scope") or "total"
|
||||||
|
txt_h = float(meta.get("stempel_txt_h", 0.20))
|
||||||
|
font = meta.get("stempel_font", "") or ""
|
||||||
|
bold = bool(meta.get("stempel_bold", False))
|
||||||
|
italic = bool(meta.get("stempel_italic", False))
|
||||||
|
new_te = _make_stempel_text(pos, scope, doc, text_height=txt_h,
|
||||||
|
z=pos.Z, font=font, bold=bold,
|
||||||
|
italic=italic)
|
||||||
|
if new_te is None: return False
|
||||||
|
# In-place replace
|
||||||
|
try:
|
||||||
|
doc.Objects.Replace(src_obj.Id, new_te)
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] stempel Replace:", ex)
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
@@ -6422,6 +6655,7 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
elif t == "CREATE_STUETZE": self._cmd_create_stuetze(p)
|
elif t == "CREATE_STUETZE": self._cmd_create_stuetze(p)
|
||||||
elif t == "CREATE_TRAEGER": self._cmd_create_traeger(p)
|
elif t == "CREATE_TRAEGER": self._cmd_create_traeger(p)
|
||||||
elif t == "CREATE_RAUM": self._cmd_create_raum(p)
|
elif t == "CREATE_RAUM": self._cmd_create_raum(p)
|
||||||
|
elif t == "CREATE_STEMPEL": self._cmd_create_stempel(p)
|
||||||
elif t == "EXPORT_RAEUME": self._cmd_export_raeume(p)
|
elif t == "EXPORT_RAEUME": self._cmd_export_raeume(p)
|
||||||
elif t == "LIST_LIBRARY": self._cmd_list_library(p)
|
elif t == "LIST_LIBRARY": self._cmd_list_library(p)
|
||||||
elif t == "CREATE_SYMBOL": self._cmd_create_symbol(p)
|
elif t == "CREATE_SYMBOL": self._cmd_create_symbol(p)
|
||||||
@@ -6678,6 +6912,23 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
"areaFmt": _format_area(area, rnd_eff),
|
"areaFmt": _format_area(area, rnd_eff),
|
||||||
"umfang": perim,
|
"umfang": perim,
|
||||||
})
|
})
|
||||||
|
elif meta["type"] == "stempel":
|
||||||
|
# Aggregierter Bilanz-Stempel (SIA 416). Scope = "total" oder
|
||||||
|
# "geschoss:<gid>". Liefert direkt die berechneten Werte fuer
|
||||||
|
# ein evtl. Properties-Panel.
|
||||||
|
scope = meta.get("stempel_scope") or "total"
|
||||||
|
bilanz = compute_sia_bilanz(doc, scope)
|
||||||
|
base.update({
|
||||||
|
"kind": "stempel",
|
||||||
|
"scope": scope,
|
||||||
|
"scopeLabel": ("Total" if scope == "total"
|
||||||
|
else (bilanz.get("geschossName") or "—")),
|
||||||
|
"txtH": meta.get("stempel_txt_h", 0.20),
|
||||||
|
"font": meta.get("stempel_font", "") or "",
|
||||||
|
"bold": bool(meta.get("stempel_bold", False)),
|
||||||
|
"italic": bool(meta.get("stempel_italic", False)),
|
||||||
|
"bilanz": bilanz,
|
||||||
|
})
|
||||||
elif meta["type"] in ("stuetze_point", "traeger_axis"):
|
elif meta["type"] in ("stuetze_point", "traeger_axis"):
|
||||||
# Tragwerk: Stuetze (Punkt) oder Traeger/Unterzug (Achse)
|
# Tragwerk: Stuetze (Punkt) oder Traeger/Unterzug (Achse)
|
||||||
gs = _geschoss_by_id(doc, meta["geschoss"])
|
gs = _geschoss_by_id(doc, meta["geschoss"])
|
||||||
@@ -8415,6 +8666,55 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
print("[ELEMENTE] Raum erzeugt: {} ({})".format(name, raum_id))
|
print("[ELEMENTE] Raum erzeugt: {} ({})".format(name, raum_id))
|
||||||
self._send_state()
|
self._send_state()
|
||||||
|
|
||||||
|
def _cmd_create_stempel(self, p):
|
||||||
|
"""Erzeugt einen SIA-Bilanz-Stempel (TextEntity) an einem User-
|
||||||
|
gepickten Punkt. Default-Scope = aktives Geschoss (oder 'total'
|
||||||
|
wenn p.scope='total' gepasst wird). Stempel re-rendert sich
|
||||||
|
automatisch wenn sich Raeume im Scope aendern."""
|
||||||
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
|
if doc is None: return
|
||||||
|
scope = p.get("scope") or ""
|
||||||
|
if not scope:
|
||||||
|
# Default: aktives Geschoss, sonst total
|
||||||
|
gid = _active_geschoss_id(doc)
|
||||||
|
scope = "geschoss:{}".format(gid) if gid else "total"
|
||||||
|
try: txt_h = float(p.get("txtH") or _last("stempel_txt_h", 0.20))
|
||||||
|
except Exception: txt_h = 0.20
|
||||||
|
try:
|
||||||
|
import Rhino.Input.Custom as ric
|
||||||
|
from Rhino.Input import GetResult
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] CREATE_STEMPEL imports:", ex); return
|
||||||
|
gp = ric.GetPoint()
|
||||||
|
gp.SetCommandPrompt("Stempel platzieren ({})".format(scope))
|
||||||
|
res = gp.Get()
|
||||||
|
if res != GetResult.Point: return
|
||||||
|
pos = gp.Point()
|
||||||
|
# Element-ID + Layer
|
||||||
|
stempel_id = "stempel_" + uuid.uuid4().hex[:10]
|
||||||
|
# Layer: nutze Raeume-Sublayer des AKTIVEN Geschosses (oder ersten),
|
||||||
|
# damit Stempel beim CPlane-Clipping auf derselben Ebene sitzt wie
|
||||||
|
# die Raeume
|
||||||
|
active_g_name = _active_geschoss_name(doc) or "EG"
|
||||||
|
layer = _ensure_layer(doc, _layer_path_raum(doc, active_g_name))
|
||||||
|
# TextEntity bauen
|
||||||
|
te = _make_stempel_text(pos, scope, doc, text_height=txt_h, z=pos.Z)
|
||||||
|
if te is None:
|
||||||
|
print("[ELEMENTE] CREATE_STEMPEL: Text-Build failed"); return
|
||||||
|
attrs = Rhino.DocObjects.ObjectAttributes()
|
||||||
|
attrs.LayerIndex = layer
|
||||||
|
_attach_meta(attrs, stempel_id, "stempel",
|
||||||
|
_active_geschoss_id(doc) or "",
|
||||||
|
0.0, "", "", "mid",
|
||||||
|
stempel_scope=scope, stempel_txt_h=txt_h)
|
||||||
|
new_id = doc.Objects.AddText(te, attrs)
|
||||||
|
if new_id == System.Guid.Empty:
|
||||||
|
print("[ELEMENTE] Stempel AddText fehlgeschlagen"); return
|
||||||
|
_save_last(stempel_txt_h=txt_h)
|
||||||
|
doc.Views.Redraw()
|
||||||
|
print("[ELEMENTE] Stempel erzeugt: {} (scope={})".format(stempel_id, scope))
|
||||||
|
self._send_state()
|
||||||
|
|
||||||
def _cmd_export_raeume(self, p):
|
def _cmd_export_raeume(self, p):
|
||||||
"""Schreibt CSV mit allen Raeumen: Nummer, Name, Geschoss,
|
"""Schreibt CSV mit allen Raeumen: Nummer, Name, Geschoss,
|
||||||
Funktion, SIA, Flaeche, Umfang. Datei via SaveFileDialog."""
|
Funktion, SIA, Flaeche, Umfang. Datei via SaveFileDialog."""
|
||||||
@@ -10402,6 +10702,29 @@ class ElementeBridge(panel_base.BaseBridge):
|
|||||||
doc.Views.Redraw()
|
doc.Views.Redraw()
|
||||||
self._send_state()
|
self._send_state()
|
||||||
return
|
return
|
||||||
|
# Stempel: scope / txtH / font / bold / italic
|
||||||
|
if old_meta["type"] == "stempel":
|
||||||
|
scope = p.get("scope", old_meta.get("stempel_scope", "total"))
|
||||||
|
if not (scope == "total" or scope.startswith("geschoss:")):
|
||||||
|
scope = "total"
|
||||||
|
try: st_th = float(p.get("txtH", old_meta.get("stempel_txt_h", 0.20)))
|
||||||
|
except Exception: st_th = 0.20
|
||||||
|
st_font = p.get("font", old_meta.get("stempel_font", "") or "")
|
||||||
|
st_bold = bool(p.get("bold", old_meta.get("stempel_bold", False)))
|
||||||
|
st_ital = bool(p.get("italic", old_meta.get("stempel_italic", False)))
|
||||||
|
attrs = axis_obj.Attributes
|
||||||
|
_attach_meta(attrs, wall_id, "stempel",
|
||||||
|
old_meta.get("geschoss", ""), 0.0, "", "", "mid",
|
||||||
|
stempel_scope=scope, stempel_txt_h=st_th,
|
||||||
|
stempel_font=st_font, stempel_bold=st_bold,
|
||||||
|
stempel_italic=st_ital)
|
||||||
|
axis_obj.Attributes = attrs
|
||||||
|
axis_obj.CommitChanges()
|
||||||
|
_save_last(stempel_txt_h=st_th)
|
||||||
|
_regenerate_element(doc, wall_id)
|
||||||
|
doc.Views.Redraw()
|
||||||
|
self._send_state()
|
||||||
|
return
|
||||||
# Treppe: Breite/Anzahl Stufen/Referenz/Zielgeschoss
|
# Treppe: Breite/Anzahl Stufen/Referenz/Zielgeschoss
|
||||||
if old_meta["type"] == "treppe_axis":
|
if old_meta["type"] == "treppe_axis":
|
||||||
try: tb = float(p.get("breite", old_meta.get("treppe_breite", 1.0)))
|
try: tb = float(p.get("breite", old_meta.get("treppe_breite", 1.0)))
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ _KIND_MAP = {
|
|||||||
"stuetze_point": "stuetze",
|
"stuetze_point": "stuetze",
|
||||||
"traeger_axis": "traeger",
|
"traeger_axis": "traeger",
|
||||||
"raum_outline": "raum",
|
"raum_outline": "raum",
|
||||||
|
"stempel": "stempel",
|
||||||
"decke_aussparung_outline": "aussparung",
|
"decke_aussparung_outline": "aussparung",
|
||||||
"oeffnung_point": "oeffnung", # wird zu fenster/tuer aufgeloest
|
"oeffnung_point": "oeffnung", # wird zu fenster/tuer aufgeloest
|
||||||
}
|
}
|
||||||
@@ -131,18 +132,21 @@ def _build_overview(doc):
|
|||||||
if not area or area <= 0: continue
|
if not area or area <= 0: continue
|
||||||
g_id = meta.get("geschoss") or "__keingeschoss__"
|
g_id = meta.get("geschoss") or "__keingeschoss__"
|
||||||
sia = (meta.get("raum_sia") or "").lower()
|
sia = (meta.get("raum_sia") or "").lower()
|
||||||
if sia not in ("hnf", "nnf", "vf", "ff"):
|
if sia not in ("hnf", "nnf", "vf", "ff", "gf", "agf"):
|
||||||
sia = "ohne"
|
sia = "ohne"
|
||||||
b = sia_bilanz.setdefault(g_id, {
|
b = sia_bilanz.setdefault(g_id, {
|
||||||
"hnf": 0.0, "nnf": 0.0, "vf": 0.0, "ff": 0.0,
|
"hnf": 0.0, "nnf": 0.0, "vf": 0.0, "ff": 0.0,
|
||||||
|
"gf": 0.0, "agf": 0.0,
|
||||||
"ohne": 0.0, "count": 0,
|
"ohne": 0.0, "count": 0,
|
||||||
})
|
})
|
||||||
b[sia] += float(area)
|
b[sia] += float(area)
|
||||||
b["count"] += 1
|
b["count"] += 1
|
||||||
# NF + Total ableiten
|
# NF/NGF/Total ableiten
|
||||||
for b in sia_bilanz.values():
|
for b in sia_bilanz.values():
|
||||||
b["nf"] = b["hnf"] + b["nnf"]
|
b["nf"] = b["hnf"] + b["nnf"]
|
||||||
b["total"] = b["hnf"] + b["nnf"] + b["vf"] + b["ff"] + b["ohne"]
|
b["ngf"] = b["nf"] + b["vf"] + b["ff"]
|
||||||
|
b["total"] = (b["hnf"] + b["nnf"] + b["vf"] + b["ff"]
|
||||||
|
+ b["gf"] + b["agf"] + b["ohne"])
|
||||||
|
|
||||||
return {"geschosse": out_geschosse, "items": items,
|
return {"geschosse": out_geschosse, "items": items,
|
||||||
"siaBilanz": sia_bilanz}
|
"siaBilanz": sia_bilanz}
|
||||||
|
|||||||
+103
-1
@@ -7,7 +7,7 @@ import {
|
|||||||
onMessage, notifyReady,
|
onMessage, notifyReady,
|
||||||
createWall, createDecke, createDach,
|
createWall, createDecke, createDach,
|
||||||
createFenster, createTuer, createAussparung, createTreppe,
|
createFenster, createTuer, createAussparung, createTreppe,
|
||||||
createStuetze, createTraeger, createRaum,
|
createStuetze, createTraeger, createRaum, createStempel,
|
||||||
openSwisstopo, openSwisstopoDialog, openOsmDialog,
|
openSwisstopo, openSwisstopoDialog, openOsmDialog,
|
||||||
updateElement, deleteElement, openElementeUebersicht, openElementeProperties,
|
updateElement, deleteElement, openElementeUebersicht, openElementeProperties,
|
||||||
saveOeffStyle, deleteOeffStyle,
|
saveOeffStyle, deleteOeffStyle,
|
||||||
@@ -178,6 +178,7 @@ const KIND_META = {
|
|||||||
stuetze: { icon: 'square_foot', label: 'Stütze', color: '#5fa896' },
|
stuetze: { icon: 'square_foot', label: 'Stütze', color: '#5fa896' },
|
||||||
traeger: { icon: 'horizontal_rule', label: 'Träger', color: '#7fc8a8' },
|
traeger: { icon: 'horizontal_rule', label: 'Träger', color: '#7fc8a8' },
|
||||||
raum: { icon: 'crop_free', label: 'Raum', color: '#a0a8b0' },
|
raum: { icon: 'crop_free', label: 'Raum', color: '#a0a8b0' },
|
||||||
|
stempel: { icon: 'receipt_long', label: 'Stempel', color: '#5fa896' },
|
||||||
aussparung: { icon: 'rectangle', label: 'Aussparung', color: '#9090a0' },
|
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: 'nnf', label: 'NNF', color: '#e8c498', hint: 'Nebennutzfläche' },
|
||||||
{ code: 'vf', label: 'VF', color: '#e8d878', hint: 'Verkehrsfläche' },
|
{ code: 'vf', label: 'VF', color: '#e8d878', hint: 'Verkehrsfläche' },
|
||||||
{ code: 'ff', label: 'FF', color: '#a8c8e0', hint: 'Funktionsflä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 = {
|
const PROFIL_META = {
|
||||||
@@ -479,6 +482,11 @@ function NeuesElementSection({ noGeschoss, activeName, elementsCount }) {
|
|||||||
'Outline zeichnen · Stempel zeigt Name + Fläche'}
|
'Outline zeichnen · Stempel zeigt Name + Fläche'}
|
||||||
disabled={dis}
|
disabled={dis}
|
||||||
onClick={() => createRaum({})} />
|
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>
|
||||||
|
|
||||||
<PillGroup label="Library">
|
<PillGroup label="Library">
|
||||||
@@ -529,6 +537,9 @@ export function PropertiesView({ selected, geschosse, materials, hatchPatterns,
|
|||||||
hatchPatterns={hatchPatterns} fonts={fonts || []}
|
hatchPatterns={hatchPatterns} fonts={fonts || []}
|
||||||
raumStempelStile={raumStempelStile || []}
|
raumStempelStile={raumStempelStile || []}
|
||||||
onUpdate={upd} onDelete={del('Raum')} />
|
onUpdate={upd} onDelete={del('Raum')} />
|
||||||
|
if (selected.kind === 'stempel')
|
||||||
|
return <StempelProperties stempel={selected} geschosse={geschosse}
|
||||||
|
onUpdate={upd} onDelete={del('Stempel')} />
|
||||||
|
|
||||||
if (selected.kind === 'aussparung')
|
if (selected.kind === 'aussparung')
|
||||||
return <AussparungProperties aussp={selected} onDelete={del('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 }) {
|
function AussparungProperties({ aussp, onDelete }) {
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { onMessage, notifyReady, send } from './lib/rhinoBridge'
|
|||||||
|
|
||||||
const KIND_ORDER = [
|
const KIND_ORDER = [
|
||||||
'wand', 'decke', 'dach', 'fenster', 'tuer', 'aussparung',
|
'wand', 'decke', 'dach', 'fenster', 'tuer', 'aussparung',
|
||||||
'treppe', 'stuetze', 'traeger', 'raum',
|
'treppe', 'stuetze', 'traeger', 'raum', 'stempel',
|
||||||
]
|
]
|
||||||
|
|
||||||
const KIND_META = {
|
const KIND_META = {
|
||||||
@@ -24,6 +24,7 @@ const KIND_META = {
|
|||||||
stuetze: { icon: 'square_foot', label: 'Stützen', color: '#c87050' },
|
stuetze: { icon: 'square_foot', label: 'Stützen', color: '#c87050' },
|
||||||
traeger: { icon: 'horizontal_rule', label: 'Träger', color: '#a87858' },
|
traeger: { icon: 'horizontal_rule', label: 'Träger', color: '#a87858' },
|
||||||
raum: { icon: 'crop_free', label: 'Räume', color: '#5fa896' },
|
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 createStuetze(p) { send('CREATE_STUETZE', p || {}) }
|
||||||
export function createTraeger(p) { send('CREATE_TRAEGER', p || {}) }
|
export function createTraeger(p) { send('CREATE_TRAEGER', p || {}) }
|
||||||
export function createRaum(p) { send('CREATE_RAUM', p || {}) }
|
export function createRaum(p) { send('CREATE_RAUM', p || {}) }
|
||||||
|
export function createStempel(p) { send('CREATE_STEMPEL', p || {}) }
|
||||||
export function exportRaeume() { send('EXPORT_RAEUME', {}) }
|
export function exportRaeume() { send('EXPORT_RAEUME', {}) }
|
||||||
// Library-Symbol/Object — Picker im Elemente-Panel
|
// Library-Symbol/Object — Picker im Elemente-Panel
|
||||||
export function listLibrary() { send('LIST_LIBRARY', {}) }
|
export function listLibrary() { send('LIST_LIBRARY', {}) }
|
||||||
|
|||||||
Reference in New Issue
Block a user