Per-Wand Joint-Rolle: explizit waehlen wer am T-Stoss durchgeht

Neues UserString-Feld 'dossier_wand_joint_rolle' per wand_axis:
- 'auto' (default): bisherige Logik. Bei beidseitig auto entscheidet die
  Style-Prio (hoehere = durchgehend)
- 'durchgehend': diese Wand ueberschreibt → ich gehe durch, T-Miter wird
  NICHT auf mich angewendet
- 'anstossend': diese Wand stoppt immer am Joint, auch wenn ich Prio-haerter waere

T-Junction-Detection in regen ruft jetzt _wand_should_apply_t_miter:
- my.rolle entscheidet zuerst
- bei my=auto: through.rolle entscheidet
- bei beide=auto: Prio-Vergleich (hoehere Prio = durchgehend)
- Default: T-Miter applied (= ich stoppe am Through)

Frontend: neue Dropdown 'Joint' in WallProperties zwischen Stil und Aufbau:
- Auto (Prio entscheidet)
- Durchgehend
- Anstossend

Backend-Pipeline: _attach_meta + _read_meta um wand_joint_rolle erweitert,
state-JSON sendet 'jointRolle', _update_wall_body handhabt jointRolle-Patch.
This commit is contained in:
2026-05-31 08:04:17 +02:00
parent 0171785b42
commit bc87ae1acc
2 changed files with 66 additions and 5 deletions
+51 -5
View File
@@ -41,6 +41,7 @@ _KEY_WAND_LAYERS = "dossier_wand_layers" # JSON-Liste [{name, dicke, color}]
_KEY_WAND_LAYER_IDX = "dossier_wand_layer_idx" # Layer-Index am Volume-Brep _KEY_WAND_LAYER_IDX = "dossier_wand_layer_idx" # Layer-Index am Volume-Brep
_KEY_WAND_CHAIN_MEMBERS = "dossier_wand_chain_members" # JSON-Liste wand_ids einer Polyline-Chain (nur auf wand_volume) _KEY_WAND_CHAIN_MEMBERS = "dossier_wand_chain_members" # JSON-Liste wand_ids einer Polyline-Chain (nur auf wand_volume)
_KEY_WAND_STYLE_ID = "dossier_wand_style_id" # Verweis auf Project-Settings wand_styles[].id _KEY_WAND_STYLE_ID = "dossier_wand_style_id" # Verweis auf Project-Settings wand_styles[].id
_KEY_WAND_JOINT_ROLLE = "dossier_wand_joint_rolle" # "auto"|"durchgehend"|"anstossend" — per-Wand Override am T-Joint
_KEY_DACH_NEIGUNG = "dossier_dach_neigung" # Grad als string ("30") _KEY_DACH_NEIGUNG = "dossier_dach_neigung" # Grad als string ("30")
_KEY_DACH_EAVE = "dossier_dach_eave" # Index der Traufkante (string) _KEY_DACH_EAVE = "dossier_dach_eave" # Index der Traufkante (string)
_KEY_DACH_TYP = "dossier_dach_typ" # "pult"|"sattel"|"walm"|"mansarde" _KEY_DACH_TYP = "dossier_dach_typ" # "pult"|"sattel"|"walm"|"mansarde"
@@ -3571,6 +3572,34 @@ def _wand_meta_prio(doc, meta):
except Exception: return 500 except Exception: return 500
def _wand_should_apply_t_miter(doc, my_meta, through_wid):
"""Entscheidet ob ich (T-Stem-Kandidat) am T-Junction zur Through-Wand
angeschmiegt werden soll (= T-mitered). Per-Wand Joint-Rolle hat Vorrang
vor Prio:
- my.rolle='durchgehend': skip miter (ich bin Through, gehe drueber)
- my.rolle='anstossend': apply miter (ich stoppe)
- my.rolle='auto' + through.rolle='anstossend': skip (through stoppt)
- my.rolle='auto' + through.rolle='durchgehend': apply
- beide 'auto': Prio entscheidet (hoehere Prio = Through)"""
if not my_meta: return True
my_rolle = (my_meta.get("wand_joint_rolle") or "auto").lower()
if my_rolle == "durchgehend": return False
if my_rolle == "anstossend": return True
# auto: schau auf through-wand
th_meta = _wand_meta_by_id(doc, through_wid) if through_wid else None
if th_meta:
th_rolle = (th_meta.get("wand_joint_rolle") or "auto").lower()
if th_rolle == "durchgehend": return True # through ist explizit through
if th_rolle == "anstossend": return False # through stoppt → ich Through
# beide auto → Prio
try:
if int(_wand_meta_prio(doc, my_meta)) > int(
_wand_meta_prio(doc, th_meta)):
return False # ich hoehere Prio = Through
except Exception: pass
return True # Default: T-mitere mich (T-Stem stoppt am Through)
def _wand_meta_by_id(doc, wall_id): def _wand_meta_by_id(doc, wall_id):
"""Kuerzel: liefert das meta-Dict fuer eine wall_id ueber _find_axis.""" """Kuerzel: liefert das meta-Dict fuer eine wall_id ueber _find_axis."""
obj = _find_axis(doc, wall_id) obj = _find_axis(doc, wall_id)
@@ -3884,6 +3913,7 @@ def _attach_meta(obj_attrs, wall_id, type_, geschoss, dicke, uk_over, ok_over,
wand_layered=None, wand_layers=None, wand_layer_idx=None, wand_layered=None, wand_layers=None, wand_layer_idx=None,
wand_chain_members=None, wand_chain_members=None,
wand_style_id=None, wand_style_id=None,
wand_joint_rolle=None,
aussp_parent=None): aussp_parent=None):
"""User-Strings auf die Object-Attributes setzen.""" """User-Strings auf die Object-Attributes setzen."""
obj_attrs.SetUserString(_KEY_ID, wall_id) obj_attrs.SetUserString(_KEY_ID, wall_id)
@@ -4186,6 +4216,12 @@ def _attach_meta(obj_attrs, wall_id, type_, geschoss, dicke, uk_over, ok_over,
obj_attrs.SetUserString(_KEY_WAND_STYLE_ID, obj_attrs.SetUserString(_KEY_WAND_STYLE_ID,
str(wand_style_id or "")) str(wand_style_id or ""))
except Exception: pass except Exception: pass
if wand_joint_rolle is not None:
try:
rv = str(wand_joint_rolle or "auto").lower()
if rv not in ("auto", "durchgehend", "anstossend"): rv = "auto"
obj_attrs.SetUserString(_KEY_WAND_JOINT_ROLLE, rv)
except Exception: pass
if wand_chain_members is not None: if wand_chain_members is not None:
try: try:
import json as _json import json as _json
@@ -4580,6 +4616,7 @@ def _read_meta(obj):
"wand_layer_idx": w_layer_idx, "wand_layer_idx": w_layer_idx,
"wand_chain_members": w_chain_members, "wand_chain_members": w_chain_members,
"wand_style_id": a.GetUserString(_KEY_WAND_STYLE_ID) or "", "wand_style_id": a.GetUserString(_KEY_WAND_STYLE_ID) or "",
"wand_joint_rolle": (a.GetUserString(_KEY_WAND_JOINT_ROLLE) or "auto").lower(),
"aussp_parent": aussp_parent_raw, "aussp_parent": aussp_parent_raw,
} }
except Exception: except Exception:
@@ -8363,8 +8400,9 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
exclude_ids=chain_set) exclude_ids=chain_set)
if tj is not None: if tj is not None:
_oid, b_tan, b_dicke = tj _oid, b_tan, b_dicke = tj
tm = _t_junction_miter(p_s, out_s, b_tan, b_dicke) if _wand_should_apply_t_miter(doc, meta, _oid):
if tm is not None: miter_start = tm tm = _t_junction_miter(p_s, out_s, b_tan, b_dicke)
if tm is not None: miter_start = tm
else: else:
# 3+ Joint: ich bin T-Stem wenn meine Tangente NICHT # 3+ Joint: ich bin T-Stem wenn meine Tangente NICHT
# collinear mit zwei collinearen Partner-Tangenten ist. # collinear mit zwei collinearen Partner-Tangenten ist.
@@ -8393,8 +8431,9 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
exclude_ids=chain_set) exclude_ids=chain_set)
if tj is not None: if tj is not None:
_oid, b_tan, b_dicke = tj _oid, b_tan, b_dicke = tj
tm = _t_junction_miter(p_e, out_e, b_tan, b_dicke) if _wand_should_apply_t_miter(doc, meta, _oid):
if tm is not None: miter_end = tm tm = _t_junction_miter(p_e, out_e, b_tan, b_dicke)
if tm is not None: miter_end = tm
else: else:
# 3+ Joint: T-Stem-Erkennung (analog start) # 3+ Joint: T-Stem-Erkennung (analog start)
_my_t = geom.TangentAtEnd _my_t = geom.TangentAtEnd
@@ -9498,6 +9537,7 @@ class ElementeBridge(panel_base.BaseBridge):
"layered": bool(meta.get("wand_layered", False)), "layered": bool(meta.get("wand_layered", False)),
"layers": meta.get("wand_layers", []), "layers": meta.get("wand_layers", []),
"styleId": meta.get("wand_style_id", "") or "", "styleId": meta.get("wand_style_id", "") or "",
"jointRolle": meta.get("wand_joint_rolle", "auto") or "auto",
}) })
elif meta["type"] == "decke_outline": elif meta["type"] == "decke_outline":
uk, ok = _resolve_decke_z(doc, meta["geschoss"], meta["dicke"], uk, ok = _resolve_decke_z(doc, meta["geschoss"], meta["dicke"],
@@ -14543,6 +14583,11 @@ class ElementeBridge(panel_base.BaseBridge):
try: try:
old_chain_members = _find_wall_chain(doc, wall_id) old_chain_members = _find_wall_chain(doc, wall_id)
except Exception: pass except Exception: pass
# Joint-Rolle: explizit per-Wand "auto"/"durchgehend"/"anstossend"
if old_meta["type"] == "wand_axis" and "jointRolle" in p:
new_joint_rolle = p.get("jointRolle") or "auto"
else:
new_joint_rolle = old_meta.get("wand_joint_rolle", "auto")
_attach_meta(attrs, wall_id, old_meta["type"], geschoss, dicke, _attach_meta(attrs, wall_id, old_meta["type"], geschoss, dicke,
uk_over, ok_over, referenz, uk_over, ok_over, referenz,
neigung=neigung, eave_idx=eave_idx, dach_typ=dach_typ, neigung=neigung, eave_idx=eave_idx, dach_typ=dach_typ,
@@ -14550,7 +14595,8 @@ class ElementeBridge(panel_base.BaseBridge):
dach_variante=dach_variante, dach_variante=dach_variante,
wand_layered=wand_layered, wand_layered=wand_layered,
wand_layers=wand_layers if wand_layered else [], wand_layers=wand_layers if wand_layered else [],
wand_style_id=new_style_id if old_meta["type"] == "wand_axis" else None) wand_style_id=new_style_id if old_meta["type"] == "wand_axis" else None,
wand_joint_rolle=new_joint_rolle if old_meta["type"] == "wand_axis" else None)
axis_obj.Attributes = attrs axis_obj.Attributes = attrs
axis_obj.CommitChanges() axis_obj.CommitChanges()
# Volumen regenerieren (Layer ggf. anpassen) # Volumen regenerieren (Layer ggf. anpassen)
+15
View File
@@ -1528,6 +1528,21 @@ function WallProperties({ wall, geschosse, materials, wandStyles, onUpdate, onDe
</div> </div>
)} )}
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}
title="Wer verbindet sich an T-Stössen? Auto = Prio entscheidet. Durchgehend = diese Wand läuft durch. Anstossend = diese Wand stösst an die andere.">
Joint
</span>
<select value={wall.jointRolle || 'auto'}
onChange={(e) => onUpdate({ jointRolle: e.target.value })}
style={{ flex: 1, fontSize: 11 }}
title="Joint-Verhalten am T-Stoss. Auto folgt der Style-Prio, Durchgehend / Anstossend überschreiben.">
<option value="auto">Auto (Prio entscheidet)</option>
<option value="durchgehend">Durchgehend</option>
<option value="anstossend">Anstossend</option>
</select>
</div>
<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 }}>Aufbau</span> <span style={{ fontSize: 10, color: 'var(--text-secondary)', width: 50 }}>Aufbau</span>
<div style={{ flex: 1, display: 'flex', gap: 3 }}> <div style={{ flex: 1, display: 'flex', gap: 3 }}>