- E03_StabSection_Walls_Node.ts — B06 computeStoredLayouts(옹벽 · 석축 · 돌쌓기 · 관 기슭막이) 꼭짓점 · drawnProfile 지표 · 계산 없음
- E03_StabSection_Walls.py — walls_by_station(복사본 루트) → {chainage_key: {walls, design_line}} · design_with_walls · walls_of · 입구 TS 가 묶음보다 새로우면 다시 지음
- common_util_cross_structure_areas.ts drawnProfile 내보냄(동작 그대로) · package.json build:e03-walls 줄 · .gitignore 묶음 폴더
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
103 lines
4.5 KiB
TypeScript
103 lines
4.5 KiB
TypeScript
/* =============================================================================
|
|
* E03_StabSection_Walls_Node.ts
|
|
* E 2D 해석 모형의 벽(옹벽 · 석축 · 돌쌓기 · 관 기슭막이) 단면 꼭짓점을 **서버가** 내는 입구(532-4).
|
|
*
|
|
* 도형은 B06 화면이 그리는 TS 그대로(`computeStoredLayouts` — 정본만 읽는 한 벌 · CLAUDE.md 5장 ②) ·
|
|
* 지표 = B06 면적과 같은 「실제로 그려지는 설계선」(`drawnProfile` · 벽 상단에서 끊김). 여기에 계산 없음.
|
|
* 파이썬 껍데기 = `E03_StabSection_Walls.py` · 번들 = `npm run build:e03-walls`.
|
|
*
|
|
* 실행: node <번들> <입력.json> <출력.json>
|
|
* 입력 { definition?: 구조물 정의 · sections: 형상 얹은 B06 횡단(chainage_m · samples · design · revetment …) }
|
|
* 출력 { stations: [{ chainage_m, walls: [{ kind, name, structure_id, form, polygon: [[offset, z], …] }],
|
|
* design_line: [[offset, z], …] | null(트림 없음 = 설계선 그대로) }] } — 입력 차례 그대로
|
|
* 끝 코드: 0 성공 / 2 인자 오류
|
|
* ========================================================================== */
|
|
|
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
import { drawnProfile } from "@util/common_util_cross_structure_areas";
|
|
import { useCatalogDefinition, type CatalogDefinition } from "@util/common_util_structure_catalog";
|
|
import type { CrossSection } from "../B06_Section/B06_Section_Api_Fetch";
|
|
import type { WallLayout } from "../B06_Section/logic/B06_Section_Cross_Culvert_Types";
|
|
import {
|
|
computeStoredLayouts,
|
|
trimOfLayouts,
|
|
} from "../B06_Section/logic/B06_Section_Structure_Layouts";
|
|
|
|
/** 트림 경계 바로 바깥 점 — 벽 쪽 연직 단차를 살림(`computeStructureAreas` 와 같은 값). */
|
|
const BOUNDARY_EPS_M = 1e-6;
|
|
|
|
type Pair = [number, number];
|
|
|
|
function stationWalls(section: CrossSection, sections: readonly CrossSection[]) {
|
|
const layouts = computeStoredLayouts(section, sections);
|
|
const walls: Array<{
|
|
kind: string;
|
|
name: string | null;
|
|
structure_id: string | null;
|
|
form: string | null;
|
|
polygon: Pair[];
|
|
}> = [];
|
|
const push = (kind: string, list: readonly WallLayout[] | undefined, name: string | null) => {
|
|
for (const wall of list ?? []) {
|
|
if (wall.points.length >= 3)
|
|
walls.push({
|
|
kind,
|
|
name,
|
|
structure_id: kind === "own" ? (layouts?.own?.structureId ?? null) : null,
|
|
form: wall.form,
|
|
polygon: wall.points.map((p) => [p.offset, p.elevation]),
|
|
});
|
|
}
|
|
};
|
|
push("own", layouts?.own?.walls, layouts?.own?.name ?? null);
|
|
push("culvert", layouts?.culvert?.walls, "기슭막이");
|
|
push("culvert", layouts?.culvert?.basinExtras, "기슭막이");
|
|
const trim = layouts ? trimOfLayouts(layouts) : null;
|
|
const line = layouts?.design.design_line;
|
|
let design_line: Pair[] | null = null;
|
|
if (trim && Array.isArray(line) && line.length >= 2) {
|
|
const ground = section.samples
|
|
.filter((s) => s.valid !== false && typeof s.elevation_m === "number")
|
|
.map((s) => ({ offset: s.offset_m ?? 0, elevation: s.elevation_m as number }))
|
|
.sort((a, b) => a.offset - b.offset);
|
|
const profile = drawnProfile({
|
|
designLine: line as Array<{ offset_m: number; elevation_m: number }>,
|
|
ground,
|
|
trim,
|
|
});
|
|
if (profile && ground.length >= 2) {
|
|
const lo = ground[0].offset;
|
|
const hi = ground[ground.length - 1].offset;
|
|
const grid = new Set<number>();
|
|
const add = (o: number) => {
|
|
if (o >= lo && o <= hi) grid.add(Math.round(o * 1e6) / 1e6);
|
|
};
|
|
ground.forEach((g) => add(g.offset));
|
|
line.forEach((p) => add(p.offset_m));
|
|
for (const b of [trim.minOffset, trim.maxOffset])
|
|
[b, b - BOUNDARY_EPS_M, b + BOUNDARY_EPS_M].forEach(add);
|
|
trim.minSlope?.points.forEach((p) => add(p.offset));
|
|
trim.maxSlope?.points.forEach((p) => add(p.offset));
|
|
design_line = [...grid].sort((a, b) => a - b).map((o) => [o, profile.drawnZ(o)]);
|
|
}
|
|
}
|
|
return { chainage_m: section.chainage_m, walls, design_line };
|
|
}
|
|
|
|
const [inputPath, outputPath] = process.argv.slice(2);
|
|
if (!inputPath || !outputPath) {
|
|
console.error("사용법: node <번들> <입력.json> <출력.json>");
|
|
process.exit(2);
|
|
}
|
|
const input = JSON.parse(readFileSync(inputPath, "utf8")) as {
|
|
definition?: CatalogDefinition | null;
|
|
sections: CrossSection[];
|
|
};
|
|
if (input.definition) useCatalogDefinition(input.definition);
|
|
const sections = input.sections ?? [];
|
|
writeFileSync(
|
|
outputPath,
|
|
JSON.stringify({ stations: sections.map((s) => stationWalls(s, sections)) }),
|
|
"utf8",
|
|
);
|