Files
Aislo/resources/tester/test_598_1_plan_select_layers.py
T
eomsangdonandClaude Opus 5.5 ed7f7fa674 feat(D 예정노선): 노드 판 — 방식 · 값 고침(앞 노드 기준 다시 놓음 · 뒤 노드 그대로 · 어긋남 ⚠) · [지점으로] · [다시 긋기] 이음 (PLAN 598-4 · 요구 463-11 · 464)
- logic/Select placeNode — 지점 자리 노드면 지점 같이 · 첫 노드 = 까닭 글
- UI_Props 노드 판 = 599-1 방식 칸(createDrawKindFields) · 방식 고름 = 바로 · 수 칸 = Enter · 칸 떠날 때
- 판 · 메뉴 [다시 긋기] = draw.respan(599-3)
- 시험 598_1 placeNode 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:02:48 +09:00

151 lines
7.6 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.
"""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]),
placed: (() => {
const out = sel.placeNode(n, [[100, 0], [9, 9]], 0, 1, { kind: "length", length_m: 50, slope_pct: null }, (x) => x * 0.1);
return [out.network.lines[0].vertices, out.network.lines[0].nodes, out.points];
})(),
placeFirst: sel.placeNode(n, [], 0, 0, { kind: "free", length_m: null, slope_pct: null }, () => 0),
nodes594: (() => {
const r = { kind: "length", length_m: 100, slope_pct: null };
const withNodes = { ...n, lines: n.lines.map((l, i) => (i ? l : { ...l, nodes: [null, r, r, r] })) };
const out = sel.removeSegment(withNodes, seg("main", 1));
return out.lines.filter((l) => l.id !== "branch-1").map((l) => [l.id, l.nodes]);
})(),
}));
"""
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]]
vertices, nodes, points = got["placed"]
assert vertices == [[0, 0], [50, 0], [200, 0], [300, 0]] # 앞 노드 기준 50 m · 뒤 노드 그대로
assert nodes[1] == {"kind": "length", "length_m": 50, "slope_pct": None} and nodes[0] is None
assert points == [[50, 0], [9, 9]] # 그 자리 지점 같이
assert got["placeFirst"] == "첫 노드는 방식이 없습니다."
rec = {"kind": "length", "length_m": 100, "slope_pct": None}
assert got["nodes594"] == [
["main", [None, rec]],
["line-2", [None, rec]],
] # 노드 기록 같이 · 뒤쪽 첫 = 없음