- 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>
191 lines
8.9 KiB
Python
191 lines
8.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""운곡교 P1 재료표 도면(DXF) 대조본 작성.
|
|
|
|
도면 재료표(3.dxf)와 같은 칸 구성(열 위치·글자 높이·레이어)에 마크마다 두 줄(도면 / 모델)을 놓고,
|
|
다른 칸을 색으로 칠하고 오른쪽에 비고 열(비고 · 비고 3 KDS)을 붙인다.
|
|
입력: 추출데이터\재료표_대조결과.json (make_bom_compare.py 가 만든다), 3.dxf (스타일·레이어 상속)
|
|
사용: python -I make_bom_dxf.py <pylib경로> <대조결과.json> <출력.dxf>
|
|
"""
|
|
import sys, json, math
|
|
sys.path.insert(0, sys.argv[1])
|
|
import ezdxf
|
|
from ezdxf.enums import TextEntityAlignment
|
|
from ezdxf.lldxf import const
|
|
|
|
SRC = r"D:\★Pier 자료\심수석님 검토 파일\철근 모델랑 운곡교P1_바이브코딩\3.dxf"
|
|
data = json.load(open(sys.argv[2], encoding="utf-8"))
|
|
OUT = sys.argv[3]
|
|
|
|
doc = ezdxf.readfile(SRC)
|
|
msp = doc.modelspace()
|
|
for e in list(msp): # 원본 도면 내용은 지우고 스타일·레이어·단위만 상속
|
|
msp.delete_entity(e)
|
|
if "KR_TBL" not in doc.styles:
|
|
doc.styles.add("KR_TBL", font="malgun.ttf")
|
|
LAYERS = {
|
|
"대조_다름(주황)": (30, (248, 203, 173)),
|
|
"대조_반올림(파랑)": (140, (221, 235, 247)),
|
|
"대조_빈칸미추출(노랑)": (50, (255, 242, 204)),
|
|
"대조_일치(초록)": (3, (226, 240, 217)),
|
|
"대조_비고": (7, None),
|
|
"대조_비고3_KDS(자홍)": (6, None),
|
|
"대조_표선": (7, None),
|
|
"대조_글자": (7, None),
|
|
}
|
|
for n, (c, _) in LAYERS.items():
|
|
if n not in doc.layers:
|
|
doc.layers.add(n, color=c)
|
|
|
|
TH = 150 # 표 글자 높이 (원본과 동일)
|
|
SUB = 360 # 한 줄 높이 (원본 행 간격)
|
|
XB = [1752, 2652, 3552, 4452, 5352, 6852, 8352, 9852, 11352, 12852, 14352, 15852, 17352, 18852, 20352, 21552, 22752, 23952, 25152]
|
|
KEYS = ["num", "shape", "dia", "n", "A", "B", "C", "D", "E", "R", "X1", "X2", "J", "L", "tot", "unit", "w", "wh"]
|
|
HEAD = ["번 호", "형 식", "직 경", "개 수", "A", "B", "C", "D", "E", "R", "X1(º)", "X2(º)", "J", "길 이", "총길이", "단위중량", "총중량", "총중량(할증)"]
|
|
UNITS = {"L": "(M)", "tot": "(M)", "unit": "(KG/M)", "w": "(TON)", "wh": "(TON)"}
|
|
XK = XB[0] - 1500 # '구분'(도면/모델) 열
|
|
XV0, XV1 = XB[-1], XB[-1] + 1600 # 판정 열
|
|
XN0, XN1 = XV1, XV1 + 16000 # 비고
|
|
XK3_0, XK3_1 = XN1, XN1 + 12000 # 비고 3
|
|
XLEFT, XRIGHT = XK, XK3_1
|
|
MCOL = 6 # ACI 자홍
|
|
|
|
def col_x(k):
|
|
i = KEYS.index(k)
|
|
return XB[i], XB[i + 1]
|
|
|
|
def txt(x, y, s, h=TH, layer="대조_글자", color=None):
|
|
if s is None or s == "":
|
|
return
|
|
t = msp.add_text(s, height=h, dxfattribs={"layer": layer, "style": "KR_TBL"})
|
|
t.set_placement((x, y), align=TextEntityAlignment.MIDDLE_CENTER)
|
|
if color is not None:
|
|
t.dxf.color = color
|
|
|
|
def mtxt(x0, ytop, width, s, h=110, layer="대조_비고", color=None):
|
|
if not s:
|
|
return
|
|
m = msp.add_mtext(s, dxfattribs={"layer": layer, "style": "KR_TBL", "char_height": h})
|
|
m.dxf.insert = (x0, ytop, 0)
|
|
m.dxf.width = width
|
|
m.dxf.attachment_point = 1 # 좌상단
|
|
if color is not None:
|
|
m.dxf.color = color
|
|
|
|
def line(x0, y0, x1, y1, layer="대조_표선", color=None):
|
|
ln = msp.add_line((x0, y0), (x1, y1), dxfattribs={"layer": layer})
|
|
if color is not None:
|
|
ln.dxf.color = color
|
|
|
|
def fill(x0, y0, x1, y1, lname):
|
|
rgb = LAYERS[lname][1]
|
|
h = msp.add_hatch(dxfattribs={"layer": lname})
|
|
h.set_solid_fill(color=LAYERS[lname][0])
|
|
h.rgb = rgb
|
|
h.paths.add_polyline_path([(x0, y0), (x1, y0), (x1, y1), (x0, y1)], is_closed=True)
|
|
|
|
def est_lines(s, width, h=110):
|
|
cw = h * 0.8
|
|
per = max(8, int((width - 200) / cw))
|
|
n = 0
|
|
for part in (s or "").split("\n"):
|
|
n += max(1, math.ceil(len(part) / per))
|
|
return n
|
|
|
|
STAT_LAYER = {"diff": "대조_다름(주황)", "round": "대조_반올림(파랑)", "drawing_blank": "대조_빈칸미추출(노랑)", "model_blank": "대조_빈칸미추출(노랑)"}
|
|
VERD_LAYER = {"차이": "대조_다름(주황)", "일치(반올림)": "대조_반올림(파랑)", "도면 빈칸/모델 미추출": "대조_빈칸미추출(노랑)", "일치": "대조_일치(초록)"}
|
|
|
|
# ------------------------------------------------------------------ 범례
|
|
y = 0.0
|
|
txt((XLEFT + XRIGHT) / 2 - 8000, y + 1900, "운 곡 교 P1 (완도방향) 철 근 재 료 표 — 도면(3.dxf) ↔ 모델(운곡교p1.3dm) 대조", h=300)
|
|
txt((XLEFT + XRIGHT) / 2 - 8000, y + 1450, "각 마크마다 위 줄 = 도면 재료표 값, 아래 줄 = 모델에서 뽑은 값. 다른 칸은 색으로 표시하고 오른쪽 비고 열에 내용을 적음 (검토 미확정, 2026-10-16).", h=130)
|
|
lx = XLEFT
|
|
for name, label in (("대조_다름(주황)", "주황 = 도면과 다름"), ("대조_반올림(파랑)", "파랑 = 반올림 범위(≤1 mm/본)"),
|
|
("대조_빈칸미추출(노랑)", "노랑 = 도면 빈칸 / 모델 미추출"), ("대조_일치(초록)", "초록 = 일치 (판정 열)")):
|
|
fill(lx, y + 700, lx + 500, y + 1100, name)
|
|
txt(lx + 600 + len(label) * 55, y + 900, label, h=130)
|
|
lx += 700 + len(label) * 115
|
|
txt(lx + 1800, y + 900, "자홍 글자 = KDS 24 14 21 기준 상이(비고 3)", h=130, color=MCOL)
|
|
|
|
def build_table(title, rows, ytop):
|
|
"""rows: JSON 행 리스트. 반환: 표 아래 y"""
|
|
y = ytop
|
|
txt((XB[0] + XB[-1]) / 2, y + 400, title, h=220)
|
|
# 헤더 2줄
|
|
hy0 = y
|
|
for i, hd in enumerate(HEAD):
|
|
x0, x1 = XB[i], XB[i + 1]
|
|
txt((x0 + x1) / 2, y - SUB / 2, hd)
|
|
if KEYS[i] in UNITS:
|
|
txt((x0 + x1) / 2, y - SUB * 1.5, UNITS[KEYS[i]])
|
|
txt((XK + XB[0]) / 2, y - SUB, "구분")
|
|
txt((XV0 + XV1) / 2, y - SUB, "판정")
|
|
txt((XN0 + XN1) / 2, y - SUB, "비 고 (칸별 차이 · 도면 내부/도면↔모델 코드)")
|
|
txt((XK3_0 + XK3_1) / 2, y - SUB, "비고 3 (KDS 24 14 21 기준 상이)", color=MCOL)
|
|
y -= SUB * 2
|
|
line(XLEFT, hy0, XRIGHT, hy0, layer="대조_표선")
|
|
line(XLEFT, y, XRIGHT, y)
|
|
body_top = y
|
|
for r in rows:
|
|
auto = " / ".join(r.get("auto", []))
|
|
note = r.get("note", "")
|
|
ntext = auto + ("\\P" if auto and note else "") + note
|
|
need1 = est_lines((auto + "\n" + note).strip("\n"), XN1 - XN0)
|
|
need3 = est_lines(r.get("kds", ""), XK3_1 - XK3_0)
|
|
H = max(2 * SUB, max(need1, need3) * 150 + 80)
|
|
half = H / 2.0
|
|
yt, ym, yb = y, y - half, y - H
|
|
# 칸 색 (도면·모델 두 줄 모두 같은 열에 칠함)
|
|
for k, st in r["res"].items():
|
|
if k in ("shape", "dia") and st == "same":
|
|
continue
|
|
lname = STAT_LAYER.get(st)
|
|
if lname and k in KEYS:
|
|
x0, x1 = col_x(k)
|
|
fill(x0, yb, x1, yt, lname)
|
|
if r.get("kind") == "bar":
|
|
vl = VERD_LAYER.get(r["verdict"])
|
|
if vl:
|
|
fill(XV0, yb, XV1, yt, vl)
|
|
# 글자
|
|
for i, k in enumerate(KEYS):
|
|
x0, x1 = XB[i], XB[i + 1]
|
|
cx = (x0 + x1) / 2
|
|
dtxt = r["d"].get(k, "")
|
|
mtxt_ = r["m"].get(k, "")
|
|
if r["kind"] == "bar" and k in ("shape", "dia"):
|
|
pass
|
|
if r["kind"] != "bar" and k == "num":
|
|
lab = r.get("label", "")
|
|
if r["kind"] == "sum":
|
|
txt((XB[7] + XB[8]) / 2, y - half / 2, "소 계")
|
|
txt((XB[7] + XB[8]) / 2, ym - half / 2, "소 계")
|
|
else:
|
|
short = lab.replace("총 계", "총 계")
|
|
txt((XB[0] + XB[11]) / 2, y - half / 2, short, h=130)
|
|
txt((XB[0] + XB[11]) / 2, ym - half / 2, short + " (모델)" if "겹이음" not in short else "(모델 미산출)", h=130)
|
|
continue
|
|
txt(cx, y - half / 2, dtxt)
|
|
txt(cx, ym - half / 2, mtxt_ if (mtxt_ != "" or dtxt == "") else "-")
|
|
txt((XK + XB[0]) / 2, y - half / 2, "도면")
|
|
txt((XK + XB[0]) / 2, ym - half / 2, "모델")
|
|
if r.get("kind") == "bar":
|
|
txt((XV0 + XV1) / 2, (yt + yb) / 2, {"차이": "차이", "일치(반올림)": "반올림", "도면 빈칸/모델 미추출": "빈칸/미추출", "일치": "일치"}.get(r["verdict"], r["verdict"]), h=120)
|
|
mtxt(XN0 + 100, yt - 50, XN1 - XN0 - 200, ntext)
|
|
mtxt(XK3_0 + 100, yt - 50, XK3_1 - XK3_0 - 200, r.get("kds", ""), layer="대조_비고3_KDS(자홍)", color=MCOL)
|
|
# 선
|
|
line(XLEFT, yb, XRIGHT, yb)
|
|
line(XLEFT, ym, XB[-1], ym, color=8)
|
|
y = yb
|
|
# 세로선
|
|
for x in [XK] + XB + [XV1, XN1, XK3_1]:
|
|
line(x, hy0, x, y)
|
|
line(XLEFT, y, XRIGHT, y, layer="대조_표선")
|
|
return y
|
|
|
|
y1 = build_table("철 근 재 료 표 (SD400)", data["SD400"], -1000.0)
|
|
y2 = build_table("철 근 재 료 표 (SD500)", data["SD500"], y1 - 2500.0)
|
|
doc.header["$EXTMIN"] = (XLEFT, y2, 0)
|
|
doc.header["$EXTMAX"] = (XRIGHT, 2500, 0)
|
|
doc.saveas(OUT)
|
|
print("saved", OUT, "y", y1, y2)
|