- 도구줄 「연도 마커」(공용 지도 도구줄 year-mark · 깃발 아이콘)를 선 위로 끌어 놓음 = 그 자리 구간을 둘로 — 구역만 나눔 · 해 그대로 - 끄는 동안 마름모 미리보기 · 선 끝 · 다른 마커 1 m 안 · 선 밖 = 경고 색 · 못 놓음 - 놓인 마커 끌기 = 경계 옮김 · 선 밖으로 끌어냄 · 오른쪽 누름 · Delete = 지움(앞 구간과 합침) · 같은 해 양쪽 = 속 빈 마름모 - 좌측 [연도 구분] · 「긋는 해」 걷음 · 새 선 = 사업연도 시작 해 · 새 갈래 = 붙은 자리 구간 해 - 속성 판 [갈래 시점 지정] = 다른 선에 닿은 끝을 시점(No.0)으로 갈래 · 꼬리에 닿은 머리 = 한 선 · 본선 = [방향 바꾸기] - 시험: 585-2 시험에 나눔 · 합침 · 갈래 해 · 갈래 시점 덧 · 530-3 갈래 해 · 553-4 속성 판 · 568-1 도구줄 목록 맞춤 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
132 lines
6.7 KiB
Python
132 lines
6.7 KiB
Python
"""585-2 · 585-3 예정노선 끌어 고르기 판정 · 여러 구간 연도 · 연도 구역 마커 나눔 · 합침 · 갈래 시점 지정
|
|
(요구 445 A4~A6 · 446) — TS `logic/D03_PlanAnalysis_Plan.ts`.
|
|
|
|
창(오른쪽으로) = 구간 꺾은선 점이 모두 상자 안 · 걸침(왼쪽으로) = 점 하나라도 안 또는 토막이 상자 변과 만남 ·
|
|
돌린 화면도 보이는 px 로 셈 · 여러 구간 연도 한 번에.
|
|
|
|
./venv/Scripts/python.exe -m pytest resources/tester/test_585_2_plan_select.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_Plan.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";
|
|
const Y = 2026;
|
|
// 본선 (0,0)→(100,0)→(100,100) · 마크 50 · 150 → p1 [0,50] p2 [50,150] p3 [150,200] · 둘째 선 (200,0)→(300,0)
|
|
let n = plan.newLine(plan.emptyNetwork(), [0, 0]);
|
|
n = plan.appendVertex(plan.appendVertex(n, 0, [100, 0]), 0, [100, 100]);
|
|
n = plan.normalizePieces(n, Y);
|
|
n = plan.normalizePieces(plan.splitPiece(n, "main", 50), Y);
|
|
n = plan.normalizePieces(plan.splitPiece(n, "main", 150), Y);
|
|
n = plan.normalizePieces(plan.appendVertex(plan.newLine(n, [200, 0]), 1, [300, 0]), Y);
|
|
const view = ([x, y]) => [x, -y]; // 화면 y 아래로
|
|
const turned = ([x, y]) => [-y, -x]; // 90° 돌린 화면
|
|
const box = (x0, y0, x1, y1) => ({ x0, y0, x1, y1 });
|
|
const pick = (b, crossing, to = view) => plan.piecesInBox(n, to, b, crossing);
|
|
writeFileSync(process.argv[2], JSON.stringify({
|
|
pieces: n.pieces.map((p) => [p.id, p.line, p.from_m, p.to_m]),
|
|
window: pick(box(-10, -10, 60, 10), false),
|
|
crossing: pick(box(60, 10, -10, -10), true),
|
|
through: [pick(box(70, -5, 80, 5), true), pick(box(70, -5, 80, 5), false)],
|
|
wholeLine: pick(box(190, -5, 310, 5), false),
|
|
partLine: [pick(box(190, -5, 250, 5), false), pick(box(250, 5, 190, -5), true)],
|
|
all: pick(box(-10, -110, 310, 10), false),
|
|
rotated: pick(box(-110, -110, -40, -90), false, turned),
|
|
years: plan.setPiecesYear(n, ["p1", "p3"], 2028).pieces.map((p) => [p.id, p.year]),
|
|
...marks(),
|
|
}));
|
|
|
|
/** 585-3 — 마커 = 구역 나누기만(해 그대로) · 지움 = 앞 구간이 먹음 · 갈래 처음 해 · 갈래 시점 지정. */
|
|
function marks() {
|
|
const span = (net) => net.pieces.filter((p) => p.line === "main").map((p) => [p.id, p.from_m, p.to_m, p.year]);
|
|
let m = plan.newLine(plan.emptyNetwork(), [0, 0]);
|
|
m = plan.normalizePieces(plan.appendVertex(m, 0, [100, 0]), Y);
|
|
const cut = plan.normalizePieces(plan.splitPiece(m, "main", 40), Y);
|
|
const edge = plan.splitPiece(cut, "main", 40.5); // 다른 마커 1 m 안 = 그대로
|
|
const yeared = plan.setPiecesYear(cut, ["p2"], 2028);
|
|
const merged = plan.normalizePieces(plan.removeMark(yeared, "main", 40, 1e-3), Y);
|
|
// 갈래 처음 해 = 붙은 자리(60 m · p2 = 2028) 구간 해
|
|
let b = plan.normalizePieces(plan.insertVertex(yeared, 0, 0, [60, 0]), Y);
|
|
b = plan.normalizePieces(plan.appendVertex(plan.addBranch(b, 0, 1), 1, [60, 50]), Y);
|
|
// 갈래 시점 지정 — (60,90) 에서 그어 (60,0) 꺾임점에 닿은 본선 → 닿은 끝이 시점인 갈래
|
|
let t = plan.normalizePieces(plan.insertVertex(m, 0, 0, [60, 0]), Y);
|
|
t = plan.normalizePieces(plan.appendVertex(plan.newLine(t, [60, 90]), 1, [60, 0]), Y);
|
|
const branched = plan.branchFromEnd(t, 1);
|
|
const lone = plan.normalizePieces(plan.appendVertex(plan.newLine(m, [0, 50]), 1, [30, 80]), Y);
|
|
return {
|
|
cut: span(cut), edgeSame: edge === cut, merged: span(merged),
|
|
branchYear: b.pieces.filter((p) => p.line !== "main").map((p) => p.year),
|
|
branched: typeof branched === "string" ? branched : branched.lines.map((l) => [l.id, l.vertices[0], l.branch]),
|
|
again: typeof branched === "string" ? null : plan.branchFromEnd(branched, 1),
|
|
lone: plan.branchFromEnd(lone, 1),
|
|
};
|
|
}
|
|
"""
|
|
|
|
|
|
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)
|
|
ids = {p[0]: p[1:] for p in got["pieces"]}
|
|
assert ids["p1"] == ["main", 0, 50] and ids["p2"] == ["main", 50, 150]
|
|
other = next(k for k, v in ids.items() if v[0] != "main")
|
|
assert got["window"] == ["p1"] # p2 는 (100, 100) 쪽이 상자 밖
|
|
assert got["crossing"] == ["p1", "p2"] # 왼쪽으로 = p2 앞 끝이 상자 안
|
|
assert got["through"] == [["p2"], []] # 점 없이 토막만 지남 = 걸침만
|
|
assert got["wholeLine"] == [other] # 마커 없는 선 = 구간 하나 = 선 통째
|
|
assert got["partLine"] == [[], [other]] # 선 반만 = 창 없음 · 걸침 있음
|
|
assert sorted(got["all"]) == sorted(ids)
|
|
assert got["rotated"] == ["p3"] # 돌린 화면 = 보이는 px 로 셈
|
|
|
|
|
|
def test_여러_구간_연도(tmp_path: Path) -> None:
|
|
got = _run_ts(tmp_path)
|
|
years = dict(got["years"])
|
|
assert years["p1"] == years["p3"] == 2028 and years["p2"] == 2026
|
|
|
|
|
|
def test_마커_구역_나눔_합침_갈래(tmp_path: Path) -> None:
|
|
got = _run_ts(tmp_path)
|
|
assert got["cut"] == [["p1", 0, 40, 2026], ["p2", 40, 100, 2026]] # 구역만 나눔 · 해 그대로
|
|
assert got["edgeSame"] # 다른 마커 1 m 안 = 안 놓음
|
|
assert got["merged"] == [["p1", 0, 100, 2026]] # 지움 = 앞 구간 해 · id 가 먹음
|
|
assert got["branchYear"] == [2028] # 갈래 처음 해 = 붙은 자리 구간 해
|
|
lines = {line_id: (head, branch) for line_id, head, branch in got["branched"]}
|
|
head, branch = lines[next(k for k in lines if k != "main")]
|
|
assert head == [60, 0] and branch == {"line": "main", "vertex": 1} # 닿은 끝 = 시점
|
|
assert got["again"] == "이미 갈래입니다 — 갈라지는 점이 시점입니다."
|
|
assert got["lone"].startswith("다른 선에 닿은 끝이 없습니다")
|