Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
"""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"]
|
||||
@@ -0,0 +1,79 @@
|
||||
"""PLAN 407-4 — 종단 측점 · 구조물 이름표 비켜 그리기(`logic/B06_Section_Label_Tiers.ts`).
|
||||
|
||||
가까운 측점(1.6 m 등)끼리 이름이 겹치면 위 단 · 안 겹치면 제자리(0) · 이름은 지우지 않음 ·
|
||||
단 수 위 끝(`MAX_LABEL_TIER`) 넘으면 마지막 단 · 글 폭 짐작(한글 = 글자 크기).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
TS = ROOT / "B06_Section" / "logic" / "B06_Section_Label_Tiers.ts"
|
||||
TYPESCRIPT = ROOT / "config" / "node_modules" / "typescript"
|
||||
|
||||
SCRIPT = r"""
|
||||
const ts = require(process.argv[1]);
|
||||
const fs = require("fs");
|
||||
const js = ts.transpileModule(fs.readFileSync(process.argv[2], "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
new Function("exports", "module", js)(box.exports, box);
|
||||
const m = box.exports;
|
||||
const cases = JSON.parse(process.argv[3]);
|
||||
process.stdout.write(JSON.stringify({
|
||||
max: m.MAX_LABEL_TIER,
|
||||
tiers: cases.map((items) => m.labelTiers(items)),
|
||||
widths: ["19+18 (MC)", "20+0", "배수관"].map((t) => m.labelWidthPx(t, 10)),
|
||||
}));
|
||||
"""
|
||||
|
||||
W = 40.0 # 이름표 폭(px)
|
||||
CASES = [
|
||||
[{"x": 100, "width": W}, {"x": 200, "width": W}, {"x": 300, "width": W}], # 안 겹침
|
||||
[{"x": 100, "width": W}, {"x": 108, "width": 24}], # 1.6 m 이웃 — 겹침
|
||||
[{"x": 108, "width": 24}, {"x": 100, "width": W}], # 차례 뒤바뀜(x 로 셈)
|
||||
[{"x": 100 + i * 2, "width": W} for i in range(6)], # 여섯 겹 — 위 끝
|
||||
[{"x": 100, "width": W}, {"x": 110, "width": W}, {"x": 150, "width": W}], # 셋째는 제자리
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def r() -> dict:
|
||||
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", "-e", SCRIPT, str(TYPESCRIPT), str(TS), json.dumps(CASES)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def test_안_겹치면_제자리(r) -> None:
|
||||
assert r["tiers"][0] == [0, 0, 0]
|
||||
|
||||
|
||||
def test_겹치면_위_단(r) -> None:
|
||||
assert r["tiers"][1] == [0, 1]
|
||||
assert r["tiers"][2] == [1, 0] # 왼쪽 것이 제자리
|
||||
|
||||
|
||||
def test_위_끝_넘으면_마지막_단(r) -> None:
|
||||
assert r["tiers"][3] == [0, 1, 2, 3, 3, 3] # 이름은 남음(지우지 않음)
|
||||
|
||||
|
||||
def test_빈_단은_다시_씀(r) -> None:
|
||||
assert r["tiers"][4] == [0, 1, 0]
|
||||
|
||||
|
||||
def test_글_폭(r) -> None:
|
||||
mc, plain, korean = r["widths"]
|
||||
assert mc == pytest.approx(60.0) and plain == pytest.approx(24.0)
|
||||
assert korean == pytest.approx(30.0)
|
||||
Reference in New Issue
Block a user