/** * Minimal Gmsh MSH 2.2 ASCII writer for a triangle surface mesh — plain * string building, no dependencies. Vertices not referenced by any facet * (interior points of the cloud) are dropped, and indices are remapped to * the dense 1-based node ids the format expects. */ export function trianglesToMsh(points: number[][], facets: number[][]): string { const used = new Map() // original index -> 1-based node id for (const f of facets) { for (const idx of f) { if (!used.has(idx)) used.set(idx, used.size + 1) } } const lines = ['$MeshFormat', '2.2 0 8', '$EndMeshFormat', '$Nodes', String(used.size)] for (const [idx, id] of used) { const [x, y, z] = points[idx] lines.push(`${id} ${x} ${y} ${z}`) } lines.push('$EndNodes', '$Elements', String(facets.length)) facets.forEach((f, i) => { // element type 2 = 3-node triangle, two tags lines.push(`${i + 1} 2 2 1 1 ${used.get(f[0])} ${used.get(f[1])} ${used.get(f[2])}`) }) lines.push('$EndElements') return lines.join('\n') + '\n' } export function downloadText(filename: string, text: string) { const blob = new Blob([text], { type: 'application/octet-stream' }) const a = document.createElement('a') a.href = URL.createObjectURL(blob) a.download = filename a.click() URL.revokeObjectURL(a.href) }