- 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>
182 lines
9.3 KiB
Python
182 lines
9.3 KiB
Python
"""G3 gate checker, form "shaft cage" (Ramp-A P1, 2026-10-13): drilled-shaft cage bars and the anti-uplift plate.
|
|
|
|
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G3) come from the D4 schedule (L, N),
|
|
the 구조물 치수표 chains measured from the shaft top EL (D1), the section radii and the user decisions S1-S6;
|
|
generator params are not read.
|
|
Checks (C9): counts, original length (+coupler) vs schedule L, main radius / division / piece levels, hoop and
|
|
tie level grids, hoop radius, X / Y ties apart, U bars (count, bottom level, width, planes), plate (size,
|
|
thickness, centre, cover), containment in the pile segments, min side cover (record).
|
|
"""
|
|
import json
|
|
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["G3"]
|
|
SCHED_L, SCHED_N, DIA = G["schedule_L"], G["schedule_n"], G["dia"]
|
|
M, HP, TI, U, PL = G["main"], G["hoops"], G["ties"], G["u_bars"], G["plate"]
|
|
TOP = G["shaft_top"]
|
|
PILE = X["G1"]["pile"]
|
|
Z_BOT = PILE["z_bottom"]
|
|
SEG = [] # (z0, z1, radius) bottom to top
|
|
z = Z_BOT
|
|
for s in PILE["segments"]:
|
|
SEG.append((z, z + s["length"], s["d"] / 2))
|
|
z += s["length"]
|
|
|
|
|
|
def chain(c):
|
|
"""Levels from the shaft top: first offset, then (count, pitch) groups downwards; returned bottom to top."""
|
|
out = [TOP - c["first"]]
|
|
for n, p in c["groups"]:
|
|
out += [out[-1] - p * k for k in range(1, n + 1)]
|
|
return sorted(out)
|
|
|
|
|
|
HOOP_LEVELS, TIE_LEVELS = chain(HP["chain"]), chain(TI["chain"])
|
|
results = []
|
|
|
|
|
|
def check(item, exp, act, tol=TOL):
|
|
results.append(dict(check="C9", 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="C9", item=item, detail=detail, result="PASS" if ok else "FAIL"))
|
|
|
|
|
|
def info(item, detail):
|
|
results.append(dict(check="C9", item=item, detail=detail, result="INFO"))
|
|
|
|
|
|
def ids(prefix):
|
|
return [o for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "").startswith(prefix)]
|
|
|
|
|
|
def main():
|
|
cl = {m: [o for o in ids(f"{PID}-RC-") if o.Attributes.GetUserString("mark") == m] for m in SCHED_L}
|
|
pipes = {m: [o for o in ids(f"{PID}-R-") if o.Attributes.GetUserString("mark") == m] for m in SCHED_L}
|
|
occ = lambda o: o.Attributes.GetUserString("occurrence").rstrip("ab")
|
|
hm, tm = HP["mark"], TI["mark"]
|
|
for m in SCHED_L:
|
|
n_occ = len({occ(o) for o in cl[m]}) if m == hm else len(cl[m])
|
|
check(f"{m} bar count", SCHED_N[m], n_occ, tol=0)
|
|
flag(f"{m} pipes closed & paired with centrelines",
|
|
len(pipes[m]) == len(cl[m]) and all(o.Geometry.IsSolid for o in pipes[m]), f"{len(pipes[m])}/{len(cl[m])}")
|
|
for m in SCHED_L:
|
|
per, curve = {}, {}
|
|
for o in cl[m]:
|
|
k = occ(o) if m == hm else o.Attributes.GetUserString("occurrence")
|
|
cp = float(o.Attributes.GetUserString("coupler_length") or 0)
|
|
per[k] = per.get(k, 0) + float(o.Attributes.GetUserString("design_length")) + cp
|
|
curve[k] = curve.get(k, 0) + o.Geometry.GetLength() + cp
|
|
if per:
|
|
check(f"{m} original length (+coupler) vs schedule L (worst of {len(per)})", 0,
|
|
max(abs(v - SCHED_L[m]) for v in per.values()))
|
|
dev = [v - SCHED_L[m] for v in curve.values()]
|
|
info(f"{m} curve length (+coupler) - L (record)", f"min {min(dev):.1f} / max {max(dev):.1f} mm")
|
|
# main bars: pieces stacked between the first and the last hoop level (S6: coupler length 0)
|
|
zb, zt = HOOP_LEVELS[0], HOOP_LEVELS[-1]
|
|
check("main span (last - first hoop level) = sum of piece L", zt - zb, sum(SCHED_L[mk] for mk in M["pieces"]))
|
|
z = zb
|
|
for mk in M["pieces"]: # bottom to top
|
|
lo = [min(o.Geometry.PointAtStart.Z, o.Geometry.PointAtEnd.Z) for o in cl[mk]]
|
|
hi = [max(o.Geometry.PointAtStart.Z, o.Geometry.PointAtEnd.Z) for o in cl[mk]]
|
|
check(f"{mk} bottom EL (worst)", 0, max(abs(v - z) for v in lo))
|
|
check(f"{mk} top EL = bottom + L (worst)", 0, max(abs(v - (z + SCHED_L[mk])) for v in hi))
|
|
z += SCHED_L[mk]
|
|
rr = [math.hypot(o.Geometry.PointAtStart.X, o.Geometry.PointAtStart.Y) for o in cl[mk]]
|
|
check(f"{mk} radius (worst)", 0, max(abs(r - M["radius"]) for r in rr), tol=0.5)
|
|
a = sorted(math.degrees(math.atan2(o.Geometry.PointAtStart.Y, o.Geometry.PointAtStart.X)) % 360 for o in cl[mk])
|
|
check(f"{mk} angle vs {M['start_deg']} + i*360/{M['divisions']} (worst, deg)", 0,
|
|
max(abs(x - (M["start_deg"] + i * 360 / M["divisions"])) for i, x in enumerate(a)), tol=0.01)
|
|
# hoops
|
|
hz = {}
|
|
for o in cl[hm]:
|
|
bb = o.Geometry.GetBoundingBox(True)
|
|
hz.setdefault(round((bb.Max.Z + bb.Min.Z) / 2, 1), []).append(o)
|
|
zs = sorted(hz)
|
|
flag(f"{hm} levels {len(HOOP_LEVELS)} x 2 pieces", len(zs) == len(HOOP_LEVELS) and all(len(v) == 2 for v in hz.values()),
|
|
f"{len(zs)} levels")
|
|
check(f"{hm} levels vs 구조물 치수표 chain (worst)", 0,
|
|
max(abs(a - b) for a, b in zip(zs, HOOP_LEVELS)) if len(zs) == len(HOOP_LEVELS) else 1e9)
|
|
for o in cl[hm][:4]:
|
|
c = o.Geometry
|
|
p = c.PointAt(c.Domain.Mid)
|
|
check(f"{hm} radius at mid point ({o.Attributes.GetUserString('occurrence')})", HP["radius"], math.hypot(p.X, p.Y), tol=0.5)
|
|
# ties
|
|
by = lambda s: sorted((o.Geometry for o in cl[tm] if o.Attributes.GetUserString("occurrence").endswith(s)),
|
|
key=lambda g: g.GetBoundingBox(True).Max.Z)
|
|
xs, ys = by("X"), by("Y")
|
|
txm = [g.GetBoundingBox(True).Max.Z for g in xs]
|
|
tym = [g.GetBoundingBox(True).Max.Z for g in ys]
|
|
flag(f"{tm} X / Y per level, {len(TIE_LEVELS)} levels", len(txm) == len(tym) == len(TIE_LEVELS), f"X {len(txm)} Y {len(tym)}")
|
|
if len(txm) == len(TIE_LEVELS) == len(tym):
|
|
check(f"{tm} X levels vs chain (worst)", 0, max(abs(a - b) for a, b in zip(txm, TIE_LEVELS)))
|
|
check(f"{tm} Y levels = X + {TI['y_lift']} (worst)", 0, max(abs(a - b - TI["y_lift"]) for a, b in zip(tym, TIE_LEVELS)))
|
|
gaps = []
|
|
for a, b in zip(xs, ys):
|
|
ok, pa, pb = a.ClosestPoints(b)
|
|
gaps.append(pa.DistanceTo(pb) if ok else 0.0)
|
|
gap = min(gaps) if gaps else 0.0
|
|
check(f"{tm} X / Y centrelines apart >= bar diameter {DIA[tm]} (min, capped)", DIA[tm], min(gap, DIA[tm]), tol=0.01)
|
|
# U bars: bottom level, overall width (vertical legs on the hoop circle), one plane each through the centre
|
|
um = U["mark"]
|
|
bots = [o.Geometry.GetBoundingBox(True).Min.Z for o in cl[um]]
|
|
check(f"{um} bottom EL = shaft bottom + {U['bottom_above']} (worst, centreline)", 0,
|
|
max(abs(b - (Z_BOT + U["bottom_above"])) for b in bots) if bots else 1e9)
|
|
w, planar = [], 0
|
|
for o in cl[um]:
|
|
c = o.Geometry
|
|
a, b = c.PointAtStart, c.PointAtEnd
|
|
w.append(math.hypot(a.X - b.X, a.Y - b.Y))
|
|
ok, pl = c.TryGetPlane(0.01)
|
|
if ok and abs(pl.Normal.Z) < 1e-6 and abs(pl.DistanceTo(rg.Point3d(0, 0, pl.Origin.Z))) < 0.5:
|
|
planar += 1
|
|
check(f"{um} leg spacing (worst) = 2 x hoop radius {2 * HP['radius']:.0f}", 0, max(abs(x - 2 * HP["radius"]) for x in w) if w else 1e9)
|
|
check(f"{um} vertical planes through the centre", len(cl[um]), planar, tol=0)
|
|
# plate
|
|
pts = ids(f"{PID}-PT")
|
|
flag("plate count 1", len(pts) == 1, str(len(pts)))
|
|
if pts:
|
|
bb = pts[0].Geometry.GetBoundingBox(True)
|
|
check("plate size X", PL["size"], bb.Max.X - bb.Min.X)
|
|
check("plate size Y", PL["size"], bb.Max.Y - bb.Min.Y)
|
|
check("plate thickness", PL["thickness"], bb.Max.Z - bb.Min.Z, tol=0.01)
|
|
check("plate centre X / Y offset", 0, math.hypot((bb.Max.X + bb.Min.X) / 2, (bb.Max.Y + bb.Min.Y) / 2))
|
|
check(f"plate top = U bottom - {DIA[um] / 2:.0f} (S4)", Z_BOT + U["bottom_above"] - DIA[um] / 2, bb.Max.Z, tol=0.01)
|
|
info("plate bottom cover (record)", f"{bb.Min.Z - Z_BOT:.1f} mm")
|
|
# containment and side cover in the pile segments
|
|
solids = [o.Geometry for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "").startswith(f"{PID}-PL")]
|
|
min_cover = {}
|
|
for m in SCHED_L:
|
|
bad, cov = 0, 1e9
|
|
r = DIA[m] / 2
|
|
for o in cl[m]:
|
|
c = o.Geometry
|
|
for t in c.DivideByCount(max(4, int(c.GetLength() / 150)), True):
|
|
p = c.PointAt(t)
|
|
radial = rg.Vector3d(p.X, p.Y, 0)
|
|
radial.Unitize() if radial.Length > 1e-6 else None
|
|
for q in (p, p + radial * r, p - radial * r, 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
|
|
R = next((rad for z0, z1, rad in SEG if z0 <= p.Z <= z1), None)
|
|
if R is not None:
|
|
cov = min(cov, R - math.hypot(p.X, p.Y) - r, p.Z - Z_BOT - r)
|
|
min_cover[m] = round(cov, 1)
|
|
check(f"{m} sample points outside the pile concrete", 0, bad, tol=0)
|
|
info("min cover per mark (side or bottom, record)", json.dumps(min_cover))
|
|
write_result(RESULT_PATH, results, dict(min_cover=min_cover))
|
|
|
|
|
|
main()
|