Stempel: Stile + Layout-Customisation (Header, Show-Toggles)
Bilanz-Stempel hat jetzt: - Custom Header (Default "Nutzflächen", frei editierbar) - Per-Stempel Show-Toggles: scope, hnf, nnf, nf, vf, ff, ngf, gf, agf, count (Anzahl Raeume), separators (Trennlinien) - Stempel-Stile (Presets) per Doc — separate Storage von Raumstempel- Stilen (dossier_stempel_stile) Backend (elemente.py): - UserStrings: dossier_stempel_header + dossier_stempel_show_* (11 Flags) + dossier_stempel_stil_id - _make_stempel_text erweitert: header_text, show_scope, show_separators, show_count, visibility-dict - _format_bilanz_lines: visibility + show_separators + show_count Args - load_stempel_stile / save_stempel_stile (eigener doc.Strings-Key) - Bridge-Handler SAVE/DELETE/APPLY_STEMPEL_STIL (analog Raum-Stil-Pattern, inkl. Bulk-Apply via Selection + applyToIds-Mechanism wie SaveRaum-Stil) - _sync_stempel_to_source spiegelt Font/Bold/Italic/TextHeight vom Live- TextEntity zur UserString-Storage (Oberleiste-Edits ueberleben Regen) - _update_wall stempel-Branch um header + show-Flags erweitert Frontend (StempelProperties): - Stil-Picker-Dropdown analog Raum (Stil wählen / + speichern / 🗑 löschen) - Header-Input (Inline-Text) - 11 Show-Toggles als kompakte Grid (Check-Box-Stil) - Live-Vorschau respektiert Visibility-Flags + bilanz.count
This commit is contained in:
+393
-31
@@ -2715,6 +2715,13 @@ def _attach_meta(obj_attrs, wall_id, type_, geschoss, dicke, uk_over, ok_over,
|
||||
raum_layout=None, raum_txt_modus=None,
|
||||
stempel_scope=None, stempel_txt_h=None,
|
||||
stempel_font=None, stempel_bold=None, stempel_italic=None,
|
||||
stempel_header=None, stempel_show_scope=None,
|
||||
stempel_show_hnf=None, stempel_show_nnf=None,
|
||||
stempel_show_nf=None, stempel_show_vf=None,
|
||||
stempel_show_ff=None, stempel_show_ngf=None,
|
||||
stempel_show_gf=None, stempel_show_agf=None,
|
||||
stempel_show_count=None, stempel_show_seps=None,
|
||||
stempel_stil_id=None,
|
||||
wand_layered=None, wand_layers=None, wand_layer_idx=None,
|
||||
wand_chain_members=None,
|
||||
aussp_parent=None):
|
||||
@@ -2934,6 +2941,25 @@ def _attach_meta(obj_attrs, wall_id, type_, geschoss, dicke, uk_over, ok_over,
|
||||
if stempel_italic is not None:
|
||||
obj_attrs.SetUserString(_KEY_STEMPEL_ITAL,
|
||||
"1" if bool(stempel_italic) else "0")
|
||||
if stempel_header is not None:
|
||||
obj_attrs.SetUserString(_KEY_STEMPEL_HEADER, str(stempel_header))
|
||||
for _k, _v in (
|
||||
(_KEY_STEMPEL_SHOW_SCOPE, stempel_show_scope),
|
||||
(_KEY_STEMPEL_SHOW_HNF, stempel_show_hnf),
|
||||
(_KEY_STEMPEL_SHOW_NNF, stempel_show_nnf),
|
||||
(_KEY_STEMPEL_SHOW_NF, stempel_show_nf),
|
||||
(_KEY_STEMPEL_SHOW_VF, stempel_show_vf),
|
||||
(_KEY_STEMPEL_SHOW_FF, stempel_show_ff),
|
||||
(_KEY_STEMPEL_SHOW_NGF, stempel_show_ngf),
|
||||
(_KEY_STEMPEL_SHOW_GF, stempel_show_gf),
|
||||
(_KEY_STEMPEL_SHOW_AGF, stempel_show_agf),
|
||||
(_KEY_STEMPEL_SHOW_COUNT, stempel_show_count),
|
||||
(_KEY_STEMPEL_SHOW_SEPS, stempel_show_seps),
|
||||
):
|
||||
if _v is not None:
|
||||
obj_attrs.SetUserString(_k, "1" if bool(_v) else "0")
|
||||
if stempel_stil_id is not None:
|
||||
obj_attrs.SetUserString(_KEY_STEMPEL_STIL_ID, str(stempel_stil_id))
|
||||
# Wand-Schichten
|
||||
if wand_layered is not None:
|
||||
obj_attrs.SetUserString(_KEY_WAND_LAYERED,
|
||||
@@ -3141,6 +3167,29 @@ def _read_meta(obj):
|
||||
st_font = a.GetUserString(_KEY_STEMPEL_FONT) or ""
|
||||
st_bold = (a.GetUserString(_KEY_STEMPEL_BOLD) == "1")
|
||||
st_ital = (a.GetUserString(_KEY_STEMPEL_ITAL) == "1")
|
||||
# Stempel-Layout-Custom (Defaults: alle "show" on ausser show_count;
|
||||
# leer/None → True ausser show_count → False)
|
||||
st_header = a.GetUserString(_KEY_STEMPEL_HEADER)
|
||||
if st_header is None or st_header == "":
|
||||
st_header = "Nutzflächen"
|
||||
def _sh_on(key, default=True):
|
||||
v = a.GetUserString(key)
|
||||
if default:
|
||||
return v != "0" # None/""/"1" → True
|
||||
else:
|
||||
return v == "1" # nur explizites "1" → True
|
||||
st_show_scope = _sh_on(_KEY_STEMPEL_SHOW_SCOPE, True)
|
||||
st_show_hnf = _sh_on(_KEY_STEMPEL_SHOW_HNF, True)
|
||||
st_show_nnf = _sh_on(_KEY_STEMPEL_SHOW_NNF, True)
|
||||
st_show_nf = _sh_on(_KEY_STEMPEL_SHOW_NF, True)
|
||||
st_show_vf = _sh_on(_KEY_STEMPEL_SHOW_VF, True)
|
||||
st_show_ff = _sh_on(_KEY_STEMPEL_SHOW_FF, True)
|
||||
st_show_ngf = _sh_on(_KEY_STEMPEL_SHOW_NGF, True)
|
||||
st_show_gf = _sh_on(_KEY_STEMPEL_SHOW_GF, True)
|
||||
st_show_agf = _sh_on(_KEY_STEMPEL_SHOW_AGF, True)
|
||||
st_show_count = _sh_on(_KEY_STEMPEL_SHOW_COUNT, False)
|
||||
st_show_seps = _sh_on(_KEY_STEMPEL_SHOW_SEPS, True)
|
||||
st_stil_id = a.GetUserString(_KEY_STEMPEL_STIL_ID) or ""
|
||||
# Field-Layout — parsed JSON list of rows
|
||||
r_layout_raw = a.GetUserString(_KEY_RAUM_LAYOUT) or ""
|
||||
r_layout = []
|
||||
@@ -3268,6 +3317,19 @@ def _read_meta(obj):
|
||||
"stempel_font": st_font,
|
||||
"stempel_bold": st_bold,
|
||||
"stempel_italic": st_ital,
|
||||
"stempel_header": st_header,
|
||||
"stempel_show_scope": st_show_scope,
|
||||
"stempel_show_hnf": st_show_hnf,
|
||||
"stempel_show_nnf": st_show_nnf,
|
||||
"stempel_show_nf": st_show_nf,
|
||||
"stempel_show_vf": st_show_vf,
|
||||
"stempel_show_ff": st_show_ff,
|
||||
"stempel_show_ngf": st_show_ngf,
|
||||
"stempel_show_gf": st_show_gf,
|
||||
"stempel_show_agf": st_show_agf,
|
||||
"stempel_show_count": st_show_count,
|
||||
"stempel_show_seps": st_show_seps,
|
||||
"stempel_stil_id": st_stil_id,
|
||||
"wand_layered": w_layered,
|
||||
"wand_layers": w_layers,
|
||||
"wand_layer_idx": w_layer_idx,
|
||||
@@ -3892,6 +3954,58 @@ _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"
|
||||
# Layout-Customisation (per Stempel)
|
||||
_KEY_STEMPEL_HEADER = "dossier_stempel_header" # default "Nutzflächen"
|
||||
_KEY_STEMPEL_SHOW_SCOPE = "dossier_stempel_show_scope" # default "1"
|
||||
_KEY_STEMPEL_SHOW_HNF = "dossier_stempel_show_hnf" # default "1"
|
||||
_KEY_STEMPEL_SHOW_NNF = "dossier_stempel_show_nnf" # default "1"
|
||||
_KEY_STEMPEL_SHOW_NF = "dossier_stempel_show_nf" # default "1"
|
||||
_KEY_STEMPEL_SHOW_VF = "dossier_stempel_show_vf" # default "1"
|
||||
_KEY_STEMPEL_SHOW_FF = "dossier_stempel_show_ff" # default "1"
|
||||
_KEY_STEMPEL_SHOW_NGF = "dossier_stempel_show_ngf" # default "1"
|
||||
_KEY_STEMPEL_SHOW_GF = "dossier_stempel_show_gf" # default "1"
|
||||
_KEY_STEMPEL_SHOW_AGF = "dossier_stempel_show_agf" # default "1"
|
||||
_KEY_STEMPEL_SHOW_COUNT = "dossier_stempel_show_count" # default "0"
|
||||
_KEY_STEMPEL_SHOW_SEPS = "dossier_stempel_show_seps" # default "1"
|
||||
_KEY_STEMPEL_STIL_ID = "dossier_stempel_stil_id" # aktiver Stil
|
||||
# Storage-Key fuer Stempel-Stile (Presets, per Doc)
|
||||
_KEY_STEMPEL_STILE = "dossier_stempel_stile"
|
||||
_STEMPEL_STIL_FIELDS = (
|
||||
"header", "showScope",
|
||||
"showHnf", "showNnf", "showNf", "showVf", "showFf",
|
||||
"showNgf", "showGf", "showAgf", "showCount", "showSeparators",
|
||||
"font", "bold", "italic", "txtH",
|
||||
)
|
||||
|
||||
|
||||
def load_stempel_stile(doc):
|
||||
if doc is None: return []
|
||||
try: raw = doc.Strings.GetValue(_KEY_STEMPEL_STILE) or ""
|
||||
except Exception: raw = ""
|
||||
if not raw: return []
|
||||
try:
|
||||
import json as _json
|
||||
data = _json.loads(raw)
|
||||
if not isinstance(data, list): return []
|
||||
return [s for s in data if isinstance(s, dict)
|
||||
and s.get("id") and s.get("name")]
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] load_stempel_stile:", ex)
|
||||
return []
|
||||
|
||||
|
||||
def save_stempel_stile(doc, stile):
|
||||
if doc is None or not isinstance(stile, list): return False
|
||||
try:
|
||||
import json as _json
|
||||
clean = [s for s in stile if isinstance(s, dict)
|
||||
and s.get("id") and s.get("name")]
|
||||
doc.Strings.SetString(_KEY_STEMPEL_STILE,
|
||||
_json.dumps(clean, ensure_ascii=False))
|
||||
return True
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] save_stempel_stile:", ex)
|
||||
return False
|
||||
# Oeffnungs-Cutout: Boolean-Difference aus Wand. Zusaetzlich kriegt die
|
||||
# Oeffnung ihr eigenes Volumen (Rahmen + Sims + Glas) als Sub-Element.
|
||||
|
||||
@@ -5061,51 +5175,89 @@ def compute_sia_bilanz(doc, scope="total"):
|
||||
return out
|
||||
|
||||
|
||||
def _format_bilanz_lines(bilanz, rundung="0.1"):
|
||||
def _format_bilanz_lines(bilanz, rundung="0.1", visibility=None,
|
||||
show_separators=True, show_count=False):
|
||||
"""Baut die Stempel-Textzeilen aus einer Bilanz. Zeigt nur Kategorien
|
||||
mit Flaeche > 0. Format kompakt: 'HNF 120.5 m²'."""
|
||||
mit Flaeche > 0 UND deren visibility-Flag True ist.
|
||||
|
||||
visibility = dict mit Keys hnf/nnf/nf/vf/ff/ngf/gf/agf → True/False.
|
||||
None heisst: alle anzeigen (Default).
|
||||
"""
|
||||
if visibility is None:
|
||||
visibility = {}
|
||||
def _v(k): return visibility.get(k, True)
|
||||
sep = "────────────"
|
||||
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("────────────")
|
||||
show_hnf = _v("hnf") and bilanz["hnf"] > 0
|
||||
show_nnf = _v("nnf") and bilanz["nnf"] > 0
|
||||
has_nf_inputs = show_hnf or show_nnf
|
||||
if show_hnf: lines.append(_row("HNF", bilanz["hnf"]))
|
||||
if show_nnf: lines.append(_row("NNF", bilanz["nnf"]))
|
||||
if _v("nf") and has_nf_inputs and bilanz["nf"] > 0:
|
||||
if show_separators: lines.append(sep)
|
||||
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"]))
|
||||
show_vf = _v("vf") and bilanz["vf"] > 0
|
||||
show_ff = _v("ff") and bilanz["ff"] > 0
|
||||
if show_vf: lines.append(_row("VF", bilanz["vf"]))
|
||||
if show_ff: lines.append(_row("FF", bilanz["ff"]))
|
||||
# NGF wenn was zusammenkommt
|
||||
if bilanz["ngf"] > 0 and (bilanz["vf"] > 0 or bilanz["ff"] > 0):
|
||||
lines.append("────────────")
|
||||
if _v("ngf") and bilanz["ngf"] > 0 and (show_vf or show_ff or has_nf_inputs):
|
||||
if show_separators: lines.append(sep)
|
||||
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"]))
|
||||
show_gf = _v("gf") and bilanz["gf"] > 0
|
||||
show_agf = _v("agf") and bilanz["agf"] > 0
|
||||
if show_gf or show_agf:
|
||||
if show_separators and lines: lines.append(sep)
|
||||
if show_gf: lines.append(_row("GF", bilanz["gf"]))
|
||||
if show_agf: lines.append(_row("AGF", bilanz["agf"]))
|
||||
# Count optional
|
||||
if show_count and bilanz.get("count", 0) > 0:
|
||||
if show_separators and lines: lines.append(sep)
|
||||
lines.append("{} Räume".format(bilanz["count"]))
|
||||
return lines
|
||||
|
||||
|
||||
def _make_stempel_text(pos, scope, doc, text_height=0.20, z=0.0,
|
||||
font=None, bold=False, italic=False):
|
||||
font=None, bold=False, italic=False,
|
||||
header_text="Nutzflächen", show_scope=True,
|
||||
show_separators=True, show_count=False,
|
||||
visibility=None):
|
||||
"""Baut die Stempel-TextEntity fuer einen Scope ("total" oder
|
||||
"geschoss:<id>"). Header = "Nutzflächen · {Scope-Label}"."""
|
||||
"geschoss:<id>"). header_text + show_* steuern Layout-Customisation.
|
||||
|
||||
visibility = dict mit hnf/nnf/nf/vf/ff/ngf/gf/agf → True/False.
|
||||
Default: alle True (= aktuelles Verhalten, nur Kategorien > 0).
|
||||
"""
|
||||
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)
|
||||
# Header zusammenbauen
|
||||
header_parts = []
|
||||
if header_text and header_text.strip():
|
||||
header_parts.append(header_text.strip())
|
||||
if show_scope:
|
||||
if scope == "total":
|
||||
scope_label = "Total"
|
||||
elif bilanz["geschossName"]:
|
||||
scope_label = bilanz["geschossName"]
|
||||
else:
|
||||
scope_label = "—"
|
||||
header_parts.append(scope_label)
|
||||
header = " · ".join(header_parts) if header_parts else ""
|
||||
body = _format_bilanz_lines(bilanz, visibility=visibility,
|
||||
show_separators=show_separators,
|
||||
show_count=show_count)
|
||||
if not body:
|
||||
body = ["(keine klassifizierten Räume)"]
|
||||
text = "\n".join([header, "════════════"] + body)
|
||||
lines = []
|
||||
if header: lines.append(header)
|
||||
if show_separators and header: lines.append("════════════")
|
||||
lines.extend(body)
|
||||
text = "\n".join(lines)
|
||||
te = rg.TextEntity()
|
||||
te.Text = text
|
||||
plane = rg.Plane(rg.Point3d(pos.X, pos.Y, float(z)),
|
||||
@@ -6588,9 +6740,26 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
||||
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)
|
||||
# Layout-Customisation aus Meta
|
||||
header_text = meta.get("stempel_header", "Nutzflächen") or "Nutzflächen"
|
||||
visibility = {
|
||||
"hnf": bool(meta.get("stempel_show_hnf", True)),
|
||||
"nnf": bool(meta.get("stempel_show_nnf", True)),
|
||||
"nf": bool(meta.get("stempel_show_nf", True)),
|
||||
"vf": bool(meta.get("stempel_show_vf", True)),
|
||||
"ff": bool(meta.get("stempel_show_ff", True)),
|
||||
"ngf": bool(meta.get("stempel_show_ngf", True)),
|
||||
"gf": bool(meta.get("stempel_show_gf", True)),
|
||||
"agf": bool(meta.get("stempel_show_agf", True)),
|
||||
}
|
||||
new_te = _make_stempel_text(
|
||||
pos, scope, doc, text_height=txt_h,
|
||||
z=pos.Z, font=font, bold=bold, italic=italic,
|
||||
header_text=header_text,
|
||||
show_scope=bool(meta.get("stempel_show_scope", True)),
|
||||
show_separators=bool(meta.get("stempel_show_seps", True)),
|
||||
show_count=bool(meta.get("stempel_show_count", False)),
|
||||
visibility=visibility)
|
||||
if new_te is None: return False
|
||||
# In-place replace
|
||||
try:
|
||||
@@ -6741,6 +6910,13 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
elif t == "DUPLICATE_RAUM_STIL":
|
||||
# p = {id, newName?} → kopiert Stil mit neuer uuid + Name
|
||||
self._cmd_duplicate_raum_stil(p)
|
||||
# Stempel-Stile (analog Raumstempel-Stile, eigene Storage)
|
||||
elif t == "SAVE_STEMPEL_STIL":
|
||||
self._cmd_save_stempel_stil(p)
|
||||
elif t == "DELETE_STEMPEL_STIL":
|
||||
self._cmd_delete_stempel_stil(p)
|
||||
elif t == "APPLY_STEMPEL_STIL":
|
||||
self._cmd_apply_stempel_stil(p)
|
||||
elif t == "OPEN_ELEMENTE_UEBERSICHT":
|
||||
try:
|
||||
import elemente_uebersicht
|
||||
@@ -6971,6 +7147,19 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
"font": meta.get("stempel_font", "") or "",
|
||||
"bold": bool(meta.get("stempel_bold", False)),
|
||||
"italic": bool(meta.get("stempel_italic", False)),
|
||||
"header": meta.get("stempel_header", "Nutzflächen"),
|
||||
"showScope": bool(meta.get("stempel_show_scope", True)),
|
||||
"showHnf": bool(meta.get("stempel_show_hnf", True)),
|
||||
"showNnf": bool(meta.get("stempel_show_nnf", True)),
|
||||
"showNf": bool(meta.get("stempel_show_nf", True)),
|
||||
"showVf": bool(meta.get("stempel_show_vf", True)),
|
||||
"showFf": bool(meta.get("stempel_show_ff", True)),
|
||||
"showNgf": bool(meta.get("stempel_show_ngf", True)),
|
||||
"showGf": bool(meta.get("stempel_show_gf", True)),
|
||||
"showAgf": bool(meta.get("stempel_show_agf", True)),
|
||||
"showCount": bool(meta.get("stempel_show_count", False)),
|
||||
"showSeparators": bool(meta.get("stempel_show_seps", True)),
|
||||
"stilId": meta.get("stempel_stil_id", ""),
|
||||
"bilanz": bilanz,
|
||||
})
|
||||
elif meta["type"] in ("stuetze_point", "traeger_axis"):
|
||||
@@ -7025,6 +7214,7 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
for n, m in _get_all_materials(doc).items()],
|
||||
"oeffStyles": list_oeff_styles(doc),
|
||||
"raumStempelStile": load_raum_stempel_stile(doc),
|
||||
"stempelStile": load_stempel_stile(doc),
|
||||
}
|
||||
self.send("STATE", payload)
|
||||
# An Properties-Satellite-Window forwarden falls offen
|
||||
@@ -9054,6 +9244,101 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
stil.get("name"), n_applied))
|
||||
self._send_state()
|
||||
|
||||
def _cmd_save_stempel_stil(self, p):
|
||||
"""Speichert einen Stempel-Stil (Layout/Header/Font-Preset).
|
||||
Payload: {id?, name, settings:{header, showScope, show*, font,
|
||||
bold, italic, txtH}, applyToIds?: [stempel_id, ...]}.
|
||||
"""
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc is None: return
|
||||
name = (p.get("name") or "").strip()
|
||||
if not name:
|
||||
print("[ELEMENTE] SAVE_STEMPEL_STIL: name leer"); return
|
||||
settings = p.get("settings") or {}
|
||||
sid = (p.get("id") or "").strip() or ("stestil_" + uuid.uuid4().hex[:8])
|
||||
stile = load_stempel_stile(doc)
|
||||
new_stil = {"id": sid, "name": name}
|
||||
for f in _STEMPEL_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)
|
||||
if save_stempel_stile(doc, stile):
|
||||
print("[ELEMENTE] Stempel-Stil '{}' ({}) gespeichert".format(
|
||||
name, sid))
|
||||
# Apply on listed Stempel (nur stil_id schreiben)
|
||||
for eid in (p.get("applyToIds") or []):
|
||||
try:
|
||||
src_obj, _ = _find_source(doc, eid)
|
||||
if src_obj is None: continue
|
||||
attrs = src_obj.Attributes.Duplicate()
|
||||
attrs.SetUserString(_KEY_STEMPEL_STIL_ID, sid)
|
||||
doc.Objects.ModifyAttributes(src_obj.Id, attrs, True)
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] save-stempel-stil apply-to:", ex)
|
||||
self._send_state()
|
||||
|
||||
def _cmd_delete_stempel_stil(self, p):
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc is None: return
|
||||
sid = (p.get("id") or "").strip()
|
||||
if not sid: return
|
||||
stile = load_stempel_stile(doc)
|
||||
stile = [s for s in stile if s.get("id") != sid]
|
||||
if save_stempel_stile(doc, stile):
|
||||
print("[ELEMENTE] Stempel-Stil {} geloescht".format(sid))
|
||||
self._send_state()
|
||||
|
||||
def _cmd_apply_stempel_stil(self, p):
|
||||
"""Wendet einen Stempel-Stil auf Stempel-IDs an. Wenn ids leer →
|
||||
alle aktuell selektierten Stempel."""
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc is None: return
|
||||
sid = (p.get("stilId") or "").strip()
|
||||
ids = p.get("ids") or []
|
||||
if not sid: return
|
||||
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") == "stempel":
|
||||
ids.append(m["id"])
|
||||
except Exception: pass
|
||||
if not ids: return
|
||||
stile = load_stempel_stile(doc)
|
||||
stil = next((s for s in stile if s.get("id") == sid), None)
|
||||
if stil is None:
|
||||
print("[ELEMENTE] APPLY_STEMPEL_STIL: {} nicht gefunden".format(sid))
|
||||
return
|
||||
# Stil-Felder → Patch fuer _update_wall stempel-Branch
|
||||
patch_base = {}
|
||||
for f in _STEMPEL_STIL_FIELDS:
|
||||
if f in stil: patch_base[f] = stil[f]
|
||||
n = 0
|
||||
for eid in ids:
|
||||
try:
|
||||
patch = dict(patch_base); patch["id"] = eid
|
||||
self._update_wall(patch)
|
||||
# stil_id markieren
|
||||
try:
|
||||
src_obj, _ = _find_source(doc, eid)
|
||||
if src_obj is not None:
|
||||
attrs = src_obj.Attributes.Duplicate()
|
||||
attrs.SetUserString(_KEY_STEMPEL_STIL_ID, sid)
|
||||
doc.Objects.ModifyAttributes(src_obj.Id, attrs, True)
|
||||
except Exception: pass
|
||||
n += 1
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] apply stempel-stil {} -> {}: {}".format(
|
||||
sid, eid, ex))
|
||||
if n > 0:
|
||||
print("[ELEMENTE] Stempel-Stil '{}' auf {} Stempel angewendet".format(
|
||||
stil.get("name"), n))
|
||||
self._send_state()
|
||||
|
||||
def _cmd_create_symbol(self, p):
|
||||
"""Platziert ein Library-Item (symbol/object) im Doc. Interactive
|
||||
GetPoint im aktiven Viewport — User klickt Position.
|
||||
@@ -10751,7 +11036,7 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
doc.Views.Redraw()
|
||||
self._send_state()
|
||||
return
|
||||
# Stempel: scope / txtH / font / bold / italic
|
||||
# Stempel: scope / txtH / font / bold / italic + Layout-Custom
|
||||
if old_meta["type"] == "stempel":
|
||||
scope = p.get("scope", old_meta.get("stempel_scope", "total"))
|
||||
if not (scope == "total" or scope.startswith("geschoss:")):
|
||||
@@ -10761,12 +11046,47 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
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)))
|
||||
st_header = p.get("header", old_meta.get("stempel_header", "Nutzflächen"))
|
||||
st_show_scope = bool(p.get("showScope",
|
||||
old_meta.get("stempel_show_scope", True)))
|
||||
st_show_hnf = bool(p.get("showHnf",
|
||||
old_meta.get("stempel_show_hnf", True)))
|
||||
st_show_nnf = bool(p.get("showNnf",
|
||||
old_meta.get("stempel_show_nnf", True)))
|
||||
st_show_nf = bool(p.get("showNf",
|
||||
old_meta.get("stempel_show_nf", True)))
|
||||
st_show_vf = bool(p.get("showVf",
|
||||
old_meta.get("stempel_show_vf", True)))
|
||||
st_show_ff = bool(p.get("showFf",
|
||||
old_meta.get("stempel_show_ff", True)))
|
||||
st_show_ngf = bool(p.get("showNgf",
|
||||
old_meta.get("stempel_show_ngf", True)))
|
||||
st_show_gf = bool(p.get("showGf",
|
||||
old_meta.get("stempel_show_gf", True)))
|
||||
st_show_agf = bool(p.get("showAgf",
|
||||
old_meta.get("stempel_show_agf", True)))
|
||||
st_show_count = bool(p.get("showCount",
|
||||
old_meta.get("stempel_show_count", False)))
|
||||
st_show_seps = bool(p.get("showSeparators",
|
||||
old_meta.get("stempel_show_seps", True)))
|
||||
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)
|
||||
stempel_italic=st_ital,
|
||||
stempel_header=st_header,
|
||||
stempel_show_scope=st_show_scope,
|
||||
stempel_show_hnf=st_show_hnf,
|
||||
stempel_show_nnf=st_show_nnf,
|
||||
stempel_show_nf=st_show_nf,
|
||||
stempel_show_vf=st_show_vf,
|
||||
stempel_show_ff=st_show_ff,
|
||||
stempel_show_ngf=st_show_ngf,
|
||||
stempel_show_gf=st_show_gf,
|
||||
stempel_show_agf=st_show_agf,
|
||||
stempel_show_count=st_show_count,
|
||||
stempel_show_seps=st_show_seps)
|
||||
axis_obj.Attributes = attrs
|
||||
axis_obj.CommitChanges()
|
||||
_save_last(stempel_txt_h=st_th)
|
||||
@@ -12759,6 +13079,46 @@ def regen_masstab_raeume(doc=None):
|
||||
return len(ids)
|
||||
|
||||
|
||||
def _sync_stempel_to_source(doc):
|
||||
"""Spiegelt User-Edits am Stempel-TextEntity (Font/Bold/Italic/TextHeight)
|
||||
auf die UserStrings. Damit ueberleben Oberleiste-/Properties-Aenderungen
|
||||
am Stempel den naechsten Regen — sonst wuerden die meta-Werte beim
|
||||
Re-Render wieder gewinnen. Stempel-Source IST die TextEntity selber."""
|
||||
if doc is None: return
|
||||
for obj in doc.Objects:
|
||||
try:
|
||||
m = _read_meta(obj)
|
||||
if not m or m.get("type") != "stempel": continue
|
||||
te = obj.Geometry
|
||||
if not isinstance(te, rg.TextEntity): continue
|
||||
try: new_h = float(te.TextHeight)
|
||||
except Exception: new_h = float(m.get("stempel_txt_h", 0.20))
|
||||
cur_font = None
|
||||
try: cur_font = te.Font
|
||||
except Exception: pass
|
||||
try: new_face = cur_font.QuartetName if cur_font else ""
|
||||
except Exception: new_face = ""
|
||||
try: new_bold = bool(cur_font.Bold) if cur_font else False
|
||||
except Exception: new_bold = False
|
||||
try: new_ital = bool(cur_font.Italic) if cur_font else False
|
||||
except Exception: new_ital = False
|
||||
old_h = float(m.get("stempel_txt_h", 0.20))
|
||||
old_face = m.get("stempel_font", "") or ""
|
||||
old_bold = bool(m.get("stempel_bold", False))
|
||||
old_ital = bool(m.get("stempel_italic", False))
|
||||
if (abs(new_h - old_h) < 1e-6 and new_face == old_face
|
||||
and new_bold == old_bold and new_ital == old_ital):
|
||||
continue # nichts geaendert
|
||||
attrs = obj.Attributes.Duplicate()
|
||||
attrs.SetUserString(_KEY_STEMPEL_TXT_H, "{:.4f}".format(new_h))
|
||||
attrs.SetUserString(_KEY_STEMPEL_FONT, new_face)
|
||||
attrs.SetUserString(_KEY_STEMPEL_BOLD, "1" if new_bold else "0")
|
||||
attrs.SetUserString(_KEY_STEMPEL_ITAL, "1" if new_ital else "0")
|
||||
doc.Objects.ModifyAttributes(obj.Id, attrs, True)
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] sync stempel:", ex)
|
||||
|
||||
|
||||
def _sync_raum_stamps_to_source(doc):
|
||||
"""Sync raum_stamp TextEntities → raum_outline Source-UserStrings.
|
||||
|
||||
@@ -12959,6 +13319,8 @@ def _on_command_end(sender, e):
|
||||
_was_sync = sc.sticky.get(_REGEN_BUSY, False)
|
||||
sc.sticky[_REGEN_BUSY] = True
|
||||
try: _sync_raum_stamps_to_source(doc)
|
||||
except Exception: pass
|
||||
try: _sync_stempel_to_source(doc)
|
||||
finally: sc.sticky[_REGEN_BUSY] = _was_sync
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] stamp-sync:", ex)
|
||||
|
||||
+125
-15
@@ -12,6 +12,7 @@ import {
|
||||
updateElement, deleteElement, openElementeUebersicht, openElementeProperties,
|
||||
saveOeffStyle, deleteOeffStyle,
|
||||
saveRaumStil, deleteRaumStil, applyRaumStil,
|
||||
saveStempelStil, deleteStempelStil, applyStempelStil,
|
||||
listLibrary,
|
||||
} from './lib/rhinoBridge'
|
||||
|
||||
@@ -512,7 +513,7 @@ function NeuesElementSection({ noGeschoss, activeName, elementsCount }) {
|
||||
|
||||
// PropertiesView: gemeinsame Komponente, rendert die passende Property-
|
||||
// Form je nach Element-Typ. Wiederverwendbar in Inline + Satellite-Window.
|
||||
export function PropertiesView({ selected, geschosse, materials, hatchPatterns, oeffStyles, fonts, raumStempelStile }) {
|
||||
export function PropertiesView({ selected, geschosse, materials, hatchPatterns, oeffStyles, fonts, raumStempelStile, stempelStile }) {
|
||||
if (!selected) return null
|
||||
const upd = (p) => updateElement(selected.id, p)
|
||||
const del = (label) => () => { if (window.confirm(`${label} löschen?`)) deleteElement(selected.id) }
|
||||
@@ -539,6 +540,7 @@ export function PropertiesView({ selected, geschosse, materials, hatchPatterns,
|
||||
onUpdate={upd} onDelete={del('Raum')} />
|
||||
if (selected.kind === 'stempel')
|
||||
return <StempelProperties stempel={selected} geschosse={geschosse}
|
||||
stempelStile={stempelStile || []}
|
||||
onUpdate={upd} onDelete={del('Stempel')} />
|
||||
|
||||
if (selected.kind === 'aussparung')
|
||||
@@ -594,7 +596,8 @@ export default function ElementeApp() {
|
||||
hatchPatterns={state.hatchPatterns}
|
||||
fonts={state.fonts || []}
|
||||
oeffStyles={state.oeffStyles || []}
|
||||
raumStempelStile={state.raumStempelStile || []} />
|
||||
raumStempelStile={state.raumStempelStile || []}
|
||||
stempelStile={state.stempelStile || []} />
|
||||
</div>
|
||||
)}
|
||||
<NeuesElementSection
|
||||
@@ -1168,21 +1171,76 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fo
|
||||
)
|
||||
}
|
||||
|
||||
function StempelProperties({ stempel, geschosse, onUpdate, onDelete }) {
|
||||
function StempelProperties({ stempel, geschosse, stempelStile, onUpdate, onDelete }) {
|
||||
const scope = stempel.scope || 'total'
|
||||
const bilanz = stempel.bilanz || {}
|
||||
const stilList = stempelStile || []
|
||||
const activeStilId = stempel.stilId || ''
|
||||
const [headerDraft, setHeaderDraft] = useState(stempel.header || 'Nutzflächen')
|
||||
useEffect(() => {
|
||||
setHeaderDraft(stempel.header || 'Nutzflächen')
|
||||
}, [stempel.id, stempel.header])
|
||||
|
||||
// Show-Flags vom Backend (default true ausser showCount)
|
||||
const _show = (k, def = true) =>
|
||||
(stempel[k] !== undefined ? !!stempel[k] : def)
|
||||
|
||||
const handleStilChange = (val) => {
|
||||
if (val === '__save__') {
|
||||
const n = (window.prompt('Name für neuen Stempel-Stil:', 'Stil') || '').trim()
|
||||
if (!n) return
|
||||
saveStempelStil('', n, {
|
||||
header: stempel.header || 'Nutzflächen',
|
||||
showScope: _show('showScope'),
|
||||
showHnf: _show('showHnf'),
|
||||
showNnf: _show('showNnf'),
|
||||
showNf: _show('showNf'),
|
||||
showVf: _show('showVf'),
|
||||
showFf: _show('showFf'),
|
||||
showNgf: _show('showNgf'),
|
||||
showGf: _show('showGf'),
|
||||
showAgf: _show('showAgf'),
|
||||
showCount: _show('showCount', false),
|
||||
showSeparators: _show('showSeparators'),
|
||||
font: stempel.font || '',
|
||||
bold: !!stempel.bold,
|
||||
italic: !!stempel.italic,
|
||||
txtH: stempel.txtH,
|
||||
}, [stempel.id])
|
||||
return
|
||||
}
|
||||
if (val === '__delete__') {
|
||||
if (activeStilId &&
|
||||
window.confirm(`Stil "${stilList.find(s => s.id === activeStilId)?.name}" löschen?`))
|
||||
deleteStempelStil(activeStilId)
|
||||
return
|
||||
}
|
||||
// Empty ids → Backend nutzt aktuell selektierte Stempel
|
||||
if (val) applyStempelStil(val, [])
|
||||
}
|
||||
|
||||
// Sichtbare Zeilen fuer die Live-Vorschau (nur die mit Werten + Visibility)
|
||||
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,
|
||||
{ key: 'hnf', label: 'HNF', val: bilanz.hnf, on: _show('showHnf') },
|
||||
{ key: 'nnf', label: 'NNF', val: bilanz.nnf, on: _show('showNnf') },
|
||||
{ key: 'nf', label: 'NF', val: bilanz.nf, on: _show('showNf'), 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,
|
||||
{ key: 'vf', label: 'VF', val: bilanz.vf, on: _show('showVf') },
|
||||
{ key: 'ff', label: 'FF', val: bilanz.ff, on: _show('showFf') },
|
||||
{ key: 'ngf', label: 'NGF', val: bilanz.ngf, on: _show('showNgf'),
|
||||
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)
|
||||
{ key: 'gf', label: 'GF', val: bilanz.gf, on: _show('showGf') },
|
||||
{ key: 'agf', label: 'AGF', val: bilanz.agf, on: _show('showAgf') },
|
||||
].filter(r => r.on && r.val && r.val > 0)
|
||||
|
||||
const ShowTog = ({ field, label, hint }) => (
|
||||
<BarToggle label={label}
|
||||
icon={_show(field, field === 'showCount' ? false : true) ? 'check_box' : 'check_box_outline_blank'}
|
||||
active={_show(field, field === 'showCount' ? false : true)}
|
||||
onClick={() => onUpdate({ [field]: !_show(field, field === 'showCount' ? false : true) })}
|
||||
title={hint || label} />
|
||||
)
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex', flexDirection: 'column', gap: 8,
|
||||
@@ -1202,6 +1260,23 @@ function StempelProperties({ stempel, geschosse, onUpdate, onDelete }) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Stil-Picker — analog Raumstempel-Stile */}
|
||||
<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 }}>Scope</span>
|
||||
<select value={scope}
|
||||
@@ -1217,6 +1292,42 @@ function StempelProperties({ stempel, geschosse, onUpdate, onDelete }) {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Header-Input — frei editierbar, Default "Nutzflächen" */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 60 }}>Header</span>
|
||||
<input type="text" value={headerDraft}
|
||||
onChange={(e) => setHeaderDraft(e.target.value)}
|
||||
onBlur={() => {
|
||||
const v = (headerDraft || '').trim()
|
||||
if (v !== (stempel.header || '')) onUpdate({ header: v })
|
||||
}}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
|
||||
placeholder="Nutzflächen"
|
||||
style={{ flex: 1, fontSize: 11 }} />
|
||||
</div>
|
||||
|
||||
{/* Show-Toggles — was im Stempel angezeigt wird */}
|
||||
<div style={{
|
||||
display: 'flex', flexDirection: 'column', gap: 4,
|
||||
paddingTop: 6, borderTop: '1px dashed var(--border)',
|
||||
}}>
|
||||
<span style={{ ...labelXs, marginBottom: 2 }}>Anzeigen im Stempel</span>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
|
||||
<ShowTog field="showScope" label="Scope" hint="Header-Suffix · Total/Geschoss" />
|
||||
<ShowTog field="showHnf" label="HNF" />
|
||||
<ShowTog field="showNnf" label="NNF" />
|
||||
<ShowTog field="showNf" label="NF" hint="Nutzfläche = HNF + NNF" />
|
||||
<ShowTog field="showVf" label="VF" />
|
||||
<ShowTog field="showFf" label="FF" />
|
||||
<ShowTog field="showNgf" label="NGF" hint="Nettogeschossfläche" />
|
||||
<ShowTog field="showGf" label="GF" />
|
||||
<ShowTog field="showAgf" label="AGF" />
|
||||
<ShowTog field="showCount" label="Anzahl" hint="Anzahl klassifizierter Räume" />
|
||||
<ShowTog field="showSeparators" label="Linien" hint="Trennlinien zwischen Sections" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Live-Vorschau der Bilanz (nur sichtbare Zeilen) */}
|
||||
<div style={{
|
||||
paddingTop: 6, borderTop: '1px dashed var(--border)',
|
||||
fontSize: 10, fontFamily: 'DM Mono, monospace',
|
||||
@@ -1225,13 +1336,12 @@ function StempelProperties({ stempel, geschosse, onUpdate, onDelete }) {
|
||||
<div style={{ fontSize: 9, color: 'var(--text-muted)',
|
||||
letterSpacing: '0.06em', textTransform: 'uppercase',
|
||||
marginBottom: 4 }}>
|
||||
Bilanz · {bilanz.count || 0} Räume klassifiziert
|
||||
Vorschau · {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.
|
||||
Nichts anzuzeigen — Räume mit SIA-Tag fehlen oder Felder deaktiviert.
|
||||
</div>
|
||||
) : rows.map(r => (
|
||||
<div key={r.key} title={r.hint || ''}
|
||||
|
||||
@@ -38,6 +38,7 @@ export default function ElementePropertiesApp() {
|
||||
fonts={state.fonts || []}
|
||||
oeffStyles={state.oeffStyles || []}
|
||||
raumStempelStile={state.raumStempelStile || []}
|
||||
stempelStile={state.stempelStile || []}
|
||||
/>
|
||||
) : (
|
||||
<div style={{
|
||||
|
||||
@@ -256,6 +256,15 @@ export function reorderRaumStile(ids) { send('REORDER_RAUM_STILE', { ids }) }
|
||||
export function duplicateRaumStil(id, newName) {
|
||||
send('DUPLICATE_RAUM_STIL', { id, newName })
|
||||
}
|
||||
// Stempel-Stile (analog Raumstempel-Stile, eigene Storage pro Doc)
|
||||
export function saveStempelStil(id, name, settings, applyToIds) {
|
||||
send('SAVE_STEMPEL_STIL', { id, name, settings,
|
||||
applyToIds: applyToIds || [] })
|
||||
}
|
||||
export function deleteStempelStil(id) { send('DELETE_STEMPEL_STIL', { id }) }
|
||||
export function applyStempelStil(stilId, ids) {
|
||||
send('APPLY_STEMPEL_STIL', { stilId, ids })
|
||||
}
|
||||
export function setSectionStyle(enabled, source, color, pattern, scale, rotation,
|
||||
opts = {}) {
|
||||
send('SET_SECTION_STYLE', {
|
||||
|
||||
Reference in New Issue
Block a user