mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-11 09:51:06 +00:00
8a5bc75e7b034e855a8720fa98eac7ee12d8cc02
15
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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.
|