diff --git a/A00_Common/design/design_structure_guard.ts b/A00_Common/design/design_structure_guard.ts index e1b4fbdb5..3d4ad1c45 100644 --- a/A00_Common/design/design_structure_guard.ts +++ b/A00_Common/design/design_structure_guard.ts @@ -83,11 +83,12 @@ export const revetKeys = (side: "inlet" | "outlet"): Record => ( }); /** 세월교 · 박스 유입 · 유출 흙막이 칸(79-7) — 높이 빈 칸 = 자동(`heightHint` · 횡단도가 셈) · - * 절토부 앞 수평공간 칸(`{side}_revet_berm_m`)을 더함. */ + * 유입만 절토부 앞 수평공간 칸(`inlet_revet_berm_m` · 유출은 없음 · 89-4)을 더함. */ export function bodyGuardItems(side: "inlet" | "outlet", heightHint: string): SpecItem[] { const items = guardItems({ 흙막이: revetKeys(side) }, undefined, true).map((item) => item.id === "height" ? { ...item, default: undefined, hint: heightHint } : item, ); + if (side === "outlet") return items; const berm: SpecItem = { id: "berm", label: "수평공간 (m)", diff --git a/B05_Profile/B05_Profile_UI_Drainage_Facility_Merge.ts b/B05_Profile/B05_Profile_UI_Drainage_Facility_Merge.ts index 0bab3c485..49dc35c2e 100644 --- a/B05_Profile/B05_Profile_UI_Drainage_Facility_Merge.ts +++ b/B05_Profile/B05_Profile_UI_Drainage_Facility_Merge.ts @@ -43,7 +43,7 @@ export const FORM_OPTION_KEYS: Readonly> = { ...revetKeys("inlet"), ...revetKeys("outlet"), "inlet_revet_berm_m", // 흙막이 절토부 앞 수평공간(79-7) - "outlet_revet_berm_m", + "outlet_revet_berm_m", // 칸 없앰(89-4) — 목록에 남겨 다음 [저장] 때 옛 값을 지움 ], // 물넘이 포장 = 표 03 명세 키(PLAN 52-3 · `design_structure_ford_pavement`) + 월류 폭 · 높이(65-5) — 옛 경사 · // 두께 · 길이는 폼에서 빠져 이어 붙임 · 도수로/돌붙임 키는 `FORM_OPTION_PREFIXES` @@ -61,7 +61,7 @@ export const FORM_OPTION_KEYS: Readonly> = { ...revetKeys("inlet"), ...revetKeys("outlet"), "inlet_revet_berm_m", - "outlet_revet_berm_m", + "outlet_revet_berm_m", // 칸 없앰(89-4) — 목록에 남겨 다음 [저장] 때 옛 값을 지움 ], revetment: ["side", ...revetKeys("inlet"), ...revetKeys("outlet")], }; diff --git a/B06_Section/B06_Section_Engine_Culvert.py b/B06_Section/B06_Section_Engine_Culvert.py index 9f0631eec..ab28ec192 100644 --- a/B06_Section/B06_Section_Engine_Culvert.py +++ b/B06_Section/B06_Section_Engine_Culvert.py @@ -329,8 +329,8 @@ def _guard_set(values: dict[str, Any], body: str, diameter_m: float) -> dict[str for role, key in zip(("inlet", "outlet"), _GUARD_KEYS[body]): guarded = str(values.get(key) or "").strip() == "흙막이" sides[role] = _revet_side(values, role) if guarded else {"role": role, "structure": ""} - if guarded: - # 절토부 앞 수평공간(79-7) — 흙막이 컨테이너 칸 · 빈 값 = 화면 기본값 + if guarded and role == "inlet": + # 절토부 앞 수평공간(79-7) — 유입만(유출은 없음 · 89-4) · 빈 값 = 화면 기본값 sides[role]["revet_berm_m"] = _number(values.get(f"{role}_revet_berm_m"), None) if not any(side["structure"] for side in sides.values()): return None diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Tiers.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Tiers.ts index 6396cc06f..4978284d2 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Tiers.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Tiers.ts @@ -5,7 +5,7 @@ * 집수정 계류측 두 채널의 입력을 짜고 면적용(미확정 단 제외) 결과를 함께 돌려준다. * ========================================================================== */ -import type { CrossSection, CulvertSideSpec } from "./B06_Section_Api_Fetch"; +import type { CrossSection } from "./B06_Section_Api_Fetch"; import { GUARD_CUT_BERM_M, REVET_EMBED_DEPTH_M, @@ -85,17 +85,14 @@ export function buildCulvertTiers(input: { side: culvert?.hidden_pipe ? culvert.inlet.face_side : null, }); // 세월교 · 박스 흙막이 끝 절토 = 컨테이너 수평공간 뒤 절토 경사(79-7) · 배관은 종전(3도 선). - const cutBerm = (spec: CulvertSideSpec | undefined) => + const cutBerm = (lengthM: number) => culvert?.body - ? { - lengthM: Math.max(spec?.revet_berm_m ?? GUARD_CUT_BERM_M, 0), - slopeRatio: section.design?.cut_slope_ratio ?? 1.0, - } + ? { lengthM: Math.max(lengthM, 0), slopeRatio: section.design?.cut_slope_ratio ?? 1.0 } : undefined; // 성토부선 + 다단 기슭막이(2026-08-22 — 보호공 삭제, 윗면 선만 성토부선으로). // 유출측과 **집수정 계류측**이 같은 체계를 쓴다: 5m 넘으면 다단을 둘 수 있다. const outletInput = { - cutBerm: cutBerm(culvert?.outlet), + cutBerm: cutBerm(0), // 유출 수평공간 없음 — 끝이 원지반에 묻히면 바로 절토(89-4) startBottomElevation: input.outletBottom, outward: input.outletInfo.outward, groundAt, @@ -109,7 +106,7 @@ export function buildCulvertTiers(input: { // 독립 기슭막이(관 숨김)는 집수정이 없어 이 채널이 **유입측 벽의 성토부선·다단**이다 — // 유출측(`extras`)과 같은 시작점(관 하단 꼭짓점)·같은 `bextra` 키(2026-08-29 사용자). const basinInput = { - cutBerm: cutBerm(culvert?.inlet), + cutBerm: cutBerm(culvert?.inlet.revet_berm_m ?? GUARD_CUT_BERM_M), startBottomElevation: input.basinBottom, outward: input.inletInfo.outward, groundAt, diff --git a/common_util/common_util_culvert_sets.ts b/common_util/common_util_culvert_sets.ts index 235142da7..490e7d4c6 100644 --- a/common_util/common_util_culvert_sets.ts +++ b/common_util/common_util_culvert_sets.ts @@ -213,12 +213,13 @@ export function guardSet( diameterM: number, ): CulvertSetSpec | null { const [inletKey, outletKey] = GUARD_KEYS[body]; - // 절토부 앞 수평공간(79-7) — 흙막이 컨테이너 칸 · 빈 값 = 화면 기본값 const side = (role: string, key: string): CulvertSetSpec => String(values[key] ?? "").trim() === "흙막이" - ? { ...revetSide(values, role), revet_berm_m: num(values[`${role}_revet_berm_m`], null) } + ? revetSide(values, role) : { role, structure: "" }; const inlet = side("inlet", inletKey); + // 절토부 앞 수평공간(79-7) — 유입만(유출은 없음 · 89-4) · 빈 값 = 화면 기본값 + if (inlet.structure) inlet.revet_berm_m = num(values.inlet_revet_berm_m, null); const outlet = side("outlet", outletKey); if (!inlet.structure && !outlet.structure) return null; return { diff --git a/resources/master_template/구조물정의.json b/resources/master_template/구조물정의.json index 32a69a793..308e34b20 100644 --- a/resources/master_template/구조물정의.json +++ b/resources/master_template/구조물정의.json @@ -3418,7 +3418,6 @@ "outlet.height": "높이 (m)", "outlet.before": "기준측점 전 (m)", "outlet.after": "기준측점 후 (m)", - "outlet.berm": "수평공간 (m)", "soil_add": "흙막이", "soil_add.form": "형태", "soil_add.form=돌쌓기(찰)": "돌 쌓기(찰)", @@ -3489,7 +3488,6 @@ "outlet.height": "높이 (m)", "outlet.before": "기준측점 전 (m)", "outlet.after": "기준측점 후 (m)", - "outlet.berm": "수평공간 (m)", "soil_add": "흙막이", "soil_add.form": "형태", "soil_add.form=돌쌓기(찰)": "돌 쌓기(찰)", diff --git a/resources/tester/helper_79_guard_shape.cjs b/resources/tester/helper_79_guard_shape.cjs index aacab3ac3..4fa0852ee 100644 --- a/resources/tester/helper_79_guard_shape.cjs +++ b/resources/tester/helper_79_guard_shape.cjs @@ -137,9 +137,14 @@ out.boxWing = wingLayout.sides.map((s) => ({ // ⑤ 흙막이 끝 절토 = 수평공간 뒤 절토 경사 — 수평공간 칸 값을 따름 function cutTails(section, list) { const culvert = computeStoredLayouts(section, list).culvert; - return [...culvert.outletFill.segments, ...culvert.basinFill.segments] + const tagged = (segments, role) => segments.map((g) => ({ ...g, role })); + return [ + ...tagged(culvert.outletFill.segments, "outlet"), + ...tagged(culvert.basinFill.segments, "inlet"), + ] .filter((g) => g.kind === "cut") .map((g) => ({ + role: g.role, points: g.points.length, berm: r3(Math.abs(g.points[1].offset - g.points[0].offset)), flat: r3(g.points[1].elevation - g.points[0].elevation), @@ -148,7 +153,7 @@ function cutTails(section, list) { out.cutDefault = [cutTails(at(352.14), sections), cutTails(at(173.087), sections)]; const berm2 = clone(at(352.14)); berm2.culvert.inlet.revet_berm_m = 2; -berm2.culvert.outlet.revet_berm_m = 2; +berm2.culvert.outlet.revet_berm_m = 2; // 유출은 칸이 없어 무시(89-4) out.cutBerm2 = cutTails( berm2, sections.map((s) => (s === at(352.14) ? berm2 : s)), diff --git a/resources/tester/test_65_ford_defaults.py b/resources/tester/test_65_ford_defaults.py index 052826723..863d2d2a4 100644 --- a/resources/tester/test_65_ford_defaults.py +++ b/resources/tester/test_65_ford_defaults.py @@ -35,7 +35,7 @@ GUARD = [ ["height", ["흙막이"], None], # 빈 칸 = 본체 끝 높이(79-7 · 횡단도가 셈) ["before", ["흙막이"], "5"], ["after", ["흙막이"], "5"], - ["berm", ["흙막이"], "1"], # 절토부 앞 수평공간(79-7) + ["berm", ["흙막이"], "1"], # 절토부 앞 수평공간(79-7) — 유입만(89-4) ] @@ -55,7 +55,8 @@ def out() -> dict: def test_유입_유출은_구조와_흙막이_칸만(out): for spec in ("ford", "box"): for side in ("inlet", "outlet"): - assert out[spec][side] == GUARD, (spec, side) + want = GUARD if side == "inlet" else GUARD[:-1] + assert out[spec][side] == want, (spec, side) assert [i[0] for i in out[spec]["soil_add"]] == [ "form", "length", diff --git a/resources/tester/test_79_guard_shape.py b/resources/tester/test_79_guard_shape.py index f12a573dc..9c8f0291d 100644 --- a/resources/tester/test_79_guard_shape.py +++ b/resources/tester/test_79_guard_shape.py @@ -79,12 +79,14 @@ def test_박스_날개벽_끝_절성토선(shape: dict) -> None: def test_흙막이_끝_절토는_수평공간_뒤(shape: dict) -> None: tails = [tail for group in shape["cutDefault"] for tail in group] assert tails, "절토 끝선이 없음" + assert any(tail["role"] == "inlet" for tail in tails) for tail in tails: assert tail["points"] == 3 assert tail["flat"] == pytest.approx(0, abs=1e-6) - assert tail["berm"] == pytest.approx(1.0, abs=1e-3) + # 유출은 수평공간 없음 — 묻히면 바로 절토(89-4) + assert tail["berm"] == pytest.approx(1.0 if tail["role"] == "inlet" else 0, abs=1e-3) for tail in shape["cutBerm2"]: - assert tail["berm"] == pytest.approx(2.0, abs=1e-3) + assert tail["berm"] == pytest.approx(2.0 if tail["role"] == "inlet" else 0, abs=1e-3) def test_유입_수평공간_시작은_본체_바닥(shape: dict) -> None: