- Update method references: activateSectionAxis → activeSection - Update call chains for all clipping modes (x/y/z/box/face) - Document new unified API: engine.clipping.active(mode) - Mark deprecated methods: fitSectionBoxToModel, resetSectionBox - Update underlying API: updateClippingValue instead of setboxPercent
21 KiB
Engine API 接口对接指南
本文档详细说明 iflow-engine SDK 中 Toolbar 按钮如何调用底层 3D 引擎 API 的完整调用链。
目录
架构概览
┌─────────────────────────────────────────────────────────────────────┐
│ 用户交互层 │
│ 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
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
export class SectionBoxDialogManager extends BaseDialogManager {
private panel: SectionBoxPanel | null = null;
// 创建对话框内容,绑定 Panel 回调
protected createContent(): HTMLElement {
this.panel = new SectionBoxPanel({
onFitToModel: () => {
console.log('[SectionBoxDialogManager] Fit to model not supported');
},
onReset: () => {
console.log('[SectionBoxDialogManager] Reset not supported');
},
onRangeChange: (range) => {
this.registry.engine3d?.setSectionBoxRange(range);
}
});
this.panel.init();
return this.panel.element;
}
// 对话框创建后,激活剖切盒
protected onDialogCreated(): void {
this.registry.engine3d?.activeSection('box'); // ← 调用 EngineManager 统一 API
this.dialog?.fitHeight(false);
}
// 对话框销毁前,停用剖切
protected onBeforeDestroy(): void {
this.registry.engine3d?.deactivateSection(); // ← 统一停用方法
// ... 清理
}
// 对话框关闭时,取消工具栏按钮激活
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
export class EngineManager extends BaseManager {
private engineInstance: Engine | null = null;
// 激活剖切(统一入口)
public activeSection(mode: 'x' | 'y' | 'z' | 'box' | 'face'): void {
if (!this.engineInstance) {
console.warn('[EngineManager] 3D Engine not initialized.');
return;
}
this.engineInstance.activeSection(mode); // ← 调用 Engine 组件
}
// 获取当前剖切模式
public getCurrentSectionMode(): 'x' | 'y' | 'z' | 'box' | 'face' | null {
if (!this.engineInstance) {
return null;
}
return this.engineInstance.getCurrentSectionMode();
}
// 停用剖切(统一出口)
public deactivateSection(): void {
if (!this.engineInstance) {
return;
}
this.engineInstance.deactivateSection();
}
// 设置剖切盒范围
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
export class Engine implements IBimComponent {
private engine: any = null; // 底层引擎实例
private currentSectionMode: 'x' | 'y' | 'z' | 'box' | 'face' | null = null; // 当前剖切模式
// 激活剖切(统一入口)
public activeSection(mode: 'x' | 'y' | 'z' | 'box' | 'face'): 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)
// 剖切盒相关 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
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
// 添加导入
const { createXxButton } = await import('./buttons/xx');
// 添加按钮
this.addButton(createXxButton());
Step 3: 创建 DialogManager
文件: src/managers/xx-dialog-manager.ts
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
// 添加类型导入
import type { XxDialogManager } from '../managers/xx-dialog-manager';
// 添加属性
public xxFeature: XxDialogManager | null = null;
Step 5: 在 BimEngine 中初始化
文件: src/bim-engine.ts
// 添加导入
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
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
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 组件中维护状态标记,在方法开头检查:
public activateSectionBox(): void {
// ...
if (this.isSectionBoxActive) {
console.log('[Engine] Section box already active, skipping.');
return; // 幂等:已激活则跳过
}
// ...
}
Q2: 如何处理底层 API 不支持的功能?
在 DialogManager 的回调中输出日志说明:
onReverseToggle: (isReversed) => {
// 底层暂不支持反向功能
console.log('[SectionBoxDialogManager] 反向切换(底层暂不支持):', isReversed);
}
Q3: 如何进行数据格式转换?
在 Engine 组件中进行,不要在 DialogManager 或 EngineManager 中:
// 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 单例访问:
// 在 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 |
| 底层引擎 | 实际功能实现 |
遵循这个分层架构,可以保持代码的清晰性和可维护性。