- 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>
131 lines
9.8 KiB
Python
131 lines
9.8 KiB
Python
"""R3 (footing rebar) reading of the Ulsan P2 drawing (배근도(4)) -> data/산외교울산P2 params.footing_rebar, expected.G3; adds G3 to the
|
|
pier scope (builder footing_rebar_mats.py, checker g3_mats.py). Same form as P1 (extract_r3.py there), P2 values (2026-10-15).
|
|
|
|
Footing 7,800 (X, front view) x 7,500 (Y, side view) x 2,500, bars in the FOUNDATION's own axes (the foundation is rotated 37 deg, G1);
|
|
the engine turns them with the footing. Front view footing x 5650..14078 (CX 9937), side view x 19802..27931 (CX 23940), y_bottom -39919.
|
|
- position chains (front, X): top '100','300','28@250=7.000','300','100' -> -3800 | -3500..3500 @250 | 3800 (31 = B1);
|
|
bottom '100','2@150','56@125=7.000','2@150','100' -> -3800,-3650 | -3500..3500 @125 | 3650,3800 (61 = B2 31 on top positions + B3 30)
|
|
- side (Y): top '100','150','28@250=7.000','150','100' -> -3650 | -3500..3500 @250 | 3650 (31 = B4); bottom '100','150','56@125=7.000',
|
|
'150','100' -> -3650 | -3500..3500 @125 | 3650 (59 = B5 31 on top positions + B6 28)
|
|
- layer heights: drawn lines z 150 / 1,275 / 2,400 (same as P1) -> Y-direction bottom 127.5, X bottom 172.5, top Y 2422.5 / X 2377.5 [derived,
|
|
P1 rule]; U bars B2/B5 legs up 2,250, lap J 1,160 (length only); L = A + 2B + J reproduces 12.960 / 13.260
|
|
- B8 perimeter hoops z 375..2,175 @300 (7; chain '225','6@300=1.800','225' + 150), centreline inset 77.5 (P1 rule) -> half 3822.5 x 3672.5;
|
|
schedule L 33.330 vs 2A + 2B + 4J = 32,190 (P1 formula reproduces its own L) -> drawing / table conflict, recorded as planned FAIL
|
|
- B9 (X, 7) / B10 (Y, 8) mid layer z 1,275, legs 100 down: positions are not dimensioned for these marks -> symmetric 1,000 pitch [추론]
|
|
- B7 verticals (XI: A 2,285, legs 78 / 52, L 2,415): drawn in elevation at the 22 front x positions and 20 side y positions (CS-RBAR-EREC
|
|
lines 2,250 long, z 150..2,400): chain '100','300','500','8@250','4@500','8@250','500','300','100' (front) / '100','400','500','7@250',
|
|
'4@500','7@250','500','400','100' (side) without the centre point 0 -> 22 x 20 = 440 grid points; schedule 320 = 440 minus the points with
|
|
|x| <= 2,000 and |y| <= 1,750 (120) [추론: the only rectangle that gives 320 besides its transpose]; z0 = 150 + (2,250 - 2,285) / 2 = 132.5
|
|
"""
|
|
import json
|
|
import shutil
|
|
import time
|
|
from pathlib import Path
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
APP = HERE.parent / "교각모델링앱"
|
|
PIER = APP / "data" / "산외교울산P2"
|
|
S = json.loads((HERE / "판독" / "schedule.json").read_text(encoding="utf-8"))
|
|
ROWS = {r["mark"]: r for t in S.values() for r in t["rows"]}
|
|
MARKS = [f"B{i}" for i in range(1, 11)]
|
|
|
|
GRID_X = [-3500, -3000, -2750, -2500, -2250, -2000, -1750, -1500, -1250, -1000, -500, 500, 1000, 1250, 1500, 1750, 2000, 2250, 2500, 2750, 3000, 3500]
|
|
GRID_Y = [-3250, -2750, -2500, -2250, -2000, -1750, -1500, -1250, -1000, -500, 500, 1000, 1250, 1500, 1750, 2000, 2250, 2500, 2750, 3250]
|
|
assert len(GRID_X) == 22 and len(GRID_Y) == 20
|
|
ROW_DEF = {
|
|
"bottom_front": {"first": [-3800, -3650], "start": -3500, "pitch": 125, "n": 57, "last": [3650, 3800]},
|
|
"top_front": {"first": [-3800], "start": -3500, "pitch": 250, "n": 29, "last": [3800]},
|
|
"bottom_side": {"first": [-3650], "start": -3500, "pitch": 125, "n": 57, "last": [3650]},
|
|
"top_side": {"first": [-3650], "start": -3500, "pitch": 250, "n": 29, "last": [3650]},
|
|
"grid_x": {"first": GRID_X, "start": 0, "pitch": 1, "n": 0},
|
|
"grid_y": {"first": GRID_Y, "start": 0, "pitch": 1, "n": 0},
|
|
}
|
|
MID_X = [-3500 + 1000 * i for i in range(8)] # B10 (8 bars along Y), positions along X [추론]
|
|
MID_Y = [-3000 + 1000 * i for i in range(7)] # B9 (7 bars along X), positions along Y [추론]
|
|
EXCLUDE = {"x": 2000, "y": 1750}
|
|
|
|
|
|
def row(spec):
|
|
return sorted(spec.get("first", []) + [spec["start"] + spec["pitch"] * i for i in range(spec["n"])] + spec.get("last", []))
|
|
|
|
|
|
def mm(v):
|
|
return round(float(v) * 1000) if "." in v else int(v)
|
|
|
|
|
|
def L(m):
|
|
return round(float(ROWS[m]["_right"][0][1]) * 1000) # first value right of the J column = L
|
|
|
|
|
|
def main():
|
|
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_R3_{stamp}.json")
|
|
bf, tf, bs_, ts = (row(ROW_DEF[k]) for k in ("bottom_front", "top_front", "bottom_side", "top_side"))
|
|
assert len(bf) == 61 and len(tf) == 31 and len(bs_) == 59 and len(ts) == 31
|
|
on_f = [v for v in bf if v in tf]
|
|
off_f = [v for v in bf if v not in tf]
|
|
on_s = [v for v in bs_ if v in ts]
|
|
off_s = [v for v in bs_ if v not in ts]
|
|
leg_u, lap, hook = mm(ROWS["B2"]["B"]), mm(ROWS["B2"]["J"]), mm(ROWS["B9"]["B"])
|
|
a_y, a_x = mm(ROWS["B1"]["A"]), mm(ROWS["B4"]["A"]) # Y-direction bars 7,300, X-direction bars 7,600
|
|
src_f, src_s = "배근도(4) 정면도 기초", "배근도(4) 측면도 기초"
|
|
groups = [
|
|
dict(mark="B1", axis="Y", z=2422.5, pos=tf, a=a_y, leg=0, src=f"{src_f} 상면 사슬 '28@250' (끝 3,800)"),
|
|
dict(mark="B2", axis="Y", z=127.5, pos=on_f, a=a_y, leg=leg_u, lap=lap, src=f"{src_f} 하면 U형 (상면 위치), 이음 J {lap} 길이만"),
|
|
dict(mark="B3", axis="Y", z=127.5, pos=off_f, a=a_y, leg=0, src=f"{src_f} 하면 '2@150','56@125' 중 B2 외"),
|
|
dict(mark="B4", axis="X", z=2377.5, pos=ts, a=a_x, leg=0, src=f"{src_s} 상면 사슬 '28@250' (끝 3,650)"),
|
|
dict(mark="B5", axis="X", z=172.5, pos=on_s, a=a_x, leg=leg_u, lap=lap, src=f"{src_s} 하면 U형 (측면 상면 위치) [추론]"),
|
|
dict(mark="B6", axis="X", z=172.5, pos=off_s, a=a_x, leg=0, src=f"{src_s} 하면 B5 외"),
|
|
dict(mark="B9", axis="X", z=1275, pos=MID_Y, a=a_x, leg=-hook, src=f"{src_s} 중앙: 위치 치수 없음, 1,000 간격 대칭 [추론]"),
|
|
dict(mark="B10", axis="Y", z=1275, pos=MID_X, a=a_y, leg=-hook, src=f"{src_f} 중앙: 위치 치수 없음, 1,000 간격 대칭 [추론]; B9와 같은 높이"),
|
|
]
|
|
hoop = dict(mark="B8", half_x=3822.5, half_y=3672.5, levels=[375 + 300 * i for i in range(7)],
|
|
design_length=2 * mm(ROWS["B8"]["A"]) + 2 * mm(ROWS["B8"]["B"]) + 4 * mm(ROWS["B8"]["J"]),
|
|
src="정면·측면 둘레 띠, z 375~2,175 @300; 중심선 안쪽 77.5 (울산P1 규칙); 길이 2A+2B+4J")
|
|
pts = [[x, y] for x in GRID_X for y in GRID_Y if not (abs(x) <= EXCLUDE["x"] and abs(y) <= EXCLUDE["y"])]
|
|
b_len, c_len = mm(ROWS["B7"]["B"]), mm(ROWS["B7"]["C"])
|
|
vert = dict(mark="B7", points=pts, z0=150 + (2250 - mm(ROWS["B7"]["A"])) / 2, a=mm(ROWS["B7"]["A"]), leg_bottom=b_len, leg_top=c_len,
|
|
leg_axis="Y", src="입면 CS-RBAR-EREC 22 x 20 위치(중앙점 0 제외) 중 |x|<=2,000 & |y|<=1,750 제외 = 320 [추론]; 작도 z 150~2,400, A 중앙 배치; 다리 78 / 52 +Y [추론]")
|
|
P["footing_rebar"] = {"form": "mats", "groups": groups, "hoops": hoop, "verticals": vert,
|
|
"src": "배근도(4) 정면·측면 기초 배근 + 재료표 B1~B10 (기초 자체 축, 기초 회전은 footing.rot_deg)"}
|
|
P["scope"] = [g for g in ("G1", "G2", "G3", "G4", "G5") if g in set(P["scope"]) | {"G3"}]
|
|
P["builders"]["G3"] = "footing_rebar_mats.py"
|
|
P["rebar_marks"] = {m: {"dia": r["dia"], "shape": r["shape"]} for m, r in ROWS.items()}
|
|
assert "25" in P["display"]["rebar_palette"], "H25 colour missing"
|
|
E["checkers"]["G3"] = "g3_mats.py"
|
|
E["negative"]["G3"] = [{"mark": "B3", "d": [5, 0, 0]}, {"mark": "B7", "d": [0, 0, 5]}]
|
|
E["G3"] = {
|
|
"schedule": {m: [L(m), int(ROWS[m]["n"])] for m in MARKS},
|
|
"dia": {m: int(ROWS[m]["dia"][1:]) for m in MARKS},
|
|
"rows": ROW_DEF,
|
|
"layers": {
|
|
"B1": {"axis": "Y", "z": 2422.5, "row": "top_front"},
|
|
"B2": {"axis": "Y", "z": 127.5, "row": "bottom_front", "subset": "on", "of": "top_front"},
|
|
"B3": {"axis": "Y", "z": 127.5, "row": "bottom_front", "subset": "off", "of": "top_front"},
|
|
"B4": {"axis": "X", "z": 2377.5, "row": "top_side"},
|
|
"B5": {"axis": "X", "z": 172.5, "row": "bottom_side", "subset": "on", "of": "top_side"},
|
|
"B6": {"axis": "X", "z": 172.5, "row": "bottom_side", "subset": "off", "of": "top_side"},
|
|
"B9": {"axis": "X", "z": 1275, "pos": MID_Y, "leg": -hook},
|
|
"B10": {"axis": "Y", "z": 1275, "pos": MID_X, "leg": -hook},
|
|
},
|
|
"hoops": {"mark": "B8", "half_x": 3822.5, "half_y": 3672.5, "chain": {"start": [375], "groups": [[6, 300]]}},
|
|
"verticals": {"mark": "B7", "grid_x": "grid_x", "grid_y": "grid_y", "exclude": EXCLUDE, "z0": vert["z0"], "a": vert["a"],
|
|
"legs": {"bottom": b_len, "top": c_len}},
|
|
"src": "재료표 L·수량; 정면·측면 위치 사슬(상면·하면·중앙); 층 높이는 작도값 [derived]",
|
|
}
|
|
E["planned_fail"] = [pf for pf in E.get("planned_fail", []) if pf.get("gate") != "G3"] + [
|
|
{"gate": "G3", "item": "B8 original length vs L (worst)",
|
|
"reason": f"재료표 B8 L 33.330 vs 2A+2B+4J = {hoop['design_length']} (울산P1 B8은 같은 식이 L과 일치) — 도면·재료표 불일치 의심, 설계자 확인 대상(OPEN #32)",
|
|
"approved": "미승인 — Agent 제안, 사용자 확인 대기"}]
|
|
(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("counts", {g["mark"]: len(g["pos"]) for g in groups}, "B7", len(pts), "B8 len", hoop["design_length"], "B7 z0", vert["z0"])
|
|
print("schedule n", {m: int(ROWS[m]["n"]) for m in MARKS})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|