Files
Pier_v1/Code/교각모델링앱/engine/rhino/checks/g3_poly.py
T
ahnyongjooandClaude Sonnet 5.5 964a841de1 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>
2026-10-28 11:20:18 +09:00

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()