添加测试信息
This commit is contained in:
159
ARCHITECTURE.md
Normal file
159
ARCHITECTURE.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# Engine3DManager 架构设计说明
|
||||
|
||||
## 🎯 设计目标
|
||||
|
||||
将第三方 3D 引擎封装为管理器组件,并通过**依赖注入**实现解耦。
|
||||
|
||||
## 🏗️ 架构模式
|
||||
|
||||
### 依赖注入 (Dependency Injection)
|
||||
|
||||
`Engine3DManager` 不直接依赖具体的 `createEngine` 实现,而是通过参数接收引擎创建函数。
|
||||
|
||||
```typescript
|
||||
// Engine3DManager 定义工厂函数类型
|
||||
export type Engine3DFactory = (config: {
|
||||
containerId: string;
|
||||
backgroundColor?: number;
|
||||
version?: 'v1' | 'v2';
|
||||
showStats?: boolean;
|
||||
showViewCube?: boolean;
|
||||
}) => any;
|
||||
|
||||
// initialize 方法接受外部传入的创建函数
|
||||
public initialize(createEngine: Engine3DFactory, options?: Engine3DOptions): boolean {
|
||||
this.engine = createEngine({ /* config */ });
|
||||
}
|
||||
```
|
||||
|
||||
### 职责分离
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ BimEngine (总控制器) │
|
||||
│ - 导入 createEngine │
|
||||
│ - 组合各个 Manager │
|
||||
│ - 提供简洁的 API │
|
||||
└──────────────┬──────────────────────────────┘
|
||||
│ 传入 createEngine
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Engine3DManager (3D引擎管理器) │
|
||||
│ - ❌ 不导入 createEngine │
|
||||
│ - ✅ 接受外部传入的创建函数 │
|
||||
│ - 管理引擎生命周期 │
|
||||
│ - 提供统一接口 │
|
||||
└──────────────┬──────────────────────────────┘
|
||||
│ 调用
|
||||
▼
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ createEngine (第三方3D引擎) │
|
||||
│ - bim-engine-sdk.es.js │
|
||||
│ - 被完全封装 │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 💡 优势
|
||||
|
||||
### 1. **解耦 (Decoupling)**
|
||||
- `Engine3DManager` 不依赖具体的 3D 引擎实现
|
||||
- 可以轻松切换不同的 3D 引擎,只需更改 `BimEngine` 中的导入
|
||||
|
||||
### 2. **可测试性 (Testability)**
|
||||
```typescript
|
||||
// 测试时可以注入 mock 引擎
|
||||
const mockCreateEngine = (config) => ({
|
||||
loader: { loadModel: jest.fn() }
|
||||
});
|
||||
|
||||
manager.initialize(mockCreateEngine, options);
|
||||
```
|
||||
|
||||
### 3. **灵活性 (Flexibility)**
|
||||
```typescript
|
||||
// 可以在运行时决定使用哪个引擎
|
||||
import { createEngineV1 } from './engine-v1';
|
||||
import { createEngineV2 } from './engine-v2';
|
||||
|
||||
const engineFactory = useV2 ? createEngineV2 : createEngineV1;
|
||||
manager.initialize(engineFactory, options);
|
||||
```
|
||||
|
||||
### 4. **单一职责 (Single Responsibility)**
|
||||
- **BimEngine**: 负责集成和协调
|
||||
- **Engine3DManager**: 负责管理 3D 引擎的生命周期
|
||||
- **createEngine**: 负责创建引擎实例
|
||||
|
||||
## 📊 调用流程
|
||||
|
||||
```typescript
|
||||
// 1. 用户创建 BimEngine
|
||||
const engine = new BimEngine('container');
|
||||
|
||||
// 2. 用户调用 initEngine3D
|
||||
engine.initEngine3D({ backgroundColor: 0x333333 });
|
||||
|
||||
// 3. BimEngine 内部执行
|
||||
// ↓
|
||||
// 导入 createEngine
|
||||
// ↓
|
||||
// 调用 engine3D.initialize(createEngine, options)
|
||||
// ↓
|
||||
// Engine3DManager 使用传入的 createEngine 创建引擎实例
|
||||
```
|
||||
|
||||
## 🔄 与其他 Manager 的对比
|
||||
|
||||
### DialogManager / ToolbarManager
|
||||
```typescript
|
||||
// 自包含,不依赖外部函数
|
||||
constructor(container: HTMLElement) {
|
||||
this.init(); // 直接初始化
|
||||
}
|
||||
```
|
||||
|
||||
### Engine3DManager
|
||||
```typescript
|
||||
// 依赖注入,接受外部函数
|
||||
constructor(container: HTMLElement) {
|
||||
this.createContainer(); // 只创建容器
|
||||
}
|
||||
|
||||
initialize(createEngine: Engine3DFactory, options) {
|
||||
this.engine = createEngine(config); // 使用注入的函数
|
||||
}
|
||||
```
|
||||
|
||||
## 🎓 设计模式
|
||||
|
||||
采用的设计模式:
|
||||
1. **工厂模式 (Factory Pattern)** - `Engine3DFactory` 类型
|
||||
2. **依赖注入 (Dependency Injection)** - `createEngine` 通过参数传入
|
||||
3. **延迟初始化 (Lazy Initialization)** - 构造时不创建引擎
|
||||
|
||||
## 🚀 未来扩展
|
||||
|
||||
### 支持多引擎
|
||||
```typescript
|
||||
engine.initEngine3D(createThreeJSEngine, options);
|
||||
// 或
|
||||
engine.initEngine3D(createBabylonEngine, options);
|
||||
```
|
||||
|
||||
### 条件加载
|
||||
```typescript
|
||||
// 根据设备性能选择引擎
|
||||
const factory = isHighPerformance
|
||||
? createAdvancedEngine
|
||||
: createLightweightEngine;
|
||||
|
||||
engine.initEngine3D(factory, options);
|
||||
```
|
||||
|
||||
## 📝 总结
|
||||
|
||||
通过依赖注入模式,`Engine3DManager` 实现了:
|
||||
- ✅ 与具体 3D 引擎解耦
|
||||
- ✅ 易于测试和维护
|
||||
- ✅ 支持灵活切换引擎
|
||||
- ✅ 保持职责单一清晰
|
||||
207
USAGE_EXAMPLE.md
Normal file
207
USAGE_EXAMPLE.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# BimEngine 使用示例
|
||||
|
||||
## 基础使用
|
||||
|
||||
### 1. 创建 BimEngine 实例
|
||||
|
||||
```typescript
|
||||
import { BimEngine } from './src/bim-engine';
|
||||
|
||||
// 方式 1: 使用 DOM 元素 ID
|
||||
const engine = new BimEngine('my-container', {
|
||||
locale: 'zh-CN',
|
||||
theme: 'dark'
|
||||
});
|
||||
|
||||
// 方式 2: 使用 DOM 元素引用
|
||||
const containerEl = document.getElementById('my-container');
|
||||
const engine2 = new BimEngine(containerEl, {
|
||||
locale: 'en-US',
|
||||
theme: 'light'
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 初始化 3D 引擎(延迟初始化)
|
||||
|
||||
**重要**: 3D 引擎采用延迟初始化模式,需要用户主动调用 `initEngine3D()` 方法。
|
||||
|
||||
```typescript
|
||||
// 先创建 BimEngine 实例
|
||||
const engine = new BimEngine('my-container');
|
||||
|
||||
// 稍后在需要时初始化 3D 引擎
|
||||
const success = engine.initEngine3D({
|
||||
backgroundColor: 0x333333,
|
||||
version: 'v1',
|
||||
showStats: true,
|
||||
showViewCube: true
|
||||
});
|
||||
|
||||
if (success) {
|
||||
console.log('3D 引擎初始化成功!');
|
||||
}
|
||||
|
||||
// 检查引擎是否已初始化
|
||||
if (engine.isEngine3DInitialized()) {
|
||||
// 可以加载模型了
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 加载 3D 模型
|
||||
|
||||
```typescript
|
||||
// 确保先初始化 3D 引擎
|
||||
engine.initEngine3D({
|
||||
backgroundColor: 0x1a1a1a,
|
||||
showStats: true
|
||||
});
|
||||
|
||||
// 加载模型 - 使用默认参数
|
||||
engine.loadModel('/model/building.glb');
|
||||
|
||||
// 加载模型 - 自定义位置、旋转、缩放
|
||||
engine.loadModel('/model/gujianzhu.glb', {
|
||||
position: [10, -5, 0],
|
||||
rotation: [0, Math.PI / 4, 0],
|
||||
scale: [2, 2, 2]
|
||||
});
|
||||
```
|
||||
|
||||
### 3. 动态切换主题和语言
|
||||
|
||||
```typescript
|
||||
// 切换主题
|
||||
engine.setTheme('dark');
|
||||
engine.setTheme('light');
|
||||
|
||||
// 切换语言
|
||||
engine.setLocale('zh-CN');
|
||||
engine.setLocale('en-US');
|
||||
|
||||
// 设置自定义主题
|
||||
engine.setCustomTheme({
|
||||
background: '#1a1a1a',
|
||||
textPrimary: '#ffffff',
|
||||
textSecondary: '#999999',
|
||||
// ... 其他主题配置
|
||||
});
|
||||
```
|
||||
|
||||
### 4. 访问 3D 引擎实例
|
||||
|
||||
```typescript
|
||||
// 直接访问底层 3D 引擎
|
||||
if (engine.engine3D) {
|
||||
// 调用第三方 3D 引擎的其他方法
|
||||
engine.engine3D.someOtherMethod();
|
||||
}
|
||||
```
|
||||
|
||||
## 在 HTML 中使用
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
#bim-container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="bim-container"></div>
|
||||
|
||||
<script type="module">
|
||||
import { BimEngine } from './dist/bim-engine-sdk.es.js';
|
||||
|
||||
const engine = new BimEngine('bim-container', {
|
||||
engine3D: {
|
||||
backgroundColor: 0x1a1a1a,
|
||||
showStats: true,
|
||||
showViewCube: true
|
||||
}
|
||||
});
|
||||
|
||||
// 加载模型
|
||||
engine.loadModel('/models/building.glb', {
|
||||
position: [0, 0, 0],
|
||||
scale: [1.5, 1.5, 1.5]
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## 在 Vue 3 中使用
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div ref="containerRef" class="bim-container"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { BimEngine } from 'bim-engine-sdk';
|
||||
|
||||
const containerRef = ref<HTMLElement>();
|
||||
let engine: BimEngine | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
if (containerRef.value) {
|
||||
engine = new BimEngine(containerRef.value, {
|
||||
locale: 'zh-CN',
|
||||
theme: 'dark',
|
||||
engine3D: {
|
||||
backgroundColor: 0x202020,
|
||||
showStats: true,
|
||||
showViewCube: true
|
||||
}
|
||||
});
|
||||
|
||||
// 加载模型
|
||||
engine.loadModel('/models/building.glb');
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
engine?.destroy();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.bim-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## 配置选项说明
|
||||
|
||||
### Engine3DOptions
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `backgroundColor` | `number` | `0x333333` | 3D 场景背景色(十六进制颜色值) |
|
||||
| `version` | `'v1' \| 'v2'` | `'v1'` | WebGL 版本 |
|
||||
| `showStats` | `boolean` | `false` | 是否显示性能统计 |
|
||||
| `showViewCube` | `boolean` | `true` | 是否显示视图立方体 |
|
||||
|
||||
### LoadModel Options
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `position` | `[number, number, number]` | `[0, 0, 0]` | 模型位置 [x, y, z] |
|
||||
| `rotation` | `[number, number, number]` | `[0, 0, 0]` | 模型旋转(弧度)[x, y, z] |
|
||||
| `scale` | `[number, number, number]` | `[1, 1, 1]` | 模型缩放 [x, y, z] |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **容器尺寸**: 确保容器元素有明确的宽高,否则 3D 引擎无法正常渲染
|
||||
2. **模型路径**: 模型文件路径需要是可访问的 URL 或相对路径
|
||||
3. **销毁实例**: 组件卸载时记得调用 `engine.destroy()` 释放资源
|
||||
4. **z-index 层级**: UI 组件会自动叠加在 3D 场景之上(z-index: 100+)
|
||||
6
demo-vue/.gitignore
vendored
Normal file
6
demo-vue/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
dist/
|
||||
dist-ssr/
|
||||
*.local
|
||||
.DS_Store
|
||||
|
||||
91
demo-vue/README.md
Normal file
91
demo-vue/README.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# BIM Engine SDK Vue3 Demo
|
||||
|
||||
这是一个使用 Vue3 + TypeScript 的演示项目,用于验证 BIM Engine SDK 在 Vue 环境下的兼容性。
|
||||
|
||||
## 前置要求
|
||||
|
||||
1. 确保父项目已经构建完成:
|
||||
```bash
|
||||
cd ..
|
||||
npm run build
|
||||
```
|
||||
|
||||
2. 安装依赖:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. 复制 SDK 文件到 public 目录:
|
||||
```bash
|
||||
npm run copy-sdk
|
||||
```
|
||||
或者手动复制:
|
||||
```bash
|
||||
mkdir -p public/lib
|
||||
cp ../dist/bim-engine-sdk.umd.js public/lib/
|
||||
cp ../dist/bim-engine-sdk.umd.js.map public/lib/
|
||||
```
|
||||
|
||||
## 运行
|
||||
|
||||
### 开发模式
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
服务器会在 `http://localhost:8081` 启动,并自动打开浏览器。
|
||||
|
||||
### 构建
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
构建后的文件会在 `dist` 目录中。
|
||||
|
||||
### 预览构建结果
|
||||
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
demo-vue/
|
||||
├── src/
|
||||
│ ├── App.vue # 主组件(包含所有功能)
|
||||
│ ├── main.ts # Vue3 入口文件
|
||||
│ ├── style.css # 全局样式
|
||||
│ └── vite-env.d.ts # TypeScript 类型声明
|
||||
├── public/
|
||||
│ ├── lib/ # SDK 文件目录
|
||||
│ │ └── bim-engine-sdk.umd.js
|
||||
│ └── model/ # 3D 模型文件
|
||||
│ └── gujianzhu.glb
|
||||
├── index.html # HTML 入口
|
||||
├── package.json # 项目配置
|
||||
├── vite.config.ts # Vite 配置
|
||||
├── tsconfig.json # TypeScript 配置
|
||||
└── README.md # 说明文档
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
|
||||
1. 点击 "初始化引擎" 按钮初始化 3D 引擎
|
||||
2. 点击 "加载模型" 按钮加载 3D 模型
|
||||
3. 其他功能按钮可以测试 SDK 的各种功能
|
||||
|
||||
## 技术栈
|
||||
|
||||
- Vue 3.4+ (Composition API)
|
||||
- TypeScript 5.9+
|
||||
- Vite 7.2+
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 确保父项目的 `dist` 目录中有构建好的 SDK 文件
|
||||
- 模型文件路径是相对于 `public` 目录的
|
||||
- 使用 HTTP 服务器运行(不能直接打开 HTML 文件)以避免 CORS 问题
|
||||
|
||||
16
demo-vue/index.html
Normal file
16
demo-vue/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BIM Engine SDK Vue3 Demo</title>
|
||||
<!-- 从 public/lib 目录加载 SDK 文件 -->
|
||||
<script src="/lib/bim-engine-sdk.umd.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
1455
demo-vue/package-lock.json
generated
Normal file
1455
demo-vue/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
demo-vue/package.json
Normal file
22
demo-vue/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "bim-engine-demo-vue",
|
||||
"version": "1.0.0",
|
||||
"description": "BIM Engine SDK Vue3 Demo",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"copy-sdk": "mkdir -p public/lib && cp ../dist/bim-engine-sdk.umd.js public/lib/ && cp ../dist/bim-engine-sdk.umd.js.map public/lib/ 2>/dev/null || true"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.4.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.4",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^6.0.0",
|
||||
"vue-tsc": "^2.0.6"
|
||||
}
|
||||
}
|
||||
|
||||
4894
demo-vue/public/lib/bim-engine-sdk.umd.js
Normal file
4894
demo-vue/public/lib/bim-engine-sdk.umd.js
Normal file
File diff suppressed because one or more lines are too long
1
demo-vue/public/lib/bim-engine-sdk.umd.js.map
Normal file
1
demo-vue/public/lib/bim-engine-sdk.umd.js.map
Normal file
File diff suppressed because one or more lines are too long
BIN
demo-vue/public/model/gujianzhu.glb
Normal file
BIN
demo-vue/public/model/gujianzhu.glb
Normal file
Binary file not shown.
396
demo-vue/src/App.vue
Normal file
396
demo-vue/src/App.vue
Normal file
@@ -0,0 +1,396 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 左侧控制面板 -->
|
||||
<aside class="sidebar">
|
||||
<h1>BIM SDK Demo (Vue3)</h1>
|
||||
|
||||
<!-- 1. 语言设置 -->
|
||||
<div class="control-group">
|
||||
<h2>🌍 语言 (Language)</h2>
|
||||
<div class="btn-container">
|
||||
<button class="primary" @click="setLang('zh-CN')">中文</button>
|
||||
<button class="primary" @click="setLang('en-US')">English</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 弹窗测试 -->
|
||||
<div class="control-group">
|
||||
<h2>🪟 弹窗 (Dialog)</h2>
|
||||
<div class="btn-container">
|
||||
<button @click="openTestDialog">测试弹窗</button>
|
||||
<button @click="openInfoDialog">信息弹窗</button>
|
||||
<button @click="openRedDialog">警告弹窗</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 工具栏操作 -->
|
||||
<div class="control-group">
|
||||
<h2>🛠️ 工具栏 (Toolbar)</h2>
|
||||
<div class="btn-container">
|
||||
<button @click="toggleToolbar">显隐工具栏</button>
|
||||
<button @click="toggleLabel">显隐标签</button>
|
||||
<button @click="toggleLocationBtn">显隐定位按钮</button>
|
||||
</div>
|
||||
<div class="btn-container" style="margin-top: 8px;">
|
||||
<button @click="addCustomGroup">加组</button>
|
||||
<button @click="addCustomButton">加按钮</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. 样式主题 -->
|
||||
<div class="control-group">
|
||||
<h2>🎨 样式 (Theme)</h2>
|
||||
<div class="btn-container">
|
||||
<button @click="setTheme('dark')">深色 (Dark)</button>
|
||||
<button @click="setTheme('light')">浅色 (Light)</button>
|
||||
<button @click="setCustomTheme">自定义 (Red)</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 5. 3D 引擎 -->
|
||||
<div class="control-group">
|
||||
<h2>🎮 3D 引擎 (Engine)</h2>
|
||||
<div class="btn-container">
|
||||
<button class="primary" @click="initEngine">初始化引擎</button>
|
||||
<button class="primary" @click="loadModel">加载模型</button>
|
||||
</div>
|
||||
<div style="margin-top: 10px; font-size: 0.85rem; color: #666;">
|
||||
<div>状态: <span :style="{ color: engineStatusColor }">{{ engineStatus }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 右侧主区域 -->
|
||||
<main class="main-content">
|
||||
<div ref="appContainer" id="app-container"></div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
// 响应式状态
|
||||
const appContainer = ref<HTMLElement | null>(null);
|
||||
const engine = ref<any>(null);
|
||||
const isToolbarVisible = ref(true);
|
||||
const isLabelVisible = ref(true);
|
||||
const isLocationVisible = ref(true);
|
||||
const customGroupAdded = ref(false);
|
||||
const engineStatus = ref('未初始化');
|
||||
const engineStatusColor = ref('#666');
|
||||
|
||||
// 初始化引擎
|
||||
onMounted(() => {
|
||||
if (window.LyzBimEngineSDK) {
|
||||
const Engine = window.LyzBimEngineSDK.BimEngine;
|
||||
try {
|
||||
if (appContainer.value) {
|
||||
engine.value = new Engine(appContainer.value, { locale: 'zh-CN' });
|
||||
initEngine();
|
||||
console.log('Engine initialized:', engine.value);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Init failed:', err);
|
||||
}
|
||||
} else {
|
||||
console.error('SDK not found');
|
||||
}
|
||||
});
|
||||
|
||||
// 清理资源
|
||||
onUnmounted(() => {
|
||||
if (engine.value) {
|
||||
engine.value.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
// 更新引擎状态
|
||||
const updateEngineStatus = (status: string) => {
|
||||
engineStatus.value = status;
|
||||
if (status === '已初始化') {
|
||||
engineStatusColor.value = '#28a745';
|
||||
} else if (status === '初始化失败' || status === '初始化错误') {
|
||||
engineStatusColor.value = '#dc3545';
|
||||
} else {
|
||||
engineStatusColor.value = '#666';
|
||||
}
|
||||
};
|
||||
|
||||
// --- 语言设置 ---
|
||||
const setLang = (lang: 'zh-CN' | 'en-US') => {
|
||||
if (engine.value) {
|
||||
engine.value.setLocale(lang);
|
||||
}
|
||||
};
|
||||
|
||||
// --- 弹窗测试 ---
|
||||
const openTestDialog = () => {
|
||||
if (!engine.value || !engine.value.dialog) return;
|
||||
engine.value.dialog.create({
|
||||
title: 'dialog.testTitle',
|
||||
width: 350,
|
||||
height: 200,
|
||||
position: 'center',
|
||||
draggable: true,
|
||||
resizable: true
|
||||
});
|
||||
};
|
||||
|
||||
const openInfoDialog = () => {
|
||||
if (!engine.value || !engine.value.dialog) return;
|
||||
engine.value.dialog.showInfoDialog();
|
||||
};
|
||||
|
||||
const openRedDialog = () => {
|
||||
if (!engine.value || !engine.value.dialog) return;
|
||||
engine.value.dialog.create({
|
||||
title: 'Alert',
|
||||
content: '<div style="color: #ffcccc;">Critical Warning!</div>',
|
||||
width: 300,
|
||||
height: 150,
|
||||
backgroundColor: 'rgba(100, 0, 0, 0.95)',
|
||||
headerBackgroundColor: '#cc0000',
|
||||
titleColor: '#ffffff',
|
||||
borderColor: '#ff6666',
|
||||
position: { x: 50, y: 50 }
|
||||
});
|
||||
};
|
||||
|
||||
// --- 工具栏操作 ---
|
||||
const toggleToolbar = () => {
|
||||
if (!engine.value || !engine.value.toolbar) return;
|
||||
isToolbarVisible.value = !isToolbarVisible.value;
|
||||
engine.value.toolbar.setVisible(isToolbarVisible.value);
|
||||
};
|
||||
|
||||
const toggleLabel = () => {
|
||||
if (!engine.value || !engine.value.toolbar) return;
|
||||
isLabelVisible.value = !isLabelVisible.value;
|
||||
engine.value.toolbar.setShowLabel(isLabelVisible.value);
|
||||
};
|
||||
|
||||
const toggleLocationBtn = () => {
|
||||
if (!engine.value || !engine.value.toolbar) return;
|
||||
isLocationVisible.value = !isLocationVisible.value;
|
||||
engine.value.toolbar.setButtonVisibility('location', isLocationVisible.value);
|
||||
};
|
||||
|
||||
const addCustomGroup = () => {
|
||||
if (!engine.value || !engine.value.toolbar) return;
|
||||
if (customGroupAdded.value) {
|
||||
alert('已添加过');
|
||||
return;
|
||||
}
|
||||
engine.value.toolbar.addGroup('custom-group', 'group-1');
|
||||
customGroupAdded.value = true;
|
||||
};
|
||||
|
||||
const addCustomButton = () => {
|
||||
if (!engine.value || !engine.value.toolbar) return;
|
||||
if (!customGroupAdded.value) {
|
||||
alert('Please add custom group first / 请先添加自定义组');
|
||||
return;
|
||||
}
|
||||
const btnId = 'custom-btn-' + Date.now();
|
||||
engine.value.toolbar.addButton({
|
||||
id: btnId,
|
||||
groupId: 'custom-group',
|
||||
type: 'button',
|
||||
label: 'New',
|
||||
icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><path d="M8 14s1.5 2 4 2 4-2 4-2"></path><line x1="9" y1="9" x2="9.01" y2="9"></line><line x1="15" y1="9" x2="15.01" y2="9"></line></svg>',
|
||||
onClick: (btn: any) => {
|
||||
alert('Clicked: ' + btn.label);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// --- 主题操作 ---
|
||||
const setTheme = (themeName: 'dark' | 'light') => {
|
||||
if (engine.value) {
|
||||
engine.value.setTheme(themeName);
|
||||
}
|
||||
};
|
||||
|
||||
const setCustomTheme = () => {
|
||||
if (!engine.value) return;
|
||||
// 定义一个红色主题
|
||||
engine.value.setCustomTheme({
|
||||
name: 'red-alert',
|
||||
primary: '#d32f2f',
|
||||
primaryHover: '#b71c1c',
|
||||
background: '#ffebee', // 浅红背景
|
||||
panelBackground: 'rgba(255, 255, 255, 0.9)',
|
||||
textPrimary: '#b71c1c',
|
||||
textSecondary: '#e57373',
|
||||
border: '#ffcdd2',
|
||||
icon: '#d32f2f',
|
||||
iconActive: '#b71c1c',
|
||||
componentBackground: 'rgba(255, 205, 210, 0.3)',
|
||||
componentHover: 'rgba(255, 205, 210, 0.8)',
|
||||
componentActive: '#e57373'
|
||||
});
|
||||
};
|
||||
|
||||
// --- 3D 引擎操作 ---
|
||||
/**
|
||||
* 初始化 3D 引擎
|
||||
*/
|
||||
const initEngine = () => {
|
||||
if (!engine.value || !engine.value.engine) {
|
||||
alert('引擎未创建,请先等待页面加载完成');
|
||||
return;
|
||||
}
|
||||
|
||||
if (engine.value.engine.isInitialized()) {
|
||||
alert('3D 引擎已经初始化过了');
|
||||
updateEngineStatus('已初始化');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 初始化引擎,使用默认配置
|
||||
const success = engine.value.initEngine({
|
||||
backgroundColor: 0x333333, // 深色背景
|
||||
version: 'v1', // WebGL 版本
|
||||
showStats: true, // 显示性能统计
|
||||
showViewCube: true // 显示视图立方体
|
||||
});
|
||||
|
||||
if (success) {
|
||||
updateEngineStatus('已初始化');
|
||||
console.log('✅ 3D 引擎初始化成功');
|
||||
} else {
|
||||
updateEngineStatus('初始化失败');
|
||||
console.error('❌ 3D 引擎初始化失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
updateEngineStatus('初始化错误');
|
||||
console.error('❌ 3D 引擎初始化错误:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 加载 3D 模型
|
||||
*/
|
||||
const loadModel = () => {
|
||||
if (!engine.value || !engine.value.engine) {
|
||||
alert('引擎未创建,请先等待页面加载完成');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!engine.value.engine.isInitialized()) {
|
||||
alert('请先初始化 3D 引擎!');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 加载模型文件(从 model 目录)
|
||||
const modelUrl = '/model/gujianzhu.glb';
|
||||
|
||||
engine.value.engine.loadModel(modelUrl, {
|
||||
position: [0, 0, 0], // 初始位置
|
||||
rotation: [0, 0, 0], // 初始旋转
|
||||
scale: [1, 1, 1] // 初始缩放
|
||||
});
|
||||
|
||||
console.log('✅ 模型加载请求已发送:', modelUrl);
|
||||
} catch (error: any) {
|
||||
console.error('❌ 模型加载错误:', error);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 左侧侧边栏:控制面板 */
|
||||
.sidebar {
|
||||
width: 320px;
|
||||
background: white;
|
||||
border-right: 1px solid #e0e0e0;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.05);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.sidebar h1 {
|
||||
font-size: 1.4rem;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
background: #f9f9f9;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.control-group h2 {
|
||||
font-size: 1rem;
|
||||
color: #555;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 6px 12px;
|
||||
font-size: 0.9rem;
|
||||
border: 1px solid #ddd;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #f0f0f0;
|
||||
border-color: #ccc;
|
||||
}
|
||||
|
||||
button.primary {
|
||||
background: #0078d4;
|
||||
color: white;
|
||||
border-color: #0063b1;
|
||||
}
|
||||
|
||||
button.primary:hover {
|
||||
background: #0063b1;
|
||||
}
|
||||
|
||||
/* 右侧主内容区:引擎展示 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
background: #eef2f5;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#app-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
6
demo-vue/src/main.ts
Normal file
6
demo-vue/src/main.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
import './style.css';
|
||||
|
||||
createApp(App).mount('#app');
|
||||
|
||||
20
demo-vue/src/style.css
Normal file
20
demo-vue/src/style.css
Normal file
@@ -0,0 +1,20 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
19
demo-vue/src/vite-env.d.ts
vendored
Normal file
19
demo-vue/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
||||
// 声明全局 SDK 类型
|
||||
declare global {
|
||||
interface Window {
|
||||
LyzBimEngineSDK: {
|
||||
BimEngine: any;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
|
||||
32
demo-vue/tsconfig.json
Normal file
32
demo-vue/tsconfig.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "preserve",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
/* Path alias */
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
||||
11
demo-vue/tsconfig.node.json
Normal file
11
demo-vue/tsconfig.node.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
|
||||
17
demo-vue/vite.config.ts
Normal file
17
demo-vue/vite.config.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import { resolve } from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
port: 8081,
|
||||
open: true
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src')
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
6
demo/.gitignore
vendored
Normal file
6
demo/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
dist/
|
||||
lib/
|
||||
.DS_Store
|
||||
*.log
|
||||
|
||||
77
demo/README.md
Normal file
77
demo/README.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# BIM Engine SDK Demo
|
||||
|
||||
这是一个独立的演示项目,展示如何使用 BIM Engine SDK。
|
||||
|
||||
## 前置要求
|
||||
|
||||
1. 确保父项目已经构建完成:
|
||||
```bash
|
||||
cd ..
|
||||
npm run build
|
||||
```
|
||||
|
||||
2. 安装依赖:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. 复制 SDK 文件到 demo 目录:
|
||||
```bash
|
||||
npm run copy-sdk
|
||||
```
|
||||
或者手动复制:
|
||||
```bash
|
||||
mkdir -p lib
|
||||
cp ../dist/bim-engine-sdk.umd.js lib/
|
||||
```
|
||||
|
||||
## 运行
|
||||
|
||||
### 开发模式
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
服务器会在 `http://localhost:8080` 启动,并自动打开浏览器。
|
||||
|
||||
### 构建
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
构建后的文件会在 `dist` 目录中。
|
||||
|
||||
### 预览构建结果
|
||||
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
demo/
|
||||
├── index.html # 主页面
|
||||
├── model/ # 3D 模型文件
|
||||
│ └── gujianzhu.glb
|
||||
├── lib/ # SDK 文件目录
|
||||
│ └── bim-engine-sdk.umd.js
|
||||
├── package.json # 项目配置
|
||||
├── vite.config.js # Vite 配置
|
||||
└── README.md # 说明文档
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
|
||||
1. 点击 "初始化引擎" 按钮初始化 3D 引擎
|
||||
2. 点击 "加载模型" 按钮加载 3D 模型
|
||||
3. 其他功能按钮可以测试 SDK 的各种功能
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 确保父项目的 `dist` 目录中有构建好的 SDK 文件
|
||||
- 模型文件路径是相对于 `index.html` 的
|
||||
- 使用 HTTP 服务器运行(不能直接打开 HTML 文件)以避免 CORS 问题
|
||||
|
||||
BIN
demo/model/gujianzhu.glb
Normal file
BIN
demo/model/gujianzhu.glb
Normal file
Binary file not shown.
1059
demo/package-lock.json
generated
Normal file
1059
demo/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
demo/package.json
Normal file
16
demo/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "bim-engine-demo",
|
||||
"version": "1.0.0",
|
||||
"description": "BIM Engine SDK Demo",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"copy-sdk": "mkdir -p lib && cp ../dist/bim-engine-sdk.umd.js lib/ && cp ../dist/bim-engine-sdk.umd.js.map lib/ 2>/dev/null || true"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^7.2.6"
|
||||
}
|
||||
}
|
||||
|
||||
22
demo/vite.config.js
Normal file
22
demo/vite.config.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import { resolve } from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
root: '.',
|
||||
server: {
|
||||
port: 8080,
|
||||
open: true,
|
||||
// 允许访问父目录的文件(用于加载 SDK)
|
||||
fs: {
|
||||
allow: ['..']
|
||||
}
|
||||
},
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
rollupOptions: {
|
||||
input: {
|
||||
main: resolve(__dirname, 'index.html')
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
89
dev.sh
Executable file
89
dev.sh
Executable file
@@ -0,0 +1,89 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 颜色输出
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}🚀 开始自动化构建和启动流程...${NC}\n"
|
||||
|
||||
# 1. 构建 SDK
|
||||
echo -e "${YELLOW}📦 步骤 1/3: 构建 SDK...${NC}"
|
||||
npm run build
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ SDK 构建失败!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✅ SDK 构建成功${NC}\n"
|
||||
|
||||
# 2. 复制 SDK 文件到 demo 目录
|
||||
echo -e "${YELLOW}📋 步骤 2/3: 复制 SDK 文件到 demo 目录...${NC}"
|
||||
|
||||
# 复制到 demo/lib
|
||||
echo " 复制到 demo/lib..."
|
||||
cd demo
|
||||
npm run copy-sdk
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ 复制到 demo/lib 失败!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
cd ..
|
||||
|
||||
# 复制到 demo-vue/public/lib
|
||||
echo " 复制到 demo-vue/public/lib..."
|
||||
cd demo-vue
|
||||
npm run copy-sdk
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ 复制到 demo-vue/public/lib 失败!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
cd ..
|
||||
|
||||
echo -e "${GREEN}✅ SDK 文件复制完成${NC}\n"
|
||||
|
||||
# 3. 启动开发服务器
|
||||
echo -e "${YELLOW}🌐 步骤 3/3: 启动开发服务器...${NC}\n"
|
||||
|
||||
# 清理函数:当脚本退出时停止所有后台进程
|
||||
cleanup() {
|
||||
echo -e "\n${YELLOW}🛑 正在停止开发服务器...${NC}"
|
||||
kill $DEMO_PID $DEMO_VUE_PID 2>/dev/null
|
||||
exit 0
|
||||
}
|
||||
|
||||
# 注册清理函数
|
||||
trap cleanup SIGINT SIGTERM
|
||||
|
||||
# 启动 demo (端口 8080)
|
||||
echo -e "${GREEN}启动 demo (http://localhost:8080)...${NC}"
|
||||
cd demo
|
||||
npm run dev &
|
||||
DEMO_PID=$!
|
||||
cd ..
|
||||
|
||||
# 等待一下确保服务器启动
|
||||
sleep 3
|
||||
|
||||
# 启动 demo-vue (端口 8081)
|
||||
echo -e "${GREEN}启动 demo-vue (http://localhost:8081)...${NC}"
|
||||
cd demo-vue
|
||||
npm run dev &
|
||||
DEMO_VUE_PID=$!
|
||||
cd ..
|
||||
|
||||
# 等待一下确保服务器启动
|
||||
sleep 3
|
||||
|
||||
echo -e "\n${GREEN}✅ 所有服务已启动!${NC}"
|
||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${GREEN}📱 Demo (HTML): http://localhost:8080${NC}"
|
||||
echo -e "${GREEN}📱 Demo (Vue3): http://localhost:8081${NC}"
|
||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${YELLOW}💡 按 Ctrl+C 停止所有服务器${NC}\n"
|
||||
|
||||
# 等待用户中断
|
||||
wait
|
||||
|
||||
32381
src/bim-engine-sdk.es.js
Normal file
32381
src/bim-engine-sdk.es.js
Normal file
File diff suppressed because one or more lines are too long
199
src/components/engine/index.ts
Normal file
199
src/components/engine/index.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
import type { ThemeConfig } from '../../themes/types';
|
||||
import { IBimComponent } from '../../types/component';
|
||||
import { themeManager } from '../../services/theme';
|
||||
import type { EngineOptions, ModelLoadOptions } from './types';
|
||||
// 导入第三方 SDK 的 createEngine 函数
|
||||
import { createEngine as createEngineSDK } from '../../bim-engine-sdk.es.js';
|
||||
|
||||
// 重新导出类型,方便外部引用
|
||||
export type { EngineOptions, ModelLoadOptions };
|
||||
|
||||
/**
|
||||
* 3D 引擎组件
|
||||
* 负责创建和管理第三方 3D 引擎实例
|
||||
*/
|
||||
export class Engine implements IBimComponent {
|
||||
/** 第三方 3D 引擎实例 */
|
||||
private engine: any = null;
|
||||
/** 引擎挂载的容器元素 */
|
||||
private container: HTMLElement;
|
||||
/** 引擎容器 ID(用于传递给 createEngine) */
|
||||
private containerId: string;
|
||||
/** 引擎配置选项(不包含 container) */
|
||||
private options: Omit<EngineOptions, 'container'>;
|
||||
/** 是否已初始化 */
|
||||
private _isInitialized = false;
|
||||
/** 是否已销毁 */
|
||||
private _isDestroyed = false;
|
||||
/** 主题订阅取消函数 */
|
||||
private unsubscribeTheme: (() => void) | null = null;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param options 3D 引擎配置选项
|
||||
*/
|
||||
constructor(options: EngineOptions) {
|
||||
// 解析容器元素
|
||||
this.container = options.container;
|
||||
// 如果容器没有 id,生成一个唯一的 id
|
||||
if (!this.container.id) {
|
||||
this.containerId = `engine-container-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
this.container.id = this.containerId;
|
||||
} else {
|
||||
this.containerId = this.container.id;
|
||||
}
|
||||
|
||||
// 保存配置选项(设置默认值)
|
||||
this.options = {
|
||||
backgroundColor: options.backgroundColor ?? 0x1a1a1a, // 默认深色背景
|
||||
version: options.version ?? 'v1', // 默认使用 v1 版本
|
||||
showStats: options.showStats ?? false, // 默认不显示统计
|
||||
showViewCube: options.showViewCube ?? true, // 默认显示视图立方体
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化组件 (接口实现)
|
||||
* 创建 div 容器并初始化引擎
|
||||
*/
|
||||
public init(): void {
|
||||
if (this._isInitialized) {
|
||||
console.warn('[Engine] Engine already initialized.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._isDestroyed) {
|
||||
console.error('[Engine] Cannot initialize destroyed engine.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建引擎配置对象
|
||||
const engineConfig = {
|
||||
containerId: this.containerId,
|
||||
backgroundColor: this.options.backgroundColor,
|
||||
version: this.options.version,
|
||||
showStats: this.options.showStats,
|
||||
showViewCube: this.options.showViewCube,
|
||||
};
|
||||
|
||||
// 调用引擎创建函数创建引擎实例
|
||||
// 将 options 中的配置复制给 createEngine
|
||||
this.engine = createEngineSDK(engineConfig);
|
||||
|
||||
if (!this.engine) {
|
||||
throw new Error('Failed to create engine instance');
|
||||
}
|
||||
|
||||
// 标记为已初始化
|
||||
this._isInitialized = true;
|
||||
|
||||
// 订阅主题变化
|
||||
this.unsubscribeTheme = themeManager.subscribe((theme) => {
|
||||
this.setTheme(theme);
|
||||
});
|
||||
|
||||
// 应用当前主题
|
||||
this.setTheme(themeManager.getTheme());
|
||||
} catch (error) {
|
||||
console.error('[Engine] Failed to initialize engine:', error);
|
||||
this._isInitialized = false;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置主题 (接口实现)
|
||||
* 根据主题调整 3D 引擎的视觉效果(如背景色)
|
||||
* @param theme 全局主题配置
|
||||
*/
|
||||
public setTheme(theme: ThemeConfig): void {
|
||||
if (!this._isInitialized || !this.engine) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据主题调整背景色
|
||||
// dark 主题使用深色背景,light 主题使用浅色背景
|
||||
let backgroundColor: number;
|
||||
if (theme.name === 'dark') {
|
||||
backgroundColor = 0x1a1a1a; // 深色背景
|
||||
} else if (theme.name === 'light') {
|
||||
backgroundColor = 0xf5f5f5; // 浅色背景
|
||||
} else {
|
||||
// 自定义主题,尝试从主题配置中获取背景色
|
||||
// 如果主题配置中有 backgroundColor,使用它;否则使用默认值
|
||||
backgroundColor = this.options.backgroundColor ?? 0x1a1a1a;
|
||||
}
|
||||
|
||||
// 如果引擎支持设置背景色,则更新
|
||||
if (this.engine && typeof this.engine.setBackgroundColor === 'function') {
|
||||
this.engine.setBackgroundColor(backgroundColor);
|
||||
} else if (this.engine && this.engine.scene) {
|
||||
// 如果引擎有 scene 对象,尝试设置背景色
|
||||
if (this.engine.scene.background) {
|
||||
this.engine.scene.background.setHex(backgroundColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置语言 (接口实现)
|
||||
*/
|
||||
public setLocales(): void {
|
||||
// 3D 引擎组件暂时不需要本地化
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已初始化
|
||||
*/
|
||||
public isInitialized(): boolean {
|
||||
return this._isInitialized;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 加载 3D 模型
|
||||
* @param url 模型文件 URL
|
||||
* @param options 加载选项(位置、旋转、缩放)
|
||||
*/
|
||||
public loadModel(url: string, options?: ModelLoadOptions): void {
|
||||
if (!this._isInitialized || !this.engine) {
|
||||
console.error('[Engine] Engine not initialized. Please call init() first.');
|
||||
return;
|
||||
}
|
||||
if (!url) {
|
||||
console.error('[Engine] Model URL is required.');
|
||||
return;
|
||||
}
|
||||
this.engine.loader.loadModel(url, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始 3D 引擎实例
|
||||
*/
|
||||
public getEngine(): any {
|
||||
return this.engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁组件 (接口实现)
|
||||
* 清理资源、取消订阅、销毁引擎实例
|
||||
*/
|
||||
public destroy(): void {
|
||||
if (this._isDestroyed) {
|
||||
return;
|
||||
}
|
||||
// 取消主题订阅
|
||||
if (this.unsubscribeTheme) {
|
||||
this.unsubscribeTheme();
|
||||
this.unsubscribeTheme = null;
|
||||
}
|
||||
// 清理容器(可选,根据需求决定是否清空容器)
|
||||
this.container.innerHTML = '';
|
||||
// 更新状态
|
||||
this._isDestroyed = true;
|
||||
this._isInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
31
src/components/engine/types.ts
Normal file
31
src/components/engine/types.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 引擎配置选项
|
||||
* 用于 Engine 组件的初始化
|
||||
*/
|
||||
export interface EngineOptions {
|
||||
/** 容器元素 */
|
||||
container: HTMLElement;
|
||||
/** 背景颜色(十六进制数字,如 0x333333) */
|
||||
backgroundColor?: number;
|
||||
/** WebGL 版本 */
|
||||
version?: 'v1' | 'v2';
|
||||
/** 是否显示性能统计 */
|
||||
showStats?: boolean;
|
||||
/** 是否显示视图立方体 */
|
||||
showViewCube?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型加载选项
|
||||
* 用于配置模型的位置、旋转和缩放
|
||||
*/
|
||||
export interface ModelLoadOptions {
|
||||
/** 模型初始位置 [x, y, z] */
|
||||
position?: [number, number, number];
|
||||
/** 模型初始旋转 [x, y, z](弧度) */
|
||||
rotation?: [number, number, number];
|
||||
/** 模型初始缩放 [x, y, z] */
|
||||
scale?: [number, number, number];
|
||||
/** 模型 ID(可选,如果不提供则自动生成) */
|
||||
id?: string;
|
||||
}
|
||||
98
src/managers/engine-manager.ts
Normal file
98
src/managers/engine-manager.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { Engine, type EngineOptions, type ModelLoadOptions } from '../components/engine';
|
||||
|
||||
/**
|
||||
* 3D 引擎管理器
|
||||
* 负责连接 Engine 组件和 BimEngine,向外部暴露简化的 API
|
||||
* 采用延迟初始化模式,用户需主动调用 initialize() 方法
|
||||
*/
|
||||
export class EngineManager {
|
||||
/** 3D 引擎挂载的父容器 */
|
||||
private container: HTMLElement;
|
||||
/** 3D 引擎组件实例 */
|
||||
private engine: Engine | null = null;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param container 3D 引擎挂载的目标容器
|
||||
*/
|
||||
constructor(container: HTMLElement) {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 3D 引擎
|
||||
* @param options 引擎配置选项(可选,如果不提供则使用默认配置)
|
||||
* @returns 是否初始化成功
|
||||
*/
|
||||
public initialize(options?: Omit<EngineOptions, 'container'>): boolean {
|
||||
// 如果已经初始化,先销毁旧的实例
|
||||
if (this.engine && this.engine.isInitialized()) {
|
||||
console.warn('[EngineManager] 3D Engine already initialized. Destroying old instance...');
|
||||
this.engine.destroy();
|
||||
this.engine = null;
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建 Engine 组件实例
|
||||
// options 中的配置会自动复制给 createEngine 使用
|
||||
this.engine = new Engine({
|
||||
container: this.container,
|
||||
...options, // 合并配置选项
|
||||
});
|
||||
|
||||
// 调用组件的 init 方法初始化引擎
|
||||
this.engine.init();
|
||||
|
||||
return this.engine.isInitialized();
|
||||
} catch (error) {
|
||||
console.error('[EngineManager] Failed to initialize 3D engine:', error);
|
||||
this.engine = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 3D 引擎是否已初始化
|
||||
*/
|
||||
public isInitialized(): boolean {
|
||||
return this.engine !== null && this.engine.isInitialized();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载 3D 模型
|
||||
* @param url 模型文件 URL
|
||||
* @param options 加载选项(位置、旋转、缩放)
|
||||
*/
|
||||
public loadModel(url: string, options?: ModelLoadOptions): void {
|
||||
if (!this.engine || !this.engine.isInitialized()) {
|
||||
console.error('[EngineManager] 3D Engine not initialized. Please call initialize() first.');
|
||||
return;
|
||||
}
|
||||
this.engine.loadModel(url, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始 3D 引擎实例
|
||||
* 用于直接调用第三方引擎的其他 API
|
||||
*/
|
||||
public getEngine(): any {
|
||||
if (!this.engine) {
|
||||
console.warn('[EngineManager] 3D Engine not initialized.');
|
||||
return null;
|
||||
}
|
||||
return this.engine.getEngine();
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁 3D 引擎实例
|
||||
*/
|
||||
public destroy(): void {
|
||||
if (this.engine) {
|
||||
this.engine.destroy();
|
||||
this.engine = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user