- 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>
144 lines
7.0 KiB
Python
144 lines
7.0 KiB
Python
"""G2 gate checker: column rebar P1-P6 (check C8) inside Rhino 8.
|
|
|
|
Globals: __rhino_doc__, EXPECTED_PATH, RESULT_PATH. Expected values from EXPECTED_PATH sections G1 (footing
|
|
bottom) and G2 (D3 schedule L / counts, D1 dimension chains, section radii); generator params are not read.
|
|
Ported from Code/교각모델링/verify_rebar.py (same checks and order).
|
|
"""
|
|
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, chain, load_expected, write_result # noqa: E402
|
|
|
|
doc = __rhino_doc__
|
|
X = load_expected(EXPECTED_PATH)
|
|
PID, TOL, G = X["pier_id"], X["tol_mm"], X["G2"]
|
|
SCHED_L, SCHED_N, DIA = G["schedule_L"], G["schedule_n"], G["dia"]
|
|
FB = X["G1"]["footing"]["z0"]
|
|
LEVELS = chain(G["hoop_chain"], FB)
|
|
TIE_LEVELS = chain(G["tie_chain"], FB)
|
|
R_OUT, R_IN, R_HOOP = G["r_outer"], G["r_inner"], G["r_hoop"]
|
|
TOP = FB + sum(G["p1_top_parts"])
|
|
NDIV = G["main_divisions"]
|
|
results = []
|
|
|
|
|
|
def check(item, exp, act, tol=TOL):
|
|
results.append(dict(check="C8", 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="C8", item=item, detail=detail, result="PASS" if ok else "FAIL"))
|
|
|
|
|
|
def ids(prefix):
|
|
return [o for o in all_objects(doc) if (o.Attributes.GetUserString("id") or "").startswith(prefix)]
|
|
|
|
|
|
def concrete():
|
|
out = []
|
|
for oid in (f"{PID}-CL", f"{PID}-FT", f"{PID}-CP"):
|
|
o = [x for x in all_objects(doc) if x.Attributes.GetUserString("id") == oid][0]
|
|
out.append(o.Geometry if isinstance(o.Geometry, rg.Brep) else rg.Brep.TryConvertBrep(o.Geometry))
|
|
return out
|
|
|
|
|
|
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}
|
|
for m in SCHED_L:
|
|
occ = {o.Attributes.GetUserString("occurrence").rstrip("ab") for o in cl[m]}
|
|
check(f"{m} bar count", SCHED_N[m], len(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"pipes {len(pipes[m])} / centrelines {len(cl[m])}")
|
|
for m in SCHED_L:
|
|
per = {}
|
|
for o in cl[m]:
|
|
k = o.Attributes.GetUserString("occurrence").rstrip("ab")
|
|
per[k] = per.get(k, 0) + float(o.Attributes.GetUserString("design_length")) + float(
|
|
o.Attributes.GetUserString("coupler_length") or 0) # schedule L includes coupler b (D3 V, IX)
|
|
worst = max(abs(v - SCHED_L[m]) for v in per.values())
|
|
check(f"{m} original length (+coupler b) vs schedule L (worst of {len(per)})", 0, worst)
|
|
curve = {}
|
|
for o in cl[m]:
|
|
k = o.Attributes.GetUserString("occurrence").rstrip("ab")
|
|
curve[k] = curve.get(k, 0) + o.Geometry.GetLength() + float(
|
|
o.Attributes.GetUserString("coupler_length") or 0)
|
|
dev = [v - SCHED_L[m] for v in curve.values()]
|
|
results.append(dict(check="C8", item=f"{m} curve length - L (record, fillets shorten hooks)",
|
|
detail=f"min {min(dev):.1f} / max {max(dev):.1f} mm", result="INFO"))
|
|
if m in G["no_fillet_marks"]:
|
|
check(f"{m} curve length (+coupler b) vs L (no fillet)", 0, max(abs(d) for d in dev))
|
|
for m, r in (("P1", R_OUT), ("P2", R_IN)):
|
|
angs, worst_r = [], 0
|
|
for o in cl[m]:
|
|
c = o.Geometry
|
|
p = c.PointAtEnd if c.PointAtEnd.Z > c.PointAtStart.Z else c.PointAtStart
|
|
angs.append(math.degrees(math.atan2(p.Y, p.X)) % 360)
|
|
worst_r = max(worst_r, abs(math.hypot(p.X, p.Y) - r))
|
|
check(f"{m} top EL", TOP, p.Z) if o is cl[m][0] else None
|
|
angs.sort()
|
|
worst_a = max(abs(a - i * 360 / NDIV) for i, a in enumerate(angs))
|
|
check(f"{m} radius (worst)", 0, worst_r, tol=0.5)
|
|
check(f"{m} angle vs i*360/{NDIV} (worst, deg)", 0, worst_a, tol=0.01)
|
|
p1b = min(min(o.Geometry.PointAtStart.Z, o.Geometry.PointAtEnd.Z) for o in cl["P1"])
|
|
p2b = min(min(o.Geometry.PointAtStart.Z, o.Geometry.PointAtEnd.Z) for o in cl["P2"])
|
|
dz = G["bottom_above_footing"]
|
|
check(f"P1 hook leg EL (footing bottom + {dz})", FB + dz, p1b)
|
|
check("P2 bottom EL", FB + dz, p2b)
|
|
hz = {}
|
|
for m in ("P3", "P4", "P5"):
|
|
for o in cl[m]:
|
|
bb = o.Geometry.GetBoundingBox(True)
|
|
hz.setdefault(round(bb.Max.Z, 1), set()).add(m)
|
|
if m != "P3":
|
|
p = o.Geometry.PointAtStart
|
|
check(f"{m} radius", R_HOOP, math.hypot(p.X, p.Y), tol=0.5) if o is cl[m][0] else None
|
|
zs = sorted(hz)
|
|
flag(f"hoop level count {len(LEVELS)}", len(zs) == len(LEVELS), f"{len(zs)}")
|
|
check("hoop levels vs dim chain (worst)", 0, max(abs(a - b) for a, b in zip(zs, LEVELS)))
|
|
marks_by_level = [list(hz[z])[0] for z in zs]
|
|
zones = G["hoop_marks_by_zone"]
|
|
flag("hoop marks by zone (" + ", ".join(f"{m} x{n}" for m, n in zones) + ")",
|
|
marks_by_level == [m for m, n in zones for _ in range(n)], "")
|
|
tz = sorted({round(o.Geometry.GetBoundingBox(True).Max.Z, 1) for o in cl["P6"]})
|
|
off = G["tie_offset"]
|
|
check(f"tie levels vs chain + {off} (worst)", 0, max(abs(a - (b + off)) for a, b in zip(tz, TIE_LEVELS)))
|
|
solids = concrete()
|
|
union = rg.Brep.CreateBooleanUnion(solids, 0.01)
|
|
shell = union[0] if union and len(union) == 1 else None # outer surface only (no internal joint faces)
|
|
results.append(dict(check="C8", item="concrete union for cover", detail=f"{len(union) if union else 0} brep",
|
|
result="INFO"))
|
|
min_cover = {}
|
|
for m in SCHED_L:
|
|
bad, cov = 0, 1e9
|
|
for o in cl[m]:
|
|
c = o.Geometry
|
|
n = max(4, int(c.GetLength() / 100))
|
|
for t in c.DivideByCount(n, 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 * (DIA[m] / 2), p - radial * (DIA[m] / 2),
|
|
p + rg.Vector3d(0, 0, DIA[m] / 2), p - rg.Vector3d(0, 0, DIA[m] / 2)):
|
|
if not any(s.IsPointInside(q, 0.01, False) for s in solids):
|
|
bad += 1
|
|
d = p.DistanceTo(shell.ClosestPoint(p)) if shell and shell.IsPointInside(p, 0.01, False) else -1
|
|
cov = min(cov, d - DIA[m] / 2)
|
|
min_cover[m] = round(cov, 1)
|
|
check(f"{m} sample points outside concrete", 0, bad, tol=0)
|
|
results.append(dict(check="C8", item="min cover to concrete surface per mark (record)",
|
|
detail=json.dumps(min_cover), result="INFO"))
|
|
results.append(dict(check="C8", item="clearance P1 outer surface vs hoop inner surface (record)",
|
|
detail=f"{R_HOOP - DIA['P4'] / 2 - (R_OUT + DIA['P1'] / 2):.1f} mm", result="INFO"))
|
|
write_result(RESULT_PATH, results, dict(min_cover=min_cover))
|
|
|
|
|
|
main()
|