"""G1 gate checker: concrete, piles, pedestals and pads (checks C1-C7) inside Rhino 8. Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Independent expected values from EXPECTED_PATH section G1 (drawing texts / DXF); generator params are not read. Ported from Code/교각모델링/verify_rhino.py (same checks and order; fingerprint part moved to the gate runner). """ 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, TOL_MM, G = X["pier_id"], X["tol_mm"], X["G1"] results = [] f, c_, cl_, pl_ = G["footing"], G["coping"], G["column"], G["piles"] FT_TOP = f["z0"] + f["thickness"] EXPECTED = { "footing": dict(dx=f["dx"], dy=f["dy"], z0=f["z0"], z1=FT_TOP), "lean": dict(dx=f["dx"] + 2 * G["lean"]["margin"], dy=f["dy"] + 2 * G["lean"]["margin"], z0=f["z0"] - G["lean"]["thickness"], z1=f["z0"]), "column": dict(d=cl_["d"], z0=FT_TOP, z1=FT_TOP + cl_["height"]), "coping": dict(dx=c_["dx"], dy=c_["dy"], z_bottom=c_["z_top_left"] - c_["rise_upper"] - c_["rise_lower"], z_tip_bottom=c_["z_top_left"] - c_["rise_upper"], z_top_left=c_["z_top_left"], z_top_right=c_["z_top_right"], z_top_center=c_["z_top_center"], flat_half=c_["dx"] / 2 - c_["flat_from_tip"], top_half=c_["dx"] / 2 - c_["end_inset"], radius=c_["radius"]), "piles": dict(nx=pl_["nx"], ny=pl_["ny"], pitch_x=pl_.get("pitch_x", pl_.get("pitch")), pitch_y=pl_.get("pitch_y", pl_.get("pitch")), edge=pl_["edge"], d=pl_["d"], length=pl_["length"]), } _a = G["dxf_arc_centres"] DXF_LEFT_ARC_CENTER = (_a["left"][0] - _a["datum_x"], _a["datum_el"] + (_a["left"][1] - _a["datum_y"])) DXF_RIGHT_ARC_CENTER = (_a["right"][0] - _a["datum_x"], _a["datum_el"] + (_a["right"][1] - _a["datum_y"])) S = G["seats"] TABLE = {int(k): tuple(v) for k, v in S["table"].items()} NG = S["girders"] STEP_TOP = {int(k): v for k, v in S["step_top"].items()} STEP_X = {int(k): tuple(v) for k, v in S["step_x"].items()} ROW_Y = S["row_y"] PITCH = S["pitch_chain"][1] / S["pitch_chain"][0] def objs(prefix): return [o for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "").startswith(prefix)] def brep(o): return o.Geometry if isinstance(o.Geometry, rg.Brep) else rg.Brep.TryConvertBrep(o.Geometry) def vol(b): return rg.VolumeMassProperties.Compute(b).Volume def bbox(b): return b.GetBoundingBox(True) def check(cid, name, expected, actual, tol=TOL_MM): ok = abs(expected - actual) <= tol results.append(dict(check=cid, item=name, expected=round(expected, 3), actual=round(actual, 3), diff=round(actual - expected, 3), tol=tol, result="PASS" if ok else "FAIL")) def flag(cid, name, ok, detail): results.append(dict(check=cid, item=name, detail=detail, result="PASS" if ok else "FAIL")) def check_seats(cp): n0 = len(PID) + 3 seats = {int(o.Attributes.GetUserString("id")[n0:]): brep(o) for o in objs(f"{PID}-BS")} pads = {int(o.Attributes.GetUserString("id")[n0:]): brep(o) for o in objs(f"{PID}-BP")} nos = sorted(TABLE) flag("C7", "pedestal / pad counts", sorted(seats) == nos and sorted(pads) == nos, f"seats={len(seats)} pads={len(pads)}") allb = list(seats.values()) + list(pads.values()) flag("C7", "IsSolid pedestals+pads", all(b.IsSolid for b in allb), f"{sum(b.IsSolid for b in allb)}/{2 * len(nos)}") m0, m1 = S["pad_margin"] pa, pc = S["pedestal_plan"] half_w = c_["dy"] / 2 pad_x = {} for no, (elx, h1, h2, a, b) in TABLE.items(): g = (no - 1) % NG + 1 row = "front" if no <= NG else "rear" s, p = seats[no], pads[no] sb, pb = bbox(s), bbox(p) check("C7", f"NO.{no} pedestal bottom = step {g}", STEP_TOP[g], sb.Min.Z) check("C7", f"NO.{no} pedestal top = step + h1", STEP_TOP[g] + h1, sb.Max.Z) check("C7", f"NO.{no} pad top = EL.'X'", elx, pb.Max.Z) flag("C7", f"NO.{no} pedestal within step {g} / coping width", STEP_X[g][0] - TOL_MM <= sb.Min.X and sb.Max.X <= STEP_X[g][1] + TOL_MM and -half_w - TOL_MM <= sb.Min.Y and sb.Max.Y <= half_w + TOL_MM, f"x {sb.Min.X:.1f}..{sb.Max.X:.1f} y {sb.Min.Y:.1f}..{sb.Max.Y:.1f}") area = pa / 1000 * pc / 1000 check("C7", f"NO.{no} pedestal half area (m2)", area, vol(s) / h1 / 1e6, tol=area * 0.002) mm_ = (m0 + m1) / 2 pv = h2 / 6 * ((a + m0) * (b + m0) + (a + m1) * (b + m1) + 4 * (a + mm_) * (b + mm_)) check("C7", f"NO.{no} pad volume (prismatoid A,B)", pv, vol(p), tol=pv * 0.001) c = rg.VolumeMassProperties.Compute(p).Centroid check("C7", f"NO.{no} pad centre Y (S1)", ROW_Y[row], c.Y, tol=2.0) pad_x.setdefault(row, {})[g] = c.X check("C7", f"NO.{no} overlap pedestal-coping (mm3)", 0, sum(vol(x) for x in (rg.Brep.CreateBooleanIntersection(s, cp, 0.01) or [])), tol=1.0) check("C7", f"NO.{no} overlap pad-pedestal (mm3)", 0, sum(vol(x) for x in (rg.Brep.CreateBooleanIntersection(p, s, 0.01) or [])), tol=1.0) for row, xs in pad_x.items(): for g in range(1, NG): check("C7", f"{row} pad pitch girder {g}-{g + 1}", PITCH, xs[g + 1] - xs[g], tol=1.0) first = S["rear_first_from_tip"] for g in range(1, NG + 1): check("C7", f"rear pad centre X girder {g} (dim {first / 1000:.3f}+{PITCH / 1000:.3f}n)", -c_["dx"] / 2 + first + (g - 1) * PITCH, pad_x["rear"][g], tol=2.0) for row, rng in (("front", range(1, NG + 1)), ("rear", range(NG + 1, 2 * NG + 1))): cxs = [rg.VolumeMassProperties.Compute(seats[n]).Centroid.X for n in rng] for g in range(NG - 1): check("C7", f"{row} pedestal pitch girder {g + 1}-{g + 2}", PITCH, cxs[g + 1] - cxs[g], tol=1.0) def main(): ids = {k: f"{PID}-{k}" for k in ("LC", "FT", "CL", "CP", "PL")} lc, ft, cl, cp = (brep(objs(ids[k])[0]) for k in ("LC", "FT", "CL", "CP")) piles = [brep(o) for o in objs(ids["PL"])] e = EXPECTED p = e["piles"] counts = {ids[k]: len(objs(ids[k])) for k in ("LC", "FT", "CL", "CP", "PL")} exp_counts = {ids["LC"]: 1, ids["FT"]: 1, ids["CL"]: 1, ids["CP"]: 1, ids["PL"]: p["nx"] * p["ny"]} flag("C5", "object counts", counts == exp_counts, str(counts)) all_b = [lc, ft, cl, cp] + piles flag("C2", f"IsSolid all {len(all_b)}", all(b.IsSolid for b in all_b), f"{sum(b.IsSolid for b in all_b)}/{len(all_b)}") for key, b in (("footing", ft), ("lean", lc)): bb = bbox(b) check("C1", f"{key} X", e[key]["dx"], bb.Max.X - bb.Min.X) check("C1", f"{key} Y", e[key]["dy"], bb.Max.Y - bb.Min.Y) check("C1", f"{key} Zbot", e[key]["z0"], bb.Min.Z) check("C1", f"{key} Ztop", e[key]["z1"], bb.Max.Z) check("C1", f"{key} centre X", 0, (bb.Max.X + bb.Min.X) / 2) check("C1", f"{key} centre Y", 0, (bb.Max.Y + bb.Min.Y) / 2) bb = bbox(cl) check("C1", "column diameter X", e["column"]["d"], bb.Max.X - bb.Min.X) check("C1", "column diameter Y", e["column"]["d"], bb.Max.Y - bb.Min.Y) check("C1", "column Zbot", e["column"]["z0"], bb.Min.Z) check("C1", "column Ztop", e["column"]["z1"], bb.Max.Z) bb = bbox(cp) check("C1", "coping length X", e["coping"]["dx"], bb.Max.X - bb.Min.X) check("C1", "coping width Y", e["coping"]["dy"], bb.Max.Y - bb.Min.Y) check("C1", "coping Zbot", e["coping"]["z_bottom"], bb.Min.Z) check("C1", "coping Ztop (right end)", e["coping"]["z_top_right"], bb.Max.Z) xs = sorted({round((bbox(b).Min.X + bbox(b).Max.X) / 2, 1) for b in piles}) ys = sorted({round((bbox(b).Min.Y + bbox(b).Max.Y) / 2, 1) for b in piles}) flag("C1", "pile grid", len(xs) == p["nx"] and len(ys) == p["ny"], f"x={xs} y={ys}") check("C1", "pile edge X", p["edge"], xs[0] - (-e["footing"]["dx"] / 2)) check("C1", "pile edge Y", p["edge"], ys[0] - (-e["footing"]["dy"] / 2)) for a, b2 in zip(xs, xs[1:]): check("C1", "pile pitch X", p["pitch_x"], b2 - a) for a, b2 in zip(ys, ys[1:]): check("C1", "pile pitch Y", p["pitch_y"], b2 - a) pb = bbox(piles[0]) check("C1", "pile diameter", p["d"], pb.Max.X - pb.Min.X) check("C1", "pile length", p["length"], pb.Max.Z - pb.Min.Z) check("C1", "pile head embedment", pl_["head_embedment"], pb.Max.Z - e["footing"]["z0"]) # C4 coping front section at Y=0 (re-projection to drawing) plane = rg.Plane(rg.Point3d(0, 0, 0), rg.Vector3d.YAxis) ok, curves, pts = rg.Intersect.Intersection.BrepPlane(cp, plane, 0.01) sec = rg.Curve.JoinCurves(curves, 0.1) flag("C4", "section closed single loop", len(sec) == 1 and sec[0].IsClosed, f"{len(sec)} loop(s)") c = e["coping"] def z_at(x, upper): line = rg.LineCurve(rg.Point3d(x, 0, 0), rg.Point3d(x, 0, 60000)) zs = sorted(ev.PointA.Z for ev in rg.Intersect.Intersection.CurveCurve(sec[0], line, 0.01, 0.01)) return zs[-1] if upper else zs[0] sx = {k: (v[0] + v[1]) / 2 for k, v in STEP_X.items()} # sample each top step at its middle check("C4", "bottom flat at X=0", c["z_bottom"], z_at(0, False)) check("C4", f"bottom flat at X=+{c['flat_half'] - 1:.0f}", c["z_bottom"], z_at(c["flat_half"] - 1, False)) check("C4", f"bottom at X=-{c['top_half'] + 199:.0f} (tip)", c["z_tip_bottom"], z_at(-c["top_half"] - 199, False), tol=2.0) check("C4", f"top left step (X={sx[1]:.0f})", c["z_top_left"], z_at(sx[1], True)) check("C4", "top centre step (X=0)", c["z_top_center"], z_at(0, True)) check("C4", f"top right step (X={sx[NG]:.0f})", c["z_top_right"], z_at(sx[NG], True)) worst = 0.0 step = (c["dx"] / 2 - c["flat_half"] - 200) / 50 for side, (cx, cz) in ((1, DXF_RIGHT_ARC_CENTER), (-1, DXF_LEFT_ARC_CENTER)): for k in range(1, 50): x = side * (c["flat_half"] + 100 + k * step) zb = z_at(x, False) worst = max(worst, abs(math.hypot(x - cx, zb - cz) - c["radius"])) check("C4", "soffit radius max deviation vs DXF ARC centre (98 samples)", 0, worst) # C3 joints and overlaps def inter_vol(a, b): r = rg.Brep.CreateBooleanIntersection(a, b, 0.01) return sum(vol(x) for x in r) if r else 0.0 check("C3", "LC top = FT bottom", bbox(lc).Max.Z, bbox(ft).Min.Z, tol=0.01) check("C3", "FT top = CL bottom", bbox(ft).Max.Z, bbox(cl).Min.Z, tol=0.01) check("C3", "CL top = CP bottom", bbox(cl).Max.Z, bbox(cp).Min.Z, tol=0.01) check("C3", "overlap LC-FT (mm3)", 0, inter_vol(lc, ft), tol=1.0) check("C3", "overlap FT-CL (mm3)", 0, inter_vol(ft, cl), tol=1.0) check("C3", "overlap CL-CP (mm3)", 0, inter_vol(cl, cp), tol=1.0) check("C3", "overlap LC-piles (mm3)", 0, sum(inter_vol(lc, b) for b in piles), tol=1.0) emb = len(piles) * math.pi * (p["d"] / 2) ** 2 * pl_["head_embedment"] check("C3", "intended overlap FT-piles (mm3)", emb, sum(inter_vol(ft, b) for b in piles), tol=emb * 0.001) flag("C3", "column within coping flat zone", e["column"]["d"] / 2 <= c["flat_half"] and e["column"]["d"] / 2 <= c["dy"] / 2, f"r={e['column']['d'] / 2:.0f} <= {c['flat_half']:.0f} (X), {c['dy'] / 2:.0f} (Y)") # C6 volumes vs analytic formulas fv = f["dx"] / 1000 * f["dy"] / 1000 * f["thickness"] / 1000 check("C6", "footing volume m3", fv, vol(ft) / 1e9, tol=fv * 0.001) cv_ = math.pi * (cl_["d"] / 2000) ** 2 * cl_["height"] / 1000 check("C6", "column volume m3", cv_, vol(cl) / 1e9, tol=round(cv_, 2) * 0.001) lv = (e["lean"]["dx"] / 1000 * e["lean"]["dy"] / 1000 * G["lean"]["thickness"] / 1000 - len(piles) * math.pi * (p["d"] / 2000) ** 2 * G["lean"]["thickness"] / 1000) check("C6", "lean volume m3", lv, vol(lc) / 1e9, tol=lv * 0.001) poly = [tuple(q) for q in G["coping_outline"]] area = 0.5 * abs(sum(x1 * y2 - x2 * y1 for (x1, y1), (x2, y2) in zip(poly, poly[1:] + poly[:1]))) (x1, z1), (x2, z2) = poly[1], poly[2] chord = math.hypot(x2 - x1, z2 - z1) theta = 2 * math.asin(chord / 2 / c["radius"]) seg = c["radius"] ** 2 / 2 * (theta - math.sin(theta)) cv = (area - 2 * seg) * c["dy"] / 1e9 check("C6", "coping volume m3", cv, vol(cp) / 1e9, tol=cv * 0.001) if objs(f"{PID}-BS") or objs(f"{PID}-BP"): check_seats(cp) write_result(RESULT_PATH, results, dict( volumes_m3={k: round(vol(b) / 1e9, 3) for k, b in (("lean", lc), ("footing", ft), ("column", cl), ("coping", cp))}, pile_volume_each_m3=round(vol(piles[0]) / 1e9, 3))) main()