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

386 lines
14 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.
# 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: 全部 PASSOK
- [ ] **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 个测试全 PASSOK
- [ ] **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 收尾。