OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)

This commit is contained in:
wangruiguo
2026-09-03 18:49:20 +08:00
commit d647428529
3501 changed files with 1988906 additions and 0 deletions

View File

@@ -0,0 +1,265 @@
/**
* 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
}