Commit Graph
4130 Commits
Author SHA1 Message Date
harrierpigeon 5ac0dd19d0 Belt brim: no near-duplicate lines in a narrow lattice band
A band barely wider than one line got a second line almost on top of the first,
and lattice rows could repeat within half a pitch.
2026-10-02 12:46:44 -05:00
harrierpigeon 550229b0fa Belt validation: refuse a brim next to a purge tower object
The purge plan moves objects onto a common layer grid after the brim bands are
built, so the two cannot share a print. The prime tower setting alone still does
not block a brim. The missing-prism warning now counts the filaments the objects
use, as the GUI does.
2026-10-02 12:46:44 -05:00
harrierpigeon 997fac8752 GCodeProcessor: key the belt height-check skip on belt_printer
The check was skipped for any machine-frame transform; what makes the printable
height meaningless is the belt axis, so ask for that directly.
2026-10-02 12:46:44 -05:00
harrierpigeon 007071bd56 GCode: skip the second crossing-avoidance pass on a brim apron layer too
travel_to runs avoid-crossing a second time after a wipe; that call needs the
current Layer as much as the first one does.
2026-10-02 12:46:44 -05:00
harrierpigeon 7a23d9ea2c Belt slicing: only model parts set the layer range again
The filter was lost with the debug logging it shared an #if with, so modifiers
and support blockers stretched the sliced range of a belt object.
2026-10-02 12:46:43 -05:00
Ian BassiandKris Austin 70bc02467b Faster Preview View (#15884)
Co-authored-by: Kris Austin <kris.austin@gmail.com>
2026-10-02 14:45:58 -03:00
0d36324323 Show the plate's IMEX mode on its mode button
The plate's mode button showed one icon whatever the mode. It now shows the
mode the plate slices as, the one its ghosts follow. imex_mode_kind() reads it
from the heads beside the mode's primary: none is Normal, any Span head is
Custom (the multicolor modes), otherwise any Mirror head is Mirror, and heads
that all copy are Copy. One Mirror head is enough because an IQEX mirror mode
copies within the primary's gantry.

The icons are Felix14-v2's: the four kinds, each in light and dark with a hover
state, replacing the single mode icon. Two fixes to them: the light Normal
border used the dark theme's gray, and an opacity="undefined" attribute hid one
of its strokes in nanosvg. The knight outlines are drawn at 1.0 rather than
0.8, matching the other plate icons and keeping Copy and Mirror apart when
zoomed out.

Co-authored-by: Felix14_v2 <75726196+Felix14-v2@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:29:33 -04:00
Clifford GarwoodandClaude Opus 5.5 e8cd4be675 Merge upstream, including deterministic painted multi-material slicing
Seven commits: painted multi-material segmentation made deterministic, missing
includes added across src/slic3r/GUI, U1 high-flow nozzle variants, Windows
ARM64 build and HTTPS fixes, and a rule added to the wxWidgets agent skill.

Three conflicts, all include lists: upstream's include pass and ours each added
to the same lists in GCodeViewer.hpp, PartPlate.hpp and PartPlate.cpp. Resolved
as the union of both. Three includes both sides had added at different places,
which git kept twice, are kept once: Color.hpp and <set> in PartPlate.cpp,
<sstream> in Plater.cpp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:22:47 -04:00
HanifKohandSoftFever 205de9ce63 Keep User Preset Values on Extruder Variants They Don't List (#16046)
* Keep User Preset Values on Extruder Variants They Don't List

A user preset stores the variant list its parent had when it was saved.
When the parent later gains variants, update_diff_values_to_child_config
matched variants by name only and left the new ones at the parent's
value, so the user's settings were silently replaced there, and a
re-save wrote the system values into the user's file.

An unmatched parent variant now takes the child's first variant of the
same extruder, the rule slicing already uses in get_config_index_base.
A child without a variant list covers the parent's first extruder. The
name match also no longer indexes the child's extruder ids when it has
none.

* Share One Variant Column Rule Between Slicing, User Presets and Projects

Three places chose which variant column a value comes from, each with
its own copy of "the same variant and owner, else the owner's first
column": get_config_index_base when slicing, the user preset merge in
update_diff_values_to_child_config, and normalize_filament_values_to_variants
for projects and the CLI.

find_variant_column now holds that rule and map_variant_columns applies
it to a variant list, so a change to how missing variants are filled
reaches all three. Each caller keeps its own copy step. There is no
behaviour change: G-code is identical before and after. The one
relaxation is that get_config_index_base no longer reads past a short
id list when its two lists differ in length, which its assert already
rules out.

* Rename variant column helpers to variant index

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-10-02 20:13:06 +08:00
HanifKoh 390b7d8e6d Make Painted Multi-Material Slicing Deterministic (#15899)
* Make Painted Multi-Material Slicing Deterministic

Painted (multi-material) models sliced to slightly different G-code on
every run: ±1 µm wall coordinates and reordered islands. Hashing each stage
of the segmentation across runs showed the projected painted lines and the
per-layer Voronoi segmentation were stable; the raw top/bottom projections
from slice_mesh_slabs() were not. Three causes, all thread-order dependent:

- slice_slabs_make_lines() appends each slab's intersection lines from a
  parallel facet loop and never restored a canonical order, so the loop
  start vertices and polygon order from make_slab_loops() depended on
  scheduling. Sort every slab's lines with the same key slice_make_lines()
  already uses.
- segmentation_top_and_bottom_layers() wrote a layer's shell projections
  into neighbouring layers' vectors from the parallel loop, relying on a
  parity double-buffer that assumes TBB ranges are exactly one group wide
  and aligned, which blocked_range does not guarantee; two threads could
  append to the same vector. Each source layer now records its projections
  in its own slot and they are gathered per target layer in source order.
- The painted-line sort in post_process_painted_lines() was not a total
  order: projections of one span from facets of different colours tied on
  every key and the first one won the span. Colour and end points now break
  the tie.

Three multi-threaded runs of each painted fixture now give one G-code;
unpainted output is unchanged.

* Test That Slab Slicing Does Not Depend on the Thread Schedule

Projects a dense, tilted sphere with slice_mesh_slabs() on one thread and
then three times multi-threaded, and requires the polygons to match exactly,
vertex order included. Fails without the canonical line sort, passes with it.
2026-10-02 16:51:06 +08:00
harrierpigeon 441ae8e877 Belt: decide the fan band per extrusion segment
A tilted layer runs from the belt to the top of the part, so a wall loop
that starts above the belt still passes along it. Tagging only the path's
first point left such loops out of the band entirely; the band is now
evaluated at each segment, with the tag capped where the fan stops
depending on it.
2026-10-02 02:36:13 -05:00
harrierpigeon 3beb448ae6 Belt brim: lattice lines closer to the belt than the band fraction move uphill
With a first layer of about 0.28 mm or more at 45 degrees (or a shallower
belt) the brim band is wider than one bead and its lines go on the nominal
lattice. A lattice line could land where the belt is almost at the band's
print_z; its flow was clamped to half a layer while the nozzle sat nearly on
the belt. Such a line now moves uphill to the 0.75 fraction the single-line
case uses, and a line that lands on the previous one is skipped.

Ported from the Unlayered fork (patch 0007 of its belt port series, found
there by fuzzing first layer heights). The fork's companion fix, restricting
the brim filament to those the writer was handed (0008), is not needed here:
ToolOrdering registers the brim filament on every band's layer, so the writer
always has it. A test pins that with every object a flush target.
2026-10-02 01:13:29 -05:00
harrierpigeon 412564cae3 Belt: document the frame of the exclude-object outlines
EXCLUDE_OBJECT_DEFINE keeps plate coordinates on a belt printer: the frame
after the slicing rotation is undone and before the G-code axis remap and
machine-frame shear, which is where the object stands on the belt.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:11:59 -05:00
harrierpigeon 360a68e078 Belt: warn when the purge tower is enabled but the project has no tower object
The purge tower is a model object the GUI creates and sizes, and libslic3r
only purges into one that exists. A multi-filament belt project sliced from
the CLI without it changed filament with nowhere to purge, silently.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:11:59 -05:00
harrierpigeon 556569c0e3 Belt: drive the first-layer fan band from the generator, not from parsed moves
The cooling buffer's band pass rebuilt positions from the layer's G-code
and tested them against the first-layer plane. The G-code is in machine
coordinates and the plane is in slicing coordinates, so on the shipped
profiles the nearest move was over 100 mm from a 0.2 mm band and the pass
never changed the fan. GCode::_extrude() already knows each path's height
above the belt, so it now tags the band changes and the buffer applies and
strips the tags.

The pass also took the S of every M106 as the part fan, whatever its P
index, and stored that 0..255 value where a percentage was expected (an
auxiliary fan line came back as M106 S651); it now uses FanMover's parser,
which ignores other fans, and converts to percent. It no longer overwrites
the layer's intended speed, only the fan's actual state.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:10:57 -05:00
harrierpigeon 646fe6384c Belt: retire the two support floor modes nothing implements
clip_only and both were never read and behaved like none; old values now
load as none.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:07:45 -05:00
harrierpigeon f7b822abb7 Belt: remove the unused clip_support_fills()
It had no caller besides its own recursion.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:07:45 -05:00
harrierpigeon c0ba2c8c44 Belt brim: read the belt axis from the config in the instance check
belt_brim_instances_compatible() runs while the slicing parameters can be
stale, like the rest of the brim predicates, which read the print config.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:07:44 -05:00
harrierpigeon 0701f64ba9 Belt: index apron layers into the first layer's nozzle map
Apron bands looked up their filament and nozzle config slot with a running
counter, while object layers use Layer::id(), so band N read the map of
object layer N. They precede layer 0 and now use its assignment.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:07:44 -05:00
harrierpigeon c49e8d32c8 Belt: drop the per-extrusion transform determinant
The mesh transform is a rotation and an axis permutation, so its
determinant is always 1; rebuilding the forward transform on every
extrusion to divide the flow by it changed nothing.

Raised in Hanif Koh's review of #14394.
2026-10-02 01:07:43 -05:00
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 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 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 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 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
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
Lam Wei Lun 03377a0242 Missing cassert include (#16039) 2026-10-01 10:34:43 -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 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 bea412fddf Move the G-code processing result instead of copying it
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.
2026-10-01 09:15:15 +02: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
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
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
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
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
harrierpigeon 69a08915d1 Belt: do not fail the G-code height check against belt travel
check_multi_extruder_gcode_valid() compares each object's max Z with
printable_height. On a belt printer machine Z is belt travel (a 3DBenchy
on the BabyBelt Pro runs from Z=197 to Z=309 on a 69 mm printable_height),
so every belt export set the over-height error bit and the CLI refused the
plate with -102 "G-code in unprintable area". The preview already skips its
ToolHeightOutside warning for the same reason; the export check now does
too. The XY printable-area check is unchanged.
2026-09-30 17:29:33 -05:00
Kris Austin 865e9963c3 perf: speed up G-code export by 7-26% via typed config apply (#16026) 2026-09-30 18:58:22 -03:00
harrierpigeon 2c0570c97d Drop references to planning docs that are not in the tree
The MachineKinematics comments pointed at docs/superpowers plan files,
which are gitignored working notes.
2026-09-30 15:44:49 -05:00