Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1
This commit is contained in:
@@ -128,7 +128,7 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {
|
|||||||
const drawn = choices.find((ch) => ch.value === now);
|
const drawn = choices.find((ch) => ch.value === now);
|
||||||
// 그림이 쓰는 값은 빈 보기 자리에 꼬리를 달아 보임 — 고른 값과 구별(54-13)
|
// 그림이 쓰는 값은 빈 보기 자리에 꼬리를 달아 보임 — 고른 값과 구별(54-13)
|
||||||
const name = drawn ? choiceLabel(c, item, drawn, names) : now;
|
const name = drawn ? choiceLabel(c, item, drawn, names) : now;
|
||||||
const blank = !tag ? "— 안 정함 —" : name ? `${name} (${tag})` : tag;
|
const blank = !tag ? "선택 안 함" : name ? `${name} (${tag})` : tag;
|
||||||
select.replaceChildren(
|
select.replaceChildren(
|
||||||
new Option(blank, ""),
|
new Option(blank, ""),
|
||||||
...choices.map((ch) => new Option(choiceLabel(c, item, ch, names), ch.value)),
|
...choices.map((ch) => new Option(choiceLabel(c, item, ch, names), ch.value)),
|
||||||
@@ -145,8 +145,8 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {
|
|||||||
input.value = values[item.id] ?? "";
|
input.value = values[item.id] ?? "";
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
const hint = hooks.hint?.(c, item, state) || (item.default ? `${item.default} (기본)` : "");
|
// 기본값은 새 상자 칸에 실제 값으로만(54-39) — 빈 칸 「(기본)」 안내 없음
|
||||||
const input = numberInput(String(item.step ?? 0.1), "0", hint);
|
const input = numberInput(String(item.step ?? 0.1), "0", hooks.hint?.(c, item, state) ?? "");
|
||||||
if (item.kind === "auto") {
|
if (item.kind === "auto") {
|
||||||
// 보여 주는 칸(54-1) — 손 입력 칸 아님
|
// 보여 주는 칸(54-1) — 손 입력 칸 아님
|
||||||
const shown = document.createElement("output");
|
const shown = document.createElement("output");
|
||||||
|
|||||||
@@ -97,6 +97,9 @@ def _apply_new_defaults(points: list[PipePoint]) -> None:
|
|||||||
for field in definition.options:
|
for field in definition.options:
|
||||||
if field.key not in options and field.default not in (None, ""):
|
if field.key not in options and field.default not in (None, ""):
|
||||||
options[field.key] = field.default
|
options[field.key] = field.default
|
||||||
|
# 집수정 모양도 실제 값으로(54-39 · 54-36 기본 L형 — 화면 짝 `DEFAULT_BASIN_SHAPE`)
|
||||||
|
if options.get("inlet_type") == "집수정":
|
||||||
|
options.setdefault("inlet_structure", "L")
|
||||||
point.options = options or None
|
point.options = options or None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ import {
|
|||||||
} from "./B05_Profile_UI_Drainage_Facility_Fields";
|
} from "./B05_Profile_UI_Drainage_Facility_Fields";
|
||||||
import { revetChoiceRow } from "./B05_Profile_UI_Drainage_Revet_Choice";
|
import { revetChoiceRow } from "./B05_Profile_UI_Drainage_Revet_Choice";
|
||||||
import { createSpecForm, type SpecLinks } from "../A00_Common/design/design_structure_form";
|
import { createSpecForm, type SpecLinks } from "../A00_Common/design/design_structure_form";
|
||||||
import { branchOf } from "../A00_Common/design/design_structure_spec";
|
|
||||||
import { PIPE_SPEC } from "../A00_Common/design/design_structure_pipe";
|
import { PIPE_SPEC } from "../A00_Common/design/design_structure_pipe";
|
||||||
import {
|
import {
|
||||||
BOX_SPEC,
|
BOX_SPEC,
|
||||||
@@ -60,7 +59,6 @@ import {
|
|||||||
import { FORD_SPEC, withLegacyGuards } from "../A00_Common/design/design_structure_ford";
|
import { FORD_SPEC, withLegacyGuards } from "../A00_Common/design/design_structure_ford";
|
||||||
import { fordHeightHint, fordHeightM } from "../A00_Common/design/design_structure_ford_calc";
|
import { fordHeightHint, fordHeightM } from "../A00_Common/design/design_structure_ford_calc";
|
||||||
import {
|
import {
|
||||||
DEFAULT_BASIN_SHAPE,
|
|
||||||
pipeDrawn,
|
pipeDrawn,
|
||||||
pipeExtraCount,
|
pipeExtraCount,
|
||||||
pipeLengthM,
|
pipeLengthM,
|
||||||
@@ -275,19 +273,7 @@ export function createFacilityOptionsForm(
|
|||||||
clear: (key) => linkSource?.clear(key),
|
clear: (key) => linkSource?.clear(key),
|
||||||
set: (key, item) => linkSource?.set(key, item),
|
set: (key, item) => linkSource?.set(key, item),
|
||||||
};
|
};
|
||||||
// 그림이 쓰는 값(54-13) — 빈 고르기 칸 = 정의 기본값(엔진 · 횡단도가 그 값으로 그림) · 보여 주기만
|
// 기본값은 새 시설 칸에 실제 값으로만(54-39) — 빈 칸에 「(기본)」 꼬리 · 안내 없음
|
||||||
let defaults: Record<string, unknown> = {};
|
|
||||||
const byDefault = (key: string | undefined): { value: string; tag: string } | null => {
|
|
||||||
const value = key ? defaults[key] : null;
|
|
||||||
return value === null || value === undefined || value === ""
|
|
||||||
? null
|
|
||||||
: { value: String(value), tag: "기본" };
|
|
||||||
};
|
|
||||||
/** 빈 날개벽 설치 = 정의 기본 설치(옛 「있음」 · 빈 값 = 날개벽 · 엔진 `_wing_spec`). */
|
|
||||||
const wingDefault = (key: string): { value: string; tag: string } => {
|
|
||||||
const value = String(defaults[key] ?? "");
|
|
||||||
return { value: ["없음", "흙막이"].includes(value) ? value : "날개벽", tag: "기본" };
|
|
||||||
};
|
|
||||||
// 유입 「자동(규칙)」 = 기슭막이 + 유입 형식 auto(옛 값 · 정의 기본) — 그림은 지형 규칙이 집수정 · 흙막이를
|
// 유입 「자동(규칙)」 = 기슭막이 + 유입 형식 auto(옛 값 · 정의 기본) — 그림은 지형 규칙이 집수정 · 흙막이를
|
||||||
// 정함. 사용자가 유입 구조를 고를 때까지 저장도 그대로(흙막이 고름의 형식 revet 을 안 적음)
|
// 정함. 사용자가 유입 구조를 고를 때까지 저장도 그대로(흙막이 고름의 형식 revet 을 안 적음)
|
||||||
let inletAuto: { structure?: string | number } | null = null;
|
let inletAuto: { structure?: string | number } | null = null;
|
||||||
@@ -331,12 +317,12 @@ export function createFacilityOptionsForm(
|
|||||||
() => pipeForm.refresh(),
|
() => pipeForm.refresh(),
|
||||||
);
|
);
|
||||||
const pipeForm = createSpecForm(specLinks, {
|
const pipeForm = createSpecForm(specLinks, {
|
||||||
// 관 보호공 [연동] · [경사](54-30) — 유입 · 유출 상자 각각 한 줄(같은 값) · 집수정이면 숨김
|
// 관 보호공 [연동] · [경사](54-30) — 유입 · 유출 상자 각각 한 줄(같은 값) · 길이 있는 흙막이만(54-40 · 집수정 · 안 고름은 숨김)
|
||||||
fields: (c, values) =>
|
fields: (c, values) =>
|
||||||
c.id === "outlet"
|
c.id === "outlet"
|
||||||
? [paintTiers(values), outletChoice.paint(false)]
|
? [paintTiers(values), outletChoice.paint(values.structure !== "기슭막이")]
|
||||||
: c.id === "inlet"
|
: c.id === "inlet"
|
||||||
? [inletChoice.paint(values.structure === "집수정")]
|
? [inletChoice.paint(values.structure !== "기슭막이")]
|
||||||
: c.id === "soil_add" // 추가 흙막이 상자도 같은 줄(54-36 · 상자마다 한 줄 · 같은 값)
|
: c.id === "soil_add" // 추가 흙막이 상자도 같은 줄(54-36 · 상자마다 한 줄 · 같은 값)
|
||||||
? [
|
? [
|
||||||
revetChoiceRow(
|
revetChoiceRow(
|
||||||
@@ -345,23 +331,15 @@ export function createFacilityOptionsForm(
|
|||||||
).paint(false),
|
).paint(false),
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
shown: (c, item, values, index) => {
|
shown: (c, item) => {
|
||||||
if (c.id === "inlet" && item.id === "structure" && inletIsAuto()) {
|
if (c.id === "inlet" && item.id === "structure" && inletIsAuto()) {
|
||||||
const drawn = drawnNow();
|
const drawn = drawnNow();
|
||||||
return drawn
|
return drawn
|
||||||
? { value: drawn.basin ? "집수정" : "기슭막이", tag: "자동" }
|
? { value: drawn.basin ? "집수정" : "기슭막이", tag: "자동" }
|
||||||
: { value: "", tag: "자동(지형 규칙)" };
|
: { value: "", tag: "자동(지형 규칙)" };
|
||||||
}
|
}
|
||||||
if (values[item.id]) return null;
|
// 빈 고르기 칸 = 「선택 안 함」 그대로(54-39 · 기본값 꼬리 없음)
|
||||||
if (item.id !== "form") return byDefault(branchOf(item, values)?.key);
|
return null;
|
||||||
const basin = c.id === "inlet" && values.structure === "집수정";
|
|
||||||
const drawn = drawnNow();
|
|
||||||
const form = basin
|
|
||||||
? (drawn?.basin ?? DEFAULT_BASIN_SHAPE)
|
|
||||||
: c.id === "soil_add"
|
|
||||||
? drawn?.extras[index]
|
|
||||||
: drawn?.forms[c.id];
|
|
||||||
return form ? { value: form, tag: "기본" } : null;
|
|
||||||
},
|
},
|
||||||
auto: (_c, item) => {
|
auto: (_c, item) => {
|
||||||
if (item.id !== "length") return "";
|
if (item.id !== "length") return "";
|
||||||
@@ -370,16 +348,6 @@ export function createFacilityOptionsForm(
|
|||||||
},
|
},
|
||||||
// 흙막이 개수 = 유출 「추가 기슭막이(단)」(54-1)
|
// 흙막이 개수 = 유출 「추가 기슭막이(단)」(54-1)
|
||||||
count: (c) => (c.id === "soil_add" ? pipeExtraCount(chainage) : undefined),
|
count: (c) => (c.id === "soil_add" ? pipeExtraCount(chainage) : undefined),
|
||||||
hint: (c, item, state) => {
|
|
||||||
if (item.default) return ""; // 명세 기본값 안내는 폼이 그림(추가 흙막이 · 54-36)
|
|
||||||
// 빈 칸 = 정의 기본값 「값 (기본)」(54-36) — 구조가 비었으면 기본 구조의 칸
|
|
||||||
const raw = state[c.id]?.[0] ?? {};
|
|
||||||
const top = c.items.find((i) => i.id === "structure");
|
|
||||||
const values =
|
|
||||||
raw.structure || !top ? raw : { ...raw, structure: byDefault(top.key)?.value ?? "" };
|
|
||||||
const fallback = item.kind === "number" ? byDefault(branchOf(item, values)?.key) : null;
|
|
||||||
return fallback ? `${fallback.value} (기본)` : "";
|
|
||||||
},
|
|
||||||
onChange: (c, item) => {
|
onChange: (c, item) => {
|
||||||
if (c.id === "inlet" && item.id === "structure") inletAuto = null;
|
if (c.id === "inlet" && item.id === "structure") inletAuto = null;
|
||||||
if (c.id === "inlet" && (item.id === "structure" || item.id === "form"))
|
if (c.id === "inlet" && (item.id === "structure" || item.id === "form"))
|
||||||
@@ -410,11 +378,6 @@ export function createFacilityOptionsForm(
|
|||||||
const fordNeed = (): number | null =>
|
const fordNeed = (): number | null =>
|
||||||
fordHeightM(fordFlow, Number(fordForm.state().ford?.[0]?.width ?? ""));
|
fordHeightM(fordFlow, Number(fordForm.state().ford?.[0]?.width ?? ""));
|
||||||
const fordForm = createSpecForm(specLinks, {
|
const fordForm = createSpecForm(specLinks, {
|
||||||
shown: (c, item, values) => {
|
|
||||||
if (values[item.id]) return null;
|
|
||||||
if (c.id !== "ford") return wingDefault(c.id === "inlet" ? "wing_in" : "wing_out");
|
|
||||||
return item.id === "diameter" ? byDefault("pipe_diameter_mm") : null;
|
|
||||||
},
|
|
||||||
hint: (c, item, state) =>
|
hint: (c, item, state) =>
|
||||||
c.id === "ford" && item.id === "height"
|
c.id === "ford" && item.id === "height"
|
||||||
? fordHeightHint(fordFlow, Number(state.ford?.[0]?.width ?? ""))
|
? fordHeightHint(fordFlow, Number(state.ford?.[0]?.width ?? ""))
|
||||||
@@ -480,18 +443,7 @@ export function createFacilityOptionsForm(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ── BOX암거 = 사용자 구조물 표 02 명세(PLAN 52-2) — 본체 · 유입 구조 · 유출 구조 ──
|
// ── BOX암거 = 사용자 구조물 표 02 명세(PLAN 52-2) — 본체 · 유입 구조 · 유출 구조 ──
|
||||||
const boxForm = createSpecForm(specLinks, {
|
const boxForm = createSpecForm(specLinks, { onChange: () => emit() });
|
||||||
onChange: () => emit(),
|
|
||||||
shown: (c, item, values) => {
|
|
||||||
if (values[item.id]) return null;
|
|
||||||
if (c.id !== "body") return wingDefault(c.id === "inlet" ? "wing_in" : "wing_out");
|
|
||||||
const size = bodyOf({
|
|
||||||
body_width_m: Number(defaults.body_width_m ?? 2),
|
|
||||||
body_height_m: Number(defaults.body_height_m ?? 2),
|
|
||||||
}).size;
|
|
||||||
return item.id === "size" && size ? { value: size, tag: "기본" } : null;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// 날개벽 칸 — 세월교 · BOX 가 같은 조각을 쓰던 자리 · 조정 행은 명세 유입 · 유출 칸으로 옮겨 붙음
|
// 날개벽 칸 — 세월교 · BOX 가 같은 조각을 쓰던 자리 · 조정 행은 명세 유입 · 유출 칸으로 옮겨 붙음
|
||||||
const wingInFields = createWingFields("날개벽(유입)", WING_IN_KEYS);
|
const wingInFields = createWingFields("날개벽(유입)", WING_IN_KEYS);
|
||||||
const wingOutFields = createWingFields("날개벽(유출)", WING_OUT_KEYS);
|
const wingOutFields = createWingFields("날개벽(유출)", WING_OUT_KEYS);
|
||||||
@@ -630,7 +582,6 @@ export function createFacilityOptionsForm(
|
|||||||
// 받은 값만 칸에 — 없으면 「안 정함」(새 시설은 부르는 쪽이 등록부 기본값을 실어 줌 · 46-7).
|
// 받은 값만 칸에 — 없으면 「안 정함」(새 시설은 부르는 쪽이 등록부 기본값을 실어 줌 · 46-7).
|
||||||
const groups = Object.fromEntries((definition?.groups ?? []).map((g) => [g.id, g.name]));
|
const groups = Object.fromEntries((definition?.groups ?? []).map((g) => [g.id, g.name]));
|
||||||
const names = definition?.spec_names ?? {};
|
const names = definition?.spec_names ?? {};
|
||||||
defaults = Object.fromEntries((definition?.options ?? []).map((o) => [o.key, o.default]));
|
|
||||||
if (facility === "pipe") {
|
if (facility === "pipe") {
|
||||||
// 정의 파일의 옛 이름 「흙막이 추가」 — 명세 이름 「흙막이」가 앞섬(54-1)
|
// 정의 파일의 옛 이름 「흙막이 추가」 — 명세 이름 「흙막이」가 앞섬(54-1)
|
||||||
const { soil_add: _old, ...pipeNames } = names;
|
const { soil_add: _old, ...pipeNames } = names;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
* · 컨테이너 = 좌측 패널 폼과 같은 상자 — 명세 종류(`STRUCTURE_SPECS`)는 명세 컨테이너(선택 컨테이너는
|
* · 컨테이너 = 좌측 패널 폼과 같은 상자 — 명세 종류(`STRUCTURE_SPECS`)는 명세 컨테이너(선택 컨테이너는
|
||||||
* 인스턴스마다) · 그 밖 종류는 정의 옵션 그룹(`StructureType.groups`)
|
* 인스턴스마다) · 그 밖 종류는 정의 옵션 그룹(`StructureType.groups`)
|
||||||
* · 첫 옵션 = 지금 보이는 첫 입력 칸(자동 칸 · 갈래가 없어 가린 칸 · 끈 칸은 건너뜀)
|
* · 첫 옵션 = 지금 보이는 첫 입력 칸(자동 칸 · 갈래가 없어 가린 칸 · 끈 칸은 건너뜀)
|
||||||
* · 빈 값 = 등록부 기본값 「(기본)」 · 값 `auto` = 「(자동)」(54-13 표시와 같음) · 그것도 없으면 「—」
|
* · 값 `auto` = 「(자동)」 · 빈 값 = 「—」(54-39 · 기본값으로 안 바꿈 · 기본값은 새 구조물 칸에 실제 값)
|
||||||
* ========================================================================== */
|
* ========================================================================== */
|
||||||
|
|
||||||
import type { StructureInstance, StructureType } from "./B05_Profile_Api_Structures";
|
import type { StructureInstance, StructureType } from "./B05_Profile_Api_Structures";
|
||||||
@@ -27,7 +27,6 @@ import {
|
|||||||
type StructureSpec,
|
type StructureSpec,
|
||||||
} from "../A00_Common/design/design_structure_spec";
|
} from "../A00_Common/design/design_structure_spec";
|
||||||
|
|
||||||
const DEFAULT_MARK = "(기본)";
|
|
||||||
const AUTO_MARK = "(자동)";
|
const AUTO_MARK = "(자동)";
|
||||||
const NONE = "—";
|
const NONE = "—";
|
||||||
|
|
||||||
@@ -62,8 +61,6 @@ function specLines(
|
|||||||
names = pipeNames;
|
names = pipeNames;
|
||||||
}
|
}
|
||||||
const state = formState(spec, structure.type_id, structure.options ?? {});
|
const state = formState(spec, structure.type_id, structure.options ?? {});
|
||||||
// 빈 칸이 그림에서 쓰는 값 — 등록부 기본값을 같은 길로 읽은 필수 컨테이너 값
|
|
||||||
const fallback = formState(spec, structure.type_id, registryDefaults(type));
|
|
||||||
const groups = Object.fromEntries((type?.groups ?? []).map((g) => [g.id, g.name]));
|
const groups = Object.fromEntries((type?.groups ?? []).map((g) => [g.id, g.name]));
|
||||||
return spec.containers.flatMap((c) => {
|
return spec.containers.flatMap((c) => {
|
||||||
const title = containerTitle(c, names, groups[c.link]);
|
const title = containerTitle(c, names, groups[c.link]);
|
||||||
@@ -72,9 +69,8 @@ function specLines(
|
|||||||
(entry) => entry.kind !== "auto" && (!entry.by || branchOf(entry, values)),
|
(entry) => entry.kind !== "auto" && (!entry.by || branchOf(entry, values)),
|
||||||
);
|
);
|
||||||
const head = c.required ? title : `${title} ${index + 1}`;
|
const head = c.required ? title : `${title} ${index + 1}`;
|
||||||
const base = c.required ? (fallback[c.id]?.[0]?.[item?.id ?? ""] ?? "") : "";
|
|
||||||
return item
|
return item
|
||||||
? `${head}: ${itemLabel(c, item, names)} ${specValue(c, item, values, names, base)}`
|
? `${head}: ${itemLabel(c, item, names)} ${specValue(c, item, values, names)}`
|
||||||
: `${head}: ${NONE}`;
|
: `${head}: ${NONE}`;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -113,7 +109,6 @@ function specValue(
|
|||||||
item: SpecItem,
|
item: SpecItem,
|
||||||
values: Instance,
|
values: Instance,
|
||||||
names: Record<string, string>,
|
names: Record<string, string>,
|
||||||
fallback: string,
|
|
||||||
): string {
|
): string {
|
||||||
const shown = (raw: string): string => {
|
const shown = (raw: string): string => {
|
||||||
const choice = choicesOf(item, values).find((ch) => ch.value === raw);
|
const choice = choicesOf(item, values).find((ch) => ch.value === raw);
|
||||||
@@ -121,8 +116,7 @@ function specValue(
|
|||||||
};
|
};
|
||||||
const raw = (values[item.id] ?? "").trim();
|
const raw = (values[item.id] ?? "").trim();
|
||||||
if (raw === "auto") return AUTO_MARK;
|
if (raw === "auto") return AUTO_MARK;
|
||||||
if (raw) return shown(raw);
|
return raw ? shown(raw) : NONE;
|
||||||
return fallback.trim() ? `${shown(fallback.trim())} ${DEFAULT_MARK}` : NONE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 정의 옵션 그룹 줄 — 그룹마다 첫 옵션(정의에 있고 켜진 칸). */
|
/** 정의 옵션 그룹 줄 — 그룹마다 첫 옵션(정의에 있고 켜진 칸). */
|
||||||
@@ -139,22 +133,7 @@ function groupLines(
|
|||||||
const unit = option.unit ?? "";
|
const unit = option.unit ?? "";
|
||||||
const raw = options[option.key];
|
const raw = options[option.key];
|
||||||
const value =
|
const value =
|
||||||
raw === "auto"
|
raw === "auto" ? AUTO_MARK : raw !== undefined && raw !== "" ? `${raw}${unit}` : NONE;
|
||||||
? AUTO_MARK
|
|
||||||
: raw !== undefined && raw !== ""
|
|
||||||
? `${raw}${unit}`
|
|
||||||
: option.default !== null && option.default !== undefined && option.default !== ""
|
|
||||||
? `${option.default}${unit} ${DEFAULT_MARK}`
|
|
||||||
: NONE;
|
|
||||||
return `${group.name}: ${option.label} ${value}`;
|
return `${group.name}: ${option.label} ${value}`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 등록부 기본값(키 → 값) — 빈 칸이 그림에서 쓰는 값. */
|
|
||||||
function registryDefaults(type: StructureType | undefined): Record<string, string | number> {
|
|
||||||
return Object.fromEntries(
|
|
||||||
(type?.options ?? [])
|
|
||||||
.filter((option) => option.default !== null && option.default !== undefined)
|
|
||||||
.map((option) => [option.key, option.default as string | number]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
* ========================================================================== */
|
* ========================================================================== */
|
||||||
|
|
||||||
import { chainageToStation, stationToChainage } from "./B05_Profile_Util_Station";
|
import { chainageToStation, stationToChainage } from "./B05_Profile_Util_Station";
|
||||||
|
import { DEFAULT_BASIN_SHAPE } from "../B06_Section/B06_Section_UI_Cross_Culvert_Basin";
|
||||||
|
|
||||||
export function field(labelText: string, input: HTMLElement): HTMLLabelElement {
|
export function field(labelText: string, input: HTMLElement): HTMLLabelElement {
|
||||||
const wrapper = document.createElement("label");
|
const wrapper = document.createElement("label");
|
||||||
@@ -65,14 +66,8 @@ export function optionControl(
|
|||||||
// 툴팁 — 표가 아직 안 읽는 칸이면 그 사유가 먼저(넣어도 안 바뀜을 알아야 함), 아니면 비울 때의 뜻.
|
// 툴팁 — 표가 아직 안 읽는 칸이면 그 사유가 먼저(넣어도 안 바뀜을 알아야 함), 아니면 비울 때의 뜻.
|
||||||
const tip = option.not_in_table ?? option.empty_means ?? "";
|
const tip = option.not_in_table ?? option.empty_means ?? "";
|
||||||
if (option.input === "select") {
|
if (option.input === "select") {
|
||||||
// 빈 칸이 쓰는 정의 기본값은 빈 보기에 꼬리를 달아 보임 — 값은 안 고름(54-13)
|
// 빈 보기 = 값 없음(54-39) — 기본값은 새 구조물 칸에 실제 값으로만(「(기본)」 꼬리 없음)
|
||||||
const fallback = option.default === null || option.default === "" ? null : option.default;
|
const blank = option.required ? "— 선택 —" : "선택 안 함";
|
||||||
const blank =
|
|
||||||
fallback !== null && fallback !== undefined
|
|
||||||
? `${label(String(fallback))} (기본)`
|
|
||||||
: option.required
|
|
||||||
? "— 선택 —"
|
|
||||||
: "— 안 정함 —";
|
|
||||||
// 정의에서 뺀 선택지(48)를 고른 옛 값 — 그 값만 남겨 보임(값을 잃지 않음)
|
// 정의에서 뺀 선택지(48)를 고른 옛 값 — 그 값만 남겨 보임(값을 잃지 않음)
|
||||||
const kept = value && !option.choices.includes(value) ? [value] : [];
|
const kept = value && !option.choices.includes(value) ? [value] : [];
|
||||||
const element = select([
|
const element = select([
|
||||||
@@ -107,11 +102,14 @@ export function optionControl(
|
|||||||
export function optionDefaults(
|
export function optionDefaults(
|
||||||
options: ReadonlyArray<{ key: string; default: string | number | null }>,
|
options: ReadonlyArray<{ key: string; default: string | number | null }>,
|
||||||
): Record<string, string | number> {
|
): Record<string, string | number> {
|
||||||
return Object.fromEntries(
|
const values: Record<string, string | number> = Object.fromEntries(
|
||||||
options
|
options
|
||||||
.filter((option) => option.default !== null && option.default !== "")
|
.filter((option) => option.default !== null && option.default !== "")
|
||||||
.map((option) => [option.key, option.default as string | number]),
|
.map((option) => [option.key, option.default as string | number]),
|
||||||
);
|
);
|
||||||
|
// 배관 집수정 모양도 실제 값으로(54-39 · 54-36 기본 · 자동 배치 짝 `_apply_new_defaults`)
|
||||||
|
if (values.inlet_type === "집수정") values.inlet_structure ??= DEFAULT_BASIN_SHAPE;
|
||||||
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 측점번호 + 잔여거리 두 칸 묶음. */
|
/** 측점번호 + 잔여거리 두 칸 묶음. */
|
||||||
|
|||||||
@@ -554,8 +554,19 @@
|
|||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
/* [연동] [경사] 한 줄(54-36) — 안 여백만 줄임 · 높이 그대로 */
|
/* [연동] [경사] 한 줄(54-36) — 안 여백만 줄임 */
|
||||||
.b05-structure__half-pair > button {
|
.b05-structure__half-pair > button {
|
||||||
padding-inline: 4px;
|
padding-inline: 4px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 상자 안 작은 단추 템플릿(54-41) — 높이 = 숫자 칸 [-][+](`--b05-control-h`) · 글 · 안 여백도 그 높이에 ·
|
||||||
|
* 상자 이름표 [빼기] · 상자 밖 [추가] · [삭제] · [리셋]은 제외 */
|
||||||
|
.b05-structure__group-body button:not(.b05-structure__step) {
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: var(--b05-control-h);
|
||||||
|
min-height: 0;
|
||||||
|
padding-block: 0;
|
||||||
|
font-size: var(--text-caption);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -209,6 +209,8 @@ export interface CulvertSideSpec {
|
|||||||
basin_after_m?: number | null;
|
basin_after_m?: number | null;
|
||||||
/** 집수정 높이(m) — 좌측 폼 「높이」. 없으면 횡단도 자동(I형 노견 접점 · ㄴ·ㄷ형 내공 높이). */
|
/** 집수정 높이(m) — 좌측 폼 「높이」. 없으면 횡단도 자동(I형 노견 접점 · ㄴ·ㄷ형 내공 높이). */
|
||||||
basin_height_m?: number | null;
|
basin_height_m?: number | null;
|
||||||
|
/** 집수정 모양(I · L · U) — 폼 「형태」(54-39) · 횡단 patch 형식이 없을 때 그림이 씀. 없으면 집수정 없음. */
|
||||||
|
basin_form?: string | null;
|
||||||
/** 기슭막이 전면 기울기(1:n) 종전값 — 그림은 `revetWallSpec().lean`(표준경사 표)을 씀. */
|
/** 기슭막이 전면 기울기(1:n) 종전값 — 그림은 `revetWallSpec().lean`(표준경사 표)을 씀. */
|
||||||
face_slope?: number;
|
face_slope?: number;
|
||||||
/** 독립 기슭막이 기울기 판정 칸(저장 원본) — 설치 측 · 성토/절토 · 전면 기울기(B08 과 같은 칸). */
|
/** 독립 기슭막이 기울기 판정 칸(저장 원본) — 설치 측 · 성토/절토 · 전면 기울기(B08 과 같은 칸). */
|
||||||
|
|||||||
@@ -168,8 +168,11 @@ def _side_spec(
|
|||||||
구조가 집수정이면 기슭막이·보호공을 만들지 않는다 — 집수정 단면은 후속 작업이라
|
구조가 집수정이면 기슭막이·보호공을 만들지 않는다 — 집수정 단면은 후속 작업이라
|
||||||
화면은 라벨만 쓴다(2026-08-19 사용자 확정).
|
화면은 라벨만 쓴다(2026-08-19 사용자 확정).
|
||||||
"""
|
"""
|
||||||
structure = options.get(f"{side}_type") or defaults.get(f"{side}_type") or "기슭막이"
|
# 고르기 칸 빈 값 = 그 부재 없음(54-39 · 기본값으로 안 바꿈)
|
||||||
|
structure = options.get(f"{side}_type") or ""
|
||||||
spec: dict[str, Any] = {"role": side, "structure": str(structure)}
|
spec: dict[str, Any] = {"role": side, "structure": str(structure)}
|
||||||
|
if not structure:
|
||||||
|
return spec
|
||||||
if spec["structure"] == INLET_STRUCTURE_BASIN:
|
if spec["structure"] == INLET_STRUCTURE_BASIN:
|
||||||
# 집수정 종방향 길이(3D 예상형상용, 기본 2m — 2026-08-23 사용자).
|
# 집수정 종방향 길이(3D 예상형상용, 기본 2m — 2026-08-23 사용자).
|
||||||
# 전/후는 기슭막이와 같은 체계로 따로 지정한다(기본 각 1m — 2026-08-24 사용자).
|
# 전/후는 기슭막이와 같은 체계로 따로 지정한다(기본 각 1m — 2026-08-24 사용자).
|
||||||
@@ -179,37 +182,37 @@ def _side_spec(
|
|||||||
)
|
)
|
||||||
spec["basin_before_m"] = _number(
|
spec["basin_before_m"] = _number(
|
||||||
options.get("inlet_basin_before_m"),
|
options.get("inlet_basin_before_m"),
|
||||||
_number(defaults.get("inlet_basin_before_m"), None),
|
None,
|
||||||
)
|
)
|
||||||
spec["basin_after_m"] = _number(
|
spec["basin_after_m"] = _number(
|
||||||
options.get("inlet_basin_after_m"),
|
options.get("inlet_basin_after_m"),
|
||||||
_number(defaults.get("inlet_basin_after_m"), None),
|
None,
|
||||||
)
|
)
|
||||||
# 집수정 높이(좌측 폼 · 비면 정의 기본값) — 횡단도 집수정 벽 높이.
|
# 집수정 높이(좌측 폼 · 비면 정의 기본값) — 횡단도 집수정 벽 높이.
|
||||||
spec["basin_height_m"] = _number(
|
spec["basin_height_m"] = _number(options.get("inlet_basin_height_m"), None)
|
||||||
options.get("inlet_basin_height_m"), _number(defaults.get("inlet_basin_height_m"), None)
|
# 집수정 모양(54-39) — 빈 값 = 안 고름
|
||||||
)
|
spec["basin_form"] = str(options.get("inlet_structure") or "") or None
|
||||||
return spec
|
return spec
|
||||||
|
|
||||||
# 높이를 안 적었으면 **횡단도가 그리는 관경 기준 최소 높이**(2026-09-14 · 옛 기본 2.5 걷음).
|
# 높이를 안 적었으면 **횡단도가 그리는 관경 기준 최소 높이**(2026-09-14 · 옛 기본 2.5 걷음).
|
||||||
height = _number(
|
height = _number(
|
||||||
options.get(f"{side}_revet_height_m"),
|
options.get(f"{side}_revet_height_m"),
|
||||||
_number(defaults.get(f"{side}_revet_height_m"), pipe_wall_min_height_m(diameter_m)),
|
pipe_wall_min_height_m(diameter_m),
|
||||||
)
|
)
|
||||||
length = _number(
|
length = _number(
|
||||||
options.get(f"{side}_revet_length_m"),
|
options.get(f"{side}_revet_length_m"),
|
||||||
_number(defaults.get(f"{side}_revet_length_m"), None),
|
None,
|
||||||
)
|
)
|
||||||
form = options.get(f"{side}_revet_form") or defaults.get(f"{side}_revet_form")
|
form = options.get(f"{side}_revet_form")
|
||||||
# 기준측점 전/후 몶(3D 예상형상 종방향 배치 — 2026-08-23 사용자).
|
# 기준측점 전/후 몶(3D 예상형상 종방향 배치 — 2026-08-23 사용자).
|
||||||
# 저장분이 없으면 길이 절반씩.
|
# 저장분이 없으면 길이 절반씩.
|
||||||
before = _number(
|
before = _number(
|
||||||
options.get(f"{side}_revet_before_m"),
|
options.get(f"{side}_revet_before_m"),
|
||||||
_number(defaults.get(f"{side}_revet_before_m"), None),
|
None,
|
||||||
)
|
)
|
||||||
after = _number(
|
after = _number(
|
||||||
options.get(f"{side}_revet_after_m"),
|
options.get(f"{side}_revet_after_m"),
|
||||||
_number(defaults.get(f"{side}_revet_after_m"), None),
|
None,
|
||||||
)
|
)
|
||||||
spec.update(
|
spec.update(
|
||||||
{
|
{
|
||||||
@@ -235,7 +238,7 @@ def _culvert_set(options: dict[str, Any] | None) -> dict[str, Any]:
|
|||||||
diameter_mm = _number(
|
diameter_mm = _number(
|
||||||
values.get("pipe_diameter_mm"), _number(defaults.get("pipe_diameter_mm"), 1000.0)
|
values.get("pipe_diameter_mm"), _number(defaults.get("pipe_diameter_mm"), 1000.0)
|
||||||
)
|
)
|
||||||
kind = values.get("pipe_kind") or defaults.get("pipe_kind")
|
kind = values.get("pipe_kind")
|
||||||
diameter_m = round((diameter_mm or 1000.0) / 1000.0, 3)
|
diameter_m = round((diameter_mm or 1000.0) / 1000.0, 3)
|
||||||
return {
|
return {
|
||||||
"type": "pipe",
|
"type": "pipe",
|
||||||
@@ -244,10 +247,7 @@ def _culvert_set(options: dict[str, Any] | None) -> dict[str, Any]:
|
|||||||
"min_cover_m": MIN_PIPE_COVER_M,
|
"min_cover_m": MIN_PIPE_COVER_M,
|
||||||
# 관 기슭막이 기초 축 — **유입·유출 한 칸으로** 받는다(2026-09-09). 한쪽만 기초유로
|
# 관 기슭막이 기초 축 — **유입·유출 한 칸으로** 받는다(2026-09-09). 한쪽만 기초유로
|
||||||
# 하는 일이 드물어 폼을 둘로 늘리지 않았다. 실무가 갈라 쓰면 그때 나눌 것.
|
# 하는 일이 드물어 폼을 둘로 늘리지 않았다. 실무가 갈라 쓰면 그때 나눌 것.
|
||||||
"foundation": (
|
"foundation": (str(values.get("revet_foundation") or "").strip() or None),
|
||||||
str(values.get("revet_foundation") or defaults.get("revet_foundation") or "").strip()
|
|
||||||
or None
|
|
||||||
),
|
|
||||||
"inlet": _side_spec(values, defaults, "inlet", diameter_m),
|
"inlet": _side_spec(values, defaults, "inlet", diameter_m),
|
||||||
"outlet": _side_spec(values, defaults, "outlet", diameter_m),
|
"outlet": _side_spec(values, defaults, "outlet", diameter_m),
|
||||||
}
|
}
|
||||||
@@ -504,6 +504,8 @@ def load_culvert_sets(project_root: Path) -> dict[float, dict[str, Any]]:
|
|||||||
sets: dict[float, dict[str, Any]] = {}
|
sets: dict[float, dict[str, Any]] = {}
|
||||||
for point in parse_pipe_points(document.get("points")):
|
for point in parse_pipe_points(document.get("points")):
|
||||||
if point.facility == PIPE_FACILITY_PIPE:
|
if point.facility == PIPE_FACILITY_PIPE:
|
||||||
|
if (point.options or {}).get("pipe_diameter_mm") in (None, ""):
|
||||||
|
continue # 관경 안 고름 = 관 없음(54-39)
|
||||||
spec = _culvert_set(point.options)
|
spec = _culvert_set(point.options)
|
||||||
elif point.facility == PIPE_FACILITY_FORD_BRIDGE:
|
elif point.facility == PIPE_FACILITY_FORD_BRIDGE:
|
||||||
spec = _ford_set(point.options)
|
spec = _ford_set(point.options)
|
||||||
|
|||||||
@@ -186,7 +186,13 @@ export function computeCulvertLayout(
|
|||||||
: inletFillSlopeLen <= BASIN_MAX_FILL_SLOPE_M + 1e-6
|
: inletFillSlopeLen <= BASIN_MAX_FILL_SLOPE_M + 1e-6
|
||||||
? "short"
|
? "short"
|
||||||
: null;
|
: null;
|
||||||
const choice: InletStructureChoice = inletStructure ?? "auto";
|
// 횡단 patch 형식이 없으면 폼이 고른 집수정 모양(54-39)
|
||||||
|
const choice: InletStructureChoice =
|
||||||
|
inletStructure && inletStructure !== "auto"
|
||||||
|
? inletStructure
|
||||||
|
: ((culvert.inlet.basin_form as InletStructureChoice | null | undefined) ??
|
||||||
|
inletStructure ??
|
||||||
|
"auto");
|
||||||
// 독립 기슭막이(관 숨김)는 집수정을 세우지 않는다 — 설치 측이 상단(유입 역할)일 때만
|
// 독립 기슭막이(관 숨김)는 집수정을 세우지 않는다 — 설치 측이 상단(유입 역할)일 때만
|
||||||
// 집수정이 서서 좌·우가 갈리던 원인(2026-08-29 사용자: 양쪽 다 성토 로직).
|
// 집수정이 서서 좌·우가 갈리던 원인(2026-08-29 사용자: 양쪽 다 성토 로직).
|
||||||
const { shape: basinShape, reason: choiceReason } = resolveBasinChoice(
|
const { shape: basinShape, reason: choiceReason } = resolveBasinChoice(
|
||||||
@@ -194,7 +200,10 @@ export function computeCulvertLayout(
|
|||||||
ruleReason,
|
ruleReason,
|
||||||
inletOptions.basinLUAllowed,
|
inletOptions.basinLUAllowed,
|
||||||
);
|
);
|
||||||
const basinReason = culvert.hidden_pipe ? null : choiceReason;
|
// 유입 구조 · 집수정 모양을 안 고름 = 집수정 없음(54-39 · 기본 모양으로 안 세움)
|
||||||
|
const inletKind = culvert.inlet.structure;
|
||||||
|
const basinUnset = !inletKind || (inletKind === "집수정" && !["I", "L", "U"].includes(choice));
|
||||||
|
const basinReason = culvert.hidden_pipe || basinUnset ? null : choiceReason;
|
||||||
// ㄴ·ㄷ형은 사이즈 유지한 채 통째로 노견 끝점 일치 — 바닥(=관 invert)이 따라 오른다.
|
// ㄴ·ㄷ형은 사이즈 유지한 채 통째로 노견 끝점 일치 — 바닥(=관 invert)이 따라 오른다.
|
||||||
if (basinReason && basinShape !== "I") {
|
if (basinReason && basinShape !== "I") {
|
||||||
inlet.elevation = inletInfo.edge.elevation_m - (basinHeightM ?? basinAdjust.innerHeightM);
|
inlet.elevation = inletInfo.edge.elevation_m - (basinHeightM ?? basinAdjust.innerHeightM);
|
||||||
@@ -368,6 +377,9 @@ export function computeCulvertLayout(
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
// 구조 · 형태를 안 고른 배관 벽 = 그리지 않음(54-39 · 조정창 형태가 있으면 그 값)
|
||||||
|
const pickedForm = (spec.role === "inlet" ? adjInlet : adjOutlet).m || spec.revet_form;
|
||||||
|
if (!culvert.hidden_pipe && (spec.structure !== "기슭막이" || !pickedForm)) return null;
|
||||||
// 형상: 배면 수직 + 계류측 1:n 평행사변형 띠(n = 표준경사). 높이는 vertical(계산용)이 들고 온다.
|
// 형상: 배면 수직 + 계류측 1:n 평행사변형 띠(n = 표준경사). 높이는 vertical(계산용)이 들고 온다.
|
||||||
const height =
|
const height =
|
||||||
vertical?.height ?? Math.min(revetTargetHeight(diameter), revetHeightLimit(spec.revet_form));
|
vertical?.height ?? Math.min(revetTargetHeight(diameter), revetHeightLimit(spec.revet_form));
|
||||||
|
|||||||
@@ -540,7 +540,8 @@ export function buildStructurePanel(
|
|||||||
basinReset.classList.toggle("is-hidden", !isBasin);
|
basinReset.classList.toggle("is-hidden", !isBasin);
|
||||||
// I형은 내공(높이·폭) 개념이 없다 — 내공 줄을 숨긴다.
|
// I형은 내공(높이·폭) 개념이 없다 — 내공 줄을 숨긴다.
|
||||||
for (const entry of basinSizeRows) {
|
for (const entry of basinSizeRows) {
|
||||||
entry.row.classList.toggle("is-hidden", !isBasin || structure === "I");
|
// 화면에서 숨김(54-40 · 값 · 그림 계산은 그대로)
|
||||||
|
entry.row.classList.add("is-hidden");
|
||||||
if (isBasin)
|
if (isBasin)
|
||||||
entry.readout.textContent = `${deps.basinAdjustFor()[entry.field].toFixed(1)}m`;
|
entry.readout.textContent = `${deps.basinAdjustFor()[entry.field].toFixed(1)}m`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,39 +96,27 @@ function sideSpec(
|
|||||||
side: string,
|
side: string,
|
||||||
diameterM: number,
|
diameterM: number,
|
||||||
): CulvertSetSpec {
|
): CulvertSetSpec {
|
||||||
const structure = text(options[`${side}_type`]) ?? text(defaults[`${side}_type`]) ?? "기슭막이";
|
// 고르기 칸 빈 값 = 그 부재 없음(54-39 · 기본값으로 안 바꿈)
|
||||||
|
const structure = text(options[`${side}_type`]) ?? "";
|
||||||
const spec: CulvertSetSpec = { role: side, structure };
|
const spec: CulvertSetSpec = { role: side, structure };
|
||||||
|
if (!structure) return spec;
|
||||||
if (structure === INLET_STRUCTURE_BASIN) {
|
if (structure === INLET_STRUCTURE_BASIN) {
|
||||||
spec.basin_length_m = num(
|
spec.basin_length_m = num(
|
||||||
options.inlet_basin_length_m,
|
options.inlet_basin_length_m,
|
||||||
num(defaults.inlet_basin_length_m, 2.0),
|
num(defaults.inlet_basin_length_m, 2.0),
|
||||||
);
|
);
|
||||||
spec.basin_before_m = num(
|
spec.basin_before_m = num(options.inlet_basin_before_m, null);
|
||||||
options.inlet_basin_before_m,
|
spec.basin_after_m = num(options.inlet_basin_after_m, null);
|
||||||
num(defaults.inlet_basin_before_m, null),
|
spec.basin_height_m = num(options.inlet_basin_height_m, null);
|
||||||
);
|
spec.basin_form = text(options.inlet_structure); // 집수정 모양(54-39) — 빈 값 = 안 고름
|
||||||
spec.basin_after_m = num(options.inlet_basin_after_m, num(defaults.inlet_basin_after_m, null));
|
|
||||||
spec.basin_height_m = num(
|
|
||||||
options.inlet_basin_height_m,
|
|
||||||
num(defaults.inlet_basin_height_m, null),
|
|
||||||
);
|
|
||||||
return spec;
|
return spec;
|
||||||
}
|
}
|
||||||
|
|
||||||
const height = num(
|
const height = num(options[`${side}_revet_height_m`], pipeWallMinHeightM(diameterM));
|
||||||
options[`${side}_revet_height_m`],
|
const length = num(options[`${side}_revet_length_m`], null);
|
||||||
num(defaults[`${side}_revet_height_m`], pipeWallMinHeightM(diameterM)),
|
const form = text(options[`${side}_revet_form`]);
|
||||||
);
|
const before = num(options[`${side}_revet_before_m`], null);
|
||||||
const length = num(
|
const after = num(options[`${side}_revet_after_m`], null);
|
||||||
options[`${side}_revet_length_m`],
|
|
||||||
num(defaults[`${side}_revet_length_m`], null),
|
|
||||||
);
|
|
||||||
const form = text(options[`${side}_revet_form`]) ?? text(defaults[`${side}_revet_form`]);
|
|
||||||
const before = num(
|
|
||||||
options[`${side}_revet_before_m`],
|
|
||||||
num(defaults[`${side}_revet_before_m`], null),
|
|
||||||
);
|
|
||||||
const after = num(options[`${side}_revet_after_m`], num(defaults[`${side}_revet_after_m`], null));
|
|
||||||
spec.revet_form = form;
|
spec.revet_form = form;
|
||||||
spec.revet_height_m = height;
|
spec.revet_height_m = height;
|
||||||
spec.revet_length_m = length;
|
spec.revet_length_m = length;
|
||||||
@@ -153,13 +141,13 @@ function culvertSet(
|
|||||||
const diameterM = pipeDiameterM(diameterMm);
|
const diameterM = pipeDiameterM(diameterMm);
|
||||||
return {
|
return {
|
||||||
type: "pipe",
|
type: "pipe",
|
||||||
pipe_kind: text(options.pipe_kind) ?? text(defaults.pipe_kind),
|
pipe_kind: text(options.pipe_kind),
|
||||||
diameter_m: diameterM,
|
diameter_m: diameterM,
|
||||||
min_cover_m: MIN_PIPE_COVER_M,
|
min_cover_m: MIN_PIPE_COVER_M,
|
||||||
// 관 기슭막이 기초 축 — **유입·유출 한 칸으로** 받는다. 한 공사에서 한쪽만 기초유로
|
// 관 기슭막이 기초 축 — **유입·유출 한 칸으로** 받는다. 한 공사에서 한쪽만 기초유로
|
||||||
// 하는 일은 드물어 폼을 둘로 늘리지 않았다(2026-09-09). 실무가 갈라 쓰는 것이
|
// 하는 일은 드물어 폼을 둘로 늘리지 않았다(2026-09-09). 실무가 갈라 쓰는 것이
|
||||||
// 확인되면 그때 나눌 것.
|
// 확인되면 그때 나눌 것.
|
||||||
foundation: text(options.revet_foundation) ?? text(defaults.revet_foundation),
|
foundation: text(options.revet_foundation),
|
||||||
inlet: sideSpec(options, defaults, "inlet", diameterM),
|
inlet: sideSpec(options, defaults, "inlet", diameterM),
|
||||||
outlet: sideSpec(options, defaults, "outlet", diameterM),
|
outlet: sideSpec(options, defaults, "outlet", diameterM),
|
||||||
};
|
};
|
||||||
@@ -406,6 +394,8 @@ export function buildCulvertSets(
|
|||||||
else if (facility === FACILITY_BOX) spec = boxSet(values, registry);
|
else if (facility === FACILITY_BOX) spec = boxSet(values, registry);
|
||||||
else if (facility === FACILITY_FORD_PAVEMENT) spec = fordPavementSet(values, registry);
|
else if (facility === FACILITY_FORD_PAVEMENT) spec = fordPavementSet(values, registry);
|
||||||
else if (facility === FACILITY_REVET) spec = revetSet(values);
|
else if (facility === FACILITY_REVET) spec = revetSet(values);
|
||||||
|
else if (text(values.pipe_diameter_mm) === null)
|
||||||
|
continue; // 관경 안 고름 = 관 없음(54-39)
|
||||||
else spec = culvertSet(values, registry);
|
else spec = culvertSet(values, registry);
|
||||||
sets.set(round(chainage, 2), spec);
|
sets.set(round(chainage, 2), spec);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ def _defaults(type_id: str) -> dict:
|
|||||||
def test_새_관만_기본값_빈_키에만():
|
def test_새_관만_기본값_빈_키에만():
|
||||||
fresh = PipePoint(chainage_m=10.0, source="stream", options={"pipe_diameter_mm": 800})
|
fresh = PipePoint(chainage_m=10.0, source="stream", options={"pipe_diameter_mm": 800})
|
||||||
basins._apply_new_defaults([fresh])
|
basins._apply_new_defaults([fresh])
|
||||||
expected = {**_defaults("pipe"), "pipe_diameter_mm": 800} # 추천(먼저 들어간 값)이 이김
|
# 추천(먼저 들어간 값)이 이김 · 집수정 모양 L 도 실제 값(54-39)
|
||||||
|
expected = {**_defaults("pipe"), "pipe_diameter_mm": 800, "inlet_structure": "L"}
|
||||||
assert fresh.options == expected
|
assert fresh.options == expected
|
||||||
assert fresh.options["pipe_kind"] == "파형강관" and fresh.options["inlet_type"] == "집수정"
|
assert fresh.options["pipe_kind"] == "파형강관" and fresh.options["inlet_type"] == "집수정"
|
||||||
box = PipePoint(chainage_m=20.0, source="stream", facility="box_culvert")
|
box = PipePoint(chainage_m=20.0, source="stream", facility="box_culvert")
|
||||||
@@ -62,7 +63,8 @@ def test_원본에_있던_관은_빈칸_그대로(monkeypatch):
|
|||||||
_, points = basins._build("p", Ctx(), [old])
|
_, points = basins._build("p", Ctx(), [old])
|
||||||
by = {p.chainage_m: p for p in points}
|
by = {p.chainage_m: p for p in points}
|
||||||
assert by[100.0].options is None # 옛 빈칸 그대로
|
assert by[100.0].options is None # 옛 빈칸 그대로
|
||||||
assert by[300.0].options == _defaults("pipe") # 새로 보충된 관 = 기본값
|
# 새로 보충된 관 = 기본값 + 집수정 모양 L(54-39)
|
||||||
|
assert by[300.0].options == {**_defaults("pipe"), "inlet_structure": "L"}
|
||||||
|
|
||||||
|
|
||||||
def test_월류_폭_기본값은_등록부_엔진과_같음():
|
def test_월류_폭_기본값은_등록부_엔진과_같음():
|
||||||
|
|||||||
@@ -33,44 +33,48 @@ def test_cross_reference_constants_match_plan():
|
|||||||
assert "min_slope" not in _culvert_set(None)
|
assert "min_slope" not in _culvert_set(None)
|
||||||
|
|
||||||
|
|
||||||
def test_empty_options_fall_back_to_registry_defaults():
|
def test_empty_options_have_no_members():
|
||||||
"""옵션이 빈 저장분은 레지스트리 기본값(Ø1000·L10)으로 채운다.
|
"""빈 고르기 칸 = 그 부재 없음(54-39) — 구조 · 형태 · 관종 · 기초 빈 값."""
|
||||||
|
|
||||||
높이는 2026-09-14 브레인 판정으로 **관경 기준 최소 높이**(Ø1000 → 1.5 + 근입 0.5 = 2.0) —
|
|
||||||
횡단도가 그리는 그 높이. 옛 기본 2.5 는 횡단도가 안 쓰던 값이라 걷음.
|
|
||||||
"""
|
|
||||||
spec = _culvert_set(None)
|
spec = _culvert_set(None)
|
||||||
assert spec["type"] == "pipe"
|
assert spec["type"] == "pipe"
|
||||||
assert spec["diameter_m"] == 1.0
|
|
||||||
assert spec["min_cover_m"] == MIN_PIPE_COVER_M
|
assert spec["min_cover_m"] == MIN_PIPE_COVER_M
|
||||||
# 54-36 기본: 유입 = 집수정(높이 1.5) · 유출 = 흙막이 찰 · 높이 1.5
|
assert spec["pipe_kind"] is None
|
||||||
assert spec["inlet"]["structure"] == "집수정"
|
assert spec["foundation"] is None
|
||||||
assert spec["inlet"]["basin_height_m"] == 1.5
|
assert spec["inlet"] == {"role": "inlet", "structure": ""}
|
||||||
|
assert spec["outlet"] == {"role": "outlet", "structure": ""}
|
||||||
|
|
||||||
|
|
||||||
|
def test_saved_values_drive_members():
|
||||||
|
"""고른 값만 부재 — 빈 높이 = 관경 기준 최소(Ø1000 → 2.0) · 빈 형태 = 없음."""
|
||||||
|
spec = _culvert_set({"inlet_type": "집수정", "inlet_structure": "L", "outlet_type": "기슭막이"})
|
||||||
|
assert spec["inlet"]["basin_form"] == "L"
|
||||||
|
assert spec["inlet"]["basin_height_m"] is None
|
||||||
part = spec["outlet"]
|
part = spec["outlet"]
|
||||||
assert part["structure"] == "기슭막이"
|
assert part["structure"] == "기슭막이"
|
||||||
assert part["revet_height_m"] == 1.5
|
assert part["revet_form"] is None
|
||||||
assert part["revet_length_m"] == 10
|
assert part["revet_height_m"] == 2.0
|
||||||
assert part["face_slope"] == REVET_FACE_SLOPE
|
assert part["face_slope"] == REVET_FACE_SLOPE
|
||||||
# 보호공 = 낙차고(1.5) × 2 = 3.0m, 두께 0.45m(사용자 확정).
|
# 보호공 = 낙차고(2.0) × 2 = 4.0m, 두께 0.45m(사용자 확정).
|
||||||
assert part["apron_length_m"] == 3.0
|
assert part["apron_length_m"] == 4.0
|
||||||
assert part["apron_thickness_m"] == 0.45
|
assert part["apron_thickness_m"] == 0.45
|
||||||
assert part["revet_form"] == "돌쌓기(찰)"
|
|
||||||
|
|
||||||
|
|
||||||
def test_basin_inlet_skips_revet_and_apron():
|
def test_basin_inlet_skips_revet_and_apron():
|
||||||
"""유입구 = 집수정이면 그쪽 기슭막이·보호공을 만들지 않는다(라벨만)."""
|
"""유입구 = 집수정이면 그쪽 기슭막이·보호공을 만들지 않는다(라벨만)."""
|
||||||
spec = _culvert_set({"inlet_type": "집수정"})
|
spec = _culvert_set({"inlet_type": "집수정", "outlet_type": "기슭막이"})
|
||||||
assert spec["inlet"]["structure"] == "집수정"
|
assert spec["inlet"]["structure"] == "집수정"
|
||||||
assert "revet_height_m" not in spec["inlet"]
|
assert "revet_height_m" not in spec["inlet"]
|
||||||
assert "apron_length_m" not in spec["inlet"]
|
assert "apron_length_m" not in spec["inlet"]
|
||||||
# 유출측은 그대로 세트를 갖춘다.
|
# 유출측은 그대로 세트를 갖춘다.
|
||||||
assert spec["outlet"]["structure"] == "기슭막이"
|
assert spec["outlet"]["structure"] == "기슭막이"
|
||||||
assert spec["outlet"]["apron_length_m"] == 3.0 # 정의 기본 높이 1.5 × 2(54-36)
|
assert spec["outlet"]["apron_length_m"] == 4.0 # 빈 높이 = 관경 기준 최소 2.0 × 2
|
||||||
|
|
||||||
|
|
||||||
def test_string_diameter_and_custom_height():
|
def test_string_diameter_and_custom_height():
|
||||||
"""관경 문자열 저장분("800")과 사용자 높이 변경이 그대로 반영된다."""
|
"""관경 문자열 저장분("800")과 사용자 높이 변경이 그대로 반영된다."""
|
||||||
spec = _culvert_set({"pipe_diameter_mm": "800", "outlet_revet_height_m": 1.5})
|
spec = _culvert_set(
|
||||||
|
{"pipe_diameter_mm": "800", "outlet_type": "기슭막이", "outlet_revet_height_m": 1.5}
|
||||||
|
)
|
||||||
assert spec["diameter_m"] == 0.8
|
assert spec["diameter_m"] == 0.8
|
||||||
assert spec["outlet"]["revet_height_m"] == 1.5
|
assert spec["outlet"]["revet_height_m"] == 1.5
|
||||||
assert spec["outlet"]["apron_length_m"] == 3.0 # 1.5 × 2
|
assert spec["outlet"]["apron_length_m"] == 3.0 # 1.5 × 2
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ TSC = PROJECT_ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc"
|
|||||||
|
|
||||||
# 다섯 시설 × 기본값 폴백/명시값 섞기. 물넘이포장은 폭 절반이 옆 측점까지 걸친다.
|
# 다섯 시설 × 기본값 폴백/명시값 섞기. 물넘이포장은 폭 절반이 옆 측점까지 걸친다.
|
||||||
POINTS = [
|
POINTS = [
|
||||||
{"chainage_m": 20.0, "facility": "pipe", "options": None},
|
{"chainage_m": 20.0, "facility": "pipe", "options": {"pipe_diameter_mm": 1000}},
|
||||||
{
|
{
|
||||||
"chainage_m": 40.0,
|
"chainage_m": 40.0,
|
||||||
"facility": "pipe",
|
"facility": "pipe",
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ def test_registry_defines_basin_before_after():
|
|||||||
|
|
||||||
|
|
||||||
def test_basin_side_spec_carries_before_after():
|
def test_basin_side_spec_carries_before_after():
|
||||||
"""유입이 집수정이면 세트 제원에 길이와 전/후가 함께 실린다(레지스트리 기본값 경유)."""
|
"""유입이 집수정이면 세트 제원에 길이와 전/후가 함께 실린다 — 빈 전/후 = 값 없음(54-39)."""
|
||||||
spec = _culvert_set({"inlet_type": "집수정"})["inlet"]
|
spec = _culvert_set({"inlet_type": "집수정"})["inlet"]
|
||||||
assert spec["structure"] == "집수정"
|
assert spec["structure"] == "집수정"
|
||||||
assert spec["basin_length_m"] == 2.0
|
assert spec["basin_length_m"] == 2.0
|
||||||
assert spec["basin_before_m"] == 1.0
|
assert spec["basin_before_m"] is None
|
||||||
assert spec["basin_after_m"] == 1.0
|
assert spec["basin_after_m"] is None
|
||||||
# 집수정 쪽에는 기슭막이가 없다 — 벽 제원이 섞이면 링크 판정이 10m로 번진다.
|
# 집수정 쪽에는 기슭막이가 없다 — 벽 제원이 섞이면 링크 판정이 10m로 번진다.
|
||||||
assert "revet_length_m" not in spec
|
assert "revet_length_m" not in spec
|
||||||
|
|
||||||
@@ -60,10 +60,10 @@ def test_basin_before_after_follow_saved_options():
|
|||||||
|
|
||||||
|
|
||||||
def test_revet_span_defaults_unchanged():
|
def test_revet_span_defaults_unchanged():
|
||||||
"""기슭막이 기본은 그대로 길이 10m·전/후 5·5다(이번 변경이 건드리지 않는다)."""
|
"""빈 길이 · 전 · 후 = 값 없음(54-39 · 정의 기본값으로 안 읽음) · 빈 유출 구조 = 벽 없음."""
|
||||||
culvert = _culvert_set({"inlet_type": "기슭막이"})
|
culvert = _culvert_set({"inlet_type": "기슭막이"})
|
||||||
for role in ("inlet", "outlet"):
|
spec = culvert["inlet"]
|
||||||
spec = culvert[role]
|
assert spec["revet_length_m"] is None
|
||||||
assert spec["revet_length_m"] == 10.0
|
assert spec["revet_before_m"] is None
|
||||||
assert spec["revet_before_m"] == 5.0
|
assert spec["revet_after_m"] is None
|
||||||
assert spec["revet_after_m"] == 5.0
|
assert culvert["outlet"] == {"role": "outlet", "structure": ""}
|
||||||
|
|||||||
Reference in New Issue
Block a user