- 까닭 셋: 접힘 버린 자리 건너뜀 모서리(898.6 → 908 m · 139°) · 촘촘히가 가운데 점만 봐 S 자 빠짐(860 → 861 · 70°) · 잔 갈지자 - 촘촘히 = 4분 점도 봄 · 비탈 끝 = 가장 많이 꺾인 점부터 앞뒤 3 m 를 두 접선에 붙는 2차 곡선으로(15° 넘음) · 측점 · 깎기↔쌓기 바뀜 점은 그대로 · 둥글린 자리 눈금 뺌 - 요구 239 세션 자료: 30° 넘는 꺾임 왼 14 · 오른 3 · 최대 140.7° → 0 · 1 · 34.8°(측점 860 · 861 사이 1 m 에 폭 4.4 → 6.0 m) - 시험 test_364_3_slope_end_round.py(전후 · 측점 그대로 · TS 같은 점) · 자료 fixtures/b05_364_3_footprint_req239.json Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf
132 lines
5.2 KiB
Python
132 lines
5.2 KiB
Python
"""364-3 절/성토선 비탈 끝 모서리 둥글림 — 요구 239 브레인 검증 세션 자료로 전후 꺾임.
|
|
|
|
자료 `fixtures/b05_364_3_footprint_req239.json` = 세션 측점 줄 70 + 노선. 고치기 전 세션 선:
|
|
비탈 끝 30° 넘는 꺾임 왼 14 · 오른 3 · 최대 140.7°(접힘 버린 자리 · S 자 촘촘히 빠짐 · 잔 갈지자).
|
|
TS(`B05_Profile_PlanFootprint.ts`) 도 같은 자료로 같은 점인지 본다.
|
|
|
|
./venv/Scripts/python.exe -m pytest resources/tester/test_364_3_slope_end_round.py -q
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
import B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanSlope as plan # noqa: E402
|
|
|
|
DATA = json.loads(
|
|
(ROOT / "resources/tester/fixtures/b05_364_3_footprint_req239.json").read_text(encoding="utf-8")
|
|
)
|
|
ROUTE = [tuple(point) for point in DATA["route"]]
|
|
RECORDS = DATA["records"]
|
|
END = ROUTE[-1][0]
|
|
TS_FILE = ROOT / "B05_Profile" / "logic" / "B05_Profile_PlanFootprint.ts"
|
|
TSC = ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc"
|
|
|
|
|
|
def _turns(lines: list) -> list[float]:
|
|
"""조각 안 꺾임(°) — 조각 끝(깎기↔쌓기 바뀜 · 양 끝)은 뺌."""
|
|
return [
|
|
plan._turn_deg(line[i - 1], line[i], line[i + 1])
|
|
for line in lines
|
|
for i in range(1, len(line) - 1)
|
|
]
|
|
|
|
|
|
def _pieces() -> tuple:
|
|
return plan.slope_pieces(ROUTE, RECORDS, 0.0, END, 2.0)
|
|
|
|
|
|
def test_비탈_끝_30도_넘는_꺾임() -> None:
|
|
edge, fill, cut, _ticks = _pieces()
|
|
fill_turns, cut_turns = _turns(fill["lines"]), _turns(cut["lines"])
|
|
over = sum(t > 30 for t in fill_turns + cut_turns)
|
|
# 남는 하나 = 측점 860 · 861 사이 1 m 에 비탈 폭 4.4 → 6.0 m(측점 자리라 그대로)
|
|
assert over <= 1, over
|
|
assert max(fill_turns + cut_turns) < 40.0
|
|
assert sum(t > 15 for t in fill_turns + cut_turns) <= 25
|
|
assert max(_turns(edge["lines"])) < 10.0 # 도로 끝은 손대지 않음
|
|
|
|
|
|
def test_둥글림을_끄면_꺾임이_남음() -> None:
|
|
"""기준이 바뀐 길을 재는지 — 둥글림만 끄면 30° 넘는 꺾임이 그대로 나옴."""
|
|
keep = plan.ROUND_TURN_DEG
|
|
plan.ROUND_TURN_DEG = 1e9
|
|
try:
|
|
_edge, fill, cut, _ticks = _pieces()
|
|
finally:
|
|
plan.ROUND_TURN_DEG = keep
|
|
turns = _turns(fill["lines"]) + _turns(cut["lines"])
|
|
assert sum(t > 30 for t in turns) >= 5 and max(turns) > 90.0
|
|
|
|
|
|
def test_측점_비탈_끝은_그대로() -> None:
|
|
"""남은 측점 비탈 끝 점은 둥글림 뒤에도 같은 자리 점으로 남음."""
|
|
keep = plan.ROUND_TURN_DEG
|
|
plan.ROUND_TURN_DEG = 1e9
|
|
try:
|
|
_e, fill0, cut0, _t = _pieces()
|
|
finally:
|
|
plan.ROUND_TURN_DEG = keep
|
|
_e, fill, cut, _t = _pieces()
|
|
before = {tuple(p) for line in fill0["lines"] + cut0["lines"] for p in line}
|
|
after = {tuple(p) for line in fill["lines"] + cut["lines"] for p in line}
|
|
for record in RECORDS:
|
|
x, y, nx, ny = plan.route_normal(ROUTE, record["chainage_m"])
|
|
for side in ("left", "right"):
|
|
offset = record[f"{side}_end_m"]
|
|
want = (round(x + nx * offset, 4), round(y + ny * offset, 4))
|
|
if want in before:
|
|
assert want in after, (record["chainage_m"], side)
|
|
|
|
|
|
DRIVER = """
|
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
import { slopePieces } from "./B05_Profile_PlanFootprint.js";
|
|
const c = JSON.parse(readFileSync(process.argv[2], "utf-8"));
|
|
writeFileSync(process.argv[3], JSON.stringify(slopePieces(c.route, c.records, 0, c.end, 2)));
|
|
"""
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def ts_pieces(tmp_path_factory: pytest.TempPathFactory) -> dict:
|
|
out = tmp_path_factory.mktemp("ts_round")
|
|
subprocess.run( # noqa: S603 — 고정 실행 파일
|
|
["node", str(TSC), str(TS_FILE), "--outDir", str(out), "--module", "esnext",
|
|
"--target", "es2022", "--moduleResolution", "bundler", "--ignoreConfig", "--noCheck",
|
|
"--noResolve"],
|
|
cwd=str(ROOT), check=True, capture_output=True,
|
|
) # fmt: skip
|
|
(out / "driver.mjs").write_text(DRIVER, encoding="utf-8")
|
|
payload = {"route": ROUTE, "records": RECORDS, "end": END}
|
|
(out / "in.json").write_text(json.dumps(payload), encoding="utf-8")
|
|
run = subprocess.run( # noqa: S603
|
|
["node", str(out / "driver.mjs"), str(out / "in.json"), str(out / "out.json")],
|
|
capture_output=True, text=True, cwd=str(out),
|
|
) # fmt: skip
|
|
assert run.returncode == 0, run.stderr
|
|
return json.loads((out / "out.json").read_text(encoding="utf-8"))
|
|
|
|
|
|
def test_TS_도_같은_점(ts_pieces) -> None:
|
|
edge, fill, cut, ticks = _pieces()
|
|
for name, want, got in (
|
|
("도로 끝", edge["lines"], ts_pieces["edge"]),
|
|
("쌓기", fill["lines"], ts_pieces["fill"]),
|
|
("깎기", cut["lines"], ts_pieces["cut"]),
|
|
("눈금", ticks["ticks"], ts_pieces["ticks"]),
|
|
):
|
|
assert len(want) == len(got), f"{name} 선 수"
|
|
for line_w, line_g in zip(want, got):
|
|
assert len(line_w) == len(line_g), f"{name} 점 수"
|
|
for pw, pg in zip(line_w, line_g):
|
|
assert abs(pw[0] - pg[0]) < 2e-4 and abs(pw[1] - pg[1]) < 2e-4, (name, pw, pg)
|