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:
commit
964a841de1
3978 files changed
+58013356
No files matched your search
@@ -0,0 +1,242 @@
|
||||
"""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()
|
||||
Reference in new issue
Block a user