Skip to content

Commit 607b5db

Browse files
committed
feat: add dxf file import
1 parent f1ad9f3 commit 607b5db

File tree

5 files changed

+51
-214
lines changed

5 files changed

+51
-214
lines changed

package-lock.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"teenyicons": "^0.4.1",
3131
"undo-stacker": "^0.2.1",
3232
"use-local-storage-state": "^19.5.0",
33+
"dxf": "^5.2.0",
3334
"xstate": "^5.18.1"
3435
},
3536
"devDependencies": {
@@ -47,7 +48,6 @@
4748
"@vitejs/plugin-react-swc": "^3.8.1",
4849
"autoprefixer": "^10.4.21",
4950
"biome": "^0.3.3",
50-
"dxf": "^5.2.0",
5151
"puppeteer": "^24.1.1",
5252
"tailwindcss": "^4.1.3",
5353
"typescript": "^5.2.2",

src/helpers/import-export-handlers/import-entities-from-dxf.test.ts

Lines changed: 0 additions & 170 deletions
This file was deleted.

src/helpers/import-export-handlers/import-entities-from-dxf.ts

Lines changed: 45 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
1-
import { toast } from 'react-toastify';
2-
import { v4 as uuidv4 } from 'uuid';
3-
import type { Point, LineEntity, CircleEntity, Entity } from '../../App.types';
4-
import { EntityType } from '../../App.types';
5-
import { getEntities, setEntities, getActiveLayerId, getActiveLineColor, getActiveLineWidth } from '../../state';
1+
import {Point} from '@flatten-js/core';
2+
import type {Entities as DxfEntities, Helper} from 'dxf';
3+
import {isNil, uniqBy} from 'es-toolkit';
4+
import {toast} from 'react-toastify';
5+
import {CircleEntity} from '../../entities/CircleEntity.ts';
6+
import type {Entity} from '../../entities/Entity.ts';
7+
import {LineEntity} from '../../entities/LineEntity.ts';
8+
import {getActiveLayerId, getActiveLineColor, getActiveLineWidth, getEntities, setEntities,} from '../../state';
9+
import {toHex} from '../rgb-to-hex-color.ts';
10+
11+
function getDxfLineColor(dxfColor: [number, number, number] | undefined): string {
12+
if (isNil(dxfColor)) {
13+
return getActiveLineColor();
14+
}
15+
return toHex(dxfColor[0], dxfColor[1], dxfColor[2], 1);
16+
}
617

718
/**
819
* Imports entities from a DXF file.
@@ -30,72 +41,64 @@ export const importEntitiesFromDxfFile = async (file?: File): Promise<void> => {
3041
console.log('DXF file content loaded, attempting to parse...');
3142
// Dynamically import the dxf library
3243
const dxf = await import('dxf');
33-
const parser = new dxf.DxfParser();
34-
const parsedDxf = parser.parseSync(fileContent); // Use parseSync as parseString is not available in the version I expect
44+
const parsedDxf = new dxf.Helper(fileContent) as Helper;
3545

36-
if (!parsedDxf || !parsedDxf.entities) {
46+
if (!parsedDxf || !parsedDxf.denormalised) {
3747
toast.error('Failed to parse DXF file. No entities found or invalid format.');
3848
console.error('Parsed DXF data is invalid or contains no entities:', parsedDxf);
3949
return;
4050
}
4151

42-
console.log(`Successfully parsed DXF. Found ${parsedDxf.entities.length} entities.`);
52+
console.log(`Successfully parsed DXF. Found ${parsedDxf.denormalised.length} entities.`);
4353
const newEntities: Entity[] = [];
4454
const currentLayerId = getActiveLayerId();
45-
const defaultColor = getActiveLineColor();
4655
const defaultWidth = getActiveLineWidth();
4756

48-
for (const entity of parsedDxf.entities) {
57+
for (const entity of parsedDxf.denormalised) {
4958
// TODO: Coordinate transformation for Y-axis (DXF Y is usually up, canvas Y is down)
5059
// For now, we assume positive Y is down for simplicity matching canvas.
5160
// If DXF typically has Y up, then Y coordinates from DXF might need to be negated or subtracted from canvas height.
5261
if (entity.type === 'LINE') {
53-
if (entity.vertices && entity.vertices.length >= 2) {
54-
const startPoint: Point = { x: entity.vertices[0].x, y: entity.vertices[0].y };
55-
const endPoint: Point = { x: entity.vertices[1].x, y: entity.vertices[1].y };
56-
const line: LineEntity = {
57-
id: uuidv4(),
58-
type: EntityType.LINE,
59-
layerId: currentLayerId,
60-
start: startPoint,
61-
end: endPoint,
62-
color: (entity.colorNumber !== undefined && entity.colorNumber !== 256) ? `#${entity.colorNumber.toString(16).padStart(6, '0')}` : defaultColor, // TODO: Map DXF color index to HEX
63-
width: defaultWidth, // TODO: Potentially use layer lineweight or entity thickness
64-
};
62+
const dxfLine = entity as DxfEntities.Line;
63+
if (dxfLine.start && dxfLine.end) {
64+
const startPoint: Point = new Point(dxfLine.start.x, dxfLine.start.y);
65+
const endPoint: Point = new Point(dxfLine.end.x, dxfLine.end.y);
66+
const line = new LineEntity(currentLayerId, startPoint, endPoint);
67+
line.lineColor = getDxfLineColor(dxfLine.colorNumber);
68+
line.lineWidth = dxfLine.thickness || defaultWidth;
69+
line.lineDash = undefined;
6570
newEntities.push(line);
66-
console.log('Converted DXF LINE to LineEntity:', line);
6771
} else {
68-
console.warn('Skipping DXF LINE entity due to missing or insufficient vertices:', entity);
72+
console.warn('Skipping DXF LINE due to missing or insufficient vertices:', dxfLine);
6973
}
7074
} else if (entity.type === 'CIRCLE') {
71-
if (entity.center && typeof entity.radius === 'number') {
72-
const centerPoint: Point = { x: entity.center.x, y: entity.center.y };
73-
const circle: CircleEntity = {
74-
id: uuidv4(),
75-
type: EntityType.CIRCLE,
76-
layerId: currentLayerId,
77-
center: centerPoint,
78-
radius: entity.radius,
79-
color: (entity.colorNumber !== undefined && entity.colorNumber !== 256) ? `#${entity.colorNumber.toString(16).padStart(6, '0')}` : defaultColor, // TODO: Map DXF color index to HEX
80-
width: defaultWidth, // TODO: Potentially use layer lineweight or entity thickness
81-
};
75+
const dxfCircle = entity as DxfEntities.Circle;
76+
if (dxfCircle.x && dxfCircle.y && dxfCircle.r) {
77+
const centerPoint = new Point(dxfCircle.x, dxfCircle.y);
78+
const circle = new CircleEntity(currentLayerId, centerPoint, dxfCircle.r);
79+
circle.lineColor = getDxfLineColor(dxfCircle.colorNumber);
80+
circle.lineWidth = defaultWidth;
81+
circle.lineDash = undefined;
8282
newEntities.push(circle);
83-
console.log('Converted DXF CIRCLE to CircleEntity:', circle);
8483
} else {
85-
console.warn('Skipping DXF CIRCLE entity due to missing center or radius:', entity);
84+
console.warn('Skipping DXF CIRCLE due to missing center or radius:', dxfCircle);
8685
}
8786
} else {
88-
console.log(`Unsupported DXF entity type: ${entity.type}. Skipping.`);
87+
console.log(`Unsupported DXF type: ${entity.type}. Skipping.`);
8988
}
9089
}
9190

9291
if (newEntities.length > 0) {
93-
setEntities([...getEntities(), ...newEntities]);
94-
toast.success(`${newEntities.length} entities imported successfully from DXF!`);
92+
const uniqueEntities = uniqBy(
93+
newEntities,
94+
(entity) =>
95+
`${JSON.stringify(entity.getShape())}|${entity.lineColor}|${entity.lineWidth}|${entity.lineDash}`
96+
);
97+
setEntities([...getEntities(), ...uniqueEntities]);
98+
toast.success(`${uniqueEntities.length} entities imported successfully from DXF!`);
9599
} else {
96100
toast.info('No supported entities found in the DXF file.');
97101
}
98-
99102
} catch (error) {
100103
console.error('Error parsing DXF file:', error);
101104
toast.error('An error occurred while parsing the DXF file. See console for details.');

src/helpers/rgb-to-hex-color.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function toHex(red: number, green: number, blue: number, alpha: number): string {
2+
return (blue | (green << 8) | (red << 16) | (1 << 24)).toString(16).slice(1) + alpha;
3+
}

0 commit comments

Comments
 (0)