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>
This commit is contained in:
commit
964a841de1
3978 files changed
+58013356
No files matched your search
@@ -0,0 +1,692 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""운곡교 P1 철근재료표 대조 (도면 재료표와 같은 양식으로 도면표 재현 → 모델표 작성 → 대조·비고).
|
||||
|
||||
사용: python -I make_bom_compare.py <pylib경로> <출력xlsx>
|
||||
입력(고정): 바이브코딩 폴더의 3.dxf (도면 재료표), 운곡교p1.3dm (모델)
|
||||
시트: ①도면 재료표(재현) ②모델 재료표 ③대조·비고 ④요약 ⑤비고코드설명 ⑥모델_개별철근
|
||||
"""
|
||||
import sys, re, math, collections
|
||||
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}
|
||||
TOL = 1.0 + 1e-6 # mm : 도면 값은 mm 로 반올림 표기
|
||||
|
||||
# =============================================================== 1. 도면 재료표 파싱
|
||||
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), ("unit", 21400, 22500),
|
||||
("w", 22500, 23600), ("wh", 23600, 25000)]
|
||||
OFF = 23750
|
||||
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()))
|
||||
rowsraw = collections.defaultdict(lambda: collections.defaultdict(list))
|
||||
for x, y, t in texts:
|
||||
if not (18800 < y < 30000):
|
||||
continue
|
||||
tab = "SD500" if x >= 25000 else "SD400"
|
||||
xx = x - OFF if tab == "SD500" else x
|
||||
if 8500 <= xx < 12500 and "계" in t:
|
||||
rowsraw[(tab, round(y / 10) * 10)]["label"].append(t.strip().replace("%%%", "%"))
|
||||
continue
|
||||
for name, lo, hi in COLS:
|
||||
if lo <= xx < hi:
|
||||
rowsraw[(tab, round(y / 10) * 10)][name].append((xx, t.strip().replace("%%%", "%")))
|
||||
break
|
||||
|
||||
def first(c, k):
|
||||
v = c.get(k)
|
||||
if not v:
|
||||
return ""
|
||||
return sorted(v)[0][1] if isinstance(v[0], tuple) else v[0]
|
||||
|
||||
drow = {"SD400": [], "SD500": []} # 도면 행 (위→아래)
|
||||
for (tab, y), c in sorted(rowsraw.items(), key=lambda kv: (kv[0][0], -kv[0][1])):
|
||||
lab = " ".join(c.get("label", []))
|
||||
num = first(c, "num")
|
||||
if lab:
|
||||
if y > 25000 + 100 and tab == "SD500" and "겹이음" in lab:
|
||||
pass
|
||||
drow[tab].append({"kind": "sum", "label": lab, "cells": {k: first(c, k) for k in ("tot", "unit", "w", "wh")}})
|
||||
elif re.match(r"^[A-Z]{1,2}\d+$", num):
|
||||
d = {k: first(c, k) for k, _, _ in COLS}
|
||||
drow[tab].append({"kind": "bar", "mark": num, "cells": d})
|
||||
# 겹이음 총계 행(라벨 위치가 달라 별도 수집): 도면 값만
|
||||
for (tab, y), c in rowsraw.items():
|
||||
pass
|
||||
|
||||
def mm(s):
|
||||
"""도면 표기 → mm 리스트 (범위는 큰 값~작은 값)"""
|
||||
if s is None or s == "" or s == '"':
|
||||
return None
|
||||
s = s.replace("°", "")
|
||||
out = []
|
||||
for part in s.split("~"):
|
||||
try:
|
||||
v = float(part)
|
||||
except ValueError:
|
||||
return None
|
||||
out.append(v * 1000.0 if ("." in part and abs(v) < 100) else v)
|
||||
return out
|
||||
|
||||
# 도면 행의 ditto(") 해소
|
||||
for tab in drow:
|
||||
last = {}
|
||||
for r in drow[tab]:
|
||||
if r["kind"] != "bar":
|
||||
continue
|
||||
for k in ("shape", "dia"):
|
||||
if r["cells"][k] == '"':
|
||||
r["cells"][k + "_v"] = last[k]
|
||||
else:
|
||||
r["cells"][k + "_v"] = r["cells"][k]
|
||||
last[k] = r["cells"][k]
|
||||
r["dia_n"] = re.sub(r"\D", "", r["cells"]["dia_v"])
|
||||
DR = {r["mark"]: r for tab in drow for r in drow[tab] if r["kind"] == "bar"}
|
||||
|
||||
# =============================================================== 2. 모델 읽기 / 치수 추출
|
||||
m3 = rhino3dm.File3dm.Read(M3DM)
|
||||
lay = {l.Index: l.FullPath for l in m3.Layers}
|
||||
|
||||
def V(p): return (p.X, p.Y, p.Z)
|
||||
def sub(a, b): return (a[0] - b[0], a[1] - b[1], a[2] - b[2])
|
||||
def dot(a, b): return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
|
||||
def nrm(a): return math.sqrt(dot(a, a))
|
||||
def addv(a, b, t): return (a[0] + b[0] * t, a[1] + b[1] * t, a[2] + b[2] * t)
|
||||
def isect(p1, d1, p2, d2):
|
||||
w = sub(p1, p2); a = dot(d1, d1); b = dot(d1, d2); c = dot(d2, d2); d = dot(d1, w); e = dot(d2, w)
|
||||
den = a * c - b * b
|
||||
if abs(den) < 1e-9:
|
||||
return None
|
||||
return addv(p1, d1, (b * e - c * d) / den)
|
||||
def segs_of(g):
|
||||
if isinstance(g, rhino3dm.PolyCurve):
|
||||
return [g.SegmentCurve(i) for i in range(g.SegmentCount)]
|
||||
return [g]
|
||||
def is_line(s): return type(s).__name__ in ("LineCurve", "PolylineCurve")
|
||||
def clen(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 += nrm(sub(V(p), V(p0))); p0 = p
|
||||
return s
|
||||
def arc_len(s): return s.Arc.Radius * math.radians(s.Arc.AngleDegrees)
|
||||
|
||||
def corner_seq(g):
|
||||
"""직선 구간을 연장해 만든 꼭짓점 사이 거리(각진 치수). 닫힌 곡선은 시작/끝 변을 합침."""
|
||||
lines = [(V(s.PointAtStart), V(s.PointAtEnd)) for s in segs_of(g) if is_line(s)]
|
||||
closed = nrm(sub(V(g.PointAtStart), V(g.PointAtEnd))) < 1e-3
|
||||
pts = []
|
||||
for i in range(len(lines) - 1):
|
||||
p1, q1 = lines[i]; p2, q2 = lines[i + 1]
|
||||
pts.append(isect(p1, sub(q1, p1), p2, sub(q2, p2)))
|
||||
if closed:
|
||||
out = [nrm(sub(pts[i + 1], pts[i])) for i in range(len(pts) - 1)]
|
||||
out.append(nrm(sub(pts[0], lines[0][0])) + nrm(sub(lines[-1][1], pts[-1])))
|
||||
return out, lines, pts
|
||||
seq = [lines[0][0]] + pts + [lines[-1][1]]
|
||||
return [nrm(sub(seq[i + 1], seq[i])) for i in range(len(seq) - 1)], lines, pts
|
||||
|
||||
def ang_at(corner, a, b):
|
||||
va = sub(a, corner); vb = sub(b, corner)
|
||||
c = dot(va, vb) / (nrm(va) * nrm(vb))
|
||||
return math.degrees(math.acos(max(-1, min(1, c))))
|
||||
|
||||
def num(s):
|
||||
try: return float(s)
|
||||
except Exception: return 0.0
|
||||
|
||||
def bar_dims(shape, us, g):
|
||||
"""한 철근의 도면 재료표 항목(A,B,C,D,E,R,X1,X2) 을 mm / 도(°) 로 반환"""
|
||||
d = {}
|
||||
if shape in ("I", "XVI"):
|
||||
d["A"] = corner_seq(g)[0][0]
|
||||
elif shape in ("III", "XII"):
|
||||
seq = corner_seq(g)[0]
|
||||
d["A"] = seq[1]; d["B"] = max(seq[0], seq[2]) if abs(seq[0] - seq[2]) > 1 else seq[0]
|
||||
if abs(seq[0] - seq[2]) > 1: d["B2"] = min(seq[0], seq[2])
|
||||
elif shape == "II":
|
||||
seq = corner_seq(g)[0]
|
||||
d["A"] = max(seq); d["B"] = min(seq)
|
||||
elif shape == "VIII":
|
||||
seq, lines, pts = corner_seq(g)
|
||||
d["A"] = seq[1]; d["B"] = seq[0]; d["C"] = seq[2]
|
||||
d["X1"] = ang_at(pts[0], lines[0][0], pts[1]); d["X2"] = ang_at(pts[1], lines[2][1], pts[0])
|
||||
elif shape == "VII":
|
||||
ls = [s for s in segs_of(g) if is_line(s)]
|
||||
arcs = [s for s in segs_of(g) if type(s).__name__ == "ArcCurve"]
|
||||
d["A"] = nrm(sub(V(ls[0].PointAtEnd), V(ls[0].PointAtStart)))
|
||||
d["C"] = nrm(sub(V(ls[1].PointAtEnd), V(ls[1].PointAtStart)))
|
||||
d["E"] = nrm(sub(V(ls[2].PointAtEnd), V(ls[2].PointAtStart)))
|
||||
d["R"] = arcs[0].Arc.Radius
|
||||
elif shape == "IV":
|
||||
d["A"] = num(us.get("w_model")); d["B"] = num(us.get("b_sched"))
|
||||
mm_ = re.search(r"laps?\s*\d+\s*x\s*(\d+)", us.get("src", ""))
|
||||
d["C"] = float(mm_.group(1)) if mm_ else None
|
||||
elif shape == "X":
|
||||
d["A"] = num(us.get("a_sched")); d["B"] = num(us.get("w_model"))
|
||||
mm_ = re.search(r"laps?\s*\d+\s*x\s*(\d+)", us.get("src", ""))
|
||||
d["C"] = float(mm_.group(1)) if mm_ else None
|
||||
elif shape == "XIII":
|
||||
seq = corner_seq(g)[0]
|
||||
d["A"] = seq[0]; d["B"] = seq[1]
|
||||
elif shape == "XIV":
|
||||
ls = [s for s in segs_of(g) if is_line(s)]
|
||||
arcs = [s for s in segs_of(g) if type(s).__name__ == "ArcCurve"]
|
||||
d["A"] = max(nrm(sub(V(s.PointAtEnd), V(s.PointAtStart))) for s in ls)
|
||||
d["C"] = min(nrm(sub(V(s.PointAtEnd), V(s.PointAtStart))) for s in ls)
|
||||
d["B"] = arc_len(arcs[0]); d["R"] = arcs[0].Arc.Radius
|
||||
elif shape in ("V", "IX"):
|
||||
s = segs_of(g)[0]
|
||||
d["A"] = arc_len(s); d["B"] = num(us.get("coupler_length")); d["R"] = s.Arc.Radius
|
||||
elif shape == "VI":
|
||||
segs = segs_of(g)
|
||||
main = max((s for s in segs if type(s).__name__ == "ArcCurve"), key=lambda s: s.Arc.Radius)
|
||||
ls = [s for s in segs if is_line(s)]
|
||||
d["R"] = main.Arc.Radius
|
||||
# A·B 는 갈고리(약 142° 굽힘) 각진 치수 정의가 도면에 없어 기하로 확정할 수 없음 → 미추출(None)
|
||||
# (접선 연장 방식으로 재면 A 2.837 / B 212 로 도면 2.818 / 200 과 다르고 합 A+2B 도 모델 L 과 안 맞음)
|
||||
elif shape == "XI":
|
||||
seq = corner_seq(g)[0]
|
||||
d["A"] = seq[1]; d["B"] = seq[0]
|
||||
d["C"] = num(us.get("design_length")) - seq[0] - seq[1] # 상단 180° 연장(미작도, 길이만 반영)
|
||||
return d
|
||||
|
||||
bars = collections.defaultdict(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())
|
||||
bars[us.get("mark")].setdefault(us.get("occurrence"), {}).update(us=us, name=nm, layer=L)
|
||||
elif L.endswith("중심선"):
|
||||
p = nm.split("-")
|
||||
if len(p) >= 4 and p[1] == "RC":
|
||||
b = bars[p[2]].setdefault("-".join(p[3:]), {})
|
||||
b["g"] = g
|
||||
|
||||
mod = {}
|
||||
for mk, dd in bars.items():
|
||||
if mk is None:
|
||||
continue
|
||||
items = [b for b in dd.values() if "us" in b and "g" in b]
|
||||
if not items:
|
||||
continue
|
||||
shape = items[0]["us"].get("shape")
|
||||
Ls = []; dims = collections.defaultdict(list); cl = 0.0
|
||||
for b in items:
|
||||
us = b["us"]
|
||||
Ls.append(num(us.get("design_length")) + num(us.get("coupler_length")))
|
||||
cl += clen(b["g"])
|
||||
for k, v in bar_dims(shape, us, b["g"]).items():
|
||||
if v is not None:
|
||||
dims[k].append(v)
|
||||
half = mk in ("P3", "P4")
|
||||
n = len(items) // 2 if half else len(items)
|
||||
mod[mk] = dict(shape=shape, dia=items[0]["us"].get("dia"), n=n, pieces=len(items), tot=sum(Ls) / 1000.0,
|
||||
L=sum(Ls) / n, Lmin=min(Ls), Lmax=max(Ls), dims=dims, cl=cl / 1000.0, items=items, half=half)
|
||||
|
||||
# =============================================================== 3. 표기·비교 도구
|
||||
def fmt_one(v, key=None):
|
||||
if key in ("X1", "X2"):
|
||||
return "%d°" % round(v)
|
||||
return "%.3f" % (v / 1000.0) if v >= 1000 else "%d" % round(v)
|
||||
|
||||
def agg(vals, key):
|
||||
"""철근별 값 목록 → (표기문자열, 대표 리스트[큰→작은])"""
|
||||
if not vals:
|
||||
return "", None
|
||||
if key in ("X1", "X2"):
|
||||
vals = [round(v) for v in vals]
|
||||
lo, hi = min(vals), max(vals)
|
||||
if hi - lo <= 0.6:
|
||||
return fmt_one(vals[0], key), [vals[0]]
|
||||
if key == "A" or key == "B" or key == "C":
|
||||
# 도면은 '큰값~작은값'
|
||||
return fmt_one(hi, key) + "~" + fmt_one(lo, key), [hi, lo]
|
||||
return fmt_one(hi, key) + "~" + fmt_one(lo, key), [hi, lo]
|
||||
|
||||
def same(dtxt, mvals, key):
|
||||
"""도면 표기(dtxt) 와 모델 값 일치 여부. 반환: 'same' | 'diff' | 'drawing_blank' | 'model_blank' | 'both_blank'"""
|
||||
dv = mm(dtxt) if dtxt else None
|
||||
if dv is None and mvals is None:
|
||||
return "both_blank"
|
||||
if dv is None:
|
||||
return "drawing_blank"
|
||||
if mvals is None:
|
||||
return "model_blank"
|
||||
if key in ("X1", "X2"):
|
||||
return "same" if [round(x) for x in dv] == [round(x) for x in mvals] else "diff"
|
||||
if len(dv) != len(mvals):
|
||||
return "diff"
|
||||
return "same" if all(abs(a - b) <= TOL for a, b in zip(dv, mvals)) else "diff"
|
||||
|
||||
DIMK = ["A", "B", "C", "D", "E", "R", "X1", "X2", "J"]
|
||||
|
||||
def model_cells(mk):
|
||||
"""도면 표와 같은 칸 구성으로 모델 값 문자열/리스트 생성"""
|
||||
m = mod.get(mk)
|
||||
cells = {}; vals = {}
|
||||
if not m:
|
||||
return cells, vals
|
||||
for k in DIMK:
|
||||
t, v = agg(m["dims"].get(k, []), k)
|
||||
cells[k] = t; vals[k] = v
|
||||
return cells, vals
|
||||
|
||||
# =============================================================== 4. 비고 (수동 코드 + 자동 차이)
|
||||
NOTE = {}
|
||||
KDS = {}
|
||||
for mk in ("C1", "C4"):
|
||||
NOTE[mk] = "[D-8] 도면 내부: 재료표 다리각 98°/96° ↔ 정면도 96.4°."
|
||||
for mk in ("C2", "C5"):
|
||||
NOTE[mk] = "[D-7] 도면 내부: 재료표 L 12.081 ↔ 치수 합 A+B+C 12.082 (모델은 합계 12.082). [D-8] 다리각 98°/96° ↔ 정면도 96.4°."
|
||||
for mk in ("C3", "C6"):
|
||||
NOTE[mk] = "[D-8] 도면 내부: 재료표 다리각 98°/96° ↔ 정면도 96.4°."
|
||||
for mk in ("C7", "C8"):
|
||||
NOTE[mk] = "[D-9] 도면 내부: 재료표 B·D·R·X·J 빈칸 → 모델은 정면도 R 12,100(하면 R 12,000 동심)·평면부 EL 40.511 로 작성. [D-10] 다리 650 정면도 겹침으로 확인 불가."
|
||||
NOTE["C9"] = "[D-4] 스터럽 폭 도면 재료표 1.407 ↔ 단면 작도 1.160/1.740/1.160 (모델은 작도값). [D-6] 재료표 L=(최대+최소)/2 근사 → 모델은 철근마다 개별 변길이. [M-5] 길이 산식 +54.3 규칙은 변하는 치수에만 적용."
|
||||
NOTE["C10"] = "[D-4] 스터럽 폭 1.407 ↔ 작도 1.160/1.740 (모델은 작도값). [M-5] +54.3 규칙."
|
||||
NOTE["C11"] = "[D-4] 스터럽 폭 1.407 ↔ 작도 1.160/1.740/1.160. [D-6] (최대+최소)/2 근사 vs 모델 개별 변길이. [M-5] +54.3 규칙."
|
||||
NOTE["C12"] = "[D-5] 수평폐합근 폭 1.407 ↔ 작도 1.106/1.686/1.106 (모델은 작도값). [D-6] 길이 (최대+최소)/2 근사 vs 모델 개별. [M-5] +54.3 규칙."
|
||||
NOTE["C13"] = "[D-5] 수평폐합근 폭 1.407 ↔ 작도 1.106/1.686/1.106. [D-6] (최대+최소)/2 근사 vs 모델 개별. [M-5] +54.3 규칙."
|
||||
NOTE["C14"] = "[M-1] 모델 최하단 점 2개(x=±1913, ±2913)가 도면보다 350 mm 높음(근거 기록 없음). [M-2] 훅 방향 도면 표기 없음 — 모델은 아래로 가정, 도면 점 위치로는 하부 점은 위·상부 점은 아래일 가능성(추정, 간접 확인)."
|
||||
NOTE["C16"] = "훅 방향·정면도 좌측 끝은 도면 직접 표기 없음 (재료표 L 5.611 = 5.261 + 0.350 한쪽 훅으로 간접 확인)."
|
||||
for mk in ("S1", "S2"):
|
||||
NOTE[mk] = "[M-6] 높이 기준: 도면(D2 전열 상세)은 대좌 상면 -50 ↔ 모델은 코핑 상면 -50 (139~164 mm 낮음). [M-7] 층 순서: 도면은 S2 점이 S1 선 위 ↔ 모델은 S1 위·S2 50 mm 아래(모델 src '추론')."
|
||||
NOTE["UA1"] = "[M-13] 모델에 없음(0본). 도면은 기초 조립용 '배치간격 예시도'(1.0×1.2 예시)라 수량 73본 근거 미확인."
|
||||
NOTE["P1"] = "[D-20] 주철근 상단: 정면도 EL 43.011 ↔ 재료표 EL 43.061 (모델은 재료표). [D-21] 반지름 치수·선 875 ↔ 점 870.5 (모델은 점)."
|
||||
NOTE["P2"] = "[D-20] 주철근 상단: 정면도 EL 40.411 ↔ 재료표 EL 43.061 (코핑 정착 2.65 m 차이, 모델은 재료표). [D-21] 반지름 830 ↔ 점 825.5 (모델은 점)."
|
||||
NOTE["P3"] = "모델은 반원 2조각(72개)=1링 → 개수는 링 수(36)로 환산. A·B 는 모델 갈고리(굽힘 142°·R42.75)의 각진 치수 정의가 도면에 없어 기하로 추출하지 못함(미추출) — 길이 L 6.436·R 897·개수는 일치. [M-12] 단마다 90° 엇갈린 이음 각도는 도면에 기준 없음(모델 가정, 간접)."
|
||||
NOTE["P4"] = "모델은 반원 2조각(18개)=1링(커플러 65 포함 L 5.636) → 개수 9 로 환산. [D-23] 라벨 H19 @100 ↔ 치수 8@120=960 (모델 @120 — 구조계산서 확인 사항, E-01). [M-12] 커플러 각도 도면에 없음."
|
||||
NOTE["P5"] = "[D-23] 라벨 @100 ↔ 치수 22@120=2,640 (모델 @120, 구조계산서 확인). [D-22] 링 점 902 ↔ R 897. [M-12] 커플러 각도 도면에 없음."
|
||||
NOTE["P6"] = "정면도 선 반길이 875 ↔ 모델 직선 894 (한쪽 19 mm, 경미)."
|
||||
NOTE["B1"] = "[M-8] 모델 교차 철근 간격 54~56 mm ↔ 도면 두 뷰 모두 27~28 mm(점 값을 서로 다른 뷰에서 가져옴: B1 2073 ↔ B5 2127). [D-15] 같은 철근을 점/선으로 그린 높이가 정면도·측면도에서 27~28 mm 다름."
|
||||
NOTE["B2"] = "[M-9] 다리 끝 +2072 (도면 선 기준 +2100). [D-15] 점/선 높이 27~28 mm 차."
|
||||
NOTE["B3"] = "[D-17] 도면 중앙 치수 33@250+450(36본) ↔ 점·재료표 38본 (모델 38)."
|
||||
NOTE["B4"] = "[M-8] 하면 교차 철근 간격 영향(B2·B4)."
|
||||
NOTE["B5"] = "[M-9] 모델 중심 73 아래(순피복 58.5 mm, 도면 표기 상면 100, 선행 E-06). [M-8] 교차 철근 간격."
|
||||
NOTE["B6"] = "[M-9] 다리 끝 +2128 (도면 선 기준 +2100, 선행 E-06 피복 57.5). [M-8] 교차 철근 간격."
|
||||
NOTE["B7"] = "[M-8] 교차 철근 간격."
|
||||
NOTE["B9"] = "[M-01 선행] 도면 개수 빈칸. 라벨 H16 @500 + 격자 34×29(@250) 체크무늬 986/2 = 493본을 총길이 1,062.415 ÷ L 2.155 로 역산(간접 확인) → 모델 493본. 상단 180° 연장 C=64 는 모델에 직선으로 작도되지 않고 길이만 반영."
|
||||
NOTE["B10"] = "[D-18] 치수 9@200(10단) ↔ 점·재료표 9단 (모델 9단), 표 9.198×7.644 ↔ 점 ±4593/±3818."
|
||||
NOTE["B11"] = "B11·B12 4단 높이(z 550)와 위치 일치."
|
||||
NOTE["B12"] = "[D-16] 수량 34본(재료표) ↔ 모델 28본. 도면이 간소화되어 작도에 일부만 남아 있어 모델 작성 시 도면을 정확히 인식하지 못함. 정면도 간격 1000 한 줄, 단면도 400 위치에서 엇갈려 1000 간격으로 배치되는 철근이라 재료표 34본이 맞는 것으로 보임 → 설계자의 판단(확인)이 필요한 부분."
|
||||
for mk in ("C1", "C2", "C3", "C4", "C5", "C6"):
|
||||
KDS[mk] = "[M-4] 내면반지름 여유 거의 없음: 모델 내면지름 101.5 mm ↔ 3.5db 100.1/101.5 mm (H29, 표 4.5-1). 도면에 내면반지름 표기 없음."
|
||||
for mk in ("C7", "C8"):
|
||||
KDS[mk] = "[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 부족)."
|
||||
for mk in ("B1", "B2"):
|
||||
KDS[mk] = "[M-10] S25 굽힘: 모델 내면지름 87.6 mm < 공칭 기준 88.9 mm (1.3 mm 부족, 3.5db)."
|
||||
for mk in ("B3", "B7", "B10"):
|
||||
KDS[mk] = "[M-10] S19 굽힘: 모델 내면지름 66.6 mm < 공칭 기준 66.9 mm (0.3 mm 부족, 3.5db)."
|
||||
|
||||
# =============================================================== 5. 표 행 만들기
|
||||
HEAD = ["번 호", "형 식", "직 경", "개 수", "A", "B", "C", "D", "E", "R", "X1(º)", "X2(º)", "J", "길 이(M)", "총길이(M)", "단위중량(KG/M)", "총중량(TON)", "총중량(할증)(TON)"]
|
||||
KEYS = ["num", "shape", "dia", "n", "A", "B", "C", "D", "E", "R", "X1", "X2", "J", "L", "tot", "unit", "w", "wh"]
|
||||
|
||||
def surch(d): return 1.06 if int(d) >= 22 else 1.03
|
||||
|
||||
def build_model_table():
|
||||
"""도면 행 순서 그대로 모델 행 생성. 반환 {tab: [row dict]} (cells=표시 문자열, vals=비교용)"""
|
||||
out = {}
|
||||
for tab in ("SD400", "SD500"):
|
||||
rows = []; grp = [] # 직전 소계 이후 막대 행들
|
||||
last = {"shape": None, "dia": None}
|
||||
for r in drow[tab]:
|
||||
if r["kind"] == "bar":
|
||||
mk = r["mark"]; m = mod.get(mk)
|
||||
cells = {"num": mk}; vals = {}
|
||||
dcells = r["cells"]
|
||||
shp = m["shape"] if m else r["cells"]["shape_v"]
|
||||
dia = m["dia"] if m else r["cells"]["dia_v"]
|
||||
cells["shape"] = '"' if shp == last["shape"] else shp
|
||||
cells["dia"] = '"' if dia == last["dia"] else dia
|
||||
last["shape"], last["dia"] = shp, dia
|
||||
if m:
|
||||
cells["n"] = str(m["n"]); vals["n"] = m["n"]
|
||||
c, v = model_cells(mk)
|
||||
for k in DIMK:
|
||||
cells[k] = c.get(k, ""); vals[k] = v.get(k)
|
||||
cells["L"] = "%.3f" % (m["L"] / 1000.0); vals["L"] = m["L"]
|
||||
cells["tot"] = "%.3f" % m["tot"]; vals["tot"] = m["tot"]
|
||||
else:
|
||||
cells["n"] = "0"; vals["n"] = 0
|
||||
cells["L"] = ""; cells["tot"] = "0.000"; vals["tot"] = 0.0
|
||||
for k in DIMK:
|
||||
cells[k] = ""; vals[k] = None
|
||||
rows.append({"kind": "bar", "mark": mk, "cells": cells, "vals": vals, "model": m, "dia_n": re.sub(r"\D", "", dia or ""), "dia": dia})
|
||||
grp.append(rows[-1])
|
||||
else:
|
||||
lab = r["label"]; cells = {"num": lab}
|
||||
dc = r["cells"]
|
||||
if "소" in lab and "총" not in lab:
|
||||
tl = sum(b["vals"]["tot"] for b in grp)
|
||||
dn = grp[0]["dia_n"]
|
||||
u = UNIT[dn]
|
||||
w = tl * u / 1000.0; wh = w * surch(dn)
|
||||
pc = "(%d%%)" % round((surch(dn) - 1) * 100)
|
||||
cells.update(tot="%.3f" % tl, unit="%.3f" % u, w="%.3f" % w, wh="%.3f%s" % (wh, pc))
|
||||
rows.append({"kind": "sum", "label": lab, "cells": cells, "vals": {"tot": tl, "w": round(w, 3), "wh": round(wh, 3)}, "dia_n": dn, "grp": list(grp)})
|
||||
grp = []
|
||||
elif "겹이음" in lab:
|
||||
cells.update(w="", wh="")
|
||||
rows.append({"kind": "lap", "label": lab, "cells": cells, "vals": {}})
|
||||
else: # 총 계
|
||||
subs = [x for x in rows if x["kind"] == "sum"]
|
||||
tl = sum(s["vals"]["tot"] for s in subs)
|
||||
w = sum(s["vals"]["w"] for s in subs); wh = sum(s["vals"]["wh"] for s in subs)
|
||||
cells["w"] = "%.3f" % w; cells["wh"] = "%.3f" % wh
|
||||
if dc.get("tot"):
|
||||
cells["tot"] = "%.3f" % tl
|
||||
rows.append({"kind": "tot", "label": lab, "cells": cells, "vals": {"tot": tl, "w": w, "wh": wh}})
|
||||
out[tab] = rows
|
||||
return out
|
||||
MT = build_model_table()
|
||||
|
||||
# =============================================================== 6. 대조
|
||||
def compare_row(dr, mr):
|
||||
"""막대 행 비교 → {col: 'same'|'diff'|'round'|...}, 자동 비고 목록"""
|
||||
res = {}; auto = []
|
||||
mk = dr["mark"]; dc = dr["cells"]; mv = mr["vals"]; m = mr["model"]
|
||||
# 개수
|
||||
dn = int(dc["n"]) if dc["n"].isdigit() else None
|
||||
if dn is None:
|
||||
res["n"] = "drawing_blank"; auto.append("개수: 도면 빈칸 → 모델 %d" % mv["n"])
|
||||
elif dn != mv["n"]:
|
||||
res["n"] = "diff"; auto.append("개수: 도면 %d → 모델 %d (%+d)" % (dn, mv["n"], mv["n"] - dn))
|
||||
else:
|
||||
res["n"] = "same"
|
||||
# 형식·직경
|
||||
for k, mkey in (("shape", "shape"), ("dia", "dia")):
|
||||
dv = dc[k + "_v"]; mvv = m[mkey] if m else None
|
||||
res[k] = "same" if (mvv is None or dv == mvv) else "diff"
|
||||
if mvv is not None and dv != mvv:
|
||||
auto.append("%s: 도면 %s → 모델 %s" % ("형식" if k == "shape" else "직경", dv, mvv))
|
||||
# 치수
|
||||
for k in DIMK:
|
||||
r_ = same(dc.get(k, ""), mv.get(k), k)
|
||||
res[k] = r_
|
||||
if r_ == "diff":
|
||||
auto.append("%s: 도면 %s → 모델 %s" % (k, dc.get(k), mr["cells"][k]))
|
||||
elif r_ == "drawing_blank":
|
||||
auto.append("%s: 도면 빈칸 → 모델 %s" % (k, mr["cells"][k]))
|
||||
elif r_ == "model_blank":
|
||||
auto.append("%s: 도면 %s → 모델 미추출" % (k, dc.get(k)))
|
||||
# 길이
|
||||
dL = mm(dc["L"]); dL = dL[0] if dL else None
|
||||
if m and dL is not None:
|
||||
d = m["L"] - dL
|
||||
if abs(d) <= TOL:
|
||||
res["L"] = "same" if abs(d) < 0.5 else "round"
|
||||
else:
|
||||
res["L"] = "diff"; auto.append("길이 L: 도면 %.3f → 모델 %.3f m (%+.3f)" % (dL / 1000, m["L"] / 1000, d / 1000))
|
||||
else:
|
||||
res["L"] = "same" if dL is None and not m else "diff"
|
||||
# 총길이
|
||||
dT = float(dc["tot"]) if dc["tot"] else None
|
||||
mT = mv["tot"]
|
||||
if dT is not None:
|
||||
d = mT - dT
|
||||
tolt = max(dn or 0, mv["n"]) * TOL / 1000.0
|
||||
if abs(d) <= 1e-9 + 0.0005:
|
||||
res["tot"] = "same"
|
||||
elif abs(d) <= tolt and res["L"] in ("same", "round"):
|
||||
res["tot"] = "round"
|
||||
else:
|
||||
res["tot"] = "diff"; auto.append("총길이: 도면 %.3f → 모델 %.3f m (%+.3f)" % (dT, mT, d))
|
||||
else:
|
||||
res["tot"] = "same"
|
||||
return res, auto
|
||||
|
||||
# =============================================================== 7. 엑셀
|
||||
thin = Side(style="thin", color="999999")
|
||||
BR = Border(left=thin, right=thin, top=thin, bottom=thin)
|
||||
HF = PatternFill("solid", fgColor="D9D9D9")
|
||||
GREY = PatternFill("solid", fgColor="EDEDED")
|
||||
ORANGE = PatternFill("solid", fgColor="F8CBAD")
|
||||
BLUE = PatternFill("solid", fgColor="DDEBF7")
|
||||
YEL = PatternFill("solid", fgColor="FFF2CC")
|
||||
GREEN = PatternFill("solid", fgColor="E2F0D9")
|
||||
FILL = {"diff": ORANGE, "drawing_blank": YEL, "model_blank": YEL, "round": BLUE}
|
||||
|
||||
wb = Workbook()
|
||||
|
||||
def write_table(ws, title, tables, extra=None, cmp_mode=False):
|
||||
ws.append([title])
|
||||
ws.cell(row=1, column=1).font = Font(bold=True, size=12)
|
||||
ws.append([])
|
||||
heads = HEAD + (["판정", "비고 (자동: 칸별 차이)", "비고 (도면 내부·도면↔모델 코드)", "비고 3 (KDS 24 14 21 기준 상이)"] if cmp_mode else [])
|
||||
hr = ws.max_row + 1
|
||||
for tab_title, rows in tables:
|
||||
ws.append([tab_title]); ws.cell(row=ws.max_row, column=1).font = Font(bold=True)
|
||||
ws.append(heads); hr = ws.max_row
|
||||
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)
|
||||
for rr in rows:
|
||||
ws.append(rr["line"])
|
||||
r_ = ws.max_row
|
||||
for c in range(1, len(heads) + 1):
|
||||
cell = ws.cell(row=r_, column=c)
|
||||
cell.border = BR
|
||||
cell.alignment = Alignment(vertical="top", wrap_text=(c > 18), horizontal=("center" if c <= 18 else "left"))
|
||||
for col, fill in rr.get("fills", {}).items():
|
||||
ws.cell(row=r_, column=col).fill = fill
|
||||
if rr.get("bold"):
|
||||
for c in range(1, len(heads) + 1):
|
||||
ws.cell(row=r_, column=c).font = Font(bold=True)
|
||||
if c not in rr.get("fills", {}):
|
||||
ws.cell(row=r_, column=c).fill = GREY
|
||||
if cmp_mode:
|
||||
ws.cell(row=r_, column=21).alignment = Alignment(vertical="top", wrap_text=True)
|
||||
ws.cell(row=r_, column=22).font = Font(color="FF00FF")
|
||||
ws.cell(row=r_, column=22).alignment = Alignment(vertical="top", wrap_text=True)
|
||||
ws.append([])
|
||||
widths = [10, 7, 7, 7, 10, 10, 10, 7, 7, 8, 8, 8, 6, 10, 11, 12, 11, 13]
|
||||
if cmp_mode:
|
||||
widths += [11, 60, 70, 52]
|
||||
for i, w in enumerate(widths, 1):
|
||||
ws.column_dimensions[get_column_letter(i)].width = w
|
||||
ws.freeze_panes = "B4"
|
||||
|
||||
def line_of(cells):
|
||||
return [cells.get(k, "") for k in KEYS]
|
||||
|
||||
# ---- ① 도면 재료표 (재현) : 도면에 인쇄된 문자열 그대로
|
||||
w1 = wb.active; w1.title = "①도면 재료표(재현)"
|
||||
tabs1 = []
|
||||
for tab, ttl in (("SD400", "철 근 재 료 표 (SD400) — 3.dxf 원본 값 그대로"), ("SD500", "철 근 재 료 표 (SD500) — 3.dxf 원본 값 그대로")):
|
||||
rows = []
|
||||
for r in drow[tab]:
|
||||
if r["kind"] == "bar":
|
||||
c = dict(r["cells"]); c["num"] = r["mark"]
|
||||
rows.append({"line": line_of(c)})
|
||||
else:
|
||||
c = dict(r["cells"]); c["num"] = r["label"]
|
||||
rows.append({"line": line_of(c), "bold": True})
|
||||
tabs1.append((ttl, rows))
|
||||
write_table(w1, "① 도면 재료표 재현 (운곡교 P1 완도방향, 도면 C0050205-027 3.dxf)", tabs1)
|
||||
|
||||
# ---- ② 모델 재료표
|
||||
w2 = wb.create_sheet("②모델 재료표")
|
||||
tabs2 = []
|
||||
for tab, ttl in (("SD400", "철 근 재 료 표 (SD400) — 모델(운곡교p1.3dm) 기준"), ("SD500", "철 근 재 료 표 (SD500) — 모델 기준")):
|
||||
rows = []
|
||||
for r in MT[tab]:
|
||||
c = dict(r["cells"])
|
||||
if r["kind"] == "bar":
|
||||
rows.append({"line": line_of(c)})
|
||||
else:
|
||||
c["num"] = r["label"]
|
||||
rows.append({"line": line_of(c), "bold": True})
|
||||
tabs2.append((ttl, rows))
|
||||
write_table(w2, "② 모델 재료표 (모델 철근에서 추출: 개수·형상·치수 A~J·길이·총길이, 중량은 도면과 같은 단위중량·할증 적용)", tabs2)
|
||||
|
||||
# ---- ③ 대조·비고
|
||||
w3 = wb.create_sheet("③대조·비고")
|
||||
tabs3 = []
|
||||
EXPORT = {"SD400": [], "SD500": []} # DXF 작성용 (make_bom_dxf.py 가 읽음)
|
||||
stat = [] # 요약용
|
||||
for tab, ttl in (("SD400", "SD400 — 모델 값 표시, 도면과 다른 칸은 색 + (도면: 값)"), ("SD500", "SD500 — 모델 값 표시, 도면과 다른 칸은 색 + (도면: 값)")):
|
||||
rows = []
|
||||
di = 0
|
||||
for r in drow[tab]:
|
||||
mr = MT[tab][di]; di += 1
|
||||
if r["kind"] == "bar":
|
||||
res, auto = compare_row(r, mr)
|
||||
cells = dict(mr["cells"])
|
||||
fills = {}
|
||||
line = []
|
||||
for ci, k in enumerate(KEYS, 1):
|
||||
txt = cells.get(k, "")
|
||||
if (k in res and res[k] not in ("same", "both_blank") and k not in ("shape", "dia")) or (k in ("shape", "dia") and res.get(k) == "diff"):
|
||||
dt = r["cells"].get(k + "_v", r["cells"].get(k, "")) if k in ("shape", "dia") else r["cells"].get(k, "")
|
||||
txt = "%s\n(도면: %s)" % (txt if txt != "" else "-", dt if dt != "" else "빈칸")
|
||||
fills[ci] = FILL.get(res[k], ORANGE)
|
||||
line.append(txt)
|
||||
kind = [v for v in res.values()]
|
||||
if "diff" in kind:
|
||||
verdict = "차이"
|
||||
elif "drawing_blank" in kind or "model_blank" in kind:
|
||||
verdict = "도면 빈칸/모델 미추출"
|
||||
elif "round" in kind:
|
||||
verdict = "일치(반올림)"
|
||||
else:
|
||||
verdict = "일치"
|
||||
fills[19] = {"차이": ORANGE, "도면 빈칸/모델 미추출": YEL, "일치(반올림)": BLUE, "일치": GREEN}[verdict]
|
||||
line += [verdict, "\n".join(auto), NOTE.get(r["mark"], ""), KDS.get(r["mark"], "")]
|
||||
rows.append({"line": line, "fills": fills})
|
||||
EXPORT[tab].append({"kind": "bar", "mark": r["mark"], "d": {k: r["cells"].get(k, "") for k in KEYS},
|
||||
"m": {k: mr["cells"].get(k, "") for k in KEYS}, "res": res, "verdict": verdict,
|
||||
"auto": auto, "note": NOTE.get(r["mark"], ""), "kds": KDS.get(r["mark"], "")})
|
||||
stat.append((r["mark"], verdict, auto))
|
||||
else:
|
||||
cells = dict(mr["cells"]); cells["num"] = r["label"]
|
||||
fills = {}
|
||||
line = []
|
||||
for ci, k in enumerate(KEYS, 1):
|
||||
txt = cells.get(k, ""); dt = r["cells"].get(k, "")
|
||||
if k in ("tot", "w", "wh") and dt and txt:
|
||||
mt_ = re.match(r"[\d.]+", txt); md_ = re.match(r"[\d.]+", dt)
|
||||
if mt_ and md_ and abs(float(mt_.group()) - float(md_.group())) > 0.0015:
|
||||
txt = "%s\n(도면: %s)" % (txt, dt); fills[ci] = ORANGE
|
||||
line.append(txt)
|
||||
extra = ""
|
||||
if mr["kind"] == "lap":
|
||||
extra = "도면 값(%s / %s)만 있음. 겹이음 할증 산식이 도면에 없어 모델은 산출하지 않음." % (r["cells"].get("w", ""), r["cells"].get("wh", ""))
|
||||
line[KEYS.index("w")] = "(도면: %s)" % r["cells"].get("w", "")
|
||||
line[KEYS.index("wh")] = "(도면: %s)" % r["cells"].get("wh", "")
|
||||
line += ["", extra, "", ""]
|
||||
rows.append({"line": line, "fills": fills, "bold": True})
|
||||
sres = {KEYS[ci - 1]: "diff" for ci in fills}
|
||||
EXPORT[tab].append({"kind": mr["kind"], "label": r["label"], "d": {k: r["cells"].get(k, "") for k in KEYS},
|
||||
"m": {k: mr["cells"].get(k, "") for k in KEYS}, "res": sres, "verdict": "", "auto": [],
|
||||
"note": extra, "kds": ""})
|
||||
tabs3.append((ttl, rows))
|
||||
write_table(w3, "③ 대조·비고 — 같은 양식(도면 표 순서)으로, 모델 값 위에 도면과 다른 칸을 표시", tabs3, cmp_mode=True)
|
||||
for row in w3.iter_rows(min_row=4):
|
||||
lines = 1
|
||||
for c in row:
|
||||
if c.value is None:
|
||||
continue
|
||||
s = str(c.value)
|
||||
wd = w3.column_dimensions[get_column_letter(c.column)].width or 10
|
||||
n = sum(max(1, int(len(p) * (1.7 if c.column > 19 else 1.0) / wd) + 1) if c.column > 19 else 1 for p in s.split("\n")) if c.column > 19 else len(s.split("\n"))
|
||||
lines = max(lines, n)
|
||||
w3.row_dimensions[row[0].row].height = max(15, 14 * lines)
|
||||
w3.insert_rows(2)
|
||||
w3.cell(row=2, column=1, value="색: 주황=도면과 다름 · 파랑=반올림 범위(≤1 mm/본) · 노랑=도면 빈칸/모델 미추출 · 판정 초록=일치 · 마젠타 글자=KDS 24 14 21 기준 상이. 코드 [M-n]=도면↔모델, [D-n]=도면 내부(검토기록_최종.md).")
|
||||
w3.cell(row=2, column=1).font = Font(italic=True)
|
||||
|
||||
# ---- ④ 요약
|
||||
w4 = wb.create_sheet("④요약")
|
||||
w4.append(["도면 재료표 ↔ 모델 재료표 차이 요약 (2026-10-16, 검토 미확정)"])
|
||||
w4.cell(row=1, column=1).font = Font(bold=True, size=12)
|
||||
w4.append([])
|
||||
w4.append(["마크", "판정", "차이 내용(자동 추출)"])
|
||||
for c in range(1, 4):
|
||||
w4.cell(row=3, column=c).font = Font(bold=True); w4.cell(row=3, column=c).fill = HF
|
||||
for mk, v, auto in stat:
|
||||
if v != "일치":
|
||||
w4.append([mk, v, " / ".join(auto)])
|
||||
w4.append([])
|
||||
cnt = collections.Counter(v for _, v, _ in stat)
|
||||
w4.append(["판정 집계: " + ", ".join("%s %d" % kv for kv in sorted(cnt.items()))])
|
||||
for i, w in enumerate([10, 22, 150], 1):
|
||||
w4.column_dimensions[get_column_letter(i)].width = w
|
||||
|
||||
# ---- ⑤ 코드 설명
|
||||
w5 = wb.create_sheet("⑤비고코드설명")
|
||||
w5.append(["코드", "구분", "내용"])
|
||||
for c in [
|
||||
("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", "선행보고서", "기둥 띠철근 간격 → 구조계산서 확인 사항")]:
|
||||
w5.append(list(c))
|
||||
for i, w in enumerate([12, 14, 90], 1):
|
||||
w5.column_dimensions[get_column_letter(i)].width = w
|
||||
for c in range(1, 4):
|
||||
w5.cell(row=1, column=c).font = Font(bold=True); w5.cell(row=1, column=c).fill = HF
|
||||
|
||||
# ---- ⑥ 개별 철근
|
||||
w6 = wb.create_sheet("⑥모델_개별철근")
|
||||
w6.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 b in sorted(mod[mk]["items"], key=lambda b: b["us"].get("occurrence", "")):
|
||||
us = b["us"]
|
||||
w6.append([mk, us.get("dia"), us.get("shape"), us.get("occurrence"), round(num(us.get("design_length")) + num(us.get("coupler_length")), 3),
|
||||
num(us.get("coupler_length")), round(clen(b["g"], 800), 1), us.get("host"), us.get("src")])
|
||||
for c in range(1, 10):
|
||||
w6.cell(row=1, column=c).font = Font(bold=True); w6.cell(row=1, column=c).fill = HF
|
||||
for i, w in enumerate([8, 7, 7, 18, 20, 11, 18, 10, 70], 1):
|
||||
w6.column_dimensions[get_column_letter(i)].width = w
|
||||
w6.freeze_panes = "A2"
|
||||
|
||||
wb.move_sheet("④요약", offset=-3)
|
||||
import json, os
|
||||
jp = os.path.join(os.path.dirname(OUT), "추출데이터", "재료표_대조결과.json")
|
||||
json.dump(EXPORT, open(jp, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
|
||||
try:
|
||||
wb.save(OUT)
|
||||
except PermissionError:
|
||||
print("엑셀 파일이 열려 있어 저장하지 못함(JSON 만 갱신):", OUT)
|
||||
print("saved", OUT)
|
||||
print("판정:", dict(cnt))
|
||||
for mk, v, auto in stat:
|
||||
if v != "일치":
|
||||
print(mk, v, " | ".join(auto))
|
||||
Reference in new issue
Block a user