/* 430 평면 형상 제어 규칙 한 곳 — `logic/B05_Profile_UI_RouteEdit_Edits.ts`(하한 검사 · 겹친 노드 · * 못 그린 곡선 · 넣은 값 되살림) · `_KeyNudge.fitNote` 를 Node 로 돌려 JSON 한 줄 * (`test_430_route_edit_rules.py` 가 판정). * node helper_430_route_edit_rules.cjs <서버 곡선표 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 LOGIC = path.join(ROOT, "B05_Profile", "logic"); const cache = new Map(); function loadTs(file) { const full = path.resolve(file); if (cache.has(full)) return cache.get(full); const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const box = { exports: {} }; cache.set(full, box.exports); const localRequire = (request) => request.startsWith(".") ? loadTs(path.join(path.dirname(full), `${request}.ts`)) : require(request); new Function("exports", "module", "require", js)(box.exports, box, localRequire); cache.set(full, box.exports); return box.exports; } const edits = loadTs(path.join(LOGIC, "B05_Profile_UI_RouteEdit_Edits.ts")); const nudge = loadTs(path.join(LOGIC, "B05_Profile_RouteEdit_KeyNudge.ts")); const LIMITS = { minRadiusM: 30, limitRadiusM: 20, limitArcM: 0 }; // IP1 · IP2 · IP3 — 직선 넉넉(곡선 R 30 다 들어감) const ROUTE = [ [0, 0], [100, 0], [200, 60], [300, 0], [400, 0], ]; function cols(planned) { const n = planned.length; return { planned: planned.map((v) => [v[0], v[1]]), curveOn: Array(n).fill(true), curveRadius: Array(n).fill(null), curveLock: Array(n).fill(null), curveArc: Array(n).fill(null), apexLock: Array(n).fill(false), }; } /** 지난 걸음 `before` 에서 `change` 로 고친 뒤 하한 검사 글. */ function stop(before, change) { const now = cols(before.planned); change(now); const built = edits.rebuildLine(now, LIMITS); return edits.floorStop(before, now.planned, built.nodes, LIMITS); } const base = cols(ROUTE); const out = { // 같은 노드 수 — IP2 를 IP1 쪽 8 m 앞으로(IP1 · IP2 접선 자리 모자람 → R 하한 아래) moveBreak: stop(base, (c) => (c.planned[2] = [108, 4])), moveOk: stop(base, (c) => (c.planned[2] = [200, 70])), // 끼우기 — IP1 바로 뒤 4 m 에 새 노드(IP1 접선 자리 2 m) insertBreak: stop(base, (c) => { c.planned.splice(2, 0, [104, 1]); for (const k of ["curveOn", "curveRadius", "curveLock", "curveArc", "apexLock"]) c[k].splice(2, 0, k === "curveOn" ? true : k === "apexLock" ? false : null); }), // 지우기 — IP2 를 빼도 IP1 · IP3 넉넉 deleteOk: stop(base, (c) => { for (const k of ["planned", "curveOn", "curveRadius", "curveLock", "curveArc", "apexLock"]) c[k].splice(2, 1); }), // 둘 넘게 다르면 안 견줌 manyGap: stop(base, (c) => { c.planned.splice(1, 0, [50, 0], [52, 0]); for (const k of ["curveOn", "curveRadius", "curveLock", "curveArc", "apexLock"]) c[k].splice( 1, 0, ...(k === "curveOn" ? [true, true] : k === "apexLock" ? [false, false] : [null, null]), ); }), }; // 이미 하한을 밑돌던 자리는 「새로」가 아님 — 밑돈 노선에서 다시 조금 옮겨도 통과 const broken = cols(ROUTE); broken.planned[2] = [108, 4]; out.alreadyBroken = stop(broken, (c) => (c.planned[2] = [108.5, 4.2])); // 겹친 노드 · 못 그린 곡선 const overlap = [ [0, 0], [100, 0], [100.3, 0.2], [200, 60], [300, 0], ]; out.flags = edits.routeFlags(overlap, Array(5).fill(true), []); out.flagsOff = edits.routeFlags(ROUTE, [true, false, false, false, true], []); out.summary = edits.curveSummary({ nodeCount: 5, curveOn: Array(5).fill(true), curveRadius: Array(5).fill(null), curveLock: Array(5).fill(null), apexLock: [], curveCount: 2, violationCount: 0, minRadiusM: 30, fresh: false, flags: out.flags, }); // fitNote — 손으로 옮긴 고른 IP 는 「밀림」에 안 셈(놓을 때 · 430) · 입력 뒤는 셈(408-1) const after = ROUTE.map((v, i) => (i === 2 ? [v[0] + 3, v[1]] : i === 3 ? [v[0] + 1, v[1]] : v)); out.noteHand = nudge.fitNote(ROUTE, after, 2, [], 20, 20, true); out.noteInput = nudge.fitNote(ROUTE, after, 2, [], 20, 20); // 짝 — 서버 곡선표(파이썬이 씀)를 그대로 펴서 넣은 값이 돌아오는가 const server = JSON.parse(fs.readFileSync(process.argv[2], "utf8")); const flat = edits.flattenServerPlan(server.nodes, server.curves, server.edits); out.flat = { planned: flat.planned, curveOn: flat.curveOn, curveRadius: flat.curveRadius, curveLock: flat.curveLock, curveArc: flat.curveArc, }; const old = edits.flattenServerPlan(server.nodes, server.curves, null); out.flatOld = { curveRadius: old.curveRadius, curveLock: old.curveLock }; process.stdout.write(JSON.stringify(out));