- 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>
75 lines
4.2 KiB
Python
75 lines
4.2 KiB
Python
"""Read the coping top-step outline of D1 (front view) straight from the DXF geometry (2026-10-19, trial of geometry reading).
|
|
Finds the two end-batter tip lines on layer CS-CONC-MAJR (|dx|~200, |dy| 1300..1900), chains the horizontal / vertical lines between their top
|
|
ends, converts x to the pier axis (centre of the two tips) and the heights to EL with the 'EL. xx.xxx' label next to the highest step.
|
|
Reads only. usage: python tools/extract_coping_outline.py <D1.dxf> <texts.json> [params.json] (params.json: compare with coping.top_steps)"""
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
import ezdxf
|
|
|
|
dxf, texts = sys.argv[1], sys.argv[2]
|
|
doc = ezdxf.readfile(dxf)
|
|
L = [(e.dxf.start.x, e.dxf.start.y, e.dxf.end.x, e.dxf.end.y) for e in doc.modelspace() if e.dxftype() == "LINE" and e.dxf.layer == "CS-CONC-MAJR"]
|
|
tips = [l for l in L if 150 < abs(l[2] - l[0]) < 250 and 1300 < abs(l[3] - l[1]) < 1900 and l[1] < 25000 and l[1] > 0]
|
|
tips = sorted({(round(l[0], 1), round(l[1], 1), round(l[2], 1), round(l[3], 1)) for l in tips})
|
|
if len(tips) != 2:
|
|
sys.exit(f"expected 2 tip lines, found {len(tips)}: {tips}")
|
|
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) < 0.05 or abs(b - d) < 0.05)]
|
|
steps, used = [], set()
|
|
while abs(cur[0] - end[0]) > 0.5 or abs(cur[1] - end[1]) > 0.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]) < 0.5 and abs(p[1] - cur[1]) < 0.5 and q[0] >= cur[0] - 0.5 and (q[1] >= cur[1] - 0.5 or abs(q[0] - cur[0]) > 0.5):
|
|
nxt = (s, q)
|
|
break
|
|
if nxt:
|
|
break
|
|
if not nxt:
|
|
sys.exit(f"chain broken at {cur}")
|
|
used.add(nxt[0])
|
|
if abs(nxt[1][1] - cur[1]) < 0.05:
|
|
steps.append([cur[0], nxt[1][0], cur[1]])
|
|
cur = nxt[1]
|
|
y_hi = max(s[2] for s in steps)
|
|
els = [(t["text"], t["x"], t["y"]) for t in json.load(open(texts, encoding="utf-8")) if t["dwg"] == "D1" and t["text"].startswith("EL.")
|
|
and abs(t["x"] - end[0]) < 1500 and 0 < t["y"] - y_hi < 1200]
|
|
if len(els) != 1:
|
|
sys.exit(f"need exactly one EL label next to the highest step, found {els}")
|
|
el_hi = float(re.search(r"\d+\.\d+", els[0][0]).group()) * 1000
|
|
res = [{"x0": round(a - cx), "x1": round(b - cx), "z": round(el_hi - (y_hi - y))} for a, b, y in steps]
|
|
print("axis x =", round(cx, 1), "| EL label", els[0][0])
|
|
for r in res:
|
|
print(r)
|
|
if len(sys.argv) > 3:
|
|
ref = json.load(open(sys.argv[3], encoding="utf-8"))["coping"]["top_steps"]
|
|
d = max(max(abs(a[k] - b[k]) for k in ("x0", "x1", "z")) for a, b in zip(ref, res)) if len(ref) == len(res) else None
|
|
print("max difference from params coping.top_steps (mm):", d)
|
|
|
|
# ---- other coping dimensions from the same geometry
|
|
other = {"length_bottom_tip_to_tip": round(bot(right)[0] - bot(left)[0]),
|
|
"tip_bottom_rise": round(top(left)[1] - bot(left)[1]),
|
|
"end_batter_top_inset": round(abs(top(left)[0] - bot(left)[0]))}
|
|
arcs = {round(e.dxf.radius) for e in doc.modelspace() if e.dxftype() == "ARC" and e.dxf.layer == "CS-CONC-MAJR" and e.dxf.radius > 3000}
|
|
other["soffit_radius"] = sorted(arcs)
|
|
flat = [(a, b, c_, d) for a, b, c_, d in hv if abs(b - d) < 0.05 and 1800 < abs(c_ - a) < 2600 and abs((a + c_) / 2 - cx) < 600 and bot(left)[1] - 1700 < b < y_hi]
|
|
flat = sorted({(round(f[0], 1), round(f[1], 1), round(f[2], 1)) for f in flat if f[1] < bot(left)[1] + 100})
|
|
if flat:
|
|
f = flat[0]
|
|
other["flat_bottom_half_width"] = round(abs(f[2] - f[0]) / 2)
|
|
other["z_bottom_flat"] = round(el_hi - (y_hi - f[1]))
|
|
other["_flat_candidates"] = len(flat)
|
|
sect = sorted({round(abs(c_ - a), 1) for a, b, c_, d in hv if abs(b - d) < 0.05 and 3000 <= abs(c_ - a) <= 3200 and a > 20000})
|
|
other["width_y_candidates"] = sect
|
|
print("other:", other)
|
|
if len(sys.argv) > 3:
|
|
print("params:", {k: ref_ for k, ref_ in json.load(open(sys.argv[3], encoding="utf-8"))["coping"].items() if k in other})
|