Files
osmWorkflow/.trellis/spec/blender/index.md
2026-08-03 14:02:28 +08:00

7.0 KiB
Raw Blame History

BlenderPython 场景生成层

覆盖 blender/**/*.py。 运行时:两个——Blender 内嵌 Pythonbpy 层)和系统 Python纯 Python 层)。 这条内部边界是本层最重要的结构约束。


先读哪一篇

你要做的事
新建模块、挪代码、加一种 OSM 要素 模块结构先确认放在哪一层
改几何构建、材质、实例化、Cesium 调色 资产生成
geom.py / osm.py 或加纯函数 测试
改材质名、动 MATERIALS 顺序 模块结构 · 材质名对接
声称"纯重构,产物不变" 产物一致性指南

依赖分层

┌─────────────────────────────────────────────────────────┐
│ 纯 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/features.py    要素分发注册                  │
│   osmassets/building.py fountain.py roads.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
行数 895 647
调用 --background --factory-startup --python --background --python
输入 --osm + --geojson(可选) --blend
输出 --output(.blend)、--render(.png) --glb--metadata(.json)
完成标记 SCENE_DONE CESIUM_EXPORT_DONE
由谁调起 build-area.jsblender 阶段 build-area.jscesium 阶段

两个 stdout 标记是 parity 契约的一部分 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_rowway 等距成排的树
landuse=grass 绿地 + 可选草簇散布
natural=scrub 低矮灌木覆盖
amenity=fountain 低模喷泉水池

三条贯穿全层的约定

  1. 构建必须确定性index 的纯函数(无理数周期 / 黄金角)或显式 seed 代替 RNG 绝不用无种子 random 或时间量。重建同一片区域必须得到相同结构, 否则 parity 校验永久红。见资产生成

  2. 压低对象数 MeshBatch 批处理 + 树实例化共享 datablock。GLB 要在浏览器里跑, 节点数和贴图数都是硬成本。

  3. 退化输入返回空,不抛异常 一个坏多边形不该中断整片区域的构建。要素模块 len(ring) < 3 直接返回 0 MeshBatch 静默 returngeom.py 的函数返回 []


文件速查

文件 行数
generate_scene.py 895 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/features.py 20 bpy · 分发
osmassets/building.py / fountain.py / roads.py 56 / 49 / 40 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.xbpy + mathutilsexport_cesium.py 额外用 numpyBlender 自带)
  • 纯 Python 层只用标准库mathjsonosxml.etree 这是它能用系统 python 跑的前提——不要给它加第三方依赖
  • 无类型标注、无 lint 配置。保持现状;引入工具链是独立决定
  • unittest 而非 pytest,零依赖跑得起来