Files
Pier_v1/Code/옥성P1판독/extract_g4.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

196 lines
12 KiB
Python

"""Okseong P1 coping + bearing-seat rebar (G4: C1~C20, S1~S8) -> centreline polylines for the generic builder rebar_polylines.py.
Inputs: the drawing (DXF front 구조도(1): stirrup lines, side-loop lines, C18 points; side view: loop / tie y), the schedule and
the shape sheet (판독/g4_shapes.json), params.json (pedestals), decisions H1-H6 (2026-10-14, all recommended).
Output: 판독/g4_bars.json {"bars": [...], "summary": {...}} used by add_g4.py.
Model axes: X along the coping, Y across, Z = EL x 1000, pier centre at the origin. Coping rebar is built in the y = 0 section frame
(x, y, z) and sheared like the coping (plan skew 18 deg): x_model = x - tan(18) * y. Seat bars are built directly in the model frame.
"""
import json
import math
from collections import defaultdict
from pathlib import Path
from dxftools import entities
HERE = Path(__file__).resolve().parent
APP = HERE.parents[1] / "Code" / "교각모델링앱"
P = json.loads((APP / "data" / "옥성교P1" / "params.json").read_text(encoding="utf-8"))
SCHED = json.loads((HERE / "판독" / "g4_shapes.json").read_text(encoding="utf-8"))["table"]
T = math.tan(math.radians(P["coping"]["skew_deg"]))
ZO, F, S = -309179.8, 573551.6, 592540.9
bars = []
def rb(dia): # min bend radius of the centreline (as the other builders)
return ((2 * dia if dia <= 16 else 3.5 * dia) + dia) / 2
def dia_of(mark):
return int(SCHED[mark][1][1:])
def add(mark, occ, pts, src, r=None, shear=True):
if shear:
pts = [(x - T * y, y, z) for x, y, z in pts]
length = sum(math.dist(a, b) for a, b in zip(pts, pts[1:]))
bars.append({"mark": mark, "occ": occ, "pts": [[round(v, 2) for v in p] for p in pts],
"r": rb(dia_of(mark)) if r is None else r, "design_length": round(length, 1), "src": src})
def front(layer="CS-RBAR-MAJR"):
return entities("D1", F - 7000, 327300, F + 7000, 330500, layer)
def side(layer="CS-RBAR-MAJR"):
return entities("D1", S - 2500, 327300, S + 2500, 330500, layer)
# ---- drawn data ---------------------------------------------------------------------------------------------------------
# front vertical lines of the stirrups: (x, bottom z, top z); column bars (bottom < 18290) and the end hooks are left out
V = defaultdict(list)
for e in front():
if e.dxftype() == "LINE" and abs(e.dxf.start.x - e.dxf.end.x) < 0.5:
x = round(e.dxf.start.x - F, 1)
lo, hi = sorted((round(e.dxf.start.y + ZO, 1), round(e.dxf.end.y + ZO, 1)))
if lo >= 18290 and hi >= 21000 and abs(x) <= 5800 and x % 25 == 0:
V[x].append((lo, hi))
STIR = {x: v[0] for x, v in sorted(V.items())}
assert len(STIR) == 106, len(STIR)
DOTS = defaultdict(list) # front points (bars along Y): z -> [x]
for e in front():
if e.dxftype() == "INSERT":
DOTS[round(e.dxf.insert.y + ZO, 1)].append(round(e.dxf.insert.x - F, 1))
SIDE_DOTS = defaultdict(list) # side points (bars along X): z -> [y]
for e in side():
if e.dxftype() == "INSERT":
SIDE_DOTS[round(e.dxf.insert.y + ZO, 1)].append(round(e.dxf.insert.x - S, 1))
FRONT_H = {} # front horizontal lines: z -> max |x|
for e in front():
if e.dxftype() == "LINE" and abs(e.dxf.start.y - e.dxf.end.y) < 0.5:
z = round(e.dxf.start.y + ZO, 1)
FRONT_H[z] = max(FRONT_H.get(z, 0), round(max(abs(e.dxf.start.x - F), abs(e.dxf.end.x - F)), 1))
Y25 = [-1525.0] + [-1375.0 + 125 * k for k in range(23)] + [1525.0] # '125 | 150 | 22@125=2.750 | 150 | 125'
FACE_Z0, FACE_Y0, FACE_Z1, FACE_Y1 = 18272.5, 1146.7, 19691.4, 1525.0 # side face: vertical above EL 19.6914, taper below
K_FACE = (FACE_Y1 - FACE_Y0) / (FACE_Z1 - FACE_Z0)
def face_y(z):
return FACE_Y1 if z >= FACE_Z1 else FACE_Y0 + K_FACE * (z - FACE_Z0)
# ---- C1~C6 top bars: 3 layers, marks alternate from the edge, straight x +-5,875, legs down at both ends ---------------------
for z, legs, (ma, mb) in ((21007.8, 1247.0, ("C1", "C4")), (20927.8, 100.0, ("C2", "C5")), (20827.8, 100.0, ("C3", "C6"))):
for i, y in enumerate(Y25):
add((ma, mb)[i % 2], f"y{y:+.0f}", [(-5875, y, z - legs), (-5875, y, z), (5875, y, z), (5875, y, z - legs)],
f"정면 선 EL {z / 1000:.4f}, x ±5,875, 다리 {legs:.0f}; 단면 사슬 25줄, 교번 [추론 F3형], 겹이음 j 미모델")
# ---- C19: centre U on top ------------------------------------------------------------------------------------------------
for y in Y25:
add("C19", f"y{y:+.0f}", [(-1171.4, y, 21122.2 - 370), (-1171.4, y, 21122.2), (1054.8, y, 21122.2), (1054.8, y, 21122.2 - 370)],
"정면 선 EL 21.1222, x -1,171.4 ~ 1,054.8, 다리 370")
# ---- C20: three L pieces x 25 rows (H3 [추론]: y rows = the top mats) --------------------------------------------------------
for z, xa, xb, leg_at in ((21055.0, -5875.0, -3402.6, "a"), (21088.6, -3647.6, -926.4, "a"), (21068.4, 809.8, 3531.0, "b")):
for y in Y25:
p = [(xa, y, z - 370), (xa, y, z), (xb, y, z)] if leg_at == "a" else [(xa, y, z), (xb, y, z), (xb, y, z - 370)]
add("C20", f"x{xa:+.0f}y{y:+.0f}", p, f"정면 선 EL {z / 1000:.4f}, x {xa} ~ {xb}, 다리 370 (H3 [추론] 상면 y 25줄)")
# ---- C7: soffit bar ('a' 606 up, slope b, flat c). y rows @250 (11) + 2 on the side face (H3 [추론]) ----------------------------
SLOPE = (19760.8 - 18295.0) / (5875.0 - 1331.1)
for y in [-1250.0 + 250 * k for k in range(11)] + [-1525.0, 1525.0]:
if abs(y) <= 1250.0:
dz = 0.0 if abs(y) <= 1000 else 18660.2 - 18295.0
else:
dz = 19691.4 - 18295.0
top = 20367.2 + dz
cap = 21127.7 - 97.5 - 16 # right (lowest) coping top - stirrup cover - bar radius
zr = 19760.8 + dz
if zr + 606.4 > cap: # the bar would leave the coping: end where the slope meets the cap
xe = 1331.1 + (cap - (18295.0 + dz)) / SLOPE
pts = [(-xe, y, cap), (-1331.1, y, 18295.0 + dz), (1331.1, y, 18295.0 + dz), (xe, y, cap)]
else:
pts = [(-5875, y, top), (-5875, y, zr), (-1331.1, y, 18295.0 + dz), (1331.1, y, 18295.0 + dz), (5875, y, zr), (5875, y, top)]
add("C7", f"y{y:+.0f}", pts, "정면 하면 경사선(±1,331.1 평평, ±5,875 EL 19.7608, 위 끝 연직 606), 측면 점 EL 18.295 / 18.6602 / 19.6914")
# ---- stirrups C8~C13: per x, top = V top + 22.5, bottom = V bottom - 22.5; X offset +20 for the pentagons (coincident planes) ----
for x, (lo, hi) in STIR.items():
zt, zb = hi + 22.5, lo - 22.5
rect, pent = (("C8", "C9") if x < -1325 else (("C10", "C11") if x <= 1275 else ("C12", "C13")))
add(rect, f"x{x:+.0f}", [(x, 0, zt), (x, 1000, zt), (x, 1000, zb), (x, -1000, zb), (x, -1000, zt), (x, 0, zt)],
f"정면 연직선 x {x} (EL {lo:.1f} ~ {hi:.1f}), 측면 선 y ±1,000, 위·아래 22.5 안쪽")
for s in (1, -1):
near = zb > FACE_Z1 - 20 # a chamfer shorter than 20 mm would break the pipe: keep the side straight
yb = FACE_Y1 if near else face_y(zb)
p = [(x + 20, s * 750, zt), (x + 20, s * 1525, zt)]
if not near:
p += [(x + 20, s * 1525, FACE_Z1)]
p += [(x + 20, s * yb, zb), (x + 20, s * 500, zb), (x + 20, s * 500, zt), (x + 20, s * 750, zt)]
add(pent, f"x{x:+.0f}{'R' if s > 0 else 'L'}", p, "측면 선 5각형: 안쪽 y ±500, 바깥 ±1,525 → EL 19.6914부터 측면 테이퍼, 위 d 1,025, x +20 (겹침 피함) [추론]")
# ---- side loops C16 (upper 6) / C14 (lower 6) and ties C17 / C15 (4 rows) -------------------------------------------------
UP = [20922.1, 20796.4, 20546.4, 20296.4, 20046.4, 19796.4]
LOW = [19546.4, 19296.4, 19046.4, 18796.4, 18546.4, 18420.7]
for levels, loop, tie in ((UP, "C16", "C17"), (LOW, "C14", "C15")):
for z in levels:
xe = FRONT_H[z]
ys = sorted(set(abs(v) for v in SIDE_DOTS[z] if abs(v) < 1600))
yo = max(ys)
add(loop, f"z{z:.0f}", [(0, yo, z), (xe, yo, z), (xe, -yo, z), (-xe, -yo, z), (-xe, yo, z), (0, yo, z)],
f"정면 선 EL {z / 1000:.4f} x ±{xe}, 측면 점 y ±{yo} (H6: 도면 점 위치)")
for y in (-977.5, -522.5, 522.5, 977.5):
assert any(abs(abs(y) - v) < 1 for v in ys), (z, y)
add(tie, f"z{z:.0f}y{y:+.1f}", [(-xe, y, z), (xe, y, z)], f"정면 선 EL {z / 1000:.4f}, 측면 점 y {y} (135° 갈고리 미모델, H4)")
# ---- C18: Y-direction U at every drawn front point (8 levels, 117) ------------------------------------------------------------
C18_Z = [21068.4, 21047.8, 20905.3, 20805.3, 20796.4, 20546.4, 19546.4, 18546.4]
for z in C18_Z:
h = face_y(z)
hl = min(FACE_Y1, h)
for x in sorted(DOTS[z]):
zl = z + 370 if z < FACE_Z1 else z - 370 # lower rows: legs UP along the face (down would leave the soffit) [추론]
yl = face_y(zl)
add("C18", f"x{x:+.0f}z{z:.0f}", [(x, -yl, zl), (x, -hl, z), (x, hl, z), (x, yl, zl)] if z < FACE_Z1 else
[(x, -hl, z - 370), (x, -hl, z), (x, hl, z), (x, hl, z - 370)],
f"정면 점 EL {z / 1000:.4f}, 측면 선 폭 ±{hl:.1f}, 다리 370 (낮은 단은 측면 테이퍼 따라 위로, H3 [추론])")
# ---- seat bars (H1, H2, H5 [추론]): pedestal k, local axes e_s (along the coping end face), e_x ---------------------------------
PED = P["bearing_seats"]["pedestals"]
for k, ped in enumerate(PED):
c = ped["corners"]
cx, cy = sum(p[0] for p in c) / 4, sum(p[1] for p in c) / 4
zp = (ped["top"]["z0"] + ped["top"]["z1"]) / 2
heavy = k == 2 # H1: pedestal 3 -> S1~S4
m_l, m_s, m_ll, m_ss = ("S1", "S2", "S3", "S4") if heavy else ("S5", "S6", "S7", "S8")
d_up = dia_of(m_l)
cs = math.cos(math.radians(18.0))
xc = lambda y, u=0.0: cx + u - T * (y - cy) # centre line of the pedestal along e_s, shifted by u in X
# upper mesh: long bars (11) along e_s, short bars (21) along X; U legs 700 down
for j, t in enumerate([-600.0] + [-500.0 + 125 * i for i in range(9)] + [600.0]):
z, u, hy = zp - 50, t / cs, 2629 * cs / 2
add(m_l, f"P{k + 1}-{j:02d}", [(xc(cy - hy, u), cy - hy, z - 700), (xc(cy - hy, u), cy - hy, z), (xc(cy + hy, u), cy + hy, z),
(xc(cy + hy, u), cy + hy, z - 700)], f"교좌면 상세 C-D, 대좌 {k + 1}, 긴 철근 e_s 방향 [추론 H2]", shear=False)
for j, o in enumerate([-1250.0] + [-1125.0 + 125 * i for i in range(19)] + [1250.0]):
z, y = zp - 50 - d_up, cy + o
add(m_s, f"P{k + 1}-{j:02d}", [(xc(y) - 631, y, z - 700), (xc(y) - 631, y, z), (xc(y) + 631, y, z), (xc(y) + 631, y, z - 700)],
f"교좌면 상세 C-D, 대좌 {k + 1}, 짧은 철근 X 방향 [추론 H2, H5]", shear=False)
# lower mesh: 2 layers (-373, -473); long bars 11 per layer along e_s, short bars 21 per layer along X
for layer, dz in enumerate((373.0, 473.0)):
z7 = zp - dz
for j, t in enumerate([-625.0 + 125 * i for i in range(11)]):
u, hy = t / cs, 2500 * cs / 2
add(m_ll, f"P{k + 1}-{layer}{j:02d}", [(xc(cy - hy, u), cy - hy, z7), (xc(cy + hy, u), cy + hy, z7)],
f"교좌면 상세 C-D, 대좌 {k + 1}, {layer + 1}층, e_s 방향 [추론 H2]", r=0, shear=False)
z8 = z7 + (dia_of(m_ll) + dia_of(m_ss)) / 2
for j, o in enumerate([-1250.0 + 125 * i for i in range(21)]):
y = cy + o
add(m_ss, f"P{k + 1}-{layer}{j:02d}", [(xc(y) - 625, y, z8), (xc(y) + 625, y, z8)],
f"교좌면 상세 C-D, 대좌 {k + 1}, {layer + 1}층, X 방향 [추론 H2, H5]", r=0, shear=False)
count = {}
for b in bars:
count[b["mark"]] = count.get(b["mark"], 0) + 1
want = {m: int(v[3]) for m, v in SCHED.items() if m[0] in "CS"}
out = {"bars": bars, "summary": {"bars": len(bars), "per_mark": count, "diff_vs_schedule": {m: count.get(m, 0) - n for m, n in want.items() if count.get(m, 0) != n}}}
(HERE / "판독" / "g4_bars.json").write_text(json.dumps(out, ensure_ascii=False), encoding="utf-8")
print(json.dumps(out["summary"], ensure_ascii=False))