Files
Aislo/A00_Common/design/design_pipe_ids.ts
T
eomsangdonandClaude Opus 5.5 ccccb9f527 fix(B05 구조물 · 관 자리): 등고선 좌표 따라감 · 관 짝 id · B06 관 열쇠 pipe (410 · 411-B · 411-D)
- 좌표 낡음 판정 — 좌표가 그 누가거리를 안 낳으면(마커 · 방향키 · 폼으로 누가거리만 옮김) 노선 위 자리로 다시 적음 · TS placeOnLine ↔ 파이썬 fill_structure_coordinates 짝(보낸 좌표가 누가거리를 낳으면 살림)
- 따라감 단계 — 좌표 없는 관 초안은 앞 따라감 좌표 · site 간격 넘김
- 배수 편집기 — 노선 초안 follow 가 다시 매긴 관 자리를 id 짝으로 받음(시설 속성 · 고름 함께) · 초안 버림 = 저장분 다시 읽음
- 관 짝 차례 → id — 관 알림에 고정 id · followPipeReports · shownPipes(movePipeIds 한 번에 옮김) · followedMarks 같은 자리 먼저
- B06 측점 열쇠 — 서버 관 id 도 pipe: (계산 묶음과 같은 갈래)
- 시험 test_410_follow_xy · test_411_b_pipe_id_pairing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
2026-10-18 09:26:18 +09:00

105 lines
5.2 KiB
TypeScript

/* =============================================================================
* A00_Common/design/design_pipe_ids.ts
* 관 세션 id(323-5) — 관을 처음 볼 때 자리마다 붙이는 id · 옮겨도 그대로 · 저장 안 함(파일 모양 그대로).
*
* id = `pipe-s` + 차례(`pipe-s3`) — 머리 `pipe-` 로 관임을 가름(옛 자리 이름 `pipe-{누가거리}` 와 같은 머리) ·
* 누가거리는 id 에서 읽지 않음(`pipeChainageOf`). 장부 = 세션 `pipe-ids`(고름 `structure-pick` 과 같은 수명 ·
* 초안 비우기에 함께 비움). 자리 → id = 찾는 거리(`PIPE_MATCH_M`) 안 가장 가까운 것 · 없으면 새 차례 ·
* 옮김 = `movePipeId`(고름 따라가기 `followMovedPipe` 가 부름).
* 프로젝트 = 보기가 열 때 정함(`usePipeIds`) — 그리는 함수(카드 · 알약)가 프로젝트를 몰라도 같은 id.
* 프로젝트가 없으면(시험 · 도움 파일) 옛 자리 이름.
* ========================================================================== */
import { readState, writeState } from "../b_page_state";
import { isNear, nearestWithin, readSelection, selectDesign } from "./design_selection";
/** 관 찾는 거리 — 정의는 `design_selection` 한 곳(배수 패널이 관 옮김과 함께 가져감). */
export { PIPE_MATCH_M } from "./design_selection";
interface Book {
next: number;
ids: Array<{ id: string; at: number }>;
}
let active: string | null = null;
/** 지금 프로젝트 — 종단 · 횡단 보기가 열 때. */
export function usePipeIds(projectId: string | null): void {
active = projectId;
}
const bookOf = (projectId: string): Book =>
readState<Book>("pipe-ids", projectId) ?? { next: 1, ids: [] };
/** 그 자리 관의 id — 장부에 없으면 새 차례로 적음. */
export function pipeIdAt(chainageM: number, projectId: string | null = active): string {
if (!projectId) return `pipe-${chainageM.toFixed(2)}`;
const book = bookOf(projectId);
const hit = nearestWithin(book.ids, chainageM, (entry) => entry.at);
if (hit) return hit.id;
const id = `pipe-s${book.next}`;
writeState(
"pipe-ids",
{ next: book.next + 1, ids: [...book.ids, { id, at: chainageM }] },
projectId,
);
return id;
}
/** id 의 관 자리 — 장부 · 옛 자리 이름 순 · 관 id 가 아니면 null. */
export function pipeChainageOf(id: string, projectId: string | null = active): number | null {
if (!id.startsWith("pipe-")) return null;
const hit = projectId ? bookOf(projectId).ids.find((entry) => entry.id === id) : undefined;
if (hit) return hit.at;
const legacy = Number(id.slice("pipe-".length));
return Number.isFinite(legacy) ? legacy : null;
}
/** 관을 옮김 — 옛 자리 id 가 새 자리로(새 자리에 남은 옛 기록은 뺌) · 옮긴 관의 id. */
export function movePipeId(fromM: number, toM: number, projectId: string | null = active): string {
if (!projectId) return `pipe-${toM.toFixed(2)}`;
return movePipeIds([[fromM, toM]], projectId)[0] ?? pipeIdAt(toM, projectId);
}
/** 관 여럿을 한 번에 옮김(노선 따라감 · 411-B) — 옮길 기록을 먼저 다 찾고 옮김(앞 관이 뒤 관 옛 자리로 가도
* 뒤 관 기록을 안 지움) · 새 자리에 남은 옮기지 않는 옛 기록은 뺌 · 옮긴 관마다 id(못 찾으면 null). */
export function movePipeIds(
moves: ReadonlyArray<readonly [number, number]>,
projectId: string | null = active,
): Array<string | null> {
if (!projectId) return moves.map(() => null);
const book = bookOf(projectId);
const moving = moves.map(([fromM]) => nearestWithin(book.ids, fromM, (entry) => entry.at));
const to = new Map(moving.flatMap((entry, i) => (entry ? [[entry, moves[i][1]]] : [])));
const still = book.ids.filter((entry) => !to.has(entry));
const stale = new Set([...to.values()].map((at) => nearestWithin(still, at, (e) => e.at)));
const ids = book.ids
.filter((entry) => !stale.has(entry))
.map((entry) => (to.has(entry) ? { ...entry, at: to.get(entry)! } : entry));
if (to.size) writeState("pipe-ids", { next: book.next, ids }, projectId);
return moving.map((entry) => entry?.id ?? null);
}
/** 관 목록에 세션 id 를 얹은 사본 — 측점 계획의 덮는 구조물 id(종단 측점 id · 알약 짝)가 세션 id. */
export function withPipeIds<T extends { chainage_m: number }>(
pipes: ReadonlyArray<T>,
projectId: string | null = active,
): Array<T & { id: string }> {
return pipes.map((pipe) => ({ ...pipe, id: pipeIdAt(pipe.chainage_m, projectId) }));
}
/** 관을 옮김 — 관 세션 id 가 새 자리로(323-5 · id 그대로) · 그 관을 골라 둔 고름이면 자리만 새로(부재키
* 그대로 · 옛 자리 이름 id 면 세션 id 로). 다른 것을 골랐으면 그대로. */
export function followMovedPipe(projectId: string | null, fromM: number, toM: number): void {
const id = movePipeId(fromM, toM, projectId);
const picked = readSelection(projectId);
if (!picked || picked.chainageM === null || !isNear(picked.chainageM, fromM)) return;
const sid = picked.structureId;
if (sid !== null && !sid.startsWith("pipe-")) return;
selectDesign(
projectId,
{ chainageM: toM, structureId: sid === null ? null : id },
{ partKey: picked.partKey },
);
}