Files
osmWorkflow/.trellis/spec/blender/module-structure.md

186 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# osmassets 模块结构
> 适用:在 `blender/` 下新增或移动代码。
> 核心是一条**按依赖切的边界**——切错了,整个测试套件就跑不起来。
---
## 按依赖分包,不按功能
`osmassets/__init__.py:3-12` 写明了这个包的切分原则:
> The package is split by dependency, not by feature:
> `osm` and `geom` are pure Python. They import no `bpy` and can be run and
> tested with a plain interpreter. Everything else may touch `bpy` and 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` 三个要素模块签名一致:
```python
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 <计数>[, <焦点用的点集>]
```
四条约定:
1. **自己裁剪**。收边界参数而不是收已裁剪的环,不假设调用方做过
(三个 `assemble` 的第一行都是 `clip_polygon`
2. **退化输入返回零,不抛异常**`len(ring) < 3` 直接返回计数 0
3. **返回计数**供调用方汇总统计;需要参与相机取景的还返回点集(`grass.py` / `scrub.py`
`focus`
4. **不自己找数据**。ring 由 `generate_scene.py` 传入,模块只负责装配
### 加一种新 OSM 要素
目标形态(`docs/refactor-plan.md`**新增一个模块 + 注册一行,不改 `build()`**。
1. 新建 `osmassets/<feature>.py`,写 `assemble(...)`,签名照抄上面
2. 只 import 需要的:`from osmassets.geom import clip_polygon`
`from osmassets.mesh import MeshBatch`
3. 材质规格加进 `catalog.MATERIALS`**追加到末尾**,顺序决定 GLB 材质索引)
4. `generate_scene.py` 的要素分发处加一行调用
5. 纯几何部分若有新函数,放 `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`
```python
# --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()`,都从 `--` 之后取参数:
```python
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` 会影响导出 | 它是死代码 |
---
## 相关
- [资产生成](./asset-generation.md)`MeshBatch`、材质、实例化
- [测试](./testing.md):纯 Python 层怎么测
- [图层表](../pipeline/layer-registry.md)`catalog.ROAD_LAYERS` 与 JS 侧的对账
- [产物一致性指南](../guides/artifact-parity-guide.md)bpy 层的回归靠它兜底