/* 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 CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) 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 (CATALOG.isCatalogRequest(request)) return CATALOG; 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));