"""288 맹암거 횡단 형상(요구 183-3 · 9). 못박는 것 - 288-1 맹암거 기준 측점 = 측구 없음 · 노면 = 암거 경사로 지대 낮은 쪽 한 경사(물넘이 286 · 287 길 — 서버 `tilt_ranges` · `tilt_args` · TS 짝 `tilted`) · 제원 얹기 서버 `attach_underdrains` ↔ TS `attachUnderdrainSpecs` 같은 값 · 형상 = 노면을 암거 높이만큼 아래로 옵셋 → 성토 쪽은 성토 사면과 만남 · 절토 쪽은 절토 사면을 늘여 만남 · 그 안 돌 무늬 · 높이 비면 안 그림. - 288-2 맹암거 형상 누름 = 그 구조물 id 고름(구조물 목록 창구 · 알약 · 폼 · 측점선 따라옴) · 물넘이와 같은 판정면(보라 손잡이 아래 · 끌기 = 고름 아님 · 287-5) · 노면공 포장 범위 고름과 따로. 진짜 TS 는 `helper_288_underdrain.cjs` 가 Node 로 돌림. 화면은 사용자 확인. """ from __future__ import annotations import json import subprocess import sys from pathlib import Path import pytest HERE = Path(__file__).resolve().parent ROOT = HERE.parents[1] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) import B06_Section.B06_Section_Engine_Tilt as tilt # noqa: E402 from B05_Profile.B05_Profile_Structures_Schema import StructureInstance # noqa: E402 from B06_Section.B06_Section_Engine_Design import compute_cross_design # noqa: E402 STRUCTURES = [ { "structure_id": "ud-1", "type_id": "cross_underdrain", "placement": "point", "chainage_m": 40.0, "options": {"culvert_height_m": 0.8, "culvert_slope_pct": 4}, }, { # 경사 · 높이 빈 맹암거 — 표준 횡단경사 · 형상 없음 "structure_id": "ud-2", "type_id": "cross_underdrain", "placement": "point", "chainage_m": 80.0, "options": {}, }, { "structure_id": "x-1", "type_id": "cross_drain_exposed", "placement": "point", "chainage_m": 60.0, }, ] SECTIONS = [{"chainage_m": c} for c in (20.0, 40.0, 60.0, 80.0)] pytestmark = pytest.mark.skipif( not (ROOT / "config" / "node_modules" / "typescript").is_dir(), reason="프론트엔드 의존성 미설치", ) @pytest.fixture() def project(monkeypatch, tmp_path) -> Path: items = [StructureInstance(**s) for s in STRUCTURES] monkeypatch.setattr(tilt, "load_structures", lambda _root: (1, items)) monkeypatch.setattr(tilt, "load_culvert_sets", lambda _root: {}) return tmp_path @pytest.fixture(scope="module") def ts(tmp_path_factory) -> dict: source = tmp_path_factory.mktemp("s288") / "in.json" source.write_text( json.dumps({"structures": STRUCTURES, "sections": SECTIONS}), encoding="utf-8" ) proc = subprocess.run( ["node", str(HERE / "helper_288_underdrain.cjs"), str(source)], capture_output=True, text=True, encoding="utf-8", timeout=60, ) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) def test_맹암거_자리_기울기_서버(project): ranges = tilt.tilt_ranges(project) assert ranges == [(40.0, 40.0, 4.0), (80.0, 80.0, None)] assert tilt.tilt_args(40.0, ranges) == {"tilted": True, "tilt_pct": 4.0} assert tilt.tilt_args(80.01, ranges) == {"tilted": True, "tilt_pct": None} assert tilt.tilt_args(60.0, ranges) == {"tilted": False, "tilt_pct": None} def test_제원_얹기_서버_TS_같은_값(project, ts): sections = [dict(s) for s in SECTIONS] assert tilt.attach_underdrains(project, sections) == 2 == ts["attached"] assert [s.get("underdrain") for s in sections] == ts["sections"] assert ts["sections"][1] == { "structure_id": "ud-1", "chainage_m": 40.0, "height_m": 0.8, "slope_pct": 4.0, } def test_맹암거_측점_설계_측구_없음_낮은_쪽_기울기(): samples = [ {"offset_m": o / 2, "elevation_m": round(100.0 + 0.35 * o / 2, 4), "valid": True} for o in range(-24, 25) ] kwargs = {"ground_type": "soil", "section_mode": "left_cut", "ditch_side": "left"} got = compute_cross_design(samples, 100.4, tilted=True, tilt_pct=4.0, **kwargs) assert got["ditch_enabled"] is False and got["cross_slope_pct"] == 4.0 left, right = (got["road_edges"][k]["elevation_m"] for k in ("left", "right")) assert left > right # 지반이 좌로 오름 = 우가 낮음 def test_형상_옵셋선_성토_절토와_만남(ts): outline = [(p["offset"], p["elevation"]) for p in ts["outline"]] expected = [ (3, 100.15), # 좌 노면 끝 (-3, 99.85), # 우 노면 끝 (-4.632653, 98.768367), # 우 성토 사면 ∩ 옵셋선 (1.846154, 99.092308), # 좌 절토 사면을 늘인 선 ∩ 옵셋선 ] assert len(outline) == len(expected) for got, want in zip(outline, expected): assert got == pytest.approx(want, abs=1e-5) assert ts["noHeight"] is None assert ts["drew"] is True and ts["stones"] > 0 def test_횡단도_B07_이_맹암거를_그림(): view = (ROOT / "B06_Section" / "B06_Section_UI_Cross_View.ts").read_text(encoding="utf-8") assert ( "appendUnderdrainOverlay(plotLayer, section.underdrain, section.design, x, toDisplayY);" in view ) b07 = (ROOT / "B07_DesignDetail" / "B07_DesignDetail_Cross_Overlay.ts").read_text( encoding="utf-8" ) assert "appendUnderdrainOverlay(svg, section.underdrain, design, x, y);" in b07 refresh = (ROOT / "B06_Section" / "B06_Section_Cross_Refresh.ts").read_text(encoding="utf-8") assert "tilted: Boolean(section.ford_pavement || section.underdrain)," in refresh def test_맹암거_형상_누름은_그_구조물_고름(ts): assert ts["bodyId"] == "ud-1" # 둘레 = 형상 고름 표식 assert ts["routed"] == [["pipe"], [ts["eventName"], "ud-1", True]] view = (ROOT / "B06_Section" / "B06_Section_UI_Section_View.ts").read_text(encoding="utf-8") assert "grid.addEventListener(SHAPE_PICK_EVENT, (event) => {" in view assert "if (id) pavementSelectListener?.(id);" in view # = structuresPanel.selectStructure(id) cross = (ROOT / "B06_Section" / "B06_Section_UI_Cross_View.ts").read_text(encoding="utf-8") assert 'revetOffset && shapePickRoute(svg, (at) => toggleRevet("pipe", at)),' in cross