feat: v1.6 组合画布编辑能力增强
- 选中单元可视化: 蓝色虚线框+四角手柄+实时尺寸标注 - 精确尺寸编辑: 工具栏 W/H 输入框(几何按比例缩放, 锚定左下角) - 方向键微调: 1mm/Shift+方向键 10mm - 右侧单元清单点击选中与画布联动, selSize 双向同步
This commit is contained in:
@@ -218,6 +218,27 @@ function render() {
|
||||
for (const h of guides.h) { ctx.beginPath(); ctx.moveTo(0, ty(h)); ctx.lineTo(W, ty(h)); ctx.stroke() }
|
||||
ctx.setLineDash([])
|
||||
|
||||
// 选中单元: 蓝色虚线框 + 角点手柄 + 尺寸标注
|
||||
if (selectedUid.value !== null) {
|
||||
const bb = unitBBox(selectedUid.value)
|
||||
if (bb) {
|
||||
const x0 = tx(bb.x0), x1 = tx(bb.x1), y0 = ty(bb.y1), y1 = ty(bb.y0)
|
||||
ctx.strokeStyle = '#409eff'
|
||||
ctx.setLineDash([6, 4])
|
||||
ctx.lineWidth = 1.5
|
||||
ctx.strokeRect(x0 - 4, y0 - 4, (x1 - x0) + 8, (y1 - y0) + 8)
|
||||
ctx.setLineDash([])
|
||||
ctx.fillStyle = '#409eff'
|
||||
for (const [hx, hy] of [[x0, y0], [x1, y0], [x0, y1], [x1, y1]]) {
|
||||
ctx.fillRect(hx - 4, hy - 4, 8, 8)
|
||||
}
|
||||
ctx.font = 'bold 12px sans-serif'
|
||||
ctx.textAlign = 'center'
|
||||
ctx.fillText(`${Math.round(bb.x1 - bb.x0)} × ${Math.round(bb.y1 - bb.y0)} mm`, (x0 + x1) / 2, y0 - 10)
|
||||
ctx.textAlign = 'left'
|
||||
}
|
||||
}
|
||||
|
||||
hint.value = `${uids().length} 个单元 · 总尺寸 ${Math.round(gw)} × ${Math.round(gh)} mm`
|
||||
}
|
||||
|
||||
@@ -308,6 +329,37 @@ function modelAt(clientX: number, clientY: number): Pt {
|
||||
}
|
||||
|
||||
function refresh() { render() }
|
||||
|
||||
/** 选中单元缩放到目标尺寸(几何按比例缩放,锚定自身左下角) */
|
||||
function resizeSelected(newW: number, newH: number) {
|
||||
if (selectedUid.value === null) return
|
||||
const target = props.shapes.filter(s => s.__uid === selectedUid.value)
|
||||
const bb = bbOfLocal(target)
|
||||
if (!bb) return
|
||||
const w = bb.x1 - bb.x0, h = bb.y1 - bb.y0
|
||||
if (w < 10 || h < 10) return
|
||||
const sx = Math.max(0.05, newW / w), sy = Math.max(0.05, newH / h)
|
||||
const scX = (p: any) => { p.x = bb.x0 + (p.x - bb.x0) * sx }
|
||||
const scY = (p: any) => { p.y = bb.y0 + (p.y - bb.y0) * sy }
|
||||
for (const s of target) {
|
||||
for (const r of (s.polygon?.polygon ?? [])) {
|
||||
for (const seg of r) { scX(seg.ps); scY(seg.ps); scX(seg.pe); scY(seg.pe) }
|
||||
}
|
||||
for (const sp of (s.mm?.sp ?? [])) { scX(sp.line.pt); scY(sp.line.pt) }
|
||||
for (const sub of (s.sm ?? [])) if (sub.bd) { scX(sub.bd.from); scY(sub.bd.from); scX(sub.bd.to); scY(sub.bd.to) }
|
||||
}
|
||||
refresh()
|
||||
emit('change')
|
||||
}
|
||||
|
||||
/** 方向键微调(mm) */
|
||||
function nudgeSelected(dx: number, dy: number) {
|
||||
if (selectedUid.value === null) return
|
||||
translateUid(selectedUid.value, dx, dy)
|
||||
refresh()
|
||||
emit('change')
|
||||
}
|
||||
|
||||
function exportDrawing(): string | null {
|
||||
if (!props.shapes.length) return null
|
||||
return JSON.stringify({
|
||||
@@ -315,7 +367,7 @@ function exportDrawing(): string | null {
|
||||
epi: [], sbf: [], wlc: '', wall: [], scale: 0, note: [],
|
||||
})
|
||||
}
|
||||
defineExpose({ refresh, modelAt, exportDrawing, rotateUnit, mirrorUnit })
|
||||
defineExpose({ refresh, modelAt, exportDrawing, rotateUnit, mirrorUnit, resizeSelected, nudgeSelected })
|
||||
|
||||
onMounted(() => {
|
||||
watch(() => props.shapes, () => { selectedUid.value = null; rebuild() }, { immediate: true })
|
||||
|
||||
Reference in New Issue
Block a user