"""G2 stage, form "ring" (Ulsan P1, 2026-10-13): circular column rebar with one main-bar ring, hoop zones of different hoop types and cross ties, from params `column_rebar` (centreline + pipe solid per bar). Run inside Rhino 8 via runner (globals PARAMS_PATH) after the G1 builder. Units mm, Z = EL x 1000. params column_rebar: main {mark, count, start_deg, radius, hook_leg_z, bend_r, hook_leg, straight_from, straight_len} bar = horizontal leg pointing outwards at hook_leg_z, R bend, vertical straight part (shape VI) hoops {radius, zones: [{mark, type, levels, a, gap | lap+hook}]} type "coupler_half": two half arcs, coupler gaps `gap` at 90 / 270 deg (shape V) "coupler_ring": one ring, one coupler gap at 90 deg (shape X) "lap_half": two half rings, each extended by lap/2 at both ends, 135-deg hooks (shape VI); joins alternate between 90/270 and 0/180 deg ties {mark, levels, angles_deg, straight, hook, z_offset}: straight bars on diameters, hooks down `design_length` is the schedule-style original length; `coupler_length` the gap per piece (schedule L 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. """ 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 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"]) COLORS = {m: tuple(c) for m, c in P["display"].get("column_mark_colours", {}).items()} Z_FT_TOP = P["footing"]["z_bottom"] + P["footing"]["thickness"] Z_CP_BOT = Z_FT_TOP + P["column"]["height"] 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 hosts(z): return f"{PID}-FT" if z < Z_FT_TOP else (f"{PID}-CP" if z > Z_CP_BOT else "") def add_bar(curve, mark, occ, design_len, anchor, 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") common = dict(mark=mark, dia=dia, shape=shape, occurrence=occ, host=f"{PID}-CL", anchor_hosts=anchor, 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}", COLORS.get(mark, (200, 140, 90))), 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 arc_xy(r, a0, a1, z): return rg.ArcCurve(rg.Arc(pt(r, a0, z), pt(r, (a0 + a1) / 2, z), pt(r, a1, 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.""" arc = arc_xy(r, a0, 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 main(): for o in list(doc.Objects): oid = o.Attributes.GetUserString("id") or "" if oid.startswith((f"{PID}-R-P", f"{PID}-RC-P")): doc.Objects.Delete(o, True) cr = P["column_rebar"] m = cr["main"] n = 0 ro, R = m["radius"], m["bend_r"] top = m["straight_from"] + m["straight_len"] for i in range(m["count"]): th = m["start_deg"] + i * 360.0 / m["count"] leg = rg.LineCurve(pt(ro + R + m["hook_leg"], th, m["hook_leg_z"]), pt(ro + R, th, m["hook_leg_z"])) bend = rg.ArcCurve(rg.Arc(pt(ro + R, th, m["hook_leg_z"]), pt(ro + R - R * math.sin(math.radians(45)), th, m["hook_leg_z"] + R - R * math.cos(math.radians(45))), pt(ro, th, m["hook_leg_z"] + R))) vert = rg.LineCurve(pt(ro, th, m["hook_leg_z"] + R), pt(ro, th, top)) c = rg.Curve.JoinCurves([leg, bend, vert], TOL)[0] add_bar(c, m["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; 배근도(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 hp = cr["hoops"] r = hp["radius"] k = 0 for zone in hp["zones"]: 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"]): k += 1 if typ == "coupler_half": gd = math.degrees(zone["gap"] / r) arcs = [(90 + gd / 2, 270 - gd / 2), (270 + gd / 2, 450 - gd / 2)] for q, (a0, a1) in enumerate(arcs): add_bar(arc_xy(r, a0, a1, z), mark, f"L{k:03d}{'ab'[q]}", zone["a"], hosts(z), f"EL {z / 1000:.3f}; coupler gaps {zone['gap']:.0f} at 90/270 deg [추론]", coupler=zone["gap"]) elif typ == "coupler_ring": gd = math.degrees(zone["gap"] / r) add_bar(arc_xy(r, 90 + gd / 2, 450 - gd / 2, z), mark, f"L{k:03d}", zone["a"], hosts(z), f"EL {z / 1000:.3f}; coupler gap {zone['gap']:.0f} at 90 deg [추론]", coupler=zone["gap"]) elif typ == "lap_half": ext = math.degrees(zone["lap"] / 2 / r) a0 = 90 if j % 2 == 0 else 0 # joins alternate by 90 deg [추론] for q, s in enumerate((a0, a0 + 180)): c = hooked_arc(r, s - ext, s + 180 + ext, z, zone["hook"], min_bend_radius(MARK[mark][0])) add_bar(c, mark, f"L{k:03d}{'ab'[q]}", zone["a"] + zone["lap"] + 2 * zone["hook"], hosts(z), f"EL {z / 1000:.3f}; laps {zone['lap']:.0f} at {a0}/{a0 + 180} deg [추론]") else: raise RuntimeError(f"unknown hoop type {typ}") n += 1 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 for q, z in enumerate(tie["levels"]): zt = z + tie["z_offset"] for ang in tie["angles_deg"]: 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(MARK[tie["mark"]][0]), TOL, math.radians(1)) or pl add_bar(c, tie["mark"], f"L{q + 1:02d}-{ang:05.1f}", tie["straight"] + 2 * tie["hook"], hosts(z), f"EL {z / 1000:.3f} (+{tie['z_offset']} on hoop); angle {ang:.2f} deg") n += 1 doc.Views.Redraw() print(json.dumps({"bars": n})) main()