Files
AIC-Project/docs/superpowers/specs/2026-07-21-uv-island-reduction-design.md

110 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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% 修正逻辑照常把面数拉到目标,无影响。