- StationFit reach = intervalM(반 간격이면 MC 18+9 ~ 19+1 못 맞춤) - 끌기 · 방향키 — 미리보기 측점 밖 곡선 수가 누를 때보다 늘면 그 걸음 되돌림 · dragMoved 는 막힘 검사 뒤(433-14) - 계산 줄 stations 단계 · follow 새 측점 — 초안 없으면 저장 노선 station_snap · station_snap_step(snapOptions) - 시험 — 390_5 끌기 훑기(놓은 뒤 측점 밖 0 · 자동 · 고정 R) · 344_3 저장 스냅 · 266 사면 이동 한 걸음 · 다시 그림 한 번 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
140 lines
5.5 KiB
Python
140 lines
5.5 KiB
Python
"""PLAN 439 — B06 카드 머리 줄 단추가 저장 노선에서 늘 반영됨(요구 266).
|
|
|
|
못박는 것
|
|
- 저장 노선 cross 차례(`refreshSavedCross`) = 카드 자료(상세 설계)를 **늘** 다시 셈 —
|
|
복합경사 · 측구 · 지반 단추마다 그 측점 설계선 · 면적이 바뀜.
|
|
- 묶음 측점 = 상세 측점이면 돌려줌 · 1 mm 넘게 다르면 null(묶음 엔진) — 그래도 카드 자료는 고침.
|
|
- 사면 이동 한 걸음도 같음 · 걸음마다 받는 쪽(카드 · 종단 · 유토 다시 그림) 한 번(441 · 요구 268-2 · 4).
|
|
- 서버 폴백(선형 저장분 없음)에도 카드 선택이 실림(열쇠 = 측점 자리) · 서버는 저장분 위에 얹음.
|
|
짝: `helper_266_card_toggle.mjs` · 진입점 `helper_266_card_toggle_entry.ts` ·
|
|
지형 · 노선 틀 = `test_346_2_pipeline_cross_page.py`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
ROOT = HERE.parents[1]
|
|
for path in (ROOT, HERE):
|
|
if str(path) not in sys.path:
|
|
sys.path.insert(0, str(path))
|
|
|
|
from test_346_2_pipeline_cross_page import _fake, _line, _sections # noqa: E402
|
|
|
|
from B06_Section.B06_Section_Router import _with_design_choices # noqa: E402
|
|
from common_util.common_util_surface_sampler import DtmGridSampler # noqa: E402
|
|
from config.config_system import STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M # noqa: E402
|
|
from config.config_system_design import STANDARD_CROSS_SECTION # noqa: E402
|
|
|
|
pytestmark = pytest.mark.skipif(
|
|
not (ROOT / "config" / "node_modules" / "vite").is_dir(),
|
|
reason="프론트엔드 의존성 미설치",
|
|
)
|
|
|
|
CUT_DEPTH_M = 3.0
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def r(tmp_path_factory) -> dict:
|
|
work = tmp_path_factory.mktemp("b439")
|
|
build = subprocess.run( # noqa: S603 — 고정 실행 파일
|
|
[
|
|
"node",
|
|
str(ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js"),
|
|
"build",
|
|
"--configLoader",
|
|
"runner",
|
|
"--ssr",
|
|
"../resources/tester/helper_266_card_toggle_entry.ts",
|
|
"--outDir",
|
|
str(work),
|
|
"--logLevel",
|
|
"error",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
timeout=300,
|
|
)
|
|
assert build.returncode == 0, build.stderr
|
|
bundle = work / "bundle.mjs"
|
|
shutil.copy(work / "helper_266_card_toggle_entry.js", bundle)
|
|
with tempfile.TemporaryDirectory() as folder:
|
|
npz, nodes = _fake(Path(folder))
|
|
sampler = DtmGridSampler.from_npz(npz)
|
|
rows = _sections(_line(nodes), sampler, [])["cross_sections"]
|
|
middle = len(rows) // 2
|
|
picked = rows[middle - 1 : middle + 2]
|
|
sections = [{**s, "plan_z": float(s["center_z"]) - CUT_DEPTH_M} for s in picked]
|
|
job = {
|
|
"sections": sections,
|
|
"target": 1,
|
|
"mode": f"{picked[1].get('uphill_side') or 'left'}_cut",
|
|
"standard": STANDARD_CROSS_SECTION,
|
|
"rockDefault": STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M,
|
|
}
|
|
path = Path(folder) / "job.json"
|
|
path.write_text(json.dumps(job), encoding="utf-8")
|
|
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
|
|
["node", str(HERE / "helper_266_card_toggle.mjs"), str(bundle), str(path)],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
timeout=120,
|
|
)
|
|
assert proc.returncode == 0, proc.stderr
|
|
return json.loads(proc.stdout)
|
|
|
|
|
|
def _check_toggles(case: dict, returned) -> None:
|
|
steps = {step["tag"]: step for step in case["steps"]}
|
|
assert steps["open"]["cut"] > 0.5 # 깎기 측점
|
|
before = steps["open"]
|
|
assert all(step["calls"] == 1 for step in case["steps"]) # 걸음마다 다시 그림 한 번(441)
|
|
for tag in ("two_stage", "ditch_off", "soil", "slope"):
|
|
step = steps[tag]
|
|
assert case["target"] in step["updated"], tag # 카드 다시 그림 대상
|
|
assert step["line"] != before["line"], tag # 카드 설계선 바뀜
|
|
assert step["out"] == returned, tag
|
|
before = step
|
|
assert steps["two_stage"]["two_stage_slope"] is True
|
|
assert steps["ditch_off"]["ditch_enabled"] is False
|
|
assert steps["soil"]["ground_type"] == "soil"
|
|
assert steps["soil"]["cut"] != steps["open"]["cut"] # 면적 바뀜
|
|
|
|
|
|
def test_측점_같음_카드_단추마다_설계선_면적_바뀜_돌려줌(r):
|
|
_check_toggles(r["same"], 3)
|
|
|
|
|
|
def test_측점_어긋남_카드_자료는_고침_돌려줄_값만_null(r):
|
|
_check_toggles(r["mismatch"], None)
|
|
|
|
|
|
def test_서버_폴백에_카드_선택_실음(r):
|
|
key = f"{r['server']['at']:.3f}"
|
|
choice = (r["server"]["choices"] or {}).get(key)
|
|
assert choice is not None, r["server"]
|
|
assert choice["ground_type"] == "soil"
|
|
assert choice["two_stage_slope"] is True
|
|
assert choice["ditch_choice"] is False
|
|
|
|
|
|
def test_서버_카드_선택이_저장분_위에_얹힘():
|
|
stored = [{"chainage_m": 40.0, "design": {"ground_type": "ripping_rock", "berm": {"w": 1}}}]
|
|
rows = _with_design_choices(stored, {"40.000": {"ground_type": "soil"}, "60.123": {"x": 1}})
|
|
by_key = {round(float(r["chainage_m"]), 3): r["design"] for r in rows} # 서버 읽는 꼴
|
|
assert by_key[40.0] == {"ground_type": "soil", "berm": {"w": 1}}
|
|
assert by_key[60.123] == {"x": 1}
|
|
assert _with_design_choices(stored, None) == stored
|