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

212 lines
7.1 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.
/**
* 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<ImportResult> {
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)}`,
};
}
}