"""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))