19 KiB
轴向剖切功能对接
Context
Original Request
对接轴向剖切功能(X/Y/Z 轴)到第三方 BIM 引擎,包括:
- 打开弹窗时自动激活默认轴向剖切
- 切换 X/Y/Z 轴向
- 关闭弹窗时停用剖切
Interview Summary
Key Discussions:
- 激活时机: 打开弹窗时自动激活默认轴向(X轴)的剖切,关闭时停用
- 轴向切换: X/Y/Z 互斥,一次只能激活一个轴向
- 隐藏按钮: ❌ 第三方引擎无 API,本期不实现
- 反向按钮: ❌ 第三方引擎无 API,本期不实现
- 验证方式: 手动 QA(在 demo 中运行)
API 确认(用户直接提供,已验证可用):
// 激活剖切
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 控制台运行以下代码确认:
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
- SectionAxisPanel (UI 完成):
Work Objectives
Core Objective
在 Engine 组件中封装轴向剖切方法,对接到 SectionAxisDialogManager,实现 X/Y/Z 轴向剖切的激活、切换和停用功能。
Concrete Deliverables
- Engine 组件新增轴向剖切方法
- EngineManager 暴露轴向剖切方法
- SectionAxisDialogManager 回调对接完成
Definition of Done
- 打开轴向剖切弹窗时,自动激活 X 轴剖切
- 点击 Y/Z 按钮时,切换到对应轴向剖切
- 关闭弹窗时,剖切功能停用
- 控制台无错误
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
-
1. Engine 组件:封装轴向剖切方法
What to do:
- 在
src/components/engine/index.ts中添加轴向剖切相关方法 - 添加状态变量跟踪当前激活的剖切轴向
- 实现以下方法和状态变量:
// ==================== 轴向剖切功能 ==================== /** 当前激活的剖切轴向 */ 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:
- 构建成功:
npm run build→ 无错误 - 运行 demo:
npm run dev:demo - 先验证 API 存在(在控制台运行):
const engine = window.bimEngine?.engine?.getEngine(); console.log('clipping:', typeof engine?.clipping); // 预期: 'object' console.log('active:', typeof engine?.clipping?.sectionPlaneX?.active); // 预期: 'function' - 验证封装方法存在:
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:
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
- 在
-
2. EngineManager:暴露轴向剖切方法
What to do:
- 在
src/managers/engine-manager.ts中添加轴向剖切方法代理 - 实现以下方法(代理到 Engine 组件):
/** * 激活轴向剖切 * @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:
- 构建成功:
npm run build→ 无错误 - 在控制台验证方法存在:
const engineManager = window.bimEngine?.engine; console.log(typeof engineManager.activateSectionAxis); // 'function'
Evidence Required:
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
- 在
-
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()方法: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()方法: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()方法: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:
- 构建成功:
npm run build→ 无错误 - 运行 demo 验证交互
Evidence Required:
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
- 修改
-
4. 最终验证:完整功能测试
What to do:
- 在 demo 中完整测试所有功能
- 验证所有交互场景
- 确认无控制台错误
Must NOT do:
- 不修改代码(除非发现 bug)
Parallelizable: NO (依赖 Task 3)
References:
- 所有已修改的文件
Acceptance Criteria:
Manual Execution Verification:
- 运行 demo:
npm run dev:demo - 加载模型后,点击工具栏的"轴向剖切"按钮
验证打开弹窗时:
- 弹窗正常显示
- 控制台输出(按顺序):
[Engine] Activating section axis: x
- 视觉确认:模型被 X 轴平面剖切(可见内部结构)
- X 按钮显示为激活状态
- 引擎状态确认:
window.bimEngine?.engine.getCurrentSectionAxis() // 预期: 'x'
验证切换轴向:
- 点击 Y 按钮
- 控制台输出(按顺序):
[SectionAxisDialogManager] 切换轴向: y[Engine] Deactivating section axis: x[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
# 构建验证
npm run build # Expected: 无错误
# 运行 demo
npm run dev:demo # Expected: 服务启动
Final Checklist
- 打开弹窗自动激活 X 轴剖切
- X/Y/Z 轴向切换正常(带切换日志)
- 重复激活同一轴向时静默返回(幂等)
- 关闭弹窗停用剖切
- 隐藏/反向按钮点击不报错(输出"暂不支持"日志)
- 控制台无 JavaScript 错误
- 所有方法有 JSDoc 中文注释
npm run build无错误