Metadata-Version: 2.4
Name: pb-calibration
Version: 0.2.1
Summary: PB 车辆配置/标定文件（vehicle_config.proto）反序列化校验、解析为 YAML、从 YAML 组装为 .pb.txt。V0.2.1：build 输出仅允许 vehicle_info、extrinsics、intrinsics 由 YAML 修改，其它顶层块从 metadata 原文回填。
License-Expression: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: PyYAML>=6.0
Requires-Dist: protobuf>=4.21.0
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"

# pb-calibration

**当前版本：0.2.1**

基于 **vehicle_config.proto**（与 **mdrive** `master` 对齐，依赖 **`modules/message/common/geometry.proto`**）的 PB 车辆配置/标定文件（`.pb.txt`）反序列化校验、解析为 YAML、从 YAML 组装为 `.pb.txt`。  
V0.2.1：输出强保护。`build` 最终输出中只有 `vehicle_info`、`extrinsics`、`intrinsics` 允许由 YAML 修改生效；`vehicle_param`、`vehicle_lidar_config`、解析自动提取的 `custom:*`/未知顶层块均从 `.pb_roundtrip_meta.json` 原始文本回填。
V0.2.0：在保留 `vehicle_param` 原文保护区的基础上，`intrinsic` 与 `distortion` 支持 `field: [a, b, ...]` 单行方括号 repeated 写法，解析到 YAML 时会拆分为真正列表。
V0.1.9：`vehicle_param` 改为原文保护区。`parse` 只抽取顶层 `vehicle_param { ... }` 原始文本块并写入 `.pb_roundtrip_meta.json`，`vehicle_param.yaml` 仅作只读查看占位，`build` 永远从 metadata 的 raw block 回填；`update_vehicle_param` / `YamlConfigManager.update_vehicle_param` 会抛 `PermissionError`。  
V0.1.8：新增 `proto_dir` 元数据记录、protobuf Python 运行时 descriptor 兼容层，并将核心 protobuf 控制块内当前 proto 未识别字段写入 `__pb_unknown_raw__`，build 时默认原样保留，降低上游 proto 频繁新增字段造成的数据丢失风险。  
V0.1.7：在 V0.1.6 基础上，新增 PB 工作区实体级 CRUD（list/get/create/update/delete/replace），把 manifest 控制权统一收回到 `pb_calibration`，不再要求外部程序自己拼 `order_manifest.yaml`。  
V0.1.6：无论是未修改、修改还是新增内容，build 输出都统一空格与缩进；未修改块尽量保留原数字字面量，修改/新增块的浮点标准化精度提升到最多 16 位小数；新增 YAML 工作区一致性校验与 register/remove/reorder 共享接口，避免“文件已存在但未被 manifest 引用”的静默错误。  
V0.1.5：新增“未修改原文保真”重组、未知字段警告与尽量保留、浮点标准化输出、YAML 全量双引号字符串化。  
V0.1.4：`vehicle_config.proto` 与上游 mdrive 一致，`Transform` 使用 `mdrive.message.common.Point3D` / `Quaternion`；`VehicleLidarConfig.enable_ids` 为 `uint32`（与此前本地单文件内嵌 `int32` 的实验性定义不同）。  
V0.1.3：在 V0.1.2 基础上为 `VehicleConfig` 增加 `vehicle_lidar_config`（`enable_ids`），兼容 mdrive_conf 新标定。  
V0.1.2：VehicleInfo 新增字段 `color`、`manufacturer` 等；此前版本曾将 Point3D/Quaternion 内嵌在单文件内。

## 环境与安装

推荐使用 **Conda** 环境 `py310`，并在该环境中安装本包及依赖：

```bash
conda activate py310
pip install -e .   # 开发安装
# 或
pip install .      # 普通安装
```

若仅安装不开发，也可：

```bash
pip install pb-calibration
```

## 功能

1. **反序列化检查**：检查 `pb.txt` 是否存在格式问题（能否正常解析）。
2. **解析为 YAML**：将 `pb.txt` 解析为每个单独的 yaml 文件，输出到**指定目录**；该目录与后续「组合」时使用的目录一致。
3. **从 YAML 组装**：按顺序将指定目录中的单独 yaml 文件组装成 `pb.txt`（输入目录与解析输出目录一致，可指定）。
4. **输出强保护**：最终 PB 只有 `vehicle_info`、`extrinsics`、`intrinsics` 允许由 YAML 修改生效；其它顶层块从 `.pb_roundtrip_meta.json` 原始文本回填。
5. **protobuf 控制块重组与未知字段保留**：`vehicle_info`、`extrinsics`、`intrinsics` 在 `build` 时按当前 `vehicle_config.proto` 重新序列化；当前 proto 未识别的字段会进入 `__pb_unknown_raw__` 并默认原样保留。
6. **只读原文保护**：`vehicle_param`、`vehicle_lidar_config` 不接受最终 PB 输出层面的 YAML 修改；`check` 会临时屏蔽 `vehicle_param` 顶层块，仅校验其括号/字符串结构可匹配，并严格校验其它 protobuf 区域。
7. **未知字段 diff 提示**：如果非 `vehicle_param` 区域含当前 proto 未识别字段，`parse/build` 会输出带行号的 diff 风格提示，并明确打印 `pb_calibration` 与“可能 proto 没更新”。
7. **统一输出规范**：修改或新增的 `float/double` 字段统一输出为十进制、不使用科学计数法、最多 16 位小数、四舍五入、`0.0` 不省略；空内容不写。
8. **`custom.yaml` 原文容器**：解析时会生成 `custom.yaml`。V0.2.1 默认从 metadata 中的原始 `custom_blocks` 回填自动提取的未知顶层块，保证原始 PB 其它顶层参数不丢失；`custom.yaml` 仍保留给低层读取与人工查看。
9. **YAML 全量字符串化**：所有 protobuf 工作区 YAML 标量值均以双引号字符串输出，避免隐式类型转换。
10. **工作区一致性校验**：`build` 前会强制检查 `order_manifest` 与 `intrinsics/`、`extrinsics/` 文件集是否一致；同时校验 `custom.yaml` 与 `root_sections` 中的 `custom:<id>` 引用是否合法。
11. **实体级 CRUD**：新增 `list/get/create/update/delete/replace` 的 intrinsics / extrinsics 公共接口，外部程序不再需要自己控制 manifest。
12. **共享工作区操作**：保留 `validate_yaml_workspace`、`register_intrinsic_file`、`register_extrinsic_file`、`remove_intrinsic_entry`、`remove_extrinsic_entry`、`reorder_intrinsic_entries`、`reorder_extrinsic_entries`，供 skill、GUI 与脚本共用。
13. **顶层块操作**：支持 `get/update_vehicle_info`、`get/update_vehicle_lidar_config`；但 `update_vehicle_lidar_config` 仅改 YAML 工作区，最终 PB 输出仍回填原始块；`get_vehicle_param` 返回只读占位信息，`update_vehicle_param` 明确禁用并抛 `PermissionError`。
14. **custom 读取接口**：提供 `read_custom_yaml`、`read_custom_blocks`，供 skill 读取 `custom.yaml` 中保存的原文片段。
15. **YamlConfigManager**：类，存储默认 YAML 路径；**不缓存 YAML 内容**，每次从默认路径读取，保证多程序共同维护时数据为最新。支持 `read_yaml` / `update_yaml`、`read_order_manifest` / `update_order_manifest`、`rescan` / `get_cached` / `get`、一致性校验与 CRUD 封装。
16. **函数式低层 API**：`read_yaml`、`update_yaml`、`read_order_manifest`、`update_order_manifest` 等接口仍保留，供专家/兼容场景使用，但不再是推荐的 manifest 编辑主路径。

## 命令行

```bash
# 1. 检查 pb.txt 格式
pb-calibration check -i /path/to/vehicle_config.pb.txt

# 2. 解析 pb.txt 为 yaml，输出到指定目录（该目录即后续 build 的输入）
pb-calibration parse -i /path/to/vehicle_config.pb.txt -o /path/to/yaml_dir

# 3. 从指定目录按顺序组装为 pb.txt（目录与 parse 的 -o 一致）
pb-calibration build -i /path/to/yaml_dir -o /path/to/vehicle_config_out.pb.txt

# 4. build 前先检查工作区一致性
pb-calibration validate -i /path/to/yaml_dir
```

## 未知字段提示格式

当输入 PB 中存在当前 protobuf 未识别字段时，`parse` 或 `build` 会输出类似：

```text
WARNING[pb_calibration]: 当前 protobuf 未识别该字段，已写入 __pb_unknown_raw__ 并将在 build 时原样保留
@@ line 7, column 3 section=vehicle_info file=vehicle_info.yaml field=unknown_text
--   unknown_text: "x"
++ [pb_calibration/current proto] <preserved in __pb_unknown_raw__>
```

含义：
- `--`：原始 PB 中存在的文本行
- `++`：当前 `pb_calibration` / 本地 `vehicle_config.proto` 下的处理结果
- 当前版本中，非 `vehicle_param` protobuf 控制块的未知字段会进入对应 YAML 的 `__pb_unknown_raw__`，修改已知字段后仍会默认保留
- `vehicle_param`、`vehicle_lidar_config` 与未知顶层块属于输出保护内容，build 时从 metadata raw block 原样回填

## 输出强保护

V0.2.1 起，最终 PB 输出采用顶层块强保护：

- 允许由 YAML 修改生效的顶层块固定为 `vehicle_info`、`extrinsics`、`intrinsics`。
- `vehicle_param`、`vehicle_lidar_config`、解析自动提取的未知顶层块从 `.pb_roundtrip_meta.json` 原始文本回填。
- 原文保留范围为从顶层块 token 到匹配右大括号的文本块；顶层块之间的空行仍由 builder 统一处理。
- `.pb_roundtrip_meta.json` 中的 `sections.vehicle_param.raw_block` 是 build 唯一数据源。
- `vehicle_param.yaml` 只写入 `read_only`、`source`、`raw_block_ref` 等占位信息，手改字段或 raw_block 都会被忽略并产生 warning。
- `vehicle_lidar_config.yaml` 仍可通过 API 写入以保持兼容，但 build 会忽略其改动并回填 metadata 中的原始 `vehicle_lidar_config` 块。
- 缺少 `vehicle_param` metadata raw block 时，build 直接失败，不会退化为 protobuf/YAML 重新序列化。

## Proto 来源与更新

`vehicle_config.proto` 的上游更新路径为：

```text
https://git.minieye.tech/ad/mdrive/mdrive/-/blob/master/modules/common/configs/proto/vehicle_config.proto
```

本库不会自动联网下载 proto。若上游 proto 更新，请将 `vehicle_config.proto` 及其 import 依赖同步到本地目录，并通过 `proto_dir` 传入；未传入时使用包内默认 proto。`proto_templates_dir` 仍只表示 YAML 缺项检查模板目录，不用于加载 protobuf schema。

## `custom.yaml` 使用方法

### 1. 默认行为

- `parse` 后总会生成 `custom.yaml`
- 默认 `build` 会按 `order_manifest.yaml` 里的 `custom:<id>` 编排，从 metadata 的原始 `custom_blocks` 回填解析自动提取的未知顶层块
- 为保证输出强保护，手改 `custom.yaml` 不会覆盖 metadata 中已保存的原始顶层块内容

### 2. 文件结构

`order_manifest.yaml` 中新增：

```yaml
root_sections:
  - vehicle_info
  - custom:manual_legacy_block
  - vehicle_param
  - extrinsics
  - intrinsics
  - vehicle_lidar_config
custom_file: custom.yaml
```

`custom.yaml` 示例：

```yaml
blocks:
  - id: manual_legacy_block
    tag: raw_text
    enabled: true
    source: manual
    target_section: ""
    target_file: ""
    note: 供 skill 原格式复原
    content: |
      legacy_block {
        foo: "bar"
        x: 1
      }
```

### 3. 设计约束

- `content` 可写任意文本格式：
  - PB 片段
  - JSON
  - YAML
  - 纯文本
  - 多行模板
- `pb_calibration` 默认**不解析** `content` 语义，只负责原样保存与读取；build 输出优先使用 metadata 中的原始 content
- `custom.yaml` 的目标是给 skill 做原格式复原，不是替代 proto 同步
- 如果想让某字段进入正式 protobuf 主路径，仍应优先更新 `vehicle_config.proto`
- 若 metadata 中的原始未知顶层块语法本身有问题，最终 `.pb.txt` 也会原样保留该问题

## 测试

在本版本目录（V0.2.1）下执行：

```bash
conda activate py310
pip install -e .[dev]
pytest test/ -v
python -m build
```

或单独运行：`pytest test/test_crud_api.py -v`、`pytest test/test_workspace_consistency.py -v`、`pytest test/test_v015_features.py -v`、`pytest test/test_roundtrip.py -v`、`pytest test/test_api.py -v`、`pytest test/test_yaml_manager.py -v`。

## 从 proto 重新生成 `*_pb2.py`（维护者）

在包根目录下，需同时生成 `geometry.proto` 与 `vehicle_config.proto`（若系统无 `protoc`，可用 `grpcio-tools`）：

```bash
conda activate py310
python -m grpc_tools.protoc --proto_path=pb_calibration/protobuf --python_out=pb_calibration/protobuf \
  pb_calibration/protobuf/modules/message/common/geometry.proto \
  pb_calibration/protobuf/vehicle_config.proto
```

生成后请将 `vehicle_config_pb2.py` 中对 `geometry_pb2` 的导入改为相对导入：  
`from .modules.message.common import geometry_pb2 as ...`，以便打包安装后可解析。

## 依赖

- Python >= 3.10（推荐在 conda 环境 `py310` 下使用）
- PyYAML >= 6.0
- protobuf >= 4.21.0

## License

MIT
