ModelTranslator: 质量门抽纯函数 uv_gate_ok + 减面 none 模式(rizom 管线前置)

This commit is contained in:
ud18010
2026-07-17 16:23:14 +08:00
parent 26d91b0e7d
commit 24084a6675
2 changed files with 31 additions and 4 deletions
+14 -4
View File
@@ -1,5 +1,5 @@
"""Blender 内运行:FBX 减面到指定三角面数 + UV 重展(锐边 seam 或 Smart UV Project)。 """Blender 内运行:FBX 减面到指定三角面数 + UV 重展(锐边 seam 或 Smart UV Project)。
调用:blender -b --factory-startup --python bl_decimate.py -- <src.fbx> <out.fbx> <target_tris> [seam|smart] 调用:blender -b --factory-startup --python bl_decimate.py -- <src.fbx> <out.fbx> <target_tris> [seam|smart|none]
""" """
import os import os
import sys import sys
@@ -28,6 +28,11 @@ UV_FLIP_TOL = 0.02 # 质量门:UV 翻转面占比超此值回退 smart_pr
UV_OVERLAP_TOL = 0.08 # 质量门:UV 重叠面占比阈值(手工 UV 同口径约 5%,灾难性失败 >20%) UV_OVERLAP_TOL = 0.08 # 质量门:UV 重叠面占比阈值(手工 UV 同口径约 5%,灾难性失败 >20%)
def uv_gate_ok(flipped, overlap):
"""UV 质量门:翻转与重叠占比都在阈值内。overlap 为 None(op 不可用)按 0 处理。"""
return flipped <= UV_FLIP_TOL and (overlap or 0.0) <= UV_OVERLAP_TOL
def count_islands(n_faces, adjacent_pairs): def count_islands(n_faces, adjacent_pairs):
"""union-find 数 UV 岛:n_faces 个面,adjacent_pairs 为 UV 连续的面索引对。""" """union-find 数 UV 岛:n_faces 个面,adjacent_pairs 为 UV 连续的面索引对。"""
parent = list(range(n_faces)) parent = list(range(n_faces))
@@ -199,8 +204,7 @@ def _do_unwrap(obj, mode, warnings):
if mode == "seam": if mode == "seam":
_unwrap_seam(obj, warnings) _unwrap_seam(obj, warnings)
m = _collect_uv_metrics(obj) m = _collect_uv_metrics(obj)
overlap = m["overlap"] or 0.0 if uv_gate_ok(m["flipped"], m["overlap"]):
if m["flipped"] <= UV_FLIP_TOL and overlap <= UV_OVERLAP_TOL:
m["mode"] = "seam" m["mode"] = "seam"
return m return m
warnings.append("seam 展开质量不达标(翻转 %.1f%% 重叠 %s),回退 smart_project" warnings.append("seam 展开质量不达标(翻转 %.1f%% 重叠 %s),回退 smart_project"
@@ -250,7 +254,13 @@ def main():
if not within_tolerance(target, cur) and cur > target: if not within_tolerance(target, cur) and cur > target:
warnings.append("修正 %d 轮后仍超差:%d 面(目标 %d ±3%%" % (MAX_RETRY, cur, target)) warnings.append("修正 %d 轮后仍超差:%d 面(目标 %d ±3%%" % (MAX_RETRY, cur, target))
uv_info = _do_unwrap(obj, unwrap_mode, warnings) if unwrap_mode == "none":
# rizom 管线:跳过展开,UV 由后续 RizomUV 步骤生成
_clear_uv_layers(obj.data)
uv_info = {"mode": "none", "islands": 0, "fill": 0.0,
"flipped": 0.0, "overlap": None}
else:
uv_info = _do_unwrap(obj, unwrap_mode, warnings)
out_dir = os.path.dirname(os.path.abspath(out_fbx)) out_dir = os.path.dirname(os.path.abspath(out_fbx))
os.makedirs(out_dir, exist_ok=True) os.makedirs(out_dir, exist_ok=True)
# 仅导出合并后的对象;整场景导出会把 armature/empty 等非 mesh 带进 _low.fbx # 仅导出合并后的对象;整场景导出会把 armature/empty 等非 mesh 带进 _low.fbx
@@ -75,5 +75,22 @@ class TestFillRatio(unittest.TestCase):
self.assertEqual(bd.fill_ratio([]), 0.0) self.assertEqual(bd.fill_ratio([]), 0.0)
class TestUvGateOk(unittest.TestCase):
def test_clean_passes(self):
self.assertTrue(bd.uv_gate_ok(0.0, 0.0))
def test_boundary_passes(self):
self.assertTrue(bd.uv_gate_ok(bd.UV_FLIP_TOL, bd.UV_OVERLAP_TOL))
def test_flip_exceeds_fails(self):
self.assertFalse(bd.uv_gate_ok(0.03, 0.0))
def test_overlap_exceeds_fails(self):
self.assertFalse(bd.uv_gate_ok(0.0, 0.09))
def test_overlap_none_treated_as_zero(self):
self.assertTrue(bd.uv_gate_ok(0.01, None))
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()