1

Affiche le part_num sur les frises de têtes

This commit is contained in:
Richard Dern 2025-12-02 22:26:52 +01:00
parent 43062b7ed4
commit 9158b7440c
5 changed files with 23 additions and 12 deletions

View File

@ -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. - `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 lordre chronologique, annotés avec lannée, le numéro de set (avec `*` si possédé) et lidentifiant de minifig. Les minifigs dont limage nest pas disponible sont remplacées par un rectangle neutre pour matérialiser le manque. - `figures/step32/minifig_characters/{personnage}.png` : frise horizontale par personnage, composée des visuels de minifigs dans lordre chronologique, annotés avec lannée, le numéro de set (avec `*` si possédé) et lidentifiant de minifig. Les minifigs dont limage nest 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 lannée, le set (avec `*` si possédé) et le `part_num` de la tête.
- Les étiquettes affichent aussi lidentifiant de la minifig (`fig-*`) et un astérisque à côté du set (`set_num*`) lorsquil est présent dans la collection. - Les étiquettes affichent aussi lidentifiant de la minifig (`fig-*`) et un astérisque à côté du set (`set_num*`) lorsquil est présent dans la collection.

View File

@ -58,6 +58,7 @@ def build_character_collage(
font: ImageFont.ImageFont, font: ImageFont.ImageFont,
missing_paths: Set[str] | None = None, missing_paths: Set[str] | None = None,
image_filename: str = "minifig.jpg", image_filename: str = "minifig.jpg",
label_field: str = "fig_num",
image_height: int = 260, image_height: int = 260,
label_height: int = 44, label_height: int = 44,
spacing: int = 28, spacing: int = 28,
@ -69,7 +70,8 @@ def build_character_collage(
for row in entries: for row in entries:
image_path = resources_dir / row["set_id"] / sanitized / image_filename image_path = resources_dir / row["set_id"] / sanitized / image_filename
owned = "*" if row.get("in_collection", "").lower() == "true" else "" 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: if str(image_path) in missing:
image = build_placeholder(image_height) image = build_placeholder(image_height)
else: else:
@ -94,6 +96,7 @@ def build_character_collages(
destination_dir: Path, destination_dir: Path,
missing_paths: Set[str] | None = None, missing_paths: Set[str] | None = None,
image_filename: str = "minifig.jpg", image_filename: str = "minifig.jpg",
label_field: str = "fig_num",
image_height: int = 260, image_height: int = 260,
label_height: int = 44, label_height: int = 44,
spacing: int = 28, spacing: int = 28,
@ -111,6 +114,7 @@ def build_character_collages(
font, font,
missing_paths=missing_paths, missing_paths=missing_paths,
image_filename=image_filename, image_filename=image_filename,
label_field=label_field,
image_height=image_height, image_height=image_height,
label_height=label_height, label_height=label_height,
spacing=spacing, spacing=spacing,

View File

@ -28,17 +28,18 @@ def build_character_sets(
) -> List[dict]: ) -> List[dict]:
"""Associe chaque personnage connu aux sets où il apparaît (hors figurants).""" """Associe chaque personnage connu aux sets où il apparaît (hors figurants)."""
excluded = set(excluded_characters or []) excluded = set(excluded_characters or [])
seen: set[tuple[str, str, str]] = set() seen: set[tuple[str, str, str, str]] = set()
character_sets: List[dict] = [] character_sets: List[dict] = []
for row in minifigs_rows: for row in minifigs_rows:
character = row["known_character"].strip() character = row["known_character"].strip()
fig_num = row["fig_num"].strip() fig_num = row["fig_num"].strip()
part_num = row["part_num"].strip()
if character == "" or fig_num == "": if character == "" or fig_num == "":
continue continue
if character in excluded: if character in excluded:
continue continue
set_row = sets_lookup[row["set_num"]] 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: if key in seen:
continue continue
character_sets.append( character_sets.append(
@ -49,11 +50,12 @@ def build_character_sets(
"year": set_row["year"], "year": set_row["year"],
"fig_num": fig_num, "fig_num": fig_num,
"in_collection": set_row["in_collection"], "in_collection": set_row["in_collection"],
"part_num": part_num,
} }
) )
seen.add(key) seen.add(key)
character_sets.sort( 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 return character_sets
@ -61,7 +63,7 @@ def build_character_sets(
def write_character_sets(destination_path: Path, rows: Sequence[dict]) -> None: def write_character_sets(destination_path: Path, rows: Sequence[dict]) -> None:
"""Écrit le CSV listant les sets par personnage.""" """Écrit le CSV listant les sets par personnage."""
ensure_parent_dir(destination_path) 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: with destination_path.open("w", newline="") as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=fieldnames) writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
writer.writeheader() writer.writeheader()

View File

@ -36,6 +36,7 @@ def main() -> None:
DESTINATION_DIR, DESTINATION_DIR,
missing_paths=missing_heads, missing_paths=missing_heads,
image_filename="head.jpg", image_filename="head.jpg",
label_field="part_num",
) )

View File

@ -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: def test_build_character_sets_and_collages(tmp_path: Path) -> None:
"""Construit les apparitions par personnage puis les frises associées.""" """Construit les apparitions par personnage puis les frises associées."""
minifigs_rows = [ minifigs_rows = [
{"set_num": "1000-1", "known_character": "Alice", "fig_num": "fig-1"}, {"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"}, {"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"}, {"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"}, {"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"}, {"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"}, {"set_num": "1003-1", "known_character": "Figurant", "fig_num": "fig-4", "part_num": "p4"},
] ]
sets_lookup = { sets_lookup = {
"1000-1": {"set_id": "1000", "year": "2020", "in_collection": "true"}, "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", "year": "2020",
"fig_num": "fig-1", "fig_num": "fig-1",
"in_collection": "true", "in_collection": "true",
"part_num": "p1",
}, },
{ {
"known_character": "Bob", "known_character": "Bob",
@ -50,6 +51,7 @@ def test_build_character_sets_and_collages(tmp_path: Path) -> None:
"year": "2021", "year": "2021",
"fig_num": "fig-2", "fig_num": "fig-2",
"in_collection": "false", "in_collection": "false",
"part_num": "p2",
}, },
{ {
"known_character": "Bob", "known_character": "Bob",
@ -58,6 +60,7 @@ def test_build_character_sets_and_collages(tmp_path: Path) -> None:
"year": "2022", "year": "2022",
"fig_num": "fig-3", "fig_num": "fig-3",
"in_collection": "false", "in_collection": "false",
"part_num": "p3",
}, },
{ {
"known_character": "Claire Dearing", "known_character": "Claire Dearing",
@ -66,6 +69,7 @@ def test_build_character_sets_and_collages(tmp_path: Path) -> None:
"year": "2024", "year": "2024",
"fig_num": "fig-5", "fig_num": "fig-5",
"in_collection": "true", "in_collection": "true",
"part_num": "p5",
}, },
] ]