Files

250 lines
9.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import sys
import unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import bl_decimate as bd
class TestDecimateRatio(unittest.TestCase):
def test_normal_reduction(self):
self.assertAlmostEqual(bd.decimate_ratio(5000, 1500000), 5000 / 1500000.0)
def test_current_below_target_returns_one(self):
self.assertEqual(bd.decimate_ratio(5000, 3000), 1.0)
def test_current_equal_target_returns_one(self):
self.assertEqual(bd.decimate_ratio(5000, 5000), 1.0)
def test_zero_current_returns_one(self):
self.assertEqual(bd.decimate_ratio(5000, 0), 1.0)
class TestWithinTolerance(unittest.TestCase):
def test_exact_hit(self):
self.assertTrue(bd.within_tolerance(5000, 5000))
def test_within_3_percent(self):
self.assertTrue(bd.within_tolerance(5000, 5150)) # +3%
self.assertTrue(bd.within_tolerance(5000, 4850)) # -3%
def test_outside_3_percent(self):
self.assertFalse(bd.within_tolerance(5000, 5200))
self.assertFalse(bd.within_tolerance(5000, 4700))
def test_zero_target_is_false(self):
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 TestIslandLabels(unittest.TestCase):
def test_no_faces(self):
self.assertEqual(bd.island_labels(0, []), [])
def test_no_pairs_each_face_own_island(self):
labels = bd.island_labels(3, [])
self.assertEqual(len(set(labels)), 3)
def test_chain_one_island(self):
labels = bd.island_labels(4, [(0, 1), (1, 2), (2, 3)])
self.assertEqual(len(set(labels)), 1)
def test_two_groups_partition(self):
labels = bd.island_labels(5, [(0, 1), (3, 4)]) # {0,1} {2} {3,4}
self.assertEqual(len(set(labels)), 3)
self.assertEqual(labels[0], labels[1])
self.assertEqual(labels[3], labels[4])
self.assertNotEqual(labels[0], labels[2])
def test_count_matches_count_islands(self):
pairs = [(0, 1), (2, 3), (3, 4)]
self.assertEqual(len(set(bd.island_labels(6, pairs))),
bd.count_islands(6, pairs))
class TestMaxIslandAreaFraction(unittest.TestCase):
def test_empty_is_zero(self):
self.assertEqual(bd.max_island_area_fraction([], []), 0.0)
def test_zero_total_is_zero(self):
self.assertEqual(bd.max_island_area_fraction([0, 1], [0.0, 0.0]), 0.0)
def test_single_island_is_one(self):
self.assertAlmostEqual(
bd.max_island_area_fraction([7, 7, 7], [0.1, 0.2, 0.3]), 1.0)
def test_two_equal_islands_half(self):
self.assertAlmostEqual(
bd.max_island_area_fraction([0, 0, 1, 1], [0.25, 0.25, 0.25, 0.25]), 0.5)
def test_dominant_island(self):
# 岛 0 面积 0.8、岛 1 面积 0.2 -> 最大占比 0.8
self.assertAlmostEqual(
bd.max_island_area_fraction([0, 1], [0.8, 0.2]), 0.8)
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)
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))
def test_custom_overlap_tol_allows_higher_overlap(self):
self.assertTrue(bd.uv_gate_ok(0.0, 0.12, overlap_tol=0.15))
self.assertFalse(bd.uv_gate_ok(0.0, 0.16, overlap_tol=0.15))
def test_custom_overlap_tol_does_not_relax_flip(self):
# 放宽重叠不影响翻转判定(翻转仍按 UV_FLIP_TOL
self.assertFalse(bd.uv_gate_ok(0.03, 0.0, overlap_tol=0.5))
def test_default_overlap_tol_matches_constant(self):
self.assertTrue(bd.uv_gate_ok(0.0, bd.UV_OVERLAP_TOL))
self.assertFalse(bd.uv_gate_ok(0.0, bd.UV_OVERLAP_TOL + 0.01))
def test_default_island_frac_backward_compatible(self):
# 不传 max_island_frac(默认 0.0)时行为与旧版一致
self.assertTrue(bd.uv_gate_ok(0.0, 0.0))
def test_island_frac_exceeds_fails(self):
# 翻转/重叠都干净,但单岛占比过大也判不合格
self.assertFalse(bd.uv_gate_ok(0.0, 0.0, max_island_frac=0.5))
def test_island_frac_boundary_passes(self):
self.assertTrue(bd.uv_gate_ok(0.0, 0.0, max_island_frac=bd.UV_MAX_ISLAND_FRAC))
self.assertFalse(bd.uv_gate_ok(
0.0, 0.0, max_island_frac=bd.UV_MAX_ISLAND_FRAC + 0.01))
def test_island_frac_none_treated_as_zero(self):
self.assertTrue(bd.uv_gate_ok(0.0, 0.0, max_island_frac=None))
def test_custom_island_frac_tol(self):
self.assertTrue(bd.uv_gate_ok(
0.0, 0.0, max_island_frac=0.5, island_frac_tol=0.6))
self.assertFalse(bd.uv_gate_ok(
0.0, 0.0, max_island_frac=0.7, island_frac_tol=0.6))
class TestUvpmModeLabel(unittest.TestCase):
def test_applied_appends_suffix(self):
self.assertEqual(bd.uvpm_mode_label("seam", True), "seam+uvpm")
self.assertEqual(bd.uvpm_mode_label("smart", True), "smart+uvpm")
self.assertEqual(bd.uvpm_mode_label("smart_fallback", True), "smart_fallback+uvpm")
def test_not_applied_keeps_base(self):
self.assertEqual(bd.uvpm_mode_label("seam", False), "seam")
self.assertEqual(bd.uvpm_mode_label("smart_fallback", False), "smart_fallback")
class TestPickBestCandidate(unittest.TestCase):
def _c(self, flipped, overlap, islands, angle, frac=0.0):
return {"flipped": flipped, "overlap": overlap,
"islands": islands, "angle": angle, "max_island_frac": frac}
def test_none_when_empty(self):
self.assertIsNone(bd.pick_best_candidate([]))
def test_none_when_no_candidate_passes_gate(self):
cands = [self._c(0.30, 0.50, 100, 66), self._c(0.20, 0.40, 200, 45)]
self.assertIsNone(bd.pick_best_candidate(cands))
def test_picks_only_passing(self):
cands = [self._c(0.30, 0.50, 50, 66), self._c(0.00, 0.00, 300, 45)]
best = bd.pick_best_candidate(cands)
self.assertEqual(best["angle"], 45)
def test_picks_fewest_islands_among_passing(self):
cands = [self._c(0.00, 0.00, 120, 55), self._c(0.01, 0.02, 90, 45),
self._c(0.00, 0.00, 300, 35)]
best = bd.pick_best_candidate(cands)
self.assertEqual(best["islands"], 90)
def test_overlap_none_treated_as_pass(self):
cands = [self._c(0.01, None, 42, 66)]
best = bd.pick_best_candidate(cands)
self.assertEqual(best["islands"], 42)
def test_overlap_tol_lets_more_candidates_pass(self):
# overlap=0.12 在默认 8% 门限下不过;放宽到 0.15 后过门并被选中
cands = [self._c(0.0, 0.12, 50, 55)]
self.assertIsNone(bd.pick_best_candidate(cands))
best = bd.pick_best_candidate(cands, overlap_tol=0.15)
self.assertEqual(best["angle"], 55)
def test_dominant_island_candidate_excluded(self):
# 高角度档岛少但有巨型大岛(占比0.6)被排除,选低角度小岛且占比达标的档
cands = [self._c(0.0, 0.0, 50, 66, frac=0.6),
self._c(0.0, 0.0, 300, 20, frac=0.10)]
best = bd.pick_best_candidate(cands)
self.assertEqual(best["angle"], 20)
def test_all_dominant_returns_none(self):
# 所有档都有巨型大岛 -> 无过门候选(上层据此回退 smart)
cands = [self._c(0.0, 0.0, 50, 66, frac=0.6),
self._c(0.0, 0.0, 300, 15, frac=0.4)]
self.assertIsNone(bd.pick_best_candidate(cands))
class TestSeamAngleSweep(unittest.TestCase):
def test_descending(self):
s = list(bd.SEAM_ANGLE_SWEEP)
self.assertEqual(s, sorted(s, reverse=True))
def test_reaches_low_angle(self):
# 需下探到 <=30° 以覆盖 QR 光滑四边网格(实测 guang 30° 才过门)
self.assertLessEqual(min(bd.SEAM_ANGLE_SWEEP), 30.0)
def test_below_first_angle(self):
self.assertLess(max(bd.SEAM_ANGLE_SWEEP), bd.SEAM_ANGLE_DEG)
if __name__ == "__main__":
unittest.main()