From 4bd48d8bc0df84fcea119f250df95cc131bf453e Mon Sep 17 00:00:00 2001 From: WuYongJie Date: Fri, 16 Oct 2026 14:46:23 +0800 Subject: [PATCH] fix(diy): keep wood grain continuous and compact inspector (cherry picked from commit 9af20a14b07f6da46691a7356c5fd1d08aa48887) --- .../src/app/diy/components/DiyCabinetMesh.tsx | 4 +-- .../app/diy/components/DiyColorOptions.tsx | 19 +++++++---- .../src/app/diy/components/DiyInspector.tsx | 34 +++++++++++-------- furniture-3d-system/src/lib/diy/geometry.js | 28 ++++++--------- 4 files changed, 44 insertions(+), 41 deletions(-) diff --git a/furniture-3d-system/src/app/diy/components/DiyCabinetMesh.tsx b/furniture-3d-system/src/app/diy/components/DiyCabinetMesh.tsx index 536ee19e..4b244313 100644 --- a/furniture-3d-system/src/app/diy/components/DiyCabinetMesh.tsx +++ b/furniture-3d-system/src/app/diy/components/DiyCabinetMesh.tsx @@ -40,11 +40,11 @@ export default function DiyCabinetMesh({ catalog, item, doors, selected, mode, d const rotateGrain=main?.name==='榆木黑色'||main?.name==='柚木色' const loadedTexture=useTexture(main?.texture||'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7') const loadedShelfTexture=useTexture(shelf?.texture||'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7') - const shelfTexture=useMemo(()=>{const value=loadedShelfTexture.clone();value.colorSpace=THREE.SRGBColorSpace;value.wrapS=value.wrapT=THREE.RepeatWrapping;value.needsUpdate=true;return value},[loadedShelfTexture]) + const shelfTexture=useMemo(()=>{const value=loadedShelfTexture.clone();value.colorSpace=THREE.SRGBColorSpace;value.wrapS=value.wrapT=THREE.ClampToEdgeWrapping;value.needsUpdate=true;return value},[loadedShelfTexture]) useEffect(()=>()=>shelfTexture.dispose(),[shelfTexture]) const texture=useMemo(()=>{ const value=loadedTexture.clone();value.colorSpace=THREE.SRGBColorSpace - value.wrapS=value.wrapT=THREE.RepeatWrapping;value.anisotropy=8 + value.wrapS=value.wrapT=THREE.ClampToEdgeWrapping;value.anisotropy=8 if(rotateGrain){value.center.set(.5,.5);value.rotation=Math.PI/2} value.needsUpdate=true;return value },[loadedTexture,rotateGrain]) diff --git a/furniture-3d-system/src/app/diy/components/DiyColorOptions.tsx b/furniture-3d-system/src/app/diy/components/DiyColorOptions.tsx index 2b786c45..62e15778 100644 --- a/furniture-3d-system/src/app/diy/components/DiyColorOptions.tsx +++ b/furniture-3d-system/src/app/diy/components/DiyColorOptions.tsx @@ -1,3 +1,4 @@ +import { Plus } from 'lucide-react' import { resolveDiyMaterials } from '@/lib/diy/catalog' import { findDiyColorForMaterial } from '@/lib/diy/materials' import type { DiyWorkbenchController } from '../hooks/useDiyWorkbench' @@ -9,19 +10,25 @@ export default function DiyColorOptions({ controller: c, disabled, onAddColor }: }) { if (!c.selected) return null const selected = c.selected, style = c.catalogAccess.getCatalogStyle(selected.code) + const selectedMaterials = resolveDiyMaterials(c.catalog, { code: style.code, colorName: selected.colorName }) + const selectedName = `${selected.colorName}${selectedMaterials.main?.deletedAt || selectedMaterials.shelf?.deletedAt ? ' · 已删除' : ''}` const available = c.catalog.materials.filter(material => material.kind === 'main' && !material.deletedAt && !findDiyColorForMaterial(c.catalog, style.code, material.itemCode)) - return
+ return
+
{style.colors.filter(color=>selected.colorName===color.name||diyColorIsAvailable(c.catalog,style.code,color.name)).map(color => { const { main, shelf } = resolveDiyMaterials(c.catalog, { code: style.code, colorName: color.name }) return })} - {available.map(material => )} +
+

{selectedName}

} diff --git a/furniture-3d-system/src/app/diy/components/DiyInspector.tsx b/furniture-3d-system/src/app/diy/components/DiyInspector.tsx index 8d1e7e25..c4d2d9d0 100644 --- a/furniture-3d-system/src/app/diy/components/DiyInspector.tsx +++ b/furniture-3d-system/src/app/diy/components/DiyInspector.tsx @@ -1,12 +1,14 @@ 'use client' -import { RotateCw, Trash2, Layers } from 'lucide-react' +import { RotateCw, Trash2, Layers, ScanLine } from 'lucide-react' import Button from '@/components/ui/Button' import type { DiyWorkbenchController } from '../hooks/useDiyWorkbench' import { doorPatternFits, getDiyDoorDimensions } from '@/lib/diy/catalog' import type { CabinetCell } from '@/lib/diy/types' import DiyColorOptions from './DiyColorOptions' +const actionClassName = 'min-w-0 px-1 py-0 text-[11px] focus:px-1 focus:py-0 lg:h-8 lg:min-h-8 [&>span]:gap-1.5' + export default function DiyInspector({ controller: c, disabled, onEditCells, onAddPattern,onAddColor }: { controller: DiyWorkbenchController; disabled: boolean; onEditCells:(code:string)=>void; onAddPattern:(door:CabinetCell['door'])=>void onAddColor:(styleCode:string,materialCode:string)=>void @@ -20,35 +22,37 @@ export default function DiyInspector({ controller: c, disabled, onEditCells, onA return [`${door.width} × ${door.height} × ${door.thickness}`,door] as const })).values()] return
-
-

当前柜体

+
+
+

当前柜体

+ {c.selected&&

{getCatalogStyle(c.selected.code).name} · {c.selected.code}

} +
{c.selected ? <> -

{getCatalogStyle(c.selected.code).name} · {c.selected.code}

-
- {style?.modelUrl&&} - - - +
+ + + + {style?.modelUrl&&}
:

点击画面中的柜体,查看颜色与摆放操作。

}
-

柜门图案

-

{style&&!style.cells.length?'当前柜体尚未配置格口,请点击“编辑格口”自动识别。':'选择图案后,切换贴门并点击格口。'}

- {doorSizes.length>0&&
{doorSizes.map(door=>
+

柜门图案

+

{style&&!style.cells.length?'点击“编辑格口”自动识别后贴门。':'选择图案,再点击画面中的格口。'}

+ {doorSizes.length>0&&
{doorSizes.map(door=>
门片 {door.width} × {door.height} × {door.thickness} mm - +
)}
}
-
+
{visiblePatterns.map(pattern => )} {!visiblePatterns.length&&Boolean(style?.cells.length)&&

暂无与当前格口尺寸匹配的柜门。

} diff --git a/furniture-3d-system/src/lib/diy/geometry.js b/furniture-3d-system/src/lib/diy/geometry.js index cf7a3b6c..e9243afa 100644 --- a/furniture-3d-system/src/lib/diy/geometry.js +++ b/furniture-3d-system/src/lib/diy/geometry.js @@ -10,8 +10,7 @@ */ const MM_TO_UNIT = 0.01; // 1 单位 = 10mm,柜体约 65~130 单位 -// 大板面每 720mm 一个贴图周期,并使用板件在模型里的真实坐标取样。 -// 这样相邻板件不会每一块都从纹理左下角重新开始,能明显减轻重复拼接感。 +// 木纹默认按 720mm 取样;较长板件整体适配一张贴图,避免板内循环接缝。 const UV_SCALE = 1 / 720; /** 长方体 6 个面,每面 4 个顶点。返回值是相对中心的偏移与对应法向 */ @@ -81,28 +80,21 @@ function buildBox(center, size, modelCenter, opts) { const t = uAxis; uAxis = vAxis; vAxis = t; } } - // 木纹按真实尺寸平铺;柜门图案则必须把整张图片映射到整块门板。 - // 若 upright 也沿用 UV_SCALE,300mm 门板只会采样纹理左下约 37.5%, - // 表现为图案被截断并偏到门板一角。 - const uSize = uvMode === 'upright' || edgeBand ? 1 : size[uAxis] * UV_SCALE; - const vSize = uvMode === 'upright' || edgeBand ? 1 : size[vAxis] * UV_SCALE; - face.v.forEach((corner, i) => { + // 原木纹不是无缝贴图。每块板从原图中央连续取样,UV 保持在 0..1, + // 不再随模型绝对坐标跨入下一个贴图周期;长板等比缩放取样范围。 + // 柜门图案和窄封边仍使用完整图片,保留原有朝向。 + const grainScale = Math.min(UV_SCALE, 1 / Math.max(size[uAxis], size[vAxis])); + const uSize = uvMode === 'upright' || edgeBand ? 1 : size[uAxis] * grainScale; + const vSize = uvMode === 'upright' || edgeBand ? 1 : size[vAxis] * grainScale; + face.v.forEach((corner) => { positions.push( cx + corner[0] * half[0], cy + corner[1] * half[1], cz + corner[2] * half[2] ); normals.push(face.n[0], face.n[1], face.n[2]); - // UV 必须按角点在 u/v 轴上的实际坐标算。 - // 之前是按顶点顺序写死 0/uSize,导致声明的那两个轴根本没生效, - // 木纹方向和柜门图案朝向都会乱。 - // 大板面使用模型内的连续绝对坐标,窄封边与柜门则完整映射 0..1。 - const u = uvMode === 'grain' && !edgeBand - ? (center[uAxis] + corner[uAxis] * size[uAxis] / 2) * UV_SCALE - : ((corner[uAxis] + 1) / 2) * uSize; - const v = uvMode === 'grain' && !edgeBand - ? (center[vAxis] + corner[vAxis] * size[vAxis] / 2) * UV_SCALE - : ((corner[vAxis] + 1) / 2) * vSize; + const u = (1 + corner[uAxis] * uSize) / 2; + const v = (1 + corner[vAxis] * vSize) / 2; uvs.push(u, v); }); indices.push(base, base + 1, base + 2, base, base + 2, base + 3);