- 새 logic/D03_PlanAnalysis_Select.ts — 토막 판정 · 끌어 고르기 토막 · 토막 지움(가운데 = 구역 둘 · 뒤쪽 새 구역) · 전체 지움 · 지점 ↔ 노드 같은 자리 - 새 UI_Select.ts — 고름 상태(다시 누름 = 한 단 넓힘 · 같은 해 = 왼쪽 해 줄로만) - UI_Edit.ts — 우클릭 즉시 지움 걷음 · 노드 끌면 지점 같이 · 지점 12 px 안 붙음 · 지움 창구(dropNode · dropRegion · dropAll · splitAt) - UI_Props.ts — 판 = 노드 · 선 · 구역 · 전체 · 구간 · 마커 몸 · [방향 바꾸기] · [갈래 시점 지정] 걷음 - 새 UI_Menu.ts — 우클릭 메뉴(Points 에서 옮김 · 노드 지움 · 지점으로 · 연도 구역 나눔) - UI_Points.ts — 핀 놓을 때 노드 12 px 안 붙음(nodeNear) - key_registry plan 줄 · 시험 585_2 갈래 시점 지정 줄 지움(요구 463-15 걷음) · 553_4 메뉴 자리 · 새 시험 598_1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
130 lines
6.3 KiB
Python
130 lines
6.3 KiB
Python
"""598-1 예정노선 고름 층 노드 → 선(토막) → 구역 → 전체 · 토막 지움(요구 463 9 · 12 · 13·16 · 15)
|
||
— TS `logic/D03_PlanAnalysis_Select.ts`.
|
||
|
||
끝 토막 지움 = 끝 점 뺌 · 가운데 토막 지움 = 구역 둘(뒤쪽 = 새 구역 · 갈래 고리 · 조각 따라감) ·
|
||
사라지는 노드에 갈래 = 까닭 글 · 끌어 고르기 토막 판정 · 지점 ↔ 노드 같은 자리.
|
||
|
||
./venv/Scripts/python.exe -m pytest resources/tester/test_598_1_plan_select_layers.py -q
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import re
|
||
import subprocess
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
TS_FILE = ROOT / "D03_PlanAnalysis" / "logic" / "D03_PlanAnalysis_Select.ts"
|
||
TSC = ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc"
|
||
|
||
_RUNNER = """
|
||
import { writeFileSync } from "node:fs";
|
||
import * as plan from "./D03_PlanAnalysis/logic/D03_PlanAnalysis_Plan.js";
|
||
import * as sel from "./D03_PlanAnalysis/logic/D03_PlanAnalysis_Select.js";
|
||
const Y = 2026;
|
||
// 본선 (0,0)→(100,0)→(200,0)→(300,0) · 마크 150 → p1 [0,150] p2 [150,300] · 갈래 at 노드 2 (200,0)→(200,50)
|
||
let n = plan.newLine(plan.emptyNetwork(), [0, 0]);
|
||
for (const x of [100, 200, 300]) n = plan.appendVertex(n, 0, [x, 0]);
|
||
n = plan.normalizePieces(plan.splitPiece(plan.normalizePieces(n, Y), "main", 150), Y);
|
||
n = plan.normalizePieces(plan.setPiecesYear(n, ["p2"], 2028), Y);
|
||
n = plan.normalizePieces(plan.appendVertex(plan.addBranch(n, 0, 2), 1, [200, 50]), Y);
|
||
const show = (net) => typeof net === "string" ? net : ({
|
||
lines: net.lines.map((l) => [l.id, l.vertices, l.branch]),
|
||
pieces: plan.normalizePieces(net, Y).pieces.map((p) => [p.id, p.line, p.from_m, p.to_m, p.year]),
|
||
});
|
||
const seg = (line, index) => ({ line, index });
|
||
const view = ([x, y]) => [x, -y];
|
||
writeFileSync(process.argv[2], JSON.stringify({
|
||
segs: sel.allSegments(n),
|
||
range: sel.segmentRange(n, seg("main", 1)),
|
||
at: [sel.segmentAt(n.lines[0].vertices, 0), sel.segmentAt(n.lines[0].vertices, 100), sel.segmentAt(n.lines[0].vertices, 300)],
|
||
nodeSeg: [sel.nodeSegment(n, { line: "main", vertex: 1 }), sel.nodeSegment(n, { line: "main", vertex: 3 })],
|
||
pieces: sel.segmentPieces(n, [seg("main", 1)]).sort(),
|
||
middle: show(sel.removeSegment(n, seg("main", 0 + 1))),
|
||
tail: show(sel.removeSegment(n, seg("main", 2))),
|
||
head: show(sel.removeSegment(n, seg("main", 0))),
|
||
only: show(sel.removeSegment(n, seg("branch-1", 0))),
|
||
parentOnly: sel.removeSegments(n, [seg("main", 0), seg("main", 1), seg("main", 2)]),
|
||
many: show(sel.removeSegments(n, [seg("main", 0), seg("main", 2)])),
|
||
box: sel.segmentsInBox(n, view, { x0: 90, y0: -10, x1: 210, y1: 10 }, false),
|
||
cross: sel.segmentsInBox(n, view, { x0: 150, y0: 10, x1: 120, y1: -10 }, true),
|
||
all: show(sel.removeAll(n)),
|
||
nodes: sel.nodesAt(n, [200, 0]),
|
||
moved: sel.movePointsAt([[200, 0], [5, 5]], [200, 0], [210, 5]),
|
||
}));
|
||
"""
|
||
|
||
|
||
def _run_ts(tmp_path: Path) -> dict:
|
||
out = tmp_path / "js"
|
||
subprocess.run( # noqa: S603 — 고정 실행 파일
|
||
["node", str(TSC), str(TS_FILE), "--outDir", str(out), "--rootDir", str(ROOT),
|
||
"--module", "esnext", "--target", "es2022", "--moduleResolution", "bundler",
|
||
"--ignoreConfig"],
|
||
cwd=str(ROOT), check=True, capture_output=True,
|
||
) # fmt: skip
|
||
for emitted in out.rglob("*.js"):
|
||
emitted.write_text(
|
||
re.sub(r'(from\s+"\.\.?/[^"]+)"', r'\1.js"', emitted.read_text(encoding="utf-8")),
|
||
encoding="utf-8",
|
||
)
|
||
(out / "runner.mjs").write_text(_RUNNER, encoding="utf-8")
|
||
result = tmp_path / "out.json"
|
||
subprocess.run( # noqa: S603
|
||
["node", str(out / "runner.mjs"), str(result)],
|
||
cwd=str(ROOT),
|
||
check=True,
|
||
capture_output=True,
|
||
)
|
||
return json.loads(result.read_text(encoding="utf-8"))
|
||
|
||
|
||
def test_토막_자리(tmp_path: Path) -> None:
|
||
got = _run_ts(tmp_path)
|
||
assert [s["line"] for s in got["segs"]] == ["main"] * 3 + ["branch-1"]
|
||
assert got["range"] == [100, 200]
|
||
assert got["at"] == [0, 1, 2] # 꺾임점 위 = 뒤 토막 · 끝 = 마지막
|
||
assert got["nodeSeg"] == [{"line": "main", "index": 1}, {"line": "main", "index": 2}]
|
||
assert got["pieces"] == ["p1", "p2"] # 100~200 토막에 마크 150 = 두 구간
|
||
|
||
|
||
def test_가운데_토막_지움_구역_둘(tmp_path: Path) -> None:
|
||
got = _run_ts(tmp_path)
|
||
lines = {i: (v, b) for i, v, b in got["middle"]["lines"]}
|
||
assert lines["main"] == ([[0, 0], [100, 0]], None) # 앞쪽 = 같은 id
|
||
back = next(k for k in lines if k not in ("main", "branch-1"))
|
||
assert lines[back] == ([[200, 0], [300, 0]], None) # 뒤쪽 = 새 구역(본선)
|
||
assert lines["branch-1"][1] == {"line": back, "vertex": 0} # 갈래 고리가 따라감
|
||
pieces = {(p[1], p[2], p[3]): (p[0], p[4]) for p in got["middle"]["pieces"]}
|
||
assert pieces[("main", 0, 100)] == ("p1", 2026) # 앞 조각 id 지님
|
||
assert pieces[(back, 0, 100)][1] == 2028 # 뒤 조각 해 그대로
|
||
|
||
|
||
def test_끝_토막_지움_끝_점_뺌(tmp_path: Path) -> None:
|
||
got = _run_ts(tmp_path)
|
||
tail = {i: v for i, v, _ in got["tail"]["lines"]}
|
||
assert tail["main"] == [[0, 0], [100, 0], [200, 0]]
|
||
head = {i: (v, b) for i, v, b in got["head"]["lines"]}
|
||
assert head["main"][0] == [[100, 0], [200, 0], [300, 0]]
|
||
assert head["branch-1"][1] == {"line": "main", "vertex": 1} # 갈래 차례 −1
|
||
assert [p[2:4] for p in got["head"]["pieces"] if p[1] == "main"] == [[0, 50], [50, 200]]
|
||
|
||
|
||
def test_갈래_붙은_노드_막음_하나뿐_구역_지움(tmp_path: Path) -> None:
|
||
got = _run_ts(tmp_path)
|
||
only = got["only"]["lines"]
|
||
assert [i for i, _, _ in only] == ["main"] # 토막 하나뿐 = 구역 지움
|
||
assert got["parentOnly"] == "갈래가 붙은 점이 사라집니다 — 갈래를 먼저 지우세요."
|
||
many = {i: v for i, v, _ in got["many"]["lines"]}
|
||
assert many["main"] == [[100, 0], [200, 0]] # 뒤부터 지움 = 차례 안 꼬임
|
||
|
||
|
||
def test_끌어_고르기_전체_지점_노드(tmp_path: Path) -> None:
|
||
got = _run_ts(tmp_path)
|
||
assert got["box"] == [{"line": "main", "index": 1}] # 창 = 두 끝 모두 안
|
||
assert got["cross"] == [{"line": "main", "index": 1}] # 걸침 = 상자와 만남
|
||
assert got["all"] == {"lines": [], "pieces": []}
|
||
assert got["nodes"] == [{"line": "main", "vertex": 2}] # 갈래 첫 점 뺌
|
||
assert got["moved"] == [[210, 5], [5, 5]]
|