149 lines
6.9 KiB
Markdown
149 lines
6.9 KiB
Markdown
# Blender:Python 场景生成层
|
||
|
||
> 覆盖 `blender/**/*.py`。
|
||
> 运行时:**两个**——Blender 内嵌 Python(bpy 层)和系统 Python(纯 Python 层)。
|
||
> 这条内部边界是本层最重要的结构约束。
|
||
|
||
---
|
||
|
||
## 先读哪一篇
|
||
|
||
| 你要做的事 | 读 |
|
||
|---|---|
|
||
| 新建模块、挪代码、加一种 OSM 要素 | [模块结构](./module-structure.md) ← **先确认放在哪一层** |
|
||
| 改几何构建、材质、实例化、Cesium 调色 | [资产生成](./asset-generation.md) |
|
||
| 改 `geom.py` / `osm.py` 或加纯函数 | [测试](./testing.md) |
|
||
| 改材质名、动 `MATERIALS` 顺序 | [模块结构 · 材质名对接](./module-structure.md#材质名对接新场景靠自定义属性旧场景靠回退表) |
|
||
| 声称"纯重构,产物不变" | [产物一致性指南](../guides/artifact-parity-guide.md) |
|
||
|
||
---
|
||
|
||
## 依赖分层
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ 纯 Python 层 —— 无 bpy,系统 python 可跑可测 │
|
||
│ │
|
||
│ osmassets/osm.py OSM XML 解析 + 局部米制投影 │
|
||
│ osmassets/geom.py 平面几何(米制) │
|
||
│ osmassets/catalog.py 图层与材质的声明 │
|
||
│ │
|
||
│ ▲ blender/tests/test_pure.py 覆盖这一层(51 个用例) │
|
||
└─────────────────────────────────────────────────────────┘
|
||
▲ 只能单向依赖
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ bpy 层 —— 只在 Blender 内运行,无单元测试 │
|
||
│ │
|
||
│ osmassets/mesh.py MeshBatch 等几何构建 │
|
||
│ osmassets/materials.py 材质构建(消费 catalog 的声明) │
|
||
│ osmassets/tree.py 树实例化 │
|
||
│ osmassets/water.py grass.py scrub.py │
|
||
│ osmassets/building.py fountain.py 要素装配 │
|
||
│ │
|
||
│ generate_scene.py export_cesium.py 两个入口 │
|
||
│ tools/scene_digest.py 结构摘要工具 │
|
||
│ │
|
||
│ ▲ 回归防线是 parity 校验,不是单元测试 │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**在纯 Python 层里 `import bpy` 会静默废掉整个测试套件**——它不会失败,
|
||
而是 import 阶段就崩,看起来像环境问题。
|
||
|
||
**推论**:能挪进纯 Python 层的逻辑就挪。一个函数只要不碰 `bpy`,放进 `geom.py`
|
||
就立刻获得被测试覆盖的资格。
|
||
|
||
---
|
||
|
||
## 两个入口
|
||
|
||
| | `generate_scene.py` | `export_cesium.py` |
|
||
|---|---|---|
|
||
| 行数 | 999 | 624 |
|
||
| 调用 | `--background --factory-startup --python` | `--background --python` |
|
||
| 输入 | `--osm` + `--geojson`(可选) | `--blend` |
|
||
| 输出 | `--output`(.blend)、`--render`(.png) | `--glb`、`--metadata`(.json) |
|
||
| 完成标记 | `SCENE_DONE` | `CESIUM_EXPORT_DONE` |
|
||
| 由谁调起 | `build-area.js` 的 `blender` 阶段 | `build-area.js` 的 `cesium` 阶段 |
|
||
|
||
两个 stdout 标记是 [parity 契约](../guides/artifact-parity-guide.md)的一部分
|
||
(`scripts/parity.js:121` 解析它们),**改动打印格式等于改动契约**。
|
||
|
||
### `--factory-startup` 只在 generate 阶段用
|
||
|
||
它屏蔽本机 Blender 的 preferences 和 addon,保证场景生成不受用户配置影响。
|
||
副作用是脚本自己的目录不在 `sys.path` 上,所以两个入口开头都有那段
|
||
`sys.path.insert` 样板 + `# noqa: E402`——**不是可以整理掉的坏味道**。
|
||
|
||
---
|
||
|
||
## 场景构建的输入约定
|
||
|
||
`generate_scene.py:9-12` 记录了一个容易踩的坑:
|
||
|
||
> **范围只认 OSM 的 `bounds` 元素**,不用全部节点算包围盒。OSM 导出可能带上
|
||
> 请求范围之外的 relation 成员,用全部节点会得到一个大得离谱的模型。
|
||
|
||
`--geojson` 是可选的。给了就用 osm2streets 的精细道路面、人行道、车道标线、
|
||
斑马线;不给则回退到简单的 OSM `highway` 折线(`generate_scene.py:14-16`,
|
||
回退逻辑在 `:849`)。
|
||
|
||
植被映射(`generate_scene.py:18-20`):
|
||
|
||
| OSM 标签 | 产物 |
|
||
|---|---|
|
||
| `natural=tree`(节点) | 单棵树 |
|
||
| `natural=tree_row`(way) | 等距成排的树 |
|
||
| `landuse=grass` | 绿地 + 可选草簇散布 |
|
||
| `natural=scrub` | 低矮灌木覆盖 |
|
||
| `amenity=fountain` | 低模喷泉水池 |
|
||
|
||
---
|
||
|
||
## 三条贯穿全层的约定
|
||
|
||
1. **构建必须确定性**
|
||
用 `index` 的纯函数(无理数周期 / 黄金角)或显式 `seed` 代替 RNG,
|
||
绝不用无种子 `random` 或时间量。重建同一片区域必须得到相同结构,
|
||
否则 parity 校验永久红。见[资产生成](./asset-generation.md#确定性用无理数周期代替-rng)。
|
||
|
||
2. **压低对象数**
|
||
`MeshBatch` 批处理 + 树实例化共享 datablock。GLB 要在浏览器里跑,
|
||
节点数和贴图数都是硬成本。
|
||
|
||
3. **退化输入返回空,不抛异常**
|
||
一个坏多边形不该中断整片区域的构建。要素模块 `len(ring) < 3` 直接返回 0,
|
||
`MeshBatch` 静默 return,`geom.py` 的函数返回 `[]`。
|
||
|
||
---
|
||
|
||
## 文件速查
|
||
|
||
| 文件 | 行数 | 层 |
|
||
|---|---|---|
|
||
| `generate_scene.py` | 899 | bpy · 入口 |
|
||
| `export_cesium.py` | 647 | bpy · 入口 |
|
||
| `osmassets/tree.py` | 318 | bpy |
|
||
| `osmassets/geom.py` | 241 | 纯 |
|
||
| `osmassets/materials.py` | 232 | bpy |
|
||
| `osmassets/catalog.py` | 197 | 纯 |
|
||
| `osmassets/mesh.py` | 126 | bpy |
|
||
| `osmassets/osm.py` | 89 | 纯 |
|
||
| `osmassets/building.py` / `fountain.py` | 56 / 49 | bpy |
|
||
| `osmassets/water.py` / `grass.py` / `scrub.py` | 15 / 22 / 13 | bpy |
|
||
| `tools/scene_digest.py` | 171 | bpy · 工具 |
|
||
| `tests/test_pure.py` | 372 | 纯 · 测试 |
|
||
|
||
`blender/README.md` 是面向使用者的运行说明,与本目录互补——**用法写那边,
|
||
改法写这边**。
|
||
|
||
---
|
||
|
||
## 技术选型现状
|
||
|
||
- **Blender 4.x**,`bpy` + `mathutils`;`export_cesium.py` 额外用 `numpy`(Blender 自带)
|
||
- **纯 Python 层只用标准库**(`math`、`json`、`os`、`xml.etree`),
|
||
这是它能用系统 python 跑的前提——**不要给它加第三方依赖**
|
||
- **无类型标注、无 lint 配置**。保持现状;引入工具链是独立决定
|
||
- **`unittest` 而非 pytest**,零依赖跑得起来
|