From b0c1aeac5599f20f4cede0625248e6b22e9aa70b Mon Sep 17 00:00:00 2001 From: ud18010 Date: Mon, 20 Jul 2026 12:19:57 +0800 Subject: [PATCH] =?UTF-8?q?ModelTranslator:=20UVPM4=20=E6=8E=92=E5=B8=83?= =?UTF-8?q?=E9=9B=86=E6=88=90=E5=AE=9E=E7=8E=B0=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- ...26-07-20-uvpackmaster4-pack-integration.md | 385 ++++++++++++++++++ 1 file changed, 385 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-20-uvpackmaster4-pack-integration.md diff --git a/docs/superpowers/plans/2026-07-20-uvpackmaster4-pack-integration.md b/docs/superpowers/plans/2026-07-20-uvpackmaster4-pack-integration.md new file mode 100644 index 00000000..7df59968 --- /dev/null +++ b/docs/superpowers/plans/2026-07-20-uvpackmaster4-pack-integration.md @@ -0,0 +1,385 @@ +# 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 headless(`D:\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/ModelTranslator`(bash 中 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.py` 的 `TestUvGateOk` 类后追加: + +```python +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.py` 的 `uv_gate_ok` 函数之后(`count_islands` 之前)插入: + +```python +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: 全部 PASS(OK) + +- [ ] **Step 5: 提交** + +```bash +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`: + +```python +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.py` 在 `sys.path.insert(...)` 行之后、`def main():` 之前插入: + +```python +def seam_fallback_label(mode): + """回退产物标注:seam 系模式改 seam_fallback(保留 +uvpm 等后缀)。""" + if mode.startswith("seam"): + return "seam_fallback" + mode[len("seam"):] + return mode +``` + +同时把 `main()` 里的: + +```python + m = _do_unwrap(obj, "seam", warnings) # 内部自带 smart 回退 + if m["mode"] == "seam": + m["mode"] = "seam_fallback" +``` + +替换为: + +```python + 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 个测试全 PASS(OK) + +- [ ] **Step 5: 提交** + +```bash +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_unwrap`(bl_decimate.py) + +**Files:** +- Modify: `Tools/ModelTranslator/bl_decimate.py`(Blender 端区域) + +此任务代码只在 Blender 内运行,无法用系统 Python 单测;由 Task 4/5 冒烟验证。 + +- [ ] **Step 1: 加常量** + +`bl_decimate.py` 中 `SEAM_ANGLE_DEG`/`PACK_MARGIN` 常量块(约 99-100 行)扩展为: + +```python +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` 函数之前插入: + +```python +_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` 整体替换为: + +```python +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: 提交** + +```bash +git add Tools/ModelTranslator/bl_decimate.py +git commit -m "ModelTranslator: 展开路径接入 UVPM4 排布——保底+增强,失败回退内置 pack" +``` + +--- + +### Task 4: 冒烟——seam 路径(store.fbx) + +- [ ] **Step 1: 跑 seam 展开减面** + +```bash +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: 清理冒烟产物** + +```bash +rm -rf /tmp/mt_uvpm_smoke +``` + +--- + +### Task 5: 冒烟——rizom 默认路径的回退链(本机 Rizom 未授权) + +- [ ] **Step 1: 跑默认(rizom)路径** + +```bash +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: 清理并提交(若前两个任务后有未提交改动则此处兜底)** + +```bash +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 条目之后加: + +```markdown +- 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: 提交** + +```bash +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 收尾。