From dd5ccec88189483e02ed4f4d2220b4165be68585 Mon Sep 17 00:00:00 2001 From: karim Date: Tue, 26 May 2026 21:59:33 +0200 Subject: [PATCH] =?UTF-8?q?Unit-aware:=20m=20=E2=86=92=20Doc-Units=20im=20?= =?UTF-8?q?Regen-Pfad?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DOSSIER-UI nimmt immer Meter entgegen — bisher wurden die Werte 1:1 als Doc-Units verwendet, was bei Doc=Millimeter winzige Geometrie ergab (0.25m getippt → 0.25mm Wand). Storage-Konvention bleibt METER (UI-friendly). Konvertierung passiert beim Geometrie-Bau: - Neue Helpers _m2u / _u2m via Rhino.RhinoMath.UnitScale - _regenerate_element_body normalisiert ALLE m-typischen Meta-Felder am Eingang via _m2u — Geometrie-Code darunter bleibt unveraendert und arbeitet in Doc-Units (funktioniert in jedem Unit-System) - Lokaler _gs() Wrapper konvertiert Geschoss-OKFF + Hoehe → Doc-Units - _resolve_uk_ok / _resolve_decke_z / _resolve_dach_base konvertieren Geschoss-Heights aus JSON (m) → Doc-Units - _resolve_raum_text_height_m masstab-Pfad: m-Result → Doc-Units - _sync_raum_stamps_to_source: Stempel-TextHeight + Position-Delta sind in Doc-Units, werden vor Storage via _u2m → m konvertiert Effekt: - Doc in Metern: kein sichtbarer Unterschied (UnitScale=1, no-op) - Doc in Millimeter: 0.25 (m) wird zu 250 (mm) Wand → richtig dick - State-Emit zum Frontend bleibt in m → UI konsistent --- rhino/elemente.py | 163 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 135 insertions(+), 28 deletions(-) diff --git a/rhino/elemente.py b/rhino/elemente.py index c57cb7d..eda1f92 100644 --- a/rhino/elemente.py +++ b/rhino/elemente.py @@ -535,6 +535,42 @@ _OEFF_PIECE_DEFS = { # Doc-Wechsel, aber NICHT Rhino-Restart — was passt: "ich hab gerade 0.30 # fuer eine Wand benutzt, neue Wand soll auch 0.30 sein". +def _m2u(value, doc=None): + """Konvertiert einen Wert in Metern in Doc-Units. Nutzt RhinoMath.UnitScale. + Bei doc=meter ist scale=1 → no-op. Bei doc=mm ist scale=1000. + DOSSIER-Konvention: UI typt in Metern, Geometrie wird in Doc-Units gebaut. + """ + try: + v = float(value) + except Exception: + return value + if doc is None: doc = Rhino.RhinoDoc.ActiveDoc + if doc is None: return v + try: + scale = Rhino.RhinoMath.UnitScale( + Rhino.UnitSystem.Meters, doc.ModelUnitSystem) + return v * float(scale) + except Exception: + return v + + +def _u2m(value, doc=None): + """Inverse von _m2u: Doc-Units → Meter. Fuer State-Emit zum Frontend.""" + try: + v = float(value) + except Exception: + return value + if doc is None: doc = Rhino.RhinoDoc.ActiveDoc + if doc is None: return v + try: + scale = Rhino.RhinoMath.UnitScale( + Rhino.UnitSystem.Meters, doc.ModelUnitSystem) + if scale == 0: return v + return v / float(scale) + except Exception: + return v + + def _last(key, default): # `_reset_panels.py` cleart sticky via `= None` (statt del), daher kann # sc.sticky.get() den default ueberlesen und ein None zurueckgeben. @@ -594,14 +630,17 @@ def _active_geschoss_name(doc): def _resolve_uk_ok(doc, gid, uk_over, ok_over): - """Wand: UK = OKFF, OK = OKFF + Hoehe (Standard fuer Geschoss-volle Wand).""" + """Wand: UK = OKFF, OK = OKFF + Hoehe (Standard fuer Geschoss-volle Wand). + Geschoss-Heights JSON ist in METERN — hier auf Doc-Units konvertieren, + damit Geometrie-Code direkt arbeiten kann. uk_over/ok_over kommen + bereits in Doc-Units von _regenerate_element_body's Meta-Normalisierung.""" g = _geschoss_by_id(doc, gid) if g is None: uk = float(uk_over) if uk_over not in (None, "") else 0.0 - ok = float(ok_over) if ok_over not in (None, "") else 3.0 + ok = float(ok_over) if ok_over not in (None, "") else _m2u(3.0, doc) return uk, ok - okff = float(g.get("okff", 0.0)) - hoehe = float(g.get("hoehe", 3.0)) + okff = _m2u(g.get("okff", 0.0), doc) + hoehe = _m2u(g.get("hoehe", 3.0), doc) auto_uk = okff auto_ok = okff + hoehe uk = float(uk_over) if uk_over not in (None, "") else auto_uk @@ -617,9 +656,11 @@ def _resolve_decke_z(doc, gid, dicke, uk_over, ok_over): Override-Logik: - Nur OK_override gesetzt → OK = override, UK = OK - dicke - Nur UK_override gesetzt → UK = override, OK = UK + dicke - - Beide gesetzt → beide literal""" + - Beide gesetzt → beide literal + + Alle Werte hier in Doc-Units (Geschoss-JSON wird via _m2u konvertiert).""" g = _geschoss_by_id(doc, gid) - okff = float(g.get("okff", 0.0)) if g else 0.0 + okff = _m2u(g.get("okff", 0.0), doc) if g else 0.0 auto_ok = okff has_ok = ok_over not in (None, "") has_uk = uk_over not in (None, "") @@ -3773,12 +3814,13 @@ def _find_target_volume(doc, element_id): def _resolve_dach_base(doc, gid, uk_over): """Basis-Hoehe des Dachs an der Traufe (= Eave) = OKFF + Hoehe des - Geschosses (Oberkante der Waende). uk_override kann das ueberschreiben.""" + Geschosses (Oberkante der Waende). uk_override kann das ueberschreiben. + Geschoss-Heights JSON ist in METERN → hier auf Doc-Units konvertieren.""" g = _geschoss_by_id(doc, gid) if g is None: - return float(uk_over) if uk_over not in (None, "") else 3.0 - okff = float(g.get("okff", 0.0)) - hoehe = float(g.get("hoehe", 3.0)) + return float(uk_over) if uk_over not in (None, "") else _m2u(3.0, doc) + okff = _m2u(g.get("okff", 0.0), doc) + hoehe = _m2u(g.get("hoehe", 3.0), doc) auto = okff + hoehe return float(uk_over) if uk_over not in (None, "") else auto @@ -4735,16 +4777,23 @@ 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) +def _resolve_raum_text_height_m(value, modus, doc=None): + """Konvertiert raum_txt_h (User-Wert) in MODELL-DOC-UNITS, je nach Modus: + modus=fix → value ist Meter, Modell-Doc-Units = m * UnitScale + (Doc=Meter → no-op; Doc=mm → ×1000) + modus=masstab → value ist Paper-mm, Modell-Doc-Units = + (value * scale / 1000) * UnitScale + (scale aus aktivem Viewport via massstab) Fallback bei fehlendem Massstab: 100 (1:100). + Hinweis: im Aufruf-Pfad in _regenerate_element_body wurde raum_txt_h + bereits durch die Meta-Normalisierung in Doc-Units konvertiert FUER + fix-Modus. Hier behandeln wir den masstab-Pfad sauber und lassen den + fix-Pfad als no-op. """ try: v = float(value) except Exception: v = 0.20 if (modus or "fix") != "masstab": + # value ist schon in Doc-Units (vor-konvertiert in _regenerate_element_body) return v try: import massstab as _ms @@ -4753,7 +4802,9 @@ def _resolve_raum_text_height_m(value, modus): sc_ratio = None if not sc_ratio or sc_ratio <= 0: sc_ratio = 100.0 - return float(v) * float(sc_ratio) / 1000.0 + # masstab: Paper-mm → Modell-Meter → Modell-Doc-Units + m_value = float(v) * float(sc_ratio) / 1000.0 + return _m2u(m_value, doc) def _make_raum_stamp_text(centroid, name, nummer, funktion, area, rundung, @@ -5516,7 +5567,60 @@ def _regenerate_element(doc, element_id): def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name): """Eigentliche Implementierung des Regen — der aeussere Wrapper - `_regenerate_element` setzt _REGEN_BUSY und dispatcht oeffnung_point.""" + `_regenerate_element` setzt _REGEN_BUSY und dispatcht oeffnung_point. + + UNIT-NORMALISIERUNG: DOSSIER-Storage ist immer in METERN (UI-Konvention). + Hier am Eingang konvertieren wir ALLE m-Werte → Doc-Units. So bleibt der + Geometrie-Code unten unveraendert und funktioniert in jedem Doc-Unit- + System (Meter, Millimeter, …). + """ + # Meta-Werte normalisieren: Liste m-typischer Felder → _m2u + _M_FIELDS = ("dicke", "uk_override", "ok_override", + "oeff_breite", "oeff_hoehe", "oeff_brueest", + "oeff_rahmen_b", "oeff_rahmen_tiefe", "oeff_rahmen_offset", + "treppe_breite", "treppe_lauf_d", "treppe_h_over", + "trag_b", "trag_h", "trag_d", "trag_t", "trag_z_over", + "raum_stamp_dx", "raum_stamp_dy") + for _k in _M_FIELDS: + if _k in meta: + _v = meta[_k] + if _v == "" or _v is None: continue + try: meta[_k] = _m2u(_v, doc) + except Exception: pass + # raum_txt_h: nur im fix-Modus in m (sonst paper-mm) → entsprechend + # konvertieren. Im masstab-Modus uebernimmt _resolve_raum_text_height_m + # die Berechnung (selber doc-unit-aware). + if meta.get("type") == "raum_outline" and meta.get("raum_txt_modus") != "masstab": + try: meta["raum_txt_h"] = _m2u(meta.get("raum_txt_h", 0.20), doc) + except Exception: pass + # Wand-Schichten: jede Schicht hat eine eigene dicke + if isinstance(meta.get("wand_layers"), list): + for _ly in meta["wand_layers"]: + if isinstance(_ly, dict) and "dicke" in _ly: + try: _ly["dicke"] = _m2u(_ly["dicke"], doc) + except Exception: pass + # Treppe Soll-Werte: dict mit s/a/sa Range-Listen + if isinstance(meta.get("treppe_soll"), dict): + for _k in ("s", "a", "sa"): + _arr = meta["treppe_soll"].get(_k) + if isinstance(_arr, list) and len(_arr) >= 2: + try: + _arr[0] = _m2u(_arr[0], doc) + _arr[1] = _m2u(_arr[1], doc) + except Exception: pass + + # Geschoss-Lookup-Wrapper: konvertiert hoehe + okff → Doc-Units. + # ALLE Geometrie-Aufrufe unten benutzen _gs() statt _geschoss_by_id(). + def _gs(gid): + g = _geschoss_by_id(doc, gid) + if g is None: return None + g = dict(g) + try: g["hoehe"] = _m2u(g.get("hoehe", 3.0), doc) + except Exception: pass + try: g["okff"] = _m2u(g.get("okff", 0.0), doc) + except Exception: pass + return g + if meta["type"] == "wand_axis": # Chain-Detection: wenn diese Wand mit Nachbarn (gleiche Geometrie/ # Material/Hoehe, 2-Wall-Joints) zu einem Polyline-Chain gehoert, @@ -5971,8 +6075,8 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name elif meta["type"] == "treppe_axis": # Start- und Zielgeschoss → uk/ok aus OKFF-Differenz. # H-Override hat Vorrang vor Zielgeschoss. - g_start = _geschoss_by_id(doc, meta["geschoss"]) - g_end = _geschoss_by_id(doc, meta.get("geschoss_end", "")) + g_start = _gs(meta["geschoss"]) + g_end = _gs(meta.get("geschoss_end", "")) if g_start is None: return False uk = float(g_start.get("okff", 0.0)) @@ -6015,7 +6119,7 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name pt = geom.Location else: pt = geom # Point3d - g_start = _geschoss_by_id(doc, meta["geschoss"]) + g_start = _gs(meta["geschoss"]) uk = float(g_start.get("okff", 0.0)) if g_start else 0.0 z_over = meta.get("trag_z_over", "") if z_over: @@ -6037,7 +6141,7 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name src_layer = layer elif meta["type"] == "traeger_axis": # Achse + z_top aus Geschoss + Override - g_start = _geschoss_by_id(doc, meta["geschoss"]) + g_start = _gs(meta["geschoss"]) uk = float(g_start.get("okff", 0.0)) if g_start else 0.0 h = float(g_start.get("hoehe", 3.0)) if g_start else 3.0 z_over = meta.get("trag_z_over", "") @@ -6075,8 +6179,8 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name doc.Objects.ModifyAttributes(src_obj, new_attrs, True) except Exception: pass area, perim, ctr = _raum_amp(geom) - # Z-Lage: auf Geschoss-OKFF - g_start = _geschoss_by_id(doc, meta["geschoss"]) + # Z-Lage: auf Geschoss-OKFF (Doc-Units via _gs) + g_start = _gs(meta["geschoss"]) z_uk = float(g_start.get("okff", 0.0)) if g_start else 0.0 # Alte Stempel + Fills loeschen @@ -6210,7 +6314,8 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name # 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")) + meta.get("raum_txt_modus", "fix"), + doc=doc) te = _make_raum_stamp_text( stamp_pt, meta.get("raum_name", "Raum"), @@ -12192,10 +12297,12 @@ def _sync_raum_stamps_to_source(doc): if not isinstance(src_geom, rg.Curve): continue _, _, ctr = _raum_amp(src_geom) if ctr is None: continue - new_dx = pos.X - ctr.X - new_dy = pos.Y - ctr.Y - # Font/Style/Size aus der TextEntity lesen - try: new_h = float(te.TextHeight) + # Stempel-Position ist in Doc-Units → fuer Storage (Meter) konvertieren + new_dx = _u2m(pos.X - ctr.X, doc) + new_dy = _u2m(pos.Y - ctr.Y, doc) + # Font/Style/Size aus der TextEntity lesen. + # TextHeight ist in Doc-Units → fuer Storage (Meter) konvertieren + try: new_h = _u2m(float(te.TextHeight), doc) except Exception: new_h = float(meta.get("raum_txt_h", 0.20)) cur_font = None try: cur_font = te.Font