- 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>
178 lines
6.5 KiB
Python
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)
|