/* PLAN 323-9 ④-② — 같은 시설을 그리는 카드 둘(같은 m · 노선 끝 EP 꼴 소수 차 0.01 m)이면 3D 관 · 세월교 · BOX 몸체는 한 번. * 화면 TS(`buildCorridorStructures`)를 그 자리에서 트랜스파일해 Node 로 돌림(로더 = helper_135_3 과 같음). * 입력 = 73-4 픽스처(세월교 173.087 · BOX 352.14) · 결과 = 표준출력 JSON. 짝 = test_323_9_twin_cards_3d.py. */ const fs = require("fs"); const path = require("path"); const ROOT = path.join(__dirname, "..", ".."); const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const STUBS = { "@ui/ui_template_toast": { showToast: () => undefined }, three: require(path.join(ROOT, "config", "node_modules", "three")), }; const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), ) ?? `${base}.ts`; const { loadTs } = makeLoader({ root: ROOT, interop: true, requireFor: (full) => (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))); }, }); const { buildCorridorStructures } = loadTs( path.join(ROOT, "B05_Profile", "logic", "B05_Profile_Corridor_Structures.ts"), ); const fixture = JSON.parse( fs.readFileSync(path.join(__dirname, "fixtures", "b06_73_4_body_guard_sections.json"), "utf8"), ); const clone = (v) => JSON.parse(JSON.stringify(v)); const at = (c) => clone(fixture.cross_sections.find((s) => Math.abs(s.chainage_m - c) < 0.6)); /** 직선 노선 — 가장 가까운 측점 프레임에서 앞쪽으로 편다. */ const frameAtOf = (sections) => (atM) => { const s = sections.reduce((b, x) => Math.abs(x.chainage_m - atM) < Math.abs(b.chainage_m - atM) ? x : b, ); const [lx, ly] = s.frame.left_xy; const along = atM - s.chainage_m; return { cx: s.center_x - ly * along, cy: s.center_y + lx * along, leftX: lx, leftY: ly, designZ: s.design?.design_elevation_m ?? null, }; }; /** 부재 차림표 — 종류 · 열쇠 · 측점(같은 몸체를 두 번 세우면 같은 줄이 둘). */ const kinds = (sections) => buildCorridorStructures(sections, frameAtOf(sections)) .map((s) => `${s.kind}:${s.key ?? ""}:${s.chainage_m.toFixed(2)}`) .sort(); const pick = loadTs( path.join(ROOT, "B05_Profile", "logic", "B05_Profile_UI_Corridor_Structures_Pick.ts"), ); const own = pick.ownsBody; /** 노선 끝 꼴 — 같은 시설을 그리는 카드가 소수 차(EP 1078 · 1078.010813)로 하나 더. */ const ep = (one) => Object.assign(clone(one), { chainage_m: one.chainage_m + 0.010813 }); const out = {}; for (const c of [173.087, 352.14]) { const one = at(c); const twin = kinds([one, clone(one)]); const epTwin = kinds([one, ep(one)]); const epFirst = kinds([ep(one), one]); // 차례가 바뀌어도 시설 자리에 가까운 카드 하나 pick.ownsBody = () => true; // 고치기 전(카드마다 몸체) — 기준이 변경을 재는지 const before = kinds([one, clone(one)]); const epBefore = kinds([one, ep(one)]); pick.ownsBody = own; out[c] = { one: kinds([one]), twin, before, epTwin, epFirst, epBefore }; } process.stdout.write(JSON.stringify(out));