English | 简体中文
Turn a coloured OpenSCAD file into a Snapmaker Orca / FullSpectrum project 3MF
in which every color() scope is printed as a blend of the filaments loaded in
the four toolheads.
./scad2fs3mf part.scad -o part.3mf -c spools.json
# or, from the repository root / with the package on PYTHONPATH:
python3 -m scad_fullspectrum part.scad -o part.3mf -c spools.json
The tool is dependency-free Python 3 (stdlib only) and needs the openscad
executable on PATH.
openscad -o model.csg model.scadflattens the design. OpenSCAD's 3MF and AMF exporters throw colours away (3MF) or refuse multi-volume objects (AMF), but the CSG dump keeps everycolor([r, g, b, a]) { ... }scope.- The CSG tree is split per colour, one part per colour, honouring OpenSCAD's
boolean semantics: the base of a
difference()keeps its colour while the cutters are removed from every colour,intersection()keeps its bounding children, andhull()/minkowski()are attributed to the first colour inside them. - Each colour is matched against the loaded spools. A colour close to a spool
(ΔE₀₀ ≤
pure_threshold) uses that spool directly; anything else becomes a mixed filament built from up tocomponentsspools on astep-percent grid. Recipes are ranked by CIEDE2000 against the blend predicted by the selected mix model (average,pigmentortransmission, see below). - The project is written with the same layout the slicer itself produces:
3D/3dmodel.model(root object with one component per colour),3D/Objects/<name>_1.model(the meshes),Metadata/model_settings.config(one<part>per colour carrying itsextruder) andMetadata/project_settings.config(spool colours, mix definitions and every other setting copied from a template project).
Virtual filament IDs start after the physical spools: with four spools, mix row
k is extruder 4 + k, exactly the convention the slicer uses when it loads
mixed_filament_definitions.
{
"base_filaments": [
{ "slot": 1, "color": "#FF00FF", "name": "Magenta" },
{ "slot": 2, "color": "#00FFFF", "name": "Cyan" },
{ "slot": 3, "color": "#808080", "name": "Grey" },
{ "slot": 4, "color": "#FFFF00", "name": "Yellow" }
],
"uncolored": 3,
"mix": { "components": 4, "step": 5, "pure_threshold": 1.0, "max_mixes": null, "model": "average" },
"printer": { "bed": [270.0, 270.0] },
"template": "templates/project_settings.config"
}| Key | Meaning |
|---|---|
base_filaments |
the four spools actually loaded, in toolhead order; color is the filament's display colour |
uncolored |
what to do with geometry outside any color() scope: a hex colour or a slot index (default: drop it and warn) |
mix.components |
maximum filaments per recipe, 2–4 (default 4, including when the field is omitted) |
mix.step |
percentage grid for candidate recipes (default 5 %) |
mix.pure_threshold |
ΔE₀₀ below which a spool is used as-is (default 1.0) |
mix.max_mixes |
cap on virtual filaments; the perceptually closest recipes are merged until the cap is met (the reported blend and ΔE always describe the recipe actually assigned) |
mix.model |
blend model used for search and prediction: average, pigment or transmission (default average; see Blend models) |
printer.bed |
build plate size; the model is centred on it |
template |
project_settings.config or a sliced .3mf whose settings are copied (printer, process, filament presets, wipe tower, …) |
settings |
arbitrary overrides merged into Metadata/project_settings.config |
All built-in presets allow up to four components. A config that omits
mix.components (or the entire mix object) also defaults to four; explicit
config values and --components still take precedence. This is an upper limit,
not a requirement to use all four spools: simpler recipes can still win.
Four-component searches, especially with --step 1, cost more time and can
increase tool changes. Use --components 2 or 3 when that trade-off is preferable.
Spool sets ship with the tool, so no config file is needed to start:
./scad2fs3mf --list-presets
./scad2fs3mf part.scad -o part.3mf --preset translucent-cmyn
| preset | slot codes (1-4) | spools | model |
|---|---|---|---|
translucent-cmyn |
C M Y N | translucent cyan, magenta, yellow, neutral grey | transmission (experimental) |
pla-cmyk |
C M Y K | cyan, magenta, yellow, black | pigment |
pla-cmyw |
C M Y W | cyan, magenta, yellow, white | pigment |
pla-cmyn |
C M Y N | cyan, magenta, yellow, neutral grey | pigment |
pla-rybw |
R Y B W | red, yellow, blue, white | pigment |
A preset is named <material>-<codes>, the codes being the slot letters joined
in slot order: pla-cmyk, pla-cmyw, pla-cmyn, pla-rybw. The letters are
C cyan, M magenta, Y yellow, K black, W white, R red, B blue,
G green and N the neutral grey - so a six- or eight-spool set that
contains both green and grey cannot be misread, and a longer set simply joins
more letters. Only those letters are codes; anything else (a "natural" spool,
say) is spelled out. presets.validate() enforces the spelling.
Colours are idealised placeholders: measure your own spools and replace them.
Overriding a preset. Precedence is preset < config file < explicit flags, and
a config file replaces the keys it mentions: base_filaments is a list, so a
file that carries its own spools replaces the preset's spools and slot mapping,
while a key the file omits stays from the preset, and mix merges key by key.
To keep a preset's spools and only change the model, use the flag or a minimal
file:
./scad2fs3mf part.scad -o part.3mf --preset pla-cmyk --mix-model average
echo '{"mix": {"model": "average"}}' > model-only.json
./scad2fs3mf part.scad -o part.3mf --preset pla-cmyk -c model-only.json
Giving the spools from a file (or -c with a preset name instead of --preset)
is equally valid - just be aware the file's four entries win:
./scad2fs3mf part.scad -o part.3mf --preset pla-cmyk -c my-spools.json
./scad2fs3mf part.scad -o part.3mf -c pla-rybw
Sets with more than four spools are not shipped yet: the planner already accepts N contiguous slots, but the bundled template carries four-slot arrays, so such a project needs a matching settings template exported from the slicer.
python3 -m scad_fullspectrum --print-example-config prints the skeleton, and
--help lists the flags that override the config (--components, --max-mixes,
--mix-model, --template, --uncolored, …). Parameterised designs take
OpenSCAD defines directly:
python3 -m scad_fullspectrum examples/hue-wheel.scad -o wheel.3mf \
-c examples/hue-wheel.json -D segments=60
templates/project_settings.config is a full Snapmaker U1 settings snapshot, so
a generated file opens with the U1 printer, the "0.08 High Quality" process and
four PLA filaments. To inherit your own preset instead, pass a project you saved
from the slicer:
python3 -m scad_fullspectrum part.scad -o part.3mf -c spools.json --template my-project.3mf
# built-in preset
./scad2fs3mf examples/hue-wheel.scad -o /tmp/wheel.3mf --preset pla-cmyw
# or a config file (examples/hue-wheel.json documents the file format)
./scad2fs3mf examples/hue-wheel.scad -o /tmp/wheel.3mf \
-c examples/hue-wheel.json --report /tmp/wheel.json
The report lists, for every source colour, the chosen extruder, the recipe, the predicted blend and its ΔE₀₀.
Every ΔE₀₀ in the report and the CLI summary is a model prediction: it
compares a source colour with the blend the selected model computes for the
recipe, not a measurement of the printed part. The CLI prints a summary of it
(median, worst, how many colours are predicted above ΔE 10) and --report
carries the per-colour detail.
--mix-model, --step and --components widen or correct that search space.
The table below runs the 60-colour hue wheel with the shipped example's spool
colours. Component counts are explicit so these measurements do not depend on
the default (the idealised #00FF… values also need the caveat below):
| setting | mixes | median ΔE | worst ΔE | predicted > ΔE 10 |
|---|---|---|---|---|
average, --components 2 --step 5 |
40 | 6.4 | 22.1 | 20/60 |
average, --components 2 --step 1 |
49 | 6.2 | 22.1 | 20/60 |
pigment, --components 2 --step 5 |
42 | 4.0 | 21.2 | 19/60 |
pigment, --components 3 --step 1 |
50 | 3.8 | 21.2 | 19/60 |
transmission (experimental), --components 2 --step 5 |
42 | 1.2 | 8.0 | 0/60 |
transmission (experimental), --components 2 --step 1 |
57 | 0.3 | 1.6 | 0/60 |
The transmission rows only say that the wheel is reachable inside that
formula: the model is uncalibrated (see below), so they are not evidence
that these spools print the wheel. Only the average and pigment rows compare
against a model whose behaviour has an external reference.
Reproduce these with the shipped example:
./tools/bench_mixes.py examples/hue-wheel.scad --preset translucent-cmyn \
--models -D segments=60
--step 1barely moves accuracy for a saturated hue wheel under the opaque models (4 of 60 colours improve by more than ΔE 0.5) - it mainly stops neighbouring colours from collapsing onto the same 5 % grid point (colours sharing a slot: 17 → 8).--components 3gives no measurable gain on saturated hues, but is decisive for muted ones (#B0B0B014.9 → 2.2,#93A9D19.7 → 1.8 under the opaque models). Three-component rows load and display correctly in Snapmaker Orca 2.3.6; that verifies parsing and display, not the blend arithmetic.--max-mixes Nmerges the closest recipes; use it to cut tool changes, never to improve colour.
tools/bench_mixes.py part.scad spools.json --models runs one design through
every model/component/step combination and prints the same table.
Two claims that are easy to get wrong, and what the measurements actually say:
- Palette limits - under the opaque models magenta/cyan/yellow/grey cannot
reach a saturated red or blue (ΔE 13-22 there), because an opaque blend stays
inside the colour hull of the spools. Whether translucent spools do better is a
question about the printed part: the experimental
transmissionmode suggests the direction (stacked translucent layers filter light instead of averaging with it) but cannot answer it, and nothing here was checked against a print. - Four-component mixes - with four spools the hull is a non-degenerate
tetrahedron (
#808080is not on the magenta/cyan/yellow plane), so 2- and 3-component recipes reach only its edges and faces, and interior colours need all four. On the measured wheel this never mattered (saturated hues sit on the hull surface, so triples and quads never beat pairs there); do not generalise it to muted or desaturated designs.
Put the spools' measured hex values into base_filaments - the idealised
#00FFFF-style values make the prediction optimistic.
| model | what it assumes | use it for |
|---|---|---|
average |
weighted average in sRGB space | opaque filaments, quick runs; conservative for saturated results |
pigment |
the slicers' degree-4 pigment polynomial (FilamentMixerModel.hpp, MIT) |
predicting the swatch the slicer draws: two swatches sampled from Snapmaker Orca 2.3.6 for recipes this tool wrote are reproduced within 2/255 per channel, one byte-exact |
transmission (experimental) |
that the entered display hex values are transmittances of "one layer", that the substrate is a white backer, and that a zero channel behaves like 1e-6 |
making the direction of subtractive mixing visible while searching recipes; never as a colour guarantee |
transmission has no reference thickness, no measured absorption data and no
illumination model, so its ΔE compares the formula with itself. Calibrate it by
printing mixture swatches before trusting any of its numbers; the CLI prints a
warning and --report marks the run as experimental.
None of the models is a physical measurement: layer height, opacity (TD), purging, surface finish, the substrate and the viewing light all shift the printed result.
Translucent filaments filter light instead of covering it, so the opaque mental model ("average the colours") does not apply. What this tool can and cannot tell you:
- It can rank recipes under the selected approximation.
- It cannot predict the printed colour: no absorption spectra, no reference
thickness, no substrate and no illumination model.
mix.model: "transmission"is experimental - calibrate it with printed swatches.
Calibrate, in the viewing mode the part is for:
- Reflective viewing (lit from the viewer's side): measure pure and mixed
patches at the intended thickness, with the same backing, lighting and
viewing face. Keep recipe assignments fixed during calibration and reserve
separate recipes for validation. Display HEX values alone do not identify
absorption or scattering; changing
mix.stepormix.componentschanges the search space, not the physical model. - Transmissive / backlit viewing (thin walls, lampshades, diffusers - a documented use of translucent PLA): the part is judged by the light passing through it, so calibrate against backlight, not against a backer. Print the same swatch grid as thin walls and compare it lit from behind; a reflective white backing would defeat this mode, so do not force one here.
Practical notes that hold in both modes:
- Thin interleaved layers can combine spectral filtering, scattering and spatial averaging. Layer order, surface orientation and viewing distance matter.
- A grey spool is not necessarily a spectrally neutral filter. An opaque white spool or backing changes the optical system and requires separate calibration.
- Thickness can change hue, lightness and chroma; do not assume that saturation always increases.
- Purge/prime towers and travel moves leave translucent smears more visibly than opaque ones - keep wiping parameters generous.
Generate exact-recipe test projects without calling the colour solver:
python3 tools/make_panchroma_calibration.py calibration/runs/new-runThe output directory must not already exist. Slots are 1=Cyan, 2=Magenta, 3=Yellow, 4=Grey (N), not black. The script uses the bundled Snapmaker U1 0.4 mm printer template. Its vendor HEX/TD metadata comes from Polymaker's table; these values are neither transmittance spectra nor measured mixture colours.
Snapmaker's colour reference also supplies 39 photographed recipes and a downloadable 177-colour project. It is a visual reference, not a numeric mixture-measurement dataset. Its filament TD table uses the same HEX values but lists C/M/Y/Gray TD as 5.5/5.5/9.5/6.5, versus Polymaker's 8/7.8/14/11.5. Matching HEX does not establish optical equivalence between those products or batches; do not substitute either calibration blindly.
| Projects | Coupons | Purpose |
|---|---|---|
thickness-cyan, thickness-magenta, thickness-yellow, thickness-grey |
11 per spool | Pure-material thickness response from 0.16 to 15.36 mm |
mix-fit |
35 recipes + 2 repeatability controls | Complete 25% composition grid across 1-4 components, at 3.2 mm thickness |
mix-holdout |
20 | Separate two-, three- and four-component recipes, excluded from fitting |
layer-order |
6 | Equal-ratio stacks with reversed block order or interleaving; physical slots, not virtual mixes |
Open the 3MF files as projects. SCAD files are geometry references and cannot
preserve the explicit virtual recipes through the normal colour-solving CLI.
SVG maps identify the coupons from above, with the front at the bottom and a
clipped front-left corner on each coupon. Labels are not printed: photograph
the plate before removal and transfer IDs to containers or non-measurement edges.
samples.csv records ratios and layout. The general-purpose measurements.csv
supports instrument readings and camera-derived results; it is deliberately
blank apart from IDs and viewing modes. Neither it nor the instrument-specific
sheet below reads RAW images or imports measurements automatically.
manifest.json records the intended process and source metadata, not measured
results.
Before printing: verify the actual printer, loaded slot order and material temperatures. The calibration process explicitly sets 0.08 mm layers, a 0.16 mm first layer, 100% rectilinear infill, no ironing and no support. Keep these conditions fixed across calibration and validation. Check the wipe tower clearance and inspect the sliced tool assignments, especially the thin coupons. The virtual percentages are requests to the slicer, not measured extrusion fractions; retain G-code and note layer rounding and first-layer effects. The layer-order controls use 0.16 mm material blocks: normally two 0.08 mm layers, except the first block, which is one 0.16 mm first layer. No permanent backing or label geometry crosses the central measurement area.
Measurement: use the central 10 x 10 mm of each 20 x 20 mm coupon. Measure actual thickness without crushing thin samples. Record spool batch, print run, face, rotation and lighting. Keep white-backed reflection, black-backed reflection and backlit transmission as separate datasets. Reposition and repeat each reading at least three times; reprint selected controls to separate capture repeatability from printer repeatability. For photography, use RAW where available, fixed exposure/white balance, a colour reference, uniform lighting and an unclipped central region. Camera RGB without colour calibration is not an absolute Lab measurement, and camera channels cannot recover a full spectrum. A spectrophotometer with transmission capability is needed for spectral transmittance; scattering samples require attention to total versus direct transmission and measurement aperture.
Start with measured-recipe nearest-neighbour matching or interpolation within
the measured composition grid. Keep mix-holdout out of model fitting and report
its error separately. This kit primarily characterizes flat, top-viewed patches;
other thicknesses, sidewalls, backing materials and layer sequences require
their own validation. It does not install a calibrated colour model in the CLI.
Every generation also writes measurements-i1pro2.csv, backings.csv,
backing-references-i1pro2.csv and both instrument guides:
measurements-i1pro2-guide.txt (English) and
measurements-i1pro2-guide.zh-CN.txt (Simplified Chinese).
The guides are copied from the versioned
English instrument protocol template
and Simplified Chinese instrument protocol template;
edit these sources rather than generated copies when improving the shared workflow.
For the current 107 coupons, the instrument sheet reserves 642 readings:
white-backed reflection first, then black-backed reflection, with three
repositioned reads per coupon and backing. Each reading_id is unique, for
example TC01-W-01. It includes design metadata for cross-checking, not measured
extrusion fractions. The general and instrument sheets do not synchronize;
choose the instrument sheet for this workflow rather than entering results twice.
- Enter the actual
L_star,a_starandb_starvalues. Preserve signed values and acquisition precision. Blank means unmeasured, not zero. - Set
spectral_fileonly after exporting that reading's spectrum. Keep the original wavelength labels and units; recording Lab alone loses the spectrum. - Confirm the prefilled D50 / CIE 1931 2-degree observer, top face and 0-degree rotation against the actual setup. These are protocol defaults, not observations.
- Record actual thickness, print run, measurement session, backing, acquisition software/version, instrument identity and measurement condition. Do not copy the design thickness into a field intended for a physical measurement.
- The generator leaves
measurement_conditionblank. A run may prefill a chosen native condition for unmeasured rows, but this is only a protocol default: verify actual acquisition and never relabel existing results. D50 in the Lab computation does not imply native M1 or M2 acquisition. ArgyllCMS documents that its i1Pro 2 driver does not use the device UV mode; identify simulation or FWA compensation rather than reporting it as a native hardware condition.
Use the original calibration base for instrument calibration and a separate, identified sample backing. Use spot measurements in the central area, with stable support and repeatable instrument placement. Do not compress thin samples or scan across coupons of different heights. Save individual readings before averaging; orientation changes and reprints need separate, uniquely identified readings. Reflection spectra include the sample/backing/geometry combination; this is not a measurement of intrinsic absorption or total transmission. Backlit measurements require a separate protocol and are not in this sheet.
Bare-backing controls: backings.csv records each backing's material, batch,
paper stack, thickness, finish, used face and underlay. WB01 and BB01 are default
white/black identities, not certified optical standards. The coupon sheet
prefills these IDs; confirm the actual setup and use a new ID when it changes.
backing-references-i1pro2.csv separately reserves 12 unmeasured controls:
two backings x start/end of one session x three repeats. Fill the same
measurement_session in coupon and control rows; match both session and
backing_id, as well as acquisition and colorimetric conditions.
The control sheet has no coupon IDs or filament percentages. Keep it out of recipe fitting and holdout evaluation, and do not subtract backing Lab from coupon Lab. Preserve individual spectra and placement information. Append uniquely identified controls for further sessions or uniformity checks instead of replacing previous readings. The instrument protocol describes the fields.
For a budget-constrained run using copier paper, fixed-backing native M2 measurements can support an empirical calibration without purchasing a certified white board. Record the actual paper stack and bare spectrum; M2 reduces UV-excited fluorescence but does not make the paper neutral or fluorescence-free. Use the same acquisition condition for coupons and both bare backings. Keep M0/M1 and simulated conditions separate, and do not subtract the backing Lab or force it to an ideal white. Validate a different backing or illumination separately.
Keep reproducible source, local experiments and published measurements separate:
tools/make_panchroma_calibration.py # versioned generation logic
templates/measurements-i1pro2-guide.txt # versioned English measurement protocol
templates/measurements-i1pro2-guide.zh-CN.txt # versioned Chinese measurement protocol
calibration/runs/<run-id>/ # local, Git-ignored experiment files
calibration/datasets/<dataset-id>/ # curated measured data, when available
The generator requires a new output directory and never refreshes an existing
run in place. Archive an existing experiment by copying its entire directory
into a new calibration/runs/<run-id>/, retaining the original until the copy is
verified. Preserve manual projects such as thickness-cmyn.3mf; that file is not
an output of the current generator. A regenerated project is not a substitute
for the actual project or G-code used to print a measured sample.
Keep run-specific models, layouts, screenshots, raw captures, settings, measurement exports and verification records together. Copies do not synchronize: choose one working copy for subsequent readings. A copy checksum or historical verification report describes that snapshot, not future edits, physical colour accuracy or a new generator version. Git-ignored runs are not backed up by Git; retain separate backups.
Create a dataset directory only after real measurements are available. Promote
the selected readings together with their original spectra, exact samples.csv
mapping, run manifest, actual print/measurement conditions and spool batches.
Record the generator/template revision when known, and retain the exact printed
project and G-code in the associated archive; do not guess missing provenance or
rebuild historical mappings with the current generator. Keep holdout labels and
raw individual readings so validation remains independent and repeatability
can be assessed.
Review dataset paths, instrument serial numbers and third-party redistribution rights before publication. Include only the relevant measured data and provenance, not an entire working directory. Large photographs and printable project bundles can remain in separately archived or release assets. Empty recording sheets are generated outputs, not measurement datasets. CSV and JSON are not globally ignored, so curated datasets can be versioned normally.
- Alpha in
color([r, g, b, a])is ignored - the printer is opaque. hull()/minkowski()of several colours cannot be split meaningfully; the whole hull is printed in the first colour found inside it.- Every colour becomes a part with its own extruder, so a hundred-colour design
means a hundred virtual filaments and a lot of tool changes. Use
mix.max_mixesto merge similar colours. - Mixing is optical: thin alternating layers of filaments. What the tool reports is a model prediction - the printed colour also depends on the filaments' opacity (TD), layer height, temperature, speed and the base under the colour. Start with the 0.08 mm process the template ships, and print a swatch to calibrate before committing to a large piece.
- The snapmaker-orca CLI of build
01.10.01.50crashes (normalize_fdm) when it loads 3MF projects, so slice from the GUI.
python3 -m unittest discover -s tests
The suite covers the CSG parser/splitter (nested scopes, cutters, hulls, background/root modifiers), the row encoding, the solver/planning rules and the generated package (structure, extruders, patched settings, plate centring); the pipeline test runs OpenSCAD when it is installed.