ModelTranslator: UV 岛数/翻转/利用率纯逻辑(TDD)
This commit is contained in:
@@ -24,6 +24,38 @@ def within_tolerance(target, actual, tol=TOLERANCE):
|
|||||||
return abs(actual - target) <= target * tol
|
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 内执行 ----------------
|
# ---------------- 以下仅在 Blender 内执行 ----------------
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -36,5 +36,44 @@ class TestWithinTolerance(unittest.TestCase):
|
|||||||
self.assertFalse(bd.within_tolerance(0, 0))
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user