- design_pipeline_page follow 되살림 차례 = 계획고 사진 + 가게 관 · 구조물 · 추가 측점(묶음 측점 유령 · 빠짐 없앰) - Section_Store 가 노선 초안 동안 상세 틀을 정해 넘김 · Drafts = 옛 틀이면 저장분으로 맞춤 · 얹음 · 새 틀이면 저장분 종류 대조 안 함 - 새 시험 test_280_3_route_undo_facilities(헬퍼 둘 · 검증_sub4 읽기만) · 옛 코드에서 되돌리기 넷 실패 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
184 lines
7.0 KiB
Python
184 lines
7.0 KiB
Python
"""요구 280-3 — 노선 초안 중 ↶ · ↷ = 관 · 구조물 · 추가 측점도 그 걸음 사진(묶음 입력까지).
|
|
|
|
· 옛 흠 — ↶ 차례 follow 가 계획고 사진만 내고 시설은 안 내 묶음 측점이 마지막 끌기 누가거리에 남음
|
|
(구조물 없는 구조물 측점 · 측점 없는 구조물) · 끌기 사이 손 옮김이 있으면 맞춤도 건너뛰어 오래 남음
|
|
· 잼 — ↶ · ↷ 첫 차례 · 조용해진 뒤 묶음 측점 시설 · 초안 상세 비정규 측점 = 측점 계획(가게 값) · 유령 0 · 빠짐 0
|
|
· 계획고 사진(356-4 ▲)은 그대로
|
|
짝: `helper_280_3_route_undo_facilities.cjs`(판 = 356-4 · 실제 TS 를 Node 로 · 화면 부품 · 서버 창구만 가짜).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import subprocess
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
import test_349_0q_undo_echo as base
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
MODES = ("structure", "pipe", "plus", "none")
|
|
PROJECT = base.ROOT / "storage/1/3/c725d747-50ae-46b0-87ce-4d4e56a53cf3" # 검증_sub4(읽기만)
|
|
ROUTE = PROJECT / "B05_Profile/route"
|
|
SECTION = PROJECT / "B06_Section"
|
|
GRID = PROJECT / "B04_PreProcess/models/dtm_classification_smooth.npz"
|
|
|
|
|
|
def build_job() -> dict:
|
|
"""입력 한 벌 — 349-0q 꼴 · 검증_sub4 초기 노선 · 구조물 둘(점 · 구간)."""
|
|
if not (GRID.is_file() and (SECTION / "longitudinal/longitudinal.json").is_file()):
|
|
pytest.skip("검증_sub4 없음")
|
|
curves = base._json(ROUTE / "planned_route_initial_curves.json")
|
|
sections = sorted(
|
|
(base._json(p) for p in (SECTION / "cross_sections").glob("cross_*.json")),
|
|
key=lambda s: s["chainage_m"],
|
|
)
|
|
for i, section in enumerate(sections):
|
|
section["design"] = {
|
|
"ground_type": "soil",
|
|
"design_elevation_m": section["center_z"],
|
|
"cut_area_m2": 4.0 + (i % 5),
|
|
"fill_area_m2": 1.5 + (i % 3),
|
|
}
|
|
pipes = [
|
|
{"id": f"p{n}", "source": "manual", "facility": "pipe", **base._between(sections, n)}
|
|
for n in (15, 20)
|
|
]
|
|
point = base._between(sections, 25)
|
|
a, b = base._between(sections, 30), base._between(sections, 31)
|
|
return {
|
|
"line": base._csv(ROUTE / "planned_route_initial.csv"),
|
|
"nodes": base._csv(ROUTE / "planned_route_initial_nodes.csv"),
|
|
"curves": [
|
|
{k: c[k] for k in ("start", "apex", "end", "radius_m")} for c in curves["curves"]
|
|
],
|
|
"stationSnap": bool(curves.get("station_snap")),
|
|
"minRadius": curves["min_radius_m"],
|
|
"interval": 20.0,
|
|
"types": [
|
|
{"type_id": t.type_id, "group": t.group, "placement": t.placement}
|
|
for t in base.structure_type_map().values()
|
|
],
|
|
"pipes": pipes,
|
|
"standard": base.STANDARD_CROSS_SECTION,
|
|
"conversion": base.ONES,
|
|
"structures": [
|
|
{
|
|
"structure_id": "s-2803a",
|
|
"type_id": "cross_underdrain",
|
|
"placement": "point",
|
|
**point,
|
|
},
|
|
{
|
|
"structure_id": "s-2803b",
|
|
"type_id": "retaining_wall",
|
|
"placement": "interval",
|
|
"start_m": a["chainage_m"],
|
|
"chainage_m": round((a["chainage_m"] + b["chainage_m"]) / 2, 2),
|
|
"end_m": b["chainage_m"],
|
|
"x": (a["x"] + b["x"]) / 2,
|
|
"y": (a["y"] + b["y"]) / 2,
|
|
},
|
|
],
|
|
"touched": {},
|
|
"detail": {
|
|
"longitudinal": base._json(SECTION / "longitudinal/longitudinal.json"),
|
|
"cross_sections": sections,
|
|
},
|
|
}
|
|
|
|
|
|
def _run(job: dict, mode: str) -> dict:
|
|
with tempfile.TemporaryDirectory() as folder:
|
|
path = Path(folder) / "job.json"
|
|
path.write_text(json.dumps(job), encoding="utf-8")
|
|
grid = Path(folder) / "grid.bin"
|
|
grid.write_bytes(base.dtm_grid_bytes(GRID))
|
|
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
|
|
[
|
|
"node",
|
|
str(HERE / "helper_280_3_route_undo_facilities.cjs"),
|
|
str(path),
|
|
str(grid),
|
|
mode,
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
timeout=300,
|
|
)
|
|
assert proc.returncode == 0, proc.stderr
|
|
return json.loads(proc.stdout)
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def runs() -> dict:
|
|
job = build_job()
|
|
# 추가 측점 — 끌면 밀리는 뒤쪽 두 측점 사이
|
|
sections = job["detail"]["cross_sections"]
|
|
job["plusAt"] = (sections[-5]["chainage_m"] + sections[-4]["chainage_m"]) / 2
|
|
return {mode: _run(job, mode) for mode in MODES}
|
|
|
|
|
|
def _clean(m: dict) -> None:
|
|
assert m["ghost"] == [] and m["missing"] == [], m
|
|
assert m["detailGhost"] in ([], None) and m["detailMissing"] in ([], None), m
|
|
|
|
|
|
@pytest.mark.parametrize("mode", MODES)
|
|
def test_되돌리기_다시하기_시설_측점_같음(runs, mode):
|
|
r = runs[mode]
|
|
assert r["moved"]["want"] != r["drag2"]["want"] or mode == "none" # 끌기 2 가 시설을 옮김
|
|
for key in ("undo", "redo"):
|
|
_clean(r[key]["settled"])
|
|
_clean(r[key]["first"]) # 맞춤 한 바퀴 전 첫 차례부터
|
|
assert r[key]["settled"]["detailGhost"] is not None # 노선 초안 그대로(초안 상세를 잼)
|
|
assert r["undo"]["settled"]["want"] == r["moved"]["want"] # ↶ = 끌기 2 전 시설 자리
|
|
assert r["redo"]["settled"]["want"] == r["drag2"]["want"]
|
|
assert r["blocked"] == 0
|
|
|
|
|
|
@pytest.mark.parametrize("mode", MODES)
|
|
def test_끌기_손옮김_사이_측점_같음(runs, mode):
|
|
r = runs[mode]
|
|
for key in ("open", "drag1", "lift", "moved", "drag2"):
|
|
_clean(r[key])
|
|
|
|
|
|
def test_계획고_사진_그대로(runs):
|
|
r = runs["structure"]
|
|
assert r["undo"]["settled"]["offs"] == r["moved"]["offs"] # ▲ 남음(356-4)
|
|
assert r["redo"]["settled"]["offs"] == r["drag2"]["offs"]
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def frames() -> dict:
|
|
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
|
|
["node", str(HERE / "helper_280_3_draft_frame.cjs")],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
timeout=120,
|
|
)
|
|
assert proc.returncode == 0, proc.stderr
|
|
return json.loads(proc.stdout)
|
|
|
|
|
|
def test_옛_틀_상세는_저장분으로_맞춤(frames):
|
|
r = frames["old_old"] # 새로고침 첫 화면 · 묶음 갈아끼움 전
|
|
assert r["hidden"] == [] and r["preview"] == []
|
|
assert r["names"] == {"100": "맹암거", "153": "옹벽", "160": "옹벽", "167": "옹벽"}
|
|
old = frames["old_null"]
|
|
assert (
|
|
old["hidden"] and "180" in old["names"]
|
|
) # 옛 동작 = 숨김 · 남의 자리 이름(시험이 흠을 잼)
|
|
|
|
|
|
def test_새_틀_상세는_저장분_종류와_안_견줌(frames):
|
|
r = frames["new_new"] # 묶음 갈아끼움 뒤
|
|
assert r["hidden"] == [] and r["preview"] == []
|
|
assert r["names"]["120"] == "맹암거"
|
|
assert frames["new_null"]["preview"] == [120] # 옛 동작 = 구조물 없는 사본
|