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

223 lines
14 KiB
Python

"""G4 gate checker, Okseong P1 coping + bearing-seat rebar (2026-10-14): C1~C20 and S1~S8 as centreline polylines (plan skew 18 deg).
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G4, plus the G1 pedestal table) come from the schedule
(L, N, polyline length = L - lap), the drawn levels / positions of the rebar drawing, the section dimension chains and the user
decisions H1-H6; generator params are not read. Coping bars are sheared like the coping: x_model = x - tan(skew) * y, so the
section x of a bar is bbox mid X + tan * bbox mid Y. Seat bars are checked in the pedestal frame.
Checks (C10): counts / pipes / polyline length, top layers (rows, ends, legs), 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 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 = X["pier_id"], X["G4"]
SCHED = {m: tuple(v) for m, v in G["schedule"].items()}
T = math.tan(math.radians(G["skew_deg"]))
COS = math.cos(math.radians(G["skew_deg"]))
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 xs(o):
"""Section x of a coping bar (shear removed)."""
mx, my = mid(o)
return mx + T * my
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 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 L - lap (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, y25 = G["top"], G["y25"]
for m in ("C1", "C2", "C3", "C4", "C5", "C6"):
s = tp[m]
want = [y for i, y in enumerate(y25) if i % 2 == s["parity"]]
got = [mid(o)[1] for o in cl[m]]
check(f"{m} y rows vs chain (worst, n={len(got)}/{len(want)})", 0, worst(got, 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']} sheared (worst)", 0,
max(max(abs(bb(o).Min.X + T * mid(o)[1] + tp["x_end"]), abs(bb(o).Max.X + T * mid(o)[1] - 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"]], y25))
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 + T * mid(o)[1] - c19["x"][0]), abs(bb(o).Max.X + T * mid(o)[1] - c19["x"][1])) for o in cl["C19"]))
check("C19 leg 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 + T * mid(o)[1] - pc["x"][0]) < 3]
flag(f"C20 piece {k + 1}: 25 bars at EL {pc['z']}, x {pc['x']}", len(grp) == 25, f"{len(grp)}")
if grp:
check(f"C20 piece {k + 1} x ends (worst)", 0, max(max(abs(bb(o).Min.X + T * mid(o)[1] - pc["x"][0]), abs(bb(o).Max.X + T * mid(o)[1] - 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], y25))
# 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"]))
normal = [o for o in cl["C7"] if abs(mid(o)[1]) < 1400]
check("C7 top end EL 20.3672 (bars not on the side face, worst)", 0,
max(abs(bb(o).Max.Z - (c7["top_end_z"] + (0 if abs(mid(o)[1]) <= 1000 else 365.2))) for o in normal))
check("C7 x ends +-5875 (worst)", 0, max(max(abs(bb(o).Min.X + T * mid(o)[1] + c7["x_end"]), abs(bb(o).Max.X + T * mid(o)[1] - c7["x_end"])) for o in normal))
edge = [o for o in cl["C7"] if abs(mid(o)[1]) >= 1400]
check("C7 side-face bars end where the slope meets the coping top (x end)", 0,
max(max(abs(bb(o).Min.X + T * mid(o)[1] + c7["capped"]["x_end"]), abs(bb(o).Max.X + T * mid(o)[1] - c7["capped"]["x_end"])) for o in edge) if edge else 1e9, tol=8)
got = sorted(round(mid(o)[1]) for o in cl["C7"])
flag("C7 y rows: 11 @250 + 2 on the side face", got == sorted([-1250 + 250 * k for k in range(11)] + [-1525, 1525]), f"{got}")
# stirrups
st = G["stirrups"]
def top_of(x):
return next(z for a, b, z in st["top"] if a <= x < b)
def bottom_of(x):
s = st["bottom"]
return s["flat_z"] if abs(x) <= s["flat_half"] else s["flat_z"] + s["slope"] * (abs(x) - s["flat_half"])
for zone, (rect, pent) in st["marks"].items():
want = st["x"][zone]
got_r = [xs(o) for o in cl[rect]]
check(f"{rect} positions = drawn x ({len(want)}, worst)", 0, worst(got_r, want))
got_p = [xs(o) - st["pent_dx"] for o in cl[pent]]
check(f"{pent} positions = drawn x + {st['pent_dx']}, 2 per x (worst)", 0, worst(got_p, [x for x in want for _ in (0, 1)]))
dt = max(abs(bb(o).Max.Z - top_of(xs(o))) for o in cl[rect] + cl[pent]) if cl[rect] else 1e9
db = max(abs(bb(o).Min.Z - bottom_of(xs(o) - (st["pent_dx"] if o in cl[pent] else 0))) for o in cl[rect] + cl[pent]) if cl[rect] else 1e9
check(f"{rect}/{pent} top = coping top - 97.5 (drawn zones, worst)", 0, dt)
check(f"{rect}/{pent} bottom = soffit + 120 - 22.5 (worst)", 0, db, tol=2.5)
check(f"{rect} legs y +-{st['rect_y']} (worst)", 0, max(max(abs(bb(o).Max.Y - st["rect_y"] - 0), abs(bb(o).Min.Y + st["rect_y"])) for o in cl[rect]) if cl[rect] else 1e9, tol=2)
lo, hi = st["pent_y"]
okp = all((abs(bb(o).Min.Y - lo) < 2 and abs(bb(o).Max.Y - hi) < 2) or (abs(bb(o).Max.Y + lo) < 2 and abs(bb(o).Min.Y + hi) < 2) for o in cl[pent])
flag(f"{pent} y between +-{lo} and +-{hi} (A-A)", 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 - 2 * T * (bb(o).Max.Y - bb(o).Min.Y) / 2, 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 (corner fillets <= 50) / half width ({len(wl)})", len(gl) == len(wl) and all(abs(a[0] - b[0]) <= 1 and abs(a[1] - b[1]) <= 50 and abs(a[2] - b[2]) <= 1 for a, b in zip(gl, wl)), f"{gl[:2]}")
flat = max((bb(o).Max.Z - bb(o).Min.Z) for o in cl[mloop] + cl[mtie])
check(f"{mloop}/{mtie} horizontal (worst dz)", 0, flat, 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"]: # lower rows have the legs UP (the straight part is the lowest level)
z = bb(o).Max.Z if bb(o).Max.Z - bb(o).Min.Z < 1 or abs((bb(o).Max.Z - bb(o).Min.Z) - 370) < 1 and bb(o).Min.Z > 20000 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
yf, yr = se["y"]["front"], se["y"]["rear"]
zp = pb.Min.Z + se["h1"]["front"] + (se["h1"]["rear"] - se["h1"]["front"]) * (cy - yf) / (yr - yf)
ml, ms, mll, mss = se["marks_heavy"] if k == se["heavy"] else se["marks_light"]
dl_, ds_ = G["dia"][ml], G["dia"][ms]
grp = lambda m: [o for o in cl[m] if o.Attributes.GetUserString("occurrence").startswith(f"P{k}-")]
gl, gs, gll, gss = grp(ml), grp(ms), grp(mll), grp(mss)
flag(f"pedestal {k}: {ml} {ms} {mll} {mss} counts 11 / 21 / 22 / 42", (len(gl), len(gs), len(gll), len(gss)) == (11, 21, 22, 42), f"{len(gl)} {len(gs)} {len(gll)} {len(gss)}")
check(f"pedestal {k} {ml} level {se['top_below']} below the pedestal top (worst)", 0, max(abs(bb(o).Max.Z - (zp - se["top_below"])) for o in gl), tol=1.5)
check(f"pedestal {k} {ml} legs {se['leg']} (worst)", 0, max(abs((bb(o).Max.Z - bb(o).Min.Z) - se["leg"]) for o in gl + gs))
check(f"pedestal {k} {ml} lateral positions (worst)", 0, worst([(mid(o)[0] - cx) * COS for o in gl], se["long_t"]), tol=1.5)
check(f"pedestal {k} {ml} length along the end face (worst)", 0, max(abs((bb(o).Max.Y - bb(o).Min.Y) - se["long_len"] * COS) for o in gl), tol=1.5)
check(f"pedestal {k} {ms} level {ds_ if False else dl_} under {ml} (worst)", 0, max(abs(bb(o).Max.Z - (zp - se["top_below"] - dl_)) for o in gs), tol=1.5)
check(f"pedestal {k} {ms} y positions (worst)", 0, worst([mid(o)[1] - cy for o in gs], se["short_o"]), tol=1.5)
check(f"pedestal {k} {ms} x length (worst)", 0, max(abs((bb(o).Max.X - bb(o).Min.X) - se["short_len"]) for o in gs), tol=1.5)
for layer, dz in enumerate(se["lower_below"]):
l1 = [o for o in gll if abs(bb(o).Min.Z - (zp - dz)) < 1.5]
l2 = [o for o in gss if abs(bb(o).Min.Z - (zp - dz + (G["dia"][mll] + G["dia"][mss]) / 2)) < 1.5]
flag(f"pedestal {k} layer {layer + 1} EL pedestal top - {dz}: {mll} 11 / {mss} 21", (len(l1), len(l2)) == (11, 21), f"{len(l1)} {len(l2)}")
if l1:
check(f"pedestal {k} {mll} layer {layer + 1} positions (worst)", 0, worst([(mid(o)[0] - cx) * COS for o in l1], se["low_long_t"]), tol=1.5)
check(f"pedestal {k} {mll} layer {layer + 1} length (worst)", 0, max(abs((bb(o).Max.Y - bb(o).Min.Y) - se["low_long_len"] * COS) for o in l1), tol=1.5)
if l2:
check(f"pedestal {k} {mss} layer {layer + 1} y positions (worst)", 0, worst([mid(o)[1] - cy for o in l2], se["low_short_o"]), 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()