diff --git a/B05_Profile/B05_Profile_UI_Corridor_Build.ts b/B05_Profile/B05_Profile_UI_Corridor_Build.ts index cc37c22d..06890650 100644 --- a/B05_Profile/B05_Profile_UI_Corridor_Build.ts +++ b/B05_Profile/B05_Profile_UI_Corridor_Build.ts @@ -39,106 +39,27 @@ import { slerpLeft, type PieceControl, } from "./B05_Profile_UI_Corridor_Frames"; -import { - buildCorridorStructures, - type CorridorStructure, - type RouteFrame, -} from "./B05_Profile_UI_Corridor_Structures"; +import { buildCorridorStructures, type RouteFrame } from "./B05_Profile_UI_Corridor_Structures"; export type { CorridorStructure, StructureFrame } from "./B05_Profile_UI_Corridor_Structures"; +import type { + CorridorBuildResult, + CorridorCap, + CorridorRibbon, +} from "./B05_Profile_UI_Corridor_Build_Types"; +// 결과 자료형은 `_Build_Types` 에 있다(2026-09-02 분리) — 호출부 경로 유지를 위해 재수출. +export type { + CorridorBuildResult, + CorridorCap, + CorridorCutWall, + CorridorRibbon, +} from "./B05_Profile_UI_Corridor_Build_Types"; import { buildStructurePatchRibbons } from "./B05_Profile_UI_Corridor_Carve"; import { structureSilhouettes } from "./B05_Profile_UI_Corridor_Station_Structure"; -import { buildPlanCurves, type PlanCurve } from "./B05_Profile_UI_Corridor_Plan"; +import { buildPlanCurves } from "./B05_Profile_UI_Corridor_Plan"; import { maskFillByPlanCurves } from "./B05_Profile_UI_Corridor_Cut"; export type { PlanCurve } from "./B05_Profile_UI_Corridor_Plan"; /** 리본 하나 — rows[i]는 프레임 i의 단면 점(colCount개, 모델 x/y/z 평탄 배열). */ -export interface CorridorRibbon { - kind: CorridorKind; - side: CorridorSide; - colCount: number; - chainages: number[]; - /** - * 구조물 **패치 리본**(2026-08-25) — 투영커브로 잘린 자리에 다시 그리는 변형 성토면. - * 절단 대상에서 빠진다(패치까지 자르면 구멍이 도로 뚫린다). - */ - patch?: boolean; - /** - * 패치를 **절취 영역 안쪽으로 클립**한다(2026-08-27 사용자: 삐져나오지도 벌어지지도 - * 않게). `maskFillByPlanCurves`가 기본 성토면을 지우는 규칙의 **정확한 반대**를 건다 — - * 영역 밖 셀은 지우고, 걸친 셀은 안쪽 조각만 남긴다. 두 면이 같은 경계를 공유하니 - * 겹침도 구멍도 안 생긴다. **세월교 전용** — 구조물별로 로직이 다르다. - */ - patchClip?: boolean; - /** - * rowCount × colCount × 3 (모델 좌표). - * - * **Float64**다 — 모델 좌표는 EPSG 절대값(십만 m대)이라 Float32에 담으면 해상도가 - * 0.14m까지 떨어져, 0.2m로 세분한 점들이 양자화 계단을 타며 최대 35°짜리 지그재그가 - * 생겼다(2026-08-23 실측). 화면에 올릴 때 원점을 빼면서 Float32로 줄인다. - */ - positions: Float64Array; - /** - * 셀 마스크 — `(rowCount-1) × (colCount-1)`, 1이면 **그 셀은 안 그린다** - * (2026-08-26 투영 커브 안쪽 지우기). 정점은 그대로 두고 삼각형만 뺀다. - */ - cellMask?: Uint8Array; - /** - * 축 고정 커브(BOX암거)가 **걸친 셀**을 커브에서 잘라 되살린 조각 삼각형 - * (모델 좌표, 삼각형당 9). 격자로는 못 담는 비스듬한 경계를 여기로 메운다 - * (2026-08-26 사용자: 이원화하여 관리). - */ - trimTris?: Float64Array; - /** `trimTris`와 짝인 UV(삼각형당 6) — 빗금이 실척으로 이어지게 한다. */ - trimUvs?: Float32Array; - /** - * 격자점별 **원지반 표고**(rowCount × colCount) — 성토 리본에만 싣는다. - * 잘린 자리 측벽을 성토면에서 원지반까지 세우는 밑선이다(2026-08-27 사용자). - */ - groundZ?: Float64Array; -} - -/** - * 잘린 자리 **측벽 서피스**(2026-08-27 사용자) — 리본 메시에 섞지 않고 구조물처럼 - * 따로 그린다. 삼각형 수프(정점당 3, 삼각형당 9). 빗금이 없어 UV는 담지 않는다. - */ -export interface CorridorCutWall { - side: "left" | "right"; - positions: Float64Array; - /** 패치 바깥 끝 스커트인가(2026-08-27) — 뷰어가 지형 색인으로 뒤늦게 붙이므로, - * 다시 붙일 때 옛 것을 걷어내려면 표식이 있어야 한다(`_Corridor_Skirt.ts`). */ - patchSkirt?: boolean; - /** - * 정점별 UV(정점당 2) — **빗금 있는 성토 서피스**로 그릴 때만 싣는다. - * 절취 측벽은 일반 서피스(빗금 없음)이고, 패치 스커트는 성토면의 연장이라 빗금을 - * 넣는다(2026-08-27 사용자: "변형 성토면 하단 끝단부 서피스는 메시 형태"). - */ - uvs?: Float32Array; -} - -/** 시·종점 마구리(캡) — 설계선과 지반선 사이를 세로로 봉인하는 스트립(모델 좌표). */ -export interface CorridorCap { - /** [x, y, z설계, z지반] × N — offset 오름차순. */ - points: Array<[number, number, number, number]>; -} - -export interface CorridorBuildResult { - ribbons: CorridorRibbon[]; - /** 클리핑 경계 — 프레임별 좌/우 최외곽(catch) XY(모델). 좌우 같은 길이. */ - outline: { chainages: number[]; left: Array<[number, number]>; right: Array<[number, number]> }; - /** 노선 시·종점 단면 봉인(2026-08-23 품질 개선). 캡이 없으면 빈 배열. */ - caps: CorridorCap[]; - /** 배수관 세트 구조물(기슭막이·집수정·배관) 솔리드(2026-08-23). 없으면 빈 배열. */ - structures: CorridorStructure[]; - /** - * 구조물 세트별 **탑뷰 투영 커브**(2026-08-26 사용자) — 세트 구조물 상단 5m - * 평면에 눕힌 닫힌 고리들. 구조물·비탈(변형/원본)을 유입·유출로 나눠 담는다. - * 저장 코리도엔 없을 수 있다. - */ - planCurves?: PlanCurve[]; - /** 절취 측벽 서피스(2026-08-27) — 저장본에도 담는다. 없으면 빈 배열. */ - cutWalls?: CorridorCutWall[]; -} - /** * 구조물 구간 **성토 패치 리본** 적용 여부(2026-08-27 사용자). B06 횡단이 내는 * 변형 성토선을 3D 포켓에 되메운다 — 다단이면 `성토 > 구조물 > 성토`라 성토선이 diff --git a/B05_Profile/B05_Profile_UI_Corridor_Build_Types.ts b/B05_Profile/B05_Profile_UI_Corridor_Build_Types.ts new file mode 100644 index 00000000..dce9f83f --- /dev/null +++ b/B05_Profile/B05_Profile_UI_Corridor_Build_Types.ts @@ -0,0 +1,99 @@ +/* ============================================================================= + * B05_Profile_UI_Corridor_Build_Types.ts + * 코리도 빌더가 내는 **결과 자료형** — 리본·절취 측벽·캡·빌드 결과. + * + * `B05_Profile_UI_Corridor_Build` 에서 떼어냈다(700줄 제한, 2026-09-02). 계산은 전혀 + * 없고 모양과 그 뜻(주석)만 있다. 빌더가 그대로 다시 내보내므로 기존 import 경로 + * (`... from "./B05_Profile_UI_Corridor_Build"`)는 전부 유효하다. + * ========================================================================== */ + +import type { CorridorKind, CorridorSide } from "./B05_Profile_UI_Corridor_Station"; +import type { CorridorStructure } from "./B05_Profile_UI_Corridor_Structures"; +import type { PlanCurve } from "./B05_Profile_UI_Corridor_Plan"; + +export interface CorridorRibbon { + kind: CorridorKind; + side: CorridorSide; + colCount: number; + chainages: number[]; + /** + * 구조물 **패치 리본**(2026-08-25) — 투영커브로 잘린 자리에 다시 그리는 변형 성토면. + * 절단 대상에서 빠진다(패치까지 자르면 구멍이 도로 뚫린다). + */ + patch?: boolean; + /** + * 패치를 **절취 영역 안쪽으로 클립**한다(2026-08-27 사용자: 삐져나오지도 벌어지지도 + * 않게). `maskFillByPlanCurves`가 기본 성토면을 지우는 규칙의 **정확한 반대**를 건다 — + * 영역 밖 셀은 지우고, 걸친 셀은 안쪽 조각만 남긴다. 두 면이 같은 경계를 공유하니 + * 겹침도 구멍도 안 생긴다. **세월교 전용** — 구조물별로 로직이 다르다. + */ + patchClip?: boolean; + /** + * rowCount × colCount × 3 (모델 좌표). + * + * **Float64**다 — 모델 좌표는 EPSG 절대값(십만 m대)이라 Float32에 담으면 해상도가 + * 0.14m까지 떨어져, 0.2m로 세분한 점들이 양자화 계단을 타며 최대 35°짜리 지그재그가 + * 생겼다(2026-08-23 실측). 화면에 올릴 때 원점을 빼면서 Float32로 줄인다. + */ + positions: Float64Array; + /** + * 셀 마스크 — `(rowCount-1) × (colCount-1)`, 1이면 **그 셀은 안 그린다** + * (2026-08-26 투영 커브 안쪽 지우기). 정점은 그대로 두고 삼각형만 뺀다. + */ + cellMask?: Uint8Array; + /** + * 축 고정 커브(BOX암거)가 **걸친 셀**을 커브에서 잘라 되살린 조각 삼각형 + * (모델 좌표, 삼각형당 9). 격자로는 못 담는 비스듬한 경계를 여기로 메운다 + * (2026-08-26 사용자: 이원화하여 관리). + */ + trimTris?: Float64Array; + /** `trimTris`와 짝인 UV(삼각형당 6) — 빗금이 실척으로 이어지게 한다. */ + trimUvs?: Float32Array; + /** + * 격자점별 **원지반 표고**(rowCount × colCount) — 성토 리본에만 싣는다. + * 잘린 자리 측벽을 성토면에서 원지반까지 세우는 밑선이다(2026-08-27 사용자). + */ + groundZ?: Float64Array; +} + +/** + * 잘린 자리 **측벽 서피스**(2026-08-27 사용자) — 리본 메시에 섞지 않고 구조물처럼 + * 따로 그린다. 삼각형 수프(정점당 3, 삼각형당 9). 빗금이 없어 UV는 담지 않는다. + */ +export interface CorridorCutWall { + side: "left" | "right"; + positions: Float64Array; + /** 패치 바깥 끝 스커트인가(2026-08-27) — 뷰어가 지형 색인으로 뒤늦게 붙이므로, + * 다시 붙일 때 옛 것을 걷어내려면 표식이 있어야 한다(`_Corridor_Skirt.ts`). */ + patchSkirt?: boolean; + /** + * 정점별 UV(정점당 2) — **빗금 있는 성토 서피스**로 그릴 때만 싣는다. + * 절취 측벽은 일반 서피스(빗금 없음)이고, 패치 스커트는 성토면의 연장이라 빗금을 + * 넣는다(2026-08-27 사용자: "변형 성토면 하단 끝단부 서피스는 메시 형태"). + */ + uvs?: Float32Array; +} + +/** 시·종점 마구리(캡) — 설계선과 지반선 사이를 세로로 봉인하는 스트립(모델 좌표). */ +export interface CorridorCap { + /** [x, y, z설계, z지반] × N — offset 오름차순. */ + points: Array<[number, number, number, number]>; +} + +export interface CorridorBuildResult { + ribbons: CorridorRibbon[]; + /** 클리핑 경계 — 프레임별 좌/우 최외곽(catch) XY(모델). 좌우 같은 길이. */ + outline: { chainages: number[]; left: Array<[number, number]>; right: Array<[number, number]> }; + /** 노선 시·종점 단면 봉인(2026-08-23 품질 개선). 캡이 없으면 빈 배열. */ + caps: CorridorCap[]; + /** 배수관 세트 구조물(기슭막이·집수정·배관) 솔리드(2026-08-23). 없으면 빈 배열. */ + structures: CorridorStructure[]; + /** + * 구조물 세트별 **탑뷰 투영 커브**(2026-08-26 사용자) — 세트 구조물 상단 5m + * 평면에 눕힌 닫힌 고리들. 구조물·비탈(변형/원본)을 유입·유출로 나눠 담는다. + * 저장 코리도엔 없을 수 있다. + */ + planCurves?: PlanCurve[]; + /** 절취 측벽 서피스(2026-08-27) — 저장본에도 담는다. 없으면 빈 배열. */ + cutWalls?: CorridorCutWall[]; +} diff --git a/B05_Profile/B05_Profile_UI_Corridor_Structures.ts b/B05_Profile/B05_Profile_UI_Corridor_Structures.ts index dc079a94..230aef90 100644 --- a/B05_Profile/B05_Profile_UI_Corridor_Structures.ts +++ b/B05_Profile/B05_Profile_UI_Corridor_Structures.ts @@ -20,6 +20,7 @@ * 폴리곤을 갈아 끼워(`polygons`) 측점 사이를 Catmull-Rom으로 잇는다. 벽은 여전히 하나다. * ========================================================================== */ +import { structureHashParts } from "./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 { restrictToSide, tierSpanOf } from "../B06_Section/B06_Section_UI_Cross_Culvert_Wire"; @@ -600,115 +601,5 @@ export function buildCorridorStructures( return solids; } -/** 해시 입력용 요약 — 구조물에 영향을 주는 값만 짧게 이어 붙인다(Corridor.ts가 쓴다). */ -export function structureHashParts(section: CrossSection): Array { - // 물넘이 파임·독립 기슭막이는 다른 세트와 함께 설 수 있으니 앞에 이어 붙인다 — - // 빼먹으면 값을 고쳐도 저장 코리도가 만료되지 않는다(2026-08-28). - const extras: Array = []; - const fordPave = section.ford_pavement; - if (fordPave) { - extras.push("fp", fordPave.span_m, fordPave.depth_m ?? "", fordPave.slope_pct ?? ""); - } - const ownRevet = section.revetment; - if (ownRevet) { - extras.push( - "rv", - ownRevet.start_m, - ownRevet.end_m, - ownRevet.anchor_m, - ownRevet.height_m ?? "", - ownRevet.side ?? "", - ownRevet.form ?? "", - ownRevet.tiers ?? "", - ownRevet.lift_m ?? "", - ownRevet.shift_m ?? "", - // 조정창 조작값이 바뀌면 저장 코리도를 만료시킨다. - JSON.stringify(section.design?.revet_adjust?.own ?? ""), - ); - } - const box = section.box; - if (box) { - return [ - ...extras, - "bx", - box.inner_width_m, - box.inner_height_m, - box.span_m, - box.cover_m, - box.wing_in.slab_extend_m, - box.wing_in.height_m ?? "", - box.wing_out.slab_extend_m, - box.wing_out.height_m ?? "", - // 조작값이 정본에 실리므로 해시에도 넣는다 — 안 넣으면 저장 코리도가 안 만료된다. - ...(["left", "right"] as const).map((side) => { - const value = section.design?.box_adjust?.[side]; - return value ? `${side}:${value.lengthM},${value.riseM}` : ""; - }), - ]; - } - const ford = section.ford; - if (ford) { - const adjust = section.design?.ford_adjust; - const wall = (role: "inlet" | "outlet"): string => { - const value = adjust?.[role]; - return value ? `${value.heightM ?? ""},${value.lateralM},${value.slopeM}` : ""; - }; - return [ - ...extras, - "fd", - ford.diameter_m, - ford.pipe_count, - ford.span_m, - ford.wing_in.slab_extend_m, - ford.wing_out.slab_extend_m, - wall("inlet"), - wall("outlet"), - ]; - } - const culvert = section.culvert; - if (!culvert) return extras; - const side = (spec: CulvertSideSpec): string => - [ - spec.structure, - spec.revet_form ?? "", - spec.revet_height_m ?? "", - spec.revet_length_m ?? "", - spec.revet_before_m ?? "", - spec.revet_after_m ?? "", - spec.basin_length_m ?? "", - spec.basin_before_m ?? "", - spec.basin_after_m ?? "", - ].join(","); - const basin = section.design?.basin_adjust; - const revet = section.design?.revet_adjust; - const counts = section.design?.extra_wall_counts; - return [ - ...extras, - "cv", - culvert.diameter_m, - culvert.pipe_kind ?? "", - side(culvert.inlet), - side(culvert.outlet), - section.design?.inlet_structure ?? "", - basin ? `${basin.innerWidthM},${basin.innerHeightM},${basin.lateralM},${basin.slopeM}` : "", - // 조작값이 정본에 실리므로 해시에도 넣는다 — 안 넣으면 저장 코리도가 안 만료된다. - revet - ? Object.keys(revet) - .sort() - .map((role) => { - const value = revet[role]; - return `${role}:${value.x},${value.d ?? ""},${value.h ?? ""},${value.m ?? ""}`; - }) - .join("|") - : "", - counts ? `${counts.outlet},${counts.basin}` : "", - section.design?.revet_follow_grade === false ? "flatZ" : "", - // 다단 단별 구간값·연동 해제도 형상을 바꾼다 — 빠지면 저장 코리도가 안 만료돼 - // 값만 바뀌고 3D는 옛 모양 그대로다(2026-08-30). - Object.entries(section.design?.extra_spans ?? {}) - .sort(([a], [b]) => (a < b ? -1 : 1)) - .map(([wall, span]) => `${wall}:${span.length_m},${span.before_m},${span.after_m}`) - .join("|"), - section.design?.revet_link_detached === true ? "detached" : "", - ]; -} +// 해시 요약은 `_Structures_Hash.ts` 에 있다(2026-09-02 분리) — 호출부 경로 유지를 위해 재수출. +export { structureHashParts } from "./B05_Profile_UI_Corridor_Structures_Hash"; diff --git a/B05_Profile/B05_Profile_UI_Corridor_Structures_Hash.ts b/B05_Profile/B05_Profile_UI_Corridor_Structures_Hash.ts new file mode 100644 index 00000000..8569f157 --- /dev/null +++ b/B05_Profile/B05_Profile_UI_Corridor_Structures_Hash.ts @@ -0,0 +1,124 @@ +/* ============================================================================= + * B05_Profile_UI_Corridor_Structures_Hash.ts + * 저장 코리도 만료 판정용 **구조물 해시 요약**. + * + * `B05_Profile_UI_Corridor_Structures` 에서 떼어냈다(700줄 제한, 2026-09-02). + * 값이 바뀌면 저장본이 만료되도록 **구조물에 영향을 주는 값만** 이어 붙인다 — + * 빠뜨린 값은 곧 "고쳐도 3D가 안 바뀌는" 버그가 되므로 항목 추가에 주의할 것. + * 원래 파일이 그대로 다시 내보내므로 기존 import 경로는 유효하다. + * ========================================================================== */ + +import type { CrossSection, CulvertSideSpec } from "../B06_Section/B06_Section_Api_Fetch"; + +/** 해시 입력용 요약 — 구조물에 영향을 주는 값만 짧게 이어 붙인다(Corridor.ts가 쓴다). */ +export function structureHashParts(section: CrossSection): Array { + // 물넘이 파임·독립 기슭막이는 다른 세트와 함께 설 수 있으니 앞에 이어 붙인다 — + // 빼먹으면 값을 고쳐도 저장 코리도가 만료되지 않는다(2026-08-28). + const extras: Array = []; + const fordPave = section.ford_pavement; + if (fordPave) { + extras.push("fp", fordPave.span_m, fordPave.depth_m ?? "", fordPave.slope_pct ?? ""); + } + const ownRevet = section.revetment; + if (ownRevet) { + extras.push( + "rv", + ownRevet.start_m, + ownRevet.end_m, + ownRevet.anchor_m, + ownRevet.height_m ?? "", + ownRevet.side ?? "", + ownRevet.form ?? "", + ownRevet.tiers ?? "", + ownRevet.lift_m ?? "", + ownRevet.shift_m ?? "", + // 조정창 조작값이 바뀌면 저장 코리도를 만료시킨다. + JSON.stringify(section.design?.revet_adjust?.own ?? ""), + ); + } + const box = section.box; + if (box) { + return [ + ...extras, + "bx", + box.inner_width_m, + box.inner_height_m, + box.span_m, + box.cover_m, + box.wing_in.slab_extend_m, + box.wing_in.height_m ?? "", + box.wing_out.slab_extend_m, + box.wing_out.height_m ?? "", + // 조작값이 정본에 실리므로 해시에도 넣는다 — 안 넣으면 저장 코리도가 안 만료된다. + ...(["left", "right"] as const).map((side) => { + const value = section.design?.box_adjust?.[side]; + return value ? `${side}:${value.lengthM},${value.riseM}` : ""; + }), + ]; + } + const ford = section.ford; + if (ford) { + const adjust = section.design?.ford_adjust; + const wall = (role: "inlet" | "outlet"): string => { + const value = adjust?.[role]; + return value ? `${value.heightM ?? ""},${value.lateralM},${value.slopeM}` : ""; + }; + return [ + ...extras, + "fd", + ford.diameter_m, + ford.pipe_count, + ford.span_m, + ford.wing_in.slab_extend_m, + ford.wing_out.slab_extend_m, + wall("inlet"), + wall("outlet"), + ]; + } + const culvert = section.culvert; + if (!culvert) return extras; + const side = (spec: CulvertSideSpec): string => + [ + spec.structure, + spec.revet_form ?? "", + spec.revet_height_m ?? "", + spec.revet_length_m ?? "", + spec.revet_before_m ?? "", + spec.revet_after_m ?? "", + spec.basin_length_m ?? "", + spec.basin_before_m ?? "", + spec.basin_after_m ?? "", + ].join(","); + const basin = section.design?.basin_adjust; + const revet = section.design?.revet_adjust; + const counts = section.design?.extra_wall_counts; + return [ + ...extras, + "cv", + culvert.diameter_m, + culvert.pipe_kind ?? "", + side(culvert.inlet), + side(culvert.outlet), + section.design?.inlet_structure ?? "", + basin ? `${basin.innerWidthM},${basin.innerHeightM},${basin.lateralM},${basin.slopeM}` : "", + // 조작값이 정본에 실리므로 해시에도 넣는다 — 안 넣으면 저장 코리도가 안 만료된다. + revet + ? Object.keys(revet) + .sort() + .map((role) => { + const value = revet[role]; + return `${role}:${value.x},${value.d ?? ""},${value.h ?? ""},${value.m ?? ""}`; + }) + .join("|") + : "", + counts ? `${counts.outlet},${counts.basin}` : "", + section.design?.revet_follow_grade === false ? "flatZ" : "", + // 다단 단별 구간값·연동 해제도 형상을 바꾼다 — 빠지면 저장 코리도가 안 만료돼 + // 값만 바뀌고 3D는 옛 모양 그대로다(2026-08-30). + Object.entries(section.design?.extra_spans ?? {}) + .sort(([a], [b]) => (a < b ? -1 : 1)) + .map(([wall, span]) => `${wall}:${span.length_m},${span.before_m},${span.after_m}`) + .join("|"), + section.design?.revet_link_detached === true ? "detached" : "", + ]; +} diff --git a/B05_Profile/B05_Profile_UI_Drainage_Panel.ts b/B05_Profile/B05_Profile_UI_Drainage_Panel.ts index 9af19b04..dcce6cb4 100644 --- a/B05_Profile/B05_Profile_UI_Drainage_Panel.ts +++ b/B05_Profile/B05_Profile_UI_Drainage_Panel.ts @@ -1,5 +1,4 @@ import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale"; -import type { MapContextMenuItem } from "@ui/ui_template_context_menu"; import { computeDetailBasins, fetchDetailPipePoints, @@ -8,7 +7,6 @@ import { saveDetailPipePoints, type DetailBasin, type DetailBasinResponse, - type PipeFacility, type PipeSource, type VWorldMeta, } from "../B04_PreProcess/B04_PreProcess_Api_Fetch"; @@ -27,7 +25,7 @@ import type { FlowArrow } from "../B04_PreProcess/B04_PreProcess_UI_FlowArrows"; import { buildStrengthArray } from "../B04_PreProcess/B04_PreProcess_UI_FlowRamp"; import { resampleRoute } from "../B04_PreProcess/B04_PreProcess_UI_RouteSamples"; import { createPipeEditor } from "./B05_Profile_UI_Drainage_Pipes"; -import { createFacilityStore, type FacilityAttributes } from "./B05_Profile_UI_Drainage_Facility"; +import { createFacilityStore } from "./B05_Profile_UI_Drainage_Facility"; import { createDrainageChrome } from "./B05_Profile_UI_Drainage_Chrome"; import { bindDrainageInteractions } from "./B05_Profile_UI_Drainage_Interact"; import { drawDrainageScene } from "./B05_Profile_UI_Drainage_Render"; @@ -61,66 +59,9 @@ function L(key: keyof typeof ui_locales): string { return ui_locales[key][currentLanguageIndex]; } -export interface DrainagePanel { - root: HTMLElement; - /** 프로젝트가 정해지면 배경지도·도엽 레이어를 불러온다. */ - load: (projectId: string) => void; - /** 확정된 노선 평면 선형(사업지 좌표계 m)을 지도 위에 겹친다. */ - setRoute: (points: ReadonlyArray) => void; - /** 현재 관 매설 누가거리 목록(종단 테이블의 "배관" 구조물 라인과 맞추는 데 쓴다). */ - pipeChainages: () => number[]; - /** 종단 테이블에서 배관 라인을 끌었을 때 — 그 자리로 옮기고 세부유역을 다시 나눈다. */ - movePipe: (fromChainage: number, toChainage: number) => void; - /** 관 목록을 통째로 맞춘다(사이드바 구조물 폼 편집 등 밖에서 바뀐 경우). - * 현재 목록과 같으면 아무 일도 하지 않는다 — 되먹임 고리를 끊는 지점이다. */ - setPipeChainages: (chainages: ReadonlyArray) => void; - /** 종단 테이블 우클릭·통합 목록에서 계곡 통과 시설을 넣을 때. - * attributes에 시설 종류·구간·부속 옵션이 담긴다(생략 = 맨 배관). */ - addPipe: (chainageM: number, attributes?: FacilityAttributes) => void; - /** 사이드 폼 [수정] — 기준점 이동·구간·부속 옵션을 정본에 반영하고 재계산한다. */ - updatePipeFacility: ( - fromChainageM: number, - toChainageM: number, - attributes: FacilityAttributes, - ) => void; - removePipe: (chainageM: number) => void; - /** 경로 확정 시 관 매설 지점을 영구저장한다(B04 "모델 확정"과 같은 저장소). */ - savePipes: () => Promise; - /** 밖에서 유역을 고른다(그래프 측점선·사이드 패널 선택과 맞추기 위함). 이미 같으면 무시. */ - selectBasinByChainage: (chainageM: number | null) => void; - /** 밖(리스트·그래프·3D)에서 관을 고른다 — 마커 선택·유역 강조를 맞춘다. - * 이미 같으면 무시(2026-08-17 전역 선택 동기화). */ - selectPipeAtChainage: (chainageM: number | null) => void; - /** 측점 선택 마킹 — 계획선 위 해당 누가거리에 표식을 그린다(null이면 지움). */ - markStation: (chainageM: number | null) => void; - dispose: () => void; -} - -export interface DrainagePanelCallbacks { - /** 관 목록이 바뀔 때마다 누가거리 + 담당 유역의 배수 유효직경(mm) + 시설 종류를 - * 넘긴다 — 종단 구조물 라인·통합 목록 동기화 및 관경 자동 지정(D800 기본)용. */ - onPipesChanged?: ( - pipes: Array<{ - chainage_m: number; - effective_diameter_mm: number | null; - /** 담당 유역의 설계유량(㎥/s, 100년빈도·2.0배). 물넘이·세월교 개략 단면의 입력이다. */ - design_flow_m3s?: number | null; - facility: PipeFacility; - start_m?: number; - end_m?: number; - source?: PipeSource; - options?: Record; - }>, - ) => void; - /** 유역을 고르거나 풀 때 그 관의 누가거리(없으면 null)를 넘긴다 — 그래프·사이드 패널 동기화용. */ - onBasinSelected?: (chainageM: number | null) => void; - /** 관 마커 선택이 바뀔 때(클릭·해제) 누가거리를 알린다 — 유역이 없는 관도 전 화면 - * (그래프·3D·리스트)이 같은 것을 가리키게 한다(2026-08-17 전역 선택 동기화). */ - onPipeSelected?: (chainageM: number | null) => void; - /** 배수유역도 우클릭 빈 자리 메뉴 — 사이드 「구조물 배치」와 같은 구조물군 → 종류 - * 2단 항목(2026-08-18 일원화). 선택 = 폼 자동 지정(addAt 경유). */ - structureMenuItems?: (chainageM: number) => MapContextMenuItem[]; -} +// 바깥 계약(창구·콜백)은 `_Types.ts` 에 있다(2026-09-02 분리) — 호출부 경로 유지를 위해 재수출. +export type { DrainagePanel, DrainagePanelCallbacks } from "./B05_Profile_UI_Drainage_Panel_Types"; +import type { DrainagePanel, DrainagePanelCallbacks } from "./B05_Profile_UI_Drainage_Panel_Types"; export function createDrainagePanel(callbacks: DrainagePanelCallbacks = {}): DrainagePanel { const chrome = createDrainageChrome( diff --git a/B05_Profile/B05_Profile_UI_Drainage_Panel_Types.ts b/B05_Profile/B05_Profile_UI_Drainage_Panel_Types.ts new file mode 100644 index 00000000..f50a2375 --- /dev/null +++ b/B05_Profile/B05_Profile_UI_Drainage_Panel_Types.ts @@ -0,0 +1,74 @@ +/* ============================================================================= + * B05_Profile_UI_Drainage_Panel_Types.ts + * 배수유역 패널의 **바깥 계약** — 패널이 내주는 창구(`DrainagePanel`)와 페이지가 + * 넘기는 콜백(`DrainagePanelCallbacks`). + * + * `B05_Profile_UI_Drainage_Panel` 에서 떼어냈다(700줄 제한, 2026-09-02). 패널이 + * 그대로 다시 내보내므로 기존 import 경로는 유효하다. + * ========================================================================== */ + +import type { RoutePoint } from "./B05_Profile_Api_Fetch"; +import type { FacilityAttributes } from "./B05_Profile_UI_Drainage_Facility"; +import type { PipeFacility, PipeSource } from "../B04_PreProcess/B04_PreProcess_Api_Fetch"; +import type { MapContextMenuItem } from "@ui/ui_template_context_menu"; + +export interface DrainagePanel { + root: HTMLElement; + /** 프로젝트가 정해지면 배경지도·도엽 레이어를 불러온다. */ + load: (projectId: string) => void; + /** 확정된 노선 평면 선형(사업지 좌표계 m)을 지도 위에 겹친다. */ + setRoute: (points: ReadonlyArray) => void; + /** 현재 관 매설 누가거리 목록(종단 테이블의 "배관" 구조물 라인과 맞추는 데 쓴다). */ + pipeChainages: () => number[]; + /** 종단 테이블에서 배관 라인을 끌었을 때 — 그 자리로 옮기고 세부유역을 다시 나눈다. */ + movePipe: (fromChainage: number, toChainage: number) => void; + /** 관 목록을 통째로 맞춘다(사이드바 구조물 폼 편집 등 밖에서 바뀐 경우). + * 현재 목록과 같으면 아무 일도 하지 않는다 — 되먹임 고리를 끊는 지점이다. */ + setPipeChainages: (chainages: ReadonlyArray) => void; + /** 종단 테이블 우클릭·통합 목록에서 계곡 통과 시설을 넣을 때. + * attributes에 시설 종류·구간·부속 옵션이 담긴다(생략 = 맨 배관). */ + addPipe: (chainageM: number, attributes?: FacilityAttributes) => void; + /** 사이드 폼 [수정] — 기준점 이동·구간·부속 옵션을 정본에 반영하고 재계산한다. */ + updatePipeFacility: ( + fromChainageM: number, + toChainageM: number, + attributes: FacilityAttributes, + ) => void; + removePipe: (chainageM: number) => void; + /** 경로 확정 시 관 매설 지점을 영구저장한다(B04 "모델 확정"과 같은 저장소). */ + savePipes: () => Promise; + /** 밖에서 유역을 고른다(그래프 측점선·사이드 패널 선택과 맞추기 위함). 이미 같으면 무시. */ + selectBasinByChainage: (chainageM: number | null) => void; + /** 밖(리스트·그래프·3D)에서 관을 고른다 — 마커 선택·유역 강조를 맞춘다. + * 이미 같으면 무시(2026-08-17 전역 선택 동기화). */ + selectPipeAtChainage: (chainageM: number | null) => void; + /** 측점 선택 마킹 — 계획선 위 해당 누가거리에 표식을 그린다(null이면 지움). */ + markStation: (chainageM: number | null) => void; + dispose: () => void; +} + +export interface DrainagePanelCallbacks { + /** 관 목록이 바뀔 때마다 누가거리 + 담당 유역의 배수 유효직경(mm) + 시설 종류를 + * 넘긴다 — 종단 구조물 라인·통합 목록 동기화 및 관경 자동 지정(D800 기본)용. */ + onPipesChanged?: ( + pipes: Array<{ + chainage_m: number; + effective_diameter_mm: number | null; + /** 담당 유역의 설계유량(㎥/s, 100년빈도·2.0배). 물넘이·세월교 개략 단면의 입력이다. */ + design_flow_m3s?: number | null; + facility: PipeFacility; + start_m?: number; + end_m?: number; + source?: PipeSource; + options?: Record; + }>, + ) => void; + /** 유역을 고르거나 풀 때 그 관의 누가거리(없으면 null)를 넘긴다 — 그래프·사이드 패널 동기화용. */ + onBasinSelected?: (chainageM: number | null) => void; + /** 관 마커 선택이 바뀔 때(클릭·해제) 누가거리를 알린다 — 유역이 없는 관도 전 화면 + * (그래프·3D·리스트)이 같은 것을 가리키게 한다(2026-08-17 전역 선택 동기화). */ + onPipeSelected?: (chainageM: number | null) => void; + /** 배수유역도 우클릭 빈 자리 메뉴 — 사이드 「구조물 배치」와 같은 구조물군 → 종류 + * 2단 항목(2026-08-18 일원화). 선택 = 폼 자동 지정(addAt 경유). */ + structureMenuItems?: (chainageM: number) => MapContextMenuItem[]; +}