mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-12 10:21:17 +00:00
de07a84bfb839e47d1c6cac8115fa1ce5be598da
32265
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
de07a84bfb |
fix: add the two includes clang-tidy reported as missing
misc-include-cleaner on the changed lines: TextureLibrary.cpp uses DecodedHeightTexture, which only reached it through TextureLibrary.hpp; it now includes libslic3r/TextureDisplacement.hpp itself. The contrast test uses std::not_equal_to, so <functional>. |
||
|
|
752b356ff0 |
fix: refresh the image-adjustment panel when the layer's image changes under it
The panel decoded the layer's image once, when it opened, so switching the layer's texture from the picker left it showing the old photo. The 3D view had already moved on, since decode_height_texture() reads layer.image_data and that had changed, so the panel and the model disagreed about what was being adjusted. Now the panel notices the image_data it decoded from is no longer the layer's and re-reads it, keeping the slider positions: the user is adjusting this layer, and the sliders are what they have just set. Possible because the panel is a window rather than a popup - the picker is reachable while it is open, which is what makes this case exist at all. Two more ways the open panel could end up pointing at the wrong thing, found while fixing that one: Slots are numbered per volume, so selecting a different object rebound the panel to whatever layer held the same slot on the newly selected one - a different image under a panel opened for another object. The panel now records the volume it was opened on and closes when the selection leaves it. And closing it reverted preview_adjust through texture_volume(), which answers for the current selection. Since a selection change is one of the ways to close it, that reverted the wrong volume's layer: it cleared an adjustment the user never made and left the real one live on the original object, which would have gone on displacing from the adjusted image with no panel open. The revert now finds the volume by the id recorded at open time. |
||
|
|
8f1c8f0580 |
feat: live displacement in the image-adjustment panel, reached from a per-layer icon
Four changes to the image-adjustment panel, all from using it: Entry point. The panel is now opened by an icon in the layer's header row, between the image name and the order arrows, rather than by a button buried under "More settings". It acts on the image the name refers to, so it belongs beside the name. Disabled for a layer with no image. Only "Save as copy". Overwrite is gone, and with it the one path that edited a layer's stored image in place. Writing the result to the texture library and pointing the layer at it is the single way an adjustment becomes permanent, which also means an adjusted texture is always a file the user can find and reuse. encode_texture_image_data() had no other caller and is removed. A window, not a popup. A popup dimmed the canvas and closed on any click outside itself, which between them made it impossible to watch the model while adjusting it. An ordinary floating window leaves the 3D view live, clickable and orbitable, and stays put until Cancel, Save as copy or its close button. The displacement follows the sliders. TextureDisplacementLayer gains a transient preview_adjust that decode_height_texture() applies on top of the decoded image, before smoothing. Everything downstream of the decode then sees the adjusted image without being told the panel exists: the relief, the colour, the thumbnails, the UV backdrop and the bake. It is not serialized by cereal or the .3mf, because it is the state of an open editor rather than a property of the layer; the panel reverts it on every exit that is not a commit, and a commit clears it before pointing the layer at the file that already carries the edit, so the adjustment is never applied twice. Two caches had to learn about it or they would have gone on serving the pixels from before the slider moved, since neither the image_data pointer nor the smoothing value changes while a slider is dragged: the new adjusted-copy cache keys on (image, adjustments), the smoothed cache now keys on both, and the GPU thumbnail, height and colour textures and the UV editor backdrop carry the adjustment in their keys alongside smoothing. Two tests cover exactly that: the same image_data with two different adjustments must decode to two different images, with and without smoothing on. The recompute is lazy. A slider move marks the preview dirty and the existing "rebuild once the mouse is released" rule in the panel consumes it, so a drag recomputes the displacement once, on release, instead of once per frame. The panel's own image still updates continuously, from a background pass over a 384 px copy, so the photo tracks the slider while the model catches up after. |
||
|
|
c07165888b |
feat: add an image-adjustment panel to texture displacement layers
Adds brightness, contrast, sharpness, blur and hue over one layer's image, shown on the image itself and updating as the sliders move. Reached from an "Adjust image" button in the layer card, beside Smoothing. What the panel produces is not a set of layer parameters: it replaces the layer's image, so the adjusted pixels feed the colour and the displacement together and a brightness change moves the relief too. That is deliberate, and the panel says so. It is also why brightness is a lift toward white rather than an additive offset: an offset drives everything above mid-grey to a flat 255, and since the image is the height map, that region would bake as flat relief instead of bright relief. Three exits: Cancel discards, Overwrite replaces the layer's image_data, and Save as copy writes a new PNG into the user's texture library and points the layer at it. Overwrite and Save as copy both hand the layer a brand new allocation, which is what invalidates decode_height_texture()'s cache, analyze_texture_detail()'s, and the GPU thumbnail and height/colour textures, all of them keyed by that pointer. Grayscale in, grayscale out: a grey image is not promoted to RGB, since that would offer the colour feature a row of identical greys and triple the stored PNG for it. Hue is disabled for a grayscale source, with the reason shown, because grey has no hue to turn. The live preview runs on its own thread over a 384 px downscale, one pass in flight at a time so a drag coalesces to wherever the sliders stop. Measured on a 1024x1024 with every knob engaged, a full-size pass costs 13-19 ms grayscale and 40-60 ms in colour (130-190 ms at 2048 in colour), which is far too much to repeat per slider frame; the same pass on the downscale is 3-4 ms and 9-12 ms. Not the plater's job worker either: that is a single FIFO shared with Bake, where a per-frame image filter would queue behind multi-second mesh work. The full-size pass runs once, on commit, on the UI thread, because the rebuild_preview() immediately after it is an order of magnitude more work synchronously already. The image maths lands in libslic3r as apply_texture_adjustments() and downscale_height_texture(), so it is testable without a GUI, and the blur reuses the same smooth_height_pixels() the Smoothing slider drives. |
||
|
|
d007134001 | fix: update translation for Assistant(HMS) in Chinese localization | ||
|
|
70f518d87a |
Added new profiles for the FELIX Printers vendor (#16314)
* Added new profiles for the FELIX Printers vendor * fixed all errors from profiles |
||
|
|
f4bde2da0a | Fix missing plate bed override normalization in Configuration Wizard (#16365) | ||
|
|
d5a11eb076 | ci: cancel Build all runs of closed pull requests (#16380) | ||
|
|
461275d9bb | ci: build pushes that change deps_src or the build scripts (#16378) | ||
|
|
95d3e8eafc |
Queue Pull Request macOS Builds for a Free Runner (#16343)
* ci: wait for the base branch's macOS deps instead of rebuilding them * Hold Pull Request macOS Builds Until a Runner Is Free A pull request's macOS build now waits in one repo-wide line, from a Linux job, until the macOS runners every active Build all run holds or still needs leave two free. A run is let in whole: its first arch takes both runners and its second goes straight through, since letting arches in one at a time deadlocks with every runner held by a run waiting for its other arch. Push, nightly and manual runs reserve two runners until their macOS work is done, and a pull request run keeps its two until its universal build and tests finish, so jobs that never pass the line are accounted for. Queued jobs count as busy. API errors and a four-hour limit let the build in rather than block it. Co-authored-by: raistlin7447 <kris.austin@gmail.com> * Count Pending Runs That Hold macOS Runners Co-authored-by: raistlin7447 <kris.austin@gmail.com> * Show the macOS Admission Checks Live in the Job Log Co-authored-by: raistlin7447 <kris.austin@gmail.com> * Let a Labelled Pull Request Go Ahead of the macOS Line A pull request labelled macos-priority when its run starts waits in its own line under the same rule, and the normal line counts every priority run still waiting as holding two runners, so the next free runners go to it. It still waits for runners in use and for main's reserve. Co-authored-by: raistlin7447 <kris.austin@gmail.com> * Let Each macOS Arch In on Its Own Without Holding an Idle Runner Letting a pull request run in whole deadlocks when its second arch reaches the line behind another pull request: the run holds two runners, the pull request in front waits for them, and the second arch never gets to check. Each arch now waits on its own. A run with one arch let in holds a runner only until that arch's app build is done, and a run with both holds two until its universal build and tests finish, so whoever is first in line always gets in eventually. Co-authored-by: raistlin7447 <kris.austin@gmail.com> * Stop Holding Runners for a Pull Request's Universal Build and Tests Holding two runners from a pull request's second arch until its universal build and tests finished kept one idle through the slower arch's build. Replaying the week of 10-03 against the real queue, that idle capacity pushed the median pull request from under three hours to over sixteen. Those jobs take minutes, so they now queue like any other job, and a pull request holds a runner only for an arch that is still building. Each arch then always needs exactly one runner. Co-authored-by: raistlin7447 <kris.austin@gmail.com> * Count Runs That Move Between Status Lists A run is listed as pending while any of its jobs waits in a concurrency group, so it moves to in_progress the moment its last gate leaves the line. A check made in those seconds read the in_progress list before the move and the pending list after it, missed the run, and let in a second arch the limit had no room for. Runs created in the last day are now listed by creation time as well, and the run doing the check is read directly. Co-authored-by: raistlin7447 <kris.austin@gmail.com> * Log API Calls and the Rate Limit Left on Each macOS Admission Check The calls per check grow with the number of active runs, and the token's hourly limit is shared by the whole repository, so each check now logs how many calls it made and what the last response says is left. * Give the macOS Admission Jobs Fixed Names A skipped job is listed under its unevaluated name, so every Linux and Windows leg showed the label expression. Split the waiting job into a normal and a priority job with fixed names, sharing their steps through a YAML anchor. --------- Co-authored-by: raistlin7447 <kris.austin@gmail.com> |
||
|
|
52eeb0f228 | Fix arc fitting with overhang fan control (#16297) | ||
|
|
8145b0792f | Free macOS Disk Space Only When the Build Might Not Fit (#16364) | ||
|
|
2ccd73759f |
Show English context for Speed Dial search matches
Display English action names and breadcrumbs for English-only matches Show English tab titles when tabs match only by their English names |
||
|
|
cab7a7d75f | Translate the web dialogs and pages through the .po catalogs (#16363) | ||
|
|
bd125d32da | Support searching the Speed Dial by English names in any UI language (#16369) | ||
|
|
7369674d8f | Add #pragma once to Headers Without an Include Guard (#16362) | ||
|
|
70da7e89bb | Release the Slice Button's Mouse Capture When a Press Loses Its Release (#16358) | ||
|
|
a671bf605d |
Dismiss the Slice-Plate Popup on Every Path Into the Filament Grouping Dialog (#16359)
#16284 closed the Slice-plate hover popup only when the Slice button opened the filament grouping dialog. The dialog also opens from the plate's filament grouping icon, the preview legend, slicing notification links and the send dialog, and a popup still shown on those paths keeps the mouse capture on macOS and starves the modal dialog of clicks. Dismiss it in FilamentMapDialog::ShowModal() instead, so every path is covered. The popup could also stay up on its own on macOS: an enter event stopped its close timer, and when the timer fired with the cursor inside it returned without re-arming, so without a later leave event the popup never closed. On macOS the timer now keeps running while the popup is shown and closes it once the cursor is outside. The popup also stops and frees its timer when destroyed. |
||
|
|
bae8f64b8b |
Complete and refine Simplified Chinese translations (#16356)
* Complete Simplified Chinese translations * Fix Simplified Chinese 3D view, camera and extruder terms * Use Chinese CAD view names on the navigator cube |
||
|
|
2df07b7ed8 | Verify the TLS Certificate When Downloading a Cloud Plugin (#16253) | ||
|
|
295094a594 |
Pack bed texture and model into printer bundles (#15965)
* feat(preset): pack bed texture and model into printer bundles Export copies bed_custom_texture and bed_custom_model into the .orca_printer zip under bed/. The manifest lists them in printer_asset. Import extracts each file into the bundle folder and rewrites the preset to that path. A missing file is skipped. A path that leaves the bundle directory is rejected. An old bundle with no printer_asset imports as before. * Add includes flagged by clang-tidy misc-include-cleaner * Gate bed asset packing on an export checkbox and key printer_asset per printer The Export Configs checkbox can leave a custom bed out of a shared printer bundle. printer_asset is keyed by each printer_config zip entry, with files under bed/<n>/, so a later multi-printer bundle does not need a second manifest. The unshipped flat printer_asset object is not read. --------- Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> |
||
|
|
210bc927bc | Revert "Add Thai Localization (Educational / Hybrid Translation)" (#16349) | ||
|
|
00cfb5b259 | Refresh translation catalogs and fix xgettext warnings (#16346) | ||
|
|
2bd105b0ce |
Add Thai Localization (Educational / Hybrid Translation) (#15940)
Hi OrcaSlicer Team,I have completed the Thai localization for OrcaSlicer (over 7,000 lines).Please note: This is a "Hybrid Translation" designed specifically for educational purposes. Instead of direct literal translations (which often confuse beginners due to technical jargon), I have included brief explanations of the mechanical and physics principles directly within the UI strings.Example: For "TPU is not supported by AMS", the translation briefly explains why (because the material is soft, rubber-like, and will jam the long tubes).This approach was developed for the EDrobot project in Khon Kaen, Thailand to help STEM students and beginners understand 3D printing concepts immediately without a steep language barrier.[cite: 8] We focus on empowering students to learn robotics and 3D design effectively.[cite: 8] I understand this makes some strings longer than standard localizations, but it has proven incredibly helpful for beginners. I would be honored if you consider adding this to the official release as the Thai language option. I am happy to maintain this file for future updates.Thank you for building such an amazing slicer!Best regards,Chetsit Srichan (EDrobot) |
||
|
|
b81d761605 |
Refresh plate tab after bed type change (#16344)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> |
||
|
|
6922edfc20 |
Fix Anycubic Vyper start G-code hotend wait (#16216)
* Fix Anycubic Vyper start gcode hotend wait * Bump Anycubic profile version --------- Co-authored-by: 陈诗健 <chenshijian@chenshijiandeMacBook-Air.local> Co-authored-by: SoftFever <softfeverever@gmail.com> Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com> |
||
|
|
7ec147603f | Default Bambu profiles to 3MF output (#14767) | ||
|
|
8ee9e5915a | Fix Prusa CORE One pressure advance condition in filament profiles (#16093) | ||
|
|
cd5b786704 |
Add AnkerMake PLA+ filament profiles (Basic, Matte, Metallic, Silk, Glitter) (#16293)
# Description Adds Anker's own AnkerMake PLA+ filament line to the Anker vendor bundle: **Basic, Matte, Metallic, Silk, Glitter**. Until now the bundle only had `Generic PLA+ @Anker`. Each filament follows the existing Anker layout. A `@Anker base` profile (`instantiation: false`, inherits `Generic PLA+ @Anker base`, `filament_vendor: AnkerMake`) holds the settings. An instantiated `@Anker` profile holds only `compatible_printers`. `filament_id` and `setting_id` were minted with `scripts/orca_profile_tool.py generate-id`, and `Anker.json` was re-indexed with `update-index`. The Anker vendor version goes from `02.04.00.05` to `02.04.00.06`. ## Source of the values The values come from the AnkerMake filament profiles bundled with **eufyMake Studio** (Anker's PrusaSlicer 2.6 fork, AGPL-3.0, source at https://github.com/eufymake/eufyMake-PrusaSlicer-Release), in `profiles/Anker-ini/AnkerMake base/base.ini`. OrcaSlicer is also AGPL-3.0. Conversion notes: - PrusaSlicer keys are mapped to their Orca equivalents. For example, `bed_temperature`/`first_layer_bed_temperature` go to `hot_plate_temp*` and `textured_plate_temp*`, `fan_below_layer_time` goes to `fan_cooling_layer_time`, `slowdown_below_layer_time` goes to `slow_down_layer_time`, and `extrusion_multiplier` goes to `filament_flow_ratio`. Only keys that differ from the inherited `Generic PLA+ @Anker base` chain are written. - Where Studio sets retraction but leaves deretraction unset (Metallic, Silk), `filament_deretraction_speed` is set equal to the retraction speed. That matches Prusa's "0 = same as retract" meaning. - **Max volumetric speed is new.** Studio ships `filament_max_volumetric_speed = 0` (uncapped) for these filaments, and Orca has no equivalent. The caps were derived from the peak flow that Studio's own M5C "Fast" process pushes: Basic/Matte/Glitter 22 mm³/s, Metallic 20, Silk 13. Please treat these as the least-validated numbers in the PR. - Studio has pressure advance disabled for these filaments, so no PA values are carried over. - Colour variants are not included because they differ only in colour. `default_filament_colour` uses each family's base colour. - `compatible_printers` follows Studio's `compatible_printers_condition` for each family, limited to the 0.4/0.6 printers that `Generic PLA+ @Anker` covers: - Basic, Glitter: M5, M5 All-Metal, M5C at 0.4 and 0.6 - Silk: 0.4 on all three, plus M5C 0.6 and M5 All-Metal 0.6 (Studio excludes M5 0.6) - Matte, Metallic: 0.4 only (Studio restricts these to 0.4) - Studio also allows Basic on 0.2 nozzles. That variant is not added, because the 0.2 Generic profiles use a separate low-flow cap. # Screenshots/Recordings/Graphs N/A (profile-only change). ## Tests - `scripts/check_profile.sh` (local twin of the CI job, run against the full profile tree with the nightly macOS validator) passes all five checks: `profile_tool`, `validate_system`, `validate_slice`, `validate_filament_subtypes`, `validate_custom` (fixtures v1.9.0 to v2.4.2). - `python3 -m unittest discover -s scripts/tests -t scripts`: OK. - The submitter printed with these filament settings in eufyMake Studio on the M5C's stock brass 0.4 nozzle without issues. This Orca conversion has been loaded and sliced in Orca 2.4.2 (as user presets inheriting the Anker generic PLA+) but not yet printed on brass. The printer now has a different nozzle fitted. [How to Download Pull Requests Artifacts for Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts) 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
d715bb760c |
Let Plugin Windows Be Maximized on X11 (#16335)
wxGTK types every wxDialog as a dialog window, and Mutter offers no maximize for anything but a normal window, so the maximize button and shortcut did nothing on GNOME under X11. Modeless plugin windows are now normal windows, kept off the taskbar as before. Modal ones stay dialogs so the window manager keeps routing focus from the main window to them. |
||
|
|
de5f2cb3b9 |
Expose App, GL, Printer and Project Facts to Plugins (#16324)
* Expose App, GL, Printer and Project Facts to Plugins Plugins that help users file bug reports could only guess these from log lines: the app config is deny-listed, and the project path, a project export and the device list had no API. orca.host gains app_info(), gl_info() and selected_printer(), and Plater gains project_path() and export_3mf_copy(path). The copy export leaves the project's file name, saved state and model unchanged, does not write the signed-in account as the designer, and raises the audit event of open(path, "w") so the plugin gets the same permissions as for writing the file itself. * Remove app_language in Favour of app_info app_info() returns the same language. app_language() was added after the last release, so no released build has it. |
||
|
|
07a12f5fe4 |
Read a local plugin's version from its file header (#16118)
* Read a local plugin's version from its file header read_install_state() let the sidecar's installed_version override the version parsed from the entry file's PEP 723 header. That is right for cloud plugins, whose header may be stale, but a local plugin has no other source of truth, so the UI kept showing the version recorded at first install after the file was edited. For local installs, use the header version. * Keep the cloud installed version of an unsubscribed plugin A cloud plugin kept as local after unsubscribing is rewritten with installed_from=local and no cloud_uuid, which the local-header rule could not tell apart from a real local install. Its installed_version is still the cloud one while the header may lag, so the next scan showed the stale header version. Record the conversion in the install state and exclude those plugins from the header rule. Add tests for a local plugin following its header and for an unsubscribed cloud plugin keeping its cloud version. * Add tests for the version of a local plugin A local plugin's version comes from its entry file header. Cover the case of a plugin edited after install, and a plugin unsubscribed from the cloud, which is now a normal local package. |
||
|
|
ea10c84d6b |
Fix scale and mirror of rotated mirrored objects (#16131)
* Fix decomposition of mirrored transformations computeRotationScaling() may place the mirror on any axis, giving unstable rotation/scale for mirrored matrices. Decompose them by flipping each local axis and choosing the one needing the least rotation (lowest axis on ties). Merge the extract helpers into a single extract_rotation_scale() and simplify contains_skew() to check that the scale is diagonal. set_scaling_factor() now preserves the mirror sign on the scaled axis. Selection::scale_and_translate() uses Transformation's rotation and scale instead of calling computeRotationScaling() directly, so scaling a mirrored instance adds no skew. Add tests for mirrored decomposition, scaling, and skew detection. * Use Eigen polar decomposition for instance scaling Selection::scale_and_translate now splits the instance matrix with computeRotationScaling instead of get_rotation_matrix(). Adds a Model test checking that an instance created from another's offset, scale, rotation and mirror reproduces its matrix (as Add instance and Fill bed do). * tidy |
||
|
|
e5d5fb47ca |
Fix BBL infill retraction reduction default (#16201)
# 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) |
||
|
|
63918b44a8 | Merge branch 'main' into anker-ankermake-pla-plus-filaments | ||
|
|
53c57c94fd |
Fix the 3D view stuttering every few seconds while signed in to Orca Cloud (#16340)
# 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) |
||
|
|
53eeedb642 | Fix the 3D view stuttering every few seconds while signed in to Orca Cloud | ||
|
|
aa14598e32 |
iXex: Parallel Printing Support for IDEX and IQEX Printers (#13086)
## 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 |
||
|
|
cf5f7b778d |
Show OK instead of Yes on notice dialogs (#16332)
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.
|
||
|
|
b14ed3e0c1 |
Multi nozzle UI Fixes / Improvements (#16075)
# New multi nozzle icon for guidance new icon has tooltip for guidance <img width="407" height="77" alt="Screenshot-20261005181353" src="https://github.com/user-attachments/assets/f27bbf79-7570-4f30-ab3c-52fa972c9fa3" /> **Before** <img width="414" height="228" alt="Screenshot-20261002144627" src="https://github.com/user-attachments/assets/7941891a-5a27-478b-a63f-49cf386e430f" /> **After - Moved icon to pre label position** • reduces possibility of wrapping of labels • Aligns labels and improves readability <img width="399" height="239" alt="Screenshot-20261005181511" src="https://github.com/user-attachments/assets/93f6b555-bbec-496a-b708-8c12c3b3185b" /> # Fix > Modified state not applied on tabs **Before - modified colors not applied to control** <img width="751" height="157" alt="Screenshot-20261001004828" src="https://github.com/user-attachments/assets/0c149fce-45e8-498f-8daa-7b54803399e9" /> **After** <img width="752" height="150" alt="Screenshot-20261005181614" src="https://github.com/user-attachments/assets/5a930bb2-6487-4987-b58d-850de6ae6779" /> Applied color as border because changing text color was not readable <img width="159" height="71" alt="Screenshot-20261002152522" src="https://github.com/user-attachments/assets/f694fc8d-c9f6-46d7-9e06-7762003c9f20" /> and applying as background color was too eye catchy <img width="184" height="80" alt="Screenshot-20261002152533" src="https://github.com/user-attachments/assets/2b67c1dd-8977-46ea-88fc-ce57bfad55bd" /> # Usable tabs on Printer settings • Fix > Using long string makes "Motion ability" and "Notes" tab unreachable • Matches T1,T2.. terminology with nozzle section • Makes https://github.com/OrcaSlicer/OrcaSlicer/pull/14737 available to use now **Before** <img width="751" height="126" alt="Screenshot-20261002152643" src="https://github.com/user-attachments/assets/0241d3c4-acc1-491e-ab85-fa60385c5e85" /> **After** <img width="748" height="129" alt="Screenshot-20261002152628" src="https://github.com/user-attachments/assets/c475d6c8-4f0f-47df-aa04-9d894b1c358d" /> No issue with 8 items <img width="748" height="124" alt="Screenshot-20261002153018" src="https://github.com/user-attachments/assets/9a8c4e12-2cdc-4508-b82b-134476589d80" /> # Dynamic Padding • buttons uses dynamic horizontal padding now depends on item count. reduces padding size to reduce scrolling **Before** <img width="416" height="91" alt="Screenshot-20261002153309" src="https://github.com/user-attachments/assets/977b22db-e686-4967-95be-13772d77cefd" /> **After** <img width="403" height="79" alt="Screenshot-20261005181728" src="https://github.com/user-attachments/assets/9aac0d17-0729-408f-9237-62b17236488f" /> **Before** <img width="745" height="153" alt="Screenshot-20261002153659" src="https://github.com/user-attachments/assets/06951759-419a-40fb-8fc2-d93f5b04d6ea" /> **After** <img width="748" height="149" alt="Screenshot-20261005181833" src="https://github.com/user-attachments/assets/9ccec344-1dd0-4b03-ac12-20d298f413c8" /> **After** <img width="756" height="178" alt="Screenshot-20261009144637" src="https://github.com/user-attachments/assets/1343c39e-5cbd-4f9e-b501-df3797579961" /> <img width="761" height="198" alt="Screenshot-20261009144459" src="https://github.com/user-attachments/assets/efb1ef75-a602-4d3a-a17f-9354ed633357" /> <img width="758" height="196" alt="Screenshot-20261009144510" src="https://github.com/user-attachments/assets/df1d88c5-1b6f-4748-a6bd-a96659a83fe0" /> <img width="764" height="190" alt="Screenshot-20261009154210" src="https://github.com/user-attachments/assets/b876ed71-5fa0-47e4-83c8-a33100e0a229" /> # Keeping compact look & Making it more readable • Removed ":" from labels to gain space as much as possible and increased font size a bit but UI didnt allowed bigger font • Matched font sizes of nozzle section while keeping height of items. Previous font was too small. removed "mm" text to gain more space but added it in tooltip **Before-After** <img width="800" height="240" alt="Screenshot-20261002154132" src="https://github.com/user-attachments/assets/bc4ddcb1-2248-4fc7-9492-ab4fb68cb659" /> • Matched font sizes for BBL nozzle selectors **Before-After** <img width="806" height="200" alt="Screenshot-20261002154513" src="https://github.com/user-attachments/assets/b3d38317-8e4d-4c1e-a490-ce4959ed700a" /> <img width="794" height="156" alt="Screenshot-20261002154649" src="https://github.com/user-attachments/assets/00917584-8e1a-44b0-8c0b-79cec30059bf" /> # Layout and Styling fixes • Matched background color of non active buttons with (Global/Objects) switch **Before** <img width="405" height="159" alt="Screenshot-20261002154947" src="https://github.com/user-attachments/assets/8a8adcc8-431a-48be-aabd-a981335b6ab9" /> **After** <img width="407" height="159" alt="Screenshot-20261005182106" src="https://github.com/user-attachments/assets/7f837be3-ce85-49e6-909a-05350b3d8b30" /> # Other effected dialogs <img width="541" height="195" alt="Screenshot-20261002165003" src="https://github.com/user-attachments/assets/109abbc3-d7c0-4384-9b85-d943e2027963" /> <img width="547" height="308" alt="Screenshot-20261002165307" src="https://github.com/user-attachments/assets/951d9a8d-56f9-4659-aebe-77f236e8c838" /> <!-- > 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) |
||
|
|
d59155b27f |
Moonraker: pass print=true in upload — fix Upload & Print race (fixes #14945) (#15032)
* 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 |
||
|
|
640dfad6e9 | List IDEX/IQEX parallel printing in the README's main features | ||
|
|
0a9e7f33e1 | Link the IDEX/IQEX settings to their wiki sections | ||
|
|
74493429fd | Name extruders in the unsaved changes dialog like the printer tabs | ||
|
|
2ede6d1631 |
Arrange every IDEX/IQEX plate inside its own primary zone
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> |
||
|
|
8b5280098f |
Merge upstream main: center of mass markers, adaptive TPMS, texture bake color mixing, post-processing preview fix, GLEW/OpenCSG removal
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e4142db820 |
Let texture bakes create a chosen number of mixed colours (#16320)
# 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) |
||
|
|
d629922060 | Keep the paint highlight off every texture preview between strokes | ||
|
|
4a20168742 | Fix loading a multi-toolhead 3MF that has no filament_self_index (#16331) | ||
|
|
1466c0e57f | Fix rare hang in mcut mesh booleans (#16330) |