# Description
Restore `"reduce_infill_retraction": "1"` in the shared Bambu Lab
process profile. The BambuStudio profile sync (#15851) replaced it with
`reduce_infill_retraction_mode`, which OrcaSlicer does not support,
causing the setting to fall back to disabled.
This restores the pre-sync behavior and bumps the BBL bundle version to
02.08.00.13.
# Validation
- All 233 selectable BBL process presets inherit the restored setting
with no other resolved settings change.
- Static profile validation, system loading, filament validation, and
all 1,263 slice cases pass.
Fixes#16194
<!--
> 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)
# Description
Orbiting or panning the 3D view no longer hitches every five seconds on
Windows and macOS while signed in to Orca Cloud. The stutter is a
regression from #15710.
No change to slicing or to the Orca Cloud connection status shown in the
GUI.
# Screenshots/Recordings/Graphs
<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->
## Tests
<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->
<!--
> 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)
## Summary
This PR adds first-class parallel printing support to OrcaSlicer for
printers with multiple independent X-axis carriages — the **IDEX** and
**IQEX** hardware families. The feature is named **IMEX** (independent
multi-extruder) internally, which captures the supported topology space
more accurately than either acronym alone, and is exposed in the UI
under the user-facing label **IDEX/IQEX Configuration**. It is designed
to be printer-agnostic and firmware-flexible, with Klipper,
RepRapFirmware, and Marlin all supported for per-tool Pressure Advance
and per-layer temperature management. MMU/AFC setups where multiple
logical filament slots share one physical extruder are supported via a
`physical_extruder_map` profile option. Printers whose firmware handles
copy/mirror placement internally — RepRapFirmware IDEX duplication mode
on Flashforge Creator Pro 2 / Creator 3 Pro is the canonical example —
are supported through a firmware-managed-zones flag that emits a
centered single-half slice for the firmware to fan out.
The implementation spans printer configuration, process settings UI, bed
visualization, per-plate mode selection, ghost-object rendering with
per-plate filament overrides, placement validation, pre-slice conflict
warnings, G-code generation (with per-tool PA, per-layer temperature
management, and optional center-origin slice frame), and layer preview
animation. It supports five distinct topology paradigms:
- **1 gantry × 2–4 tools** — classic IDEX with 2 tools (BCN3D Sigma,
Snapmaker J1, Tenlog Hands-2 style — two independent X carriages on a
shared Y gantry); multi-extruder on a shared rail when extended to 3 or
4.
- **2 gantries × 1 tool each** — two fully-independent XY systems
sharing a bed (Vivedino Xplorer style). Both Y rails independent —
effectively "half an IQEX."
- **2×2 grid, independent quadrants** — IQEX running four separate
prints in parallel.
- **2×2 grid, paired-gantry multicolor** — IQEX where cross-gantry tools
share color responsibilities via the **Span** tile state.
- **Firmware-managed center-origin** — slicer emits a centered
single-half slice; firmware decides where to physically fan it out.
Overlays on any of the above hardware topologies; canonical example is
Flashforge IDEX with RepRapFirmware.
---
## Feature Walkthrough
### Printer Configuration
Printer preset options that declare IMEX capability and geometry:
| Option | Type | Description |
|--------|------|-------------|
| `is_imex` | bool | Marks this printer as IMEX-capable |
| `imex_firmware_managed_zones` | bool | Emit centered single-half
slice; firmware handles copy/mirror placement (default `false`) |
| `imex_gantry_count` | int | Number of independent Y-axis gantries
(rows) |
| `imex_tools_per_gantry` | int (1–4) | Toolheads per gantry along X
(columns) |
| `imex_nozzle_clearance_x` / `imex_nozzle_clearance_y` | float |
Nozzle-to-collision-edge distance in mm (literal, not halved) |
| `imex_tool_layout` | string | Physical orientation: front-left /
front-right / rear-left / rear-right |
| `imex_mode_names` | string[] | Names of user-defined parallel modes;
first entry is the reserved `primary` sentinel |
| `imex_mode_active_tools` | string[] | Tool role assignments per mode
(e.g. `0:P,1:C,2:M,3:M`, or `0:P,1:S,2:M,3:M` with Span) |
| `imex_mode_gcodes` | string[] | Firmware macro to activate per mode |
| `physical_extruder_map` | string[] | 0-indexed map from logical
filament slots to physical extruders (for MMU/AFC — see below) |
Tool roles per mode: **Primary** (P), **Copy** (C), **Mirror** (M),
**Span** (S), **Inactive**.
### Parallel Mode Editor
[Kooha-2026-05-14-13-45-35.webm](https://github.com/user-attachments/assets/af7650e6-d636-478b-9398-3f02ec662f03)
The **IDEX/IQEX Configuration** and **IDEX/IQEX Parallel Modes**
sections live in the printer preset's **Multimaterial** page (visible
only when `is_imex = true`). The mode editor is a visual grid:
- Each row defines one named parallel mode. The first row is a reserved,
non-deletable **Primary** row stored as the sentinel `primary` in
`imex_mode_names`.
- The Primary tool is always T0; **Tool 0 Position** chooses which
physical corner it occupies, and its tile is read-only. The other tool
buttons cycle Inactive → Copy → Mirror → Span → Inactive, color-coded,
with Span offered only where it applies.
- **Span** is only offered when `imex_gantry_count >= 2` AND the tile is
on the primary's gantry row — it declares "this tool is the multi-color
partner of Primary on the same gantry," distinct from a Copy/Mirror role
on a non-primary gantry.
- A G-code field per mode holds the firmware macro to activate that mode
(e.g. `IMEX_COPY` for Klipper). A placeholder-browser button per row
opens the `EditGCodeDialog` for quick insertion.
- Modes can be added and removed (removed via a dedicated
`imex_remove.svg` ScalableButton).
- Deleting the active mode resets affected plates to Primary.
- Tool assignments are preserved across `imex_gantry_count` changes:
going from IQEX (4 carriages) → IDEX (2 carriages) → IQEX restores all
previously assigned roles, and the visible grid anchors to the gantry
row containing the Primary assignment.
<!-- TODO: replace this placeholder with a re-recorded
parallel-mode-editor video
showing the Span tile state, the remove button, and the
placeholder-browser button.
Old URL (out of date):
https://github.com/user-attachments/assets/cabf1f70-206d-4f84-9c2e-594b38b83951
-->
### Span Tile State — Paired-Gantry Multicolor
<img width="2833" height="1300" alt="2026-05-14-135412_grim"
src="https://github.com/user-attachments/assets/52203133-7380-459a-973f-0502a1edc3c2"
/>
For IQEX printers (`imex_gantry_count >= 2`), the **Span (S)** tile role
enables paired-gantry multicolor mirror mode. The same active-tools
string `0:P,1:C,2:M,3:M` is ambiguous between two distinct hardware jobs
— four independent quadrants vs. paired-gantry multicolor mirror — so
the topology has to be declared explicitly rather than inferred. Span on
the primary's gantry row is the declaration.
Behavior driven by Span presence:
- **Multicolor block rule**: requires Span on primary's gantry to allow
multi-color slicing in a parallel mode. Without Span, multi-color
slicing in a parallel mode is blocked with an actionable error message.
- **Ghost aggregation**: one aggregated ghost per non-primary gantry
when Span is present, using the column-paired representative tool.
Mixed-role gantries fall back to per-tool.
- **Zone aggregation**: one row-strip zone per non-primary gantry when
Span is present (cell at primary's column collapses col-sep;
`make_boxes` expands to full-X strip).
- **Aggregated mirror drag**: ghost translates 1:1 with primary in X
(copy-style), with X-flip baked into the mesh-local frame so geometry
still reads as mirrored. Gantries don't share an X rail, so reflecting
motion serves no collision purpose.
- **Carriage collision strip audit**: X-boundary checks require `zr ==
pri_row_k` (matching the existing Y-boundary `c == pri_col` constraint).
Prevents spurious strips on primary's right edge in paired-gantry
mc-mirror, where T3 sits diagonally and can't actually collide with
primary's carriage.
Single source of truth:
`IMEXHelpers::group_imex_active_tools_by_gantry(active_tools_str,
tools_per_gantry)`. Ghost factory and zone calculator both consume it,
so pairing logic lives in one place.
### Per-Plate Mode Selection
Each build plate has an IMEX mode icon in its toolbar (normal, hover,
dark, and dark-hover SVG variants). The mode can be set independently
per plate:
- **Left-click** cycles through all non-sentinel modes in order.
- **Right-click** opens a popup menu listing all modes as radio items
for direct selection.
- Mode changes are recorded in the undo/redo snapshot system.
- The selected mode is persisted in the 3MF project file per plate
(`imex_parallel_mode` key).
https://github.com/user-attachments/assets/5ab497e3-9c2f-476a-ac67-ef34a592395b
When a parallel mode is active alongside multi-material objects on the
same plate, a warning badge (`obj_warning.svg`) overlays the plate icon
— see **Pre-Slice Warning System** below.
### Bed Visualization + Auto-Arrange Constraints
When an IMEX mode is active, the build plate renders the carriage grid:
<img width="2761" height="1447" alt="IMEX bed visualization with active
primary zone and dimmed secondary zones"
src="https://github.com/user-attachments/assets/3ee64b7b-a3ed-40aa-ad07-fa2f1cb74edf"
/>
- The **active (primary) zone** is full brightness.
- **Inactive zones** are dimmed with a color-coded overlay (blue for
copy, orange for mirror).
- **Zone dividers** are rendered as lines across the bed.
- The grid is a full 2D layout: `imex_tools_per_gantry` columns ×
`imex_gantry_count` rows. Zone sizing is based on the **active** tool
count only — inactive tools donate their bed share to active neighbors.
- Span-present configs render as a single row-strip zone per non-primary
gantry rather than per-tool quadrants, reflecting that the firmware will
paint the multi-color across the entire strip.
- Colors are drawn from the Okabe-Ito palette for colorblind
accessibility, with a deuteranopia/protanopia-safe alternate theme.
- **Auto-arrange** is constrained to the primary zone when a parallel
mode is active — `ArrangeJob::process()` replaces full-bed `bedpts` with
the primary zone corners via `PartPlate::imex_primary_zone()`. Collision
strips are additionally registered as hard obstacles through
`m_unselected` (the working NFP placer input, not the dead
`excluded_regions` field), so placement cannot drop parts into the
danger strips.
### Placement Validation
https://github.com/user-attachments/assets/5cb5a94a-981e-4fe9-898e-7e2f891b4f40
Objects placed outside the primary zone block slicing:
- `has_imex_placement_violations()` in `PartPlate` checks each object's
convex hull against the primary zone boundaries and collision strips.
- Violations inject into the existing `update_background_process`
validation pathway — the Slice button is disabled and an error
notification is shown.
- Mirror tools additionally generate X-axis collision strips (copy tools
move in the same direction and cannot collide). Y-direction strips are
scoped to same-column tools to avoid false positives from diagonal
mirror pairs.
- Strip width is taken **literally** from `imex_nozzle_clearance_x` (the
measurement is nozzle-to-collision-edge distance, not carriage
half-width).
- Multi-color block path: a pre-slice rule blocks multi-color slicing in
parallel modes that can't physically support it (e.g. a "fake IMEX" mode
where all tools sit on a single gantry without a Span partner). The rule
is centralized in `imex_multicolor_block_reason()` with unit-test
coverage of every gating case.
### Ghost Object Rendering + Per-Plate Filament Picker
When a parallel mode is active, the slicer renders colored, transparent
**ghost copies** of primary-head instances on the plate — one per
secondary active head, transformed under its Copy/Mirror role (or
aggregated per gantry when Span is present).
- Ghosts track the primary through drag/rotate/scale/mirror and
invalidate on mode, filament-map, or `physical_extruder_map` changes.
- **Left-click** on a ghost opens the `IMEXFilamentPickerPopover` for
that ghost's head — a compact `BitmapComboBox` that writes directly to
the per-plate `imex_head_filament_map` (MMU lane override).
- Mirror ghost geometry is a **true reflection about the zone-boundary
plane** (`x = primary_zone_center.x + gantry_offset.x/2`), so the ghost
stays anchored in the target zone as the primary moves and drag reflects
correctly (primary +X → ghost −X, Y tracks 1:1).
- Hover tooltip: `"Tn → filament N"` with a color swatch. When no
filament resolves to a head, the tooltip surfaces an actionable message
directing the user to extend the extruder count in the Machine tab.
- Ghost rendering + picking are scoped to the active plate — non-active
plates don't draw stale ghosts during arrange/preview transitions, and
click-picking never falls through to a non-current plate's ghost
geometry.
- Per-plate `imex_head_filament_map` round-trips through the 3MF project
file.
### Pre-Slice Warning System
<img width="1786" height="911" alt="2026-05-14-135714_grim"
src="https://github.com/user-attachments/assets/16532d47-2da5-4472-ab9c-4b8b00303bae"
/>
Before a plate slices, IMEX parallel-mode plates are checked for three
classes of conflict:
1. **Multi-material on secondary tools** — when the mode has Copy/Mirror
tools and the plate has multiple filaments active; plate icon gets a
warning badge.
2. **Bed temperature mismatch** — any two carriages configured >5 °C
apart.
3. **Filament type incompatibility** — filaments from different type
families on active carriages.
A dismissible Yes/No `RichMessageDialog` fires once per user action from
both `on_action_slice_plate` and `on_action_slice_all`. The dialog has a
**"Don't show again"** checkbox that persists to `app_config` as
`imex_pre_slice_warnings=false`. A re-enable toggle lives in **Printer
Settings → Multimaterial → IDEX/IQEX Configuration** so suppressed
warnings can be restored.
### Firmware-Managed Zones — Center-Origin Slice
<img width="701" height="197" alt="2026-05-14-135842_grim"
src="https://github.com/user-attachments/assets/3e2dfaab-fb4f-42b1-bf9d-c88dc0c95110"
/>
The `imex_firmware_managed_zones` printer-config option (default
`false`) supports IDEX/IQEX printers whose firmware applies its own
copy/mirror offsets in non-primary modes. Canonical examples:
**RepRapFirmware IDEX duplication mode** on **Flashforge Creator Pro 2 /
Creator 3 Pro**. These printers expect a centered single-half slice at
bed origin and fan toolheads out from there — the slicer-managed
paradigm of placing toolpaths at zone-relative positions produces gcode
the firmware can't reconcile, since it'd double-apply offsets.
When the flag is on and the active mode is non-primary, the slicer
subtracts the primary zone's plate-local center from the gcode emission
frame:
- **Writer offset** is augmented by the primary zone center so emitted
gcode is centered at bed origin.
- **Processor offset** stays at plate_origin only, so the gcode-preview
visualizer renders the centered toolpath at bed center rather than at
the prepare-view zone placement. The user sees what'll physically print
after firmware fan-out.
- **`translate_to_print_space()`** is augmented for frame coherence so
`first_layer_print_min/max` placeholders consumed by user start_gcode
(e.g. Flashforge's M118 "max delta from zero" header) reflect the
centered frame.
Slice-handoff runs per-slice: `PartPlate::refresh_imex_slice_offset()`
is called from `Plater::priv::update_background_process` after
`Print::apply()`, so reslicing with mode toggled but no plate change
picks up the updated offset.
When the flag is off, all related code paths reduce to no-ops
byte-identical to standard slicer-managed behavior. The two stock-code
touches at `Print.cpp:2553-2554` (gcode_offset composition) and
`Print.cpp:2823-2829` (translate_to_print_space) are explicit additive
shifts that collapse to identity when the offset is `Vec2d::Zero()`.
### G-code Injection
The selected parallel mode's G-code is written into the output file
immediately before `machine_start_gcode`:
- Looks up the plate's active mode name in `imex_mode_names` and writes
the corresponding `imex_mode_gcodes` entry.
- Processed through `placeholder_parser_process()` first, so
Klipper-style variable substitution works **and** any `{global}`
declarations flow forward into `machine_start_gcode`.
- Primary mode's G-code field is emitted too.
- Guarded against headless CLI slicing — `ensure_imex_zones()`
short-circuits when `m_plater` is null so the CLI path (used by
upstream's regression-test CI step) doesn't segfault on
`wxGetApp().preset_bundle` dereference.
### Placeholder Parser Integration
<img width="535" height="393" alt="2026-05-14-140227_grim"
src="https://github.com/user-attachments/assets/6d059c77-38f6-4418-bfe9-3bf963e8c836"
/>
Three placeholders register under **Slicing State** and are settable
from anywhere downstream:
| Placeholder | Type | Description |
|---|---|---|
| `imex_mode` | string | The active mode name |
| `imex_mode_index` | int | Index into `imex_mode_names` |
| `imex_mode_gcode` | string | The resolved mode G-code (post-parser) |
### Per-Tool Pressure Advance — Firmware-Agnostic
`set_pressure_advance()` takes an optional tool index (default `-1`,
preserving existing behavior for all non-IMEX call sites). Per firmware:
- **Klipper**: `EXTRUDER=extruder[N]` when `tool >= 0`, bare command
otherwise
- **RepRapFirmware**: `M572 D<N>` when `tool >= 0`, `M572 D0` otherwise
(preserves the pre-IMEX output; a bare `M572` applies to whatever tool
is selected and errors when there is none)
- **Marlin 2**: `M900 K<X> T<N>` when `tool >= 0`, bare `M900` otherwise
- **Marlin Legacy / fallback**: `M900 K<X>` always
- **Repetier**: `M233 X<X> Y<X>` (X is quadratic, Y is linear; same
value applied to both)
`m_imex_parallel_mode` is set once per export from the active plate
mode. PA tool-qualification is gated on this being a **non-primary**
parallel mode — primary-mode prints emit ordinary tool-change PA exactly
like any non-IMEX printer. Secondary active tools in parallel modes
receive explicit per-tool PA at print start since they never go through
a tool-change sequence.
### Per-Layer Temperature Management
In IMEX parallel modes, all active tools (primary + secondaries) get
temperature commands in `layer_change_gcode`:
- Layer 1 temperatures only emit on the first layer; subsequent layers
use normal layer-change temperatures.
- IMEX temperature handling is consolidated into the second-layer
transition.
- Layer-change temperature commands use `M104 T<N>` with
`physical_extruder_map` translation when applicable.
### MMU / AFC Support via `physical_extruder_map`
For printers where multiple logical filament slots share one physical
extruder (MMU, AFC, toolchangers), the `physical_extruder_map` profile
option translates tool slot indices to physical extruder qualifiers
before G-code emission.
- **Fallback**: on IMEX printers, `Print::apply()` uses the profile's
map only when it has one entry per extruder (the `nozzle_diameter`
count). Anything else, including the single-entry default, is replaced
by the identity map `0..n-1`.
- **Used by**: IMEX PA emission, layer-change temperature commands
(`M104 T`, `EXTRUDER=`, `M572 D`), ghost color resolution, ghost cache
key, tooltip lookup, click gate.
- **Profile authoring example** for a 7-slot printer with a 4-lane MMU
on extruder 0 and three independent direct drives on extruders 1/2/3:
```json
"physical_extruder_map": ["0","0","0","0","1","2","3"]
```
Non-MMU printers need no action — the identity fallback handles them.
- A centralized helper in `IMEXHelpers`,
`effective_physical_extruder_map(explicit_pem, nozzle_count)` (with a
`PresetBundle` overload that prefers the project's map over the
printer's), routes all PA/temp/ghost-color lookups through a single code
path.
- **No UI for editing the map** in this PR — non-trivial MMU/AFC layouts
require profile-authoring (hand-edit the printer JSON) or an updated
printer profile shipped by a vendor. A future enhancement would expose a
per-slot extruder picker in the Multimaterial section.
### Layer Preview Multi-Carriage Animation
The sequential preview (scrubber) animates all active carriages
simultaneously:
- One toolhead marker (colored cube) per active carriage, in addition to
the primary.
- Secondary marker colors: cyan (T1), yellow (T2), magenta (T3).
- **Copy** tools: marker placed at the same relative position within
their bed zone as the primary is in the primary zone.
- **Mirror** tools: reflect across the target zone's facing edge
(left-of-copy reflects across copy zone's left edge; right-of-copy
across the right edge). Y position is always zone-relative copy (all
tools on a row share a physical Y rail).
- Carriage footprint boxes use per-carriage `box_offset_x/y` so the
nozzle marker sits at the physically correct edge of the footprint —
zone-based X by default, collision-side edge for Mirror; gantry-behind Y
for back-row primaries, flipped for front-row primaries with a back-row
secondary.
- The filament usage legend notes the active carriage count and mode
name (e.g. `IMEX: ×2 (copy_mode)`).
- **View menu toggle**: `View → Show IDEX/IQEX Toolhead` (visible only
on the Preview tab, only when the active printer is IDEX/IQEX)
hides/shows the per-carriage toolhead representation during preview
playback. State persists to `app_config` as `show_imex_toolhead_boxes`.
Useful when scrubbing through dense toolpaths and the boxes get in the
way of seeing the underlying geometry:
[Kooha-2026-05-14-14-04-09.webm](https://github.com/user-attachments/assets/9869cd4d-3b60-4a6b-8dca-7ee1fc573ac1)
Copy mode:
https://github.com/user-attachments/assets/0774d285-ba36-4b86-a594-fb6572c3aede
Mirror mode:
https://github.com/user-attachments/assets/51528926-94dc-4d90-9b1a-1b42d9044be7
---
## Known Limitations
**Per-layer G-code collision detection**
The placement-time zone check catches gross violations (object placed in
wrong zone) but does not verify that toolpaths on any given layer
maintain adequate X separation between adjacent carriages. A per-layer
check via `ConflictChecker` was designed but deferred. Without it, a
print that passes placement validation could still crash carriages if
the primary object's toolpaths reach too close to a zone boundary.
**Brim avoidance of IMEX zones**
Standard `bed_exclude_area` exclusion zones are already respected by
brim generation. IMEX collision strips are not — the brim generator has
no visibility into them. The correct fix (feeding computed strip
polygons from `PartPlate` through the `Print` object to `Brim.cpp`) was
designed but deferred. In practice, users should leave adequate
clearance between printed objects and zone boundaries to account for
brim width.
**`extruder_printable_area` integration**
IMEX zones are not clipped against per-extruder printable polygons, and
there is no violation check for placing an object outside the
intersection of its active extruder's printable area and the IMEX
primary zone. Deferred pending clarification on the tool→extruder index
mapping.
**Ghost rendering in firmware-managed mode**
When `imex_firmware_managed_zones` is on, ghost rendering is suppressed
entirely. The existing `imex_head_transform` math is slicer-managed
semantics (places ghosts at `primary_zone_center + gantry_offset`) and
produces wrong positions when the toolpath is being emitted in a
centered frame. Proper firmware-managed ghost rendering — showing where
copies/mirrors will physically print after firmware fan-out — needs new
transforms designed around firmware-frame positions rather than a
coordinate-flip of the slicer-managed ones. Deferred to a follow-up.
**Filament-accurate multi-region ghost color**
For paired-gantry multicolor mirror (Span mode), the aggregated ghost
currently uses the representative tool's filament color as a single
solid swatch. A future enhancement would render the ghost split into
per-source-tool regions, each colored by the secondary tool that mirrors
it. Slicer-side correctness is already shipped — the G-code emits the
right T-codes; the ghost is a visual aid only.
**Ghost path overlays**
Secondary carriage toolpaths are not rendered in the layer preview. The
secondary markers animate correctly, but the paths they would trace are
not drawn. Adding ghost path rendering would require duplicating and
offsetting the toolpath geometry per secondary carriage, which is a
significant addition to the libvgcode rendering pipeline.
**No per-mode slicing**
All carriages in a mode execute the same sliced toolpaths (transformed
per zone). There is no support for slicing different objects for each
carriage independently within one mode.
**Global default IMEX mode**
There is no job-level IMEX default mode. Each plate's mode must be set
individually (default is always Primary). A future improvement would add
a global default in the sidebar (following the same pattern as bed type
and nozzle diameter), with per-plate overrides.
**Dynamic GL-rendered mode icons**
The per-plate icon currently uses static SVGs. A GL-rendered
carriage-grid icon that visually represents the mode's tool layout would
be a nicer UX but is deferred.
**Slice-all thumbnail icon refresh**
On the "Slice All" path, plates flagged as IMEX-violated do not refresh
their toolbar icon. Root cause and fix identified, not yet applied.
**Start-G-code filament placeholder**
The resolved per-head filament map is not yet exposed as a
`PlaceholderParser` vector. Exposing it would let MMU firmware macros
pre-load lanes before print start.
**No UI for `physical_extruder_map` authoring**
The MMU/AFC slot-to-physical-extruder map is currently profile-only —
there's no in-app dropdown or editor for it. Users with non-trivial
MMU/AFC setups must hand-edit the printer-preset JSON (or rely on a
vendor-supplied profile). A future enhancement would expose a per-slot
extruder picker in the Multimaterial section so users can declare the
mapping without touching JSON.
**Bundled IQEX printer profile**
A full IQEX printer profile with cover image, bed mesh, and matched
process/filament profiles is not bundled with this PR. Users must
currently author their own printer preset. Deferred to a follow-up
profile-only PR.
**Mode lifecycle gap**
Per-plate mode is stored as a string (the mode name). If a mode is
renamed or deleted from the printer preset after a project is saved, the
plate's saved mode name will not resolve and will silently fall back to
Primary on next load. A warning on load would be a useful addition.
**Firmware-managed prepare→preview frame jump**
In firmware-managed-zones mode, the prepare view shows the part at its
placed-in-zone position while the preview view shows the centered slice.
This is intentional — the views truthfully represent the prepare frame
vs. the post-firmware-fan-out frame — but the visual jump can be
confusing on first use.
---
## Firmware Assumptions
- The implementation assumes the firmware handles all carriage
synchronization and offset math. OrcaSlicer only injects the
mode-activation macro before machine start and is entirely dependent on
user configuration.
- Per-tool Pressure Advance is firmware-aware (Klipper / RRF / Marlin 2
/ Marlin Legacy / Repetier). Layer-change temperature commands use `M104
T<N>` with `physical_extruder_map` translation.
- Tested against Klipper on a real IQEX printer. RepRapFirmware gcode
emission is exercised via profile-driven slice tests (Flashforge Creator
Pro 2 profile); Marlin and RRF paths have not been validated on real
hardware print runs.
- Mid-print mode switching is explicitly not supported. The mode is
locked at print start.
- Firmware-managed-zones mode requires the printer's firmware to
translate centered slice coordinates into physical toolhead positions;
the slicer does not attempt to model the firmware's offset logic.
---
## Files Changed (High-Level)
| File | Change |
|------|--------|
| `src/libslic3r/PrintConfig.{cpp,hpp}` | IMEX config option definitions
+ declarations, `IMEXMode` enum, `physical_extruder_map`,
`imex_head_filament_map` (plate option), `imex_firmware_managed_zones` |
| `src/libslic3r/Preset.cpp` | IMEX keys registered in printer and
process preset option lists |
| `src/libslic3r/PrintApply.cpp` | `physical_extruder_map` identity
fallback on IMEX printers |
| `src/libslic3r/Print.{cpp,hpp}` | IMEX slice-offset field + accessors;
`translate_to_print_space` augmentation for frame coherence |
| `src/libslic3r/GCode.{cpp,hpp}` | Mode G-code injection, per-tool PA
emission (firmware-agnostic), per-layer temperatures for all active
tools, `m_imex_parallel_mode` state, `set_gcode_offset_with_imex_shift`
writer/processor split |
| `src/libslic3r/GCodeWriter.{cpp,hpp}` |
`set_pressure_advance(tool_index = -1)` per-firmware implementation |
| `src/libslic3r/IMEXHelpers.{cpp,hpp}` | `imex_head_transform`
(Primary/Copy/Mirror), `parse_imex_active_tools`,
`imex_primary_tool_for_mode`, `effective_physical_extruder_map`,
per-head filament resolution, `group_imex_active_tools_by_gantry` (Span
pairing), `compute_imex_slice_offset` (firmware-managed) |
| `src/libslic3r/Format/bbs_3mf.cpp` | Per-plate IMEX mode and
`imex_head_filament_map` serialization |
| `src/slic3r/GUI/Tab.{cpp,hpp}` | `IMEXModesCtrl` in Multimaterial
page, tool assignment persistence across gantry count changes, `primary`
sentinel handling, Span tile state, firmware-managed-zones checkbox,
null guard in `clear_pages()` |
| `src/slic3r/GUI/PartPlate.{cpp,hpp}` | Zone visualization (Span
row-strip aggregation), placement violation detection, per-plate mode
icon, ghost volume rebuild on mode/map/object mutation, warning-badge
overlay, pre-slice warning collection, `refresh_imex_slice_offset()`,
ghost suppression in firmware-managed mode, headless-CLI guard |
| `src/slic3r/GUI/Plater.cpp` | Validation pathway injection, per-plate
mode popup, ghost click handling, pre-slice warning dialog with "Don't
show again", IMEX multimaterial conflict routing, per-slice IMEX offset
refresh in `update_background_process` |
| `src/slic3r/GUI/GLCanvas3D.cpp` | Ghost rendering with per-head
filament color and translucent blending, picking via volume composite
id, active-plate scoping |
| `src/slic3r/GUI/GCodeViewer.cpp` | Multi-carriage marker animation,
Mirror math fix, carriage footprint box offsets, legend annotation |
| `src/slic3r/GUI/IMEXFilamentPickerPopover.{cpp,hpp}` | Ghost-click
filament picker |
| `src/slic3r/GUI/Jobs/ArrangeJob.cpp` | Auto-arrange constrained to
IMEX primary zone + collision strip exclusion via `m_unselected` |
| `src/slic3r/GUI/OG_CustomCtrl.cpp` | Empty `option_set` guard for
widget-only lines |
| `resources/images/plate_imex_mode*.svg`, `imex_remove.svg` | Per-plate
mode icons (light/dark/hover variants), remove button |
| `tests/libslic3r/test_imex_helpers.cpp` | Coverage for IMEX head
transforms, `effective_physical_extruder_map`,
`imex_multicolor_block_reason` gating,
`group_imex_active_tools_by_gantry` Span pairing,
`compute_imex_slice_offset` |
---
## Testing Notes
Validated on an IQEX printer (4 carriages, 2×2 grid, Klipper firmware)
and against the Flashforge Creator Pro 2 profile (2-tool IDEX, RRF
flavor, center-origin bed) with the following configurations:
- **Primary only** — baseline, no regression vs. standard
single-extruder workflow
- **Copy mode** (T0 Primary, T1 Copy, same row) — carriage markers
animate in sync offset by strip width; ghost tracks drag/rotate/scale
- **Mirror mode** (T0 Primary, T1 Mirror, same row) — T1 marker reflects
T0 across zone-boundary plane; ghost drag reflects X correctly while Y
tracks 1:1
- **Cross-row copy + mirror** (T0 Primary row 0, T2 Copy row 1, T3
Mirror row 1) — T2 follows T0's zone-relative position; T3 mirrors T2's
X; Y shared per row
- **4-tool copy mode** (T0 Primary, T1/T2/T3 Copy) — zone sizing
correct, all four markers + ghosts render in sync
- **Paired-gantry multicolor (mc-mirror, Span)** — `0:P,1:S,2:M,3:M`:
ghost aggregates to one per non-primary gantry, X-flipped, drag tracks
1:1 with primary; collision strip audit doesn't fire spurious strips on
primary's right edge
- **Firmware-managed copy mode** (Flashforge Creator Pro 2) — emitted
gcode coordinates are centered at bed origin (X = −cube_half_width …
+cube_half_width), `first_layer_print_min/max` placeholders evaluate
symmetrically, M118 header produces correct "max delta from zero" values
for Flashforge's existing template, ghosts suppressed
- **Firmware-managed flag toggle** — flipping the checkbox off restores
slicer-managed iMEX behavior (toolpath at zone position)
byte-identically; flipping on restores center-origin slice
- **Delete active mode** — no crash; plate resets to Primary
- **Placement outside primary zone** — slicing blocked with error
notification
- **Multi-material conflict warning** — plate icon gets warning badge;
Yes/No dialog fires on slice; "Don't show again" checkbox persists
- **Multi-color block rule** — `0:P,1:C` on a single gantry blocks at
slice time with actionable error message
- **Ghost filament picker** — left-click on ghost opens picker;
selection writes to `imex_head_filament_map` and round-trips through 3MF
save/load
- **Per-plate mode selection** — left-click cycles modes, right-click
shows popup, undo/redo correctly reverts mode changes, mode persists
through project save/load
- **Gantry count change round-trip** — reducing from IQEX (4 carriages)
to IDEX (2 carriages) and back restores all previously assigned tool
roles
- **MMU/AFC `physical_extruder_map`** — explicit map routes PA and
temperature commands to correct physical extruder qualifiers;
auto-derive fallback preserves 1:1 behavior for non-MMU printers
- **Headless CLI slicing** — `orca-slicer --slice project.3mf` does not
segfault on IMEX-enabled printers
- **Dark mode** — all four plate-icon variants render correctly
- **Pre-slice warnings suppress + restore** — `app_config` flag flips on
checkbox; re-enable toggle in Multimaterial config restores dialog
- **Stock non-IMEX printer regression check** — slicing a single-head
Voron Trident 350 profile produces byte-identical gcode vs. baseline;
the firmware-managed flag and Span tile state both no-op when not
applicable
- **Full unit test suite** — 247/247 passing, including Layer 1 coverage
for `compute_imex_slice_offset`, `group_imex_active_tools_by_gantry`
Span pairing, and `imex_multicolor_block_reason` gating
Several warning and info dialogs passed wxYES as their only button, so
the only choice was "Yes" even though nothing is asked. Use wxOK.
None of the callers act on wxID_YES. They ignore the result, except
Field.cpp, which only checks that it's nonzero, and every button id is.
wxYES_DEFAULT is 0, so dropping it next to wxOK changes nothing.
Also drop the empty `if (ShowModal() == wxID_YES) {}` in
Plater::priv::load_files.
* Moonraker: pass print=true in upload — fixes Upload & Print race with power-on-upload
Closes#14945.
Upload & Print on the Moonraker (Klipper) host type failed with
HTTP 503 "Klippy Host not connected" on any printer that Moonraker
powers up in response to an upload (the [power] on_when_upload_queued
feature). The file landed on disk, the print never started, and the
user hit an error dialog.
Root cause: after POST /server/files/upload succeeds we immediately
fire POST /printer/print/start. On a cold printer that Moonraker just
powered up, Klippy is still coming up when /printer/print/start
arrives, so Moonraker returns 503.
Fix: add `print=true` to the upload multipart form. Moonraker's own
upload endpoint queues the print inside the upload response — and
when a [power] device with on_when_upload_queued is configured, it
powers the printer on and waits for Klippy READY before starting.
That's the whole point of the power-on-upload feature; our second
POST was defeating it.
Also read `result.print_started` from the upload response — when
true, skip our explicit /printer/print/start (Moonraker handled it);
when false (older Moonraker or buddy-fork that ignores the print
flag), fall back to the explicit call so the existing behaviour is
preserved for those servers.
Reporter and root-cause identification: @RubenOllesch.
(cherry picked from commit bd442155ba)
* Moonraker: treat print_queued as Moonraker owning the print
Reading only result.print_started missed the exact case this PR set out to
fix. Moonraker's upload response carries two flags:
print_started : it began the print immediately
print_queued : it accepted the job but has not started it yet
The power-on path (`[power] on_when_upload_queued`) is the second one:
Moonraker queues the job, powers the printer up and waits for Klippy to
report READY, so it answers print_started=false, print_queued=true.
With only print_started read, moonraker_started_print stayed false, the
fallback fired, and our explicit /printer/print/start hit the same not-ready
Klippy that produced the original 503 — i.e. the fix did not fix#14945 for
the configuration that reported it.
Verified the response schema against Moonraker v0.11.0 (API 1.5.0); an
upload with print=true on a ready printer returns:
{"action": "create_file", "item": {...},
"print_started": true, "print_queued": false}
Both fields are present, so reading print_queued is safe on this version and
the `false` default keeps older hosts on the existing fallback path.
Caught by @raistlin7447 in review of #15032; the fix is their suggestion.
(cherry picked from commit 43e8eff003)
* Moonraker: read the upload reply's fields at top level
Moonraker's FileUploadHandler writes the upload result straight to the
response instead of wrapping it in {"result": ...} like the endpoints
registered through register_endpoint. item.path, print_started and
print_queued are therefore top-level keys. Reading them under result.
silently fell back to the local filename and to "not started", so the
explicit /printer/print/start still ran after every upload.
Also correct the comments on when Moonraker queues a job instead of
starting it, and on what it renames on upload.
Arrange read the zones of the selected plate only and applied them to
every bed it packed. Arranging all plates with a plate in Primary
selected spread a parallel plate's parts across its whole bed, and with
a parallel plate selected, a Primary plate's parts were squeezed into
the zone. Arranging a single plate other than the first also ignored its
collision strips: they were tagged with the plate's index, while the
plate packs into the arranger's bed 0.
The IDEX/IQEX constraints now live in ImexArranger (IMEXArrange), and
ArrangeJob only snapshots each plate's zones on the main thread and
calls it:
- When every bed, plates the arrange may add included, has the same
primary zone, the bed shape is that zone, so parts stay centered in it.
- Otherwise each plate in a parallel mode fences off the rest of its bed
with fixed items on its own bed, and its parts are then arranged again
inside the zone, so they sit centered rather than piled against the
zone edge nearest the bed's center. Plates in Primary keep the whole
bed, and plates the arrange adds take the process preset's mode.
- Zone edges inside the bed get the bed's own edge margin, and strips
also keep room for the brim.
- A part too big for its zone, which libnest2d's first-fit retry places
across the fixed items, is left unarranged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Description
Follow-up to #16242, which turned texture colour mixes into mixed
filament slots but created a slot for every possible mix, even when only
previewing. A new Mixed colors setting (default 8) caps how many mixed
filaments a bake adds. They are picked from the texture's own colours,
and only the ones the bake actually paints with are created. Previews no
longer touch the project's filaments, both previews show a mix in the
colour its slot will have, and the bake paints each mix with the slot it
got, which went wrong whenever the project already held mixed slots.
It also improves the colour preview: the green paint highlight no longer
covers the colours, and the colours a bake writes stay visible in the
gizmo.
Only texture displacement changes. Models without a colour layer behave
as before, and there is no change to project or profile formats.
# Screenshots/Recordings/Graphs
**Before**
https://github.com/user-attachments/assets/554a0d0d-cd9f-4095-840e-ca764dde6d52
**After**
https://github.com/user-attachments/assets/7fe10431-8c3f-4759-bd3f-78b4f7a12fc7
## Tests
<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->
<!--
> 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)
The green highlight and tint no longer cover a colour preview, and return while a stroke is
painted. The Fast view also keeps the other parts of a multi-part object.
The new Mixed colors setting caps how many mixed filaments a bake adds. They are picked from the
texture's colours, and only the ones the bake paints with are created. Previewing no longer creates
filament slots, both previews show a mix in its slot's colour, and the bake paints each mix with the
slot it actually got.
Its toggle handler did not call Skip(), so CheckBox's own handler, which
redraws the tick, never ran. Each click still flipped and saved the
setting, but the box kept showing it ticked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Multimaterial page showed an IDEX/IQEX section in Simple mode on
every FFF printer. The pre-slice warnings row's hand-built option
definition left its mode at comSimple; it now uses comAdvanced, like the
IMEX options around it. The parallel modes grid had a group of its own
holding only a full-width widget line, which records no mode, so that
group showed in every mode. The grid now sits in the configuration
group, which follows its Advanced rows. That group shows or hides
everything in it, so the grid keeps itself hidden on non-IMEX printers
(IMEXModesCtrl::set_applicable).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MoonrakerPrinterAgent filled physical_extruder_map from AFC's per-lane
extruder_index whenever the printer's filaments were synced. IMEX does
not need it: printers carry their map in the printer profile, and IMEX
reads it from there. The sync also wrote device state into the edited
printer preset, so a sync could mark the preset modified or replace a
hand-tuned map. The two Moonraker agent files go back to upstream's
version. The sync can return separately, writing the map somewhere other
than the printer preset.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remove three update_non_diff_values_to_base_config scenarios that
tested the truncation guard from upstream #13316. IMEX does not change
that code, and upstream's own tests cover it: removing the guard fails
#13316's test, and loosening it to `>=` fails #16107's.
The equal-size scenario broke when #16107 changed how variants are
matched, and the scalar-key scenario could not fail at all.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Upstream's clang-tidy gate now checks that the lines a pull request changes
include the header for every symbol they use. The IMEX sources, their tests,
and the lines this PR adds to shared files relied on the precompiled header and
transitive includes. This adds the includes clang-tidy names; no code changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A tool change added from the layer slider switches heads mid-print like a
painted color, but the parallel-mode checks only looked at the filaments of the
plate's objects, support and prime tower. A one-filament plate with a slider
change to a copying head's filament passed them. They now include those tool
changes, as the plate's warning badge already did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only the primary and its Span tools print a Span plate's colors; the other
active tools replay them. The multicolor rule never checked where each color
went, so a filament on a copying or mirroring tool, an unused one, or past the
end of the physical extruder map was accepted. It is now refused, and the
message names the tools that print colors and where the map sends the
offending filament.
The plate's warning badge also read the raw physical_extruder_map rather than
the effective one slicing uses, so on printers that set no map it disagreed
with the slicer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three commits: G-code export caches its filament config slot and repeated
option lookups, per-plate bed type overrides follow the printer's multi-bed
support, and the gizmo checkboxes and texture displacement panel get styling
and refresh fixes. No conflicts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The name field rebuilt its suggestions in wxEVT_COMBOBOX_DROPDOWN, which
ComboBox sends after it has sized and shown the popup from the items it
already held. With every suggested name taken, the popup opened around an
empty list: a small empty box on GTK, a black one on Windows. The list is now
rebuilt from the field's own mouse-down and double-click, which run ahead of
ComboBox's handlers. With nothing to offer the popup stays closed and the
click focuses the field instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The editor only replaced an empty name, so a row could be given a name another
row already had. A plate stores its mode by name and find_imex_mode() takes the
first row with it, so the second row was unreachable, and the plate's mode
list repeated the name: left-click stuck on it, or looped without getting back
to Primary.
An edited name that another row already carries, or the reserved Primary name
in any case, is now replaced when the edit is committed: "copy" becomes
"copy 2". The edited row yields, so plates keep resolving to the row they
meant, and tabbing through a field without changing it checks nothing.
Resetting a row to a saved name that another row has since taken does the
same. The plate's mode list comes from imex_plate_mode_choices(), which lists
each name once, so a profile that already has duplicates still cycles.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The editor took its background from the app's window default, which in dark
mode is a gray the dark-mode walk has no entry for. In dark mode it never
matched the page's palette color, and an editor built in dark mode kept that
gray after a switch to light while the controls inside it changed. It now
takes the page's color, which the walk remaps both ways.
Its labels and G-code text had no color of their own, so on Windows they took
the system's text color, which follows Windows' theme rather than Orca's. They
now start from the page's label and input text colors, which the walk maps
with the page.
The walk also ran the tool tiles' role colors through the palette map, which
turned their labels gray after a theme change. The tiles carry wxBU_AUTODRAW,
the flag it skips, so the role colors stay as set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The remove button drew imex_remove.svg, a redrawn delete.svg, and the help
button drew the mascot question icon. They now use delete and icon_qusetion,
the tip icon the send-print dialog uses, and imex_remove.svg is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The update merged the same upstream commits this branch already carries. Its
GLCanvas3D resolution kept the assembly view painter block after the ghost
render, which section view removed along with the members it calls, so that
tree does not build. This merge keeps the removal; nothing else differs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Five commits: a section view for the 3D canvas, a faster G-code preview, user
preset values kept on extruder variants they don't list, debug build CMake
fixes, and a 30 minute timeout on macOS notarization.
Two conflicts, both in GLCanvas3D. The header's were neighboring
declarations, kept from both sides. In the transparent pass, the section view
removed the assemble view's painter block that followed our ghost render;
the ghost render stays where it was, at the end of that pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The plate's mode button showed one icon whatever the mode. It now shows the
mode the plate slices as, the one its ghosts follow. imex_mode_kind() reads it
from the heads beside the mode's primary: none is Normal, any Span head is
Custom (the multicolor modes), otherwise any Mirror head is Mirror, and heads
that all copy are Copy. One Mirror head is enough because an IQEX mirror mode
copies within the primary's gantry.
The icons are Felix14-v2's: the four kinds, each in light and dark with a hover
state, replacing the single mode icon. Two fixes to them: the light Normal
border used the dark theme's gray, and an opacity="undefined" attribute hid one
of its strokes in nanosvg. The knight outlines are drawn at 1.0 rather than
0.8, matching the other plate icons and keeping Copy and Mirror apart when
zoomed out.
Co-authored-by: Felix14_v2 <75726196+Felix14-v2@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seven commits: painted multi-material segmentation made deterministic, missing
includes added across src/slic3r/GUI, U1 high-flow nozzle variants, Windows
ARM64 build and HTTPS fixes, and a rule added to the wxWidgets agent skill.
Three conflicts, all include lists: upstream's include pass and ours each added
to the same lists in GCodeViewer.hpp, PartPlate.hpp and PartPlate.cpp. Resolved
as the union of both. Three includes both sides had added at different places,
which git kept twice, are kept once: Color.hpp and <set> in PartPlate.cpp,
<sstream> in Plater.cpp.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The IMEX fixtures set seven nozzles but kept multifilament_config's single
filaments x filaments flush block. get_flush_volumes_matrix splits that block
across the nozzles, leaving each with 7 values, and
ToolOrdering::reorder_extruders_for_minimum_flush_volume then reads them as a
7 x 7 matrix, past the end of the buffer. One of the affected tests segfaulted
on Windows x64; ASan reproduces the overflow in that test on Linux, where it
passed only by luck.
The fixtures now repeat the block once per nozzle, as the GUI does, and size
flush_multiplier to match, since append_full_config takes the nozzle count from
it. The helper is shared in test_helpers.
Co-authored-by: HanifKoh <76276251+HanifKoh@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
filament_minimal_purge_on_wipe_tower became variant-keyed upstream, which
invalidated the comment claiming none of full_config's keys were.
WipeTower2::extract_wipe_volumes indexes it by raw filament slot. Not reachable
with shipped presets; noted rather than worked around.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ComboBox::SetLabel is overridden and writes the text control when the text
control is shown, which it is on an editable combo. Clearing the label after a
pick therefore erased the name. Removed; the label is never written on this
control.
Suggestions are also rebuilt when the list opens. Built in add_row they filtered
against only the rows that already existed, so a row was offered names the rows
below it had taken.
Drops the 1px inset on the Primary label, which matched a frame the name field
no longer has.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
138 commits. The count is large because texture displacement merged with its
whole branch history behind it, going back to July, alongside config and preset
file locking across instances, a foundation for configurable printer agent
connections, and a day of smaller fixes and CI work.
Two conflicts, both the same shape: each side had appended to a sorted list and
git could not choose an order. libslic3r's CMakeLists gained InstanceLock
alongside our IMEXHelpers and IMEXZones, and the preset bundle loading test
gained an include for ParallelResolve alongside ours for IMEXHelpers. Both sides
kept, alphabetical. No logic conflicted.
Verified: 740 targets build clean under -Werror, and the Release suite passes
1740 of 1740, up from 1665 -- the 75 new cases arrived with the merge and all
pass. That mattered more than usual here, since preset loading and config
locking are both areas the IMEX preset code touches.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The name field becomes an editable ComboBox, the same pattern the sidebar uses
for parameters like sparse infill anchor length: predefined entries in a
drop-down, with the text still typeable. The mode table is authored for whatever
hardware the user has, so a closed list would be wrong -- nothing in the slicer
reads a mode's name except as the key a plate stores -- but the conventional
names are worth offering rather than leaving everyone to retype them.
What is offered follows the tool grid rather than a fixed list. Four carriages
get iq-copy and iq-mirror; multicolor needs a Span partner beside the primary
and a second gantry to copy the pair onto, so mc-copy and mc-mirror appear only
on a grid that can hold one. imex_resolve_routing() already refuses a multicolor
mode with no Span on the primary's gantry, and suggesting a name it would then
reject is worse than not suggesting it. Names a row already uses are dropped, so
the list only ever offers what is still free.
Two things about ComboBox matter when it is editable, which nothing else in the
tree does -- the other 78 call sites all pass wxCB_READONLY:
GetValue() returns the drop-down selection whenever there is one, so a name
typed after picking a suggestion would be silently discarded. Every read goes
through GetTextCtrl() instead. Field.cpp reconciles the same way for its own
open enums.
The constructor hands its value to TextInput as the LABEL -- the small
right-aligned slot a unit like "mm" occupies -- because a read-only combo hides
the text control and shows the label in its place. Left there, the name rendered
as a greyed echo beside the hint while the field itself sat empty. The combo is
built empty and the value written to the text control, and the selection handler
clears the label again afterwards, since SetSelection() writes there too.
The mode column widens to 176 to leave room for the drop-down arrow, with the
header spacer deriving from the same constant.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Community work from Rob Niccum, adding 0.04mm Ultra Fine and 0.08mm Extra Fine
for the 0.4 nozzle, a toolchange prime on the three IQEX machines, and
recompressed cover art and build plate model that are byte-identical in content.
Bundle version bumped to 02.04.00.13 here rather than in his branch, so the two
of us were not editing the same line while his review was open. Without the bump
the updater refuses the bundle outright, and nothing in CI catches that.
Verified rather than assumed, since his own validator predates the parallel
printing code and the fine tiers changed shape during review: the full profile
check passes all five stages tree-wide, and both new tiers slice clean on all
four 0.4 machines, 8 of 8. His last revision dropped a bottom_shell_thickness
override that was making those tiers thinner against our common process rather
than thicker against his; the emitted G-code now carries the inherited 1.0,
which is what that fix was for.
Co-Authored-By: Rob Niccum <klober81@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Eleven commits, including a typed-config G-code export speedup, a printer agent
refactor that generalizes the infrastructure beyond Bambu, gyroid optimization,
and three CLI crash fixes.
One conflict, in PrintConfig.cpp. Upstream introduced a new_def macro and began
migrating the placeholder table onto it, adding curr_bed_type that way in the
same block where this branch had added imex_mode, imex_mode_index and
imex_mode_gcode in the older def = this->add(...) form. Both sides are kept and
ours are converted to the macro, which expands to the same three statements and
wraps label and tooltip in L() exactly as before, so nothing changes about what
is registered or what is translatable.
Note for anyone building this branch: the agent refactor adds a dependency,
LibDataChannel, so the deps tree needs dep_DataChannel built before the app will
configure. A distribution package of the same name will be found first if one is
installed, and the resulting error names a missing RelWithDebInfo location
rather than the wrong package, so point LibDataChannel_DIR at the dependency
prefix if that happens.
Verified: 789 targets build clean under -Werror, and the Release suite passes
1665 of 1665, up from 1630 before the merge -- the 35 new cases arrived with it
and all pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses the interface notes on the IDEX/IQEX modes editor.
Add Mode moves from below the rows to the top of the panel, beside a "?" button
that now carries the overview text as its tooltip. At the bottom the button
shifted down the page every time a mode was added, so where it sat depended on
how many modes already existed. It is an Orca Button in the Confirm style, width
matched to the mode column it creates a row in, and the panel opens on the
legend rather than on a paragraph.
Remove moves out of the right-hand column, where it sat one icon away from
Edit -- a destructive control beside the one pressed most -- to under the name
field it deletes, and its icon becomes a boxed minus rather than an X, which
read as "close". Reset joins Edit in the right-hand column, which is now
top-aligned so the icons hold position regardless of row height. Tool tiles are
square at 24px, and the header spacer tracks that width so the column titles
stay over their columns when the grid changes shape.
The two text fields were landing on GTK's near-black default border, invisible
against the panel: measured 45,45,49 against a 43,43,43 background, where the
settings fields above use 74,74,81. wxTextCtrl cannot color its own border, so
each sits in a one pixel frame taking the color TextInput derives for the
theme, and carries wxBORDER_NONE so Windows and macOS do not draw a native edge
inside it. The G-code boxes also take the monospace face EditGCodeDialog uses.
Bed zone fills drop to roughly half opacity in the Standard theme. They cover
whole quadrants for a whole session, so at swatch saturation they dominate the
scene. The collision strip is dimmed less, since it marks where a head hits
something. The deuteranopia, tritanopia and high contrast themes keep their
alphas: those are chosen for discriminability, which is the opposite trade.
Also fixes the icon size never applying. All four ScalableButton call sites
passed eight arguments, so the size bound to use_default_disabled_bitmap and
bmp_px_cnt kept its default of 16. Both are passed now.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
set_filament_pressure_advance's declaration said -1 omits the tool qualifier,
without qualification. That holds on Klipper, Marlin and BBL but not on
RepRapFirmware, where no-tool keeps the historical `D0`: a bare M572 applies to
whichever tool is selected and errors when none is, so omitting the qualifier
would make pressure advance depend on tool-selection state for every RRF user,
none of whom are using IMEX. The behavior is deliberate and unchanged; only the
declaration overstated it. GCodeWriter::set_pressure_advance and the index-space
notes in IMEXHelpers.hpp already described it correctly.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
In a parallel mode no tool changes occur, so two loops address each active
carriage explicitly: pressure advance before the print, and the second-layer
drop off the initial-layer temperature. Neither had coverage, and both fail
silently -- a carriage missing from one emits nothing at all, so it holds the
initial-layer temperature for the whole job, or runs on whatever pressure
advance the firmware was last given. The new case pins that the carriages
addressed are exactly the ones the mode declares active, each with the values of
the slot physical_extruder_map routes its head to.
The test needs filament_self_index, set here on imex_7x4_printer() so the whole
file has it. Production authors that key 1..n; its all-1s default collapses
every per-filament vector to filament 1's value through get_config_index_base(),
which leaves a per-slot assertion comparing a value against itself.
Also bounds the second-layer loop on filament_diameter alone. The bound belongs
in slot space, and filament_diameter is the one per-filament vector never
expanded per variant; the previous min() against nozzle_temperature mixed the
two index spaces without changing the result. The pressure advance loop already
bounds this way, so the two now read alike.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The lane map fetched over Moonraker was applied to whichever printer preset
happened to be edited when the response arrived, with no check that it was the
machine polled. Not a race, as the whole chain runs on the GUI thread with the UI
blocked, but a steady-state mismatch: two IMEX printers of different models with
the same logical extruder count both pass every existing guard, and the map is
written to the wrong preset and dirties it with no user action.
The pairing is now evaluated when the callback runs, against the selected device,
on the predicate update_sync_status() uses. Capturing an identity at connect time
would instead ask whether the edited preset had changed since then, and would
reject the user who selects a machine and only then switches to its matching
preset.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A plate can carry its own parallel mode, or leave it on Primary and inherit the
process preset's. set_imex_mode() erases the key on Primary precisely so the
preset's value survives the config merge, and the slicer honors it: Print::validate()
and the G-code path both read the merged object config, so such a plate slices, and
emits, in the preset's mode.
Three places that describe that plate did not resolve it the same way. They read
the plate's own value and stopped, so on a plate left at Primary:
the multi-material conflict badge stayed dark on a plate validate() will refuse,
which is the one invariant the comment above it claims to keep;
the bed-temperature and filament-type warnings never ran, so the job went out in
copy or mirror with a mismatched bed and no notice;
the plate tooltip reported Primary for a plate about to print in another mode.
The geometric badge beside the first of those already resolved correctly, because
it goes through the zone layout, so one badge fired while its neighbour stayed
dark on the same plate.
The fallback had been written out by hand four times. Three are now collapsed onto
one accessor, get_effective_imex_mode(); the fourth is in libslic3r, which cannot
call a GUI method and resolves the two modes itself from arguments. The remaining
callers of the raw get_imex_mode() want the raw value and keep it: the accessor
itself, the zone layout call that passes both modes separately, the mode menu and
the left-click cycle, which act on what the plate stores, and the reset that looks
for plates whose own mode was removed.
The tooltip is the one place the two readings meet. It headlines the stored mode,
because it sits on the button whose menu and click act on that value, and names
the inherited mode after it when they differ -- so one control no longer says
three different things while still telling the user what will actually print.
Reachability, honestly: nothing in the UI writes the process preset's copy today,
so this needs a hand-edited preset, a vendor process profile or a project that
carries one. It is latent rather than live -- and it stops being latent the moment
a process-level mode selector exists. Note the key lives in the process preset, so
one value there would govern every Primary plate in every project using it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Upstream moved pressure advance onto the extruder variant: enable_pressure_advance,
pressure_advance and the four adaptive keys joined filament_options_with_variant,
the repeated inline blocks in set_extruder() became a helper, and the lookups
moved from the filament id to get_filament_config_index().
All three conflicts were the same collision, because this branch had modified two
of those same inline blocks to pass a tool qualifier so each carriage is addressed
explicitly in parallel modes. Taking either side whole would have lost something:
upstream's drops the qualifier and leaves parallel carriages with no pressure
advance, ours drops the per-variant indexing and reads the wrong column on a
multi-variant printer. The helper now takes an optional tool, defaulting to -1,
which omits the qualifier. imex_pem_tool_for() already returns -1 off IMEX and in
primary mode, so non-IMEX output is unchanged, and the three call sites that never
passed a tool keep upstream's behavior exactly.
The third conflict was two test cases appended at the same place. Both are kept.
Separately, one defect that merged cleanly and so was not flagged: the loop that
emits pressure advance for secondary carriages at the start of a print still
bounded and indexed those vectors with a raw filament id. They are variant
expanded now, so their length is columns rather than filament slots -- the value
read was the wrong column, and the bound no longer sat in slot space, letting an
out-of-slot filament through. It now bounds on filament_diameter and translates
with get_filament_config_index(), which is what the sibling second-layer
temperature loop already does.
Verified: both changed translation units compile clean under -Werror. The merge
was resolved independently twice and the two resolutions agree on every line of
code. Not yet run: the Release test suite and a parallel-mode slice sweep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every extruder on every model carried the same yellow. The key exists so the
plater, the preview and the filament mapping can tell toolheads apart, which
matters most on the machine this bundle exists to demonstrate: four independent
heads that were indistinguishable at a glance.
Yellow, blue, plum and orange, assigned in tool order, so the two-tool models
take the first two. Red and green are avoided as a pair because they are the
hardest to separate for the commonest color vision deficiency.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- IQEX (0.4/0.6/0.8): retract_restart_extra_toolchange 1 -> 0 on all four
tools. With the silicone ooze blockers the parked nozzle stays primed,
so an extra 1 mm after each toolchange can over-extrude at the restart.
- Add 0.04mm Ultra Fine and 0.08mm Extra Fine @Xplorer 0.4 on the existing
fdm_process_xplorer_common ladder for all four 0.4 printers. Same speeds,
accelerations and line widths as the 0.12mm Fine tier; only the layer
height, shell layers, bottom_shell_thickness 0.6 and (0.04) top solid
infill flow differ. Support stays off, as on the other tiers.
- Xplorer_buildplate_model.stl: decimated from 82,508 to 10,000 triangles
(4.1 MB -> 500 KB). Same bounding box and origin, so bed_model is unchanged.
- Covers: one distinct 240x240 image per model instead of the same image
copied four times (39 KB each -> about 11 KB each).
Adds the four Xplorer configurations, their twelve machines and fifteen
processes, so the parallel printing modes can be exercised on a real printer
rather than only on hand-built configurations.
Brings in nine commits, including stricter slice validation of custom G-code and
filename formats across system profiles, OBJ and DRC import hardening, and the
Ender-3 V3 SE extrusion mode fix. None of them touch the files this branch
changes.
The Xplorer ships in four configurations that differ in how many toolheads they
carry and how those heads are arranged: Single, IDEX with two heads on one
gantry, Dual Gantry with one head on each of two, and IQEX with two on each of
two. Each is offered at 0.4, 0.6 and 0.8 mm, giving four machine models, twelve
machines over a shared base, and a five-tier process ladder per nozzle.
The dual-gantry machines park the second gantry's tools over the plate, so the
area both gantries can reach is 57.5 mm shallower than the plate itself. Those
two declare 400 x 342.5 where Single and IDEX declare 400 x 400. That depth is
what the parallel print modes divide into equal zones, so it has to be the
reachable area rather than the physical one.
The bed textures are drawn to match. Texture coordinates are normalised per
axis, so a canvas whose aspect differs from printable_area is stretched and
anything drawn outside the plate is pulled onto it; each viewBox equals its own
model's area. They mark the real reach limits and nothing else, because the
print zones are computed and drawn per plate at run time and a static copy of
them only disagrees with the live one.
Motion limits are the firmware's: 5000 acceleration, 300 mm/s, 100 on Z, 120 on
the extruder, and a square corner velocity of 5 rather than a jerk, which Klipper
does not have. Every per-extruder and per-variant value is written at full width,
because padding a short array repeats its first value rather than its last pair
and would otherwise hand every extruder past the first a normal-mode figure in
its silent slot.
The processes are based on a profile tuned on the hardware. Line widths are
expressed as percentages of the nozzle so one statement serves all three sizes,
and the ladder varies only what belongs to layer height. Extruder variants are
declared rather than left to a default that would have capped volumetric flow at
a figure describing a plain V6.
Verified by slicing all fifteen tiers on all four models: every one completes,
emitted accelerations and widths match what the profiles resolve to, no
coordinate leaves the printable area, and the two pre-existing Troodon models in
the same bundle are unaffected. The bed graphics, the print zones and the
volumetric cap resolve through paths the command line does not exercise and want
confirming in the application. Values tuned against hardware the author does not
have - the 0.8 ladder in particular - are a starting point rather than a result.
Co-Authored-By: Dan_3dp <corexy.diy@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Multi-material previews now open in Filament view, where toolpaths are coloured
by the filament printing them, and a marker carrying a fixed palette colour can
sit on toolpaths of that same colour. The plan already resolves which physical
head each carriage is, so in that view every carriage - the primary marker
included - takes its head's filament colour, from the accessor the ghosts
restamp themselves with, and marker, ghost and toolpath agree on what is loaded
where. The primary marker is the one every preview draws, so it goes back to its
own colour when no parallel mode is active.
Every other view keeps the Okabe-Ito palette, which is what identifies the
carriages when colour means something else, and so does a head whose filament
does not resolve. Each head is resolved once per frame and shared by its marker
and its toolhead box, the way the ghosts already hoist the map they all read.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
X-Ray takes both shaded passes over and returns, and it draws only the volume
collection, which the ghosts are not part of. They disappeared while their
picking pass, hover tooltip and filament picker all kept answering, so a plate
in a parallel mode offered an invisible click target.
They go through the X-Ray shader rather than their own, so a ghost reads as one
more see-through body, which is what the mode is for. The pass supplies z_range
and clipping_plane itself, since the volume collection sets them for the whole
pass it runs and the vertex shader discards everything outside z_range. It also
draws two-sided, so a hollow ghost shows its far wall like a real body does, and
hands the shader an opaque colour: X-Ray derives coverage from the view angle
and multiplies the colour's alpha into it, so a ghost carrying its own
translucency as well would composite far fainter than the body it mirrors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two conflicts are both places where upstream landed on top of IDEX/IQEX
code. In GCode.cpp the relocated file header block meets the IMEX placeholder
block, and the placeholders are set first: file_start_gcode is processed through
the placeholder parser now, which throws on a name it does not know, so a script
naming {imex_mode} would abort the export if the header ran first. In
test_multifilament.cpp both sides appended a case at the end of the file.
A coEnum config value has two representations: the typed ConfigOptionEnum<T> a
config cloned from the static classes carries, and the ConfigOptionEnumGeneric
that a config assembled from the option definitions creates - which is what a
preset, a project's own settings and the CLI all hold. imex_cfg_enum() accepted
only the first, so every IDEX/IQEX reader took the option default instead: a
printer saved as rear-left came back front-left in the settings, the bed zones
and the carriage markers, while the preset on disk still held rear-left.
Read the generic form too, keyed on the value map it carries, since only T's own
map yields a T. The last reader that matched on the coEnum tag alone and cast
across the two hierarchies now goes through the helper with everything else.
Declaring the three keys the static classes were missing is what lets a change
to imex_tool_layout invalidate the slice it moves, which it never did before.
The two that are only ever drawn stay out of that: a colour scheme and the
advisory margin bands do not reach a slice, so changing one must not discard it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
set_hover_tooltip records one string per frame, so the multi-material warning
replaced the mode tooltip instead of joining it, and hovering the icon on a
conflicted plate no longer said which mode was active or that clicking cycles
it. The two are composed into one string, paragraph separated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The estimate no longer reports a tower for a single filament whose flush matrix
purges, so the comments that justify calling prime_tower_is_printed() instead of
reading a depth now cite what still holds: it reads neither enable_prime_tower
nor print_sequence. GCodeViewer's comments name render_scene(), the function
that replaced the render() they still pointed at, and the pass contract mentions
the toolhead boxes it draws.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The estimate now returns no tower for a lone filament whatever the flush matrix
says, so the discrepancy this case pinned between it and normalize_fdm_2 is gone
and the sibling case covers what remains.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The merge of upstream main (dc021c6ef6) resolved the GCodeViewer.cpp conflict
from #15674 by keeping both sides. That left a stray `}` in
SequentialView::render_overlay, so the file no longer compiles. It also kept
the IMEX carriage update and the toolhead-box GL draw in the ImGui overlay
pass, which #15674 no longer uses for 3D geometry.
The carriage update and the toolhead boxes now run in render_scene next to the
primary marker, and render_marker draws the secondary markers. render_overlay
goes back to what upstream has, without the duplicated marker-position block
and the unused bottom_margin the merge left behind.
The same upstream change renamed PartPlate::show_tooltip to set_hover_tooltip;
the two IMEX call sites follow it.
Four files overlapped, and each resolution favours upstream where the two sides
had done the same work:
WipeTower's M104/M109 tool qualifier. Both sides bounds-checked the physical
extruder map lookup; upstream omits the T qualifier when the map cannot answer,
where this branch fell back to the logical index and so named a carriage that may
not be the one printing. Upstream's behaviour is what this branch documents
elsewhere, so its version is taken and the local helper is dropped.
get_extruders' mixed-slot switch. Upstream added the same concept to the CLI
overload as expand_mixed_slots, so the GUI overloads' parameter is renamed to
match rather than carrying two names for one idea.
GLCanvas3D's sequential-clearance branch gains upstream's
update_compacted_wipe_tower_clearance for the by-layer case.
The printer_agent re-sync in TabPrinter::reload_config was upstream's and their
preset-undo fix removed it, so it goes; the IMEX modes grid re-sync beside it
stays, since it spans three options and is not a Field.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
imex_wipe_tower_hull() open-coded "does this plate print a tower?" as one
filament and no forcing reason. normalize_fdm_2, which is what actually
clears enable_prime_tower before slicing, has a second arm the copy omitted,
and reads the mixed-filament flag project-wide where the copy read the
plate. Two cases went wrong in opposite directions.
A ByObject plate with several objects prints no tower and the scene draws
none, yet the copy validated one and could refuse the slice with "the prime
tower overlaps an area reserved for IDEX/IQEX parallel printing" - with
nothing on screen to move.
A plate using one plain slot while some other slot in the project is a blend
does print a tower, because normalize_fdm_2 keeps it for any mixed filament
in the project, and the copy skipped validation entirely, so the tower could
be placed in a carriage zone and sliced.
prime_tower_is_printed() in libslic3r states the rule once, and both the
gate and a test use it. The counts are the ones normalize_fdm_2 is handed:
filament slots as authored, so a mixed slot counts once, and distinct
objects rather than instances. filament_is_mixed is a project option, so it
is passed in rather than read from the print preset.
The test drives every combination the rule looks at and compares the verdict
against normalize_fdm_2 itself, so the two cannot drift again without
failing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The block describing what imex_wipe_tower_hull() does and does not cover had
drifted. It named estimate_wipe_tower_size, which upstream replaced with
estimate_wipe_tower_footprint; it described the plate filament count as an
override where the rewritten estimate treats it as a floor; and it
documented a Type2 stabilization cone as unhandled when the estimate now
folds the cone's worst-axis bulge into the margin the hull is built from, so
a second allowance would double-count. The comment on the m_print arm of the
guard now says what that arm actually does, which is nothing, since the
estimate stopped reading m_print.
It also records why the gate takes "is a tower printed" from
normalize_fdm_2's rule rather than from the estimate: the estimate reports a
tower for a single filament whenever the flush matrix purges, which would
hard-block a plate whose tower normalize_fdm_2 had already cleared, with
nothing drawn on screen to move.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
With single_extruder_multi_material and purge_in_prime_tower, the flush
matrix replaces the prime volume and its average is non-zero for one
filament, so the estimate reports a tower. normalize_fdm_2 clears the tower
for that same plate, so none is printed: the estimate answers how big a
tower is, never whether there is one, and a caller reading a non-zero depth
as "a tower is printed" reserves space for, or blocks on, a phantom. The
test asserts that disagreement directly.
Type1 is the exception in the same case, because it decides from its
per-filament purge list and a lone filament is never changed to. The scene
reads the same estimate either way. Type2 is the default for every non-Bambu
printer, and so for every IDEX/IQEX one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
nozzle_temperature is variant-expanded, so its length is columns rather than
filament slots. On the dynamic-nozzle path that makes it longer than the
slot count, and an out-of-slot index reached get_filament_config_index() and
came back as filament 0 - the clamp the bounds-checking was meant to remove.
Bound by the slot count instead; on the ordinary path the two are equal and
nothing changes. The is_extruder_used write gains the matching lower-bound
guard.
IMEXHelpers.hpp now states both halves of the rule its call sites follow.
Bound anything derived from the extruder map against the filament slot count
before using it as a filament id, not against the option about to be read.
And a miss is -1, which is a correct tool qualifier but matches no physical
head, so it cannot serve as a skip-the-primary sentinel: which head prints a
filament is answered by the filament and the map, never by a mode role,
since a primary-mode print may use any or all tools, one at a time.
The consequence is recorded there rather than left implicit. The two skip
sites skip nothing for a slot past the end of the map, so a plate with more
slots than nozzles double-writes the primary's pressure advance. It is
narrow and unreported, and a guard there would be a smaller change than
naming a head.
The header also records that RepRapFirmware sends an unqualified pressure
advance as M572 D0, naming drive 0 absolutely rather than the active tool.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CameraPopup and StatusPanel each bind a toggle handler that captures this
and never uses it, inside an #if !BBL_RELEASE_TO_PUBLIC block. CMakeLists
defines that as $<CONFIG:Release>, so the block compiles in every
configuration except Release - and Release is the only one CI builds. Under
-Werror the two captures therefore fail RelWithDebInfo, which is what a
development build uses, while CI never sees them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The parallel printing options shipped to testers as is_ixex and ixex_*,
became is_imex and imex_* when the feature stopped being called iXex, and
the two clearance keys were renamed once more to say what they measure:
nozzle to carriage edge on the collision side, not the carriage's full
width. Nothing translated any of it, so loading an existing printer profile
dropped every one of these values - the keys are unknown and cleared.
is_ixex is the one that matters most, because without it the others migrate
into a feature that stays switched off, leaving settings that look
configured and do nothing.
Per-plate mode is persisted twice and only one path went through
handle_legacy. Plate metadata in a 3MF is matched by exact attribute name
and written with set_key_value, so a project saved between the per-plate
mode landing and the rename loaded every plate back on Primary and sliced
single-carriage with no warning. The loader now accepts the old attribute
name.
ixex_primary_col and ixex_primary_row are dropped rather than mapped: the
primary is a role inside the mode's active-tools string now, not a grid
coordinate, and they were never in an option list, so no saved file carries
them.
The test drives the full era-1 key list and asserts the enum values rather
than non-nullness, since a forward-compatible substitution would otherwise
hide a failed deserialize behind a default. handle_legacy had no test before
this.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two range-for loops bound const std::string& to braced lists of string
literals, so each iteration constructed a temporary to bind to. GCC 16
reports it as -Wrange-loop-construct, which upstream's blanket -Werror turns
into a build failure; clang does not report it at all. Spell the
initializers the way the loop above them already does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review findings on the preceding commit, plus one defect it should have
caught.
- The IDEX/IQEX pressure-advance loop fed resolve_filament_for_head()'s
result straight into enable_pressure_advance and pressure_advance. That
result is bounded by physical_extruder_map, which holds one entry per
NOZZLE, while both options are indexed per filament SLOT. On a printer
with more nozzles than the project has filaments the two spaces diverge
and get_at() clamped the overflow onto filament 0, emitting its pressure
advance on a secondary carriage. The second-layer temperature loop bounds the
same lookup, but against nozzle_temperature, which is variant-expanded and so
is not the slot count either -- it is not the precedent it looks like.
IMEXHelpers.hpp states the rule
once, and a test pins the contract that makes the bound necessary:
resolve_filament_for_head() answers in nozzle space, so a non-negative
result is not by itself safe to use as a filament id.
- The header claimed every caller renders a -1 tool qualifier as "emit
none". RepRapFirmware substitutes the historical D0 instead, deliberately
and with its own comment in GCodeWriter. Say so, rather than leaving a
contract a future author would code against.
- A cross-reference pointed at a hard-coded line number that the preceding
commit had itself shifted by nine lines. Name the function instead.
- The multi-color rejection reasons reach the user through Print::validate()
as raw English, while the returns on either side of them use L(). Wrap
them and register IMEXHelpers.cpp for extraction. They also still said
"IMEX", the internal name, so they move to IDEX/IQEX with the rest of the
user-facing strings rather than shipping the internal one to translators.
- Trim the preceding commit's comments. One block explained the same
clamping hazard six times; the canonical explanation now lives in
IMEXHelpers.hpp and the call sites point at it. The mode grid carried
twelve lines of commentary and no code, most of it archaeology already in
the commit message, and one claim about the modes editor that was not
true. The ArrangeJob threading note stays: it documents an invariant that
cannot be recovered from the code.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review findings on the IDEX/IQEX parallel printing code, all in paths the
feature owns.
- physical_extruder_map lookups used ConfigOptionVector::get_at(), which
clamps an out-of-range index to values.front() rather than reporting a
miss. The map holds one entry per nozzle while filament ids index slots,
and nothing caps the slot count at the nozzle count, so a project authored
with more filaments than the printer has extruders silently addressed the
primary's head: pressure advance pinned to the wrong carriage, and
skip-primary loops suppressing whichever head sat at pem[0]. Bounds-check
at all four sites and treat the miss as "no mapping" (-1). Covered by a new
imex_pem_tool_for test; the header note now warns against get_at here.
- IMEXFilamentPickerPopover leaked a top-level window per ghost click:
wxPopupTransientWindow::Dismiss() only hides, and never reaches OnDismiss().
Destroy from an OnDismiss() override and dismiss the picker through
DismissAndNotify(), which is the path a successful pick takes.
- ArrangeJob read PartPlate's IMEX zone cache from the worker thread, where
a cache miss rebuilds GLModel members with no GL context current while the
GUI thread may be painting them. Snapshot the zones in prepare(), on the
main thread, already converted to plate-local coordinates.
- The mode grid anchored its row window to the Primary's gantry row. A window
as tall as the grid can only start at row 0, so this drew tiles for tools
that do not exist and hid real ones. Render the whole grid instead; a
Primary outside it is a data problem the zone layout already reports.
- Build the mode tooltip from one format string rather than two catalog
fragments concatenated around a runtime value, so translators can move the
mode name within the sentence, and register IMEXModesCtrl.cpp for string
extraction.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five sites carried a hardcoded fallback for imex_tools_per_gantry that had
to match the value registered in print_config_def, with nothing enforcing
the agreement, and several explanatory comments miscounted the sites they
described or cited stale line numbers.
Add imex_cfg_int/_float/_bool/_enum<T> to IMEXHelpers, which return the
value registered for the key when it is absent from the config, so the
registration is the single source and there is nothing left to keep in
sync. Route every read of the IMEX geometry keys through them: 32 call
sites across IMEXZones, PartPlate, GCodeViewer and Tab. The only direct
lookup left is the bail in PartPlate::imex_multicolor_block_reason, which
must not default because it reports a routing conflict and a defaulted
grid would produce a false warning.
imex_cfg_enum uses dynamic_cast on both halves rather than the type()
comparison the others use: every ConfigOptionEnum<T> reports coEnum, so a
type() check cannot tell one enum type from another and would cast a
ConfigOptionEnum<OtherEnum> to the requested T. The ConfigOptionPercent :
ConfigOptionFloat inheritance that rules dynamic_cast out for the float
accessor has no analogue for enums.
Correct the comments that prompted this: the cache-key input list in
PartPlate named five inputs for a nine-part key, the ImexMarkerKey note
in GCodeViewer called imex_tool_layout an input only the preview reads
when the plate keys it too, and four file:line citations pointed at the
wrong lines. Values are unchanged at every converted site; cache key
strings keep their existing representation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resolves all 21 inline comments, plus six changes that altered behaviour for
users not using the feature and seven defects found alongside them.
The reviewer's central point generalised: a pressure-advance change had moved
every RepRapFirmware user onto an unverified command form. Auditing for that
class found five more — 14 config keys leaking into every exported g-code, an
ungated Moonraker sync writing to non-IMEX printers' presets, every slice
eagerly re-rendering all plate thumbnails, a preset delta-encoding regression,
and physical_extruder_map being normalised for printers that read it the other
way.
The structural asks landed as asked: the 392-line bed-zone geometry moved to
libslic3r and is now unit-tested, the preview consumes that same layout instead
of a second copy, nine open-coded mode lookups became one, and the modes editor
moved out of Tab.cpp. Making the geometry testable exposed three further
defects in it, including an aggregated gantry that raised no collision strip.
The worst bug was not in the review: the GUI computed the firmware-managed
slice offset in the plate-list world frame while both consumers subtracted the
plate origin again, so every plate after the first failed to slice with 'part
is off the plate'.
User-facing strings now read IDEX/IQEX, honouring 461c69c83e. Config keys, C++
identifiers and 3MF metadata keys keep the imex_ spelling as on-disk format.
815/815 tests pass in Release.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
No IMEX code upstream, so nothing in this merge touches the feature. All 21 overlapping
files auto-resolved; verified every upstream addition is present in the merged tree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes review comment 10.
`461c69c83e` settled this in April — IMEX internally, IDEX/IQEX as the user-facing label — but the
UI strings were never converted. Every translated string naming the feature now reads IDEX/IQEX:
41 occurrences across the printer and process option labels and tooltips, the modes editor, the
plate mode indicator, the pre-slice warnings, the placement refusals and the slicing errors. The
reviewer listed eight; the rest were in the same class.
Nothing else moves. The config keys keep the `imex_` spelling — `is_imex`, `imex_mode_names`,
`imex_parallel_mode` and the rest are on-disk format in existing printer presets and 3MF projects,
so renaming them would break every profile and project already saved. C++ identifiers, filenames,
comments and test names keep IMEX as well: it stays the internal name of the subsystem, which is
what covers the topology space (one gantry with 2-4 tools, 2x1 and 2x2 grids) that neither acronym
names on its own. Where a tooltip quotes a key, the key spelling is preserved and only the feature
word around it changed.
The `is_imex` tooltip is reworded rather than substituted: it already named the hardware families
parenthetically, so a literal replacement would have said IDEX/IQEX twice in one sentence.
No translation impact — no IMEX string had reached OrcaSlicer.pot or any catalogue, so there is
nothing to migrate. One test asserted on the old error text and now matches the new one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes review comments 13, 14 and 15, and adds the test for a shipped-profile
regression that nothing guarded.
- 14 and 15: compute_imex_slice_offset had eight tests on the calculation and none
on the result, which is the whole firmware-managed path. test_imex_slice_offset
now covers the derivation end (which config produces a non-zero offset, and that
it is plate-local rather than moving with the plate origin -- the bug that
shifted every plate after the first) and the consumption end (emitted
coordinates and first_layer_print_min/max both move by the derived amount).
The first_layer case also cross-checks the two consumers against each other: the
declared bounds must keep the same relationship to the emitted toolpaths in both
frames, which fails if exactly one of them is shifted. It deliberately does not
pin the size of that gap -- it is 2.225 mm here, set by the wall generator, the
same with no offset at all, and pinning it would fail on an unrelated change.
- 13: nothing exercised the imex_mode / imex_mode_index / imex_mode_gcode
placeholders or the {global} flow into machine_start_gcode that their ordering
exists to guarantee. Seven cases now do, including the ordering itself -- the
mode script declares a global and machine_start_gcode reads it back, so moving
the mode processing later leaves the variable undefined and fails the export --
plus the inert cases (Primary mode, and a printer with the table filled in but
is_imex off). All matching is whole-line, because the config block the exporter
appends repeats machine_start_gcode verbatim and would make substring checks
meaningless.
- New: GCodeWriter passes this->config.is_imex.value into the heater remap, and
nothing tested that it passes the flag rather than a constant. Hardcode true
there and the whole suite stays green while fdm_bbl_3dp_002_common, which ships
physical_extruder_map [1,0], starts sending filament 0's M104/M109 to heater 1.
The new case runs a two-nozzle non-IMEX printer with that map and asserts each
filament's temperature reaches only its own tool. It uses idle_temperature via
ooze prevention rather than nozzle_temperature: keys in
filament_options_with_variant are re-indexed per filament by variant slot at
apply time, and this harness pins nozzle_diameter to one value, so every filament
resolves to the same slot and the temperatures stop telling the heads apart.
Also fixes two weaknesses in tests added earlier in this branch: an assertion that
would have been prefix-satisfied by the very routing it was meant to exclude, and
a whole-file command comparison between two slices, which this slicer's output is
not stable enough to support.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes review comment 20; the other two are non-IMEX leaks found auditing the
branch.
collect_imex_warnings re-derived the active mode, its tools, the primary and the
filament routing that Print::validate also derives, so the warning and the hard
block could drift apart -- and this function had already had one index-confusion
bug, the AFC/MMU wrong-filament names fixed in fbc58d2a1d. Both now read
imex_resolve_routing() and derive nothing themselves. The function stays
file-static: what is left in it is PresetBundle lookups, bed-type resolution and
formatting, none of which can disagree with the slicer about what the plate is
doing, and the index-confusion surface is now library code with tests covering the
AFC manifold in both directions. A latent out-of-bounds read went with it -- the
primary fallback can return -1 and the bounds checks were upper-only, so
filament_presets[-1] was reachable on a profile whose roster names a head absent
from the map.
Slicing eagerly re-rendered every plate thumbnail on the main thread after
switching to Preview, up to two blocking offscreen GL renders per plate on every
slice click. The work was already redundant: select_view_3D("Preview") invalidates
the thumbnails and marks the toolbar dirty, and the next frame force-regenerates
them anyway. It could not have served its stated purpose either, since it ran
immediately after reslice(), which only starts the background slice. Both calls
dropped; the plate badge state is recomputed per frame and is unaffected. The
export_3mf thumbnail log lines are back at info, and the slice-event traces
restored.
The Moonraker device sync wrote physical_extruder_map into the edited printer
preset ungated, so any Klipper machine running a current AFC build had its preset
marked dirty with no user action, and saving persisted the map into every 3MF
after. The map is indexed by logical extruder while the device reports one entry
per lane, and nothing in the lane payload carries the logical slot, so the two
index spaces coincide only when the counts match. Now gated on is_imex, written
only when the counts agree, and only when the value actually differs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes review comment 17.
GCodeViewer had its own copy of the flip_x/flip_y corner mapping, the active
column/row sets, the physical-to-zone index mapping and the zone pitch -- the same
derivation as the plate's, with nothing keeping the two in step. It now calls
compute_imex_zone_layout() and consumes head_zone_centers. The mirror is expressed
as a reflection about the midpoint of the two zone centres rather than about a
zone-relative strip width, which is algebraically identical for equal-sized zones
and needs no pitch, and the toolhead-box face is chosen by comparing zone centres
instead of physical columns.
The July report of a math error in the visualizer for non-primary heads was this
drift: the sec_box_offset_y else-branch hardcoded -imex_box_wy, which happened to
equal the primary's offset on the rear-* layouts and pointed the wrong way on the
front-* ones. Structurally unreachable now.
Verifying the two sides matched turned up two config defaults that disagreed, both
fixed in their own commits: imex_nozzle_clearance_x/y (the viewer's 30.0f matched
PrintConfig, the zone code's 0.0 did not, and the strip loops are gated on it) and
imex_tools_per_gantry (the library's 2 matched, both GUI paths used 1).
Consuming the shared function meant resolving it per frame, and the sequential-view
marker flag is sticky, so one drag of the slider made every subsequent frame parse
five strings and allocate a dozen containers from inputs that never change. The
resolve now sits behind a cache key mirroring PartPlate::build_imex_cache_key(),
plus the two inputs only the preview reads -- the bed extents and the tool layout.
An idle frame compares scalars and allocates nothing. The toolhead-box mesh, which
was being re-uploaded to the GPU every frame for the same reason, is rebuilt only
when the clearances change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes review comment 9.
PartPlate::reset_imex_mode() had no callers, while the PR description said
deleting a mode resets affected plates to Primary. IMEXModesCtrl now exposes an
on_mode_removed callback that TabPrinter::build_fff handles by resetting every
plate whose mode matches the deleted row, under a single undo snapshot, followed
by the same dirty/update sequence the plate's own mode button runs.
Wired to deletion only, on purpose: the name field notifies on every keystroke, so
routing renames through the same path would orphan and reset the plate on the
first character typed. Renames stay covered by the slice-time fallback and its
warning. The callback is copied to a local before notify(), because notify()
reaches load_from_config() -> clear_rows(), which tears down the row the handler
is running inside.
The rest of this file is the modes editor moving out to its own translation unit,
leaving the include and the construction site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes review comments 21, 4 and 12, and the widget half of 19.
- 21: IMEXModesCtrl was 572 lines inside Tab.cpp. It now lives in
IMEXModesCtrl.{hpp,cpp} next to IMEXFilamentPickerPopover, which was the
precedent named in the comment. The move itself is exact -- member order,
comments and every string literal unchanged -- and the class had no file-local
dependencies in Tab.cpp, only its include list, so the new source states those
explicitly.
- 4: a mode row with an empty Name was silently dropped on save, tools and G-code
with it, and matches_config() compared against that same filtered output so the
preset never went dirty and the row stayed on screen. Rows are now given a
generated unique name instead of being discarded, and add_row() pre-fills one so
the common path never produces a blank. Names are deliberately not translated:
objects store a mode name in imex_parallel_mode and GCode.cpp matches it by
string, so a localized name would break a project reopened in another language.
- 12: the editor had a third parser that read a bare token and an unknown role
suffix as Primary, while parse_imex_active_tools reads both as Copy -- so the
editor and the slicer could read one imex_mode_active_tools string two different
ways. Deleted; the editor now uses the same two helpers the slicer does.
- 19: tile state was an int shadowing ImexRole, with the role letters duplicated in
a second switch that wrote the on-disk format. The tile now holds
optional<ImexRole>, with Inactive spelled as the absence of a role rather than a
fifth integer, and the letters come from kImexRoleTable.
Four further changes, from testing rather than the review:
- Deleting a mode reported only the row's current name, so renaming a mode and then
deleting it left every plate using it stranded on a name that no longer exists.
Both the build-time and current names are now reported, minus any a surviving row
still carries.
- The instruction text and colour legend were built once in the constructor and
never rebuilt, so raising gantry count to 2 gave the tiles a Span role the legend
never explained until the preset was saved and the page reopened. Both are
rebuilt with the grid, and the per-role detail moved into legend tooltips so the
panel no longer opens with a paragraph.
- The tile holding Primary is now read-only. Primary is tool 0 and moves only via
Tool 0 Position; a click could previously demote the only Primary, leaving a mode
that parses to no primary at all, which degrades the plate to an ordinary
single-tool print with nothing in the editor showing what is wrong. A mode
arriving without a Primary keeps every tile live so it can still be repaired.
- Names and G-code were read with ToStdString() (the ANSI codepage on Windows) and
written with from_u8() (UTF-8). On a non-UTF-8 codepage a name like "Modus A"
with a diaeresis was stored as invalid UTF-8, came back blank, and was then
silently renamed by the auto-naming above. Every read is now into_u8() and every
write from_u8(); EditGCodeDialog was affected in both directions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
calc_imex_zones() is now 69 lines: fetch the two edited configs, call
compute_imex_zone_layout(), store the result, and clip each returned rect to the
bed outline to build the GLModels. That last step is the only part that needs GUI
types, which is why it stayed. See the extraction commit for the behaviour-
preservation evidence.
refresh_imex_slice_offset() is deleted along with its call in
update_slice_context(); the offset is derived in the engine now, and it was
computing it in the plate-list world frame, which double-counted the plate origin
for every plate after the first.
Two smaller changes:
- The zone/ghost cache key omitted imex_tool_layout, which decides which physical
corner tool 0 occupies and therefore moves every zone rectangle, collision strip
and ghost offset while every other keyed field stays put. A layout change
produced an identical key. That this currently appears to work is incidental --
some other path happens to rebuild -- and not something to depend on. Found by
building the preview's own cache key against this one.
- The tools-per-gantry fallback for a missing key was 1 in two places where
PrintConfig registers 2 and the zone code uses 2. All four sites now agree; a
missing key otherwise grouped tools against a grid divided a different way.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
None of these came from the review; they were found auditing the branch for the
same class of leak review comment 6 identified.
physical_extruder_map was normalised through effective_physical_extruder_map on
every Print::apply(), so any printer with more than one nozzle and no authored map
got the identity [0,1,...,n-1] where the single-element {0} default belongs. The
key carries two readings: the IMEX paths index it by logical extruder and need one
entry per extruder, while the inherited BBL paths read it through the clamping
get_at(), for which {0} means "everything is physical 0". Deriving unconditionally
imposed the IMEX reading on profiles that mean the other one, changing the config
block line and the {first_tools} / {first_filaments} / {curr_physical_extruder_id}
placeholders for multi-nozzle non-IMEX printers. Gated on is_imex; every consumer
needing the per-extruder form is already IMEX-gated, and profiles with an authored
map of the right length are unaffected either way.
That gating unmasked a latent out-of-bounds read: WipeTower's M104/M109 emitters
index m_physical_extruder_map by tool with no bounds check, which reads past the
end of the single-element default on a multi-nozzle machine. Upstream's bug, from
the BambuStudio wipe tower sync, previously hidden because the map was being
widened for everyone. Now bounds-checked, falling back to the tool's own index --
the form GCodeProcessor already uses for the same map.
Preset::save() and get_preset_differed_for_save() carried a branch storing the
full vector whenever a child and its parent had different lengths. It was written
against a set_with_nil that threw on mismatched sizes; upstream #13035 replaced
that with a tolerant version that keeps the child vector verbatim and nil-marks
only the overlapping range, and that fix was already in the tree when this branch
was rebased. Left in, it defeated the delta encoding for every user printer preset
whose extruder count differs from its parent's: a 7-extruder profile inheriting a
single-extruder base wrote out all of its per-variant retraction keys as literals,
including ones identical to the parent, pinning them against future vendor updates
while the UI still reported the preset as inheriting. Removed; the two save loops
are now identical to upstream. Note this only affects new saves -- presets already
written keep their frozen values until re-saved.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes review comment 1.
imex_parallel_mode and imex_head_filament_map were streamed raw into XML attribute
values, while every other free-text attribute in the same writer goes through
xml_escape. Mode names are free text, so "PLA & ABS", a quote or a "<" made the
document malformed. The failure is not a bad value on reload: both load paths for
model_settings.config return false on an expat error, and m_is_bbl_3mf is set
before the second entry loop runs, so the whole project fails to open with
"Archive does not contain a valid model config".
Both attributes now use xml_escape_double_quotes_attribute_value(), which also
emits tab, CR and LF as numeric character references. That matters and plain
xml_escape would not do: XML normalises literal whitespace in attribute values on
read, so a tab in a mode name would come back as a space and silently rename the
mode. The read side needs no change -- it takes expat's already-decoded value with
no second unescape -- so this is a lossless round trip and a file written by the
new code still loads in an older build.
The round-trip test used "copy_mode", which exercised none of this; it now carries
&, <, a quote and a tab, and also pins that ' and > come back unmodified, since
both are legal raw inside a double-quoted value.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes review comment 6.
The per-tool pressure advance work changed set_pressure_advance() for users who
are not using the feature. RepRapFirmware lost its D qualifier when no tool index
was supplied: upstream emits M572 D0 S<pa> unconditionally, and a bare M572
applies to whatever tool is currently selected and errors when there is none, so
PA started depending on tool-selection state for every RRF user. The D is back,
defaulting to 0, and D<tool> is reached only from the IMEX paths.
The same rewrite had also changed the comment separator from "<value>; Override"
to "<value> ; Override" on the Klipper, RRF, Marlin 2.x and Marlin Legacy
branches, so every non-IMEX print of those flavors carried a one-byte diff.
Restored. Upstream is internally inconsistent here -- BBL and Repetier do use the
spaced form -- and the point is to match it exactly rather than to tidy it.
Emitted output for all six flavors with no tool index is now byte-identical to
upstream. Verified on a real slice: a Klipper profile emits
"SET_PRESSURE_ADVANCE ADVANCE=0.02; Override pressure advance value", an exact
string match, with no EXTRUDER= qualifier. The tests were pinning the regressed
form and are inverted.
Also records at the imex key registrations why they are kept out of the g-code
config block, matching the house convention at the other banned keys.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes review comment 2, part of 18, and one non-IMEX regression the review did
not cover.
An imex_parallel_mode naming no entry in imex_mode_names still entered the
parallel branches. get_imex_active_tools() returned empty and the else was
skipped, so no head received its 1st-to-2nd layer temperature transition, and
imex_suppresses_bare_toolchange() still dropped the initial T<n> on the
expectation that a mode script would select the tool. validate() did not catch it
because its guard is declared_primary >= 0 and an unresolved mode yields -1. You
reach it by renaming a mode after a plate is set to it, or by opening a 3MF whose
printer preset names its modes differently. m_imex_parallel_mode is no longer
assigned before the lookup; a non-Primary name that matches no row now warns and
re-resolves against the Primary row, which is the fallback the PR description
already claimed. A mode that resolves to an empty tool roster takes the same path,
since the emitted G-code is wrong in the same way.
Warned rather than blocked: opening someone else's 3MF is a legitimate way to get
here and the Primary reading prints correctly, so refusing to slice would turn a
recoverable situation into a dead end. Silent was not an option either, because
the plate keeps showing the stale mode name while drawing no zones.
Separately, the 14 imex config keys all register non-nil defaults, so
append_full_config was emitting "; imex_* = <default>" into every exported
G-code, including on single-nozzle printers with nothing to do with the feature.
They are banned from the dump, matching the treatment already given to the
fast-purge, extruder-change and timelapse keys, so the config block is
byte-identical to the pre-IMEX baseline for the whole shipping fleet. Nothing
reads them back: GCodeProcessor has no imex reference, and the two per-plate keys
round-trip through the 3MF's model_settings.config on an independent path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes review comment 11, and fixes a worse bug found while doing so.
The offset was only ever pushed from PartPlate::refresh_imex_slice_offset(),
reachable from update_slice_context() and the plater -- both GUI-only, and it
dereferences wxGetApp(). A headless slice therefore kept Vec2d::Zero(), so
orca-slicer --slice on a plate with imex_firmware_managed_zones emitted
slicer-managed coordinates while the firmware applied its own offsets on top.
Print::update_imex_slice_offset() now derives it from the applied config and runs
from process() and export_gcode(), so a CLI slice gets the value a GUI slice does.
It reads m_full_print_config rather than m_config because imex_tool_layout and
imex_carriage_margin are printer-preset options with no member in the static
PrintConfig, and it takes the mode from the same place GCode.cpp resolves it, so
the shift cannot disagree with the mode that is emitted.
The GUI push and Print::set_imex_slice_offset() are deleted rather than kept as an
override, because the two did not agree. calc_imex_zones() divides
get_extents(m_shape), and set_shape translates m_shape by the plate position, so
the pushed offset carried the plate origin -- which translate_to_print_space() and
the writer offset already subtract. Plate 1 sits at the origin and agreed by
accident; every later plate had the origin subtracted twice and was shifted by a
full plate stride. Not silent, either: the displaced geometry fell outside the
printable area, so slicing plate 2 failed validation with "part is off the plate".
Confirmed fixed on hardware profiles -- the same model on plates 1 and 2 now emits
identical extents.
Deleting the push also removes the post-apply ordering constraint that forced the
duplicate call in Plater::priv::update_background_process: the value is computed
at the point of use, and process()/export_gcode() are structurally after apply().
Also routes validate()'s primary-routing check through imex_resolve_routing() so
the hard block and the plater's warning cannot describe a plate differently
(review comment 20), and through find_imex_mode() for the mode lookup (18).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes review comment 16, and fixes three defects the move exposed.
PartPlate::calc_imex_zones() was 392 lines deciding where every zone, collision
strip and safety margin sits, in the GUI layer, with no test coverage. The three
wxGetApp() calls that kept it there were all in its first 25 lines, fetching two
configs. The geometry now lives in compute_imex_zone_layout(); the wrapper fetches
the configs, calls it, and clips the returned rects to the bed outline for the
GLModels, which is the only part needing GUI types. libslic3r gained no wx
dependency: it takes DynamicPrintConfig directly, so the option lookups moved
verbatim rather than through a hand-written value struct that could drift.
The move is otherwise exact -- verified by a line-for-line diff of every
arithmetic expression against the original, and by running 15 scenarios through
the extracted code against hand-derived values. The only deletion is a lambda that
was never called.
Three fixes on top, each of which needed the code to be testable:
- An off-grid or absent Primary left pri_col/pri_row at their (0,0) initialisers
and built a layout from them, reporting the whole bed as the clear primary zone
and the whole bed as a blocked mirror zone at once; under
imex_firmware_managed_zones it shifted the slice by the bed centre. Guarding on
the resolved primary head covers both routes. Reachable only from a hand-edited
preset or a 3MF authored against another printer -- the editor pins Primary to
tool 0 -- but that is the same class the unresolved-mode fallback handles.
- imex_nozzle_clearance_x/y fell back to 0.0 where PrintConfig registers 30.0.
Both strip loops are gated on the value being positive, so the fallback emitted
no collision strips at all while the preview still drew 30 mm toolhead boxes.
- The collision-strip loop asked each mirror head for its own grid cell, but an
aggregated gantry's cell is pinned to the primary's column and expanded into a
full-width row strip. Where the representative's column differed from the
primary's, no boundary matched and the plate came back with no strips and no
margin bands -- an object flush against the shared boundary sliced without a
warning while the far carriage occupied it. Present since Span aggregation was
added in 4966d0fae8 and carried out of PartPlate verbatim. The flags now come
from the painted cells; an exhaustive sweep of the reachable grid, role and
layout space (1,630,720 configurations) shows the only behaviour change is the
missing strips appearing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes review comments 3, 5, 7, 8, 18 and 19, plus the library half of 20.
These share a file, so they share a commit; each is independent of the others.
- 3: ::isspace(char) is undefined for bytes above 0x7F because char is signed on
our targets. Three call sites now go through one strip_whitespace() using an
unsigned char cast. Line 308 parses imex_head_filament_map straight out of 3MF
metadata, so a non-ASCII byte reached it without passing through the UI.
- 5: an imex_head_filament_map override past the end of physical_extruder_map now
falls through to the printer's own routing instead of resolving to a wrong
filament. Bounded in resolve_filament_for_head, where the slot count is known,
rather than at the parse site, which has no count to check against; the parse
site also gains the absolute MAXIMUM_EXTRUDER_NUMBER cap its sibling already had.
- 7: imex_physical_heater_for's !is_imex early return is what keeps a stock BBL
profile (physical_extruder_map [1,0]) out of the heater remap, and had no test.
Six cases now cover it, pinning pass-through rather than get_at()'s clamp.
- 8: ImexRole::Span was missing from the imex_head_transform switch, so it warned
under -Wswitch. Identity is correct, not merely convenient: a Span tool prints
the primary's own zone through mid-print toolchanges and has no zone to be
translated into.
- 18: three positionally coupled string vectors were resolved by nine open-coded
lookups using three incompatible bounds idioms. None read out of bounds, but six
folded the guard into the match condition, so a ragged row did not stop the scan
and a later duplicate name could win. struct ImexMode + find_imex_mode() is now
the only resolution rule: the names array is the roster, first match wins, a
short sibling pads to empty and sets ragged, not-found is an explicit -1.
- 19: the letters P/C/M/S existed in three independent copies, one of which was the
writer of the on-disk format. kImexRoleTable is now the single source, read by
both parsers and the serializer. Adding a role was 14 edit sites with one
compiler-enforced; it is now the enum, the table entry, and four -Wswitch
switches. Verified by adding a fifth enumerator and recompiling: exactly four
warnings, nothing else.
- 20: imex_resolve_routing() extracts the mode/primary/routing chain that
Print::validate and the plater's warning collector each derived separately.
The three config keys keep their names, types and on-disk representation. This is
a read-side view only; presets and 3MF files are unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
update_values_from_multi_to_multi_2 iterates the destination PRINTER's variant
list while writing into a row taken from the destination PRINT preset. Those two
lengths are maintained independently -- print_extruder_variant against
printer_extruder_variant -- and Tab::load_current_preset() runs the migration
before the print preset is re-selected for the new printer. Opening a project
saved on a single-variant printer and switching to a seven-variant one therefore
wrote six elements past the end of a one-element vector. The corruption stays
silent until the next allocation, so the abort surfaces somewhere unrelated and
the backtrace points at innocent code.
Size the row to the variant count before indexing it. Every write is then in
range, and the result carries one value per destination variant, which is what
the callers consume. Pad with nil rather than a copied value: set_to_index()
skips nil entries, so a variant the object has no opinion about keeps tracking
the print preset instead of being pinned to another variant's number.
The same shape -- a count from one array indexing another -- appears twice more
in this file. update_values_from_multi_to_multi has three of these writes
protected only by assert(idx < old_count), and NDEBUG is defined for every
non-Debug configuration, so those guards are absent from shipping builds.
update_values_from_single_to_multi has the read half. Both are bounded here;
leaving them would fix one third of one defect.
Source reads are bounded too. is_nil(size_t) indexes values[idx] without
checking, so an index past the end was undefined behaviour on that side as well.
Where the row already matches the variant list -- every case that was not
corrupting the heap -- the resize is a no-op and the output is unchanged.
Fixes#15455
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
append_tcr2 scanned the tower's G-code for a "preheat T<n>" comment and rewrote
its S value to the interface temperature. Nothing it could match was ever there.
That comment has exactly one producer, GCodeProcessor's backtrace injector, and
that runs inside run_post_process() -- a pass over the finished, exported file.
append_tcr2 runs while the file is still being generated, so the text it looked
for did not exist yet and could not.
The loop therefore walked every line, matched none, and swapped the string for
an identical copy. Delete it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The badge is meant to predict whether slicing will be refused, and it delegates
to the same helper for that reason. It was feeding that helper a different
filament list. get_extruders(true) resolves a mixed slot into its physical
components -- right for AMS mapping, which has to know what is actually loaded
-- while Print::validate counts the slot itself.
So a plate holding one two-component blend reads as two filaments to the badge
and one to validate. The badge sees two, decides the plate is fine, and stays
silent; the slice is then refused. It also runs the other way: a plate the user
sees as a single colour draws a multi-material warning, because the expansion
made it look like two.
Give get_extruders an expand_mixed flag, defaulted so every existing caller
keeps the resolved list, and have the badge ask for the authored one.
The badge was also only mirroring validate's multi-color rule, not its first
one -- a mixed filament is unsupported in a parallel mode outright. Without it
the badge stays quiet on exactly the plate validate refuses first.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rule's comment claimed the plate "is not printable as configured: the
primary tool executes the toolpaths while the flow and temperatures were
computed for a filament it cannot load". That is not what the emitter does. It
never uses the declared primary -- it re-derives an effective one from the
filament actually in use -- so a plate whose only filament sits on a Span tool
sharing the primary's gantry produces coherent G-code and would print.
The refusal is still correct, but it rests on intent rather than physics: a
parallel mode exists to run carriages in parallel, and a single-colour plate
riding one span lane is not that. Left as a physical-impossibility claim, the
rule reads as a false positive to anyone who checks it against the emitter --
a review already flagged it as one -- and the obvious "fix" is to relax it.
Say which it is, and keep the genuinely-broken case distinct: a filament routed
to a head outside the mode's active tools still yields a stuck-hot nozzle, and
that one is not a matter of taste.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A mixed filament is unsupported in a parallel mode outright, but the rule saying
so ran third. A plate carrying a blend plus any second filament tripped the
multi-color rule's used > 1 gate first and was told its active tools all sit on
one gantry -- a diagnosis of a multi-color print the user never configured,
whose remedy is to go rework the mode's tool roster. The blend was never
mentioned. Move the check ahead of both rules below it; being unsupported
regardless of routing or topology, it dominates them.
Nothing is masked that leads anywhere else: every branch of
imex_multicolor_block_reason is itself confined to non-primary modes, so the
mixed message's remedy -- switch this plate to Primary -- silences those too.
Say "Mixed filaments", not "Blended". Every other string in the app calls these
mixed, including the button that creates one and the sibling refusal for the
wipe tower filament, so the user had no way to connect the message to the
feature it names.
Three comments in the block were wrong, and two of them were newly wrong. The
routing rule's bounds-check note still said "Blended slots are out of range by
construction, but they never reach here -- the rule above returns first": the
rule above is now the multi-color one, which does not return first for a single
mixed filament, and out-of-range is not guaranteed at all. Mixed slots are kept
at the tail of the filament arrays by convention, not by enforcement --
PresetBundle::set_num_filaments grows filament_is_mixed with resize(), so
raising a printer's extruder count with a blend present lands physical slots
after the mixed one. The scan is position-agnostic and stays correct; only the
stated reason was wrong.
The same discovery makes the empty-routed_list guard live rather than the dead
code it was described as. Print::apply() normalises physical_extruder_map before
validate() runs, so an unauthored map is never the cause -- but a printer with
more filaments than logical extruders leaves the tail slots outside the map, and
raising the extruder count does exactly that.
The new test validates the plate twice. The first pass, with no blend, asserts
the multi-color rule is armed at all; without it the second proves nothing,
because the rule only fires here thanks to a degenerate fixture mode whose two
tools share a gantry. Give that mode a Span tool and the whole test would pass
under either ordering while appearing to guard it. It also pins err.object,
which the mixed path sets and the multi-color path leaves null -- a discriminator
that survives the next wording change. Verified by reverting the order: the test
fails on both the message and the object.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
M104/M109 address a heater, but every caller of the instance
GCodeWriter::set_temperature overload addresses filaments by logical id, so on a
printer whose physical_extruder_map is not the identity the emitted T named the
wrong head -- or, where the logical id exceeds the head count, no head at all.
With a map of 0,0,0,0,1,2,3 a toolchange to filament 5 emitted "M109 S265 T4"
and "M104 S190 T4 ;cooldown" while the head it meant was T1.
Upstream already treats these commands as physical: the preheat it injects in
GCodeProcessor maps through the same map before emitting, and BBS's own wipe
tower does likewise. Emitting logical is the half that never got the memo.
That mismatch also disabled the cooldown suppression beside the preheat, which
compares the line's T against pem[tool_number] and so never matched a logical
one -- 171 cooldowns survived in a two-head print where none should have. Worse,
it could match the wrong line: a cooldown for filament 1 emitted T1, and a
toolchange to filament 5 gives pem[4] == 1, so a legitimate cooldown for head 0
was deleted because the incoming head happened to be numbered 1.
Translate once, in the instance overload every logical-space caller passes
through. The static overload is already physical-in and is left alone.
Gated on is_imex. physical_extruder_map carries two readings in this tree: the
BBS paths index it by extruder id, the IMEX paths by filament id, and the two
coincide only when the filament and nozzle counts match. Mapping unconditionally
would impose the IMEX reading on profiles that mean the other one --
fdm_bbl_3dp_002_common ships a non-identity [1,0], spared today only because
single_extruder_multi_material suppresses the T qualifier entirely.
The wipe tower's interface-temperature pass has to move with it. It strips the
M109 that post_toolchange emits by searching for that filament's tool index, so
it now searches for the mapped one; left alone it would have stopped matching,
and the surviving blocking M109 would have silently defeated the interface
temperature. Its sibling pass reads WipeTower2 output, which emits no T at all,
and is deliberately unchanged.
The bare T<n> toolchange stays logical -- it selects an AFC lane, not a heater.
Test slices two objects across a head boundary, the only case that reaches this
emission: the same-physical short-circuit in set_extruder suppresses the
cooldown entirely for lane swaps within one head. It scans every M104/M109
rather than matching fixed strings, so it catches any unmapped emission and not
just the two sites changed here. With the mapping neutered it reports 101
offending lines; with it in place, none.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The routing error ran to roughly 450 characters and explained the mechanism
before it got to the remedy. It also offered to "edit the mode in Printer
Settings so its Primary tool is one of %3%", which on a plate whose filaments
resolve to no head at all rendered as "one of no configured extruder". Cut it
to the mode, the tool it prints with, where the plate's filaments actually are,
and the two things the user can do about it.
The second msgid that named candidate modes went with it. It could only suggest
a mode whose primary is among the routed heads, and every mode on the printers
this fires for declares 0:P, so it had nothing to offer.
Blended filaments now return before that check rather than falling through it.
A blend is mixed at the nozzle by its component toolheads, and a parallel mode
is already using those toolheads to print copies or mirrors, so the two cannot
run at once regardless of where the components route -- including when a
component sits on the declared primary. Reaching the routing rule would also
have described them wrongly: mixed slots sit past the end of
physical_extruder_map, so they resolve to no head and read as merely unrouted.
Keeps the empty-list guard the shortening first dropped. validate() reads the
raw physical_extruder_map, whose registered default is a single entry, so a
profile that declares IMEX modes without authoring a map leaves every slot past
the first outside it -- and the sentence ended in a dangling "on .".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brings the upstream color-mixing feature and its follow-ups onto the branch so the
IMEX placement and primary-routing checks are built and tested against them for the
first time.
Merged clean, no conflicts. Not yet exercised together: a mixed filament is a virtual
slot no nozzle carries, while physical_extruder_map routes logical slots to physical
heads, so the IMEX pem lookups have no defined answer for one. Print::extruders()
lists mixed slots under their own id while tool_ordering.all_extruders() lists them
post-expansion, and the IMEX code reads both.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Plater::validate_current_plate() runs the same background_process.validate() as
update_background_process(), and on success clears update_apply_result_invalid(false)
and closes the ValidateError notification -- but it never consulted
imex_placement_violation(). Any event reaching it wiped a live IMEX placement error
and re-enabled the Slice button on a plate the slicer still refused; pressing Slice
then hit the check in reslice() and returned early, so the job simply never started.
Reproduce by clicking the bed with the prime tower overlapping a reserved area.
Plater::select_plate_by_hover_id() -> select_plate() calls validate_current_plate()
unconditionally, even when the clicked plate is already current, and deselects as a
side effect -- which makes it look as though deselecting the tower cleared the error.
Escape and clicks off the bed go through selection_changed(), which only renders and
clears nothing.
Extract the violation-to-message mapping into one helper and call it from both paths.
model_fits is set alongside err.string in validate_current_plate, mirroring the
missing-plugin block below it: the slice is already gated by m_apply_invalid, but
leaving m_ready_for_slice true would trap a future consumer that reads it alone.
These are the only two sites that matter. NotificationType::ValidateError has exactly
three references in the tree and update_apply_result_invalid exactly four; the other
slice-ready writers can only touch m_ready_for_slice, never m_apply_invalid, so they
cannot re-enable Slice on their own.
Three adjacent gaps are left alone, all pre-existing: "Slice all" is hard-coded
always-enabled regardless of plate state; a slice-all batch halts silently at a
violating plate because reslice() returns above the line that queues the advance; and
object_list_changed() computes its own can_slice from geometry, harmless only because
the result is ANDed with PartPlate::can_slice().
This is a hole in the shipped tower-zone check rather than a regression from rotating
the tower hull -- it was simply invisible until a tower could be placed in violation.
No automated gate: Plater is GUI-only, the helper is file-local and unlinkable, and
imex_placement_violation() needs a live wxApp and preset bundle. "Both call sites
consult it" is a call-graph property no unit test can express. The grep for a single
imex_placement_violation reference is a future regression tripwire, not evidence this
refactor happened -- it already returned 1 beforehand.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brings in 54 upstream commits, the bulk of them the BambuStudio-ported color
mixing / mixed filament subsystem (#15347) plus its follow-ups, along with the
Assimp-backed colored OBJ import, warning-policy build changes, and assorted
profile and localization updates.
Two conflicts, both "each side added at the same point", resolved by keeping
both:
- Print::validate() -- our IMEX multi-color block and upstream's new gradient
mixed filament warning were inserted at the same spot after the empty
extruders check. They test unrelated conditions, so both are kept, each with
its own closing brace.
- tests/libslic3r/test_3mf.cpp -- our three IMEX per-plate round-trip scenarios
and upstream's mixed-filament round-trip scenario both append to the end of
the file, and each side added one include. All four scenarios and both
includes are kept.
Everything else merged cleanly, including GCode.cpp, ToolOrdering.cpp,
PartPlate.cpp and PrintConfig.cpp. Upstream left the is_extruder_used block
untouched, so the IMEX supplement still applies, and estimate_wipe_tower_polygon
is unchanged, so the prime tower hull work is unaffected.
Not addressed here, and worth its own change: a mixed filament is a virtual slot
that no nozzle carries, while physical_extruder_map routes logical slots to
physical heads. Print::extruders() lists mixed slots under their own id whereas
tool_ordering.all_extruders() lists them post-expansion, so the IMEX pem lookups
have no defined answer for a mixed slot. Upstream's own guards reject a mixed
filament where a physical slot is required; IMEX likely wants the same.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The IMEX primary tool prints the sliced paths directly, so it can only load a
filament that physical_extruder_map routes to it. The ghost filament picker
enforces that for the secondary tools -- it offers only lanes whose pem entry
equals that head -- but the primary's filament comes from the ordinary object
filament selector, which has no IMEX awareness. Nothing detected the mismatch:
collect_imex_warnings() computes the same condition and discards it into a
display fallback, and the multi-color rule never examines it.
Block it in Print::validate() via the existing imex_primary_tool_for_mode and
imex_primary_logical_from_objects helpers. The message names the declared
primary, the heads the plate's filaments actually live on, and any configured
modes whose primary would work, and carries the object so the notification can
offer a jump to it.
Blocks rather than warns, matching the multi-color rule: the plate is not
printable as configured, and where the routed head is also absent from the
mode's active tools the 1st->2nd layer temperature branch skips it too, leaving
that head at its initial-layer temperature for the whole job.
The multi-color check now runs first. Its constraints -- an MMU manifold sharing
one head, a single-gantry mode -- cannot be fixed by switching mode, so the more
specific error should win rather than be masked by routing advice that leads
straight back to it. The extruders().size() > 1 gate moved onto that call, since
the routing check must also see single-filament plates, which is its common case.
The copy-mode guard-rail test printed on a filament routed off the primary, so
it asserted a plate this rule now refuses; retargeted to a well-formed plate.
Its replacement pins the object's own extruder, because ModelVolume reports its
extruder_id and would otherwise put a primary-routed slot on the plate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
imex_wipe_tower_hull() took the axis-aligned box estimate_wipe_tower_polygon()
returns and compared it to the IMEX collision zones as-is. The real tower is
rotated about its anchor corner before placement -- first_layer_wipe_tower_corners
builds the box in tower-local coordinates, rotates about the local origin, then
translates by wipe_tower_x/y -- so a rotated tower's true footprint fell outside
the hull and the placement check passed on a tower that intrudes into a
carriage's reserved space.
The gap was documented in place and previously harmless, because
wipe_tower_rotation_angle was read from the project config after it had been
moved to the print preset, so the setting did nothing. Upstream repaired that
read ("Fix prime tower rotation angle setting not working"), which makes the
angle reachable and the stale hull wrong.
Rotation is applied to the hull alone. estimate_wipe_tower_polygon() still
returns an unrotated box and still leaves ArrangePolygon::rotation unset, since
the arranger consumes that field separately.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Upstream replaced MainFrame's fixed-index TabPosition enum with string-based
page ids ("feat: refactor notebook/tabs to be string based instead of fixed
index based"). The IMEX toolhead-visibility menu item was the only consumer of
that enum left on this branch, so its enable check now compares
m_tabpanel->GetSelectedPageName() against TAB_ID_PREVIEW -- the same form the
neighbouring upstream menu items use.
That mismatch is what broke CI: the branch built on its own, but the merge
commit CI builds no longer had TabPosition declared. No other conflicts.
666/666 tests pass in Release.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- parallel modes now give the printing head its second-layer temperature
transition; it previously held nozzle_temperature_initial_layer all print
- single-tool primary mode no longer marks a phantom filament slot used, which
made machine_start_gcode heat an extruder that never prints
- the IMEX mode icon's bed raycaster is re-registered after the icon is
rebuilt, fixing a use-after-free in the picking pass
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SceneRaycasterItem keeps the MeshRaycaster it was registered with as a raw
pointer, while PickingModel::reset() destroys it through a unique_ptr. Rebuilding
an icon therefore invalidates any registration still referring to it.
refresh_imex_icon(), reached only from Plater::on_config_change when is_imex or
the bed shape changes, rebuilt the IMEX mode icon without touching the
SceneRaycaster. The stale entry survived, and the next picking pass dereferenced
freed memory inside AABBMesh::intersect_ray.
Swap that one registration in place, matching how calc_vertex_for_plate_name()
handles the name-edit icon. Only the mode icon is registered for picking -- the
warning badge beside it is a plain GLModel -- so a single id is affected and the
blanket remove/re-register reload_scene() performs is not needed here.
Crashes were delayed and looked unrelated to the config change, because bed
raycasters are only tested when the camera looks down (SceneRaycaster::hit). The
reported dump landed on File > New Project, whose render ran a picking pass with
a registration that had gone stale earlier.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
get_imex_active_tools returned every physical head named by the active mode's
tool string, including the one carrying the Primary role. The pressure-advance
and nozzle-temperature sites are already gated on the mode not being primary, so
only the is_extruder_used supplement was exposed.
In primary mode that supplement treated the mode's single declared tool as a
secondary carriage and marked its filament slot used, so machine_start_gcode
emitted a heat command for an extruder that never prints. The phantom slot
appears when the mode's declared tool differs from the head the initial tool
routes to through physical_extruder_map -- on an AFC/MMU layout, printing with a
filament that lives on any head other than the declared one.
Return an empty roster for primary mode, where there are no parallel carriages by
definition. This lives in the enumerator rather than at the call site because the
mode is already resolved and normalized there, and the two guarded callers cannot
reach it in that mode, so their behaviour is unchanged.
Scope: this closes the primary-mode instance. The same phantom slot still occurs
in a parallel mode when the initial tool's head is not the mode's declared
Primary, which turns on which of the two notions of "primary" the three emission
sites should skip. That question is unresolved and deliberately left alone here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The IMEX branch of the 1st->2nd layer temperature transition is mutually
exclusive with the standard per-extruder path in its `else`, but it skipped the
head carrying the print's own toolpaths on the premise that "the standard
per-extruder temp path already addresses it". That path is the `else` branch and
never runs for a parallel mode, so the printing head received no transition at
all and held nozzle_temperature_initial_layer for the entire job.
Emit for every carriage the mode drives, the printing one included. The printing
head takes this layer's own filament; the parallel carriages, which carry no
toolpaths of their own, keep resolving through the per-plate head map with pem
inversion as the fallback. The lookup now goes through get_filament_config_index()
like the standard path, since a variant-expanded printer gives a filament its own
column and a raw index would read the wrong one.
Reproduced on a 4-carriage IQEX in copy mode: the only temperature command in the
whole file set the idle secondary carriage to the value it already had, while the
head doing the printing never left its first-layer temperature. The defect is
invisible whenever initial and regular temperatures match, which is why earlier
per-tool validation passed.
Tests cover both gantry counts, since the active set comes from the mode's tool
roster: an IDEX copy mode drives two carriages, an IQEX mode drives four, and the
IQEX case asserts a first-layer filament and a second-layer transition for each of
the four.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review follow-up. Removing pem from the ghost cache key also removed its
accidental role as the key's only printer-identity signal: the resolved
active-tools string (roster + primary) and imex_firmware_managed_zones both
shape the baked zone/ghost set but were never keyed directly, so a printer
swap between presets with matching mode names and topology could leave a
stale ghost set. Key all three in build_imex_cache_key, which also hardens
the zone cache against the same pre-existing gap.
Also from review: the tooltip swatch reuses the pem/map its label already
hoisted (one resolution, not two); the bake constructs ghosts with no color
at all, making update_imex_ghost_colors the sole color author; the headless
!m_plater guard is documented as the wxGetApp sentinel it is.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow-up to the live ghost-color fix, addressing review findings: with the
render loop restamping ghost colors every frame, the bake-time resolution in
calc_imex_ghosts was dead code (its RGB was displayed for zero frames), and
the pem + head-filament-map entries in the ghost cache key had become
color-only inputs that forced a full mesh re-bake — including a visible
hitch on every ghost-picker selection — for what is now a pure recolor.
- calc_imex_ghosts bakes an alpha-only placeholder; IMEX_GHOST_ALPHA is
hoisted to file scope as the single opacity authority (no more reading
alpha back out of the field the restamp overwrites).
- build_imex_ghost_cache_key drops pem and the head-filament map; the
forced invalidations in set/reset_imex_head_filament_map go with them.
Picker selections now recolor live with no rebuild.
- The restamp moves into PartPlate::update_imex_ghost_colors(), beside the
transform refresh, so PartPlate owns its volumes' colors and the canvas
calls one hook. Plate-level inputs (pem, override map) are hoisted once
per frame via a new get_imex_head_filament_color overload that the
single-head form delegates to, keeping tooltip parity by construction.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ghost GLVolumes baked their filament color once in calc_imex_ghosts and
only rebuilt when build_imex_ghost_cache_key changed. The key covers the
mesh-shaping inputs (mode topology, pem, object set, per-plate head map)
but not filament_colour, and no invalidation hook fires on filament
preset or color changes — so a ghost baked under a transient palette
(late-loading project colors, a subsequently edited filament color)
kept the stale color forever. A failed lookup at bake time baked
GLVolume::UNPRINTABLE_COLOR, which renders as a jet-black ghost while
the hover tooltip — which re-resolves live — shows the correct color.
Re-stamp each ghost's color in _render_imex_ghosts from
get_imex_head_filament_color, the same resolution the tooltip runs, so
the two can never disagree. Resolution is hoisted into a per-head map
so instances sharing a head resolve once per frame; the alpha baked by
calc_imex_ghosts is preserved. The render loop already stamps color per
ghost per frame (set_render_color + model.set_color), so the added cost
is a couple of map lookups. The cache key stays scoped to what it
actually protects: the expensive mesh bake and transforms.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
set_extruder skips the ooze-prevention standby cool-down when the outgoing and
incoming filament route to the same physical extruder. That is an IMEX behaviour --
an AFC/MMU lane swap keeps the same heater selected -- but the check was not gated,
so it ran on every printer.
73 shipping profiles enable ooze_prevention by default (37 Snapmaker, 21 WonderMaker,
9 Flashforge, plus Lulzbot, Prusa, re3D, iQ and the MyToolChanger), and the 150
multi-nozzle machines behind them author no physical_extruder_map. They were spared
only because every one declares a single variant per extruder, so the map came out as
the identity and nothing was ever suppressed. Correctness should not rest on that.
Gate on is_imex. Verified on one fixture with only is_imex differing, with two
filaments mapped to the same physical extruder: 0 cool-downs emitted with it on, 26
with it off.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
physical_extruder_map has one entry per logical extruder -- the index space of
nozzle_diameter -- and its consumers size their own arrays from that count. It was
being derived from printer_extruder_id, which is indexed by variant slot: one entry
per extruder+variant pair. An X1 Carbon has one nozzle and printer_extruder_id
{1,1}; an H2D 0.4 has two nozzles and {1,1,2,2,2}. The two spaces coincide only
when every extruder declares a single variant.
The visible effect was on the standby cool-down. set_extruder skips it when the
outgoing and incoming filaments share a physical extruder, and that check is not
gated on IMEX. With the map built from the wrong array, two filaments on a
dual-nozzle machine read as sharing one hotend and the cool-down was dropped --
caught by "Toolchange temperature commands are unchanged when the wipe tower wait
is off", which failed on all five CI platforms with the ;cooldown line missing.
Derive the identity over the nozzle count instead, the same fallback Plater.cpp
already applies where a profile authors no map. A profile counts as authoring one
only when its length matches the nozzle count, so the single-element PrintConfig
default is replaced rather than read as a one-extruder machine. Authored maps pass
through untouched, including the {1,0} numbering permutation the BBL dual-nozzle
profiles ship.
Tests pin the four branches and the length invariant the consumers depend on.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Labels, tooltips, menu items and dialog text mixed "IDEX/IQEX" with "IMEX"
for the same feature. IMEX is now the user-facing name throughout: IDEX and
IQEX are hardware categories, IMEX is the feature spanning them. The
hardware terms remain only where they help a user tell whether the feature
applies to their printer.
Comments and log messages keep IDEX/IQEX, where the specific carriage
topology is the more precise term.
Also corrects the imex_parallel_mode tooltip, which pointed at a
"Printer -> IDEX/IQEX tab" that does not exist; the options live under
Printer -> Multimaterial -> IMEX Configuration.
None of these strings appear in any .po or the .pot, so no translation is
affected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
IMEX placement validation only walked model instances. The prime tower is
not a ModelObject, so it could sit in a secondary zone or a carriage
collision strip and slice with no warning -- on mirror mode, a carriage
crash. Span (paired-gantry multicolor) is what made towers reachable in
parallel modes, so this is a gap in that feature, not inherited breakage.
The check now returns a cause instead of a bool so the message can name the
offender, and the tower and per-instance paths share one predicate,
imex_hull_violates_zones(), moved to libslic3r and covered by tests.
Overlap is area-based: a hull flush against a zone boundary is legal, only
a crossing violates. The tests pin that in both directions, since switching
to a touch-based test would silently block placements that work today.
The tower footprint comes from the same estimate the scene draws, so
validation matches what the user sees and drags. Three config reads there
are load-bearing in non-obvious ways and are commented at the point of use.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The merge of upstream main into the PR branch resolved the conflict in
test_gcodewriter.cpp by keeping both sides but dropping the closing brace
of the set_pressure_advance BBL SCENARIO. That nested the incoming
TEST_CASE inside it and broke the build on every target that compiles the
test suite:
test_gcodewriter.cpp:1087: error: namespaces can only be defined in
global or namespace scope
Close the SCENARIO before the TEST_CASE. Both tests are preserved and no
test logic changes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The big upstream merge replaced the old per-idle unconditional plate-selector toolbar
refresh with a dirty flag (Plater::mark_plate_toolbar_image_dirty), which the
geometry-change sites already set. PartPlate::update_slice_result_valid_state flips a
plate's slice-ready / IMEX blocked-plate ("naughty plate") state without any geometry
change and did not set the flag, so after the merge nothing repaints that plate's
thumbnail or warning badge — the old per-idle refresh used to mask it. Mark the toolbar
image dirty when the state actually changes, matching the established pattern.
Cosmetic; no effect on slicing. Surfaced by the merge review of 58b4a68a10.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolved 4 conflicts:
- PrintConfig.hpp / Preset.cpp: upstream restored calib_flowrate_topinfill_special_order
(removed in the prior merge, now re-added and re-registered); kept it alongside our
IMEX options (imex_parallel_mode, imex_head_filament_map).
- Plater.cpp: kept our <sstream> include plus upstream's <optional> and plugin includes.
- tests/fff_print/test_gcodewriter.cpp: both sides appended disjoint scenarios after a
shared base. Reconstructed as upstream's full file (base + its 3 toolchange/H2C
scenarios + the boost/filesystem include its helper needs) followed by our 10
set_pressure_advance / set_temperature scenarios. 22 scenarios total, no duplicates.
Resolved conflicts in PrintConfig.hpp and Preset.cpp. Both were adjacent to upstream's
removal of calib_flowrate_topinfill_special_order (superseded by the new
top/bottom_surface_fill_order options); took upstream's removal from the option class
and the print-options list while keeping our new IMEX options (imex_parallel_mode,
imex_head_filament_map) and the IMEX enums (ImexToolLayout, ImexVizTheme) alongside
upstream's new SurfaceFillOrder enum. The option remains in PrintConfig's legacy
ignore-set so old projects still load.
Follow-up to the cross-gantry mirror axis change, from an adversarial review of it.
GCodeViewer builds its own copy of the zone grid to place the sequential-preview
carriage markers, and it must reproduce PartPlate::calc_imex_zones exactly or the
markers drift away from the ghosts they are meant to track. It did not, in two ways,
because sizing the grid and placing a cell answer different questions:
- Sizing: calc_imex_zones counts every Copy/Mirror tool's OWN column, including the
non-representatives of an aggregated (Span) gantry -- they still donate a column.
GCodeViewer only ever saw the representative, so on an aggregated gantry it could
count fewer columns than the plate and lay its markers out against wider strips.
- Placement: calc_imex_zones PINS an aggregated cell to the primary's column, because
that row-strip spans the full bed and has no column of its own. GCodeViewer used the
representative's own column, which put the marker a strip away from the ghost
whenever the representative was not column-paired with the primary.
Track the two sets separately: grid_tool_ids sizes the grid from own columns, eff_col_of
pins only aggregated tools when placing. Out-of-grid tool indices are deliberately left
unfiltered -- calc_imex_zones drops them while calc_imex_ghosts keeps them, so no policy
here can agree with both, and a comment says so rather than pretending otherwise.
Also:
- The mirror-axis rule lived in three copies (two PartPlate lambdas plus an inline
re-derivation here). Hoist it to imex_mirror_axis_for() so the ghosts and the markers
cannot drift apart, and unit-test it, including degenerate tools_per_gantry.
- Drop imex_head_transform's mirror_axis default. A defaulted axis silently hands a
forgetful caller the X reflection, which is wrong for every cross-gantry tool and
fails silently -- exactly how a stale test kept certifying the old rule.
- Replace that stale test: it asserted a diagonal mirror "flips X only", the rule this
work overturned, and stayed green only because of the default.
- A secondary sharing the primary's gantry now takes the primary's Y box facing. The
box shows the side a tool could be hit from, and two tools on one beam can only be
hit by the same other gantry. No-op on the rear-* layouts, where the hardcoded value
already matched; on front-* layouts it pointed the box away from the only tools that
could reach it.
- Correct two comments that described the aggregated X-frame substitution as a
reflection plane. It is not one: it exists to zero gantry_offset.x, and removing it
would push aggregated ghosts a column off their strip.
Verified: 385/385 tests; CLI slice of the IMEX regression project is byte-identical to
the previous commit's G-code apart from the timestamp, confirming this is
visualization-only and cannot affect sliced output.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
catch_discover_tests registers each Catch2 test with ctest by name, and ctest then
re-invokes the binary passing that name back as a -# filter. The IMEX test names
contain em dashes (U+2014). They survive discovery, but on Windows the round trip
through the console codepage mangles them, so the filter matches nothing:
Filters: "imex_head_transform G-- copy mode is pure translation"
No test cases matched -> No tests ran -> exit != 0 -> ctest: Failed
All 85 IMEX tests were reported as failures on both Windows x64 and arm64 without a
single one of them ever executing. Linux and macOS are UTF-8 end to end and were
unaffected, which is why this went unnoticed since the names were introduced in
77c32a2e15.
These were the only non-ASCII test names in the whole tests/ tree. Renaming them to
plain hyphens costs nothing and keeps the suite portable.
Test names only -- no assertion, no logic, no comment is touched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A Mirror tool reflects across the boundary it shares with the primary's zone, and
which boundary that is depends on where the tool sits:
- Same gantry: the tools are side by side along X, so the shared boundary is
vertical and the reflection negates X. This is what single-gantry IDEX does,
and it was the only case the code modelled.
- Different gantry: the zones are stacked along Y (front strip vs rear strip),
so the shared boundary is horizontal and the reflection negates Y. The part
that comes off gantry 1 is a Y-reflection of the tool directly behind it.
imex_head_transform() hardcoded diag(-1, 1, 1) for every mirror, as its own TODO
acknowledged. Lift the axis to a caller-supplied ImexMirrorAxis; PartPlate picks it
from the tool's gantry row. Both reflections keep det = -1, so a mirrored part stays
a true mirror image rather than a 180-degree rotation, which would print the
primary's part merely turned around.
The correct axis removes two workarounds. Both ghost paths special-cased aggregated
mirrors to "drop the X reflection, translate 1:1 and bake the flip into the mesh"
because reflecting X pushed the ghost off-bed as the primary was dragged. With a Y
reflection the X translation is already zero for aggregated tools, so that falls out
for free and the special cases are deleted.
Preview markers follow the same rule, which also fixes two placement bugs:
- Mirrors reflected across a Copy tool's zone edge, falling back to the primary's
column when a row had no Copy. In iq-mirror (0:P,1:C,2:M,3:M) the front row has
no Copy, so t2 and t3 both fell back and computed the identical X — both drawn
on top of each other in t3's zone. A mirror now reflects within its own zone.
- The toolhead footprint box flipped to the far side of the nozzle for any mirror
right of the primary. That only holds for an X-axis mirror, which reverses the
carriage's orientation; a cross-gantry mirror keeps the X orientation of the
tool behind it, so its box stays on the same side.
Tests cover the cross-gantry and diagonal cases, that the axis is caller-supplied
rather than inferred from the offset vector, and that both axes are reflections
(det = -1) rather than rotations.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Print::apply() derives physical_extruder_map from printer_extruder_id and writes
it into new_full_config, but does so after m_ori_full_print_config is snapshotted.
The selector write-back path rebuilds m_full_print_config from that snapshot, so
m_full_print_config kept the unexpanded default while every subsequent apply
re-derived the expanded map. An unchanged config therefore diffed on
physical_extruder_map on every re-apply, and since that key is not handled by
invalidate_state_by_config_options() it fell through to the catch-all and
invalidated every step — forcing a full reslice on each apply.
Mirror the derived map into the snapshot so the two stay consistent.
Caught by the upstream test 'Selector write-back expands migrating filaments and
survives re-apply', which asserts a re-applied unchanged config is not
APPLY_STATUS_INVALIDATED.
Conflicts were all co-located additions rather than design collisions:
- GCode.cpp: adopt upstream's toolchange(filament_id, nozzle_id) signature and
per-variant set_config_index() while keeping the IMEX bare-T<n> suppression;
rebase the second-layer temperature loop's non-IMEX branch onto upstream's
get_filament_config_index() resolution.
- Preset.cpp / PresetBundle.cpp / PrintConfig.cpp: keep both sides' option-list
and enum-map entries.
- GLCanvas3D.cpp: upstream's printable_heights argument plus the IMEX ghost pass.
- PartPlate.cpp: keep <set> (still used).
- test_gcodewriter.cpp / test_3mf.cpp: keep both sides' test cases.
Resolves two conflicts:
- GLCanvas3D.hpp: keep both the IMEX ghost render declarations and
upstream's _render_wireframe_overlay().
- test_gcodewriter.cpp: both sides appended test cases to the same
region; keep upstream's origin/machine-limit tests alongside the
pressure-advance and temperature scenarios.
Catches the iXex/IDEX branch up to upstream main (44 commits). One
content conflict resolved:
- src/libslic3r/Print.hpp: kept upstream's default-initialized
m_origin {0,0,0} alongside our m_imex_slice_offset member.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Catches the iXex/IDEX parallel-printing branch up to upstream main
(102 commits). Two content conflicts resolved:
- src/libslic3r/Preset.cpp: s_Preset_printer_options — kept upstream's
new "use_3mf" key and our iMEX printer-capability/mode keys.
- tests/fff_print/test_gcodewriter.cpp: upstream revived the disabled
suite (#14196), dropping the obsolete [.]-tagged lift() test and its
config_lift_unlift.ini; kept their set_speed + z_hop tests and appended
our 10 per-firmware set_pressure_advance/set_temperature scenarios.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ooze prevention currently treats every toolchange as a transition between
independent hotends — the OLD filament's extruder cools to a standby
temperature (via standby_temperature_delta or the filament's
idle_temperature) before the change, and the NEW filament's extruder
ramps back up afterward. This is correct for IDEX/toolchanger setups
where the parked nozzle would drip otherwise.
For AFC/MMU lane swaps where the SAME physical extruder stays selected
(only the loaded filament changes), the cool-down → re-heat round trip
is pointless: same nozzle, same heater, just a different filament feeding
it. In-print this costs 30+ seconds per lane swap, and the AFC tip-form
sequence ends up running on a cooling extruder.
Gate the pre_toolchange call on physical_extruder_map: when both the old
and new filament index map to the same physical extruder, skip the
standby cool-down. post_toolchange is left untouched — its M109 to the
new filament's print temp is still emitted, so per-lane temperature
differences (e.g. PLA → PETG on the same AFC manifold) are still handled.
Note on pem sizing: the guard requires physical_extruder_map to be sized
to the filament count for the per-filament lookup to succeed. The option's
registered default is a single-entry [0], which is shorter than the filament
count on any multi-filament setup, so the bounds check fails and ooze runs
as before. The fix fires only on profiles that explicitly author pem to
match filament count (the AFC/MMU/toolchanger configs that actually encode
same-physical routing).
Behavior matrix:
| Config | pem | Ooze behavior |
|---|---|---|
| Single-extruder + SEMM | (any) | `init_ooze_prevention` already disables ooze. No change. |
| Single-extruder, no SEMM | (any) | Single filament, no toolchanges. N/A. |
| IDEX / Toolchanger | `[0,1,…]` per-filament | Cross-physical → ooze runs as before. |
| Vanilla AFC, SEMM=true | (any) | `init_ooze_prevention` disables ooze. No change. |
| Vanilla AFC, SEMM=false | `[0,0,…]` per-filament | Same physical → **ooze SKIPPED** (the fix). |
| Toolchanger + AFC | per-filament | AFC swaps skip, cross-physical swaps run. |
| Default pem `[0]` (1 entry) | shorter than filament count | Bounds check fails for filament index ≥ 1 → ooze runs as before. No-op for profiles that haven't authored a per-filament pem. |
| Empty pem | `[]` | Guard returns false → ooze runs as before. |
The guard depends only on physical_extruder_map; no machine-class check.
Any printer whose pem is sized to filament count and indicates multiple
logical slots routed to the same physical hotend benefits.
The firmware-managed-zones writer offset puts prim_pos and the preview
toolpaths in a frame shifted relative to the bed bounds the zone math
uses, so secondaries would have rendered off the plate. Skipping the
secondary computation when firmware-managed is on keeps the centered
preview honest.
Addresses Felix14-v2 feedback on PR #13086.
124 upstream commits including CrealityPrint integration (added include
in Plater.cpp alongside the existing IMEXHelpers include), profile fixes
and version bumps (#14084, #14085, Polymaker), CI artifact publishing,
test refactors (arachne walls test added; test_3mf/test_config/
test_gcodewriter content moved/removed upstream — IMEX test file
preserved as it is feedback-only), translations.
The firmware-managed-zones writer offset centers the slice at bed origin,
so prim_pos and the preview toolpaths sit in a shifted frame relative to
the plate-local bed bounds the secondary-marker zone math uses. Computing
secondaries against unshifted bed bounds puts them off the build plate.
Skip the secondary-marker / toolhead-box computation when the
firmware-managed-zones option is enabled. The firmware physically fans the
centered toolpath out into the zones at print time, so the honest preview
is the single centered toolpath with no secondaries.
Reported by @Felix14-v2 on PR #13086.
Secondary carriage markers now read the tool layout enum correctly,
aggregate to one active marker per gantry in Span modes, and mirror
across the bed centerline so they render on-bed.
Brings the standalone PrintApply dedup-fix commit and the prior upstream
merge into staging. Their content is already present here (the dedup fix
was re-applied during the latest upstream sync), so this only reconciles
the commit ancestry — no tree changes expected.
- Layout enum read via opt<ConfigOptionEnum<ImexToolLayout>>() did a
dynamic_cast that fails for the ConfigOptionEnumGeneric type enums load
as from presets, silently defaulting to FrontLeft. This desynced flip_y
from PartPlate::calc_imex_zones, computing secondaries against the wrong
primary zone and rendering them off the bed. Use option<>() (type-checked
static_cast), matching how PartPlate reads the same key.
- Span modes now collapse each non-primary gantry to one active marker via
group_imex_active_tools_by_gantry() (the same aggregation PartPlate uses
for zones), since only one tool prints per zone at a time. Previously every
Copy/Mirror tool got its own marker.
- Aggregated mirror markers reflect across the bed centerline rather than a
column edge; the aggregated strip spans full-X, so edge reflection would
push the marker off the bed.
Includes nozzle-diameter guards for printers without nozzle info (#13255,
now landed upstream) and manual-calibration nozzle mismatch fix (#13882).
# Conflicts:
# src/slic3r/GUI/GCodeViewer.cpp
# src/slic3r/GUI/GLCanvas3D.hpp
# src/slic3r/GUI/Tab.cpp
Upstream #13360 moved `m_ori_full_print_config = ...` and
`update_values_to_printer_extruders_for_multiple_filaments(...)` into a
new `if (!extruder_applied)` block. The 3-way merge with our IMEX
`physical_extruder_map` derivation block (sitting between the old and
new positions) silently kept both copies, running the per-filament
reshape twice and miscounting filaments for the multi-temperature
compatibility check. Removed the obsolete second copy at the old
position. Resolves `p1s_multicolor.3mf` regression test failure.
Brings in upstream sync + AFC extruder_index integration:
- physical_extruder_map auto-population from AFC lane data
- verified end-to-end against xplorer (7 lanes, pem [0,0,0,0,1,2,3])
For single-gantry IMEX setups, front/rear is meaningless — only left/right
matters. Collapse the imex_tool_layout dropdown to two items ("Left" / "Right",
mapped to front-left/front-right internally) when imex_gantry_count == 1, and
show all four corner items otherwise. Normalize stored rear-* selections to
their front-* equivalents on the transition so the displayed selection always
matches the persisted value.
Also rename the field label "Tool 0 Corner" → "Tool 0 Position" so it reads
correctly in both single- and dual-gantry contexts, with a tooltip that
explains the IDEX/IQEX distinction.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds the `imex_firmware_managed_zones` printer-config key (default off) for
IDEX/IQEX printers whose firmware applies its own copy/mirror offsets in
non-primary modes (e.g. RepRapFirmware IDEX duplication mode, Flashforge
Creator Pro 2/3 Pro). For these printers the slicer needs to emit a single
centered slice at bed origin and let the firmware fan toolheads out from there;
the previous slicer-managed iMEX rendering would draw a print at the primary
zone's world position (off-bed for the firmware-fan-out paradigm).
When the flag is on and the active mode is non-primary, the slicer subtracts
the primary zone's plate-local center from the gcode emission frame. The
writer offset is augmented but the gcode-processor offset stays at plate_origin
so the gcode-preview visualizer renders the centered slice at the bed center
rather than at the prepare-view zone placement. translate_to_print_space is
augmented too so first_layer_print_min/max placeholders (consumed by user
start_gcode like Felix's M118 header) reflect the centered frame.
Slice handoff lives in PartPlate::refresh_imex_slice_offset, called from both
update_slice_context (plate switch) and Plater::priv::update_background_process
(every-slice path — reslice() goes through here with switch_print=false so the
plate-switch hook alone wouldn't fire on mode toggle).
calc_imex_ghosts early-returns in firmware-managed mode: the existing
imex_head_transform math places ghosts at primary_zone_center + gantry_offset
(slicer-managed semantics), which renders off-bed when the toolpath is being
emitted in a centered frame. Proper firmware-managed ghost rendering (showing
where copies/mirrors will actually print after firmware fan-out) is deferred.
When the flag is off, all the new code paths reduce to no-ops byte-identical to
prior behavior. Layer 1 unit tests in test_imex_helpers cover every gating path
of compute_imex_slice_offset; full ctest suite passes (247/247).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`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>
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>
Three of the four IMEX physical-vs-logical fixes landed earlier on this
branch (fbc58d2a1d, fa048babeb, a38b95bf45, e11e7d46df) used inline
lambdas / direct loops to translate physical extruder indices to logical
filament slots. No test coverage existed for the specific composition,
even though the underlying primitives (resolve_filament_for_head,
first_filament_for_physical_head) were tested.
Pull two patterns out of Plater.cpp and GCode.cpp into IMEXHelpers as
named helpers, then unit-test them:
imex_primary_logical_from_objects(used_slots_1b, pem, primary_physical)
Walks the plate's used filament slots (1-based) and returns the
first one whose pem entry maps to the primary's physical extruder.
This is what the warning's `logical_for_primary` now delegates to —
moves the "look at object assignments, not pem first-routed default"
behavior introduced in fa048babeb out of the lambda and into a
separately-testable function.
imex_secondary_logical_slots(active_physicals, primary_physical,
plate_head_filament_map, pem)
Iterates IMEX active physicals, skips the one matching primary,
resolves each remainder via resolve_filament_for_head (per-plate
override + first-routed fallback), deduplicates, drops -1 entries.
Replaces the inline loop in GCode::_do_export's is_extruder_used
marking (a38b95bf45 + e11e7d46df).
10 new test cases in test_imex_helpers.cpp cover the cases that
correspond directly to bugs hit:
imex_primary_logical_from_objects:
- AFC primary picks the object's slot (the user's specific bug)
- Multi-color AFC primary returns first input-order match
- Direct extruder primary unambiguous (no MMU)
- No object routed to primary returns -1
- Empty inputs (no objects, empty pem)
imex_secondary_logical_slots:
- Copy mode skips primary, falls back to first-routed
- IQEX 4-mode enumerates all three secondaries
- Per-plate override wins over first-routed
- Drops unrouted physicals + deduplicates
- Only-primary-active returns empty
All [IMEX] + [Variant] + [3mf] regression: 166 assertions / 61 cases.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
a38b95bf45 translated physical -> logical for IMEX active tools but used
resolve_filament_for_head for *all* active tools including the primary.
For the primary, that falls back to first_filament_for_physical_head —
which returns the FIRST logical slot routed to the primary's physical
extruder, not the slot the user actually assigned to the printing object.
On the user's Neo XP 0.6 (pem [0,0,0,0,1,2,3,3]) printing in copy mode
with the object on slot 2 (PLA dark grey):
- tool_ordering correctly marks slot 2 (object's filament).
- The IMEX-marking loop then *also* marked slot 0 (ABS) as the primary's
"first-routed" slot — wrong: slot 0 isn't loaded, slot 2 is.
- Start-gcode template emitted EXTRUDER=260 EXTRUDER2=235 EXTRUDER4=235;
the ABS-temp emission for slot 0 was harmless noise in this macro
design but conceptually bogus.
The primary's filament is already correctly covered by
tool_ordering.all_extruders() — that lists the slots the objects on the
plate are assigned to. Skip the primary in the IMEX-marking loop using
the same pattern as the IMEX PA emission path at GCode.cpp:3265
(translate initial_extruder_id -> physical via pem, skip that physical).
Result on the user's setup post-fix:
- is_extruder_used: slot 2 (object), slot 4 (T1's filament in copy).
- Start-gcode emits EXTRUDER2=235 EXTRUDER4=235 — exactly two temps,
one per active heater, with no spurious ABS bookkeeping.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
is_extruder_used is a logical-filament-slot indexed bool array — start-gcode
templates use it as `is_extruder_used[N]` where N is a logical filament slot
(matches how the rest of the codebase consumes per-filament arrays like
filament_settings_id, nozzle_temperature_initial_layer, etc.).
`tool_ordering.all_extruders()` returns LOGICAL slots and was correctly
marking those. But the IMEX-secondary marking added in 5aa624b025 was
writing PHYSICAL extruder indices straight into the logical array, mixing
index spaces. On any printer with physical_extruder_map size > 1 (MMU/AFC),
this marks the wrong slots and misses the right ones.
Symptom on the user's Neo XP 0.6 (pem [0,0,0,0,1,2,3,3]) in copy mode
[0:P,1:C] with object on slot 2 (PLA):
- tool_ordering marks slot 2 (correct: object's filament).
- IMEX active = [0, 1] (physical T0, T1) → mistakenly marks logical
slots 0 (ABS) and 1 (ASA), neither of which is used.
- Slot 4 (PLA on physical T1, the actual filament that loads on the
secondary in copy mode) is NOT marked.
- Start-gcode template emits EXTRUDER=ABS_temp EXTRUDER1=ASA_temp
EXTRUDER2=PLA_temp; no EXTRUDER4.
- PRINT_START macro reads t4=0, skips heating extruder1 — T1 stays
cold during the print.
Translate physical → logical via resolve_filament_for_head before marking
(per-plate imex_head_filament_map override consulted, with first-routed
fallback when no override is set). This matches what the firmware actually
loads on each carriage during the parallel-mode print, and what the rest
of the IMEX hot path (PA emission, layer-change temperature) already does.
Result on the user's setup post-fix:
- is_extruder_used[2]=true (object), is_extruder_used[4]=true (T1 in copy).
- Start-gcode emits 2 temps for the actually-used filaments.
- extruder1 heats correctly to slot 4's temp before the print begins.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The pre-slice warning's primary-tool filament lookup (added in
fbc58d2a1d) used resolve_filament_for_head, which returns the FIRST
slot routed to the primary's physical extruder via pem. That's the
right rule for *secondaries* (no object owns them in copy/mirror mode;
filament comes from the per-plate imex_head_filament_map override) but
wrong for the *primary*: the primary prints the actual objects on the
plate, and the slot it uses is whatever the user assigned to those
objects — not whatever happens to be at the head of the AFC manifold.
Symptom on the user's Neo XP 0.6:
- pem = [0,0,0,0,1,2,3,3] (slots 0-3 share AFC manifold on physical T0)
- filaments: slot 0 ABS, slot 2 PLA, slots 4-6 PLA, etc.
- object assigned to slot 2 (PLA), IMEX mode "copy" (T0 primary, T1 copy)
- User picks slot 5 PLA for T1 via the IMEX ghost picker (writes a
per-plate imex_head_filament_map).
- Warning reads slot 0 (ABS) for primary because that's
first_filament_for_physical_head(pem, 0). Fires "T0 ABS vs T1 PLA
type mismatch" even though the actual print uses slot 2 (PLA) for
the primary — slicer and warning disagree.
Split the lookup:
logical_for_primary(physical_idx)
Walks plate->get_extruders(true) (1-based slots used by objects on
this plate), returns the first slot whose pem entry maps to
physical_idx. Falls back to first_filament_for_physical_head if no
object on the plate routes to this physical extruder (defensive).
logical_for_secondary(physical_idx)
Unchanged behavior: per-plate imex_head_filament_map override with
first_filament_for_physical_head fallback.
The user-facing "T%d" labels still display the physical extruder index
(carriage identity); only the filament-info lookups change.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
collect_imex_warnings parses imex_mode_active_tools to get the active tool
indices. Those are PHYSICAL extruder indices (one per carriage). It then
used those same indices to look up filament_presets[tool_idx] and
bed_temps[tool_idx] — but both arrays are indexed by LOGICAL filament slot.
For MMU/AFC layouts where multiple logical slots feed one physical
extruder (e.g. AFC manifold: 4 lanes on physical T0), the warning would
report the wrong filament: a secondary on physical T1 would be named with
filament_presets[1] (= AFC lane 2) instead of the actual filament on T1.
Symptom: in IMEX parallel mode on the user's IQEX-AFC printer (pem
[0,0,0,0,1,2,3]), the multi-extruder warning called the secondary tool
"T1" but reported the filament type for logical slot 1 (an AFC lane),
not the actual filament 4 routed to physical T1.
Translate physical → logical via effective_physical_extruder_map (with
per-plate imex_head_filament_map override) before indexing into
filament_presets and bed_temps. The displayed "T%d" still shows the
PHYSICAL extruder number — that's the carriage identity the user sees
on hardware. Only the filament-info lookup is changed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three IMEX bugs of the same shape have surfaced over the lifetime of
this feature:
- Inline pem lookup duplicated at two PA emission sites
- Pre-slice warnings indexing filament_presets by physical index
- Ghost color resolution with stale default pem handling
Each was a place where a per-filament array got indexed by what the call
site had on hand (a physical T-number) without translating through
physical_extruder_map. On non-MMU/non-AFC printers the indices coincide
and nothing breaks; on AFC layouts the slicer reads the wrong filament
preset for a carriage with no error or log line.
Add a header comment block to IMEXHelpers.hpp describing the two index
spaces, when each is used, how to translate, and a list of the bugs we
hit so future contributors can recognize the pattern.
The constant kImexPrimaryMode and the helper declarations follow this
block; readers searching for pem helpers will land on the guidance first.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Brings in the IMEX test coverage (PA per-firmware, Temperature per-firmware,
[Variant] expansion, 3MF round-trip, imex_pem_tool_for helper + tests, and
the cherry-picked variant-truncation regression test).
Resolution notes:
- Two GCode.cpp call sites for set_pressure_advance had divergent edits:
* tests/imex-coverage rewrote them to use the new imex_pem_tool_for
helper (commit c2492ccc47), eliminating the inline parallel-mode
check entirely.
* feedback replaced the literal "primary" with kImexPrimaryMode in the
same lines (commit 085f5ccec8).
Resolution: keep the helper-call form. The kImex change is moot on lines
the helper replaces, and imex_pem_tool_for in IMEXHelpers.cpp is also
updated to use kImexPrimaryMode for consistency with the rest of the
codebase.
- Test test_3mf.cpp updated for upstream's load_bbs_3mf signature change
(PR adds is_orca_3mf out-parameter between is_bbl_3mf and file_version).
All three call sites in the new IMEX 3MF round-trip tests pass &is_orca
in addition to &is_bbl.
Full regression post-merge:
libslic3r: 143 cases / 48,553 assertions (+10 cases from new tests)
fff_print: 24 cases / 245 assertions (+10 cases from new tests)
sla_print: 21 cases / 14,100 assertions
libnest2d: 14 cases / 488 assertions
slic3rutils: 3 cases / 3 assertions
All tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Follow-up to 085f5ccec8. Self-review of that commit's grep output missed
GCodeViewer.cpp. Three sites in the layer-preview multi-carriage marker
logic still compared against the bare "primary" literal:
- GCodeViewer.cpp:1538 — process-preset mode default
- GCodeViewer.cpp:1542 — per-plate mode override gate
- GCodeViewer.cpp:1551 — secondary marker computation gate
All three now use kImexPrimaryMode. The file already includes
IMEXHelpers.hpp (line 13) so no new include needed.
Verified by grepping the full IMEX-touching set: only IMEXHelpers.hpp
itself (the constant definition) still references the literal string,
which is correct.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The IMEX zone cache key drives ghost rebuild and zone-overlay
invalidation. Adding a printer config option that affects zone geometry,
ghost transforms, or collision strips without extending the key produces
a silent staleness bug: the cache thinks the zones are still valid and
ghost meshes / overlays don't refresh after the new option changes.
Document what currently feeds the key and pin the precision convention
(*10 scale on float values for 0.1 mm resolution) so future contributors
know the contract and where to extend it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
GCode.cpp's static get_imex_active_tools() inlined its own "phys[:role]"
tokenizer with subtly different semantics from IMEXHelpers'
parse_imex_active_tools — only the GCode version bounded against
MAXIMUM_EXTRUDER_NUMBER. Three other call sites (PartPlate zones,
GCodeViewer legend, Plater warnings) routed through parse_imex_active_tools
already.
Move the bounds check into parse_imex_active_tools so all consumers get
it consistently, then rewrite get_imex_active_tools to do only the
Print-extraction portion (active mode lookup, tools-string fetch) and
delegate token parsing to the helper. Keeps Print out of IMEXHelpers'
include set.
No behavior change for the non-pathological case (mode strings have always
parsed identically); for indices >= MAXIMUM_EXTRUDER_NUMBER (64) the three
older call sites silently filter them out now where previously they would
have accepted them — this matches what get_imex_active_tools already did.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Across 6 files the literal "primary" string was the sentinel for "no IMEX
parallel mode active" — used for short-circuiting in serialization, ghost
visualization, zone calc, popup-menu list construction, the IMEXModesCtrl
non-deletable first row, and several layer-of-export checks. A typo in
any one would silently bypass the guard.
Define kImexPrimaryMode in IMEXHelpers.hpp with a docstring describing
what equality with it means semantically, and route every call site
through it. No behavior change.
Touched: bbs_3mf.cpp, GCode.cpp, PartPlate.cpp, Plater.cpp, Tab.cpp.
The bbs_3mf and Tab files now include IMEXHelpers.hpp; the other three
already did.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Four quick-fix items surfaced by pre-PR self-review.
GCodeViewer.cpp:1607
Null-check get_curr_plate() before dereferencing. Other call sites in
the file already guard; this was the only unguarded one in the IMEX
layer-preview path. In practice m_plate_list always has a plate, but
the inconsistency is easy to fix and removes the only ungated deref.
PartPlate.cpp:build_imex_cache_key
Cache key for IMEX zone geometry truncated nozzle_clearance_x/y to int
before stringifying — a config change from 30.0 to 30.5 would not
invalidate the cache. Match the *10 precision pattern already used for
imex_carriage_margin so 0.1 mm steps invalidate correctly.
Plater.cpp:select_plate_by_hover_id (right-click popup)
Two issues:
1. Lambda captured `modes` by reference. PopupMenu() is synchronous
today so the reference outlived the menu's event handling, but the
pattern is fragile — anyone refactoring to async Popup() would
silently dangle. Capture by value.
2. Used wxID_HIGHEST + i for menu item IDs — standard wx anti-pattern
because it can collide with other handlers listening in that range.
Allocate per-item IDs via wxNewId() and look up the chosen mode by
finding the event ID in a parallel vector. The lookup becomes O(N)
instead of O(1) but N is small (mode count) and this is clicker
latency, not a hot path.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
af59501f4a ("feat: firmware-agnostic per-tool PA emission for IMEX
parallel modes") inadvertently deleted the BBL-specific PA emission for
initial_non_support_extruder_id while adding IMEX per-tool PA support.
That deletion was scope creep into core BBL functionality and not part
of the IMEX feature.
Restore the original block verbatim. The new IMEX-parallel-modes PA
emission (per-secondary-tool, gated on m_imex_parallel_mode) is left
untouched — that's legitimately IMEX scope. BBL printers in non-IMEX
mode now get back the pre-PR initial-PA behavior.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Self-review found two weaknesses in the preceding test commits:
1) The equal-size [Variant] scenario claimed to distinguish the truncation
guard's `cur > target` predicate from a regression to `cur >= target`,
but both paths yield identical child values in practice: when
extruder_variant names match, set_with_restore's variant_index is fully
populated (no -1 slots) and the merge path restores every position from
backup — producing the same {1.5, 2.5} output as the skip path. The
test passes in both guard states.
Rewritten to use mismatched variant names between child and parent.
variant_index then has -1 slots, and set_with_restore overwrites those
positions with parent values. Now the merge path yields {0.8, 0.8} and
the skip path yields {1.5, 2.5} — observably different. Verified:
- `cur > target` (correct): 4 scenarios pass, 15 assertions
- `cur >= target` (regressed): equal-size scenario fails with
"1.5 is within 0.000000001 of 0.80000000000000004"
- Guard removed entirely: child>parent + stride=2 both fail with
truncation ("1 == 2" / "2 == 4")
2) The [3mf][IMEX] round-trip only covered a single plate. A plate-
indexing regression (IMEX metadata landing on the wrong plate, or
bleeding across plates on reload) would not have been caught.
Added a multi-plate scenario: two plates with distinct mode and
head-filament-map values. Asserts both land on their respective
destination plates after reload. Load-bearing verified:
- With IMEX serialization intact: 3 scenarios pass, 45 assertions
- With IMEX serialization disabled: positive + multi-plate fail
(both "nullptr != nullptr"); primary-mode passes (expects nullptr)
- With primary-mode short-circuit removed: primary-mode scenario
fails ("0x... == nullptr") because primary modes now serialize
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Four scenarios cover the temperature emission surface that IMEX layer-change
handling routes through (Tier 1 of the deferred Target B test plan —
pure-function-only, no fixture).
- Per-flavor command routing: Marlin (M104), RRF (G10 — M104 is deprecated
on RRF), Mach3/Machinekit (P-prefix for value instead of S).
- Wait handling: Marlin emits M109, MakerWare/Sailfish silently drop wait
requests (the firmware doesn't support blocking waits), Teacup and RRF
both emit a separate M116 poll.
- Per-tool qualifier for IMEX secondary carriages: Marlin and Klipper
emit T<N>, RRF uses P<N> (same P override as its wait poll). This is
exactly the path that lets IMEX set secondary-tool layer temperatures
without a tool-change.
- Instance overload's multi-extruder gating: a tool index passed to a
single-extruder GCodeWriter is discarded (no spurious T0 on
single-tool printers), but a multiple_extruders writer passes it
through verbatim.
All 24 assertions in 4 cases pass under [Temperature].
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The physical_extruder_map translation used by IMEX per-tool PA emission was
inlined identically at two sites in GCode.cpp (tool-change and second-layer
transition). Extract to IMEXHelpers so the routing rule ("parallel mode AND
populated pem → physical index, else -1") is testable in isolation and the
call sites read as intent rather than re-deriving the conditional.
Production change is behavior-preserving:
- Same predicate (`!mode.empty() && mode != "primary"`)
- Same empty-pem short-circuit returning -1
- Same get_at() dispatch on hit
- Both call sites replaced with a single call
Four unit tests in [IMEX] cover the routing matrix:
- non-IMEX ("") and primary mode short-circuit
- parallel mode + empty pem short-circuits (defense-in-depth; get_at would
throw on empty values otherwise)
- identity pem (non-MMU IDEX) routes filament to itself
- MMU collapse routes multiple logical slots to one physical (7-slot profile
with 4-lane MMU on physical 0 and direct drives on 1/2/3)
All IMEX + Variant regression suites pass post-refactor (133 assertions / 48
cases under libslic3r, 25 assertions / 6 cases under fff_print [PressureAdvance]).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Validates that imex_parallel_mode and imex_head_filament_map survive a
full store_bbs_3mf → load_bbs_3mf cycle — the same silent-state-loss bug
class that produced the variant-vector truncation regression, applied to
IMEX plate state which rides the same XML metadata path.
- Positive round-trip: a plate with copy_mode + a non-trivial head
filament map ("1:2,2:3") is saved and reloaded; both options land on
the destination plate's config with the exact values preserved.
- Guard scope: a plate with mode="primary" and empty head-filament-map
does NOT emit metadata (per the serializer's short-circuit), and the
reload leaves both options absent from the destination config. If the
serializer ever regressed to writing primary-mode plates, the load
path would surface phantom "primary" strings on plates that shipped
clean — this catches that.
Both scenarios call set_temporary_dir to point the BBS exporter's backup
scaffolding at a writable per-process temp directory (by default it
resolves under root at runtime, which fails for non-root test
processes).
All 27 assertions in 2 test cases pass under [3mf][IMEX].
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Exercises the IMEX per-tool PA emission surface added in af59501f4a
("feat: firmware-agnostic per-tool PA emission for IMEX parallel modes").
Six scenarios cover the full routing matrix:
- Negative PA returns empty across all flavors (early-exit guard).
- Klipper: bare vs EXTRUDER=extruder vs EXTRUDER=extruderN. Asserts the
tool=0 case emits the unsuffixed extruder name (first Klipper extruder
is named "extruder", not "extruder0") — a subtle edge case easy to
regress.
- RRF: bare vs D0 vs DN. The D0 case matters: passing tool=0 explicitly
must emit `D0`, not the current-tool fallback.
- Marlin 2.x: bare vs T0 vs TN.
- Marlin Legacy: tool index is silently dropped — verifies the fallback
branch can't accidentally start emitting T qualifiers on firmware that
doesn't support them.
- BBL: flag wins over firmware flavor (Marlin 2 flavor + BBL flag emits
the BBL-specific `M900 K... L1000 M10`) and BBL never emits a per-tool
qualifier regardless of the tool argument.
All 25 assertions across 6 cases pass under [PressureAdvance].
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds three scenarios alongside the existing child>parent stride=1 regression
test for update_non_diff_values_to_base_config:
- stride=2 child>parent: machine_max_acceleration_x (size 4 vs 2) — confirms
the truncation guard fires for the (normal,silent)-pair stride=2 path, not
just stride=1. Catches a regression class the existing test would miss
because stride=2 routes through normalize_stride2_floats and a different
set_with_restore call site.
- equal-size (2=2): exercises the path the guard does NOT short-circuit;
asserts child per-extruder values survive set_with_restore's nil-restore
merge. Catches any future change that breaks the equal-size merge — the
fix's `cur > target ? skip` predicate could regress to `cur >= target` and
silently override child values otherwise.
- non-variant scalar: layer_height in `keys` and `different_keys` but absent
from printer_options_with_variant_1/_2. Hits the is_scalar() / "nothing to
do" branch and must remain untouched. Scopes the guard's blast radius.
All four scenarios in the [Variant] tag pass: 15 assertions, 4 test cases.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a Catch2 scenario that builds a 2-extruder child DynamicPrintConfig
inheriting from a 1-extruder parent, calls update_non_diff_values_to_base_config
through the real printer_options_with_variant_1 / _2 key sets, and asserts
that printer_extruder_id, printer_extruder_variant, and retraction_length
retain their full size after the merge. Covers three distinct
set_with_restore<T> instantiations (Ints, Strings, Floats) and verifies
both size preservation and per-extruder value preservation.
Verified load-bearing: with the guard in update_non_diff_values_to_base_config
temporarily removed, the test fails with "1 == 2" on pe_id.values.size() and
retraction_length.values.size(); with the guard restored, all six assertions
pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
update_non_diff_values_to_base_config sizes variant_index to the parent's
(inherits-target's) extruder count, and set_with_restore then replaces
the child's vector with a parent-sized one. When the child preset has
more extruders than the parent (e.g. an IDEX preset inheriting from a
single-nozzle base), every key in printer_options_with_variant_1 is
truncated to the parent's size on project reload, destroying per-extruder
data.
Observable symptoms: objects render with the wrong color (often black)
after reopening the project, and the printer preset shows a permanent
dirty-asterisk that no save/reload cycle can clear.
The child's saved value is authoritative for its own extruder count, so
skip the parent-shaped merge for the variant-keyed branch when cur >
target. Confirmed by loading a 2-extruder IDEX preset inheriting from a
single-nozzle base: all 24 variant-keyed options previously truncated
from child_size=2 -> 1 are now preserved at size 2.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
_picking_pass_imex_ghosts is what resets m_hover_ghost_head, but _picking_pass
early-returns (mouse drag, mouse off-canvas, gizmo drag) skip that reset. If
the user switches from an IMEX printer to a non-IMEX one during such a window
the plate clears its ghost volumes while the stale head index survives,
producing an orphan tooltip anchored to nothing.
Validate the hover state against live ghost volumes before rendering the
tooltip and self-heal the indices when they no longer point at anything.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace imex_head_transform's fifth argument (Vec3d primary_origin) with a
Vec2d primary_zone_center and rewrite the Mirror branch as a true reflection
about the plane x = primary_zone_center.x + gantry_offset.x/2. Previous math
flipped about the primary's current origin, which:
* let the ghost drift out of the target zone as the primary moved, and
* made mirrored drag motion track 1:1 with the primary instead of reflecting.
The new transform places the ghost at the mirrored position within the target
zone (matching where the mirror tool actually prints) and reflects drag so
primary +X → ghost -X while Y tracks 1:1 — i.e. the ghost stays a true
mirror while the user drags. Off-row Mirror targets (e.g. T3 on a 2x2) still
reflect across the same X-plane as on-row peers.
PartPlate::calc_imex_ghosts and update_imex_ghost_transforms now feed primary_off
(the primary head's zone center) instead of an instance-space Vec3d.
Mirror tests rewritten against the new geometric contract: ghost origin at the
reflected position, primary drag deltas reflected across the zone-boundary plane.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Centralize the project→printer→printer_extruder_id fallback for the physical
extruder map. PrintApply, PartPlate (ghost color + cache key), Plater (tooltip
+ click gate) all previously open-coded the three-step lookup, and each handled
the "pem unset, derive from pei" case slightly differently — an IDEX printer
without an explicit pem could paint an UNPRINTABLE_COLOR ghost even though the
slicer would have derived a valid mapping.
- IMEXHelpers: add effective_physical_extruder_map(explicit_pem, pei) and a
PresetBundle overload that wraps the project→printer precedence.
- PrintApply: use the helper in place of the inline pei→pem normalization.
- PartPlate / Plater: call the PresetBundle overload at every ghost-color,
ghost-cache-key, tooltip, and click-gate site.
- Plater::format_imex_ghost_tooltip: when no filament resolves to a head,
surface an actionable message directing the user to extend the extruder
count in the Machine tab, instead of the generic "(no filament routed)".
- IMEXFilamentPickerPopover: hold m_pem by value so callers can pass a
stack-local derived pem without lifetime worries.
- Tests: 5 new cases covering explicit-wins, default-pem fallback, null
inputs, and the IDEX ghost-color regression that motivated this.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PartPlate::calc_imex_zones, GCodeViewer::render, and Plater::collect_imex_warnings
each hand-rolled their own "phys:P/C/M" tokenizer with subtly different error
handling. Replace the three inline loops with parse_imex_active_tools +
imex_primary_tool_for_mode so the Primary/Copy/Mirror classification agrees
across zones, the G-code viewer legend, and slice warnings.
No behavior change: the shared helpers preserve the 1=Primary / 2=Copy /
3=Mirror encoding already consumed downstream and continue to accept the
legacy bare-index form as Primary.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the plater-icon popover with colored transparent ghost copies
of primary-head instances on the plate, one per secondary active head
under its Copy/Mirror role transform. Left-click on a ghost opens a
compact filament picker popover for the ghost's head (MMU lane override).
Ghosts track the primary through drag/rotate/scale/mirror and invalidate
on mode or pem changes.
Key pieces:
IMEXHelpers -- imex_head_transform (Primary/Copy/Mirror), shared role
parser, per-head filament resolution with X-axis Mirror anchor.
PartPlate -- ghost state, volume rebuild on mode/map/object mutation,
primary_origin plumbed for Mirror reflection across the primary-row
gantry plane.
GLCanvas3D -- ghost rendering with per-head filament color and
translucent blending; picking routed via volume composite id.
Plater -- ghost click + tooltip; plater icon left-click always cycles.
IMEXFilamentPickerPopover -- BitmapComboBox row for one secondary head,
writes imex_head_filament_map on selection.
bbs_3mf -- round-trip the per-plate imex_head_filament_map option.
PrintConfig -- add imex_head_filament_map as a plate option.
MMU/AFC routing for parallel modes relies on the printer profile's
physical_extruder_map (see prior commit for authoring format). Primary-
row heads and their per-plate filament overrides are resolved through
that map, so PA and temperature emission address the correct physical
extruder when multiple logical slots share one carriage.
Tests: IMEXHelpers coverage for Primary/Copy/Mirror transforms
including a 2x2 off-row regression guard for the X-axis reflection fix.
Auto-populate physical_extruder_map (0-indexed) from printer_extruder_id
(1-indexed) in Print::apply(). The guard only runs when the map is still
at default size (<= 1 element), so printer profiles that set it explicitly
are untouched.
All IMEX parallel-mode PA and temperature emission now routes tool slot
indices through physical_extruder_map before constructing firmware
extruder qualifiers (EXTRUDER=, M104 T, M572 D). This ensures AFC/MMU
setups where multiple slots share one physical extruder get the correct
qualifier -- e.g. T6 on physical extruder 3 emits EXTRUDER=extruder3
instead of EXTRUDER=extruder6.
Profile authoring for MMU/AFC printers:
Add physical_extruder_map to the printer profile JSON as a 0-indexed
string array, one entry per logical filament slot, whose value is the
physical extruder carrying that slot. The array size must be > 1 for
the explicit map to override the auto-derive. Example for a 7-slot
printer with a 4-lane MMU on extruder 0 and three independent direct
drives on extruders 1/2/3:
"physical_extruder_map": ["0","0","0","0","1","2","3"]
Non-MMU printers need no action -- printer_extruder_id already encodes
the 1:1 mapping and the auto-derive handles it.
Deeper integration (zone validation, collision detection, filament
assignment grouping, Moonraker agent auto-population) is deferred.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The "Pre-slice warnings" line in the IDEX/IQEX Configuration section
was a widget-only line (no options) with full_width left at the default
of 0. activate_line() only skips the option_set.front() call when
full_width=1; without it, the code falls through to:
bool is_legend_line = option_set.front().opt.gui_type == ...
Calling front() on an empty std::vector is undefined behavior. On
Windows/MSVC release builds this dereferences a null pointer and reads
at offset 0x30 (where ConfigOptionDef::gui_type lands), producing an
ACCESS_VIOLATION at 0x30. On Linux/GCC the same UB happens to be
harmless, so the crash is Windows-only and cannot be reproduced on
Linux.
Fix: set line.full_width = 1, matching the pattern used by the "Modes"
(IMEXModesCtrl) line. This takes the early-return widget path in both
append_line and activate_line, bypassing option_set.front() entirely.
Reported by tester: crash on clicking Multimaterial tab with any
printer (K3D VOSTOK confirmed), build af59501f.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends set_pressure_advance() with an optional tool index (default -1,
preserving existing behavior for all non-IMEX call sites). Per-firmware:
- Klipper: EXTRUDER=extruder[N] when tool >= 0, bare command otherwise
- RRF: M572 D<N> when tool >= 0, bare M572 otherwise (no D0 fallback)
- Marlin 2: M900 K<X> T<N> when tool >= 0, bare M900 otherwise
- Marlin Legacy / fallback: M900 K<X> always
Adds m_imex_parallel_mode to GCode, set once per export from the active
plate mode. PA and layer-transition temperature tool-qualification are
gated on this being a non-primary parallel mode — primary mode prints
use regular tool-change PA exactly like any non-IMEX printer. Secondary
active tools in parallel modes receive explicit per-tool PA at print
start since they never go through a tool-change sequence.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces MessageDialog with RichMessageDialog to show a suppress
checkbox on both slice-plate and slice-all warning paths. Persists
the choice to app_config as imex_pre_slice_warnings=false. Adds a
re-enable toggle in Printer Settings → Multimaterial → IDEX/IQEX
Configuration so the warnings can be restored if suppressed accidentally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
update_visibility() and render() both called front() on an empty vector
when a CtrlLine had no options (pure widget lines). Added an early-return
path in update_visibility() and a null-guard in render().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Temperature emission (GCode.cpp, PrintConfig.cpp, ConfigManipulation.cpp):
- Set temperatures for all active tools in layer_change_gcode for iMEX
parallel modes (primary + secondary carriages)
- Fix filament temperature commands so Layer 1 temperatures only emit on
the first layer; subsequent layers use normal layer-change temperatures
- Remove stray temperature commands that fired outside the intended context
- Consolidate iMEX temperature handling into the second-layer transition;
clean up emission logic and naming throughout
Pre-slice warning dialogs (PartPlate.cpp/hpp, Plater.cpp):
- Collect per-plate IMEX warnings before slicing: multi-material conflict,
bed temperature mismatch between carriages (>5 °C delta), and filament
type incompatibility
- Show a dismissible Yes/No dialog from both "Slice Plate" and "Slice All"
actions; No returns to 3D view, Yes proceeds to slice
- Refresh plate thumbnails after the panel switch so previously-generated
thumbnails are not left black
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In iMEX (IDEX/IQEX) parallel printing modes (copy/mirror), only the
primary extruder generates toolpaths. The firmware duplicates the
primary's movements for secondary carriages, so they don't appear in
tool_ordering.all_extruders().
This caused is_extruder_used[N] to return false for secondary tools
even though they're physically active and moving.
The fix adds logic to parse the active mode's tool assignments from
imex_mode_active_tools config and marks all assigned tools as used.
Changes:
- Added null checks and bounds validation for config options
- Skip empty tool strings to avoid unnecessary parsing
- Reordered bounds checks for defensive programming
- Added clarifying comments for exception handling
This ensures is_extruder_used[N] is true for all tools in a parallel
mode, allowing printer profiles to correctly enable heaters and
emit cleanup G-code for all active carriages.
Fixes: is_extruder_used[1] returns false in copy/mirror modes (#13086)
Related: Comment [28]/[30], Comment [18] (is_extruder_used in G-code header)
- Add warning badge (obj_warning.svg overlay) to the iMEX plate icon when
a parallel mode is active alongside multi-material objects on the same plate
- Add has_imex_multimaterial_conflict() using get_extruders(true) so only
filaments actually used on the plate are checked
- Move multi-material caution dialog from reslice() into on_action_slice_plate /
on_action_slice_all so it fires exactly once per user action and does not
disrupt GL thumbnail generation during Slice All
- Fix is_imex missing from p->config init key list so on_config_change()
diff detection correctly triggers refresh_imex_icons()
- Defer imex_changed handling until after set_bed_shape() so m_shape is current
- Replace plain remove button with ScalableButton (imex_remove.svg) in mode rows
- Add EditGCodeDialog launch button per mode row for placeholder browsing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Register imex_mode (string), imex_mode_index (int), and imex_mode_gcode
(string) in OtherSlicingStatesConfigDef so they appear in the placeholder
search UI under Slicing State.
Set all three via placeholder_parser().set() before any script processing
in _do_export(). Process imex_mode_gcode first so {global} declarations
defined there flow forward into machine_start_gcode.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
IMEXModesCtrl:
- Primary mode is now a non-deletable first row stored as sentinel
"primary" in imex_mode_names; older configs load cleanly
- New rows default T0 → Primary when no tool assignment is stored
- Filter "primary" from plater popup/cycle list to prevent double entry
- imex_tools_per_gantry cap raised 2 → 4
Zone sizing:
- Zone width/height now based on active tool count only; inactive tools
donate their bed share to active neighbors (fixes 4-tool layout)
- Active col/row maps (col_to_zone/row_to_zone) applied consistently
across zone fills, collision strips, and primary zone box
GCodeViewer animation:
- Mirror position formula fixed: left-of-copy reflects across copy
zone's left edge; right-of-copy reflects across right edge
(T3 was rendering on top of T1)
- strip_width/row_strip_height use active counts, matching PartPlate
Stability:
- Early return in calc_imex_zones() when tool_states is empty; prevents
OOB crash on new printer with stale process-preset mode name
- is_imex toggle in on_config_change calls refresh_imex_icons() so the
plate mode icon appears without requiring a new project
GCode:
- Remove "primary" guard so Primary mode gcode field is emitted at
start of print
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add all_tool_states map to IXexModesCtrl Row, storing all tool
assignments including those not currently visible due to grid size
- active_tools_string() now serializes from all_tool_states so
off-screen assignments survive the round trip through a smaller grid
- Button clicks keep all_tool_states in sync with visible btn_states
- Display anchors to the gantry row containing the Primary assignment
so reducing gantry count keeps the meaningful row visible rather
than always defaulting to row 0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add per-plate iXex mode icon to the plate toolbar (normal, hover, dark, dark-hover SVG variants)
- Left-click cycles through available modes; right-click shows a popup menu with all modes as radio items
- Mode changes are recorded in the undo/redo snapshot system
- Fix double context menu: suppress EVT_GLCANVAS_PLATE_RIGHT_CLICK and EVT_GLCANVAS_RIGHT_CLICK when the iXex icon popup was already shown
- Remove ixex_parallel_mode combo from Print Settings > Other > Special mode (superseded by per-plate icon)
- Remove dead code: refresh_ixex_mode_combo(), m_ixex_mode_combo member, related Tab reload hook
- iXex mode persisted in 3MF project files via existing plate metadata serialization
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add per-plate iXex mode icon to the plate toolbar (normal, hover, dark, dark-hover SVG variants)
- Left-click cycles through available modes; right-click shows a popup menu with all modes as radio items
- Mode changes are recorded in the undo/redo snapshot system
- Fix double context menu: suppress EVT_GLCANVAS_PLATE_RIGHT_CLICK and EVT_GLCANVAS_RIGHT_CLICK when the iXex icon popup was already shown
- Remove ixex_parallel_mode combo from Print Settings > Other > Special mode (superseded by per-plate icon)
- Remove dead code: refresh_ixex_mode_combo(), m_ixex_mode_combo member, related Tab reload hook
- iXex mode persisted in 3MF project files via existing plate metadata serialization
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Includes two feature sets on top of the base iXex implementation:
Auto-arrange iXex zone constraint:
Constrains placement to the primary zone when a parallel mode is active.
Collision strips registered as hard obstacles via m_unselected so the
NFP placer correctly excludes them.
Nozzle clearance rename + carriage box visualization overhaul:
ixex_carriage_width_x/y → ixex_nozzle_clearance_x/y (breaking rename).
Collision strip width now uses the literal clearance value, not half.
Carriage footprint boxes place the nozzle at the correct physical edge
for all printer types (IDEX, IQEX 2x2) and modes (copy, mirror).
Fixed GLModel::reset() bug causing stale mesh on config change.
Config key rename (breaking for saved profiles — call out in PR):
ixex_carriage_width_x/y → ixex_nozzle_clearance_x/y
Labels updated to "Nozzle Clearance X/Y" with consistent tooltips
describing the measurement as nozzle-to-collision-side-edge distance.
Strip math fix:
Previously halved the clearance value (× 0.5) under the assumption
the nozzle was centered in the carriage. The measurement is now the
literal nozzle-to-edge distance, so the × 0.5 factor is removed.
The collision strip width now equals the configured value directly.
Carriage box visualization (GCodeViewer):
- Add per-carriage box_offset_x/y so the nozzle marker sits at the
physically correct edge of the footprint box rather than centered.
- X: zone-based by default (nozzle at inner edge facing bed center).
Copy secondaries inherit the primary's X orientation (same movement
direction). Mirror secondaries use the collision-side edge.
- Y: always row-based regardless of copy/mirror mode. Gantry is always
behind the nozzle (high-Y); front-row primaries with a back-row
secondary override to place nozzle at the low-Y edge.
- Fix stale mesh bug: GLModel::init_from() is a no-op when already
initialized. Call reset() before init_from() so mesh rebuilds
correctly when nozzle clearance values change in config.
- Remove m_ixex_toolhead_box_dims (was the now-unnecessary cache key).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
params.excluded_regions flows into libnest2d PlacementConfig.m_excluded_regions
which is declared but never consumed by the NFP placer — the field is dead code.
m_unselected is the working path: it becomes fixeditems that are preloaded as
physical fixed obstacles in the NFP computation, same as the wipe tower.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Auto-arrange now injects the carriage collision zones as hard excluded
regions (is_virt_object) when an iXex parallel mode is active, using the
same BoundingBoxf3 data already stored for rendering and violation checks.
Objects will no longer be placed in the mirror-edge danger strips.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds std::optional<BoundingBoxf> m_ixex_primary_zone_box to PartPlate,
populated by calc_ixex_zones() alongside the existing secondary/collision
zone geometry. The new ixex_primary_zone() getter calls ensure_ixex_zones()
so callers always get fresh data. ArrangeJob::process() replaces the
full-bed bedpts with the primary zone corners when the getter returns a
value, so auto-arrange no longer drops objects into the bed center.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The thin rectangular outlines around copy/mirror zones caused visible
aliasing and appeared to flash during interaction. Removed the border
GLModel, its build code in calc_ixex_zones(), the render block, and
the unused border field from IXexTheme.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Same class of bug as the TabPrinter fix: clear_pages() destroys all
page widgets but didn't null m_ixex_mode_combo, causing a SIGSEGV in
refresh_ixex_mode_combo() when load_current_preset() ran after a
page rebuild (e.g. triggered by Plater::reset()).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Arrays cannot be captured by value in C++ lambdas; replaced with a
static local declared inside the event handler.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces first-class parallel printing (copy/mirror modes) for printers
with multiple independent X-axis carriages. Branded iXex (independent X
extruder), targeting Klipper firmware with a firmware-agnostic design.
- PrintConfig: new printer options declaring iXex capability and geometry
(is_ixex, ixex_gantry_count, ixex_tools_per_gantry, carriage dims,
tool layout, and per-mode name/role/gcode arrays)
- Preset: iXex keys registered in printer and process preset option lists
- Tab: IXexModesCtrl visual grid editor in Printer preset tab; mode
dropdown in Process → Others tab; clear_pages() nulls iXex pointers to
prevent dangling-pointer crash on preset save
- PartPlate: 2D zone visualization (active/dimmed/dividers) and
placement-violation detection (has_ixex_placement_violations) that
blocks slicing when objects fall outside the primary zone
- Plater: violation detection wired into update_background_process so
the Slice button is disabled with an error notification on violation
- GCode: mode-activation G-code injected before machine_start_gcode
- GCodeViewer: multi-carriage toolhead markers in sequential preview,
filament legend annotated with active carriage count and mode name
2026-04-06 14:57:39 -04:00
161 changed files with 18461 additions and 645 deletions
Slice for belt / conveyor (infinite-Z) printers, with belt-aware supports and a tilted-bed preview. Contributed by [Joseph Robertson (@HarrierPigeon)](https://github.com/HarrierPigeon).
Print copies or mirror images of a part on every carriage of an IDEX or IQEX printer at once, with the mode chosen per plate and nozzle clearance zones shown on the bed. Contributed by [Clifford (@cgarwood82)](https://github.com/cgarwood82).
- Additional features can be found in the [change notes](https://github.com/OrcaSlicer/OrcaSlicer/releases/).
d="M 22.60772,9.2135632 H 19.221048 V 5.4247238 h -3.831173 v 3.7888394 h -2.582337 v 0.1693336 l 1.058335,2.7516712 h 1.524002 v 8.699514 h 3.831173 v -8.699514 h 3.386672 z m 1.227659,1.1006688 v 6.096009 c 0,2.878672 1.375835,4.423841 4.762507,4.423841 h 8.805348 c 3.407838,0 4.762507,-1.545169 4.762507,-4.423841 v -6.096009 c 0,-3.0903387 -1.354669,-4.8895082 -4.762507,-4.8895082 h -8.805348 c -3.386672,0 -4.762507,1.7991695 -4.762507,4.8895082 z m 4.191006,0.656167 c 0,-1.2700017 0.592668,-1.820336 1.566336,-1.820336 h 6.582844 c 1.227669,0 1.799169,0.6138344 1.799169,1.98967 v 3.915839 c 0,1.333503 -0.5715,1.905003 -1.799169,1.905003 h -6.582844 c -1.227668,0 -1.566336,-0.719667 -1.566336,-2.159003 z"
d="m 14.890625,4.9238281 v 3.7890625 h -2.583984 v 0.7636719 l 1.216797,3.1582035 h 1.367187 v 8.699218 h 4.830078 v -8.699218 h 3.386719 V 8.7128906 H 19.720703 V 4.9238281 Z m 13.707031,0 c -1.781877,0 -3.138417,0.4891086 -4.019531,1.4492188 -0.881114,0.9601101 -1.242188,2.322036 -1.242188,3.9414061 v 6.095703 c 0,1.518993 0.370925,2.78417 1.261719,3.650391 0.890794,0.86622 2.230623,1.273437 4,1.273437 h 8.804688 c 1.779488,0 3.121173,-0.408321 4.009765,-1.27539 0.888593,-0.86707 1.253907,-2.13067 1.253907,-3.648438 v -6.095703 c 0,-1.6182286 -0.355554,-2.9804801 -1.234375,-3.9414061 -0.878822,-0.960926 -2.237387,-1.4492188 -4.029297,-1.4492188 z m -12.707031,1 h 2.830078 v 3.7890625 h 3.386719 v 1.9218754 h -3.386719 v 8.699218 H 15.890625 V 11.634766 H 14.208984 L 13.46875,9.7128906 h 2.421875 z m 12.707031,0 h 8.804688 c 1.615927,0 2.640543,0.4137566 3.291015,1.125 0.650473,0.7112434 0.972657,1.793515 0.972657,3.2656249 v 6.095703 c 0,1.360905 -0.312424,2.308411 -0.953125,2.933594 -0.640702,0.625183 -1.682198,0.990234 -3.310547,0.990234 h -8.804688 c -1.617294,0 -2.658943,-0.364202 -3.302734,-0.990234 -0.643792,-0.626032 -0.958984,-1.573915 -0.958985,-2.933594 v -6.095703 c 0,-1.4709684 0.325044,-2.5535656 0.978516,-3.2656249 0.653472,-0.7120593 1.678409,-1.125 3.283203,-1.125 z m 0.994141,2.7265625 c -0.581037,0 -1.137457,0.1802682 -1.517578,0.5976563 -0.380122,0.4173881 -0.546875,1.0098771 -0.546875,1.7226561 v 3.830078 c 0,0.758884 0.06928,1.390241 0.404297,1.896485 0.335014,0.506243 0.948621,0.763671 1.660156,0.763671 h 6.583984 c 0.691421,0 1.302744,-0.170803 1.720703,-0.607421 0.417959,-0.436618 0.578125,-1.060649 0.578125,-1.798828 v -3.914063 c 0,-0.757148 -0.157083,-1.3938421 -0.570312,-1.8476562 C 37.491068,8.8391546 36.872949,8.6503906 36.175781,8.6503906 Z m 0,1 h 6.583984 c 0.530501,0 0.810796,0.1173912 0.990235,0.3144532 0.179438,0.1970622 0.308593,0.5570942 0.308593,1.1757812 v 3.914063 c 0,0.595323 -0.126073,0.924914 -0.300781,1.107421 -0.174708,0.182508 -0.461798,0.298828 -0.998047,0.298828 h -6.583984 c -0.516133,0 -0.684935,-0.102981 -0.826172,-0.316406 -0.141237,-0.213424 -0.238281,-0.663298 -0.238281,-1.34375 v -3.830078 c 0,-0.557223 0.12748,-0.875502 0.287109,-1.0507811 0.15963,-0.1752795 0.384713,-0.2695313 0.777344,-0.2695313 z"
d="m 378.62465,9.2135632 h -3.38667 V 5.4247238 h -3.83117 v 3.7888394 h -2.58234 v 0.1693336 l 1.05834,2.7516712 h 1.524 v 8.699514 h 3.83117 v -8.699514 h 3.38667 z m 3.70417,-0.0635 c 0.74083,0 0.97367,0.2751672 0.97367,1.0795018 v 10.604517 h 4.191 V 9.2770633 c 0,-2.7940045 -0.762,-3.8523395 -3.70417,-3.8523395 h -4.65667 v 0.1693336 l 1.37583,3.5560056 z"
d="m 370.90625,4.9238281 v 3.7890625 h -2.58203 v 0.7636719 l 1.21484,3.1582035 h 1.36719 v 8.699218 h 4.83203 V 12.634766 H 379.125 V 8.7128906 h -3.38672 V 4.9238281 Z m 7.72656,0 V 5.6875 l 1.53321,3.9628906 h 2.1621 c 0.30851,0 0.37476,0.050297 0.39258,0.070312 0.0178,0.020016 0.082,0.1558452 0.082,0.5097654 v 11.103515 h 5.19141 V 9.2773438 c 0,-1.4424568 -0.18358,-2.5460127 -0.87891,-3.3105469 -0.69533,-0.7645343 -1.79513,-1.0429688 -3.32617,-1.0429688 z m -6.72656,1 h 2.83203 v 3.7890625 h 3.38672 v 1.9218754 h -3.38672 v 8.699218 h -2.83203 v -8.699218 h -1.67969 l -0.74023,-1.9218754 h 2.41992 z m 7.89062,0 h 3.99219 c 1.41114,0 2.16473,0.2517094 2.58594,0.7148438 0.42121,0.4631344 0.61914,1.2871241 0.61914,2.6386719 V 20.333984 h -3.19141 V 10.230469 c 0,-0.4504146 -0.0521,-0.85713 -0.33593,-1.1757815 -0.2838,-0.3186515 -0.70635,-0.4042969 -1.13868,-0.4042969 h -1.47656 z"
d="m 198.37891,137.20312 v 4.82618 h -4.80469 v 3.17968 h 4.80469 v 4.82618 h 3.20117 v -4.82618 h 4.80469 v -3.17968 h -4.80469 v -4.82618 z m 1,1 h 1.20117 v 4.82618 h 4.80469 v 1.17968 h -4.80469 v 4.82618 h -1.20117 v -4.82618 h -4.80469 v -1.17968 h 4.80469 z"
d="M 22.60772,9.2135632 H 19.221048 V 5.4247238 h -3.831173 v 3.7888394 h -2.582337 v 0.1693336 l 1.058335,2.7516712 h 1.524002 v 8.699514 h 3.831173 v -8.699514 h 3.386672 z m 1.227659,1.1006688 v 6.096009 c 0,2.878672 1.375835,4.423841 4.762507,4.423841 h 8.805348 c 3.407838,0 4.762507,-1.545169 4.762507,-4.423841 v -6.096009 c 0,-3.0903387 -1.354669,-4.8895082 -4.762507,-4.8895082 h -8.805348 c -3.386672,0 -4.762507,1.7991695 -4.762507,4.8895082 z m 4.191006,0.656167 c 0,-1.2700017 0.592668,-1.820336 1.566336,-1.820336 h 6.582844 c 1.227669,0 1.799169,0.6138344 1.799169,1.98967 v 3.915839 c 0,1.333503 -0.5715,1.905003 -1.799169,1.905003 h -6.582844 c -1.227668,0 -1.566336,-0.719667 -1.566336,-2.159003 z"
d="m 14.890625,4.9238281 v 3.7890625 h -2.583984 v 0.7636719 l 1.216797,3.1582035 h 1.367187 v 8.699218 h 4.830078 v -8.699218 h 3.386719 V 8.7128906 H 19.720703 V 4.9238281 Z m 13.707031,0 c -1.781877,0 -3.138417,0.4891086 -4.019531,1.4492188 -0.881114,0.9601101 -1.242188,2.322036 -1.242188,3.9414061 v 6.095703 c 0,1.518993 0.370925,2.78417 1.261719,3.650391 0.890794,0.86622 2.230623,1.273437 4,1.273437 h 8.804688 c 1.779488,0 3.121173,-0.408321 4.009765,-1.27539 0.888593,-0.86707 1.253907,-2.13067 1.253907,-3.648438 v -6.095703 c 0,-1.6182286 -0.355554,-2.9804801 -1.234375,-3.9414061 -0.878822,-0.960926 -2.237387,-1.4492188 -4.029297,-1.4492188 z m -12.707031,1 h 2.830078 v 3.7890625 h 3.386719 v 1.9218754 h -3.386719 v 8.699218 H 15.890625 V 11.634766 H 14.208984 L 13.46875,9.7128906 h 2.421875 z m 12.707031,0 h 8.804688 c 1.615927,0 2.640543,0.4137566 3.291015,1.125 0.650473,0.7112434 0.972657,1.793515 0.972657,3.2656249 v 6.095703 c 0,1.360905 -0.312424,2.308411 -0.953125,2.933594 -0.640702,0.625183 -1.682198,0.990234 -3.310547,0.990234 h -8.804688 c -1.617294,0 -2.658943,-0.364202 -3.302734,-0.990234 -0.643792,-0.626032 -0.958984,-1.573915 -0.958985,-2.933594 v -6.095703 c 0,-1.4709684 0.325044,-2.5535656 0.978516,-3.2656249 0.653472,-0.7120593 1.678409,-1.125 3.283203,-1.125 z m 0.994141,2.7265625 c -0.581037,0 -1.137457,0.1802682 -1.517578,0.5976563 -0.380122,0.4173881 -0.546875,1.0098771 -0.546875,1.7226561 v 3.830078 c 0,0.758884 0.06928,1.390241 0.404297,1.896485 0.335014,0.506243 0.948621,0.763671 1.660156,0.763671 h 6.583984 c 0.691421,0 1.302744,-0.170803 1.720703,-0.607421 0.417959,-0.436618 0.578125,-1.060649 0.578125,-1.798828 v -3.914063 c 0,-0.757148 -0.157083,-1.3938421 -0.570312,-1.8476562 C 37.491068,8.8391546 36.872949,8.6503906 36.175781,8.6503906 Z m 0,1 h 6.583984 c 0.530501,0 0.810796,0.1173912 0.990235,0.3144532 0.179438,0.1970622 0.308593,0.5570942 0.308593,1.1757812 v 3.914063 c 0,0.595323 -0.126073,0.924914 -0.300781,1.107421 -0.174708,0.182508 -0.461798,0.298828 -0.998047,0.298828 h -6.583984 c -0.516133,0 -0.684935,-0.102981 -0.826172,-0.316406 -0.141237,-0.213424 -0.238281,-0.663298 -0.238281,-1.34375 v -3.830078 c 0,-0.557223 0.12748,-0.875502 0.287109,-1.0507811 0.15963,-0.1752795 0.384713,-0.2695313 0.777344,-0.2695313 z"
d="m 378.62465,9.2135632 h -3.38667 V 5.4247238 h -3.83117 v 3.7888394 h -2.58234 v 0.1693336 l 1.05834,2.7516712 h 1.524 v 8.699514 h 3.83117 v -8.699514 h 3.38667 z m 3.70417,-0.0635 c 0.74083,0 0.97367,0.2751672 0.97367,1.0795018 v 10.604517 h 4.191 V 9.2770633 c 0,-2.7940045 -0.762,-3.8523395 -3.70417,-3.8523395 h -4.65667 v 0.1693336 l 1.37583,3.5560056 z"
d="m 370.90625,4.9238281 v 3.7890625 h -2.58203 v 0.7636719 l 1.21484,3.1582035 h 1.36719 v 8.699218 h 4.83203 V 12.634766 H 379.125 V 8.7128906 h -3.38672 V 4.9238281 Z m 7.72656,0 V 5.6875 l 1.53321,3.9628906 h 2.1621 c 0.30851,0 0.37476,0.050297 0.39258,0.070312 0.0178,0.020016 0.082,0.1558452 0.082,0.5097654 v 11.103515 h 5.19141 V 9.2773438 c 0,-1.4424568 -0.18358,-2.5460127 -0.87891,-3.3105469 -0.69533,-0.7645343 -1.79513,-1.0429688 -3.32617,-1.0429688 z m -6.72656,1 h 2.83203 v 3.7890625 h 3.38672 v 1.9218754 h -3.38672 v 8.699218 h -2.83203 v -8.699218 h -1.67969 l -0.74023,-1.9218754 h 2.41992 z m 7.89062,0 h 3.99219 c 1.41114,0 2.16473,0.2517094 2.58594,0.7148438 0.42121,0.4631344 0.61914,1.2871241 0.61914,2.6386719 V 20.333984 h -3.19141 V 10.230469 c 0,-0.4504146 -0.0521,-0.85713 -0.33593,-1.1757815 -0.2838,-0.3186515 -0.70635,-0.4042969 -1.13868,-0.4042969 h -1.47656 z"
d="m 198.37891,137.20312 v 4.82618 h -4.80469 v 3.17968 h 4.80469 v 4.82618 h 3.20117 v -4.82618 h 4.80469 v -3.17968 h -4.80469 v -4.82618 z m 1,1 h 1.20117 v 4.82618 h 4.80469 v 1.17968 h -4.80469 v 4.82618 h -1.20117 v -4.82618 h -4.80469 v -1.17968 h 4.80469 z"
d="M 22.60772,9.2135632 H 19.221048 V 5.4247238 h -3.831173 v 3.7888394 h -2.582337 v 0.1693336 l 1.058335,2.7516712 h 1.524002 v 8.699514 h 3.831173 v -8.699514 h 3.386672 z m 1.227659,1.1006688 v 6.096009 c 0,2.878672 1.375835,4.423841 4.762507,4.423841 h 8.805348 c 3.407838,0 4.762507,-1.545169 4.762507,-4.423841 v -6.096009 c 0,-3.0903387 -1.354669,-4.8895082 -4.762507,-4.8895082 h -8.805348 c -3.386672,0 -4.762507,1.7991695 -4.762507,4.8895082 z m 4.191006,0.656167 c 0,-1.2700017 0.592668,-1.820336 1.566336,-1.820336 h 6.582844 c 1.227669,0 1.799169,0.6138344 1.799169,1.98967 v 3.915839 c 0,1.333503 -0.5715,1.905003 -1.799169,1.905003 h -6.582844 c -1.227668,0 -1.566336,-0.719667 -1.566336,-2.159003 z"
d="m 14.890625,4.9238281 v 3.7890625 h -2.583984 v 0.7636719 l 1.216797,3.1582035 h 1.367187 v 8.699218 h 4.830078 v -8.699218 h 3.386719 V 8.7128906 H 19.720703 V 4.9238281 Z m 13.707031,0 c -1.781877,0 -3.138417,0.4891086 -4.019531,1.4492188 -0.881114,0.9601101 -1.242188,2.322036 -1.242188,3.9414061 v 6.095703 c 0,1.518993 0.370925,2.78417 1.261719,3.650391 0.890794,0.86622 2.230623,1.273437 4,1.273437 h 8.804688 c 1.779488,0 3.121173,-0.408321 4.009765,-1.27539 0.888593,-0.86707 1.253907,-2.13067 1.253907,-3.648438 v -6.095703 c 0,-1.6182286 -0.355554,-2.9804801 -1.234375,-3.9414061 -0.878822,-0.960926 -2.237387,-1.4492188 -4.029297,-1.4492188 z m -12.707031,1 h 2.830078 v 3.7890625 h 3.386719 v 1.9218754 h -3.386719 v 8.699218 H 15.890625 V 11.634766 H 14.208984 L 13.46875,9.7128906 h 2.421875 z m 12.707031,0 h 8.804688 c 1.615927,0 2.640543,0.4137566 3.291015,1.125 0.650473,0.7112434 0.972657,1.793515 0.972657,3.2656249 v 6.095703 c 0,1.360905 -0.312424,2.308411 -0.953125,2.933594 -0.640702,0.625183 -1.682198,0.990234 -3.310547,0.990234 h -8.804688 c -1.617294,0 -2.658943,-0.364202 -3.302734,-0.990234 -0.643792,-0.626032 -0.958984,-1.573915 -0.958985,-2.933594 v -6.095703 c 0,-1.4709684 0.325044,-2.5535656 0.978516,-3.2656249 0.653472,-0.7120593 1.678409,-1.125 3.283203,-1.125 z m 0.994141,2.7265625 c -0.581037,0 -1.137457,0.1802682 -1.517578,0.5976563 -0.380122,0.4173881 -0.546875,1.0098771 -0.546875,1.7226561 v 3.830078 c 0,0.758884 0.06928,1.390241 0.404297,1.896485 0.335014,0.506243 0.948621,0.763671 1.660156,0.763671 h 6.583984 c 0.691421,0 1.302744,-0.170803 1.720703,-0.607421 0.417959,-0.436618 0.578125,-1.060649 0.578125,-1.798828 v -3.914063 c 0,-0.757148 -0.157083,-1.3938421 -0.570312,-1.8476562 C 37.491068,8.8391546 36.872949,8.6503906 36.175781,8.6503906 Z m 0,1 h 6.583984 c 0.530501,0 0.810796,0.1173912 0.990235,0.3144532 0.179438,0.1970622 0.308593,0.5570942 0.308593,1.1757812 v 3.914063 c 0,0.595323 -0.126073,0.924914 -0.300781,1.107421 -0.174708,0.182508 -0.461798,0.298828 -0.998047,0.298828 h -6.583984 c -0.516133,0 -0.684935,-0.102981 -0.826172,-0.316406 -0.141237,-0.213424 -0.238281,-0.663298 -0.238281,-1.34375 v -3.830078 c 0,-0.557223 0.12748,-0.875502 0.287109,-1.0507811 0.15963,-0.1752795 0.384713,-0.2695313 0.777344,-0.2695313 z"
d="m 378.62465,9.2135632 h -3.38667 V 5.4247238 h -3.83117 v 3.7888394 h -2.58234 v 0.1693336 l 1.05834,2.7516712 h 1.524 v 8.699514 h 3.83117 v -8.699514 h 3.38667 z m 3.70417,-0.0635 c 0.74083,0 0.97367,0.2751672 0.97367,1.0795018 v 10.604517 h 4.191 V 9.2770633 c 0,-2.7940045 -0.762,-3.8523395 -3.70417,-3.8523395 h -4.65667 v 0.1693336 l 1.37583,3.5560056 z"
d="m 370.90625,4.9238281 v 3.7890625 h -2.58203 v 0.7636719 l 1.21484,3.1582035 h 1.36719 v 8.699218 h 4.83203 V 12.634766 H 379.125 V 8.7128906 h -3.38672 V 4.9238281 Z m 7.72656,0 V 5.6875 l 1.53321,3.9628906 h 2.1621 c 0.30851,0 0.37476,0.050297 0.39258,0.070312 0.0178,0.020016 0.082,0.1558452 0.082,0.5097654 v 11.103515 h 5.19141 V 9.2773438 c 0,-1.4424568 -0.18358,-2.5460127 -0.87891,-3.3105469 -0.69533,-0.7645343 -1.79513,-1.0429688 -3.32617,-1.0429688 z m -6.72656,1 h 2.83203 v 3.7890625 h 3.38672 v 1.9218754 h -3.38672 v 8.699218 h -2.83203 v -8.699218 h -1.67969 l -0.74023,-1.9218754 h 2.41992 z m 7.89062,0 h 3.99219 c 1.41114,0 2.16473,0.2517094 2.58594,0.7148438 0.42121,0.4631344 0.61914,1.2871241 0.61914,2.6386719 V 20.333984 h -3.19141 V 10.230469 c 0,-0.4504146 -0.0521,-0.85713 -0.33593,-1.1757815 -0.2838,-0.3186515 -0.70635,-0.4042969 -1.13868,-0.4042969 h -1.47656 z"
d="m 22.61014,297.44787 h -3.386672 v -3.78884 h -3.831173 v 3.78884 h -2.582337 v 0.16933 l 1.058335,2.75168 h 1.524002 v 8.69951 h 3.831173 v -8.69951 h 3.386672 z m 18.944187,7.81051 h -8.001013 c -1.227669,0 -2.286004,0.0212 -4.064006,0.127 v -0.14816 c 0.952501,-0.23284 1.735669,-0.46567 3.810006,-1.143 l 5.969009,-1.92617 c 1.98967,-0.63501 2.455337,-1.35467 2.455337,-3.47134 v -1.54517 c 0,-2.09551 -0.952501,-3.49251 -3.661839,-3.49251 H 23.753132 v 0.16934 l 1.312335,3.429 h 11.408852 c 0.698501,0 0.867834,0.27517 0.867834,0.78317 v 0.5715 c 0,0.55033 -0.211667,0.8255 -0.825501,1.03717 l -11.408851,3.89467 c -0.783168,0.27517 -1.016002,0.61384 -1.016002,1.56634 v 3.19617 c 0,0.55033 0.254,0.762 0.592668,0.762 h 16.86986 z"
d="m 14.892578,293.1582 v 3.78907 h -2.582031 v 0.76367 l 1.214844,3.1582 h 1.367187 v 8.69922 h 4.830078 v -8.69922 h 3.386719 v -3.92187 h -3.386719 v -3.78907 z m 8.361328,0 v 0.76172 l 1.466797,3.83789 h 11.753906 c 0.283594,0 0.342775,0.0558 0.337891,0.0508 -0.0049,-0.005 0.0293,0.034 0.0293,0.23243 v 0.57031 c 0,0.21106 -0.03003,0.28002 -0.07422,0.33594 -0.04419,0.0559 -0.153527,0.13867 -0.414062,0.22851 l -11.410157,3.89649 h -0.002 c -0.437489,0.15371 -0.814289,0.36283 -1.052734,0.72656 -0.238445,0.36373 -0.296875,0.79355 -0.296875,1.31054 v 3.19727 c 0,0.35209 0.0826,0.67409 0.292969,0.91602 0.210369,0.24192 0.525326,0.3457 0.798828,0.3457 h 17.371093 v -4.81055 h -8.501953 c -0.243624,0 -0.570664,0.0232 -0.808593,0.0254 0.305142,-0.0978 0.326312,-0.0879 0.708984,-0.21289 l 5.966797,-1.92578 c 1.036192,-0.3307 1.774785,-0.71448 2.232422,-1.37109 0.457636,-0.65661 0.572265,-1.48117 0.572265,-2.57617 v -1.54493 c 0,-1.12351 -0.260337,-2.14285 -0.953125,-2.87695 -0.692788,-0.7341 -1.768378,-1.11719 -3.208984,-1.11719 z m -7.361328,1 h 2.830078 v 3.78907 h 3.386719 v 1.92187 h -3.386719 v 8.69922 h -2.830078 v -8.69922 h -1.68164 l -0.738282,-1.92187 h 2.419922 z m 8.523438,0 H 38.0625 c 1.268732,0 2.021625,0.31641 2.482422,0.80469 0.460797,0.48828 0.679687,1.21746 0.679687,2.18945 v 1.54493 c 0,1.02166 -0.119963,1.61276 -0.392578,2.0039 -0.272614,0.39114 -0.761365,0.68593 -1.714844,0.99024 h -0.002 l -5.970703,1.92773 c -2.071176,0.6763 -2.834211,0.90322 -3.773437,1.13281 l -0.38086,0.0918 v 1.07227 l 0.529297,-0.0312 c 1.770212,-0.10534 2.811291,-0.12696 4.033203,-0.12696 h 7.501953 v 2.81055 H 24.683594 c -0.06517,0 -0.04362,-4.6e-4 -0.04492,-0.002 -0.0013,-0.002 -0.04687,-0.0615 -0.04687,-0.25977 v -3.19727 c 0,-0.4355 0.05905,-0.64919 0.132812,-0.76171 0.07376,-0.11253 0.20315,-0.21058 0.548828,-0.33204 l 11.40625,-3.89257 v -0.002 c 0.353299,-0.12183 0.657948,-0.28056 0.873047,-0.55273 0.215099,-0.27217 0.289063,-0.61776 0.289063,-0.95703 v -0.57031 c 0,-0.30956 -0.05028,-0.66208 -0.314453,-0.93165 -0.264176,-0.26956 -0.637827,-0.35156 -1.052735,-0.35156 h -11.06446 z"
d="m 368.78211,297.97579 h -3.38667 v -3.78883 h -3.83117 v 3.78883 h -2.58234 v 0.16934 l 1.05833,2.75167 h 1.52401 v 8.69951 h 3.83117 v -8.69951 h 3.38667 z m 13.52551,-0.27516 c 0.61384,0 0.889,0.254 0.889,0.762 v 0.74083 c 0,0.5715 -0.254,0.91017 -0.91016,0.91017 h -10.11769 v 0.16934 l 0.99484,2.70933 h 9.05935 c 0.6985,0 1.03716,0.29634 1.03716,0.9525 v 0.76201 c 0,0.71966 -0.29633,1.05833 -1.16416,1.05833 h -11.74752 v 3.83117 h 13.03868 c 3.00568,0 4.10634,-1.50283 4.10634,-4.064 v -1.16417 c 0,-1.3335 -0.6985,-2.30717 -1.56633,-2.73051 0.635,-0.46566 1.22767,-1.22766 1.22767,-2.56117 v -1.29117 c 0,-2.159 -1.03717,-3.59833 -3.89468,-3.59833 h -13.50435 v 0.16933 l 1.35467,3.34434 z"
d="m 361.06445,293.6875 v 3.78906 h -2.58203 v 0.76172 l 1.21485,3.1582 h 1.36718 v 8.69922 h 4.83008 v -8.69922 h 3.38672 v -3.91992 h -3.38672 v -3.78906 z m 8.19141,0 v 0.76562 l 1.51758,3.74805 h 11.53515 c 0.23796,0 0.328,0.051 0.34571,0.0664 0.0177,0.0154 0.043,0.0316 0.043,0.19531 v 0.74023 c 0,0.21168 -0.0464,0.29622 -0.0762,0.32813 -0.0297,0.0319 -0.0919,0.082 -0.33398,0.082 h -10.61914 v 0.75781 l 1.14648,3.1211 h 9.40821 c 0.27854,0 0.40023,0.0587 0.44531,0.0996 0.0451,0.0409 0.0918,0.11145 0.0918,0.35351 v 0.76172 c 0,0.2912 -0.0525,0.39055 -0.10157,0.4375 -0.0491,0.047 -0.19362,0.12109 -0.5625,0.12109 h -12.24804 v 4.83008 h 13.53906 c 1.58617,0 2.77621,-0.40668 3.54492,-1.22851 0.76871,-0.82184 1.0625,-1.98178 1.0625,-3.33399 v -1.16601 c 0,-1.24687 -0.61226,-2.15723 -1.39258,-2.75782 0.56011,-0.56303 1.05274,-1.33541 1.05274,-2.5332 v -1.29101 c 0,-1.15957 -0.2836,-2.20926 -1.01758,-2.95899 -0.73398,-0.74972 -1.86425,-1.13867 -3.37695,-1.13867 z m -7.19141,1 h 2.83008 v 3.78906 h 3.38672 v 1.91992 h -3.38672 v 8.69922 h -2.83008 v -8.69922 h -1.68164 l -0.73828,-1.91992 h 2.41992 z m 8.36524,0 h 12.83008 c 1.3448,0 2.16312,0.3282 2.6621,0.83789 0.49899,0.50969 0.73243,1.26033 0.73243,2.25977 v 1.29101 c 0,1.18536 -0.47856,1.75863 -1.02344,2.1582 l -0.66797,0.49024 0.74414,0.36328 c 0.69133,0.33724 1.28711,1.1204 1.28711,2.2793 v 1.16601 c 0,1.20896 -0.25798,2.08052 -0.79102,2.65039 -0.53303,0.56988 -1.39689,0.91211 -2.8164,0.91211 h -12.53906 v -2.83008 h 11.24804 c 0.49896,0 0.93787,-0.0961 1.25391,-0.39843 0.31604,-0.3023 0.41016,-0.7317 0.41016,-1.16016 v -0.76172 c 0,-0.4141 -0.12105,-0.82246 -0.41993,-1.09375 -0.29887,-0.27129 -0.69723,-0.35937 -1.11718,-0.35937 h -8.71094 l -0.68945,-1.87891 h 9.46484 c 0.4141,0 0.80511,-0.12009 1.06641,-0.40039 0.26129,-0.2803 0.34375,-0.64994 0.34375,-1.00977 v -0.74023 c 0,-0.34427 -0.11339,-0.71067 -0.38672,-0.94922 -0.27333,-0.23854 -0.62607,-0.3125 -1.00196,-0.3125 h -10.86132 z"
d="m 198.37891,6.9902344 v 4.8261716 h -4.80469 v 3.181641 h 4.80469 v 4.826172 h 3.20117 v -4.826172 h 4.80469 v -3.181641 h -4.80469 V 6.9902344 Z m 1,1 h 1.20117 v 4.8261716 h 4.80469 v 1.181641 h -4.80469 v 4.826172 h -1.20117 v -4.826172 h -4.80469 v -1.181641 h 4.80469 z"
d="m 28.367187,137.08398 v 4.82618 h -4.80664 v 3.17968 h 4.80664 v 4.82618 h 3.201172 v -4.82618 h 4.804688 v -3.17968 h -4.804688 v -4.82618 z m 1,1 h 1.201172 v 4.82618 h 4.804688 v 1.17968 h -4.804688 v 4.82618 h -1.201172 v -4.82618 h -4.80664 v -1.17968 h 4.80664 z"
d="m 198.37891,137.20312 v 4.82618 h -4.80469 v 3.17968 h 4.80469 v 4.82618 h 3.20117 v -4.82618 h 4.80469 v -3.17968 h -4.80469 v -4.82618 z m 1,1 h 1.20117 v 4.82618 h 4.80469 v 1.17968 h -4.80469 v 4.82618 h -1.20117 v -4.82618 h -4.80469 v -1.17968 h 4.80469 z"
d="m 368.375,137.12109 v 4.82618 h -4.80469 v 3.17968 h 4.80469 v 4.82617 h 3.20117 v -4.82617 h 4.80469 v -3.17968 h -4.80469 v -4.82618 z m 1,1 h 1.20117 v 4.82618 h 4.80469 v 1.17968 h -4.80469 v 4.82617 H 369.375 v -4.82617 h -4.80469 v -1.17968 h 4.80469 z"
d="m 198.36133,295.08203 v 4.82617 h -4.80469 v 3.17969 h 4.80469 v 4.82617 h 3.20117 v -4.82617 h 4.80469 v -3.17969 h -4.80469 v -4.82617 z m 1,1 h 1.20117 v 4.82617 h 4.80469 v 1.17969 h -4.80469 v 4.82617 h -1.20117 v -4.82617 h -4.80469 v -1.17969 h 4.80469 z"
def->tooltip=L("Distance (mm) from the nozzle to the collision-side carriage edge in X. Used to calculate the width of the collision exclusion strip at each X boundary.");
def->tooltip=L("Distance (mm) from the nozzle to the collision-side carriage edge in Y. Used to calculate the width of the collision exclusion strip at each Y boundary.");
def->tooltip=L("Non-blocking advisory clearance strip (mm) drawn inside the primary zone at each carriage boundary. Parts placed within this strip will still slice; it is a visual reminder to leave extra clearance near the zone edge.");
def->tooltip=L("Name of the active IDEX/IQEX parallel print mode, or \"primary\" for single-carriage printing. Requires IDEX/IQEX Printer enabled in the Printer preset (Printer \u2192 Multimaterial \u2192 IDEX/IQEX Configuration).");
def->tooltip=L("Tool role assignments for each mode. Format: \"idx:P,idx:C,idx:M\" where P=Primary, C=Copy, M=Mirror (e.g. \"0:P,1:C,2:M,3:M\"). Managed by the IDEX/IQEX Modes editor in the Printer preset.");
def->mode=comAdvanced;
def->set_default_value(newConfigOptionStrings());
def=this->add("imex_mode_gcodes",coStrings);
def->label=L("IDEX/IQEX Mode G-codes");
def->tooltip=L("G-code or macro call to inject at print start for each mode.");
def->mode=comAdvanced;
def->set_default_value(newConfigOptionStrings());
def=this->add("manual_filament_change",coBool);
def->label=L("Manual Filament Change");
def->tooltip=L("Enable this option to omit the custom Change filament G-code only at the beginning of the print. "
new_def("initial_no_support_extruder",coInt,"Initial no support extruder","Zero-based index of the first extruder used for printing without support. Same as initial_no_support_tool.");
new_def("in_head_wrap_detect_zone",coBool,"In head wrap detect zone","Indicates if the first layer overlaps with the head wrap zone.");
new_def("curr_bed_type",coString,"Current bed type","Name of the currently selected bed plate type (e.g. 'Textured PEI Plate', 'Smooth High Temp Plate').");
new_def("imex_mode",coString,"IDEX/IQEX active mode","Name of the active IDEX/IQEX parallel print mode for this plate (e.g. 'primary', 'mirror', 'copy'). Empty string if IDEX/IQEX is not enabled.");
new_def("imex_mode_index",coInt,"IDEX/IQEX active mode index","Zero-based index of the active IDEX/IQEX parallel print mode within imex_mode_names.");
new_def("imex_mode_gcode",coString,"IDEX/IQEX active mode G-code","The raw mode G-code template for the active IDEX/IQEX parallel print mode, after placeholder evaluation. Globals defined here flow into machine_start_gcode.");
MessageDialogdlg(this,_L("Filament type is not selected, please reselect type."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),wxYES|wxYES_DEFAULT|wxCENTRE);
MessageDialogdlg(this,_L("Filament type is not selected, please reselect type."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),wxOK|wxCENTRE);
MessageDialogdlg(this,_L("There may be disallowed characters in the vendor or serial input of the filament. Please delete and re-enter."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),
MessageDialogdlg(this,_L("The vendor cannot be a number; please re-enter."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),
wxYES|wxYES_DEFAULT|wxCENTRE);
wxOK|wxCENTRE);
dlg.ShowModal();
return;
}
if(!is_check_box_selected()){
MessageDialogdlg(this,_L("You have not selected a printer or preset yet. Please select at least one."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),
MessageDialogdlg(this,_L("The model was not found; please reselect vendor."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),wxYES|wxYES_DEFAULT|wxCENTRE);
MessageDialogdlg(this,_L("The model was not found; please reselect vendor."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),wxOK|wxCENTRE);
MessageDialogdlg(this,_L("You need to select at least one filament preset."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),wxYES|wxYES_DEFAULT|wxCENTRE);
MessageDialogdlg(this,_L("You need to select at least one filament preset."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),wxOK|wxCENTRE);
MessageDialogdlg(this,_L("You need to select at least one process preset."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),wxYES|wxYES_DEFAULT|wxCENTRE);
MessageDialogdlg(this,_L("You need to select at least one process preset."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),wxOK|wxCENTRE);
MessageDialogdlg(this,_L("Current vendor has no models, please reselect."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),wxYES|wxYES_DEFAULT|wxCENTRE);
MessageDialogdlg(this,_L("Current vendor has no models, please reselect."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),wxOK|wxCENTRE);
MessageDialogdlg(this,_L("You have not selected the vendor and model or input the custom vendor and model."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),
MessageDialogdlg(this,_L("All inputs in the custom printer vendor or model are spaces. Please re-enter."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),
wxYES|wxYES_DEFAULT|wxCENTRE);
wxOK|wxCENTRE);
dlg.ShowModal();
returnfalse;
}
if(check_printable_area()==false){
MessageDialogdlg(this,_L("Please check bed printable shape and origin input."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),wxYES|wxYES_DEFAULT|wxCENTRE);
MessageDialogdlg(this,_L("Please check bed printable shape and origin input."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),wxOK|wxCENTRE);
MessageDialogdlg(this,_L("You have not yet selected the printer to replace the nozzle for; please choose a printer."),wxString(SLIC3R_APP_FULL_NAME)+" - "+_L("Info"),
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.