Files
Pier_v1/Code/울산P2판독/extract_r4.py
T
ahnyongjooandClaude Sonnet 5.5 964a841de1 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>
2026-10-28 11:20:18 +09:00

289 lines
16 KiB
Python

"""R4 (coping rebar C1~C15, bearing-seat reinforcement S1~S4) of the Ulsan P2 drawing (derived from the P1 script, see make_r4.py) ->
data/산외교울산P2 params.coping_rebar (explicit centreline polylines), expected.G4; adds G4 to the scope.
Sources (배근도(1) front view CX 10473.9, side view CX 24604.3; 배근도(2) sections A-A / B-B, 받침부 보강 상세 1·3,
F-F / G-G; 배근도(3) schedule and shape table). Front-view drawing y -> EL: z = y + 99715.8 (bottom bars
-31753.3 = 67962.5 = coping bottom + 100; layer 1 -28752.8 = right top - 100).
- C1/C3 layer 1 (z 70962.5), C2/C4 layer 2 (z 70862.5): drawn lines 9,258.6 / 9,297 (= A); end legs drawn
(C1 left 1,364 / right 1,369 along the end faces; C2 102); y 24@125 (25 per layer) - C1/C2 even, C3/C4 odd [추론]
- C15 left top (z 71092.5): 4,500 + legs 510 (left leg along the end face [추론], right leg at x -100); y 12@250
- C5/C6 bottom: drawn polyline (650 / 4,018 / 2,457 / 4,016 / 650); y 12@250, C5 even (7) / C6 odd (6) [추론]
- stirrups C7 (x -4,600..-1,400 @200, 17) / C8 (-1,200 -1,000 -500 0 500 1,000 1,200) / C9 (1,400..4,600):
3 per position, legs y (-1,500,-250) (-1,000,1,000) (250,1,500) (A-A; shape table II note a = (1,250x2 +
2,000)/3 + 54); centreline height b = drawn height + 54 (schedule B range reproduced to 1 mm): top = layer-1
(x >= 0) or C15 level (x <= -500) + 27, bottom = drawn bottom bar line - 27
- side bars 13 levels z 70,562.5 - 200k (side view / A-A '530','13@200'), drawn ends per level; y +-977.5 and
+-272.5 = C13 (levels 1-5, 20) / C11 (levels 6-13, 32), a = drawn + 44 (schedule range reproduced);
180-deg hooks (b 165, c 140) in plan towards y = 0 inside the drawn ends [추론]
- perimeter hoops y +-1,477.5: C12 (XII, plan corners R400, levels 1-6) / C10 (III, levels 7-13); counts kept,
schedule ranges suggest 5 / 8 levels (C10 a max 9,570 = level 6) -> drawing inconsistency recorded
- C14 (H16, 3,000 + 300) at the drawn section marks (5 levels, 8+4+4+3+1); legs along X towards the column and
x offset 19 from the stirrup plane [추론]
- S1/S2 (front bearings 1,2) S3/S4 (rear 3,4): 11 bars per direction (50,100,'8@125',100,50) in the 1,300 pedestal,
50 below the pedestal top, legs 700 down; S1/S3 along Y on top, S2/S4 along X below [추론]
"""
import json
import shutil
import time
from pathlib import Path
HERE = Path(__file__).resolve().parent
APP = HERE.parent / "교각모델링앱"
PIER = APP / "data" / "산외교울산P2"
S = json.loads((HERE / "판독" / "schedule.json").read_text(encoding="utf-8"))
ROWS = {r["mark"]: r for t in S.values() for r in t["rows"]}
MARKS = [f"C{i}" for i in range(1, 16)] + ["S1", "S2", "S3", "S4"]
Z0 = 99715.8 + 853 # P1 drawing y -> z, shifted by the P2 coping bottom (68716 - 67863)
DIA = {"H32": 32, "H22": 22, "H16": 16, "H13": 13}
def z(y):
return round(y + Z0, 1)
def mm(v):
return round(float(v) * 1000) if "." in v else int(v)
def L(m):
"""Schedule length: the value in the L column (sheet x 111,000..112,500)."""
return round(float(next(v for x, v in ROWS[m]["_right"] if 110000 < x < 111100)) * 1000)
def rmin(dia):
d = DIA[dia]
return ((2 * d if d <= 16 else 3.5 * d) + d) / 2
# ---- drawn data (front view, x relative to CX) ----
LAYER1 = dict(z=z(-28752.8), pts=[(-4887.5, z(-30094.5)), (-4640.6, z(-28752.8)), (4618.0, z(-28752.8)), (4886.4, z(-30095.0))])
LAYER2 = dict(z=z(-28852.8), pts=[(-4677.4, z(-28952.8)), (-4659.0, z(-28852.8)), (4638.0, z(-28852.8)), (4658.0, z(-28952.8))])
BOTTOM = [(-4769.8, z(-29455.0)), (-4887.5, z(-30094.5)), (-1228.4, z(-31753.3)), (1228.4, z(-31753.3)),
(4886.4, z(-30095.0)), (4758.9, z(-29457.4))]
C15_Z = z(-28623.3)
C15_X = (-4616.7, -100.0)
LEVELS = [(z(y), a, b) for y, a, b in (
(-29153.3, -4714.3, 4698.1), (-29353.3, -4751.1, 4738.1), (-29553.3, -4787.9, 4778.1), (-29753.3, -4824.7, 4818.0),
(-29953.3, -4861.5, 4858.0), (-30153.3, -4757.8, 4757.8), (-30353.3, -4316.6, 4316.6), (-30553.3, -3875.4, 3875.5),
(-30753.3, -3434.3, 3434.3), (-30953.3, -2993.1, 2993.1), (-31153.3, -2551.9, 2551.9), (-31353.3, -2110.7, 2110.7),
(-31553.3, -1669.6, 1669.6))]
STIR_X = {"C7": [-4600 + 200 * i for i in range(17)], "C8": [-1200, -1000, -500, 0, 500, 1000, 1200],
"C9": [1400 + 200 * i for i in range(17)]}
STIR_Y = [(-1500, -250), (-1000, 1000), (250, 1500)]
C14_PTS = [(x, z(-28875.3)) for x in (-4600, -3600, -2600, -1600, 1600, 2600, 3600, 4600)] + \
[(x, z(-29353.3)) for x in (-4600, -2600, 1600, 3600)] + [(x, z(-29553.3)) for x in (-3600, -1600, 2600, 4600)] + \
[(x, z(-30553.3)) for x in (-2600, 1600, 3600)] + [(-1600, z(-31553.3))]
SEAT_OFFS = [-600] + [-500 + 125 * i for i in range(9)] + [600]
ZTL, ZTR, ZTIP = 72046.0, 71916.0, 70416.0
def x_face(z, x):
"""End face |x| of the coping at level z (batter 300 above the tip; vertical tip below)."""
if z < ZTIP:
return 5000.0
return 4700 + 300 * ((ZTL - z) / 1630 if x < 0 else (ZTR - z) / 1500)
def lim_x(y, z, x):
"""Largest |x| of a bar centreline at (y, z) inside the R500 plan corner with 100 clearance (R400 arc)."""
dy = abs(y) - 1100
if dy <= 0:
return 1e9
return x_face(z, x) - 500 + (400 ** 2 - min(dy, 400) ** 2) ** 0.5
def lim_y(x, z):
dx = abs(x) - (x_face(z, x) - 500)
if dx <= 0:
return 1e9
return 1100 + (400 ** 2 - min(dx, 400) ** 2) ** 0.5
def clip_end(pts, y):
"""Shift the first / last straight run ends inward to the corner limit; legs (first / last point) move along."""
pts = [list(p) for p in pts]
for i_end, i_leg in ((1, 0), (-2, -1)):
x, zz = pts[i_end]
lim = lim_x(y, zz, x)
if abs(x) > lim:
d = (abs(x) - lim) * (1 if x > 0 else -1)
pts[i_end][0] -= d
pts[i_leg][0] -= d
return [tuple(p) for p in pts]
def drawn_bottom(x):
"""Drawn bottom-bar line (front view) at x: flat 67,962.5 inside +-1,228.4, slope 1,658.8 / 3,659.1 outside."""
zf = z(-31753.3)
return zf if abs(x) <= 1228.4 else zf + (abs(x) - 1228.4) * 1658.8 / 3659.1
def stir_top_drawn(x):
return C15_Z if x <= -500 else LAYER1["z"]
def hook_bar(xl, xr, y, zz, r, tail):
"""Straight bar with 180-deg plan hooks towards y = 0; the hook extremes sit at xl / xr."""
s = -1 if y > 0 else 1
yy = y + s * 2 * r
return [(xl + r + tail, yy, zz), (xl, yy, zz), (xl, y, zz), (xr, y, zz), (xr, yy, zz), (xr - r - tail, yy, zz)]
def main():
P = json.loads((PIER / "params.json").read_text(encoding="utf-8"))
E = json.loads((PIER / "expected.json").read_text(encoding="utf-8"))
stamp = time.strftime("%Y%m%d_%H%M%S")
for f in ("params.json", "expected.json"):
shutil.copy2(PIER / f, PIER / "backup" / f"{Path(f).stem}_before_R4_{stamp}.json")
R = {m: ROWS[m] for m in MARKS}
dia = {m: R[m]["dia"] for m in MARKS}
bars = []
def add(mark, occ, pts, design, src, r=None):
bars.append(dict(mark=mark, occ=occ, pts=[[round(c, 2) for c in p] for p in pts],
r=rmin(dia[mark]) if r is None else r, design_length=round(design, 1), src=src))
# top layers C1~C4
for lay, (ma, mb), keys in ((LAYER1, ("C1", "C3"), ("A", "B", "C", "J")), (LAYER2, ("C2", "C4"), ("A", "B", "C", "J"))):
for i in range(25):
y = -1500 + 125 * i
m = ma if i % 2 == 0 else mb
add(m, f"y{y:+.0f}", [(x, y, zz) for x, zz in clip_end(lay["pts"], y)], sum(mm(R[m][k]) for k in keys),
f"정면 {'1' if lay is LAYER1 else '2'}단 작도선; 측면 '24@125'; 교번 배치 [추론]")
# C15 left top
dx, dz = -300 / 1657.4, -1630 / 1657.4
leg = mm(R["C15"]["B"])
for i in range(13):
y = -1500 + 250 * i
x0, x1 = C15_X
x0 = max(x0, -lim_x(y, C15_Z, x0))
pts = [(x0 + dx * leg, y, C15_Z + dz * leg), (x0, y, C15_Z), (x1, y, C15_Z), (x1, y, C15_Z - leg)]
add("C15", f"y{y:+.0f}", pts, mm(R["C15"]["A"]) + 2 * leg, "정면 좌측 상면 작도선 4,516.7; 단면 A-A '12@250'; 좌측 다리 단부면 평행 [추론]")
# bottom C5/C6
for i in range(13):
y = -1500 + 250 * i
m = "C5" if i % 2 == 0 else "C6"
pts = [list(p) for p in BOTTOM]
for i_end, i_leg, i_in in ((1, 0, 2), (4, 5, 3)):
(xe, ze), (xi, zi) = pts[i_end], pts[i_in]
lim = lim_x(y, ze, xe)
if abs(xe) > lim:
t = (lim - abs(xi)) / (abs(xe) - abs(xi))
nx, nz = xi + t * (xe - xi), zi + t * (ze - zi)
pts[i_leg] = [pts[i_leg][0] + nx - xe, pts[i_leg][1] + nz - ze]
pts[i_end] = [nx, nz]
add(m, f"y{y:+.0f}", [(x, y, zz) for x, zz in pts], sum(mm(R[m][k]) for k in "ABCDEJ"),
"정면 하면 작도선 (650/4,018/2,457/4,016/650); 단면 '12@250'; 교번 [추론]")
# stirrups C7~C9
stir_exp, clipped = {}, set()
for m, xs in STIR_X.items():
for x in xs:
zt = stir_top_drawn(x) + 27
zb = drawn_bottom(x) - 27
stir_exp[f"{x:.0f}"] = round(zt - zb, 1)
ly = lim_y(x, zt)
for k, (y0, y1) in enumerate(STIR_Y):
y0, y1 = max(y0, -ly), min(y1, ly)
if (y0, y1) != STIR_Y[k]:
clipped.add(x)
ym = (y0 + y1) / 2
pts = [(x, ym, zt), (x, y1, zt), (x, y1, zb), (x, y0, zb), (x, y0, zt), (x, ym, zt)]
design = 2 * (y1 - y0 + 54) + 2 * (zt - zb) + 2 * mm(R[m]["C"])
add(m, f"x{x:+.0f}_{k}", pts, design, "정면 스터럽 작도 위치·높이 (+54); 단면 A-A 3조; 형상표 II 주기")
# side bars C13 / C11 and hoops C12 / C10
hr = (mm(R["C11"]["B"]) - DIA["H22"]) / 2 # hook centreline radius 71.5
tail = mm(R["C11"]["C"])
for k, (zz, xl, xr) in enumerate(LEVELS):
m = "C13" if k < 5 else "C11"
a = xr - xl + 44
hooks = 2 * mm(R[m]["B"]) + 2 * mm(R[m]["C"])
for y in (-977.5, -272.5, 272.5, 977.5):
add(m, f"L{k + 1:02d}y{y:+.0f}", hook_bar(xl, xr, y, zz, hr, tail),
a + hooks + (mm(R[m]["J"]) if "J" in R[m] else 0),
f"측면 단 {k + 1} (z {zz}); 정면 작도 끝 {xl}/{xr}; a = 작도 + 44; 평면 갈고리 [추론]", r=hr - 0.5)
hm = "C12" if k < 6 else "C10"
yh = 1477.5
pts = [(0, -yh, zz), (xr, -yh, zz), (xr, yh, zz), (xl, yh, zz), (xl, -yh, zz), (0, -yh, zz)]
if hm == "C12":
design = 2 * (a - 790) + 2 * mm(R[hm]["B"]) + 4 * mm(R[hm]["C"]) + 4 * mm(R[hm]["J"])
add(hm, f"L{k + 1:02d}", pts, design, f"둘레 띠 단 {k + 1}; 모서리 R400; 길이 2a+2b+4c+4j", r=400)
else:
design = 2 * (xr - xl + 54) + 2 * mm(R[hm]["B"]) + 4 * mm(R[hm]["J"])
add(hm, f"L{k + 1:02d}", pts, design, f"둘레 띠 단 {k + 1}; 길이 2a+2b+4j (a = 작도 + 54)")
# C14 cross bars
c14 = []
for x, zz in C14_PTS:
s = 1 if x > 0 else -1
xx = x - s * 19
lg = mm(R["C14"]["B"])
yl = min(1500, lim_y(xx, zz), lim_y(xx - s * lg, zz) + 0)
if yl < 1500:
clipped.add(x)
add("C14", f"x{x:+.0f}z{zz:.0f}", [(xx - s * lg, -yl, zz), (xx, -yl, zz), (xx, yl, zz), (xx - s * lg, yl, zz)],
mm(R["C14"]["A"]) + 2 * lg, "정면 단면 표시 위치; 단면 A-A 3,000; 다리 방향·x 19 이격 [추론]")
c14.append([xx, zz])
# S1~S4
seats = {b["no"]: b for b in P["bearing_seats"]["bearings"]}
lg = mm(R["S1"]["B"])
s_exp = {}
for (my, mx), nos in ((("S1", "S2"), (1, 2)), (("S3", "S4"), (3, 4))):
for no in nos:
b = seats[no]
cx, cy = b["centre"]
top = b["seat_z"] + b["h1"]
z1, z2 = top - 50, top - 50 - DIA["H13"]
for o in SEAT_OFFS:
x = cx + o
add(my, f"B{no}x{o:+.0f}", [(x, cy - 600, z1 - lg), (x, cy - 600, z1), (x, cy + 600, z1), (x, cy + 600, z1 - lg)],
mm(R[my]["A"]) + 2 * lg, f"받침 {no} 보강 상세; '50','100','8@125','100','50'")
y = cy + o
add(mx, f"B{no}y{o:+.0f}", [(cx - 600, y, z2 - lg), (cx - 600, y, z2), (cx + 600, y, z2), (cx + 600, y, z2 - lg)],
mm(R[mx]["A"]) + 2 * lg, f"받침 {no} 보강 상세; S1/S3 아래 단 [추론]")
s_exp[str(no)] = dict(centre=[cx, cy], z_top=z1, marks=[my, mx])
P["coping_rebar"] = {"form": "polylines", "host": "CP", "group": "코핑", "bars": bars,
"src": "배근도(1) 정면·측면·평면, 배근도(2) 단면 A-A/B-B·받침부 보강 상세, 배근도(3) 재료표·형상표"}
P["scope"] = [g for g in ("G1", "G2", "G3", "G4", "G5") if g in set(P["scope"]) | {"G4"}]
P["builders"]["G4"] = "rebar_polylines.py"
E["checkers"]["G4"] = "g4_polylines.py"
E["negative"]["G4"] = [{"mark": "C7", "d": [5, 0, 0]}, {"mark": "C11", "d": [0, 0, 5]}]
E["G4"] = {
"schedule": {m: [L(m), int(R[m]["n"])] for m in MARKS},
"dia": {m: DIA[dia[m]] for m in MARKS},
"fixed_length": ["C1", "C2", "C3", "C4", "C5", "C6", "C14", "C15", "S1", "S2", "S3", "S4"],
"ranges": {"C7": ["B", 1656, 3106], "C8": ["B", 3054, 3184], "C9": ["B", 1526, 2977],
"C11": ["A", 3383, 9560], "C13": ["A", 9456, 9764], "C12": ["A", 8666, 8974], "C10": ["A", 3393, 9570]},
"layers": {"C1": {"z": LAYER1["z"], "y": "top125", "parity": 0}, "C3": {"z": LAYER1["z"], "y": "top125", "parity": 1},
"C2": {"z": LAYER2["z"], "y": "top125", "parity": 0}, "C4": {"z": LAYER2["z"], "y": "top125", "parity": 1},
"C15": {"z": C15_Z, "y": "y250", "parity": None},
"C5": {"z_min": z(-31753.3), "y": "y250", "parity": 0}, "C6": {"z_min": z(-31753.3), "y": "y250", "parity": 1}},
"x_extent": {"C1": [-4887.5, 4886.4], "C3": [-4887.5, 4886.4], "C2": [-4677.4, 4658.0], "C4": [-4677.4, 4658.0],
"C5": [-4887.5, 4886.4], "C6": [-4887.5, 4886.4]},
"rows": {"top125": {"start": -1500, "pitch": 125, "n": 25}, "y250": {"start": -1500, "pitch": 250, "n": 13}},
"stirrups": {"x": STIR_X, "y_pairs": STIR_Y, "height": stir_exp,
"rule": "높이 = 작도 높이 + 54 (상단 1단/C15 선 +27, 하단 하면선 -27)"},
"levels": [[zz, xl, xr] for zz, xl, xr in LEVELS],
"side": {"y": [-977.5, -272.5, 272.5, 977.5], "marks": [["C13", 5], ["C11", 8]]},
"hoops": {"y": 1477.5, "marks": [["C12", 6], ["C10", 7]]},
"c14": {"pts": c14, "y": [-1500, 1500]},
"corner": {"y_abs": 1100, "x_clipped": sorted(clipped),
"rule": "평면 모서리 R500: |y| > 1,100 철근은 R400 원호(중심선 이격 100)까지 단축 - 평면도 작도 [추론]"},
"seats": {"offsets": SEAT_OFFS, "half": 600, "leg": lg, "bearings": s_exp},
"src": "재료표 L·수량·범위; 울산P1 작도 높이(코핑 하면 기준) + 853 = 울산P2 코핑 하면 EL 68.716; 단면 A-A/B-B; 받침부 보강 상세",
}
(PIER / "params.json").write_text(json.dumps(P, ensure_ascii=False, indent=1), encoding="utf-8")
(PIER / "expected.json").write_text(json.dumps(E, ensure_ascii=False, indent=1), encoding="utf-8")
from collections import Counter
cnt = Counter(b["mark"] for b in bars)
print("bars", len(bars), {m: (cnt[m], E["G4"]["schedule"][m][1]) for m in MARKS if cnt[m] != E["G4"]["schedule"][m][1]} or "counts OK")
import statistics
for m in MARKS:
dl = [b["design_length"] for b in bars if b["mark"] == m]
print(m, "L", E["G4"]["schedule"][m][0], "mean", round(statistics.mean(dl), 1), "min", min(dl), "max", max(dl))
print("scope", P["scope"])
if __name__ == "__main__":
main()