Files
Aislo/resources/tester/test_271_straighten_pick.py
T
eomsangdonandClaude Opus 5.5 182de3b2e3 fix(461 B05 종단 직선화): 클릭 = 늘 측점 고름 · 끝 자동 R 지움 기본 L · 이미 직선 범위 부분 구간 · 실시간 범위 띠
- 직선화 첫 클릭이 직선 안이면 직선 통째 고름(붉은 강조)으로 새던 갈림 지움 · 빈 곳 클릭 측점선에 붙음
- 이미 직선인 범위 = 부분 구간 고름(보라 띠 · 안내) · ▲▼ = 가운데 + 양 끝 기본 L 곡선
- 직선화 · 자유지정 끝 R 이 저장 자동 R 이면 지움 = 끝 곡선 L 20 · 사용자 R 은 둠
- 법정 미달 판정 오차 1e-3 짝(TS · 파이썬) — L 19.99998 거짓 미달 없앰
- 새 도우미 design_profile_range_band — 직선화 · 자유지정 범위 단계 실시간 띠 · 시작–끝 · 길이 글
- Esc = 시작만 취소 · 모드 유지 · 고름 풀기로 안 샘
- 시험 test_271_straighten_pick (거울 포함)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 22:49:09 +09:00

197 lines
8.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""271-1~3 B05 종단 [직선화] 측점 고름 · 끝 R 기본 L · [직선화] · [자유지정] 실시간 띠.
요구 271 · PLAN 461.
계산(`logic/B05_Profile_UI_Profile_Straighten.ts` · `_Tools.ts` · `design_profile_free.ts` ·
`design_profile_range_band.ts` · `_CurveLaw.ts`)은 Node 헬퍼(`helper_271_straighten_pick.cjs`)가
트랜스파일해 돌리고, 파이썬 짝(`build_alignment` · `vertical_curve_shortfalls`)은 같은 base · 편집으로
곡선 자리 · R · L · 미달 경고가 같은지 거울로 본다.
./venv/Scripts/python.exe -m pytest resources/tester/test_271_straighten_pick.py -q
"""
import json
import os
import subprocess
import sys
from pathlib import Path
import numpy as np
import pytest
HERE = Path(__file__).resolve().parent
ROOT = HERE.parent.parent
sys.path.insert(0, str(ROOT))
from B05_Profile.B05_Profile_Engine_Grade_Alignment import ( # noqa: E402
AlignmentPolicy,
build_alignment,
)
from B05_Profile.B05_Profile_Engine_Grade_CurveLaw import ( # noqa: E402
SHORTFALL_TOLERANCE_M,
vertical_curve_shortfalls,
)
def _read(relative: str) -> str:
return (ROOT / relative).read_text(encoding="utf-8")
@pytest.fixture(scope="module")
def out():
proc = subprocess.run(
["node", os.path.join(HERE, "helper_271_straighten_pick.cjs")],
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
def _at(curves, chainage):
return next((c for c in curves if abs(c["at"] - chainage) < 1e-6), None)
def test_직선화_끝_기본_L_사이_곡선_없음(out):
curves = out["straight"]["curves"]
assert not [c for c in curves if 40 < c["at"] < 200]
assert _at(curves, 40)["l"] == pytest.approx(20) and _at(curves, 200)["l"] == pytest.approx(20)
assert out["straight"]["radii"] == []
def test_이미_직선_범위_부분_구간_반대_방향_같음(out):
assert out["sub"] == {"fromM": 80, "toM": 160, "nodes": [80, 100, 120, 140, 160]}
assert out["subReverse"] == out["sub"]
assert out["subBent"] is None and out["subMixed"] is None
assert out["subOneSpan"] is None # 사이 꼭짓점 없음 = 꺾을 곳 없음
def test_부분_구간_꺾기_양_끝_L20_거짓_미달_없음(out):
curves = out["tiltSub"]["curves"]
for at in (80, 120, 160):
assert _at(curves, at)["l"] == pytest.approx(20, abs=1e-3)
assert not [c for c in curves if 80 < c["at"] < 160 and c["at"] != 120]
assert out["tiltSub"]["shortfalls"] == []
def test_법정_판정_오차_짝(out):
assert out["tolerance"] == SHORTFALL_TOLERANCE_M == 1e-3
assert out["nearMiss"] == []
assert len(out["realMiss"]) == 1
near = [{"chainage_m": 120, "l_m": 19.99998, "r_m": 800}]
assert vertical_curve_shortfalls(near, 100, 20) == []
real = [{"chainage_m": 120, "l_m": 19.9, "r_m": 800}]
assert vertical_curve_shortfalls(real, 100, 20) == out["realMiss"]
def test_끝_저장_자동_R_지움_사용자_R_둠(out):
assert out["autoEnds"]["radii"] == []
curves = out["autoEnds"]["curves"]
assert _at(curves, 40)["l"] == pytest.approx(20) and _at(curves, 200)["l"] == pytest.approx(20)
assert out["userEnd"]["radii"] == {"40.000": 600}
assert out["noAuto"] == ["40.000", "200.000"]
def test_BP_EP_곡선_없음(out):
assert out["bpEp"] == []
assert out["tiltBp"][:2] == [40, 80] # 시점 0 엔 곡선 없음 · 가운데 · 끝만
def test_곡선_섞인_범위_안쪽_R_지움_양_끝_L20(out):
assert out["mixed"]["radii"] == []
curves = out["mixed"]["curves"]
assert not [c for c in curves if 100 < c["at"] < 260]
assert _at(curves, 100)["l"] == pytest.approx(20) and _at(curves, 260)["l"] == pytest.approx(20)
def test_자유지정_끝_자동_R_지움_사용자_R_둠(out):
assert out["freeAuto"] == ["120.000"]
assert out["freeUser"] == ["40.000", "120.000"]
def test_자유지정_범위_실시간_띠_Esc_시작만(out):
assert out["liveBand"] == [40, 120] and out["liveEdges"] == [40, 120]
assert out["liveReverse"] == [20, 40]
assert out["outside"] == {"band": None, "edges": [40]} # 그래프 밖 = 시작선만
assert "Esc 시작 취소" in out["hintPending"]
assert out["escTaken"] and out["afterEsc"] == {"phase": "range", "edges": [], "closed": 0}
assert out["fixedBand"] == [40, 160] and out["armedPhase"] == "armed" # 둘째 클릭 뒤 고정
assert out["escArmed"] and out["afterEscArmed"] == {"phase": "range", "closed": 1}
def test_범위_띠_그림(out):
assert out["bandShape"] == ["rect:80+160", "text:40.0–120.0m · 80.0m", "line:80", "line:240"]
assert out["bandStart"] == ["line:80"]
assert out["viewIdle"] == 0 and out["viewOff"] == 0
assert out["viewStart"] == ["line:80"]
assert out["viewHover"] == out["bandShape"]
assert out["viewLeave"] == ["line:80"]
assert out["viewRemount"] == ["rect:80+40", "text:40.0–60.0m · 20.0m", "line:80", "line:120"]
def test_도구_직선화_부분_구간_붉은_강조_아님(out):
assert out["toolsNone"] is False
assert out["pending"] == {
"at": 40,
"hint": "40.0m 선택 — 두 번째 측점을 고르세요 (Esc 시작 취소)",
}
assert out["cancel"] == {"taken": True, "pending": None, "mode": "straighten"}
assert out["cancelAgain"] is False
assert out["subPicked"]["red"] == [] and out["subPicked"]["sub"]["fromM"] == 80
assert out["subPicked"]["hint"] == "이미 직선 — ▲▼ 로 꺾으면 양 끝에 기본 L 곡선"
assert out["tiltCall"] == ["tilt", 80, 160, 0.1]
assert out["newStart"] == {"pending": 60, "sub": None}
assert out["straightenCall"] == [60, 180]
assert out["shiftRed"] == {"red": 1, "sub": None, "cancel": False} # [쉬프트] 붉은 강조 그대로
def test_파이썬_거울_곡선_자리_R_L_미달(out):
base = out["mirror"]["base"]
policy = AlignmentPolicy.from_dict(base["policy"])
for case in out["mirror"]["cases"]:
got = build_alignment(
base_s=np.array([n["chainage_m"] for n in base["basePvi"]]),
base_z=np.array([n["elevation_m"] for n in base["basePvi"]]),
chainage=np.array(base["chainage"]),
ground=np.array(base["ground"]),
stations=base["stations"],
policy=policy,
edits=case["edits"],
)
py = [[c["chainage_m"], c["r_m"], c["l_m"]] for c in got["curves"]]
ts = case["curves"]
assert [c[0] for c in py] == pytest.approx([c[0] for c in ts], abs=1e-6), case["name"]
assert [c[1] for c in py] == pytest.approx([c[1] for c in ts], rel=1e-6), case["name"]
assert [c[2] for c in py] == pytest.approx([c[2] for c in ts], abs=1e-6), case["name"]
assert not [w for w in got["warnings"] if "미달" in w], case["name"]
def test_배선():
panel = _read("B05_Profile/B05_Profile_UI_Profile_Panel_Tools.ts")
assert "tools.handleStationPick(station ?? chainageM)" in panel
assert "straightSubRun(alignment, fromM, toM)" in panel
assert "straightenBetween(base, ctx.edits(), fromM, toM, ctx.savedRadii())" in panel
assert "autoRadii: ctx.savedRadii" in panel
assert 'window.addEventListener("keydown", onEscape, true)' in panel
assert "createRangeBandView(" in panel
view = _read("A00_Common/design/design_profile_free_view.ts")
assert "appendRangeBand(svg, x, heightPx" in view
css = _read("B05_Profile/B05_Profile_UI_Style_Table.css")
rule = css[css.index(".b05-profile-freemark__span {") :]
rule = rule[: rule.index("}")]
assert "--color-primary" in rule and "--color-surface" in rule # 밝음 · 어두움 토큰
def test_줄_수():
for relative in (
"B05_Profile/B05_Profile_UI_Profile_Panel_Tools.ts",
"B05_Profile/B05_Profile_UI_Profile_Tools.ts",
"B05_Profile/logic/B05_Profile_UI_Profile_Straighten.ts",
"A00_Common/design/logic/design_profile_free.ts",
"A00_Common/design/design_profile_free_view.ts",
"A00_Common/design/design_profile_range_band.ts",
):
assert len(_read(relative).splitlines()) <= 700