mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-11 09:51:06 +00:00
2e883df7d9003164052d144db47aafe2dde66206
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 ``` |
||
|
|
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. |
||
|
|
6fda82476d |
fix: out-of-bounds read computing tool-ordering max layer height (#14665)
* fix: out-of-bounds read computing tool-ordering max layer height calc_max_layer_height() loops over the extruder count (nozzle_diameter) but indexes max_layer_height with the same counter, reading past the end when that array is shorter. Silent on release builds, aborts under a bounds-checked STL (_GLIBCXX_ASSERTIONS). Read via get_at(), which falls back to the first entry when the index is out of range, as Slicing.cpp already does for this option. Add a fff_print regression test slicing a two-extruder printer with a single-entry max_layer_height. * docs: clarify how max_layer_height ends up short in the regression test Normalization sizes it to the filament count under single_extruder_multi_material, not "a mismatch a profile can ship" as the earlier comment guessed. |
||
|
|
29f31b9b38 |
fff_print: a maintainable testing framework (proposal + coverage) (#14426)
* fix: initialize Print::m_isBBLPrinter
Built outside the GUI/CLI (headless tests, embedded use) the member was read
uninitialized: is_BBL_printer()/wipe_tower_type() feed it into ToolOrdering,
which then non-deterministically dropped per-feature filament assignments.
Default it to false, the value the GUI and CLI already assign for non-Bambu
printers.
* docs(test): add the fff_print testing contract
tests/fff_print/README.md codifies how the suite is organized: one file per
subsystem (each owning both in-memory and emitted-G-code assertions), flat
behavioral test names with a single [Subsystem] tag, a robust-tests guide,
the shared helpers, and an add-a-test checklist. Linked from tests/CLAUDE.md.
* test(fff_print): reorganize the suite to the contract and add coverage
Bring every subsystem into one file per the README: rename the test_data
harness to test_helpers; consolidate skirt/brim; split multi-filament and
cooling into their own files; disperse the test_printgcode grab-bag and the
end-to-end smoke scenario into focused tests; fold test_gcode into
test_gcodewriter. Standardize names and tags, align cube tests on the cube()
helper, and de-qualify the flagship files.
New coverage: multi-filament per-feature and per-object routing; a skirt/brim
behavior matrix (the #14333 rework, including brim ears, with regression
coverage for #14319 and #14366); resolved extrusion-width and config
comments; custom-G-code placeholders; fan control and speed-marker
consumption.
Re-enable three slice tests previously tagged [NotWorking]: the clipper
"Coordinate outside allowed range" error that disabled them was specific to a
past CI runner environment and no longer reproduces.
* test(fff_print): tag arm64-flaky skirt/brim tests NotWorking
Four skirt/brim slice tests intermittently throw ClipperLib's "Coordinate
outside allowed range" on the macOS and Windows arm64 CI toolchains (an FP
divergence, not a slicing bug; see PR #14207). Linux x86_64 and aarch64 are
unaffected. Tag them [NotWorking] so ctest -LE NotWorking skips them.
* test(fff_print): re-enable the arm64 skirt/brim tests
These were tagged [NotWorking] as a stopgap when myfork's daily-driver build
combined them with the cross-platform CI on a base that predated upstream's
m_origin fix (
|