Commit Graph
268 Commits
Author SHA1 Message Date
Clifford GarwoodandClaude Opus 5 018091f43b fix(imex): let the mixed-filament refusal outrank the multi-color rule
A mixed filament is unsupported in a parallel mode outright, but the rule saying
so ran third. A plate carrying a blend plus any second filament tripped the
multi-color rule's used > 1 gate first and was told its active tools all sit on
one gantry -- a diagnosis of a multi-color print the user never configured,
whose remedy is to go rework the mode's tool roster. The blend was never
mentioned. Move the check ahead of both rules below it; being unsupported
regardless of routing or topology, it dominates them.

Nothing is masked that leads anywhere else: every branch of
imex_multicolor_block_reason is itself confined to non-primary modes, so the
mixed message's remedy -- switch this plate to Primary -- silences those too.

Say "Mixed filaments", not "Blended". Every other string in the app calls these
mixed, including the button that creates one and the sibling refusal for the
wipe tower filament, so the user had no way to connect the message to the
feature it names.

Three comments in the block were wrong, and two of them were newly wrong. The
routing rule's bounds-check note still said "Blended slots are out of range by
construction, but they never reach here -- the rule above returns first": the
rule above is now the multi-color one, which does not return first for a single
mixed filament, and out-of-range is not guaranteed at all. Mixed slots are kept
at the tail of the filament arrays by convention, not by enforcement --
PresetBundle::set_num_filaments grows filament_is_mixed with resize(), so
raising a printer's extruder count with a blend present lands physical slots
after the mixed one. The scan is position-agnostic and stays correct; only the
stated reason was wrong.

The same discovery makes the empty-routed_list guard live rather than the dead
code it was described as. Print::apply() normalises physical_extruder_map before
validate() runs, so an unauthored map is never the cause -- but a printer with
more filaments than logical extruders leaves the tail slots outside the map, and
raising the extruder count does exactly that.

The new test validates the plate twice. The first pass, with no blend, asserts
the multi-color rule is armed at all; without it the second proves nothing,
because the rule only fires here thanks to a degenerate fixture mode whose two
tools share a gantry. Give that mode a Span tool and the whole test would pass
under either ordering while appearing to guard it. It also pins err.object,
which the mixed path sets and the multi-color path leaves null -- a discriminator
that survives the next wording change. Verified by reverting the order: the test
fails on both the message and the object.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 08:48:14 -04:00
Clifford GarwoodandClaude Opus 5 2e883df7d9 fix(imex): name the physical head in M104/M109 tool indices
M104/M109 address a heater, but every caller of the instance
GCodeWriter::set_temperature overload addresses filaments by logical id, so on a
printer whose physical_extruder_map is not the identity the emitted T named the
wrong head -- or, where the logical id exceeds the head count, no head at all.
With a map of 0,0,0,0,1,2,3 a toolchange to filament 5 emitted "M109 S265 T4"
and "M104 S190 T4 ;cooldown" while the head it meant was T1.

Upstream already treats these commands as physical: the preheat it injects in
GCodeProcessor maps through the same map before emitting, and BBS's own wipe
tower does likewise. Emitting logical is the half that never got the memo.

That mismatch also disabled the cooldown suppression beside the preheat, which
compares the line's T against pem[tool_number] and so never matched a logical
one -- 171 cooldowns survived in a two-head print where none should have. Worse,
it could match the wrong line: a cooldown for filament 1 emitted T1, and a
toolchange to filament 5 gives pem[4] == 1, so a legitimate cooldown for head 0
was deleted because the incoming head happened to be numbered 1.

Translate once, in the instance overload every logical-space caller passes
through. The static overload is already physical-in and is left alone.

Gated on is_imex. physical_extruder_map carries two readings in this tree: the
BBS paths index it by extruder id, the IMEX paths by filament id, and the two
coincide only when the filament and nozzle counts match. Mapping unconditionally
would impose the IMEX reading on profiles that mean the other one --
fdm_bbl_3dp_002_common ships a non-identity [1,0], spared today only because
single_extruder_multi_material suppresses the T qualifier entirely.

The wipe tower's interface-temperature pass has to move with it. It strips the
M109 that post_toolchange emits by searching for that filament's tool index, so
it now searches for the mapped one; left alone it would have stopped matching,
and the surviving blocking M109 would have silently defeated the interface
temperature. Its sibling pass reads WipeTower2 output, which emits no T at all,
and is deliberately unchanged.

The bare T<n> toolchange stays logical -- it selects an AFC lane, not a heater.

Test slices two objects across a head boundary, the only case that reaches this
emission: the same-physical short-circuit in set_extruder suppresses the
cooldown entirely for lane swaps within one head. It scans every M104/M109
rather than matching fixed strings, so it catches any unmapped emission and not
just the two sites changed here. With the mapping neutered it reports 101
offending lines; with it in place, none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 22:04:23 -04:00
Clifford GarwoodandClaude Opus 5 f0778ef5fa fix(imex): shorten the off-primary message and block blended filaments
The routing error ran to roughly 450 characters and explained the mechanism
before it got to the remedy. It also offered to "edit the mode in Printer
Settings so its Primary tool is one of %3%", which on a plate whose filaments
resolve to no head at all rendered as "one of no configured extruder". Cut it
to the mode, the tool it prints with, where the plate's filaments actually are,
and the two things the user can do about it.

The second msgid that named candidate modes went with it. It could only suggest
a mode whose primary is among the routed heads, and every mode on the printers
this fires for declares 0:P, so it had nothing to offer.

Blended filaments now return before that check rather than falling through it.
A blend is mixed at the nozzle by its component toolheads, and a parallel mode
is already using those toolheads to print copies or mirrors, so the two cannot
run at once regardless of where the components route -- including when a
component sits on the declared primary. Reaching the routing rule would also
have described them wrongly: mixed slots sit past the end of
physical_extruder_map, so they resolve to no head and read as merely unrouted.

Keeps the empty-list guard the shortening first dropped. validate() reads the
raw physical_extruder_map, whose registered default is a single entry, so a
profile that declares IMEX modes without authoring a map leaves every slot past
the first outside it -- and the sentence ended in a dangling "on .".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 22:04:02 -04:00
Clifford GarwoodandClaude Opus 5 b24733cea8 Merge the color mixing subsystem into the IMEX work
Brings the upstream color-mixing feature and its follow-ups onto the branch so the
IMEX placement and primary-routing checks are built and tested against them for the
first time.

Merged clean, no conflicts. Not yet exercised together: a mixed filament is a virtual
slot no nozzle carries, while physical_extruder_map routes logical slots to physical
heads, so the IMEX pem lookups have no defined answer for one. Print::extruders()
lists mixed slots under their own id while tool_ordering.all_extruders() lists them
post-expansion, and the IMEX code reads both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 09:25:41 -04:00
Clifford GarwoodandClaude Opus 5 ba07716ddb Merge upstream main: color mixing feature and related fixes
Brings in 54 upstream commits, the bulk of them the BambuStudio-ported color
mixing / mixed filament subsystem (#15347) plus its follow-ups, along with the
Assimp-backed colored OBJ import, warning-policy build changes, and assorted
profile and localization updates.

Two conflicts, both "each side added at the same point", resolved by keeping
both:

- Print::validate() -- our IMEX multi-color block and upstream's new gradient
  mixed filament warning were inserted at the same spot after the empty
  extruders check. They test unrelated conditions, so both are kept, each with
  its own closing brace.
- tests/libslic3r/test_3mf.cpp -- our three IMEX per-plate round-trip scenarios
  and upstream's mixed-filament round-trip scenario both append to the end of
  the file, and each side added one include. All four scenarios and both
  includes are kept.

Everything else merged cleanly, including GCode.cpp, ToolOrdering.cpp,
PartPlate.cpp and PrintConfig.cpp. Upstream left the is_extruder_used block
untouched, so the IMEX supplement still applies, and estimate_wipe_tower_polygon
is unchanged, so the prime tower hull work is unaffected.

Not addressed here, and worth its own change: a mixed filament is a virtual slot
that no nozzle carries, while physical_extruder_map routes logical slots to
physical heads. Print::extruders() lists mixed slots under their own id whereas
tool_ordering.all_extruders() lists them post-expansion, so the IMEX pem lookups
have no defined answer for a mixed slot. Upstream's own guards reject a mixed
filament where a physical slot is required; IMEX likely wants the same.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 13:07:26 -04:00
Clifford GarwoodandClaude Opus 5 baef398ee3 fix(imex): refuse a plate whose filament cannot reach the mode's primary tool
The IMEX primary tool prints the sliced paths directly, so it can only load a
filament that physical_extruder_map routes to it. The ghost filament picker
enforces that for the secondary tools -- it offers only lanes whose pem entry
equals that head -- but the primary's filament comes from the ordinary object
filament selector, which has no IMEX awareness. Nothing detected the mismatch:
collect_imex_warnings() computes the same condition and discards it into a
display fallback, and the multi-color rule never examines it.

Block it in Print::validate() via the existing imex_primary_tool_for_mode and
imex_primary_logical_from_objects helpers. The message names the declared
primary, the heads the plate's filaments actually live on, and any configured
modes whose primary would work, and carries the object so the notification can
offer a jump to it.

Blocks rather than warns, matching the multi-color rule: the plate is not
printable as configured, and where the routed head is also absent from the
mode's active tools the 1st->2nd layer temperature branch skips it too, leaving
that head at its initial-layer temperature for the whole job.

The multi-color check now runs first. Its constraints -- an MMU manifold sharing
one head, a single-gantry mode -- cannot be fixed by switching mode, so the more
specific error should win rather than be masked by routing advice that leads
straight back to it. The extruders().size() > 1 gate moved onto that call, since
the routing check must also see single-filament plates, which is its common case.

The copy-mode guard-rail test printed on a filament routed off the primary, so
it asserted a plate this rule now refuses; retargeted to a well-formed plate.
Its replacement pins the object's own extruder, because ModelVolume reports its
extruder_id and would otherwise put a primary-routed slot on the plate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 12:58:52 -04:00
Ian Bassi a5223279ac Fix uneven corner rounding in multiline infill (#15352)
* Skip straight-run splits in corner smoothing

Teach `CornerSmoother` to treat vertices that only continue a straight segment as part of the same leg instead of rounding them as corners. The smoother now keeps a three-point window so it can emit a corner only once both adjoining legs are known, which avoids unnecessary corner processing while preserving real turns such as hairpins.

* Add regression test for split-leg smoothing

Adds a FillCornerSmoothing regression test covering polylines with an extra collinear vertex in a straight run. The test ensures corner smoothing treats split and unsplit geometry identically, preventing inconsistent rounding radii in triangular/grid infill paths.
2026-08-25 12:46:34 -03:00
SoftFever bfe5f7e63c fix text error 2026-08-25 21:35:43 +08:00
SoftFever e342698d8e Update sublayer option check. Add validation warning for gradient mixed filament without sublayer mixing 2026-08-24 15:30:02 +08:00
SoftFever 2b1499a087 clean up comments 2026-08-23 22:43:41 +08:00
SoftFever 4a32a9e066 Match mixed filament swatches to the editor's gradient preview
The sidebar Mixed Filament list, the extruder icons, the color painting
gizmo and the canvas filament bar now show the same bottom-to-top fade the
Edit Mixed Filament preview shows, custom gradient curves included, instead
of a horizontal fade between the two component colours. Ordinary and vendor
multi-colour filaments are drawn exactly as before.
2026-08-23 22:11:49 +08:00
SoftFever 38d51783ae Refresh the mixed filament list when a component preset changes 2026-08-23 22:11:49 +08:00
SoftFever 9985688b5b Blend mixed slots in the machine-send and AMS-sync thumbnails 2026-08-23 22:11:49 +08:00
SoftFever d27766aff0 Reject a mixed filament as the wipe tower filament 2026-08-23 22:11:49 +08:00
SoftFever 0c3d7c6ed1 Expand mixed slots in by-object filament bookkeeping 2026-08-23 22:11:49 +08:00
SoftFever 2131ef0560 Keep mixed filaments across app restarts 2026-08-23 22:11:49 +08:00
Ian Bassi 3c37bf9ca4 Import project 2026-08-23 22:11:48 +08:00
Ian Bassi 42f708bbb6 Fixes from Full spectrum port
https://github.com/OrcaSlicer/OrcaSlicer/pull/14383
2026-08-23 22:11:48 +08:00
Ian Bassi 86a7e93a48 Layer subdivision fix 2026-08-23 22:11:48 +08:00
Ian Bassi 8fea099d99 BBL Port Color Mix Base 2026-08-23 22:11:48 +08:00
Clifford GarwoodandClaude Opus 5 90d1481463 Merge upstream main into the IMEX work
Upstream replaced MainFrame's fixed-index TabPosition enum with string-based
page ids ("feat: refactor notebook/tabs to be string based instead of fixed
index based"). The IMEX toolhead-visibility menu item was the only consumer of
that enum left on this branch, so its enable check now compares
m_tabpanel->GetSelectedPageName() against TAB_ID_PREVIEW -- the same form the
neighbouring upstream menu items use.

That mismatch is what broke CI: the branch built on its own, but the merge
commit CI builds no longer had TabPosition declared. No other conflicts.

666/666 tests pass in Release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 17:34:10 -04:00
Clifford GarwoodandClaude Opus 5 ef8d80980d fix(imex): enumerate no secondary carriages in single-tool primary mode
get_imex_active_tools returned every physical head named by the active mode's
tool string, including the one carrying the Primary role. The pressure-advance
and nozzle-temperature sites are already gated on the mode not being primary, so
only the is_extruder_used supplement was exposed.

In primary mode that supplement treated the mode's single declared tool as a
secondary carriage and marked its filament slot used, so machine_start_gcode
emitted a heat command for an extruder that never prints. The phantom slot
appears when the mode's declared tool differs from the head the initial tool
routes to through physical_extruder_map -- on an AFC/MMU layout, printing with a
filament that lives on any head other than the declared one.

Return an empty roster for primary mode, where there are no parallel carriages by
definition. This lives in the enumerator rather than at the call site because the
mode is already resolved and normalized there, and the two guarded callers cannot
reach it in that mode, so their behaviour is unchanged.

Scope: this closes the primary-mode instance. The same phantom slot still occurs
in a parallel mode when the initial tool's head is not the mode's declared
Primary, which turns on which of the two notions of "primary" the three emission
sites should skip. That question is unresolved and deliberately left alone here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 12:37:28 -04:00
Clifford GarwoodandClaude Opus 5 c94e8324d0 fix(imex): give the printing head its second-layer temperature in parallel modes
The IMEX branch of the 1st->2nd layer temperature transition is mutually
exclusive with the standard per-extruder path in its `else`, but it skipped the
head carrying the print's own toolpaths on the premise that "the standard
per-extruder temp path already addresses it". That path is the `else` branch and
never runs for a parallel mode, so the printing head received no transition at
all and held nozzle_temperature_initial_layer for the entire job.

Emit for every carriage the mode drives, the printing one included. The printing
head takes this layer's own filament; the parallel carriages, which carry no
toolpaths of their own, keep resolving through the per-plate head map with pem
inversion as the fallback. The lookup now goes through get_filament_config_index()
like the standard path, since a variant-expanded printer gives a filament its own
column and a raw index would read the wrong one.

Reproduced on a 4-carriage IQEX in copy mode: the only temperature command in the
whole file set the idle secondary carriage to the value it already had, while the
head doing the printing never left its first-layer temperature. The defect is
invisible whenever initial and regular temperatures match, which is why earlier
per-tool validation passed.

Tests cover both gantry counts, since the active set comes from the mode's tool
roster: an IDEX copy mode drives two carriages, an IQEX mode drives four, and the
IQEX case asserts a first-layer filament and a second-layer transition for each of
the four.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 12:37:28 -04:00
Kris Austin 4b397fc2cc fix: slice the same model to the same lightning infill every time (#15311) 2026-08-21 22:24:39 -03:00
ExPikaPakaandSoftFever 5ed56eb876 Cache system presets to eliminate startup and wizard load times (#14217)
* Add caching system for presets

* Removing user\bundle serialization and keeping it only for system presets

* Integrate caching into WebGuideDialog which speeds up time of SetupWizzard and PrinterSelection dialog

* Add CI\CD step to prepare cache file in ahead of time so user does not need to wait

* Add partial cache generation when only one of the vendros is changed to speed up recalculation time

* Handle corrupted files

* Add cache to GuideDialog as previos version didn't work as expected

* Add inspecting tool and fix CI cache generation

* Generate cache per vendor

* Simplify code by mergin it in PresetBundle

* Simplify code a bit more

* Add cereal serialize() to VendorProfile, PrinterModel, Preset, and Semver

* Remove CachedPrinterModel/VendorProfile/Preset mirror structs from VendorCache

* Fix use-after-free in CallAfter lambda; replace raw thread pointer with unique_ptr

* Use get_vendor_cache_key() to match cache keys written by the app

* Remove BOM added by VSC

* Skip invalid vendors

* Remove leftover cache file

* Fix build for windows arm64

* Revert json cache back

* Update check for stale cache

* Serealize all value fields for Preset class to minimize regression later

* Minimize field duplication by moving Cache thing into PresetBundle

* Add tests for Cache system

* Add a bit more tests

* Merge branch 'main' into feature/cache_profiles_and_optimize_loading_speed

* Rvert from per-verndor to single cache file

Replace N per-vendor .cache files with a single system_presets.cache
that holds all vendors and presets in one serialized blob.

Cache load is now all-or-nothing: on hit all vendors are applied from
the bundle (sub-second); on miss all vendors are parsed from JSON and
a fresh bundle is written to the user cache dir.

Invalidation is driven by bundle_key - a sorted concatenation of all
vendor JSON version strings. Any vendor update invalidates the whole
cache and triggers re-parse on next launch.

Guide wizard (WebGuideDialog) loads the bundled cache into a plain
PresetBundle instead of a separate VendorGuideData struct, removing
the duplicate data model.

generate_system_cache simplified from a per-vendor loop to a single
save_system_presets_cache() call producing one output file.

* Transfer all Preset fields from cache via move assignmet

apply_vendor_preset_group was copying fields manually and missed
bundle_id, user_id, base_id, sync_info, updated_time, key_values,
ini_str. Replace field-by-field copy with move assignment of the
fully-deserialized Preset, then restore the vendor pointer which
is excluded from serialization.

* Ignore cache for future

* Remove not used files

* Ship one preset cache per vendor in place of the profile JSONs

Each vendor's system presets serialize into a single <vendor>.opc built at
package time, and a shipped build carries that file alone — the profile JSON
and its sub-file tree are pruned. The vendor loader, the setup wizard's profile
list and the resource installer all read a vendor through its cache, falling
back to parsing whenever one is absent, stale or unreadable, so the cache stays
an optimization and never a source of truth. Caches hold presets in source form
and resolve inheritance at load, through the same code the JSON path uses.

* Make the preset cache self-describing and load each vendor from the system folder alone

The cached DynamicPrintConfig is keyed by name, through a per-file dictionary of the
distinct opt_keys, the type each was written as, and the distinct enum value names,
instead of by serialization_key_ordinal — a position assigned by declaration order at
static init, where inserting one option shifts every later ordinal and the lookup then
succeeds on the wrong option. Because a name-keyed payload drops the options this build
cannot place rather than being rejected wholesale, the schema fingerprint goes, and with
it the two fallbacks that existed only because an installed cache died on every app
upgrade: the second lookup tier into resources/profiles and the parse fallback to the
same place. A vendor is loaded from <data_dir>/system/ and nowhere else, as on main —
which is what makes the app write its .opc files there again.

* Simplify the preset cache internals after review

* Use the shared temp-dir helper in the preset bundle loading test

* Bound stamp string reads in the preset cache

* Speed up the setup wizard with a profile-data cache

The wizard's per-vendor fast path threw on vendors present only in
resources, falling back to a ~29 s raw JSON scan on every open. Each
vendor now loads from the directory it was found in, and the derived
model/machine/filament/process catalog is cached whole in
<data_dir>/cache/wizard_profile_data.json, stamped by each vendor's
name and version - a fresh cache makes an open one file read, with no
bundle built and no presets installed (~0.2 s vs ~2 s).

* Remove debug SVG dump from a geometry test

* Move the per-vendor cache file format into PresetCacheFormat

* Move the vendor install helpers from PresetBundle into Utils

* rename

* fix flatpak

* change cache version to 1

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
2026-08-21 16:56:52 +08:00
Ian Bassi ca65f0fd8e Normalize the junction direction vector over XYZE (#15308)
* Normalize the junction direction vector over XYZE

calc_vmax_junction_deviation() treats the dot product of two jd_unit_vec as a
cosine, but the vectors were scaled by 1 / block.distance, which is the XYZ
length. On an extruding move the E component then pushes the 4D norm above 1 and
the dot product below -1, so the corner reads as straighter than it is and is
planned too fast -- the more so the higher the flow. Measured on a 6 degree
corner at scv 5: 86.9mm/s with no extrusion, 94.4mm/s at 0.029mm/mm, 150.0mm/s
at 0.1mm/mm.

Neither firmware does that. Marlin normalizes over XYZE for any extruding move
(planner.cpp: `if (... || esteps > 0) normalize_junction_vector(unit_vec)`) and
Klipper leaves E out of the cosine entirely, dotting only axes_r[0..2]
(toolhead.py::Move.calc_junction). Normalizing satisfies both: with E normalized
in, the cosine differs from the XYZ-only one by ~1e-5 at printing flow rates.

This is a deliberate divergence from PrusaSlicer, which still scales by
1 / distance -- it carries an older Marlin's behaviour.

Travel moves are unaffected, their vector was already unit length.

Reported by Copilot in review of #15304.

* Test that extrusion rate does not change corner planning

The junction deviation tests were all travel-only, which is exactly why the E
component of the junction vector went unchecked. Cover it: the same corner has
to be planned the same whether nothing, an ordinary 0.42 x 0.2 line, or a fat
large-nozzle line is extruded through it, on both Klipper and Marlin 2.

Reported by Copilot in review of #15304.
2026-08-20 18:50:23 -03:00
Ian Bassi aaa8e98bb0 Time estimator fixes (#15304)
* Plan corners with junction deviation where the firmware uses it

The time estimator only ever had the classic per-axis jerk model, which limits a
corner by the largest single-axis component of the velocity change. That is
anisotropic: the same corner is allowed sqrt(2) more speed on a diagonal than on
an axis, which paints a four-lobed ripple around every circular wall in the
actual speed and actual flow views, worst on small parts whose walls are made of
short segments.

Klipper has no classic jerk at all and Marlin 2 has none while M205 J is in use;
both plan corners with junction deviation, which sees only the corner angle. Add
that model and use it for those machines:

  - Klipper: derived from the square corner velocity, as the firmware does
    (jd = scv^2 * (sqrt(2) - 1) / max_accel), reading the scv from
    machine_max_jerk_x, where process_SET_VELOCITY_LIMIT() already stores
    SQUARE_CORNER_VELOCITY.
  - Marlin 2: machine_max_junction_deviation, which was already loaded into the
    machine limits but never reached the planner.
  - Every other flavor keeps the classic jerk path unchanged.

The model has no per-axis jerk floor, so this also drops the hard slow spot the
estimator drew at the start of every loop from machine_max_jerk_e.

Toolpaths are unaffected: on a full export the only lines that change are M73.

The junction deviation maths, including Marlin's JD_HANDLE_SMALL_SEGMENTS arc
approximation, is ported from PrusaSlicer's src/libslic3r/GCode/GCodeProcessor.cpp.
The Klipper mapping is not in PrusaSlicer, which ignores SET_VELOCITY_LIMIT.

* Add tests for junction deviation corner planning

Cover the three properties the change rests on:

  - a right angle on Klipper is planned at exactly the square corner velocity,
    the identity that makes the scv to junction deviation mapping correct, and a
    shallow corner is planned far faster than per-axis jerk allows;
  - junction deviation gives the same speed whatever the corner's orientation,
    while classic jerk keeps its sqrt(2) spread, which is the four-lobed ripple;
  - machines that do not plan with junction deviation are provably untouched,
    including a Marlin 2 printer that has it disabled.
2026-08-20 09:16:02 -03:00
Kris Austin 8047141981 test: fix the flaky multiline lightning smoothing assertion (#15294) 2026-08-19 11:28:58 -03:00
Ian Bassi 322dc9b6a6 Smooth more patterns (#15205) 2026-08-18 12:19:27 -03:00
f529692ac0 Fix slowdown for caged external overhangs (#14735)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
2026-08-16 23:40:50 -03:00
Clifford Garwood f41752546c Merge upstream main (multi-nozzle override fix, slice-all toolbar crash guard, filament_colour_type G-code skip) into IMEX branch 2026-08-14 16:00:26 -04:00
78eef79ffe Fix flushing-volume warning for single-filament plates (#14704)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-08-13 17:50:55 -03:00
Kris Austin 74aed7a2bb test: finish the temp-file cleanup (#14976)
Follow-up to #14785. Routes the tests that still hand-rolled temp paths through
the shared helpers and unifies the temp guards.

- Add ScopedTemporaryDir and a shared ScopedTemporaryPath base under it and
  ScopedTemporaryFile.
- Move test_3mf's round-trip .3mf output out of the TEST_DATA_DIR source tree
  (a fixed-name leak) and test_toolordering's fixed-name temp .gcode (a sharding
  collision) onto ScopedTemporaryFile.
- Move test_config, test_slicing_pipeline_bindings, the test_3mf backup dirs, and
  test_preset_bundle_loading onto the guards.
- Make slic3rutils ScopedDataDir compose ScopedTemporaryDir; dedupe
  test_network_versions' fixture and delete test_plugin_lifecycle's duplicate.
2026-08-07 13:33:37 -03:00
Clifford GarwoodandClaude Opus 5 df4009e734 fix(imex): size physical_extruder_map from the nozzle count
physical_extruder_map has one entry per logical extruder -- the index space of
nozzle_diameter -- and its consumers size their own arrays from that count. It was
being derived from printer_extruder_id, which is indexed by variant slot: one entry
per extruder+variant pair. An X1 Carbon has one nozzle and printer_extruder_id
{1,1}; an H2D 0.4 has two nozzles and {1,1,2,2,2}. The two spaces coincide only
when every extruder declares a single variant.

The visible effect was on the standby cool-down. set_extruder skips it when the
outgoing and incoming filaments share a physical extruder, and that check is not
gated on IMEX. With the map built from the wrong array, two filaments on a
dual-nozzle machine read as sharing one hotend and the cool-down was dropped --
caught by "Toolchange temperature commands are unchanged when the wipe tower wait
is off", which failed on all five CI platforms with the ;cooldown line missing.

Derive the identity over the nozzle count instead, the same fallback Plater.cpp
already applies where a profile authors no map. A profile counts as authoring one
only when its length matches the nozzle count, so the single-element PrintConfig
default is replaced rather than read as a one-extruder machine. Authored maps pass
through untouched, including the {1,0} numbering permutation the BBL dual-nozzle
profiles ship.

Tests pin the four branches and the length invariant the consumers depend on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 12:20:52 -04:00
Rodrigo Faselli 1a30c49993 Merge branch 'main' into main 2026-08-07 10:50:35 -03:00
Clifford 8e243faa3a Fix Linux unit test failure in the wipe tower temperature trace comparison (#15161)
## Problem

`Toolchange temperature commands are unchanged when the wipe tower wait
is off`
(added in #15144) fails on both Linux runners and passes on Windows and
macOS.
It is the only failing test in the suite, and it has been failing on
main since
that PR merged.

| Job | Result |
| --- | --- |
| Windows x64 / Unit Tests | pass |
| Windows arm64 / Unit Tests | pass |
| macOS arm64 / Unit Tests | pass |
| Linux x86_64 / Unit Tests | **fail** |
| Linux aarch64 / Unit Tests | **fail** |

From the merge commit
([Linux
x86_64](https://github.com/OrcaSlicer/OrcaSlicer/actions/runs/31072382258/job/92531704095),
[Linux
aarch64](https://github.com/OrcaSlicer/OrcaSlicer/actions/runs/31072382258/job/92531704075)),
still reproducing on current main:

```
first difference at trace entry 29
  main:   M104 S240 T0 ; preheat T0 time: 31s	lead 30.9s
  branch: M104 S240 T0 ; preheat T0 time: 30s	lead 30.3s
```

## Cause

Each preheat entry records the same quantity twice: `lead` at one
decimal, and
`time:` inside the command text as that value rounded to a whole second.

`split_lead` already compares `lead` with a 0.5s tolerance and explains
why the
estimate moves. `time:` sits in the exactly-compared command text, so it
never
got that tolerance — and being rounded, it flips on a drift far below
0.5s
(30.4 and 30.6 render as `30s` and `31s`). Entry 29 is the only entry in
the
163-entry golden whose lead rounds up; every other preheat sits at
30.0–30.4 and
rounds down, which is why it is the only one that fails.

The variation is per-toolchain, not run to run. Both Linux arches
produce
exactly `lead 30.3s`; Windows x64/arm64 and macOS arm64 all produce
exactly
`30.9s`. Repeated local runs are byte-identical. macOS arm64 passing
while Linux
aarch64 fails rules out the ISA — it is floating-point accumulation over
a few
thousand move durations under GCC vs Clang vs MSVC.

The mechanism makes it discrete rather than gradual: the backtrace parks
the
preheat at the first exported line at least `preheat_time` before the
tool
change, so `lead` is `preheat_time` plus the leftover of whichever move
that
landed on. A sub-tenth difference selects the neighbouring move and
`lead` steps
by that move's whole duration.

Entries 1–28 match exactly, including five earlier preheats whose leads
fall
inside the existing tolerance, so the toolpaths themselves are
identical. I also
reverted the two prime-tower commits that landed between the golden's
capture
point and now, rebuilt, and got a byte-identical trace — this is not
behavioural
drift.

That also rules out regenerating the golden: no single capture satisfies
all
three toolchains, and recapturing on Linux would turn the three
currently-green
runners red.

## Fix

Test-only.

- `lead` keeps a tolerance, widened to 1.5s (measured drift 0.6s; a
preheat
actually leaving its backtrace position would move by tens of seconds).
- `time:` is **not** compared across runs at all. Being a rounding of
`lead`, it
carries nothing the tolerance does not already cover, and comparing it
across
runs can only reproduce the flake. It is instead checked against its own
  entry's `lead` — a correct rounding keeps `|time - lead| <= 0.5`.

That second point matters: simply tolerating `time:` numerically would
have made
the test blind to a real change, because drift and a wrong rounding both
move it
by 1. The self-consistency check keeps that coverage. I verified it by
changing
`(int) std::round(time_diffs[0])` to `(int) time_diffs[0]` in
`GCodeProcessor::export_lines` — the test fails with
`"time:" is not its entry's "lead" rounded to a whole second`, where a
plain
tolerance would have passed silently.

Everything else is still compared exactly: all M104/M109 values, tool
ids,
block markers, ordering, entry count, and the annotation text including
its
trailing `s`. The other 138 entries remain byte-exact.

No production code, no golden regeneration. The golden file and these
helpers
are used by this one test and nothing else, and the tolerance only
widens, so
Windows and macOS keep passing unchanged. A note is added to the
golden's header
so the next mismatch in those fields is not "fixed" by recapturing.

## How to verify

Before, on Linux:

```bash
git checkout main && ./build_linux.sh -t
ctest --test-dir build/tests -R "Toolchange temperature commands are unchanged" --output-on-failure
# fails at trace entry 29
```

After:

```bash
cmake --build build --config Release --target fff_print_tests
ctest --test-dir build/tests --output-on-failure     # 463/463
```
2026-08-07 20:26:18 +08:00
Clifford Garwood 520cf3a415 Merge upstream main: prime tower on non-rectangular beds, toolchange temperature wait, ironing speed override, preset dialog QOL
# Conflicts:
#	src/libslic3r/GCode.cpp
#	src/slic3r/GUI/Tab.cpp
2026-08-06 17:07:02 -04:00
SoftFever 7c73739e1a Keep the prime tower and its approach travel on non-rectangular beds
The placement clamps and the tower-approach router both stood in the bed's
bounding box for the bed itself, so on a delta or hexagonal bed the prime tower
could be parked in a corner that does not exist and the nozzle could be routed
across it. Both now test the real printable outline, slicing reports a tower
that does not fit instead of printing it off the bed, and a tower parked near an
edge is routed along the clamped side rather than falling back to a straight
line across the tower.

Also fixes the placement validation rotating the tower hull by degrees read as
radians about the plate origin, and never rotating the generated tower footprint
at all.
2026-08-06 15:48:40 +08:00
SoftFever 408db4b3b0 Wait for the toolchange temperature on the wipe tower
Adds a printer option that picks up the new tool without a blocking temperature
wait, travels to the wipe tower, and waits there right before purging, parked
beside the tower so the ooze from the heat-up lands next to it rather than on the
model. The incoming filament's target is raised ahead of the tool change, so the
heat-up overlaps both the change itself and the travel to the tower.

Off by default, and only offered for multi-extruder printers using a Type 2 wipe
tower; the generic toolchanger profile enables it.
2026-08-06 12:24:00 +08:00
SoftFever 1d023216f2 clean up 2026-08-05 18:09:36 +08:00
SoftFever 194ef34080 Wait in the wipe tower with a millisecond dwell on Klipper
The wipe tower's "Delay after unloading" never happened on Klipper. It was
emitted as G4 S<seconds>, and Klipper's G4 reads only the P parameter, in
milliseconds, so the pause was silently skipped. The option now produces a
dwell Klipper actually performs.

Also corrects the planner flush rationale, which cited an extruder position
reset that Klipper resolves at parse time and does not need synchronized, and
adds end-to-end coverage that slices a two-filament print and checks the
emitted wipe tower G-code on both a Klipper and a non-Klipper flavor.

No change to any other firmware flavor's output, and no shipped profile sets a
non-zero delay, so no shipped profile's output moves either.
2026-08-05 17:15:35 +08:00
SoftFever 4e1caa39eb Flush the wipe tower planner queue with M400 on Klipper
The wipe tower emitted G4 S0 to make the firmware finish its queued moves
before commands that must not take effect early. Klipper's G4 reads only the
P parameter, so that flush never happened there and a temperature change could
land seconds ahead of the moves it was meant to follow. Klipper now gets M400
instead, through one helper shared by both wipe tower implementations.

No change to any other firmware flavor's output, so no shipped profile or saved
project is affected.
2026-08-05 17:15:35 +08:00
Clifford Garwood 7541d6a1e6 Merge upstream main: printer-specific OrcaFilamentLibrary profiles, convex_hull_2d test replacement
# Conflicts:
#	tests/libslic3r/test_3mf.cpp
2026-08-03 15:17:00 -04:00
Mikhail f. Shiryaev 7b404596e9 Add Skip G-code config block to exclude the config comments from G-code files (#12455)
Add feature to skip CONFIG_BLOCK in G-code files
2026-08-03 15:10:01 -03:00
Clifford Garwood 612af109f5 Merge upstream main: outer-only brim ears, Hilbert infill smooth factor, ImGui mouse capture and gizmo fixes, Wayland splash and Linux control styling, 32-bit and warning cleanups 2026-08-03 10:58:46 -04:00
Kris Austin 06ef58bad8 test: replace the disabled convex_hull_2d test (#14892)
test(libslic3r): replace the disabled convex_hull_2d test, closing #11269

The last "failing libslic3r test" from #11269 was the disabled
SCENARIO("2D convex hull of sinking object", "[3mf][.]") in test_3mf.cpp.
It checked ModelObject::convex_hull_2d for a sinking object against
PrusaSlicer's reference hull, but Orca's convex_hull_2d does not clip
geometry below the bed the way PrusaSlicer's its_convex_hull_2d_above does,
so the reference never matched. The test also wrote a debug mesh to a
hardcoded /tmp path and its comparison loop was inverted.

Remove it and add tests/libslic3r/test_model.cpp characterizing
convex_hull_2d on non-sinking transforms (identity and scale+offset),
where the projected footprint is unambiguous. Homed in a Model test file
since it exercises ModelObject, not 3MF.
2026-08-03 22:29:00 +08:00
SoftFever 74c4a7e450 Support printer specific filament profiles in the OrcaFilamentLibrary (#15101)
* Support printer specific filament profiles in the Orca Filament Library
2026-08-03 22:25:50 +08:00
Clifford GarwoodandClaude Opus 5 3852f4be61 fix(imex): block slicing when the prime tower overlaps a parallel-printing zone
IMEX placement validation only walked model instances. The prime tower is
not a ModelObject, so it could sit in a secondary zone or a carriage
collision strip and slice with no warning -- on mirror mode, a carriage
crash. Span (paired-gantry multicolor) is what made towers reachable in
parallel modes, so this is a gap in that feature, not inherited breakage.

The check now returns a cause instead of a bool so the message can name the
offender, and the tower and per-instance paths share one predicate,
imex_hull_violates_zones(), moved to libslic3r and covered by tests.
Overlap is area-based: a hull flush against a zone boundary is legal, only
a crossing violates. The tests pin that in both directions, since switching
to a touch-based test would silently block placements that work today.

The tower footprint comes from the same estimate the scene draws, so
validation matches what the user sees and drags. Three config reads there
are load-bearing in non-obvious ways and are commented at the point of use.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 08:26:38 -04:00
maddavo 13ae3a1c90 Add outer-only mouse ears and align ear radius controls (#15015)
Improve mouse ear brim controls
2026-08-02 10:54:16 +08:00
Valerii Bokhan fb36d5e73b Feature: Smooth Factor for the Hilbert Curve sparse infill (#14969) 2026-08-01 17:58:04 -03:00