Files
Aislo/resources/tester/test_430_route_edit_rules.py
T
eomsangdonandClaude Opus 5.5 92e5157890 fix(453 B05 평면 편집): 노드 가운데만 노드 우선 · 손잡이 끌기 측점 맞춤 · 막힌 걸음 놓음 되돌림 · 스냅만 바꾼 초안
- 433-14 ③ · 433-21 ③ 누름 · 맴돔 · 커서 한 판정(pressWinner) — 노드 가운데 6 px = 노드 · 밖 = 마커 · 노드 · 손잡이 중 가까운 것
- 433-21 ① 손잡이 끌기 = 노드 갈래와 같게 — 맞추기 전 자리에서 셈 · 미리보기 맞춤 · 측점 밖이 늘면 그 걸음 되돌림
- 433-14 ① 막힌 걸음만 하고 놓으면 미리보기 맞춤을 누르기 전 자리로
- 433-14 ② 사진첩 다리도 스냅 · 분석 지형 바뀜을 봄 — 노선이 처음으로 돌아가도 초안
- 433-1 test_365 ⑧ 이름 = 노선 한 걸음 + 계획고 맞춤
- 시험 test_453_plan_pick_draft 새로 · 390_5 손잡이 훑기 · 381_3 · 399 · 409 · 430 배선 글 맞춤

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

192 lines
8.1 KiB
Python

"""430 평면 형상 제어 규칙 한 곳 — 하한 검사(`applyEdit` 한 곳 · 끼우기 · 지우기 · 손잡이) ·
곡선 삭제 155° 경고 · 놓을 때 맞춤 보고 · 스냅 끔 · 간격 즉시 계산 줄 · R · 길이 고정 ·
넣은 R 곡선표 저장(짝) · 겹친 노드 · 못 그린 곡선 표시.
TS 셈은 Node(`helper_430_route_edit_rules.cjs`) · 짝 = 파이썬 `_write_planned_polyline` 이 쓴
곡선표를 TS `flattenServerPlan` 이 펴서 넣은 값이 돌아오는가.
./venv/Scripts/python.exe -m pytest resources/tester/test_430_route_edit_rules.py -q
"""
import csv
import json
import subprocess
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from B05_Profile import B05_Profile_Router_Replan as replan # noqa: E402
from B05_Profile.B05_Profile_Route_NodeEdits import node_edits # noqa: E402
from common_util.common_util_route_polyline import build_planned_polyline # noqa: E402
B05 = ROOT / "B05_Profile"
HELPER = Path(__file__).with_name("helper_430_route_edit_rules.cjs")
# IP2 뒤 직선 16 m — 넣은 R 200 은 접선 자리에 눌려 그려짐 · IP3 바로 뒤 0.3 m 겹친 점
NODES = [(0.0, 0.0), (120.0, 0.0), (220.0, 70.0), (236.0, 70.0), (236.2, 70.2), (340.0, 0.0)]
FLAGS = [True, True, True, False, True, True]
RADII = [None, 40.0, 200.0, None, None, None]
LOCKS = [None, "radius", "arc", None, "radius", "bogus"]
ARCS = [None, None, 33.5, None, None, None]
@pytest.fixture(scope="module")
def saved(tmp_path_factory) -> dict:
path = tmp_path_factory.mktemp("route") / "planned_route.csv"
replan._write_planned_polyline(
path,
NODES,
30.0,
simplify=False,
curve_flags=FLAGS,
radii=RADII,
locks=LOCKS,
arcs=ARCS,
)
with replan._nodes_path(path).open(encoding="utf-8") as handle:
nodes = [(float(row["x"]), float(row["y"])) for row in csv.DictReader(handle)]
outline = build_planned_polyline(nodes, min_radius_m=30.0, simplify=False)
return {
"path": path,
"nodes": [node.as_dict() for node in outline.nodes],
"curves": replan._read_curves(path),
"edits": replan._read_curves(path, "edits"),
}
@pytest.fixture(scope="module")
def got(saved, tmp_path_factory) -> dict:
server = tmp_path_factory.mktemp("server") / "plan.json"
payload = {key: saved[key] for key in ("nodes", "curves", "edits")}
server.write_text(json.dumps(payload), encoding="utf-8")
run = subprocess.run( # noqa: S603 — 고정 실행 파일
["node", str(HELPER), str(server)],
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert run.returncode == 0, run.stderr
return json.loads(run.stdout)
# ── 하한 검사 한 곳 ──────────────────────────────────────────────────────────
def test_옮겨_새로_깨면_까닭(got) -> None:
assert got["moveBreak"].startswith("하한에 걸려 되돌렸습니다 — IP1")
assert "(곡선반지름 20m)" in got["moveBreak"]
assert got["moveOk"] == ""
def test_끼우기_새로_깨면_까닭_지우기_넉넉하면_통과(got) -> None:
assert "IP1" in got["insertBreak"] and "IP2" in got["insertBreak"]
assert got["deleteOk"] == ""
assert got["manyGap"] == "" # 둘 넘게 다르면 안 견줌
def test_이미_밑돈_자리는_새로가_아님(got) -> None:
assert got["alreadyBroken"] == ""
# ── 겹친 노드 · 못 그린 곡선 ─────────────────────────────────────────────────
def test_겹친_노드_못_그린_곡선(got) -> None:
assert got["flags"] == {"overlap": [2], "undrawn": [1, 3]}
assert got["flagsOff"] == {"overlap": [], "undrawn": []} # 곡선 끈 자리는 「못 그림」 아님
assert "겹친 노드 IP2(0.5 m 안 · 선에서 빠짐)" in got["summary"]
assert "못 그린 곡선 IP1 · IP3" in got["summary"]
def test_놓을_때_손으로_옮긴_IP_는_밀림에_안_셈(got) -> None:
assert got["noteHand"] == " · 함께 밀린 교점 1곳"
assert got["noteInput"] == " · 함께 밀린 교점 2곳"
# ── 넣은 R · 고정 곡선표 저장(짝) ────────────────────────────────────────────
def test_곡선표_edits_는_저장_노드와_자리가_같음(saved) -> None:
assert len(saved["edits"]) == len(saved["nodes"]) == len(NODES) - 1 # 겹친 점 하나 빠짐
assert saved["edits"][2] == {"curve": True, "radius_m": 200.0, "lock": "arc", "arc_m": 33.5}
assert saved["edits"][1]["lock"] == "radius"
assert saved["edits"][3]["curve"] is False
assert saved["edits"][4]["lock"] is None # 모르는 고정 값은 버림
def test_다시_열면_넣은_R_고정이_돌아옴(got, saved) -> None:
flat = got["flat"]
drawn = next(c["radius_m"] for c in saved["curves"] if c["node_first"] == 2)
assert drawn < 200 - 1 # 그린 R 은 눌림
assert flat["curveRadius"][1:3] == [40, 200] # 넣은 값
assert flat["curveLock"][1:3] == ["radius", "arc"]
assert flat["curveArc"][2] == 33.5
assert flat["curveOn"][3] is False
assert got["flatOld"]["curveRadius"][2] == pytest.approx(drawn, abs=0.01) # 옛 곡선표 = 그린 R
def test_node_edits_짧은_칸은_기본값() -> None:
edits = node_edits([(0, 0), (10, 0), (20, 5)], None, [None, 15.0])
assert edits[1] == {"curve": True, "radius_m": 15.0, "lock": None, "arc_m": None}
assert edits[2] == {"curve": True, "radius_m": None, "lock": None, "arc_m": None}
def test_편집값_없이_쓴_곡선표는_edits_없음(tmp_path) -> None:
path = tmp_path / "planned_route.csv"
replan._write_planned_polyline(path, NODES, 30.0, simplify=False)
assert replan._read_curves(path, "edits") == []
# ── 배선 ────────────────────────────────────────────────────────────────────
def _read(name: str) -> str:
return (B05 / name).read_text(encoding="utf-8")
def test_편집기_배선() -> None:
main = _read("B05_Profile_UI_RouteEdit.ts")
apply_edit = main.split("function applyEdit(", 1)[1][:700]
assert "edits.floorStop(before, planned, nodeInfo, fitLimits())" in apply_edit
assert "restoreSnapshot(before, stop, false)" in apply_edit
assert "curveBar.noteFit(snap.text(), before, index)" in main # 놓을 때 맞춤 보고
snap = main.split("bindStationSnap(chrome", 1)[1][:400]
assert "tellDraft();" in snap # 끔 · 간격 바꿈 즉시 계산 줄
assert (
"lineChanged(history) || otherChanged()" in main.split("function tellDraft()", 1)[1][:400]
)
assert (
"const otherChanged = (): boolean => pickedTerrain() !== designTerrain || snap.changed();"
in main
) # 433-14 ②
assert "plan.node_edits" in main
assert "nodes: () => ({ planned, curveOn, curveRadius, curveLock, curveArc })" in main
assert len(main.splitlines()) <= 695
def test_손잡이_끌기_하한_검사() -> None:
pointer = _read("B05_Profile_UI_RouteEdit_Pointer.ts")
handle = pointer.split("// 곡선 시작·끝점을 끈다", 1)[1][:2200]
assert "shortfallCrossed(" in handle
assert "params.commit(dragHandle?.node ?? dragNode)" in pointer
def test_곡선_삭제_155_경고() -> None:
bar = _read("B05_Profile_UI_RouteEdit_CurveBar.ts")
on = bar.split("onCurveOn:", 1)[1][:900]
assert "!curveOptionalAt(inner)" in on and '"warning"' in on
def test_저장에_고정_실음_읽기에_node_edits() -> None:
apply = _read("B05_Profile_UI_RouteEdit_Apply.ts")
assert "nodes.curveLock?.[index] ?? null" in apply
assert "nodes.curveArc?.[index] ?? null" in apply
router = _read("B05_Profile_Router_Replan.py")
assert '"node_edits":' in router
assert "locks=list(draft.curveLock) if draft else None" in router