- 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>
181 lines
8.6 KiB
Python
181 lines
8.6 KiB
Python
"""G2 stage: column rebar P1-P6 (centreline + pipe solid) from params `column_rebar`.
|
|
|
|
Run inside Rhino 8 via runner (globals PARAMS_PATH) after concrete.py.
|
|
Ported from Code/교각모델링/build_rebar_rhino.py (Ungok P1, model v0.1.09). 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
|
|
PID = P["pier_id"]
|
|
MARK_DIA = {m: v["dia"] for m, v in P["rebar_marks"].items()}
|
|
DIA = {d: float(d[1:]) for d in MARK_DIA.values()} # 'S32' -> 32.0
|
|
COLORS = {m: tuple(c) for m, c in P["display"]["column_mark_colours"].items()}
|
|
CL_RGB = tuple(P["display"]["centreline_colour"])
|
|
Z_FT_TOP = P["footing"]["z_bottom"] + P["footing"]["thickness"] # ties/hoops below -> anchored in footing
|
|
Z_CP_BOT = P["coping"]["z_bottom_flat"] # above -> anchored in coping
|
|
|
|
|
|
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=f"{PID}-CL", anchor_hosts=hosts, design_length=f"{design_len:.3f}",
|
|
coupler_length=f"{coupler:.3f}",
|
|
src=src)
|
|
for geo, lay, prefix in ((curve, layer(f"{PID}::철근::중심선", CL_RGB), f"{PID}-RC"),
|
|
(pipes[0], layer(f"{PID}::철근::기둥::{mark}", COLORS[mark]), f"{PID}-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((f"{PID}-R-", f"{PID}-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"],
|
|
f"{PID}-FT,{PID}-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"], f"{PID}-FT,{PID}-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"])):
|
|
r = hp.get("radius_by_mark", {}).get(mark, hp["radius"]) # Ungok P2: P3 R899, P4/P5 R904
|
|
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(MARK_DIA["P3"]))
|
|
add_bar(c, mark, f"L{k + 1:02d}{'ab'[j]}", a + 2 * hook,
|
|
f"{PID}-CP" if z > Z_CP_BOT 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[MARK_DIA["P4"]] / 2 + DIA[MARK_DIA["P6"]] / 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(MARK_DIA["P6"]), TOL, math.radians(1)) or pl
|
|
add_bar(c, "P6", f"L{k + 1}-{ang:03d}", tie["straight"] + 2 * tie["hook"],
|
|
f"{PID}-FT" if z < Z_FT_TOP else (f"{PID}-CP" if z > Z_CP_BOT else ""),
|
|
f"EL {z / 1000:.3f} (+{zoff} on hoop); angle {ang} deg")
|
|
count += 1
|
|
doc.Views.Redraw()
|
|
print(json.dumps({"bars": count}))
|
|
|
|
|
|
main()
|