#!/usr/bin/env python3 # coding=utf-8 """Tests du noyau geometrique, executables sans Inkscape : pytest test_packing.py""" import math import random import pytest from packing_core import ( SpatialGrid, distance_point_segment, distance_to_rings, pack_circles, point_in_rings, rings_bbox, ) SQUARE = [[(0, 0), (100, 0), (100, 100), (0, 100)]] SQUARE_WITH_HOLE = SQUARE + [[(40, 40), (60, 40), (60, 60), (40, 60)]] # --- appartenance ----------------------------------------------------------- def test_point_inside_square(): assert point_in_rings(50, 50, SQUARE) def test_point_outside_square(): assert not point_in_rings(150, 50, SQUARE) assert not point_in_rings(-1, 50, SQUARE) assert not point_in_rings(50, 200, SQUARE) def test_hole_is_outside(): assert not point_in_rings(50, 50, SQUARE_WITH_HOLE) assert point_in_rings(20, 20, SQUARE_WITH_HOLE) # --- distances -------------------------------------------------------------- def test_distance_point_segment_projection_clamped(): # projection hors du segment : la distance est celle de l'extremite assert distance_point_segment(-3, 0, 0, 0, 10, 0) == pytest.approx(3) assert distance_point_segment(5, 4, 0, 0, 10, 0) == pytest.approx(4) def test_distance_to_rings_center_of_square(): assert distance_to_rings(50, 50, SQUARE) == pytest.approx(50) assert distance_to_rings(5, 50, SQUARE) == pytest.approx(5) def test_distance_to_rings_accounts_for_hole(): # au centre du carre troue, le bord le plus proche est celui du trou assert distance_to_rings(50, 50, SQUARE_WITH_HOLE) == pytest.approx(10) def test_rings_bbox(): assert rings_bbox(SQUARE) == (0, 0, 100, 100) assert rings_bbox([]) is None # --- grille spatiale -------------------------------------------------------- def test_grid_returns_near_circle_only(): grid = SpatialGrid(cell_size=10) grid.add(0, 0, 5) assert list(grid.neighbors(3, 3, reach=10)) == [(0, 0, 5)] assert list(grid.neighbors(500, 500, reach=10)) == [] assert len(grid) == 1 # --- packing ---------------------------------------------------------------- def _invariants(circles, rings, min_radius, max_radius, gap, margin): eps = 1e-9 for x, y, r in circles: assert min_radius - eps <= r <= max_radius + eps assert point_in_rings(x, y, rings) assert distance_to_rings(x, y, rings) >= r + margin - eps for i, (x1, y1, r1) in enumerate(circles): for x2, y2, r2 in circles[i + 1:]: assert math.hypot(x1 - x2, y1 - y2) >= r1 + r2 + gap - eps def test_pack_circles_respects_invariants(): circles = pack_circles(SQUARE, min_radius=2, max_radius=15, gap=1, margin=1, attempts=3000, rng=random.Random(42)) assert len(circles) > 20 _invariants(circles, SQUARE, 2, 15, 1, 1) def test_pack_circles_avoids_hole(): circles = pack_circles(SQUARE_WITH_HOLE, min_radius=2, max_radius=15, gap=0.5, margin=0.5, attempts=3000, rng=random.Random(7)) assert circles _invariants(circles, SQUARE_WITH_HOLE, 2, 15, 0.5, 0.5) for x, y, _ in circles: assert not (40 < x < 60 and 40 < y < 60) def test_same_seed_gives_same_result(): args = dict(min_radius=2, max_radius=12, gap=0.5, margin=0.5, attempts=800) first = pack_circles(SQUARE, rng=random.Random(123), **args) second = pack_circles(SQUARE, rng=random.Random(123), **args) assert first == second def test_max_circles_caps_output(): circles = pack_circles(SQUARE, min_radius=1, max_radius=10, gap=0.2, margin=0.2, attempts=5000, max_circles=10, rng=random.Random(1)) assert len(circles) == 10 def test_larger_gap_places_fewer_circles(): args = dict(min_radius=2, max_radius=10, margin=0.5, attempts=2000) tight = pack_circles(SQUARE, gap=0.0, rng=random.Random(5), **args) loose = pack_circles(SQUARE, gap=6.0, rng=random.Random(5), **args) assert len(loose) < len(tight) def test_empty_rings(): assert pack_circles([], min_radius=1, max_radius=5) == [] # --- bout en bout dans Inkscape (saute si inkex absent) --------------------- def test_extension_runs_on_sample_svg(tmp_path): pytest.importorskip("inkex") from circle_packing import CirclePacking svg = tmp_path / "in.svg" svg.write_text( '' '', encoding="utf-8") ext = CirclePacking() ext.run(["--id=r", "--unit=px", "--min_radius=2", "--max_radius=10", "--attempts=2000", "--seed=1", str(svg)], output=str(tmp_path / "out.svg")) out = (tmp_path / "out.svg").read_text(encoding="utf-8") assert out.count(" 10 assert "#3366cc" in out