auto: 2026-09-02 16:52 (ESD_LAPTOP)
This commit is contained in:
@@ -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 포켓에 되메운다 — 다단이면 `성토 > 구조물 > 성토`라 성토선이
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -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<string | number> {
|
||||
// 물넘이 파임·독립 기슭막이는 다른 세트와 함께 설 수 있으니 앞에 이어 붙인다 —
|
||||
// 빼먹으면 값을 고쳐도 저장 코리도가 만료되지 않는다(2026-08-28).
|
||||
const extras: Array<string | number> = [];
|
||||
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";
|
||||
|
||||
@@ -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<string | number> {
|
||||
// 물넘이 파임·독립 기슭막이는 다른 세트와 함께 설 수 있으니 앞에 이어 붙인다 —
|
||||
// 빼먹으면 값을 고쳐도 저장 코리도가 만료되지 않는다(2026-08-28).
|
||||
const extras: Array<string | number> = [];
|
||||
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" : "",
|
||||
];
|
||||
}
|
||||
@@ -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<RoutePoint>) => void;
|
||||
/** 현재 관 매설 누가거리 목록(종단 테이블의 "배관" 구조물 라인과 맞추는 데 쓴다). */
|
||||
pipeChainages: () => number[];
|
||||
/** 종단 테이블에서 배관 라인을 끌었을 때 — 그 자리로 옮기고 세부유역을 다시 나눈다. */
|
||||
movePipe: (fromChainage: number, toChainage: number) => void;
|
||||
/** 관 목록을 통째로 맞춘다(사이드바 구조물 폼 편집 등 밖에서 바뀐 경우).
|
||||
* 현재 목록과 같으면 아무 일도 하지 않는다 — 되먹임 고리를 끊는 지점이다. */
|
||||
setPipeChainages: (chainages: ReadonlyArray<number>) => void;
|
||||
/** 종단 테이블 우클릭·통합 목록에서 계곡 통과 시설을 넣을 때.
|
||||
* attributes에 시설 종류·구간·부속 옵션이 담긴다(생략 = 맨 배관). */
|
||||
addPipe: (chainageM: number, attributes?: FacilityAttributes) => void;
|
||||
/** 사이드 폼 [수정] — 기준점 이동·구간·부속 옵션을 정본에 반영하고 재계산한다. */
|
||||
updatePipeFacility: (
|
||||
fromChainageM: number,
|
||||
toChainageM: number,
|
||||
attributes: FacilityAttributes,
|
||||
) => void;
|
||||
removePipe: (chainageM: number) => void;
|
||||
/** 경로 확정 시 관 매설 지점을 영구저장한다(B04 "모델 확정"과 같은 저장소). */
|
||||
savePipes: () => Promise<number>;
|
||||
/** 밖에서 유역을 고른다(그래프 측점선·사이드 패널 선택과 맞추기 위함). 이미 같으면 무시. */
|
||||
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<string, string | number>;
|
||||
}>,
|
||||
) => 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(
|
||||
|
||||
@@ -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<RoutePoint>) => void;
|
||||
/** 현재 관 매설 누가거리 목록(종단 테이블의 "배관" 구조물 라인과 맞추는 데 쓴다). */
|
||||
pipeChainages: () => number[];
|
||||
/** 종단 테이블에서 배관 라인을 끌었을 때 — 그 자리로 옮기고 세부유역을 다시 나눈다. */
|
||||
movePipe: (fromChainage: number, toChainage: number) => void;
|
||||
/** 관 목록을 통째로 맞춘다(사이드바 구조물 폼 편집 등 밖에서 바뀐 경우).
|
||||
* 현재 목록과 같으면 아무 일도 하지 않는다 — 되먹임 고리를 끊는 지점이다. */
|
||||
setPipeChainages: (chainages: ReadonlyArray<number>) => void;
|
||||
/** 종단 테이블 우클릭·통합 목록에서 계곡 통과 시설을 넣을 때.
|
||||
* attributes에 시설 종류·구간·부속 옵션이 담긴다(생략 = 맨 배관). */
|
||||
addPipe: (chainageM: number, attributes?: FacilityAttributes) => void;
|
||||
/** 사이드 폼 [수정] — 기준점 이동·구간·부속 옵션을 정본에 반영하고 재계산한다. */
|
||||
updatePipeFacility: (
|
||||
fromChainageM: number,
|
||||
toChainageM: number,
|
||||
attributes: FacilityAttributes,
|
||||
) => void;
|
||||
removePipe: (chainageM: number) => void;
|
||||
/** 경로 확정 시 관 매설 지점을 영구저장한다(B04 "모델 확정"과 같은 저장소). */
|
||||
savePipes: () => Promise<number>;
|
||||
/** 밖에서 유역을 고른다(그래프 측점선·사이드 패널 선택과 맞추기 위함). 이미 같으면 무시. */
|
||||
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<string, string | number>;
|
||||
}>,
|
||||
) => 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[];
|
||||
}
|
||||
Reference in New Issue
Block a user