Files
bim_engine/docs/API调用链.md

36 KiB
Raw Blame History

BIM Engine SDK - API 调用链文档

本文档详细记录 BIM Engine SDK 中各功能模块的完整调用链,从用户交互到底层 3D 引擎 API。


目录

第一章:工具栏 (Toolbar)

  1. 首页 (Home)
  2. 框选放大 (Zoom Box)
  3. 测量 (Measure)
  4. 剖切菜单 (Section)
  5. 漫游 (Walk)
  6. 地图 (Map)
  7. 构件详情 (Property)
  8. 设置 (Setting)
  9. 信息 (Info)
  10. 全屏 (Fullscreen)

第二章:右键菜单 (Context Menu)

  1. 构件详情 (Component Detail)
  2. 显示全部 (Show All)
  3. 信息 (Info)
  4. 首页 (Home)

第三章:构件交互 (Component Interaction)

  1. 构件选中 (Component Selection)
  2. 取消选中 (Deselection)

第一章:工具栏 (Toolbar)


目录

  1. 首页 (Home)
  2. 框选放大 (Zoom Box)
  3. 测量 (Measure)
  4. 剖切菜单 (Section)
  5. 漫游 (Walk)
  6. 地图 (Map)
  7. 构件详情 (Property)
  8. 设置 (Setting)
  9. 信息 (Info)
  10. 全屏 (Fullscreen)

1. 首页 (Home)

按钮 ID: home
功能: 相机回到初始位置(主视角)
保持激活: 否

调用链

用户点击 [首页] 按钮
    │
    ▼
Button onClick (buttons/home/index.ts)
    │   registry.engine3d?.CameraGoHome()
    ▼
EngineManager.CameraGoHome() (managers/engine-manager.ts)
    │   this.engineInstance.CameraGoHome()
    ▼
Engine.CameraGoHome() (components/engine/index.ts)
    │   this.engine.viewCube.CameraGoHome()
    ▼
底层引擎 API: engine.viewCube.CameraGoHome()

代码摘要

按钮定义 (buttons/home/index.ts):

onClick: (button) => {
    const registry = ManagerRegistry.getInstance();
    registry.engine3d?.CameraGoHome();
}

EngineManager (managers/engine-manager.ts):

public CameraGoHome(): void {
    this.engineInstance.CameraGoHome();
}

Engine 组件 (components/engine/index.ts):

public CameraGoHome() {
    this.engine.viewCube.CameraGoHome();
}

2. 框选放大 (Zoom Box)

按钮 ID: zoom-box
功能: 激活框选放大模式,用户可以框选区域进行放大
保持激活: 否

调用链

用户点击 [框选放大] 按钮
    │
    ▼
Button onClick (buttons/zoom-box/index.ts)
    │   registry.engine3d?.activateZoomBox()
    ▼
EngineManager.activateZoomBox() (managers/engine-manager.ts)
    │   this.engineInstance.activateZoomBox()
    ▼
Engine.activateZoomBox() (components/engine/index.ts)
    │   this.engine.rangeScale?.active()
    ▼
底层引擎 API: engine.rangeScale.active()

代码摘要

按钮定义 (buttons/zoom-box/index.ts):

onClick: () => {
    const registry = ManagerRegistry.getInstance();
    registry.engine3d?.activateZoomBox();
}

EngineManager (managers/engine-manager.ts):

public activateZoomBox(): void {
    this.engineInstance.activateZoomBox();
}

Engine 组件 (components/engine/index.ts):

public activateZoomBox(): void {
    this.engine.rangeScale?.active();
}

3. 测量 (Measure)

按钮 ID: measure
功能: 打开测量对话框,支持距离、角度、标高、体积等多种测量
保持激活: 是

调用链

3.1 打开测量对话框

用户点击 [测量] 按钮(激活)
    │
    ▼
Button onClick (buttons/measure/index.ts)
    │   registry.measure?.show()
    ▼
MeasureDialogManager.show() (继承自 BaseDialogManager)
    │   createContent() → 创建 MeasurePanel
    │   onDialogCreated()
    ▼
对话框显示,等待用户选择测量模式

3.2 用户选择测量模式

用户在 Panel 中选择测量模式(如:距离测量)
    │
    ▼
MeasurePanel.onModeChange('distance')
    │
    ▼
MeasureDialogManager 回调 (managers/measure-dialog-manager.ts)
    │   registry.engine3d?.activateMeasure(mode)
    ▼
EngineManager.activateMeasure('distance') (managers/engine-manager.ts)
    │   this.engineInstance.activateMeasure(mode)
    ▼
Engine.activateMeasure('distance') (components/engine/index.ts)
    │   switch(mode) → activateDistanceMeasure()
    ▼
Engine.activateDistanceMeasure()
    │   this.engine.measure.active()           // 激活主测量模块
    │   this.engine.measure.distanceMeasure.active()  // 激活距离测量
    ▼
底层引擎 API:
    - engine.measure.active()
    - engine.measure.distanceMeasure.active()

3.3 清除所有测量

用户点击 [删除全部] 按钮
    │
    ▼
MeasurePanel.onClearAll()
    │
    ▼
MeasureDialogManager 回调
    │   registry.engine3d?.clearAllMeasures()
    ▼
EngineManager.clearAllMeasures()
    │   this.engineInstance.clearAllMeasures()
    ▼
Engine.clearAllMeasures()
    │   this.engine.measure.clearAll()
    ▼
底层引擎 API: engine.measure.clearAll()

3.4 关闭测量对话框

用户关闭对话框 / 再次点击按钮
    │
    ▼
MeasureDialogManager.onBeforeDestroy()
    │   registry.engine3d.deactivateMeasure()
    ▼
EngineManager.deactivateMeasure()
    │   this.engineInstance.deactivateMeasure()
    ▼
Engine.deactivateMeasure()
    │   this.engine.measure.disActive()
    ▼
底层引擎 API: engine.measure.disActive()

测量模式与底层 API 对照表

模式 Engine 方法 底层 API
distance (距离) activateDistanceMeasure() engine.measure.distanceMeasure.active()
minDistance (最小距离) activateMinDistanceMeasure() engine.measure.clearDistanceMeasure.active()
angle (角度) activateAngleMeasure() engine.measure.angleMeasure.active()
elevation (标高) activateElevationMeasure() engine.measure.elevationMeasure.active()
volume (体积) activateVolumeMeasure() engine.measure.areaMeasure.active()
slope (坡度) activateSlopeMeasure() engine.measure.slopeMeasure.active()
laserDistance (激光测距) activateLaserDistanceMeasure() ⚠️ 暂未实现
spaceVolume (空间体积) activateSpaceVolumeMeasure() ⚠️ 暂未实现

4. 剖切菜单 (Section)

按钮 ID: section
类型: 菜单按钮,展开后显示子按钮

4.1 拾取面剖切 (Section Plane)

按钮 ID: section-plane
功能: 拾取模型表面进行剖切
保持激活: 是
互斥: 是(同一时间只能激活一种剖切)

调用链

用户点击 [拾取面剖切] 按钮(激活)
    │
    ▼
Button onClick (buttons/section/section-plane/index.ts)
    │   registry.sectionPlane?.show()
    ▼
SectionPlaneDialogManager.show() (继承自 BaseDialogManager)
    │   createContent() → 创建 SectionPlanePanel
    │   onDialogCreated()
    ▼
对话框显示拾取面剖切需要用户在3D场景中点击表面

⚠️ 注意: 拾取面剖切的底层 API 对接尚未完成,目前仅显示 UI 面板。


4.2 轴向剖切 (Section Axis)

按钮 ID: section-axis
功能: 沿 X/Y/Z 轴向进行剖切
保持激活: 是
互斥: 是

调用链 - 打开对话框

用户点击 [轴向剖切] 按钮(激活)
    │
    ▼
Button onClick (buttons/section/section-axis/index.ts)
    │   registry.sectionAxis?.show()
    ▼
SectionAxisDialogManager.show() (继承自 BaseDialogManager)
    │   createContent() → 创建 SectionAxisPanel
    │   onDialogCreated()
    │       └── registry.engine3d.activateSectionAxis('x')  // 默认激活 X 轴
    ▼
EngineManager.activateSectionAxis('x')
    │   this.engineInstance.activateSectionAxis(axis)
    ▼
Engine.activateSectionAxis('x')
    │   this.engine.clipping.sectionPlaneX.active()
    ▼
底层引擎 API: engine.clipping.sectionPlaneX.active()

调用链 - 切换轴向

用户在 Panel 中切换轴向Y 轴)
    │
    ▼
SectionAxisPanel.onAxisChange('y')
    │
    ▼
SectionAxisDialogManager 回调
    │   registry.engine3d?.activateSectionAxis('y')
    ▼
EngineManager.activateSectionAxis('y')
    │   this.engineInstance.activateSectionAxis(axis)
    ▼
Engine.activateSectionAxis('y')
    │   // 1. 先停用当前轴向 (X)
    │   this.engine.clipping.sectionPlaneX.disActive()
    │   // 2. 再激活新轴向 (Y)
    │   this.engine.clipping.sectionPlaneY.active()
    ▼
底层引擎 API:
    - engine.clipping.sectionPlaneX.disActive()
    - engine.clipping.sectionPlaneY.active()

调用链 - 关闭对话框

用户关闭对话框 / 再次点击按钮
    │
    ▼
SectionAxisDialogManager.onBeforeDestroy()
    │   registry.engine3d?.deactivateSectionAxis()
    ▼
EngineManager.deactivateSectionAxis()
    │   this.engineInstance.deactivateSectionAxis()
    ▼
Engine.deactivateSectionAxis()
    │   this.engine.clipping.disActive()
    ▼
底层引擎 API: engine.clipping.disActive()

轴向与底层 API 对照表

轴向 Engine 内部调用 底层 API
x planeMap['x'] engine.clipping.sectionPlaneX.active()
y planeMap['y'] engine.clipping.sectionPlaneY.active()
z planeMap['z'] engine.clipping.sectionPlaneZ.active()

4.3 剖切盒 (Section Box)

按钮 ID: section-box
功能: 六面体剖切盒,可调节 X/Y/Z 轴的最小/最大范围
保持激活: 是
互斥: 是

调用链 - 打开对话框

用户点击 [剖切盒] 按钮(激活)
    │
    ▼
Button onClick (buttons/section/section-box/index.ts)
    │   registry.sectionBox?.show()
    ▼
SectionBoxDialogManager.show() (继承自 BaseDialogManager)
    │   createContent() → 创建 SectionBoxPanel
    │   onDialogCreated()
    │       └── registry.engine3d?.activateSectionBox()
    ▼
EngineManager.activateSectionBox()
    │   this.engineInstance.activateSectionBox()
    ▼
Engine.activateSectionBox()
    │   this.engine.clipping.sectionBox.active()
    ▼
底层引擎 API: engine.clipping.sectionBox.active()

调用链 - 调整范围(拖动滑块)

用户拖动 X/Y/Z 轴滑块调整范围
    │
    ▼
SectionBoxPanel.onRangeChange(range)
    │   range = { x: {min, max}, y: {min, max}, z: {min, max} }  // 百分比 0-100
    ▼
SectionBoxDialogManager 回调
    │   registry.engine3d?.setSectionBoxRange(range)
    ▼
EngineManager.setSectionBoxRange(range)
    │   this.engineInstance.setSectionBoxRange(range)
    ▼
Engine.setSectionBoxRange(range)
    │   this.engine.clipping.sectionBox.setboxPercent(range)  // 直传百分比
    ▼
底层引擎 API: engine.clipping.sectionBox.setboxPercent(range)
    │   (底层负责百分比→坐标转换)

调用链 - 适应到模型

用户点击 [适应到模型] 按钮
    │
    ▼
SectionBoxPanel.onFitToModel()
    │
    ▼
SectionBoxDialogManager 回调
    │   registry.engine3d?.fitSectionBoxToModel()
    ▼
EngineManager.fitSectionBoxToModel()
    │   this.engineInstance.fitSectionBoxToModel()
    ▼
Engine.fitSectionBoxToModel()
    │   const box = this.engine.octreeBox?.getBoundingBox()
    │   this.engine.clipping.sectionBox.setBox(box)
    ▼
底层引擎 API:
    - engine.octreeBox.getBoundingBox()
    - engine.clipping.sectionBox.setBox(box)

调用链 - 重置

用户点击 [重置] 按钮
    │
    ▼
SectionBoxPanel.onReset()
    │
    ▼
SectionBoxDialogManager 回调
    │   registry.engine3d?.resetSectionBox()
    ▼
EngineManager.resetSectionBox()
    │   this.engineInstance.resetSectionBox()
    ▼
Engine.resetSectionBox()
    │   this.fitSectionBoxToModel()  // 内部调用适应到模型
    ▼
(同上 "适应到模型" 流程)

调用链 - 关闭对话框

用户关闭对话框 / 再次点击按钮
    │
    ▼
SectionBoxDialogManager.onBeforeDestroy()
    │   registry.engine3d?.deactivateSectionBox()
    ▼
EngineManager.deactivateSectionBox()
    │   this.engineInstance.deactivateSectionBox()
    ▼
Engine.deactivateSectionBox()
    │   this.engine.clipping.sectionBox.disActive()
    ▼
底层引擎 API: engine.clipping.sectionBox.disActive()

剖切盒 API 汇总

操作 Engine 方法 底层 API
激活 activateSectionBox() engine.clipping.sectionBox.active()
停用 deactivateSectionBox() engine.clipping.sectionBox.disActive()
设置范围(百分比) setSectionBoxRange(range) engine.clipping.sectionBox.setboxPercent(range)
设置范围(坐标) - engine.clipping.sectionBox.setboxXyz(xyz)
适应到模型 fitSectionBoxToModel() engine.clipping.sectionBox.setBox(box)
获取包围盒 - engine.octreeBox.getBoundingBox()

数据格式说明

层级 数据格式 示例
UI / SDK 百分比 (0-100) { x: {min: 20, max: 80}, y: {...}, z: {...} }
底层引擎 百分比 → 内部转换为坐标 底层负责转换

5. 漫游 (Walk) - 第一人称漫游

标注说明: [SDK] = SDK 层代码, [底层] = bim_engine_base 底层引擎

按钮 ID: walk
功能: 打开漫游控制面板,支持第一人称漫游模式
保持激活: 否

5.1 第一人称漫游模式开关

UI 点击漫游按钮
    → [SDK] WalkControlPanel.onWalkModeToggle(isActive)
    → [SDK] WalkControlManager.onWalkModeToggle
    → [SDK] EngineManager.activateFirstPersonMode() / deactivateFirstPersonMode()
    → [SDK] Engine.activateFirstPersonMode() / deactivateFirstPersonMode()
    → [底层] engine.controlModule.switchFirstPersonMode() / switchDefaultMode()

5.2 速度调节

UI 速度按钮 (1-10)
    → [SDK] WalkControlPanel.onSpeedChange(speed)
    → [SDK] WalkControlManager.onSpeedChange
    → [SDK] EngineManager.setWalkSpeed(speed * 0.01)  // 值转换: UI 1-10 → 引擎 0.01-0.1
    → [SDK] Engine.setWalkSpeed(speed)
    → [底层] engine.controlModule.firstPersonControls.moveSpeed = speed

5.3 重力开关

UI 重力复选框
    → [SDK] WalkControlPanel.onGravityToggle(enabled)
    → [SDK] WalkControlManager.onGravityToggle
    → [SDK] EngineManager.setWalkGravity(enabled)
    → [SDK] Engine.setWalkGravity(enabled)
    → [底层] engine.controlModule.firstPersonControls.applyGravity = enabled

5.4 碰撞检测开关

UI 碰撞复选框
    → [SDK] WalkControlPanel.onCollisionToggle(enabled)
    → [SDK] WalkControlManager.onCollisionToggle
    → [SDK] EngineManager.setWalkCollision(enabled)
    → [SDK] Engine.setWalkCollision(enabled)
    → [底层] engine.controlModule.firstPersonControls.applyCollision = enabled

6. 地图 (Map)

按钮 ID: map
功能: 打开/关闭地图对话框
保持激活: 是

调用链

用户点击 [地图] 按钮
    │
    ▼
Button onClick (buttons/map/index.ts)
    │   if (registry.map?.isOpen()) {
    │       registry.map?.hide()
    │   } else {
    │       registry.map?.show()
    │   }
    ▼
MapDialogManager.show() / hide()
    │
    ▼
地图对话框显示/隐藏

事件监听

// 按钮定义中监听地图开关事件,同步按钮激活状态
registry.on('map:opened', () => {
    registry.toolbar?.setBtnActive('map', true);
});

registry.on('map:closed', () => {
    registry.toolbar?.setBtnActive('map', false);
});

7. 构件详情 (Property)

按钮 ID: property
功能: 打开构件属性面板
保持激活: 否

调用链

用户点击 [构件详情] 按钮
    │
    ▼
Button onClick (buttons/property/index.ts)
    │   registry.componentDetail?.show()
    ▼
ComponentDetailManager.show()
    │
    ▼
属性面板显示

8. 设置 (Setting)

按钮 ID: setting
功能: 打开设置面板
保持激活: 否

调用链

用户点击 [设置] 按钮
    │
    ▼
Button onClick (buttons/setting/index.ts)
    │   console.log('设置按钮被点击')
    ▼
⚠️ 暂未实现具体功能

9. 信息 (Info)

按钮 ID: info
功能: 打开信息对话框
保持激活: 否

调用链

用户点击 [信息] 按钮
    │
    ▼
Button onClick (buttons/info/index.ts)
    │   registry.emit('ui:open-dialog', { id: 'info' })
    ▼
事件系统广播 'ui:open-dialog' 事件
    │
    ▼
监听该事件的组件响应并打开信息对话框

10. 全屏 (Fullscreen)

按钮 ID: fullscreen
功能: 切换全屏模式
保持激活: 否

调用链

用户点击 [全屏] 按钮
    │
    ▼
Button onClick (buttons/fullscreen/index.ts)
    │   // 检测当前是否全屏
    │   const isFullscreen = !!document.fullscreenElement
    │
    ├── 如果不是全屏:
    │       targetElem.requestFullscreen({ navigationUI: 'hide' })
    │       ▼
    │       浏览器 API: Element.requestFullscreen()
    │
    └── 如果是全屏:
            document.exitFullscreen()
            ▼
            浏览器 API: Document.exitFullscreen()

注意事项

  • 全屏功能使用浏览器原生 API不涉及底层 3D 引擎
  • 支持多种浏览器前缀:webkitRequestFullscreenmozRequestFullScreenmsRequestFullscreen
  • 在 iframe 中使用需要父级 iframe 标签添加 allow="fullscreen" 属性

附录:调用链层级说明

┌─────────────────────────────────────────────────────────────────────┐
│ L1: Toolbar Button                                                  │
│     文件: src/components/button-group/toolbar/buttons/*/index.ts    │
│     职责: 定义按钮配置,处理 onClick 事件                             │
└────────────────────────────────┬────────────────────────────────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────────────────────────────────┐
│ L2: DialogManager                                                   │
│     文件: src/managers/*-dialog-manager.ts                          │
│     职责: 管理对话框生命周期,绑定 Panel 回调到 EngineManager          │
└────────────────────────────────┬────────────────────────────────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────────────────────────────────┐
│ L3: EngineManager                                                   │
│     文件: src/managers/engine-manager.ts                            │
│     职责: 代理 Engine 组件,提供统一 API检查初始化状态               │
└────────────────────────────────┬────────────────────────────────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────────────────────────────────┐
│ L4: Engine Component                                                │
│     文件: src/components/engine/index.ts                            │
│     职责: 封装底层引擎,维护状态,进行数据转换                         │
└────────────────────────────────┬────────────────────────────────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────────────────────────────────┐
│ L5: 底层 3D 引擎 (iflow-engine-base)                                │
│     来源: 第三方 SDK                                                 │
│     职责: 实际 3D 渲染和功能实现                                      │
└─────────────────────────────────────────────────────────────────────┘

附录ManagerRegistry 访问方式

// 在 Button 中获取 Registry
const registry = ManagerRegistry.getInstance();

// 访问各个 Manager
registry.engine3d        // EngineManager - 3D 引擎
registry.toolbar         // ToolbarManager - 工具栏
registry.dialog          // DialogManager - 对话框
registry.measure         // MeasureDialogManager - 测量
registry.sectionPlane    // SectionPlaneDialogManager - 拾取面剖切
registry.sectionAxis     // SectionAxisDialogManager - 轴向剖切
registry.sectionBox      // SectionBoxDialogManager - 剖切盒
registry.walkControl     // WalkControlManager - 漫游控制
registry.map             // MapDialogManager - 地图
registry.componentDetail // ComponentDetailManager - 构件详情弹窗
registry.componentDetail // ComponentDetailManager - 构件详情弹窗

第二章:右键菜单 (Context Menu)

右键菜单系统提供上下文相关的操作选项,根据当前状态动态生成菜单项。

2.1 构件详情 (Component Detail)

触发条件: 用户右键点击,且有构件被选中
功能: 打开构件详情弹窗,展示构件属性信息

调用链

用户右键点击 3D 场景(有构件选中)
    │
    ▼
RightKeyManager.handleContextMenu() (managers/right-key-manager.ts)
    │   获取当前选中构件: registry.engine3d.getSelectedComponent()
    │   动态生成菜单项(包含"构件详情"
    ▼
用户点击 [构件详情] 菜单项
    │
    ▼
MenuItem onClick (managers/engine-manager.ts:63-68)
    │   registry.componentDetail?.show(selected.url, selected.id)
    ▼
ComponentDetailManager.show(url, id) (managers/component-detail-manager.ts)
    │   1. createDialog() - 创建右侧对话框400px 宽)
    │   2. showLoading() - 显示"加载中..."
    │   3. registry.engine3d.getComponentProperties(url, id, callback)
    ▼
EngineManager.getComponentProperties(url, id, callback)
    │   this.engineInstance.getComponentProperties(url, id, callback)
    ▼
Engine.getComponentProperties(url, id, callback)
    │   this.engine.modelProperties.getModelProperties(url, id, callback)
    ▼
底层引擎 API: engine.modelProperties.getModelProperties(url, id, callback)
    │   异步回调返回属性数据
    ▼
ComponentDetailManager.renderProperties(data)
    │   使用 BimCollapse + BimDescription 渲染属性
    ▼
对话框显示构件属性(分类折叠面板 + 键值对列表)

数据流

// 1. 选中信息 (从 Engine)
{
  url: string,     // 模型 URL
  id: string       // 构件 ID
}

// 2. 属性数据 (从底层引擎回调)
[
  {
    categoryName: "基本属性",
    items: [
      { key: "名称", value: "墙-001" },
      { key: "类型", value: "承重墙" }
    ]
  },
  {
    categoryName: "尺寸",
    items: [
      { key: "长度", value: "5000mm" },
      { key: "高度", value: "3000mm" }
    ]
  }
]

// 3. UI 渲染 (BimCollapse)
- 每个 category  一个折叠面板
- items[]  BimDescription 键值对列表

代码摘要

动态菜单生成 (managers/engine-manager.ts):

const selected = this.getSelectedComponent();
if (selected) {
    menuItems.push({
        id: 'component-detail',
        label: 'menu.componentDetail',
        group: 'component',
        onClick: () => {
            const registry = ManagerRegistry.getInstance();
            registry.componentDetail?.show(selected.url, selected.id);
            this.rightKey?.hide();
        }
    });
}

ComponentDetailManager (managers/component-detail-manager.ts):

public show(modelUrl: string, componentId: string): void {
    this.createDialog();
    this.showLoading();
    
    const registry = ManagerRegistry.getInstance();
    registry.engine3d?.getComponentProperties(
        modelUrl,
        componentId,
        (data) => {
            this.renderProperties(data);
        }
    );
}

2.2 显示全部 (Show All)

触发条件: 用户右键点击(任何情况)
功能: 显示所有已加载的模型构件

调用链

用户右键点击 3D 场景
    │
    ▼
RightKeyManager.handleContextMenu()
    │   动态生成菜单项(始终包含"显示全部"
    ▼
用户点击 [显示全部] 菜单项
    │
    ▼
MenuItem onClick (managers/engine-manager.ts:71-77)
    │   registry.engine3d?.showAllComponents()
    ▼
EngineManager.showAllComponents()
    │   ⚠️ 方法尚未实现,当前为空方法
    ▼
⚠️ 待实现:调用底层引擎 API 显示所有构件

注意: "显示全部"功能的底层 API 对接尚未完成,当前仅显示菜单项。


2.3 信息 (Info)

触发条件: 用户右键点击(任何情况)
功能: 打开信息对话框

调用链

用户右键点击 3D 场景
    │
    ▼
RightKeyManager.handleContextMenu()
    │   动态生成菜单项(始终包含"信息"
    ▼
用户点击 [信息] 菜单项
    │
    ▼
MenuItem onClick (managers/engine-manager.ts)
    │   registry.emit('ui:open-dialog', { id: 'info' })
    │   this.rightKey?.hide()
    ▼
事件系统广播 'ui:open-dialog' 事件
    │
    ▼
监听该事件的组件响应并打开信息对话框

2.4 首页 (Home)

触发条件: 用户右键点击(任何情况)
功能: 相机回到初始位置

调用链

用户右键点击 3D 场景
    │
    ▼
RightKeyManager.handleContextMenu()
    │   动态生成菜单项(始终包含"首页"
    ▼
用户点击 [首页] 菜单项
    │
    ▼
MenuItem onClick (managers/engine-manager.ts)
    │   registry.engine3d?.CameraGoHome()
    │   this.rightKey?.hide()
    ▼
(调用链同 Toolbar 首页按钮)

右键菜单系统架构

动态菜单机制

// 1. 注册处理器 (EngineManager.initialize)
const handler: RightKeyHandler = () => {
    const menuItems: MenuItemConfig[] = [];
    
    // 根据选中状态动态生成菜单
    const selected = this.getSelectedComponent();
    if (selected) {
        menuItems.push({ /* 构件详情 */ });
    }
    
    // 通用菜单项
    menuItems.push({ /* 显示全部 */ });
    menuItems.push({ /* 信息 */ });
    menuItems.push({ /* 首页 */ });
    
    return menuItems;
};

registry.rightKey?.registerHandler('engine', handler);

菜单项配置结构

interface MenuItemConfig {
    id: string;              // 唯一标识
    label: string;           // 国际化 key
    group?: string;          // 分组(可选)
    onClick: () => void;     // 点击回调
}

第三章:构件交互 (Component Interaction)

构件交互系统处理用户与 3D 场景中构件的交互事件,包括选中和取消选中。

3.1 构件选中 (Component Selection)

触发: 用户点击 3D 场景中的构件
效果: 记录选中状态,可用于后续操作(如右键菜单、属性面板等)

调用链

用户点击 3D 场景中的构件
    │
    ▼
[底层] interactionModule.handleMouseClick() (bim_engine_base)
    │   检测鼠标点击并进行射线拾取
    │   获取点击到的物体信息 (hit)
    ▼
[底层] engine.events.trigger('click', hit)
    │   hit = { object: { url, name }, point, distance, ... }
    ▼
[SDK] Engine.init() 中的 'click' 事件监听器 (components/engine/index.ts:127-136)
    │   if (hit && hit.object) {
    │       this.selectedComponent = { 
    │           url: hit.object.url, 
    │           id: hit.object.name 
    │       };
    │       console.log('[Engine] 构件选中:', this.selectedComponent);
    │   }
    ▼
选中状态已记录在 Engine.selectedComponent

状态存储

// Engine 组件 (components/engine/index.ts)
private selectedComponent: { url: string; id: string } | null = null;

// 点击事件监听器
this.engine.events.on('click', (hit: any) => {
    if (hit && hit.object) {
        this.selectedComponent = { 
            url: hit.object.url, 
            id: hit.object.name 
        };
        console.log('[Engine] 构件选中:', this.selectedComponent);
    } else {
        this.selectedComponent = null;
        console.log('[Engine] 取消选中');
    }
});

获取选中状态

// Engine 方法
public getSelectedComponent(): { url: string; id: string } | null {
    return this.selectedComponent;
}

// EngineManager 代理
public getSelectedComponent(): { url: string; id: string } | null {
    if (!this.engineInstance) {
        console.warn('[EngineManager] Engine 尚未初始化');
        return null;
    }
    return this.engineInstance.getSelectedComponent();
}

// 使用示例
const registry = ManagerRegistry.getInstance();
const selected = registry.engine3d?.getSelectedComponent();
if (selected) {
    console.log(`选中构件: ${selected.url}#${selected.id}`);
}

3.2 取消选中 (Deselection)

触发: 用户点击 3D 场景的空白区域
效果: 清除选中状态

调用链

用户点击 3D 场景空白区域
    │
    ▼
[底层] interactionModule.handleMouseClick()
    │   射线拾取未命中任何物体
    ▼
[底层] engine.events.trigger('click', null)
    │   hit = null表示未点击到任何物体
    ▼
[SDK] Engine.init() 中的 'click' 事件监听器
    │   if (hit && hit.object) { ... }
    │   else {
    │       this.selectedComponent = null;
    │       console.log('[Engine] 取消选中');
    │   }
    ▼
选中状态已清除

构件交互数据流

┌─────────────────────────────────────────────────────────────────┐
│ [底层] 3D 引擎 - 事件系统                                         │
│   engine.events.trigger('click', hit)                           │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼ hit: { object: {url, name}, ... } | null
┌─────────────────────────────────────────────────────────────────┐
│ [SDK] Engine 组件 - 事件监听 + 状态管理                           │
│   - 监听 'click' 事件                                            │
│   - 更新 selectedComponent 状态                                  │
│   - 提供 getSelectedComponent() 方法                             │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│ [SDK] EngineManager - 状态代理                                   │
│   - 代理 getSelectedComponent() 方法                             │
│   - 供其他 Manager 调用                                          │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│ [SDK] 消费者 - 使用选中状态                                       │
│   - RightKeyManager: 动态生成右键菜单                            │
│   - ComponentDetailManager: 显示构件详情                         │
│   - ComponentDetailManager: 显示构件详情弹窗                     │
│   - ... 其他需要选中状态的功能                                    │
└─────────────────────────────────────────────────────────────────┘

使用场景示例

场景 1: 右键菜单根据选中状态显示不同菜单项

// RightKeyManager.handleContextMenu()
const handler: RightKeyHandler = () => {
    const menuItems: MenuItemConfig[] = [];
    
    // 检查是否有选中的构件
    const selected = registry.engine3d?.getSelectedComponent();
    
    if (selected) {
        // 有选中构件:显示"构件详情"菜单
        menuItems.push({
            id: 'component-detail',
            label: 'menu.componentDetail',
            onClick: () => {
                registry.componentDetail?.show(selected.url, selected.id);
            }
        });
    }
    
    // 通用菜单项(无论是否选中都显示)
    menuItems.push({
        id: 'show-all',
        label: 'menu.showAll',
        onClick: () => { /* ... */ }
    });
    
    return menuItems;
};

场景 2: 构件详情面板获取当前选中构件

// ComponentDetailManager
public showCurrentSelection(): void {
    const registry = ManagerRegistry.getInstance();
    const selected = registry.engine3d?.getSelectedComponent();
    
    if (!selected) {
        console.warn('未选中任何构件');
        return;
    }
    
    // 使用选中信息加载构件详情
    this.show(selected.url, selected.id);
}

附录:完整 ManagerRegistry 访问方式(更新)

// 在任意位置获取 Registry
const registry = ManagerRegistry.getInstance();

// 访问各个 Manager
registry.engine3d          // EngineManager - 3D 引擎
registry.toolbar           // ToolbarManager - 工具栏
registry.dialog            // DialogManager - 对话框
registry.rightKey          // RightKeyManager - 右键菜单
registry.measure           // MeasureDialogManager - 测量
registry.sectionPlane      // SectionPlaneDialogManager - 拾取面剖切
registry.sectionAxis       // SectionAxisDialogManager - 轴向剖切
registry.sectionBox        // SectionBoxDialogManager - 剖切盒
registry.walkControl       // WalkControlManager - 漫游控制
registry.map               // MapDialogManager - 地图
registry.componentDetail   // ComponentDetailManager - 构件详情弹窗
registry.componentDetail   // ComponentDetailManager - 构件详情弹窗(新增)