Files
Aislo/resources/tester/helper_107_ford_free.cjs
T

173 lines
6.3 KiB
JavaScript

/* 107 세월교 헬퍼 — 화면 TS 를 그 자리에서 트랜스파일해 Node 로 돌림.
* 입력 = 73-4 픽스처(세월교 173.09 · 박스 352.14) · 결과 = 표준출력 JSON 한 벌. 짝 = `test_107_ford_free.py`.
*/
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const ROOT = path.join(__dirname, "..", "..");
const cache = new Map();
// 화면 전용 모듈(토스트 · 글) — 기하 계산엔 안 쓰임
const STUBS = {
"@ui/ui_template_toast": { showToast: () => undefined },
};
function loadTs(file) {
const full = path.resolve(file);
if (cache.has(full)) return cache.get(full);
if (full.endsWith(".json")) {
const data = JSON.parse(fs.readFileSync(full, "utf8"));
cache.set(full, data);
return data;
}
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
compilerOptions: {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2020,
esModuleInterop: true,
},
}).outputText;
const box = { exports: {} };
cache.set(full, box.exports);
const localRequire = (request) => {
if (STUBS[request]) return STUBS[request];
const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" };
for (const [prefix, dir] of Object.entries(alias))
if (request.startsWith(prefix))
return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length))));
return loadTs(resolve(path.join(path.dirname(full), request)));
};
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
cache.set(full, box.exports);
return box.exports;
}
const resolve = (base) =>
[base, `${base}.ts`, path.join(base, "index.ts")].find(
(p) => fs.existsSync(p) && fs.statSync(p).isFile(),
) ?? `${base}.ts`;
const B06 = (f) => path.join(ROOT, "B06_Section", f);
const out = {};
// 107-1 — 세월교 · 박스를 고르면 같은 측점 벽 고름을 풂(푸는 것은 안 부름)
const { createBodyControls } = loadTs(B06("B06_Section_UI_Page_Ford_Controls.ts"));
const released = [];
const bodies = createBodyControls(
{
sectionAt: () => undefined,
patchCachedDesign: () => undefined,
refreshCard: () => undefined,
queuePipeOptions: () => undefined,
round1: (v) => Math.round(v * 10) / 10,
clampMove: (v) => v,
releaseWall: (chainageM) => released.push(chainageM),
},
() => null,
);
bodies.ford.control.select(173.09, "inlet");
bodies.ford.control.select(173.09, null);
bodies.box.control.select(352.14, "left");
out.released = released;
// 107-2 — 원지반을 바꾼 사본: 세월교 바닥 = 노면 − (관경 + 토피) · 박스 본체 · 끝 · 날개 · 흙막이 자리 그대로
const { computeStoredLayouts } = loadTs(B06("B06_Section_Structure_Layouts.ts"));
const fixture = JSON.parse(
fs.readFileSync(path.join(__dirname, "fixtures", "b06_73_4_body_guard_sections.json"), "utf8"),
);
const sections = fixture.cross_sections;
const at = (c) => sections.find((x) => Math.abs(x.chainage_m - c) < 0.6);
const clone = (v) => JSON.parse(JSON.stringify(v));
const r3 = (v) => Math.round(v * 1000) / 1000;
const pts = (list) => (list ?? []).map((p) => [r3(p.offset), r3(p.elevation)]);
/** 원지반 사본 — [from, to] 안 샘플을 dz 만큼 옮김. */
const shiftGround = (x, dz, from = -Infinity, to = Infinity) => {
for (const p of x.samples)
if (p.offset_m >= from && p.offset_m <= to && p.elevation_m != null) p.elevation_m += dz;
};
const GROUNDS = {
base: () => undefined,
bedDown2: (x) => shiftGround(x, -2, -2, 2),
allUp05: (x) => shiftGround(x, 0.5),
allDown1: (x) => shiftGround(x, -1),
};
const walls = (culvert) =>
(culvert?.walls ?? []).filter((w) => w.role !== "extra").map((w) => pts(w.points));
function fordShape(base, edit, ground) {
const x = clone(base);
edit(x);
ground(x);
const { ford, culvert } = computeStoredLayouts(
x,
sections.map((y) => (y === base ? x : y)),
);
return {
sides: ford.sides.map((side) => ({
role: side.role,
parts: side.parts.map((part) => pts(part.points)),
slabTop: r3(side.slabTopElevation),
// 노면(벽 상단) − 바닥판 상면 = 관경 + 최소 토피
depth: r3(side.top.elevation - side.slabTopElevation),
})),
pipeCover: r3(ford.ford.diameter_m + ford.ford.min_cover_m),
slab: pts(ford.slabBridge),
pipe: pts(ford.pipe.outer),
walls: walls(culvert),
};
}
function boxShape(base, edit, ground) {
const x = clone(base);
edit(x);
ground(x);
const { box, culvert } = computeStoredLayouts(
x,
sections.map((y) => (y === base ? x : y)),
);
return {
top: pts(box.topSlab),
bottom: pts(box.bottomSlab),
barrel: pts(box.barrel),
sides: box.sides.map((side) => ({
role: side.role,
offset: r3(side.offset),
top: r3(side.topElevation),
bottom: r3(side.bottomElevation),
apron: pts(side.apron),
})),
walls: walls(culvert),
};
}
const FORD = at(173.087);
const BOX = at(352.14);
const keep = () => undefined;
const inletWing = (x) => {
x.culvert.inlet = { role: "inlet", structure: "" };
};
// 날개벽 박스 — 흙막이 없이 양쪽 날개벽(에이프런 1.414 m)
const noGuard = (x) => {
delete x.culvert;
for (const wing of [x.box.wing_in, x.box.wing_out])
Object.assign(wing, { installed: true, slab_extend_m: 1.414 });
};
const run = (shape, base, edit) =>
Object.fromEntries(Object.entries(GROUNDS).map(([k, g]) => [k, shape(base, edit, g)]));
out.ford = { guards: run(fordShape, FORD, keep), inletWing: run(fordShape, FORD, inletWing) };
out.box = { guards: run(boxShape, BOX, keep), bare: run(boxShape, BOX, noGuard) };
// 107-3 — 유입 날개벽 끝이 묻히면 수평 여유(흙막이 수평공간과 같은 칸 · 기본 1 m) 뒤 절토
function wingCut(berm) {
const x = clone(FORD);
delete x.culvert;
for (const wing of [x.ford.wing_in, x.ford.wing_out])
Object.assign(wing, { installed: true, slab_extend_m: 1.414 });
x.ford.wing_in.berm_m = berm;
shiftGround(x, 3); // 바닥판 끝을 원지반 안으로
const { ford } = computeStoredLayouts(
x,
sections.map((y) => (y === FORD ? x : y)),
);
return Object.fromEntries(ford.sides.map((side) => [side.role, pts(side.cutLine)]));
}
out.wingCut = { auto: wingCut(null), two: wingCut(2), zero: wingCut(0) };
process.stdout.write(JSON.stringify(out));