import fs from "fs"; export function readDxfText(filePath) { return fs.readFileSync(filePath, "utf8").replace(/\r/g, ""); } export function parseDxfText(text) { const pairs = parsePairs(text); const sections = parseSections(pairs); return { sections, headerBounds: parseHeaderBounds(sections.get("HEADER") ?? []), entities: collectEntities(sections.get("ENTITIES") ?? []), blocks: collectBlocks(sections.get("BLOCKS") ?? []), }; } export function first(item, code) { const hit = item.pairs.find(([group]) => group === String(code)); return hit ? hit[1].trim() : undefined; } export function all(item, code) { return item.pairs.filter(([group]) => group === String(code)).map(([, value]) => value.trim()); } export function num(item, code, fallback = 0) { const value = first(item, code); return value == null || value === "" ? fallback : Number(value); } export function pointsFromLWPolyline(entity) { const xs = all(entity, 10).map(Number); const ys = all(entity, 20).map(Number); const points = []; for (let i = 0; i < xs.length; i += 1) { points.push({ x: xs[i], y: ys[i] ?? 0 }); } return points; } export function extractPolylineVertices(entityList, startIndex) { const entity = entityList[startIndex]; const closed = (num(entity, 70) & 1) === 1; const points = []; let i = startIndex + 1; while (i < entityList.length) { const next = entityList[i]; if (next.type === "VERTEX") { points.push({ x: num(next, 10), y: num(next, 20) }); i += 1; continue; } if (next.type === "SEQEND") break; break; } return { points, closed, nextIndex: i }; } export function boundsFromPoints(points) { if (!points.length) return null; let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity; for (const point of points) { minX = Math.min(minX, point.x); minY = Math.min(minY, point.y); maxX = Math.max(maxX, point.x); maxY = Math.max(maxY, point.y); } return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY, cx: (minX + maxX) / 2, cy: (minY + maxY) / 2, }; } export function transformPoint(point, insert, basePoint) { const theta = (insert.rotation * Math.PI) / 180; const localX = (point.x - basePoint.x) * insert.xScale; const localY = (point.y - basePoint.y) * insert.yScale; return { x: insert.x + localX * Math.cos(theta) - localY * Math.sin(theta), y: insert.y + localX * Math.sin(theta) + localY * Math.cos(theta), }; } export function computeEntityListBounds(entityList) { const points = []; for (let i = 0; i < entityList.length; i += 1) { const entity = entityList[i]; if (entity.type === "LINE") { points.push({ x: num(entity, 10), y: num(entity, 20) }); points.push({ x: num(entity, 11), y: num(entity, 21) }); continue; } if (entity.type === "CIRCLE" || entity.type === "ARC") { const cx = num(entity, 10); const cy = num(entity, 20); const r = Math.abs(num(entity, 40)); points.push({ x: cx - r, y: cy - r }); points.push({ x: cx + r, y: cy + r }); continue; } if (entity.type === "INSERT") { points.push({ x: num(entity, 10), y: num(entity, 20) }); continue; } if (entity.type === "LWPOLYLINE") { points.push(...pointsFromLWPolyline(entity)); continue; } if (entity.type === "POLYLINE") { const parsed = extractPolylineVertices(entityList, i); points.push(...parsed.points); i = parsed.nextIndex; } } return boundsFromPoints(points); } function parsePairs(text) { const lines = text.split("\n"); const pairs = []; for (let i = 0; i < lines.length - 1; i += 2) { pairs.push([lines[i].trim(), lines[i + 1]]); } return pairs; } function parseSections(pairs) { const sections = new Map(); let sectionName = null; let bucket = null; for (const [code, value] of pairs) { if (code === "0" && value === "SECTION") { sectionName = null; bucket = []; continue; } if (code === "2" && bucket && sectionName === null) { sectionName = value.trim(); sections.set(sectionName, bucket); continue; } if (code === "0" && value === "ENDSEC") { sectionName = null; bucket = null; continue; } if (bucket) bucket.push([code, value]); } return sections; } function collectEntities(sectionPairs) { const entities = []; let entity = null; for (const [code, value] of sectionPairs) { if (code === "0") { if (entity) entities.push(entity); if (value === "EOF") break; entity = { type: value.trim(), pairs: [] }; } if (entity) entity.pairs.push([code, value]); } if (entity) entities.push(entity); return entities; } function collectBlocks(sectionPairs) { const blocks = []; let block = null; let entity = null; for (const [code, value] of sectionPairs) { if (code === "0" && value === "BLOCK") { block = { pairs: [], entities: [] }; entity = null; continue; } if (code === "0" && value === "ENDBLK") { if (entity) { block.entities.push(entity); entity = null; } if (block) blocks.push(block); block = null; continue; } if (!block) continue; if (!entity) { if (code === "0") entity = { type: value.trim(), pairs: [[code, value]] }; else block.pairs.push([code, value]); continue; } if (code === "0") { block.entities.push(entity); entity = { type: value.trim(), pairs: [[code, value]] }; } else { entity.pairs.push([code, value]); } } return blocks; } function parseHeaderBounds(headerPairs) { const header = {}; let current = null; for (const [code, rawValue] of headerPairs) { const value = rawValue.trim(); if (code === "9") { current = value; header[current] = {}; continue; } if (!current) continue; header[current][code] = value; } return { minX: Number(header.$EXTMIN?.["10"] ?? 0), minY: Number(header.$EXTMIN?.["20"] ?? 0), maxX: Number(header.$EXTMAX?.["10"] ?? 0), maxY: Number(header.$EXTMAX?.["20"] ?? 0), }; }