"""Stage 4: column rebar P1-P6 (centreline + pipe solid) from params.json `column_rebar`. Run inside Rhino 8 after build_rhino.py: exec(open(, encoding="utf-8").read(), {"__rhino_doc__": doc, "PARAMS_PATH": }) Rules: 설계서 v0.1.04 §5 (HITL H1-H3 approved 2026-10-07). Units mm, Z = EL x 1000. Each centreline carries `design_length` (schedule-style original length: straight + arc lengths before hook fillets) and the actual curve length is measured separately (box rule: 원길이 vs 곡선길이). """ import json import math import Rhino import Rhino.Geometry as rg from Rhino.DocObjects import ObjectAttributes from System.Drawing import Color P = json.load(open(PARAMS_PATH, encoding="utf-8")) doc = __rhino_doc__ TOL = 0.01 DIA = {"S32": 32.0, "H19": 19.0, "H16": 16.0} MARK_DIA = {"P1": "S32", "P2": "S32", "P3": "H19", "P4": "H19", "P5": "H19", "P6": "H16"} COLORS = {"P1": (200, 40, 40), "P2": (230, 120, 40), "P3": (40, 120, 200), "P4": (40, 170, 170), "P5": (90, 90, 220), "P6": (140, 60, 160)} def layer(path, color): idx = doc.Layers.FindByFullPath(path, -1) if idx >= 0: return idx parent = -1 parts = path.split("::") for i in range(1, len(parts) + 1): sub = "::".join(parts[:i]) idx = doc.Layers.FindByFullPath(sub, -1) if idx < 0: lay = Rhino.DocObjects.Layer() lay.Name, lay.Color = parts[i - 1], Color.FromArgb(*color) if parent >= 0: lay.ParentLayerId = doc.Layers[parent].Id idx = doc.Layers.Add(lay) parent = idx return parent def min_bend_radius(dia_name): d = DIA[dia_name] inner = 2 * d if d <= 16 else 3.5 * d # box project rule (REBAR_BENDS_20260914) return (inner + d) / 2 def add_bar(curve, mark, occ, design_len, hosts, src, coupler=0.0): d = DIA[MARK_DIA[mark]] # fitRail=False: fitRail=True fails on line-arc-line rails and crashed a headless Rhino (2026-10-07) pipes = rg.Brep.CreatePipe(curve, d / 2, False, rg.PipeCapMode.Flat, False, TOL, math.radians(1)) if not pipes or not pipes[0].IsSolid: raise RuntimeError(f"{mark} {occ}: pipe failed") common = dict(mark=mark, dia=MARK_DIA[mark], shape=P["column_rebar"]["schedule"][mark]["shape"], occurrence=occ, host="P1-CL", anchor_hosts=hosts, design_length=f"{design_len:.3f}", coupler_length=f"{coupler:.3f}", src=src) for geo, lay, prefix in ((curve, layer("P1::철근::중심선", (60, 60, 60)), "P1-RC"), (pipes[0], layer(f"P1::철근::기둥::{mark}", COLORS[mark]), "P1-R")): attr = ObjectAttributes() attr.LayerIndex = lay oid = f"{prefix}-{mark}-{occ}" attr.Name = oid attr.SetUserString("id", oid) for k, v in common.items(): attr.SetUserString(k, v) if isinstance(geo, rg.Brep): doc.Objects.AddBrep(geo, attr) else: doc.Objects.AddCurve(geo, attr) def pt(r, deg, z): t = math.radians(deg) return rg.Point3d(r * math.cos(t), r * math.sin(t), z) def arc_xy(r, a0, a1, z): """Horizontal arc at height z from angle a0 to a1 (deg, counter-clockwise).""" am = (a0 + a1) / 2 return rg.ArcCurve(rg.Arc(pt(r, a0, z), pt(r, am, z), pt(r, a1, z))) def hooked_half_ring(r, a0, z, hook, bend_r): """Semicircle a0 -> a0+180 with inward 135-degree hooks at both ends (P3, shape VI).""" arc = arc_xy(r, a0, a0 + 180, z) pieces = [] for at_start in (True, False): p = arc.PointAtStart if at_start else arc.PointAtEnd t = arc.TangentAtStart if at_start else arc.TangentAtEnd if at_start: t = -t # travel direction away from the arc n = rg.Vector3d(-p.X, -p.Y, 0) n.Unitize() d = t * math.cos(math.radians(135)) + n * math.sin(math.radians(135)) # back + inward d.Unitize() pieces.append(rg.LineCurve(p, p + d * hook)) joined = rg.Curve.JoinCurves([pieces[0], arc, pieces[1]], TOL)[0] fil = rg.Curve.CreateFilletCornersCurve(joined, bend_r, TOL, math.radians(1)) return fil or joined def main(): for o in list(doc.Objects): if (o.Attributes.GetUserString("id") or "").startswith(("P1-R-", "P1-RC-")): doc.Objects.Delete(o, True) cr = P["column_rebar"] mb, hp, tie = cr["main_bars"], cr["hoops"], cr["ties"] sch = cr["schedule"] count = 0 # Main bars P1 (outer, hooked) / P2 (inner, straight) p1, p2 = mb["p1"], mb["p2"] for i in range(mb["divisions"]): th = mb["start_deg"] * 0 + i * mb["pitch_deg"] # start angle 0 (CAD 0.001 deg) ro, ri = mb["r_outer"], mb["r_inner"] z_leg, R = p1["hook_leg_z"], p1["bend_r"] top = p1["straight_from"] + p1["straight_len"] leg = rg.LineCurve(pt(ro + R + p1["hook_leg"], th, z_leg), pt(ro + R, th, z_leg)) bend = rg.ArcCurve(rg.Arc(pt(ro + R, th, z_leg), pt(ro + R - R * math.sin(math.radians(45)), th, z_leg + R - R * math.cos(math.radians(45))), pt(ro, th, z_leg + R))) vert = rg.LineCurve(pt(ro, th, z_leg + R), pt(ro, th, top)) c1 = rg.Curve.JoinCurves([leg, bend, vert], TOL)[0] add_bar(c1, "P1", f"{i + 1:02d}", p1["straight_len"] + math.pi / 2 * R + p1["hook_leg"], "P1-FT,P1-CP", f"angle {th:.4f} deg; D1 단면 C-C / 정면도; D3 P1") c2 = rg.LineCurve(pt(ri, th, p2["z0"]), pt(ri, th, p2["z0"] + p2["length"])) add_bar(c2, "P2", f"{i + 1:02d}", p2["length"], "P1-FT,P1-CP", f"angle {th:.4f} deg; D1 단면 C-C / 정면도; D3 P2") count += 2 # Hoops: P4 (V: 2 half arcs + 2 coupler gaps), P5 (IX: ring + 1 gap), P3 (VI: 2 hooked halves) r = hp["radius"] n3 = 0 for k, (z, mark) in enumerate(zip(hp["levels"], hp["marks"])): s = sch[mark] a = float(s["A"]) * 1000 if float(s["A"]) < 100 else float(s["A"]) if mark in ("P4", "P5"): gap = float(s["B"]) gap_deg = math.degrees(gap / r) arcs = ([(90 + gap_deg / 2, 270 - gap_deg / 2), (270 + gap_deg / 2, 450 - gap_deg / 2)] if mark == "P4" else [(90 + gap_deg / 2, 450 - gap_deg / 2)]) curves = [arc_xy(r, a0, a1, z) for a0, a1 in arcs] for j, c in enumerate(curves): add_bar(c, mark, f"L{k + 1:02d}{'ab'[j] if len(curves) > 1 else ''}", a, "", f"EL {z / 1000:.3f}; coupler gap {gap:.0f} at {'90/270' if mark == 'P4' else '90'} deg", coupler=gap) # one coupler (b) per piece: P4 2 pieces/2 gaps, P5 1/1 else: n3 += 1 a0 = 90 if n3 % 2 else 0 # joins staggered by 90 deg hook = float(s["B"]) for j, start in enumerate((a0, a0 + 180)): c = hooked_half_ring(r, start, z, hook, min_bend_radius("H19")) add_bar(c, mark, f"L{k + 1:02d}{'ab'[j]}", a + 2 * hook, "P1-CP" if z > 40411 else "", f"EL {z / 1000:.3f}; joins at {a0}/{a0 + 180} deg") count += 1 # Cross ties P6 (III): straight 1836 on diameters, 150 hooks down at r=918, sitting on hoop half = tie["straight"] / 2 zoff = DIA["H19"] / 2 + DIA["H16"] / 2 for k, z in enumerate(tie["levels"]): for ang in tie["angles_deg"]: zt = z + zoff pts = [pt(half, ang + 180, zt - tie["hook"]), pt(half, ang + 180, zt), pt(half, ang, zt), pt(half, ang, zt - tie["hook"])] pl = rg.PolylineCurve(pts) c = rg.Curve.CreateFilletCornersCurve(pl, min_bend_radius("H16"), TOL, math.radians(1)) or pl add_bar(c, "P6", f"L{k + 1}-{ang:03d}", tie["straight"] + 2 * tie["hook"], "P1-FT" if z < 28408 else ("P1-CP" if z > 40411 else ""), f"EL {z / 1000:.3f} (+{zoff} on hoop); angle {ang} deg") count += 1 doc.Views.Redraw() print(json.dumps({"bars": count})) main()