Merge upstream main (Bambu H2C/A2L multi-nozzle support) into IMEX branch

Conflicts were all co-located additions rather than design collisions:
- GCode.cpp: adopt upstream's toolchange(filament_id, nozzle_id) signature and
  per-variant set_config_index() while keeping the IMEX bare-T<n> suppression;
  rebase the second-layer temperature loop's non-IMEX branch onto upstream's
  get_filament_config_index() resolution.
- Preset.cpp / PresetBundle.cpp / PrintConfig.cpp: keep both sides' option-list
  and enum-map entries.
- GLCanvas3D.cpp: upstream's printable_heights argument plus the IMEX ghost pass.
- PartPlate.cpp: keep <set> (still used).
- test_gcodewriter.cpp / test_3mf.cpp: keep both sides' test cases.
This commit is contained in:
Clifford Garwood
2026-07-13 16:22:22 -04:00
691 changed files with 84512 additions and 2016 deletions
+41 -20
View File
@@ -612,23 +612,15 @@ std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, boo
std::string GCodeWriter::toolchange_prefix() const
{
std::string gcode = "T";
// Orca: the manual-filament-change tag must stay ahead of the flavor selection so
// MMU manual-change handling keeps working.
if (config.manual_filament_change)
gcode = ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Manual_Tool_Change) + "T";
else {
if (m_is_bbl_printers)
gcode = "M1020 S";
else {
if (FLAVOR_IS(gcfMakerWare))
gcode = "M135 T";
else if (FLAVOR_IS(gcfSailfish))
gcode = "M108 T";
}
}
return gcode;
return ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Manual_Tool_Change) + "T";
return FLAVOR_IS(gcfMakerWare) ? "M135 T" :
FLAVOR_IS(gcfSailfish) ? "M108 T" : "T";
}
std::string GCodeWriter::toolchange(unsigned int filament_id)
std::string GCodeWriter::toolchange(unsigned int filament_id, int nozzle_id)
{
// set the new extruder
auto filament_extruder_iter = Slic3r::lower_bound_by_predicate(m_filament_extruders.begin(), m_filament_extruders.end(), [filament_id](const Extruder &e) { return e.id() < filament_id; });
@@ -640,9 +632,16 @@ std::string GCodeWriter::toolchange(unsigned int filament_id)
// return the toolchange command
// if we are running a single-extruder setup, just set the extruder and return nothing
std::ostringstream gcode;
// Orca: also emit for non-BBL single-extruder multi-filament setups (MMU-style).
if (this->multiple_extruders || (this->config.filament_diameter.values.size() > 1 && !is_bbl_printers())) {
// Orca: call toolchange_prefix() to get the correct command prefix based on the configuration and flavor.
gcode << this->toolchange_prefix() << filament_id;
// Orca: manual filament change keeps its tag line even on BBL machines, so the
// M1020 form must not shadow it. nozzle_id is signed: the null-safe nozzle
// lookup legitimately yields -1 ("no specific nozzle"), matching the literal
// H-1 the stock change templates emit; an unsigned would wrap.
if (m_is_bbl_printers && !config.manual_filament_change)
gcode << "M1020 S" << filament_id << " H" << nozzle_id;
else
gcode << this->toolchange_prefix() << filament_id;
if (GCodeWriter::full_gcode_comment)
gcode << " ; change extruder";
gcode << "\n";
@@ -651,6 +650,25 @@ std::string GCodeWriter::toolchange(unsigned int filament_id)
return gcode.str();
}
// Current parked-retract length of the filament's extruder, share-aware. m_filament_extruders is
// sorted by id (see toolchange), so a lower_bound lookup finds the entry; unknown filament ids
// degrade to 0 rather than dereferencing end().
double GCodeWriter::get_extruder_retracted_length(const int filament_id)
{
double res = 0.0;
auto filament_extruder_iter = Slic3r::lower_bound_by_predicate(m_filament_extruders.begin(), m_filament_extruders.end(),
[filament_id](const Extruder &e) { return (int) e.id() < filament_id; });
if (filament_extruder_iter == m_filament_extruders.end() || (int) filament_extruder_iter->id() != filament_id)
return res;
if (filament_extruder_iter->is_share_extruder())
res = filament_extruder_iter->get_share_retracted_length();
else
res = filament_extruder_iter->get_single_retracted_length();
return res;
}
std::string GCodeWriter::set_speed(double F, const std::string &comment, const std::string &cooling_marker)
{
assert(F > 0.);
@@ -1120,7 +1138,7 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std
return gcode;
}
std::string GCodeWriter::unretract()
std::string GCodeWriter::unretract(float extra_retract)
{
std::string gcode;
@@ -1136,7 +1154,9 @@ std::string GCodeWriter::unretract()
//BBS
// use G1 instead of G0 because G0 will blend the restart with the previous travel move
GCodeG1Formatter w;
w.emit_e(filament()->E());
// extra_retract over-extrudes for the PETG pre-extrusion; 0 by
// default -> identical to the plain deretract E position.
w.emit_e(filament()->E() + extra_retract);
w.emit_f(filament()->deretract_speed() * 60.);
//BBS
w.emit_comment(GCodeWriter::full_gcode_comment, " ; unretract");
@@ -1269,8 +1289,9 @@ std::string GCodeWriter::set_extruder(unsigned int filament_id)
auto filament_ext_it = Slic3r::lower_bound_by_predicate(m_filament_extruders.begin(), m_filament_extruders.end(), [filament_id](const Extruder &e) { return e.id() < filament_id; });
unsigned int extruder_id = filament_ext_it->extruder_id();
assert(filament_ext_it != m_filament_extruders.end() && filament_ext_it->id() == filament_id);
//TODO: optmize here, pass extruder_id to toolchange
return this->need_toolchange(filament_id) ? this->toolchange(filament_id) : "";
// Orca: writer-only context (calibration paths) has no nozzle grouping; the
// filament's own extruder id is the correct degenerate nozzle value.
return this->need_toolchange(filament_id) ? this->toolchange(filament_id, (int) extruder_id) : "";
}
void GCodeWriter::init_extruder(unsigned int filament_id)