Every slider in the texture displacement panel can now be given an exact
number, either by Ctrl+clicking the track or via a small edit icon at
its right end. Both routes reuse ImGui's own temp-input mode.
Each slider also gets a tooltip with its description and its range, and
the panel's `?` popup lists both ways in.
Draft: description to be refined.
## General
Nothing about texture displacement was written to the `.3mf`, so a
painted relief did not survive closing the project.
Everything the user set up is saved now, so a project reopens with the
relief intact and still editable.
### What is saved
- The layer stack: every per-layer setting (depth, midlevel, smoothing,
tiling, projection blend mode, colour) and the per-volume options
- Each layer's texture, as a file in the archive (why? because user
might want to move .3mf to another computer where custom textures are
missing, or he saves .3mf then deletes textures, and returns back to
.3mf)
- The painted region, one mask per layer slot
- The UV work: LSCM seams, island placement, per-vertex overrides
- Editor state: the selected layer and Standard/Pro, so the panel
reopens where it was left
### What is not saved
- The preview mesh. It is a second copy of the model in the file, for
something the app rebuilds quickly on reopen.
- Panel docked/undocked, which goes to the application config instead
## Format
- `<volume id>.json` - the layer stack and the per-volume options
- `<volume id>_<slot>.png` - each layer's texture, referenced from the
JSON through `path_in_3mf`
Paint masks ride along as one per-triangle attribute per slot
(`paint_texture_0..7`), beside
the attributes the existing paint tools already write.
## Compatibility
Both ways:
- an older build ignores what it does not know and still opens the
project, losing only the relief
- a newer build finds nothing in an older project and leaves the volume
as today.
SysInfoDialog, JumpPointSearch and WebUpdatePlugin are in no CMake list on any platform, and nothing includes ConfigExceptions.hpp, wxinit.h or Utils/Profile.hpp. MultiTaskModel holds only empty namespaces. Also drop the empty GUI_App::system_info() and comments that still mention SysInfoDialog.
TriangleMeshDeal.hpp reused Timer.hpp's guard and CloneDialog.hpp reused
SingleChoiceDialog.hpp's, so including both headers of a pair silently
dropped the second one. Use #pragma once instead.
Its inline suggestions rarely apply: clang-tidy adds a missing include
to the include block at the top of the file, which a PR seldom touches,
and GitHub only accepts review suggestions on lines in the PR's diff.
* Add a /bot fix-tidy Command That Pushes clang-tidy's Missing Includes
A maintainer comments /bot fix-tidy on a PR. A read-only job runs
scripts/run_clang_tidy.sh --fix on the PR's merge commit and returns the
patch; a second job, which never runs the PR's code, accepts it only when
it adds nothing but #include lines to C++ files the PR already changes,
then commits it on the PR head and pushes with CLANG_TIDY_BOT_TOKEN.
* Post clang-tidy's Fix as Review Suggestions Instead of Pushing It
A failed clang-tidy check now runs once more with --fix and uploads the
patch as the clang-tidy-fix artifact; the check still fails. /bot
fix-tidy downloads that patch for the PR's latest commit, checks it adds
only includes to C++ files the PR changes, and posts one review: a
one-click suggestion for each include on a line the PR's diff shows, and
the whole patch with a git apply command. Nothing is pushed and no PR
code runs in the bot, so the PR's author can run it too and no token is
needed.
* Make the clang-tidy Fix in Its Own PR-Scoped Workflow on Request
clang_tidy.yml is back to main's version. clang_tidy_fix.yml runs on
every PR push but skips its job on the first attempt; /bot fix-tidy
re-runs it, so run_clang_tidy.sh --fix runs in the PR's own context
with a read-only token and the PR's cache, never main's, and the PR's
author can ask for it. When that re-run finishes, the bot posts the
patch as review suggestions as before. When the clang-tidy step fails,
the bot also comments asking the author to run the script or the
command, edits that comment on later failures, and deletes it once the
check passes.
* Give the clang-tidy Bot Write Access to PRs It Answers
* Tell /bot fix-tidy Users How to Start a Fix Run When There Is None to Re-Run
A project no longer reads texture files from outside its archive, and a
malformed value or layer slot drops only the texture layers instead of
failing the whole load. Copies of an object that share a mesh keep their
own texture paint when saved, and projects without texture paint no
longer use extra memory to load.
# Description
With "Support multi bed types" enabled on a Qidi printer, the
first-layer bed temperature was always taken from the Smooth PEI / High
Temp Plate setting, whichever plate type was selected.
The slicer computes the right value (`first_layer_bed_temperature` in
the G-code footer follows the selected plate), but the machine start
G-code of these presets hardcoded `[hot_plate_temp_initial_layer]`:
- `fdm_qidi_x3_common` (base of X-Max 3, X-Plus 3, X-Smart 3, X-Plus 4,
Q1 Pro)
- `Qidi Q1 Pro 0.4 nozzle`
- `Qidi X-CF Pro 0.4 nozzle`
They now use `[bed_temperature_initial_layer_single]`, which follows the
selected plate type, as most other profiles do. The Qidi bundle version
is bumped to `02.04.00.17`.
With "Support multi bed types" disabled, these printers use the default
plate (Smooth PEI / High Temp Plate), so the temperature is unchanged
there.
# Screenshots/Recordings/Graphs
N/A — profile change only.
## Tests
`python3 scripts/orca_profile_tool.py check` passes.
Sliced on Qidi X-Max 3 0.4 nozzle with "Support multi bed types"
enabled, `0.20mm Standard @Qidi XMax3`, `Generic PLA @Qidi`, with a
different bed temperature set for each plate type (first layer / other
layers).
Before, with first layer set to 50–55 °C: `M190 S54` for every plate
type.
After, a two-layer print:
| Plate type | Set (first / other) | `M190` at start | `M140` at layer 2
|
| --- | --- | --- | --- |
| Cool Plate SuperTack | 51 / 61 | 51 | 61 |
| Cool Plate | 52 / 62 | 52 | 62 |
| Textured Cool Plate | 53 / 63 | 53 | 63 |
| Engineering Plate | 54 / 64 | 54 | 64 |
| Smooth PEI / High Temp Plate | 55 / 65 | 55 | 65 |
| Textured PEI Plate | 56 / 66 | 56 | 66 |
🤖 Generated with [Claude Code](https://claude.com/claude-code)4
On Linux and macOS the app follows the system theme: at startup it
overwrites the saved dark_color_mode with the system appearance. The label
colours and the cached dark mode flag were already set from the saved value
by then, and the later "changed" check reads the mode only after the
overwrite, so it never fires. For that session every dialog that maps its
colours through UpdateDlgDarkUI keeps the old mode, e.g. white dialogs on a
dark main window after switching the desktop to dark, or on a fresh install
on a dark desktop.
Refresh the label colours and the flag when the system theme differs from
the saved mode.
# Description
## Summary:
Adds favorite filament support to the AMS material selection dropdown.
Users can now click a star icon next to a selectable filament in the AMS Materials Setting dialog to mark it as a favorite. Favorited filaments appear in a new `Favorite filaments` section at the top of the dropdown, while all compatible filaments remain available in the `All filaments` section.
This PR also adds generic right-side action icon support to Orca's custom dropdown widget:
- Adds optional action icons to `DropDown::Item`.
- Adds a new `EVT_DROPDOWN_ITEM_ACTION` event for clicks on the action icon.
- Relays the action event through `ComboBox`.
- Adds `ComboBox::RefreshDropdownIfOpen()` so callers can refresh an already-open dropdown without changing normal `ForceDropdownOpen()` behavior.
The AMS favorites implementation:
- Uses existing bundled star assets:
- `score_star_dark` for non-favorited filaments
- `score_star_light` for favorited filaments
- Stores favorite filament IDs in app config under `ams_favorite_filament_ids`.
- Stores stable filament IDs instead of display names
- Uses existing C-style string escaping helpers for persistence.
- Keeps Bambu/RFID/read-only slot behavior unchanged
No new external dependencies.
**Note:** `AMSMaterialsSetting` now uses Orca's custom `ComboBox` on macOS as well, because the favorite-star UI depends on the custom `DropDown` item renderer. I was able to test this on Windows, but macOS/Linux smoke testing is still needed and would be appreciated.
# Screenshots/Recordings:
https://github.com/user-attachments/assets/41e881be-d4de-4a7d-8f99-4297567adef2
<img width="494" height="36" alt="Screenshot 2026-05-30 103432" src="https://github.com/user-attachments/assets/e7c34ed3-db7a-4705-a1cd-4bfe7ac1616a" />
## Tests
Tested on Windows:
- Built OrcaSlicer locally
- Opened the Device tab with Bambu Lab printer with AMS
- Opened the AMS Materials Setting dialog from an editable AMS slot
- Confirmed clicking a star toggles favorite state.
- Star clicks do not select the filament row.
- Dropdown remains open after toggling a favorite.
- Confirmed favorited filaments appear under `Favorite filaments`.
- All compatible filaments remain visible under `All filaments`.
- Confirmed Filament still selected as before
- Confirmed favorites persist after closing and reopening the dialog.
- Confirmed favorites persist after restarting OrcaSlicer.
- Confirmed the favorite IDs are saved in `OrcaSlicer.conf` under `ams_favorite_filament_ids`.
- Bambu/RFID/read-only filament slots continue using the existing read-only display behavior.
Not tested:
- macOS
- Linux/Wayland
These should be tested, `ComboBox` behavior not tested on macOS.
[How to Download Pull Requests Artifacts for Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
Five Plater::TakeSnapshot guards in GUI_ObjectList were unnamed temporaries, so snapshots were allowed again as soon as the snapshot was taken. Naming them keeps the suppression until the edit is done, as everywhere else.
The importer assigned pick_file.empty() and pattern_bbox_file.empty(), a bool, to the string fields, storing a one-character string instead of the path.
Every slider in the texture displacement panel now takes an exact number,
either by Ctrl+clicking the track or through a small edit icon at its right
end. Both routes end in ImGui's own temp-input mode: the icon only asks ImGui
to focus the slider, which is what turns it into an input box, so the parsing,
the clamping and the Enter/click-away behaviour stay ImGui's rather than
becoming a second implementation.
The mechanism lives in one helper, typed_row(), with a float and an int form
over it. float_row() and int_row() are thin wrappers on those, and the six
sliders that previously built their widget inline (brush size, smart fill
angle, edge fade amount, remesh sharp angle, refine resolution and the uniform
triangle budget) now go through the same path.
Each slider carries a tooltip with its description and its range. The range is
derived from the bounds and the format string the slider is already given, so
no row states them twice and the tooltip cannot drift from the track. The
tooltip allows hovering while disabled, since several rows are drawn inside
disabled_begin(), where a greyed slider is exactly when the explanation is
wanted.
The panel's "?" popup gains an entry for each of the two ways in, appended to
every tool's shortcut list and shown on their own when no painting tool is
selected, where that popup was previously empty.
Clearing per-export state called unique_ptr::release(), which gives up ownership without deleting. Every export uses a new GCode, so the pointer is still empty there today and nothing leaks; reset() states the intent and would free the object if a GCode were ever reused.
* 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>
#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.
* Complete Simplified Chinese translations
* Fix Simplified Chinese 3D view, camera and extruder terms
* Use Chinese CAD view names on the navigator cube
* 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>
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)
The start G-code of fdm_qidi_x3_common, Qidi Q1 Pro 0.4 nozzle and
Qidi X-CF Pro 0.4 nozzle heated the bed to hot_plate_temp_initial_layer,
so with "Support multi bed types" enabled every plate type got the
Smooth PEI / High Temp Plate temperature. Use
bed_temperature_initial_layer_single, which follows the selected plate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# 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)
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.
* 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.
* 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.