fix: v2.3 组合排列 3D 重叠修复
- CombineCanvas 新增 autoArrange: 单元按 X 序紧贴排布+底边对齐 - 拖动松手重叠检测: 与其他单元相交时自动重新排列 (消除 3D 玻璃面穿插与 z-fighting) - 工具栏新增自动排列按钮 - 移除 CombineCanvas 孤儿 @drop(归父容器统一处理)
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 6.8 KiB |
@@ -1,5 +1,6 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
- [2026-09-21 12:35] FIX: v2.3 组合排列 3D 重叠修复——①CombineCanvas 新增 autoArrange: 全部单元按 X 序紧贴排布+底边对齐; ②拖动松手重叠检测: 移动单元与其他单元相交时自动重新排列(消除 3D 玻璃面穿插/z-fighting); ③CombineView 工具栏新增自动排列按钮; ④组合画布移除孤儿 @drop(归父容器) (Files: frontend/src/components/CombineCanvas.vue, frontend/src/views/CombineView.vue)
|
||||||
- [2026-09-21 12:40] FIX: v2.2 前端完整性治理——引入 vue-tsc 全量类型检查并清零全部错误: ①组合画布 3D 扇面板解构键名错误(rect/s→rect/sash)致扇面板全挂(3D 混乱根因之一); ②WindoorCanvas isDoor 标注 cx 作用域泄漏; ③WindoorThreed rebuild 函数缺失(墙体开关点击报错)+wallBoxes 元组类型; ④量尺页 openCreate 未定义(点击报错); ⑤状态筛选 ref 类型纳入空串(el-select clearable 语义); ⑥CombineCanvas 移除孤儿 @drop(归父容器)+容器补 dragover.prevent(修复拖放入库); ⑦AIRenderCanvas style 属性名保留处理+表单联合类型; ⑧零散隐式 any 参数; 新增检查脚本 scripts/check-frontend.cjs(未定义引用+API 路径一致性比对) (Files: frontend/src/**, frontend/scripts/check-frontend.cjs)
|
- [2026-09-21 12:40] FIX: v2.2 前端完整性治理——引入 vue-tsc 全量类型检查并清零全部错误: ①组合画布 3D 扇面板解构键名错误(rect/s→rect/sash)致扇面板全挂(3D 混乱根因之一); ②WindoorCanvas isDoor 标注 cx 作用域泄漏; ③WindoorThreed rebuild 函数缺失(墙体开关点击报错)+wallBoxes 元组类型; ④量尺页 openCreate 未定义(点击报错); ⑤状态筛选 ref 类型纳入空串(el-select clearable 语义); ⑥CombineCanvas 移除孤儿 @drop(归父容器)+容器补 dragover.prevent(修复拖放入库); ⑦AIRenderCanvas style 属性名保留处理+表单联合类型; ⑧零散隐式 any 参数; 新增检查脚本 scripts/check-frontend.cjs(未定义引用+API 路径一致性比对) (Files: frontend/src/**, frontend/scripts/check-frontend.cjs)
|
||||||
- [2026-09-21 12:25] FIX: v2.2 修复 3D 截图为纯色空白——墙体尺寸单位错误(bw=1/S*0.72 误用模型单位≈2304, 而放置坐标是世界单位±0.5), 相机被包在数千单位宽的巨墙内只见背景色; 改为世界单位(侧墙 0.55, 上下墙覆盖窗洞±2.2), 墙体不投影(避免自身遮挡), Node 验证四墙尺寸全部 ≤2.2 世界单位 (Files: frontend/src/components/WindoorThreed.vue)
|
- [2026-09-21 12:25] FIX: v2.2 修复 3D 截图为纯色空白——墙体尺寸单位错误(bw=1/S*0.72 误用模型单位≈2304, 而放置坐标是世界单位±0.5), 相机被包在数千单位宽的巨墙内只见背景色; 改为世界单位(侧墙 0.55, 上下墙覆盖窗洞±2.2), 墙体不投影(避免自身遮挡), Node 验证四墙尺寸全部 ≤2.2 世界单位 (Files: frontend/src/components/WindoorThreed.vue)
|
||||||
- [2026-09-21 12:10] FIX: 3D"解析失败"根因修复+错误可见性——①根因: watch immediate 首跑时 camera 未创建而 build() 触碰 camera 抛 TypeError; 移除 build 内相机定位(每帧 loop 已定位); ②shadowMap.type 赋值整行移除(three r186 已移除 PCFSoft 常量, 默认即 PCFShadowMap, 杜绝弃用警告); ③解析失败时显示真实错误信息(info+console.error), 便于后续定位; 重建 dist 后 PCFSoft 赋值触发路径已清零 (Files: frontend/src/components/WindoorThreed.vue)
|
- [2026-09-21 12:10] FIX: 3D"解析失败"根因修复+错误可见性——①根因: watch immediate 首跑时 camera 未创建而 build() 触碰 camera 抛 TypeError; 移除 build 内相机定位(每帧 loop 已定位); ②shadowMap.type 赋值整行移除(three r186 已移除 PCFSoft 常量, 默认即 PCFShadowMap, 杜绝弃用警告); ③解析失败时显示真实错误信息(info+console.error), 便于后续定位; 重建 dist 后 PCFSoft 赋值触发路径已清零 (Files: frontend/src/components/WindoorThreed.vue)
|
||||||
|
|||||||
@@ -305,9 +305,15 @@ function onMove(e: MouseEvent) {
|
|||||||
|
|
||||||
function onUp() {
|
function onUp() {
|
||||||
if (dragUnit) {
|
if (dragUnit) {
|
||||||
if (dragUnit.moved) emit('change')
|
const uid = dragUnit.uid
|
||||||
|
const moved = dragUnit.moved
|
||||||
guides = { v: [], h: [] }
|
guides = { v: [], h: [] }
|
||||||
dragUnit = null
|
dragUnit = null
|
||||||
|
if (moved && uid !== null && unitOverlaps(uid)) {
|
||||||
|
// 拖动后与其他单元重叠 → 自动重新排列消除重叠
|
||||||
|
autoArrange()
|
||||||
|
return
|
||||||
|
}
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -362,6 +368,40 @@ function nudgeSelected(dx: number, dy: number) {
|
|||||||
emit('change')
|
emit('change')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function rectsIntersect(a: BBox, b: BBox): boolean {
|
||||||
|
return a.x0 < b.x1 - 1 && b.x0 < a.x1 - 1 && a.y0 < b.y1 - 1 && b.y0 < a.y1 - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 单元是否与其它单元重叠 */
|
||||||
|
function unitOverlaps(uid: number): boolean {
|
||||||
|
const bb = unitBBox(uid)
|
||||||
|
if (!bb) return false
|
||||||
|
return uids().filter(u => u !== uid).some(u => {
|
||||||
|
const o = unitBBox(u)
|
||||||
|
return o ? rectsIntersect(bb, o) : false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 自动排列: 全部单元按 X 序紧贴排布、底边对齐(消除重叠) */
|
||||||
|
function autoArrange() {
|
||||||
|
const ids = uids().filter(u => unitBBox(u)).sort((a, b) =>
|
||||||
|
(unitBBox(a)!.x0) - (unitBBox(b)!.x0))
|
||||||
|
if (!ids.length) return
|
||||||
|
let bottom = Infinity
|
||||||
|
for (const id of ids) {
|
||||||
|
const bb = unitBBox(id)!
|
||||||
|
bottom = Math.min(bottom, bb.y0)
|
||||||
|
}
|
||||||
|
let x = ids.length ? unitBBox(ids[0])!.x0 : 0
|
||||||
|
for (const id of ids) {
|
||||||
|
const bb = unitBBox(id)!
|
||||||
|
translateUid(id, x - bb.x0, bottom - bb.y0)
|
||||||
|
x += bb.x1 - bb.x0
|
||||||
|
}
|
||||||
|
refresh()
|
||||||
|
emit('change')
|
||||||
|
}
|
||||||
|
|
||||||
function exportDrawing(): string | null {
|
function exportDrawing(): string | null {
|
||||||
if (!props.shapes.length) return null
|
if (!props.shapes.length) return null
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
@@ -369,7 +409,7 @@ function exportDrawing(): string | null {
|
|||||||
epi: [], sbf: [], wlc: '', wall: [], scale: 0, note: [],
|
epi: [], sbf: [], wlc: '', wall: [], scale: 0, note: [],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
defineExpose({ refresh, modelAt, exportDrawing, rotateUnit, mirrorUnit, resizeSelected, nudgeSelected })
|
defineExpose({ refresh, modelAt, exportDrawing, rotateUnit, mirrorUnit, resizeSelected, nudgeSelected, autoArrange })
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
watch(() => props.shapes, () => { selectedUid.value = null; rebuild() }, { immediate: true })
|
watch(() => props.shapes, () => { selectedUid.value = null; rebuild() }, { immediate: true })
|
||||||
|
|||||||
@@ -41,6 +41,9 @@
|
|||||||
<el-input-number v-model="selSize.h" :min="300" :step="10" size="small" controls-position="right"
|
<el-input-number v-model="selSize.h" :min="300" :step="10" size="small" controls-position="right"
|
||||||
style="width:96px" @change="onSelResize" />
|
style="width:96px" @change="onSelResize" />
|
||||||
</template>
|
</template>
|
||||||
|
<el-tooltip content="全部单元按顺序紧贴排布、底边对齐" placement="top">
|
||||||
|
<el-button size="small" :disabled="!shapes.length" @click="canvasRef?.autoArrange(); refreshMeta()">自动排列</el-button>
|
||||||
|
</el-tooltip>
|
||||||
<el-divider direction="vertical" />
|
<el-divider direction="vertical" />
|
||||||
<el-button size="small" :disabled="!selectedUid" @click="alignBottom">底边对齐</el-button>
|
<el-button size="small" :disabled="!selectedUid" @click="alignBottom">底边对齐</el-button>
|
||||||
<el-button size="small" :disabled="!selectedUid" type="danger" plain @click="deleteSelected">删除单元</el-button>
|
<el-button size="small" :disabled="!selectedUid" type="danger" plain @click="deleteSelected">删除单元</el-button>
|
||||||
|
|||||||
Reference in New Issue
Block a user