Files
Aislo/B05_Profile/B05_Profile_UI_RouteEdit_Shift.ts
T
eomsangdonandClaude Opus 5.5 842473bcea fix(B06): 364-7 중심점 좌우 = 노면 그대로 — 바뀐 구간 첫 계획선 맞춤에 쓸린 모든 측점 계획고 · 꺾임 · R 을 옮기기 전 종단으로 되살림(holdGrade · 한 측점 보정 대신)
- 까닭: 노선이 바뀌면 planline 단계가 바뀐 구간을 새 지반 첫 계획선으로 다시 맞춤(B05 꺾임점 끌기와 같은 길) · 옛 보정은 누른 측점 하나만 되돌림
- 고침: 옛 · 새 꺾은선 꼭짓점 · 측점을 견줘 다른 곳만 계획고 편집(바뀐 구간 끝까지 측점 열쇠 그대로 · 뒤는 길이 차) · 옛 R 되살림 · B05 꺾임점 끌기 규칙은 그대로
- 시험: 356 묶음에 바뀐 구간 안 사용자 계획고 · R 넣고 모든 측점 ±0.001 · 최대 기울기 · R 그대로 · 356-4 헬퍼 새 함수로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QfHKQs7J3hQELKMWQMUJEy
2026-10-17 00:32:55 +09:00

97 lines
4.5 KiB
TypeScript

/* =============================================================================
* B05_Profile_UI_RouteEdit_Shift.ts
* 횡단 중심점 좌우 → 노선 편집(356-3) — B06 카드가 부르는 host(`setRouteShiftHost`)를 편집기가 등록.
*
* 한 번 = 노드 옮김 · 끼움 한 번(고름은 `logic/B05_Profile_RouteShift`) → 편집기 한 걸음(applyEdit) →
* 계산 묶음이 뒤 측점을 조용히 다시 셈 → 새 종단이 오면 옮기기 전 종단을 그대로(노면 그대로 · 364-7 ·
* 바뀐 구간 첫 계획선 맞춤에 휩쓸리지 않게 · 계획고 편집). 둘을 페이지 되돌리기 한 걸음으로(사진첩 pause · resume).
* ========================================================================== */
import type { DesignPipeline } from "../A00_Common/design/logic/design_pipeline";
import { pagePipeline, pipelineGrade } from "../A00_Common/design/design_pipeline_page";
import { setRouteShiftHost } from "../B06_Section/B06_Section_UI_Cross_Center";
import { pageHistory } from "./B05_Profile_UI_Profile_History";
import type { RouteEditHost } from "./B05_Profile_UI_RouteEdit";
import type { RouteEditSnapshot } from "./B05_Profile_UI_RouteEdit_History";
import type { EditedCurve } from "./logic/B05_Profile_UI_RouteEdit_Curve";
import { spliceNode } from "./logic/B05_Profile_UI_RouteEdit_Edits";
import { emptyEdits, type AlignmentEdits } from "./logic/B05_Profile_UI_Profile_Alignment";
import { holdGrade, planRouteShift } from "./logic/B05_Profile_RouteShift";
type Vertex = [number, number];
/** 새 종단을 기다리는 한계(ms) — 넘으면 높이 보정 없이 한 걸음을 닫음. */
const PROFILE_WAIT_MS = 10000;
/** 다음 조작의 종단 단계(가장 늦은 차례)가 끝날 때 true · 한계를 넘으면 false. */
function nextProfile(pipe: DesignPipeline): Promise<boolean> {
return new Promise((resolve) => {
const done = (ok: boolean): void => {
clearTimeout(timer);
stop();
resolve(ok);
};
const timer = setTimeout(() => done(false), PROFILE_WAIT_MS);
const stop = pipe.subscribe((event) => {
if (
event.kind === "stage" &&
event.stage === "profile" &&
event.generation === pipe.generation
)
done(true);
}, "중심점 좌우");
});
}
/**
* 편집기가 자료를 읽은 뒤 부름 — `columns` = 지금 편집값(산 배열 · 끼움 · 옮김이 그 자리를 고침) ·
* `apply` = 편집기 한 걸음 · `drawn` = [그린 계획노선, 곡선표]. 편집기가 떠나면 host 를 뗌.
*/
export function bindRouteShift(
host: RouteEditHost,
projectId: string,
columns: () => RouteEditSnapshot,
apply: (message: string) => void,
drawn: () => [Vertex[], EditedCurve[]],
): void {
setRouteShiftHost(async (chainageM, deltaM) => {
const pipe = pagePipeline(projectId);
if (pipe && !pipe.output("profile")) await nextProfile(pipe); // 막 세운 묶음(B06 바로 열기) — 첫 셈
const now = columns();
const [line, curves] = drawn();
const plan = planRouteShift({
planned: now.planned,
line: line.length ? line : now.planned,
curves,
locked: now.apexLock,
chainageM,
deltaM,
});
if (!plan) return "노선을 아직 못 읽었습니다.";
if (plan.kind === "locked") return "고정한 노드라 옮기지 않았습니다 — 고정을 먼저 푸세요.";
const before = pipe?.output("profile");
const lineBefore = pipe?.output("route")?.line ?? [...line]; // 끼움 · 옮김이 산 배열을 고침
const album = pageHistory();
album?.pause();
try {
const routed = pipe ? nextProfile(pipe) : null;
if (plan.kind === "move") now.planned[plan.index] = plan.to;
else spliceNode(now, plan.index, plan.at);
const side = deltaM > 0 ? "좌" : "우";
apply(`횡단 중심점 — 측점 노선을 ${side}로 ${Math.abs(deltaM).toFixed(1)}m 옮겼습니다.`);
if (!pipe || !before || !(await routed)) return null;
const base = pipe.output("planline");
const edits = (pipe.input("alignment") as AlignmentEdits | null) ?? emptyEdits();
const lineAfter = pipe.output("route")?.line ?? drawn()[0];
const pinned = base && holdGrade(base, edits, before, lineBefore, lineAfter);
if (!pinned) return null;
const graded = nextProfile(pipe);
pipelineGrade(projectId, pinned);
await graded;
return null;
} finally {
album?.resume();
}
});
host.onDispose(() => setRouteShiftHost(null));
}