ModelTranslator: 减面 UV seam 展开实现计划
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
9f326c9f80
commit
c24d7b9999
@@ -0,0 +1,403 @@
|
|||||||
|
# 减面工具 UV 展开优化实现计划
|
||||||
|
|
||||||
|
> **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:** `model_decimate.py` 的 UV 展开从 Smart UV Project 换成"锐边 seam + MINIMUM_STRETCH 展开 + 质量门自动回退",大幅减少 UV 碎岛。
|
||||||
|
|
||||||
|
**Architecture:** 纯逻辑(union-find 数岛、翻转/利用率计算)放 `bl_decimate.py` 顶部可单测区;Blender 侧新增 seam 展开路径与 bmesh 指标提取,质量门(翻转/重叠占比 >2%)触发时回退原 smart_project 路径。`MT_SUMMARY` 增加 `uv` 字段,CLI 增加 `--unwrap {seam,smart}`(默认 seam)。
|
||||||
|
|
||||||
|
**Tech Stack:** Python 3 标准库、Blender 5.0(bpy/bmesh)、unittest。
|
||||||
|
|
||||||
|
**设计文档:** `docs/superpowers/specs/2026-07-16-decimate-uv-seam-unwrap-design.md`
|
||||||
|
|
||||||
|
**重要提醒:**
|
||||||
|
- 仓库路径含 `#`(`D:\UD\AI\AIC#Project`),**bash 里所有路径必须加引号**
|
||||||
|
- Blender:`D:\tools\blender-5.0.0-windows-x64\blender.exe`
|
||||||
|
- 冒烟素材:`Tools/ModelTranslator/src/well1500.fbx`(149.9 万面)→ 5000 面
|
||||||
|
- 产物(`_low.fbx`)不入 git,只提交代码/文档
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
Tools/ModelTranslator/
|
||||||
|
bl_decimate.py # 修改:纯逻辑区加 3 函数+1 常量;Blender 区重构展开路径
|
||||||
|
model_decimate.py # 修改:--unwrap 参数 + UV 指标打印
|
||||||
|
tests/test_bl_decimate.py # 修改:追加纯逻辑测试
|
||||||
|
README.md # 修改:--unwrap 说明(Task 3)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: UV 指标纯逻辑(TDD)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `Tools/ModelTranslator/tests/test_bl_decimate.py`(文件末尾 `if __name__` 之前追加)
|
||||||
|
- Modify: `Tools/ModelTranslator/bl_decimate.py`(纯逻辑区,`within_tolerance` 之后、`# ---------------- 以下仅在 Blender 内执行` 之前)
|
||||||
|
|
||||||
|
- [ ] **Step 1: 追加失败测试**
|
||||||
|
|
||||||
|
在 `tests/test_bl_decimate.py` 的 `if __name__ == "__main__":` 之前插入:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class TestCountIslands(unittest.TestCase):
|
||||||
|
def test_no_faces(self):
|
||||||
|
self.assertEqual(bd.count_islands(0, []), 0)
|
||||||
|
|
||||||
|
def test_no_pairs_each_face_is_island(self):
|
||||||
|
self.assertEqual(bd.count_islands(3, []), 3)
|
||||||
|
|
||||||
|
def test_chain_merges_to_one(self):
|
||||||
|
self.assertEqual(bd.count_islands(4, [(0, 1), (1, 2), (2, 3)]), 1)
|
||||||
|
|
||||||
|
def test_two_groups(self):
|
||||||
|
self.assertEqual(bd.count_islands(5, [(0, 1), (3, 4)]), 3) # {0,1} {2} {3,4}
|
||||||
|
|
||||||
|
def test_duplicate_pairs_ok(self):
|
||||||
|
self.assertEqual(bd.count_islands(2, [(0, 1), (1, 0), (0, 1)]), 1)
|
||||||
|
|
||||||
|
|
||||||
|
class TestFlippedFraction(unittest.TestCase):
|
||||||
|
def test_empty_is_zero(self):
|
||||||
|
self.assertEqual(bd.flipped_fraction([]), 0.0)
|
||||||
|
|
||||||
|
def test_mixed(self):
|
||||||
|
self.assertAlmostEqual(bd.flipped_fraction([0.1, -0.2, 0.3, 0.4]), 0.25)
|
||||||
|
|
||||||
|
def test_all_positive(self):
|
||||||
|
self.assertEqual(bd.flipped_fraction([0.1, 0.2]), 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
class TestFillRatio(unittest.TestCase):
|
||||||
|
def test_sum(self):
|
||||||
|
self.assertAlmostEqual(bd.fill_ratio([0.2, 0.3]), 0.5)
|
||||||
|
|
||||||
|
def test_clamped_to_one(self):
|
||||||
|
self.assertEqual(bd.fill_ratio([0.8, 0.9]), 1.0)
|
||||||
|
|
||||||
|
def test_empty_is_zero(self):
|
||||||
|
self.assertEqual(bd.fill_ratio([]), 0.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: 运行确认失败**
|
||||||
|
|
||||||
|
Run: `cd "D:/UD/AI/AIC#Project/Tools/ModelTranslator" && python -m unittest tests.test_bl_decimate -v`
|
||||||
|
Expected: ERROR(`module 'bl_decimate' has no attribute 'count_islands'` 等)
|
||||||
|
|
||||||
|
- [ ] **Step 3: 写实现**
|
||||||
|
|
||||||
|
在 `bl_decimate.py` 的 `within_tolerance` 函数之后、`# ---------------- 以下仅在 Blender 内执行` 注释之前插入:
|
||||||
|
|
||||||
|
```python
|
||||||
|
UV_BAD_TOL = 0.02 # 质量门:UV 翻转/重叠面占比超此值则回退 smart_project
|
||||||
|
|
||||||
|
|
||||||
|
def count_islands(n_faces, adjacent_pairs):
|
||||||
|
"""union-find 数 UV 岛:n_faces 个面,adjacent_pairs 为 UV 连续的面索引对。"""
|
||||||
|
parent = list(range(n_faces))
|
||||||
|
|
||||||
|
def find(i):
|
||||||
|
while parent[i] != i:
|
||||||
|
parent[i] = parent[parent[i]]
|
||||||
|
i = parent[i]
|
||||||
|
return i
|
||||||
|
|
||||||
|
for a, b in adjacent_pairs:
|
||||||
|
ra, rb = find(a), find(b)
|
||||||
|
if ra != rb:
|
||||||
|
parent[ra] = rb
|
||||||
|
return len({find(i) for i in range(n_faces)})
|
||||||
|
|
||||||
|
|
||||||
|
def flipped_fraction(signed_areas):
|
||||||
|
"""UV 面签名面积为负(翻转)的占比。"""
|
||||||
|
if not signed_areas:
|
||||||
|
return 0.0
|
||||||
|
return sum(1 for a in signed_areas if a < 0) / float(len(signed_areas))
|
||||||
|
|
||||||
|
|
||||||
|
def fill_ratio(abs_areas):
|
||||||
|
"""UV 空间利用率:面积和(重叠会虚高,截断到 1.0)。"""
|
||||||
|
return min(1.0, sum(abs_areas))
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: 运行确认通过**
|
||||||
|
|
||||||
|
Run: `cd "D:/UD/AI/AIC#Project/Tools/ModelTranslator" && python -m unittest tests.test_bl_decimate -v`
|
||||||
|
Expected: 19 个测试全部 PASS(原 8 + 新 11)
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "D:/UD/AI/AIC#Project" && git add Tools/ModelTranslator/bl_decimate.py Tools/ModelTranslator/tests/test_bl_decimate.py && git commit -m "ModelTranslator: UV 岛数/翻转/利用率纯逻辑(TDD)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: seam 展开路径 + 质量门 + CLI 参数
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `Tools/ModelTranslator/bl_decimate.py`(Blender 区:替换 `_smart_unwrap`,`main()` 接线)
|
||||||
|
- Modify: `Tools/ModelTranslator/model_decimate.py`(`--unwrap` + UV 指标打印)
|
||||||
|
|
||||||
|
- [ ] **Step 1: bl_decimate.py 替换 `_smart_unwrap` 为新展开体系**
|
||||||
|
|
||||||
|
把现有 `_smart_unwrap` 函数(`def _smart_unwrap(obj):` 到其 `bpy.ops.object.mode_set(mode='OBJECT')` 整个函数)整体替换为:
|
||||||
|
|
||||||
|
```python
|
||||||
|
SEAM_ANGLE_DEG = 66.0 # 锐边阈值:两面夹角超此值标 seam
|
||||||
|
PACK_MARGIN = 0.002 # 岛间距 ≈ 2048 图 4px
|
||||||
|
|
||||||
|
|
||||||
|
def _clear_uv_layers(mesh):
|
||||||
|
while mesh.uv_layers:
|
||||||
|
mesh.uv_layers.remove(mesh.uv_layers[0])
|
||||||
|
|
||||||
|
|
||||||
|
def _unwrap_smart(obj):
|
||||||
|
"""Smart UV Project(原路径,也是质量门的回退路径)。"""
|
||||||
|
import bpy
|
||||||
|
import math
|
||||||
|
_clear_uv_layers(obj.data)
|
||||||
|
bpy.context.view_layer.objects.active = obj
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
bpy.ops.mesh.select_all(action='SELECT')
|
||||||
|
bpy.ops.uv.smart_project(angle_limit=math.radians(66.0), island_margin=PACK_MARGIN)
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
|
||||||
|
|
||||||
|
def _unwrap_seam(obj, warnings):
|
||||||
|
"""锐边标 seam -> MINIMUM_STRETCH/ANGLE_BASED 展开 -> pack。碎岛远少于投影式。"""
|
||||||
|
import bpy
|
||||||
|
import math
|
||||||
|
_clear_uv_layers(obj.data)
|
||||||
|
obj.data.uv_layers.new()
|
||||||
|
bpy.context.view_layer.objects.active = obj
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
# 锐边标 seam:接缝只落在硬边处,平滑区域保持整岛
|
||||||
|
bpy.ops.mesh.select_mode(type='EDGE')
|
||||||
|
bpy.ops.mesh.select_all(action='DESELECT')
|
||||||
|
bpy.ops.mesh.edges_select_sharp(sharpness=math.radians(SEAM_ANGLE_DEG))
|
||||||
|
bpy.ops.mesh.mark_seam(clear=False)
|
||||||
|
bpy.ops.mesh.select_all(action='SELECT')
|
||||||
|
try:
|
||||||
|
bpy.ops.uv.unwrap(method='MINIMUM_STRETCH', margin=PACK_MARGIN)
|
||||||
|
except TypeError: # 老版本无 SLIM 枚举
|
||||||
|
warnings.append("MINIMUM_STRETCH 不可用,展开改用 ANGLE_BASED")
|
||||||
|
bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=PACK_MARGIN)
|
||||||
|
try:
|
||||||
|
bpy.ops.uv.pack_islands(rotate=True, margin=PACK_MARGIN)
|
||||||
|
except RuntimeError: # headless 上下文不满足时靠 unwrap 自带打包
|
||||||
|
warnings.append("pack_islands 不可用,沿用 unwrap 自带布局")
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
|
||||||
|
|
||||||
|
def _collect_uv_metrics(obj):
|
||||||
|
"""bmesh 提取 UV 指标:岛数(UV 连续边并查集)、利用率、翻转占比、重叠占比。"""
|
||||||
|
import bmesh
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(obj.data)
|
||||||
|
uv = bm.loops.layers.uv.active
|
||||||
|
pairs = []
|
||||||
|
for e in bm.edges:
|
||||||
|
if len(e.link_faces) != 2:
|
||||||
|
continue
|
||||||
|
f1, f2 = e.link_faces
|
||||||
|
cont = True
|
||||||
|
for v in e.verts: # 两面在该边两端 UV 一致 => UV 连续(同岛)
|
||||||
|
l1 = next(l for l in f1.loops if l.vert == v)
|
||||||
|
l2 = next(l for l in f2.loops if l.vert == v)
|
||||||
|
if (l1[uv].uv - l2[uv].uv).length > 1e-6:
|
||||||
|
cont = False
|
||||||
|
break
|
||||||
|
if cont:
|
||||||
|
pairs.append((f1.index, f2.index))
|
||||||
|
signed = []
|
||||||
|
for f in bm.faces:
|
||||||
|
area = 0.0
|
||||||
|
loops = f.loops
|
||||||
|
for i in range(len(loops)):
|
||||||
|
a = loops[i][uv].uv
|
||||||
|
b = loops[(i + 1) % len(loops)][uv].uv
|
||||||
|
area += a.x * b.y - b.x * a.y
|
||||||
|
signed.append(area * 0.5)
|
||||||
|
n = len(bm.faces)
|
||||||
|
bm.free()
|
||||||
|
return {"islands": count_islands(n, pairs),
|
||||||
|
"fill": round(fill_ratio([abs(a) for a in signed]), 4),
|
||||||
|
"flipped": round(flipped_fraction(signed), 4),
|
||||||
|
"overlap": _overlap_fraction(obj)}
|
||||||
|
|
||||||
|
|
||||||
|
def _overlap_fraction(obj):
|
||||||
|
"""uv.select_overlap 统计重叠 UV 面占比;op 上下文不可用返回 None。"""
|
||||||
|
import bpy
|
||||||
|
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='DESELECT')
|
||||||
|
try:
|
||||||
|
bpy.ops.uv.select_overlap()
|
||||||
|
except RuntimeError:
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
polys = obj.data.polygons
|
||||||
|
if not len(polys):
|
||||||
|
return 0.0
|
||||||
|
return round(sum(1 for p in polys if p.select) / float(len(polys)), 4)
|
||||||
|
|
||||||
|
|
||||||
|
def _do_unwrap(obj, mode, warnings):
|
||||||
|
"""按模式展开;seam 质量不达标自动回退 smart。返回 uv 指标 dict(含 mode)。"""
|
||||||
|
if mode == "seam":
|
||||||
|
_unwrap_seam(obj, warnings)
|
||||||
|
m = _collect_uv_metrics(obj)
|
||||||
|
overlap = m["overlap"] or 0.0
|
||||||
|
if m["flipped"] <= UV_BAD_TOL and overlap <= UV_BAD_TOL:
|
||||||
|
m["mode"] = "seam"
|
||||||
|
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)
|
||||||
|
m = _collect_uv_metrics(obj)
|
||||||
|
m["mode"] = mode if mode == "smart_fallback" else "smart"
|
||||||
|
return m
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: main() 接线(argv 第 4 参 + uv 字段)**
|
||||||
|
|
||||||
|
`main()` 里两处修改。argv 解析行:
|
||||||
|
|
||||||
|
```python
|
||||||
|
src, out_fbx, target = argv[0], argv[1], int(argv[2])
|
||||||
|
```
|
||||||
|
|
||||||
|
改为:
|
||||||
|
|
||||||
|
```python
|
||||||
|
src, out_fbx, target = argv[0], argv[1], int(argv[2])
|
||||||
|
unwrap_mode = argv[3] if len(argv) > 3 else "seam"
|
||||||
|
```
|
||||||
|
|
||||||
|
`_smart_unwrap(obj)` 调用行改为:
|
||||||
|
|
||||||
|
```python
|
||||||
|
uv_info = _do_unwrap(obj, unwrap_mode, warnings)
|
||||||
|
```
|
||||||
|
|
||||||
|
`MT_SUMMARY` 的 dict 加 `"uv": uv_info`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
print("MT_SUMMARY " + json.dumps(
|
||||||
|
{"src": os.path.basename(src), "fbx": os.path.basename(out_fbx),
|
||||||
|
"tris_before": orig, "tris_after": cur, "target": target,
|
||||||
|
"uv": uv_info, "warnings": warnings}, ensure_ascii=False))
|
||||||
|
```
|
||||||
|
|
||||||
|
同时把文件顶部 docstring 第 1-2 行更新为:
|
||||||
|
|
||||||
|
```python
|
||||||
|
"""Blender 内运行:FBX 减面到指定三角面数 + UV 重展(锐边 seam 或 Smart UV Project)。
|
||||||
|
调用:blender -b --factory-startup --python bl_decimate.py -- <src.fbx> <out.fbx> <target_tris> [seam|smart]
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: model_decimate.py 加 --unwrap 与指标打印**
|
||||||
|
|
||||||
|
argparse 增加(`--blender` 行之前):
|
||||||
|
|
||||||
|
```python
|
||||||
|
ap.add_argument("--unwrap", choices=("seam", "smart"), default="seam",
|
||||||
|
help="UV 展开方式:seam=锐边接缝整岛展开(默认),smart=Smart UV Project")
|
||||||
|
```
|
||||||
|
|
||||||
|
`run_blender_script` 调用改为:
|
||||||
|
|
||||||
|
```python
|
||||||
|
s = run_blender_script(blender, "bl_decimate.py",
|
||||||
|
[args.input, out_fbx, str(args.tris), args.unwrap])
|
||||||
|
```
|
||||||
|
|
||||||
|
结果打印行之后、警告循环之前加:
|
||||||
|
|
||||||
|
```python
|
||||||
|
u = s["uv"]
|
||||||
|
overlap = "%.1f%%" % (u["overlap"] * 100) if u["overlap"] is not None else "n/a"
|
||||||
|
print(" UV: 模式=%s 岛数=%d 利用率=%.1f%% 翻转=%.1f%% 重叠=%s" %
|
||||||
|
(u["mode"], u["islands"], u["fill"] * 100, u["flipped"] * 100, overlap))
|
||||||
|
```
|
||||||
|
|
||||||
|
文件 docstring 第 1 行的"并 Smart UV 重展"改为"并重展 UV(默认锐边 seam 整岛展开)"。
|
||||||
|
|
||||||
|
- [ ] **Step 4: 语法检查 + 全部单测**
|
||||||
|
|
||||||
|
Run: `cd "D:/UD/AI/AIC#Project/Tools/ModelTranslator" && python -m py_compile bl_decimate.py model_decimate.py && python -m unittest discover -s tests -v`
|
||||||
|
Expected: 编译通过,40 个测试全部 PASS(29 原有 + 11 新增)
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "D:/UD/AI/AIC#Project" && git add Tools/ModelTranslator/bl_decimate.py Tools/ModelTranslator/model_decimate.py && git commit -m "ModelTranslator: 减面 UV 改锐边 seam 展开(质量门回退 smart,--unwrap 可选)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: 双模式冒烟对比 + README
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `Tools/ModelTranslator/README.md`
|
||||||
|
- 产物 `src/well1500_low.fbx`、`/tmp` 对比数据不入 git
|
||||||
|
|
||||||
|
- [ ] **Step 1: smart 模式跑基线**(timeout 300000)
|
||||||
|
|
||||||
|
Run: `cd "D:/UD/AI/AIC#Project/Tools/ModelTranslator" && python model_decimate.py src/well1500.fbx --tris 5000 --unwrap smart`
|
||||||
|
Expected: 正常完成,记录 `UV: 模式=smart 岛数=N_smart 利用率=F_smart ...`
|
||||||
|
|
||||||
|
- [ ] **Step 2: seam 模式(默认)跑对比**(timeout 300000)
|
||||||
|
|
||||||
|
Run: `cd "D:/UD/AI/AIC#Project/Tools/ModelTranslator" && python model_decimate.py src/well1500.fbx --tris 5000`
|
||||||
|
Expected: `UV: 模式=seam ...`,且满足验收标准:
|
||||||
|
- `岛数` 显著低于 smart 基线(至少降一个数量级或 <1/5)
|
||||||
|
- `利用率` ≥ smart 基线的 90%
|
||||||
|
- `翻转` ≤ 2%、`重叠` ≤ 2%(未触发回退警告)
|
||||||
|
|
||||||
|
不达标则按 systematic-debugging 排查(常见点:`edges_select_sharp` 的 sharpness 语义、`MINIMUM_STRETCH` 枚举名、`pack_islands`/`select_overlap` headless poll 失败——后两者已有降级路径,看警告判断),修复后重跑并把修复单独 commit。
|
||||||
|
|
||||||
|
- [ ] **Step 3: 用新低模快速复烘验证**(timeout 600000)
|
||||||
|
|
||||||
|
Run: `cd "D:/UD/AI/AIC#Project/Tools/ModelTranslator" && python model_bake.py src/well1500.fbx src/well1500_low.fbx --size 1024 --samples 16`
|
||||||
|
Expected: 5 张 PNG 正常产出、无 error;用下面命令确认 normal/color 非空:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "D:/UD/AI/AIC#Project/Tools/ModelTranslator" && "D:/tools/blender-5.0.0-windows-x64/blender.exe" -b --factory-startup --python-expr "
|
||||||
|
import bpy
|
||||||
|
for n in ('color', 'normal'):
|
||||||
|
img = bpy.data.images.load(r'D:\UD\AI\AIC#Project\Tools\ModelTranslator\out_bake\well1500\well1500_%s.png' % n)
|
||||||
|
uniq = len(set(round(v, 2) for v in img.pixels[:4000]))
|
||||||
|
print('CHECK', n, 'unique=', uniq)
|
||||||
|
"
|
||||||
|
```
|
||||||
|
Expected: 两图 unique 明显 >1
|
||||||
|
|
||||||
|
- [ ] **Step 4: README 更新**
|
||||||
|
|
||||||
|
`README.md` 中 model_decimate.py 说明条目("旧 UV 全删,Smart UV Project 重展"那句)改为:
|
||||||
|
|
||||||
|
```
|
||||||
|
- **model_decimate.py**:多 mesh 自动 join;三角化后 Decimate(collapse) 减到 `--tris`(±3%,最多 2 轮修正);旧 UV 全删后重展——默认锐边标 seam + MINIMUM_STRETCH 整岛展开(碎岛少、接缝落在硬边),质量不达标(翻转/重叠 >2%)自动回退 Smart UV Project,`--unwrap smart` 可强制旧行为;输出 `<名>_low.fbx`(`-o` 改目录),日志报 UV 岛数/利用率
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: 全部单测最后过一遍 + Commit**
|
||||||
|
|
||||||
|
Run: `cd "D:/UD/AI/AIC#Project/Tools/ModelTranslator" && python -m unittest discover -s tests -v`
|
||||||
|
Expected: 40/40 PASS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "D:/UD/AI/AIC#Project" && git add Tools/ModelTranslator/README.md && git commit -m "ModelTranslator: README 补 --unwrap 说明(well1500 双模式冒烟对比通过)"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 6: Unity 手动验证(用户操作)**
|
||||||
|
|
||||||
|
如需上 Unity 验证:`python model_translator.py out_bake/well1500/well1500.fbx` 后拷 `out/well1500/` 进 `Client/Assets/` 查看接缝表现。此步由用户完成。
|
||||||
Reference in New Issue
Block a user