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

665 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 |
| **底层引擎** | 实际功能实现 |
遵循这个分层架构,可以保持代码的清晰性和可维护性。