dxf: split into layer-key and block-partition workflows

This commit is contained in:
2026-04-13 09:22:03 +09:00
parent be58c7ed3c
commit bd79da2b64
13 changed files with 38154 additions and 0 deletions

View 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),
};
}