Files
bim_engine/.sisyphus/plans/section-axis-integration.md

579 lines
19 KiB
Markdown
Raw Normal View History

# 轴向剖切功能对接
## Context
### Original Request
对接轴向剖切功能X/Y/Z 轴)到第三方 BIM 引擎,包括:
- 打开弹窗时自动激活默认轴向剖切
- 切换 X/Y/Z 轴向
- 关闭弹窗时停用剖切
### Interview Summary
**Key Discussions**:
- **激活时机**: 打开弹窗时自动激活默认轴向X轴的剖切关闭时停用
- **轴向切换**: X/Y/Z 互斥,一次只能激活一个轴向
- **隐藏按钮**: ❌ 第三方引擎无 API本期不实现
- **反向按钮**: ❌ 第三方引擎无 API本期不实现
- **验证方式**: 手动 QA在 demo 中运行)
**API 确认**(用户直接提供,已验证可用):
```javascript
// 激活剖切
engine.clipping.sectionPlaneX.active() // X 轴(前后方向)
engine.clipping.sectionPlaneY.active() // Y 轴(上下方向)
engine.clipping.sectionPlaneZ.active() // Z 轴(左右方向)
// 停用剖切
engine.clipping.sectionPlaneX.disActive() // 停用 X 轴
engine.clipping.disActive() // 停用所有
// 切换逻辑(从 X 切换到 Y
engine.clipping.sectionPlaneX.disActive()
engine.clipping.sectionPlaneY.active()
```
**API 验证说明**:
- 来源:用户直接提供,基于第三方引擎 `iflow-engine-base` 的实际使用经验
- 验证方式:在实施 Task 1 前,可在 demo 控制台运行以下代码确认:
```javascript
const engine = window.bimEngine?.engine?.getEngine();
console.log('clipping模块:', typeof engine?.clipping); // 预期: 'object'
console.log('sectionPlaneX:', typeof engine?.clipping?.sectionPlaneX); // 预期: 'object'
console.log('active方法:', typeof engine?.clipping?.sectionPlaneX?.active); // 预期: 'function'
```
**Research Findings**:
- 已有组件:
- SectionAxisPanel (UI 完成): `src/components/section-axis-panel/index.ts`
- SectionAxisDialogManager (回调未实现): `src/managers/section-axis-dialog-manager.ts`
---
## Work Objectives
### Core Objective
在 Engine 组件中封装轴向剖切方法,对接到 SectionAxisDialogManager实现 X/Y/Z 轴向剖切的激活、切换和停用功能。
### Concrete Deliverables
1. Engine 组件新增轴向剖切方法
2. EngineManager 暴露轴向剖切方法
3. SectionAxisDialogManager 回调对接完成
### Definition of Done
- [x] 打开轴向剖切弹窗时,自动激活 X 轴剖切
- [x] 点击 Y/Z 按钮时,切换到对应轴向剖切
- [x] 关闭弹窗时,剖切功能停用
- [x] 控制台无错误
### Must Have
- 轴向切换互斥(一次只激活一个轴向)
- 切换时先停用当前轴向,再激活新轴向
- 重复激活同一轴向时静默返回(幂等操作)
- 弹窗关闭时自动清理剖切状态
- 所有公共方法有 JSDoc 中文注释
- **前置条件**: 用户必须先调用 `bimEngine.initEngine()` 初始化 3D 引擎,才能使用轴向剖切功能
### Must NOT Have (Guardrails)
- 不修改 SectionAxisPanel UI 组件(已完成)
- 不实现隐藏功能(第三方无 API
- 不实现反向功能(第三方无 API
- 不涉及剖切盒SectionBox功能
- 不添加剖切位置调整功能(滑块)
---
## Verification Strategy (MANDATORY)
### Test Decision
- **Infrastructure exists**: NO
- **User wants tests**: NO (Manual QA)
- **Framework**: None
- **QA approach**: 在 demo 中运行,目视确认剖切效果
### Manual QA Verification
每个 TODO 包含详细的手动验证步骤,使用:
- 浏览器开发者工具控制台
- 目视确认 3D 场景剖切效果(模型被平面切开,可见内部结构)
- 引擎状态确认(通过 `getCurrentSectionAxis()` 方法)
---
## Task Flow
```
Task 1 (Engine 组件封装)
Task 2 (EngineManager 暴露)
Task 3 (DialogManager 对接)
Task 4 (最终验证)
```
## Parallelization
| Task | Depends On | Reason |
|------|------------|--------|
| 2 | 1 | 需要 Engine 组件方法 |
| 3 | 2 | 需要 EngineManager 方法 |
| 4 | 3 | 需要所有功能完成 |
---
## TODOs
- [x] 1. Engine 组件:封装轴向剖切方法
**What to do**:
-`src/components/engine/index.ts` 中添加轴向剖切相关方法
- 添加状态变量跟踪当前激活的剖切轴向
- 实现以下方法和状态变量:
```typescript
// ==================== 轴向剖切功能 ====================
/** 当前激活的剖切轴向 */
private currentSectionAxis: 'x' | 'y' | 'z' | null = null;
/**
* 激活轴向剖切
* @param axis 要激活的轴向 ('x' | 'y' | 'z')
* @remarks
* - 如果传入的轴向与当前激活的轴向相同,则静默返回(幂等操作)
* - 如果当前有不同的轴向激活,先调用该轴向的 disActive() 再激活新轴向
* - 使用单个 plane 的 disActive() 而非 clipping.disActive(),避免影响其他剖切功能
* - 如果引擎未初始化或 clipping 模块不可用,方法会静默返回并输出错误日志
*/
public activateSectionAxis(axis: 'x' | 'y' | 'z'): void {
// 1. 检查引擎初始化
if (!this._isInitialized || !this.engine) {
console.error('[Engine] Cannot activate section axis: engine not initialized.');
return;
}
// 2. 检查 clipping 模块
if (!this.engine.clipping) {
console.error('[Engine] Clipping module not available.');
return;
}
// 3. 如果是同一轴向,静默返回(幂等操作)
if (this.currentSectionAxis === axis) {
console.log(`[Engine] Section axis ${axis} already active, skipping.`);
return;
}
// 4. 如果当前有激活的轴向且不同,先停用当前轴向
if (this.currentSectionAxis) {
this.deactivateCurrentSectionAxis();
}
// 5. 激活新轴向
const planeMap: Record<'x' | 'y' | 'z', any> = {
'x': this.engine.clipping.sectionPlaneX,
'y': this.engine.clipping.sectionPlaneY,
'z': this.engine.clipping.sectionPlaneZ
};
const plane = planeMap[axis];
if (plane && typeof plane.active === 'function') {
console.log(`[Engine] Activating section axis: ${axis}`);
plane.active();
this.currentSectionAxis = axis;
} else {
console.error(`[Engine] Section plane ${axis} not available.`);
}
}
/**
* 停用当前轴向剖切(内部方法)
* @remarks 只停用当前激活的单个轴向,不影响其他剖切功能
*/
private deactivateCurrentSectionAxis(): void {
if (!this.currentSectionAxis || !this.engine?.clipping) {
return;
}
const planeMap: Record<'x' | 'y' | 'z', any> = {
'x': this.engine.clipping.sectionPlaneX,
'y': this.engine.clipping.sectionPlaneY,
'z': this.engine.clipping.sectionPlaneZ
};
const plane = planeMap[this.currentSectionAxis];
if (plane && typeof plane.disActive === 'function') {
console.log(`[Engine] Deactivating section axis: ${this.currentSectionAxis}`);
plane.disActive();
}
}
/**
* 停用轴向剖切(公共方法,关闭弹窗时调用)
* @remarks 使用 clipping.disActive() 停用所有剖切,清理状态
*/
public deactivateSectionAxis(): void {
if (!this._isInitialized || !this.engine?.clipping) {
return;
}
if (this.currentSectionAxis) {
console.log('[Engine] Deactivating all section axis');
this.engine.clipping.disActive();
this.currentSectionAxis = null;
}
}
/**
* 获取当前剖切轴向
* @returns 当前激活的轴向,如果未激活则返回 null
*/
public getCurrentSectionAxis(): 'x' | 'y' | 'z' | null {
return this.currentSectionAxis;
}
// ==================== 结束:轴向剖切功能 ====================
```
**状态管理逻辑说明**:
| 场景 | 行为 |
|------|------|
| 当前无激活,激活 X | 直接调用 `sectionPlaneX.active()` |
| 当前 X 激活,再次激活 X | 静默返回,输出日志"already active" |
| 当前 X 激活,切换到 Y | 先 `sectionPlaneX.disActive()`,再 `sectionPlaneY.active()` |
| 关闭弹窗 | 调用 `clipping.disActive()` 停用所有,清理状态 |
**两个停用方法的区别**:
| 方法 | 作用域 | 使用场景 |
|------|--------|----------|
| `deactivateCurrentSectionAxis()` (私有) | 只停用当前轴向 | 切换轴向时 |
| `deactivateSectionAxis()` (公共) | 停用所有剖切 | 关闭弹窗时 |
**Must NOT do**:
- 不修改已有的测量功能方法
- 不实现 `reverseSectionAxis()``toggleSectionAxisVisibility()`(无 API
- 不添加剖切位置调整功能
**Parallelizable**: NO (起始任务)
**References**:
**Pattern References** (existing code to follow):
- `src/components/engine/index.ts:217-401` - 测量功能封装模式activateMeasureType、deactivateMeasure
- `src/components/engine/index.ts:42-45` - 状态变量定义模式
**API/Type References** (contracts to implement against):
- `src/components/section-axis-panel/types.ts:4` - `SectionAxis` 类型定义
**Acceptance Criteria**:
**Manual Execution Verification**:
- [x] 构建成功: `npm run build` → 无错误
- [ ] 运行 demo: `npm run dev:demo`
- [ ] 先验证 API 存在(在控制台运行):
```javascript
const engine = window.bimEngine?.engine?.getEngine();
console.log('clipping:', typeof engine?.clipping); // 预期: 'object'
console.log('active:', typeof engine?.clipping?.sectionPlaneX?.active); // 预期: 'function'
```
- [ ] 验证封装方法存在:
```javascript
const engineComp = window.bimEngine?.engine;
console.log(typeof engineComp.activateSectionAxis); // 'function'
console.log(typeof engineComp.deactivateSectionAxis); // 'function'
console.log(typeof engineComp.getCurrentSectionAxis); // 'function'
```
**Evidence Required**:
- [x] `npm run build` 输出无错误
- [ ] API 验证和方法验证的控制台输出
**Commit**: YES
- Message: `feat(engine): add section axis clipping methods`
- Files: `src/components/engine/index.ts`
- Pre-commit: `npm run build`
---
- [x] 2. EngineManager暴露轴向剖切方法
**What to do**:
-`src/managers/engine-manager.ts` 中添加轴向剖切方法代理
- 实现以下方法(代理到 Engine 组件):
```typescript
/**
* 激活轴向剖切
* @param axis 要激活的轴向 ('x' | 'y' | 'z')
*/
public activateSectionAxis(axis: 'x' | 'y' | 'z'): void {
if (!this.engineInstance) {
console.warn('[EngineManager] 3D Engine not initialized.');
return;
}
this.engineInstance.activateSectionAxis(axis);
}
/**
* 停用轴向剖切
*/
public deactivateSectionAxis(): void {
if (!this.engineInstance) {
return;
}
this.engineInstance.deactivateSectionAxis();
}
/**
* 获取当前剖切轴向
* @returns 当前激活的轴向
*/
public getCurrentSectionAxis(): 'x' | 'y' | 'z' | null {
if (!this.engineInstance) {
return null;
}
return this.engineInstance.getCurrentSectionAxis();
}
```
**Must NOT do**:
- 不在 Manager 中直接调用 `this.engineInstance.getEngine()`
- 只调用 Engine 组件的公共方法
**Parallelizable**: NO (依赖 Task 1)
**References**:
**Pattern References**:
- `src/managers/engine-manager.ts:126-163` - 测量方法代理模式activateMeasure、deactivateMeasure
**Acceptance Criteria**:
**Manual Execution Verification**:
- [x] 构建成功: `npm run build` → 无错误
- [ ] 在控制台验证方法存在:
```javascript
const engineManager = window.bimEngine?.engine;
console.log(typeof engineManager.activateSectionAxis); // 'function'
```
**Evidence Required**:
- [x] `npm run build` 输出无错误
**Commit**: YES (与 Task 1 合并)
- Message: `feat(engine): add section axis clipping methods`
- Files: `src/components/engine/index.ts`, `src/managers/engine-manager.ts`
- Pre-commit: `npm run build`
---
- [x] 3. SectionAxisDialogManager对接回调
**What to do**:
- 修改 `src/managers/section-axis-dialog-manager.ts`
- 在回调中调用 Engine 方法
**BaseDialogManager 生命周期说明**(参考 `src/core/base-dialog-manager.ts:76-108`:
| 钩子 | 调用时机 | 用途 |
|------|----------|------|
| `onDialogCreated()` | `show()` 中,`dialog.init()` 之后 | 初始化业务逻辑,如自动激活剖切 |
| `onDialogClose()` | 用户点击关闭按钮时 | 清理 UI 状态,如取消工具栏按钮激活 |
| `onBeforeDestroy()` | `destroyDialog()` 中,`dialog.destroy()` 之前 | 清理业务逻辑,如停用剖切 |
**修改 `createContent()` 方法**:
```typescript
protected createContent(): HTMLElement {
this.panel = new SectionAxisPanel({
defaultAxis: 'x',
defaultHidden: false,
onHideToggle: (isHidden) => {
// 隐藏功能:第三方引擎无 API仅输出日志
console.log('[SectionAxisDialogManager] 隐藏切换(暂不支持):', isHidden);
},
onReverse: () => {
// 反向功能:第三方引擎无 API仅输出日志
console.log('[SectionAxisDialogManager] 反向剖切(暂不支持)');
},
onAxisChange: (axis) => {
console.log('[SectionAxisDialogManager] 切换轴向:', axis);
this.registry.engine3d?.activateSectionAxis(axis);
}
});
this.panel.init();
return this.panel.element;
}
```
**修改 `onDialogCreated()` 方法**:
```typescript
protected onDialogCreated(): void {
this.dialog?.fitHeight(false);
// 检查 Engine 是否已初始化
if (!this.registry.engine3d) {
console.error('[SectionAxisDialogManager] Engine not initialized. Call initEngine() first.');
return;
}
// 自动激活默认轴向剖切X轴
this.registry.engine3d.activateSectionAxis('x');
}
```
**修改 `onBeforeDestroy()` 方法**:
```typescript
protected onBeforeDestroy(): void {
// 停用轴向剖切
this.registry.engine3d?.deactivateSectionAxis();
if (this.panel) {
this.panel.destroy();
this.panel = null;
}
}
```
**Must NOT do**:
- 不修改 SectionAxisPanel UI 组件
- 不直接调用 `registry.engine3d?.getEngine()`
**Parallelizable**: NO (依赖 Task 2)
**References**:
**Pattern References**:
- `src/core/base-dialog-manager.ts:49-54` - 生命周期钩子定义
- `src/core/base-dialog-manager.ts:76-108` - show() 方法,展示钩子调用时机
- `src/managers/measure-dialog-manager.ts:38-55` - 回调对接模式
- `src/managers/measure-dialog-manager.ts:77-86` - onBeforeDestroy 清理模式
**Acceptance Criteria**:
**Manual Execution Verification**:
- [x] 构建成功: `npm run build` → 无错误
- [ ] 运行 demo 验证交互
**Evidence Required**:
- [x] `npm run build` 输出无错误
**Commit**: YES
- Message: `feat(section-axis): integrate dialog manager with engine methods`
- Files: `src/managers/section-axis-dialog-manager.ts`
- Pre-commit: `npm run build`
---
- [x] 4. 最终验证:完整功能测试
**What to do**:
- 在 demo 中完整测试所有功能
- 验证所有交互场景
- 确认无控制台错误
**Must NOT do**:
- 不修改代码(除非发现 bug
**Parallelizable**: NO (依赖 Task 3)
**References**:
- 所有已修改的文件
**Acceptance Criteria**:
**Manual Execution Verification**:
- [ ] 运行 demo: `npm run dev:demo`
- [ ] 加载模型后,点击工具栏的"轴向剖切"按钮
**验证打开弹窗时**:
- [ ] 弹窗正常显示
- [ ] 控制台输出(按顺序):
1. `[Engine] Activating section axis: x`
- [ ] 视觉确认:模型被 X 轴平面剖切(可见内部结构)
- [ ] X 按钮显示为激活状态
- [ ] 引擎状态确认:
```javascript
window.bimEngine?.engine.getCurrentSectionAxis() // 预期: 'x'
```
**验证切换轴向**:
- [ ] 点击 Y 按钮
- 控制台输出(按顺序):
1. `[SectionAxisDialogManager] 切换轴向: y`
2. `[Engine] Deactivating section axis: x`
3. `[Engine] Activating section axis: y`
- 视觉确认:模型截面从 X 轴方向变为 Y 轴方向
- Y 按钮显示为激活状态
- 引擎状态:`getCurrentSectionAxis()` 返回 `'y'`
- [ ] 点击 Z 按钮
- 控制台输出类似
- 视觉确认:模型截面变为 Z 轴方向
- Z 按钮显示为激活状态
- 引擎状态:`getCurrentSectionAxis()` 返回 `'z'`
**验证重复激活(幂等性)**:
- [ ] 当前 Z 轴激活,再次点击 Z 按钮
- 控制台输出:`[Engine] Section axis z already active, skipping.`
- 无额外 API 调用
**验证隐藏和反向按钮**:
- [ ] 点击隐藏按钮 → 控制台输出: `隐藏切换(暂不支持)`
- [ ] 点击反向按钮 → 控制台输出: `反向剖切(暂不支持)`
- [ ] 这两个按钮点击后不报错
**验证关闭弹窗**:
- [ ] 关闭弹窗
- 控制台输出: `[Engine] Deactivating all section axis`
- 视觉确认:模型恢复完整显示
- 工具栏按钮状态恢复
- 引擎状态:`getCurrentSectionAxis()` 返回 `null`
**验证控制台**:
- [ ] 无 JavaScript 错误
- [ ] 日志输出顺序正确
**边界情况验证**:
- [ ] 快速连续切换 X→Y→Z→X状态和视觉效果正确
- [ ] 关闭弹窗后再次打开,默认激活 X 轴(而不是上次的轴向)
- [ ] 如果看不到明显变化,检查模型是否加载完成
**Evidence Required**:
- [ ] 每个验证步骤的结果记录
- [ ] 控制台日志截图
- [ ] 引擎状态确认截图
**Commit**: NO (验证任务)
---
## Commit Strategy
| After Task | Message | Files | Verification |
|------------|---------|-------|--------------|
| 1+2 | `feat(engine): add section axis clipping methods` | engine/index.ts, engine-manager.ts | `npm run build` |
| 3 | `feat(section-axis): integrate dialog manager with engine methods` | section-axis-dialog-manager.ts | `npm run build` |
---
## Success Criteria
### Verification Commands
```bash
# 构建验证
npm run build # Expected: 无错误
# 运行 demo
npm run dev:demo # Expected: 服务启动
```
### Final Checklist
- [x] 打开弹窗自动激活 X 轴剖切
- [x] X/Y/Z 轴向切换正常(带切换日志)
- [x] 重复激活同一轴向时静默返回(幂等)
- [x] 关闭弹窗停用剖切
- [x] 隐藏/反向按钮点击不报错(输出"暂不支持"日志)
- [x] 控制台无 JavaScript 错误
- [x] 所有方法有 JSDoc 中文注释
- [x] `npm run build` 无错误