"""R1 (shape) reading of the Ungok P2 drawings -> app params / expected (data/운곡교p2). params = drawn CAD entities (D1, read by handle) + D2 ELEVATION TABLE for bearing values (user rule from P1: table wins over drawn pedestal/pad sizes). expected = dimension texts / EL texts / tables (D1, D2), typed below with their text as the source. Drawing frame (same as P1): front view column CL x=12159.4, column top y=16515.6, footing bottom y=8115.6; side view CL x=30133.6; plan datum (12162.4, 26412.3). Writes only the R1 sections (others stay as they are); previous files go to data/운곡교p2/backup/. """ import json import math import shutil import sys import time from pathlib import Path HERE = Path(__file__).resolve().parent APP = HERE.parent / "교각모델링앱" PIER = APP / "data" / "운곡교p2" sys.path.insert(0, str(HERE)) sys.path.insert(0, str(APP)) import dxftools as T # noqa: E402 from engine import reader # noqa: E402 D1 = T.doc("D1").entitydb FX, COL_TOP_Y, FT_BOT_Y = 12159.4, 16515.6, 8115.6 PLAN = (12162.4, 26412.3) EL_FT_BOT = 25123 # D1 'EL. 25.123' FT_T, COL_H = 2200, 12133 # D1 '2.200', '12.133' Z_COL_TOP = EL_FT_BOT + FT_T + COL_H # 39456 PITCH = 7507 / 3 # D1 평면도 '3@2.502=7.507' def line(h): e = D1[h] return (e.dxf.start.x, e.dxf.start.y), (e.dxf.end.x, e.dxf.end.y) def pts(h): return [(p[0], p[1]) for p in D1[h].get_points()] def rel(p): return [round(p[0] - PLAN[0], 1), round(p[1] - PLAN[1], 1)] def centre(q): return [sum(p[0] for p in q) / 4, sum(p[1] for p in q) / 4] def angle_deg(a, b): return math.degrees(math.atan2(b[0] - a[0], b[1] - a[1])) def z_front(y): """Front-view coping region y -> elevation (mm); coping is drawn to scale above the column top.""" return Z_COL_TOP + (y - COL_TOP_Y) def table(): texts = json.loads((PIER / "drawings" / "texts.json").read_text(encoding="utf-8")) tabs = {t["key"]: t for t in reader.table_candidates(texts)} el = {int(r["_key"]): r for _, r in reader.table_records(tabs["elevation"]["rows"], str.isdigit)} sp = {int(r["_key"]): r for _, r in reader.table_records(tabs["sole_plate"]["rows"], str.isdigit)} return el, sp def params_r1(el): ft = line("21A76") # footing bottom 6984.4..17334.4 lc = line("21B62") # lean bottom 6884.4..17434.4 side_ft, side_lc = line("21CFC"), line("21DCF") steps_x = [line(h)[0][0] - FX for h in ("20E78", "20E7A", "20E7C", "20E7E")] # step risers steps_y = [line(h)[1][1] for h in ("20E77", "20E79", "20E7B", "20E7D", "20E7F")] # step treads xs = [-6000.0] + [round(x, 1) for x in steps_x] + [6000.0] top_steps = [{"x0": xs[i], "x1": xs[i + 1], "z": round(z_front(steps_y[i]), 1)} for i in range(5)] tipL, topL = line("20D63") # (6159.4,19515.6) -> (5959.4,18015.6) tipR = line("20E74")[0] # (18359.4,18015.6) flat = line("20E87") # coping flat bottom 11059.4..13259.4 p = { "lean_concrete": {"size_x": round(lc[0][0] - lc[1][0], 1), "size_y": round(side_lc[0][0] - side_lc[1][0], 1), "z_bottom": EL_FT_BOT - 100, "z_top": EL_FT_BOT, "src": {"size": "D1 정면도 21B62 / 측면도 21DCF", "thickness": "D1 21B61 (100)"}}, "footing": {"size_x": round(ft[0][0] - ft[1][0], 1), "size_y": round(side_ft[0][0] - side_ft[1][0], 1), "z_bottom": EL_FT_BOT, "thickness": round(line("21A75")[1][1] - line("21A75")[0][1], 1), "src": {"size": "D1 정면도 21A76 / 측면도 21CFC", "z_bottom": "D1 'EL. 25.123'", "thickness": "D1 21A75"}}, "column": {"shape": "circular", "diameter": round(line("214C2")[0][0] - line("214C1")[0][0], 1), "height": COL_H, "src": {"diameter": "D1 정면도 214C1/214C2", "height": "D1 dim '12.133' (column drawn with break lines)"}}, "coping": {"length_bottom_tip_to_tip": round(tipR[0] - topL[0], 1), "width_y": round(line("2143A")[1][0] - line("2143A")[0][0], 1), "z_bottom_flat": Z_COL_TOP, "flat_bottom_half_width": round((flat[1][0] - flat[0][0]) / 2, 1), "soffit_radius": round(D1["20E89"].dxf.radius, 1), "tip_bottom_rise": round(topL[1] - COL_TOP_Y, 1), "end_batter_top_inset": round(tipL[0] - topL[0], 1), "top_steps": top_steps, "src": {"outline": "D1 정면도 20D63, 20E74, 20E77~20E7F, 20E87, ARC 20E89/20E8B", "width_y": "D1 측면도 2143A", "z": "y - 16515.6 + EL 39.456 (column top)"}}, "piles": {"diameter": 600, "count_x": 6, "count_y": 5, "pitch_x": round(line("21C6D")[0][0] - line("21B44")[0][0], 1), "pitch_y": round(line("21DD6")[0][0] - line("21DD4")[0][0], 1) * -1, "edge": round(line("21B44")[0][0] - ft[1][0], 1), "length": 25900, "head_embedment": round(line("21B45")[0][1] - FT_BOT_Y, 1), "kind": "composite pile", "src": {"layout": "D1 CS-CNTL 21B44/21C6D/21C88 (X), 21DD2/21DD4/21DD6 (Y)", "diameter_length": "D1 '(Φ600.0X9T, L=25.9M)'"}}, "materials": {"fck": {"lean": "15MPa", "footing": "27MPa", "column": "40MPa", "coping": "40MPa", "seat": "40MPa"}, "src": "D1 주기 fck=40MPa(기둥·코핑), 27MPa(기초), 정면도 'CON'C fck = 15 MPa'"}, } # bearing seats (pattern of Code/교각모델링/extract_bearing_seats.py) (fl, rl), rr, fr = line("20C1E"), line("20C1F")[1], line("20C20")[1] sr, sl = line("21A3F") # split line: right point, left point ped = [fl, rl, rr, fr] front_half, rear_half = [fl, sl, sr, fr], [sl, rl, rr, sr] rows = {} for row, (b, t_) in {"front": ("20C6D", "20C6E"), "rear": ("20C64", "20C65")}.items(): q, qt = pts(b), pts(t_) rows[row] = dict(centre_rel_girder5=rel(centre(q)), rotation_deg=round(angle_deg(q[0], q[1]), 3), drawn_bottom_along=round(math.dist(q[0], q[1]), 1), drawn_bottom_across=round(math.dist(q[1], q[2]), 1), drawn_top_along=round(math.dist(qt[0], qt[1]), 1), drawn_top_across=round(math.dist(qt[1], qt[2]), 1), src=f"D1 평면도 {b} (bottom), {t_} (top)") checks = [] g4 = [line("20C26")[0], line("20C26")[1], line("20C27")[1], line("20C28")[1]] for a, b in zip(ped, g4): checks.append(dict(item="girder4 pedestal vertex", dx=round(b[0] - (a[0] - PITCH), 2), dy=round(b[1] - a[1], 2))) for row, hs in (("front", ("20C6D", "20C70")), ("rear", ("20C64", "20C67"))): c5, c4 = centre(pts(hs[0])), centre(pts(hs[1])) checks.append(dict(item=f"girder4 {row} pad centre", dx=round(c4[0] - (c5[0] - PITCH), 2), dy=round(c4[1] - c5[1], 2))) bearings = [] for no in range(1, 11): r = el[no] g, row = (no - 1) % 5 + 1, "front" if no <= 5 else "rear" pr = rows[row] bearings.append(dict(no=no, type=r["교량받침"], girder_line=g, row=row, centre=[round(pr["centre_rel_girder5"][0] + (g - 5) * PITCH, 1), pr["centre_rel_girder5"][1]], rotation_deg=pr["rotation_deg"], el_x=round(float(r["EL.'X'"]) * 1000), h1=int(r["h1"]), h2=int(r["h2"]), A=int(r["A"]), B=int(r["B"]), inferred=g <= 2)) p["bearing_seats"] = dict( _note="pedestal (h1) per girder line split front/rear; pad (h2) = frustum (A+200)x(B+200) -> (A+100)x(B+100)", plan_datum=list(PLAN), pitch_x=PITCH, reference_girder=5, pad_margin_bottom=200, pad_margin_top=100, pad_margin_src="P1 rule (D1 평면도 pad = (A+200)x(B+200) / (A+100)x(B+100))", pedestal_girder5=dict(front_half=[rel(q) for q in front_half], rear_half=[rel(q) for q in rear_half], rotation_deg=round(angle_deg(fl, rl), 3), src="D1 평면도 20C1E~20C21 (2500x1200), 21A3F (front/rear split)"), pad_rows=rows, bearings=bearings, translation_checks=checks, src={"h1_h2_A_B_ELX": "D2 교량받침 ELEVATION TABLE (table wins over drawn sizes: P1 user rule)", "row_mapping": "front NO.1-5 (S1 851), rear NO.6-10 (S1 950); 851+1299+950=3100", "girder_mapping": "NO.k / NO.k+5 -> girder line k, left->right", "drawn_vs_table": "D1 평면도 pads 900x840 (B=640) and 측면도 h1 163.4/139, h3 181 = P1 values not updated " "-> CONFLICT, table used"}) return p def expected_g1(el): tops = {1: 42456, 3: 42602, 5: 42748} # D1 'EL. 42.456' / 'EL. 42.602' / 'EL. 42.748' step = 72.9 # drawn step rise (no dimension text) [derived] step_top = {str(k): round(tops[1] + (k - 1) * step) if k in (2, 4) else tops[k] for k in range(1, 6)} step_top["2"], step_top["4"] = 42529, 42675 sx = [-6000, -6000 + 2219, -6000 + 2219 + 2502, 6000 - 2274 - 2502, 6000 - 2274, 6000] return { "footing": {"dx": 10350, "dy": 7800, "z0": EL_FT_BOT, "thickness": 2200, "src": "D1 '10.350' '7.800' 'EL. 25.123' '2.200'"}, "lean": {"margin": 100, "thickness": 100, "src": "D1 lean +100 each side, 100 thick"}, "column": {"d": 2000, "height": 12133, "src": "D1 단면 C-C '2.000', 정면도 '12.133'"}, "coping": {"dx": 12400, "dy": 3100, "z_top_left": 42456, "rise_upper": 1500, "rise_lower": 1500, "z_top_right": 42748, "z_top_center": 42602, "flat_from_tip": 5100, "end_inset": 200, "radius": 12000, "src": "D1 '12.400' '3.100' 'EL. 42.456' '1.500' x2 'EL. 42.748' 'EL. 42.602' '5.100' '200' 'R=12.000'"}, "coping_outline": [[-1100, Z_COL_TOP], [1100, Z_COL_TOP], [6200, Z_COL_TOP + 1500], [6000, 42748], [sx[4], 42748], [sx[4], 42675], [sx[3], 42675], [sx[3], 42602], [sx[2], 42602], [sx[2], 42529], [sx[1], 42529], [sx[1], 42456], [-6000, 42456], [-6200, Z_COL_TOP + 1500]], "coping_outline_src": "D1 정면도 외형 from dims '200','2.219','3@2.502=7.507','2.274','200' and EL texts; " "intermediate step EL 42.529 / 42.675 from drawn risers 72.9 [derived]", "dxf_arc_centres": {"datum_x": FX, "datum_y": COL_TOP_Y, "datum_el": Z_COL_TOP, "left": [5207.5, 6039.2], "right": [19111.2, 6039.2], "src": "DXF ARC 20E8B / 20E89 centre; datum CS-CNTL 21539 / coping bottom line 20E87"}, "piles": {"nx": 6, "ny": 5, "pitch_x": 1750, "pitch_y": 1550, "edge": 800, "d": 600, "length": 25900, "head_embedment": 100, "src": "D1 '5@1.750=8.750' '4@1.550=6.200' '800' 'Φ600' 'L=25.9M'"}, "seats": {"table": {str(n): [round(float(r["EL.'X'"]) * 1000), int(r["h1"]), int(r["h2"]), int(r["A"]), int(r["B"])] for n, r in el.items()}, "table_columns": ["EL.'X'", "h1", "h2", "A", "B"], "girders": 5, "step_top": step_top, "step_x": {str(i + 1): [sx[i], sx[i + 1]] for i in range(5)}, "row_y": {"front": -(1550 - int(el[1]["S1"])), "rear": 1550 - int(el[6]["S1"])}, "pitch_chain": [3, 7507], "pedestal_plan": [1250, 1200], "pad_margin": [200, 100], "rear_first_from_tip": 1223, "src": "D2 ELEVATION TABLE; D1 EL texts + drawn steps; S1 851/950; '3@2.502=7.507'; 대좌 2500x1200; '1.223'"}, } def main(): el, _ = table() 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_R1_{stamp}.json") P.update(params_r1(el)) P.pop("_meta", None) P["_meta"] = {"structure": "운곡교 P2 교각 (완도방향)", "status": "R1 형상 판독 2026-10-12 (Agent 초안, 사용자 확정 전)", "units": "mm; Z = EL x 1000", "frame": "X 교축직각, Y 교축, 원점 = 교각 중심", "sources": {"D1": "C0050205-028 교각도(1) P2", "D2": "C0050205-029 교각도(2) P2", "D3": "C0050205-030 교각도(3) P2"}} P["pier_id"] = "P2" P["display"]["hidden_ids"] = [] P["display"]["src"] = "R8 팔레트(2026-10-07); 숨김 없음(요구사항 기본값 '모두 표시')" E["pier_id"] = "P2" E["G1"] = expected_g1(el) (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") s = P["bearing_seats"] print(json.dumps({k: P[k] for k in ("footing", "column", "piles")}, ensure_ascii=False)) print("coping", {k: v for k, v in P["coping"].items() if k != "src"}) print("pedestal rot", s["pedestal_girder5"]["rotation_deg"], "rows", {r: (v["centre_rel_girder5"], v["rotation_deg"]) for r, v in s["pad_rows"].items()}) print("translation checks", s["translation_checks"]) print("bearing 1/6", s["bearings"][0], s["bearings"][5]) print("expected step_top", E["G1"]["seats"]["step_top"], "row_y", E["G1"]["seats"]["row_y"]) if __name__ == "__main__": main()