feat(B05): 계획노선 편집 창 — 측점 라벨 · 호 고르기 판정 함수(22-2 · 22-3 1차)
- 측점 라벨 글자 상자를 그릴 때 기록(drawStationTicks 의 onLabel · B04 지도 · 배수유역도 동작 그대로) - stationAtScreen 이 눈금 다음 라벨 상자를 봄 · 솎여 안 보이는 눈금은 안 잡음 - 곡선 호 점열(curveArcVertices)과 점-호 판정(arcAtScreen) 더함 - 시험 resources/tester/test_b05_routeedit_pick.py 8건 - RouteEdit.ts 배선은 sub6 나누기 뒤 새 파일에 잇는 다음 커밋 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EauA2ATmqNRbeV48s9CcBr
This commit is contained in:
@@ -208,6 +208,18 @@ export interface StationTickOptions {
|
||||
/** 돌린 지도에서 **글자만 되돌려 세울** 각(라디안). 0이면 그림과 함께 돈다.
|
||||
* 눈금 막대는 노선에 직각이라 함께 돌아야 맞고, 숫자만 눈높이로 세운다. */
|
||||
uprightRad?: number;
|
||||
/** 라벨을 그릴 때마다 그 글자 상자를 알린다 — 누름 판정이 쓴다. 그림 좌표(회전 전)이고,
|
||||
* 상자는 `uprightRad` 만큼 (x, y) 를 축으로 돌아 있다. 안 주면 아무 일도 안 한다. */
|
||||
onLabel?: (box: StationLabelBox) => void;
|
||||
}
|
||||
|
||||
/** 그려진 측점 라벨 글자 상자 — 가운데 (x, y) · 가로 반폭 `half` · 세로 반폭 8 · 돌린 각. */
|
||||
export interface StationLabelBox {
|
||||
chainage: number;
|
||||
x: number;
|
||||
y: number;
|
||||
half: number;
|
||||
uprightRad: number;
|
||||
}
|
||||
|
||||
export function drawStationTicks(
|
||||
@@ -299,6 +311,7 @@ export function drawStationTicks(
|
||||
context.stroke();
|
||||
}
|
||||
drawn.push({ x: lx, y: ly, half });
|
||||
options.onLabel?.({ chainage, x: lx, y: ly, half, uprightRad: options.uprightRad ?? 0 });
|
||||
context.save();
|
||||
if (options.uprightRad) {
|
||||
context.translate(lx, ly);
|
||||
|
||||
@@ -172,6 +172,30 @@ function arcPoints(center: Vertex, start: Vertex, end: Vertex, clockwise: boolea
|
||||
return points;
|
||||
}
|
||||
|
||||
/** 곡선의 호를 점열로 — 시작점 · 원호 위 점 · 끝점. 그려지는 선(`buildEditedPolyline`)과 같은
|
||||
* 5° 걸음이라 「호를 눌렀나」 판정이 눈에 보이는 선과 어긋나지 않는다. 호는 늘 180° 미만이라
|
||||
* 두 접선점 사이 **짧은 쪽**으로 돈다(도는 방향을 따로 안 받는다). */
|
||||
export function curveArcVertices(curve: EditedCurve): Vertex[] {
|
||||
const toStart = unit(curve.apex, curve.start);
|
||||
const toEnd = unit(curve.apex, curve.end);
|
||||
const bisector: Vertex = [toStart[0] + toEnd[0], toStart[1] + toEnd[1]];
|
||||
const bisectorLength = Math.hypot(bisector[0], bisector[1]);
|
||||
const half = Math.sin((curve.inner_angle_deg * RAD_PER_DEG) / 2);
|
||||
if (bisectorLength <= 1e-9 || !(half > 1e-9)) return [curve.start, curve.end];
|
||||
const centerDistance = curve.radius_m / half;
|
||||
const center: Vertex = [
|
||||
curve.apex[0] + (bisector[0] / bisectorLength) * centerDistance,
|
||||
curve.apex[1] + (bisector[1] / bisectorLength) * centerDistance,
|
||||
];
|
||||
const sweep = Math.atan2(
|
||||
(curve.start[0] - center[0]) * (curve.end[1] - center[1]) -
|
||||
(curve.start[1] - center[1]) * (curve.end[0] - center[0]),
|
||||
(curve.start[0] - center[0]) * (curve.end[0] - center[0]) +
|
||||
(curve.start[1] - center[1]) * (curve.end[1] - center[1]),
|
||||
);
|
||||
return [curve.start, ...arcPoints(center, curve.start, curve.end, sweep < 0), curve.end];
|
||||
}
|
||||
|
||||
/** 노드 하나의 요약 — 화면이 붉은 점·내각·R 을 그리는 재료(서버 `RouteNode` 와 같은 꼴). */
|
||||
export interface EditedNode {
|
||||
inner_angle_deg: number | null;
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
import { computeMaxScale, type ViewState } from "../B04_PreProcess/B04_PreProcess_UI_MapRender";
|
||||
import type { VWorldMeta } from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
|
||||
import type { StationLabelBox } from "../B04_PreProcess/B04_PreProcess_UI_MapOverlays";
|
||||
import { curveArcVertices, type EditedCurve } from "./B05_Profile_UI_RouteEdit_Curve";
|
||||
|
||||
/** 배수유역도와 같은 값 — 한 번에 얼마나 확대·축소할지. */
|
||||
const ZOOM_IN_FACTOR = 1.15;
|
||||
@@ -189,6 +191,41 @@ export function handleAtScreen(
|
||||
return best;
|
||||
}
|
||||
|
||||
/** 호(곡선 부분)를 눌렀다고 볼 거리(px) — 노선 선 굵기보다 넉넉히. */
|
||||
export const ARC_HIT_PX = 8;
|
||||
|
||||
/** 화면 좌표에 가장 가까운 **호**를 가진 곡선의 노드 번호(`node_first`). 문턱 밖이면 -1.
|
||||
*
|
||||
* 호는 그려지는 선과 같은 점열(`curveArcVertices`)을 화면으로 옮겨 선분 거리로 잰다.
|
||||
* 손잡이(접선점)는 호의 양 끝이라 이 판정에도 걸린다 — 손잡이를 먼저 보는 것은 부르는 쪽 몫. */
|
||||
export function arcAtScreen(
|
||||
curves: ReadonlyArray<EditedCurve>,
|
||||
toScreen: ScreenOf,
|
||||
px: number,
|
||||
py: number,
|
||||
hitPx: number = ARC_HIT_PX,
|
||||
): number {
|
||||
let best = -1;
|
||||
let bestDistance = hitPx;
|
||||
for (const curve of curves) {
|
||||
const arc = curveArcVertices(curve).map(([x, y]) => toScreen([x, y]));
|
||||
for (let index = 0; index < arc.length - 1; index += 1) {
|
||||
const [ax, ay] = arc[index];
|
||||
const [bx, by] = arc[index + 1];
|
||||
const dx = bx - ax;
|
||||
const dy = by - ay;
|
||||
const lengthSquared = dx * dx + dy * dy || 1;
|
||||
const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / lengthSquared));
|
||||
const distance = Math.hypot(ax + t * dx - px, ay + t * dy - py);
|
||||
if (distance <= bestDistance) {
|
||||
bestDistance = distance;
|
||||
best = curve.node_first;
|
||||
}
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/** 두 노드 사이 선분 중 클릭에 가장 가까운 것 — 새 노드를 끼울 자리. 없으면 -1. */
|
||||
export function segmentAtScreen(
|
||||
points: Array<[number, number]>,
|
||||
@@ -275,6 +312,59 @@ export function stationAtScreen(
|
||||
px: number,
|
||||
py: number,
|
||||
maxPx: number,
|
||||
labels: ReadonlyArray<StationLabelBox> = [],
|
||||
): number | null {
|
||||
// 라벨이 그려진 측점만 눈금도 보인다(`drawStationTicks` 가 성기게 솎는다) — 안 보이는 눈금이
|
||||
// 호·등고선 누름을 가로채지 않게 한다. 라벨 정보가 없으면 전부 본다.
|
||||
const shown = labels.length
|
||||
? new Set(labels.map((box) => Math.round(box.chainage / intervalM)))
|
||||
: null;
|
||||
const tick = stationTickAtScreen(line, toScreen, intervalM, px, py, maxPx, shown);
|
||||
// 눈금과 라벨이 겹치면 눈금이 먼저다 — 라벨은 눈금을 못 잡았을 때만 본다.
|
||||
return tick ?? stationLabelAtScreen(labels, px, py);
|
||||
}
|
||||
|
||||
/** 그려 둔 측점 라벨 글자 상자 — 그릴 때마다 `Render` 가 새로 적는다. 창(캔버스)마다 따로. */
|
||||
const labelBoxes = new WeakMap<HTMLCanvasElement, ReadonlyArray<StationLabelBox>>();
|
||||
export const setStationLabelBoxes = (
|
||||
canvas: HTMLCanvasElement,
|
||||
boxes: ReadonlyArray<StationLabelBox>,
|
||||
): void => void labelBoxes.set(canvas, boxes);
|
||||
export const stationLabelBoxes = (canvas: HTMLCanvasElement): ReadonlyArray<StationLabelBox> =>
|
||||
labelBoxes.get(canvas) ?? [];
|
||||
|
||||
/** 라벨 글자 상자 안을 눌렀나 — 상자는 가운데를 축으로 `uprightRad` 만큼 돌아 있어, 눌린 자리를
|
||||
* 거꾸로 돌려 세운 뒤 가로 반폭 · 세로 반폭(8) 안인지 본다. 여럿이면 가운데가 가까운 것. */
|
||||
export function stationLabelAtScreen(
|
||||
labels: ReadonlyArray<StationLabelBox>,
|
||||
px: number,
|
||||
py: number,
|
||||
): number | null {
|
||||
let best: number | null = null;
|
||||
let bestDistance = Infinity;
|
||||
for (const box of labels) {
|
||||
const dx = px - box.x;
|
||||
const dy = py - box.y;
|
||||
const cos = Math.cos(-box.uprightRad);
|
||||
const sin = Math.sin(-box.uprightRad);
|
||||
if (Math.abs(dx * cos - dy * sin) > box.half || Math.abs(dx * sin + dy * cos) > 8) continue;
|
||||
const distance = Math.hypot(dx, dy);
|
||||
if (distance < bestDistance) {
|
||||
bestDistance = distance;
|
||||
best = box.chainage;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
function stationTickAtScreen(
|
||||
line: Array<[number, number]>,
|
||||
toScreen: ScreenOf,
|
||||
intervalM: number,
|
||||
px: number,
|
||||
py: number,
|
||||
maxPx: number,
|
||||
shown: ReadonlySet<number> | null,
|
||||
): number | null {
|
||||
if (line.length < 2 || !(intervalM > 0)) return null;
|
||||
const cumulative: number[] = [0];
|
||||
@@ -289,6 +379,7 @@ export function stationAtScreen(
|
||||
let bestDistance = maxPx;
|
||||
let cursor = 1;
|
||||
for (let chainage = 0; chainage <= total; chainage += intervalM) {
|
||||
if (shown && !shown.has(Math.round(chainage / intervalM))) continue;
|
||||
while (cursor < cumulative.length - 1 && cumulative[cursor] < chainage) cursor += 1;
|
||||
const back = line[cursor - 1];
|
||||
const front = line[cursor];
|
||||
|
||||
@@ -20,9 +20,12 @@ import {
|
||||
type ViewState,
|
||||
} from "../B04_PreProcess/B04_PreProcess_UI_MapRender";
|
||||
import type { RouteEditContours } from "./B05_Profile_UI_RouteEdit_Contour";
|
||||
import { drawStationTicks } from "../B04_PreProcess/B04_PreProcess_UI_MapOverlays";
|
||||
import {
|
||||
drawStationTicks,
|
||||
type StationLabelBox,
|
||||
} from "../B04_PreProcess/B04_PreProcess_UI_MapOverlays";
|
||||
import type { EditedCurve, EditedNode, Vertex } from "./B05_Profile_UI_RouteEdit_Curve";
|
||||
import { contourBandRect } from "./B05_Profile_UI_RouteEdit_Input";
|
||||
import { contourBandRect, setStationLabelBoxes } from "./B05_Profile_UI_RouteEdit_Input";
|
||||
import type { CrossWatch } from "./B05_Profile_UI_RouteEdit_Cross";
|
||||
import { stationFrameAt } from "./B05_Profile_UI_RouteEdit_Frame";
|
||||
import type { MeasureMark } from "./B05_Profile_UI_RouteEdit_Measure";
|
||||
@@ -442,6 +445,9 @@ function drawStationMarks(
|
||||
scene: RouteEditScene,
|
||||
line: ReadonlyArray<Vertex>,
|
||||
): void {
|
||||
// 라벨 글자 상자는 그릴 때마다 새로 적는다 — 누름 판정(`stationAtScreen`)이 이것을 본다.
|
||||
const labels: StationLabelBox[] = [];
|
||||
setStationLabelBoxes(context.canvas, labels);
|
||||
if (line.length < 2) return;
|
||||
// 눈금은 B04 지도·배수유역도와 같은 한 곳이 그린다 — 표기가 화면마다 갈리지 않게.
|
||||
drawStationTicks(
|
||||
@@ -452,6 +458,7 @@ function drawStationMarks(
|
||||
pxPerMeter: scene.pxPerMeter,
|
||||
toScreen: (x, y) => scene.toScreen([x, y]),
|
||||
uprightRad: scene.uprightRad,
|
||||
onLabel: (box) => labels.push(box),
|
||||
},
|
||||
);
|
||||
const total = polylineLengthM(line);
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/* 계획노선 편집 창 **측점 라벨 · 호 고르기**(계획서 22-2 · 22-3) 단위검증 헬퍼 —
|
||||
* `_RouteEdit_Input.ts` · `_RouteEdit_Curve.ts` · B04 `drawStationTicks` 를 트랜스파일해 Node 로 돌린다.
|
||||
* 브라우저 전용 팔레트·표기 함수는 가짜로 채운다 — 셈만 본다.
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const cache = new Map();
|
||||
const STUBS = {
|
||||
"@ui/ui_template_palette": { themeColor: (_name, fallback) => fallback },
|
||||
"@util/common_util_svg": {
|
||||
stationLabel: (chainage, interval) => `No.${Math.round(chainage / interval)}`,
|
||||
},
|
||||
};
|
||||
|
||||
function loadTs(file) {
|
||||
const full = path.resolve(file);
|
||||
if (cache.has(full)) return cache.get(full);
|
||||
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const localRequire = (request) => {
|
||||
if (STUBS[request]) return STUBS[request];
|
||||
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
||||
return require(request);
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||||
cache.set(full, box.exports);
|
||||
return box.exports;
|
||||
}
|
||||
|
||||
const input = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_RouteEdit_Input.ts"));
|
||||
const curve = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_RouteEdit_Curve.ts"));
|
||||
const overlays = loadTs(path.join(ROOT, "B04_PreProcess", "B04_PreProcess_UI_MapOverlays.ts"));
|
||||
|
||||
// 화면 = 사업지 m × 2, y 뒤집음(실제 `metricToScreen` 처럼 y 가 화면에서 뒤집힌다).
|
||||
const toScreen = ([x, y]) => [x * 2, 400 - y * 2];
|
||||
const near = (a, b, tol) => Math.hypot(a[0] - b[0], a[1] - b[1]) <= tol;
|
||||
|
||||
// 노드 세 개 · 가운데 R=30 곡선 하나.
|
||||
const nodes = [
|
||||
[0, 0],
|
||||
[100, 0],
|
||||
[100, 100],
|
||||
];
|
||||
const built = curve.buildEditedPolyline(nodes, [true, true, true], [null, 30, null], 12);
|
||||
const c = built.curves[0];
|
||||
|
||||
// ① 호 점열 == 그려지는 선에서 시작~끝점 사이 조각(거울).
|
||||
const drawn = built.vertices;
|
||||
const iStart = drawn.findIndex((v) => near(v, c.start, 1e-9));
|
||||
const iEnd = drawn.findIndex((v) => near(v, c.end, 1e-9));
|
||||
const arc = curve.curveArcVertices(c);
|
||||
const arcMatchesDrawn =
|
||||
arc.length === iEnd - iStart + 1 && arc.every((v, i) => near(v, drawn[iStart + i], 1e-9));
|
||||
|
||||
// ② 호 누름 — 호 가운데 · 호에서 5px 비킨 곳(잡힘) · 곧은 선 · 빈 곳.
|
||||
const mid = drawn[Math.floor((iStart + iEnd) / 2)];
|
||||
const [mx, my] = toScreen(mid);
|
||||
const arcAt = (px, py) => input.arcAtScreen(built.curves, toScreen, px, py);
|
||||
const pick = {
|
||||
onArc: arcAt(mx, my),
|
||||
nearArc: arcAt(mx + 3, my + 3),
|
||||
farFromArc: arcAt(mx - 60, my + 60),
|
||||
onStraight: arcAt(...toScreen([50, 0])),
|
||||
empty: arcAt(5, 5),
|
||||
noCurves: input.arcAtScreen([], toScreen, mx, my),
|
||||
};
|
||||
|
||||
// ③ 반대로 도는(오른쪽 꺾임) 곡선도 같은 판정.
|
||||
const built2 = curve.buildEditedPolyline(
|
||||
[
|
||||
[0, 0],
|
||||
[100, 0],
|
||||
[100, -100],
|
||||
],
|
||||
[true, true, true],
|
||||
[null, 30, null],
|
||||
12,
|
||||
);
|
||||
const mid2 = built2.vertices[Math.floor(built2.vertices.length / 2)];
|
||||
const pick2 = input.arcAtScreen(built2.curves, toScreen, ...toScreen(mid2));
|
||||
|
||||
// ④ 측점 라벨 — 가짜 캔버스로 `drawStationTicks` 를 돌려 상자를 받고, 그린 사각형과 같은지 본다.
|
||||
const rects = [];
|
||||
const fakeContext = {
|
||||
save() {},
|
||||
restore() {},
|
||||
beginPath() {},
|
||||
moveTo() {},
|
||||
lineTo() {},
|
||||
stroke() {},
|
||||
translate() {},
|
||||
rotate() {},
|
||||
fillText() {},
|
||||
fillRect: (x, y, w, h) => rects.push([x, y, w, h]),
|
||||
measureText: (text) => ({ width: text.length * 6 }),
|
||||
};
|
||||
const line = [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 100, y: 0 },
|
||||
];
|
||||
const boxes = [];
|
||||
overlays.drawStationTicks(fakeContext, line, {
|
||||
intervalM: 20,
|
||||
pxPerMeter: 4, // 간격 80px → 두 칸에 하나(step 2)
|
||||
toScreen: (x, y) => toScreen([x, y]),
|
||||
uprightRad: 0.5,
|
||||
onLabel: (box) => boxes.push(box),
|
||||
});
|
||||
const labelsMatchRects =
|
||||
boxes.length === rects.length &&
|
||||
boxes.every(
|
||||
(box, i) =>
|
||||
Math.abs(rects[i][0] - (box.x - box.half)) < 1e-9 &&
|
||||
Math.abs(rects[i][1] - (box.y - 8)) < 1e-9 &&
|
||||
Math.abs(rects[i][2] - box.half * 2) < 1e-9,
|
||||
);
|
||||
|
||||
// ⑤ 라벨 누름 — 라벨 가운데 · 라벨 밖 · 돌린 상자 모서리 안팎 · 눈금 우선.
|
||||
const target = boxes[1];
|
||||
const stationLine = [
|
||||
[0, 0],
|
||||
[100, 0],
|
||||
];
|
||||
const stationAt = (px, py, labels) =>
|
||||
input.stationAtScreen(stationLine, toScreen, 20, px, py, 11, labels);
|
||||
const rot = (dx, dy, rad) => [
|
||||
dx * Math.cos(rad) - dy * Math.sin(rad),
|
||||
dx * Math.sin(rad) + dy * Math.cos(rad),
|
||||
];
|
||||
// 돌린 상자의 오른쪽 위 모서리 안쪽(-1px) · 바깥쪽(+3px) — 세워 본 좌표(half, 8)를 상자 각만큼 돌린 자리.
|
||||
const insideCorner = rot(target.half - 1, 7, target.uprightRad);
|
||||
const outsideCorner = rot(target.half + 3, 11, target.uprightRad);
|
||||
const tickScreen = toScreen([40, 0]); // 라벨이 그려진 측점(0 · 40 · 80)의 눈금
|
||||
const hiddenTickScreen = toScreen([20, 0]); // 솎여 라벨이 없는 측점의 눈금
|
||||
const label = {
|
||||
count: boxes.length,
|
||||
chainages: boxes.map((b) => b.chainage),
|
||||
onLabel: stationAt(target.x, target.y, boxes),
|
||||
insideCorner: stationAt(target.x + insideCorner[0], target.y + insideCorner[1], boxes),
|
||||
outsideCorner: stationAt(target.x + outsideCorner[0], target.y + outsideCorner[1], boxes),
|
||||
// 눈금 자리를 누르면 다른 측점 라벨 상자와 겹쳐도 눈금 몫이다.
|
||||
tickBeatsLabel: stationAt(tickScreen[0], tickScreen[1], [
|
||||
target,
|
||||
{ chainage: 80, x: tickScreen[0], y: tickScreen[1], half: 30, uprightRad: 0 },
|
||||
]),
|
||||
// 안 보이는(솎인) 눈금은 누름을 가로채지 않는다 — 라벨 정보가 없을 때만 전부 본다.
|
||||
hiddenTick: stationAt(hiddenTickScreen[0], hiddenTickScreen[1], boxes),
|
||||
hiddenTickNoInfo: stationAt(hiddenTickScreen[0], hiddenTickScreen[1], []),
|
||||
emptyNoLabels: stationAt(target.x, target.y, []),
|
||||
emptyElsewhere: stationAt(5, 5, boxes),
|
||||
};
|
||||
|
||||
process.stdout.write(
|
||||
JSON.stringify({ arcMatchesDrawn, pick, pick2, node: c.node_first, labelsMatchRects, label }),
|
||||
);
|
||||
@@ -0,0 +1,71 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""계획노선 편집 창 — **측점 라벨 고르기**(계획서 22-2) · **호 고르기**(22-3) 회귀검증.
|
||||
|
||||
Node 헬퍼(`helper_b05_routeedit_pick.cjs`)가 TS 를 트랜스파일해 돌리고, 여기서 값을 판정한다.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def results():
|
||||
proc = subprocess.run(
|
||||
["node", os.path.join(HERE, "helper_b05_routeedit_pick.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def test_호_점열은_그려지는_선의_시작_끝_조각과_같다(results):
|
||||
assert results["arcMatchesDrawn"] is True
|
||||
|
||||
|
||||
def test_호를_누르면_그_곡선_노드_번호(results):
|
||||
pick = results["pick"]
|
||||
assert pick["onArc"] == results["node"]
|
||||
assert pick["nearArc"] == results["node"]
|
||||
assert results["pick2"] == results["node"] # 반대로 도는 곡선도
|
||||
|
||||
|
||||
def test_곧은_선_빈_곳_곡선_없음은_안_고름(results):
|
||||
pick = results["pick"]
|
||||
assert pick["farFromArc"] == -1
|
||||
assert pick["onStraight"] == -1
|
||||
assert pick["empty"] == -1
|
||||
assert pick["noCurves"] == -1
|
||||
|
||||
|
||||
def test_라벨_상자는_실제로_그린_사각형과_같다(results):
|
||||
assert results["labelsMatchRects"] is True
|
||||
assert results["label"]["chainages"] == [0, 40, 80]
|
||||
|
||||
|
||||
def test_라벨_글자를_누르면_그_측점(results):
|
||||
label = results["label"]
|
||||
assert label["onLabel"] == 40
|
||||
assert label["insideCorner"] == 40 # 돌린 상자 모서리 안쪽
|
||||
assert label["outsideCorner"] is None # 모서리 바깥
|
||||
|
||||
|
||||
def test_눈금이_라벨보다_먼저(results):
|
||||
assert results["label"]["tickBeatsLabel"] == 40
|
||||
|
||||
|
||||
def test_솎여_안_보이는_눈금은_안_잡힘(results):
|
||||
assert results["label"]["hiddenTick"] is None
|
||||
assert results["label"]["hiddenTickNoInfo"] == 20 # 라벨 정보가 없으면 예전처럼 전부
|
||||
|
||||
|
||||
def test_라벨이_없거나_빈_곳은_안_고름(results):
|
||||
label = results["label"]
|
||||
assert label["emptyNoLabels"] is None
|
||||
assert label["emptyElsewhere"] is None
|
||||
Reference in New Issue
Block a user