Files
Pier_v1/Code/교각모델링앱/engine/rhino/column_rebar_ring.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

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()