From 5a6617f27888915524351f0650beb5fd25bf90b9 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 7 Sep 2026 14:58:18 +0900 Subject: [PATCH] =?UTF-8?q?fix(=ED=9A=A1=EB=8B=A8):=20=EC=86=8C=EB=8B=A8?= =?UTF-8?q?=EC=9D=B4=20=EC=95=94=20=EA=B2=BD=EA=B3=84=20=EC=95=84=EB=9E=98?= =?UTF-8?q?=EB=A1=9C=20=EB=90=98=EB=8F=8C=EC=95=84=EA=B0=80=EB=A9=B4=20?= =?UTF-8?q?=EB=8B=A4=EC=8B=9C=20=EC=95=94=20=EA=B2=BD=EC=82=AC=EB=A1=9C=20?= =?UTF-8?q?=EA=B7=B8=EB=A6=BC=20(=EA=B3=84=ED=9A=8D=EC=84=9C=203-9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 배분 창 지적으로 확인한 자리 — 미룰 수 없는 쪽이었음. 무엇이 문제였나 — 소단은 평탄한데 암 경계선은 지반을 따라 올라감. 지반이 1:1 이면 폭 0.5m 소단 하나를 지나는 동안 경계는 0.5m 오르고 소단은 2°(0.017m)만 오름. 그래서 설계선이 경계 아래로 **되돌아 들어가는 일이 흔함**. 무릎을 한 번만 꺾는 종전 규칙으로는 그 구간을 **암인데 토사 경사로** 그려 절토가 조용히 커짐 — 경고도 안 뜸. 고침 — `multi_knee` 를 둬서 경계를 오갈 때마다 꺾게 함. **소단이 있을 때만** 켬. 소단이 없을 때 켜면 지금 측점들의 설계가 같이 바뀌기 때문임(실측: 물결 경계 0.0016m). 기본값은 거짓이라 소단을 안 쓰는 프로젝트는 그대로임. 자체검증 - 새 시험 1건 — 경계를 뚫고 올라갔다가 소단마다 뒤처져 되돌아 들어가는 지형에서 한 번만 꺾는 결과와 갈리고, 되돌아 들어간 구간이 암 경사(가파름)라 같은 거리에서 더 높이 올라가는 것을 확인. - 거울 시험에 되돌아 들어가는 경우를 한 줄 더해 파이썬·TS 일치 확인(12건). - 전체 539 passed · 18 skipped. Co-Authored-By: Claude Opus 5 (1M context) --- B06_Section/B06_Section_Engine_Design.py | 11 +++++++- common_util/common_util_cross_berm.py | 27 ++++++++++++------- common_util/common_util_cross_berm.ts | 13 +++++---- .../common_util_cross_design_geometry.ts | 3 +++ 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/B06_Section/B06_Section_Engine_Design.py b/B06_Section/B06_Section_Engine_Design.py index 75290112..2ea1e9ba 100644 --- a/B06_Section/B06_Section_Engine_Design.py +++ b/B06_Section/B06_Section_Engine_Design.py @@ -294,7 +294,16 @@ class _SectionGeometry: start_dist, start_z = self._slope_start(side) boundary = (lambda dist: self._rock_boundary_z(side, dist)) if self.two_stage else None points = cut_profile_points( - start_dist, start_z, self.cut_ratio, self.soil_cut_ratio, boundary, self.berm + start_dist, + start_z, + self.cut_ratio, + self.soil_cut_ratio, + boundary, + self.berm, + # 소단이 있으면 경계를 오갈 때마다 꺾는다 — 소단은 평탄한데 경계선은 지반을 + # 따라 올라가서 되돌아 들어가는 일이 흔하다. 한 번만 꺾으면 그 구간을 암인데 + # 토사 경사로 그려 절토가 조용히 커진다(2026-09-07). + multi_knee=self.berm is not None, ) self._cut_points_cache[side] = points return points diff --git a/common_util/common_util_cross_berm.py b/common_util/common_util_cross_berm.py index d0f28997..02c69c73 100644 --- a/common_util/common_util_cross_berm.py +++ b/common_util/common_util_cross_berm.py @@ -48,6 +48,7 @@ def cut_profile_points( rock_boundary_z: Callable[[float], float] | None, berm: BermSpec | None, max_reach_m: float = _MAX_REACH_M, + multi_knee: bool = False, ) -> list[tuple[float, float]]: """절토 사면 꼭짓점 `[(거리, 표고), ...]` — 사면 시작에서 바깥으로. @@ -55,10 +56,16 @@ def cut_profile_points( 있으면 걸어가며 경계를 만나는 자리에서 암(`cut_ratio`) → 토사(`soil_cut_ratio`)로 **한 번** 꺾는다. - ⚠ **한 번만 꺾는 것은 기존 규칙을 그대로 지킨 것이다.** 소단이 들어가면 사면이 경계를 여러 - 번 오갈 수 있어 「무릎은 하나」 전제를 풀 수 있으나, 풀면 **소단이 없는 지금 측점들의 설계도 - 같이 바뀐다**(2026-09-07 실측: 물결 경계에서 0.0016m, 경계를 다시 만나는 자리에서는 구조가 - 통째로 달라짐). 소단을 넣는 김에 기존 설계를 조용히 바꿀 수는 없으므로 **별건으로 미룬다**. + `multi_knee` 가 거짓이면 **한 번만** 꺾는다(기존 규칙 그대로). 참이면 경계를 오갈 때마다 + 꺾는다. + + ⚠ **소단이 있으면 반드시 참이어야 한다.** 소단은 평탄한데 경계선은 지반을 따라 올라가므로, + 폭 0.5m 짜리 소단 하나만 지나도 설계선이 경계 **아래로 되돌아가는 일이 흔하다**(지반이 + 1:1 이면 경계는 0.5m 오르고 소단은 2°=0.017m 만 오른다). 한 번만 꺾으면 그 구간을 **암인데 + 토사 경사로** 그려 절토가 조용히 커진다 — 경고도 안 뜬다(2026-09-07 배분 창 지적으로 확인). + + 반대로 소단이 없을 때 참으로 두면 **지금 측점들의 설계가 같이 바뀐다**(실측: 물결 경계에서 + 0.0016m). 그래서 기본값은 거짓이고, 소단을 줄 때만 참으로 켠다. `berm` 이 있으면 사면길이가 `interval_m` 에 닿을 때마다 폭 `width_m` 의 평탄부를 넣는다. 평탄부는 안쪽이 낮도록 `slope_deg` 만큼 기울어 있어 바깥으로 갈수록 조금 올라간다 @@ -99,11 +106,13 @@ def cut_profile_points( next_dist = dist + _STEP_M next_z = elevation + rise - # ② 암 → 토사 전환(무릎) — 한 번만. 교차점은 보간해 정확히 찍는다. - if rock_boundary_z is not None and not in_soil: + # ② 경계를 지나는 자리(무릎) — 교차점을 보간해 정확히 찍고 경사를 바꾼다. + # `multi_knee` 가 거짓이면 암 → 토사 한 번만 본다(기존 규칙). + if rock_boundary_z is not None and (multi_knee or not in_soil): diff_now = elevation - rock_boundary_z(dist) diff_next = next_z - rock_boundary_z(next_dist) - if diff_next >= 0: + crossed = (diff_next >= 0) if not in_soil else (diff_next < 0) + if crossed: span = diff_next - diff_now share = (-diff_now) / span if abs(span) > 1e-12 else 0.0 share = min(max(share, 0.0), 1.0) @@ -112,8 +121,8 @@ def cut_profile_points( slant_since_berm += math.hypot(knee_dist - dist, knee_z - elevation) dist, elevation = knee_dist, knee_z points.append((dist, elevation)) # 무릎 - in_soil = True - ratio = soil_cut_ratio + in_soil = not in_soil + ratio = soil_cut_ratio if in_soil else cut_ratio continue dist, elevation = next_dist, next_z diff --git a/common_util/common_util_cross_berm.ts b/common_util/common_util_cross_berm.ts index d9a669b4..c4e0a192 100644 --- a/common_util/common_util_cross_berm.ts +++ b/common_util/common_util_cross_berm.ts @@ -57,6 +57,7 @@ export function cutProfilePoints( rockBoundaryZ: ((dist: number) => number) | null, berm: BermSpec | null, maxReachM: number = MAX_REACH_M, + multiKnee = false, ): Array<[number, number]> { const points: Array<[number, number]> = [[startDist, startZ]]; let dist = startDist; @@ -90,11 +91,13 @@ export function cutProfilePoints( const nextDist = dist + STEP_M; const nextZ = elevation + rise; - // ② 암 → 토사 전환(무릎) — 한 번만. 교차점은 보간해 정확히 찍는다. - if (rockBoundaryZ !== null && !inSoil) { + // ② 경계를 지나는 자리(무릎) — 교차점을 보간해 정확히 찍고 경사를 바꾼다. + // `multiKnee` 가 거짓이면 암 → 토사 한 번만 본다(기존 규칙). + if (rockBoundaryZ !== null && (multiKnee || !inSoil)) { const diffNow = elevation - rockBoundaryZ(dist); const diffNext = nextZ - rockBoundaryZ(nextDist); - if (diffNext >= 0) { + const crossed = !inSoil ? diffNext >= 0 : diffNext < 0; + if (crossed) { const span = diffNext - diffNow; let share = Math.abs(span) > 1e-12 ? -diffNow / span : 0; share = Math.min(Math.max(share, 0), 1); @@ -104,8 +107,8 @@ export function cutProfilePoints( dist = kneeDist; elevation = kneeZ; points.push([dist, elevation]); // 무릎 - inSoil = true; - ratio = soilCutRatio; + inSoil = !inSoil; + ratio = inSoil ? soilCutRatio : cutRatio; continue; } } diff --git a/common_util/common_util_cross_design_geometry.ts b/common_util/common_util_cross_design_geometry.ts index bb9cdbc1..d9b9502f 100644 --- a/common_util/common_util_cross_design_geometry.ts +++ b/common_util/common_util_cross_design_geometry.ts @@ -219,6 +219,9 @@ export class SectionGeometry { this.soilCutRatio, boundary, this.berm, + undefined, + // 소단이 있으면 경계를 오갈 때마다 꺾는다 — 짝 파이썬과 같은 이유(2026-09-07). + this.berm !== null, ); this.cutPointsCache.set(side, points); return points;