ModelTranslator: UVPM 排布利用率优化设计文档(旋转+启发式)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
ud18010
2026-07-21 15:11:02 +08:00
co-authored by Claude Opus 4.8
parent b37a2946e4
commit f117886091
@@ -0,0 +1,107 @@
# UVPM 排布利用率优化:开启旋转 + 启发式搜索
日期:2026-07-21
范围:`Tools/ModelTranslator/bl_decimate.py``_uvpm_repack`)、`README.md`
## 背景与动机
减岛优化后 gargoyle 3000 面走 seam@45、390 岛,但 UV 利用率仅 **52.3%**——约 48% 为空白(烘焙时填黑,用户观感为"错误的黑色块")。
根因定位(已查 `_uvpm_repack` `bl_decimate.py:286-314`):
- 当前 UVPM 调用只设 `pixel_margin`4px@2048),**旋转/启发式搜索全用 UVPM4 默认值**:`bpy.ops.uvpackmaster4.pack(mode_id='pack.single_tile', pack_op_type='0')`
- gargoyle 减面后 UV 由大量**又长又斜的薄条岛**(尖刺/薄翅缘几何)主导。薄斜条在缺乏精细旋转对齐时外接矩形浪费极大——这是 packer 效率的天敌。
- UVPM 的旋转 + 启发式迭代搜索正是为此类不规则岛设计的,当前未启用。
用户选择:**只调 UVPM 排布**(不碰边距/烘焙),启发式耗时可接受(离线工具,设时间上限)。
## 目标
开启 UVPM4 的旋转与启发式搜索,把薄条岛塞得更紧,**明显提升 gargoyle 3000 的 UV 利用率**,且不退化其他指标(岛数、翻转、重叠不变差)。
## 非目标(YAGNI
- 不改像素边距 `pixel_margin`4px)与 `model_bake` 的烘焙 padding(避免渗色,用户明确排除)。
- 不改展开/减岛/清理逻辑(`_do_unwrap``_clean_mesh``--max-overlap` 均不动)。
- 不动 `average_islands_scale`(纹素均匀,保留)。
- 不追求硬性利用率数字(薄条几何天然受限),只求"明显上升"。
## 设计
### 1. UVPM4 属性探针(实现首步 spike)
因 UVPM4 属性名/默认值随版本可能不同,先用一次性脚本确认,避免猜 API:
- 启用 UVPM4(复用 `_uvpm_enable` 的 headless GPU 补丁思路)。
- 枚举 `bpy.context.scene.uvpm4_props.default_main_props` 中旋转/启发式相关属性:名字、默认值、类型/取值范围(候选:`rotation_enable``rotation_step``rotation_step_value``heuristic_enable``heuristic_search_time``heuristic_max_wait_time` 等)。
- 在测试网格(UV 球 + smart_project)上开启这些属性跑一次 `uvpackmaster4.pack`,确认 headless 不报错、返回 `FINISHED`
- 产出:确切属性名与合理取值,供第 2 节定稿;脚本用完删除、不入库。
### 2. `_uvpm_repack` 开启旋转 + 启发式
新增防御式辅助与常量,在 `_uvpm_repack``bl_decimate.py:294-305`)的 `pixel_margin` 设置之后、`pack` 调用之前设置属性:
```python
# 常量(值以探针结论为准,示意)
UVPM_ROTATION_STEP = <探针定> # 旋转步进(度),更细利于薄条对齐
UVPM_HEURISTIC_TIME = <探针定> # 启发式搜索秒数上限(如 510
def _uvpm_set(p, name, value, warnings):
"""防御式设 UVPM 属性:属性存在才设,否则记警告跳过——
名字不匹配(版本差异)时降级为按原 margin 跑 UVPM,不丢整个排布。"""
if hasattr(p, name):
setattr(p, name, value)
else:
warnings.append("UVPM 属性 %s 不存在,跳过" % name)
```
`_uvpm_repack` 内(属性名以探针为准):
```python
_uvpm_set(p, "rotation_enable", True, warnings)
_uvpm_set(p, "rotation_step", UVPM_ROTATION_STEP, warnings)
_uvpm_set(p, "heuristic_enable", True, warnings)
_uvpm_set(p, "heuristic_search_time", UVPM_HEURISTIC_TIME, warnings)
```
- 属性设置在现有 `try/except Exception``bl_decimate.py:293-314`)内——但用 `_uvpm_set``hasattr` 守卫,使**单个属性名不匹配只跳过该项、不抛异常**,从而不会触发整体失败回退(那会丢掉 UVPM 全部排布)。
- 旋转后的 UV 对烘焙无影响(烘焙按 UV 原样采样)。
- 边距、`pack` 调用签名(`mode_id`/`pack_op_type`)保持不变,除非探针发现启发式必须换 `pack_op_type`(若如此,在第 2 节据实调整并记录)。
### 3. 数据流(不变,仅 UVPM 内部行为增强)
```
… → _do_unwrap → 选定 UV → _uvpm_repack(现在:margin + 旋转 + 启发式搜索)→ 抽 UV 几何 → export
```
## 错误处理
| 失败点 | 处理 |
|---|---|
| UVPM 某属性名不存在(版本差异) | `_uvpm_set` 记警告、跳过该属性,其余照设、pack 照跑 |
| UVPM 启用/pack 整体失败 | 现有 `_uvpm_repack` try/except:记警告、`_uvpm_failed=True`、回退内置 pack(行为不变) |
| 启发式耗时过长 | 由 `UVPM_HEURISTIC_TIME` 上限约束 |
原则:排布增强单点失败降级,不影响主流程与 FBX 产出。
## 测试与验证
1. **现有单测不回归**`python -m unittest tests.test_bl_decimate tests.test_uv_preview tests.test_unity_assets`(本改动无纯函数,主要确认无破坏)。
2. **端到端(主验收)**`python model_decimate.py src/gargoyle.fbx --tris 3000 --max-overlap 0.15`,对比基线(seam@45 / 390 岛 / **利用率 52.3%** / 翻转 0% / 重叠 13.3%):
- 利用率**明显上升**
- 岛数不变(排布不改岛数)、翻转仍 0%、重叠仍 ≤ 门限;
- 记录 pack 耗时增量;
- Read 观察图确认薄条更紧、黑块变少。
3. **回归**`python model_decimate.py src/well1500.fbx --tris 5000`(正常走 seam@66 的模型)确认排布增强不破坏,流程不崩、产物正常。
## 验收标准
- gargoyle 3000`--max-overlap 0.15`):UV 利用率明显高于 52.3%;岛数/翻转/重叠不退化。
- 属性名不匹配时降级不崩、仍出 UVPM 排布(或内置兜底)。
- well1500 回归正常。
- 现有单测全绿。
## 实现注意
- 属性名/取值一律以第 1 节探针结论为准;spike 未确认前不写死具体名。
- `_uvpm_set``hasattr` 守卫是关键:防止版本差异把整个 UVPM 排布拖垮。
- 若探针发现启发式需要非 `'0'``pack_op_type` 或专门 `mode_id`,在计划中据实调整并在 README 注明。