# Description
<!--
> Please provide a summary of the changes made in this PR. Include
details such as:
> * What issue does this PR address or fix?
> * What new features or enhancements does this PR introduce?
> * Are there any breaking changes or dependencies that need to be
considered?
-->
Windows traditionally limits many file operations to `MAX_PATH` (260
characters). Deeply nested plugin packages can exceed this limit during
`.whl` extraction, leaving files missing and causing the plugin to fail
to load.
This change adds a shared extended_path helper that prepares paths for
Windows file operations using the extended-length prefixes `\\?\` or
`\\?\UNC\`. The helper is used for ZIP archive I/O and extraction
destinations, including plugin wheel extraction. This avoids relying on
the Windows long-path setting, which changes machine-wide settings.
Paths remain unprefixed outside file operations. Code that bypasses the
shared ZIP helpers may still require separate long-path handling.
See [Microsoft's documentation on path length
limitation](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry)
# 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.
-->
Tested on Windows 11 by using a portable installation of OrcaSlicer and
placing it in some really long filepath length e.g.
`C:/somename/<240 characters long folder path>/OrcaSlicer` and then
running and trying to install any plugin locally or on OrcaCloud.
<!--
> A guide for users on how to download the artifacts from this PR.
-->
[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
# Description
Opening the texture picker decoded every texture in the expanded
sections on the GUI thread in a single frame, each one twice at full
size. This stalled the UI on large libraries, and the full-size decodes
stayed in memory afterwards.
- Picker thumbnails are loaded lazily: only tiles inside the visible
scroll area are considered, at most 2 uncached decodes per frame, and
the rest fill in over the following frames. Deferred tiles draw as a
placeholder.
- Those follow-up frames are requested with `request_extra_frame()`.
Marking the canvas dirty from inside the render did not schedule another
idle pass, so thumbnails waited for the next mouse event.
- The selected texture is still centred on open, even when it is off
screen. The scroll reveal runs before the visibility check so that tile
is loaded on the next frame.
- Library validation no longer decodes pixels. The new
`png::is_decodable_8bit_png()` reads only the PNG header and accepts
8-bit grey or RGB/RGBA, which is what `decode_png()` and
`decode_colored_png()` take.
- Picker thumbnails are decoded without entering the decode cache
(`decode_height_texture(layer, keep_decoded = false)`). Only the 128 px
thumbnail is kept per library entry; the full-size decode is made
transiently and freed.
Tradeoff: the header-only check does not read pixel data, so a PNG with
a valid header but corrupt or truncated pixel data now passes validation
and fails when the texture is loaded. The previous full decode caught
that case.
Each thumbnail decode still runs at full size on the GUI thread, one at
a time. Peak memory per decode is unchanged; a streaming downscale would
be needed to reduce it.
# Screenshots/Recordings/Graphs
Old:
https://github.com/user-attachments/assets/03038532-396f-48c2-898c-aa06ef83e5f5
New:
https://github.com/user-attachments/assets/49fdc31a-ce1b-470a-88a7-4c6319f03b81
## Tests
Not yet run. Suggested checks:
- Open the texture picker with a library of many textures, and do not
move the mouse: tiles should fill in over a few frames without a visible
stall.
- Open the picker with a layer whose texture is far down the list: it
should be centred and loaded.
- Import a valid 8-bit grey and an RGB PNG, plus an unsupported one
(16-bit, palette, or grey-alpha): only the supported ones should be
accepted as-is, and the rest converted on load.
- Watch memory while scrolling through a large colour library: it should
not grow by the full decoded size of every texture shown.
<!--
> 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)
Marking the canvas dirty from inside the render does not schedule another
idle pass, so deferred thumbnails waited for the next mouse event. Asking
for an extra frame does.
The thumbnail is downscaled on upload, but the full decode it was made
from stayed in the decode cache for as long as the library entry lived.
Thumbnail decodes now skip the cache, so only the 128 px thumbnail is
kept per library texture.
* Fix TabCtrl clipping titles and indicator dots
Button measures a label plus icon without the icon gap that render draws,
so icon tabs were ellipsized and the indicator dot was cut off. Widen the
horizontal padding so the measured width covers the gap.
* Re-layout publish dialog when extruder switch labels change
Setting the indicator bullet on a switch option changed its width without
laying out the page, leaving the switch distorted until the next layout.
With ooze prevention on, an extruder that has no layers left is set to 0 °C when it is parked, instead of its standby temperature. On IMEX printers where several filaments share one heater, the heater is only turned off once every filament on it is done.
The clearance preview shown while dragging sorted instances with a comparator that is not a strict weak ordering, which aborts with libc++ debug hardening. The preview now uses object-list order, with each object's copies in their last validated print order, which is the order print validation uses. The height check is now in SequentialPrintClearance.hpp, with unit tests.
Opening the texture picker decoded every texture in the expanded sections
on the GUI thread in one frame, each one twice at full size. Only the
visible tiles now load, at most a couple per frame, and the rest fill in
over the following frames.
The library validation no longer decodes the pixels: a header-only check,
is_decodable_8bit_png(), accepts exactly what the two decoders take.
# Description
Adds a fifth Python plugin capability type, `ResourceTexture`, which
contributes
height-map textures to the Texture Displacement picker. A plugin returns
a
directory of images; they appear as selectable textures in the picker,
grouped
under one collapsible section per pack. Disabling or uninstalling a pack
removes
them again, and projects that already use one of its textures keep
working,
because a layer holds its own image bytes.
**The plugin-facing contract** is one method:
```python
class WoodPack(orca.resource_texture.ResourceTexturePluginCapabilityBase):
def get_name(self):
return "Wood Pack"
def get_texture_dir(self):
return os.path.join(os.path.dirname(__file__), "textures")
```
The host scans one non-recursive level of that directory, accepting the
same
formats a hand-picked image already accepts (`.png/.jpg/.jpeg/.bmp`),
and decodes
them through the same `load_texture_image_data()` the picker uses for
the user's
own images, so a pack texture and a hand-picked one behave identically.
**Distribution is `.whl` only for v1.** `install_plugin` copies exactly
one
file, so a `.py` plugin cannot ship sibling images; a wheel is a single
file and
the loader already extracts it on demand.
**Files of interest**
- `src/slic3r/plugin/pluginTypes/resourceTexture/` — the capability, its
trampoline and its pybind base, mirroring the Pages type.
- `src/slic3r/plugin/host/PluginResourceTextures.{hpp,cpp}` — host
registry,
free of wx and GUI includes so it can sit beside `PluginManager`.
- `src/slic3r/GUI/TextureLibrary.{hpp,cpp}` — the third texture source,
the
`pack_name` provenance on an entry, and a generation counter.
- `src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp` — the picker.
- `src/slic3r/plugin/PluginFsUtils.{hpp,cpp}` —
`collect_resource_texture_files()`.
**Behaviour change worth a reviewer's attention:** the picker popup is
now a
fixed size that scrolls rather than sizing itself to its content, and it
gains
one collapsible section per pack. The sizing applies to every user, not
only
those with packs installed. With no packs installed the picker otherwise
behaves
exactly as before.
**Not in scope for v1:** folder or `.zip` install for non-programmers,
and
preset-referenceable pack textures (which would need a plugin-backed
`ConfigOptionDef::plugin_type`).
# Screenshots/Recordings/Graphs
https://github.com/user-attachments/assets/b96787cf-b475-4698-826d-5db8b9f386f1
## Tests
New suite `tests/slic3rutils/test_plugin_resource_texture.cpp`, 19
cases:
- Validator: a file inside the root is listed; a parent-directory escape
and an
absolute directory outside the root are refused; a symlink pointing
outside is
skipped; files in a subdirectory are not listed; non-regular entries are
skipped; the extension whitelist is case-insensitive and rejects
near-misses;
the listing stops at 64 files; a missing directory yields an error.
- Listing and lifecycle: a pack's images appear and clear on disable and
unload;
an empty pack contributes nothing; two distinctly named packs each stay
one
contiguous run; a second pack declaring a loaded capability name is
refused;
reinstalling with different bytes at the same path changes the decoded
content.
- Hostile overrides: `get_texture_dir()` returning `data_dir()` or
`resources_dir()` is refused and the pack loads with no textures; a
raising
override still loads the pack.
- Decoding: PNG and BMP from a pack decode through the same path as the
same
files picked by hand.
- Persistence: a project keeps a pack texture after the pack is
uninstalled,
bytes unchanged across a re-save.
- Wheel metadata: a minimal resource-texture wheel is accepted.
Known gaps, for transparency: the wheel path is covered only at the
metadata
gate, not through a full extraction-and-import lifecycle; there is no
test for a
registry callback after interpreter shutdown, as there is no seam for it
without
reaching into `src/`; and `.jpg` decode parity is covered by the listing
rather
than by a real fixture, since no JPEG encoder is available to synthesise
one.
Manually exercised in-app on Linux with a locally built pack wheel (five
colour
PNGs): install, enable, textures listed under the pack's own section,
then
disable and uninstall remove them. The new suite itself has not been run
locally — CI is the first execution.
* Linux: only disable WebKit compositing for WebKitGTK older than 2.42
The default Wayland path set WEBKIT_DISABLE_COMPOSITING_MODE=1 whenever the
session had both DISPLAY and WAYLAND_DISPLAY, i.e. any Wayland session with
XWayland. That forces WebKitGTK accelerated compositing off and also disables
threaded scrolling, so the Device tab web view (Mainsail/Fluidd) lagged unless
the app was launched with GDK_BACKEND=x11, the only path that skipped the
variable. Disabling compositing also turns off threaded scrolling.
The XWayland compositing bug the fallback worked around is fixed in WebKitGTK
>= 2.42, so apply it only for older WebKit. WebView::NeedsCompositingWorkaround()
reads the runtime WebKitGTK version; the X11 opt-in path is unchanged.
Behavior is unchanged on WebKit < 2.42, on pure X11, and on native Wayland
without XWayland. The wxWidgets skill reference is updated to match.
* Linux: use WebKit's shared-memory transport for the NVIDIA DMABUF renderer
The Device tab web view is slow on NVIDIA proprietary drivers even with
accelerated compositing left on: since the DMABUF renderer became the
default in WebKitGTK 2.42, the driver cannot back it, and web views fall
back to a slow path. Gating the old WEBKIT_DISABLE_COMPOSITING_MODE
fallback on WebKit < 2.42 is the wrong axis, because the failure is
version-independent from 2.42 on.
Force the shared-memory transport with WEBKIT_DMABUF_RENDERER_FORCE_SHM=1.
That keeps compositing and threaded scrolling and skips only the hardware
path. Also set WEBKIT_FORCE_DMABUF_RENDERER=1, which clears the isNVIDIA()
early return that Debian/Ubuntu/openSUSE patch into
rendererBufferTransportMode() and which otherwise returns before the
FORCE_SHM check is reached.
Both are set before the X11-vs-Wayland split so the workaround also covers
XWayland, and only when WebKitGTK is new enough to honour FORCE_SHM
(>= 2.44, WebView::SupportsForceSharedMemory()).
The AppImage launcher exported the superseded
WEBKIT_DISABLE_DMABUF_RENDERER=1, which is read first, leaves the transport
set empty and so disables compositing outright; replace it with the same
pair. The wxWidgets skill reference is updated to match.
* Linux: drop the AppImage WebKit DMABUF exports; correct the skill notes
The launcher cannot gate on the runtime WebKitGTK version, so exporting
WEBKIT_DMABUF_RENDERER_FORCE_SHM/WEBKIT_FORCE_DMABUF_RENDERER there would
clear a distro isNVIDIA() guard on hosts older than 2.44, where FORCE_SHM
is ignored, re-exposing the broken hardware path. The binary already sets
both with the correct gate, so remove them and say who owns the workaround.
Also correct two claims in the skill reference. An empty transport set only
means "no accelerated compositing" from 2.44 on; 2.42/2.43 still fall back
to the Wayland/X11 renderers, removed in the same release that added
FORCE_SHM. And threaded scrolling is off by default in 2.44/2.45 (on from
2.46), so FORCE_SHM keeps compositing rather than scrolling. Document the
module-level detection cost and the WEBKIT_DMABUF_RENDERER_FORCE_SHM=0
opt-out.
* Flatpak: force WebKit's shared-memory transport instead of disabling DMABUF
The entrypoint exported WEBKIT_DISABLE_DMABUF_RENDERER=1 for NVIDIA. WebKit
reads that variable first and leaves the transport set empty, which turns
accelerated compositing off, so the Device tab still lagged for NVIDIA
Flatpak users, and the binary could not override it because its setenv is
non-replacing.
The runtime's WebKitGTK is vanilla, so there is no distro isNVIDIA() guard
to clear and WEBKIT_DMABUF_RENDERER_FORCE_SHM=1 alone is the right switch:
compositing stays on and only the hardware DMABUF path is skipped.
* Flatpak: drop the WebKit NVIDIA export; the binary owns the workaround
/proc/driver/nvidia/version is readable inside the Flatpak sandbox, so the
binary's NVIDIA branch already sets WEBKIT_DMABUF_RENDERER_FORCE_SHM with
the correct runtime gate and the entrypoint's own export is redundant.
Verified with:
flatpak run --command=cat com.orcaslicer.OrcaSlicer /proc/driver/nvidia/version
which prints the NVRM version (driver 615.71.09) from inside the sandbox.
* Linux: say what the XWayland compositing gate actually tests
DISPLAY and WAYLAND_DISPLAY are both set in a native Wayland session as
well, because GNOME and KDE run Xwayland as part of the session and export
DISPLAY for it whether or not this process is an X client. The pair means
XWayland is available in the session, not that GTK picked X11 for this
process, so "(an XWayland session)" reads as more than the condition
establishes.
Say what the pair means, and name the case the fallback actually covers:
the X11 backend inside a Wayland session, which is what the forced
GDK_BACKEND=x11 above produces in a build without EGL.
Comment only; behaviour is unchanged.
* Linux: apply the XWayland compositing fallback only where GTK picks X11
The default path set WEBKIT_DISABLE_COMPOSITING_MODE whenever DISPLAY and
WAYLAND_DISPLAY were both set, but a native Wayland app inherits both, so a
build with EGL disabled accelerated compositing — and with it threaded
scrolling — while running on Wayland, where the XWayland bug does not apply.
The only default-path case that really runs through XWayland is the forced
GDK_BACKEND=x11 used when wx lacks EGL, so set the variable there. The
backend cannot be re-checked later: that branch exports GDK_BACKEND itself,
so the force and a user's GDK_BACKEND=x11 opt-in, which deliberately keeps
compositing, would be indistinguishable.
Behaviour is unchanged for WebKitGTK >= 2.42, where the gate already skipped
the variable, and for builds without EGL. A build with EGL on WebKitGTK <
2.42 in a Wayland session no longer loses compositing and threaded scrolling.
The wxWidgets skill reference is updated to match.
---------
Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
* Fix unreadable text in wiki and video guide tooltips
* Use a keyboard icon for the gizmo shortcut button
* Fix blank tooltips on the Move and Scale gizmo combo boxes
* Fix external spool gradient and multicolor rendering
* Display AMS gradients and multicolors in color popup
* Preserve AMS gradient selection through color popup
* Match popup selections by full filament color data
* Align AMS Lite gradient geometry with BambuStudio
* Align AMS color palette with BambuStudio presets
* Match Bambu circular color rendering without background masks
* Preserve AMS gradient colors when applying filament settings
* Match AMS preview gradient swatches to the solid color shape
The texture picker's Download from OrcaCloud shortcut asks for a specific page. Re-running the
session handoff to reach it flashed the sign-in interstitial every time; instead an already-open
pane is navigated straight to the page URL, keeping its session. The handoff is still used on the
first open, where a session has to be established.
set_cloud_mode() gains an optional page_url for this. The homepage nav passes none, so its ordinary
re-open keeps the page the pane already holds (guide §8/§10).
* Let Z contouring slice objects that have several instances
The contouring step refused any object with more than one instance. The
instances of a PrintObject differ only by their XY shift, so the mesh is now
placed with the first instance of the PrintObject being contoured.
* Say in the Z contouring comment how instances map to print objects
* Check that Z contouring follows each instance as it is turned
The texture picker lists every plugin pack as an indented, collapsible child of one
"Resource Texture" section, which now always renders so the new button is reachable even
before any pack is installed. A pack whose name carries a " by ..." suffix shows that part
on a smaller, dimmed second line, keeping the name line short.
The button switches to the Home tab and opens the OrcaCloud pane on the plugin hub filtered
to resource textures. A native open now also drives the homepage shell (nav highlight and the
cloud-mode layout), and the requested page is remembered so a sign-in re-seed keeps it.
Also fixes the PluginsDialog fuzzy-search script path, which pointed at a directory the file
is not in.
Space opened the Speed Dial and Ctrl+P the Preferences while the text tool's
box or a sketch value field was being typed into. Those fields are ImGui drawn
on the GL canvas, so the canvas is the focused wx window and focus_keeps_space()
cannot see them; the CHAR_HOOK handler took the chord first and the field never
saw the key.
Skip the global shortcut dispatch while ImGui reports that one of its items owns
the keyboard and the focused window is not a wxControl.
# Description
Adds School House Spools PLA and PETG to the global Orca Filament
Library. Both are standard PLA and PETG, so Bambu PLA Basic / PETG Basic
served as the baseline for tuning values. The one intentional difference
is PETG nozzle temperature (252°C vs Bambu's inherited 255°C default),
based on our own testing.
# Screenshots/Recordings/Graphs
N/A
## Tests
Ran scripts/assign_vendor_setting_ids.py to generate setting_id values,
and scripts/orca_extra_profile_check.py --vendor="OrcaFilamentLibrary"
--check-filaments --check-materials to validate. No new errors or
warnings introduced (pre-existing unrelated errors in other vendors'
profiles were not touched).
[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
* Define validated color picker selection contract
* Integrate web color picker with gradient and alpha controls
Co-authored-by: yw4z <ywsyildiz@gmail.com>
* Apply shared Orca theme to web color picker
* Host color picker in a validated WebView dialog
* Persist color picker favorites in user preferences
* Integrate localized web color picker with filament actions
* Add color picker browser regressions and verify direct includes
* Fit color picker content in a borderless floating panel
* Anchor color picker panels to their triggering controls
* Use accent foreground for selected color picker switches
* Integrate web color picker into generic color fields
* Use web color picker for AMS custom colors
* Use shared web color picker from Linux sidebar menu
* Use web color picker when adding texture import material
* Preserve alpha in filament color picker actions
* Clean up color picker asset whitespace
* Use application translations for color picker validation
* Preserve existing formatting in color picker integration
* Match SpeedDial rounded window handling on macOS
* Align color picker host behavior with SpeedDial
* Consolidate color picker implementation into its dialog
* Remove native color picker fallback
* Limit color picker interfaces to production consumers
* Use shared WebView translation field name
* Reapply anchored color picker placement after GTK show
* update
* fix increment buttons not working on spin inputs
* Use thumb color from its own channel
* init gradients with shifted color for second color
* fix hsl sliders cannot move freely and lock downs other one
* unify save button and color preview & increase stored color count to 32
* switch to hsl based calculation and fix plot and slider drag events
* update
* add support for multi colored gradients
* Update test_filament_color_picker.cpp
* add filtering color palette and store sidebar and combo state
* open dialog with calculated size
* apply shadow to dialog on windows
* add tooltip for gradient add color button
* Restore draggable fixed-size color picker frame
* Fix color picker sidebar sizing in WebKit
* Fix invisible default filament color button on macOS
* Remove duplicated color defs for color picker
* Remove duplicate HTML color picker frame
* Match color picker tests to current theme roles
* update icons
* use focus color on background of filter box
* align gradient switch with hex input
* fix initial window sizing
* Preserve caller color order when drawing filament icons
---------
Co-authored-by: yw4z <ywsyildiz@gmail.com>
The wizard's loading page waits for HandleStudio to be called with
userguide_profile_load_finish and jumps to its target page on it; if the
call never arrives, a fallback timer in the page jumps after 180 s.
GuideFrame started the profile load from the first wxEVT_WEBVIEW_NAVIGATED
event and ran HandleStudio in the page as soon as the data was ready. That
event is the navigation's commit, before the page's scripts have run, and
with the wizard's derived cache the data is ready about 100 ms later.
Since the web view is created with an empty URL so the translated strings
can be injected before the first load, the first navigated event is the
commit of about:blank, so the data is ready before the real page has even
begun loading and the call reaches a page without HandleStudio. WebKit
drops the error and nothing retries, so on macOS the wizard often sits on
"Loading..." for three minutes before the printer list appears.
The loading page now reports in from its OnInit, the way the other wizard
pages request what they need, and the host sends the message once both
the data is ready and the page has reported; every instance of the page
reports again, so one that replaced an earlier one gets its own message.
Fix Move gizmo alignment icon scaling
Apply the native Windows DPI factor when sizing Move gizmo alignment icons.
This matches the sizing path used by other 16 px gizmo controls,
including support paint, seam, fuzzy-skin, and MMU painting tools.
`ninja-build` and `webkit2gtk4-devel` no longer exist on Tumbleweed. Use `ninja`, and use `webkitgtk3-devel` for WebKit. It is the GTK3 build and ships the `webkit2gtk-4.1.pc` that `src/slic3r/CMakeLists.txt` requires.
The `; first_layer_bed_temperature` and `; first_layer_temperature` compatibility lines were always taken from filament 1. Anycubic's GoKlipper reads the bed line to heat the bed, so a plate printed only with another filament got the wrong temperature.
The bed line now follows `bed_temperature_formula` (highest used filament or first printing filament). The nozzle line uses the first printing filament. Both values are computed before the by-object loop.
* Remove unused CMake modules
FindAlienWx.cmake and FindPerlEmbed.cmake came over from the Slic3r Perl
build. Nothing calls find_package(AlienWx) or find_package(PerlEmbed).
LibFindMacros.cmake was only included by those two modules.
bin2h.cmake lost its last caller when the avrdude sources were removed
in e6fcbfae07.
* ci: build changes under cmake/
The Build all path filters did not include cmake/, so a change to a
find module or to ClangClShowIncludes.cmake ran no build on a pull
request or on a push to main.
* fix: pin the Python that builds CPython on Windows
CPython's build needs a Python to run its own build scripts. Without a local
Python 3.10-3.13, find_python.bat installs the newest pythonx86 from nuget.
That became 3.15.0 on 2026-10-09, and 3.15 removed
SourceFileLoader.load_module, which 3.12's makeopcodetargets.py calls:
AttributeError: 'SourceFileLoader' object has no attribute 'load_module'
Download Python 3.13.16 from nuget.org as its own dependency, with a hash
(pythonarm64 on ARM64 hosts, pythonx86 elsewhere), and pass it to PCbuild as
PYTHON, which find_python.bat takes before any other Python. PYTHONHOME and
PYTHONPATH are dropped from that step, since PCbuild runs the scripts
without -E. DEP_PYTHON_HOST points at an existing Python 3.10-3.13 instead,
for machines that cannot reach nuget.org.
Drop 01-windows-nuget.patch. It fixed the dead nuget.exe download link in
find_python.bat, and that fallback is never reached now. Existing deps trees
keep working: with no patch command, ExternalProject skips the patch step
instead of re-applying the patch to patched sources.
* fetch externals separately
---------
Co-authored-by: yw4z <ywsyildiz@gmail.com>
* Option: Show/ Hide inactive plates
* Hide inactive plate volumes by bounds
Switch inactive-plate volume hiding to use geometric overlap with the current plate bounding box instead of object and wipe tower ID mapping. This also guards against a missing current plate and keeps any active volume visible when its transformed bounds overlap the current plate footprint.
* Hide inactive plates per instance and keep them unpickable
Decide what is shown per object instance, from its footprint over the
current plate, so the parts of an object are never split; a selected
instance stays visible.
Keep the hidden state between frames instead of restoring it after each
one. GLVolume::on_hidden_plate records what this update hid, so it only
shows that again and leaves the volumes gizmos hide alone, and the raycasts
made while handling the mouse see the same scene as the frame. Nothing runs
while the inactive plates are shown.
Deactivate the raycasters of the other plates, whose icons still took
clicks where nothing was drawn, move the axes to the current plate, and
limit the option to Prepare, as Preview already shows the current plate
alone.
* Follow the selected plate with the camera while the others are hidden
Selecting a plate in Prepare while the inactive plates are hidden moves
the camera target over it and keeps the angle, distance and height, so
the plate comes into view where the previous one was. A zoom to all
plates, as after loading a project or adding a plate, frames the current
plate instead.
* Add a compact plate switcher while inactive plates are hidden
The hidden plates can no longer be clicked, and the object list gets
crowded on large projects. While they are hidden in Prepare, a column of
numbered buttons in the top left corner, a compact counterpart of the
Preview plate toolbar, selects the plate to show. It highlights the
current plate, dims the empty ones and gives the plate name in the
tooltip.
* Simplify the inactive plates update and the plate switcher layout
Decide and apply each volume's state in one pass instead of filling a
per-volume vector first, and hoist the switcher's loop-invariant colors.
Keep the height the bottom-left corner takes next to the constants it is
made of, with the canvas toolbar's button spacing named, rather than
repeating that layout in the switcher.
* Rename the option to Focus mode
The canvas menu entry reads Focus mode, lit while the other plates are
hidden, and is off by default, so nothing changes until it is turned on.
The app config key is focus_mode. Turning it on zooms to the current
plate, so the plate fills the view.
* Show plate thumbnails in the plate switcher
The switcher shows the thumbnails of the Preview plate toolbar instead of
numbered buttons, with rounded corners concentric with the window's.
Plater::plate_toolbar_image_version() lets it refresh them without
clearing the Preview toolbar's dirty flag.
* Don't draw the sinking contours of inactive volumes
GLVolume::render() skips an inactive volume, but its sinking contour was
still drawn, so an object on a hidden plate left its outline behind.
* Show the plate switcher's scrollbar only on hover, as in Preview
The plate switcher now shares the Preview plate toolbar's scrollbar: the
same width and colors, shown only while the mouse is over the column or
drags the bar, with the mouse wheel scrolling it either way, and tighter
window corners while it shows. Both toolbars draw it through the same
helpers, so they cannot drift apart; the Preview toolbar behaves as before.
Drop the frame style the switcher no longer uses since it draws
thumbnails instead of buttons.
* Make the plate switcher's thumbnails cheaper and match the Preview toolbar
The thumbnail version moves after almost every edit, and the switcher
redrew both thumbnails of every plate each time. It now redraws only the
current plate's, the only one edited while the others are hidden, and the
other plates' once their thumbnails are invalidated, through the new
Plater::update_plate_thumbnails().
IMToolbarItem::set_thumbnail() uploads a plate thumbnail without keeping
a copy of its pixels, a megabyte per plate, and replaces the item setup
the Preview toolbar and the switcher each repeated. The switcher's hover
border takes the Preview toolbar's color.
---------
Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
* Export G-code statistics as JSON
Add an export of a plate's G-code statistics for scripts and
spreadsheets that compare prints or settings: print time, travel,
filament and feature totals, the same per layer, and the filament of
each object instance and its supports. The times are the preview's
estimates, summed the way the preview sums them.
The GUI writes <name>.statistics.json next to an exported G-code when
"Export statistics with G-code" is enabled in Preferences (Control >
Slicing). The CLI writes plate_N.statistics.json to the output directory
with --export-statistics, and exits with -25 when it cannot.
The per-instance masses now carry the object's name and the instance's
index, so the export can name them. The vertices the processor adds
inside a move for the actual speed view repeat the move's extrusion, so
the filament sums skip them.
* Move the statistics export preference to the Developer tab
"Export statistics with G-code" now sits in Developer > Settings
instead of Control > Slicing.
* Store the G-code statistics inside exported 3MF files
With the preference on, an exported sliced 3MF file holds each plate's
statistics as Metadata/plate_N.statistics.json, next to its G-code. The
print job sent to a printer and the autosave backup leave them out.
The CLI does the same with --export-3mf, and writes the separate file
only next to the G-code in --outputdir, as without it the G-code is a
temporary file. The file is written in binary mode, so it reads the
same on every platform and matches the copy in the 3MF.
* Address review of the statistics export
- Number each object instance by its position among its model object's
copies. A copy of another rotation or scale, and every copy on a belt
printer, slices as a print object of its own, where it was always
instance 0; a copy after a non-printable one shifted too.
- Take the filament totals from the processor's statistics, which count
the flush as the preview's filament table does, and list each
filament's model, support, prime tower and flush use. The summary's
cost is now filament_cost, as the preview's cost adds the time cost.
- State when the version goes up.
- Drop the preference's default, which get_bool already gives.
Object and copy ids were only assigned in set_object_info(), which runs only for Klipper, Marlin and RRF with Exclude objects on. Every other printer, Bambu included, printed an uninitialized object id and labeled every copy as copy 0. The numbering now runs on every export, so the labels are stable and unique on all printers. Output for Klipper, Marlin and RRF with Exclude objects on is unchanged.
The label test covers Marlin, Klipper and Repetier, plus a BBL row that checks each copy gets its own M624/M625 skip block.
Co-authored-by: raistlin7447 <kris.austin@gmail.com>
When a CLI export rendered the no-light thumbnail but no plate thumbnail, the no-light loop set the plate thumbnail's status flag. That skipped the fallback that copies the plate thumbnail from disk, so re-exporting a 3mf that has plate_N.png but no plate_no_light_N.png left plate_N.png out of the package. The loop now sets no_light_thumbnail_status instead.
# Description
Follow-up to #13373, which added Align/Distribute to the object context
menu. This fixes the problems found in its review and brings
BambuStudio's align and distribute controls to the Move gizmo.
Align and distribute now work when all copies of an object are selected,
on parts of rotated, scaled or mirrored objects, and on objects with
several copies, whose other copies used to lose the part move. Aligning
a single object arranges its solid parts and leaves modifiers and
negative volumes in place. Moves in Z are offered only when Auto-Drop
would not undo them. Commands that would do nothing are disabled, each
operation is a single undo step, and other plates keep their slicing
result. The same fix stops the automatic drop to the bed after a move,
rotate, scale or mirror from also moving copies of other selected
objects.
The Move gizmo gets BambuStudio's align row. It aligns or distributes
the selected objects or parts, or aligns them to their object or to the
plate, and previews the target while you hover an icon. While the Move
gizmo is open, Ctrl+click adds objects to the selection, and Alt+click
adds or removes parts of the selected object.
No change to slicing output or project files.
# Screenshots/Recordings/Graphs
https://github.com/user-attachments/assets/44c308c3-faff-4f51-a3b7-88f264f03743
## 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)
Print::apply() decided the shrinkage compensation from the existing PrintObjects and their regions. On the first apply() they do not exist yet, so the first slice was not scaled. The CLI calls apply() only once, so filament_shrink and filament_shrinkage_compensation_z had no effect there.
The compensation is now decided before the PrintObjects are built, from the filaments the model uses, and stored in Print. When the filaments have different shrinkage values, every filament assigned to a feature, height range, modifier, support, tool change or the wipe tower counts, even if it does not end up printing. If they disagree, compensation stays off with the existing warning.
Supports, seam, fuzzy skin and texture displacement loaded paint data with the default max_ebt, so a 3mf with a paint state above what the gizmo paints read past the 3-entry color table. Each gizmo now passes its highest paint state as max_ebt, as the MMU gizmo already does, and higher states load as unpainted.
has_restore_data() signals "this backup is in use, leave it alone" by setting
origin to "<lock>". Its caller treats anything else as permission to delete
(Plater.cpp, the EVT_RESTORE_PROJECT handler):
try {
if (originfile != "<lock>") // see bbs_3mf.cpp for lock detail
boost::filesystem::remove_all(last);
}
catch (...) {}
So every "return false" that leaves origin empty is a delete instruction, and
three of them fired without anyone having established that the backup was
spent.
lock.txt exists but cannot be read. load_string_file() was outside the try, so
the exception left has_restore_data() altogether. The handler is queued and has
no try around the call, so this took the application down. Fixes#15670.
lock.txt reads but holds no pid. A crash or a full disk mid-write leaves a
zero-length lock.txt, because save_string_file() truncates before it writes.
boost::lexical_cast<int>("") throws into the catch, which returned false with
origin still empty, and the caller deleted the backup. Also #15670.
The .3mf probe could not answer. A path that cannot be probed at all reports
false exactly like a missing one, so an unreachable backup looked spent. The
error_code now separates the two: ENOENT and ENOTDIR mean "not there" and stay
collectable, any other error means the question was never answered.
All three now claim the lock. Declining a restore is recoverable; deleting a
backup nobody could examine is not.
What deliberately did not change: a readable directory with no lock and no
project still comes back with origin empty and stays collectable, as do a lock
held by another process and a directory that does not exist. Claiming the lock
whenever anything looked amiss would strand every spent backup on disk, so
those three cases are tests rather than prose.
Tested in tests/libslic3r/test_3mf.cpp. Against the previous behaviour the new
cases fail as they should -- the two uninterpretable locks come back with an
empty origin, and the unreadable one escapes as
basic_ios::clear: iostream error
while the three collectable cases pass unchanged, before and after.
Regenerate the template after rebasing onto main, which brought the 14
strings of the object Align/Distribute menu (#13373), and translate
them in all 22 catalogs. Each language reuses its catalog's existing
"align" wording, with one consistent pattern for the edge, center and
distribute items. Entries whose wording needs a native speaker's check
carry review notes.
Labels that were wrong or contradicted the glossary:
- Bridge flow ratio and internal bridge flow ratio use the glossary's
Flow ratio term (ca, de, fr, ja, nl, ru, sv, zh_CN, zh_TW), with the
tooltips that quote them.
- tr Line width was "layer width"; tr Modifier was the verb "change";
hu Rectangle was "square"; pl No sparse layers meant "layers without
purging"; uk Auto backup was slang.
- Wall order labels made consistent (de, fr, it); th Migrate User
Presets uses the glossary's preset term; lt keeps Pressure Advance in
English per the glossary and translates Adaptive.
- Role based wipe speed now says it follows the extrusion role (de, ru,
pl, tr, zh_CN, zh_TW).
- ru Line infill pattern is "Линия", matching the glossary.
Translations that only covered part of the source or had typos: de
external bridge flow tooltip, zh_TW role-based wipe tooltip, ru wall
type tooltip option names, fr apostrophes, uk final period. es entries
from the last pass now use usted, the catalog's register.
Terms aligned with the glossary: purge/flush, prime tower, wipe and
flushing volume (ko), flow ratio (ca, ja), layer, shell, travel, walls,
plates, raft and fuzzy skin (th), wipe (eu), bridge (de).
Machine-translated entries that use the unsettled Prime tower / Wipe
tower and Tool (printer) terms carry review notes (cs, fr, ja, nl, tr,
uk).
The design said every pattern other than the trapezoidal and adaptive
ones outlines its centerlines with multiline_fill(). 3D Honeycomb skips
it and draws its own parallel copies of each wave, which never cross.
small_support_perimeter_speed declared outer_wall_speed as its ratio_over
parent, while GCode.cpp and the tooltip use the support or support
interface speed. The option is a per-nozzle vector, so ratio_over is
only metadata here and no output changes.
The prompt that clumping detection needs a prime tower was nested inside
the smooth timelapse check, so disabling the tower with clumping
detection on and traditional timelapse gave no prompt; the problem only
surfaced as a warning after slicing. The prompt now appears whenever
clumping detection is on. The smooth timelapse prompt is unchanged, and
answering it with No still skips the clumping one.
extruder_printable_area linked to the Extruder offset Position section,
which describes a different setting; the wiki now has an Extruder
printable area section. The two compatibility conditions linked to their
parent sections instead of the Printer Condition and Process Condition
sub-sections that describe them.
The wiki now has sections for options that were linked to a page top or
not linked at all: the polyhole sub-options, accel to decel, extrusion
rate smoothing on external features, max bridge length, the tree support
brim, seven prime tower options, Flush into this object and clumping
detection. Point each link at its section, and follow the corrected
spelling of the wall transitioning threshold angle heading.
- Minimize wall height angle acts on slopes steeper than the angle, not
shallower (ContourZ.cpp).
- The wipe tower's default wall type is Rib, not Rectangle; extra flow
for purging can't make lines narrower (minimum 100%); the max purge
speed caps the sparse infill speed on sparse layers and also the outer
walls.
- Interlocking depth is no longer ignored without a maximum width: every
other layer is cut to that depth unless beam interlocking is on.
- Draft shield: skirts are placed around brims now, so the note about
intersecting brims is removed.
- Infill/wall overlap percentages are relative to the inner wall line
width, not the sparse infill width.
- Bridge line width 0 uses the nozzle diameter for thick bridges.
- Fully overhanging walls use the 75% overhang speed limited by the
bridge speed; there is no 100% speed. The bridge speed tooltip no
longer mentions the removed classic overhang mode.
- First layer speed covers walls and brim; everything else uses first
layer infill speed.
- Inner/Outer/Inner is recommended over Outer/Inner, not against it.
- Minimum wall length is a multiple of the wall width, not mm, and One
wall threshold is in the Walls and surfaces group.
- Small perimeter speeds can't be set to zero (min 1), so "Set to zero
for auto" is removed.
- Label typo: Role based wipe speed.
The translation template is regenerated; it also picks up strings that
other recent changes added without regenerating it.
Linux webviews lost the BBL-Slicer, dark|light and BBL-Language tags
with the stock WebKitGTK User-Agent, so the bundled pages stayed light
and dropped every message to the host. The tags come back on Linux, now
appended to the string WebKitGTK reports for itself instead of the Mac
one. Windows and macOS are untouched.
A fifth Python plugin capability type, ResourceTexture, contributes
height-map textures to the Texture Displacement picker. A pack returns a
directory of images; the host validates it once at load and lists the
images under a section of their own.
- New capability type ResourceTexture, type string "resource-texture",
exposed to plugin authors as
orca.resource_texture.ResourceTexturePluginCapabilityBase with one
required method, get_texture_dir().
- The directory is resolved once on the load worker, under the GIL, and
cached as a plain string: the picker runs inside the ImGui frame, where
a trampoline call would block on the GIL behind a load/unload worker, or
throw past EndPopup.
- Validation resolves the directory and requires containment in the plugin
root, re-checked per file so a symlink inside the pack cannot resolve
outside it. Containment is the only security control here and it guards
exfiltration: layer bytes are embedded in every saved .3mf, so a pack
that could return ~/.ssh/id_rsa would leak it through any shared project.
- New host registry PluginResourceTextures, filled from both the plugin
and the capability lifecycle callbacks plus a startup sweep. It holds
strings only and never a capability reference, so nothing outlives
Py_Finalize.
- The picker lists pack textures read-only, one collapsible section per
pack, in a fixed-size scrolling popup whose height follows the window. A
generation counter invalidates the cached listing, and each gizmo's
cached thumbnails, when the set of packs changes.
- Tests in tests/slic3rutils/test_plugin_resource_texture.cpp cover the
validator (parent-directory and absolute escapes, an escaping symlink,
the extension whitelist, the 64-file cap), the listing and its
load/disable/re-enable/unload lifecycle, hostile get_texture_dir()
overrides, decode parity with hand-picked files, and a .3mf round trip
with the pack uninstalled.
No persistence work is needed: a layer carries its own image bytes and 3MF
writes them into the archive, so uninstalling a pack leaves existing
projects intact.
Behaviour change worth noting for review: the picker popup is now a fixed
size that scrolls, rather than sizing itself to its content, and it gains
one collapsible section per pack. That sizing applies to every user, not
only those with packs installed.
* Add Align/Distribute objects on the print plate
Ports the Align/Distribute feature from BambuStudio 2.5 (AGPLv3,
same license as OrcaSlicer). Adds a new GLGizmoAlignment helper
that operates on the GLCanvas3D selection, twelve thin Plater
wrappers, and an Align/Distribute submenu attached to the same
five context menus BambuStudio attaches it to: object menu, text
part, BBL part, and the single/multi-volume context menus.
GLCanvas3D::do_move gains a defaulted force_volume_move parameter
so the helper can move multiple selected volumes within a single
object even when selection mode is Instance. GLGizmosManager gains
check_object_located_outside_plate, which switches the active plate
to the one containing the selection before align/distribute runs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix build errors
---------
Co-authored-by: yw4z <ywsyildiz@gmail.com>
Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
Everything after `--` is appended, unchanged and last, to the cmake
configure line of the deps and OrcaSlicer builds, so a custom compiler,
compile flags or linker can be passed without adding an option to the
script for each one. The arguments are kept as an array, so a value with
spaces such as -DCMAKE_CXX_FLAGS="-O2 -march=native" survives, and they are
forwarded into the -g container. A stray argument without `--` aborts with
a message instead of reaching cmake as a path.
The existing DEPS_EXTRA_BUILD_ARGS and ORCA_EXTRA_BUILD_ARGS hooks are
unchanged and now mentioned in the usage text.
* add new repositories and new push
* fix bug about setting_id
* fix bug and complete the test
* Fix Goofoo profiles so the bundle loads and passes profile checks
Repair the OrcaFilamentLibrary index entry that broke JSON parsing, regenerate the hand-written filament ids as Orca ids, and bump the library version. Point each printer's default filament and print profiles at presets that exist, normalize the two presets that needed it, rebuild both indexes, correct the ABS-GF/PA-GF/PA6-CF filament types, align the GP1/Velo+ preset names with the library's @ spacing, and fix the casing and resolution of the model cover images.
* Drop the copied Custom Klipper printer from the Goofoo bundle
Goofoo shipped Custom's MyKlipper machine model verbatim, so the Add Printer list offered "Generic Klipper Printer" twice and the printer type resolved its model_id by vendor order. The Goofoo machines now inherit the shared fdm_klipper_common directly, carrying over the per-variant layer heights the removed base supplied, matching how the other Klipper vendors wire theirs. Each machine's resolved settings are unchanged.
* Align the Goofoo preset names with the profile conventions
The machine models and variants already followed the <Model> / <Model> <nozzle> nozzle shape, but the processes put the model in the quality slot and used the bare vendor as the @ target (0.20mm GP1 @Goofoo). They are now named like every other vendor's: <lh>mm <Quality> @<Vendor> <Model> (0.20mm Standard @Goofoo GP1), with the machines' default_print_profile and the index updated to match. The four leaves also inherit the shared fdm_process_klipper_common directly instead of the copied Custom "0.20mm Standard @MyKlipper" base, which is removed; GP1 and TurboM gained the layer_height 0.2 that base supplied, so every preset's resolved settings are unchanged. Removed the orphaned Generic Klipper Printer cover and the unused Turbo-M texture, and renamed TurboM's bed texture to the -texture.svg form the other two models use.
* new Goofoo machine and clean profile
* Restore library filaments dropped by merges from main
* Give the Goofoo Velo+ Tool and 0.2 nozzle fitting processes
---------
Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
* fix: login crash when network plugin is unavailable
* fix: directly use !config_version.empty() for the control block
* Use if/else for the empty plug-in version check
* Fix multi-device task list using a deleted network agent after plug-in reload
---------
Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
The filament list groups presets by vendor: hovering a vendor in the main
popup opens a side popup with that vendor's materials. On GTK the side popup
holds the popup grab, and gtk_main_do_event hands every GDK_SCROLL of the popup
chain to the grab widget. GDK names the event for the window under the cursor,
so the side popup received the wheel events raised over the main list and
consumed them: the main list could not be scrolled while a vendor was hovered.
The problem shows on both X11 and Wayland, because the rerouting happens in
GTK, not in the compositor.
The side popup now inspects the event's GDK window and, when the event belongs
to the main popup, replays it there. The replay goes through
gtk_propagate_event(GetConnectWidget()): wxGTK connects its mouse handlers to
the inner client widget of a popup (a wxPizza child of the outer GtkWindow), so
a signal emitted on the outer GtkWindow reaches no handler at all.
The widget reference in .claude/skills/orca-wxwidgets records the mechanism and
the connect-widget fact.
The build height check in Print::validate() compared object layers that already include the Z shrinkage compensation against printable_height divided by that compensation. That counted the compensation twice, so objects that fit were rejected. The check also left out the raft, so an object whose top plus the raft is above printable_height passed validation.
The check now adds object_print_z_min to the top layer and applies the compensation once. The shrinkage message is shown only when the object fits without the compensation. Belt printers apply the compensation to the raw model height, since their height does not come from the layers.
The constructor re-declares `namespace fs = boost::filesystem;`, shadowing the
identical file-scope alias the rest of the file already uses. Removing it
leaves every `fs::` in the file resolving to that one declaration.
No functional change.
The startup probes for download_path and last_backup_path used the throwing
boost::filesystem overloads, so a path that could not be probed at all, such as
one whose parent is not searchable, raised filesystem_error instead of falling
back. init_download_path() runs from the GUI_App constructor inside GUI_Run()'s
try, which left the application showing a message box and exiting with no window
and no log file, the throw preceding the log sink. has_restore_data() runs from
the queued EVT_RESTORE_PROJECT handler, whose try starts after the call.
Five probes in those three functions take the error_code overload instead.
Nothing branches on ec, and readable or missing paths are unaffected because both
overloads return the same value for them. A new test covers has_restore_data()
against a backup directory whose parent has no search permission, and skips on
Windows, as root, and where the mode change is refused or not enforced.
- Slicing pipeline plugin linked to others_settings_plugin_picker, a page
that doesn't exist. It now opens the Slicing Pipeline section of the
plugin types page.
- Infill shift step linked to the Cross Hatch pattern, which doesn't use
it. It only applies to Cross Zag and Locked Zag, so it now opens Cross
Zag.
- Fuzzy skin and Fuzzy skin generator mode were crossed: the generator
mode opened the section that documents Fuzzy skin's values. Each now
opens its own section.
- The process Change extrusion role G-code had no wiki link; it now has
one, like the printer and filament versions.
- The hidden Frequent page linked Wall loops to the top of the walls page
instead of its section, which made the wiki sync write its metadata
twice.
Orca sent `X-Api-Key: 88888888` to Moonraker whenever the printer preset's API Key was empty, which Moonraker rejects with HTTP 401 since Arksine/moonraker@93f5d9c. The preset's API Key is now the only thing that sets the header, and an empty key sends none.
Logging out of a printer now marks it revoked rather than only clearing its code, since an empty code is a valid Moonraker binding. Discovery events are delivered through the network agent and dropped if their printer agent was replaced while queued.
* Precise Seam: new full-coverage policy for Seam Blocked
A Seam Blocked modifier that covers a whole perimeter now blocks it, like a
perimeter painted red all round, instead of being skipped. It overrides
painting and lower-priority modifiers, like Enforced and Neutral. A new
warning names the modifier: "Seam Blocked modifier "<name>" of "<object>"
covers an entire perimeter". It is shown only when a perimeter stays
entirely Blocked after all weak modifiers. The full-containment warning now
covers only Center, Left and Right.
* Precise Seam: fix seams on perimeters blocked all round
- SeamComparator::is_first_not_much_worse() compares the types first, so two
Blocked candidates fall through like Neutral ones. On a perimeter blocked
all round, by a whole Seam Blocked modifier or by red painting, Random put
the seam on the closing vertex of every layer and Aligned stopped aligning.
- The whole-perimeter Blocked warning flags only the highest whole zone, the
one in effect. A lower whole zone it overwrites is no longer reported, and
the candidate scan runs only when that zone is Blocked.
- The warning msgids take the type name from the menu's "Seam Blocked" msgid.
- PreciseSeamWarnings::set() shares the load-before-store idiom, and every
weak segment records its modifier.
- Tests: a whole Blocked perimeter gets the seams of an unmarked one under
every seam position; overwritten whole Blocked zones are not reported.
---------
Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
The ubuntu:24.04 base image has a built-in ubuntu user with UID 1000, so the Dockerfile skips creating the host user and `docker run -u "$USER"` cannot find it. Run the container with the numeric UID and GID instead, and set HOME and the working directory to the bind-mounted home.
The time estimate added 260 s for every G29 on non-Bambu printers. Prusa MK4, MK4S and CORE One start G-code splits bed leveling across six G29 commands, so those printers showed about 26 extra minutes. The 260 s is now added once per G-code processing run. Bambu printers still only count a G29 inside an M622 J1 block, and their estimates are unchanged.
Fixes#15088
* Keep User Filament Presets on Printers Their Parent Listed in 2.4.2
User presets store only their diff, so they inherit the parent's
compatible_printers. Profile changes since 2.4.2 renamed and narrowed many
system filament presets, so user presets made against them disappear from
printers they were made for and the saved selection silently falls back to
another filament at startup.
orca_profile_tool.py legacy-compat writes, per current system filament
preset, the printers it covered in an older release and no longer lists,
kept only where a preset with the same filament_id still covers the printer
and they share an extruder variant. The loader gives these printers to user
presets that inherit their parent's list, without writing any file; saving
a preset detached, cloned or with its own list drops them.
* Ask Before Migrating User Filament Presets to Their Legacy Printers
Instead of giving user presets their parent's 2.4.2 printers in memory,
detect the user presets that inherit their parent's compatible_printers
and are missing printers from the legacy map, and offer to migrate them
at startup or from Help > Migrate Filament Presets.
Migrating first backs up the user folder to
filament_preset_migration_<datetime>_backup.zip in the configuration
folder, and migrates nothing if that fails. It then writes the printers
into each preset's compatible_printers, patching only that key so the
preset keeps the parent name it was saved with and older releases can
still resolve it. Cloud-synced presets are marked for upload.
No asks again at the next launch; "Don't remind me again" stops the
startup prompt.
* Key the legacy filament preset map by the parent's 2.4.2 name
* Check the legacy filament preset map in CI
* Limit the 2.4.2 filament preset migration to presets that need it
Only presets that still name their parent as 2.4.2 did and have no printer
list of their own are offered, and a preset changed after it was found is
left alone. The startup prompt appears once, after upgrading from 2.4.2 or
earlier, as a standard dialog that keeps "Don't remind me again"; Help >
Migrate Filament Presets is unchanged. Migrating no longer overwrites an
unsaved printer-list edit or marks the project modified.
* Ask on Every Start and Take the User Presets Lock Once When Migrating Filament Presets
The startup prompt no longer depends on the version the previous run
recorded: whoever still has user filament presets that lost printers is
asked, including users already on a 2.5.0 nightly or alpha. Answering No
asks again at the next start; "Don't remind me again" stops it.
Migration takes the user presets lock once, around the backup and every
file write, instead of once per preset. A lock another instance holds is
reported once ("another OrcaSlicer instance is saving presets") with
nothing written, rather than as one failed preset after another once the
lock's cool-down kicks in. Detection records each preset's file and the
list it will get, so the writes need no collection lock while the user
presets lock is held, the order InstanceLock requires; the loaded presets
are updated afterwards.
* Run the Legacy Compatible Printers Map Check in check_profile.sh and .ps1
CI's "Check profiles" job checks resources/info/legacy_compatible_printers.json
against the v2.4.2 profiles, and its failure message points at these scripts
as the local reproduction, so they run the same check: legacy_compat, right
after profile_tool. The v2.4.2 tree comes from git archive of the tag, is
cached in the work dir like the other downloads, re-extracted with --refresh,
and fetched from upstream first when the checkout lacks the tag.
* Back Up Before the User Presets Lock and Merge Migrated Printers Into the Live List
The lock guard is meant for a few file operations, and zipping a large user
folder is not one, so the backup is taken before it and the guard covers the
writes only; each write re-reads its file, so a save that slipped in before
is still respected.
The in-memory update adds the lost printers to the list the preset holds at
that moment rather than replacing it with the one found at detection, and
leaves a pending cloud sync state alone: a download waiting to be saved kept
its newer list and its "save" flag, which "update" would have cancelled.
The lock message no longer blames another instance alone, since the lock is
also refused for a while after a miss and when the lock file cannot be
opened.
---------
Co-authored-by: SoftFever <softfeverever@gmail.com>
Auto-arrange centered the pile on `best_object_pos`, so on printers with an off-center position (A1 mini, H2C, H2D, H2D Pro, X2D) a large pile could end up partly off the bed. The final alignment now always clamps the pile to the bed, and the `clamp_to_bin` flag is removed. Belt printers already clamped, and centered alignment is unchanged.
The check against fixed parts and excluded regions now tests the position the pile is actually moved to.
Fixes#15727
CurlGlobalInit() probed the CA bundle with the throwing fs::exists overload, which
returns false only for a missing path and throws on EACCES. An existing but
unreadable cert path then killed the GUI at startup instead of falling through to
the CA_BUNDLES list. Use the error_code overload at both probe sites.
Fixes#15653.
Print::validate sizes the pre-slice prime tower from Print::extruders(),
which lists every mixed filament slot under its own id, so the estimator
planned one purge block per slot. The tool ordering resolves a mixed slot
to one physical component on every layer, with or without colour
sublayers, and the generated tower only purges those. The GUI preview and
its placement clamp already expand the slots, so the tower was drawn
inside the bed and then refused as partially outside it: with 8 mixed
slots at 0.08 mm layers the estimate was 114 mm for a tower that prints
at 64 mm.
The estimator now expands mixed slots to their components itself, so
validation, the GUI and the CLI placement size the same tower.
* Add orca-translations Claude skill and PO tool
Add a .claude skill that guides translation passes over the localization/i18n gettext catalogs. It covers the workflow: run gettext, settle new glossary terms, translate in batches, verify, and report.
Includes translation-rules.md, which condenses the placeholder, line-break and glossary rules plus the guidance on review notes.
Also adds scripts/po_tool.py (standard library only). It lists pending entries with glossary hints, validates and applies translations, adding '# AI Translated' and '# Needs human review' comments, and rewrites only the entries it changes. It can also check catalogs, look up existing translations, suggest glossary candidates and update the wiki glossary TSV.
* Let po_tool apply translations by entry id
pending now gives each entry a short id, so a translation file can name
entries by id instead of repeating the English msgid. apply accepts either.
A "review": "" item clears an earlier review note when overwriting an
entry, and check gives each msgfmt run its own temporary output file so
parallel checks do not collide.
* Fill all empty translations
Regenerate the template with run_gettext --full and translate every empty
entry in the 22 catalogs (7,089 entries) with the orca-translations skill,
following the localization glossary, including the new belt printer,
IDEX/IQEX and plugin terms. Every new entry is marked "# AI Translated";
122 also carry "# Needs human review" with what to check.
* Show entry ids in po_tool lookup and search translations
lookup now prints each entry's id, so an entry found there can be
rewritten with apply --overwrite without repeating the English, and
--msgstr searches the translations instead of the msgids, which finds
every entry that still uses a term being changed.
SKILL.md describes how to change an established term: check the
catalog's usage first, update the glossary cell, then rewrite only the
entries where the term has that meaning.
* Fix mistranslated terms and align catalogs with the glossary
Correct labels that the glossary had copied, and the entries that used
them:
- ca: Raft was rendered as the Brim term; it is now "Rai". Exhaust fan,
Monotonic line, Unretract, Slice, Lateral Honeycomb and PA Tower
labels fixed.
- cs: Lightning infill is "Blesk", not "Osvětlení" (illumination).
- sv: Retraction is "Retraktion", not "Reduktion"; Unretract,
Lagerhöjd, Delkylningsfläkt, Utsugsfläkt, Aligned Rectilinear and the
Preset label fixed.
- de: Brim is "Rand" and Skirt is "Umrandung", as most entries already
had them.
- lt: brim entries used the Skirt word "apvadas"; they now use "kraštas".
- es: Sparse infill is "relleno de baja densidad", the catalog majority.
- eu: nozzle wipe is "garbiketa", not purge.
- th: the project plate is "เพลต", distinct from the bed.
- uk: Quarter Cubic, Latin "PA" in PA Tower, and Belt purge tower.
- pl: Cross Hatch, Tri-hexagon, Volumetric speed and Raft labels.
- zh_TW: Brim stays "Brim", as the existing labels have it.
- tr: "Prime Tower" read "Prime Minister Tower".
- ko: the plugin "Capabilities" label read "performance".
- Build Volume labels fixed in tr, de, cs and eu.
All 242 rewritten entries are marked "# AI Translated".
* Make more UI text translatable and fix pluralization and context
Audit the sources for text that translators could not translate
correctly and fix it.
Mark text that reached users in English only: IDEX/IQEX role names,
plugin permission prompts and errors, 3MF/preset loading errors, G-code
placeholder parser errors, calibration and device messages, notification
texts, file dialog filters, and the config validation messages.
Replace sentences built from translated fragments, such as the prime
tower warnings, "left"/"right" nozzle names, and "Filament " + number,
with single msgids that have placeholders, so translators can reorder
and inflect them.
Use plural forms for texts with a count ("%1% mixed filaments",
"%d objects", "%1% triangles") instead of "(s)" and mismatched verbs.
Fix the swapped singular/plural pair in "The following presets inherit
this preset".
Add translation contexts where one English word has several meanings
(Subscribed/Local, Standard, Pause, View, Step, Model, Camera, Flow,
Filter, Others, Prepare, the Design tools). Show "Size" instead of
"Build Volume" in the Cut tool, which displays the object size.
Fix code that compared translated text with English (the Edit G-code
dialog group, the plugin registration failure check), and add the
bundled web pages' missing string-table entries. Use the single
character for degrees Celsius, say "G-code", and describe the head
filament map without internal jargon.
Install the libslic3r translate callback before the presets load, so
their load errors are translated, and list the 28 source files that had
marked strings but were never extracted into localization/i18n/list.txt.
* Suggest earlier translations and list fuzzy entries in po_tool pending
pending now attaches the closest earlier translation of a similar
English text (from obsolete or translated entries), so a reworded or
split string keeps its wording, and it lists fuzzy entries by default,
since users never see them. A fuzzy entry also carries its stale
translation as previous_translation.
* Fix wording of the non-boolean operands error
The logical operation error said "operators" where it means the
operands the operation was applied to.
* Translate the newly translatable strings in all languages
Regenerate the template after the translatability changes and fill every
new or changed entry in the 22 catalogs (about 12,800 entries): the
strings that became translatable, the plural forms, the entries split by
context, and the previously fuzzy singular entries that became plurals.
Every new entry is marked "# AI Translated", and about 360 also carry a
"# Needs human review" note saying what to check. Terms follow the
localization glossary. Earlier translations of reworded strings were
reused where the meaning was unchanged. Existing entries were not
changed, and obsolete entries are kept.
* Document the source translatability audit in the orca-translations skill
Add a reference describing how strings reach translators, what to look
for (untranslated text, spliced sentences, missing plurals, ambiguous
words), what not to translate, where a context cannot be carried, and
what to do after the edits, plus a scanner that lists untranslated
user-facing string literals.
* Document how to write translatable strings in AGENTS.md
Tell developers to keep each message complete in one msgid: use
placeholders instead of concatenating translated fragments, plural forms
for counts, and contexts for ambiguous words, and to list new source
files in localization/i18n/list.txt. Add a matching code review item.
* Translate the strings added since the rebase
Regenerate the template against the current main and translate the 26
new entries per language: the Fix Model commands and messages, and the
texture displacement image adjustment panel. Each new entry is marked
"# AI Translated"; existing entries are unchanged.
* tidy
A 3mf saved without a plate thumbnail, such as a CLI export with no OpenGL context or a backup, still listed /Metadata/plate_1.png and plate_1_small.png in _rels/.rels. lib3mf rejects a package whose thumbnail relationship points at a missing part. The writer now records which plate thumbnails it wrote and only declares those.
A tooltip appeared only on the next pixel of mouse movement after its wait
had finished. ImGui hides a window on the first frame it is submitted, to
measure it before showing it, and does so every time a tooltip reappears
because those windows are recycled (the Popup|Tooltip branch of Begin()).
So the frame a tooltip is decided on is never the frame it is seen on, and
one carrying no animation asked for nothing further - leaving it in its
hidden measuring frame until some other event happened to produce the next
one. Before the hover delay this never showed, because the tooltip was
submitted on every mouse-move frame and the movement supplied the second.
It now asks for frames until it has been drawn twice in a row.
that finishes on its own
The hover delay only finished while the mouse was moving: a pointer resting
on a control produces no events, so nothing redrew the canvas and the wait
never reached its end - the tooltip appeared on the next pixel of movement
instead. It asked for its frames with schedule_extra_frame(), which only
arranges a timer whose event an idle event is assumed to follow, and
on_idle renders nothing unless the canvas was already dirty when it ran.
The wait and the animations now both use request_extra_frame(), which ends
on_idle with evt.RequestMore() and so obliges wx to deliver the next idle;
it is what 3DBed's fade uses. Asking for frames as fast as they come is
safe now that which frame is drawn comes from the clock rather than from
the frame count - a redraw that arrives early draws the same frame again.
Nothing explains a control while the control is being used, so no tooltip
appears while a slider is being dragged, a value typed into one, a combo
open or a layer card dragged. The pointer sits on the very thing being
edited, so a tooltip there covered the model exactly while the user was
watching it change. The full wait comes back afterwards rather than the
short one: having just let go of a slider is not a reason to be shown a
picture of it.
AnimatedTexture::texture_id() loses its time-to-next-frame out-parameter,
which nothing asks for any more.
The animations crawled when left alone and raced while the mouse moved.
They were timed by ImGui::GetTime(), and nothing in OrcaSlicer ever sets
io.DeltaTime, so it keeps imgui's default of 1/60 per frame: imgui's clock
counts frames, not seconds. On a canvas that renders only when something
asks it to, that is not a clock. Playback now reads a steady_clock, so a
frame is due when it is due whatever the canvas is doing.
An animation also restarts when its tooltip opens again, not only when a
different one opens: a gap in the frames it was drawn on is what that
looks like from here.
Tooltips now wait for the pointer to rest on a control for 0.45 s. The
panel is dense and a tooltip lands on the model beside it, so with no wait
a sweep from the top of the panel down to Bake flashed a dozen large panels
over the scene. After one has just been up the wait drops to 0.1 s for 0.7
s, so reading several settings in a row stays quick - but not to zero, or
that same sweep would flash again once one tooltip had been seen. A pointer
resting on a control produces no events, so the wait asks for the frame it
will appear on itself.
rest of the panel
The animations advanced one frame per mouse movement and stood still
whenever the mouse did. schedule_extra_frame() only guarantees an idle
event at the moment the next frame is due; GLCanvas3D::on_idle() returns
without rendering anything unless something marked the canvas dirty by
then, and nothing had. set_dirty() on the gizmo is what on_idle reads,
through GLGizmoBase::update_items_state(), to find out there are frames
still to draw.
Thirty-odd more pictures, covering the paint tools, brush size, the
connected-area fill, the five views, the five projections, subdivision,
feature-adaptive refinement, remeshing, hard edges, slivers, geometry
smoothing, filament mixing and denoise.
The generator grew three more drawing languages beside the shaded relief,
each picked by what the setting is rather than by taste: a flat mesh of
triangles where the subject is the mesh or a tool, a section where the
subject is where the surface sits relative to where it was, and a flat ramp
of greys where the subject is which filament a spot ends up as. Lighting a
colour illustration or looking down on a vertical change would both add a
second thing moving between two pictures meant to differ in one.
Mapping is one ball with the checker laid on it five ways, which is the
whole of what choosing a projection does - except Unwrap, which is not a
projection onto the model and so is drawn as the flat layout it produces.
Ten more tooltips get a picture: Depth, Tile size, Repeat/Mirror, Invert,
Smoothing, Edge fade and Blend as stills, Depth, Rotation and Midlevel as
animations. All drawn by scripts/gen_texture_displacement_tips.py from the
same arithmetic the feature uses, one motif throughout, so that whatever
differs between two of them is the setting and nothing else.
Midlevel is drawn in section instead of as a relief. Shading reads slopes,
so a height field moved up or down as a whole shades exactly as it did
before - and where the relief sits relative to the surface is the entire
meaning of that setting. From the side, against the bare surface, it is the
one thing visible.
A single picture is now capped at 10 em tall and centred in its column,
rather than drawn at the tooltip's full width, where a square one came out
as tall as the text beside it was long.
The panel is pinned to the right edge of the canvas, so a tooltip left to
follow the mouse covers the very control it explains. panel_tooltip()
places every tooltip in the gizmo to the left of the panel instead - or of
the texture picker, while a tooltip is coming from inside that popup - the
same placement the picker itself already uses.
A tooltip can now carry pictures under its text: stills, or GIFs that loop
while it is open. Several settings in this panel describe a shape rather
than a number, and a sentence about a shape is read twice and understood
once. Files live in resources/images/texture_displacement and are named by
a tooltip; one that is missing is skipped, so a tooltip without its art yet
is exactly the text tooltip it was before.
AnimatedTexture decodes a file once and uploads a GL texture per frame -
wxImage for stills, wxGIFDecoder for GIFs, whose frames are composited here
with the disposal method each one asks for. An animation restarts when its
tooltip opens and asks the canvas for a redraw timed to its next frame.
Tile and Rotation are wired up as the first two; scripts/
gen_texture_displacement_tips.py draws their relief from the same
arithmetic the feature uses. docs/HLSD/tooltip-illustrations.md covers the
rest.
WebFreak001/deploy-nightly still targets Node.js 20, so every main build
logged a deprecation warning per deploy step. The action has had no
release since 2024 and its Node 24 PR is unmerged.
The local action in .github/actions/deploy-nightly uploads the new file
under a temporary name and renames it once the upload finishes, so a
failed upload keeps the existing nightly asset. It also fails on a missing
file before deleting anything, retries transient API errors, times out
stalled transfers, handles concurrent runs, and finds the release by its
nightly-builds tag. The macOS validator DMG deploy now runs only when that
DMG was built.
* Carry existing colour across a bake instead of reindexing it
Baking, painting colour, then baking again left colour debris on surfaces
nothing had painted and destroyed what the earlier bake had laid down.
TextureDisplacementBakeJob::finalize() deserialized the volume's pre-bake
mmu_segmentation_facets into a TriangleSelector built on the post-bake mesh,
on the assumption that the bake is topology-preserving. That holds for the
classic path, which moves vertices and leaves the topology alone, but not for
the one-run pipeline, which rebuilds the mesh. TriangleSelector::deserialize()
only checks that the stored triangle ids are in range, and a bake almost
always grows the triangle count, so every stale id passed that check and
landed on an unrelated face.
Measured on a 26 mm slab baked twice at 0.19 mm with a 750 k budget: 7580
triangles ended up carrying paint neither bake produced, as far as 31 mm from
any colour that was actually laid down, while the first pass's colour was lost
where the second bake left the surface alone.
TriangleSelector::remap_painting() already transfers paint between meshes by
position, and ModelVolume::restore_painting() uses it for the same reason, so
the bake job now uses it too. It runs in process(), on the worker thread: it
builds an AABB tree over both meshes and took around a second on the case
above, which would have frozen the UI in finalize(). The bake's own colour is
still applied last, so it wins where it painted and the rest is kept, which is
what makes successive bakes accumulate rather than overwrite. If the volume is
painted while the bake is running, a timestamp check falls back to the live
mask so that stroke is not dropped.
The out_triangle comment in TextureColorRequest claimed the bake is
topology-preserving, which is what justified the original indexing; it now
says which mesh the colours belong to.
The regression test bakes twice, checks both results are closed, and checks
the first bake's colour survives the second at the positions where it was
laid down and nowhere else.
* Carry the paint across with transfer_facet_paint
The branch used TriangleSelector::remap_painting(), which pairs a source
paint triangle with a target triangle overlapping it to within
TriangleCursor::tolerance. That is right after a remesh, which leaves the
surface where it was, and wrong after a bake, which has moved it by the
relief depth - most of the paint finds no partner. transfer_facet_paint()
asks the other question: every triangle of the new mesh takes the state
painted at its foot point on the old one, which is exactly where it came
from however deep the relief.
It also does all four paint channels rather than colour alone, and it runs
where the volume is read, so the snapshot and the timestamp that guarded
against the user painting while the bake ran are no longer needed: the
annotation is read at commit time and is by definition current.
The function and its call site are byte for byte what the model-UV branch
carries, so the two merge in any order and in either order produce one
mechanism rather than two fighting over mmu_segmentation_facets. The
regression test is unchanged except for the call it makes.
---------
Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
# Description
An image laid onto a model came out upside down under the box
(tri-planar) projection, and on three of a cube's six faces it was also
mirrored left to right.
The sampler treats `v = 0` as the height map's top pixel row, but every
projection built `v` from world +Z, so `v` grew upward. The mirroring is
a separate defect: both faces sharing a world plane got the same `u`.
Note: re-baking an existing project now places its texture upright, so
it will look different from before. Geometry already baked into a 3mf is
unaffected.
# Images
<img width="2420" height="1655" alt="image"
src="https://github.com/user-attachments/assets/dcf7f62f-0f51-4966-97e4-d0386eb454c2"
/>
# Tests
6 tests added in `tests/libslic3r/test_texture_displacement.cpp`,
covering all six cube faces and every mapping mode, plus a full bake of
a known asymmetric image.
<!--
> 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)
GCodeProcessorResult declares a copy assignment, which suppresses the
implicit
move assignment, so `*result = std::move(processor.extract_result())`
binds to
the copy and duplicates the whole moves array: 96 bytes per move,
measured at
147.7 MiB in one allocation for a 597k facet model at 0.08 mm, with the
source
staying alive until the export returns.
Add the move assignment. It assigns exactly the same 39 members in the
same
order as the copy, including the ones the copy deliberately leaves
alone, so a
target that carries its own filament maps and nozzle type keeps them. A
move
constructor is intentionally not added: the type holds a std::mutex, so
it is
neither copy nor move constructible today and a partial one would leave
ten
members uninitialised.
reset() now releases the two print sized vectors rather than clearing
them. Its
callers are the paths that discard a result, so the memory went back
only at the
next slice.
ViewerImpl::reset() does the same for the two vectors sized by the
print, which
is what keeps a discarded preview resident, and counts m_vertices_colors
in the
reported CPU memory, where it was missing.
The texture-displacement gizmo pushes pane state to the UV editor from the
preview rebuild that runs on every frame of a slider drag. Tile size is the
only one of the layer card's sliders whose value appears in that pane state,
so dragging it changes the "N.N mm tile" label, the panel relays out, and the
UV canvas receives a synchronous wxEVT_SIZE. Its on_size() flushes a paint
with Update(), and render() then makes the app's single shared wxGLContext
current on the UV drawable in the middle of the 3D canvas's frame.
On X11/GLX that out-of-turn context activation is fatal: the drawable the
3D canvas later swaps no longer belongs to the current context, the server
returns BadMatch, and GDK turns the unhandled X error into g_error(). EGL on
Wayland reports only a status, so the same theft silently corrupts the frame
instead of aborting - which is why the guard is not conditioned on the
display backend.
Add GLCanvas3D::is_frame_in_progress(), backed by a counter that
_render_frame() maintains through its existing scope guard, and let the UV
canvas skip the synchronous paint while a frame is on the stack, re-queueing
it with Refresh() so nothing is dropped. Rebind the context with
_set_current() before SwapBuffers() as well, so a surface that steals it by
some other route - a modal GL dialog a gizmo opens from its panel - cannot
turn the swap into the same fatal error.
The cloud upload sent only the keys that differ from the parent, so a
variant layout equal to the parent's was left out. On download the
loader sized every per-variant value by the missing layout, cut it to
one value and kept the parent's values for the other nozzles and nozzle
volume types. On dual-nozzle printers the right nozzle's settings
silently reverted to the parent's after a sync.
Upload the variant layout always, as the local save already does, and
read copies stored without it against the parent's layout when they
carry one value per parent variant.
Opened or drag-dropped G-code files ignored `; MANUAL_TOOL_CHANGE` comments, so the preview showed every move on the first filament. Only `apply_config(const PrintConfig&)` read `manual_filament_change`, and loading a file goes through `apply_config(const DynamicPrintConfig&)`. Read the option there too, initialize the flag to false, and add a regression test.
Debug builds asserted on startup because these calls raise GL errors on a core-profile context. Both draws already use shaders, so removing the calls changes nothing on compatibility contexts.
* Add CGAL-based mesh repair, drop Netfabb remnants
Add repair_mesh() in MeshRepair.cpp, a CGAL-based repair that replaces MeshBoolean::cgal::repair(). It welds cracks, cancels coincident triangles, fixes orientation, fills holes (including planar annular regions), removes local self-intersections, and unites crossing shells with libigl's exact boolean while following the nonzero winding rule used by slicing.
FixModelByCgal now repairs each volume in place without splitting it into parts, and reports whether the mesh was modified so unchanged meshes keep their painting and ids. The texture import repair prompt no longer depends on the Windows SDK, and the FIX_THROUGH_CGAL_ALWAYS conditionals are removed. The commented-out Netfabb/WIN10SDK build code is deleted.
libslic3r_cgal now builds with CGAL_LINKED_WITH_TBB and links TBB. Add docs/HLSD/mesh-repair.md and unit tests in test_mesh_repair.cpp.
* Parallelize repair while preserving separate shells
Repair connected mesh units and shells independently in parallel, keep crossing shells separate, and limit self-intersection remeshing to local defects. Update mesh-repair documentation and tests for the revised behavior.
* Make mesh repair deterministic and report its progress
Flags written from parallel loops were std::vector<bool>, whose neighbouring
bits share a word, so shells could be kept or dropped at random; they are now
std::vector<char>. Holes of up to 100 edges are filled with the search over all
triangles: the search restricted to a 3D Delaunay triangulation picks between
equally good fills differently from run to run.
Shells of fewer than four faces are dropped, as the volumes of two triangles
back to back need not cancel exactly, and a shell that self-intersection removal
changed is split again so the pieces it cut off are dropped too.
repair_mesh() takes a progress callback in place of the cancel callback, and
Fix Model reports each stage. The object's bounding box is now invalidated even
when a cancel or an error stops the repair after some volumes were replaced.
The edge-use sort, union-find and box-overlap code shared by several stages is
factored out, the topology analysed for welding is reused to build the work
units, the orientation pass checks only the edges it cut, and the output is
written from the shells without joining them first.
* Fix Split to parts crashing on flat pieces and staying offered after a repair
ModelVolume::split() added every piece to the object and then deleted the
pieces without a convex hull. When the first piece was flat, as often in a
triangle soup, that deleted the volume being split while split() and its
caller kept using it. A part made only of flat pieces lost all its volumes,
and a soup added thousands of pieces, saving a backup of the object for each
one it deleted. Pieces without a convex hull are now dropped before any joins
the object, the volume being split keeps the first remaining piece, and a part
with no solid piece is left whole. Other parts of the object without a convex
hull are no longer deleted as a side effect.
ModelVolume caches whether its mesh can be split, and set_mesh() kept the
cached value, so after Fix Model, Simplify or Emboss replaced a mesh, Split to
parts could still be offered for a single piece. set_mesh() and reset_mesh()
now clear the cache.
* Import STEP surface models with Split compound enabled
Splitting a compound kept only its solids, so a surface model, which OCCT
reads as a compound of loose faces, gave nothing to mesh and the import failed
with "Meshing of a model file failed or no valid shape". Each shell outside a
solid now becomes a part too, and loose faces stay together as one part, as
they do without splitting.
* Add Fix All for plates and re-enable --repair on the command line
ModelVolume::repair() applies repair_mesh() to a part and replaces the mesh
only when the repair changed it. Fix Model, the new Fix All and Fix All Plates
items of the plate context menu, and the command line's --repair option, which
had been commented out since the Win10 SDK repair was dropped, all use it.
With Keep painted feature after mesh change off, Fix Model cleared the painting
of every part of an object before repairing. Now only a part whose mesh changes
loses its painting, so fixing a whole project does not wipe it. The
notification counts the models that needed no repair instead of listing them as
repaired, and objects that did not change are not refreshed.
A filament_start_gcode list shorter than the filament count made the Bambu start-of-export path throw std::out_of_range. Use get_at() like the other filament_start_gcode reads, and add a regression test.
stapler fetches the notarization ticket from Apple's CloudKit server,
and a dropped connection there (NSURLError -1005, exit 68) failed the
whole macOS universal job twice on 2026-10-12, after notarization had
already been accepted. Rerunning the job passed, but repeats the full
build and both notarizations.
Wrap both stapler calls in the existing retry helper already used for
hdiutil in this step.
GLGizmosManager can be called while its gizmo list is empty, either at startup before the canvas has loaded the gizmos or after their init() failed. reset_all_states(), open_gizmo(), open_gizmo_by_shortcut() and activate_gizmo() now return early in that case. This fixes a Debug-build assert when a new project resets the gizmos at startup, and an out-of-bounds read in release builds when a gizmo shortcut key is pressed after gizmo init failed. activate_gizmo(Undefined) still returns true, since no gizmo is open. The reset_all_states() guard was first proposed in #16074 by kenrap.
Co-authored-by: kenrap <101818165+kenrap@users.noreply.github.com>
The reason recorded for CLI_NO_SUITABLE_OBJECTS (-50) assumed a 3mf being uploaded. The CLI also slices STL, OBJ and STEP input, so the reason now states what both -50 sites check: the plate is empty, or no object on it is both printable and fully inside the print volume. The exit code is unchanged.
Adds tests/cli/test_cli_no_suitable_objects.sh.
Pop the FramePadding style var before end() in the move and rotate gizmo windows. It was pushed inside the window and popped after End(), which failed ImGui's style stack check in Debug builds.
The CLI's arrange no longer rotates objects unless --allow-rotations is passed, which matches the GUI's default. STL, OBJ and STEP inputs are arranged even without --arrange, so a part that only fits the bed when rotated now exits with -50 unless --allow-rotations is given. The CLI prints that reason to stderr.
FanMover could split a move inside change_filament_gcode to place a delayed fan command. It computed the split point from the last position the G-code set, but on Bambu printers the firmware moves the head during the T command, so the inserted waypoint sent the head to a spot the template never visits (#15789).
Both toolchange paths, set_extruder() and the Type1 append_tcr(), now wrap the template output in ;_FAN_MOVER_SKIP_START/END tags when fan speed-up or kick-start is on. FanMover keeps fan commands inside them where they are and drops the tags from the output. multifilament_config() also sizes the per-filament options that the wipe tower reads by index.
This fixes the H2C start G-code template by commenting the two
`========== record data ==========` separator lines.
Those lines are currently emitted as raw text inside
`machine_start_gcode`. Since they are not comments or G-code commands,
they can make the generated start sequence invalid or firmware-dependent
when the template is used. The fix preserves the separators as comments.
Fixes#16218.
Validation:
- Parsed the modified JSON profile with Python `json.loads`.
- Checked both `record data` separator lines now start with `;` after
unescaping the G-code string.
# Description
Re-slicing no longer leaks layers in two places. Tree supports dropped
the support layers above the topmost overhang without freeing them. And
an object that had sliced itself, then became identical to another
object (for example a copy whose original lost a per-object setting or
modifier), leaked its own layers when it switched to sharing that
object's slices; it now frees them once it takes the shared layers over.
If slicing is cancelled before that point, they still leak, as before.
Tree support also stops copying its collision and avoidance areas on
every lookup, and releases its working cache once supports are generated
instead of keeping it until the next slice. Elephant-foot compensation
now uses each object's own slice closing radius, where the first object
sliced in a session used to fix the value for every later slice.
Slicing output changes only on layers with more than one region printed
with elephant-foot compensation and no raft, and only where an object's
slice closing radius differs from the value the session had fixed.
# Screenshots/Recordings/Graphs
<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->
## Tests
Adds a regression test for an object that switches to sharing another
object's slices. The `fff_print` suite passes.
<!--
> 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)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
# Description
Fixes highlights in the texture displacement gizmo that stayed on screen
after the state behind them changed, or were drawn where they could not
be seen. Erase whole model, Select whole model and Capture current view
update the paint highlight at once instead of on the next right-click. A
stroke released outside the 3D view ends on the next mouse move, in
every paint gizmo, rather than leaving the brush stuck in its pressed
colour. The layer textures stop showing an image that has been replaced.
Selecting islands in the UV editor switches a Normal view to Fast, where
their highlight is visible instead of buried under the displaced relief,
and Normal comes back once no island is selected unless another view was
picked in the meantime.
No change to slicing output; the changes are confined to the paint
gizmos and the UV editor.
# 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)
The island highlight was hidden under the Normal view's relief. Normal comes back once no island is selected, unless another view was picked in the meantime.
The segment draw switched to an element buffer of 8-bit indices. GCN 1/2
GPUs cannot read 8-bit indices in hardware, so the driver converts them,
and on a Mac Pro 2013 FirePro under Windows Boot Camp the toolpaths come
out as scattered spikes. 16-bit indices are supported everywhere and keep
the eight shader runs per segment.
Erase whole model, Select whole model and Capture current view update the
highlight at once. A stroke released outside the 3D view ends on the next mouse
move, in every paint gizmo, instead of leaving the brush in its pressed colour.
The layer textures no longer show an image that was replaced.
* Wait for the PR's Deps Cache in clang-tidy When deps/ Changes
* Fail the clang-tidy Deps Wait When Listing Caches Keeps Failing
* Wait for Deps Through ci_deps_cache.sh in clang-tidy
Reuse the macOS deps wait for Linux: the job names, workflow and cache
ref are passed in, and a new 'own' mode waits for the PR's own build
when it changes deps/. A PR that keeps deps/ now waits for a running base
build with the same deps, and warns when it falls back to older ones. The
wait ends as soon as the deps job fails.
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.
They were appended at the end of test_texture_displacement.cpp, where every
other open texture-displacement branch also appends, so the four of them
cannot be merged in any order without a conflict in a file none of them
really disagree about. Same tests, moved next to the patch-frame test they
belong with.
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.
An image laid onto a model came out upside down under the ordinary box
(tri-planar) projection, and on three of a cube's six faces it was also
mirrored left to right, which reads as a 180 degree rotation.
DecodedHeightTexture::sample() defines v = 0 as the height map's first
pixel row, i.e. its top. Every projection built v from world +Z, so v
grew upward and the image landed bottom row up. The one path that was
already right is the projective "project from view" matrix, which
measures window y downward.
Both halves of the box basis were wrong:
- v is now negated on all three world planes, so the top of an image
sits at the top of the model.
- u now carries a sign for the side of the plane the face is on, chosen
so that u x v == -normal. Without it the two faces sharing a plane get
one u and one of them is mirrored. The sign flips exactly where that
plane's blend weight (the fourth power of a normal component) reaches
zero, so the blended tri-planar sum stays continuous and the hard axis
switch the old code removed does not come back.
The same v convention was missing from the other mapping modes:
cylindrical (which also had a left handed frame, right x up rather than
up x right, so its wrap ran mirrored), spherical, the affine branch of
view projection, and the flat chart fallback of the LSCM unwrap, which
now reuses the box basis so a cube face unwraps to the orientation
tri-planar would have given it.
The basis table is exported as box_projection_axes() and the gizmo's
adjust_tangent_basis() calls it instead of restating it, so the
placement ring and offset handle cannot drift from the bake. All four
preview shaders get the matching change, including projection_axes(),
whose tangent and bitangent have to flip with the coordinate or the
relief shading inverts.
Re-baking an existing project now places its texture upright, so it will
differ from how it looked before. Geometry already baked into a 3mf is
untouched, so nothing changes until the user re-bakes.
Verified by measuring the sampled texture row and column against world
position for every mapping mode on all six faces of a cube, and by a
full bake of a quadrant coded image, which lands each quadrant within
1e-3 mm of its expected depth.
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.
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>
The project carried the layer stack but not the user's place in it: a reopened
project landed on slot 0 in Standard, whatever they had been editing. The active
layer and Standard/Pro now live on the volume, next to the rest of its options,
and come back with it. Docked/undocked goes to the application config instead -
it describes the user's window, not the model, so opening someone else's project
must not move their panel.
A layer whose image_data was empty at save time was written with no texture and
came back with none. It now falls back to the file the layer came from, which the
importer put in the user's own texture library, so a project reopened on the same
machine keeps its pixels.
The texture is always stored as .png: the importer converts whatever the user
picked, so the extension was being taken from a name that no longer described the
bytes.
Also adds v2_flip_edges to the cereal field list, which the undo stack was
silently dropping.
The round-trip test named a TextureTileMethod enumerator that does not
exist (MirroredRepeat, not Mirror) and used a bare Approx, which Catch2 v3
keeps in its own namespace. The rest of the file compares floats with
WithinAbs, so these follow it rather than reaching for Catch::Approx.
bbs_3mf.cpp reads and writes the layer JSON but picked up the declarations
through another header, and the same for boost::to_lower.
A painted relief did not survive closing the project: nothing about the feature
was written to the .3mf, so the layers, their textures and the paint mask were
all lost on save. The layer struct's cereal save()/load() serve the undo/redo
stack only, which is one session and one binary stream.
Write the layer stack and the per-volume options as JSON in the archive, and
each layer's texture as the image file it was loaded from, both referenced by
path - the same split EmbossShape already makes for its SVG. The image stays
out of the XML deliberately: it is binary and routinely megabytes, and base64
in an attribute would bloat the one file every reader parses just to list the
objects. JSON rather than the cereal stream next to it because that one is
positional and unversioned, which would make every future field a
project-breaking change; an unknown key is ignored and a missing one keeps its
default, so a project written by either side keeps loading.
The paint masks go in as one attribute per layer slot, mirroring paint_color.
Older readers ignore attributes they do not know, so a project written here
still opens in a build without the feature - it loses the relief, which is all
it could have done with it anyway.
The layers are restored while the volume is being built rather than after the
archive is walked: volumes are constructed only once the whole archive has been
read, so a hook placed after the walk ran before any volume existed and
restored nothing.
GCodeProcessorResult declares a copy assignment, which suppresses the implicit
move assignment, so `*result = std::move(processor.extract_result())` binds to
the copy and duplicates the whole moves array: 96 bytes per move, measured at
147.7 MiB in one allocation for a 597k facet model at 0.08 mm, with the source
staying alive until the export returns.
Add the move assignment. It assigns exactly the same 39 members in the same
order as the copy, including the ones the copy deliberately leaves alone, so a
target that carries its own filament maps and nozzle type keeps them. A move
constructor is intentionally not added: the type holds a std::mutex, so it is
neither copy nor move constructible today and a partial one would leave ten
members uninitialised.
reset() now releases the two print sized vectors rather than clearing them. Its
callers are the paths that discard a result, so the memory went back only at the
next slice.
ViewerImpl::reset() does the same for the two vectors sized by the print, which
is what keeps a discarded preview resident, and counts m_vertices_colors in the
reported CPU memory, where it was missing.
simplify_path, simplify_multi_path and simplify_loop each copied the whole
PrintConfig, 10520 bytes with around 200 heap owning members, once per extrusion
entity, from inside the parallel_for over all layers. LayerRegion.cpp already
binds a reference a few hundred lines above. Three more sites in Layer.cpp do the
same thing.
The tree support changes in the same area: draw_circles() dropped layers from an
owning vector without deleting them, so every re-slice leaked the layers above
the topmost overhang. std::stable_partition rather than std::remove_if, because
remove_if leaves moved-from duplicates of kept pointers in the tail and deleting
those would be a double free. The collision and avoidance accessors returned
ExPolygons by value out of a cache that hands them out by reference, several
times per node per layer. The preview cache is released when the support step
finishes instead of at the start of the next slice; nothing reads it in between.
set_shared_object() now releases the layers the object still owns before it
starts aliasing another object's. Both clear functions are no-ops once the alias
is set, so this cannot free the sharee's layers.
PrintObjectSlice.cpp: eps was a function local static inside a parallel_for, so
slice_closing_radius was latched once per process. This one changes output for
projects that override it per object, or on a re-slice after editing it. The old
behaviour was undefined: whichever object won the race set it for the session.
description:Use when updating, filling, reviewing or fixing OrcaSlicer translations in localization/i18n — running scripts/run_gettext --full, translating empty or new msgids in the .po catalogs, AI translation passes and their "# AI Translated" / "# Needs human review" comments, plural forms, msgctxt homonyms, and keeping catalogs consistent with the localization glossary (OrcaSlicer_WIKI localization_glossary.tsv), including adding new glossary terms. Also use for msgfmt or run_gettext failures, placeholder or line-break mismatches, and reviewing a translation PR against the glossary.
---
# OrcaSlicer translations
The UI strings live in gettext catalogs: `localization/i18n/OrcaSlicer.pot` is the template, and
`localization/i18n/<lang>/OrcaSlicer_<lang>.po` holds each language. The `en` catalog is empty on
purpose, so skip it. Recurring terms are fixed by the
- **Batch files go in a scratch directory, not the repository.**
- **Each entry's `id` identifies it,** so the translation file need not repeat the English. `apply`
also accepts `msgid` with `msgctxt`, or the pending file itself with its `msgstr` fields filled.
- **Translate every entry in the batch.** A blank `msgstr` is skipped, not cleared.
- **Next batch:** applied entries leave the list, so run `pending` again at offset 0. Use `--offset`
only to step past entries you deliberately leave blank.
- **What each entry carries:**
- `glossary`: the terms it contains, with the language's rendering.
- `note`: the developer's `TRN` comment.
- `flags`: `c-format` or `boost-format`.
- `description`: present when a sense has to be chosen.
3. Read every apply message:
- **REJECTED:** a hard rule broke. Fix the translation and apply the corrected items again.
- **WARNING:** something worth a second look, such as a glossary rendering not found, ending
punctuation, or snake_case. A glossary warning on an inflected form you checked is fine.
Otherwise correct the translation (`apply --overwrite` replaces your own entry), or add a
`review` reason.
`apply` adds `# AI Translated` to every entry it writes, and `# Needs human review: <reason>` where an
item has `review`. On an `--overwrite`, `"review": ""` removes an earlier note. Never add either to a translation a person wrote. `--human` is for applying their
text.
**The work is large:** every new string, times every language. When subagents are available, give each
one a language or a few. Each agent only touches its own catalogs, so they can run
in parallel. Settle step 3 first. Give each agent the language list, this skill's path, the rules file,
and these instructions:
- Fill every empty entry.
- Don't add glossary rows.
- Report what it could not translate, the review notes it added, and any term it thinks the glossary
still lacks.
Add the terms they report yourself, then have the affected languages revisit the entries.
### 5. Verify
```bash
python3 $T check --lang <L> --warnings # every AI-translated entry, plus msgfmt --check-format
```
- **Run the full gettext again.** It must exit 0 and leave the catalogs as the pass left them.
- **Run `python generate_glossary.py --check` in the wiki clone.**
- **Errors on entries you didn't write predate the pass.** Report them rather than fixing them in the
same change.
### 6. Report
Report:
- the entries filled, per language;
- the review notes added;
- anything left empty, and why;
- the glossary rows added or filled.
The catalogs (and `OrcaSlicer.pot`) and the glossary (`localization_glossary.tsv` and `.md`) are
separate changes in separate repositories. The wiki change should land with, or before, the catalog
change. Commit or open PRs only when asked.
## Rules
1. **Run the full gettext first.** Translate against the regenerated template, never a stale one.
2. **The glossary is binding.** Use the language's rendering for each term, in the right sense. When a
rendering looks wrong, use it and flag the entry. Then raise the change with the user, rather than
deviating in one string.
3. **New recurring terms go into the glossary before the translations that use them.** Without a wiki
clone, stop and tell the user (step 3).
4. **Fill, don't rewrite.** A fill pass writes only empty and fuzzy entries.
- Existing translations, human or AI, change only on request (`--overwrite`).
- A glossary violation in a human translation is reported, not changed.
5. **Machine output is labelled.** Every AI translation gets `# AI Translated`; uncertain ones also get
`# Needs human review: <reason>`. Never use `fuzzy`.
6. **Never edit `msgid`, `msgctxt`, the header, or obsolete `#~` entries,** and never fix the English in
a translation. Report source problems (see the rules file).
7. **Write catalogs only through `po_tool.py` or gettext.** PO libraries such as polib reflow unrelated
entries, and hand edits skip the validation.
## Changing an established term
A glossary cell is usually copied from the catalog's own label. When the label is wrong, the glossary
inherits the mistake, so fix both in the same change:
1. **Check the catalog's usage first.** `lookup --lang <L> "<term>"` searches the English, and with
`--msgstr` it searches the translations.
- When most entries already use the correct form, move the cell to it.
- When the wrong form is used consistently throughout, leave both as they are and ask a
translator.
2. **Update the cell:** `glossary-set --term "<Term>" --replace --set <L>=<value>`.
3. **Rewrite the entries that still use the old form,** only where the term has that meaning.
- Write `[{"id": "<id from lookup>", "msgstr": "<full corrected text>"}]` and run
`apply --overwrite`.
- The rewritten entries are marked `# AI Translated`.
## Reviewing a translation change
Review it for these:
- glossary compliance (`check --scope all --warnings` on the language, then read the hits);
- placeholders and line breaks (`check` errors);
- `# AI Translated` kept on text a person rewrote, or added to text a person wrote;
- purged obsolete entries;
- reflowed unrelated entries;
- plural forms short of `nplurals`.
When a reviewer disputes a term, compare it with the glossary first. A machine translation that follows
the glossary is correct even if the term itself is debatable. Then the fix is a glossary change, made
with the translators, followed by the catalogs.
## Source of truth
When this skill and the checkouts disagree, the checkouts win:
- `scripts/run_gettext.sh` / `.bat` for extraction and merging;
- `localization/i18n/list.txt` for the scanned sources;
- the wiki's `localization_guide.md` and `localization_glossary.md` / `.tsv` for the rules and terms;
- `AGENTS.md` (Localization & translations) for the repository's conventions.
## Editing this skill
State rules and procedures, not the state of the catalogs: no counts, lists of untranslated strings or
per-language defects. Language-specific terminology belongs in the glossary's *Language specifics*
run:echo "::warning title=Older deps::No cache matches this PR's deps/, so clang-tidy parsed against ${{ steps.newest_cache.outputs.cache-matched-key }}. If it reports errors in code that uses a dependency, rerun clang-tidy once the base branch's Build all has saved the new deps."
- Keep code concise and clear. Manually simplify AI generated bloated codes before review.
- Include targeted tests or documented verification for behavior changes, especially in slicing logic, profiles, formats, and GUI defaults.
- For profile changes (`resources/profiles/<Vendor>/**`), check that `version` in the sibling `resources/profiles/<Vendor>.json` was bumped.
- For new or changed UI strings, check they follow [Writing translatable strings in the source](#writing-translatable-strings-in-the-source): no concatenated fragments, plural forms for counts, contexts for ambiguous words.
- For translation changes (`localization/i18n/**/*.po`), check that recurring terms match the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/developer_reference/localization_glossary.md) for that language.
Catalogs live in `localization/i18n/<lang>/OrcaSlicer_<lang>.po`; the template is `OrcaSlicer.pot`.
See the [Localization guide](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/developer_reference/localization_guide.md) for the human-facing version of these principles.
### Writing translatable strings in the source
Translators see one msgid at a time and cannot change the code around it, so each message must be complete in a single msgid. Use the wrapper that fits instead of assembling text from pieces:
- **One msgid per sentence, with placeholders.** Never build a message by concatenating translated fragments (`_L("A") + name + _L(" is B")`) or by splicing a translated word such as `_L("left")` into another sentence: the translator can't reorder or inflect the parts. Write `format(_L("%1% is too close to others"), name)`.
- **A count needs a plural form.** Don't write `file(s)` or leave a verb unchanged after a number. Use `_L_PLURAL("%1% object", "%1% objects", n)`. libslic3r has no plural lookup, so word the message so it doesn't depend on a count, or build it in the GUI.
- **An ambiguous word needs a context.** When a short string means different things in different places (verb or noun, a mode name, a CAD term), give each sense its own context with `_L_CONTEXT`/`_u8L_CONTEXT`/`L_CONTEXT`. Keep the most common sense without a context so its existing translations stay valid.
- **Mark every user-visible string** (`_L`/`_u8L`, or `L` where it is translated later) and list new source files in `localization/i18n/list.txt`: files that aren't listed are never extracted.
- Add a `// TRN` comment above a new string whose meaning or placeholders aren't obvious.
- Never choose behavior by comparing translated text; compare ids or flags.
See `.claude/skills/orca-translations/references/source-audit.md` for the full checklist.
### Terminology
- Use the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/developer_reference/localization_glossary.md) as the source of truth for recurring terms, so the same English term is always rendered the same way within a language, and terms that must stay in English (brand/product names, acronyms, materials, file formats, G-code tokens, macros/variables/identifiers) are not translated.
@@ -86,7 +86,8 @@ The [wiki](https://www.orcaslicer.com/wiki) aims to provide a detailed explanati
## Stable Release
📥 **[Download the Latest Stable Release](https://github.com/OrcaSlicer/OrcaSlicer/releases/latest)**
Visit our GitHub Releases page for the latest stable version of OrcaSlicer, recommended for most users.
Visit our GitHub Releases page for the latest stable version of OrcaSlicer, recommended for most users.
Windows release binaries are code signed; see our [Code signing policy](#code-signing-policy).
## Nightly Builds
@@ -244,6 +245,16 @@ OrcaSlicer began in that same spirit, drawing from BambuStudio, PrusaSlicer, and
The OrcaSlicer logo was designed by community member [Justin Levine](https://github.com/jal-co).
# Code Signing Policy
Free code signing provided by [SignPath.io](https://about.signpath.io), certificate by [SignPath Foundation](https://signpath.org).
Windows release binaries are built by GitHub Actions from this repository and signed through the SignPath Foundation program. Every signing request is manually approved.
**Team roles:** [SoftFever](https://github.com/SoftFever) is the committer, reviewer and approver for all signed releases.
**Privacy policy:** See the [OrcaSlicer privacy policy](https://www.orcaslicer.com/privacy-policy/).
# License
- **OrcaSlicer** is licensed under the GNU Affero General Public License, version 3.
echo" -u: install system dependencies (asks for sudo password; build prerequisite)"
echo" -l: use Clang instead of GCC (default: GCC)"
echo" -L: use ld.lld as linker (if available)"
echo" -- CMAKE_ARGS...: pass everything after '--' to the cmake configure step of the deps (-d) and OrcaSlicer (-s/-t) builds,"
echo" e.g. -DCMAKE_CXX_FLAGS=\"-O2 -march=native\", -DCMAKE_CXX_COMPILER=g++-14, -DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=mold."
echo" They come last on the cmake command line, so they override what -l and -L set."
echo"For a first use, you want to './${SCRIPT_NAME} -u'"
echo" and then './${SCRIPT_NAME} -dsi'"
echo"For a GitHub Actions-like Linux build locally, use './${SCRIPT_NAME} -g -istrlL'"
echo"Use './${SCRIPT_NAME} -gF -istrlL' to rebuild the cached runner image first."
echo"Set ORCA_CONTAINER_CLI, ORCA_DOCKER_IMAGE, ORCA_DOCKER_BASE_IMAGE, or ORCA_DOCKER_CMAKE_VERSION to override the container runtime, cached image tag, base image, or CMake version."
echo"DEPS_EXTRA_BUILD_ARGS and ORCA_EXTRA_BUILD_ARGS add whitespace-separated cmake configure arguments to the deps and OrcaSlicer builds; prefer '--' for arguments that contain spaces."
}
SLIC3R_PRECOMPILED_HEADERS="ON"
@@ -131,6 +135,20 @@ while getopts ":1j:J:bcCdDeFghiprstulL" opt ; do
esac
done
# getopts stops at '--'; whatever follows is handed to cmake unchanged.
if[[${OPTIND} -le $#]];then
separator_index=$((OPTIND-1))
if[["${!separator_index}" !="--"]];then
echo"Unexpected argument '${!OPTIND}': cmake arguments must follow '--', aborting."
message(AUTHOR_WARNING"Plural form ${tmp} found in config options of ${PREFIX}. This works as before but is now deprecated. Please only use singular forms INCLUDE_DIR and LIBRARY, and update your find scripts for LibFindMacros > 2.0 automatic dependency system (most often you can simply remove the PROCESS variables entirely).")
message(WARNING"The find module for ${PREFIX} does not provide version information, so we'll just assume that it is OK. Please fix the module or remove package version requirements to get rid of this warning.")
set(vars"${vars}You may use CMake GUI, cmake -D or ccmake to modify the values. Delete CMakeCache.txt to discard all values and force full re-detection if necessary.\n")
if(version_unsuitable)
set(msg"${PREFIX} ${${PREFIX}_VERSION} was found but")
if(exactver)
set(msg"${msg} only version ${findver} is acceptable.")
else()
set(msg"${msg} version ${findver} is the minimum requirement.")
endif()
else()
if(missing_headers)
set(msg"We could not find development headers for ${PREFIX}. Do you have the necessary dev package installed?")
elseif(some_files)
set(msg"We only found some files of ${PREFIX}, not all of them. Perhaps your installation is incomplete or maybe we just didn't look in the right place?")
if(findver)
set(msg"${msg} This could also be caused by incompatible version (if it helps, at least ${PREFIX} ${findver} should work).")
endif()
else()
set(msg"We were unable to find package ${PREFIX}.")
endif()
endif()
#FatalerroroutifREQUIRED
if(required)
set(msg"REQUIRED PACKAGE NOT FOUND\n${msg} This package is REQUIRED and you need to install it or adjust CMake configuration in order to continue building ${CMAKE_PROJECT_NAME}.")
message(FATAL_ERROR"${msg}\n${vars}")
endif()
#Otherwisejustprintanastywarning
if(NOTquiet)
message(WARNING"WARNING: MISSING PACKAGE\n${msg} This package is NOT REQUIRED and you may ignore this warning but by doing so you may miss some functionality of ${CMAKE_PROJECT_NAME}. \n${vars}")
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.