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

20 KiB
Raw Blame 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

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: () => {
                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

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

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)

// 剖切盒相关 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
底层引擎 实际功能实现

遵循这个分层架构,可以保持代码的清晰性和可维护性。