/* importer.js — parse OpenSCAD ECHO / CSV / JSON / parametric generator. * * All parsers return an array of {id, x, y} in millimetres. * Generator formulas are 1:1 mirrors of `get_hex_center_points_*` from * Addy's hex_cell.scad (see CLAUDE.md for the cheat sheet). */ const Importer = (() => { const COS30 = Math.cos(Math.PI / 6); /** Parse OpenSCAD ECHO lines or loose `Cell N: x = …, y = …`. */ function parsePaste(text) { const out = []; const re = /Cell\s*(\d+)\s*:\s*x\s*=\s*(-?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?)\s*,\s*y\s*=\s*(-?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?)/g; let m; while ((m = re.exec(text)) !== null) { out.push({ id: parseInt(m[1], 10), x: parseFloat(m[2]), y: parseFloat(m[3]) }); } if (out.length) return out; // Fallback: lines of form `index x y` or `index, x, y` (any separator). const lines = text.split(/\r?\n/); let auto = 1; for (const line of lines) { const nums = line.match(/-?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?/g); if (!nums) continue; if (nums.length >= 3) { out.push({ id: parseInt(nums[0], 10), x: parseFloat(nums[1]), y: parseFloat(nums[2]) }); } else if (nums.length === 2) { out.push({ id: auto++, x: parseFloat(nums[0]), y: parseFloat(nums[1]) }); } } return out; } /** CSV with optional header. Columns: index,x,y OR x,y. */ function parseCSV(text) { const out = []; const lines = text.trim().split(/\r?\n/).filter(Boolean); let auto = 1; for (const raw of lines) { const cols = raw.split(/[,;\t]/).map((s) => s.trim()); // Skip header rows that don't parse as numbers. if (cols.length >= 2 && isNaN(parseFloat(cols[0])) && isNaN(parseFloat(cols[1]))) continue; if (cols.length >= 3) { const id = parseInt(cols[0], 10); const x = parseFloat(cols[1]); const y = parseFloat(cols[2]); if (!isNaN(x) && !isNaN(y)) out.push({ id: isNaN(id) ? auto++ : id, x, y }); } else if (cols.length === 2) { const x = parseFloat(cols[0]); const y = parseFloat(cols[1]); if (!isNaN(x) && !isNaN(y)) out.push({ id: auto++, x, y }); } } return out; } /** JSON: array of objects {id,x,y} OR array of [x,y]. */ function parseJSON(text) { const j = JSON.parse(text); if (!Array.isArray(j)) throw new Error("JSON must be a top-level array"); return j.map((item, i) => { if (Array.isArray(item)) { return { id: i + 1, x: +item[0], y: +item[1] }; } return { id: item.id != null ? +item.id : i + 1, x: +item.x, y: +item.y, }; }); } /** Generator — mirrors get_hex_center_points_{rect,para,tria} from hex_cell.scad. */ function generate({ cellDia, wall, rows, cols, style }) { const hex_w = cellDia + 2 * wall; const hex_pt = (hex_w / 2) / COS30; const rowY = (r) => r * 1.5 * hex_pt; const out = []; let id = 1; if (style === "rect") { for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { const x = (r % 2 === 0) ? hex_w * c : 0.5 * hex_w + hex_w * c; out.push({ id: id++, x, y: rowY(r) }); } } } else if (style === "para") { for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { const x = r * 0.5 * hex_w + hex_w * c; out.push({ id: id++, x, y: rowY(r) }); } } } else if (style === "tria") { for (let r = 0; r < rows; r++) { for (let c = 0; c <= r; c++) { const x = r * 0.5 * hex_w - hex_w * c; out.push({ id: id++, x, y: rowY(r) }); } } } else { throw new Error(`unknown style: ${style}`); } return out; } return { parsePaste, parseCSV, parseJSON, generate }; })();