Merge upstream main: belt printing, texture displacement color mixing, 3MF component cycle checks, undo blocked during background jobs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Clifford Garwood
2026-10-09 07:44:12 -04:00
co-authored by Claude Opus 5.5
227 changed files with 14506 additions and 1079 deletions
+170 -1
View File
@@ -4,6 +4,7 @@
#include "libslic3r/IMEXHelpers.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/BeltTransform.hpp"
#include "libslic3r/FilamentMixer.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Model.hpp"
@@ -1654,6 +1655,13 @@ void Tab::update_mode()
{
m_mode = wxGetApp().get_mode();
// toggle_options reads m_mode to gate Lines whose contents are mode-mixed
// (e.g., a multi-option row where some options are Advanced and some Expert):
// when all of a Line's options would be hidden, we hide the Line itself.
// Without refreshing here, the toggle_visible state stays stale across mode
// switches and Lines stay hidden.
toggle_options();
update_visibility();
update_changed_tree_ui();
@@ -3145,6 +3153,7 @@ void TabPrint::build()
optgroup->append_single_option_line("enable_tower_interface_cooldown_during_tower", "multimaterial_settings_prime_tower");
optgroup->append_single_option_line("prime_tower_enable_framework", "multimaterial_settings_prime_tower");
optgroup->append_single_option_line("prime_tower_width", "multimaterial_settings_prime_tower#width");
optgroup->append_single_option_line("belt_purge_tower_width", "multimaterial_settings_prime_tower#belt-purge-tower-width");
optgroup->append_single_option_line("prime_volume", "multimaterial_settings_prime_tower");
optgroup->append_single_option_line("prime_tower_brim_width", "multimaterial_settings_prime_tower#brim-width");
optgroup->append_single_option_line("prime_tower_infill_gap", "multimaterial_settings_prime_tower");
@@ -3210,6 +3219,8 @@ void TabPrint::build()
optgroup = page->new_optgroup(L("Brim"), L"param_adhension");
optgroup->append_single_option_line("brim_type", "others_settings_brim#type");
optgroup->append_single_option_line("brim_width", "others_settings_brim#width");
optgroup->append_single_option_line("leading_brim_length", "others_settings_brim#leading-length");
optgroup->append_single_option_line("extra_brim_width", "others_settings_brim#extra-width");
optgroup->append_single_option_line("brim_object_gap", "others_settings_brim#brim-object-gap");
optgroup->append_single_option_line("brim_flow_ratio", "others_settings_brim#brim-flow-ratio");
optgroup->append_single_option_line("brim_use_efc_outline", "others_settings_brim#brim-use-efc-outline");
@@ -3370,6 +3381,42 @@ void TabPrint::toggle_options()
cb->SetValue(n);
}
// "Leading edge only" describes where a part meets a moving belt, so it is offered only
// on belt printers. Same pattern as support_style above: the field owns a copy of the
// option definition, and Choice maps the combobox selection straight onto that copy's
// enum_values, so rewriting both together keeps the mapping correct.
field = m_active_page->get_field("brim_type");
if (auto choice = dynamic_cast<Choice *>(field)) {
bool is_belt_printer = false;
if (m_preset_bundle) {
const auto *belt_opt = m_preset_bundle->printers.get_edited_preset().config.option<ConfigOptionBool>("belt_printer");
if (belt_opt)
is_belt_printer = belt_opt->value;
}
auto def = print_config_def.get("brim_type");
const auto current = m_config->opt_enum<BrimType>("brim_type");
auto &opt = const_cast<ConfigOptionDef &>(field->m_opt);
auto cb = dynamic_cast<ComboBox *>(choice->window);
// Keep the entry if it is already selected, so switching to a non-belt
// printer cannot leave the control showing a value it does not offer.
const bool offer_leading_edge = is_belt_printer || current == btLeadingEdgeOnly;
const bool offered = std::find(opt.enum_values.begin(), opt.enum_values.end(), "leading_edge_only") != opt.enum_values.end();
if (cb != nullptr && offer_leading_edge != offered) {
auto n = cb->GetValue();
opt.enum_values.clear();
opt.enum_labels.clear();
cb->Clear();
for (size_t i = 0; i < def->enum_values.size(); ++ i) {
if (def->enum_values[i] == "leading_edge_only" && ! offer_leading_edge)
continue;
opt.enum_values.push_back(def->enum_values[i]);
opt.enum_labels.push_back(def->enum_labels[i]);
cb->Append(_(def->enum_labels[i]));
}
cb->SetValue(n);
}
}
// BBL printers do not support cone wipe tower
field = m_active_page->get_field("wipe_tower_wall_type");
if (auto choice = dynamic_cast<Choice*>(field)) {
@@ -5234,6 +5281,35 @@ void TabPrinter::build_fff()
//option.opt.full_width = true;
//optgroup->append_single_option_line(option);
optgroup->append_single_option_line("disable_m73", "printer_basic_information_advanced#disable-set-remaining-print-time");
// Belt printer: dedicated section. Everything except the "Enable belt printing"
// checkbox is hidden when belt_printer is off (see TabPrinter::toggle_options).
auto belt_og = page->new_optgroup(L("Belt printer"), L"param_advanced");
belt_og->append_single_option_line("belt_printer", "printer_basic_information_belt_printer#enable-belt-printing");
belt_og->append_single_option_line("belt_printer_infinite_y", "printer_basic_information_belt_printer#infinite-y-axis");
// Belt tilt: the sole mesh-side transform and the single source of truth for
// the physical tilt (drives bed rendering and support gravity tilt too).
// Isometric rotation, no distortion; the back-transform inverts it before the
// machine-frame remap. The angle is what a user checks against the machine;
// the axis is a profile-level kinematics choice, so it is Develop-only. They
// are separate rows because a shared line is shown by its first option's mode.
belt_og->append_single_option_line("belt_slice_rotation_angle", "printer_basic_information_belt_printer#tilt-angle");
belt_og->append_single_option_line("belt_slice_rotation", "printer_basic_information_belt_printer#tilt-axis");
belt_og->append_single_option_line("belt_support_floor_offset", "printer_basic_information_belt_printer#support-floor-z-offset");
// Machine-frame transform: the shear (cot) + scale (1/sin) that map
// Cartesian G-code into the printer's physical machine frame are derived
// from the belt tilt angle. Only the post-slice axis remap and the expert
// decouple override are exposed here, one option per row.
{
auto mf = page->new_optgroup(L("Machine frame transforms"), L"param_advanced");
mf->append_single_option_line("gcode_remap_x", "printer_basic_information_machine_frame_transforms#g-code-axis-remap");
mf->append_single_option_line("gcode_remap_y", "printer_basic_information_machine_frame_transforms#g-code-axis-remap");
mf->append_single_option_line("gcode_remap_z", "printer_basic_information_machine_frame_transforms#g-code-axis-remap");
mf->append_single_option_line("belt_frame_tilt_decouple", "printer_basic_information_machine_frame_transforms#machine-frame-tilt");
mf->append_single_option_line("belt_frame_tilt_angle", "printer_basic_information_machine_frame_transforms#machine-frame-tilt");
}
option = optgroup->get_option("thumbnails");
option.opt.full_width = true;
optgroup->append_single_option_line(option, "printer_basic_information_advanced#g-code-thumbnails");
@@ -5278,6 +5354,8 @@ void TabPrinter::build_fff()
optgroup->append_single_option_line("use_firmware_retraction", "printer_basic_information_advanced#use-firmware-retraction");
// optgroup->append_single_option_line("spaghetti_detector");
optgroup->append_single_option_line("time_cost", "printer_basic_information_advanced#time-cost");
optgroup->append_single_option_line("build_plate_tilt_x", "printer_basic_information_advanced#build-plate-tilt");
optgroup->append_single_option_line("build_plate_tilt_y", "printer_basic_information_advanced#build-plate-tilt");
optgroup = page->new_optgroup(L("Plugin Configuration"), L"param_gcode");
optgroup->append_single_option_line("printer_plugin_config_overrides");
@@ -5810,6 +5888,12 @@ if (is_marlin_flavor)
optgroup->append_single_option_line("tool_change_on_wipe_tower", "printer_multimaterial_wipe_tower#tool-change-on-wipe-tower");
optgroup->append_single_option_line("wait_for_temp_on_wipe_tower", "printer_multimaterial_wipe_tower#wait-for-temperature-on-wipe-tower");
// Orca-Belt: belt printers replace the classic wipe tower with an
// auto-generated purge prism; this is its enable (gated to belt printers
// in toggle_options()).
optgroup = page->new_optgroup(L("Belt purge tower"), "param_tower");
optgroup->append_single_option_line("enable_belt_purge_tower", "printer_multimaterial_wipe_tower#belt-purge-tower");
optgroup = page->new_optgroup(L("Single extruder multi-material parameters"), "param_settings");
optgroup->append_single_option_line("cooling_tube_retraction", "printer_multimaterial_semm_parameters#cooling-tube-position");
@@ -6110,6 +6194,18 @@ if (is_marlin_flavor)
// this gets executed after preset is loaded and before GUI fields are updated
void TabPrinter::on_preset_loaded()
{
// R8: reset the belt-tilt transition tracking to reflect the freshly loaded preset WITHOUT
// running update_fff()'s reset logic. on_preset_loaded() is called from Tab::load_current_preset()
// on every printer preset load, right before update()->update_fff(). Seeding m_was_belt_printer
// from the loaded preset's belt_printer flag means a preset switch (belt preset -> non-belt preset)
// enters update_fff() with m_was_belt_printer==false, so the belt->off clear branch is skipped and
// the newly loaded preset's manual tilt is preserved. An in-place belt toggle does NOT go through
// here (only through on_value_change->update), so m_was_belt_printer stays true there and the clear
// still fires. Seed m_belt_synced_tilt from the loaded tilt as a safeguard.
m_was_belt_printer = m_config->opt_bool("belt_printer");
m_belt_synced_tilt_x = m_config->opt_float("build_plate_tilt_x");
m_belt_synced_tilt_y = m_config->opt_float("build_plate_tilt_y");
// Orca
//update nozzle_volume_type
const Preset& current_printer = m_preset_bundle->printers.get_selected_preset();
@@ -6407,6 +6503,39 @@ void TabPrinter::toggle_options()
bool gcf_is_marlin_firmware = m_config->option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value == GCodeFlavor::gcfMarlinFirmware;
toggle_line("enable_power_loss_recovery", is_BBL_printer || gcf_is_marlin_firmware);
// Belt printer: show belt-specific settings only when belt_printer is enabled.
bool is_belt = m_config->opt_bool("belt_printer");
// update_fff() derives build_plate_tilt_{x,y} from the belt tilt on a belt
// printer, so an edit here would be overwritten; keep them read-only there.
toggle_option("build_plate_tilt_x", !is_belt);
toggle_option("build_plate_tilt_y", !is_belt);
bool expert_or_above = (m_mode >= comExpert);
toggle_line("belt_printer_infinite_y", is_belt);
// Belt tilt: the sole mesh-side belt transform (visible by default in belt mode).
toggle_line("belt_slice_rotation_angle", is_belt);
toggle_line("belt_slice_rotation", is_belt);
// Remap, back-transform, and global mesh-transforms toggles are gated by belt
// mode here; finer mode-based visibility is handled by each option's
// ConfigOptionMode in PrintConfig.cpp. The axis remap is Develop-only: a
// printer profile sets it once for its kinematics, and a wrong value sends
// the gantry outside the machine.
for (auto el : {"gcode_remap_x", "gcode_remap_y", "gcode_remap_z"})
toggle_line(el, is_belt);
// Rotation is the only mesh-side belt transform. Gray out its angle when no
// rotation axis is selected.
auto rot_axis = m_config->option<ConfigOptionEnum<BeltRotationAxis>>("belt_slice_rotation")->value;
toggle_option("belt_slice_rotation_angle", is_belt && rot_axis != BeltRotationAxis::None);
// Machine-frame transform: derived from the belt tilt. Only the expert
// decouple override is exposed; its angle is shown only when decoupled.
toggle_line("belt_frame_tilt_decouple", is_belt && expert_or_above);
toggle_line("belt_frame_tilt_angle",
is_belt && expert_or_above && m_config->opt_bool("belt_frame_tilt_decouple"));
toggle_line("belt_support_floor_offset", is_belt);
const bool support_parallel_printheads = printer_cfg.opt_bool("support_parallel_printheads");
toggle_line("parallel_printheads_count", support_parallel_printheads);
@@ -6425,8 +6554,14 @@ void TabPrinter::toggle_options()
}
if (m_active_page->title() == L("Multimaterial")) {
// Orca-Belt: belt printers use the belt purge tower instead of the classic
// wipe tower — show its enable only on belt printers, and hide the classic
// wipe-tower fields there (the classic tower's G-code bypasses the belt transform).
const bool is_belt_printer = m_config->opt_bool("belt_printer");
toggle_line("enable_belt_purge_tower", is_belt_printer);
const bool supports_wipe_tower_2 = !is_BBL_printer && m_config->opt_enum<WipeTowerType>("wipe_tower_type") == WipeTowerType::Type2;
toggle_line("wipe_tower_type", !is_BBL_printer);
toggle_line("wipe_tower_type", !is_BBL_printer && !is_belt_printer);
// SoftFever: hide specific settings for BBL printer
for (auto el : {
"enable_filament_ramming",
@@ -6759,6 +6894,40 @@ void TabPrinter::update_fff()
m_imex_modes_ctrl->load_from_config(*m_config);
}
// Belt printer: auto-sync build_plate_tilt_{x,y} (which drives support gravity tilt)
// from the belt slicing rotation, the single source of truth for the physical tilt.
// Tilt about X drives tilt_x, tilt about Y drives tilt_y.
//
// R8: value-guessing (zeroing any tilt matching the dormant belt-derived tilt) wiped a
// legitimate manual build_plate_tilt on a non-belt tilted-bed printer, because the belt
// defaults (rotation=X, angle=45) make a manual tilt of 45 look belt-derived. Instead we
// track the belt->non-belt transition and the exact values belt-sync wrote, and clear the
// tilt only on a genuine in-place belt-off toggle, and only if the value is still what
// belt-sync last wrote. Preset switches reset the tracking in on_preset_loaded(), so they
// never trip the reset.
if (m_config->opt_bool("belt_printer")) {
auto rot_axis = m_config->option<ConfigOptionEnum<BeltRotationAxis>>("belt_slice_rotation")->value;
const auto tilt = BeltTransformPipeline::physical_tilt(
rot_axis, m_config->opt_float("belt_slice_rotation_angle"));
if (m_config->opt_float("build_plate_tilt_x") != tilt.tilt_x_deg)
m_config->set_key_value("build_plate_tilt_x", new ConfigOptionFloat(tilt.tilt_x_deg));
if (m_config->opt_float("build_plate_tilt_y") != tilt.tilt_y_deg)
m_config->set_key_value("build_plate_tilt_y", new ConfigOptionFloat(tilt.tilt_y_deg));
// Remember exactly what belt-sync wrote, so an in-place belt-off toggle can distinguish
// a still-belt-derived tilt (safe to clear) from a since-edited manual one (keep).
m_belt_synced_tilt_x = tilt.tilt_x_deg;
m_belt_synced_tilt_y = tilt.tilt_y_deg;
} else if (m_was_belt_printer) {
// Genuine in-place belt->off toggle on the same preset (on_preset_loaded() was not called
// since the last update, so m_was_belt_printer still reflects belt mode). Clear each axis
// only if it still holds the value belt-sync last wrote; a manual override is preserved.
if (m_config->opt_float("build_plate_tilt_x") == m_belt_synced_tilt_x)
m_config->set_key_value("build_plate_tilt_x", new ConfigOptionFloat(0.));
if (m_config->opt_float("build_plate_tilt_y") == m_belt_synced_tilt_y)
m_config->set_key_value("build_plate_tilt_y", new ConfigOptionFloat(0.));
}
m_was_belt_printer = m_config->opt_bool("belt_printer");
toggle_options();
}