/* PLAN 261-3 단위검증 헬퍼 — [상세] 누름 판정(거리 · 얇은 띠) · 구조물 둘레로 닫히는 깎기 영역. * 화면 TS 를 그 자리에서 트랜스파일해 Node 로 돌림. 입력 = 표준입력 JSON * {cases: [{name, axes, elements: [{tag, cls, dl, pts(그림 px)}], clicks: [[x, y, scale]]}]} · * 결과 = 표준출력 JSON {이름: {pieces, faces, clicks}}. 짝 = `test_261_3_detail_pick.py`. */ const fs = require("fs"); const path = require("path"); const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const B06 = path.join(ROOT, "B06_Section"); const common = fs.readFileSync(path.join(B06, "B06_Section_UI_Section_Common.ts"), "utf8"); const CROSS_PAD = Function(`return ${/export const CROSS_PAD = (\{[^}]*\})/.exec(common)[1]}`)(); globalThis.window = { sessionStorage: new Map(), localStorage: new Map() }; globalThis.document = { addEventListener: () => {}, removeEventListener: () => {} }; const STUBS = { "@ui/ui_template_context_menu": { createMapContextMenu: () => ({}) }, "./B06_Section_UI_Section_Common": { CROSS_PAD }, }; const resolve = (base) => (fs.existsSync(`${base}.ts`) ? `${base}.ts` : base); const { loadTs } = makeLoader({ root: ROOT, cache: false, requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; if (request.endsWith(".css")) return {}; if (request.startsWith("@util/")) return loadTs(resolve(path.join(ROOT, "common_util", request.slice(6)))); return loadTs(resolve(path.join(path.dirname(full), request))); }, }); const G = loadTs(path.join(B06, "logic/B06_Section_UI_Cross_Detail_Geom.ts")); const S = loadTs(path.join(B06, "B06_Section_UI_Cross_Detail_Select.ts")); const element = (e) => ({ tagName: e.tag, classList: { contains: (name) => e.cls.split(/\s+/).includes(name) }, hasAttribute: (name) => name === "data-design-line" && e.dl, getAttribute: (name) => { if (name === "points") return e.pts.map((p) => p.join(",")).join(" "); const m = /^([xy])([12])$/.exec(name); return m ? String(e.pts[+m[2] - 1][m[1] === "x" ? 0 : 1]) : null; }, }); const { cases } = JSON.parse(fs.readFileSync(0, "utf8")); const out = {}; for (const { name, axes, elements, clicks } of cases) { const pieces = S.collectPieces(elements.map(element), axes); const faces = G.findFaces(pieces); const toPx = ([offset, elevation]) => [ CROSS_PAD.left + (axes.maxOffset - offset) * axes.pixelsPerMeter, CROSS_PAD.top + (axes.displayMax - (axes.elevationMid + (elevation - axes.elevationMid) * axes.exaggeration)) * axes.pixelsPerMeter, ]; const paths = pieces.map((piece) => piece.points.map(toPx)); const rings = faces.map((face) => face.ring.map(toPx)); const areas = faces.map((face) => face.area); out[name] = { pieces: pieces.map((piece) => ({ line: piece.line, points: piece.points })), faces: faces.map((face) => { const edges = face.edges.map((i) => pieces[i]); const source = S.selectionSource("area", edges); return { area: face.area, edges: face.edges, lines: [...new Set(edges.map((piece) => piece.line))].sort(), ring: face.ring, source, }; }), clicks: (clicks ?? []).map(([x, y, scale]) => S.pickAt([x, y], scale, paths, rings, areas, pieces), ), px: { paths, rings }, }; } process.stdout.write(JSON.stringify(out));