chore(rebar): Pier_T_rebar 작업 폴더 초기 구성 및 외부 의존 파일 통합

- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환
- Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용
- pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관)
- worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md)
- D:\Rhino-MCP 경로 참조를 새 위치로 치환

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ahnyongjooandClaude Sonnet 5.5 committed 2026-10-28 11:20:18 +09:00
commit 964a841de1
3978 files changed
+58013356

No files matched your search

@@ -0,0 +1,185 @@
"""G4 gate checker, Ramp-A P1 coping rebar (2026-10-14): C1~C16 and bearing-seat S1~S4 as centreline polylines.
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G4, plus the G1 coping dimensions) come
from the D3 schedule (L, N), the D2 / D3 drawn positions and section dimensions, and the user decisions T1-T6;
generator params are not read.
Checks (C10): counts, fixed-length marks vs L (variable marks recorded), top layers (level, y rows, x ends),
stirrup positions per zone, stirrup top / bottom cover along the soffit, inner stirrup offset, side loops per level,
C6 bottom level, C15 / C16, seat bars per bearing, containment in the coping + pedestals, min cover (record).
"""
import json
import math
import os
import statistics
import sys
import Rhino.Geometry as rg
sys.path.insert(0, os.path.dirname(__file__))
from _common import all_objects, load_expected, write_result # noqa: E402
doc = __rhino_doc__
X = load_expected(EXPECTED_PATH)
PID, G, C, CO = X["pier_id"], X["G4"], X["G1"]["coping"], X["G1"]["column"]
SCHED = {m: tuple(v) for m, v in G["schedule"].items()}
ZB = CO["z0"] + CO["length"] # column top = coping bottom
ZT = ZB + C["soffit_rise"]
ZTL, ZTR = C["z_top_left"], C["z_top_right"]
R, HB, HT = C["soffit_radius"], C["column_face_half"], C["bottom_length"] / 2
results = []
def soffit_centre():
ax, az, bx, bz = -HB, ZB, -HT, ZT # R arc through (-HB, ZB) and (-HT, ZT), centre below the chord
mx, mz = (ax + bx) / 2, (az + bz) / 2
d = math.hypot(bx - ax, bz - az)
h = math.sqrt(R * R - d * d / 4)
ux, uz = (bz - az) / d, -(bx - ax) / d
return min(((mx + h * ux, mz + h * uz), (mx - h * ux, mz - h * uz)), key=lambda c: c[1])
SCX, SCZ = soffit_centre()
SCX = abs(SCX)
def soffit_z(x):
return ZB if abs(x) <= HB else SCZ + math.sqrt(R * R - (SCX - abs(x)) ** 2)
def check(item, exp, act, tol=1.0):
results.append(dict(check="C10", item=item, expected=round(exp, 3), actual=round(act, 3),
diff=round(act - exp, 3), tol=tol, result="PASS" if abs(act - exp) <= tol else "FAIL"))
def flag(item, ok, detail=""):
results.append(dict(check="C10", item=item, detail=detail, result="PASS" if ok else "FAIL"))
def info(item, detail):
results.append(dict(check="C10", item=item, detail=detail, result="INFO"))
def row(s):
return [s["start"] + s["pitch"] * i for i in range(s["n"])]
def bb(o):
return o.Geometry.GetBoundingBox(True)
def worst(pairs):
pairs = list(pairs)
return max((abs(a - b) for a, b in pairs), default=1e9)
def main():
cl = {m: [] for m in SCHED}
pp = {m: [] for m in SCHED}
for o in all_objects(doc):
oid = o.Attributes.GetUserString("id") or ""
m = o.Attributes.GetUserString("mark")
if m in SCHED and oid.startswith(f"{PID}-RC-"):
cl[m].append(o)
elif m in SCHED and oid.startswith(f"{PID}-R-"):
pp[m].append(o)
for m, (L, n) in SCHED.items():
check(f"{m} bar count vs schedule", n, len(cl[m]), tol=0)
flag(f"{m} pipes closed & paired", len(pp[m]) == len(cl[m]) and all(o.Geometry.IsSolid for o in pp[m]),
f"{len(pp[m])}/{len(cl[m])}")
dl = [float(o.Attributes.GetUserString("design_length")) for o in cl[m]]
if not dl:
continue
if m in G["fixed_length"]:
check(f"{m} original length vs L (worst)", 0, max(abs(v - L) for v in dl))
else:
info(f"{m} mean original length vs L (variable mark, record)",
f"mean {statistics.mean(dl):.1f} / L {L} (diff {statistics.mean(dl) - L:+.1f}); min {min(dl):.0f} max {max(dl):.0f}")
# top layers (straight part = top of the bar)
for m, s in G["layers"].items():
got = sorted(round((bb(o).Min.Y + bb(o).Max.Y) / 2, 1) for o in cl[m])
want = row(s["y"])
check(f"{m} y rows (worst, {len(got)}/{len(want)})", 0, worst(zip(got, want)) if len(got) == len(want) else 1e9)
check(f"{m} level of the straight part (worst)", 0, max(abs(bb(o).Max.Z - s["z"]) for o in cl[m]))
if "x_top" in s:
x0, x1 = s["x_top"]
ends = []
for o in cl[m]:
c = o.Geometry
top_pts = [p for p in (c.PointAt(t) for t in c.DivideByLength(5, True)) if abs(p.Z - s["z"]) < 0.5]
ends.append(max(abs(min(p.X for p in top_pts) - x0), abs(max(p.X for p in top_pts) - x1)))
check(f"{m} straight part x ends vs drawn (worst; fillet tangent points)", 0, max(ends), tol=s.get("x_tol", 70))
# stirrups
st = G["stirrups"]
xs = row(st["x"])
for zone, (m_out, m_in, x0, x1) in st["zones"].items():
want = [x for x in xs if x0 <= x <= x1]
got_o = sorted(round(bb(o).Min.X, 1) for o in cl[m_out])
got_i = sorted(round(bb(o).Min.X, 1) for o in cl[m_in])
check(f"{m_out} positions = drawn x ({len(want)})", 0, worst(zip(got_o, want)) if len(got_o) == len(want) else 1e9)
check(f"{m_in} positions = drawn x + {st['inner_dx']} (2 per x)", 0,
worst(zip(got_i, sorted(x + st["inner_dx"] for x in want for _ in (0, 1)))) if len(got_i) == 2 * len(want) else 1e9)
for m in (m_out, m_in):
dt, dbm = [], []
for o in cl[m]:
b = bb(o)
x = b.Min.X - (st["inner_dx"] if m == m_in else 0)
t = ZTL if x <= 0 else ZTR
dt.append(abs((t - b.Max.Z) - st["top_cover"]))
dbm.append(abs((b.Min.Z - soffit_z(x)) - st["bottom_cover"] * (1 if abs(x) < HB else
# on the arc the 77.5 cover is radial: vertical gap = R' - R projected
(SCZ + math.sqrt((R + st["bottom_cover"]) ** 2 - (SCX - abs(x)) ** 2) - soffit_z(x)) / st["bottom_cover"])))
check(f"{m} top cover {st['top_cover']} (worst)", 0, max(dt))
check(f"{m} bottom cover {st['bottom_cover']} to the soffit (worst)", 0, max(dbm))
ys = [(bb(o).Min.Y, bb(o).Max.Y) for o in cl[m_out]]
check(f"{m_out} legs y {st['outer_y']} (worst)", 0, worst([(a, st["outer_y"][0]) for a, _ in ys] + [(b, st["outer_y"][1]) for _, b in ys]))
# side loops
lp = G["loops"]
for m, levels in lp["levels"].items():
zs = {}
for o in cl[m]:
zs.setdefault(round(bb(o).Min.Z, 1), []).append(o)
flag(f"{m} levels {levels} x {lp['per_level']}", sorted(zs) == sorted(levels)
and all(len(v) == lp["per_level"] for v in zs.values()), f"{sorted(zs)}")
flat = [abs(bb(o).Max.Z - bb(o).Min.Z) for o in cl[m]]
check(f"{m} loops horizontal (worst dz)", 0, max(flat) if flat else 1e9, tol=0.01)
# C6, C15, C16
c6 = G["c6"]
check("C6 bottom straight level (worst)", 0, max(abs(bb(o).Min.Z - c6["z_bottom"]) for o in cl["C6"]) if cl["C6"] else 1e9)
got = sorted(round(o.Geometry.PointAt(o.Geometry.Domain.Mid).Y, 1) for o in cl["C6"])
check("C6 y rows at mid-span (worst)", 0, worst(zip(got, c6["y"])) if len(got) == len(c6["y"]) else 1e9)
for m in ("C15",):
w = [bb(o).Max.Y - bb(o).Min.Y for o in cl[m]]
check(f"{m} width a (worst)", G["c15"]["a"], max(w, key=lambda v: abs(v - G["c15"]["a"])) if w else 0)
# seats
for no, want in G["seats"].items():
for m, n in want.items():
k = sum(1 for o in cl[m] if o.Attributes.GetUserString("occurrence").startswith(f"B{no}-"))
check(f"bearing {no} {m} count", n, k, tol=0)
# containment and cover
solids = [o.Geometry for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "") == f"{PID}-CP"
or (o.Attributes.GetUserString("id") or "").startswith(f"{PID}-BS")]
solids = [s if isinstance(s, rg.Brep) else rg.Brep.TryConvertBrep(s) for s in solids]
cp = [o.Geometry for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "") == f"{PID}-CP"][0]
col = [o.Geometry for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "") == f"{PID}-CL"]
info("concrete solids for containment", f"{len(solids)} (coping + pedestals); bars below the coping bottom checked in the column")
cover = {}
for m in SCHED:
bad, cov = 0, 1e9
r = G["dia"][m] / 2
for o in cl[m]:
cu = o.Geometry
for t in cu.DivideByCount(max(6, int(cu.GetLength() / 150)), True):
p = cu.PointAt(t)
for q in (p, p + rg.Vector3d(r, 0, 0), p - rg.Vector3d(r, 0, 0), p + rg.Vector3d(0, r, 0),
p - rg.Vector3d(0, r, 0), p + rg.Vector3d(0, 0, r), p - rg.Vector3d(0, 0, r)):
if not any(s.IsPointInside(q, 0.01, False) for s in solids + col):
bad += 1
if cp.IsPointInside(p, 0.01, False):
cov = min(cov, p.DistanceTo(cp.ClosestPoint(p)) - r)
cover[m] = round(cov, 1)
check(f"{m} sample points outside concrete", 0, bad, tol=0)
info("min cover to the coping surface per mark (record; seat bars measured to the coping only)", json.dumps(cover))
write_result(RESULT_PATH, results, dict(cover=cover))
main()