# 技术设计:Cesium 材质导出契约化 > 对应 `prd.md`。本文件定义边界、数据流、兼容策略和风险控制。 --- ## 1. 当前边界 `catalog.py` 是纯 Python 层,只声明材质事实,不能 import `bpy`。`materials.py` 属于 bpy 层,负责把 catalog spec 变成真实 `bpy.types.Material`。`export_cesium.py` 打开 `.blend` 后只应消费场景里已有的材质信息。 因此正确边界是: ``` catalog.MATERIALS[*]["cesium"] # 纯数据 │ ▼ materials.from_spec() # bpy 层写 material["cesium_export"] │ ▼ .blend # 契约随场景文件保存 │ ▼ export_cesium.py # 优先读 material custom property ``` 不采用: ``` export_cesium.py -> import catalog -> 按 material.name 反查 ``` 原因:这仍然依赖材质名字符串,而且旧 `.blend` 应该能在没有新 catalog contract 的情况下导出。 ## 2. Contract Shape 材质自定义属性名固定为: ```python CESIUM_EXPORT_PROPERTY = "cesium_export" ``` JSON payload 建议 shape: ```json { "tint": [[0.92, 0.94, 0.92], 0.38], "metallic": 0.0, "base_color": [0.93, 0.94, 0.91], "emission": [[0.93, 0.94, 0.91], 0.18] } ``` 字段语义: | 字段 | 类型 | 语义 | 旧来源 | |---|---|---|---| | `tint` | `[[r,g,b], factor]` | diffuse 贴图导出前往目标颜色混合 | `EXPORT_TINTS` | | `metallic` | number | 覆盖导出 PBR metallic | `EXPORT_METALLIC_OVERRIDES` | | `base_color` | `[r,g,b]` | 直接覆盖导出材质 base color,并禁用 diffuse/normal 图 | `EXPORT_BASE_COLOR_OVERRIDES` | | `emission` | `[[r,g,b], strength]` | 设置导出材质自发光 | `EXPORT_EMISSION_OVERRIDES` | 缺失字段表示沿用源材质当前值或当前特殊路径。 ## 3. Catalog Migration 把四张旧表中的有效条目迁入 `catalog.MATERIALS` 对应项: - `Grass` -> `MATERIALS["grass"]["cesium"]` - `Scrub Ground Cover` -> `MATERIALS["scrub"]["cesium"]` - `Tree Crown*` -> 现有 `tree_crown*["cesium"]` 扩展到完整字段 - `Office White Plaster Facade` -> `building_default` - `Office Light Flat Roof` -> `building_office_roof` - `Industrial White Ribbed Facade` -> `building_industrial` - `Factory Blue Metal Roof` -> `building_industrial_roof` `Office White Metal Facade` 当前没有 catalog 材质,不迁入新契约。旧表可保留该 key 作为旧 `.blend` 回退兼容。 `catalog.CESIUM_EXPORT` 应消失或变成真实消费路径。优先方案:删除全局 `CESIUM_EXPORT`, 把其中仍有效的内容迁入各自 `MATERIALS[*]["cesium"]`。 ## 4. Material Construction 在 `materials.py`: 1. import `json` 2. 定义同名常量 `CESIUM_EXPORT_PROPERTY = "cesium_export"`,或一个私有 helper 3. `from_spec(spec)` 构造材质后调用: ```python def apply_cesium_contract(material, spec): contract = spec.get("cesium") if contract is None: material.pop(CESIUM_EXPORT_PROPERTY, None) return material[CESIUM_EXPORT_PROPERTY] = json.dumps(contract, sort_keys=True) ``` 注意: - `sort_keys=True` 让 `.blend` 摘要更稳定。 - 没有 `cesium` 的材质应清掉旧属性,避免同名材质复用时残留。 - `catalog.py` 仍不 import `bpy`。 ## 5. Export Consumption 在 `export_cesium.py`: 1. 增加 `cesium_contract(material)`,读取并 `json.loads(material.get("cesium_export", "{}"))` 2. JSON 解析失败时返回 `{}` 并继续旧回退,避免坏旧文件直接崩溃 3. `make_export_material(material)` 开头读取一次 contract: ```python contract = cesium_contract(material) ``` 4. 每个覆盖点改成 “contract 优先,旧表回退”: ```python base_color = contract.get("base_color", EXPORT_BASE_COLOR_OVERRIDES.get(material.name, source_color(material))) metallic = contract.get("metallic", EXPORT_METALLIC_OVERRIDES.get(...)) emission = contract.get("emission", EXPORT_EMISSION_OVERRIDES.get(material.name)) tint = contract.get("tint", EXPORT_TINTS.get(material.name)) ``` 5. `cesium_tinted_image()` 可以改为接收 `tint` 参数,避免函数内部再查旧表。 ## 6. Foliage Special Case Alpha cut-out foliage 不在本次 contract 化范围内,原因是它不只是普通材质覆盖,还包含: - `source_alpha_clipped(material)` - `alpha_dilated_image(..., gain=FOLIAGE_ALBEDO_GAIN, saturation=FOLIAGE_SATURATION)` - diffuse texture 连接到 emission color - `FOLIAGE_EMISSION` 这些逻辑保持原样。普通 contract 的 `emission` 只处理非 alpha-clipped 或程序化材质的平铺 emission override;alpha-clipped 分支仍使用 diffuse-as-emissive texture。 ## 7. Validation Strategy 快速检查: ```bash python3 -m unittest discover blender/tests ``` 结构验证: ```bash node scripts/parity.js capture material-contract-before node scripts/parity.js capture material-contract-after node scripts/parity.js compare material-contract-before material-contract-after ``` 如果 before capture 已存在于实现前,after 在实现后跑。若 compare 报差异: - `.blend` 摘要多出材质自定义属性 `cesium_export` 是预期差异,但需要确认没有对象、网格、 材质槽顺序、GLB material PBR 参数等非预期变化。 - GLB 摘要应尽量全绿;若发生 PBR 参数变化,优先修实现而不是接受差异。 ## 8. Rollback 回滚点明确: - `catalog.py` 还原 `MATERIALS["cesium"]` 迁移和 `CESIUM_EXPORT` 处理。 - `materials.py` 删除 custom property 写入。 - `export_cesium.py` 恢复旧四张表直接读取。 - spec 更新随代码回滚。