- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환 - Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용 - pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관) - worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md) - D:\Rhino-MCP 경로 참조를 새 위치로 치환 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
218 lines
12 KiB
Python
218 lines
12 KiB
Python
"""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()
|