Files
bim_engine/docs/引擎API对接.md

665 lines
20 KiB
Markdown
Raw Normal View History

# Engine API 接口对接指南
本文档详细说明 iflow-engine SDK 中 Toolbar 按钮如何调用底层 3D 引擎 API 的完整调用链。
---
## 目录
1. [架构概览](#架构概览)
2. [调用链层级](#调用链层级)
3. [完整调用链示例:剖切盒](#完整调用链示例剖切盒)
4. [各层职责说明](#各层职责说明)
5. [新增功能对接步骤](#新增功能对接步骤)
6. [数据流向](#数据流向)
---
## 架构概览
```
┌─────────────────────────────────────────────────────────────────────┐
│ 用户交互层 │
│ Toolbar Button (src/components/button-group/toolbar/buttons/*) │
└────────────────────────────────┬────────────────────────────────────┘
│ onClick → registry.xxxManager.show()
┌─────────────────────────────────────────────────────────────────────┐
│ 对话框管理层 (DialogManager) │
│ src/managers/*-dialog-manager.ts │
│ 继承自 BaseDialogManager管理 UI 面板生命周期 │
└────────────────────────────────┬────────────────────────────────────┘
│ Panel 回调 → registry.engine3d.xxx()
┌─────────────────────────────────────────────────────────────────────┐
│ 引擎管理层 (EngineManager) │
│ src/managers/engine-manager.ts │
│ 封装 Engine 组件,提供统一 API │
└────────────────────────────────┬────────────────────────────────────┘
│ 调用 this.engineInstance.xxx()
┌─────────────────────────────────────────────────────────────────────┐
│ 引擎组件层 (Engine Component) │
│ src/components/engine/index.ts │
│ 封装底层 3D 引擎,处理状态管理和数据转换 │
└────────────────────────────────┬────────────────────────────────────┘
│ 调用 this.engine.xxx.xxx()
┌─────────────────────────────────────────────────────────────────────┐
│ 底层 3D 引擎 (iflow-engine-base) │
│ 第三方 SDK通过 createEngine() 创建实例 │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 调用链层级
| 层级 | 文件位置 | 职责 |
|------|----------|------|
| **L1: Toolbar Button** | `src/components/button-group/toolbar/buttons/` | 定义按钮配置,处理点击事件 |
| **L2: DialogManager** | `src/managers/*-dialog-manager.ts` | 管理对话框/面板 UI绑定回调 |
| **L3: EngineManager** | `src/managers/engine-manager.ts` | 封装引擎组件,提供公共 API |
| **L4: Engine Component** | `src/components/engine/index.ts` | 封装底层引擎,处理状态和转换 |
| **L5: 底层引擎** | `iflow-engine-base` (npm 包) | 实际 3D 渲染和功能实现 |
---
## 完整调用链示例:剖切盒
以剖切盒Section Box功能为例展示完整的调用链
### 1. Toolbar Button 定义
**文件**: `src/components/button-group/toolbar/buttons/section/section-box/index.ts`
```typescript
export const createSectionBoxButton = (): ButtonConfig => {
return {
id: 'section-box',
groupId: 'group-1',
parentId: 'section', // 父菜单 ID
type: 'button',
keepActive: true, // 保持激活状态
exclusive: true, // 互斥(同组只能激活一个)
label: 'toolbar.sectionBox', // 国际化 key
icon: getIcon('剖切盒'),
onClick: (button) => {
const registry = ManagerRegistry.getInstance();
if (button.isActive) {
registry.sectionBox?.show(); // ← 调用 DialogManager
} else {
registry.sectionBox?.hide();
}
}
};
};
```
**要点**:
- 通过 `ManagerRegistry.getInstance()` 获取全局管理器实例
- `button.isActive` 表示当前激活状态
- 调用 `registry.sectionBox.show()` 打开对话框
### 2. DialogManager 处理
**文件**: `src/managers/section-box-dialog-manager.ts`
```typescript
export class SectionBoxDialogManager extends BaseDialogManager {
private panel: SectionBoxPanel | null = null;
// 创建对话框内容,绑定 Panel 回调
protected createContent(): HTMLElement {
this.panel = new SectionBoxPanel({
onFitToModel: () => {
this.registry.engine3d?.fitSectionBoxToModel(); // ← 调用 EngineManager
},
onReset: () => {
this.registry.engine3d?.resetSectionBox();
},
onRangeChange: (range) => {
this.registry.engine3d?.setSectionBoxRange(range);
}
});
this.panel.init();
return this.panel.element;
}
// 对话框创建后,激活剖切盒
protected onDialogCreated(): void {
this.registry.engine3d?.activateSectionBox(); // ← 调用 EngineManager
this.dialog?.fitHeight(false);
}
// 对话框销毁前,停用剖切盒
protected onBeforeDestroy(): void {
this.registry.engine3d?.deactivateSectionBox();
// ... 清理
}
// 对话框关闭时,取消工具栏按钮激活
protected onDialogClose(): void {
this.registry.toolbar?.setBtnActive('section-box', false);
}
}
```
**要点**:
- 继承 `BaseDialogManager`,自动获得 `show()/hide()/toggle()` 方法
- 通过 `this.registry.engine3d` 访问 EngineManager
- Panel 的回调函数中调用 EngineManager 方法
- 生命周期钩子:`onDialogCreated` / `onBeforeDestroy` / `onDialogClose`
### 3. EngineManager 封装
**文件**: `src/managers/engine-manager.ts`
```typescript
export class EngineManager extends BaseManager {
private engineInstance: Engine | null = null;
// 激活剖切盒
public activateSectionBox(): void {
if (!this.engineInstance) {
console.warn('[EngineManager] 3D Engine not initialized.');
return;
}
this.engineInstance.activateSectionBox(); // ← 调用 Engine 组件
}
// 设置剖切盒范围
public setSectionBoxRange(range: SectionBoxRange): void {
if (!this.engineInstance) {
console.warn('[EngineManager] 3D Engine not initialized.');
return;
}
this.engineInstance.setSectionBoxRange(range);
}
// ... 其他方法
}
```
**要点**:
- 每个方法都要检查 `engineInstance` 是否存在
- 方法签名与 Engine 组件一致,起到代理作用
- 负责错误处理和日志输出
### 4. Engine 组件实现
**文件**: `src/components/engine/index.ts`
```typescript
export class Engine implements IBimComponent {
private engine: any = null; // 底层引擎实例
private isSectionBoxActive: boolean = false; // 状态标记
private sectionBoxFullBounds: any = null; // 缓存的包围盒
// 激活剖切盒
public activateSectionBox(): void {
if (!this._isInitialized || !this.engine) {
console.error('[Engine] Cannot activate section box: engine not initialized.');
return;
}
if (!this.engine.clipping?.sectionBox) {
console.error('[Engine] Section box module not available.');
return;
}
if (this.isSectionBoxActive) {
return; // 幂等操作
}
// 保存模型包围盒(用于百分比计算)
this.sectionBoxFullBounds = this.engine.octreeBox?.getBoundingBox()?.clone();
// 调用底层 API
this.engine.clipping.sectionBox.active(); // ← 底层引擎 API
this.isSectionBoxActive = true;
}
// 设置剖切盒范围(百分比 → 坐标转换)
public setSectionBoxRange(range: SectionBoxRange): void {
if (!this.sectionBoxFullBounds) {
console.error('[Engine] Cannot set section box range: full bounds not available.');
return;
}
const full = this.sectionBoxFullBounds;
// 百分比转实际坐标
const toCoord = (percent: number, min: number, max: number): number => {
return min + (max - min) * (percent / 100);
};
const xyz = {
minX: toCoord(range.x.min, full.min.x, full.max.x),
maxX: toCoord(range.x.max, full.min.x, full.max.x),
minY: toCoord(range.y.min, full.min.y, full.max.y),
maxY: toCoord(range.y.max, full.min.y, full.max.y),
minZ: toCoord(range.z.min, full.min.z, full.max.z),
maxZ: toCoord(range.z.max, full.min.z, full.max.z),
};
// 调用底层 API
this.engine.clipping.sectionBox.setboxXyz(xyz); // ← 底层引擎 API
}
}
```
**要点**:
- 维护功能状态(`isSectionBoxActive`
- 缓存必要数据(`sectionBoxFullBounds`
- 进行数据转换(百分比 → 坐标)
- 调用底层引擎的实际 API
### 5. 底层引擎 API
**来源**: `iflow-engine-base` (第三方 SDK)
```typescript
// 剖切盒相关 API
engine.clipping.sectionBox.active() // 激活剖切盒
engine.clipping.sectionBox.disActive() // 停用剖切盒
engine.clipping.sectionBox.getboxXyz() // 获取范围 { minX, maxX, minY, maxY, minZ, maxZ }
engine.clipping.sectionBox.setboxXyz(xyz) // 设置范围
engine.clipping.sectionBox.setBox(box) // 设置 THREE.Box3 格式范围
engine.clipping.sectionBox.reverseBox() // 反向剖切(当前为空实现)
// 获取模型包围盒
engine.octreeBox.getBoundingBox() // 返回 THREE.Box3
```
---
## 各层职责说明
### L1: Toolbar Button
| 职责 | 说明 |
|------|------|
| 定义按钮配置 | `id`, `label`, `icon`, `groupId`, `parentId` 等 |
| 处理点击事件 | 在 `onClick` 中调用 DialogManager 的 `show()`/`hide()` |
| 控制激活状态 | `keepActive`, `exclusive` 等属性控制按钮行为 |
### L2: DialogManager
| 职责 | 说明 |
|------|------|
| 管理对话框生命周期 | `show()` / `hide()` / `toggle()` |
| 创建 UI 面板 | 在 `createContent()` 中实例化 Panel 组件 |
| 绑定回调函数 | 将 Panel 的事件回调连接到 EngineManager |
| 处理生命周期钩子 | `onDialogCreated` / `onBeforeDestroy` / `onDialogClose` |
| 同步工具栏状态 | 关闭时调用 `toolbar.setBtnActive(id, false)` |
### L3: EngineManager
| 职责 | 说明 |
|------|------|
| 代理 Engine 组件 | 提供统一的公共 API |
| 检查初始化状态 | 每个方法都检查 `engineInstance` 是否存在 |
| 错误处理 | 输出警告日志 |
| 暴露给 Registry | 通过 `registry.engine3d` 访问 |
### L4: Engine Component
| 职责 | 说明 |
|------|------|
| 封装底层引擎 | 隔离第三方 SDK 的具体实现 |
| 状态管理 | 维护 `isSectionBoxActive` 等状态标记 |
| 数据转换 | UI 层数据格式 ↔ 底层引擎数据格式 |
| 缓存数据 | 保存 `sectionBoxFullBounds` 等中间数据 |
| 幂等操作 | 防止重复激活/停用 |
### L5: 底层引擎
| 职责 | 说明 |
|------|------|
| 3D 渲染 | WebGL/Three.js 渲染 |
| 功能实现 | 测量、剖切、漫游等实际功能 |
| 场景管理 | 模型加载、相机控制等 |
---
## 新增功能对接步骤
以新增一个 "XX 功能" 为例:
### Step 1: 创建 Toolbar Button
**文件**: `src/components/button-group/toolbar/buttons/xx/index.ts`
```typescript
import type { ButtonConfig } from '../../../index.type';
import { getIcon } from '../../../../../utils/icon-manager';
import { ManagerRegistry } from '../../../../../core/manager-registry';
export const createXxButton = (): ButtonConfig => {
return {
id: 'xx-feature',
groupId: 'group-1',
type: 'button',
keepActive: true,
label: 'toolbar.xxFeature',
icon: getIcon('XX图标'),
onClick: (button) => {
const registry = ManagerRegistry.getInstance();
if (button.isActive) {
registry.xxFeature?.show();
} else {
registry.xxFeature?.hide();
}
}
};
};
```
### Step 2: 注册按钮到 Toolbar
**文件**: `src/components/button-group/toolbar/index.ts`
```typescript
// 添加导入
const { createXxButton } = await import('./buttons/xx');
// 添加按钮
this.addButton(createXxButton());
```
### Step 3: 创建 DialogManager
**文件**: `src/managers/xx-dialog-manager.ts`
```typescript
import { BaseDialogManager } from '../core/base-dialog-manager';
import { XxPanel } from '../components/xx-panel';
export class XxDialogManager extends BaseDialogManager {
private panel: XxPanel | null = null;
protected get dialogId(): string {
return 'xx-dialog';
}
protected get dialogTitle(): string {
return 'xxFeature.dialogTitle';
}
protected createContent(): HTMLElement {
this.panel = new XxPanel({
onSomeAction: () => {
this.registry.engine3d?.doSomething();
}
});
this.panel.init();
return this.panel.element;
}
protected onDialogCreated(): void {
this.registry.engine3d?.activateXx();
}
protected onBeforeDestroy(): void {
this.registry.engine3d?.deactivateXx();
this.panel?.destroy();
this.panel = null;
}
protected onDialogClose(): void {
this.registry.toolbar?.setBtnActive('xx-feature', false);
}
}
```
### Step 4: 注册到 ManagerRegistry
**文件**: `src/core/manager-registry.ts`
```typescript
// 添加类型导入
import type { XxDialogManager } from '../managers/xx-dialog-manager';
// 添加属性
public xxFeature: XxDialogManager | null = null;
```
### Step 5: 在 BimEngine 中初始化
**文件**: `src/bim-engine.ts`
```typescript
// 添加导入
import { XxDialogManager } from './managers/xx-dialog-manager';
// 添加属性
public xxFeature: XxDialogManager | null = null;
// 在 init() 中初始化
this.xxFeature = new XxDialogManager();
this.registry.xxFeature = this.xxFeature;
// 在 destroy() 中销毁
this.xxFeature?.destroy();
```
### Step 6: 在 EngineManager 中添加方法
**文件**: `src/managers/engine-manager.ts`
```typescript
public activateXx(): void {
if (!this.engineInstance) {
console.warn('[EngineManager] 3D Engine not initialized.');
return;
}
this.engineInstance.activateXx();
}
public deactivateXx(): void {
if (!this.engineInstance) {
return;
}
this.engineInstance.deactivateXx();
}
public doSomething(): void {
if (!this.engineInstance) {
console.warn('[EngineManager] 3D Engine not initialized.');
return;
}
this.engineInstance.doSomething();
}
```
### Step 7: 在 Engine 组件中实现
**文件**: `src/components/engine/index.ts`
```typescript
private isXxActive: boolean = false;
public activateXx(): void {
if (!this._isInitialized || !this.engine) {
console.error('[Engine] Cannot activate XX: engine not initialized.');
return;
}
if (this.isXxActive) {
return;
}
// 调用底层 API
this.engine.xxModule.active();
this.isXxActive = true;
}
public deactivateXx(): void {
if (!this._isInitialized || !this.engine?.xxModule) {
return;
}
if (!this.isXxActive) {
return;
}
this.engine.xxModule.disActive();
this.isXxActive = false;
}
public doSomething(): void {
if (!this._isInitialized || !this.engine?.xxModule) {
return;
}
this.engine.xxModule.doSomething();
}
```
---
## 数据流向
### 用户操作 → 底层引擎
```
用户点击按钮
Button.onClick(button)
registry.xxxManager.show()
BaseDialogManager.show()
createContent() → 创建 Panel
onDialogCreated() → registry.engine3d.activateXxx()
EngineManager.activateXxx()
Engine.activateXxx()
this.engine.xxxModule.active() ← 底层引擎 API
```
### UI 面板操作 → 底层引擎
```
用户操作 Panel拖动滑块、点击按钮等
Panel 回调触发 onXxxChange(data)
DialogManager 中的回调registry.engine3d.setXxx(data)
EngineManager.setXxx(data)
Engine.setXxx(data)
数据转换(如百分比 → 坐标)
this.engine.xxxModule.setXxx(convertedData) ← 底层引擎 API
```
### 关闭对话框
```
用户点击关闭按钮 / 再次点击工具栏按钮
BimDialog.onClose() / Button.onClick()
BaseDialogManager.hide() / registry.xxxManager.hide()
destroyDialog()
onBeforeDestroy() → registry.engine3d.deactivateXxx()
EngineManager.deactivateXxx()
Engine.deactivateXxx()
this.engine.xxxModule.disActive() ← 底层引擎 API
onDialogClose() → registry.toolbar.setBtnActive('xxx', false)
```
---
## 常见问题
### Q1: 如何确保激活/停用的幂等性?
在 Engine 组件中维护状态标记,在方法开头检查:
```typescript
public activateSectionBox(): void {
// ...
if (this.isSectionBoxActive) {
console.log('[Engine] Section box already active, skipping.');
return; // 幂等:已激活则跳过
}
// ...
}
```
### Q2: 如何处理底层 API 不支持的功能?
在 DialogManager 的回调中输出日志说明:
```typescript
onReverseToggle: (isReversed) => {
// 底层暂不支持反向功能
console.log('[SectionBoxDialogManager] 反向切换(底层暂不支持):', isReversed);
}
```
### Q3: 如何进行数据格式转换?
在 Engine 组件中进行,不要在 DialogManager 或 EngineManager 中:
```typescript
// Engine 组件中
public setSectionBoxRange(range: SectionBoxRange): void {
// UI 层:百分比 (0-100)
// 底层:实际坐标
const toCoord = (percent: number, min: number, max: number): number => {
return min + (max - min) * (percent / 100);
};
const xyz = {
minX: toCoord(range.x.min, full.min.x, full.max.x),
// ...
};
this.engine.clipping.sectionBox.setboxXyz(xyz);
}
```
### Q4: 如何访问其他 Manager
通过 `ManagerRegistry` 单例访问:
```typescript
// 在 Button 中
const registry = ManagerRegistry.getInstance();
registry.sectionBox?.show();
// 在 DialogManager 中(继承自 BaseManager自动获得 this.registry
this.registry.engine3d?.activateSectionBox();
this.registry.toolbar?.setBtnActive('section-box', false);
```
---
## 总结
| 层级 | 关注点 |
|------|--------|
| **Button** | 按钮配置、点击事件、调用 DialogManager |
| **DialogManager** | UI 生命周期、Panel 回调绑定、调用 EngineManager |
| **EngineManager** | API 代理、初始化检查、错误处理 |
| **Engine** | 状态管理、数据转换、调用底层 API |
| **底层引擎** | 实际功能实现 |
遵循这个分层架构,可以保持代码的清晰性和可维护性。