# -*- coding: utf-8 -*- """운곡교 P1 재료표 도면(DXF) 대조본 작성. 도면 재료표(3.dxf)와 같은 칸 구성(열 위치·글자 높이·레이어)에 마크마다 두 줄(도면 / 모델)을 놓고, 다른 칸을 색으로 칠하고 오른쪽에 비고 열(비고 · 비고 3 KDS)을 붙인다. 입력: 추출데이터\재료표_대조결과.json (make_bom_compare.py 가 만든다), 3.dxf (스타일·레이어 상속) 사용: python -I make_bom_dxf.py <대조결과.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)