1

Filtre les couleurs ignorées et aligne les palettes

This commit is contained in:
2025-12-02 15:07:35 +01:00
parent 7b6045941f
commit 812fd4a862
10 changed files with 66 additions and 2 deletions

View File

@@ -9,6 +9,7 @@ from matplotlib.lines import Line2D
from lib.filesystem import ensure_parent_dir
from lib.color_sort import lab_sort_key, sort_hex_colors_lab
from lib.rebrickable.color_ignores import is_ignored_color_rgb
from lib.rebrickable.parts_inventory import normalize_boolean
from lib.rebrickable.stats import read_rows
@@ -30,6 +31,8 @@ def load_used_colors(parts_path: Path, colors_path: Path, minifig_only: bool = F
colors_lookup = {(row["rgb"], normalize_boolean(row["is_trans"])): row["name"] for row in read_rows(colors_path)}
totals: Dict[Tuple[str, str], int] = {}
for row in rows:
if is_ignored_color_rgb(row["color_rgb"]):
continue
if minifig_only and row.get("is_minifig_part") != "true":
continue
if not minifig_only and row.get("is_minifig_part") == "true":

View File

@@ -0,0 +1,8 @@
"""Couleurs à exclure des palettes."""
IGNORED_COLOR_RGB = {"0033b2", "05131d"}
def is_ignored_color_rgb(rgb: str) -> bool:
"""Retourne vrai si le code couleur doit être ignoré."""
return rgb.strip().lower() in IGNORED_COLOR_RGB

View File

@@ -5,6 +5,7 @@ from pathlib import Path
from typing import Dict, Iterable, List, Tuple
from lib.filesystem import ensure_parent_dir
from lib.rebrickable.color_ignores import is_ignored_color_rgb
def load_parts(parts_path: Path) -> List[dict]:
@@ -28,6 +29,8 @@ def aggregate_colors_by_set(parts: Iterable[dict], colors_lookup: Dict[Tuple[str
"""Agrège les quantités par set et par couleur."""
totals: Dict[Tuple[str, str, str, str, str], dict] = {}
for row in parts:
if is_ignored_color_rgb(row["color_rgb"]):
continue
key = (row["set_num"], row["set_id"], row["year"], row["color_rgb"], row["is_translucent"])
existing = totals.get(key)
if existing is None:

View File

@@ -5,6 +5,7 @@ from pathlib import Path
from typing import Dict, Iterable, List, Set, Tuple
from lib.rebrickable.colors_by_set import build_colors_lookup
from lib.rebrickable.color_ignores import is_ignored_color_rgb
from lib.rebrickable.stats import read_rows
@@ -39,6 +40,8 @@ def aggregate_head_colors_by_set(
continue
if row["is_spare"] == "true":
continue
if is_ignored_color_rgb(row["color_rgb"]):
continue
key = (row["set_num"], row["set_id"], row["year"], row["color_rgb"])
existing = aggregates.get(key)
if existing is None:

View File

@@ -5,7 +5,10 @@ from collections import defaultdict
from pathlib import Path
from typing import Dict, Iterable, List, Sequence
import colorsys
from lib.filesystem import ensure_parent_dir
from lib.rebrickable.color_ignores import is_ignored_color_rgb
from lib.rebrickable.stats import read_rows
@@ -24,6 +27,39 @@ def load_sets_enriched(path: Path) -> Dict[str, dict]:
return lookup
def parse_rgb_hex(value: str) -> tuple[float, float, float]:
"""Parse un code hexadécimal (RRGGBB) en composantes 0-1."""
normalized = value.strip()
if len(normalized) != 6:
return (0.0, 0.0, 0.0)
r = int(normalized[0:2], 16) / 255.0
g = int(normalized[2:4], 16) / 255.0
b = int(normalized[4:6], 16) / 255.0
return (r, g, b)
def hue_bucket(degrees: float) -> int:
"""Regroupe les teintes en grandes familles pour l'affichage."""
if degrees >= 330 or degrees < 30:
return 0 # rouge
if degrees < 90:
return 1 # jaune / orangé
if degrees < 150:
return 2 # vert
if degrees < 270:
return 3 # bleu
return 4 # violet
def color_display_key(row: dict) -> tuple[float, int, float, str]:
"""Clé de tri visuelle : teinte regroupée d'abord, puis luminosité."""
r, g, b = parse_rgb_hex(row["color_rgb"])
h, _s, v = colorsys.rgb_to_hsv(r, g, b)
degrees = h * 360.0
bucket = hue_bucket(degrees)
return (bucket, degrees, v, row["color_name"])
def build_top_colors_by_set(rows: Iterable[dict], sets_lookup: Dict[str, dict], top_n: int = 5) -> List[dict]:
"""Sélectionne les top couleurs hors minifigs pour chaque set."""
colors_by_set: Dict[str, List[dict]] = defaultdict(list)
@@ -31,6 +67,8 @@ def build_top_colors_by_set(rows: Iterable[dict], sets_lookup: Dict[str, dict],
quantity = int(row["quantity_non_minifig"])
if quantity <= 0:
continue
if is_ignored_color_rgb(row["color_rgb"]):
continue
set_num = row["set_num"]
set_meta = sets_lookup.get(set_num)
if set_meta is None:
@@ -49,7 +87,9 @@ def build_top_colors_by_set(rows: Iterable[dict], sets_lookup: Dict[str, dict],
results: List[dict] = []
for set_num, color_rows in colors_by_set.items():
sorted_rows = sorted(color_rows, key=lambda r: (-r["quantity"], r["color_name"]))
for rank, color_row in enumerate(sorted_rows[:top_n], start=1):
selected = sorted_rows[:top_n]
ordered = sorted(selected, key=color_display_key)
for rank, color_row in enumerate(ordered, start=1):
results.append(
{
"set_num": color_row["set_num"],
@@ -62,7 +102,7 @@ def build_top_colors_by_set(rows: Iterable[dict], sets_lookup: Dict[str, dict],
"quantity_non_minifig": str(color_row["quantity"]),
}
)
results.sort(key=lambda r: (int(r["year"]), r["name"], r["set_num"], int(r["rank"])))
results.sort(key=lambda r: (int(r["year"]), r["set_num"], r["name"], int(r["rank"])))
return results