Files
Aislo/B06_Section/B06_Section_UI_Page_Ford_Controls.ts
T
eomsangdonandClaude Opus 5 81bb505cc4 feat(B05·B06): 세월교 횡단 구체·조정창·3D 예상형상
B05 세월교 옵션에 BOX암거 날개벽 한 벌(설치·짧은쪽 높이 1m·길이 2m·각도 45°)을
그대로 붙이고, B06 횡단도에 세월교 구체를 그린다. 구체는 양측 ㄴ형 집수정 측벽 +
그 사이를 잇는 바닥판 + 바닥판 상면에 안힌 관 + 관 위 성토(노체) 채움이다.

- 바닥판 편측 연장 = 날개벽 길이 × cos(각도) — 각도는 관축 기준 벌어짐각
- 측벽은 buildBasin 재사용이라 1:0.3 기움·좌우·상하 대각 이동·계류측 성토부선이
  집수정과 같은 규칙으로 따라온다 (기슭막이 다단·재질과는 연계하지 않음)
- 구체는 월류 폭만큼 도로 방향으로 이어져 기준 측점 전후 절반까지 붙는다
- 조정창: 측벽 높이·좌우·상하, 관경·수량(정본 pipe_points 되쓰기).
  높이·좌우·상하는 세션 + design.ford_adjust에 저장
- 3D 예상형상: 구체 부재는 월류 폭 구간 스윕, 관은 련수만큼 폭 안 등간격
- 부재 두께: 바닥판 0.3m(교본 물넘이 물받이 최소 30cm), 측벽 0.2m(집수정 승계)

700줄 제한으로 카드 렌더러·측점 제어기·페이지에서 배선을 분리했다
(_Cross_View_Ford, _Page_Ford_Controls, _Page_Patches, Culvert_Const 구간값 헬퍼).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 12:05:42 +09:00

137 lines
5.6 KiB
TypeScript

/* =============================================================================
* B06_Section_UI_Page_Ford_Controls.ts
* 세월교 측벽 조작값 제어 — 측점 제어기(`_UI_Page_Station_Controls.ts`)에서 700줄
* 제한으로 분리했다(2026-08-25).
*
* 데이터 흐름은 집수정과 같다: 세션 사본 + 캐시 `design.ford_adjust`에 함께 실어
* 3D(코리도)가 같은 값으로 다시 그리게 한다. 관경·수량은 B05 정본(`pipe_points`)이
* 원천이라 배수관 구간값과 같은 저장기로 되돌려 쓴다.
* ========================================================================== */
import type { CrossDesign, CrossSection } from "./B06_Section_Api_Fetch";
import { DEFAULT_FORD_WALL_ADJUST } from "./B06_Section_UI_Cross_Ford";
import type { FordAdjust, FordWallAdjust, FordWallRole } from "./B06_Section_UI_Cross_Ford";
import type { FordControl } from "./B06_Section_UI_Cross_Ford_Panel";
export interface FordControlDeps {
/** 세션 보관 키(프로젝트·노선별). 없으면 세션에 담지 않는다. */
sessionKey: () => string | null;
sectionAt: (chainageM: number) => CrossSection | undefined;
patchCachedDesign: (chainageM: number, patch: Partial<CrossDesign>) => void;
refreshCard: (chainageM: number) => void;
/** 관경·수량을 B05 정본으로 되돌려 쓴다(묶어서 늦게 저장). */
queuePipeOptions: (chainageM: number, patch: Record<string, number>) => void;
round1: (value: number) => number;
clampMove: (value: number) => number;
}
export interface FordControls {
control: FordControl;
/** 확정 payload용 — 측점별 조작값. */
byChainage: () => Map<number, FordAdjust>;
/** 노선이 바뀔 때 세션 값을 다시 읽는다. */
load: () => void;
}
export function createFordControls(deps: FordControlDeps): FordControls {
const { round1, clampMove, sectionAt, patchCachedDesign } = deps;
/* ── 세월교 측벽 조작(2026-08-25 사용자) ───────────────────────────────
* 축은 집수정과 같다(높이·좌우·상하 대각). 값은 세션 사본 + 캐시 design에 함께
* 실어 3D가 같은 값으로 다시 그리게 한다. 관경·수량은 B05 정본이 원천이라
* 배수관 구간값과 같은 저장기(`pipe_points` 되쓰기)로 보낸다. */
const fordAdjustments = new Map<string, FordAdjust>();
const fordSelections = new Map<string, FordWallRole | null>();
const fordSessionKey = (): string | null => deps.sessionKey();
function loadFordAdjustments(): void {
fordAdjustments.clear();
const key = fordSessionKey();
if (!key) return;
try {
const parsed = JSON.parse(window.sessionStorage.getItem(key) ?? "{}") as Record<
string,
FordAdjust
>;
Object.entries(parsed).forEach(([chainage, value]) =>
fordAdjustments.set(chainage, {
inlet: { ...DEFAULT_FORD_WALL_ADJUST, ...value.inlet },
outlet: { ...DEFAULT_FORD_WALL_ADJUST, ...value.outlet },
}),
);
} catch {
/* 손상된 세션 값은 기본값으로 대체. */
}
}
function persistFordAdjustments(): void {
const key = fordSessionKey();
if (key)
window.sessionStorage.setItem(key, JSON.stringify(Object.fromEntries(fordAdjustments)));
}
const fordAdjustAt = (chainageM: number): FordAdjust => {
const stored = sectionAt(chainageM)?.design?.ford_adjust;
return (
fordAdjustments.get(chainageM.toFixed(2)) ?? {
inlet: { ...DEFAULT_FORD_WALL_ADJUST, ...(stored?.inlet ?? {}) },
outlet: { ...DEFAULT_FORD_WALL_ADJUST, ...(stored?.outlet ?? {}) },
}
);
};
const writeFord = (chainageM: number, next: FordAdjust): void => {
fordAdjustments.set(chainageM.toFixed(2), next);
persistFordAdjustments();
patchCachedDesign(chainageM, { ford_adjust: next });
deps.refreshCard(chainageM);
};
const fordControl: FordControl = {
adjustFor: fordAdjustAt,
update: (chainageM, role, patch) => {
const current = fordAdjustAt(chainageM);
const wall: FordWallAdjust = { ...current[role], ...patch };
writeFord(chainageM, {
...current,
[role]: {
// 높이는 0.1m 눈금, 하한(관경+토피)은 기하가 다시 잡는다.
heightM: wall.heightM === null ? null : Math.max(round1(wall.heightM), 0),
lateralM: Math.max(0, clampMove(wall.lateralM)),
slopeM: clampMove(wall.slopeM),
},
});
},
reset: (chainageM, role) => {
const current = fordAdjustAt(chainageM);
writeFord(chainageM, { ...current, [role]: { ...DEFAULT_FORD_WALL_ADJUST } });
},
setPipe: (chainageM, patch) => {
const spec = sectionAt(chainageM)?.ford;
if (spec) {
// 캐시를 먼저 고쳐 즉시 반영한다 — 저장은 늦게 묶어서 간다.
if (patch.pipe_diameter_mm) spec.diameter_m = patch.pipe_diameter_mm / 1000;
if (patch.pipe_count) spec.pipe_count = patch.pipe_count;
}
deps.queuePipeOptions(chainageM, patch);
deps.refreshCard(chainageM);
},
selectedFor: (chainageM) => fordSelections.get(chainageM.toFixed(2)) ?? null,
select: (chainageM, role) => {
fordSelections.set(chainageM.toFixed(2), role);
},
};
return {
control: fordControl,
byChainage: () => {
const result = new Map<number, FordAdjust>();
fordAdjustments.forEach((adjust, key) => {
const value = Number(key);
if (Number.isFinite(value)) result.set(value, adjust);
});
return result;
},
load: loadFordAdjustments,
};
}