feat: firmware-agnostic per-tool PA emission for IMEX parallel modes

Extends set_pressure_advance() with an optional tool index (default -1,
preserving existing behavior for all non-IMEX call sites). Per-firmware:
- Klipper: EXTRUDER=extruder[N] when tool >= 0, bare command otherwise
- RRF: M572 D<N> when tool >= 0, bare M572 otherwise (no D0 fallback)
- Marlin 2: M900 K<X> T<N> when tool >= 0, bare M900 otherwise
- Marlin Legacy / fallback: M900 K<X> always

Adds m_imex_parallel_mode to GCode, set once per export from the active
plate mode. PA and layer-transition temperature tool-qualification are
gated on this being a non-primary parallel mode — primary mode prints
use regular tool-change PA exactly like any non-IMEX printer. Secondary
active tools in parallel modes receive explicit per-tool PA at print
start since they never go through a tool-change sequence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Clifford Garwood
2026-04-14 09:17:39 -04:00
co-authored by Claude Sonnet 4.6
parent 84fc851846
commit af59501f4a
4 changed files with 61 additions and 23 deletions
+26 -10
View File
@@ -3094,9 +3094,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
std::string imex_active_mode; std::string imex_active_mode;
int imex_active_mode_index = 0; int imex_active_mode_index = 0;
std::string imex_active_mode_gcode; std::string imex_active_mode_gcode;
m_imex_parallel_mode.clear();
if (print.config().is_imex.value && !print.objects().empty()) { if (print.config().is_imex.value && !print.objects().empty()) {
const std::string& raw = print.objects().front()->config().imex_parallel_mode.value; const std::string& raw = print.objects().front()->config().imex_parallel_mode.value;
imex_active_mode = raw.empty() ? "primary" : raw; imex_active_mode = raw.empty() ? "primary" : raw;
m_imex_parallel_mode = imex_active_mode;
const auto& mode_names = print.config().imex_mode_names.values; const auto& mode_names = print.config().imex_mode_names.values;
const auto& mode_gcodes = print.config().imex_mode_gcodes.values; const auto& mode_gcodes = print.config().imex_mode_gcodes.values;
for (size_t i = 0; i < mode_names.size(); ++i) { for (size_t i = 0; i < mode_names.size(); ++i) {
@@ -3161,13 +3163,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
// mark the first filament used in print // mark the first filament used in print
file.write_format(";VT%d\n", initial_extruder_id); file.write_format(";VT%d\n", initial_extruder_id);
} }
// Orca: add missing PA settings for initial filament
if (m_config.enable_pressure_advance.get_at(initial_non_support_extruder_id)) {
file.write(m_writer.set_pressure_advance(m_config.pressure_advance.get_at(initial_non_support_extruder_id)));
// Orca: Adaptive PA
// Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(initial_non_support_extruder_id));
}
} }
//flush FanMover buffer to avoid modifying the start gcode if it's manual. //flush FanMover buffer to avoid modifying the start gcode if it's manual.
@@ -3227,6 +3222,19 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
// Set initial extruder only after custom start G-code. // Set initial extruder only after custom start G-code.
// Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed. // Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed.
file.write(this->set_extruder(initial_extruder_id, 0.)); file.write(this->set_extruder(initial_extruder_id, 0.));
// IMEX parallel modes only: emit PA for all secondary active tools.
// In primary mode, regular tool-change PA handles each tool as it becomes active.
// In parallel modes no tool changes occur, so every carriage must be addressed
// explicitly here before printing starts.
if (!m_imex_parallel_mode.empty() && m_imex_parallel_mode != "primary") {
for (int tool_idx : get_imex_active_tools(print)) {
if (tool_idx == (int)initial_extruder_id) continue;
if (!print.config().enable_pressure_advance.get_at(tool_idx)) continue;
file.write(m_writer.set_pressure_advance(
print.config().pressure_advance.get_at(tool_idx), tool_idx));
}
}
} }
// BBS: set that indicates objs with brim // BBS: set that indicates objs with brim
for (auto iter = print.m_brimMap.begin(); iter != print.m_brimMap.end(); ++iter) { for (auto iter = print.m_brimMap.begin(); iter != print.m_brimMap.end(); ++iter) {
@@ -4678,7 +4686,8 @@ LayerResult GCode::process_layer(
// IMEX parallel modes: secondary carriages (T1-T3) never receive tool-change commands, // IMEX parallel modes: secondary carriages (T1-T3) never receive tool-change commands,
// so they're not in m_writer.extruders() and multiple_extruders==false (max id==0). // so they're not in m_writer.extruders() and multiple_extruders==false (max id==0).
// Use the static set_temperature path for IMEX so the T index is always emitted. // Use the static set_temperature path for IMEX so the T index is always emitted.
if (print.config().is_imex.value) { // In primary mode, fall through to the standard path — tool changes handle temps normally.
if (!m_imex_parallel_mode.empty() && m_imex_parallel_mode != "primary") {
// All active tools need explicit temps — none receive tool-change commands, // All active tools need explicit temps — none receive tool-change commands,
// so we can't rely on the condition used for non-IMEX (temp != initial_layer_temp). // so we can't rely on the condition used for non-IMEX (temp != initial_layer_temp).
// A tool whose initial and regular temps are the same still needs to be set here. // A tool whose initial and regular temps are the same still needs to be set here.
@@ -7550,7 +7559,12 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
} }
if (m_config.enable_pressure_advance.get_at(new_filament_id)) { if (m_config.enable_pressure_advance.get_at(new_filament_id)) {
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id)); // In IMEX parallel modes each carriage needs an explicit tool address.
// In primary mode (single active tool) regular tool changes handle PA
// so no qualifier is needed — same as a non-IMEX printer.
const bool imex_parallel = !m_imex_parallel_mode.empty() && m_imex_parallel_mode != "primary";
const int pa_tool = imex_parallel ? (int)new_filament_id : -1;
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id), pa_tool);
// Orca: Adaptive PA // Orca: Adaptive PA
// Reset Adaptive PA processor last PA value // Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(new_filament_id)); m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(new_filament_id));
@@ -7849,7 +7863,9 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo
gcode += m_ooze_prevention.post_toolchange(*this); gcode += m_ooze_prevention.post_toolchange(*this);
if (m_config.enable_pressure_advance.get_at(new_filament_id)) { if (m_config.enable_pressure_advance.get_at(new_filament_id)) {
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id)); const bool imex_parallel = !m_imex_parallel_mode.empty() && m_imex_parallel_mode != "primary";
const int pa_tool = imex_parallel ? (int)new_filament_id : -1;
gcode += m_writer.set_pressure_advance(m_config.pressure_advance.get_at(new_filament_id), pa_tool);
// Orca: Adaptive PA // Orca: Adaptive PA
// Reset Adaptive PA processor last PA value // Reset Adaptive PA processor last PA value
m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(new_filament_id)); m_pa_processor->resetPreviousPA(m_config.pressure_advance.get_at(new_filament_id));
+5
View File
@@ -592,6 +592,11 @@ private:
std::unique_ptr<AdaptivePAProcessor> m_pa_processor; std::unique_ptr<AdaptivePAProcessor> m_pa_processor;
// IMEX: active parallel mode name ("primary", "copy", "iq-copy", etc.).
// Set at the start of export. Empty string means non-IMEX or not yet set.
// PA and temperature tool-qualification is only applied when this is not "primary".
std::string m_imex_parallel_mode;
std::unique_ptr<WipeTowerIntegration> m_wipe_tower; std::unique_ptr<WipeTowerIntegration> m_wipe_tower;
std::unique_ptr<SmallAreaInfillFlowCompensator> m_small_area_infill_flow_compensator; std::unique_ptr<SmallAreaInfillFlowCompensator> m_small_area_infill_flow_compensator;
+29 -12
View File
@@ -351,22 +351,39 @@ std::string GCodeWriter::set_junction_deviation(double junction_deviation){
return gcode.str(); return gcode.str();
} }
std::string GCodeWriter::set_pressure_advance(double pa) const std::string GCodeWriter::set_pressure_advance(double pa, int tool) const
{ {
std::ostringstream gcode; std::ostringstream gcode;
if (pa < 0) if (pa < 0)
return gcode.str(); return gcode.str();
if(m_is_bbl_printers){ if (m_is_bbl_printers) {
//SoftFever: set L1000 to use linear model // SoftFever: set L1000 to use linear model
gcode << "M900 K" <<std::setprecision(4)<< pa << " L1000 M10 ; Override pressure advance value\n"; gcode << "M900 K" << std::setprecision(4) << pa << " L1000 M10 ; Override pressure advance value\n";
} } else if (FLAVOR_IS(gcfKlipper)) {
else{ // Klipper routes PA by extruder name, not active tool.
if (FLAVOR_IS(gcfKlipper)) // Convention: first extruder is "extruder", subsequent are "extruder1", "extruder2", etc.
gcode << "SET_PRESSURE_ADVANCE ADVANCE=" << std::setprecision(4) << pa << "; Override pressure advance value\n"; gcode << "SET_PRESSURE_ADVANCE ADVANCE=" << std::setprecision(4) << pa;
else if(FLAVOR_IS(gcfRepRapFirmware)) if (tool > 0)
gcode << ("M572 D0 S") << std::setprecision(4) << pa << "; Override pressure advance value\n"; gcode << " EXTRUDER=extruder" << tool;
else else if (tool == 0)
gcode << "M900 K" <<std::setprecision(4)<< pa << "; Override pressure advance value\n"; gcode << " EXTRUDER=extruder";
gcode << " ; Override pressure advance value\n";
} else if (FLAVOR_IS(gcfRepRapFirmware)) {
// RRF: M572 without D applies to the current tool; with D targets a specific extruder.
// Use D only when an explicit tool index is provided (IMEX parallel modes).
gcode << "M572";
if (tool >= 0)
gcode << " D" << tool;
gcode << " S" << std::setprecision(4) << pa << " ; Override pressure advance value\n";
} else if (FLAVOR_IS(gcfMarlinFirmware)) {
// Marlin 2.x supports T parameter for per-extruder LA
gcode << "M900 K" << std::setprecision(4) << pa;
if (tool >= 0)
gcode << " T" << tool;
gcode << " ; Override pressure advance value\n";
} else {
// Marlin Legacy and everything else: single-extruder M900, no tool parameter
gcode << "M900 K" << std::setprecision(4) << pa << " ; Override pressure advance value\n";
} }
return gcode.str(); return gcode.str();
} }
+1 -1
View File
@@ -57,7 +57,7 @@ public:
// Orca: set acceleration and jerk in one command for Klipper // Orca: set acceleration and jerk in one command for Klipper
std::string set_accel_and_jerk(unsigned int acceleration, double jerk); std::string set_accel_and_jerk(unsigned int acceleration, double jerk);
std::string set_junction_deviation(double junction_deviation); std::string set_junction_deviation(double junction_deviation);
std::string set_pressure_advance(double pa) const; std::string set_pressure_advance(double pa, int tool = -1) const;
std::string set_input_shaping(char axis, float damp, float freq, std::string type) const; std::string set_input_shaping(char axis, float damp, float freq, std::string type) const;
std::string reset_e(bool force = false); std::string reset_e(bool force = false);
std::string update_progress(unsigned int num, unsigned int tot, bool allow_100 = false) const; std::string update_progress(unsigned int num, unsigned int tot, bool allow_100 = false) const;