fix(B06): 유출 관 끝점·전면 근입 기준 + 줌 유지·조정창 레이아웃 정리

- 유출 관 하단 끝점 = 벽 바닥 +0.5m 기준선과 전면 경사선(1:0.3)의 교차점
  (성토부선 시작점과 동일 자리) — 정수 맞춤 재배치에도 동일 적용
- 근입 0.5m는 경사선(전면) 측 깊이 기준 — 전면 발끝 지반 아래 0.5까지
  수렴 반복으로 내림(배관 벽·추가 벽 공통, 뜬 벽은 기준선 아래 0.5)
- 카드 내부 조작(조정창 등)으로 다시 그려져도 줌·팬 배율 유지
  (측점별 상태 보관, ZoomPanState)
- 조정창: 재질 행을 높이 행 위 별도 행으로(폭 축소 — 한계는 툴팁),
  추가 기슭막이(단) 행은 유출 벽에서 항상 표시(레이아웃 널뜀 방지)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-22 14:32:30 +09:00
co-authored by Claude Opus 5
parent ff3e3b734b
commit 97bf00a8ef
6 changed files with 111 additions and 53 deletions
+20 -15
View File
@@ -32,6 +32,7 @@ import type {
} from "./B06_Section_UI_Cross_Culvert_Wire";
import { buildStructurePanel } from "./B06_Section_UI_Cross_Structure_Panel";
import { attachZoomPan, buildZoomControls } from "./B06_Section_UI_Cross_View_Zoom";
import type { ZoomPanState } from "./B06_Section_UI_Cross_View_Zoom";
import type { RevetHighlightSetter, RevetKey } from "./B06_Section_UI_Cross_Culvert";
import type { RevetMaterial } from "./B06_Section_UI_Cross_Culvert_Const";
import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types";
@@ -166,14 +167,16 @@ export interface StationWidthControl {
reset: (chainageM: number) => void;
}
// 기슭막이·유입 구조물·다단 제어 인터페이스는 Wire로 옮겼다(700줄 제한) —
// 기존 import 경로 유지를 위해 재수출한다.
// 기슭막이·유입 구조물·다단 제어 인터페이스는 Wire에 있다(700줄 제한) — 재수출.
export type {
ExtraWallControl,
InletStructureControl,
RevetOffsetControl,
} from "./B06_Section_UI_Cross_Culvert_Wire";
/** 측점별 줌·팬 상태 — 카드 재생성에도 배율 유지(2026-08-22 사용자 ③). */
const cardZoomStates = new Map<string, ZoomPanState>();
export function createCrossSectionCard(
section: CrossSection,
selected: boolean,
@@ -219,15 +222,14 @@ export function createCrossSectionCard(
let activeRevet: RevetKey | null = revetOffset?.selectedFor(section) ?? null;
let setRevetActive: RevetHighlightSetter = () => undefined;
let showRevetControl: (visible: boolean) => void = () => undefined;
/** 지금 그린 관 길이(m) — 조정창이 "관 길이 8m"으로 적는다. 배수관 측점이 아니면 null. */
/** 지금 그린 관 길이(m) — 조정창 표기용. 배수관 측점이 아니면 null. */
let culvertPipeLengthM: number | null = null;
/** 유입이 집수정인가 — 조정창이 ◀/▶ 표시 여부·제목을 가른다. */
let culvertInletIsBasin = false;
let culvertInletIsBasin = false; // 유입이 집수정인가 — 조정창 표시·제목 분기
/** 드롭다운 선택지 가용성(2026-08-22) — 기하 판정을 조정창에 전달. */
let culvertInletOptions = { revetAllowed: true, basinLUAllowed: true };
/** 추가 기슭막이 상태(2026-08-22) — 끝 성토부 5m 이상(canAdd)·세워진 개수. */
let culvertExtraState = { canAdd: false, count: 0 };
/** 마지막 계산의 벽 제원(높이·재질) — 조정창 높이 행 표시·높이 조작 기준. */
/** 마지막 계산의 벽 제원(높이·재질) — 조정창 표시·높이 조작 기준. */
let culvertWallSpecs = new Map<RevetKey, { height: number; material: RevetMaterial }>();
/**
* 기슭막이를 고른다. 구조물 선택과 절·성토 면적 강조는 **같은 레벨**이라 하나를
@@ -495,8 +497,7 @@ export function createCrossSectionCard(
toDisplayY,
);
}
// 배수관 측점 세트 — 기존 도형 위에 추가만 한다(2026-08-19). 조정창이 쓸
// 카드 상태(관 길이·집수정 여부·선택지·추가 벽 상태)를 여기서 받아 둔다.
// 배수관 세트 — 조정창이 쓸 카드 상태를 여기서 받아 둔다(2026-08-19).
culvertPipeLengthM = culvertLayout?.pipe.lengthM ?? null;
culvertInletIsBasin = !!culvertLayout?.basin;
if (culvertLayout) {
@@ -588,13 +589,19 @@ export function createCrossSectionCard(
// 확대·축소 버튼, 드래그 팬, 더블클릭 원복(E-5). 도형 레이어 transform + non-scaling-stroke.
const chartWrap = document.createElement("div");
chartWrap.className = "b06-cross-card__chart-wrap";
const zoomPan = attachZoomPan(svg, plotLayer, widthPx, heightPx);
const zoomPan = attachZoomPan(
svg,
plotLayer,
widthPx,
heightPx,
cardZoomStates.get(section.station_id),
(state) => cardZoomStates.set(section.station_id, state),
);
// 절·성토 면적값은 그래프 중상단 오버레이로 표시(E-4). 값 칸은 항상 강조 토글이다.
const readout = buildAreaReadout(section.design, toggleArea);
setChipActive = readout.setActive;
// 구조물 위치 조정은 **도면 안 오버레이 창**으로 한다(2026-08-21 사용자 확정).
// 벽이 서는 쪽(outward): 상단측(유입)이 좌측이면 유입 벽은 좌(+), 유출 벽은 우(−).
// 화면 좌(◀)로 민다 = offset이 커진다 — 벽 기준 이동량으로 환산해 넘긴다.
// 구조물 조정은 도면 안 오버레이 창(2026-08-21). 화면 좌(◀) = offset 증가 —
// 벽 기준 이동량(outward 부호)으로 환산해 넘긴다.
const outwardOf = (role: RevetKey): number =>
((section.uphill_side ?? "left") === "left") === (role === "inlet") ? 1 : -1;
const heightOfWall = (key: RevetKey): number => culvertWallSpecs.get(key)?.height ?? 0;
@@ -608,7 +615,6 @@ export function createCrossSectionCard(
revetOffset?.update(section.chainage_m, key, {
x: adjustOf(key).x + screenDeltaM * outwardOf(key),
}),
// 상하(대각) — ▲(위) = 성토선을 타고 노견 쪽, ▼(아래) = 계류 쪽. 수평 성분 1m.
nudgeSlope: (key, deltaM) =>
revetOffset?.update(section.chainage_m, key, { d: adjustOf(key).d + deltaM }),
nudgeHeight: (key, deltaM) => {
@@ -633,8 +639,7 @@ export function createCrossSectionCard(
optionsFor: () => culvertInletOptions,
extraState: () => culvertExtraState,
setExtraCount: (count) => {
// 줄어들며 사라지는 벽이 선택 중이면 선택을 유출 벽으로 옮긴다 — 다시
// 그려질 때 없는 키를 강조하려다 조정창이 빈 대상으로 남는 것을 막는다.
// 줄어들며 사라지는 벽이 선택 중이면 선택을 유출 벽으로 옮긴다(빈 대상 방지).
if (activeRevet?.startsWith("extra") && Number(activeRevet.slice(5)) >= count) {
activeRevet = "outlet";
revetOffset?.select(section.chainage_m, "outlet");