Files
AIC-Project/docs/superpowers/specs/2026-07-21-uvpm-packing-utilization-design.md
T

5.9 KiB
Raw Blame History

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_margin4px@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_margin4px)与 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_enablerotation_steprotation_step_valueheuristic_enableheuristic_search_timeheuristic_max_wait_time 等)。
  • 在测试网格(UV 球 + smart_project)上开启这些属性跑一次 uvpackmaster4.pack,确认 headless 不报错、返回 FINISHED
  • 产出:确切属性名与合理取值,供第 2 节定稿;脚本用完删除、不入库。

2. _uvpm_repack 开启旋转 + 启发式

新增防御式辅助与常量,在 _uvpm_repackbl_decimate.py:294-305)的 pixel_margin 设置之后、pack 调用之前设置属性:

# 常量(值以探针结论为准,示意)
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 内(属性名以探针为准):

_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 Exceptionbl_decimate.py:293-314)内——但用 _uvpm_sethasattr 守卫,使单个属性名不匹配只跳过该项、不抛异常,从而不会触发整体失败回退(那会丢掉 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_sethasattr 守卫是关键:防止版本差异把整个 UVPM 排布拖垮。
  • 若探针发现启发式需要非 '0'pack_op_type 或专门 mode_id,在计划中据实调整并在 README 注明。