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,158 @@
"""G3 gate checker, form "mats" (Ulsan P1, 2026-10-13): footing rebar groups, perimeter hoops, verticals.
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G3) from the schedule and the
position chains of the rebar drawing (layer heights are drawn values, marked [derived]); params are not read.
expected.G3: schedule {mark: [L, n]}, dia {mark: d}, rows {name: {first, start, pitch, n, last}},
layers {mark: {axis, z, row | pos, subset}} (subset "on"/"off": positions on / off the `of` row)
hoops {mark, half, chain {start, groups}}, verticals {mark, grid_x row, grid_y row, parity, z0, a}
Optional (Ulsan P2, 2026-10-15; absent -> unchanged): G1.footing.rot_deg (bars are checked after turning their centrelines back by the
foundation rotation), hoops.half_x / half_y, verticals {exclude {x, y} (grid points with |x| <= x and |y| <= y are absent), legs {bottom, top}}.
Checks (C9): counts, original length, layer z, positions, hoop levels / size, vertical grid, containment in the
footing, minimum cover (record), rebar colours by diameter.
"""
import json
import math
import os
import sys
import Rhino.Geometry as rg
from Rhino.DocObjects import ObjectColorSource
sys.path.insert(0, os.path.dirname(__file__))
from _common import all_objects, chain, load_expected, write_result # noqa: E402
doc = __rhino_doc__
X = load_expected(EXPECTED_PATH)
PID, G = X["pier_id"], X["G3"]
FB = X["G1"]["footing"]["z0"]
SCHED = {m: tuple(v) for m, v in G["schedule"].items()}
DIA = G["dia"]
PALETTE = {int(k): tuple(v) for k, v in X["rebar_palette"].items()}
ROT = X["G1"]["footing"].get("rot_deg", 0.0)
results = []
def bbx(o):
"""Bounding box of the bar centreline in the foundation's own axes."""
g = o.Geometry
if ROT:
g = g.DuplicateCurve()
g.Transform(rg.Transform.Rotation(-math.radians(ROT), rg.Vector3d.ZAxis, rg.Point3d.Origin))
return g.GetBoundingBox(True)
def check(item, exp, act, tol=1.0):
results.append(dict(check="C9", 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="C9", item=item, detail=detail, result="PASS" if ok else "FAIL"))
def info(item, detail):
results.append(dict(check="C9", item=item, detail=detail, result="INFO"))
def row(spec):
return sorted(list(spec.get("first", [])) + [spec["start"] + spec["pitch"] * i for i in range(spec["n"])]
+ list(spec.get("last", [])))
def positions(lay):
if "pos" in lay:
return sorted(lay["pos"])
base = row(G["rows"][lay["row"]])
if "subset" in lay:
ref = set(row(G["rows"][lay["of"]]))
base = [v for v in base if (v in ref) == (lay["subset"] == "on")]
return base
def objs(prefix, mark):
return [o for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "").startswith(prefix)
and o.Attributes.GetUserString("mark") == mark]
def main():
cl = {m: objs(f"{PID}-RC-", m) for m in SCHED}
pp = {m: objs(f"{PID}-R-", m) for m in SCHED}
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])}")
if cl[m]:
check(f"{m} original length vs L (worst)", 0,
max(abs(float(o.Attributes.GetUserString("design_length")) - L) for o in cl[m]))
dev = [o.Geometry.GetLength() - L for o in cl[m]]
info(f"{m} curve length - L (bend fillets / laps)", f"min {min(dev):.1f} / max {max(dev):.1f}")
for m, lay in G["layers"].items():
if not cl[m]:
continue
# horizontal part: the lowest (legs up / none) or highest (legs down) straight segment -> bbox mid level
zs, ps = [], []
for o in cl[m]:
bb = bbx(o)
leg_down = lay.get("leg", 0) < 0
zs.append((bb.Max.Z if leg_down else bb.Min.Z) - FB)
ps.append(round((bb.Min.X + bb.Max.X) / 2 if lay["axis"] == "Y" else (bb.Min.Y + bb.Max.Y) / 2, 1))
check(f"{m} layer z (worst) [derived]", 0, max(abs(z - lay["z"]) for z in zs))
exp = positions(lay)
got = sorted(ps)
ok = len(got) == len(exp)
check(f"{m} positions vs chain (worst, n={len(got)}/{len(exp)})", 0,
max(abs(a - b) for a, b in zip(got, exp)) if ok else 1e9)
hp = G["hoops"]
lv = chain(hp["chain"], 0)
hz = sorted(round(bbx(o).Min.Z - FB, 1) for o in cl[hp["mark"]])
check(f"{hp['mark']} levels vs chain (worst)", 0, max(abs(a - b) for a, b in zip(hz, lv)) if len(hz) == len(lv) else 1e9)
if cl[hp["mark"]]:
bb = bbx(cl[hp["mark"]][0])
check(f"{hp['mark']} centreline size X [derived]", 2 * hp.get("half_x", hp.get("half")), bb.Max.X - bb.Min.X)
check(f"{hp['mark']} centreline size Y [derived]", 2 * hp.get("half_y", hp.get("half")), bb.Max.Y - bb.Min.Y)
vt = G["verticals"]
gx, gy = row(G["rows"][vt["grid_x"]]), row(G["rows"][vt["grid_y"]])
pts = {(round(bbx(o).Min.X), round(bbx(o).Min.Y)) for o in cl[vt["mark"]]}
if "exclude" in vt: # Ulsan P2: grid minus the central rectangle
ex = vt["exclude"]
exp = {(round(x), round(y)) for x in gx for y in gy if not (abs(x) <= ex["x"] and abs(y) <= ex["y"])}
flag(f"{vt['mark']} positions = {len(gx)}x{len(gy)} grid minus |x|<={ex['x']} & |y|<={ex['y']} [추론]", pts == exp, f"{len(pts)} / {len(exp)}")
else:
exp = {(round(x), round(y)) for i, x in enumerate(gx) for j, y in enumerate(gy) if (i + j) % 2 == vt["parity"]}
flag(f"{vt['mark']} positions = checkerboard of {len(gx)}x{len(gy)} grid [추론]", pts == exp, f"{len(pts)} / {len(exp)}")
if cl[vt["mark"]]:
check(f"{vt['mark']} bottom z (worst)", 0,
max(abs(bbx(o).Min.Z - FB - vt["z0"]) for o in cl[vt["mark"]]))
bb = bbx(cl[vt["mark"]][0])
check(f"{vt['mark']} vertical extent a", vt["a"], bb.Max.Z - bb.Min.Z)
if "legs" in vt: # both legs run towards +Y from the same point: Y extent = the longer leg
check(f"{vt['mark']} leg extent along Y (longer leg)", max(vt["legs"].values()), bb.Max.Y - bb.Min.Y)
ft = [o for o in all_objects(doc) if o.Attributes.GetUserString("id") == f"{PID}-FT"][0].Geometry
cover = {}
for m in SCHED:
bad, cov = 0, 1e9
r = DIA[m] / 2
for o in cl[m]:
c = o.Geometry
for t in c.DivideByCount(max(4, int(c.GetLength() / 200)), True):
p = c.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 ft.IsPointInside(q, 0.01, False):
bad += 1
cov = min(cov, p.DistanceTo(ft.ClosestPoint(p)) - r)
cover[m] = round(cov, 1)
check(f"{m} sample points outside footing", 0, bad, tol=0)
info("min cover to footing surface per mark (record; top face under the column counted)", json.dumps(cover))
wrong = 0
for o in all_objects(doc):
if (o.Attributes.GetUserString("id") or "").startswith(f"{PID}-R-"):
d = int(o.Attributes.GetUserString("dia")[1:])
c = o.Attributes.ObjectColor
if o.Attributes.ColorSource != ObjectColorSource.ColorFromObject or (c.R, c.G, c.B) != PALETTE.get(d):
wrong += 1
check("rebar pipe colour by diameter mismatches (all rebar)", 0, wrong, tol=0)
write_result(RESULT_PATH, results, dict(cover=cover))
main()