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

215 lines
5.6 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 Writer converts CADElement[] to DXF string
* Minimal DXF R12 format for compatibility
*/
import type { CADElement, CADLayer, ProjectData } from '../types/cad.types';
/**
* Generate a DXF R12 string from project data.
*/
export function writeDXF(data: ProjectData): string {
const lines: string[] = [];
const w = (code: number, value: string | number) => {
lines.push(String(code));
lines.push(String(value));
};
// Header
w(0, 'SECTION');
w(2, 'HEADER');
w(9, '$ACADVER'); w(1, 'AC1009'); // R12
w(9, '$INSBASE'); w(10, 0); w(20, 0); w(30, 0);
w(9, '$EXTMIN'); w(10, 0); w(20, 0);
w(9, '$EXTMAX'); w(10, 1000); w(20, 1000);
w(0, 'ENDSEC');
// Tables section
w(0, 'SECTION');
w(2, 'TABLES');
// Layer table
w(0, 'TABLE');
w(2, 'LAYER');
w(70, data.layers.length);
for (const layer of data.layers) {
w(0, 'LAYER');
w(2, layer.name);
w(70, 0);
w(62, hexToACI(layer.color));
w(6, lineTypeToDXF(layer.lineType));
}
w(0, 'ENDTAB');
w(0, 'ENDSEC');
// Entities section
w(0, 'SECTION');
w(2, 'ENTITIES');
for (const el of data.elements) {
const layerName = getLayerName(data.layers, el.layerId);
writeEntity(w, el, layerName);
}
w(0, 'ENDSEC');
// EOF
w(0, 'EOF');
return lines.join('\n');
}
function writeEntity(
w: (code: number, value: string | number) => void,
el: CADElement,
layerName: string,
): void {
const p = el.properties;
const stroke = (p.stroke as string) || '#ffffff';
const aci = hexToACI(stroke);
switch (el.type) {
case 'line': {
w(0, 'LINE');
w(8, layerName);
w(62, aci);
w(10, p.x1 ?? el.x); w(20, p.y1 ?? el.y); w(30, 0);
w(11, p.x2 ?? el.x + el.width); w(21, p.y2 ?? el.y + el.height); w(31, 0);
break;
}
case 'circle': {
const cx = el.x + el.width / 2;
const cy = el.y + el.height / 2;
const r = p.radius ?? el.width / 2;
w(0, 'CIRCLE');
w(8, layerName);
w(62, aci);
w(10, cx); w(20, cy); w(30, 0);
w(40, r);
break;
}
case 'arc': {
const cx = el.x + el.width / 2;
const cy = el.y + el.height / 2;
const r = p.radius ?? el.width / 2;
w(0, 'ARC');
w(8, layerName);
w(62, aci);
w(10, cx); w(20, cy); w(30, 0);
w(40, r);
w(50, radToDeg(p.startAngle ?? 0));
w(51, radToDeg(p.endAngle ?? 360));
break;
}
case 'rect': {
// Rectangle as LWPOLYLINE
w(0, 'LWPOLYLINE');
w(8, layerName);
w(62, aci);
w(90, 4);
w(70, 1); // closed
w(10, el.x); w(20, el.y);
w(10, el.x + el.width); w(20, el.y);
w(10, el.x + el.width); w(20, el.y + el.height);
w(10, el.x); w(20, el.y + el.height);
break;
}
case 'polygon':
case 'polyline': {
const pts = p.points ?? [];
w(0, 'LWPOLYLINE');
w(8, layerName);
w(62, aci);
w(90, pts.length);
w(70, el.type === 'polygon' ? 1 : 0);
for (const pt of pts) {
w(10, pt.x); w(20, pt.y);
}
break;
}
case 'text': {
w(0, 'TEXT');
w(8, layerName);
w(62, aci);
w(10, el.x); w(20, el.y); w(30, 0);
w(40, p.fontSize ?? 12);
w(1, p.text ?? '');
break;
}
case 'dimension': {
const pts = p.points ?? [];
if (pts.length >= 2) {
// Draw dimension as LINE + TEXT
w(0, 'LINE');
w(8, layerName);
w(62, aci);
w(10, pts[0].x); w(20, pts[0].y); w(30, 0);
w(11, pts[1].x); w(21, pts[1].y); w(31, 0);
const midX = (pts[0].x + pts[1].x) / 2;
const midY = (pts[0].y + pts[1].y) / 2;
const dist = Math.hypot(pts[1].x - pts[0].x, pts[1].y - pts[0].y);
w(0, 'TEXT');
w(8, layerName);
w(62, aci);
w(10, midX); w(20, midY); w(30, 0);
w(40, 12);
w(1, dist.toFixed(2));
}
break;
}
case 'block_instance': {
w(0, 'INSERT');
w(8, layerName);
w(62, aci);
w(2, p.blockId ?? 'BLOCK');
w(10, el.x); w(20, el.y); w(30, 0);
w(41, p.scale ?? 1);
w(42, p.scale ?? 1);
w(50, radToDeg(p.rotation ?? 0));
break;
}
default:
// For chair, seating-row, etc. — export as LWPOLYLINE bounding box
w(0, 'LWPOLYLINE');
w(8, layerName);
w(62, aci);
w(90, 4);
w(70, 1);
w(10, el.x); w(20, el.y);
w(10, el.x + el.width); w(20, el.y);
w(10, el.x + el.width); w(20, el.y + el.height);
w(10, el.x); w(20, el.y + el.height);
break;
}
}
function getLayerName(layers: CADLayer[], layerId: string): string {
return layers.find(l => l.id === layerId)?.name ?? '0';
}
function hexToACI(hex: string): number {
const h = hex.replace('#', '');
const r = parseInt(h.substring(0, 2), 16);
const g = parseInt(h.substring(2, 4), 16);
const b = parseInt(h.substring(4, 6), 16);
// Map common colors to ACI
if (r > 200 && g < 100 && b < 100) return 1; // red
if (r > 200 && g > 200 && b < 100) return 2; // yellow
if (r < 100 && g > 200 && b < 100) return 3; // green
if (r < 100 && g > 200 && b > 200) return 4; // cyan
if (r < 100 && g < 100 && b > 200) return 5; // blue
if (r > 200 && g < 100 && b > 200) return 6; // magenta
if (r > 200 && g > 200 && b > 200) return 7; // white
if (r < 100 && g < 100 && b < 100) return 8; // gray
return 7; // default white
}
function lineTypeToDXF(lt: string): string {
if (lt === 'dashed') return 'DASHED';
if (lt === 'dotted') return 'DOT';
return 'CONTINUOUS';
}
function radToDeg(rad: number): number {
return (rad * 180) / Math.PI;
}