diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts index 53647d45..35081935 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts @@ -118,6 +118,8 @@ export function computeCulvertLayout( // 하므로 자리를 풀어서 정한다(집수정은 측구부 자리 그대로). const wallHeightFor = (spec: CulvertSideSpec): number => Math.min(revetTargetHeight(diameter), revetHeightLimit(spec.revet_form)); + /** 요청값이 한계에 걸려 잘린 뒤의 **실제** 이동량. 조정창이 이 값을 되받는다. */ + const appliedShift = { inlet: 0, outlet: 0 }; let inletPlacement: FillWallPlacement | null = null; if (!basinReason && designAt) { // 훑는 범위는 **성토 사면 끝까지**다 — 그 너머는 받칠 성토가 없다. 성토가 얕아 벽 @@ -142,6 +144,8 @@ export function computeCulvertLayout( inletBaseAt, inletInfo.outward, ); + appliedShift.inlet = + Math.round((shifted - inletPlacement.offset) * inletInfo.outward * 10) / 10; inlet.offset = shifted; inlet.elevation = Math.min(groundAt(shifted), invertCap(inletInfo.edge)); } @@ -440,6 +444,8 @@ export function computeCulvertLayout( invertAt, outletInfo.outward, ); + appliedShift.outlet = + Math.round((shifted - outletPlacement.offset) * outletInfo.outward * 10) / 10; outletWallAnchor = { offset: shifted, elevation: invertAt(shifted) }; } } @@ -669,6 +675,7 @@ export function computeCulvertLayout( minLengthM: minPitchingM, }, basin, + revetShift: appliedShift, designTrim: walls.length || basin ? { diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Solve.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Solve.ts index 5a30663b..bb16575e 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Solve.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Solve.ts @@ -235,12 +235,21 @@ export function clampWallOffset( outward: number, ): number { const steps = 40; + // 안쪽으로 당기면 사면이 짧아져 물매가 급해진다 — **1:1.2(가장 급한 한계)**를 넘지 + // 않게 막는다(2026-08-21 사용자 지시). 바깥으로 밀 때는 완만해지므로 여기서 막지 + // 않고(관 길이 조정 여지를 남긴다) 범위 밖이면 경고로만 알린다. + const inward = (wantedOffset - autoOffset) * outward < 0; let allowed = autoOffset; for (let i = 1; i <= steps; i += 1) { const candidate = autoOffset + ((wantedOffset - autoOffset) * i) / steps; // 벽은 제 노견 바깥에 선다 — 안쪽으로 넘기면 도로 밑을 파고들어 반대편까지 간다. if ((candidate - edge.offset_m) * outward < 0) break; - if (edge.elevation_m - (baseAt(candidate) + height) < FILL_MIN_RISE_M) break; + const rise = edge.elevation_m - (baseAt(candidate) + height); + if (rise < FILL_MIN_RISE_M) break; + if (inward) { + const run = Math.abs(candidate + outward * REVET_TRAP_TOP_M - edge.offset_m); + if (run / rise < FILL_SLOPE_RATIO_MIN) break; + } allowed = candidate; } return allowed; diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts index e1479e9a..c7bbb6ca 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts @@ -111,4 +111,10 @@ export interface CulvertLayout { /** 유입 집수정 단면(있을 때만). */ basin: BasinLayout | null; designTrim: CulvertDesignTrim | null; + /** + * **실제 적용된** 기슭막이 이동량(m, + = 계류측 바깥). 요청값이 노견·사면 역전·사면 끝 + * 한계에 걸리면 잘려 들어온다 — 조정창은 이 값을 보여 주고 저장한다. 그러지 않으면 + * 눌러도 안 움직이는데 숫자만 계속 커진다(2026-08-21 사용자 지적). + */ + revetShift: { inlet: number; outlet: number }; } diff --git a/B06_Section/B06_Section_UI_Cross_View.ts b/B06_Section/B06_Section_UI_Cross_View.ts index 8397ef27..58e03df5 100644 --- a/B06_Section/B06_Section_UI_Cross_View.ts +++ b/B06_Section/B06_Section_UI_Cross_View.ts @@ -167,6 +167,8 @@ export interface RevetOffsetControl { shiftFor: (section: CrossSection, role: RevetKey) => number; selectedFor: (section: CrossSection) => RevetKey | null; select: (chainageM: number, key: RevetKey | null) => void; + /** 기하가 실제로 적용한 이동량을 되받아 담는다(한계에 걸린 요청값을 잘라 낸다). */ + syncShift: (chainageM: number, role: RevetKey, appliedM: number) => void; adjust: (chainageM: number, role: RevetKey, deltaM: number) => void; reset: (chainageM: number, role: RevetKey) => void; } @@ -484,6 +486,12 @@ export function createCrossSectionCard( } // 배수관 측점 세트(배관·기슭막이·보호공) — 기존 도형 위에 추가만 한다(2026-08-19). culvertPipeLengthM = culvertLayout?.pipe.lengthM ?? null; + // 요청한 이동량이 한계에 걸려 잘렸으면 그 값으로 되돌려 담는다 — 안 그러면 눌러도 + // 안 움직이는데 창의 숫자만 계속 커진다(2026-08-21 사용자 지적). + if (culvertLayout && revetOffset) { + revetOffset.syncShift(section.chainage_m, "inlet", culvertLayout.revetShift.inlet); + revetOffset.syncShift(section.chainage_m, "outlet", culvertLayout.revetShift.outlet); + } if (culvertLayout) { setRevetActive = appendCulvertOverlay( plotLayer, diff --git a/B06_Section/B06_Section_UI_Page_Station_Controls.ts b/B06_Section/B06_Section_UI_Page_Station_Controls.ts index 5d8ce1fb..80251447 100644 --- a/B06_Section/B06_Section_UI_Page_Station_Controls.ts +++ b/B06_Section/B06_Section_UI_Page_Station_Controls.ts @@ -146,6 +146,14 @@ export function createStationControls(deps: StationControlDeps): StationControls if (key) revetSelected.set(chainageM.toFixed(2), key); else revetSelected.delete(chainageM.toFixed(2)); }, + syncShift: (chainageM, role, appliedM) => { + const key = revetKey(chainageM, role); + const stored = revetShifts.get(key) ?? 0; + if (Math.abs(stored - appliedM) < 1e-9) return; + if (Math.abs(appliedM) < 1e-9) revetShifts.delete(key); + else revetShifts.set(key, appliedM); + persistRevetShifts(); + }, adjust: (chainageM, role, deltaM) => { const key = revetKey(chainageM, role); revetShifts.set(key, clampRevetShift((revetShifts.get(key) ?? 0) + deltaM));