feat(B05,B06): 독립 기슭막이를 배수관 시설 종류로 이관

독립 기슭막이(구조물 정본 D군 구간형)를 배수관 측점(pipe_points)의 시설 종류
"revetment"로 옮긴다. 관을 숨긴(hidden_pipe) 배관 세트로 얹혀 컴퓨트·렌더·패널·
연동·경사·3D·수량이 배관 경로를 그대로 탄다.

- common_util_drainage_pipes: PIPE_FACILITY_REVET 추가, 여유고 0
- B06_Section_Engine_Culvert: _revet_set/_revet_side — 관 숨김 세트 생성
- B06_Section_Router: attach_revetments 제거(관 세트에 통합)
- B05_Profile_Engine_Sections: 기슭막이는 시작·기준·종료 세 측점 심기
- B05_Profile_Structure_Types.json: revetment placement=point, managed_by=pipe_points,
  설치 측에 "양쪽" 추가
- B05_Profile_UI_Drainage_Facility: 기슭막이 옵션 폼(설치 측·형태·높이·길이·전후·단 수)
- B06_Section_UI_Cross_Culvert_Geom: 관 숨김 시 관경 하한·역경사 클램프·유출 가드 해제
- B06_Section_UI_Cross_Culvert_Wire: restrictToSide — 설치 측(좌/우)만 남기기
- B06_Section_UI_Cross_View: 옛 D군 경로 이중그리기 가드, appliedAdjust 되받기
- B06_Section_UI_Cross_Wall: 기슭막이 벽 공용 기하·그리기 층 신설
- B05_Profile_UI_Corridor_Structures: 3D도 같은 규칙(관 실린더 숨김, 관통 컷 없음)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-28 19:09:28 +09:00
co-authored by Claude Opus 5
parent 0050adf7d2
commit f916aa52a1
17 changed files with 636 additions and 263 deletions
+52 -31
View File
@@ -26,6 +26,7 @@ import {
type RockBoundaryControl,
} from "./B06_Section_UI_Cross_Design";
import { appendCulvertOverlay } from "./B06_Section_UI_Cross_Culvert";
import type { CulvertDesignTrim } from "./B06_Section_UI_Cross_Culvert";
import { appendBoxOverlay, computeBoxLayout } from "./B06_Section_UI_Cross_Box";
import type { BoxControl } from "./B06_Section_UI_Cross_Box_Panel";
import { createBodyWiring } from "./B06_Section_UI_Cross_View_Bodies";
@@ -442,27 +443,38 @@ export function createCrossSectionCard(
toDisplayY,
);
if (!fordPaved) appendPavementOverlay(plotLayer, section.design, x, toDisplayY);
// 독립 기슭막이 — 성토면 끝에 서는 벽. 3D도 같은 폴리곤을 스윕한다.
// 조작값(4축)은 배관 벽과 같은 저장소(design.revet_adjust["own"])를 쓴다.
const ownAdjust = revetOffset?.adjustFor(section, "own");
const ownLayout = computeRevetmentLayout(section, ownAdjust);
const setOwnActive = appendRevetmentOverlay(
plotLayer,
ownLayout,
x,
toDisplayY,
revetOffset ? () => toggleRevet("own") : undefined,
);
if (ownLayout && setOwnActive) {
// 재질은 구조물 옵션(형태)이 정본이라 조정창에서 숨긴다 — 자리만 채운다.
culvertWallSpecs.set("own", { height: ownLayout.heightM, material: "dry" });
culvertAppliedD.set("own", ownAdjust?.d ?? 0);
const baseSetActive = setRevetActive;
setRevetActive = (key) => {
baseSetActive(key);
setOwnActive(key === "own");
};
if (activeRevet === "own") setOwnActive(true);
// 독립 기슭막이(구 D군 경로) — 이 측점에 배관/숨김 기슭막이 세트(section.culvert)가
// 붙으면 그쪽(배관 경로)이 그린다. 아직 이관 안 된 정본만 이 옛 경로로 그린다
// (2026-08-28 이관: pipe_points 숨김 세트로 옮기는 중 — 이중 그리기 방지 가드).
let ownDesignTrim: CulvertDesignTrim | undefined;
// 이 측점에 배관/숨김 기슭막이 세트가 직접 붙었거나(section.culvert) **연동으로
// 옆에서 이어져 온**(culvertLink) 경우엔 배관 경로가 그린다 — 옛 D경로는 건너뛴다
// (둘 다 그리면 이웃 카드에 벽이 겹친다 — 2026-08-28 이관 이중그리기 방지).
if (!section.culvert && !culvertLink) {
const ownAdjust = revetOffset?.adjustFor(section, "own");
const ownLayout = computeRevetmentLayout(section, ownAdjust);
ownDesignTrim = ownLayout?.designTrim;
const setOwnActive = appendRevetmentOverlay(
plotLayer,
ownLayout,
x,
toDisplayY,
revetOffset ? () => toggleRevet("own") : undefined,
);
if (ownLayout && setOwnActive) {
// 한계에 잘린 실제 적용값을 조작 저장소에 되받는다(배관 벽과 같은 규칙) —
// 눌러도 안 움직이는데 숫자만 커지는 것 방지.
revetOffset?.syncApplied(section.chainage_m, "own", ownLayout.appliedAdjust);
// 재질은 구조물 옵션(형태)이 정본이라 조정창에서 숨긴다 — 자리만 채운다.
culvertWallSpecs.set("own", { height: ownLayout.heightM, material: "dry" });
culvertAppliedD.set("own", ownLayout.appliedAdjust.d ?? 0);
const baseSetActive = setRevetActive;
setRevetActive = (key) => {
baseSetActive(key);
setOwnActive(key === "own");
};
if (activeRevet === "own") setOwnActive(true);
}
}
appendCrossDesignOverlay(
plotLayer,
@@ -470,7 +482,10 @@ export function createCrossSectionCard(
x,
toDisplayY,
drawSamples,
culvertLayout?.designTrim ?? fordLayout?.designTrim ?? boxLayout?.designTrim,
culvertLayout?.designTrim ??
fordLayout?.designTrim ??
boxLayout?.designTrim ??
ownDesignTrim,
);
// 암 경계선 = 지면선 복사 + 오프셋(계획선 기준 아님).
if (rockBoundary && section.design.geometry_preset === "rock") {
@@ -484,7 +499,11 @@ export function createCrossSectionCard(
}
// 배수관 세트 — 조정창이 쓸 카드 상태를 받아 둔다(2026-08-19). 관은 소유 측점
// 전용이라 링크 카드에서는 길이를 비우고, 나머지는 링크 카드도 채운다(2026-08-24).
culvertPipeLengthM = isLinkedCulvert ? null : (culvertLayout?.pipe.lengthM ?? null);
// 숨김 기슭막이·링크 카드는 관이 없거나 옆에서 이어져 온 것 — 관 길이 표기 숨김.
culvertPipeLengthM =
isLinkedCulvert || culvertLayout?.culvert.hidden_pipe
? null
: (culvertLayout?.pipe.lengthM ?? null);
culvertInletIsBasin = !!culvertLayout?.basin;
if (culvertLayout) {
const state = culvertCardState(culvertLayout);
@@ -519,7 +538,8 @@ export function createCrossSectionCard(
x,
toDisplayY,
revetOffset ? toggleRevet : undefined,
isLinkedCulvert,
// 독립 기슭막이(hidden_pipe)는 관을 그리지 않는다 — 연동 링크 카드와 같은 규칙.
isLinkedCulvert || culvertLayout.culvert.hidden_pipe === true,
);
if (activeRevet) setRevetActive(activeRevet);
}
@@ -571,12 +591,6 @@ export function createCrossSectionCard(
y2: heightPx - CROSS_PAD.bottom,
class: "b06-chart__axis",
}),
svgText(L("B06_Profile_View_CrossXAxis"), {
x: widthPx / 2,
y: heightPx - 8,
"text-anchor": "middle",
class: "b06-chart__axis-label",
}),
svgText(L("B06_Profile_View_ElevationAxis"), {
x: 13,
y: heightPx / 2,
@@ -620,7 +634,14 @@ export function createCrossSectionCard(
// 구조물 조정은 도면 안 오버레이 창(2026-08-21). 화면 좌(◀) = offset 증가 —
// 벽 기준 이동량(outward 부호)으로 환산해 넘긴다.
const outwardOf = (role: RevetKey): number =>
((section.uphill_side ?? "left") === "left") === (role === "inlet") ? 1 : -1;
// 독립 기슭막이는 배관 유입/유출과 무관 — 정본 설치 측(좌 = +offset)으로 부호를 잡는다.
role === "own"
? section.revetment?.side === "우"
? -1
: 1
: ((section.uphill_side ?? "left") === "left") === (role === "inlet")
? 1
: -1;
const heightOfWall = (key: RevetKey): number => culvertWallSpecs.get(key)?.height ?? 0;
const materialOfWall = (key: RevetKey): RevetMaterial =>
culvertWallSpecs.get(key)?.material ?? "dry";