"""G1 gate checker, form "tapered T with shared pedestals": piles and plan skew optional (unified 2026-10-15 from g1_skew.py, Okseong P1, and this file, Saejae-1 P1): lean, footing, column, coping (straight soffit, stepped top, side taper), shared pedestals with a sloped top, pads. G1 expected "piles" absent = spread footing (no pile checks); coping.skew_deg 0 = rectangular plan. Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G1) come from dimension / EL texts and the bearing tables; the skew angle and the pedestal outlines are drawn values ([작도]). Generator params are not read. Checks: C5 population, C2 closed solids, C1 extents (lean, footing, column, piles), C4 coping (front section at Y=0, side width at X=0, skewed end faces), C3 joints / overlaps, C6 volumes, C7 pedestals / pads. """ 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, G = X["pier_id"], X["tol_mm"], X["G1"] F, LN, CO, C, PI = G["footing"], G["lean"], G["column"], G["coping"], G.get("piles") FT_TOP = F["z0"] + F["thickness"] ZB = FT_TOP + CO["height"] # column top = coping bottom ZT = ZB + C["soffit_rise"] FLAT, TIP, W, WB = C["bottom_flat_half"], C["tip_half"], C["width_y"] / 2, C["side_bottom_width"] / 2 K = math.tan(math.radians(C["skew_deg"])) # [작도] EDGES = [-TIP] + C["step_bounds"] + [TIP] # top step boundaries at Y = 0 (front dimension chain) STEPS = C["step_z"] # step levels (EL.'X' - h1 - h2 of the bearing rows) results = [] 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): 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 abs(actual - expected) <= tol else "FAIL")) def flag(cid, name, ok, detail=""): results.append(dict(check=cid, item=name, detail=detail, result="PASS" if ok else "FAIL")) def top_z(x): for a, b, z in zip(EDGES, EDGES[1:], STEPS): if a <= x <= b: return z return None def bottom_z(x): ax = abs(x) return ZB if ax <= FLAT else ZB + C["soffit_rise"] * (ax - FLAT) / (TIP - FLAT) def coping_volume_expected(): """Shear keeps the volume: integrate the unsheared plan area (front x extent x side width) over z.""" total, n = 0.0, 20000 z1 = max(STEPS) dz = (z1 - ZB) / n for i in range(n): z = ZB + (i + 0.5) * dz if z < ZT: xh = FLAT + (TIP - FLAT) * (z - ZB) / (ZT - ZB) yh = WB + (W - WB) * (z - ZB) / (ZT - ZB) total += 2 * xh * 2 * yh * dz else: length = sum(b - a for a, b, s in zip(EDGES, EDGES[1:], STEPS) if s >= z) total += length * 2 * W * dz return total 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 def main(): ids = {k: f"{PID}-{k}" for k in ("LC", "FT", "CL", "CP", "PL")} counts = {k: len(objs(v)) for k, v in ids.items()} flag("C5", "object counts", counts == {"LC": 1, "FT": 1, "CL": 1, "CP": 1, "PL": PI["nx"] * PI["ny"] if PI else 0}, str(counts)) 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"])] allb = [lc, ft, cl, cp] + piles flag("C2", f"IsSolid all {len(allb)}", all(b.IsSolid for b in allb), f"{sum(b.IsSolid for b in allb)}/{len(allb)}") # C1 extents for key, b, dx, dy, z0, z1 in (("footing", ft, F["dx"], F["dy"], F["z0"], FT_TOP), ("lean", lc, LN["dx"], LN["dy"], F["z0"] - LN["thickness"], F["z0"])): bb = bbox(b) check("C1", f"{key} X", dx, bb.Max.X - bb.Min.X) check("C1", f"{key} Y", dy, bb.Max.Y - bb.Min.Y) check("C1", f"{key} Zbot", z0, bb.Min.Z) check("C1", f"{key} Ztop", 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", CO["d"], bb.Max.X - bb.Min.X) check("C1", "column diameter Y", CO["d"], bb.Max.Y - bb.Min.Y) check("C1", "column Zbot", FT_TOP, bb.Min.Z) check("C1", "column Ztop", ZB, 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}) if PI: flag("C1", f"pile grid {PI['nx']} x {PI['ny']}", len(xs) == PI["nx"] and len(ys) == PI["ny"], f"x={xs} y={ys}") if PI and xs and ys: check("C1", "pile edge X", PI["edge"], xs[0] + F["dx"] / 2) check("C1", "pile edge Y", PI["edge"], ys[0] + F["dy"] / 2) check("C1", "pile pitch X (worst)", 0, max(abs(b - a - PI["pitch"]) for a, b in zip(xs, xs[1:]))) check("C1", "pile pitch Y (worst)", 0, max(abs(b - a - PI["pitch"]) for a, b in zip(ys, ys[1:]))) pb = bbox(piles[0]) check("C1", "pile diameter", PI["d"], pb.Max.X - pb.Min.X) check("C1", "pile length", PI["length"], pb.Max.Z - pb.Min.Z) check("C1", "pile head embedment", PI["head_embedment"], pb.Max.Z - F["z0"]) # C4 coping: front section at Y = 0 ok, curves, pts = rg.Intersect.Intersection.BrepPlane(cp, rg.Plane(rg.Point3d(0, 0, 0), rg.Vector3d.YAxis), 0.01) sec = rg.Curve.JoinCurves(curves, 0.1) flag("C4", "front section (Y=0) closed single loop", len(sec) == 1 and sec[0].IsClosed, f"{len(sec)} loop(s)") def z_at(x, upper): line = rg.LineCurve(rg.Point3d(x, 0, 0), rg.Point3d(x, 0, 200000)) 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] for x in (0, FLAT - 1, -(FLAT - 1), 3500, -3500, TIP - 5, -(TIP - 5)): check("C4", f"bottom at X={x:+.0f}", bottom_z(x), z_at(x, False)) for a, b in zip(EDGES, EDGES[1:]): x = (a + b) / 2 check("C4", f"top step at X={x:+.0f}", top_z(x), z_at(x, True)) bb = bbox(cp) check("C4", "coping Zbot", ZB, bb.Min.Z) check("C4", "coping Ztop (highest step)", max(STEPS), bb.Max.Z) # side width at X = 0 (side taper) ok, curves, pts = rg.Intersect.Intersection.BrepPlane(cp, rg.Plane(rg.Point3d(0, 0, 0), rg.Vector3d.XAxis), 0.01) ss = rg.Curve.JoinCurves(curves, 0.1) for zz in (ZB + 1, (ZB + ZT) / 2, ZT - 1, ZT + 500): hits = sorted(ev.PointA.Y for c in ss for ev in rg.Intersect.Intersection.CurveCurve( c, rg.LineCurve(rg.Point3d(0, -9000, zz), rg.Point3d(0, 9000, zz)), 0.01, 0.01)) w = WB + (W - WB) * min(zz - ZB, ZT - ZB) / (ZT - ZB) check("C4", f"side width at Z={zz:.0f}", 2 * w, (hits[-1] - hits[0]) if hits else 0) # skewed end faces: points 3 mm inside / outside the sheared ends at y = +-(W - 50), z mid of the end face for sx in (-1, 1): for y in (-(W - 50), W - 50): zz = (ZT + top_z(sx * (TIP - 10))) / 2 xe = sx * TIP - K * y ins = cp.IsPointInside(rg.Point3d(xe - sx * 3, y, zz), 0.01, False) out = not cp.IsPointInside(rg.Point3d(xe + sx * 3, y, zz), 0.01, False) flag("C4", f"skewed end {'left' if sx < 0 else 'right'} at y={y:+.0f} [작도 skew {C['skew_deg']}]", ins and out, f"x_end {xe:.1f}") # C3 joints / overlaps 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 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) if PI: emb = len(piles) * math.pi * (PI["d"] / 2) ** 2 * PI["head_embedment"] check("C3", "intended overlap FT-piles (mm3)", emb, sum(inter_vol(ft, b) for b in piles), tol=emb * 0.001) # C6 volumes fv = F["dx"] * F["dy"] * F["thickness"] / 1e9 check("C6", "footing volume m3", fv, vol(ft) / 1e9, tol=fv * 0.001) lv = (LN["dx"] * LN["dy"] - (len(piles) * math.pi * (PI["d"] / 2) ** 2 if PI else 0)) * LN["thickness"] / 1e9 check("C6", "lean volume m3 (minus pile holes)", lv, vol(lc) / 1e9, tol=lv * 0.001) cv_ = math.pi * (CO["d"] / 2000) ** 2 * CO["height"] / 1000 check("C6", "column volume m3", cv_, vol(cl) / 1e9, tol=cv_ * 0.001) cpv = coping_volume_expected() / 1e9 check("C6", "coping volume m3 (dimension outline, shear keeps volume)", cpv, vol(cp) / 1e9, tol=cpv * 0.001) # C7 shared pedestals and pads S = G["seats"] 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")} flag("C7", "pedestal / pad counts", len(seats) == S["n_pedestals"] and sorted(pads) == sorted(int(k) for k in S["table"]), f"pedestals={len(seats)} pads={len(pads)}") flag("C7", "IsSolid pedestals+pads", all(b.IsSolid for b in list(seats.values()) + list(pads.values()))) for no_s, row in S["table"].items(): no = int(no_s) elx, h1, h2, a, b = row x, y = S["pos"][no_s] p = pads[no] top = top_z(x + K * y) # back to the Y = 0 step of this bearing line check("C7", f"NO.{no} table EL.'X' - h1 - h2 = coping step", top, elx - h1 - h2) check("C7", f"NO.{no} pad top = EL.'X'", elx, bbox(p).Max.Z) pb = bbox(p) # plan centre: the sloped pad bottom shifts the volume centroid check("C7", f"NO.{no} pad centre X (bbox)", x, (pb.Min.X + pb.Max.X) / 2) check("C7", f"NO.{no} pad centre Y (bbox)", y, (pb.Min.Y + pb.Max.Y) / 2) ped = [s for s in seats.values() if s.IsPointInside(rg.Point3d(x, y, top + 1), 0.01, False)] flag("C7", f"NO.{no} on one pedestal", len(ped) == 1, f"{len(ped)}") if ped: line = rg.LineCurve(rg.Point3d(x, y, top - 10), rg.Point3d(x, y, top + 1000)) _ok, _crv, hits = rg.Intersect.Intersection.CurveBrep(line, ped[0], 0.01) zs = [pt.Z for pt in hits] check("C7", f"NO.{no} pedestal top at the bearing = step + h1", top + h1, max(zs) if zs else 0) check("C7", f"NO.{no} pedestal bottom at the bearing = step", top, min(zs) if zs else 0) check("C7", f"NO.{no} overlap pad-pedestal (mm3)", 0, inter_vol(p, ped[0]), tol=1.0) for no, s in seats.items(): check("C7", f"pedestal {no} overlap with coping (mm3)", 0, inter_vol(s, cp), tol=1.0) write_result(RESULT_PATH, results, dict(volumes_m3={"lean": round(vol(lc) / 1e9, 3), "footing": round(vol(ft) / 1e9, 3), "column": round(vol(cl) / 1e9, 3), "coping": round(vol(cp) / 1e9, 3), "pile_each": round(vol(piles[0]) / 1e9, 3) if piles else 0})) main()