feat(B06): 구조물 면적·유토곡선 서버 계산 자리 마련 + 상단측 저장 누락 수정 계산 자리 일원화(CLAUDE.md 5장) — 브라우저에서만 돌던 두 계산을 서버가 같은 TS 로 한 번 더 돌려 정본에 얹음. 파이썬 포팅 금지(기하가 두 벌이 되면 그림과 수량이 갈림). - B06_Section_Server_Calc_Node.ts 신설 — 구조물 폐회로 면적 계산 후 그 위에서 유토곡선을 쌓음(화면과 같은 순서). balloon 위치는 서버가 만들지 않음. - B06_Section_Structure_Layouts.ts 신설 — 정본만 읽는 제어기 흉내를 B07 도면에서 떼어 공용화. B07·서버가 같은 한 벌을 씀. - common_util_node_bundle.py 신설 — 번들 빌드·실행 배관 공용화(코리도도 이걸 씀). - 전처리 체인(초기값 스냅샷 앞)·[저장]·[확정]에서 서버 재계산 호출. - 상단측(측구 방향) 변경이 B05 [임시저장]에만 실리던 것을 B06 [저장]·[확정]에도 실음 — flushUphillOverrides. - 죽은 세션 등록 항목 pipes 제거(읽는 곳도 쓰는 곳도 없었음). 검증: tsc --noEmit 통과, pytest 386 passed, tmp/tests/test_b06_server_calc_node.mjs 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> @
108 lines
4.8 KiB
TypeScript
108 lines
4.8 KiB
TypeScript
/* =============================================================================
|
|
* 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<WallAdjust>) } : { ...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<ReturnType<typeof computeStoredLayouts>>;
|
|
|
|
/** 실제로 그려지는 설계선의 트림 — B06 카드와 **같은 우선순위**로 고른다. */
|
|
export function trimOfLayouts(layouts: StoredLayouts) {
|
|
return (
|
|
layouts.culvert?.designTrim ??
|
|
layouts.ford?.designTrim ??
|
|
layouts.box?.designTrim ??
|
|
layouts.own?.designTrim
|
|
);
|
|
}
|