- P&ID: 연결 분석 API, Prefix 규칙 관리, 카테고리 분류, DXF 그래프 빌드 - LLM: 대화 요약, tool card 영구 보존, 시계열 차트(uPlot), 에이전트 모드 - KB: 청크 미리보기, Field Instrument Inference, 인증/Qdrant 클라이언트 - MCP: 서버 기능 확장, 파이프라인 수정, timeout 개선 - Frontend: P&ID UI, LLM UI, KB UI, OPC UA Write 탭 추가 - 설정: AGENTS.md, plant_context, README, opencode.json 업데이트 - 정리: 진단 체크리스트 문서 삭제
130 lines
4.3 KiB
Python
130 lines
4.3 KiB
Python
"""
|
|
범례 심볼 프로브 — 좌표 박스 내 프리미티브 추출 + 정규화 시그니처 도출.
|
|
|
|
사용:
|
|
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()
|