Files
osmWorkflow/.trellis/tasks/archive/2026-08/08-03-material-export-contract/design.md

5.5 KiB
Raw Blame History

技术设计Cesium 材质导出契约化

对应 prd.md。本文件定义边界、数据流、兼容策略和风险控制。


1. 当前边界

catalog.py 是纯 Python 层,只声明材质事实,不能 import bpymaterials.py 属于 bpy 层,负责把 catalog spec 变成真实 bpy.types.Materialexport_cesium.py 打开 .blend 后只应消费场景里已有的材质信息。

因此正确边界是:

catalog.MATERIALS[*]["cesium"]      # 纯数据
        │
        ▼
materials.from_spec()               # bpy 层写 material["cesium_export"]
        │
        ▼
<area>.blend                         # 契约随场景文件保存
        │
        ▼
export_cesium.py                     # 优先读 material custom property

不采用:

export_cesium.py -> import catalog -> 按 material.name 反查

原因:这仍然依赖材质名字符串,而且旧 .blend 应该能在没有新 catalog contract 的情况下导出。

2. Contract Shape

材质自定义属性名固定为:

CESIUM_EXPORT_PROPERTY = "cesium_export"

JSON payload 建议 shape

{
  "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) 构造材质后调用:
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
contract = cesium_contract(material)
  1. 每个覆盖点改成 “contract 优先,旧表回退”:
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))
  1. 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 overridealpha-clipped 分支仍使用 diffuse-as-emissive texture。

7. Validation Strategy

快速检查:

python3 -m unittest discover blender/tests

结构验证:

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 更新随代码回滚。