/** * DXF Parser – converts DXF entities to CADElement[] * Uses dxf-parser library */ import DxfParser from 'dxf-parser'; import type { CADElement, CADLayer, CADProperties, ElementType } from '../types/cad.types'; export interface DXFImportResult { elements: CADElement[]; layers: CADLayer[]; warnings: string[]; } let idCounter = 0; const nextId = () => `dxf-${Date.now()}-${idCounter++}`; /** * Parse a DXF string into CAD elements and layers. */ export function parseDXF(dxfString: string): DXFImportResult { const parser = new DxfParser(); const dxf = parser.parseSync(dxfString) as any; if (!dxf) return { elements: [], layers: [], warnings: ['DXF parse returned null'] }; const warnings: string[] = []; const layerMap = new Map(); const elements: CADElement[] = []; // Build layers from DXF tables if (dxf.tables?.layer?.layers) { let sortOrder = 0; for (const [layerName, layerData] of Object.entries(dxf.tables.layer.layers) as [string, any][]) { const layer: CADLayer = { id: `dxf-layer-${layerName}`, name: layerName, visible: true, locked: false, color: dxfColorToHex(layerData.color) ?? '#ffffff', lineType: mapLineType(layerData.lineType), transparency: 0, sortOrder: sortOrder++, parentId: null, }; layerMap.set(layerName, layer); } } // Ensure a default layer exists if (layerMap.size === 0) { layerMap.set('0', { id: 'dxf-layer-0', name: '0', visible: true, locked: false, color: '#ffffff', lineType: 'solid', transparency: 0, sortOrder: 0, parentId: null, }); } // Parse entities if (dxf.entities) { for (const entity of dxf.entities) { const el = entityToCADElement(entity, layerMap); if (el) { elements.push(el); } else { warnings.push(`Unsupported entity type: ${entity.type}`); } } } return { elements, layers: Array.from(layerMap.values()), warnings }; } function entityToCADElement( entity: any, layerMap: Map, ): CADElement | null { const layerName = entity.layer || '0'; const layer = layerMap.get(layerName) ?? layerMap.get('0')!; const color = entity.color ? (dxfColorToHex(entity.color) ?? layer.color) : layer.color; const baseProps: CADProperties = { stroke: color, strokeWidth: 1, }; switch (entity.type) { case 'LINE': { const s = entity.vertices?.[0]; const e = entity.vertices?.[1]; if (!s || !e) return null; const minX = Math.min(s.x, e.x); const minY = Math.min(s.y, e.y); const maxX = Math.max(s.x, e.x); const maxY = Math.max(s.y, e.y); return { id: nextId(), type: 'line', layerId: layer.id, x: minX, y: minY, width: maxX - minX, height: maxY - minY, properties: { ...baseProps, x1: s.x, y1: s.y, x2: e.x, y2: e.y }, }; } case 'CIRCLE': { const c = entity.center; const r = entity.radius; if (!c || r == null) return null; return { id: nextId(), type: 'circle', layerId: layer.id, x: c.x - r, y: c.y - r, width: r * 2, height: r * 2, properties: { ...baseProps, radius: r }, }; } case 'ARC': { const c = entity.center; const r = entity.radius; if (!c || r == null) return null; return { id: nextId(), type: 'arc', layerId: layer.id, x: c.x - r, y: c.y - r, width: r * 2, height: r * 2, properties: { ...baseProps, radius: r, startAngle: entity.startAngle, endAngle: entity.endAngle }, }; } case 'LWPOLYLINE': case 'POLYLINE': { const pts = (entity.vertices || []).map((v: any) => ({ x: v.x, y: v.y })); if (pts.length < 2) return null; const minX = Math.min(...pts.map((p: any) => p.x)); const minY = Math.min(...pts.map((p: any) => p.y)); const maxX = Math.max(...pts.map((p: any) => p.x)); const maxY = Math.max(...pts.map((p: any) => p.y)); const isClosed = entity.shape === true; const type: ElementType = isClosed ? 'polygon' : 'polyline'; return { id: nextId(), type, layerId: layer.id, x: minX, y: minY, width: maxX - minX, height: maxY - minY, properties: { ...baseProps, points: pts }, }; } case 'TEXT': { const s = entity.startPoint; if (!s) return null; return { id: nextId(), type: 'text', layerId: layer.id, x: s.x, y: s.y, width: 0, height: 0, properties: { ...baseProps, text: entity.text || '', fontSize: entity.height || 12 }, }; } case 'INSERT': { // Block reference — store as block_instance const pos = entity.position; if (!pos) return null; return { id: nextId(), type: 'block_instance', layerId: layer.id, x: pos.x, y: pos.y, width: 0, height: 0, properties: { ...baseProps, blockId: entity.name, scale: entity.scale || 1, rotation: entity.rotation || 0 }, }; } case 'DIMENSION': { // Basic dimension support const pts = entity.definitionPoint || entity.points; if (!pts) return null; return { id: nextId(), type: 'dimension', layerId: layer.id, x: 0, y: 0, width: 0, height: 0, properties: { ...baseProps, points: Array.isArray(pts) ? pts.map((p: any) => ({ x: p.x, y: p.y })) : [] }, }; } default: return null; } } /** * DXF ACI color to hex string */ function dxfColorToHex(aci: number | undefined): string | null { if (aci == null) return null; const colors: Record = { 0: '#ffffff', 1: '#ff0000', 2: '#ffff00', 3: '#00ff00', 4: '#00ffff', 5: '#0000ff', 6: '#ff00ff', 7: '#ffffff', 8: '#808080', 9: '#c0c0c0', }; return colors[aci] ?? null; } function mapLineType(lt: string | undefined): 'solid' | 'dashed' | 'dotted' { if (!lt) return 'solid'; const u = lt.toUpperCase(); if (u.includes('DASH')) return 'dashed'; if (u.includes('DOT')) return 'dotted'; return 'solid'; }