"""G3 gate checker, footing rebar as centreline polylines with drawn position lists (Saejae-1 P1, 2026-10-15; adapted from g3_poly.py, Okseong P1): mats B1~B8, mid layers B11 / B12, perimeter loops B10, vertical ties B9 (6 rows, 8 / 7 per row alternating). Positions are explicit lists (expected G3 "pos" / "levels" / "b9") typed from the drawn points, not dimension chains. 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, 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 mid_(o): return (bb(o).Min.X + bb(o).Max.X) / 2, (bb(o).Min.Y + bb(o).Max.Y) / 2 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 = sorted(mt["pos"]) 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(): lift = md.get("lift", 0) 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((float(z) + lift, p) for z, ps in md["levels"].items() for p in ps) 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"] got9 = sorted((round(mid_(o)[0], 1), round(mid_(o)[1], 1)) for o in cl["B9"]) want9 = sorted((x, y) for k, y in enumerate(b9["rows"]) for x in (b9["cols_even"] if k % 2 == 0 else b9["cols_odd"])) flag(f"B9 grid {len(want9)} (rows {len(b9['rows'])}, 8 / 7 per row)", len(got9) == len(want9) and all(abs(g[0] - w[0]) <= 1 and abs(g[1] - w[1]) <= 1 for g, w in zip(got9, want9)), f"{len(got9)}") 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) check("B9 span along Y a (worst)", b9["a"], max((bb(o).Max.Y - bb(o).Min.Y for o in cl["B9"]), key=lambda h: abs(h - b9["a"])) 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()