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

579 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 轴向剖切功能对接
## 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` 无错误