feat(B05): 종단 ▲▼ 꺾임 누름 시 이웃 꺾임 축으로 사이 직선 돌림 (355-1)
- adjustStation — 누른 점이 꺾임이면 pivotAxes 로 좌우 다음 꺾임(없으면 시점 · 종점)을 축으로 사이 측점을 새 직선 위로 · 사이 R 키 지움 · 직선 안쪽 · 처음 계획선은 옛 값 그대로 - 새 logic/B05_Profile_UI_Profile_Pivot.ts — 타입만 import(순환 없음) - 시험 test_355_1 · helper_355_1 세 경우 · test_213_1/2 tsc 산출 ESM import 에 .js 붙임 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013QyNXY2TUKXCFMwAca2APG
This commit is contained in:
@@ -13,6 +13,8 @@
|
|||||||
* - 편집 델타는 항상 **자동 선형(base_pvi) 기준**이라 키를 지우면 정확히 원복된다.
|
* - 편집 델타는 항상 **자동 선형(base_pvi) 기준**이라 키를 지우면 정확히 원복된다.
|
||||||
* ========================================================================== */
|
* ========================================================================== */
|
||||||
|
|
||||||
|
import { pivotAxes } from "./B05_Profile_UI_Profile_Pivot";
|
||||||
|
|
||||||
export interface AlignmentPolicy {
|
export interface AlignmentPolicy {
|
||||||
station_interval_m: number;
|
station_interval_m: number;
|
||||||
curve_radius_ratio: number;
|
curve_radius_ratio: number;
|
||||||
@@ -552,6 +554,7 @@ const FEEDBACK_TOLERANCE_M = 1e-4;
|
|||||||
* 변화점이 새로 생기면 종단곡선의 중앙종거만큼 계획고가 함께 내려가(또는 올라가)
|
* 변화점이 새로 생기면 종단곡선의 중앙종거만큼 계획고가 함께 내려가(또는 올라가)
|
||||||
* 버튼 1클릭이 정확히 0.1m가 되지 않는다. **화면에 보이는 계획고**가 정확히 delta만큼
|
* 버튼 1클릭이 정확히 0.1m가 되지 않는다. **화면에 보이는 계획고**가 정확히 delta만큼
|
||||||
* 움직이도록 중앙종거 변화분을 몇 번 되먹여 보정한다.
|
* 움직이도록 중앙종거 변화분을 몇 번 되먹여 보정한다.
|
||||||
|
* 누른 점이 꺾임이면 이웃 꺾임(`pivotAxes`)을 축으로 사이 측점이 새 직선 위로 따라 돈다(355-1).
|
||||||
*/
|
*/
|
||||||
export function adjustStation(
|
export function adjustStation(
|
||||||
base: AlignmentBase,
|
base: AlignmentBase,
|
||||||
@@ -562,17 +565,24 @@ export function adjustStation(
|
|||||||
const key = chainageKey(chainageM);
|
const key = chainageKey(chainageM);
|
||||||
const current = buildAlignment(base, edits);
|
const current = buildAlignment(base, edits);
|
||||||
const target = planElevationAt(current, chainageM) + delta;
|
const target = planElevationAt(current, chainageM) + delta;
|
||||||
const baseElevation = interpolate(
|
const baseS = base.basePvi.map((node) => node.chainage_m);
|
||||||
base.basePvi.map((node) => node.chainage_m),
|
const baseZ = base.basePvi.map((node) => node.elevation_m);
|
||||||
base.basePvi.map((node) => node.elevation_m),
|
const baseElevation = interpolate(baseS, baseZ, chainageM);
|
||||||
chainageM,
|
|
||||||
);
|
|
||||||
let offset =
|
let offset =
|
||||||
(edits.station_offsets[key] ?? planElevationAt(current, chainageM) - baseElevation) + delta;
|
(edits.station_offsets[key] ?? planElevationAt(current, chainageM) - baseElevation) + delta;
|
||||||
const withOffset = (value: number): AlignmentEdits => ({
|
const axes = pivotAxes(current, chainageM);
|
||||||
...edits,
|
const withOffset = (value: number): AlignmentEdits => {
|
||||||
station_offsets: { ...edits.station_offsets, [key]: Number(value.toFixed(6)) },
|
const offsets = { ...edits.station_offsets, [key]: Number(value.toFixed(6)) };
|
||||||
});
|
const radii = { ...edits.curve_radii };
|
||||||
|
axes?.inner.forEach((chainage) => {
|
||||||
|
const [axisM, axisZ] = chainage < chainageM ? [axes.fromM, axes.fromZ] : [axes.toM, axes.toZ];
|
||||||
|
const z =
|
||||||
|
axisZ + ((baseElevation + value - axisZ) * (chainage - axisM)) / (chainageM - axisM);
|
||||||
|
offsets[chainageKey(chainage)] = Number((z - interpolate(baseS, baseZ, chainage)).toFixed(6));
|
||||||
|
delete radii[chainageKey(chainage)];
|
||||||
|
});
|
||||||
|
return { ...edits, station_offsets: offsets, curve_radii: radii };
|
||||||
|
};
|
||||||
let next = withOffset(offset);
|
let next = withOffset(offset);
|
||||||
for (let pass = 0; pass < FEEDBACK_PASSES; pass += 1) {
|
for (let pass = 0; pass < FEEDBACK_PASSES; pass += 1) {
|
||||||
const error = target - planElevationAt(buildAlignment(base, next), chainageM);
|
const error = target - planElevationAt(buildAlignment(base, next), chainageM);
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/* =============================================================================
|
||||||
|
* B05_Profile_UI_Profile_Pivot.ts
|
||||||
|
* 종단 ▲▼ 축 찾기(355-1 · 요구 235-15) — 누른 점이 꺾임(R 있는 점)이면 좌우로 같은
|
||||||
|
* 기울기 마디를 건너 다음 꺾임(없으면 시점 · 종점)을 축으로 삼는다. 사이 측점은
|
||||||
|
* 새 직선(축 → 누른 점 → 축) 위로 따라 돈다(`adjustStation`).
|
||||||
|
*
|
||||||
|
* 처음 계획선은 마디마다 꺾여 축 = 누른 점 ±1 → 사이 측점 없음(옛 동작 그대로).
|
||||||
|
* `B05_Profile_UI_Profile_Alignment` 를 타입으로만 읽는다(순환 import 없음).
|
||||||
|
* ========================================================================== */
|
||||||
|
|
||||||
|
import type { ProfileAlignment } from "./B05_Profile_UI_Profile_Alignment";
|
||||||
|
|
||||||
|
/** 같은 직선으로 볼 기울기 차이 — `B05_Profile_UI_Profile_Straighten` 과 같은 값. */
|
||||||
|
const COLLINEAR_EPSILON = 1e-6;
|
||||||
|
const SAME_STATION_M = 1e-6;
|
||||||
|
|
||||||
|
export interface PivotAxes {
|
||||||
|
fromM: number;
|
||||||
|
fromZ: number;
|
||||||
|
toM: number;
|
||||||
|
toZ: number;
|
||||||
|
/** 두 축 사이(끝 · 누른 점 제외) 변화점 chainage. */
|
||||||
|
inner: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 누른 점이 꺾임이면 두 축과 사이 변화점 — 직선 안쪽 · 시종점 · 변화점 아님이면 null. */
|
||||||
|
export function pivotAxes(alignment: ProfileAlignment, chainageM: number): PivotAxes | null {
|
||||||
|
const { pvi, segments } = alignment;
|
||||||
|
const index = pvi.findIndex((node) => Math.abs(node.chainage_m - chainageM) < SAME_STATION_M);
|
||||||
|
if (index <= 0 || index >= pvi.length - 1) return null;
|
||||||
|
const grade = (at: number): number => {
|
||||||
|
const segment = segments[at];
|
||||||
|
return segment && segment.length_m > 1e-12 ? segment.height_m / segment.length_m : 0;
|
||||||
|
};
|
||||||
|
const same = (a: number, b: number): boolean => Math.abs(grade(a) - grade(b)) < COLLINEAR_EPSILON;
|
||||||
|
if (same(index - 1, index)) return null;
|
||||||
|
let from = index - 1;
|
||||||
|
while (from > 0 && same(from - 1, index - 1)) from -= 1;
|
||||||
|
let to = index + 1;
|
||||||
|
while (to < pvi.length - 1 && same(to, index)) to += 1;
|
||||||
|
return {
|
||||||
|
fromM: pvi[from].chainage_m,
|
||||||
|
fromZ: pvi[from].elevation_m,
|
||||||
|
toM: pvi[to].chainage_m,
|
||||||
|
toZ: pvi[to].elevation_m,
|
||||||
|
inner: pvi
|
||||||
|
.slice(from + 1, to)
|
||||||
|
.map((node) => node.chainage_m)
|
||||||
|
.filter((chainage) => Math.abs(chainage - chainageM) >= SAME_STATION_M),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/* 355-1 종단 ▲▼ 이웃 꺾임 축 돌림 단위검증 헬퍼 — `B05_Profile_UI_Profile_Alignment.ts` ·
|
||||||
|
* `_Straighten.ts` 를 그 자리에서 트랜스파일해 세 경우를 Node 로 돌린다.
|
||||||
|
* ① 처음 계획선 ▲ = 옛 값 ② 직선화 뒤 꺾임 ▲ = 사이 측점 한 직선 · 축 표고 그대로 · +0.1
|
||||||
|
* ③ 직선 안쪽 ▲ = 옛 값. 옛 값 = 355-1 앞 adjustStation(아래 oldAdjust) 그대로.
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
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 (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 LOGIC = path.join(ROOT, "B05_Profile", "logic");
|
||||||
|
const align = loadTs(path.join(LOGIC, "B05_Profile_UI_Profile_Alignment.ts"));
|
||||||
|
const straight = loadTs(path.join(LOGIC, "B05_Profile_UI_Profile_Straighten.ts"));
|
||||||
|
|
||||||
|
// 전체 측점 폴리라인(20m 간격 · 0~200m) — 서버와 같이 꺾임마다 기본 L 라운드.
|
||||||
|
const stations = Array.from({ length: 11 }, (_, i) => i * 20);
|
||||||
|
const ground = stations.map((s, i) => 100 + 0.3 * i + (i % 2 ? 0.4 : 0));
|
||||||
|
const base = {
|
||||||
|
policy: {
|
||||||
|
station_interval_m: 20,
|
||||||
|
curve_radius_ratio: 1,
|
||||||
|
curve_tangent_max_ratio: 0.5,
|
||||||
|
curve_skip_legal_exception: false,
|
||||||
|
default_curve_length_m: 15,
|
||||||
|
default_curve_radius_m: 300,
|
||||||
|
balance_tolerance_percent: 10,
|
||||||
|
edit_step_m: 0.1,
|
||||||
|
grade_violation_policy: "warn",
|
||||||
|
max_grade_pct: 14,
|
||||||
|
curve_skip_delta_pct: 0,
|
||||||
|
paved: true,
|
||||||
|
},
|
||||||
|
onlyExplicitCurves: false,
|
||||||
|
basePvi: stations.map((s, i) => ({ chainage_m: s, elevation_m: ground[i] })),
|
||||||
|
chainage: stations,
|
||||||
|
ground,
|
||||||
|
stations: stations.map((s) => ({ station_id: `S${s}`, chainage_m: s })),
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 355-1 앞 adjustStation — 누른 측점 하나만 옮김. */
|
||||||
|
function oldAdjust(edits, chainageM, delta) {
|
||||||
|
const key = align.chainageKey(chainageM);
|
||||||
|
const current = align.buildAlignment(base, edits);
|
||||||
|
const target = align.planElevationAt(current, chainageM) + delta;
|
||||||
|
const baseElevation = straight.baseElevationAt(base, chainageM);
|
||||||
|
let offset =
|
||||||
|
(edits.station_offsets[key] ?? align.planElevationAt(current, chainageM) - baseElevation) +
|
||||||
|
delta;
|
||||||
|
const withOffset = (value) => ({
|
||||||
|
...edits,
|
||||||
|
station_offsets: { ...edits.station_offsets, [key]: Number(value.toFixed(6)) },
|
||||||
|
});
|
||||||
|
let next = withOffset(offset);
|
||||||
|
for (let pass = 0; pass < 4; pass += 1) {
|
||||||
|
const error = target - align.planElevationAt(align.buildAlignment(base, next), chainageM);
|
||||||
|
if (Math.abs(error) < 1e-4) break;
|
||||||
|
offset += error;
|
||||||
|
next = withOffset(offset);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sorted = (record) =>
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.keys(record ?? {})
|
||||||
|
.sort()
|
||||||
|
.map((key) => [key, record[key]]),
|
||||||
|
);
|
||||||
|
const pviZ = (built, s) => built.pvi.find((n) => Math.abs(n.chainage_m - s) < 1e-6).elevation_m;
|
||||||
|
const out = {};
|
||||||
|
|
||||||
|
// ① 처음 계획선 — 마디마다 꺾임 → 축 = 누른 점 ±1 · 옛 값과 같음(▲ · ▼).
|
||||||
|
{
|
||||||
|
const empty = align.emptyEdits();
|
||||||
|
const up = align.adjustStation(base, empty, 100, 0.1);
|
||||||
|
const down = align.adjustStation(base, empty, 60, -0.1);
|
||||||
|
out.initial = {
|
||||||
|
sameUp:
|
||||||
|
JSON.stringify(sorted(up.station_offsets)) ===
|
||||||
|
JSON.stringify(sorted(oldAdjust(empty, 100, 0.1).station_offsets)),
|
||||||
|
sameDown:
|
||||||
|
JSON.stringify(sorted(down.station_offsets)) ===
|
||||||
|
JSON.stringify(sorted(oldAdjust(empty, 60, -0.1).station_offsets)),
|
||||||
|
upKeys: Object.keys(up.station_offsets),
|
||||||
|
radiiKeys: Object.keys(up.curve_radii ?? {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ② 40~100 · 100~160 직선화 → 100 = 꺾임 · 두 축 40 · 160 · ▲ 100.
|
||||||
|
{
|
||||||
|
let edits = straight.straightenBetween(base, align.emptyEdits(), 40, 100);
|
||||||
|
edits = straight.straightenBetween(base, edits, 100, 160);
|
||||||
|
const before = align.buildAlignment(base, edits);
|
||||||
|
const next = align.adjustStation(base, edits, 100, 0.1);
|
||||||
|
const after = align.buildAlignment(base, next);
|
||||||
|
const p = pviZ(after, 100);
|
||||||
|
const line = (s, aM, aZ) => aZ + ((p - aZ) * (s - aM)) / (100 - aM);
|
||||||
|
const z40 = pviZ(before, 40);
|
||||||
|
const z160 = pviZ(before, 160);
|
||||||
|
out.kink = {
|
||||||
|
planRise: align.planElevationAt(after, 100) - align.planElevationAt(before, 100),
|
||||||
|
pivotRise: p - pviZ(before, 100),
|
||||||
|
axisMove: [pviZ(after, 40) - z40, pviZ(after, 160) - z160],
|
||||||
|
outsideMove: [20, 180].map((s) => pviZ(after, s) - pviZ(before, s)),
|
||||||
|
innerResidual: [60, 80]
|
||||||
|
.map((s) => pviZ(after, s) - line(s, 40, z40))
|
||||||
|
.concat([120, 140].map((s) => pviZ(after, s) - line(s, 160, z160))),
|
||||||
|
innerMoved: [60, 80, 120, 140].map((s) => pviZ(after, s) - pviZ(before, s)),
|
||||||
|
curvesAt: after.curves.map((c) => c.chainage_m).sort((a, b) => a - b),
|
||||||
|
oldWouldBreak: (() => {
|
||||||
|
const old = align.buildAlignment(base, oldAdjust(edits, 100, 0.1));
|
||||||
|
return [80, 120].map((s) => pviZ(old, s) - pviZ(before, s));
|
||||||
|
})(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ③ 직선 안쪽(80 · 40~100 직선 위) — 꺾임 아님 → 옛 값.
|
||||||
|
{
|
||||||
|
let edits = straight.straightenBetween(base, align.emptyEdits(), 40, 100);
|
||||||
|
edits = straight.straightenBetween(base, edits, 100, 160);
|
||||||
|
const next = align.adjustStation(base, edits, 80, 0.1);
|
||||||
|
out.inside = {
|
||||||
|
same:
|
||||||
|
JSON.stringify(sorted(next.station_offsets)) ===
|
||||||
|
JSON.stringify(sorted(oldAdjust(edits, 80, 0.1).station_offsets)),
|
||||||
|
planRise:
|
||||||
|
align.planElevationAt(align.buildAlignment(base, next), 80) -
|
||||||
|
align.planElevationAt(align.buildAlignment(base, edits), 80),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write(JSON.stringify(out));
|
||||||
@@ -15,6 +15,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@@ -136,6 +137,9 @@ def ts_alignment(tmp_path_factory):
|
|||||||
capture_output=True,
|
capture_output=True,
|
||||||
)
|
)
|
||||||
(out / "runner.mjs").write_text(_RUNNER, encoding="utf-8")
|
(out / "runner.mjs").write_text(_RUNNER, encoding="utf-8")
|
||||||
|
for js in out.glob("*.js"): # 355-1 Alignment 이 Pivot 을 부름 — Node ESM 은 확장자 필요
|
||||||
|
text = js.read_text(encoding="utf-8")
|
||||||
|
js.write_text(re.sub(r'(from "\./\w+)"', r'\1.js"', text), encoding="utf-8")
|
||||||
data = _synthetic()
|
data = _synthetic()
|
||||||
base = {
|
base = {
|
||||||
"policy": AlignmentPolicy.from_dict({}).as_dict(),
|
"policy": AlignmentPolicy.from_dict({}).as_dict(),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -163,6 +164,9 @@ def test_followed_station_ts(tmp_path):
|
|||||||
capture_output=True,
|
capture_output=True,
|
||||||
)
|
)
|
||||||
(out / "runner.mjs").write_text(_RUNNER, encoding="utf-8")
|
(out / "runner.mjs").write_text(_RUNNER, encoding="utf-8")
|
||||||
|
for js in out.glob("*.js"): # 355-1 Alignment 이 Pivot 을 부름 — Node ESM 은 확장자 필요
|
||||||
|
text = js.read_text(encoding="utf-8")
|
||||||
|
js.write_text(re.sub(r'(from "\./\w+)"', r'\1.js"', text), encoding="utf-8")
|
||||||
src, dst = tmp_path / "in.json", tmp_path / "out.json"
|
src, dst = tmp_path / "in.json", tmp_path / "out.json"
|
||||||
src.write_text(json.dumps([case for case, _ in CASES]), encoding="utf-8")
|
src.write_text(json.dumps([case for case, _ in CASES]), encoding="utf-8")
|
||||||
subprocess.run( # noqa: S603
|
subprocess.run( # noqa: S603
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""355-1 B05 종단 ▲▼ — 누른 점이 꺾임이면 이웃 꺾임을 축으로 사이 직선이 따라 돎(요구 235-15).
|
||||||
|
|
||||||
|
계산(`B05_Profile/logic/B05_Profile_UI_Profile_Alignment.ts` adjustStation ·
|
||||||
|
`_Pivot.ts` pivotAxes)은 Node 헬퍼(`helper_355_1_pivot_tilt.cjs`)가 트랜스파일해 돌리고,
|
||||||
|
부르는 곳 셋(▲▼ 버튼 · 표 값 입력 · 방향키)이 같은 함수인지는 소스로 본다.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
HERE = Path(__file__).resolve().parent
|
||||||
|
ROOT = HERE.parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def _read(relative: str) -> str:
|
||||||
|
return (ROOT / relative).read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def out():
|
||||||
|
proc = subprocess.run(
|
||||||
|
["node", os.path.join(HERE, "helper_355_1_pivot_tilt.cjs")],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
encoding="utf-8",
|
||||||
|
timeout=60,
|
||||||
|
)
|
||||||
|
assert proc.returncode == 0, proc.stderr
|
||||||
|
return json.loads(proc.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
def test_처음_계획선은_옛_값(out):
|
||||||
|
initial = out["initial"]
|
||||||
|
assert initial["sameUp"] and initial["sameDown"]
|
||||||
|
assert initial["upKeys"] == ["100.000"]
|
||||||
|
assert initial["radiiKeys"] == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_꺾임_누르면_사이_측점이_한_직선(out):
|
||||||
|
kink = out["kink"]
|
||||||
|
assert kink["planRise"] == pytest.approx(0.1, abs=1e-4)
|
||||||
|
assert kink["axisMove"] == [0, 0]
|
||||||
|
assert kink["outsideMove"] == [0, 0]
|
||||||
|
assert all(abs(value) < 1e-6 for value in kink["innerResidual"])
|
||||||
|
# 축에서 멀수록 더 오름(40 → 60 → 80 → 100 · 160 → 140 → 120 → 100)
|
||||||
|
moved = kink["innerMoved"]
|
||||||
|
assert 0 < moved[0] < moved[1] < kink["pivotRise"]
|
||||||
|
assert 0 < moved[3] < moved[2] < kink["pivotRise"]
|
||||||
|
# 옛 길은 사이 측점이 그대로라 P±1 에서 꺾였음
|
||||||
|
assert kink["oldWouldBreak"] == [0, 0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_직선_안쪽은_옛_값(out):
|
||||||
|
assert out["inside"]["same"]
|
||||||
|
assert out["inside"]["planRise"] == pytest.approx(0.1, abs=1e-4)
|
||||||
|
|
||||||
|
|
||||||
|
def test_부르는_곳_셋이_같은_함수():
|
||||||
|
assert "adjustStation(base, store.edits(), chainage, delta)" in _read(
|
||||||
|
"B05_Profile/B05_Profile_UI_Profile_Render.ts"
|
||||||
|
)
|
||||||
|
assert "ctx.applyEdits(adjustStation(base, ctx.edits(), chainage, delta))" in _read(
|
||||||
|
"B05_Profile/B05_Profile_UI_Profile_Panel_Tools.ts"
|
||||||
|
)
|
||||||
|
assert "pivotAxes(current, chainageM)" in _read(
|
||||||
|
"B05_Profile/logic/B05_Profile_UI_Profile_Alignment.ts"
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user