最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

vue2引入Three.js預(yù)覽3D文件的實(shí)現(xiàn)方法

 更新時(shí)間:2025年10月19日 11:27:08   作者:weixin_45435220  
three.js是一個(gè)基于WebGL的JavaScript庫(kù),它允許開(kāi)發(fā)者在網(wǎng)頁(yè)上創(chuàng)建和顯示3D圖形,這篇文章主要介紹了vue2引入Three.js預(yù)覽3D文件的實(shí)現(xiàn)方法,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下

實(shí)現(xiàn)方案

使用occt-import-js將.stp或者.STEP文件解析成可以渲染的模型對(duì)象,使用Three.js創(chuàng)建3D場(chǎng)景,渲染模型。我使用的node版本是v18.20.0

引入Three.js:

npm install three

這一步一般很少出錯(cuò),如果版本沖突可以加上–legacy-peer-deps參數(shù)解決。

引入occt-import-js解析模型文件

npm install occt-import-js

這里有個(gè)坑,依賴安裝之后運(yùn)行報(bào)錯(cuò)

Module parse failed: Unexpected token (3:76)
You may need an appropriate loader to handle this file type.
|
| var occtimportjs = (() => {
>   var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;
|   if (typeof __filename != 'undefined') _scriptName = _scriptName || __filename;
|   return (

 @ ./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/views/emsTool/info/preview.vue?vue&type=script&lang=js 207:63-88

這里是說(shuō)Babel 未正確轉(zhuǎn)譯現(xiàn)代 JavaScript 語(yǔ)法,檢查了很久咱也不知道到底哪個(gè)語(yǔ)法不支持,ai建議我配置 Babel 支持可選鏈操作符然后強(qiáng)制 Webpack 轉(zhuǎn)譯特定依賴,一頓操作發(fā)現(xiàn)沒(méi)啥用,而且問(wèn)題越整越復(fù)雜了。后來(lái)靈機(jī)一動(dòng)想著是不是occt-import-js版本太新了,然后就把occt-import-js卸載裝了一個(gè)更低的版本:

npm install occt-import-js@0.0.20

問(wèn)題完美解決!

加載occt解析器

這一步需要將occt-import-js腳本加載進(jìn)來(lái),這樣才能使用里面的解析方法。官方推薦了兩種方式1、手動(dòng)加載(如果需要自定義解析器能力時(shí)可選);2、使用官方膠水代碼加載。
一開(kāi)始傻傻的跟著資料一步步手動(dòng)加載,發(fā)現(xiàn)問(wèn)題特別多,這一步也是卡我最久的地方,**這里非常不推薦使用手動(dòng)加載,直接用官方的膠水代碼省時(shí)省力還省心。**步驟如下:

  1. 將occt-import-js.js(膠水腳本)和occt-import-js.wasm(核心?。?!WASM 二進(jìn)制文件)復(fù)制到public目錄下。方便后面在代碼中導(dǎo)入。這一步很有必要,因?yàn)閜ublic目錄在Vue項(xiàng)目中是一個(gè)特殊目錄,這個(gè)目錄下的文件在構(gòu)建時(shí)會(huì)被直接復(fù)制到輸出目錄,不會(huì)被Webpack處理。
  2. vue代碼中動(dòng)態(tài)注入script標(biāo)簽,即引入該膠水腳本
const script = document.createElement('script');
script.src = `${process.env.BASE_URL}/occt-import-js.js`;
document.head.appendChild(script);

// 然后使用腳本中的occtimportjs()方法去初始化occt實(shí)例

初始化3D場(chǎng)景

導(dǎo)入Three.js

import * as THREE from 'three'

這一步就沒(méi)啥說(shuō)的,官方文檔里寫(xiě)的很清楚,按照流程走就行,詳見(jiàn)完整代碼。

  1. 創(chuàng)建場(chǎng)景
  2. 初始化相機(jī)
  3. 創(chuàng)建渲染器
  4. 添加光源
  5. 添加軌道控制器

創(chuàng)建3D模型方法

  1. 設(shè)置頂點(diǎn)數(shù)據(jù)
  2. 設(shè)置法線數(shù)據(jù)
  3. 設(shè)置索引
  4. 創(chuàng)建材質(zhì)
  5. 創(chuàng)建網(wǎng)格并添加到場(chǎng)景

到這一步基本上準(zhǔn)備工作就完成了,接下來(lái)就是使用occt實(shí)例解析stp文件內(nèi)容,創(chuàng)建3D模型,然后使用Three.js渲染即可。
這里放出完整代碼供各位參考:

<template>
  <div class="stp-renderer">
    <!-- 渲染容器 -->
    <div ref="canvasContainer" class="canvas-container"></div>
  </div>
</template>

<script>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import api from '@/utils/api'

export default {
  name: 'STPRenderer',
  props: {
    fileId: {  // 從父組件接收文件ID
      type: String,
      required: true
    }
  },
  data() {
    return {
      scene: null,
      camera: null,
      renderer: null,
      controls: null,
      animationId: null,
      occt: null,
    }
  },
  async mounted() {
    console.log('mounted');
    try {
      await this.initOCCT();
      this.initThreeScene();
      if (this.fileId) {
        this.renderModel();
      } 
    } catch (error) {
      console.error(error);
    }
  },
  watch: {
    fileId(newVal) {
      if (newVal) {
        this.renderModel();
      } 
    }
  },

  beforeDestroy() {
    this.cleanupResources()
  },
  methods: {
    async initOCCT() {
      // 動(dòng)態(tài)注入 script 標(biāo)簽
      const script = document.createElement('script');
      script.src = `${process.env.BASE_URL}wasm/occt-import-js.js`;
      document.head.appendChild(script);
      
      // 等待腳本加載完成
      await new Promise((resolve, reject) => {
        script.onload = resolve;
        script.onerror = () => reject(new Error('腳本加載失敗'));
      });
      
      return occtimportjs();
    },
    
    // 初始化Three.js場(chǎng)景
    initThreeScene() {
      console.log('initThreeScene');
      const container = this.$refs.canvasContainer
      
      // 1. 創(chuàng)建場(chǎng)景
      this.scene = new THREE.Scene()
      this.scene.background = new THREE.Color(0xf0f0f0);
      
      
      // 2. 初始化相機(jī)
      this.camera = new THREE.PerspectiveCamera(
        75,
        container.clientWidth / container.clientHeight,
        0.1,
        1000
      )
      this.camera.position.set(10, 10, 10)
      this.camera.lookAt(0, 0, 0)
      
      // 3. 創(chuàng)建渲染器
      this.renderer = new THREE.WebGLRenderer({ antialias: true })
      this.renderer.setSize(container.clientWidth, container.clientHeight)
      this.renderer.setPixelRatio(window.devicePixelRatio)
      container.appendChild(this.renderer.domElement)
      
      // 4. 添加光源
      const ambientLight = new THREE.AmbientLight(0xffffff, 2.0)
      this.scene.add(ambientLight)
      
      const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5)
      directionalLight.position.set(10, 20, 15)
      this.scene.add(directionalLight);

      // 增加左上角光源
      const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.8);
      directionalLight2.position.set(-10, 5, -10); // 左前方向,增加立體感
      this.scene.add(directionalLight2);

      this.scene.add(new THREE.AxesHelper(10));
      const gridHelper = new THREE.GridHelper(50, 20);
      this.scene.add(gridHelper);
      
      // 5. 添加軌道控制器
      this.controls = new OrbitControls(this.camera, this.renderer.domElement)
      this.controls.enableDamping = true
      this.controls.dampingFactor = 0.05
      
      // 6. 窗口大小自適應(yīng)
      window.addEventListener('resize', this.handleResize)
    },

    // 核心渲染流程
    async renderModel() {
      console.log('renderModel');
      let loadingInstance = null;
      
      try {
        // 1. 下載文件
        loadingInstance = this.$loading({
          lock: true,
          text: '正在下載文件...',
          spinner: 'el-icon-loading',
          background: 'rgba(0, 0, 0, 0.7)'
        });
        const { buffer } = await api.downloadSTP(this.fileId);
        console.log(buffer);
        
        // 2. 加載OCCT解析器
        loadingInstance.setText('正在解析模型數(shù)據(jù)...');
        const occt = await occtimportjs();
        console.log(occt)
        
        // 3. 解析STEP文件
        loadingInstance.setText('正在解析模型數(shù)據(jù)...');
        const fileBuffer = new Uint8Array(buffer)
        // console.log(this.occt);
        const result = occt.ReadStepFile(fileBuffer, null)
        
        if (!result.success) {
          throw new Error(`解析失敗: ${result.error || '未知錯(cuò)誤'}`)
        }
        
        // 4. 清除舊模型
        this.clearScene()
        
        // 5. 創(chuàng)建3D模型
        this.createModelFromData(result.meshes)
        
      } catch (err) {
        this.$message.error(`渲染失敗: ${err.message}`);
        this.cleanupResources();
        this.initThreeScene();
      } finally {
        loadingInstance.close();
        this.startAnimationLoop()
      }
    },

    // 創(chuàng)建3D模型
    createModelFromData(meshes) {
      meshes.forEach((meshData, index) => {
        const geometry = new THREE.BufferGeometry()
        
        // 設(shè)置頂點(diǎn)數(shù)據(jù)
        const positionArray = Array.isArray(meshData.attributes.position.array)
        ? new Float32Array(meshData.attributes.position.array) // 普通數(shù)組轉(zhuǎn)TypedArray
        : meshData.attributes.position.array;
        geometry.setAttribute(
          'position',
          new THREE.BufferAttribute(positionArray, 3)
        )
        
        // 設(shè)置法線數(shù)據(jù)
        if (meshData.attributes.normal) {
          const normalArray = Array.isArray(meshData.attributes.normal.array)
          ? new Float32Array(meshData.attributes.normal.array)
          : meshData.attributes.normal.array;
          geometry.setAttribute(
            'normal',
            new THREE.BufferAttribute(normalArray, 3)
          )
        }
        
        // 設(shè)置索引
        const indexArray = meshData.index.array;
        let indexTypedArray;
        
        if (Array.isArray(indexArray)) {
          // 根據(jù)索引數(shù)量選擇合適類(lèi)型
          indexTypedArray = indexArray.length > 65535 
            ? new Uint32Array(indexArray)  // 超過(guò)65535用32位
            : new Uint16Array(indexArray); // 否則用16位
        } else {
          indexTypedArray = indexArray;
        }
        
        // 創(chuàng)建索引BufferAttribute
        geometry.setIndex(
          new THREE.BufferAttribute(indexTypedArray, 1)
        )
        // geometry.setIndex(
        //   new THREE.BufferAttribute(meshData.index.array, 1)
        // )
        
        // 計(jì)算法線(如果缺失)
        if (!meshData.attributes.normal) {
          geometry.computeVertexNormals()
        }
        
        // 創(chuàng)建材質(zhì)
        const material = new THREE.MeshPhongMaterial({
          color: meshData.color 
            ? new THREE.Color(...meshData.color) 
            : 0x42a5f5,
          emissive: 0x333333, // 添加自發(fā)光避免全黑
          emissiveIntensity: 0.3,
          specular: 0x111111,
          shininess: 50,
          side: THREE.DoubleSide
        })
        
        // 創(chuàng)建網(wǎng)格并添加到場(chǎng)景
        const mesh = new THREE.Mesh(geometry, material)
        this.scene.add(mesh)
      })
      
      // 自動(dòng)調(diào)整視角
      this.autoCenterModel()
    },

    // 自動(dòng)居中模型
    autoCenterModel() {
      const bbox = new THREE.Box3().setFromObject(this.scene);
      if (bbox.isEmpty()) { // 檢查包圍盒是否為空
        console.log("模型包圍盒為空,未添加任何物體");
        return;
      }
      const center = bbox.getCenter(new THREE.Vector3())
      const size = bbox.getSize(new THREE.Vector3());
      console.log(size.length());
      
      this.camera.position.copy(center)
      this.camera.position.x += size.length() * 1.2
      this.camera.position.y += size.length() * 0.8
      this.camera.position.z += size.length() * 1.2
      
      this.camera.lookAt(center)
      this.controls.target.copy(center)
      this.controls.update()
    },

    // 動(dòng)畫(huà)循環(huán)
    startAnimationLoop() {
      if (this.animationId) {
        cancelAnimationFrame(this.animationId);
      } 
      
      const animate = () => {
        this.animationId = requestAnimationFrame(animate)
        this.controls.update()
        this.renderer.render(this.scene, this.camera)
      }
      
      animate()
    },

    // 窗口大小調(diào)整
    handleResize() {
      if (!this.camera || !this.renderer) {
        return;
      } 
      
      const container = this.$refs.canvasContainer
      this.camera.aspect = container.clientWidth / container.clientHeight
      this.camera.updateProjectionMatrix()
      this.renderer.setSize(container.clientWidth, container.clientHeight)
    },

    // 清理場(chǎng)景
    clearScene() {
      // 保留光源和相機(jī)
      const preserveObjects = this.scene.children.filter(
        obj => obj instanceof THREE.Light || obj === this.camera
      )
      
      // 銷(xiāo)毀幾何體和材質(zhì)
      this.scene.children.forEach(child => {
        if (child instanceof THREE.Mesh) {
          child.geometry.dispose()
          if (Array.isArray(child.material)) {
            child.material.forEach(mat => mat.dispose())
          } else {
            child.material.dispose()
          }
        }
      })
      
      // 重置場(chǎng)景
      this.scene.children = preserveObjects
    },

    // 釋放資源
    cleanupResources() {
      if (this.animationId) {
        cancelAnimationFrame(this.animationId)
      };
      window.removeEventListener('resize', this.handleResize)
      
      if (this.renderer) {
        const container = this.$refs.canvasContainer;
        if (container.contains(this.renderer.domElement)) {
          container.removeChild(this.renderer.domElement);
        }
        this.renderer.dispose();
        this.renderer.forceContextLoss();
        this.renderer = null;
      }
      
      if (this.controls) {
        this.controls.dispose()
      } 
      this.clearScene()
    },

    // 重置組件
    reset() {
      this.cleanupResources()
      this.initThreeScene()
      this.renderModel()
    }
  }
}
</script>

<style scoped>
.canvas-container {
  width: 100%;
  height: 80vh;
  position: relative;
}

.status-overlay, .error-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  padding: 15px;
  text-align: center;
  background: rgba(19, 18, 18, 0.7);
  color: white;
  font-size: 16px;
}

.error-overlay {
  background: rgba(255, 0, 0, 0.7);
}

.error-overlay button {
  margin-left: 10px;
  padding: 5px 10px;
  background: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
</style>

補(bǔ)充說(shuō)明

  • utils/api.js就是一個(gè)下載文件的公共方法,需要注意的就是返回類(lèi)型一定要是arraybuffer,代碼如下:
import axios from 'axios'

export default {
  /**
   * 下載STP文件
   * @param {string} fileId - 文件唯一標(biāo)識(shí)
   * @returns {Promise<ArrayBuffer>} - 文件二進(jìn)制數(shù)據(jù)
   */
  async downloadSTP(fileId) {
    try {
      const response = await axios.get('http://' + xxx + fileId, {
        responseType: 'arraybuffer',
        onDownloadProgress: (progressEvent) => {
          const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100)
          console.log(`下載進(jìn)度: ${percent}%`)
        }
      })
      
      // 從響應(yīng)頭提取文件名
      const contentDisposition = response.headers['content-disposition']
      const filename = contentDisposition 
        ? contentDisposition.split('filename=')[1].replace(/"/g, '') 
        : 'model.stp'
      
      return {
        buffer: response.data,
        filename
      }
    } catch (error) {
      throw new Error(`文件下載失敗: ${error.message}`)
    }
  }
}

總結(jié) 

到此這篇關(guān)于vue2引入Three.js預(yù)覽3D文件的文章就介紹到這了,更多相關(guān)vue2 Three.js預(yù)覽3D文件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

伊宁县| 洱源县| 池州市| 滦南县| 仪陇县| 五河县| 临邑县| 宿州市| 大渡口区| 贡觉县| 锡林浩特市| 大关县| 阳原县| 黑河市| 和顺县| 石渠县| 惠来县| 玉门市| 天镇县| 固始县| 广东省| 肇源县| 清涧县| 闵行区| 泾川县| 祁连县| 攀枝花市| 芜湖县| 视频| 新安县| 星座| 灵台县| 家居| 游戏| 凤台县| 陇川县| 和田市| 鲁甸县| 会宁县| 开江县| 南皮县|