301 lines
10 KiB
JavaScript
301 lines
10 KiB
JavaScript
import fs from "fs";
|
|
import path from "path";
|
|
import {
|
|
boundsFromPoints,
|
|
computeEntityListBounds,
|
|
first,
|
|
num,
|
|
parseDxfText,
|
|
readDxfText,
|
|
transformPoint,
|
|
} from "./lib/dxf_basic_parser.mjs";
|
|
|
|
function parseArgs(argv) {
|
|
const out = {};
|
|
for (let i = 0; i < argv.length; i += 1) {
|
|
const token = argv[i];
|
|
if (!token.startsWith("--")) continue;
|
|
const key = token.slice(2);
|
|
const value = argv[i + 1];
|
|
if (!value || value.startsWith("--")) continue;
|
|
out[key] = value;
|
|
i += 1;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function toFixedNumber(value, fraction = 3) {
|
|
return Number(Number(value).toFixed(fraction));
|
|
}
|
|
|
|
function computeZoneKey(centerX, centerY, bounds, split) {
|
|
const width = Math.max(1, bounds.maxX - bounds.minX);
|
|
const height = Math.max(1, bounds.maxY - bounds.minY);
|
|
const col = Math.min(split - 1, Math.max(0, Math.floor(((centerX - bounds.minX) / width) * split)));
|
|
const row = Math.min(split - 1, Math.max(0, Math.floor(((centerY - bounds.minY) / height) * split)));
|
|
return `Z-R${row + 1}C${col + 1}`;
|
|
}
|
|
|
|
function buildBlockPartitionData(dxfPath, split) {
|
|
const { blocks, entities, headerBounds } = parseDxfText(readDxfText(dxfPath));
|
|
const drawingWidth = Math.max(1, headerBounds.maxX - headerBounds.minX);
|
|
const drawingHeight = Math.max(1, headerBounds.maxY - headerBounds.minY);
|
|
const drawingArea = drawingWidth * drawingHeight;
|
|
|
|
const blockDefs = new Map();
|
|
for (const block of blocks) {
|
|
const name = first(block, 2) ?? "*anonymous*";
|
|
const basePoint = { x: num(block, 10), y: num(block, 20) };
|
|
const localBounds = computeEntityListBounds(block.entities);
|
|
blockDefs.set(name, {
|
|
name,
|
|
basePoint,
|
|
localBounds,
|
|
entityCount: block.entities.length,
|
|
});
|
|
}
|
|
|
|
const partitions = [];
|
|
for (const entity of entities) {
|
|
if (entity.type !== "INSERT") continue;
|
|
const blockName = first(entity, 2) ?? "";
|
|
const blockLayer = first(entity, 8) ?? "0";
|
|
const blockDef = blockDefs.get(blockName);
|
|
if (!blockDef || !blockDef.localBounds) continue;
|
|
|
|
const insert = {
|
|
x: num(entity, 10),
|
|
y: num(entity, 20),
|
|
rotation: num(entity, 50),
|
|
xScale: num(entity, 41, 1),
|
|
yScale: num(entity, 42, 1),
|
|
};
|
|
|
|
const b = blockDef.localBounds;
|
|
const corners = [
|
|
{ x: b.minX, y: b.minY },
|
|
{ x: b.maxX, y: b.minY },
|
|
{ x: b.maxX, y: b.maxY },
|
|
{ x: b.minX, y: b.maxY },
|
|
].map((point) => transformPoint(point, insert, blockDef.basePoint));
|
|
|
|
const worldBounds = boundsFromPoints(corners);
|
|
if (!worldBounds) continue;
|
|
const area = Math.max(1, worldBounds.width * worldBounds.height);
|
|
const oversized =
|
|
area > drawingArea * 0.6 ||
|
|
worldBounds.width > drawingWidth * 0.9 ||
|
|
worldBounds.height > drawingHeight * 0.9;
|
|
if (oversized) continue;
|
|
|
|
const zoneKey = computeZoneKey(worldBounds.cx, worldBounds.cy, headerBounds, split);
|
|
partitions.push({
|
|
id: `P-${String(partitions.length + 1).padStart(4, "0")}`,
|
|
zoneKey,
|
|
blockName,
|
|
layer: blockLayer,
|
|
handle: first(entity, 5) ?? "",
|
|
rotation: toFixedNumber(insert.rotation, 2),
|
|
scale: {
|
|
x: toFixedNumber(insert.xScale, 4),
|
|
y: toFixedNumber(insert.yScale, 4),
|
|
},
|
|
center: {
|
|
x: toFixedNumber(worldBounds.cx),
|
|
y: toFixedNumber(worldBounds.cy),
|
|
},
|
|
bounds: {
|
|
minX: toFixedNumber(worldBounds.minX),
|
|
minY: toFixedNumber(worldBounds.minY),
|
|
maxX: toFixedNumber(worldBounds.maxX),
|
|
maxY: toFixedNumber(worldBounds.maxY),
|
|
width: toFixedNumber(worldBounds.width),
|
|
height: toFixedNumber(worldBounds.height),
|
|
},
|
|
blockEntityCount: blockDef.entityCount,
|
|
});
|
|
}
|
|
|
|
partitions.sort((a, b) => {
|
|
const dy = b.center.y - a.center.y;
|
|
if (Math.abs(dy) > 0.0001) return dy;
|
|
return a.center.x - b.center.x;
|
|
});
|
|
|
|
const byBlock = {};
|
|
const byZone = {};
|
|
for (const item of partitions) {
|
|
byBlock[item.blockName] = (byBlock[item.blockName] ?? 0) + 1;
|
|
byZone[item.zoneKey] = (byZone[item.zoneKey] ?? 0) + 1;
|
|
}
|
|
|
|
return {
|
|
meta: {
|
|
source: path.basename(dxfPath),
|
|
generatedAt: new Date().toISOString(),
|
|
split,
|
|
partitionCount: partitions.length,
|
|
blockDefinitionCount: blockDefs.size,
|
|
headerBounds,
|
|
},
|
|
byBlock,
|
|
byZone,
|
|
partitions,
|
|
};
|
|
}
|
|
|
|
function buildHtml(payload) {
|
|
return `<!doctype html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>DXF block partition report</title>
|
|
<style>
|
|
:root {
|
|
--bg: #f2f4f8;
|
|
--card: #ffffff;
|
|
--line: #d8dee8;
|
|
--ink: #152230;
|
|
--muted: #6b7a8e;
|
|
--accent: #1861bf;
|
|
--accent-soft: rgba(24, 97, 191, 0.12);
|
|
}
|
|
* { box-sizing: border-box; }
|
|
body { margin: 0; font-family: "Segoe UI", "Pretendard", sans-serif; color: var(--ink); background: linear-gradient(180deg, #f8fafc 0%, var(--bg) 100%); }
|
|
.layout { display: grid; grid-template-columns: 1.8fr 1fr; gap: 14px; padding: 14px; min-height: 100vh; }
|
|
.panel { background: var(--card); border: 1px solid var(--line); border-radius: 14px; overflow: hidden; box-shadow: 0 10px 24px rgba(28, 39, 58, 0.06); }
|
|
.header { padding: 12px 14px; border-bottom: 1px solid var(--line); display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
|
|
.header strong { font-size: 14px; }
|
|
.header span { font-size: 12px; color: var(--muted); }
|
|
.canvas-wrap { padding: 12px; }
|
|
svg { width: 100%; height: 78vh; border: 1px solid var(--line); border-radius: 10px; background: #f9fbfd; }
|
|
.list { max-height: 84vh; overflow: auto; }
|
|
table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
|
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid #ecf0f6; white-space: nowrap; }
|
|
th { position: sticky; top: 0; background: #f8fafc; z-index: 1; font-weight: 600; }
|
|
tr:hover td { background: var(--accent-soft); }
|
|
.tag { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px; border: 1px solid #cadaf4; color: #124c95; background: #edf4ff; }
|
|
@media (max-width: 980px) {
|
|
.layout { grid-template-columns: 1fr; }
|
|
svg { height: 54vh; }
|
|
.list { max-height: none; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="layout">
|
|
<section class="panel">
|
|
<div class="header">
|
|
<strong>Block partition map</strong>
|
|
<span id="meta"></span>
|
|
</div>
|
|
<div class="canvas-wrap">
|
|
<svg id="map"></svg>
|
|
</div>
|
|
</section>
|
|
<section class="panel">
|
|
<div class="header">
|
|
<strong>Partition list</strong>
|
|
<span>${payload.partitions.length} items</span>
|
|
</div>
|
|
<div class="list">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Zone</th>
|
|
<th>Block</th>
|
|
<th>Layer</th>
|
|
<th>Size</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="rows"></tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
<script>
|
|
const DATA = ${JSON.stringify(payload)};
|
|
const map = document.getElementById("map");
|
|
const rows = document.getElementById("rows");
|
|
const meta = document.getElementById("meta");
|
|
|
|
const W = 1600;
|
|
const H = 980;
|
|
map.setAttribute("viewBox", \`0 0 \${W} \${H}\`);
|
|
meta.textContent = \`\${DATA.meta.source} | partitions: \${DATA.meta.partitionCount}\`;
|
|
|
|
const bounds = DATA.meta.headerBounds;
|
|
const worldW = Math.max(1, bounds.maxX - bounds.minX);
|
|
const worldH = Math.max(1, bounds.maxY - bounds.minY);
|
|
const pad = 30;
|
|
const drawW = W - pad * 2;
|
|
const drawH = H - pad * 2;
|
|
|
|
function tx(x) { return pad + ((x - bounds.minX) / worldW) * drawW; }
|
|
function ty(y) { return pad + ((bounds.maxY - y) / worldH) * drawH; }
|
|
|
|
const bg = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
bg.setAttribute("x", pad);
|
|
bg.setAttribute("y", pad);
|
|
bg.setAttribute("width", drawW);
|
|
bg.setAttribute("height", drawH);
|
|
bg.setAttribute("fill", "#ffffff");
|
|
bg.setAttribute("stroke", "#dde5ef");
|
|
map.appendChild(bg);
|
|
|
|
DATA.partitions.forEach((item, index) => {
|
|
const x = tx(item.bounds.minX);
|
|
const y = ty(item.bounds.maxY);
|
|
const width = Math.max(2, tx(item.bounds.maxX) - tx(item.bounds.minX));
|
|
const height = Math.max(2, ty(item.bounds.minY) - ty(item.bounds.maxY));
|
|
|
|
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
|
rect.setAttribute("x", x);
|
|
rect.setAttribute("y", y);
|
|
rect.setAttribute("width", width);
|
|
rect.setAttribute("height", height);
|
|
rect.setAttribute("fill", "rgba(24,97,191,0.12)");
|
|
rect.setAttribute("stroke", "rgba(24,97,191,0.8)");
|
|
rect.setAttribute("stroke-width", "1.1");
|
|
map.appendChild(rect);
|
|
|
|
if (index % 4 === 0) {
|
|
const label = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
|
label.setAttribute("x", x + 3);
|
|
label.setAttribute("y", Math.max(16, y + 12));
|
|
label.setAttribute("fill", "#0e3d7b");
|
|
label.setAttribute("font-size", "11");
|
|
label.textContent = item.id;
|
|
map.appendChild(label);
|
|
}
|
|
|
|
const tr = document.createElement("tr");
|
|
tr.innerHTML = \`
|
|
<td>\${item.id}</td>
|
|
<td><span class="tag">\${item.zoneKey}</span></td>
|
|
<td>\${item.blockName}</td>
|
|
<td>\${item.layer}</td>
|
|
<td>\${item.bounds.width} x \${item.bounds.height}</td>
|
|
\`;
|
|
rows.appendChild(tr);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>`;
|
|
}
|
|
|
|
const args = parseArgs(process.argv.slice(2));
|
|
const scriptDir = path.dirname(new URL(import.meta.url).pathname);
|
|
const dxfPath = path.resolve(args.input ?? path.join(scriptDir, "center.dxf"));
|
|
const outputJsonPath = path.resolve(args.json ?? path.join(scriptDir, "block_partition_report.json"));
|
|
const outputHtmlPath = path.resolve(args.html ?? path.join(scriptDir, "block_partition_report.html"));
|
|
const split = Math.max(2, Number(args.split ?? 4) || 4);
|
|
|
|
const report = buildBlockPartitionData(dxfPath, split);
|
|
fs.writeFileSync(outputJsonPath, `${JSON.stringify(report, null, 2)}\n`, "utf8");
|
|
fs.writeFileSync(outputHtmlPath, buildHtml(report), "utf8");
|
|
console.log(`saved: ${outputJsonPath}`);
|
|
console.log(`saved: ${outputHtmlPath}`);
|