Files
Pier_v1/Code/pier-program/tests/test_centerline.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

178 lines
6.5 KiB
Python

"""centerline 테스트 (Rhino 없이 실행됩니다).
단면 외곽선을 안쪽으로 피복만큼 옮긴 직선들의 교점으로 철근 중심선 꺾임점을 구합니다.
2D 좌표는 (s, z): s = 길이방향, z = 높이.
기준: 기존 프로젝트 rebar_pipeline/rhino/02_build_shape_VII.py (형식 VII, 도면 b=4167.1, c=3158.8)
"""
import math
import pytest
import centerline as cl
# ---- 직선 오프셋 ----
def test_offset_line_right_side_of_rightward_segment_is_below():
# +s 방향으로 가는 선분의 오른쪽은 아래(-z)
point, direction = cl.offset_line((0.0, 0.0), (100.0, 0.0), 10.0, "right")
assert point == pytest.approx((0.0, -10.0))
assert direction == pytest.approx((1.0, 0.0))
def test_offset_line_left_side_of_rightward_segment_is_above():
point, direction = cl.offset_line((0.0, 0.0), (100.0, 0.0), 10.0, "left")
assert point == pytest.approx((0.0, 10.0))
def test_offset_line_downward_segment_right_side_is_minus_s():
# 아래로 가는 선분의 오른쪽은 -s 쪽 (끝면 s=12100 에서 안쪽)
point, _ = cl.offset_line((12100.0, 10738.0), (12100.0, 9238.0), 120.0, "right")
assert point[0] == pytest.approx(11980.0)
def test_offset_line_direction_is_unit_length():
_, d = cl.offset_line((0.0, 0.0), (30.0, 40.0), 5.0, "left")
assert math.hypot(*d) == pytest.approx(1.0)
def test_offset_line_rejects_zero_length_segment():
with pytest.raises(ValueError):
cl.offset_line((1.0, 1.0), (1.0, 1.0), 10.0, "left")
def test_offset_line_rejects_bad_side():
with pytest.raises(ValueError):
cl.offset_line((0.0, 0.0), (1.0, 0.0), 10.0, "up")
# ---- 직선 교점 ----
def test_line_intersection_perpendicular_lines():
a = ((0.0, 5.0), (1.0, 0.0))
b = ((7.0, 0.0), (0.0, 1.0))
assert cl.line_intersection(a, b) == pytest.approx((7.0, 5.0))
def test_line_intersection_diagonal():
a = ((0.0, 0.0), (1.0, 1.0))
b = ((10.0, 0.0), (-1.0, 1.0))
assert cl.line_intersection(a, b) == pytest.approx((5.0, 5.0))
def test_line_intersection_parallel_raises():
a = ((0.0, 0.0), (1.0, 0.0))
b = ((0.0, 5.0), (1.0, 0.0))
with pytest.raises(ValueError):
cl.line_intersection(a, b)
# ---- 중심선 꺾임점: 단순한 경우 ----
def test_centerline_of_u_shape_inside_a_box():
# 바깥 외곽: 위에서 왼쪽 벽을 따라 내려가 바닥을 지나 오른쪽 벽을 올라가는 ㄷ자 경로
segs = [
((0.0, 100.0), (0.0, 0.0)), # 왼쪽 벽, 아래로
((0.0, 0.0), (200.0, 0.0)), # 바닥, 오른쪽으로
((200.0, 0.0), (200.0, 100.0)), # 오른쪽 벽, 위로
]
pts = cl.centerline_points(segs, 10.0, "left")
# 아래로 가는 선분의 왼쪽은 +s, 오른쪽으로 가는 선분의 왼쪽은 +z (안쪽)
assert pts[0] == pytest.approx((10.0, 100.0))
assert pts[1] == pytest.approx((10.0, 10.0))
assert pts[2] == pytest.approx((190.0, 10.0))
assert pts[3] == pytest.approx((190.0, 100.0))
def test_centerline_fixed_start_and_end_lengths():
segs = [
((0.0, 100.0), (0.0, 0.0)),
((0.0, 0.0), (200.0, 0.0)),
((200.0, 0.0), (200.0, 100.0)),
]
pts = cl.centerline_points(segs, 10.0, "left", start_len=30.0, end_len=40.0)
assert pts[0] == pytest.approx((10.0, 40.0)) # 첫 꺾임점(10,10)에서 위로 30
assert pts[-1] == pytest.approx((190.0, 50.0)) # 마지막 꺾임점(190,10)에서 위로 40
assert len(pts) == 4
def test_centerline_single_segment_is_just_offset():
pts = cl.centerline_points([((0.0, 0.0), (100.0, 0.0))], 10.0, "left")
assert pts == [pytest.approx((0.0, 10.0)), pytest.approx((100.0, 10.0))]
def test_centerline_single_segment_rejects_fixed_lengths():
with pytest.raises(ValueError):
cl.centerline_points([((0.0, 0.0), (100.0, 0.0))], 10.0, "left", start_len=5.0)
def test_centerline_parallel_neighbors_raise():
segs = [((0.0, 0.0), (100.0, 0.0)), ((100.0, 0.0), (200.0, 0.0))]
with pytest.raises(ValueError):
cl.centerline_points(segs, 10.0, "left")
def test_centerline_rejects_empty():
with pytest.raises(ValueError):
cl.centerline_points([], 10.0, "left")
# ---- 길이와 꺾임각 ----
def test_segment_lengths_and_total():
pts = [(0.0, 0.0), (3.0, 4.0), (3.0, 10.0)]
assert cl.segment_lengths(pts) == pytest.approx([5.0, 6.0])
assert cl.polyline_length(pts) == pytest.approx(11.0)
def test_turn_angles_right_angle():
pts = [(0.0, 0.0), (10.0, 0.0), (10.0, 10.0)]
assert cl.turn_angles(pts) == pytest.approx([90.0])
def test_turn_angles_straight_is_zero():
pts = [(0.0, 0.0), (10.0, 0.0), (20.0, 0.0)]
assert cl.turn_angles(pts) == pytest.approx([0.0])
# ---- 형식 VII (대호지교 P1 C8/C10) 도면 값 재현 ----
P1_VII_SEGMENTS = [
((12100.0, 10738.0), (12100.0, 9238.0)), # 끝면, 아래로
((12100.0, 9238.0), (8100.0, 7738.0)), # 끝헌치
((8100.0, 7738.0), (4900.0, 7738.0)), # 바닥
((4900.0, 7738.0), (3400.0, 8238.0)), # 내측헌치
]
def test_shape_vii_matches_drawing_b_and_c():
pts = cl.centerline_points(P1_VII_SEGMENTS, 120.0, "right", start_len=630.0, end_len=2331.0)
lengths = cl.segment_lengths(pts)
assert len(pts) == 5
assert lengths[0] == pytest.approx(630.0) # a
assert lengths[1] == pytest.approx(4167.1, abs=0.1) # b (도면)
assert lengths[2] == pytest.approx(3158.8, abs=0.1) # c (도면)
assert lengths[3] == pytest.approx(2331.0) # d
def test_shape_vii_key_points():
pts = cl.centerline_points(P1_VII_SEGMENTS, 120.0, "right", start_len=630.0, end_len=2331.0)
assert pts[0] == pytest.approx((11980.0, 9951.16), abs=0.01) # A
assert pts[1] == pytest.approx((11980.0, 9321.16), abs=0.01) # 끝면-끝헌치 교점
assert pts[2][1] == pytest.approx(7858.0) # 바닥 위 120 (7738+120)
assert pts[3][1] == pytest.approx(7858.0)
assert pts[3][0] == pytest.approx(4919.47, abs=0.01)
def test_shape_vii_cover_is_120_from_every_face():
# 중심선 위의 각 꺾임점은 이웃한 두 면에서 모두 120 떨어져 있어야 합니다.
pts = cl.centerline_points(P1_VII_SEGMENTS, 120.0, "right", start_len=630.0, end_len=2331.0)
for i, (p0, p1) in enumerate(P1_VII_SEGMENTS[:-1]):
vertex = pts[i + 1]
for seg in (P1_VII_SEGMENTS[i], P1_VII_SEGMENTS[i + 1]):
assert cl.distance_to_line(vertex, seg[0], seg[1]) == pytest.approx(120.0, abs=1e-6)
def test_distance_to_line_basic():
assert cl.distance_to_line((5.0, 7.0), (0.0, 0.0), (10.0, 0.0)) == pytest.approx(7.0)