Skip to content

prusa3d/prusa-fdm-mixer

Repository files navigation

prusa-fdm-mixer

A calibrated color-mixing model for FDM 3D printers that interleave filaments at the layer level. Predicts the visible color of a multi-filament print from the source filament hexes and their ratios, calibrated against measured Prusa XL prints.

No runtime dataset, ~50 lines of math, MIT-licensed — vendor it freely.

Live results, distributions, and per-recipe breakdown are published at prusa3d.github.io/prusa-fdm-mixer/apps/harness/. The harness re-renders on every commit, so the numbers there are always current — toggle between the training set, the held-out set, or all measurements. This README intentionally avoids hard-coding sample counts and ΔE figures, since the dataset grows over time.

Why this exists

This work started while integrating the OrcaSlicer-FullSpectrum multi-color FDM fork into Prusa EasyPrint and PrusaSlicer. Once the toolpath was producing real multi-filament prints, the slicer's preview colors visibly disagreed with the parts coming off the bed — bright complementary mixes in particular looked nothing like reality.

Digging in surfaced two compounding errors that any naive per-channel blend has:

  1. sRGB is gamma-encoded, so averaging in it adds spurious brightness.
  2. Real FDM prints darken by ~5–10% from inter-layer shadows that pure-RGB math doesn't see at all.

The result: previews come out brighter and more washed-out than the print actually does, especially for saturated complementary mixes (cyan + magenta, etc.) where the prediction is laughably wrong.

In parallel, BambuStudio shipped their own per-channel linear RGB mixing for multi-filament previews — same family of approach, same underlying issues. The gap is industry-wide, not a Prusa-specific quirk.

This repo ships a model that fixes both errors — empirically tuned against real measurements rather than from physics first-principles — and the infrastructure to keep extending it.

Development

Requires Node 20+ (matches the version pinned in .github/workflows/deploy.yml).

git clone https://github.com/prusa3d/prusa-fdm-mixer.git
cd prusa-fdm-mixer
npm install
npm run dev      # vite dev server with hot reload
npm test         # vitest
npm run build    # production build to dist/

For the drop-in C++17 implementation and its build instructions, see cpp/README.md.

Quick links

App What it does
Playground Interactive palette generator: pick extruders, set ratios, see predicted colors sorted by hue
Harness Score the prusa-fdm-mixer model against measured prints; compare against linear RGB, Kubelka-Munk, PolyMixer. Toggle between the training set, the held-out set, or all measurements
Gatherer Standalone tool to enter your own LAB measurements and export JSONL
Calibration NR200 colorimeter calibration — Tier 1 (3-point neutral linear fit), Tier 2 (24-patch ColorChecker 3×3 XYZ matrix), Tier 3 (batch Lab → display hex with three rendering variants)

All four apps are served from one static GitHub Pages build at prusa3d.github.io/prusa-fdm-mixer/; the apps/ source directories live under this repo.

Filament libraries

The playground browses real spools from two sources:

A daily GitHub Action refreshes both files and commits any changes.

To trigger a sync manually:

npm run sync             # both libraries
npm run sync:openprinttag # OpenPrintTag only
npm run sync:hueforge     # HueForge only

Or run the workflow from the Actions tab on GitHub. If your spool isn't in either library, the playground's "+ Custom hex" button still works for any hex you paste — the libraries are convenience, not a hard dependency.

Library usage

TypeScript / JavaScript

import { mixFilaments } from 'prusa-fdm-mixer';

const result = mixFilaments([
  { hex: '#009bc3', ratio: 0.5 },  // cyan
  { hex: '#f6b921', ratio: 0.5 },  // yellow
]);

console.log(result.hex);  // '#519e5f'
console.log(result.lab);  // { L, a, b }

The package also exports comparison models (mixLinearRgb, mixKubelkaMunk, mixPolyMixer) and color helpers (hexToLab, deltaE2000, chroma, hueDegrees).

C++ (PrusaSlicer / OrcaSlicer integration)

A drop-in C++17 implementation lives in cpp/. Single header + single source file, no external dependencies:

#include "prusa_fdm_mixer.hpp"

const std::vector<prusa_fdm_mixer::Part> parts = {
    {"#009bc3", 0.5},
    {"#f6b921", 0.5},
};
const auto result = prusa_fdm_mixer::mix(parts);
// result.hex, result.lab, result.rgb

See cpp/README.md for vendoring instructions and the 33-test suite.

How the prusa-fdm-mixer model works

  1. Yule-Nielsen base — gamma-decode each filament to linear-light RGB, raise to 1/n (n = 3.0), ratio-average, raise back to n. Standard halftone math.
  2. Lightness correction — measured prints are darker than YN predicts, especially when the input filaments span a wide L* range. Apply ΔL = -0.0477·L_gap - 2.112, plus an extra -0.060·(L_gap - 15) knee when L_gap > 15.
  3. Chroma correction — bright mixes lose saturation faster than dark mixes. Apply ΔC = 0.2780·predicted_L - 15.580 to scale (a, b).
  4. Cyan-band hue rotation — predictions in the cyan band drift slightly warm. Rotate by up to +10.38° at hue 210°, with linear fall-off ±30°.
  5. Bell-curve weighting — corrections scaled by w = N^N · ∏ratios (peaks at uniform mixing, zero at pure components) so pure colors are returned unchanged and gradients stay smooth.

All constants were fitted on the published fitting set (data/fitting-set.jsonl). The full methodology, including which alternatives were tried and rejected, is in docs/methodology.md.

Comparison to other models

The harness scores prusa-fdm-mixer side-by-side with Kubelka-Munk, the PolyMixer port (BambuStudio's current model, ex-OrcaSlicer-FullSpectrum), HueForge-style TD blending, and the legacy linear-sRGB blend that most slicers ship today. Live numbers — median ΔE2000, hit-rate at ΔE 5/8/10, distributions, and per-recipe breakdowns — are at prusa3d.github.io/prusa-fdm-mixer/apps/harness/.

prusa-fdm-mixer is the only model in the comparison where 3-color performance doesn't collapse versus 2-color. Linear sRGB is on the table because it's what slicers actually use today, not because it's a serious physics candidate.

Repository layout

prusa-fdm-mixer/
├── src/                    TypeScript model + comparison models
├── data/                   Fitting set + held-out validation set
├── apps/                   Three browser apps (playground, harness, gatherer)
├── cpp/                    Drop-in C++17 implementation + tests
├── tests/                  Vitest unit tests
└── docs/                   Methodology, results

Caveats

  • A held-out batch in data/holdout-set.jsonl was never seen during calibration; out-of-sample ΔE there is the honest performance number. The harness toggles between training, held-out, and all measurements.
  • Calibrated against Prusament PLA. Other brands and materials may differ.
  • 3-color predictions are extrapolated from 2-color fits with limited validation data. Treat as directional, not precise.
  • Bronze/galaxy/glitter "special effect" filaments mix less predictably than solid-color ones and are slightly over-represented in the harder tail of the error distribution.

License

MIT — vendor freely. See LICENSE.

Acknowledgements

About

Playground for Color measuring and computing color mixing

Resources

License

Stars

Watchers

Forks

Contributors