"""G2 stage: column rebar P1-P6 (centreline + pipe solid) from params `column_rebar`. Run inside Rhino 8 via runner (globals PARAMS_PATH) after concrete.py. Ported from Code/교각모델링/build_rebar_rhino.py (Ungok P1, model v0.1.09). 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 PID = P["pier_id"] MARK_DIA = {m: v["dia"] for m, v in P["rebar_marks"].items()} DIA = {d: float(d[1:]) for d in MARK_DIA.values()} # 'S32' -> 32.0 COLORS = {m: tuple(c) for m, c in P["display"]["column_mark_colours"].items()} CL_RGB = tuple(P["display"]["centreline_colour"]) Z_FT_TOP = P["footing"]["z_bottom"] + P["footing"]["thickness"] # ties/hoops below -> anchored in footing Z_CP_BOT = P["coping"]["z_bottom_flat"] # above -> anchored in coping 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=f"{PID}-CL", anchor_hosts=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}", COLORS[mark]), 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) 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((f"{PID}-R-", f"{PID}-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"], f"{PID}-FT,{PID}-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"], f"{PID}-FT,{PID}-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"])): r = hp.get("radius_by_mark", {}).get(mark, hp["radius"]) # Ungok P2: P3 R899, P4/P5 R904 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(MARK_DIA["P3"])) add_bar(c, mark, f"L{k + 1:02d}{'ab'[j]}", a + 2 * hook, f"{PID}-CP" if z > Z_CP_BOT 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[MARK_DIA["P4"]] / 2 + DIA[MARK_DIA["P6"]] / 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(MARK_DIA["P6"]), TOL, math.radians(1)) or pl add_bar(c, "P6", f"L{k + 1}-{ang:03d}", tie["straight"] + 2 * tie["hook"], f"{PID}-FT" if z < Z_FT_TOP else (f"{PID}-CP" if z > Z_CP_BOT else ""), f"EL {z / 1000:.3f} (+{zoff} on hoop); angle {ang} deg") count += 1 doc.Views.Redraw() print(json.dumps({"bars": count})) main()