Files
Aislo/resources/tester/test_407_3_curve_station_follow.py
T
eomsangdonandClaude Opus 5.5 5269859ee2 fix(B05·B06 측점): 구조물 놓음 · 뺌에도 서버 종단 곡선 측점 다시 셈 (407-3)
- [저장] 횡단 차례 ensure_structure_stations 가 곡선 측점 어긋남(_curve_marks)도 셈 · 노선 저장 안 기다림
- 다시 뜰 때 곡선 측점도 뜸(sample_structure_sections with_curves) · 종단 병합이 옛 곡선 측점도 갈음
- 측점 자리 한 벌 _station_layout 으로 뺌 · planned_curve_stations = generate_sections 와 같은 목록(표본 없음)
- 시험: test_407_3_curve_station_follow(놓음 = 구조물 측점 하나 · 뺌 = 곡선 측점 되살아남)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013QyNXY2TUKXCFMwAca2APG
2026-10-18 09:31:14 +09:00

131 lines
5.5 KiB
Python

"""PLAN 407-3 — 구조물을 MC 위에 놓았다 빼면 서버 종단 정본의 곡선 측점이 되살아남.
[저장] 차례(관 · 구조물 → 횡단) 의 `ensure_structure_stations` 길 — `_curve_marks`(어긋남 판정) ·
`sample_structure_sections(with_curves=True)`(곡선 측점도 뜸) · `_merge_irregular_into_longitudinal`
(옛 곡선 측점도 새 목록이 갈음). 노선 저장을 안 기다림 · 놓은 동안엔 그 자리 구조물 측점 하나.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import numpy as np
import pytest
ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from B05_Profile import B05_Profile_Engine_Sections as sections # noqa: E402
from B05_Profile import B05_Profile_Engine_Sections_Core as core # noqa: E402
from B05_Profile.B05_Profile_Router_Confirm import ( # noqa: E402
_merge_irregular_into_longitudinal,
)
from B06_Section.B06_Section_Router_Stations import _curve_marks # noqa: E402
LINE = [[0.0, 0.0, 100.0], [1000.3, 0.0, 100.0]]
SHORT = {"start": [101.0, 0.0], "apex": [107.3, 2.0], "end": [113.6, 0.0], "radius_m": 30.0}
LONG = {"start": [616.1, 0.0], "apex": [631.3, 3.0], "end": [646.5, 0.0], "radius_m": 40.0}
class _Flat:
def sample_xy(self, xy):
count = len(np.asarray(xy, dtype=np.float64))
return np.full(count, 100.0), np.ones(count, dtype=bool)
@pytest.fixture
def project(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> dict:
"""노선 · 곡선표 · 관 · 구조물 정본 대신 — 바꿀 수 있는 구조물 측점 하나(`extras`)."""
state: dict = {"extras": ()}
monkeypatch.setattr(sections, "_load_route_polyline", lambda *_: LINE)
monkeypatch.setattr(sections, "_load_design_curves", lambda *_: [SHORT, LONG])
monkeypatch.setattr(sections, "load_station_snap", lambda *_: False)
monkeypatch.setattr(sections, "load_station_snap_step", lambda *_: None)
monkeypatch.setattr(sections, "build_surface_sampler", lambda *_, **__: _Flat())
monkeypatch.setattr(sections, "_load_pipe_points", lambda *_: [])
monkeypatch.setattr(sections, "resolve_extra_stations", lambda *_: state["extras"])
# 노선 저장 때 정본 = 전체 생성(곡선 측점 107 · 631)
full = core.generate_sections(LINE, _Flat(), design_curves=[SHORT, LONG])["longitudinal"]
folder = tmp_path / "B06_Section" / "longitudinal"
folder.mkdir(parents=True)
(tmp_path / "B06_Section" / "cross_sections").mkdir()
(folder / "longitudinal.json").write_text(json.dumps(full), encoding="utf-8")
state["root"] = tmp_path
return state
def _save(state: dict) -> list[dict]:
"""[저장] 의 횡단 차례 — 어긋나면 다시 떠 병합(`ensure_structure_stations` 와 같은 길)."""
root = state["root"]
options = core.SectionGenerationOptions()
if _curve_marks(root, "route.json", options) == 0 and not state["extras"]:
return _stations(root)
fresh = sections.generate_irregular_sections(
root, "route.json", "f", "m", False, extra_stations=state["extras"], options=options
)
_merge_irregular_into_longitudinal(root, "B06_Section/longitudinal/longitudinal.json", fresh)
return _stations(root)
def _stations(root: Path) -> list[dict]:
path = root / "B06_Section" / "longitudinal" / "longitudinal.json"
return json.loads(path.read_text(encoding="utf-8"))["stations"]
def _at(stations: list[dict], chainage: float) -> list[str]:
return [s["kind"] for s in stations if abs(s["chainage_m"] - chainage) < 0.01]
def test_처음은_맞음(project) -> None:
assert _curve_marks(project["root"], "route.json", core.SectionGenerationOptions()) == 0
assert _at(_stations(project["root"]), 107.0) == ["curve"]
def test_MC_위_구조물_놓음_뺌(project) -> None:
root = project["root"]
project["extras"] = ((107.0, "관"),)
assert _curve_marks(root, "route.json", core.SectionGenerationOptions()) == 1
placed = _save(project)
assert _at(placed, 107.0) == ["irregular"] # 놓은 동안 = 구조물 측점 하나
assert _at(placed, 631.0) == ["curve"] # 다른 곡선 측점은 그대로
assert list((root / "B06_Section" / "cross_sections").glob("cross_*.json"))
project["extras"] = ()
assert _curve_marks(root, "route.json", core.SectionGenerationOptions()) == 1
removed = _save(project)
assert _at(removed, 107.0) == ["curve"] # 노선 저장 없이 되살아남
assert _at(removed, 631.0) == ["curve"]
assert _curve_marks(root, "route.json", core.SectionGenerationOptions()) == 0
assert len(removed) == len(_stations_full())
def _stations_full() -> list[dict]:
return core.generate_sections(LINE, _Flat(), design_curves=[SHORT, LONG])["longitudinal"][
"stations"
]
def test_planned_curve_stations_는_생성과_같음() -> None:
extras = ((107.0, "관"), (300.0, "옹벽"))
for snap, step in [(False, None), (True, None), (True, 0.1)]:
options = core.SectionGenerationOptions(extra_stations=extras)
made = core.generate_sections(
LINE,
_Flat(),
options,
design_curves=[SHORT, LONG],
station_snap=snap,
station_snap_step=step,
)["longitudinal"]["stations"]
planned = core.planned_curve_stations(
LINE,
options,
design_curves=[SHORT, LONG],
station_snap=snap,
station_snap_step=step,
)
assert planned == [s["chainage_m"] for s in made if s["kind"] == "curve"]