feat: v1.4 组合画布旋转编辑+方向核验+双向门符号
- 组合画布旋转编辑: 选中单元 ⟲/⟳ 90°旋转(绕几何中心) 分割线法向与开向角度同步旋转, 水平镜像(开向镜像) - 数据方向核验: 以31729/31740真实图纸比对确认2D渲染Y向正确 - 2D渲染补双向开启门虚线交叉符号(wtf) - Node数学验证: 旋转后宽高互换/法向转轴/可逆性全部通过
This commit is contained in:
@@ -66,6 +66,70 @@ function uids(): number[] {
|
||||
return [...new Set(props.shapes.map(s => s.__uid))]
|
||||
}
|
||||
|
||||
function bbOfLocal(shapes: any[]): BBox | null {
|
||||
const pts = shapes.flatMap(shapePts)
|
||||
return pts.length ? bboxOf(pts) : null
|
||||
}
|
||||
|
||||
// ---------- 旋转/镜像编辑 ----------
|
||||
/** 旋转整个单元(绕其几何中心),同步旋转分割线法向与开向角度 */
|
||||
function rotateUnit(uid: number, deg: 90 | -90 | 180) {
|
||||
const target = props.shapes.filter(s => s.__uid === uid)
|
||||
const bb = bbOfLocal(target)
|
||||
if (!bb) return
|
||||
const cx2 = (bb.x0 + bb.x1) / 2, cy2 = (bb.y0 + bb.y1) / 2
|
||||
const rad = (deg * Math.PI) / 180
|
||||
const cos = Math.cos(rad), sin = Math.sin(rad)
|
||||
const rotPt = (p: any) => {
|
||||
const dx = p.x - cx2, dy = p.y - cy2
|
||||
p.x = cx2 + dx * cos - dy * sin
|
||||
p.y = cy2 + dx * sin + dy * cos
|
||||
}
|
||||
const rotVec = (v: any) => {
|
||||
const nx = v.x ?? 1, ny = v.y ?? 0
|
||||
v.x = nx * cos - ny * sin
|
||||
v.y = nx * sin + ny * cos
|
||||
}
|
||||
for (const s of target) {
|
||||
for (const r of (s.polygon?.polygon ?? [])) for (const seg of r) { rotPt(seg.ps); rotPt(seg.pe) }
|
||||
for (const sp of (s.mm?.sp ?? [])) { rotPt(sp.line.pt); if (sp.line.norm) rotVec(sp.line.norm) }
|
||||
for (const sub of (s.sm ?? [])) {
|
||||
if (sub.bd) { rotPt(sub.bd.from); rotPt(sub.bd.to) }
|
||||
if (typeof sub.od === 'number') sub.od = (sub.od + deg + 360) % 360
|
||||
if (Array.isArray(sub.sashes)) for (const e of sub.sashes) {
|
||||
if (typeof e?.od === 'number') e.od = (e.od + deg + 360) % 360
|
||||
}
|
||||
}
|
||||
}
|
||||
refresh()
|
||||
emit('change')
|
||||
}
|
||||
|
||||
/** 水平镜像整个单元(开向同步镜像) */
|
||||
function mirrorUnit(uid: number) {
|
||||
const target = props.shapes.filter(s => s.__uid === uid)
|
||||
const bb = bbOfLocal(target)
|
||||
if (!bb) return
|
||||
const cx2 = (bb.x0 + bb.x1) / 2
|
||||
const mirPt = (p: any) => { p.x = 2 * cx2 - p.x }
|
||||
for (const s of target) {
|
||||
for (const r of (s.polygon?.polygon ?? [])) for (const seg of r) { mirPt(seg.ps); mirPt(seg.pe) }
|
||||
for (const sp of (s.mm?.sp ?? [])) {
|
||||
mirPt(sp.line.pt)
|
||||
if (sp.line.norm) sp.line.norm.x = -(sp.line.norm.x ?? 1)
|
||||
}
|
||||
for (const sub of (s.sm ?? [])) {
|
||||
if (sub.bd) { mirPt(sub.bd.from); mirPt(sub.bd.to) }
|
||||
if (typeof sub.od === 'number') sub.od = (180 - sub.od + 360) % 360
|
||||
if (Array.isArray(sub.sashes)) for (const e of sub.sashes) {
|
||||
if (typeof e?.od === 'number') e.od = (180 - e.od + 360) % 360
|
||||
}
|
||||
}
|
||||
}
|
||||
refresh()
|
||||
emit('change')
|
||||
}
|
||||
|
||||
// 吸附: 单元边缘贴近其他单元边缘时对齐(返回修正后的位置与辅助线)
|
||||
function snap(bb: BBox, excludeUid: number): { dx: number; dy: number; guides: { v: number[]; h: number[] } } {
|
||||
const TOL = 12
|
||||
@@ -251,7 +315,7 @@ function exportDrawing(): string | null {
|
||||
epi: [], sbf: [], wlc: '', wall: [], scale: 0, note: [],
|
||||
})
|
||||
}
|
||||
defineExpose({ refresh, modelAt, exportDrawing })
|
||||
defineExpose({ refresh, modelAt, exportDrawing, rotateUnit, mirrorUnit })
|
||||
|
||||
onMounted(() => {
|
||||
watch(() => props.shapes, () => { selectedUid.value = null; rebuild() }, { immediate: true })
|
||||
|
||||
Reference in New Issue
Block a user