ModelTranslator: 质量门抽纯函数 uv_gate_ok + 减面 none 模式(rizom 管线前置)
This commit is contained in:
@@ -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,6 +254,12 @@ 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))
|
||||||
|
|
||||||
|
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)
|
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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user