diff --git a/README.md b/README.md index cd1e18d..89a4e05 100644 --- a/README.md +++ b/README.md @@ -352,5 +352,5 @@ Le script lit `data/intermediate/minifigs_by_set.csv`, `data/intermediate/sets_e - `data/intermediate/minifig_character_sets.csv` : apparitions des personnages avec set, identifiant de set, année, possession et fig_num. - `figures/step32/minifig_characters/{personnage}.png` : frise horizontale par personnage, composée des visuels de minifigs dans l’ordre chronologique, annotés avec l’année, le numéro de set (avec `*` si possédé) et l’identifiant de minifig. Les minifigs dont l’image n’est pas disponible sont remplacées par un rectangle neutre pour matérialiser le manque. -- `figures/step32/minifig_heads/{personnage}.png` : même principe mais en utilisant les visuels de têtes (`head.jpg`) pour chaque apparition. +- `figures/step32/minifig_heads/{personnage}.png` : même principe mais en utilisant les visuels de têtes (`head.jpg`) pour chaque apparition, annotés avec l’année, le set (avec `*` si possédé) et le `part_num` de la tête. - Les étiquettes affichent aussi l’identifiant de la minifig (`fig-*`) et un astérisque à côté du set (`set_num*`) lorsqu’il est présent dans la collection. diff --git a/lib/plots/minifig_character_collages.py b/lib/plots/minifig_character_collages.py index 46a1b4d..e5029d8 100644 --- a/lib/plots/minifig_character_collages.py +++ b/lib/plots/minifig_character_collages.py @@ -58,6 +58,7 @@ def build_character_collage( font: ImageFont.ImageFont, missing_paths: Set[str] | None = None, image_filename: str = "minifig.jpg", + label_field: str = "fig_num", image_height: int = 260, label_height: int = 44, spacing: int = 28, @@ -69,7 +70,8 @@ def build_character_collage( for row in entries: image_path = resources_dir / row["set_id"] / sanitized / image_filename owned = "*" if row.get("in_collection", "").lower() == "true" else "" - label = f"{row['year']} - {row['set_num']}{owned} ({row['fig_num']})" + label_value = row[label_field] + label = f"{row['year']} - {row['set_num']}{owned} ({label_value})" if str(image_path) in missing: image = build_placeholder(image_height) else: @@ -94,6 +96,7 @@ def build_character_collages( destination_dir: Path, missing_paths: Set[str] | None = None, image_filename: str = "minifig.jpg", + label_field: str = "fig_num", image_height: int = 260, label_height: int = 44, spacing: int = 28, @@ -111,6 +114,7 @@ def build_character_collages( font, missing_paths=missing_paths, image_filename=image_filename, + label_field=label_field, image_height=image_height, label_height=label_height, spacing=spacing, diff --git a/lib/rebrickable/minifig_character_sets.py b/lib/rebrickable/minifig_character_sets.py index 9f5d821..dfac409 100644 --- a/lib/rebrickable/minifig_character_sets.py +++ b/lib/rebrickable/minifig_character_sets.py @@ -28,17 +28,18 @@ def build_character_sets( ) -> List[dict]: """Associe chaque personnage connu aux sets où il apparaît (hors figurants).""" excluded = set(excluded_characters or []) - seen: set[tuple[str, str, str]] = set() + seen: set[tuple[str, str, str, str]] = set() character_sets: List[dict] = [] for row in minifigs_rows: character = row["known_character"].strip() fig_num = row["fig_num"].strip() + part_num = row["part_num"].strip() if character == "" or fig_num == "": continue if character in excluded: continue set_row = sets_lookup[row["set_num"]] - key = (character, row["set_num"], fig_num) + key = (character, row["set_num"], fig_num, part_num) if key in seen: continue character_sets.append( @@ -49,11 +50,12 @@ def build_character_sets( "year": set_row["year"], "fig_num": fig_num, "in_collection": set_row["in_collection"], + "part_num": part_num, } ) seen.add(key) character_sets.sort( - key=lambda row: (row["known_character"], int(row["year"]), row["set_num"], row["fig_num"]) + key=lambda row: (row["known_character"], int(row["year"]), row["set_num"], row["fig_num"], row["part_num"]) ) return character_sets @@ -61,7 +63,7 @@ def build_character_sets( def write_character_sets(destination_path: Path, rows: Sequence[dict]) -> None: """Écrit le CSV listant les sets par personnage.""" ensure_parent_dir(destination_path) - fieldnames = ["known_character", "set_num", "set_id", "year", "fig_num", "in_collection"] + fieldnames = ["known_character", "set_num", "set_id", "year", "fig_num", "in_collection", "part_num"] with destination_path.open("w", newline="") as csv_file: writer = csv.DictWriter(csv_file, fieldnames=fieldnames) writer.writeheader() diff --git a/scripts/plot_minifig_head_collages.py b/scripts/plot_minifig_head_collages.py index e175142..f9e0073 100644 --- a/scripts/plot_minifig_head_collages.py +++ b/scripts/plot_minifig_head_collages.py @@ -36,6 +36,7 @@ def main() -> None: DESTINATION_DIR, missing_paths=missing_heads, image_filename="head.jpg", + label_field="part_num", ) diff --git a/tests/test_minifig_character_collages.py b/tests/test_minifig_character_collages.py index 6be4d4d..5b08be0 100644 --- a/tests/test_minifig_character_collages.py +++ b/tests/test_minifig_character_collages.py @@ -18,12 +18,12 @@ def create_image(path: Path, color: tuple[int, int, int], size: tuple[int, int]) def test_build_character_sets_and_collages(tmp_path: Path) -> None: """Construit les apparitions par personnage puis les frises associées.""" minifigs_rows = [ - {"set_num": "1000-1", "known_character": "Alice", "fig_num": "fig-1"}, - {"set_num": "1000-1", "known_character": "Alice", "fig_num": "fig-1"}, - {"set_num": "1001-1", "known_character": "Bob", "fig_num": "fig-2"}, - {"set_num": "1002-1", "known_character": "Bob", "fig_num": "fig-3"}, - {"set_num": "1004-1", "known_character": "Claire Dearing", "fig_num": "fig-5"}, - {"set_num": "1003-1", "known_character": "Figurant", "fig_num": "fig-4"}, + {"set_num": "1000-1", "known_character": "Alice", "fig_num": "fig-1", "part_num": "p1"}, + {"set_num": "1000-1", "known_character": "Alice", "fig_num": "fig-1", "part_num": "p1"}, + {"set_num": "1001-1", "known_character": "Bob", "fig_num": "fig-2", "part_num": "p2"}, + {"set_num": "1002-1", "known_character": "Bob", "fig_num": "fig-3", "part_num": "p3"}, + {"set_num": "1004-1", "known_character": "Claire Dearing", "fig_num": "fig-5", "part_num": "p5"}, + {"set_num": "1003-1", "known_character": "Figurant", "fig_num": "fig-4", "part_num": "p4"}, ] sets_lookup = { "1000-1": {"set_id": "1000", "year": "2020", "in_collection": "true"}, @@ -42,6 +42,7 @@ def test_build_character_sets_and_collages(tmp_path: Path) -> None: "year": "2020", "fig_num": "fig-1", "in_collection": "true", + "part_num": "p1", }, { "known_character": "Bob", @@ -50,6 +51,7 @@ def test_build_character_sets_and_collages(tmp_path: Path) -> None: "year": "2021", "fig_num": "fig-2", "in_collection": "false", + "part_num": "p2", }, { "known_character": "Bob", @@ -58,6 +60,7 @@ def test_build_character_sets_and_collages(tmp_path: Path) -> None: "year": "2022", "fig_num": "fig-3", "in_collection": "false", + "part_num": "p3", }, { "known_character": "Claire Dearing", @@ -66,6 +69,7 @@ def test_build_character_sets_and_collages(tmp_path: Path) -> None: "year": "2024", "fig_num": "fig-5", "in_collection": "true", + "part_num": "p5", }, ]