fix: v2.2 修复 3D 截图纯色空白(墙体尺寸单位错误)

- 根因: 墙体宽度 bw=1/S*0.72 误用模型单位(≈最大边长*0.72, 数千单位)
  而放置坐标是世界单位(窗体≈1), 相机被包在巨墙内只见背景色
- 改为世界单位: 上下墙覆盖窗洞±2.2x0.55, 左右墙 0.55x窗高
- 墙体不投影(避免自遮挡), 地面尺寸同步修正
- Node 验证四墙尺寸全部 ≤2.2 世界单位
This commit is contained in:
HHY
2026-09-21 04:08:56 -07:00
parent cbb888c7ff
commit 5eb93f043e
3 changed files with 16 additions and 11 deletions
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

+1
View File
@@ -1,5 +1,6 @@
# CHANGELOG # CHANGELOG
- [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)
- [2026-09-21 11:30] FIX: v2.1 3D 渲染问题根治——①profileGeo 外轮廓路径自相交(闭合斜边穿过槽口区域)导致三角剖分破面, 重写为正确顶点顺序的 U 槽简单多边形; ②inferSashGlyphs 兜底 bboxOf 误传段对象(应为点对象)产生 NaN 扇矩形(JSON 序列化为 null), 修正为有限值点集; ③型材截面挤出抽为共享模块 three/profileGeo.ts(geometryIsValid 校验器); ④新增 scripts/validate3d.cjs 全量验证工具: 254 样板×3700 网格×798 扇逐网格 NaN 检测全部通过; ⑤过小面板(窄条扇)为合法数据跳过渲染不视为失败 (Files: frontend/src/components/WindoorThreed.vue, frontend/src/three/profileGeo.ts, frontend/src/types/drawing.ts, frontend/scripts/validate3d.cjs) - [2026-09-21 11:30] FIX: v2.1 3D 渲染问题根治——①profileGeo 外轮廓路径自相交(闭合斜边穿过槽口区域)导致三角剖分破面, 重写为正确顶点顺序的 U 槽简单多边形; ②inferSashGlyphs 兜底 bboxOf 误传段对象(应为点对象)产生 NaN 扇矩形(JSON 序列化为 null), 修正为有限值点集; ③型材截面挤出抽为共享模块 three/profileGeo.ts(geometryIsValid 校验器); ④新增 scripts/validate3d.cjs 全量验证工具: 254 样板×3700 网格×798 扇逐网格 NaN 检测全部通过; ⑤过小面板(窄条扇)为合法数据跳过渲染不视为失败 (Files: frontend/src/components/WindoorThreed.vue, frontend/src/three/profileGeo.ts, frontend/src/types/drawing.ts, frontend/scripts/validate3d.cjs)
- [2026-09-21 11:55] FEAT: v2.0 DXF 截面学习+多腔体型材落地。①下载源站 49 个真实型材截面 DXF(8 产品系×外框/玻璃扇/中梃/纱扇/压线/转角立柱等); ②自研 DXF 解析器提取截面结构: 归纳出各型材深度/面宽/腔体数区间(外框46-62深3-6腔, 玻璃扇80-90深4-10腔, 中梃79-84深3-7腔等); ③3D 型材升级为多腔体截面挤出(外框3腔/扇2腔, 玻璃槽深20%, 壁厚≈深度14%); ④学习结论沉淀至方案文档§3.3; ⑤按约定删除全部 DXF 素材与解析脚本 (Files: frontend/src/components/WindoorThreed.vue, doc/克隆实施方案.md) - [2026-09-21 11:55] FEAT: v2.0 DXF 截面学习+多腔体型材落地。①下载源站 49 个真实型材截面 DXF(8 产品系×外框/玻璃扇/中梃/纱扇/压线/转角立柱等); ②自研 DXF 解析器提取截面结构: 归纳出各型材深度/面宽/腔体数区间(外框46-62深3-6腔, 玻璃扇80-90深4-10腔, 中梃79-84深3-7腔等); ③3D 型材升级为多腔体截面挤出(外框3腔/扇2腔, 玻璃槽深20%, 壁厚≈深度14%); ④学习结论沉淀至方案文档§3.3; ⑤按约定删除全部 DXF 素材与解析脚本 (Files: frontend/src/components/WindoorThreed.vue, doc/克隆实施方案.md)
+15 -11
View File
@@ -308,30 +308,34 @@ function build(drawing: DrawingData) {
} }
} }
// ---- 墙体洞口 + 地面 ---- // ---- 墙体洞口 + 地面(世界单位:窗体约 1 单位见方)----
if (showWall.value) { if (showWall.value) {
const bw = 1 / S * 0.72, bh = 1 / S * 0.72 const hw = gw * S / 2 + 0.05 // 窗洞半宽
const wx0 = -gw * S / 2 - 0.06, wx1 = gw * S / 2 + 0.06 const hh = gh * S / 2 + 0.05 // 窗洞半高
const wy0 = -gh * S / 2 - 0.04, wy1 = gh * S / 2 + 0.06 const side = 0.55 // 上下墙高度/左右墙宽度
const wt = DEPTH * 3 const wt = DEPTH * 3
const wallBoxes: [number, number, number, number, number][] = [ const wallBoxes: [number, number, number, number, number][] = [
[wx0 - bw, wy1, bw * 2, bh], // 上 [-hw - side, hh, side * 2 + hw * 2, side], // 上
[wx0 - bw, wy0 - bh, bw * 2, bh], // 下 [-hw - side, -hh - side, side * 2 + hw * 2, side], // 下
[wx0 - bw, wy0, bw, wy1 - wy0], // 左 [-hw - side, -hh, side, hh * 2], // 左
[wx1, wy0, bw, wy1 - wy0], // 右 [hw, -hh, side, hh * 2], // 右
] ]
for (const [px, py, pw, ph] of wallBoxes) { for (const [px, py, pw, ph] of wallBoxes) {
const m = new THREE.Mesh(new THREE.BoxGeometry(pw, ph, wt), wallMat) const m = new THREE.Mesh(new THREE.BoxGeometry(pw, ph, wt), wallMat)
m.position.set(px + pw / 2, py + ph / 2, 0) m.position.set(px + pw / 2, py + ph / 2, 0)
m.receiveShadow = true m.receiveShadow = true
m.userData.isWall = true
sceneRoot.add(m) sceneRoot.add(m)
} }
const ground = new THREE.Mesh(new THREE.BoxGeometry(bw * 4, 0.02, DEPTH * 8), groundMat) const ground = new THREE.Mesh(new THREE.BoxGeometry(3, 0.02, DEPTH * 8), groundMat)
ground.position.set(0, wy0 - 0.02, DEPTH * 2) ground.position.set(0, -hh - side - 0.02, DEPTH * 2)
ground.receiveShadow = true ground.receiveShadow = true
sceneRoot.add(ground) sceneRoot.add(ground)
} }
sceneRoot.traverse(o => { if ((o as THREE.Mesh).isMesh) { o.castShadow = true; o.receiveShadow = true } }) sceneRoot.traverse(o => {
const m = o as THREE.Mesh
if (m.isMesh) { m.castShadow = !m.userData.isWall; m.receiveShadow = true }
})
// 玻璃不投影 // 玻璃不投影
sceneRoot.traverse(o => { sceneRoot.traverse(o => {
const mat = (o as THREE.Mesh).material as THREE.Material | undefined const mat = (o as THREE.Mesh).material as THREE.Material | undefined