refactor: slim down EngineManager from 861 to 290 lines by removing passthrough proxy pattern
- EngineManager now only exposes public SDK API (initialize, loadModel, pause/resumeRendering, getEngineComponent, destroy) - Internal managers access Engine component directly via this.engineComponent getter on BaseManager - Non-manager components use registry.engine3d.getEngineComponent() for direct Engine access - Replaced getEngine() with onRawEvent()/offRawEvent() for raw engine event access - Migrated 62 call sites across 13 files (9 managers, 1 panel, 3 toolbar buttons) - Updated all architecture docs, API docs, and README to reflect new patterns
This commit is contained in:
52
README.md
52
README.md
@@ -70,16 +70,18 @@ onMounted(() => {
|
||||
bimEngine.initWalkControl(); // 漫游控制
|
||||
bimEngine.initMap(); // 地图
|
||||
|
||||
// 加载模型
|
||||
bimEngine.engine?.loadModel({
|
||||
url: '/path/to/your/model.ifc',
|
||||
onProgress: (progress) => {
|
||||
console.log(`加载进度: ${progress}%`);
|
||||
},
|
||||
onComplete: () => {
|
||||
console.log('模型加载完成');
|
||||
// 加载模型(EngineManager 对外公共 API)
|
||||
bimEngine.engine?.loadModel(
|
||||
['/path/to/your/model.ifc'],
|
||||
{
|
||||
onProgress: (progress) => {
|
||||
console.log(`加载进度: ${progress}%`);
|
||||
},
|
||||
onComplete: () => {
|
||||
console.log('模型加载完成');
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -179,6 +181,13 @@ BIM Engine SDK 采用 **管理器模式 (Manager Pattern)** 作为核心架构,
|
||||
- ✅ 简化集成复杂度
|
||||
- ✅ 支持按需加载
|
||||
|
||||
> 架构更新(2026-03)
|
||||
>
|
||||
> - `EngineManager` 已从全量透传层收敛为“外部公共 API + 生命周期编排”
|
||||
> - 内部 Manager 统一通过 `BaseManager.engineComponent` 直接访问 `Engine` 组件
|
||||
> - 非 Manager 场景通过 `registry.engine3d?.getEngineComponent()?.xxx()` 访问引擎能力
|
||||
> - `Engine.getEngine()` 已移除,事件订阅使用 `onRawEvent()/offRawEvent()`
|
||||
|
||||
### 架构分层
|
||||
|
||||
```
|
||||
@@ -193,7 +202,7 @@ BIM Engine SDK 采用 **管理器模式 (Manager Pattern)** 作为核心架构,
|
||||
│ Manager 层 (管理器) │
|
||||
│ - ToolbarManager (工具栏) │
|
||||
│ - DialogManager (对话框) │
|
||||
│ - EngineManager (3D引擎) │
|
||||
│ - EngineManager (外部公共API/生命周期) │
|
||||
│ - MeasureDialogManager (测量) │
|
||||
│ - SectionPlaneDialogManager (剖切) │
|
||||
│ - WalkControlManager (漫游) │
|
||||
@@ -333,12 +342,14 @@ class BimEngine {
|
||||
每个管理器负责特定功能的生命周期管理:
|
||||
|
||||
```typescript
|
||||
// 3D 引擎管理器
|
||||
bimEngine.engine?.loadModel({
|
||||
url: '/path/to/model.ifc',
|
||||
onProgress: (progress) => console.log(progress),
|
||||
onComplete: () => console.log('完成')
|
||||
});
|
||||
// 3D 引擎管理器(对外公共 API)
|
||||
bimEngine.engine?.loadModel(
|
||||
['/path/to/model.ifc'],
|
||||
{
|
||||
onProgress: (progress) => console.log(progress),
|
||||
onComplete: () => console.log('完成')
|
||||
}
|
||||
);
|
||||
|
||||
// 测量工具管理器
|
||||
bimEngine.measure?.show();
|
||||
@@ -509,6 +520,15 @@ npm run dev
|
||||
|
||||
## 📝 更新日志
|
||||
|
||||
### v1.3.2 (2026-03-05)
|
||||
|
||||
**架构优化**
|
||||
- ✨ EngineManager 瘦身:移除大规模 1:1 透传方法
|
||||
- ✨ 新增 `EngineManager.getEngineComponent()` 供内部访问
|
||||
- ✨ BaseManager 新增 `engineComponent` 便捷访问器
|
||||
- ✨ Manager 内部调用统一迁移为 `this.engineComponent?.xxx()`
|
||||
- ✨ 原始事件访问改为 `onRawEvent()/offRawEvent()`,移除 `getEngine()`
|
||||
|
||||
### v1.0.0 (2024-12-26)
|
||||
|
||||
**核心功能**
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1260
docs/API调用链.md
1260
docs/API调用链.md
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,21 @@
|
||||
|
||||
本文档为 BIM Engine SDK 的完整 API 参考,适合大模型阅读和理解引擎的功能结构。
|
||||
|
||||
> 2026-03 更新说明(SDK 接入层)
|
||||
>
|
||||
> 本文档主体描述的是底层 3D 内核与模块能力。SDK 接入层在本次重构后有以下关键变化:
|
||||
>
|
||||
> 1. `EngineManager` 已收敛为少量公共 API,不再作为全量透传层。
|
||||
> 2. SDK 内部 Manager 统一通过 `BaseManager.engineComponent` 直接访问 `Engine` 组件能力。
|
||||
> 3. 非 Manager 组件通过 `registry.engine3d?.getEngineComponent()?.xxx()` 访问 `Engine` 组件。
|
||||
> 4. `Engine.getEngine()` 已移除,原始事件访问改为 `onRawEvent()/offRawEvent()`。
|
||||
>
|
||||
> 若你在接入层实现新功能,请优先参考:
|
||||
>
|
||||
> - `docs/架构设计.md`
|
||||
> - `docs/API调用链.md`
|
||||
> - `docs/引擎API对接.md`
|
||||
|
||||
## 目录
|
||||
|
||||
1. [核心引擎类](#1-核心引擎类)
|
||||
|
||||
@@ -2,39 +2,45 @@
|
||||
|
||||
## 模块概述
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| **模块名** | managers |
|
||||
| **职责** | 处理业务逻辑,协调组件交互,管理组件生命周期 |
|
||||
| **公开 API** | 15 个 Manager 类 |
|
||||
| **状态** | ✅ 稳定 |
|
||||
`src/managers/` 负责业务编排与生命周期管理,不直接承载底层 3D 细节。
|
||||
|
||||
2026-03 更新后,管理器层的核心变化:
|
||||
|
||||
- `EngineManager` 从“全量透传”改为“对外公共 API + 初始化编排”
|
||||
- `BaseManager` 新增 `engineComponent` getter,内部管理器直接调用 `Engine` 组件
|
||||
- Manager 层统一收敛为“编排者”,而不是“透传中间层”
|
||||
|
||||
---
|
||||
|
||||
## 代码地图
|
||||
|
||||
```
|
||||
```text
|
||||
src/managers/
|
||||
├── engine-manager.ts # 3D 引擎管理器
|
||||
├── toolbar-manager.ts # 工具栏管理器
|
||||
├── dialog-manager.ts # 对话框管理器
|
||||
├── button-group-manager.ts # 按钮组管理器
|
||||
├── right-key-manager.ts # 右键菜单管理器
|
||||
├── component-detail-manager.ts # 构件详情弹窗管理器
|
||||
├── construct-tree-manager-btn.ts # 构件树管理器
|
||||
├── walk-control-manager.ts # 漫游控制管理器
|
||||
├── measure-dialog-manager.ts # 测量对话框管理器
|
||||
├── section-plane-dialog-manager.ts # 平面剖切对话框管理器
|
||||
├── section-box-dialog-manager.ts # 剖切盒对话框管理器
|
||||
├── section-axis-dialog-manager.ts # 轴向剖切对话框管理器
|
||||
├── map-dialog-manager.ts # 地图对话框管理器
|
||||
├── walk-path-dialog-manager.ts # 漫游路径对话框管理器
|
||||
└── walk-plan-view-dialog-manager.ts # 漫游平面图对话框管理器
|
||||
├── engine-manager.ts
|
||||
├── toolbar-manager.ts
|
||||
├── dialog-manager.ts
|
||||
├── button-group-manager.ts
|
||||
├── right-key-manager.ts
|
||||
├── component-detail-manager.ts
|
||||
├── construct-tree-manager-btn.ts
|
||||
├── walk-control-manager.ts
|
||||
├── measure-dialog-manager.ts
|
||||
├── section-plane-dialog-manager.ts
|
||||
├── section-axis-dialog-manager.ts
|
||||
├── section-box-dialog-manager.ts
|
||||
├── map-dialog-manager.ts
|
||||
├── walk-path-dialog-manager.ts
|
||||
├── walk-plan-view-dialog-manager.ts
|
||||
├── setting-dialog-manager.ts
|
||||
├── engine-info-dialog-manager.ts
|
||||
└── ai-chat-manager.ts
|
||||
```
|
||||
|
||||
## 管理器分类
|
||||
---
|
||||
|
||||
### 按继承关系
|
||||
## 继承关系
|
||||
|
||||
```
|
||||
```text
|
||||
BaseManager
|
||||
├── EngineManager
|
||||
├── ToolbarManager
|
||||
@@ -43,9 +49,12 @@ BaseManager
|
||||
├── RightKeyManager
|
||||
├── ComponentDetailManager
|
||||
├── ConstructTreeManagerBtn
|
||||
└── WalkControlManager
|
||||
├── WalkControlManager
|
||||
├── SettingDialogManager
|
||||
├── EngineInfoDialogManager
|
||||
└── AiChatManager
|
||||
|
||||
BaseDialogManager (继承自 BaseManager)
|
||||
BaseDialogManager (extends BaseManager)
|
||||
├── MeasureDialogManager
|
||||
├── SectionPlaneDialogManager
|
||||
├── SectionAxisDialogManager
|
||||
@@ -55,435 +64,100 @@ BaseDialogManager (继承自 BaseManager)
|
||||
└── WalkPlanViewDialogManager
|
||||
```
|
||||
|
||||
### 按功能分类
|
||||
---
|
||||
|
||||
| 类别 | 管理器 | 职责 |
|
||||
|------|--------|------|
|
||||
| **核心** | EngineManager | 3D 引擎管理 |
|
||||
| **UI 容器** | ToolbarManager, DialogManager, ButtonGroupManager | 容器组件管理 |
|
||||
| **交互** | RightKeyManager, ComponentDetailManager, ConstructTreeManagerBtn | 用户交互 |
|
||||
| **3D 工具** | MeasureDialogManager, SectionPlaneDialogManager, SectionAxisDialogManager, SectionBoxDialogManager | 3D 操作工具 |
|
||||
| **漫游** | WalkControlManager, WalkPathDialogManager, WalkPlanViewDialogManager, MapDialogManager | 漫游功能 |
|
||||
## BaseManager 统一能力
|
||||
|
||||
文件:`src/core/base-manager.ts`
|
||||
|
||||
```ts
|
||||
protected get engineComponent(): Engine | null;
|
||||
```
|
||||
|
||||
用途:
|
||||
|
||||
- 内部 manager 直接调用 `Engine` 组件能力
|
||||
- 替代历史写法 `this.registry.engine3d?.xxx()`
|
||||
|
||||
示例:
|
||||
|
||||
```ts
|
||||
this.engineComponent?.activeSection('box');
|
||||
this.engineComponent?.setWalkSpeed(speed);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## EngineManager
|
||||
## EngineManager(最新职责)
|
||||
|
||||
### 概述
|
||||
文件:`src/managers/engine-manager.ts`
|
||||
|
||||
管理 3D 引擎的初始化、模型加载和测量功能。
|
||||
### 职责
|
||||
|
||||
### API
|
||||
1. 创建/销毁 `Engine` 组件
|
||||
2. 创建/销毁 `RightKeyManager`
|
||||
3. 组装右键菜单逻辑
|
||||
4. 暴露外部公共 API
|
||||
|
||||
```typescript
|
||||
### 公共 API
|
||||
|
||||
```ts
|
||||
class EngineManager extends BaseManager {
|
||||
initialize(options?: EngineOptions): void;
|
||||
rightKey: RightKeyManager | null;
|
||||
|
||||
getEngineComponent(): Engine | null;
|
||||
initialize(options?: Omit<EngineOptions, 'container'>): boolean;
|
||||
isInitialized(): boolean;
|
||||
loadModel(url: string, options?: ModelLoadOptions): void;
|
||||
getEngine(): any;
|
||||
CameraGoHome(): void;
|
||||
activateMeasure(mode: MeasureMode): void;
|
||||
deactivateMeasure(): void;
|
||||
getCurrentMeasureType(): MeasureMode | null;
|
||||
loadModel(urls: string[], options?: ModelLoadOptions): void;
|
||||
pauseRendering(): void;
|
||||
resumeRendering(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
```
|
||||
|
||||
### 使用示例
|
||||
说明:
|
||||
|
||||
```typescript
|
||||
const engine = new EngineManager(container);
|
||||
engine.initialize();
|
||||
engine.loadModel('model.gltf', { autoFit: true });
|
||||
engine.activateMeasure('distance');
|
||||
- 不再提供 `activateMeasure()/activeSection()/setWalkSpeed()` 等透传 API
|
||||
- 这些能力由内部 manager 直接调用 `engineComponent`
|
||||
|
||||
---
|
||||
|
||||
## 各管理器职责分层
|
||||
|
||||
| 类别 | 代表 Manager | 主要职责 |
|
||||
|---|---|---|
|
||||
| 核心入口 | `EngineManager` | 引擎生命周期与外部 API |
|
||||
| UI 容器 | `ToolbarManager`, `DialogManager`, `ButtonGroupManager` | UI 容器与通用交互 |
|
||||
| 业务编排 | `MeasureDialogManager`, `Section*DialogManager`, `WalkControlManager`, `SettingDialogManager` | 对话框/面板回调与引擎能力编排 |
|
||||
| 数据/交互 | `ConstructTreeManagerBtn`, `ComponentDetailManager`, `RightKeyManager` | 构件树、属性、右键菜单 |
|
||||
|
||||
---
|
||||
|
||||
## 推荐调用方式
|
||||
|
||||
### Manager 内部
|
||||
|
||||
```ts
|
||||
// 推荐
|
||||
this.engineComponent?.setSectionBoxRange(range);
|
||||
|
||||
// 不推荐
|
||||
this.registry.engine3d?.setSectionBoxRange(range);
|
||||
```
|
||||
|
||||
### 非 Manager 组件
|
||||
|
||||
```ts
|
||||
registry.engine3d?.getEngineComponent()?.toggleMiniMap();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ToolbarManager
|
||||
## 迁移约束(团队规范)
|
||||
|
||||
### 概述
|
||||
新增功能时遵循:
|
||||
|
||||
管理底部工具栏的按钮配置、显示控制、主题管理。
|
||||
|
||||
### API
|
||||
|
||||
```typescript
|
||||
class ToolbarManager extends BaseManager {
|
||||
addGroup(groupId: string, beforeGroupId?: string): void;
|
||||
addButton(config: ButtonConfig): void;
|
||||
setBtnActive(id: string, active?: boolean): void;
|
||||
setButtonVisibility(id: string, visible: boolean): void;
|
||||
setShowLabel(show: boolean): void;
|
||||
updateTheme(theme: ThemeConfig): void;
|
||||
setBackgroundColor(color: string): void;
|
||||
setColors(colors: ButtonGroupColors): void;
|
||||
show(): void;
|
||||
hide(): void;
|
||||
setVisible(visible: boolean): void;
|
||||
refresh(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
```
|
||||
|
||||
### 使用示例
|
||||
|
||||
```typescript
|
||||
const toolbar = new ToolbarManager(container);
|
||||
|
||||
toolbar.addGroup('custom-group');
|
||||
toolbar.addButton({
|
||||
id: 'custom-btn',
|
||||
groupId: 'custom-group',
|
||||
type: 'button',
|
||||
label: 'Custom',
|
||||
icon: '<svg>...</svg>',
|
||||
onClick: () => console.log('Clicked')
|
||||
});
|
||||
|
||||
toolbar.setBtnActive('custom-btn', true);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DialogManager
|
||||
|
||||
### 概述
|
||||
|
||||
创建和管理通用对话框实例。
|
||||
|
||||
### API
|
||||
|
||||
```typescript
|
||||
class DialogManager extends BaseManager {
|
||||
create(options: DialogOptions): BimDialog;
|
||||
showInfoDialog(): void;
|
||||
updateTheme(theme: ThemeConfig): void;
|
||||
destroy(): void;
|
||||
}
|
||||
```
|
||||
|
||||
### 使用示例
|
||||
|
||||
```typescript
|
||||
const dialogManager = new DialogManager(container);
|
||||
|
||||
const dialog = dialogManager.create({
|
||||
title: 'My Dialog',
|
||||
content: element,
|
||||
width: 400,
|
||||
onClose: () => console.log('Closed')
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MeasureDialogManager
|
||||
|
||||
### 概述
|
||||
|
||||
管理测量工具的对话框和测量面板。
|
||||
|
||||
### API
|
||||
|
||||
```typescript
|
||||
class MeasureDialogManager extends BaseDialogManager {
|
||||
getActiveMode(): MeasureMode;
|
||||
switchMode(mode: MeasureMode): void;
|
||||
setMeasureResult(result: MeasureResult | null): void;
|
||||
getConfig(): MeasureConfig;
|
||||
setConfig(partial: Partial<MeasureConfig>, persist?: boolean): void;
|
||||
clearAll(): void;
|
||||
openSettings(): void;
|
||||
show(): void;
|
||||
hide(): void;
|
||||
toggle(): void;
|
||||
}
|
||||
```
|
||||
|
||||
### 测量模式
|
||||
|
||||
| 模式 | 说明 |
|
||||
|------|------|
|
||||
| `distance` | 距离测量 |
|
||||
| `minDistance` | 最小距离 |
|
||||
| `angle` | 角度测量 |
|
||||
| `elevation` | 标高测量 |
|
||||
| `volume` | 体积测量 |
|
||||
| `laserDistance` | 激光测距 |
|
||||
| `slope` | 坡度测量 |
|
||||
| `spaceVolume` | 空间体积 |
|
||||
|
||||
### 使用示例
|
||||
|
||||
```typescript
|
||||
const measure = new MeasureDialogManager();
|
||||
measure.show();
|
||||
measure.switchMode('distance');
|
||||
|
||||
const config = measure.getConfig();
|
||||
measure.setConfig({ unit: 'cm', precision: 1 });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SectionPlaneDialogManager
|
||||
|
||||
### 概述
|
||||
|
||||
管理平面剖切工具对话框。
|
||||
|
||||
### API
|
||||
|
||||
```typescript
|
||||
class SectionPlaneDialogManager extends BaseDialogManager {
|
||||
show(): void;
|
||||
hide(): void;
|
||||
toggle(): void;
|
||||
isOpen(): boolean;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SectionAxisDialogManager
|
||||
|
||||
### 概述
|
||||
|
||||
管理轴向剖切工具对话框。
|
||||
|
||||
### API
|
||||
|
||||
```typescript
|
||||
class SectionAxisDialogManager extends BaseDialogManager {
|
||||
getHiddenState(): boolean;
|
||||
setHiddenState(isHidden: boolean): void;
|
||||
getActiveAxis(): 'x' | 'y' | 'z';
|
||||
setActiveAxis(axis: 'x' | 'y' | 'z'): void;
|
||||
show(): void;
|
||||
hide(): void;
|
||||
toggle(): void;
|
||||
}
|
||||
```
|
||||
|
||||
### 使用示例
|
||||
|
||||
```typescript
|
||||
const sectionAxis = new SectionAxisDialogManager();
|
||||
sectionAxis.show();
|
||||
sectionAxis.setActiveAxis('y');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SectionBoxDialogManager
|
||||
|
||||
### 概述
|
||||
|
||||
管理剖切盒工具对话框。
|
||||
|
||||
### API
|
||||
|
||||
```typescript
|
||||
class SectionBoxDialogManager extends BaseDialogManager {
|
||||
getHiddenState(): boolean;
|
||||
setHiddenState(isHidden: boolean): void;
|
||||
getReversedState(): boolean;
|
||||
setReversedState(isReversed: boolean): void;
|
||||
getRange(): SectionBoxRange;
|
||||
setRange(range: Partial<SectionBoxRange>): void;
|
||||
show(): void;
|
||||
hide(): void;
|
||||
toggle(): void;
|
||||
}
|
||||
|
||||
interface SectionBoxRange {
|
||||
minX: number;
|
||||
maxX: number;
|
||||
minY: number;
|
||||
maxY: number;
|
||||
minZ: number;
|
||||
maxZ: number;
|
||||
}
|
||||
```
|
||||
|
||||
### 使用示例
|
||||
|
||||
```typescript
|
||||
const sectionBox = new SectionBoxDialogManager();
|
||||
sectionBox.show();
|
||||
|
||||
const range = sectionBox.getRange();
|
||||
sectionBox.setRange({ minX: 0, maxX: 100 });
|
||||
sectionBox.setReversedState(true);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## WalkControlManager
|
||||
|
||||
### 概述
|
||||
|
||||
管理漫游控制面板和相关交互。
|
||||
|
||||
### API
|
||||
|
||||
```typescript
|
||||
class WalkControlManager extends BaseManager {
|
||||
init(): void;
|
||||
show(): void;
|
||||
hide(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
```
|
||||
|
||||
### 事件
|
||||
|
||||
| 事件 | 说明 |
|
||||
|------|------|
|
||||
| `walk:plan-view-toggle` | 平面图切换 |
|
||||
| `walk:path-mode-toggle` | 路径模式切换 |
|
||||
| `walk:walk-mode-toggle` | 漫游模式切换 |
|
||||
| `walk:speed-change` | 速度变化 |
|
||||
| `walk:gravity-toggle` | 重力开关 |
|
||||
| `walk:collision-toggle` | 碰撞开关 |
|
||||
|
||||
---
|
||||
|
||||
## ComponentDetailManager
|
||||
|
||||
### 概述
|
||||
|
||||
管理构件详情弹窗(属性展示)的显示。
|
||||
|
||||
### API
|
||||
|
||||
```typescript
|
||||
class ComponentDetailManager extends BaseManager {
|
||||
init(): void;
|
||||
show(): void;
|
||||
isOpen(): boolean;
|
||||
hide(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ConstructTreeManagerBtn
|
||||
|
||||
### 概述
|
||||
|
||||
管理构件树按钮和对话框。
|
||||
|
||||
### API
|
||||
|
||||
```typescript
|
||||
class ConstructTreeManagerBtn extends BaseManager {
|
||||
openConstructTreeDialog(): void;
|
||||
addGroup(groupId: string, beforeGroupId?: string): void;
|
||||
addButton(config: ButtonConfig): void;
|
||||
setButtonVisibility(id: string, visible: boolean): void;
|
||||
setShowLabel(show: boolean): void;
|
||||
setVisible(visible: boolean): void;
|
||||
setBackgroundColor(color: string): void;
|
||||
setColors(colors: ButtonGroupColors): void;
|
||||
refresh(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## RightKeyManager
|
||||
|
||||
### 概述
|
||||
|
||||
管理右键上下文菜单。
|
||||
|
||||
### API
|
||||
|
||||
```typescript
|
||||
class RightKeyManager extends BaseManager {
|
||||
registerHandler(handler: ContextMenuHandler): void;
|
||||
showMenu(x: number, y: number, items: MenuItemConfig[], groupOrder?: string[]): void;
|
||||
hide(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
type ContextMenuHandler = (e: MouseEvent) => MenuItemConfig[];
|
||||
```
|
||||
|
||||
### 使用示例
|
||||
|
||||
```typescript
|
||||
const rightKey = new RightKeyManager(container);
|
||||
|
||||
rightKey.registerHandler((e) => [
|
||||
{ id: 'view', label: 'View', onClick: () => {} },
|
||||
{ id: 'delete', label: 'Delete', onClick: () => {} }
|
||||
]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ButtonGroupManager
|
||||
|
||||
### 概述
|
||||
|
||||
创建和管理多个按钮组实例。
|
||||
|
||||
### API
|
||||
|
||||
```typescript
|
||||
class ButtonGroupManager extends BaseManager {
|
||||
create(id: string, options: ButtonGroupOptions): BimButtonGroup;
|
||||
get(id: string): BimButtonGroup | undefined;
|
||||
updateTheme(theme: ThemeConfig): void;
|
||||
destroy(): void;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 交互流程
|
||||
|
||||
### 工具栏按钮触发测量
|
||||
|
||||
```
|
||||
ToolbarManager (按钮点击)
|
||||
↓
|
||||
MeasureDialogManager.show()
|
||||
↓
|
||||
onModeChange 事件
|
||||
↓
|
||||
EngineManager.activateMeasure(mode)
|
||||
↓
|
||||
3D 引擎进入测量模式
|
||||
```
|
||||
|
||||
### 漫游模式激活
|
||||
|
||||
```
|
||||
ToolbarManager (walk 按钮)
|
||||
↓
|
||||
WalkControlManager.show()
|
||||
↓
|
||||
隐藏底部工具栏
|
||||
↓
|
||||
显示漫游控制面板
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 依赖关系
|
||||
|
||||
所有 Manager 都依赖:
|
||||
- `BaseManager` 或 `BaseDialogManager`
|
||||
- `ManagerRegistry`
|
||||
|
||||
各 Manager 之间通过事件系统通信,避免直接依赖。
|
||||
|
||||
---
|
||||
|
||||
**文档生成时间**: 2026-01-23
|
||||
1. 先在 `Engine` 组件实现能力
|
||||
2. 内部 manager 通过 `engineComponent` 访问
|
||||
3. 仅当确有外部 SDK 需求时,才新增 `EngineManager` 公共方法
|
||||
4. 不新增大规模透传方法
|
||||
|
||||
716
docs/引擎API对接.md
716
docs/引擎API对接.md
@@ -1,679 +1,173 @@
|
||||
# Engine API 接口对接指南
|
||||
# Engine API 对接指南(2026-03)
|
||||
|
||||
本文档详细说明 iflow-engine SDK 中 Toolbar 按钮如何调用底层 3D 引擎 API 的完整调用链。
|
||||
本文档说明 SDK 层与底层 `iflow-engine-base` 的最新对接方式。
|
||||
|
||||
## 核心原则
|
||||
|
||||
1. `Engine` 组件是底层能力封装层(状态管理 + 参数转换 + 底层调用)
|
||||
2. `EngineManager` 只保留少量对外公共 API,不再做全量透传
|
||||
3. 内部 Manager 直接通过 `this.engineComponent` 调 `Engine`
|
||||
4. 非 Manager 组件通过 `registry.engine3d?.getEngineComponent()` 调 `Engine`
|
||||
5. 不暴露 raw engine 实例,事件订阅走 `onRawEvent/offRawEvent`
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
## 对接层级
|
||||
|
||||
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() 创建实例 │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```text
|
||||
L1 UI 层(Button/Panel/Dialog)
|
||||
-> L2 Manager 层(业务编排)
|
||||
-> L3 Engine 组件(统一封装)
|
||||
-> L4 iflow-engine-base(底层能力)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 调用链层级
|
||||
## A. 对外 API(EngineManager)
|
||||
|
||||
| 层级 | 文件位置 | 职责 |
|
||||
|------|----------|------|
|
||||
| **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 渲染和功能实现 |
|
||||
对外稳定入口:`bimEngine.engine`
|
||||
|
||||
---
|
||||
|
||||
## 完整调用链示例:剖切盒
|
||||
|
||||
以剖切盒(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: () => {
|
||||
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);
|
||||
}
|
||||
```ts
|
||||
interface EngineManagerPublicApi {
|
||||
initialize(options?: Omit<EngineOptions, 'container'>): boolean;
|
||||
isInitialized(): boolean;
|
||||
loadModel(urls: string[], options?: ModelLoadOptions): void;
|
||||
pauseRendering(): void;
|
||||
resumeRendering(): void;
|
||||
getEngineComponent(): Engine | null;
|
||||
destroy(): void;
|
||||
}
|
||||
```
|
||||
|
||||
**要点**:
|
||||
- 继承 `BaseDialogManager`,自动获得 `show()/hide()/toggle()` 方法
|
||||
- 通过 `this.registry.engine3d` 访问 EngineManager
|
||||
- Panel 的回调函数中调用 EngineManager 方法
|
||||
- 生命周期钩子:`onDialogCreated` / `onBeforeDestroy` / `onDialogClose`
|
||||
`EngineManager` 内部仍负责:
|
||||
|
||||
### 3. EngineManager 封装
|
||||
- 创建 `Engine` 实例
|
||||
- 创建并持有 `RightKeyManager`
|
||||
- 组装右键菜单逻辑
|
||||
|
||||
**文件**: `src/managers/engine-manager.ts`
|
||||
---
|
||||
|
||||
```typescript
|
||||
export class EngineManager extends BaseManager {
|
||||
private engineInstance: Engine | null = null;
|
||||
## B. 内部 Manager 对接(推荐)
|
||||
|
||||
// 激活剖切(统一入口)
|
||||
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 组件
|
||||
}
|
||||
`BaseManager` 已提供:
|
||||
|
||||
// 获取当前剖切模式
|
||||
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);
|
||||
}
|
||||
|
||||
// ... 其他方法
|
||||
}
|
||||
```ts
|
||||
protected get engineComponent(): Engine | null;
|
||||
```
|
||||
|
||||
**要点**:
|
||||
- 每个方法都要检查 `engineInstance` 是否存在
|
||||
- 方法签名与 Engine 组件一致,起到代理作用
|
||||
- 负责错误处理和日志输出
|
||||
### 示例:剖切盒
|
||||
|
||||
### 4. Engine 组件实现
|
||||
|
||||
**文件**: `src/components/engine/index.ts`
|
||||
|
||||
```typescript
|
||||
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
|
||||
}
|
||||
}
|
||||
```ts
|
||||
this.engineComponent?.activeSection('box');
|
||||
this.engineComponent?.setSectionBoxRange(range);
|
||||
this.engineComponent?.deactivateSection();
|
||||
```
|
||||
|
||||
**要点**:
|
||||
- 维护功能状态(`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
|
||||
```ts
|
||||
this.engineComponent?.activateFirstPersonMode();
|
||||
this.engineComponent?.setWalkSpeed(speed);
|
||||
this.engineComponent?.deactivateFirstPersonMode();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 各层职责说明
|
||||
## C. 非 Manager 组件对接
|
||||
|
||||
### L1: Toolbar Button
|
||||
适用于 toolbar 按钮、独立组件(如 `WalkPathPanel`):
|
||||
|
||||
| 职责 | 说明 |
|
||||
|------|------|
|
||||
| 定义按钮配置 | `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 渲染 |
|
||||
| 功能实现 | 测量、剖切、漫游等实际功能 |
|
||||
| 场景管理 | 模型加载、相机控制等 |
|
||||
```ts
|
||||
registry.engine3d?.getEngineComponent()?.CameraGoHome();
|
||||
registry.engine3d?.getEngineComponent()?.activateZoomBox();
|
||||
registry.engine3d?.getEngineComponent()?.toggleMiniMap();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 新增功能对接步骤
|
||||
## D. 原始事件对接(替代 getEngine)
|
||||
|
||||
以新增一个 "XX 功能" 为例:
|
||||
`Engine.getEngine()` 已删除,改为事件桥接:
|
||||
|
||||
### 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();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
```ts
|
||||
engineComponent?.onRawEvent('measure-changed', handler);
|
||||
engineComponent?.offRawEvent('measure-changed', handler);
|
||||
```
|
||||
|
||||
### Step 2: 注册按钮到 Toolbar
|
||||
适用场景:
|
||||
|
||||
**文件**: `src/components/button-group/toolbar/index.ts`
|
||||
- 测量回调(`measure-changed` / `measure-click`)
|
||||
- 剖切盒拖动回调(`section-move`)
|
||||
|
||||
```typescript
|
||||
// 添加导入
|
||||
const { createXxButton } = await import('./buttons/xx');
|
||||
---
|
||||
|
||||
// 添加按钮
|
||||
this.addButton(createXxButton());
|
||||
```
|
||||
## E. 新功能对接步骤(最新模板)
|
||||
|
||||
### Step 3: 创建 DialogManager
|
||||
### Step 1: 在 Engine 组件实现能力
|
||||
|
||||
**文件**: `src/managers/xx-dialog-manager.ts`
|
||||
文件:`src/components/engine/index.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
|
||||
```ts
|
||||
public activateXx(): void {
|
||||
if (!this.engineInstance) {
|
||||
console.warn('[EngineManager] 3D Engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engineInstance.activateXx();
|
||||
if (!this._isInitialized || !this.engine?.xxModule) return;
|
||||
this.engine.xxModule.active();
|
||||
}
|
||||
|
||||
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();
|
||||
if (!this._isInitialized || !this.engine?.xxModule) return;
|
||||
this.engine.xxModule.disActive();
|
||||
}
|
||||
```
|
||||
|
||||
### Step 7: 在 Engine 组件中实现
|
||||
### Step 2: 在 Manager 中调用(不要新增 EngineManager 透传)
|
||||
|
||||
**文件**: `src/components/engine/index.ts`
|
||||
文件:`src/managers/xx-dialog-manager.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;
|
||||
```ts
|
||||
protected onDialogCreated(): void {
|
||||
this.engineComponent?.activateXx();
|
||||
}
|
||||
|
||||
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();
|
||||
protected onBeforeDestroy(): void {
|
||||
this.engineComponent?.deactivateXx();
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: 若是非 Manager 组件,走 getEngineComponent
|
||||
|
||||
```ts
|
||||
registry.engine3d?.getEngineComponent()?.activateXx();
|
||||
```
|
||||
|
||||
### Step 4: 仅当需要“对外 SDK 公共 API”时,再考虑给 EngineManager 增加方法
|
||||
|
||||
判定标准:
|
||||
|
||||
- 该能力是否需要 `bimEngine.engine?.xxx()` 供外部用户直接调用
|
||||
- 若仅为内部链路使用,不应在 `EngineManager` 再包一层
|
||||
|
||||
---
|
||||
|
||||
## 数据流向
|
||||
## F. 常见反模式
|
||||
|
||||
### 用户操作 → 底层引擎
|
||||
- ❌ 在 `EngineManager` 里新增大批 `this.engineInstance?.xxx()` 透传
|
||||
- ❌ 内部 Manager 继续写 `this.registry.engine3d?.xxx()`
|
||||
- ❌ 通过 `Engine.getEngine()` 暴露 raw engine
|
||||
|
||||
```
|
||||
用户点击按钮
|
||||
↓
|
||||
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)
|
||||
```
|
||||
- ✅ 内部 Manager 使用 `this.engineComponent?.xxx()`
|
||||
- ✅ 非 Manager 使用 `registry.engine3d?.getEngineComponent()?.xxx()`
|
||||
- ✅ 原始事件使用 `onRawEvent/offRawEvent`
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
## G. 验收清单
|
||||
|
||||
### 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 |
|
||||
| **底层引擎** | 实际功能实现 |
|
||||
|
||||
遵循这个分层架构,可以保持代码的清晰性和可维护性。
|
||||
1. `EngineManager` 是否只保留必要公共 API
|
||||
2. Manager 层是否统一走 `engineComponent`
|
||||
3. 是否无 `getEngine()` 调用
|
||||
4. 事件订阅是否成对 `onRawEvent/offRawEvent`
|
||||
5. `npm run build` 是否通过
|
||||
|
||||
639
docs/架构设计.md
639
docs/架构设计.md
@@ -1,502 +1,153 @@
|
||||
# iflow-engine SDK 架构文档
|
||||
# iflow-engine SDK 架构设计
|
||||
|
||||
## 概述
|
||||
|
||||
iflow-engine 是一个面向开发者的 BIM 3D 引擎 SDK,采用分层架构设计,提供模型查看、测量、剖切、漫游等功能。
|
||||
iflow-engine 采用分层架构:`BimEngine` 作为入口,`ManagerRegistry` 作为实例级注册表,Manager 层负责编排,Component 层负责 UI/引擎封装,Services/Core 层提供基础能力。
|
||||
|
||||
## 架构图
|
||||
2026-03 的核心调整:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ BimEngine │
|
||||
│ (主入口 / 门面模式) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ ManagerRegistry │ │
|
||||
│ │ (单例 / 全局注册表) │ │
|
||||
│ │ ┌─────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ EventEmitter (事件总线) │ │ │
|
||||
│ │ └─────────────────────────────────────────────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ Managers Layer │ │
|
||||
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
|
||||
│ │ │EngineManager │ │ToolbarManager│ │ DialogManager│ │ │
|
||||
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
|
||||
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
|
||||
│ │ │MeasureDialog │ │SectionDialog │ │WalkControl │ │ │
|
||||
│ │ │Manager │ │Manager │ │Manager │ │ │
|
||||
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
|
||||
│ │ ... (15 个管理器) │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ Components Layer │ │
|
||||
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │
|
||||
│ │ │ Engine │ │ Dialog │ │ Tree │ │ Menu │ │ButtonGr│ │ │
|
||||
│ │ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ │ │
|
||||
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │
|
||||
│ │ │Collapse│ │ Tab │ │Descript│ │Measure │ │Section │ │ │
|
||||
│ │ │ │ │ │ │ │ │Panel │ │Panel │ │ │
|
||||
│ │ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ │ │
|
||||
│ │ ... (20+ 个组件) │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ Services Layer │ │
|
||||
│ │ ┌──────────────────┐ ┌──────────────────┐ │ │
|
||||
│ │ │ LocaleManager │ │ ThemeManager │ │ │
|
||||
│ │ │ (国际化服务) │ │ (主题服务) │ │ │
|
||||
│ │ └──────────────────┘ └──────────────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ Core Layer │ │
|
||||
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
|
||||
│ │ │ EventEmitter │ │ BaseManager │ │BaseDialog │ │ │
|
||||
│ │ │ │ │ │ │Manager │ │ │
|
||||
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ External Dependencies │
|
||||
│ ┌──────────────────────┐ ┌──────────────────────┐ │
|
||||
│ │ iflow-engine-base │ │ Three.js │ │
|
||||
│ │ (第三方 3D 引擎核心) │ │ (3D 渲染库) │ │
|
||||
│ └──────────────────────┘ └──────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 分层说明
|
||||
|
||||
### 1. BimEngine (入口层)
|
||||
|
||||
**职责**: 作为 SDK 的唯一入口,整合所有功能,提供简洁的公开 API。
|
||||
|
||||
**设计模式**: 门面模式 (Facade Pattern)
|
||||
|
||||
**核心 API**:
|
||||
- 初始化引擎和容器
|
||||
- 设置主题和语言
|
||||
- 访问各个管理器
|
||||
|
||||
```typescript
|
||||
const engine = new BimEngine('container', {
|
||||
locale: 'zh-CN',
|
||||
theme: 'dark'
|
||||
});
|
||||
|
||||
// 访问管理器
|
||||
engine.engine.loadModel('model.gltf');
|
||||
engine.toolbar.show();
|
||||
engine.measure.show();
|
||||
```
|
||||
|
||||
### 2. ManagerRegistry (注册表层)
|
||||
|
||||
**职责**: 全局单例,集中管理所有 Manager 实例,提供跨 Manager 通信。
|
||||
|
||||
**设计模式**: 单例模式 (Singleton) + 服务定位器 (Service Locator)
|
||||
|
||||
**特点**:
|
||||
- 避免 Manager 之间的循环依赖
|
||||
- 提供类型安全的事件系统
|
||||
- 支持动态访问任意 Manager
|
||||
|
||||
```typescript
|
||||
const registry = ManagerRegistry.getInstance();
|
||||
registry.toolbar?.show();
|
||||
registry.emit('engine:model-loaded', { url: 'model.gltf' });
|
||||
```
|
||||
|
||||
### 3. Managers Layer (管理器层)
|
||||
|
||||
**职责**: 处理业务逻辑,协调组件交互,管理组件生命周期。
|
||||
|
||||
**设计模式**: 模板方法模式 (Template Method)
|
||||
|
||||
**管理器分类**:
|
||||
|
||||
| 类别 | 管理器 | 职责 |
|
||||
|------|--------|------|
|
||||
| 核心 | EngineManager | 3D 引擎管理 |
|
||||
| UI 容器 | ToolbarManager, DialogManager, ButtonGroupManager | 容器组件管理 |
|
||||
| 交互 | RightKeyManager, ComponentDetailManager, ConstructTreeManagerBtn | 用户交互 |
|
||||
| 工具 | MeasureDialogManager, SectionPlaneDialogManager, SectionAxisDialogManager, SectionBoxDialogManager | 3D 操作工具 |
|
||||
| 漫游 | WalkControlManager, WalkPathDialogManager, WalkPlanViewDialogManager, MapDialogManager | 漫游功能 |
|
||||
|
||||
### 4. Components Layer (组件层)
|
||||
|
||||
**职责**: 纯 UI 渲染,不包含业务逻辑。
|
||||
|
||||
**设计模式**: 组合模式 (Composite Pattern)
|
||||
|
||||
**组件接口**:
|
||||
```typescript
|
||||
interface IBimComponent {
|
||||
element: HTMLElement;
|
||||
init(): void;
|
||||
setTheme(theme: ThemeConfig): void;
|
||||
setLocales?(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
```
|
||||
|
||||
**组件分类**:
|
||||
|
||||
| 类别 | 组件 | 说明 |
|
||||
|------|------|------|
|
||||
| 核心 | Engine | 3D 视口容器 |
|
||||
| 容器 | Dialog, Tree, Menu, ButtonGroup | 承载内容的容器组件 |
|
||||
| 面板 | MeasurePanel, SectionPlanePanel, WalkControlPanel | 功能面板 |
|
||||
| 展示 | Collapse, Tab, Description | 数据展示组件 |
|
||||
|
||||
### 5. Services Layer (服务层)
|
||||
|
||||
**职责**: 提供横切关注点的全局服务。
|
||||
|
||||
**设计模式**: 单例模式 + 观察者模式
|
||||
|
||||
| 服务 | 职责 |
|
||||
|------|------|
|
||||
| LocaleManager | 国际化,多语言支持 |
|
||||
| ThemeManager | 主题管理,明暗切换 |
|
||||
|
||||
### 6. Core Layer (核心层)
|
||||
|
||||
**职责**: 提供基础设施和抽象。
|
||||
|
||||
| 类 | 职责 |
|
||||
|-----|------|
|
||||
| EventEmitter | 事件发布/订阅 |
|
||||
| BaseManager | Manager 基类,提供注册表访问和事件管理 |
|
||||
| BaseDialogManager | 对话框 Manager 基类,封装对话框生命周期 |
|
||||
|
||||
## 设计模式详解
|
||||
|
||||
### 1. 单例模式 (Singleton)
|
||||
|
||||
**应用**: ManagerRegistry, LocaleManager, ThemeManager
|
||||
|
||||
**目的**: 确保全局唯一实例,提供全局访问点。
|
||||
|
||||
```typescript
|
||||
export class ManagerRegistry {
|
||||
private static instance: ManagerRegistry | null = null;
|
||||
|
||||
public static getInstance(): ManagerRegistry {
|
||||
if (!ManagerRegistry.instance) {
|
||||
ManagerRegistry.instance = new ManagerRegistry();
|
||||
}
|
||||
return ManagerRegistry.instance;
|
||||
}
|
||||
|
||||
public static reset(): void {
|
||||
ManagerRegistry.instance = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 观察者模式 (Observer)
|
||||
|
||||
**应用**: EventEmitter, 服务订阅
|
||||
|
||||
**目的**: 解耦事件发送者和接收者,支持多对多通信。
|
||||
|
||||
```typescript
|
||||
// 订阅
|
||||
const unsubscribe = registry.on('engine:model-loaded', (payload) => {
|
||||
console.log('Model loaded:', payload.url);
|
||||
});
|
||||
|
||||
// 发布
|
||||
registry.emit('engine:model-loaded', { url: 'model.gltf' });
|
||||
|
||||
// 取消订阅
|
||||
unsubscribe();
|
||||
```
|
||||
|
||||
### 3. 模板方法模式 (Template Method)
|
||||
|
||||
**应用**: BaseManager, BaseDialogManager
|
||||
|
||||
**目的**: 定义算法骨架,允许子类重写特定步骤。
|
||||
|
||||
```typescript
|
||||
export abstract class BaseDialogManager extends BaseManager {
|
||||
// 模板方法
|
||||
public show(): void {
|
||||
this.destroyDialog();
|
||||
const content = this.createContent(); // 抽象方法
|
||||
this.createDialog(content);
|
||||
this.onDialogCreated(); // 钩子方法
|
||||
}
|
||||
|
||||
// 抽象方法 - 子类必须实现
|
||||
protected abstract createContent(): HTMLElement;
|
||||
|
||||
// 钩子方法 - 子类可选重写
|
||||
protected onDialogCreated(): void {}
|
||||
protected onDialogClose(): void {}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 工厂模式 (Factory)
|
||||
|
||||
**应用**: BimEngine 初始化
|
||||
|
||||
**目的**: 封装对象创建逻辑,统一创建流程。
|
||||
|
||||
```typescript
|
||||
private init() {
|
||||
this.engine = new EngineManager(this.wrapper);
|
||||
this.dialog = new DialogManager(this.wrapper);
|
||||
this.toolbar = new ToolbarManager(this.wrapper);
|
||||
// ... 创建其他管理器
|
||||
}
|
||||
```
|
||||
|
||||
## 数据流
|
||||
|
||||
### 1. 用户交互流
|
||||
|
||||
```
|
||||
用户操作 (点击按钮)
|
||||
↓
|
||||
Toolbar 组件捕获事件
|
||||
↓
|
||||
ToolbarManager 处理
|
||||
↓
|
||||
发送事件到 Registry
|
||||
↓
|
||||
目标 Manager 响应
|
||||
↓
|
||||
更新相关组件 UI
|
||||
```
|
||||
|
||||
### 2. 主题变更流
|
||||
|
||||
```
|
||||
用户调用 setTheme('dark')
|
||||
↓
|
||||
ThemeManager.setTheme()
|
||||
↓
|
||||
更新内部状态
|
||||
↓
|
||||
通知所有订阅者
|
||||
↓
|
||||
各组件 setTheme() 被调用
|
||||
↓
|
||||
更新 CSS 变量和样式
|
||||
```
|
||||
|
||||
### 3. 国际化变更流
|
||||
|
||||
```
|
||||
用户调用 setLocale('en-US')
|
||||
↓
|
||||
LocaleManager.setLocale()
|
||||
↓
|
||||
更新内部状态
|
||||
↓
|
||||
通知所有订阅者
|
||||
↓
|
||||
各组件 setLocales() 被调用
|
||||
↓
|
||||
重新渲染文本内容
|
||||
```
|
||||
|
||||
## 模块依赖关系
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
BimEngine --> ManagerRegistry
|
||||
BimEngine --> LocaleManager
|
||||
BimEngine --> ThemeManager
|
||||
|
||||
ManagerRegistry --> EventEmitter
|
||||
|
||||
EngineManager --> BaseManager
|
||||
ToolbarManager --> BaseManager
|
||||
DialogManager --> BaseManager
|
||||
|
||||
MeasureDialogManager --> BaseDialogManager
|
||||
SectionPlaneDialogManager --> BaseDialogManager
|
||||
WalkControlManager --> BaseManager
|
||||
|
||||
BaseDialogManager --> BaseManager
|
||||
BaseManager --> ManagerRegistry
|
||||
|
||||
Engine --> ThemeManager
|
||||
Dialog --> ThemeManager
|
||||
Tree --> ThemeManager
|
||||
Tree --> LocaleManager
|
||||
Menu --> ThemeManager
|
||||
Menu --> LocaleManager
|
||||
|
||||
ThemeManager --> darkTheme
|
||||
ThemeManager --> lightTheme
|
||||
LocaleManager --> zhCN
|
||||
LocaleManager --> enUS
|
||||
```
|
||||
|
||||
## 扩展点
|
||||
|
||||
### 1. 添加新 Manager
|
||||
|
||||
```typescript
|
||||
import { BaseManager } from '../core/base-manager';
|
||||
|
||||
export class CustomManager extends BaseManager {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
this.subscribe('engine:model-loaded', this.onModelLoaded.bind(this));
|
||||
}
|
||||
|
||||
private onModelLoaded(payload: any): void {
|
||||
// 处理逻辑
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 添加新对话框 Manager
|
||||
|
||||
```typescript
|
||||
import { BaseDialogManager } from '../core/base-dialog-manager';
|
||||
|
||||
export class CustomDialogManager extends BaseDialogManager {
|
||||
protected get dialogId(): string {
|
||||
return 'custom-dialog';
|
||||
}
|
||||
|
||||
protected get dialogTitle(): string {
|
||||
return 'custom.dialogTitle';
|
||||
}
|
||||
|
||||
protected createContent(): HTMLElement {
|
||||
const div = document.createElement('div');
|
||||
// 创建内容
|
||||
return div;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 添加新组件
|
||||
|
||||
```typescript
|
||||
import type { ThemeConfig } from '../themes/types';
|
||||
|
||||
export class CustomComponent {
|
||||
public element: HTMLElement;
|
||||
|
||||
constructor(private options: CustomOptions) {
|
||||
this.element = document.createElement('div');
|
||||
}
|
||||
|
||||
public init(): void {
|
||||
// 初始化
|
||||
}
|
||||
|
||||
public setTheme(theme: ThemeConfig): void {
|
||||
// 应用主题
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.element.remove();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 添加新语言
|
||||
|
||||
1. 创建语言文件 `src/locales/ja-JP.ts`
|
||||
2. 更新类型 `src/locales/types.ts`
|
||||
3. 注册到 LocaleManager
|
||||
|
||||
### 5. 添加新主题
|
||||
|
||||
```typescript
|
||||
import { createThemeFromPartial, lightTheme } from 'iflow-engine';
|
||||
|
||||
const customTheme = createThemeFromPartial(lightTheme, {
|
||||
name: 'custom',
|
||||
primary: '#ff6b6b',
|
||||
// 其他自定义属性
|
||||
});
|
||||
|
||||
themeManager.setCustomTheme(customTheme);
|
||||
```
|
||||
|
||||
## 事件系统
|
||||
|
||||
### 事件类型
|
||||
|
||||
| 类别 | 事件 | 数据 |
|
||||
|------|------|------|
|
||||
| UI | `ui:open-dialog` | `{ id: string; data?: any }` |
|
||||
| UI | `ui:close-dialog` | `{ id: string }` |
|
||||
| 引擎 | `engine:model-loaded` | `{ url: string }` |
|
||||
| 引擎 | `engine:object-clicked` | `{ objectId: string; position: Point3D }` |
|
||||
| 树 | `ui:tree-node-check` | `{ id: string; checked: boolean; node: any }` |
|
||||
| 树 | `ui:tree-node-select` | `{ id: string; selected: boolean; node: any }` |
|
||||
| 系统 | `sys:theme-changed` | `{ theme: string }` |
|
||||
| 系统 | `sys:locale-changed` | `{ locale: string }` |
|
||||
| 漫游 | `walk:speed-change` | `{ speed: number }` |
|
||||
| 漫游 | `walk:path-mode-toggle` | `{ isActive: boolean }` |
|
||||
| 地图 | `map:opened` | `{}` |
|
||||
| 地图 | `map:closed` | `{}` |
|
||||
|
||||
### 事件使用
|
||||
|
||||
```typescript
|
||||
// 发送事件
|
||||
registry.emit('engine:model-loaded', { url: 'model.gltf' });
|
||||
|
||||
// 订阅事件
|
||||
const unsubscribe = registry.on('engine:model-loaded', (payload) => {
|
||||
console.log('Model loaded:', payload.url);
|
||||
});
|
||||
|
||||
// 在 Manager 中使用
|
||||
this.subscribe('engine:model-loaded', this.handleModelLoaded.bind(this));
|
||||
this.emit('custom:action', { data: 'value' });
|
||||
```
|
||||
|
||||
## 性能考虑
|
||||
|
||||
### 1. 懒加载
|
||||
|
||||
- 组件按需创建,不预先实例化
|
||||
- 对话框在首次 show() 时才创建 DOM
|
||||
|
||||
### 2. 事件清理
|
||||
|
||||
- BaseManager 自动追踪订阅,destroy 时统一清理
|
||||
- 避免内存泄漏
|
||||
|
||||
### 3. DOM 操作优化
|
||||
|
||||
- 使用 requestAnimationFrame 节流拖拽/缩放
|
||||
- 批量 DOM 更新
|
||||
|
||||
### 4. 主题切换
|
||||
|
||||
- 使用 CSS 变量,避免重绘整个组件树
|
||||
- 订阅机制确保只更新必要的组件
|
||||
- `EngineManager` 从“大量透传”收敛为“少量公共 API + 生命周期编排”
|
||||
- 内部 Manager 不再通过 `registry.engine3d?.xxx()` 间接透传,而是通过 `BaseManager.engineComponent` 直接访问 `Engine` 组件
|
||||
- 非 Manager 组件(如 toolbar 按钮、walk-path-panel)通过 `registry.engine3d?.getEngineComponent()?.xxx()` 访问 `Engine`
|
||||
- `Engine.getEngine()` 已移除,改为 `onRawEvent()/offRawEvent()` 订阅底层事件
|
||||
|
||||
---
|
||||
|
||||
**文档生成时间**: 2026-01-23
|
||||
**文档版本**: 1.0.0
|
||||
## 分层结构
|
||||
|
||||
```text
|
||||
BimEngine (入口 / Facade)
|
||||
└─ ManagerRegistry (实例级,不是全局单例)
|
||||
├─ Managers (业务编排)
|
||||
│ ├─ EngineManager (公共 API + 初始化 + 右键菜单组装)
|
||||
│ ├─ *DialogManager / *ControlManager
|
||||
│ └─ ...
|
||||
├─ Components (UI + Engine 封装)
|
||||
│ ├─ Engine (封装 iflow-engine-base)
|
||||
│ ├─ Dialog/Toolbar/Panel...
|
||||
│ └─ ...
|
||||
├─ Services (Theme / Locale)
|
||||
└─ Core (EventEmitter / BaseManager / BaseDialogManager)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ManagerRegistry 设计
|
||||
|
||||
`ManagerRegistry` 是 **实例模式**:每个 `BimEngine` 都创建独立 registry,支持多实例隔离。
|
||||
|
||||
- ✅ 无全局共享状态污染
|
||||
- ✅ 多画布/多引擎实例可并行
|
||||
- ✅ 事件总线隔离
|
||||
|
||||
关键文件:`src/core/manager-registry.ts`
|
||||
|
||||
---
|
||||
|
||||
## Engine 访问策略(最新)
|
||||
|
||||
### 1) 外部 SDK 用户
|
||||
|
||||
通过 `bimEngine.engine`(`EngineManager`)调用稳定公共 API:
|
||||
|
||||
- `initialize(options?)`
|
||||
- `isInitialized()`
|
||||
- `loadModel(urls, options?)`
|
||||
- `pauseRendering()`
|
||||
- `resumeRendering()`
|
||||
- `destroy()`
|
||||
|
||||
### 2) 内部 Manager(BaseManager 子类)
|
||||
|
||||
通过 `BaseManager` 新增 getter:
|
||||
|
||||
```ts
|
||||
protected get engineComponent(): Engine | null
|
||||
```
|
||||
|
||||
直接调用 `Engine` 组件方法,例如:
|
||||
|
||||
```ts
|
||||
this.engineComponent?.activeSection('box');
|
||||
this.engineComponent?.setWalkSpeed(speed);
|
||||
```
|
||||
|
||||
### 3) 非 Manager 组件
|
||||
|
||||
通过 registry 获取组件实例:
|
||||
|
||||
```ts
|
||||
registry.engine3d?.getEngineComponent()?.toggleMiniMap();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## EngineManager 职责边界
|
||||
|
||||
`EngineManager` 现在聚焦在“入口能力 + 组装能力”:
|
||||
|
||||
1. 创建并持有 `Engine` 组件实例
|
||||
2. 初始化并持有 `RightKeyManager`
|
||||
3. 注册右键菜单项并编排右键行为
|
||||
4. 对外暴露少量稳定 API
|
||||
|
||||
不再承担:
|
||||
|
||||
- 对 `Engine` 全量方法的 1:1 透传
|
||||
|
||||
关键文件:`src/managers/engine-manager.ts`
|
||||
|
||||
---
|
||||
|
||||
## 原始引擎事件订阅策略
|
||||
|
||||
为避免暴露底层 raw engine 实例,`Engine` 组件提供:
|
||||
|
||||
- `onRawEvent(event, handler)`
|
||||
- `offRawEvent(event, handler)`
|
||||
|
||||
例如测量与剖切盒事件:
|
||||
|
||||
```ts
|
||||
this.engineComponent?.onRawEvent('measure-changed', handler);
|
||||
this.engineComponent?.offRawEvent('measure-changed', handler);
|
||||
```
|
||||
|
||||
关键文件:`src/components/engine/index.ts`
|
||||
|
||||
---
|
||||
|
||||
## 典型调用链
|
||||
|
||||
### Toolbar Home
|
||||
|
||||
```text
|
||||
Button onClick
|
||||
-> registry.engine3d?.getEngineComponent()?.CameraGoHome()
|
||||
-> Engine.CameraGoHome()
|
||||
-> rawEngine.viewCube.CameraGoHome()
|
||||
```
|
||||
|
||||
### Section Box
|
||||
|
||||
```text
|
||||
SectionBoxDialogManager (BaseDialogManager)
|
||||
-> this.engineComponent?.activeSection('box')
|
||||
-> Engine.activeSection('box')
|
||||
-> rawEngine.clipping.active('box')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 设计收益
|
||||
|
||||
- 降低中间层重复代码:`EngineManager` 大幅瘦身
|
||||
- 减少三层转发链:Manager 可直接访问 `Engine` 组件
|
||||
- 对外 API 稳定:仍保留 `bimEngine.engine` 作为入口
|
||||
- 保持封装边界:不暴露 raw engine,仅开放受控事件桥接
|
||||
|
||||
---
|
||||
|
||||
## 后续建议
|
||||
|
||||
当前已完成“Manager 瘦身”阶段。若后续继续演进,可考虑将 `Engine` 组件按领域继续拆分(measure/section/walk/model/scene),进一步降低单类复杂度。
|
||||
|
||||
@@ -12,7 +12,7 @@ export const createHomeButton = (registry: ManagerRegistry): ButtonConfig => {
|
||||
keepActive: false,
|
||||
onClick: (button) => {
|
||||
console.log('首页按钮被点击:', button.id);
|
||||
registry.engine3d?.CameraGoHome();
|
||||
registry.engine3d?.getEngineComponent()?.CameraGoHome();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ export const createMapButton = (registry: ManagerRegistry): ButtonConfig => {
|
||||
keepActive: true,
|
||||
icon: getIcon('地图'),
|
||||
onClick: () => {
|
||||
registry.engine3d?.toggleMiniMap();
|
||||
registry.engine3d?.getEngineComponent()?.toggleMiniMap();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ export const createZoomBoxButton = (registry: ManagerRegistry): ButtonConfig =>
|
||||
label: 'toolbar.zoomBox',
|
||||
icon: getIcon('框选放大'),
|
||||
onClick: () => {
|
||||
registry.engine3d?.activateZoomBox();
|
||||
registry.engine3d?.getEngineComponent()?.activateZoomBox();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -204,10 +204,22 @@ export class Engine implements IBimComponent {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始 3D 引擎实例
|
||||
* 订阅原始引擎事件
|
||||
* 用于需要访问底层引擎事件的场景(如测量回调、剖切移动等)
|
||||
* @param event 事件名称
|
||||
* @param handler 事件处理函数
|
||||
*/
|
||||
public getEngine(): any {
|
||||
return this.engine;
|
||||
public onRawEvent(event: string, handler: (...args: any[]) => void): void {
|
||||
this.engine?.events?.on(event, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订阅原始引擎事件
|
||||
* @param event 事件名称
|
||||
* @param handler 事件处理函数
|
||||
*/
|
||||
public offRawEvent(event: string, handler: (...args: any[]) => void): void {
|
||||
this.engine?.events?.off(event, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -612,6 +624,202 @@ export class Engine implements IBimComponent {
|
||||
|
||||
// ==================== 结束:渲染模式 ====================
|
||||
|
||||
// ==================== 设置功能 ====================
|
||||
|
||||
// ---- 边线 ----
|
||||
|
||||
/** 启用模型边线显示 */
|
||||
public activeModelEdge(): void {
|
||||
if (!this._isInitialized || !this.engine?.modelEdge) {
|
||||
console.warn('[Engine] Cannot active model edge: engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engine.modelEdge.active();
|
||||
}
|
||||
|
||||
/** 关闭模型边线显示 */
|
||||
public disActiveModelEdge(): void {
|
||||
if (!this._isInitialized || !this.engine?.modelEdge) {
|
||||
return;
|
||||
}
|
||||
this.engine.modelEdge.disActive();
|
||||
}
|
||||
|
||||
/** 获取边线显示状态 */
|
||||
public getModelEdgeActive(): boolean {
|
||||
if (!this._isInitialized || !this.engine?.modelEdge) {
|
||||
return false;
|
||||
}
|
||||
return this.engine.modelEdge.getActive?.() ?? false;
|
||||
}
|
||||
|
||||
// ---- 光照强度 ----
|
||||
|
||||
/**
|
||||
* 设置环境光照强度
|
||||
* @param intensity 0-100
|
||||
*/
|
||||
public setAmbientLightIntensity(intensity: number): void {
|
||||
if (!this._isInitialized || !this.engine?.setting) {
|
||||
console.warn('[Engine] Cannot set ambient light intensity: engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engine.setting.setAmbientLightIntensity(intensity);
|
||||
}
|
||||
|
||||
/** 获取当前环境光照强度 */
|
||||
public getAmbientLightIntensity(): number {
|
||||
if (!this._isInitialized || !this.engine?.setting) {
|
||||
return 50;
|
||||
}
|
||||
return this.engine.setting.getAmbientLightIntensity?.() ?? 50;
|
||||
}
|
||||
|
||||
// ---- 对比度 ----
|
||||
|
||||
/**
|
||||
* 设置场景对比度
|
||||
* @param contrast 0-100
|
||||
*/
|
||||
public setSceneContrast(contrast: number): void {
|
||||
if (!this._isInitialized || !this.engine?.setting) {
|
||||
console.warn('[Engine] Cannot set scene contrast: engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engine.setting.setSceneContrast(contrast);
|
||||
}
|
||||
|
||||
/** 获取当前场景对比度 */
|
||||
public getSceneContrast(): number {
|
||||
if (!this._isInitialized || !this.engine?.setting) {
|
||||
return 50;
|
||||
}
|
||||
return this.engine.setting.getSceneContrast?.() ?? 50;
|
||||
}
|
||||
|
||||
// ---- 饾和度 ----
|
||||
|
||||
/**
|
||||
* 设置场景饾和度
|
||||
* @param saturation 0-100
|
||||
*/
|
||||
public setSceneSaturation(saturation: number): void {
|
||||
if (!this._isInitialized || !this.engine?.setting) {
|
||||
console.warn('[Engine] Cannot set scene saturation: engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engine.setting.setSceneSaturation(saturation);
|
||||
}
|
||||
|
||||
/** 获取当前场景饾和度 */
|
||||
public getSceneSaturation(): number {
|
||||
if (!this._isInitialized || !this.engine?.setting) {
|
||||
return 50;
|
||||
}
|
||||
return this.engine.setting.getSceneSaturation?.() ?? 50;
|
||||
}
|
||||
|
||||
// ---- 环境背景 ----
|
||||
|
||||
/** 获取 HDR 背景列表 */
|
||||
public getHDRBackgroundList(): { name: string; id: string }[] {
|
||||
if (!this._isInitialized || !this.engine?.setting) {
|
||||
return [];
|
||||
}
|
||||
return this.engine.setting.getHDRBackground?.() ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前 HDR 背景
|
||||
* @param id 背景 ID
|
||||
*/
|
||||
public setHDRBackgroundId(id: string): void {
|
||||
if (!this._isInitialized || !this.engine?.setting) {
|
||||
console.warn('[Engine] Cannot set HDR background: engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engine.setting.setHDRBackgroundId(id);
|
||||
}
|
||||
|
||||
/** 获取当前 HDR 背景 ID */
|
||||
public getHDRBackgroundId(): string {
|
||||
if (!this._isInitialized || !this.engine?.setting) {
|
||||
return '';
|
||||
}
|
||||
return this.engine.setting.getHDRBackgroundId?.() ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 HDR 背景可见性
|
||||
* @param visible 是否可见
|
||||
*/
|
||||
public setHDRBackgroundVisibility(visible: boolean): void {
|
||||
if (!this._isInitialized || !this.engine?.setting) {
|
||||
console.warn('[Engine] Cannot set HDR background visibility: engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engine.setting.setHDRBackgroundVisibility(visible);
|
||||
}
|
||||
|
||||
/** 获取 HDR 背景可见性 */
|
||||
public getHDRBackgroundVisibility(): boolean {
|
||||
if (!this._isInitialized || !this.engine?.setting) {
|
||||
return true;
|
||||
}
|
||||
return this.engine.setting.getHDRBackgroundVisibility?.() ?? true;
|
||||
}
|
||||
|
||||
// ---- 地面 ----
|
||||
|
||||
/** 获取地面列表 */
|
||||
public getGroundList(): { name: string; id: string }[] {
|
||||
if (!this._isInitialized || !this.engine?.setting) {
|
||||
return [];
|
||||
}
|
||||
return this.engine.setting.getGroundList?.() ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前地面类型
|
||||
* @param id 地面 ID
|
||||
*/
|
||||
public setGroundId(id: string): void {
|
||||
if (!this._isInitialized || !this.engine?.setting) {
|
||||
console.warn('[Engine] Cannot set ground: engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engine.setting.setGroundId(id);
|
||||
}
|
||||
|
||||
/** 获取当前地面 ID */
|
||||
public getGroundId(): string {
|
||||
if (!this._isInitialized || !this.engine?.setting) {
|
||||
return '';
|
||||
}
|
||||
return this.engine.setting.getGroundId?.() ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置地面高度(单位:m)
|
||||
* @param elevation 地面高度值
|
||||
*/
|
||||
public setGroundElevation(elevation: number): void {
|
||||
if (!this._isInitialized || !this.engine?.setting) {
|
||||
console.warn('[Engine] Cannot set ground elevation: engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engine.setting.setGroundElevation(elevation);
|
||||
}
|
||||
|
||||
/** 获取当前地面高度(单位:m) */
|
||||
public getGroundElevation(): number {
|
||||
if (!this._isInitialized || !this.engine?.setting) {
|
||||
return 0;
|
||||
}
|
||||
return this.engine.setting.getGroundElevation?.() ?? 0;
|
||||
}
|
||||
|
||||
// ==================== 结束:设置功能 ====================
|
||||
// ==================== 漫游功能 ====================
|
||||
|
||||
/** 漫游模式是否激活 */
|
||||
|
||||
@@ -66,7 +66,7 @@ export class WalkPathPanel implements IBimComponent {
|
||||
* 在面板打开时调用,获取底层引擎中已存在的漫游点
|
||||
*/
|
||||
private loadPointsFromEngine(): void {
|
||||
const enginePoints = this.registry.engine3d?.pathRoamingGetPoints() ?? [];
|
||||
const enginePoints = this.registry.engine3d?.getEngineComponent()?.pathRoamingGetPoints() ?? [];
|
||||
// 将引擎返回的点转换为本地格式
|
||||
this.points = enginePoints.map((_: any, index: number) => ({
|
||||
index: index
|
||||
@@ -288,7 +288,7 @@ export class WalkPathPanel implements IBimComponent {
|
||||
*/
|
||||
private addPoint(): void {
|
||||
// 调用引擎添加漫游点
|
||||
this.registry.engine3d?.pathRoamingAddPoint();
|
||||
this.registry.engine3d?.getEngineComponent()?.pathRoamingAddPoint();
|
||||
// 更新本地状态
|
||||
const newIndex = this.points.length;
|
||||
this.points.push({ index: newIndex });
|
||||
@@ -302,7 +302,7 @@ export class WalkPathPanel implements IBimComponent {
|
||||
*/
|
||||
private deletePoint(index: number): void {
|
||||
// 调用引擎删除漫游点
|
||||
this.registry.engine3d?.pathRoamingRemovePoint(index);
|
||||
this.registry.engine3d?.getEngineComponent()?.pathRoamingRemovePoint(index);
|
||||
// 从本地列表中移除
|
||||
this.points.splice(index, 1);
|
||||
// 重新索引
|
||||
@@ -316,7 +316,7 @@ export class WalkPathPanel implements IBimComponent {
|
||||
*/
|
||||
private deleteAllPoints(): void {
|
||||
// 调用引擎清除所有漫游点
|
||||
this.registry.engine3d?.pathRoamingClearPoints();
|
||||
this.registry.engine3d?.getEngineComponent()?.pathRoamingClearPoints();
|
||||
// 清空本地列表
|
||||
this.points = [];
|
||||
// 重新渲染
|
||||
@@ -328,7 +328,7 @@ export class WalkPathPanel implements IBimComponent {
|
||||
* @param index 目标漫游点索引
|
||||
*/
|
||||
private jumpToPoint(index: number): void {
|
||||
this.registry.engine3d?.pathRoamingJumpToPoint(index);
|
||||
this.registry.engine3d?.getEngineComponent()?.pathRoamingJumpToPoint(index);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -343,7 +343,7 @@ export class WalkPathPanel implements IBimComponent {
|
||||
|
||||
console.log('[WalkPathPanel] 开始播放漫游', { duration: this.duration, loop: this.loop, pointsCount: this.points.length });
|
||||
|
||||
this.registry.engine3d?.pathRoamingPlay({
|
||||
this.registry.engine3d?.getEngineComponent()?.pathRoamingPlay({
|
||||
duration: this.duration,
|
||||
loop: this.loop,
|
||||
onPointComplete: (pointIndex: number) => {
|
||||
@@ -365,7 +365,7 @@ export class WalkPathPanel implements IBimComponent {
|
||||
*/
|
||||
private stopPath(): void {
|
||||
console.log('[WalkPathPanel] 停止漫游');
|
||||
this.registry.engine3d?.pathRoamingStop();
|
||||
this.registry.engine3d?.getEngineComponent()?.pathRoamingStop();
|
||||
this.isPlaying = false;
|
||||
this.playingPointIndex = -1;
|
||||
this.render();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
import { ManagerRegistry } from './manager-registry';
|
||||
import type { EngineEvents } from '../types/events';
|
||||
import type { Engine } from '../components/engine';
|
||||
|
||||
/**
|
||||
* Manager 抽象基类
|
||||
@@ -20,6 +21,15 @@ export abstract class BaseManager {
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Engine 组件实例的便捷访问器
|
||||
* 内部管理器通过此 getter 直接访问 Engine 组件的全部能力
|
||||
* 替代原来的 this.registry.engine3d?.xxx() 透传链
|
||||
*/
|
||||
protected get engineComponent(): Engine | null {
|
||||
return this.registry.engine3d?.getEngineComponent?.() ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅事件(自动追踪,destroy 时自动取消)
|
||||
* @param event 事件名称
|
||||
|
||||
@@ -227,27 +227,13 @@ export const enUS: TranslationDictionary = {
|
||||
advanced: 'Quality',
|
||||
},
|
||||
edgeLine: 'Edge Lines',
|
||||
ground: 'Ground',
|
||||
groundSize: 'Size',
|
||||
groundTypes: {
|
||||
none: 'None',
|
||||
concrete: 'Concrete',
|
||||
grass: 'Grass',
|
||||
tile: 'Tile',
|
||||
water: 'Water',
|
||||
wood: 'Wood',
|
||||
},
|
||||
contrast: 'Contrast',
|
||||
saturation: 'Saturation',
|
||||
lightIntensity: 'Light Intensity',
|
||||
environment: 'Environment',
|
||||
environments: {
|
||||
default: 'Default',
|
||||
outdoor: 'Outdoor',
|
||||
indoor: 'Indoor',
|
||||
night: 'Night',
|
||||
overcast: 'Overcast',
|
||||
studio: 'Studio',
|
||||
},
|
||||
backgroundVisible: 'Show Background',
|
||||
ground: 'Ground',
|
||||
groundElevation: 'Ground Elevation',
|
||||
groundElevationUnit: 'm',
|
||||
}
|
||||
};
|
||||
|
||||
@@ -250,17 +250,6 @@ export interface TranslationDictionary {
|
||||
};
|
||||
/** 边线 */
|
||||
edgeLine: string;
|
||||
/** 显示地面 */
|
||||
ground: string;
|
||||
groundSize: string;
|
||||
groundTypes: {
|
||||
none: string;
|
||||
concrete: string;
|
||||
grass: string;
|
||||
tile: string;
|
||||
water: string;
|
||||
wood: string;
|
||||
};
|
||||
/** 对比度 */
|
||||
contrast: string;
|
||||
/** 饱和度 */
|
||||
@@ -269,14 +258,14 @@ export interface TranslationDictionary {
|
||||
lightIntensity: string;
|
||||
/** 环境背景 */
|
||||
environment: string;
|
||||
environments: {
|
||||
default: string;
|
||||
outdoor: string;
|
||||
indoor: string;
|
||||
night: string;
|
||||
overcast: string;
|
||||
studio: string;
|
||||
};
|
||||
/** 显示背景 */
|
||||
backgroundVisible: string;
|
||||
/** 显示地面 */
|
||||
ground: string;
|
||||
/** 地面高度 */
|
||||
groundElevation: string;
|
||||
/** 地面高度单位 */
|
||||
groundElevationUnit: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -227,27 +227,13 @@ export const zhCN: TranslationDictionary = {
|
||||
advanced: '效果模式',
|
||||
},
|
||||
edgeLine: '边线',
|
||||
ground: '显示地面',
|
||||
groundSize: '大小',
|
||||
groundTypes: {
|
||||
none: '无',
|
||||
concrete: '混凝土',
|
||||
grass: '草地',
|
||||
tile: '瓷砖',
|
||||
water: '水面',
|
||||
wood: '木地板',
|
||||
},
|
||||
contrast: '对比度',
|
||||
saturation: '饱和度',
|
||||
lightIntensity: '光照强度',
|
||||
environment: '环境背景',
|
||||
environments: {
|
||||
default: '默认',
|
||||
outdoor: '室外',
|
||||
indoor: '室内',
|
||||
night: '夜晚',
|
||||
overcast: '阴天',
|
||||
studio: '工作室',
|
||||
},
|
||||
backgroundVisible: '显示背景',
|
||||
ground: '显示地面',
|
||||
groundElevation: '地面高度',
|
||||
groundElevationUnit: 'm',
|
||||
}
|
||||
};
|
||||
|
||||
@@ -71,14 +71,12 @@ export class ComponentDetailManager extends BaseManager {
|
||||
|
||||
this.showLoading();
|
||||
|
||||
this.registry.engine3d?.getComponentProperties(
|
||||
this.currentSelection.url,
|
||||
this.currentSelection.id,
|
||||
(data) => {
|
||||
this.propertiesData = data;
|
||||
this.renderTabbedContent();
|
||||
}
|
||||
);
|
||||
this.engineComponent?.getComponentProperties(this.currentSelection.url,
|
||||
this.currentSelection.id,
|
||||
(data) => {
|
||||
this.propertiesData = data;
|
||||
this.renderTabbedContent();
|
||||
});
|
||||
}
|
||||
|
||||
private showLoading(): void {
|
||||
|
||||
@@ -334,9 +334,9 @@ export class ConstructTreeManagerBtn extends BaseManager {
|
||||
this.setVisible(false);
|
||||
|
||||
// 从 3D 引擎获取原始树数据
|
||||
const levelTreeData = this.registry.engine3d?.getLevelTreeData() ?? [];
|
||||
const typeTreeData = this.registry.engine3d?.getTypeTreeData() ?? [];
|
||||
const majorTreeData = this.registry.engine3d?.getMajorTreeData() ?? [];
|
||||
const levelTreeData = this.engineComponent?.getLevelTreeData() ?? [];
|
||||
const typeTreeData = this.engineComponent?.getTypeTreeData() ?? [];
|
||||
const majorTreeData = this.engineComponent?.getMajorTreeData() ?? [];
|
||||
|
||||
// 调试日志:输出原始数据
|
||||
console.log('[ConstructTree] 原始数据 (Level):', levelTreeData);
|
||||
@@ -370,9 +370,9 @@ export class ConstructTreeManagerBtn extends BaseManager {
|
||||
if (!modelParam.length) return;
|
||||
|
||||
if (node.checkState === TreeNodeCheckState.Checked) {
|
||||
this.registry.engine3d?.showModel(modelParam);
|
||||
this.engineComponent?.showModel(modelParam);
|
||||
} else {
|
||||
this.registry.engine3d?.hideModels(modelParam);
|
||||
this.engineComponent?.hideModels(modelParam);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -383,14 +383,14 @@ export class ConstructTreeManagerBtn extends BaseManager {
|
||||
const modelParam = collectModelParams(node);
|
||||
if (!modelParam.length) return;
|
||||
|
||||
this.registry.engine3d?.unhighlightAllModels();
|
||||
this.registry.engine3d?.highlightModel(modelParam);
|
||||
this.registry.engine3d?.viewScaleToModel(modelParam);
|
||||
this.engineComponent?.unhighlightAllModels();
|
||||
this.engineComponent?.highlightModel(modelParam);
|
||||
this.engineComponent?.viewScaleToModel(modelParam);
|
||||
},
|
||||
|
||||
// 再次点击已选中节点时取消高亮
|
||||
onNodeDeselect: () => {
|
||||
this.registry.engine3d?.unhighlightAllModels();
|
||||
this.engineComponent?.unhighlightAllModels();
|
||||
},
|
||||
|
||||
onNodeExpand: () => {
|
||||
@@ -430,7 +430,7 @@ export class ConstructTreeManagerBtn extends BaseManager {
|
||||
* 在 Tab 切换和对话框初始化时调用
|
||||
*/
|
||||
const resetAllTrees = () => {
|
||||
this.registry.engine3d?.showAllModels();
|
||||
this.engineComponent?.showAllModels();
|
||||
componentTree.checkAllNodes(true);
|
||||
typeTree.checkAllNodes(true);
|
||||
majorTree.checkAllNodes(true);
|
||||
|
||||
@@ -29,7 +29,7 @@ export class EngineInfoDialogManager extends BaseDialogManager {
|
||||
}
|
||||
|
||||
protected createContent(): HTMLElement {
|
||||
const info: EngineInfo | null = this.registry.engine3d?.getEngineInfo() ?? null;
|
||||
const info: EngineInfo | null = this.engineComponent?.getEngineInfo() ?? null;
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.className = 'engine-info-content';
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
/**
|
||||
* 3D 引擎管理器
|
||||
* 负责管理 3D 渲染引擎的初始化、模型加载和测量功能
|
||||
* 负责管理 3D 渲染引擎的初始化、模型加载和生命周期
|
||||
*
|
||||
* 设计原则:
|
||||
* - EngineManager 只暴露面向外部用户的公共 API
|
||||
* - 内部管理器通过 getEngineComponent() 直接访问 Engine 组件
|
||||
* - 不再透传 Engine 组件的方法
|
||||
*/
|
||||
import { Engine, type EngineOptions, type ModelLoadOptions, type EngineInfo } from '../components/engine';
|
||||
import { Engine, type EngineOptions, type ModelLoadOptions } from '../components/engine';
|
||||
import { BaseManager } from '../core/base-manager';
|
||||
import { RightKeyManager } from './right-key-manager';
|
||||
import type { MeasureMode, ClearHeightDirection, ClearHeightSelectType } from '../types/measure';
|
||||
import type { MeasureUnit, MeasurePrecision } from '../components/measure-panel/types';
|
||||
import type { SectionBoxRange } from '../components/section-box-panel/types';
|
||||
import type { MenuItemConfig } from '../components/menu/item';
|
||||
import { ManagerRegistry } from '../core/manager-registry';
|
||||
|
||||
/**
|
||||
* 3D 引擎管理器
|
||||
* 封装底层 3D 引擎,提供模型加载、相机控制、测量等功能
|
||||
* 封装底层 3D 引擎,提供模型加载、渲染控制等公共 API
|
||||
*/
|
||||
export class EngineManager extends BaseManager {
|
||||
/** 容器元素 */
|
||||
private container: HTMLElement;
|
||||
/** 引擎实例 */
|
||||
/** 引擎组件实例 */
|
||||
private engineInstance: Engine | null = null;
|
||||
/** 右键菜单管理器 */
|
||||
public rightKey: RightKeyManager | null = null;
|
||||
@@ -28,6 +30,15 @@ export class EngineManager extends BaseManager {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Engine 组件实例
|
||||
* 内部管理器通过此方法直接访问 Engine 组件的全部能力
|
||||
* @returns Engine 组件实例,未初始化时返回 null
|
||||
*/
|
||||
public getEngineComponent(): Engine | null {
|
||||
return this.engineInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 3D 引擎
|
||||
* @param options 引擎配置选项
|
||||
@@ -51,7 +62,7 @@ export class EngineManager extends BaseManager {
|
||||
this.rightKey = new RightKeyManager(this.container, this.registry);
|
||||
|
||||
this.rightKey.registerHandler((_e) => {
|
||||
const selected = this.getSelectedComponent();
|
||||
const selected = this.engineInstance?.getSelectedComponent();
|
||||
const items: MenuItemConfig[] = [];
|
||||
|
||||
if (selected) {
|
||||
@@ -76,9 +87,9 @@ export class EngineManager extends BaseManager {
|
||||
group: 'component',
|
||||
order: 2,
|
||||
onClick: () => {
|
||||
const models = this.getHighlightModels();
|
||||
const models = this.engineInstance?.getHighlightModels();
|
||||
if (models) {
|
||||
this.hideModels(models);
|
||||
this.engineInstance?.hideModels(models);
|
||||
}
|
||||
this.rightKey?.hide();
|
||||
}
|
||||
@@ -91,9 +102,9 @@ export class EngineManager extends BaseManager {
|
||||
group: 'component',
|
||||
order: 3,
|
||||
onClick: () => {
|
||||
const models = this.getHighlightModels();
|
||||
const models = this.engineInstance?.getHighlightModels();
|
||||
if (models) {
|
||||
this.translucentModels(models);
|
||||
this.engineInstance?.translucentModels(models);
|
||||
}
|
||||
this.rightKey?.hide();
|
||||
}
|
||||
@@ -106,7 +117,7 @@ export class EngineManager extends BaseManager {
|
||||
group: 'component',
|
||||
order: 4,
|
||||
onClick: () => {
|
||||
this.unTranslucentModel();
|
||||
this.engineInstance?.unTranslucentModel();
|
||||
this.rightKey?.hide();
|
||||
}
|
||||
});
|
||||
@@ -123,9 +134,9 @@ export class EngineManager extends BaseManager {
|
||||
id: 'hideOthers',
|
||||
label: 'menu.hideOthers',
|
||||
onClick: () => {
|
||||
const models = this.getHighlightModels();
|
||||
const models = this.engineInstance?.getHighlightModels();
|
||||
if (models) {
|
||||
this.isolateModels(models);
|
||||
this.engineInstance?.isolateModels(models);
|
||||
}
|
||||
this.rightKey?.hide();
|
||||
}
|
||||
@@ -134,9 +145,9 @@ export class EngineManager extends BaseManager {
|
||||
id: 'transparentOthers',
|
||||
label: 'menu.transparentOthers',
|
||||
onClick: () => {
|
||||
const models = this.getHighlightModels();
|
||||
const models = this.engineInstance?.getHighlightModels();
|
||||
if (models) {
|
||||
this.translucentOtherModels(models);
|
||||
this.engineInstance?.translucentOtherModels(models);
|
||||
}
|
||||
this.rightKey?.hide();
|
||||
}
|
||||
@@ -155,9 +166,9 @@ export class EngineManager extends BaseManager {
|
||||
id: 'selectSameType',
|
||||
label: 'menu.selectSameType',
|
||||
onClick: () => {
|
||||
const models = this.getHighlightModels();
|
||||
const models = this.engineInstance?.getHighlightModels();
|
||||
if (models) {
|
||||
this.batchSelectSameTypeModel(models);
|
||||
this.engineInstance?.batchSelectSameTypeModel(models);
|
||||
}
|
||||
this.rightKey?.hide();
|
||||
}
|
||||
@@ -166,9 +177,9 @@ export class EngineManager extends BaseManager {
|
||||
id: 'selectSameLevel',
|
||||
label: 'menu.selectSameLevel',
|
||||
onClick: () => {
|
||||
const models = this.getHighlightModels();
|
||||
const models = this.engineInstance?.getHighlightModels();
|
||||
if (models) {
|
||||
this.batchSelectSameLevelModel(models);
|
||||
this.engineInstance?.batchSelectSameLevelModel(models);
|
||||
}
|
||||
this.rightKey?.hide();
|
||||
}
|
||||
@@ -177,9 +188,9 @@ export class EngineManager extends BaseManager {
|
||||
id: 'selectSameLevelType',
|
||||
label: 'menu.selectSameLevelType',
|
||||
onClick: () => {
|
||||
const models = this.getHighlightModels();
|
||||
const models = this.engineInstance?.getHighlightModels();
|
||||
if (models) {
|
||||
this.batchSelectSameLevelTypeModel(models);
|
||||
this.engineInstance?.batchSelectSameLevelTypeModel(models);
|
||||
}
|
||||
this.rightKey?.hide();
|
||||
}
|
||||
@@ -194,7 +205,7 @@ export class EngineManager extends BaseManager {
|
||||
group: 'component',
|
||||
order: 7,
|
||||
onClick: () => {
|
||||
this.fitSectionBoxToModel();
|
||||
this.engineInstance?.fitSectionBoxToModel();
|
||||
this.rightKey?.hide();
|
||||
}
|
||||
});
|
||||
@@ -207,7 +218,7 @@ export class EngineManager extends BaseManager {
|
||||
group: 'component',
|
||||
order: 8,
|
||||
onClick: () => {
|
||||
this.showAllModels();
|
||||
this.engineInstance?.showAllModels();
|
||||
this.emit('menu:show-all', {});
|
||||
this.rightKey?.hide();
|
||||
}
|
||||
@@ -245,27 +256,6 @@ export class EngineManager extends BaseManager {
|
||||
this.engineInstance.loadModel(urls, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取底层引擎实例
|
||||
* @returns 引擎实例
|
||||
*/
|
||||
public getEngine(): any {
|
||||
if (!this.engineInstance) {
|
||||
console.warn('[EngineManager] 3D Engine not initialized.');
|
||||
return null;
|
||||
}
|
||||
return this.engineInstance.getEngine();
|
||||
}
|
||||
|
||||
/** 相机回到初始位置 */
|
||||
public CameraGoHome(): void {
|
||||
if (!this.engineInstance) {
|
||||
console.warn('[EngineManager] 3D Engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engineInstance.CameraGoHome();
|
||||
}
|
||||
|
||||
/** 暂停渲染 */
|
||||
public pauseRendering(): void {
|
||||
if (!this.engineInstance) {
|
||||
@@ -284,457 +274,6 @@ export class EngineManager extends BaseManager {
|
||||
this.engineInstance.resumeRendering();
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活测量模式
|
||||
* @param mode 测量模式
|
||||
*/
|
||||
public activateMeasure(mode: MeasureMode): void {
|
||||
if (!this.engineInstance) {
|
||||
console.warn('[EngineManager] 3D Engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engineInstance.activateMeasure(mode);
|
||||
}
|
||||
|
||||
/** 停用测量模式 */
|
||||
public deactivateMeasure(): void {
|
||||
if (!this.engineInstance) {
|
||||
return;
|
||||
}
|
||||
this.engineInstance.deactivateMeasure();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前测量类型
|
||||
* @returns 当前测量模式
|
||||
*/
|
||||
public getCurrentMeasureType(): MeasureMode | null {
|
||||
if (!this.engineInstance) {
|
||||
return null;
|
||||
}
|
||||
return this.engineInstance.getCurrentMeasureType();
|
||||
}
|
||||
|
||||
public clearAllMeasures(): void {
|
||||
if (!this.engineInstance) {
|
||||
return;
|
||||
}
|
||||
this.engineInstance.clearAllMeasures();
|
||||
}
|
||||
|
||||
public saveMeasureSetting(setting: { unit: MeasureUnit; precision: MeasurePrecision }): void {
|
||||
if (!this.engineInstance) {
|
||||
return;
|
||||
}
|
||||
this.engineInstance.saveMeasureSetting(setting);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置净高测量朝向
|
||||
* @param direction 0=朝下,1=朝上
|
||||
*/
|
||||
public setClearHeightDirection(direction: ClearHeightDirection): void {
|
||||
if (!this.engineInstance) {
|
||||
return;
|
||||
}
|
||||
this.engineInstance.setClearHeightDirection(direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置净高测量选择对象类型
|
||||
* @param selectType 'point'=选择点,'element'=选择构件
|
||||
*/
|
||||
public setClearHeightSelectType(selectType: ClearHeightSelectType): void {
|
||||
if (!this.engineInstance) {
|
||||
return;
|
||||
}
|
||||
this.engineInstance.setClearHeightSelectType(selectType);
|
||||
}
|
||||
|
||||
public activeSection(mode: 'x' | 'y' | 'z' | 'box' | 'face'): void {
|
||||
if (!this.engineInstance) {
|
||||
console.warn('[EngineManager] 3D Engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engineInstance.activeSection(mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前激活的剖切模式
|
||||
* @returns 当前剖切模式,未激活时返回 null
|
||||
*/
|
||||
public getCurrentSectionMode(): 'x' | 'y' | 'z' | 'box' | 'face' | null {
|
||||
if (!this.engineInstance) {
|
||||
return null;
|
||||
}
|
||||
return this.engineInstance.getCurrentSectionMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停用所有剖切功能
|
||||
* @remarks 关闭剖切对话框时调用
|
||||
*/
|
||||
public deactivateSection(): void {
|
||||
if (!this.engineInstance) {
|
||||
return;
|
||||
}
|
||||
this.engineInstance.deactivateSection();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置剖切盒范围
|
||||
* @param range 各轴的剖切范围 (百分比 0-100)
|
||||
* @remarks 仅在 'box' 模式下有效
|
||||
*/
|
||||
public setSectionBoxRange(range: SectionBoxRange): void {
|
||||
if (!this.engineInstance) {
|
||||
console.warn('[EngineManager] 3D Engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engineInstance.setSectionBoxRange(range);
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏剖切面(临时隐藏,可恢复)
|
||||
* @remarks 配合 recoverSection() 使用
|
||||
*/
|
||||
public hideSection(): void {
|
||||
if (!this.engineInstance) {
|
||||
return;
|
||||
}
|
||||
this.engineInstance.hideSection();
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复剖切面(从隐藏状态恢复)
|
||||
* @remarks 恢复被 hideSection() 隐藏的剖切面
|
||||
*/
|
||||
public recoverSection(): void {
|
||||
if (!this.engineInstance) {
|
||||
return;
|
||||
}
|
||||
this.engineInstance.recoverSection();
|
||||
}
|
||||
|
||||
public fitSectionBoxToModel(): void {
|
||||
if (!this.engineInstance) {
|
||||
return;
|
||||
}
|
||||
this.engineInstance.fitSectionBoxToModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 剖切盒适应(缩放到场景整体包围盒)
|
||||
* @remarks 对接底层 clipping.scaleBox()
|
||||
*/
|
||||
public scaleSectionBox(): void {
|
||||
if (!this.engineInstance) {
|
||||
return;
|
||||
}
|
||||
this.engineInstance.scaleSectionBox();
|
||||
}
|
||||
|
||||
/**
|
||||
* 反向剖切
|
||||
* @remarks 对接底层 clipping.reverse()
|
||||
*/
|
||||
public reverseSection(): void {
|
||||
if (!this.engineInstance) {
|
||||
return;
|
||||
}
|
||||
this.engineInstance.reverseSection();
|
||||
}
|
||||
|
||||
/** 激活框选放大功能 */
|
||||
public activateZoomBox(): void {
|
||||
if (!this.engineInstance) {
|
||||
console.warn('[EngineManager] 3D Engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engineInstance.activateZoomBox();
|
||||
}
|
||||
|
||||
// ==================== 漫游功能 ====================
|
||||
|
||||
/** 激活第一人称漫游模式 */
|
||||
public activateFirstPersonMode(): void {
|
||||
if (!this.engineInstance) {
|
||||
console.warn('[EngineManager] 3D Engine not initialized.');
|
||||
return;
|
||||
}
|
||||
this.engineInstance.activateFirstPersonMode();
|
||||
}
|
||||
|
||||
/** 停用第一人称漫游模式 */
|
||||
public deactivateFirstPersonMode(): void {
|
||||
if (!this.engineInstance) {
|
||||
return;
|
||||
}
|
||||
this.engineInstance.deactivateFirstPersonMode();
|
||||
}
|
||||
|
||||
public setWalkSpeed(speed: number): void {
|
||||
this.engineInstance?.setWalkSpeed(speed);
|
||||
}
|
||||
|
||||
public setWalkGravity(enabled: boolean): void {
|
||||
this.engineInstance?.setWalkGravity(enabled);
|
||||
}
|
||||
|
||||
public setWalkCollision(enabled: boolean): void {
|
||||
this.engineInstance?.setWalkCollision(enabled);
|
||||
}
|
||||
|
||||
public toggleMiniMap(): void {
|
||||
this.engineInstance?.toggleMiniMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取小地图显示状态
|
||||
* @returns true=显示,false=隐藏
|
||||
*/
|
||||
public getMiniMapState(): boolean {
|
||||
return this.engineInstance?.getMiniMapState() ?? false;
|
||||
}
|
||||
|
||||
public isFirstPersonModeActive(): boolean {
|
||||
return this.engineInstance?.isFirstPersonModeActive() ?? false;
|
||||
}
|
||||
|
||||
// ==================== 结束:漫游功能 ====================
|
||||
|
||||
// ==================== 构件选中 ====================
|
||||
|
||||
/**
|
||||
* 获取当前选中的构件
|
||||
* @returns 选中构件的 URL 和 ID,未选中时返回 null
|
||||
*/
|
||||
public getSelectedComponent(): { url: string; id: string } | null {
|
||||
return this.engineInstance?.getSelectedComponent() ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取构件属性
|
||||
* @param url 模型 URL
|
||||
* @param id 构件 ID
|
||||
* @param callback 回调函数,接收属性数据
|
||||
*/
|
||||
public getComponentProperties(
|
||||
url: string,
|
||||
id: string,
|
||||
callback: (data: any) => void
|
||||
): void {
|
||||
this.engineInstance?.getComponentProperties(url, id, callback);
|
||||
}
|
||||
|
||||
// ==================== 结束:构件选中 ====================
|
||||
|
||||
// ==================== 模型树 ====================
|
||||
|
||||
public getTypeTreeData(): any[] {
|
||||
return this.engineInstance?.getTypeTreeData() ?? [];
|
||||
}
|
||||
|
||||
public getLevelTreeData(): any[] {
|
||||
return this.engineInstance?.getLevelTreeData() ?? [];
|
||||
}
|
||||
|
||||
public getMajorTreeData(): any[] {
|
||||
return this.engineInstance?.getMajorTreeData() ?? [];
|
||||
}
|
||||
|
||||
public getEngineInfo(): EngineInfo | null {
|
||||
return this.engineInstance?.getEngineInfo() ?? null;
|
||||
}
|
||||
// ==================== 结束:模型树 ====================
|
||||
|
||||
// ==================== 路径漫游 ====================
|
||||
|
||||
/**
|
||||
* 添加漫游点
|
||||
* 将当前相机位置添加为一个漫游点
|
||||
*/
|
||||
public pathRoamingAddPoint(): void {
|
||||
this.engineInstance?.pathRoamingAddPoint();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定索引的漫游点
|
||||
* @param index 要删除的漫游点索引
|
||||
*/
|
||||
public pathRoamingRemovePoint(index: number): void {
|
||||
this.engineInstance?.pathRoamingRemovePoint(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有漫游点
|
||||
*/
|
||||
public pathRoamingClearPoints(): void {
|
||||
this.engineInstance?.pathRoamingClearPoints();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有漫游点
|
||||
* @returns 漫游点数组
|
||||
*/
|
||||
public pathRoamingGetPoints(): any[] {
|
||||
return this.engineInstance?.pathRoamingGetPoints() ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到指定漫游点
|
||||
* @param index 目标漫游点索引
|
||||
*/
|
||||
public pathRoamingJumpToPoint(index: number): void {
|
||||
this.engineInstance?.pathRoamingJumpToPoint(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放漫游
|
||||
* @param options 播放选项,包含时长、循环、回调等配置
|
||||
*/
|
||||
public pathRoamingPlay(options?: {
|
||||
duration?: number;
|
||||
loop?: boolean;
|
||||
onComplete?: () => void;
|
||||
onPointComplete?: (pointIndex: number) => void;
|
||||
}): void {
|
||||
this.engineInstance?.pathRoamingPlay(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止漫游
|
||||
*/
|
||||
public pathRoamingStop(): void {
|
||||
this.engineInstance?.pathRoamingStop();
|
||||
}
|
||||
|
||||
// ==================== 结束:路径漫游 ====================
|
||||
|
||||
// ==================== 构件操作 ====================
|
||||
|
||||
/**
|
||||
* 获取当前高亮(选中)的模型
|
||||
* @returns 高亮模型对象,未选中时返回 null
|
||||
*/
|
||||
public getHighlightModels(): any {
|
||||
return this.engineInstance?.getHighlightModels() ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 高亮指定模型构件
|
||||
*
|
||||
* @param models - 要高亮的模型数组,格式: [{ url: string, ids: string[] }]
|
||||
*
|
||||
* @example
|
||||
* manager.highlightModel([
|
||||
* { url: 'https://xxx/models/xxx/', ids: [350518, 350520] }
|
||||
* ]);
|
||||
*/
|
||||
public highlightModel(models: { url: string; ids: number[] }[]): void {
|
||||
this.engineInstance?.highlightModel(models);
|
||||
}
|
||||
|
||||
public unhighlightAllModels(): void {
|
||||
this.engineInstance?.unhighlightAllModels();
|
||||
}
|
||||
|
||||
public viewScaleToModel(models: { url: string; ids: number[] }[]): void {
|
||||
this.engineInstance?.viewScaleToModel(models);
|
||||
}
|
||||
|
||||
public hideModels(models: { url: string; ids: number[] }[]): void {
|
||||
this.engineInstance?.hideModels(models);
|
||||
}
|
||||
|
||||
public showModel(models: { url: string; ids: number[] }[]): void {
|
||||
this.engineInstance?.showModel(models);
|
||||
}
|
||||
|
||||
/**
|
||||
* 半透明指定模型
|
||||
* @param models 要半透明的模型对象
|
||||
*/
|
||||
public translucentModels(models: any): void {
|
||||
this.engineInstance?.translucentModels(models);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消半透明
|
||||
*/
|
||||
public unTranslucentModel(): void {
|
||||
this.engineInstance?.unTranslucentModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 隔离指定模型(隐藏其他)
|
||||
* @param models 要隔离的模型对象
|
||||
*/
|
||||
public isolateModels(models: any): void {
|
||||
this.engineInstance?.isolateModels(models);
|
||||
}
|
||||
|
||||
/**
|
||||
* 半透明其他构件
|
||||
* @param models 基准模型对象
|
||||
*/
|
||||
public translucentOtherModels(models: any): void {
|
||||
this.engineInstance?.translucentOtherModels(models);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示所有模型
|
||||
*/
|
||||
public showAllModels(): void {
|
||||
this.engineInstance?.showAllModels();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量选择同类模型
|
||||
* @param models 基准模型对象
|
||||
*/
|
||||
public batchSelectSameTypeModel(models: any): void {
|
||||
this.engineInstance?.batchSelectSameTypeModel(models);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量选择同层模型
|
||||
* @param models 基准模型对象
|
||||
*/
|
||||
public batchSelectSameLevelModel(models: any): void {
|
||||
this.engineInstance?.batchSelectSameLevelModel(models);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量选择同层同类模型
|
||||
* @param models 基准模型对象
|
||||
*/
|
||||
public batchSelectSameLevelTypeModel(models: any): void {
|
||||
this.engineInstance?.batchSelectSameLevelTypeModel(models);
|
||||
}
|
||||
|
||||
// ==================== 结束:构件操作 ====================
|
||||
|
||||
// ==================== 渲染模式 ====================
|
||||
|
||||
/**
|
||||
* 获取当前渲染模式
|
||||
* @returns 'simple' | 'balance' | 'advanced'
|
||||
*/
|
||||
public getRenderMode(): string {
|
||||
return this.engineInstance?.getRenderMode() ?? 'balance';
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置渲染模式
|
||||
* @param mode 'simple' | 'balance' | 'advanced'
|
||||
*/
|
||||
public setRenderMode(mode: 'simple' | 'balance' | 'advanced'): void {
|
||||
this.engineInstance?.setRenderMode(mode);
|
||||
}
|
||||
|
||||
// ==================== 结束:渲染模式 ====================
|
||||
|
||||
/** 销毁引擎管理器 */
|
||||
public destroy(): void {
|
||||
if (this.engineInstance) {
|
||||
|
||||
@@ -42,11 +42,11 @@ export class MeasureDialogManager extends BaseDialogManager {
|
||||
defaultExpanded: false,
|
||||
onModeChange: (mode) => {
|
||||
console.log('[MeasureDialogManager] 当前测量方式已切换:', mode);
|
||||
this.registry.engine3d?.activateMeasure(mode);
|
||||
this.engineComponent?.activateMeasure(mode);
|
||||
},
|
||||
onClearAll: () => {
|
||||
console.log('[MeasureDialogManager] 删除全部');
|
||||
this.registry.engine3d?.clearAllMeasures();
|
||||
this.engineComponent?.clearAllMeasures();
|
||||
},
|
||||
onSettings: () => {
|
||||
console.log('[MeasureDialogManager] 打开设置');
|
||||
@@ -56,18 +56,18 @@ export class MeasureDialogManager extends BaseDialogManager {
|
||||
},
|
||||
onConfigSave: (config) => {
|
||||
console.log('[MeasureDialogManager] 保存设置:', config);
|
||||
this.registry.engine3d?.saveMeasureSetting({
|
||||
this.engineComponent?.saveMeasureSetting({
|
||||
unit: config.unit,
|
||||
precision: config.precision
|
||||
});
|
||||
},
|
||||
onClearHeightDirectionChange: (direction) => {
|
||||
console.log('[MeasureDialogManager] 净高朝向切换:', direction);
|
||||
this.registry.engine3d?.setClearHeightDirection(direction);
|
||||
this.engineComponent?.setClearHeightDirection(direction);
|
||||
},
|
||||
onClearHeightSelectTypeChange: (selectType) => {
|
||||
console.log('[MeasureDialogManager] 净高选择对象切换:', selectType);
|
||||
this.registry.engine3d?.setClearHeightSelectType(selectType);
|
||||
this.engineComponent?.setClearHeightSelectType(selectType);
|
||||
},
|
||||
});
|
||||
this.panel.init();
|
||||
@@ -75,7 +75,7 @@ export class MeasureDialogManager extends BaseDialogManager {
|
||||
this.config = this.panel.getConfig();
|
||||
if (this.config) {
|
||||
console.log('[MeasureDialogManager] 同步缓存设置到引擎:', this.config);
|
||||
this.registry.engine3d?.saveMeasureSetting({
|
||||
this.engineComponent?.saveMeasureSetting({
|
||||
unit: this.config.unit,
|
||||
precision: this.config.precision
|
||||
});
|
||||
@@ -92,22 +92,22 @@ export class MeasureDialogManager extends BaseDialogManager {
|
||||
this.dialog?.fitHeight(false);
|
||||
|
||||
// 同步净高默认值到引擎(朝下=0,选择点='point')
|
||||
this.registry.engine3d?.setClearHeightDirection(0);
|
||||
this.registry.engine3d?.setClearHeightSelectType('point');
|
||||
this.engineComponent?.setClearHeightDirection(0);
|
||||
this.engineComponent?.setClearHeightSelectType('point');
|
||||
|
||||
const engine = this.registry.engine3d?.getEngine();
|
||||
if (engine?.events) {
|
||||
const ec = this.engineComponent;
|
||||
if (ec) {
|
||||
const handler = (data: EngineMeasureData) => {
|
||||
console.log('[MeasureDialogManager] 测量值回调:', data);
|
||||
if (data && this.panel) {
|
||||
this.handleMeasureChanged(data);
|
||||
}
|
||||
};
|
||||
engine.events.on('measure-changed', handler);
|
||||
engine.events.on('measure-click', handler);
|
||||
ec.onRawEvent('measure-changed', handler);
|
||||
ec.onRawEvent('measure-click', handler);
|
||||
this.unsubscribeMeasureChanged = () => {
|
||||
engine.events.off('measure-changed', handler);
|
||||
engine.events.off('measure-click', handler);
|
||||
ec.offRawEvent('measure-changed', handler);
|
||||
ec.offRawEvent('measure-click', handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,9 +158,7 @@ export class MeasureDialogManager extends BaseDialogManager {
|
||||
this.unsubscribeMeasureChanged();
|
||||
this.unsubscribeMeasureChanged = null;
|
||||
}
|
||||
if (this.registry.engine3d) {
|
||||
this.registry.engine3d.deactivateMeasure();
|
||||
}
|
||||
this.engineComponent?.deactivateMeasure();
|
||||
if (this.panel) {
|
||||
this.panel.destroy();
|
||||
this.panel = null;
|
||||
|
||||
@@ -64,9 +64,9 @@ export class SectionAxisDialogManager extends BaseDialogManager {
|
||||
onHideToggle: (isHidden) => {
|
||||
console.log('[SectionAxisDialogManager] 隐藏切换:', isHidden);
|
||||
if (isHidden) {
|
||||
this.registry.engine3d?.hideSection();
|
||||
this.engineComponent?.hideSection();
|
||||
} else {
|
||||
this.registry.engine3d?.recoverSection();
|
||||
this.engineComponent?.recoverSection();
|
||||
}
|
||||
},
|
||||
onReverse: () => {
|
||||
@@ -75,7 +75,7 @@ export class SectionAxisDialogManager extends BaseDialogManager {
|
||||
},
|
||||
onAxisChange: (axis) => {
|
||||
console.log('[SectionAxisDialogManager] 切换轴向:', axis);
|
||||
this.registry.engine3d?.activeSection(axis);
|
||||
this.engineComponent?.activeSection(axis);
|
||||
}
|
||||
});
|
||||
this.panel.init();
|
||||
@@ -87,13 +87,13 @@ export class SectionAxisDialogManager extends BaseDialogManager {
|
||||
this.dialog?.fitHeight(false);
|
||||
|
||||
// 检查 Engine 是否已初始化
|
||||
if (!this.registry.engine3d) {
|
||||
if (!this.engineComponent) {
|
||||
console.error('[SectionAxisDialogManager] Engine not initialized. Call initEngine() first.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 自动激活默认轴向剖切(X轴)
|
||||
this.registry.engine3d.activeSection('x');
|
||||
this.engineComponent.activeSection('x');
|
||||
}
|
||||
|
||||
/** 对话框关闭时的回调,取消工具栏按钮激活状态 */
|
||||
@@ -103,7 +103,7 @@ export class SectionAxisDialogManager extends BaseDialogManager {
|
||||
|
||||
/** 销毁前的清理,销毁面板实例 */
|
||||
protected onBeforeDestroy(): void {
|
||||
this.registry.engine3d?.deactivateSection();
|
||||
this.engineComponent?.deactivateSection();
|
||||
|
||||
if (this.panel) {
|
||||
this.panel.destroy();
|
||||
|
||||
@@ -56,30 +56,30 @@ export class SectionBoxDialogManager extends BaseDialogManager {
|
||||
onHideToggle: (isHidden) => {
|
||||
console.log('[SectionBoxDialogManager] 隐藏切换:', isHidden);
|
||||
if (isHidden) {
|
||||
this.registry.engine3d?.hideSection();
|
||||
this.engineComponent?.hideSection();
|
||||
} else {
|
||||
this.registry.engine3d?.recoverSection();
|
||||
this.engineComponent?.recoverSection();
|
||||
}
|
||||
},
|
||||
onReverseToggle: (isReversed) => {
|
||||
console.log('[SectionBoxDialogManager] 反向切换:', isReversed);
|
||||
// 底层 reverse() 为“切换一次”,这里不使用 isReversed 作为入参,只要用户点击就触发。
|
||||
this.registry.engine3d?.reverseSection();
|
||||
this.engineComponent?.reverseSection();
|
||||
},
|
||||
onFitToModel: () => {
|
||||
// 对接底层 scaleBox():缩放剖切盒到场景整体包围盒
|
||||
this.registry.engine3d?.scaleSectionBox();
|
||||
this.engineComponent?.scaleSectionBox();
|
||||
},
|
||||
onReset: () => {
|
||||
// 重置定义:关闭剖切再打开剖切盒。
|
||||
// UI 侧会自行将滑块强制恢复到 0-100,并将隐藏/反向按钮恢复为关闭状态。
|
||||
this.registry.engine3d?.deactivateSection();
|
||||
this.registry.engine3d?.activeSection('box');
|
||||
this.engineComponent?.deactivateSection();
|
||||
this.engineComponent?.activeSection('box');
|
||||
// 确保剖切可见(避免上一次处于隐藏状态导致“看起来没重置”)
|
||||
this.registry.engine3d?.recoverSection();
|
||||
this.engineComponent?.recoverSection();
|
||||
},
|
||||
onRangeChange: (range) => {
|
||||
this.registry.engine3d?.setSectionBoxRange(range);
|
||||
this.engineComponent?.setSectionBoxRange(range);
|
||||
}
|
||||
});
|
||||
this.panel.init();
|
||||
@@ -87,18 +87,18 @@ export class SectionBoxDialogManager extends BaseDialogManager {
|
||||
}
|
||||
|
||||
protected onDialogCreated(): void {
|
||||
this.registry.engine3d?.activeSection('box');
|
||||
this.engineComponent?.activeSection('box');
|
||||
this.dialog?.fitHeight(false);
|
||||
|
||||
const engine = this.registry.engine3d?.getEngine();
|
||||
if (engine?.events) {
|
||||
const ec = this.engineComponent;
|
||||
if (ec) {
|
||||
const handler = (data: any) => {
|
||||
if (data && this.panel) {
|
||||
this.panel.setRange(data as Partial<SectionBoxRange>);
|
||||
}
|
||||
};
|
||||
engine.events.on('section-move', handler);
|
||||
this.unsubscribeSectionMove = () => engine.events.off('section-move', handler);
|
||||
ec.onRawEvent('section-move', handler);
|
||||
this.unsubscribeSectionMove = () => ec.offRawEvent('section-move', handler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ export class SectionBoxDialogManager extends BaseDialogManager {
|
||||
this.unsubscribeSectionMove();
|
||||
this.unsubscribeSectionMove = null;
|
||||
}
|
||||
this.registry.engine3d?.deactivateSection();
|
||||
this.engineComponent?.deactivateSection();
|
||||
if (this.panel) {
|
||||
this.panel.destroy();
|
||||
this.panel = null;
|
||||
|
||||
@@ -56,9 +56,9 @@ export class SectionPlaneDialogManager extends BaseDialogManager {
|
||||
onHideToggle: (isHidden) => {
|
||||
console.log('[SectionPlaneDialogManager] 隐藏切换:', isHidden);
|
||||
if (isHidden) {
|
||||
this.registry.engine3d?.hideSection();
|
||||
this.engineComponent?.hideSection();
|
||||
} else {
|
||||
this.registry.engine3d?.recoverSection();
|
||||
this.engineComponent?.recoverSection();
|
||||
}
|
||||
},
|
||||
onReverse: () => {
|
||||
@@ -74,7 +74,7 @@ export class SectionPlaneDialogManager extends BaseDialogManager {
|
||||
|
||||
/** 对话框创建后的回调 */
|
||||
protected onDialogCreated(): void {
|
||||
this.registry.engine3d?.activeSection('face');
|
||||
this.engineComponent?.activeSection('face');
|
||||
this.dialog?.fitHeight(false);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ export class SectionPlaneDialogManager extends BaseDialogManager {
|
||||
|
||||
/** 销毁前的清理 */
|
||||
protected onBeforeDestroy(): void {
|
||||
this.registry.engine3d?.deactivateSection();
|
||||
this.engineComponent?.deactivateSection();
|
||||
if (this.panel) {
|
||||
this.panel.destroy();
|
||||
this.panel = null;
|
||||
|
||||
@@ -5,11 +5,8 @@ import { t } from '../services/locale';
|
||||
/** 渲染模式类型 */
|
||||
type RenderMode = 'simple' | 'balance' | 'advanced';
|
||||
|
||||
/** 地面类型 */
|
||||
type GroundType = 'none' | 'concrete' | 'grass' | 'tile' | 'water' | 'wood';
|
||||
|
||||
/** 环境背景类型 */
|
||||
type EnvironmentType = 'default' | 'outdoor' | 'indoor' | 'night' | 'overcast' | 'studio';
|
||||
/** 设置项列表项(环境背景/地面类型由底层引擎动态返回) */
|
||||
type SettingListItem = { name: string; id: string };
|
||||
|
||||
// ======================== 通用样式常量 ========================
|
||||
// 全部使用项目主题 CSS 变量(--bim-*),同时提供浅色/深色皆可见的 fallback
|
||||
@@ -46,7 +43,7 @@ const HOVER_BG = 'var(--bim-component-bg-hover, rgba(0,0,0,0.04))';
|
||||
|
||||
/**
|
||||
* 设置弹窗管理器
|
||||
* 管理全局设置面板,包含渲染模式、边线、地面、对比度/饱和度、光照强度、环境背景等设置项
|
||||
* 管理全局设置面板,包含渲染模式、边线、光照/对比度/饱和度、环境背景、地面等设置项
|
||||
*/
|
||||
export class SettingDialogManager extends BaseDialogManager {
|
||||
protected get dialogId() { return 'setting-dialog'; }
|
||||
@@ -57,18 +54,26 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
|
||||
/** 边线开关状态 */
|
||||
private edgeLineEnabled: boolean = false;
|
||||
/** 当前选中的地面类型 */
|
||||
private groundType: GroundType = 'none';
|
||||
/** 地面大小 */
|
||||
private groundSize: number = 100;
|
||||
/** 对比度(0-200,100为默认值) */
|
||||
private contrastValue: number = 100;
|
||||
/** 饱和度(0-200,100为默认值) */
|
||||
private saturationValue: number = 100;
|
||||
/** 光照强度(0-200,100为默认值) */
|
||||
private lightIntensityValue: number = 100;
|
||||
/** 当前选中的环境背景 */
|
||||
private environmentType: EnvironmentType = 'default';
|
||||
/** 对比度(0-100,50为默认值) */
|
||||
private contrastValue: number = 50;
|
||||
/** 饱和度(0-100,50为默认值) */
|
||||
private saturationValue: number = 50;
|
||||
/** 光照强度(0-100,50为默认值) */
|
||||
private lightIntensityValue: number = 50;
|
||||
/** 环境背景列表(从底层引擎动态获取) */
|
||||
private environmentList: SettingListItem[] = [];
|
||||
/** 当前选中的环境背景 ID */
|
||||
private environmentId: string = '';
|
||||
/** 是否显示背景 */
|
||||
private backgroundVisible: boolean = true;
|
||||
/** 地面列表(从底层引擎动态获取) */
|
||||
private groundList: SettingListItem[] = [];
|
||||
/** 当前选中的地面 ID */
|
||||
private groundId: string = '';
|
||||
/** 地面高度(单位:m) */
|
||||
private groundElevation: number = 0;
|
||||
/** 保存的对话框位置(用于 hide/show 时保持位置不变) */
|
||||
private savedPosition: { x: number; y: number } | null = null;
|
||||
|
||||
constructor(registry: ManagerRegistry) {
|
||||
super(registry);
|
||||
@@ -76,8 +81,15 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
|
||||
public init(): void { }
|
||||
|
||||
/** 对话框居中显示 */
|
||||
/** 对话框位置:若有保存的位置则恢复,否则居中 */
|
||||
protected getDialogPosition() {
|
||||
// 若上次 hide/show 保存了位置,则恢复到原位
|
||||
if (this.savedPosition) {
|
||||
const pos = this.savedPosition;
|
||||
this.savedPosition = null;
|
||||
return pos;
|
||||
}
|
||||
|
||||
const container = this.registry.container;
|
||||
if (!container) return { x: 100, y: 100 };
|
||||
|
||||
@@ -90,9 +102,39 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 从底层引擎加载当前状态(回显)
|
||||
* 在 createContent() 前调用,保证 UI 和引擎状态同步
|
||||
*/
|
||||
private loadEngineState(): void {
|
||||
const engine = this.engineComponent;
|
||||
if (!engine) return;
|
||||
|
||||
// 边线
|
||||
this.edgeLineEnabled = engine.getModelEdgeActive();
|
||||
|
||||
// 光照 / 对比度 / 饱和度
|
||||
this.lightIntensityValue = engine.getAmbientLightIntensity();
|
||||
this.contrastValue = engine.getSceneContrast();
|
||||
this.saturationValue = engine.getSceneSaturation();
|
||||
|
||||
// 环境背景
|
||||
this.environmentList = engine.getHDRBackgroundList();
|
||||
this.environmentId = engine.getHDRBackgroundId();
|
||||
this.backgroundVisible = engine.getHDRBackgroundVisibility();
|
||||
|
||||
// 地面
|
||||
this.groundList = engine.getGroundList();
|
||||
this.groundId = engine.getGroundId();
|
||||
this.groundElevation = engine.getGroundElevation();
|
||||
}
|
||||
|
||||
// ======================== 主内容构建 ========================
|
||||
|
||||
protected createContent(): HTMLElement {
|
||||
// 从引擎加载当前状态(回显)
|
||||
this.loadEngineState();
|
||||
|
||||
// 注入一次全局滑块样式
|
||||
this.ensureSliderStyle();
|
||||
|
||||
@@ -111,32 +153,41 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
content.appendChild(this.createSliderSection(
|
||||
t('setting.lightIntensity'),
|
||||
this.lightIntensityValue,
|
||||
0, 200,
|
||||
(v) => { this.lightIntensityValue = v; }
|
||||
0, 100,
|
||||
(v) => {
|
||||
this.lightIntensityValue = v;
|
||||
this.engineComponent?.setAmbientLightIntensity(v);
|
||||
}
|
||||
));
|
||||
|
||||
// 4. 对比度
|
||||
content.appendChild(this.createSliderSection(
|
||||
t('setting.contrast'),
|
||||
this.contrastValue,
|
||||
0, 200,
|
||||
(v) => { this.contrastValue = v; }
|
||||
0, 100,
|
||||
(v) => {
|
||||
this.contrastValue = v;
|
||||
this.engineComponent?.setSceneContrast(v);
|
||||
}
|
||||
));
|
||||
|
||||
// 5. 饱和度
|
||||
content.appendChild(this.createSliderSection(
|
||||
t('setting.saturation'),
|
||||
this.saturationValue,
|
||||
0, 200,
|
||||
(v) => { this.saturationValue = v; }
|
||||
0, 100,
|
||||
(v) => {
|
||||
this.saturationValue = v;
|
||||
this.engineComponent?.setSceneSaturation(v);
|
||||
}
|
||||
));
|
||||
content.appendChild(this.createDivider());
|
||||
|
||||
// 6. 环境背景(下拉)
|
||||
// 6. 环境背景(动态列表 + 显示开关)
|
||||
content.appendChild(this.createEnvironmentSection());
|
||||
content.appendChild(this.createDivider());
|
||||
|
||||
// 7. 显示地面(下拉 + 大小输入)
|
||||
// 7. 地面(动态列表 + 地面高度)
|
||||
content.appendChild(this.createGroundSection());
|
||||
|
||||
return content;
|
||||
@@ -155,7 +206,7 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
|
||||
/** 创建渲染模式按钮组 */
|
||||
private createRenderModeSection(): HTMLElement {
|
||||
const currentMode = (this.registry.engine3d?.getRenderMode() ?? 'balance') as RenderMode;
|
||||
const currentMode = (this.engineComponent?.getRenderMode() ?? 'balance') as RenderMode;
|
||||
|
||||
const section = document.createElement('div');
|
||||
|
||||
@@ -201,8 +252,12 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
});
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
this.registry.engine3d?.setRenderMode(mode.key);
|
||||
// 重建弹窗以刷新状态
|
||||
this.engineComponent?.setRenderMode(mode.key);
|
||||
// 保存当前对话框位置,重建后恢复
|
||||
const el = document.getElementById(this.dialogId);
|
||||
if (el) {
|
||||
this.savedPosition = { x: el.offsetLeft, y: el.offsetTop };
|
||||
}
|
||||
this.hide();
|
||||
this.show();
|
||||
});
|
||||
@@ -218,45 +273,18 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
|
||||
/** 创建边线开关行 */
|
||||
private createEdgeLineSection(): HTMLElement {
|
||||
const row = document.createElement('div');
|
||||
row.style.cssText = ROW_STYLE;
|
||||
|
||||
// 标签
|
||||
const label = document.createElement('span');
|
||||
label.style.cssText = 'font-size: 13px; color: var(--bim-text-secondary, #475569);';
|
||||
label.textContent = t('setting.edgeLine');
|
||||
row.appendChild(label);
|
||||
|
||||
// 开关容器 —— 关闭态使用 --bim-border-strong 保证浅色主题可见
|
||||
const toggleOffBg = 'var(--bim-border-strong, #cbd5e1)';
|
||||
const toggleOnBg = PRIMARY;
|
||||
|
||||
const toggle = document.createElement('div');
|
||||
toggle.style.cssText = `
|
||||
width: 40px; height: 22px; border-radius: 11px; cursor: pointer;
|
||||
position: relative; transition: background 0.2s;
|
||||
background: ${this.edgeLineEnabled ? toggleOnBg : toggleOffBg};
|
||||
`;
|
||||
|
||||
// 滑块圆点
|
||||
const knob = document.createElement('div');
|
||||
knob.style.cssText = `
|
||||
width: 18px; height: 18px; border-radius: 50%;
|
||||
background: #fff; position: absolute; top: 2px;
|
||||
transition: left 0.2s;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
||||
left: ${this.edgeLineEnabled ? '20px' : '2px'};
|
||||
`;
|
||||
toggle.appendChild(knob);
|
||||
|
||||
toggle.addEventListener('click', () => {
|
||||
this.edgeLineEnabled = !this.edgeLineEnabled;
|
||||
toggle.style.background = this.edgeLineEnabled ? toggleOnBg : toggleOffBg;
|
||||
knob.style.left = this.edgeLineEnabled ? '20px' : '2px';
|
||||
});
|
||||
|
||||
row.appendChild(toggle);
|
||||
return row;
|
||||
return this.createToggleRow(
|
||||
t('setting.edgeLine'),
|
||||
this.edgeLineEnabled,
|
||||
(enabled) => {
|
||||
this.edgeLineEnabled = enabled;
|
||||
if (enabled) {
|
||||
this.engineComponent?.activeModelEdge();
|
||||
} else {
|
||||
this.engineComponent?.disActiveModelEdge();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// ======================== 3/4/5. 通用滑块 ========================
|
||||
@@ -351,98 +379,112 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
// ======================== 6. 环境背景(下拉) ========================
|
||||
// ======================== 6. 环境背景(动态列表 + 显示开关) ========================
|
||||
|
||||
/** 创建环境背景下拉选择区 */
|
||||
/** 创建环境背景区 */
|
||||
private createEnvironmentSection(): HTMLElement {
|
||||
const section = document.createElement('div');
|
||||
|
||||
// 标签
|
||||
const label = document.createElement('div');
|
||||
label.style.cssText = SECTION_LABEL_STYLE;
|
||||
label.textContent = t('setting.environment');
|
||||
section.appendChild(label);
|
||||
|
||||
const envKeys: EnvironmentType[] = ['default', 'outdoor', 'indoor', 'night', 'overcast', 'studio'];
|
||||
|
||||
// 下拉列表(从底层引擎动态获取)
|
||||
const select = this.createSelect(
|
||||
envKeys.map(key => ({
|
||||
value: key,
|
||||
label: t(`setting.environments.${key}` as any)
|
||||
})),
|
||||
this.environmentType,
|
||||
(val) => { this.environmentType = val as EnvironmentType; }
|
||||
this.environmentList.map(item => ({ value: item.id, label: item.name })),
|
||||
this.environmentId,
|
||||
(val) => {
|
||||
this.environmentId = val;
|
||||
this.engineComponent?.setHDRBackgroundId(val);
|
||||
}
|
||||
);
|
||||
|
||||
section.appendChild(select);
|
||||
|
||||
// 显示背景开关
|
||||
const toggleRow = this.createToggleRow(
|
||||
t('setting.backgroundVisible'),
|
||||
this.backgroundVisible,
|
||||
(enabled) => {
|
||||
this.backgroundVisible = enabled;
|
||||
this.engineComponent?.setHDRBackgroundVisibility(enabled);
|
||||
}
|
||||
);
|
||||
toggleRow.style.marginTop = '10px';
|
||||
section.appendChild(toggleRow);
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
// ======================== 7. 显示地面(下拉 + 大小) ========================
|
||||
// ======================== 7. 地面(动态列表 + 地面高度) ========================
|
||||
|
||||
/** 创建地面设置区 */
|
||||
private createGroundSection(): HTMLElement {
|
||||
const section = document.createElement('div');
|
||||
|
||||
// 地面类型标签 + 下拉
|
||||
// 标签
|
||||
const label = document.createElement('div');
|
||||
label.style.cssText = SECTION_LABEL_STYLE;
|
||||
label.textContent = t('setting.ground');
|
||||
section.appendChild(label);
|
||||
|
||||
const groundKeys: GroundType[] = ['none', 'concrete', 'grass', 'tile', 'water', 'wood'];
|
||||
|
||||
// 下拉列表(从底层引擎动态获取)
|
||||
const select = this.createSelect(
|
||||
groundKeys.map(key => ({
|
||||
value: key,
|
||||
label: t(`setting.groundTypes.${key}` as any)
|
||||
})),
|
||||
this.groundType,
|
||||
this.groundList.map(item => ({ value: item.id, label: item.name })),
|
||||
this.groundId,
|
||||
(val) => {
|
||||
this.groundType = val as GroundType;
|
||||
// 显示/隐藏大小输入
|
||||
sizeRow.style.display = val === 'none' ? 'none' : 'flex';
|
||||
this.groundId = val;
|
||||
this.engineComponent?.setGroundId(val);
|
||||
}
|
||||
);
|
||||
section.appendChild(select);
|
||||
|
||||
// 大小输入行
|
||||
const sizeRow = document.createElement('div');
|
||||
sizeRow.style.cssText = ROW_STYLE + ' margin-top: 10px;';
|
||||
sizeRow.style.display = this.groundType === 'none' ? 'none' : 'flex';
|
||||
// 地面高度输入行
|
||||
const elevationRow = document.createElement('div');
|
||||
elevationRow.style.cssText = ROW_STYLE + ' margin-top: 10px;';
|
||||
|
||||
const sizeLabel = document.createElement('span');
|
||||
sizeLabel.style.cssText = 'font-size: 13px; color: var(--bim-text-secondary, #475569);';
|
||||
sizeLabel.textContent = t('setting.groundSize');
|
||||
sizeRow.appendChild(sizeLabel);
|
||||
const elevationLabel = document.createElement('span');
|
||||
elevationLabel.style.cssText = 'font-size: 13px; color: var(--bim-text-secondary, #475569);';
|
||||
elevationLabel.textContent = t('setting.groundElevation');
|
||||
elevationRow.appendChild(elevationLabel);
|
||||
|
||||
const sizeInput = document.createElement('input');
|
||||
sizeInput.type = 'number';
|
||||
sizeInput.value = String(this.groundSize);
|
||||
sizeInput.min = '1';
|
||||
sizeInput.max = '10000';
|
||||
sizeInput.style.cssText = `
|
||||
width: 80px; padding: 4px 8px; border-radius: 4px;
|
||||
// 输入框 + 单位容器
|
||||
const inputWrapper = document.createElement('div');
|
||||
inputWrapper.style.cssText = 'display: flex; align-items: center; gap: 4px;';
|
||||
|
||||
const elevationInput = document.createElement('input');
|
||||
elevationInput.type = 'number';
|
||||
elevationInput.value = String(this.groundElevation);
|
||||
elevationInput.style.cssText = `
|
||||
width: 72px; padding: 4px 8px; border-radius: 4px;
|
||||
border: 1px solid ${BORDER_COLOR};
|
||||
background: ${INPUT_BG};
|
||||
color: ${TEXT_PRIMARY};
|
||||
font-size: 13px; outline: none; text-align: right;
|
||||
`;
|
||||
sizeInput.addEventListener('input', () => {
|
||||
const v = Number(sizeInput.value);
|
||||
if (!isNaN(v) && v > 0) {
|
||||
this.groundSize = v;
|
||||
elevationInput.addEventListener('input', () => {
|
||||
const v = Number(elevationInput.value);
|
||||
if (!isNaN(v)) {
|
||||
this.groundElevation = v;
|
||||
this.engineComponent?.setGroundElevation(v);
|
||||
}
|
||||
});
|
||||
// 聚焦时高亮边框
|
||||
sizeInput.addEventListener('focus', () => {
|
||||
sizeInput.style.borderColor = PRIMARY;
|
||||
elevationInput.addEventListener('focus', () => {
|
||||
elevationInput.style.borderColor = PRIMARY;
|
||||
});
|
||||
sizeInput.addEventListener('blur', () => {
|
||||
sizeInput.style.borderColor = BORDER_COLOR;
|
||||
elevationInput.addEventListener('blur', () => {
|
||||
elevationInput.style.borderColor = BORDER_COLOR;
|
||||
});
|
||||
|
||||
sizeRow.appendChild(sizeInput);
|
||||
section.appendChild(sizeRow);
|
||||
const unitLabel = document.createElement('span');
|
||||
unitLabel.style.cssText = 'font-size: 13px; color: var(--bim-text-secondary, #475569);';
|
||||
unitLabel.textContent = t('setting.groundElevationUnit');
|
||||
|
||||
inputWrapper.appendChild(elevationInput);
|
||||
inputWrapper.appendChild(unitLabel);
|
||||
elevationRow.appendChild(inputWrapper);
|
||||
section.appendChild(elevationRow);
|
||||
|
||||
return section;
|
||||
}
|
||||
@@ -493,7 +535,7 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
onChange(select.value);
|
||||
});
|
||||
|
||||
// 聚焦高亮
|
||||
// 聚焦<EFBFBD><EFBFBD>亮
|
||||
select.addEventListener('focus', () => {
|
||||
select.style.borderColor = PRIMARY;
|
||||
});
|
||||
@@ -503,4 +545,61 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
|
||||
return select;
|
||||
}
|
||||
|
||||
// ======================== 通用开关行 ========================
|
||||
|
||||
/**
|
||||
* 创建一个标签 + 开关的行
|
||||
* @param labelText 标签文本
|
||||
* @param enabled 当前开关状态
|
||||
* @param onChange 状态变化回调
|
||||
*/
|
||||
private createToggleRow(
|
||||
labelText: string,
|
||||
enabled: boolean,
|
||||
onChange: (enabled: boolean) => void
|
||||
): HTMLElement {
|
||||
const row = document.createElement('div');
|
||||
row.style.cssText = ROW_STYLE;
|
||||
|
||||
// 标签
|
||||
const label = document.createElement('span');
|
||||
label.style.cssText = 'font-size: 13px; color: var(--bim-text-secondary, #475569);';
|
||||
label.textContent = labelText;
|
||||
row.appendChild(label);
|
||||
|
||||
// 开关容器
|
||||
const toggleOffBg = 'var(--bim-border-strong, #cbd5e1)';
|
||||
const toggleOnBg = PRIMARY;
|
||||
|
||||
let currentState = enabled;
|
||||
|
||||
const toggle = document.createElement('div');
|
||||
toggle.style.cssText = `
|
||||
width: 40px; height: 22px; border-radius: 11px; cursor: pointer;
|
||||
position: relative; transition: background 0.2s;
|
||||
background: ${currentState ? toggleOnBg : toggleOffBg};
|
||||
`;
|
||||
|
||||
// 滑块圆点
|
||||
const knob = document.createElement('div');
|
||||
knob.style.cssText = `
|
||||
width: 18px; height: 18px; border-radius: 50%;
|
||||
background: #fff; position: absolute; top: 2px;
|
||||
transition: left 0.2s;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
||||
left: ${currentState ? '20px' : '2px'};
|
||||
`;
|
||||
toggle.appendChild(knob);
|
||||
|
||||
toggle.addEventListener('click', () => {
|
||||
currentState = !currentState;
|
||||
toggle.style.background = currentState ? toggleOnBg : toggleOffBg;
|
||||
knob.style.left = currentState ? '20px' : '2px';
|
||||
onChange(currentState);
|
||||
});
|
||||
|
||||
row.appendChild(toggle);
|
||||
return row;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,12 +38,12 @@ export class WalkControlManager extends BaseManager {
|
||||
|
||||
// 打开漫游面板时,默认激活第一人称模式
|
||||
console.log('[WalkControl] 打开漫游面板,激活第一人称模式');
|
||||
this.registry.engine3d?.activateFirstPersonMode();
|
||||
this.engineComponent?.activateFirstPersonMode();
|
||||
|
||||
this.panel = new WalkControlPanel({
|
||||
onPlanViewToggle: (isActive) => {
|
||||
console.log('[WalkControl] 小地图:', isActive);
|
||||
this.registry.engine3d?.toggleMiniMap();
|
||||
this.engineComponent?.toggleMiniMap();
|
||||
this.emit('walk:plan-view-toggle', { isActive });
|
||||
},
|
||||
onPathModeToggle: (isActive) => {
|
||||
@@ -66,17 +66,17 @@ export class WalkControlManager extends BaseManager {
|
||||
onSpeedChange: (speed) => {
|
||||
console.log('[WalkControl] 速度变化:', speed);
|
||||
const engineSpeed = speed * 0.1;
|
||||
this.registry.engine3d?.setWalkSpeed(engineSpeed);
|
||||
this.engineComponent?.setWalkSpeed(engineSpeed);
|
||||
this.emit('walk:speed-change', { speed });
|
||||
},
|
||||
onGravityToggle: (enabled) => {
|
||||
console.log('[WalkControl] 重力:', enabled);
|
||||
this.registry.engine3d?.setWalkGravity(enabled);
|
||||
this.engineComponent?.setWalkGravity(enabled);
|
||||
this.emit('walk:gravity-toggle', { enabled });
|
||||
},
|
||||
onCollisionToggle: (enabled) => {
|
||||
console.log('[WalkControl] 碰撞:', enabled);
|
||||
this.registry.engine3d?.setWalkCollision(enabled);
|
||||
this.engineComponent?.setWalkCollision(enabled);
|
||||
this.emit('walk:collision-toggle', { enabled });
|
||||
},
|
||||
onCharacterModelChange: (model) => {
|
||||
@@ -111,7 +111,7 @@ export class WalkControlManager extends BaseManager {
|
||||
this.pathManager?.hide();
|
||||
|
||||
console.log('[WalkControl] 关闭漫游面板,退出第一人称模式');
|
||||
this.registry.engine3d?.deactivateFirstPersonMode();
|
||||
this.engineComponent?.deactivateFirstPersonMode();
|
||||
|
||||
if (this.panel) {
|
||||
this.panel.destroy();
|
||||
|
||||
Reference in New Issue
Block a user