- 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
"""G1 stage, form "single shaft" (Ramp-A P1, 2026-10-13): drilled shaft in two diameters (no footing, no lean
|
|
concrete), circular column, Y-shaped coping (R soffit arcs flaring from the column, battered ends that are convex
|
|
in plan, a step in the top surface), one pedestal (box) and one h2 pad (frustum) per bearing.
|
|
|
|
Run inside Rhino 8 via runner with globals PARAMS_PATH. Units mm, Z = EL x 1000, origin = pier centre.
|
|
params:
|
|
pile {segments [{d, z0, z1}, ...] bottom to top}; column {shape "circular", diameter, z0, z1}
|
|
coping {form "y_shaft", z_bottom (= column top), z_tip, half_bottom (column face), soffit {centre_x, centre_z,
|
|
radius} (left arc; the right one is mirrored), z_top_left, z_top_right, top_half (straight top edge),
|
|
plan_end {corner_x, tip_x, half_width} (bottom edge arc through (corner_x, +-half_width) and (tip_x, 0)),
|
|
side {half_width_top, half_width_bottom} (2,200 above z_tip, straight taper to 2,000 at z_bottom)}
|
|
bearing_seats {pedestal {size_x, size_y}, pad_margin_bottom, pad_margin_top,
|
|
bearings [{no, centre [x, y], rotation_deg, seat_z, h1, h2, A, B, type}]}
|
|
Coping = (front outline extruded along Y) n (side outline extruded along X) - two end cutters. The end face is
|
|
ruled: straight top edge x = +-top_half at the top level, convex plan arc at z_tip. Each extrusion reaches past
|
|
the faces the other one defines (no coincident or tangent faces in the booleans, see concrete_tapered.py).
|
|
Not modelled (user decision 2026-10-13): the 30 mm decorative grooves (D1 detail "B").
|
|
"""
|
|
import json
|
|
import math
|
|
|
|
import Rhino.Geometry as rg
|
|
import System
|
|
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"]
|
|
FCK = P["materials"]["fck"]
|
|
LAYERS = {k: (f"{PID}::{name}", Color.FromArgb(*rgb)) for k, (name, rgb) in P["display"]["concrete_layers"].items()}
|
|
|
|
|
|
def layer_index(full_path, color):
|
|
idx = doc.Layers.FindByFullPath(full_path, -1)
|
|
if idx >= 0:
|
|
return idx
|
|
parent_name, child = full_path.split("::")
|
|
parent = doc.Layers.FindByFullPath(parent_name, -1)
|
|
if parent < 0:
|
|
parent = doc.Layers.Add(parent_name, Color.Black)
|
|
layer = __import__("Rhino").DocObjects.Layer()
|
|
layer.Name, layer.Color = child, color
|
|
layer.ParentLayerId = doc.Layers[parent].Id
|
|
return doc.Layers.Add(layer)
|
|
|
|
|
|
def add(brep, key, oid, role, fck, views):
|
|
if brep is None or not brep.IsSolid:
|
|
raise RuntimeError(f"{oid}: not a closed solid")
|
|
if not brep.IsValid: # AddBrep silently refuses invalid breps
|
|
ok, log = brep.IsValidWithLog()
|
|
raise RuntimeError(f"{oid}: invalid brep: {log[:400]}")
|
|
attr = ObjectAttributes()
|
|
attr.LayerIndex = layer_index(*LAYERS[key])
|
|
attr.Name = oid
|
|
for k, v in (("id", oid), ("role", role), ("fck", fck), ("source_views", views)):
|
|
attr.SetUserString(k, v)
|
|
gid = doc.Objects.AddBrep(brep, attr)
|
|
if gid == System.Guid.Empty:
|
|
raise RuntimeError(f"{oid}: AddBrep failed")
|
|
return gid
|
|
|
|
|
|
def solid(b):
|
|
if b is not None and b.SolidOrientation == rg.BrepSolidOrientation.Inward:
|
|
b.Flip()
|
|
return b
|
|
|
|
|
|
def cylinder(z0, z1, r):
|
|
return rg.Cylinder(rg.Circle(rg.Plane(rg.Point3d(0, 0, z0), rg.Vector3d.ZAxis), r), z1 - z0).ToBrep(True, True)
|
|
|
|
|
|
def extrude_closed(curve, vec):
|
|
return solid(rg.Surface.CreateExtrusion(curve, vec).ToBrep().CapPlanarHoles(TOL))
|
|
|
|
|
|
def rotated_rect(cx, cy, along, across, rot_deg, z):
|
|
"""Rectangle with `along` side on +Y rotated clockwise by rot_deg (plan view)."""
|
|
t = math.radians(rot_deg)
|
|
ua, uc = (math.sin(t), math.cos(t)), (math.cos(t), -math.sin(t))
|
|
pts = [(cx + sa * ua[0] * along / 2 + sc * uc[0] * across / 2, cy + sa * ua[1] * along / 2 + sc * uc[1] * across / 2)
|
|
for sa, sc in ((-1, -1), (1, -1), (1, 1), (-1, 1))]
|
|
return rg.PolylineCurve([rg.Point3d(x, y, z) for x, y in pts + pts[:1]])
|
|
|
|
|
|
def frustum(bottom, top):
|
|
lofts = rg.Brep.CreateFromLoft([bottom, top], rg.Point3d.Unset, rg.Point3d.Unset, rg.LoftType.Straight, False)
|
|
return solid(lofts[0].CapPlanarHoles(TOL))
|
|
|
|
|
|
def boolean(kind, a, b):
|
|
f = rg.Brep.CreateBooleanIntersection if kind == "and" else rg.Brep.CreateBooleanDifference
|
|
out = f(a, b, TOL)
|
|
if not out or len(out) != 1:
|
|
raise RuntimeError(f"coping boolean {kind} failed ({len(out) if out else 0} pieces)")
|
|
return solid(out[0])
|
|
|
|
|
|
def front_extrusion(c):
|
|
"""Front outline (XZ): soffit arcs from the column face, continued past the end (cut later), vertical outer
|
|
ends at +-ext, top levels with the step at x = 0. Extruded over y = +-(half_width_top + 100)."""
|
|
s, zb = c["soffit"], c["z_bottom"]
|
|
cx, cz, r = s["centre_x"], s["centre_z"], s["radius"]
|
|
a0 = math.atan2(zb - cz, -c["half_bottom"] - cx) # left arc start (column face)
|
|
ext = c["plan_end"]["tip_x"] + 400 # past the tip (abs)
|
|
a1 = math.acos((-ext - cx) / r) if abs((-ext - cx) / r) <= 1 else None
|
|
if a1 is None:
|
|
raise RuntimeError("soffit arc does not reach the extension")
|
|
am = (a0 + a1) / 2 # left arc through start, mid, end
|
|
pl = [(cx + r * math.cos(a), cz + r * math.sin(a)) for a in (a0, am, a1)] # (x, z)
|
|
w = c["side"]["half_width_top"] + 100
|
|
to_xyz = lambda x, z: rg.Point3d(x, -w, z)
|
|
arc_l = rg.ArcCurve(rg.Arc(*[to_xyz(x, z) for x, z in pl]))
|
|
arc_r = rg.ArcCurve(rg.Arc(*[to_xyz(-x, z) for x, z in reversed(pl)]))
|
|
zl, zr = c["z_top_left"], c["z_top_right"]
|
|
xe, ze = pl[2] # left extension end (xe < 0)
|
|
poly_top = rg.PolylineCurve([to_xyz(xe, ze), to_xyz(xe, zl), to_xyz(0, zl), to_xyz(0, zr), to_xyz(-xe, zr),
|
|
to_xyz(-xe, ze)])
|
|
bottom = rg.LineCurve(to_xyz(c["half_bottom"], zb), to_xyz(-c["half_bottom"], zb))
|
|
j = rg.Curve.JoinCurves([arc_l, poly_top, arc_r, bottom], 0.01)
|
|
if len(j) != 1 or not j[0].IsClosed:
|
|
raise RuntimeError(f"front outline not closed ({len(j)} pieces)")
|
|
return extrude_closed(j[0], rg.Vector3d(0, 2 * w, 0))
|
|
|
|
|
|
def side_extrusion(c):
|
|
"""Side outline (YZ): straight taper from the column width at z_bottom to the full width at z_tip, continued
|
|
100 below z_bottom (no face on the coping bottom plane), vertical above, up past the top. Extruded over X."""
|
|
sd, zb, zt = c["side"], c["z_bottom"], c["z_tip"]
|
|
hb, ht = sd["half_width_bottom"], sd["half_width_top"]
|
|
k = (ht - hb) / (zt - zb)
|
|
y_low, z_low = hb - k * 100, zb - 100
|
|
ztop = max(c["z_top_left"], c["z_top_right"]) + 200
|
|
x0 = c["plan_end"]["tip_x"] + 600
|
|
pts = [(-y_low, z_low), (y_low, z_low), (ht, zt), (ht, ztop), (-ht, ztop), (-ht, zt)]
|
|
crv = rg.PolylineCurve([rg.Point3d(-x0, y, z) for y, z in pts + pts[:1]])
|
|
return extrude_closed(crv, rg.Vector3d(2 * x0, 0, 0))
|
|
|
|
|
|
def end_cutter(c, sx):
|
|
"""Waste beyond the ruled end face of one end (sx = -1 left, +1 right). Two straight-lofted sections, below the
|
|
tip level and above the top: on each, x(y) = arc(y) + t * (top_edge - arc(y)), t = (z - z_tip) / (z_top - z_tip),
|
|
so the loft lines are the rulings of the end face. Plan arc sampled every 25 mm (chord error < 0.1 mm)."""
|
|
pe, zt = c["plan_end"], c["z_tip"]
|
|
ztop = c["z_top_left"] if sx < 0 else c["z_top_right"]
|
|
hw, corner, tip, top = pe["half_width"], pe["corner_x"], pe["tip_x"], c["top_half"]
|
|
sag = tip - corner
|
|
R = (hw * hw + sag * sag) / (2 * sag)
|
|
ys = [-hw - 200 + 25 * i for i in range(int((2 * hw + 400) / 25) + 1)]
|
|
arc = lambda y: tip - (R - math.sqrt(R * R - y * y)) # abs x of the bottom edge
|
|
secs = []
|
|
for z in (zt - 700, ztop + 150):
|
|
t = (z - zt) / (ztop - zt)
|
|
pts = [(arc(y) + t * (top - arc(y)), y) for y in ys]
|
|
pts += [(tip + 1500, ys[-1]), (tip + 1500, ys[0])]
|
|
secs.append(rg.PolylineCurve([rg.Point3d(sx * x, y, z) for x, y in pts + pts[:1]]))
|
|
lofts = rg.Brep.CreateFromLoft(secs, rg.Point3d.Unset, rg.Point3d.Unset, rg.LoftType.Straight, False)
|
|
b = solid(lofts[0].CapPlanarHoles(TOL))
|
|
if b is None or not b.IsSolid:
|
|
raise RuntimeError("end cutter is not a closed solid")
|
|
return b
|
|
|
|
|
|
def coping_brep(c):
|
|
body = boolean("and", [front_extrusion(c)], [side_extrusion(c)])
|
|
return boolean("diff", [body], [end_cutter(c, -1), end_cutter(c, 1)])
|
|
|
|
|
|
def main():
|
|
for o in list(doc.Objects):
|
|
if o.Attributes.GetUserString("id"):
|
|
doc.Objects.Delete(o, True)
|
|
pl, cl, cp, bs = (P[k] for k in ("pile", "column", "coping", "bearing_seats"))
|
|
segs = pl["segments"]
|
|
for a, b in zip(segs, segs[1:]):
|
|
if abs(a["z1"] - b["z0"]) > TOL:
|
|
raise RuntimeError("pile segments not continuous")
|
|
if abs(segs[-1]["z1"] - cl["z0"]) > TOL or abs(cl["z1"] - cp["z_bottom"]) > TOL:
|
|
raise RuntimeError("pile top / column / coping bottom levels do not meet")
|
|
for i, s in enumerate(segs, 1):
|
|
add(cylinder(s["z0"], s["z1"], s["d"] / 2), "pile", f"{PID}-PL{i:02d}",
|
|
f"drilled shaft segment {i} D{s['d']:.0f}", FCK["pile"], "일반도 정면도·측면도")
|
|
add(cylinder(cl["z0"], cl["z1"], cl["diameter"] / 2), "column", f"{PID}-CL", "column", FCK["column"],
|
|
"일반도 정면도·측면도·기둥 단면도")
|
|
add(coping_brep(cp), "coping", f"{PID}-CP", "coping (Y-shaped, single shaft)", FCK["coping"],
|
|
"일반도 정면도·측면도·평면도")
|
|
ped, mb, mt = bs["pedestal"], bs["pad_margin_bottom"], bs["pad_margin_top"]
|
|
for b in bs["bearings"]:
|
|
cx, cy = b["centre"]
|
|
z0, z1 = b["seat_z"], b["seat_z"] + b["h1"]
|
|
base = rotated_rect(cx, cy, ped["size_y"], ped["size_x"], b["rotation_deg"], z0)
|
|
add(extrude_closed(base, rg.Vector3d(0, 0, b["h1"])), "seat", f"{PID}-BS{b['no']:02d}",
|
|
f"bearing pedestal h1 NO.{b['no']}", FCK["seat"], "일반도 평면도·교량받침 상세도·ELEVATION TABLE")
|
|
bottom = rotated_rect(cx, cy, b["A"] + mb, b["B"] + mb, b["rotation_deg"], z1)
|
|
top = rotated_rect(cx, cy, b["A"] + mt, b["B"] + mt, b["rotation_deg"], z1 + b["h2"])
|
|
add(frustum(bottom, top), "pad", f"{PID}-BP{b['no']:02d}", f"bearing pad h2 NO.{b['no']} {b['type']}", "-",
|
|
"일반도 평면도·교량받침 상세도·ELEVATION TABLE")
|
|
doc.Views.Redraw()
|
|
print(json.dumps({"objects": len(segs) + 2 + 2 * len(bs["bearings"])}))
|
|
|
|
|
|
main()
|