/* 294-6 물넘이 연결 표기 헬퍼 — 진짜 TS 를 Node 로 돌려 표준출력 JSON 한 벌로. * 옆 카드(물넘이 측점 ≠ 카드 측점) · 주인 카드에 물넘이포장을 그리고 연결 꼴 · 호버 글 · 고름 표식을 잼. */ 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 load = (file) => { if (cache.has(file)) return cache.get(file); const js = ts.transpileModule(fs.readFileSync(file, "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const box = { exports: {} }; cache.set(file, box.exports); const near = (m) => { if (m.startsWith("@config/")) return load(path.join(ROOT, "config", `${m.slice(8)}.ts`)); if (m.startsWith(".")) return load(path.join(path.dirname(file), `${m}.ts`)); return new Proxy({}, { get: () => () => {} }); }; new Function("exports", "module", "require", js)(box.exports, box, near); return box.exports; }; // DOM 흉내 — 속성 · 자식 · classList · 글만 class Node { constructor(tag) { this.tag = tag; this.attrs = {}; this.children = []; this.dataset = {}; this.style = {}; this.textContent = ""; } setAttribute(k, v) { this.attrs[k] = String(v); } getAttribute(k) { return this.attrs[k] ?? null; } hasAttribute(k) { return k in this.attrs; } append(...kids) { this.children.push(...kids); } get classList() { const names = () => (this.attrs.class ?? "").split(" ").filter(Boolean); const set = (list) => (this.attrs.class = list.join(" ")); return { contains: (c) => names().includes(c), add: (c) => names().includes(c) || set([...names(), c]), toggle: (c, on) => set(on ? [...names().filter((n) => n !== c), c] : names().filter((n) => n !== c)), }; } querySelectorAll(selector) { const attr = /^\[([\w-]+)\]$/.exec(selector)?.[1]; const all = this.children.flatMap((k) => [k, ...k.querySelectorAll(selector)]); return all.filter((k) => attr && k.hasAttribute(attr)); } } globalThis.document = { createElementNS: (_ns, tag) => new Node(tag) }; const B06 = (f) => path.join(ROOT, "B06_Section", f); const pave = load(B06("B06_Section_UI_Cross_Ford_Pavement.ts")); const linked = load(B06("B06_Section_UI_Cross_Ford_Linked.ts")); const shapePick = load(B06("B06_Section_UI_Cross_Shape_Pick.ts")); const design = { section_mode: "left_cut", tilted: true, cross_slope_pct: 5, design_elevation_m: 100, pavement_thickness_m: 0.2, paved: true, road_edges: { left: { offset_m: 3, elevation_m: 100.15 }, right: { offset_m: -3, elevation_m: 99.85 }, }, carriageway_edges: { left: { offset_m: 2.5, elevation_m: 100.125 }, right: { offset_m: -2.5, elevation_m: 99.875 }, }, design_line: [ { offset_m: 6, elevation_m: 103 }, { offset_m: 3, elevation_m: 100.15 }, { offset_m: -3, elevation_m: 99.85 }, { offset_m: -7, elevation_m: 97.2 }, ], }; // 검증 프로젝트 꼴 — 4+10(물넘이 · 폭 10) → 4+15 카드에도 포장 · 측점 간격 20 const spec = { chainage_m: 90, span_m: 10, depth_m: 0.4, slope_pct: 5 }; const draw = (chainage) => { const svg = new Node("g"); const before = new Node("polyline"); // 앞서 그린 것(세월교 점선 등) — 연결 꼴 밖 before.setAttribute("class", "b06-chart__ford-deck-plan"); svg.append(before); const from = svg.children.length; pave.appendFordPavementOverlay( svg, spec, design, (o) => o, (e) => e, ); const marked = linked.markLinkedFordPavement( svg, from, { ford_pavement: spec, chainage_m: chainage }, 20, ); const shapes = svg.children .slice(from) .filter((k) => ["b06-chart__ford-pavement", "b06-chart__ford-deck-plan"].some((c) => k.classList.contains(c), ), ); shapePick.highlightShapes(svg, "pipe-90.00"); return { marked, classes: shapes.map((k) => k.attrs.class), titles: shapes.map((k) => k.children.filter((c) => c.tag === "title").map((c) => c.textContent), ), pick: shapes .filter((k) => k.hasAttribute("data-structure-id")) .map((k) => k.attrs["data-structure-id"]), beforeClass: before.attrs.class, }; }; process.stdout.write( JSON.stringify({ neighbor: draw(95), owner: draw(90), noSpec: linked.markLinkedFordPavement(new Node("g"), 0, { chainage_m: 95 }, 20), }), );