Files
Pier_v1/Code/울산P2판독/extract_s1.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

187 lines
12 KiB
Python

"""S1 (shape) reading of RAG/교각배근도(울산P2).dwg -> app pier data/산외교울산P2 (params, expected), scope G1 + G5.
Same sheet layout as Ulsan P1 (일반도(1), drawn in mm); the P2 differences were found by comparing the text sets and the
entities (2026-10-15): footing 7,800 x 7,500 on 30 steel pipe piles, ROTATED 37.000 deg against the pier axes, column 27.300 m,
all levels shifted; coping outline, bearings (4) and plan positions follow the same rules as P1.
Datums (front view, drawn): column CL x -85062.7; footing bottom y -23099.7 = EL 38.916; coping bottom y -14499.7
(EL 38.916 + 2.500 + 27.300 = 68.716, coping drawn to scale above it). Plan centre (-85062.7, -1608.4) = column circle 1ECD9.
params = drawn entities (handles in src) + ELEVATION / 솔플레이트 TABLE.
expected = dimension / EL texts and tables typed with their text, independent of params.
Rotation: the foundation plan is drawn in the foundation's own axes; the pier CL (arrow) lies at +52.5 deg, so the pier X axis is at
-37 deg in the foundation frame and the foundation is rotated +37 deg (counter-clockwise, X right / Y up) in the pier frame.
The drawing note '구조물 기초는 37.000˚회전' + '회전이 고려된 길이 9.767 m / 9.391 m' (= 7.800/cos37, 7.500/cos37) confirms the angle.
"""
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
E1 = T.doc("D1").entitydb
FX, FB_Y, CB_Y = -85062.7, -23099.7, -14499.7
PLAN = (-85062.7, -1608.4)
EL_FB, FT_T, COL_H = 38916, 2500, 27300 # 'EL. 38.916', '2.500', '27.300'
Z_CB = EL_FB + FT_T + COL_H # 68716
ROT_DEG = 37.0 # '구조물 기초는 37.000˚회전'
def line(h):
e = E1[h]
return (e.dxf.start.x, e.dxf.start.y), (e.dxf.end.x, e.dxf.end.y)
def z(y):
return round(Z_CB + (y - CB_Y), 1)
def rx(x):
return round(x - FX, 1)
def tables(texts):
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) if "EL.'X'" in r}
sp = {int(r["_key"]): r for _, r in reader.table_records(tabs["sole_plate"]["rows"], str.isdigit)
if "S(%)" in r and int(r["_key"]) in el}
return el, sp
def main():
p1 = json.loads((APP / "data" / "산외교울산P1" / "params.json").read_text(encoding="utf-8"))
e1 = json.loads((APP / "data" / "산외교울산P1" / "expected.json").read_text(encoding="utf-8"))
bk = PIER / "backup"
bk.mkdir(exist_ok=True)
stamp = time.strftime("%Y%m%d_%H%M%S")
for f in ("params.json", "expected.json"):
shutil.copy2(PIER / f, bk / f"{Path(f).stem}_before_S1_{stamp}.json")
texts = json.loads((PIER / "drawings" / "texts.json").read_text(encoding="utf-8"))
el, sp = tables(texts)
# ---- drawn entities (handles from the 2026-10-15 dump)
ft, lc = line("1EA54"), line("1EA5A") # footing / lean bottoms, front (7,800 / 8,000)
ft_side, lc_side = line("1EBE5"), line("1EBEA") # side (7,500 / 7,700)
thick = line("1EBE4") # 2,500
col = abs(line("1EA45")[0][0] - line("1EA46")[0][0]) # column outline +-1250
flat = line("1EA1F") # coping bottom flat +-1250
tipL, tipR = line("1EA1E")[1], line("1EA20")[0] # (-5000, 1700) / (+5000, 1700) above the flat
topL, topR = line("1EA2A")[0], line("1EA21")[0] # (-4700, EL 72.046) / (+4700, EL 71.916)
zcb = z(flat[0][1])
assert abs(zcb - Z_CB) < 0.5, zcb
profile = [[-1250.0, zcb], [1250.0, zcb], [rx(tipR[0]), z(tipR[1])], [rx(topR[0]), z(topR[1])], [0.0, z(topR[1])],
[0.0, z(topL[1])], [rx(topL[0]), z(topL[1])], [rx(tipL[0]), z(tipL[1])]]
width = abs(line("1EBC8")[0][0] - line("1EBC8")[1][0])
P = json.loads(json.dumps(p1)) # start from P1 (same form), then replace what differs
P["pier_id"] = "P2"
P["_meta"] = {"structure": "산외교 울산 P2 교각 (T형, 직선 경사 코핑, 강관말뚝 기초 37° 회전)",
"status": "S1 형상 판독 2026-10-15 (Agent 초안, 사용자 확정 전)", "units": "mm; Z = EL x 1000",
"frame": "X 교축직각(정면도 좌→우), Y 교축(평면도 아래→위 = 전열→후열), 원점 = 교각(기둥) 중심. 기초(버림·기초판·말뚝)는 이 축에서 +37° 회전(평면도 반시계)",
"sources": {"D1": "RAG/교각배근도(울산P2).dwg — 일반도(2) + 배근도"}}
P["scope"] = ["G1", "G5"]
P["builders"] = {"G1": "concrete_tapered.py"}
P["lean_concrete"] = {"size_x": round(lc[0][0] - lc[1][0], 1), "size_y": round(lc_side[0][0] - lc_side[1][0], 1),
"z_bottom": EL_FB - 100, "z_top": EL_FB, "src": "일반도 정면 1EA5A (8,000) / 측면 1EBEA (7,700), 두께 100 (기초 자체 좌표계)"}
P["footing"] = {"size_x": round(ft[0][0] - ft[1][0], 1), "size_y": round(ft_side[0][0] - ft_side[1][0], 1),
"z_bottom": EL_FB, "thickness": round(abs(thick[0][1] - thick[1][1]), 1), "rot_deg": ROT_DEG,
"src": "일반도 정면 1EA54 (7,800) / 측면 1EBE5 (7,500), 1EBE4 (2,500); 'EL. 38.916'; 기초 평면도 '구조물 기초는 37.000˚회전' "
"(회전 길이 9.767 m = 7.800/cos37, 9.391 m = 7.500/cos37). 회전 방향: 기초 평면 교각 CL(화살표 +Y)이 +52.5° → 기초가 +37° [추론]"}
P["piles"] = {"count_x": 6, "count_y": 5, "pitch_x": 1300.0, "pitch_y": 1550.0, "diameter": 508.0, "length": 7000.0,
"head_embedment": 100.0, "kind": "강관말뚝 Ø508.0x9T (속찬 원기둥, O5)",
"src": "기초 평면도 '5@1.300=6.500' · '4@1.550=6.200', 가장자리 '650' + 버림 '100'; '(Φ508.0X9T, L=7.0M)'; 말뚝 머리 = 기초 하면 + 100 (정면 말뚝 상단 y -22999.7 vs 기초 하면 -23099.7); 속찬 원기둥은 옥성교 O5 결정 재적용 [추론]"}
P["column"] = {"shape": "circular", "diameter": round(col, 1), "height": COL_H,
"src": "일반도 정면 1EA45/1EA46 (+-1,250), 단면 A-A '2.500'; 높이 '27.300' (파단 작도)"}
c = P["coping"]
c["profile_xz"] = profile
c["width_y"] = round(width, 1)
c["plan_length"] = round(tipR[0] - tipL[0], 1)
c["top_length"] = round(topR[0] - topL[0], 1)
c["z_tip"], c["z_top_left"], c["z_top_right"] = z(tipL[1]), z(topL[1]), z(topR[1])
c["src"] = dict(c["src"], profile="일반도 정면 1EA1F (하면 평탄), 1EA1E/1EA20 (하면 경사), 1EA2A/1EA21 (끝면 300 경사), 1EA1D/1EA1C (상면, 중심 130 단차)",
width="일반도 측면 1EBC8 (3,200)")
# ---- seats (same rules as P1)
bearings = []
for no in sorted(el):
r = el[no]
x = -3250.0 if no in (1, 3) else 3250.0
s1 = int(r["S1"])
y = -(1600 - s1) if no in (1, 2) else (1600 - s1)
bearings.append({"no": no, "type": r["교량받침"], "row": "front" if no in (1, 2) else "rear",
"centre": [x, float(y)], "rotation_deg": 0.0,
"seat_z": float(c["z_top_left"] if x < 0 else c["z_top_right"]),
"el_x": round(float(r["EL.'X'"]) * 1000), "h1": int(r["h1"]), "h2": int(r["h2"]),
"A": int(r["A"]), "B": int(r["B"])})
drawn = {}
for no, hs in ((3, ("1F053", "1F054", "1F055")), (4, ("1F04B", "1F04C", "1F04D")), (1, ("1F063", "1F064", "1F065")),
(2, ("1F05B", "1F05C", "1F05D"))):
(x0, y0), (x1, y1) = line(hs[0])
(_, _), (x2, _) = line(hs[2])
ya, yb = line(hs[0])[0][1], line(hs[0])[1][1]
drawn[no] = [round((x0 + x2) / 2 - PLAN[0], 1), round((ya + yb) / 2 - PLAN[1], 1)]
bs = P["bearing_seats"]
bs["pedestal"] = {"size_x": int(el[1]["C3"]), "size_y": int(el[1]["C6"])}
bs["bearings"], bs["drawn_pedestal_centres"] = bearings, drawn
bs["src"] = dict(bs["src"], position="평면 '1.750','6.500','1.750' (X), S1 (Y); 작도 대좌(1F04B~1F066) 중심과 비교")
rows = []
for no in sorted(sp):
r = sp[no]
rows.append({"no": no, "S": float(r["S(%)"]), "L": int(r["L"]), "W": int(r["W"]), "T": int(r["T"]),
"T1": int(r["T1"]), "T2": int(r["T2"]), "h3": int(el[no]["h3"])})
P["sole_plates"]["table"] = rows
P["rebar_marks_src"] = "철근(G2~G4)은 이번 단계 범위 밖 — 울산 P1 값(사용 안 함)"
E = json.loads(json.dumps(e1))
E["pier_id"] = "P2"
E["_note"] = "독립 기대값: 일반도 치수 문자·EL 문자·표에서 옮긴 값. params와 별도."
E["checkers"] = {"G1": "g1_tapered.py"}
E["negative"] = {"G1": [{"id": "BS02", "d": [3, 0, 0]}, {"id": "BP03", "d": [0, 0, 2]}],
"G5": [{"id": "BB04", "d": [5, 0, 0]}, {"id": "SP03", "d": [0, 0, 5]}]}
E["planned_fail"] = []
for k in [k for k in E if k.startswith("G") and k not in ("G1", "G5")]:
del E[k]
G1 = E["G1"]
G1["footing"] = {"dx": 7800, "dy": 7500, "z0": EL_FB, "thickness": 2500, "rot_deg": ROT_DEG,
"src": "'7.800'(정면·기초 평면), '7.500'(측면·기초 평면), 'EL. 38.916', '2.500', '구조물 기초는 37.000˚회전' (회전 길이 9.767 = 7.800/cos37, 9.391 = 7.500/cos37)"}
G1["lean"] = {"margin": 100, "thickness": 100, "src": "'100','7.800','100' / '100','7.500','100'; 두께 '100'"}
G1["piles"] = {"nx": 6, "ny": 5, "pitch_x": 1300, "pitch_y": 1550, "edge_x": 650, "edge_y": 650, "d": 508, "length": 7000,
"head_embedment": 100, "src": "'5@1.300=6.500','650','100'; '4@1.550=6.200','650','100'; 'Φ508.0X9T, L=7.0M'; 머리 100(작도)"}
G1["column"] = {"d": 2500, "height": 27300, "src": "단면 A-A '2.500', '27.300'"}
G1["coping"] = dict(G1["coping"], z_top_left=72046, z_top_right=71916,
src="'1.250','1.250','3.750' x2; '10.000'; '300','2@4.700=9.400','300'; '1.700'; '3.200'; 'EL. 72.046','EL. 71.916'; 'R=500'")
G1["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()},
"pos": {str(n): [-3250 if n in (1, 3) else 3250, -(1600 - int(r["S1"])) if n in (1, 2) else 1600 - int(r["S1"])]
for n, r in el.items()},
"pedestal": [int(el[1]["C3"]), int(el[1]["C6"])], "pad_margin": [200, 100],
"src": "ELEVATION TABLE; 평면 '1.750','6.500','1.750', S1; 상세 C3·C6, h2·50"}
G5 = E["G5"]
G5["el_x"] = {str(n): round(float(r["EL.'X'"]) * 1000) for n, r in el.items()}
G5["S"] = {str(r["no"]): r["S"] for r in rows}
G5["W"] = {str(r["no"]): r["W"] for r in rows}
G5["L"] = {str(r["no"]): r["L"] for r in rows}
G5["T"] = int(el[1]["h4"]) # sole plate thickness = h4 (P1: 31)
G5["h3"] = {str(n): int(r["h3"]) for n, r in el.items()}
G5["bearing_size"] = {str(n): [int(r["A"]), int(r["B"])] for n, r in el.items()}
(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")
print("profile", profile)
print("coping", {k: c[k] for k in ("width_y", "plan_length", "top_length", "z_tip", "z_top_left", "z_top_right")})
print("lean", P["lean_concrete"]["size_x"], P["lean_concrete"]["size_y"], "footing", P["footing"]["size_x"], P["footing"]["size_y"],
P["footing"]["thickness"], "column", P["column"]["diameter"])
print("bearings", [(b["no"], b["centre"], b["seat_z"], b["el_x"], b["h1"], b["A"]) for b in bearings])
print("drawn pedestal centres", drawn)
print("sole plates", rows)
print("G5 T", G5["T"])
if __name__ == "__main__":
main()