feat(B05 종단): ▲▼ 직선 안 측점 · 꼭짓점 아닌 자리 = 그 점 새 꺾임 · 좌우 다음 R 점 축 (368-1)
- pivotAxes 직선 안쪽 null 지움 · 꼭짓점 아닌 자리는 품은 마디로 축 찾기 - 시험 helper_355_1 ③ 직선 안 80 바꿈 · ④ 꼭짓점 아닌 70 더함 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013QyNXY2TUKXCFMwAca2APG
This commit is contained in:
@@ -554,7 +554,7 @@ const FEEDBACK_TOLERANCE_M = 1e-4;
|
||||
* 변화점이 새로 생기면 종단곡선의 중앙종거만큼 계획고가 함께 내려가(또는 올라가)
|
||||
* 버튼 1클릭이 정확히 0.1m가 되지 않는다. **화면에 보이는 계획고**가 정확히 delta만큼
|
||||
* 움직이도록 중앙종거 변화분을 몇 번 되먹여 보정한다.
|
||||
* 누른 점이 꺾임이면 이웃 꺾임(`pivotAxes`)을 축으로 사이 측점이 새 직선 위로 따라 돈다(355-1).
|
||||
* 누른 점 좌우 다음 꺾임(`pivotAxes`)을 축으로 사이 측점이 새 직선 위로 따라 돈다(355-1) · 직선 안 점도 그 점이 새 꺾임(368-1).
|
||||
*/
|
||||
export function adjustStation(
|
||||
base: AlignmentBase,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/* =============================================================================
|
||||
* B05_Profile_UI_Profile_Pivot.ts
|
||||
* 종단 ▲▼ 축 찾기(355-1 · 요구 235-15) — 누른 점이 꺾임(R 있는 점)이면 좌우로 같은
|
||||
* 기울기 마디를 건너 다음 꺾임(없으면 시점 · 종점)을 축으로 삼는다. 사이 측점은
|
||||
* 새 직선(축 → 누른 점 → 축) 위로 따라 돈다(`adjustStation`).
|
||||
* 종단 ▲▼ 축 찾기(355-1 · 368-1 · 요구 235-15 · 240-20) — 누른 점에서 좌우로 같은
|
||||
* 기울기 마디를 건너 다음 꺾임(R 있는 점 · 없으면 시점 · 종점)을 축으로 삼는다. 사이
|
||||
* 측점은 새 직선(축 → 누른 점 → 축) 위로 따라 돈다(`adjustStation`). 직선 안 점 ·
|
||||
* 꼭짓점 아닌 자리(추가 측점)도 같은 규칙 — 누른 점이 새 꺾임(기본 L 곡선)이 된다.
|
||||
*
|
||||
* 처음 계획선은 마디마다 꺾여 축 = 누른 점 ±1 → 사이 측점 없음(옛 동작 그대로).
|
||||
* `B05_Profile_UI_Profile_Alignment` 를 타입으로만 읽는다(순환 import 없음).
|
||||
@@ -23,21 +24,23 @@ export interface PivotAxes {
|
||||
inner: number[];
|
||||
}
|
||||
|
||||
/** 누른 점이 꺾임이면 두 축과 사이 변화점 — 직선 안쪽 · 시종점 · 변화점 아님이면 null. */
|
||||
/** 누른 점의 두 축과 사이 변화점 — 시종점 · 계획선 밖이면 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 right = index >= 0 ? index : pvi.findIndex((node) => node.chainage_m > chainageM);
|
||||
if (right <= 0 || (index >= 0 && index >= pvi.length - 1)) return null;
|
||||
const [leftSeg, rightSeg] = index >= 0 ? [index - 1, index] : [right - 1, right - 1];
|
||||
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;
|
||||
let from = leftSeg;
|
||||
while (from > 0 && same(from - 1, leftSeg)) from -= 1;
|
||||
let to = rightSeg + 1;
|
||||
while (to < pvi.length - 1 && same(to, rightSeg)) to += 1;
|
||||
return {
|
||||
fromM: pvi[from].chainage_m,
|
||||
fromZ: pvi[from].elevation_m,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/* 355-1 종단 ▲▼ 이웃 꺾임 축 돌림 단위검증 헬퍼 — `B05_Profile_UI_Profile_Alignment.ts` ·
|
||||
* `_Straighten.ts` 를 그 자리에서 트랜스파일해 세 경우를 Node 로 돌린다.
|
||||
* ① 처음 계획선 ▲ = 옛 값 ② 직선화 뒤 꺾임 ▲ = 사이 측점 한 직선 · 축 표고 그대로 · +0.1
|
||||
* ③ 직선 안쪽 ▲ = 옛 값. 옛 값 = 355-1 앞 adjustStation(아래 oldAdjust) 그대로.
|
||||
* ③ 직선 안 측점 ▲ = 그 점 새 꺾임 · 축 = 좌우 다음 꺾임(368-1) ④ 꼭짓점 아닌 자리 ▲ = 같은 규칙.
|
||||
* 옛 값 = 355-1 앞 adjustStation(아래 oldAdjust) 그대로.
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
@@ -132,19 +133,41 @@ const out = {};
|
||||
};
|
||||
}
|
||||
|
||||
// ③ 직선 안쪽(80 · 40~100 직선 위) — 꺾임 아님 → 옛 값.
|
||||
{
|
||||
/** 직선 안 점 ▲ — 축 40 · 100 그대로 · 사이 점 새 직선 위 · 누른 점 꺾임(368-1). */
|
||||
function tiltInside(pressM, innerS) {
|
||||
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),
|
||||
const before = align.buildAlignment(base, edits);
|
||||
const next = align.adjustStation(base, edits, pressM, 0.1);
|
||||
const after = align.buildAlignment(base, next);
|
||||
const p = pviZ(after, pressM);
|
||||
const z40 = pviZ(before, 40);
|
||||
const z100 = pviZ(before, 100);
|
||||
const line = (s) =>
|
||||
s < pressM
|
||||
? z40 + ((p - z40) * (s - 40)) / (pressM - 40)
|
||||
: z100 + ((p - z100) * (s - 100)) / (pressM - 100);
|
||||
return {
|
||||
planRise: align.planElevationAt(after, pressM) - align.planElevationAt(before, pressM),
|
||||
axisMove: [pviZ(after, 40) - z40, pviZ(after, 100) - z100],
|
||||
outsideMove: [20, 120, 140, 160].map((s) => pviZ(after, s) - pviZ(before, s)),
|
||||
innerResidual: innerS.map((s) => pviZ(after, s) - line(s)),
|
||||
innerMoved: innerS.map((s) => pviZ(after, s) - pviZ(before, s)),
|
||||
curveAtPress: after.curves.some((c) => Math.abs(c.chainage_m - pressM) < 1e-6),
|
||||
pressRadiusKey: Object.prototype.hasOwnProperty.call(
|
||||
next.curve_radii,
|
||||
align.chainageKey(pressM),
|
||||
),
|
||||
innerRadiusKeys: innerS.filter((s) =>
|
||||
Object.prototype.hasOwnProperty.call(next.curve_radii, align.chainageKey(s)),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// ③ 직선 안 측점(80 · 40~100 직선 위) — 80 새 꺾임 · 축 40 · 100 · 60 새 직선 위.
|
||||
out.inside = tiltInside(80, [60]);
|
||||
|
||||
// ④ 꼭짓점 아닌 자리(70) — 같은 규칙 · 60 · 80 새 직선 위.
|
||||
out.between = tiltInside(70, [60, 80]);
|
||||
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
|
||||
@@ -55,9 +55,17 @@ def test_꺾임_누르면_사이_측점이_한_직선(out):
|
||||
assert kink["oldWouldBreak"] == [0, 0]
|
||||
|
||||
|
||||
def test_직선_안쪽은_옛_값(out):
|
||||
assert out["inside"]["same"]
|
||||
assert out["inside"]["planRise"] == pytest.approx(0.1, abs=1e-4)
|
||||
@pytest.mark.parametrize("case", ["inside", "between"])
|
||||
def test_직선_안_점은_새_꺾임_축은_좌우_다음_꺾임(out, case):
|
||||
# 368-1: 직선 안 측점(80) · 꼭짓점 아닌 자리(70) 모두 그 점에 R · 축 40 · 100
|
||||
got = out[case]
|
||||
assert got["planRise"] == pytest.approx(0.1, abs=1e-4)
|
||||
assert got["axisMove"] == [0, 0]
|
||||
assert got["outsideMove"] == [0, 0, 0, 0]
|
||||
assert all(abs(value) < 1e-6 for value in got["innerResidual"])
|
||||
assert all(value > 0 for value in got["innerMoved"])
|
||||
assert got["curveAtPress"] and not got["pressRadiusKey"]
|
||||
assert got["innerRadiusKeys"] == []
|
||||
|
||||
|
||||
def test_부르는_곳_셋이_같은_함수():
|
||||
|
||||
Reference in New Issue
Block a user