- 명세 폼 고르기 칸 — 비었거나 자동 규칙이면 빈 보기 자리에 「값 (기본)」 · 「값 (자동)」 · 고른 값과 구별 · 저장 값 그대로 - 배관: 관경 · 유출 구조 = 정의 기본값 · 유입 구조 · 집수정 모양 · 벽 형태 = 횡단 기하 결과(세션 유입 형식 반영) - 옛 「자동(규칙)」(유입 기슭막이 + 형식 없음) = 「집수정 (자동)」 등 그림 결과 · 유입 구조를 고를 때까지 형식 revet 안 굳음 - 그림 값을 보이던 위 칸 아래를 고치면 그 위 값을 고른 것(집수정 높이 → 집수정) - 세월교 관경 · 유입 · 유출 / BOX 규격 · 유입 · 유출 = 정의 기본값(날개벽 설치 규칙 = 엔진과 같음) - 정의 그룹 꼴 칸 — 빈 칸에 정의 기본값이 있으면 「값 (기본)」 - 시험: ui_checks/54-13_그림값_표시.js Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK
107 lines
5.7 KiB
TypeScript
107 lines
5.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`)에 실음 ·
|
|
* 빈 칸 안내 = 관경 기준 최소 높이(`pipeWallMinHeightM` ↔ 파이썬 `pipe_wall_min_height_m` 짝).
|
|
* 흙막이 개수 = 주인 측점 유출 「추가 기슭막이(단)」(횡단 설계 patch `extra_wall_counts` · 54-1) 중 지형이 세운 단(54-10) — 읽기만.
|
|
* ========================================================================== */
|
|
|
|
import { pipeWallMinHeightM } from "../../common_util/common_util_culvert_sets";
|
|
import type { CrossSection } from "../../B06_Section/B06_Section_Api_Fetch";
|
|
import { computeStoredLayouts } from "../../B06_Section/B06_Section_Structure_Layouts";
|
|
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;
|
|
}
|
|
|
|
/** 유입 형식을 대는 곳 — 횡단 보기의 세션 값 우선(`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),
|
|
};
|
|
}
|
|
|
|
/** 흙막이 높이 빈 칸 안내 — 관경 기준 최소 높이(엔진이 빈 칸을 이 값으로 그림). */
|
|
export function guardHeightHint(diameterMm: string): string {
|
|
const d = Number(diameterMm || "1000") / 1000;
|
|
return `비우면 관경 기준 ${pipeWallMinHeightM(d)}`;
|
|
}
|