- 흙막이 양식 한 벌(design_structure_guard) — 배관 · 박스 · 세월교가 같은 칸(형태 · 길이 · 높이 · 측점 전후) · 추가 흙막이 · 도수로/돌붙임 상자
- 세월교 · 박스 유입 · 유출: 흙막이 고르면 흙막이 칸(키 {side}_revet_* · 기본 찰 10 · 1.5 · 5 · 5) · [연동][경사] 옵션 줄
- B06 측벽 높이 · 좌우 · 상하 · 바닥판 읽기 줄을 세월교 · 박스 유입 · 유출 칸에 안 옮김(강조만)
- 유입 · 유출 「없음」 = 목록 램프 · 구조물 조합 잇기 줄 없음(명세 none)
- 세월교: 수량(련) 관경 바로 뒤 · 련 기본 2 · 새 시설 빈 월류 높이 = 유량 오면 필요 수심 · 종류 바꿀 때 유량 넘김
- 물넘이 포장: 경사각 기본 3° · 월류 폭 · 높이 칸(세월교와 같은 키 ford_width_m · ford_height_m · 같은 계산)
- 구조물정의 판 18(판_17 이력) · 이름 표 · 집계표 선택 컨테이너 짝 · 저장 병합 키
- 시험: 세월교 · 박스 · 물넘이 명세 기대값 · 흙막이 짝 자리 옮김
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ
133 lines
5.6 KiB
TypeScript
133 lines
5.6 KiB
TypeScript
/* =============================================================================
|
||
* design_structure_box.ts
|
||
* 횡단배수 › 박스암거 명세 — 사용자 구조물 표 02 고친 판(`docs/raw/구조물정의_표/02_횡단배수_박스암거.md`) 그대로.
|
||
*
|
||
* 컨테이너: 본체 · 유입 구조 · 유출 구조(필수) · 흙막이(선택 · 여러 개) · 도수로/돌붙임(선택 · 하나 · 65-2).
|
||
* 구조 · 정본 = 이 명세 · 이름만 정의(M02)에서 바꿈.
|
||
* 저장 키 = pipe_points.json 옛 키 그대로(B06 횡단 · B07 · B08 이 읽는 자리):
|
||
* · 본체 = `body_width_m` · `body_height_m` 수 — 규격 칸은 저장 안 함(폭 · 높이로 되찾음 · `bodyOf`)
|
||
* · 사용자 입력에 두 칸 다 비면 `body_custom` = 「사용자 입력」 표시만
|
||
* · 유입 · 유출 구조 = `wing_in` · `wing_out`(없음 · 날개벽 · 흙막이 · 옛 「있음」 = 날개벽)
|
||
* — 엔진은 날개벽만 세움(`_wing_spec` ↔ `wingSpec` 짝) · 흙막이 = 배관과 같은 양식(`{side}_revet_*` · 65-2) ·
|
||
* 「없음」 = 목록 램프 · 잇기 줄 없음(65-3)
|
||
* 표 02 에 없는 옛 날개벽 치수 칸은 화면에서 빼고 값은 남김(지난 정의 값).
|
||
* import = 같은 폴더 명세뿐 — 시험이 이 폴더 파일만 옮겨 돌림.
|
||
* ========================================================================== */
|
||
|
||
import type { Instance, SpecContainer, SpecItem, StructureSpec } from "./design_structure_spec";
|
||
import { CHUTE_ADD, guardItems, revetKeys, SOIL_ADD } from "./design_structure_guard";
|
||
|
||
export const BOX_CUSTOM = "사용자 입력";
|
||
/** 사용자 입력에 가로 · 세로 둘 다 빈 채 저장했다는 표시(폭 · 높이 수가 없을 때만 적음). */
|
||
export const BOX_CUSTOM_KEY = "body_custom";
|
||
/** 본체 규격 → [가로, 세로](m) — 실무 관측 규격(2026-08-17 사용자 확정). */
|
||
const BOX_PRESETS: Record<string, readonly [number, number]> = {
|
||
"2.0×2.0": [2, 2],
|
||
"3.0×3.0": [3, 3],
|
||
};
|
||
|
||
/** 유입 · 유출 구조 — 같은 선택지 · 키만 다름 · 흙막이면 흙막이 칸(65-2). */
|
||
const guardContainer = (
|
||
id: "inlet" | "outlet",
|
||
title: string,
|
||
link: string,
|
||
key: string,
|
||
): SpecContainer => ({
|
||
id,
|
||
title,
|
||
required: true,
|
||
none: "없음",
|
||
link,
|
||
items: [
|
||
{
|
||
id: "structure",
|
||
label: "구조",
|
||
kind: "select",
|
||
key,
|
||
choices: [
|
||
{ value: "없음", label: "없음" },
|
||
{ value: "날개벽", label: "날개벽" },
|
||
{ value: "흙막이", label: "흙막이" },
|
||
],
|
||
},
|
||
...guardItems({ 흙막이: revetKeys(id) }, undefined, true),
|
||
],
|
||
});
|
||
|
||
/** 사용자 입력일 때만 서는 숫자 칸. */
|
||
const customSide = (id: string, label: string, key: string): SpecItem => ({
|
||
id,
|
||
label,
|
||
kind: "number",
|
||
step: 0.1,
|
||
by: { item: "size", map: { [BOX_CUSTOM]: { key } } },
|
||
});
|
||
|
||
export const BOX_SPEC: StructureSpec = {
|
||
typeId: "box_culvert",
|
||
containers: [
|
||
{
|
||
id: "body",
|
||
title: "본체",
|
||
required: true,
|
||
link: "gboxbody",
|
||
items: [
|
||
{
|
||
id: "size",
|
||
label: "본체 규격",
|
||
kind: "select",
|
||
choices: [
|
||
...Object.keys(BOX_PRESETS).map((value) => ({
|
||
value,
|
||
label: value.replace("×", " × "),
|
||
})),
|
||
{ value: BOX_CUSTOM, label: BOX_CUSTOM },
|
||
],
|
||
},
|
||
customSide("width", "가로 (m)", "body_width_m"),
|
||
customSide("height", "세로 (m)", "body_height_m"),
|
||
],
|
||
},
|
||
guardContainer("inlet", "유입 구조", "gboxin", "wing_in"),
|
||
guardContainer("outlet", "유출 구조", "gboxout", "wing_out"),
|
||
SOIL_ADD,
|
||
CHUTE_ADD,
|
||
],
|
||
// 표 02 에 없는 옛 날개벽 치수 — 화면에서 빼고 값은 남김(지난 정의 값)
|
||
retired: ["in", "out"].flatMap((side) =>
|
||
["height_m", "length_m", "angle_deg"].map((field) => `wing_${side}_${field}`),
|
||
),
|
||
};
|
||
|
||
/** 옛 저장 → 폼에 줄 옵션 — 설치 「있음」 = 날개벽(같은 뜻 · 저장하면 새 값으로 굳음). */
|
||
export function boxOptionsIn(
|
||
options: Record<string, string | number>,
|
||
): Record<string, string | number> {
|
||
const out = { ...options };
|
||
for (const key of ["wing_in", "wing_out"]) if (out[key] === "있음") out[key] = "날개벽";
|
||
return out;
|
||
}
|
||
|
||
/** 저장 폭 · 높이 → 본체 칸 값 — 규격과 맞으면 그 규격 · 손 값이 하나라도 있거나 사용자 입력 표시면
|
||
* 사용자 입력(빈 칸은 빈 채) · 아니면 빈 값. */
|
||
export function bodyOf(options: Record<string, string | number>): Instance {
|
||
const width = Number(options.body_width_m);
|
||
const height = Number(options.body_height_m);
|
||
const preset = Object.entries(BOX_PRESETS).find(
|
||
([, [w, h]]) => Math.abs(w - width) < 0.05 && Math.abs(h - height) < 0.05,
|
||
);
|
||
if (preset) return { size: preset[0] };
|
||
if (!(width > 0) && !(height > 0) && options[BOX_CUSTOM_KEY] !== BOX_CUSTOM) return { size: "" };
|
||
const side = (n: number): string => (n > 0 ? String(n) : "");
|
||
return { size: BOX_CUSTOM, width: side(width), height: side(height) };
|
||
}
|
||
|
||
/** 본체 칸 값 → 저장 폭 · 높이 — 규격이면 규격 수(사용자 입력은 명세 키가 적음) · 사용자 입력에 두 칸 다
|
||
* 비면 표시 키만(다시 열 때 「사용자 입력」). */
|
||
export function bodyOptions(body: Instance | undefined): Record<string, string | number> {
|
||
const preset = BOX_PRESETS[body?.size ?? ""];
|
||
if (preset) return { body_width_m: preset[0], body_height_m: preset[1] };
|
||
const blank = !(body?.width ?? "").trim() && !(body?.height ?? "").trim();
|
||
return body?.size === BOX_CUSTOM && blank ? { [BOX_CUSTOM_KEY]: BOX_CUSTOM } : {};
|
||
}
|