Skip to content

[Draft] Multi Print Head Capability#38

Open
Bjohnson131 wants to merge 10 commits into
vycdev:developfrom
Bjohnson131:mph-calc-develop
Open

[Draft] Multi Print Head Capability#38
Bjohnson131 wants to merge 10 commits into
vycdev:developfrom
Bjohnson131:mph-calc-develop

Conversation

@Bjohnson131

@Bjohnson131 Bjohnson131 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

How Multi-Head Processing Works

Multi-head printing loads N filaments at once and prints them in parallel, so colour changes happen at scheduled pauses rather than after every single run. Kromacut's job is to figure out the best way to group layers, assign filaments to nozzles, and arrange colors so the fewest pauses produce the most accurate result.

There are four main problems it solves, in order.


Glossary

  • Run: a series of layers that all use the same filament.
  • Window: A group of sequential runs, typically the number of runs equals the number of nozzles.
  • DP: Display Program, synonymous with 'algorithm'.
  • ΔE: The ΔE between an actual and a proposed layer configuration is the difference in errorFactor between the two.
  • N: when N is referred to, you can assume that's equal to the nozzle count.
  • K: the number of unique colors in a window
  • Quality score: See ΔE
  • ErrorFactor: The difference between the desired color, and the actual color that a series of layers will produce. Always a positive value, or 0.

1. Grouping Layers into Windows

Instead of swapping filament for every color change, the printer batches N consecutive color regions into a "window" and handles them all with the same N loaded filaments. It only pauses at window boundaries.

Before windows can be defined, Kromacut builds a run stack — a list of contiguous layer blocks where each block uses one filament. Colors are mapped to print heights by luminance (brighter = higher), then the full layer sequence is expanded at the printer's actual layer height. Where the filament index changes between layers, a new run starts.

01_window_run_diagram

Under the hood: selecting which runs become windows isn't just "take the first N, then the next N." Kromacut runs a dynamic programming pass over all candidate windows to find the non-overlapping subset that maximises a cumulative quality score. Each window gets an errorFactor — how well its N filaments cover the colours in that band — and the DP finds the combination of windows that maximises the total. Because windows are fixed-width (always N runs), the DP is O(n): the best previous non-overlapping window is always exactly N steps back, so there's no backtracking. The search also stops early once no remaining window improves the score by more than 0.01% — no point squeezing out tiny gains near the bottom of the pile. It will also end if no windows of >1 run long can be constructed.

Runs that don't land in any selected window are unclaimed and print with whatever filament was last loaded.


2. Assigning Filaments to Nozzles

Once windows are chosen and arranged appropriately, each window needs a filament assigned to each of its N nozzle slots. The goal is to match the target colors as closely as possible while reusing filaments across windows to minimize swaps.

02_nozzle_swap_schedule

Under the hood: this is split into two stages.

Stage 1 — what goes in each slot. For every window, Kromacut tries every possible combination of K filaments across the N layers — all K^N of them — and scores each one. The score is a perceptual color difference (more on that below) weighted by how many pixels in the frame use each color, so dominant colors matter more than rare ones. The top-scoring combination becomes that window's assignment.

Stage 2 — linking windows together. The per-window assignments are then threaded across the full print with a second DP. The state at each step is the N-tuple of currently loaded filament IDs. Moving from one window to the next costs one swap per nozzle slot that changes filament; idle slots carry their previous filament for free. The DP finds the sequence of assignments that minimises total swaps across the whole print.

One extra detail: individual runs inside a window can override the window-level consensus if a per-run slot assignment scores better. This lets two adjacent color regions in the same window use slightly different arrangements without forcing a full nozzle change.


3. Scoring Color Accuracy

FDM filaments are semi-translucent, so the color you see on a print isn't just the top filament — it's a blend of everything light passes through on its way in and out. The order filaments are stacked in directly changes the perceived color.

03_beer_lambert_blending

Under the hood: Kromacut models light transmission with:

T = 0.1 ^ (thickness / TD)

TD is the filament's transmission distance — the depth at which 10% of light still passes through. At one TD of thickness, 90% is absorbed; stack more layers and it drops fast. The perceived color at each depth is composited as:

result = filament_color × (1 − T) + background × T

This is evaluated layer by layer from the outside in, so the outer layers dominate and deeper layers contribute less and less.

Scoring uses ΔE in CIE Lab space (the CIE76 formula) rather than comparing raw RGB values. Lab is designed to match human perception, so a ΔE of 1 means a just-noticeable difference regardless of which part of the color spectrum you're in. This matters because two assignments with similar RGB error can look very different to a viewer — Kromacut optimises for what the eye sees.

For frontlit prints, the effective TD is multiplied by 0.1, compressing penetration depth to match how surface lighting works in practice.


4. Reducing Height Cliffs Between Columns (Spatial Variance)

The basic idea: when adjacent pixel columns are printed at very different heights, the side of the taller column is exposed to light at an angle rather than face-on. This bleeds color sideways and degrades the image near edges. The fix is to keep neighbouring columns close in height by grouping similar colors into the same print phase.

04_spatial_variance

Under the hood: colors are sorted by luminance and divided into M = ⌈K/N⌉ bands, one per phase. The starting assignment is simple: band[i] = floor(colour_index / N) after sorting. This is a reasonable first guess but not necessarily optimal, so it's refined by a local search.

The search evaluates swapping pairs of colours between adjacent bands. The quality metric is:

Σ (for all pairs A, B) W(A,B) × (band[A] − band[B])²

where W(A,B) = 1 / (luminance_distance(A,B)² + 0.0001). The weight is high when two colours are close in luminance — those are the ones most likely to appear side-by-side in the image, so pulling them into the same band has the biggest impact on cliff height. Any swap that lowers the total is accepted; the search repeats until no improving swap exists.

Once phases are assigned, the support layers within each bar follow a rotation formula: support_index = phase × N + (column_position + phase) mod N. This spreads support layers across nozzle slots so no two adjacent columns in the same phase pull from the same head — keeping the number of distinct colours at any single layer level at exactly N, matching what the printer has loaded.

Bjohnson131 and others added 3 commits June 15, 2026 01:22
Squashed feature branch: multi-head (toolchanger) nozzle assignment and
filament-swap scheduling, plus supporting work.

- Multi-head layer analysis (windows, nozzle assignments, color-first and
  spatial-variance optimization modes) and structured result plumbing
- Per-nozzle mesh tagging in the 3D preview and a head-swap schedule
- 3MF export for multi-extruder printers: identity filament_map (Manual mode),
  full per-extruder config arrays, flush matrix, filament_diameter, and
  custom_gcode_per_layer pauses at swap layers
- Misc: ortho/perspective camera toggle, mixed-colour layer Z-fighting fix

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ad schedule tests

Signed-off-by: Brice Johnson <1939015+Bjohnson131@users.noreply.github.com>
Signed-off-by: Brice Johnson <1939015+Bjohnson131@users.noreply.github.com>
@Bjohnson131

Copy link
Copy Markdown
Contributor Author

@vycdev Is this PR too large?

@vycdev

vycdev commented Jun 17, 2026

Copy link
Copy Markdown
Owner

@vycdev Is this PR too large?

Sorry I haven't really had a chance to look at this yet. From what I see at a first glance, you've done a lot of amazing work, so I look forward to testing this.

I'm gonna try to review it in the upcoming days, but it should be fine even if it's big.

Edit: If you think this is not ready for review yet, you can tell me, since I see this is still a draft.

@Bjohnson131

Copy link
Copy Markdown
Contributor Author

@vycdev I'll ping you when it's ready- probably in 1-2 days. I'm going through with a fine-tooth comb right now.

@vycdev

vycdev commented Jun 17, 2026

Copy link
Copy Markdown
Owner

@Bjohnson131

I was thinking about improving or adding new algorithms for auto-paint, do you think that would collide with this pull request? It matters if this is either simply a separate algorithm, or if its another step in the pipeline and integrates nicely with current algorithms while allowing them to be modified or new ones to be added without affecting this feature.

I was also wondering if number 4, Reducing Height Cliffs Between Columns, would've been better suited as a separate pull request of it it's deeply tied with the multi head printing feature.

I'm also interested how you made the graphs, because they look great and I think they can be used in the docs.

And I'm curious if your 3d printer swaps filament on the parked nozzles, aka the non active ones while it's printing. At least that's what I am assuming.

@Bjohnson131

Copy link
Copy Markdown
Contributor Author

I was thinking about improving or adding new algorithms for auto-paint, do you think that would collide with this pull request? It matters if this is either simply a separate algorithm, or if its another step in the pipeline and integrates nicely with current algorithms while allowing them to be modified or new ones to be added without affecting this feature.

It may, but not in a large way- the architecture of MPH is as a post-processor. It takes what auto-paint provides, and re-arranges things, creates windows, etc. The only real breaking thing would be if the data structures were fundamentally altered heavily.

I was also wondering if number 4, Reducing Height Cliffs Between Columns, would've been better suited as a separate pull request of it it's deeply tied with the multi head printing feature.

I can pull that out- spatial variance optimization is a beast of its own.

I'm also interested how you made the graphs, because they look great and I think they can be used in the docs.

I used python to make SVGs, and then converted them to pngs. Here's the script I used for spatial vairance: https://gist.github.com/Bjohnson131/b38f134125de0af37ef3537de6a125f1
I'll upload the rest later, along with the SVGs, I'd love to use them in the docs.

And I'm curious if your 3d printer swaps filament on the parked nozzles, aka the non active ones while it's printing. At least that's what I am assuming.

It does. TBH I hadn't considered doing this at all. right now, the .3mf file inserts gcode 'pause' commands every time filaments need to be swapped out, and then the user is supposed to go and change all the filaments according to the schedule. I hadn't considered a 'hot-swap' design... I'm sure that would be a lot faster, and more convenient to many, but right now that's not how it works. Would 100% be a good followup feature though.

Bjohnson131 and others added 5 commits June 17, 2026 22:52
Signed-off-by: Brice Johnson <1939015+Bjohnson131@users.noreply.github.com>
Strips the spatial-variance mode from the multi-head pipeline — algorithm
file, SVG diagram, type fields, and all UI/dispatch wiring — leaving
color-accuracy as the sole optimization path. The full implementation is
preserved on the feature/spatial-variance branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Brice Johnson <1939015+Bjohnson131@users.noreply.github.com>
Signed-off-by: Brice Johnson <1939015+Bjohnson131@users.noreply.github.com>
Covers computeColorOptimalAssignments, optimizeNozzleAssignments,
buildMultiHeadSchedule edge cases, and patchedLayersToPlan boundary
conditions. Renames two misidentified tests so they reflect the path
they actually exercise.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Bjohnson131 Bjohnson131 marked this pull request as ready for review June 18, 2026 05:40
@vycdev

vycdev commented Jun 20, 2026

Copy link
Copy Markdown
Owner

tests/multiHeadAnalysisColorFirst.test.ts contains an extra closing }); at line 659. This causes npm test, npm run build, and npm run lint to fail with TypeScript parse error TS1128. Please remove it so the new multi-head test module runs.

@vycdev

vycdev commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Non-blocking follow-up: Smooth meshing is not safe for a layer that has been partitioned into multiple colours. Each colour mask is passed separately to generateSmoothMesh, so both sides of a shared colour boundary are independently smoothed inward. This leaves the visible gaps shown in the preview and is present in the generated geometry, not just its shading.

I created a follow up issue for this #40

image

@vycdev

vycdev commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Multi-head analysis runs synchronously from the Build handler, so larger head/palette configurations freeze the UI. The Search depth selector currently has no effect: it is stored and rendered but never reaches the solver.

Please move the multi-head analysis off the main thread and make Fast, Balanced, and Thorough control a real bounded or sampled search strategy.

@vycdev

vycdev commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Multi-head Printing was added to DOC_NAV_GROUPS, but the matching source page, src/docs/multi-head-printing.md, is not included. The docs UI silently filters that unresolved slug out, so the feature has no in-app documentation despite the new diagrams and navigation entry.

Please add the documentation page.

@vycdev

vycdev commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Multi-head settings are not persisted to kromacut.autopaint.v1. After enabling Multi-head mode, changing Head count/Search depth, and reloading the app, the settings reset to their defaults (off, 4, Balanced).

Please include multiHeadMode, multiHeadCount, and multiHeadSearchDepth in the existing storage load/save payload, with backwards-compatible defaults for older saved data.

@Bjohnson131

Copy link
Copy Markdown
Contributor Author

Non-blocking follow-up: Smooth meshing is not safe for a layer that has been partitioned into multiple colours. Each colour mask is passed separately to generateSmoothMesh, so both sides of a shared colour boundary are independently smoothed inward. This leaves the visible gaps shown in the preview and is present in the generated geometry, not just its shading.

IMO this should be fixed before merging- I'll have some time tn to take a closer look.

Signed-off-by: Brice Johnson <1939015+Bjohnson131@users.noreply.github.com>
@Bjohnson131

Copy link
Copy Markdown
Contributor Author

Ive got a fix for all but the smooth meshing.

And let me say: i really need smooth meshing. Im doing my test prints, and it appears that filling in singular pixels is very difficult for the printer to do:PXL_20260623_140417012.RAW-01.COVER.jpg

PXL_20260623_140431095.RAW-01.COVER.jpg

PXL_20260623_140509122.RAW-01.COVER.jpg

Of course im printing at both the smallest pixel pitch as well as smallest layer height- just to make sure things work well at easier settings.

@vycdev

vycdev commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Hm, that doesn't look right, are you sure it's not also a problem with your printer/filament? It looks like the nozzle is digging too much into the print, and there's also lots of stringing

Signed-off-by: Brice Johnson <1939015+Bjohnson131@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants