T-Junction Phase 2 mit 3D Brep Union + Material-Prio-Carve
Asymmetric L-merge fuer Schichtdurchdringung: - Backbone (= hoechste Material-Prio in beiden Waenden) bildet T-form - Non-backbone Layer werden gecarved mit backbone-Column + through-Bands hoeherer Prio - ext=0 fuer T-Stem-Axis (= column endet am snap, kein "drueber") - 3D Brep Union via Brep.CreateBooleanUnion mit cross-junction safety (= aktueller through-Brep statt von Meta neu zu bauen) - Cleanup: MergeCoplanarFaces Plus: - Innenwand Beton 20cm Style (Putz + Beton + Putz, ref-mid) - curve_vertex_dots.py: gruene Vertex-Punkte fuer Polylinen/Curves - Cluster-Volume Select Handler: Shift-Modifier fuer Multi-Select - startup.py: Top-View maximieren on Doc-Open Known limitation: Putz-Schicht kann in bestimmten Konfigurationen visuell suedlich des Daemm-Band-Top weiter sichtbar sein (= edge case fuer asymmetric layers). Naechster Schritt: manuelle 2D-Polygon-Konstruktion statt 3D Boolean.
This commit is contained in:
@@ -0,0 +1,161 @@
|
|||||||
|
#! python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
# Copyright (C) 2026 Karim Gabriele Varano
|
||||||
|
"""
|
||||||
|
curve_vertex_dots.py
|
||||||
|
Display-only Vertex-Dots fuer GENERISCHE Curves (Polylinen, Linien,
|
||||||
|
Rectangles, NurbsCurves etc). Zeigt gruene Punkte an allen Vertices
|
||||||
|
selektierter Curves — hilft beim Visuell-Finden von Grip-Positionen
|
||||||
|
wenn die Curve eine Fuellung (Hatch) hat und schwer per Klick auf
|
||||||
|
einen einzelnen Vertex zu treffen ist.
|
||||||
|
|
||||||
|
Display-only — kein eigener Drag-Handler. User editiert Vertices via
|
||||||
|
Rhino's native _Grips (Punkte sichtbar machen + Standard-Drag) oder
|
||||||
|
direktes Object-Snapping waehrend Drag.
|
||||||
|
|
||||||
|
Skipt dossier-managed Curves (wand_axis, treppe_axis, schnitt_axis,
|
||||||
|
wand_outline, wand_centerline, raum_polylinie etc) — die haben ihre
|
||||||
|
eigenen Conduits oder duerfen nicht via Vertex editiert werden.
|
||||||
|
"""
|
||||||
|
import Rhino
|
||||||
|
import Rhino.Display as rd
|
||||||
|
import Rhino.Geometry as rg
|
||||||
|
import scriptcontext as sc
|
||||||
|
import System.Drawing as SD
|
||||||
|
|
||||||
|
|
||||||
|
# --- Konstanten ------------------------------------------------------------
|
||||||
|
|
||||||
|
_MARKER_RADIUS_PX = 6
|
||||||
|
_MARKER_FILL = SD.Color.FromArgb(200, 95, 168, 150) # accent-gruen
|
||||||
|
_MARKER_BORDER = SD.Color.FromArgb(255, 47, 93, 84)
|
||||||
|
|
||||||
|
# Dossier-managed Element-Types die NICHT mit generic dots versehen werden
|
||||||
|
# (= haben eigene Conduits oder sind nicht editierbar via Vertex-Click).
|
||||||
|
_SKIP_TYPES = {
|
||||||
|
"wand_axis", "wand_centerline", "wand_outline", "wand_volume",
|
||||||
|
"treppe_axis", "treppe_outline", "treppe_volume",
|
||||||
|
"schnitt_axis", "schnitt_outline",
|
||||||
|
"raum_polylinie", "raum_stempel",
|
||||||
|
"ausschnitt_polylinie",
|
||||||
|
"decke_polylinie", "decke_volume",
|
||||||
|
"dach_polylinie", "dach_volume",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --- Helpers --------------------------------------------------------------
|
||||||
|
|
||||||
|
def _is_dossier_managed(obj):
|
||||||
|
"""True wenn obj ein dossier-managed Element ist (= Skip)."""
|
||||||
|
if obj is None or obj.IsDeleted: return True
|
||||||
|
try:
|
||||||
|
t = obj.Attributes.GetUserString("dossier_element_type") or ""
|
||||||
|
return t in _SKIP_TYPES
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _curve_vertices(curve):
|
||||||
|
"""Liefert Liste von rg.Point3d fuer alle relevanten Vertices der
|
||||||
|
Curve. Verschiedene Curve-Types haben verschiedene Vertices:
|
||||||
|
- LineCurve: 2 Endpunkte
|
||||||
|
- PolylineCurve: alle Polyline-Punkte (deduplizert wenn closed)
|
||||||
|
- PolyCurve: rekursiv Segmente
|
||||||
|
- NurbsCurve/sonst: Start + End (control points nicht — zu viele)"""
|
||||||
|
pts = []
|
||||||
|
if curve is None: return pts
|
||||||
|
try:
|
||||||
|
if isinstance(curve, rg.PolylineCurve):
|
||||||
|
ok, pline = curve.TryGetPolyline()
|
||||||
|
if ok and pline is not None:
|
||||||
|
n = pline.Count
|
||||||
|
# Deduplizieren wenn closed (letzter Punkt = erster)
|
||||||
|
last = n
|
||||||
|
try:
|
||||||
|
if (n >= 2
|
||||||
|
and pline[0].DistanceTo(pline[n - 1]) < 1e-6):
|
||||||
|
last = n - 1
|
||||||
|
except Exception: pass
|
||||||
|
for i in range(last):
|
||||||
|
pts.append(rg.Point3d(pline[i]))
|
||||||
|
return pts
|
||||||
|
if isinstance(curve, rg.LineCurve):
|
||||||
|
pts.append(curve.PointAtStart)
|
||||||
|
pts.append(curve.PointAtEnd)
|
||||||
|
return pts
|
||||||
|
if isinstance(curve, rg.PolyCurve):
|
||||||
|
for i in range(curve.SegmentCount):
|
||||||
|
seg = curve.SegmentCurve(i)
|
||||||
|
if seg is None: continue
|
||||||
|
# Nur Start jedes Segments (End ist Start des naechsten)
|
||||||
|
pts.append(seg.PointAtStart)
|
||||||
|
# Letztes Segment-End anhaengen
|
||||||
|
try:
|
||||||
|
pts.append(curve.PointAtEnd)
|
||||||
|
except Exception: pass
|
||||||
|
return pts
|
||||||
|
# Generic Curve: nur Start + End
|
||||||
|
try:
|
||||||
|
pts.append(curve.PointAtStart)
|
||||||
|
pts.append(curve.PointAtEnd)
|
||||||
|
except Exception: pass
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return pts
|
||||||
|
|
||||||
|
|
||||||
|
# --- Conduit -------------------------------------------------------------
|
||||||
|
|
||||||
|
class _VertexDotConduit(rd.DisplayConduit):
|
||||||
|
"""Zeichnet bei jeder selektierten generischen Curve gruene Punkte
|
||||||
|
an allen Vertices."""
|
||||||
|
|
||||||
|
def DrawForeground(self, e):
|
||||||
|
try:
|
||||||
|
doc = Rhino.RhinoDoc.ActiveDoc
|
||||||
|
if doc is None: return
|
||||||
|
try:
|
||||||
|
sel = list(doc.Objects.GetSelectedObjects(False, False))
|
||||||
|
except Exception: return
|
||||||
|
seen_curve_ids = set()
|
||||||
|
for obj in sel:
|
||||||
|
if _is_dossier_managed(obj): continue
|
||||||
|
try:
|
||||||
|
cid = str(obj.Id)
|
||||||
|
except Exception: continue
|
||||||
|
if cid in seen_curve_ids: continue
|
||||||
|
seen_curve_ids.add(cid)
|
||||||
|
geom = obj.Geometry
|
||||||
|
if not isinstance(geom, rg.Curve): continue
|
||||||
|
for pt in _curve_vertices(geom):
|
||||||
|
try:
|
||||||
|
e.Display.DrawPoint(
|
||||||
|
pt, rd.PointStyle.RoundControlPoint,
|
||||||
|
_MARKER_RADIUS_PX, _MARKER_FILL)
|
||||||
|
except Exception:
|
||||||
|
try: e.Display.DrawDot(
|
||||||
|
pt, "·", _MARKER_FILL, _MARKER_BORDER)
|
||||||
|
except Exception: pass
|
||||||
|
except Exception as ex:
|
||||||
|
print("[CURVE_DOTS] DrawForeground:", ex)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Install -------------------------------------------------------------
|
||||||
|
|
||||||
|
_STICKY_CONDUIT = "_dossier_curve_vertex_dots_conduit"
|
||||||
|
|
||||||
|
|
||||||
|
def install_curve_vertex_dots():
|
||||||
|
"""Idempotent: alten Conduit disable, neuen installieren."""
|
||||||
|
try:
|
||||||
|
old = sc.sticky.get(_STICKY_CONDUIT)
|
||||||
|
if old is not None:
|
||||||
|
try: old.Enabled = False
|
||||||
|
except Exception: pass
|
||||||
|
conduit = _VertexDotConduit()
|
||||||
|
conduit.Enabled = True
|
||||||
|
sc.sticky[_STICKY_CONDUIT] = conduit
|
||||||
|
print("[CURVE_DOTS] Vertex-Dot-Conduit aktiv")
|
||||||
|
except Exception as ex:
|
||||||
|
print("[CURVE_DOTS] install:", ex)
|
||||||
+534
-51
@@ -2214,7 +2214,8 @@ def _detect_t_junction(doc, geschoss_id, wall_id, endpoint,
|
|||||||
tan = geom.TangentAt(t)
|
tan = geom.TangentAt(t)
|
||||||
return (meta["id"],
|
return (meta["id"],
|
||||||
rg.Vector3d(tan.X, tan.Y, 0),
|
rg.Vector3d(tan.X, tan.Y, 0),
|
||||||
float(meta["dicke"]))
|
float(meta["dicke"]),
|
||||||
|
meta.get("referenz", "mid"))
|
||||||
except Exception: continue
|
except Exception: continue
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -2299,32 +2300,30 @@ def _detect_through_wall_at(doc, partners_at_joint, exclude_self_tan):
|
|||||||
# Gefunden: ti/tj formen die Through-Wand
|
# Gefunden: ti/tj formen die Through-Wand
|
||||||
mi = _wand_meta_by_id(doc, wi)
|
mi = _wand_meta_by_id(doc, wi)
|
||||||
b_dicke = float((mi or {}).get("dicke", 0.25))
|
b_dicke = float((mi or {}).get("dicke", 0.25))
|
||||||
return (rg.Vector3d(ti.X, ti.Y, 0), b_dicke)
|
b_ref = (mi or {}).get("referenz", "mid")
|
||||||
|
return (rg.Vector3d(ti.X, ti.Y, 0), b_dicke, b_ref)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _t_junction_miter(endpoint, out_dir, b_tan, b_dicke):
|
def _t_junction_miter(endpoint, out_dir, b_tan, b_dicke, b_referenz="mid"):
|
||||||
"""Berechnet (miter_pt, miter_dir) fuer einen T-Stoss.
|
"""Berechnet (miter_pt, miter_dir) fuer einen T-Stoss.
|
||||||
miter_dir = Tangente der Durchgangs-Wand (Linie laeuft parallel zu B's Achse).
|
miter_pt = endpoint verschoben bis zur NAHEN Aussenflaeche von B.
|
||||||
miter_pt = endpoint verschoben um d_B/2 in Approach-Richtung — also auf
|
Beruecksichtigt B's referenz: bei mid ist Axis Centerline (Aussen-
|
||||||
der NAHEN Aussenflaeche von B (der Seite an der A ankommt).
|
flaechen ±d/2), bei left/right ist Axis auf einer Aussenkante."""
|
||||||
|
|
||||||
A (= T-Stem, das zweite Wand-Stueck) wird an dieser Stelle abgeschnitten;
|
|
||||||
B (= Durchgangswand) bleibt unveraendert. Das ist das BIM-Standard-
|
|
||||||
Verhalten: 'erste Wand bleibt, zweite haengt sich dran'."""
|
|
||||||
perp_b = rg.Vector3d(-b_tan.Y, b_tan.X, 0)
|
perp_b = rg.Vector3d(-b_tan.Y, b_tan.X, 0)
|
||||||
try: perp_b.Unitize()
|
try: perp_b.Unitize()
|
||||||
except Exception: return None
|
except Exception: return None
|
||||||
# A's Body liegt auf der Seite -out_dir. Approach-Seite (perp_b
|
|
||||||
# ausgerichtet zur Approach) = sign(dot(-out_dir, perp_b)).
|
|
||||||
s = -(out_dir.X * perp_b.X + out_dir.Y * perp_b.Y)
|
s = -(out_dir.X * perp_b.X + out_dir.Y * perp_b.Y)
|
||||||
if abs(s) < 1e-6:
|
if abs(s) < 1e-6: return None
|
||||||
# A parallel zu B — kein sauberer T-Stoss
|
|
||||||
return None
|
|
||||||
side = 1.0 if s > 0 else -1.0
|
side = 1.0 if s > 0 else -1.0
|
||||||
off = float(b_dicke) * 0.5 * side
|
# B's Aussenflaechen-Offsets entlang perp_b
|
||||||
mpt = rg.Point3d(endpoint.X + perp_b.X * off,
|
start_off, d_total = _wall_offsets_from_referenz(b_dicke, b_referenz)
|
||||||
endpoint.Y + perp_b.Y * off, 0)
|
off_a = start_off
|
||||||
|
off_b = start_off - d_total
|
||||||
|
# Nahe Aussenflaeche = die in approach-Richtung naehere (= side-Richtung)
|
||||||
|
near_off = max(off_a, off_b) if side > 0 else min(off_a, off_b)
|
||||||
|
mpt = rg.Point3d(endpoint.X + perp_b.X * near_off,
|
||||||
|
endpoint.Y + perp_b.Y * near_off, 0)
|
||||||
mdir = rg.Vector3d(b_tan.X, b_tan.Y, 0)
|
mdir = rg.Vector3d(b_tan.X, b_tan.Y, 0)
|
||||||
try: mdir.Unitize()
|
try: mdir.Unitize()
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
@@ -2867,11 +2866,17 @@ class _ClusterVolumeSelectHandler(Rhino.UI.MouseCallback):
|
|||||||
best_d2 = d2; best_axis = c_ax
|
best_d2 = d2; best_axis = c_ax
|
||||||
except Exception: continue
|
except Exception: continue
|
||||||
if best_axis is None: return
|
if best_axis is None: return
|
||||||
|
# Shift-Modifier: ADD zur Selektion (kein UnselectAll)
|
||||||
|
try:
|
||||||
|
_shift = bool(e.ShiftKeyDown)
|
||||||
|
except Exception:
|
||||||
|
_shift = False
|
||||||
try: e.Cancel = True
|
try: e.Cancel = True
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
self._busy = True
|
self._busy = True
|
||||||
try:
|
try:
|
||||||
doc.Objects.UnselectAll()
|
if not _shift:
|
||||||
|
doc.Objects.UnselectAll()
|
||||||
doc.Objects.Select(best_axis.Id, True)
|
doc.Objects.Select(best_axis.Id, True)
|
||||||
try: view.Redraw()
|
try: view.Redraw()
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
@@ -3367,6 +3372,53 @@ def _make_wall_layer_brep(axis_curve, d_left, d_right, uk, ok,
|
|||||||
return extrusion.ToBrep()
|
return extrusion.ToBrep()
|
||||||
|
|
||||||
|
|
||||||
|
def _layer_rect_2d(axis_curve, d_left, d_right):
|
||||||
|
"""Liefert geschlossene XY-Rect-Curve fuer eine Schicht (Achse +
|
||||||
|
perp Offsets). Genutzt fuer 2D-Polylinen-Union beim T-Junction.
|
||||||
|
Nutzt _offset_curve wie _make_wall_layer_brep — wichtig fuer
|
||||||
|
Boolean-Compatibility (PolyCurve statt PolylineCurve).
|
||||||
|
Forced z=0 + CCW winding (= ClosedCurveOrientation check)."""
|
||||||
|
if not isinstance(axis_curve, rg.Curve): return None
|
||||||
|
d_l = float(d_left); d_r = float(d_right)
|
||||||
|
if abs(d_l - d_r) < 1e-9: return None
|
||||||
|
try:
|
||||||
|
# Force axis to z=0 plane
|
||||||
|
ax_xy = axis_curve.DuplicateCurve()
|
||||||
|
if abs(ax_xy.PointAtStart.Z) > 1e-9 or abs(ax_xy.PointAtEnd.Z) > 1e-9:
|
||||||
|
ax_xy.Transform(rg.Transform.PlaneToPlane(
|
||||||
|
rg.Plane(rg.Point3d(0,0,ax_xy.PointAtStart.Z),
|
||||||
|
rg.Vector3d.ZAxis),
|
||||||
|
rg.Plane.WorldXY))
|
||||||
|
plane = rg.Plane.WorldXY
|
||||||
|
tol = 0.001
|
||||||
|
left = _offset_curve(ax_xy, plane, d_l, tol)
|
||||||
|
right = _offset_curve(ax_xy, plane, d_r, tol)
|
||||||
|
if not left or not right: return None
|
||||||
|
L = left[0]; R = right[0]
|
||||||
|
R.Reverse()
|
||||||
|
cap_s = rg.LineCurve(L.PointAtEnd, R.PointAtStart)
|
||||||
|
cap_e = rg.LineCurve(R.PointAtEnd, L.PointAtStart)
|
||||||
|
joined = rg.Curve.JoinCurves([L, cap_s, R, cap_e], tol)
|
||||||
|
if not joined or len(joined) == 0 or not joined[0].IsClosed:
|
||||||
|
return None
|
||||||
|
out = joined[0]
|
||||||
|
# Force z=0 (offset_curve might preserve original z)
|
||||||
|
try:
|
||||||
|
if (abs(out.PointAtStart.Z) > 1e-9):
|
||||||
|
xform = rg.Transform.Translation(0, 0, -out.PointAtStart.Z)
|
||||||
|
out.Transform(xform)
|
||||||
|
except Exception: pass
|
||||||
|
# Force CCW orientation (= positive area in WorldXY)
|
||||||
|
try:
|
||||||
|
ori = out.ClosedCurveOrientation(rg.Plane.WorldXY)
|
||||||
|
if ori == rg.CurveOrientation.Clockwise:
|
||||||
|
out.Reverse()
|
||||||
|
except Exception: pass
|
||||||
|
return out
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _wall_offsets_from_referenz(dicke, referenz):
|
def _wall_offsets_from_referenz(dicke, referenz):
|
||||||
"""Liefert (start_offset, d_total) — start_offset ist der Wert von 'links'
|
"""Liefert (start_offset, d_total) — start_offset ist der Wert von 'links'
|
||||||
relativ zur Achse, d_total ist die Summe der Wand-Dicke (immer positiv)."""
|
relativ zur Achse, d_total ist die Summe der Wand-Dicke (immer positiv)."""
|
||||||
@@ -3600,19 +3652,38 @@ def _wand_meta_prio(doc, meta):
|
|||||||
except Exception: return 500
|
except Exception: return 500
|
||||||
|
|
||||||
|
|
||||||
|
# Material-Prio fuer T-Junction Schichtdurchdringung: an einem T-Stoss
|
||||||
|
# zwischen zwei layered Waenden geht NUR die hoechste gemeinsame
|
||||||
|
# Material-Prio als 'Backbone' durch + uniont (= T-Form). Alle anderen
|
||||||
|
# T-Stem-Layer T-mitern am Near-Face → bei symmetrisch geschichteten
|
||||||
|
# Waenden ergibt das automatisch L-Stoesse mit gleichfarbigen Aussenlagen.
|
||||||
|
_MATERIAL_PRIO = {
|
||||||
|
"stahlbeton": 800,
|
||||||
|
"beton": 800,
|
||||||
|
"mauerwerk": 600,
|
||||||
|
"kalksandstein": 600,
|
||||||
|
"ziegel": 550,
|
||||||
|
"holzstaender": 400,
|
||||||
|
"holz": 400,
|
||||||
|
"daemmung": 200,
|
||||||
|
"putz": 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _material_prio(mat):
|
||||||
|
if not mat: return 0
|
||||||
|
return _MATERIAL_PRIO.get(mat.strip().lower(), 300)
|
||||||
|
|
||||||
|
|
||||||
def _t_junction_layer_overrides(doc, my_meta, through_meta, ep_pt, out_dir,
|
def _t_junction_layer_overrides(doc, my_meta, through_meta, ep_pt, out_dir,
|
||||||
b_tan, b_dicke):
|
b_tan, b_dicke):
|
||||||
"""Per-Layer Schichtdurchdringung bei T-Junction zwischen layered Waenden.
|
"""Per-Layer Schichtdurchdringung bei T-Junction zwischen layered Waenden.
|
||||||
|
|
||||||
Pro T-Stem-Schicht wird in der Through-Wand nach einer Schicht mit
|
NEU: nur das BACKBONE-Material extends + uniont. Backbone = das mit
|
||||||
GLEICHEM MATERIAL gesucht:
|
hoechster Material-Prio das in BEIDEN Waenden vorkommt. Andere Layer
|
||||||
- Match: T-Stem-Layer extends durch die Through-Wand (Axis-Extension um
|
stoppen am Near-Face (Standard T-Miter).
|
||||||
b_dicke/2 = bis Far-Face) → visuell verbunden
|
|
||||||
- No Match: T-Stem-Layer stoppt am Near-Face (Standard T-Miter)
|
|
||||||
|
|
||||||
Returns (per_layer_ext, per_layer_miter) — Listen pro T-Stem-Layer.
|
Returns (per_layer_ext, per_layer_miter) — Listen pro T-Stem-Layer."""
|
||||||
Wenn my keine Layers hat: (None, None) (Caller faellt auf uniform Miter
|
|
||||||
zurueck)."""
|
|
||||||
if not my_meta or not through_meta: return None, None
|
if not my_meta or not through_meta: return None, None
|
||||||
my_layers = my_meta.get("wand_layers") or []
|
my_layers = my_meta.get("wand_layers") or []
|
||||||
if not my_layers: return None, None
|
if not my_layers: return None, None
|
||||||
@@ -3624,26 +3695,101 @@ def _t_junction_layer_overrides(doc, my_meta, through_meta, ep_pt, out_dir,
|
|||||||
mat = (l.get("material") or "").strip()
|
mat = (l.get("material") or "").strip()
|
||||||
if mat: th_materials.add(mat)
|
if mat: th_materials.add(mat)
|
||||||
else:
|
else:
|
||||||
# Through ist solid → 1 Material aus Style
|
|
||||||
try:
|
try:
|
||||||
sm = _wand_solid_material(doc, through_meta) if doc else ""
|
sm = _wand_solid_material(doc, through_meta) if doc else ""
|
||||||
if sm: th_materials.add(sm)
|
if sm: th_materials.add(sm)
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
standard_miter = _t_junction_miter(ep_pt, out_dir, b_tan, b_dicke)
|
# Backbone = hoechste Material-Prio in BEIDEN Waenden
|
||||||
extension = float(b_dicke) * 0.5 # bis Far-Face
|
my_materials = set()
|
||||||
|
for layer in my_layers:
|
||||||
|
m = (layer.get("material") or "").strip()
|
||||||
|
if m: my_materials.add(m)
|
||||||
|
common = my_materials & th_materials
|
||||||
|
backbone = None
|
||||||
|
backbone_prio = -1
|
||||||
|
for m in common:
|
||||||
|
p = _material_prio(m)
|
||||||
|
if p > backbone_prio:
|
||||||
|
backbone = m
|
||||||
|
backbone_prio = p
|
||||||
|
|
||||||
|
# Backbone-Schicht-Position in der Through-Wand finden (perp-Offsets)
|
||||||
|
backbone_dL = None
|
||||||
|
backbone_dR = None
|
||||||
|
if backbone and through_meta.get("wand_layered") and th_layers:
|
||||||
|
th_dicke = float(through_meta.get("dicke", 0) or 0)
|
||||||
|
th_ref = through_meta.get("referenz", "mid")
|
||||||
|
start_off, _ = _wall_offsets_from_referenz(th_dicke, th_ref)
|
||||||
|
cur = start_off
|
||||||
|
for tl in th_layers:
|
||||||
|
d = float(tl.get("dicke", 0) or 0)
|
||||||
|
if d <= 0: continue
|
||||||
|
if (tl.get("material") or "").strip() == backbone:
|
||||||
|
backbone_dL = cur
|
||||||
|
backbone_dR = cur - d
|
||||||
|
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_ext = 0.0
|
||||||
|
if backbone_ext is None:
|
||||||
|
backbone_ext = float(b_dicke) * 0.5
|
||||||
|
|
||||||
|
standard_miter = _t_junction_miter(ep_pt, out_dir, b_tan, b_dicke,
|
||||||
|
through_meta.get("referenz", "mid"))
|
||||||
|
# Through-Layer pro Material indexieren (Liste von (d_l, d_r))
|
||||||
|
through_by_mat_pos = {}
|
||||||
|
if through_meta.get("wand_layered") and th_layers:
|
||||||
|
th_dicke2 = float(through_meta.get("dicke", 0) or 0)
|
||||||
|
th_ref2 = through_meta.get("referenz", "mid")
|
||||||
|
start_off2, _ = _wall_offsets_from_referenz(th_dicke2, th_ref2)
|
||||||
|
cur2 = start_off2
|
||||||
|
for tl in th_layers:
|
||||||
|
d2 = float(tl.get("dicke", 0) or 0)
|
||||||
|
if d2 <= 0: continue
|
||||||
|
m2 = (tl.get("material") or "").strip()
|
||||||
|
if m2:
|
||||||
|
through_by_mat_pos.setdefault(m2, []).append(
|
||||||
|
(cur2, cur2 - d2))
|
||||||
|
cur2 -= d2
|
||||||
|
# perp_b und Approach-Richtung
|
||||||
|
perp_bx = rg.Vector3d(-b_tan.Y, b_tan.X, 0)
|
||||||
|
try: perp_bx.Unitize()
|
||||||
|
except Exception: pass
|
||||||
|
dot_opx = out_dir.X * perp_bx.X + out_dir.Y * perp_bx.Y
|
||||||
|
b_tan_u = rg.Vector3d(b_tan.X, b_tan.Y, 0)
|
||||||
|
try: b_tan_u.Unitize()
|
||||||
|
except Exception: pass
|
||||||
per_ext = []
|
per_ext = []
|
||||||
per_miter = []
|
per_miter = []
|
||||||
|
match_log = []
|
||||||
for layer in my_layers:
|
for layer in my_layers:
|
||||||
mat = (layer.get("material") or "").strip()
|
mat = (layer.get("material") or "").strip()
|
||||||
if mat and mat in th_materials:
|
if backbone and mat == backbone:
|
||||||
# Match → durchstossen
|
per_ext.append(backbone_ext)
|
||||||
per_ext.append(extension)
|
|
||||||
per_miter.append(None)
|
per_miter.append(None)
|
||||||
|
match_log.append("{}=BACKBONE(+{:.3f})".format(mat, backbone_ext))
|
||||||
else:
|
else:
|
||||||
# Kein Match → an Near-Face stoppen
|
# Non-backbone: Standard T-Miter an Through-Aussenkante.
|
||||||
|
# T-Stem-Layer + Through-Putz-Band touchieren am L-Korner
|
||||||
|
# mit gleichem Material (Same-color visual L, seam moeglich).
|
||||||
per_ext.append(0.0)
|
per_ext.append(0.0)
|
||||||
per_miter.append(standard_miter)
|
per_miter.append(standard_miter)
|
||||||
|
match_log.append("{}=stop".format(mat or "?"))
|
||||||
|
print("[ELEMENTE] T-Junction Schichtdurchdringung: backbone={} "
|
||||||
|
"(prio={}, ext={:.3f}), through-mats={}, layers: {}".format(
|
||||||
|
backbone or "none", backbone_prio, backbone_ext,
|
||||||
|
sorted(th_materials), ", ".join(match_log)))
|
||||||
return per_ext, per_miter
|
return per_ext, per_miter
|
||||||
|
|
||||||
|
|
||||||
@@ -8507,24 +8653,21 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
|||||||
element_id, p_s,
|
element_id, p_s,
|
||||||
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, b_ref = tj
|
||||||
if _wand_should_apply_t_miter(doc, meta, _oid):
|
if _wand_should_apply_t_miter(doc, meta, _oid):
|
||||||
tm = _t_junction_miter(p_s, out_s, b_tan, b_dicke)
|
tm = _t_junction_miter(p_s, out_s, b_tan,
|
||||||
|
b_dicke, b_ref)
|
||||||
if tm is not None: miter_start = tm
|
if tm is not None: miter_start = tm
|
||||||
# Through-Meta merken fuer per-Layer Schicht-
|
|
||||||
# durchdringung (s. unten bei layer_breps build)
|
|
||||||
t_junction_start = (_oid, b_tan, b_dicke, p_s, out_s)
|
t_junction_start = (_oid, b_tan, b_dicke, p_s, out_s)
|
||||||
else:
|
else:
|
||||||
# 3+ Joint: ich bin T-Stem wenn meine Tangente NICHT
|
|
||||||
# collinear mit zwei collinearen Partner-Tangenten ist.
|
|
||||||
# Eigene Tangente (gerichtet aus Body raus) am Start = -TangentAtStart
|
|
||||||
_my_t = geom.TangentAtStart
|
_my_t = geom.TangentAtStart
|
||||||
_my_out_tan = rg.Vector3d(-_my_t.X, -_my_t.Y, 0)
|
_my_out_tan = rg.Vector3d(-_my_t.X, -_my_t.Y, 0)
|
||||||
through = _detect_through_wall_at(
|
through = _detect_through_wall_at(
|
||||||
doc, joints.get(key_s, []), _my_out_tan)
|
doc, joints.get(key_s, []), _my_out_tan)
|
||||||
if through is not None:
|
if through is not None:
|
||||||
b_tan, b_dicke = through
|
b_tan, b_dicke, b_ref = through
|
||||||
tm = _t_junction_miter(p_s, out_s, b_tan, b_dicke)
|
tm = _t_junction_miter(p_s, out_s, b_tan,
|
||||||
|
b_dicke, b_ref)
|
||||||
if tm is not None: miter_start = tm
|
if tm is not None: miter_start = tm
|
||||||
if out_e is not None:
|
if out_e is not None:
|
||||||
key_e = _pt_key(p_e)
|
key_e = _pt_key(p_e)
|
||||||
@@ -8541,20 +8684,21 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
|||||||
element_id, p_e,
|
element_id, p_e,
|
||||||
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, b_ref = tj
|
||||||
if _wand_should_apply_t_miter(doc, meta, _oid):
|
if _wand_should_apply_t_miter(doc, meta, _oid):
|
||||||
tm = _t_junction_miter(p_e, out_e, b_tan, b_dicke)
|
tm = _t_junction_miter(p_e, out_e, b_tan,
|
||||||
|
b_dicke, b_ref)
|
||||||
if tm is not None: miter_end = tm
|
if tm is not None: miter_end = tm
|
||||||
t_junction_end = (_oid, b_tan, b_dicke, p_e, out_e)
|
t_junction_end = (_oid, b_tan, b_dicke, p_e, out_e)
|
||||||
else:
|
else:
|
||||||
# 3+ Joint: T-Stem-Erkennung (analog start)
|
|
||||||
_my_t = geom.TangentAtEnd
|
_my_t = geom.TangentAtEnd
|
||||||
_my_out_tan = rg.Vector3d(_my_t.X, _my_t.Y, 0)
|
_my_out_tan = rg.Vector3d(_my_t.X, _my_t.Y, 0)
|
||||||
through = _detect_through_wall_at(
|
through = _detect_through_wall_at(
|
||||||
doc, joints.get(key_e, []), _my_out_tan)
|
doc, joints.get(key_e, []), _my_out_tan)
|
||||||
if through is not None:
|
if through is not None:
|
||||||
b_tan, b_dicke = through
|
b_tan, b_dicke, b_ref = through
|
||||||
tm = _t_junction_miter(p_e, out_e, b_tan, b_dicke)
|
tm = _t_junction_miter(p_e, out_e, b_tan,
|
||||||
|
b_dicke, b_ref)
|
||||||
if tm is not None: miter_end = tm
|
if tm is not None: miter_end = tm
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[ELEMENTE] wall joints:", ex)
|
print("[ELEMENTE] wall joints:", ex)
|
||||||
@@ -8589,14 +8733,346 @@ def _regenerate_element_body(doc, element_id, src_obj, meta, geom, geschoss_name
|
|||||||
per_layer_ext_start=pl_ext_s, per_layer_ext_end=pl_ext_e,
|
per_layer_ext_start=pl_ext_s, per_layer_ext_end=pl_ext_e,
|
||||||
per_layer_miter_start=pl_miter_s,
|
per_layer_miter_start=pl_miter_s,
|
||||||
per_layer_miter_end=pl_miter_e)
|
per_layer_miter_end=pl_miter_e)
|
||||||
# Diagnostic: layer build status — User sieht oft "solid" obwohl
|
# Diagnostic: layer build status
|
||||||
# walls layered sind, kontrollieren obs an Brep-Build oder Display liegt
|
|
||||||
_n_ok = sum(1 for (b, _c, _n) in layer_breps if b is not None)
|
_n_ok = sum(1 for (b, _c, _n) in layer_breps if b is not None)
|
||||||
_n_fail = len(layer_breps) - _n_ok
|
|
||||||
print("[ELEMENTE] layered build {} (chain={}): {}/{} layers built"
|
print("[ELEMENTE] layered build {} (chain={}): {}/{} layers built"
|
||||||
" (def={} layers)".format(
|
" (def={} layers)".format(
|
||||||
element_id, len(chain_ids) if chain_ids else 1,
|
element_id, len(chain_ids) if chain_ids else 1,
|
||||||
_n_ok, len(layer_breps), len(layers_def)))
|
_n_ok, len(layer_breps), len(layers_def)))
|
||||||
|
# Phase 2 Schichtdurchdringung (2D-Polylinen-Approach):
|
||||||
|
# Pro Material 2D-Rechtecke (Through-Bands + T-Stem-Spalten)
|
||||||
|
# bauen, in 2D unionen, fuer non-backbone Material mit
|
||||||
|
# Backbone-Column subtrahieren (= 2D-Carve), dann extrudieren.
|
||||||
|
# Robuster als 3D-BoolUnion mit touching breps.
|
||||||
|
try:
|
||||||
|
import json as _j
|
||||||
|
for _tj_info, _is_end in ((t_junction_start, False),
|
||||||
|
(t_junction_end, True)):
|
||||||
|
if _tj_info is None: continue
|
||||||
|
_toid = _tj_info[0]
|
||||||
|
_mit_arr = pl_miter_e if _is_end else pl_miter_s
|
||||||
|
_ext_arr = pl_ext_e if _is_end else pl_ext_s
|
||||||
|
if _mit_arr is None: continue
|
||||||
|
# Through axis + meta
|
||||||
|
_th_axis = _find_axis(doc, _toid)
|
||||||
|
if _th_axis is None: continue
|
||||||
|
_th_meta = _read_meta(_th_axis)
|
||||||
|
if not _th_meta or not _th_meta.get("wand_layered"): continue
|
||||||
|
_th_geom = _th_axis.Geometry
|
||||||
|
if not isinstance(_th_geom, rg.Curve): continue
|
||||||
|
_th_layers = _th_meta.get("wand_layers") or []
|
||||||
|
_th_ref = _th_meta.get("referenz", "mid")
|
||||||
|
_th_dicke = float(_th_meta.get("dicke", 0) or 0)
|
||||||
|
# Through-Layer-Offsets per Material
|
||||||
|
_th_start, _ = _wall_offsets_from_referenz(
|
||||||
|
_th_dicke, _th_ref)
|
||||||
|
_th_off_by_mat = {}
|
||||||
|
_cur = _th_start
|
||||||
|
for _tl in _th_layers:
|
||||||
|
_d = float(_tl.get("dicke", 0) or 0)
|
||||||
|
if _d <= 0: continue
|
||||||
|
_m = (_tl.get("material") or "").strip()
|
||||||
|
if _m:
|
||||||
|
_th_off_by_mat.setdefault(_m, []).append(
|
||||||
|
(_cur, _cur - _d))
|
||||||
|
_cur -= _d
|
||||||
|
# T-Stem-Layer-Offsets + layer-idx per Material
|
||||||
|
_my_layers = meta.get("wand_layers") or []
|
||||||
|
_my_dicke = float(meta.get("dicke", 0) or 0)
|
||||||
|
_my_ref = meta.get("referenz", "mid")
|
||||||
|
_my_start, _ = _wall_offsets_from_referenz(
|
||||||
|
_my_dicke, _my_ref)
|
||||||
|
_my_info_by_mat = {}
|
||||||
|
_cur = _my_start
|
||||||
|
for _i, _ml in enumerate(_my_layers):
|
||||||
|
_d = float(_ml.get("dicke", 0) or 0)
|
||||||
|
if _d <= 0: continue
|
||||||
|
_m = (_ml.get("material") or "").strip()
|
||||||
|
if _m:
|
||||||
|
_my_info_by_mat.setdefault(_m, []).append(
|
||||||
|
(_cur, _cur - _d, _i))
|
||||||
|
_cur -= _d
|
||||||
|
# Backbone-Material + Ext-Wert
|
||||||
|
_backbone_mat = None
|
||||||
|
_backbone_ext_val = 0.0
|
||||||
|
for _li_x in range(len(layers_def)):
|
||||||
|
if (_li_x < len(_mit_arr)
|
||||||
|
and _mit_arr[_li_x] is None):
|
||||||
|
_backbone_mat = (layers_def[_li_x].get(
|
||||||
|
"material") or "").strip()
|
||||||
|
if _ext_arr and _li_x < len(_ext_arr):
|
||||||
|
_backbone_ext_val = float(
|
||||||
|
_ext_arr[_li_x] or 0)
|
||||||
|
break
|
||||||
|
if not _backbone_mat: continue
|
||||||
|
# T-Stem axis (extended for backbone-side)
|
||||||
|
if _backbone_ext_val > 0:
|
||||||
|
if _is_end:
|
||||||
|
_my_axis_ext = _extend_axis_curve(
|
||||||
|
geom, 0, _backbone_ext_val)
|
||||||
|
else:
|
||||||
|
_my_axis_ext = _extend_axis_curve(
|
||||||
|
geom, _backbone_ext_val, 0)
|
||||||
|
else:
|
||||||
|
_my_axis_ext = geom
|
||||||
|
try:
|
||||||
|
_ps = _my_axis_ext.PointAtStart
|
||||||
|
_pe = _my_axis_ext.PointAtEnd
|
||||||
|
_gs = geom.PointAtStart
|
||||||
|
_ge = geom.PointAtEnd
|
||||||
|
print(("[ELEMENTE] axis-ext: geom y=[{:.3f}{:.3f}]"
|
||||||
|
" → ext y=[{:.3f}{:.3f}] (ext_val={:.3f},"
|
||||||
|
" is_end={})").format(
|
||||||
|
_gs.Y, _ge.Y, _ps.Y, _pe.Y,
|
||||||
|
_backbone_ext_val, _is_end))
|
||||||
|
except Exception: pass
|
||||||
|
# Backbone-Near-Face in T-Stem-Axis-Richtung (=
|
||||||
|
# wo Backbone-Layer auf Approach-Seite anfaengt).
|
||||||
|
# Non-backbone matching columns sollen DORT stoppen
|
||||||
|
# (Backbone hat hoehere Prio, Putz/etc weicht).
|
||||||
|
_btan_v = _tj_info[1]
|
||||||
|
_od_v = _tj_info[4]
|
||||||
|
_perp_b = rg.Vector3d(-_btan_v.Y, _btan_v.X, 0)
|
||||||
|
try: _perp_b.Unitize()
|
||||||
|
except Exception: pass
|
||||||
|
_dot_op = _od_v.X * _perp_b.X + _od_v.Y * _perp_b.Y
|
||||||
|
_backbone_near_dist = None
|
||||||
|
if _backbone_mat in _th_off_by_mat:
|
||||||
|
_bb_through = _th_off_by_mat[_backbone_mat]
|
||||||
|
if _bb_through:
|
||||||
|
_bb_dl, _bb_dr = _bb_through[0]
|
||||||
|
if _dot_op > 0:
|
||||||
|
_bn_perp = min(_bb_dl, _bb_dr)
|
||||||
|
else:
|
||||||
|
_bn_perp = max(_bb_dl, _bb_dr)
|
||||||
|
_backbone_near_dist = _bn_perp * (
|
||||||
|
1 if _dot_op > 0 else -1)
|
||||||
|
# Helper: T-Stem axis clipped an gegebener axial-distance
|
||||||
|
# vom Endpoint (negativ = retract, positiv = extend)
|
||||||
|
def _ax_clipped(_dist):
|
||||||
|
try:
|
||||||
|
_p1 = geom.PointAtStart
|
||||||
|
_p2 = geom.PointAtEnd
|
||||||
|
_tan = _p2 - _p1
|
||||||
|
if _tan.Length < 1e-9: return geom
|
||||||
|
_tan.Unitize()
|
||||||
|
if _is_end:
|
||||||
|
_new_end = _p2 + _tan * _dist
|
||||||
|
return rg.LineCurve(_p1, _new_end)
|
||||||
|
else:
|
||||||
|
_new_start = _p1 - _tan * _dist
|
||||||
|
return rg.LineCurve(_new_start, _p2)
|
||||||
|
except Exception:
|
||||||
|
return geom
|
||||||
|
# Backbone-Column 2D Rect + 3D Brep (fuer Carve)
|
||||||
|
_backbone_col_rect = None
|
||||||
|
_backbone_col_brep_3d = None
|
||||||
|
if _backbone_mat in _my_info_by_mat:
|
||||||
|
_bbones = _my_info_by_mat[_backbone_mat]
|
||||||
|
if _bbones:
|
||||||
|
_bd_l, _bd_r, _ = _bbones[0]
|
||||||
|
_backbone_col_rect = _layer_rect_2d(
|
||||||
|
_my_axis_ext, _bd_l, _bd_r)
|
||||||
|
# 3D Version fuer Carve
|
||||||
|
if _backbone_col_rect is not None:
|
||||||
|
try:
|
||||||
|
_bbh = float(ok) - float(uk)
|
||||||
|
_bbp = _backbone_col_rect.DuplicateCurve()
|
||||||
|
if abs(uk) > 1e-9:
|
||||||
|
_bbp.Transform(rg.Transform.Translation(
|
||||||
|
0, 0, uk))
|
||||||
|
_bbe = rg.Extrusion.Create(
|
||||||
|
_bbp, _bbh, True)
|
||||||
|
if _bbe is not None:
|
||||||
|
_bbb = _bbe.ToBrep()
|
||||||
|
if _bbb is not None and _bbb.IsValid:
|
||||||
|
_backbone_col_brep_3d = _bbb
|
||||||
|
except Exception: pass
|
||||||
|
# Through wand_volume objects per material
|
||||||
|
_th_objs_by_mat = {}
|
||||||
|
for _o in doc.Objects:
|
||||||
|
try:
|
||||||
|
if (_o.Attributes.GetUserString(_KEY_TYPE)
|
||||||
|
!= "wand_volume"): continue
|
||||||
|
if (_o.Attributes.GetUserString(_KEY_ID)
|
||||||
|
!= _toid): continue
|
||||||
|
_idx_s = _o.Attributes.GetUserString(
|
||||||
|
_KEY_WAND_LAYER_IDX) or ""
|
||||||
|
_lj = _o.Attributes.GetUserString(
|
||||||
|
_KEY_WAND_LAYERS) or ""
|
||||||
|
if not _idx_s or not _lj: continue
|
||||||
|
_tl = _j.loads(_lj)
|
||||||
|
_i = int(_idx_s)
|
||||||
|
if 0 <= _i < len(_tl):
|
||||||
|
_m = (_tl[_i].get("material") or "").strip()
|
||||||
|
if _m:
|
||||||
|
_th_objs_by_mat.setdefault(
|
||||||
|
_m, []).append(_o)
|
||||||
|
except Exception: pass
|
||||||
|
# 2D-Polylinen Union per Material:
|
||||||
|
# ALLE matching T-Stem-Columns nutzen die GLEICHE
|
||||||
|
# _my_axis_ext (= backbone-extended). Damit reichen
|
||||||
|
# ALLE Schichten gleich hoch ins Through (nicht nur
|
||||||
|
# Backbone) — Daemm + Putz visuell durchgehend.
|
||||||
|
# Carve verhindert Material-Konflikte: non-backbone
|
||||||
|
# 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
|
||||||
|
_is_backbone = (_mat == _backbone_mat)
|
||||||
|
# Cross-junction safe: nutze CURRENT through-Brep(s)
|
||||||
|
_layer_breps_3d = []
|
||||||
|
for _o in _th_objs_by_mat.get(_mat, []):
|
||||||
|
try:
|
||||||
|
_br_cur = _o.Geometry
|
||||||
|
if isinstance(_br_cur, rg.Brep) and _br_cur.IsValid:
|
||||||
|
_layer_breps_3d.append(
|
||||||
|
_br_cur.DuplicateBrep())
|
||||||
|
except Exception: pass
|
||||||
|
# Sammle T-Stem-Column Rects + Column-X-Range
|
||||||
|
# (= 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)
|
||||||
|
if _r is None: continue
|
||||||
|
try:
|
||||||
|
_profile = _r.DuplicateCurve()
|
||||||
|
if abs(uk) > 1e-9:
|
||||||
|
_profile.Transform(
|
||||||
|
rg.Transform.Translation(0, 0, uk))
|
||||||
|
_extr = rg.Extrusion.Create(
|
||||||
|
_profile, _height, True)
|
||||||
|
if _extr is None: continue
|
||||||
|
_br = _extr.ToBrep()
|
||||||
|
if _br is None or not _br.IsValid: continue
|
||||||
|
_column_breps.append(_br)
|
||||||
|
# Column x range fuer filter
|
||||||
|
_cbb = _br.GetBoundingBox(True)
|
||||||
|
_column_x_ranges.append(
|
||||||
|
(_cbb.Min.X, _cbb.Max.X))
|
||||||
|
_layer_breps_3d.append(_br)
|
||||||
|
except Exception as _ex:
|
||||||
|
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
|
||||||
|
# Carve mit backbone-col + hoehere-prio through-bands
|
||||||
|
if not _is_backbone:
|
||||||
|
_carve_breps = []
|
||||||
|
if _backbone_col_brep_3d is not None:
|
||||||
|
_carve_breps.append(_backbone_col_brep_3d)
|
||||||
|
_my_prio = _material_prio(_mat)
|
||||||
|
for _other_mat, _bands in _th_off_by_mat.items():
|
||||||
|
if _other_mat == _mat: continue
|
||||||
|
_other_prio = _material_prio(_other_mat)
|
||||||
|
if _other_prio <= _my_prio: continue
|
||||||
|
for (_obdl, _obdr) in _bands:
|
||||||
|
_ob_rect = _layer_rect_2d(
|
||||||
|
_th_geom, _obdl, _obdr)
|
||||||
|
if _ob_rect is None: continue
|
||||||
|
try:
|
||||||
|
_ob_profile = _ob_rect.DuplicateCurve()
|
||||||
|
if abs(uk) > 1e-9:
|
||||||
|
_ob_profile.Transform(
|
||||||
|
rg.Transform.Translation(
|
||||||
|
0, 0, uk))
|
||||||
|
_ob_extr = rg.Extrusion.Create(
|
||||||
|
_ob_profile, _height, True)
|
||||||
|
if _ob_extr is None: continue
|
||||||
|
_ob_br = _ob_extr.ToBrep()
|
||||||
|
if (_ob_br is not None
|
||||||
|
and _ob_br.IsValid):
|
||||||
|
_carve_breps.append(_ob_br)
|
||||||
|
except Exception: pass
|
||||||
|
if _carve_breps:
|
||||||
|
_current = list(_result_breps)
|
||||||
|
for _cb in _carve_breps:
|
||||||
|
_next = []
|
||||||
|
for _br in _current:
|
||||||
|
try:
|
||||||
|
_diff = rg.Brep.CreateBooleanDifference(
|
||||||
|
_br, _cb, 0.001)
|
||||||
|
except Exception:
|
||||||
|
_next.append(_br); continue
|
||||||
|
if (_diff is not None
|
||||||
|
and len(_diff) > 0):
|
||||||
|
for _db in _diff:
|
||||||
|
if (_db is not None
|
||||||
|
and _db.IsValid):
|
||||||
|
_next.append(_db)
|
||||||
|
else:
|
||||||
|
_next.append(_br)
|
||||||
|
_current = _next
|
||||||
|
_result_breps = _current
|
||||||
|
# (West-stub filter entfernt — war zu aggressiv,
|
||||||
|
# discarded legitime through-wall west pieces)
|
||||||
|
# 4. Validity + MergeCoplanarFaces
|
||||||
|
_result_breps = [_br for _br in _result_breps
|
||||||
|
if _br is not None and _br.IsValid]
|
||||||
|
for _br in _result_breps:
|
||||||
|
try: _br.MergeCoplanarFaces(0.01)
|
||||||
|
except Exception: pass
|
||||||
|
# Re-Extrude entfernt — war Bug-Quelle wo carve-
|
||||||
|
# holes verloren gingen. Direkter carved Brep ist
|
||||||
|
# OK (= 2 disconnected pieces nach BoolDifference).
|
||||||
|
if not _result_breps: continue
|
||||||
|
# Diagnostic: BBox y-range per result brep
|
||||||
|
for _bri, _br_diag in enumerate(_result_breps):
|
||||||
|
try:
|
||||||
|
_bb_d = _br_diag.GetBoundingBox(True)
|
||||||
|
print("[ELEMENTE] {} result[{}] y=[{:.3f}"
|
||||||
|
",{:.3f}]".format(
|
||||||
|
_mat, _bri, _bb_d.Min.Y, _bb_d.Max.Y))
|
||||||
|
except Exception: pass
|
||||||
|
# Replace through objects mit Extrude-Resultaten
|
||||||
|
_existing = _th_objs_by_mat.get(_mat, [])
|
||||||
|
for _ri in range(len(_result_breps)):
|
||||||
|
if _ri < len(_existing):
|
||||||
|
try:
|
||||||
|
doc.Objects.Replace(
|
||||||
|
_existing[_ri].Id, _result_breps[_ri])
|
||||||
|
except Exception as _ex:
|
||||||
|
print("[ELEMENTE] 2D Replace:", _ex)
|
||||||
|
else:
|
||||||
|
if _existing:
|
||||||
|
try:
|
||||||
|
_attrs = _existing[0].Attributes.Duplicate()
|
||||||
|
doc.Objects.AddBrep(
|
||||||
|
_result_breps[_ri], _attrs)
|
||||||
|
except Exception as _ex:
|
||||||
|
print("[ELEMENTE] 2D Add:", _ex)
|
||||||
|
for _ei in range(len(_result_breps), len(_existing)):
|
||||||
|
try: doc.Objects.Delete(
|
||||||
|
_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])
|
||||||
|
print("[ELEMENTE] 2D-Phase2: {} ({}) → {} brep(s)"
|
||||||
|
" aus {} cols".format(
|
||||||
|
_mat,
|
||||||
|
"backbone" if _is_backbone else "L-merge",
|
||||||
|
len(_result_breps), len(_column_breps)))
|
||||||
|
except Exception as _ex:
|
||||||
|
print("[ELEMENTE] T-Junction Phase2 2D:", _ex)
|
||||||
else:
|
else:
|
||||||
single_brep = _make_volume_geometry(
|
single_brep = _make_volume_geometry(
|
||||||
geom, meta["dicke"], uk, ok,
|
geom, meta["dicke"], uk, ok,
|
||||||
@@ -17843,6 +18319,13 @@ def _install_listeners(bridge):
|
|||||||
install_cluster_select_handler()
|
install_cluster_select_handler()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[ELEMENTE] cluster-select install:", ex)
|
print("[ELEMENTE] cluster-select install:", ex)
|
||||||
|
# Generic Vertex-Dots fuer Curves (Polylinen, Rectangles etc) —
|
||||||
|
# display-only, hilft beim visuellen Finden von Grip-Positionen.
|
||||||
|
try:
|
||||||
|
import curve_vertex_dots
|
||||||
|
curve_vertex_dots.install_curve_vertex_dots()
|
||||||
|
except Exception as ex:
|
||||||
|
print("[ELEMENTE] curve_vertex_dots install:", ex)
|
||||||
# Pre-Warm: native OpenNURBS-Libraries beim Plugin-Start laden um den
|
# Pre-Warm: native OpenNURBS-Libraries beim Plugin-Start laden um den
|
||||||
# First-Call-Lag zu vermeiden (User-Meldung: beim ersten Wand-Verbinden
|
# First-Call-Lag zu vermeiden (User-Meldung: beim ersten Wand-Verbinden
|
||||||
# haengt das UI kurz, danach nicht mehr → native code wird lazy-loaded).
|
# haengt das UI kurz, danach nicht mehr → native code wird lazy-loaded).
|
||||||
|
|||||||
@@ -349,6 +349,22 @@ _PROJECT_SETTINGS_DEFAULTS = {
|
|||||||
{"material": "Daemmung", "dicke": 0.10},
|
{"material": "Daemmung", "dicke": 0.10},
|
||||||
{"material": "Putz", "dicke": 0.02},
|
{"material": "Putz", "dicke": 0.02},
|
||||||
]},
|
]},
|
||||||
|
{"id": "style_innen_holz", "name": "Innenwand Holzstaender 14 cm",
|
||||||
|
"prio": 250, "dicke": 0.14, "referenz": "mid",
|
||||||
|
"layered": True, "material": "",
|
||||||
|
"layers": [
|
||||||
|
{"material": "Putz", "dicke": 0.015},
|
||||||
|
{"material": "Holzstaender", "dicke": 0.110},
|
||||||
|
{"material": "Putz", "dicke": 0.015},
|
||||||
|
]},
|
||||||
|
{"id": "style_innen_beton", "name": "Innenwand Beton 20 cm",
|
||||||
|
"prio": 700, "dicke": 0.23, "referenz": "mid",
|
||||||
|
"layered": True, "material": "",
|
||||||
|
"layers": [
|
||||||
|
{"material": "Putz", "dicke": 0.015},
|
||||||
|
{"material": "Stahlbeton", "dicke": 0.200},
|
||||||
|
{"material": "Putz", "dicke": 0.015},
|
||||||
|
]},
|
||||||
],
|
],
|
||||||
"project": {
|
"project": {
|
||||||
"name": "",
|
"name": "",
|
||||||
|
|||||||
@@ -151,6 +151,45 @@ def _assign_default_display_modes(doc):
|
|||||||
print("[STARTUP] view-modes: {} Viewport(s) gesetzt".format(n_set))
|
print("[STARTUP] view-modes: {} Viewport(s) gesetzt".format(n_set))
|
||||||
|
|
||||||
|
|
||||||
|
_DOC_FLAG_VIEW_MAXIMIZED = "dossier_top_view_maximized"
|
||||||
|
|
||||||
|
|
||||||
|
def _maximize_top_view(doc):
|
||||||
|
"""Maximiert den Top-Viewport (= einzige aktive View statt 4-Viewport-
|
||||||
|
Default). Persistiert Flag in doc.Strings → laeuft nur EINMAL pro Doc.
|
||||||
|
User-Overrides (manuelles Wechseln zu 4-View etc) bleiben erhalten."""
|
||||||
|
if doc is None: return
|
||||||
|
try:
|
||||||
|
if doc.Strings.GetValue(_DOC_FLAG_VIEW_MAXIMIZED) == "1":
|
||||||
|
return # schon initialisiert
|
||||||
|
except Exception: pass
|
||||||
|
try:
|
||||||
|
top_view = None
|
||||||
|
for view in doc.Views:
|
||||||
|
try:
|
||||||
|
vp = view.ActiveViewport
|
||||||
|
if vp is None: continue
|
||||||
|
if vp.Name == "Top":
|
||||||
|
top_view = view
|
||||||
|
break
|
||||||
|
except Exception: pass
|
||||||
|
if top_view is None:
|
||||||
|
print("[STARTUP] view-max: kein Top-Viewport gefunden")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
top_view.Maximized = True
|
||||||
|
doc.Views.ActiveView = top_view
|
||||||
|
doc.Views.Redraw()
|
||||||
|
print("[STARTUP] view-max: Top-Viewport maximiert")
|
||||||
|
except Exception as ex:
|
||||||
|
print("[STARTUP] view-max set:", ex); return
|
||||||
|
try:
|
||||||
|
doc.Strings.SetString(_DOC_FLAG_VIEW_MAXIMIZED, "1")
|
||||||
|
except Exception: pass
|
||||||
|
except Exception as ex:
|
||||||
|
print("[STARTUP] view-max:", ex)
|
||||||
|
|
||||||
|
|
||||||
_DOC_FLAG_UNIT_CHECKED = "dossier_unit_checked"
|
_DOC_FLAG_UNIT_CHECKED = "dossier_unit_checked"
|
||||||
|
|
||||||
|
|
||||||
@@ -227,6 +266,7 @@ def _on_doc_opened(sender, e):
|
|||||||
import panel_base
|
import panel_base
|
||||||
panel_base.migrate_to_dossier(doc)
|
panel_base.migrate_to_dossier(doc)
|
||||||
_assign_default_display_modes(doc)
|
_assign_default_display_modes(doc)
|
||||||
|
_maximize_top_view(doc)
|
||||||
_check_doc_unit(doc)
|
_check_doc_unit(doc)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[STARTUP] _on_doc_opened:", ex)
|
print("[STARTUP] _on_doc_opened:", ex)
|
||||||
@@ -301,6 +341,11 @@ def _load_all(sender, e):
|
|||||||
_pb._t_mark("post_init", "view_modes", _t_vm)
|
_pb._t_mark("post_init", "view_modes", _t_vm)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print("[STARTUP] view-modes assign:", ex)
|
print("[STARTUP] view-modes assign:", ex)
|
||||||
|
# Top-View maximieren (= einzige aktive View statt 4-View Default)
|
||||||
|
try:
|
||||||
|
_maximize_top_view(Rhino.RhinoDoc.ActiveDoc)
|
||||||
|
except Exception as ex:
|
||||||
|
print("[STARTUP] view-max:", ex)
|
||||||
# Unit-Check fuer das beim Start aktive Doc — fragt einmal pro Doc
|
# Unit-Check fuer das beim Start aktive Doc — fragt einmal pro Doc
|
||||||
# wenn doc.ModelUnitSystem != Project-Setting
|
# wenn doc.ModelUnitSystem != Project-Setting
|
||||||
_t_uc = _t.time()
|
_t_uc = _t.time()
|
||||||
|
|||||||
Reference in New Issue
Block a user