mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-11 18:01:14 +00:00
3c7b62bbca4f23e304d0d080ae8f10ab9fb13ce9
21
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
af1cf71a4c |
Read the IDEX/IQEX enum settings back as the values they were saved as
A coEnum config value has two representations: the typed ConfigOptionEnum<T> a config cloned from the static classes carries, and the ConfigOptionEnumGeneric that a config assembled from the option definitions creates - which is what a preset, a project's own settings and the CLI all hold. imex_cfg_enum() accepted only the first, so every IDEX/IQEX reader took the option default instead: a printer saved as rear-left came back front-left in the settings, the bed zones and the carriage markers, while the preset on disk still held rear-left. Read the generic form too, keyed on the value map it carries, since only T's own map yields a T. The last reader that matched on the coEnum tag alone and cast across the two hierarchies now goes through the helper with everything else. Declaring the three keys the static classes were missing is what lets a change to imex_tool_layout invalidate the slice it moves, which it never did before. The two that are only ever drawn stay out of that: a colour scheme and the advisory margin bands do not reach a slice, so changing one must not discard it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3c31856b85 |
Bound the second-layer temperature lookup in filament-slot space
nozzle_temperature is variant-expanded, so its length is columns rather than filament slots. On the dynamic-nozzle path that makes it longer than the slot count, and an out-of-slot index reached get_filament_config_index() and came back as filament 0 - the clamp the bounds-checking was meant to remove. Bound by the slot count instead; on the ordinary path the two are equal and nothing changes. The is_extruder_used write gains the matching lower-bound guard. IMEXHelpers.hpp now states both halves of the rule its call sites follow. Bound anything derived from the extruder map against the filament slot count before using it as a filament id, not against the option about to be read. And a miss is -1, which is a correct tool qualifier but matches no physical head, so it cannot serve as a skip-the-primary sentinel: which head prints a filament is answered by the filament and the map, never by a mode role, since a primary-mode print may use any or all tools, one at a time. The consequence is recorded there rather than left implicit. The two skip sites skip nothing for a slot past the end of the map, so a plate with more slots than nozzles double-writes the primary's pressure advance. It is narrow and unreported, and a guard there would be a smaller change than naming a head. The header also records that RepRapFirmware sends an unqualified pressure advance as M572 D0, naming drive 0 absolutely rather than the active tool. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f7a08b0868 |
Spell the mode loop initializers as std::string
Two range-for loops bound const std::string& to braced lists of string literals, so each iteration constructed a temporary to bind to. GCC 16 reports it as -Wrange-loop-construct, which upstream's blanket -Werror turns into a build failure; clang does not report it at all. Spell the initializers the way the loop above them already does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
98544fd6bc |
Bound the parallel pressure-advance lookup to the filament count
Review findings on the preceding commit, plus one defect it should have caught. - The IDEX/IQEX pressure-advance loop fed resolve_filament_for_head()'s result straight into enable_pressure_advance and pressure_advance. That result is bounded by physical_extruder_map, which holds one entry per NOZZLE, while both options are indexed per filament SLOT. On a printer with more nozzles than the project has filaments the two spaces diverge and get_at() clamped the overflow onto filament 0, emitting its pressure advance on a secondary carriage. The second-layer temperature loop bounds the same lookup, but against nozzle_temperature, which is variant-expanded and so is not the slot count either -- it is not the precedent it looks like. IMEXHelpers.hpp states the rule once, and a test pins the contract that makes the bound necessary: resolve_filament_for_head() answers in nozzle space, so a non-negative result is not by itself safe to use as a filament id. - The header claimed every caller renders a -1 tool qualifier as "emit none". RepRapFirmware substitutes the historical D0 instead, deliberately and with its own comment in GCodeWriter. Say so, rather than leaving a contract a future author would code against. - A cross-reference pointed at a hard-coded line number that the preceding commit had itself shifted by nine lines. Name the function instead. - The multi-color rejection reasons reach the user through Print::validate() as raw English, while the returns on either side of them use L(). Wrap them and register IMEXHelpers.cpp for extraction. They also still said "IMEX", the internal name, so they move to IDEX/IQEX with the rest of the user-facing strings rather than shipping the internal one to translators. - Trim the preceding commit's comments. One block explained the same clamping hazard six times; the canonical explanation now lives in IMEXHelpers.hpp and the call sites point at it. The mode grid carried twelve lines of commentary and no code, most of it archaeology already in the commit message, and one claim about the modes editor that was not true. The ArrangeJob threading note stays: it documents an invariant that cannot be recovered from the code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e309029b28 |
Fix extruder-map bounds, popover lifetime, and arrange thread safety
Review findings on the IDEX/IQEX parallel printing code, all in paths the feature owns. - physical_extruder_map lookups used ConfigOptionVector::get_at(), which clamps an out-of-range index to values.front() rather than reporting a miss. The map holds one entry per nozzle while filament ids index slots, and nothing caps the slot count at the nozzle count, so a project authored with more filaments than the printer has extruders silently addressed the primary's head: pressure advance pinned to the wrong carriage, and skip-primary loops suppressing whichever head sat at pem[0]. Bounds-check at all four sites and treat the miss as "no mapping" (-1). Covered by a new imex_pem_tool_for test; the header note now warns against get_at here. - IMEXFilamentPickerPopover leaked a top-level window per ghost click: wxPopupTransientWindow::Dismiss() only hides, and never reaches OnDismiss(). Destroy from an OnDismiss() override and dismiss the picker through DismissAndNotify(), which is the path a successful pick takes. - ArrangeJob read PartPlate's IMEX zone cache from the worker thread, where a cache miss rebuilds GLModel members with no GL context current while the GUI thread may be painting them. Snapshot the zones in prepare(), on the main thread, already converted to plate-local coordinates. - The mode grid anchored its row window to the Primary's gantry row. A window as tall as the grid can only start at row 0, so this drew tiles for tools that do not exist and hid real ones. Render the whole grid instead; a Primary outside it is a data problem the zone layout already reports. - Build the mode tooltip from one format string rather than two catalog fragments concatenated around a runtime value, so translators can move the mode name within the sentence, and register IMEXModesCtrl.cpp for string extraction. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
171a721304 |
Harden the IMEX helper layer and give it one mode lookup
Closes review comments 3, 5, 7, 8, 18 and 19, plus the library half of 20. These share a file, so they share a commit; each is independent of the others. - 3: ::isspace(char) is undefined for bytes above 0x7F because char is signed on our targets. Three call sites now go through one strip_whitespace() using an unsigned char cast. Line 308 parses imex_head_filament_map straight out of 3MF metadata, so a non-ASCII byte reached it without passing through the UI. - 5: an imex_head_filament_map override past the end of physical_extruder_map now falls through to the printer's own routing instead of resolving to a wrong filament. Bounded in resolve_filament_for_head, where the slot count is known, rather than at the parse site, which has no count to check against; the parse site also gains the absolute MAXIMUM_EXTRUDER_NUMBER cap its sibling already had. - 7: imex_physical_heater_for's !is_imex early return is what keeps a stock BBL profile (physical_extruder_map [1,0]) out of the heater remap, and had no test. Six cases now cover it, pinning pass-through rather than get_at()'s clamp. - 8: ImexRole::Span was missing from the imex_head_transform switch, so it warned under -Wswitch. Identity is correct, not merely convenient: a Span tool prints the primary's own zone through mid-print toolchanges and has no zone to be translated into. - 18: three positionally coupled string vectors were resolved by nine open-coded lookups using three incompatible bounds idioms. None read out of bounds, but six folded the guard into the match condition, so a ragged row did not stop the scan and a later duplicate name could win. struct ImexMode + find_imex_mode() is now the only resolution rule: the names array is the roster, first match wins, a short sibling pads to empty and sets ragged, not-found is an explicit -1. - 19: the letters P/C/M/S existed in three independent copies, one of which was the writer of the on-disk format. kImexRoleTable is now the single source, read by both parsers and the serializer. Adding a role was 14 edit sites with one compiler-enforced; it is now the enum, the table entry, and four -Wswitch switches. Verified by adding a fifth enumerator and recompiling: exactly four warnings, nothing else. - 20: imex_resolve_routing() extracts the mode/primary/routing chain that Print::validate and the plater's warning collector each derived separately. The three config keys keep their names, types and on-disk representation. This is a read-side view only; presets and 3MF files are unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
90e8ff7cee |
fix(imex): align the preview marker grid with the plate zone grid
Follow-up to the cross-gantry mirror axis change, from an adversarial review of it.
GCodeViewer builds its own copy of the zone grid to place the sequential-preview
carriage markers, and it must reproduce PartPlate::calc_imex_zones exactly or the
markers drift away from the ghosts they are meant to track. It did not, in two ways,
because sizing the grid and placing a cell answer different questions:
- Sizing: calc_imex_zones counts every Copy/Mirror tool's OWN column, including the
non-representatives of an aggregated (Span) gantry -- they still donate a column.
GCodeViewer only ever saw the representative, so on an aggregated gantry it could
count fewer columns than the plate and lay its markers out against wider strips.
- Placement: calc_imex_zones PINS an aggregated cell to the primary's column, because
that row-strip spans the full bed and has no column of its own. GCodeViewer used the
representative's own column, which put the marker a strip away from the ghost
whenever the representative was not column-paired with the primary.
Track the two sets separately: grid_tool_ids sizes the grid from own columns, eff_col_of
pins only aggregated tools when placing. Out-of-grid tool indices are deliberately left
unfiltered -- calc_imex_zones drops them while calc_imex_ghosts keeps them, so no policy
here can agree with both, and a comment says so rather than pretending otherwise.
Also:
- The mirror-axis rule lived in three copies (two PartPlate lambdas plus an inline
re-derivation here). Hoist it to imex_mirror_axis_for() so the ghosts and the markers
cannot drift apart, and unit-test it, including degenerate tools_per_gantry.
- Drop imex_head_transform's mirror_axis default. A defaulted axis silently hands a
forgetful caller the X reflection, which is wrong for every cross-gantry tool and
fails silently -- exactly how a stale test kept certifying the old rule.
- Replace that stale test: it asserted a diagonal mirror "flips X only", the rule this
work overturned, and stayed green only because of the default.
- A secondary sharing the primary's gantry now takes the primary's Y box facing. The
box shows the side a tool could be hit from, and two tools on one beam can only be
hit by the same other gantry. No-op on the rear-* layouts, where the hardcoded value
already matched; on front-* layouts it pointed the box away from the only tools that
could reach it.
- Correct two comments that described the aggregated X-frame substitution as a
reflection plane. It is not one: it exists to zero gantry_offset.x, and removing it
would push aggregated ghosts a column off their strip.
Verified: 385/385 tests; CLI slice of the IMEX regression project is byte-identical to
the previous commit's G-code apart from the timestamp, confirming this is
visualization-only and cannot affect sliced output.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
0cb1788b23 |
fix(tests): use ASCII hyphens in IMEX test names so Windows can run them
catch_discover_tests registers each Catch2 test with ctest by name, and ctest then
re-invokes the binary passing that name back as a -# filter. The IMEX test names
contain em dashes (U+2014). They survive discovery, but on Windows the round trip
through the console codepage mangles them, so the filter matches nothing:
Filters: "imex_head_transform G-- copy mode is pure translation"
No test cases matched -> No tests ran -> exit != 0 -> ctest: Failed
All 85 IMEX tests were reported as failures on both Windows x64 and arm64 without a
single one of them ever executing. Linux and macOS are UTF-8 end to end and were
unaffected, which is why this went unnoticed since the names were introduced in
|
||
|
|
f850912123 |
fix(imex): mirror across the gantry-row axis on two-gantry printers
A Mirror tool reflects across the boundary it shares with the primary's zone, and
which boundary that is depends on where the tool sits:
- Same gantry: the tools are side by side along X, so the shared boundary is
vertical and the reflection negates X. This is what single-gantry IDEX does,
and it was the only case the code modelled.
- Different gantry: the zones are stacked along Y (front strip vs rear strip),
so the shared boundary is horizontal and the reflection negates Y. The part
that comes off gantry 1 is a Y-reflection of the tool directly behind it.
imex_head_transform() hardcoded diag(-1, 1, 1) for every mirror, as its own TODO
acknowledged. Lift the axis to a caller-supplied ImexMirrorAxis; PartPlate picks it
from the tool's gantry row. Both reflections keep det = -1, so a mirrored part stays
a true mirror image rather than a 180-degree rotation, which would print the
primary's part merely turned around.
The correct axis removes two workarounds. Both ghost paths special-cased aggregated
mirrors to "drop the X reflection, translate 1:1 and bake the flip into the mesh"
because reflecting X pushed the ghost off-bed as the primary was dragged. With a Y
reflection the X translation is already zero for aggregated tools, so that falls out
for free and the special cases are deleted.
Preview markers follow the same rule, which also fixes two placement bugs:
- Mirrors reflected across a Copy tool's zone edge, falling back to the primary's
column when a row had no Copy. In iq-mirror (0:P,1:C,2:M,3:M) the front row has
no Copy, so t2 and t3 both fell back and computed the identical X — both drawn
on top of each other in t3's zone. A mirror now reflects within its own zone.
- The toolhead footprint box flipped to the far side of the nozzle for any mirror
right of the primary. That only holds for an X-axis mirror, which reverses the
carriage's orientation; a cross-gantry mirror keeps the X orientation of the
tool behind it, so its box stays on the same side.
Tests cover the cross-gantry and diagonal cases, that the axis is caller-supplied
rather than inferred from the offset vector, and that both axes are reflections
(det = -1) rather than rotations.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
48c603faa5 |
feat(imex): firmware-managed zones — center slice + ghost suppression
Adds the `imex_firmware_managed_zones` printer-config key (default off) for IDEX/IQEX printers whose firmware applies its own copy/mirror offsets in non-primary modes (e.g. RepRapFirmware IDEX duplication mode, Flashforge Creator Pro 2/3 Pro). For these printers the slicer needs to emit a single centered slice at bed origin and let the firmware fan toolheads out from there; the previous slicer-managed iMEX rendering would draw a print at the primary zone's world position (off-bed for the firmware-fan-out paradigm). When the flag is on and the active mode is non-primary, the slicer subtracts the primary zone's plate-local center from the gcode emission frame. The writer offset is augmented but the gcode-processor offset stays at plate_origin so the gcode-preview visualizer renders the centered slice at the bed center rather than at the prepare-view zone placement. translate_to_print_space is augmented too so first_layer_print_min/max placeholders (consumed by user start_gcode like Felix's M118 header) reflect the centered frame. Slice handoff lives in PartPlate::refresh_imex_slice_offset, called from both update_slice_context (plate switch) and Plater::priv::update_background_process (every-slice path — reslice() goes through here with switch_print=false so the plate-switch hook alone wouldn't fire on mode toggle). calc_imex_ghosts early-returns in firmware-managed mode: the existing imex_head_transform math places ghosts at primary_zone_center + gantry_offset (slicer-managed semantics), which renders off-bed when the toolpath is being emitted in a centered frame. Proper firmware-managed ghost rendering (showing where copies/mirrors will actually print after firmware fan-out) is deferred. When the flag is off, all the new code paths reduce to no-ops byte-identical to prior behavior. Layer 1 unit tests in test_imex_helpers cover every gating path of compute_imex_slice_offset; full ctest suite passes (247/247). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
4966d0fae8 |
feat(imex): Span tile state for paired-gantry multicolor
Introduce ImexRole::Span as a 5th tile cycle state that declares "this tool is the multicolor partner of Primary on the same gantry." Encoded as the `S` role suffix in `imex_mode_active_tools` (e.g. `0:P,1:S,2:M,3:M`). Disambiguates paired-gantry mc-mirror from 4-independent-copies — both share the same active_tools shape sans the marker. Span drives: - Multicolor block rule: now requires Span on primary's gantry to allow multi-color slicing in a parallel mode. Replaces the prior "≥2 tools on primary's gantry" check; pre-existing 4-tool multicolor configs need T1 flipped to Span. - Ghost aggregation: one ghost per non-primary gantry when Span is present, using the column-paired representative. Aggregated-mirror drag tracks primary 1:1 in X (gantries don't share an X rail) with X-flip baked into mesh-local frame so geometry still reads as mirrored. - Zone aggregation: one full-X row strip per non-primary gantry instead of per-tool quadrants. - UI: 5th button in IMEXModesCtrl. Cycle Off→P→C→M→S→Off, only offered on multi-gantry printers and only on tiles sharing primary's gantry row. Single source of pairing truth: group_imex_active_tools_by_gantry in IMEXHelpers, consumed by ghost factory and zone calculator. Also fixes the carriage collision strip's X-boundary check, which lacked the row constraint its Y-boundary counterpart already had — paired-gantry mc-mirror was drawing a spurious right-edge strip from T3 sitting diagonally from primary. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
2c604173d3 |
fix(imex): block multi-color slicing for single-gantry "fake IMEX" modes
Catches the case where a non-primary IMEX mode's active tools all sit on one gantry — e.g. mode "0:P,1:C" on a 2x2 IQEX where T0 and T1 share gantry 0. The "Copy"/"Mirror" label is decorative there: nothing actually parallel-prints, but the user's mode_gcode still fires and emits firmware setup that doesn't apply, while the slicer treats it as a multi-color parallel print. Conceptually it's just a regular multi-tool single-gantry print and belongs in Primary mode. Changes: - imex_multicolor_block_reason now collects the set of distinct gantries spanned by the active tools and short-circuits with a clear "single gantry — not a parallel-print scenario" message before falling through to the existing within-gantry-swap check. - Drops the redundant pre-slice "Multi-material objects detected" soft warning from collect_imex_warnings — the slice-time block surfaces a more specific message at the right moment, and the soft warning was vague handwaving in front of it. Bed-temp + filament-type checks stay. - New unit test covering the single-gantry block. Behavior matrix on a 2x2 IQEX with multi-color: "0:P,1:C" single gantry -> BLOCK (new) "0:P,2:C" dual gantry, 1 each -> BLOCK (existing within-gantry-swap) "0:P,1:C,2:M,3:M" dual gantry, 2 each -> ALLOW multiple filaments to same physical via pem -> BLOCK (existing MMU sharing) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
66891899e9 |
refactor(imex): drop unused Split mode-type infrastructure
The mode-type tag (imex_mode_types config + imex_mode_type_for helper + Split sentinel) was added in |
||
|
|
0bb1cef78e |
feat(imex): polish UI per PR feedback + add parallel-mode safeguards
Felix14-v2 PR review feedback (https://github.com/OrcaSlicer/OrcaSlicer/pull/13086#issuecomment-4323696312) plus the slice-time validation work that follows from his bug list. UI polish: - Capitalize "Primary" in display (right-click mode menu, plate tooltip). Sentinel stays lowercase for wire compatibility. - Pre-slice-warnings checkbox now uses Orca's ::CheckBox so it matches the green toggle style of the rest of the IDEX/IQEX configuration page. - DPI-scaled the IMEXModesCtrl (modes editor) sub-panels, button grid, text wraps, gcode textarea, and the ghost-tooltip swatch (imgui.scaled). Legend swatches sized to body-text height for visual balance. - Primary mode tool buttons in the modes editor are now disabled (read- only): cycling roles on the IMEX-off mode is a no-op and confusing. - Modes editor sub-panels now explicitly inherit the app's window-default dark colour so chromeless ScalableButtons don't render with a visible light box around their icons on GTK dark themes. - Per-mode-line reset arrows in the modes editor: each row gets a small reset bitmap that snaps that row's name+tools+gcode triplet back to the saved preset's value. matches_config() guard on the page-level reload prevents the textbox-being-typed-into from being destroyed mid-keystroke. - New View menu item "Show IDEX/IQEX Toolhead" — toggles the per-carriage footprint boxes during G-code preview playback. Gated to Preview tab + IMEX printer; backed by app_config so it persists. Coordinated config migrations: - imex_tool_layout and imex_viz_theme migrate from coString to coEnum (ImexToolLayout / ImexVizTheme). Existing wire format preserved so saved presets and 3MFs deserialize unchanged. Side-benefit: both now pick up standard Field rendering and so finally show reset arrows. Slice-time safeguards (the validation half): - imex_suppresses_bare_toolchange(parallel_mode, count): suppresses the slicer's bare T<n> at print-start in any IMEX parallel mode (the user's imex_mode_gcode + machine_start_gcode owns tool activation there). Mid- print T<n> emits normally — Print::validate blocks the configurations where mid-print T<n> wouldn't make sense. Applied to both code paths inside GCode::set_extruder (the long multi-extruder path AND the single-extruder path that fires when multiple_extruders=false). - imex_multicolor_block_reason(): hard-stop validator returning a user- facing reason string when the active IMEX configuration can't physically support multi-color. Catches IDEX (1 tool/gantry), 2-tool-active IQEX (no within-gantry swap topology), and any MMU/AFC lane sharing among used filaments. Wired into Print::validate as a slice blocker, and into PartPlate::has_imex_multimaterial_conflict so the plater badge agrees with the slice block (no more false positives where the badge warns but the slice goes through). - New imex_mode_types config option (parallel array to imex_mode_names) and imex_mode_type_for() helper. Mode-type tag drives behaviour: zones, ghosts, and validation interpret modes differently per type. Initial types: "primary", "copy", "mirror", "split". Split modes are explicitly designed for paired-gantry IQEX multi-color and bypass the gantry-pair check in imex_multicolor_block_reason (MMU sharing still blocks them). Test coverage: - New unit tests cover imex_suppresses_bare_toolchange (4 cases), imex_multicolor_block_reason (8 cases including IDEX, IQEX 2-/4-tool- active, MMU sharing, missing primary, Split type), and imex_mode_type_for (3 cases including legacy fallback). Total: 186 IMEX assertions across 71 test cases, all passing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
41457635e9 |
test(imex): extract physical→logical helpers + cover them with unit tests
Three of the four IMEX physical-vs-logical fixes landed earlier on this
branch (fbc58d2a1d, fa048babeb, a38b95bf45, e11e7d46df) used inline
lambdas / direct loops to translate physical extruder indices to logical
filament slots. No test coverage existed for the specific composition,
even though the underlying primitives (resolve_filament_for_head,
first_filament_for_physical_head) were tested.
Pull two patterns out of Plater.cpp and GCode.cpp into IMEXHelpers as
named helpers, then unit-test them:
imex_primary_logical_from_objects(used_slots_1b, pem, primary_physical)
Walks the plate's used filament slots (1-based) and returns the
first one whose pem entry maps to the primary's physical extruder.
This is what the warning's `logical_for_primary` now delegates to —
moves the "look at object assignments, not pem first-routed default"
behavior introduced in fa048babeb out of the lambda and into a
separately-testable function.
imex_secondary_logical_slots(active_physicals, primary_physical,
plate_head_filament_map, pem)
Iterates IMEX active physicals, skips the one matching primary,
resolves each remainder via resolve_filament_for_head (per-plate
override + first-routed fallback), deduplicates, drops -1 entries.
Replaces the inline loop in GCode::_do_export's is_extruder_used
marking (a38b95bf45 + e11e7d46df).
10 new test cases in test_imex_helpers.cpp cover the cases that
correspond directly to bugs hit:
imex_primary_logical_from_objects:
- AFC primary picks the object's slot (the user's specific bug)
- Multi-color AFC primary returns first input-order match
- Direct extruder primary unambiguous (no MMU)
- No object routed to primary returns -1
- Empty inputs (no objects, empty pem)
imex_secondary_logical_slots:
- Copy mode skips primary, falls back to first-routed
- IQEX 4-mode enumerates all three secondaries
- Per-plate override wins over first-routed
- Drops unrouted physicals + deduplicates
- Only-primary-active returns empty
All [IMEX] + [Variant] + [3mf] regression: 166 assertions / 61 cases.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
c2492ccc47 |
refactor(imex): extract imex_pem_tool_for helper + unit tests
The physical_extruder_map translation used by IMEX per-tool PA emission was
inlined identically at two sites in GCode.cpp (tool-change and second-layer
transition). Extract to IMEXHelpers so the routing rule ("parallel mode AND
populated pem → physical index, else -1") is testable in isolation and the
call sites read as intent rather than re-deriving the conditional.
Production change is behavior-preserving:
- Same predicate (`!mode.empty() && mode != "primary"`)
- Same empty-pem short-circuit returning -1
- Same get_at() dispatch on hit
- Both call sites replaced with a single call
Four unit tests in [IMEX] cover the routing matrix:
- non-IMEX ("") and primary mode short-circuit
- parallel mode + empty pem short-circuits (defense-in-depth; get_at would
throw on empty values otherwise)
- identity pem (non-MMU IDEX) routes filament to itself
- MMU collapse routes multiple logical slots to one physical (7-slot profile
with 4-lane MMU on physical 0 and direct drives on 1/2/3)
All IMEX + Variant regression suites pass post-refactor (133 assertions / 48
cases under libslic3r, 25 assertions / 6 cases under fff_print [PressureAdvance]).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
9a87b96ded |
refactor(imex): Mirror reflects about zone-boundary plane, not primary origin
Replace imex_head_transform's fifth argument (Vec3d primary_origin) with a Vec2d primary_zone_center and rewrite the Mirror branch as a true reflection about the plane x = primary_zone_center.x + gantry_offset.x/2. Previous math flipped about the primary's current origin, which: * let the ghost drift out of the target zone as the primary moved, and * made mirrored drag motion track 1:1 with the primary instead of reflecting. The new transform places the ghost at the mirrored position within the target zone (matching where the mirror tool actually prints) and reflects drag so primary +X → ghost -X while Y tracks 1:1 — i.e. the ghost stays a true mirror while the user drags. Off-row Mirror targets (e.g. T3 on a 2x2) still reflect across the same X-plane as on-row peers. PartPlate::calc_imex_ghosts and update_imex_ghost_transforms now feed primary_off (the primary head's zone center) instead of an instance-space Vec3d. Mirror tests rewritten against the new geometric contract: ghost origin at the reflected position, primary drag deltas reflected across the zone-boundary plane. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
6a3de6a28f |
feat(imex): effective_physical_extruder_map helper + diagnostic no-filament tooltip
Centralize the project→printer→printer_extruder_id fallback for the physical extruder map. PrintApply, PartPlate (ghost color + cache key), Plater (tooltip + click gate) all previously open-coded the three-step lookup, and each handled the "pem unset, derive from pei" case slightly differently — an IDEX printer without an explicit pem could paint an UNPRINTABLE_COLOR ghost even though the slicer would have derived a valid mapping. - IMEXHelpers: add effective_physical_extruder_map(explicit_pem, pei) and a PresetBundle overload that wraps the project→printer precedence. - PrintApply: use the helper in place of the inline pei→pem normalization. - PartPlate / Plater: call the PresetBundle overload at every ghost-color, ghost-cache-key, tooltip, and click-gate site. - Plater::format_imex_ghost_tooltip: when no filament resolves to a head, surface an actionable message directing the user to extend the extruder count in the Machine tab, instead of the generic "(no filament routed)". - IMEXFilamentPickerPopover: hold m_pem by value so callers can pass a stack-local derived pem without lifetime worries. - Tests: 5 new cases covering explicit-wins, default-pem fallback, null inputs, and the IDEX ghost-color regression that motivated this. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
77c32a2e15 |
feat(imex): plate ghost renderer + per-plate filament map
Replaces the plater-icon popover with colored transparent ghost copies
of primary-head instances on the plate, one per secondary active head
under its Copy/Mirror role transform. Left-click on a ghost opens a
compact filament picker popover for the ghost's head (MMU lane override).
Ghosts track the primary through drag/rotate/scale/mirror and invalidate
on mode or pem changes.
Key pieces:
IMEXHelpers -- imex_head_transform (Primary/Copy/Mirror), shared role
parser, per-head filament resolution with X-axis Mirror anchor.
PartPlate -- ghost state, volume rebuild on mode/map/object mutation,
primary_origin plumbed for Mirror reflection across the primary-row
gantry plane.
GLCanvas3D -- ghost rendering with per-head filament color and
translucent blending; picking routed via volume composite id.
Plater -- ghost click + tooltip; plater icon left-click always cycles.
IMEXFilamentPickerPopover -- BitmapComboBox row for one secondary head,
writes imex_head_filament_map on selection.
bbs_3mf -- round-trip the per-plate imex_head_filament_map option.
PrintConfig -- add imex_head_filament_map as a plate option.
MMU/AFC routing for parallel modes relies on the printer profile's
physical_extruder_map (see prior commit for authoring format). Primary-
row heads and their per-plate filament overrides are resolved through
that map, so PA and temperature emission address the correct physical
extruder when multiple logical slots share one carriage.
Tests: IMEXHelpers coverage for Primary/Copy/Mirror transforms
including a 2x2 off-row regression guard for the X-axis reflection fix.
|