Fix Ender-3 V3 KE nozzle switching by adding printer_variant
The four Creality Ender-3 V3 KE 0.X nozzle profiles were missing the
printer_variant key, so OrcaSlicer could not map a UI nozzle selection
to a specific profile and reverted every choice to 0.2mm. Add the
matching printer_variant to each, following the CR-10 SE / K1C pattern.
Introduce an 'indentation_check' job step to .github/workflows/check_profiles.yml that validates profile JSON files under resources/profiles use tab-only indentation (1 tab per level). The step runs an inline Python check, emits a log (indentation_check.log) with errors and a fix tip, and exits non‑zero on violations. Workflow conditionals for preparing the PR comment artifact, uploading the artifact, and failing the job were updated to include the new step so indentation failures are reported alongside other profile validation failures.
Fix extrusion of some support layers at wrong Z height
Fixes the issue that PR #12736 (github.com/OrcaSlicer/OrcaSlicer/pull/12736) reverted the changes of PR #13327 (github.com/OrcaSlicer/OrcaSlicer/pull/13327)
# Description
With this change, builds on ARM64 hosts will successfully kick off!
Unfortunately there are still missing/outdated dependencies, so the
build doesn't yet succeed. This makes it much easier to check what we're
failing on though.
I've also added the processor/arch to "Unknown X" error messages for
easier debugging.
Follow-up to #10183
Affects #5783
# Screenshots/Recordings/Graphs
N/A
## Tests
Kicked off `build_release_vs.bat` on my Windows ARM machine. Observed
progress in the build process!
<!--
> A guide for users on how to download the artifacts from this PR.
-->
[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
This PR supersedes #12225, which originally proposed this feature but
appears inactive.
The feature originated from work I developed earlier in
[BambuStudio-ZAA](https://github.com/adob/BambuStudio-ZAA), a private
fork of Bambu Studio
Compared to #12225, I updated the implementation for current upstream
and fixed the following issues:
- fixed broken tests
- removed references to nonplanar directory
Reviewers may want to compare against #12225 for earlier
discussion/context.
## Summary
Port of **Z Anti-Aliasing (ZAA)** from
[BambuStudio-ZAA](https://github.com/adob/BambuStudio-ZAA) to
OrcaSlicer.
ZAA eliminates visible stair-stepping on curved and sloped top surfaces
by raycasting each extrusion point against the original 3D mesh and
micro-adjusting its Z height to follow the actual surface geometry. The
result is visibly smoother domes, chamfers, and shallow slopes — without
post-processing.
## How It Works
1. The slicer runs normally, then a **posContouring** step processes
each layer
2. `ContourZ.cpp` raycasts every extrusion point vertically against the
source mesh
3. Each point's Z is adjusted to the mesh intersection, converting flat
`Polyline` paths into `Polyline3` paths with per-point Z coordinates
4. The G-code writer emits the adjusted Z values, so the printer follows
the true surface
## Configuration
Five new settings under **Print Settings > Quality**:
| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `zaa_enabled` | bool | off | Master enable/disable switch |
| `zaa_min_z` | float | 0.06 mm | Minimum Z layer height; controls
slicing plane offset |
| `zaa_minimize_perimeter_height` | float | 35° | Reduce perimeter
heights on slopes below this angle (0 = disabled) |
| `zaa_dont_alternate_fill_direction` | bool | off | Keep fill direction
consistent instead of alternating |
| `zaa_region_disable` | bool | off | Disable ZAA for a specific print
region/material |
## Key Changes
- **Core algorithm**: New `src/libslic3r/ContourZ.cpp` (~330 lines) —
raycasting engine
- **3D geometry**: `Point3`, `Line3`, `Polyline3`, `MultiPoint3` extend
existing 2D types
- **Arc fitting**: Templated to work with both 2D and 3D geometry
- **Pipeline**: `ExtrusionPath::polyline` changed from `Polyline` to
`Polyline3`; new `posContouring` step in `PrintObject.cpp`
- **G-code**: `GCode.cpp` writes per-point Z when `path.z_contoured` is
set
- **UI**: ZAA settings exposed in Print Settings > Quality panel
- **Documentation**: `docs/ZAA.md` with usage and implementation details
57 files changed, ~1800 insertions, ~200 deletions.
## Test Plan
- [ ] Load a model with curved top surfaces (sphere, dome, chamfered
box)
- [ ] Enable **Z contouring** in Print Settings > Quality
- [ ] Slice and verify G-code has varying Z values within contoured
layers
- [ ] Build on macOS (verified), test on Linux and Windows
`check_outside` (placement check) calls `ensure_imex_zones()` to make sure
zone geometry is current before deciding if an instance is in-bounds. In
the GUI path that's fine, but the CLI / headless 3MF-load path also reaches
this through `PartPlateList::load_from_3mf_structure -> reload_all_objects
-> add_instance -> check_outside`, and CLI mode has no GUI_App initialized.
`build_imex_cache_key` and `calc_imex_zones` both dereference
`wxGetApp().preset_bundle` — without a GUI_App, `wxGetApp()` returns memory
that segfaults on member access, killing the slicer with SIGSEGV before
any G-code is produced.
Latent since 461c69c83e (Apr 9), surfaced now that upstream's main carries
the headless regression-test CI step (#13353) that exercises CLI slicing
on every PR build.
Fix: short-circuit `ensure_imex_zones()` when `m_plater` is null (already
the GUI/CLI marker used by `calc_imex_ghosts`). Also tighten the existing
`build_imex_cache_key` null check to consult `m_plater` first as defence
in depth, so the function stays safe if reached from another headless
caller.
Verified locally against the upstream regression suite — klipper /
p1s_multicolor / toolchanger_4_color all slice cleanly within the
20% baseline tolerance.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Add OrcaCloud sync platform and preset bundle sharing system
Introduce OrcaCloud, a cloud sync platform for user presets, alongside
a preset bundle system that enables sharing printer/filament/process
profiles as local exportable bundles or subscribed cloud bundles.
OrcaCloud platform:
- Auth to Orca Cloud
- Encrypted token storage (file-based or system keychain)
- User preset sync with
- Profile migration from default/bambu folders on first login
- Homepage integration with entrance to cloud.orcaslicer.com
Preset bundles:
- Local bundle import/export with bundle_structure.json metadata
- Subscribed cloud bundles with version-based update checking
- Thread-safe concurrent bundle access with read-write mutex
- Canonical bundle preset naming (_local/<id>/... and _subscribed/<id>/...)
- Bundle presets are read-only; grouped under subheaders in combo boxes
- PresetBundleDialog with auto-sync toggle, refresh, update notifications
- Hyperlinked bundle names to cloud bundle pages
Co-authored-by: Sabriel Koh <sabrielkcr@gmail.com>
Co-authored-by: Derrick <derrick992110@gmail.com>
Co-authored-by: Mykola Nahirnyi <mnahirnyi@amcbridge.com>
Co-authored-by: Ian Chua <iancrb00@gmail.com>
Co-authored-by: Draginraptor <draginraptor@gmail.com>
Co-authored-by: ExPikaPaka <112851715+ExPikaPaka@users.noreply.github.com>
Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
Co-authored-by: Ocraftyone <Ocraftyone@users.noreply.github.com>
Co-authored-by: yw4z <ywsyildiz@gmail.com>
Co-authored-by: peterm-m <101202951+peterm-m@users.noreply.github.com>
* Fixed an issue on Windows it failed to login Orca Cloud with Google account
With this change, builds on ARM64 hosts will successfully kick off!
Unfortunately there are still missing/outdated dependencies, so the build doesn't yet succeed. This makes it much easier to check what we're failing on though.
The aggregated-mirror ghost was X-flipping about the mesh's local origin,
which shifted the ghost sideways for models whose local origin sits at a
corner (calibration cubes, calicat, most STL imports anchored at the
min corner). Visible as a constant left-X offset between the primary's
position and the ghost's position.
Pivot on `mo->raw_mesh_bounding_box().center()` instead, applied through
the instance transform so rotated objects flip about the rotated bbox
center too. Same correction applied to the live-drag update path.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Introduce ImexRole::Span as a 5th tile cycle state that declares "this
tool is the multicolor partner of Primary on the same gantry." Encoded
as the `S` role suffix in `imex_mode_active_tools` (e.g. `0:P,1:S,2:M,3:M`).
Disambiguates paired-gantry mc-mirror from 4-independent-copies — both
share the same active_tools shape sans the marker.
Span drives:
- Multicolor block rule: now requires Span on primary's gantry to allow
multi-color slicing in a parallel mode. Replaces the prior "≥2 tools
on primary's gantry" check; pre-existing 4-tool multicolor configs
need T1 flipped to Span.
- Ghost aggregation: one ghost per non-primary gantry when Span is
present, using the column-paired representative. Aggregated-mirror
drag tracks primary 1:1 in X (gantries don't share an X rail) with
X-flip baked into mesh-local frame so geometry still reads as mirrored.
- Zone aggregation: one full-X row strip per non-primary gantry instead
of per-tool quadrants.
- UI: 5th button in IMEXModesCtrl. Cycle Off→P→C→M→S→Off, only offered
on multi-gantry printers and only on tiles sharing primary's gantry row.
Single source of pairing truth: group_imex_active_tools_by_gantry in
IMEXHelpers, consumed by ghost factory and zone calculator.
Also fixes the carriage collision strip's X-boundary check, which lacked
the row constraint its Y-boundary counterpart already had — paired-gantry
mc-mirror was drawing a spurious right-edge strip from T3 sitting
diagonally from primary.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
_render_imex_ghosts and _picking_pass_imex_ghosts both walked every
plate in the partplate list and rendered/hit-tested all ghosts found —
which meant background plates' ghosts bled through into the active
scene whenever GL state was shared (most visibly when entering paint
mode), and clicks could land on a ghost that belonged to a plate the
user wasn't actually looking at.
Switch both paths to read get_curr_plate() and skip the per-plate loop.
Per-plate ghost volumes still live on each PartPlate so 3MF
round-trips work and switching plates picks up the new active plate's
ghosts cleanly; we just don't draw or hit-test the ones whose plate
isn't the active one.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Catches the case where a non-primary IMEX mode's active tools all sit on
one gantry — e.g. mode "0:P,1:C" on a 2x2 IQEX where T0 and T1 share
gantry 0. The "Copy"/"Mirror" label is decorative there: nothing actually
parallel-prints, but the user's mode_gcode still fires and emits firmware
setup that doesn't apply, while the slicer treats it as a multi-color
parallel print. Conceptually it's just a regular multi-tool single-gantry
print and belongs in Primary mode.
Changes:
- imex_multicolor_block_reason now collects the set of distinct gantries
spanned by the active tools and short-circuits with a clear "single
gantry — not a parallel-print scenario" message before falling through
to the existing within-gantry-swap check.
- Drops the redundant pre-slice "Multi-material objects detected" soft
warning from collect_imex_warnings — the slice-time block surfaces a
more specific message at the right moment, and the soft warning was
vague handwaving in front of it. Bed-temp + filament-type checks stay.
- New unit test covering the single-gantry block.
Behavior matrix on a 2x2 IQEX with multi-color:
"0:P,1:C" single gantry -> BLOCK (new)
"0:P,2:C" dual gantry, 1 each -> BLOCK (existing within-gantry-swap)
"0:P,1:C,2:M,3:M" dual gantry, 2 each -> ALLOW
multiple filaments to same physical via pem -> BLOCK (existing MMU sharing)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The mode-type tag (imex_mode_types config + imex_mode_type_for helper +
Split sentinel) was added in 0bb1cef as scaffolding for the Split rendering
work that landed in 4370cca and then got reverted in d9be71b. With Phase 2-4
gone, this scaffolding is now unused dead code — and the design we settled
on instead is to leave topology entirely implicit (parsed from active_tools_str)
rather than carrying a per-mode type tag the user would otherwise have to
manage explicitly.
The multi-color slicing block + bare T<n> suppression that were the actual
substance of the safeguards work stay in place:
- imex_multicolor_block_reason still allows multi-color exactly when 2+ tools
are active on the primary's gantry — Felix's hypothetical IQEX paired-gantry
case works through this path, no new mode type required.
- Slicer-side: bare T<n> stays suppressed at print-start in IMEX parallel modes;
mid-print T<n> emits naturally for the legitimate IQEX 4-tool-active scenario.
Removed:
- ConfigOptionStrings imex_mode_types (PrintConfig.hpp/cpp + Preset.cpp key list)
- imex_mode_type_for helper + kImexModeType{Primary,Copy,Mirror,Split} sentinels
- mode_type parameter on imex_multicolor_block_reason and the Split short-circuit
- mode_type plumbing in Print::validate and PartPlate::has_imex_multimaterial_conflict
- Three unit tests for imex_mode_type_for + two Split-specific multicolor block tests
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Completes the Split-type IMEX mode plumbing started in 0bb1cef. Phase 1
landed the config option, helper, validator, and plater badge updates;
this commit lands the visualization side and the editor surface so users
can actually create and use Split modes.
Zone aggregation (PartPlate::calc_imex_zones)
- When mode_type == "split" and there's row separation between primary
and secondaries, collapse copy_cells and mirror_cells per-gantry
(each cell gets primary's column) and force has_col_sep = false.
make_boxes then takes its full-X-row branch and renders one zone
covering each non-primary gantry's full Y band, instead of per-tool
quadrants.
Ghost aggregation (PartPlate::calc_imex_ghosts)
- Pre-scan the active mode's tool list to pick a canonical head per
non-primary gantry (the tool whose physical column matches primary's;
fallback to first-seen). For Split modes, the per-head emission loop
skips non-canonical tools so each non-primary gantry gets exactly one
aggregate ghost rendered at the canonical's mirrored position.
- update_imex_ghost_transforms unaffected — it iterates the already-
filtered ghost set.
Modes editor Type column (IMEXModesCtrl in Tab.cpp)
- Each non-primary mode row gains a wxChoice dropdown selecting Copy /
Mirror / Split. Primary row gets a static "Primary" label.
- Header row picks up a "Type" column header with sized spacer that
aligns with the dropdown.
- get_mode_data() now returns a fourth tuple element (types vector).
load_from_config() reads imex_mode_types via imex_mode_type_for so
legacy presets without the new option still infer types from mode
names. on_change writes imex_mode_types back to config.
- matches_config(), snapshot_row(), row_differs_from_parent(), and
reset_row_to_parent() all extended to track type alongside name/
tools/gcode — per-row reset arrows reflect type-only changes, and
reset restores the parent's type via imex_mode_type_for fallback.
- Type-choice dropdown fires notify() on change so the dirty/save flow
catches it like any other row edit.
End-to-end: switching a row to Split type in the modes editor, then
selecting that mode on a plate, allows multicolor slicing (the gantry-
pair check is bypassed in imex_multicolor_block_reason) and the bed
visualization shows a single aggregate zone + one ghost per non-primary
gantry instead of per-tool clutter.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Felix14-v2 PR review feedback (https://github.com/OrcaSlicer/OrcaSlicer/pull/13086#issuecomment-4323696312)
plus the slice-time validation work that follows from his bug list.
UI polish:
- Capitalize "Primary" in display (right-click mode menu, plate tooltip).
Sentinel stays lowercase for wire compatibility.
- Pre-slice-warnings checkbox now uses Orca's ::CheckBox so it matches the
green toggle style of the rest of the IDEX/IQEX configuration page.
- DPI-scaled the IMEXModesCtrl (modes editor) sub-panels, button grid,
text wraps, gcode textarea, and the ghost-tooltip swatch (imgui.scaled).
Legend swatches sized to body-text height for visual balance.
- Primary mode tool buttons in the modes editor are now disabled (read-
only): cycling roles on the IMEX-off mode is a no-op and confusing.
- Modes editor sub-panels now explicitly inherit the app's window-default
dark colour so chromeless ScalableButtons don't render with a visible
light box around their icons on GTK dark themes.
- Per-mode-line reset arrows in the modes editor: each row gets a small
reset bitmap that snaps that row's name+tools+gcode triplet back to the
saved preset's value. matches_config() guard on the page-level reload
prevents the textbox-being-typed-into from being destroyed mid-keystroke.
- New View menu item "Show IDEX/IQEX Toolhead" — toggles the per-carriage
footprint boxes during G-code preview playback. Gated to Preview tab +
IMEX printer; backed by app_config so it persists.
Coordinated config migrations:
- imex_tool_layout and imex_viz_theme migrate from coString to coEnum
(ImexToolLayout / ImexVizTheme). Existing wire format preserved so
saved presets and 3MFs deserialize unchanged. Side-benefit: both now
pick up standard Field rendering and so finally show reset arrows.
Slice-time safeguards (the validation half):
- imex_suppresses_bare_toolchange(parallel_mode, count): suppresses the
slicer's bare T<n> at print-start in any IMEX parallel mode (the user's
imex_mode_gcode + machine_start_gcode owns tool activation there). Mid-
print T<n> emits normally — Print::validate blocks the configurations
where mid-print T<n> wouldn't make sense. Applied to both code paths
inside GCode::set_extruder (the long multi-extruder path AND the
single-extruder path that fires when multiple_extruders=false).
- imex_multicolor_block_reason(): hard-stop validator returning a user-
facing reason string when the active IMEX configuration can't physically
support multi-color. Catches IDEX (1 tool/gantry), 2-tool-active IQEX
(no within-gantry swap topology), and any MMU/AFC lane sharing among
used filaments. Wired into Print::validate as a slice blocker, and into
PartPlate::has_imex_multimaterial_conflict so the plater badge agrees
with the slice block (no more false positives where the badge warns but
the slice goes through).
- New imex_mode_types config option (parallel array to imex_mode_names)
and imex_mode_type_for() helper. Mode-type tag drives behaviour: zones,
ghosts, and validation interpret modes differently per type. Initial
types: "primary", "copy", "mirror", "split". Split modes are explicitly
designed for paired-gantry IQEX multi-color and bypass the gantry-pair
check in imex_multicolor_block_reason (MMU sharing still blocks them).
Test coverage:
- New unit tests cover imex_suppresses_bare_toolchange (4 cases),
imex_multicolor_block_reason (8 cases including IDEX, IQEX 2-/4-tool-
active, MMU sharing, missing primary, Split type), and imex_mode_type_for
(3 cases including legacy fallback). Total: 186 IMEX assertions across
71 test cases, all passing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AFC now publishes an extruder_index field per lane. This lets Moonraker
auto-derive physical_extruder_map without user configuration:
- Add extruder_index field to AmsTrayData (-1 = not provided)
- Parse extruder_index from AFC lane JSON using contains() check since
0 is a valid extruder index (safe_json_int returns 0 for missing fields,
which would be ambiguous)
- After build_ams_payload, if all trays have extruder_index, derive
physical_extruder_map and write it to the printer preset config via
CallAfter so IMEX PA and temperature emission use the correct physical
extruder qualifier
AFC lanes sharing a carriage all report extruder_index=0; independent
direct-drive tools on separate carriages report their carriage index.
PrintApply.cpp's auto-derive from printer_extruder_id is skipped when
physical_extruder_map has more than 1 element, so the AFC-populated map
takes precedence.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>