下面我为你详细讲解 ThreeG + BIM + Vue 的完整使用指南,从安装到实战应用。

📦 环境准备与安装

1. Vue 项目创建和依赖安装

# 创建 Vue 项目(选择 Vue 3)
npm create vue@latest my-bim-project
cd my-bim-project

# 安装 Three.js 和相关依赖
npm install three
npm install @types/three  # 如果使用 TypeScript

# 安装其他可能需要的工具
npm install axios  # 用于请求 BIM 数据
npm install element-plus  # UI 组件库(可选)

2. ThreeG 的引入方式

方式一:CDN 引入(推荐初学者)

<!-- public/index.html -->
<head>
  <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/threeg/dist/threeg.min.js"></script>
</head>

方式二:本地文件引入

<!-- 将 threeg 库文件放在 public/jssdk/ 目录下 -->
<script src="./jssdk/threeg.min.js"></script>

方式三:NPM 安装(如果可用)

npm install threeg  # 如果官方提供了 npm 包

🏗️ Vue 项目结构规划

src/
├── components/
│   ├── BIMViewer.vue          # 主要的BIM查看器组件
│   ├── Toolbar.vue            # 工具栏
│   ├── PropertyPanel.vue      # 属性面板
│   └── Navigation.vue         # 导航控件
├── views/
│   ├── Dashboard.vue          # 主面板
│   └── ModelManagement.vue    # 模型管理
├── utils/
│   ├── bimLoader.js           # BIM加载器
│   └── threeg-helper.js       # ThreeG工具函数
└── assets/
    └── models/                # 本地模型文件

🔧 核心代码实现

1. 基础 BIM 查看器组件

<template>
  <div class="bim-container">
    <!-- 3D视图容器 -->
    <div ref="viewerContainer" class="viewer-container"></div>
    
    <!-- 加载状态 -->
    <div v-if="loading" class="loading-overlay">
      <div class="loading-spinner">加载中...</div>
    </div>
    
    <!-- 工具栏 -->
    <div class="toolbar">
      <button @click="fitToView">适应视图</button>
      <button @click="toggleWireframe">{{ wireframe ? '关闭线框' : '显示线框' }}</button>
      <button @click="measureDistance">测量距离</button>
      <button @click="toggleLabels">{{ showLabels ? '隐藏标签' : '显示标签' }}</button>
    </div>
    
    <!-- 属性面板 -->
    <PropertyPanel 
      v-if="selectedComponent" 
      :properties="selectedComponent.properties"
      @close="selectedComponent = null"
    />
  </div>
</template>

<script>
import { ref, onMounted, onUnmounted } from 'vue'

export default {
  name: 'BIMViewer',
  setup() {
    const viewerContainer = ref(null)
    const loading = ref(true)
    const wireframe = ref(false)
    const showLabels = ref(true)
    const selectedComponent = ref(null)
    
    // ThreeG 相关变量
    let viewer3D = null
    let app = null
    
    // 初始化 ThreeG
    const initThreeG = async () => {
      if (!viewerContainer.value) return
      
      try {
        // 1. 创建 ThreeG 应用配置
        const webAppConfig = new THREEG.D3.Application.WebApplication3DConfig()
        webAppConfig.domElement = viewerContainer.value
        
        // 2. 创建应用实例
        app = new THREEG.D3.Application.WebApplication3D(webAppConfig)
        viewer3D = app.getViewer()
        
        // 3. 基础配置
        viewer3D.setOrbitControls()
        viewer3D.setMaximalRangeofCamera(0.15)
        viewer3D.enableTranslate(true)
        
        // 4. 加载 BIM 模型
        await loadBIMModel()
        
        loading.value = false
      } catch (error) {
        console.error('ThreeG 初始化失败:', error)
        loading.value = false
      }
    }
    
    // 加载 BIM 模型
    const loadBIMModel = () => {
      return new Promise((resolve, reject) => {
        // 配置加载器
        const loaderConfig = new THREEGLoaderConfig()
        loaderConfig.dataEnvType = "Local"
        loaderConfig.sdkPath = "./jssdk"
        loaderConfig.path = "./auth.json"  // 认证文件
        
        // 加载模型
        THREEGLoader.load(loaderConfig, 
          // 成功回调
          (viewMetaData) => {
            if (viewMetaData.viewType === "3DView") {
              viewer3D.addModel(viewMetaData)
              
              // 监听模型添加完成事件
              viewer3D.addEventListener(
                THREEG.D3.Viewer.Viewer3DEvent.ViewAdded,
                () => {
                  console.log('BIM模型加载完成')
                  setupInteractions()
                  resolve()
                }
              )
            }
          },
          // 失败回调
          (error) => {
            console.error('BIM模型加载失败:', error)
            reject(error)
          }
        )
      })
    }
    
    // 设置交互功能
    const setupInteractions = () => {
      // 启用选择功能
      viewer3D.enableSelect()
      
      // 监听选择事件
      viewer3D.addEventListener(
        THREEG.D3.Viewer.Viewer3DEvent.MouseClicked,
        (event) => {
          if (event.objects.length > 0) {
            const selectedObject = event.objects[0]
            // 获取构件属性
            const properties = selectedObject.getProperties()
            selectedComponent.value = {
              object: selectedObject,
              properties: properties
            }
          }
        }
      )
    }
    
    // 工具栏功能
    const fitToView = () => {
      if (viewer3D) {
        viewer3D.fitToView()
      }
    }
    
    const toggleWireframe = () => {
      if (viewer3D) {
        wireframe.value = !wireframe.value
        viewer3D.enableWireframe(wireframe.value)
      }
    }
    
    const measureDistance = () => {
      if (viewer3D) {
        const measurer = new THREEG.D3.Plugins.Measure(viewer3D)
        measurer.enableMeasureDistance()
      }
    }
    
    const toggleLabels = () => {
      showLabels.value = !showLabels.value
      // 这里需要根据实际情况控制标签显示
    }
    
    // 生命周期
    onMounted(() => {
      initThreeG()
    })
    
    onUnmounted(() => {
      // 清理资源
      if (viewer3D) {
        viewer3D.dispose()
      }
    })
    
    return {
      viewerContainer,
      loading,
      wireframe,
      showLabels,
      selectedComponent,
      fitToView,
      toggleWireframe,
      measureDistance,
      toggleLabels
    }
  }
}
</script>

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

.viewer-container {
  width: 100%;
  height: 100%;
}

.loading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
  z-index: 1000;
}

.toolbar {
  position: absolute;
  top: 20px;
  left: 20px;
  z-index: 100;
}

.toolbar button {
  margin: 5px;
  padding: 8px 16px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.toolbar button:hover {
  background: rgba(0, 0, 0, 0.9);
}
</style>

2. 属性面板组件

<template>
  <div class="property-panel">
    <div class="panel-header">
      <h3>构件属性</h3>
      <button @click="$emit('close')" class="close-btn">×</button>
    </div>
    <div class="panel-content">
      <div v-for="(value, key) in properties" :key="key" class="property-item">
        <span class="property-key">{{ key }}:</span>
        <span class="property-value">{{ value }}</span>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'PropertyPanel',
  props: {
    properties: {
      type: Object,
      default: () => ({})
    }
  },
  emits: ['close']
}
</script>

<style scoped>
.property-panel {
  position: absolute;
  top: 20px;
  right: 20px;
  width: 300px;
  background: rgba(255, 255, 255, 0.95);
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  z-index: 100;
}

.panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 16px;
  border-bottom: 1px solid #eee;
}

.close-btn {
  background: none;
  border: none;
  font-size: 20px;
  cursor: pointer;
  color: #666;
}

.panel-content {
  max-height: 400px;
  overflow-y: auto;
  padding: 16px;
}

.property-item {
  display: flex;
  justify-content: space-between;
  margin-bottom: 8px;
  padding-bottom: 8px;
  border-bottom: 1px solid #f0f0f0;
}

.property-key {
  font-weight: bold;
  color: #333;
}

.property-value {
  color: #666;
  text-align: right;
  max-width: 150px;
  word-break: break-all;
}
</style>

🚀 高级功能扩展

1. BIM 模型工具函数

// utils/bim-loader.js
export class BIMLoader {
  constructor(viewer3D) {
    this.viewer3D = viewer3D
    this.extObjMng = new THREEG.D3.Plugins.ExternalObject.ExternalObjectManager(viewer3D)
  }

  // 加载 IFC 模型
  async loadIFCModel(modelPath) {
    return new Promise((resolve, reject) => {
      const ifcLoader = new THREEG.IFCLoader()
      ifcLoader.load(
        modelPath,
        (model) => {
          this.viewer3D.add(model)
          this.viewer3D.fitToView()
          resolve(model)
        },
        (progress) => {
          console.log(`加载进度: ${(progress.loaded / progress.total * 100).toFixed(2)}%`)
        },
        (error) => {
          reject(error)
        }
      )
    })
  }

  // 获取模型树结构
  getModelTree(model) {
    return model.getModelTree()
  }

  // 添加天气效果
  addRainEffect() {
    const rainConfig = new THREEG.D3.Plugins.WeatherEffect.RainConfig()
    rainConfig.viewer = this.viewer3D
    rainConfig.density = 3
    return new THREEG.D3.Plugins.WeatherEffect.Rain(rainConfig)
  }

  // 创建剖切面
  createSectionBox() {
    return new THREEG.D3.Plugins.SectionBox(this.viewer3D)
  }
}

2. 在 Vue 中使用工具函数

<script>
import { BIMLoader } from '@/utils/bim-loader'

export default {
  // ... 其他代码
  methods: {
    async setupBIMTools() {
      this.bimLoader = new BIMLoader(this.viewer3D)
      
      // 加载模型
      await this.bimLoader.loadIFCModel('/models/building.ifc')
      
      // 添加交互功能
      this.setupInteractions()
    },

    async toggleRain() {
      if (!this.rainEffect) {
        this.rainEffect = this.bimLoader.addRainEffect()
      }
      this.rainEffect.enableEffect(!this.isRaining)
      this.isRaining = !this.isRaining
    },

    toggleSection() {
      if (!this.sectionBox) {
        this.sectionBox = this.bimLoader.createSectionBox()
      }
      this.sectionBox.setVisible(!this.sectionVisible)
      this.sectionVisible = !this.sectionVisible
    }
  }
}
</script>

⚙️ 部署配置

Nginx 配置(确保支持 BIM 文件)

server {
    listen 8080;
    server_name your-domain.com;
    root /www/wwwroot/your-bim-project;
    
    # SPA 路由支持
    location / {
        try_files $uri $uri/ /index.html;
    }
    
    # BIM 文件支持
    location ~ \.(ifc|gltf|glb|fbx)$ {
        add_header Content-Type application/octet-stream;
        add_header Access-Control-Allow-Origin *;
        expires 30d;
    }
    
    # 压缩文件
    location ~ \.gz$ {
        add_header Content-Encoding gzip;
        add_header Access-Control-Allow-Origin *;
        expires 30d;
    }
}

🔍 调试技巧

1. 控制台调试

// 在浏览器控制台中调试
window.viewer3D = viewer3D  // 暴露到全局
console.log('Viewer3D 对象:', viewer3D)

2. 错误处理

// 全局错误处理
window.addEventListener('error', (event) => {
  console.error('ThreeG 错误:', event.error)
})

// Promise 错误处理
window.addEventListener('unhandledrejection', (event) => {
  console.error('ThreeG Promise 错误:', event.reason)
})

💡 最佳实践

  1. 组件化设计:将不同功能拆分为独立组件
  2. 错误边界:添加适当的错误处理
  3. 性能优化:大模型使用分块加载
  4. 响应式设计:适配不同屏幕尺寸
  5. 类型安全:使用 TypeScript 获得更好的开发体验

这样你就有了一个完整的 Vue + ThreeG + BIM 开发环境!从基础查看器到高级功能都涵盖了。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐