feat: Add iXex parallel printing support for IDEX and IQEX printers

Introduces first-class parallel printing (copy/mirror modes) for printers
with multiple independent X-axis carriages. Branded iXex (independent X
extruder), targeting Klipper firmware with a firmware-agnostic design.

- PrintConfig: new printer options declaring iXex capability and geometry
  (is_ixex, ixex_gantry_count, ixex_tools_per_gantry, carriage dims,
  tool layout, and per-mode name/role/gcode arrays)
- Preset: iXex keys registered in printer and process preset option lists
- Tab: IXexModesCtrl visual grid editor in Printer preset tab; mode
  dropdown in Process → Others tab; clear_pages() nulls iXex pointers to
  prevent dangling-pointer crash on preset save
- PartPlate: 2D zone visualization (active/dimmed/dividers) and
  placement-violation detection (has_ixex_placement_violations) that
  blocks slicing when objects fall outside the primary zone
- Plater: violation detection wired into update_background_process so
  the Slice button is disabled with an error notification on violation
- GCode: mode-activation G-code injected before machine_start_gcode
- GCodeViewer: multi-carriage toolhead markers in sequential preview,
  filament legend annotated with active carriage count and mode name
This commit is contained in:
Clifford Garwood
2026-04-06 14:57:39 -04:00
parent d27889eb0f
commit 1e56b65146
12 changed files with 1536 additions and 46 deletions
+18
View File
@@ -3054,6 +3054,24 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
file.write(m_writer.set_chamber_temperature(max_chamber_temp, true)); // set chamber_temperature
}
// iXex: inject mode G-code before machine_start_gcode so the carriage mode
// macro is active before any motion, homing, or probing in start G-code.
if (print.config().is_ixex.value && !print.objects().empty()) {
const std::string& ixex_mode = print.objects().front()->config().ixex_parallel_mode.value;
if (!ixex_mode.empty() && ixex_mode != "primary") {
const auto& mode_names = print.config().ixex_mode_names.values;
const auto& mode_gcodes = print.config().ixex_mode_gcodes.values;
for (size_t i = 0; i < mode_names.size() && i < mode_gcodes.size(); ++i) {
if (mode_names[i] == ixex_mode && !mode_gcodes[i].empty()) {
std::string ixex_gcode = this->placeholder_parser_process(
"ixex_mode_gcode", mode_gcodes[i], initial_extruder_id);
file.writeln(ixex_gcode);
break;
}
}
}
}
// Write the custom start G-code
file.writeln(machine_start_gcode);