"""Create the app pier 'Ramp-A교P1' (form "single shaft", scope G1 + G5) through the web client: new pier (Ulsan template as a starting copy), upload copies of the four RAG DWGs, then write params / expected from the reading (판독/g1_values.json, D1 tables; DOC/02_분석/분석_Ramp-A교P1교각도면판독_2026-10-13_v0.1.00.md). params = drawn values (handles), user decisions R1-R4 (2026-10-13). expected = dimension / EL texts and the D1 tables, typed in by hand; two drawn-only inputs marked [작도]. usage: python make_pier.py (refuses if the pier already exists) """ import json import math import sys from io import BytesIO from pathlib import Path HERE = Path(__file__).resolve().parent ROOT = HERE.parents[1] APP = ROOT / "Code" / "교각모델링앱" sys.path.insert(0, str(APP / "web")) sys.path.insert(0, str(APP)) import app as webapp # noqa: E402 import store # noqa: E402 NAME = "Ramp-A교P1" V = json.loads((HERE / "판독" / "g1_values.json").read_text(encoding="utf-8")) cp = V["coping"] def circle_centre(a, b, r): """Centre (lower side) of the radius-r circle through a and b.""" (ax, az), (bx, bz) = a, b mx, mz = (ax + bx) / 2, (az + bz) / 2 d = math.hypot(bx - ax, bz - az) h = math.sqrt(r * r - d * d / 4) ux, uz = (bz - az) / d, -(bx - ax) / d return min(((mx + h * ux, mz + h * uz), (mx - h * ux, mz - h * uz)), key=lambda c: c[1]) ZB, ZT = V["column"]["z1"], cp["end_left_front"]["bottom"][1] cx, cz = circle_centre((-1000.0, ZB), (-3800.0, ZT), 5000.0) bear = {1: dict(el_x=60112, seat_z=cp["top_left"]["z"]), 2: dict(el_x=60427, seat_z=cp["top_right"]["z"])} params = { "_schema": "pier-T/1", "pier_id": "P1", "_meta": {"structure": "Ramp-A교 P1 교각 (T형 계열: 단주 + Y형 코핑 + 단일 현장타설말뚝)", "status": "형상(G1)·받침(G5) 판독 2026-10-13, 결정 R1~R4 사용자 확정. 철근 G2~G4 미판독(범위 밖)", "units": "mm; Z = EL x 1000", "frame": "X 교축직각(정면도 좌→우), Y 교축(평면도 아래→위), 원점 = 교각 중심", "sources": {"D1": "RAG/Ramp-A교 P1 교각(1).dwg 일반도", "D2": "(2) 코핑·기둥 배근(1)", "D3": "(3) 주철근 조립도·재료표", "D4": "(4) 기둥 재료표·떠오름 방지 PLATE"}, "reading": "DOC/02_분석/분석_Ramp-A교P1교각도면판독_2026-10-13_v0.1.00.md, Code/RampA판독/판독/g1_values.json"}, "scope": ["G1", "G5"], "builders": {"G1": "concrete_shaft.py"}, "pile": {"segments": [{"d": V["pile_lower"]["d"], "z0": V["pile_lower"]["z0"], "z1": V["pile_lower"]["z1"]}, {"d": V["pile_upper"]["d"], "z0": V["pile_upper"]["z0"], "z1": V["pile_upper"]["z1"]}], "kind": "현장타설말뚝 (암반 근입 Ø2,350 + 상부 Ø2,500)", "src": f"D1 정면 {V['pile_lower']['src']}; {V['pile_upper']['src']}; 상부 Ø2,500은 '75, 2,350, 75' 표기(케이싱 포함 가능) — 작도선대로 콘크리트"}, "column": {"shape": "circular", "diameter": V["column"]["d"], "z0": V["column"]["z0"], "z1": V["column"]["z1"], "src": f"D1 정면 {V['column']['src']}"}, "coping": {"form": "y_shaft", "z_bottom": ZB, "z_tip": ZT, "half_bottom": 1000.0, "soffit": {"centre_x": round(cx, 4), "centre_z": round(cz, 4), "radius": 5000.0}, "z_top_left": cp["top_left"]["z"], "z_top_right": cp["top_right"]["z"], "top_half": 3500.0, "plan_end": {"corner_x": abs(cp["plan_end_left"]["corner_bottom_x"]), "tip_x": 3800.0, "half_width": cp["plan_end_left"]["half_width"]}, "side": {"half_width_top": cp["side"]["half_width_top"], "half_width_bottom": 1000.0}, "src": {"front": "D1 정면 856/854/855 상면, 857/7D1 끝면, ARC 501/4FF R5,000 (중심은 기둥면·끝 하단 두 점과 R로 재계산)", "side": cp["side"]["src"], "plan_end": f"{cp['plan_end_left']['src']} / {cp['plan_end_right']['src']}", "decisions": "R2 직사각 단면(정면 ∩ 측면), R3 우측 상단 3,500(평면값; 정면 작도 3,495.1)", "not_modelled": "R1 장식 홈(상세 B, 30x30 2줄)"}}, "materials": {"fck": {"pile": "35MPa", "column": "40MPa", "coping": "40MPa", "seat": "40MPa"}, "src": "D1 NOTE '기둥 (40MPa), 코핑 (40MPa), 현장타설말뚝 (35MPa)'; 대좌 40 [추론]"}, "bearing_seats": { "_note": "pedestal = C3 (along) x C6 (across) box per bearing (h1), rotated as drawn; pad (h2) = frustum (A+200)x(B+200) -> (A+100)x(B+100)", "pedestal": {"size_x": 1300.0, "size_y": 1400.0}, "pad_margin_bottom": 200, "pad_margin_top": 100, "bearings": [dict(no=no, type="7000 kN", centre=V["plan"][str(no)]["pedestal"]["centre"], rotation_deg=V["plan"][str(no)]["pedestal"]["rotation_deg"], h1=110, h2=50, A=900, B=800, **bear[no]) for no in (1, 2)], "src": "D1 평면 49D~4A0 / 4A5~4A8 (대좌), 486~48B (패드·받침), ELEVATION TABLE h1·h2·A·B·C3·C6"}, "sole_plates": {"table": [{"no": no, "S": -5.526, "L": 1000, "W": 900, "T": 50, "T1": 78, "T2": 22} for no in (1, 2)], "h3": 190, "bearing_size": "AB", "bearing_shrink": 100, "colours": {"bearing": [0, 0, 0], "sole_plate": [150, 150, 150]}, "sole_plate_src": "D1 솔플레이트 상세 TABLE (S, L, W, T, T1, T2); L 교축 방향; S < 0 -> 두꺼운 쪽 -Y (R4, 운곡교 규칙 [추론])", "src": "교량받침 = A x B (900 x 800), h3 190 (ELEVATION TABLE); 솔플레이트 T = h4 50"}, "rebar_marks": {}, "rebar_marks_src": "철근 G2~G4 다음 Task", } expected = { "_schema": "pier-T-expected/1", "_note": "독립 기대값: D1 치수 문자·EL 문자·표에서 옮긴 값. params와 별도. [작도] = 치수 문자 없음(작도값)", "pier_id": "P1", "tol_mm": 1.0, "checkers": {"G1": "g1_shaft.py"}, "negative": {"G1": [{"id": "BS02", "d": [3, 0, 0]}, {"id": "BP01", "d": [0, 0, 2]}], "G5": [{"id": "BB02", "d": [5, 0, 0]}, {"id": "SP01", "d": [0, 0, 5]}]}, "planned_fail": [], "G1": { "pile": {"z_bottom": 32752, "segments": [{"d": 2350, "length": 7500}, {"d": 2500, "length": 5200}], "src": "'EL=32.752', '75, 2,350, 75', '2,500', '7,500', '5,200'"}, "column": {"d": 2000, "z0": 45452, "length": 12000, "src": "'2,000', 'EL=45.452', '11,500' + '500'"}, "coping": {"top_length": 7000, "bottom_length": 7600, "column_face_half": 1000, "soffit_rise": 1500, "soffit_radius": 5000, "width_y": 2200, "side_bottom_width": 2000, "z_top_left": 59952, "z_top_right": 60267, "top_step": 315, "plan_end_corner_x": 3650, "src": "'7,000', '7,600' (2,800+2,000+2,800), '1,500', '1,000', 'R=5,000', '2,200' (1,100+1,100), " "'EL=59.952', 'EL=60.267', '315'; plan_end_corner_x [작도] D1 평면 51A"}, "total_height": 27200, "seats": {"table": {"1": [60112, 110, 50, 900, 800], "2": [60427, 110, 50, 900, 800]}, "pos": {"1": [-2250, 0], "2": [2250, 0]}, "pedestal_along": 1400, "pedestal_across": 1300, "rotation_deg": {"1": -5.55, "2": -9.194}, "pad_margin": [200, 100], "src": "ELEVATION TABLE; 평면 '2,250' x2; C3 1,400, C6 1,300; rotation [작도] D1 평면"}, }, "G5": {"el_x": {"1": 60112, "2": 60427}, "S": {"1": -5.526, "2": -5.526}, "W": {"1": 900, "2": 900}, "L": 1000, "T": 50, "h3": 190, "bearing_size": {"1": [900, 800], "2": [900, 800]}, "bearing_shrink": 100, "thick_end": "-Y", "colours": {"bearing": [0, 0, 0], "sole_plate": [150, 150, 150]}, "src": "ELEVATION TABLE (EL.'X', h3, A, B, h4 = T); 솔플레이트 상세 TABLE (S, L, W, T1 78 / T2 22 -> 두꺼운 쪽 -Y, R4)"}, } if __name__ == "__main__": if (store.DATA / NAME).exists(): sys.exit(f"pier exists: {NAME}") c = webapp.app.test_client() r = c.post("/pier/new", data={"name": NAME, "type": "T형", "template": "tpl:산외교울산P1형"}) assert r.status_code == 302, r.status_code dwgs = sorted((ROOT / "RAG").glob("Ramp-A교 P1 교각(*).dwg")) r = c.post(f"/pier/{NAME}/upload", data={"dwg": [(BytesIO(p.read_bytes()), p.name) for p in dwgs]}, content_type="multipart/form-data") assert r.status_code == 302, r.status_code p = store.Pier(NAME) params["display"] = p.load("params")["display"] # Ulsan display (layers incl. 말뚝, palette) expected["rebar_palette"] = p.load("expected")["rebar_palette"] p.save("params", params) p.save("expected", expected) meta = store.rjson(p.dir / "pier.json") meta["template"] = "신규 판독 (Ramp-A교 P1 판독서 v0.1.00)" store.wjson(p.dir / "pier.json", meta) print("created", p.dir, "texts", len(p.texts()))