- 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>
196 lines
12 KiB
Python
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))
|