/** * Import Service – handles DXF, SVG, PDF, JSON imports */ import { parseDXF, type DXFImportResult } from './dxfParser'; import type { CADElement, CADLayer, BlockDefinition, ProjectData } from '../types/cad.types'; export interface ImportResult { success: boolean; elements: CADElement[]; layers?: CADLayer[]; blocks?: BlockDefinition[]; warnings: string[]; error?: string; } let idCounter = 0; const nextId = () => `imp-${Date.now()}-${idCounter++}`; /** * Import a file based on its extension. */ export async function importFile(file: File): Promise { const ext = file.name.split('.').pop()?.toLowerCase(); const text = await file.text(); switch (ext) { case 'dxf': return importDXF(text); case 'svg': return importSVG(text); case 'json': return importJSON(text); case 'pdf': return { success: false, elements: [], warnings: ['PDF import not yet supported'] }; default: return { success: false, elements: [], warnings: [`Unsupported format: ${ext}`] }; } } /** * Import DXF string. */ export function importDXF(dxfString: string): ImportResult { try { const result: DXFImportResult = parseDXF(dxfString); return { success: true, elements: result.elements, layers: result.layers, warnings: result.warnings, }; } catch (err) { return { success: false, elements: [], warnings: [], error: `DXF parse error: ${err instanceof Error ? err.message : String(err)}`, }; } } /** * Import SVG string — converts SVG elements to CAD elements. */ export function importSVG(svgString: string): ImportResult { try { const parser = new DOMParser(); const doc = parser.parseFromString(svgString, 'image/svg+xml'); const svg = doc.documentElement; const elements: CADElement[] = []; const warnings: string[] = []; // Parse SVG viewBox for coordinate mapping const viewBox = svg.getAttribute('viewBox'); let vbX = 0, vbY = 0; if (viewBox) { const parts = viewBox.split(/[\s,]+/).map(Number); vbX = parts[0] || 0; vbY = parts[1] || 0; } // Process SVG elements const processElement = (node: Element) => { const tag = node.tagName.toLowerCase(); const stroke = node.getAttribute('stroke') || '#ffffff'; const strokeWidth = parseFloat(node.getAttribute('stroke-width') || '1'); const fill = node.getAttribute('fill') || 'none'; switch (tag) { case 'line': { const x1 = parseFloat(node.getAttribute('x1') || '0') - vbX; const y1 = parseFloat(node.getAttribute('y1') || '0') - vbY; const x2 = parseFloat(node.getAttribute('x2') || '0') - vbX; const y2 = parseFloat(node.getAttribute('y2') || '0') - vbY; elements.push({ id: nextId(), type: 'line', layerId: 'layer-0', x: Math.min(x1, x2), y: Math.min(y1, y2), width: Math.abs(x2 - x1), height: Math.abs(y2 - y1), properties: { stroke, strokeWidth, x1, y1, x2, y2 }, }); break; } case 'rect': { const x = parseFloat(node.getAttribute('x') || '0') - vbX; const y = parseFloat(node.getAttribute('y') || '0') - vbY; const w = parseFloat(node.getAttribute('width') || '0'); const h = parseFloat(node.getAttribute('height') || '0'); elements.push({ id: nextId(), type: 'rect', layerId: 'layer-0', x, y, width: w, height: h, properties: { stroke, strokeWidth, fill: fill !== 'none' ? fill : undefined }, }); break; } case 'circle': { const cx = parseFloat(node.getAttribute('cx') || '0') - vbX; const cy = parseFloat(node.getAttribute('cy') || '0') - vbY; const r = parseFloat(node.getAttribute('r') || '0'); elements.push({ id: nextId(), type: 'circle', layerId: 'layer-0', x: cx - r, y: cy - r, width: r * 2, height: r * 2, properties: { stroke, strokeWidth, radius: r }, }); break; } case 'ellipse': { const cx = parseFloat(node.getAttribute('cx') || '0') - vbX; const cy = parseFloat(node.getAttribute('cy') || '0') - vbY; const rx = parseFloat(node.getAttribute('rx') || '0'); const ry = parseFloat(node.getAttribute('ry') || '0'); elements.push({ id: nextId(), type: 'circle', layerId: 'layer-0', x: cx - rx, y: cy - ry, width: rx * 2, height: ry * 2, properties: { stroke, strokeWidth, radius: Math.max(rx, ry) }, }); break; } case 'polyline': case 'polygon': { const ptsStr = node.getAttribute('points') || ''; const pts = ptsStr.trim().split(/[\s,]+/).reduce((acc: Array<{x:number;y:number}>, val: string, idx: number) => { if (idx % 2 === 0) acc.push({ x: parseFloat(val) - vbX, y: 0 }); else acc[acc.length - 1].y = parseFloat(val) - vbY; return acc; }, []); if (pts.length >= 2) { const minX = Math.min(...pts.map(p => p.x)); const minY = Math.min(...pts.map(p => p.y)); const maxX = Math.max(...pts.map(p => p.x)); const maxY = Math.max(...pts.map(p => p.y)); elements.push({ id: nextId(), type: tag === 'polygon' ? 'polygon' : 'polyline', layerId: 'layer-0', x: minX, y: minY, width: maxX - minX, height: maxY - minY, properties: { stroke, strokeWidth, points: pts }, }); } break; } case 'text': { const x = parseFloat(node.getAttribute('x') || '0') - vbX; const y = parseFloat(node.getAttribute('y') || '0') - vbY; const fontSize = parseFloat(node.getAttribute('font-size') || '12'); const text = node.textContent || ''; elements.push({ id: nextId(), type: 'text', layerId: 'layer-0', x, y, width: 0, height: 0, properties: { stroke, strokeWidth, text, fontSize }, }); break; } case 'g': { // Process group children Array.from(node.children).forEach(processElement); break; } default: warnings.push(`Unsupported SVG element: ${tag}`); } }; Array.from(svg.children).forEach(processElement); return { success: true, elements, warnings }; } catch (err) { return { success: false, elements: [], warnings: [], error: `SVG parse error: ${err instanceof Error ? err.message : String(err)}`, }; } } /** * Import JSON project file. */ export function importJSON(jsonString: string): ImportResult { try { const data: ProjectData = JSON.parse(jsonString); return { success: true, elements: data.elements || [], layers: data.layers || [], blocks: data.blocks || [], warnings: [], }; } catch (err) { return { success: false, elements: [], warnings: [], error: `JSON parse error: ${err instanceof Error ? err.message : String(err)}`, }; } }