⚠ **뿌리** — `tmp/` 는 창 사이에 안 건너감(실측 확인: 상대 창이 놓은 `tmp/_sync_probe.txt` 가 시간을 두고 두 번 봐도 안 보임). 그래서 **정본(등록부 스키마)만 건너가고 그것을 읽는 시험은 안 건너가** 오늘 두 번, 같은 시험이 **연 창은 통과·받은 창은 실패**가 됐음. - `tmp/tests/*` 를 `resources/tester/` 로 **복사**(127 파일). 내용은 **한 줄도 안 고침** - `tmp/tests` 는 **남겨 둠** — 되돌릴 자리(사용자 지시) - 실행: `./venv/Scripts/python.exe -m pytest resources/tester/ -q` 옮기기 전과 **같은 수**: 617 통과 / 22 건너뜀 / 실패 0 ⇒ 이제 시험·예외·까닭이 **정본과 함께** 움직임. 오늘 세운 「예외는 정본 스키마에」와 짝임. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
111 lines
4.5 KiB
Python
111 lines
4.5 KiB
Python
"""계획선 출처(`basis`) 구분과 B04 3D 화면맞춤 범위 (2026-09-02 결함 2건).
|
|
|
|
① 1차(배관 정착)와 2차(직선 분할)가 같은 `_profile_entry()` 를 쓰다 보니 저장본
|
|
`basis` 가 둘 다 `station_alignment` 로 나갔다. 사고 조사 때 "폴백으로 떨어졌다" 를
|
|
저장본에서 확인하지 못한 원인이다. 값을 갈랐는지 잠근다.
|
|
② B04 지표면 3D 뷰어가 `referenceBounds`(지표면 범위)만으로 카메라 거리를 정해,
|
|
라이다가 노선의 일부만 덮으면 나머지가 화면 밖으로 잘렸다(용화 실측 —
|
|
노선 2,136m 중 라이다 1,400m). 노선까지 담는 대칭 확장이 들어갔는지 잠근다.
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
if str(PROJECT_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
from B05_Profile.B05_Profile_Engine_Grade import resolve_grade_options # noqa: E402
|
|
from B05_Profile.B05_Profile_Engine_Grade_Profile import ( # noqa: E402
|
|
ALIGNMENT_BASIS,
|
|
PIPE_ANCHORED_BASIS,
|
|
design_alignment_profile,
|
|
design_pipe_anchored_profile,
|
|
rebuild_alignment_profile,
|
|
)
|
|
|
|
|
|
def _longitudinal(length_m: float = 400.0, step: float = 20.0) -> dict:
|
|
"""지반고가 완만하게 내려가는 종단 — 두 진입점 모두 계획선을 만들 수 있다."""
|
|
chainages = np.arange(0.0, length_m + step, step)
|
|
return {
|
|
"length_m": float(length_m),
|
|
"samples": [
|
|
{"chainage_m": float(c), "elevation_m": 100.0 - 0.02 * float(c), "valid": True}
|
|
for c in chainages
|
|
],
|
|
"stations": [
|
|
{"chainage_m": float(c), "kind": "regular", "elevation_m": 100.0 - 0.02 * float(c)}
|
|
for c in chainages
|
|
],
|
|
}
|
|
|
|
|
|
# ── ① 계획선 출처가 저장본에서 갈린다 ────────────────────────────────────────
|
|
|
|
|
|
def test_two_bases_are_distinct_values():
|
|
"""두 값이 같으면 저장본으로 진입점을 못 가른다."""
|
|
assert PIPE_ANCHORED_BASIS != ALIGNMENT_BASIS
|
|
|
|
|
|
def test_pipe_anchored_profile_reports_its_own_basis():
|
|
"""1차(배관 정착)는 `pipe_anchored` 로 남는다."""
|
|
_, entry = design_pipe_anchored_profile(
|
|
_longitudinal(),
|
|
resolve_grade_options("trunk"),
|
|
[200.0],
|
|
station_interval_m=20.0,
|
|
)
|
|
assert entry["basis"] == PIPE_ANCHORED_BASIS
|
|
|
|
|
|
def test_fallback_profile_keeps_the_station_basis():
|
|
"""2차(직선 분할 폴백)는 종전 값 그대로 — 옛 저장본과 뜻이 어긋나지 않는다."""
|
|
_, entry = design_alignment_profile(
|
|
_longitudinal(),
|
|
resolve_grade_options("trunk"),
|
|
station_interval_m=20.0,
|
|
)
|
|
assert entry["basis"] == ALIGNMENT_BASIS
|
|
|
|
|
|
def test_rebuild_inherits_the_stored_basis():
|
|
"""사용자 편집 재구성은 저장된 자동 선형을 그대로 쓰므로 출처도 물려받는다."""
|
|
longitudinal = _longitudinal()
|
|
alignment, entry = design_pipe_anchored_profile(
|
|
longitudinal,
|
|
resolve_grade_options("trunk"),
|
|
[200.0],
|
|
station_interval_m=20.0,
|
|
)
|
|
# 저장 경로(`B05_Profile_Engine_Sections.py:252`)와 같은 자리에 선형을 얹는다.
|
|
longitudinal["design_profiles"] = [entry]
|
|
longitudinal["profile_alignment"] = alignment
|
|
_, rebuilt = rebuild_alignment_profile(longitudinal, None)
|
|
assert rebuilt["basis"] == PIPE_ANCHORED_BASIS
|
|
|
|
|
|
# ── ② B04 3D 화면맞춤이 노선까지 담는다 ──────────────────────────────────────
|
|
|
|
VIEWER_SOURCE = (PROJECT_ROOT / "B04_PreProcess" / "B04_PreProcess_UI_TerrainViewer.ts").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
|
|
|
|
def test_fit_camera_uses_route_expanded_bounds():
|
|
"""`fitCamera` 가 지표면 범위 대신 노선을 담은 범위를 쓴다."""
|
|
assert "const bounds = fitBounds();" in VIEWER_SOURCE
|
|
assert "getTopFitDistance(bounds, aspect)" in VIEWER_SOURCE
|
|
assert "getTopFitDistance(referenceBounds, aspect)" not in VIEWER_SOURCE
|
|
|
|
|
|
def test_fit_bounds_expands_symmetrically_around_the_reference_center():
|
|
"""카메라 타깃이 지표면 중심이라 한쪽만 넓히면 소용없다 — 반폭을 대칭으로 잡는다."""
|
|
assert "halfX = Math.max(halfX, Math.abs(point.x - cx))" in VIEWER_SOURCE
|
|
assert "halfY = Math.max(halfY, Math.abs(point.y - cy))" in VIEWER_SOURCE
|
|
assert "x_min: cx - halfX" in VIEWER_SOURCE
|
|
assert "x_max: cx + halfX" in VIEWER_SOURCE
|