- 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>
201 lines
11 KiB
Python
201 lines
11 KiB
Python
"""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")
|