Skip to content

Multi nozzle printing with per Extruder Layerheight#580

Open
LixNix wants to merge 7 commits into
Snapmaker:mainfrom
LixNix-Swap-Org:main
Open

Multi nozzle printing with per Extruder Layerheight#580
LixNix wants to merge 7 commits into
Snapmaker:mainfrom
LixNix-Swap-Org:main

Conversation

@LixNix

@LixNix LixNix commented Jul 12, 2026

Copy link
Copy Markdown

Description

This PR makes mixed-nozzle printing practical on multi-tool printers like the Snapmaker U1: each extruder can carry a preferred layer height (extruder_layer_height, allows selecting layer height via UI on a per extruder basis), and together with the existing Filament for Features selectors, each feature of a part prints with the layer height of the extruder assigned to it wherever physics allows — instead of everything being forced to the finest nozzle's layer height. The added filaments for features were ported from mainline Orcaslicer together with their requirements.

Slicing engine

  • A part follows one layer pitch, defined by its wall filaments' explicit preferences — or, when no wall filament carries one, by the agreement of its other pitch-bound feature filaments (top/bottom/internal solid). Runs of N base layers combine and extrude once at N× the object layer height; region tops/bottoms, overhangs and the first layer keep the finer base layers, and combined-away layers trigger no toolchange.
  • When the rest of the part vetoes the walls' pitch, the walls still combine on their own (walls-only pitch). Disagreeing explicit wall preferences meet at the lower height instead of silently falling back.
  • Full-density top surfaces absorb the internal solid layers below them and extrude once at their filament's preferred height.
  • Sparse infill keeps combining separately; at 100% density the combined solid interior is internal solid infill printed with the Internal solid infill filament.
  • Only a nozzle that physically cannot extrude a pitch vetoes it; per-extruder min/max layer heights are soft profile limits that warn instead of silently cancelling the feature.
  • Snapmaker mixed (virtual) filaments rotate physical extruders per layer and never combine.
  • Tool ordering and G-code dispatch classify wall extrusions per entity, so overhang-first inner loops, split mixed perimeters and wipe overrides route to the correct filament.

Validation & user feedback

  • Hard errors for wall preferences the printer cannot honor (below the object layer height, not an integer multiple of it, above the nozzle bore), for line widths below the combined heights, and for unsupported combinations (spiral vase, interface shells, variable layer height).
  • One-shot warnings when preferences are ignored or cannot all be honored, when a pitch prints below an extruder's minimum or above its maximum layer height, when disagreeing wall preferences meet at the lower height, and when a fine nozzle forces a part back to the object layer height.

GUI

  • The Prepare sidebar shows one tab per nozzle with a Diameter combo (sets only that nozzle's diameter, instead of switching the whole printer preset and forcing all nozzles to the same size) and a Preferred layer height combo listing the valid multiples of the current object layer height for that extruder. The tabs follow the printer's extruder count and refresh live on config changes.
  • Changing a nozzle's diameter automatically adopts the min/max layer height limits from the printer's profile for the new nozzle size when one exists (with a notification); otherwise a warning asks to review the limits manually. A preferred height that no longer fits through the new nozzle resets to Default.
  • The preview attributes extrusions to the per-feature filaments.

Profiles

  • Snapmaker U1 process profiles express line widths as % of nozzle diameter, so they scale with per-nozzle sizes.

Compatibility / breaking changes

  • No breaking changes: extruder_layer_height defaults to 0 (= use the object layer height) on every extruder, so existing profiles and projects slice exactly as before until a preference is set. Existing 3MF projects load unchanged.

Screenshots/Recordings/Graphs

Image 12 07 26 at 18 24 Image 12 07 26 at 18 40 Image 12 07 26 at 18 26 Image 12 07 26 at 18 27

Tests

  • New Catch2 suite tests/fff_print/test_multi_nozzle_layer_height.cpp ([MultiNozzleLayerHeight], 14 cases / 159 assertions) on a four-nozzle 0.2/0.4/0.6/0.8 printer config: whole-region combining, walls-only pitch, top-surface absorption, 100%-density interior ownership, nozzle-bore vetoes, disagreeing wall preferences meeting at the lower height, per-extruder minimum handling, and the validation errors.

  • End-to-end G-code verification with the real Snapmaker U1 profiles: walls assigned to the 0.8 mm extruder print in 0.48 mm runs (tool changes on a test cube drop from ~474 to ~45), combining top surfaces produce a single thick top pass, and the 100%-density interior prints with the Internal solid infill filament.

  • GUI changes exercised manually: per-nozzle tab rebuild on extruder count changes, live refresh of the valid layer height lists when the base layer height or limits change, notifications on nozzle size changes, and the wall-conflict warning popup.

  • Full fff_print suite: the feature-related cases all pass; the pre-existing failures in unrelated suites fail identically on the base commit.

  • Old PR (removed to to changes and issue in pr and branch): Multi nozzle printing #575

How to Download Pull Requests Artifacts for Testing

LixNix and others added 6 commits July 10, 2026 09:10
Port of the mainline OrcaSlicer feature chain to this fork (based on
Orca ~2.3.4):

- Fix Filament for Features (OrcaSlicer#13607)
- Fix + Support 'Default' filament option, index 0 (OrcaSlicer#13887)
- Filament for features for SEMM (OrcaSlicer#13937)
- Divided filament for features (OrcaSlicer#14042)

The coarse per-feature filament selectors are split into six independent
ones: outer_wall_filament_id, inner_wall_filament_id,
sparse_infill_filament_id, internal_solid_filament_id,
top_surface_filament_id and bottom_surface_filament_id. All default to 0
("Default" = use the active object/part filament) instead of the old
1-based scheme where "1" doubled as the default.

Backward compatibility: PrintConfigDef::handle_legacy() renames the old
keys (wall_filament, sparse_infill_filament, solid_infill_filament and
their pre-Orca aliases) on any config load - presets, 3mf project files
and imported G-code alike - and maps the legacy default value 1 to 0.

Fork-specific integrations kept working: the mixed-filament manager's
per-layer filament resolution (LayerTools resolve_mixed, pointillism and
grouped-manual-pattern splitting in GCode::process_layer, effective
filament remapping in LayerRegion/Fill), Local-Z clipped collections,
SSWCP/Flutter filament slot collection, and the object settings tab
resolver all route the new six selectors through the same paths the
three old ones used. Mixed perimeter collections are now split into
outer/inner sub-collections when the two wall filaments differ.
Port of LixNix/OrcaSlicer-multi-nozzle-size-printing commit 944da13
"Add per extruder layer height" onto this fork.

New printer option extruder_layer_height (per extruder): the preferred
layer height of each extruder, for printers whose extruders carry
different nozzle sizes. Object parts whose walls are assigned to an
extruder preferring an integer multiple N of the object layer height
print only on every Nth layer with N-times-thicker extrusions wherever
geometry allows (region slices are combined at the slicing step); sparse
infill assigned to such an extruder is combined up to that height
instead. New print options extruder_layer_height_mode
(consistent/adaptive) and extruder_layer_height_tolerance control the
run combining, and support_nozzle_diameter restricts support, raft and
interface printing to filaments of one nozzle diameter.

Includes: per-tool line widths on the wipe tower (WipeTower2), tool
ordering that skips combined-away layers and prefers nozzle-matching
filaments for flushing, per-feature-filament flow resolution (bottom
surfaces / external bridges / gap fill print with their own filament's
nozzle width), support flow and layer height limits honoring the
support nozzle restriction, and extensive Print::validate() checks.

Adaptations to this fork: no BBL filament-map rerouting (a filament
index is the extruder index on classic multi-tool printers), the
single-StringObjectException warning channel of this fork's
Print::validate(), mixed-filament (Local-Z / pointillism / grouped
patterns) integration kept working - resolved effective filaments feed
the new flow lookups and the wipe tower's Local-Z planning gained the
same per-tool widths. All new behavior is inert while the new options
stay at their defaults (0).

Tests: tests/fff_print/test_multi_nozzle_layer_height.cpp (ported to
Catch2 v2 and this fork's APIs).
…ment logic

Follow-up fixes from an adversarial review of the two ports:

- GCode::process_layer's fork-local dispatch lambdas
  (configured_filament_id_1based / configured_extruder_id) now mirror
  ToolOrdering's classification: the role of a fill collection is
  derived from its first non-gap-fill entity, pure gap fill dispatches
  to the outer wall filament, and external bridges print with the
  bottom surface filament. Without this, gap fill and external bridges
  were grouped under a different extruder than ToolOrdering scheduled
  and than their flow widths were computed for (inert at default
  settings, wrong once the per-feature filaments diverge).

- combine_infill() and its Print::validate() mirror pick the combining
  filament at 100% sparse density from the sparse infill filament,
  matching this fork's print-time dispatch
  (internal_solid_infill_uses_sparse_filament), instead of mainline's
  internal solid filament.

- Per-extruder layer height combining ignores Snapmaker mixed
  (virtual) filament ids: mixed rows resolve to a different physical
  extruder per layer, so regions whose walls use them never combine
  and no preferred height is derived from an out-of-range id.

- ToolOrdering::collect_extruders expands grouped manual-pattern mixed
  filaments for the inner wall exactly like the outer wall, so the
  wipe tower planner sees every extruder the grouped perimeter split
  will emit when the two wall filaments differ.
…d per-nozzle sidebar

Make the per-extruder preferred layer heights ("extruder_layer_height")
actually take effect on mixed-nozzle printers and give them a first-class
UI, so each feature of a part prints with the layer height of the
extruder assigned to it wherever physics allows.

Engine (libslic3r):
- A part follows one layer pitch, defined by its wall filaments'
  explicit preferences, or by the agreement of its other pitch-bound
  feature filaments (top/bottom/internal solid) when no wall filament
  carries one. Runs of N base layers combine and extrude once at N
  times the object layer height; region tops/bottoms, overhangs and the
  first layer keep the finer base layers.
- When the rest of the part vetoes the walls' pitch, the walls still
  combine on their own (walls-only pitch); disagreeing explicit wall
  preferences meet at the lower height instead of falling back.
- Full-density top surfaces absorb the internal solid layers below them
  and extrude once at their filament's preferred height.
- Sparse infill combines separately as before; at 100% density the
  combined solid interior is internal solid infill printed with the
  internal solid filament (PrintRegion::extruder()).
- Only a nozzle that physically cannot extrude a pitch vetoes it;
  min/max layer heights are soft profile limits that warn instead.
- Mixed (virtual) filaments rotate physical extruders per layer and
  never combine.
- Tool ordering and G-code dispatch classify wall extrusions per
  entity, so overhang-first inner loops, split mixed perimeters and
  wipe overrides route to the correct filament.

Validation (Print::validate()):
- Strict errors for wall preferences the printer cannot honor
  (below the object layer height, not an integer multiple, above the
  nozzle bore) and for line widths below the combined heights.
- Warnings when preferences are ignored, unhonorable, below an
  extruder's minimum or above its maximum, when disagreeing wall
  preferences meet at the lower height, and when a fine nozzle forces a
  part back to the object layer height.
- Per-extruder layer heights are rejected with spiral vase, interface
  shells and variable layer height.

GUI:
- The Prepare sidebar shows one tab per nozzle with a Diameter combo
  (sets only that nozzle's diameter instead of switching the whole
  printer preset) and a Preferred layer height combo listing the valid
  multiples of the object layer height for that extruder.
- Changing a nozzle's diameter adopts the layer height limits of the
  printer's profile for the new size when one exists and notifies the
  user; otherwise it warns to review the limits manually. The tabs
  follow the extruder count and refresh on config changes.
- The preview attributes extrusions to the per-feature filaments.

Tests: fff_print [MultiNozzleLayerHeight] scenarios cover region
combining, walls-only pitch, top-surface absorption, bore vetoes,
disagreeing wall preferences and validation errors.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EniRUwSktuaiWwd11GH7FP
@LixNix LixNix mentioned this pull request Jul 12, 2026
@LixNix

LixNix commented Jul 12, 2026

Copy link
Copy Markdown
Author

Works would appreciate feedback.

@LixNix LixNix changed the title Multi nozzle printing with per Multi nozzle printing with per Extruder layer height Jul 12, 2026
@LixNix LixNix changed the title Multi nozzle printing with per Extruder layer height Multi nozzle printing with per Extruder Layerheight Jul 12, 2026
Let the outer and inner walls print at their own layer heights when
they are assigned to filaments with different preferred layer heights
("Filament for Features" wall selectors + per-extruder layer heights).

Splitting is automatic whenever both wall filaments carry explicit
conforming preferred heights and one is a whole multiple of the other
(e.g. inner walls 0.24 mm, outer wall 0.48 mm over a 0.12 mm object
layer height): the finer class prints every time its height is reached
and the coarser class extrudes once per multiple at the full height, so
the two classes' tops stay flush. Either wall class may be the coarse
one, and the finer pitch may equal the object layer height itself.
Where geometry prevents a coarse run (shape drift, caps, first layer),
both classes fall back to printing together at the lower height.

When the two preferred heights do NOT divide evenly, the new "Adjust
wall layer height" option (split_wall_adjust, default off, in the
Filament for Features group) adjusts the wall-only layer height of one
wall filament to the nearest divisor or multiple of the other so the
walls can still split; landing on the other filament's height merges
the walls there instead. Two selections appear while the toggle is on:
which wall filament is adjusted (split_wall_adjust_filament) and
whether its height may only decrease or increase
(split_wall_adjust_direction). Unlike the preferred heights themselves,
adjusted heights are hard-bounded by the adjusted filament's min/max
layer heights and nozzle bore; without a legal candidate in the chosen
direction the walls keep merging at the lower height with the existing
conflict warning. The adjustment applies only to that filament's walls,
its other features keep the preferred height, and validation reports
the adjusted height once as an info message.

Implementation. The decision logic lives in one place,
PrintObject::wall_effective_multipliers() - the raw conforming
multipliers of the two wall classes plus the optional adjustment -
consumed by wall_layer_height_multiplier() (min-merge = the fine
cadence), wall_split_pitches() (unequal + divisible = split) and
Print::validate(). The fine cadence is the existing walls-only run
machinery unchanged. A second marking pass in
apply_extruder_layer_heights() groups whole fine runs into coarse runs
(same shape tolerance and rest-on-object tests, applied across the
whole span) and marks them on the LayerRegions
(wall_split_count/height). LayerRegion::make_perimeters() drops the
coarse class from the fine pass inside committed coarse runs -
classified with perimeter_entity_uses_outer_wall_filament(), the same
predicate the G-code dispatch uses, so cadence and filament routing
agree by construction - and on a coarse-run top regenerates the coarse
class once at the full height with the layer below the run as overhang
reference; both generator passes share one parameterized routine. Thin
/ gap fills and the fill boundaries stay with the fine pass. validate()
detects adjusted classes by comparing effective against raw
multipliers, keeps the min-merge conflict warning silent when a split
or adjustment resolved the disagreement, checks per-class heights
against line widths and max layer heights, and does not count an
adjusted class's own preference as unhonored. Mixed (virtual)
filament ids never combine and never split; the engine code stays
textually aligned with the mainline-Orca fork except for the mixed
guards and the direct filament-to-extruder mapping.

Tests: scenarios for the automatic divisible split (including the
fine-pitch-equals-object-layer-height case), the non-divisible fallback,
and the three adjustment outcomes (decrease to a divisor -> split,
increase onto the other wall's height -> merge there, no legal
candidate -> unchanged fallback with the conflict warning).
@LixNix

LixNix commented Jul 13, 2026

Copy link
Copy Markdown
Author

Something that i forgot to mention in explicitly in the support tap a user can select which nozzle sizes should print the support, so in case multiple things, for example, are printed with .2 nozzles, you can restrict support to just .2 nozzle sizes instead of just being able to select one filament to so support in that case, which would have increased print time.

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.

1 participant