color_tools.palette
Color and filament palette management with fast lookup.
This module provides: 1. Data classes for colors (ColorRecord) and filaments (FilamentRecord) 2. Functions to load color/filament data from JSON with user override support 3. Palette classes with multiple indices for O(1) lookups 4. Nearest-color search using various distance metrics
The palette classes are like databases with multiple indexes - you can search by name, RGB, HSL, maker, type, etc. and get instant results!
User files (user/user-colors.json, user/user-filaments.json) always override core data when there are conflicts. Override information is logged for transparency.
Example
>>> from color_tools import Palette, FilamentPalette
>>>
>>> # Load CSS color database
>>> palette = Palette.load_default()
>>> print(f"Loaded {len(palette.colors)} CSS colors")
Loaded 148 CSS colors
>>>
>>> # Find exact color by name
>>> coral = palette.get_by_name("coral")
>>> print(f"Coral: RGB{coral.rgb} (#{coral.hex})")
Coral: RGB(255, 127, 80) (#FF7F50)
>>>
>>> # Find nearest color to custom RGB
>>> custom_orange = (255, 140, 60)
>>> nearest, distance = palette.nearest_color(custom_orange)
>>> print(f"Nearest to RGB{custom_orange}: {nearest.name} (ΔE: {distance:.1f})")
Nearest to RGB(255, 140, 60): darkorange (ΔE: 7.2)
>>>
>>> # Load 3D printing filaments
>>> filaments = FilamentPalette.load_default()
>>>
>>> # Search by maker and material
>>> pla_colors = filaments.find_by_maker("Bambu", type_name="PLA")
>>> print(f"Found {len(pla_colors)} Bambu PLA colors")
Found 24 Bambu PLA colors
- class color_tools.palette.ColorRecord(name, hex, rgb, hsl, lab, lch, source='colors.json')[source]
Bases:
objectImmutable record representing a named CSS color with precomputed color space values.
This dataclass is frozen (immutable) - once created, you can’t change it. This is perfect for colors: a color IS what it IS! 🎨
All color space values are precomputed and stored for fast access without conversion overhead. The source field tracks which JSON file provided this color, enabling user override detection and debugging.
- name
Color name (e.g., “coral”, “lightblue”, “darkslategray”)
- hex
Hex color code with # prefix (e.g., “#FF7F50”)
- rgb
RGB color tuple with values 0-255 (e.g., (255, 127, 80))
- hsl
HSL color tuple (H: 0-360°, S: 0-100%, L: 0-100%)
- lab
CIE LAB color tuple (L: 0-100, a: ~-128 to +127, b: ~-128 to +127)
- lch
CIE LCH color tuple (L: 0-100, C: 0-100+, H: 0-360°)
- source
JSON filename where this record originated (default: “colors.json”)
Example
>>> from color_tools import Palette >>> palette = Palette.load_default() >>> color = palette.get_by_name("coral") >>> print(f"{color.name}: RGB{color.rgb}") coral: RGB(255, 127, 80) >>> print(f"LAB: {color.lab}") LAB: (67.29, 44.61, 49.72)
- Parameters:
- class color_tools.palette.FilamentRecord(id, maker, type, finish, color, hex, td_value=None, other_names=None, source='filaments.json')[source]
Bases:
objectImmutable record representing a 3D printing filament color.
This dataclass handles both single-color and dual-color filaments (like silk/rainbow filaments with two twisted colors). The rgb property intelligently handles dual-color hex codes (e.g., “#AABBCC-#DDEEFF”) based on the global dual_color_mode setting.
The source field tracks which JSON file provided this filament, enabling user override detection and debugging.
- id
Unique identifier slug (e.g., “bambu-lab-pla-silk-plus-red”)
- maker
Manufacturer name (e.g., “Bambu Lab”, “Polymaker”, “Prusament”)
- type
Filament material type (e.g., “PLA”, “PETG”, “ABS”)
- finish
Surface finish type (e.g., “Matte”, “Silk”, “PolyMax”) or None
- color
Color name as labeled by manufacturer (e.g., “Jet Black”, “Signal Red”)
- hex
Hex color code with # prefix, may contain dash for dual colors (e.g., “#333333-#666666”)
- td_value
Light transmission depth - how much light passes through the filament. Used for HueForge 3D printing to model light transmission through layers. Most filaments: 0-10 (opaque to slightly translucent) Transparent/clear filaments: ~100 Scale is open-ended and can exceed 100 for highly transparent materials. None indicates opaque or unknown transmission characteristics.
- other_names
Alternative color names (regional variants, historical names) or None
- source
JSON filename where this record originated (default: “filaments.json”)
- Properties:
- rgb: RGB tuple (0-255 each) computed from hex, handles dual-color filaments
based on dual_color_mode (“first”, “last”, or “mix”)
lab: CIE LAB tuple computed on-demand from RGB lch: CIE LCH tuple computed on-demand from RGB
Example
>>> from color_tools import FilamentPalette >>> palette = FilamentPalette.load_default() >>> filament = palette.find_by_maker("Bambu Lab", type_name="PLA")[0] >>> print(f"{filament.maker} {filament.type} - {filament.color}") Bambu Lab PLA - Jet Black >>> print(f"RGB: {filament.rgb}, Hex: {filament.hex}") RGB: (51, 51, 51), Hex: #333333
- Parameters:
- property rgb: Tuple[int, int, int]
Convert hex to RGB tuple, handling dual-color filaments.
This is where the dual-color magic happens! If hex contains a dash (e.g., “#333333-#666666”), we parse BOTH colors and handle them based on the global dual_color_mode setting: - “first”: Use first color (default) - “last”: Use second color - “mix”: Perceptually blend in LAB space (the proper way!)
- Returns:
RGB tuple (0-255 for each component)
- color_tools.palette.load_colors(json_path=None)[source]
Load CSS color database from JSON files (core + user additions).
Loads colors from both the core colors.json file and optional user/user-colors.json file in the data directory. Core colors are loaded first, followed by user colors.
- color_tools.palette.load_filaments(json_path=None)[source]
Load filament database from JSON files (core + user additions).
Loads filaments from both the core filaments.json file and optional user/user-filaments.json file in the data directory. Core filaments are loaded first, followed by user filaments.
- color_tools.palette.load_maker_synonyms(json_path=None)[source]
Load maker synonyms from JSON files (core + user additions).
Loads synonyms from both the core maker_synonyms.json file and optional user/user-synonyms.json file in the data directory. Synonyms are merged, with user additions extending or replacing core synonyms per maker.
- color_tools.palette.load_palette(name, json_path=None)[source]
Load a named retro palette from the palettes directory.
Palettes are loaded from: 1. User palettes: data/user/palettes/{name}.json (if exists) 2. Core palettes: data/palettes/{name}.json (built-in)
User palettes override core palettes with the same name.
Common built-in palettes include: - cga4: CGA 4-color palette (Palette 1, high intensity) - classic gaming! - cga16: CGA 16-color palette (full RGBI) - ega16: EGA 16-color palette (standard/default) - ega64: EGA 64-color palette (full 6-bit RGB) - vga: VGA 256-color palette (Mode 13h) - web: Web-safe 216-color palette (6x6x6 RGB cube) - gameboy: Game Boy 4-shade green palette
- Parameters:
- Return type:
- Returns:
Palette object loaded from the specified palette file
- Raises:
FileNotFoundError – If the palette file doesn’t exist
ValueError – If the palette file is malformed or contains invalid data
Example
>>> cga = load_palette("cga4") >>> color, dist = cga.nearest_color((128, 64, 200)) >>> print(f"Nearest CGA color: {color.name}")
>>> # Load custom user palette >>> custom = load_palette("my_custom_palette") >>> print(f"Loaded {len(custom.records)} colors from user palette")
- class color_tools.palette.Palette(records)[source]
Bases:
objectCSS color palette with multiple indexing strategies for fast lookup.
Think of this as a database with multiple indexes. Want to find a color by name? O(1). By RGB? O(1). By LAB? O(1). The tradeoff is memory - we keep multiple dictionaries pointing to the same ColorRecords.
For a palette with ~150 CSS colors, this is totally fine! 🚀
- Parameters:
records (
List[ColorRecord])
- __init__(records)[source]
- Parameters:
records (
List[ColorRecord])
- classmethod load_default()[source]
Load the default CSS color palette from the package data.
This is a convenience method so you don’t have to worry about file paths - just call Palette.load_default() and go!
- Return type:
- find_by_name(name)[source]
Find color by exact name match (case-insensitive).
- Parameters:
name (
str)- Return type:
- nearest_color(value, space='lab', metric='de2000', *, cmc_l=2.0, cmc_c=1.0)[source]
Find nearest color by space/metric.
This is the main search function! It iterates through all colors and finds the one with minimum distance in the specified space.
- Parameters:
value (
Tuple[float,float,float]) – Color in the specified space (RGB, HSL, LAB, or LCH)space (
str) – Color space - ‘rgb’, ‘hsl’, ‘lab’, or ‘lch’ (default: ‘lab’)metric (
str) – Distance metric - ‘euclidean’, ‘de76’, ‘de94’, ‘de2000’, ‘cmc’cmc_l (
float) – Parameters for CMC metric (default 2:1 for acceptability)cmc_c (
float) – Parameters for CMC metric (default 2:1 for acceptability)
- Return type:
- Returns:
(nearest_color_record, distance) tuple
- nearest_colors(value, space='lab', metric='de2000', count=5, *, cmc_l=2.0, cmc_c=1.0)[source]
Find the nearest N colors by space/metric.
Similar to nearest_color but returns multiple results sorted by distance.
- Parameters:
value (
Tuple[float,float,float]) – Color in the specified space (RGB, HSL, LAB, or LCH)space (
str) – Color space - ‘rgb’, ‘hsl’, ‘lab’, or ‘lch’ (default: ‘lab’)metric (
str) – Distance metric - ‘euclidean’, ‘de76’, ‘de94’, ‘de2000’, ‘cmc’count (
int) – Number of results to return (default: 5, max: 50)cmc_l (
float) – Parameters for CMC metric (default 2:1 for acceptability)cmc_c (
float) – Parameters for CMC metric (default 2:1 for acceptability)
- Return type:
- Returns:
List of (color_record, distance) tuples sorted by distance (closest first)
- class color_tools.palette.FilamentPalette(records, maker_synonyms=None)[source]
Bases:
objectFilament palette with multiple indexing strategies for fast lookup.
Similar to Palette, but designed for 3D printing filaments which have additional properties (maker, type, finish) that we want to search by.
The indices allow for fast filtering: “Show me all Bambu Lab PLA Matte filaments” becomes a simple dictionary lookup instead of scanning the whole list! 📇
Supports maker synonyms: you can search for “Bambu” and it will find “Bambu Lab” filaments automatically.
- classmethod load_default()[source]
Load the default filament palette from the package data.
Convenience method for quick loading without worrying about paths. Also loads maker synonyms automatically.
- Return type:
- find_by_maker(maker)[source]
Find all filaments by a single maker or a list of makers.
Supports synonyms: searching for “Bambu” will find “Bambu Lab” filaments.
- find_by_type(type_name)[source]
Find all filaments by a single type or a list of types.
- find_by_color(color)[source]
Find all filaments by color name (case-insensitive).
- Parameters:
color (
str)- Return type:
- find_by_finish(finish)[source]
Find all filaments by a single finish or a list of finishes.
- filter(maker=None, type_name=None, finish=None, color=None)[source]
Filter filaments by multiple criteria.
This is like SQL WHERE clauses! Start with all records, then filter down by each criterion that’s provided. Maker, type, and finish can accept a single string or a list of strings.
Supports maker synonyms: filtering by “Bambu” will include “Bambu Lab”.
- Parameters:
maker (
Union[str,List[str],None]) – A maker name or list of maker names (can use synonyms).type_name (
Union[str,List[str],None]) – A filament type or list of types.finish (
Union[str,List[str],None]) – A filament finish or list of finishes.color (
Optional[str]) – A single color name to match (case-insensitive).
- Return type:
- Returns:
A list of FilamentRecord objects matching the criteria.
- nearest_filament(target_rgb, metric='de2000', *, maker=None, type_name=None, finish=None, cmc_l=2.0, cmc_c=1.0)[source]
Find nearest filament by color similarity, with optional filters.
The killer feature for 3D printing! “I want this exact color… what filament should I buy?” 🎨🖨️
- Parameters:
metric (
str) – Distance metric - ‘euclidean’, ‘de76’, ‘de94’, ‘de2000’, ‘cmc’.maker (
Union[str,List[str],None]) – Optional maker name or list of names to filter by. Use “*” to ignore filter.type_name (
Union[str,List[str],None]) – Optional filament type or list of types to filter by. Use “*” to ignore filter.finish (
Union[str,List[str],None]) – Optional filament finish or list of finishes to filter by. Use “*” to ignore filter.cmc_l (
float) – Parameters for CMC metric.cmc_c (
float) – Parameters for CMC metric.
- Return type:
- Returns:
(nearest_filament_record, distance) tuple.
- nearest_filaments(target_rgb, metric='de2000', count=5, *, maker=None, type_name=None, finish=None, cmc_l=2.0, cmc_c=1.0)[source]
Find nearest N filaments by color similarity, with optional filters.
Similar to nearest_filament but returns multiple results sorted by distance.
- Parameters:
metric (
str) – Distance metric - ‘euclidean’, ‘de76’, ‘de94’, ‘de2000’, ‘cmc’.count (
int) – Number of results to return (default: 5, max: 50)maker (
Union[str,List[str],None]) – Optional maker name or list of names to filter by. Use “*” to ignore filter.type_name (
Union[str,List[str],None]) – Optional filament type or list of types to filter by. Use “*” to ignore filter.finish (
Union[str,List[str],None]) – Optional filament finish or list of finishes to filter by. Use “*” to ignore filter.cmc_l (
float) – Parameters for CMC metric.cmc_c (
float) – Parameters for CMC metric.
- Return type:
- Returns:
List of (filament_record, distance) tuples sorted by distance (closest first).
- get_override_info()[source]
Get information about user overrides in this filament palette.
- Returns:
{ "filaments": { "rgb": {"(r,g,b)": {"core": [sources], "user": [sources]}} }, "synonyms": { "maker_name": { "type": "replaced|extended", "old": [core_synonyms], "new": [user_synonyms] } } }
- Return type:
Dictionary with override information structure