7.6 KiB
osmassets 模块结构
适用:在
blender/下新增或移动代码。 核心是一条按依赖切的边界——切错了,整个测试套件就跑不起来。
按依赖分包,不按功能
osmassets/__init__.py:3-12 写明了这个包的切分原则:
The package is split by dependency, not by feature:
osmandgeomare pure Python. They import nobpyand can be run and tested with a plain interpreter. Everything else may touchbpyand only runs inside Blender.
┌─ 纯 Python 层(无 bpy,可用系统 python 直接跑和测)
│ osmassets/osm.py OSM XML 解析 + 局部米制投影
│ osmassets/geom.py 平面几何:裁剪、采样、面积、点在多边形内
│ osmassets/catalog.py 图层与材质的声明(只有 json/os,无 bpy)
│
└─ bpy 层(只能在 Blender 内运行)
osmassets/mesh.py MeshBatch、prism、polyline
osmassets/materials.py 材质构建
osmassets/tree.py 树实例化
osmassets/water.py grass.py scrub.py 要素装配
generate_scene.py export_cesium.py 两个入口脚本
tools/scene_digest.py 结构摘要工具
这条线是几何可测试的唯一前提。 拆分之前,验证 clip_polygon 或 sample_tree_row
的唯一办法是渲染整片区域然后看图(__init__.py:9-12、test_pure.py:5-8)。
在纯 Python 模块里写
import bpy会静默废掉 51 个单元测试——它们不会失败, 而是 import 阶段就崩,看起来像环境问题。
catalog.py 属于纯 Python 层是刻意的:它只声明"是什么",让任何不启动 Blender 的
工具也能读到场景的材质定义(materials.py:3-7)。
各模块职责
| 模块 | 层 | 职责 |
|---|---|---|
osm.py |
纯 | parse_osm() 读 OSM XML → (bounds, ways, points);Projector 局部米制投影;parse_height()、tags() |
geom.py |
纯 | 平面几何全家桶。输入输出一律是投影后的米,例外只有 geometry_rings / feature_in_bounds(收原始 GeoJSON 的经纬度) |
catalog.py |
纯 | ROAD_LAYERS、MATERIALS、road_material_specs()、check_layers() |
mesh.py |
bpy | MeshBatch、make_prism、add_roof、add_wall_panel、add_polyline、集合管理 |
materials.py |
bpy | 把 catalog 的规格变成真实材质:from_spec()、贴图、程序化噪声、tint、alpha-clip |
tree.py |
bpy | 两个 vendored 模型 → 一套可实例化的运行时形状 |
water.py / grass.py / scrub.py |
bpy | 单一 OSM 要素的装配 |
geom.py 的两条隐含约定
- 环是
(x, y)元组的列表。重复的闭合点"到处容忍但从不要求" (geom.py:8-9)——新函数要维持这个宽容度 - 单位是米,除非函数名另有说明
要素模块的统一形状
water.py / grass.py / scrub.py 三个要素模块签名一致:
def assemble(ring, [way_id,] scene_xmin, scene_xmax, scene_ymin, scene_ymax,
<颜色/材质...>, [回调...]):
ring = clip_polygon(ring, scene_xmin, scene_xmax, scene_ymin, scene_ymax)
if len(ring) < 3:
return 0[, ...]
...
return <计数>[, <焦点用的点集>]
四条约定:
- 自己裁剪。收边界参数而不是收已裁剪的环,不假设调用方做过
(三个
assemble的第一行都是clip_polygon) - 退化输入返回零,不抛异常。
len(ring) < 3直接返回计数 0 - 返回计数供调用方汇总统计;需要参与相机取景的还返回点集(
grass.py/scrub.py的focus) - 不自己找数据。ring 由
generate_scene.py传入,模块只负责装配
加一种新 OSM 要素
目标形态(docs/refactor-plan.md):新增一个模块 + 注册一行,不改 build()。
- 新建
osmassets/<feature>.py,写assemble(...),签名照抄上面 - 只 import 需要的:
from osmassets.geom import clip_polygon、from osmassets.mesh import MeshBatch - 材质规格加进
catalog.MATERIALS(追加到末尾,顺序决定 GLB 材质索引) generate_scene.py的要素分发处加一行调用- 纯几何部分若有新函数,放
geom.py并补blender/tests/test_pure.py
两个入口脚本
generate_scene.py (999行) |
export_cesium.py (624行) |
|
|---|---|---|
| 调用 | --background --factory-startup --python |
--background --python |
| 输入 | --osm + --geojson |
--blend |
| 输出 | --output(.blend) + --render(.png) |
--glb + --metadata(.json) |
| 完成标记 | SCENE_DONE |
CESIUM_EXPORT_DONE |
完成标记是 parity 契约的一部分(parity.js:121 解析它们),改动打印格式等于改动
契约。
sys.path 那段样板不能删
两个脚本开头都有(generate_scene.py:30-35、export_cesium.py:19-24):
# --factory-startup does not put the script's own directory on sys.path, so the
# osmassets package next to this file is not importable without this.
_HERE = os.path.dirname(os.path.abspath(__file__))
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
因此其后的 import 全部带 # noqa: E402。这不是可以"整理"掉的坏味道。
CLI 参数解析
两个脚本各有一份 cli_args(),都从 -- 之后取参数:
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
export_cesium.py:118-124 对三个必填项逐个 raise RuntimeError。必填项显式抛错,
不要给静默默认值——Blender 子进程里一个错的默认路径会写到意想不到的地方。
⚠️ 已知现状:两个入口靠材质名字符串对接
这是当前实际状态,不是设计目标。改动材质名之前必读。
export_cesium.py 不 import catalog(它只 import
from osmassets.materials import link_alpha_clip)。它自己维护四张以材质名字符串
为键的覆盖表:
| 表 | 位置 |
|---|---|
EXPORT_TINTS |
export_cesium.py:68 |
EXPORT_METALLIC_OVERRIDES |
:80 |
EXPORT_BASE_COLOR_OVERRIDES |
:86 |
EXPORT_EMISSION_OVERRIDES |
:95 |
后果:
- 改
catalog.MATERIALS里的name会静默断开这些覆盖。没有任何校验, 材质只是悄悄退回未调过的样子 catalog.CESIUM_EXPORT(catalog.py:142)是死代码——定义了但全仓无人引用。 它是一次未完成的迁移,不要以为改它会生效"Office White Metal Facade"在四张表里都有,但catalog里已无此材质 (docs/refactor-plan.md记为缺陷 D1,本轮只记录不修)
改材质名时:四张表 + catalog.MATERIALS + catalog.ROAD_LAYERS 全部 grep 一遍。
反模式
| 反模式 | 后果 |
|---|---|
在 osm.py / geom.py / catalog.py 里 import bpy |
51 个单元测试整体崩,且像环境问题 |
| 按功能而非依赖新建模块(把几何和 bpy 混在一起) | 该几何从此不可测 |
删掉 sys.path.insert 样板或 # noqa: E402 |
Blender 里 import 不到 osmassets |
| 要素模块假设 ring 已裁剪 | 越界几何进场景 |
| 要素模块对退化输入抛异常 | 一个坏多边形中断整片区域 |
| 改材质名只改一处 | Cesium 侧调色静默失效 |
以为改 catalog.CESIUM_EXPORT 会影响导出 |
它是死代码 |