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,192 @@
|
||||
"""Verify footing rebar B1-B12 (check C9) and rebar colours inside Rhino 8.
|
||||
|
||||
Independent expected values typed from D3 schedule (L, counts) and D1/D2 dimension chains; layer heights
|
||||
are the F1-approved drawn values (D1 dots), mid-layer positions are drawn dots (no dimension chain ->
|
||||
derived, not independent; marked in the report). params.json is not read.
|
||||
Globals: __rhino_doc__, RESULT_PATH; optional FINGERPRINT_REF / FINGERPRINT_OUT (all objects except
|
||||
footing rebar; breps: bbox + volume, curves: bbox + length).
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
|
||||
import Rhino.Geometry as rg
|
||||
from Rhino.DocObjects import ObjectColorSource
|
||||
|
||||
doc = __rhino_doc__
|
||||
|
||||
|
||||
def ALL_OBJECTS():
|
||||
"""All live objects incl. hidden/locked (default doc.Objects skips hidden ones; 2026-10-07 P1-FT hidden)."""
|
||||
from Rhino.DocObjects import ObjectEnumeratorSettings
|
||||
s = ObjectEnumeratorSettings()
|
||||
s.HiddenObjects = True
|
||||
s.LockedObjects = True
|
||||
return list(doc.Objects.GetObjectList(s))
|
||||
FB = 26208
|
||||
SCHED = {"B1": (8000, 38), "B2": (11500, 38), "B3": (8000, 38), "B4": (7600, 35), "B5": (9550, 32),
|
||||
"B6": (13050, 32), "B7": (9550, 32), "B8": (9150, 29), "B9": (2155, 493), "B10": (33684, 9),
|
||||
"B11": (9350, 28), "B12": (7800, 34)}
|
||||
DIA = {"B1": 25, "B2": 25, "B3": 19, "B4": 25, "B5": 29, "B6": 29, "B7": 19, "B8": 29, "B9": 16, "B10": 19,
|
||||
"B11": 16, "B12": 16}
|
||||
Y_BOT = sorted([-4575, 4575] + [-4375 + 125 * i for i in range(71)]) # '100','200','70@125=8.750'
|
||||
Y_250 = sorted([-4575, 4575] + [-4375 + 250 * i for i in range(36)]) # '100','200','35@250=8.750'
|
||||
X_BOT = sorted([-3800, 3800] + [-3625 + 125 * i for i in range(59)]) # '100','175','58@125=7.250'
|
||||
X_250 = sorted([-3800, 3800] + [-3625 + 250 * i for i in range(30)]) # '100','175','29@250=7.250'
|
||||
Z = {"B2": 122, "B4": 122, "B6": 178, "B8": 178, "B3": 227.5, "B7": 277, "B1": 2073, "B5": 2127} # F1
|
||||
B10_Z = [150 + 200 * k for k in range(1, 10)] # '150','9@200=1.800'
|
||||
B9_X = [-4125 + 250 * i for i in range(34)] # '450','33@250=8.250'
|
||||
B9_Y = [-3500 + 250 * j for j in range(29)] # '300','28@250=7.000'
|
||||
PALETTE = {32: (214, 39, 40), 29: (255, 127, 14), 25: (188, 60, 160), 19: (44, 160, 44), 16: (31, 119, 180)}
|
||||
results = []
|
||||
|
||||
|
||||
def check(item, exp, act, tol=1.0):
|
||||
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 fingerprints():
|
||||
fp = {}
|
||||
for o in ALL_OBJECTS():
|
||||
oid = o.Attributes.GetUserString("id")
|
||||
if not oid or oid.startswith(("P1-R-B", "P1-RC-B")):
|
||||
continue
|
||||
g = o.Geometry
|
||||
bb = g.GetBoundingBox(True)
|
||||
size = (rg.VolumeMassProperties.Compute(g).Volume / 1e6 if isinstance(g, rg.Brep)
|
||||
else g.GetLength() if isinstance(g, rg.Curve) else 0)
|
||||
fp[oid] = [round(v, 3) for v in (bb.Min.X, bb.Min.Y, bb.Min.Z, bb.Max.X, bb.Max.Y, bb.Max.Z, size)]
|
||||
return fp
|
||||
|
||||
|
||||
def objs(prefix, mark):
|
||||
return [o for o in ALL_OBJECTS() if (o.Attributes.GetUserString("id") or "").startswith(prefix)
|
||||
and o.Attributes.GetUserString("mark") == mark]
|
||||
|
||||
|
||||
def main():
|
||||
if "FINGERPRINT_OUT" in globals():
|
||||
fp = fingerprints()
|
||||
json.dump(fp, open(FINGERPRINT_OUT, "w", encoding="utf-8"), indent=0)
|
||||
print("fingerprints written", len(fp))
|
||||
return
|
||||
cl = {m: objs("P1-RC-", m) for m in SCHED}
|
||||
pp = {m: objs("P1-R-", m) for m in SCHED}
|
||||
for m, (L, n) in SCHED.items():
|
||||
check(f"{m} bar count vs schedule", n, len(cl[m]), tol=0)
|
||||
flag(f"{m} pipes closed & paired", len(pp[m]) == len(cl[m]) and all(o.Geometry.IsSolid for o in pp[m]),
|
||||
f"{len(pp[m])}/{len(cl[m])}")
|
||||
if cl[m]:
|
||||
worst = max(abs(float(o.Attributes.GetUserString("design_length")) - L) for o in cl[m])
|
||||
check(f"{m} original length vs L (worst)", 0, worst)
|
||||
dev = [o.Geometry.GetLength() - L for o in cl[m]]
|
||||
info(f"{m} curve length - L (bend fillets)", f"min {min(dev):.1f} / max {max(dev):.1f}")
|
||||
# layer heights and positions (horizontal bars: use curve point at mid-length)
|
||||
def mids(m):
|
||||
return [o.Geometry.PointAt(o.Geometry.Domain.Mid) for o in cl[m]]
|
||||
for m in ("B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8"):
|
||||
ps = mids(m)
|
||||
check(f"{m} layer z (worst)", 0, max(abs(p.Z - (FB + Z[m])) for p in ps))
|
||||
def pos_check(marks, axis, expected, name):
|
||||
got = sorted(round(getattr(p, axis), 1) for m in marks for p in mids(m))
|
||||
ok = len(got) == len(expected)
|
||||
worst = max(abs(a - b) for a, b in zip(got, expected)) if ok else 1e9
|
||||
check(f"{name} positions vs chain (worst, n={len(got)}/{len(expected)})", 0, worst)
|
||||
pos_check(("B2", "B4"), "X", Y_BOT, "bottom Y-bars B2+B4")
|
||||
pos_check(("B6", "B8"), "Y", X_BOT, "bottom X-bars B6+B8")
|
||||
pos_check(("B3",), "X", Y_250, "B3")
|
||||
pos_check(("B1",), "X", Y_250, "B1")
|
||||
pos_check(("B7",), "Y", X_250, "B7")
|
||||
pos_check(("B5",), "Y", X_250, "B5")
|
||||
# alternation: U-bars at ends and every other inner position
|
||||
b2x = sorted(round(p.X) for p in mids("B2"))
|
||||
flag("B2 at ends and alternate from -4375", b2x == sorted([-4575, 4575] + Y_BOT[1:-1][0::2]), "")
|
||||
b6y = sorted(round(p.Y) for p in mids("B6"))
|
||||
flag("B6 at ends and alternate from -3625", b6y == sorted([-3800, 3800] + X_BOT[1:-1][0::2]), "")
|
||||
# mid layers (drawn dots; derived)
|
||||
for m in ("B11", "B12"):
|
||||
zs = sorted({round(p.Z - FB) for p in mids(m)})
|
||||
flag(f"{m} mid levels 550/950/1350/1750 [derived]", zs == [550, 950, 1350, 1750], str(zs))
|
||||
# B10 rings
|
||||
zs = sorted(round(o.Geometry.GetBoundingBox(True).Min.Z - FB, 1) for o in cl["B10"])
|
||||
check("B10 levels vs chain (worst)", 0, max(abs(a - b) for a, b in zip(zs, B10_Z)) if len(zs) == 9 else 1e9)
|
||||
bb = cl["B10"][0].Geometry.GetBoundingBox(True)
|
||||
check("B10 centreline size X", 9198, bb.Max.X - bb.Min.X)
|
||||
check("B10 centreline size Y", 7644, bb.Max.Y - bb.Min.Y)
|
||||
# B9 grid / checkerboard
|
||||
# vertical leg x = bbox min X (hooks point +X); y constant. (Domain.Mid falls on the hook: checker fix)
|
||||
pts = {(round(o.Geometry.GetBoundingBox(True).Min.X), round(o.Geometry.GetBoundingBox(True).Center.Y))
|
||||
for o in cl["B9"]}
|
||||
exp = {(x, y) for i, x in enumerate(B9_X) for j, y in enumerate(B9_Y) if (i + j) % 2 == 0}
|
||||
flag("B9 positions = checkerboard of 34x29 grid", pts == exp, f"{len(pts)} / {len(exp)}")
|
||||
zr = [o.Geometry.GetBoundingBox(True) for o in cl["B9"]]
|
||||
# absolute height: centred on drawn EREC lines z 150~2100 -> bottom = 1125 - 1995/2 = 127.5 (checker fix:
|
||||
# negative test 2026-10-07 moved one B9 +5mm and the extent-only check missed it)
|
||||
check("B9 bottom z (worst of all)", 0, max(abs(b.Min.Z - (FB + (150 + 2100) / 2 - 1995 / 2)) for b in zr))
|
||||
check("B9 vertical extent a (bottom leg z to top of straight)", 1995,
|
||||
max(b.Max.Z for b in zr[:1]) - min(b.Min.Z for b in zr[:1]) - 64 / math.pi, tol=1.0)
|
||||
# containment & cover against concrete union (FT + CL + CP)
|
||||
solids = []
|
||||
for oid in ("P1-FT", "P1-CL", "P1-CP"):
|
||||
o = [x for x in ALL_OBJECTS() if x.Attributes.GetUserString("id") == oid][0]
|
||||
solids.append(o.Geometry)
|
||||
ft = solids[0]
|
||||
shell = None
|
||||
for order in ([solids[1], solids[0], solids[2]], solids): # union is order-sensitive in Rhino 8
|
||||
u = rg.Brep.CreateBooleanUnion(order, 0.01)
|
||||
if u and len(u) == 1:
|
||||
shell = u[0]
|
||||
break
|
||||
if shell is None:
|
||||
raise RuntimeError("concrete union failed")
|
||||
cover = {}
|
||||
for m in SCHED:
|
||||
bad, cov = 0, 1e9
|
||||
for o in cl[m]:
|
||||
c = o.Geometry
|
||||
for t in c.DivideByCount(max(4, int(c.GetLength() / 200)), True):
|
||||
p = c.PointAt(t)
|
||||
r = DIA[m] / 2
|
||||
for q in (p, p + rg.Vector3d(r, 0, 0), p - rg.Vector3d(r, 0, 0), p + rg.Vector3d(0, r, 0),
|
||||
p - rg.Vector3d(0, r, 0), p + rg.Vector3d(0, 0, r), p - rg.Vector3d(0, 0, r)):
|
||||
if not ft.IsPointInside(q, 0.01, False):
|
||||
bad += 1
|
||||
cov = min(cov, p.DistanceTo(shell.ClosestPoint(p)) - r)
|
||||
cover[m] = round(cov, 1)
|
||||
check(f"{m} sample points outside footing", 0, bad, tol=0)
|
||||
info("min cover to concrete exterior per mark", json.dumps(cover))
|
||||
# colours by diameter
|
||||
wrong = 0
|
||||
for o in ALL_OBJECTS():
|
||||
if (o.Attributes.GetUserString("id") or "").startswith("P1-R-"):
|
||||
d = int(o.Attributes.GetUserString("dia")[1:])
|
||||
c = o.Attributes.ObjectColor
|
||||
if o.Attributes.ColorSource != ObjectColorSource.ColorFromObject or (c.R, c.G, c.B) != PALETTE[d]:
|
||||
wrong += 1
|
||||
check("rebar pipe colour by diameter mismatches (all rebar)", 0, wrong, tol=0)
|
||||
if "FINGERPRINT_REF" in globals():
|
||||
ref = json.load(open(FINGERPRINT_REF, encoding="utf-8"))
|
||||
now = fingerprints()
|
||||
worst = max(abs(a - b) for k in ref for a, b in zip(ref[k], now.get(k, [1e9] * 7)))
|
||||
flag("existing objects (v0.1.02) unchanged", set(ref) == set(now) and worst <= 0.001,
|
||||
f"{len(now)}/{len(ref)} max diff {worst}")
|
||||
summ = dict(total=len([r for r in results if r["result"] != "INFO"]),
|
||||
passed=sum(r["result"] == "PASS" for r in results),
|
||||
failed=sum(r["result"] == "FAIL" for r in results), cover=cover)
|
||||
json.dump(dict(summary=summ, results=results), open(RESULT_PATH, "w", encoding="utf-8"),
|
||||
ensure_ascii=False, indent=1)
|
||||
print(json.dumps(summ, ensure_ascii=False))
|
||||
for r in results:
|
||||
if r["result"] in ("FAIL", "INFO"):
|
||||
print(r["result"], r["item"], r.get("detail", r.get("diff")))
|
||||
|
||||
|
||||
main()
|
||||
Reference in new issue
Block a user