OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
517
dev/DWGViewer/js/flatten.js
Normal file
517
dev/DWGViewer/js/flatten.js
Normal file
@@ -0,0 +1,517 @@
|
||||
/**
|
||||
* 把归一化后的图纸文档(Doc)展平成渲染用的「显示列表」。
|
||||
*
|
||||
* 展平只做一次(加载时),之后缩放平移都不再重算几何。
|
||||
* INSERT 会递归展开并把变换烘焙进坐标,因此显示列表里的坐标全是世界坐标。
|
||||
*/
|
||||
|
||||
import ACI from '../libs/dxf-parser/AutoCadColorIndex.js'
|
||||
import {
|
||||
applyX, applyY, arcBBox, arcPoints, bboxOfPoints, bulgeArcPoints, circlePoints, conformal,
|
||||
ellipsePoints, fitPointCurve, growBox, IDENTITY, makeInsertMatrix, matrixScale, mul,
|
||||
splinePoints, TAU, transformPoints,
|
||||
} from './curves.js'
|
||||
import { builtinDefinitionLines, hatchLoops, hatchPatternSegments } from './hatch.js'
|
||||
import { parseMText, parseSimpleText } from './mtext.js'
|
||||
|
||||
const MAX_INSERT_DEPTH = 12
|
||||
const MAX_ARRAY_CELLS = 2000
|
||||
|
||||
/** DWG 线宽枚举(值 = 1/100 mm);其余为特殊值 */
|
||||
const LW_TABLE = new Set([0, 5, 9, 13, 15, 18, 20, 25, 30, 35, 40, 50, 53, 60, 70, 80, 90, 100, 106, 120, 140, 158, 200, 211])
|
||||
const LW_BYLAYER = -1, LW_BYBLOCK = -2, LW_DEFAULT = -3
|
||||
|
||||
function normLineweight(v) {
|
||||
if (v == null) return LW_BYLAYER
|
||||
if (v < 0) return v >= -3 ? v : LW_BYLAYER
|
||||
if (v === 28) return LW_BYLAYER
|
||||
if (v === 29) return LW_BYBLOCK
|
||||
if (v === 30 || v === 31) return LW_DEFAULT
|
||||
return LW_TABLE.has(v) ? v : LW_DEFAULT
|
||||
}
|
||||
|
||||
/** ACI 索引 → 0xRRGGBB */
|
||||
export function aciColor(i) {
|
||||
if (i == null || i < 0 || i > 255) return 0x000000
|
||||
const c = ACI[i]
|
||||
return typeof c === 'number' ? c : 0x000000
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析实体最终颜色。
|
||||
* @returns {number} 0xRRGGBB
|
||||
*/
|
||||
function resolveColor(ent, layer, inherited) {
|
||||
if (typeof ent.trueColor === 'number') return ent.trueColor & 0xffffff
|
||||
const ci = ent.colorIndex
|
||||
if (ci === 0) return inherited != null ? inherited : (layer ? layerColor(layer) : 0x000000) // BYBLOCK
|
||||
if (ci == null || ci === 256) return layer ? layerColor(layer) : 0x000000 // BYLAYER
|
||||
if (ci > 0 && ci < 256) return aciColor(ci)
|
||||
return 0x000000
|
||||
}
|
||||
|
||||
function layerColor(layer) {
|
||||
if (typeof layer.trueColor === 'number') return layer.trueColor & 0xffffff
|
||||
if (layer.colorIndex != null && layer.colorIndex !== 0) return aciColor(Math.abs(layer.colorIndex))
|
||||
if (typeof layer.color === 'number' && layer.color !== 16777215) return layer.color & 0xffffff
|
||||
return 0x000000
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 主入口
|
||||
|
||||
/**
|
||||
* @param {Doc} doc 归一化文档
|
||||
* @param {object} opt { space: 'model' | layout 名 }
|
||||
* @returns {{shapes: Array, bbox: number[], stats: object}}
|
||||
*/
|
||||
export function flatten(doc, opt = {}) {
|
||||
const shapes = []
|
||||
const bbox = [Infinity, Infinity, -Infinity, -Infinity]
|
||||
const stats = { entities: 0, shapes: 0, skipped: 0, byType: {} }
|
||||
const ctx = { doc, shapes, bbox, stats, blockStack: new Set() }
|
||||
|
||||
const entities = opt.entities || doc.modelEntities || []
|
||||
for (const ent of entities) {
|
||||
emitEntity(ctx, ent, IDENTITY, null, null, 0)
|
||||
}
|
||||
stats.shapes = shapes.length
|
||||
return { shapes, bbox, stats }
|
||||
}
|
||||
|
||||
function emitEntity(ctx, ent, m, inheritColor, inheritLayer, depth) {
|
||||
if (!ent || ent.isVisible === false) return
|
||||
const type = ent.type
|
||||
ctx.stats.entities++
|
||||
ctx.stats.byType[type] = (ctx.stats.byType[type] || 0) + 1
|
||||
|
||||
// 图层:块内实体用 "0" 图层时继承外层图层(AutoCAD 规则)
|
||||
let layerName = ent.layer || '0'
|
||||
if (layerName === '0' && inheritLayer) layerName = inheritLayer
|
||||
const layer = ctx.doc.layers.get(layerName)
|
||||
|
||||
const base = {
|
||||
layer: layerName,
|
||||
color: resolveColor(ent, layer, inheritColor),
|
||||
lw: normLineweight(ent.lineweight),
|
||||
lt: resolveLineType(ent, layer),
|
||||
ltScale: ent.lineTypeScale || 1,
|
||||
ent,
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'LINE': {
|
||||
const p = ent.startPoint, q = ent.endPoint
|
||||
if (!p || !q) return
|
||||
addPoly(ctx, base, new Float64Array([
|
||||
applyX(m, p.x, p.y), applyY(m, p.x, p.y),
|
||||
applyX(m, q.x, q.y), applyY(m, q.x, q.y),
|
||||
]), false)
|
||||
break
|
||||
}
|
||||
case 'CIRCLE':
|
||||
addCircleLike(ctx, base, m, ent.center, ent.radius, ent.radius, 0, 0, TAU)
|
||||
break
|
||||
case 'ARC': {
|
||||
const a0 = ang(ent.startAngle), a1 = ang(ent.endAngle)
|
||||
addCircleLike(ctx, base, m, ent.center, ent.radius, ent.radius, 0, a0, a1)
|
||||
break
|
||||
}
|
||||
case 'ELLIPSE': {
|
||||
const mj = ent.majorAxisEndPoint || { x: 1, y: 0 }
|
||||
const rx = Math.hypot(mj.x, mj.y)
|
||||
const ry = rx * (ent.axisRatio || 1)
|
||||
const rot = Math.atan2(mj.y, mj.x)
|
||||
let a0 = ent.startAngle || 0, a1 = ent.endAngle
|
||||
if (a1 == null || Math.abs((a1 - a0) % TAU) < 1e-9) { a0 = 0; a1 = TAU }
|
||||
addCircleLike(ctx, base, m, ent.center, rx, ry, rot, a0, a1)
|
||||
break
|
||||
}
|
||||
case 'LWPOLYLINE': {
|
||||
const pts = lwPolyPoints(ent)
|
||||
if (pts.length >= 4) addPoly(ctx, base, transformPoints(pts, m), isClosed(ent.flag))
|
||||
break
|
||||
}
|
||||
case 'POLYLINE':
|
||||
case 'POLYLINE2D':
|
||||
case 'POLYLINE3D': {
|
||||
const pts = polyPoints(ent)
|
||||
if (pts.length >= 4) addPoly(ctx, base, transformPoints(pts, m), isClosed(ent.flag))
|
||||
break
|
||||
}
|
||||
case 'SPLINE': {
|
||||
const pts = splineEntityPoints(ent)
|
||||
if (pts.length >= 4) addPoly(ctx, base, transformPoints(pts, m), !!(ent.flag & 1))
|
||||
break
|
||||
}
|
||||
case 'POINT': {
|
||||
const p = ent.position || ent.startPoint || ent.center
|
||||
if (!p) return
|
||||
push(ctx, { ...base, kind: 'point', x: applyX(m, p.x, p.y), y: applyY(m, p.x, p.y) },
|
||||
[applyX(m, p.x, p.y), applyY(m, p.x, p.y), applyX(m, p.x, p.y), applyY(m, p.x, p.y)])
|
||||
break
|
||||
}
|
||||
case 'SOLID':
|
||||
case 'TRACE': {
|
||||
const cs = [ent.corner1, ent.corner2, ent.corner4, ent.corner3].filter(Boolean)
|
||||
if (cs.length < 3) return
|
||||
const pts = new Float64Array(cs.length * 2)
|
||||
cs.forEach((c, i) => { pts[i * 2] = applyX(m, c.x, c.y); pts[i * 2 + 1] = applyY(m, c.x, c.y) })
|
||||
push(ctx, { ...base, kind: 'fill', loops: [pts] }, bboxOfPoints(pts))
|
||||
break
|
||||
}
|
||||
case '3DFACE': {
|
||||
const cs = [ent.corner1 || ent.vertices?.[0], ent.corner2 || ent.vertices?.[1],
|
||||
ent.corner3 || ent.vertices?.[2], ent.corner4 || ent.vertices?.[3]].filter(Boolean)
|
||||
if (cs.length < 3) return
|
||||
const pts = new Float64Array(cs.length * 2)
|
||||
cs.forEach((c, i) => { pts[i * 2] = applyX(m, c.x, c.y); pts[i * 2 + 1] = applyY(m, c.x, c.y) })
|
||||
addPoly(ctx, base, pts, true)
|
||||
break
|
||||
}
|
||||
case 'HATCH':
|
||||
emitHatch(ctx, base, ent, m)
|
||||
break
|
||||
case 'TEXT':
|
||||
case 'ATTRIB':
|
||||
case 'ATTDEF':
|
||||
emitText(ctx, base, ent, m)
|
||||
break
|
||||
case 'MTEXT':
|
||||
emitMText(ctx, base, ent, m)
|
||||
break
|
||||
case 'DIMENSION':
|
||||
case 'ARC_DIMENSION':
|
||||
emitBlockRef(ctx, base, ent, ent.name, m, inheritColor, layerName, depth, ent.definitionPoint)
|
||||
break
|
||||
case 'LEADER':
|
||||
emitLeader(ctx, base, ent, m)
|
||||
break
|
||||
case 'MULTILEADER':
|
||||
case 'MLEADER':
|
||||
emitMLeader(ctx, base, ent, m)
|
||||
break
|
||||
case 'INSERT':
|
||||
emitInsert(ctx, base, ent, m, inheritColor, layerName, depth)
|
||||
break
|
||||
case 'VIEWPORT':
|
||||
case 'SEQEND':
|
||||
case 'VERTEX':
|
||||
case 'BLOCK':
|
||||
case 'ENDBLK':
|
||||
break
|
||||
default:
|
||||
ctx.stats.skipped++
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 各类实体
|
||||
|
||||
function resolveLineType(ent, layer) {
|
||||
let lt = ent.lineType || ent.lineTypeName
|
||||
if (!lt || lt === 'ByLayer' || lt === 'BYLAYER') lt = layer ? layer.lineType : null
|
||||
if (!lt || lt === 'ByBlock' || lt === 'BYBLOCK') return null
|
||||
if (/^continuous$/i.test(lt)) return null
|
||||
return lt
|
||||
}
|
||||
|
||||
const isClosed = (flag) => !!(flag & 1)
|
||||
/**
|
||||
* 文档内的角度约定:一律弧度。
|
||||
* libredwg 本身就给弧度;dxf-parser 混用(圆弧是弧度、rotation 是度),
|
||||
* 已在 source.js 的归一化里统一,这里不再换算。
|
||||
*/
|
||||
const ang = (a) => a || 0
|
||||
|
||||
function lwPolyPoints(ent) {
|
||||
const vs = ent.vertices || []
|
||||
if (!vs.length) return new Float64Array(0)
|
||||
const out = []
|
||||
const closed = isClosed(ent.flag)
|
||||
const n = vs.length
|
||||
out.push(vs[0].x, vs[0].y)
|
||||
for (let i = 0; i < (closed ? n : n - 1); i++) {
|
||||
const a = vs[i], b = vs[(i + 1) % n]
|
||||
if (a.bulge) out.push(...bulgeArcPoints(a.x, a.y, b.x, b.y, a.bulge))
|
||||
else out.push(b.x, b.y)
|
||||
}
|
||||
return new Float64Array(out)
|
||||
}
|
||||
|
||||
function polyPoints(ent) {
|
||||
const vs = (ent.vertices || []).filter((v) => v && v.x != null)
|
||||
if (!vs.length) return new Float64Array(0)
|
||||
// 曲线拟合的多段线(flag & 4 = spline-fit):顶点已是拟合点,直接连
|
||||
const out = []
|
||||
const closed = isClosed(ent.flag)
|
||||
const n = vs.length
|
||||
out.push(vs[0].x, vs[0].y)
|
||||
for (let i = 0; i < (closed ? n : n - 1); i++) {
|
||||
const a = vs[i], b = vs[(i + 1) % n]
|
||||
if (a.bulge) out.push(...bulgeArcPoints(a.x, a.y, b.x, b.y, a.bulge))
|
||||
else out.push(b.x, b.y)
|
||||
}
|
||||
return new Float64Array(out)
|
||||
}
|
||||
|
||||
function splineEntityPoints(ent) {
|
||||
const cps = ent.controlPoints || []
|
||||
if (cps.length >= 2) {
|
||||
const weights = ent.weights
|
||||
const pts = cps.map((c, i) => ({ x: c.x, y: c.y, weight: weights ? weights[i] : c.weight }))
|
||||
return splinePoints(ent.degree || 3, pts, ent.knots, !!(ent.flag & 1))
|
||||
}
|
||||
const fps = ent.fitPoints || []
|
||||
if (fps.length >= 2) return fitPointCurve(fps, !!(ent.flag & 1))
|
||||
return new Float64Array(0)
|
||||
}
|
||||
|
||||
function addPoly(ctx, base, pts, closed) {
|
||||
if (pts.length < 4) return
|
||||
push(ctx, { ...base, kind: 'poly', pts, closed }, bboxOfPoints(pts))
|
||||
}
|
||||
|
||||
/**
|
||||
* 圆/圆弧/椭圆:变换若是共形的(等比缩放+旋转),保留解析形式交给
|
||||
* canvas 的 ellipse() 精确绘制;否则退化成折线。
|
||||
*/
|
||||
function addCircleLike(ctx, base, m, center, rx, ry, rot, a0, a1) {
|
||||
if (!center) return
|
||||
if (!(rx > 0)) return
|
||||
const full = Math.abs(a1 - a0) >= TAU - 1e-9
|
||||
const cf = conformal(m)
|
||||
if (cf) {
|
||||
// 共形变换下圆仍是圆、椭圆仍是椭圆,保留解析形式让 canvas 画出无锯齿的曲线。
|
||||
// 镜像会让参数角反向:a → -a,且弧的走向翻转,故起止角对调。
|
||||
const s = cf.scale, phi = cf.rotation
|
||||
const R = cf.mirror ? phi - rot : rot + phi
|
||||
const A0 = cf.mirror ? -a1 : a0
|
||||
const A1 = cf.mirror ? -a0 : a1
|
||||
const cx = applyX(m, center.x, center.y)
|
||||
const cy = applyY(m, center.x, center.y)
|
||||
const RX = rx * s, RY = ry * s
|
||||
push(ctx, { ...base, kind: 'arc', cx, cy, rx: RX, ry: RY, rot: R, a0: A0, a1: A1 },
|
||||
arcBBox(cx, cy, RX, RY, R, A0, A1))
|
||||
return
|
||||
}
|
||||
const pts = rx === ry
|
||||
? (full ? circlePoints(center.x, center.y, rx) : arcPoints(center.x, center.y, rx, a0, a1))
|
||||
: ellipsePoints(center.x, center.y, rx * Math.cos(rot), rx * Math.sin(rot), ry / rx, a0, a1)
|
||||
addPoly(ctx, base, transformPoints(pts, m), full)
|
||||
}
|
||||
|
||||
function emitHatch(ctx, base, ent, m) {
|
||||
const loops = hatchLoops(ent)
|
||||
if (!loops.length) return
|
||||
const solid = ent.solidFill === 1 || /^solid$/i.test(ent.patternName || '')
|
||||
if (solid) {
|
||||
const world = loops.map((l) => transformPoints(l, m))
|
||||
const bb = [Infinity, Infinity, -Infinity, -Infinity]
|
||||
world.forEach((l) => growBox(bb, bboxOfPoints(l)))
|
||||
push(ctx, { ...base, kind: 'fill', loops: world }, bb)
|
||||
return
|
||||
}
|
||||
// 文件里没带定义线时(预定义图案只存名字),从内置图案表展开
|
||||
let defs = ent.definitionLines
|
||||
if (!defs || !defs.length) {
|
||||
defs = builtinDefinitionLines(ent.patternName, ent.patternAngle || 0, ent.patternScale || 1)
|
||||
}
|
||||
const segs = hatchPatternSegments(loops, defs)
|
||||
if (!segs.length) {
|
||||
// 没有图案定义线(例如未知图案):至少把边界画出来
|
||||
for (const l of loops) addPoly(ctx, base, transformPoints(l, m), true)
|
||||
return
|
||||
}
|
||||
const arr = transformPoints(new Float64Array(segs), m)
|
||||
push(ctx, { ...base, kind: 'segs', pts: arr }, bboxOfPoints(arr))
|
||||
}
|
||||
|
||||
function emitText(ctx, base, ent, m) {
|
||||
const raw = ent.text
|
||||
if (raw == null || raw === '') return
|
||||
const txt = parseSimpleText(raw)
|
||||
if (!txt) return
|
||||
const style = ctx.doc.styles.get(ent.styleName) || null
|
||||
const sc = matrixScale(m)
|
||||
const h = (ent.textHeight || (style && style.fixedTextHeight) || 2.5) * sc
|
||||
if (!(h > 0)) return
|
||||
// TEXT 的对齐点:halign/valign 非默认时用 endPoint(第二对齐点)
|
||||
const useAlt = (ent.halign && ent.halign !== 0) || (ent.valign && ent.valign !== 0)
|
||||
const p = (useAlt && ent.endPoint && (ent.endPoint.x || ent.endPoint.y)) ? ent.endPoint : (ent.startPoint || ent.position)
|
||||
if (!p) return
|
||||
const cf = conformal(m)
|
||||
const rot = ang(ent.rotation) + (cf ? cf.rotation : 0)
|
||||
push(ctx, {
|
||||
...base,
|
||||
kind: 'text',
|
||||
x: applyX(m, p.x, p.y),
|
||||
y: applyY(m, p.x, p.y),
|
||||
h,
|
||||
rot,
|
||||
wFactor: ent.xScale || (style && style.widthFactor) || 1,
|
||||
oblique: ang(ent.obliqueAngle || (style && style.obliqueAngle) || 0),
|
||||
halign: ent.halign || 0,
|
||||
valign: ent.valign || 0,
|
||||
mirrorX: !!(ent.generationFlag & 2),
|
||||
mirrorY: !!(ent.generationFlag & 4),
|
||||
font: style && style.font,
|
||||
runs: [{ text: txt, hFactor: 1, wFactor: 1, rise: 0 }],
|
||||
plain: txt,
|
||||
}, textBox(applyX(m, p.x, p.y), applyY(m, p.x, p.y),
|
||||
charWidth(txt) * h * (ent.xScale || 1), h,
|
||||
ent.halign === 1 || ent.halign === 4 ? 0.5 : ent.halign === 2 ? 1 : 0,
|
||||
ent.valign === 3 ? 1 : ent.valign === 2 || ent.halign === 4 ? 0.5 : 0,
|
||||
rot))
|
||||
}
|
||||
|
||||
/** 估算字符串宽度(以字高为单位):中日韩按 1 个字宽,西文按 0.55 */
|
||||
function charWidth(t) {
|
||||
let w = 0
|
||||
for (const c of t) w += c.charCodeAt(0) > 0x2e80 ? 1 : 0.55
|
||||
return w
|
||||
}
|
||||
|
||||
/**
|
||||
* 文字包围盒。
|
||||
* @param ax 0=左 0.5=中 1=右;ay 0=基线/底 0.5=中 1=顶
|
||||
*/
|
||||
function textBox(x, y, w, h, ax, ay, rot) {
|
||||
const x0 = -w * ax, x1 = x0 + w
|
||||
const y1 = h * (1 - ay) + h * 0.25 // 多留一点给下伸部
|
||||
const y0 = y1 - h * 1.25
|
||||
const c = Math.cos(rot || 0), s = Math.sin(rot || 0)
|
||||
const b = [Infinity, Infinity, -Infinity, -Infinity]
|
||||
for (const [lx, ly] of [[x0, y0], [x1, y0], [x1, y1], [x0, y1]]) {
|
||||
const px = x + lx * c - ly * s
|
||||
const py = y + lx * s + ly * c
|
||||
if (px < b[0]) b[0] = px
|
||||
if (py < b[1]) b[1] = py
|
||||
if (px > b[2]) b[2] = px
|
||||
if (py > b[3]) b[3] = py
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
/** MTEXT 的 9 种对齐点 */
|
||||
const MT_H = { 1: 0, 2: 0.5, 3: 1, 4: 0, 5: 0.5, 6: 1, 7: 0, 8: 0.5, 9: 1 }
|
||||
const MT_V = { 1: 1, 2: 1, 3: 1, 4: 0.5, 5: 0.5, 6: 0.5, 7: 0, 8: 0, 9: 0 }
|
||||
|
||||
function emitMText(ctx, base, ent, m) {
|
||||
const { lines } = parseMText(ent.text)
|
||||
const plain = lines.map((l) => l.map((r) => r.text).join('')).join('\n')
|
||||
if (!plain.trim()) return
|
||||
const style = ctx.doc.styles.get(ent.styleName) || null
|
||||
const sc = matrixScale(m)
|
||||
const h = (ent.textHeight || 2.5) * sc
|
||||
const p = ent.insertionPoint
|
||||
if (!p || !(h > 0)) return
|
||||
const cf = conformal(m)
|
||||
let rot = 0
|
||||
if (ent.direction && (ent.direction.x || ent.direction.y)) rot = Math.atan2(ent.direction.y, ent.direction.x)
|
||||
else rot = ang(ent.rotation)
|
||||
rot += cf ? cf.rotation : 0
|
||||
|
||||
const ap = ent.attachmentPoint || 1
|
||||
const lineGap = (ent.lineSpacing || 1) * 1.667 // AutoCAD 默认行距系数
|
||||
const ax = MT_H[ap] ?? 0
|
||||
const ay = MT_V[ap] ?? 1
|
||||
const width = (ent.rectWidth || 0) * sc
|
||||
const X = applyX(m, p.x, p.y), Y = applyY(m, p.x, p.y)
|
||||
|
||||
// 包围盒:有 rectWidth 就按框宽算(正文会在这个宽度内折行),
|
||||
// 否则按最长一行估。行数同样按折行后估,否则长段落的盒子会短一截。
|
||||
const longest = Math.max(...lines.map((l) => charWidth(l.map((r) => r.text).join(''))), 1)
|
||||
const boxW = width > 0 ? width : longest * h
|
||||
const nLines = width > 0
|
||||
? lines.reduce((n, l) => n + Math.max(1, Math.ceil((charWidth(l.map((r) => r.text).join('')) * h) / width)), 0)
|
||||
: lines.length
|
||||
const boxH = Math.max(1, nLines) * h * lineGap
|
||||
|
||||
push(ctx, {
|
||||
...base, kind: 'mtext', x: X, y: Y, h, rot, lines, lineGap, ax, ay, width,
|
||||
font: style && style.font, plain,
|
||||
}, textBox(X, Y, boxW, boxH, ax, ay, rot))
|
||||
}
|
||||
|
||||
function emitLeader(ctx, base, ent, m) {
|
||||
const vs = ent.vertices || ent.points || []
|
||||
if (vs.length < 2) return
|
||||
const pts = new Float64Array(vs.length * 2)
|
||||
vs.forEach((v, i) => { pts[i * 2] = applyX(m, v.x, v.y); pts[i * 2 + 1] = applyY(m, v.x, v.y) })
|
||||
addPoly(ctx, base, pts, false)
|
||||
}
|
||||
|
||||
function emitMLeader(ctx, base, ent, m) {
|
||||
const ctxData = ent.contextData || ent.context || {}
|
||||
const leaders = ctxData.leaders || ent.leaders || []
|
||||
for (const ld of leaders) {
|
||||
for (const line of ld.leaderLines || ld.lines || []) {
|
||||
const vs = line.vertices || line.points || []
|
||||
if (vs.length < 2) continue
|
||||
const pts = new Float64Array(vs.length * 2)
|
||||
vs.forEach((v, i) => { pts[i * 2] = applyX(m, v.x, v.y); pts[i * 2 + 1] = applyY(m, v.x, v.y) })
|
||||
addPoly(ctx, base, pts, false)
|
||||
}
|
||||
}
|
||||
const t = ctxData.text || ctxData.textLabel
|
||||
if (t && ctxData.textLocation) {
|
||||
emitMText(ctx, base, {
|
||||
text: t, insertionPoint: ctxData.textLocation, textHeight: ctxData.textHeight || ent.textHeight,
|
||||
rotation: 0, attachmentPoint: ctxData.textAttachmentPoint || 1, styleName: ctxData.textStyleName,
|
||||
}, m)
|
||||
}
|
||||
}
|
||||
|
||||
/** DIMENSION:真正的图形放在一个匿名块里,直接展开那个块 */
|
||||
function emitBlockRef(ctx, base, ent, blockName, m, inheritColor, layerName, depth, fallbackPt) {
|
||||
const blk = blockName && ctx.doc.blocks.get(blockName)
|
||||
if (!blk) return
|
||||
if (depth > MAX_INSERT_DEPTH) return
|
||||
const bp = blk.basePoint || { x: 0, y: 0 }
|
||||
const bm = mul(m, [1, 0, 0, 1, -bp.x, -bp.y])
|
||||
for (const e of blk.entities || []) {
|
||||
emitEntity(ctx, e, bm, base.color, layerName, depth + 1)
|
||||
}
|
||||
}
|
||||
|
||||
function emitInsert(ctx, base, ent, m, inheritColor, layerName, depth) {
|
||||
if (depth > MAX_INSERT_DEPTH) return
|
||||
const blk = ctx.doc.blocks.get(ent.name)
|
||||
// ATTRIB 无论块在不在都要画
|
||||
for (const a of ent.attribs || []) {
|
||||
if (a && a.isVisible !== false) emitEntity(ctx, a, m, base.color, layerName, depth + 1)
|
||||
}
|
||||
if (!blk) { ctx.stats.skipped++; return }
|
||||
const p = ent.insertionPoint || { x: 0, y: 0 }
|
||||
const sx = ent.xScale === 0 || ent.xScale == null ? 1 : ent.xScale
|
||||
const sy = ent.yScale === 0 || ent.yScale == null ? 1 : ent.yScale
|
||||
const rot = ang(ent.rotation)
|
||||
const bp = blk.basePoint || { x: 0, y: 0 }
|
||||
|
||||
const cols = Math.max(1, ent.columnCount || 1)
|
||||
const rows = Math.max(1, ent.rowCount || 1)
|
||||
const cSp = ent.columnSpacing || 0
|
||||
const rSp = ent.rowSpacing || 0
|
||||
if (cols * rows > MAX_ARRAY_CELLS) { ctx.stats.skipped++; return }
|
||||
|
||||
for (let r = 0; r < rows; r++) {
|
||||
for (let c = 0; c < cols; c++) {
|
||||
const local = makeInsertMatrix(p.x + c * cSp, p.y + r * rSp, sx, sy, rot)
|
||||
// 先把块基点移到原点,再套插入变换
|
||||
const bm = mul(mul(m, local), [1, 0, 0, 1, -bp.x, -bp.y])
|
||||
for (const e of blk.entities || []) {
|
||||
emitEntity(ctx, e, bm, base.color, layerName, depth + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 输出
|
||||
|
||||
function push(ctx, shape, bb) {
|
||||
if (!bb || !isFinite(bb[0]) || !isFinite(bb[2])) return
|
||||
shape.bbox = bb
|
||||
shape.i = ctx.shapes.length
|
||||
ctx.shapes.push(shape)
|
||||
growBox(ctx.bbox, bb)
|
||||
}
|
||||
Reference in New Issue
Block a user