- 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>
6.7 KiB
6.7 KiB
机械零件套装互动教学(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;对象合并(底座开孔刀具、螺栓头+杆)改为 typedjoinObjects,与产品 Object → Join 同一路径。 - 实时校验读取 SceneIR:节点名称/类型/父子、变换、网格包围盒、修改器栈与参数、布尔操作数、集合成员、材质槽。
- 保存/导出校验绑定 App 的
userAction状态(SAVE/SAVE_AS/EXPORT),不以dirty=false单独判定。 - 进度持久化:
- OPFS
tutorial/parts-progress.json为权威记录,localStorage 仅为同步镜像/降级;OPFS 写入串行化避免旧写覆盖新写。 - 每步推进后写入 OPFS 恢复快照(
saveSnapshot),重新进入教程时从 OPFS 恢复工程并回到记录的步骤。
- 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。
本批修复的真实缺口
- 引导入口就绪竞态:侧栏在默认工程尚未打开时就能点击“演示本步”,Python 请求被引擎拒绝
(
Invalid embedded Python request)。现在侧栏暴露ready/busy,场景就绪前禁用演示与下一步。 - 修改器 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。 - 保存校验:教程把
SAVE/SAVE_AS的 userAction 状态与dirty结合,避免“新工程未保存”误判。 - 合并/父子路径:演示中的 Join 改为 typed
joinObjects(与 Object → Join 一致);assemblies 的父子改为 typedsetParents(与 Object → Parent 一致),避免bpy.ops.object.join/parent_set在无头上下文中的行为差异。 - OPFS 进度串行写 + 恢复前置:OPFS 记录先于本地镜像落盘,加载完成后再决定是否恢复工程,避免空进度先占位。
- Web 业务工具模式下的 GLB 导出失败:SceneIR 会列出
object.convert后遗留的孤立曲线数据块; 导出前对任意nonMeshData都调用evaluateDepsgraph,在没有原生 GPU 画布的 Web 业务工具模式下该评估不返回; 即使跳过评估,导出器也会因为这些未求值数据报NON_MESH_EVALUATION_REQUIRED而不生成 GLB。 现在导出前只保留被场景节点引用的非网格数据(孤立数据块丢弃),仅对实际使用的数据评估; 渲染能力查询也改为最长等待 3 秒的保守探测,避免无 GPU 画布时永久挂起。 - GLB 导出被未使用材质阻塞:导出器会校验 SceneIR 里的全部材质,而启动模板带有未使用的
Dots Stroke材质(没有 Principled/Output 节点图),导致整个导出报SHADER_GRAPH_UNMAPPABLE。 桌面 glTF 导出器只收集被导出对象引用的材质;现在导出前同样只保留被场景网格材质槽引用的材质。 默认场景复测导出 3996 字节 GLB;教程完整场景导出 31264 字节。 - “继续学习”未恢复工程:启动模板在新会话中复用同一个 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,教程 chunkPartsTutorialLanding-*.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)不在本教学案例范围内。