diff --git a/rhino/elemente.py b/rhino/elemente.py index 6f63daf..63c2918 100644 --- a/rhino/elemente.py +++ b/rhino/elemente.py @@ -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") diff --git a/rhino/massstab.py b/rhino/massstab.py index 94f5134..9ce0b00 100644 --- a/rhino/massstab.py +++ b/rhino/massstab.py @@ -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 diff --git a/src/ElementeApp.jsx b/src/ElementeApp.jsx index 7584b8a..446e137 100644 --- a/src/ElementeApp.jsx +++ b/src/ElementeApp.jsx @@ -887,11 +887,20 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fo const [name, setName] = useState(raum.name || 'Raum') const [nummer, setNummer] = useState(raum.nummer || '') const [funktion, setFunktion] = useState(raum.funktion || '') + // Texthoehe lokal puffern — Modus bestimmt Einheit (m bei fix, mm bei masstab) + const txtModus = raum.txtModus || 'fix' + const txtHDisplay = (() => { + const v = parseFloat(raum.txtH) + if (Number.isNaN(v)) return '' + return String(v) + })() + const [txtH, setTxtH] = useState(txtHDisplay) useEffect(() => { setName(raum.name || 'Raum') setNummer(raum.nummer || '') setFunktion(raum.funktion || '') - }, [raum.id, raum.name, raum.nummer, raum.funktion]) + setTxtH(txtHDisplay) + }, [raum.id, raum.name, raum.nummer, raum.funktion, raum.txtH, raum.txtModus]) // Aktueller Wert von raum_fuellung: "" | "Solid" | "Hatch1" | … | "ByLayer" const fuell = raum.fuellung || '' @@ -1027,6 +1036,38 @@ function RaumProperties({ raum, geschosse, onUpdate, onDelete, hatchPatterns, fo + {/* Texthoehe + Modus: fix = m, masstab = mm-auf-Papier (skaliert mit + aktivem Plan-Massstab). Bei masstab wirkt jede Massstabs- + Aenderung der Oberleiste auch auf den Stempel. */} +