chore(rebar): Pier_T_rebar 작업 폴더 초기 구성 및 외부 의존 파일 통합

- 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>
This commit is contained in:
ahnyongjooandClaude Sonnet 5.5 committed 2026-10-28 11:20:18 +09:00
commit 964a841de1
3978 files changed
+58013356

No files matched your search

@@ -0,0 +1,257 @@
"""G1 gate checker for the "tapered T" form (Ulsan P1, 2026-10-13): footing, lean, column, coping, pedestals, pads.
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values (section G1 of EXPECTED_PATH) come from
drawing dimension texts / EL texts / tables; generator params are not read.
Optional (Ulsan P2, 2026-10-15; absent -> the checks are unchanged): G1.footing.rot_deg (foundation drawn in its own axes, rotated
counter-clockwise about the pier centre; lean / footing / piles are checked after rotating them back and their rotated plan extents are
compared with the closed form), G1.piles {nx, ny, pitch_x, pitch_y, edge_x, edge_y, d, length, head_embedment}.
Checks: C5 population, C2 closed solids, C1 extents, C4 coping section at Y=0 and plan corner rounding,
C3 joints and overlaps, C6 volumes (coping: plan section areas integrated over Z from the
dimensions, R corner fillets in closed form), C7 pedestals / pads per bearing.
"""
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")
ROT = F.get("rot_deg", 0.0)
FT_TOP = F["z0"] + F["thickness"]
Z_CB = FT_TOP + CO["height"] # column top = coping bottom
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 unrot(b):
"""Copy of b rotated back by the foundation rotation (identity when the foundation is not rotated)."""
b = b.DuplicateBrep()
if ROT:
b.Transform(rg.Transform.Rotation(-math.radians(ROT), rg.Vector3d.ZAxis, rg.Point3d.Origin))
return b
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"))
# expected coping outline from the dimensions (front view, Y = 0)
FLAT, TIP, TOPH, RISE = C["bottom_flat_half"], C["tip_half"], C["top_half"], C["soffit_rise"]
ZTL, ZTR = C["z_top_left"], C["z_top_right"]
Z_TIP = Z_CB + RISE
def bottom_z(x):
ax = abs(x)
return Z_CB if ax <= FLAT else Z_CB + RISE * (ax - FLAT) / (TIP - FLAT)
def top_z(x):
"""Upper outline: top level of the side, then the battered end face from top_half down to the tip."""
zt = ZTL if x < 0 else ZTR
ax = abs(x)
if ax <= TOPH:
return zt
return zt - (zt - Z_TIP) * (ax - TOPH) / (TIP - TOPH)
def coping_volume_expected():
"""Integrate the plan section area over Z. At height z each side reaches x_end(z): the soffit line below the
tip level (plan outline: tip length, corner radius R), the battered end face above it (rounded outline
shrinking from the tip length to the top length). Corner cuts use the closed form of the R fillet area."""
W, R = C["width_y"], C["plan_corner_radius"]
def cut_area(e): # area cut by one R fillet over 0..e beyond its start
e = min(max(e, 0.0), R)
return R * e - (e / 2 * math.sqrt(R * R - e * e) + R * R / 2 * math.asin(e / R))
total, n = 0.0, 40000
z0, z1 = Z_CB, max(ZTL, ZTR)
dz = (z1 - z0) / n
for i in range(n):
z = z0 + (i + 0.5) * dz
area = 0.0
for zt in (ZTL, ZTR):
if z > zt:
continue
if z < Z_TIP:
x_end = FLAT + (TIP - FLAT) * (z - Z_CB) / RISE # soffit line
rect_end = TIP
else:
x_end = TIP - (TIP - TOPH) * (z - Z_TIP) / (zt - Z_TIP)
rect_end = x_end
area += W * x_end - 2 * cut_area(x_end - (rect_end - R))
total += area * dz
return total
def main():
ids = {k: f"{PID}-{k}" for k in ("LC", "FT", "CL", "CP", "PL")}
S = G["seats"]
nos = sorted(int(k) for k in S["table"])
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)}")
for key, b, dx, dy, z0, z1 in (("footing", ft, F["dx"], F["dy"], F["z0"], FT_TOP),
("lean", lc, F["dx"] + 2 * LN["margin"], F["dy"] + 2 * LN["margin"],
F["z0"] - LN["thickness"], F["z0"])):
if ROT: # raw plan extent of the rotated rectangle (closed form)
t = math.radians(abs(ROT))
rb = bbox(b)
check("C1", f"{key} rotated extent X", dx * math.cos(t) + dy * math.sin(t), rb.Max.X - rb.Min.X)
check("C1", f"{key} rotated extent Y", dx * math.sin(t) + dy * math.cos(t), rb.Max.Y - rb.Min.Y)
bb = bbox(unrot(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", Z_CB, bb.Max.Z)
if PI and piles:
pr = [bbox(unrot(b)) for b in piles]
xs = sorted({round((q.Min.X + q.Max.X) / 2, 1) for q in pr})
ys = sorted({round((q.Min.Y + q.Max.Y) / 2, 1) for q in pr})
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 len(xs) == PI["nx"] and len(ys) == PI["ny"]:
check("C1", "pile edge X", PI["edge_x"], xs[0] + F["dx"] / 2)
check("C1", "pile edge Y", PI["edge_y"], ys[0] + F["dy"] / 2)
check("C1", "pile pitch X (worst)", 0, max(abs(b - a - PI["pitch_x"]) for a, b in zip(xs, xs[1:])))
check("C1", "pile pitch Y (worst)", 0, max(abs(b - a - PI["pitch_y"]) for a, b in zip(ys, ys[1:])))
check("C1", "pile diameter", PI["d"], pr[0].Max.X - pr[0].Min.X)
check("C1", "pile length", PI["length"], pr[0].Max.Z - pr[0].Min.Z)
check("C1", "pile head embedment", PI["head_embedment"], pr[0].Max.Z - F["z0"])
bb = bbox(cp)
check("C1", "coping length X (tip to tip)", 2 * TIP, bb.Max.X - bb.Min.X)
check("C1", "coping width Y", C["width_y"], bb.Max.Y - bb.Min.Y)
check("C1", "coping Zbot", Z_CB, bb.Min.Z)
check("C1", "coping Ztop", max(ZTL, ZTR), bb.Max.Z)
check("C1", "coping centre X", 0, (bb.Max.X + bb.Min.X) / 2)
# C4 section at Y = 0 against the dimension outline
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", "section 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), 3000, -3000, TIP - 5, -(TIP - 5)):
check("C4", f"bottom at X={x:+.0f}", bottom_z(x), z_at(x, False))
for x in (-2000, 2000, -(TOPH - 10), TOPH - 10, -(TOPH + TIP) / 2, (TOPH + TIP) / 2):
check("C4", f"top at X={x:+.0f}", top_z(x), z_at(x, True))
check("C4", "top step at the pier centre (left - right)", ZTL - ZTR, z_at(-1, True) - z_at(1, True))
# plan corner rounding (R) just above the tips
R, W = C["plan_corner_radius"], C["width_y"]
zc = Z_TIP + 50
inside = lambda x, y: cp.IsPointInside(rg.Point3d(x, y, zc), 0.01, False)
# test point on the 45 deg diagonal from the fillet centre, half way between the arc (R) and the sharp corner
d = (R + R * math.sqrt(2)) / 2 / math.sqrt(2)
corner_x, corner_y = TIP - R + d, W / 2 - R + d
flag("C4", f"plan corners rounded R{R} (4 corners outside, edges inside)",
all(not inside(sx * corner_x, sy * corner_y) for sx in (1, -1) for sy in (1, -1))
and all(inside(sx * (TIP - R - 50), sy * (W / 2 - 10)) for sx in (1, -1) for sy in (1, -1)),
f"z {zc}, test point ({corner_x:.0f}, {corner_y:.0f})")
# 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)
if PI:
check("C3", "overlap LC-piles (mm3)", 0, sum(inter_vol(lc, b) for b in piles), tol=1.0)
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)
flag("C3", "column within coping bottom flat zone", CO["d"] / 2 <= FLAT and CO["d"] / 2 <= W / 2,
f"r={CO['d'] / 2:.0f} <= {FLAT:.0f} (X), {W / 2:.0f} (Y)")
# C6 volumes
fv = F["dx"] * F["dy"] * F["thickness"] / 1e9
check("C6", "footing volume m3", fv, vol(ft) / 1e9, tol=fv * 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)
holes = len(piles) * math.pi * (PI["d"] / 2) ** 2 if PI else 0
lv = ((F["dx"] + 2 * LN["margin"]) * (F["dy"] + 2 * LN["margin"]) - holes) * LN["thickness"] / 1e9
check("C6", "lean volume m3 (minus pile holes)", lv, vol(lc) / 1e9, tol=lv * 0.001)
cpv = coping_volume_expected() / 1e9
check("C6", "coping volume m3 (dimension outline, R corners)", cpv, vol(cp) / 1e9, tol=cpv * 0.001)
# C7 pedestals and pads
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", 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)}/{len(allb)}")
px, py = S["pedestal"]
m0, m1 = S["pad_margin"]
for no in nos:
elx, h1, h2, a, b = S["table"][str(no)]
x, y = S["pos"][str(no)]
top = ZTL if x < 0 else ZTR
s, p = seats[no], pads[no]
sb, pb = bbox(s), bbox(p)
check("C7", f"NO.{no} table EL.'X' - h1 - h2 = coping top ({'left' if x < 0 else 'right'})", top, elx - h1 - h2)
check("C7", f"NO.{no} pedestal bottom = coping top", top, sb.Min.Z)
check("C7", f"NO.{no} pedestal top = top + h1", top + h1, sb.Max.Z)
check("C7", f"NO.{no} pad top = EL.'X'", elx, pb.Max.Z)
check("C7", f"NO.{no} pedestal size X", px, sb.Max.X - sb.Min.X)
check("C7", f"NO.{no} pedestal size Y", py, sb.Max.Y - sb.Min.Y)
check("C7", f"NO.{no} pedestal centre X", x, (sb.Max.X + sb.Min.X) / 2)
check("C7", f"NO.{no} pedestal centre Y", y, (sb.Max.Y + sb.Min.Y) / 2)
c = rg.VolumeMassProperties.Compute(p).Centroid
check("C7", f"NO.{no} pad centre X", x, c.X)
check("C7", f"NO.{no} pad centre Y", y, c.Y)
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)
check("C7", f"NO.{no} overlap pedestal-coping (mm3)", 0,
sum(vol(q) for q in (rg.Brep.CreateBooleanIntersection(s, cp, 0.01) or [])), tol=1.0)
check("C7", f"NO.{no} overlap pad-pedestal (mm3)", 0,
sum(vol(q) for q in (rg.Brep.CreateBooleanIntersection(p, s, 0.01) or [])), tol=1.0)
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))}))
main()