"""Okseong P1 coping + bearing-seat rebar (G4: C1~C20, S1~S8) -> centreline polylines for the generic builder rebar_polylines.py. Inputs: the drawing (DXF front 구조도(1): stirrup lines, side-loop lines, C18 points; side view: loop / tie y), the schedule and the shape sheet (판독/g4_shapes.json), params.json (pedestals), decisions H1-H6 (2026-10-14, all recommended). 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. Coping rebar is built in the y = 0 section frame (x, y, z) and sheared like the coping (plan skew 18 deg): x_model = x - tan(18) * y. Seat bars are built directly in the model frame. """ import json import math from collections import defaultdict from pathlib import Path from dxftools import entities HERE = Path(__file__).resolve().parent APP = HERE.parents[1] / "Code" / "교각모델링앱" P = json.loads((APP / "data" / "옥성교P1" / "params.json").read_text(encoding="utf-8")) SCHED = json.loads((HERE / "판독" / "g4_shapes.json").read_text(encoding="utf-8"))["table"] T = math.tan(math.radians(P["coping"]["skew_deg"])) ZO, F, S = -309179.8, 573551.6, 592540.9 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 dia_of(mark): return int(SCHED[mark][1][1:]) def add(mark, occ, pts, src, r=None, shear=True): if shear: pts = [(x - T * y, y, z) for x, y, z in pts] length = sum(math.dist(a, b) for a, b in zip(pts, pts[1:])) bars.append({"mark": mark, "occ": occ, "pts": [[round(v, 2) for v in p] for p in pts], "r": rb(dia_of(mark)) if r is None else r, "design_length": round(length, 1), "src": src}) def front(layer="CS-RBAR-MAJR"): return entities("D1", F - 7000, 327300, F + 7000, 330500, layer) def side(layer="CS-RBAR-MAJR"): return entities("D1", S - 2500, 327300, S + 2500, 330500, layer) # ---- drawn data --------------------------------------------------------------------------------------------------------- # front vertical lines of the stirrups: (x, bottom z, top z); column bars (bottom < 18290) and the end hooks are left out V = defaultdict(list) for e in front(): if e.dxftype() == "LINE" and abs(e.dxf.start.x - e.dxf.end.x) < 0.5: x = round(e.dxf.start.x - F, 1) lo, hi = sorted((round(e.dxf.start.y + ZO, 1), round(e.dxf.end.y + ZO, 1))) if lo >= 18290 and hi >= 21000 and abs(x) <= 5800 and x % 25 == 0: V[x].append((lo, hi)) STIR = {x: v[0] for x, v in sorted(V.items())} assert len(STIR) == 106, len(STIR) DOTS = defaultdict(list) # front points (bars along Y): z -> [x] for e in front(): if e.dxftype() == "INSERT": DOTS[round(e.dxf.insert.y + ZO, 1)].append(round(e.dxf.insert.x - F, 1)) SIDE_DOTS = defaultdict(list) # side points (bars along X): z -> [y] for e in side(): if e.dxftype() == "INSERT": SIDE_DOTS[round(e.dxf.insert.y + ZO, 1)].append(round(e.dxf.insert.x - S, 1)) FRONT_H = {} # front horizontal lines: z -> max |x| for e in front(): if e.dxftype() == "LINE" and abs(e.dxf.start.y - e.dxf.end.y) < 0.5: z = round(e.dxf.start.y + ZO, 1) FRONT_H[z] = max(FRONT_H.get(z, 0), round(max(abs(e.dxf.start.x - F), abs(e.dxf.end.x - F)), 1)) Y25 = [-1525.0] + [-1375.0 + 125 * k for k in range(23)] + [1525.0] # '125 | 150 | 22@125=2.750 | 150 | 125' FACE_Z0, FACE_Y0, FACE_Z1, FACE_Y1 = 18272.5, 1146.7, 19691.4, 1525.0 # side face: vertical above EL 19.6914, taper below K_FACE = (FACE_Y1 - FACE_Y0) / (FACE_Z1 - FACE_Z0) def face_y(z): return FACE_Y1 if z >= FACE_Z1 else FACE_Y0 + K_FACE * (z - FACE_Z0) # ---- C1~C6 top bars: 3 layers, marks alternate from the edge, straight x +-5,875, legs down at both ends --------------------- for z, legs, (ma, mb) in ((21007.8, 1247.0, ("C1", "C4")), (20927.8, 100.0, ("C2", "C5")), (20827.8, 100.0, ("C3", "C6"))): for i, y in enumerate(Y25): add((ma, mb)[i % 2], f"y{y:+.0f}", [(-5875, y, z - legs), (-5875, y, z), (5875, y, z), (5875, y, z - legs)], f"정면 선 EL {z / 1000:.4f}, x ±5,875, 다리 {legs:.0f}; 단면 사슬 25줄, 교번 [추론 F3형], 겹이음 j 미모델") # ---- C19: centre U on top ------------------------------------------------------------------------------------------------ for y in Y25: add("C19", f"y{y:+.0f}", [(-1171.4, y, 21122.2 - 370), (-1171.4, y, 21122.2), (1054.8, y, 21122.2), (1054.8, y, 21122.2 - 370)], "정면 선 EL 21.1222, x -1,171.4 ~ 1,054.8, 다리 370") # ---- C20: three L pieces x 25 rows (H3 [추론]: y rows = the top mats) -------------------------------------------------------- for z, xa, xb, leg_at in ((21055.0, -5875.0, -3402.6, "a"), (21088.6, -3647.6, -926.4, "a"), (21068.4, 809.8, 3531.0, "b")): for y in Y25: p = [(xa, y, z - 370), (xa, y, z), (xb, y, z)] if leg_at == "a" else [(xa, y, z), (xb, y, z), (xb, y, z - 370)] add("C20", f"x{xa:+.0f}y{y:+.0f}", p, f"정면 선 EL {z / 1000:.4f}, x {xa} ~ {xb}, 다리 370 (H3 [추론] 상면 y 25줄)") # ---- C7: soffit bar ('a' 606 up, slope b, flat c). y rows @250 (11) + 2 on the side face (H3 [추론]) ---------------------------- SLOPE = (19760.8 - 18295.0) / (5875.0 - 1331.1) for y in [-1250.0 + 250 * k for k in range(11)] + [-1525.0, 1525.0]: if abs(y) <= 1250.0: dz = 0.0 if abs(y) <= 1000 else 18660.2 - 18295.0 else: dz = 19691.4 - 18295.0 top = 20367.2 + dz cap = 21127.7 - 97.5 - 16 # right (lowest) coping top - stirrup cover - bar radius zr = 19760.8 + dz if zr + 606.4 > cap: # the bar would leave the coping: end where the slope meets the cap xe = 1331.1 + (cap - (18295.0 + dz)) / SLOPE pts = [(-xe, y, cap), (-1331.1, y, 18295.0 + dz), (1331.1, y, 18295.0 + dz), (xe, y, cap)] else: pts = [(-5875, y, top), (-5875, y, zr), (-1331.1, y, 18295.0 + dz), (1331.1, y, 18295.0 + dz), (5875, y, zr), (5875, y, top)] add("C7", f"y{y:+.0f}", pts, "정면 하면 경사선(±1,331.1 평평, ±5,875 EL 19.7608, 위 끝 연직 606), 측면 점 EL 18.295 / 18.6602 / 19.6914") # ---- stirrups C8~C13: per x, top = V top + 22.5, bottom = V bottom - 22.5; X offset +20 for the pentagons (coincident planes) ---- for x, (lo, hi) in STIR.items(): zt, zb = hi + 22.5, lo - 22.5 rect, pent = (("C8", "C9") if x < -1325 else (("C10", "C11") if x <= 1275 else ("C12", "C13"))) add(rect, f"x{x:+.0f}", [(x, 0, zt), (x, 1000, zt), (x, 1000, zb), (x, -1000, zb), (x, -1000, zt), (x, 0, zt)], f"정면 연직선 x {x} (EL {lo:.1f} ~ {hi:.1f}), 측면 선 y ±1,000, 위·아래 22.5 안쪽") for s in (1, -1): near = zb > FACE_Z1 - 20 # a chamfer shorter than 20 mm would break the pipe: keep the side straight yb = FACE_Y1 if near else face_y(zb) p = [(x + 20, s * 750, zt), (x + 20, s * 1525, zt)] if not near: p += [(x + 20, s * 1525, FACE_Z1)] p += [(x + 20, s * yb, zb), (x + 20, s * 500, zb), (x + 20, s * 500, zt), (x + 20, s * 750, zt)] add(pent, f"x{x:+.0f}{'R' if s > 0 else 'L'}", p, "측면 선 5각형: 안쪽 y ±500, 바깥 ±1,525 → EL 19.6914부터 측면 테이퍼, 위 d 1,025, x +20 (겹침 피함) [추론]") # ---- side loops C16 (upper 6) / C14 (lower 6) and ties C17 / C15 (4 rows) ------------------------------------------------- UP = [20922.1, 20796.4, 20546.4, 20296.4, 20046.4, 19796.4] LOW = [19546.4, 19296.4, 19046.4, 18796.4, 18546.4, 18420.7] for levels, loop, tie in ((UP, "C16", "C17"), (LOW, "C14", "C15")): for z in levels: xe = FRONT_H[z] ys = sorted(set(abs(v) for v in SIDE_DOTS[z] if abs(v) < 1600)) yo = max(ys) add(loop, f"z{z:.0f}", [(0, yo, z), (xe, yo, z), (xe, -yo, z), (-xe, -yo, z), (-xe, yo, z), (0, yo, z)], f"정면 선 EL {z / 1000:.4f} x ±{xe}, 측면 점 y ±{yo} (H6: 도면 점 위치)") for y in (-977.5, -522.5, 522.5, 977.5): assert any(abs(abs(y) - v) < 1 for v in ys), (z, y) add(tie, f"z{z:.0f}y{y:+.1f}", [(-xe, y, z), (xe, y, z)], f"정면 선 EL {z / 1000:.4f}, 측면 점 y {y} (135° 갈고리 미모델, H4)") # ---- C18: Y-direction U at every drawn front point (8 levels, 117) ------------------------------------------------------------ C18_Z = [21068.4, 21047.8, 20905.3, 20805.3, 20796.4, 20546.4, 19546.4, 18546.4] for z in C18_Z: h = face_y(z) hl = min(FACE_Y1, h) for x in sorted(DOTS[z]): zl = z + 370 if z < FACE_Z1 else z - 370 # lower rows: legs UP along the face (down would leave the soffit) [추론] yl = face_y(zl) add("C18", f"x{x:+.0f}z{z:.0f}", [(x, -yl, zl), (x, -hl, z), (x, hl, z), (x, yl, zl)] if z < FACE_Z1 else [(x, -hl, z - 370), (x, -hl, z), (x, hl, z), (x, hl, z - 370)], f"정면 점 EL {z / 1000:.4f}, 측면 선 폭 ±{hl:.1f}, 다리 370 (낮은 단은 측면 테이퍼 따라 위로, H3 [추론])") # ---- seat bars (H1, H2, H5 [추론]): pedestal k, local axes e_s (along the coping end face), e_x --------------------------------- PED = P["bearing_seats"]["pedestals"] for k, ped in enumerate(PED): c = ped["corners"] cx, cy = sum(p[0] for p in c) / 4, sum(p[1] for p in c) / 4 zp = (ped["top"]["z0"] + ped["top"]["z1"]) / 2 heavy = k == 2 # H1: pedestal 3 -> S1~S4 m_l, m_s, m_ll, m_ss = ("S1", "S2", "S3", "S4") if heavy else ("S5", "S6", "S7", "S8") d_up = dia_of(m_l) cs = math.cos(math.radians(18.0)) xc = lambda y, u=0.0: cx + u - T * (y - cy) # centre line of the pedestal along e_s, shifted by u in X # upper mesh: long bars (11) along e_s, short bars (21) along X; U legs 700 down for j, t in enumerate([-600.0] + [-500.0 + 125 * i for i in range(9)] + [600.0]): z, u, hy = zp - 50, t / cs, 2629 * cs / 2 add(m_l, f"P{k + 1}-{j:02d}", [(xc(cy - hy, u), cy - hy, z - 700), (xc(cy - hy, u), cy - hy, z), (xc(cy + hy, u), cy + hy, z), (xc(cy + hy, u), cy + hy, z - 700)], f"교좌면 상세 C-D, 대좌 {k + 1}, 긴 철근 e_s 방향 [추론 H2]", shear=False) for j, o in enumerate([-1250.0] + [-1125.0 + 125 * i for i in range(19)] + [1250.0]): z, y = zp - 50 - d_up, cy + o add(m_s, f"P{k + 1}-{j:02d}", [(xc(y) - 631, y, z - 700), (xc(y) - 631, y, z), (xc(y) + 631, y, z), (xc(y) + 631, y, z - 700)], f"교좌면 상세 C-D, 대좌 {k + 1}, 짧은 철근 X 방향 [추론 H2, H5]", shear=False) # lower mesh: 2 layers (-373, -473); long bars 11 per layer along e_s, short bars 21 per layer along X for layer, dz in enumerate((373.0, 473.0)): z7 = zp - dz for j, t in enumerate([-625.0 + 125 * i for i in range(11)]): u, hy = t / cs, 2500 * cs / 2 add(m_ll, f"P{k + 1}-{layer}{j:02d}", [(xc(cy - hy, u), cy - hy, z7), (xc(cy + hy, u), cy + hy, z7)], f"교좌면 상세 C-D, 대좌 {k + 1}, {layer + 1}층, e_s 방향 [추론 H2]", r=0, shear=False) z8 = z7 + (dia_of(m_ll) + dia_of(m_ss)) / 2 for j, o in enumerate([-1250.0 + 125 * i for i in range(21)]): y = cy + o add(m_ss, f"P{k + 1}-{layer}{j:02d}", [(xc(y) - 625, y, z8), (xc(y) + 625, y, z8)], f"교좌면 상세 C-D, 대좌 {k + 1}, {layer + 1}층, X 방향 [추론 H2, H5]", r=0, shear=False) count = {} for b in bars: count[b["mark"]] = count.get(b["mark"], 0) + 1 want = {m: int(v[3]) for m, v in SCHED.items() if m[0] in "CS"} out = {"bars": bars, "summary": {"bars": len(bars), "per_mark": count, "diff_vs_schedule": {m: count.get(m, 0) - n for m, n in want.items() if count.get(m, 0) != n}}} (HERE / "판독" / "g4_bars.json").write_text(json.dumps(out, ensure_ascii=False), encoding="utf-8") print(json.dumps(out["summary"], ensure_ascii=False))