diff --git a/B05_Profile/B05_Profile_UI_Corridor_Structures.ts b/B05_Profile/B05_Profile_UI_Corridor_Structures.ts index fbdeb6050..866093597 100644 --- a/B05_Profile/B05_Profile_UI_Corridor_Structures.ts +++ b/B05_Profile/B05_Profile_UI_Corridor_Structures.ts @@ -22,17 +22,17 @@ import { structureHashParts, wallSweepAt } from "./logic/B05_Profile_UI_Corridor_Structures_Hash"; import type { CrossSection, CulvertSideSpec } from "../B06_Section/B06_Section_Api_Fetch"; -import { computeCulvertLayout } from "../B06_Section/B06_Section_UI_Cross_Culvert"; +import { computeCulvertLayout } from "../B06_Section/logic/B06_Section_Cross_Culvert"; import { culvertOwnerFor, ownWallsDrawAt, restrictToSide, tierSpanOf, -} from "../B06_Section/B06_Section_UI_Cross_Culvert_Wire"; +} from "../B06_Section/logic/B06_Section_Cross_Culvert_Wire"; import { pipeWallThicknessM } from "../B06_Section/logic/B06_Section_UI_Cross_Culvert_Const"; -import type { CulvertLayout } from "../B06_Section/B06_Section_UI_Cross_Culvert"; -import { computeFordLayout, fordAdjustOf } from "../B06_Section/B06_Section_UI_Cross_Ford"; -import type { FordLayout } from "../B06_Section/B06_Section_UI_Cross_Ford"; +import type { CulvertLayout } from "../B06_Section/logic/B06_Section_Cross_Culvert"; +import { computeFordLayout, fordAdjustOf } from "../B06_Section/logic/B06_Section_Cross_Ford_Geom"; +import type { FordLayout } from "../B06_Section/logic/B06_Section_Cross_Ford_Geom"; import { boreBandAt, boreRingChainages, @@ -40,8 +40,11 @@ import { splitByBand, } from "./B05_Profile_UI_Corridor_Structures_Box"; import type { SectionPolygon } from "./B05_Profile_UI_Corridor_Structures_Box"; -import { computeBoxLayout, DEFAULT_BOX_SIDE_ADJUST } from "../B06_Section/B06_Section_UI_Cross_Box"; -import type { BoxAdjust, BoxLayout } from "../B06_Section/B06_Section_UI_Cross_Box"; +import { + computeBoxLayout, + DEFAULT_BOX_SIDE_ADJUST, +} from "../B06_Section/logic/B06_Section_Cross_Box_Geom"; +import type { BoxAdjust, BoxLayout } from "../B06_Section/logic/B06_Section_Cross_Box_Geom"; import { buildWingSolids } from "./logic/B05_Profile_UI_Corridor_Structures_Wing"; import { buildWallPath, diff --git a/B05_Profile/B05_Profile_UI_Corridor_Structures_Box.ts b/B05_Profile/B05_Profile_UI_Corridor_Structures_Box.ts index 347f64537..f52e7ddec 100644 --- a/B05_Profile/B05_Profile_UI_Corridor_Structures_Box.ts +++ b/B05_Profile/B05_Profile_UI_Corridor_Structures_Box.ts @@ -12,8 +12,8 @@ * ========================================================================== */ import type { CrossSection } from "../B06_Section/B06_Section_Api_Fetch"; -import { computeBoxLayout } from "../B06_Section/B06_Section_UI_Cross_Box"; -import type { BoxAdjust } from "../B06_Section/B06_Section_UI_Cross_Box"; +import { computeBoxLayout } from "../B06_Section/logic/B06_Section_Cross_Box_Geom"; +import type { BoxAdjust } from "../B06_Section/logic/B06_Section_Cross_Box_Geom"; import type { CorridorStructure, StructureFrame } from "./B05_Profile_UI_Corridor_Structures"; import { buildWingSolids } from "./logic/B05_Profile_UI_Corridor_Structures_Wing"; import { chuteBands } from "./B05_Profile_UI_Corridor_Structures_Chute"; diff --git a/B05_Profile/B05_Profile_UI_Corridor_Structures_Chute.ts b/B05_Profile/B05_Profile_UI_Corridor_Structures_Chute.ts index cedbba41f..5d52fcdfb 100644 --- a/B05_Profile/B05_Profile_UI_Corridor_Structures_Chute.ts +++ b/B05_Profile/B05_Profile_UI_Corridor_Structures_Chute.ts @@ -9,13 +9,13 @@ * ========================================================================== */ import type { ChuteSpec, CrossSection } from "../B06_Section/B06_Section_Api_Fetch"; -import type { OffsetPoint } from "../B06_Section/B06_Section_UI_Cross_Culvert_Types"; +import type { OffsetPoint } from "../B06_Section/logic/B06_Section_Cross_Culvert_Types"; import { chuteBandOutline, chuteRun, groundLine, } from "../B06_Section/B06_Section_UI_Cross_Wall_Hatch"; -import { outletFillLine } from "../B06_Section/B06_Section_UI_Cross_Ford_Pavement"; +import { outletFillLine } from "../B06_Section/logic/B06_Section_Cross_Ford_Pavement"; /** 성토 조각 하나 — B06 오버레이가 띠를 그리는 선(절토 조각은 뺀다). */ interface FillSegment { diff --git a/B05_Profile/B05_Profile_UI_Corridor_Structures_Path.ts b/B05_Profile/B05_Profile_UI_Corridor_Structures_Path.ts index 1ae8dd7a6..7235b747c 100644 --- a/B05_Profile/B05_Profile_UI_Corridor_Structures_Path.ts +++ b/B05_Profile/B05_Profile_UI_Corridor_Structures_Path.ts @@ -11,16 +11,16 @@ * ========================================================================== */ import type { CrossSection } from "../B06_Section/B06_Section_Api_Fetch"; -import type { CulvertLayout } from "../B06_Section/B06_Section_UI_Cross_Culvert"; -import { computeCulvertLayout } from "../B06_Section/B06_Section_UI_Cross_Culvert"; +import type { CulvertLayout } from "../B06_Section/logic/B06_Section_Cross_Culvert"; +import { computeCulvertLayout } from "../B06_Section/logic/B06_Section_Cross_Culvert"; import { bodyAbsolute } from "../B06_Section/B06_Section_UI_Cross_Body_Guard"; -import type { WallAdjust } from "../B06_Section/B06_Section_UI_Cross_Culvert_Types"; -import { ZERO_ADJUST } from "../B06_Section/B06_Section_UI_Cross_Culvert_Types"; +import type { WallAdjust } from "../B06_Section/logic/B06_Section_Cross_Culvert_Types"; +import { ZERO_ADJUST } from "../B06_Section/logic/B06_Section_Cross_Culvert_Types"; import { culvertOwnerFor, culvertReach, restrictToSide, -} from "../B06_Section/B06_Section_UI_Cross_Culvert_Wire"; +} from "../B06_Section/logic/B06_Section_Cross_Culvert_Wire"; /** 단면 폴리곤 [offset, 표고][] — 코리도 솔리드가 쓰는 그 형식. */ export type SectionPolygon = Array<[number, number]>; diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_Cross_Card.ts b/B05_Profile/B05_Profile_UI_RouteEdit_Cross_Card.ts index f99e4050e..cb1ed51c5 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit_Cross_Card.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit_Cross_Card.ts @@ -24,10 +24,13 @@ import { inletStructure, revetOffset, } from "../B06_Section/B06_Section_Structure_Layouts"; -import { DEFAULT_BOX_SIDE_ADJUST } from "../B06_Section/B06_Section_UI_Cross_Box_Geom"; +import { DEFAULT_BOX_SIDE_ADJUST } from "../B06_Section/logic/B06_Section_Cross_Box_Geom"; import type { BoxControl } from "../B06_Section/B06_Section_UI_Cross_Box_Panel"; -import { culvertLinkFor, type CulvertLink } from "../B06_Section/B06_Section_UI_Cross_Culvert_Wire"; -import { fordAdjustOf } from "../B06_Section/B06_Section_UI_Cross_Ford_Geom"; +import { + culvertLinkFor, + type CulvertLink, +} from "../B06_Section/logic/B06_Section_Cross_Culvert_Wire"; +import { fordAdjustOf } from "../B06_Section/logic/B06_Section_Cross_Ford_Geom"; import type { FordControl } from "../B06_Section/B06_Section_UI_Cross_Ford_Panel"; import { createCrossSectionCard } from "../B06_Section/B06_Section_UI_Cross_View"; import type { RockBoundaryControl } from "../B06_Section/B06_Section_UI_Cross_Design"; diff --git a/B06_Section/B06_Section_Api_Types.ts b/B06_Section/B06_Section_Api_Types.ts index dde05d7ba..8c25ad53b 100644 --- a/B06_Section/B06_Section_Api_Types.ts +++ b/B06_Section/B06_Section_Api_Types.ts @@ -58,7 +58,7 @@ export type { } from "@util/common_util_mass_haul_types"; /** 물넘이포장 제원 — 정의처는 렌더 모듈이다(사본 금지, 타입 전용 import라 순환 없음). */ -import type { FordPavementSpec } from "./B06_Section_UI_Cross_Ford_Pavement"; +import type { FordPavementSpec } from "./logic/B06_Section_Cross_Ford_Pavement"; import type { RevetmentSpec } from "./B06_Section_UI_Cross_Revetment"; import type { PavementEntry } from "@util/common_util_structure_pavement"; import type { UnderdrainSpec } from "@util/common_util_structure_underdrain"; diff --git a/B06_Section/B06_Section_Route_Carry_Patch.ts b/B06_Section/B06_Section_Route_Carry_Patch.ts index e365281ef..a527d7d3b 100644 --- a/B06_Section/B06_Section_Route_Carry_Patch.ts +++ b/B06_Section/B06_Section_Route_Carry_Patch.ts @@ -16,14 +16,14 @@ import type { CrossSectionPatch, SectionDetailResponse, } from "./B06_Section_Api_Fetch"; -import { DEFAULT_BASIN_ADJUST, ZERO_ADJUST } from "./B06_Section_UI_Cross_Culvert_Types"; -import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types"; -import { fordAdjustOf } from "./B06_Section_UI_Cross_Ford_Geom"; -import { DEFAULT_BOX_SIDE_ADJUST } from "./B06_Section_UI_Cross_Box_Geom"; +import { DEFAULT_BASIN_ADJUST, ZERO_ADJUST } from "./logic/B06_Section_Cross_Culvert_Types"; +import type { WallAdjust } from "./logic/B06_Section_Cross_Culvert_Types"; +import { fordAdjustOf } from "./logic/B06_Section_Cross_Ford_Geom"; +import { DEFAULT_BOX_SIDE_ADJUST } from "./logic/B06_Section_Cross_Box_Geom"; import { PIPE_MATCH_M } from "../common_util/common_util_pipe_match"; import { inferStationInterval } from "../common_util/common_util_svg"; import { applySpanPatch, WALL_MIN_PURE_HEIGHT_M } from "./logic/B06_Section_UI_Cross_Culvert_Const"; -import { tierSpanOf } from "./B06_Section_UI_Cross_Culvert_Wire"; +import { tierSpanOf } from "./logic/B06_Section_Cross_Culvert_Wire"; import { computeStoredLayouts } from "./B06_Section_Structure_Layouts"; import { ROW_CARRY_KEYS } from "./logic/B06_Section_Station_Swap"; import { mergeCrossPatches } from "./logic/B06_Section_UI_Page_Patches"; diff --git a/B06_Section/B06_Section_Structure_Layouts.ts b/B06_Section/B06_Section_Structure_Layouts.ts index abe2571dc..7bb97e3c1 100644 --- a/B06_Section/B06_Section_Structure_Layouts.ts +++ b/B06_Section/B06_Section_Structure_Layouts.ts @@ -13,25 +13,25 @@ import { computeStructureAreas, wallBodyInFillM2 } from "@util/common_util_cross_structure_areas"; import type { CrossSection } from "./B06_Section_Api_Fetch"; import { withoutGuardedFill } from "./B06_Section_UI_Cross_Body_Guard"; -import { computeBoxLayout, DEFAULT_BOX_SIDE_ADJUST } from "./B06_Section_UI_Cross_Box_Geom"; -import type { BoxLayout } from "./B06_Section_UI_Cross_Box_Geom"; +import { computeBoxLayout, DEFAULT_BOX_SIDE_ADJUST } from "./logic/B06_Section_Cross_Box_Geom"; +import type { BoxLayout } from "./logic/B06_Section_Cross_Box_Geom"; import { REVET_EMBED_DEPTH_M } from "./logic/B06_Section_UI_Cross_Culvert_Const"; import { groundInterpolator } from "./logic/B06_Section_UI_Cross_Culvert_Solve"; -import { DEFAULT_BASIN_ADJUST, ZERO_ADJUST } from "./B06_Section_UI_Cross_Culvert_Types"; -import type { BasinShape, WallAdjust, WallLayout } from "./B06_Section_UI_Cross_Culvert_Types"; +import { DEFAULT_BASIN_ADJUST, ZERO_ADJUST } from "./logic/B06_Section_Cross_Culvert_Types"; +import type { BasinShape, WallAdjust, WallLayout } from "./logic/B06_Section_Cross_Culvert_Types"; import { computeCardCulvert, culvertLinkFor, ownWallsDrawAt, tierSpanOf, -} from "./B06_Section_UI_Cross_Culvert_Wire"; +} from "./logic/B06_Section_Cross_Culvert_Wire"; import type { ExtraWallControl, InletStructureControl, RevetOffsetControl, -} from "./B06_Section_UI_Cross_Culvert_Wire"; -import { computeFordLayout, fordAdjustOf } from "./B06_Section_UI_Cross_Ford_Geom"; -import type { FordLayout } from "./B06_Section_UI_Cross_Ford_Geom"; +} from "./logic/B06_Section_Cross_Culvert_Wire"; +import { computeFordLayout, fordAdjustOf } from "./logic/B06_Section_Cross_Ford_Geom"; +import type { FordLayout } from "./logic/B06_Section_Cross_Ford_Geom"; import { computeRevetmentLayout } from "./B06_Section_UI_Cross_Revetment"; /** 같은 측점으로 볼 누가거리 오차(m) — 정본 반올림 자릿수보다 크게 잡는다. */ diff --git a/B06_Section/B06_Section_UI_Cross_Body_Guard.ts b/B06_Section/B06_Section_UI_Cross_Body_Guard.ts index 83e6b671b..c5cbe4ceb 100644 --- a/B06_Section/B06_Section_UI_Cross_Body_Guard.ts +++ b/B06_Section/B06_Section_UI_Cross_Body_Guard.ts @@ -11,8 +11,8 @@ * ========================================================================== */ import type { CrossSection, SectionSample } from "./B06_Section_Api_Fetch"; -import { computeBoxLayout, DEFAULT_BOX_SIDE_ADJUST } from "./B06_Section_UI_Cross_Box_Geom"; -import type { BoxLayout } from "./B06_Section_UI_Cross_Box_Geom"; +import { computeBoxLayout, DEFAULT_BOX_SIDE_ADJUST } from "./logic/B06_Section_Cross_Box_Geom"; +import type { BoxLayout } from "./logic/B06_Section_Cross_Box_Geom"; import { FILL_SLOPE_RATIO_MIN } from "./logic/B06_Section_UI_Cross_Culvert_Const"; import type { CulvertDesignTrim, @@ -20,9 +20,9 @@ import type { OffsetPoint, WallAdjust, WallLayout, -} from "./B06_Section_UI_Cross_Culvert_Types"; -import { computeFordLayout, fordAdjustOf } from "./B06_Section_UI_Cross_Ford_Geom"; -import type { FordLayout } from "./B06_Section_UI_Cross_Ford_Geom"; +} from "./logic/B06_Section_Cross_Culvert_Types"; +import { computeFordLayout, fordAdjustOf } from "./logic/B06_Section_Cross_Ford_Geom"; +import type { FordLayout } from "./logic/B06_Section_Cross_Ford_Geom"; import { buildRevetWallGeometry } from "./B06_Section_UI_Cross_Wall"; type Role = "inlet" | "outlet"; diff --git a/B06_Section/B06_Section_UI_Cross_Box.ts b/B06_Section/B06_Section_UI_Cross_Box.ts index 72132057b..109a85975 100644 --- a/B06_Section/B06_Section_UI_Cross_Box.ts +++ b/B06_Section/B06_Section_UI_Cross_Box.ts @@ -2,7 +2,7 @@ * B06_Section_UI_Cross_Box.ts * BOX암거 측점 횡단 카드의 구체(에이프런·저판·상판·내공·성토선) **오버레이 그리기**. * - * 기하 계산은 `B06_Section_UI_Cross_Box_Geom.ts`가 맡는다(700줄 제한 분리). + * 기하 계산은 `logic/B06_Section_Cross_Box_Geom.ts`가 맡는다(700줄 제한 분리). * 여기서는 계산 결과(`BoxLayout`)를 SVG 도형으로 옮기기만 한다 — 치수 결정 금지. * * 그리는 순서 = 시공 순서: ① 에이프런 → ② 저판 → ③ 상판 → ④ 내공 윤곽 → ⑤ 성토선. @@ -10,22 +10,22 @@ * ========================================================================== */ import { pipeTagId, tagShape } from "../common_util/common_util_pick_tag"; -import type { BoxLayout, BoxSideRole } from "./B06_Section_UI_Cross_Box_Geom"; -import type { OffsetPoint } from "./B06_Section_UI_Cross_Culvert_Types"; +import type { BoxLayout, BoxSideRole } from "./logic/B06_Section_Cross_Box_Geom"; +import type { OffsetPoint } from "./logic/B06_Section_Cross_Culvert_Types"; import { appendChuteBand } from "./B06_Section_UI_Cross_Wall_Hatch"; export { computeBoxLayout, DEFAULT_BOX_ADJUST, DEFAULT_BOX_SIDE_ADJUST, -} from "./B06_Section_UI_Cross_Box_Geom"; +} from "./logic/B06_Section_Cross_Box_Geom"; export type { BoxAdjust, BoxLayout, BoxSideAdjust, BoxSideLayout, BoxSideRole, -} from "./B06_Section_UI_Cross_Box_Geom"; +} from "./logic/B06_Section_Cross_Box_Geom"; const SVG_NS = "http://www.w3.org/2000/svg"; diff --git a/B06_Section/B06_Section_UI_Cross_Box_Panel.ts b/B06_Section/B06_Section_UI_Cross_Box_Panel.ts index ac1f08861..cbaf947c3 100644 --- a/B06_Section/B06_Section_UI_Cross_Box_Panel.ts +++ b/B06_Section/B06_Section_UI_Cross_Box_Panel.ts @@ -8,7 +8,7 @@ * CSS 클래스와 조작 관례는 세월교·배수관 조정창과 같다. * ========================================================================== */ -import type { BoxSideAdjust, BoxSideRole } from "./B06_Section_UI_Cross_Box_Geom"; +import type { BoxSideAdjust, BoxSideRole } from "./logic/B06_Section_Cross_Box_Geom"; import type { BoxPick } from "./B06_Section_UI_Cross_Box"; import { bindArrowKeys, diff --git a/B06_Section/B06_Section_UI_Cross_Culvert.ts b/B06_Section/B06_Section_UI_Cross_Culvert.ts index 5a82725cb..dd6cf157c 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert.ts @@ -2,7 +2,7 @@ * B06_Section_UI_Cross_Culvert.ts * 배수관 측점 횡단 카드의 배수관 세트(배관·기슭막이·보호공) **오버레이 그리기**. * - * 기하 계산은 `B06_Section_UI_Cross_Culvert_Geom.ts`가 맡는다(700줄 제한 분리). + * 기하 계산은 `logic/B06_Section_Cross_Culvert_Geom.ts`가 맡는다(700줄 제한 분리). * 여기서는 계산 결과(`CulvertLayout`)를 SVG 도형으로 옮기기만 한다 — 치수 결정 금지. * * 그리는 순서 = 시공·계산 순서(2026-08-20 사용자 확정): @@ -15,14 +15,12 @@ import { REVET_EMBED_DEPTH_M, FILL_SLOPE_RATIO_MIN, pipeWallThicknessM, -} from "./B06_Section_UI_Cross_Culvert_Geom"; -import type { - BasinShape, - CulvertLayout, - PipeEnd, - WallLayout, -} from "./B06_Section_UI_Cross_Culvert_Geom"; -import { appendPipeInfoText, outlineBox } from "./B06_Section_UI_Cross_Culvert_Types"; +} from "./logic/B06_Section_Cross_Culvert_Geom"; +import type { BasinShape, CulvertLayout, WallLayout } from "./logic/B06_Section_Cross_Culvert_Geom"; +import { appendPipeInfoText } from "./B06_Section_UI_Cross_Culvert_Types"; +import { outlineBox } from "./logic/B06_Section_Cross_Culvert_Types"; +import { pipeShellCorners } from "./logic/B06_Section_Cross_Culvert"; +import type { RevetKey, RevetPick } from "./logic/B06_Section_Cross_Culvert"; import { pipeLengthText } from "./logic/B06_Section_UI_Cross_Culvert_Const"; import { appendWallTrench, foundationChoice } from "./B06_Section_UI_Cross_Excavation"; import { @@ -33,7 +31,7 @@ import { // 기하 계산 진입점과 공개 상수·타입은 여기서 재수출한다 — B05(최소 토피)와 횡단 뷰가 // 이 모듈 경로로 이미 참조하고 있어 분리 후에도 import 경로를 바꾸지 않는다. -export { MIN_PIPE_COVER_M, computeCulvertLayout } from "./B06_Section_UI_Cross_Culvert_Geom"; +export { MIN_PIPE_COVER_M, computeCulvertLayout } from "./logic/B06_Section_Cross_Culvert"; export type { BasinLayout, BasinShape, @@ -41,7 +39,9 @@ export type { CulvertLayout, EndFace, InletStructureChoice, -} from "./B06_Section_UI_Cross_Culvert_Geom"; + RevetKey, + RevetPick, +} from "./logic/B06_Section_Cross_Culvert"; const SVG_NS = "http://www.w3.org/2000/svg"; @@ -59,13 +59,6 @@ function polygon( return shape; } -/** 기슭막이 선택 키 — 측점 안에서 어느 벽인지 가린다. 추가 벽은 `extra{n}`. */ -// "own" = 배관과 무관한 **독립 기슭막이**(구조물 정본 D군). 조정창·조작값 저장은 -// 배관 벽과 같은 체계를 그대로 쓴다(2026-08-28 사용자: 배관측점처럼 이동). -export type RevetKey = "inlet" | "outlet" | "own" | `extra${number}` | `bextra${number}`; -/** 고름 전용 키 — 벽 키 + 관(118-1) · 도수로 띠(152-4) — 조정창 없음 · 강조만. 조정 함수에는 `RevetKey` 만. */ -export type RevetPick = RevetKey | "pipe" | "chute"; - /** 벽 강조를 카드 재생성 없이 갈아 끼우는 setter. */ export type RevetHighlightSetter = (key: RevetPick | null) => void; @@ -88,7 +81,7 @@ export function appendCulvertOverlay( /** 옆 측점에서 연동으로 넘어온 벽 — 주 형상과 색을 갈라 그림(79-6). */ linked?: boolean, ): RevetHighlightSetter { - const { culvert, pipe, pipeCorners } = layout; + const { culvert, pipe } = layout; // 신원표(323-5) — 관 세션 id · 부분 = 조정창 부재키 const ownerId = culvert.chainage_m != null ? pipeTagId(culvert.chainage_m) : null; const diameter = culvert.diameter_m; @@ -292,23 +285,6 @@ export function appendCulvertOverlay( } // ③ 관 — 축 법선 두께의 직사각형. 끝단면은 구조물 계류측 변에서 잘려 온다(Geom). - const { axis, normal } = layout.pipeAxis; - // 미세한 틈 방지: 양 끝을 축 방향으로 아주 조금만 밀어 넣는다. 벽 전면이 1:0.3으로 - // 기울어 있어 크게 밀면 관 하단이 벽 **바깥으로 삐져나온다**(2026-08-20 사용자 ③ - // 위치 어긋남 원인) — 렌더링 틈만 덮을 만큼(2㎝)으로 줄였다. - const OVERLAP_M = 0.02; - const pushOut = (corner: PipeEnd, dirSign: number): PipeEnd => ({ - bottom: { - offset: corner.bottom.offset + dirSign * axis.offset * OVERLAP_M, - elevation: corner.bottom.elevation + dirSign * axis.elevation * OVERLAP_M, - }, - top: { - offset: corner.top.offset + dirSign * axis.offset * OVERLAP_M, - elevation: corner.top.elevation + dirSign * axis.elevation * OVERLAP_M, - }, - }); - const inletFinal = pushOut(pipeCorners.inlet, -1); - const outletFinal = pushOut(pipeCorners.outlet, 1); const kindLabel = culvert.pipe_kind ? `${culvert.pipe_kind} ` : ""; const wallThickness = pipeWallThicknessM(culvert.pipe_kind, diameter); @@ -321,16 +297,6 @@ export function appendCulvertOverlay( ` · 관 두께 ${(wallThickness * 1000).toFixed(1)}㎜(외경선 기준)` + ` · 최소 토피 ${culvert.min_cover_m.toFixed(1)}m(별표2 복토 교차 참조)`; // 외경(관 두께 바깥) → 내경 순으로 2겹. 도면처럼 관벽 두께가 보이게 한다(사용자 ③). - const shifted = (corner: PipeEnd, delta: number): PipeEnd => ({ - bottom: { - offset: corner.bottom.offset - normal.offset * delta, - elevation: corner.bottom.elevation - normal.elevation * delta, - }, - top: { - offset: corner.top.offset + normal.offset * delta, - elevation: corner.top.elevation + normal.elevation * delta, - }, - }); const pipeShapes: SVGPolygonElement[] = []; // 관 두 겹 — 관 고름 강조(118-1) let pipeHitPoints: Array<[number, number]> = []; const pipeLayers = hidePipe @@ -340,8 +306,7 @@ export function appendCulvertOverlay( [0, "b06-chart__culvert-pipe"], ] as const); for (const [delta, cls] of pipeLayers) { - const a = shifted(inletFinal, delta); - const b = shifted(outletFinal, delta); + const [a, b] = pipeShellCorners(layout, delta); const points: Array<[number, number]> = [ [x(a.bottom.offset), toDisplayY(a.bottom.elevation)], [x(b.bottom.offset), toDisplayY(b.bottom.elevation)], diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts b/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts index ac4aec3d0..c0f224860 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert_Types.ts @@ -1,55 +1,8 @@ /* ============================================================================= * B06_Section_UI_Cross_Culvert_Types.ts - * 배수관 세트 기하 **자료형** — 기하 계산(`_Cross_Culvert_Geom.ts`)에서 분리했다 - * (700줄 제한). 계산·그리기 양쪽이 함께 쓴다. + * 배수관 · 세월교 관 정보 글 그리기 — 자료형은 `logic/B06_Section_Cross_Culvert_Types.ts`. * ========================================================================== */ -import type { CulvertSet } from "./B06_Section_Api_Fetch"; -import type { RevetMaterial } from "./logic/B06_Section_UI_Cross_Culvert_Const"; - -/** - * 기슭막이 1매의 사용자 조작값(2026-08-22 확정 4축): - * x = 좌우(m, + = 계류측 바깥) — 노견(또는 윗단 시작점 수평선)을 늘려 평행이동, - * 성토선 물매 1:1.2 불변. - * d = 상하(m, 수평 성분, + = 사면 아래) — 성토선을 타고 대각 이동. - * **배관(유입·유출) 벽**은 0점 = 성토선 길이 0 지점(벽 상단 = 노견)인 - * 절대값(2026-08-23 사용자 확정), null = 기본 자리(성토선 0 지점 · 79-5). - * 추가 벽은 종전대로 자동 자리 기준 상대값(null = 0). - * h = 높이(m). 배관 벽은 **순수 높이**(바닥~상단, 관 아래 0.5 포함 — - * 2026-08-23), 추가 벽은 계산용(근입 0.5 위 기준선~상단). null = 기본값 - * (배관 관경+1.0·일반 1.5, 재질 한계로 잘림). - * m = 재질. null = 기본값(메쌓기). 한계: 메 2.0 / 찰 3.0 / 콘크리트 5.0. - */ -export interface WallAdjust { - x: number; - d: number | null; - h: number | null; - /** 형태(`REVET_FORMS` 문자열). 옛 저장분은 재질 코드(dry/wet/concrete) — `revetFormOf`가 옮긴다. */ - m: string | null; -} - -/** 조작값 기본(전부 자동). */ -export const ZERO_ADJUST: WallAdjust = { x: 0, d: null, h: null, m: null }; - -export interface OffsetPoint { - offset: number; - elevation: number; -} - -/** 점들을 감싸는 가상 사각형 — 고르기 판정면만(그림 아님) · 세월교 측벽 ㄴ(79-6) · 집수정(190-1). */ -export function outlineBox(points: OffsetPoint[]): OffsetPoint[] { - const offsets = points.map((point) => point.offset); - const elevations = points.map((point) => point.elevation); - const [left, right] = [Math.max(...offsets), Math.min(...offsets)]; - const [top, bottom] = [Math.max(...elevations), Math.min(...elevations)]; - return [ - { offset: left, elevation: top }, - { offset: right, elevation: top }, - { offset: right, elevation: bottom }, - { offset: left, elevation: bottom }, - ]; -} - /** * 관 정보 글(세월교 · 관 자리) — 노폭 · 노견 라벨(`.b06-chart__carriageway-label`) 바로 위 줄(225-1). * 같은 그림에 그 라벨이 없으면(B07 덧그림) `fallback` 자리 · fallback 도 없으면 안 그림. @@ -74,198 +27,3 @@ export function appendPipeInfoText( label.textContent = text; (road?.parentNode ?? layer).appendChild(label); } - -/** 설계선(성토 경사) 트림 범위 — 이 밖은 벽·보호공이 대신하므로 그리지 않는다. */ -export interface CulvertDesignTrim { - minOffset: number; - maxOffset: number; - /** 트림 경계에서 성토 경사선이 **닿아야 할 표고**(= 벽 이음선 상단점). 없으면 설계선 그대로. */ - minElevation?: number; - maxElevation?: number; - /** - * 노견 → (노폭 연장) → 벽 이음선 상단점 폴리라인. 물매가 1:1.2 고정이라 벽이 밖으로 - * 나간 만큼 **노폭이 늘어난다** — 그 연장 구간이 가운데 점으로 들어온다. - */ - minSlope?: { points: OffsetPoint[] }; - maxSlope?: { points: OffsetPoint[] }; -} - -export interface WallLayout { - role: "inlet" | "outlet" | "extra"; - /** 추가 기슭막이 번호(role === "extra"일 때만) — 조정창 키 `extra{n}`이 된다. */ - extraIndex?: number; - form: string | null; - lengthM: number | null; - /** 배면(도로측) 바닥 offset. */ - backOffset: number; - /** 전면(계류측) 바닥 offset. */ - outerOffset: number; - base: number; - /** 벽 높이(m) = **계산용**(관 invert~상단, 근입 제외 — 2026-08-22 사용자 확정). - * 기초는 시공 시 묻히는 부분이라 높이 표기·한계 검사(찰 3.0/메 2.0) 모두 이 기준. */ - height: number; - /** - * 바닥이 원지반에서 뜬 간격(m, 2026-08-22 사용자 확정). 손으로 안쪽에 당겨 높이 - * 한계로도 사면선에 못 닿을 때 바닥을 띄워 최소 노견을 확보한다 — 뜬 공간은 - * 별도 지지 구조물로 메운다(추가 예정). 0이면 지반 위(근입 0.5m). - */ - floatGapM: number; - /** 좌우 이동의 **하한**(m, 2026-09-07). 선반 길이가 음수가 되지 않게 x 는 최소 - * `1.2 × 상하 내림`까지 따라 나간다. 요청이 이 값보다 작으면 눌러도 벽이 안 움직임 — - * 그 까닭을 툴팁으로 알린다. */ - shiftFloorM?: number; - /** 재질(메/찰/콘크리트) — 높이 한계를 정한다(2026-08-22 사용자). */ - material: RevetMaterial; - /** 전면 기울기 1:n 의 n — 품셈 13-4-4 [주]⑪ 표준경사(`wallLeanRatio`, B08 과 한 벌 · 2026-09-14). */ - lean: number; - outward: number; - /** 합성 단면(하부 사다리꼴 + 상부 평행사변형) 꼭짓점 — 데이터 좌표. */ - points: OffsetPoint[]; - /** 상단 배면 꼭짓점 — 성토 사면선과의 접점(설계선 트림 경계). */ - topBack: OffsetPoint; - /** 이음선 상단점(평행사변형↔사다리꼴 상단 교차점) — 안 무늬 이음선 자리. */ - topJoint: OffsetPoint; - /** 성토 위 이음 — 윗면 사면 쪽 끝에서 0.1 m 안(371-1). 성토 사면선이 여기서 끝난다. */ - fillTop: OffsetPoint; - /** 하단선 양 끝 — **수평 기초**(2026-08-22 사용자 확정): 발자국 최저 지반(또는 관 - * invert) − 0.5m 표고의 수평선. 전면 발끝은 1:0.3 경사선을 하단까지 연장한 자리다. */ - bottomBack: OffsetPoint; - bottomFront: OffsetPoint; - /** 옹벽(구조물 `retaining_wall`) — 안 무늬에 물빼기 파이프 단면을 더함(73-2). */ - retaining?: boolean; -} - -/** - * 유입 집수정 단면 — 실무 도면(2026-08-20 사용자 제공 L형 확대본) 기준. - * · **I형** = 기슭막이와 같은 **평행사변형 벽 1매**만. - * · **ㄴ(L)형** = I형 + **바닥판**. - * · **ㄷ형** = ㄴ형 + I형 **반대측 막음벽**. - * 돌 표현은 재질 확정 시 반영 예정(성토부 기슭막이와 동일 규칙) — 지금은 윤곽만. - */ -export type BasinShape = "I" | "L" | "U"; - -export interface BasinLayout { - shape: BasinShape; - /** 부재별 폴리곤(벽·바닥·막음벽). 각각 분리 도형으로 그린다. */ - parts: Array<{ kind: "wall" | "floor"; points: OffsetPoint[] }>; - label: OffsetPoint; - /** 자동 전환 사유 — 툴팁·라벨에 쓴다(2026-08-22 재정의: 성토사면 ≤3m 기준). - * "cut" = 절토측 유입(성토사면 없음), "short" = 성토사면 3m 이하, - * "manual" = 규칙상 기슭막이 자리지만 사용자가 집수정을 선택. */ - reason: "cut" | "short" | "manual"; - /** 집수정이 원지반 안으로 들어갈 때의 절토선 점열(구조물 바깥 끝 → 복합경사 → 지반 교차점 · 91-4). */ - cutLine: OffsetPoint[] | null; - /** 구조물이 원지반 밖으로 나올 때의 성토(되메움)선 — 경사 5도(임시), 지반 교차점까지. */ - fillLine: { from: OffsetPoint; to: OffsetPoint } | null; -} - -/** 측점별 집수정 조정값. 위치값은 기본 배치 기준이며 +는 계류측/사면 아래 방향이다. */ -export interface BasinAdjust { - innerWidthM: number; - innerHeightM: number; - lateralM: number; - slopeM: number; -} - -export const DEFAULT_BASIN_ADJUST: BasinAdjust = { - innerWidthM: 1.0, - innerHeightM: 1.2, - lateralM: 0, - slopeM: 0, -}; - -/** 관 끝단을 자를 마감면 — 구조물(기슭막이·집수정)의 계류측 변을 **그대로 복사**한다. */ -export interface EndFace { - base: OffsetPoint; - direction: { offset: number; elevation: number }; -} - -/** 관 한쪽 끝단의 두 꼭짓점(마감면에서 자른 결과). */ -export interface PipeEnd { - bottom: OffsetPoint; - top: OffsetPoint; -} - -/** - * 유출측 **성토부선** 한 구간(2026-08-22 사용자 — 보호공 삭제, 윗면 선만 성토부선으로). - * 관 하단 꼭짓점(또는 앞 추가 벽 전면 상단)에서 1:1.2로 원지반(또는 다음 벽)까지. - */ -export interface OutletFillSegment { - points: OffsetPoint[]; - lengthM: number; - /** 구간 물매(1:n) — 벽 사이 구간은 높이 역산으로 정확히 1.2다. */ - ratio?: number; - /** 선 성격(2026-08-22 사용자 ②③): fill = 성토선(기본), cut = 0도/접속 절토선. */ - kind?: "fill" | "cut"; - /** 사면길이 5m 이상 — 기슭막이 의무 구간(별표2·성토_비탈면 §2). */ - overLimit: boolean; -} - -export interface CulvertLayout { - culvert: CulvertSet; - /** 원지반 — 도수로 · 돌붙임 길이가 사면보다 길면 이어 그림(294-4). */ - ground?: OffsetPoint[]; - pipe: { inlet: OffsetPoint; outlet: OffsetPoint; lengthM: number; slopePct: number }; - /** 관 축·법선 단위벡터(그리기가 두께 방향으로 밀 때 쓴다). */ - pipeAxis: { axis: OffsetPoint; normal: OffsetPoint }; - /** 관 양 끝단 꼭짓점 — 보호공 시작점이 유출 하단 꼭짓점을 그대로 쓴다. */ - pipeCorners: { inlet: PipeEnd; outlet: PipeEnd }; - walls: WallLayout[]; - /** 유출측 성토사면 — 경사길이(m)·법정 5m 충족 여부(별표2)·풀어낸 물매(1:n). */ - fillSlope: { - lengthM: number; - withinLimit: boolean; - /** 사면선이 벽 이음선 상단점을 지나도록 역산한 물매. 범위 밖이면 끝값으로 고정. */ - ratio: number; - /** 물매가 1.2~2.0 범위를 벗어났는지 — 도면 경고용. */ - ratioClamped: boolean; - /** 물매 1:1.2를 지키려고 늘린 **노폭 연장량**(m, 편측). 0이면 연장 없음. */ - roadWideningM: number; - /** - * 기슭막이만으로는 성토를 받을 수 없는 자리(성토고 3m↑인데 사면 5m·물매 1:1.2를 - * 동시에 만족하는 벽 자리가 없다) — **옹벽·석축 등 구조물 검토 대상** - * (`성토_비탈면.md` §2 "5m 초과 시 구조물 설치 의무"). - */ - structureRequired: boolean; - }; - /** 유출측 성토부선(보호공 대체 — 2026-08-22). 끝 구간이 5m 이상이면 추가 가능. */ - outletFill: { segments: OutletFillSegment[]; addable: boolean }; - /** 유입 집수정(ㄴ·ㄷ형) 계류측 성토부선 — 유출측과 같은 체계(2026-08-22 사용자). */ - basinFill: { segments: OutletFillSegment[]; addable: boolean }; - /** 집수정 계류측 다단 기슭막이 — 조정창 키 `bextra{n}`. */ - basinExtras: WallLayout[]; - /** 한계에 잘린 뒤의 **실제 적용된** 집수정 조작값 — 조정창이 되받는다. */ - basinAdjust: BasinAdjust; - /** 유입 기슭막이의 관 시작 접속선(2026-08-22 ②) — 위면 0도 성토선(지반 정지), - * 아래면 0도 1m 후 표준 절토 경사선. 집수정·접속 불필요면 null. */ - inletFill: OutletFillSegment | null; - /** 추가 기슭막이(배관 없는 벽 — 계단식). 조정창 키 = `extra{n}`. */ - extraWalls: WallLayout[]; - /** 유입 집수정 단면(있을 때만). */ - basin: BasinLayout | null; - designTrim: CulvertDesignTrim | null; - /** - * **실제 적용된** 기슭막이 이동량(m, + = 계류측 바깥). 요청값이 노견·사면 역전·사면 끝 - * 한계에 걸리면 잘려 들어온다 — 조정창은 이 값을 보여 주고 저장한다. 그러지 않으면 - * 눌러도 안 움직이는데 숫자만 계속 커진다(2026-08-21 사용자 지적). - */ - revetShift: { - inlet: WallAdjust; - outlet: WallAdjust; - extras: WallAdjust[]; - /** 집수정 계류측 다단의 적용 조작값. */ - basinExtras: WallAdjust[]; - }; - /** - * 조정창 드롭다운 선택지 가용성(2026-08-22 사용자 — 상황에 안 맞는 선택지 숨김): - * revetAllowed = 기슭막이+배관(배관이 원지반에 안 묻힐 때만), - * basinLUAllowed = 집수정 ㄴ·ㄷ형(구조물이 원지반으로 밀고 들어갈 때만). - */ - inletOptions: { revetAllowed: boolean; basinLUAllowed: boolean }; - /** 세월교 · 박스 흙막이(73-4) — 본체 끝 자리의 배관 4축 x(적용 x 의 0점). 본체 없으면 없음. */ - bodyX?: { inlet: number; outlet: number } | null; - /** 유입 · 유출 벽의 순수 높이(m) — 높이 빈 칸인 연동 옆 카드가 물려받음(152-2). */ - pureHeights?: { inlet: number; outlet: number }; -} -/** 유입측 구조물 사용자 선택 — auto = 규칙 기반 자동 선택. */ -export type InletStructureChoice = "auto" | "revet" | "I" | "L" | "U"; diff --git a/B06_Section/B06_Section_UI_Cross_Excavation.ts b/B06_Section/B06_Section_UI_Cross_Excavation.ts index a432f412d..ba0a8dff5 100644 --- a/B06_Section/B06_Section_UI_Cross_Excavation.ts +++ b/B06_Section/B06_Section_UI_Cross_Excavation.ts @@ -20,7 +20,7 @@ import { WALL_FOUNDATION_WIDTH_M, WALL_TRENCH_CLEARANCE_M, } from "@util/common_util_excavation"; -import type { WallLayout } from "./B06_Section_UI_Cross_Culvert_Types"; +import type { WallLayout } from "./logic/B06_Section_Cross_Culvert_Types"; const SVG_NS = "http://www.w3.org/2000/svg"; diff --git a/B06_Section/B06_Section_UI_Cross_Ford.ts b/B06_Section/B06_Section_UI_Cross_Ford.ts index dda7e6a5f..d21005bc7 100644 --- a/B06_Section/B06_Section_UI_Cross_Ford.ts +++ b/B06_Section/B06_Section_UI_Cross_Ford.ts @@ -2,7 +2,7 @@ * B06_Section_UI_Cross_Ford.ts * 세월교 측점 횡단 카드의 구체(측벽·바닥판·관·성토 채움) **오버레이 그리기**. * - * 기하 계산은 `B06_Section_UI_Cross_Ford_Geom.ts`가 맡는다(700줄 제한 분리). + * 기하 계산은 `logic/B06_Section_Cross_Ford_Geom.ts`가 맡는다(700줄 제한 분리). * 여기서는 계산 결과(`FordLayout`)를 SVG 도형으로 옮기기만 한다 — 치수 결정 금지. * * 그리는 순서 = 시공 순서: ① 바닥판 → ② 측벽(ㄴ형 집수정 재사용) → ③ 관 → @@ -10,12 +10,9 @@ * ========================================================================== */ import { pipeTagId, tagShape } from "../common_util/common_util_pick_tag"; -import type { FordLayout, FordWallRole } from "./B06_Section_UI_Cross_Ford_Geom"; -import { - appendPipeInfoText, - outlineBox, - type OffsetPoint, -} from "./B06_Section_UI_Cross_Culvert_Types"; +import type { FordLayout, FordWallRole } from "./logic/B06_Section_Cross_Ford_Geom"; +import { appendPipeInfoText } from "./B06_Section_UI_Cross_Culvert_Types"; +import { outlineBox, type OffsetPoint } from "./logic/B06_Section_Cross_Culvert_Types"; import { appendChuteBand } from "./B06_Section_UI_Cross_Wall_Hatch"; export { @@ -24,14 +21,14 @@ export { DEFAULT_FORD_WALL_ADJUST, fordAdjustOf, fordDropOf, -} from "./B06_Section_UI_Cross_Ford_Geom"; +} from "./logic/B06_Section_Cross_Ford_Geom"; export type { FordAdjust, FordLayout, FordSideLayout, FordWallAdjust, FordWallRole, -} from "./B06_Section_UI_Cross_Ford_Geom"; +} from "./logic/B06_Section_Cross_Ford_Geom"; const SVG_NS = "http://www.w3.org/2000/svg"; diff --git a/B06_Section/B06_Section_UI_Cross_Ford_Panel.ts b/B06_Section/B06_Section_UI_Cross_Ford_Panel.ts index 9a869638b..4a70e7d7a 100644 --- a/B06_Section/B06_Section_UI_Cross_Ford_Panel.ts +++ b/B06_Section/B06_Section_UI_Cross_Ford_Panel.ts @@ -12,7 +12,7 @@ * 바닥판 연장은 날개벽 길이×cos(각도)가 정하므로 읽기 전용으로 보여 준다. * ========================================================================== */ -import type { FordAdjust, FordWallAdjust, FordWallRole } from "./B06_Section_UI_Cross_Ford_Geom"; +import type { FordAdjust, FordWallAdjust, FordWallRole } from "./logic/B06_Section_Cross_Ford_Geom"; import type { FordPick } from "./B06_Section_UI_Cross_Ford"; import { bindArrowKeys, diff --git a/B06_Section/B06_Section_UI_Cross_Ford_Pavement.ts b/B06_Section/B06_Section_UI_Cross_Ford_Pavement.ts index bd9d00b8d..11060739e 100644 --- a/B06_Section/B06_Section_UI_Cross_Ford_Pavement.ts +++ b/B06_Section/B06_Section_UI_Cross_Ford_Pavement.ts @@ -12,37 +12,13 @@ * ========================================================================== */ import type { CrossDesign } from "./B06_Section_Api_Fetch"; -import type { OffsetPoint } from "./B06_Section_UI_Cross_Culvert_Types"; +import type { OffsetPoint } from "./logic/B06_Section_Cross_Culvert_Types"; +import { fordEdges, outletFillLine, overflowLine } from "./logic/B06_Section_Cross_Ford_Pavement"; +import type { FordPavementSpec } from "./logic/B06_Section_Cross_Ford_Pavement"; import { appendPavementOverlay } from "./B06_Section_UI_Cross_Design_Surface"; import { pipeTagId, tagShape } from "../common_util/common_util_pick_tag"; import { appendChuteBand, chuteRun } from "./B06_Section_UI_Cross_Wall_Hatch"; -/** 백엔드 `section.ford_pavement` 제원(치수 결정은 서버 몫 — 여기서는 좌표만 만든다). */ -export interface FordPavementSpec { - /** 관 지점 누가거리 — 폭 절반 걸친 옆 카드도 같은 값. */ - chainage_m?: number; - span_m: number; - /** 월류 높이(m) — 노면 위 점선. 없으면 점선을 안 그림(수치를 지어내지 않는다). */ - depth_m: number | null; - /** 노면 경사(%) — 경사각도 환산 · 서버 설계 노면이 이 값으로 기욺. */ - slope_pct: number | null; - /** 정본에 안 적혀 기본값으로 그린 칸 「이름 값」 — 카드 머리에 「기본값(미확정)」. */ - defaulted?: string[]; - /** 도수로 · 돌붙임(`ford_chute_{n}_*` · 136-2) — 유출측 성토사면 띠 · 길이 = 사면 따라(286-3 · 비면 원지반까지). */ - chute?: { structure: string; thickness_m: number; length_m?: number }; -} - -/** 유입(상류)이 좌측(+offset)인가 — 기울인 노면이면 높은 노면 끝 · 아니면 설계선 양 끝 표고가 높은 - * 쪽(286-2) · 같으면 단면유형. */ -export function inflowLeft(design: CrossDesign): boolean { - const edges = design.road_edges; - if (design.tilted && edges && Math.abs(edges.left.elevation_m - edges.right.elevation_m) > 1e-6) - return edges.left.elevation_m > edges.right.elevation_m; - const line = [...(design.design_line ?? [])].sort((a, b) => a.offset_m - b.offset_m); - const gap = line.length >= 2 ? line[line.length - 1].elevation_m - line[0].elevation_m : 0; - return Math.abs(gap) > 1e-3 ? gap > 0 : design.section_mode !== "right_cut"; -} - function line(points: string[], className: string): SVGPolylineElement { const polyline = document.createElementNS("http://www.w3.org/2000/svg", "polyline"); polyline.setAttribute("points", points.join(" ")); @@ -134,90 +110,6 @@ export function appendFordPavementOverlay( return true; } -/** 콘크리트 · 노면 양 끝 — 낮은 쪽을 늘였으면(294-3 `tilt_reach`) 그 쪽은 원지반과 만난 자리까지. */ -export function fordEdges(design: CrossDesign): CrossDesign["road_edges"] | undefined { - const edges = design.road_edges; - const reach = design.tilt_reach; - if (!edges || !reach) return edges; - const end = { offset_m: reach.offset_m, elevation_m: reach.elevation_m }; - return reach.side === "left" ? { ...edges, left: end } : { ...edges, right: end }; -} - -/** - * 월류 높이선(315-2) — 노면 양 끝 위 월류 높이 · 노면과 같은 각도. 성토 쪽(노면 끝 밖 설계선이 내려감)은 - * 성토 사면선을 노면 끝 너머 위 · 안쪽으로 늘여 월류 높이선과 만나는 자리에서 끝 → 노면 끝까지 그 연장선. - * 절토 쪽 · 늘인 노면 끝(294-3)은 노면 끝 위 그대로. 좌(+offset) → 우 차례. - */ -export function overflowLine(design: CrossDesign, depth: number): OffsetPoint[] { - const edges = fordEdges(design); - if (!edges) return []; - const { left, right } = edges; - const span = Math.abs(left.offset_m - right.offset_m); - const side = (edge: typeof left, other: typeof left): OffsetPoint[] => { - const top = { offset: edge.offset_m, elevation: edge.elevation_m + depth }; - const outward = Math.sign(edge.offset_m - other.offset_m) || 1; - const next = (design.design_line ?? []) - .filter((p) => (p.offset_m - edge.offset_m) * outward > 1e-6) - .sort((a, b) => (a.offset_m - b.offset_m) * outward)[0]; - const cut = edge.offset_m === design.tilt_reach?.offset_m; // 늘인 끝 = 원지반(294-3 · 절토) - if (cut || !next || next.elevation_m >= edge.elevation_m - 1e-6 || span < 1e-9) return [top]; - // 안쪽 d m 에서 성토선 = edge + fill·d · 월류선 = edge + depth + road·d → d = depth / (fill − road) - const fill = (edge.elevation_m - next.elevation_m) / Math.abs(next.offset_m - edge.offset_m); - const road = (other.elevation_m - edge.elevation_m) / span; - if (fill <= road + 1e-9) return [top]; - const d = Math.min(depth / (fill - road), span); - const meet = { offset: edge.offset_m - outward * d, elevation: edge.elevation_m + fill * d }; - return [{ offset: edge.offset_m, elevation: edge.elevation_m }, meet]; - }; - return [...side(left, right), ...side(right, left).reverse()]; -} - -/** 원지반 꺾은선의 그 오프셋 표고(밖은 끝값). */ -function groundZ(ground: readonly OffsetPoint[], offset: number): number | null { - if (!ground.length) return null; - if (offset <= ground[0].offset) return ground[0].elevation; - for (let i = 1; i < ground.length; i += 1) { - const [a, b] = [ground[i - 1], ground[i]]; - if (offset > b.offset) continue; - const span = b.offset - a.offset; - return span > 1e-9 - ? a.elevation + ((b.elevation - a.elevation) * (offset - a.offset)) / span - : b.elevation; - } - return ground[ground.length - 1].elevation; -} - -/** - * 유출측(낮은 쪽) 성토사면 선(136-2 · 294-4) — 낮은 쪽 끝단(늘인 노면 끝 · 아니면 유출 쪽 노견 끝)에서 - * 설계선을 바깥으로 따라 내려가는 동안만(오르면 절토라 멈춤) · 원지반(`ground`)에 닿으면 끝. - * 성토가 없으면(끝단이 이미 원지반 · 양측 절토 물넘이) 점 하나 = 띠 없음. - */ -export function outletFillLine( - design: CrossDesign, - ground?: readonly OffsetPoint[], -): OffsetPoint[] { - const edges = fordEdges(design); - if (!edges || !design.design_line?.length) return []; - const reach = design.tilt_reach; - const outLeft = reach ? reach.side === "left" : !inflowLeft(design); // +offset = 좌측 - const outward = outLeft ? 1 : -1; - const edge = outLeft - ? Math.max(edges.left.offset_m, edges.right.offset_m) - : Math.min(edges.left.offset_m, edges.right.offset_m); - const points = design.design_line - .filter((p) => (p.offset_m - edge) * outward >= -1e-6) - .sort((a, b) => (a.offset_m - b.offset_m) * outward); - const line: OffsetPoint[] = []; - for (const p of points) { - const last = line[line.length - 1]; - if (last && p.elevation_m > last.elevation + 1e-6) break; - line.push({ offset: p.offset_m, elevation: p.elevation_m }); - const z = ground ? groundZ(ground, p.offset_m) : null; - if (z !== null && Math.abs(z - p.elevation_m) < 1e-3) break; // 원지반에 닿음 = 끝 - } - return line; -} - /** * 물넘이 도수로 · 돌붙임 선(286-3 · 294-4) — 낮은 쪽 끝단에서 성토사면 따라 · 길이 비움 = 원지반 닿으면 끝 · * 길이 넣음 = 그 길이(사면보다 길면 원지반 따라 이어 · 315-1 이은 조각까지 한 선) · 양측 절토(늘인 노면 끝이 diff --git a/B06_Section/B06_Section_UI_Cross_Revetment.ts b/B06_Section/B06_Section_UI_Cross_Revetment.ts index 8ac2c4316..0a4223eb4 100644 --- a/B06_Section/B06_Section_UI_Cross_Revetment.ts +++ b/B06_Section/B06_Section_UI_Cross_Revetment.ts @@ -29,14 +29,14 @@ import { outlineLeanOf } from "./logic/B06_Section_UI_Cross_Lean"; import { catalogRow } from "../common_util/common_util_structure_catalog"; import type { RevetMaterial } from "./logic/B06_Section_UI_Cross_Culvert_Const"; import { fillWallBaseWidth, groundInterpolator } from "./logic/B06_Section_UI_Cross_Culvert_Solve"; -import { buildExtrasAt } from "./B06_Section_UI_Cross_Culvert_Extra"; +import { buildExtrasAt } from "./logic/B06_Section_Cross_Culvert_Extra"; import { ZERO_ADJUST, type CulvertDesignTrim, type OutletFillSegment, type WallAdjust, type WallLayout, -} from "./B06_Section_UI_Cross_Culvert_Types"; +} from "./logic/B06_Section_Cross_Culvert_Types"; import { appendWallTrench, foundationChoice } from "./B06_Section_UI_Cross_Excavation"; import { appendPlanLine, buildRevetWallGeometry, drawRevetWall } from "./B06_Section_UI_Cross_Wall"; diff --git a/B06_Section/B06_Section_UI_Cross_Structure_Panel.ts b/B06_Section/B06_Section_UI_Cross_Structure_Panel.ts index 439d042e5..ab5f585dd 100644 --- a/B06_Section/B06_Section_UI_Cross_Structure_Panel.ts +++ b/B06_Section/B06_Section_UI_Cross_Structure_Panel.ts @@ -16,8 +16,8 @@ import type { InletStructureChoice, RevetKey, RevetPick } from "./B06_Section_UI_Cross_Culvert"; import { REVET_FORMS } from "./logic/B06_Section_UI_Cross_Culvert_Const"; -import type { BasinAdjust, WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types"; -import type { SpanValues } from "./B06_Section_UI_Cross_Culvert_Wire"; +import type { BasinAdjust, WallAdjust } from "./logic/B06_Section_Cross_Culvert_Types"; +import type { SpanValues } from "./logic/B06_Section_Cross_Culvert_Wire"; import { L } from "./B06_Section_UI_Section_Common"; import { arrowsBelongToProfile, diff --git a/B06_Section/B06_Section_UI_Cross_Underdrain.ts b/B06_Section/B06_Section_UI_Cross_Underdrain.ts index 4b43d4798..111df2f66 100644 --- a/B06_Section/B06_Section_UI_Cross_Underdrain.ts +++ b/B06_Section/B06_Section_UI_Cross_Underdrain.ts @@ -13,7 +13,7 @@ * ========================================================================== */ import type { CrossDesign } from "./B06_Section_Api_Fetch"; -import type { OffsetPoint } from "./B06_Section_UI_Cross_Culvert_Types"; +import type { OffsetPoint } from "./logic/B06_Section_Cross_Culvert_Types"; import { cell, courses, type BandFrame } from "./B06_Section_UI_Cross_Wall_Hatch"; import type { UnderdrainSpec } from "@util/common_util_structure_underdrain"; diff --git a/B06_Section/B06_Section_UI_Cross_View.ts b/B06_Section/B06_Section_UI_Cross_View.ts index 3f1ad8390..07a63a6f6 100644 --- a/B06_Section/B06_Section_UI_Cross_View.ts +++ b/B06_Section/B06_Section_UI_Cross_View.ts @@ -19,7 +19,7 @@ import { appendFordPavementOverlay, appendFordSurfaceDropPlan, } from "./B06_Section_UI_Cross_Ford_Pavement"; -import { ownWallsDrawAt } from "./B06_Section_UI_Cross_Culvert_Wire"; +import { ownWallsDrawAt } from "./logic/B06_Section_Cross_Culvert_Wire"; import { attachShapePick, shapePickRoute, splitPipePick } from "./B06_Section_UI_Cross_Shape_Pick"; import { stackPickTargets } from "./B06_Section_UI_Cross_Pick_Order"; import { appendUnderdrainOverlay } from "./B06_Section_UI_Cross_Underdrain"; @@ -46,16 +46,16 @@ import { } from "./B06_Section_UI_Cross_View_Frame"; import { appendFordOverlay, computeFordLayout } from "./B06_Section_UI_Cross_Ford"; import type { FordControl } from "./B06_Section_UI_Cross_Ford_Panel"; -import { computeCardCulvert, setCulvertNotice } from "./B06_Section_UI_Cross_Culvert_Wire"; +import { computeCardCulvert, setCulvertNotice } from "./logic/B06_Section_Cross_Culvert_Wire"; import { showToast } from "@ui/ui_template_elements"; -import type { CulvertLink } from "./B06_Section_UI_Cross_Culvert_Wire"; +import type { CulvertLink } from "./logic/B06_Section_Cross_Culvert_Wire"; import type { ExtraWallControl, InletStructureControl, RevetLinkControl, RevetOffsetControl, StructureSpanControl, -} from "./B06_Section_UI_Cross_Culvert_Wire"; +} from "./logic/B06_Section_Cross_Culvert_Wire"; import { culvertCardState } from "./B06_Section_UI_Cross_View_Structure"; import { buildCardPanels, emptyCardCulvertState } from "./B06_Section_UI_Cross_View_Panels"; import { @@ -134,7 +134,7 @@ export type { SpanRole, SpanValues, StructureSpanControl, -} from "./B06_Section_UI_Cross_Culvert_Wire"; +} from "./logic/B06_Section_Cross_Culvert_Wire"; export function createCrossSectionCard( section: CrossSection, diff --git a/B06_Section/B06_Section_UI_Cross_View_Panels.ts b/B06_Section/B06_Section_UI_Cross_View_Panels.ts index 7f53c1105..bf7c4d0ea 100644 --- a/B06_Section/B06_Section_UI_Cross_View_Panels.ts +++ b/B06_Section/B06_Section_UI_Cross_View_Panels.ts @@ -7,7 +7,7 @@ import type { CrossSection } from "./B06_Section_Api_Fetch"; import type { RevetKey, RevetPick } from "./B06_Section_UI_Cross_Culvert"; import { REVET_FORM_DEFAULT } from "./logic/B06_Section_UI_Cross_Culvert_Const"; -import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types"; +import type { WallAdjust } from "./logic/B06_Section_Cross_Culvert_Types"; import type { CulvertLink, ExtraWallControl, @@ -15,7 +15,7 @@ import type { RevetLinkControl, RevetOffsetControl, StructureSpanControl, -} from "./B06_Section_UI_Cross_Culvert_Wire"; +} from "./logic/B06_Section_Cross_Culvert_Wire"; import { buildStructurePanel } from "./B06_Section_UI_Cross_Structure_Panel"; import { adoptAdjustPanel, releaseAdjustPanel } from "./B06_Section_UI_Adjust_Dock"; import { structurePanelDeps } from "./B06_Section_UI_Cross_View_Structure"; diff --git a/B06_Section/B06_Section_UI_Cross_View_Structure.ts b/B06_Section/B06_Section_UI_Cross_View_Structure.ts index 7eab881c9..a14973fcc 100644 --- a/B06_Section/B06_Section_UI_Cross_View_Structure.ts +++ b/B06_Section/B06_Section_UI_Cross_View_Structure.ts @@ -15,8 +15,8 @@ import { WALL_MIN_PURE_HEIGHT_M, } from "./logic/B06_Section_UI_Cross_Culvert_Const"; import { clampAxis, type AxisDragSpec } from "./B06_Section_UI_Cross_Panel_Base"; -import type { CulvertLayout } from "./B06_Section_UI_Cross_Culvert_Types"; -import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types"; +import type { CulvertLayout } from "./logic/B06_Section_Cross_Culvert_Types"; +import type { WallAdjust } from "./logic/B06_Section_Cross_Culvert_Types"; import type { StructurePanelDeps } from "./B06_Section_UI_Cross_Structure_Panel"; import { showToast } from "@ui/ui_template_elements"; import { L } from "./B06_Section_UI_Section_Common"; @@ -27,7 +27,7 @@ import type { RevetOffsetControl, SpanRole, StructureSpanControl, -} from "./B06_Section_UI_Cross_Culvert_Wire"; +} from "./logic/B06_Section_Cross_Culvert_Wire"; /** 다단(추가) 벽 키인가 — 유출측 `extra0…`·유입측 `bextra0…`. */ export const isTierKey = (key: RevetKey): boolean => diff --git a/B06_Section/B06_Section_UI_Cross_Wall.ts b/B06_Section/B06_Section_UI_Cross_Wall.ts index ae56ba825..82fc28a05 100644 --- a/B06_Section/B06_Section_UI_Cross_Wall.ts +++ b/B06_Section/B06_Section_UI_Cross_Wall.ts @@ -18,7 +18,7 @@ import { WALL_MIN_PURE_HEIGHT_M, } from "./logic/B06_Section_UI_Cross_Culvert_Const"; import type { RevetMaterial } from "./logic/B06_Section_UI_Cross_Culvert_Const"; -import type { OffsetPoint, WallLayout } from "./B06_Section_UI_Cross_Culvert_Types"; +import type { OffsetPoint, WallLayout } from "./logic/B06_Section_Cross_Culvert_Types"; import { appendWallHatch } from "./B06_Section_UI_Cross_Wall_Hatch"; const SVG_NS = "http://www.w3.org/2000/svg"; diff --git a/B06_Section/B06_Section_UI_Cross_Wall_Hatch.ts b/B06_Section/B06_Section_UI_Cross_Wall_Hatch.ts index 38ac262c8..f6836625b 100644 --- a/B06_Section/B06_Section_UI_Cross_Wall_Hatch.ts +++ b/B06_Section/B06_Section_UI_Cross_Wall_Hatch.ts @@ -17,7 +17,7 @@ * ========================================================================== */ import { REVET_THICKNESS_M } from "./logic/B06_Section_UI_Cross_Culvert_Const"; -import type { OffsetPoint, WallLayout } from "./B06_Section_UI_Cross_Culvert_Types"; +import type { OffsetPoint, WallLayout } from "./logic/B06_Section_Cross_Culvert_Types"; const SVG_NS = "http://www.w3.org/2000/svg"; const HATCH_CLASS = "b06-chart__culvert-stone"; diff --git a/B06_Section/B06_Section_UI_Page_Link_Session.ts b/B06_Section/B06_Section_UI_Page_Link_Session.ts index 9825a3885..4b2333af9 100644 --- a/B06_Section/B06_Section_UI_Page_Link_Session.ts +++ b/B06_Section/B06_Section_UI_Page_Link_Session.ts @@ -7,7 +7,7 @@ import type { CrossSection, StoredWallSpan } from "./B06_Section_Api_Fetch"; import type { RevetKey } from "./B06_Section_UI_Cross_Culvert"; -import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types"; +import type { WallAdjust } from "./logic/B06_Section_Cross_Culvert_Types"; import { computeCardCulvert, culvertLinkFor, @@ -20,7 +20,7 @@ import { type RevetLinkControl, type RevetOffsetControl, type SpanValues, -} from "./B06_Section_UI_Cross_Culvert_Wire"; +} from "./logic/B06_Section_Cross_Culvert_Wire"; import { culvertCardState } from "./B06_Section_UI_Cross_View_Structure"; import type { PipeRevetChoice } from "../A00_Common/design/design_structure_pipe_calc"; diff --git a/B06_Section/B06_Section_UI_Page_Span_Control.ts b/B06_Section/B06_Section_UI_Page_Span_Control.ts index 1f9117023..28e4d7bf9 100644 --- a/B06_Section/B06_Section_UI_Page_Span_Control.ts +++ b/B06_Section/B06_Section_UI_Page_Span_Control.ts @@ -12,7 +12,7 @@ import { tierSpanOf, type SpanValues, type StructureSpanControl, -} from "./B06_Section_UI_Cross_Culvert_Wire"; +} from "./logic/B06_Section_Cross_Culvert_Wire"; import * as CulvertConst from "./logic/B06_Section_UI_Cross_Culvert_Const"; import type { SessionCells } from "./B06_Section_Session_Cells"; diff --git a/B06_Section/B06_Section_UI_Page_Station_Controls.ts b/B06_Section/B06_Section_UI_Page_Station_Controls.ts index 09ec48789..dbb997678 100644 --- a/B06_Section/B06_Section_UI_Page_Station_Controls.ts +++ b/B06_Section/B06_Section_UI_Page_Station_Controls.ts @@ -12,8 +12,8 @@ import type { StoredWallSpan, } from "./B06_Section_Api_Fetch"; import type { InletStructureChoice, RevetKey, RevetPick } from "./B06_Section_UI_Cross_Culvert"; -import { DEFAULT_BASIN_ADJUST, ZERO_ADJUST } from "./B06_Section_UI_Cross_Culvert_Types"; -import type { BasinAdjust, WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types"; +import { DEFAULT_BASIN_ADJUST, ZERO_ADJUST } from "./logic/B06_Section_Cross_Culvert_Types"; +import type { BasinAdjust, WallAdjust } from "./logic/B06_Section_Cross_Culvert_Types"; import type { ExtraWallControl, InletStructureControl, @@ -23,7 +23,11 @@ import type { StationWidthControl, StructureSpanControl, } from "./B06_Section_UI_Cross_View"; -import { culvertOwnerFor, culvertReach, wallStandsAt } from "./B06_Section_UI_Cross_Culvert_Wire"; +import { + culvertOwnerFor, + culvertReach, + wallStandsAt, +} from "./logic/B06_Section_Cross_Culvert_Wire"; import { createCulvertOptionWriter } from "./B06_Section_Api_Culvert_Options"; import { createBodyControls } from "./B06_Section_UI_Page_Ford_Controls"; import { diff --git a/B06_Section/B06_Section_UI_Section_View.ts b/B06_Section/B06_Section_UI_Section_View.ts index 3fc2d15c8..a6c46b5dc 100644 --- a/B06_Section/B06_Section_UI_Section_View.ts +++ b/B06_Section/B06_Section_UI_Section_View.ts @@ -46,8 +46,8 @@ import { cardZoomStates } from "./B06_Section_UI_Cross_View_Zoom"; import { CROSS_GRID_GAP, CROSS_HEIGHT, createIdleQueue } from "./B06_Section_UI_Section_Common"; import { eagerCardCount, pendingCard } from "./B06_Section_UI_Section_View_Lazy"; import type { SectionStructureEdit } from "./B06_Section_UI_Section_View_Menu"; -import { culvertLinkFor as culvertLink } from "./B06_Section_UI_Cross_Culvert_Wire"; -import type { CulvertLink } from "./B06_Section_UI_Cross_Culvert_Wire"; +import { culvertLinkFor as culvertLink } from "./logic/B06_Section_Cross_Culvert_Wire"; +import type { CulvertLink } from "./logic/B06_Section_Cross_Culvert_Wire"; import { CROSS_COLUMNS_BASE, CROSS_COLUMNS_MAX, diff --git a/B06_Section/B06_Section_UI_Cross_Box_Geom.ts b/B06_Section/logic/B06_Section_Cross_Box_Geom.ts similarity index 95% rename from B06_Section/B06_Section_UI_Cross_Box_Geom.ts rename to B06_Section/logic/B06_Section_Cross_Box_Geom.ts index ff0268907..0d033643e 100644 --- a/B06_Section/B06_Section_UI_Cross_Box_Geom.ts +++ b/B06_Section/logic/B06_Section_Cross_Box_Geom.ts @@ -1,5 +1,5 @@ /* ============================================================================= - * B06_Section_UI_Cross_Box_Geom.ts + * B06_Section_Cross_Box_Geom.ts * BOX암거 구체 **기하 계산** — 그리기(`_Cross_Box.ts`)와 분리(700줄 제한). * 백엔드 `section.box` 제원을 좌표로 옮긴다(치수 결정 금지). * @@ -16,18 +16,18 @@ * 부재 두께는 세월교 승계(측벽 0.2·상판 0.3·저판 0.3)이며 백엔드가 실어 보낸다. * ========================================================================== */ -import { groundLine } from "./B06_Section_UI_Cross_Wall_Hatch"; -import type { BoxSet, CrossSection, SectionSample } from "./B06_Section_Api_Fetch"; +import { groundLine } from "../B06_Section_UI_Cross_Wall_Hatch"; +import type { BoxSet, CrossSection, SectionSample } from "../B06_Section_Api_Fetch"; import type { BasinLayout, CulvertDesignTrim, OffsetPoint, OutletFillSegment, -} from "./B06_Section_UI_Cross_Culvert_Types"; -import { buildExtrasAt } from "./B06_Section_UI_Cross_Culvert_Extra"; -import { FILL_SLOPE_RATIO_MIN } from "./logic/B06_Section_UI_Cross_Culvert_Const"; -import { designInterpolator, groundInterpolator } from "./logic/B06_Section_UI_Cross_Culvert_Solve"; -import { structureCutPoints, structureCutSpec } from "./logic/B06_Section_UI_Cross_Structure_Cut"; +} from "./B06_Section_Cross_Culvert_Types"; +import { buildExtrasAt } from "./B06_Section_Cross_Culvert_Extra"; +import { FILL_SLOPE_RATIO_MIN } from "./B06_Section_UI_Cross_Culvert_Const"; +import { designInterpolator, groundInterpolator } from "./B06_Section_UI_Cross_Culvert_Solve"; +import { structureCutPoints, structureCutSpec } from "./B06_Section_UI_Cross_Structure_Cut"; /** 구체 한쪽 끝의 조작값 — 좌·우를 따로 잡는다(2026-08-25 사용자). */ export interface BoxSideAdjust { diff --git a/B06_Section/logic/B06_Section_Cross_Culvert.ts b/B06_Section/logic/B06_Section_Cross_Culvert.ts new file mode 100644 index 000000000..0e4098bef --- /dev/null +++ b/B06_Section/logic/B06_Section_Cross_Culvert.ts @@ -0,0 +1,58 @@ +/* ============================================================================= + * B06_Section_Cross_Culvert.ts + * 배수관 세트 기하 입구 — `computeCulvertLayout` · 고름 키 · 관 겹 꼭짓점. + * 그리기(`UI_Cross_Culvert.ts`)는 여기서 받은 좌표를 SVG 로 옮기기만 한다. + * ========================================================================== */ + +import type { CulvertLayout, PipeEnd } from "./B06_Section_Cross_Culvert_Types"; + +export { MIN_PIPE_COVER_M, computeCulvertLayout } from "./B06_Section_Cross_Culvert_Geom"; +export type { + BasinLayout, + BasinShape, + CulvertDesignTrim, + CulvertLayout, + EndFace, + InletStructureChoice, +} from "./B06_Section_Cross_Culvert_Geom"; + +/** 기슭막이 선택 키 — 측점 안에서 어느 벽인지 가린다. 추가 벽은 `extra{n}`. */ +// "own" = 배관과 무관한 **독립 기슭막이**(구조물 정본 D군). 조정창·조작값 저장은 +// 배관 벽과 같은 체계를 그대로 쓴다(2026-08-28 사용자: 배관측점처럼 이동). +export type RevetKey = "inlet" | "outlet" | "own" | `extra${number}` | `bextra${number}`; +/** 고름 전용 키 — 벽 키 + 관(118-1) · 도수로 띠(152-4) — 조정창 없음 · 강조만. 조정 함수에는 `RevetKey` 만. */ +export type RevetPick = RevetKey | "pipe" | "chute"; + +// 미세한 틈 방지: 양 끝을 축 방향으로 아주 조금만 밀어 넣는다. 벽 전면이 1:0.3으로 +// 기울어 있어 크게 밀면 관 하단이 벽 **바깥으로 삐져나온다**(2026-08-20 사용자 ③ +// 위치 어긋남 원인) — 렌더링 틈만 덮을 만큼(2㎝)으로 줄였다. +const OVERLAP_M = 0.02; + +/** 관 한 겹의 유입 · 유출 끝 — 축 법선으로 `delta`(m) 벌림(외경 = 관 두께 · 내경 = 0). */ +export function pipeShellCorners(layout: CulvertLayout, delta: number): [PipeEnd, PipeEnd] { + const { axis, normal } = layout.pipeAxis; + const pushOut = (corner: PipeEnd, dirSign: number): PipeEnd => ({ + bottom: { + offset: corner.bottom.offset + dirSign * axis.offset * OVERLAP_M, + elevation: corner.bottom.elevation + dirSign * axis.elevation * OVERLAP_M, + }, + top: { + offset: corner.top.offset + dirSign * axis.offset * OVERLAP_M, + elevation: corner.top.elevation + dirSign * axis.elevation * OVERLAP_M, + }, + }); + const shifted = (corner: PipeEnd): PipeEnd => ({ + bottom: { + offset: corner.bottom.offset - normal.offset * delta, + elevation: corner.bottom.elevation - normal.elevation * delta, + }, + top: { + offset: corner.top.offset + normal.offset * delta, + elevation: corner.top.elevation + normal.elevation * delta, + }, + }); + return [ + shifted(pushOut(layout.pipeCorners.inlet, -1)), + shifted(pushOut(layout.pipeCorners.outlet, 1)), + ]; +} diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts b/B06_Section/logic/B06_Section_Cross_Culvert_Extra.ts similarity index 98% rename from B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts rename to B06_Section/logic/B06_Section_Cross_Culvert_Extra.ts index facb70214..e5bf3639a 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts +++ b/B06_Section/logic/B06_Section_Cross_Culvert_Extra.ts @@ -1,5 +1,5 @@ /* ============================================================================= - * B06_Section_UI_Cross_Culvert_Extra.ts + * B06_Section_Cross_Culvert_Extra.ts * 유출측 **성토부선 + 추가 기슭막이(다단)** — Geom에서 분리(700줄 제한). * * 다단 기슭막이는 **1:1.2 고정 물매에서 안정적으로 성토고를 올리는 수단**이다 @@ -23,16 +23,16 @@ import { REVET_LEAN_RATIO, REVET_THICKNESS_M, WALL_MIN_PURE_HEIGHT_M, -} from "./logic/B06_Section_UI_Cross_Culvert_Const"; +} from "./B06_Section_UI_Cross_Culvert_Const"; import type { OffsetPoint, OutletFillSegment, WallAdjust, WallLayout, -} from "./B06_Section_UI_Cross_Culvert_Types"; -import { structureCutPoints } from "./logic/B06_Section_UI_Cross_Structure_Cut"; -import type { StructureCutSpec } from "./logic/B06_Section_UI_Cross_Structure_Cut"; -import { buildRevetWallGeometry } from "./B06_Section_UI_Cross_Wall"; +} from "./B06_Section_Cross_Culvert_Types"; +import { structureCutPoints } from "./B06_Section_UI_Cross_Structure_Cut"; +import type { StructureCutSpec } from "./B06_Section_UI_Cross_Structure_Cut"; +import { buildRevetWallGeometry } from "../B06_Section_UI_Cross_Wall"; export interface OutletExtrasInput { /** 성토부선 시작점 = 관 유출 하단 꼭짓점(배관 벽의 성토선 시작 자리). */ diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts b/B06_Section/logic/B06_Section_Cross_Culvert_Geom.ts similarity index 96% rename from B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts rename to B06_Section/logic/B06_Section_Cross_Culvert_Geom.ts index cab7ffa11..78ce55886 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts +++ b/B06_Section/logic/B06_Section_Cross_Culvert_Geom.ts @@ -1,13 +1,13 @@ /* ============================================================================= - * B06_Section_UI_Cross_Culvert_Geom.ts + * B06_Section_Cross_Culvert_Geom.ts * 배수관 세트(배관·기슭막이·성토부) **기하 계산** — 그리기(`_Cross_Culvert.ts`)와 분리. * 백엔드 `section.culvert` 제원을 좌표로 옮긴다(치수 결정 금지). 기슭막이는 전면 * 1:n 평행사변형(n = 품셈 표준경사 · `outlineLeanOf`), 관 끝단면은 구조물 변과 평행, 성토 경사선은 벽 접점에서 끊는다 * (designTrim). 유입 = 상단측. 집수정은 `_Basin.ts`, 성토부·다단은 `_Extra.ts`. * ========================================================================== */ -import { groundLine } from "./B06_Section_UI_Cross_Wall_Hatch"; -import type { CrossSection, CulvertSideSpec, SectionSample } from "./B06_Section_Api_Fetch"; +import { groundLine } from "../B06_Section_UI_Cross_Wall_Hatch"; +import type { CrossSection, CulvertSideSpec, SectionSample } from "../B06_Section_Api_Fetch"; import { revetWallSpec, pipeWallDefaultD, @@ -19,10 +19,10 @@ import { REVET_LEAN_RATIO, REVET_THICKNESS_M, revetTargetHeight, -} from "./logic/B06_Section_UI_Cross_Culvert_Const"; -import type { RevetMaterial } from "./logic/B06_Section_UI_Cross_Culvert_Const"; -import { outlineLeanOf } from "./logic/B06_Section_UI_Cross_Lean"; -import { buildRevetWallGeometry } from "./B06_Section_UI_Cross_Wall"; +} from "./B06_Section_UI_Cross_Culvert_Const"; +import type { RevetMaterial } from "./B06_Section_UI_Cross_Culvert_Const"; +import { outlineLeanOf } from "./B06_Section_UI_Cross_Lean"; +import { buildRevetWallGeometry } from "../B06_Section_UI_Cross_Wall"; import { bodyGuardAxes, bodyGuardBase, @@ -30,8 +30,8 @@ import { bodyGuardHeight, fordPipeFit, mergeGuardTrim, -} from "./B06_Section_UI_Cross_Body_Guard"; -import { ZERO_ADJUST } from "./B06_Section_UI_Cross_Culvert_Types"; +} from "../B06_Section_UI_Cross_Body_Guard"; +import { ZERO_ADJUST } from "./B06_Section_Cross_Culvert_Types"; import type { BasinLayout, BasinAdjust, @@ -42,12 +42,12 @@ import type { OffsetPoint, WallAdjust, WallLayout, -} from "./B06_Section_UI_Cross_Culvert_Types"; -import { DEFAULT_BASIN_ADJUST } from "./B06_Section_UI_Cross_Culvert_Types"; +} from "./B06_Section_Cross_Culvert_Types"; +import { DEFAULT_BASIN_ADJUST } from "./B06_Section_Cross_Culvert_Types"; // 상수·자료형은 분리 파일에 있고, 기존 import 경로를 유지하기 위해 그대로 다시 내보낸다. -export * from "./logic/B06_Section_UI_Cross_Culvert_Const"; -export * from "./B06_Section_UI_Cross_Culvert_Types"; +export * from "./B06_Section_UI_Cross_Culvert_Const"; +export * from "./B06_Section_Cross_Culvert_Types"; import { applyBasinPipeFill, @@ -56,16 +56,16 @@ import { decideInletBasin, BASIN_INNER_WIDTH_M, inletChoiceAvailability, -} from "./logic/B06_Section_UI_Cross_Culvert_Basin"; -import { floatFillStart, inletGroundConnector } from "./B06_Section_UI_Cross_Culvert_Extra"; -import { withBackCut } from "./logic/B06_Section_UI_Cross_Structure_Cut"; +} from "./B06_Section_UI_Cross_Culvert_Basin"; +import { floatFillStart, inletGroundConnector } from "./B06_Section_Cross_Culvert_Extra"; +import { withBackCut } from "./B06_Section_UI_Cross_Structure_Cut"; import { buildCulvertTiers, drawnFillPoints, outletPipeEnd, slopeZeroAnchor, -} from "./B06_Section_UI_Cross_Culvert_Tiers"; -import type { FillSlopeSegment, WallVertical } from "./logic/B06_Section_UI_Cross_Culvert_Solve"; +} from "./B06_Section_Cross_Culvert_Tiers"; +import type { FillSlopeSegment, WallVertical } from "./B06_Section_UI_Cross_Culvert_Solve"; import { cutLength, fillSlopeOf, @@ -75,7 +75,7 @@ import { pipeAxisSolver, placePipeWall, slopeLengthAlong, -} from "./logic/B06_Section_UI_Cross_Culvert_Solve"; +} from "./B06_Section_UI_Cross_Culvert_Solve"; /** 배수관 세트 기하 계산. 부족한 입력이면 null — 그리기와 분리(설계선 트림이 먼저 쓴다). */ export function computeCulvertLayout( diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Tiers.ts b/B06_Section/logic/B06_Section_Cross_Culvert_Tiers.ts similarity index 91% rename from B06_Section/B06_Section_UI_Cross_Culvert_Tiers.ts rename to B06_Section/logic/B06_Section_Cross_Culvert_Tiers.ts index 234def22d..e60c68376 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Tiers.ts +++ b/B06_Section/logic/B06_Section_Cross_Culvert_Tiers.ts @@ -1,22 +1,22 @@ /* ============================================================================= - * B06_Section_UI_Cross_Culvert_Tiers.ts + * B06_Section_Cross_Culvert_Tiers.ts * 배수관 세트 기하(`_Cross_Culvert_Geom`)에서 떼어 낸 **벽 자리 도우미 + 다단 흙막이** 층. * 다단 벽은 `buildExtrasAt`(공용 `buildRevetWallGeometry`)이 세우고, 여기서는 유출측과 * 집수정 계류측 두 채널의 입력을 짜고 면적용(미확정 단 제외) 결과를 함께 돌려준다. * ========================================================================== */ -import type { CrossSection } from "./B06_Section_Api_Fetch"; +import type { CrossSection } from "../B06_Section_Api_Fetch"; import { GUARD_CUT_BERM_M, REVET_EMBED_DEPTH_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"; -import { fillWallBaseWidth } from "./logic/B06_Section_UI_Cross_Culvert_Solve"; -import type { OffsetPoint, WallAdjust, WallLayout } from "./B06_Section_UI_Cross_Culvert_Types"; -import { outlineLeanOf } from "./logic/B06_Section_UI_Cross_Lean"; -import { structureCutSpec } from "./logic/B06_Section_UI_Cross_Structure_Cut"; +} from "./B06_Section_UI_Cross_Culvert_Const"; +import type { OutletExtrasResult } from "./B06_Section_Cross_Culvert_Extra"; +import { buildExtrasAt } from "./B06_Section_Cross_Culvert_Extra"; +import { fillWallBaseWidth } from "./B06_Section_UI_Cross_Culvert_Solve"; +import type { OffsetPoint, WallAdjust, WallLayout } from "./B06_Section_Cross_Culvert_Types"; +import { outlineLeanOf } from "./B06_Section_UI_Cross_Lean"; +import { structureCutSpec } from "./B06_Section_UI_Cross_Structure_Cut"; // d의 0점 = **성토선 길이 0 지점**(벽 상단 = 노견 — 2026-08-23 사용자 확정). // 그 자리(하단선 중점)는 이음선 상단점이 노견 끝에 오는 offset이고, 기본 자리는 diff --git a/B06_Section/logic/B06_Section_Cross_Culvert_Types.ts b/B06_Section/logic/B06_Section_Cross_Culvert_Types.ts new file mode 100644 index 000000000..5aebe40c7 --- /dev/null +++ b/B06_Section/logic/B06_Section_Cross_Culvert_Types.ts @@ -0,0 +1,246 @@ +/* ============================================================================= + * B06_Section_Cross_Culvert_Types.ts + * 배수관 세트 기하 **자료형** — 기하 계산(`_Cross_Culvert_Geom.ts`)에서 분리했다 + * (700줄 제한). 계산·그리기 양쪽이 함께 쓴다(그리기 글 = `UI_Cross_Culvert_Types`). + * ========================================================================== */ + +import type { CulvertSet } from "../B06_Section_Api_Fetch"; +import type { RevetMaterial } from "./B06_Section_UI_Cross_Culvert_Const"; + +/** + * 기슭막이 1매의 사용자 조작값(2026-08-22 확정 4축): + * x = 좌우(m, + = 계류측 바깥) — 노견(또는 윗단 시작점 수평선)을 늘려 평행이동, + * 성토선 물매 1:1.2 불변. + * d = 상하(m, 수평 성분, + = 사면 아래) — 성토선을 타고 대각 이동. + * **배관(유입·유출) 벽**은 0점 = 성토선 길이 0 지점(벽 상단 = 노견)인 + * 절대값(2026-08-23 사용자 확정), null = 기본 자리(성토선 0 지점 · 79-5). + * 추가 벽은 종전대로 자동 자리 기준 상대값(null = 0). + * h = 높이(m). 배관 벽은 **순수 높이**(바닥~상단, 관 아래 0.5 포함 — + * 2026-08-23), 추가 벽은 계산용(근입 0.5 위 기준선~상단). null = 기본값 + * (배관 관경+1.0·일반 1.5, 재질 한계로 잘림). + * m = 재질. null = 기본값(메쌓기). 한계: 메 2.0 / 찰 3.0 / 콘크리트 5.0. + */ +export interface WallAdjust { + x: number; + d: number | null; + h: number | null; + /** 형태(`REVET_FORMS` 문자열). 옛 저장분은 재질 코드(dry/wet/concrete) — `revetFormOf`가 옮긴다. */ + m: string | null; +} + +/** 조작값 기본(전부 자동). */ +export const ZERO_ADJUST: WallAdjust = { x: 0, d: null, h: null, m: null }; + +export interface OffsetPoint { + offset: number; + elevation: number; +} + +/** 점들을 감싸는 가상 사각형 — 고르기 판정면만(그림 아님) · 세월교 측벽 ㄴ(79-6) · 집수정(190-1). */ +export function outlineBox(points: OffsetPoint[]): OffsetPoint[] { + const offsets = points.map((point) => point.offset); + const elevations = points.map((point) => point.elevation); + const [left, right] = [Math.max(...offsets), Math.min(...offsets)]; + const [top, bottom] = [Math.max(...elevations), Math.min(...elevations)]; + return [ + { offset: left, elevation: top }, + { offset: right, elevation: top }, + { offset: right, elevation: bottom }, + { offset: left, elevation: bottom }, + ]; +} + +/** 설계선(성토 경사) 트림 범위 — 이 밖은 벽·보호공이 대신하므로 그리지 않는다. */ +export interface CulvertDesignTrim { + minOffset: number; + maxOffset: number; + /** 트림 경계에서 성토 경사선이 **닿아야 할 표고**(= 벽 이음선 상단점). 없으면 설계선 그대로. */ + minElevation?: number; + maxElevation?: number; + /** + * 노견 → (노폭 연장) → 벽 이음선 상단점 폴리라인. 물매가 1:1.2 고정이라 벽이 밖으로 + * 나간 만큼 **노폭이 늘어난다** — 그 연장 구간이 가운데 점으로 들어온다. + */ + minSlope?: { points: OffsetPoint[] }; + maxSlope?: { points: OffsetPoint[] }; +} + +export interface WallLayout { + role: "inlet" | "outlet" | "extra"; + /** 추가 기슭막이 번호(role === "extra"일 때만) — 조정창 키 `extra{n}`이 된다. */ + extraIndex?: number; + form: string | null; + lengthM: number | null; + /** 배면(도로측) 바닥 offset. */ + backOffset: number; + /** 전면(계류측) 바닥 offset. */ + outerOffset: number; + base: number; + /** 벽 높이(m) = **계산용**(관 invert~상단, 근입 제외 — 2026-08-22 사용자 확정). + * 기초는 시공 시 묻히는 부분이라 높이 표기·한계 검사(찰 3.0/메 2.0) 모두 이 기준. */ + height: number; + /** + * 바닥이 원지반에서 뜬 간격(m, 2026-08-22 사용자 확정). 손으로 안쪽에 당겨 높이 + * 한계로도 사면선에 못 닿을 때 바닥을 띄워 최소 노견을 확보한다 — 뜬 공간은 + * 별도 지지 구조물로 메운다(추가 예정). 0이면 지반 위(근입 0.5m). + */ + floatGapM: number; + /** 좌우 이동의 **하한**(m, 2026-09-07). 선반 길이가 음수가 되지 않게 x 는 최소 + * `1.2 × 상하 내림`까지 따라 나간다. 요청이 이 값보다 작으면 눌러도 벽이 안 움직임 — + * 그 까닭을 툴팁으로 알린다. */ + shiftFloorM?: number; + /** 재질(메/찰/콘크리트) — 높이 한계를 정한다(2026-08-22 사용자). */ + material: RevetMaterial; + /** 전면 기울기 1:n 의 n — 품셈 13-4-4 [주]⑪ 표준경사(`wallLeanRatio`, B08 과 한 벌 · 2026-09-14). */ + lean: number; + outward: number; + /** 합성 단면(하부 사다리꼴 + 상부 평행사변형) 꼭짓점 — 데이터 좌표. */ + points: OffsetPoint[]; + /** 상단 배면 꼭짓점 — 성토 사면선과의 접점(설계선 트림 경계). */ + topBack: OffsetPoint; + /** 이음선 상단점(평행사변형↔사다리꼴 상단 교차점) — 안 무늬 이음선 자리. */ + topJoint: OffsetPoint; + /** 성토 위 이음 — 윗면 사면 쪽 끝에서 0.1 m 안(371-1). 성토 사면선이 여기서 끝난다. */ + fillTop: OffsetPoint; + /** 하단선 양 끝 — **수평 기초**(2026-08-22 사용자 확정): 발자국 최저 지반(또는 관 + * invert) − 0.5m 표고의 수평선. 전면 발끝은 1:0.3 경사선을 하단까지 연장한 자리다. */ + bottomBack: OffsetPoint; + bottomFront: OffsetPoint; + /** 옹벽(구조물 `retaining_wall`) — 안 무늬에 물빼기 파이프 단면을 더함(73-2). */ + retaining?: boolean; +} + +/** + * 유입 집수정 단면 — 실무 도면(2026-08-20 사용자 제공 L형 확대본) 기준. + * · **I형** = 기슭막이와 같은 **평행사변형 벽 1매**만. + * · **ㄴ(L)형** = I형 + **바닥판**. + * · **ㄷ형** = ㄴ형 + I형 **반대측 막음벽**. + * 돌 표현은 재질 확정 시 반영 예정(성토부 기슭막이와 동일 규칙) — 지금은 윤곽만. + */ +export type BasinShape = "I" | "L" | "U"; + +export interface BasinLayout { + shape: BasinShape; + /** 부재별 폴리곤(벽·바닥·막음벽). 각각 분리 도형으로 그린다. */ + parts: Array<{ kind: "wall" | "floor"; points: OffsetPoint[] }>; + label: OffsetPoint; + /** 자동 전환 사유 — 툴팁·라벨에 쓴다(2026-08-22 재정의: 성토사면 ≤3m 기준). + * "cut" = 절토측 유입(성토사면 없음), "short" = 성토사면 3m 이하, + * "manual" = 규칙상 기슭막이 자리지만 사용자가 집수정을 선택. */ + reason: "cut" | "short" | "manual"; + /** 집수정이 원지반 안으로 들어갈 때의 절토선 점열(구조물 바깥 끝 → 복합경사 → 지반 교차점 · 91-4). */ + cutLine: OffsetPoint[] | null; + /** 구조물이 원지반 밖으로 나올 때의 성토(되메움)선 — 경사 5도(임시), 지반 교차점까지. */ + fillLine: { from: OffsetPoint; to: OffsetPoint } | null; +} + +/** 측점별 집수정 조정값. 위치값은 기본 배치 기준이며 +는 계류측/사면 아래 방향이다. */ +export interface BasinAdjust { + innerWidthM: number; + innerHeightM: number; + lateralM: number; + slopeM: number; +} + +export const DEFAULT_BASIN_ADJUST: BasinAdjust = { + innerWidthM: 1.0, + innerHeightM: 1.2, + lateralM: 0, + slopeM: 0, +}; + +/** 관 끝단을 자를 마감면 — 구조물(기슭막이·집수정)의 계류측 변을 **그대로 복사**한다. */ +export interface EndFace { + base: OffsetPoint; + direction: { offset: number; elevation: number }; +} + +/** 관 한쪽 끝단의 두 꼭짓점(마감면에서 자른 결과). */ +export interface PipeEnd { + bottom: OffsetPoint; + top: OffsetPoint; +} + +/** + * 유출측 **성토부선** 한 구간(2026-08-22 사용자 — 보호공 삭제, 윗면 선만 성토부선으로). + * 관 하단 꼭짓점(또는 앞 추가 벽 전면 상단)에서 1:1.2로 원지반(또는 다음 벽)까지. + */ +export interface OutletFillSegment { + points: OffsetPoint[]; + lengthM: number; + /** 구간 물매(1:n) — 벽 사이 구간은 높이 역산으로 정확히 1.2다. */ + ratio?: number; + /** 선 성격(2026-08-22 사용자 ②③): fill = 성토선(기본), cut = 0도/접속 절토선. */ + kind?: "fill" | "cut"; + /** 사면길이 5m 이상 — 기슭막이 의무 구간(별표2·성토_비탈면 §2). */ + overLimit: boolean; +} + +export interface CulvertLayout { + culvert: CulvertSet; + /** 원지반 — 도수로 · 돌붙임 길이가 사면보다 길면 이어 그림(294-4). */ + ground?: OffsetPoint[]; + pipe: { inlet: OffsetPoint; outlet: OffsetPoint; lengthM: number; slopePct: number }; + /** 관 축·법선 단위벡터(그리기가 두께 방향으로 밀 때 쓴다). */ + pipeAxis: { axis: OffsetPoint; normal: OffsetPoint }; + /** 관 양 끝단 꼭짓점 — 보호공 시작점이 유출 하단 꼭짓점을 그대로 쓴다. */ + pipeCorners: { inlet: PipeEnd; outlet: PipeEnd }; + walls: WallLayout[]; + /** 유출측 성토사면 — 경사길이(m)·법정 5m 충족 여부(별표2)·풀어낸 물매(1:n). */ + fillSlope: { + lengthM: number; + withinLimit: boolean; + /** 사면선이 벽 이음선 상단점을 지나도록 역산한 물매. 범위 밖이면 끝값으로 고정. */ + ratio: number; + /** 물매가 1.2~2.0 범위를 벗어났는지 — 도면 경고용. */ + ratioClamped: boolean; + /** 물매 1:1.2를 지키려고 늘린 **노폭 연장량**(m, 편측). 0이면 연장 없음. */ + roadWideningM: number; + /** + * 기슭막이만으로는 성토를 받을 수 없는 자리(성토고 3m↑인데 사면 5m·물매 1:1.2를 + * 동시에 만족하는 벽 자리가 없다) — **옹벽·석축 등 구조물 검토 대상** + * (`성토_비탈면.md` §2 "5m 초과 시 구조물 설치 의무"). + */ + structureRequired: boolean; + }; + /** 유출측 성토부선(보호공 대체 — 2026-08-22). 끝 구간이 5m 이상이면 추가 가능. */ + outletFill: { segments: OutletFillSegment[]; addable: boolean }; + /** 유입 집수정(ㄴ·ㄷ형) 계류측 성토부선 — 유출측과 같은 체계(2026-08-22 사용자). */ + basinFill: { segments: OutletFillSegment[]; addable: boolean }; + /** 집수정 계류측 다단 기슭막이 — 조정창 키 `bextra{n}`. */ + basinExtras: WallLayout[]; + /** 한계에 잘린 뒤의 **실제 적용된** 집수정 조작값 — 조정창이 되받는다. */ + basinAdjust: BasinAdjust; + /** 유입 기슭막이의 관 시작 접속선(2026-08-22 ②) — 위면 0도 성토선(지반 정지), + * 아래면 0도 1m 후 표준 절토 경사선. 집수정·접속 불필요면 null. */ + inletFill: OutletFillSegment | null; + /** 추가 기슭막이(배관 없는 벽 — 계단식). 조정창 키 = `extra{n}`. */ + extraWalls: WallLayout[]; + /** 유입 집수정 단면(있을 때만). */ + basin: BasinLayout | null; + designTrim: CulvertDesignTrim | null; + /** + * **실제 적용된** 기슭막이 이동량(m, + = 계류측 바깥). 요청값이 노견·사면 역전·사면 끝 + * 한계에 걸리면 잘려 들어온다 — 조정창은 이 값을 보여 주고 저장한다. 그러지 않으면 + * 눌러도 안 움직이는데 숫자만 계속 커진다(2026-08-21 사용자 지적). + */ + revetShift: { + inlet: WallAdjust; + outlet: WallAdjust; + extras: WallAdjust[]; + /** 집수정 계류측 다단의 적용 조작값. */ + basinExtras: WallAdjust[]; + }; + /** + * 조정창 드롭다운 선택지 가용성(2026-08-22 사용자 — 상황에 안 맞는 선택지 숨김): + * revetAllowed = 기슭막이+배관(배관이 원지반에 안 묻힐 때만), + * basinLUAllowed = 집수정 ㄴ·ㄷ형(구조물이 원지반으로 밀고 들어갈 때만). + */ + inletOptions: { revetAllowed: boolean; basinLUAllowed: boolean }; + /** 세월교 · 박스 흙막이(73-4) — 본체 끝 자리의 배관 4축 x(적용 x 의 0점). 본체 없으면 없음. */ + bodyX?: { inlet: number; outlet: number } | null; + /** 유입 · 유출 벽의 순수 높이(m) — 높이 빈 칸인 연동 옆 카드가 물려받음(152-2). */ + pureHeights?: { inlet: number; outlet: number }; +} +/** 유입측 구조물 사용자 선택 — auto = 규칙 기반 자동 선택. */ +export type InletStructureChoice = "auto" | "revet" | "I" | "L" | "U"; diff --git a/B06_Section/B06_Section_UI_Cross_Culvert_Wire.ts b/B06_Section/logic/B06_Section_Cross_Culvert_Wire.ts similarity index 98% rename from B06_Section/B06_Section_UI_Cross_Culvert_Wire.ts rename to B06_Section/logic/B06_Section_Cross_Culvert_Wire.ts index 7e1a8b209..b725818d2 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert_Wire.ts +++ b/B06_Section/logic/B06_Section_Cross_Culvert_Wire.ts @@ -1,27 +1,27 @@ /* ============================================================================= - * B06_Section_UI_Cross_Culvert_Wire.ts + * B06_Section_Cross_Culvert_Wire.ts * 횡단 카드의 배수관 세트 **계산 호출 + 이동량 되받기(토스트)** — Cross_View에서 * 분리(700줄 제한). 카드가 다시 그려질 때마다 한 번 불린다. * ========================================================================== */ -import { bodyAbsolute } from "./B06_Section_UI_Cross_Body_Guard"; -import type { CrossSection, SectionSample } from "./B06_Section_Api_Fetch"; -import { computeCulvertLayout } from "./B06_Section_UI_Cross_Culvert"; +import { bodyAbsolute } from "../B06_Section_UI_Cross_Body_Guard"; +import type { CrossSection, SectionSample } from "../B06_Section_Api_Fetch"; +import { computeCulvertLayout } from "./B06_Section_Cross_Culvert"; import type { CulvertLayout, InletStructureChoice, RevetKey, RevetPick, -} from "./B06_Section_UI_Cross_Culvert"; -import type { BasinAdjust, CulvertDesignTrim } from "./B06_Section_UI_Cross_Culvert_Types"; +} from "./B06_Section_Cross_Culvert"; +import type { BasinAdjust, CulvertDesignTrim } from "./B06_Section_Cross_Culvert_Types"; import { basinSpanOfSpec, FILL_SLOPE_RATIO_MIN, revetSpanOfSpec, -} from "./logic/B06_Section_UI_Cross_Culvert_Const"; -import { EXTRA_SPAN_DEFAULT } from "./logic/B06_Section_UI_Cross_Culvert_Const"; -import type { StructureSpan } from "./logic/B06_Section_UI_Cross_Culvert_Const"; -import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types"; +} from "./B06_Section_UI_Cross_Culvert_Const"; +import { EXTRA_SPAN_DEFAULT } from "./B06_Section_UI_Cross_Culvert_Const"; +import type { StructureSpan } from "./B06_Section_UI_Cross_Culvert_Const"; +import type { WallAdjust } from "./B06_Section_Cross_Culvert_Types"; import type { LocaleKey } from "@ui/ui_template_locale"; /** * 기슭막이 4축 조작 제어(2026-08-22 확정) — 벽을 눌러 고르고 조정창에서 diff --git a/B06_Section/B06_Section_UI_Cross_Ford_Geom.ts b/B06_Section/logic/B06_Section_Cross_Ford_Geom.ts similarity index 95% rename from B06_Section/B06_Section_UI_Cross_Ford_Geom.ts rename to B06_Section/logic/B06_Section_Cross_Ford_Geom.ts index dd456b134..0bc888f49 100644 --- a/B06_Section/B06_Section_UI_Cross_Ford_Geom.ts +++ b/B06_Section/logic/B06_Section_Cross_Ford_Geom.ts @@ -1,5 +1,5 @@ /* ============================================================================= - * B06_Section_UI_Cross_Ford_Geom.ts + * B06_Section_Cross_Ford_Geom.ts * 세월교 구체 **기하 계산** — 그리기(`_Cross_Ford.ts`)와 분리(700줄 제한). * 백엔드 `section.ford` 제원을 좌표로 옮긴다(치수 결정 금지). * @@ -19,19 +19,19 @@ * 성토측이라도 기슭막이 세트(다단·재질)와는 연계하지 않는다. * ========================================================================== */ -import { groundLine } from "./B06_Section_UI_Cross_Wall_Hatch"; -import type { CrossSection, FordSet, SectionSample } from "./B06_Section_Api_Fetch"; -import { GUARD_CUT_BERM_M, pipeWallThicknessM } from "./logic/B06_Section_UI_Cross_Culvert_Const"; +import { groundLine } from "../B06_Section_UI_Cross_Wall_Hatch"; +import type { CrossSection, FordSet, SectionSample } from "../B06_Section_Api_Fetch"; +import { GUARD_CUT_BERM_M, pipeWallThicknessM } from "./B06_Section_UI_Cross_Culvert_Const"; import type { BasinLayout, CulvertDesignTrim, OffsetPoint, OutletFillSegment, -} from "./B06_Section_UI_Cross_Culvert_Types"; -import { basinApproachPoints, buildBasin } from "./logic/B06_Section_UI_Cross_Culvert_Basin"; -import { bermCut, buildExtrasAt } from "./B06_Section_UI_Cross_Culvert_Extra"; -import { designInterpolator, groundInterpolator } from "./logic/B06_Section_UI_Cross_Culvert_Solve"; -import { structureCutSpec } from "./logic/B06_Section_UI_Cross_Structure_Cut"; +} from "./B06_Section_Cross_Culvert_Types"; +import { basinApproachPoints, buildBasin } from "./B06_Section_UI_Cross_Culvert_Basin"; +import { bermCut, buildExtrasAt } from "./B06_Section_Cross_Culvert_Extra"; +import { designInterpolator, groundInterpolator } from "./B06_Section_UI_Cross_Culvert_Solve"; +import { structureCutSpec } from "./B06_Section_UI_Cross_Structure_Cut"; /** 측벽 한 매의 조작값 — 좌우만(139-3). `heightM` · `slopeM` 은 옛 저장(측벽마다 높이 · 1:1.2 * 대각) 읽기 전용 — `dropM` 이 없을 때 바닥판 표고로 옮김(`fordDropOf`). */ diff --git a/B06_Section/logic/B06_Section_Cross_Ford_Pavement.ts b/B06_Section/logic/B06_Section_Cross_Ford_Pavement.ts new file mode 100644 index 000000000..e345d213c --- /dev/null +++ b/B06_Section/logic/B06_Section_Cross_Ford_Pavement.ts @@ -0,0 +1,118 @@ +/* ============================================================================= + * B06_Section_Cross_Ford_Pavement.ts + * 물넘이포장 기하 — 유입 쪽 · 노면 양 끝 · 월류 높이선 · 유출측 성토사면 선(287-2 · 294 · 315). + * 그리기는 `UI_Cross_Ford_Pavement.ts` · 3D 띠(B05)도 같은 선을 씀. + * ========================================================================== */ + +import type { CrossDesign } from "../B06_Section_Api_Fetch"; +import type { OffsetPoint } from "./B06_Section_Cross_Culvert_Types"; + +/** 백엔드 `section.ford_pavement` 제원(치수 결정은 서버 몫 — 여기서는 좌표만 만든다). */ +export interface FordPavementSpec { + /** 관 지점 누가거리 — 폭 절반 걸친 옆 카드도 같은 값. */ + chainage_m?: number; + span_m: number; + /** 월류 높이(m) — 노면 위 점선. 없으면 점선을 안 그림(수치를 지어내지 않는다). */ + depth_m: number | null; + /** 노면 경사(%) — 경사각도 환산 · 서버 설계 노면이 이 값으로 기욺. */ + slope_pct: number | null; + /** 정본에 안 적혀 기본값으로 그린 칸 「이름 값」 — 카드 머리에 「기본값(미확정)」. */ + defaulted?: string[]; + /** 도수로 · 돌붙임(`ford_chute_{n}_*` · 136-2) — 유출측 성토사면 띠 · 길이 = 사면 따라(286-3 · 비면 원지반까지). */ + chute?: { structure: string; thickness_m: number; length_m?: number }; +} + +/** 유입(상류)이 좌측(+offset)인가 — 기울인 노면이면 높은 노면 끝 · 아니면 설계선 양 끝 표고가 높은 + * 쪽(286-2) · 같으면 단면유형. */ +export function inflowLeft(design: CrossDesign): boolean { + const edges = design.road_edges; + if (design.tilted && edges && Math.abs(edges.left.elevation_m - edges.right.elevation_m) > 1e-6) + return edges.left.elevation_m > edges.right.elevation_m; + const line = [...(design.design_line ?? [])].sort((a, b) => a.offset_m - b.offset_m); + const gap = line.length >= 2 ? line[line.length - 1].elevation_m - line[0].elevation_m : 0; + return Math.abs(gap) > 1e-3 ? gap > 0 : design.section_mode !== "right_cut"; +} + +/** 콘크리트 · 노면 양 끝 — 낮은 쪽을 늘였으면(294-3 `tilt_reach`) 그 쪽은 원지반과 만난 자리까지. */ +export function fordEdges(design: CrossDesign): CrossDesign["road_edges"] | undefined { + const edges = design.road_edges; + const reach = design.tilt_reach; + if (!edges || !reach) return edges; + const end = { offset_m: reach.offset_m, elevation_m: reach.elevation_m }; + return reach.side === "left" ? { ...edges, left: end } : { ...edges, right: end }; +} + +/** + * 월류 높이선(315-2) — 노면 양 끝 위 월류 높이 · 노면과 같은 각도. 성토 쪽(노면 끝 밖 설계선이 내려감)은 + * 성토 사면선을 노면 끝 너머 위 · 안쪽으로 늘여 월류 높이선과 만나는 자리에서 끝 → 노면 끝까지 그 연장선. + * 절토 쪽 · 늘인 노면 끝(294-3)은 노면 끝 위 그대로. 좌(+offset) → 우 차례. + */ +export function overflowLine(design: CrossDesign, depth: number): OffsetPoint[] { + const edges = fordEdges(design); + if (!edges) return []; + const { left, right } = edges; + const span = Math.abs(left.offset_m - right.offset_m); + const side = (edge: typeof left, other: typeof left): OffsetPoint[] => { + const top = { offset: edge.offset_m, elevation: edge.elevation_m + depth }; + const outward = Math.sign(edge.offset_m - other.offset_m) || 1; + const next = (design.design_line ?? []) + .filter((p) => (p.offset_m - edge.offset_m) * outward > 1e-6) + .sort((a, b) => (a.offset_m - b.offset_m) * outward)[0]; + const cut = edge.offset_m === design.tilt_reach?.offset_m; // 늘인 끝 = 원지반(294-3 · 절토) + if (cut || !next || next.elevation_m >= edge.elevation_m - 1e-6 || span < 1e-9) return [top]; + // 안쪽 d m 에서 성토선 = edge + fill·d · 월류선 = edge + depth + road·d → d = depth / (fill − road) + const fill = (edge.elevation_m - next.elevation_m) / Math.abs(next.offset_m - edge.offset_m); + const road = (other.elevation_m - edge.elevation_m) / span; + if (fill <= road + 1e-9) return [top]; + const d = Math.min(depth / (fill - road), span); + const meet = { offset: edge.offset_m - outward * d, elevation: edge.elevation_m + fill * d }; + return [{ offset: edge.offset_m, elevation: edge.elevation_m }, meet]; + }; + return [...side(left, right), ...side(right, left).reverse()]; +} + +/** 원지반 꺾은선의 그 오프셋 표고(밖은 끝값). */ +function groundZ(ground: readonly OffsetPoint[], offset: number): number | null { + if (!ground.length) return null; + if (offset <= ground[0].offset) return ground[0].elevation; + for (let i = 1; i < ground.length; i += 1) { + const [a, b] = [ground[i - 1], ground[i]]; + if (offset > b.offset) continue; + const span = b.offset - a.offset; + return span > 1e-9 + ? a.elevation + ((b.elevation - a.elevation) * (offset - a.offset)) / span + : b.elevation; + } + return ground[ground.length - 1].elevation; +} + +/** + * 유출측(낮은 쪽) 성토사면 선(136-2 · 294-4) — 낮은 쪽 끝단(늘인 노면 끝 · 아니면 유출 쪽 노견 끝)에서 + * 설계선을 바깥으로 따라 내려가는 동안만(오르면 절토라 멈춤) · 원지반(`ground`)에 닿으면 끝. + * 성토가 없으면(끝단이 이미 원지반 · 양측 절토 물넘이) 점 하나 = 띠 없음. + */ +export function outletFillLine( + design: CrossDesign, + ground?: readonly OffsetPoint[], +): OffsetPoint[] { + const edges = fordEdges(design); + if (!edges || !design.design_line?.length) return []; + const reach = design.tilt_reach; + const outLeft = reach ? reach.side === "left" : !inflowLeft(design); // +offset = 좌측 + const outward = outLeft ? 1 : -1; + const edge = outLeft + ? Math.max(edges.left.offset_m, edges.right.offset_m) + : Math.min(edges.left.offset_m, edges.right.offset_m); + const points = design.design_line + .filter((p) => (p.offset_m - edge) * outward >= -1e-6) + .sort((a, b) => (a.offset_m - b.offset_m) * outward); + const line: OffsetPoint[] = []; + for (const p of points) { + const last = line[line.length - 1]; + if (last && p.elevation_m > last.elevation + 1e-6) break; + line.push({ offset: p.offset_m, elevation: p.elevation_m }); + const z = ground ? groundZ(ground, p.offset_m) : null; + if (z !== null && Math.abs(z - p.elevation_m) < 1e-3) break; // 원지반에 닿음 = 끝 + } + return line; +} diff --git a/B06_Section/logic/B06_Section_UI_Cross_Culvert_Basin.ts b/B06_Section/logic/B06_Section_UI_Cross_Culvert_Basin.ts index 0bf390a13..1abdaa324 100644 --- a/B06_Section/logic/B06_Section_UI_Cross_Culvert_Basin.ts +++ b/B06_Section/logic/B06_Section_UI_Cross_Culvert_Basin.ts @@ -33,7 +33,7 @@ import type { EndFace, OffsetPoint, InletStructureChoice, -} from "../B06_Section_UI_Cross_Culvert_Types"; +} from "./B06_Section_Cross_Culvert_Types"; import { structureCutPoints, structureCutSpec } from "./B06_Section_UI_Cross_Structure_Cut"; /** 자동(기본) 집수정 모양(54-36) — 좌측 폼 「(기본)」 표시도 이 값. */ 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 1c04150e9..e29972def 100644 --- a/B06_Section/logic/B06_Section_UI_Cross_Culvert_Const.ts +++ b/B06_Section/logic/B06_Section_UI_Cross_Culvert_Const.ts @@ -5,7 +5,7 @@ * ========================================================================== */ import type { CrossSection, CulvertSideSpec } from "../B06_Section_Api_Fetch"; -import type { SpanRole, SpanValues } from "../B06_Section_UI_Cross_Culvert_Wire"; +import type { SpanRole, SpanValues } from "./B06_Section_Cross_Culvert_Wire"; /** 관 위 최소 토피(m) — 프론트 정의처(백엔드는 `B06_Section_Engine_Culvert.MIN_PIPE_COVER_M`). * ⚠ 임도 배수관 토피 규정이 지식DB에 없어 별표2 교량·암거 "복토 50㎝ 이상"을 교차 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 1827b9473..1c20c58dd 100644 --- a/B06_Section/logic/B06_Section_UI_Cross_Culvert_Solve.ts +++ b/B06_Section/logic/B06_Section_UI_Cross_Culvert_Solve.ts @@ -13,12 +13,7 @@ import { REVET_THICKNESS_M, REVET_FILL_TOP_M, } from "./B06_Section_UI_Cross_Culvert_Const"; -import type { - EndFace, - OffsetPoint, - PipeEnd, - WallAdjust, -} from "../B06_Section_UI_Cross_Culvert_Types"; +import type { EndFace, OffsetPoint, PipeEnd, WallAdjust } from "./B06_Section_Cross_Culvert_Types"; /** 기슭막이 하단선 폭(m) — 사다리꼴 밑변. 자리 기준(하단선 중점) 환산에 쓴다. * `lean` = 그 벽의 전면 기울기(표준경사 · `revetWallSpec().lean`). */ diff --git a/B06_Section/logic/B06_Section_UI_Cross_Structure_Cut.ts b/B06_Section/logic/B06_Section_UI_Cross_Structure_Cut.ts index e23394541..183bdacc2 100644 --- a/B06_Section/logic/B06_Section_UI_Cross_Structure_Cut.ts +++ b/B06_Section/logic/B06_Section_UI_Cross_Structure_Cut.ts @@ -11,7 +11,7 @@ import { cutProfilePoints } from "../../common_util/common_util_cross_berm"; import type { BermSpec } from "../../common_util/common_util_cross_berm"; import type { CrossDesign } from "../B06_Section_Api_Fetch"; -import type { OffsetPoint } from "../B06_Section_UI_Cross_Culvert_Types"; +import type { OffsetPoint } from "./B06_Section_Cross_Culvert_Types"; /** 절토 제원 — 암(또는 단일) 경사 · 토사 경사 · 암 경계 오프셋(없으면 경사 하나) · 소단. */ export interface StructureCutSpec { diff --git a/B06_Section/logic/B06_Section_UI_Page_Patches.ts b/B06_Section/logic/B06_Section_UI_Page_Patches.ts index 28a5ffba6..f7a54fe8a 100644 --- a/B06_Section/logic/B06_Section_UI_Page_Patches.ts +++ b/B06_Section/logic/B06_Section_UI_Page_Patches.ts @@ -8,10 +8,10 @@ * ========================================================================== */ import type { CrossSectionPatch, StoredWallAdjust, StoredWallSpan } from "../B06_Section_Api_Fetch"; -import type { BasinAdjust } from "../B06_Section_UI_Cross_Culvert_Types"; -import type { FordAdjust } from "../B06_Section_UI_Cross_Ford"; -import type { BoxAdjust } from "../B06_Section_UI_Cross_Box"; -import type { InletStructureChoice } from "../B06_Section_UI_Cross_Culvert"; +import type { BasinAdjust } from "./B06_Section_Cross_Culvert_Types"; +import type { FordAdjust } from "./B06_Section_Cross_Ford_Geom"; +import type { BoxAdjust } from "./B06_Section_Cross_Box_Geom"; +import type { InletStructureChoice } from "./B06_Section_Cross_Culvert"; export interface CrossPatchSources { /** 암 경계선 오프셋(누가거리 문자열 키). */ diff --git a/resources/tester/helper_118_pipe_pick.cjs b/resources/tester/helper_118_pipe_pick.cjs index 6b38ffe6a..addb2be25 100644 --- a/resources/tester/helper_118_pipe_pick.cjs +++ b/resources/tester/helper_118_pipe_pick.cjs @@ -122,7 +122,7 @@ const resolve = (base) => const B06 = (f) => path.join(ROOT, "B06_Section", `${f}.ts`); const { computeStoredLayouts } = loadTs(B06("B06_Section_Structure_Layouts")); const { appendCulvertOverlay } = loadTs(B06("B06_Section_UI_Cross_Culvert")); -const { wallStandsAt } = loadTs(B06("B06_Section_UI_Cross_Culvert_Wire")); +const { wallStandsAt } = loadTs(B06("logic/B06_Section_Cross_Culvert_Wire")); const { applyStructurePick } = loadTs(B06("B06_Section_UI_Page_Structure_Pick")); const fixture = JSON.parse( diff --git a/resources/tester/helper_135_3_chute_solid.cjs b/resources/tester/helper_135_3_chute_solid.cjs index 5cb934254..84e8c90d2 100644 --- a/resources/tester/helper_135_3_chute_solid.cjs +++ b/resources/tester/helper_135_3_chute_solid.cjs @@ -33,9 +33,9 @@ const { buildCorridorStructures } = loadTs(B05("B05_Profile_UI_Corridor_Structur const { structureHashParts } = loadTs(B05("logic/B05_Profile_UI_Corridor_Structures_Hash")); const { structureTopLoops } = loadTs(B05("logic/B05_Profile_UI_Corridor_Tin_Design")); const { chuteBandOutline, isChuteSegment } = loadTs(B06("B06_Section_UI_Cross_Wall_Hatch")); -const { computeBoxLayout } = loadTs(B06("B06_Section_UI_Cross_Box_Geom")); +const { computeBoxLayout } = loadTs(B06("logic/B06_Section_Cross_Box_Geom")); const { computeFordLayout } = loadTs(B06("B06_Section_UI_Cross_Ford")); -const { outletFillLine } = loadTs(B06("B06_Section_UI_Cross_Ford_Pavement")); +const { outletFillLine } = loadTs(B06("logic/B06_Section_Cross_Ford_Pavement")); const fixture = JSON.parse( fs.readFileSync(path.join(__dirname, "fixtures", "b06_73_4_body_guard_sections.json"), "utf8"), diff --git a/resources/tester/helper_139_2_link_freeze.cjs b/resources/tester/helper_139_2_link_freeze.cjs index 7a820194a..6c36377bf 100644 --- a/resources/tester/helper_139_2_link_freeze.cjs +++ b/resources/tester/helper_139_2_link_freeze.cjs @@ -27,7 +27,9 @@ const resolve = (base) => const B06 = (f) => path.join(ROOT, "B06_Section", f); const { freezeLinkedWalls } = loadTs(B06("B06_Section_UI_Page_Link_Session.ts")); -const { computeCardCulvert, culvertLinkFor } = loadTs(B06("B06_Section_UI_Cross_Culvert_Wire.ts")); +const { computeCardCulvert, culvertLinkFor } = loadTs( + B06("logic/B06_Section_Cross_Culvert_Wire.ts"), +); const sections = JSON.parse( fs.readFileSync(path.join(__dirname, "fixtures", "b06_73_4_body_guard_sections.json"), "utf8"), diff --git a/resources/tester/helper_139_3_ford_level.cjs b/resources/tester/helper_139_3_ford_level.cjs index e17cfb285..d87feebfb 100644 --- a/resources/tester/helper_139_3_ford_level.cjs +++ b/resources/tester/helper_139_3_ford_level.cjs @@ -19,7 +19,7 @@ const { loadTs } = makeLoader({ }); const { computeFordLayout, fordAdjustOf } = loadTs( - path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Ford_Geom.ts"), + path.join(ROOT, "B06_Section", "logic/B06_Section_Cross_Ford_Geom.ts"), ); const sections = JSON.parse( fs.readFileSync(path.join(__dirname, "fixtures", "b06_73_4_body_guard_sections.json"), "utf8"), diff --git a/resources/tester/helper_139_6_pipe_fit.cjs b/resources/tester/helper_139_6_pipe_fit.cjs index e340f2916..f01bcaf55 100644 --- a/resources/tester/helper_139_6_pipe_fit.cjs +++ b/resources/tester/helper_139_6_pipe_fit.cjs @@ -30,7 +30,7 @@ const { computeCrossDesign } = loadTs( path.join(ROOT, "common_util", "common_util_cross_design.ts"), ); const { computeCulvertLayout, REVET_EMBED_DEPTH_M } = loadTs( - path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Culvert_Geom.ts"), + path.join(ROOT, "B06_Section", "logic/B06_Section_Cross_Culvert_Geom.ts"), ); const { cutLength } = loadTs( path.join(ROOT, "B06_Section", "logic", "B06_Section_UI_Cross_Culvert_Solve.ts"), diff --git a/resources/tester/helper_186_1_outlet_fold.cjs b/resources/tester/helper_186_1_outlet_fold.cjs index 0b754a8b7..efbc5ddd0 100644 --- a/resources/tester/helper_186_1_outlet_fold.cjs +++ b/resources/tester/helper_186_1_outlet_fold.cjs @@ -31,7 +31,7 @@ const resolve = (base) => const B06 = (f) => path.join(ROOT, "B06_Section", f); const { computeStoredLayouts, structureAreaRows } = loadTs(B06("B06_Section_Structure_Layouts.ts")); -const { computeCulvertLayout } = loadTs(B06("B06_Section_UI_Cross_Culvert_Geom.ts")); +const { computeCulvertLayout } = loadTs(B06("logic/B06_Section_Cross_Culvert_Geom.ts")); const read = (name) => JSON.parse(fs.readFileSync(path.join(__dirname, "fixtures", name), "utf8")).cross_sections; const clone = (v) => JSON.parse(JSON.stringify(v)); diff --git a/resources/tester/helper_190_1_basin_pick.cjs b/resources/tester/helper_190_1_basin_pick.cjs index 2206c2fae..97af1a297 100644 --- a/resources/tester/helper_190_1_basin_pick.cjs +++ b/resources/tester/helper_190_1_basin_pick.cjs @@ -82,7 +82,7 @@ const resolve = (base) => const B06 = (f) => path.join(ROOT, "B06_Section", `${f}.ts`); const { computeStoredLayouts } = loadTs(B06("B06_Section_Structure_Layouts")); const { appendCulvertOverlay } = loadTs(B06("B06_Section_UI_Cross_Culvert")); -const { outlineBox } = loadTs(B06("B06_Section_UI_Cross_Culvert_Types")); +const { outlineBox } = loadTs(B06("logic/B06_Section_Cross_Culvert_Types")); const fixture = JSON.parse( fs.readFileSync(path.join(__dirname, "fixtures", "b07_cross_overlay_sections.json"), "utf8"), diff --git a/resources/tester/helper_225_1_pipe_round.cjs b/resources/tester/helper_225_1_pipe_round.cjs index 92f1b33fb..161a7f0a4 100644 --- a/resources/tester/helper_225_1_pipe_round.cjs +++ b/resources/tester/helper_225_1_pipe_round.cjs @@ -62,7 +62,7 @@ const { loadTs } = makeLoader({ }); const B06 = (name) => loadTs(path.join(ROOT, "B06_Section", `${name}.ts`)); -const { computeCulvertLayout } = B06("B06_Section_UI_Cross_Culvert_Geom"); +const { computeCulvertLayout } = B06("logic/B06_Section_Cross_Culvert_Geom"); const { cutLength } = B06("logic/B06_Section_UI_Cross_Culvert_Solve"); const { pipeLengthText, pipeLengthWhole } = B06("logic/B06_Section_UI_Cross_Culvert_Const"); const { appendPipeInfoText } = B06("B06_Section_UI_Cross_Culvert_Types"); diff --git a/resources/tester/helper_271_detail_notify.cjs b/resources/tester/helper_271_detail_notify.cjs index d84bc67eb..3650fa4a1 100644 --- a/resources/tester/helper_271_detail_notify.cjs +++ b/resources/tester/helper_271_detail_notify.cjs @@ -115,9 +115,9 @@ const STUBS = { "B06_Section_Cross_Design_Session.ts": anyModule(), "common_util_cross_design.ts": anyModule(), "B06_Section_Structure_Layouts.ts": anyModule({ computeStoredLayouts: () => null }), - "B06_Section_UI_Cross_Culvert_Wire.ts": anyModule(), - "B06_Section_UI_Cross_Ford_Geom.ts": anyModule(), - "B06_Section_UI_Cross_Box_Geom.ts": anyModule(), + "B06_Section_Cross_Culvert_Wire.ts": anyModule(), + "B06_Section_Cross_Ford_Geom.ts": anyModule(), + "B06_Section_Cross_Box_Geom.ts": anyModule(), "B06_Section_UI_Cross_View.ts": anyModule(), "B06_Section_UI_Section_Common.ts": anyModule({ designElevationAt: () => undefined }), "B06_Section_Display_Width.ts": anyModule({ displayHalfWidth: () => 10 }), diff --git a/resources/tester/helper_286_1_ford_pavement.cjs b/resources/tester/helper_286_1_ford_pavement.cjs index af326b295..af0df99f1 100644 --- a/resources/tester/helper_286_1_ford_pavement.cjs +++ b/resources/tester/helper_286_1_ford_pavement.cjs @@ -29,6 +29,9 @@ const ford = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Ford_Pa { "./logic/B06_Section_UI_Cross_Culvert_Const": {} }, ), "./B06_Section_UI_Cross_Design_Surface": { appendPavementOverlay() {} }, + "./logic/B06_Section_Cross_Ford_Pavement": loadTs( + path.join(ROOT, "B06_Section", "logic", "B06_Section_Cross_Ford_Pavement.ts"), + ), // 관 신원표(323-5 · 472-2 에서 common_util 로) — 진짜 · 장부 안 꽂음 = 옛 자리 이름 "../common_util/common_util_pick_tag": loadTs( path.join(ROOT, "common_util", "common_util_pick_tag.ts"), diff --git a/resources/tester/helper_287_ford_pavement.cjs b/resources/tester/helper_287_ford_pavement.cjs index e0c0faf0b..95720a713 100644 --- a/resources/tester/helper_287_ford_pavement.cjs +++ b/resources/tester/helper_287_ford_pavement.cjs @@ -73,6 +73,7 @@ class Node { } globalThis.document = { createElementNS: (_ns, tag) => new Node(tag) }; const pave = load(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Ford_Pavement.ts")); +const paveGeo = load(path.join(ROOT, "B06_Section", "logic", "B06_Section_Cross_Ford_Pavement.ts")); // 서버가 지대 낮은 쪽(우)으로 5 % 기울인 노면 · 노견 끝 ±3 · 차도 끝 ±2.5 const tilted = { section_mode: "left_cut", @@ -244,7 +245,7 @@ const out = { drew, pavePoints: ofClass("b06-chart__ford-pavement").map((k) => k.attrs.points), planPoints: ofClass("b06-chart__ford-deck-plan").map((k) => k.attrs.points), - inflowLeft: pave.inflowLeft(tilted), + inflowLeft: paveGeo.inflowLeft(tilted), // 287-3 월류 폭 → 높이 = 세월교와 같은 풀이(유량 1.2 ㎥/s · 폭 10 m) · 유량 없으면 null(손 입력) heightByWidth: calc.fordHeightM(1.2, 10), heightNoFlow: calc.fordHeightM(null, 10), diff --git a/resources/tester/helper_294_3_ford_extend_chute.cjs b/resources/tester/helper_294_3_ford_extend_chute.cjs index d4a04dc54..532c595be 100644 --- a/resources/tester/helper_294_3_ford_extend_chute.cjs +++ b/resources/tester/helper_294_3_ford_extend_chute.cjs @@ -44,6 +44,7 @@ globalThis.document = { createElementNS: (_ns, tag) => new Node(tag) }; const input = JSON.parse(fs.readFileSync(process.argv[2], "utf8")); const pave = load(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Ford_Pavement.ts")); +const paveGeo = load(path.join(ROOT, "B06_Section", "logic", "B06_Section_Cross_Ford_Pavement.ts")); const design = input.design; // 서버가 낸 양측 절토 · 낮은 쪽 연장 설계 const svg = new Node("g"); pave.appendFordPavementOverlay( @@ -70,18 +71,18 @@ const fordPave = load( ); const out = { - edges: pave.fordEdges(design), + edges: paveGeo.fordEdges(design), pavePoints: paveLayer?.attrs.points ?? null, runNone: pts(hatch.chuteRun(slope)), runShort: pts(hatch.chuteRun(slope, 2)), runLong: pts(hatch.chuteRun(slope, 7, flat)), runPointOnly: pts(hatch.chuteRun([{ points: [P(3, -4)] }])), runPointLength: pts(hatch.chuteRun([{ points: [P(3, -4)] }], 2, flat)), - dipLine: pave.outletFillLine(design, dipGround), + dipLine: paveGeo.outletFillLine(design, dipGround), dipChute: pave.fordChuteLine(design, {}, dipGround), dipChuteLength: pave.fordChuteLine(design, { length_m: 1.5 }, dipGround), - fillLine: pave.outletFillLine(input.fillDesign, fillGround), - fillLineOld: pave.outletFillLine(input.fillDesign), + fillLine: paveGeo.outletFillLine(input.fillDesign, fillGround), + fillLineOld: paveGeo.outletFillLine(input.fillDesign), fillChuteLength: pave.fordChuteLine(input.fillDesign, { length_m: 1 }, fillGround), chuteItems: guard.CHUTE_ITEMS.map((i) => i.id), fordPaveChuteSame: diff --git a/resources/tester/helper_315_ford_chute_overflow.cjs b/resources/tester/helper_315_ford_chute_overflow.cjs index f32a65852..ade0c7b0a 100644 --- a/resources/tester/helper_315_ford_chute_overflow.cjs +++ b/resources/tester/helper_315_ford_chute_overflow.cjs @@ -44,6 +44,7 @@ globalThis.document = { createElementNS: (_ns, tag) => new Node(tag) }; const input = JSON.parse(fs.readFileSync(process.argv[2], "utf8")); const pave = load(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Ford_Pavement.ts")); +const paveGeo = load(path.join(ROOT, "B06_Section", "logic", "B06_Section_Cross_Ford_Pavement.ts")); const hatch = load(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Wall_Hatch.ts")); const fillGround = hatch.groundLine(input.fillSamples); const dipGround = hatch.groundLine(input.dipSamples); @@ -73,10 +74,10 @@ const out = { fillLong: chute(input.fillDesign, fillGround, 6), dipNone: chute(input.dipDesign, dipGround), dipLong: chute(input.dipDesign, dipGround, 2), - fillOverflow: pave.overflowLine(input.fillDesign, 0.3), - dipOverflow: pave.overflowLine(input.dipDesign, 0.3), - fillEdges: pave.fordEdges(input.fillDesign), - dipEdges: pave.fordEdges(input.dipDesign), + fillOverflow: paveGeo.overflowLine(input.fillDesign, 0.3), + dipOverflow: paveGeo.overflowLine(input.dipDesign, 0.3), + fillEdges: paveGeo.fordEdges(input.fillDesign), + dipEdges: paveGeo.fordEdges(input.dipDesign), deckPoints: deck?.attrs.points ?? null, bandPoints: band?.attrs.points ?? null, }; diff --git a/resources/tester/helper_371_2_pipe_slope.cjs b/resources/tester/helper_371_2_pipe_slope.cjs index fd4d9d855..d9d34e441 100644 --- a/resources/tester/helper_371_2_pipe_slope.cjs +++ b/resources/tester/helper_371_2_pipe_slope.cjs @@ -62,7 +62,7 @@ const { loadTs } = makeLoader({ }); const { computeCulvertLayout } = loadTs( - path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Culvert_Geom.ts"), + path.join(ROOT, "B06_Section", "logic/B06_Section_Cross_Culvert_Geom.ts"), ); const { appendCulvertOverlay } = loadTs( path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Culvert.ts"), diff --git a/resources/tester/helper_387_4b_pipe_options.cjs b/resources/tester/helper_387_4b_pipe_options.cjs index af8cf6a80..e00142047 100644 --- a/resources/tester/helper_387_4b_pipe_options.cjs +++ b/resources/tester/helper_387_4b_pipe_options.cjs @@ -46,7 +46,7 @@ const B06 = (f) => path.join(ROOT, "B06_Section", `${f}.ts`); const { structureAreaRows, computeStoredLayouts } = loadTs(B06("B06_Section_Structure_Layouts")); const { layPipeOptions } = loadTs(B06("B06_Section_Section_Adjusts")); const { applyPipeOptionsToCache } = loadTs(B06("B06_Section_UI_Page_Pipe_Options")); -const { ZERO_ADJUST } = loadTs(B06("B06_Section_UI_Cross_Culvert_Types")); +const { ZERO_ADJUST } = loadTs(B06("logic/B06_Section_Cross_Culvert_Types")); const fresh = () => JSON.parse(fs.readFileSync(path.join(__dirname, "fixtures", "b06_186_1_pipe_292.json"), "utf8")) diff --git a/resources/tester/helper_387_8_folded_profile.cjs b/resources/tester/helper_387_8_folded_profile.cjs index 061435904..09240b930 100644 --- a/resources/tester/helper_387_8_folded_profile.cjs +++ b/resources/tester/helper_387_8_folded_profile.cjs @@ -87,9 +87,9 @@ const STUBS = { "B06_Section_UI_Standard_Panel.ts": anyModule(), "B06_Section_Cross_Design_Session.ts": anyModule(), "common_util_cross_design.ts": anyModule(), - "B06_Section_UI_Cross_Culvert_Wire.ts": anyModule(), - "B06_Section_UI_Cross_Ford_Geom.ts": anyModule(), - "B06_Section_UI_Cross_Box_Geom.ts": anyModule(), + "B06_Section_Cross_Culvert_Wire.ts": anyModule(), + "B06_Section_Cross_Ford_Geom.ts": anyModule(), + "B06_Section_Cross_Box_Geom.ts": anyModule(), "B06_Section_UI_IrregularStations.ts": anyModule({ PIPE_DISPLAY_NAME: {} }), "B05_Profile_UI_IrregularStations.ts": anyModule({ PIPE_DISPLAY_NAME: {} }), "config_frontend.ts": anyModule({ PIPE_TYPES: [] }), diff --git a/resources/tester/helper_416_page_cache.cjs b/resources/tester/helper_416_page_cache.cjs index 4f1881d64..d4b88a998 100644 --- a/resources/tester/helper_416_page_cache.cjs +++ b/resources/tester/helper_416_page_cache.cjs @@ -82,9 +82,9 @@ const STUBS = { "B06_Section_Cross_Design_Session.ts": anyModule(), "common_util_cross_design.ts": anyModule(), "B06_Section_Structure_Layouts.ts": anyModule({ computeStoredLayouts: () => null }), - "B06_Section_UI_Cross_Culvert_Wire.ts": anyModule(), - "B06_Section_UI_Cross_Ford_Geom.ts": anyModule(), - "B06_Section_UI_Cross_Box_Geom.ts": anyModule(), + "B06_Section_Cross_Culvert_Wire.ts": anyModule(), + "B06_Section_Cross_Ford_Geom.ts": anyModule(), + "B06_Section_Cross_Box_Geom.ts": anyModule(), "B06_Section_UI_Cross_View.ts": anyModule(), "B06_Section_UI_Section_Common.ts": anyModule({ designElevationAt: () => undefined }), "B06_Section_Display_Width.ts": anyModule({ displayHalfWidth: () => 10 }), diff --git a/resources/tester/helper_445_save_patch.cjs b/resources/tester/helper_445_save_patch.cjs index 6ebdeb7f5..4b7840c89 100644 --- a/resources/tester/helper_445_save_patch.cjs +++ b/resources/tester/helper_445_save_patch.cjs @@ -71,7 +71,7 @@ const load = makeLoader( B06("B06_Section_UI_Page_Station_Controls"), B06("B06_Section_UI_Page_Link_Session"), B06("B06_Section_Session_Cells"), // 조정창 칸 창구(472-5 5-D · 메모리 맵 없음) - B06("B06_Section_UI_Cross_Culvert_Types"), + B06("logic/B06_Section_Cross_Culvert_Types"), B06("logic/B06_Section_Station_Swap"), B06("logic/B06_Section_UI_Page_Patches"), B06("logic/B06_Section_Design_Keys"), // 칸 목록 · 얹기 · patch 꼴 한 벌(472-6) diff --git a/resources/tester/helper_451_refresh_hold.cjs b/resources/tester/helper_451_refresh_hold.cjs index 0e5168b9c..0baf96b6c 100644 --- a/resources/tester/helper_451_refresh_hold.cjs +++ b/resources/tester/helper_451_refresh_hold.cjs @@ -71,9 +71,9 @@ const STUBS = { "B06_Section_Cross_Design_Session.ts": anyModule({ crossDesignChoices: () => new Map() }), "B06_Section_Section_Adjusts.ts": anyModule({ withPipeOptions: async () => undefined }), "common_util_cross_design.ts": anyModule(), - "B06_Section_UI_Cross_Culvert_Wire.ts": anyModule(), - "B06_Section_UI_Cross_Ford_Geom.ts": anyModule(), - "B06_Section_UI_Cross_Box_Geom.ts": anyModule(), + "B06_Section_Cross_Culvert_Wire.ts": anyModule(), + "B06_Section_Cross_Ford_Geom.ts": anyModule(), + "B06_Section_Cross_Box_Geom.ts": anyModule(), "B06_Section_UI_IrregularStations.ts": anyModule({ PIPE_DISPLAY_NAME: {} }), "B05_Profile_UI_IrregularStations.ts": anyModule({ PIPE_DISPLAY_NAME: {} }), "config_frontend.ts": anyModule({ PIPE_TYPES: [] }), diff --git a/resources/tester/helper_451_session_canon.cjs b/resources/tester/helper_451_session_canon.cjs index 3a11f3709..16cb7e1c5 100644 --- a/resources/tester/helper_451_session_canon.cjs +++ b/resources/tester/helper_451_session_canon.cjs @@ -69,9 +69,9 @@ const STUBS = { "B06_Section_Cross_Design_Session.ts": anyModule({ crossDesignChoices: () => new Map() }), "B06_Section_Section_Adjusts.ts": anyModule({ withPipeOptions: async () => undefined }), "common_util_cross_design.ts": anyModule(), - "B06_Section_UI_Cross_Culvert_Wire.ts": anyModule(), - "B06_Section_UI_Cross_Ford_Geom.ts": anyModule(), - "B06_Section_UI_Cross_Box_Geom.ts": anyModule(), + "B06_Section_Cross_Culvert_Wire.ts": anyModule(), + "B06_Section_Cross_Ford_Geom.ts": anyModule(), + "B06_Section_Cross_Box_Geom.ts": anyModule(), "B06_Section_UI_IrregularStations.ts": anyModule({ PIPE_DISPLAY_NAME: {} }), "B05_Profile_UI_IrregularStations.ts": anyModule({ PIPE_DISPLAY_NAME: {} }), "config_frontend.ts": anyModule({ PIPE_TYPES: [] }), diff --git a/resources/tester/helper_79_guard_shape.cjs b/resources/tester/helper_79_guard_shape.cjs index b0b53c880..d2d283790 100644 --- a/resources/tester/helper_79_guard_shape.cjs +++ b/resources/tester/helper_79_guard_shape.cjs @@ -31,8 +31,8 @@ const resolve = (base) => const B06 = (f) => path.join(ROOT, "B06_Section", f); const { computeStoredLayouts, structureAreaRows } = loadTs(B06("B06_Section_Structure_Layouts.ts")); const { bodyGuardBase } = loadTs(B06("B06_Section_UI_Cross_Body_Guard.ts")); -const { computeBoxLayout } = loadTs(B06("B06_Section_UI_Cross_Box_Geom.ts")); -const { computeCulvertLayout } = loadTs(B06("B06_Section_UI_Cross_Culvert_Geom.ts")); +const { computeBoxLayout } = loadTs(B06("logic/B06_Section_Cross_Box_Geom.ts")); +const { computeCulvertLayout } = loadTs(B06("logic/B06_Section_Cross_Culvert_Geom.ts")); const { computeRevetmentLayout } = loadTs(B06("B06_Section_UI_Cross_Revetment.ts")); const fixture = JSON.parse( diff --git a/resources/tester/helper_91_ground_free.cjs b/resources/tester/helper_91_ground_free.cjs index ae12a5ccf..7d8e959b6 100644 --- a/resources/tester/helper_91_ground_free.cjs +++ b/resources/tester/helper_91_ground_free.cjs @@ -80,7 +80,7 @@ out.fordHandBedDown = fordFloor((s) => { shiftGround(s, -2, -2, 2); }); // ② 91-3 다단 자동 자리 — 관 180 유출 흙막이 + 2단 · 원지반을 옮겨도 단 자리 같음 · 사면 1.0 m -const { computeCulvertLayout } = loadTs(B06("B06_Section_UI_Cross_Culvert_Geom.ts")); +const { computeCulvertLayout } = loadTs(B06("logic/B06_Section_Cross_Culvert_Geom.ts")); function tiers(dz) { const s = clone(at(180)); shiftGround(s, dz); @@ -139,8 +139,8 @@ function guardTails(base, edit = () => undefined) { } out.guardTails = [...guardTails(at(352.14)), ...guardTails(FORD)]; // 박스 끝 · 세월교 측벽 끝(흙막이 없음) -const { computeBoxLayout } = loadTs(B06("B06_Section_UI_Cross_Box_Geom.ts")); -const { computeFordLayout } = loadTs(B06("B06_Section_UI_Cross_Ford_Geom.ts")); +const { computeBoxLayout } = loadTs(B06("logic/B06_Section_Cross_Box_Geom.ts")); +const { computeFordLayout } = loadTs(B06("logic/B06_Section_Cross_Ford_Geom.ts")); function endCuts(base, compute) { const s = clone(base); delete s.culvert; diff --git a/resources/tester/test_139_4_buried_outlet.py b/resources/tester/test_139_4_buried_outlet.py index 259e980a9..866c76887 100644 --- a/resources/tester/test_139_4_buried_outlet.py +++ b/resources/tester/test_139_4_buried_outlet.py @@ -1,6 +1,6 @@ """PLAN 139-4 · 139-5 — 묻힌 흙막이 유출 끝 = 3도 수평 절토선(보라 · 절토 면적) · 못 만나면 복합경사. -짝: `B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts`(끝 구간 규칙) · `_Culvert_Tiers.ts`(채널 입력) · +짝: `B06_Section/logic/B06_Section_Cross_Culvert_Extra.ts`(끝 구간 규칙) · `_Culvert_Tiers.ts`(채널 입력) · `_Culvert_Geom.ts`(유출 절토선 면적에 넣음) · `_Culvert.ts`(그림 · 툴팁). 실제 TS 를 `helper_139_4_buried_outlet.cjs` 가 돌린다. """ @@ -68,7 +68,7 @@ def test_유출_절토선은_그리고_면적에_넣음() -> None: culvert = _src("B06_Section/B06_Section_UI_Cross_Culvert.ts") assert 'if (segment.kind === "cut" && !culvert.body) continue;' not in culvert assert 'planLine(segment.points, cutLineTip("유출부", segment));' in culvert - geom = _src("B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts") + geom = _src("B06_Section/logic/B06_Section_Cross_Culvert_Geom.ts") assert "extendTrimSlope(drawnFillPoints(extrasForArea, true), outletInfo.outward);" in geom # 집수정 계류측 3도 선은 그림만(면적 밖) — 세월교 · 박스 계류측만 면적 assert ( diff --git a/resources/tester/test_152_2_link_height.py b/resources/tester/test_152_2_link_height.py index d0945ba5f..bec28cbff 100644 --- a/resources/tester/test_152_2_link_height.py +++ b/resources/tester/test_152_2_link_height.py @@ -51,5 +51,5 @@ def test_풀린_카드_이_측점_높이_줄() -> None: dock = _src("B06_Section/B06_Section_UI_Adjust_Dock.ts") assert 'const OWN_HEIGHT_ROW = ".b06-structure-panel__struct--own-height";' in dock assert "return OWN_HEIGHT_ROW;" in dock # 세월교 · 박스 날개벽 칸도 받음 - wire = _src("B06_Section/B06_Section_UI_Cross_Culvert_Wire.ts") + wire = _src("B06_Section/logic/B06_Section_Cross_Culvert_Wire.ts") assert "h: shift.inlet.h ?? heights.inlet" in wire diff --git a/resources/tester/test_190_1_basin_pick.py b/resources/tester/test_190_1_basin_pick.py index cea785514..89d52fb8c 100644 --- a/resources/tester/test_190_1_basin_pick.py +++ b/resources/tester/test_190_1_basin_pick.py @@ -1,7 +1,7 @@ """PLAN 190-1 — B06 집수정 고르기 판정면 = 부재 외곽 가상 사각형(세월교 측벽 ㄴ 꼴 · 79-6). 짝: `B06_Section/B06_Section_UI_Cross_Culvert.ts`(집수정 판정면) · `_Cross_Ford.ts`(측벽 판정면) · -`_Cross_Culvert_Types.ts`(`outlineBox` 한 벌). [상세] 모달은 카드 그림 복제라 같은 판정면을 가짐. +`logic/B06_Section_Cross_Culvert_Types.ts`(`outlineBox` 한 벌). [상세] 모달은 카드 그림 복제라 같은 판정면을 가짐. 실제 TS 를 `helper_190_1_basin_pick.cjs` 가 Node 로 돌린다. """ @@ -76,7 +76,7 @@ def test_고름_없으면_판정면_없음(out) -> None: def test_세월교_측벽과_같은_함수() -> None: - types = _src("B06_Section/B06_Section_UI_Cross_Culvert_Types.ts") + types = _src("B06_Section/logic/B06_Section_Cross_Culvert_Types.ts") assert "export function outlineBox(points: OffsetPoint[]): OffsetPoint[] {" in types ford = _src("B06_Section/B06_Section_UI_Cross_Ford.ts") assert 'polygon(outlineBox(points), "b06-chart__culvert-revet-hit", "")' in ford diff --git a/resources/tester/test_193_1_line_axis.py b/resources/tester/test_193_1_line_axis.py index 9a6e59951..23a1fec8d 100644 --- a/resources/tester/test_193_1_line_axis.py +++ b/resources/tester/test_193_1_line_axis.py @@ -107,7 +107,7 @@ def test_서버_한쪽_이동_반대측_고정_바깥으로() -> None: def test_구조물은_노견_끝에_섬() -> None: """관 기슭막이 자리 = 설계 노견 끝(`road_edges` · 노견 확장 포함) — 사면과 한 벌로 감.""" - src = (ROOT / "B06_Section" / "B06_Section_UI_Cross_Culvert_Geom.ts").read_text( + src = (ROOT / "B06_Section" / "logic/B06_Section_Cross_Culvert_Geom.ts").read_text( encoding="utf-8" ) assert "const edges = section.design?.road_edges;" in src diff --git a/resources/tester/test_433_19_20_span_cad.py b/resources/tester/test_433_19_20_span_cad.py index a03c49530..3d1f7ce50 100644 --- a/resources/tester/test_433_19_20_span_cad.py +++ b/resources/tester/test_433_19_20_span_cad.py @@ -21,7 +21,7 @@ def test_구간값_제어에_culvertopt_큐_쓰기_없음(): span = _read("B06_Section_UI_Page_Span_Control.ts") assert "culvertOptions" not in span assert "update: (section, role" not in span - wire = _read("B06_Section_UI_Cross_Culvert_Wire.ts") + wire = _read("logic/B06_Section_Cross_Culvert_Wire.ts") assert not re.search(r"^\s*update: \(section: CrossSection, role", wire, re.M) view = _read("B06_Section_UI_Cross_View_Structure.ts") assert "structureSpan?.update(" not in view diff --git a/resources/tester/test_472_3_calc_registry.py b/resources/tester/test_472_3_calc_registry.py index 4812d13dd..b0ef03916 100644 --- a/resources/tester/test_472_3_calc_registry.py +++ b/resources/tester/test_472_3_calc_registry.py @@ -97,19 +97,7 @@ GEOMETRY_PULLS = ( "B05_Profile/B05_Profile_UI_Corridor_Structures_Path.ts", "B06_Section/B06_Section_Structure_Layouts.ts", "B06_Section/B06_Section_UI_Cross_Body_Guard.ts", - "B06_Section/B06_Section_UI_Cross_Box.ts", - "B06_Section/B06_Section_UI_Cross_Box_Geom.ts", - "B06_Section/B06_Section_UI_Cross_Culvert.ts", - "B06_Section/B06_Section_UI_Cross_Culvert_Extra.ts", - "B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts", - "B06_Section/B06_Section_UI_Cross_Culvert_Tiers.ts", - "B06_Section/B06_Section_UI_Cross_Culvert_Types.ts", - "B06_Section/B06_Section_UI_Cross_Culvert_Wire.ts", - "B06_Section/B06_Section_UI_Cross_Design_Surface.ts", "B06_Section/B06_Section_UI_Cross_Excavation.ts", - "B06_Section/B06_Section_UI_Cross_Ford.ts", - "B06_Section/B06_Section_UI_Cross_Ford_Geom.ts", - "B06_Section/B06_Section_UI_Cross_Ford_Pavement.ts", "B06_Section/B06_Section_UI_Cross_Revetment.ts", "B06_Section/B06_Section_UI_Cross_Wall.ts", "B06_Section/B06_Section_UI_Cross_Wall_Hatch.ts", diff --git a/resources/tester/test_52_3_structure_spec_ford_pavement.py b/resources/tester/test_52_3_structure_spec_ford_pavement.py index cb71637b7..3508d61f6 100644 --- a/resources/tester/test_52_3_structure_spec_ford_pavement.py +++ b/resources/tester/test_52_3_structure_spec_ford_pavement.py @@ -162,6 +162,7 @@ class Node { } globalThis.document = { createElementNS: (_ns, tag) => new Node(tag) }; const pave = load(path.join(ROOT, "B06_Section/B06_Section_UI_Cross_Ford_Pavement.ts")); +const paveGeo = load(path.join(ROOT, "B06_Section/logic/B06_Section_Cross_Ford_Pavement.ts")); // +offset = 좌측 · 좌 성토 1:1.5 · 우 절토 오름 — 유입 = 표고 높은 쪽(설계선 끝 · 286-2) = 우 → 유출 = 좌 // `rightEnd` = 우 끝 표고 — 좌 끝(97)과 같으면 단면유형으로 가름(left_cut = 유입 좌 → 유출 우) const design = (mode, rightEnd = 99) => ({ @@ -181,7 +182,7 @@ const run = (mode, withChute = true) => { return band ? band.attrs.points : null; }; process.stdout.write(JSON.stringify({ - outLeft: pave.outletFillLine(design("left_cut", 97)), outRight: pave.outletFillLine(design("left_cut")), + outLeft: paveGeo.outletFillLine(design("left_cut", 97)), outRight: paveGeo.outletFillLine(design("left_cut")), band: run("left_cut"), none: run("left_cut", false), })); """ diff --git a/resources/tester/test_91_ground_free.py b/resources/tester/test_91_ground_free.py index 70f2fda8a..b2f5fb776 100644 --- a/resources/tester/test_91_ground_free.py +++ b/resources/tester/test_91_ground_free.py @@ -78,9 +78,9 @@ def test_지형에_막힘_잔재_없음() -> None: for name in ( "B06_Section_UI_Cross_Culvert.ts", "B06_Section_UI_Cross_Revetment.ts", - "B06_Section_UI_Cross_Culvert_Wire.ts", + "logic/B06_Section_Cross_Culvert_Wire.ts", "B06_Section_UI_Cross_Culvert_Types.ts", - "B06_Section_UI_Cross_Culvert_Extra.ts", + "logic/B06_Section_Cross_Culvert_Extra.ts", ): source = (ROOT / "B06_Section" / name).read_text(encoding="utf-8") assert "shiftBlockedM" not in source and "지형에 막힘" not in source, name diff --git a/resources/tester/test_b06_extra_shift_floor.py b/resources/tester/test_b06_extra_shift_floor.py index f93126548..2812078db 100644 --- a/resources/tester/test_b06_extra_shift_floor.py +++ b/resources/tester/test_b06_extra_shift_floor.py @@ -22,7 +22,7 @@ if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) TSC = PROJECT_ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc" -MODULE = PROJECT_ROOT / "B06_Section" / "B06_Section_UI_Cross_Culvert_Extra.ts" +MODULE = PROJECT_ROOT / "B06_Section" / "logic/B06_Section_Cross_Culvert_Extra.ts" # 상하 내림 2.0m → 하한 2.4m. 좌우 0.5m 를 요청해도 2.4m 로 밀려 나가야 한다. REQUESTED_X = 0.5 @@ -79,7 +79,7 @@ def _run(tmp_path: Path, requested_x: float = REQUESTED_X) -> dict: check=False, capture_output=True, ) - compiled = next(out.rglob("B06_Section_UI_Cross_Culvert_Extra.js"), None) + compiled = next(out.rglob("B06_Section_Cross_Culvert_Extra.js"), None) assert compiled is not None, "화면 코드가 JS 로 안 나옴 — tsc 실패" (out / "runner.cjs").write_text( _RUNNER % {"x": requested_x, "d": REQUESTED_D}, encoding="utf-8" diff --git a/resources/tester/test_b06_ground_unlock.py b/resources/tester/test_b06_ground_unlock.py index f2dc4f33a..8ce6b1a35 100644 --- a/resources/tester/test_b06_ground_unlock.py +++ b/resources/tester/test_b06_ground_unlock.py @@ -18,8 +18,8 @@ BUNDLE = PROJECT_ROOT / "config" / "design_calc_node" / "design_calc_node.js" _RUNNER = """ import { writeFileSync } from "node:fs"; -import { placePipeWall } from "./logic/B06_Section_UI_Cross_Culvert_Solve.js"; -import { buildOutletExtras } from "./B06_Section_UI_Cross_Culvert_Extra.js"; +import { placePipeWall } from "./B06_Section_UI_Cross_Culvert_Solve.js"; +import { buildOutletExtras } from "./B06_Section_Cross_Culvert_Extra.js"; const pipe = (d, x) => placePipeWall({ autoOffset: 10, outward: 1, height: 2, baseElevation0: 98, edgeElevation: 100, invertCap: Number.POSITIVE_INFINITY, adjust: { x, d, h: null, m: null }, defaultD: 4.8, @@ -42,7 +42,10 @@ writeFileSync(process.argv[2], JSON.stringify({ @pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치") def test_원지반이_자리_다단을_막지_않고_노면_위만_막음(tmp_path: Path) -> None: out = tmp_path / "js" - for name in ("logic/B06_Section_UI_Cross_Culvert_Solve", "B06_Section_UI_Cross_Culvert_Extra"): + for name in ( + "logic/B06_Section_UI_Cross_Culvert_Solve", + "logic/B06_Section_Cross_Culvert_Extra", + ): subprocess.run( # noqa: S603 — 고정 실행 파일 [ "node", @@ -67,7 +70,7 @@ def test_원지반이_자리_다단을_막지_않고_노면_위만_막음(tmp_pa re.sub(r'(from\s+"\.{1,2}/[^"]+)"', r'\1.js"', emitted.read_text(encoding="utf-8")), encoding="utf-8", ) - base = next(path.parent for path in out.rglob("B06_Section_UI_Cross_Culvert_Extra.js")) + base = next(path.parent for path in out.rglob("B06_Section_Cross_Culvert_Extra.js")) (base / "runner.mjs").write_text(_RUNNER, encoding="utf-8") result = tmp_path / "out.json" subprocess.run( # noqa: S603 @@ -87,7 +90,7 @@ def test_원지반이_자리_다단을_막지_않고_노면_위만_막음(tmp_pa def test_화면_서버_같은_판() -> None: for name in ( "logic/B06_Section_UI_Cross_Culvert_Solve", - "B06_Section_UI_Cross_Culvert_Geom", + "logic/B06_Section_Cross_Culvert_Geom", "B06_Section_UI_Cross_Revetment", ): source = (B06 / f"{name}.ts").read_text(encoding="utf-8") @@ -96,7 +99,7 @@ def test_화면_서버_같은_판() -> None: assert "const placeable" not in (B06 / "B06_Section_UI_Cross_Revetment.ts").read_text( encoding="utf-8" ), "사면안정 흙막이 · 옹벽 매몰 자리 금지가 남음" - assert "const placeable" not in (B06 / "B06_Section_UI_Cross_Culvert_Extra.ts").read_text( + assert "const placeable" not in (B06 / "logic/B06_Section_Cross_Culvert_Extra.ts").read_text( encoding="utf-8" ), "추가 흙막이 매몰 자리 금지가 남음" bundle = BUNDLE.read_text(encoding="utf-8") diff --git a/resources/tester/test_b06_height_unlock.py b/resources/tester/test_b06_height_unlock.py index fe5941507..a389cb963 100644 --- a/resources/tester/test_b06_height_unlock.py +++ b/resources/tester/test_b06_height_unlock.py @@ -18,8 +18,8 @@ BUNDLE = PROJECT_ROOT / "config" / "design_calc_node" / "design_calc_node.js" _RUNNER = """ import { writeFileSync } from "node:fs"; -import { revetWallSpec } from "./logic/B06_Section_UI_Cross_Culvert_Const.js"; -import { buildOutletExtras } from "./B06_Section_UI_Cross_Culvert_Extra.js"; +import { revetWallSpec } from "./B06_Section_UI_Cross_Culvert_Const.js"; +import { buildOutletExtras } from "./B06_Section_Cross_Culvert_Extra.js"; const spec = { revet_form: "찰쌓기", revet_height_m: 1.5 }; const pipe = (h) => revetWallSpec(spec, { h, m: null }, false, 0.8).pureHeight; const own = (h) => revetWallSpec(spec, { h, m: null }, true, 0).pureHeight; @@ -41,7 +41,10 @@ writeFileSync(process.argv[2], JSON.stringify({ @pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치") def test_높이는_입력_그대로_0이하만_멈춤(tmp_path: Path) -> None: out = tmp_path / "js" - for name in ("logic/B06_Section_UI_Cross_Culvert_Const", "B06_Section_UI_Cross_Culvert_Extra"): + for name in ( + "logic/B06_Section_UI_Cross_Culvert_Const", + "logic/B06_Section_Cross_Culvert_Extra", + ): subprocess.run( # noqa: S603 — 고정 실행 파일 [ "node", @@ -66,7 +69,7 @@ def test_높이는_입력_그대로_0이하만_멈춤(tmp_path: Path) -> None: re.sub(r'(from\s+"\.{1,2}/[^"]+)"', r'\1.js"', emitted.read_text(encoding="utf-8")), encoding="utf-8", ) - base = next(path.parent for path in out.rglob("B06_Section_UI_Cross_Culvert_Extra.js")) + base = next(path.parent for path in out.rglob("B06_Section_Cross_Culvert_Extra.js")) (base / "runner.mjs").write_text(_RUNNER, encoding="utf-8") result = tmp_path / "out.json" subprocess.run( # noqa: S603 @@ -85,8 +88,8 @@ def test_높이는_입력_그대로_0이하만_멈춤(tmp_path: Path) -> None: def test_높이_토스트_없음_서버_같은_판() -> None: - for name in ("UI_Cross_Culvert_Wire", "UI_Page_Pipe_Options"): - source = (B06 / f"B06_Section_{name}.ts").read_text(encoding="utf-8") + for name in ("logic/B06_Section_Cross_Culvert_Wire", "B06_Section_UI_Page_Pipe_Options"): + source = (B06 / f"{name}.ts").read_text(encoding="utf-8") assert "B06_Cross_Height_Floor" not in source, f"{name} 에 최소 높이 토스트가 남음" assert "높이는 최소" not in source, f"{name} 에 최소 높이 토스트가 남음" panel = (B06 / "B06_Section_UI_Cross_Structure_Panel.ts").read_text(encoding="utf-8") diff --git a/resources/tester/test_b06_wall_lean_table.py b/resources/tester/test_b06_wall_lean_table.py index 64ebd2036..f0ecda854 100644 --- a/resources/tester/test_b06_wall_lean_table.py +++ b/resources/tester/test_b06_wall_lean_table.py @@ -19,9 +19,9 @@ if str(ROOT) not in sys.path: DATA_DIR = ROOT / "resources" / "data_masonry" GEOMETRY = [ - "B06_Section_UI_Cross_Culvert_Geom.ts", + "logic/B06_Section_Cross_Culvert_Geom.ts", "B06_Section_UI_Cross_Wall.ts", - "B06_Section_UI_Cross_Culvert_Extra.ts", + "logic/B06_Section_Cross_Culvert_Extra.ts", "logic/B06_Section_UI_Cross_Culvert_Solve.ts", ] diff --git a/resources/tester/test_b06_wall_shape_unified.py b/resources/tester/test_b06_wall_shape_unified.py index e79c94898..31178e785 100644 --- a/resources/tester/test_b06_wall_shape_unified.py +++ b/resources/tester/test_b06_wall_shape_unified.py @@ -95,12 +95,16 @@ def test_벽_외곽_한_벌_높이_입력_그대로_노면_멈춤(tmp_path: Path def test_네_벽이_공용_함수로_서고_형태_한도가_없다() -> None: - for name in ("Culvert_Geom", "Culvert_Extra", "Revetment"): - source = (B06 / f"B06_Section_UI_Cross_{name}.ts").read_text(encoding="utf-8") + for name in ( + "logic/B06_Section_Cross_Culvert_Geom", + "logic/B06_Section_Cross_Culvert_Extra", + "B06_Section_UI_Cross_Revetment", + ): + source = (B06 / f"{name}.ts").read_text(encoding="utf-8") assert "buildRevetWallGeometry(" in source, f"{name} 가 공용 벽 함수를 안 씀" for path in B06.rglob("*.ts"): # logic/ 까지(348-1) source = path.read_text(encoding="utf-8") assert "revetHeightLimit" not in source, f"{path.name} 에 형태 높이 한도가 남음" assert "materialLimit" not in source, f"{path.name} 에 재질 높이 한도가 남음" - extra = (B06 / "B06_Section_UI_Cross_Culvert_Extra.ts").read_text(encoding="utf-8") + extra = (B06 / "logic/B06_Section_Cross_Culvert_Extra.ts").read_text(encoding="utf-8") assert "fitted" not in extra, "추가 흙막이가 원지반에 맞춰 늘어남" diff --git a/resources/tester/ui_checks/54-3_배관_횡단그림.js b/resources/tester/ui_checks/54-3_배관_횡단그림.js index d2218c8c3..d1d72c45e 100644 --- a/resources/tester/ui_checks/54-3_배관_횡단그림.js +++ b/resources/tester/ui_checks/54-3_배관_횡단그림.js @@ -151,7 +151,7 @@ .map((e) => e.name) .filter((n) => n.includes(part)) .pop(); - const geom = await import(modUrl("/B06_Section/B06_Section_UI_Cross_Culvert_Geom.ts")); + const geom = await import(modUrl("/B06_Section/logic/B06_Section_Cross_Culvert_Geom.ts")); const key = Object.keys(sessionStorage).find((k) => k.includes("section-detail")); const detail = JSON.parse(sessionStorage.getItem(key)); const forms = geom.REVET_FORMS;