From 63bd922711b1efe162a766780fab71b429a63c09 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 22 Aug 2026 15:06:31 +0900 Subject: [PATCH] =?UTF-8?q?fix(B06):=20=EA=B4=80=20=EB=81=9D=EC=A0=90=20?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=20=3D=20=EC=8B=A4=EC=A0=9C=20=EB=B0=94?= =?UTF-8?q?=EB=8B=A5+0.5=20=C2=B7=20=EC=B6=94=EA=B0=80=20=EB=B2=BD=20?= =?UTF-8?q?=EB=A7=A4=EB=AA=B0=20=EC=9D=B4=EB=8F=99=20=EC=B0=A8=EB=8B=A8=20?= =?UTF-8?q?=C2=B7=20=EA=B8=B0=EB=B3=B8=20=EB=86=92=EC=9D=B4=202.0=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 유출 관 하단 끝점·다음 단 성토선 시작점의 기준을 기준선(anchor)이 아니라 **실제 바닥(전면 근입 심화 반영) +0.5m**로 통일 — 가파른 지반에서 바닥만 깊어지고 관이 벽 위쪽에 떠 보이던 문제 해소(2026-08-22 사용자 지적) - 추가 기슭막이 이동 한계 = 원지반 매몰 자리 금지: 이음선 상단이 지반 아래 (통째 매몰)이거나 자기 성토선 시작점이 지반 아래 0.05m 이상이면 그 앞에서 멈춤(상하 먼저, 다음 좌우 되돌림) — 자동 자리조차 매몰이면 그 단은 불가 - 배관 기슭막이 기본 높이 = 재질 무관 2.0m(찰·콘크리트 기본 2.5 → 2.0 정정) Co-Authored-By: Claude Opus 5 (1M context) --- .../B06_Section_UI_Cross_Culvert_Const.ts | 4 +- .../B06_Section_UI_Cross_Culvert_Extra.ts | 58 +++++++++++++++++-- .../B06_Section_UI_Cross_Culvert_Geom.ts | 17 ++++-- 3 files changed, 66 insertions(+), 13 deletions(-) diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Const.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Const.ts index 8ddd04c7..5f707d51 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Const.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Const.ts @@ -112,8 +112,8 @@ export function materialFromForm(form: string | null | undefined): RevetMaterial /** 배관 기슭막이 높이 조작 하한(m) — 2~3m 구간 지시(2026-08-22)의 하한. */ export const PIPE_WALL_MIN_HEIGHT_M = 2.0; -/** 배관 기슭막이 기본 높이(m, 2026-08-22 지시 2.5) — 재질 한계로 잘린다(메→2.0). */ -export const PIPE_WALL_DEFAULT_HEIGHT_M = 2.5; +/** 배관 기슭막이 기본 높이(m) — 재질과 무관하게 2.0(2026-08-22 사용자 정정). */ +export const PIPE_WALL_DEFAULT_HEIGHT_M = 2.0; /** 일반(추가) 기슭막이 높이 조작 하한(m). */ export const EXTRA_WALL_MIN_HEIGHT_M = 0.5; /** 일반(추가) 기슭막이 기본 높이(m). */ diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts index 24e51616..2a167c55 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts @@ -217,8 +217,52 @@ export function buildOutletExtras(input: OutletExtrasInput): OutletExtrasResult // 관통 금지: 상단 ≤ 윗단 하단 → 성토선 수평거리(경사부) ≥ 1.2×(시작−윗단하단). const autoJointRun = (autoOffset - src.offset) * outward - jointRunOf(height); const minJointRun = FILL_SLOPE_RATIO_MIN * Math.max(src.elevation - prevBottom, 0); - const appliedD = Math.max(adjust.d, Math.ceil((minJointRun - autoJointRun) * 10) / 10); - const appliedX = Math.max(adjust.x, 0); + const minD = Math.ceil((minJointRun - autoJointRun) * 10) / 10; + /** 후보 자리의 바닥(전면 근입 심화 반영) — buildExtraWall과 같은 계산. */ + const bottomAt = (anchorX: number, baseElevation: number): number => { + const baseWidth = REVET_THICKNESS_M * 1.5 + REVET_LEAN_RATIO * height; + const jointX = anchorX - outward * (baseWidth / 2 - REVET_THICKNESS_M / 2); + const topFrontX = jointX + outward * REVET_THICKNESS_M; + const topElevation2 = baseElevation + height; + const frontXAt = (e: number): number => + topFrontX + outward * REVET_LEAN_RATIO * (topElevation2 - e); + let bottom = baseElevation - REVET_EMBED_DEPTH_M; + if (baseElevation - groundAt(anchorX) <= 1e-6) { + for (let pass = 0; pass < 6; pass += 1) { + const toe = Math.min(groundAt(frontXAt(bottom)), baseElevation); + if (toe - REVET_EMBED_DEPTH_M >= bottom - 1e-6) break; + bottom = toe - REVET_EMBED_DEPTH_M; + } + } + return bottom; + }; + // 이동 한계 = 원지반 매몰 자리 금지(2026-08-22 사용자 확정): ① 이음선 상단이 + // 지반 아래(벽이 통째로 묻힘) ② 자기 성토선 시작점(실바닥+0.5 전면 교차점)이 + // 지반 아래 0.05m 이상(= 0.5m 이상 매몰 → 성토 불필요 자리). + const placeable = (xShift: number, dShift: number): boolean => { + const topE = src.elevation - (autoJointRun + dShift) / FILL_SLOPE_RATIO_MIN; + const aX = autoOffset + outward * (xShift + dShift); + const jointX = aX - outward * jointRunOf(height); + if (Math.max(groundAt(jointX), groundAt(jointX + outward * REVET_THICKNESS_M)) >= topE - 0.01) + return false; + const baseE = topE - height; + const bottom = bottomAt(aX, baseE); + const startE = bottom + REVET_EMBED_DEPTH_M; + const topFrontX = jointX + outward * REVET_THICKNESS_M; + const startX = topFrontX + outward * REVET_LEAN_RATIO * (topE - startE); + return groundAt(startX) < startE + 0.05; + }; + let appliedD = Math.max(adjust.d, minD); + let appliedX = Math.max(adjust.x, 0); + for (let pass = 0; pass < 400 && !placeable(appliedX, appliedD); pass += 1) { + // 매몰 자리 — 요청을 자동 자리 쪽으로 0.05m씩 되돌린다(상하 먼저, 다음 좌우). + if (appliedD > minD + 1e-9) appliedD = Math.max(minD, appliedD - 0.05); + else if (appliedX > 1e-9) appliedX = Math.max(0, appliedX - 0.05); + else break; + } + if (!placeable(appliedX, appliedD)) break; // 자동 자리조차 매몰 — 이 단은 불가. + appliedD = Math.round(appliedD * 10) / 10; + appliedX = Math.round(appliedX * 10) / 10; appliedAdjusts.push({ x: appliedX, d: appliedD, @@ -254,10 +298,14 @@ export function buildOutletExtras(input: OutletExtrasInput): OutletExtrasResult ratio: rise > 1e-9 ? run / rise : FILL_SLOPE_RATIO_MIN, overLimit: Math.hypot(run, rise) >= FILL_SLOPE_MAX_LENGTH_M - 1e-6, }); - // 다음 단 시작점 = 이 벽 하단 +0.5m(기준선) 수평선과 전면 경사선(1:0.3)의 교차점. + // 다음 단 시작점 = 이 벽 **실제 바닥**(근입 심화 반영) +0.5m 수평선과 전면 + // 경사선(1:0.3)의 교차점(2026-08-22 사용자 — 관 끝점 규칙과 동일 기준). + const startElevation = wall.bottomBack.elevation + REVET_EMBED_DEPTH_M; src = { - offset: wall.points[2].offset + outward * REVET_LEAN_RATIO * height, - elevation: base, + offset: + wall.points[2].offset + + outward * REVET_LEAN_RATIO * (wall.topJoint.elevation - startElevation), + elevation: startElevation, }; prevBottom = wall.bottomBack.elevation; } diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts index 2155fde6..2582ddcd 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts @@ -461,12 +461,17 @@ export function computeCulvertLayout( const pipeStart = basinPipeEnd ?? inlet; /** 유출 관 하단 끝점 목표 = 벽 바닥 기준 0.5m 상단(기준선)과 **전면 경사선의 * 교차점**(2026-08-22 사용자 ① — 성토부선 시작 규칙과 같은 자리). */ - const outletPipeEnd = (wall: WallLayout): OffsetPoint => ({ - offset: - wall.points[2].offset + - wall.outward * REVET_LEAN_RATIO * (wall.topJoint.elevation - wall.base), - elevation: wall.base, - }); + const outletPipeEnd = (wall: WallLayout): OffsetPoint => { + // 기준은 **실제 바닥**(전면 근입으로 깊어진 값) +0.5 — 기준선(anchor)을 쓰면 + // 가파른 지반에서 바닥만 내려가고 관이 벽 위쪽에 떠 보인다(2026-08-22 사용자 ②). + const reference = wall.bottomBack.elevation + REVET_EMBED_DEPTH_M; + return { + offset: + wall.points[2].offset + + wall.outward * REVET_LEAN_RATIO * (wall.topJoint.elevation - reference), + elevation: reference, + }; + }; // 유출 벽이 사면 끝과 다른 자리면 관 끝도 그 자리 기준으로 맞춘다(올림 연장분은 // 유출 쪽 — 2026-08-20 확정). 관 하단선은 전면 기준선 교차점을 지나 정수 길이로. if (outletWall) {