Skip to content

Orca 2.5 per extruder layer height - #802

Open
LixNix wants to merge 40 commits into
Snapmaker:feature_multi_extruders_devfrom
LixNix-Swap-Org:orca-2.5-per-extruder-layer-height
Open

Orca 2.5 per extruder layer height#802
LixNix wants to merge 40 commits into
Snapmaker:feature_multi_extruders_devfrom
LixNix-Swap-Org:orca-2.5-per-extruder-layer-height

Conversation

@LixNix

@LixNix LixNix commented Aug 31, 2026

Copy link
Copy Markdown

Per-extruder layer heights and multi-nozzle-size printing

Adds independent nozzle sizes and per-extruder layer heights for the Snapmaker U1, on top of the 2.5 mainline sync already merged via #801.

Multi-nozzle-size printing

  • Each nozzle can use its own diameter; the Prepare sidebar selects the diameter per nozzle instead of switching the whole printer to a single-diameter variant
  • U1 profiles express line widths as a percentage of nozzle diameter, so mixed sizes extrude with correct widths
  • Support material selection for mixed nozzle sizes; support materials available for every printer, with the legacy selectors kept behind a toggle

Per-extruder preferred layer heights

  • Per-nozzle "Preferred layer height" in the sidebar: regions print at their filament's preferred height by combining object layers
  • Fixed extruder layer height mode, plus a "thick layer regions" control (fixed mode, 80% tolerance by default)
  • Optional split wall layer heights with wall height adjustment, so walls can stay fine over thick combined infill
  • Combining-engine correctness fixes: bottom surface detection over combined groups, region-boundary geometry, and internal solid / sparse infill combining that honors preferred heights and the minimum layer height

Old PR for 2.3.5 just for reference: #580
More infos available in old PR and on Lark in private chat.

LixNix and others added 30 commits July 7, 2026 17:58
Printing with different nozzle sizes is now possible and each extruder can now print at a different semi fixed / semi adaptive layer height depending on user choice.

Includes:
         - 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)
         - Port of LixNix/OrcaSlicer-multi-nozzle-size-printing commit 944da13
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
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).
Add a third "Thick layer regions" choice next to Consistent and
Adaptive: Fixed. Regions assigned to a thicker-pitch extruder always
print at the extruder layer height: the run walk skips the shape
drift tolerance and the rest-on-below overhang fallback, so curved
boundaries become steps of the run shape instead of dropping to finer
layers. Only geometry that cannot span a whole run keeps finer
heights: the first layer, region tops, lateral jumps, and columns
whose surviving intersection is too thin to carry a bead of the
region's nozzle. The thick layer tolerance field grays out in fixed
mode, as it no longer participates.

The step faces such combined groups expose are classified correctly:
a group prints only its layers' common shape, and the combined-away
layers' geometry outside it becomes the group's step. Surface
detection compared against Layer::lslices, which carry the uncombined
shape, so those step faces counted as covered - no top skin or solid
backing formed and sparse infill showed through on stepped slopes,
and bridges over such steps went undetected. Each combined-away layer
now remembers its unprinted remainder; detection classifies the faces
it exposes as top / bottom on the printed layers around it, and a
combined group's own bridges check the layer below the whole group.

Covered by pyramid scenarios comparing fixed against consistent mode
and a stepped-cube regression test for the step top surfaces.
Fixed mode is the intended everyday behavior for per-extruder layer
heights, so ship it as the default; the tolerance default rises to 80%
so consistent and adaptive combine generously when selected (fixed
ignores it). The test suite pins the previous consistent / 10%
baseline explicitly, as its scenarios are written against it.
Geometry starting inside a combined group could count as supported and
print as sparse infill over air, depending on where the group phase
fell. Track the groups' unprinted remainders (run top included) and
use them in the unsupported-bottom check.
Refactor the feature code for review: shared filament-to-extruder
mapping helpers, deduplicated filters and lower-layer walks, folded
duplicate blocks. No behavior change.

At a filament boundary a region's per-layer geometry can lose its
support to a neighboring region's combined group: the support prints
later (at the run top) or never (dropped remainders). Classify bottoms
over any region's unprinted remainder as unsupported bridges; drop
pieces that would extrude into thin air before the covering pass exists
so the column resumes - bridging, anchored - at the pass; where a
piece's only anchor is the pass committing at its own layer, fill the
object volume with the pass (welded into its shape) and resume fully
supported on top of it.
…height

Internal solid infill was not covered by any combining pass: whole-region,
walls-only and split-wall combining skip it, combine_top_surfaces() only
absorbs it under a full-density top, and combine_infill() reaches it only
at 100% density. Interior and shell-backing solid always fell back to the
object layer height - silently below the assigned extruder's minimum layer
height when a coarse nozzle prints it (0.12 mm courses on a 0.8 mm nozzle
whose minimum is 0.16 mm).

Add a combining pass for the leftover internal solid: groups anchor
top-down at the top of each solid column (shell bands sit wherever their
surface is, not on a fixed grid) and extrude once at the solid filament's
preferred height, leaving VOID surfaces behind like the other passes.
Leftover bands re-group over ever shorter runs down to the filament's
minimum layer height, so odd-length bands don't strand a below-minimum
layer. Only the single-layer seat an internal bridge rests on - tied to
its own layer - and the bottom surfaces keep the object layer height,
which Print::validate() warns about.

Print::validate() mirrors the pass: the internal solid pitch feeds the
per-feature height checks (line width, max/min layer height, unhonored
preferences) like the top-surface pitch does.

Reported-by: jiyang1018 (https://github.com/jiyang1018)
…eight

With an explicit preferred layer height, sparse infill combined on a fixed
window grid: windows align to the global layer stack, not to where an infill
band starts at a shell boundary, so band-edge layers fell out of every window
- and out of the min-height re-group pass, which used the same grid - and
printed at the object layer height, below the infill extruder's minimum layer
height when one is set.

Extract the anchored run machinery of combine_internal_solid_infill() into a
shared combine_surface_runs() and use it for the preference-driven infill
path too. The full-height sparse runs stay anchored on one global grid phase,
so a contiguous field extrudes on the same layers everywhere (independently
anchored areas would combine phase-shifted wherever their band tops differ,
leaving a permanent one-course step along the seam). Band edges re-group over
ever shorter runs down to the extruder's min layer height, with one-layer
lookaheads at both band ends splitting off pairs instead of stranding a
below-minimum single layer. Internal solid keeps free band-top anchors - its
short shell bands sit wherever their surface is and would mostly miss a fixed
grid. The plain infill_combination option keeps the stock window grid.
Enabling support on a printer whose extruders have differing nozzle
sizes opens a dialog asking which nozzle size prints the support and
which loaded filament types serve as the raft/base and the interface.
The choices land in the support nozzle diameter option and two new
print options, "Support/raft base material" and "Support/raft interface
material", shown in Support -> Filament for Supports; the legacy
base/interface filament selectors keep working behind a "Show legacy
filament selection" toggle so older projects stay valid.

The material options act as exclusion filters, mirroring the support
nozzle diameter restriction: extruders loaded with another material
type are not used for the role, a "default" (0) support filament still
resolves per layer (a passing filament already printing the layer is
preferred, with a deterministic fallback otherwise), and an explicitly
selected support filament stays authoritative. Validation rejects a
material no loaded filament matches and an explicit selection that
conflicts with the material.
Add translations to every shipped language for the UI strings the
multi-nozzle work introduced: the per-extruder layer height options and
their warnings and validation messages, the split-wall options, the
support nozzle diameter restriction, the support material options, the
mixed-nozzle support dialog and the legacy filament selection toggle.

Translated with AI so there might be incorrect translation.
Show the support/raft base and interface material options for every
printer, not only mixed-nozzle ones, so multi-filament setups with a
single nozzle size can use them too; they stay visible like the other
support rows and merely disable while neither support nor a skirt needs
them. The legacy base/interface filament selectors are hidden unless
the "Show legacy filament selection" toggle is on. Ticking the toggle
takes effect immediately, and opening a 3mf project that assigns either
selector - globally, per object, per part or per height range -
switches the toggle on, so explicit selections from older projects stay
visible and editable. Nothing else changes the toggle.

Also fix a crash to desktop after visiting the Support page: the toggle
checkbox is cached to keep its state in sync, but the page's controls
are destroyed when another page activates, so the cached pointer
dangled; it now clears on wxEVT_DESTROY.
Translation for added features and improved support material selection.
Resolves conflicts between the multi-nozzle feature-filament migration
(legacy wall/infill filament keys replaced by *_filament_id selectors
with 0 = Default) and upstream's temp-mixing detail/flow-ratio-zero
plate gating:

- Plater.cpp: keep upstream's structure (FilamentTempMixingDetail
  overload, used_slots_0_based) with this branch's selector-override
  slot collection; port the same collection logic into upstream's new
  collect_used_filament_slots_on_plate and check_flow_ratio_zero so
  cold-plate and flow-ratio checks see the migrated selector keys.
- Snapmaker.json: take upstream's newer profile version 02.02.55.02.
- zh_CN/zh_TW po: union of both sides' new entries (msgfmt-clean).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Combine the mainline-synchronized Snapmaker Orca base with the
per-extruder layer height and multi-nozzle-size printing features:
per-tool preferred layer heights, nozzle-aware tool ordering and wipe
tower widths, support filament restrictions, and the associated UI and
test coverage.

AI assistance (Anthropic Claude) was used for porting and conflict
resolution.
check_volumes_outside_state() dereferenced its ObjectFilamentResults
argument unconditionally, but several callers use the default null
argument (e.g. plate validation triggered from the canvas context-menu
path). Route null callers to a local results object. Also guard the
per-extruder printable-area lookup in the unprintable-error text
builder: not every printer profile defines extruder_printable_area.
- Flush-volume auto-calculation: iterate physical filaments only (the
  flush matrix and minimum flush volumes are sized by the physical
  count) and clamp all matrix accesses.
- Plate filament-to-extruder mapping: bounds-check the filament map and
  physical extruder map lookups; return -1 for out-of-range ids.
- Plate list import: stop when the plate limit is reached instead of
  indexing with a failed create result.
- Printer preset switch: tolerate an unknown target preset or missing
  extruder-variant option; default to not transferring variants.
- Object list: clear the real UI-to-3D volume map entry (the loop-by-
  value clear was a no-op), and bounds-check remapped volume indices
  before dereferencing.
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