Treppe L+Wendel: Lauflinie, Aussen-Polygon, Pfeil-voll, Lock + Cmd+Z
Cmd+Z: - _update_wall wrapped in BeginUndoRecord/EndUndoRecord — sodass Property-Patches + Regen-Delete/Add als ein Undo-Schritt rueckgaengig L-Treppe Aussenlinie: - _aussen_l_polygon: sauberes 6-Punkt L-Polygon mit korrekt projizierten Ecken (Outer + Inner via Linien-Schnitt der mid-versetzten Seiten) - _aussen_l: nutzt Polygon wenn kein Cut, faellt sonst zurueck auf per-Lauf Rechtecke mit Diagonal-Cut (wie bisher) L-Treppe Lauflinie: - ueber BEIDE Laeufe, mid-perp versetzt, Pfeil am Treppen-Ende - Eck-Mitte als Linien-Schnitt der zwei versetzten Schaft-Linien → sauberer Übergang auch bei Lage=links/rechts (kein Versatz an der Ecke) - 'voll'-Pfeil-Style mit wide-Offsets relativ zur Lauflinie Wendel-Treppe: - _lauflinie_wendel: 'voll'-Pfeil-Style mit r_inner/r_outer-Spitzen relativ zu r_mid (Radial-Offsets) Trittmass-Lock auf alle Treppen-Arten: - L: Beide Laeufe proportional skalieren (ratio = N*target_A / (L1+L2)) - Wendel: Sweep-Winkel anpassen (new_delta = sign × N*target_A/r_mid) - Axis-Geometrie wird in-place via Replace ausgetauscht — Source moves fliessen in regulären Regen-Pfad ein
This commit is contained in:
+186
-21
@@ -6374,36 +6374,115 @@ def _tritte_l(axis_polyline, breite, referenz, n_stufen, z,
|
||||
|
||||
def _lauflinie_l(axis_polyline, n_stufen, z, arrow_style="klassisch", doc=None,
|
||||
breite=None, referenz=None):
|
||||
"""Lauflinie ueber BEIDE Laeufe einer L-Treppe. Schaft 1 vom Start zur
|
||||
PROJEZIERTEN Eck-Mitte (Schnittpunkt der zwei mid-versetzten Linien),
|
||||
Schaft 2 von Eck-Mitte zum Ende mit Pfeil. So sauberer Übergang ohne
|
||||
Versatz an der Ecke — auch bei Lage=links/rechts."""
|
||||
segs = _l_segments(axis_polyline, n_stufen, z)
|
||||
if segs is None: return []
|
||||
s1, _s2, N1, _N2 = segs
|
||||
lo, _up = _lauflinie_gerade(s1, N1, z, -1, False, arrow_style, doc,
|
||||
breite, referenz)
|
||||
return lo
|
||||
s1, s2, _N1, _N2 = segs
|
||||
P0a = s1.PointAtStart; P1a = s1.PointAtEnd
|
||||
txa, tya = P1a.X - P0a.X, P1a.Y - P0a.Y
|
||||
La = (txa * txa + tya * tya) ** 0.5
|
||||
P0b = s2.PointAtStart; P1b = s2.PointAtEnd
|
||||
txb, tyb = P1b.X - P0b.X, P1b.Y - P0b.Y
|
||||
Lb = (txb * txb + tyb * tyb) ** 0.5
|
||||
if La < 1e-6 or Lb < 1e-6: return []
|
||||
uxa, uya = txa / La, tya / La
|
||||
uxb, uyb = txb / Lb, tyb / Lb
|
||||
# Mid-offset relativ zur Axis
|
||||
mid_off = 0.0
|
||||
wide_l = wide_r = None
|
||||
if breite is not None and referenz is not None:
|
||||
off_l, off_r = _treppe_2d_side_offsets(breite, referenz)
|
||||
mid_off = (off_l + off_r) * 0.5
|
||||
wide_l = off_l - mid_off
|
||||
wide_r = off_r - mid_off
|
||||
# perp 90° CCW
|
||||
pa_x, pa_y = -uya * mid_off, uxa * mid_off
|
||||
pb_x, pb_y = -uyb * mid_off, uxb * mid_off
|
||||
P0a_s = rg.Point3d(P0a.X + pa_x, P0a.Y + pa_y, z)
|
||||
P1a_s = rg.Point3d(P1a.X + pa_x, P1a.Y + pa_y, z)
|
||||
P0b_s = rg.Point3d(P0b.X + pb_x, P0b.Y + pb_y, z)
|
||||
P1b_s = rg.Point3d(P1b.X + pb_x, P1b.Y + pb_y, z)
|
||||
# Echte Eck-Mitte als Schnittpunkt der zwei versetzten Schaft-Linien
|
||||
if abs(mid_off) > 1e-6:
|
||||
meet = _line_intersect_xy(P0a_s, rg.Vector3d(uxa, uya, 0),
|
||||
P1b_s, rg.Vector3d(uxb, uyb, 0))
|
||||
if meet is None: meet = P1a_s # Fallback (parallel oder collinear)
|
||||
else:
|
||||
meet = P1a_s # = P0b_s wenn mid=0
|
||||
out = []
|
||||
out.append(rg.LineCurve(P0a_s, meet))
|
||||
head_size = min(0.25, Lb * 0.05)
|
||||
out.extend(_treppe_2d_arrow_curves(meet, P1b_s, head_size, arrow_style,
|
||||
doc, wide_l, wide_r))
|
||||
return out
|
||||
|
||||
|
||||
def _aussen_l_polygon(axis_polyline, breite, referenz, z):
|
||||
"""Sauberes L-Polygon: 6-Punkt-Outline, die beiden Laeufe schliessen
|
||||
sich an der Ecke an (Outer/Inner via Linien-Schnitt). Returnt eine
|
||||
Polyline ODER None bei Singularitaet (parallele Schenkel)."""
|
||||
ok, poly = axis_polyline.TryGetPolyline()
|
||||
if not ok or poly is None or poly.Count != 3: return None
|
||||
p0 = rg.Point3d(poly[0].X, poly[0].Y, z)
|
||||
pc = rg.Point3d(poly[1].X, poly[1].Y, z)
|
||||
p1 = rg.Point3d(poly[2].X, poly[2].Y, z)
|
||||
L1 = ((pc.X - p0.X) ** 2 + (pc.Y - p0.Y) ** 2) ** 0.5
|
||||
L2 = ((p1.X - pc.X) ** 2 + (p1.Y - pc.Y) ** 2) ** 0.5
|
||||
if L1 < 1e-6 or L2 < 1e-6: return None
|
||||
u1 = rg.Vector3d((pc.X - p0.X) / L1, (pc.Y - p0.Y) / L1, 0)
|
||||
u2 = rg.Vector3d((p1.X - pc.X) / L2, (p1.Y - pc.Y) / L2, 0)
|
||||
# perp 90° CCW
|
||||
pp1 = (-u1.Y, u1.X)
|
||||
pp2 = (-u2.Y, u2.X)
|
||||
off_l, off_r = _treppe_2d_side_offsets(breite, referenz)
|
||||
def _off(pt, perp, d):
|
||||
return rg.Point3d(pt.X + perp[0] * d, pt.Y + perp[1] * d, z)
|
||||
# Lauf1 Seiten (start + corner)
|
||||
p0_l = _off(p0, pp1, off_l); p0_r = _off(p0, pp1, off_r)
|
||||
pc_l1 = _off(pc, pp1, off_l); pc_r1 = _off(pc, pp1, off_r)
|
||||
# Lauf2 Seiten (corner + end)
|
||||
pc_l2 = _off(pc, pp2, off_l); pc_r2 = _off(pc, pp2, off_r)
|
||||
p1_l = _off(p1, pp2, off_l); p1_r = _off(p1, pp2, off_r)
|
||||
# Ecken-Schnitte: linker Schenkel intersect, rechter Schenkel intersect
|
||||
corner_l = _line_intersect_xy(p0_l, u1, p1_l, u2) or pc_l1
|
||||
corner_r = _line_intersect_xy(p0_r, u1, p1_r, u2) or pc_r1
|
||||
pts = [p0_l, p0_r, corner_r, p1_r, p1_l, corner_l, p0_l]
|
||||
return rg.PolylineCurve(rg.Polyline(pts))
|
||||
|
||||
|
||||
def _aussen_l(axis_polyline, breite, referenz, z,
|
||||
total_h=0.0, cut_h=0.0, n_stufen=15):
|
||||
"""Returnt (lower, upper). Bei Cut: betroffener Lauf wird gesplittet,
|
||||
anderer Lauf komplett lower bzw. upper."""
|
||||
"""Returnt (lower, upper). Ohne Cut: sauberes L-Polygon (eine Outline).
|
||||
Mit Cut: faellt zurueck auf Per-Lauf-Rechtecke mit Diagonal-Cut auf der
|
||||
betroffenen Seite — der jeweils andere Lauf wird komplett lower bzw.
|
||||
upper (= gestrichelt wenn oben)."""
|
||||
segs = _l_segments(axis_polyline, n_stufen, z)
|
||||
if segs is None: return [], []
|
||||
s1, s2, N1, N2 = segs
|
||||
H1 = total_h * (N1 / float(N1 + N2)) if total_h > 0 else 0.0
|
||||
H2 = total_h - H1
|
||||
cut1 = _cut_idx_gerade(N1, H1, cut_h) if total_h > 0 else -1
|
||||
cut2 = _cut_idx_gerade(N2, H2, cut_h - H1) if total_h > 0 else -1
|
||||
# Ohne Cut: zeichne sauberes L-Polygon
|
||||
if cut1 < 0 and cut2 < 0:
|
||||
poly = _aussen_l_polygon(axis_polyline, breite, referenz, z)
|
||||
if poly is not None:
|
||||
return [poly], []
|
||||
# Fallback: zwei Rechtecke
|
||||
l1, _ = _aussen_gerade(s1, breite, referenz, z, -1, None)
|
||||
l2, _ = _aussen_gerade(s2, breite, referenz, z, -1, None)
|
||||
return l1 + l2, []
|
||||
# Mit Cut: per-Lauf, betroffener Lauf wird diagonal gesplittet
|
||||
if cut1 >= 0:
|
||||
l1, u1 = _aussen_gerade(s1, breite, referenz, z, cut1, N1)
|
||||
l2, _ = _aussen_gerade(s2, breite, referenz, z, -1, None)
|
||||
return l1, u1 + l2
|
||||
cut2 = _cut_idx_gerade(N2, H2, cut_h - H1) if total_h > 0 else -1
|
||||
if cut2 >= 0:
|
||||
l1, _ = _aussen_gerade(s1, breite, referenz, z, -1, None)
|
||||
l2, u2 = _aussen_gerade(s2, breite, referenz, z, cut2, N2)
|
||||
return l1 + l2, u2
|
||||
l1, _ = _aussen_gerade(s1, breite, referenz, z, -1, None)
|
||||
l2, _ = _aussen_gerade(s2, breite, referenz, z, -1, None)
|
||||
return l1 + l2, []
|
||||
l2, u2 = _aussen_gerade(s2, breite, referenz, z, cut2, N2)
|
||||
return l1 + l2, u2
|
||||
|
||||
|
||||
def _bruch_l(axis_polyline, breite, referenz, n_stufen, total_h, cut_h, z):
|
||||
@@ -6487,7 +6566,13 @@ def _lauflinie_wendel(axis_polyline, breite, referenz, n_stufen, z,
|
||||
head_len = min(0.25, abs(r_mid * da) * 0.6)
|
||||
tail = rg.Point3d(arc_end.X - tang_x * head_len * 0.3,
|
||||
arc_end.Y - tang_y * head_len * 0.3, z)
|
||||
out.extend(_treppe_2d_arrow_curves(tail, arc_end, head_len, arrow_style, doc))
|
||||
# 'voll'-Style: Spitzen-Offsets relativ zum r_mid (Tangenten-perp =
|
||||
# radial). Innen → negativ, Aussen → positiv (in perp-Richtung
|
||||
# CCW vom Tangenten-Vektor — entspricht der Radial-Richtung).
|
||||
wide_in = r_inner - r_mid # negativ
|
||||
wide_out = r_outer - r_mid # positiv
|
||||
out.extend(_treppe_2d_arrow_curves(tail, arc_end, head_len, arrow_style,
|
||||
doc, wide_in, wide_out))
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] _lauflinie_wendel:", ex)
|
||||
return out
|
||||
@@ -11907,12 +11992,30 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
|
||||
def _update_wall(self, p):
|
||||
"""Properties eines Elements aendern (Wand/Decke/Dach/Oeffnung).
|
||||
Volumen wird anschliessend regeneriert."""
|
||||
Volumen wird anschliessend regeneriert.
|
||||
Wird in EINEM Undo-Record zusammengefasst, sodass Cmd+Z die ganze
|
||||
Patch-Operation inkl. Regen rueckgaengig macht (statt nur den
|
||||
letzten AddBrep)."""
|
||||
doc = Rhino.RhinoDoc.ActiveDoc
|
||||
wall_id = p.get("id")
|
||||
if not wall_id: return
|
||||
axis_obj, old_meta = _find_source(doc, wall_id)
|
||||
if axis_obj is None or old_meta is None: return
|
||||
# Undo-Record umschliesst Source-Mods + Regen-Delete/Add
|
||||
_ur_serial = None
|
||||
try:
|
||||
if doc is not None:
|
||||
_ur_serial = doc.BeginUndoRecord("Dossier: Element-Update")
|
||||
except Exception: _ur_serial = None
|
||||
try:
|
||||
return self._update_wall_body(doc, p, wall_id, axis_obj, old_meta)
|
||||
finally:
|
||||
if _ur_serial is not None:
|
||||
try: doc.EndUndoRecord(_ur_serial)
|
||||
except Exception: pass
|
||||
|
||||
def _update_wall_body(self, doc, p, wall_id, axis_obj, old_meta):
|
||||
"""Eigentliche Patch-Logik. Sieh _update_wall fuer Undo-Wrapping."""
|
||||
# Tragwerk: Stuetze (Punkt) oder Traeger/Unterzug (Achse)
|
||||
if old_meta["type"] in ("stuetze_point", "traeger_axis"):
|
||||
kind = p.get("kind", old_meta.get("trag_kind", "stuetze"))
|
||||
@@ -12234,26 +12337,88 @@ class ElementeBridge(panel_base.BaseBridge):
|
||||
if H_now > 0.1:
|
||||
tn = max(2, int(round(H_now / ttarget_s)))
|
||||
# Axis-Laenge so anpassen dass A = target_A bleibt
|
||||
# (nur fuer gerade Treppe = LineCurve)
|
||||
if ttarget_a > 0.05:
|
||||
new_L = tn * ttarget_a
|
||||
new_total_L = tn * ttarget_a
|
||||
tart_cur = old_meta.get("treppe_art", "gerade")
|
||||
try:
|
||||
_g = axis_obj.Geometry
|
||||
# ---- GERADE: LineCurve P1 verschieben
|
||||
if (isinstance(_g, rg.LineCurve)
|
||||
and abs(cur_axis_L - new_L) > 1e-4):
|
||||
and abs(cur_axis_L - new_total_L) > 1e-4):
|
||||
P0 = _g.PointAtStart
|
||||
P1 = _g.PointAtEnd
|
||||
dx = P1.X - P0.X; dy = P1.Y - P0.Y
|
||||
if cur_axis_L > 1e-6:
|
||||
ux = dx / cur_axis_L
|
||||
uy = dy / cur_axis_L
|
||||
P1n = rg.Point3d(P0.X + ux * new_L,
|
||||
P0.Y + uy * new_L, P0.Z)
|
||||
P1n = rg.Point3d(P0.X + ux * new_total_L,
|
||||
P0.Y + uy * new_total_L, P0.Z)
|
||||
new_line = rg.LineCurve(P0, P1n)
|
||||
doc.Objects.Replace(axis_obj.Id, new_line)
|
||||
# axis_obj-Reference neu holen
|
||||
axis_obj = doc.Objects.Find(axis_obj.Id)
|
||||
attrs = axis_obj.Attributes
|
||||
# ---- L: PolylineCurve mit 3 Punkten,
|
||||
# beide Laeufe proportional skalieren
|
||||
elif (isinstance(_g, rg.PolylineCurve) and tart_cur == "l"):
|
||||
ok_pl, poly = _g.TryGetPolyline()
|
||||
if ok_pl and poly is not None and poly.Count == 3:
|
||||
p0 = poly[0]; pc = poly[1]; p1 = poly[2]
|
||||
L1c = ((pc.X-p0.X)**2 + (pc.Y-p0.Y)**2) ** 0.5
|
||||
L2c = ((p1.X-pc.X)**2 + (p1.Y-pc.Y)**2) ** 0.5
|
||||
cur_total = L1c + L2c
|
||||
if cur_total > 1e-6:
|
||||
ratio = new_total_L / cur_total
|
||||
if abs(ratio - 1.0) > 1e-4:
|
||||
# Lauf 1 skalieren — pc neu setzen
|
||||
u1x = (pc.X-p0.X)/L1c
|
||||
u1y = (pc.Y-p0.Y)/L1c
|
||||
new_L1 = L1c * ratio
|
||||
pc_n = rg.Point3d(
|
||||
p0.X + u1x*new_L1,
|
||||
p0.Y + u1y*new_L1, p0.Z)
|
||||
# Lauf 2 skalieren — p1 neu setzen
|
||||
u2x = (p1.X-pc.X)/L2c
|
||||
u2y = (p1.Y-pc.Y)/L2c
|
||||
new_L2 = L2c * ratio
|
||||
p1_n = rg.Point3d(
|
||||
pc_n.X + u2x*new_L2,
|
||||
pc_n.Y + u2y*new_L2, p1.Z)
|
||||
new_poly = rg.PolylineCurve(
|
||||
rg.Polyline([p0, pc_n, p1_n]))
|
||||
doc.Objects.Replace(axis_obj.Id, new_poly)
|
||||
axis_obj = doc.Objects.Find(axis_obj.Id)
|
||||
attrs = axis_obj.Attributes
|
||||
# ---- WENDEL: PolylineCurve [center, start, end]
|
||||
# → Sweep-Winkel anpassen damit Bogenlaenge =
|
||||
# new_total_L (Bogenlaenge = r_mid * |delta|)
|
||||
elif (isinstance(_g, rg.PolylineCurve) and tart_cur == "wendel"):
|
||||
import math as _m
|
||||
ok_pl, poly = _g.TryGetPolyline()
|
||||
if ok_pl and poly is not None and poly.Count == 3:
|
||||
center = poly[0]; pstart = poly[1]; pend = poly[2]
|
||||
r_click = _m.hypot(pstart.X-center.X,
|
||||
pstart.Y-center.Y)
|
||||
if r_click > 0.2:
|
||||
r_inner_l, r_outer_l = _wendel_radii(
|
||||
r_click, tb, tref)
|
||||
r_mid = (r_inner_l + r_outer_l) * 0.5
|
||||
a_start_cur, delta_cur = _wendel_sweep(
|
||||
center, pstart, pend)
|
||||
if abs(delta_cur) > 0.01 and r_mid > 1e-6:
|
||||
cur_arc = abs(r_mid * delta_cur)
|
||||
if abs(cur_arc - new_total_L) > 1e-4:
|
||||
sign = 1.0 if delta_cur > 0 else -1.0
|
||||
new_delta = sign * (new_total_L / r_mid)
|
||||
a_end_new = a_start_cur + new_delta
|
||||
pend_n = rg.Point3d(
|
||||
center.X + r_click * _m.cos(a_end_new),
|
||||
center.Y + r_click * _m.sin(a_end_new),
|
||||
pend.Z)
|
||||
new_poly = rg.PolylineCurve(
|
||||
rg.Polyline([center, pstart, pend_n]))
|
||||
doc.Objects.Replace(axis_obj.Id, new_poly)
|
||||
axis_obj = doc.Objects.Find(axis_obj.Id)
|
||||
attrs = axis_obj.Attributes
|
||||
except Exception as ex:
|
||||
print("[ELEMENTE] lock axis-len adjust:", ex)
|
||||
print("[ELEMENTE] Lock: H={:.3f}/S={:.3f}→n={}, A={:.3f}".format(
|
||||
|
||||
Reference in New Issue
Block a user