Files
Leantime/dev/DWGViewer/js/dxfHatch.js

266 lines
9.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* DXF 的 HATCH 补丁解析器。
*
* dxf-parserMIT不认 HATCH而机械图纸里剖面线随处可见缺了整张图会显得很空。
* 这里独立扫一遍 DXF 组码,只挑 HATCH 出来,产出与 libredwg 一致的结构,
* 这样 hatch.js / flatten.js 两边共用同一套渲染逻辑。
*
* 角度统一转成弧度DXF 里 50/51/53 都是度)。
*/
const RAD = Math.PI / 180
/**
* @param {string} text DXF 原文
* @param {Doc} doc 已归一化的文档,会就地把 HATCH 追加进模型空间/对应块
*/
export function parseDxfHatches(text, doc) {
const pairs = tokenize(text)
let section = null
let block = null
let count = 0
for (let i = 0; i < pairs.length; i++) {
const [code, value] = pairs[i]
if (code !== 0) continue
if (value === 'SECTION') {
const nx = pairs[i + 1]
section = nx && nx[0] === 2 ? nx[1] : null
continue
}
if (value === 'ENDSEC') { section = null; block = null; continue }
if (section !== 'ENTITIES' && section !== 'BLOCKS') continue
if (value === 'BLOCK') { block = findValue(pairs, i + 1, 2); continue }
if (value === 'ENDBLK') { block = null; continue }
if (value !== 'HATCH') continue
// 收集到下一个 0 组码为止
let j = i + 1
const body = []
while (j < pairs.length && pairs[j][0] !== 0) { body.push(pairs[j]); j++ }
const ent = parseHatch(body)
if (ent && ent.boundaryPaths.length) {
const target = block ? doc.blocks.get(block) : null
if (target) target.entities.push(ent)
else if (!block) doc.modelEntities.push(ent)
count++
}
i = j - 1
}
return count
}
function findValue(pairs, from, code) {
for (let i = from; i < pairs.length && i < from + 20; i++) {
if (pairs[i][0] === 0) return null
if (pairs[i][0] === code) return pairs[i][1]
}
return null
}
/** DXF 是「一行组码、一行值」的纯文本 */
function tokenize(text) {
const lines = text.split(/\r\n|\r|\n/)
const out = []
for (let i = 0; i + 1 < lines.length; i += 2) {
const c = parseInt(lines[i], 10)
if (!isFinite(c)) { i -= 1; continue } // 行错位时向前挪一行重新对齐
out.push([c, lines[i + 1]])
}
return out
}
function parseHatch(p) {
const ent = {
type: 'HATCH', layer: '0', colorIndex: 256, lineTypeScale: 1, isVisible: true,
patternName: '', solidFill: 0, boundaryPaths: [], definitionLines: [], xdata: [],
}
let i = 0
while (i < p.length) {
const [c, v] = p[i]
switch (c) {
case 8: ent.layer = v; i++; break
case 62: ent.colorIndex = int(v); i++; break
case 420: ent.trueColor = int(v) & 0xffffff; i++; break
case 6: ent.lineType = v; i++; break
case 370: ent.lineweight = int(v); i++; break
case 48: ent.lineTypeScale = num(v) || 1; i++; break
case 60: ent.isVisible = int(v) === 0; i++; break
case 2: ent.patternName = v; i++; break
case 70: ent.solidFill = int(v); i++; break
case 71: ent.associativity = int(v); i++; break
case 75: ent.hatchStyle = int(v); i++; break
case 76: ent.patternType = int(v); i++; break
case 52: ent.patternAngle = num(v) * RAD; i++; break
case 41: ent.patternScale = num(v); i++; break
case 91: i = readPaths(p, i + 1, int(v), ent); break
case 78: i = readDefLines(p, i + 1, int(v), ent); break
default: i++
}
}
return ent
}
const num = (v) => { const n = parseFloat(v); return isFinite(n) ? n : 0 }
const int = (v) => { const n = parseInt(v, 10); return isFinite(n) ? n : 0 }
function seek(p, i, code) {
while (i < p.length && p[i][0] !== code) i++
return i
}
function readPaths(p, i, n, ent) {
for (let k = 0; k < n; k++) {
i = seek(p, i, 92)
if (i >= p.length) break
const flag = int(p[i][1]); i++
if (flag & 2) {
// 多段线边界
const path = { boundaryPathTypeFlag: flag, hasBulge: false, isClosed: false, numberOfVertices: 0, vertices: [] }
let guard = 0
while (i < p.length && guard++ < 8) {
const c = p[i][0]
if (c === 72) { path.hasBulge = int(p[i][1]) !== 0; i++ }
else if (c === 73) { path.isClosed = int(p[i][1]) !== 0; i++ }
else if (c === 93) {
const nv = int(p[i][1]); i++
for (let t = 0; t < nv && i < p.length; t++) {
i = seek(p, i, 10)
if (i >= p.length) break
const x = num(p[i][1]); i++
const y = p[i] && p[i][0] === 20 ? num(p[i][1]) : 0
if (p[i] && p[i][0] === 20) i++
let bulge = 0
if (p[i] && p[i][0] === 42) { bulge = num(p[i][1]); i++ }
path.vertices.push({ x, y, bulge })
}
break
} else i++
}
path.numberOfVertices = path.vertices.length
if (path.vertices.length >= 2) ent.boundaryPaths.push(path)
} else {
// 边(直线/圆弧/椭圆弧/样条)组成的边界
i = seek(p, i, 93)
if (i >= p.length) break
const ne = int(p[i][1]); i++
const path = { boundaryPathTypeFlag: flag, numberOfEdges: ne, edges: [] }
for (let t = 0; t < ne && i < p.length; t++) {
i = seek(p, i, 72)
if (i >= p.length) break
const et = int(p[i][1]); i++
const r = readEdge(p, i, et)
i = r.i
if (r.edge) path.edges.push(r.edge)
}
if (path.edges.length) ent.boundaryPaths.push(path)
}
// 跳过尾部的源对象引用97 + 330...
}
return i
}
/** 顺序读取若干组码,遇到不属于本边的组码就停 */
function readEdge(p, i, type) {
const g = {}
const take = new Set(
type === 1 ? [10, 20, 11, 21]
: type === 2 ? [10, 20, 40, 50, 51, 73]
: type === 3 ? [10, 20, 11, 21, 40, 50, 51, 73]
: [94, 73, 74, 95, 96, 40, 10, 20, 42, 97, 11, 21, 12, 22, 13, 23],
)
if (type === 4) return readSplineEdge(p, i)
while (i < p.length && take.has(p[i][0])) {
const [c, v] = p[i]
if (g[c] !== undefined && (c === 10 || c === 40)) break // 下一条边开始了
g[c] = v
i++
}
switch (type) {
case 1:
return { i, edge: { type: 1, start: { x: num(g[10]), y: num(g[20]) }, end: { x: num(g[11]), y: num(g[21]) } } }
case 2:
return {
i,
edge: {
type: 2, center: { x: num(g[10]), y: num(g[20]) }, radius: num(g[40]),
startAngle: num(g[50]) * RAD, endAngle: num(g[51]) * RAD, isCCW: g[73] == null || int(g[73]) !== 0,
},
}
case 3:
return {
i,
edge: {
type: 3, center: { x: num(g[10]), y: num(g[20]) }, end: { x: num(g[11]), y: num(g[21]) },
lengthOfMinorAxis: num(g[40]), startAngle: num(g[50]) * RAD, endAngle: num(g[51]) * RAD,
isCCW: g[73] == null || int(g[73]) !== 0,
},
}
default:
return { i, edge: null }
}
}
function readSplineEdge(p, i) {
const edge = { type: 4, degree: 3, knots: [], controlPoints: [], fitDatum: [], numberOfKnots: 0, numberOfControlPoints: 0, numberOfFitData: 0 }
// 94 degree, 73 rational, 74 periodic, 95 numKnots, 96 numCtrl
let rational = false
while (i < p.length && [94, 73, 74, 95, 96].includes(p[i][0])) {
const [c, v] = p[i]; i++
if (c === 94) edge.degree = int(v)
else if (c === 73) rational = int(v) !== 0
else if (c === 95) edge.numberOfKnots = int(v)
else if (c === 96) edge.numberOfControlPoints = int(v)
}
for (let k = 0; k < edge.numberOfKnots && i < p.length; k++) {
if (p[i][0] !== 40) break
edge.knots.push(num(p[i][1])); i++
}
for (let k = 0; k < edge.numberOfControlPoints && i < p.length; k++) {
i = seek(p, i, 10)
if (i >= p.length) break
const x = num(p[i][1]); i++
const y = p[i] && p[i][0] === 20 ? num(p[i][1]) : 0
if (p[i] && p[i][0] === 20) i++
let w
if (rational && p[i] && p[i][0] === 42) { w = num(p[i][1]); i++ }
edge.controlPoints.push(w != null ? { x, y, weight: w } : { x, y })
}
if (i < p.length && p[i][0] === 97) {
const nf = int(p[i][1]); i++
for (let k = 0; k < nf && i < p.length; k++) {
if (p[i][0] !== 11) break
const x = num(p[i][1]); i++
const y = p[i] && p[i][0] === 21 ? num(p[i][1]) : 0
if (p[i] && p[i][0] === 21) i++
edge.fitDatum.push({ x, y })
}
edge.numberOfFitData = edge.fitDatum.length
}
return { i, edge }
}
function readDefLines(p, i, n, ent) {
for (let k = 0; k < n; k++) {
i = seek(p, i, 53)
if (i >= p.length) break
const dl = { angle: num(p[i][1]) * RAD, base: { x: 0, y: 0 }, offset: { x: 0, y: 0 }, numberOfDashLengths: 0, dashLengths: [] }
i++
while (i < p.length && [43, 44, 45, 46, 79].includes(p[i][0])) {
const [c, v] = p[i]; i++
if (c === 43) dl.base.x = num(v)
else if (c === 44) dl.base.y = num(v)
else if (c === 45) dl.offset.x = num(v)
else if (c === 46) dl.offset.y = num(v)
else if (c === 79) {
const nd = int(v)
for (let t = 0; t < nd && i < p.length && p[i][0] === 49; t++) { dl.dashLengths.push(num(p[i][1])); i++ }
}
}
dl.numberOfDashLengths = dl.dashLengths.length
ent.definitionLines.push(dl)
}
return i
}