ModelTranslator: UV 减岛优化设计文档(--max-overlap + 网格清理)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
ud18010
2026-07-21 12:20:26 +08:00
co-authored by Claude Opus 4.8
parent c7e10af0d2
commit b21925be58
@@ -0,0 +1,109 @@
# UV 减岛优化:--max-overlap 参数 + 减面前网格清理
日期:2026-07-21
范围:`Tools/ModelTranslator/bl_decimate.py``Tools/ModelTranslator/model_decimate.py`
## 背景与动机
减面工具的 seam 角度扫描(66→55→45→35°)在质量门(翻转 ≤2%、重叠 ≤8%)失败时下探角度抢救。角度越低 → seam 越多 → UV 岛越碎。实测 gargoyle 3000 面走到 `seam@35`,产生 **686 个碎岛、利用率仅 38.2%**
关键事实:
- **UVPackmaster 只排布、不合并岛**,无法减少岛数。
- 低利用率是**岛数过多的症状**——每岛一圈 padding,岛越碎、总周长越大、留白越多。
- 两个问题同源同解:**减少 seam/岛数**(展开阶段),利用率随之回升。
用户诉求:**岛数优先**,愿意用少量重叠/利用率换更少的岛。
## 目标
1. 让更高的 seam 角度(岛更少)能通过质量门被选中——通过**可运行时覆盖的重叠容忍**(新增 `--max-overlap`)。翻转门限保持严格。
2. 减面前清理源网格(焊接重合点 + 去退化面),给 collapse 更干净的流形,从源头减少被迫加的 seam。
3. 默认行为不变;减岛为显式开启。
## 非目标(YAGNI
- 不改 UVPM 排布逻辑(它减不了岛)。
- 不放宽翻转门限 `UV_FLIP_TOL`(翻转无法靠排布修复)。
- 不改减面后低模(清理只在减面前的高模上做,避免改变目标面数)。
- 不改 `model_bake.py` / `model_translator.py` / `uv_preview.py`
- 不做减面后的碎岛后处理/岛合并(无通用可靠算子)。
## 设计
### 1. `--max-overlap` 参数穿透
重叠容忍从模块常量升级为可运行时覆盖,翻转门限不动。
**`bl_decimate.py`**
- `uv_gate_ok(flipped, overlap, overlap_tol=UV_OVERLAP_TOL)``return flipped <= UV_FLIP_TOL and (overlap or 0.0) <= overlap_tol`。加默认参数,现有调用与测试(`uv_gate_ok(0.0, 0.0)` 等)不受影响。
- `pick_best_candidate(candidates, overlap_tol=UV_OVERLAP_TOL)`:把 `overlap_tol` 透传给 `uv_gate_ok`
- `_do_unwrap(obj, mode, warnings, overlap_tol=UV_OVERLAP_TOL)`:扫描循环内 `uv_gate_ok(m["flipped"], m["overlap"], overlap_tol)``pick_best_candidate(candidates, overlap_tol)` 均用它。
- `main()`:argv 增加第 5 个可选位置参数 max_overlap;解析 `overlap_tol = float(argv[4]) if len(argv) > 4 and argv[4] else UV_OVERLAP_TOL`;传入 `_do_unwrap`
**`model_decimate.py`**
- 新增 `--max-overlap``type=float`,默认 `None`)。
- 传参:`run_blender_script(..., [args.input, out_fbx, str(args.tris), args.unwrap, "" if args.max_overlap is None else str(args.max_overlap)])`(第 5 位;None → 空串,Blender 侧回落到内置默认)。
- 校验:`--max-overlap` 若给定需在 (0, 1] 内,否则 `ap.error`
日志与 `mode` 标注不变。放宽后,若 `seam@55` 重叠 12% ≤ 15%,即被选中,岛数远少于 `seam@35`
### 2. 减面前网格清理
**`bl_decimate.py`** 新增 `_clean_mesh(obj, warnings)`,在 `main()``obj = join_meshes(meshes)` 之后、`_triangulate(obj)` 之前调用:
```
- 进入 EDIT、全选
- remove_doubles(threshold=weld) # 焊接重合点
- dissolve_degenerate() # 去零面积/退化面
- 回 OBJECT
```
- 焊接阈值**相对局部包围盒对角线**:新常量 `REL_WELD = 1e-4`(对角线的 0.01%)。用 `obj.bound_box`(局部坐标 8 角)算对角线 `diag``weld = diag * REL_WELD`。避免 cm/m 单位差异导致绝对阈值焊过头或焊不动。`diag <= 0`(退化输入)时跳过焊接、记警告。
- 两步各自 try/except`RuntimeError`):失败记警告、跳过、不中断。
- 清理在三角化前做(remove_doubles/dissolve_degenerate 对任意网格有效;更干净的流形让后续三角化+collapse 少产狭长三角)。
### 3. 数据流
```
FBX → join → _clean_mesh(焊接+去退化) → triangulate → decimate(±3%)
→ _do_unwrap(overlap_tol=max_overlap or 8%):
seam 66→55→45→35(各 unwrap+stretch+均衡,用 overlap_tol 判门)
首个过门即选(角度高→岛少);pick_best_candidate(overlap_tol) 兜底择优
全失败 → smart
→ UVPM 一次 → 抽 UV 几何 sidecar → export fbx
→ MT_SUMMARY(json)
```
## 错误处理
| 失败点 | 处理 |
|---|---|
| `remove_doubles` / `dissolve_degenerate` 不可用 | try/except RuntimeError 记警告,跳过该步 |
| 包围盒对角线 ≤ 0 | 跳过焊接,记警告 |
| `--max-overlap` 越界 (0,1] | `model_decimate``ap.error` 提前拒绝 |
| max_overlap 空/缺省 | Blender 侧回落 `UV_OVERLAP_TOL`(默认 8%),行为不变 |
原则:清理与门限放宽均为可选增强,单点失败降级不中断,FBX 必产出。
## 测试与验证
1. **纯函数 TDD**`tests/test_bl_decimate.py`):
- `uv_gate_ok``overlap_tol`:默认值下同现状;显式 `overlap_tol=0.15` 时 overlap=0.12 通过、0.16 不过;翻转仍按 `UV_FLIP_TOL` 严格(overlap_tol 放宽不影响翻转判定)。
- `pick_best_candidate``overlap_tol`:放宽后原本不过门(overlap 介于 8%~15%)的候选变为过门并可被选中;无参时同现状。
2. **现有单测不回归**`python -m unittest tests.test_bl_decimate tests.test_uv_preview tests.test_unity_assets`
3. **端到端**
- `python model_decimate.py src/gargoyle.fbx --tris 3000 --max-overlap 0.15`:对比基线(686 岛 / 38.2% / seam@35 / 重叠 6.3%)——期望走更高角度、**岛数明显下降、利用率上升**;产出 FBX + UV 观察图。
- `python model_decimate.py src/gargoyle.fbx --tris 3000`(无 `--max-overlap`):确认默认行为不回归(仍 8% 门限)。注意网格清理默认开启,默认跑的指标可能与历史基线略有不同——如变化记录并说明。
## 验收标准
- `--max-overlap 0.15` 下 gargoyle 3000UV 岛数显著低于 686 且利用率高于 38.2%;`mode` 反映实际生效的更高角度(如 `seam@55`/`seam@45`)。
- 默认路径(无 `--max-overlap`):翻转门限严格不变;流程不崩,FBX + 观察图照常产出。
- 网格清理任一算子不可用时降级不中断。
- 现有单测全绿;新增纯函数测试通过。
## 实现注意
- `_do_unwrap`/`pick_best_candidate`/`uv_gate_ok` 的新参数一律 keyword-default,保持向后兼容与现有测试不改。
- argv 第 5 位为空串时必须回落默认,不能 `float("")` 崩溃。
- `_clean_mesh` 改变面数(去退化会略减面),但发生在 decimate 之前,decimate 的 ±3% 修正逻辑照常把面数拉到目标,无影响。