diff --git a/B06_Section/B06_Section_UI_Cross_Culvert.ts b/B06_Section/B06_Section_UI_Cross_Culvert.ts index bf838e93..6a67e9a9 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert.ts @@ -120,12 +120,15 @@ export function appendCulvertOverlay( ); } // 집수정(ㄴ·ㄷ형) 계류측 성토부선 — 유출측과 같은 체계(2026-08-22 사용자 확정). + // 독립 기슭막이(관 숨김)에는 집수정이 없다 — 이 채널이 **유입측 벽**의 성토부선이라 + // 이름도 그렇게 적는다(2026-08-29 좌우 통일). + const basinFillName = layout.culvert.hidden_pipe ? "유입부" : "집수정 계류측"; for (const segment of layout.basinFill.segments) { planLine( segment.points, segment.kind === "cut" - ? `집수정 계류측 3도 절토선 — 구조물이 원지반에 묻혀 성토 불필요, 원지반 교차까지 ${segment.lengthM.toFixed(2)}m` - : `집수정 계류측 성토부선 — 사면길이 ${segment.lengthM.toFixed(2)}m` + + ? `${basinFillName} 3도 절토선 — 구조물이 원지반에 묻혀 성토 불필요, 원지반 교차까지 ${segment.lengthM.toFixed(2)}m` + : `${basinFillName} 성토부선 — 사면길이 ${segment.lengthM.toFixed(2)}m` + (segment.overLimit ? " (법정 5m 이상 — 기슭막이 의무 구간, 조정창에서 추가)" : " (법정 5m 이내)") + diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Const.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Const.ts index fe45f206..abdc97c1 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Const.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Const.ts @@ -85,7 +85,7 @@ export function revetHeightLimit(form: string | null | undefined): number { if (form.includes("메")) return 2.0; if (form.includes("찰")) return 3.0; if (form.includes("돌망태")) return 2.0; - if (form.includes("콘크리트")) return 4.0; // 콘크리트 흙막이 산복기초 4.0m 이하 + if (form.includes("콘크리트")) return 5.0; // 프로젝트 확정값 — materialLimit과 동일 return 3.0; } diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts index e34e16d3..5ca9ca45 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts @@ -151,7 +151,10 @@ export function computeCulvertLayout( ? "short" : null; const choice: InletStructureChoice = inletStructure ?? "auto"; - const { reason: basinReason, shape: basinShape } = resolveBasinChoice(choice, ruleReason); + // 독립 기슭막이(관 숨김)는 집수정을 세우지 않는다 — 설치 측이 상단(유입 역할)일 때만 + // 집수정이 서서 좌·우가 갈리던 원인(2026-08-29 사용자: 양쪽 다 성토 로직). + const { shape: basinShape, reason: choiceReason } = resolveBasinChoice(choice, ruleReason); + const basinReason = culvert.hidden_pipe ? null : choiceReason; // ㄴ·ㄷ형은 사이즈 유지한 채 통째로 노견 끝점 일치 — 바닥(=관 invert)이 따라 오른다. if (basinReason && basinShape !== "I") { inlet.elevation = inletInfo.edge.elevation_m - basinAdjust.innerHeightM; @@ -229,13 +232,20 @@ export function computeCulvertLayout( defaultD: PIPE_WALL_DEFAULT_RUN_M, groundAt, limitOffset: inletInfo.limit, + // 독립 기슭막이는 유출 벽과 같은 자리 규칙 — 매몰-무교차 자리 금지(2026-08-29). + requireCrossing: culvert.hidden_pipe === true, outletGuard: culvert.hidden_pipe ? undefined : { edge: outletInfo.edge, outward: outletInfo.outward, height: outletWallSpec.height, - toeOffset: slopeToeOffset(designAt, groundAt, outletInfo.edge.offset_m, outletInfo.limit), + toeOffset: slopeToeOffset( + designAt, + groundAt, + outletInfo.edge.offset_m, + outletInfo.limit, + ), limitOffset: outletInfo.limit, }, }); @@ -400,7 +410,7 @@ export function computeCulvertLayout( }; return wall; }; - buildWall( + const inletWall = buildWall( culvert.inlet, inlet, inletInfo.outward, @@ -583,15 +593,16 @@ export function computeCulvertLayout( } // 유입 기슭막이의 관 시작 접속선 — 위 0도 성토선 / 아래 0도 1m 공간 확보에서 끝 // (2026-08-23 사용자 — 절토 경사 연장 삭제). - const inletFill = !basin - ? inletGroundConnector( - pipeCorners.inlet.bottom, - inletInfo.outward, - groundAt, - section.design?.cut_slope_ratio ?? 1.0, - inletInfo.limit, - ) - : null; + const inletFill = + !basin && !culvert.hidden_pipe + ? inletGroundConnector( + pipeCorners.inlet.bottom, + inletInfo.outward, + groundAt, + section.design?.cut_slope_ratio ?? 1.0, + inletInfo.limit, + ) + : null; // 성토부선 + 다단 기슭막이(2026-08-22 — 보호공 삭제, 윗면 선만 성토부선으로). // 유출측과 **집수정 계류측**이 같은 체계를 쓴다: 5m 넘으면 다단을 둘 수 있다. @@ -603,14 +614,21 @@ export function computeCulvertLayout( adjusts: (revetShift?.extras ?? []).map(adjustOf), equalize: equalizeExtras === true, }); - const basinExtras = buildExtrasAt(basinFillStart, { - startBottomElevation: basinFillBottom, - outward: inletInfo.outward, - groundAt, - limitOffset: inletInfo.limit, - adjusts: (revetShift?.basinExtras ?? []).map(adjustOf), - equalize: false, - }); + // 독립 기슭막이(관 숨김)는 집수정이 없어 이 채널이 **유입측 벽의 성토부선·다단**이다 — + // 유출측(`extras`)과 같은 시작점(관 하단 꼭짓점)·같은 `bextra` 키(2026-08-29 사용자). + const basinExtras = buildExtrasAt( + culvert.hidden_pipe ? (inletWall ? pipeCorners.inlet.bottom : null) : basinFillStart, + { + startBottomElevation: culvert.hidden_pipe + ? (inletWall?.bottomBack.elevation ?? 0) + : basinFillBottom, + outward: inletInfo.outward, + groundAt, + limitOffset: inletInfo.limit, + adjusts: (revetShift?.basinExtras ?? []).map(adjustOf), + equalize: false, + }, + ); // ── 성토 사면 구간 확정. 벽 자리가 굳은 뒤에 물매를 역산해야 관 길이 맞춤(벽 이동)이 // 접점을 다시 깨뜨리지 않는다 — 종전 어긋남의 직접 원인이 이 순서였다. diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Solve.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Solve.ts index 8a45e345..6ebc313a 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Solve.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Solve.ts @@ -487,9 +487,11 @@ export function placeInletWall(input: { toeOffset: number; limitOffset: number; }; + /** 매몰-무교차 자리 금지(유출 벽 규칙) — 독립 기슭막이는 유입도 같은 규칙을 탄다. */ + requireCrossing?: boolean; }): PipeWallPlacement { const place = (adjust: WallAdjust): PipeWallPlacement => - placePipeWall({ ...input, adjust, requireCrossing: false }); + placePipeWall({ ...input, adjust, requireCrossing: input.requireCrossing === true }); let placed = place(input.adjust); const guard = input.outletGuard; if (!guard) return placed; diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Wire.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Wire.ts index 14d5b79b..89378a5a 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Wire.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Wire.ts @@ -253,8 +253,10 @@ function trimLinkedLayout(layout: CulvertLayout, link: CulvertLink): CulvertLayo // 집수정은 **소유 측점 횡단도 하나에만** 선다(2026-08-24 사용자 확정) — 기슭막이와 // 달리 옆 측점으로 이어지는 구조물이 아니다. 계류측 다단·성토부선도 집수정에 딸린 // 것이라 함께 뺀다. 3D에서는 소유 측점 하나가 자기 길이(기본 2m)만큼 스윕한다. - basinExtras: [], - basinFill: empty, + // 단 독립 기슭막이(관 숨김)는 집수정이 없고 이 채널이 **유입측 벽의 다단·성토부선** + // 이라, 유출측과 같은 규칙으로 그 벽의 연장을 따른다(2026-08-29 좌우 통일). + basinExtras: spec.hidden_pipe && inletIn ? layout.basinExtras : [], + basinFill: spec.hidden_pipe && inletIn ? layout.basinFill : empty, basin: null, designTrim: keepTrimSides( layout.designTrim, diff --git a/B06_Section/B06_Section_UI_Cross_Structure_Panel.ts b/B06_Section/B06_Section_UI_Cross_Structure_Panel.ts index 08a93d37..46389f7f 100644 --- a/B06_Section/B06_Section_UI_Cross_Structure_Panel.ts +++ b/B06_Section/B06_Section_UI_Cross_Structure_Panel.ts @@ -47,9 +47,13 @@ export interface StructurePanelDeps { /** 집수정 좌우 — **화면 기준**(+ = 화면 왼쪽). 부호 환산·한계 토스트는 카드가 한다. */ nudgeBasinLateral: (screenDeltaM: number) => void; resetBasin: () => void; - /** 집수정 계류측 다단 상태·단 수 지정(2026-08-22 — 유출측과 같은 체계). */ + /** 집수정 계류측 다단 상태·단 수 지정(2026-08-22 — 유출측과 같은 체계). + * 독립 기슭막이(관 숨김)에서는 집수정이 없으므로 이 채널이 **유입측 벽의 다단**이다. */ basinExtraState: () => { canAdd: boolean; count: number }; setBasinExtraCount: (count: number) => void; + /** 독립 기슭막이(관 숨김) 카드인가 — 유입·유출 벽이 같은 성토 로직이라 조정창도 + * 좌우 같은 행을 준다(집수정 형식 숨김 · 유입측 단 수 행 표시 — 2026-08-29). */ + hiddenPipe: () => boolean; /** 이동 조작 가능 여부 — 집수정(자리 고정)은 숨긴다. */ canNudge: (key: RevetKey) => boolean; /** 좌우·상하 걸음이 0.1m인가(독립·추가 기슭막이). 배관 기준벽은 false = 1m. */ @@ -505,9 +509,12 @@ export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHan : movable ? L("B06_Cross_Revet_Inlet") : L("B06_Cross_Struct_InletBasin"); + // 독립 기슭막이(관 숨김)는 유입·유출이 같은 성토 벽이다 — 집수정 형식 선택을 + // 숨기고 유입측에도 단 수 행을 준다(2026-08-29 사용자: 좌우 조작 통일). + const ownTiers = deps.hiddenPipe() && key === "inlet"; // 형식 선택은 유입측에서만, 이동·높이 조작은 기슭막이(집수정 제외)만. - structureRow.classList.toggle("is-hidden", key !== "inlet"); - if (key === "inlet") { + structureRow.classList.toggle("is-hidden", key !== "inlet" || deps.hiddenPipe()); + if (key === "inlet" && !deps.hiddenPipe()) { // 상황에 안 맞는 선택지는 숨긴다(2026-08-22 사용자). 단 지금 선택된 값은 // 남긴다 — 숨기면 셀렉트가 빈 값이 된다. const allow = deps.optionsFor(); @@ -528,10 +535,14 @@ export function buildStructurePanel(deps: StructurePanelDeps): StructurePanelHan basinRow.row.classList.toggle("is-hidden", !isBasin); basinReset.classList.toggle("is-hidden", !isBasin); const basinExtra = deps.basinExtraState(); + // 독립 기슭막이 유입측은 유출측과 같은 규칙 — 단 수 행이 **항상** 보인다. basinExtraRow.classList.toggle( "is-hidden", - !(isBasin && (basinExtra.canAdd || basinExtra.count > 0)), + !(ownTiers || (isBasin && (basinExtra.canAdd || basinExtra.count > 0))), ); + const basinExtraLabel = basinExtraRow.querySelector(".b06-structure-panel__label"); + if (basinExtraLabel) + basinExtraLabel.textContent = ownTiers ? "다단 기슭막이(단)" : "집수정 계류측 기슭막이(단)"; basinCount = basinExtra.count; basinCountValue.textContent = `${basinExtra.count}단`; // I형은 내공(높이·폭) 개념이 없다 — 9키의 **네 모서리**만 숨긴다. diff --git a/B06_Section/B06_Section_UI_Cross_View_Structure.ts b/B06_Section/B06_Section_UI_Cross_View_Structure.ts index 5aca099e..c434020d 100644 --- a/B06_Section/B06_Section_UI_Cross_View_Structure.ts +++ b/B06_Section/B06_Section_UI_Cross_View_Structure.ts @@ -138,11 +138,11 @@ export function structurePanelDeps(ctx: StructurePanelContext): StructurePanelDe canNudge: (key) => key !== "inlet" || !ctx.inletIsBasin(), // 걸음 분류(2026-08-29 사용자): **배관 기준벽만 1m**, 나머지(독립 기슭막이 기준벽· // 배관/독립 종속 추가 벽·옛 D경로 독립 벽)는 0.1m. - fineMove: (key) => - (key !== "inlet" && key !== "outlet") || ctx.hiddenPipe(), + fineMove: (key) => (key !== "inlet" && key !== "outlet") || ctx.hiddenPipe(), optionsFor: () => ctx.inletOptions(), extraState: () => ctx.extraState(), basinExtraState: () => ctx.basinExtraState(), + hiddenPipe: () => ctx.hiddenPipe(), setExtraCount: (count) => { // 줄어들며 사라지는 벽이 선택 중이면 선택을 유출 벽으로 옮긴다(빈 대상 방지). const active = ctx.activeRevet() ?? "";