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:
ahnyongjooandClaude Sonnet 5.5 committed 2026-10-28 11:20:18 +09:00
commit 964a841de1
3978 files changed
+58013356

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

@@ -0,0 +1,60 @@
# 산외교(울산) P1 철근 도면↔모델 검토 기록
작성 2026-10-19 · 상태: **1차(재료표 대조 + 모델 표식) 완료, 검토 미확정** · 기준: KDS 24 14 21 · 범위: 모델↔도면 (도면 내부 불일치는 `도면내부_오류_보관.md` 에 따로 보관)
## 1. 입력·산출 파일
- 도면: `교각배근도(울산P1)_2016.10.28.dwg` → **`.dxf` 변환 완료**(라이노로 변환, 선 13,490·삽입 2,072·글자 856), `교각일반도P1.dxf`(기존)
- 선행 보고서: `보고서_산외교울산P1교각모델링검토_2026-10-19_v0.1.00.pdf` (G1~G5 전부 PASS, 도면 오류·불일치 "자동으로 찾은 항목 없음", 재료표 전 마크 본수 일치)
- 모델: `산외교P1 G5.3dm`(원본, 그대로) / **`산외교P1 G5_대조코드표식.3dm`**(표식 추가본, 현재 라이노에 열려 있음)
- 대조표: `철근재료표_도면_모델_대조.xlsx` (시트 ④요약 ①도면 재료표 ②모델 재료표 ③대조·비고 ⑤비고코드설명 ⑥모델_개별철근)
- 스크립트 `스크립트\make_bom_compare_sw.py`, 추출 `추출데이터\도면_글자.json`, `추출데이터\재료표_대조결과.json`
## 2. 재료표 대조 결과 (35마크: 일치 14 · 일치(반올림) 2 · 차이 13 · 모델 미추출 5)
본수·직경·형식은 전 마크 일치. 다음은 **도면 재료표 ↔ 모델 형상**의 차이.
| 코드 | 구분 | 내용 | 표식 위치 |
|---|---|---|---|
| M-1 | 도면↔모델 | C1~C6·C14·C15 일부 철근이 재료표 단일값보다 짧음 (C1 A 8.462~9.259, C5 B 3.580~4.018, C14 A 2.633~3.000, C15 A 4.118~4.517) | C1 (y+1500) 중간 |
| M-2 | 도면↔모델 | J·C 길이가 재료표 L 에만 있고 모델 형상에는 없음 (C1~C4 J 1.560 · C5·C6 940 · C7~C9 C 660×2 · C10·C12·C13 660 · P2 380 · B2·B5 1.000 · B8 570). 모델 설계길이(user string)는 재료표 L 과 같고 중심선 형상이 그만큼 짧음 | C1 (y+1000) 끝 |
| M-3 | 도면↔모델 | C7~C13 치수 가산(+54/+44/+99) 산식 — 재료표는 대푯값·가산값, 모델은 철근마다 작도값 (C11·C12·C13 은 가산 규칙으로 일치 확인) | C12-L05 시작 |
| M-4 | 도면↔모델 | C10 재료표 A 최대 9.570 에 대응하는 철근이 모델에 없음 (모델 최대 8.687, 설계길이 합 −6.178 m) | C10-L07 중간 |
| M-5 | 도면↔모델 | B8 모델 정사각 7.345×7.345 ↔ 재료표 A 7.300·B 7.350 | B8 시작 |
| M-6 | 도면↔모델 | 도면 표 주철근 커플러 476개(H32)가 모델에 없음 — P1 은 연속 28.545 m (68본×7 로 추정) | P1-19 중간 |
| M-7 | 기준↔모델 | 굽힘 내면지름 H22(B2·B5·C7~C10·C15) 76.8 < 77.7, H19(P5) 66.5 < 66.85 (표 4.5-1 3.5db 에 0.9/0.35 mm 부족) | P5-L16-090 시작 |
| M-8 | 미확인 | P3·P4 커플러 각도 90°/270° 도면에 기준 없음 (모델 가정) | P3-L012b 시작 |
일치로 확인: P1 (R340·A 27.510·B 534·C 500), P3·P4 링 (R 1.149·커플러 55), C1~C6 각도(X1·X2 100/101°, C5·C6 104/156/156/103°), C12 호 628 · R 400, S1~S4, B1~B10 중 B8 외 치수, P5, C14·C15 대표값.
## 3. 아직 안 한 것 (운곡교 대비)
- 색 구분 도면 DXF(1_/2_/3_), 4자 분류 대조표(105항목형) 와 도면↔모델 캡처 이미지
- 정면도·단면도 좌표 기준 점검: 코핑·기초·기둥 철근 위치/높이/간격, 피복, 받침 위치 (재료표 외 항목 — 선행 보고서의 위치 확인은 "수량만 확인, 위치·좌표는 도면과 직접 대조" 로 표시됨)
- 표식은 재료표 대조에서 나온 8건만 있음.
## 4. 라이노 표식
레이어 `P1::검토표시_코드표::{주황_도면모델, 진한노랑_기준모델, 회색_미확인}` — 코드당 구슬(R150)+지시선+TextDot (`대조_M-n`, user string code/gu/content/marks/where). 원본 파일은 그대로.
## 5. V2 형식 엑셀 (2026-10-19 재작성)
`대조표_산외교P1_도면모델_분류_V2.xlsx` — 운곡교 V2 형식: 분류 안내 / ① 도면↔모델 불일치(M-1~M-8, 캡처 8장) / ② 전체 대조표 34마크 / ③ 미검토 / ④ 측정 근거. 이미지 `도면↔모델_불일치_시각자료\`. 먼저 만든 `철근재료표_도면_모델_대조.xlsx` 는 치수 상세 표로 그대로 둠. 도면 색구분 DXF 는 아직 안 만듦(옥성교 방식으로 가능).
## 6. 도면 쪽 위치 표기
`교각배근도(울산P1)_도면검토_색구분.dxf` — 원본 도면에 검토 레이어 3개 추가(`검토_도면모델_주황`, `검토_기준모델_진한노랑`, `검토_미확인_회색`): 재료표 행(코드 M-n 병기), 형상 도해 셀(II·III·IV·V·IX·X), a 산식 NOTE(M-3), 커플러 개수 표(M-6)를 사각형으로 표시, 범례는 재료표 위. 미리보기 `1_색구분_재료표·형상.png`.
## 7. 추가 항목 M-9 (2026-10-19) — 기초 철근 중심 높이
기초 철근 B1~B6 중심 높이가 도면 철근선(상면 100 아래 / 하면 150 위)과 ±22.5 mm 다름 (상면 B1·B5 77.5 / B2·B4 122.5, 하면 B2·B3 127.5 / B5·B6 172.5). 어긋나게 배치하라는 지시는 없었으므로 모델 쪽 오류 항목으로 기록 (운곡교 M-8·M-9 와 같은 유형). 엑셀 ① M-9, 도면 색구분 DXF 기초 정면도·측면도에 표시. 모델(.3dm) 표식에는 아직 M-9 없음(모델을 라이노에 열어야 추가 가능).
## 위치·CTC·높이 점검 (2026-10-19, 코핑·기둥·기초)
- 방법: 도면 DXF 의 단면 점(원, 지름 22.5 안쪽 점 = 철근 중심)·수직선·치수를 모델 중심선(.3dm)과 좌표로 비교. 기초 정면도 중심 x 10473.9 / 측면도 24604.3, 기초 하면 y -285253.3 ↔ 모델 z 42763 (오프셋 328016.3), 코핑 정면도 오프셋 344716.3 (코핑 상면·모서리·기둥 접합이 0.5 mm 이내 일치).
- 일치: 기초 B1·B2+B3·B4·B5+B6·B10·B7·B8 위치 (도면 점 31/59/31/59/7 개 0.0 mm), 코핑 스터럽 x 41개 0.0 mm, C1+C3·C2+C4(25본 @125)·C5+C6(13본 @250), 기둥 P1 68본 r 1125, 띠철근 r 1148↔1149, P3·P4·P2 z 구간(12/25/70개), 코핑 콘크리트 윤곽.
- 신규 M-10: 코핑 스터럽 C7~C14 수직선 상단 +26.5 / 하단 -27.5 (41곳, 합 54 = M-3 가산 +54).
- **M-9 보정**: 도면 단면의 철근 점(원)은 모델과 같은 높이 (B1 77.5, B4 122.5, B3 127.5, B6 172.5; 정면·측면 22.5 차) — 모델 = 도면 점 0.0 mm. 도면 철근 선(100/150)과 점이 도면 안에서 다름. 옥성교 B5~B8 도 같은 양상 (측면 점 321527.3/323432.3 = 모델). 운곡교는 같은 방법으로 아직 미확인.
- 미확인: 코핑 C1~C6 높이, C7 상·하단 구간, C14·C15, S1~S4 높이/간격.
## 8. 2026-10-20 옥성교 방식 재검토 (라이노 재연결) — 길이 전수 점검 · 커플러
- 중심선 992개(34마크) 실제 형상 길이(모서리 각진 환산, 큰 호는 호 길이)를 재료표 L 과 대조. 본수 전 마크 일치. 0 mm: B1~B7·B9·B10·P1·P5·S1~S4. 이전 검토는 모델 설계길이(user string)와 L 을 비교해 형상이 짧은 것을 놓침.
- **M-8 통합 (커플러)**: P3·P4 철근은 호 A 만 있고 커플러 몸체 없음 → 이음 B 55 가 빈 틈 54.99. 재료표 L 은 링 둘레 (P3 2A+2B, P4 A+B). 형상 합이 L 보다 P3 링당 -109.6 (12링 -1.32 m), P4 -54.6 (25링 -1.37 m). 철근이 커플러에 B/2 씩 들어가야 L 과 맞음 (삽입 깊이 도면에 없음, 역산 = 추론). 구분 회색 → 도면↔모델(주황). 각도 기준은 그대로 미확인.
- **M-4 확장 (C10·C12)**: 닫힌 사각 스터럽 형상이 L - J 보다 짧음 — C10 평균 -3.169 m (7본 -22.2 m, 기존 기록 -6.178 m), C12 평균 -2.302 m (6본 -13.8 m). 재료표 L 이 A 평균·J 개수를 어떻게 잡았는지 도해(III·XII)로 확인 필요.
- **정정**: P2 반링 2조각 형상 합 8.796 ≈ L 8.779 (+17 mm) → M-2 의 'P2 J 0.380 형상에 없음' 은 길이 부족이 아님. B8 은 L = 2A+2B+4J (J 2.280), 형상 합 + 2.280 ↔ L = -79 mm = M-5.
- 엑셀 `대조표_산외교P1_도면모델_분류_V2.xlsx` ① M-8·M-4·M-2, ② P2·P3·P4·C10·C12·B8, ④ 전수 점검 행, 분류 안내 (주황 9 · 회색 1 · 미확인 행 P2). 모델(.3dm) 표식 수정 없음, 모델 수정 없음.
## 9. ⑥ 길이·본수 전수 점검 시트 추가 (2026-10-20)
엑셀 `⑥ 길이·본수 전수 점검` — 34마크 한 줄씩 (본수·L·형상 평균·부족량·총 길이 차이·판정·원인). 재료표 총길이 8268.293 m ↔ 모델 형상 7857.756 m. 옥성교·Ramp-A교와 같은 구성. 솔리드(Brep) 길이 확인은 산외교에서는 안 함.
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 977 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

@@ -0,0 +1,13 @@
# 산외교 P1 — 도면 내부 오류 보관 (사용자가 요청할 때 꺼내서 보고)
모델↔도면 검토 중 발견된, 도면 자체의 불일치/근사. 모델이 어느 쪽을 따랐는지 함께 기록.
| 번호 | 위치 | 내용 A | 내용 B | 모델 적용 |
|---|---|---|---|---|
| D-1 | 재료표 B8 | 표 A 7.300 · B 7.350 (L 31.581 = 2A+2B+4J) | 기초 평면 작도 ±3,672.5 (7.345×7.345) | 작도값 (모델 src 기록) |
| D-2 | 재료표 C7~C9 | A 1.554 · C 660 · L 은 대푯값(a = (1.250×2ea + 2.000×1ea)/3ea + 주철근D + 수직스터럽D) | 단면 A-A 철근마다 폭 다름 | 철근마다 작도값 |
| D-3 | 재료표 C10·C11·C13·C12 | A 범위(최대~최소)·L 이 (최대+최소)/2 근사 | 철근별 개별 치수 | 철근별 개별 작도 + 가산 |
| D-4 | 재료표 C10 | A 최대 9.570 | C11 A 최대 9.560, 작도 폭과 대응 불명 (M-4 와 연결) | 작도 최대 8.633+54 |
출처: 재료표(SD400) 판독, 모델 user string src. 이후 발견분은 아래에 추가.
| D-5 | 재료표 B8 형식 | 형식 V (도해는 원형 링 a·b) | 치수 A 7.300 · B 7.350 은 사각 둘레 | 사각(작도 7.345) |
@@ -0,0 +1,596 @@
# -*- coding: utf-8 -*-
"""산외교(울산) P1 철근재료표 대조 (운곡교 make_bom_compare.py 를 산외교 도면·모델용으로 옮긴 것).
사용: python -I make_bom_compare_sw.py <pylib경로> <texts.json> <출력xlsx>
texts.json : 도면 DXF 의 글자 목록 (x, y, 레이어, 글자, 높이) - 같은 폴더 '추출데이터\도면_글자.json'
입력(고정): 산외교P1 G5.3dm (모델)
시트: ①도면 재료표(재현) ②모델 재료표 ③대조·비고 ④요약 ⑤비고코드설명 ⑥모델_개별철근
검토 범위: 모델↔도면. 도면 내부 불일치는 '도면내부_오류_보관.md' 에만 기록(여기 비고에는 넣지 않음).
"""
import sys, re, math, collections, json, os
sys.path.insert(0, sys.argv[1])
import rhino3dm
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
R = json.load(open(sys.argv[2], encoding="utf-8"))
OUT = sys.argv[3]
M3DM = r"D:\★Pier 자료\심수석님 검토 파일\검토_산외교P1\산외교P1 G5.3dm"
UNIT = {"32": 6.230, "29": 5.040, "25": 3.980, "22": 3.040, "19": 2.250, "16": 1.560, "13": 0.995}
TOL = 1.0 + 1e-6
# =============================================================== 1. 도면 재료표 파싱
COLS = [("num", 97500, 98300), ("shape", 98300, 99000), ("dia", 99000, 99800), ("n", 99800, 100600),
("A", 100600, 101800), ("B", 101800, 103000), ("C", 103000, 104300), ("D", 104300, 105500),
("E", 105500, 106600), ("R", 106600, 107600), ("X1", 107600, 108300), ("X2", 108300, 108900),
("X3", 108900, 109500), ("X4", 109500, 110100), ("J", 110100, 111300), ("L", 111300, 112600),
("tot", 112600, 113700), ("unit", 113700, 114700), ("w", 114700, 115800), ("wh", 115800, 117500)]
rowsraw = collections.defaultdict(lambda: collections.defaultdict(list))
for x, y, lay, t, h in R:
if not lay.startswith("CS-TABL") or not (-278800 < y < -265300):
continue
tab = "SD400" if y > -274000 else "SD500"
yy = round(y / 10) * 10
t = t.strip().replace("%%%", "%").replace("%%d", "°")
if 104000 <= x < 106000 and ("소계" in t or "총계" in t):
rowsraw[(tab, yy)]["label"].append(t)
continue
for name, lo, hi in COLS:
if lo <= x < hi:
rowsraw[(tab, yy)][name].append((x, t))
break
def first(c, k):
v = c.get(k)
return sorted(v)[0][1] if v else ""
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:
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}
d["shape_v"] = d["shape"]; d["dia_v"] = d["dia"]
drow[tab].append({"kind": "bar", "mark": num, "cells": d, "dia_n": re.sub(r"\D", "", d["dia"])})
DR = {r["mark"]: r for tab in drow for r in drow[tab] if r["kind"] == "bar"}
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
# =============================================================== 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 is_arc(s): return type(s).__name__ == "ArcCurve"
def clen(c, n=1500):
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 num(s):
try: return float(s)
except Exception: return 0.0
def lines_of(g):
return [(V(s.PointAtStart), V(s.PointAtEnd)) for s in segs_of(g) if is_line(s)]
def corner_seq(g):
"""직선 구간을 연장한 꼭짓점 사이 거리(각진 치수)."""
lines = lines_of(g)
if len(lines) < 2:
return [nrm(sub(l[1], l[0])) for l in lines]
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)))
seq = [lines[0][0]] + pts + [lines[-1][1]]
return [nrm(sub(seq[i + 1], seq[i])) for i in range(len(seq) - 1)]
def bar_dims(mk, shape, us, g):
"""한 철근의 재료표 항목(A..E, R, X1~X4) 을 mm / 도(°) 로. 모델에서 정의할 수 없는 항목(J 등)은 넣지 않는다."""
d = {}
segs = segs_of(g)
arcs = [s for s in segs if is_arc(s)]
seq = corner_seq(g)
if shape == "I":
if len(seq) == 1:
d["A"] = seq[0]
elif len(seq) == 3:
d["A"] = seq[1]; d["B"] = seq[0]
elif shape == "II": # C7~C9
d["C"] = seq[0]; d["B"] = seq[1]; d["A"] = seq[2]
elif shape == "III":
if mk == "C10": # 폐합 스터럽(변 2개씩)
d["B"] = seq[1]; d["A"] = seq[2]
else: # B9·B10
d["B"] = seq[0]; d["A"] = seq[1]
elif shape in ("VII", "VIII"): # C11·C13 갈고리 달린 수평근
ls = [s for s in segs if is_line(s)]
d["C"] = nrm(sub(V(ls[0].PointAtEnd), V(ls[0].PointAtStart)))
d["A"] = seq[2]
d["R"] = arcs[0].Arc.Radius - num("0")
elif shape == "XII": # C12 (R400 굽힘이 있어 도면 치수는 접선 직선 길이)
ln = [nrm(sub(l[1], l[0])) for l in lines_of(g)]
d["B"] = ln[1]; d["A"] = ln[2]
d["C"] = arc_len(arcs[0]); d["R"] = arcs[0].Arc.Radius
elif shape == "IX": # C1~C4
d["B"] = seq[0]; d["A"] = seq[1]; d["C"] = seq[2]
d["X1"] = 180.0 - arcs[0].Arc.AngleDegrees; d["X2"] = 180.0 - arcs[1].Arc.AngleDegrees
elif shape == "IV":
if mk in ("C5", "C6"):
d["A"], d["B"], d["C"], d["D"], d["E"] = seq[0], seq[1], seq[2], seq[3], seq[4]
for i, a in enumerate(arcs[:4]):
d["X%d" % (i + 1)] = 180.0 - a.Arc.AngleDegrees
else: # B2·B5
d["B"] = seq[0]; d["A"] = seq[1]
elif shape == "V":
if mk == "B8": # 기초 둘레 폐합근: 변 길이
d["A"] = seq[1]; d["B"] = seq[2]
else: # P3 이음 링 (반원)
d["A"] = arc_len(arcs[0]); d["B"] = num(us.get("coupler_length")); d["R"] = arcs[0].Arc.Radius
elif shape == "X": # P4 링
d["A"] = arc_len(arcs[0]); d["B"] = num(us.get("coupler_length")); d["R"] = arcs[0].Arc.Radius
elif shape == "VI":
big = max(arcs, key=lambda s: s.Arc.Radius)
d["R"] = big.Arc.Radius
if mk == "P1":
ls = [s for s in segs if is_line(s)]
lens = sorted(nrm(sub(V(s.PointAtEnd), V(s.PointAtStart))) for s in ls)
d["C"] = lens[0]; d["A"] = lens[-1]; d["B"] = arc_len(arcs[0])
return d
bars = collections.defaultdict(dict)
for o in m3.Objects:
L = lay.get(o.Attributes.LayerIndex, "")
g = o.Geometry; nm = o.Attributes.Name or ""
if isinstance(g, rhino3dm.Brep):
us = dict(o.Attributes.GetUserStrings())
if us.get("mark"):
bars[us["mark"]].setdefault(us.get("occurrence"), {}).update(us=us, name=nm, layer=L)
elif nm.startswith("P1-RC-"):
p = nm.split("-")
bars[p[2]].setdefault("-".join(p[3:]), {})["g"] = g
mod = {}
HALF = ("P2", "P3")
for mk, dd in bars.items():
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"], 800)
for k, v in bar_dims(mk, shape, us, b["g"]).items():
if v is not None:
dims[k].append(v)
half = mk in HALF
n = len(items) // 2 if half else len(items)
# 형상 길이 합(곡선 실길이) 과 design_length 의 차
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", "X3", "X4"):
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", "X3", "X4"):
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]]
return fmt_one(hi, key) + "~" + fmt_one(lo, key), [hi, lo]
def same(dtxt, mvals, key):
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", "X3", "X4"):
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", "X3", "X4", "J"]
# 재료표 치수 = 모델(작도) 치수 + 가산값 (주철근D+스터럽D 등 도면 산식). 일치하면 'rule' 로 표시
RULE = {"C10": {"A": 54, "B": 99}, "C12": {"A": 54, "B": 99}, "C11": {"A": 44}, "C13": {"A": 44}}
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", "C2", "C3", "C4"):
NOTE[mk] = "[M-1] 일부 철근 A 가 도면 단일값보다 짧음(모델은 철근마다 개별 길이). [M-2] J 1.560 은 재료표 L 에만 포함되고 모델 형상에는 작도되지 않음."
for mk in ("C5", "C6"):
NOTE[mk] = "[M-1] 일부 철근 B·D 가 도면 4.018/4.016 보다 짧음. [M-2] J 940 은 재료표 L 에만 포함, 모델 형상에는 없음."
for mk in ("C7", "C8", "C9"):
NOTE[mk] = "[M-3] 재료표 A·C·L 은 대푯값(a=평균+54), 모델은 철근마다 작도값 → 철근별로 다름(길이 합은 근사). [M-2] 재료표 L=2A+2B+2C 의 C 660 이 모델 폐합 스터럽 형상에는 없음."
NOTE["C10"] = "[M-3] 재료표 A·B 는 모델 작도값 +54/+99. [M-4] 재료표 A 최대 9.570 에 대응하는 철근이 모델에 없음(모델 최대 A 8.633+54=8.687, 설계길이 합 -6.2 m). [M-2] J 660 은 L 에만 반영."
NOTE["C11"] = "[M-3] 재료표 A = 모델 작도값 +44 (일치). B 165 는 모델에서 정의 불가(미추출)."
NOTE["C12"] = "[M-3] 재료표 A·B = 모델 작도값 +54/+99 (일치). [M-2] J 660 은 L 에만 반영."
NOTE["C13"] = "[M-3] 재료표 A = 모델 작도값 +44 (일치). B 165 미추출. [M-2] J 660 은 L 에만 반영."
NOTE["C14"] = "[M-1] 재료표 A 3.000 단일 ↔ 모델 일부 철근 A 2.633~2.892(짧음)."
NOTE["C15"] = "[M-1] 재료표 A 4.500 단일 ↔ 모델 A 4.118/4.489/4.517."
NOTE["P2"] = "[M-2] A·B·J 는 모델에서 정의 불가(미추출). 모델은 반링 2조각(140개)=1본 → 개수 70 으로 환산."
NOTE["P3"] = "모델은 반원 2조각(24개)=1링 → 개수 12 로 환산."
NOTE["P1"] = "[M-6] 도면 표의 주철근 커플러 476개(H32)가 모델에 없음 — 모델 P1 은 커플러 없이 연속 28.545 m 로 작도(68본×7 = 476 로 추정)."
NOTE["B2"] = "[M-2] J 1.000 은 L 에만 반영, 모델 형상에는 없음(모델 중심선 11.758 < L 12.800)."
NOTE["B5"] = NOTE["B2"]
NOTE["B8"] = "[M-5] 모델은 정사각 7.345×7.345, 재료표 A 7.300 · B 7.350. [M-2] J 570 은 L 에만 반영(모델 29.339 < L 31.581)."
KDS["P5"] = "[M-7] H19 굽힘 내면지름 66.5 mm < 공칭(19.1) 3.5db 66.85 mm (0.35 mm 부족)."
for mk in ("B2", "B5", "C7", "C8", "C9", "C10", "C15"):
KDS[mk] = "[M-7] H22 굽힘 내면지름 76.8 mm < 공칭(22.2) 3.5db 77.7 mm (0.9 mm 부족)."
NOTE["P3"] = NOTE["P3"] + " [M-8] 이음(커플러 55) 각도 90°/270° 는 도면에 기준이 없어 모델 가정."
NOTE["P4"] = "[M-8] 커플러(55) 각도 90° 는 도면에 기준이 없어 모델 가정."
# =============================================================== 5. 표 행 만들기
HEAD = ["번 호", "형 식", "직 경", "개 수", "A", "B", "C", "D", "E", "R", "X1(º)", "X2(º)", "X3(º)", "X4(º)", "J", "길 이(M)", "총길이(M)", "단위중량(KG/M)", "총중량(TON)", "총중량(할증)(TON)"]
KEYS = ["num", "shape", "dia", "n", "A", "B", "C", "D", "E", "R", "X1", "X2", "X3", "X4", "J", "L", "tot", "unit", "w", "wh"]
NK = len(KEYS)
def surch(d): return 1.06 if int(d) >= 22 else 1.03
def build_model_table():
out = {}
for tab in ("SD400", "SD500"):
rows = []; grp = []
for r in drow[tab]:
if r["kind"] == "bar":
mk = r["mark"]; m = mod.get(mk)
cells = {"num": mk}; vals = {}
shp = m["shape"] if m else r["cells"]["shape_v"]
dia = m["dia"] if m else r["cells"]["dia_v"]
cells["shape"] = shp; cells["dia"] = 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}
if "소계" 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 = []
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.update(tot="%.3f" % tl, w="%.3f" % w, wh="%.3f" % wh)
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):
res = {}; auto = []
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:
if k == "J":
# J 는 모델 형상에서 정의되지 않음 (L 에만 반영)
res[k] = "model_blank" if dc.get("J") else "both_blank"
if dc.get("J"):
auto.append("J: 도면 %s → 모델 미작도(L 에만 반영)" % dc["J"])
continue
r_ = same(dc.get(k, ""), mv.get(k), k)
off = RULE.get(dr["mark"], {}).get(k)
if r_ == "diff" and off and mv.get(k):
r2 = same(dc.get(k, ""), [v + off for v in mv[k]], k)
if r2 == "same":
r_ = "rule"
auto.append("%s: 도면 %s = 모델 %s + %d (가산 규칙)" % (k, dc.get(k), mr["cells"][k], off))
res[k] = r_
if r_ == "rule":
pass
elif 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) <= 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"
# 형상 실길이(중심선) 대비: 재료표 L 은 각진 치수 합이라 굽힘 때문에 약간 짧은 것은 정상. 큰 차이(>3%)만 기록
if m and dL is not None and dn:
clm = m["cl"] * 1000.0 / m["n"]
if dL - clm > 0.03 * dL:
auto.append("형상 중심선 평균 %.3f m < 재료표 L %.3f m (%.0f mm 짧음, 굽힘 감소 제외 시 J 등 미작도 의심)" % (clm / 1000, dL / 1000, dL - clm))
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, "rule": BLUE}
wb = Workbook()
VCOL = NK + 1 # 판정 열 번호
def write_table(ws, title, tables, 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 [])
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 > NK), horizontal=("center" if c <= NK 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=NK + 4).font = Font(color="FF00FF")
ws.append([])
widths = [8, 6, 6, 6, 10, 10, 10, 7, 7, 8, 7, 7, 7, 7, 8, 10, 11, 11, 10, 12]
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 in ("SD400", "SD500"):
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(("철 근 재 료 표 (%s) — 도면 원본 값 그대로" % tab, rows))
write_table(w1, "① 도면 재료표 재현 (산외교 울산 P1, 교각배근도(울산P1)_2016.10.28)", tabs1)
w2 = wb.create_sheet("②모델 재료표")
tabs2 = []
for tab in ("SD400", "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(("철 근 재 료 표 (%s) — 모델(산외교P1 G5.3dm) 기준" % tab, rows))
write_table(w2, "② 모델 재료표 (모델 철근에서 추출: 개수·형상·치수·길이·총길이, 중량은 도면과 같은 단위중량·할증 적용)", tabs2)
w3 = wb.create_sheet("③대조·비고")
tabs3 = []
EXPORT = {"SD400": [], "SD500": []}
stat = []
for tab in ("SD400", "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 = list(res.values())
if "diff" in kind:
verdict = "차이"
elif "drawing_blank" in kind or "model_blank" in kind:
verdict = "도면 빈칸/모델 미추출"
elif "round" in kind or "rule" in kind:
verdict = "일치(반올림)"
else:
verdict = "일치"
fills[VCOL] = {"차이": 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)
line += ["", "", "", ""]
rows.append({"line": line, "fills": fills, "bold": True})
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": {KEYS[ci - 1]: "diff" for ci in fills},
"verdict": "", "auto": [], "note": "", "kds": ""})
tabs3.append(("%s — 모델 값 표시, 도면과 다른 칸은 색 + (도면: 값)" % tab, 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 / wd) + 1) for p in s.split("\n")) if c.column > NK 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]=도면↔모델(⑤ 참고). 도면 내부 불일치는 별도 보관(도면내부_오류_보관.md).")
w3.cell(row=2, column=1).font = Font(italic=True)
w4 = wb.create_sheet("④요약")
w4.append(["도면 재료표 ↔ 모델 재료표 차이 요약 (검토 미확정, 자동 비교)"]); 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, 160], 1):
w4.column_dimensions[get_column_letter(i)].width = w
CODES = [
("M-1", "도면↔모델", "C1~C6·C14·C15 일부 철근이 재료표 단일값보다 짧음(C1 A 8.462~9.259, C5 B 3.580~4.018, C14 A 2.633~3.000, C15 A 4.118~4.517) — 모델은 철근마다 개별 길이"),
("M-2", "도면↔모델", "J·C 길이(C1~C4 J 1.560, C5·C6 J 940, C7~C9 C 660×2, C10·C12·C13 J 660, P2 J 380, B2·B5 J 1.000, B8 J 570)가 재료표 L 에만 있고 모델 형상에는 작도되지 않음"),
("M-3", "도면↔모델", "C7~C13 치수 산식(+54/+44/+99 가산): 재료표는 대푯값·가산값, 모델은 철근마다 작도값"),
("M-4", "도면↔모델", "C10 재료표 A 최대 9.570 에 대응하는 철근이 모델에 없음(모델 A 최대 8.687)"),
("M-5", "도면↔모델", "B8 모델 정사각 7.345×7.345 ↔ 재료표 A 7.300·B 7.350"),
("M-6", "도면↔모델", "주철근 커플러 476개(H32, 도면 표)가 모델에 없음 — P1 은 연속 철근으로 작도"),
("M-7", "기준↔모델", "굽힘 내면지름: H22(B2·B5·C7~C10·C15) 76.8 < 77.7, H19(P5) 66.5 < 66.85 — KDS 24 14 21 표 4.5-1 3.5db 에 0.9/0.35 mm 부족"),
("M-8", "미확인", "P3 이음·P4 커플러 각도(90°/270°) 도면에 기준 없음, 모델 가정"),
]
w5 = wb.create_sheet("⑤비고코드설명")
w5.append(["코드", "구분", "내용"])
for c in CODES:
w5.append(list(c))
for i, w in enumerate([10, 14, 120], 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)", "호스트"])
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"], 600), 1), us.get("host")])
for c in range(1, 9):
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, 20, 22, 11, 18, 10], 1):
w6.column_dimensions[get_column_letter(i)].width = w
w6.freeze_panes = "A2"
wb.move_sheet("④요약", offset=-3)
jp = os.path.join(os.path.dirname(os.path.dirname(OUT)) if False else os.path.dirname(OUT), "추출데이터", "재료표_대조결과.json")
os.makedirs(os.path.dirname(jp), exist_ok=True)
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))
@@ -0,0 +1 @@
[[35676, -270389, "CS-TEXT", "68 EA", 125.0], [35637, -278211, "CS-TEXT", "68 EA", 125.0], [36444, -285068, "CS-TEXT", "C.T.C 300", 125.0], [35621, -285973, "CS-TEXT", "68 EA", 125.0], [31785, -286034, "CS-DIMT", "125", 125.0], [31785, -285159, "CS-DIMT", "1.125", 125.0], [31785, -284034, "CS-DIMT", "1.125", 125.0], [31785, -283096, "CS-DIMT", "125", 125.0], [31285, -284596, "CS-DIMT", "2.500", 125.0], [34935, -282071, "CS-DIMT", "DIVISION 68", 125.0], [33498, -281446, "CS-DIMT", "125", 125.0], [34373, -281446, "CS-DIMT", "1.125", 125.0], [35498, -281446, "CS-DIMT", "1.125", 125.0], [36435, -281446, "CS-DIMT", "125", 125.0], [34935, -280946, "CS-DIMT", "2.500", 125.0], [36460, -277305, "CS-TEXT", "C.T.C 150", 125.0], [31801, -278271, "CS-DIMT", "125", 125.0], [31801, -277396, "CS-DIMT", "1.125", 125.0], [31801, -276271, "CS-DIMT", "1.125", 125.0], [31801, -275333, "CS-DIMT", "125", 125.0], [31301, -276833, "CS-DIMT", "2.500", 125.0], [34951, -274308, "CS-DIMT", "DIVISION 68", 125.0], [33514, -273683, "CS-DIMT", "125", 125.0], [34389, -273683, "CS-DIMT", "1.125", 125.0], [35514, -273683, "CS-DIMT", "1.125", 125.0], [36451, -273683, "CS-DIMT", "125", 125.0], [34951, -273183, "CS-DIMT", "2.500", 125.0], [36498, -269484, "CS-TEXT", "C.T.C 150", 125.0], [31840, -270449, "CS-DIMT", "125", 125.0], [31840, -269574, "CS-DIMT", "1.125", 125.0], [31840, -268449, "CS-DIMT", "1.125", 125.0], [31840, -267512, "CS-DIMT", "125", 125.0], [31340, -269012, "CS-DIMT", "2.500", 125.0], [34990, -266487, "CS-DIMT", "DIVISION 68", 125.0], [33552, -265862, "CS-DIMT", "125", 125.0], [34427, -265862, "CS-DIMT", "1.125", 125.0], [35552, -265862, "CS-DIMT", "1.125", 125.0], [36490, -265862, "CS-DIMT", "125", 125.0], [34990, -265362, "CS-DIMT", "2.500", 125.0], [35259, -264933, "CZ-TEX1", "S=1:50", 250.0], [35220, -272734, "CZ-TEX1", "S=1:50", 250.0], [35148, -280526, "CZ-TEX1", "S=1:50", 250.0], [34354, -264595, "CZ-TEX1", "단면 C-C", 350.0], [34315, -272417, "CZ-TEX1", "단면 D-D", 350.0], [34299, -280180, "CZ-TEX1", "단면 E-E", 350.0], [50481, -274306, "CS-DIMT", "150", 125.0], [16774, -279453, "CS-DIMT", "28.300", 125.0], [4074, -277741, "CS-DIMT", "11@1.800=19.800", 125.0], [4074, -275378, "CS-DIMT", "1.200", 125.0], [4574, -277141, "CS-DIMT", "70@300=21.000", 125.0], [97556, -274757, "CS-TABL-TEX2", "(SD500)", 125.0], [107148, -274624, "CZ-TEX1", "철 근 재 료 표 (SD500)", 350.0], [116678, -279002, "CS-TABL-TEX2", "17.944", 125.0], [116678, -278732, "CS-TABL-TEX2", "1.251(3%%%)", 125.0], [116678, -277112, "CS-TABL-TEX2", "3.875(6%)", 125.0], [116678, -275762, "CS-TABL-TEX2", "12.818(6%%%)", 125.0], [116121, -275215, "CS-TABL-TEX2", "(TON)", 125.0], [116121, -275049, "CS-TABL-TEX2", "총중량(할증)", 125.0], [115440, -279002, "CS-TABL-TEX2", "16.963", 125.0], [115440, -278732, "CS-TABL-TEX2", "1.215", 125.0], [115440, -277112, "CS-TABL-TEX2", "3.655", 125.0], [115440, -275762, "CS-TABL-TEX2", "12.093", 125.0], [115010, -275215, "CS-TABL-TEX2", "(TON)", 125.0], [115010, -275049, "CS-TABL-TEX2", "총중량", 125.0], [114456, -278732, "CS-TABL-TEX2", "1.560", 125.0], [114456, -277112, "CS-TABL-TEX2", "3.040", 125.0], [114456, -275762, "CS-TABL-TEX2", "6.230", 125.0], [114026, -275215, "CS-TABL-TEX2", "(KG/M)", 125.0], [114026, -275049, "CS-TABL-TEX2", "단위중량", 125.0], [113472, -279002, "CS-TABL-TEX2", "3922.127", 125.0], [113472, -278732, "CS-TABL-TEX2", "778.667", 125.0], [113472, -278462, "CS-TABL-TEX2", "52.500", 125.0], [113472, -278192, "CS-TABL-TEX2", "52.500", 125.0], [113472, -277922, "CS-TABL-TEX2", "221.067", 125.0], [113472, -277652, "CS-TABL-TEX2", "226.300", 125.0], [113472, -277382, "CS-TABL-TEX2", "226.300", 125.0], [113472, -277112, "CS-TABL-TEX2", "1202.400", 125.0], [113472, -276842, "CS-TABL-TEX2", "204.400", 125.0], [113472, -276572, "CS-TABL-TEX2", "396.800", 125.0], [113472, -276302, "CS-TABL-TEX2", "204.400", 125.0], [113472, -276032, "CS-TABL-TEX2", "396.800", 125.0], [113472, -275762, "CS-TABL-TEX2", "1941.060", 125.0], [113472, -275492, "CS-TABL-TEX2", "1941.060", 125.0], [112915, -275215, "CS-TABL-TEX2", "(M)", 125.0], [112915, -275049, "CS-TABL-TEX2", "총길이", 125.0], [112234, -278462, "CS-TABL-TEX2", "7.500", 125.0], [112234, -278192, "CS-TABL-TEX2", "7.500", 125.0], [112234, -277922, "CS-TABL-TEX2", "31.581", 125.0], [112234, -277652, "CS-TABL-TEX2", "7.300", 125.0], [112234, -277382, "CS-TABL-TEX2", "7.300", 125.0], [112234, -276842, "CS-TABL-TEX2", "7.300", 125.0], [112234, -276572, "CS-TABL-TEX2", "12.800", 125.0], [112234, -276302, "CS-TABL-TEX2", "7.300", 125.0], [112234, -276032, "CS-TABL-TEX2", "12.800", 125.0], [112234, -275492, "CS-TABL-TEX2", "28.545", 125.0], [111677, -275215, "CS-TABL-TEX2", "(M)", 125.0], [111677, -275049, "CS-TABL-TEX2", "길이", 125.0], [110566, -277922, "CS-TABL-TEX2", "570", 125.0], [110566, -276572, "CS-TABL-TEX2", "1.000", 125.0], [110566, -276032, "CS-TABL-TEX2", "1.000", 125.0], [110566, -275132, "CS-TABL-TEX2", "J", 125.0], [109772, -275132, Line truncated
File diff suppressed because it is too large. Load diff