chore(rebar): Pier_T_rebar 작업 폴더 초기 구성 및 외부 의존 파일 통합
- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환 - Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용 - pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관) - worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md) - D:\Rhino-MCP 경로 참조를 새 위치로 치환 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
964a841de1
3978 files changed
+58013356
No files matched your search
@@ -0,0 +1,212 @@
|
||||
"""'공통 문제 상세(이미지)' 시트에 넣을 '수정 후' 이미지를 만든다 (2026-10-26).
|
||||
|
||||
C-7 (기초 철근 높이): 수정된 모델의 Rhino Front / Right 뷰 캡처(Code/검토수정/결과/캡처/*.png, c1_capture.py 로 생성)에
|
||||
높이 기준선과 라벨, 제목 배너, 하단 설명을 얹는다.
|
||||
C-1 (굽힘 반지름): 수정된 모델에서 확인한 호 반지름(KDS 확인 결과 JSON)으로 기존 이미지와 같은 형식(굽힘 평면도 + 확대)을 그린다.
|
||||
출력: 검토파일/수정후_캡처/*.png
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib import font_manager
|
||||
from matplotlib.patches import Arc, Circle
|
||||
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"
|
||||
font_manager.fontManager.addfont(FONT)
|
||||
font_manager.fontManager.addfont(FONTB)
|
||||
plt.rcParams["font.family"] = "Malgun Gothic"
|
||||
plt.rcParams["axes.unicode_minus"] = False
|
||||
|
||||
|
||||
def F(size, bold=False):
|
||||
return ImageFont.truetype(FONTB if bold else FONT, size)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ C-7 images
|
||||
C7 = {
|
||||
"ungok": {"name": "운곡교", "H": 2200, "lines": [("B5 (H29 상면 바깥)", 2100, "top", "m"), ("B1 (상면 안쪽)", 2072.5, "top", "g"),
|
||||
("B2·B4 (하면 안쪽)", 177, "bot", "g"), ("B6·B8 (H29 하면 바깥)", 150, "bot", "m")],
|
||||
"note": "바깥층 B5(상면 100)·B6(하면 150), 나머지는 안쪽에 접촉 배치"},
|
||||
"sanoe": {"name": "산외교", "H": 2500, "lines": [("B1 (H16 상면 바깥)", 2400, "top", "m"), ("B4 (상면 안쪽)", 2384, "top", "g"),
|
||||
("B5·B6 (H22 하면 안쪽)", 172, "bot", "g"), ("B2·B3 (H22 하면 바깥)", 150, "bot", "m")],
|
||||
"note": "바깥층 상면 100·하면 150, 나머지는 안쪽에 접촉 배치 (직경 동일)"},
|
||||
"okseong": {"name": "옥성교", "H": 2200, "lines": [("B1·B3 (H29 상면 바깥)", 2100, "top", "m"), ("B5·B7 (상면 안쪽)", 2071, "top", "g"),
|
||||
("B6·B8 (하면 안쪽)", 179, "bot", "g"), ("B2·B4 (H29 하면 바깥)", 150, "bot", "m")],
|
||||
"note": "B1~B4 는 원래 2100 / 150, 안쪽 B5~B8 을 접촉 위치로 이동 (직경 동일)"},
|
||||
}
|
||||
|
||||
|
||||
def compose_c7(key, view):
|
||||
spec = C7[key]
|
||||
cj = json.loads((CAP / ("%s_capture.json" % key)).read_text(encoding="utf-8"))
|
||||
info = cj["views"][view]
|
||||
img = Image.open(CAP / ("%s_%s.png" % (key, view))).convert("RGB")
|
||||
lines = info["lines"]
|
||||
ys = [p[1] for v in lines.values() for p in v]
|
||||
xs = [p[0] for v in lines.values() for p in v]
|
||||
# footing frame in pixels: from the extreme lines, extended to the footing faces (z 0 and z H) by linear scaling
|
||||
keys = list(lines.keys())
|
||||
z = {k: spec["lines"][i][1] for i, k in enumerate(keys)}
|
||||
k1, k2 = keys[0], keys[-1]
|
||||
y1, y2 = lines[k1][0][1], lines[k2][0][1]
|
||||
pxmm = (y2 - y1) / (z[k2] - z[k1]) # pixels per mm (negative: z up = y down)
|
||||
ytop = y1 + (spec["H"] - z[k1]) * pxmm
|
||||
ybot = y1 + (0 - z[k1]) * pxmm
|
||||
xl, xr = min(xs), max(xs)
|
||||
pad_l, pad_r, pad_t, pad_b = 430, 40, 90, 90
|
||||
box = (max(0, int(xl - pad_l)), max(0, int(min(ytop, ybot) - pad_t)), min(img.width, int(xr + pad_r)), min(img.height, int(max(ytop, ybot) + pad_b)))
|
||||
crop = img.crop(box)
|
||||
d = ImageDraw.Draw(crop)
|
||||
ox, oy = box[0], box[1]
|
||||
for fy in (ytop, ybot): # footing top / bottom face
|
||||
d.line([(xl - ox, fy - oy), (xr - ox, fy - oy)], fill=(40, 40, 160), width=2)
|
||||
d.text((xr - ox - 130, ytop - oy - 34), "기초 상면", font=F(22, True), fill=(40, 40, 160))
|
||||
d.text((xr - ox - 130, ybot - oy + 8), "기초 하면", font=F(22, True), fill=(40, 40, 160))
|
||||
entries = []
|
||||
for label, zrel, side, col in spec["lines"]:
|
||||
yy = y1 + (zrel - z[k1]) * pxmm - oy
|
||||
color = (200, 0, 200) if col == "m" else (0, 130, 0)
|
||||
x = xl - ox
|
||||
while x < xr - ox: # dashed reference line
|
||||
d.line([(x, yy), (min(x + 18, xr - ox), yy)], fill=color, width=2)
|
||||
x += 30
|
||||
cover = (spec["H"] - zrel) if side == "top" else zrel
|
||||
txt = "%s z %g (%s에서 %g)" % (label, zrel, "상면" if side == "top" else "하면", cover)
|
||||
entries.append({"y": yy, "ly": yy, "txt": txt, "color": color})
|
||||
# spread the labels so they do not overlap (min 34 px apart), keep order, groups stay near their lines
|
||||
entries.sort(key=lambda e: e["y"])
|
||||
for i in range(1, len(entries)):
|
||||
if entries[i]["ly"] - entries[i - 1]["ly"] < 34:
|
||||
entries[i]["ly"] = entries[i - 1]["ly"] + 34
|
||||
mid_shift = (sum(e["y"] for e in entries) - sum(e["ly"] for e in entries)) / len(entries)
|
||||
for e in entries:
|
||||
e["ly"] += mid_shift * 0.0
|
||||
for i in range(len(entries) - 1, 0, -1): # if pushed below the footing bottom area, pull back up
|
||||
if entries[i]["ly"] > crop.height - 40:
|
||||
entries[i]["ly"] = crop.height - 40
|
||||
if entries[i]["ly"] - entries[i - 1]["ly"] < 34:
|
||||
entries[i - 1]["ly"] = entries[i]["ly"] - 34
|
||||
for e in entries:
|
||||
w = d.textlength(e["txt"], font=F(21, True))
|
||||
tx = max(6, xl - ox - w - 40)
|
||||
d.line([(tx + w + 6, e["ly"]), (xl - ox - 4, e["y"])], fill=e["color"], width=2) # leader to the line
|
||||
d.rectangle([tx - 4, e["ly"] - 15, tx + w + 4, e["ly"] + 15], fill=(255, 255, 255), outline=e["color"])
|
||||
d.text((tx, e["ly"] - 13), e["txt"], font=F(21, True), fill=e["color"])
|
||||
title = "%s · 수정 후 모델 %s (라이노 %s 뷰)" % (spec["name"], "정면도" if view == "Front" else "단면도", view)
|
||||
sub = "기초 철근 중심 높이 — 기초 하면 기준 z (mm), 괄호 = 해당 면에서 철근 중심까지 거리"
|
||||
W = crop.width
|
||||
banner = Image.new("RGB", (W, 112), (30, 120, 60))
|
||||
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, 74), (246, 246, 246))
|
||||
fd = ImageDraw.Draw(foot)
|
||||
fd.rectangle([14, 12, 40, 38], fill=(200, 0, 200))
|
||||
fd.text((50, 8), "자홍 = 바깥층(피복 기준, 도면 철근선)", font=F(24), fill=(0, 0, 0))
|
||||
fd.rectangle([W // 2 + 10, 12, W // 2 + 36, 38], fill=(0, 130, 0))
|
||||
fd.text((W // 2 + 46, 8), "녹색 = 안쪽층(바깥층에 접촉)", font=F(24), fill=(0, 0, 0))
|
||||
fd.text((14, 42), spec["note"] + " ※ 기초 콘크리트는 숨기고 철근만 표시", font=F(22), fill=(60, 60, 60))
|
||||
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 / ("C-7_%s_수정후_%s.png" % ("정면도" if view == "Front" else "단면도", spec["name"]))
|
||||
out.save(p)
|
||||
return p
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ C-1 images
|
||||
NOM = {13: 12.7, 16: 15.9, 19: 19.1, 22: 22.2, 25: 25.4, 29: 28.6, 32: 31.8}
|
||||
|
||||
|
||||
def compose_c1(bridge, dia, R_old, R_new, row_title, changed=True, hook=False):
|
||||
d = int(dia[1:])
|
||||
nom = NOM[d]
|
||||
k = 3.5 if d >= 19 else 2.0
|
||||
inner_actual = R_new - d / 2.0
|
||||
need_inner_d = k * nom
|
||||
need_inner_R = need_inner_d / 2.0
|
||||
diff_d = 2 * inner_actual - need_inner_d
|
||||
fig = plt.figure(figsize=(10, 6.05), dpi=100)
|
||||
fig.patch.set_facecolor("white")
|
||||
head = fig.add_axes([0, 0.66, 1, 0.34])
|
||||
head.set_facecolor((0.09, 0.45, 0.22))
|
||||
head.set_xticks([]); head.set_yticks([])
|
||||
for s in head.spines.values():
|
||||
s.set_visible(False)
|
||||
head.set_xlim(0, 1); head.set_ylim(0, 1)
|
||||
head.text(0.012, 0.80, "%s · %s %s — 수정 후" % (bridge, dia, row_title), color="white", fontsize=19, fontweight="bold", va="center")
|
||||
if changed:
|
||||
head.text(0.012, 0.58, "중심선 R %.2f (수정 전 %.2f) 파이프 지름 %d 바깥면 R %.2f" % (R_new, R_old, d, R_new + d / 2.0), color="white", fontsize=11.5, va="center")
|
||||
else:
|
||||
head.text(0.012, 0.58, "중심선 R %.2f (수정 없음, 이미 기준 충족) 파이프 지름 %d 바깥면 R %.2f" % (R_new, d, R_new + d / 2.0), color="white", fontsize=11.5, va="center")
|
||||
head.text(0.012, 0.40, "내면 실측: 중심선에서 %.1f(직경/2) 안쪽 → R %.2f, 내면지름 %.2f" % (d / 2.0, inner_actual, 2 * inner_actual), color="white", fontsize=11.5, va="center")
|
||||
head.text(0.012, 0.22, "KDS 필요: %.1f × 공칭 %.1f = 내면지름 %.2f (필요 R %.3f)" % (k, nom, need_inner_d, need_inner_R), color="white", fontsize=11.5, va="center")
|
||||
ok = diff_d >= -0.005
|
||||
head.text(0.012, 0.06, "차이(실측-필요): 내면지름 %+.2f mm → %s" % (diff_d, "충족" if ok else "미달"), color=(1, 1, 0.3), fontsize=14, fontweight="bold", va="center")
|
||||
ax1 = fig.add_axes([0.01, 0.06, 0.48, 0.58])
|
||||
ax2 = fig.add_axes([0.51, 0.06, 0.48, 0.58])
|
||||
for ax in (ax1, ax2):
|
||||
ax.set_facecolor((0.62, 0.65, 0.68))
|
||||
ax.set_aspect("equal")
|
||||
ax.set_xticks([]); ax.set_yticks([])
|
||||
if hook: # 180 deg hook elevation: vertical leg, semicircle, tail
|
||||
R = R_new
|
||||
ax1.add_patch(Arc((R, 0), 2 * R, 2 * R, theta1=0, theta2=180, color="magenta", lw=2))
|
||||
ax1.plot([0, 0], [-150, 0], color="magenta", lw=2)
|
||||
ax1.plot([2 * R, 2 * R], [0, -100], color="magenta", lw=2)
|
||||
ax1.add_patch(Arc((R, 0), 2 * (R + d / 2), 2 * (R + d / 2), theta1=0, theta2=180, color="black", lw=1))
|
||||
ax1.add_patch(Arc((R, 0), 2 * inner_actual, 2 * inner_actual, theta1=0, theta2=180, color="blue", lw=1.5))
|
||||
ax1.add_patch(Arc((R, 0), 2 * need_inner_R, 2 * need_inner_R, theta1=0, theta2=180, color="red", lw=1.2))
|
||||
ax1.text(R, -60, "180° 갈고리 R%.0f · 연장 100" % R, ha="center", fontsize=9)
|
||||
ax1.set_xlim(-60, 2 * R + 60); ax1.set_ylim(-170, R + 40)
|
||||
else:
|
||||
cx, cy = 0.0, 0.0
|
||||
ax1.add_patch(Arc((cx, cy), 2 * R_new, 2 * R_new, theta1=0, theta2=90, color="magenta", lw=2))
|
||||
ax1.add_patch(Arc((cx, cy), 2 * (R_new + d / 2), 2 * (R_new + d / 2), theta1=0, theta2=90, color="black", lw=1))
|
||||
ax1.add_patch(Arc((cx, cy), 2 * inner_actual, 2 * inner_actual, theta1=0, theta2=90, color="blue", lw=1.5))
|
||||
ax1.add_patch(Arc((cx, cy), 2 * need_inner_R, 2 * need_inner_R, theta1=0, theta2=90, color="red", lw=1.2))
|
||||
ax1.add_patch(Circle((R_new, 0), d / 2, fill=False, color="gray", lw=0.8))
|
||||
ax1.add_patch(Circle((0, R_new), d / 2, fill=False, color="gray", lw=0.8))
|
||||
ax1.set_xlim(-10, R_new + d); ax1.set_ylim(-10, R_new + d)
|
||||
ax1.text(0.02, 0.02, "전체 보기 (%s)" % ("180° 갈고리" if hook else "90° 굽힘, 평면도"), transform=ax1.transAxes, fontsize=9, color="white")
|
||||
# zoom: 10 mm window around the inner surface at 45 degrees (blue = actual, red = required)
|
||||
ang = math.radians(45)
|
||||
for r_, col, lw in ((inner_actual, "blue", 1.6), (need_inner_R, "red", 1.2)):
|
||||
t = [math.radians(a) for a in range(20, 71)]
|
||||
ax2.plot([r_ * math.cos(a) for a in t], [r_ * math.sin(a) for a in t], color=col, lw=lw)
|
||||
mx, my = inner_actual * math.cos(ang), inner_actual * math.sin(ang)
|
||||
ax2.set_xlim(mx - 5, mx + 5); ax2.set_ylim(my - 3, my + 3)
|
||||
ax2.text(0.02, 0.02, "확대: 폭 10 mm 파랑(실측)·빨강(KDS 필요) 간격 = 차이", transform=ax2.transAxes, fontsize=9, color="white")
|
||||
foot = fig.add_axes([0, 0, 1, 0.055]); foot.axis("off")
|
||||
foot.text(0.01, 0.4, "수정 완료 (2026-10-26) — 중심선 R %.2f → 내면지름 %.2f mm ≥ KDS 필요 %.2f mm" % (R_new, 2 * inner_actual, need_inner_d), fontsize=9.5, va="center")
|
||||
p = OUT / ("C-1_%s_%s_수정후.png" % (bridge, dia))
|
||||
fig.savefig(p, dpi=100, facecolor="white")
|
||||
plt.close(fig)
|
||||
return p
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
made = []
|
||||
for key in ("ungok", "sanoe", "okseong"):
|
||||
for view in ("Front", "Right"):
|
||||
made.append(compose_c7(key, view))
|
||||
C1 = [("운곡교", "H29", 65.25, 65.25, "90° 굽힘", False), ("옥성교", "H29", 65.25, 65.25, "90° 굽힘", False), ("Ramp-A교", "H29", 65.25, 65.25, "90° 굽힘", False),
|
||||
("운곡교", "H25", 56.25, 56.95, "90° 굽힘", True), ("옥성교", "H25", 56.25, 56.95, "90° 굽힘", True), ("Ramp-A교", "H25", 56.25, 56.95, "90° 굽힘", True),
|
||||
("산외교", "H22", 49.5, 49.85, "굽힘", True), ("옥성교", "H22", 49.5, 49.85, "굽힘", True),
|
||||
("운곡교", "H19", 42.75, 42.93, "90° 굽힘", True), ("산외교", "H19", 42.75, 42.93, "90° 굽힘", True), ("옥성교", "H19", 42.75, 42.93, "90° 굽힘", True), ("Ramp-A교", "H19", 42.75, 42.93, "90° 굽힘", True)]
|
||||
for b, dia, ro, rn, t, ch in C1:
|
||||
made.append(compose_c1(b, dia, ro, rn, t, ch))
|
||||
made.append(compose_c1("운곡교", "H16", 20.37, 24.0, "180° 갈고리 (B9)", True, hook=True))
|
||||
for p in made:
|
||||
print(p.name)
|
||||
Reference in new issue
Block a user