- update native web_engine sources and rebuilt wasm artifacts - add task cards, status notes and tooling scripts - add web e2e specs, tutorial UI sources and tutorial assets - sync golden task manifests and audit records Co-Authored-By: Claude Code <noreply@anthropic.com>
76 lines
6.7 KiB
Markdown
76 lines
6.7 KiB
Markdown
# 机械零件套装互动教学(PARTS_TUTORIAL)
|
||
|
||
## 用户入口
|
||
|
||
- `/?tutorial=parts`:React 图文落地页(课程介绍、零件卡片、课堂实录截图、开始/继续/重置)。
|
||
- `/?tutorial=parts&start=1`:真实 Web Blender(本地 WASM Main)+ 引导侧栏。
|
||
- 帮助 → 零件建模教学:在新标签页打开落地页。
|
||
|
||
## 已实现
|
||
|
||
- 21 步课程(0 引导、19 个必需步骤、完成总结),覆盖:
|
||
- 创建与变换:立方体、圆柱、贝塞尔圆环、文字、Empty;缩放/位置、应用缩放。
|
||
- 编辑模式:顶面内插、挤出、沉台、轴肩、六角头细节、支架。
|
||
- 修改器与布尔:Bevel、Boolean(Difference,刀具对象指针)、Array、Solidify、Mirror、Subsurf。
|
||
- 层级、材质与交付:集合、父子、三种材质并按槽位分配、复制、保存 `.blend`、导出 GLB。
|
||
- “演示本步”通过同一原生 Main 执行:Python 使用 `bpy`;对象合并(底座开孔刀具、螺栓头+杆)改为 typed `joinObjects`,与产品 Object → Join 同一路径。
|
||
- 实时校验读取 SceneIR:节点名称/类型/父子、变换、网格包围盒、修改器栈与参数、布尔操作数、集合成员、材质槽。
|
||
- 保存/导出校验绑定 App 的 `userAction` 状态(SAVE/SAVE_AS/EXPORT),不以 `dirty=false` 单独判定。
|
||
- 进度持久化:
|
||
- OPFS `tutorial/parts-progress.json` 为权威记录,localStorage 仅为同步镜像/降级;OPFS 写入串行化避免旧写覆盖新写。
|
||
- 每步推进后写入 OPFS 恢复快照(`saveSnapshot`),重新进入教程时从 OPFS 恢复工程并回到记录的步骤。
|
||
- 有头 Chromium 截图:`tools/web/capture-parts-tutorial-screenshots.mjs` 执行全部步骤并输出
|
||
`web/app/public/tutorial/parts/steps/<stepId>.jpg`(含引导侧栏的整页图)与 `<stepId>-view.jpg`(视口参考图)。
|
||
- 教程端到端测试:`web/tests/e2e/wbv2-parts-tutorial-production.spec.ts`。
|
||
|
||
## 本批修复的真实缺口
|
||
|
||
1. 引导入口就绪竞态:侧栏在默认工程尚未打开时就能点击“演示本步”,Python 请求被引擎拒绝
|
||
(`Invalid embedded Python request`)。现在侧栏暴露 `ready/busy`,场景就绪前禁用演示与下一步。
|
||
2. 修改器 SceneIR 元数据缺失(桌面源码对齐):
|
||
`web_engine_blend_main_merge_modifier_snapshot` 中有修改器的对象被错误地 `continue` 跳过,
|
||
导致 `editableParameters` 从未合并,布尔修改器的 `operation`/`object` 无法读取(教程布尔校验失败)。
|
||
已删除该反向早退,保留“有修改器时不做原始法线回填”的原有条件。
|
||
增量编译 `cmake --build build_web_blender6 --target web_engine -j8` 并
|
||
`node tools/web/sync-web-engine-runtime.mjs`;同步后的 `web_engine.wasm` 哈希
|
||
`4cb478454c8cbc2936e346224820c2f56fc70645cb3b8d3237f05b96bd27c6ac`。
|
||
3. 保存校验:教程把 `SAVE`/`SAVE_AS` 的 userAction 状态与 `dirty` 结合,避免“新工程未保存”误判。
|
||
4. 合并/父子路径:演示中的 Join 改为 typed `joinObjects`(与 Object → Join 一致);assemblies 的父子改为 typed `setParents`(与 Object → Parent 一致),避免 `bpy.ops.object.join` / `parent_set` 在无头上下文中的行为差异。
|
||
5. OPFS 进度串行写 + 恢复前置:OPFS 记录先于本地镜像落盘,加载完成后再决定是否恢复工程,避免空进度先占位。
|
||
6. Web 业务工具模式下的 GLB 导出失败:SceneIR 会列出 `object.convert` 后遗留的孤立曲线数据块;
|
||
导出前对任意 `nonMeshData` 都调用 `evaluateDepsgraph`,在没有原生 GPU 画布的 Web 业务工具模式下该评估不返回;
|
||
即使跳过评估,导出器也会因为这些未求值数据报 `NON_MESH_EVALUATION_REQUIRED` 而不生成 GLB。
|
||
现在导出前只保留被场景节点引用的非网格数据(孤立数据块丢弃),仅对实际使用的数据评估;
|
||
渲染能力查询也改为最长等待 3 秒的保守探测,避免无 GPU 画布时永久挂起。
|
||
7. GLB 导出被未使用材质阻塞:导出器会校验 SceneIR 里的全部材质,而启动模板带有未使用的
|
||
`Dots Stroke` 材质(没有 Principled/Output 节点图),导致整个导出报 `SHADER_GRAPH_UNMAPPABLE`。
|
||
桌面 glTF 导出器只收集被导出对象引用的材质;现在导出前同样只保留被场景网格材质槽引用的材质。
|
||
默认场景复测导出 3996 字节 GLB;教程完整场景导出 31264 字节。
|
||
8. “继续学习”未恢复工程:启动模板在新会话中复用同一个 projectId,侧栏用
|
||
`progress.projectId === context.projectId` 判断“已是学习工程”,于是跳过 OPFS 恢复,停留在默认立方体场景。
|
||
教程编辑器的挂载恢复只应比较“是否存在学习进度”,现已移除该 id 相等早退;
|
||
生产 spec 验证恢复后 `[data-testid="scene-stats"]` 显示 `Active 底座`,状态栏显示 `Recovery: baseline r2`。
|
||
(Outliner 集合折叠时行不在 DOM,恢复断言因此改用场景统计而不是 treeitem。)
|
||
|
||
## 验证状态
|
||
|
||
- 有头 Chromium 全流程(2026-11-11 05:14,`capture-parts-tutorial-screenshots.mjs --dev --port 5191`):
|
||
21 步全部通过,`status: PASS`,`errors: []`;保存与 GLB 导出检查均为绿色,
|
||
截图写入 `web/app/public/tutorial/parts/`(21 张步骤图 + 21 张视口图 + 落地页/总览图)。
|
||
- `npm run typecheck`:exit 0(缓存指纹在 App.tsx 修改后重新生成)。
|
||
- `npm run build`:exit 0,教程 chunk `PartsTutorialLanding-*.js`、`usePartsTutorialProgress-*.js` 正常产出。
|
||
- 生产无障碍 spec:通过(落地页 + 首个练习步骤的引导侧栏 axe 检查无 critical/serious 问题);
|
||
测试从引导页进入 `base-rename` 后检查 `.tutorial-checks[aria-live=polite]`。
|
||
- 生产端到端 spec:覆盖“演示 → 下一步 → OPFS 进度 → 刷新后继续学习 → OPFS 恢复工程 →
|
||
保存 .blend 与导出 GLB”,并直接断言 OPFS `tutorial/parts-progress.json`、
|
||
`projects/<id>/snapshots/*.blend` 与 `projects/<id>/scene.blend`。
|
||
- 2026-11-11 最终生产构建 4 个 spec(parts/reducer 生产与无障碍)全部通过;
|
||
parts 恢复断言改用 `scene-stats`,下载用事件队列按扩展名匹配。
|
||
|
||
## 已知限制
|
||
|
||
- 编辑模式演示走 `bpy.ops` 的 EXEC 路径;循环切割滑动、切割刀、模态倒角/内插等模态工具未纳入课程。
|
||
- 完成总结不含“应用全部修改器”步骤;GLB 导出前不强制烘焙修改器。
|
||
- 材质按对象槽位分配,未覆盖共享材质 data-block 的分配 UI。
|
||
- 环境阻塞的既有缺口(OpenVDB voxel remesh、Ocean FFTW3、light-probe bake)不在本教学案例范围内。
|