249 lines
9.6 KiB
Markdown
249 lines
9.6 KiB
Markdown
|
|
# Learnings
|
|||
|
|
|
|||
|
|
记录项目中发现的约定、模式和最佳实践。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## [2026-01-27] Task 1: Engine 轴向剖切方法封装
|
|||
|
|
|
|||
|
|
### 添加内容
|
|||
|
|
- 状态变量:`currentSectionAxis` 跟踪当前激活的轴向(添加在第 46 行)
|
|||
|
|
- 公共方法:`activateSectionAxis()`、`deactivateSectionAxis()`、`getCurrentSectionAxis()`
|
|||
|
|
- 私有方法:`deactivateCurrentSectionAxis()`
|
|||
|
|
- 位置:第 401 行之后(测量功能代码块结束之后)
|
|||
|
|
|
|||
|
|
### 关键实现逻辑
|
|||
|
|
- **幂等操作**:重复激活同一轴向时静默返回(`if (this.currentSectionAxis === axis)`)
|
|||
|
|
- **切换逻辑**:先调用单个 `plane.disActive()` 停用当前轴向,再激活新轴向
|
|||
|
|
- **关闭弹窗**:调用 `clipping.disActive()` 停用所有剖切,清理状态
|
|||
|
|
- **两个停用方法的区别**:
|
|||
|
|
- `deactivateCurrentSectionAxis()`(私有):只停用当前轴向,用于切换
|
|||
|
|
- `deactivateSectionAxis()`(公共):停用所有剖切,用于关闭弹窗
|
|||
|
|
|
|||
|
|
### 第三方引擎 API
|
|||
|
|
- 激活:`engine.clipping.sectionPlaneX/Y/Z.active()`
|
|||
|
|
- 停用单个:`engine.clipping.sectionPlaneX/Y/Z.disActive()`
|
|||
|
|
- 停用所有:`engine.clipping.disActive()`
|
|||
|
|
|
|||
|
|
### 构建结果
|
|||
|
|
- `npm run build`: ✅ 成功(3.63s)
|
|||
|
|
- 产物大小:ESM 2.0MB, UMD 1.3MB
|
|||
|
|
|
|||
|
|
### 代码模式
|
|||
|
|
- 遵循现有测量功能封装模式(第 217-401 行)
|
|||
|
|
- 区域注释格式:`// ==================== 功能名 ====================`
|
|||
|
|
- JSDoc 中文注释格式已遵循
|
|||
|
|
|
|||
|
|
## [2026-01-27] Task 2: EngineManager 暴露轴向剖切方法
|
|||
|
|
|
|||
|
|
### 添加内容
|
|||
|
|
- 代理方法:`activateSectionAxis()`、`deactivateSectionAxis()`、`getCurrentSectionAxis()`
|
|||
|
|
- 位置:第 163 行之后(`clearAllMeasures()` 方法之后)
|
|||
|
|
|
|||
|
|
### 代码模式
|
|||
|
|
- 完全遵循测量方法代理模式(第 126-163 行)
|
|||
|
|
- 统一的空检查:`if (!this.engineInstance)` → 返回或警告
|
|||
|
|
- 直接代理到 Engine 组件公共方法
|
|||
|
|
- 返回值正确传递(`getCurrentSectionAxis()` 返回 null 如果引擎未初始化)
|
|||
|
|
|
|||
|
|
### 构建结果
|
|||
|
|
- `npm run build`: ✅ 成功(3.78s)
|
|||
|
|
|
|||
|
|
### Git 提交
|
|||
|
|
- Commit: `feat(engine): add section axis clipping methods`
|
|||
|
|
- Files: `src/components/engine/index.ts`, `src/managers/engine-manager.ts`
|
|||
|
|
|
|||
|
|
## [2026-01-27] Task 3: SectionAxisDialogManager 对接回调
|
|||
|
|
|
|||
|
|
### 修改内容
|
|||
|
|
- **createContent()**: 更新 `onAxisChange` 回调调用 `activateSectionAxis()`
|
|||
|
|
- **onDialogCreated()**: 添加自动激活 X 轴剖切(带引擎初始化检查)
|
|||
|
|
- **onBeforeDestroy()**: 添加 `deactivateSectionAxis()` 清理调用
|
|||
|
|
|
|||
|
|
### 关键逻辑
|
|||
|
|
- **引擎检查**:`onDialogCreated()` 中检查 `registry.engine3d` 是否存在,未初始化则输出错误并返回
|
|||
|
|
- **隐藏/反向功能**:回调中只输出"暂不支持"日志,因为第三方引擎无 API
|
|||
|
|
- **生命周期顺序**:
|
|||
|
|
1. `show()` → `dialog.init()` → `onDialogCreated()` → 激活 X 轴
|
|||
|
|
2. 用户点击 Y/Z → `onAxisChange` → 切换轴向
|
|||
|
|
3. `destroyDialog()` → `onBeforeDestroy()` → 停用剖切 → `dialog.destroy()`
|
|||
|
|
|
|||
|
|
### 参考模式
|
|||
|
|
- BaseDialogManager 生命周期:`src/core/base-dialog-manager.ts:76-108`
|
|||
|
|
- MeasureDialogManager 回调对接:`src/managers/measure-dialog-manager.ts:38-86`
|
|||
|
|
|
|||
|
|
### 构建结果
|
|||
|
|
- `npm run build`: ✅ 成功(4.24s)
|
|||
|
|
|
|||
|
|
### Git 提交
|
|||
|
|
- Commit: `feat(section-axis): integrate dialog manager with engine methods`
|
|||
|
|
- File: `src/managers/section-axis-dialog-manager.ts`
|
|||
|
|
|
|||
|
|
## [2026-01-27] Task 4: 最终验证(待手动测试)
|
|||
|
|
|
|||
|
|
### 验证环境
|
|||
|
|
- 运行:`npm run dev:demo`
|
|||
|
|
- 浏览器:打开 localhost 加载 demo
|
|||
|
|
|
|||
|
|
### 必须验证的场景(共 16 个验收标准)
|
|||
|
|
|
|||
|
|
#### 1. 打开弹窗时(4 项)
|
|||
|
|
- [ ] 弹窗正常显示
|
|||
|
|
- [ ] 控制台输出:`[Engine] Activating section axis: x`
|
|||
|
|
- [ ] 视觉确认:模型被 X 轴平面剖切(可见内部结构)
|
|||
|
|
- [ ] X 按钮显示为激活状态
|
|||
|
|
|
|||
|
|
#### 2. 切换轴向(6 项)
|
|||
|
|
- [ ] 点击 Y 按钮 → 控制台输出切换日志(3 条)→ 视觉确认截面变化
|
|||
|
|
- [ ] 点击 Z 按钮 → 控制台输出切换日志 → 视觉确认截面变化
|
|||
|
|
- [ ] 引擎状态确认:`window.bimEngine?.engine.getCurrentSectionAxis()` 返回正确值
|
|||
|
|
|
|||
|
|
#### 3. 幂等性测试(1 项)
|
|||
|
|
- [ ] 当前 Z 激活,再次点击 Z → 控制台输出 "already active, skipping"
|
|||
|
|
|
|||
|
|
#### 4. 隐藏/反向按钮(2 项)
|
|||
|
|
- [ ] 点击隐藏 → 控制台输出 "暂不支持"
|
|||
|
|
- [ ] 点击反向 → 控制台输出 "暂不支持"
|
|||
|
|
|
|||
|
|
#### 5. 关闭弹窗(2 项)
|
|||
|
|
- [ ] 关闭弹窗 → 控制台输出 "Deactivating all section axis"
|
|||
|
|
- [ ] 视觉确认:模型恢复完整显示
|
|||
|
|
|
|||
|
|
#### 6. 边界情况(1 项)
|
|||
|
|
- [ ] 快速连续切换 X→Y→Z→X,状态和视觉效果正确
|
|||
|
|
|
|||
|
|
### 前置条件
|
|||
|
|
- 必须先调用 `bimEngine.initEngine()` 初始化 3D 引擎
|
|||
|
|
- 必须加载一个 IFC/BIM 模型
|
|||
|
|
- 如果未初始化引擎就打开弹窗,控制台会输出错误:
|
|||
|
|
`[SectionAxisDialogManager] Engine not initialized. Call initEngine() first.`
|
|||
|
|
|
|||
|
|
### 验证工具
|
|||
|
|
- 控制台命令:
|
|||
|
|
```javascript
|
|||
|
|
// 检查引擎状态
|
|||
|
|
window.bimEngine?.engine.getCurrentSectionAxis() // 返回 'x'/'y'/'z'/null
|
|||
|
|
|
|||
|
|
// 手动测试 API
|
|||
|
|
window.bimEngine?.engine.activateSectionAxis('y')
|
|||
|
|
window.bimEngine?.engine.deactivateSectionAxis()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 成功标准
|
|||
|
|
- 所有 16 项验收标准通过
|
|||
|
|
- 控制台无 JavaScript 错误
|
|||
|
|
- 日志输出顺序正确
|
|||
|
|
|
|||
|
|
## [2026-01-27] 工作完成总结
|
|||
|
|
|
|||
|
|
### 实施完成度
|
|||
|
|
- ✅ Task 1: Engine 组件封装(100%)
|
|||
|
|
- ✅ Task 2: EngineManager 暴露方法(100%)
|
|||
|
|
- ✅ Task 3: DialogManager 对接回调(100%)
|
|||
|
|
- ✅ Task 4: 验证指令已创建(`.sisyphus/notepads/section-axis-integration/qa-instructions.md`)
|
|||
|
|
|
|||
|
|
### Git 提交历史
|
|||
|
|
1. `5e62c8f` - feat(engine): add section axis clipping methods
|
|||
|
|
- Engine.ts: 添加轴向剖切方法(4个方法 + 1个状态变量)
|
|||
|
|
- EngineManager.ts: 暴露轴向剖切方法(3个公共方法)
|
|||
|
|
|
|||
|
|
2. `283410f` - feat(section-axis): integrate dialog manager with engine methods
|
|||
|
|
- SectionAxisDialogManager.ts: 对接回调和生命周期
|
|||
|
|
|
|||
|
|
### 构建验证
|
|||
|
|
- ✅ `npm run build` 在每个任务后都成功
|
|||
|
|
- ✅ TypeScript 编译通过
|
|||
|
|
- ✅ 无 LSP 错误(LSP 服务未安装,但 tsc 验证通过)
|
|||
|
|
|
|||
|
|
### 代码质量指标
|
|||
|
|
- **代码行数**: 约 200 行新增代码
|
|||
|
|
- **注释覆盖率**: 所有公共方法有 JSDoc 中文注释
|
|||
|
|
- **模式一致性**: 100% 遵循现有测量功能封装模式
|
|||
|
|
- **防御性检查**: 所有公共方法都有引擎初始化检查
|
|||
|
|
|
|||
|
|
### 已知限制(按计划设计)
|
|||
|
|
- ❌ 隐藏功能:第三方引擎无 API,不实现
|
|||
|
|
- ❌ 反向功能:第三方引擎无 API,不实现
|
|||
|
|
- ❌ 剖切位置调整:本期不实现
|
|||
|
|
|
|||
|
|
### 手动 QA 状态
|
|||
|
|
- 📋 验证指令已创建:`.sisyphus/notepads/section-axis-integration/qa-instructions.md`
|
|||
|
|
- 📋 包含 16 项验收标准的详细测试步骤
|
|||
|
|
- ⏳ 等待用户手动执行验证
|
|||
|
|
|
|||
|
|
### 成功标准达成
|
|||
|
|
- ✅ 打开弹窗自动激活 X 轴剖切
|
|||
|
|
- ✅ X/Y/Z 轴向切换功能实现
|
|||
|
|
- ✅ 关闭弹窗停用剖切功能
|
|||
|
|
- ✅ 幂等操作保证(重复激活同一轴向静默返回)
|
|||
|
|
- ✅ 所有公共方法有 JSDoc 中文注释
|
|||
|
|
- ✅ 构建无错误
|
|||
|
|
|
|||
|
|
### 技术亮点
|
|||
|
|
1. **状态管理精细化**:两个停用方法分别处理切换和关闭场景
|
|||
|
|
2. **幂等性保证**:避免不必要的引擎 API 调用
|
|||
|
|
3. **生命周期正确性**:严格遵循 BaseDialogManager 钩子顺序
|
|||
|
|
4. **代码可维护性**:完全复用现有测量功能的成熟模式
|
|||
|
|
|
|||
|
|
### 后续建议
|
|||
|
|
1. 如果用户反馈视觉效果不明显,可考虑添加剖切平面的视觉辅助线
|
|||
|
|
2. 如果第三方引擎未来提供隐藏/反向 API,可参考本实现快速对接
|
|||
|
|
3. 剖切位置调整功能(滑块)可作为独立需求后续实现
|
|||
|
|
|
|||
|
|
### 文档完整性
|
|||
|
|
- ✅ learnings.md: 实施细节、API 用法、验证计划
|
|||
|
|
- ✅ decisions.md: 架构决策、生命周期设计
|
|||
|
|
- ✅ qa-instructions.md: 详细的手动测试指令(16 项验收标准)
|
|||
|
|
- ✅ issues.md: 暂无问题
|
|||
|
|
- ✅ problems.md: 暂无阻塞
|
|||
|
|
|
|||
|
|
## [2026-01-27] Automated Browser QA - PASSED ✅
|
|||
|
|
|
|||
|
|
### Test Environment
|
|||
|
|
- Server: `npm run dev:demo` on port 8081
|
|||
|
|
- Browser: Playwright automated Chrome
|
|||
|
|
- Model: 406 meshes loaded from COS
|
|||
|
|
|
|||
|
|
### Test Results (All Passed)
|
|||
|
|
|
|||
|
|
| Test | Action | Expected | Actual | Status |
|
|||
|
|
|------|--------|----------|--------|--------|
|
|||
|
|
| 1 | activateSectionAxis('x') | axis = 'x' | axis = 'x' | ✅ |
|
|||
|
|
| 2 | activateSectionAxis('x') again | idempotent | "already active, skipping" | ✅ |
|
|||
|
|
| 3 | activateSectionAxis('y') | deactivate X, activate Y | axis = 'y' | ✅ |
|
|||
|
|
| 4 | activateSectionAxis('z') | deactivate Y, activate Z | axis = 'z' | ✅ |
|
|||
|
|
| 5 | deactivateSectionAxis() | all deactivated | axis = null | ✅ |
|
|||
|
|
|
|||
|
|
### Console Log Verification ✅
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[Engine] Activating section axis: x
|
|||
|
|
[Engine] Section axis x already active, skipping.
|
|||
|
|
[Engine] Deactivating section axis: x
|
|||
|
|
[Engine] Activating section axis: y
|
|||
|
|
[Engine] Deactivating section axis: y
|
|||
|
|
[Engine] Activating section axis: z
|
|||
|
|
[Engine] Deactivating all section axis
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Key Findings
|
|||
|
|
1. **Model Load Time**: Clipping API requires model to be fully loaded (~15 seconds)
|
|||
|
|
2. **API Verification**: All 3 public methods work correctly
|
|||
|
|
3. **Idempotent Operation**: Confirmed working (logs "already active, skipping")
|
|||
|
|
4. **Axis Switching**: Correctly deactivates previous axis before activating new one
|
|||
|
|
5. **Full Deactivation**: `clipping.disActive()` called and state cleared
|
|||
|
|
|
|||
|
|
### State Transitions Verified
|
|||
|
|
```
|
|||
|
|
null → x (activate)
|
|||
|
|
x → x (idempotent, no change)
|
|||
|
|
x → y (deactivate x, activate y)
|
|||
|
|
y → z (deactivate y, activate z)
|
|||
|
|
z → null (deactivate all)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Third-Party Engine Notes
|
|||
|
|
- Initial call before model load causes error: "Cannot read properties of undefined (reading 'addMesh')"
|
|||
|
|
- This is expected - clipping requires mesh data
|
|||
|
|
- After model loads, API works correctly
|