"""Stage 4b: footing rebar B1-B12 (centreline + pipe solid) from params.json `footing_rebar`. Run inside Rhino 8 after build_rhino.py (and build_rebar_rhino.py): exec(open(, encoding="utf-8").read(), {"__rhino_doc__": doc, "PARAMS_PATH": }) Rules: 설계서 v0.1.06 §6 (HITL F1-F3 approved 2026-10-07). z in params is above footing bottom EL 26.208. `design_length` = schedule original length (a, a+2b, 2(a+b), a+b+c); curve length is measured separately. """ 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 FB = 26208 DIA = {"S29": 29.0, "S25": 25.0, "S19": 19.0, "S16": 16.0, "H16": 16.0} MARK = {"B1": ("S25", "III"), "B2": ("S25", "XII"), "B3": ("S19", "III"), "B4": ("S25", "I"), "B5": ("S29", "XII"), "B6": ("S29", "XII"), "B7": ("S19", "XII"), "B8": ("S29", "XVI"), "B9": ("H16", "XI"), "B10": ("S19", "XIII"), "B11": ("S16", "XII"), "B12": ("S16", "III")} def layer(path, color): 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_r(dia): d = DIA[dia] return ((2 * d if d <= 16 else 3.5 * d) + d) / 2 # box rule (REBAR_BENDS_20260914) def poly(pts, dia): pl = rg.PolylineCurve([rg.Point3d(*p) for p in pts]) if len(pts) < 3: return pl f = rg.Curve.CreateFilletCornersCurve(pl, min_r(dia), TOL, math.radians(1)) return f or pl def add_bar(curve, mark, occ, design_len, src): 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="P1-FT", anchor_hosts="", design_length=f"{design_len:.3f}", coupler_length="0.000", src=src) for geo, lay, prefix in ((curve, layer("P1::철근::중심선", (60, 60, 60)), "P1-RC"), (pipes[0], layer(f"P1::철근::기초::{mark}", (150, 110, 60)), "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) (doc.Objects.AddBrep if isinstance(geo, rg.Brep) else doc.Objects.AddCurve)(geo, attr) def bar_y(x, z, a, end, dz): """Bar along Y (length a) at x, height z; end legs of length dz (+up / -down) or none.""" h = a / 2 if not end: return [(x, -h, z), (x, h, z)] return [(x, -h, z + dz), (x, -h, z), (x, h, z), (x, h, z + dz)] def bar_x(y, z, a, end, dz): h = a / 2 if not end: return [(-h, y, z), (h, y, z)] return [(-h, y, z + dz), (-h, y, z), (h, y, z), (h, y, z + dz)] def sched(m): row = P["footing_rebar"]["schedule"][m] nums = [float(t) for t in row[3:] if t.replace(".", "", 1).isdigit()] return row, nums def mm(v): return v * 1000 if v < 100 else v def main(): for o in list(doc.Objects): oid = o.Attributes.GetUserString("id") or "" if oid.startswith(("P1-R-B", "P1-RC-B")): doc.Objects.Delete(o, True) fr = P["footing_rebar"] L = fr["layers"] n = 0 a_y, a_x = 7600.0, 9150.0 # D3 a of Y-bars (B1-B4, B12) / X-bars (B5-B8, B11) # bottom layer 1 (Y): B2 U (legs 1950 up) at +-4575 and alternate from -4375; B4 straight pos = L["bottom_Y1"]["pos"] inner = [x for x in pos if abs(x) < 4500] for i, x in enumerate(pos): mark = "B2" if abs(x) > 4500 or inner.index(x) % 2 == 0 else "B4" z = FB + L["bottom_Y1"]["z"] if mark == "B2": c, dl = poly(bar_y(x, z, a_y, True, 1950), "S25"), a_y + 2 * 1950 else: c, dl = poly(bar_y(x, z, a_y, False, 0), "S25"), a_y add_bar(c, mark, f"x{x:+.0f}", dl, "D1 정면도 하면 점 z122 / D1·D2 '70@125=8.750'") n += 1 pos = L["bottom_X2"]["pos"] inner = [y for y in pos if abs(y) < 3700] for y in pos: mark = "B6" if abs(y) > 3700 or inner.index(y) % 2 == 0 else "B8" z = FB + L["bottom_X2"]["z"] if mark == "B6": c, dl = poly(bar_x(y, z, a_x, True, 1950), "S29"), a_x + 2 * 1950 else: c, dl = poly(bar_x(y, z, a_x, False, 0), "S29"), a_x add_bar(c, mark, f"y{y:+.0f}", dl, "D1 단면도 하면 점 z178 / D1 '58@125=7.250'") n += 1 for key, mark, dia, axis, leg, a in (("bottom_Y3", "B3", "S19", "Y", 200, a_y), ("bottom_X4", "B7", "S19", "X", 200, a_x), ("top_Y", "B1", "S25", "Y", -200, a_y), ("top_X", "B5", "S29", "X", -200, a_x)): z = FB + L[key]["z"] for v in L[key]["pos"]: pts = bar_y(v, z, a, True, leg) if axis == "Y" else bar_x(v, z, a, True, leg) add_bar(poly(pts, dia), mark, f"{axis.lower()}{v:+.0f}", a + 2 * abs(leg), f"D1 {'정면도' if axis == 'Y' else '단면도'} 점 z{L[key]['z']}") n += 1 for key, mark, axis, a in (("mid_X", "B11", "X", a_x), ("mid_Y", "B12", "Y", a_y)): for zs, vs in L[key]["levels"].items(): z = FB + float(zs) for v in vs: pts = bar_y(v, z, a, True, -100) if axis == "Y" else bar_x(v, z, a, True, -100) add_bar(poly(pts, "S16"), mark, f"z{float(zs):.0f}{axis.lower()}{v:+.0f}", a + 200, f"D1 {'정면도' if axis == 'Y' else '단면도'} 중앙 점 z{zs}") n += 1 b10 = fr["b10"] hx, hy = b10["size_x"] / 2, b10["size_y"] / 2 for k, zs in enumerate(b10["levels"]): z = FB + zs pts = [(-hx, 0, z), (-hx, -hy, z), (hx, -hy, z), (hx, hy, z), (-hx, hy, z), (-hx, 0.0, z)] c = poly(pts, "S19") add_bar(c, "B10", f"z{zs}", 2 * (b10["size_x"] + b10["size_y"]), "D1 정면도 '150','9@200'; D3 B10 9.198x7.644 (closed, start/end at -X mid side)") n += 1 b9 = fr["b9"] rb = min_r("H16") rc = b9["c"] / math.pi # 180-degree hook developed length = c for i, x in enumerate(b9["grid_x"]): for j, y in enumerate(b9["grid_y"]): if (i + j) % 2 != b9["checker_parity"]: continue z0, z1 = FB + b9["z0"], FB + b9["z0"] + b9["a"] base = poly([(x + b9["b"], y, z0), (x, y, z0), (x, y, z1)], "H16") top = rg.ArcCurve(rg.Arc(rg.Point3d(x, y, z1), rg.Point3d(x + rc, y, z1 + rc), rg.Point3d(x + 2 * rc, y, z1))) c = rg.Curve.JoinCurves([base, top], TOL)[0] add_bar(c, "B9", f"x{x:+.0f}y{y:+.0f}", b9["a"] + b9["b"] + b9["c"], "D1 EREC grid @250 checker (i+j even) [추론]; count 493 [역산]") n += 1 doc.Views.Redraw() print(json.dumps({"footing_bars": n})) main()