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: object

Immutable 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:
name: str
hex: str
rgb: Tuple[int, int, int]
hsl: Tuple[float, float, float]
lab: Tuple[float, float, float]
lch: Tuple[float, float, float]
source: str = 'colors.json'
__str__()[source]

Human-readable color representation: name (#hex)

Return type:

str

__init__(name, hex, rgb, hsl, lab, lch, source='colors.json')
Parameters:
class color_tools.palette.FilamentRecord(id, maker, type, finish, color, hex, td_value=None, other_names=None, source='filaments.json')[source]

Bases: object

Immutable 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:
id: str
maker: str
type: str
finish: Optional[str]
color: str
hex: str
td_value: Optional[float] = None
other_names: Optional[List[str]] = None
source: str = 'filaments.json'
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)

property lab: Tuple[float, float, float]

Convert to LAB color space.

property lch: Tuple[float, float, float]

Convert to LCH color space.

property hsl: Tuple[float, float, float]

Convert to HSL color space.

__str__()[source]

Human-readable filament representation: Maker Type Finish - Color (#hex)

Return type:

str

__init__(id, maker, type, finish, color, hex, td_value=None, other_names=None, source='filaments.json')
Parameters:
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.

Parameters:

json_path (Path | str | None) – Path to directory containing JSON files, or path to specific colors JSON file. If None, looks for colors.json in the package’s data/ directory.

Return type:

List[ColorRecord]

Returns:

List of ColorRecord objects (core colors + 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.

Parameters:

json_path (Path | str | None) – Path to directory containing JSON files, or path to specific filaments JSON file. If None, looks for filaments.json in the package’s data/ directory.

Return type:

List[FilamentRecord]

Returns:

List of FilamentRecord objects (core filaments + 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.

Parameters:

json_path (Path | str | None) – Path to directory containing JSON files, or path to specific maker_synonyms JSON file. If None, looks for maker_synonyms.json in the package’s data/ directory.

Return type:

Dict[str, List[str]]

Returns:

Dictionary mapping canonical maker names to lists of synonyms

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:
  • name (str) – Palette name (e.g., ‘cga4’, ‘ega16’, ‘vga’, ‘web’, or custom user palette)

  • json_path (Path | str | None) – Optional custom data directory. If None, uses package default.

Return type:

Palette

Returns:

Palette object loaded from the specified palette file

Raises:

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: object

CSS 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:

Palette

find_by_name(name)[source]

Find color by exact name match (case-insensitive).

Parameters:

name (str)

Return type:

Optional[ColorRecord]

find_by_rgb(rgb)[source]

Find color by exact RGB match.

Parameters:

rgb (Tuple[int, int, int])

Return type:

Optional[ColorRecord]

find_by_hsl(hsl, rounding=2)[source]

Find color by HSL match (with rounding for fuzzy matching).

Parameters:
Return type:

Optional[ColorRecord]

find_by_lab(lab, rounding=2)[source]

Find color by LAB match (with rounding for fuzzy matching).

Parameters:
Return type:

Optional[ColorRecord]

find_by_lch(lch, rounding=2)[source]

Find color by LCH match (with rounding for fuzzy matching).

Parameters:
Return type:

Optional[ColorRecord]

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:

Tuple[ColorRecord, float]

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:

List[Tuple[ColorRecord, float]]

Returns:

List of (color_record, distance) tuples sorted by distance (closest first)

get_override_info()[source]

Get information about user overrides in this palette.

Returns:

{
    "colors": {
        "name": {"color_name": ("core_source", "user_source")},
        "rgb": {"(r,g,b)": ("core_source", "user_source")}
    }
}

Return type:

Dictionary with override information structure

class color_tools.palette.FilamentPalette(records, maker_synonyms=None)[source]

Bases: object

Filament 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.

Parameters:
__init__(records, maker_synonyms=None)[source]
Parameters:
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:

FilamentPalette

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.

Parameters:

maker (Union[str, List[str]]) – A single maker name (str) or a list of names (can use synonyms).

Return type:

List[FilamentRecord]

Returns:

A list of all matching FilamentRecord objects.

find_by_type(type_name)[source]

Find all filaments by a single type or a list of types.

Parameters:

type_name (Union[str, List[str]]) – A single filament type (str) or a list of types.

Return type:

List[FilamentRecord]

Returns:

A list of all matching FilamentRecord objects.

find_by_color(color)[source]

Find all filaments by color name (case-insensitive).

Parameters:

color (str)

Return type:

List[FilamentRecord]

find_by_rgb(rgb)[source]

Find all filaments by exact RGB match, with user filaments prioritized.

Parameters:

rgb (Tuple[int, int, int])

Return type:

List[FilamentRecord]

find_by_finish(finish)[source]

Find all filaments by a single finish or a list of finishes.

Parameters:

finish (Union[str, List[str]]) – A single filament finish (str) or a list of finishes.

Return type:

List[FilamentRecord]

Returns:

A list of all matching FilamentRecord objects.

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:

List[FilamentRecord]

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:
  • target_rgb (Tuple[int, int, int]) – Target RGB color tuple.

  • 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:

Tuple[FilamentRecord, float]

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:
  • target_rgb (Tuple[int, int, int]) – Target RGB color tuple.

  • 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:

List[Tuple[FilamentRecord, float]]

Returns:

List of (filament_record, distance) tuples sorted by distance (closest first).

property makers: List[str]

Get sorted list of all makers.

property types: List[str]

Get sorted list of all types.

property finishes: List[str]

Get sorted list of all finishes.

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