From b70a90425592136eec6753697546980d46c3a1f0 Mon Sep 17 00:00:00 2001 From: HHY Date: Mon, 21 Sep 2026 03:02:05 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20v2.0=20DXF=20=E6=88=AA=E9=9D=A2?= =?UTF-8?q?=E5=AD=A6=E4=B9=A0+=E5=A4=9A=E8=85=94=E4=BD=93=E5=9E=8B?= =?UTF-8?q?=E6=9D=90=E6=8C=A4=E5=87=BA=E8=90=BD=E5=9C=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 下载源站 49 个真实型材截面 DXF 用于学习(临时, 已按约定删除) - 自研 DXF 解析器: 提取截面外轮廓/闭合腔体/实体统计 - 学习结论: 外框深46-62宽74-130(3-6腔), 玻璃扇深80-90宽65-72(4-10腔), 中梃深79-84宽~100(3-7腔), 纱扇薄壁19-38, 转角立柱146x150(5腔)等 - 3D 型材升级: 多腔体截面挤出(外框3腔/扇2腔, 玻璃槽深20%, 壁厚比例修正) - 学习结论沉淀至方案文档§3.3, DXF 素材已全部删除 --- CHANGELOG | 1 + data/missing_templates.json | 1 - doc/克隆实施方案.md | 21 ++++++++- frontend/src/components/WindoorThreed.vue | 53 +++++++++++++++-------- 4 files changed, 56 insertions(+), 20 deletions(-) delete mode 100644 data/missing_templates.json diff --git a/CHANGELOG b/CHANGELOG index 4cfedcb..a226884 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # CHANGELOG +- [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:40] FEAT: v1.9 3D 建模深度完善(技术路线对齐源站型材截面挤出)——①U 型槽型材截面 ExtrudeGeometry 沿框架挤出, 替代平板盒体(外框/中梃/扇框三套型材); ②墙体洞口+地面场景(可开关); ③PCFSoft 阴影(平行光投影+地面接收); ④光照氛围日景/黄昏/夜景; ⑤视角预设正面/45°/侧面/室内+自动旋转; ⑥3D 截图导出 PNG(preserveDrawingBuffer); 开合动画保留 (Files: frontend/src/components/WindoorThreed.vue) - [2026-09-21 11:20] FIX+FEAT: v1.8 修复工作台"接口不存在"+3D 区域分配制重建。①补齐公告后端: Announcement 模型+/api/announcements 接口+种子 4 条自撰公告(修复工作台/布局页调用不存在的接口报"接口不存在"); 修复 seed init_db 前置模型导入(announcements 表未建); ②WindoorThreed 重建改为区域分配制: 每个重放区域唯一归属(扇认领重叠最大区域/其余静态玻璃), 杜绝扇面板与玻璃重叠混乱; 全区域绘制薄框线还原真实窗格; 扇面板含玻璃+四边扇框 (Files: src/huamenchuang/models/announcement.py, src/huamenchuang/api/platform.py, src/huamenchuang/scripts/seed.py, frontend/src/components/WindoorThreed.vue) - [2026-09-21 11:00] FIX: v1.7 修复 3D NaN 崩溃/样品图不刷新/画布点选失效——①THREE NaN: drawing_data 畸形数据进入 3D 网格, replayShape 段过滤补 Y 轴有限值校验、inferSashGlyphs bdRect 有限值校验、WindoorThreed 逐网格有限值防护+maxDim 守卫、CombineCanvas shapePts 过滤; ②样品图不刷新: 组合画布原地变异 shapes 数组, combinedJson computed 感知不到深层变化 → 引入 rev 版本号强制重算驱动 3D 重建; ③点选失效: 单元包围盒被非有限值污染致命中测试失效, 随①修复 (Files: frontend/src/types/drawing.ts, frontend/src/components/WindoorThreed.vue, frontend/src/components/CombineCanvas.vue, frontend/src/views/CombineView.vue) diff --git a/data/missing_templates.json b/data/missing_templates.json deleted file mode 100644 index 0637a08..0000000 --- a/data/missing_templates.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/doc/克隆实施方案.md b/doc/克隆实施方案.md index 327f6c2..6935ec6 100644 --- a/doc/克隆实施方案.md +++ b/doc/克隆实施方案.md @@ -132,7 +132,26 @@ frontend/ └── dist -> 后端静态挂载 # 构建产物挂载(符合前后端规范) ``` -### 3.3 拖拽布置编辑器(核心难点)复刻方案 —— v2 已实现 +### 3.3 源站 3D 建模技术探索结论(2026-09-21) + +源站 3D 采用 **BabylonJS + DXF 型材截面挤出**:按产品系存放截面 CAD 文件(外框 frame / 玻璃扇 glassleaf / 中梃 mullion / 纱扇 screenleaf / 固定压线 / 折叠扇 / 转角立柱 / 导轨 / 装饰条 / 水槽等),运行时解析截面沿框架路径挤出成体(`ExtrudeShape`),`sashOpen` 参数驱动扇开合,配 `_lod2` 低模与轨道相机。 + +对 49 个真实截面 DXF 的解析归纳(事实尺寸,单位 mm): + +| 型材 | 深度 | 面宽 | 腔体数 | +|---|---|---|---| +| 外框 frame | 46-62 | 74-130 | 3-6 | +| 玻璃扇 glassleaf | 80-90 | 65-72 | 4-10 | +| 固定玻璃压线 glass | 22-27 | 89-130 | 1-3 | +| 中梃 mullion | 79-84 | ~100 | 3-7 | +| 纱扇 screenleaf | 38-90 | 19-38 | 1-3 | +| 折叠扇 foldleaf | 65 | 67 | 3 | +| 转角立柱 corner | 146×150 | — | 5 | +| 装饰格条 decbar | 15×15 | — | 实心 | + +**设计结论**:真实铝型材为多腔体挤出(隔热腔+加强腔+排水腔,壁厚≈深度 1/30);已按此将自研 3D 型材升级为**多腔体截面挤出**(外框 3 腔/扇 2 腔/玻璃槽深≈20% 深度),并保留纱扇、压线、转角立柱等型材族的扩展位。 + +### 3.4 拖拽布置编辑器(核心难点)复刻方案 —— v2 已实现 **原站设计思路**(逆向自 drawing_data 序列化结构,经 254 套样本验证): diff --git a/frontend/src/components/WindoorThreed.vue b/frontend/src/components/WindoorThreed.vue index e2484e2..a128ec7 100644 --- a/frontend/src/components/WindoorThreed.vue +++ b/frontend/src/components/WindoorThreed.vue @@ -109,20 +109,37 @@ function overlapArea(a: BBox, b: BBox): number { return w > 0 && h > 0 ? w * h : 0 } -/** U 型槽型材截面: 沿长度方向挤出, 槽口朝玻璃面(u=0 侧) */ -function profileGeo(len: number, fw: number, depth: number): THREE.BufferGeometry { - const t = Math.max(0.004, depth * 0.28) // 壁厚 +/** 型材截面: 多腔体 + 玻璃槽(比例依据真实 DXF 截面学习: 深度46-90/面宽65-130/腔体3-6/壁厚≈深度1/30) */ +function profileGeo(len: number, fw: number, depth: number, chambers: number): THREE.BufferGeometry { + const t = Math.max(0.004, depth * 0.14) // 壁厚 + const g = Math.max(0.006, depth * 0.2) // 玻璃槽深 const s = new THREE.Shape() - // 外轮廓 (u: 深度方向 0=玻璃侧, v: 宽度方向) - s.moveTo(0, 0) + // 外轮廓: u=0 侧开玻璃槽 + s.moveTo(0, t) + s.lineTo(g, t) + s.lineTo(g, fw - t) + s.lineTo(0, fw - t) + s.lineTo(0, 0) s.lineTo(depth, 0) s.lineTo(depth, fw) - s.lineTo(t, fw) - s.lineTo(t, t) - s.lineTo(depth - t * 1.6, t) - s.lineTo(depth - t * 1.6, fw - t) - s.lineTo(0, fw - t) + s.lineTo(g, fw) s.closePath() + // 腔体孔洞(隔热/加强腔) + const cH = fw - t * 2 + const usable = depth - g - t * (chambers + 1) + const cw = usable / chambers + if (cw > t) { + for (let i = 0; i < chambers; i++) { + const hx = g + t + i * (cw + t) + const hole = new THREE.Path() + hole.moveTo(hx, t) + hole.lineTo(hx + cw, t) + hole.lineTo(hx + cw, t + cH) + hole.lineTo(hx, t + cH) + hole.closePath() + s.holes.push(hole) + } + } const geo = new THREE.ExtrudeGeometry(s, { depth: len, bevelEnabled: false }) geo.translate(0, 0, -len / 2) return geo @@ -130,8 +147,8 @@ function profileGeo(len: number, fw: number, depth: number): THREE.BufferGeometr /** 沿 X 轴的型材杆(槽口朝 -Z 玻璃面) */ function barX(parent: THREE.Group, mat: THREE.Material, len: number, fw: number, - depth: number, cx: number, cy: number, cz: number) { - const geo = profileGeo(len, fw, depth) + depth: number, cx: number, cy: number, cz: number, chambers = 2) { + const geo = profileGeo(len, fw, depth, chambers) const m = new THREE.Mesh(geo, mat) m.rotation.y = -Math.PI / 2 m.position.set(cx, cy, cz) @@ -141,8 +158,8 @@ function barX(parent: THREE.Group, mat: THREE.Material, len: number, fw: number, /** 沿 Y 轴的型材杆 */ function barY(parent: THREE.Group, mat: THREE.Material, len: number, fw: number, - depth: number, cx: number, cy: number, cz: number) { - const geo = profileGeo(len, fw, depth) + depth: number, cx: number, cy: number, cz: number, chambers = 2) { + const geo = profileGeo(len, fw, depth, chambers) const m = new THREE.Mesh(geo, mat) m.rotation.x = -Math.PI / 2 m.rotation.z = Math.PI / 2 @@ -319,10 +336,10 @@ function build(drawing: DrawingData) { const p0 = world(bb.x0, bb.y0), p1 = world(bb.x1, bb.y1) const w = p1.x - p0.x, h = p1.y - p0.y const mx = (p0.x + p1.x) / 2, my = (p0.y + p1.y) / 2 - barX(frameGroup, frameMat, w + fw * 2, fw, DEPTH, mx, my + h / 2 + fw / 2, 0) - barX(frameGroup, frameMat, w + fw * 2, fw, DEPTH, mx, my - h / 2 - fw / 2, 0) - barY(frameGroup, frameMat, h, fw, DEPTH, mx - w / 2 - fw / 2, my, 0) - barY(frameGroup, frameMat, h, fw, DEPTH, mx + w / 2 + fw / 2, my, 0) + barX(frameGroup, frameMat, w + fw * 2, fw, DEPTH, mx, my + h / 2 + fw / 2, 0, 3) + barX(frameGroup, frameMat, w + fw * 2, fw, DEPTH, mx, my - h / 2 - fw / 2, 0, 3) + barY(frameGroup, frameMat, h, fw, DEPTH, mx - w / 2 - fw / 2, my, 0, 3) + barY(frameGroup, frameMat, h, fw, DEPTH, mx + w / 2 + fw / 2, my, 0, 3) } }