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:
ahnyongjooandClaude Sonnet 5.5 committed 2026-10-28 11:20:18 +09:00
commit 964a841de1
3978 files changed
+58013356

No files matched your search

+123
View File
@@ -0,0 +1,123 @@
"""단면 외곽선에서 철근 중심선 꺾임점을 계산하는 모듈.
Rhino 없이 실행됩니다. 단위는 mm, 각도는 도(degree)입니다.
2D 좌표는 (s, z): s = 길이방향, z = 높이 (frame.py 의 로컬 좌표와 같은 방향).
방법 (기존 프로젝트 rebar_pipeline/README.md §3):
1. 단면 외곽의 각 면을 안쪽으로 피복만큼 옮긴 직선으로 바꿉니다.
2. 이웃한 두 직선의 교점이 철근 중심선의 꺾임점입니다.
3. 시작과 끝은 도면의 고정 길이만큼 연장합니다.
"""
import math
EPS = 1e-9
def _unit_direction(p0, p1):
dx = p1[0] - p0[0]
dz = p1[1] - p0[1]
length = math.hypot(dx, dz)
if length < EPS:
raise ValueError("길이가 0인 선분입니다: %r" % (p0,))
return (dx / length, dz / length)
def _normal(direction, side):
"""진행 방향의 왼쪽 또는 오른쪽 단위 법선."""
dx, dz = direction
if side == "left":
return (-dz, dx)
if side == "right":
return (dz, -dx)
raise ValueError("side 는 'left' 또는 'right' 여야 합니다: %r" % (side,))
def offset_line(p0, p1, distance, side):
"""선분 p0->p1 을 진행 방향의 side(left/right)로 distance 만큼 옮긴 직선.
돌려주는 값: (직선 위의 한 점, 진행 방향 단위벡터)
"""
direction = _unit_direction(p0, p1)
n = _normal(direction, side)
point = (p0[0] + n[0] * distance, p0[1] + n[1] * distance)
return point, direction
def line_intersection(line_a, line_b):
"""두 직선 (점, 방향) 의 교점. 평행하면 ValueError."""
(ax, az), (adx, adz) = line_a
(bx, bz), (bdx, bdz) = line_b
cross = adx * bdz - adz * bdx
if abs(cross) < EPS:
raise ValueError("두 직선이 평행해서 교점이 없습니다")
t = ((bx - ax) * bdz - (bz - az) * bdx) / cross
return (ax + adx * t, az + adz * t)
def centerline_points(segments, cover, side, start_len=None, end_len=None):
"""철근 중심선의 꺾임점 목록 (s, z).
segments : 외곽선 선분 목록 [(시작점, 끝점), ...]. 철근이 지나가는 순서와 방향대로 적습니다.
cover : 피복 (철근 중심에서 면까지 거리)
side : 콘크리트 안쪽이 진행 방향의 'left' 인지 'right' 인지
start_len: 첫 꺾임점에서 첫 선분 반대 방향으로 연장할 길이. None 이면 첫 선분의 시작점을 그대로 씁니다.
end_len : 마지막 꺾임점에서 마지막 선분 방향으로 연장할 길이. None 이면 마지막 선분의 끝점을 그대로 씁니다.
"""
if not segments:
raise ValueError("선분이 하나 이상 필요합니다")
lines = [offset_line(p0, p1, cover, side) for (p0, p1) in segments]
if len(lines) == 1:
if start_len is not None or end_len is not None:
raise ValueError("선분이 1개이면 고정 길이를 쓸 수 없습니다")
(p0, d) = lines[0]
length = math.hypot(segments[0][1][0] - segments[0][0][0],
segments[0][1][1] - segments[0][0][1])
return [p0, (p0[0] + d[0] * length, p0[1] + d[1] * length)]
vertices = [line_intersection(lines[i], lines[i + 1]) for i in range(len(lines) - 1)]
if start_len is None:
first = lines[0][0]
else:
d0 = lines[0][1]
first = (vertices[0][0] - d0[0] * start_len, vertices[0][1] - d0[1] * start_len)
if end_len is None:
seg_end = segments[-1][1]
n = _normal(lines[-1][1], side)
last = (seg_end[0] + n[0] * cover, seg_end[1] + n[1] * cover)
else:
dn = lines[-1][1]
last = (vertices[-1][0] + dn[0] * end_len, vertices[-1][1] + dn[1] * end_len)
return [first] + vertices + [last]
def segment_lengths(points):
"""꺾임점 목록의 각 구간 길이."""
return [math.hypot(b[0] - a[0], b[1] - a[1]) for a, b in zip(points, points[1:])]
def polyline_length(points):
"""꺾임점을 이은 전체 길이."""
return sum(segment_lengths(points))
def turn_angles(points):
"""각 안쪽 꺾임점에서 진행 방향이 바뀌는 각도(도, 0~180). 직선이면 0, 직각이면 90."""
angles = []
for i in range(1, len(points) - 1):
d1 = _unit_direction(points[i - 1], points[i])
d2 = _unit_direction(points[i], points[i + 1])
dot = max(-1.0, min(1.0, d1[0] * d2[0] + d1[1] * d2[1]))
angles.append(math.degrees(math.acos(dot)))
return angles
def distance_to_line(point, line_p0, line_p1):
"""점에서 (선분이 아니라) 무한 직선 line_p0-line_p1 까지의 수직거리."""
d = _unit_direction(line_p0, line_p1)
vx = point[0] - line_p0[0]
vz = point[1] - line_p0[1]
return abs(vx * d[1] - vz * d[0])