- 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>
206 lines
11 KiB
Python
206 lines
11 KiB
Python
"""G2 stage, form "ring" (Ulsan P1, 2026-10-13): circular column rebar with one main-bar ring, hoop zones of
|
|
different hoop types and cross ties, from params `column_rebar` (centreline + pipe solid per bar).
|
|
|
|
Run inside Rhino 8 via runner (globals PARAMS_PATH) after the G1 builder. Units mm, Z = EL x 1000.
|
|
params column_rebar:
|
|
main {mark, count, start_deg, radius, hook_leg_z, bend_r, hook_leg, straight_from, straight_len}
|
|
bar = horizontal leg pointing outwards at hook_leg_z, R bend, vertical straight part (shape VI)
|
|
hoops {radius, zones: [{mark, type, levels, a, gap | lap+hook}]}
|
|
type "coupler_half": two half arcs, coupler gaps `gap` at 90 / 270 deg (shape V)
|
|
"coupler_ring": one ring, one coupler gap at 90 deg (shape X)
|
|
"lap_half": two half rings, each extended by lap/2 at both ends, 135-deg hooks (shape VI);
|
|
joins alternate between 90/270 and 0/180 deg
|
|
ties {mark, levels, angles_deg, straight, hook, z_offset}: straight bars on diameters, hooks down
|
|
`design_length` is the schedule-style original length; `coupler_length` the gap per piece (schedule L
|
|
includes the coupler).
|
|
Optional (Okseong P1, 2026-10-14; absent -> unchanged): main.inner {mark, radius} second ring at the same angles
|
|
with the hook bent inwards; zone.radius per hoop zone; ties.shape "90-135" {leg, tail, y_lift, design_length}:
|
|
straight on the X / Y diameters, 90-deg leg down at one end, 135-deg hook at the other, ends swapped per level,
|
|
the Y tie y_lift above the X tie.
|
|
"""
|
|
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 = {m: (v["dia"], v["shape"]) for m, v in P["rebar_marks"].items()}
|
|
DIA = {d: float(d[1:]) for d, _ in MARK.values()}
|
|
CL_RGB = tuple(P["display"]["centreline_colour"])
|
|
COLORS = {m: tuple(c) for m, c in P["display"].get("column_mark_colours", {}).items()}
|
|
Z_FT_TOP = P["footing"]["z_bottom"] + P["footing"]["thickness"]
|
|
Z_CP_BOT = Z_FT_TOP + P["column"]["height"]
|
|
|
|
|
|
def layer(path, color):
|
|
parent = -1
|
|
parts = path.split("::")
|
|
for i in range(1, len(parts) + 1):
|
|
idx = doc.Layers.FindByFullPath("::".join(parts[:i]), -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]
|
|
return ((2 * d if d <= 16 else 3.5 * d) + d) / 2
|
|
|
|
|
|
def hosts(z):
|
|
return f"{PID}-FT" if z < Z_FT_TOP else (f"{PID}-CP" if z > Z_CP_BOT else "")
|
|
|
|
|
|
def add_bar(curve, mark, occ, design_len, anchor, src, coupler=0.0):
|
|
dia, shape = MARK[mark]
|
|
pipes = rg.Brep.CreatePipe(curve, DIA[dia] / 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=dia, shape=shape, occurrence=occ, host=f"{PID}-CL", anchor_hosts=anchor,
|
|
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.get(mark, (200, 140, 90))), 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)
|
|
gid = doc.Objects.AddBrep(geo, attr) if isinstance(geo, rg.Brep) else doc.Objects.AddCurve(geo, attr)
|
|
if gid == __import__("System").Guid.Empty:
|
|
raise RuntimeError(f"{oid}: add failed")
|
|
|
|
|
|
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):
|
|
return rg.ArcCurve(rg.Arc(pt(r, a0, z), pt(r, (a0 + a1) / 2, z), pt(r, a1, z)))
|
|
|
|
|
|
def hooked_arc(r, a0, a1, z, hook, bend_r):
|
|
"""Arc a0 -> a1 (deg, ccw) with inward 135-deg hooks at both ends, fillet bend_r."""
|
|
arc = arc_xy(r, a0, a1, 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
|
|
n = rg.Vector3d(-p.X, -p.Y, 0)
|
|
n.Unitize()
|
|
d = t * math.cos(math.radians(135)) + n * math.sin(math.radians(135))
|
|
d.Unitize()
|
|
pieces.append(rg.LineCurve(p, p + d * hook))
|
|
joined = rg.Curve.JoinCurves([pieces[0], arc, pieces[1]], TOL)[0]
|
|
return rg.Curve.CreateFilletCornersCurve(joined, bend_r, TOL, math.radians(1)) or joined
|
|
|
|
|
|
def main():
|
|
for o in list(doc.Objects):
|
|
oid = o.Attributes.GetUserString("id") or ""
|
|
if oid.startswith((f"{PID}-R-P", f"{PID}-RC-P")):
|
|
doc.Objects.Delete(o, True)
|
|
cr = P["column_rebar"]
|
|
m = cr["main"]
|
|
n = 0
|
|
ro, R = m["radius"], m["bend_r"]
|
|
top = m["straight_from"] + m["straight_len"]
|
|
for i in range(m["count"]):
|
|
th = m["start_deg"] + i * 360.0 / m["count"]
|
|
leg = rg.LineCurve(pt(ro + R + m["hook_leg"], th, m["hook_leg_z"]), pt(ro + R, th, m["hook_leg_z"]))
|
|
bend = rg.ArcCurve(rg.Arc(pt(ro + R, th, m["hook_leg_z"]),
|
|
pt(ro + R - R * math.sin(math.radians(45)), th, m["hook_leg_z"] + R - R * math.cos(math.radians(45))),
|
|
pt(ro, th, m["hook_leg_z"] + R)))
|
|
vert = rg.LineCurve(pt(ro, th, m["hook_leg_z"] + R), pt(ro, th, top))
|
|
c = rg.Curve.JoinCurves([leg, bend, vert], TOL)[0]
|
|
add_bar(c, m["mark"], f"{i + 1:02d}", m["straight_len"] + math.pi / 2 * R + m["hook_leg"],
|
|
f"{PID}-FT,{PID}-CP", f"angle {th:.4f} deg; 배근도(1) 정면도 갈고리 R{R:.0f}·{m['hook_leg']:.0f}; 단면 DIVISION {m['count']}")
|
|
n += 1
|
|
if "inner" in m: # second ring, hook bent inwards (Okseong P1)
|
|
ri = m["inner"]["radius"]
|
|
z0 = m["hook_leg_z"]
|
|
leg = rg.LineCurve(pt(ri - R - m["hook_leg"], th, z0), pt(ri - R, th, z0))
|
|
bend = rg.ArcCurve(rg.Arc(pt(ri - R, th, z0),
|
|
pt(ri - R + R * math.sin(math.radians(45)), th, z0 + R - R * math.cos(math.radians(45))),
|
|
pt(ri, th, z0 + R)))
|
|
vert = rg.LineCurve(pt(ri, th, z0 + R), pt(ri, th, top))
|
|
c = rg.Curve.JoinCurves([leg, bend, vert], TOL)[0]
|
|
add_bar(c, m["inner"]["mark"], f"{i + 1:02d}", m["straight_len"] + math.pi / 2 * R + m["hook_leg"],
|
|
f"{PID}-FT,{PID}-CP", f"angle {th:.4f} deg; inner ring r {ri:.0f}, hook inwards")
|
|
n += 1
|
|
hp = cr["hoops"]
|
|
r = hp["radius"]
|
|
k = 0
|
|
for zone in hp["zones"]:
|
|
mark, typ = zone["mark"], zone["type"]
|
|
r = zone.get("radius", hp["radius"]) # per zone (Okseong P3 1,150 / P4 1,147)
|
|
for j, z in enumerate(zone["levels"]):
|
|
k += 1
|
|
if typ == "coupler_half":
|
|
gd = math.degrees(zone["gap"] / r)
|
|
arcs = [(90 + gd / 2, 270 - gd / 2), (270 + gd / 2, 450 - gd / 2)]
|
|
for q, (a0, a1) in enumerate(arcs):
|
|
add_bar(arc_xy(r, a0, a1, z), mark, f"L{k:03d}{'ab'[q]}", zone["a"], hosts(z),
|
|
f"EL {z / 1000:.3f}; coupler gaps {zone['gap']:.0f} at 90/270 deg [추론]", coupler=zone["gap"])
|
|
elif typ == "coupler_ring":
|
|
gd = math.degrees(zone["gap"] / r)
|
|
add_bar(arc_xy(r, 90 + gd / 2, 450 - gd / 2, z), mark, f"L{k:03d}", zone["a"], hosts(z),
|
|
f"EL {z / 1000:.3f}; coupler gap {zone['gap']:.0f} at 90 deg [추론]", coupler=zone["gap"])
|
|
elif typ == "lap_half":
|
|
ext = math.degrees(zone["lap"] / 2 / r)
|
|
a0 = 90 if j % 2 == 0 else 0 # joins alternate by 90 deg [추론]
|
|
for q, s in enumerate((a0, a0 + 180)):
|
|
c = hooked_arc(r, s - ext, s + 180 + ext, z, zone["hook"], min_bend_radius(MARK[mark][0]))
|
|
add_bar(c, mark, f"L{k:03d}{'ab'[q]}", zone["a"] + zone["lap"] + 2 * zone["hook"], hosts(z),
|
|
f"EL {z / 1000:.3f}; laps {zone['lap']:.0f} at {a0}/{a0 + 180} deg [추론]")
|
|
else:
|
|
raise RuntimeError(f"unknown hoop type {typ}")
|
|
n += 1
|
|
tie = cr["ties"]
|
|
if tie.get("shape") == "90-135": # Okseong P1 (2026-10-14), as column_rebar_straight.py
|
|
rb = min_bend_radius(MARK[tie["mark"]][0])
|
|
for q, z in enumerate(tie["levels"]):
|
|
for axis, name in ((0, "X"), (1, "Y")):
|
|
zt = z + (tie["y_lift"] if axis else 0)
|
|
u = rg.Vector3d(1, 0, 0) if axis == 0 else rg.Vector3d(0, 1, 0)
|
|
s = -1 if q % 2 else 1
|
|
a = rg.Point3d(0, 0, zt) - u * (s * tie["straight"] / 2)
|
|
b = rg.Point3d(0, 0, zt) + u * (s * tie["straight"] / 2)
|
|
d = -u * s * math.cos(math.radians(45)) + rg.Vector3d(0, 0, -1) * math.sin(math.radians(45))
|
|
pl = rg.PolylineCurve([a + rg.Vector3d(0, 0, -tie["leg"]), a, b, b + d * tie["tail"]])
|
|
c = rg.Curve.CreateFilletCornersCurve(pl, rb, TOL, math.radians(1)) or pl
|
|
add_bar(c, tie["mark"], f"L{q + 1:02d}{name}", tie["design_length"], hosts(zt),
|
|
f"EL {zt / 1000:.3f}; {name} (Y +{tie['y_lift']}, 90/135 교번 [추론])")
|
|
n += 1
|
|
doc.Views.Redraw()
|
|
print(json.dumps({"bars": n}))
|
|
return
|
|
half = tie["straight"] / 2
|
|
for q, z in enumerate(tie["levels"]):
|
|
zt = z + tie["z_offset"]
|
|
for ang in tie["angles_deg"]:
|
|
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[tie["mark"]][0]), TOL, math.radians(1)) or pl
|
|
add_bar(c, tie["mark"], f"L{q + 1:02d}-{ang:05.1f}", tie["straight"] + 2 * tie["hook"], hosts(z),
|
|
f"EL {z / 1000:.3f} (+{tie['z_offset']} on hoop); angle {ang:.2f} deg")
|
|
n += 1
|
|
doc.Views.Redraw()
|
|
print(json.dumps({"bars": n}))
|
|
|
|
|
|
main()
|