- 중심점 = 보라 선 고름 끝 조각(판정 원 · 보라 선 위 · 다른 고름과 하나만) · 십자는 누름 통과 - 좌우 = 노선 편집기가 등록한 host(B05_Profile_UI_RouteEdit_Shift) — 0.5 m 안 노드 옮김 · 곡선 안 = 교각점 · 아니면 끼움 · 고정 노드 안 옮김 - 끄는 동안 · 키를 잇는 동안 십자만 옮겨 보임 · 놓을 때 한 번 커밋 - 뒤 측점은 계산 묶음이 조용히 · 그 측점 계획고 = 옮기기 전 값(계획고 편집) · 노선 + 계획고 = 되돌리기 한 걸음 - B06 을 바로 열어 편집기가 없으면 종단 보기를 숨긴 채 세움(design_page 등록) - 시험 test_356 · helper_356(노드 고름 · 뒤 측점 범위 · 높이 그대로 · 되돌리기 한 걸음 · 바로 열기) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
100 lines
4.4 KiB
TypeScript
100 lines
4.4 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_UI_RouteEdit_Shift.ts
|
|
* 횡단 중심점 좌우 → 노선 편집(356-3) — B06 카드가 부르는 host(`setRouteShiftHost`)를 편집기가 등록.
|
|
*
|
|
* 한 번 = 노드 옮김 · 끼움 한 번(고름은 `logic/B05_Profile_RouteShift`) → 편집기 한 걸음(applyEdit) →
|
|
* 계산 묶음이 뒤 측점을 조용히 다시 셈 → 새 종단이 오면 그 측점 계획고를 옮기기 전 값으로(바뀐 구간 첫
|
|
* 계획선 맞춤에 휩쓸리지 않게 · 계획고 편집). 둘을 페이지 되돌리기 한 걸음으로(사진첩 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,
|
|
planElevationAt,
|
|
type AlignmentEdits,
|
|
} from "./logic/B05_Profile_UI_Profile_Alignment";
|
|
import { pinStationHeight, 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 profile = pipe?.output("profile");
|
|
const zBefore = profile ? planElevationAt(profile, chainageM) : null;
|
|
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 || zBefore === null || !(await routed)) return null;
|
|
const base = pipe.output("planline");
|
|
const edits = (pipe.input("alignment") as AlignmentEdits | null) ?? emptyEdits();
|
|
const pinned = base && pinStationHeight(base, edits, chainageM, zBefore);
|
|
if (!pinned) return null;
|
|
const graded = nextProfile(pipe);
|
|
pipelineGrade(projectId, pinned);
|
|
await graded;
|
|
return null;
|
|
} finally {
|
|
album?.resume();
|
|
}
|
|
});
|
|
host.onDispose(() => setRouteShiftHost(null));
|
|
}
|