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