Commit Graph
32302 Commits
Author SHA1 Message Date
harrierpigeon 3752144995 Belt: do not refuse a brim because the prime tower setting is on
enable_prime_tower stays on for any multi-filament project, but a belt
printer never prints the classic tower and the belt purge prism is an
ordinary object that never takes a brim, so every brim on a multi-filament
belt print was refused for nothing.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:07:42 -05:00
harrierpigeon 2aa4122aae Belt: check object height against the gantry clearance again
validate() skipped the build-volume height check whenever the machine-frame
transform was active, which is every shipped belt profile, so a 400 mm
object passed on a 300 mm printable_height. The transform only changes how
the height is written to G-code; the clearance check from f682ab5cd3
applies regardless.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:07:42 -05:00
harrierpigeon 97034b22f6 Belt: remove the tagged diagnostic logging
Drops the [BELT-DEBUG], [BELTRACE], [BELT-CALIB] and [BELT-PREVIEW] log
lines, the SLIC3R_BELT_DIAGNOSTIC_LOG blocks, and the counters and
temporaries that existed only to feed them. Six of the purge tower lines
logged at warning level, which is Orca's default, on every plan. Raised in
Hanif Koh's review of #14394.
2026-10-02 01:04:33 -05:00
harrierpigeon 3776739cb5 docs: drop the private build notification note
The build-notify workflow is a local tool of one contributor and does not
belong in the shared agent instructions.
2026-10-02 01:02:58 -05:00
harrierpigeon 93fc4b31e9 Belt: show the axis remap options in Develop mode only
preslice_remap_*, preslice_remap_global and gcode_remap_* describe the
printer's kinematics and are set once by its profile. A wrong value sends
the gantry outside the machine (a user preset with the pre-slice remap in
place of the G-code remap emitted gantry moves to Y=646 mm), so they are no
longer offered in Expert mode.
2026-10-02 01:02:58 -05:00
harrierpigeon 33fc99785a Belt profiles: print without a z-hop by default
On a belt printer a lift is a move along the belt axis (0.4 mm / sin 45 =
0.57 mm of belt travel out and back on every hop), not a lift away from the
part. The three belt printer bases now ship z_hop 0, the IR3 V2 leaf no
longer restates 0.4, and the BabyBelt Pro and IR3 V2 filaments stop
overriding the printer with filament_z_hop 0.4. The option stays editable.
2026-10-02 01:02:57 -05:00
harrierpigeon f219431935 GUI: the plate is open along Y for containment tests on an infinite-Y belt printer
PartPlate's containment tests treat the plate as open along Y on a belt printer with
belt_printer_infinite_y, so a long part is no longer flagged outside the plate in Prepare
while the slicer and the G-code checks accept it. The check reads the printer preset
through the app object, which does not exist headlessly, so it is guarded on the plater.
2026-10-02 01:00:36 -05:00
harrierpigeon b8a8e38ceb G-code: no crossing-perimeter avoidance while travelling on a brim apron layer
Crossing-perimeter avoidance dereferenced the (null) layer while travelling on a brim
apron layer.
2026-10-02 01:00:36 -05:00
harrierpigeon d755580c99 Belt brim: the purge prism never gets a brim, whatever its config says
The prism's generator already sets no_brim; PrintObject::has_belt_brim() now also ignores
any brim setting on the prism (belt_purge_tower_object), so a brim on the parts beside it
never blocks purging.
2026-10-02 01:00:36 -05:00
harrierpigeon e8926efbe3 Belt purge: detect filament changes by scanning the ordering, not the first layer's flag
ToolOrdering::has_wipe_tower() reads the first layer's flag. On a belt the first layer may
be a brim apron band, which carries neither object nor support and never gets the flag, so
with a brim the purge plan returned early and nothing was purged. Scan the layers for a
change.
2026-10-02 01:00:36 -05:00
harrierpigeon 2ae41bb0ef Painting: guard the top/bottom projection erase when no shell layers are requested
With top_shell_layers = 0 the `top` vector is never filled and erasing its begin() was
undefined (found by fuzzing on a painted object dropped below the plate).
2026-10-02 01:00:36 -05:00
harrierpigeon 6a6ceb2612 G-code: key the island tour cache on the island layout, never index past the islands
The per-filament island tour was cached by island centroids only. A later layer with the
same centroids but fewer islands (thin walls, negative volumes) reused the stale visit
list, whose catch-all index pointed past the layer's islands, and extrude_perimeters read
freed memory (three fuzz crashes, planar and belt). The per-instance island layout is part
of the cache key and the use site never indexes past the islands.
2026-10-02 01:00:36 -05:00
harrierpigeon 6be7911e40 CLI: survive a project with missing printable_height or fewer filaments than loaded
Found by fuzzing the headless slicer:

- A BBS-style 3MF without Metadata/project_settings.config segfaulted the CLI silently on
  the missing printable_height option.
- A project saved with fewer filaments (or filament groups) than --load-filaments overran
  the filament variant tables (segfault in the variant match) and then hit an uncaught
  ConfigurationError from set_with_restore_2 (std::terminate). The tables are regenerated
  for the filaments the project did not know about, the destination vectors grown first
  (only from a non-empty source), the match bounded, and a failure becomes a CLI config
  error.
2026-10-02 01:00:36 -05:00
harrierpigeon d57549159d 3MF loader: refuse non-finite vertex coordinates
A 3MF vertex with a nan/inf coordinate was accepted by both parsers and crashed qhull in
ModelVolume's convex hull while the file was still loading. Both vertex handlers refuse it,
and volume generation checks again whichever parser produced the geometry. The main
parser's _stop_object_xml_parser keeps a message a handler already set.
2026-10-02 01:00:36 -05:00
Clifford GarwoodandClaude Opus 5 c9b2b89b2b Correct the variant-keying note on the wipe tower estimate
filament_minimal_purge_on_wipe_tower became variant-keyed upstream, which
invalidated the comment claiming none of full_config's keys were.
WipeTower2::extract_wipe_volumes indexes it by raw filament slot. Not reachable
with shipped presets; noted rather than worked around.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 21:40:39 -04:00
Clifford GarwoodandClaude Opus 5 6c46942c57 Fix the mode name being cleared when a suggestion is picked
ComboBox::SetLabel is overridden and writes the text control when the text
control is shown, which it is on an editable combo. Clearing the label after a
pick therefore erased the name. Removed; the label is never written on this
control.

Suggestions are also rebuilt when the list opens. Built in add_row they filtered
against only the rows that already existed, so a row was offered names the rows
below it had taken.

Drops the 1px inset on the Primary label, which matched a frame the name field
no longer has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 21:08:58 -04:00
Clifford GarwoodandClaude Opus 5 1f8310c727 Merge upstream, including texture displacement and the instance lock
138 commits. The count is large because texture displacement merged with its
whole branch history behind it, going back to July, alongside config and preset
file locking across instances, a foundation for configurable printer agent
connections, and a day of smaller fixes and CI work.

Two conflicts, both the same shape: each side had appended to a sorted list and
git could not choose an order. libslic3r's CMakeLists gained InstanceLock
alongside our IMEXHelpers and IMEXZones, and the preset bundle loading test
gained an include for ParallelResolve alongside ours for IMEXHelpers. Both sides
kept, alphabetical. No logic conflicted.

Verified: 740 targets build clean under -Werror, and the Release suite passes
1740 of 1740, up from 1665 -- the 75 new cases arrived with the merge and all
pass. That mattered more than usual here, since preset loading and config
locking are both areas the IMEX preset code touches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 18:46:19 -04:00
Clifford GarwoodandClaude Opus 5 3c7b62bbca Offer the mode names this printer can carry in the name field
The name field becomes an editable ComboBox, the same pattern the sidebar uses
for parameters like sparse infill anchor length: predefined entries in a
drop-down, with the text still typeable. The mode table is authored for whatever
hardware the user has, so a closed list would be wrong -- nothing in the slicer
reads a mode's name except as the key a plate stores -- but the conventional
names are worth offering rather than leaving everyone to retype them.

What is offered follows the tool grid rather than a fixed list. Four carriages
get iq-copy and iq-mirror; multicolor needs a Span partner beside the primary
and a second gantry to copy the pair onto, so mc-copy and mc-mirror appear only
on a grid that can hold one. imex_resolve_routing() already refuses a multicolor
mode with no Span on the primary's gantry, and suggesting a name it would then
reject is worse than not suggesting it. Names a row already uses are dropped, so
the list only ever offers what is still free.

Two things about ComboBox matter when it is editable, which nothing else in the
tree does -- the other 78 call sites all pass wxCB_READONLY:

GetValue() returns the drop-down selection whenever there is one, so a name
typed after picking a suggestion would be silently discarded. Every read goes
through GetTextCtrl() instead. Field.cpp reconciles the same way for its own
open enums.

The constructor hands its value to TextInput as the LABEL -- the small
right-aligned slot a unit like "mm" occupies -- because a read-only combo hides
the text control and shows the label in its place. Left there, the name rendered
as a greyed echo beside the hint while the field itself sat empty. The combo is
built empty and the value written to the text control, and the selection handler
clears the label again afterwards, since SetSelection() writes there too.

The mode column widens to 176 to leave room for the drop-down arrow, with the
header spacer deriving from the same constant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 18:37:53 -04:00
Kris Austin 9d71125495 ci: allow running the Main build status workflow manually (#16057) 2026-10-01 18:25:12 -03:00
Kris Austin 88f05d3060 ci: move the nightly Build all to the quietest hour for macOS runners (#16053) 2026-10-01 17:58:00 -03:00
Kris Austin 4743d00793 ci: README build badge no longer shows failing when a queued main build is cancelled (#16054) 2026-10-01 17:56:57 -03:00
SoftFever 6a90946294 Keep the sketch value field on top of the dimension labels
Dimension labels drawn after the value field each lifted their own
window to the front, so a label could print over the number being typed.
2026-10-02 01:11:04 +08:00
Claude 410399fdb5 Design tab: follow Orca's theme, scale, mouse settings, dialogs and undo
Felix14-v2's review of OrcaSlicer#16019 found the tab behaving as a world of its own.

- Icons: the design_* glyphs were drawn in a fixed light grey, made for the dark ribbon, and the
  toolbar re-tinted some of them by rebuilding the bitmap from a wxImage, which drops the HiDPI
  scale factor Orca sets on Windows: at 150 % the icons came out half again too large for buttons
  that were sized in raw pixels, overlapping and clipped. The glyphs now use Orca's sidebar icon
  grey (#949494), which the icon cache maps per theme, nothing is re-tinted, toolbar glyphs drawn
  for Prepare's light toolbar use their "_dark" twin, and every size is in DIP.
- Theme: the chrome colours were read once, at construction, and nothing in the tab answered a
  theme switch, so switching left light surfaces and unreadable text in a dark tab and the other
  way round. The colours are now {light, dark} token pairs; MainFrame::on_sys_color_changed
  reaches DesignPanel::on_sys_color_changed, which moves every token colour onto the other
  theme's, runs the app's dark pass and re-rasterises the icons. Card borders are StateColors,
  resolved at paint time.
- Scale: MainFrame::on_dpi_changed reaches DesignPanel::msw_rescale, which re-rasterises every
  icon (buttons, flyout rows, card headers, the tree's image list, now sized from its bitmaps)
  and re-measures the Orca widgets.
- Mouse: the canvas no longer forces middle-drag to orbit and right-drag to pan; it reads the
  drag actions in Preferences > Control like Prepare. Left-drag is shared with picking, so the
  whole-body rubber band takes Shift+left-drag while left-drag is given to the camera.
- FPS counter: ImGui's display size is shared and only refreshed when a canvas sees its own
  size change; the Design canvas now re-announces its size when the tab is entered (and the
  editor canvas when it is left), as Plater does between Prepare and Preview.
- Viewport text: the status line and the tool readout were top-level popups over GL. A popup
  does not follow its frame, so the empty-canvas hint floated over other applications, and the
  readout was never taken down with the tab. Both are drawn by the canvas in the tool's ImGui
  pass now, with the theme's overlay style.
- Dialogs: messages use MessageDialog/RichMessageDialog; Add/Edit Variable is one Orca dialog
  with Name and Expression fields instead of two native text prompts; the Text dialog uses
  TextInput, ComboBox and CheckBox (its height is a TextInput: SpinInput is integer-only), and
  enumerates the installed fonts once per session. The ribbon's Confirm/Cancel, the reference
  pick buttons and the expression buttons are Orca Buttons; the variable actions are icon
  buttons like the other cards'.
- Undo: the tab's own Undo/Redo buttons are gone. The top bar's Undo/Redo drive the Design
  history while the tab is shown, greyed to what an undo would do, as Ctrl+Z and Edit already
  did.
- The first build of the tab logs how long each phase took ("Design tab build: ..."): it is
  under a second here but was reported at about fifteen on Windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
2026-10-01 15:38:24 +00:00
Ian Chua 2f4758446a feat: add foundation for configurable printer agent connection (#16027)
* feat: add foundation for configurable printer agent connection

* fix: regression after merge

* chore: remove duplcate http include

* fix: remove unrelated moonraker.cpp changes

* chore: port add_platform_root_certificates to 15711

* fix: clean diff
2026-10-01 22:50:56 +08:00
HanifKoh c023632a7d Lock Config and Preset Files Across Instances and Write Them Atomically (#15861)
* Lock Config and Preset Files Across Instances and Write Them Atomically

Every running instance shares one OrcaSlicer.conf and one user preset
tree, and nothing kept their writers apart. Two instances saving at the
same moment, or the cloud preset sync thread writing while the GUI thread
saved, could interleave, and a reader in another instance could open a
preset JSON or .info file between truncate and close and get a partial
file, dropping that preset for the session with a parse error.

Add InstanceLock, a scoped guard that serialises the threads of one
process through a recursive mutex and other processes through an advisory
OS file lock: flock on POSIX, held on the guard's own descriptor so no
other close in the process can drop it, and LockFileEx on Windows. The
outermost guard opens the lock file and closes it on release, so nothing
stays open between saves and a data dir can be removed once nothing is
saving into it; the file itself is kept, since deleting it would let a
third instance lock a fresh file while the second still holds the old
one. It is best effort: when the lock file cannot be opened or locked, or
another instance still holds it after a second, the guard logs once and
lets the write proceed, then leaves the file alone for ten seconds, so a
hung instance never blocks every other one and a holder stuck in a
debugger does not cost a stall per save. The guard sits at the leaf
readers and writers: set_sync_info_and_save() calls save_info() under the
preset collection mutex, so a batch lock around save_user_presets() would
invert the order against the sync thread. The user preset scan reads its
files on worker threads without the guard, since the mutex would
serialise them, and takes it per file in the serial commit step, so a
save never waits for the whole scan. Each read keeps the bytes of the
preset and its .info as they were before parsing; commit compares them
with the disk under the guard and reads a file that changed again, so it
never deletes or writes back over another instance's newer save, nor
installs a .json and .info from two different saves; a preset another
instance removed in the meantime is not installed. Without the guard, in
a cool-down, the scan still loads the presets but leaves their files
alone: an unreadable file stays for the next scan, and a derived
compatible printer is not written back. Read-only scans, which is what
the CLI does, take no lock and create no lock file.

AppConfig holds OrcaSlicer.conf.lock in load() and save(); load is
included because the Windows path restores from the .bak copy. Every
user preset writer and reader holds user.lock: Preset::save(), which
writes no .info when the preset itself could not be written, since an
.info without its preset reads as a cloud deletion request, save_info(),
reload() and remove_files(), each preset the scan commits, the
bundle metadata reads and write, the .info removal after a cloud-confirmed
delete, the orphaned-.info scan on the sync thread, the bundle folder
removal on unsubscribe and the physical printer writers and delete
paths. A bundle import extracts under cache/ into a folder per process
and per import, where no scan reads.

Preset JSON, .info, bundle metadata, physical printer and config files,
and the caches and state files that already used a temporary by hand,
now go through write_file_atomically(), which writes <file>.<pid>.<n>.tmp
beside the target and renames it over, so a reader that never waits sees
a complete old or new file. A symlink is followed; a target that is not
a regular file is written in place; and when no temporary can be created
beside an existing target, or the rename itself is refused, by a Windows
reader holding the file open or a mount that cannot replace in one step,
the helper writes in place as before, since losing the save is worse
than a torn read. On POSIX the rename replaces the
target atomically where the old code removed it first and left a window
with no file at all; only a mount that refuses a one-step replace gets
the old remove-then-rename. A crash between temporary and rename leaves
the temporary behind, which no scan reads. Preset::save() returns
whether it wrote the preset, so the scan counts a compatible printer it
could not write back as an error. A failed config write keeps
the config dirty, and the idle handler waits ten seconds before retrying
while an explicit save always tries.

* Run the Cross-Process Lock Test on Every Platform

The test that checks the guard yields to a lock held elsewhere forked a
child to hold it, so it was left out on Windows. The OS lock belongs to
the handle on Windows and to the open file description elsewhere, so a
second handle in the same process is refused like another instance
would be. The test now holds the lock that way and runs everywhere.
2026-10-01 22:18:21 +08:00
SoftFever 96bf754af9 Merge branch 'main' into claude/inspiring-knuth-7cp6pk-upstream 2026-10-01 21:57:57 +08:00
SoftFever b102ae3aaa delete unwanted docs 2026-10-01 21:56:56 +08:00
SoftFever 1046851b50 add orca-wxwidgets skill 2026-10-01 21:56:56 +08:00
Lam Wei Lun 03377a0242 Missing cassert include (#16039) 2026-10-01 10:34:43 -03:00
Kris Austin e7c0e2cd82 ci: let main builds finish instead of cancelling them on every merge (#16044) 2026-10-01 10:34:13 -03:00
Kris Austin d1d14329d9 fix: exporting a sliced print again gives different G-code (#16025)
extrude_infill() and extrude_support() reversed the layer's extrusion
entities in place while chaining them, so each export started from the
previous one's reversed toolpaths, and each copy of an object from the
copy before it. The export-time region lists now hold const pointers,
and chaining reverses a clone instead.
2026-10-01 08:08:03 -03:00
HanifKoh 236a8786ef Keep the Orca Cloud Agent Tests Out of the System Keychain (#15980)
The display-name tests call set_user_session(), which persists the session.
The agent they built was in keychain mode, so every run saved a fake
OrcaSlicer/Auth session into the system keychain of whoever ran the tests,
replacing their real Orca Cloud login on any desktop with a working keychain.

Move them next to the other agent tests and build the agent the same way:
encrypted-file mode with a throwaway config directory.
2026-10-01 16:58:52 +08:00
HanifKoh 92d30fbc55 Clamp Ironing Line Spacing to a Usable Minimum (#15949)
An ironing line spacing of 0 reached the fillers from a 3MF, the CLI or
the per-filament override, which has no GUI guard. Concentric ironing
then never finished slicing, because a zero inset never shrinks the
region, and rectilinear ironing was silently dropped. Tiny positive
values produced an unprintable number of lines.

Top surface and support ironing now clamp the spacing to the 0.05 mm
floor the process GUI guard already enforces, so these configurations
iron at that spacing. Spacings at or above the floor, including every
shipped profile, are unchanged. The concentric filler also returns early
on a non-positive step so no other caller can hang it, and the filament
settings page now resets a too-small override the same way the process
page does.
2026-10-01 16:53:16 +08:00
ExPikaPaka 391c53a8da Take the print config by reference while simplifying extrusions
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.
2026-10-01 09:15:15 +02:00
ExPikaPaka ae3e41eb02 Add a regression test for a cyclic component reference
Stores a painted cube, points its component back at the object that holds it and
expects the load to fail. Without the bound the test does not finish: the work
list grows until the process is killed.
2026-10-01 09:14:46 +02:00
SoftFever 3384daa6bc Fix bundled Python crashing on macOS 26 and older when built with Xcode 27 (#16035)
# Description

With Xcode 27, building deps on macOS 26 fails at the Python install
step with a segfault, and a libpython built with Xcode 27 crashes on
macOS 12–26 the first time anything calls `os.pipe()`, which every
plugin `subprocess` call does. The macOS 27 SDK declares `pipe2()` and
`dup3()` as macOS 27-only, and CPython 3.12 calls them without a runtime
check once configure finds them, so on older systems they resolve to
NULL. This keeps CPython on the `pipe()`/`dup2()` fallbacks it already
uses with older SDKs; upstream fixed it in 3.13+
([python/cpython#153711](https://github.com/python/cpython/issues/153711))
but not in 3.12.

No change for builds with Xcode 26 or older, or on Linux and Windows.

# Screenshots/Recordings/Graphs

<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->

## Tests

Rebuilt deps with Xcode 27 on macOS 26.6: the Python install step now
completes, the installed libpython no longer imports `pipe2`/`dup3`, and
CPython's `test_os`, `test_subprocess` and `test_posix` pass, apart from
one test that needs `_testcapi`, which `--disable-test-modules` leaves
out. Running CPython's configure against the macOS 26.5 and 27.0 SDKs
gives a byte-identical `pyconfig.h` for 26.5 with and without this
change, and for 27.0 with it. The x86_64 cross-build path was configured
on arm64 to confirm both of its configure runs pick up the change.

<!--
> 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)
2026-10-01 14:57:30 +08:00
SoftFever 377ebf2a03 Merge branch 'main' into claude/inspiring-knuth-7cp6pk-upstream 2026-10-01 14:51:35 +08:00
ExPikaPaka 92ab583ecc Reject 3MF component references that form a cycle
_generate_current_object_list expands component references through a work list
with no bound. An object whose component points back at itself, or a pair that
point at each other, makes the list grow until the process runs out of memory:
a few hundred bytes of XML take the slicer past 20 GB of resident size.

Bound the expansion by the number of objects in the file. A reference chain
longer than that has to revisit an object, so this rejects every cycle and no
acyclic file, however deeply nested. A second bound on the number of expanded
components stops an acyclic graph that fans out exponentially.
2026-10-01 08:50:22 +02:00
HanifKoh 6842d9c778 Keep the Plugin Tests' Python Packages Out of the Working Directory (#15981)
The plugin test fixtures start the interpreter before the test points
data_dir at its temporary directory, so PythonInterpreter creates
{data_dir}/python/packages and {data_dir}/log with an empty data_dir: a
python/ and log/ folder in whatever directory the tests run from. When that
is the test binary's folder, the next run's embedded-interpreter tests took
the stray python/ as their home and failed to start Python.

Give each fixture that initializes the plugin manager its own temporary
data directory, set up before initialize(), and only use the python/ folder
next to the test binary as the interpreter's home when it holds a standard
library.
2026-10-01 14:42:20 +08:00
HanifKoh 1a5bc8982d Stop Logged-Out Login Polling from Hitting the System Keychain (#15979)
With stealth mode off the home page asks for the login status every 2 s,
and while nobody is logged in that ends in clear_user_secret(), which
opened the system keychain and deleted the OrcaSlicer/Auth entry on the
UI thread every tick. A working keychain cost a D-Bus round trip per tick;
a keychain that never answers blocked the UI for 25 s per tick. It also
deleted a login another running instance had just saved, and ignored
use_encrypted_token_file, so opting out of the keychain did not help.

Remember whether this process read a secret from the store or wrote one,
and only then touch the store when a logged-out poll asks for a logout.
A logged-out instance never touches the keychain, and in encrypted-file
mode the poll no longer opens the keychain at all. A secret this process
cannot read, such as a token file encrypted for another OS user sharing
the data directory, is left alone by the poll. An explicit logout still
wipes both backends, so a token stranded by switching the token storage
option cannot sign the account back in later.
2026-10-01 14:41:50 +08:00
SoftFever 8aa5b1130a Allow nozzle variants to specify different cooling parameters (#16007)
# Description

Include variant-aware validation, temperature and pressure controls,
named nozzle selection, and filament sidebar refresh.

## Support nozzle-specific filament cooling and tuning

Filament presets can require different cooling and tuning for Standard
and High Flow nozzle variants. This change makes part and auxiliary
cooling, pressure advance, temperature limits, and multitool ramming
settings follow the selected variant, with matching UI controls and
profile validation.

The Snapmaker U1 profiles in #15755 / Snorca illustrate why this
matters:

| Profile | Standard cooling | High Flow cooling |
|---|---|---|
| Snapmaker ABS | Part fan: 15–15% | Part fan: 10–60% |
| Snapmaker PETG HF, 0.4 mm | Part fan: 20–40% | Part fan: 30–60% |
| Snapmaker PETG-CF | Part fan: 0–20%; auxiliary: 0% | Part fan: 5–40%;
auxiliary: 20% |
| Snapmaker PLA Matte | Auxiliary: 80% | Auxiliary: 100% |

These differences need to survive variant selection, editing, and
slicing. Shared single values continue to apply across variants, and
short filament arrays fall back to element zero, matching the loader.

### Additional changes

- Preserve named nozzle selections when refreshing the diameter
selector.
- Enable or disable the pressure-advance input for the selected variant.
- Initialize profile-validation slicing with the printer’s declared
nozzle volume type.
- Refresh filament controls after preset loading.

Profile changes remain separate in `u1-hf`.

### Validation

- All 13 focused variant-tool tests passed.
- Broader checks encountered an existing Windows path-separator
assertion failure, reproduced with upstream code.
- C++ build and GUI validation have not been run.

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-10-01 13:53:22 +08:00
SoftFever ba361d9882 Texture displacement (#14662)
## Summary

Adds a new paint-style **Texture Displacement** gizmo that stamps
grayscale
height-map textures onto a model's surface and turns them into real
relief -
engraved or embossed detail - either as a live preview or baked into
actual mesh
geometry.

You paint where a texture applies, stack up to **8 blended texture
layers**
(image-editor semantics: Add / Subtract / Multiply / Divide), choose how
each is
projected onto the surface (Triplanar / Cylindrical / Spherical / LSCM
unwrap /
From-view), and - for the LSCM projection - lay the charts out by hand
in a new
dockable 2D **UV Editor** pane. Coarse models can be **Subdivided** or
**Remeshed** first so there are enough vertices to carry fine detail,
and the
result is committed with **Bake**, restricted to the painted area only.

The tool only ever affects the **painted** region; everything left
unpainted
keeps its original surface, and bake blends the relief seamlessly into
it with no
remeshing or hole-filling at the seam.

---

## User-facing features

- **Paint the affected area** with Brush (circle/sphere), single-Face,
or
Connected-area flood fill; or **Select whole model** - reusing the
existing
`TriangleSelector` / `FacetsAnnotation` painting machinery, one full
mask per
  layer slot.
- **Up to 8 texture layers**, each with its own paint mask, texture, and
  parameters, combined in slot order like image layers.
- **Per-layer controls:** Depth, Tile size, Rotation, Midlevel
(bidirectional
  emboss/engrave), Smoothing, Edge smoothing, Invert, Blend mode, Tile
  (Repeat / Mirrored-repeat / decal), and Projection.
- **Projection methods:** Triplanar (blended, seam-free across sharp
edges),
  Cylindrical, Spherical, **Unwrap (LSCM)** - a real CGAL conformal
  parameterization - and **From view** (slide-projector decals).
- **UV Editor pane** for LSCM layers: move / rotate / scale / snap / cut
/ join
islands, average texel density, Checker and Distortion overlays, manual
mark-
  seam workflow, live model update while dragging.
- **View modes:** Normal (true displaced geometry = what Bake produces),
Fast
(GPU bump-shaded approximation of the active layer), Checker, Distortion
  heatmap, and an independent Wireframe toggle.
- **Mesh prep:** Subdivide (1–5* uniform 1->4 split) with cyan-wireframe
preview,
  and CGAL isotropic **Remesh** to a target edge length.
- **Texture library:** 10 shipped seamless 512×512 grayscale height
maps, plus
import of any PNG/JPG/BMP (converted to an 8-bit grayscale map and
copied into
  the user data dir so app updates can't clobber it).
- **Bake** runs in the background (off the UI thread); the preview is
free to
  explore and only Bake changes the real mesh.

---

## How it works (implementation)

- **Bake is accumulate-then-displace and topology-preserving.** The
output mesh
  has exactly the input's vertices and triangles in the same order; only
displaced vertex positions differ. Each layer is evaluated against the
**base
mesh** and folded per-vertex into a shared accumulator via its blend
mode, then
every touched vertex moves once along its precomputed undisplaced
normal. This
replaced an earlier sequential re-mesh-per-layer design that was the
root cause
of the "second layer never applies" bug and made blend modes impossible.
- **Boundary vertices are pinned.** Any vertex shared with an unpainted
triangle
is never displaced, which is what keeps bakes seamless with zero
remeshing.
- **LSCM unwrap** uses a new `MeshBoolean::cgal::parameterize_lscm()`
built on
CGAL's already-vendored `Surface_mesh_parameterization` package - **no
new
  external dependency**.
- **Fast GPU preview** perturbs the shading normal from the height
gradient
(analytic mm-per-mm slope for triplanar; Mikkelsen's
screen-space-derivative
method for the conformal LSCM path), so apparent depth matches the bake.
- **Background jobs:** preview compute and bake both run off the UI
thread on the
shared job worker (queued, not `replace_job`, so a preview never cancels
an
  in-flight bake); a generation counter discards stale results.
- **UV Editor** shares the app's single real `wxGLContext` and reuses
the
registered `flat`/`flat_texture` shaders; island drags update one affine
matrix
  per island rather than re-uploading geometry.


---


## Backward compatibility & constraints

- **Feature is fully gated behind the new gizmo** - it adds no new
default
behavior and does not touch existing slicing, profiles, or defaults.
Models
  that never open the tool are unaffected.
- **Cross-platform** - pure `libslic3r` / `libslic3r_gui` /
`libslic3r_cgal`
code; no new dependency and no `deps/` rebuild. (Built and tested on
Windows;
  no platform-specific APIs introduced.)
- **`.3mf` compatibility:** **baked** relief round-trips fine, since it
becomes
ordinary mesh geometry via the existing serialization path. **Unbaked**
paint
masks and layer definitions are **not yet serialized** - see
Limitations. No
  existing project data is affected.

---

## Testing

Unit tests in `tests/libslic3r/test_texture_displacement.cpp` - **run
and
passing** (7 cases, 116 assertions). Coverage:

- `decode_height_texture` round-trip
- Empty-layer no-op
- Full-cube uniform displacement
- Boundary-vertex pinning on a hand-built fan mesh
- Regression: a **second layer over the same area actually contributes**
(the
  bug the bake rewrite fixed)
- Table-driven check of all four blend modes
- The lowest painted layer ignoring its blend mode

`BUILD_TESTS` is `OFF` in the checked-in cache; enable to run:

```bash
cmake -S . -B build -DBUILD_TESTS=ON
cmake --build build --config Release --target libslic3r_tests -- -m
./build/tests/libslic3r/Release/libslic3r_tests.exe "[TextureDisplacement]" --order rand
```

---
2026-10-01 12:40:33 +08:00
SoftFever 2a9cb32c1f Fix bundled Python crashing on macOS 26 and older when built with Xcode 27
Building deps with Xcode 27 on macOS 26 failed at the Python install step,
and a libpython built with Xcode 27 segfaulted on macOS 12-26 whenever a
plugin started a subprocess.
2026-10-01 12:29:57 +08:00
158ada9054 Merge the finer Xplorer 0.4 tiers and the IQEX toolchange prime
Community work from Rob Niccum, adding 0.04mm Ultra Fine and 0.08mm Extra Fine
for the 0.4 nozzle, a toolchange prime on the three IQEX machines, and
recompressed cover art and build plate model that are byte-identical in content.

Bundle version bumped to 02.04.00.13 here rather than in his branch, so the two
of us were not editing the same line while his review was open. Without the bump
the updater refuses the bundle outright, and nothing in CI catches that.

Verified rather than assumed, since his own validator predates the parallel
printing code and the fine tiers changed shape during review: the full profile
check passes all five stages tree-wide, and both new tiers slice clean on all
four 0.4 machines, 8 of 8. His last revision dropped a bottom_shell_thickness
override that was making those tiers thinner against our common process rather
than thicker against his; the emitted G-code now carries the inherited 1.0,
which is what that fix was for.

Co-Authored-By: Rob Niccum <klober81@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 00:01:48 -04:00
Clifford GarwoodandClaude Opus 5 74409f1d3b Merge upstream, and move the IMEX placeholders onto the new def macro
Eleven commits, including a typed-config G-code export speedup, a printer agent
refactor that generalizes the infrastructure beyond Bambu, gyroid optimization,
and three CLI crash fixes.

One conflict, in PrintConfig.cpp. Upstream introduced a new_def macro and began
migrating the placeholder table onto it, adding curr_bed_type that way in the
same block where this branch had added imex_mode, imex_mode_index and
imex_mode_gcode in the older def = this->add(...) form. Both sides are kept and
ours are converted to the macro, which expands to the same three statements and
wraps label and tooltip in L() exactly as before, so nothing changes about what
is registered or what is translatable.

Note for anyone building this branch: the agent refactor adds a dependency,
LibDataChannel, so the deps tree needs dep_DataChannel built before the app will
configure. A distribution package of the same name will be found first if one is
installed, and the resulting error names a missing RelWithDebInfo location
rather than the wrong package, so point LibDataChannel_DIR at the dependency
prefix if that happens.

Verified: 789 targets build clean under -Werror, and the Release suite passes
1665 of 1665, up from 1630 before the merge -- the 35 new cases arrived with it
and all pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 23:55:21 -04:00
Clifford GarwoodandClaude Opus 5 dafc4ce39c Rework the parallel modes editor from the UI review
Addresses the interface notes on the IDEX/IQEX modes editor.

Add Mode moves from below the rows to the top of the panel, beside a "?" button
that now carries the overview text as its tooltip. At the bottom the button
shifted down the page every time a mode was added, so where it sat depended on
how many modes already existed. It is an Orca Button in the Confirm style, width
matched to the mode column it creates a row in, and the panel opens on the
legend rather than on a paragraph.

Remove moves out of the right-hand column, where it sat one icon away from
Edit -- a destructive control beside the one pressed most -- to under the name
field it deletes, and its icon becomes a boxed minus rather than an X, which
read as "close". Reset joins Edit in the right-hand column, which is now
top-aligned so the icons hold position regardless of row height. Tool tiles are
square at 24px, and the header spacer tracks that width so the column titles
stay over their columns when the grid changes shape.

The two text fields were landing on GTK's near-black default border, invisible
against the panel: measured 45,45,49 against a 43,43,43 background, where the
settings fields above use 74,74,81. wxTextCtrl cannot color its own border, so
each sits in a one pixel frame taking the color TextInput derives for the
theme, and carries wxBORDER_NONE so Windows and macOS do not draw a native edge
inside it. The G-code boxes also take the monospace face EditGCodeDialog uses.

Bed zone fills drop to roughly half opacity in the Standard theme. They cover
whole quadrants for a whole session, so at swatch saturation they dominate the
scene. The collision strip is dimmed less, since it marks where a head hits
something. The deuteranopia, tritanopia and high contrast themes keep their
alphas: those are chosen for discriminability, which is the opposite trade.

Also fixes the icon size never applying. All four ScalableButton call sites
passed eight arguments, so the size bound to use_default_disabled_bitmap and
bmp_px_cnt kept its default of 16. Both are passed now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 23:38:37 -04:00
SoftFever 059e171954 Merge branch 'main' into nozzle-variant-cooling 2026-10-01 11:21:59 +08:00
SoftFever d849b30906 Make BBL and library filaments pass the variant width check
Fan speeds, the recommended nozzle temperature range, minimal purge and
multi-tool ramming flow now take one value per extruder variant. Each
single value is repeated for every variant the filament lists, so every
preset loads exactly what it did before.
2026-10-01 11:21:16 +08:00
Clifford GarwoodandClaude Opus 5 e7fad16137 State the no-tool pressure advance contract per firmware
set_filament_pressure_advance's declaration said -1 omits the tool qualifier,
without qualification. That holds on Klipper, Marlin and BBL but not on
RepRapFirmware, where no-tool keeps the historical `D0`: a bare M572 applies to
whichever tool is selected and errors when none is, so omitting the qualifier
would make pressure advance depend on tool-selection state for every RRF user,
none of whom are using IMEX. The behavior is deliberate and unchanged; only the
declaration overstated it. GCodeWriter::set_pressure_advance and the index-space
notes in IMEXHelpers.hpp already described it correctly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 21:00:55 -04:00
Clifford GarwoodandClaude Opus 5 d5d940d6b4 Cover the per-carriage pressure advance and temperature emissions
In a parallel mode no tool changes occur, so two loops address each active
carriage explicitly: pressure advance before the print, and the second-layer
drop off the initial-layer temperature. Neither had coverage, and both fail
silently -- a carriage missing from one emits nothing at all, so it holds the
initial-layer temperature for the whole job, or runs on whatever pressure
advance the firmware was last given. The new case pins that the carriages
addressed are exactly the ones the mode declares active, each with the values of
the slot physical_extruder_map routes its head to.

The test needs filament_self_index, set here on imex_7x4_printer() so the whole
file has it. Production authors that key 1..n; its all-1s default collapses
every per-filament vector to filament 1's value through get_config_index_base(),
which leaves a per-slot assertion comparing a value against itself.

Also bounds the second-layer loop on filament_diameter alone. The bound belongs
in slot space, and filament_diameter is the one per-filament vector never
expanded per variant; the previous min() against nozzle_temperature mixed the
two index spaces without changing the result. The pressure advance loop already
bounds this way, so the two now read alike.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 20:54:17 -04:00