Files
AIC-Project/docs/superpowers/plans/2026-07-20-uvpackmaster4-pack-integration.md
2026-07-20 12:19:57 +08:00

14 KiB
Raw Permalink Blame History

UVPackmaster 4 排布集成实现计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: ModelTranslator 的 Blender 展开路径(seam/smart)在内置 pack_islands 保底排布后,用 UVPackmaster 4 重排提升 UV 利用率;UVPM4 不可用时无感回退现状。

Architecture: 全部实质改动在 bl_decimate.py:新增 _uvpm_repack()(GPU 补丁 → 运行时启用扩展 → 设像素边距 → 执行 pack,任何失败记警告返 False),在 _do_unwrap 两个展开分支后调用,成功时 mode 加 +uvpm 后缀。bl_uvgate.py 的回退标注改为前缀匹配以兼容新 mode。设计文档:docs/superpowers/specs/2026-07-20-uvpackmaster4-pack-design.md

Tech Stack: Python 3(系统 Python 跑单测)、Blender 5.0 headlessD:\tools\blender-5.0.0-windows-x64\blender.exe)、UVPackmaster 4 扩展(bl_ext.user_default.uvpackmaster4,操作符 uvpackmaster4.pack)。

重要环境事实(spike 已验证,2026-07-20):

  • UVPM4 导入期执行 gpu.shader.from_builtin('UNIFORM_COLOR'),后台模式抛 SystemError——必须先补丁
  • addon_utils.enable(模块名, default_set=True) 才会建 preferences.addons 条目(UVPM4 register 依赖);--factory-startup 下不写盘
  • 引擎经注册表 HKLM\Software\UVPackmaster\Engine4InstallPath 自动发现,无需配置
  • pack 返回 {'FINISHED', 'PASS_THROUGH'},非交互调用同步阻塞完成
  • 仓库路径含 #bash 中所有路径必须加引号

文件结构

文件 职责 操作
Tools/ModelTranslator/bl_decimate.py 纯逻辑 uvpm_mode_label + Blender 端 _uvpm_enable/_uvpm_repack,接线 _do_unwrap 修改
Tools/ModelTranslator/bl_uvgate.py 纯逻辑 seam_fallback_label,替换 main() 里的等值判断 修改
Tools/ModelTranslator/tests/test_bl_decimate.py uvpm_mode_label 单测 修改
Tools/ModelTranslator/tests/test_bl_uvgate.py seam_fallback_label 单测 新建
Tools/ModelTranslator/README.md 依赖节 + 减面条目补 UVPM4 说明 修改

所有命令的工作目录:D:/UD/AI/AIC#Project/Tools/ModelTranslatorbash 中 cd 时路径加引号)。


Task 1: uvpm_mode_label 纯函数(bl_decimate.py

Files:

  • Modify: Tools/ModelTranslator/bl_decimate.py(纯逻辑区,uv_gate_ok 之后)

  • Test: Tools/ModelTranslator/tests/test_bl_decimate.py

  • Step 1: 写失败测试

tests/test_bl_decimate.pyTestUvGateOk 类后追加:

class TestUvpmModeLabel(unittest.TestCase):
    def test_applied_appends_suffix(self):
        self.assertEqual(bd.uvpm_mode_label("seam", True), "seam+uvpm")
        self.assertEqual(bd.uvpm_mode_label("smart", True), "smart+uvpm")
        self.assertEqual(bd.uvpm_mode_label("smart_fallback", True), "smart_fallback+uvpm")

    def test_not_applied_keeps_base(self):
        self.assertEqual(bd.uvpm_mode_label("seam", False), "seam")
        self.assertEqual(bd.uvpm_mode_label("smart_fallback", False), "smart_fallback")
  • Step 2: 跑测试确认失败

Run: python -m unittest tests.test_bl_decimate -v 2>&1 | tail -5 Expected: ERROR — module 'bl_decimate' has no attribute 'uvpm_mode_label'

  • Step 3: 最小实现

bl_decimate.pyuv_gate_ok 函数之后(count_islands 之前)插入:

def uvpm_mode_label(base, applied):
    """UV 模式标注:UVPackmaster 排布生效时加 +uvpm 后缀。"""
    return base + "+uvpm" if applied else base
  • Step 4: 跑测试确认通过

Run: python -m unittest tests.test_bl_decimate -v 2>&1 | tail -5 Expected: 全部 PASSOK

  • Step 5: 提交
git add Tools/ModelTranslator/bl_decimate.py Tools/ModelTranslator/tests/test_bl_decimate.py
git commit -m "ModelTranslator: uvpm_mode_label——UVPM4 排布生效的 UV 模式标注"

Task 2: seam_fallback_label 纯函数(bl_uvgate.py

Files:

  • Modify: Tools/ModelTranslator/bl_uvgate.py
  • Create: Tools/ModelTranslator/tests/test_bl_uvgate.py

背景:bl_uvgate.main() 目前用 if m["mode"] == "seam": m["mode"] = "seam_fallback" 标注回退产物;mode 带上 +uvpm 后缀后等值判断失效,改为前缀匹配。

  • Step 1: 写失败测试

新建 tests/test_bl_uvgate.py

import os
import sys
import unittest

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import bl_uvgate as bg


class TestSeamFallbackLabel(unittest.TestCase):
    def test_plain_seam(self):
        self.assertEqual(bg.seam_fallback_label("seam"), "seam_fallback")

    def test_seam_with_uvpm_suffix(self):
        self.assertEqual(bg.seam_fallback_label("seam+uvpm"), "seam_fallback+uvpm")

    def test_smart_fallback_untouched(self):
        self.assertEqual(bg.seam_fallback_label("smart_fallback"), "smart_fallback")
        self.assertEqual(bg.seam_fallback_label("smart_fallback+uvpm"),
                         "smart_fallback+uvpm")


if __name__ == "__main__":
    unittest.main()
  • Step 2: 跑测试确认失败

Run: python -m unittest tests.test_bl_uvgate -v 2>&1 | tail -5 Expected: ERROR — module 'bl_uvgate' has no attribute 'seam_fallback_label'

  • Step 3: 最小实现

bl_uvgate.pysys.path.insert(...) 行之后、def main(): 之前插入:

def seam_fallback_label(mode):
    """回退产物标注:seam 系模式改 seam_fallback(保留 +uvpm 等后缀)。"""
    if mode.startswith("seam"):
        return "seam_fallback" + mode[len("seam"):]
    return mode

同时把 main() 里的:

    m = _do_unwrap(obj, "seam", warnings)   # 内部自带 smart 回退
    if m["mode"] == "seam":
        m["mode"] = "seam_fallback"

替换为:

    m = _do_unwrap(obj, "seam", warnings)   # 内部自带 smart 回退
    m["mode"] = seam_fallback_label(m["mode"])
  • Step 4: 跑测试确认通过

Run: python -m unittest tests.test_bl_uvgate -v 2>&1 | tail -5 Expected: 3 个测试全 PASSOK

  • Step 5: 提交
git add Tools/ModelTranslator/bl_uvgate.py Tools/ModelTranslator/tests/test_bl_uvgate.py
git commit -m "ModelTranslator: uvgate 回退标注改前缀匹配——兼容 +uvpm 后缀"

Task 3: _uvpm_enable / _uvpm_repack + 接线 _do_unwrapbl_decimate.py

Files:

  • Modify: Tools/ModelTranslator/bl_decimate.pyBlender 端区域)

此任务代码只在 Blender 内运行,无法用系统 Python 单测;由 Task 4/5 冒烟验证。

  • Step 1: 加常量

bl_decimate.pySEAM_ANGLE_DEG/PACK_MARGIN 常量块(约 99-100 行)扩展为:

SEAM_ANGLE_DEG = 66.0   # 锐边阈值:两面夹角超此值标 seam
PACK_MARGIN = 0.002     # 岛间距 ≈ 2048 图 4px
UVPM_EXT = "bl_ext.user_default.uvpackmaster4"   # UVPackmaster 4 扩展模块名
UVPM_PIXEL_MARGIN = 4   # UVPM 岛间距(像素),与 PACK_MARGIN * UVPM_TEX_SIZE 同口径
UVPM_TEX_SIZE = 2048
  • Step 2: 实现 _uvpm_enable_uvpm_repack

_do_unwrap 函数之前插入:

_uvpm_failed = False    # 进程内失败一次即不再重试(避免重复警告与启动开销)


def _uvpm_enable():
    """启用 UVPM4 扩展(幂等)。headless 下先补丁 GPU shader 创建——
    UVPM4 导入期为视口覆盖层建 shader,后台模式无 GPU 绘图会 SystemError
    覆盖层仅交互用,pack 不受影响。引擎路径经注册表自动发现。"""
    import bpy
    import addon_utils
    if bpy.app.background:
        import gpu
        orig = gpu.shader.from_builtin
        if not getattr(orig, "_mt_safe", False):
            def _safe(*a, **k):
                try:
                    return orig(*a, **k)
                except SystemError:
                    return None
            _safe._mt_safe = True
            gpu.shader.from_builtin = _safe
    # default_set=True 才建 preferences.addons 条目(UVPM4 register 依赖);
    # factory-startup 关闭偏好自动保存,不会写盘
    if addon_utils.enable(UVPM_EXT, default_set=True) is None:
        raise RuntimeError("扩展 %s 启用失败(未安装或版本不兼容)" % UVPM_EXT)


def _uvpm_repack(obj, warnings):
    """UVPM4 重排当前 UV 布局,成功返回 True;任何失败记警告返回 False,
    保底布局(pack_islands/smart_project)原样保留。"""
    global _uvpm_failed
    if _uvpm_failed:
        return False
    import bpy
    try:
        _uvpm_enable()
        p = bpy.context.scene.uvpm4_props.default_main_props
        p.pixel_margin_enable = True
        p.pixel_margin = UVPM_PIXEL_MARGIN
        p.pixel_margin_tex_size = UVPM_TEX_SIZE
        bpy.context.scene.tool_settings.use_uv_select_sync = True
        bpy.context.view_layer.objects.active = obj
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_all(action='SELECT')
        try:
            ret = bpy.ops.uvpackmaster4.pack(mode_id='pack.single_tile',
                                             pack_op_type='0')
        finally:
            bpy.ops.object.mode_set(mode='OBJECT')
        if 'FINISHED' not in ret:
            raise RuntimeError("pack 返回 %s" % sorted(ret))
        return True
    except Exception as e:
        _uvpm_failed = True
        warnings.append("UVPackmaster 排布不可用(%s),沿用内置 pack 布局" % e)
        return False
  • Step 3: 接线 _do_unwrap

把现有 _do_unwrap 整体替换为:

def _do_unwrap(obj, mode, warnings):
    """按模式展开(排布默认 UVPM4 增强,失败保底内置 pack);
    seam 质量不达标自动回退 smart。返回 uv 指标 dict(含 mode)。"""
    if mode == "seam":
        _unwrap_seam(obj, warnings)
        uvpm = _uvpm_repack(obj, warnings)
        m = _collect_uv_metrics(obj)
        if uv_gate_ok(m["flipped"], m["overlap"]):
            m["mode"] = uvpm_mode_label("seam", uvpm)
            return m
        warnings.append("seam 展开质量不达标(翻转 %.1f%% 重叠 %s),回退 smart_project"
                        % (m["flipped"] * 100,
                           "%.1f%%" % (m["overlap"] * 100) if m["overlap"] is not None else "未知"))
        mode = "smart_fallback"
    _unwrap_smart(obj)
    uvpm = _uvpm_repack(obj, warnings)
    m = _collect_uv_metrics(obj)
    m["mode"] = uvpm_mode_label(mode if mode == "smart_fallback" else "smart", uvpm)
    return m
  • Step 4: 回归单测(纯逻辑未破坏)

Run: python -m unittest tests.test_bl_decimate tests.test_bl_uvgate 2>&1 | tail -3 Expected: OK

  • Step 5: 提交
git add Tools/ModelTranslator/bl_decimate.py
git commit -m "ModelTranslator: 展开路径接入 UVPM4 排布——保底+增强,失败回退内置 pack"

Task 4: 冒烟——seam 路径(store.fbx

  • Step 1: 跑 seam 展开减面
cd "D:/UD/AI/AIC#Project/Tools/ModelTranslator"
python model_decimate.py src/store.fbx --tris 3000 --unwrap seam -o /tmp/mt_uvpm_smoke

Expected 输出(数值允许小幅浮动):

  • UV: 模式=seam+uvpm

  • 利用率=65% 上下(现状约 38.5%,显著提升即通过)

  • 翻转=0.0%,重叠 ≤8%(质量门内)

  • UVPackmaster 排布不可用 警告

  • Step 2: 若失败按 systematic-debugging 排查

常见故障对照:

  • 扩展 ... 启用失败 → 确认 C:/Users/Administrator/AppData/Roaming/Blender Foundation/Blender/5.0/extensions/user_default/uvpackmaster4 存在

  • SystemError GPU → 确认 _uvpm_enable 的补丁在 addon_utils.enable 之前执行

  • KeyError preferences.addons → 确认 default_set=True

  • Step 3: 清理冒烟产物

rm -rf /tmp/mt_uvpm_smoke

Task 5: 冒烟——rizom 默认路径的回退链(本机 Rizom 未授权)

  • Step 1: 跑默认(rizom)路径
cd "D:/UD/AI/AIC#Project/Tools/ModelTranslator"
python model_decimate.py src/store.fbx --tris 3000 -o /tmp/mt_uvpm_smoke2

本机 RizomUV 未授权:Pack 空结果守卫报 RizomUV 不可用bl_uvgate 发现无 UV 就地 seam 重展。 Expected

  • 警告含 RizomUV 不可用(或 RizomUV 异常)与 rizom 输出无 UV,回退 seam
  • UV: 模式=seam_fallback+uvpm
  • 利用率与 Task 4 同量级(~65%)

注意:此步会短暂弹 RizomUV 窗口并等其超时/报错,耗时比 Task 4 长属正常。

  • Step 2: 清理并提交(若前两个任务后有未提交改动则此处兜底)
rm -rf /tmp/mt_uvpm_smoke2
git status --short

Expected: 工作区无本功能相关未提交文件(README 留给 Task 6)。


Task 6: README 文档更新

Files:

  • Modify: Tools/ModelTranslator/README.md

  • Step 1: 依赖节加一行

## 依赖 列表 RizomUV 条目之后加:

- UVPackmaster 4(可选,UV 排布质量更好:Blender 扩展 `uvpackmaster4` + 独立引擎,引擎路径经注册表自动发现;已装则 Blender 展开路径(seam/smart)的排布自动启用,未装自动回退内置 pack_islands
  • Step 2: 减面条目补说明

model_decimate.py 条目中 (再不达标回退 Smart UV Project 之后插入:

Blender 展开路径(seam/smart,含 rizom 失败回退)的排布默认用 UVPackmaster 4 重排(利用率显著提升,日志模式带 `+uvpm` 后缀,4px@2048 像素边距与烘焙 padding 同口径),UVPM4 不可用自动沿用内置 pack_islands 布局
  • Step 3: 提交
git add Tools/ModelTranslator/README.md
git commit -m "ModelTranslator: README 补 UVPackmaster 4 排布说明"

完成标准

  1. python -m unittest tests.test_bl_decimate tests.test_bl_uvgate 全绿
  2. Task 4 冒烟:模式=seam+uvpm,利用率较 38.5% 显著提升
  3. Task 5 冒烟:回退链 模式=seam_fallback+uvpm
  4. README 已更新,全部改动已提交

完成后按 superpowers:verification-before-completion 复核证据,再按 superpowers:finishing-a-development-branch 收尾。