Tueren-Schwung: Live-Update + Hinge-Position auf Wand-Innenkante

- Schwung-Curves-Block VOR dem Volume-Replace-Pfad — sonst greifen
  openAngle/hingeSide/swingInvert-Aenderungen nicht (Volume-Anzahl
  bleibt gleich → continue → Swing-Skip).
- Hinge-Punkt sitzt nicht mehr auf der Wand-Achse sondern auf der
  Wand-Innenkante (half_d * inside-Richtung). Tuerblatt + Bogen
  beginnen damit an der Wandflucht statt in der Wandmitte.
- Rotation-Vorzeichen korrigiert (rad = open_angle * sweep_sign *
  aus_sign) damit Tuere wirklich ins Innere schwingt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 13:23:44 +02:00
parent 0c5f8055a5
commit 655d368c92
2 changed files with 275 additions and 5 deletions
+216 -5
View File
@@ -67,6 +67,13 @@ _KEY_AKTIVE_DARSTELLUNG = "dossier_aktive_darstellung" # doc-level global overr
_KEY_OEFF_STYLE_ID = "dossier_oeff_style_id" # per-Object: referenziert einen Style aus dossier_oeff_styles _KEY_OEFF_STYLE_ID = "dossier_oeff_style_id" # per-Object: referenziert einen Style aus dossier_oeff_styles
_KEY_OEFF_STYLES = "dossier_oeff_styles" # JSON-Liste aller Fenster/Tueren-Styles _KEY_OEFF_STYLES = "dossier_oeff_styles" # JSON-Liste aller Fenster/Tueren-Styles
_KEY_OEFF_STYLE_ACTIVE = "dossier_oeff_style_active" # zuletzt benutzte Style-ID (pro typ) _KEY_OEFF_STYLE_ACTIVE = "dossier_oeff_style_active" # zuletzt benutzte Style-ID (pro typ)
_KEY_OEFF_TUER_TYP = "dossier_oeff_tuer_typ" # "normal" | "wandoeffnung"
_KEY_OEFF_HINGE_SIDE = "dossier_oeff_hinge_side" # "links" | "rechts" — Bandseite (welche Tuerflueg-Seite)
_KEY_OEFF_OPEN_ANGLE = "dossier_oeff_open_angle" # float Grad 0180 (Plan-Oeffnungswinkel)
_KEY_OEFF_SWING_INVERT = "dossier_oeff_swing_invert" # "1"/"0" — flippt die Schwung-Richtung
_OEFF_TUER_TYPEN = ("normal", "wandoeffnung")
_OEFF_HINGE_SIDES = ("links", "rechts")
_OEFF_DARSTELLUNGEN = ("einfach", "standard", "detail") _OEFF_DARSTELLUNGEN = ("einfach", "standard", "detail")
@@ -101,6 +108,7 @@ _OEFF_STYLE_FIELDS = (
"rahmenB", "rahmenTiefe", "rahmenOffset", "rahmenB", "rahmenTiefe", "rahmenOffset",
"fluegel", "simsAus", "glas", "fluegel", "simsAus", "glas",
"darstellung", "tuerRahmen", "darstellung", "tuerRahmen",
"tuerTyp", "hingeSide", "openAngle",
) )
_OEFF_DEFAULT_STYLES = [ _OEFF_DEFAULT_STYLES = [
@@ -133,7 +141,14 @@ _OEFF_DEFAULT_STYLES = [
"breite": 0.90, "hoehe": 2.10, "brueest": 0.00, "breite": 0.90, "hoehe": 2.10, "brueest": 0.00,
"rahmenB": 0.06, "rahmenTiefe": 0.08, "rahmenOffset": 0.05, "rahmenB": 0.06, "rahmenTiefe": 0.08, "rahmenOffset": 0.05,
"fluegel": 1, "simsAus": "ohne", "glas": True, "fluegel": 1, "simsAus": "ohne", "glas": True,
"darstellung": "standard", "tuerRahmen": "zarge"}, "darstellung": "standard", "tuerRahmen": "zarge",
"tuerTyp": "normal", "hingeSide": "links", "openAngle": 90.0},
{"name": "Wandöffnung", "typ": "tuer",
"breite": 1.20, "hoehe": 2.10, "brueest": 0.00,
"rahmenB": 0.04, "rahmenTiefe": 0.04, "rahmenOffset": 0.05,
"fluegel": 1, "simsAus": "ohne", "glas": False,
"darstellung": "standard", "tuerRahmen": "zarge",
"tuerTyp": "wandoeffnung", "hingeSide": "links", "openAngle": 0.0},
] ]
@@ -599,6 +614,15 @@ def _layer_path_volume(doc, geschoss_name):
return _layer_path_axis(doc, geschoss_name) return _layer_path_axis(doc, geschoss_name)
def _layer_path_oeff_swing(doc, geschoss_name):
"""Türschwung-Linien — eigener Sublayer (Code 23) damit User die
Schwung-Bögen unabhängig von den Türen-Volumes ausblenden kann."""
sub = _find_ebene_sublayer_name(doc, ["schwung", "tuerschwung"],
"23", "Türschwung",
default_color="#888888", default_lw=0.13)
return "{}::{}".format(geschoss_name, sub)
def _layer_path_decke(doc, geschoss_name): def _layer_path_decke(doc, geschoss_name):
"""Decken-Outline + Volumen — Sublayer 'DECKEN' (Code 30).""" """Decken-Outline + Volumen — Sublayer 'DECKEN' (Code 30)."""
sub = _find_ebene_sublayer_name(doc, ["decke"], "30", "DECKEN", sub = _find_ebene_sublayer_name(doc, ["decke"], "30", "DECKEN",
@@ -1884,6 +1908,8 @@ def _attach_meta(obj_attrs, wall_id, type_, geschoss, dicke, uk_over, ok_over,
oeff_referenz=None, oeff_darstellung=None, oeff_referenz=None, oeff_darstellung=None,
oeff_aussenseite=None, oeff_tuer_rahmen=None, oeff_aussenseite=None, oeff_tuer_rahmen=None,
oeff_rahmen_offset=None, oeff_style_id=None, oeff_rahmen_offset=None, oeff_style_id=None,
oeff_tuer_typ=None, oeff_hinge_side=None, oeff_open_angle=None,
oeff_swing_invert=None,
geschoss_end=None, treppe_breite=None, geschoss_end=None, treppe_breite=None,
treppe_n=None, treppe_referenz=None, treppe_n=None, treppe_referenz=None,
treppe_modus=None, treppe_lauf_d=None, treppe_art=None, treppe_modus=None, treppe_lauf_d=None, treppe_art=None,
@@ -1967,6 +1993,19 @@ def _attach_meta(obj_attrs, wall_id, type_, geschoss, dicke, uk_over, ok_over,
if oeff_style_id is not None: if oeff_style_id is not None:
try: obj_attrs.SetUserString(_KEY_OEFF_STYLE_ID, str(oeff_style_id) or "") try: obj_attrs.SetUserString(_KEY_OEFF_STYLE_ID, str(oeff_style_id) or "")
except Exception: pass except Exception: pass
if oeff_tuer_typ is not None and oeff_tuer_typ in _OEFF_TUER_TYPEN:
obj_attrs.SetUserString(_KEY_OEFF_TUER_TYP, oeff_tuer_typ)
if oeff_hinge_side is not None and oeff_hinge_side in _OEFF_HINGE_SIDES:
obj_attrs.SetUserString(_KEY_OEFF_HINGE_SIDE, oeff_hinge_side)
if oeff_open_angle is not None:
try:
v = max(0.0, min(180.0, float(oeff_open_angle)))
obj_attrs.SetUserString(_KEY_OEFF_OPEN_ANGLE, "{:.2f}".format(v))
except Exception: pass
if oeff_swing_invert is not None:
try: obj_attrs.SetUserString(_KEY_OEFF_SWING_INVERT,
"1" if bool(oeff_swing_invert) else "0")
except Exception: pass
# --- Treppen-Felder --- # --- Treppen-Felder ---
if geschoss_end is not None: if geschoss_end is not None:
obj_attrs.SetUserString(_KEY_GESCHOSS_END, geschoss_end or "") obj_attrs.SetUserString(_KEY_GESCHOSS_END, geschoss_end or "")
@@ -2130,6 +2169,15 @@ def _read_meta(obj):
except Exception: oro = 0.05 except Exception: oro = 0.05
if oro < 0: oro = 0.0 if oro < 0: oro = 0.0
ostyle = a.GetUserString(_KEY_OEFF_STYLE_ID) or "" ostyle = a.GetUserString(_KEY_OEFF_STYLE_ID) or ""
ottyp = a.GetUserString(_KEY_OEFF_TUER_TYP) or "normal"
if ottyp not in _OEFF_TUER_TYPEN: ottyp = "normal"
ohinge = a.GetUserString(_KEY_OEFF_HINGE_SIDE) or "links"
if ohinge not in _OEFF_HINGE_SIDES: ohinge = "links"
try: oangle = float(a.GetUserString(_KEY_OEFF_OPEN_ANGLE) or "90")
except Exception: oangle = 90.0
if oangle < 0: oangle = 0.0
elif oangle > 180: oangle = 180.0
oswinv = (a.GetUserString(_KEY_OEFF_SWING_INVERT) == "1")
# Treppen-Felder # Treppen-Felder
gend = a.GetUserString(_KEY_GESCHOSS_END) or "" gend = a.GetUserString(_KEY_GESCHOSS_END) or ""
try: tb = float(a.GetUserString(_KEY_TREPPE_BREITE) or "1.0") try: tb = float(a.GetUserString(_KEY_TREPPE_BREITE) or "1.0")
@@ -2251,6 +2299,10 @@ def _read_meta(obj):
"oeff_tuer_rahmen": otrah, "oeff_tuer_rahmen": otrah,
"oeff_rahmen_offset": oro, "oeff_rahmen_offset": oro,
"oeff_style_id": ostyle, "oeff_style_id": ostyle,
"oeff_tuer_typ": ottyp,
"oeff_hinge_side": ohinge,
"oeff_open_angle": oangle,
"oeff_swing_invert": oswinv,
"geschoss_end": gend, "geschoss_end": gend,
"treppe_breite": tb, "treppe_breite": tb,
"treppe_n": tn, "treppe_n": tn,
@@ -2471,6 +2523,109 @@ def _resolve_rahmen_perp_range(half_d, rahmen_tiefe, rahmen_pos):
return lo, hi return lo, hi
def _make_tuer_swing_curves(axis_curve, point_on_axis, wall_dicke,
oeff_meta, base_z):
"""Generiert die 2D-Plan-Schwung-Linien einer Tuer:
- 1 Linie: Tuerblatt im geoeffneten Zustand (vom Scharnier um
open_angle gedreht)
- 1 Arc: Schwung-Bogen vom geschlossenen Endpunkt zum offenen
Endpunkt
Alle Kurven liegen auf Floor-Level (z = base_z).
Returns [Curve] oder leere Liste wenn Tuer-Typ='wandoeffnung'
oder Oeffnung keine Tuer ist."""
import math
if oeff_meta.get("oeff_typ") != "tuer": return []
if oeff_meta.get("oeff_tuer_typ", "normal") != "normal": return []
frame = _oeff_axis_frame(axis_curve, point_on_axis)
if frame is None: return []
pt, tan, perp = frame
breite = float(oeff_meta.get("oeff_breite", 0.9))
rahmen_b = float(oeff_meta.get("oeff_rahmen_b", 0.06))
darstellung = oeff_meta.get("oeff_darstellung", "standard")
aussenseite = oeff_meta.get("oeff_aussenseite", "rechts")
aus_sign = +1 if aussenseite == "rechts" else -1
hinge_side = oeff_meta.get("oeff_hinge_side", "links")
try: open_angle = float(oeff_meta.get("oeff_open_angle", 90))
except Exception: open_angle = 90.0
if open_angle <= 0: return [] # tuer geschlossen — keine Schwung-Curves
swing_invert = bool(oeff_meta.get("oeff_swing_invert", False))
half_b = breite * 0.5
# Lichte-Breite je nach Darstellung: einfach = volle Breite,
# standard/detail = Innenmass abzueglich Rahmen
if darstellung == "einfach":
leaf_t_lo, leaf_t_hi = -half_b, +half_b
else:
leaf_t_lo, leaf_t_hi = -half_b + rahmen_b, +half_b - rahmen_b
leaf_len = leaf_t_hi - leaf_t_lo
if leaf_len <= 1e-6: return []
# Scharnier-Tangentenkoordinate (along axis)
if hinge_side == "links":
hinge_t = leaf_t_lo
# Schwung-Richtung: vom Scharnier nach aussen → Aussenseite-Vorzeichen
# Linke Bandseite: Tuer schwingt entlang +tan-Richtung im geschlossenen
# Zustand, perpendikulaer ins Innere (= -aus_sign perp) im offenen.
sweep_sign = -1 # gegen Uhrzeigersinn um Scharnier wenn aus +Z gesehen
else:
hinge_t = leaf_t_hi
sweep_sign = +1
# Hinge-Punkt: sitzt auf der WAND-INNENKANTE (nicht auf der Achse).
# Innen-Richtung: aussenseite='rechts' (aus_sign=+1) bedeutet aussen
# liegt in box-perp-Richtung (tan.Y, -tan.X). Innen ist dann das
# Gegenstueck (-tan.Y, +tan.X). aus_sign=-1 flippt das.
half_d = float(wall_dicke) * 0.5
inside_x = aus_sign * (-tan.Y)
inside_y = aus_sign * tan.X
z0 = float(base_z)
hinge_pt = rg.Point3d(
pt.X + hinge_t * tan.X + half_d * inside_x,
pt.Y + hinge_t * tan.Y + half_d * inside_y,
z0)
# Geschlossener Endpunkt (Tuer-Blatt in Wand-Flucht, parallel zur Achse):
closed_pt = rg.Point3d(
pt.X + (hinge_t + sweep_sign * leaf_len) * tan.X + half_d * inside_x,
pt.Y + (hinge_t + sweep_sign * leaf_len) * tan.Y + half_d * inside_y,
z0)
# Offener Endpunkt: hinge + leaf_len * direction(sweep_angle)
# Direction startet bei +sweep_sign * tan (geschlossen) und rotiert
# um Z-Achse um sweep_sign * (-aus_sign) * open_angle Grad — Tuer
# schwingt in den Innenraum (-aus_sign * perp).
rad = math.radians(open_angle) * sweep_sign * aus_sign
if swing_invert: rad = -rad
cos_r, sin_r = math.cos(rad), math.sin(rad)
base_dx = sweep_sign * tan.X
base_dy = sweep_sign * tan.Y
open_dx = base_dx * cos_r - base_dy * sin_r
open_dy = base_dx * sin_r + base_dy * cos_r
open_pt = rg.Point3d(hinge_pt.X + open_dx * leaf_len,
hinge_pt.Y + open_dy * leaf_len, z0)
curves = []
# 1) Tuerblatt-Linie (Hinge → Open)
try:
leaf_line = rg.LineCurve(hinge_pt, open_pt)
curves.append(leaf_line)
except Exception as ex:
print("[ELEMENTE] swing leaf:", ex)
# 2) Schwung-Arc — Mittelpunkt = Scharnier, Radius = Tuerblatt-Laenge.
# Plane mit X→closed_pt + Y→open_pt; Arc(plane, radius, angle) startet
# auf der X-Achse und sweept CCW um angle Radian. Damit liegt der
# Bogen genau auf einem Kreis um das Scharnier.
try:
dir_closed = closed_pt - hinge_pt
dir_open = open_pt - hinge_pt
arc_plane = rg.Plane(hinge_pt, dir_closed, dir_open)
arc = rg.Arc(arc_plane, leaf_len, math.radians(open_angle))
if arc.IsValid:
curves.append(rg.ArcCurve(arc))
except Exception as ex:
print("[ELEMENTE] swing arc:", ex)
return curves
def _make_oeffnung_pieces(axis_curve, point_on_axis, wall_dicke, oeff_meta, base_z): def _make_oeffnung_pieces(axis_curve, point_on_axis, wall_dicke, oeff_meta, base_z):
"""Baut die einzelnen Brep-Pieces der Oeffnung — Rahmen (single Brep """Baut die einzelnen Brep-Pieces der Oeffnung — Rahmen (single Brep
via Boolean-Differenz), Mittelpfosten (pro Fluegel), Glas, Sims aussen, via Boolean-Differenz), Mittelpfosten (pro Fluegel), Glas, Sims aussen,
@@ -2680,7 +2835,7 @@ SOURCE_TYPES = ("wand_axis", "decke_outline", "dach_outline",
"stuetze_point", "traeger_axis", "stuetze_point", "traeger_axis",
"raum_outline", "decke_aussparung_outline") "raum_outline", "decke_aussparung_outline")
VOLUME_TYPES = ("wand_volume", "decke_volume", "dach_volume", VOLUME_TYPES = ("wand_volume", "decke_volume", "dach_volume",
"oeffnung_volume", "treppe_volume", "oeffnung_volume", "oeffnung_swing", "treppe_volume",
"stuetze_volume", "traeger_volume", "stuetze_volume", "traeger_volume",
"raum_stamp", "raum_fill") "raum_stamp", "raum_fill")
# Oeffnungs-Cutout: Boolean-Difference aus Wand. Zusaetzlich kriegt die # Oeffnungs-Cutout: Boolean-Difference aus Wand. Zusaetzlich kriegt die
@@ -4458,6 +4613,30 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
# Aenderung (z.B. Fluegel-Wechsel) Fallback auf Delete+Add. # Aenderung (z.B. Fluegel-Wechsel) Fallback auf Delete+Add.
op_layer = _ensure_layer(doc, _layer_path_volume(doc, geschoss_name)) op_layer = _ensure_layer(doc, _layer_path_volume(doc, geschoss_name))
for op_meta, pt_loc, op_uk in opening_jobs: for op_meta, pt_loc, op_uk in opening_jobs:
# Schwung-Curves IMMER zuerst regenerieren — unabhaengig davon
# ob die Volume-Pieces sich geaendert haben. Andernfalls greifen
# Aenderungen an openAngle/hingeSide/swingInvert nicht weil der
# Replace-Pfad weiter unten via `continue` rausspringt.
if op_meta.get("oeff_typ") == "tuer":
old_sw = list(_find_objects_by_wall_id(doc, op_meta["id"],
"oeffnung_swing"))
for o, _m in old_sw:
try: doc.Objects.Delete(o.Id, True)
except Exception: pass
swings = _make_tuer_swing_curves(geom, pt_loc, meta["dicke"],
op_meta, op_uk)
if swings:
sw_layer_idx = _ensure_layer(doc,
_layer_path_oeff_swing(doc, geschoss_name))
for crv in swings:
sw_attrs = Rhino.DocObjects.ObjectAttributes()
sw_attrs.LayerIndex = sw_layer_idx
_attach_meta(sw_attrs, op_meta["id"], "oeffnung_swing",
op_meta["geschoss"], meta["dicke"], "", "",
oeff_typ="tuer",
oeff_parent=op_meta.get("oeff_parent"))
doc.Objects.AddCurve(crv, sw_attrs)
old_objs = list(_find_objects_by_wall_id(doc, op_meta["id"], old_objs = list(_find_objects_by_wall_id(doc, op_meta["id"],
"oeffnung_volume")) "oeffnung_volume"))
pieces = _make_oeffnung_pieces(geom, pt_loc, meta["dicke"], pieces = _make_oeffnung_pieces(geom, pt_loc, meta["dicke"],
@@ -4494,7 +4673,11 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
oeff_aussenseite=op_meta.get("oeff_aussenseite"), oeff_aussenseite=op_meta.get("oeff_aussenseite"),
oeff_tuer_rahmen=op_meta.get("oeff_tuer_rahmen"), oeff_tuer_rahmen=op_meta.get("oeff_tuer_rahmen"),
oeff_rahmen_offset=op_meta.get("oeff_rahmen_offset"), oeff_rahmen_offset=op_meta.get("oeff_rahmen_offset"),
oeff_style_id=op_meta.get("oeff_style_id")) oeff_style_id=op_meta.get("oeff_style_id"),
oeff_tuer_typ=op_meta.get("oeff_tuer_typ"),
oeff_hinge_side=op_meta.get("oeff_hinge_side"),
oeff_open_angle=op_meta.get("oeff_open_angle"),
oeff_swing_invert=op_meta.get("oeff_swing_invert"))
doc.Objects.AddBrep(pbrep, op_attrs) doc.Objects.AddBrep(pbrep, op_attrs)
# Source-Layer migrieren + Volumen-Layer-Index ermitteln # Source-Layer migrieren + Volumen-Layer-Index ermitteln
@@ -5107,6 +5290,10 @@ class ElementeBridge(panel_base.BaseBridge):
"tuerRahmen": meta.get("oeff_tuer_rahmen", "zarge"), "tuerRahmen": meta.get("oeff_tuer_rahmen", "zarge"),
"rahmenOffset": meta.get("oeff_rahmen_offset", 0.05), "rahmenOffset": meta.get("oeff_rahmen_offset", 0.05),
"styleId": meta.get("oeff_style_id", ""), "styleId": meta.get("oeff_style_id", ""),
"tuerTyp": meta.get("oeff_tuer_typ", "normal"),
"hingeSide": meta.get("oeff_hinge_side", "links"),
"openAngle": meta.get("oeff_open_angle", 90.0),
"swingInvert": bool(meta.get("oeff_swing_invert", False)),
}) })
elif meta["type"] == "treppe_axis": elif meta["type"] == "treppe_axis":
gs = _geschoss_by_id(doc, meta["geschoss"]) gs = _geschoss_by_id(doc, meta["geschoss"])
@@ -6174,7 +6361,10 @@ class ElementeBridge(panel_base.BaseBridge):
oeff_aussenseite=detected_aussen, oeff_aussenseite=detected_aussen,
oeff_darstellung=darst_def, oeff_darstellung=darst_def,
oeff_tuer_rahmen=tuer_rahmen_def, oeff_tuer_rahmen=tuer_rahmen_def,
oeff_style_id=pending_sid) oeff_style_id=pending_sid,
oeff_tuer_typ="normal",
oeff_hinge_side="links",
oeff_open_angle=90.0)
# Oeffnungs-Punkt auf UK+Brueestung-Hoehe platzieren (= visuell auf # Oeffnungs-Punkt auf UK+Brueestung-Hoehe platzieren (= visuell auf
# Unterkante Oeffnung). Constraint vergleicht spaeter pt.Z mit # Unterkante Oeffnung). Constraint vergleicht spaeter pt.Z mit
# UK+brueest — wenn der Punkt am axis.Z=0 saesse, wuerde der erste # UK+brueest — wenn der Punkt am axis.Z=0 saesse, wuerde der erste
@@ -8614,6 +8804,17 @@ class ElementeBridge(panel_base.BaseBridge):
# Stil das eben gemachte Field-Edit wegmaecken). # Stil das eben gemachte Field-Edit wegmaecken).
o_style_id = p.get("styleId", o_style_id = p.get("styleId",
old_meta.get("oeff_style_id", "")) or "" old_meta.get("oeff_style_id", "")) or ""
# Tueren-Schwung-Felder
o_tuer_typ = p.get("tuerTyp", old_meta.get("oeff_tuer_typ", "normal"))
if o_tuer_typ not in _OEFF_TUER_TYPEN: o_tuer_typ = "normal"
o_hinge = p.get("hingeSide", old_meta.get("oeff_hinge_side", "links"))
if o_hinge not in _OEFF_HINGE_SIDES: o_hinge = "links"
try: o_angle = float(p.get("openAngle",
old_meta.get("oeff_open_angle", 90)))
except Exception: o_angle = 90.0
o_angle = max(0.0, min(180.0, o_angle))
o_swinv = bool(p.get("swingInvert",
old_meta.get("oeff_swing_invert", False)))
if p.get("styleId") and p.get("styleId") != old_meta.get("oeff_style_id"): if p.get("styleId") and p.get("styleId") != old_meta.get("oeff_style_id"):
# Style wurde NEU gewaehlt → seine Werte applizieren # Style wurde NEU gewaehlt → seine Werte applizieren
styles = list_oeff_styles(doc) styles = list_oeff_styles(doc)
@@ -8632,6 +8833,12 @@ class ElementeBridge(panel_base.BaseBridge):
if "darstellung" in stl: odarst = stl["darstellung"] if "darstellung" in stl: odarst = stl["darstellung"]
if otyp == "tuer" and "tuerRahmen" in stl: if otyp == "tuer" and "tuerRahmen" in stl:
otrah = stl["tuerRahmen"] otrah = stl["tuerRahmen"]
if otyp == "tuer" and "tuerTyp" in stl:
o_tuer_typ = stl["tuerTyp"]
if otyp == "tuer" and "hingeSide" in stl:
o_hinge = stl["hingeSide"]
if otyp == "tuer" and "openAngle" in stl:
o_angle = float(stl["openAngle"])
set_active_oeff_style_id(doc, otyp, p["styleId"]) set_active_oeff_style_id(doc, otyp, p["styleId"])
attrs = axis_obj.Attributes attrs = axis_obj.Attributes
_attach_meta(attrs, wall_id, "oeffnung_point", _attach_meta(attrs, wall_id, "oeffnung_point",
@@ -8652,7 +8859,11 @@ class ElementeBridge(panel_base.BaseBridge):
oeff_aussenseite=oauss, oeff_aussenseite=oauss,
oeff_tuer_rahmen=otrah, oeff_tuer_rahmen=otrah,
oeff_rahmen_offset=oro, oeff_rahmen_offset=oro,
oeff_style_id=o_style_id) oeff_style_id=o_style_id,
oeff_tuer_typ=o_tuer_typ,
oeff_hinge_side=o_hinge,
oeff_open_angle=o_angle,
oeff_swing_invert=o_swinv)
axis_obj.Attributes = attrs axis_obj.Attributes = attrs
axis_obj.CommitChanges() axis_obj.CommitChanges()
parent_id = old_meta.get("oeff_parent", "") parent_id = old_meta.get("oeff_parent", "")
+59
View File
@@ -1746,6 +1746,8 @@ function OeffnungProperties({ oeff, onUpdate, onDelete, oeffStyles = [] }) {
fluegel: oeff.fluegel, simsAus: oeff.simsAus, fluegel: oeff.fluegel, simsAus: oeff.simsAus,
glas: oeff.glas, darstellung: oeff.darstellung, glas: oeff.glas, darstellung: oeff.darstellung,
tuerRahmen: oeff.tuerRahmen, tuerRahmen: oeff.tuerRahmen,
tuerTyp: oeff.tuerTyp, hingeSide: oeff.hingeSide,
openAngle: oeff.openAngle,
}) })
return return
} }
@@ -1799,6 +1801,63 @@ function OeffnungProperties({ oeff, onUpdate, onDelete, oeffStyles = [] }) {
</div> </div>
{!isFenster && ( {!isFenster && (
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
title="Tueren-Typ. Wandoeffnung = nur Cutout ohne Schwung-Blatt">
Typ
</span>
<div style={{ flex: 1, display: 'flex', gap: 3 }}>
<BarToggle label="Normal"
active={(oeff.tuerTyp || 'normal') === 'normal'}
onClick={() => onUpdate({ tuerTyp: 'normal' })}
title="Tuere mit Tuerblatt + Schwung-Bogen" />
<BarToggle label="Wandöffnung"
active={(oeff.tuerTyp || 'normal') === 'wandoeffnung'}
onClick={() => onUpdate({ tuerTyp: 'wandoeffnung' })}
title="Nur Wand-Cutout ohne Schwung" />
</div>
</div>
)}
{!isFenster && (oeff.tuerTyp || 'normal') === 'normal' && (
<>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
title="Bandseite — welche Tuerflueg-Seite. Im Plan = Scharnier-Position">
Band
</span>
<div style={{ flex: 1, display: 'flex', gap: 3 }}>
<BarToggle label="Links"
active={(oeff.hingeSide || 'links') === 'links'}
onClick={() => onUpdate({ hingeSide: 'links' })} />
<BarToggle label="Rechts"
active={(oeff.hingeSide || 'links') === 'rechts'}
onClick={() => onUpdate({ hingeSide: 'rechts' })} />
</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
title="Oeffnungswinkel im Plan (Grad, 0-180)">
Öffn.
</span>
<input type="text"
value={String(oeff.openAngle ?? 90)}
onChange={(e) => {
const v = parseFloat(e.target.value.replace(',', '.'))
if (!Number.isNaN(v) && v >= 0 && v <= 180) onUpdate({ openAngle: v })
}}
onKeyDown={(e) => { if (e.key === 'Enter') e.target.blur() }}
style={{ flex: 1, fontSize: 11, fontFamily: 'DM Mono, monospace' }} />
<span style={{ fontSize: 10, color: 'var(--text-muted)' }}>°</span>
<BarToggle label="Umkehren"
onClick={() => onUpdate({ swingInvert: !oeff.swingInvert })}
title="Schwung-Richtung umkehren (nach aussen statt innen)" />
</div>
</>
)}
{!isFenster && (oeff.tuerTyp || 'normal') === 'normal' && (
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }} <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
title="Tueren-Rahmen-Typ. Zarge sitzt in der Oeffnung, Blockrahmen sitzt aussen herum"> title="Tueren-Rahmen-Typ. Zarge sitzt in der Oeffnung, Blockrahmen sitzt aussen herum">