"""Patch (2026-10-14, Okseong P1 G2): optional inner ring / per-zone hoop radius / 90-135 ties in engine/rhino/column_rebar_ring.py and the matching optional checks in engine/rhino/checks/g2_ring.py. Absent options -> the Ulsan code paths are unchanged. Backups: backup/2026-10-14_okseong_g2/.""" import shutil from pathlib import Path APP = Path(__file__).resolve().parents[2] BK = APP / "backup" / "2026-10-14_okseong_g2" BK.mkdir(parents=True, exist_ok=True) B = APP / "engine" / "rhino" / "column_rebar_ring.py" C = APP / "engine" / "rhino" / "checks" / "g2_ring.py" for f in (B, C): if not (BK / f.name).exists(): shutil.copy2(f, BK / f.name) def patch(path, reps): s = path.read_text(encoding="utf-8") for a, b in reps: assert s.count(a) == 1, (path.name, a[:60]) s = s.replace(a, b) path.write_text(s, encoding="utf-8") patch(B, [ ('''includes the coupler). """''', '''includes the coupler). Optional (Okseong P1, 2026-10-14; absent -> unchanged): main.inner {mark, radius} second ring at the same angles with the hook bent inwards; zone.radius per hoop zone; ties.shape "90-135" {leg, tail, y_lift, design_length}: straight on the X / Y diameters, 90-deg leg down at one end, 135-deg hook at the other, ends swapped per level, the Y tie y_lift above the X tie. """'''), (''' f"{PID}-FT,{PID}-CP", f"angle {th:.4f} deg; 배근도(1) 정면도 갈고리 R{R:.0f}·{m['hook_leg']:.0f}; 단면 DIVISION {m['count']}") n += 1''', ''' f"{PID}-FT,{PID}-CP", f"angle {th:.4f} deg; 배근도(1) 정면도 갈고리 R{R:.0f}·{m['hook_leg']:.0f}; 단면 DIVISION {m['count']}") n += 1 if "inner" in m: # second ring, hook bent inwards (Okseong P1) ri = m["inner"]["radius"] z0 = m["hook_leg_z"] leg = rg.LineCurve(pt(ri - R - m["hook_leg"], th, z0), pt(ri - R, th, z0)) bend = rg.ArcCurve(rg.Arc(pt(ri - R, th, z0), pt(ri - R + R * math.sin(math.radians(45)), th, z0 + R - R * math.cos(math.radians(45))), pt(ri, th, z0 + R))) vert = rg.LineCurve(pt(ri, th, z0 + R), pt(ri, th, top)) c = rg.Curve.JoinCurves([leg, bend, vert], TOL)[0] add_bar(c, m["inner"]["mark"], f"{i + 1:02d}", m["straight_len"] + math.pi / 2 * R + m["hook_leg"], f"{PID}-FT,{PID}-CP", f"angle {th:.4f} deg; inner ring r {ri:.0f}, hook inwards") n += 1'''), (''' mark, typ = zone["mark"], zone["type"] for j, z in enumerate(zone["levels"]):''', ''' mark, typ = zone["mark"], zone["type"] r = zone.get("radius", hp["radius"]) # per zone (Okseong P3 1,150 / P4 1,147) for j, z in enumerate(zone["levels"]):'''), (''' tie = cr["ties"] half = tie["straight"] / 2''', ''' tie = cr["ties"] if tie.get("shape") == "90-135": # Okseong P1 (2026-10-14), as column_rebar_straight.py rb = min_bend_radius(MARK[tie["mark"]][0]) for q, z in enumerate(tie["levels"]): for axis, name in ((0, "X"), (1, "Y")): zt = z + (tie["y_lift"] if axis else 0) u = rg.Vector3d(1, 0, 0) if axis == 0 else rg.Vector3d(0, 1, 0) s = -1 if q % 2 else 1 a = rg.Point3d(0, 0, zt) - u * (s * tie["straight"] / 2) b = rg.Point3d(0, 0, zt) + u * (s * tie["straight"] / 2) d = -u * s * math.cos(math.radians(45)) + rg.Vector3d(0, 0, -1) * math.sin(math.radians(45)) pl = rg.PolylineCurve([a + rg.Vector3d(0, 0, -tie["leg"]), a, b, b + d * tie["tail"]]) c = rg.Curve.CreateFilletCornersCurve(pl, rb, TOL, math.radians(1)) or pl add_bar(c, tie["mark"], f"L{q + 1:02d}{name}", tie["design_length"], hosts(zt), f"EL {zt / 1000:.3f}; {name} (Y +{tie['y_lift']}, 90/135 교번 [추론])") n += 1 doc.Views.Redraw() print(json.dumps({"bars": n})) return half = tie["straight"] / 2'''), ]) patch(C, [ (''' check(f"{m} radius (mid point)", G["r_hoop"], math.hypot(p.X, p.Y), tol=0.5)''', ''' check(f"{m} radius (mid point)", G.get("r_hoop_by_mark", {}).get(m, G["r_hoop"]), math.hypot(p.X, p.Y), tol=0.5) if "inner" in M: # Okseong P1: inner ring (2026-10-14) im = M["inner"] tops = [max((o.Geometry.PointAtStart, o.Geometry.PointAtEnd), key=lambda q: q.Z) for o in cl[im["mark"]]] lows = [min((o.Geometry.PointAtStart, o.Geometry.PointAtEnd), key=lambda q: q.Z) for o in cl[im["mark"]]] check(f"{im['mark']} inner ring radius (worst)", 0, max(abs(math.hypot(q.X, q.Y) - im["radius"]) for q in tops) if tops else 1e9, tol=0.5) flag(f"{im['mark']} hooks bent inwards", bool(lows) and all(math.hypot(q.X, q.Y) < im["radius"] for q in lows))'''), (''' tz = {} for o in cl[tie_mark]: tz.setdefault(round(o.Geometry.GetBoundingBox(True).Max.Z, 1), []).append(o) lv = sorted(tz) flag(f"tie levels {len(TIE_LEVELS)} x {TIE['per_level']}", len(lv) == len(TIE_LEVELS) and all(len(v) == TIE["per_level"] for v in tz.values()), f"{len(lv)} levels") check(f"tie levels vs chain + {TIE['offset']} (worst)", 0, max(abs(a - (b + TIE["offset"])) for a, b in zip(lv, TIE_LEVELS)) if lv else 1e9)''', ''' if TIE.get("y_lift"): # Okseong P1: X tie at the level, Y tie lifted occ_ = lambda o: o.Attributes.GetUserString("occurrence") lx = sorted(round(o.Geometry.GetBoundingBox(True).Max.Z, 1) for o in cl[tie_mark] if occ_(o).endswith("X")) ly = sorted(round(o.Geometry.GetBoundingBox(True).Max.Z, 1) for o in cl[tie_mark] if occ_(o).endswith("Y")) flag(f"tie X / Y levels {len(TIE_LEVELS)} each", len(lx) == len(ly) == len(TIE_LEVELS), f"X {len(lx)} Y {len(ly)}") check("tie X levels vs chain (worst)", 0, max(abs(a - b - TIE["offset"]) for a, b in zip(lx, TIE_LEVELS)) if lx else 1e9) check(f"tie Y = X + {TIE['y_lift']} (worst)", 0, max(abs(b - a - TIE["y_lift"]) for a, b in zip(lx, ly)) if ly else 1e9) else: tz = {} for o in cl[tie_mark]: tz.setdefault(round(o.Geometry.GetBoundingBox(True).Max.Z, 1), []).append(o) lv = sorted(tz) flag(f"tie levels {len(TIE_LEVELS)} x {TIE['per_level']}", len(lv) == len(TIE_LEVELS) and all(len(v) == TIE["per_level"] for v in tz.values()), f"{len(lv)} levels") check(f"tie levels vs chain + {TIE['offset']} (worst)", 0, max(abs(a - (b + TIE["offset"])) for a, b in zip(lv, TIE_LEVELS)) if lv else 1e9)'''), ]) print("patched")