ModelTranslator: 质量门抽纯函数 uv_gate_ok + 减面 none 模式(rizom 管线前置)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
"""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 sys
|
||||
@@ -28,6 +28,11 @@ UV_FLIP_TOL = 0.02 # 质量门:UV 翻转面占比超此值回退 smart_pr
|
||||
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):
|
||||
"""union-find 数 UV 岛:n_faces 个面,adjacent_pairs 为 UV 连续的面索引对。"""
|
||||
parent = list(range(n_faces))
|
||||
@@ -199,8 +204,7 @@ def _do_unwrap(obj, mode, warnings):
|
||||
if mode == "seam":
|
||||
_unwrap_seam(obj, warnings)
|
||||
m = _collect_uv_metrics(obj)
|
||||
overlap = m["overlap"] or 0.0
|
||||
if m["flipped"] <= UV_FLIP_TOL and overlap <= UV_OVERLAP_TOL:
|
||||
if uv_gate_ok(m["flipped"], m["overlap"]):
|
||||
m["mode"] = "seam"
|
||||
return m
|
||||
warnings.append("seam 展开质量不达标(翻转 %.1f%% 重叠 %s),回退 smart_project"
|
||||
@@ -250,6 +254,12 @@ def main():
|
||||
if not within_tolerance(target, cur) and cur > target:
|
||||
warnings.append("修正 %d 轮后仍超差:%d 面(目标 %d ±3%%)" % (MAX_RETRY, cur, target))
|
||||
|
||||
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))
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
|
||||
@@ -75,5 +75,22 @@ class TestFillRatio(unittest.TestCase):
|
||||
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__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user