Merge upstream main: multi-variant printers, X-Ray view, preview and startup work

The two conflicts are both places where upstream landed on top of IDEX/IQEX
code. In GCode.cpp the relocated file header block meets the IMEX placeholder
block, and the placeholders are set first: file_start_gcode is processed through
the placeholder parser now, which throws on a name it does not know, so a script
naming {imex_mode} would abort the export if the header ran first. In
test_multifilament.cpp both sides appended a case at the end of the file.
This commit is contained in:
Clifford Garwood
2026-09-28 17:12:53 -04:00
5610 changed files with 235531 additions and 279338 deletions
+65 -15
View File
@@ -509,7 +509,7 @@ void Tab::create_preset_tab()
if (dynamic_cast<TabPrinter *>(this) || dynamic_cast<TabPrint *>(this)) {
m_extruder_switch = new MultiSwitchButton(panel);
m_extruder_switch->SetMaxSize({em_unit(this) * 40, -1});
m_extruder_switch->SetFitToOptions();
m_extruder_switch->Bind(wxCUSTOMEVT_MULTISWITCH_SELECTION, [this](auto &evt) {
evt.Skip();
int selection = evt.GetInt();
@@ -542,7 +542,8 @@ void Tab::create_preset_tab()
auto right_sizer = new wxBoxSizer(wxHORIZONTAL);
m_variant_sizer->AddStretchSpacer(1);
m_variant_sizer->Add(m_extruder_switch, 0, wxALIGN_CENTER, 0);
// Orca: proportion 1 lets a narrow row squeeze the switch, which then scrolls its buttons.
m_variant_sizer->Add(m_extruder_switch, 1, wxALIGN_CENTER, 0);
m_variant_sizer->Add(right_sizer, 1, wxALIGN_CENTER);
right_sizer->AddStretchSpacer(1);
right_sizer->Add(m_extruder_sync_box, 0, wxALIGN_CENTER | wxRIGHT, m_em_unit);
@@ -2794,7 +2795,7 @@ void TabPrint::build()
optgroup->append_single_option_line("is_infill_first", "quality_settings_wall_and_surfaces#print-infill-first");
optgroup->append_single_option_line("wall_direction", "quality_settings_wall_and_surfaces#wall-loop-direction");
optgroup->append_single_option_line("print_flow_ratio", "quality_settings_wall_and_surfaces#surface-flow-ratio");
optgroup->append_single_option_line("top_solid_infill_flow_ratio", "quality_settings_wall_and_surfaces#surface-flow-ratio");
optgroup->append_single_option_line("top_solid_infill_flow_ratio", "quality_settings_wall_and_surfaces#surface-flow-ratio", 0);
optgroup->append_single_option_line("bottom_solid_infill_flow_ratio", "quality_settings_wall_and_surfaces#surface-flow-ratio");
optgroup->append_single_option_line("set_other_flow_ratios", "quality_settings_wall_and_surfaces#surface-flow-ratio");
optgroup->append_single_option_line("first_layer_flow_ratio", "quality_settings_wall_and_surfaces#surface-flow-ratio");
@@ -3072,6 +3073,7 @@ void TabPrint::build()
optgroup->append_single_option_line("wipe_tower_rib_width", "multimaterial_settings_prime_tower#rib-width");
optgroup->append_single_option_line("wipe_tower_fillet_wall", "multimaterial_settings_prime_tower#fillet-wall");
optgroup->append_single_option_line("wipe_tower_no_sparse_layers", "multimaterial_settings_prime_tower#no-sparse-layers");
optgroup->append_single_option_line("wipe_tower_sparse_layers_combination", "multimaterial_settings_prime_tower#combine-sparse-layers");
optgroup->append_single_option_line("single_extruder_multi_material_priming", "multimaterial_settings_prime_tower");
optgroup = page->new_optgroup(L("Filament for Features"), L"param_filament_for_features");
@@ -7383,12 +7385,18 @@ void Tab::restore_last_select_item()
bool Tab::page_build_pending() const
{
return m_active_page != nullptr && m_active_page->build_pending();
return m_active_page != nullptr && (m_active_page->build_pending() || m_active_page->visibility_pending());
}
bool Tab::page_build_step()
{
return m_active_page != nullptr && m_active_page->build_step(m_mode);
if (m_active_page == nullptr)
return false;
if (m_active_page->build_pending())
m_active_page->build_step(m_mode);
else
m_active_page->update_visibility(m_mode, true);
return page_build_pending();
}
void Tab::update_description_lines()
@@ -7402,6 +7410,14 @@ void Tab::activate_selected_page(std::function<void()> throw_if_canceled)
if (!m_active_page)
return;
#ifdef __WXGTK__
// Builds the page off screen, since GTK crashes when it desensitizes a multiline text view
// that was built on screen and hidden before its first size allocation.
const bool hide_view = m_active_page->build_pending() && m_page_view->IsShown();
if (hide_view)
m_page_view->Hide();
ScopeGuard show_view([this, hide_view] { if (hide_view) m_page_view->Show(); });
#endif
m_active_page->activate(m_mode, throw_if_canceled);
update_changed_ui();
update_description_lines();
@@ -8375,7 +8391,7 @@ void Tab::update_extruder_variants(int extruder_id, bool reload)
// Orca: a non-Bambu dual-nozzle printer has two extruders but a single variant column, so
// the nozzle switch and sync button have nothing to act on. Only enable with real variants.
if (extruder_nums == 2 && m_preset_bundle->support_different_extruders()) {
if (extruder_nums >= 2 && m_preset_bundle->support_different_extruders()) {
auto options = generate_extruder_options();
m_extruder_switch->SetOptions(options);
@@ -8422,6 +8438,25 @@ void Tab::update_extruder_variants(int extruder_id, bool reload)
}
}
// The variant switch tags are the narrowest place a volume type is named, so they abbreviate it;
// the flow combo boxes and the Printer tab's parameter labels keep the full names.
static wxString short_nozzle_volume_name(const std::string &volume_name)
{
// Hybrid has no entry on purpose: it is never a variant string, and the switch lists its two
// sub-nozzle types as separate tags (see generate_extruder_options).
if (volume_name == "Standard")
return "SF";
if (volume_name == "High Flow")
return "HF";
if (volume_name == "TPU High Flow")
return "TPU HF";
if (volume_name == "E3D High Flow")
return "E3D HF";
if (volume_name == "Extra High Flow")
return "XHF";
return from_u8(volume_name);
}
std::vector<wxString> Tab::generate_extruder_options()
{
std::vector<wxString> options;
@@ -8462,7 +8497,7 @@ std::vector<wxString> Tab::generate_extruder_options()
nozzle = "";
}
}
options.push_back(wxString::Format(_L("%s: %s"), _L(drive), _L(nozzle)));
options.push_back(wxString::Format(_L("%s: %s"), _L(drive), short_nozzle_volume_name(nozzle)));
}
return options;
}
@@ -8476,18 +8511,22 @@ std::vector<wxString> Tab::generate_extruder_options()
}
std::string pt = m_preset_bundle->printers.get_edited_preset().get_printer_type(m_preset_bundle);
// Orca: the main/deputy toolhead names describe a dual-nozzle printer, where extruder 0 is the
// left (deputy) and extruder 1 the right (main) nozzle. From three extruders on the tools are
// interchangeable, so name them by index instead of repeating one side.
for (int i = 0; i < extruder_nums; ++i) {
int ext_id = (i == 0) ? DEPUTY_EXTRUDER_ID : MAIN_EXTRUDER_ID;
wxString extruder_name = _L(DevPrinterConfigUtil::get_toolhead_display_name(
pt, ext_id, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase, true));
wxString extruder_name = extruder_nums > 2 ? wxString::Format("T%d", i + 1) :
_L(DevPrinterConfigUtil::get_toolhead_display_name(
pt, (i == 0) ? DEPUTY_EXTRUDER_ID : MAIN_EXTRUDER_ID,
ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase, true));
NozzleVolumeType volume_type = NozzleVolumeType(nozzle_volumes->values[i]);
if (volume_type == NozzleVolumeType::nvtHybrid) {
options.push_back(wxString::Format(_L("%s: %s"), extruder_name, _L("Standard")));
options.push_back(wxString::Format(_L("%s: %s"), extruder_name, _L("High Flow")));
options.push_back(wxString::Format(_L("%s: %s"), extruder_name, short_nozzle_volume_name(get_nozzle_volume_type_string(NozzleVolumeType::nvtStandard))));
options.push_back(wxString::Format(_L("%s: %s"), extruder_name, short_nozzle_volume_name(get_nozzle_volume_type_string(NozzleVolumeType::nvtHighFlow))));
} else {
wxString volume_name = get_nozzle_volume_type_name(volume_type);
options.push_back(wxString::Format(_L("%s: %s"), extruder_name, volume_name));
options.push_back(wxString::Format(_L("%s: %s"), extruder_name,
short_nozzle_volume_name(get_nozzle_volume_type_string(volume_type))));
}
}
return options;
@@ -8518,7 +8557,10 @@ bool Tab::get_extruder_sync_enable_state(int extruder_id)
Preset& printer_preset = m_preset_bundle->printers.get_edited_preset();
auto nozzle_volumes = m_preset_bundle->project_config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type");
auto extruders = printer_preset.config.option<ConfigOptionEnumsGeneric>("extruder_type");
if (nozzle_volumes->values.size() < 2 || extruders->values.size() < 2) {
// Orca: every rule below describes the two toolheads of a dual-nozzle printer as left/right.
// A printer with any other extruder count has no single counterpart to copy to, so it gets no
// sync button (a toolchanger would need a target to be chosen).
if (nozzle_volumes->values.size() != 2 || extruders->values.size() != 2) {
return false;
}
@@ -8659,6 +8701,10 @@ void Tab::sync_excluder()
ExtruderType(extruders->values[extruder_id]), nozzle_type, variant_keys.second, stride);
};
int active_index = get_current_active_extruder();
// The button copies to the other toolhead, so it is only offered when that other one exists;
// without this the `1 - active_index` below would index an extruder that is not there.
if (!get_extruder_sync_enable_state(active_index))
return;
auto active_nozzle = get_actual_nozzle_volume_type(active_index);
int from_index = get_index_for_extruder(active_index, active_nozzle);
int dest_index = get_index_for_extruder(1 - active_index, active_nozzle);
@@ -8811,6 +8857,8 @@ void Page::update_visibility(ConfigOptionMode mode, bool update_contolls_visibil
}
m_show = ret_val;
if (update_contolls_visibility)
m_visibility_applied = true;
#ifdef __WXMSW__
if (!m_show) return;
// BBS: fix field control position
@@ -8864,6 +8912,7 @@ bool Page::activate_group(size_t i, ConfigOptionMode mode, std::function<void()>
auto& group = m_optgroups[i];
if (!group->activate(throw_if_canceled))
return false;
m_visibility_applied = false;
m_vsizer->Add(group->sizer, 0, wxEXPAND | (group->is_legend_line() ? (wxLEFT|wxTOP) : wxALL), m_parent->FromDIP(5)); // ORCA use less margin on parameters section
group->update_visibility(mode);
#if HIDE_FIRST_SPLIT_LINE
@@ -8898,6 +8947,7 @@ void Page::clear()
for (auto group : m_optgroups)
group->clear();
m_page_title = NULL;
m_visibility_applied = false;
}
void Page::msw_rescale()