Files
web-cad/frontend/src/services/dxfParser.ts
T

187 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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<string, CADLayer>();
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<string, CADLayer>,
): 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<number, string> = {
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';
}