""" 범례 심볼 프로브 — 좌표 박스 내 프리미티브 추출 + 정규화 시그니처 도출. 사용: python3 mcp-server/legend_probe.py XMIN YMIN XMAX YMAX [라벨] 출력: 1) 원시 프리미티브 (LINE/ARC/CIRCLE/LWPOLYLINE) 2) 정규화 시그니처: 앵커=박스 좌하단 기준 상대좌표, 도형종류별 개수, 선분 길이·각도, 공유 정점(연결 토폴로지), 폐합 삼각형 등 패턴 단서 """ import sys, math, collections import ezdxf from ezdxf import recover DXF = "dxf-graph/No-10_Plant_PID.dxf" def load(): try: return ezdxf.readfile(DXF) except ezdxf.DXFStructureError: d, _ = recover.readfile(DXF) return d def main(): if len(sys.argv) < 5: print("usage: legend_probe.py XMIN YMIN XMAX YMAX [label]") sys.exit(1) x0, y0, x1, y1 = map(float, sys.argv[1:5]) label = sys.argv[5] if len(sys.argv) > 5 else "?" if x0 > x1: x0, x1 = x1, x0 if y0 > y1: y0, y1 = y1, y0 msp = load().modelspace() def inb(x, y): return x0 <= x <= x1 and y0 <= y <= y1 lines, arcs, circles, polys, texts = [], [], [], [], [] for e in msp: t = e.dxftype() try: if t == "LINE": s, en = e.dxf.start, e.dxf.end if inb(s.x, s.y) or inb(en.x, en.y): lines.append(((s.x, s.y), (en.x, en.y))) elif t == "ARC": c = e.dxf.center if inb(c.x, c.y): arcs.append((c.x, c.y, e.dxf.radius, e.dxf.start_angle, e.dxf.end_angle)) elif t == "CIRCLE": c = e.dxf.center if inb(c.x, c.y): circles.append((c.x, c.y, e.dxf.radius)) elif t == "LWPOLYLINE": p = [(a, b) for a, b in e.get_points("xy")] if p and inb(p[0][0], p[0][1]): polys.append(p) elif t in ("TEXT", "MTEXT"): ip = e.dxf.insert if inb(ip.x, ip.y): v = (e.plain_text() if t == "MTEXT" else e.dxf.text).strip() if v: texts.append((round(ip.x, 1), round(ip.y, 1), v[:40])) except Exception: pass print(f"=== '{label}' box=({x0:.1f},{y0:.1f})-({x1:.1f},{y1:.1f}) ===") print(f"LINE={len(lines)} ARC={len(arcs)} CIRCLE={len(circles)} " f"LWPOLY={len(polys)} TEXT={len(texts)}") if texts: print("텍스트:", [t[2] for t in texts]) # 앵커 = 비텍스트 프리미티브 최소 x,y pts = [] for a, b in lines: pts += [a, b] for cx, cy, r, *_ in arcs: pts += [(cx - r, cy - r), (cx + r, cy + r)] for cx, cy, r in circles: pts += [(cx - r, cy - r), (cx + r, cy + r)] for p in polys: pts += p if not pts: print("(비텍스트 프리미티브 없음)") return ax = min(p[0] for p in pts) ay = min(p[1] for p in pts) w = max(p[0] for p in pts) - ax h = max(p[1] for p in pts) - ay print(f"앵커=({ax:.2f},{ay:.2f}) 정규화 bbox= {w:.2f} x {h:.2f}") def n(x, y): return (round(x - ax, 2), round(y - ay, 2)) print("\n-- LINE (상대좌표 | 길이 | 각도°) --") seg = [] for (sx, sy), (ex, ey) in sorted(lines): ln = math.hypot(ex - sx, ey - sy) ang = round(math.degrees(math.atan2(ey - sy, ex - sx)) % 180, 1) print(f" {n(sx,sy)} → {n(ex,ey)} len={ln:.2f} ang={ang}") seg.append((n(sx, sy), n(ex, ey), round(ln, 2), ang)) for cx, cy, r in circles: print(f"-- CIRCLE c={n(cx,cy)} r={r:.2f}") for cx, cy, r, sa, ea in arcs: print(f"-- ARC c={n(cx,cy)} r={r:.2f} {sa:.0f}°→{ea:.0f}°") for p in polys: print(f"-- LWPOLY {[n(x,y) for x,y in p]}") # 공유 정점 (연결 토폴로지) — 0.3u 이내 동일점 vtx = collections.defaultdict(int) for s, e, *_ in seg: vtx[s] += 1 vtx[e] += 1 shared = {v: c for v, c in vtx.items() if c >= 3} print(f"\n공유정점(차수≥3 = apex/junction): {shared}") ang_hist = collections.Counter(s[3] for s in seg) print(f"각도 분포: {dict(ang_hist)}") print(f"선분 길이 분포: {sorted(s[2] for s in seg)}") if __name__ == "__main__": main()