Files
Pier_v1/Code/RampA판독/extract_g4.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

199 lines
11 KiB
Python

"""Ramp-A P1 coping rebar (G4: C1~C16, S1~S4) -> centreline polylines for the generic builder rebar_polylines.py.
Inputs: drawn values read from D2 / D3 (판독서 v0.1.06 §13, values typed below with their source) and the user
decisions T1-T6 (2026-10-14). Output: 판독/g4_bars.json {"bars": [...], "summary": {...}} used by add_g4.py.
Model axes: X along the coping, Y across, Z = EL x 1000; pier centre at the origin.
"""
import json
import math
from pathlib import Path
HERE = Path(__file__).resolve().parent
APP = HERE.parents[1] / "Code" / "교각모델링앱"
P = json.loads((APP / "data" / "Ramp-A교P1" / "params.json").read_text(encoding="utf-8"))
CP = P["coping"]
TL, TR = CP["z_top_left"], CP["z_top_right"] # 59952 / 60267
ZB, ZT = CP["z_bottom"], CP["z_tip"] # 57452 / 58952
SCX, SCZ = abs(CP["soffit"]["centre_x"]), CP["soffit"]["centre_z"]
bars = []
def rb(dia): # min bend radius of the centreline (as the other builders)
return ((2 * dia if dia <= 16 else 3.5 * dia) + dia) / 2
def add(mark, occ, pts, dia, L, src, r=None):
bars.append({"mark": mark, "occ": occ, "pts": [[round(v, 2) for v in p] for p in pts],
"r": rb(dia) if r is None else r, "design_length": round(L, 1), "src": src})
def top(x):
return TL if x <= 0 else TR
def arc_z(x, R):
"""z of the soffit-parallel arc of radius R (centre (+-SCX, SCZ)) at x; flat zone at the column handled by caller."""
d = SCX - abs(x)
return SCZ + math.sqrt(R * R - d * d)
def face_y(z):
"""Half width of the coping side at z (2,200 above the tip level, taper to 2,000 at the column top)."""
return 1100.0 if z >= ZT else 1000.0 + 100.0 * (z - ZB) / (ZT - ZB)
# ---- top bars C1/C4 (1단), C2/C5 (2단), C3 (3단): D2 정면 작도 end points, 단면 A-A/B-B rows
ROW17 = [-1000 + 125 * k for k in range(17)]
ROW9 = [-1000 + 250 * k for k in range(9)]
TOPS = { # mark: (z, [left leg end], x_left, x_right, [right leg end], L, dia)
1: (59852.0, (-3672.0, 59030.4), -3425.6, 3492.1, (3679.3, 59031.7), 8617, 29),
2: (59752.0, (-3485.6, 59652.0), -3455.6, 3515.0, (3537.8, 59652.0), 7177, 29),
3: (59652.0, (-3515.6, 59552.0), -3485.6, 3537.8, (3560.6, 59552.0), 7230, 25),
}
for layer, (z, le, xl, xr, re_, L, dia) in TOPS.items():
ys = ROW9 if layer == 3 else ROW17
for k, y in enumerate(ys):
mark = {1: ("C1", "C4"), 2: ("C2", "C5"), 3: ("C3", "C3")}[layer][k % 2]
add(mark, f"y{y:+.0f}", [(le[0], y, le[1]), (xl, y, z), (xr, y, z), (re_[0], y, re_[1])], dia, L,
f"D2 정면 {layer}단 작도선, 단면 A-A/B-B {'@250' if layer == 3 else '@125 교번'}")
# ---- C6 soffit bars: leg 400 along the end face, R 5,100 arc, bottom 1,914 at EL 57.552 (D2 ARC 478/479); T1: 9 @250;
# the bars near the faces keep inside the side taper (y <= face - 100.2) [T1 추론]
for k in range(9):
y0 = -1000 + 250 * k
pts = [(-3557.1, y0, 59413.5)]
a0, a1 = math.radians(79.1), math.radians(43.8)
for i in range(21):
t = a0 + (a1 - a0) * i / 20
pts.append((-SCX + 5100 * math.cos(t), y0, SCZ + 5100 * math.sin(t)))
for i in range(21):
t = math.radians(180 - 43.8) + (math.radians(180 - 79.1) - math.radians(180 - 43.8)) * i / 20
pts.append((SCX + 5100 * math.cos(t), y0, SCZ + 5100 * math.sin(t)))
pts.append((3590.3, y0, 59421.7))
pts = [(x, math.copysign(min(abs(y), face_y(z) - 100.2), y) if y else 0.0, z) for x, y, z in pts]
add("C6", f"y{y0:+.0f}", pts, 25, 9003, "D2 정면 ARC 478/479 R5,100, 하면 1,914 EL 57.552; @250 9본 (T1 [추론])", r=0)
# ---- stirrups at x = -3375 ... 3375 @125 (D2 정면 55 작도선): outer C7 / C9 / C11, inner pair C8 / C10 / C12
XS = [-3375 + 125 * i for i in range(55)]
def bottom(x):
"""Stirrup bottom (centreline): 77.5 above the soffit, i.e. on R 5,077.5, flat 57,529.5 over the column."""
return ZB + 77.5 if abs(x) < 1000 else arc_z(x, 5077.5)
Z_C1 = TL - 996.7 # inner outer-leg vertical bottom (단면 A-A/B-B)
for x in XS:
zt, zb = top(x) - 77.5, bottom(x)
b = zt - zb
outer, inner = ("C7", "C8") if x < -875 else (("C9", "C10") if x <= 875 else ("C11", "C12"))
add(outer, f"x{x:+.0f}", [(x, -62.5, zt), (x, 750, zt), (x, 750, zb), (x, -875, zb), (x, -875, zt), (x, -62.5, zt)],
19, 2 * 1673 + 2 * b + 2 * 450, f"D2 정면 x {x}; 단면 B-B y -875~750; b {b:.1f}")
yb = face_y(zb) - 100.2
for s in (1, -1):
xi = x + 20 # beside the outer stirrup (T6 [추론])
p = [(xi, s * 687.5, zt), (xi, s * 1000, zt), (xi, s * 1000, Z_C1), (xi, s * yb, zb), (xi, s * 375, zb),
(xi, s * 375, zt), (xi, s * 687.5, zt)]
c2 = math.hypot(1000 - yb, Z_C1 - zb)
add(inner, f"x{x:+.0f}{'R' if s > 0 else 'L'}", p, 19, 673 + b + (yb - 375) + c2 + (zt - Z_C1) + 2 * 450,
f"단면 A-A/B-B 안쪽 모따기 스터럽, x {x}+20 [T6 추론]; c1 {zt - Z_C1:.0f} (T4)")
# ---- side loops C14 (4 levels) / C13 (6 levels): D2 정면 수평선 ends; 3 concentric loops per level (T2)
LEVELS = [ # z, x_left, x_right (D2 정면)
(59827.0, -3433.1, 3497.9), (59577.0, -3508.1, 3554.9), (59327.0, -3583.1, 3611.9), (59077.0, -3658.1, 3669.0),
(58827.0, -2927.0, 2927.1), (58577.0, -2343.2, 2343.2), (58327.0, -1903.0, 1903.1), (58077.0, -1544.6, 1544.7),
(57827.0, -1241.9, 1242.0), (57577.0, -981.2, 981.3)]
for i, (z, xl, xr) in enumerate(LEVELS):
mark = "C14" if i < 4 else "C13"
yo = min(977.5, face_y(z) - 122.7) # outer dots follow the taper (단면 B-B -968.9 ...)
for k, (yn, yp) in enumerate(((-yo, yo), (-852.5, 727.5), (-397.5, 397.5))):
a, b_ = xl + 25 * k, xr - 25 * k
p = [(0, yp, z), (b_, yp, z), (b_, yn, z), (a, yn, z), (a, yp, z), (0, yp, z)]
L = 2 * (b_ - a + 48) + 2 * (yp - yn) + 2 * 350 + (290 if mark == "C14" else 0)
add(mark, f"z{z:.0f}-{k}", p, 19, L, f"D2 정면 수평선 EL {z / 1000:.3f}; 동심 고리 {k} (T2 [추론])")
# ---- C16: raised right top, x 100~3,400 at EL 60.167, legs 350, y @125 (17)
for y in ROW17:
add("C16", f"y{y:+.0f}", [(100, y, 60167 - 350), (100, y, 60167), (3400, y, 60167), (3400, y, 60167 - 350)], 19,
4000, "D2 정면 C16 EL 60.167, x 100~3,400, 다리 350")
# ---- C15: transverse U on top, midway between stirrups, |x| <= 3,187.5 except 62.5 (T3 [추론]) -> 51
for x in [-3312.5 + 125 * i for i in range(54)]:
if abs(x) > 3187.5 or x == 62.5:
continue
z = top(x) - 77.5 - 19
add("C15", f"x{x:+.1f}", [(x, -1000, z - 350), (x, -1000, z), (x, 1000, z), (x, 1000, z - 350)], 19, 2700,
"상면 횡방향 U형, 스터럽 사이 중간 위치 (T3 [추론])")
# ---- seat bars per bearing (T5): local axes u (along, rotated), v (across); pedestal top = seat_z + h1
for bng in P["bearing_seats"]["bearings"]:
no, (cx, cy), rot = bng["no"], bng["centre"], math.radians(bng["rotation_deg"])
u = (math.sin(rot), math.cos(rot))
v = (math.cos(rot), -math.sin(rot))
zp = bng["seat_z"] + bng["h1"]
g = lambda su, sv, z: (cx + su * u[0] + sv * v[0], cy + su * u[1] + sv * v[1], z)
# S1: straight a 1,300 along u, legs 700, 11 positions along v (50|100|8@125|100|50 = 1,300)
for j, sv in enumerate([-600] + [-500 + 125 * k for k in range(9)] + [600]):
z = zp - 50
add("S1", f"B{no}-{j:02d}", [g(-650, sv, z - 700), g(-650, sv, z), g(650, sv, z), g(650, sv, z - 700)], 16, 2700,
f"교좌면 보강 E-E, 받침 {no} [T5 추론]")
# S2: straight a 1,200 along v, legs 700, 12 positions along u (9@125 + 2 edge [추론])
for j, su in enumerate([-625] + [-562.5 + 125 * k for k in range(10)] + [625]):
z = zp - 50 - 16
add("S2", f"B{no}-{j:02d}", [g(su, -600, z - 700), g(su, -600, z), g(su, 600, z), g(su, 600, z - 700)], 16, 2600,
f"교좌면 보강 E-E, 받침 {no} [T5 추론]")
# S3 (along u) / S4 (along v): 2 layers x 11, 10@125 = 1,250, straight 1,250, 345 / 445 below the pedestal top
for layer, dz in enumerate((345, 445)):
for j, s_ in enumerate([-625 + 125 * k for k in range(11)]):
add("S3", f"B{no}-{layer}{j:02d}", [g(-625, s_, zp - dz), g(625, s_, zp - dz)], 25, 1250,
f"교좌면 보강 F-F, 받침 {no}, {layer + 1}층 [T5 추론]", r=0)
add("S4", f"B{no}-{layer}{j:02d}", [g(s_, -625, zp - dz - 25), g(s_, 625, zp - dz - 25)], 25, 1250,
f"교좌면 보강 F-F, 받침 {no}, {layer + 1}층 [T5 추론]", r=0)
# ---- plan-corner clipping [추론, 울산 R500 모서리 단축과 같은 방식]: the end face is convex in plan (corner 3,650 at the
# tip level vs 3,800 at y = 0), so points nearer than cover 40 (+ bar radius) to it are pulled inwards in x
PE = CP["plan_end"]
RP = (PE["half_width"] ** 2 + (PE["tip_x"] - PE["corner_x"]) ** 2) / (2 * (PE["tip_x"] - PE["corner_x"]))
def face_x(y, z, sx):
xa = PE["tip_x"] - (RP - math.sqrt(RP * RP - min(abs(y), PE["half_width"]) ** 2))
zt = TL if sx < 0 else TR
return xa + (z - ZT) / (zt - ZT) * (CP["top_half"] - xa)
DIA = {"C1": 29, "C2": 29, "C3": 25, "C4": 29, "C5": 29, "C6": 25, "C13": 19, "C14": 19}
clipped = {}
for b in bars:
m = b["mark"]
if m not in DIA:
continue
r = DIA[m] / 2
pts = b["pts"]
if m in ("C13", "C14"): # keep the loop rectangular: one limit per short side
ymax = max(abs(p[1]) for p in pts)
for sx in (-1, 1):
lim = min(face_x(ymax + r, p[2], sx) for p in pts) - (r + 40)
if any(p[0] * sx > lim for p in pts):
clipped[m] = clipped.get(m, 0) + 1
for p in pts:
if p[0] * sx > 0 and abs(p[0]) > 1:
p[0] = round(sx * min(abs(p[0]), lim), 2)
continue
hit = False
for p in pts:
sx = 1 if p[0] > 0 else -1
lim = face_x(abs(p[1]) + r, p[2], sx) - (r + 40)
if abs(p[0]) > lim:
p[0] = round(sx * lim, 2)
hit = True
clipped[m] = clipped.get(m, 0) + hit
if hit:
b["src"] += "; 평면 모서리 단축 [추론]"
count = {}
for b in bars:
count[b["mark"]] = count.get(b["mark"], 0) + 1
out = {"bars": bars, "summary": {"bars": len(bars), "per_mark": count, "corner_clipped": clipped}}
(HERE / "판독" / "g4_bars.json").write_text(json.dumps(out, ensure_ascii=False), encoding="utf-8")
print(json.dumps(out["summary"], ensure_ascii=False))