dxf: split into layer-key and block-partition workflows
This commit is contained in:
231
dxf/lib/dxf_basic_parser.mjs
Normal file
231
dxf/lib/dxf_basic_parser.mjs
Normal file
@@ -0,0 +1,231 @@
|
||||
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),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user