"""Ramp-A교 P3-2 수정 전·후 이미지 (Rhino 캡처 + 도면 형상 기준선 + 치수). 입력: 결과/캡처/p32_ramp3|4(.png, _capture.json).""" import json from pathlib import Path from PIL import Image, ImageDraw, ImageFont ROOT = Path(r"D:\★Pier 자료\프로그램\Pier_T") CAP = ROOT / "Code" / "검토수정" / "결과" / "캡처" OUT = ROOT / "검토파일" / "수정후_캡처" OUT.mkdir(parents=True, exist_ok=True) FONT = r"C:\Windows\Fonts\malgun.ttf" FONTB = r"C:\Windows\Fonts\malgunbd.ttf" def F(s, b=False): return ImageFont.truetype(FONTB if b else FONT, s) def compose(key, title, sub, color, lines, legend, outname, before): cj = json.loads((CAP / ("%s_capture.json" % key)).read_text(encoding="utf-8")) img = Image.open(CAP / ("%s.png" % key)).convert("RGB") T1, T2, C1, C2 = cj["T1"], cj["T2"], cj["C1"], cj["C2"] pad = 60 box = (max(0, int(T1[0] - 330)), int(T1[1] - 110), min(1500, int(T2[0] + 160)), int(C1[1] + 150)) crop = img.crop(box) ox, oy = box[0], box[1] d = ImageDraw.Draw(crop) P = lambda p: (p[0] - ox, p[1] - oy) if before: # drawing shape (plain U) as dashed magenta guide mag = (200, 0, 200) for a, b in ((T1, C1), (C1, C2), (C2, T2)): x0, y0 = P(a); x1, y1 = P(b) n = max(1, int(((x1 - x0) ** 2 + (y1 - y0) ** 2) ** 0.5 // 22)) for i in range(n): if i % 2 == 0: d.line([(x0 + (x1 - x0) * i / n, y0 + (y1 - y0) * i / n), (x0 + (x1 - x0) * (i + 1) / n, y0 + (y1 - y0) * (i + 1) / n)], fill=mag, width=4) d.text((P(C1)[0] + 20, P(C1)[1] + 36), "자홍 점선 = 도면 형상(바닥 수평 + 다리 수직 ㄷ자)", font=F(26, True), fill=mag) else: # dimension lines x0, y0 = P(C1); x1, y1 = P(C2) yd = y0 + 70 d.line([(x0, yd), (x1, yd)], fill=(0, 110, 0), width=3) for x in (x0, x1): d.line([(x, yd - 14), (x, yd + 14)], fill=(0, 110, 0), width=3) txt = lines[0] w = d.textlength(txt, font=F(28, True)) d.rectangle([(x0 + x1) / 2 - w / 2 - 8, yd - 20, (x0 + x1) / 2 + w / 2 + 8, yd + 20], fill=(255, 255, 255)) d.text(((x0 + x1) / 2 - w / 2, yd - 18), txt, font=F(28, True), fill=(0, 110, 0)) tx, ty = P(T1) xd = tx - 70 d.line([(xd, ty), (xd, P(C1)[1])], fill=(0, 110, 0), width=3) for y in (ty, P(C1)[1]): d.line([(xd - 14, y), (xd + 14, y)], fill=(0, 110, 0), width=3) w2 = d.textlength(lines[1], font=F(28, True)) lx = tx + 26 # label inside the U (left margin of the capture is too small) d.rectangle([lx - 8, (ty + P(C1)[1]) / 2 - 20, lx + w2 + 8, (ty + P(C1)[1]) / 2 + 20], fill=(255, 255, 255)) d.text((lx, (ty + P(C1)[1]) / 2 - 18), lines[1], font=F(28, True), fill=(0, 110, 0)) d.text((P(C1)[0] + 16, P(C1)[1] + 12), lines[2], font=F(26, True), fill=(0, 110, 0)) W = crop.width banner = Image.new("RGB", (W, 112), color) bd = ImageDraw.Draw(banner) bd.text((14, 8), title, font=F(40, True), fill=(255, 255, 255)) bd.text((14, 64), sub, font=F(26), fill=(255, 255, 255)) foot = Image.new("RGB", (W, 80), (246, 246, 246)) fd = ImageDraw.Draw(foot) for i, t in enumerate(legend): fd.text((14, 6 + i * 34), t, font=F(24), fill=(20, 20, 20)) out = Image.new("RGB", (W, banner.height + crop.height + foot.height), (255, 255, 255)) out.paste(banner, (0, 0)); out.paste(crop, (0, banner.height)); out.paste(foot, (0, banner.height + crop.height)) p = OUT / outname out.save(p) return p p1 = compose("p32_ramp3", "Ramp-A교 · P3-2 (H16) — 수정 전 모델", "도면은 바닥 수평 + 다리 수직 ㄷ자 (다리 200 은 P1-2 에 용접), 모델은 깔때기 형상", (170, 40, 30), None, ["다리 위쪽 184 만 수직, 그 아래 459 는 약 13.4° 안쪽으로 기울고 모서리가 R135 두 호(13.44° + 76.56°)로 이어짐", "바닥 직선이 다리 위쪽 간격보다 좁음 (깔때기). 중심선 길이 3,276.3 (재료표 L 3,342)"], "RampA_P3-2_수정전.png", True) p2 = compose("p32_ramp4", "Ramp-A교 · P3-2 (H16) — 수정 후 모델", "바닥 수평 + 다리 수직 ㄷ자, 모서리 R24 (KDS H16 최소 23.9)", (30, 120, 60), ["바닥 폭 2,050", "다리 높이 646", "모서리 R24"], ["바닥 폭 2,050 + 다리 2×646 = 3,342 (재료표 L 직선 치수 합). 다리 200 용접 구간은 다리 안에 포함", "중심선 길이 3,321.4. 모서리 R 은 도면에 값이 없어 KDS 최소(H16)를 적용"], "RampA_P3-2_수정후.png", False) print(p1.name, p2.name)