三維裝配可視化界面開發筆記

三維裝配可視化界面開發筆記

項目概述

這是一個基于Vue.js和Three.js的三維裝配可視化系統,用于展示機械零部件的裝配和拆解過程。系統支持模型加載、拆解/裝配路徑生成、動畫展示和工藝流程圖生成等功能。

技術棧

  • 前端框架: Vue 3 (使用組合式API)
  • 構建工具: Vite 6.3.4
  • 3D引擎: Three.js r152
  • 狀態管理: Pinia 2.1.0
  • 路由: Vue Router 4.2.0
  • 模型加載: GLTFLoader, OBJLoader
  • 控制器: OrbitControls

開發日志

2025-04-30

  • 初始化項目,使用npm create vite@latest創建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渲染引擎。數據流向如下:

  1. 用戶交互 -> Vue組件 -> Pinia Store -> Three.js場景更新
  2. 模型加載 -> 部件提取 -> 存儲到Store -> 渲染到場景
  3. 部件操作 -> 記錄步驟 -> 生成工藝流程

模塊劃分

系統分為以下幾個主要模塊:

  1. 模型查看器模塊:負責3D場景渲染、模型加載和交互
  2. 工藝步驟模塊:記錄和展示裝配/拆解步驟
  3. 工藝流程圖模塊:可視化展示裝配流程
  4. 工具欄模塊:提供視圖切換、模型加載等功能

項目結構

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)
}

開發流程與工作方式

開發流程

  1. 需求分析:確定系統功能和用戶交互方式
  2. 技術選型:選擇Vue3和Three.js作為主要技術棧
  3. 架構設計:設計系統模塊和數據流
  4. 組件開發
    • 先開發核心的ModelViewer組件
    • 實現基本的模型加載和顯示
    • 添加部件選擇和拖動功能
    • 實現工藝步驟記錄
    • 開發工藝流程圖生成功能
  5. 集成測試:測試各模塊之間的交互
  6. 優化改進:根據測試結果進行優化

工作方式

  • 使用Git進行版本控制
  • 采用組件化開發方式,每個功能模塊獨立開發
  • 使用Pinia進行狀態管理,確保數據流的清晰性
  • 定期進行代碼審查和重構,保持代碼質量

參考資料

  • Three.js文檔: https://threejs.org/docs/
  • Vue 3文檔: https://v3.vuejs.org/
  • GLTF格式規范: https://github.com/KhronosGroup/glTF
  • Pinia狀態管理: https://pinia.vuejs.org/
  • 《3D Game Engine Design》 - David H. Eberly
  • 《Learning Three.js》 - Jos Dirksen

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/903729.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/903729.shtml
英文地址,請注明出處:http://en.pswp.cn/news/903729.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

深?理解指針(8)

1.對上一篇的補充內容 typedef int* ptr_t #define PTR_T int* 這兩種寫法都是可以的 ptr_t p1, p2; //p1, p2 都是指針變量 PTR_T p3, p4; //p3 是指針變量, p4是整型變量 為什么p3 是指針變量, p4是整型變量呢? 因為PTR_T 真的被改為了 int* 在編譯器中…

neo4j暴露公網ip接口——給大模型聯通知識圖譜

特別鳴謝 我的領導,我的腦子,我的學習能力,感動了 1. 搭建知識圖譜數據庫(見上一章博客) 這里不加贅述了,請參考上一篇博客搭建 2. FastApi包裝接口 這里注意:NEO4J_URI不得寫http:,只能寫…

AI編程新選擇!VSCode + RooCode,超越Cursor?

在當今快節奏的開發環境中,AI編程助手已經成為提升開發效率的關鍵工具。然而,面對眾多選擇,開發者往往陷入糾結:如何在眾多AI編程工具中找到最適合自己的方案?尤其是當VSCode搭配RooCode時,相比Cursor&…

電子病歷高質量語料庫構建方法與架構項目(環境聆聽與自動化文檔生成篇)

電子病歷高質量語料庫的構建是一個復雜而系統的工程,涉及數據收集、清洗、標注、驗證等多個環節。在項目實施過程中,"環境聆聽"和"自動化文檔生成"是兩個關鍵支撐要素,前者確保項目能夠適應不斷變化的技術和業務環境,后者則保障項目過程的可追溯性和知…

Python協程入門指北

一、什么是協程? 協程(Coroutine)就像可以暫停執行的函數,能夠在執行過程中主動讓出控制權,等準備好后再繼續執行。 生活小例子 想象你在咖啡店排隊: 普通函數:必須一直排到取餐&#xff08…

mysql-5.7.24-linux-glibc2.12-x86_64.tar.gz的下載安裝和使用

資源獲取鏈接: mysql-5.7.24-linux-glibc2.12-x86-64.tar.gz和使用說明資源-CSDN文庫 詳細作用 數據庫服務器的核心文件: 這是一個壓縮包,解壓后包含 MySQL 數據庫服務器的可執行文件、庫文件、配置文件模板等。 它用于在 Linux 系統上安裝…

C++筆記-繼承(下)(包含派生類的默認成員函數,菱形繼承等)

一.派生類的默認成員函數 1.14個常見默認成員函數 默認成員函數,默認的意思就是指我們不寫,編譯器會自動為我們生成一個,那么在派生類中,這幾個成員函數是如何生成的呢? 1.派生類的構造函數必須調用基類的構造函數初…

C++中指針使用詳解(3)數組、指針和函數參數傳遞的底層 ABI實現

要深入理解 數組、指針和函數參數傳遞 的底層 ABI(Application Binary Interface)實現,需要從以下幾個維度出發進行學習: 一、什么是 ABI? ABI 是編譯器和操作系統之間的協定,規定了: 函數如何…

【RustDesk 】中繼1:壓力測試 Python 版 RustDesk 中繼服務器

測試 Python 版 RustDesk 中繼服務器 測試我們實現的中繼服務器有幾種方法,從簡單到復雜依次如下: 1. 基本連接測試客戶端 創建一個簡單的測試客戶端來驗證中繼服務器的基本功能: 2. 用兩個測試客戶端測試中繼功能 要測試完整的中繼功能,你需要運行兩個客戶端實例來模擬…

Spring Boot集成Spring Cloud 2024(不使用Feign)

本文介紹Spring Boot集成Spring Cloud 2024,且不使用Feign,而是采用Spring 6自帶的HttpExchange方式進行服務調用的詳細步驟: 環境準備 Spring Boot版本:推薦使用Spring Boot 3.4.1及以上版本,以更好地與Spring Clou…

vue中$set原理

Vue 中的 $set 方法(Vue.set)主要用于 向響應式對象中添加一個新的屬性,并確保這個新屬性是響應式的,能夠觸發視圖更新。 📌 背景問題:為什么需要 $set? 在 Vue 2 中,直接給對象新增…

Superset二次開發之深度解讀系列:1.概述

Apache Superset 是一款現代化的企業級商業智能 Web 應用程序,專為數據探索和可視化而設計。本概述介紹了 Superset 的架構、核心組件和主要功能,以幫助開發人員了解該系統的工作原理。 What is Apache Superset? Apache Superset 是一個開源數據探索…

Linux系統之elfedit詳解

elfedit 是一個用于修改 ELF(可執行與可鏈接格式)文件頭的工具。它允許用戶根據指定的條件(如機器類型、文件類型、操作系統/ABI)匹配并更新 ELF 文件的頭部信息。支持 32 位和 64 位 ELF 文件,以及包含 ELF 文件的歸檔…

前端HTML基礎知識

1.HTML介紹 HTML(HyperText Markup Language,超文本標記語言)是構成網頁的基本元素,是一種用于創建網頁的標準化標記語言。HTML不是一種編程語言,而是一種標記語言,通過標簽來描述網頁的結構和內容。 超文本:超文本是…

【IP101】圖像濾波技術詳解:從均值濾波到高斯濾波的完整指南

🌟 圖像濾波魔法指南 🎨 在圖像處理的世界里,濾波就像是給圖片"美顏"的魔法工具。讓我們一起來探索這些神奇的濾波術吧! 📑 目錄 1. 均值濾波:圖像的"磨皮"大法2. 中值濾波&#xff1…

LINE FRIENDS 正式與 Walrus 合作,全新 AI 驅動的游戲即將上線

風靡全球的 LINE FRIENDS 角色即將以“minini”迷你造型登陸 Walrus,雖然尺寸更小,但承諾帶來“大”動作。IPX(LINE FRIENDS 背后的公司)打造了《minini universe: ROOM》游戲,這是一款基于其 minini 系列角色的多鏈游…

2025年信息素養大賽C++算法創意實踐挑戰賽初賽樣題及答案解析(小學組)

一、選擇題 1、下列代碼&#xff0c;能夠輸出 hello world 的是_____ A. cout (hello world) B. cout << hello world C. cout:hello world D. cout << "hello world"; 答案&#xff1a;D 解析&#xff1a;cout輸出的文本內容要用雙引號引起來 2、…

[c語言日寄]檢查環形鏈表

【作者主頁】siy2333 【專欄介紹】?c語言日寄?&#xff1a;這是一個專注于C語言刷題的專欄&#xff0c;精選題目&#xff0c;搭配詳細題解、拓展算法。從基礎語法到復雜算法&#xff0c;題目涉及的知識點全面覆蓋&#xff0c;助力你系統提升。無論你是初學者&#xff0c;還是…

黃雀在后:外賣大戰新變局,淘寶+餓了么開啟電商大零售時代

當所有人以為美團和京東的“口水戰”硝煙漸散&#xff0c;外賣大戰告一段落時&#xff0c;“螳螂捕蟬&#xff0c;黃雀在后”&#xff0c;淘寶閃購聯合餓了么“閃現”外賣戰場&#xff0c;外賣烽火再度燃起。 4 月30日&#xff0c;淘寶天貓旗下即時零售業務“小時達”正式升級…

如何在uni-app中自定義輸入框placeholder的樣式

在開發uni-app應用時&#xff0c;我們經常需要自定義輸入框&#xff08;<input>&#xff09;的樣式以匹配應用的整體設計。默認情況下&#xff0c;uni-app的輸入框提供了一些基本的樣式選項&#xff0c;但有時候我們需要更細致地控制輸入框的每個部分&#xff0c;例如pla…