林业厅网站建设方案,网站维护包括哪些,南京中小企业网站制作,阳江市招聘最新招聘信息三维装配可视化界面开发笔记
项目概述
这是一个基于Vue.js和Three.js的三维装配可视化系统#xff0c;用于展示机械零部件的装配和拆解过程。系统支持模型加载、拆解/装配路径生成、动画展示和工艺流程图生成等功能。
技术栈
前端框架: Vue 3 (使用组合式API)构建工具: Vi…三维装配可视化界面开发笔记
项目概述
这是一个基于Vue.js和Three.js的三维装配可视化系统用于展示机械零部件的装配和拆解过程。系统支持模型加载、拆解/装配路径生成、动画展示和工艺流程图生成等功能。
技术栈
前端框架: Vue 3 (使用组合式API)构建工具: Vite 6.3.43D引擎: Three.js r152状态管理: Pinia 2.1.0路由: Vue Router 4.2.0模型加载: GLTFLoader, OBJLoader控制器: OrbitControls
开发日志
2025-04-30
初始化项目使用npm create vitelatest创建Vue3项目安装Three.js及相关依赖npm install three types/three实现了基本的Three.js场景初始化包括场景、相机、渲染器和控制器添加了模型加载功能支持GLTF和OBJ格式遇到问题模型加载后显示太小调整了相机距离参数从默认的5改为根据模型大小动态计算踩坑Three.js的OrbitControls需要从examples中导入不是核心包的一部分
2025-05-01
实现了部件选择和拖动功能使用Raycaster进行射线检测添加了拆解步骤记录功能记录部件ID、动作类型和移动路径实现了视图切换功能正视图、俯视图、侧视图遇到问题视图切换后模型显示不正确调整了相机参数和上方向设置踩坑Three.js中相机的up向量设置对视图方向有重要影响特别是在俯视图中需要设置为(0,0,-1)
2023-05-01
修复了模型加载问题现在可以正确加载zhuangpeitu_asm模型优化了相机控制使模型显示更加合理调整了fitCameraToObject函数中的边距系数添加了模型加载失败时的备用方案loadFallbackModel函数遇到问题某些复杂模型的部件层次结构难以正确解析踩坑GLTF模型中的bin文件路径问题需要确保bin文件和gltf文件在同一目录下
系统架构
整体架构
系统采用前端单页应用架构使用Vue3作为框架Three.js作为3D渲染引擎。数据流向如下
用户交互 - Vue组件 - Pinia Store - Three.js场景更新模型加载 - 部件提取 - 存储到Store - 渲染到场景部件操作 - 记录步骤 - 生成工艺流程
模块划分
系统分为以下几个主要模块
模型查看器模块负责3D场景渲染、模型加载和交互工艺步骤模块记录和展示装配/拆解步骤工艺流程图模块可视化展示装配流程工具栏模块提供视图切换、模型加载等功能
项目结构
src/
├── assets/ # 静态资源
├── components/ # 组件
│ ├── ModelViewer/ # 3D模型查看器
│ │ └── ModelViewer.vue # 核心3D渲染组件
│ ├── ProcessChart/# 工艺流程图
│ │ └── ProcessChart.vue # 流程图组件
│ ├── StepList/ # 工艺步骤列表
│ │ └── StepList.vue # 步骤列表组件
│ └── ToolBar/ # 工具栏
│ └── ToolBar.vue # 工具栏组件
├── router/ # 路由配置
│ └── index.js # 路由定义
├── services/ # 服务
│ └── assemblyService.js # 装配相关服务包含路径计算等
├── stores/ # 状态管理
│ ├── modelStore.js # 模型状态存储模型和部件信息
│ └── assemblyStore.js # 装配状态存储装配步骤和播放状态
└── views/ # 页面视图├── AssemblyDesignView.vue # 装配设计页面├── ProcessDesignView.vue # 工艺设计页面└── StepDesignView.vue # 工步设计页面核心文件说明
ModelViewer.vue: 系统核心组件包含Three.js场景初始化、模型加载、部件交互等功能assemblyStore.js: 存储装配步骤、播放状态等信息提供步骤添加、播放控制等方法modelStore.js: 存储模型信息、部件列表等提供部件选择、信息更新等方法assemblyService.js: 提供路径计算、碰撞检测等服务
关键功能实现与数据结构
模型加载
模型加载使用Three.js的GLTFLoader和OBJLoader实现。加载后会提取模型的部件信息并存储在modelStore中。
// 加载GLTF模型
const loadGLTF (url) {const loader new GLTFLoader()loader.load(url,(gltf) {// 清除现有模型clearScene()// 添加新模型到场景scene.add(gltf.scene)// 调整相机位置以适应模型fitCameraToObject(gltf.scene)// 提取部件信息const parts extractParts(gltf.scene)modelStore.setParts(parts)// 设置动画混合器if (gltf.animations gltf.animations.length 0) {animationMixer new THREE.AnimationMixer(gltf.scene)gltf.animations.forEach((clip) {animationMixer.clipAction(clip).play()})}},// ...错误处理)
}模型加载中遇到的主要问题是bin文件路径问题。GLTF文件通常引用外部的bin文件需要确保这些文件在正确的相对路径上。我们通过将所有模型文件放在public目录下解决了这个问题。
部件提取与数据结构
部件提取是从加载的3D模型中识别和分离各个组件的过程。我们使用以下数据结构来表示部件
// 部件数据结构
{id: String, // 部件唯一标识符name: String, // 部件名称mesh: THREE.Mesh, // 部件的3D网格对象parentId: String // 父部件ID用于构建层次结构
}提取过程中遍历模型的所有网格对象为每个网格创建一个部件对象
// 从模型中提取部件信息
const extractParts (object) {const parts []object.traverse((child) {if (child.isMesh) {// 为每个网格创建一个唯一IDconst id part_${parts.length}// 获取部件名称const name child.name || 部件 ${parts.length 1}// 确定父部件IDlet parentId nullif (child.parent child.parent ! object) {parentId child.parent.uuid}// 添加到部件列表parts.push({id,name,mesh: child,parentId})// 存储原始位置child.userData.originalPosition child.position.clone()child.userData.originalRotation child.rotation.clone()// 添加点击事件child.userData.partId id}})return parts
}部件拖拽与交互
实现了基于射线检测的部件选择和拖拽功能。当用户拖动部件时会记录拆解步骤。
// 鼠标按下事件处理
const onMouseDown (event) {// 计算鼠标位置const rect renderer.domElement.getBoundingClientRect()mouse.x ((event.clientX - rect.left) / rect.width) * 2 - 1mouse.y -((event.clientY - rect.top) / rect.height) * 2 1// 设置射线raycaster.setFromCamera(mouse, camera)// 获取与射线相交的对象const intersects raycaster.intersectObjects(scene.children, true)if (intersects.length 0) {// 找到第一个有partId的对象const intersectedObject intersects.find(intersect intersect.object.userData intersect.object.userData.partId)if (intersectedObject) {// 禁用轨道控制器controls.enabled false// 设置拖拽状态isDragging true// 获取选中的部件const partId intersectedObject.object.userData.partIdselectedPart modelStore.parts.find(part part.id partId)// 记录起始位置dragStartPosition.copy(selectedPart.mesh.position)// 设置拖拽平面planeNormal.copy(camera.position).sub(controls.target).normalize()planePoint.copy(selectedPart.mesh.position)plane.setFromNormalAndCoplanarPoint(planeNormal, planePoint)}}
}拖拽过程中使用射线与平面的交点来确定部件的新位置
// 鼠标移动事件处理
const onMouseMove (event) {if (!isDragging || !selectedPart) return// 计算鼠标位置const rect renderer.domElement.getBoundingClientRect()mouse.x ((event.clientX - rect.left) / rect.width) * 2 - 1mouse.y -((event.clientY - rect.top) / rect.height) * 2 1// 设置射线raycaster.setFromCamera(mouse, camera)// 计算射线与平面的交点const ray raycaster.rayif (ray.intersectPlane(plane, intersectionPoint)) {// 移动部件selectedPart.mesh.position.copy(intersectionPoint)// 更新当前位置dragCurrentPosition.copy(intersectionPoint)}
}工艺步骤记录
当用户拖动部件完成拆解操作时系统会记录这个步骤。步骤数据结构如下
// 步骤数据结构
{partId: String, // 部件IDaction: String, // 动作类型拆解/装配path: Array // 移动路径包含一系列位置点
}步骤记录过程
// 鼠标释放事件处理
const onMouseUp () {if (!isDragging || !selectedPart) return// 启用轨道控制器controls.enabled true// 计算移动距离const distance dragStartPosition.distanceTo(dragCurrentPosition)// 如果移动距离足够大则记录拆解步骤if (distance 0.5) {// 计算移动路径const path calculateLinearPath(dragStartPosition, dragCurrentPosition, 20)// 记录拆解步骤assemblyStore.addStep({partId: selectedPart.id,action: 拆解,path: path})} else {// 如果移动距离不够则恢复原位selectedPart.mesh.position.copy(dragStartPosition)}// 重置拖拽状态isDragging falseselectedPart null
}视图切换
实现了正视图、俯视图和侧视图的切换功能。关键是设置相机位置和上方向向量
// 改变视角
const changeView (viewType) {// 获取模型的边界框const box new THREE.Box3().setFromObject(scene)const size box.getSize(new THREE.Vector3())const center box.getCenter(new THREE.Vector3())// 计算合适的距离const maxDim Math.max(size.x, size.y, size.z)const distance maxDim * 1.2// 根据视角类型设置相机位置switch (viewType) {case front:camera.position.set(center.x, center.y, center.z distance)camera.up.set(0, 1, 0) // Y轴向上breakcase top:camera.position.set(center.x, center.y distance, center.z)camera.up.set(0, 0, -1) // Z轴向下breakcase side:camera.position.set(center.x distance, center.y, center.z)camera.up.set(0, 1, 0) // Y轴向上break}// 更新相机camera.lookAt(center)camera.updateProjectionMatrix()// 更新控制器controls.update()
}工艺流程图生成
工艺流程图基于记录的拆解步骤生成使用简单的节点和连线表示装配关系
// 生成工艺流程图
const generateProcessChart () {const steps assemblyStore.stepsif (steps.length 0) return// 清除现有图表chartContainer.innerHTML // 创建SVG元素const svg document.createElementNS(http://www.w3.org/2000/svg, svg)svg.setAttribute(width, 100%)svg.setAttribute(height, 100%)// 为每个步骤创建节点steps.forEach((step, index) {const part modelStore.parts.find(p p.id step.partId)if (!part) return// 创建节点const node document.createElementNS(http://www.w3.org/2000/svg, circle)node.setAttribute(cx, 50 index * 100)node.setAttribute(cy, 50)node.setAttribute(r, 20)node.setAttribute(fill, #42b883)// 创建标签const text document.createElementNS(http://www.w3.org/2000/svg, text)text.setAttribute(x, 50 index * 100)text.setAttribute(y, 90)text.setAttribute(text-anchor, middle)text.textContent part.name// 添加到SVGsvg.appendChild(node)svg.appendChild(text)// 添加连线if (index 0) {const line document.createElementNS(http://www.w3.org/2000/svg, line)line.setAttribute(x1, 50 (index - 1) * 100)line.setAttribute(y1, 50)line.setAttribute(x2, 50 index * 100)line.setAttribute(y2, 50)line.setAttribute(stroke, #666)line.setAttribute(stroke-width, 2)svg.appendChild(line)}})// 添加到容器chartContainer.appendChild(svg)
}开发流程与工作方式
开发流程
需求分析确定系统功能和用户交互方式技术选型选择Vue3和Three.js作为主要技术栈架构设计设计系统模块和数据流组件开发 先开发核心的ModelViewer组件实现基本的模型加载和显示添加部件选择和拖动功能实现工艺步骤记录开发工艺流程图生成功能 集成测试测试各模块之间的交互优化改进根据测试结果进行优化
工作方式
使用Git进行版本控制采用组件化开发方式每个功能模块独立开发使用Pinia进行状态管理确保数据流的清晰性定期进行代码审查和重构保持代码质量
参考资料
Three.js文档: https://threejs.org/docs/Vue 3文档: https://v3.vuejs.org/GLTF格式规范: https://github.com/KhronosGroup/glTFPinia状态管理: https://pinia.vuejs.org/《3D Game Engine Design》 - David H. Eberly《Learning Three.js》 - Jos Dirksen