feat: v2.15 图纸封面图一致性收尾——官方 PNG 剩余元素补齐
- 分段尺寸标注: 按重放区域分割点生成蓝色分段+总尺寸双排标注(端部刻度/左侧旋转文字) - 玻璃配置第二行: fl.spet.sp(如 5+12+5)画于编号下方 - 中梃与外框交点白色连接件方块 - 验证: 704 画布与官方封面图逐元素一致, ui-shots 6 页无报错
This commit is contained in:
@@ -338,6 +338,7 @@ function drawFrame(
|
||||
const regions = rp.regions.map(regionPoints).filter(pts => pts.length >= 3)
|
||||
// 型材视觉宽度: 60mm 料宽映射像素, 限幅
|
||||
const lw = Math.max(3, Math.min(10, 60 * scale))
|
||||
const frameBb = regions.length ? bboxOf(regions.flat()) : null
|
||||
|
||||
// 1) 玻璃格: teal 渐变(官方图纸配色)
|
||||
for (const pts of regions) {
|
||||
@@ -356,11 +357,33 @@ function drawFrame(
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
// 2) 中梃(按目标区域裁剪, 型材三段线; 选中高亮)
|
||||
rp.clips.forEach(({ sp, bb }, spIdx) => {
|
||||
// 2) 中梃(按目标区域裁剪, 型材三段线; 选中高亮) + 与外框交点白块(官方连接件符号)
|
||||
for (let spIdx = 0; spIdx < rp.clips.length; spIdx++) {
|
||||
const { sp, bb } = rp.clips[spIdx]
|
||||
const isSel = selected.value?.shapeIdx === shapeIdx && selected.value.spIdx === spIdx
|
||||
drawBar(ctx, spAxis(sp), spPos(sp), bb, tx, ty, scale, isSel)
|
||||
})
|
||||
}
|
||||
if (frameBb) {
|
||||
const fx0 = Math.round(frameBb.x0), fy0 = Math.round(frameBb.y0)
|
||||
const fx1 = Math.round(frameBb.x1), fy1 = Math.round(frameBb.y1)
|
||||
const dot = Math.max(3, lw * 0.5)
|
||||
ctx.fillStyle = '#f2f2f4'
|
||||
ctx.strokeStyle = STYLE.profileDark
|
||||
ctx.lineWidth = 1
|
||||
for (const { sp, bb } of rp.clips) {
|
||||
const axis = spAxis(sp), pos = spPos(sp)
|
||||
const ends = axis === 'v' ? [[pos, bb.y0], [pos, bb.y1]] : [[bb.x0, pos], [bb.x1, pos]]
|
||||
for (const [ex, ey] of ends) {
|
||||
const onFrame = (Math.round(ex) === fx0 || Math.round(ex) === fx1) && ey >= fy0 - 2 && ey <= fy1 + 2
|
||||
const onFrameY = (Math.round(ey) === fy0 || Math.round(ey) === fy1) && ex >= fx0 - 2 && ex <= fx1 + 2
|
||||
if (!(onFrame || onFrameY)) continue
|
||||
ctx.beginPath()
|
||||
ctx.rect(tx(ex) - dot / 2, ty(ey) - dot / 2, dot, dot)
|
||||
ctx.fill()
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3) 外框型材 + 四角角码
|
||||
if (regions.length) {
|
||||
@@ -446,27 +469,35 @@ function drawFrame(
|
||||
}
|
||||
})
|
||||
|
||||
// 5) 玻璃编号: 固定格(frame.mm.fls) + 扇格(sash.mm.fls), 经 polyId 树寻址定位
|
||||
// 5) 玻璃编号: 固定格(frame.mm.fls) + 扇格(sash.mm.fls), 经 polyId 树寻址定位; 次行=玻璃配置(spet.sp)
|
||||
ctx.fillStyle = STYLE.label
|
||||
ctx.textAlign = 'center'
|
||||
ctx.textBaseline = 'middle'
|
||||
const drawCellLabel = (text: string, bb: { x0: number; y0: number; x1: number; y1: number }) => {
|
||||
const drawCellLabel = (text: string, bb: { x0: number; y0: number; x1: number; y1: number }, subText?: string) => {
|
||||
const fs = Math.max(10, Math.min(16, (bb.x1 - bb.x0) * scale * 0.09))
|
||||
const cx = tx((bb.x0 + bb.x1) / 2), cy = ty((bb.y0 + bb.y1) / 2)
|
||||
ctx.font = `bold ${fs}px sans-serif`
|
||||
ctx.fillText(text, tx((bb.x0 + bb.x1) / 2), ty((bb.y0 + bb.y1) / 2))
|
||||
ctx.fillText(text, cx, subText ? cy - fs * 0.6 : cy)
|
||||
if (subText) {
|
||||
ctx.font = `${Math.max(9, fs * 0.78)}px sans-serif`
|
||||
ctx.fillText(subText, cx, cy + fs * 0.7)
|
||||
}
|
||||
}
|
||||
for (const sub of (shape.sm ?? []) as SashShape[]) {
|
||||
if (sub.enabled === false) continue
|
||||
const text = ((sub.mm as any)?.fls as any)?.fls?.[0]?.ser?.text
|
||||
if (!text || sub.polyId?.idx == null) continue
|
||||
const segs = rp.resolveRef?.(sub.polyId.idx, sub.polyId.pos)
|
||||
if (segs) drawCellLabel(text, bboxOf(regionPoints(segs)))
|
||||
if (segs) {
|
||||
const subText = ((sub.mm as any)?.fls as any)?.fls?.[0]?.spet?.sp || undefined
|
||||
drawCellLabel(text, bboxOf(regionPoints(segs)), subText)
|
||||
}
|
||||
}
|
||||
for (const fl of ((shape.mm as any)?.fls as any)?.fls ?? []) {
|
||||
const text = fl?.ser?.text
|
||||
if (!text || fl.pid?.idx == null) continue
|
||||
const segs = rp.resolveRef?.(fl.pid.idx, fl.pid.pos)
|
||||
if (segs) drawCellLabel(text, bboxOf(regionPoints(segs)))
|
||||
if (segs) drawCellLabel(text, bboxOf(regionPoints(segs)), fl?.spet?.sp || undefined)
|
||||
}
|
||||
ctx.textBaseline = 'alphabetic'
|
||||
|
||||
@@ -489,31 +520,71 @@ function drawFrame(
|
||||
}
|
||||
}
|
||||
|
||||
// 7) 尺寸标注(官方蓝色刻度线)
|
||||
if (regions.length) {
|
||||
const bb = bboxOf(regions.flat())
|
||||
const x0 = tx(bb.x0), x1 = tx(bb.x1), y0 = ty(bb.y0), y1 = ty(bb.y1)
|
||||
const wmm = Math.round(bb.x1 - bb.x0), hmm = Math.round(bb.y1 - bb.y0)
|
||||
ctx.fillStyle = STYLE.dim
|
||||
// 7) 尺寸标注(官方蓝色): 分段(每格宽高) + 总尺寸
|
||||
if (frameBb) {
|
||||
const x0 = tx(frameBb.x0), y0 = ty(frameBb.y0), x1 = tx(frameBb.x1), y1 = ty(frameBb.y1)
|
||||
const xs = new Set<number>(), ys = new Set<number>()
|
||||
for (const pts of regions) {
|
||||
const b = bboxOf(pts)
|
||||
xs.add(Math.round(b.x0)); xs.add(Math.round(b.x1))
|
||||
ys.add(Math.round(b.y0)); ys.add(Math.round(b.y1))
|
||||
}
|
||||
const xsA = [...xs].sort((a, b) => a - b)
|
||||
const ysA = [...ys].sort((a, b) => a - b)
|
||||
ctx.strokeStyle = STYLE.dim
|
||||
ctx.font = '11px sans-serif'
|
||||
ctx.textAlign = 'center'
|
||||
ctx.textBaseline = 'alphabetic'
|
||||
ctx.fillText(`${wmm} mm`, (x0 + x1) / 2, y1 + 16)
|
||||
ctx.save()
|
||||
ctx.translate(x0 - 14, (y0 + y1) / 2)
|
||||
ctx.rotate(-Math.PI / 2)
|
||||
ctx.fillText(`${hmm} mm`, 0, 0)
|
||||
ctx.restore()
|
||||
ctx.fillStyle = STYLE.dim
|
||||
ctx.lineWidth = 1
|
||||
ctx.font = '10px sans-serif'
|
||||
ctx.textAlign = 'center'
|
||||
ctx.textBaseline = 'middle'
|
||||
const tick = (x: number, y: number, vertical: boolean) => {
|
||||
ctx.beginPath()
|
||||
if (vertical) { ctx.moveTo(x - 3, y); ctx.lineTo(x + 3, y) }
|
||||
else { ctx.moveTo(x, y - 3); ctx.lineTo(x, y + 3) }
|
||||
ctx.stroke()
|
||||
}
|
||||
// 底部: 分段宽 + 总宽
|
||||
const segY = y1 + 14
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x0, y1 + 6); ctx.lineTo(x1, y1 + 6)
|
||||
ctx.moveTo(x0, y1 + 3); ctx.lineTo(x0, y1 + 9)
|
||||
ctx.moveTo(x1, y1 + 3); ctx.lineTo(x1, y1 + 9)
|
||||
ctx.moveTo(x0 - 6, y0); ctx.lineTo(x0 - 6, y1)
|
||||
ctx.moveTo(x0 - 9, y0); ctx.lineTo(x0 - 3, y0)
|
||||
ctx.moveTo(x0 - 9, y1); ctx.lineTo(x0 - 3, y1)
|
||||
for (let i = 0; i < xsA.length - 1; i++) {
|
||||
const a = tx(xsA[i]), b = tx(xsA[i + 1])
|
||||
if (b - a < 16) continue
|
||||
ctx.moveTo(a, segY); ctx.lineTo(b, segY)
|
||||
tick(a, segY, false); tick(b, segY, false)
|
||||
ctx.fillText(String(xsA[i + 1] - xsA[i]), (a + b) / 2, segY - 8)
|
||||
}
|
||||
ctx.stroke()
|
||||
const dimY = segY + (xsA.length > 2 ? 14 : 0)
|
||||
ctx.beginPath(); ctx.moveTo(x0, dimY); ctx.lineTo(x1, dimY)
|
||||
tick(x0, dimY, false); tick(x1, dimY, false)
|
||||
ctx.stroke()
|
||||
ctx.fillText(`${Math.round(frameBb.x1 - frameBb.x0)}`, (x0 + x1) / 2, dimY - 8)
|
||||
// 左侧: 分段高 + 总高(文字旋转)
|
||||
const segX = x0 - 14
|
||||
ctx.beginPath()
|
||||
for (let i = 0; i < ysA.length - 1; i++) {
|
||||
const a = ty(ysA[i]), b = ty(ysA[i + 1])
|
||||
if (b - a < 16) continue
|
||||
ctx.moveTo(segX, a); ctx.lineTo(segX, b)
|
||||
tick(segX, a, true); tick(segX, b, true)
|
||||
ctx.save()
|
||||
ctx.translate(segX - 8, (a + b) / 2)
|
||||
ctx.rotate(-Math.PI / 2)
|
||||
ctx.fillText(String(ysA[i + 1] - ysA[i]), 0, 0)
|
||||
ctx.restore()
|
||||
}
|
||||
ctx.stroke()
|
||||
const dimX = segX - (ysA.length > 2 ? 14 : 0)
|
||||
ctx.beginPath(); ctx.moveTo(dimX, y0); ctx.lineTo(dimX, y1)
|
||||
tick(dimX, y0, true); tick(dimX, y1, true)
|
||||
ctx.stroke()
|
||||
ctx.save()
|
||||
ctx.translate(dimX - 8, (y0 + y1) / 2)
|
||||
ctx.rotate(-Math.PI / 2)
|
||||
ctx.fillText(`${Math.round(frameBb.y1 - frameBb.y0)}`, 0, 0)
|
||||
ctx.restore()
|
||||
ctx.textAlign = 'left'
|
||||
ctx.textBaseline = 'alphabetic'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user