Files
Pier_v1/심수석님 검토 파일/검토_운곡교P1/스크립트/make_model_bom.py
T
ahnyongjooandClaude Sonnet 5.5 964a841de1 chore(rebar): Pier_T_rebar 작업 폴더 초기 구성 및 외부 의존 파일 통합
- 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>
2026-10-28 11:20:18 +09:00

411 lines
25 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""운곡교 P1 철근재료표 재작성 (모델 기준) + 도면 재료표(3.dxf)와 비교 + 비고.
사용: python -I make_model_bom.py <pylib경로> <출력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)