T-Junction Phase 2 + Layer-Smart-Join: Backbone-Drill bis Stahl-Layer-Far-Face
Phase-2-Fixes: - Backbone-Ext berechnet bis MATCHING-Layer (Stahl) far-face, nicht bis Body-far-face. Beton drillt durch Stahl-Band, nicht durch Daemm/Putz. - Case A/B detection via dot(out_dir, RhinoPerp). Rhino Curve.Offset benutzt (tan × +z) = (b_tan.Y, -b_tan.X), nicht (-b_tan.Y, b_tan.X). - Backbone-axis-ext separat von _my_axis_ext: nur Backbone-Column extended, non-backbone columns stoppen am Snap. - Through-only Mats (z.B. Daemm wo T-stem keinen hat) werden auch durchlaufen damit hoehere-prio backbone diese carven kann. - Post-Carve Union: gleiche-Material-Pieces mergen (Backbone-Beton-Col + Through-Stahl-Band → T-Shape). - BBox-overlap strict-Filter vor BoolDiff: touching coplanar faces ueberspringen, vermeidet Rhino BoolDiff "punch-through" Artefakte. - _has_my_cols guard: KeyError beim consume von T-stem-Layern fuer through-only mats verhindert. Layer-Smart-Join (smart_join.py): - Neue _layer_join_attempt: 2 selektierte wand_volume Breps gleicher Material via BoolUnion mergen. Manueller Override fuer Edge-Cases wo auto Phase 2 nicht reicht. Cluster-Volume Select-Handler (elemente.py): - Alt-Click bypassed Cluster-Swap → User kann einzelne Layer-Breps direkt anwaehlen (= fuer Layer-Smart-Join).
This commit is contained in:
@@ -296,12 +296,80 @@ def _l_join_attempt(doc, sel):
|
||||
doc.EndUndoRecord(ur)
|
||||
|
||||
|
||||
def _layer_join_attempt(doc, wand_volumes):
|
||||
"""Layer-Level Join: Versuch BoolUnion zwischen 2 wand_volume Breps.
|
||||
Wenn beide gleiches Material haben + sich beruehren oder ueberlappen
|
||||
→ mergen zu einem Brep. Behaelt UserStrings (= wand_id, material,
|
||||
layer_idx) vom ersten Brep."""
|
||||
if len(wand_volumes) != 2: return False
|
||||
br0 = wand_volumes[0].Geometry
|
||||
br1 = wand_volumes[1].Geometry
|
||||
if not isinstance(br0, rg.Brep) or not isinstance(br1, rg.Brep):
|
||||
return False
|
||||
# Material-Check via UserStrings (= dossier_element_material)
|
||||
mat0 = wand_volumes[0].Attributes.GetUserString(
|
||||
"dossier_layer_material") or ""
|
||||
mat1 = wand_volumes[1].Attributes.GetUserString(
|
||||
"dossier_layer_material") or ""
|
||||
if mat0 and mat1 and mat0 != mat1:
|
||||
print("[SMART-JOIN] Layer-Join: Materialien unterschiedlich "
|
||||
"({} vs {}), skip.".format(mat0, mat1))
|
||||
return False
|
||||
try:
|
||||
union = rg.Brep.CreateBooleanUnion([br0, br1], 0.01)
|
||||
except Exception as ex:
|
||||
print("[SMART-JOIN] Layer-Join BoolUnion exc:", ex)
|
||||
return False
|
||||
if not union or len(union) == 0:
|
||||
print("[SMART-JOIN] Layer-Join: Breps overlappen nicht "
|
||||
"(BoolUnion returned None/empty)")
|
||||
return False
|
||||
if len(union) > 1:
|
||||
print("[SMART-JOIN] Layer-Join: Breps overlappen nicht (BoolUnion"
|
||||
" returned {} pieces, brauche 1)".format(len(union)))
|
||||
return False
|
||||
# 1 merged Brep
|
||||
merged = union[0]
|
||||
if not merged.IsValid:
|
||||
print("[SMART-JOIN] Layer-Join: merged Brep invalid")
|
||||
return False
|
||||
try: merged.MergeCoplanarFaces(0.01)
|
||||
except Exception: pass
|
||||
ur = doc.BeginUndoRecord("DOSSIER Layer-Join")
|
||||
try:
|
||||
ok = doc.Objects.Replace(wand_volumes[0].Id, merged)
|
||||
if ok:
|
||||
doc.Objects.Delete(wand_volumes[1].Id, True)
|
||||
return True
|
||||
return False
|
||||
finally:
|
||||
doc.EndUndoRecord(ur)
|
||||
|
||||
|
||||
def _run():
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
if doc is None: return
|
||||
sel = list(doc.Objects.GetSelectedObjects(False, False))
|
||||
if not sel:
|
||||
Rhino.RhinoApp.RunScript("_Join", False); return
|
||||
# Layer-Level Join: wenn GENAU 2 wand_volume Breps selektiert + keine
|
||||
# wand_axis → versuche Layer-Join (= BoolUnion der zwei Layer-Breps).
|
||||
_wand_vols = [o for o in sel
|
||||
if (o.Attributes.GetUserString("dossier_element_type")
|
||||
or "") == "wand_volume"]
|
||||
_wand_axes = [o for o in sel
|
||||
if (o.Attributes.GetUserString("dossier_element_type")
|
||||
or "") == "wand_axis"]
|
||||
if (len(_wand_vols) == 2 and len(_wand_axes) == 0
|
||||
and len(sel) == 2):
|
||||
print("[SMART-JOIN] Layer-Join attempt: 2 wand_volume Breps")
|
||||
try:
|
||||
if _layer_join_attempt(doc, _wand_vols):
|
||||
doc.Views.Redraw()
|
||||
print("[SMART-JOIN] Layer-Join: Breps zu einem gemerged")
|
||||
return
|
||||
except Exception as ex:
|
||||
print("[SMART-JOIN] Layer-Join exc:", ex)
|
||||
# Info-Hint (T-Join unterstuetzt 1-Wand-Modus, L-Join braucht 2)
|
||||
n_wand_axes = sum(1 for o in sel
|
||||
if (o.Attributes.GetUserString("dossier_element_type")
|
||||
|
||||
+117
-45
@@ -2751,6 +2751,11 @@ class _ClusterVolumeSelectHandler(Rhino.UI.MouseCallback):
|
||||
try:
|
||||
if "Left" not in str(e.MouseButton): return
|
||||
except Exception: pass
|
||||
# Alt-Click bypassed Cluster-Swap → user kann Layer-Breps
|
||||
# direkt anwaehlen (= fuer Layer-Smart-Join)
|
||||
try:
|
||||
if bool(e.AltKeyDown): return
|
||||
except Exception: pass
|
||||
view = e.View
|
||||
if view is None: return
|
||||
vp = view.ActiveViewport
|
||||
@@ -3731,19 +3736,33 @@ def _t_junction_layer_overrides(doc, my_meta, through_meta, ep_pt, out_dir,
|
||||
break
|
||||
cur -= d
|
||||
|
||||
# Backbone-Extension: IMMER 0 = column endet exakt am Through-axis,
|
||||
# geht nie ueber outer face hinaus. Asymmetric Merge ist Resultat
|
||||
# der natuerlichen Geometrie:
|
||||
# - Wenn T-Stem-Body auf gleicher Seite wie Through-Wand-Body
|
||||
# (= column passes durch wall body): alle Layer mergen via 3D
|
||||
# Union + Carve (L-merge auf T-Stem-Layer-Seite, west_piece
|
||||
# getrennt).
|
||||
# - Wenn T-Stem-Body auf gegenueberliegender Seite (= column endet
|
||||
# am Through-Aussenrand, betritt wall body nicht): kein Merge,
|
||||
# visually 2 separate Walls die sich am axis touchieren.
|
||||
# Backbone-Extension: column drills durch through-body bis FAR FACE
|
||||
# auf out_dir-Seite (= durchbricht non-backbone Layer auf Far-Seite,
|
||||
# z.B. Daemm + Putz von Aussenwand wenn Beton-Innenwand anstoesst).
|
||||
# Resultat: "durchgehendes T" beim Backbone-Material.
|
||||
# Backbone column extends in out_dir-Richtung bis Far-Face der
|
||||
# MATCHING-Layer (= Stahl-Band) im through-wall. NICHT bis Body-Far-Face.
|
||||
# = Beton drillt nur durch Stahl-Schicht, nicht durch Daemm/Putz.
|
||||
# Case A (T-stem-axis covers body schon naturally) → ext=0.
|
||||
# Case B (OPPOSITE side) → ext = backbone-layer-thickness auf out_dir-Seite.
|
||||
backbone_ext = 0.0
|
||||
if backbone_ext is None:
|
||||
backbone_ext = float(b_dicke) * 0.5
|
||||
if backbone and backbone_dL is not None and backbone_dR is not None:
|
||||
# Rhino Curve.Offset perp-Konvention: positive offset = (tan × +z)
|
||||
perp_bx_e = rg.Vector3d(b_tan.Y, -b_tan.X, 0)
|
||||
try: perp_bx_e.Unitize()
|
||||
except Exception: pass
|
||||
dot_opx_e = (out_dir.X * perp_bx_e.X
|
||||
+ out_dir.Y * perp_bx_e.Y)
|
||||
if dot_opx_e > 0:
|
||||
backbone_ext = max(backbone_dL, backbone_dR, 0.0)
|
||||
else:
|
||||
backbone_ext = max(-backbone_dL, -backbone_dR, 0.0)
|
||||
print(("[ELEMENTE] bb-ext-calc: bb_dL={:.3f} bb_dR={:.3f}"
|
||||
" btan=({:.2f},{:.2f}) perp=({:.2f},{:.2f})"
|
||||
" od=({:.2f},{:.2f}) dot_opx={:.2f} → ext={:.3f}").format(
|
||||
backbone_dL, backbone_dR,
|
||||
b_tan.X, b_tan.Y, perp_bx_e.X, perp_bx_e.Y,
|
||||
out_dir.X, out_dir.Y, dot_opx_e, backbone_ext))
|
||||
|
||||
standard_miter = _t_junction_miter(ep_pt, out_dir, b_tan, b_dicke,
|
||||
through_meta.get("referenz", "mid"))
|
||||
@@ -8805,23 +8824,25 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
||||
_ext_arr[_li_x] or 0)
|
||||
break
|
||||
if not _backbone_mat: continue
|
||||
# T-Stem axis (extended for backbone-side)
|
||||
# Backbone-axis = extended bis Through-Far-Face (drill).
|
||||
# Non-backbone columns nutzen geom (= stoppen am Snap).
|
||||
if _backbone_ext_val > 0:
|
||||
if _is_end:
|
||||
_my_axis_ext = _extend_axis_curve(
|
||||
_backbone_axis_ext = _extend_axis_curve(
|
||||
geom, 0, _backbone_ext_val)
|
||||
else:
|
||||
_my_axis_ext = _extend_axis_curve(
|
||||
_backbone_axis_ext = _extend_axis_curve(
|
||||
geom, _backbone_ext_val, 0)
|
||||
else:
|
||||
_my_axis_ext = geom
|
||||
_backbone_axis_ext = geom
|
||||
_my_axis_ext = geom
|
||||
try:
|
||||
_ps = _my_axis_ext.PointAtStart
|
||||
_pe = _my_axis_ext.PointAtEnd
|
||||
_ps = _backbone_axis_ext.PointAtStart
|
||||
_pe = _backbone_axis_ext.PointAtEnd
|
||||
_gs = geom.PointAtStart
|
||||
_ge = geom.PointAtEnd
|
||||
print(("[ELEMENTE] axis-ext: geom y=[{:.3f}{:.3f}]"
|
||||
" → ext y=[{:.3f}{:.3f}] (ext_val={:.3f},"
|
||||
" → bb-ext y=[{:.3f}{:.3f}] (ext_val={:.3f},"
|
||||
" is_end={})").format(
|
||||
_gs.Y, _ge.Y, _ps.Y, _pe.Y,
|
||||
_backbone_ext_val, _is_end))
|
||||
@@ -8872,7 +8893,7 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
||||
if _bbones:
|
||||
_bd_l, _bd_r, _ = _bbones[0]
|
||||
_backbone_col_rect = _layer_rect_2d(
|
||||
_my_axis_ext, _bd_l, _bd_r)
|
||||
_backbone_axis_ext, _bd_l, _bd_r)
|
||||
# 3D Version fuer Carve
|
||||
if _backbone_col_rect is not None:
|
||||
try:
|
||||
@@ -8918,8 +8939,11 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
||||
# Polygone werden gegen Backbone-Column geschnitten.
|
||||
_height = float(ok) - float(uk)
|
||||
for _mat in list(_th_off_by_mat.keys()):
|
||||
if _mat not in _my_info_by_mat: continue
|
||||
# Auch Mats die NUR in through sind durchlaufen,
|
||||
# damit hoehere-prio backbone diese carven kann
|
||||
# (z.B. Aussenwand-Daemm wo Beton durchdrillt).
|
||||
_is_backbone = (_mat == _backbone_mat)
|
||||
_has_my_cols = _mat in _my_info_by_mat
|
||||
# Cross-junction safe: nutze CURRENT through-Brep(s)
|
||||
_layer_breps_3d = []
|
||||
for _o in _th_objs_by_mat.get(_mat, []):
|
||||
@@ -8933,8 +8957,12 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
||||
# (= fuer west-stub-filter spaeter)
|
||||
_column_breps = []
|
||||
_column_x_ranges = []
|
||||
for (_dl, _dr, _layer_i) in _my_info_by_mat[_mat]:
|
||||
_r = _layer_rect_2d(_my_axis_ext, _dl, _dr)
|
||||
_ax_for_col = (_backbone_axis_ext if _is_backbone
|
||||
else _my_axis_ext)
|
||||
_my_cols_iter = (_my_info_by_mat[_mat]
|
||||
if _has_my_cols else [])
|
||||
for (_dl, _dr, _layer_i) in _my_cols_iter:
|
||||
_r = _layer_rect_2d(_ax_for_col, _dl, _dr)
|
||||
if _r is None: continue
|
||||
try:
|
||||
_profile = _r.DuplicateCurve()
|
||||
@@ -8956,21 +8984,11 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
||||
print("[ELEMENTE] Column-extr exc ({}):"
|
||||
.format(_mat), _ex)
|
||||
if not _layer_breps_3d: continue
|
||||
# 3D Union all
|
||||
if len(_layer_breps_3d) == 1:
|
||||
_result_breps = _layer_breps_3d
|
||||
else:
|
||||
try:
|
||||
_u3d = rg.Brep.CreateBooleanUnion(
|
||||
_layer_breps_3d, 0.01)
|
||||
if _u3d and len(_u3d) > 0:
|
||||
_result_breps = list(_u3d)
|
||||
else:
|
||||
_result_breps = _layer_breps_3d
|
||||
except Exception as _ex:
|
||||
print("[ELEMENTE] {} Union exc:"
|
||||
.format(_mat), _ex)
|
||||
_result_breps = _layer_breps_3d
|
||||
# Kein 3D Union — bei disjoint breps wuerde Union eine
|
||||
# Mega-Brep mit combined bbox machen, was den BBox-
|
||||
# overlap-filter unten kaputt macht. Jeder brep wird
|
||||
# separat carved.
|
||||
_result_breps = list(_layer_breps_3d)
|
||||
# Carve mit backbone-col + hoehere-prio through-bands
|
||||
if not _is_backbone:
|
||||
_carve_breps = []
|
||||
@@ -9000,10 +9018,50 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
||||
_carve_breps.append(_ob_br)
|
||||
except Exception: pass
|
||||
if _carve_breps:
|
||||
# Diagnostic: pre-carve breps + carve breps
|
||||
for _di, _dbr in enumerate(_result_breps):
|
||||
try:
|
||||
_bbd = _dbr.GetBoundingBox(True)
|
||||
print(("[ELEMENTE] {} PRE-CARVE br[{}]"
|
||||
" x=[{:.3f},{:.3f}]"
|
||||
" y=[{:.3f},{:.3f}]").format(
|
||||
_mat, _di,
|
||||
_bbd.Min.X, _bbd.Max.X,
|
||||
_bbd.Min.Y, _bbd.Max.Y))
|
||||
except Exception: pass
|
||||
for _di, _dcb in enumerate(_carve_breps):
|
||||
try:
|
||||
_bbd = _dcb.GetBoundingBox(True)
|
||||
print(("[ELEMENTE] {} CARVE cb[{}]"
|
||||
" x=[{:.3f},{:.3f}]"
|
||||
" y=[{:.3f},{:.3f}]").format(
|
||||
_mat, _di,
|
||||
_bbd.Min.X, _bbd.Max.X,
|
||||
_bbd.Min.Y, _bbd.Max.Y))
|
||||
except Exception: pass
|
||||
_eps = 0.001
|
||||
_current = list(_result_breps)
|
||||
for _cb in _carve_breps:
|
||||
for _ci, _cb in enumerate(_carve_breps):
|
||||
_cb_bb = _cb.GetBoundingBox(True)
|
||||
_next = []
|
||||
for _br in _current:
|
||||
for _bi, _br in enumerate(_current):
|
||||
# Strict bbox overlap: touching faces
|
||||
# triggern Rhino BoolDiff "punch-through"
|
||||
# Artefakte → vorher rausfiltern.
|
||||
_br_bb = _br.GetBoundingBox(True)
|
||||
if (_br_bb.Min.X + _eps >= _cb_bb.Max.X
|
||||
or _cb_bb.Min.X + _eps >= _br_bb.Max.X
|
||||
or _br_bb.Min.Y + _eps >= _cb_bb.Max.Y
|
||||
or _cb_bb.Min.Y + _eps >= _br_bb.Max.Y
|
||||
or _br_bb.Min.Z + _eps >= _cb_bb.Max.Z
|
||||
or _cb_bb.Min.Z + _eps >= _br_bb.Max.Z):
|
||||
print(("[ELEMENTE] {} SKIP carve"
|
||||
" cb[{}] vs br[{}]").format(
|
||||
_mat, _ci, _bi))
|
||||
_next.append(_br); continue
|
||||
print(("[ELEMENTE] {} DO carve"
|
||||
" cb[{}] vs br[{}]").format(
|
||||
_mat, _ci, _bi))
|
||||
try:
|
||||
_diff = rg.Brep.CreateBooleanDifference(
|
||||
_br, _cb, 0.001)
|
||||
@@ -9021,6 +9079,18 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
||||
_result_breps = _current
|
||||
# (West-stub filter entfernt — war zu aggressiv,
|
||||
# discarded legitime through-wall west pieces)
|
||||
# Union NACH Carve: touchende gleiches-Material
|
||||
# Pieces zusammenführen (z.B. Backbone-Beton-Säule
|
||||
# + Aussenwand-Stahl-Band → T-Shape).
|
||||
if len(_result_breps) > 1:
|
||||
try:
|
||||
_u3d_post = rg.Brep.CreateBooleanUnion(
|
||||
list(_result_breps), 0.01)
|
||||
if _u3d_post and len(_u3d_post) > 0:
|
||||
_result_breps = list(_u3d_post)
|
||||
except Exception as _ex:
|
||||
print("[ELEMENTE] {} post-Union exc:"
|
||||
.format(_mat), _ex)
|
||||
# 4. Validity + MergeCoplanarFaces
|
||||
_result_breps = [_br for _br in _result_breps
|
||||
if _br is not None and _br.IsValid]
|
||||
@@ -9061,11 +9131,13 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
||||
_existing[_ei].Id, True)
|
||||
except Exception: pass
|
||||
# T-Stem-Layer fuer dieses Material consumen
|
||||
for (_dl, _dr, _layer_i) in _my_info_by_mat[_mat]:
|
||||
if _layer_i < len(layer_breps):
|
||||
_lb_old = layer_breps[_layer_i]
|
||||
layer_breps[_layer_i] = (
|
||||
None, _lb_old[1], _lb_old[2])
|
||||
# (nur wenn T-stem dieses Material ueberhaupt hat)
|
||||
if _has_my_cols:
|
||||
for (_dl, _dr, _layer_i) in _my_info_by_mat[_mat]:
|
||||
if _layer_i < len(layer_breps):
|
||||
_lb_old = layer_breps[_layer_i]
|
||||
layer_breps[_layer_i] = (
|
||||
None, _lb_old[1], _lb_old[2])
|
||||
print("[ELEMENTE] 2D-Phase2: {} ({}) → {} brep(s)"
|
||||
" aus {} cols".format(
|
||||
_mat,
|
||||
|
||||
Reference in New Issue
Block a user