Files
osmWorkflow/.trellis/tasks/00-bootstrap-guidelines/design.md

12 KiB
Raw Blame History

技术设计Trellis spec 重建

对应 prd.md。本文件定义目录结构、每个文件的内容边界与取材来源。


1. 为什么按这四个包切分

Trellis 单仓模式下,.trellis/spec/ 的每个子目录自动成为一个 "spec layer" packages_context.py:30-41guides 被显式排除)。零配置,不需要动 config.yamlpackages:(那是 monorepo 用的)。

切分依据是运行时边界,不是目录名:

覆盖代码 运行时 切分理由
pipeline/ scripts/*.js, scripts/lib/scene-layers.js Node宿主机 唯一能调外部进程QGIS/Blender/ogr2ogr的层
blender/ blender/**/*.py Blender 内嵌 Python + 系统 Python 依赖 bpy,且内部还有一条纯 Python 子边界
preview/ scripts/lib/cesium-preview.{js,css} 浏览器 无构建步骤的 IIFE唯一的 DOM 环境
config/ config/areas/*.json, config/examples/template.json 数据(无运行时) 三层共同消费的契约,改一个字段会同时影响三层

scripts/normalize-lane-arrows.py 虽是 Python但跑在 QGIS 的 Python 里、由 build-osm2streets-qgis.js 调起,属于管线的外部工具调用,归 pipeline/

2. 目标结构

.trellis/spec/
├── index.md                        # 顶层索引:四个包 + guides 的路由表
├── pipeline/
│   ├── index.md                    # 包索引 + 五个 stage 的数据流全景
│   ├── cli-and-stages.md
│   ├── layer-registry.md
│   └── external-tools.md
├── blender/
│   ├── index.md                    # 包索引 + osmassets 依赖分层图
│   ├── module-structure.md
│   ├── asset-generation.md
│   └── testing.md
├── preview/
│   └── index.md
├── config/
│   └── index.md
└── guides/
    ├── index.md                    # 更新:触发点换成本项目的
    ├── code-reuse-thinking-guide.md    # 保留,补本项目实例
    ├── cross-layer-thinking-guide.md   # 保留,补本项目实例
    └── artifact-parity-guide.md    # 新增

被删除:.trellis/spec/frontend/ 全部 7 个文件6 个模板 + index

3. 每个文件的内容边界与取材

3.1 pipeline/cli-and-stages.md

要写什么 取材
--kebab-casecamelCase 的参数解析约定,重复实现在三个脚本里 build-area.js:50, reimport-gpkg.js:93, build-osm2streets-qgis.js
五个 stage 的职责与依赖顺序 build-area.js:32-46 的顶层调度
reimport 不在 all 里,是恢复步骤不是构建步骤 build-area.js:157-160 注释
intermediates × reimport 互斥,显式抛错 build-area.js:19-26
配置归一化:从 id 推导默认输出路径,outputs 可覆盖 build-area.js:74 normalizeAreaConfig
缺失必填项用 requireText 抛错而非默认值兜底 build-area.js:143, reimport-gpkg.js:125
外部命令统一走 runCommand,非零退出即终止 build-area.js:301

边界:只写"怎么加一个 stage / 怎么加一个 CLI 参数",不复述 README 里的用法示例。

3.2 pipeline/layer-registry.md

最重要的一篇。核心是"九个图层在两种语言里各存一份"这个刻意设计。

要写什么 取材
SCENE_LAYERS 是 JS 侧唯一事实源,zIndex 兼作绘制顺序 scene-layers.js:3-13 顶部注释
从前同一份表复制了四次z_index 表 / 样式 JSON / QGIS 工程 / README改一处漏一处会静默错栈 scene-layers.js:5-10
outline: null = 无描边QGIS 侧转成全透明 scene-layers.js:13-14
mergeScene(getCollection) 用回调取集合,让 build内存和 reimport磁盘共用同一套合并 scene-layers.js:107-124
Python 侧 catalog.ROAD_LAYERS 存的是另一组事实Blender 高度 z + 线性色) catalog.py:25-47
颜色故意不同步JS 是 QGIS sRGB 调试色Python 是 Blender 线性场景色,分别调过 catalog.py:11-15
check_layers() 只校验图层集合与顺序,读的是产物 osm2streets_scene_style.json catalog.py:162-195
校验是 warn 不是 fail过期或缺失的输出目录不该阻断重建 catalog.py:168-169
加一个图层的完整清单:改 scene-layers.js + catalog.py 两处,顺序必须一致 综合

3.3 pipeline/external-tools.md

要写什么 取材
QGIS 可执行文件路径推导(Contents/MacOS/{ogr2ogr,ogrinfo}),启动前 existsSync 校验 reimport-gpkg.js:30-41
GDAL 必须注入 PROJ_LIB / GDAL_DATA,否则坐标系静默出错 reimport-gpkg.js:132-137
不设 COORDINATE_PRECISION:会触发精度裁剪,实测丢 28 个顶点 reimport-gpkg.js:152-157
ogr2ogr 失败会留 0 字节文件 → 必须 staging 目录先导出+校验,全通过才拷回 reimport-gpkg.js:11-13, 61-91
copyFileSync 不用 rename:临时目录可能跨文件系统 reimport-gpkg.js:72-73
导出后必须验 type === "FeatureCollection"features 是数组 reimport-gpkg.js:168-179
Blender 以 --background --factory-startup 调起,-- 之后才是脚本参数 build-area.js:237-290, README 低层命令
空图层是警告不是错误 reimport-gpkg.js:85-88

3.4 blender/module-structure.md

要写什么 取材
按依赖分包不按功能osm.py/geom.py 纯 Python其余可 import bpy osmassets/__init__.py:1-12
这条线是几何可测试的前提,拆分前只能靠渲染整片区域来验证 __init__.py:9-12, test_pure.py:5-8
各模块职责一览geom/osm/mesh/materials/catalog/tree/water/grass/scrub 各文件 docstring
要素模块统一 assemble(...) 签名,返回计数供调用方汇总 water.py:7, grass.py:7, scrub.py:6
要素模块接收裁剪边界参数,自己调 clip_polygon,不假设调用方已裁剪 三个 assemble 的首行
catalog 声明"是什么"、materials 负责"怎么建"——这个拆分让 catalog 能被无 Blender 环境读取 materials.py:1-7
新增一种 OSM 要素 = 新增一个模块 + 注册一行,不改 build() docs/refactor-plan.md 目标节

3.5 blender/asset-generation.md

要写什么 取材
MeshBatch 是主力:批成一个 mesh datablock压低对象数和 glTF 节点数 mesh.py:1-9
累积几何后调一次 finish() mesh.py:6-8
树用实例化import 一次 → bake 朝向 → 每棵树只链一个轻对象复用 datablock tree.py:1-8
材质在本地重建而非沿用源文件因为两个源模型都不可直接用apple 57% 贴图透明,需 alpha-clip tree.py:12-16
MATERIALS / ROAD_LAYERS 是 list顺序决定 GLB 材质索引,只能追加 catalog.py:16-18
kind 选择构建器:solid / texturedprocedural 叠加噪声 catalog.py:52-55
Cesium 侧的调色覆盖(cesium 字段 + CESIUM_EXPORT)为什么单独存在 catalog.py:139-153
Cesium 默认光照偏白,场景内材质普遍手工提亮过;新资产不提亮会显得发黑 docs/changelog.md 2026-07-31 条目

3.6 blender/testing.md

要写什么 取材
运行方式:python3 -m unittest discover blender/tests,不需要 Blender test_pure.py:1-3
期望值必须从几何推导,不能录制当前实现——录制型测试会把 bug 固化成规范 test_pure.py:9-11
每个几何函数都覆盖退化输入(空、单点、共线、重复顶点、零长线段) test_pure.pytest_degenerate_*
覆盖除零守卫要写明针对哪一行代码 test_pure.py:76-78
随机采样函数必须验 seed 可复现 test_pure.py:135-138
解析器的容错语义:坏节点跳过不致命,缺 bounds 直接抛 test_pure.py:343-356
只能测纯 Python 侧;bpy 侧的回归靠 parity 校验 指向 guides/artifact-parity-guide.md

3.7 preview/index.md

要写什么 取材
无构建步骤IIFE + "use strict",通过 window.OSM_ASSET_PREVIEW_CONFIG 接参 cesium-preview.js:1-4
HTML 由 build-area.js:697 cesiumPreviewHtml() 生成,注入需转义(escapeHtml / escapeScriptJson build-area.js:759-767
DOM 句柄在顶部集中获取,不散在函数里 cesium-preview.js:5-20
status 分两类消息(场景摘要 vs 瞬时提示),摘要要保留以便瞬时提示可被替换而非摧毁 cesium-preview.js:21-25
Cesium.Ion.defaultAccessToken = "":不依赖 Ion 服务 cesium-preview.js:27
必须用 HTTP 服务打开,file:// 会被浏览器拦截 README 预览节
车辆巡航是预览层实验功能,不影响 Blender/GLB 主资产 README 实验节

3.8 config/index.md

要写什么 取材
字段全表与默认值 config/examples/template.json, build-area.js:74
路径必须绝对 template.json
outputs 覆盖是逃生舱,默认从 id 推导 README 区域配置节
qgis.* 各旋钮的物理含义arrowScale / arrowMergeTriangles / arrowOutlineSimplifyMeters / intersectionCornerSourceMaxDimensionMeters README QGIS knobs 节
arrowOutlineSimplifyMeters: 0.05 这个默认值的来历(去掉两个畸形尾顶点而不动箭头头部) README
intersectionCornerSourceMaxDimensionMeters 为什么要过滤大多边形(会盖住可行驶路口) README
新增区域:从 config/examples/template.json 复制,落到 config/areas/ README
已沉淀的两个区域配置 config/areas/

3.9 guides/artifact-parity-guide.md(新增)

要写什么 取材
什么时候需要跑 parity任何声称"纯重构"的改动 docs/refactor-plan.md 硬约束节
三件套分工:scene_digest.py.blend 结构)/ glb-digest.jsGLB 结构)/ parity.js(驱动+比对) refactor-plan 工具表
必须先做 control 实验(同代码跑两次)确定天然不稳定字段,跳过这步的 parity 校验是假的 refactor-plan
已确定的不稳定字段与原因blend sha256内嵌绝对路径+图片打包顺序、PNGEEVEE 非位级可复现、accessor 数UV 浮点噪声影响去重) refactor-plan control 结论
忽略名单写在 parity.js:IGNORED_PATHS 且必须附原因 parity.js:28-32
真正的契约stage stdout 标记 + .blend 全量结构摘要 + GLB node/mesh/material/image + <area>.json refactor-plan
基线落在 gitignore 的 outputs/_refactor-baseline/,是本地草稿不是产物 parity.js:16-17

3.10 guides/ 现有两篇的本地化

保留通用内容,把"触发点"清单换成本项目的真实场景:

  • cross-layer-thinking-guide.md:加图层表 JS↔Python 双向同步、GeoJSON→gpkg→GeoJSON 往返、材质名跨 generate_scene.py/export_cesium.py 对接
  • code-reuse-thinking-guide.md:加 parseArgs 在三个脚本重复实现、clip_polygon 在四个要素模块各调一次

3.11 spec/index.md(新增)

一张路由表:改哪类代码 → 先读哪个包的 index。加一句"跨语言/跨层改动先读 guides/"。

4. 风险与对策

风险 对策
写成"应该怎么做"的理想规范,与代码实际不符 每条约定必须能追到 path:linePRD 验收标准里已定"每文件至少 2 处真实引用"
把 README 的用法说明搬进 spec造成两处维护 spec 只写"怎么改代码",用法一律指向 README
frontend/ 时误删 guides/ 只删 .trellis/spec/frontend/ 单个目录,删后立即用 --mode packages 验证
行号引用随代码变动失效 行号只作定位提示,同时写函数名/常量名;关键处引用注释原文而非行号

5. 兼容性

  • 不动 config.yaml:单仓模式自动扫描 spec/ 子目录
  • 不动 .template-hashes.json:只有 2 个条目,均与本任务文件无关,trellis update 不会覆盖
  • 现有任务的 implement.jsonl / check.jsonl:本任务尚未生成,无需迁移