Standalone CLI that runs both Snapmaker-Orca color-mixing algorithms on a table of target colors and reports each algorithm's best recipe side by side.
- FS — legacy Justin-Hayes degree-4 polynomial pigment blend
(
vendor/filament_mixer*, header-only model). - Prusa — calibrated Yule-Nielsen spectral model
(
vendor/prusa_fdm_mixer*), median ΔE2000 ≈ 5.7 vs real FDM prints.
The two algorithm files are vendored unmodified from the Snapmaker Orca
slicer — see vendor/PROVENANCE.md for the source
commit and file SHAs.
① Web UI (recommended) — edit palette/targets in the browser, see color swatches side by side, auto-recompute on every edit, export CSV when done:
./web/start.sh # builds CLI if needed, opens http://localhost:8008
Requires Python 3. No third-party packages (stdlib http.server only).
② CLI — batch-process CSV files directly (see Usage below).
For every target color in your spreadsheet:
- Normalize the target to 8-bit sRGB (from hex / RGB / Lab / CMYK).
- Run the FS reverse-match search → best
(filamentA, filamentB[, C], ratios)recipe + its predicted color. - Run the Prusa reverse-match search → best recipe + predicted color.
- Emit both recipes (hex, RGB, Lab, CMYK + ΔE2000) into one output CSV row.
Both algorithms use the same ΔE2000 yardstick (prusa_fdm_mixer), so their
ΔE values are directly comparable — this is the same scoring the slicer uses
internally.
Requires only a C++17 compiler. No cmake needed.
Windows (MSVC) — run from a x64 Native Tools Command Prompt for VS:
build.bat
Linux / macOS / MinGW:
./build.sh
Or with cmake:
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
Output: build/color_match_batch[.exe].
color_match_batch \
--palette samples/palette.csv \
--targets samples/targets_hex.csv \
--output results.csv \
--input-format hex
| Flag | Meaning |
|---|---|
--palette <file> |
CSV of physical filament colors (one #hex per line, optional ,label) |
--targets <file> |
CSV of target colors (first column = label, rest = color components) |
--output <file> |
Where to write the results CSV |
--input-format <fmt> |
hex / rgb / lab / cmyk — applies to the whole targets table |
| Flag | Default | Meaning |
|---|---|---|
--min-percent <n> |
0 |
Min per-component percent (0–50); higher excludes tiny admixtures |
--exclude "1-2,3-4" |
— | 1-based palette-id pairs to skip (incompatible materials) |
--rgb-scale auto|255|1 |
auto |
How to read numeric RGB columns |
palette.csv — one color per row, #hex first (or label,#hex):
#FF0000,Red
#00FF00,Green
targets.csv — first column is always a label; the meaning of the remaining
columns depends on --input-format:
| Format | Columns (after label) | Example row |
|---|---|---|
hex |
#RRGGBB |
orange,#FF8800 |
rgb |
R,G,B |
orange,255,136,0 (auto-detects 0-255 vs 0-1) |
lab |
L,a,b |
mid_gray,50,0,0 |
cmyk |
C,M,Y,K |
pure_red,0,100,100,0 (percent; auto-handles 0-100 vs 0-1) |
A leading header row (e.g. label,color) is auto-detected and skipped.
Each row is one target; columns (in order):
label,
target_hex, target_R, target_G, target_B, target_L, target_a, target_b,
target_C, target_M, target_Y, target_K,
fs_type, fs_ids, fs_weights, fs_hex, fs_R, fs_G, fs_B,
fs_L, fs_a, fs_b, fs_C, fs_M, fs_Y, fs_K, fs_delta_e,
prusa_type, prusa_ids, prusa_weights, prusa_hex, prusa_R, prusa_G, prusa_B,
prusa_L, prusa_a, prusa_b, prusa_C, prusa_M, prusa_Y, prusa_K, prusa_delta_e
fs_type/prusa_type:pair(2 filaments) ortriple(3 filaments).fs_ids/prusa_ids:/-joined 1-based palette indices (e.g.1/3).fs_weights/prusa_weights:/-joined integer percents summing to 100 (pair:pctA/pctB; triple:wA/wB/wC).- Color columns repeat for both target and each recipe's preview color.
Open results.csv directly in Excel, or paste-special it next to your source
sheet.
- Put your filament palette on one sheet → Save As → CSV →
palette.csv. - Put your target colors on another sheet → Save As → CSV →
targets.csv. - Run the tool (one format at a time).
- Data → From Text/CSV → import
results.csvonto a new sheet.
- FS triple blends are order-sensitive. The FS polynomial has no native
N-color form, so triples are folded left-to-right in filament-ID-ascending
order (matching the slicer's
blend_color_multi). Results are deterministic but would differ with a different fold order — this is inherent to FS. - Prusa handles N colors natively via its Yule-Nielsen weighted average; no folding artifact.
- Lab/CMYK targets lose precision when normalized to 8-bit sRGB (both algorithms operate on uint8 channels). ΔE from a Lab target to its best match therefore includes this quantization floor.
- CMYK uses simple device conversion (
R = 255·(1-C)·(1-K)), not ICC profile-aware. Fine for relative comparison, not for print-accurate prediction.
color-mixer-batch/
├── CMakeLists.txt optional cmake build
├── build.bat one-shot MSVC build
├── build.sh one-shot g++/clang build
├── README.md this file
├── src/ tool sources
│ ├── main.cpp CLI + CSV I/O
│ ├── match_search.{h,cpp} reverse-match search (port of slicer logic)
│ └── color_io.{h,cpp} hex/rgb/lab/cmyk parsing + formatting
├── vendor/ vendored algorithm files (see PROVENANCE.md)
│ ├── filament_mixer.h / .cpp / filament_mixer_model.h
│ ├── prusa_fdm_mixer.hpp / .cpp
│ └── PROVENANCE.md
└── samples/ example CSVs
├── palette.csv
├── targets_hex.csv
├── targets_rgb.csv
├── targets_lab.csv
└── targets_cmyk.csv
web/
├── app.py stdlib-only HTTP server; spawns CLI, returns JSON
├── start.sh / start.bat build-if-needed + launch
└── static/
└── index.html single-page UI (no framework, vanilla JS)
The server writes temp CSVs, invokes build/color_match_batch, parses the
42-column result back into JSON, and the page renders color swatches + recipe
tables. Every edit triggers a debounced recompute (400ms) — the CLI runs in
<100ms for typical palettes, so it feels instant. The page auto-loads demo
palette/targets on first visit; edit freely or paste your own.