From 6ba26ee61cecf0a7acfa88b604132d01e078f88a Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 17 Oct 2026 12:12:43 +0900 Subject: [PATCH] =?UTF-8?q?fix(B06=20=EB=B2=BD):=20371-1=20=EC=84=B1?= =?UTF-8?q?=ED=86=A0=20=EC=9C=84=20=EC=9D=B4=EC=9D=8C=20=3D=20=EC=9C=97?= =?UTF-8?q?=EB=A9=B4=20=EC=82=AC=EB=A9=B4=20=EC=AA=BD=20=EB=81=9D=EC=97=90?= =?UTF-8?q?=EC=84=9C=200.1=20m=20=C2=B7=20=EB=8B=A4=EB=8B=A8=20=EC=82=AC?= =?UTF-8?q?=EC=9D=B4=20=EC=8B=9C=EC=9E=91=20=3D=20=EC=9C=97=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=9C=EB=81=9D=20=C2=B7=20=EA=B8=B0=EB=B3=B8=20=EC=9E=90?= =?UTF-8?q?=EB=A6=AC=20=EC=83=88=20=EC=9D=B4=EC=9D=8C=20=EA=B8=B0=EC=A4=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 공용 벽 fillTop 칸 · 성토 사면선 · 트림 · 3D 실루엣 목표 topJoint → fillTop(무늬 이음선 그대로) - 0점 · 다단 자동 자리 · 최소 노견 자리 = 배면에서 1.5t − 0.1(노폭 연장 0 유지 · 관 길이 7.9 → 7.5 m) - 다단 사이 = 늘 윗단 발끝(원지반 무관) · 끝 구간 · 관 유출 벽 = 발끝이 지반 위에 뜬 벽만 발끝 - 시험 186_1 · 139_6 · 225_1 · 139_4 새 수치 · wall_shape_unified 이음 0.1 m 확인 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf --- ...5_Profile_UI_Corridor_Station_Structure.ts | 8 +-- .../B06_Section_UI_Cross_Culvert_Extra.ts | 49 +++++++++++++------ .../B06_Section_UI_Cross_Culvert_Geom.ts | 29 ++++++++--- .../B06_Section_UI_Cross_Culvert_Tiers.ts | 6 +-- .../B06_Section_UI_Cross_Culvert_Types.ts | 4 +- B06_Section/B06_Section_UI_Cross_Revetment.ts | 6 +-- B06_Section/B06_Section_UI_Cross_Wall.ts | 2 + .../B06_Section_UI_Cross_Culvert_Const.ts | 6 +++ .../B06_Section_UI_Cross_Culvert_Solve.ts | 8 +-- .../tester/helper_139_4_buried_outlet.cjs | 4 +- resources/tester/test_139_4_buried_outlet.py | 7 ++- resources/tester/test_139_6_pipe_fit.py | 7 +-- resources/tester/test_186_1_outlet_fold.py | 6 ++- resources/tester/test_225_1_pipe_round.py | 3 +- .../tester/test_b06_wall_shape_unified.py | 8 +++ 15 files changed, 107 insertions(+), 46 deletions(-) diff --git a/B05_Profile/B05_Profile_UI_Corridor_Station_Structure.ts b/B05_Profile/B05_Profile_UI_Corridor_Station_Structure.ts index 8a22ff43b..cac55c2ca 100644 --- a/B05_Profile/B05_Profile_UI_Corridor_Station_Structure.ts +++ b/B05_Profile/B05_Profile_UI_Corridor_Station_Structure.ts @@ -308,8 +308,8 @@ export function culvertSilhouettes(section: CrossSection): StructureSilhouette[] const side: "left" | "right" = wall.outward > 0 ? "left" : "right"; const slope = trimSlopeOf(side); if (slope) pushTopSlope(side, slope.points); - // 구조물 전면 실루엣 — 이음선 상단에서 전면 발끝까지 내려온다. - push(side, [wall.topJoint, wall.bottomFront]); + // 구조물 전면 실루엣 — 성토 위 이음에서 전면 발끝까지 내려온다(371-1). + push(side, [wall.fillTop, wall.bottomFront]); spans[side] = wallSpanOf(section, wall.role === "inlet" ? "inlet" : "outlet"); } const outletSegments = layout.outletFill.segments.filter((segment) => segment.kind !== "cut"); @@ -321,13 +321,13 @@ export function culvertSilhouettes(section: CrossSection): StructureSilhouette[] addRun(outletSide, segment.points); } const wall = layout.extraWalls[step]; - if (wall) push(outletSide, [wall.topJoint, wall.bottomFront]); + if (wall) push(outletSide, [wall.fillTop, wall.bottomFront]); } layout.basinFill.segments.forEach((segment) => { push(inletSide, segment.points); addRun(inletSide, segment.points); }); - layout.basinExtras.forEach((wall) => push(inletSide, [wall.topJoint, wall.bottomFront])); + layout.basinExtras.forEach((wall) => push(inletSide, [wall.fillTop, wall.bottomFront])); let basinCut = false; if (layout.basin) { diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts index 406e38e33..facb70214 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts @@ -6,8 +6,8 @@ * (2026-08-22 사용자 확정 규칙): * · 기슭막이 사이 성토사면은 **정확히 1:1.2** · 자동 자리 = 사면 1.0 m 뒤 벽 상단(원지반 무관 · 91-3). * · 아랫단 벽 상단은 윗단 벽 **하단(수평 기초선)을 뚫고 올라갈 수 없다**. - * · 다음 단 성토선 시작점 = 윗단 벽 **하단 수평선 +0.5m와 전면 경사선(1:n · 표준경사)의 - * 교차점**(배관 기슭막이의 관 하단 꼭짓점과 같은 자리 — 배관만 없다). + * · 다음 단 성토선 시작점 = 윗단 벽 **발끝**(하단 수평선 ∩ 전면 경사선 · 원지반 무관 · 371-1). + * 마지막 벽 → 끝 구간만 하단 수평선 +0.5m ∩ 전면(관 하단 꼭짓점과 같은 자리) · 뜬 벽은 발끝. * ========================================================================== */ import { @@ -19,6 +19,7 @@ import { materialFromForm, revetFormOf, REVET_EMBED_DEPTH_M, + REVET_FILL_TOP_M, REVET_LEAN_RATIO, REVET_THICKNESS_M, WALL_MIN_PURE_HEIGHT_M, @@ -208,11 +209,25 @@ export function inletGroundConnector( return null; } -/** 벽 중심(하단 중점)에서 이음선 상단점까지의 수평거리 — 높이·기울기에 따라 커진다. */ +/** 벽 중심(하단 중점)에서 성토 위 이음까지의 수평거리 — 높이·기울기에 따라 커진다(371-1). */ function jointRunOf(height: number, lean: number): number { - return 0.25 * REVET_THICKNESS_M + (lean / 2) * height; + return 0.75 * REVET_THICKNESS_M - REVET_FILL_TOP_M + (lean / 2) * height; } +/** 벽 아래 성토 시작점(371-1) — 아래 단이 있으면(`tiered`) 늘 발끝(전면 ∩ 하단 수평선 · + * 원지반 무관) · 끝 구간은 발끝이 원지반 위에 뜬 벽만 발끝 · 땅에 선 · 묻힌 벽은 `grounded` + * (기준선 ∩ 전면 · 묻힘 판정 그대로). */ +export const floatFillStart = ( + wall: WallLayout, + grounded: OffsetPoint, + groundAt: (offset: number) => number, + tiered: boolean, +): OffsetPoint => + tiered || + (wall.floatGapM > 1e-6 && groundAt(wall.bottomFront.offset) < wall.bottomFront.elevation - 0.01) + ? wall.bottomFront + : grounded; + /** 끝 성토부선 — src에서 1:1.2로 내려가며 원지반을 만나면 끝(지반이 높으면 지반 따름). */ function trailingFill( src: OffsetPoint, @@ -359,22 +374,28 @@ export function buildOutletExtras(input: OutletExtrasInput): OutletExtrasResult ? { offset: src.offset + outward * shelfRun, elevation: src.elevation } : null; const slopeFrom = shelf ?? src; - const run = Math.abs(wall.topJoint.offset - slopeFrom.offset); - const rise = slopeFrom.elevation - wall.topJoint.elevation; + const run = Math.abs(wall.fillTop.offset - slopeFrom.offset); + const rise = slopeFrom.elevation - wall.fillTop.elevation; segments.push({ - points: shelf ? [src, shelf, wall.topJoint] : [src, wall.topJoint], + points: shelf ? [src, shelf, wall.fillTop] : [src, wall.fillTop], lengthM: Math.hypot(run, rise), 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)의 교차점(2026-08-22 사용자 — 관 끝점 규칙과 동일 기준). + // 다음 단 시작점 = 벽 발끝(전면 경사 ∩ 하단 수평선 · 원지반 무관 · 371-1). 마지막 벽 → + // 끝 구간은 바닥 +0.5m(= 기준선) ∩ 전면(2026-08-22 사용자) · 뜬 벽만 발끝. const startElevation = wall.bottomBack.elevation + REVET_EMBED_DEPTH_M; - src = { - offset: - wall.points[2].offset + outward * wall.lean * (wall.topJoint.elevation - startElevation), - elevation: startElevation, - }; + src = floatFillStart( + wall, + { + offset: + wall.points[2].offset + + outward * wall.lean * (wall.topJoint.elevation - startElevation), + elevation: startElevation, + }, + groundAt, + i < input.adjusts.length - 1, + ); 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 dee524084..cab7ffa11 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts @@ -57,7 +57,7 @@ import { BASIN_INNER_WIDTH_M, inletChoiceAvailability, } from "./logic/B06_Section_UI_Cross_Culvert_Basin"; -import { inletGroundConnector } from "./B06_Section_UI_Cross_Culvert_Extra"; +import { floatFillStart, inletGroundConnector } from "./B06_Section_UI_Cross_Culvert_Extra"; import { withBackCut } from "./logic/B06_Section_UI_Cross_Structure_Cut"; import { buildCulvertTiers, @@ -377,17 +377,17 @@ export function computeCulvertLayout( lean, roadElevation: side.elevation_m, }); - const topJoint = wall.topJoint.offset; - const topElevation = wall.topJoint.elevation; + const fillTop = wall.fillTop.offset; + const topElevation = wall.fillTop.elevation; const topFront = wall.points[2].offset; const bottomFront = wall.bottomFront; walls.push(wall); - // 성토 사면선은 **이음선 상단점**에서 끊고, 끝단 표고도 그 점에 맞춘다(사용자 ②). + // 성토 사면선은 **성토 위 이음**에서 끊고, 끝단 표고도 그 점에 맞춘다(사용자 ② · 371-1). if (outward > 0) { - trimMax = topJoint; + trimMax = fillTop; trimMaxElevation = topElevation; } else { - trimMin = topJoint; + trimMin = fillTop; trimMinElevation = topElevation; } // 관 끝단 마감면 = 기슭막이의 **계류측 변**(전면)을 그대로 복사(사용자 ③). @@ -507,11 +507,24 @@ export function computeCulvertLayout( // 다단 흙막이(유출측 · 집수정 계류측) — `_Culvert_Tiers` 가 푼다. const { extras, basinExtras, extrasForArea, basinExtrasForArea } = buildCulvertTiers({ section, - outletStart: outletWall ? pipeCorners.outlet.bottom : null, + outletStart: outletWall + ? floatFillStart( + outletWall, + pipeCorners.outlet.bottom, + groundAt, + (revetShift?.extras ?? []).length > 0, + ) + : null, outletBottom: outletWall?.bottomBack.elevation ?? 0, basinStart: culvert.hidden_pipe ? inletWall - ? (guardFloorStart(section, body, inletWall) ?? pipeCorners.inlet.bottom) + ? (guardFloorStart(section, body, inletWall) ?? + floatFillStart( + inletWall, + pipeCorners.inlet.bottom, + groundAt, + (revetShift?.basinExtras ?? []).length > 0, + )) : null : basinFillStart, basinBottom: culvert.hidden_pipe ? (inletWall?.bottomBack.elevation ?? 0) : basinFillBottom, diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Tiers.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Tiers.ts index 06c103e63..234def22d 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Tiers.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Tiers.ts @@ -9,7 +9,7 @@ import type { CrossSection } from "./B06_Section_Api_Fetch"; import { GUARD_CUT_BERM_M, REVET_EMBED_DEPTH_M, - REVET_TRAP_TOP_M, + REVET_FILL_TOP_M, } from "./logic/B06_Section_UI_Cross_Culvert_Const"; import type { OutletExtrasResult } from "./B06_Section_UI_Cross_Culvert_Extra"; import { buildExtrasAt } from "./B06_Section_UI_Cross_Culvert_Extra"; @@ -26,7 +26,7 @@ export const slopeZeroAnchor = ( outward: number, wallHeight: number, lean: number, -): number => edge.offset_m + outward * (fillWallBaseWidth(wallHeight, lean) / 2 - REVET_TRAP_TOP_M); +): number => edge.offset_m + outward * (fillWallBaseWidth(wallHeight, lean) / 2 - REVET_FILL_TOP_M); /** 유출 관 하단 끝점 목표 = 벽 바닥 기준 0.5m 상단(기준선)과 **전면 경사선의 * 교차점**(2026-08-22 사용자 ① — 성토부선 시작 규칙과 같은 자리). */ @@ -106,7 +106,7 @@ export function buildCulvertTiers(input: { }; const extras = buildExtrasAt(input.outletStart, outletInput); // 독립 기슭막이(관 숨김)는 집수정이 없어 이 채널이 **유입측 벽의 성토부선·다단**이다 — - // 유출측(`extras`)과 같은 시작점(관 하단 꼭짓점)·같은 `bextra` 키(2026-08-29 사용자). + // 유출측(`extras`)과 같은 시작점(`floatFillStart` · 371-1)·같은 `bextra` 키(2026-08-29 사용자). const basinInput = { cutBerm: cutBerm(culvert?.body ? (culvert.inlet.revet_berm_m ?? GUARD_CUT_BERM_M) : 0), startBottomElevation: input.basinBottom, diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts index 65c5dae08..ac4aec3d0 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts @@ -123,8 +123,10 @@ export interface WallLayout { points: OffsetPoint[]; /** 상단 배면 꼭짓점 — 성토 사면선과의 접점(설계선 트림 경계). */ topBack: OffsetPoint; - /** 이음선 상단점(평행사변형↔사다리꼴 상단 교차점) — 성토 사면선이 여기서 끝난다. */ + /** 이음선 상단점(평행사변형↔사다리꼴 상단 교차점) — 안 무늬 이음선 자리. */ topJoint: OffsetPoint; + /** 성토 위 이음 — 윗면 사면 쪽 끝에서 0.1 m 안(371-1). 성토 사면선이 여기서 끝난다. */ + fillTop: OffsetPoint; /** 하단선 양 끝 — **수평 기초**(2026-08-22 사용자 확정): 발자국 최저 지반(또는 관 * invert) − 0.5m 표고의 수평선. 전면 발끝은 1:0.3 경사선을 하단까지 연장한 자리다. */ bottomBack: OffsetPoint; diff --git a/B06_Section/B06_Section_UI_Cross_Revetment.ts b/B06_Section/B06_Section_UI_Cross_Revetment.ts index d664d6bdd..7164088df 100644 --- a/B06_Section/B06_Section_UI_Cross_Revetment.ts +++ b/B06_Section/B06_Section_UI_Cross_Revetment.ts @@ -22,7 +22,7 @@ import { FILL_SLOPE_RATIO_MIN, PIPE_WALL_DEFAULT_RUN_M, REVET_EMBED_DEPTH_M, - REVET_THICKNESS_M, + REVET_FILL_TOP_M, WALL_MIN_PURE_HEIGHT_M, } from "./logic/B06_Section_UI_Cross_Culvert_Const"; import { outlineLeanOf } from "./logic/B06_Section_UI_Cross_Lean"; @@ -208,7 +208,7 @@ export function computeRevetmentLayout( }); const roadSlope = roadSlopePerOutward(design, side, outward); const autoOffset = - edge.offset_m + outward * (fillWallBaseWidth(height, lean) / 2 - REVET_THICKNESS_M / 2); + edge.offset_m + outward * (fillWallBaseWidth(height, lean) / 2 - REVET_FILL_TOP_M); // x 원점 = 성토사면(1:1.2) 경사길이 4m 지점 — 저장된 x 의 절대 자리 그대로(79b). const autoRun = PIPE_WALL_DEFAULT_RUN_M; /** @@ -285,7 +285,7 @@ export function computeRevetmentLayout( const walls: WallLayout[] = [tier1, ...extras.walls]; // 물빼기 파이프 단면 = 표 형상칸.weep(322-1 · 옹벽) if (catalogRow(spec.type_id)?.shapeOpts.weep) for (const wall of walls) wall.retaining = true; - const jt = tier1.topJoint; + const jt = tier1.fillTop; // 노견 → (물매를 탄 노견 연장) → 벽 이음선 상단점. 연장 구간은 노면 물매 그대로 가고, // 꺾임점에서 성토 물매 1:1.2로 벽 상단에 닿는다(2026-08-29 사용자 확정). // D = 노견에서 벽 상단까지 바깥 방향 거리, R0 = 노견과 벽 상단의 낙차 diff --git a/B06_Section/B06_Section_UI_Cross_Wall.ts b/B06_Section/B06_Section_UI_Cross_Wall.ts index 2acbf3b55..ae56ba825 100644 --- a/B06_Section/B06_Section_UI_Cross_Wall.ts +++ b/B06_Section/B06_Section_UI_Cross_Wall.ts @@ -14,6 +14,7 @@ import { REVET_EMBED_DEPTH_M, REVET_LEAN_RATIO, REVET_THICKNESS_M, + WALL_FILL_TOP_INSET_M, WALL_MIN_PURE_HEIGHT_M, } from "./logic/B06_Section_UI_Cross_Culvert_Const"; import type { RevetMaterial } from "./logic/B06_Section_UI_Cross_Culvert_Const"; @@ -104,6 +105,7 @@ export function buildRevetWallGeometry(input: RevetWallGeometryInput): WallLayou outward, topBack, topJoint: { offset: topJoint, elevation: topElevation }, + fillTop: { offset: topFront - outward * WALL_FILL_TOP_INSET_M, elevation: topElevation }, bottomBack, bottomFront, points: [bottomBack, topBack, { offset: topFront, elevation: topElevation }, bottomFront], diff --git a/B06_Section/logic/B06_Section_UI_Cross_Culvert_Const.ts b/B06_Section/logic/B06_Section_UI_Cross_Culvert_Const.ts index 12191aff8..dcc50e510 100644 --- a/B06_Section/logic/B06_Section_UI_Cross_Culvert_Const.ts +++ b/B06_Section/logic/B06_Section_UI_Cross_Culvert_Const.ts @@ -31,6 +31,12 @@ export const REVET_LEAN_RATIO = 0.3; /** 사다리꼴(배면부) 상단 폭(m) — 띠 두께의 절반(2026-08-20 사용자 확정). */ export const REVET_TRAP_TOP_M = REVET_THICKNESS_M / 2; +/** 성토 위 이음 = 벽 윗면 사면 쪽 끝에서 이만큼 안(m · 371-1). */ +export const WALL_FILL_TOP_INSET_M = 0.1; + +/** 배면에서 성토 위 이음까지 수평거리(m) — 윗면 1.5t − 0.1(371-1). */ +export const REVET_FILL_TOP_M = REVET_THICKNESS_M * 1.5 - WALL_FILL_TOP_INSET_M; + /** * 집수정 상부 여유(m) — 관 상단에서 집수정 벽 상단까지의 간격. 관 벽(유입·유출)은 * 여유 없이 벽 윗면 = 관 윗면(139-1 · `revetTargetHeight`). diff --git a/B06_Section/logic/B06_Section_UI_Cross_Culvert_Solve.ts b/B06_Section/logic/B06_Section_UI_Cross_Culvert_Solve.ts index db9a03ed9..28bb3f116 100644 --- a/B06_Section/logic/B06_Section_UI_Cross_Culvert_Solve.ts +++ b/B06_Section/logic/B06_Section_UI_Cross_Culvert_Solve.ts @@ -11,7 +11,7 @@ import { FILL_SLOPE_RATIO_MIN, REVET_LEAN_RATIO, REVET_THICKNESS_M, - REVET_TRAP_TOP_M, + REVET_FILL_TOP_M, } from "./B06_Section_UI_Cross_Culvert_Const"; import type { EndFace, @@ -144,10 +144,10 @@ export interface FillSlopeSegment { * 접점이 다시 어긋난다. */ export function fillSlopeOf( - wall: { role: "inlet" | "outlet" | "extra"; topJoint: OffsetPoint; outward: number }, + wall: { role: "inlet" | "outlet" | "extra"; fillTop: OffsetPoint; outward: number }, edge: { offset_m: number; elevation_m: number }, ): FillSlopeSegment { - const to = wall.topJoint; + const to = wall.fillTop; const rise = edge.elevation_m - to.elevation; const edgePoint: OffsetPoint = { offset: edge.offset_m, elevation: edge.elevation_m }; if (!(rise > 1e-6)) { @@ -244,7 +244,7 @@ export function minShoulderWallOffset( const offset = edge.offset_m + outward * ((span * i) / steps); const rise = edge.elevation_m - (baseAt(offset) + height); if (rise < FILL_MIN_RISE_M) continue; - const joint = offset - outward * (fillWallBaseWidth(height, lean) / 2 - REVET_TRAP_TOP_M); + const joint = offset - outward * (fillWallBaseWidth(height, lean) / 2 - REVET_FILL_TOP_M); if (((joint - edge.offset_m) * outward) / rise < FILL_SLOPE_RATIO_MIN) continue; if ((offset - toeOffset) * outward > 1e-6) { const toeRise = edge.elevation_m - (baseAt(toeOffset) + height); diff --git a/resources/tester/helper_139_4_buried_outlet.cjs b/resources/tester/helper_139_4_buried_outlet.cjs index 9cc617d61..8b8a20cb2 100644 --- a/resources/tester/helper_139_4_buried_outlet.cjs +++ b/resources/tester/helper_139_4_buried_outlet.cjs @@ -131,7 +131,8 @@ function reshaped(rise, slope) { const s = clone(pipe); for (const p of s.samples) { const t = ((p.offset_m ?? 0) - start.offset) * outward; - if (t >= 0 && typeof p.elevation_m === "number") + // 시작점 바로 앞 표본까지(−1 m) — 보간이 시작점을 옛 지반에 두지 않게(371-1 벽 자리 이동) + if (t >= -1 && typeof p.elevation_m === "number") p.elevation_m = start.elevation + rise + slope * t; } return study( @@ -141,4 +142,5 @@ function reshaped(rise, slope) { } out.pipeDown = reshaped(0.3, -0.15); // 3도보다 가파르게 내림 → 3도 선이 만남 out.pipeUp = reshaped(0.3, 0.1); // 오르막 → 3도 선이 못 만남 → 복합경사 +out.pipeOpen = reshaped(-0.3, -0.5); // 끝 바깥이 지반 아래로 열림 → 성토부선(371-1) process.stdout.write(JSON.stringify(out)); diff --git a/resources/tester/test_139_4_buried_outlet.py b/resources/tester/test_139_4_buried_outlet.py index 6ddb7d15e..259e980a9 100644 --- a/resources/tester/test_139_4_buried_outlet.py +++ b/resources/tester/test_139_4_buried_outlet.py @@ -54,11 +54,14 @@ def test_3도_선이_원지반을_못_만나면_복합경사(out: dict) -> None: assert len(study["cut"]) == 1 tail = study["cut"][0] assert tail["rises"] is True and tail["grade"] == -1 and tail["endGap"] == 0 - assert study["cutArea"] > study["cutAreaWithout"] + # 371-1 — 벽 자리 0.35 m 도로 쪽 · 오르는 선 0.3 m 라 면적 차가 백엔드 설계선 끝에 묻힘 → 폐회로에 듦만 봄 + assert study["inTrim"] is True def test_성토_끝은_그대로(out: dict) -> None: - assert out["pipeBase"] == ["fill", "fill"] # 묻히지 않은 기본 = 성토부선 그대로 + # 371-1 — 다단 사이 시작 = 윗단 발끝 · 단이 0.5 m 내려가 기본 자료 끝 벽이 0.3 m 묻힘 → 3도 선 + assert out["pipeBase"] == ["fill", "cut"] + assert out["pipeOpen"]["cut"] == [] # 묻히지 않은 끝 = 성토부선 그대로 def test_유출_절토선은_그리고_면적에_넣음() -> None: diff --git a/resources/tester/test_139_6_pipe_fit.py b/resources/tester/test_139_6_pipe_fit.py index d2959f13a..320542c0a 100644 --- a/resources/tester/test_139_6_pipe_fit.py +++ b/resources/tester/test_139_6_pipe_fit.py @@ -40,10 +40,11 @@ def out(tmp_path_factory) -> dict: def test_안_넓힌_측점은_종전_그대로(out: dict) -> None: - """225-1 — 정수 맞춤 없앰: 벽은 기본 자리 · 관 길이 = 그려진 길이 0.1 m(5.772 → 5.8).""" + """225-1 — 정수 맞춤 없앰: 벽은 기본 자리 · 관 길이 = 그려진 길이 0.1 m(5.772 → 5.8). + 371-1 — 기본 자리 = 성토 위 이음(끝에서 0.1 m) 기준 · 벽 0.35 m 도로 쪽(5.772 → 5.422).""" base = out["base"] - assert (base["edge"], base["pureHeight"], base["lengthM"]) == (2, 2, 5.8) - assert base["cut"] == pytest.approx(5.772) + assert (base["edge"], base["pureHeight"], base["lengthM"]) == (2, 2, 5.4) + assert base["cut"] == pytest.approx(5.422) @pytest.mark.parametrize("name, extend", [("shifted", 1.0), ("shifted2", 1.3)]) diff --git a/resources/tester/test_186_1_outlet_fold.py b/resources/tester/test_186_1_outlet_fold.py index 4a173b744..cae1cb7bb 100644 --- a/resources/tester/test_186_1_outlet_fold.py +++ b/resources/tester/test_186_1_outlet_fold.py @@ -61,10 +61,12 @@ def test_묻힌_유출_벽_설계선이_안_접힘(out: dict, case: str) -> None def test_저장값_사본_면적(out: dict) -> None: """접힌 폐회로(절토 27.94 · 성토 0.34 ㎡) → 노견~벽~3도 선 절토만.""" - assert out["stored"]["cut"] == pytest.approx(34.0391, abs=1e-3) + # 371-1 — 성토 위 이음 = 윗면 끝에서 0.1 m · 기본 자리 새 이음 기준(34.0391 → 34.2539) + assert out["stored"]["cut"] == pytest.approx(34.2539, abs=1e-3) assert out["stored"]["fill"] == 0 # 225-1 — 유출 벽이 관 길이 맞춤으로 밀리지 않고 기본 자리(16.8211 → 16.0929) - assert out["auto"]["cut"] == pytest.approx(16.0929, abs=1e-3) + # 371-1 — 기본 자리 = 새 이음 기준(16.0929 → 16.2613) + assert out["auto"]["cut"] == pytest.approx(16.2613, abs=1e-3) def test_절토_쪽_배관_유출도_안_접힘(out: dict) -> None: diff --git a/resources/tester/test_225_1_pipe_round.py b/resources/tester/test_225_1_pipe_round.py index e0dabdb2f..004a21a43 100644 --- a/resources/tester/test_225_1_pipe_round.py +++ b/resources/tester/test_225_1_pipe_round.py @@ -72,7 +72,8 @@ def test_좌우_한_번은_d_를_지금_자리로_묶음(out: dict) -> None: def test_관_길이는_벽_자리에서_0_1m_로(out: dict) -> None: """유출 벽을 0.1 m 씩 밀면 관 길이도 0.1 m 씩 — 정수 올림 · 길이 맞춤 이동 없음.""" lengths = [case["lengthM"] for case in out["moved"]] - assert lengths == [7.9, 8.0, 8.1, 8.2] + # 371-1 — 기본 자리 = 성토 위 이음 기준 · 벽 0.35 m 도로 쪽(7.9 → 7.5) + assert lengths == [7.5, 7.6, 7.7, 7.8] for case in out["moved"]: assert case["lengthM"] == round(case["cut"], 1) diff --git a/resources/tester/test_b06_wall_shape_unified.py b/resources/tester/test_b06_wall_shape_unified.py index c64546122..1f56901bd 100644 --- a/resources/tester/test_b06_wall_shape_unified.py +++ b/resources/tester/test_b06_wall_shape_unified.py @@ -36,6 +36,9 @@ writeFileSync(process.argv[2], JSON.stringify({ under: buildRevetWallGeometry({ anchor, outward: 1, height: 2, material: "dry", role: "outlet", roadElevation: 103, }).topJoint.elevation, + fill: (({ fillTop, topJoint, points }) => [fillTop.offset, fillTop.elevation, topJoint.offset, points[2].offset, points[2].elevation])( + buildRevetWallGeometry({ anchor, outward: 1, height: 2.5, material: "dry", role: "outlet" }), + ), dry: revetWallSpec({ revet_form: "돌쌓기(메)" }, { h: 3.0 }, true, 0.3).pureHeight, concrete: revetWallSpec({ revet_form: "콘크리트" }, { h: 7.5 }, true, 0.3).pureHeight, })); @@ -84,6 +87,11 @@ def test_벽_외곽_한_벌_높이_입력_그대로_노면_멈춤(tmp_path: Path assert got["under"] == pytest.approx(102.0), "노면 아래 벽이 잘림" assert got["dry"] == pytest.approx(3.0), "돌쌓기(메) 3.0 이 잘림" assert got["concrete"] == pytest.approx(7.5), "콘크리트 7.5 가 잘림" + # 371-1 — 성토 위 이음 = 윗면 사면 쪽 끝에서 0.1 m 안 · 무늬 이음선(topJoint)은 그대로 + fill_offset, fill_elevation, joint_offset, front_offset, front_elevation = got["fill"] + assert fill_offset == pytest.approx(front_offset - 0.1), "성토 위 이음이 끝에서 0.1 m 아님" + assert fill_elevation == pytest.approx(front_elevation), "성토 위 이음이 윗면 표고 아님" + assert joint_offset == pytest.approx(front_offset - 0.45), "무늬 이음선이 옮겨짐" def test_네_벽이_공용_함수로_서고_형태_한도가_없다() -> None: