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

+6
View File
@@ -0,0 +1,6 @@
# 교량 도면↔모델 검토 파이프라인 (산외교·옥성교·Ramp-A교, 2026-10-19)
순서: DWG→DXF(라이노 headless `RhinoDoc.CreateHeadless` → Import → Export) → `dxfdump.py`(글자 추출) → 교량별 `*_parse/*_analyze/*_crops`(재료표·철근상세 읽기, rhino3dm 으로 모델 치수 추출) → `shots_*.py`(라이노에서 `cap.py` 로 캡처) → `*_build_images.py`(`compose_new.py`) → `*_cfg.py` → `make_review_xlsx.py` → `*_mark_dxf.py`(도면 표시).
- 접두어 ok_=옥성교, sw_=산외교, ra_=Ramp-A교. pylib(rhino3dm·ezdxf·openpyxl·matplotlib·PIL)는 `pip install --target` 으로 설치.
- 주의: DXF 텍스트는 UTF-8(cp949 지정 금지), 터미널 한글 출력은 깨져 보임(파일로 쓰고 Read). 경로는 스크립트 안 SCR 상수를 환경에 맞게 수정.
- 결과 폴더: D:\★Pier 자료\심수석님 검토 파일\검토_산외교P1 / 검토_옥성교P1 / Ramp-A교P1 (각 검토기록_최종.md, 도면내부_오류_보관.md)
+120
View File
@@ -0,0 +1,120 @@
# exec'd inside Rhino via execute_rhino_code: g={}; exec(open(path,encoding='utf-8').read(), g); g['run'](shot)
import scriptcontext as sc
import Rhino
import System
from System.Drawing import Size, Color
SCR = r"C:\Users\User\AppData\Local\Temp\claude\D--Rhino-MCP-rhino-mcp-main\f46b47bc-9105-4079-9b62-fcbee31b26ae\scratchpad"
OUT = SCR + r"\shots"
def all_objs():
st = Rhino.DocObjects.ObjectEnumeratorSettings()
st.HiddenObjects = True
st.NormalObjects = True
st.LockedObjects = True
st.IncludeLights = False
st.IncludeGrips = False
return sc.doc.Objects.GetObjectList(st)
def state_path():
return SCR + r"\hidden_state.txt"
def save_state():
doc = sc.doc
ids = []
for o in doc.Objects.GetObjectList(Rhino.DocObjects.ObjectEnumeratorSettings()):
pass
return None
def run(shot):
doc = sc.doc
import os
if not os.path.isdir(OUT):
os.makedirs(OUT)
layers = shot["layers"] # list of layer-path prefixes to show
names = shot.get("names") # optional name substrings (applies to rebar objects only)
show_solids = shot.get("solids", []) # exact layer paths of concrete to show
for o in all_objs():
ln = doc.Layers[o.Attributes.LayerIndex].FullPath
vis = False
for s in show_solids:
if ln == s:
vis = True
for p in layers:
if ln == p or ln.startswith(p + "::"):
if names:
ok = False
for n in names:
if n in o.Name:
ok = True
if ok:
vis = True
else:
vis = True
# skip centerline curves unless asked
if "중심선" in ln and not shot.get("centerline", False):
vis = False
if vis:
doc.Objects.Show(o, True)
else:
doc.Objects.Hide(o, True)
view = doc.Views.ActiveView
vp = view.ActiveViewport
mode = None
for d in Rhino.Display.DisplayModeDescription.GetDisplayModes():
if d.EnglishName == shot.get("mode", "Ghosted"):
mode = d
if mode:
vp.DisplayMode = mode
pr = shot["view"]
if pr == "Front":
vp.SetProjection(Rhino.Display.DefinedViewportProjection.Front, "Front", False)
elif pr == "Right":
vp.SetProjection(Rhino.Display.DefinedViewportProjection.Right, "Right", False)
elif pr == "Top":
vp.SetProjection(Rhino.Display.DefinedViewportProjection.Top, "Top", False)
else:
vp.SetProjection(Rhino.Display.DefinedViewportProjection.Perspective, "Perspective", False)
c = shot["cam"]
vp.SetCameraLocations(Rhino.Geometry.Point3d(c[3], c[4], c[5]), Rhino.Geometry.Point3d(c[0], c[1], c[2]))
b = shot["box"]
bb = Rhino.Geometry.BoundingBox(Rhino.Geometry.Point3d(b[0], b[1], b[2]), Rhino.Geometry.Point3d(b[3], b[4], b[5]))
vp.ZoomBoundingBox(bb)
view.Redraw()
vs = vp.Size
k = shot.get("k", 2.0)
w = int(vs.Width * k)
h = int(vs.Height * k)
bmp = view.CaptureToBitmap(Size(w, h), False, False, False)
bmp.Save(OUT + "\\" + shot["file"])
xf = vp.GetTransform(Rhino.DocObjects.CoordinateSystem.World, Rhino.DocObjects.CoordinateSystem.Screen)
res = []
for p in shot.get("pts", []):
q = Rhino.Geometry.Point3d(p[0], p[1], p[2])
q.Transform(xf)
res.append("%.1f,%.1f" % (q.X * k, q.Y * k))
open(OUT + "\\" + shot["file"] + ".txt", "w").write(("%d,%d" % (w, h)) + chr(10) + chr(10).join(res))
return shot["file"]
def restore():
doc = sc.doc
f3 = Rhino.FileIO.File3dm.Read(doc.Path)
hid = set()
for ob in f3.Objects:
if not ob.Attributes.Visible:
hid.add(str(ob.Attributes.ObjectId))
f3.Dispose()
n = 0
for o in all_objs():
if str(o.Id) in hid:
doc.Objects.Hide(o, True)
n += 1
else:
doc.Objects.Show(o, True)
doc.Views.Redraw()
return n
+12
View File
@@ -0,0 +1,12 @@
import sys,io; sys.path.insert(0,'pylib')
import openpyxl
p=sys.argv[1]
wb=openpyxl.load_workbook(p)
o=io.open('chk_out.txt','w',encoding='utf-8')
for ws in wb:
o.write(u'== %s rows %d cols %d images %d\n'%(ws.title,ws.max_row,ws.max_column,len(ws._images)))
ws=wb['② 전체 대조표']
for r in list(ws.iter_rows(min_row=5,max_row=9,values_only=True)): o.write(u'%s\n'%(list(r),))
for r in list(ws.iter_rows(min_row=45,max_row=51,values_only=True)): o.write(u'%s\n'%(list(r),))
import collections
c=collections.Counter(r[8] for r in ws.iter_rows(min_row=5,values_only=True)); o.write(u'%s\n'%c)
+134
View File
@@ -0,0 +1,134 @@
"""도면↔모델 불일치 시각자료 합성기 (운곡교 M-5 이미지 형식: 헤더 / 도면 패널(파랑) / 모델 패널(주황·노랑·회색) / 차이·도면 쪽·조치).
사용: from compose_new import make; make(cfg)
cfg = {code, title, subtitle, color:(r,g,b), out,
panels:[{label, color, images:[{path, crop(optional), cap(optional)}], maxh}], rows:[(라벨, 본문)]}
"""
import os, sys
sys.path.insert(0, r"C:\Users\User\AppData\Local\Temp\claude\D--Rhino-MCP-rhino-mcp-main\f46b47bc-9105-4079-9b62-fcbee31b26ae\scratchpad\pylib")
from PIL import Image, ImageDraw, ImageFont
FN = r"C:\Windows\Fonts\malgun.ttf"
FB = r"C:\Windows\Fonts\malgunbd.ttf"
W = 2400
PAD = 40
BLUE = (60, 120, 200)
ORANGE = (230, 110, 20)
DYEL = (190, 150, 0)
GREY = (120, 120, 120)
def font(sz, bold=False):
return ImageFont.truetype(FB if bold else FN, sz)
def fit(img, w, h):
r = min(w / img.width, h / img.height)
return img.resize((max(1, int(img.width * r)), max(1, int(img.height * r))), Image.LANCZOS)
def wrap(draw, text, fnt, maxw):
lines = []
for para in text.split("\n"):
cur = ""
for ch in para:
if draw.textlength(cur + ch, font=fnt) > maxw:
lines.append(cur)
cur = ch
else:
cur += ch
lines.append(cur)
return lines
def autocrop(im, pad=30, thr=14):
bg = im.getpixel((2, 2))
px = im.load()
w, h = im.size
x0, y0, x1, y1 = w, h, 0, 0
for y in range(0, h, 2):
for x in range(0, w, 2):
p = px[x, y]
if abs(p[0] - bg[0]) + abs(p[1] - bg[1]) + abs(p[2] - bg[2]) > thr:
if x < x0: x0 = x
if x > x1: x1 = x
if y < y0: y0 = y
if y > y1: y1 = y
if x1 <= x0:
return im
return im.crop((max(0, x0 - pad), max(0, y0 - pad), min(w, x1 + pad), min(h, y1 + pad)))
def load(spec):
im = Image.open(spec["path"]).convert("RGB")
if spec.get("auto", spec["path"].find("shots") >= 0):
im = autocrop(im)
if spec.get("crop"):
im = im.crop(spec["crop"])
if spec.get("draw"):
spec["draw"](im)
return im
def make(cfg):
PW = W - 2 * PAD
# 패널 이미지 준비
prepared = []
for p in cfg["panels"]:
ims = [load(s) for s in p["images"]]
n = len(ims)
gap = 20
cw = (PW - 8 - gap * (n - 1)) // n
maxh = p.get("maxh", 560)
fitted = [fit(im, cw, maxh) for im in ims]
ph = max(f.height for f in fitted) + 8
capsp = 0
if any(s.get("cap") for s in p["images"]):
capsp = 52
prepared.append((p, fitted, ph, capsp, cw, gap))
# 본문 줄 계산
dtmp = ImageDraw.Draw(Image.new("RGB", (10, 10)))
fr = font(32)
rows = []
for lab, text in cfg["rows"]:
lines = wrap(dtmp, text, fr, PW - 230)
rows.append((lab, lines))
H = 120 + 14
for p, fitted, ph, capsp, cw, gap in prepared:
H += 50 + 6 + ph + capsp + 14
H += 12
for lab, lines in rows:
H += max(46, 42 * len(lines)) + 12
H += 24
img = Image.new("RGB", (W, H), (255, 255, 255))
d = ImageDraw.Draw(img)
# 헤더
d.rectangle([0, 0, W, 120], fill=(38, 44, 56))
d.rounded_rectangle([PAD - 6, 24, PAD + 130, 96], radius=10, fill=cfg.get("color", ORANGE))
d.text((PAD + 62, 60), cfg["code"], font=font(52, True), fill=(255, 255, 255), anchor="mm")
d.text((PAD + 170, 14), cfg["title"], font=font(48, True), fill=(255, 255, 255))
d.text((PAD + 170, 74), cfg["subtitle"], font=font(30), fill=(210, 215, 225))
y = 134
for p, fitted, ph, capsp, cw, gap in prepared:
col = p.get("color", BLUE)
d.rectangle([PAD - 6, y, W - PAD + 6, y + 50], fill=col)
d.text((PAD + 10, y + 25), p["label"], font=font(32, True), fill=(255, 255, 255), anchor="lm")
y += 56
d.rectangle([PAD - 6, y, W - PAD + 6, y + ph + capsp + 4], outline=col, width=4)
x = PAD
for f, spec in zip(fitted, p["images"]):
img.paste(f, (x, y + 4))
if spec.get("cap"):
d.text((x + 6, y + 4 + f.height + 4), spec["cap"], font=font(30, True), fill=(30, 30, 30))
x += cw + gap
y += ph + capsp + 18
y += 6
for lab, lines in rows:
h = max(46, 42 * len(lines))
d.rounded_rectangle([PAD - 6, y, PAD + 180, y + 44], radius=8, fill=(232, 236, 244))
d.text((PAD + 87, y + 22), lab, font=font(30, True), fill=(30, 30, 40), anchor="mm")
for i, ln in enumerate(lines):
d.text((PAD + 210, y + 4 + 42 * i), ln, font=fr, fill=(25, 25, 25))
y += h + 12
os.makedirs(os.path.dirname(cfg["out"]), exist_ok=True)
img.save(cfg["out"])
return img.size
Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

@@ -0,0 +1,8 @@
# 단면 점(원) 기준 위치·높이 대조 방법 (2026-10-19)
도면 DXF 는 원·호가 모두 LINE 다각형이라 CIRCLE 엔티티가 없다. 단면 철근 점은 "바깥 45 / 안쪽 22.5 지름" 두 겹 다각형 = 철근 중심.
- swdots.py : dots(x0,x1,y0,y1) — 짧은 LINE 을 끝점 일치로 군집 → (중심x, 중심y, 폭, 높이, 선수, 레이어). 안쪽 점 = 폭 22.5. 캐시 pkl(fastrender 가 만든 cache_*.pkl)을 읽음.
- 기초: 정면 점 ↔ 모델에서 y 방향 직선 끝 (x,z), 측면 점 ↔ x 방향 직선 끝 (y,z). z 오프셋 = 도면 기초 하면 y − 모델 기초 하면 z.
- 기둥: 단면 점 원 피팅(중심·반지름·개수), 띠철근은 긴 LINE 의 반지름 히스토그램.
- 코핑: 정면 수직선 x·상하단 y(오프셋은 코핑 콘크리트 윤곽 일치로 확정), 단면 A-A/B-B 점 ↔ x 방향 직선 (표(y,z) 오프셋은 득표로 추정).
- 주의: python -I 는 스크립트 폴더를 import 경로에서 뺌 → sys.path.insert(0,".") 필요. 정규식 '\f' 는 form feed → chr(92)*2+'f[^;]*;'. rhino3dm 직선 단일 LineCurve 는 SegmentCount 없음.
- 결과: 산외교(M-10 신규, 기초·기둥·코핑 위치 일치), 옥성교(M-12 신규, B-B 점 전부 일치, A-A C7 13점 미확인). **M-9(기초 높이 ±22.5)는 도면 점과 모델이 일치 → 도면 선(100/150)과 점이 도면 안에서 다름. 운곡교·Ramp-A 는 이 방법으로 확인 필요.**
@@ -0,0 +1,283 @@
import sys, json, os
SCR = r"C:\Users\User\AppData\Local\Temp\claude\D--Rhino-MCP-rhino-mcp-main\f46b47bc-9105-4079-9b62-fcbee31b26ae\scratchpad"
sys.path.insert(0, SCR + r"\pylib")
import openpyxl
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.drawing.image import Image as XImage
from openpyxl.utils import get_column_letter
from PIL import Image
ROOT = r"D:\★Pier 자료\심수석님 검토 파일"
OUT = ROOT + r"\4개교_공통사항_종합.xlsx"
TMP = SCR + r"\c4_imgs"
os.makedirs(TMP, exist_ok=True)
BR = {"운곡교": ROOT + r"\검토_운곡교P1\도면↔모델_불일치_시각자료", "산외교": ROOT + r"\검토_산외교P1\도면↔모델_불일치_시각자료",
"옥성교": ROOT + r"\검토_옥성교P1\도면↔모델_불일치_시각자료", "Ramp-A교": ROOT + r"\Ramp-A교P1\도면↔모델_불일치_시각자료"}
BRIDGES = ["운곡교", "산외교", "옥성교", "Ramp-A교"]
ug = json.load(open(SCR + r"\ug_items.json", encoding="utf-8"))
sw = json.load(open(SCR + r"\sw_cfg.json", encoding="utf-8"))["items"]
ok = json.load(open(SCR + r"\ok_cfg.json", encoding="utf-8"))["items"]
ra = json.load(open(SCR + r"\ra_cfg.json", encoding="utf-8"))["items"]
ITEMS = {"운곡교": ug, "산외교": sw, "옥성교": ok, "Ramp-A교": ra}
# 항목 → 공통 문제 번호
MAP = {
"운곡교": {"M-1": "개별", "M-2": "개별", "M-3": "C-1", "M-4": "C-1", "M-5": "C-4", "M-6": "개별", "M-7": "개별", "M-8": "C-8", "M-9": "C-8", "M-10": "C-1", "M-11": "C-1", "M-12": "C-2", "M-13": "C-7"},
"산외교": {"M-1": "C-5", "M-2": "C-3", "M-3": "C-4", "M-4": "C-5", "M-5": "개별", "M-6": "C-6", "M-7": "C-1", "M-8": "C-2", "M-9": "C-8", "M-10": "개별"},
"옥성교": {"M-1": "C-7", "M-2": "C-3", "M-3": "개별", "M-4": "C-4", "M-5": "C-4", "M-6": "C-5", "M-7": "C-1", "M-8": "C-2", "M-9": "C-8", "M-10": "개별", "M-11": "개별", "M-12": "C-8"},
"Ramp-A교": {"M-1": "C-3", "M-2": "C-4", "M-3": "C-5", "M-4": "C-5", "M-5": "C-6", "M-6": "C-1", "M-7": "C-2"},
}
thin = Side(style="thin", color="999999")
B = Border(left=thin, right=thin, top=thin, bottom=thin)
HF = PatternFill("solid", fgColor="D9D9D9")
FILL = {"도면↔모델": PatternFill("solid", fgColor="F8CBAD"), "기준↔모델": PatternFill("solid", fgColor="E6C84A"), "미확인": PatternFill("solid", fgColor="D9D9D9"), "일치": PatternFill("solid", fgColor="E2F0D9")}
YES = PatternFill("solid", fgColor="F8CBAD")
REL = PatternFill("solid", fgColor="FFF2CC")
TOP = Alignment(wrap_text=True, vertical="top")
CEN = Alignment(wrap_text=True, vertical="center", horizontal="center")
COMMON = [
("C-1", "굽힘 내면지름이 KDS 24 14 21 (3.5db) 에 미달", {"운곡교": "M-3·M-4·M-10·M-11", "산외교": "M-7", "옥성교": "M-7", "Ramp-A교": "M-6"}, "확정",
"H19 내면지름 66.5 < 66.85 (3교 같은 값), H22 76.8 < 77.7 (산외·옥성 같은 값), H25 87.0 < 88.9 (옥성·Ramp-A 같은 값), 운곡교 S25 87.6·S19 66.6, P3 갈고리 66.5",
"모델 생성 규칙의 굽힘 반지름이 프로젝트와 무관하게 같은 값 (H19 R 42.8, H22 R 49.5, H25 R 56.2 — 공칭 db 의 2.2~2.25배 반지름) → 규칙 하나 때문으로 추정",
"굽힘 R 규칙을 KDS 기준(내면지름 ≥ 3.5db → 중심선 R ≥ 2.25db 이상)으로 바꿀지 결정. 도면에는 반지름 표기가 없음"),
("C-2", "이음·커플러·갈고리 끝 각도/위치가 도면에 없어 모델이 임의로 가정", {"운곡교": "M-12", "산외교": "M-8", "옥성교": "M-8", "Ramp-A교": "M-7"}, "확정",
"P3 이음 엇갈림 90°/270°, P4·P5 커플러 위치, 링 이음 j 위치 (4교 모두 같은 가정)",
"도면이 형상 도해에 커플러 위치만 보이고 단마다 엇갈림 각도를 적지 않음 → 모델 생성기가 기본 각도(90°/270°)를 사용",
"설계자에게 각도 기준 확인 (도면 보완 요청 대상)"),
("C-3", "겹침·갈고리 길이(j, c)가 재료표 L 에만 있고 모델 형상에는 없음", {"운곡교": "관련 M-11(갈고리 연장)", "산외교": "M-2", "옥성교": "M-2", "Ramp-A교": "M-1"}, "확정 3교",
"산외 J 1.560(C1~C4)·0.660(C10) 등, 옥성 j 1.510(C1~C7)·0.560(B10~B12) 등 합 약 600 m, Ramp-A c 450/350·j 290. 일부는 설계길이에는 포함·형상에서만 빠짐, 옥성 C1~C7 은 설계길이에서도 제외",
"도면은 '이음 길이/겹침'을 L 에 더하지만 위치를 그리지 않음 → 모델이 기하(형상)로는 만들지 않고 설계길이 속성에만 반영",
"겹침·갈고리를 형상에 넣을지 가산 길이로만 둘지 정책 결정 (옥성 C1~C7 설계길이 누락은 별도 확인)"),
("C-4", "재료표 치수 = 모델 작도값 + 가산 규칙(주철근D + 스터럽D 등)", {"운곡교": "M-5", "산외교": "M-3", "옥성교": "M-4·M-5", "Ramp-A교": "M-2"}, "확정",
"운곡 +54.3 (C9~C13, 폭에는 미적용), 산외 +54/+44/+99, 옥성 a +46 / C14 a -54, Ramp-A a +48",
"도면 치수는 가산이 포함된 값, 모델은 작도 치수 → 같은 부재의 치수 정의가 달라 항상 수십 mm 차이가 남",
"가산 규칙과 적용 범위(길이/폭)를 확정하고 모델 치수 정의를 통일 (운곡교 M-5 와 같은 결정)"),
("C-5", "재료표·철근상세는 대표값/단일값, 모델은 철근마다 개별 길이 → 총길이 차이", {"운곡교": "관련 M-5(도면 내부 D-6)", "산외교": "M-1·M-4", "옥성교": "M-6", "Ramp-A교": "M-3·M-4"}, "확정 3교",
"산외 C1~C6·C14·C15 일부 짧음(8.462~9.259), 옥성 C18 a 3.050 ↔ 3종·C8~C13 총길이 -259 m, Ramp-A C8 -37.7 m·C12 +17.0 m (총 -49.9 m)",
"도면은 마크당 값 1개(또는 b 범위의 평균으로 L 계산), 모델은 코핑 하면 형상에 맞춘 개별 길이",
"철근별 개별 길이가 의도인지 확인, 재료표 총길이를 어느 기준으로 볼지 결정"),
("C-6", "커플러 수량 표의 커플러가 모델에 없음 (연속 철근으로 작도)", {"운곡교": "-", "산외교": "M-6", "옥성교": "미확인(표 대조 안 함)", "Ramp-A교": "M-5"}, "확정 2교",
"산외 주철근 커플러 476개, Ramp-A 115개(P1 54 + P1-1 61). 옥성 표(주철근 120·띠철근 58)는 대조 못 함",
"모델이 주철근을 한 개의 긴 직선 철근으로 만들고 커플러 객체·위치를 만들지 않음 (P1 은 연결 간격 길이만 반영)",
"커플러 위치·개수를 모델에 반영할지 판단, 옥성교 표 대조 필요"),
("C-7", "기초 조립용 철근(UA1/UM)이 모델에 없음", {"운곡교": "M-13", "산외교": "-", "옥성교": "M-1", "Ramp-A교": "-"}, "확정 2교",
"운곡 UA1 H22 73본 377.848 m, 옥성 UM H22 92본 414.368 m",
"도면의 '기초 조립용 철근 상세'는 배치 예시도(S=NONE)라 위치·간격이 없어 모델이 생략",
"조립용 철근을 모델에 넣을지 판단 (수량 근거는 재료표 뿐)"),
("C-8", "기초 교차 철근: 도면은 같은 높이(상면 100 · 하면 150)에 그렸는데 모델은 위·아래로 어긋나게 배치 (어긋나게 배치하라는 지시 없음 → 모델 쪽 오류)", {"운곡교": "M-8·M-9", "산외교": "M-9", "옥성교": "M-9·M-12", "Ramp-A교": "해당 없음(말뚝 기초)"}, "확정 3교",
"운곡 B5 73·B6 다리 끝 72·B2 128 (도면 100, ±27~28), 산외 B1~B6 77.5/122.5/127.5/172.5 (±22.5), 옥성 B5~B8 122.5/77.5/172.5 (±22.5, B1~B4 는 일치)",
"모델 생성 시 교차하는 철근끼리 겹치지 않게 한 철근 지름 정도씩 어긋나게 배치한 것으로 추정 (사용자 확인: 그렇게 하라는 지시는 한 적 없음)",
"모델 철근을 도면 선 높이에 맞출지 결정 — 맞추면 교차 철근 간격(운곡 M-8)도 같이 바뀜"),
("C-9", "선행 자동 검사(G1~G5)는 전부 PASS였지만 재료표 치수·형상 대조는 없었음", {"운곡교": "-", "산외교": "수량만 확인", "옥성교": "재료표 미인식", "Ramp-A교": "P1 만 인식"}, "프로세스 관찰 3교",
"산외 '자동으로 찾은 항목이 없습니다', 옥성·Ramp-A 도 같음 — 이번 대조에서 M-n 이 모두 나옴",
"선행 검사는 본수·고정 값 중심이고 도면 표의 치수/길이/커플러/형상 정의는 확인 범위 밖",
"선행 검사에 재료표 길이·치수·커플러 대조를 추가할지 검토"),
]
wb = Workbook()
# ------------------------------------------------ 안내
ws = wb.active
ws.title = "안내"
ws.append(["4개 교량 도면 ↔ 라이노 모델 검토 종합 — 공통 문제 정리"])
ws["A1"].font = Font(bold=True, size=14)
ws.append(["대상: 운곡교 P1 · 산외교(울산) P1 · 옥성교 P1 · Ramp-A교 P1 (작성 2026-10-19, 검토 미확정 초안). 기준 KDS 24 14 21. 범위는 모델↔도면이며 도면 내부 불일치는 이 표에서 뺐다(교량별 보관 파일)."])
ws["A2"].alignment = TOP
ws.merge_cells("A2:G2")
ws.row_dimensions[2].height = 48
ws.append([])
ws.append(["시트", "내용"])
for c in (1, 2):
ws.cell(row=4, column=c).font = Font(bold=True)
ws.cell(row=4, column=c).fill = HF
for a, b in [("공통 문제 요약", "4개 교량에서 겹치거나 반복된 문제 9가지(C-1~C-9) — 교량별 해당 코드, 대표 수치, 추정 원인, 조치 방향"),
("공통 문제 상세(이미지)", "공통 문제별로 교량별 캡처 이미지를 나란히 (도면 ↔ 모델)"),
("교량별 항목 합본", "4개 교량의 모든 불일치 항목 38건 + 어느 공통 문제에 해당하는지"),
("개별 항목", "한 교량에서만 나온 항목 (공통 문제가 아닌 것)"),
("교량 개요", "교량별 구조·검토 범위·본수 일치 여부·파일 위치")]:
ws.append([a, b])
ws.append([])
ws.append(["색: 주황=도면↔모델 / 진한 노랑=기준(KDS)↔모델 / 회색=미확인 · 공통 문제 표의 주황 칸=해당 교량 확정, 연노랑 칸=관련/미검토"])
ws.column_dimensions["A"].width = 26
ws.column_dimensions["B"].width = 120
# ------------------------------------------------ 공통 문제 요약
w1 = wb.create_sheet("공통 문제 요약")
w1.append(["공통 문제 요약 — 4개 교량에서 겹쳐 나온 문제"])
w1["A1"].font = Font(bold=True, size=13)
w1.append(["공통 정도 열: '확정'은 해당 교량 모두에서 확인, '3교/2교'는 그만큼 확인되고 나머지는 해당 없음, '미검토'는 아직 확인 안 함. 추정 원인은 모델 생성 방식에 대한 추정이며 확정이 아님."])
w1["A2"].alignment = TOP
w1.merge_cells("A2:K2")
w1.row_dimensions[2].height = 36
w1.append([])
H = ["번호", "공통 문제", "공통 정도"] + BRIDGES + ["대표 수치·근거", "추정 원인", "확인·조치 방향"]
w1.append(H)
for c in range(1, len(H) + 1):
cell = w1.cell(row=4, column=c)
cell.font = Font(bold=True)
cell.fill = HF
cell.border = B
cell.alignment = CEN
for no, title, br, deg, ev, cause, act in COMMON:
w1.append([no, title, deg] + [br[b] for b in BRIDGES] + [ev, cause, act])
r = w1.max_row
for c in range(1, len(H) + 1):
cell = w1.cell(row=r, column=c)
cell.border = B
cell.alignment = TOP
w1.cell(row=r, column=1).font = Font(bold=True)
for k, b in enumerate(BRIDGES):
v = br[b]
cell = w1.cell(row=r, column=4 + k)
cell.alignment = CEN
if v.startswith("M-"):
cell.fill = YES
elif v not in ("-",):
cell.fill = REL
w1.row_dimensions[r].height = 118
for i, w in enumerate([7, 34, 13, 15, 12, 14, 14, 44, 44, 40], 1):
w1.column_dimensions[get_column_letter(i)].width = w
w1.freeze_panes = "C5"
# ------------------------------------------------ 공통 문제 상세(이미지)
w2 = wb.create_sheet("공통 문제 상세(이미지)")
w2.append(["공통 문제 상세 — 교량별 캡처 (왼쪽 도면 / 오른쪽 모델, 각 이미지는 해당 교량 M-n 이미지)"])
w2["A1"].font = Font(bold=True, size=13)
w2.append([])
w2.append(["번호", "공통 문제"] + BRIDGES)
for c in range(1, 7):
w2.cell(row=3, column=c).font = Font(bold=True)
w2.cell(row=3, column=c).fill = HF
w2.cell(row=3, column=c).border = B
w2.cell(row=3, column=c).alignment = CEN
IMG = {
"C-1": {"운곡교": "M-10_기초_굽힘내면반지름.png", "산외교": "M-7_굽힘내면지름.png", "옥성교": "M-7_굽힘내면지름.png", "Ramp-A교": "M-6_굽힘내면지름.png"},
"C-2": {"운곡교": "M-12_기둥_이음커플러각도.png", "산외교": "M-8_P3P4_이음각도.png", "옥성교": "M-8_P3P4_이음각도.png", "Ramp-A교": "M-7_P2P3_갈고리겹침.png"},
"C-3": {"운곡교": "M-11_P3_갈고리_도면.png", "산외교": "M-2_J길이_미작도.png", "옥성교": "M-2_j길이_미작도.png", "Ramp-A교": "M-1_C7-C14_겹침c_j.png"},
"C-4": {"운곡교": "M-5_C9-C13_길이산식.png", "산외교": "M-3_C7-C13_가산산식.png", "옥성교": "M-4_C8-C13_스터럽.png", "Ramp-A교": "M-2_스터럽폭_가산48.png"},
"C-5": {"산외교": "M-1_일부철근짧음.png", "옥성교": "M-6_C18_단일값.png", "Ramp-A교": "M-3_C8-C14_단일값_총길이.png"},
"C-6": {"산외교": "M-6_주철근커플러476.png", "Ramp-A교": "M-5_커플러115.png"},
"C-7": {"운곡교": "M-13_UA1_모델누락.png", "옥성교": "M-1_UM_조립철근누락.png"},
"C-8": {"운곡교": "M-9_기초_상면피복.png", "산외교": "M-9_기초_철근중심높이.png", "옥성교": "M-9_기초_철근중심높이.png"},
}
IH = 300
for no, title, br, deg, ev, cause, act in COMMON:
if no not in IMG:
continue
w2.append([no, title])
r = w2.max_row
for c in range(1, 7):
cell = w2.cell(row=r, column=c)
cell.border = B
cell.alignment = TOP
w2.cell(row=r, column=1).font = Font(bold=True)
w2.row_dimensions[r].height = IH * 0.75 + 8
for k, b in enumerate(BRIDGES):
fn = IMG[no].get(b)
if not fn:
w2.cell(row=r, column=3 + k, value="(해당 없음 / 이미지 없음)").alignment = CEN
continue
src = BR[b] + "\\" + fn
if not os.path.exists(src):
w2.cell(row=r, column=3 + k, value="이미지 없음: " + fn)
continue
im = Image.open(src).convert("RGB")
h = IH
w = int(im.width * h / im.height)
tmp = TMP + "\\" + no + "_" + b + ".png"
im.resize((w, h), Image.LANCZOS).save(tmp)
xi = XImage(tmp)
xi.width, xi.height = w, h
w2.add_image(xi, get_column_letter(3 + k) + str(r))
w2.column_dimensions[get_column_letter(3 + k)].width = max(w2.column_dimensions[get_column_letter(3 + k)].width or 0, w / 7.0)
w2.column_dimensions["A"].width = 7
w2.column_dimensions["B"].width = 30
w2.freeze_panes = "C4"
# ------------------------------------------------ 교량별 항목 합본
w3 = wb.create_sheet("교량별 항목 합본")
w3.append(["교량별 불일치 항목 합본 (도면 내부 불일치 제외)"])
w3["A1"].font = Font(bold=True, size=13)
w3.append([])
H3 = ["교량", "코드", "마크", "내용", "비교", "차이 크기", "도면 쪽 상태", "확인·조치 필요 사항", "구분", "공통 문제"]
w3.append(H3)
for c in range(1, len(H3) + 1):
cell = w3.cell(row=3, column=c)
cell.font = Font(bold=True)
cell.fill = HF
cell.border = B
cell.alignment = CEN
for b in BRIDGES:
for it in ITEMS[b]:
cm = MAP[b].get(it["code"], "개별")
w3.append([b, it["code"], it["mark"], it["content"], it.get("cmp"), it.get("diff"), it.get("drawing_side"), it.get("action"), it["kind"], cm])
r = w3.max_row
for c in range(1, len(H3) + 1):
cell = w3.cell(row=r, column=c)
cell.border = B
cell.alignment = TOP
w3.cell(row=r, column=9).fill = FILL.get(it["kind"], FILL["미확인"])
if cm != "개별":
w3.cell(row=r, column=10).fill = YES
w3.row_dimensions[r].height = 62
for i, w in enumerate([10, 7, 14, 60, 24, 28, 34, 42, 11, 10], 1):
w3.column_dimensions[get_column_letter(i)].width = w
w3.freeze_panes = "D4"
w3.auto_filter.ref = "A3:J%d" % w3.max_row
# ------------------------------------------------ 개별 항목
w4 = wb.create_sheet("개별 항목")
w4.append(["한 교량에서만 나온 항목 (공통 문제에 해당하지 않음)"])
w4["A1"].font = Font(bold=True, size=13)
w4.append([])
w4.append(["교량", "코드", "마크", "내용", "차이 크기", "구분", "비고"])
for c in range(1, 8):
cell = w4.cell(row=3, column=c)
cell.font = Font(bold=True)
cell.fill = HF
cell.border = B
NOTE = {("옥성교", "M-3"): "C7 바깥 2본 형상 다름 — 모델 생성 방식 문제로 보임", ("산외교", "M-5"): "B8 정사각 7.345 vs 7.300×7.350",
("운곡교", "M-1"): "C14 최하단 점 높이 — 근거 기록 없음", ("운곡교", "M-2"): "C14 훅 방향 도면 표기 없음 (미확인)",
("운곡교", "M-6"): "S1·S2 높이 기준 (받침 쪽)", ("운곡교", "M-7"): "S1·S2 층 순서"}
for b in BRIDGES:
for it in ITEMS[b]:
if MAP[b].get(it["code"], "개별") == "개별":
w4.append([b, it["code"], it["mark"], it["content"], it.get("diff"), it["kind"], NOTE.get((b, it["code"]), "")])
r = w4.max_row
for c in range(1, 8):
w4.cell(row=r, column=c).border = B
w4.cell(row=r, column=c).alignment = TOP
w4.cell(row=r, column=6).fill = FILL.get(it["kind"], FILL["미확인"])
w4.row_dimensions[r].height = 52
for i, w in enumerate([10, 7, 16, 70, 24, 11, 40], 1):
w4.column_dimensions[get_column_letter(i)].width = w
# ------------------------------------------------ 교량 개요
w5 = wb.create_sheet("교량 개요")
w5.append(["교량", "구조·특징", "검토 마크 수", "본수 일치", "불일치 항목 수", "도면 형식", "검토 범위(완료/미실시)", "폴더"])
for c in range(1, 9):
w5.cell(row=1, column=c).font = Font(bold=True)
w5.cell(row=1, column=c).fill = HF
w5.cell(row=1, column=c).border = B
rows5 = [
["운곡교 P1", "2주식, 코핑 C1~C16·S·기초 B1~B12·기둥 P1~P6·받침", "약 40", "일치 (B12 34본은 도면대로가 맞음, 모델 28본 — 사용자 확인)", "%d" % len(ug), "재료표 열 A~J, 정면도·단면도 점 좌표", "재료표·치수·점 좌표·받침·단면까지 완료 (간섭 제외)", ROOT + r"\검토_운곡교P1"],
["산외교(울산) P1", "T형, 확대기초(말뚝 없음), 코핑 C1~C15·기둥 P1~P5", "34", "전부 일치", "%d" % len(sw), "재료표 열 A~J + 형상 도해 I~XII", "재료표·형상 치수 완료 / 위치·피복·받침 미실시", ROOT + r"\검토_산외교P1"],
["옥성교 P1", "T형, 확대기초, 코핑 C1~C20·기둥 P1~P5·받침 10", "46", "UM 92본 제외 전부 일치", "%d" % len(ok), "재료표에 A~J 열 없음 → 철근상세 글자(L= N= a= b= c= j=) 판독", "재료표·철근상세 완료 / 위치·받침 미실시", ROOT + r"\검토_옥성교P1"],
["Ramp-A교 P1", "T형, 말뚝 포함(P1-1·P1-2·P3-2), 코핑 C1~C16", "28", "전부 일치", "%d" % len(ra), "재료표 2장(코핑·기둥/말뚝) + 철근상세", "재료표·철근상세 완료 / 위치·받침·PLATE 미실시", ROOT + r"\Ramp-A교P1"],
]
for r_ in rows5:
w5.append(r_)
rr = w5.max_row
for c in range(1, 9):
w5.cell(row=rr, column=c).border = B
w5.cell(row=rr, column=c).alignment = TOP
w5.row_dimensions[rr].height = 70
for i, w in enumerate([16, 44, 12, 34, 14, 40, 40, 52], 1):
w5.column_dimensions[get_column_letter(i)].width = w
try:
wb.save(OUT)
print("saved", OUT)
except PermissionError:
print("열려 있어 저장 실패")
tot = sum(len(v) for v in ITEMS.values())
print("items", tot, {b: len(v) for b, v in ITEMS.items()})
+180
View File
@@ -0,0 +1,180 @@
import json, re, os
SCR = r"C:\Users\User\AppData\Local\Temp\claude\D--Rhino-MCP-rhino-mcp-main\f46b47bc-9105-4079-9b62-fcbee31b26ae\scratchpad"
tab = json.load(open(SCR + r"\ok_table.json", encoding="utf-8"))
S = json.load(open(SCR + r"\ok_shapes.json", encoding="utf-8"))
Mo = json.load(open(SCR + r"\ok_model_dims.json", encoding="utf-8"))
IM = json.load(open(SCR + r"\ok_images.json", encoding="utf-8"))
_t = {}
for k, v in tab.items():
_t[k] = v
if k == "S4":
_t["UM"] = {"dia": "H22", "L": 4504.0, "n": 92, "tot": 414.368}
tab = _t
D = {}
for e in S:
D.setdefault(e["mark"], e)
HALF = ("P3", "P4")
J = {"C1": "1.510", "C2": "1.510", "C3": "1.510", "C4": "1.510", "C5": "1.510", "C6": "1.510", "C7": "1.050",
"B1": "1.530", "B2": "1.070", "B3": "1.530", "B4": "1.070", "B5": "1.530", "B6": "1.070", "B7": "1.530", "B8": "1.070",
"B10": "0.560×6", "B11": "0.560", "B12": "0.560", "C14": "0.550×4", "C16": "0.550×4", "C17": "0.550"}
KDS = {"C8": "H25", "C9": "H25", "C10": "H25", "C11": "H25", "C12": "H25", "C13": "H25", "C14": "H22", "C16": "H22",
"B9": "H19", "B10": "H19", "B11": "H19", "B12": "H19", "C18": "H19", "C19": "H19", "C20": "H19", "S1": "H19", "S2": "H19"}
KDS_TXT = {"H25": "[M-7] H25 굽힘 내면지름 87.0 < 3.5db 88.9 (1.9 mm 부족).", "H22": "[M-7] H22 굽힘 내면지름 76.8 < 77.7 (0.9 mm 부족).",
"H19": "[M-7] H19 굽힘 내면지름 66.5 < 66.85 (0.35 mm 부족)."}
V = {}
for mk in ("C1", "C2", "C3", "C4", "C5", "C6"):
d = {"C1": "-19.630", "C2": "-19.630", "C3": "-19.630", "C4": "-18.120", "C5": "-18.120", "C6": "-18.120"}[mk]
V[mk] = ("차이 (j)", "[M-2] 모델 설계길이 = a+b+c (재료표 L 은 j 1.510 포함). 모델 총길이 %s m." % d, "도면↔모델")
V["C7"] = ("차이", "[M-3] 바깥 2본(y ±1525) 형상 다름(a·e 606 없음, b·d 4.309). [M-2] j 1.050 제외. 모델 총길이 -17.940 m.", "도면↔모델")
for mk in ("B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8"):
V[mk] = ("일치 (j 형상 없음)", "a·b 일치, 설계길이 = 재료표 L. [M-2] j %s 는 모델 형상에 없음." % J[mk], "도면↔모델")
V["B9"] = ("일치 (경미)", "철근상세 a 586~548 범위 ↔ 모델 567 단일 (재료표 L 4.763 은 567 기준). 길이·본수 일치.", "일치")
V["B10"] = ("일치 (경미)", "a·b 9.448 ↔ 모델 9.445 (3 mm). [M-2] j 0.560×6 은 모델 형상에 없음.", "도면↔모델")
V["B11"] = ("일치 (j 형상 없음)", "a·b 일치. [M-2] j 0.560 은 모델 형상에 없음.", "도면↔모델")
V["B12"] = V["B11"]
for mk in ("C8", "C9", "C10", "C11", "C12", "C13"):
V[mk] = ("차이", ("[M-11] 오각형 스터럽 모따기 d 가 도면보다 33~43 mm 큼. " if mk in ("C9", "C11", "C13") else "") + "[M-10] 코핑 스터럽 상·하단이 도면 선보다 22.5 mm씩 더 나감. [M-4] 철근상세 a·c(630)·d 가 모델 폐합 형상과 다름, 재료표 L 은 b 평균 기준, 모델은 철근마다 b 가 달라 길이 개별.", "도면↔모델")
V["C14"] = ("차이", "[M-5] a 도면 10.475~3.496 = 모델 +54, b 3.207 단일 ↔ 모델 변화. [M-2] j 0.550×4 는 형상에 없음.", "도면↔모델")
V["C16"] = ("차이", "[M-5] a 11.804 = 모델 11.750 +54, b 3.207 ↔ 모델 3.160. [M-2] j 0.550×4 는 형상에 없음.", "도면↔모델")
V["C15"] = ("차이", "[M-5] 모델 직선 3.442~10.421, 도면 b 188 · c 140 끝 갈고리 없음.", "도면↔모델")
V["C17"] = ("차이", "[M-5] 모델 직선 11.750, 도면 a 11.794 · b 188 · c 140 갈고리 없음. j 0.550 형상 없음.", "도면↔모델")
V["C18"] = ("차이", "[M-6] 도면 a 3.050 단일 ↔ 모델 a 3.207/3.126/2.565 (총길이 +14.302 m).", "도면↔모델")
V["C19"] = ("일치", "a 2.226 · b 370 일치.", "일치")
V["C20"] = ("일치 (경미)", "a 2.721~2.472 범위가 모델과 일치, 평균 3.008 ↔ 재료표 L 2.967 (총길이 +3.095 m).", "일치")
V["P1"] = ("일치", "a 8.313 · b 424(호) · c 500 · r 270 일치.", "일치")
V["P2"] = V["P1"]
V["P3"] = ("일치 (각도 미확인)", "a 3.533 일치(호 3532.8, 반원 2조각 = 1본 환산). b 80 끝 연장은 모델 형상에 없음 (형상 합 7065.6 + 160 = 설계길이 7226, [M-2] 유형). [M-8] 이음 각도 도면에 없음.", "미확인")
V["P4"] = ("일치 (각도 미확인)", "a 3.538 일치(호 3538.4, 반원 2조각 = 1본 환산). b 65 끝 연장은 모델 형상에 없음 (형상 합 7076.8 + 129 = 설계길이 7206, [M-2] 유형). [M-8] 이음 각도 도면에 없음.", "미확인")
V["P5"] = ("차이 (c, d)", "a 160 · b 2.341 일치. 끝 c 도면 141 ↔ 모델 150 (+9), d 80 은 모델 형상에 없음 (모델: 90°·135° 갈고리 R24 호). 도면 c·d 정의 확인 필요.", "도면↔모델")
V["S1"] = ("일치", "a 2.629 · b 700 일치.", "일치")
V["S2"] = ("일치", "a 1.262 · b 700 일치.", "일치")
for mk in ("S3", "S4", "S5", "S6", "S7", "S8"):
V[mk] = ("일치", "길이·본수 일치.", "일치")
V["UM"] = ("모델 없음", "[M-1] 기초 조립용 철근. 모델에 0본.", "도면↔모델")
M9 = {"B5": "상면 122.5 (도면 100, +22.5)", "B6": "상면 77.5 (-22.5) · 하면 172.5 (도면 150, +22.5)", "B7": "상면 122.5 (도면 100, +22.5)", "B8": "상면 77.5 (-22.5) · 하면 172.5 (+22.5)"}
for mk, t in M9.items():
v, n_, k_ = V[mk]
V[mk] = ("차이 (높이)", n_ + " [M-9] 철근 중심 높이가 도면 철근선과 다름: " + t + ".", "도면↔모델")
for mk, t in (("B1", "상면 100"), ("B3", "상면 100"), ("B2", "하면 150"), ("B4", "하면 150")):
v, n_, k_ = V[mk]
V[mk] = (v, n_ + " 기초 중심 높이는 도면 선과 일치(" + t + ").", k_)
def m3(x):
return "%.3f" % (x / 1000.0)
rows = []
last_dia = None
n = 0
tot_t = 0.0
tot_m = 0.0
for mk, t in tab.items():
n += 1
verdict, note, kind = V[mk]
if mk in KDS:
note = note + " " + KDS_TXT[KDS[mk]]
m = Mo.get(mk)
d = D.get(mk)
dd = list(d["dims"]) if d else []
if mk in ("C1", "C2", "C3", "C4", "C5", "C6", "C8", "C10", "C12"):
dd = [x for x in dd if not x.startswith("r=")]
if mk == "C7" and d:
dd += d["dims2"]
detail = " ".join(dd) if d and dd else ("(철근상세 치수 없음: 직선)" if mk in ("S3", "S4", "S7", "S8") else "-")
if mk == "UM" and d:
detail = " ".join(d["dims"])
if m:
k = 2 if mk in HALF else 1
cnt = m["n"] // k
dl = [x * k for x in m["dl"]]
mean = sum(dl) / len(dl)
tot = sum(dl) / 1000.0 / k
tot = sum(m["dl"]) / 1000.0
model = "N %d · 설계길이 %s m" % (cnt, m3(mean)) if min(dl) == max(dl) else "N %d · 설계길이 %s~%s m (평균 %s)" % (cnt, m3(min(dl)), m3(max(dl)), m3(mean))
model += " · 총 %.3f m" % tot
vals = m["values"]
if mk not in HALF and vals:
sv = [str(int(x)) for x in vals[:6]]
model += " · 꼭짓점 치수 " + "/".join(sv) + ("…" if len(vals) > 6 else "")
tot_m += tot
else:
model = "모델 없음 (0본)"
tot_t += t["tot"]
if t["dia"] not in ('"', None):
last_dia = t["dia"]
dia = Mo[mk]["dia"] if mk in Mo else last_dia
table = "L %s × N %d = %.3f m" % (m3(t["L"]), t["n"], t["tot"])
rows.append(dict(no=str(n), mark=mk, item="형상·길이·본수 (%s)" % dia, table=table, detail=detail, model=model, verdict=verdict, note=note, kind=kind))
items = [
dict(code="M-1", mark="UM", content="UM(H22, 기초 조립용 철근) 92본 모델에 없음", cmp="재료표·철근상세 ↔ 모델", diff="L 4.504 m × 92본 = 414.368 m 누락", drawing_side="상세·재료표에 있음 (a 200 · b 1.952 · c 200, 70° 사각 설치). 배치 위치·간격은 예시도뿐", action="모델에 넣을지 판단 필요 (운곡교 UA1 과 같은 유형)", kind="도면↔모델", image=IM["M-1"]),
dict(code="M-2", mark="C1~C7, B1~B8, B10~B12, C14·C16·C17", content="j(겹침) 길이가 재료표 L 에만 있고 모델 형상에는 없음. C1~C7 은 모델 설계길이에도 j 제외", cmp="재료표 L = a+b+c+j ↔ 모델 a+b+c", diff="C1~C6 총길이 -113.25 m, C7 -17.94 m. 그 외 마크는 설계길이 포함, 형상에서만 j 만큼 짧음(합 약 600 m)", drawing_side="j 는 상세의 작은 박스로만 표시, 작도 위치 없음", action="형상에 넣을지 가산 길이로만 둘지 판단. C1~C7 만 설계길이에서 j 가 빠진 점 확인", kind="도면↔모델", image=IM["M-2"]),
dict(code="M-3", mark="C7", content="C7 바깥 2본(y ±1525) 형상이 도면(13본 동일)과 다름", cmp="철근상세 C7 ↔ 모델", diff="a·e 606 없음, b·d 4.309 (도면 4.774), 길이 11.280 (나머지 13.424)", drawing_side="N=13 에 형상 하나, L 14.474", action="바깥 2본을 도면 형상대로 맞출지, 의도된 단축인지 확인", kind="도면↔모델", image=IM["M-3"]),
dict(code="M-4", mark="C8~C13", content="스터럽 폭 a(2.057 ↔ 2.103)와 끝 길이 c 630 이 모델 폐합 형상과 다름", cmp="철근상세 ↔ 모델", diff="총길이 C8 -40.8, C9 -86.6, C10 -30.6, C11 -24.4, C12 -41.8, C13 -35.0 m (합 -259 m)", drawing_side="재료표 L 은 b 범위 평균 기준(예 C8 9.570 = 2×2.057 + 2×2.098 + 2×0.630)", action="c 630(겹침·갈고리) 모델 반영 여부, a 2.057 ↔ 2.103 어느 쪽이 맞는지 판단", kind="도면↔모델", image=IM["M-4"]),
dict(code="M-5", mark="C14~C17", content="루프 a 가산 54, b 단일 3.207 ↔ 모델 변화, C15·C17 끝 갈고리(b 188 · c 140) 없음", cmp="철근상세 ↔ 모델", diff="총길이 C14 -22.5, C15 -23.0, C16 -14.4, C17 -30.0 m", drawing_side="C14 a 10.475~3.496, b 3.207, j 0.550×4 / C15 b 188 · c 140", action="갈고리·j 반영 여부, b 단일값 ↔ 모델 변화 판단", kind="도면↔모델", image=IM["M-5"]),
dict(code="M-6", mark="C18", content="a 3.050 단일값 ↔ 모델 3종 (3.207 / 3.126 / 2.565)", cmp="철근상세 ↔ 모델", diff="총길이 +14.302 m (모델 457.732 / 재료표 443.430)", drawing_side="a 3.050 · b 370 · L 3.790 · 117본", action="모델의 개별 길이가 의도인지 확인", kind="도면↔모델", image=IM["M-6"]),
dict(code="M-7", mark="C8~C13, C14·C16, B9~B12, C18~C20, S1·S2", content="굽힘 내면지름: H25 87.0 < 88.9, H22 76.8 < 77.7, H19 66.5 < 66.85 (3.5db)", cmp="KDS 24 14 21 표 4.5-1 ↔ 모델", diff="1.9 / 0.9 / 0.35 mm 부족", drawing_side="도면에 내면반지름 표기 없음", action="모델 R 을 KDS 기준으로 키울지 결정", kind="기준↔모델", image=IM["M-7"]),
dict(code="M-9", mark="B5~B8", content="기초 직각 방향 철근 B5~B8 중심 높이가 도면 철근선(상면 100 아래 / 하면 150 위)과 ±22.5 mm 다름 (B1~B4 는 일치)", cmp="도면 정면도·측면도 철근선 ↔ 모델", diff="상면 122.5/77.5, 하면 172.5 (±22.5)", drawing_side="철근 선은 상면 -100 / 하면 +150 같은 높이. ※ 단, 도면 측면 단면의 철근 점(원)은 모델과 같은 높이(하면 321527.3, 상면 323432.3)에 그려져 있고 정면 단면 점과 22.5 다름 (선·점이 도면 안에서 다름)", action="모델 철근을 도면 선 높이에 맞출지 결정 (지시된 적 없는 모델 쪽 가정). 점 기준으로는 모델 = 도면 — 도면 내부 불일치 여부 판단 필요", kind="도면↔모델", image=IM["M-9"], image2=IM.get("M-9b")),
dict(code="M-10", mark="C8~C13", content="코핑 스터럽 상·하단이 도면 정면도 철근선보다 22.5 mm씩 더 나감 (106곳 전부 동일)", cmp="도면 정면도 수직선 ↔ 모델 중심선", diff="상단 +22.5 (피복 얕아짐), 하단 -22.5", drawing_side="상·하 끝이 철근선 위치로 작도 (예 C10 상단 코핑 상면 -120), 어긋나게 하라는 지시 없음", action="모델 스터럽 상·하단을 도면 선에 맞출지 결정 (M-9 와 같은 22.5 mm)", kind="도면↔모델", image=IM["M-10"]),
dict(code="M-11", mark="C9, C11, C13", content="오각형 스터럽 모따기 아래 평면부 d 가 도면 철근상세보다 33~43 mm 큼, C11 b 최대 +54", cmp="철근상세 범위 ↔ 모델 꼭짓점 치수", diff="d +33~43, b +54, c2 +5~6, a -4 (mm)", drawing_side="범위(~)로만 표기, 철근별 값 없음", action="모따기 d·b 를 도면 범위대로 맞출지 확인 (M-4 와 같은 계열)", kind="도면↔모델", image=IM["M-11"]),
dict(code="M-12", mark="B12", content="기초 중앙 B12(H19, x방향) 중간층 높이가 도면 점보다 19.0 mm 높음 (B11 은 일치, 도면은 B11·B12 같은 높이)", cmp="도면 정면·측면 단면 점 ↔ 모델 중심선", diff="+19.0 (z 322023.8/323023.8 ↔ 322004.8/323004.8)", drawing_side="B11·B12 모두 바닥 위 650·1650 에 점으로 작도", action="모델 B12 를 도면 점 높이에 맞출지 결정 (B11 과 간섭 피해 올린 것으로 보임, 지시된 적 없는 가정 — M-9 와 같은 유형)", kind="도면↔모델"),
dict(code="M-8", mark="P3, P4", content="링 이음(커플러) 각도·위치 도면에 기준 없음 (모델 90°/270° 가정)", cmp="도면 없음 ↔ 모델 가정", diff="-", drawing_side="철근상세 a·b 뿐, 방향·엇갈림 없음", action="각도 기준 확인 (설계자)", kind="미확인", image=IM["M-8"]),
]
CHECKS = [
["재료표","본수(전 마크)","재료표 N","모델 본수","일치 (UM 제외)","UM 92본 모델 없음 (M-1)"],
["재료표","길이·총길이·형상 치수","재료표 L·철근상세 a b c j","모델 설계길이·꼭짓점 치수","불일치 (M-1~M-6)","46마크 중 30마크 차이"],
["CTC","기초 상면 B1+B3 (x)","2@100 + 72@125 + 2@100 = 77본","77본, 위치 차이 0.0 mm","일치",""],
["CTC","기초 하면 B2+B4 (x)","2@100 + 72@125 + 2@100 = 77본","77본, 0.0 mm","일치",""],
["CTC","기초 상면 B5+B7 (y)","2@163 + 70@125 + 2@163 = 75본","75본, 0.0 mm","일치","2@163 = 162.5"],
["CTC","기초 하면 B6+B8 (y)","2@163 + 70@125 + 2@163 = 75본","75본, 0.0 mm","일치",""],
["CTC","기초 중앙 B12 (x)","18@500=9.000 (19본)","19본, 0.0 mm","일치",""],
["CTC","기초 중앙 B11 (y)","16@500=8.000 (17본)","19본 (±4537.5 에 2본 추가), 재료표 N=19","확인 필요","도면 치수는 17본, 재료표는 19본 → 도면 내부 (보관 D-6)"],
["CTC","코핑 스터럽 x (C8+C10+C12)","10@150 + 85@100 + 10@150 = 106본","106본, 도면 수직선과 0.4 mm 이내","일치","도면 원점 기준 +25 비대칭 패턴까지 같음"],
["CTC","코핑 단별 y (1~4단 C1+C4, C2+C5, C3+C6, C7+C19)","22@125 + 가장자리 150/125 = 25본","25본씩, 0.0 mm","일치","단면 A-A·B-B"],
["CTC","기둥 띠철근 P3·P4 구간","2@300 / 16@150 / 8@300 / 2@600 (z)","P3 13개, P4 16개 위치가 구간과 일치","일치","P3 14375 포함 17개 = 16@150"],
["형상","코핑 오각형 스터럽 C9·C11·C13 (a b c d c1 c2)","철근상세 범위 치수","모델 꼭짓점 치수","불일치 (M-11)","d +33~43, C11 b +54"],
["재료표","표 산술 (행 L×N, 소계, 단위중량, 총계)","L×N=총길이, 직경별 합 = 소계, 중량","검산","일치","옥성교·산외교·Ramp-A 모두 산술 오류 없음"],
["CTC","기둥 띠철근 P5 CTC","C.T.C 300 / 150 / 300 / 600","z 간격 300 (구간별)","일치 (대략)","구간별 세부 위치는 미확인"],
["피복","기초 B1~B4 (상면/하면)","상면 100 / 하면 150","100 / 150","일치",""],
["피복","기초 B5~B8","상면 100 / 하면 150","122.5 · 77.5 / 172.5","불일치 (M-9)","±22.5"],
["피복","기초 B11 중간층 (y방향 점)","도면 점 z 322004.8 / 323004.8 (바닥 위 650·1650)","322004.8 / 323004.8","일치","19본, 위치도 일치"],
["피복","기초 B12 중간층 (x방향 점)","도면 점 z 322004.8 / 323004.8 (B11 과 같은 높이)","322023.8 / 323023.8 (+19.0)","불일치 (M-12)","x 위치 -4500~4500 @500 19본은 일치, 높이만 +19 (H19 지름)"],
["점대조","기초 정면·측면 단면 점 (B1~B8, B10, B11)","도면 점 183 / 184개 (z·위치)","모델 직선 끝 위치와 3 mm 이내 일치","일치","B12 제외. 정면 상·하 77본, 측면 상·하 75본 (±4700)"],
["점대조","기둥 단면 C-C~F-F 점","60점 r 1080 + 60점 r 1125 / 띠철근 링 r 1148","P2 r 1080 (60), P1 r 1125 (60), 띠 P3 1150·P4 1147","일치","띠철근 ±2 mm"],
["점대조","코핑 단면 B-B 점 (C1~C7·C14~C17·C19·C20·S)","도면 점 173개","173개 전부 위치·높이 일치","일치","코핑 단면 높이·피복 체인 포함"],
["점대조","코핑 단면 A-A 점","도면 점 137개","124개 일치, C7 바닥 13점(z 19755.1, y ±1525 @250) 불일치","미확인","모델 C7 은 코핑 평면 끝이 사각이라 y 별 높이가 다름 — 절단 위치 확정 필요"],
["피복","코핑 스터럽 C8~C13 상·하단","도면 수직선 끝","상 +22.5 / 하 -22.5","불일치 (M-10)","106곳 전부"],
["피복","코핑 최상단 단 (C19 120)","코핑 상면 -120","120.0","일치",""],
["피복","기둥 주철근 반지름","DIVISION 60, 125+1125+1125+125","P1 1125 (60본), P2 1080 (60본)","일치","P2 는 안쪽 링"],
["피복","코핑 C1~C7 단별 깊이","단면 B-B 점 (치수 체인 대신 점 좌표)","C1~C7 점 모두 일치","일치 (B-B)","A-A 는 C7 바닥 13점만 미확인"],
["피복","코핑 C14~C17 루프·타이, S 받침 철근","단면 B-B·A-A 점","B-B 일치, A-A 일치 (C7 외)","일치","S 받침 철근은 점 있는 부분만"],
["피복","기초 측면 피복(x,y)","양 끝 100(상면/중앙/하면)","100","일치","B 철근 중심 기준"],
]
cfg = dict(image2_header="캡처 2 (M-9: 모델 기초 콘크리트 + 철근 중심 피복 치수)", checks=CHECKS, checks_desc="도면 DXF 의 작도 선·치수와 모델 철근 중심선을 좌표로 비교 (2026-10-19). 일치·불일치·미확인 구분. 판정 보류(기록만).",
bridge="옥성교 P1",
title="옥성교 P1 철근 도면 ↔ 모델 일치 검토 (불일치 종류별 분류)",
purpose="목적: 도면(재료표·철근상세)과 라이노 모델이 일치하는지가 최우선. 범위는 모델↔도면 (도면 내부 불일치는 따로 보관). 작성 2026-10-19, 검토 미확정 초안. 기준: KDS 24 14 21.",
sheet1_desc="도면에 기준이 있거나 없는데 모델이 다르게 만들어진 항목 (M-n). 캡처: 위 도면(철근상세·재료표) / 아래 모델(라이노).",
items=items, rows=rows,
unchecked=[
dict(item="코핑·기둥·기초 철근 위치", content="정면도·단면도의 철근 높이·간격·피복(점 좌표)과 모델 위치 대조", note="재료표·철근상세(형상·길이·본수) 대조만 완료", status="미검토"),
dict(item="받침 세트 10개", content="받침대좌·패드·교량받침·솔플레이트 (ELEVATION TABLE · 솔플레이트 TABLE) 모델 대조", note="선행 보고서 G5 183/183 PASS (표 값 기준)", status="미검토"),
dict(item="코핑 철근 간 간섭", content="철근끼리 교차·관통", note="사용자 지시로 제외 (검토 단계)", status="제외"),
dict(item="P5 갈고리 c 141 · d 80", content="철근상세의 끝 길이 정의가 모델(90°·135° 갈고리)과 맞춰지지 않음", note="도면으로 확인 불가", status="확인 불가"),
],
basis=[
dict(item="입력 파일", content="모델: 옥성교P1_G5.3dm (원본 그대로), 도면: 옥성교P1.dwg → 옥성교P1.dxf (라이노 변환)"),
dict(item="재료표", content="DXF 레이어 CS-TABL-TEX2 의 글자를 x(열)·y(행)로 읽음 (번호 x≈715129, 직경 715879, 길이 717004, 개수 717629, 총길이 719004). 재료표에는 A~J 치수 열이 없음 — 치수는 '철근상세'(레이어 CS-TEXT, 형상 도해 옆 L= N= a= b= c= j=)에서 읽음"),
dict(item="모델 치수", content="중심선(P1-RC-*)의 직선 구간을 연장한 꼭짓점 사이 거리(각진 치수)로 환산. P1·P2·P3·P4 는 호 길이/접선 길이로 비교. P3·P4 는 반원 2조각 = 1본으로 환산"),
dict(item="길이 비교", content="모델 철근의 설계길이(user string design_length + coupler_length)와 재료표 L·총길이를 대조. 형상 실길이(중심선)는 굽힘으로 약간 짧아 비교하지 않음"),
dict(item="KDS 계산", content="내면지름 = 2×(중심선 R − 공칭 db/2). H25 R 56.2 → 87.0, H22 49.5 → 76.8, H19 42.8 → 66.5, H29 65.2 → 101.8, H32 72 → 112.2, H16 24 → 32.1, H13 해당 없음. 기준: D19 이상 3.5db, D16 이하 2db (KDS 24 14 21 표 4.5-1)"),
dict(item="재료표 총계", content="도면 총계 13806.206 m (소계 합). 모델 설계길이 합 %.3f m (UM 제외 시 도면 %.3f m)" % (tot_m, tot_t - tab["UM"]["tot"])),
],
)
json.dump(cfg, open(SCR + r"\ok_cfg.json", "w", encoding="utf-8"), ensure_ascii=False, indent=1)
print(len(rows), tot_t, tot_m)
@@ -0,0 +1,56 @@
# 옥성교 재료표 처음부터: DXF 재판독 → ok2_table.json, ok2_shapes.json
import sys, re, json, collections
sys.path.insert(0,'pylib')
import ezdxf
F=r"D:\★Pier 자료\심수석님 검토 파일\검토_옥성교P1\옥성교P1.dxf"
d=ezdxf.readfile(F); m=d.modelspace()
FONT=re.compile(chr(92)*2+'f[^;]*;')
texts=[]; tags=[]
def walk(ents,depth=0):
for e in ents:
t=e.dxftype()
if t=='TEXT': texts.append((e.dxf.insert.x,e.dxf.insert.y,FONT.sub('',e.dxf.text).replace('{','').replace('}','').strip(),e.dxf.layer))
elif t=='MTEXT':
s=FONT.sub('',e.text).replace('{','').replace('}','').replace(chr(92)+'P',' / ')
texts.append((e.dxf.insert.x,e.dxf.insert.y,s.strip(),e.dxf.layer))
elif t=='INSERT':
if e.dxf.name.startswith('CSBNUM1'):
tags.append((e.dxf.insert.x,e.dxf.insert.y,dict((a.dxf.tag,a.dxf.text.strip()) for a in e.attribs)))
walk(m)
print('texts',len(texts),'tags',len(tags))
# ---- 재료표
rows=collections.defaultdict(dict)
for x,y,t,l in texts:
if l=='CS-TABL-TEX2' and x>=715000 and 324000<y<340000: rows[round(y/10)][round(x)]=t
tab=[]; dia=None
for y in sorted(rows,reverse=True):
r=rows[y]
mk=r.get(715129)
if mk and (re.match(r'^[A-Z]{1,2}\d+$',mk) or mk=='UM'):
dd=r.get(715879)
if dd=='"': dd=dia
dia=dd
tab.append({'mark':mk,'dia':dd,'L':float(r[717004])*1000,'n':int(r[717629]),'tot':float(r[719004]),'y':y*10,'raw':r})
print('표 행',len(tab))
json.dump(tab,open('ok2_table.json','w',encoding='utf-8'),ensure_ascii=False,indent=0)
# ---- 철근상세
Lt=[t for t in texts if t[2].replace(' ','').startswith('L=') and t[3]=='CS-TEXT']
print('Lt',len(Lt), Lt[:2], collections.Counter(t[3] for t in Lt))
det=[]
for xt,yt,a in tags:
mk=a.get('RB_NO');
if not mk: continue
# L= 글자: 태그 오른쪽 x+1700..+2100, y 차이 <60
cand=[t for t in Lt if xt+1600<t[0]<xt+2200 and abs(t[1]-yt)<60]
if not cand: det.append({'mark':mk,'dia':a.get('RB_DIA'),'note':'L 글자 없음','x':xt,'y':yt}); continue
L=cand[0]
N=[t for t in texts if re.match(r'N\s*=',t[2]) and abs(t[1]-L[1])<5 and L[0]<t[0]<L[0]+3000]
row=[t for t in texts if abs(t[1]-(L[1]-200))<5 and xt+900<t[0]<xt+5900 and re.match(r'^[a-zA-Z]\d?\s*=',t[2])]
row.sort(key=lambda t:t[0])
row2=[t for t in texts if abs(t[1]-(L[1]-400))<5 and xt+900<t[0]<xt+5900 and re.match(r'^[a-zA-Z]\d?\s*=',t[2])]
det.append({'mark':mk,'dia':a.get('RB_DIA'),'L':L[2],'N':N[0][2] if N else None,'dims':[t[2] for t in row],'dims2':[t[2] for t in sorted(row2,key=lambda t:t[0])],'x':L[0],'y':L[1]})
json.dump(det,open('ok2_shapes.json','w',encoding='utf-8'),ensure_ascii=False,indent=0)
print('상세',len(det),'마크',len(set(e['mark'] for e in det)))
cs=[t for t in texts if t[3]=='CS-TEXT']
print('CS-TEXT',len(cs))
open('dbg_cs.txt','w',encoding='utf-8').write('\n'.join(repr(t[2]) for t in cs[:15]))
@@ -0,0 +1,67 @@
import sys, json, re, io, collections, math
sys.path.insert(0,'pylib')
import rhino3dm
tab=json.load(open('ok2_table.json',encoding='utf-8'))
det=[e for e in json.load(open('ok2_shapes.json',encoding='utf-8')) if 'L' in e]
Mo=json.load(open('ok_model_dims.json',encoding='utf-8'))
UW={'H35':7.51,'H32':6.23,'H29':5.04,'H25':3.98,'H22':3.04,'H19':2.25,'H16':1.56,'H13':0.995}
# 모델 곡선 실제 길이
m3=rhino3dm.File3dm.Read(r"D:\★Pier 자료\심수석님 검토 파일\검토_옥성교P1\옥성교P1_G5.3dm")
geo=collections.defaultdict(list)
for o in m3.Objects:
g=o.Geometry; nm=o.Attributes.Name or ''
if nm.startswith('P1-RC-') and hasattr(g,'GetLength'):
us=dict(o.Attributes.GetUserStrings()); mk=us.get('mark') or nm.split('-')[2]
geo[mk].append(g.GetLength())
def mm(s):
out=[]
for p in s.split('~'):
p=re.sub(r'[^\d.]','',p)
if not p: continue
v=float(p); out.append(v*1000 if '.' in p and v<100 else v)
return out
D=collections.defaultdict(list)
for e in det: D[e['mark']].append(e)
o=io.open('ok_rerun2.txt','w',encoding='utf-8')
res={}
for t in tab:
mk=t['mark']; r={'mark':mk,'issues':[]}
# 표 산술
if abs(t['L']*t['n']/1000.0-t['tot'])>0.0015*max(1,t['n']): r['issues'].append(u'표 산술 L×N≠총길이 (%.3f×%d=%.3f ↔ %.3f)'%(t['L']/1000,t['n'],t['L']*t['n']/1e6,t['tot']))
m=Mo.get(mk)
if not m:
r['issues'].append(u'모델에 없음'); res[mk]=r; o.write(u'%s 표 n=%d L=%.0f → 모델 없음\n'%(mk,t['n'],t['L'])); continue
half=2 if mk in('P3','P4') else 1
n=m['n']//half
if n!=t['n']: r['issues'].append(u'본수 표%d/모델%d'%(t['n'],n))
dl=[v*half for v in m['dl']]; mean=sum(dl)/len(dl); tot=sum(dl)/1000.0/half*half
tot_m=sum(m['dl'])/1000.0
r['L']=(t['L'],mean,min(dl),max(dl)); r['tot']=(t['tot'],tot_m)
# 길이 판정
dev=mean-t['L']
if abs(dev)>3 or max(dl)-min(dl)>3: r['issues'].append(u'길이 표%.0f 모델평균%.0f (%.0f~%.0f) %+.0f'%(t['L'],mean,min(dl),max(dl),dev))
# 실제 곡선 길이 vs 설계길이
gl=geo.get(mk,[])
if gl:
gm=sum(gl)/len(gl)*(half)
r['geo']=gm
if gm<mean*0.97: r['issues'].append(u'형상 실길이 %.0f < 설계길이 %.0f (j 등 미작도 %.0f)'%(gm,mean,mean-gm))
# 철근상세 치수
miss=[]
dd=D.get(mk,[])
for e in dd[:1]:
for s in e['dims']:
k,_,v=s.partition('='); k=k.strip()
if k.startswith('j') or k.startswith('x'): continue
for val in mm(v):
if not any(abs(val-q)<=3 for q in m['values']): miss.append('%s=%s'%(k,v))
if miss: r['issues'].append(u'상세 치수 모델에 없음 %s'%miss)
r['dia']=(t['dia'],m['dia']);
if t['dia']!=m['dia']: r['issues'].append(u'직경 표%s/모델%s'%(t['dia'],m['dia']))
res[mk]=r
o.write(u'%s %s n표%d/모 %d | L표 %.0f 모델 %.0f(%.0f~%.0f) 실길이 %s | 총 %.3f/%.3f | 도면 %s | %s\n'%(mk,t['dia'],t['n'],n,t['L'],mean,min(dl),max(dl),('%.0f'%r['geo']) if 'geo' in r else '-',t['tot'],tot_m,(' '.join(dd[0]['dims']) if dd else '-'),' ; '.join(r['issues']) if r['issues'] else '일치'))
json.dump(res,open('ok_rerun2.json','w',encoding='utf-8'),ensure_ascii=False)
bad=[k for k,v in res.items() if v['issues']]
o.write(u'\n문제 있는 마크 %d / %d: %s\n'%(len(bad),len(res),bad))
o.close()
print(len(bad),len(res))
@@ -0,0 +1,33 @@
import sys, json, collections, math, io
sys.path.insert(0,'pylib')
import rhino3dm
m3=rhino3dm.File3dm.Read(r"D:\★Pier 자료\심수석님 검토 파일\검토_옥성교P1\옥성교P1_G5.3dm")
o=io.open('ok_rerun3.txt','w',encoding='utf-8')
want=('P1','P2','P3','P4','P5','B9','C8','C10','C12','C20','C18')
S=collections.defaultdict(list)
for ob in m3.Objects:
nm=ob.Attributes.Name or ''
g=ob.Geometry
if not nm.startswith('P1-RC-') or isinstance(g,rhino3dm.Brep): continue
p=nm.split('-'); mk=p[2]
if mk not in want: continue
segs=[g.SegmentCurve(i) for i in range(g.SegmentCount)] if hasattr(g,'SegmentCount') else [g]
desc=[]
for s in segs:
t=type(s).__name__
a=s.PointAtStart; b=s.PointAtEnd
ch=math.sqrt((a.X-b.X)**2+(a.Y-b.Y)**2+(a.Z-b.Z)**2)
if t=='ArcCurve':
desc.append(('A',round(s.Arc.Radius,1),round(s.Arc.AngleDegrees,1),round(s.Arc.Length,1)))
else: desc.append(('L',round(ch,1)))
S[mk].append(desc)
for mk in want:
L=S.get(mk,[])
if not L: continue
lines=collections.Counter(); arcs=collections.Counter()
for d in L:
for e in d:
if e[0]=='L': lines[round(e[1])]+=1
else: arcs[(e[1],e[2],e[3])]+=1
o.write(u'%s n=%d 직선 길이 분포 %s\n 호 %s\n'%(mk,len(L),sorted(lines.items())[:25],sorted(arcs.items())[:10]))
o.close()
+57
View File
@@ -0,0 +1,57 @@
import sys; sys.path.insert(0,"."); sys.path.insert(0,"pylib")
import pickle, collections, swdots, io, rhino3dm
segs,texts=pickle.load(open('cache_옥성교P1.dxf.pkl','rb')); swdots.segs=segs
m3=rhino3dm.File3dm.Read(r"D:\★Pier 자료\심수석님 검토 파일\검토_옥성교P1\옥성교P1_G5.3dm")
S=[]
for ob in m3.Objects:
g=ob.Geometry
if isinstance(g,rhino3dm.Brep) or not hasattr(g,'PointAtStart'): continue
us=dict(ob.Attributes.GetUserStrings()); mk=us.get('mark')
if not mk or mk[0] not in 'CS': continue
cs=[g.SegmentCurve(i) for i in range(g.SegmentCount)] if hasattr(g,'SegmentCount') else [g]
for c in cs:
a=c.PointAtStart; b=c.PointAtEnd
if abs(b.Y-a.Y)<1 and abs(b.Z-a.Z)<1 and abs(b.X-a.X)>300: S.append((mk,round(a.Y,1),round(a.Z,1),ob.Attributes.Name))
o=io.open('okab.txt','w',encoding='utf-8')
o.write(u'모델 x방향 직선 %d: %s\n'%(len(S),collections.Counter(s[0] for s in S)))
for nm,(x0,x1,y0,y1) in (('A-A',(604500,612900,332000,339500)),('B-B',(611500,621000,332000,340000))):
D=[d for d in swdots.dots(x0,x1,y0,y1,maxlen=60)]
sz=collections.Counter((d[2],d[3]) for d in D); o.write(u'%s 점 크기 %s\n'%(nm,sz.most_common(8)))
P=[d for d in D if d[2]==d[3] and d[2] in (22.5,)]
o.write(u' 22.5 점 %d\n'%len(P))
best=None
for sg in (1,-1):
votes=collections.Counter()
for d in P:
for mk,y,z,n in S:
votes[(round((d[0]-sg*y)),round(d[1]-z))]+=1
(ox,oz),n=votes.most_common(1)[0]
o.write(u' 부호 %d 최다 득표 오프셋 (%d,%d) 득표 %d\n'%(sg,ox,oz,n))
if best is None or n>best[0]: best=(n,sg,ox,oz)
n,sg,ox,oz=best
o.write(u' → 부호 %d 오프셋 (%d, %d)\n'%(sg,ox,oz))
hit=0; un=[]
for d in P:
ok=[s for s in S if abs(d[0]-(ox+sg*s[1]))<3 and abs(d[1]-(oz+s[2]))<3]
if ok: hit+=1
else: un.append((round(d[0]-ox,1),round(d[1]-oz,1)))
o.write(u' 도면 점 %d 중 모델 일치 %d, 불일치 %d\n'%(len(P),hit,len(un)))
c=collections.Counter(z for y,z in un); o.write(u' 불일치 도면 점 높이별(z) %s\n'%sorted(c.items())[:30])
pts=set((round(d[0]-ox,1),round(d[1]-oz,1)) for d in P)
miss=[s for s in S if not any(abs(sg*s[1]-px)<3 and abs(s[2]-pz)<3 for px,pz in pts)]
o.write(u' 모델 직선 중 도면 점 없음 %d: %s\n'%(len(miss),sorted(collections.Counter((m[0],round(m[2],1)) for m in miss).items())[:30]))
o.write(u'\n=== 불일치 상세 ===\n')
M14=collections.defaultdict(list)
for mk,y,z,n in S:
if mk in ('C14','C16'): M14[(mk,round(z,1))].append(y)
for k,v in sorted(M14.items()): o.write(u'모델 %s z %.1f y %s\n'%(k[0],k[1],sorted(v)))
for nm,(x0,x1,y0,y1,ox,oz) in (('A-A',(604500,612900,332000,339500,608643,314067)),('B-B',(611500,621000,332000,340000,616068,314815))):
D=[d for d in swdots.dots(x0,x1,y0,y1,maxlen=60) if d[2]==22.5 and d[3]==22.5]
U=[]
for d in D:
if not any(abs(d[0]-(ox+s[1]))<3 and abs(d[1]-(oz+s[2]))<3 for s in S): U.append((round(d[1]-oz,1),round(d[0]-ox,1)))
byz=collections.defaultdict(list)
for z,y in U: byz[z].append(y)
o.write(u'%s 불일치 점: \n'%nm)
for z,ys in sorted(byz.items()): o.write(u' z %.1f y %s\n'%(z,sorted(ys)))
o.close()
+13
View File
@@ -0,0 +1,13 @@
import sys; sys.path.insert(0,"."); sys.path.insert(0,"pylib")
import pickle, collections, swdots, json
segs,texts=pickle.load(open('cache_옥성교P1.dxf.pkl','rb')); swdots.segs=segs
D=[d for d in swdots.dots(568500,578600,321300,324000) if d[2]==22.5 and d[3]==22.5 and d[1] in (322004.8,323004.8)]
dd=collections.defaultdict(list)
for d in D: dd[d[1]].append(round(d[0]-573552,1))
for k,v in sorted(dd.items()): print(k,len(v),sorted(v))
B=json.load(open('okpos.json',encoding='utf-8'))
for m in ('B11','B12'):
zs=collections.Counter((round(b['bb'][2]+309179.8,1),round(b['bb'][5]+309179.8,1),b['dia'],b['shape']) for b in B[m]); print(m,dict(zs))
xs=collections.defaultdict(list)
for b in B[m]: xs[round(b['bb'][2]+309179.8,1)].append((round((b['bb'][0]+b['bb'][3])/2,1),round((b['bb'][1]+b['bb'][4])/2,1)))
for k,v in xs.items(): print(' ',k,sorted(v)[:20])
@@ -0,0 +1,15 @@
import sys; sys.path.insert(0,"."); sys.path.insert(0,"pylib")
import pickle, collections, swdots, math, numpy as np, io
segs,texts=pickle.load(open('cache_옥성교P1.dxf.pkl','rb')); swdots.segs=segs
o=io.open('okcol4.txt','w',encoding='utf-8')
def fit(pts):
A=np.array([[2*x,2*y,1] for x,y in pts]); b=np.array([x*x+y*y for x,y in pts])
s=np.linalg.lstsq(A,b,rcond=None)[0]; cx,cy=s[0],s[1]; return cx,cy,math.sqrt(s[2]+cx*cx+cy*cy)
for nm,(cx0,cy0) in {'C-C':(624030,335430),'D-D':(631600,335430),'E-E':(639490,335430),'F-F':(608700,322950)}.items():
D=swdots.dots(cx0-1700,cx0+1700,cy0-1700,cy0+1700,maxlen=60)
sz=collections.Counter((d[2],d[3]) for d in D); o.write(u'%s dots %s\n'%(nm,sz.most_common(6)))
P=[(d[0],d[1]) for d in D if d[2]==22.5 and d[3]==22.5]
if len(P)>10:
cx,cy,r=fit(P); rr=[math.hypot(x-cx,y-cy) for x,y in P]
o.write(u' 22.5점 %d개 중심 (%.1f,%.1f) r %.1f (min %.1f max %.1f)\n'%(len(P),cx,cy,r,min(rr),max(rr)))
c=collections.Counter(round(v/10)*10 for v in rr); o.write(u' 군집 %s\n'%sorted(c.items()))
@@ -0,0 +1,16 @@
import sys; sys.path.insert(0,".")
import pickle, math, collections, json
import swdots
segs,texts=pickle.load(open('cache_옥성교P1.dxf.pkl','rb'))
swdots.segs=segs
B=json.load(open('okpos.json',encoding='utf-8'))
D=swdots.dots(569700,577400,321300,324000)
print(len(D),collections.Counter((d[2],d[3]) for d in D).most_common(6))
lev=collections.defaultdict(list)
for d in D:
if d[2]<=60: lev[(d[2],d[1])].append(d[0]-573552)
for k,v in sorted(lev.items(),key=lambda t:(t[0][1],t[0][0])): print(k,len(v),sorted(v)[:3])
print('--model')
for m in ['B1','B2','B3','B4','B5','B6','B7','B8']:
if m in B:
zs=collections.Counter(round(b['bb'][2]+309179.8,1) for b in B[m]); print(m,B[m][0]['dia'],dict(zs))
@@ -0,0 +1,15 @@
import sys; sys.path.insert(0,".")
import pickle, collections, json
import swdots
segs,texts=pickle.load(open('cache_옥성교P1.dxf.pkl','rb'))
swdots.segs=segs
B=json.load(open('okpos.json',encoding='utf-8'))
for nm,(x0,x1,cx) in (('front',(569700,577400,573552)),('side',(588700,596400,592541))):
D=swdots.dots(x0,x1,321300,324000)
lev=collections.defaultdict(list)
for d in D:
if d[2]==22.5: lev[d[1]].append(round(d[0]-cx,1))
print(nm,[(k,len(v)) for k,v in sorted(lev.items())])
# model centers of z
for m in ['B1','B2','B3','B4','B5','B6','B7','B8']:
zs=collections.Counter(round((b['bb'][2]+b['bb'][5])/2+309179.8,1) for b in B[m]); print(m,B[m][0]['dia'],B[m][0]['shape'],dict(zs))
@@ -0,0 +1,59 @@
import sys; sys.path.insert(0,"."); sys.path.insert(0,"pylib")
import pickle, collections, io, math, json
import rhino3dm, swdots
segs,texts=pickle.load(open('cache_옥성교P1.dxf.pkl','rb')); swdots.segs=segs
m3=rhino3dm.File3dm.Read(r"D:\★Pier 자료\심수석님 검토 파일\검토_옥성교P1\옥성교P1_G5.3dm")
OFF=309179.8
S=[] # (mark, p0, p1)
for ob in m3.Objects:
g=ob.Geometry
if isinstance(g,rhino3dm.Brep) or not hasattr(g,'SegmentCount'): continue
us=dict(ob.Attributes.GetUserStrings()); mk=us.get('mark')
if not mk or not mk.startswith('B'): continue
for i in range(g.SegmentCount):
c=g.SegmentCurve(i); a=c.PointAtStart; b=c.PointAtEnd
if type(c).__name__.startswith('Line') or abs((a.X-b.X))+abs(a.Y-b.Y)+abs(a.Z-b.Z)>0:
S.append((mk,(a.X,a.Y,a.Z),(b.X,b.Y,b.Z)))
o=io.open('okfoot.txt','w',encoding='utf-8')
o.write(u'B 선분 %d, 마크 %s\n'%(len(S),sorted(set(s[0] for s in S))))
for nm,(x0,x1,cx,axis) in (('정면(y 방향으로 보는 철근 끝)',(568500,578600,573552,1)),('측면(x 방향)',(587500,597600,592541,0))):
D=[d for d in swdots.dots(x0,x1,321300,324000) if d[2]==22.5 and d[3]==22.5]
P=collections.defaultdict(set)
other=1-axis if axis==1 else 1
for mk,a,b in S:
# 보는 축과 평행한 직선 (다른 두 축 변화 < 1)
d=[abs(b[i]-a[i]) for i in range(3)]
if d[axis]>800 and sum(d)-d[axis]<2:
pc=0 if axis==1 else 1
P[(round(a[pc],1),round(a[2]+OFF,1))].add(mk)
# 부호 시험
best=None
for sg in (1,-1):
mp={(round(sg*k[0],1),k[1]):v for k,v in P.items()}
hit=0
for dd in D:
if any(abs(dd[0]-cx-px)<3 and abs(dd[1]-py)<3 for (px,py) in mp): hit+=1
if best is None or hit>best[0]: best=(hit,sg)
sg=best[1]; mp={(round(sg*k[0],1),k[1]):v for k,v in P.items()}
o.write(u'== %s: 도면 점 %d, 모델 끝이 보이는 직선 %d, 부호 %d, 도면 점 중 모델 일치 %d\n'%(nm,len(D),len(mp),sg,best[0]))
un=[(round(dd[0]-cx,1),dd[1]) for dd in D if not any(abs(dd[0]-cx-px)<3 and abs(dd[1]-py)<3 for (px,py) in mp)]
c=collections.Counter(y for x,y in un); o.write(u' 모델과 안 맞는 도면 점 %d: 높이별 %s\n'%(len(un),sorted(c.items())))
dp=set((round(dd[0]-cx,1),dd[1]) for dd in D)
um=[(k,v) for k,v in mp.items() if not any(abs(k[0]-x)<3 and abs(k[1]-y)<3 for x,y in dp)]
c2=collections.Counter((tuple(sorted(v)),k[1]) for k,v in um); o.write(u' 도면 점 없는 모델 위치 %d: %s\n'%(len(um),sorted(c2.items())[:12]))
o.write(u'\n=== 상세 ===\n')
for nm,(x0,x1,cx,axis) in (('정면',(568500,578600,573552,1)),('측면',(587500,597600,592541,0))):
D=[d for d in swdots.dots(x0,x1,321300,324000) if d[2]==22.5 and d[3]==22.5]
lev=collections.defaultdict(list)
for d in D: lev[d[1]].append(round(d[0]-cx,1))
o.write(u'%s 도면 점: \n'%nm)
for y,xs in sorted(lev.items()): o.write(u' z %.1f n=%d x %s..%s\n'%(y,len(xs),min(xs),max(xs)))
G=collections.defaultdict(list)
for mk,a,b in S:
d=[abs(b[i]-a[i]) for i in range(3)]
if d[axis]>800 and sum(d)-d[axis]<2:
pc=0 if axis==1 else 1
G[(mk,round(a[2]+OFF,1))].append(round(a[pc],1))
o.write(u'%s 모델 끝 보이는 직선:\n'%nm)
for k,v in sorted(G.items()): o.write(u' %s z %.1f n=%d x %s..%s\n'%(k[0],k[1],len(v),min(v),max(v)))
o.close()
+106
View File
@@ -0,0 +1,106 @@
import json, os
SCR = r"C:\Users\User\AppData\Local\Temp\claude\D--Rhino-MCP-rhino-mcp-main\f46b47bc-9105-4079-9b62-fcbee31b26ae\scratchpad"
EX = json.load(open(r"D:\★Pier 자료\심수석님 검토 파일\검토_산외교P1\추출데이터\재료표_대조결과.json", encoding="utf-8"))
IM = json.load(open(SCR + r"\sw_images.json", encoding="utf-8"))
DIM = ["A", "B", "C", "D", "E", "R", "X1", "X2", "X3", "X4", "J"]
V = {}
for mk in ("C1", "C2", "C3", "C4"):
V[mk] = ("차이", "[M-1] 일부 철근 A 가 단일값보다 짧음. [M-2] J 1.560 은 모델 형상에 없음 (L 에만 포함).", "도면↔모델")
for mk in ("C5", "C6"):
V[mk] = ("차이", "[M-1] 일부 철근 B·D 가 4.018/4.016 보다 짧음(3.580~). [M-2] J 0.940 은 모델 형상에 없음.", "도면↔모델")
for mk in ("C7", "C8", "C9"):
V[mk] = ("차이", "[M-3] 재료표 A·C·L 대푯값(가산 포함) ↔ 모델 철근별 작도값. [M-2] C 660×2 는 폐합 형상에 없음. [M-7] H22 굽힘 내면지름 76.8 < 77.7.", "도면↔모델")
V["C10"] = ("차이", "[M-3] B = 모델 +99(일치). [M-4] A 최대 9.570 ↔ 모델 8.687 (대응 철근 없음). [M-2] J 0.660 형상에 없음. [M-7] H22.", "도면↔모델")
V["C11"] = ("일치 (가산 규칙)", "[M-3] A = 모델 +44 (일치). B 165 는 모델에서 정의 불가(미추출).", "일치")
V["C12"] = ("차이 (J)", "[M-3] A·B = 모델 +54/+99 (일치). [M-2] J 0.660 은 형상에 없음.", "도면↔모델")
V["C13"] = ("차이 (J)", "[M-3] A = 모델 +44 (일치). B 165 미추출. [M-2] J 0.660 은 형상에 없음.", "도면↔모델")
V["C14"] = ("차이", "[M-1] 재료표 A 3.000 단일 ↔ 모델 일부 2.633~2.892 (짧음).", "도면↔모델")
V["C15"] = ("차이", "[M-1] 재료표 A 4.500 단일 ↔ 모델 4.118/4.489/4.517. [M-7] H22 굽힘 내면지름.", "도면↔모델")
V["P5"] = ("일치", "A 2.295 · B 150 일치. [M-7] H19 굽힘 내면지름 66.5 < 66.85 (0.35 mm 부족).", "일치")
V["P2"] = ("미확인", "A·B·J 는 모델에서 정의 불가(미추출). R 1.149 일치. 반링 2조각(140개) = 1본 → 70 으로 환산.", "미확인")
V["P3"] = ("일치 (각도 미확인)", "A·B·R 일치(반원 2조각 = 1본 환산). [M-8] 이음 각도 도면에 없음.", "미확인")
V["P4"] = ("일치 (각도 미확인)", "A·B·R 일치. [M-8] 커플러 각도 도면에 없음.", "미확인")
V["B7"] = ("일치", "길이·본수 일치.", "일치")
for mk in ("S1", "S2", "S3", "S4", "B1", "B3", "B4", "B6", "B9", "B10"):
V[mk] = ("일치", "치수·길이·본수 일치.", "일치")
V["B2"] = ("차이 (J)", "[M-2] J 1.000 은 모델 형상에 없음 (중심선 11.758 < L 12.800). [M-7] H22 굽힘 내면지름.", "도면↔모델")
V["B5"] = V["B2"]
V["B8"] = ("차이", "[M-5] 모델 정사각 7.345×7.345 ↔ A 7.300 · B 7.350. [M-2] J 0.570 은 형상에 없음.", "도면↔모델")
V["P1"] = ("차이", "R 340 · A 27.510 · B 534 · C 500 일치. [M-6] 도면 표의 주철근 커플러 476개가 모델에 없음 (연속 28.545 m).", "도면↔모델")
M9 = {"B1": "상면 쪽 중심 77.5 (도면 100, -22.5)", "B2": "상면 쪽 122.5 (+22.5) · 하면 쪽 127.5 (도면 150, -22.5)", "B3": "하면 쪽 127.5 (도면 150, -22.5)",
"B4": "상면 쪽 122.5 (도면 100, +22.5)", "B5": "상면 쪽 77.5 (-22.5) · 하면 쪽 172.5 (+22.5)", "B6": "하면 쪽 172.5 (도면 150, +22.5)"}
for mk, t in M9.items():
v, n_, k_ = V[mk]
V[mk] = (v if v.startswith("차이") else "차이 (높이)", n_ + " [M-9] 철근 중심 높이가 도면 철근선과 다름: " + t + ".", "도면↔모델")
rows = []
n = 0
for tab in ("SD400", "SD500"):
for e in EX[tab]:
if e["kind"] != "bar":
continue
mk = e["mark"]
n += 1
d = e["d"]
m = e["m"]
detail = " ".join("%s=%s" % (k, d[k]) for k in DIM if d.get(k))
mdl = "N %s · L %s m · 총 %s m" % (m.get("n"), m.get("L"), m.get("tot"))
mdims = " ".join("%s=%s" % (k, m[k]) for k in DIM if m.get(k))
if mdims:
mdl += " · " + mdims
verdict, note, kind = V[mk]
rows.append(dict(no=str(n), mark=mk, item="형상·길이·본수 (%s)" % d["dia"], table="L %s × N %s = %s m" % (d["L"], d["n"], d["tot"]), detail=detail or "-", model=mdl, verdict=verdict, note=note, kind=kind))
items = [
dict(code="M-1", mark="C1~C6, C14, C15", content="일부 철근이 재료표 단일값보다 짧음 (모델은 철근마다 개별 길이)", cmp="재료표 ↔ 모델", diff="C1 A 8.462~9.259, C5 B 3.580~4.018, C14 A 2.633~3.000, C15 A 4.118~4.517", drawing_side="마크당 값 하나 (13본 동일 표기)", action="코핑 형상에 맞춘 의도된 단축인지 확인", kind="도면↔모델", image=IM["M-1"]),
dict(code="M-2", mark="C1~C13, P2, B2, B5, B8", content="J·C 길이가 재료표 L 에만 있고 모델 형상에는 없음", cmp="재료표 L = A+B+C+J ↔ 모델 J 제외", diff="C1~C4 J 1.560, C5·C6 0.940, C7~C9 C 660×2, C10·C12·C13 0.660, P2 0.380, B2·B5 1.000, B8 0.570", drawing_side="J 는 표 열 / 형상 III 의 작은 박스, 작도 위치 없음", action="형상에 넣을지 가산 길이로만 둘지 판단", kind="도면↔모델", image=IM["M-2"]),
dict(code="M-3", mark="C7~C13", content="치수 가산(+54/+44/+99) 산식: 재료표 대푯값·가산값 ↔ 모델 작도값", cmp="재료표 ↔ 모델", diff="C7~C9 L 평균 -0.027/-0.018/-0.027 m, C11~C13 은 가산 규칙으로 일치", drawing_side="NOTE: a=(1.250×2ea+2.000×1ea)/3ea + 주철근D + 수직스터럽D", action="가산 규칙의 적용 범위 확인 (운곡교 M-5 와 같은 유형)", kind="도면↔모델", image=IM["M-3"]),
dict(code="M-4", mark="C10", content="재료표 A 최대 9.570 에 대응하는 철근이 모델에 없음", cmp="재료표 ↔ 모델", diff="모델 최대 8.687, 설계길이 합 -6.178 m", drawing_side="A 9.570~3.393 (7본)", action="최대 폭 9.570 루프 존재 여부 확인", kind="도면↔모델", image=IM["M-4"]),
dict(code="M-5", mark="B8", content="모델 정사각 7.345×7.345 ↔ 재료표 A 7.300 · B 7.350", cmp="재료표 ↔ 모델", diff="A -45 / B +5 mm", drawing_side="L 31.581 = 2A+2B+4J", action="어느 값이 맞는지 확인", kind="도면↔모델", image=IM["M-5"]),
dict(code="M-6", mark="P1", content="주철근 커플러 476개(H32)가 모델에 없음 — P1 은 연속 28.545 m", cmp="커플러 표 ↔ 모델", diff="476개 (68본×7 추정)", drawing_side="재료표 아래 커플러 규격 표에 주철근 476 · 띠철근 49", action="커플러 위치·개수 모델 반영 여부 판단", kind="도면↔모델", image=IM["M-6"]),
dict(code="M-7", mark="B2, B5, C7~C10, C15, P5", content="굽힘 내면지름: H22 76.8 < 77.7, H19 66.5 < 66.85 (3.5db)", cmp="KDS 24 14 21 표 4.5-1 ↔ 모델", diff="0.9 / 0.35 mm 부족", drawing_side="도면에 내면반지름 표기 없음", action="모델 R 을 KDS 기준으로 키울지 결정", kind="기준↔모델", image=IM["M-7"]),
dict(code="M-9", mark="B1~B6", content="기초 철근 중심 높이가 도면 철근선(상면 100 아래 / 하면 150 위)과 ±22.5 mm 다름 (같은 방향 철근끼리 한 지름씩 어긋남)", cmp="도면 정면도·측면도 철근선 ↔ 모델", diff="상면 77.5/122.5, 하면 127.5/172.5 (±22.5)", drawing_side="철근 선은 상면 -100 / 하면 +150 같은 높이. ※ 단, 도면 정면·측면 단면의 철근 점(원)은 모델과 같은 높이(77.5/122.5/127.5/172.5)에 그려져 있음 (선과 점이 도면 안에서 다름)", action="모델 철근을 도면 선 높이에 맞출지 결정 (지시된 적 없는 모델 쪽 가정). 점 기준으로는 모델 = 도면 (0.0 mm) — 도면 내부 불일치 여부 판단 필요", kind="도면↔모델", image=IM["M-9"], image2=IM.get("M-9b")),
dict(code="M-10", mark="C7~C14 (수직 스터럽)", content="코핑 스터럽 수직선 상·하단이 도면 철근선보다 위 26.5 / 아래 27.5 mm 더 나감 (41곳 전부, 합 54 = 가산 +54)", cmp="도면 정면도 수직선 ↔ 모델 중심선", diff="상단 +26.5, 하단 -27.5 (합 54.0)", drawing_side="도면 선은 스터럽 철근선 위치 (코핑 상면 -100), 54 는 재료표 NOTE '주철근D + 스터럽D' 가산값", action="모델 스터럽 높이를 도면 선에 맞출지(재료표 L 에는 가산 포함) 결정 (옥성교 M-10 과 같은 유형)", kind="도면↔모델"),
dict(code="M-8", mark="P3, P4", content="링 이음(커플러) 각도 도면에 기준 없음 (모델 90°/270° 가정)", cmp="도면 없음 ↔ 모델 가정", diff="-", drawing_side="형상 V·X 도해에 COUPLER 위치만", action="각도 기준 확인 (설계자)", kind="미확인", image=IM["M-8"]),
]
CHECKS = [
["재료표","본수(전 마크)","재료표 N","모델 본수","일치","전 마크 본수 일치 (선행 확인)"],
["재료표","길이·총길이·형상 치수","재료표 L·치수","모델 설계길이·꼭짓점 치수","불일치 (M-1~M-8)",""],
["재료표","표 산술 (행 L×N, 소계, 단위중량, 총계)","L×N=총길이, 직경별 합","검산","일치","산술 오류 없음"],
["CTC","기초 상면 B1 (x)","28@250=7000 + 양끝 (도면 단면 점 31개)","31본, 점 위치차 0.0 mm","일치","좌 -3650/-3500, 우 3500/3600 (도면 점 그대로)"],
["CTC","기초 하면 B2+B3 (x)","56@125=7000 + 양끝 (점 59개)","59본, 0.0 mm","일치",""],
["CTC","기초 상면 B4 (y)","28@250=7000 + 양끝 (점 31개)","31본, 0.0 mm","일치","좌 -3650, 우 3650"],
["CTC","기초 하면 B5+B6 (y)","56@125=7000 + 양끝 (점 59개)","59본, 0.0 mm","일치",""],
["CTC","기초 중앙 B10 (x)","도면 점 7개 @1000 (-3250~2750)","7본 @1000, 0.0 mm","일치",""],
["CTC","기초 중앙 B7 수직근 (x,y)","13@500=6500, 14본 ×14 (도면 수직선 ±3250)","x -3250~3250, y -3250~3250 (98본 격자)","일치","모델 B7 은 한쪽 100 갈고리 달린 수직근, 길이 2179(수직부) vs 도면 선 2250 (끝 ±16 mm)"],
["CTC","기초 B8 링 (z)","도면 점 7단 @300 (±3672.5)","7단 @300, 높이 일치","일치",""],
["CTC","코핑 스터럽 x (C7~C9, C8)","도면 수직선 41개 (-4600~4600, @200/@500)","41개, 위치 차 0.0 mm","일치","도면에 -100 위치 선 1개 더 있음 (스터럽 아님)"],
["CTC","코핑 C1+C3, C2+C4 (y)","24@125=3000 (25본)","25본씩 @125, -1500~1500","일치",""],
["CTC","코핑 C5+C6 (y)","12@250=3000 (13본)","13본 @250","일치",""],
["CTC","기둥 띠철근 P3·P4·P2 (z)","11@150 / 25@150 / 70@300","P3 12개, P4 25개, P2 70개, 위치 일치","일치","합 위치 43538~69938 도면 치수와 같음"],
["피복","기초 B1~B6 중심 높이","도면 선 상면 100 / 하면 150","77.5/122.5 · 127.5/172.5","불일치 (M-9)","단, 도면 단면 점(원) 높이와는 0.0 mm 일치 (선 ↔ 점 도면 내부)"],
["피복","코핑 스터럽 C7~C14 상·하단","도면 수직선 끝","상 +26.5 / 하 -27.5","불일치 (M-10)","41곳 전부, 합 54"],
["피복","코핑 콘크리트 윤곽","코핑 상면 -273523.3(좌)/-273652.8(우), 기둥 접합 -276853.3","상면 71193/71063, 기둥 67863 (오프셋 344716.3)","일치","상면 높이차 129.5↔130, 0.5 mm 이내"],
["피복","기둥 주철근 P1 반지름·본수","단면 C-C·D-D·E-E 점 68개, r 1125","68본, r 1125.0","일치",""],
["피복","기둥 띠철근 반지름","도면 링 r 1148","P2·P3·P4 r 1149","일치","1 mm"],
["피복","코핑 C1~C6 높이, C7 상·하단 구간, C14·C15, S1~S4","—","—","미확인","정면·단면 높이/간격 판독 미완"],
]
cfg = dict(checks=CHECKS, checks_desc="도면 DXF 의 작도 선·단면 점(원)·치수와 모델 철근 중심선을 좌표로 비교 (2026-10-19). 점(원)은 도면 단면에서 철근 중심.", image2_header="캡처 2 (M-9: 모델 기초 콘크리트 + 철근 중심 피복 치수)", bridge="산외교(울산) P1", title="산외교(울산) P1 철근 도면 ↔ 모델 일치 검토 (불일치 종류별 분류)",
purpose="목적: 도면(재료표·형상 도해)과 라이노 모델이 일치하는지가 최우선. 범위는 모델↔도면 (도면 내부 불일치는 따로 보관). 작성 2026-10-19, 검토 미확정 초안. 기준: KDS 24 14 21.",
sheet1_desc="도면에 기준이 있거나 없는데 모델이 다르게 만들어진 항목 (M-n). 캡처: 위 도면(형상 도해·재료표) / 아래 모델(라이노).",
items=items, rows=rows,
unchecked=[
dict(item="코핑 C1~C6·C14·C15·S 높이/간격", content="정면도·단면도의 철근 높이·간격·피복 중 코핑 일부", note="기초·기둥·코핑 스터럽 위치는 ⑤ 시트에서 완료", status="일부 미검토"),
dict(item="받침 세트", content="받침대좌·패드·교량받침·솔플레이트 (일반도 표) 모델 대조", note="선행 보고서 G5 75/75 PASS (표 값 기준)", status="미검토"),
dict(item="코핑 철근 간 간섭", content="철근끼리 교차·관통", note="사용자 지시로 제외 (검토 단계)", status="제외"),
dict(item="형식 V·X 의 사각 치수", content="B8(형식 V)은 도해가 원형 링인데 재료표는 사각 A·B — 형상 정의가 불분명", note="도면으로 확인 불가", status="확인 불가"),
],
basis=[
dict(item="입력 파일", content="모델: 산외교P1 G5.3dm (원본 그대로), 도면: 교각배근도(울산P1)_2016.10.28.dwg → .dxf (라이노 변환)"),
dict(item="재료표", content="DXF 레이어 CS-TABL-TEX2 글자를 x(열)·y(행)로 읽음. 열: 번호 97922 · 형식 98653 · 직경 99384 · 개수 100115 · A 101099 · B 102337 · C 103575 · D 104813 · E 106051 · R 107162 · X1~X4 107957~109772 · J 110566 · L 112234 · 총길이 113472"),
dict(item="모델 치수", content="중심선(P1-RC-*)의 직선 구간을 연장한 꼭짓점 사이 거리(각진 치수). R 이 있는 형상(P1·C12)은 접선 직선 길이, P3·P4 는 호 길이. P2·P3 는 반원 2조각 = 1본 환산"),
dict(item="길이 비교", content="모델 철근의 설계길이(user string design_length + coupler_length)와 재료표 L·총길이를 대조. 중심선 실길이가 L 보다 3% 이상 짧은 마크는 J 등 미작도로 기록"),
dict(item="KDS 계산", content="내면지름 = 2×(중심선 R − 공칭 db/2). H22 R 49.5 → 76.8, H19 42.8 → 66.5 (기준 3.5db 77.7 / 66.85), H32 72 → 112.2, H16 24 → 32.1, H13 19.5 → 26.3 (기준 2db)"),
dict(item="가산 규칙", content="C10~C13 재료표 치수 = 모델 작도값 + 54(A, C10·C12) / +99(B) / +44(C11·C13 A) — 재료표 NOTE 의 '주철근D + 수직스터럽D' 산식과 연결"),
])
json.dump(cfg, open(SCR + r"\sw_cfg.json", "w", encoding="utf-8"), ensure_ascii=False, indent=1)
print(len(rows))
+17
View File
@@ -0,0 +1,17 @@
import sys; sys.path.insert(0,".")
import json, collections, io
from swdots import dots
B=json.load(open('swpos.json',encoding='utf-8'))
o=io.open('swb7.txt','w',encoding='utf-8')
D=dots(51600,59200,-282000,-274400,maxlen=40)
sz=collections.Counter((d[2],d[3]) for d in D); o.write(u'plan dots %d sizes %s\n'%(len(D),sz.most_common(8)))
CX,CY=55406.2,-278231.2
for s in sorted(set(d[2] for d in D if d[2]==d[3])):
P=[(round(d[0]-CX,1),round(d[1]-CY,1)) for d in D if d[2]==s and d[3]==s]
xs=sorted(set(p[0] for p in P)); ys=sorted(set(p[1] for p in P))
o.write(u'size %.1f n=%d x:%d [%s..%s] y:%d [%s..%s]\n'%(s,len(P),len(xs),xs[0],xs[-1],len(ys),ys[0],ys[-1]))
mx=sorted(set(round((b['bb'][0]+b['bb'][3])/2,1) for b in B['B7'])); my=sorted(set(round((b['bb'][1]+b['bb'][4])/2,1) for b in B['B7']))
o.write(u'model B7 x %s\n y %s\n'%(mx,my))
pts=collections.Counter((round((b['bb'][0]+b['bb'][3])/2,1),round((b['bb'][1]+b['bb'][4])/2,1)) for b in B['B7'])
o.write(u'model B7 positions %d (unique %d)\n'%(sum(pts.values()),len(pts)))
b=B['B7'][0]; o.write(u'sample %s\n'%b)
@@ -0,0 +1,16 @@
import sys, json, collections; sys.path.insert(0,'pylib')
import ezdxf
F=r"D:\★Pier 자료\심수석님 검토 파일\검토_산외교P1\교각배근도(울산P1)_2016.10.28.dxf"
d=ezdxf.readfile(F); m=d.modelspace()
C=[]
def walk(ents,depth=0):
for e in ents:
t=e.dxftype()
if t=='CIRCLE':
c=e.dxf.center; C.append((round(c.x,1),round(c.y,1),round(e.dxf.radius,1),e.dxf.layer))
elif t=='INSERT' and depth<3:
try: walk(e.virtual_entities(),depth+1)
except Exception: pass
walk(m)
json.dump(C,open('swcirc.json','w'))
print(len(C),collections.Counter((c[2],c[3]) for c in C).most_common(15))
@@ -0,0 +1,35 @@
import sys; sys.path.insert(0,".")
import collections, math, io, json, numpy as np, pickle
sys.path.insert(0,'pylib')
from swdots import dots
o=io.open('swcol2.txt','w',encoding='utf-8')
def fit(pts):
A=np.array([[2*x,2*y,1] for x,y in pts]); b=np.array([x*x+y*y for x,y in pts])
s=np.linalg.lstsq(A,b,rcond=None)[0]; cx,cy=s[0],s[1]; r=math.sqrt(s[2]+cx*cx+cy*cy); return cx,cy,r
for nm,(y0,y1) in (('C-C',(-271000,-262500)),('D-D',(-279500,-271500)),('E-E',(-287500,-279500))):
D=[d for d in dots(31500,37500,y0,y1,maxlen=60) if d[2]==22.5 and d[3]==22.5]
pts=[(d[0],d[1]) for d in D]
cx,cy,r=fit(pts)
rr=[math.hypot(x-cx,y-cy) for x,y in pts]
ang=sorted(math.degrees(math.atan2(y-cy,x-cx))%360 for x,y in pts)
o.write(u'%s 점 %d 중심 (%.1f, %.1f) 반지름 평균 %.1f (min %.1f max %.1f)\n'%(nm,len(pts),cx,cy,r,min(rr),max(rr)))
# 반지름 분포 군집
c=collections.Counter(round(v/10)*10 for v in rr); o.write(u' 반지름 군집 %s\n'%sorted(c.items()))
# 모델 P1/P2 중심선 반지름
import rhino3dm
m3=rhino3dm.File3dm.Read(r"D:\★Pier 자료\심수석님 검토 파일\검토_산외교P1\산외교P1 G5.3dm")
R=collections.defaultdict(list)
for ob in m3.Objects:
g=ob.Geometry
if isinstance(g,rhino3dm.Brep) or not hasattr(g,'PointAtStart'): continue
us=dict(ob.Attributes.GetUserStrings()); mk=us.get('mark')
if mk in ('P1','P2','P3','P4','P5'):
try:
n=40
dom=(g.Domain.T0,g.Domain.T1)
for i in range(n+1):
p=g.PointAt(dom[0]+(dom[1]-dom[0])*i/n); R[mk].append((p.X,p.Y,p.Z))
except Exception as e: pass
for mk,L in sorted(R.items()):
rs=np.array([math.hypot(x,y) for x,y,z in L]); zs=np.array([z for x,y,z in L])
o.write(u'모델 %s 점 %d: r min %.1f median %.1f max %.1f, z %.0f~%.0f\n'%(mk,len(L),rs.min(),np.median(rs),rs.max(),zs.min(),zs.max()))
+27
View File
@@ -0,0 +1,27 @@
import sys; sys.path.insert(0,".")
import json, collections, io
d=json.load(open('swdrw.json')); B=json.load(open('swpos.json',encoding='utf-8'))
o=io.open('swcop.txt','w',encoding='utf-8')
CX=10473.9
V=[v for v in d['V'] if v[3]=='CS-RBAR-MAJR' and 5300<v[0]<15700 and -277000<v[1] and v[2]<-273300 and v[2]-v[1]>400]
xs=collections.defaultdict(list)
for x,y0,y1,l in V: xs[round(x-CX,1)].append((y0,y1))
o.write(u'도면 코핑 정면도 수직 철근선: 선 %d개, 서로 다른 x %d개\n'%(len(V),len(xs)))
dx=sorted(xs)
o.write(u'%s\n'%dx)
# 모델: 코핑 스터럽류 (정면도에서 수직으로 보이는 철근)
M=collections.defaultdict(list)
for mk in ('C7','C8','C9','C10','C11','C12','C13','C14'):
for b in B[mk]:
bb=b['bb']
if bb[5]-bb[2]>300 and bb[3]-bb[0]<60:
M[round((bb[0]+bb[3])/2,1)].append(mk)
mx=sorted(M)
o.write(u'모델 수직 철근 x %d개: %s\n'%(len(mx),mx))
import bisect
dd=[min(abs(x-m) for m in mx) for x in dx]
o.write(u'도면 선 → 가장 가까운 모델 x 최대 %.1f\n'%max(dd))
miss=[x for x in dx if min(abs(x-m) for m in mx)>3]
o.write(u'모델에 없는 도면 선 x: %s\n'%miss)
miss2=[m for m in mx if min(abs(x-m) for x in dx)>3]
o.write(u'도면에 없는 모델 x: %s (%s)\n'%(miss2,[M[m] for m in miss2]))
@@ -0,0 +1,24 @@
import sys; sys.path.insert(0,".")
import json, collections, io
d=json.load(open('swdrw.json')); B=json.load(open('swpos.json',encoding='utf-8'))
o=io.open('swcop2.txt','w',encoding='utf-8')
CX=10473.9
V=[v for v in d['V'] if v[3]=='CS-RBAR-MAJR' and 5300<v[0]<15700 and -277000<v[1] and v[2]<-273300 and v[2]-v[1]>400]
D=collections.defaultdict(list)
for x,y0,y1,l in V: D[round(x-CX,1)].append((y0,y1))
M=collections.defaultdict(list)
for mk in ('C7','C8','C9','C10','C11','C12','C13','C14'):
for b in B[mk]:
bb=b['bb']
if bb[5]-bb[2]>300 and bb[3]-bb[0]<60: M[round((bb[0]+bb[3])/2,1)].append((mk,bb[2],bb[5]))
res=[]
for x,L in sorted(D.items()):
if x not in M: continue
dy0=min(a for a,b in L); dy1=max(b for a,b in L)
mz0=min(m[1] for m in M[x]); mz1=max(m[2] for m in M[x])
res.append((x,dy0,dy1,mz0,mz1,sorted(set(m[0] for m in M[x]))))
off1=[r[4]-r[2] for r in res]; off0=[r[3]-r[1] for r in res]
o.write(u'위치 %d개. 모델 상단-도면 상단 오프셋: min %.1f max %.1f / 하단: min %.1f max %.1f\n'%(len(res),min(off1),max(off1),min(off0),max(off0)))
c1=collections.Counter(round(v,1) for v in off1); c0=collections.Counter(round(v,1) for v in off0)
o.write(u'상단 오프셋 분포 %s\n하단 오프셋 분포 %s\n'%(sorted(c1.items())[:20],sorted(c0.items())[:20]))
for r in res[:6]+res[18:24]+res[-4:]: o.write(u'x %.0f 도면 y %.1f~%.1f / 모델 z %.1f~%.1f %s\n'%r)
@@ -0,0 +1,19 @@
import sys; sys.path.insert(0,"pylib")
import rhino3dm, pickle, collections, io
o=io.open('swcopc.txt','w',encoding='utf-8')
m3=rhino3dm.File3dm.Read(r"D:\★Pier 자료\심수석님 검토 파일\검토_산외교P1\산외교P1 G5.3dm")
for ob in m3.Objects:
if ob.Attributes.Name=='P1-CP':
g=ob.Geometry
vs=sorted(set((round(v.Location.X,1),round(v.Location.Z,1)) for v in g.Vertices))
# y 구분 없이 (x,z)
o.write(u'코핑 Brep 꼭짓점 (x,z) %d개\n'%len(vs))
for v in vs: o.write(u' %s\n'%(v,))
segs,texts=pickle.load(open('cache_교각배근도(울산P1)_2016.10.28.dxf.pkl','rb'))
pts=collections.Counter()
for a,b,c,l in segs:
if l!='CS-CONC-MAJR': continue
if 5200<a[0]<15800 and -277300<a[1]<-273300 and 5200<b[0]<15800 and -277300<b[1]<-273300:
for p in (a,b): pts[(round(p[0]-10473.9,1),round(p[1],1))]+=1
o.write(u'도면 코핑 윤곽 꼭짓점(x-10473.9, y)\n')
for p,n in sorted(pts.items()): o.write(u' %s x%d\n'%(p,n))
@@ -0,0 +1,21 @@
import sys; sys.path.insert(0,".")
import json, collections, io
from swdots import dots
B=json.load(open('swpos.json',encoding='utf-8'))
o=io.open('swcops.txt','w',encoding='utf-8')
OFF=344716.3
D=dots(19000,30000,-277200,-271000,maxlen=80)
sz=collections.Counter((d[2],d[3]) for d in D); o.write(u'dots %d sizes %s\n'%(len(D),sz.most_common(10)))
M=[]
for mk,L in B.items():
if not (mk.startswith('C') or mk.startswith('S')): continue
for b in L:
bb=b['bb']; ext=[bb[3]-bb[0],bb[4]-bb[1],bb[5]-bb[2]]
if ext[0]>800 and ext[1]<60 and ext[2]<60: M.append(((bb[1]+bb[4])/2,(bb[2]+bb[5])/2-OFF,mk,ext[0]))
o.write(u'model end-on (x-direction long) bars: %d marks %s\n'%(len(M),collections.Counter(m[2] for m in M)))
pts=[d for d in D if d[2]==d[3] and d[2]<60]
cs=collections.Counter(d[2] for d in pts); o.write(u'dot size list %s\n'%cs)
# 중심 x 추정: 모델 y 범위와 맞춰 보기
xs=sorted(set(round(d[0],1) for d in pts)); o.write(u'drawn dot x min %.1f max %.1f\n'%(xs[0],xs[-1]))
ys=sorted(set(round(d[1],1) for d in pts)); o.write(u'drawn dot y levels %d: %s\n'%(len(ys),ys[:40]))
mz=sorted(set(round(m[1],1) for m in M)); o.write(u'model z levels %d: %s\n'%(len(mz),mz[:40]))
@@ -0,0 +1,28 @@
import pickle, math, collections, json, sys
segs,texts=pickle.load(open('cache_교각배근도(울산P1)_2016.10.28.dxf.pkl','rb'))
def dots(x0,x1,y0,y1,maxlen=40,lay=None):
S=[s for s in segs if x0<=s[0][0]<=x1 and y0<=s[0][1]<=y1 and x0<=s[1][0]<=x1 and y0<=s[1][1]<=y1 and math.hypot(s[0][0]-s[1][0],s[0][1]-s[1][1])<maxlen and (lay is None or s[3] in lay)]
# grid-hash union-find on endpoints
key=lambda p:(round(p[0]/0.5),round(p[1]/0.5))
par={}
def f(a):
while par.setdefault(a,a)!=a:
par[a]=par[par[a]]; a=par[a]
return a
for a,b,c,l in S:
ka,kb=key(a),key(b)
par[f(ka)]=f(kb)
comp=collections.defaultdict(list)
for a,b,c,l in S:
comp[f(key(a))].append((a,b,l))
out=[]
for k,L in comp.items():
xs=[p[0] for a,b,l in L for p in (a,b)]; ys=[p[1] for a,b,l in L for p in (a,b)]
w=max(xs)-min(xs); h=max(ys)-min(ys)
out.append((round((max(xs)+min(xs))/2,1),round((max(ys)+min(ys))/2,1),round(w,1),round(h,1),len(L),L[0][2]))
return out
if __name__=='__main__':
D=dots(6700,14250,-285300,-282700)
print(len(D));
c=collections.Counter((d[2],d[3],d[4]) for d in D); print(c.most_common(10))
for d in sorted(D,key=lambda d:(d[1],d[0]))[:10]: print(d)
@@ -0,0 +1,15 @@
import sys; sys.path.insert(0,".")
import json, collections, io
from swdots import dots
B=json.load(open('swpos.json',encoding='utf-8'))
o=io.open('swfinal.txt','w',encoding='utf-8')
def pos(ms,ax): return sorted(set(round((b['bb'][ax]+b['bb'][ax+3])/2,1) for m in ms for b in B[m]))
for nm,(x0,x1,cx,lvl,ms,ax) in (('정면 하면 B2+B3 x',(6700,14250,10473.9,-285125.8,['B2','B3'],0)),('정면 상면 B1 x',(6700,14250,10473.9,-282830.8,['B1'],0)),('측면 하면 B5+B6 y',(20850,28400,24604.3,-285080.8,['B5','B6'],1)),('측면 상면 B4 y',(20850,28400,24604.3,-282875.8,['B4'],1))):
D=sorted(round(d[0]-cx,1) for d in dots(x0,x1,-285300,-282700) if d[2]==22.5 and abs(d[1]-lvl)<1)
M=pos(ms,ax)
o.write(u'%s: 도면 점 %d / 모델 %d 위치\n'%(nm,len(D),len(M)))
if len(D)==len(M): o.write(u' 최대 위치차 %.1f\n'%max(abs(a-b) for a,b in zip(D,M)))
else: o.write(u' 도면 %s\n 모델 %s\n'%(D,M))
# 코핑 y 패턴
for nm,ms in (('C1+C3',['C1','C3']),('C2+C4',['C2','C4']),('C5+C6',['C5','C6'])):
P=pos(ms,1); o.write(u'코핑 %s y %d개: %s .. %s 간격 %s\n'%(nm,len(P),P[0],P[-1],sorted(set(round(b-a,1) for a,b in zip(P[:-1],P[1:])))))
@@ -0,0 +1,34 @@
import sys; sys.path.insert(0,".")
import json, collections, io
from swdots import dots
B=json.load(open('swpos.json',encoding='utf-8'))
OFF=328016.3
o=io.open('swfoot2.txt','w',encoding='utf-8')
foot=['B1','B2','B3','B4','B5','B6','B7','B8','B9','B10']
for nm,(x0,x1,cx,ax,perp) in (('정면도',(6700,14250,10473.9,0,1)),('측면도',(20850,28400,24604.3,1,0))):
# 도면 점 (원 지름 22.5 = 안쪽 점)
D=[(d[0]-cx,d[1]) for d in dots(x0,x1,-285300,-282700) if d[2]==22.5]
# 모델: 보는 방향(perp 축)으로 길게 뻗은 철근 — 끝이 보이는 점
M=[]
for m in foot:
for b in B[m]:
bb=b['bb']; ext=[bb[3]-bb[0],bb[4]-bb[1],bb[5]-bb[2]]
if ext[perp]>1000 and ext[ax]<60 and ext[2]<60:
M.append(((bb[ax]+bb[ax+3])/2,(bb[2]+bb[5])/2-OFF,m))
o.write(u'== %s: 도면 점 %d, 모델 \"끝이 보이는\" 철근 %d\n'%(nm,len(D),len(M)))
# 도면점 -> 모델
unmatchedD=[]; used=set()
for (x,y) in D:
best=None
for i,(mx,mz,m) in enumerate(M):
d=max(abs(mx-x),abs(mz-y))
if best is None or d<best[0]: best=(d,i)
if best and best[0]<6: used.add(best[1])
else: unmatchedD.append((round(x,1),round(y,1),round(best[0],1) if best else None))
o.write(u' 도면 점 중 모델과 불일치 %d개\n'%len(unmatchedD))
c=collections.Counter(y for x,y,d in unmatchedD)
o.write(u' 불일치 도면 점 높이별 %s\n'%sorted(c.items()))
o.write(u' 예 %s\n'%unmatchedD[:6])
unM=[M[i] for i in range(len(M)) if i not in used]
o.write(u' 모델 철근 중 도면 점 없음 %d개: 마크별 %s\n'%(len(unM),collections.Counter(m for x,z,m in unM)))
cz=collections.Counter((m,round(z,1)) for x,z,m in unM); o.write(u' %s\n'%sorted(cz.items()))
@@ -0,0 +1,30 @@
import sys; sys.path.insert(0,".")
import json, collections, io, sys
from swdots import dots
B=json.load(open('swpos.json',encoding='utf-8'))
OFFY=328016.3
o=io.open('swfootcmp.txt','w',encoding='utf-8')
def model(marks,axis):
# returns list of (pos along axis, z_d)
r=[]
for m in marks:
for b in B[m]:
bb=b['bb']; r.append((round((bb[axis]+bb[axis+3])/2,1), round((bb[2]+bb[5])/2-OFFY,1), m))
return r
for nm,(x0,x1,cx,axis,marks_) in (('정면도(x 위치)',(6700,14250,10473.9,0,['B1','B2','B3','B7','B8','B9','B10'])),('측면도(y 위치)',(20850,28400,24604.3,1,['B4','B5','B6','B7','B8','B9','B10']))):
D=[d for d in dots(x0,x1,-285300,-282700) if d[2]==22.5]
lev=collections.defaultdict(list)
for d in D: lev[d[1]].append(round(d[0]-cx,1))
o.write(u'== %s : 도면 점 %d개, 높이 레벨 %s\n'%(nm,len(D),sorted((k,len(v)) for k,v in lev.items())))
for y,xs in sorted(lev.items()):
xs=sorted(xs)
# 가장 가까운 모델 (같은 높이 ±60)
cand=[(p,z,m) for p,z,m in model(marks_,axis) if abs(z-y)<80]
ms=sorted(set(p for p,z,m in cand))
o.write(u' y=%.1f 도면 %d개 [%s ... %s] / 모델(±80) %d개 마크 %s\n'%(y,len(xs),xs[:4],xs[-4:],len(ms),sorted(set(m for p,z,m in cand))))
if cand:
import bisect
dd=[min(abs(x-p) for p in ms) for x in xs]
o.write(u' 도면→모델 위치차 최대 %.1f\n'%max(dd))
dd2=[min(abs(x-p) for x in xs) for p in ms]
o.write(u' 모델→도면 위치차 최대 %.1f (모델 %d)\n'%(max(dd2),len(ms)))
+15
View File
@@ -0,0 +1,15 @@
import sys, io, json, math, collections; sys.path.insert(0,'pylib')
import rhino3dm
m3=rhino3dm.File3dm.Read(r"D:\★Pier 자료\심수석님 검토 파일\검토_산외교P1\산외교P1 G5.3dm")
bars=collections.defaultdict(list)
for o in m3.Objects:
g=o.Geometry
if isinstance(g,rhino3dm.Brep) or not hasattr(g,'PointAtStart'): continue
us=dict(o.Attributes.GetUserStrings())
mk=us.get('mark')
if not mk: continue
bb=g.GetBoundingBox()
bars[mk].append({'name':o.Attributes.Name,'bb':[round(v,1) for v in (bb.Min.X,bb.Min.Y,bb.Min.Z,bb.Max.X,bb.Max.Y,bb.Max.Z)],'dia':us.get('dia'),'shape':us.get('shape'),'host':us.get('host'),
'p0':[round(v,1) for v in (g.PointAtStart.X,g.PointAtStart.Y,g.PointAtStart.Z)],'p1':[round(v,1) for v in (g.PointAtEnd.X,g.PointAtEnd.Y,g.PointAtEnd.Z)]})
json.dump(bars,open('swpos.json','w',encoding='utf-8'),ensure_ascii=False)
print({k:len(v) for k,v in bars.items()})
@@ -0,0 +1,34 @@
import json, io, collections
B=json.load(open('swpos.json',encoding='utf-8'))
o=io.open('swruns.txt','w',encoding='utf-8')
def runs(vals):
v=sorted(set(round(x,1) for x in vals))
if len(v)<2: return str(v)
out=[]; cur=[v[0]]
for a in v[1:]:
if len(cur)>=2 and abs((a-cur[-1])-(cur[-1]-cur[-2]))<1.5: cur.append(a)
elif len(cur)==1: cur.append(a)
else:
out.append(cur); cur=[cur[-1],a] if False else [a]
out.append(cur)
# 간단 표기: 연속 구간 (start, n, pitch)
res=[]
i=0
v=sorted(set(round(x,1) for x in vals))
i=0
while i<len(v):
j=i+1
if j<len(v):
p=v[j]-v[i]
while j+1<len(v) and abs((v[j+1]-v[j])-p)<1.5: j+=1
res.append('%s..%s n=%d @%.1f'%(v[i],v[j],j-i+1,p)); i=j+1
else:
res.append('%s'%v[i]); i+=1
return ' | '.join(res)
for mk in sorted(B,key=lambda s:(s[0],int(s[1:]) if s[1:].isdigit() else 0)):
L=B[mk]
cx=[(b['bb'][0]+b['bb'][3])/2 for b in L]; cy=[(b['bb'][1]+b['bb'][4])/2 for b in L]; cz=[(b['bb'][2]+b['bb'][5])/2 for b in L]
nx,ny,nz=len(set(round(v) for v in cx)),len(set(round(v) for v in cy)),len(set(round(v) for v in cz))
o.write(u'%s n=%d %s %s dist x=%d y=%d z=%d\n'%(mk,len(L),L[0]['dia'],L[0]['shape'],nx,ny,nz))
for ax,vals,nn in (('x',cx,nx),('y',cy,ny),('z',cz,nz)):
if nn>1: o.write(u' %s: %s\n'%(ax,runs(vals)))
@@ -0,0 +1,14 @@
import sys, collections; sys.path.insert(0,'pylib')
import ezdxf
F=r"D:\★Pier 자료\심수석님 검토 파일\검토_산외교P1\교각배근도(울산P1)_2016.10.28.dxf"
d=ezdxf.readfile(F); m=d.modelspace()
c=collections.Counter(); arcs=[]
def walk(ents,depth=0):
for e in ents:
t=e.dxftype(); c[(t,depth)]+=1
if t=='ARC' and len(arcs)<8: arcs.append((e.dxf.center,e.dxf.radius,e.dxf.start_angle,e.dxf.end_angle,e.dxf.layer))
if t=='INSERT' and depth<3:
try: walk(e.virtual_entities(),depth+1)
except Exception: pass
walk(m)
print(c); print(arcs)
+20
View File
@@ -0,0 +1,20 @@
import sys, re, json
sys.path.insert(0, 'pylib')
import ezdxf
f = sys.argv[1]
d = ezdxf.readfile(f)
m = d.modelspace()
rows = []
FONT = re.compile(r'\\f[^;]*;')
for e in m:
if e.dxftype() == 'MTEXT':
t = e.text
t = FONT.sub('', t).replace('{', '').replace('}', '').replace('\\P', ' / ')
rows.append((round(e.dxf.insert.x), round(e.dxf.insert.y), e.dxf.layer, t.strip(), round(e.dxf.char_height, 1)))
elif e.dxftype() == 'TEXT':
rows.append((round(e.dxf.insert.x), round(e.dxf.insert.y), e.dxf.layer, e.dxf.text, round(e.dxf.height, 1)))
json.dump(rows, open(sys.argv[2], 'w', encoding='utf-8'), ensure_ascii=False)
print(len(rows))
ex = [r for r in rows if 'H32' in r[3]][:3]
print(ex)
print(rows[:15])
+109
View File
@@ -0,0 +1,109 @@
"""빠른 DXF 크롭 렌더러 (LINE/ARC/CIRCLE/INSERT 분해/TEXT/MTEXT)
사용: python -I fastrender.py <dxf> <x0> <x1> <y0> <y1> <out.png> [width_px]
캐시: <dxf>.cache.pkl (분해한 선분·글자)
"""
import sys, os, pickle, math, re
sys.path.insert(0, 'pylib')
import ezdxf
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from matplotlib import font_manager
f = sys.argv[1]
x0, x1, y0, y1 = map(float, sys.argv[2:6])
out = sys.argv[6]
wpx = int(sys.argv[7]) if len(sys.argv) > 7 else 2400
DARK = len(sys.argv) > 8 and sys.argv[8] == 'dark'
cache = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cache_' + os.path.basename(f) + '.pkl')
ACI = {1: 'red', 2: '#b8a000', 3: 'green', 4: '#00a0a0', 5: 'blue', 6: 'magenta', 7: 'black', 8: 'gray', 9: 'gray', 30: '#ff8000', 41: '#b08800'}
ACID = {1: '#ff3030', 2: '#ffff00', 3: '#00e000', 4: '#00e0e0', 5: '#5080ff', 6: '#ff40ff', 7: '#ffffff', 8: '#aaaaaa', 9: '#aaaaaa', 30: '#ff9a1f', 41: '#e6c200'}
FONT = re.compile(r'\\f[^;]*;')
def color_of(e, doc):
c = e.dxf.color
if c == 256 or c is None:
try:
c = doc.layers.get(e.dxf.layer).dxf.color
except Exception:
c = 7
c = abs(c)
return ACI.get(c, '#555555')
def arc_pts(cx, cy, r, a0, a1, n=24):
if a1 < a0:
a1 += 360
return [(cx + r * math.cos(math.radians(a0 + (a1 - a0) * i / n)), cy + r * math.sin(math.radians(a0 + (a1 - a0) * i / n))) for i in range(n + 1)]
def collect(doc):
segs = []; texts = []
def walk(ents, depth=0):
for e in ents:
t = e.dxftype()
col = color_of(e, doc)
lay = e.dxf.layer
if t == 'LINE':
segs.append(((e.dxf.start.x, e.dxf.start.y), (e.dxf.end.x, e.dxf.end.y), col, lay))
elif t == 'ARC':
p = arc_pts(e.dxf.center.x, e.dxf.center.y, e.dxf.radius, e.dxf.start_angle, e.dxf.end_angle)
for a, b in zip(p[:-1], p[1:]):
segs.append((a, b, col, lay))
elif t == 'CIRCLE':
p = arc_pts(e.dxf.center.x, e.dxf.center.y, e.dxf.radius, 0, 360, 36)
for a, b in zip(p[:-1], p[1:]):
segs.append((a, b, col, lay))
elif t in ('LWPOLYLINE', 'POLYLINE'):
try:
pts = [(v[0], v[1]) for v in e.get_points()] if t == 'LWPOLYLINE' else [(v.dxf.location.x, v.dxf.location.y) for v in e.vertices]
for a, b in zip(pts[:-1], pts[1:]):
segs.append((a, b, col, lay))
except Exception:
pass
elif t == 'INSERT' and depth < 3:
try:
walk(e.virtual_entities(), depth + 1)
except Exception:
pass
try:
for a in e.attribs:
if a.dxf.tag == 'RB_NO' or True:
texts.append((a.dxf.insert.x, a.dxf.insert.y, a.dxf.text, a.dxf.height, ACI[3], 0))
except Exception:
pass
elif t == 'MTEXT':
s = FONT.sub('', e.text).replace('{', '').replace('}', '').replace('\\P', '\n')
texts.append((e.dxf.insert.x, e.dxf.insert.y, s, e.dxf.char_height, col, e.dxf.get('rotation', 0)))
elif t == 'TEXT':
texts.append((e.dxf.insert.x, e.dxf.insert.y, e.dxf.text, e.dxf.height, col, e.dxf.get('rotation', 0)))
elif t == 'POINT':
segs.append(((e.dxf.location.x - 20, e.dxf.location.y), (e.dxf.location.x + 20, e.dxf.location.y), col, lay))
walk(doc.modelspace())
return segs, texts
if os.path.exists(cache) and os.path.getmtime(cache) > os.path.getmtime(f):
segs, texts = pickle.load(open(cache, 'rb'))
else:
doc = ezdxf.readfile(f)
segs, texts = collect(doc)
pickle.dump((segs, texts), open(cache, 'wb'))
fp = font_manager.FontProperties(fname=r'C:\Windows\Fonts\malgun.ttf')
w = x1 - x0; h = y1 - y0
fig = plt.figure(figsize=(wpx / 100.0, wpx / 100.0 * h / w), dpi=100, facecolor=('#222831' if DARK else 'white'))
ax = fig.add_axes([0, 0, 1, 1])
sel = [s for s in segs if max(s[0][0], s[1][0]) >= x0 and min(s[0][0], s[1][0]) <= x1 and max(s[0][1], s[1][1]) >= y0 and min(s[0][1], s[1][1]) <= y1]
def rc(c):
if not DARK:
return c
inv = {v: ACID[k] for k, v in ACI.items()}
return inv.get(c, '#dddddd')
lc = LineCollection([(s[0], s[1]) for s in sel], colors=[rc(s[2]) for s in sel], linewidths=0.6)
ax.add_collection(lc)
px_per_unit = wpx / w
for x, y, s, hh, col, rot in texts:
if x0 <= x <= x1 and y0 <= y <= y1:
ax.text(x, y, s, fontproperties=fp, fontsize=max(4, hh * px_per_unit * 0.72), color=rc(col), rotation=rot, ha='left', va='bottom')
ax.set_xlim(x0, x1); ax.set_ylim(y0, y1); ax.set_aspect('equal'); ax.axis('off')
fig.savefig(out, dpi=100, facecolor=fig.get_facecolor())
print('ok', len(sel), out)
+165
View File
@@ -0,0 +1,165 @@
"""운곡교 '대조표_C1-C16_도면모델_분류_V2.xlsx' 형식(분류 안내 / ① 도면↔모델 불일치(+캡처) / ② 전체 대조표 / ③ 미검토 / ④ 측정 근거)으로
교량별 검토 엑셀을 만든다. 사용: python -I make_review_xlsx.py <cfg.json> <out.xlsx>
cfg: bridge, title, purpose, legend_counts(자동), items[{code,mark,content,cmp,diff,drawing_side,action,kind,image}],
rows[{no,mark,item,table,detail,model,verdict,note,kind}], unchecked[{item,content,note,status}], basis[{item,content,note}]
도면 내부 불일치는 이 엑셀에 넣지 않는다(별도 보관 파일).
"""
import sys, json, os
sys.path.insert(0, r"C:\Users\User\AppData\Local\Temp\claude\D--Rhino-MCP-rhino-mcp-main\f46b47bc-9105-4079-9b62-fcbee31b26ae\scratchpad\pylib")
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.drawing.image import Image as XImage
from openpyxl.utils import get_column_letter
from PIL import Image as PImage
cfg = json.load(open(sys.argv[1], encoding="utf-8"))
OUT = sys.argv[2]
thin = Side(style="thin", color="999999")
BR = Border(left=thin, right=thin, top=thin, bottom=thin)
HF = PatternFill("solid", fgColor="D9D9D9")
FILL = {"도면↔모델": PatternFill("solid", fgColor="F8CBAD"), "기준↔모델": PatternFill("solid", fgColor="E6C84A"),
"도면 내부": PatternFill("solid", fgColor="FFF2CC"), "일치": PatternFill("solid", fgColor="E2F0D9"),
"미확인": PatternFill("solid", fgColor="D9D9D9")}
TOPW = Alignment(wrap_text=True, vertical="top")
CEN = Alignment(wrap_text=True, vertical="center", horizontal="center")
wb = Workbook()
# ------------------------------------------------ 분류 안내
ws = wb.active
ws.title = "분류 안내"
ws.append([cfg["title"]])
ws["A1"].font = Font(bold=True, size=14)
ws.append([cfg["purpose"]])
ws["A2"].alignment = TOPW
ws.merge_cells("A2:E2")
ws.row_dimensions[2].height = 64
ws.append([])
ws.append(["색", "구분", "뜻", "항목 수", "볼 시트"])
for c in range(1, 6):
ws.cell(row=4, column=c).font = Font(bold=True)
ws.cell(row=4, column=c).fill = HF
ws.cell(row=4, column=c).border = BR
cnt = {}
for r in cfg["rows"]:
cnt[r["kind"]] = cnt.get(r["kind"], 0) + 1
legend = [("주황", "도면↔모델", "도면에는 정보가 있거나 없는데 모델이 다르게 만들어졌거나 가정한 것 (모델 확인·수정 대상)", "① 도면↔모델 불일치"),
("진한 노랑", "기준↔모델", "설계기준(KDS 24 14 21)에 모델이 맞는지 (도면에는 정보 없음)", "① 도면↔모델 불일치"),
("초록", "일치", "도면(재료표·철근상세)과 모델이 일치", "② 전체 대조표"),
("회색", "미확인", "도면에 정보가 없어 확인하지 못한 것", "② 전체 대조표 · ③")]
for col, kind, mean, sheet in legend:
n = len([i for i in cfg["items"] if i["kind"] == kind]) if kind in ("도면↔모델", "기준↔모델") else cnt.get(kind, 0)
ws.append([col, kind, mean, str(n), sheet])
r = ws.max_row
ws.cell(row=r, column=2).fill = FILL[kind]
for c in range(1, 6):
ws.cell(row=r, column=c).border = BR
ws.cell(row=r, column=c).alignment = TOPW
ws.row_dimensions[r].height = 40
ws.append([])
ws.append(["※ 도면 내부 불일치(도면끼리 다른 것)는 이 파일에 넣지 않고 '도면내부_오류_보관.md' 에 따로 보관 — 요청 시 보고."])
ws.append(["※ ② 전체 대조표의 구분 열은 마크 행 수 기준, ① 의 항목 수는 코드(M-n) 수 기준."])
for i, w in enumerate([12, 14, 80, 10, 26], 1):
ws.column_dimensions[get_column_letter(i)].width = w
# ------------------------------------------------ ① 도면↔모델 불일치
w1 = wb.create_sheet("① 도면↔모델 불일치")
w1.append(["① 도면 ↔ 모델 불일치 (최우선 확인) — 주황 / 진한 노랑 / 회색"])
w1["A1"].font = Font(bold=True, size=13)
w1.append([cfg["sheet1_desc"]])
w1["A2"].alignment = TOPW
w1.merge_cells("A2:I2")
w1.row_dimensions[2].height = 48
w1.append([])
heads = ["번호", "마크", "내용", "비교", "차이 크기", "도면 쪽 상태", "확인·조치 필요 사항", "구분", "캡처 (왼쪽·위 도면 / 오른쪽·아래 모델)"]
w1.append(heads)
for c in range(1, 10):
cell = w1.cell(row=4, column=c)
cell.font = Font(bold=True)
cell.fill = HF
cell.border = BR
cell.alignment = CEN
w1.row_dimensions[4].height = 30
MAXH = 520
for it in cfg["items"]:
w1.append([it["code"], it["mark"], it["content"], it["cmp"], it["diff"], it["drawing_side"], it["action"], it["kind"], None])
r = w1.max_row
for c in range(1, 9):
cell = w1.cell(row=r, column=c)
cell.border = BR
cell.alignment = TOPW
w1.cell(row=r, column=8).fill = FILL[it["kind"]]
w1.cell(row=r, column=1).font = Font(bold=True)
if it.get("image") and os.path.exists(it["image"]):
im = PImage.open(it["image"])
h = MAXH
w = int(im.width * h / im.height)
xi = XImage(it["image"])
xi.width, xi.height = w, h
w1.add_image(xi, "I%d" % r)
w1.row_dimensions[r].height = h * 0.75 + 6
w1.column_dimensions["I"].width = max(w1.column_dimensions["I"].width or 0, w / 7.0)
w1.cell(row=r, column=9).border = BR
for i, w in enumerate([8, 12, 62, 24, 26, 34, 52, 12], 1):
w1.column_dimensions[get_column_letter(i)].width = w
w1.freeze_panes = "A5"
# ------------------------------------------------ ② 전체 대조표
w2 = wb.create_sheet("② 전체 대조표")
w2.append(["② 도면(재료표·철근상세) · 라이노 모델 대조표 — 전 마크"])
w2["A1"].font = Font(bold=True, size=13)
w2.append(["구분 열 색: 초록=일치 / 주황=도면↔모델 / 진한 노랑=기준↔모델 / 회색=미확인. 모델 치수는 중심선 꼭짓점 사이 거리(각진 치수), 길이는 모델 철근의 설계길이(user string)."])
w2.append([])
h2 = ["번호", "마크", "검토 항목", "도면 재료표 (L × N = 총길이)", "도면 철근상세 (치수)", "라이노 모델", "판정", "비고 / 근거", "구분"]
w2.append(h2)
for c in range(1, 10):
cell = w2.cell(row=4, column=c)
cell.font = Font(bold=True)
cell.fill = HF
cell.border = BR
cell.alignment = CEN
for r_ in cfg["rows"]:
w2.append([r_["no"], r_["mark"], r_["item"], r_["table"], r_["detail"], r_["model"], r_["verdict"], r_["note"], r_["kind"]])
r = w2.max_row
for c in range(1, 10):
cell = w2.cell(row=r, column=c)
cell.border = BR
cell.alignment = TOPW
w2.cell(row=r, column=9).fill = FILL[r_["kind"]]
n = max(len(str(r_["note"])) / 40.0, len(str(r_["model"])) / 34.0, len(str(r_["detail"])) / 30.0, 1)
w2.row_dimensions[r].height = max(32, 17 * (int(n) + 1))
for i, w in enumerate([6, 9, 26, 30, 32, 38, 14, 52, 12], 1):
w2.column_dimensions[get_column_letter(i)].width = w
w2.freeze_panes = "C5"
# ------------------------------------------------ ③ 미검토
w3 = wb.create_sheet("③ 미검토·확인 불가")
w3.append(["항목", "내용", "비고", "상태"])
for c in range(1, 5):
w3.cell(row=1, column=c).font = Font(bold=True)
w3.cell(row=1, column=c).fill = HF
for u in cfg["unchecked"]:
w3.append([u["item"], u["content"], u["note"], u["status"]])
for c in range(1, 5):
w3.cell(row=w3.max_row, column=c).alignment = TOPW
w3.row_dimensions[w3.max_row].height = 50
for i, w in enumerate([22, 80, 40, 12], 1):
w3.column_dimensions[get_column_letter(i)].width = w
# ------------------------------------------------ ④ 측정 근거
w4 = wb.create_sheet("④ 측정 근거")
w4.append(["항목", "측정/내용", "비고"])
for c in range(1, 4):
w4.cell(row=1, column=c).font = Font(bold=True)
w4.cell(row=1, column=c).fill = HF
for b in cfg["basis"]:
w4.append([b["item"], b["content"], b.get("note", "")])
for c in range(1, 4):
w4.cell(row=w4.max_row, column=c).alignment = TOPW
w4.row_dimensions[w4.max_row].height = 48
for i, w in enumerate([30, 95, 52], 1):
w4.column_dimensions[get_column_letter(i)].width = w
try:
wb.save(OUT)
print("saved", OUT)
except PermissionError:
print("열려 있어 저장 실패:", OUT)
+51
View File
@@ -0,0 +1,51 @@
import json,re,io
S=json.load(open('ok_shapes.json',encoding='utf-8'))
Mo=json.load(open('ok_model_dims.json',encoding='utf-8'))
T=json.load(open('ok_texts.json',encoding='utf-8'))
# 표
rows={}
for x,y,l,t,h in T:
if l!='CS-TABL-TEX2' or not(324000<y<340000) or x<715000: continue
rows.setdefault(round(y/10),{})[round(x)]=t.strip()
tab={}
for y in sorted(rows,reverse=True):
r=rows[y]
if 715129 in r and re.match(r'^[A-Z]{1,2}\d+$',r[715129]):
tab[r[715129]]={'dia':r.get(715879),'L':float(r[717004])*1000,'n':int(r[717629]),'tot':float(r[719004])}
json.dump(tab,open('ok_table.json','w',encoding='utf-8'),ensure_ascii=False)
def mm(s):
s=s.strip()
out=[]
for p in s.split('~'):
p=re.sub(r'[^\d.]','',p)
if not p: continue
v=float(p); out.append(v*1000 if '.' in p and v<100 else v)
return out
D={}
for e in S:
if e['mark'] in D: continue
D[e['mark']]=e
o=io.open('ok_analysis.txt','w',encoding='utf-8')
for mk,t in tab.items():
m=Mo.get(mk)
if not m:
o.write(u'%s 모델 없음 table n=%d L=%.0f\n'%(mk,t['n'],t['L'])); continue
n=m['n']//(2 if mk in('P3','P4') else 1)
dl=m['dl']; mean=sum(dl)/len(dl)
if mk in ('P3','P4'): mean*=2
tot=sum(dl)/1000.0
if mk in ('P3','P4'): pass
line=u'%s n 표%d/모델%d L 표%.0f 모델평균%.0f(min%.0f max%.0f) 총 표%.3f 모델%.3f'%(mk,t['n'],n,t['L'],mean,min(dl)*(2 if mk in('P3','P4') else 1),max(dl)*(2 if mk in('P3','P4') else 1),t['tot'],tot)
d=D.get(mk)
if d:
miss=[]
vals=m['values']
for s in d['dims']:
k,_,v=s.partition('=')
k=k.strip()
if k in('j','r','d') and False: continue
if k.startswith('j') or k.startswith('x'): continue
for val in mm(v):
if not any(abs(val-q)<=3 for q in vals): miss.append('%s=%s'%(k,v))
line+=u' | 도면 %s | 모델값 %s | 못 찾음 %s'%(' '.join(d['dims']), m['values'][:14], miss)
o.write(line+u'\n')
+101
View File
@@ -0,0 +1,101 @@
import sys, os, json
sys.path.insert(0, r"C:\Users\User\AppData\Local\Temp\claude\D--Rhino-MCP-rhino-mcp-main\f46b47bc-9105-4079-9b62-fcbee31b26ae\scratchpad")
sys.path.insert(0, r"C:\Users\User\AppData\Local\Temp\claude\D--Rhino-MCP-rhino-mcp-main\f46b47bc-9105-4079-9b62-fcbee31b26ae\scratchpad\pylib")
from PIL import Image
import compose_new as C
SCR = r"C:\Users\User\AppData\Local\Temp\claude\D--Rhino-MCP-rhino-mcp-main\f46b47bc-9105-4079-9b62-fcbee31b26ae\scratchpad"
CR = SCR + r"\ok_crops"
SH = SCR + r"\shots"
OUT = r"D:\★Pier 자료\심수석님 검토 파일\검토_옥성교P1\도면↔모델_불일치_시각자료"
os.makedirs(OUT, exist_ok=True)
def stack(names, out):
ims = [Image.open(CR + "\\" + n + ".png").convert("RGB") for n in names]
w = max(i.width for i in ims)
h = sum(i.height for i in ims)
m = Image.new("RGB", (w, h), (34, 40, 49))
y = 0
for i in ims:
m.paste(i, (0, y))
y += i.height
m.save(CR + "\\" + out + ".png")
return CR + "\\" + out + ".png"
def cr(n): return CR + "\\" + n + ".png"
def sh(n): return SH + "\\" + n + ".png"
tabs = {
"tab_UM": ["row_head", "row_UM"],
"tab_C1": ["row_head", "row_C1", "row_C2", "row_C3", "row_C4", "row_C5", "row_C6"],
"tab_C7": ["row_head", "row_C7"],
"tab_C8": ["row_head", "row_C8", "row_C9", "row_C10", "row_C11", "row_C12", "row_C13"],
"tab_C14": ["row_head", "row_C14", "row_C15", "row_C16", "row_C17"],
"tab_C18": ["row_head", "row_C18", "row_C19", "row_C20"],
}
for k, v in tabs.items():
stack(v, k)
items = []
items.append(dict(code="M-1", title="UM 기초 조립용 철근 모델 누락", subtitle="도면 철근상세·재료표: UM H22 L 4.504 m × 92본 (총 414.368 m) ↔ 모델: UM 없음 (0본)", color=C.ORANGE,
panels=[dict(label="도면 기초 조립용 철근 상세 · 재료표 UM 행", color=C.BLUE, maxh=520,
images=[dict(path=cr("um_detail"), cap="기초 조립용 철근 상세 (70° 사각 설치, 예시도)"), dict(path=cr("tab_UM"), cap="재료표 UM 행: L 4.504 · N 92 · 총 414.368")]),
dict(label="모델 (라이노) 기초 철근 B1~B12 — UM 없음", color=C.ORANGE, maxh=620, images=[dict(path=sh("O1"))])],
rows=[("차이", "모델에 UM(H22) 마크가 없음. 도면 재료표 H22 소계 1345.896 m 중 UM 414.368 m 가 모델 총길이에서 빠짐."),
("도면 쪽", "철근상세는 형상(a 200 · b 1.952 · c 200)과 '70° 사각 설치' NOTE 만 있고, 92본의 배치 위치·간격 근거는 예시도뿐."),
("조치", "UM 92본을 모델에 넣을지 판단 필요 (운곡교 UA1 과 같은 유형).")]))
items.append(dict(code="M-2", title="j 길이(겹침)가 재료표 L 에만 포함", subtitle="재료표 L = a+b+c+j ↔ 모델 철근은 a+b+c (j 형상 없음) · C1~C7 은 모델 설계길이에도 j 제외", color=C.ORANGE,
panels=[dict(label="도면 철근상세 C1 (a·b·c + j 칸) · 재료표 C1~C6", color=C.BLUE, maxh=520,
images=[dict(path=cr("cell_C1"), cap="C1: L 15.754 = 11.750 + 1.247 + 1.247 + j 1.510"), dict(path=cr("tab_C1"), cap="재료표 C1~C6 (L 15.754 / 13.460)")]),
dict(label="모델 (라이노) 정면도 방향 · C1~C6 + 코핑", color=C.ORANGE, maxh=520, images=[dict(path=sh("O2"), cap="모델 C1: 1.247 + 11.750 + 1.247 = 14.244 (j 없음)")])],
rows=[("차이", "C1~C6 모델 총길이가 재료표보다 113.25 m 짧음 (C1·C2·C3 각 -19.630, C4·C5·C6 각 -18.120), C7 -17.940. C1~C7 의 모델 설계길이(user string)도 j 를 뺀 값(14.244)."),
("다른 마크", "B1~B8(j 1.530/1.070)·B10·B11·B12(j 0.560)·C14·C16(j 0.550×4)·C17(j 0.550)은 설계길이에는 j 가 포함돼 총길이는 재료표와 같지만, 형상(중심선)은 j 만큼 짧음. 모델 형상에 없는 j 합계 약 600 m."),
("조치", "j(겹침 길이)는 도면 상세에서 작은 박스로만 표시되고 작도 위치가 없음 → 모델 형상에 넣을지, 재료표 가산 길이로만 둘지 판단 필요. C1~C7 만 설계길이에서 j 가 빠진 점은 확인 필요.")]))
items.append(dict(code="M-3", title="C7 바깥 2본의 형상이 도면과 다름", subtitle="도면 C7: a 606 · b 4.774 · c 2.662 · d 4.774 · e 606, x1·x2 162° (13본 동일) ↔ 모델 11본 일치 + 바깥 2본(y ±1525) 다름", color=C.ORANGE,
panels=[dict(label="도면 철근상세 C7 · 재료표 C7", color=C.BLUE, maxh=480, images=[dict(path=cr("cell_C7")), dict(path=cr("tab_C7"))]),
dict(label="모델 (라이노) 정면도 방향 · C7 + 코핑", color=C.ORANGE, maxh=480,
images=[dict(path=sh("O3a"), cap="바깥 2본 (y ±1525): a·e 다리 없음, b·d 4.309"), dict(path=sh("O3b"), cap="나머지 11본: 도면 형상(606 / 4.774 / 2.662)")])],
rows=[("차이", "모델 C7 13본 중 y=+1525, -1525 두 본은 끝 다리 a·e(606)가 없고 비스듬한 부분 b·d 가 4.309 (도면 4.774). 길이 11.280 (나머지 13.424)."),
("도면 쪽", "철근상세는 N=13 에 형상 하나만 표기, 길이 14.474 (j 1.050 포함)."),
("조치", "바깥 2본 형상을 도면(동일 형상)대로 맞출지, 의도된 단축인지 확인 필요.")]))
items.append(dict(code="M-4", title="C8~C13 스터럽 치수·끝 길이 c 630", subtitle="철근상세 C8: a 2.057 · b 2.808~1.388 · c 630 ↔ 모델 폐합 직사각형 a 2.103 (c 630 없음)", color=C.ORANGE,
panels=[dict(label="도면 철근상세 C8 · C9 · 재료표 C8~C13", color=C.BLUE, maxh=480,
images=[dict(path=cr("cell_C8")), dict(path=cr("cell_C9")), dict(path=cr("tab_C8"))]),
dict(label="모델 (라이노) 측면(Y-Z) · C8 · C9 각 1본 + 코핑", color=C.ORANGE, maxh=520,
images=[dict(path=sh("O4"), cap="C8 (모델): 폭 2.103, 높이 b 변화"), dict(path=sh("O4b"), cap="C9 (모델)")])],
rows=[("차이", "(1) 스터럽 폭: 철근상세 a 2.057 ↔ 모델 2.103 (+46). (2) 끝 길이 c 630(2개 합 1.260)이 모델 폐합 형상에 없음. (3) 총길이 모델-재료표: C8 -40.8, C9 -86.6, C10 -30.6, C11 -24.4, C12 -41.8, C13 -35.0 m (합 -259 m)."),
("도면 쪽", "재료표 L 은 b 범위의 평균 기준(예: C8 9.570 = 2×2.057 + 2×2.098 + 2×0.630). 모델은 철근마다 b 가 달라 길이가 6.981~9.822 로 다름."),
("조치", "c 630(겹침·갈고리)을 모델에 그릴지, a 2.057 ↔ 2.103 어느 쪽이 맞는지 판단 필요.")]))
items.append(dict(code="M-5", title="C14~C17 루프·타이: a 가산 54, b·갈고리", subtitle="C14·C16 a 모델 = 도면 -54 · b 3.207 단일 ↔ 모델 변화 · C15·C17 끝 갈고리 b 188 · c 140 이 모델에 없음 (직선)", color=C.ORANGE,
panels=[dict(label="도면 철근상세 C14~C17 · 재료표", color=C.BLUE, maxh=420,
images=[dict(path=cr("cell_C14")), dict(path=cr("cell_C15")), dict(path=cr("tab_C14"))]),
dict(label="모델 (라이노) 평면 · 정면 C14~C17 + 코핑", color=C.ORANGE, maxh=520,
images=[dict(path=sh("O5"), cap="평면: C14·C16 루프 / C15·C17 타이"), dict(path=sh("O5b"), cap="정면: C15·C17 (직선, 끝 갈고리 없음)")])],
rows=[("차이", "C14 a 도면 10.475~3.496 ↔ 모델 10.421~3.442 (-54, 가산 규칙). C14·C16 b 도면 3.207 ↔ 모델 2.343~2.974(a 에 따라 변함). C15·C17 은 모델이 직선 3.442~10.421 / 11.750, 도면은 b 188 · c 140 갈고리. 총길이 모델-재료표: C14 -22.5, C15 -23.0, C16 -14.4, C17 -30.0 m."),
("조치", "C14~C17 의 갈고리(b·c)·j 550 반영 여부와 b 단일값 ↔ 모델 변화 판단 필요.")]))
items.append(dict(code="M-6", title="C18 a 3.050 단일값 ↔ 모델 개별 길이", subtitle="철근상세 C18: a 3.050 · b 370 · L 3.790 (117본) ↔ 모델 a 3.207 / 3.126 / 2.565 (3종)", color=C.ORANGE,
panels=[dict(label="도면 철근상세 C18 · 재료표 C18~C20", color=C.BLUE, maxh=420, images=[dict(path=cr("cell_C18")), dict(path=cr("tab_C18"))]),
dict(label="모델 (라이노) 측면(Y-Z) · C18 1본 + 코핑", color=C.ORANGE, maxh=520, images=[dict(path=sh("O6"))])],
rows=[("차이", "모델 C18 은 99본 3.207, 13본 3.126, 5본 2.565. 총길이 모델 457.732 / 재료표 443.430 (+14.3 m). C20 도 a 2.721~2.472 범위와 일치하나 총길이 +3.1 m."),
("조치", "도면은 a 단일값이라 모델의 개별 길이가 의도인지 확인 필요.")]))
items.append(dict(code="M-7", title="굽힘 내면지름 H25·H22·H19 KDS 3.5db 미달", subtitle="KDS 24 14 21 표 4.5-1: 내면지름 ≥ 3.5db · 도면에는 내면반지름 표기 없음", color=C.DYEL,
panels=[dict(label="모델 (라이노) C8 스터럽 모서리 확대", color=C.DYEL, maxh=560, images=[dict(path=sh("O7"), cap="C8 (H25) 굽힘: 중심선 R 56.2 → 내면지름 87.0")])],
rows=[("계산", "H25(C8~C13): 내면지름 87.0 < 3.5×25.4 = 88.9 (1.9 mm 부족). H22(C14~C17 등): 76.8 < 77.7 (0.9 부족). H19(B9~B12·C18~C20·S1·S2): 66.5 < 66.85 (0.35 부족). H29(B1~B8) 101.8 ≥ 100.1, H32(C1~C6) 112.2 ≥ 111.3 은 충족. H16·H13 은 2db 로 충족."),
("조치", "도면에 반지름이 없어 모델 R 을 KDS 기준으로 키울지 결정 필요 (기준↔모델).")]))
items.append(dict(code="M-8", title="P3·P4 이음(커플러) 각도·위치 — 도면 기준 없음", subtitle="철근상세 P3 a 3.533 · b 80, P4 a 3.538 · b 65 ↔ 모델은 반원 2조각의 90°/270° 위치로 가정", color=C.GREY,
panels=[dict(label="도면 철근상세 P3 · P4 (이음 위치·각도 표기 없음)", color=C.BLUE, maxh=420, images=[dict(path=cr("cell_P3")), dict(path=cr("cell_P4"))]),
dict(label="모델 (라이노) 평면 · P3·P4 링 (이음 틈 2곳)", color=C.GREY, maxh=520, images=[dict(path=sh("O8"))])],
rows=[("내용", "철근상세에 링 이음의 방향·엇갈림이 없어 모델이 임의로 90°/270° 에 커플러(P3 80, P4 65)를 둠. 도면으로 확인 불가."),
("조치", "각도 기준 확인 필요 (설계자).")]))
names = {"M-1": "M-1_UM_조립철근누락", "M-2": "M-2_j길이_미작도", "M-3": "M-3_C7_바깥2본형상", "M-4": "M-4_C8-C13_스터럽", "M-5": "M-5_C14-C17_루프타이",
"M-6": "M-6_C18_단일값", "M-7": "M-7_굽힘내면지름", "M-8": "M-8_P3P4_이음각도"}
res = {}
for it in items:
it["out"] = OUT + "\\" + names[it["code"]] + ".png"
res[it["code"]] = it["out"]
print(it["code"], C.make(it))
json.dump(res, open(SCR + r"\ok_images.json", "w", encoding="utf-8"), ensure_ascii=False)
+137
View File
@@ -0,0 +1,137 @@
import json, re, os
SCR = r"C:\Users\User\AppData\Local\Temp\claude\D--Rhino-MCP-rhino-mcp-main\f46b47bc-9105-4079-9b62-fcbee31b26ae\scratchpad"
tab = json.load(open(SCR + r"\ok_table.json", encoding="utf-8"))
S = json.load(open(SCR + r"\ok_shapes.json", encoding="utf-8"))
Mo = json.load(open(SCR + r"\ok_model_dims.json", encoding="utf-8"))
IM = json.load(open(SCR + r"\ok_images.json", encoding="utf-8"))
_t = {}
for k, v in tab.items():
_t[k] = v
if k == "S4":
_t["UM"] = {"dia": "H22", "L": 4504.0, "n": 92, "tot": 414.368}
tab = _t
D = {}
for e in S:
D.setdefault(e["mark"], e)
HALF = ("P3", "P4")
J = {"C1": "1.510", "C2": "1.510", "C3": "1.510", "C4": "1.510", "C5": "1.510", "C6": "1.510", "C7": "1.050",
"B1": "1.530", "B2": "1.070", "B3": "1.530", "B4": "1.070", "B5": "1.530", "B6": "1.070", "B7": "1.530", "B8": "1.070",
"B10": "0.560×6", "B11": "0.560", "B12": "0.560", "C14": "0.550×4", "C16": "0.550×4", "C17": "0.550"}
KDS = {"C8": "H25", "C9": "H25", "C10": "H25", "C11": "H25", "C12": "H25", "C13": "H25", "C14": "H22", "C16": "H22",
"B9": "H19", "B10": "H19", "B11": "H19", "B12": "H19", "C18": "H19", "C19": "H19", "C20": "H19", "S1": "H19", "S2": "H19"}
KDS_TXT = {"H25": "[M-7] H25 굽힘 내면지름 87.0 < 3.5db 88.9 (1.9 mm 부족).", "H22": "[M-7] H22 굽힘 내면지름 76.8 < 77.7 (0.9 mm 부족).",
"H19": "[M-7] H19 굽힘 내면지름 66.5 < 66.85 (0.35 mm 부족)."}
V = {}
for mk in ("C1", "C2", "C3", "C4", "C5", "C6"):
d = {"C1": "-19.630", "C2": "-19.630", "C3": "-19.630", "C4": "-18.120", "C5": "-18.120", "C6": "-18.120"}[mk]
V[mk] = ("차이 (j)", "[M-2] 모델 설계길이 = a+b+c (재료표 L 은 j 1.510 포함). 모델 총길이 %s m." % d, "도면↔모델")
V["C7"] = ("차이", "[M-3] 바깥 2본(y ±1525) 형상 다름(a·e 606 없음, b·d 4.309). [M-2] j 1.050 제외. 모델 총길이 -17.940 m.", "도면↔모델")
for mk in ("B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8"):
V[mk] = ("일치 (j 형상 없음)", "a·b 일치, 설계길이 = 재료표 L. [M-2] j %s 는 모델 형상에 없음." % J[mk], "도면↔모델")
V["B9"] = ("일치 (경미)", "철근상세 a 586~548 범위 ↔ 모델 567 단일 (재료표 L 4.763 은 567 기준). 길이·본수 일치.", "일치")
V["B10"] = ("일치 (경미)", "a·b 9.448 ↔ 모델 9.445 (3 mm). [M-2] j 0.560×6 은 모델 형상에 없음.", "도면↔모델")
V["B11"] = ("일치 (j 형상 없음)", "a·b 일치. [M-2] j 0.560 은 모델 형상에 없음.", "도면↔모델")
V["B12"] = V["B11"]
for mk in ("C8", "C9", "C10", "C11", "C12", "C13"):
V[mk] = ("차이", "[M-4] 철근상세 a·c(630)·d 가 모델 폐합 형상과 다름, 재료표 L 은 b 평균 기준, 모델은 철근마다 b 가 달라 길이 개별.", "도면↔모델")
V["C14"] = ("차이", "[M-5] a 도면 10.475~3.496 = 모델 +54, b 3.207 단일 ↔ 모델 변화. [M-2] j 0.550×4 는 형상에 없음.", "도면↔모델")
V["C16"] = ("차이", "[M-5] a 11.804 = 모델 11.750 +54, b 3.207 ↔ 모델 3.160. [M-2] j 0.550×4 는 형상에 없음.", "도면↔모델")
V["C15"] = ("차이", "[M-5] 모델 직선 3.442~10.421, 도면 b 188 · c 140 끝 갈고리 없음.", "도면↔모델")
V["C17"] = ("차이", "[M-5] 모델 직선 11.750, 도면 a 11.794 · b 188 · c 140 갈고리 없음. j 0.550 형상 없음.", "도면↔모델")
V["C18"] = ("차이", "[M-6] 도면 a 3.050 단일 ↔ 모델 a 3.207/3.126/2.565 (총길이 +14.302 m).", "도면↔모델")
V["C19"] = ("일치", "a 2.226 · b 370 일치.", "일치")
V["C20"] = ("일치 (경미)", "a 2.721~2.472 범위가 모델과 일치, 평균 3.008 ↔ 재료표 L 2.967 (총길이 +3.095 m).", "일치")
V["P1"] = ("일치", "a 8.313 · b 424(호) · c 500 · r 270 일치.", "일치")
V["P2"] = V["P1"]
V["P3"] = ("일치 (각도 미확인)", "a 3.533 · b 80 일치(반원 2조각 = 1본 환산). [M-8] 이음 각도 도면에 없음.", "미확인")
V["P4"] = ("일치 (각도 미확인)", "a 3.538 · b 65 일치(반원 2조각 = 1본 환산). [M-8] 이음 각도 도면에 없음.", "미확인")
V["P5"] = ("미확인", "a 160 · b 2.341 일치, 끝 길이 c 141 · d 80 이 모델(150 / 갈고리 90°·135°)과 정의가 달라 확인 불가.", "미확인")
V["S1"] = ("일치", "a 2.629 · b 700 일치.", "일치")
V["S2"] = ("일치", "a 1.262 · b 700 일치.", "일치")
for mk in ("S3", "S4", "S5", "S6", "S7", "S8"):
V[mk] = ("일치", "길이·본수 일치.", "일치")
V["UM"] = ("모델 없음", "[M-1] 기초 조립용 철근. 모델에 0본.", "도면↔모델")
def m3(x):
return "%.3f" % (x / 1000.0)
rows = []
last_dia = None
n = 0
tot_t = 0.0
tot_m = 0.0
for mk, t in tab.items():
n += 1
verdict, note, kind = V[mk]
if mk in KDS:
note = note + " " + KDS_TXT[KDS[mk]]
m = Mo.get(mk)
d = D.get(mk)
dd = list(d["dims"]) if d else []
if mk in ("C1", "C2", "C3", "C4", "C5", "C6", "C8", "C10", "C12"):
dd = [x for x in dd if not x.startswith("r=")]
if mk == "C7" and d:
dd += d["dims2"]
detail = " ".join(dd) if d and dd else ("(철근상세 치수 없음: 직선)" if mk in ("S3", "S4", "S7", "S8") else "-")
if mk == "UM" and d:
detail = " ".join(d["dims"])
if m:
k = 2 if mk in HALF else 1
cnt = m["n"] // k
dl = [x * k for x in m["dl"]]
mean = sum(dl) / len(dl)
tot = sum(dl) / 1000.0 / k
tot = sum(m["dl"]) / 1000.0
model = "N %d · 설계길이 %s m" % (cnt, m3(mean)) if min(dl) == max(dl) else "N %d · 설계길이 %s~%s m (평균 %s)" % (cnt, m3(min(dl)), m3(max(dl)), m3(mean))
model += " · 총 %.3f m" % tot
vals = m["values"]
if mk not in HALF and vals:
sv = [str(int(x)) for x in vals[:6]]
model += " · 꼭짓점 치수 " + "/".join(sv) + ("…" if len(vals) > 6 else "")
tot_m += tot
else:
model = "모델 없음 (0본)"
tot_t += t["tot"]
if t["dia"] not in ('"', None):
last_dia = t["dia"]
dia = Mo[mk]["dia"] if mk in Mo else last_dia
table = "L %s × N %d = %.3f m" % (m3(t["L"]), t["n"], t["tot"])
rows.append(dict(no=str(n), mark=mk, item="형상·길이·본수 (%s)" % dia, table=table, detail=detail, model=model, verdict=verdict, note=note, kind=kind))
items = [
dict(code="M-1", mark="UM", content="UM(H22, 기초 조립용 철근) 92본 모델에 없음", cmp="재료표·철근상세 ↔ 모델", diff="L 4.504 m × 92본 = 414.368 m 누락", drawing_side="상세·재료표에 있음 (a 200 · b 1.952 · c 200, 70° 사각 설치). 배치 위치·간격은 예시도뿐", action="모델에 넣을지 판단 필요 (운곡교 UA1 과 같은 유형)", kind="도면↔모델", image=IM["M-1"]),
dict(code="M-2", mark="C1~C7, B1~B8, B10~B12, C14·C16·C17", content="j(겹침) 길이가 재료표 L 에만 있고 모델 형상에는 없음. C1~C7 은 모델 설계길이에도 j 제외", cmp="재료표 L = a+b+c+j ↔ 모델 a+b+c", diff="C1~C6 총길이 -113.25 m, C7 -17.94 m. 그 외 마크는 설계길이 포함, 형상에서만 j 만큼 짧음(합 약 600 m)", drawing_side="j 는 상세의 작은 박스로만 표시, 작도 위치 없음", action="형상에 넣을지 가산 길이로만 둘지 판단. C1~C7 만 설계길이에서 j 가 빠진 점 확인", kind="도면↔모델", image=IM["M-2"]),
dict(code="M-3", mark="C7", content="C7 바깥 2본(y ±1525) 형상이 도면(13본 동일)과 다름", cmp="철근상세 C7 ↔ 모델", diff="a·e 606 없음, b·d 4.309 (도면 4.774), 길이 11.280 (나머지 13.424)", drawing_side="N=13 에 형상 하나, L 14.474", action="바깥 2본을 도면 형상대로 맞출지, 의도된 단축인지 확인", kind="도면↔모델", image=IM["M-3"]),
dict(code="M-4", mark="C8~C13", content="스터럽 폭 a(2.057 ↔ 2.103)와 끝 길이 c 630 이 모델 폐합 형상과 다름", cmp="철근상세 ↔ 모델", diff="총길이 C8 -40.8, C9 -86.6, C10 -30.6, C11 -24.4, C12 -41.8, C13 -35.0 m (합 -259 m)", drawing_side="재료표 L 은 b 범위 평균 기준(예 C8 9.570 = 2×2.057 + 2×2.098 + 2×0.630)", action="c 630(겹침·갈고리) 모델 반영 여부, a 2.057 ↔ 2.103 어느 쪽이 맞는지 판단", kind="도면↔모델", image=IM["M-4"]),
dict(code="M-5", mark="C14~C17", content="루프 a 가산 54, b 단일 3.207 ↔ 모델 변화, C15·C17 끝 갈고리(b 188 · c 140) 없음", cmp="철근상세 ↔ 모델", diff="총길이 C14 -22.5, C15 -23.0, C16 -14.4, C17 -30.0 m", drawing_side="C14 a 10.475~3.496, b 3.207, j 0.550×4 / C15 b 188 · c 140", action="갈고리·j 반영 여부, b 단일값 ↔ 모델 변화 판단", kind="도면↔모델", image=IM["M-5"]),
dict(code="M-6", mark="C18", content="a 3.050 단일값 ↔ 모델 3종 (3.207 / 3.126 / 2.565)", cmp="철근상세 ↔ 모델", diff="총길이 +14.302 m (모델 457.732 / 재료표 443.430)", drawing_side="a 3.050 · b 370 · L 3.790 · 117본", action="모델의 개별 길이가 의도인지 확인", kind="도면↔모델", image=IM["M-6"]),
dict(code="M-7", mark="C8~C13, C14·C16, B9~B12, C18~C20, S1·S2", content="굽힘 내면지름: H25 87.0 < 88.9, H22 76.8 < 77.7, H19 66.5 < 66.85 (3.5db)", cmp="KDS 24 14 21 표 4.5-1 ↔ 모델", diff="1.9 / 0.9 / 0.35 mm 부족", drawing_side="도면에 내면반지름 표기 없음", action="모델 R 을 KDS 기준으로 키울지 결정", kind="기준↔모델", image=IM["M-7"]),
dict(code="M-8", mark="P3, P4", content="링 이음(커플러) 각도·위치 도면에 기준 없음 (모델 90°/270° 가정)", cmp="도면 없음 ↔ 모델 가정", diff="-", drawing_side="철근상세 a·b 뿐, 방향·엇갈림 없음", action="각도 기준 확인 (설계자)", kind="미확인", image=IM["M-8"]),
]
cfg = dict(
bridge="옥성교 P1",
title="옥성교 P1 철근 도면 ↔ 모델 일치 검토 (불일치 종류별 분류)",
purpose="목적: 도면(재료표·철근상세)과 라이노 모델이 일치하는지가 최우선. 범위는 모델↔도면 (도면 내부 불일치는 따로 보관). 작성 2026-10-19, 검토 미확정 초안. 기준: KDS 24 14 21.",
sheet1_desc="도면에 기준이 있거나 없는데 모델이 다르게 만들어진 항목 (M-n). 캡처: 위 도면(철근상세·재료표) / 아래 모델(라이노).",
items=items, rows=rows,
unchecked=[
dict(item="코핑·기둥·기초 철근 위치", content="정면도·단면도의 철근 높이·간격·피복(점 좌표)과 모델 위치 대조", note="재료표·철근상세(형상·길이·본수) 대조만 완료", status="미검토"),
dict(item="받침 세트 10개", content="받침대좌·패드·교량받침·솔플레이트 (ELEVATION TABLE · 솔플레이트 TABLE) 모델 대조", note="선행 보고서 G5 183/183 PASS (표 값 기준)", status="미검토"),
dict(item="코핑 철근 간 간섭", content="철근끼리 교차·관통", note="사용자 지시로 제외 (검토 단계)", status="제외"),
dict(item="P5 갈고리 c 141 · d 80", content="철근상세의 끝 길이 정의가 모델(90°·135° 갈고리)과 맞춰지지 않음", note="도면으로 확인 불가", status="확인 불가"),
],
basis=[
dict(item="입력 파일", content="모델: 옥성교P1_G5.3dm (원본 그대로), 도면: 옥성교P1.dwg → 옥성교P1.dxf (라이노 변환)"),
dict(item="재료표", content="DXF 레이어 CS-TABL-TEX2 의 글자를 x(열)·y(행)로 읽음 (번호 x≈715129, 직경 715879, 길이 717004, 개수 717629, 총길이 719004). 재료표에는 A~J 치수 열이 없음 — 치수는 '철근상세'(레이어 CS-TEXT, 형상 도해 옆 L= N= a= b= c= j=)에서 읽음"),
dict(item="모델 치수", content="중심선(P1-RC-*)의 직선 구간을 연장한 꼭짓점 사이 거리(각진 치수)로 환산. P1·P2·P3·P4 는 호 길이/접선 길이로 비교. P3·P4 는 반원 2조각 = 1본으로 환산"),
dict(item="길이 비교", content="모델 철근의 설계길이(user string design_length + coupler_length)와 재료표 L·총길이를 대조. 형상 실길이(중심선)는 굽힘으로 약간 짧아 비교하지 않음"),
dict(item="KDS 계산", content="내면지름 = 2×(중심선 R − 공칭 db/2). H25 R 56.2 → 87.0, H22 49.5 → 76.8, H19 42.8 → 66.5, H29 65.2 → 101.8, H32 72 → 112.2, H16 24 → 32.1, H13 해당 없음. 기준: D19 이상 3.5db, D16 이하 2db (KDS 24 14 21 표 4.5-1)"),
dict(item="재료표 총계", content="도면 총계 13806.206 m (소계 합). 모델 설계길이 합 %.3f m (UM 제외 시 도면 %.3f m)" % (tot_m, tot_t - tab["UM"]["tot"])),
],
)
json.dump(cfg, open(SCR + r"\ok_cfg.json", "w", encoding="utf-8"), ensure_ascii=False, indent=1)
print(len(rows), tot_t, tot_m)
+47
View File
@@ -0,0 +1,47 @@
import sys, json, re, math, collections
sys.path.insert(0,'pylib')
import rhino3dm
M = r"D:\★Pier 자료\심수석님 검토 파일\검토_옥성교P1\옥성교P1_G5.3dm"
m3 = rhino3dm.File3dm.Read(M)
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 cseq(g):
lines=[(V(s.PointAtStart),V(s.PointAtEnd)) for s in segs_of(g) if is_line(s)]
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)]
bars=collections.defaultdict(dict)
lay={l.Index:l.FullPath for l in m3.Layers}
for o in m3.Objects:
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)
elif nm.startswith("P1-RC-"):
p=nm.split("-"); bars[p[2]].setdefault("-".join(p[3:]),{})["g"]=g
out={}
for mk,dd in bars.items():
items=[b for b in dd.values() if 'us' in b and 'g' in b]
seqs=[[round(x) for x in cseq(b['g'])] for b in items]
allv=sorted(set(v for s in seqs for v in s))
out[mk]={'n':len(items),'dl':[float(b['us']['design_length'])+float(b['us']['coupler_length'] or 0) for b in items],
'seqs':seqs,'values':allv,'shape':items[0]['us']['shape'],'dia':items[0]['us']['dia'],
'cl':[0]*0}
json.dump(out,open('ok_model_dims.json','w',encoding='utf-8'),ensure_ascii=False)
print('ok',len(out))
+23
View File
@@ -0,0 +1,23 @@
import json, subprocess, sys, re
F = r"D:\★Pier 자료\심수석님 검토 파일\검토_옥성교P1\옥성교P1.dxf"
S = json.load(open('ok_shapes.json', encoding='utf-8'))
T = json.load(open('ok_texts.json', encoding='utf-8'))
cell = {}
for e in S:
cell.setdefault(e['mark'], (e['x'], e['y']))
rowy = {}
for x, y, l, t, h in T:
if l == 'CS-TABL-TEX2' and round(x) == 715129 and re.match(r'^[A-Z]{1,2}\d+$', t.strip()) and 324000 < y < 340000:
rowy[t.strip()] = y
jobs = []
for mk, (x, y) in cell.items():
jobs.append(('cell_%s' % mk, x - 2100, x + 3700, y - 450, y + 2700, 1500))
for mk, y in rowy.items():
jobs.append(('row_%s' % mk, 714300, 722400, y - 170, y + 190, 1700))
jobs.append(('row_head', 714300, 722400, 339950, 340520, 1700))
jobs.append(('um_detail', 649000, 662500, 318400, 326500, 1700))
jobs.append(('p3p4_ring', 732000, 736800, 331000, 333300, 1400))
for name, x0, x1, y0, y1, w in jobs:
h = int((y1 - y0) / (x1 - x0) * w)
subprocess.run([sys.executable, '-I', 'fastrender.py', F, str(x0), str(x1), str(y0), str(y1), 'ok_crops/%s.png' % name, str(w), 'dark'], check=True)
print(len(jobs))
+70
View File
@@ -0,0 +1,70 @@
import sys, math, collections, json
sys.path.insert(0, 'pylib')
import rhino3dm
M = r"D:\★Pier 자료\심수석님 검토 파일\검토_옥성교P1\옥성교P1_G5.3dm"
m3 = rhino3dm.File3dm.Read(M)
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 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")
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, layer=L)
elif nm.startswith("P1-RC-"):
p = nm.split("-")
bars[p[2]].setdefault("-".join(p[3:]), {})["g"] = g
out = {}
for mk, dd in sorted(bars.items()):
items = [b for b in dd.values() if "us" in b and "g" in b]
if not items:
print(mk, 'no items'); continue
# pick shortest and longest by design length
items.sort(key=lambda b: float(b["us"].get("design_length", 0)))
for tag, b in (("min", items[0]), ("max", items[-1])):
g = b["g"]
segs = segs_of(g)
desc = []
for s in segs:
t = type(s).__name__
if is_line(s):
desc.append("L%.0f" % nrm(sub(V(s.PointAtEnd), V(s.PointAtStart))))
elif t == "ArcCurve":
desc.append("A r%.1f a%.1f" % (s.Arc.Radius, s.Arc.AngleDegrees))
else:
desc.append(t)
print(mk, tag, b["us"].get("shape"), b["us"].get("dia"), "dl", b["us"].get("design_length"), b["us"].get("coupler_length"), "|", " ".join(desc))
print(" src:", b["us"].get("src"))
print("=========== CORNER SEQ")
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 cseq(g):
lines = [(V(s.PointAtStart), V(s.PointAtEnd)) for s in segs_of(g) if is_line(s)]
if len(lines) < 2: return [round(nrm(sub(lines[0][1], lines[0][0])))] if lines else []
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 [round(nrm(sub(seq[i+1], seq[i]))) for i in range(len(seq)-1)]
for mk, dd in sorted(bars.items()):
items = [b for b in dd.values() if "us" in b and "g" in b]
seqs = collections.Counter(tuple(cseq(b["g"])) for b in items)
print(mk, len(items), "distinct", len(seqs))
for s, c in sorted(seqs.items())[:3] + sorted(seqs.items())[-2:]:
print(" ", c, s)
+79
View File
@@ -0,0 +1,79 @@
import sys, json, re
sys.path.insert(0, 'pylib')
import ezdxf
SRC = r"D:\★Pier 자료\심수석님 검토 파일\검토_옥성교P1\옥성교P1.dxf"
OUT = r"D:\★Pier 자료\심수석님 검토 파일\검토_옥성교P1\옥성교P1_도면검토_색구분.dxf"
S = json.load(open('ok_shapes.json', encoding='utf-8'))
T = json.load(open('ok_texts.json', encoding='utf-8'))
raw = json.load(open('ok_shape_raw.json', encoding='utf-8'))
cell = {}
for e in S:
cell.setdefault(e['mark'], (e['x'], e['y']))
rowy = {}
for x, y, l, t, h in T:
if l == 'CS-TABL-TEX2' and round(x) == 715129 and 324000 < y < 340000 and re.match(r'^([A-Z]{1,2}\d+|UM)$', t.strip()):
rowy[t.strip()] = y
CODES = {}
def add(code, marks):
for m in marks:
CODES.setdefault(m, []).append(code)
add('M-1', ['UM'])
add('M-2', ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B10', 'B11', 'B12', 'C14', 'C16', 'C17'])
add('M-3', ['C7'])
add('M-4', ['C8', 'C9', 'C10', 'C11', 'C12', 'C13'])
add('M-5', ['C14', 'C15', 'C16', 'C17'])
add('M-6', ['C18'])
add('M-7', ['C8', 'C9', 'C10', 'C11', 'C12', 'C13', 'C14', 'C16', 'B9', 'B10', 'B11', 'B12', 'C18', 'C19', 'C20', 'S1', 'S2'])
add('M-8', ['P3', 'P4'])
CODES.setdefault('P5', []).append('미확인')
def kind(codes):
if any(c in ('M-1', 'M-2', 'M-3', 'M-4', 'M-5', 'M-6') for c in codes):
return 'dm'
if 'M-7' in codes:
return 'kj'
return 'mh'
doc = ezdxf.readfile(SRC)
msp = doc.modelspace()
LAY = {'dm': ('검토_도면모델_주황', 30), 'kj': ('검토_기준모델_진한노랑', 41), 'mh': ('검토_미확인_회색', 9)}
for k, (n, c) in LAY.items():
doc.layers.add(n, color=c)
if 'KR_REVIEW' not in doc.styles:
doc.styles.add('KR_REVIEW', font='malgun.ttf')
def rect(x0, y0, x1, y1, k, w=40):
msp.add_lwpolyline([(x0, y0), (x1, y0), (x1, y1), (x0, y1)], close=True, dxfattribs={'layer': LAY[k][0], 'const_width': w})
def label(x, y, s, k, h=420):
msp.add_text(s, height=h, dxfattribs={'layer': LAY[k][0], 'style': 'KR_REVIEW', 'insert': (x, y)})
n = 0
for mk, codes in CODES.items():
k = kind(codes)
s = '·'.join(codes)
if mk in rowy:
y = rowy[mk]
rect(714800, y - 130, 722300, y + 150, k)
label(722450, y - 130, s, k, 300)
n += 1
if mk in cell:
x, y = cell[mk]
rect(x - 2100, y - 420, x + 3700, y + 330, k)
label(x + 2300, y + 360, s, k, 260)
n += 1
for mk, m_codes in (('C7', 'M-3'), ('C8', 'M-4'), ('C9', 'M-4')):
x, y = cell[mk]
rect(x - 2100, y + 330, x + 3700, y + 2650, 'dm', 30)
label(x - 2000, y + 2700, m_codes + ' ' + mk + ' 형상', 'dm', 300)
# j 글자 (M-2)
for x, y, t, l in raw['texts']:
if x > 600000 and re.match(r'\s*j\s*=', t):
rect(x - 150, y - 120, x + 2100, y + 330, 'dm', 25)
n += 1
# UM 기초 조립용 철근 상세
rect(649500, 318500, 661500, 326500, 'dm', 60)
label(649600, 326700, 'M-1 UM 기초 조립용 철근 (모델에 없음)', 'dm', 450)
# 범례
lx, ly = 690000, 349000
label(lx, ly, '【검토 표시 범례】 도면 ↔ 모델 상이점 (2026-10-19, 미확정)', 'dm', 420)
label(lx, ly - 600, '주황: 도면↔모델 M-1 UM누락 / M-2 j 미작도 / M-3 C7 형상 / M-4 C8~C13 / M-5 C14~C17 / M-6 C18', 'dm', 300)
label(lx, ly - 1000, '진한 노랑: 기준(KDS)↔모델 M-7 굽힘 내면지름', 'kj', 300)
label(lx, ly - 1400, '회색: 미확인 M-8 P3·P4 이음 각도, P5 끝 길이', 'mh', 300)
doc.saveas(OUT)
print('saved', n)
+28
View File
@@ -0,0 +1,28 @@
import sys, re, json, collections
sys.path.insert(0, 'pylib')
import ezdxf
F = r"D:\★Pier 자료\심수석님 검토 파일\검토_옥성교P1\옥성교P1.dxf"
d = ezdxf.readfile(F)
m = d.modelspace()
FONT = re.compile(r'\\f[^;]*;')
texts = []
for e in m:
t = e.dxftype()
if t == 'TEXT':
texts.append((e.dxf.insert.x, e.dxf.insert.y, e.dxf.text, e.dxf.layer))
elif t == 'MTEXT':
s = FONT.sub('', e.text).replace('{', '').replace('}', '').replace('\\P', ' / ')
texts.append((e.dxf.insert.x, e.dxf.insert.y, s, e.dxf.layer))
tags = []
for e in m:
if e.dxftype() == 'INSERT' and e.dxf.name.startswith('CSBNUM1'):
at = [(a.dxf.tag, a.dxf.text) for a in e.attribs]
tags.append((e.dxf.insert.x, e.dxf.insert.y, e.dxf.name, at))
print(len(texts), len(tags))
json.dump({"texts": texts, "tags": tags}, open('ok_shape_raw.json', 'w', encoding='utf-8'), ensure_ascii=False)
# 철근상세 영역(시트 D 의 좌측 도해 x<687000) 에서 L= 로 시작하는 글자
Ls = [t for t in texts if re.match(r'\s*L\s*=', t[2])]
print(len(Ls))
for t in sorted(Ls, key=lambda a: (round(a[0] / 5000), -a[1]))[:80]:
print(round(t[0]), round(t[1]), t[2])
print(tags[:5])
+7
View File
@@ -0,0 +1,7 @@
import sys; sys.path.insert(0,'pylib')
import fitz,glob,os
base='D:/★Pier 자료/심수석님 검토 파일/'
for sub,name in [('검토_산외교P1','산외교'),('검토_옥성교P1','옥성교'),('Ramp-A교P1','RampA')]:
for f in glob.glob(base+sub+'/보고서_*.pdf'):
d=fitz.open(f); t='\n'.join('--- p%d ---\n'%(i+1)+p.get_text() for i,p in enumerate(d))
open('rep_'+name+'.txt','w',encoding='utf-8').write(t); print(name,len(d),len(t))
+80
View File
@@ -0,0 +1,80 @@
import json, re, io
T3 = json.load(open('ra_texts3.json', encoding='utf-8'))
T4 = json.load(open('ra_texts4.json', encoding='utf-8'))
S = json.load(open('ra_shapes.json', encoding='utf-8'))
Mo = json.load(open('ra_model_dims.json', encoding='utf-8'))
def num(s):
s = s.strip().replace(',', '')
try: return float(s)
except Exception: return None
def rows(T, xs, ylo, yhi):
out = {}
for x, y, l, t, h in T:
if l.startswith('CS-TABL') and ylo < y < yhi:
out.setdefault(round(y / 20), {})[round(x)] = t.strip()
return out
tab = {}
order = []
r3 = rows(T3, None, -708000, -699500)
last = None
for y in sorted(r3, reverse=True):
r = r3[y]
if 64862 in r and re.match(r'^[A-Z]\d+$', r[64862]):
dia = r.get(65762)
if dia == '"': dia = last
last = dia
tab[r[64862]] = {'dia': dia, 'L': num(r[67162]), 'n': int(r[67862]), 'tot': num(r[69562])}
order.append(r[64862])
# 커플러 표 (P1 54, P1-1 61) 는 제외
r4 = rows(T4, None, -707200, -705500)
for y in sorted(r4, reverse=True):
r = r4[y]
for mx, dx, lx, nx, tx in ((89586, 90486, 91886, 92586, 94286), (99898, 100798, 102198, 102898, 104598)):
if mx in r and re.match(r'^[A-Z]\d+(-\d)?$', r[mx]):
dia = r.get(dx)
tab[r[mx]] = {'dia': dia, 'L': num(r[lx]) * (1000 if num(r[lx]) < 100 else 1), 'n': int(r[nx]), 'tot': num(r[tx])}
order.append(r[mx])
# ditto 해소 (P 계열)
prev = {}
for k in order:
if tab[k]['dia'] == '"':
tab[k]['dia'] = 'H16'
tab['P1-1']['dia'] = 'H35'; tab['P1']['dia'] = 'H35'; tab['P1-2']['dia'] = 'H22'
json.dump({'tab': tab, 'order': order}, open('ra_table.json', 'w', encoding='utf-8'), ensure_ascii=False)
D = {}
for e in S:
D.setdefault(e['mark'], e)
def mm(s):
out = []
for p in s.strip().split('~'):
p = re.sub(r'[^\d.,]', '', p).replace(',', '')
if p: out.append(float(p))
return out
o = io.open('ra_analysis.txt', 'w', encoding='utf-8')
tot_t = 0; tot_m = 0
for mk in order:
t = tab[mk]; m = Mo.get(mk)
tot_t += t['tot']
if not m:
o.write(u'%s 모델 없음 table n=%d L=%.0f\n' % (mk, t['n'], t['L'])); continue
half = mk in ('P2', 'P2-1')
cnt = m['n'] // (2 if half else 1)
dl = [x * (2 if half else 1) for x in m['dl']]
mean = sum(dl) / len(dl)
tot = sum(m['dl']) / 1000.0
tot_m += tot
line = u'%s n 표%d/모델%d L 표%.0f 모델평균%.0f(min%.0f max%.0f) 총 표%.3f 모델%.3f' % (mk, t['n'], cnt, t['L'], mean, min(dl), max(dl), t['tot'], tot)
d = D.get(mk)
if d:
miss = []
for s in d['dims'] + d['dims2']:
k, _, v = s.partition('=')
k = k.strip()
if k[:1] in ('j', 'x', 'r', 'd') and False: continue
if k.startswith('j') or k.startswith('x'): continue
for val in mm(v):
if not any(abs(val - q) <= 3 for q in m['values']): miss.append('%s=%s' % (k, v))
line += u' | 도면 %s | 모델값 %s | 못 찾음 %s' % (' '.join(d['dims']), m['values'][:14], miss)
o.write(line + u'\n')
o.write(u'총계 표 %.3f 모델 %.3f\n' % (tot_t, tot_m))
print(len(tab), tot_t, tot_m)
+76
View File
@@ -0,0 +1,76 @@
import sys, os, json
SCR = r"C:\Users\User\AppData\Local\Temp\claude\D--Rhino-MCP-rhino-mcp-main\f46b47bc-9105-4079-9b62-fcbee31b26ae\scratchpad"
sys.path.insert(0, SCR)
sys.path.insert(0, SCR + r"\pylib")
from PIL import Image
import compose_new as C
CR = SCR + r"\ra_crops"
SH = SCR + r"\shots"
OUT = r"D:\★Pier 자료\심수석님 검토 파일\Ramp-A교P1\도면↔모델_불일치_시각자료"
os.makedirs(OUT, exist_ok=True)
def stack(names, out):
ims = [Image.open(CR + "\\" + n + ".png").convert("RGB") for n in names]
w = max(i.width for i in ims)
m = Image.new("RGB", (w, sum(i.height for i in ims)), (34, 40, 49))
y = 0
for i in ims:
m.paste(i, (0, y)); y += i.height
m.save(CR + "\\" + out + ".png")
def cr(n): return CR + "\\" + n + ".png"
def sh(n): return SH + "\\" + n + ".png"
stack(["row_head", "row_C7", "row_C8", "row_C9", "row_C10", "row_C11", "row_C12", "row_C13", "row_C14"], "tab_C7")
stack(["row_head", "row_C1", "row_C2", "row_C3", "row_C4", "row_C5"], "tab_C1")
stack(["row_head4r", "row_P1", "row_P1-1"], "tab_P1")
stack(["row_head4", "row_P2", "row_P2-1", "row_P3", "row_P3-1"], "tab_P2")
items = []
items.append(dict(code="M-1", title="C7~C14 끝 겹침 c(450/350)·j 290 이 모델 형상에 없음", subtitle="철근상세: C7·C9·C11 a·b·c 450, C8·C10·C12 a·b·c 450·d, C13·C14 a·b·c 350 (j 290) ↔ 모델 폐합·루프 형상은 c·j 없음", color=C.ORANGE,
panels=[dict(label="도면 철근상세 C7 · C13 · 재료표 C7~C14", color=C.BLUE, maxh=480,
images=[dict(path=cr("cell_C7"), cap="C7: c 450"), dict(path=cr("cell_C13"), cap="C13: c 350"), dict(path=cr("tab_C7"))]),
dict(label="모델 (라이노) C7 스터럽 1본 (측면) · C13 루프 1본 (평면)", color=C.ORANGE, maxh=480,
images=[dict(path=sh("R1a"), cap="C7 모델: 폐합 직사각형 (c 450 없음)"), dict(path=sh("R1b"), cap="C13 모델: 루프 (c 350 없음)")])],
rows=[("차이", "재료표 L 에는 끝 겹침 c(2곳)·j 가 포함(C7 L 7.480 = 2×1.673 + 2×1.617 + 2×0.450)되고 모델 설계길이(user string)도 포함하지만, 중심선 형상은 그만큼 짧음 (C7 형상 평균 대비 약 0.9 m)."),
("조치", "c·j 를 모델 형상에 넣을지, 가산 길이로만 둘지 판단 필요 (옥성교 M-2 와 같은 유형).")]))
items.append(dict(code="M-2", title="C7~C12 스터럽 폭 a = 모델 + 48 (가산)", subtitle="철근상세 a 1.673 (C7·C9·C11) / 0.673 (C8·C10·C12) ↔ 모델 1.625 / 0.625", color=C.ORANGE,
panels=[dict(label="도면 철근상세 C7 · C8", color=C.BLUE, maxh=480, images=[dict(path=cr("cell_C7")), dict(path=cr("cell_C8"))]),
dict(label="모델 (라이노) C8 (좌·우 2조각) 측면", color=C.ORANGE, maxh=520, images=[dict(path=sh("R2"), cap="C8 모델: 폭 0.625 (한 조각)")])],
rows=[("차이", "도면 a 는 모델 작도 폭 + 48 (1.625 → 1.673, 0.625 → 0.673). 재료표 a 값은 가산 포함, 모델은 작도값."),
("조치", "가산 48 의 적용 범위(폭·길이)를 확인 (운곡교 M-5 · 산외교 M-3 과 같은 유형).")]))
items.append(dict(code="M-3", title="C8~C14 도면 단일값·범위 ↔ 모델 철근별 변화, 총길이 차이", subtitle="재료표 L 은 대표값 1개 ↔ 모델 철근마다 높이·길이가 다름 (C8 모델 4.065~6.729, 재료표 6.071)", color=C.ORANGE,
panels=[dict(label="도면 철근상세 C8 (b 2.301 단일) · C12 · C13 (a 5.898~2.006 var.) · 재료표", color=C.BLUE, maxh=440,
images=[dict(path=cr("cell_C8")), dict(path=cr("cell_C12")), dict(path=cr("cell_C13"))]),
dict(label="모델 (라이노) 정면도 방향 · C8 · C12 (코핑 하면을 따라 높이 변화)", color=C.ORANGE, maxh=420,
images=[dict(path=sh("R3a"), cap="C8 20쌍"), dict(path=sh("R3b"), cap="C12 20쌍")])],
rows=[("차이", "총길이 모델-재료표(m): C7 -5.33, C8 -37.68, C9 -0.32, C10 -10.08, C11 -5.33, C12 +17.04, C13 -8.99, C14 +0.88. 도면 총계 5758.450 ↔ 모델 5708.545 m (-49.9)."),
("도면 쪽", "철근상세는 b 단일값(C8 2.301, C10 2.660, C12 1.248) 또는 범위(C7 2.301~0.933, C13 a 5.898~2.006 var.)만 적고 개별 값 없음."),
("조치", "철근별 개별 길이가 의도인지 확인. C12 는 모델이 재료표보다 길음(평균 5.759 ↔ 5.333).")]))
items.append(dict(code="M-4", title="C1~C5 일부 철근 다리 b·c 가 도면 단일값보다 약간 짧음", subtitle="철근상세 C1 b 858 · c 841 (9본 동일) ↔ 모델 C1 b·c 828~858 (4본 짧음, 최대 17 mm)", color=C.ORANGE,
panels=[dict(label="도면 철근상세 C1 · 재료표 C1~C5", color=C.BLUE, maxh=440, images=[dict(path=cr("cell_C1"), cap="C1: a 6.918 · b 858 · c 841"), dict(path=cr("tab_C1"))]),
dict(label="모델 (라이노) 정면도 방향 · C1", color=C.ORANGE, maxh=420,
images=[dict(path=sh("R4a"), cap="y+1000 (끝 짧은 철근)"), dict(path=sh("R4b"), cap="y+0 (도면 치수)")])],
rows=[("차이", "C1 9본 중 4본(2본 b·c 828/841→, 2본 837/853)과 C4 2본, C2·C3 일부가 3~17 mm 짧음. 설계길이는 재료표와 같음(8.617)."),
("조치", "경미한 차이 — 코핑 형상에 맞춘 조정인지 확인.")]))
items.append(dict(code="M-5", title="커플러 수량 P1 54 · P1-1 61 (115개) 모델에 없음", subtitle="철근상세 시트 커플러 수량 표 ↔ 모델 P1·P1-1 은 커플러 없이 연속(P1 은 연결 간격 40 만 길이에 반영)", color=C.ORANGE,
panels=[dict(label="도면 커플러 수량 표 · 재료표 P1 · P1-1", color=C.BLUE, maxh=420, images=[dict(path=cr("coupler_qty")), dict(path=cr("tab_P1"))]),
dict(label="모델 (라이노) 기둥 P1 + 말뚝 P1-1 · P1-2 (정면)", color=C.ORANGE, maxh=620, images=[dict(path=sh("R5"))])],
rows=[("차이", "표의 커플러 115개(P1 H35 54 + P1-1 H35 61)가 모델에 없음. P1 설계길이 16.260 + 커플러 0.040 = 16.300 (재료표 L), P1-1 8.750."),
("조치", "커플러 위치·개수를 모델에 반영할지 판단 (산외교 M-6 과 같은 유형).")]))
items.append(dict(code="M-6", title="굽힘 내면지름 H25·H19 KDS 3.5db 미달", subtitle="KDS 24 14 21 표 4.5-1: 내면지름 ≥ 3.5db · 도면에는 내면반지름 표기 없음", color=C.DYEL,
panels=[dict(label="모델 (라이노) C7 스터럽 모서리 확대", color=C.DYEL, maxh=560, images=[dict(path=sh("R6"), cap="C7 (H19) 굽힘: 중심선 R 42.8 → 내면지름 66.5")])],
rows=[("계산", "H25 (C3): R 56.2 → 87.0 < 3.5×25.4 = 88.9 (1.9 mm 부족). H19 (C7~C16): R 42.8 → 66.5 < 66.85 (0.35 부족). H29 (C1·C2·C4·C5) 101.8 ≥ 100.1 충족, H16 32.1 ≥ 2db 31.8 충족."),
("조치", "모델 R 을 KDS 기준으로 키울지 결정 (기준↔모델).")]))
items.append(dict(code="M-7", title="P2·P3 계열 갈고리·겹침 정의 도면 기준 불명", subtitle="P3 c 141·d 100, P3-1 c 165·d 150, P2 j 650×2, P2-1 j 880×2 ↔ 모델 갈고리 끝 92/176, 반링 이음 위치 가정", color=C.GREY,
panels=[dict(label="도면 철근상세 P3 · P3-1 · P2 · 재료표 P2~P3-1", color=C.BLUE, maxh=440, images=[dict(path=cr("cell_P3")), dict(path=cr("cell_P3-1")), dict(path=cr("cell_P2"))]),
dict(label="모델 (라이노) 평면 · 기둥 띠철근 P2·P3", color=C.GREY, maxh=520, images=[dict(path=sh("R7"))])],
rows=[("내용", "P3 모델 (200 / 1.832 / 150) ↔ 도면 a 200 · b 1.832 · c 141 · d 100 (끝 길이 정의 불일치 가능), P3-1 c 165 ↔ 모델 150. P2·P2-1 겹침 j 와 반링 이음 각도는 도면 기준 없음 (모델은 반링 2조각 = 1본 환산)."),
("조치", "갈고리 끝 길이·이음 각도 기준 확인 (설계자).")]))
names = {"M-1": "M-1_C7-C14_겹침c_j", "M-2": "M-2_스터럽폭_가산48", "M-3": "M-3_C8-C14_단일값_총길이", "M-4": "M-4_C1-C5_다리경미", "M-5": "M-5_커플러115", "M-6": "M-6_굽힘내면지름", "M-7": "M-7_P2P3_갈고리겹침"}
res = {}
for it in items:
it["out"] = OUT + "\\" + names[it["code"]] + ".png"
res[it["code"]] = it["out"]
print(it["code"], C.make(it))
json.dump(res, open(SCR + r"\ra_images.json", "w", encoding="utf-8"), ensure_ascii=False)
Loaded 100 of 3978 files, more files were not shown because too many files have changed in this diff. Show more