fix: v2.19 组合水平镜像的 3D 开向同步+放大层菜单可见性
- mirrorUnit 补铰链侧换向: hm.openDirection/sashes.openDirection left↔right 镜像(含变体) - 数学确认 mirPt+norm.x 翻转组合下重放树拓扑保持, 扇 polyId 无需换位 - rotateUnit 补 hm.openDirection 刚体旋转换向(left→up→right→down) - ctx-menu z-index 40→2100, 放大预览时右键菜单不再被盖 - 验证: 镜像前后开合 50% 对比截图(铰链侧正确换向), 全量校验 0 失败
This commit is contained in:
@@ -93,6 +93,21 @@ function bbOfLocal(shapes: any[]): BBox | null {
|
||||
}
|
||||
|
||||
// ---------- 旋转/镜像编辑 ----------
|
||||
/** 铰链侧随刚体旋转换向: 顺时针 left→up→right→down→left */
|
||||
function rotDir(d: unknown, deg: number) {
|
||||
const s = String(d ?? '')
|
||||
if (!s) return undefined
|
||||
const order = ['left', 'up', 'right', 'down']
|
||||
const mapOne = (one: string) => {
|
||||
const i = order.indexOf(one)
|
||||
if (i < 0) return one
|
||||
const step = ((deg / 90) % 4 + 4) % 4 // 顺时针步数
|
||||
return order[(i + step) % 4]
|
||||
}
|
||||
// 形如 left_with_down: 两段分别映射
|
||||
return s.split('_with_').map(mapOne).join('_with_')
|
||||
}
|
||||
|
||||
/** 旋转整个单元(绕其几何中心),同步旋转分割线法向与开向角度 */
|
||||
function rotateUnit(uid: number, deg: 90 | -90 | 180) {
|
||||
const target = props.shapes.filter(s => s.__uid === uid)
|
||||
@@ -117,8 +132,11 @@ function rotateUnit(uid: number, deg: 90 | -90 | 180) {
|
||||
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
|
||||
const hm = (sub as any).hm
|
||||
if (hm?.openDirection) hm.openDirection = rotDir(hm.openDirection, deg)
|
||||
if (Array.isArray(sub.sashes)) for (const e of sub.sashes) {
|
||||
if (typeof e?.od === 'number') e.od = (e.od + deg + 360) % 360
|
||||
if ((e as any)?.hm?.openDirection) (e as any).hm.openDirection = rotDir((e as any).hm.openDirection, deg)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,13 +144,20 @@ function rotateUnit(uid: number, deg: 90 | -90 | 180) {
|
||||
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 }
|
||||
// 铰链侧换向: left↔right(含 *_with_* 变体), up 不变
|
||||
const flipDir = (d: unknown) => {
|
||||
const s = String(d ?? '')
|
||||
if (s.startsWith('left')) return 'right' + s.slice(4)
|
||||
if (s.startsWith('right')) return 'left' + s.slice(5)
|
||||
return s || undefined
|
||||
}
|
||||
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 ?? [])) {
|
||||
@@ -142,8 +167,12 @@ function mirrorUnit(uid: number) {
|
||||
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
|
||||
const hm = (sub as any).hm
|
||||
if (hm?.openDirection) hm.openDirection = flipDir(hm.openDirection)
|
||||
if (Array.isArray(sub.sashes)) for (const e of sub.sashes) {
|
||||
if (typeof e?.od === 'number') e.od = (180 - e.od + 360) % 360
|
||||
if ((e as any)?.hm?.openDirection) (e as any).hm.openDirection = flipDir((e as any).hm.openDirection)
|
||||
if (e?.openDirection) e.openDirection = flipDir(e.openDirection)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -552,7 +581,7 @@ canvas { width: 100%; height: 100%; display: block; cursor: grab; }
|
||||
canvas:active { cursor: grabbing; }
|
||||
.hud { position: absolute; left: 10px; top: 10px; background: rgba(255,255,255,0.9);
|
||||
border-radius: 6px; padding: 3px 10px; font-size: 12px; color: #606266; }
|
||||
.ctx-menu { position: absolute; z-index: 40; background: #fff; border-radius: 8px;
|
||||
.ctx-menu { position: absolute; z-index: 2100; background: #fff; border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.16); padding: 4px 0; min-width: 150px; }
|
||||
.ctx-title { font-size: 11px; color: #909399; padding: 4px 12px; border-bottom: 1px solid #f0f0f0; }
|
||||
.ctx-item { display: flex; align-items: center; gap: 8px; padding: 7px 12px; font-size: 13px;
|
||||
|
||||
Reference in New Issue
Block a user