# 执行计划:Material export contract > 对应 `prd.md` / `design.md`。按序执行,避免把视觉调参和契约迁移混在一起。 --- ## 前置检查 ```bash python3 ./.trellis/scripts/get_context.py --mode packages python3 -m unittest discover blender/tests git status --porcelain ``` 可选但推荐:若本机 Blender / 输出产物可用,先采集 before parity: ```bash node scripts/parity.js capture material-contract-before ``` ## Step 1 — 迁移 catalog 契约 - [x] 在 `blender/osmassets/catalog.py` 中把有效 Cesium 覆盖迁入对应 `MATERIALS[*]["cesium"]` - [x] 不把 `"Office White Metal Facade"` 迁入新契约源 - [x] 删除或消解 `catalog.CESIUM_EXPORT` 死代码状态 - [x] 保持 `MATERIALS` 顺序不变,不在中间插入新材质 检查: ```bash grep -n "CESIUM_EXPORT\\|Office White Metal Facade\\|cesium" blender/osmassets/catalog.py ``` ## Step 2 — 材质创建时写自定义属性 - [x] 在 `blender/osmassets/materials.py` 增加 `cesium_export` 写入 helper - [x] `from_spec()` 对每个 material 应用 helper - [x] 无 `cesium` 的材质清理旧属性,避免复用同名材质时残留 检查点: ```bash python3 -m unittest discover blender/tests ``` ## Step 3 — 导出器优先读 contract - [x] 在 `blender/export_cesium.py` 增加 `cesium_contract(material)` helper - [x] `make_export_material()` 使用 contract 优先、旧表回退 - [x] `cesium_tinted_image()` 改为由调用方传入 tint - [x] 旧四张表保留为旧 `.blend` 兼容层,并注释说明 - [x] alpha-clipped foliage 分支不做行为重构 检查: ```bash grep -n "EXPORT_TINTS\\|EXPORT_METALLIC_OVERRIDES\\|EXPORT_BASE_COLOR_OVERRIDES\\|EXPORT_EMISSION_OVERRIDES\\|cesium_contract" blender/export_cesium.py ``` ## Step 4 — 验证新 `.blend` 带 contract 用一个区域跑 `blender` 阶段或 parity capture 后,使用 Blender/Python 摘要确认材质自定义属性存在。 可接受的验证方式: - 从 `blender/tools/scene_digest.py` 输出里看材质 custom property - 或用 Blender `--background --python-expr` 检查若干材质的 `cesium_export` 目标材质至少覆盖: - `Grass` - `Office White Plaster Facade` - `Industrial White Ribbed Facade` - `Factory Blue Metal Roof` - `Tree Crown` ## Step 5 — Spec 更新 - [x] 更新 `.trellis/spec/blender/module-structure.md` - [x] 更新 `.trellis/spec/blender/asset-generation.md` - [x] 必要时更新 `.trellis/spec/guides/artifact-parity-guide.md` 的 P3 状态 移除或改写“`catalog.CESIUM_EXPORT` 是死代码 / 改它不会生效 / P3 未做”的当前事实。 ## Step 6 — 全量验收 ```bash python3 -m unittest discover blender/tests node scripts/parity.js capture material-contract-after node scripts/parity.js compare material-contract-before material-contract-after git status --porcelain ``` 若没有可用 before capture,则先在未改动代码上 capture before;如果实现已经完成但 before 缺失, 不要伪造 parity 结论,明确记录“未运行 before/after parity”的缺口。 ## 回滚点 - Step 1 后如果纯测试失败,回滚 `catalog.py` 迁移。 - Step 3 后如果旧 `.blend` 导出行为不清晰,保留旧表优先,重新收窄 contract 读取范围。 - Parity 若出现非自定义属性以外的 `.blend` / GLB 结构差异,先修实现,不扩大忽略名单。 ## 实施记录 - 2026-08-03:实现完成。`node scripts/parity.js capture material-contract-before` 与 `material-contract-after` 均成功。 - `node scripts/parity.js compare material-contract-before material-contract-after` 返回差异 15 项:仅 `.blend` 材质 `props.cesium_export` 新增和 `.blend` bytes 变化; 未出现对象、网格、材质 PBR、GLB 结构或 metadata 差异。 - after snapshot 确认 `Grass`、`Office White Plaster Facade`、 `Industrial White Ribbed Facade`、`Factory Blue Metal Roof` 带 `cesium_export`; `hanyang-block` 还确认 `Tree Crown Dark` / `Tree Crown Light`。默认样本不创建 `Tree Crown`,另用 Blender `--python-expr` 构造 `catalog.MATERIALS["tree_crown"]` 确认该材质也写入 `cesium_export`。