Raumstempel: masstab-Modus — Texthoehe als Paper-mm @ Plan-Massstab

Neuer UserString dossier_raum_txt_modus = "fix" | "masstab" (default fix).
- fix:     raum_txt_h ist Meter (Modellhoehe, bisheriges Verhalten)
- masstab: raum_txt_h ist Paper-mm. Render-Hoehe (m) =
           paper_mm * applied_scale / 1000 — wird zur Render-Zeit aus
           massstab.get_applied_scale_ratio() gelesen, Fallback 1:100.

Massstab-Sync:
- massstab._apply_scale ruft nach Skala-Wechsel elemente.regen_masstab_raeume(doc)
  → alle Raeume im masstab-Modus regennen automatisch, Texthoehe folgt der
  neuen Skala (z.B. Switch 1:100 → 1:50 halbiert die Modellhoehe).

_sync_raum_stamps_to_source masstab-aware: im masstab-Modus wird die
TextHeight am Stempel NICHT zurueck auf raum_txt_h gespiegelt (sie ist
abgeleitet, nicht die Wahrheit) — sonst waere der naechste Regen sofort
falsch positioniert. Offset + Font/Style werden weiterhin gespiegelt.

UI: Modus-Toggle "fix m" / "masstab mm" + Hoehen-Input + Einheits-Suffix
in RaumProperties zwischen Ausrichtung und Funktion.
This commit is contained in:
2026-05-26 20:49:26 +02:00
parent 01b6501a0c
commit 238d7d062b
3 changed files with 140 additions and 9 deletions
+88 -8
View File
@@ -314,6 +314,10 @@ _KEY_RAUM_SHOW_SIA = "dossier_raum_show_sia"
# naechsten Outline-Modify nicht wieder auf den Centroid zurueck-snapped.
_KEY_RAUM_STAMP_DX = "dossier_raum_stamp_dx"
_KEY_RAUM_STAMP_DY = "dossier_raum_stamp_dy"
# Texthoehen-Modus:
# "fix" → raum_txt_h ist Meter (Modellhoehe), aktuelles Default-Verhalten
# "masstab" → raum_txt_h ist Paper-mm (gerendert × applied_scale / 1000)
_KEY_RAUM_TXT_MODUS = "dossier_raum_txt_modus"
# Field-Layout als JSON-Array von Rows. Jede Row ist eine Liste von
# Field-IDs. Z.B. [["nummer","name","area"],["funktion"]] → eine Zeile mit
# Nummer+Name+Flaeche, dann Funktion auf eigener Zeile. Leer = legacy
@@ -2597,7 +2601,7 @@ def _attach_meta(obj_attrs, wall_id, type_, geschoss, dicke, uk_over, ok_over,
raum_show_funktion=None, raum_show_area=None,
raum_show_sia=None,
raum_stamp_dx=None, raum_stamp_dy=None,
raum_layout=None,
raum_layout=None, raum_txt_modus=None,
wand_layered=None, wand_layers=None, wand_layer_idx=None,
wand_chain_members=None,
aussp_parent=None):
@@ -2800,6 +2804,8 @@ def _attach_meta(obj_attrs, wall_id, type_, geschoss, dicke, uk_over, ok_over,
_json.dumps(raum_layout))
except Exception as ex:
print("[ELEMENTE] raum_layout set:", ex)
if raum_txt_modus is not None and raum_txt_modus in ("fix", "masstab"):
obj_attrs.SetUserString(_KEY_RAUM_TXT_MODUS, raum_txt_modus)
# Wand-Schichten
if wand_layered is not None:
obj_attrs.SetUserString(_KEY_WAND_LAYERED,
@@ -2995,6 +3001,9 @@ def _read_meta(obj):
except Exception: r_stamp_dx = 0.0
try: r_stamp_dy = float(a.GetUserString(_KEY_RAUM_STAMP_DY) or "0")
except Exception: r_stamp_dy = 0.0
# Texthoehen-Modus (default "fix" — Backwards-Compat)
r_txt_modus = a.GetUserString(_KEY_RAUM_TXT_MODUS) or "fix"
if r_txt_modus not in ("fix", "masstab"): r_txt_modus = "fix"
# Field-Layout — parsed JSON list of rows
r_layout_raw = a.GetUserString(_KEY_RAUM_LAYOUT) or ""
r_layout = []
@@ -3115,6 +3124,7 @@ def _read_meta(obj):
"raum_stamp_dx": r_stamp_dx,
"raum_stamp_dy": r_stamp_dy,
"raum_layout": r_layout,
"raum_txt_modus": r_txt_modus,
"wand_layered": w_layered,
"wand_layers": w_layers,
"wand_layer_idx": w_layer_idx,
@@ -4712,6 +4722,27 @@ def _format_area(area, rundung):
return "{:.2f}".format(a) # exakt
def _resolve_raum_text_height_m(value, modus):
"""Konvertiert raum_txt_h (User-Wert) in Modell-Meter, je nach Modus:
modus=fix value ist bereits Meter
modus=masstab value ist Paper-mm, Modell-Meter = value*scale/1000
(scale aus aktivem Viewport via massstab.get_applied_scale_ratio)
Fallback bei fehlendem Massstab: 100 (1:100).
"""
try: v = float(value)
except Exception: v = 0.20
if (modus or "fix") != "masstab":
return v
try:
import massstab as _ms
sc_ratio = _ms.get_applied_scale_ratio() or _ms.get_current_scale_ratio()
except Exception:
sc_ratio = None
if not sc_ratio or sc_ratio <= 0:
sc_ratio = 100.0
return float(v) * float(sc_ratio) / 1000.0
def _make_raum_stamp_text(centroid, name, nummer, funktion, area, rundung,
text_height, z=0.0, align="mid",
font=None, bold=False, italic=False,
@@ -6163,6 +6194,10 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
sx = float(meta.get("raum_stamp_dx", 0.0))
sy = float(meta.get("raum_stamp_dy", 0.0))
stamp_pt = rg.Point3d(ctr.X + sx, ctr.Y + sy, ctr.Z)
# Texthoehe: bei modus=masstab aus Paper-mm + aktuellem Scale aufloesen
eff_h = _resolve_raum_text_height_m(
meta.get("raum_txt_h", 0.20),
meta.get("raum_txt_modus", "fix"))
te = _make_raum_stamp_text(
stamp_pt,
meta.get("raum_name", "Raum"),
@@ -6170,7 +6205,7 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
meta.get("raum_funktion", ""),
area,
_resolve_raum_rundung(meta, doc),
meta.get("raum_txt_h", 0.20),
eff_h,
z=z_uk,
align=meta.get("raum_align", "mid"),
font=meta.get("raum_txt_font", ""),
@@ -6205,7 +6240,8 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
raum_show_name=meta.get("raum_show_name"),
raum_show_funktion=meta.get("raum_show_funktion"),
raum_show_area=meta.get("raum_show_area"),
raum_show_sia=meta.get("raum_show_sia"))
raum_show_sia=meta.get("raum_show_sia"),
raum_txt_modus=meta.get("raum_txt_modus"))
try: doc.Objects.AddText(te, attrs)
except Exception as ex: print("[ELEMENTE] Raum AddText:", ex)
return True
@@ -6542,6 +6578,7 @@ class ElementeBridge(panel_base.BaseBridge):
"showArea": bool(meta.get("raum_show_area", True)),
"showSia": bool(meta.get("raum_show_sia", False)),
"layout": meta.get("raum_layout") or [],
"txtModus": meta.get("raum_txt_modus", "fix"),
"area": area,
"areaFmt": _format_area(area, rnd_eff),
"umfang": perim,
@@ -10038,6 +10075,8 @@ class ElementeBridge(panel_base.BaseBridge):
# nicht im Patch → vom alten meta uebernehmen.
r_layout = p.get("layout", old_meta.get("raum_layout", []))
if not isinstance(r_layout, list): r_layout = []
r_modus = p.get("txtModus", old_meta.get("raum_txt_modus", "fix"))
if r_modus not in ("fix", "masstab"): r_modus = "fix"
gstart = p.get("geschoss", old_meta["geschoss"])
attrs = axis_obj.Attributes
if gstart != old_meta["geschoss"]:
@@ -10062,7 +10101,8 @@ class ElementeBridge(panel_base.BaseBridge):
raum_show_funktion=r_show_fkt,
raum_show_area=r_show_are,
raum_show_sia=r_show_sia,
raum_layout=r_layout)
raum_layout=r_layout,
raum_txt_modus=r_modus)
axis_obj.Attributes = attrs
axis_obj.CommitChanges()
_save_last(raum_name_last=r_name, raum_rundung=r_rnd,
@@ -10076,7 +10116,8 @@ class ElementeBridge(panel_base.BaseBridge):
raum_show_funktion=r_show_fkt,
raum_show_area=r_show_are,
raum_show_sia=r_show_sia,
raum_layout=r_layout)
raum_layout=r_layout,
raum_txt_modus=r_modus)
_regenerate_volume(doc, wall_id)
doc.Views.Redraw()
self._send_state()
@@ -12035,6 +12076,37 @@ def _on_command_begin(sender, e):
sc.sticky["_dossier_undo_serial"] = None
def regen_masstab_raeume(doc=None):
"""Regennt alle Raeume im masstab-Modus. Wird vom Massstab-Modul
aufgerufen wenn der Plan-Massstab des aktiven Viewports geaendert wird
die Stempel-Texthoehe haengt am applied_scale und muss neu gerendert
werden. Idempotent: macht nichts wenn keine masstab-Raeume da sind."""
if doc is None: doc = Rhino.RhinoDoc.ActiveDoc
if doc is None: return 0
if sc.sticky.get(_REGEN_BUSY): return 0
ids = []
for obj in doc.Objects:
try:
m = _read_meta(obj)
if not m or m.get("type") != "raum_outline": continue
if m.get("raum_txt_modus") == "masstab":
ids.append(m["id"])
except Exception: pass
if not ids: return 0
_was_busy = sc.sticky.get(_REGEN_BUSY, False)
sc.sticky[_REGEN_BUSY] = True
try:
for rid in ids:
try: _regenerate_element(doc, rid)
except Exception as ex:
print("[ELEMENTE] regen_masstab_raeume", rid, ":", ex)
finally:
sc.sticky[_REGEN_BUSY] = _was_busy
try: doc.Views.Redraw()
except Exception: pass
return len(ids)
def _sync_raum_stamps_to_source(doc):
"""Sync raum_stamp TextEntities → raum_outline Source-UserStrings.
@@ -12108,10 +12180,17 @@ def _sync_raum_stamps_to_source(doc):
old_face = meta.get("raum_txt_font", "") or ""
old_bold = bool(meta.get("raum_txt_bold", False))
old_ital = bool(meta.get("raum_txt_italic", False))
# Im masstab-Modus ist die TextHeight am Stempel abgeleitet
# (paper_mm * scale / 1000) — NICHT zurueck-spiegeln, sonst
# waere der naechste Regen sofort verschoben. Wenn der User
# die Hoehe via Oberleiste aendert: er muss erst zurueck in
# den fix-Modus, oder die Paper-mm in der UI editieren.
modus = meta.get("raum_txt_modus", "fix")
sync_height = (modus != "masstab")
changed = (
abs(new_dx - old_dx) > 1e-6 or
abs(new_dy - old_dy) > 1e-6 or
abs(new_h - old_h) > 1e-6 or
(sync_height and abs(new_h - old_h) > 1e-6) or
new_face != old_face or
new_bold != old_bold or
new_ital != old_ital
@@ -12122,8 +12201,9 @@ def _sync_raum_stamps_to_source(doc):
"{:.6f}".format(new_dx))
attrs.SetUserString(_KEY_RAUM_STAMP_DY,
"{:.6f}".format(new_dy))
attrs.SetUserString(_KEY_RAUM_TXT_H,
"{:.4f}".format(new_h))
if sync_height:
attrs.SetUserString(_KEY_RAUM_TXT_H,
"{:.4f}".format(new_h))
attrs.SetUserString(_KEY_RAUM_TXT_FONT, new_face)
attrs.SetUserString(_KEY_RAUM_TXT_BOLD,
"1" if new_bold else "0")
+10
View File
@@ -890,6 +890,16 @@ def _apply_scale(doc, vp, ratio):
apply_scaled_hatches(doc, float(ratio))
except Exception as ex:
print("[MASSSTAB] Hatch-Rescale:", ex)
# Raumstempel im masstab-Modus skalieren mit dem Scale-Wechsel —
# ihre TextHeight ist (paper_mm * scale / 1000) und muss neu
# gerendert werden. Lazy-Import um circular dep zu vermeiden.
try:
import elemente as _el
n_regen = _el.regen_masstab_raeume(doc)
if n_regen > 0:
print("[MASSSTAB] {} masstab-Raum/Raeume regenned".format(n_regen))
except Exception as ex:
print("[MASSSTAB] Raumstempel-Regen:", ex)
# Neuen Wert persistieren — sowohl per-Viewport (fuer das Dropdown,
# damit jeder Viewport seinen eigenen Massstab behaelt) als auch als
# globaler "letzter Wert" (Legacy-Key; wird von Plotweight/Hatch-Rescale