- 구조물정의 판 16(판_15 이력): 유입 구조 기본 집수정 · 유출 형태 찰 · 유입 · 유출 높이 1.5 · 집수정 높이 1.5 새 칸 · 사면안정 흙막이 형태 찰 · 높이 1.5 - 엔진 짝(파이썬 · TS): 집수정 높이 빈 값 = 정의 기본값 - 횡단 기하: 자동 집수정 모양 = L형(DEFAULT_BASIN_SHAPE 한 곳) · ㄴ·ㄷ형을 못 세우는 지형만 I형 - 명세 기본값 칸(default) — 추가 흙막이 새 상자 = 찰 · 10 · 1.5 · 5 · 5 실제 값 · 이미 있는 상자 값 그대로 - B05 폼: 빈 숫자 칸 「값 (기본)」 · 빈 유입 구조 = 집수정 (기본) · 집수정 모양 L형 (기본) · 추가 흙막이 상자에도 [연동][경사] - [연동][경사] 단추 안 여백 줄임 · 한 줄(높이 31.7 그대로) - 시험: 기본값 바뀐 기대값 맞춤(culvert_set · structure_span · new_structure_defaults) 화면 확인(sub5_검증): 새 배관 = 관경 1000 · 집수정 · L형 (기본) · 1.5 · 유출 흙막이 찰 10 · 1.5 · 5 · 5 · 단 0 / 36+0 단 0→1 새 흙막이 1 = 찰 10 · 1.5 · 5 · 5 · 연동 · 경사 켬 / 사면안정 새 흙막이 = 찰 10 · 1.5 · 5 · 5 / 단추 여섯 31.7 한 줄 · 세션 캐시 비워 되돌림 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ
156 lines
7.8 KiB
TypeScript
156 lines
7.8 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/B06_Section_UI_Cross_Culvert_Basin";
|
|
import { PIPE_MATCH_M } from "./design_selection";
|
|
|
|
/** 횡단 측점 목록을 대는 곳 — 횡단 보기가 자료를 받으면 걸어 둠(종단만 연 동안은 없음). */
|
|
let sectionsSource: (() => readonly CrossSection[] | null) | null = null;
|
|
export function setPipeSectionsSource(source: () => readonly CrossSection[] | null): void {
|
|
sectionsSource = source;
|
|
}
|
|
|
|
/** 유출 단 수를 대는 곳 — 횡단 보기의 세션 값 우선(`countFor`) · 없으면 정본 patch. */
|
|
let extraCountSource: ((section: CrossSection) => number) | null = null;
|
|
export function setPipeExtraCountSource(source: (section: CrossSection) => number): void {
|
|
extraCountSource = source;
|
|
}
|
|
|
|
/** 유출 단 수를 쓰는 곳 — 횡단 보기의 단 수 제어(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;
|
|
}
|
|
export function setPipeCrossOpener(opener: () => Promise<void>): void {
|
|
crossOpener = opener;
|
|
}
|
|
|
|
/** 좌측 「추가 흙막이(단)」 → 주인 측점 유출 단 수(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;
|
|
}
|
|
|
|
/** 그 관의 주인 측점(관 자리에서 가장 가까운 측점) — 횡단 자료가 없으면 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;
|
|
}
|
|
|
|
/** 그 관의 [연동] · [경사] 지금 값 — 횡단 자료가 없으면 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);
|
|
}
|