# -*- coding: utf-8 -*- """운곡교 P1 철근재료표 재작성 (모델 기준) + 도면 재료표(3.dxf)와 비교 + 비고. 사용: python -I make_model_bom.py <출력xlsx> - pylib경로 : rhino3dm, openpyxl, ezdxf 가 설치된 폴더 (pip install --target) - 입력(고정): 3.dxf(도면 재료표), 운곡교p1.3dm(모델) """ import sys, re, collections, statistics as st sys.path.insert(0, sys.argv[1]) import ezdxf, rhino3dm from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment, Border, Side from openpyxl.utils import get_column_letter BASE = r"D:\★Pier 자료\심수석님 검토 파일\철근 모델랑 운곡교P1_바이브코딩" DXF3 = BASE + r"\3.dxf" M3DM = BASE + r"\운곡교p1.3dm" OUT = sys.argv[2] UNIT = {"32": 6.230, "29": 5.040, "25": 3.980, "22": 3.040, "19": 2.250, "16": 1.560} # kg/m (도면 표) def surcharge(d): # 도면: D22 이상 6%, 미만 3% return 1.06 if int(d) >= 22 else 1.03 # ---------------------------------------------------------------- 도면 재료표 파싱 doc = ezdxf.readfile(DXF3) texts = [] for e in doc.modelspace(): if e.dxftype() == "TEXT": texts.append((e.dxf.insert.x, e.dxf.insert.y, e.dxf.text)) elif e.dxftype() == "MTEXT": texts.append((e.dxf.insert.x, e.dxf.insert.y, e.plain_text())) COLS = [("num", 1900, 2500), ("shape", 2500, 3500), ("dia", 3500, 4400), ("n", 4400, 5300), ("A", 5300, 6700), ("B", 6700, 8200), ("C", 8200, 9700), ("D", 9700, 11200), ("E", 11200, 12700), ("R", 12700, 14200), ("X1", 14200, 15600), ("X2", 15600, 17100), ("J", 17100, 18800), ("L", 18800, 20500), ("tot", 20500, 21400)] OFF = 23750 rowsraw = collections.defaultdict(dict) for x, y, t in texts: if y > 30300 or y < 20000: pass tab = "SD500" if x >= 25000 else "SD400" xx = x - OFF if tab == "SD500" else x for name, lo, hi in COLS: if lo <= xx < hi: rowsraw[(tab, round(y / 10) * 10)].setdefault(name, []).append(t.strip()) break drw = {} # mark -> dict order_draw = {"SD400": [], "SD500": []} last = {} for (tab, y), cells in sorted(rowsraw.items(), key=lambda kv: (kv[0][0], -kv[0][1])): num = (cells.get("num") or [""])[0] if not re.match(r"^[A-Z]{1,2}\d+(-\d+)?$", num) or y < 20000 or y > 30000: continue r = {"tab": tab, "mark": num} for k in ("shape", "dia", "n", "A", "B", "C", "D", "E", "R", "X1", "X2", "J", "L", "tot"): r[k] = (cells.get(k) or [None])[0] for k in ("shape", "dia"): if r[k] == '"': r[k] = last[(tab, k)] else: last[(tab, k)] = r[k] drw[num] = r order_draw[tab].append(num) def f(s): try: return float(s) except Exception: return None for m, r in drw.items(): r["n_i"] = int(r["n"]) if r["n"] and r["n"].isdigit() else None r["L_f"] = f(r["L"]) if r["L"] else None r["tot_f"] = f(r["tot"]) if r["tot"] else None r["dia_n"] = re.sub(r"\D", "", r["dia"] or "") dims = [] for k in ("A", "B", "C", "D", "E", "R", "X1", "X2", "J"): if r[k]: dims.append("%s=%s" % (k, r[k])) r["dims"] = " ".join(dims) # ---------------------------------------------------------------- 모델 m3 = rhino3dm.File3dm.Read(M3DM) lay = {l.Index: l.FullPath for l in m3.Layers} def curve_len(c, n=3000): d = c.Domain p0 = c.PointAt(d.T0) s = 0.0 for i in range(1, n + 1): p = c.PointAt(d.T0 + (d.T1 - d.T0) * i / n) s += ((p.X - p0.X) ** 2 + (p.Y - p0.Y) ** 2 + (p.Z - p0.Z) ** 2) ** 0.5 p0 = p return s bars = collections.defaultdict(dict) # mark -> occ -> dict for o in m3.Objects: L = lay.get(o.Attributes.LayerIndex, "") if not L.startswith("P1::철근"): continue g = o.Geometry nm = o.Attributes.Name or "" if isinstance(g, rhino3dm.Brep): us = dict(o.Attributes.GetUserStrings()) mk = us.get("mark") occ = us.get("occurrence") b = bars[mk].setdefault(occ, {}) b.update(us=us, name=nm, layer=L) elif L.endswith("중심선"): p = nm.split("-") if len(p) >= 4 and p[1] == "RC": mk = p[2] occ = "-".join(p[3:]) bars[mk].setdefault(occ, {})["cl"] = curve_len(g) # ---------------------------------------------------------------- 마크별 모델 집계 def num(s): try: return float(s) except Exception: return 0.0 mod = {} for mk, d in bars.items(): if mk is None: continue items = [b for b in d.values() if "us" in b] Ls = [num(b["us"].get("design_length")) + num(b["us"].get("coupler_length")) for b in items] cls = [b.get("cl") for b in items if b.get("cl") is not None] pieces = len(items) # P3·P4 는 반원 2조각 = 1링 (재료표 개수 = 링 수) half = mk in ("P3", "P4") n_tab = pieces // 2 if half else pieces mod[mk] = dict(pieces=pieces, n=n_tab, tot=sum(Ls) / 1000.0, Lmean=(sum(Ls) / n_tab / 1000.0) if n_tab else 0, Lmin=min(Ls) / 1000.0, Lmax=max(Ls) / 1000.0, cl=sum(cls) / 1000.0 if cls else None, dia=items[0]["us"].get("dia"), shape=items[0]["us"].get("shape"), items=items, half=half) # C9~C13 : 도면 폭(1.407) 기준으로 환산한 길이 -> 폭 영향 / (최대+최소)/2 근사 영향 분리 def width_effect(mk): ex = 0.0 for b in mod[mk]["items"]: w = num(b["us"].get("w_model")) if w: ex += 2 * (1407.0 - w) return ex / 1000.0 # ---------------------------------------------------------------- 비고 사전 NOTE = {} KDS = {} def n_(mk, s): NOTE[mk] = s for mk in ("C1", "C4"): n_(mk, "L 도면 14.490 ↔ 모델 14.489 (0.001 m, 각진 치수 반올림). [D-8] 도면 내부: 재료표 다리각 98°/96° ↔ 정면도 96.4°.") for mk in ("C2", "C5"): n_(mk, "[D-7] 도면 내부: 재료표 L 12.081 ↔ 치수 합 A+B+C 12.082 (모델 12.082, 1 mm). [D-8] 다리각 98°/96° ↔ 정면도 96.4°.") for mk in ("C3", "C6"): n_(mk, "개수·길이 일치. [D-8] 도면 내부: 재료표 다리각 98°/96° ↔ 정면도 96.4°.") for mk in ("C7", "C8"): n_(mk, "개수·길이 일치. [D-9] 도면 내부: 재료표 B·D·R·X·J 빈칸 → 모델은 정면도 R 12,100(하면 R 12,000 동심)·평면부 EL 40.511 로 작성. [D-10] 다리 650 은 정면도 겹침으로 확인 불가.") n_("C9", "[D-4] 스터럽 폭 도면 재료표 1.407 ↔ 단면 작도 1.160/1.740/1.160 (모델은 작도값). [D-6] 재료표 L = (최대+최소)/2 근사 → 모델은 철근마다 개별 변길이. [M-5] 길이 산식 +54.3 규칙은 변하는 치수에만 적용.") n_("C10", "[D-4] 스터럽 폭 1.407 ↔ 작도 1.160 (모델은 작도값). 변길이 b=2.998 고정이라 [D-6] 근사 영향 없음. [M-5] +54.3 규칙.") n_("C11", "[D-4] 스터럽 폭 1.407 ↔ 작도 1.160/1.740/1.160. [D-6] (최대+최소)/2 근사 vs 모델 개별 변길이. [M-5] +54.3 규칙.") n_("C12", "[D-5] 수평폐합근 폭 1.407 ↔ 작도 1.106/1.686/1.106 (모델은 작도값). [D-6] 길이 (최대+최소)/2 근사 vs 모델 개별. [M-5] +54.3 규칙.") n_("C13", "[D-5] 수평폐합근 폭 1.407 ↔ 작도 1.106/1.686/1.106. [D-6] (최대+최소)/2 근사 vs 모델 개별. [M-5] +54.3 규칙.") n_("C14", "개수·길이 일치. [M-1] 모델 최하단 점 2개(x=±1913, ±2913)가 도면보다 350 mm 높음(근거 기록 없음). [M-2] 훅 방향 도면 표기 없음 — 모델은 아래로 가정, 도면 점 위치로는 하부 점은 위·상부 점은 아래일 가능성(추정, 간접 확인).") n_("C15", "개수·길이 일치.") n_("C16", "개수·길이 일치. 훅 방향·정면도 좌측 끝은 도면 직접 표기 없음 (재료표 L 5.611 = 5.261 + 0.350 한쪽 훅으로 간접 확인).") for mk in ("S1", "S2"): n_(mk, "개수·길이 일치. [M-6] 높이 기준: 도면(D2 전열 상세)은 대좌 상면 -50 ↔ 모델은 코핑 상면 -50 (139~164 mm 낮음). [M-7] 층 순서: 도면은 S2 점이 S1 선 위 ↔ 모델은 S1 위·S2 50 mm 아래(모델 src '추론').") n_("UA1", "[M-13] 모델에 없음(0본). 도면은 기초 조립용 '배치간격 예시도'(1.0×1.2 예시)라 수량 73본 근거 미확인. 도면 총길이 377.848 m 가 모델에는 빠짐.") n_("P1", "개수·길이 일치(모델 17.247). [D-20] 주철근 상단: 정면도 EL 43.011 ↔ 재료표 EL 43.061 (모델은 재료표). [D-21] 반지름 치수·선 875 ↔ 점 870.5 (모델은 점).") n_("P2", "개수·길이 일치. [D-20] 주철근 상단: 정면도 EL 40.411 ↔ 재료표 EL 43.061 (코핑 정착 2.65 m 차이, 모델은 재료표). [D-21] 반지름 830 ↔ 점 825.5 (모델은 점).") n_("P3", "모델은 반원 2조각(72개)=1링, 재료표 개수 36 = 링 수로 환산. 링 길이 일치. [M-12] 단마다 90° 엇갈린 이음 각도는 도면에 기준 없음(모델 가정, 간접).") n_("P4", "모델은 반원 2조각(18개)=1링(커플러 포함 L 5.636), 재료표 개수 9 = 링 수로 환산. [D-23] 라벨 H19 @100 ↔ 치수 8@120=960 (모델 @120 — 구조계산서 확인 사항, E-01). [M-12] 커플러 각도 도면에 없음.") n_("P5", "개수·길이 일치(커플러 65 포함 L 5.636). [D-23] 라벨 @100 ↔ 치수 22@120=2,640 (모델 @120, 구조계산서 확인). [D-22] 링 점 902 ↔ R 897. [M-12] 커플러 각도 도면에 없음.") n_("P6", "개수·길이 일치. 정면도 선 반길이 875 ↔ 모델 직선 894 (한쪽 19 mm, 경미).") n_("B1", "개수·길이 일치. [M-8] 모델 교차 철근 간격 54~56 mm ↔ 도면 두 뷰 모두 27~28 mm(점 값을 서로 다른 뷰에서 가져옴: B1 2073 ↔ B5 2127). [D-15] 같은 철근을 점/선으로 그린 높이가 정면도·측면도에서 27~28 mm 다름.") n_("B2", "개수·길이 일치. [M-9] 다리 끝 +2072 (도면 선 기준 +2100). [D-15] 점/선 높이 27~28 mm 차.") n_("B3", "개수·길이 일치. [D-17] 도면 중앙 치수 33@250+450(36본) ↔ 점·재료표 38본 (모델 38).") n_("B4", "개수·길이 일치. [M-8] 하면 교차 철근 간격 영향(B2·B4).") n_("B5", "개수·길이 일치. [M-9] 모델 중심 73 아래(순피복 58.5 mm, 도면 표기 상면 100, 선행 E-06). [M-8] 교차 철근 간격.") n_("B6", "개수·길이 일치. [M-9] 다리 끝 +2128 (도면 선 기준 +2100, 선행 E-06 피복 57.5). [M-8] 교차 철근 간격.") n_("B7", "개수·길이 일치. [M-8] 교차 철근 간격.") n_("B8", "개수·길이 일치.") n_("B9", "[M-01 선행] 도면 재료표 개수 빈칸. 라벨 H16 @500 + 격자 34×29(@250) 체크무늬 986/2 = 493본을 총길이 1,062.415 ÷ L 2.155 로 역산(간접 확인) → 모델 493본, 총길이 일치. 기초 내 위치 확정은 도면에 없음.") n_("B10", "개수·길이 일치. [D-18] 치수 9@200(10단) ↔ 점·재료표 9단 (모델 9단), 표 9.198×7.644 ↔ 점 ±4593/±3818.") n_("B11", "개수·길이 일치. B11·B12 4단 높이(z 550)와 위치 일치.") n_("B12", "[D-16] 도면 내부: 재료표 34본 ↔ 작도 28곳 (선행 E-05, 6본 과다 또는 작도 누락, 46.8 m). 모델은 작도 28본을 따름 → 6본 차이.") KDS["C1"] = KDS["C2"] = KDS["C3"] = KDS["C4"] = KDS["C5"] = KDS["C6"] = "[M-4] 내면반지름 여유 거의 없음: 모델 내면지름 101.5 mm ↔ 3.5db 100.1/101.5 mm (H29, 표 4.5-1). 도면에 내면반지름 표기 없음." KDS["C7"] = KDS["C8"] = "[M-3] 굽힘 4곳이 각진 연결(내면반지름 미반영) → 내면지름 3.5db(H29 100.1 mm) 미적용 (KDS 24 14 21 표 4.5-1)." KDS["P3"] = "[M-11] 갈고리 연장 모델 86.7 mm < 5db 95 mm (KDS 4.5.4), 내면지름 66.5 mm < 공칭 기준 66.85 mm (0.35 mm 부족)." KDS["B1"] = KDS["B2"] = "[M-10] S25 굽힘: 모델 내면지름 87.6 mm < 공칭 기준 88.9 mm (1.3 mm 부족, 3.5db)." KDS["B3"] = KDS["B7"] = KDS["B10"] = "[M-10] S19 굽힘: 모델 내면지름 66.6 mm < 공칭 기준 66.9 mm (0.3 mm 부족, 3.5db)." # ---------------------------------------------------------------- 엑셀 wb = Workbook() ws = wb.active ws.title = "철근재료표_모델기준" thin = Side(style="thin", color="999999") BR = Border(left=thin, right=thin, top=thin, bottom=thin) HF = PatternFill("solid", fgColor="D9D9D9") GREEN = PatternFill("solid", fgColor="E2F0D9") ORANGE = PatternFill("solid", fgColor="FCE4D6") YEL = PatternFill("solid", fgColor="FFF2CC") GREY = PatternFill("solid", fgColor="EDEDED") heads = ["번호", "형식", "직경", "도면 치수(재료표 표기)", "도면 개수", "도면 L(m)", "도면 총길이(m)", "모델 개수", "모델 L 평균(m)", "모델 L 범위(m)", "모델 총길이(m)\n(재료표 방식: 각진 치수+커플러)", "모델 중심선 실총길이(m)\n(굽힘 반영)", "개수 차이\n(모델−도면)", "총길이 차이(m)\n(모델−도면)", "판정", "단위중량\n(kg/m)", "도면 총중량(t)", "모델 총중량(t)", "비고 (도면↔모델 차이 · 도면 내부 불일치)", "비고 3 (KDS 24 14 21 기준 상이)"] ws.append(["운곡교 P1 (완도방향) 철근재료표 — 모델(운곡교p1.3dm) 기준 재작성 + 도면 재료표(3.dxf) 대비 비고"]) ws.append(["작성 2026-10-16 · 검토 미확정 · 모델 L = 철근 모델의 재료표식 길이(각진 치수 합 + 커플러), 중심선 실길이는 굽힘 반영값이라 짧음 · " "[M-n]=도면↔모델, [D-n]=도면 내부, KDS=기준↔모델 (검토기록_최종.md 번호)"]) ws.append([]) ws.append(heads) HR = 4 for c in range(1, len(heads) + 1): cell = ws.cell(row=HR, column=c) cell.font = Font(bold=True) cell.fill = HF cell.border = BR cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True) def status(dn, mn, dt, mt, dL=None, mL=None): if dn is None: return "도면 개수 빈칸" if mn != dn: return "차이" if dL is not None and mL is not None and abs(mL - dL) <= 0.0015: # 반올림 수준(≤1.5 mm/본) return "일치" if abs(mt - dt) <= 0.0005 else "일치(경미)" if abs(mt - dt) > 0.01: return "차이" return "일치(경미)" if abs(mt - dt) > 0.0005 else "일치" summary_rows = [] # 소계 계산용 bw_rows = [] # C9~C13 차이 분해 all_rows = [] # 요약용 (마크, 판정, 개수차, 총길이차) sections = [("SD400", "SD400 (코핑 C·S, 기둥 P3~P6, 기초 B9, UA1)"), ("SD500", "SD500 (기둥 P1·P2, 기초 B1~B8, B10~B12)")] tot_all = {} for tab, title in sections: r0 = ws.max_row + 1 ws.append([title]) ws.cell(row=r0, column=1).font = Font(bold=True, size=12) # 직경별 묶음 (도면 순서 유지, 직경 큰 순) marks = order_draw[tab] groups = collections.OrderedDict() for mk in marks: groups.setdefault(drw[mk]["dia_n"], []).append(mk) grp_tot = [] for dia, mks in groups.items(): sd = drw[mks[0]]["dia"] gd = gm = gdw = gmw = 0.0 gcl = 0.0 for mk in mks: d = drw[mk] mm = mod.get(mk) mn = mm["n"] if mm else 0 mt = mm["tot"] if mm else 0.0 mcl = mm["cl"] if mm and mm["cl"] is not None else None dn = d["n_i"] dt = d["tot_f"] or 0.0 Lr = ("%.3f ~ %.3f" % (mm["Lmin"], mm["Lmax"])) if mm and abs(mm["Lmax"] - mm["Lmin"]) > 1e-6 else "" stt = status(dn, mn, dt, mt, d["L_f"], mm["Lmean"] if mm else None) note = NOTE.get(mk, "") if mk in ("C9", "C10", "C11", "C12", "C13") and mm: we = width_effect(mk) diff = mt - dt note += " ▶ 총길이 차이 %+.3f m = 폭 영향 %+.3f m + 근사·기타 %+.3f m." % (diff, -we, diff + we) note += " 중심선 실길이는 겹침 이음(미작도, 길이만 반영)이 빠져 재료표 길이보다 짧음." bw_rows.append((mk, dt, mt, diff, -we, diff + we)) if stt in ("차이", "도면 개수 빈칸") and mm is None: pass unit = UNIT[dia] row = [mk, d["shape"], d["dia"], d["dims"], dn if dn is not None else "(빈칸)", d["L_f"], dt, mn, round(mm["Lmean"], 3) if mm else 0, Lr, round(mt, 3), round(mcl, 3) if mcl is not None else None, (mn - dn) if dn is not None else None, round(mt - dt, 3), stt, unit, round(dt * unit / 1000, 3), round(mt * unit / 1000, 3), note, KDS.get(mk, "")] ws.append(row) all_rows.append((mk, stt, (mn - dn) if dn is not None else None, mt - dt)) rr = ws.max_row fill = {"일치": GREEN, "일치(경미)": GREEN, "차이": ORANGE, "도면 개수 빈칸": YEL}[stt] for c in range(1, len(heads) + 1): cell = ws.cell(row=rr, column=c) cell.border = BR cell.alignment = Alignment(vertical="top", wrap_text=(c in (4, 19, 20))) if c in (15,): cell.fill = fill ws.cell(row=rr, column=20).font = Font(color="FF00FF") for c in (7, 11, 12, 14): ws.cell(row=rr, column=c).number_format = "0.000" for c in (17, 18): ws.cell(row=rr, column=c).number_format = "0.000" gd += dt; gm += mt; gcl += (mcl or 0.0) gdw = gd * UNIT[dia] / 1000 gmw = gm * UNIT[dia] / 1000 sc = surcharge(dia) grp_tot.append((sd, gd, gm, gcl, gdw, gmw, sc)) ws.append(["소 계", "", sd, "", "", "", round(gd, 3), "", "", "", round(gm, 3), round(gcl, 3), "", round(gm - gd, 3), "", UNIT[dia], round(gdw, 3), round(gmw, 3), "할증 %d%% 적용 시: 도면 %.3f t / 모델 %.3f t" % (round((sc - 1) * 100), gdw * sc, gmw * sc), ""]) rr = ws.max_row for c in range(1, len(heads) + 1): ws.cell(row=rr, column=c).fill = GREY ws.cell(row=rr, column=c).font = Font(bold=True) ws.cell(row=rr, column=c).border = BR for c in (7, 11, 12, 14, 17, 18): ws.cell(row=rr, column=c).number_format = "0.000" sd_ = sum(g[1] for g in grp_tot); sm_ = sum(g[2] for g in grp_tot); scl = sum(g[3] for g in grp_tot) dw = sum(g[4] for g in grp_tot); mw = sum(g[5] for g in grp_tot) dws = sum(g[4] * g[6] for g in grp_tot); mws = sum(g[5] * g[6] for g in grp_tot) ws.append(["총 계 (%s)" % tab, "", "", "", "", "", round(sd_, 3), "", "", "", round(sm_, 3), round(scl, 3), "", round(sm_ - sd_, 3), "", "", round(dw, 3), round(mw, 3), "할증 적용 총중량: 도면 %.3f t / 모델 %.3f t (모델 − 도면 %+.3f t)" % (dws, mws, mws - dws), ""]) rr = ws.max_row for c in range(1, len(heads) + 1): ws.cell(row=rr, column=c).fill = HF ws.cell(row=rr, column=c).font = Font(bold=True) ws.cell(row=rr, column=c).border = BR for c in (7, 11, 12, 14, 17, 18): ws.cell(row=rr, column=c).number_format = "0.000" tot_all[tab] = (sd_, sm_, dw, mw, dws, mws) ws.append([]) widths = [8, 7, 7, 34, 8, 9, 11, 8, 10, 14, 17, 17, 9, 12, 12, 9, 11, 11, 70, 48] for i, w in enumerate(widths, 1): ws.column_dimensions[get_column_letter(i)].width = w ws.row_dimensions[HR].height = 48 ws.freeze_panes = "B5" # ---------------------------------------------------------------- 시트 0: 요약 w0 = wb.create_sheet("요약", 0) w0.append(["도면 재료표 ↔ 모델 재료표 차이 요약 (운곡교 P1, 2026-10-16 작성, 검토 미확정)"]) w0.append([]) w0.append(["구분", "마크", "도면 총길이(m)", "모델 총길이(m)", "차이(m)", "내용"]) dd = {m: drw[m]["tot_f"] or 0.0 for m in drw} mm_ = {m: mod[m]["tot"] if m in mod else 0.0 for m in drw} w0.append(["개수·길이 불일치", "UA1", dd["UA1"], mm_["UA1"], round(mm_["UA1"] - dd["UA1"], 3), "모델에 없음 (M-13). 도면은 배치 예시도, 73본 근거 미확인"]) w0.append(["개수·길이 불일치", "B12", dd["B12"], mm_["B12"], round(mm_["B12"] - dd["B12"], 3), "도면 재료표 34본 ↔ 작도·모델 28본 (D-16, 6본)"]) sw = sum(r[4] for r in bw_rows); so = sum(r[5] for r in bw_rows) w0.append(["길이 불일치", "C9~C13", round(sum(r[1] for r in bw_rows), 3), round(sum(r[2] for r in bw_rows), 3), round(sum(r[3] for r in bw_rows), 3), "스터럽·폐합근: 폭 1.407↔작도값 영향 %+.3f m + (최대+최소)/2 근사 등 %+.3f m (D-4/D-5/D-6)" % (sw, so)]) for r in bw_rows: w0.append([" (세부)", r[0], round(r[1], 3), round(r[2], 3), round(r[3], 3), "폭 영향 %+.3f / 근사·기타 %+.3f" % (r[4], r[5])]) w0.append(["개수 표기 차이(합계 일치)", "P3·P4", "", "", 0, "모델은 반원 2조각 = 1링 (P3 72개, P4 18개), 재료표 개수는 링 수"]) w0.append(["개수 빈칸 보완", "B9", dd["B9"], mm_["B9"], round(mm_["B9"] - dd["B9"], 3), "도면 개수 빈칸 → 총길이 역산 493본(체크무늬)이 모델과 일치 (간접 확인)"]) w0.append([]) w0.append(["재료표 합계", "", "도면", "모델", "차이", ""]) for tab, v in tot_all.items(): w0.append([tab + " 총길이(m)", "", round(v[0], 3), round(v[1], 3), round(v[1] - v[0], 3), ""]) w0.append([tab + " 총중량(t)", "", round(v[2], 3), round(v[3], 3), round(v[3] - v[2], 3), "할증 적용: 도면 %.3f / 모델 %.3f" % (v[4], v[5])]) w0.append([]) w0.append(["그 외 마크는 개수·길이가 일치(반올림 ≤1.5 mm/본 포함)이며 위치·형상·굽힘 차이는 '철근재료표_모델기준' 시트 비고와 비고 3(KDS)에 적었음."]) for i, w in enumerate([24, 10, 16, 16, 12, 100], 1): w0.column_dimensions[get_column_letter(i)].width = w for c in range(1, 7): w0.cell(row=3, column=c).font = Font(bold=True); w0.cell(row=3, column=c).fill = HF w0.cell(row=1, column=1).font = Font(bold=True, size=12) # ---------------------------------------------------------------- 시트 2: 비고 코드 설명 w2 = wb.create_sheet("비고코드설명") w2.append(["코드", "구분", "내용"]) codes = [ ("M-1", "도면↔모델", "C14 최하단 점 2개 모델이 350 mm 높음(근거 없음)"), ("M-2", "미확인", "C14 훅 방향 도면 표기 없음 (모델은 아래 가정)"), ("M-3", "기준↔모델", "C7·C8 굽힘 4곳 각진 연결 (내면반지름 미반영)"), ("M-4", "기준↔모델", "C1~C6 내면반지름 여유 거의 없음"), ("M-5", "도면↔모델", "C9~C13 길이 산식 +54.3 규칙이 변하는 치수에만 적용"), ("M-6", "도면↔모델", "S1·S2 높이 기준 (도면 대좌 상면 -50 ↔ 모델 코핑 상면 -50)"), ("M-7", "도면↔모델", "S1·S2 층 순서 (도면 S2 위 ↔ 모델 S1 위)"), ("M-8", "도면↔모델", "기초 교차 철근 간격 (도면 27~28 mm ↔ 모델 54~56 mm)"), ("M-9", "도면↔모델", "기초 B5 중심 73 아래(피복 58.5), B2·B6 다리 끝 높이"), ("M-10", "기준↔모델", "기초 S25·S19 굽힘 내면지름이 공칭 기준에 1.3/0.3 mm 부족"), ("M-11", "기준↔모델", "P3 갈고리 연장 86.7 < 5db 95, 내면지름 66.5 < 66.85"), ("M-12", "미확인", "P3 이음·P4/P5 커플러 각도 도면에 없음 (모델 가정)"), ("M-13", "도면↔모델", "UA1(73본, H22) 모델에 없음"), ("D-4/D-5", "도면 내부", "스터럽 폭 재료표 1.407 ↔ 단면 작도 1.160/1.740, 폐합근 1.106/1.686"), ("D-6", "도면 내부", "재료표 L 이 (최대+최소)/2 근사 → 총길이 과대"), ("D-7", "도면 내부", "C2·C5 L 12.081 ↔ 치수 합 12.082"), ("D-8", "도면 내부", "다리 각도 재료표 98°/96° ↔ 정면도 96.4°"), ("D-9", "도면 내부", "C7·C8 재료표 B·D·R·X·J 빈칸"), ("D-15~D-18", "도면 내부", "기초 점/선 높이 27~28 mm 차, B12 34↔28, B3 치수↔본수, B10 단 수"), ("D-20~D-23", "도면 내부", "P1/P2 상단 EL, 기둥 반지름, 링 R, P4·P5 @100↔@120"), ("E-01", "선행보고서", "기둥 띠철근 간격 → 구조계산서 확인 사항"), ] for c in codes: w2.append(list(c)) for i, w in enumerate([12, 14, 90], 1): w2.column_dimensions[get_column_letter(i)].width = w for c in range(1, 4): w2.cell(row=1, column=c).font = Font(bold=True); w2.cell(row=1, column=c).fill = HF # ---------------------------------------------------------------- 시트 3: 모델 개별 철근 w3 = wb.create_sheet("모델_개별철근") w3.append(["마크", "직경", "형식", "발생(occurrence)", "모델 L(각진+커플러, mm)", "커플러(mm)", "중심선 실길이(mm)", "호스트", "근거(src)"]) for mk in sorted(mod, key=lambda s: (re.sub(r"\d", "", s), int(re.sub(r"\D", "", s) or 0))): for occ, b in sorted(bars[mk].items()): if "us" not in b: continue us = b["us"] w3.append([mk, us.get("dia"), us.get("shape"), occ, round(num(us.get("design_length")) + num(us.get("coupler_length")), 3), num(us.get("coupler_length")), round(b["cl"], 1) if b.get("cl") is not None else None, us.get("host"), us.get("src")]) for c in range(1, 10): w3.cell(row=1, column=c).font = Font(bold=True); w3.cell(row=1, column=c).fill = HF for i, w in enumerate([8, 7, 7, 18, 20, 11, 18, 10, 70], 1): w3.column_dimensions[get_column_letter(i)].width = w w3.freeze_panes = "A2" wb.save(OUT) print("saved", OUT) for tab, v in tot_all.items(): print(tab, "도면 총길이 %.3f / 모델 %.3f | 도면 t %.3f(할증 %.3f) / 모델 t %.3f(할증 %.3f)" % (v[0], v[1], v[2], v[4], v[3], v[5])) print("drawing marks:", len(drw), "model marks:", len(mod)) miss = [m for m in drw if m not in mod] extra = [m for m in mod if m not in drw] print("도면에만:", miss, "모델에만:", extra)