chore: checkpoint web Blender feature-gap work, docs and tests
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

- 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>
This commit is contained in:
mes123456
2026-11-11 10:46:55 +08:00
parent f3e21c6b98
commit d1e6b63d54
665 changed files with 59149 additions and 39194 deletions

View File

@@ -16,10 +16,10 @@
| 字段 | 值 |
| --- | --- |
| 里程碑 | WBV2 capability workflow |
| 当前任务 | `WBV2-P1424-001` |
| parent manifest | `tests/golden/WBV2-P1424-000/manifest.json` |
| 任务卡 | [`tasks/WBV2-P1424-001.md`](tasks/WBV2-P1424-001.md) |
| 里程碑 | 三域全量对标(创建模型 / 模型化 / 模型层级) |
| 当前任务 | `WBV2-P1425-001` |
| parent manifest | `tests/golden/WBV2-P1425-000/manifest.json` |
| 任务卡 | [`tasks/WBV2-P1425-001.md`](tasks/WBV2-P1425-001.md) |
| 专项验收 | `node tools/web/check-execution-control-plane.mjs` |
| legacy coverage checkpoint | `M16-GAP-00282` |
| legacy coverage parent | `tests/golden/M16-GAP-00281/manifest.json` |

View File

@@ -0,0 +1,154 @@
# Web Blender 三域全量对标桌面版实施计划
## Context
桌面 Blender 5.2 → Web/WASM 移植已形成可运行的编辑器骨架,但用户要求的三个核心建模域仍是有界子集:
- **创建模型**:仅 7 种网格基础体走原生 BMesh;Curve/Text/相机/灯光/Empty/骨架等 Add 入口在 `localOnly` 下被禁用;Surface/Metaball/Volume/GP/Lattice/Point Cloud 等整类缺失。
- **模型化**:仅 12 个网格操作,UI 参数固定;Knife/Rip/Spin/Symmetrize/Poke/Grid Fill/Shade Smooth 等缺失;元素选择仍是 React 本地状态;Web 修改器栈无 Apply。
- **模型层级**:父子/集合创建/移动/重命名/可见性已有原生 Main 路径;缺集合嵌套拖拽、删除、active collection、排序、per-ViewLayer 可见性、collection instance、bone/vertex parent、场景切换。
本计划把这三大域按「全量对标桌面 Blender」分阶段补齐。用户已确认:全量对标、沿用现有 WBV2 队列、全部对象类型、Edit Mode + 修改器 + 对象工具、桌面式 UI 全量对标、采用**通用 operator 通道**。
关键复用资产:`web_blender.desktop_operator_catalog/schema/desktop_operator`(`blender-5.2.0/source/blender/web_engine/web_engine_python_bootstrap.py:105-521`)与 `gpuUIInvoke` 原生窗口上下文(`web_engine_gpu_ui.cpp:1483-1523`)已具备属性 schema、选区上下文、modal 所有权、取消/撤销。现有 Add 菜单的 Curve/Text/相机/灯光本就指向这条通道,只是被 `localOnly` 开关挡住。
## 基线(源码派生,规划量化口径)
`tests/golden/WBV2/dynamic-inventory.json`(3,811 units,1,025 VERIFIED / 2,786 unverified):
| 域 | 源码单元 | 已验证 | 未验证 |
|---|---|---|---|
| 创建 + 对象操作 `OBJECT_OT_` | 215(object 目录 235) | 15 | 200 |
| 集合操作 `COLLECTION_OT_` | 12 | 0 | 12 |
| 模型化 `MESH_OT_` | 164 | 8 | 156 |
| Outliner `OUTLINER_OT_` | 73 | 0 | 73 |
| 修改器源 `MODIFIER` | 98 | — | — |
| RNA 属性(UI/属性面板相关) | 979 | — | — |
这些数字只是源码单元覆盖口径,不等于用户可见功能;完成判定必须绑定 capability、module manifest 与真实 Main mutation 证据。
`web/app/src/capabilities/blender-feature-route-catalog.json`(3,811 entries)当前路由分类:
| 单元族 | WEB_MAIN | SERVER_NATIVE | BROWSER_EQUIVALENT |
|---|---|---|---|
| `MESH_OT_` | 8 | 156 | 0 |
| `OBJECT_OT_` | 15 | 200 | 0 |
| `COLLECTION_OT_` | 0 | 12 | 0 |
| `OUTLINER_OT_` | 0 | 0 | 73 |
| `MODIFIER` | 96 | 0 | 2 |
完成口径:三域所有 `SERVER_NATIVE` 单元必须转为有证据的 `WEB_MAIN`;`OUTLINER_OT_` 的 73 个 `BROWSER_EQUIVALENT` 需重新裁决——要么改走原生 Main/operator 通道,要么以只读/纯 UI 语义给出明确证据,不能默认把浏览器近似当等价。
## 总体路线
**共享原语优先,一个模块覆盖一族操作符。** 不为单个 operator 建任务,也不为每个 operator 写专用 C++ 分支。
```text
通用 operator 通道(catalog / schema / invoke / context / undo)
-> 三个域的 module 能力层(capability + 命令契约 + 负例边界)
-> 桌面式 UI(Add/Edit/Object/Outliner 菜单、F3、右键、属性面板)
-> 每模块 manifest / unit 绑定 / status
```
### 现有共享实现(直接复用,不重造)
- **Operator Palette 已存在**:`web/app/src/app/App.tsx:380-530` 已实现路由搜索、`listLocalDesktopOperators`、`describeLocalDesktopOperator`、`chooseRoute`、`chooseOperator` 和属性表单;`gpuUIInvoke` 在 `WebEngineClient.ts:470`。
- **本地原生目录已覆盖全部注册 operator**:`WebEngineClient.ts:879-907` 的 `listLocalDesktopOperators` 直接查询 `bpy` 实时 WM registry,返回全部已注册操作符(route 标 `UNAVAILABLE`,由 `desktop_operator` 的 RNA/poll/EXEC_DEFAULT 决定可执行性),`describeLocalDesktopOperator` 读取实时 RNA schema。因此通用通道本身已覆盖 164/215/73 等操作符,主要工作是启用、菜单接线、逐族证据与缺失语义补齐。
- **原生目录与 schema**:`web_blender.desktop_operator_catalog` / `desktop_operator_schema` / `desktop_operator`(`web_engine_python_bootstrap.py:105-521`)。
- **路由目录**:`web/app/src/capabilities/blender-feature-route-catalog.json`(3,811 entries,按 `WEB_MAIN` / `SERVER_NATIVE` / `BROWSER_EQUIVALENT` 分类)。
- **Main 写回与重建**:`web_engine_api.cpp:1033-1105` 命令后写 .blend 并重建 SceneIR;undo push 在 `:4043-4047`。
- **Outliner 原生行**:`web_engine_main_state.cpp:10230-10279` 调 `outliner_build_tree`,前端 `App.tsx:1606-1903` 渲染。
- **能力矩阵契约**:`docs/BLENDER_WASM_CAPABILITY_MATRIX_TEMPLATE.json`(L0-L5、positive/negative/persistence/undoRedo/resource cases、evidence)。
三个域的工作重点是:把 `SERVER_NATIVE` 路由逐族转为有证据的 `WEB_MAIN`,把 Operator Palette 的能力接入真实菜单/快捷键/Outliner,而不是新建第二套执行器。
每个 module 任务必须交付:能力契约、原生 Main mutation、UI 接线、负例 no-mutation、undo/redo、save/reopen、Worker restart、module manifest 与 status;实现期结果标 `NOT_CLAIMED`。
## 阶段与模块
**阶段顺序理由**:先创建(一切对象的前提),再层级(创建落点、active collection、父子上下文与 Outliner 操作符,同时体量较小可快速验证通道),最后模型化(最大体量,依赖已统一的选择权威与对象上下文)。修改器与对象工具随模型化阶段推进。
### Phase 0 — OPERATOR_CHANNEL_FOUNDATION(前置,计入覆盖需带正例)
- 冻结三域 464 个 operator 单元(164 + 215 + 12 + 73)+ 98 个修改器源 + 相关 RNA 到 module/capability 的映射。
- 加固通用通道:catalog 分页、schema 深度/预算、`areaType`/`mode`/`selectedObjects`/`activeObject` 上下文、`baseRevision` 失效码、modal 取消与输入恢复。
- 代表正例:通过现有 Operator Palette 创建一个非 7 基础体对象(如 Curve Text 或 Monkey),验证 Main mutation、undo、save/reopen;同时验证 `baseRevision` 失效、错误 mode/area、modal 占用三类负例。
- 关键文件:`web_engine_python_bootstrap.py`、`web_engine_gpu_ui.cpp`、`web_engine_python.cpp`、`web/app/src/engine-client/WebEngineClient.ts`、`web/app/src/app/App.tsx:380-530`、`web/protocol/web-engine.ts`。
### Phase 1 — 创建模型全量
| Module | 覆盖 | 复用/接线 |
|---|---|---|
| `OBJECT_CREATE_MESH_PRIMITIVES` | 桌面 Mesh 全部基础体(含 Circle/Grid/Monkey)及参数(rotation/scale/align/enter_editmode) | 通用通道 + `bpy.ops.mesh.primitive_*_add`;保留 `createPrimitive` 作为快路径 |
| `OBJECT_CREATE_CURVE_SURFACE` | Bezier/NURBS/Circle/Path/Curve、Surface、Text 3D 对象 | 通道 + `editors/curve/curve_ops.cc` 注册表 |
| `OBJECT_CREATE_NONMESH` | Metaball、Volume、GP、Point Cloud、Hair Curves、Lattice | 通道 + 对应 `OBJECT_OT_*_add` |
| `OBJECT_CREATE_HELPERS` | Empty(全部显示类型)、Light(全部类型/参数)、Camera、Speaker、Light Probe、Effector | 通道;属性面板改为 schema 驱动 |
| `OBJECT_CREATE_GENERIC` | 通用 `OBJECT_OT_add` / `add_named`、Collection Instance、Edit Mode Add | 通道 + `object_add.cc` |
UI:`web/app/src/app/App.tsx:6881-6925` Add 菜单改为复用 Operator Palette 的 `chooseRoute`/`chooseOperator`(`:380-530`),移除 `localOnly` 禁用;F3(`:6560-6563`)与快捷条(`:7175`)同源;属性对话框由 `desktop_operator_schema` 生成。每类 Add 项在 `blender-feature-route-catalog.json` 中从 `SERVER_NATIVE` 转为 `WEB_MAIN` 并绑定证据。
### Phase 2 — 模型层级全量
| Module | 覆盖 | 缺口/接线 |
|---|---|---|
| `SCENE_HIERARCHY` | 集合嵌套/拖拽、删除集合、active collection、对象/集合排序、场景新建/切换/重命名、master collection 重命名 | 扩展 `core-scene-commands.ts` 构造器;`web_engine_main_state.cpp:17155-17319` BKE 路径 |
| `COLLECTION_VISIBILITY` | per-ViewLayer 眼/渲染开关、exclude/holdout/indirect/hide | 已有 `set_scene_collection_settings:22256`,补 Outliner UI |
| `PARENTING_TYPES` | Bone/Vertex parent、parent_type、inherit flags、保持变换 | `setParent` 扩展 + `object_relations.cc` 语义 |
| `COLLECTION_INSTANCE` | collection instance 数据、SceneIR、视口、Main 写回 | `scene-ir.ts:607-616` 扩展;`reader`/`viewport` 同步 |
| `OUTLINER_OPERATORS` | 73 个 `OUTLINER_OT_`(删除、重命名、排序、可见性、选择、拖放等) | 通用通道 + `space_outliner` 原生实现;`App.tsx:1606-1903` 接线 |
数据模型扩展:`SceneNodeIR`(`scene-ir.ts:40-57`)增加 parent_type/parent_bone/collection 成员;`SceneIR`(`:618-692`)增加 activeCollectionId;`SceneIR` 校验与 reader 同步。
### Phase 3 — 模型化全量
| Module | 覆盖 | 缺口/接线 |
|---|---|---|
| `MESH_SELECTION_AUTHORITY` | 点/边/面选择写回原生、选择历史、Select All/None/Invert/Linked/Similar | `selection-history.ts` 接入 `App.tsx`;原生 selection 上下文 |
| `MESH_EDIT_OPERATORS` | 164 个 `MESH_OT_`:Extrude/Inset/Bevel/Loop Cut/Knife/Rip/Spin/Symmetrize/Poke/Grid Fill/Unsubdivide/Merge by Distance/Shade Smooth/Flip Normals 等 | 通用通道 + `editors/mesh/` 注册表;现有 12 个 typed 命令保留为快路径 |
| `MESH_MODAL_TOOLS` | 交互式 Knife/Loop Cut/Bevel/Inset/Extrude/Spin 等模态操作 | `gpuUIInvoke` modal 路径(`web_engine_gpu_ui.cpp:1470-1530`),取消/输入恢复 |
| `OBJECT_MODELING_TOOLS` | 变换 pivot/orientation/snapping、duplicate/linked、join/separate、mirror/array、shade smooth/flat、apply transform | `App.tsx:1561-1591,6895,7175`;对象级 `OBJECT_OT_` |
| `MODIFIER_FULL` | 98 个修改器源、WITH_WEB 白名单扩展、Apply/Apply as Shape Key/copy-to-selected、schema 驱动参数 | `web_engine_main_state.cpp:12223-12254,7955-7974`;`editor-workflow.ts:516-517` |
| `UV_AND_ATTRIBUTES` | seam/sharp 标记、unwrap/pack/stitch 全量、属性域编辑 | 现有 `NativeUVEdit` 与 `uv-*` 协议扩展 |
### Phase 4 — 桌面式 UI 全量对标与收口
- `DESKTOP_MENU_KEYMAP_PARITY`:Add/Object/Select/Mesh/Vertex/Edge/Face/UV 菜单结构、快捷键、F3 操作符搜索、右键菜单、属性面板布局对齐桌面。
- 每个 module 的 UI 入口必须走同一能力目录(`web/app/src/capabilities/`),避免 App.tsx 内再造平行列表。
- 收口检查:三域所有映射 unit 有证据、无 unverified;负例边界稳定错误码;undo/save/reopen/restart 全链路;module manifest 与 capability matrix 一致。
## 实施批次规则(遵循 AGENTS.md)
- 每个 module 任务按小批次执行:单次读源码≤30 行、修改≤2 文件且增删≤10 行;批次间写 `continuationNotes`。
- 复用现有 `wbv2-implementation.service` / `run-wbv2-scheduler.mjs` 自动补缺;不手工推进 `nextTask`。
- 实现期只做增量编译(`cmake --build build_web_blender6 --target web_engine -j8` → `node tools/web/sync-web-engine-runtime.mjs`),不跑全量测试/回归/浏览器验收。
- 全部功能实现完成后进入用户验证;未验证结果如实记录,编译成功不等于功能完成。
- 新任务由现有队列生成器创建(`tools/web/generate-task-card.mjs` / scheduler),任务 ID 由队列分配;计划只定义 module/capability,不手写队列指针。
- 队列接入方式:把 module 记录加入 `tests/golden/WBV2/task-index.json` 指向的 source plan(字段含 `id`、`module.moduleId`、`module.sourceBacked: true`、`dependsOn`、`capabilities`、`reusedReceipts`),运行 `node tools/web/generate-task-index.mjs` 与 `node tools/web/generate-task-card.mjs --task <id>` 生成索引/任务卡;执行用 `node tools/web/run-wbv2-module-task.mjs --task <id> --write`。
- 源码单元绑定:模块完成时更新 `dynamic-inventory.json` 的 coverage、`blender-feature-route-catalog.json` 的 route/evidenceStatus,并在 module manifest 记录 `unitCount`、`verifiedUnits`、`sourceTreeSha256` 与本模块新增绑定。
## 关键风险与对策
1. **通用通道的上下文依赖**:`desktop_operator` 需要正确的 area/mode/selection;Web UI 无原生 area 时需映射(`areaType`),失败返回稳定错误码而不是静默降级。
2. **模态操作所有权**:原生 modal 期间 Web 输入必须让位;复用 `NATIVE_INVOKE_MODAL_OWNER_ACTIVE` 与 cancel/recovery 语义。
3. **选择系统割裂**:Web 本地选择与原生选择必须统一为 Main 权威,否则 Extrude/Knife 等操作对象错误;`MESH_SELECTION_AUTHORITY` 必须先于大规模操作符接线。
4. **修改器白名单**:`MOD_util.cc` WITH_WEB 与 98 个源之间存在差距;按 family 逐个打开并绑定负例,不一次性放开。
5. **Collection per-ViewLayer 语义**:桌面眼/渲染开关属于 LayerCollection,不是全局 Collection flag;需先扩展数据模型与 reader。
6. **场景切换与 Main 生命周期**:切换场景会影响 depsgraph/undo/Worker;复用现有 Main reset/reload 语义并加负例。
7. **linked 只读**:linked 集合/对象必须继续 fail closed,不允许通过通用通道绕过。
8. **UI 规模**:菜单/快捷键全量对齐容易与现有 Web 快捷操作冲突;建立单一 keymap 契约(`web/protocol/keyboard-contract.ts`)并逐项迁移。
9. **OCCT 约束**:三域移植以 Blender 原生 BMesh/BKE 为准;仓库内未检索到 OCCT 适配,若后续新增非 Blender 几何算法,按 AGENTS.md 统一到 OCCT WASM,不用 Three.js 计算。
## 验证与验收(全部功能实现后)
- 每个 module:正例 Main mutation、负例 no-mutation、undo/redo、save/reopen、Worker restart、Chromium 主线程与 OffscreenCanvas 视口。
- 三域汇总:inventory 映射 unit 全部有证据;module manifest 与 `docs/status/` 一致;未验证项为 0 才可声明该域对标完成。
- 用户验证:在 Chromium 中按桌面 Blender 操作路径实测创建、建模、层级三类工作流。
- 不以编译成功、单例通过、协议存在或覆盖率统计替代功能完成。
## 第一批可执行任务(建议)
1. `OPERATOR_CHANNEL_FOUNDATION`:通道加固 + 一个非网格对象的创建正例(Proof)。
2. `OBJECT_CREATE_MESH_PRIMITIVES`:补齐 Circle/Grid/Monkey 与基础体参数,Add 菜单去 `localOnly` 限制。
3. `OBJECT_CREATE_CURVE_SURFACE`:Bezier/NURBS/Circle/Text 3D 对象 + schema 属性。
4. `SCENE_HIERARCHY`:集合嵌套拖拽、删除、active collection、排序。
5. `MESH_SELECTION_AUTHORITY`:Web 选择写回原生,为后续 164 个网格操作符铺路。

View File

@@ -0,0 +1,57 @@
# Web Blender 三域用户验证清单
更新时间:2026-11-10
范围:创建模型 / 模型化 / 模型层级
验证环境:Chromium,本地 WASM 引擎(`localOnly`)
状态:全部条目 **NOT_CLAIMED**,仅作为用户验证路径清单,不是验收证据。
## 1. 创建模型
在 Object 模式打开“添加”菜单:
- 网格:立方体、平面、UV 球、棱角球、圆柱、锥体、环体、圆环(Circle)、栅格(Grid)、猴头(Monkey)。
- 曲线:贝塞尔、圆形、NURBS 曲线、NURBS 圆、路径。
- 曲面:6 种 NURBS Surface。
- 文本、骨架、空物体(普通轴/箭头/单箭头/圆/立方体/球/锥体)、相机、灯光(点/面/日光/聚光)。
- 非网格:融球(5 类型)、体积、晶格、蜡笔(空白/笔画/猴头/场景线稿/集合线稿)、点云、毛发曲线、随机毛发、扬声器、光照探针(3 类型)、力场(13 类型)。
- “更多对象…”打开 F3,可搜索通用 `object.add` / `add_named`。
- 编辑模式下“添加”子菜单可向当前网格加入基础体。
每条路径检查:对象出现在 Outliner/视口、撤销/重做、保存后重开仍存在。
## 2. 模型层级
- Outliner:对象行拖到对象行(Shift)设父级;拖到集合行移入集合(Ctrl 链接);集合行拖到集合行嵌套。
- Collection Properties(Properties 面板):
- 重命名、删除集合、删除层级、在本集合下新建子集合、设为活动集合、集合实例、上移/下移。
- 每视图层的排除/Holdout/仅间接/隐藏开关(作用在当前活动 View Layer)。
- 顶栏场景选择器切换场景;场景菜单新建/复制设置/删除场景、添加/删除视图层。
- 物体菜单:父级(原生物体/骨架/骨骼/骨骼相对/曲线)、清除父级(保持变换/清除逆矩阵)。
- 检查:层级在保存/重开后保持;循环父子或删除 master 应被拒绝。
## 3. 模型化
- 选择:视口选择模式(点/边/面);选择工具(全选/取消/反选/相连/相似/更多/更少/松散/非流形/随机/内部面/未分组/按轴/间隔/按边数)。
- 网格菜单:挤出/内插/倒角/细分/循环切割/合并/溶解/删除;扩展工具(Poke、Grid Fill、对称化、按距离合并、取消细分、着色、翻转法线、拆离、缝合线/锐边);更多网格(平滑、圆形化、凸包、清理、法线、UV 数据、顶点连接、线框等)。
- 模态工具:切割(K)、循环切割、倒角、内插面、挤出区域、投影切割(需要原生画布)。
- UV:展开(角度/共形/最小拉伸)、智能投影、打包/平均岛缩放、接缝转岛、减小拉伸。
- 修改器面板:添加/删除/上下移动/参数编辑;Apply、存为形态键、复制、复制到选中。
- 新增支持的修改器类型:Weighted Normal、Corrective Smooth、Laplacian Smooth、Mask、Normal Edit、Skin、UV Project、UV Warp、Warp、Data Transfer、WeightVG Edit/Mix/Proximity、Laplacian Deform、Particle Instance、Surface、Mesh Cache、Mesh Sequence Cache、Multires、Explode。
- 物体菜单:着色(平滑/平直/自动/按角度)、转换(网格/曲线/曲面/文本/融球/点云/体积/蜡笔)、设置原点、应用变换、清除变换、数据关联、形态键、顶点组、变换轴心/方向/吸附设置。
每条路径检查:Main 对象/网格发生变化、撤销/重做、保存重开一致;不支持的操作应给出结构化错误且不修改 Main。
## 4. 曲线编辑
- 进入曲线对象的编辑模式,选择控制点(Web 选择会写入原生 Curve 编辑数据)。
- “曲线”菜单:挤出/复制/删除/切换循环/手柄类型/样条类型/分离/添加顶点/细分/平滑/反向/重算法线/半径/倾斜/权重/着色/隐藏/显示/选择工具。
- 模态工具:挤出移动、复制移动、最短路径、绘制、钢笔、选择相连(拾取)。
- 检查:控制点选择与操作一致;撤销/重做、保存重开。
## 5. 已知未验证/限制
- 所有本轮实现均未运行负例、undo/redo、save/reopen、Worker restart 或 Chromium 自动化。
- 模态工具依赖原生画布与输入所有权;在 Web 视口点击会被原生画布接管。
- 修改器白名单新增类型的运行时求值未验证;Ocean/OpenVDB 依赖类型未编译。
- 选择读回依赖嵌入 Python 的 BMesh 查询;复杂模态操作后如高亮与实际选择不一致,请记录操作路径。
- route catalog 仍将大部分操作符标为 `SERVER_NATIVE`;本轮未做 `WEB_MAIN` 重新分类或 inventory 单元绑定。

View File

@@ -0,0 +1,75 @@
# 机械零件套装互动教学(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)不在本教学案例范围内。

View File

@@ -0,0 +1,81 @@
# 二级减速器互动教学(REDUCER_TUTORIAL)
## 用户入口
- `/?tutorial=reducer`:React 图文落地页(结构、零件卡、课堂实录、课程树、开始/继续/重置)。
- `/?tutorial=reducer&start=1`:真实 Web Blender(本地 WASM Main)+ 引导侧栏。
- 帮助 → 二级减速器教学:在新标签页打开落地页。
## 已实现
- 35 步课程(引导 + 33 个必需步骤 + 完成总结),覆盖:
- 箱体/箱盖:立方体、应用缩放、删面 + Solidify、Mirror + Boolean UNION 箱脚、
圆柱孔刀 + Join + Boolean DIFFERENCE 轴承孔、Inset/挤出止口、Mirror 螺栓孔、Bevel。
- 传动件:三根轴(圆柱 + 轴颈 + Join + 键槽布尔)、四个齿轮
(圆柱轮坯 + 齿槽刀 + Empty 对象偏移 Array + Boolean 切齿 + 轴孔/键槽布尔 + Bevel)、
Torus 套圈 + 滚球轴承。
- 标准件与附件:端盖、平键、六角法兰螺栓、通气帽、油标、放油塞、文字转网格铭牌(Subsurf 底板)。
- 装配与交付:集合层级、轴组件 Empty + 父子、根 Empty 总装、四种材质与槽位、Linked Duplicate、
保存 `.blend`(OPFS + 下载)与 GLB 导出。
- **课程工程身份**:reducer 首次进入时通过 `ensureCourseProject("tutorial-reducer", "二级减速器.blend")`
把默认场景写入 OPFS 课程工程(复用 `persistProject`,不触发下载),
检查点写 `projects/tutorial-reducer/snapshots`,与 parts 课程的快照链隔离。
- **React TreeView**:新增 `tutorial/TutorialTreeView.tsx`(role=tree/treeitem、方向键/Home/End、
展开/折叠、状态标记),用于:
- 落地页课程结构(章节 → 步骤);
- 装配章节的“实时装配树”(按 SceneIR 的集合/父子关系生成)。
- **课程化改造**:新增 `tutorial/course.ts`,把课程 id、步骤、进度文件名、资源目录、
保存/总结步骤、文案与零件卡参数化;parts 组件改为薄包装,parts 行为不变。
- **进度与恢复**:OPFS `tutorial/reducer-progress.json` 为权威记录,localStorage 镜像;
恢复前先读 OPFS 进度,恢复失败显示提示;首次推进前串行完成“恢复或确保课程工程”。
## 本批修复的真实缺口
1. 课程工程身份与就绪竞态:引导页的“下一步”可能在 `ensureCourseProject` 启动前被点击,
从而把错误的默认工程 id 写进进度并排队检查点。现在侧栏把 `projectState`(idle/preparing/ready/failed)
纳入 `ready` 门控,串行完成“恢复或确保课程工程”后才允许演示与推进;失败时显示提示并允许继续练习,但不写错误 id。
2. 箱体轴承孔校验:箱体在箱脚步骤已有一个 Boolean(Union),按类型取第一个 Boolean 会命中箱脚而不是轴承孔。
校验现在按操作数“箱体孔刀”匹配 Boolean 修改器。
3. 轴与轴承的局部坐标:输入轴与轴承的旋转未应用时,SceneIR 网格包围盒沿局部 Z 轴,
长度/外径校验误判。演示现在在 Join 前应用旋转(与桌面建模习惯一致)。
4. 编号对象计数:`findNodes` 的名称后缀规则只接受 `.001` 形式,无法匹配“轴承1…6/端盖1…6/键1…4/法兰螺栓1…9”。
计数校验改为按 `^轴承\d+$` 等编号名称匹配。
5. 齿轮齿槽阵列与布尔:Boolean 操作数上的**活动 Array 修改器**在 Web Main 的实时视口中只切出一条齿槽
(17 份阵列未被布尔求值)。已做对照:即使齿槽刀保持可见,活动 Array 仍只切一条,可见性不是原因;
在布尔前 `bpy.context.view_layer.update()` 后应用齿槽阵列则得到正确齿形。
课程因此改为“阵列 → 应用 → Boolean”;齿轮1 的 `-view` 截图显示 17 条齿槽,齿轮2 显示 34 齿结果。
这仍是引擎的实时操作数求值缺口(已记录在“已知限制”),应用阵列是桌面同样可用的工作流。
6. 重场景的保存竞争:减速器场景较大,通用 autosave 与教程检查点同时序列化会长时间占用项目锁,
导致“下一步”在完成检查后仍被禁用。现在课程激活时停用通用 autosave,教程检查点串行化(只允许一个在跑),
重课程每 3 次推进写一次检查点(零件课程保持每步);“下一步”不再等待后台保存。
代价是最多可能丢失 2 步的场景快照,进度步骤仍每步写入。
7. 材质创建校验:未分配到任何对象的材质不会出现在 SceneIR,四种新材质的颜色校验全部失败。
课程在创建步先给箱体/输入轴/轴承1/箱盖各分配一个代表材质,下一步再分配其余零件。
8. 实时装配树可读性:SceneIR 中的隐藏辅助对象(Mirror/Array 控制 Empty、隐藏切刀)会淹没装配层级。
React 树现在过滤 `visible=false` 的对象与空集合,仅显示可见装配层级。
## 验证状态
- 有头 Chromium 全流程(2026-11-11,`capture-parts-tutorial-screenshots.mjs --course reducer --dev --port 5218`):
35 步全部通过,`status: PASS`,`errors: []`;保存与 GLB 导出检查均为绿色
(GLB 2,674,332 字节,31 条相机/灯光等警告);齿轮1 视口参考图确认 17 条齿槽。
截图:`web/app/public/tutorial/reducer/landing.jpg`、`steps/<id>.jpg`、`steps/<id>-view.jpg`、`00-assembly.jpg`
(35 步共 70 张步骤图 + 总览;落地页已在全部步骤截图生成后重新截取)。
- `npm run typecheck`:exit 0;`npm run build`:exit 0(含 `PartsTutorialLanding-*.js` 课程 chunk)。
- 生产构建上的 4 个 spec 全部通过(2026-11-11,`playwright.deployment-local.config.ts`,workers=1,48.1s):
- `wbv2-reducer-tutorial-production.spec.ts`(19.9s):落地页/课程树 → 开始 → `tutorial-reducer` OPFS 工程 →
箱体演示 → 齿轮毛坯/阵列/布尔 → 刷新继续 → OPFS 恢复(Active 齿轮1)→ 保存 `.blend` + GLB 下载与 magic →
`projects/<savedProjectId>/scene.blend`。
- `reducer-tutorial-accessibility.spec.ts`(4.5s):落地页与课程树 axe、装配步骤的实时 React 树 axe。
- `wbv2-parts-tutorial-production.spec.ts`(18.6s)与 `parts-tutorial-accessibility.spec.ts`(4.4s):零件课程回归。
- 无障碍修复:课程目录序号颜色 #7d8287→#a6abb0(对比度 4.45→通过 4.5),装配树元信息同样改为 #a6abb0。
## 已知限制
- Boolean 的**活动** Array 操作数在 Web Main 实时视口中只求值一份;课程在布尔前应用阵列。
待补:按桌面源码检查 Boolean 操作数的求值依赖(`MOD_boolean.cc` 的 `DEG_OB_COMP_GEOMETRY`
与 Array 的 `offset_ob` 关系),并加回归用例。
- 齿轮为教学简化齿形(直齿、无渐开线修形),用于讲解阵列+布尔流程,不用于强度/噪声分析。
- 轴承为简化模型(内外套圈 + 8 个滚球),未建模保持架与游隙。
- 端盖为简化圆盘+凸台,未做密封槽与标准螺栓孔阵列。
- 油标/放油塞/通气帽为示意模型;法兰螺栓数量与位置为教学布置。

View File

@@ -0,0 +1,7 @@
# WBV2-P1424-900 status
- task: `WBV2-P1424-900`; parent: `WBV2-P1424-001`; status: `done`
- operation: `SUPERSEDE_STALLED_TASK_FOR_DOMAIN_PARITY`
- preserved: `WBV2-P1424-001` manifest `212423` bytes, status `in_progress`, continuance `tests/golden/WBV2-P1424-001/continuation-20261103-goal-numpy.json`
- successor: `WBV2-P1425-000`; plan: `tests/golden/WBV2-P1425-000/domain-parity-plan.json`
- nextTask: `WBV2-P1425-000`

View File

@@ -0,0 +1,7 @@
# WBV2-P1425-000 status
- task: `WBV2-P1425-000`; parent: `WBV2-P1424-900`; status: `done`
- operation: `AUTHORIZE_DOMAIN_PARITY_EPOCH`; module: `OBJECT_ADD_ROUTE`
- preserved: `WBV2-P1424-001` manifest remains `in_progress` (`212423` bytes); continuation `tests/golden/WBV2-P1424-001/continuation-20261103-goal-numpy.json`; acceptance `NOT_CLAIMED`
- new plan: `tests/golden/WBV2-P1425-000/domain-parity-plan.json`; bound source units: `15`
- nextTask: `WBV2-P1425-001`

View File

@@ -0,0 +1,88 @@
# WBV2-P1425-001 status
- task: `WBV2-P1425-001`; module: `OBJECT_ADD_ROUTE`; status: `in_progress`
- creation
- Add menu uses `web_blender.desktop_operator(native=true)` with revision/scene/area/mode/selection context.
- Mesh primitives incl. Circle/Grid/Monkey; NURBS curves/surfaces; non-mesh types and enum submenus; Empty display types; Grease Pencil line-art types; random hair; Edit-mode Add; generic F3; quick-toolbar primitives.
- hierarchy
- Commands/UI: `moveCollection` (including sibling ordering), `deleteCollection` (normal/recursive), `createCollection` child, `setActiveCollection`, `setScene`, `setSceneCollectionSettings`.
- Outliner collection drag/drop; Collection Properties delete/child/active/instance/up/down; per-ViewLayer exclude/holdout/indirectOnly/hide; topbar scene selector and scene delete; view layer add/remove; parent types and clear modes.
- modeling
- Mesh element selection bridge and post-operator readback; curve control-point selection bridge.
- Broad mesh/curve/UV operator coverage; selection-aware modal tools; UV projection/select/stitch/pack; vertex groups; shape keys; modifier Apply/Shape Key/Duplicate/Copy; object shading/convert/origin/transform/data/pivot/snap.
- Modifier whitelist expanded by 20 source-backed types: WeightedNormal, CorrectiveSmooth, LaplacianSmooth, Mask, NormalEdit, Skin, UVProject, UVWarp, Warp, DataTransfer, WeightVGEdit, WeightVGMix, WeightVGProximity, LaplacianDeform, ParticleInstance, Surface, MeshCache, MeshSequenceCache, Multires, Explode.
- implementation coverage: 486 operator units in five families have an implementation route (317 menu/modal, 26 typed command, 143 F3 operator palette). `MESH_OT_` 164/164, `OBJECT_OT_` 215/215, `CURVE_OT_` 51/51, `SURFACE_OT_` 6/6, `UV_OT_` 50/50 classified `WEB_MAIN` in the route catalog; behavioral evidence remains separate and `UNVERIFIED` for the broader catalog.
- artifacts: `implemented-routes.json`, `implemented-unit-bindings.json`, `implementation-summary.json`, `docs/plans/WEB_BLENDER_THREE_DOMAIN_VALIDATION_CHECKLIST.md`.
- build: WASM rebuilt with the project environment and absolute paths; runtime synced (wasm SHA-256 `8ec2268f73584215419b72292cda0bc8fb055a9e9044224722077bfe3f43987d`). Command registry check PASS (170 commands, 161 WEB_MAIN routes). Frontend incremental build passed with `WBV2_SKIP_TYPECHECK=1`.
- blockers fixed in this round:
- `editors/mesh` registers `mesh.convex_hull` under `WITH_BULLET`, but the Web `bf_bmesh` target omitted Bullet and therefore did not register the `convex_hull` BMO. Executing it ran a null operator type and crashed WASM (`table index is out of bounds`); SKIN used the same path. The WITH_WEB branch of `bmesh/CMakeLists.txt` now links `bf_intern_rigidbody` and `bf::dependencies::optional::bullet`.
- Speaker `distance_max` defaults to `FLT_MAX` in DNA while SceneIR budgets it to 1e9; the blend reader now clamps distance fields, and empty `actionSlotIdentifier` values (sound strips created by Speaker Add) are omitted instead of failing the schema.
- Add → 数据实例 now resolves the active object's data-block name and ID type from SceneIR before invoking `object.data_instance_add` (invoking it operand-less is correctly cancelled).
- Embedded the Essentials geometry-node bundles (`geometry_nodes_essentials`, `geometry_nodes_dynamics_assets`, `procedural_hair_node_assets` + catalog) under `/blender/datafiles/assets`. The browser asset index does not list packaged-FS files, so `add_essential_asset_modifier` now falls back to appending the requested NodeTree directly from the embedded .blend; hair curves Add completes with the Capture Rest Geometry and Hair Dynamics modifiers.
- Modifier parameter changes only wrote DNA and left the embedded Python depsgraph’s evaluated copy stale, so `object.modifier_apply` baked the previous level. Updated parameter publication now uses the concrete modifier RNA struct, full recalc flags (`GEOMETRY | SYNC_TO_EVAL | PARAMETERS`) and a notifier, re-tags after the SceneIR refresh, and the operator bootstrap re-syncs modifier RNA once before native execution. SUBSURF levels 2 evaluates 98 vertices, Apply bakes 98, and save/reopen keeps 98.
- Saving while an object was in Edit mode omitted the edit data (e.g. Armature edit-bones). The save export now calls `ED_editors_flush_edits` before serialization, matching desktop save; added/subdivided/duplicated bones and hair-curves modifiers persist across save/reopen.
- SceneIR published every Object in Main, so a newly created empty scene still listed the other scene’s objects and native operators failed with `OPERATOR_SELECTION_CONTEXT_UNAVAILABLE`. The reader now emits nodes only for objects reachable from the active scene’s master collection, and `setScene` also points every wmWindow at the selected scene. `scene.new` → switch → `scene.delete` now publishes 0 nodes for the empty scene, 3 for the original and returns to `Scene` after deletion.
- Library append read the source from memory but left a Library entry pointing at a browser locator; snapshot/save packing could not find that path and failed with `LIBRARY_DEPENDENCY_UNAVAILABLE`. The append path now retains the uploaded source bytes in `recovery_library_bytes` keyed by the missing-library path, so append, undo/redo, save and reopen work.
- Collection instances published only an EMPTY owner. The reader now emits `instanceCollectionId`, `SceneNodeIR` validates it, and the Three.js viewport recursively expands nested collection instances with composed matrices, respecting collection/member visibility and depth/cycle limits. Display copies share member geometry/materials; the viewport also draws camera frustums and empty axes, so instanced cameras and empties are visible. Picking reports the nearest instance owner, so nested instance contents select their own instance object. Save/reopen keeps the reference.
- `uv.select_overlap` now works on Web: the WASM call trapped with `'unreachable'` while constructing `blender::Array<ChangedInfo>` from its bitfield initializer. The local `ChangedInfo` array now uses `std::vector`, behavior-equivalent; the operator finishes and the 60-case matrix has no open operator failure.
- `object.shade_auto_smooth` now works on Web: the embedded Essentials bundle is not indexed by the asset system, so the operator cancelled with “No asset found … Smooth by Angle”. With `WITH_WEB` it fetches the bundle, suppresses the false asset report, direct-appends the NodeTree with `WM_file_append_datablock`, and validates it as the Smooth by Angle group.
- The full `mathutils` Python surface is available again on Web: the `MATH_WEB_CORE_ONLY` compile definition was removed from `bf_python_mathutils`, so `mathutils.geometry/interpolate/noise/bvhtree/kdtree` are registered. `uv.lightmap_pack` (`mathutils.geometry.box_pack_2d`) now finishes instead of raising `AttributeError`.
- Native canvas replacement no longer breaks GPU resources. The Web/native toggle destroys the old WebGL context, but a retained vert/index buffer from that context made the next modal frame throw “Blender WebGL2 drawing resource belongs to another canvas”. `WebGLObjects::require_owner` now drops the dead GL names and lets `bind()` re-create and re-upload from the retained CPU bytes, so the next canvas draws normally.
- Added a Web “调整上一步操作” panel for operators invoked through the native channel. It records the operator, schema and created objects; applying edits deletes the created objects and re-runs the operator with converted RNA values from the original context. Non-creating operators fall back to undo + re-invoke guarded by the history cursor. The panel’s “完整属性…” action opens the existing F3 operator form preselected with the recorded operator and values, so pointer, collection, nested-group, enum-set and matrix properties are editable in the same adjustment flow.
- operator sweep: `web/tests/e2e/wbv2-operator-sweep-production.spec.ts` invokes every registered operator in the native `mesh`/`object`/`uv`/`curve` families (167/250/55/51 identifiers extracted from the desktop registry `bpy.ops.*`) through `desktop_operator` with a fresh Main per case, classifies EXECUTED / CONTEXT_POLL / CONTEXT_CANCEL / CONTEXT_MESSAGE / ERROR / TIMEOUT / CRASH and writes `verification/operator-sweep-<family>.json` bound to the WASM hash. The mesh family sweep is running; results and any real failures will be recorded per family.
- verification PASS (Chromium, focused slices):
- `web/tests/e2e/wbv2-add-menu-native-production.spec.ts`: Add menu → native `mesh.primitive_monkey_add` → Main/SceneIR → Outliner shows Suzanne/Monkey.
- `web/tests/e2e/wbv2-three-domain-focused-production.spec.ts` creation: base 1 → create 2 → undo 3 → redo 4; saved 421,265 bytes; reopen revision 1 with `VerifyCube` present.
- hierarchy: create/move/active/delete revisions 3-6, undo 7, redo 8; saved 417,882 bytes; reopen revision 1 with `VerifyParent`.
- modeling: meshTriangulate 2, undo 3, redo 4; SUBSURF add 5, remove 6; saved 417,327 bytes; reopen revision 1.
- `web/tests/e2e/wbv2-mesh-native-operator-ui-production.spec.ts`: Web mode switch → Edit mode → meshSelection bridge → native `mesh.poke`; status `已执行:mesh.poke`.
- `web/tests/e2e/wbv2-modifier-matrix-production.spec.ts`: all 20 expanded modifier types add/evaluate/remove/save/reopen, every case `FINISHED` (including SKIN after the Bullet linkage fix).
- `web/tests/e2e/wbv2-convex-hull-native-production.spec.ts`: Edit mode → `mesh.select_all` → native `mesh.convex_hull` state `DONE`.
- `web/tests/e2e/wbv2-native-operator-matrix-production.spec.ts`: 60/60 mesh/UV/object operators `FINISHED` on WASM `8ec2268f`, including shade_auto_smooth, uv.lightmap_pack and uv.select_overlap after the asset, mathutils and overlap-container fixes. The receipt records the WASM SHA-256. UV-editor-only/cursor/active-face operators and no-op selection clears are documented as inapplicable to the detached Layout/VIEW_3D fixture, not counted. Earlier test-only failures were corrected against the native RNA registry (`mesh.vertices_smooth`, `object.hide_view_set` without `clear`, `mesh.unsubdivide` without `threshold`, etc.).
- `web/tests/e2e/wbv2-creation-object-types-production.spec.ts`: 29 creation/data cases on WASM `8ec2268f` — all six `SURFACE_OT_` primitives (surface, sphere, circle, curve, cylinder, torus), five curve primitives (Bezier, NURBS curve, NURBS path, Bezier circle, NURBS circle) and the mesh/text/armature/empty/camera/light/metaball/volume/lattice/GP/point cloud/hair curves/speaker/light probe/effector types each produced a SceneIR node; operand-less `object.data_instance_add` remains cancelled with no mutation. Receipt records the WASM SHA-256.
- `web/tests/e2e/wbv2-data-instance-add-production.spec.ts`: Outliner selection → Add → 数据实例 creates `Cube.001` through the native ID lookup.
- `web/tests/e2e/wbv2-hair-curves-add-production.spec.ts`: hair curves Add appends both embedded Essentials node groups; surface gains `Capture Rest Geometry`, Curves object gains `Hair Dynamics` and is parented to `BasicCube`.
- `web/tests/e2e/wbv2-modifier-parameters-apply-production.spec.ts`: SUBSURF levels 2 evaluates 98 vertices through `evaluateDepsgraph`, undo restores level 1 / 26 vertices, redo restores level 2 / 98, native `object.modifier_apply` bakes 98 and removes the modifier, save/reopen keeps 98.
- `web/tests/e2e/wbv2-modifier-apply-convert-production.spec.ts`: BEVEL width 0.1/segments 2 evaluates 56 vertices and `object.modifier_apply` bakes 56; SUBSURF level 2 followed by `object.convert(target=MESH)` bakes 98 and clears the stack.
- `web/tests/e2e/wbv2-curve-edit-operators-production.spec.ts`: Bezier create → Edit → 32 curve operators all `FINISHED` on WASM `1e6e7d3f` (added select more/less/next/previous/random/similar/linked, de_select_first/last, smooth radius/tilt/weight, spline_weight_set, shade smooth/flat, dissolve_verts, split, separate, hide, normals_make_consistent) and the receipt records the WASM SHA-256. `curve.select_nth`/`curve.select_row` are desktop-inapplicable to a default Bezier circle (same native errors: no active point / poll context incorrect) and are documented instead of counted. Fixed the menu enum value `AUTO` → `AUTOMATIC`.
- `web/tests/e2e/wbv2-convert-to-mesh-production.spec.ts`: Bezier (13 verts), NURBS surface (256 verts / 225 faces) and default Text (177 verts / 171 faces) all convert to `MESH` through `object.convert`.
- `web/tests/e2e/wbv2-armature-edit-production.spec.ts`: Armature create → Edit → primitive add (1→2 bones), subdivide (1→2), duplicate (1→2), calculate roll (count unchanged) all `FINISHED`; save while in Edit mode and reopen preserves the new bone count.
- `web/tests/e2e/wbv2-object-tools-production.spec.ts`: two shape keys, one vertex group, modifier copy (two SUBSURF entries) and SIMPLE_DEFORM apply-as-shape-key (2 keys, modifier kept).
- `web/tests/e2e/wbv2-hair-curves-add-production.spec.ts` now also saves and reopens: the Curves object keeps `Hair Dynamics`, its parent and the surface’s `Capture Rest Geometry`.
- `web/tests/e2e/wbv2-edit-mode-persistence-production.spec.ts`: `mesh.poke` in Edit mode grows the cube from 8 to 14 vertices, and save/reopen preserves 14.
- `web/tests/e2e/wbv2-scene-flow-production.spec.ts`: `scene.new` publishes 0 nodes for the empty scene, switching back publishes 3, switching to the empty scene publishes 0, and `scene.delete` returns to `Scene` with 3 nodes.
- `web/tests/e2e/wbv2-collection-instance-production.spec.ts`: the instance EMPTY publishes `instanceCollectionId: collection:Collection`; the Web `ViewportRenderer` creates 3 member display copies (mesh + light + camera frustum) for the 3-object collection and 1 base-scene helper; save/reopen keeps the reference.
- `web/tests/e2e/wbv2-nested-collection-instance-production.spec.ts`: a collection instance inside another collection instance expands recursively; the nested BasicCube clone reports `object:NestedInner` as its picking owner (not the outer `object:Collection`), and the nested instance contents render with composed matrices.
- `web/tests/e2e/wbv2-adjust-last-operation-production.spec.ts`: Add Circle → Web adjust panel → vertices 8 → apply replaces the original with one 8-vertex Circle; a second adjustment sets location X=3; “完整属性…” opens the preselected F3 form, sets vertices 12, and the final scene has one 12-vertex Circle at [3, 0, 0].
- `web/tests/e2e/modifier-stack-ui-production.spec.ts` (existing scenario suite, now unlocked): 8 cases — add/remove/move Bevel and parameters for Bevel/Array/Mirror/Solidify/Surf — pass with sequential single-writer clients, invalid-command rejection, undo/redo, save/reopen and replay; native Blender readback confirms both the modified stack and untouched objects.
- Unlocked existing production scenarios (17 tests): `rig-pose-authoring-native` bones/pose with native golden readback and independent replay; `mesh-main-reader-production`; `light-subtypes-reader-production`; `nonmesh-main-reader-production`; `native-main-lifecycle`; `nla-evaluation-golden`; `library-append-main` (append/undo/redo/save/reopen against the desktop canonical report); `rna-datablock-reflection-native` (native datablock reflection survives reopen; zero-user orphan IDs are excluded because desktop save prunes them).
- Combined verification: 22 focused three-domain tests + 17 unlocked existing scenarios, all passing with bounded parallelism on the current WASM.
- Re-run on WASM `8ec2268f`: 22 focused production scenarios pass — three-domain creation/hierarchy/modeling (3), creation/operator slices (10), hierarchy/persistence/collection/adjust (7), native operator matrix (60/60 operators), 32 curve operators, the native modal scenario and the `?ui=web` object-editor scenario. Receipts were regenerated against this hash.
- `web/tests/e2e/object-hierarchy-production.spec.ts` now passes against the production build served by `tools/web/deployment-http-server.mjs` with `?ui=web` (`web/playwright.deployment-local.config.ts`): parent/unparent UI, collection lifecycle, negatives, undo/redo, save/reopen and native golden readback. `web/dist` was rebuilt from the current sources/WASM before the run.
- `web/tests/e2e/modeling-native-modal-production.spec.ts` now passes on the current WASM: translate cancel restores Main and history, translate/rotate/scale modals, Edit-mode projected-vertex click plus modal transform, undo/redo, save/reopen and Properties readback — across the Web-tools/native-canvas toggles before and after save. The scenario takes ~56s with SwiftShader, so the spec now sets its own 300s budget instead of the 30s default. Receipt: `verification/native-modal-production.json` plus its `native-modal-artifacts/`.
- `web/tests/e2e/object-editor-ui-production.spec.ts` now passes on `?ui=web` against `web/dist`: clear location/rotation/scale, create CAMERA/LIGHT/EMPTY/ARMATURE, selection, collection viewport/render toggles, undo/redo, negative no-mutation, save/reopen and an independent determinism run; the native desktop readback agrees on every checkpoint. The previous `selected` mismatch was not a save-path difference: with the native build, `Collection.hide_viewport = True` deselects the object and showing the collection again keeps it deselected, so the Web-saved `selected=false` matches desktop. The test checkpoint now records the Outliner activation state that `Select Active` is undone to, and the reopen step no longer re-clicks the Outliner row (which had masked the selection stored in the file).
- receipts: `tests/golden/WBV2-P1425-001/verification/{add-menu-native-production,create-production,hierarchy-production,modeling-production,mesh-native-operator-ui-production,modifier-matrix-production,convex-hull-native-production,native-operator-matrix-production,creation-object-types-production,data-instance-add-production,hair-curves-add-production,modifier-parameters-apply-production,modifier-apply-convert-production,curve-edit-operators-production,convert-to-mesh-production,armature-edit-production,object-tools-production,edit-mode-persistence-production,scene-flow-production,collection-instance-production,nested-collection-instance-production,adjust-last-operation-production,object-editor-positive,object-editor-negative,object-editor-undo_redo,object-editor-save_reopen,object-editor-determinism,native-modal-production}.json`.
- retained verification blockers (not claimed as passed):
- Scenario-style specs that create several `WebEngineClient`s must terminate each client before opening the next; the unlocked suites now do this on `?engine=manual` and pass. Specs that still drive the app page remain subject to the single-writer contract.
- resolved in this round: `object-editor-ui-production.spec.ts` passes on `?ui=web` with the deployment server and native readback. Desktop Blender deselects objects when their Collection is hidden and does not restore the selection when it is shown, so the Web-saved `selected=false` is correct; the stale expectation and two harness assumptions (undo baseline, post-open re-selection) were corrected.
- resolved in this round: `modeling-native-modal-production.spec.ts` passes after `WebGLObjects` re-homes retained CPU bytes onto the replacement canvas; no GPU canvas detach remains.
- resolved in this round: Web-only Edit-mode switch and `mesh.poke` pass after passing `selectedObjects`/`activeObject`/`baseRevision`, detaching the GPU surface on the Web-tools toggle, and importing `bpy` inside the selection helpers; verified by `wbv2-mesh-native-operator-ui-production.spec.ts`.
- remaining: broader per-operator behavioral evidence for the 486 routes; Ocean/OpenVDB modifiers cannot be enabled because the Web build has `WITH_FFTW3=OFF` and `WITH_OPENVDB=OFF`, and no WASM FFTW/OpenVDB libraries or sources are available locally. Full desktop parity is not claimed.
- 2026-11-10 operator-family sweep and Web fixes (WASM `33db5f5e`):
- `web/tests/e2e/wbv2-operator-sweep-production.spec.ts` invokes every live registered operator in the mesh/object/uv/curve families (167/250/55/51) through `desktop_operator` with a fresh Main per case; each family receipt in `verification/operator-sweep-<family>.json` records the per-operator outcome plus the WASM SHA-256. Final counts: mesh 136 EXECUTED, object 104, uv 32, curve 43 (315/523). Remaining outcomes are MODAL (PASS_THROUGH), CONTEXT_POLL/CANCEL/MESSAGE matching a native poll/report for the detached Layout/VIEW_3D fixture, or ADDON_MISSING for `uv.export_layout` (the bundled `io_mesh_uv_layout` addon is not loaded). Zero CRASH, zero TIMEOUT, zero core OPERATOR_MISSING; the classifier separates native Blender RuntimeError reports from WASM traps.
- Freestyle mesh-data operators now exist on Web. `WITH_FREESTYLE=OFF` compiled out `mesh.mark_freestyle_edge/face` although the Web menu exposed them, so clicking always failed with `get_rna_type(...) not found`. The registration and the two exec functions are now guarded by `WITH_FREESTYLE || WITH_WEB` in `mesh_ops.cc`, `editmesh_tools.cc` and `mesh_intern.hh`; they only write the `freestyle_edge`/`freestyle_face` boolean attributes and need no Freestyle renderer. Both FINISHED in the 62-case matrix.
- `object.quick_explode` now works. The EXPLODE modifier's dynamic `vertex_group` string has no fixed RNA buffer, so the accessor generator emitted `maxLength=-1` and the editor-workflow validator rejected the whole snapshot (`EDITOR_LAYOUT_INVALID: Invalid modifier string resource metadata`). `extract_modifier_accessors.py` now clamps a missing declared maxlength to 63; Quick Explode FINISHED with ParticleSystem + Explode modifiers on the object.
- Geometry Nodes modifier inputs are editable from Python again. `node_socket.cc` wrapped all 19 `make_geometry_nodes_input_srna` registrations in `#ifndef WITH_WEB`, so every NodesModifier input item exposed only `rna_type`/`name` (`object.quick_fur` failed at `inputs.Input_12.value`). Removing those guards restores `value`/`type`/`attribute_name`/`layer_name` on boolean, float, object, material, geometry, menu and collection inputs; verified with a temporary Chromium probe (Input_12/15/20/22 expose `value`), probe removed.
- Menu wiring: `mesh.spin`/`mesh.screw` now pass `axis=[0,0,1]` (EXEC without a view axis reported `Invalid/unset axis`), `mesh.rip` routes through `queueLocalDesktopOperator` so the native invoke runs, and the curve menu's `curve.spin` was removed because its poll (`ED_operator_editsurf`) only accepts an edited surface object.
- The modifier-stack UI test was retargeted from the legacy `.modifier-stack-authoring` panel (not rendered in the current Web tools layout) to the live `[data-modifier-row]` properties panel and its desktop labels (`Amount`/`Segments`, `Solidify` option id). This was a stale test selector, not a product regression. Focused regression on `33db5f5e`: 38/38 tests passed with 4 workers (three-domain chains, 62-case operator matrix, modifier-stack UI/native scenarios, creation/adjust/data-instance/hair-curves/modifier-apply/convert/armature/object-tools/edit-persistence/scene/collection, native modal, rig, readers, NLA, library append, RNA reflection).
- Open gaps (not claimed): `object.quick_fur` still fails because the linked procedural-hair node group evaluates to non-curves (`Evaluated geometry from modifier does not contain curves`) and the Web RNA Main-access guard then invalidates the Python callback (`Main changed during Python property conversion or callback`); QuadriFlow remesh (`WITH_QUADRIFLOW=OFF`), OpenVDB voxel remesh (`WITH_OPENVDB=OFF`), Mantaflow fluid quick effects (`WITH_MOD_FLUID=OFF`) and Ocean bake (`WITH_FFTW3=OFF`) remain build-flag gaps; `object.lightprobe_cache_bake` hits the browser draw-manager single-canvas guard; `uv.export_layout` needs the unloaded bundled addon. The changed spin/screw/rip menu wiring was verified through the same native operator channel, not by clicking the production menu.
- Receipts on `33db5f5e`: `verification/operator-sweep-{mesh,object,uv,curve}.json`, `native-operator-matrix-production.json` (62/62), `creation-object-types-production.json` (29/29), `curve-edit-operators-production.json` (32/32), `object-editor-*.json` (5), plus the production hierarchy timestamped receipt.
- 2026-11-10 late round (WASM `dc7a76eb`): further modeling gaps closed:
- Quick Fur and geometry-node assets now evaluate. `WEB_headless_node_system_init()` registered only a small geometry-node subset and no zone types, so the bundled procedural-hair groups loaded Capture Attribute / Separate Components / Input Radius as `NodeUndefined` and the node-tree updater called through a null zone vtable (`RuntimeError: null function`). The Web `bf_nodes` source set now includes `intern/node_register.cc` and the engine calls the desktop `register_nodes()` once. The hair groups load with zero undefined nodes; `object.quick_fur` creates 1212 points across 101 curves with Generate applied and Set Profile/Interpolate/Noise/Frizz/Hair Dynamics kept.
- Temporary `G_MAIN` swaps during library append/link no longer poison an in-flight Python callback. `RNAMainAccessScope::restore_after_temporary_main_swap` / `restore_context_after_temporary_main_swap` revive suspended scopes when the same Main is swapped back (genuine replacement/clear still invalidates); `BKE_blender_globals_main_swap` and the Web context refresh call the restore after rebinding. Quick Fur's nested `modifier_apply` now completes instead of failing with `Main changed during Python property conversion or callback`.
- QuadriFlow remesh is enabled (`WITH_QUADRIFLOW=ON`): `object.quadriflow_remesh` finishes with “QuadriFlow: Remeshing completed” and remeshes the 8-vertex test cube to 3954 vertices.
- Mantaflow fluid is enabled (`WITH_MOD_FLUID=ON`). `MOD_fluid.cc` forced the Fluid modifier's RNA `srna` to `nullptr` under `WITH_WEB`, so `rna_Modifier_refine` dereferenced null when a Fluid modifier was converted to Python. The real `&RNA_FluidModifier` is restored (and the matching Cloth `nullptr` is fixed). `object.quick_liquid` creates a Liquid Domain with Fluid modifiers plus a Liquid Particle System; `object.quick_smoke` creates a Smoke Domain with Fluid modifiers; a Cloth modifier exposes its settings without crashing.
- `uv.export_layout` is available: the Python bootstrap registers the bundled `io_mesh_uv_layout` addon when the session starts (retry-safe). The operator poll passes for a UV-mapped active mesh and an SVG export writes 1288 bytes to `/browser/uv_layout.svg`.
- Final sweep on `dc7a76eb`: mesh 136/167, object 108/250, uv 32/55, curve 43/51 EXECUTED (319/523); zero CRASH/TIMEOUT/core OPERATOR_MISSING. `object.quick_fur`, `quick_explode`, `quick_liquid`, `quick_smoke` and `quadriflow_remesh` are EXECUTED; `uv.export_layout` is registered (native context message for the empty-filepath sweep invocation instead of ADDON_MISSING).
- remaining (not claimed): OpenVDB voxel remesh (`WITH_OPENVDB=OFF`; `.deps/native/openvdb` contains only `include/` and native `lib/*.so`, no OpenVDB source or wasm OpenVDB/Boost/TBB/blosc artifacts); Ocean bake (`WITH_FFTW3=OFF`; `.deps/native/fftw3` is native only, no WASM FFTW sources/libs); light-probe cache bake runs the EEVEE lightbake job, which calls DRW_gpu_context_create; the headless manual host has no active GPU context, so it reports 'Browser draw manager must be created once per WM canvas'. A canvas-attached UI session is not yet verified, and this is render-engine behavior outside the three modeling domains. The non-executed sweep operators are modal/context classes matching desktop semantics. Full desktop parity remains NOT_CLAIMED pending user acceptance and WASM ports of OpenVDB/FFTW3.
- next action: port OpenVDB and FFTW3 to WASM to close voxel remesh and ocean bake; keep the documented gaps explicit; full desktop parity remains NOT_CLAIMED pending user acceptance.

View File

@@ -0,0 +1,89 @@
# Web UI 对标 + PBR 真实案例实现状态(2026-11-11)
- 状态:`in_progress`(实现证据,不宣称验收)
- 范围:Outliner React treeview、着色器编辑器 React Flow、真实 PBR 资产链路与色彩管理
- 验收:`NOT_CLAIMED`
## 已实现
### Outliner
- 新增 `web/app/src/app/ui/OutlinerTreeView.tsx`、`outliner-tree-data.ts`、`outliner-tree.css`。
- 使用 `react-accessible-treeview@2.11.2` 渲染原生 `tree.rows`;保留选择、展开、拖放 payload、重命名、Remap、可见性、截断提示。
- `App.tsx` 原生行渲染改为 treeview `renderRow`,并保留 `role="treeitem"` 可访问名、`data-node-id`/`data-active`、`.tree-node-name`、展开按钮可访问名。
- 修复 treeview 受控展开反馈环:不再由库自动触发 `OUTLINER_TOGGLE_EXPAND`,仅用户按钮/键盘触发。
### 着色器编辑器(React Flow)
- 新增 `web/app/src/app/ui/ShaderNodeCanvas.tsx`、`ShaderNode.tsx`、`shader-flow-adapter.ts`、`shader-node-catalog.ts`、`shader-node.css`。
- `WasmShaderEditor.tsx` 画布主体切换到 `@xyflow/react@12.12.1`:
- 节点拖动 → `MOVE`
- 端口拖拽 → `LINK`,边删除 → `UNLINK`
- 节点内 socket 编辑 → `SET_SOCKET`
- Shift+A / 工具栏添加节点 → `ADD`
- 节点属性(Mix data/blend、Mapping vector type、Normal Map space、Bump invert、UV Map 名称)→ `SET_PROPERTY`
- Image Texture 换图 → `SET_IMAGE`
- 只读链接材质、revision 冲突、未知节点占位、全部端口折叠。
- 坐标约定 `position={x, -y}`;读回 `fromSocket/toSocket` 与命令 `fromSocketId/toSocketId` 由适配器唯一转换。
- SSE/React Flow 要求:CSP 增加 `style-src-attr 'unsafe-inline'`,脚本/Worker 仍严格;去掉 attribution 隐藏。
### 原生读回与编辑
- `web_engine_blend_reader.cpp`:节点类型补齐 MIX/MAPPING/TEX_COORD/UV_MAP/BUMP/SEPARATE_COLOR/OBJECT_INFO;socket `available`/`hidden`;节点属性(dataType/blendType/vectorType/space/invert/projection/interpolation/extension/uvMap/mode);材质 `textureBindings`;`normalImageId` 真正赋值;Image `colorSpace` 读取(当前资产未返回,见残余)。
- `web_engine_main_state.cpp`:`editShaderNode` 新增 `SET_PROPERTY`/`SET_IMAGE`,ADD 支持 UV_MAP/OBJECT_INFO/SEPARATE_COLOR;`setShaderGraph` 类型扩展并按 socket **identifier** 绑定(修复 Mix 同名 socket)。
- 新增 `packImage`:外部 PNG/JPEG 字节直接打包进已有 Image,保留节点链接,允许 0 尺寸由调用方按文件头提供。
- 新增 `assignMaterialSlot`:把已有材质装入对象槽(`BKE_object_material_slot_add` + `BKE_object_material_assign`),属性面板材质槽下拉已接入。
- `web_engine_api.cpp`、`web/protocol/web-engine.ts`、`web-engine.worker.ts`、`web-engine-capabilities.ts` 同步命令。
### PBR 显示
- `three-adapter/pbr.ts`:按场景 `colorManagement.viewTransform` 选择 AgX/Standard/Neutral/ACES;`toneMappingExposure = 2 ** exposure`;`data-tone-mapping` 场景驱动。
- `three-adapter/texture-assets.ts`:应用 `map`、`normalMap`+strength、`metalnessMap`、`roughnessMap`、`emissiveMap`、`alphaMap`、`bumpMap`;贴图驱动时中性化 factor。
- `texture-assets.applyEnvironment` 使用 `WorldIR.environmentRotation`。
- `render-assets.ts` 纹理用途扩展;`scene-ir.ts` 校验新节点类型/属性/textureBindings。
- 外部贴图 UI:着色器编辑器“外部贴图”列表;文件先 `StorageClient.putAsset` 写入 OPFS,再 `packImage`;项目保存继续走 `StorageClient.saveProject`(OPFS)。
## Chromium 实测(手动探针,实现证据)
- `tests/files/web/m12_glb_main_v1/pbr.blend`:Outliner 树、React Flow 2 节点/1 连线;拖动、连线、断开、改 Roughness、添加 Mapping 均发出正确原生命令;保存后重开 Roughness=0.7、Mapping 位置/连线保留;Ctrl+Z / Ctrl+Shift+Z 恢复 0.28 / 0.7。
- `blender-5.2.0/tests/files/io_tests/blend_geometry/materials_pbr.blend`:ImageTexture/Principled 读回;三张外部 PNG 经 OPFS→packImage 变为 PACKED;viewport 显示红/金属黑 PBR 立方体;`data-tone-mapping` 场景驱动。
- `cubes_with_textures.blend`:读回 MAPPING/UV_MAP/SEPARATE_COLOR/IMAGE_TEXTURE;textureBindings 含 BASE_COLOR/ROUGHNESS/NORMAL/EMISSION,`normalImageId` 已赋值。
- `user-testing/WEB_Blender_Robot_Tool_20260916/outputs/robot-project.blend`:18 对象、7 个 link 材质;着色器材质列表可见。
## Playwright 用例
- `web/tests/e2e/outliner-treeview-production.spec.ts`:通过。
- `web/tests/e2e/shader-editor-flow-production.spec.ts`:通过(headless 合成指针的节点拖动不稳定,拖动由手动 Chromium 探针覆盖;其余连线/删除/添加/参数/保存重开在用例内)。
- `web/tests/e2e/pbr-real-assets-production.spec.ts`:通过。三张外部 PNG 在 Web UI(`?ui=web`)下经 OPFS→`packImage` 打包,SceneIR 依次 r7/r10/r13,外部贴图列表清空。
- 三条用例合并运行:`3 passed (37.3s)`。
- 注意:测试使用 `?ui=web`(React UI 模式)以避免 headless SwiftShader 下原生 GPU 节点编辑器绘制抛 `WASM_INIT_FAILED`;这不是 React UI 路径的缺陷。
## 残余缺口
1. Image `colorSpace` 未从 `.blend` 读出(`undefined`);当前按纹理用途推断 sRGB/Non-Color,颜色贴图默认正确,非默认色彩空间未覆盖。
2. `robot-project.blend` 保存工作区中 Outliner 深层的展开按钮发送选择而非 `OUTLINER_TOGGLE_EXPAND`;`pbr.blend`/`basic_scene.blend` 展开正常。需继续定位该工程的 workspace/area 映射。
3. Headless Playwright 合成鼠标拖动 React Flow 节点不稳定;真实 Chromium 手动拖拽正常。
4. Compositor 原生 `setCompositorNodeGraph` 仍未实现;Geometry Nodes 尚未复用 React Flow 画布。
5. 非 Principled BSDF(Glass/Hair/Anisotropic 等)、Subsurface/Volume/UDIM/HDRI 仍按 capability block 或后续批次处理。
6. 本轮未运行全量回归、未生成正式 candidate receipt;构建哈希与证据需在后续批次绑定。
## 构建与构件
- 原生:`cmake --build build_web_blender6 --target web_engine -- -j8`
- 同步:`node tools/web/sync-web-engine-runtime.mjs`
- 前端:`cd web && npm run build`
- `web_engine.wasm` SHA-256:`84fc21e7728b580579424f97495d27275d30c01f2898ccadf4e368ea37d646ea`(与 `web/app/src/vendor/blender/web_engine.wasm` 一致)
- `web/dist/index.html` SHA-256:`d36f002b7793b7684f3ac570537bd33527143a6ea64fb43b3dabff0d3d313a8e`
## CSP 与样式加载
- React Flow 需要内联 style 属性;部署 CSP 改为 `style-src 'self'; style-src-attr 'unsafe-inline'`,脚本/Worker 指令保持严格。
- React Flow 与新增组件 CSS 通过 `web/app/index.html` 的同源 `<link>` 引入(`ui/xyflow.css`、`ui/outliner-tree.css`、`ui/shader-node.css`),避免 Vite dev 的内联 `<style>` 注入被 CSP 阻断;生产构建提取为 CSS 文件。
- `tools/web/check-csp-policy.mjs` 已改为仅对脚本/Worker 指令拒绝 `'unsafe-inline'`,并断言 `style-src-attr` 的最小放宽。既有 `tests/golden/M13-05A/csp-report.json` 需在后续验收批次重新生成。
## OPFS 资产策略
- 渲染用 packed 图像:`cachePackedAssets` 在每次快照后通过 `StorageClient.putAsset` 写入 OPFS。
- 外部贴图:用户选择文件后先 `putAsset`(OPFS),再 `packImage` 进工程;项目保存走 `StorageClient.saveProject`(OPFS)。
- 打开工程时提供的库 `.blend` 文件在 `openBlend` 前写入项目 OPFS(`application/x-blender`)。
- 资产库/OBJ/多分辨率/VDB 等既有导入路径继续使用 `putAsset`。

View File

@@ -0,0 +1,27 @@
# WBV2-P1424-900: supersede stalled task for domain parity
- task: WBV2-P1424-900
- parent: WBV2-P1424-001
- status: done
- ownerFamily: PROGRAM
- targetImplementationClass: QUEUE_ONLY
## 目标
记录用户 2026-11-10 授权的队列桥接:保留 `WBV2-P1424-001` 的 manifest、continuation、`in_progress` 状态和全部未闭合证据,不修改该文件;由本桥接指向三域全量对标 epoch 根任务 `WBV2-P1425-000`。
## 输入与范围
- 保留任务:`tests/golden/WBV2-P1424-001/manifest.json`(期望 212423 bytes,原样)
- 保留 continuation:`tests/golden/WBV2-P1424-001/continuation-20261103-goal-numpy.json`
- 新计划:`tests/golden/WBV2-P1425-000/domain-parity-plan.json`
## 验收
- P1424-001 manifest 字节级不变;桥接 manifest 为 done 且 nextTask 指向 `WBV2-P1425-000`。
- 负例:P1424-001 manifest 大小变化、continuation 缺失或队列已被再次切换时拒绝继续。
- 校验:`node tools/web/check-execution-control-plane.mjs`
## 交付与回滚
- 交付桥接 manifest/status/report;回滚把队列恢复为 `WBV2-P1424-001` 并删除桥接指针,不删除任何证据。

View File

@@ -0,0 +1,32 @@
# WBV2-P1425-000: authorize create/modeling/hierarchy parity epoch
- task: WBV2-P1425-000
- parent: WBV2-P1424-900
- status: done
- ownerFamily: PROGRAM
- targetImplementationClass: QUEUE_ONLY
- Implementation entrypoint: `node tools/web/authorize-wbv2-p1425.mjs`
## 目标
以用户 2026-11-10 批准的三域全量对标计划为依据,授权新的 module epoch,并把队列指向首个模块 `WBV2-P1425-001`。`WBV2-P1424-001` 的 manifest、continuation、in_progress 状态和全部未闭合证据原样保留,不删除、不伪装完成。
## 输入与范围
- 用户批准计划:`docs/plans/CREATE_MODEL_HIERARCHY_PARITY_PLAN.md`
- 源队列计划:`tests/golden/WBV2-P1424-001/recovery-20261020/execution-plan.json`
- 新计划:`tests/golden/WBV2-P1425-000/domain-parity-plan.json`
- 保留任务:`WBV2-P1424-001`(`tests/golden/WBV2-P1424-001/manifest.json`)
- 桥接 manifest:`tests/golden/WBV2-P1424-900/manifest.json`
## 验收
- P1424-001 manifest 仍为 `in_progress` 且字节级未变。
- 新计划中恰好一个 ready/in_progress 模块;索引 activeTask 与队列指针一致。
- 负例:P1424-001 缺失、重复授权或索引不一致时脚本必须拒绝对队列做第二次切换。
- `node tools/web/check-execution-control-plane.mjs`
## 交付与回滚
- 交付 plan、manifest、status、report、task-context;校验 `node tools/web/check-execution-control-plane.mjs`。
- 回滚:恢复队列指针为 `WBV2-P1424-001`;新计划文件保留但不得作为 active 队列。

View File

@@ -0,0 +1,48 @@
# WBV2-P1425-001: implement Add menu native operator route
- task: WBV2-P1425-001
- parent: WBV2-P1425-000
- status: in_progress
- gap: workflow:object_add_route
- ownerFamily: OBJECT
- targetImplementationClass: LOCAL_EXACT
- moduleId: OBJECT_ADD_ROUTE
- Implementation entrypoint: `node tools/web/run-wbv2-module-task.mjs --task WBV2-P1425-001 --write`
## 目标
让 WASM 本地模式的 Add 菜单通过现有通用桌面 operator 通道(`desktop_operator_catalog/schema/desktop_operator` + `gpuUIInvoke`)创建 Mesh 全部基础体,包括缺失的 Circle/Grid/Monkey,并支持 location/rotation/scale/align/enter_editmode 参数;复用 Operator Palette、`WebEngineClient` 与原生 Main,不新增并行执行器。
## 输入与范围
- 原生通道:`blender-5.2.0/source/blender/web_engine/web_engine_python_bootstrap.py`、`web_engine_gpu_ui.cpp`、`web_engine_api.cpp`
- 前端:`web/app/src/app/App.tsx`(Add 菜单、F3、快捷条)、`web/app/src/engine-client/WebEngineClient.ts`
- 协议/路由:`web/protocol/web-engine.ts`、`web/app/src/capabilities/blender-feature-route-catalog.json`
- 绑定的源码单元:operator:MESH_OT_primitive_circle_add, operator:MESH_OT_primitive_cone_add, operator:MESH_OT_primitive_cube_add, operator:MESH_OT_primitive_cylinder_add, operator:MESH_OT_primitive_grid_add, operator:MESH_OT_primitive_ico_sphere_add, operator:MESH_OT_primitive_monkey_add, operator:MESH_OT_primitive_plane_add, operator:MESH_OT_primitive_uv_sphere_add, operator:OBJECT_OT_add, operator:OBJECT_OT_add_named, operator:OBJECT_OT_armature_add, operator:OBJECT_OT_camera_add, operator:OBJECT_OT_empty_add, operator:OBJECT_OT_light_add
- 已复用证据:`tests/golden/WBV2-P18-002/manifest.json`、`tests/golden/WBV2-P18-002/object-create-production.json`
## 失败边界
不支持的基础体/参数、错误 mode/area、陈旧 baseRevision、modal 占用必须返回结构化错误,Main revision 不变且不产生 undo 记录;linked/只读对象不得被创建路径绕过。
## 验收
- 正例:Add Circle/Grid/Monkey 及既有基础体经原生通道创建,Main mutation 与 snapshot 反映对象。
- 负例:非法标识、非法参数、陈旧 revision、错误上下文均 no-mutation。
- 恢复:undo/redo、save/reopen、Worker restart 语义一致。
- 命令:实现期只做增量编译与 runtime 同步;全部功能完成后由用户在 Chromium 验证。结果标 `NOT_CLAIMED`。
- `cmake --build build_web_blender6 --target web_engine -- -j8`
- `node tools/web/sync-web-engine-runtime.mjs`
## 交付与回滚
- 交付 module receipt、manifest、status、continuation;失败保持 `in_progress`,保留已有构件与证据。
## 本轮扩展范围(2026-11-10)
- 创建模型:全部 Mesh 基础体(含 Circle/Grid/Monkey)、NURBS 曲线/曲面、非网格类型与类型子菜单、编辑模式 Add、F3 通用添加。
- 模型层级:集合创建/移动/删除/排序/活动集合/实例、Outliner 拖放、每视图层可见性、场景切换/删除、视图层增删、父级类型与清除。
- 模型化:原生元素选择桥与读回、网格操作符覆盖、选择感知模态工具、UV 工具、顶点组、形态键、修改器 Apply/Shape Key/复制、对象着色/转换/原点/变换清除/数据关联/轴心方向吸附、20 个新增修改器类型。
- 实现清单:`implemented-routes.json`、`implemented-unit-bindings.json`(235 条路由,全部 `IMPLEMENTED_UNVERIFIED`)。
- 用户验证清单:`docs/plans/WEB_BLENDER_THREE_DOMAIN_VALIDATION_CHECKLIST.md`。
- 所有实现均未运行负例、undo/redo、save/reopen、Worker restart 或 Chromium 自动化;验收 `NOT_CLAIMED`。

View File

@@ -22,7 +22,7 @@
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Embedder-Policy": "require-corp",
"Cross-Origin-Resource-Policy": "same-origin",
"Content-Security-Policy": "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; worker-src 'self'; connect-src 'self'; font-src 'self'; img-src 'self' blob:; media-src 'self' blob:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'"
"Content-Security-Policy": "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self'; style-src-attr 'unsafe-inline'; worker-src 'self'; connect-src 'self'; font-src 'self'; img-src 'self' blob:; media-src 'self' blob:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'"
},
"routes": [
{