- 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>
289 lines
16 KiB
Python
289 lines
16 KiB
Python
"""R4 (coping rebar C1~C15, bearing-seat reinforcement S1~S4) reading of the Ulsan P1 drawing ->
|
|
data/산외교울산P1 params.coping_rebar (explicit centreline polylines), expected.G4; adds G4 to the scope.
|
|
|
|
Sources (배근도(1) front view CX 10473.9, side view CX 24604.3; 배근도(2) sections A-A / B-B, 받침부 보강 상세 1·3,
|
|
F-F / G-G; 배근도(3) schedule and shape table). Front-view drawing y -> EL: z = y + 99715.8 (bottom bars
|
|
-31753.3 = 67962.5 = coping bottom + 100; layer 1 -28752.8 = right top - 100).
|
|
- C1/C3 layer 1 (z 70962.5), C2/C4 layer 2 (z 70862.5): drawn lines 9,258.6 / 9,297 (= A); end legs drawn
|
|
(C1 left 1,364 / right 1,369 along the end faces; C2 102); y 24@125 (25 per layer) - C1/C2 even, C3/C4 odd [추론]
|
|
- C15 left top (z 71092.5): 4,500 + legs 510 (left leg along the end face [추론], right leg at x -100); y 12@250
|
|
- C5/C6 bottom: drawn polyline (650 / 4,018 / 2,457 / 4,016 / 650); y 12@250, C5 even (7) / C6 odd (6) [추론]
|
|
- stirrups C7 (x -4,600..-1,400 @200, 17) / C8 (-1,200 -1,000 -500 0 500 1,000 1,200) / C9 (1,400..4,600):
|
|
3 per position, legs y (-1,500,-250) (-1,000,1,000) (250,1,500) (A-A; shape table II note a = (1,250x2 +
|
|
2,000)/3 + 54); centreline height b = drawn height + 54 (schedule B range reproduced to 1 mm): top = layer-1
|
|
(x >= 0) or C15 level (x <= -500) + 27, bottom = drawn bottom bar line - 27
|
|
- side bars 13 levels z 70,562.5 - 200k (side view / A-A '530','13@200'), drawn ends per level; y +-977.5 and
|
|
+-272.5 = C13 (levels 1-5, 20) / C11 (levels 6-13, 32), a = drawn + 44 (schedule range reproduced);
|
|
180-deg hooks (b 165, c 140) in plan towards y = 0 inside the drawn ends [추론]
|
|
- perimeter hoops y +-1,477.5: C12 (XII, plan corners R400, levels 1-6) / C10 (III, levels 7-13); counts kept,
|
|
schedule ranges suggest 5 / 8 levels (C10 a max 9,570 = level 6) -> drawing inconsistency recorded
|
|
- C14 (H16, 3,000 + 300) at the drawn section marks (5 levels, 8+4+4+3+1); legs along X towards the column and
|
|
x offset 19 from the stirrup plane [추론]
|
|
- S1/S2 (front bearings 1,2) S3/S4 (rear 3,4): 11 bars per direction (50,100,'8@125',100,50) in the 1,300 pedestal,
|
|
50 below the pedestal top, legs 700 down; S1/S3 along Y on top, S2/S4 along X below [추론]
|
|
"""
|
|
import json
|
|
import shutil
|
|
import time
|
|
from pathlib import Path
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
APP = HERE.parent / "교각모델링앱"
|
|
PIER = APP / "data" / "산외교울산P1"
|
|
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"C{i}" for i in range(1, 16)] + ["S1", "S2", "S3", "S4"]
|
|
Z0 = 99715.8 # z = drawing y + Z0 (front / side views)
|
|
DIA = {"H32": 32, "H22": 22, "H16": 16, "H13": 13}
|
|
|
|
|
|
def z(y):
|
|
return round(y + Z0, 1)
|
|
|
|
|
|
def mm(v):
|
|
return round(float(v) * 1000) if "." in v else int(v)
|
|
|
|
|
|
def L(m):
|
|
"""Schedule length: the value in the L column (sheet x 111,000..112,500)."""
|
|
return round(float(next(v for x, v in ROWS[m]["_right"] if 111000 < x < 112500)) * 1000)
|
|
|
|
|
|
def rmin(dia):
|
|
d = DIA[dia]
|
|
return ((2 * d if d <= 16 else 3.5 * d) + d) / 2
|
|
|
|
|
|
# ---- drawn data (front view, x relative to CX) ----
|
|
LAYER1 = dict(z=z(-28752.8), pts=[(-4887.5, z(-30094.5)), (-4640.6, z(-28752.8)), (4618.0, z(-28752.8)), (4886.4, z(-30095.0))])
|
|
LAYER2 = dict(z=z(-28852.8), pts=[(-4677.4, z(-28952.8)), (-4659.0, z(-28852.8)), (4638.0, z(-28852.8)), (4658.0, z(-28952.8))])
|
|
BOTTOM = [(-4769.8, z(-29455.0)), (-4887.5, z(-30094.5)), (-1228.4, z(-31753.3)), (1228.4, z(-31753.3)),
|
|
(4886.4, z(-30095.0)), (4758.9, z(-29457.4))]
|
|
C15_Z = z(-28623.3)
|
|
C15_X = (-4616.7, -100.0)
|
|
LEVELS = [(z(y), a, b) for y, a, b in (
|
|
(-29153.3, -4714.3, 4698.1), (-29353.3, -4751.1, 4738.1), (-29553.3, -4787.9, 4778.1), (-29753.3, -4824.7, 4818.0),
|
|
(-29953.3, -4861.5, 4858.0), (-30153.3, -4757.8, 4757.8), (-30353.3, -4316.6, 4316.6), (-30553.3, -3875.4, 3875.5),
|
|
(-30753.3, -3434.3, 3434.3), (-30953.3, -2993.1, 2993.1), (-31153.3, -2551.9, 2551.9), (-31353.3, -2110.7, 2110.7),
|
|
(-31553.3, -1669.6, 1669.6))]
|
|
STIR_X = {"C7": [-4600 + 200 * i for i in range(17)], "C8": [-1200, -1000, -500, 0, 500, 1000, 1200],
|
|
"C9": [1400 + 200 * i for i in range(17)]}
|
|
STIR_Y = [(-1500, -250), (-1000, 1000), (250, 1500)]
|
|
C14_PTS = [(x, z(-28875.3)) for x in (-4600, -3600, -2600, -1600, 1600, 2600, 3600, 4600)] + \
|
|
[(x, z(-29353.3)) for x in (-4600, -2600, 1600, 3600)] + [(x, z(-29553.3)) for x in (-3600, -1600, 2600, 4600)] + \
|
|
[(x, z(-30553.3)) for x in (-2600, 1600, 3600)] + [(-1600, z(-31553.3))]
|
|
SEAT_OFFS = [-600] + [-500 + 125 * i for i in range(9)] + [600]
|
|
|
|
|
|
ZTL, ZTR, ZTIP = 71193.0, 71063.0, 69563.0
|
|
|
|
|
|
def x_face(z, x):
|
|
"""End face |x| of the coping at level z (batter 300 above the tip; vertical tip below)."""
|
|
if z < ZTIP:
|
|
return 5000.0
|
|
return 4700 + 300 * ((ZTL - z) / 1630 if x < 0 else (ZTR - z) / 1500)
|
|
|
|
|
|
def lim_x(y, z, x):
|
|
"""Largest |x| of a bar centreline at (y, z) inside the R500 plan corner with 100 clearance (R400 arc)."""
|
|
dy = abs(y) - 1100
|
|
if dy <= 0:
|
|
return 1e9
|
|
return x_face(z, x) - 500 + (400 ** 2 - min(dy, 400) ** 2) ** 0.5
|
|
|
|
|
|
def lim_y(x, z):
|
|
dx = abs(x) - (x_face(z, x) - 500)
|
|
if dx <= 0:
|
|
return 1e9
|
|
return 1100 + (400 ** 2 - min(dx, 400) ** 2) ** 0.5
|
|
|
|
|
|
def clip_end(pts, y):
|
|
"""Shift the first / last straight run ends inward to the corner limit; legs (first / last point) move along."""
|
|
pts = [list(p) for p in pts]
|
|
for i_end, i_leg in ((1, 0), (-2, -1)):
|
|
x, zz = pts[i_end]
|
|
lim = lim_x(y, zz, x)
|
|
if abs(x) > lim:
|
|
d = (abs(x) - lim) * (1 if x > 0 else -1)
|
|
pts[i_end][0] -= d
|
|
pts[i_leg][0] -= d
|
|
return [tuple(p) for p in pts]
|
|
|
|
|
|
def drawn_bottom(x):
|
|
"""Drawn bottom-bar line (front view) at x: flat 67,962.5 inside +-1,228.4, slope 1,658.8 / 3,659.1 outside."""
|
|
zf = z(-31753.3)
|
|
return zf if abs(x) <= 1228.4 else zf + (abs(x) - 1228.4) * 1658.8 / 3659.1
|
|
|
|
|
|
def stir_top_drawn(x):
|
|
return C15_Z if x <= -500 else LAYER1["z"]
|
|
|
|
|
|
def hook_bar(xl, xr, y, zz, r, tail):
|
|
"""Straight bar with 180-deg plan hooks towards y = 0; the hook extremes sit at xl / xr."""
|
|
s = -1 if y > 0 else 1
|
|
yy = y + s * 2 * r
|
|
return [(xl + r + tail, yy, zz), (xl, yy, zz), (xl, y, zz), (xr, y, zz), (xr, yy, zz), (xr - r - tail, yy, zz)]
|
|
|
|
|
|
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_R4_{stamp}.json")
|
|
R = {m: ROWS[m] for m in MARKS}
|
|
dia = {m: R[m]["dia"] for m in MARKS}
|
|
bars = []
|
|
|
|
def add(mark, occ, pts, design, src, r=None):
|
|
bars.append(dict(mark=mark, occ=occ, pts=[[round(c, 2) for c in p] for p in pts],
|
|
r=rmin(dia[mark]) if r is None else r, design_length=round(design, 1), src=src))
|
|
|
|
# top layers C1~C4
|
|
for lay, (ma, mb), keys in ((LAYER1, ("C1", "C3"), ("A", "B", "C", "J")), (LAYER2, ("C2", "C4"), ("A", "B", "C", "J"))):
|
|
for i in range(25):
|
|
y = -1500 + 125 * i
|
|
m = ma if i % 2 == 0 else mb
|
|
add(m, f"y{y:+.0f}", [(x, y, zz) for x, zz in clip_end(lay["pts"], y)], sum(mm(R[m][k]) for k in keys),
|
|
f"정면 {'1' if lay is LAYER1 else '2'}단 작도선; 측면 '24@125'; 교번 배치 [추론]")
|
|
# C15 left top
|
|
dx, dz = -300 / 1657.4, -1630 / 1657.4
|
|
leg = mm(R["C15"]["B"])
|
|
for i in range(13):
|
|
y = -1500 + 250 * i
|
|
x0, x1 = C15_X
|
|
x0 = max(x0, -lim_x(y, C15_Z, x0))
|
|
pts = [(x0 + dx * leg, y, C15_Z + dz * leg), (x0, y, C15_Z), (x1, y, C15_Z), (x1, y, C15_Z - leg)]
|
|
add("C15", f"y{y:+.0f}", pts, mm(R["C15"]["A"]) + 2 * leg, "정면 좌측 상면 작도선 4,516.7; 단면 A-A '12@250'; 좌측 다리 단부면 평행 [추론]")
|
|
# bottom C5/C6
|
|
for i in range(13):
|
|
y = -1500 + 250 * i
|
|
m = "C5" if i % 2 == 0 else "C6"
|
|
pts = [list(p) for p in BOTTOM]
|
|
for i_end, i_leg, i_in in ((1, 0, 2), (4, 5, 3)):
|
|
(xe, ze), (xi, zi) = pts[i_end], pts[i_in]
|
|
lim = lim_x(y, ze, xe)
|
|
if abs(xe) > lim:
|
|
t = (lim - abs(xi)) / (abs(xe) - abs(xi))
|
|
nx, nz = xi + t * (xe - xi), zi + t * (ze - zi)
|
|
pts[i_leg] = [pts[i_leg][0] + nx - xe, pts[i_leg][1] + nz - ze]
|
|
pts[i_end] = [nx, nz]
|
|
add(m, f"y{y:+.0f}", [(x, y, zz) for x, zz in pts], sum(mm(R[m][k]) for k in "ABCDEJ"),
|
|
"정면 하면 작도선 (650/4,018/2,457/4,016/650); 단면 '12@250'; 교번 [추론]")
|
|
# stirrups C7~C9
|
|
stir_exp, clipped = {}, set()
|
|
for m, xs in STIR_X.items():
|
|
for x in xs:
|
|
zt = stir_top_drawn(x) + 27
|
|
zb = drawn_bottom(x) - 27
|
|
stir_exp[f"{x:.0f}"] = round(zt - zb, 1)
|
|
ly = lim_y(x, zt)
|
|
for k, (y0, y1) in enumerate(STIR_Y):
|
|
y0, y1 = max(y0, -ly), min(y1, ly)
|
|
if (y0, y1) != STIR_Y[k]:
|
|
clipped.add(x)
|
|
ym = (y0 + y1) / 2
|
|
pts = [(x, ym, zt), (x, y1, zt), (x, y1, zb), (x, y0, zb), (x, y0, zt), (x, ym, zt)]
|
|
design = 2 * (y1 - y0 + 54) + 2 * (zt - zb) + 2 * mm(R[m]["C"])
|
|
add(m, f"x{x:+.0f}_{k}", pts, design, "정면 스터럽 작도 위치·높이 (+54); 단면 A-A 3조; 형상표 II 주기")
|
|
# side bars C13 / C11 and hoops C12 / C10
|
|
hr = (mm(R["C11"]["B"]) - DIA["H22"]) / 2 # hook centreline radius 71.5
|
|
tail = mm(R["C11"]["C"])
|
|
for k, (zz, xl, xr) in enumerate(LEVELS):
|
|
m = "C13" if k < 5 else "C11"
|
|
a = xr - xl + 44
|
|
hooks = 2 * mm(R[m]["B"]) + 2 * mm(R[m]["C"])
|
|
for y in (-977.5, -272.5, 272.5, 977.5):
|
|
add(m, f"L{k + 1:02d}y{y:+.0f}", hook_bar(xl, xr, y, zz, hr, tail),
|
|
a + hooks + (mm(R[m]["J"]) if "J" in R[m] else 0),
|
|
f"측면 단 {k + 1} (z {zz}); 정면 작도 끝 {xl}/{xr}; a = 작도 + 44; 평면 갈고리 [추론]", r=hr - 0.5)
|
|
hm = "C12" if k < 6 else "C10"
|
|
yh = 1477.5
|
|
pts = [(0, -yh, zz), (xr, -yh, zz), (xr, yh, zz), (xl, yh, zz), (xl, -yh, zz), (0, -yh, zz)]
|
|
if hm == "C12":
|
|
design = 2 * (a - 790) + 2 * mm(R[hm]["B"]) + 4 * mm(R[hm]["C"]) + 4 * mm(R[hm]["J"])
|
|
add(hm, f"L{k + 1:02d}", pts, design, f"둘레 띠 단 {k + 1}; 모서리 R400; 길이 2a+2b+4c+4j", r=400)
|
|
else:
|
|
design = 2 * (xr - xl + 54) + 2 * mm(R[hm]["B"]) + 4 * mm(R[hm]["J"])
|
|
add(hm, f"L{k + 1:02d}", pts, design, f"둘레 띠 단 {k + 1}; 길이 2a+2b+4j (a = 작도 + 54)")
|
|
# C14 cross bars
|
|
c14 = []
|
|
for x, zz in C14_PTS:
|
|
s = 1 if x > 0 else -1
|
|
xx = x - s * 19
|
|
lg = mm(R["C14"]["B"])
|
|
yl = min(1500, lim_y(xx, zz), lim_y(xx - s * lg, zz) + 0)
|
|
if yl < 1500:
|
|
clipped.add(x)
|
|
add("C14", f"x{x:+.0f}z{zz:.0f}", [(xx - s * lg, -yl, zz), (xx, -yl, zz), (xx, yl, zz), (xx - s * lg, yl, zz)],
|
|
mm(R["C14"]["A"]) + 2 * lg, "정면 단면 표시 위치; 단면 A-A 3,000; 다리 방향·x 19 이격 [추론]")
|
|
c14.append([xx, zz])
|
|
# S1~S4
|
|
seats = {b["no"]: b for b in P["bearing_seats"]["bearings"]}
|
|
lg = mm(R["S1"]["B"])
|
|
s_exp = {}
|
|
for (my, mx), nos in ((("S1", "S2"), (1, 2)), (("S3", "S4"), (3, 4))):
|
|
for no in nos:
|
|
b = seats[no]
|
|
cx, cy = b["centre"]
|
|
top = b["seat_z"] + b["h1"]
|
|
z1, z2 = top - 50, top - 50 - DIA["H13"]
|
|
for o in SEAT_OFFS:
|
|
x = cx + o
|
|
add(my, f"B{no}x{o:+.0f}", [(x, cy - 600, z1 - lg), (x, cy - 600, z1), (x, cy + 600, z1), (x, cy + 600, z1 - lg)],
|
|
mm(R[my]["A"]) + 2 * lg, f"받침 {no} 보강 상세; '50','100','8@125','100','50'")
|
|
y = cy + o
|
|
add(mx, f"B{no}y{o:+.0f}", [(cx - 600, y, z2 - lg), (cx - 600, y, z2), (cx + 600, y, z2), (cx + 600, y, z2 - lg)],
|
|
mm(R[mx]["A"]) + 2 * lg, f"받침 {no} 보강 상세; S1/S3 아래 단 [추론]")
|
|
s_exp[str(no)] = dict(centre=[cx, cy], z_top=z1, marks=[my, mx])
|
|
P["coping_rebar"] = {"form": "polylines", "host": "CP", "group": "코핑", "bars": bars,
|
|
"src": "배근도(1) 정면·측면·평면, 배근도(2) 단면 A-A/B-B·받침부 보강 상세, 배근도(3) 재료표·형상표"}
|
|
P["scope"] = [g for g in ("G1", "G2", "G3", "G4", "G5") if g in set(P["scope"]) | {"G4"}]
|
|
P["builders"]["G4"] = "rebar_polylines.py"
|
|
E["checkers"]["G4"] = "g4_polylines.py"
|
|
E["negative"]["G4"] = [{"mark": "C7", "d": [5, 0, 0]}, {"mark": "C11", "d": [0, 0, 5]}]
|
|
E["G4"] = {
|
|
"schedule": {m: [L(m), int(R[m]["n"])] for m in MARKS},
|
|
"dia": {m: DIA[dia[m]] for m in MARKS},
|
|
"fixed_length": ["C1", "C2", "C3", "C4", "C5", "C6", "C14", "C15", "S1", "S2", "S3", "S4"],
|
|
"ranges": {"C7": ["B", 1656, 3106], "C8": ["B", 3054, 3184], "C9": ["B", 1526, 2977],
|
|
"C11": ["A", 3383, 9560], "C13": ["A", 9456, 9764], "C12": ["A", 8666, 8974], "C10": ["A", 3393, 9570]},
|
|
"layers": {"C1": {"z": LAYER1["z"], "y": "top125", "parity": 0}, "C3": {"z": LAYER1["z"], "y": "top125", "parity": 1},
|
|
"C2": {"z": LAYER2["z"], "y": "top125", "parity": 0}, "C4": {"z": LAYER2["z"], "y": "top125", "parity": 1},
|
|
"C15": {"z": C15_Z, "y": "y250", "parity": None},
|
|
"C5": {"z_min": z(-31753.3), "y": "y250", "parity": 0}, "C6": {"z_min": z(-31753.3), "y": "y250", "parity": 1}},
|
|
"x_extent": {"C1": [-4887.5, 4886.4], "C3": [-4887.5, 4886.4], "C2": [-4677.4, 4658.0], "C4": [-4677.4, 4658.0],
|
|
"C5": [-4887.5, 4886.4], "C6": [-4887.5, 4886.4]},
|
|
"rows": {"top125": {"start": -1500, "pitch": 125, "n": 25}, "y250": {"start": -1500, "pitch": 250, "n": 13}},
|
|
"stirrups": {"x": STIR_X, "y_pairs": STIR_Y, "height": stir_exp,
|
|
"rule": "높이 = 작도 높이 + 54 (상단 1단/C15 선 +27, 하단 하면선 -27)"},
|
|
"levels": [[zz, xl, xr] for zz, xl, xr in LEVELS],
|
|
"side": {"y": [-977.5, -272.5, 272.5, 977.5], "marks": [["C13", 5], ["C11", 8]]},
|
|
"hoops": {"y": 1477.5, "marks": [["C12", 6], ["C10", 7]]},
|
|
"c14": {"pts": c14, "y": [-1500, 1500]},
|
|
"corner": {"y_abs": 1100, "x_clipped": sorted(clipped),
|
|
"rule": "평면 모서리 R500: |y| > 1,100 철근은 R400 원호(중심선 이격 100)까지 단축 - 평면도 작도 [추론]"},
|
|
"seats": {"offsets": SEAT_OFFS, "half": 600, "leg": lg, "bearings": s_exp},
|
|
"src": "재료표 L·수량·범위; 정면·측면 작도 좌표 (z = y + 99,715.8); 단면 A-A/B-B; 받침부 보강 상세",
|
|
}
|
|
(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")
|
|
from collections import Counter
|
|
cnt = Counter(b["mark"] for b in bars)
|
|
print("bars", len(bars), {m: (cnt[m], E["G4"]["schedule"][m][1]) for m in MARKS if cnt[m] != E["G4"]["schedule"][m][1]} or "counts OK")
|
|
import statistics
|
|
for m in MARKS:
|
|
dl = [b["design_length"] for b in bars if b["mark"] == m]
|
|
print(m, "L", E["G4"]["schedule"][m][0], "mean", round(statistics.mean(dl), 1), "min", min(dl), "max", max(dl))
|
|
print("scope", P["scope"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|