dJoin: T-Join Ergaenzung — Endpunkt einer Wand mitten auf andere snappen

Bisher konnte dJoin nur L-Verbindungen herstellen (zwei Endpunkte zum
Schnittpunkt der verlaengerten Tangenten ziehen). Neu auch T-Verbindungen:

_t_join_attempt: pro Endpunkt-Kombination wird der naechste Punkt auf der
ANDEREN Curve gesucht. Wenn distance < 20cm UND nicht nahe deren Endpunkt
(= waere L-Sache) → snap diesen Endpunkt exakt auf die Curve. Die andere
Curve bleibt unveraendert (= Through-Wand stays).

_run: T-Join wird ZUERST probiert (spezifischer), L-Join als Fallback.

UX: User selektiert 2 Waende die fast aber nicht ganz verbinden →
Cmd+J (dJoin) → System erkennt T- oder L-Konfig und snappt entsprechend.
Predictable + intentional, kein auto-snap-Magic mehr.
This commit is contained in:
2026-05-31 11:45:53 +02:00
parent e9e727c66f
commit 3609236da9
+63 -3
View File
@@ -124,6 +124,58 @@ def _walls_and_curves_from_sel(doc, sel):
return axes, generic
def _t_join_attempt(doc, sel):
"""T-Join: 2 OFFENE Kurven wobei der EINE Endpunkt der einen Kurve
nahe (< 20cm) auf der ANDEREN Kurve mitten landet (zwischen deren
Endpunkten). Schiebt diesen Endpunkt exakt auf die andere Kurve.
Die andere Kurve bleibt unveraendert.
Liefert True wenn ausgefuehrt."""
axes, generic = _walls_and_curves_from_sel(doc, sel)
if len(axes) == 2 and len(generic) == 0:
o1, o2 = axes[0], axes[1]
elif len(axes) == 0 and len(generic) == 2:
o1, o2 = generic[0], generic[1]
else:
return False
c1 = o1.Geometry; c2 = o2.Geometry
if not (isinstance(c1, rg.Curve) and isinstance(c2, rg.Curve)):
return False
if c1.IsClosed or c2.IsClosed: return False
tol_snap = 0.20 # 20 cm Snap-Radius fuer T-Verbindung
end_tol = 0.05 # 5cm: wenn closest-point nahe Endpunkt → eigentlich L
candidates = []
# Pro Endpunkt der einen Kurve: ClosestPoint auf der ANDEREN Kurve
for (a_obj, ac, b_obj, bc) in ((o1, c1, o2, c2), (o2, c2, o1, c1)):
for end in (0, 1):
ep = ac.PointAtStart if end == 0 else ac.PointAtEnd
try:
rc, t = bc.ClosestPoint(ep)
if not rc: continue
cp = bc.PointAt(t)
d = cp.DistanceTo(ep)
# Skip wenn schon snapped oder zu weit
if d < 1e-6 or d > tol_snap: continue
# Skip wenn cp nahe einem Endpunkt von bc — das ist L-Join Territory
ps = bc.PointAtStart; pe = bc.PointAtEnd
if cp.DistanceTo(ps) < end_tol or cp.DistanceTo(pe) < end_tol:
continue
candidates.append((d, a_obj, ac, end, cp))
except Exception: continue
if not candidates: return False
# Naechster Endpunkt → der wird gesnappt
candidates.sort(key=lambda x: x[0])
_d, a_obj, ac, end, cp = candidates[0]
new_c = _replace_curve_endpoint(ac, end, cp)
if new_c is None: return False
ur = doc.BeginUndoRecord("DOSSIER T-Join")
try:
ok = doc.Objects.Replace(a_obj.Id, new_c)
return bool(ok)
finally:
doc.EndUndoRecord(ur)
def _l_join_attempt(doc, sel):
"""Wenn genau 2 OFFENE Kurven (Wand-Achsen oder generische Lines)
selektiert sind, deren End-Tangenten sich in einem Punkt schneiden →
@@ -187,9 +239,17 @@ def _run():
if not sel:
Rhino.RhinoApp.RunScript("_Join", False); return
# L-Join: genau 2 offene Kurven die sich (verlaengert) treffen wuerden.
# Walls werden via ihrer Achse automatisch regenert (Replace-Listener).
if len(sel) == 2:
# T-Join: Endpunkt der einen Curve trifft mitten auf die andere → snap.
# L-Join: beide Endpunkte werden zum Schnittpunkt der verlaengerten Linien
# gezogen. T zuerst probieren (= spezifischer), dann L als Fallback.
if len(sel) >= 2:
try:
if _t_join_attempt(doc, sel):
doc.Views.Redraw()
print("[SMART-JOIN] T-Join: Endpunkt auf Achse gesnappt")
return
except Exception as ex:
print("[SMART-JOIN] T-Join error:", ex)
try:
if _l_join_attempt(doc, sel):
doc.Views.Redraw()