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 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)) 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): return {"flipped": flipped, "overlap": overlap, "islands": islands, "angle": angle} 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) if __name__ == "__main__": unittest.main()