"""G3 stage, form "shaft cage" (Ramp-A P1, 2026-10-13): drilled-shaft reinforcement cage (no footing). Straight main bars in two pieces joined by couplers (upper / lower mark), lapped half-ring hoops with 135-deg hooks, X / Y cross ties, bottom U bars in vertical planes through the centre, and the anti-uplift plate under them. Run inside Rhino 8 via runner (globals PARAMS_PATH) after the G1 / G2 builders. Units mm, Z = EL x 1000. params shaft_rebar: main {count, start_deg, radius, pieces: [{mark, z0, z1}, ...] bottom to top, coupler_length} hoops {mark, radius, levels, a, lap, hook, join_deg [first, second]} joins alternate per level (S5) ties {mark, levels, straight, leg, hook_tail, y_lift} as column_rebar_straight.py (S5) u_bars {mark, count, z_bottom, half_bottom, half_top, leg_top_z, top_z, bend_r} one U per plane at main-bar angle i (i < count): bottom +-half_bottom at z_bottom, sloped legs to +-half_top at leg_top_z, vertical to top_z (S3) plate {size, thickness, z_top, colour} square, centred (S4) design_lengths {mark: schedule-style original length per piece} Bars below the pile segment boundary are hosted by the lower pile segment, above it by the upper one. """ import json import math import Rhino import Rhino.Geometry as rg from Rhino.DocObjects import ObjectAttributes, ObjectColorSource from System.Drawing import Color P = json.load(open(PARAMS_PATH, encoding="utf-8")) doc = __rhino_doc__ TOL = 0.01 PID = P["pier_id"] MARK = {m: (v["dia"], v["shape"]) for m, v in P["rebar_marks"].items()} DIA = {d: float(d[1:]) for d, _ in MARK.values()} CL_RGB = tuple(P["display"]["centreline_colour"]) SEGS = P["pile"]["segments"] def layer(path, color): parent = -1 parts = path.split("::") for i in range(1, len(parts) + 1): idx = doc.Layers.FindByFullPath("::".join(parts[:i]), -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] return ((2 * d if d <= 16 else 3.5 * d) + d) / 2 def host(z): for i, s in enumerate(SEGS, 1): if s["z0"] <= z <= s["z1"]: return f"{PID}-PL{i:02d}" return "" def add_bar(curve, mark, occ, design_len, src, coupler=0.0): dia, shape = MARK[mark] pipes = rg.Brep.CreatePipe(curve, DIA[dia] / 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") bb = curve.GetBoundingBox(True) common = dict(mark=mark, dia=dia, shape=shape, occurrence=occ, host=host((bb.Min.Z + bb.Max.Z) / 2), anchor_hosts="", design_length=f"{design_len:.3f}", coupler_length=f"{coupler:.3f}", src=src) for geo, lay, prefix in ((curve, layer(f"{PID}::철근::중심선", CL_RGB), f"{PID}-RC"), (pipes[0], layer(f"{PID}::철근::말뚝::{mark}", (110, 110, 110)), f"{PID}-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) gid = doc.Objects.AddBrep(geo, attr) if isinstance(geo, rg.Brep) else doc.Objects.AddCurve(geo, attr) if gid == __import__("System").Guid.Empty: raise RuntimeError(f"{oid}: add failed") def pt(r, deg, z): t = math.radians(deg) return rg.Point3d(r * math.cos(t), r * math.sin(t), z) def hooked_arc(r, a0, a1, z, hook, bend_r): """Arc a0 -> a1 (deg, ccw) with inward 135-deg hooks at both ends, fillet bend_r (as column_rebar_ring.py).""" arc = rg.ArcCurve(rg.Arc(pt(r, a0, z), pt(r, (a0 + a1) / 2, z), pt(r, a1, 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 n = rg.Vector3d(-p.X, -p.Y, 0) n.Unitize() d = t * math.cos(math.radians(135)) + n * math.sin(math.radians(135)) d.Unitize() pieces.append(rg.LineCurve(p, p + d * hook)) joined = rg.Curve.JoinCurves([pieces[0], arc, pieces[1]], TOL)[0] return rg.Curve.CreateFilletCornersCurve(joined, bend_r, TOL, math.radians(1)) or joined def tie_curve(axis, z, half, leg, tail, flip): """As column_rebar_straight.py: straight part on the X / Y diameter, 90-deg leg down at one end, 135-deg hook.""" u = rg.Vector3d(1, 0, 0) if axis == 0 else rg.Vector3d(0, 1, 0) s = -1 if flip else 1 a, b = rg.Point3d(0, 0, z) - u * (s * half), rg.Point3d(0, 0, z) + u * (s * half) d = -u * s * math.cos(math.radians(45)) + rg.Vector3d(0, 0, -1) * math.sin(math.radians(45)) return rg.PolylineCurve([a + rg.Vector3d(0, 0, -leg), a, b, b + d * tail]) def main(): for o in list(doc.Objects): oid = o.Attributes.GetUserString("id") or "" if oid.startswith((f"{PID}-PT",)) or (oid.startswith((f"{PID}-R-", f"{PID}-RC-")) and (o.Attributes.GetUserString("mark") or "") in SR_MARKS): doc.Objects.Delete(o, True) DL = SR["design_lengths"] m, n = SR["main"], 0 for i in range(m["count"]): th = m["start_deg"] + i * 360.0 / m["count"] for pc in m["pieces"]: c = rg.LineCurve(pt(m["radius"], th, pc["z0"]), pt(m["radius"], th, pc["z1"])) add_bar(c, pc["mark"], f"{i + 1:02d}", DL[pc["mark"]], f"angle {th:.4f} deg; D4 단면 G-G·J-J 61개; r {m['radius']} (S2); EL {pc['z0'] / 1000:.3f}~{pc['z1'] / 1000:.3f}; " f"커플러 이음 (S6)", coupler=m["coupler_length"]) n += 1 h = SR["hoops"] r, ext = h["radius"], math.degrees(h["lap"] / 2 / h["radius"]) for j, z in enumerate(h["levels"]): a0 = h["join_deg"][j % 2] for q, s in enumerate((a0, a0 + 180)): c = hooked_arc(r, s - ext, s + 180 + ext, z, h["hook"], min_bend_radius(MARK[h["mark"]][0])) add_bar(c, h["mark"], f"L{j + 1:03d}{'ab'[q]}", DL[h["mark"]], f"EL {z / 1000:.3f}; r {r} (S1); 겹이음 {h['lap']:.0f} at {a0}/{a0 + 180} deg (S5 [추론])") n += 1 t = SR["ties"] rb = min_bend_radius(MARK[t["mark"]][0]) for j, z in enumerate(t["levels"]): for axis, name in ((0, "X"), (1, "Y")): zt = z + (t["y_lift"] if axis == 1 else 0) pl = tie_curve(axis, zt, t["straight"] / 2, t["leg"], t["hook_tail"], flip=(j % 2 == 1)) c = rg.Curve.CreateFilletCornersCurve(pl, rb, TOL, math.radians(1)) or pl add_bar(c, t["mark"], f"L{j + 1:02d}{name}", DL[t["mark"]], f"EL {zt / 1000:.3f}; {name} 방향 (S5 [추론])") n += 1 u = SR["u_bars"] for i in range(u["count"]): th = m["start_deg"] + i * 360.0 / m["count"] e = rg.Vector3d(math.cos(math.radians(th)), math.sin(math.radians(th)), 0) P0 = lambda s, z: rg.Point3d(0, 0, z) + e * s pl = rg.PolylineCurve([P0(-u["half_top"], u["top_z"]), P0(-u["half_top"], u["leg_top_z"]), P0(-u["half_bottom"], u["z_bottom"]), P0(u["half_bottom"], u["z_bottom"]), P0(u["half_top"], u["leg_top_z"]), P0(u["half_top"], u["top_z"])]) c = rg.Curve.CreateFilletCornersCurve(pl, u["bend_r"], TOL, math.radians(1)) or pl add_bar(c, u["mark"], f"{i + 1:02d}", DL[u["mark"]], f"plane {th:.4f} deg through the centre (S3 [추론]); bottom EL {u['z_bottom'] / 1000:.3f}; c {u['top_z'] - u['leg_top_z']:.0f} welded to P1-2") n += 1 pt_ = SR["plate"] a = pt_["size"] / 2 box = rg.Box(rg.BoundingBox(-a, -a, pt_["z_top"] - pt_["thickness"], a, a, pt_["z_top"])).ToBrep() attr = ObjectAttributes() attr.LayerIndex = layer(f"{PID}::철근::말뚝::PLATE", tuple(pt_["colour"])) attr.ObjectColor = Color.FromArgb(*pt_["colour"]) attr.ColorSource = ObjectColorSource.ColorFromObject oid = f"{PID}-PT01" attr.Name = oid for k, v in (("id", oid), ("role", "anti-uplift plate"), ("host", host(pt_["z_top"])), ("src", f"D4 떠오름 방지 PLATE {pt_['size']:.0f}x{pt_['size']:.0f}x{pt_['thickness']:.0f}; U형 바닥 아래 (S4 [추론])")): attr.SetUserString(k, v) if doc.Objects.AddBrep(box, attr) == __import__("System").Guid.Empty: raise RuntimeError("plate: add failed") doc.Views.Redraw() print(json.dumps({"bars": n, "plate": 1})) SR = P["shaft_rebar"] SR_MARKS = {pc["mark"] for pc in SR["main"]["pieces"]} | {SR[k]["mark"] for k in ("hoops", "ties", "u_bars")} main()