# BIM Engine SDK - API 调用链文档 本文档详细记录 BIM Engine SDK 中各功能模块的完整调用链,从用户交互到底层 3D 引擎 API。 --- ## 目录 ### 第一章:工具栏 (Toolbar) 1. [首页 (Home)](#1-首页-home) 2. [框选放大 (Zoom Box)](#2-框选放大-zoom-box) 3. [测量 (Measure)](#3-测量-measure) 4. [剖切菜单 (Section)](#4-剖切菜单-section) - [拾取面剖切 (Section Plane)](#41-拾取面剖切-section-plane) - [轴向剖切 (Section Axis)](#42-轴向剖切-section-axis) - [剖切盒 (Section Box)](#43-剖切盒-section-box) 5. [漫游 (Walk)](#5-漫游-walk) 6. [地图 (Map)](#6-地图-map) 7. [构件详情 (Property)](#7-构件详情-property) 8. [设置 (Setting)](#8-设置-setting) 9. [信息 (Info)](#9-信息-info) 10. [全屏 (Fullscreen)](#10-全屏-fullscreen) ### 第二章:右键菜单 (Context Menu) 1. [构件详情 (Component Detail)](#21-构件详情-component-detail) 2. [显示全部 (Show All)](#22-显示全部-show-all) 3. [信息 (Info)](#23-信息-info-1) 4. [首页 (Home)](#24-首页-home-1) ### 第三章:构件交互 (Component Interaction) 1. [构件选中 (Component Selection)](#31-构件选中-component-selection) 2. [取消选中 (Deselection)](#32-取消选中-deselection) --- # 第一章:工具栏 (Toolbar) --- ## 目录 1. [首页 (Home)](#1-首页-home) 2. [框选放大 (Zoom Box)](#2-框选放大-zoom-box) 3. [测量 (Measure)](#3-测量-measure) 4. [剖切菜单 (Section)](#4-剖切菜单-section) - [拾取面剖切 (Section Plane)](#41-拾取面剖切-section-plane) - [轴向剖切 (Section Axis)](#42-轴向剖切-section-axis) - [剖切盒 (Section Box)](#43-剖切盒-section-box) 5. [漫游 (Walk)](#5-漫游-walk) 6. [地图 (Map)](#6-地图-map) 7. [构件详情 (Property)](#7-构件详情-property) 8. [设置 (Setting)](#8-设置-setting) 9. [信息 (Info)](#9-信息-info) 10. [全屏 (Fullscreen)](#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`): ```typescript onClick: (button) => { const registry = ManagerRegistry.getInstance(); registry.engine3d?.CameraGoHome(); } ``` **EngineManager** (`managers/engine-manager.ts`): ```typescript public CameraGoHome(): void { this.engineInstance.CameraGoHome(); } ``` **Engine 组件** (`components/engine/index.ts`): ```typescript 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`): ```typescript onClick: () => { const registry = ManagerRegistry.getInstance(); registry.engine3d?.activateZoomBox(); } ``` **EngineManager** (`managers/engine-manager.ts`): ```typescript public activateZoomBox(): void { this.engineInstance.activateZoomBox(); } ``` **Engine 组件** (`components/engine/index.ts`): ```typescript 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() │ ▼ 地图对话框显示/隐藏 ``` ### 事件监听 ```typescript // 按钮定义中监听地图开关事件,同步按钮激活状态 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 引擎 - 支持多种浏览器前缀:`webkitRequestFullscreen`、`mozRequestFullScreen`、`msRequestFullscreen` - 在 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 访问方式 ```typescript // 在 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 渲染属性 ▼ 对话框显示构件属性(分类折叠面板 + 键值对列表) ``` ### 数据流 ```typescript // 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`): ```typescript 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`): ```typescript 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 首页按钮) ``` --- ## 右键菜单系统架构 ### 动态菜单机制 ```typescript // 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); ``` ### 菜单项配置结构 ```typescript 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 ``` ### 状态存储 ```typescript // 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] 取消选中'); } }); ``` ### 获取选中状态 ```typescript // 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: 右键菜单根据选中状态显示不同菜单项 ```typescript // 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: 构件详情面板获取当前选中构件 ```typescript // 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 访问方式(更新) ```typescript // 在任意位置获取 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 - 构件详情弹窗(新增) ```