/* ============================================================================= * B06_Section_Structure_Layouts.ts * 정본(`section.design`)만 읽어 **구조물 기하 한 벌**을 내는 자리 — 화면·조작 없이 돈다. * * 왜 있나(2026-09-06, CLAUDE.md 5장 「계산 자리」) — 같은 기하를 세 곳이 쓴다: * ① B06 횡단 카드(사용자 조작 중) ② B07 도면 작도 ③ 서버 초기 계산(Node 진입점). * ①은 조작 제어기를 물고 돌아야 하고, ②·③은 저장된 값만 보면 된다. ②가 갖고 있던 * 「제어기 흉내내기」를 여기로 옮겨 ③이 그대로 쓴다 — 기하를 두 벌로 만들지 않는다. * * DOM·SVG 를 부르지 않는다. 브라우저에서도 Node 에서도 같은 결과가 나와야 한다. * ========================================================================== */ import type { CrossSection } from "./B06_Section_Api_Fetch"; import { computeBoxLayout, DEFAULT_BOX_SIDE_ADJUST } from "./B06_Section_UI_Cross_Box_Geom"; import { DEFAULT_BASIN_ADJUST, ZERO_ADJUST } from "./B06_Section_UI_Cross_Culvert_Types"; import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types"; import { computeCardCulvert, culvertLinkFor } from "./B06_Section_UI_Cross_Culvert_Wire"; import type { ExtraWallControl, InletStructureControl, RevetOffsetControl, } from "./B06_Section_UI_Cross_Culvert_Wire"; import { computeFordLayout, DEFAULT_FORD_WALL_ADJUST } from "./B06_Section_UI_Cross_Ford_Geom"; import { computeRevetmentLayout } from "./B06_Section_UI_Cross_Revetment"; /** 같은 측점으로 볼 누가거리 오차(m) — 정본 반올림 자릿수보다 크게 잡는다. */ const CHAINAGE_TOLERANCE_M = 0.02; function storedWallAdjust(section: CrossSection, role: string): WallAdjust { const stored = section.design?.revet_adjust?.[role]; return stored ? { ...ZERO_ADJUST, ...(stored as Partial) } : { ...ZERO_ADJUST }; } /* 정본만 읽는 조작값 — 편집하지 않으므로 되받기·토스트는 빈 동작이다. */ const revetOffset: RevetOffsetControl = { adjustFor: (section, role) => storedWallAdjust(section, role), storedAdjustFor: (section, role) => section.design?.revet_adjust?.[role] ? storedWallAdjust(section, role) : null, selectedFor: () => null, highlightFor: () => null, select: () => undefined, syncApplied: () => undefined, update: () => undefined, reset: () => undefined, }; const extraWalls: ExtraWallControl = { countFor: (section, side = "outlet") => section.design?.extra_wall_counts?.[side] ?? 0, setCount: () => undefined, equalize: () => undefined, consumeEqualize: () => false, syncCount: () => undefined, }; const inletStructure: InletStructureControl = { valueFor: (section) => section.design?.inlet_structure ?? "auto", adjustFor: (section) => ({ ...DEFAULT_BASIN_ADJUST, ...(section.design?.basin_adjust ?? {}) }), set: () => undefined, updateAdjust: () => undefined, resetAdjust: () => undefined, }; /** 한 측점의 구조물 기하 한 벌 — 설계선 트림과 구조물 그리기가 같은 결과를 나눠 쓴다. */ export function computeStoredLayouts(section: CrossSection, sections: readonly CrossSection[]) { const design = section.design; if (!design) return null; const designZAt = (chainageM: number): number | null => { const found = sections.find( (item) => Math.abs(item.chainage_m - chainageM) <= CHAINAGE_TOLERANCE_M, ); return found?.design?.design_elevation_m ?? null; }; const link = section.culvert ? undefined : culvertLinkFor(section, sections, designZAt); const culvert = computeCardCulvert( section, section.samples, null, revetOffset, inletStructure, extraWalls, link, ); const box = computeBoxLayout(section, section.samples, { left: { ...DEFAULT_BOX_SIDE_ADJUST, ...(design.box_adjust?.left ?? {}) }, right: { ...DEFAULT_BOX_SIDE_ADJUST, ...(design.box_adjust?.right ?? {}) }, }); const ford = computeFordLayout(section, section.samples, { inlet: { ...DEFAULT_FORD_WALL_ADJUST, ...(design.ford_adjust?.inlet ?? {}) }, outlet: { ...DEFAULT_FORD_WALL_ADJUST, ...(design.ford_adjust?.outlet ?? {}) }, }); // 독립 기슭막이(옛 D군 경로) — 배관 세트가 붙었거나 옆에서 이어져 오면 그쪽이 그린다. const own = !section.culvert && !link ? computeRevetmentLayout(section, design.revet_adjust?.own) : null; return { design, link, culvert, box, ford, own }; } export type StoredLayouts = NonNullable>; /** 실제로 그려지는 설계선의 트림 — B06 카드와 **같은 우선순위**로 고른다. */ export function trimOfLayouts(layouts: StoredLayouts) { return ( layouts.culvert?.designTrim ?? layouts.ford?.designTrim ?? layouts.box?.designTrim ?? layouts.own?.designTrim ); }