From 1d8f60cbaae28de2ca1be4e746c462fcf634a549 Mon Sep 17 00:00:00 2001 From: liupeng <511646242@qq.com> Date: Tue, 10 Jun 2025 11:16:24 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BE=93=E9=80=81=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Model3DView.vue | 422 +++++++++++++++++++++++++---------------- 1 file changed, 255 insertions(+), 167 deletions(-) diff --git a/src/components/Model3DView.vue b/src/components/Model3DView.vue index 2f38a72..e87bbcd 100644 --- a/src/components/Model3DView.vue +++ b/src/components/Model3DView.vue @@ -16,7 +16,7 @@ 打开材质 添加输送线 - 添加货架 + 添加货架2 添加地堆
材质颜色 @@ -86,10 +86,9 @@ import { TransformControls } from 'three/examples/jsm/controls/TransformControls import Split from '@/components/split/split.vue' import SplitArea from '@/components/split/split-area.vue' import { renderIcon } from '@/utils/webutils.js' -import textureUrl from '@/assets/images/conveyor/shapes/RibSideSkirtThumbnail.jpg' -import moveUrl from '@/assets/images/conveyor/shapes/move.svg' -import arrowRightUrl from '@/assets/images/conveyor/shapes/arrow-right.svg' -import rackUrl from '@/assets/images/conveyor/shapes/Rack.png' +import rackPlatUrl from "@/assets/images/conveyor/shapes/RackPlatform.png"; +import rackBlue from "@/assets/images/conveyor/shapes/Rack-blue.png"; +import triangleUrl from "@/assets/images/conveyor/shapes/triangle.png"; // DOM refs const canvasContainer = ref(null) @@ -169,77 +168,119 @@ watch(() => restate.mode, (newVal) => { tcontrols.setMode(newVal) } }) - -function addConveyor(){//输送线 - // 1. 创建几何体:长 10 米,宽 0.8 米 - const length = 20; // 长度 (X轴) - const width = 0.8; // 宽度 (Y轴) - const height = 0.2; // 厚度/高度 (Z轴) - - const geometry = new THREE.BoxGeometry(length, width, height); - - // 2. 加载纹理 - const textureLoader = new THREE.TextureLoader(); - textureLoader.load(textureUrl, (texture)=>{ - // 3. 设置纹理参数 - texture.wrapS = THREE.RepeatWrapping; // X方向重复(实际会拉伸) - texture.wrapT = THREE.RepeatWrapping; // Y方向重复 - texture.center.set(0.5,0.5) - texture.rotation = Math.PI / 2 - - texture.repeat.set(1, 1); // 初始不缩放,后面根据需要调整 - - // 4. 根据输送线长度自动调整Y方向的重复次数 - // 假设你的纹理高度是 256px,想要在 0.8m 宽度上显示完整的一行 - // 所以我们计算 repeat.y = 0.8 / (texture.height / texture.width * length) - // 或者你可以手动设置 repeat.y 来控制重复频率 - texture.repeat.set(1, 20); // 调整这个值来控制Y方向的重复频率 - - // 5. 创建材质并应用纹理 - const material = new THREE.MeshBasicMaterial( - { map: texture,color: 0x9f9f9f}); - - // 6. 创建网格对象 - const conveyorBelt = new THREE.Mesh(geometry, material); - - // 7. 可选:旋转为X轴方向(默认PlaneGeometry是X/Y平面) - conveyorBelt.rotation.x = -Math.PI / 2; // 让其平放在地面上(X/Z 平面) - - // 放置到合适的位置(如果需要) - conveyorBelt.position.y = height / 2; // 让底部贴地 - // // 示例:在输送线不同位置添加多个标记 - addMarkerAt(-9.5, height,0,moveUrl); // 起点 - addMarkerAt(0, height,0,arrowRightUrl); // 中间 - addMarkerAt(9.5, height,0,moveUrl); // 终点 - scene.add(conveyorBelt) - // 动画函数 - let offsetSpeed = 0.01; // 控制滚动速度 - function animate() { - requestAnimationFrame(animate); - - // 沿Y轴滚动 - if (texture) { - let currentOffset = texture.offset.y; - currentOffset += offsetSpeed; - - // 当偏移量超过1时重置,以实现无缝循环 - if (currentOffset >= 1) { - currentOffset -= 1; - } - texture.offset.set(texture.offset.x,currentOffset); - } - - renderer.render(scene, camera); - } - animate(); +function addConveyor(){ + const conveyorLength = 10; // 长度 + const conveyorWidth = 0.8; // 宽度 + const conveyorHeight = 0.08; // 厚度 + const wallHeight = conveyorHeight + 0.15; // 挡板高出输送线 0.2 米 + + const legWidth = 0.1; // 柱子宽度(X) + const legDepth = 0.1; // 柱子深度(Z) + const legHeight = 0.8; // 柱子高度(Y) + + // 创建输送线底部板子(灰色) + const conveyorGeometry = new THREE.BoxGeometry(conveyorWidth, conveyorHeight, conveyorLength); + const conveyorMaterial = new THREE.MeshBasicMaterial({ color: 0x6a6a6a }); + const conveyor = new THREE.Mesh(conveyorGeometry, conveyorMaterial); + conveyor.position.y = conveyorHeight / 2; + + // 添加两侧挡板(灰色) + const wallWidth = 0.05; + const wallGeometry = new THREE.BoxGeometry(wallWidth, wallHeight, conveyorLength); + const wallMaterial = new THREE.MeshBasicMaterial({ color: 0x6a6a6a }); + + const leftWall = new THREE.Mesh(wallGeometry, wallMaterial); + leftWall.position.set(-(conveyorWidth / 2 + wallWidth / 2), wallHeight / 2, 0); + + const rightWall = new THREE.Mesh(wallGeometry, wallMaterial); + rightWall.position.set((conveyorWidth / 2 + wallWidth / 2), wallHeight / 2, 0); + + // 创建柱子(灰色,类似桌腿) + const legGeometry = new THREE.BoxGeometry(legWidth, legHeight, legDepth); + const legMaterial = new THREE.MeshBasicMaterial({ color: 0x6a6a6a }); // 灰色 + + const legPositions = [ + [conveyorWidth / 2 - legWidth / 2, -(conveyorLength / 2 - legDepth / 2)], // 右前 + [-conveyorWidth / 2 + legWidth / 2, -(conveyorLength / 2 - legDepth / 2)], // 左前 + [conveyorWidth / 2 - legWidth / 2, +(conveyorLength / 2 - legDepth / 2)], // 右后 + [-conveyorWidth / 2 + legWidth / 2, +(conveyorLength / 2 - legDepth / 2)] // 左后 + ]; + + const legs = []; + + legPositions.forEach(pos => { + const leg = new THREE.Mesh(legGeometry, legMaterial); + leg.position.set( + pos[0], + -legHeight / 2, + pos[1] + ); + legs.push(leg); }); -} -function addMarkerAt(x, y,z,textUrl, scale = 1) { + // ====== 新增部分:顶部带圆角的长方体(胶囊型)====== + const beamWidth = conveyorWidth; // 和输送线一样宽(0.8米) + const beamLength = conveyorLength-conveyorHeight/2;// 和输送线一样长(10米) + const beamHeight = 0.08; // 高度(0.1米) + + // 创建 Shape(二维路径) + const shape = new THREE.Shape(); + + const radius = beamHeight / 2; // 圆角半径 = 一半宽度 + + // 左侧半圆(从顶部开始,顺时针) + shape.absarc(-beamLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2, false); + + // 右侧半圆(从底部开始,顺时针) + shape.absarc(beamLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2, false); + + shape.closePath(); // 封闭路径 + + // 拉伸成三维几何体 + const extrudeSettings = { + depth: beamWidth, + steps: 16, + bevelEnabled: false + }; + + const beamGeometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); + const beamMaterial = new THREE.MeshBasicMaterial({ color: 0x032702 }); // 粉色 + + const beam = new THREE.Mesh(beamGeometry, beamMaterial); + beam.rotation.x = Math.PI / 2; + beam.rotation.z = Math.PI / 2 + beam.rotation.y = Math.PI / 2 + beam.position.y = beamHeight+conveyorHeight/2; // 底部贴着输送线顶部 + beam.position.x = -beamWidth/2; + addMarkerAt(0, legHeight+beamHeight+conveyorHeight,0,triangleUrl,1,false); + addMarkerAt(beamWidth/2+wallWidth+0.01, legHeight+wallHeight/2,0,triangleUrl,.5,true); + addMarkerAt(beamWidth/2+wallWidth+0.01, legHeight+wallHeight/2,conveyorLength/4,triangleUrl,.5,true); + addMarkerAt(beamWidth/2+wallWidth+0.01, legHeight+wallHeight/2,-conveyorLength/4,triangleUrl,.5,true); + + addMarkerAt(-(beamWidth/2+wallWidth+0.01), legHeight+wallHeight/2,0,triangleUrl,.5,true); + addMarkerAt(-(beamWidth/2+wallWidth+0.01), legHeight+wallHeight/2,conveyorLength/4,triangleUrl,.5,true); + addMarkerAt(-(beamWidth/2+wallWidth+0.01), legHeight+wallHeight/2,-conveyorLength/4,triangleUrl,.5,true); + // 将所有元素组合成一个组 + const group = new THREE.Group(); + group.add(conveyor); + group.add(leftWall); + group.add(rightWall); + legs.forEach(leg => group.add(leg)); + group.add(beam); // 添加新长方体 + // 设置组沿Z轴向上移动的距离,例如 1 米 + group.position.y = legHeight; // 修改这里的值来改变提升的高度 + scene.add(group); +} +function addMarkerAt(x, y,z,textUrl, scale = 1,lip) { const loader = new THREE.TextureLoader(); loader.load(textUrl, (texture) => { // 创建一个平面作为标记(大小可以调整) - const markerGeometry = new THREE.PlaneGeometry(.6 * scale, 0.5 * scale); // 宽高比例保持一致 + let markerGeometry + if(lip){ + markerGeometry=new THREE.PlaneGeometry(.25 * scale, .25 * scale); + }else{ + markerGeometry = new THREE.PlaneGeometry(.45 * scale, .3 * scale); // 宽高比例保持一致 + } const markerMaterial = new THREE.MeshBasicMaterial({ map: texture, @@ -257,126 +298,172 @@ function addMarkerAt(x, y,z,textUrl, scale = 1) { // 旋转为 X/Z 平面方向(与输送线一致) marker.rotation.x = -Math.PI / 2; // 和 conveyorBelt 一样旋转角度 marker.rotation.z = Math.PI / 2; // 在平面上旋转 90 度 + if(lip){ + marker.rotation.y = Math.PI / 2; // 在平面上旋转 90 度 + }else{ + marker.rotation.y = Math.PI; + } scene.add(marker); }); } function createShelf(){//创建货架 - const textureLoader = new THREE.TextureLoader(); - const metalTexture = textureLoader.load('path/to/your/metal/texture.jpg'); // 替换为实际路径 + const shelfLength = 5; + const shelfWidth = 0.8; + const shelfThickness = 0.02; + const rows = 3; + const columns = 4; + const spacingY = 1; // 每行之间的间距 + const gapBetweenPlanks = 0.1; // 层板之间的间隙宽度 + const baseHeight = 0; // 地面高度设为0 - // 材质设置 - const material = new THREE.MeshStandardMaterial({ - map: metalTexture, - metalness: 1.0, - roughness: 0.5 + const unitLength = (shelfLength - (columns - 1) * gapBetweenPlanks) / columns; + + const textureLoader = new THREE.TextureLoader(); + const shelfTexture = textureLoader.load(rackPlatUrl, () => { + renderer.render(scene, camera); }); + const redPoleTexture = textureLoader.load(rackBlue, () => { + renderer.render(scene, camera); // 确保贴图加载后重新渲染 + }); + + shelfTexture.wrapS = THREE.RepeatWrapping; + shelfTexture.wrapT = THREE.RepeatWrapping; + shelfTexture.repeat.set(1, 20); + shelfTexture.rotation = Math.PI / 2; - // 货架参数 - const shelfLength = 10; // 总长 - const shelfWidth = 0.8; // 宽度(深度) - const shelfThickness = 0.1; // 层板厚度 - const rows = 3; // 层数 - const columns = 4; // 列数 - const spacingY = 1; // 层间距 + const shelfMaterial = new THREE.MeshPhongMaterial({ + map: shelfTexture, + color: 0x999999, + shininess: 60 + }); - const unitLength = shelfLength / columns; // 每个格子长度:2.5m + // 垂直柱子材质 + const poleMaterial = new THREE.MeshPhongMaterial({ + map: shelfTexture, + color: 0x333333, + shininess: 60 + }); - // ✅ 整体上浮高度:让模型位于水平面之上 - const baseHeight = 2; // 单位:米 + // 柱子尺寸 + const poleSize = gapBetweenPlanks; // 柱子的宽度和深度都等于缝隙宽度 + const totalHeight = rows * spacingY; // 柱子总高度 - // --------------------- - // 创建层板(3层 × 4列) - // --------------------- for (let row = 0; row < rows; row++) { + let currentXOffset = -shelfLength / 2; + for (let col = 0; col < columns; col++) { const geometry = new THREE.BoxGeometry(unitLength, shelfThickness, shelfWidth); - const mesh = new THREE.Mesh(geometry, material); + const mesh = new THREE.Mesh(geometry, shelfMaterial); - const x = -shelfLength / 2 + unitLength * col + unitLength / 2; - const y = -(shelfThickness + spacingY) * row - shelfThickness / 2 + baseHeight; // 加上偏移 + const x = currentXOffset + unitLength / 2; + // 计算层板的 Y 坐标,使其基于地面高度 + const y = baseHeight + row * spacingY + shelfThickness / 2; const z = 0; mesh.position.set(x, y, z); scene.add(mesh); - } - } - - // --------------------- - // 计算第三层层板底部位置(用于支撑柱对齐) - // --------------------- - const thirdShelfCenterY = -(shelfThickness + spacingY) * 2 - shelfThickness / 2 + baseHeight; - const thirdShelfBottomY = thirdShelfCenterY - shelfThickness / 2; - - // --------------------- -// 创建支撑柱 + 添加贴纸 -// --------------------- - - const postWidth = 0.1; - const postDepth = 0.1; - const postHeight = 3.3; - + if (row === 0) { // 只在第一层添加垂直柱子 + // 最左侧柱子 + if (col === 0) { + const leftPoleX = currentXOffset - poleSize / 2; + const leftPoleY = baseHeight + totalHeight / 2; + const leftPoleZ = z - shelfWidth / 2 + poleSize / 2; + + // 黑色柱子 + const leftPole = new THREE.Mesh( + new THREE.BoxGeometry(poleSize, totalHeight, poleSize), + poleMaterial + ); + leftPole.position.set(leftPoleX, leftPoleY, leftPoleZ); + scene.add(leftPole); + + // // 红色柱子 + const redLeftPoleZ = z + shelfWidth / 2 - poleSize / 2; + const redLeftPole = new THREE.Mesh( + new THREE.BoxGeometry(poleSize, totalHeight, poleSize), + new THREE.MeshPhongMaterial({ + map: redPoleTexture, + color: 0xffffff, // 颜色可保留作为叠加色 + shininess: 60, + transparent: true, + }) + // new THREE.MeshPhongMaterial({ color: 0xff0000, shininess: 60 }) + ); + redLeftPole.position.set(leftPoleX, leftPoleY, redLeftPoleZ); + scene.add(redLeftPole); + } + // 层板间隙中的柱子 + if (col < columns - 1) { + const gapStartX = currentXOffset + unitLength; + + // 原有灰色柱子(居中) + const poleX = gapStartX + poleSize / 2; + const poleY = baseHeight + totalHeight / 2; + const poleZ = z- shelfWidth / 2+poleSize/2; + + const pole = new THREE.Mesh( + new THREE.BoxGeometry(poleSize, totalHeight, poleSize), + poleMaterial + ); + pole.position.set(poleX, poleY, poleZ); + scene.add(pole); + + // 新增红色柱子,在原有柱子的前面或后面并排 + const redPoleX = poleX; // 和灰柱 X 相同 + const redPoleY = poleY; // 和灰柱 Y 相同 + const redPoleZ = z + shelfWidth / 2-poleSize/2; // 在前方 + + const redPoleMaterial = new THREE.MeshPhongMaterial({ + color: 0xff0000, + shininess: 60 + }); + + const redPole = new THREE.Mesh( + new THREE.BoxGeometry(poleSize, totalHeight, poleSize), + redPoleMaterial + ); + redPole.position.set(redPoleX, redPoleY, redPoleZ); + scene.add(redPole); + } - const postPositionsX = [0, 2.5, 5, 7.5, 10]; - const postPositionsZ = [-shelfWidth / 2, shelfWidth / 2]; + // 最右侧柱子 + if (col === columns - 1) { + const rightPoleX = currentXOffset + unitLength + gapBetweenPlanks - poleSize / 2; + const rightPoleY = baseHeight + totalHeight / 2; + const rightPoleZ = z - shelfWidth / 2 + poleSize / 2; + + // 黑色柱子 + const rightPole = new THREE.Mesh( + new THREE.BoxGeometry(poleSize, totalHeight, poleSize), + poleMaterial + ); + rightPole.position.set(rightPoleX, rightPoleY, rightPoleZ); + scene.add(rightPole); + + // 红色柱子 + const redRightPoleZ = z + shelfWidth / 2 - poleSize / 2; + const redRightPole = new THREE.Mesh( + new THREE.BoxGeometry(poleSize, totalHeight, poleSize), + new THREE.MeshPhongMaterial({ color: 0xff0000, shininess: 60 }) + ); + redRightPole.position.set(rightPoleX, rightPoleY, redRightPoleZ); + scene.add(redRightPole); + } + } -// 加载贴纸纹理(可以是透明PNG) - const loader = new THREE.TextureLoader(); - loader.load(rackUrl, (texture) => { - // 创建贴纸材质(支持透明) - const stickerMaterial = new THREE.MeshBasicMaterial({ - map: texture, - transparent: true, - side: THREE.DoubleSide // 双面可见 - }); + currentXOffset += unitLength + gapBetweenPlanks; + } + } - postPositionsX.forEach(x => { - postPositionsZ.forEach(z => { - // 创建柱子 - const postGeometry = new THREE.BoxGeometry(postWidth, postHeight, postDepth); - const post = new THREE.Mesh(postGeometry, material); - - // 设置柱子中心 Y 坐标,使其底部对齐第三层层板底部 - const postCenterY = thirdShelfBottomY + postHeight / 2; - - post.position.set( - x - shelfLength / 2, - postCenterY, - z - ); - - scene.add(post); - - // --------------------- - // 创建贴纸(附加在外侧面上) - // --------------------- - const stickerWidth = 0.08; // 贴纸宽度 - const stickerHeight = 0.2; // 贴纸高度 - - const stickerGeometry = new THREE.PlaneGeometry(stickerWidth, stickerHeight); - const sticker = new THREE.Mesh(stickerGeometry, stickerMaterial); - - // 将贴纸放在柱子外侧表面中间位置 - sticker.position.set( - post.position.x + postWidth / 2, // 柱子外侧 - post.position.y, // Y 中心 - post.position.z // Z 对齐 - ); - - // 让贴纸始终面向相机(可选) - // 使用 dcm.orientation.lookAt(camera.position) 或手动设置朝向 - // 这里简单设置固定朝向正面(可根据需求扩展为自动朝向相机) - sticker.lookAt(new THREE.Vector3(1, 0, 0)); // 面向 X 正方向 - - // 微调:让贴纸略微浮出柱子表面,避免深度冲突 - sticker.position.x += 0.01; - - scene.add(sticker); - }); - }); - }) + // 光源 + const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); + scene.add(ambientLight); + const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); + directionalLight.position.set(5, 10, 7); + scene.add(directionalLight); } function createGroundStore() { @@ -888,6 +975,7 @@ function cleanupThree() { padding: 10px; background: #f5f5f5; border-bottom: 1px solid #ccc; + flex-wrap: wrap; } .dialog-container {