From 64bda5e97b076d10bd9d71324f2066a253d765f9 Mon Sep 17 00:00:00 2001 From: HHY Date: Mon, 5 Oct 2026 08:22:19 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=97=A8=E7=AA=97=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E8=AF=AD=E4=B9=89=E5=8C=BA=E5=88=86(=E9=97=A8=3D=E8=B4=AF?= =?UTF-8?q?=E7=A9=BF=E9=95=BF=E6=8A=8A=E6=89=8B+=E9=94=81=E7=82=B9+?= =?UTF-8?q?=E5=9E=8B=E6=9D=90=E5=90=8C=E8=89=B2=E8=A3=99=E6=9D=BF,=20?= =?UTF-8?q?=E7=AA=97=3D=E7=9F=AD=E6=8A=8A=E6=89=8B),=20=E5=AF=B9=E7=85=A7?= =?UTF-8?q?=E5=AE=98=E6=96=B9=E5=8F=82=E8=80=83=E5=9B=BE=E4=B8=89=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E5=99=A8=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG | 1 + frontend/src/components/RenderCanvas.vue | 27 ++++++++++++++++++----- frontend/src/components/WindoorCanvas.vue | 15 ++++++++----- frontend/src/components/WindoorThreed.vue | 3 ++- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 572556a..fd2757d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # CHANGELOG +- [2026-10-05 07:52] FIX: v2.41 门窗渲染语义区分(对齐官方图纸)。对照官方参考图(windoor-images/31337/31343/31637)逐项核对门的视觉语义, 三渲染器同步: ①门把手贯穿长条(约55%扇高, 官方门统一长条贯穿中缝侧) vs 窗短把手(20%) ②门上下锁点小方块(RenderCanvas 此前缺失, 补齐; WindoorCanvas 已有) ③裙板色=型材同色系(RenderCanvas 取 shape.cm.g 暗化 62%, 解析失败退中性深灰; WindoorCanvas 图纸深灰+竖纹肌理+浅色编号; 3D 0x606369)——此前裙板米色与型材脱节; 门窗开向符号(V形虚线顶点指铰链侧)经官方图核对为同一语义无需区分; 视觉验收: 31343/31337 门(长把手+锁点+型材色裙板)与 704 窗(短把手)对比官方图一致; 回归: 24 页巡检 0 异常+冒烟 60/60 (Files: frontend/src/components/RenderCanvas.vue, frontend/src/components/WindoorCanvas.vue, frontend/src/components/WindoorThreed.vue) - [2026-10-05 07:30] TEST: v2.40 全量 254 套模板视觉校验——v2.39 消费路径全量回归 0 缺陷。新增两个常驻校验资产: ①frontend/scripts/verify-sash-panels.mjs(结构级: 渲染器同路径全量跑 重放/扇推断/锚点匹配/inferSashPanels, A~E 五级异常分类+子格覆盖度断言, 实测 254 文件/409 形状/570 扇/726 子格(裙板 150/格条 58) 0 异常) ②frontend/scripts/patrol-render-all.mjs(渲染级: 无头浏览器逐套切换样板真实渲染, 收集 JS 异常+画布非透明像素活性, 实测 254/254 OK 0 空白 0 异常); 6 套代表性样板视觉抽验通过(704 平开窗/31637 推拉门格条裙板/31343 双扇门裙板/31353 三扇推拉16条扇内分割/31593 平开窗/31603 四扇推拉)——v2.39 为渲染器/工具层共性修复, 全量样板自动受益, 无需逐套修改 (Files: frontend/scripts/verify-sash-panels.mjs, frontend/scripts/patrol-render-all.mjs) - [2026-10-05 07:14] FEAT: v2.39 渲染视觉与图纸对齐——源站原生扇内结构全量消费(扇内分割/裙板/格条/编号)。数据层诊断: 704(mm.sp=2 三区域+A1 平开扇)/31637(Frame 无分割+Slide 双推拉扇, 扇自带 mm.sp 裙板分割+mm.fls 玻璃/Panel 挂接+mm.dbs.bars decorationChinese 格条, hc=2 主格+gp=80 回纹)数据本身完整, 自研渲染器只消费顶层 mm.sp 未下钻扇内。新增 types/drawing.ts inferSashPanels(扇矩形构造临时 shape 复用 replayShape 全语义重放扇内分割, fls 按 polyId{idx,pos} 树寻址挂玻璃/裙板(type=Panel 判定), dbs.bars 格条解析 tp/gp/hc); 三渲染器消费: WindoorCanvas(2D 图纸: 裙板实色+格条内框/主格/四角回纹+玻璃编号 A1~A4)+RenderCanvas(效果图: 同语义)+WindoorThreed(3D: 裙板实体板+格条条体; 修复单 region 多扇时第二扇被 used 丢弃(改 glyph 自身矩形+共享区域保留静态底玻璃)); 扇形状按 sp 锚点归属匹配(修 sashes 数组序与视觉序交叉); WindoorCanvas 挂载加 ResizeObserver+零尺寸保护(修 el-dialog 内首帧 0 尺寸空白, 与 v2.38 3D 同型); 视觉验收: 704 渲染(开向三角/把手/编号)与 31637 四视图(双推拉+中式格条+裙板)均与官方参考图对齐; 回归: 254 套数据校验 0 失败+24 页巡检 0 异常+冒烟 60/60 (Files: frontend/src/types/drawing.ts, frontend/src/components/WindoorCanvas.vue, frontend/src/components/RenderCanvas.vue, frontend/src/components/WindoorThreed.vue) - [2026-10-05 06:23] FIX: v2.38 效果图页切 Tab 后 3D 展示空白修复。根因: WindoorThreed 在 /rendering 页以 v-show=false(display:none) 挂载, 容器 clientWidth=0 时 Three.js 以 0 尺寸初始化(camera.aspect=0/0=NaN), 页内切 Tab 显示后仅 window resize 事件无法触发重设尺寸→画布永远空白(直接访问 /threed 路由正常, 故 24 页巡检未覆盖); 修复: onResize 加零尺寸保护(避免 NaN 投影矩阵)+ResizeObserver 监听容器(显示瞬间尺寸 0→正常自动恢复)+初始 setSize 兜底 ||1; 无头复验 /rendering 切 Tab 场景渲染正常; 巡检 24 页 0 异常 (Files: frontend/src/components/WindoorThreed.vue) diff --git a/frontend/src/components/RenderCanvas.vue b/frontend/src/components/RenderCanvas.vue index 3e64362..74aadcd 100644 --- a/frontend/src/components/RenderCanvas.vue +++ b/frontend/src/components/RenderCanvas.vue @@ -84,6 +84,12 @@ function render() { // 扇层:扇内子格(裙板/格条/编号) + 扇料框 + 开向符号 + 把手(语义同 WindoorCanvas,视觉对齐 2D 图纸) const rp = replayShape(shape) const glyphs = inferSashGlyphs(shape, rp).sashes + // 裙板色 = 型材色(cm.g rgba 字符串 → 不透明深色), 解析失败退中性深灰 + const cmRaw = typeof shape.cm?.g === 'string' ? shape.cm.g : '' + const cmMatch = cmRaw.match(/(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/) + const frameTint = cmMatch + ? `rgb(${Math.round(+cmMatch[1] * 0.62)},${Math.round(+cmMatch[2] * 0.62)},${Math.round(+cmMatch[3] * 0.62)})` + : '#6b6e73' const sashShapes: SashShape[] = [] const collect = (s: FrameShape | SashShape) => { if (!['Frame', 'Slide'].includes(String(s.type))) sashShapes.push(s as SashShape) @@ -105,10 +111,10 @@ function render() { const px0 = tx(p.x0), px1 = tx(p.x1), py0 = ty(p.y0), py1 = ty(p.y1) if (px1 - px0 < 4 || py1 - py0 < 4) continue if (!p.glass) { - // 裙板/实板:暖米色 + 内凹描边 - ctx.fillStyle = '#ddd3c0' + // 裙板/实板:型材同色系(取 cm.g, 官方图纸裙板与框扇同色) + 内凹描边 + ctx.fillStyle = frameTint ctx.fillRect(px0, py0, px1 - px0, py1 - py0) - ctx.strokeStyle = 'rgba(90,84,72,0.55)' + ctx.strokeStyle = 'rgba(0,0,0,0.35)' ctx.lineWidth = 1.5 ctx.strokeRect(px0 + 3, py0 + 3, px1 - px0 - 6, py1 - py0 - 6) } else if (p.deco) { @@ -183,9 +189,9 @@ function render() { ctx.stroke() ctx.setLineDash([]) } - // 把手: 铰链对面侧白条 + // 把手: 铰链对面侧白条(门=贯穿长条, 窗=短条) + 门上下锁点块 const dir = g.openDirection ?? 'left' - const hl = Math.max(10, h * (g.isDoor ? 0.34 : 0.2)) + const hl = Math.max(10, h * (g.isDoor ? 0.55 : 0.2)) const hw2 = Math.max(4, 5) ctx.fillStyle = '#f4f4f6' ctx.strokeStyle = '#3c4147' @@ -200,6 +206,17 @@ function render() { } ctx.fill() ctx.stroke() + // 门锁点(把手上下两端小方块) + if (g.isDoor && g.kind === 'casement' && !dir.startsWith('up')) { + const dx = dir.startsWith('left') ? bx1 - hw2 / 2 - 3 : bx0 + hw2 / 2 + 3 + const dot = Math.max(3, hw2 * 0.8) + for (const oy of [cy - hl / 3, cy + hl / 3]) { + ctx.beginPath() + ctx.rect(dx - dot / 2, oy - dot / 2, dot, dot) + ctx.fill() + ctx.stroke() + } + } }) // 外框 const all = dd.sm.flatMap(s => replayShape(s).regions.flat().map(seg => seg.ps)) diff --git a/frontend/src/components/WindoorCanvas.vue b/frontend/src/components/WindoorCanvas.vue index db7bc08..22dee7c 100644 --- a/frontend/src/components/WindoorCanvas.vue +++ b/frontend/src/components/WindoorCanvas.vue @@ -596,10 +596,15 @@ function drawFrame( const px0 = tx(p.x0), px1 = tx(p.x1), py0 = ty(p.y0), py1 = ty(p.y1) if (px1 - px0 < 4 || py1 - py0 < 4) continue if (!p.glass) { - ctx.fillStyle = '#e5dfd3' + // 裙板: 型材深色(官方图纸裙板与框扇同色系) + 竖纹肌理 + ctx.fillStyle = '#5f6268' ctx.fillRect(px0, py0, px1 - px0, py1 - py0) - ctx.strokeStyle = STYLE.profileDark + ctx.strokeStyle = 'rgba(255,255,255,0.14)' ctx.lineWidth = 1 + for (let vx = px0 + 7; vx < px1 - 3; vx += 7) { + ctx.beginPath(); ctx.moveTo(vx, py0 + 3); ctx.lineTo(vx, py1 - 3); ctx.stroke() + } + ctx.strokeStyle = STYLE.profileDark ctx.strokeRect(px0 + 2, py0 + 2, px1 - px0 - 4, py1 - py0 - 4) } else if (p.deco) { // hc 等分主格 + gp 内框 + 四角回纹块(近似源站 decorationChinese) @@ -627,7 +632,7 @@ function drawFrame( } if (p.glassText) { const fs2 = Math.max(9, Math.min(14, (px1 - px0) * 0.1)) - ctx.fillStyle = p.glass ? 'rgba(255,255,255,0.92)' : STYLE.label + ctx.fillStyle = 'rgba(255,255,255,0.92)' ctx.font = `bold ${fs2}px sans-serif` ctx.textAlign = 'center' ctx.textBaseline = 'middle' @@ -668,9 +673,9 @@ function drawFrame( ctx.stroke() ctx.setLineDash([]) } - // 把手: 铰链对面侧(白条+描边+上下锁点) + // 把手: 铰链对面侧(白条+描边; 门=贯穿长条, 窗=短条)+上下锁点 const dir = g.openDirection ?? 'left' - const hl = Math.max(8, h * (g.isDoor ? 0.36 : 0.2)) + const hl = Math.max(8, h * (g.isDoor ? 0.55 : 0.2)) const hw2 = Math.max(3.5, lw * 0.55) ctx.fillStyle = '#f4f4f6' ctx.strokeStyle = STYLE.profileDark diff --git a/frontend/src/components/WindoorThreed.vue b/frontend/src/components/WindoorThreed.vue index a468780..bd18bd0 100644 --- a/frontend/src/components/WindoorThreed.vue +++ b/frontend/src/components/WindoorThreed.vue @@ -361,8 +361,9 @@ function build(drawing: DrawingData) { const lmy = (gRect.y1 / 2 + gRect.y0 / 2 - (p.y0 + p.y1) / 2) * S if (![pw, ph, lmx, lmy].every(Number.isFinite) || pw <= 0.01 || ph <= 0.01) continue if (!p.glass) { + // 裙板: 型材深色系(与框扇同色域, 对齐官方门板观感) const board = new THREE.Mesh(new THREE.BoxGeometry(pw, ph, 0.012), - new THREE.MeshStandardMaterial({ color: 0xd9cdb8, roughness: 0.7, metalness: 0.05 })) + new THREE.MeshStandardMaterial({ color: 0x606369, roughness: 0.65, metalness: 0.1 })) board.position.set(lmx, lmy, 0.004) group.add(board) } else if (p.deco) {