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