# 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` 的同源 `` 引入(`ui/xyflow.css`、`ui/outliner-tree.css`、`ui/shader-node.css`),避免 Vite dev 的内联 `