Files
Pier_v1/Code/교각모델링앱/engine/rhino/checks/g4_sj.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

208 lines
12 KiB
Python

"""G4 gate checker, Saejae-1 P1 coping + bearing-seat rebar (2026-10-15): C1~C20 and S1~S2 as centreline polylines (rectangular plan, no skew).
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G4) come from the schedule (L, N, polyline length = a + legs),
the drawn levels / positions of the rebar drawing, the section dimension chains and the Okseong rules H1-H6 applied again; generator params
are not read. Checks (C10): counts / pipes / polyline length, top layers, C19 / C20, C7, stirrups (x, top, bottom, y), side loops and ties,
C18, seat bars per pedestal, containment in the coping + pedestals, minimum cover (record).
"""
import json
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 = X["pier_id"], X["G4"]
SCHED = {m: tuple(v) for m, v in G["schedule"].items()}
results = []
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 bb(o):
return o.Geometry.GetBoundingBox(True)
def mid(o):
b = bb(o)
return (b.Min.X + b.Max.X) / 2, (b.Min.Y + b.Max.Y) / 2
def worst(got, want):
got, want = sorted(got), sorted(want)
return max((abs(a - b) for a, b in zip(got, want)), default=1e9) if len(got) == len(want) else 1e9
def occ(o):
return o.Attributes.GetUserString("occurrence") or ""
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 m in G["fixed_length"]:
check(f"{m} polyline length vs a + legs (worst)", 0, max(abs(v - G["fixed_length"][m]) for v in dl) if dl else 1e9)
elif dl:
info(f"{m} mean polyline 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 C1~C6
tp, y23 = G["top"], G["y23"]
for m in ("C1", "C2", "C3", "C4", "C5", "C6"):
s = tp[m]
want = [y for i, y in enumerate(y23) if i % 2 == s["parity"]]
check(f"{m} y rows vs chain (worst, n={len(cl[m])}/{len(want)})", 0, worst([mid(o)[1] for o in cl[m]], want))
check(f"{m} level of the straight part (worst)", 0, max(abs(bb(o).Max.Z - s["z"]) for o in cl[m]))
check(f"{m} leg {s['leg']} (worst)", 0, max(abs((bb(o).Max.Z - bb(o).Min.Z) - s["leg"]) for o in cl[m]))
check(f"{m} x ends +-{tp['x_end']} (worst)", 0, max(max(abs(bb(o).Min.X + tp["x_end"]), abs(bb(o).Max.X - tp["x_end"])) for o in cl[m]))
# C19, C20
c19 = G["c19"]
check("C19 y rows vs chain (worst)", 0, worst([mid(o)[1] for o in cl["C19"]], y23))
check("C19 level (worst)", 0, max(abs(bb(o).Max.Z - c19["z"]) for o in cl["C19"]))
check("C19 x ends (worst)", 0, max(max(abs(bb(o).Min.X - c19["x"][0]), abs(bb(o).Max.X - c19["x"][1])) for o in cl["C19"]))
check("C19 legs 370 (worst)", 0, max(abs((bb(o).Max.Z - bb(o).Min.Z) - G["c20_leg"]) for o in cl["C19"]))
for k, pc in enumerate(G["c20"]):
grp = [o for o in cl["C20"] if abs(bb(o).Max.Z - pc["z"]) < 0.5 and abs(bb(o).Min.X - pc["x"][0]) < 3]
flag(f"C20 piece {k + 1}: 23 bars at EL {pc['z']}, x {pc['x']}", len(grp) == 23, f"{len(grp)}")
if grp:
check(f"C20 piece {k + 1} x ends (worst)", 0, max(max(abs(bb(o).Min.X - pc["x"][0]), abs(bb(o).Max.X - pc["x"][1])) for o in grp))
check(f"C20 piece {k + 1} leg 370 (worst)", 0, max(abs((bb(o).Max.Z - bb(o).Min.Z) - G["c20_leg"]) for o in grp))
check(f"C20 piece {k + 1} y rows (worst)", 0, worst([mid(o)[1] for o in grp], y23))
# C7
c7 = G["c7"]
check("C7 lowest level per bar vs drawn (worst)", 0, worst([bb(o).Min.Z for o in cl["C7"]], c7["flat_z"]))
flat = [o for o in cl["C7"] if abs(mid(o)[1]) <= 875]
check("C7 top end EL 31.8453 (flat bars, worst)", 0, max(abs(bb(o).Max.Z - c7["top_end_z"]) for o in flat))
check("C7 x ends +-5875 (flat bars, worst)", 0, max(max(abs(bb(o).Min.X + c7["x_end"]), abs(bb(o).Max.X - c7["x_end"])) for o in flat))
raised = [o for o in cl["C7"] if 875 < abs(mid(o)[1]) <= 1125]
check("C7 raised bars (y +-1125) top end (worst)", 0, max(abs(bb(o).Max.Z - c7["raised_top_z"]) for o in raised))
edge = [o for o in cl["C7"] if abs(mid(o)[1]) > 1125]
check("C7 side-face bars capped at the cap level (worst)", 0, max(abs(bb(o).Max.Z - c7["cap_z"]) for o in edge), tol=1.5)
flag("C7 y rows: 12 @250", sorted(round(mid(o)[1]) for o in cl["C7"]) == [-1375 + 250 * k for k in range(12)])
# stirrups
st = G["stirrups"]
def zone(x):
return "left" if x < st["zone_cut"][0] else ("centre" if x <= st["zone_cut"][1] else "right")
def top_of(x):
return st["step_z"][sum(1 for b in st["step_b"] if x >= b)] - st["top_below"]
def bottom_of(x):
if abs(x) <= st["flat_half"]:
return st["flat_bottom"]
return st["flat_bottom"] - st["cover_n"] + st["soffit_slope"] * (abs(x) - st["flat_half"]) + st["cover_n"] * (1 + st["soffit_slope"] ** 2) ** 0.5
for zn, (rect, pent) in st["marks"].items():
sx = [x for x in st["x"] if zone(x) == zn]
want_r = [x + o for x in sx for o in st["off"]["R"]]
want_p = [x + o for x in sx for o in st["off"]["P"]]
check(f"{rect} positions = stations + x offset ({len(want_r)}, worst)", 0, worst([mid(o)[0] for o in cl[rect]], want_r), tol=0.5)
check(f"{pent} positions = stations + x offset ({len(want_p)}, worst)", 0, worst([mid(o)[0] for o in cl[pent]], want_p), tol=0.5)
dt = db = 0.0
for o in cl[rect] + cl[pent]:
xb = mid(o)[0]
dt = max(dt, abs(bb(o).Max.Z - top_of(xb)))
db = max(db, abs(bb(o).Min.Z - bottom_of(xb)))
check(f"{rect}/{pent} top = step top - {st['top_below']:.0f} (worst)", 0, dt, tol=1.5)
check(f"{rect}/{pent} bottom = soffit + cover along the normal (worst, sloped part <= 4)", 0, db, tol=4.0)
okr = all(any(abs(bb(o).Min.Y - a) < 2 and abs(bb(o).Max.Y - b) < 2 for a, b in st["rect_y"]) for o in cl[rect])
flag(f"{rect} y legs {st['rect_y']} (B-B)", okr)
pin, pout = st["pent_in"], st["pent_out"]
okp = all((abs(bb(o).Min.Y + pin) < 2 and abs(bb(o).Max.Y - pout) < 2) or (abs(bb(o).Max.Y - pin) < 2 and abs(bb(o).Min.Y + pout) < 2) for o in cl[pent])
flag(f"{pent} y between {pin:.0f} and {pout:.0f} (B-B)", okp)
# side loops and ties
lp = G["loops"]
for key in ("upper", "lower"):
sec = lp[key]
mloop, mtie = sec["marks"]
gl = sorted((round(bb(o).Min.Z, 1), round(bb(o).Max.X - bb(o).Min.X, 1), round((bb(o).Max.Y - bb(o).Min.Y) / 2, 1)) for o in cl[mloop])
wl = sorted((z, round(2 * x, 1), y) for z, x, y in zip(sec["z"], sec["x"], sec["y"]))
flag(f"{mloop} loops: level / x length / half width ({len(wl)})", len(gl) == len(wl) and all(abs(a[0] - b[0]) <= 1 and abs(a[1] - b[1]) <= 1 and abs(a[2] - b[2]) <= 1 for a, b in zip(gl, wl)), f"{gl[:2]}")
check(f"{mloop}/{mtie} horizontal (worst dz)", 0, max((bb(o).Max.Z - bb(o).Min.Z) for o in cl[mloop] + cl[mtie]), tol=0.01)
gt = sorted((round(bb(o).Min.Z, 1), round(mid(o)[1], 1), round(bb(o).Max.X - bb(o).Min.X, 1)) for o in cl[mtie])
wt = sorted((z, y, round(2 * x, 1)) for z, x in zip(sec["z"], sec["x"]) for y in lp["tie_y"])
flag(f"{mtie} ties: level / y / x length ({len(wt)})", len(gt) == len(wt) and all(abs(a[0] - b[0]) <= 1 and abs(a[1] - b[1]) <= 1 and abs(a[2] - b[2]) <= 1 for a, b in zip(gt, wt)), f"{len(gt)}")
# C18
c18 = G["c18"]
got = {}
for o in cl["C18"]: # upper rows: legs DOWN (straight part highest); lower rows: legs UP (lowest)
z = bb(o).Max.Z if bb(o).Max.Z > 32100 else bb(o).Min.Z
got.setdefault(f"{z:.1f}", []).append(o)
flag("C18 levels and counts", {k: len(v) for k, v in got.items()} == c18["levels"], f"{ {k: len(v) for k, v in got.items()} }")
check("C18 widths per level (worst; fillets on the slanted legs <= 30)", 0, max(abs((bb(o).Max.Y - bb(o).Min.Y) - c18["width"][k]) for k, v in got.items() if k in c18["width"] for o in v), tol=30)
# seats
se = G["seats"]
ped = {}
for o in all_objects(doc):
oid = o.Attributes.GetUserString("id") or ""
if oid.startswith(f"{PID}-BS"):
ped[int(oid[len(PID) + 3:])] = o.Geometry.GetBoundingBox(True)
flag("5 pedestals found", sorted(ped) == [1, 2, 3, 4, 5], f"{sorted(ped)}")
for k, pb in sorted(ped.items()):
cx, cy = (pb.Min.X + pb.Max.X) / 2, (pb.Min.Y + pb.Max.Y) / 2
g1 = [o for o in cl["S1"] if occ(o).startswith(f"P{k}-")]
g2 = [o for o in cl["S2"] if occ(o).startswith(f"P{k}-")]
flag(f"pedestal {k}: S1 11 / S2 20", (len(g1), len(g2)) == (11, 20), f"{len(g1)} {len(g2)}")
zt = pb.Max.Z - se["top_below"]
check(f"pedestal {k} S1 level {se['top_below']} below the pedestal top (worst, plane slope <= 20)", 0, max(abs(bb(o).Max.Z - zt) for o in g1), tol=20)
check(f"pedestal {k} S1 legs {se['leg']} (worst)", 0, max(abs((bb(o).Max.Z - bb(o).Min.Z) - se["leg"]) for o in g1 + g2))
check(f"pedestal {k} S2 level 13 under S1 (worst)", 0, max(abs(bb(o).Max.Z - (min(bb(p).Max.Z for p in g1) - 13)) for o in g2), tol=1.5)
check(f"pedestal {k} S1 x positions (worst)", 0, worst([mid(o)[0] - cx for o in g1], se["s1_t"]), tol=1.5)
check(f"pedestal {k} S1 length along Y (worst)", 0, max(abs((bb(o).Max.Y - bb(o).Min.Y) - se["s1_len"]) for o in g1), tol=1.5)
check(f"pedestal {k} S2 y positions (worst)", 0, worst([mid(o)[1] - cy for o in g2], se["s2_o"]), tol=1.5)
check(f"pedestal {k} S2 length along X (worst)", 0, max(abs((bb(o).Max.X - bb(o).Min.X) - se["s2_len"]) for o in g2), tol=1.5)
# 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]
info("concrete solids for containment", f"{len(solids)} (coping + pedestals)")
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() / 200)), 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):
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()