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,200 @@
"""Read the main dimensions of a Ungok-type pier straight from its drawings and compare them with the pier's params.json (2026-10-19, read-only).
Same frame as the Ungok P1 drawing (D1 front view / plan at the same sheet coordinates). Every reader was first checked against P1 (see prompt log
Task_도면도형직접판독시험): coping outline + rebar lines (CS-CONC-MAJR / CS-RBAR-MAJR lines), plan pad polylines, hoop dots, schedule tables, footing rule.
usage: python tools/verify_pier_geometry.py <pier> (exit 0 always; prints a table 'item | drawing | params | OK/DIFF')"""
import glob
import json
import math
import re
import subprocess
import sys
from pathlib import Path
import ezdxf
APP = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(APP))
sys.path.insert(0, str(APP / "tools"))
from engine import reader # noqa: E402
import infer_footing_rebar as inf # noqa: E402
name = sys.argv[1]
D = APP / "data" / name
P = json.loads((D / "params.json").read_text(encoding="utf-8"))
texts = json.loads((D / "drawings" / "texts.json").read_text(encoding="utf-8"))
dxf1 = sorted(glob.glob(str(D / "drawings" / "dxf" / "*(1)*.dxf")))[0]
doc = ezdxf.readfile(dxf1)
MS = list(doc.modelspace())
rows = []
def cmp(item, drawing, par, tol=1.0):
try:
ok = abs(float(drawing) - float(par)) <= tol
except (TypeError, ValueError):
ok = drawing == par
rows.append((item, drawing, par, "OK" if ok else "DIFF"))
def num(s):
return float(re.search(r"\d+\.\d+|\d+", s).group())
T = lambda t: [x for x in texts if x["dwg"] == "D1" and x["text"] == t] # noqa: E731
# ---- coping top outline + EL datum (extract_coping_outline logic)
L = [(e.dxf.start.x, e.dxf.start.y, e.dxf.end.x, e.dxf.end.y) for e in MS if e.dxftype() == "LINE" and e.dxf.layer == "CS-CONC-MAJR"]
tips = sorted({(round(a, 1), round(b, 1), round(c, 1), round(d, 1)) for a, b, c, d in L if 150 < abs(c - a) < 250 and 1300 < abs(d - b) < 1900 and 0 < b < 25000})
top = lambda l: (l[0], l[1]) if l[1] > l[3] else (l[2], l[3]) # noqa: E731
bot = lambda l: (l[2], l[3]) if l[1] > l[3] else (l[0], l[1]) # noqa: E731
left, right = sorted(tips, key=lambda l: top(l)[0])
cx = (bot(left)[0] + bot(right)[0]) / 2
cur, end = top(left), top(right)
hv = [(round(a, 1), round(b, 1), round(c, 1), round(d, 1)) for a, b, c, d in L if abs(a - c) < .05 or abs(b - d) < .05]
steps, used = [], set()
while abs(cur[0] - end[0]) > .5 or abs(cur[1] - end[1]) > .5:
nxt = None
for s in hv:
if s in used:
continue
for p, q in (((s[0], s[1]), (s[2], s[3])), ((s[2], s[3]), (s[0], s[1]))):
if abs(p[0] - cur[0]) < .5 and abs(p[1] - cur[1]) < .5 and q[0] >= cur[0] - .5 and (q[1] >= cur[1] - .5 or abs(q[0] - cur[0]) > .5):
nxt = (s, q)
break
if nxt:
break
if not nxt:
sys.exit(f"coping outline chain broken at {cur}")
used.add(nxt[0])
if abs(nxt[1][1] - cur[1]) < .05:
steps.append([cur[0], nxt[1][0], cur[1]])
cur = nxt[1]
y_hi = max(s[2] for s in steps)
els = [t for t in texts if t["dwg"] == "D1" and t["text"].startswith("EL.") and abs(t["x"] - end[0]) < 1500 and 0 < t["y"] - y_hi < 1200]
el_hi = num(els[0]["text"]) * 1000
OFF = el_hi - y_hi # EL = y + OFF in the front view
ts = P["coping"]["top_steps"]
for i, (a, b, y) in enumerate(steps):
for k, v in (("x0", a - cx), ("x1", b - cx), ("z", y + OFF)):
cmp(f"coping step {i + 1} {k}", round(v), ts[i][k], tol=1.5)
flat = sorted({round(f[1], 1) for f in hv if abs(f[1] - f[3]) < .05 and 1800 < abs(f[2] - f[0]) < 2600 and abs((f[0] + f[2]) / 2 - cx) < 600 and bot(left)[1] - 1700 < f[1] < bot(left)[1] + 100})
z_flat = round(flat[0] + OFF)
cmp("coping z_bottom_flat", z_flat, P["coping"]["z_bottom_flat"])
cmp("coping length", round(bot(right)[0] - bot(left)[0]), P["coping"]["length_bottom_tip_to_tip"])
# ---- stirrups (soffit arc centre) and coping rebar lines
cz_arc = None
V = []
for e in MS:
if e.dxftype() == "LINE" and e.dxf.layer == "CS-RBAR-MAJR" and abs(e.dxf.start.x - e.dxf.end.x) < .05 and cx - 10 < e.dxf.start.x < 18500:
y0, y1 = sorted((e.dxf.start.y, e.dxf.end.y))
if 15500 < y0 and y1 < 20300 and y1 - y0 > 400:
V.append((round(e.dxf.start.x - cx), y0 + OFF, y1 + OFF))
if V:
xs = sorted(V)
z0, z1 = xs[0][1], xs[0][1]
# centre z from the stirrup at x = 2900 (R12100 arc about x = 6951.9)
pick = [v for v in V if v[0] == 2900]
if pick:
cz_arc = round(pick[0][1] - math.sqrt(12100 ** 2 - (6951.9 - 2900) ** 2), 1)
cmp("coping_rebar soffit_arc_centre z", cz_arc, P["coping_rebar"]["soffit_arc_centre"][1], tol=1.5)
H = {}
for e in MS:
if e.dxftype() == "LINE" and e.dxf.layer == "CS-RBAR-MAJR" and abs(e.dxf.start.y - e.dxf.end.y) < .05 and 5500 < min(e.dxf.start.x, e.dxf.end.x) and max(e.dxf.start.x, e.dxf.end.x) < 18700 \
and 17000 < e.dxf.start.y < 20300 and abs(e.dxf.end.x - e.dxf.start.x) > 1500:
H[round(e.dxf.start.y + OFF)] = (round(min(e.dxf.start.x, e.dxf.end.x) - cx), round(max(e.dxf.start.x, e.dxf.end.x) - cx))
tiers = sorted((z for z, (a, b) in H.items() if a == 0 and b > 5900), reverse=True)
if len(tiers) >= 3:
for t, z in zip(("1", "2", "3"), tiers[:3]):
cmp(f"coping_rebar top tier {t} z", z, P["coping_rebar"]["top"]["tiers"][t]["z"], tol=1.5)
c15 = [(z, a, b) for z, (a, b) in H.items() if a > 3000]
if c15:
cmp("coping_rebar C15 z", c15[0][0], P["coping_rebar"]["c15"]["z"], tol=2)
cmp("coping_rebar C15 x0", c15[0][1], P["coping_rebar"]["c15"]["x0"], tol=2)
# ---- footing / piles / column from the D1 dimension texts at the template's text positions
el_f = [num(t["text"]) * 1000 for t in texts if t["dwg"] == "D1" and t["text"].startswith("EL. 2") and 17000 < t["x"] < 18500 and 7000 < t["y"] < 8200]
if el_f:
cmp("footing z_bottom (EL text)", round(el_f[0]), P["footing"]["z_bottom"])
fx = [num(t["text"]) * 1000 for t in texts if t["dwg"] == "D1" and 11000 < t["x"] < 12500 and 3500 < t["y"] < 4000 and re.fullmatch(r"\d+\.\d{3}", t["text"])]
fy = [num(t["text"]) * 1000 for t in texts if t["dwg"] == "D1" and 29000 < t["x"] < 30500 and 3500 < t["y"] < 4000 and re.fullmatch(r"\d+\.\d{3}", t["text"])]
if fx:
cmp("footing size_x", round(fx[0]), P["footing"]["size_x"])
if fy:
cmp("footing size_y", round(fy[0]), P["footing"]["size_y"])
pl = [t["text"] for t in texts if t["dwg"] == "D1" and t["text"].startswith("L=") and t["x"] < 10000 and t["y"] < 7000]
if pl:
cmp("pile length", round(num(pl[0]) * 1000), P["piles"]["length"])
chx = [t["text"] for t in texts if t["dwg"] == "D1" and "@1." in t["text"] and 10000 < t["x"] < 13000 and 5500 < t["y"] < 6600]
chy = [t["text"] for t in texts if t["dwg"] == "D1" and "@1." in t["text"] and 28000 < t["x"] < 31000 and 5500 < t["y"] < 6600]
if chx:
cmp("pile pitch x", round(num(chx[0].split("@")[1]) * 1000), P["piles"].get("pitch_x", P["piles"].get("pitch")))
cmp("pile count x", int(chx[0].split("@")[0]) + 1, P["piles"]["count_x"], tol=0)
if chy:
cmp("pile pitch y", round(num(chy[0].split("@")[1]) * 1000), P["piles"].get("pitch_y", P["piles"].get("pitch")))
cmp("pile count y", int(chy[0].split("@")[0]) + 1, P["piles"]["count_y"], tol=0)
ch = [t for t in texts if t["dwg"] == "D1" and re.fullmatch(r"\d+\.\d{3}", t["text"]) and 22000 < t["x"] < 24000 and 12500 < t["y"] < 15500]
col_h = None
if z_flat and el_f:
col_h = z_flat - (el_f[0] * 1000 if el_f[0] < 1000 else el_f[0]) - P["footing"]["thickness"]
cmp("column height (soffit flat - footing top)", round(col_h), P["column"]["height"])
# ---- bearings (plan pad polylines, datum = coping plan centre)
pads = []
for e in MS:
if e.dxftype() == "LWPOLYLINE" and e.dxf.layer == "CS-CONC-MAJR" and len(e) == 4:
q = [(v[0], v[1]) for v in e.get_points()]
if not (5000 < q[0][0] < 20000 and 23000 < q[0][1] < 29500):
continue
s = [math.dist(q[i], q[(i + 1) % 4]) for i in range(4)]
if abs(s[0] - s[2]) < 1 and abs(s[1] - s[3]) < 1 and 780 < s[0] < 920 and 700 < s[1] < 860:
pads.append((sum(v[0] for v in q) / 4 - 12162.4, sum(v[1] for v in q) / 4 - 26412.3, math.degrees(math.atan2(q[1][1] - q[0][1], q[1][0] - q[0][0])) % 180))
_seen, _u = set(), []
for a, b, c in sorted(pads):
k = (round(a / 3), round(b / 3)) # nested outlines of one pad share a centre
if k not in _seen:
_seen.add(k)
_u.append((round(a, 1), round(b, 1), round(90 - c, 2)))
pads = _u
fr = [p for p in pads if p[1] < 0]
rr = [p for p in pads if p[1] > 0]
if fr and rr:
g5f, g5r = max(fr), max(rr)
c = P["bearing_seats"]["pad_rows"]
cmp("pad front g5 x", g5f[0], c["front"]["centre_rel_girder5"][0], tol=1.5)
cmp("pad front g5 y", g5f[1], c["front"]["centre_rel_girder5"][1], tol=1.5)
cmp("pad rear g5 x", g5r[0], c["rear"]["centre_rel_girder5"][0], tol=1.5)
cmp("pad rear g5 y", g5r[1], c["rear"]["centre_rel_girder5"][1], tol=1.5)
cmp("pad front rotation", g5f[2], c["front"]["rotation_deg"], tol=0.05)
cmp("pad rear rotation", g5r[2], c["rear"]["rotation_deg"], tol=0.05)
if len(fr) >= 2:
cmp("bearing pitch_x", round((g5f[0] - sorted(fr)[-2][0]), 1), P["bearing_seats"]["pitch_x"], tol=1.5)
# ---- schedule counts vs footing rule and params
rows_s = {}
for c in reader.table_candidates(texts):
if c["key"].startswith("schedule"):
for r in c["rows"]:
tx = [x["text"] for x in sorted(r["cells"], key=lambda z: z["x"])]
if tx and re.fullmatch(r"[BPC]\d+|S\d+", tx[0].strip()):
rows_s[tx[0].strip()] = tx
fs = P["footing"]
rule = inf.infer(fs["size_x"], fs["size_y"])
for lay, marks in (("bottom_Y1", ("B2", "B4")), ("bottom_X2", ("B6", "B8")), ("bottom_Y3", ("B3",)), ("bottom_X4", ("B7",)), ("top_Y", ("B1",)), ("top_X", ("B5",))):
try:
cmp(f"footing rebar {lay} count (schedule {'+'.join(marks)} vs params layer)", sum(int(rows_s[m][3]) for m in marks), len(P["footing_rebar"]["layers"][lay]["pos"]), tol=0)
cmp(f"footing rebar {lay} count (schedule vs footing rule)", sum(int(rows_s[m][3]) for m in marks), len(rule[lay]), tol=0)
except KeyError:
pass
try:
cmp("column rebar P3 hoop count (schedule vs params levels/marks)", int(rows_s["P3"][3]), P["column_rebar"]["hoops"]["marks"].count("P3"), tol=0)
cmp("column rebar P4 hoop count", int(rows_s["P4"][3]), P["column_rebar"]["hoops"]["marks"].count("P4"), tol=0)
cmp("column rebar P5 hoop count", int(rows_s["P5"][3]), P["column_rebar"]["hoops"]["marks"].count("P5"), tol=0)
cmp("column rebar P2 length (schedule vs params)", num(rows_s["P2"][4]) * 1000, P["column_rebar"]["main_bars"]["p2"]["length"], tol=1.5)
except KeyError:
pass
w = max(len(r[0]) for r in rows)
bad = [r for r in rows if r[3] == "DIFF"]
for r in rows:
print(f"{r[0]:{w}s} | drawing {str(r[1]):>16} | params {str(r[2]):>16} | {r[3]}")
print(f"== {name}: {len(rows)} items compared, {len(bad)} differ")