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>
This commit is contained in:
commit
964a841de1
3978 files changed
+58013356
No files matched your search
@@ -0,0 +1,190 @@
|
||||
"""G1 stage: T-pier concrete (lean, footing, circular column, coping, piles, pedestals, pads).
|
||||
|
||||
Run inside Rhino 8 via runner with globals PARAMS_PATH (app schema pier-T/1).
|
||||
Only params are read; no drawing-name branching. Units: mm, Z = EL x 1000.
|
||||
Ported from Code/교각모델링/build_rhino.py (Ungok P1, model v0.1.09).
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
|
||||
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"]
|
||||
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")
|
||||
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)
|
||||
return doc.Objects.AddBrep(brep, attr)
|
||||
|
||||
|
||||
def box(x0, x1, y0, y1, z0, z1):
|
||||
return rg.Box(rg.BoundingBox(x0, y0, z0, x1, y1, z1)).ToBrep()
|
||||
|
||||
|
||||
def cylinder(x, y, z0, z1, r):
|
||||
circle = rg.Circle(rg.Plane(rg.Point3d(x, y, z0), rg.Vector3d.ZAxis), r)
|
||||
return rg.Cylinder(circle, z1 - z0).ToBrep(True, True)
|
||||
|
||||
|
||||
def arc_through(p_start, p_end, radius, y):
|
||||
"""Arc of given radius between two XZ points; center on the lower side (soffit)."""
|
||||
(x1, z1), (x2, z2) = p_start, p_end
|
||||
mx, mz = (x1 + x2) / 2, (z1 + z2) / 2
|
||||
dx, dz = x2 - x1, z2 - z1
|
||||
chord = math.hypot(dx, dz)
|
||||
h = math.sqrt(radius ** 2 - (chord / 2) ** 2)
|
||||
nx, nz = -dz / chord, dx / chord
|
||||
centers = [(mx + nx * h, mz + nz * h), (mx - nx * h, mz - nz * h)]
|
||||
cx, cz = min(centers, key=lambda c: c[1])
|
||||
a1, a2 = math.atan2(z1 - cz, x1 - cx), math.atan2(z2 - cz, x2 - cx)
|
||||
am = (a1 + a2) / 2
|
||||
mid = rg.Point3d(cx + radius * math.cos(am), y, cz + radius * math.sin(am))
|
||||
arc = rg.Arc(rg.Point3d(x1, y, z1), mid, rg.Point3d(x2, y, z2))
|
||||
return rg.ArcCurve(arc), (cx, cz)
|
||||
|
||||
|
||||
def coping_brep(c):
|
||||
y0 = -c["width_y"] / 2
|
||||
half = c["length_bottom_tip_to_tip"] / 2
|
||||
zb, rise, r = c["z_bottom_flat"], c["tip_bottom_rise"], c["soffit_radius"]
|
||||
flat, inset = c["flat_bottom_half_width"], c["end_batter_top_inset"]
|
||||
steps = c["top_steps"]
|
||||
right_arc, rc = arc_through((flat, zb), (half, zb + rise), r, y0)
|
||||
left_arc, lc = arc_through((-half, zb + rise), (-flat, zb), r, y0)
|
||||
pts = [(half, zb + rise), (half - inset, steps[-1]["z"])]
|
||||
for s_prev, s in zip(reversed(steps[1:]), reversed(steps[:-1])):
|
||||
pts += [(s_prev["x0"], s_prev["z"]), (s["x1"], s["z"])]
|
||||
pts += [(-half + inset, steps[0]["z"]), (-half, zb + rise)]
|
||||
top = rg.PolylineCurve([rg.Point3d(x, y0, z) for x, z in pts])
|
||||
bottom = rg.LineCurve(rg.Point3d(-flat, y0, zb), rg.Point3d(flat, y0, zb))
|
||||
joined = rg.Curve.JoinCurves([bottom, right_arc, top, left_arc], TOL)
|
||||
if len(joined) != 1 or not joined[0].IsClosed:
|
||||
raise RuntimeError("coping profile not closed")
|
||||
srf = rg.Surface.CreateExtrusion(joined[0], rg.Vector3d(0, c["width_y"], 0))
|
||||
brep = srf.ToBrep().CapPlanarHoles(TOL)
|
||||
if brep.SolidOrientation == rg.BrepSolidOrientation.Inward:
|
||||
brep.Flip()
|
||||
return brep, {"right_arc_center": rc, "left_arc_center": lc}
|
||||
|
||||
|
||||
def prism(pts_xy, z0, z1):
|
||||
poly = rg.PolylineCurve([rg.Point3d(x, y, z0) for x, y in pts_xy + pts_xy[:1]])
|
||||
srf = rg.Surface.CreateExtrusion(poly, rg.Vector3d(0, 0, z1 - z0))
|
||||
b = srf.ToBrep().CapPlanarHoles(TOL)
|
||||
if b is not None and b.SolidOrientation == rg.BrepSolidOrientation.Inward:
|
||||
b.Flip()
|
||||
return b
|
||||
|
||||
|
||||
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)
|
||||
b = lofts[0].CapPlanarHoles(TOL)
|
||||
if b is not None and b.SolidOrientation == rg.BrepSolidOrientation.Inward:
|
||||
b.Flip()
|
||||
return b
|
||||
|
||||
|
||||
def add_bearing_seats(bs, steps):
|
||||
"""Pedestals (h1) per girder line split front/rear, and h2 pads (D2 TABLE A, B, h2)."""
|
||||
ped = bs["pedestal_girder5"]
|
||||
ref = bs["reference_girder"]
|
||||
mb, mt = bs["pad_margin_bottom"], bs["pad_margin_top"]
|
||||
by_no = {b["no"]: b for b in bs["bearings"]}
|
||||
for b in bs["bearings"]:
|
||||
shift = (b["girder_line"] - ref) * bs["pitch_x"]
|
||||
half = ped["front_half"] if b["row"] == "front" else ped["rear_half"]
|
||||
z0 = steps[b["girder_line"] - 1]["z"]
|
||||
z1 = z0 + b["h1"]
|
||||
tag = " [추론: 평행 이동]" if b["inferred"] else ""
|
||||
add(prism([(x + shift, y) for x, y in half], z0, z1), "seat", f"{PID}-BS{b['no']:02d}",
|
||||
f"bearing pedestal h1 NO.{b['no']} girder {b['girder_line']} {b['row']}{tag}", FCK["seat"],
|
||||
"D1 평면도, D1 단면도, D2 ELEVATION TABLE")
|
||||
cx, cy = b["centre"]
|
||||
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']}{tag}", "-",
|
||||
"D1 평면도, D1 단면도, D2 교량받침 상세·ELEVATION TABLE")
|
||||
return len(by_no) * 2
|
||||
|
||||
|
||||
def main():
|
||||
# Rebuild from scratch: remove previous P1 objects only.
|
||||
for o in list(doc.Objects):
|
||||
if o.Attributes.GetUserString("id"):
|
||||
doc.Objects.Delete(o, True)
|
||||
lc, ft, cl, cp, pl = (P[k] for k in ("lean_concrete", "footing", "column", "coping", "piles"))
|
||||
|
||||
ft_top = ft["z_bottom"] + ft["thickness"]
|
||||
col_top = ft_top + cl["height"]
|
||||
if abs(col_top - cp["z_bottom_flat"]) > TOL:
|
||||
raise RuntimeError(f"column top {col_top} != coping bottom {cp['z_bottom_flat']}")
|
||||
|
||||
pile_top = ft["z_bottom"] + pl["head_embedment"]
|
||||
px, py = pl.get("pitch_x", pl.get("pitch")), pl.get("pitch_y", pl.get("pitch")) # Ungok P2: X 1750 / Y 1550
|
||||
pile_xy = [(-((pl["count_x"] - 1) * px) / 2 + i * px,
|
||||
-((pl["count_y"] - 1) * py) / 2 + j * py)
|
||||
for j in range(pl["count_y"]) for i in range(pl["count_x"])]
|
||||
piles = [cylinder(x, y, pile_top - pl["length"], pile_top, pl["diameter"] / 2) for x, y in pile_xy]
|
||||
|
||||
lean = box(-lc["size_x"] / 2, lc["size_x"] / 2, -lc["size_y"] / 2, lc["size_y"] / 2,
|
||||
lc["z_bottom"], lc["z_top"])
|
||||
cut = rg.Brep.CreateBooleanDifference([lean], piles, TOL)
|
||||
if not cut or len(cut) != 1:
|
||||
raise RuntimeError("lean concrete boolean failed")
|
||||
add(cut[0], "lean", f"{PID}-LC", "lean concrete", FCK["lean"], "D1 정면도, D1 단면도")
|
||||
add(box(-ft["size_x"] / 2, ft["size_x"] / 2, -ft["size_y"] / 2, ft["size_y"] / 2,
|
||||
ft["z_bottom"], ft_top), "footing", f"{PID}-FT", "footing", FCK["footing"],
|
||||
"D1 정면도, D1 단면도, D2 기초평면도")
|
||||
add(cylinder(0, 0, ft_top, col_top, cl["diameter"] / 2), "column", f"{PID}-CL", "column", FCK["column"],
|
||||
"D1 정면도, D1 단면도, D1 단면 C-C/D-D/E-E, D2 기초평면도")
|
||||
cop, centers = coping_brep(cp)
|
||||
add(cop, "coping", f"{PID}-CP", "coping", FCK["coping"], "D1 평면도, D1 정면도, D1 단면도, D1 단면 A-A/B-B")
|
||||
pile_role = f"{pl['kind']} D{pl['diameter']:.0f} L{pl['length'] / 1000:g}m"
|
||||
for n, b in enumerate(piles, 1):
|
||||
add(b, "pile", f"{PID}-PL{n:02d}", pile_role, "-", "D1 정면도, D1 단면도")
|
||||
seats = add_bearing_seats(P["bearing_seats"], cp["top_steps"]) if "bearing_seats" in P else 0
|
||||
doc.Views.Redraw()
|
||||
print(json.dumps({"objects": 4 + len(piles) + seats, "coping_arc_centers": centers}))
|
||||
|
||||
|
||||
main()
|
||||
Reference in new issue
Block a user