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

19 KiB
Raw Blame History

轴向剖切功能对接

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

Work Objectives

Core Objective

在 Engine 组件中封装轴向剖切方法,对接到 SectionAxisDialogManager实现 X/Y/Z 轴向剖切的激活、切换和停用功能。

Concrete Deliverables

  1. Engine 组件新增轴向剖切方法
  2. EngineManager 暴露轴向剖切方法
  3. 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
    • 加载模型后,点击工具栏的"轴向剖切"按钮

    验证打开弹窗时:

    • 弹窗正常显示
    • 控制台输出(按顺序):
      1. [Engine] Activating section axis: x
    • 视觉确认:模型被 X 轴平面剖切(可见内部结构)
    • X 按钮显示为激活状态
    • 引擎状态确认:
      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

# 构建验证
npm run build  # Expected: 无错误

# 运行 demo
npm run dev:demo  # Expected: 服务启动

Final Checklist

  • 打开弹窗自动激活 X 轴剖切
  • X/Y/Z 轴向切换正常(带切换日志)
  • 重复激活同一轴向时静默返回(幂等)
  • 关闭弹窗停用剖切
  • 隐藏/反向按钮点击不报错(输出"暂不支持"日志)
  • 控制台无 JavaScript 错误
  • 所有方法有 JSDoc 中文注释
  • npm run build 无错误