Files
Pier_v1/Code/교각모델링/build_rebar_rhino.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

178 lines
8.2 KiB
Python

"""Stage 4: column rebar P1-P6 (centreline + pipe solid) from params.json `column_rebar`.
Run inside Rhino 8 after build_rhino.py:
exec(open(<this file>, encoding="utf-8").read(), {"__rhino_doc__": doc, "PARAMS_PATH": <params.json>})
Rules: 설계서 v0.1.04 §5 (HITL H1-H3 approved 2026-10-07). Units mm, Z = EL x 1000.
Each centreline carries `design_length` (schedule-style original length: straight + arc lengths
before hook fillets) and the actual curve length is measured separately (box rule: 원길이 vs 곡선길이).
"""
import json
import math
import Rhino
import Rhino.Geometry as rg
from Rhino.DocObjects import ObjectAttributes
from System.Drawing import Color
P = json.load(open(PARAMS_PATH, encoding="utf-8"))
doc = __rhino_doc__
TOL = 0.01
DIA = {"S32": 32.0, "H19": 19.0, "H16": 16.0}
MARK_DIA = {"P1": "S32", "P2": "S32", "P3": "H19", "P4": "H19", "P5": "H19", "P6": "H16"}
COLORS = {"P1": (200, 40, 40), "P2": (230, 120, 40), "P3": (40, 120, 200), "P4": (40, 170, 170),
"P5": (90, 90, 220), "P6": (140, 60, 160)}
def layer(path, color):
idx = doc.Layers.FindByFullPath(path, -1)
if idx >= 0:
return idx
parent = -1
parts = path.split("::")
for i in range(1, len(parts) + 1):
sub = "::".join(parts[:i])
idx = doc.Layers.FindByFullPath(sub, -1)
if idx < 0:
lay = Rhino.DocObjects.Layer()
lay.Name, lay.Color = parts[i - 1], Color.FromArgb(*color)
if parent >= 0:
lay.ParentLayerId = doc.Layers[parent].Id
idx = doc.Layers.Add(lay)
parent = idx
return parent
def min_bend_radius(dia_name):
d = DIA[dia_name]
inner = 2 * d if d <= 16 else 3.5 * d # box project rule (REBAR_BENDS_20260914)
return (inner + d) / 2
def add_bar(curve, mark, occ, design_len, hosts, src, coupler=0.0):
d = DIA[MARK_DIA[mark]]
# fitRail=False: fitRail=True fails on line-arc-line rails and crashed a headless Rhino (2026-10-07)
pipes = rg.Brep.CreatePipe(curve, d / 2, False, rg.PipeCapMode.Flat, False, TOL, math.radians(1))
if not pipes or not pipes[0].IsSolid:
raise RuntimeError(f"{mark} {occ}: pipe failed")
common = dict(mark=mark, dia=MARK_DIA[mark], shape=P["column_rebar"]["schedule"][mark]["shape"],
occurrence=occ, host="P1-CL", anchor_hosts=hosts, design_length=f"{design_len:.3f}",
coupler_length=f"{coupler:.3f}",
src=src)
for geo, lay, prefix in ((curve, layer("P1::철근::중심선", (60, 60, 60)), "P1-RC"),
(pipes[0], layer(f"P1::철근::기둥::{mark}", COLORS[mark]), "P1-R")):
attr = ObjectAttributes()
attr.LayerIndex = lay
oid = f"{prefix}-{mark}-{occ}"
attr.Name = oid
attr.SetUserString("id", oid)
for k, v in common.items():
attr.SetUserString(k, v)
if isinstance(geo, rg.Brep):
doc.Objects.AddBrep(geo, attr)
else:
doc.Objects.AddCurve(geo, attr)
def pt(r, deg, z):
t = math.radians(deg)
return rg.Point3d(r * math.cos(t), r * math.sin(t), z)
def arc_xy(r, a0, a1, z):
"""Horizontal arc at height z from angle a0 to a1 (deg, counter-clockwise)."""
am = (a0 + a1) / 2
return rg.ArcCurve(rg.Arc(pt(r, a0, z), pt(r, am, z), pt(r, a1, z)))
def hooked_half_ring(r, a0, z, hook, bend_r):
"""Semicircle a0 -> a0+180 with inward 135-degree hooks at both ends (P3, shape VI)."""
arc = arc_xy(r, a0, a0 + 180, z)
pieces = []
for at_start in (True, False):
p = arc.PointAtStart if at_start else arc.PointAtEnd
t = arc.TangentAtStart if at_start else arc.TangentAtEnd
if at_start:
t = -t # travel direction away from the arc
n = rg.Vector3d(-p.X, -p.Y, 0)
n.Unitize()
d = t * math.cos(math.radians(135)) + n * math.sin(math.radians(135)) # back + inward
d.Unitize()
pieces.append(rg.LineCurve(p, p + d * hook))
joined = rg.Curve.JoinCurves([pieces[0], arc, pieces[1]], TOL)[0]
fil = rg.Curve.CreateFilletCornersCurve(joined, bend_r, TOL, math.radians(1))
return fil or joined
def main():
for o in list(doc.Objects):
if (o.Attributes.GetUserString("id") or "").startswith(("P1-R-", "P1-RC-")):
doc.Objects.Delete(o, True)
cr = P["column_rebar"]
mb, hp, tie = cr["main_bars"], cr["hoops"], cr["ties"]
sch = cr["schedule"]
count = 0
# Main bars P1 (outer, hooked) / P2 (inner, straight)
p1, p2 = mb["p1"], mb["p2"]
for i in range(mb["divisions"]):
th = mb["start_deg"] * 0 + i * mb["pitch_deg"] # start angle 0 (CAD 0.001 deg)
ro, ri = mb["r_outer"], mb["r_inner"]
z_leg, R = p1["hook_leg_z"], p1["bend_r"]
top = p1["straight_from"] + p1["straight_len"]
leg = rg.LineCurve(pt(ro + R + p1["hook_leg"], th, z_leg), pt(ro + R, th, z_leg))
bend = rg.ArcCurve(rg.Arc(pt(ro + R, th, z_leg),
pt(ro + R - R * math.sin(math.radians(45)), th,
z_leg + R - R * math.cos(math.radians(45))),
pt(ro, th, z_leg + R)))
vert = rg.LineCurve(pt(ro, th, z_leg + R), pt(ro, th, top))
c1 = rg.Curve.JoinCurves([leg, bend, vert], TOL)[0]
add_bar(c1, "P1", f"{i + 1:02d}", p1["straight_len"] + math.pi / 2 * R + p1["hook_leg"],
"P1-FT,P1-CP", f"angle {th:.4f} deg; D1 단면 C-C / 정면도; D3 P1")
c2 = rg.LineCurve(pt(ri, th, p2["z0"]), pt(ri, th, p2["z0"] + p2["length"]))
add_bar(c2, "P2", f"{i + 1:02d}", p2["length"], "P1-FT,P1-CP",
f"angle {th:.4f} deg; D1 단면 C-C / 정면도; D3 P2")
count += 2
# Hoops: P4 (V: 2 half arcs + 2 coupler gaps), P5 (IX: ring + 1 gap), P3 (VI: 2 hooked halves)
r = hp["radius"]
n3 = 0
for k, (z, mark) in enumerate(zip(hp["levels"], hp["marks"])):
s = sch[mark]
a = float(s["A"]) * 1000 if float(s["A"]) < 100 else float(s["A"])
if mark in ("P4", "P5"):
gap = float(s["B"])
gap_deg = math.degrees(gap / r)
arcs = ([(90 + gap_deg / 2, 270 - gap_deg / 2), (270 + gap_deg / 2, 450 - gap_deg / 2)]
if mark == "P4" else [(90 + gap_deg / 2, 450 - gap_deg / 2)])
curves = [arc_xy(r, a0, a1, z) for a0, a1 in arcs]
for j, c in enumerate(curves):
add_bar(c, mark, f"L{k + 1:02d}{'ab'[j] if len(curves) > 1 else ''}", a, "",
f"EL {z / 1000:.3f}; coupler gap {gap:.0f} at {'90/270' if mark == 'P4' else '90'} deg",
coupler=gap) # one coupler (b) per piece: P4 2 pieces/2 gaps, P5 1/1
else:
n3 += 1
a0 = 90 if n3 % 2 else 0 # joins staggered by 90 deg
hook = float(s["B"])
for j, start in enumerate((a0, a0 + 180)):
c = hooked_half_ring(r, start, z, hook, min_bend_radius("H19"))
add_bar(c, mark, f"L{k + 1:02d}{'ab'[j]}", a + 2 * hook,
"P1-CP" if z > 40411 else "", f"EL {z / 1000:.3f}; joins at {a0}/{a0 + 180} deg")
count += 1
# Cross ties P6 (III): straight 1836 on diameters, 150 hooks down at r=918, sitting on hoop
half = tie["straight"] / 2
zoff = DIA["H19"] / 2 + DIA["H16"] / 2
for k, z in enumerate(tie["levels"]):
for ang in tie["angles_deg"]:
zt = z + zoff
pts = [pt(half, ang + 180, zt - tie["hook"]), pt(half, ang + 180, zt), pt(half, ang, zt),
pt(half, ang, zt - tie["hook"])]
pl = rg.PolylineCurve(pts)
c = rg.Curve.CreateFilletCornersCurve(pl, min_bend_radius("H16"), TOL, math.radians(1)) or pl
add_bar(c, "P6", f"L{k + 1}-{ang:03d}", tie["straight"] + 2 * tie["hook"],
"P1-FT" if z < 28408 else ("P1-CP" if z > 40411 else ""),
f"EL {z / 1000:.3f} (+{zoff} on hoop); angle {ang} deg")
count += 1
doc.Views.Redraw()
print(json.dumps({"bars": count}))
main()