- 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>
116 lines
6.0 KiB
Python
116 lines
6.0 KiB
Python
"""G3 gate checker, footing rebar as centreline polylines (Okseong P1, 2026-10-14): mats B1~B8, mid layers B11 / B12,
|
|
perimeter loops B10, vertical ties B9.
|
|
|
|
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G3) come from the schedule (L, N), the
|
|
front / side dimension chains (bar positions), the leg lengths b of the schedule, the drawn mat / loop levels and
|
|
the B9 grid rule with the user decisions F1-F4; generator params are not read.
|
|
Checks (C9): counts, original length vs L, per mat: positions vs chain, straight level, span, leg length and
|
|
direction; mid layers; B10 levels / size; B9 grid / height; containment in the footing; min cover (record).
|
|
"""
|
|
import json
|
|
import math
|
|
import os
|
|
import sys
|
|
|
|
import Rhino.Geometry as rg
|
|
|
|
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"]
|
|
SCHED = {m: tuple(v) for m, v in G["schedule"].items()}
|
|
results = []
|
|
|
|
|
|
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 bb(o):
|
|
return o.Geometry.GetBoundingBox(True)
|
|
|
|
|
|
def worst(a, b):
|
|
return max(abs(x - y) for x, y in zip(a, b)) if len(a) == len(b) and a else 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])}")
|
|
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]))
|
|
half = G["half_a"]
|
|
for name, mt in G["mats"].items():
|
|
objs = [o for m in mt["marks"] for o in cl[m]]
|
|
axis = mt["axis"]
|
|
pos = sorted(round((bb(o).Min.X + bb(o).Max.X) / 2 if axis == "Y" else (bb(o).Min.Y + bb(o).Max.Y) / 2, 1) for o in objs)
|
|
want = chain(mt["chain"])
|
|
check(f"{name} positions vs chain ({len(pos)}/{len(want)}, worst)", 0, worst(pos, want))
|
|
leg = mt["leg"]
|
|
straight = [bb(o).Min.Z if leg > 0 else bb(o).Max.Z for o in objs]
|
|
check(f"{name} straight part level {mt['z']} (worst)", 0, max(abs(z - mt["z"]) for z in straight) if straight else 1e9)
|
|
span = [(bb(o).Max.Y - bb(o).Min.Y) if axis == "Y" else (bb(o).Max.X - bb(o).Min.X) for o in objs]
|
|
check(f"{name} span 2 x {half} (worst)", 0, max(abs(s - 2 * half) for s in span) if span else 1e9)
|
|
legs = [(bb(o).Max.Z - bb(o).Min.Z) for o in objs]
|
|
check(f"{name} leg {abs(leg)} {'up' if leg > 0 else 'down'} (worst)", 0, max(abs(h - abs(leg)) for h in legs) if legs else 1e9)
|
|
a, b = mt["marks"]
|
|
ia = sorted(round((bb(o).Min.X + bb(o).Max.X) / 2 if axis == "Y" else (bb(o).Min.Y + bb(o).Max.Y) / 2, 1) for o in cl[a])
|
|
flag(f"{name} {a}/{b} alternate from the edge (F3)", ia == want[0::2], f"{a} {len(ia)}")
|
|
for m, md in G["mid"].items():
|
|
got = sorted((round(bb(o).Max.Z, 1), round((bb(o).Min.X + bb(o).Max.X) / 2 if md["axis"] == "Y" else (bb(o).Min.Y + bb(o).Max.Y) / 2, 1)) for o in cl[m])
|
|
want = sorted((md["z_hi"] if round(p) % 1000 == 0 else md["z_lo"], round(p, 1)) for p in chain(md["chain"]))
|
|
flag(f"{m} levels / positions ({len(want)})", len(got) == len(want) and all(abs(g[0] - w[0]) <= 1 and abs(g[1] - w[1]) <= 1 for g, w in zip(got, want)),
|
|
f"{got[:3]} vs {want[:3]}")
|
|
b10 = G["b10"]
|
|
zs = sorted(round(bb(o).Min.Z, 1) for o in cl["B10"])
|
|
check("B10 levels (worst)", 0, worst(zs, sorted(b10["levels"])))
|
|
check("B10 loop size X (worst)", 0, max(abs(bb(o).Max.X - bb(o).Min.X - b10["size"]) for o in cl["B10"]) if cl["B10"] else 1e9)
|
|
b9 = G["b9"]
|
|
cols = sorted({round((bb(o).Min.X + bb(o).Max.X) / 2, 1) for o in cl["B9"]})
|
|
flag(f"B9 columns {b9['n_cols']} @{b9['pitch_x']}", len(cols) == b9["n_cols"] and all(abs(b - a - b9["pitch_x"]) < 1 for a, b in zip(cols, cols[1:])), f"{len(cols)}")
|
|
check("B9 height top-bottom (worst)", b9["height"], max((bb(o).Max.Z - bb(o).Min.Z for o in cl["B9"]), key=lambda h: abs(h - b9["height"])) if cl["B9"] else 0)
|
|
flag("B9 hooks the top and bottom Y bars", all(bb(o).Max.Z > G["mats"]["y_top"]["z"] and bb(o).Min.Z < G["mats"]["y_bottom"]["z"] for o in cl["B9"]))
|
|
# containment and cover
|
|
ft = [o.Geometry for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "") == f"{PID}-FT"][0]
|
|
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() / 300)), 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 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 the footing", 0, bad, tol=0)
|
|
info("min cover to the footing surface per mark (record)", json.dumps(cover))
|
|
write_result(RESULT_PATH, results, dict(cover=cover))
|
|
|
|
|
|
main()
|