- 1 가림막 저장(확정)도 도중 고침 검사 · 「저장 중 고친 값」 알림(키는 안 막음 · B07~B09 안 번짐) - 2b snapOptions 간격 = 10 만 · 옛 초안 0.1 · 1 m = null(load_station_snap_step 거울) - 3 추가 측점 = 측점 묶음 data-plus · B05 누름 · 가까이 표시가 그 속성으로 - 4d 숨은 동안 고른 카드 = 다음 다시 그림 끝에 밂 · 휠 · 누름이면 버림 - 7 pipe_calc set 다섯 = 풀개 · 횡단 · 종단 보기 onDispose - 8 저장 중 [초기화] = [저장]과 같은 알림 + 막음 · /route/reset = 프로젝트 잠금 안 - 9 isUserPick 거름 = 묶음 띄운 그 누름만 · 계산 중 새 누름 = 폄 - 시험 test_505_2_save_pick 새 · 398_1 busyPick 1 · 390_5 글 맞춤 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
178 lines
8.7 KiB
TypeScript
178 lines
8.7 KiB
TypeScript
/* =============================================================================
|
|
* design_structure_pipe_calc.ts
|
|
* 배관 명세 계산 — 관 길이(자동) · 보호공 높이 안내. 계산 자리 = ② TS 를 서버 Node 로:
|
|
* 관 길이는 횡단 기하 `computeStoredLayouts`(B06 · 서버 Node 진입점이 같은 함수로 `pipe_length_m` 를
|
|
* 셈 · 시험 test_b06_pipe_length) 그대로 — 여기서 새로 셈하지 않음 · 화면에 보이기만(저장 안 함).
|
|
* 높이 = 손 입력 · 횡단도 조정 높이는 B06 `withSpecDefaults` 가 같은 키(`*_revet_height_m`)에 실음 ·
|
|
* 빈 칸 안내 = 정의 기본값 · 집수정 모양 기본 = 횡단 기하 `DEFAULT_BASIN_SHAPE`(54-36).
|
|
* 흙막이 개수 = 주인 측점 유출 「추가 기슭막이(단)」(횡단 설계 patch `extra_wall_counts` · 54-1) 중 지형이 세운 단(54-10) ·
|
|
* 쓰기는 횡단 보기 단 수 제어로만(`setPipeExtraCount` · 54-27).
|
|
* ========================================================================== */
|
|
|
|
import type { CrossSection } from "../../B06_Section/B06_Section_Api_Fetch";
|
|
import { computeStoredLayouts } from "../../B06_Section/B06_Section_Structure_Layouts";
|
|
export { DEFAULT_BASIN_SHAPE } from "../../B06_Section/logic/B06_Section_UI_Cross_Culvert_Basin";
|
|
export { pipeLengthText } from "../../B06_Section/logic/B06_Section_UI_Cross_Culvert_Const"; // 「8 m (7.6)」(225-1)
|
|
import { PIPE_MATCH_M } from "./design_selection";
|
|
|
|
/** 횡단 측점 목록을 대는 곳 — 횡단 보기가 자료를 받으면 걸어 둠(종단만 연 동안은 없음). */
|
|
let sectionsSource: (() => readonly CrossSection[] | null) | null = null;
|
|
export function setPipeSectionsSource(source: () => readonly CrossSection[] | null): void {
|
|
sectionsSource = source;
|
|
}
|
|
|
|
/** 횡단 측점 목록 읽기 — 노면공 확폭 구간 상자(87-3)가 확폭을 읽음 · 자료 없으면 null. */
|
|
export const pipeSections = (): readonly CrossSection[] | null => sectionsSource?.() ?? null;
|
|
|
|
/** 유출 단 수를 대는 곳 — 횡단 보기의 세션 값 우선(`countFor`) · 없으면 정본 patch.
|
|
* set 마다 풀개를 돌려줌(아직 제 값일 때만 비움) — 보기 onDispose 에 붙여 떠난 페이지를 놓음(505-2). */
|
|
let extraCountSource: ((section: CrossSection) => number) | null = null;
|
|
export function setPipeExtraCountSource(source: (section: CrossSection) => number): () => void {
|
|
extraCountSource = source;
|
|
return () => {
|
|
if (extraCountSource === source) extraCountSource = null;
|
|
};
|
|
}
|
|
|
|
/** 유출 단 수를 쓰는 곳 — 횡단 보기의 단 수 제어(54-27) · 종단 보기는 횡단 보기를 숨긴 채 세워 씀. */
|
|
let extraCountSink: ((section: CrossSection, count: number) => void) | null = null;
|
|
let crossOpener: (() => Promise<void>) | null = null;
|
|
export function setPipeExtraCountSink(
|
|
sink: (section: CrossSection, count: number) => void,
|
|
): () => void {
|
|
extraCountSink = sink;
|
|
return () => {
|
|
if (extraCountSink === sink) extraCountSink = null;
|
|
};
|
|
}
|
|
export function setPipeCrossOpener(opener: () => Promise<void>): () => void {
|
|
crossOpener = opener;
|
|
return () => {
|
|
if (crossOpener === opener) crossOpener = null;
|
|
};
|
|
}
|
|
|
|
/** 좌측 「추가 흙막이(단)」 → 주인 측점 유출 단 수(B06 횡단 조정과 같은 저장 자리 · 54-27). */
|
|
export async function setPipeExtraCount(chainageM: number | null, count: number): Promise<void> {
|
|
if (!extraCountSink) await crossOpener?.();
|
|
const found = ownerSection(chainageM);
|
|
if (found) extraCountSink?.(found.owner, count);
|
|
}
|
|
|
|
/** 유입 형식을 대는 곳 — 횡단 보기의 세션 값 우선(`valueFor`) · 없으면 정본 patch(54-13). */
|
|
type InletChoice = NonNullable<CrossSection["design"]>["inlet_structure"];
|
|
let inletSource: ((section: CrossSection) => InletChoice) | null = null;
|
|
export function setPipeInletSource(source: (section: CrossSection) => InletChoice): () => void {
|
|
inletSource = source;
|
|
return () => {
|
|
if (inletSource === source) inletSource = null;
|
|
};
|
|
}
|
|
|
|
/** 그 관의 주인 측점(관 자리에서 가장 가까운 측점) — 횡단 자료가 없으면 null. */
|
|
function ownerSection(
|
|
chainageM: number | null,
|
|
): { owner: CrossSection; sections: readonly CrossSection[] } | null {
|
|
const sections = sectionsSource?.() ?? null;
|
|
if (chainageM === null || !sections?.length) return null;
|
|
const holders = sections.filter(
|
|
(s) => Math.abs((s.culvert?.chainage_m ?? Infinity) - chainageM) < PIPE_MATCH_M,
|
|
);
|
|
if (!holders.length) return null;
|
|
const owner = holders.reduce((a, b) =>
|
|
Math.abs(b.chainage_m - chainageM) < Math.abs(a.chainage_m - chainageM) ? b : a,
|
|
);
|
|
return { owner, sections };
|
|
}
|
|
|
|
/** 관 길이(m) — 주인 측점 횡단 기하 값 · 못 세우면 저장된 `pipe_length_m` · 그것도 없으면 null. */
|
|
export function pipeLengthM(chainageM: number | null): number | null {
|
|
const found = ownerSection(chainageM);
|
|
if (!found) return null;
|
|
const { owner, sections } = found;
|
|
const drawn = computeStoredLayouts(owner, sections)?.culvert?.pipe?.lengthM;
|
|
const length = typeof drawn === "number" && drawn > 0 ? drawn : owner.design?.pipe_length_m;
|
|
return typeof length === "number" && length > 0 ? length : null;
|
|
}
|
|
|
|
/** 흙막이 개수 = 주인 측점 유출 「추가 기슭막이(단)」 중 지형이 세운 단(54-10) — 횡단 자료가 없으면
|
|
* null(모름). 저장 단 수가 더 커도 넘친 단은 컨테이너를 안 세움(값은 파일에 그대로). */
|
|
export function pipeExtraCount(chainageM: number | null): number | null {
|
|
const found = ownerSection(chainageM);
|
|
if (!found) return null;
|
|
const { owner, sections } = found;
|
|
const outlet = extraCountSource?.(owner) ?? owner.design?.extra_wall_counts?.outlet ?? 0;
|
|
if (!outlet || !owner.design) return outlet;
|
|
const counts = { basin: 0, ...owner.design.extra_wall_counts, outlet };
|
|
const probe = { ...owner, design: { ...owner.design, extra_wall_counts: counts } };
|
|
return computeStoredLayouts(probe, sections)?.culvert?.extraWalls.length ?? outlet;
|
|
}
|
|
|
|
/** 그림이 쓰는 유입 · 유출 값(54-13) — 집수정 모양(없으면 null = 흙막이 벽) · 벽 형태(유입 · 유출 ·
|
|
* 흙막이 n). 폼 빈 · 자동 칸에 보여 주기만. 횡단 자료가 없으면 null(모름). */
|
|
export function pipeDrawn(chainageM: number | null): {
|
|
basin: string | null;
|
|
forms: Record<string, string | null>;
|
|
extras: Array<string | null>;
|
|
} | null {
|
|
const found = ownerSection(chainageM);
|
|
const design = found?.owner.design;
|
|
if (!found || !design) return null;
|
|
const { owner, sections } = found;
|
|
const outlet = extraCountSource?.(owner) ?? design.extra_wall_counts?.outlet ?? 0;
|
|
const counts = { basin: 0, ...design.extra_wall_counts, outlet };
|
|
const inlet = inletSource?.(owner) ?? design.inlet_structure;
|
|
const probe = {
|
|
...owner,
|
|
design: { ...design, extra_wall_counts: counts, inlet_structure: inlet },
|
|
};
|
|
const layout = computeStoredLayouts(probe, sections)?.culvert;
|
|
if (!layout) return null;
|
|
const formOf = (role: string) => layout.walls.find((w) => w.role === role)?.form ?? null;
|
|
return {
|
|
basin: layout.basin?.shape ?? null,
|
|
forms: { inlet: formOf("inlet"), outlet: formOf("outlet") },
|
|
extras: layout.extraWalls.map((w) => w.form),
|
|
};
|
|
}
|
|
|
|
/** 관 보호공 [연동] · [경사](54-30) — null = 그 쪽을 쓸 수 없음(벽이 서는 옆 측점 없음 · 구간 없음). */
|
|
export interface PipeRevetChoice {
|
|
linked: boolean | null;
|
|
grade: boolean | null;
|
|
}
|
|
/** 읽고 쓰는 곳 — 횡단 보기의 연동 제어(`revetLink` · 옛 저장 자리)가 걸어 둠. */
|
|
interface RevetChoiceBridge {
|
|
read: (owner: CrossSection, sections: readonly CrossSection[]) => PipeRevetChoice;
|
|
write: (
|
|
owner: CrossSection,
|
|
sections: readonly CrossSection[],
|
|
kind: "link" | "grade",
|
|
on: boolean,
|
|
) => void;
|
|
}
|
|
let revetChoice: RevetChoiceBridge | null = null;
|
|
export function setPipeRevetChoiceBridge(bridge: RevetChoiceBridge): () => void {
|
|
revetChoice = bridge;
|
|
return () => {
|
|
if (revetChoice === bridge) revetChoice = null;
|
|
};
|
|
}
|
|
|
|
/** 그 관의 [연동] · [경사] 지금 값 — 횡단 자료가 없으면 null(모름). */
|
|
export function pipeRevetChoice(chainageM: number | null): PipeRevetChoice | null {
|
|
const found = ownerSection(chainageM);
|
|
return found && revetChoice ? revetChoice.read(found.owner, found.sections) : null;
|
|
}
|
|
|
|
/** 좌측 [연동] · [경사] → 횡단 연동 제어(종단 보기는 횡단 보기를 숨긴 채 세워 씀 · 54-27 과 같은 길). */
|
|
export async function setPipeRevetChoice(
|
|
chainageM: number | null,
|
|
kind: "link" | "grade",
|
|
on: boolean,
|
|
): Promise<void> {
|
|
if (!revetChoice) await crossOpener?.();
|
|
const found = ownerSection(chainageM);
|
|
if (found) revetChoice?.write(found.owner, found.sections, kind, on);
|
|
}
|