From 564b289ee64c5d024e5d772f002c0e359216e93c Mon Sep 17 00:00:00 2001 From: umsangdon Date: Fri, 21 Aug 2026 17:24:35 +0900 Subject: [PATCH] =?UTF-8?q?fix(B06):=20=EC=9D=B4=EB=8F=99=EB=9F=89?= =?UTF-8?q?=EC=9D=B4=20=ED=95=9C=EA=B3=84=EC=97=90=20=EA=B1=B8=EB=A0=A4?= =?UTF-8?q?=EB=8F=84=20=EC=88=AB=EC=9E=90=EB=A7=8C=20=EC=BB=A4=EC=A7=80?= =?UTF-8?q?=EB=8D=98=20=EB=AC=B8=EC=A0=9C=C2=B7=EC=95=88=EC=AA=BD=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EC=B5=9C=EC=86=8C=20=EB=AC=BC=EB=A7=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 조정창 숫자가 실제와 갈렸다. 안쪽으로 계속 누르니 "안쪽 10.0m"까지 올라가는데 벽은 노견 한계에서 멈춰 있었다. 기하가 **실제 적용한 이동량**(revetShift)을 돌려 주고, 카드가 그 값을 제어기에 되담는다 — 이제 한계에 닿으면 숫자도 멈춘다. - 안쪽으로 당기면 사면이 짧아져 물매가 급해진다. **1:1.2(가장 급한 한계)**를 넘지 않는 자리까지만 들어가게 막았다(2026-08-21 사용자 지시). 바깥으로 밀 때는 완만해 지므로 막지 않고 범위 밖이면 경고로만 알린다. 검증: 4+4.3은 자동 자리가 이미 1:1.21이라 안쪽 이동 0(차단), 13+15.7은 −1m까지 허용(1:1.61)하고 그 이상 차단. 조정창 숫자도 같은 자리에서 멈춘다. 두께 16케이스 0.450 고정, 계획고 스윕 24케이스 문제 0. tsc 통과, pytest 148 pass. Co-Authored-By: Claude Opus 5 (1M context) --- B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts | 7 +++++++ B06_Section/B06_Section_UI_Cross_Culvert_Solve.ts | 11 ++++++++++- B06_Section/B06_Section_UI_Cross_Culvert_Types.ts | 6 ++++++ B06_Section/B06_Section_UI_Cross_View.ts | 8 ++++++++ B06_Section/B06_Section_UI_Page_Station_Controls.ts | 8 ++++++++ 5 files changed, 39 insertions(+), 1 deletion(-) 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));