"""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"]