添加测试信息

This commit is contained in:
yuding
2025-12-04 18:41:11 +08:00
parent 8a727c4485
commit 244891ceec
28 changed files with 41330 additions and 0 deletions

159
ARCHITECTURE.md Normal file
View 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
View 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
View File

@@ -0,0 +1,6 @@
node_modules/
dist/
dist-ssr/
*.local
.DS_Store

91
demo-vue/README.md Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

22
demo-vue/package.json Normal file
View 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"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

396
demo-vue/src/App.vue Normal file
View 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
View 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
View 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
View 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
View 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" }]
}

View 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
View 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
View File

@@ -0,0 +1,6 @@
node_modules/
dist/
lib/
.DS_Store
*.log

77
demo/README.md Normal file
View 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

Binary file not shown.

1059
demo/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

16
demo/package.json Normal file
View 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
View 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
View 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

File diff suppressed because one or more lines are too long

View 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;
}
}

View 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;
}

View 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;
}
}
}