Merge upstream/main into belt-printer

Brings belt-printer up to main 4b4a261787. Resolutions:

- G-code header (#15897, #15915): main moved the header, config and
  thumbnail block later in _do_export; write_belt_header() moves with it,
  still after the thumbnails and outside the BTT_TFT gate.
- _extrude: first-layer acceleration keeps the per-path first-layer plane
  test with main's cached nozzle index (#16028); main's set_speed out-param
  form (#16108) everywhere else.
- GCodeWriter (#16108): the arc-to-polyline fallback for machine mappings
  that cannot express G2/G3 now runs in the out-param extrude_arc_to_xy,
  which is the overload GCode calls, and appends to the caller's string.
- GCodeProcessorResult: the belt fields join main's forwarding assign.
- Clipper2 (#15969): belt arrange helpers take Slic3r::Point; the tree
  support join types lose their ClipperLib qualifier.
- CLI arrange (#15837): belt printers still reserve no wipe tower.
- Wipe tower options (#15841): the two new sparse-layer toggles are hidden
  for belt printers like the rest of the tower options.
- Keyboard shortcuts (#15706): main's registry replaces the old key switch;
  the belt view toggle is re-registered in the next commit.
- Print::process: the belt purge-plan undo runs before main's SliceStarted
  event.
- scripts/filament_id_snapshot.json: deleted on main (a77209af8f).
- Includes and appended tests: union of both sides.
This commit is contained in:
harrierpigeon
2026-10-04 19:20:33 -05:00
11471 changed files with 2992690 additions and 2720441 deletions
+69 -22
View File
@@ -14,8 +14,17 @@
#include "Plater.hpp"
#include <algorithm>
#include "slic3r/GUI/GUI.hpp"
#include "libslic3r/libslic3r.h"
#include "slic3r/GUI/Field.hpp"
#include <cstdlib>
#include <set>
#include <map>
#include <sstream>
#include <string>
#include <vector>
#include <wx/msgdlg.h>
#include <wx/string.h>
namespace Slic3r {
namespace GUI {
@@ -78,12 +87,12 @@ void ConfigManipulation::set_option_label(const std::string& opt_key, const wxSt
cb_set_option_label(opt_key, label, opt_index);
}
void ConfigManipulation::check_nozzle_recommended_temperature_range(DynamicPrintConfig *config) {
void ConfigManipulation::check_nozzle_recommended_temperature_range(DynamicPrintConfig *config, unsigned int variant_index) {
if (is_msg_dlg_already_exist)
return;
int temperature_range_low, temperature_range_high;
if (!get_temperature_range(config, temperature_range_low, temperature_range_high)) return;
if (!get_temperature_range(config, temperature_range_low, temperature_range_high, variant_index)) return;
// Get the selected filament type
std::string filament_type = "";
@@ -123,16 +132,16 @@ void ConfigManipulation::check_nozzle_recommended_temperature_range(DynamicPrint
}
}
void ConfigManipulation::check_nozzle_temperature_range(DynamicPrintConfig *config)
void ConfigManipulation::check_nozzle_temperature_range(DynamicPrintConfig *config, unsigned int variant_index)
{
if (is_msg_dlg_already_exist)
return;
int temperature_range_low, temperature_range_high;
if (!get_temperature_range(config, temperature_range_low, temperature_range_high)) return;
if (!get_temperature_range(config, temperature_range_low, temperature_range_high, variant_index)) return;
if (config->has("nozzle_temperature")) {
if (config->opt_int("nozzle_temperature", 0) < temperature_range_low || config->opt_int("nozzle_temperature", 0) > temperature_range_high) {
if (config->opt_int("nozzle_temperature", variant_index) < temperature_range_low || config->opt_int("nozzle_temperature", variant_index) > temperature_range_high) {
wxString msg_text = _(L("The nozzle may become clogged when the temperature is out of the recommended range.\nPlease make sure whether to use this temperature to print.\n\n"));
msg_text += wxString::Format(_L("The recommended nozzle temperature for this filament type is [%d, %d] degrees Celsius."), temperature_range_low, temperature_range_high);
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
@@ -143,17 +152,17 @@ void ConfigManipulation::check_nozzle_temperature_range(DynamicPrintConfig *conf
}
}
void ConfigManipulation::check_nozzle_temperature_initial_layer_range(DynamicPrintConfig* config)
void ConfigManipulation::check_nozzle_temperature_initial_layer_range(DynamicPrintConfig* config, unsigned int variant_index)
{
if (is_msg_dlg_already_exist)
return;
int temperature_range_low, temperature_range_high;
if (!get_temperature_range(config, temperature_range_low, temperature_range_high)) return;
if (!get_temperature_range(config, temperature_range_low, temperature_range_high, variant_index)) return;
if (config->has("nozzle_temperature_initial_layer")) {
if (config->opt_int("nozzle_temperature_initial_layer", 0) < temperature_range_low ||
config->opt_int("nozzle_temperature_initial_layer", 0) > temperature_range_high)
if (config->opt_int("nozzle_temperature_initial_layer", variant_index) < temperature_range_low ||
config->opt_int("nozzle_temperature_initial_layer", variant_index) > temperature_range_high)
{
wxString msg_text = _(L("The nozzle may become clogged when the temperature is out of the recommended range.\nPlease make sure whether to use this temperature to print.\n\n"));
msg_text += wxString::Format(_L("The recommended nozzle temperature for this filament type is [%d, %d] degrees Celsius."), temperature_range_low, temperature_range_high);
@@ -171,14 +180,16 @@ void ConfigManipulation::check_adaptive_pressure_advance_model(DynamicPrintConfi
return;
const auto* model = config->option<ConfigOptionStrings>("adaptive_pressure_advance_model");
if (model == nullptr || model->values.empty())
if (model == nullptr)
return;
std::string raw_model;
for (const std::string& chunk : model->values)
raw_model += chunk;
std::string error = AdaptivePAProcessor::validate_adaptive_pa_model(raw_model);
// Each extruder variant holds its own model.
std::string error;
for (const std::string& variant_model : model->values) {
error = AdaptivePAProcessor::validate_adaptive_pa_model(variant_model);
if (!error.empty())
break;
}
if (!error.empty()) {
wxString msg_text = _L("Adaptive Pressure Advance model validation failed:\n");
msg_text += from_u8(error);
@@ -210,6 +221,30 @@ void ConfigManipulation::check_filament_max_volumetric_speed(DynamicPrintConfig
}
void ConfigManipulation::check_filament_ironing_spacing(DynamicPrintConfig *config)
{
const auto *opt = config->option<ConfigOptionFloatsNullable>("filament_ironing_spacing");
if (opt == nullptr)
return;
std::vector<double> values = opt->values;
bool reset = false;
for (size_t i = 0; i < values.size(); ++i)
if (!opt->is_nil(i) && values[i] < IRONING_SPACING_MIN) {
values[i] = 0.1;
reset = true;
}
if (!reset)
return;
const wxString msg_text = _(L("Ironing spacing too small\nIt has been reset to 0.1"));
MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK);
DynamicPrintConfig new_conf = *config;
is_msg_dlg_already_exist = true;
dialog.ShowModal();
new_conf.set_key_value("filament_ironing_spacing", new ConfigOptionFloatsNullable(values));
apply(config, &new_conf);
is_msg_dlg_already_exist = false;
}
void ConfigManipulation::check_chamber_temperature(DynamicPrintConfig* config)
{
bool support_chamber_temp_control=GUI::wxGetApp().preset_bundle->printers.get_selected_preset().config.opt_bool("support_chamber_temp_control");
@@ -332,7 +367,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
}
//BBS: ironing_spacing shouldn't be too small or equal to zero
if (config->opt_float("ironing_spacing") < 0.05)
if (config->opt_float("ironing_spacing") < IRONING_SPACING_MIN)
{
const wxString msg_text = _(L("Ironing spacing too small\nIt has been reset to 0.1"));
MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK);
@@ -343,7 +378,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
apply(config, &new_conf);
is_msg_dlg_already_exist = false;
}
if (config->opt_float("support_ironing_spacing") < 0.05)
if (config->opt_float("support_ironing_spacing") < IRONING_SPACING_MIN)
{
const wxString msg_text = _(L("Ironing spacing too small\nIt has been reset to 0.1"));
MessageDialog dialog(nullptr, msg_text, "", wxICON_WARNING | wxOK);
@@ -1093,10 +1128,14 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
for (auto el : {"wipe_tower_rotation_angle", "wipe_tower_cone_angle",
"wipe_tower_extra_spacing", "wipe_tower_max_purge_speed",
"wipe_tower_bridging", "wipe_tower_extra_flow",
"wipe_tower_no_sparse_layers"})
"wipe_tower_bridging", "wipe_tower_extra_flow"})
toggle_line(el, have_prime_tower && supports_wipe_tower_2 && !is_belt_printer);
// Orca: both tower generators skip sparse layers, so this is not a wipe tower 2 exclusive.
toggle_line("wipe_tower_no_sparse_layers", have_prime_tower && !is_belt_printer);
// Dropping the sparse layers outright leaves nothing to combine, so the two are exclusive.
toggle_line("wipe_tower_sparse_layers_combination", have_prime_tower && !is_belt_printer && !config->opt_bool("wipe_tower_no_sparse_layers"));
WipeTowerWallType wipe_tower_wall_type = config->opt_enum<WipeTowerWallType>("wipe_tower_wall_type");
bool have_rib_wall = (wipe_tower_wall_type == WipeTowerWallType::wtwRib)&&have_prime_tower&&!is_belt_printer;
toggle_line("wipe_tower_cone_angle", have_prime_tower && supports_wipe_tower_2 && !is_belt_printer && wipe_tower_wall_type == WipeTowerWallType::wtwCone);
@@ -1107,6 +1146,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
toggle_line("single_extruder_multi_material_priming", !bSEMM && have_prime_tower && supports_wipe_tower_2 && !is_belt_printer);
bool use_cyclic_ordering = config->opt_enum<ToolChangeOrderingType>("toolchange_ordering") == ToolChangeOrderingType::Cyclic;
toggle_line("toolchange_cyclic_order", use_cyclic_ordering);
toggle_line("toolchange_cyclic_first_layer", use_cyclic_ordering);
toggle_line("prime_volume",have_prime_tower && (!purge_in_primetower || !bSEMM));
for (auto el : {"flush_into_infill", "flush_into_support", "flush_into_objects"})
@@ -1156,6 +1199,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
auto is_role_based_wipe_speed = config->opt_bool("role_based_wipe_speed");
toggle_field("wipe_speed",!is_role_based_wipe_speed);
const bool have_wipe_inward = config->opt_bool("wipe_inward");
toggle_line("wipe_inward_distance", have_wipe_inward);
for (auto el : {"accel_to_decel_enable", "accel_to_decel_factor"})
toggle_line(el, gcf_is_klipper);
if(gcf_is_klipper)
@@ -1177,6 +1223,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
bool has_detect_overhang_wall = config->opt_bool("detect_overhang_wall");
bool has_overhang_reverse = config->opt_bool("overhang_reverse");
bool allow_overhang_reverse = !has_spiral_vase;
toggle_line("unsupported_wall_last", has_detect_overhang_wall);
toggle_line("overhang_reverse", allow_overhang_reverse);
toggle_line("overhang_reverse_internal_only", allow_overhang_reverse && has_overhang_reverse);
bool has_overhang_reverse_internal_only = config->opt_bool("overhang_reverse_internal_only");
@@ -1335,15 +1382,15 @@ int ConfigManipulation::show_spiral_mode_settings_dialog(bool is_object_config)
return answer;
}
bool ConfigManipulation::get_temperature_range(DynamicPrintConfig *config, int &range_low, int &range_high)
bool ConfigManipulation::get_temperature_range(DynamicPrintConfig *config, int &range_low, int &range_high, unsigned int variant_index)
{
bool range_low_exist = false, range_high_exist = false;
if (config->has("nozzle_temperature_range_low")) {
range_low = config->opt_int("nozzle_temperature_range_low", (unsigned int) 0);
range_low = config->opt_int("nozzle_temperature_range_low", variant_index);
range_low_exist = true;
}
if (config->has("nozzle_temperature_range_high")) {
range_high = config->opt_int("nozzle_temperature_range_high", (unsigned int) 0);
range_high = config->opt_int("nozzle_temperature_range_high", variant_index);
range_high_exist = true;
}
return range_low_exist && range_high_exist;