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>
No files matched your search
|
After Width: | Height: | Size: 467 KiB |
|
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) 길이 확인은 산외교에서는 안 함.
|
||||
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 244 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 255 KiB |
|
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
|
||||