Skip to content

Multi Print Head Capability#38

Open
Bjohnson131 wants to merge 1 commit into
vycdev:developfrom
Bjohnson131:mph-calc-develop
Open

Multi Print Head Capability#38
Bjohnson131 wants to merge 1 commit 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

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

@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

@Bjohnson131

Copy link
Copy Markdown
Contributor Author

@vycdev This is ready for re-review. the smooth meshing works a LOT better now :)

@vycdev

vycdev commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Thanks for the updates. I re-reviewed current head 6b194f9. The smooth meshing/export work looks much healthier now, and npm test plus npm run build both pass.

Remaining items I found:

  1. npm run lint still fails.

src/hooks/useSwapPlan.ts:132 includes multiHeadWindows in the first useMemo dependency list even though that memo does not read it. Because lint runs with --max-warnings=0, this warning blocks CI.

  1. Multi-head print instructions can disappear after remount.

ThreeDControls initializes local multiHeadWindows to [], then passes that local value into useSwapPlan, while the other schedule inputs come from persisted. After building, switching away from 3D and back can leave multiHeadPlan null even though the built/persisted state still has the schedule data.

Also, PrintInstructions has a multiHeadMode fallback message, but ThreeDControls never passes multiHeadMode, so the UI can fall back to single-head instructions instead of showing the multi-head “build schedule” state.

  1. The multi-head docs page is still missing.

DocsPage.tsx references the multi-head-printing slug, but there is no src/docs/multi-head-printing.md. Since docs are bundled from actual Markdown files and missing slugs are filtered out, the new feature still has no in-app docs page.

  1. useMultiHeadWorker should clean up on unmount.

The hook terminates the worker when a new run starts or when the worker responds, but it does not have an unmount cleanup effect. If the user leaves the 3D panel while analysis is running, the worker can continue running and then try to update state after unmount.

@Bjohnson131
Bjohnson131 force-pushed the mph-calc-develop branch 2 times, most recently from ff05571 to bd5fe0d Compare July 11, 2026 05:04
Squashed the mph-calc-develop feature branch prior to rebasing onto
upstream/develop.

Signed-off-by: Brice Johnson <1939015+Bjohnson131@users.noreply.github.com>
@Bjohnson131 Bjohnson131 changed the title [Draft] Multi Print Head Capability Multi Print Head Capability Jul 14, 2026
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