diff --git a/Tools/ModelTranslator/bl_decimate.py b/Tools/ModelTranslator/bl_decimate.py index afc4259e..d553dd41 100644 --- a/Tools/ModelTranslator/bl_decimate.py +++ b/Tools/ModelTranslator/bl_decimate.py @@ -24,6 +24,38 @@ def within_tolerance(target, actual, tol=TOLERANCE): return abs(actual - target) <= target * tol +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)) + + # ---------------- 以下仅在 Blender 内执行 ---------------- diff --git a/Tools/ModelTranslator/tests/test_bl_decimate.py b/Tools/ModelTranslator/tests/test_bl_decimate.py index 9c59ba8d..91269446 100644 --- a/Tools/ModelTranslator/tests/test_bl_decimate.py +++ b/Tools/ModelTranslator/tests/test_bl_decimate.py @@ -36,5 +36,44 @@ class TestWithinTolerance(unittest.TestCase): self.assertFalse(bd.within_tolerance(0, 0)) +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) + + if __name__ == "__main__": unittest.main()