mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 10:51:22 +00:00
X2D Support (#13388)
# Description Adresses #13294 - Adds the X2D printer definition, machine presets, process presets, filament presets, BBL profile index entries, CLI config entries, filament blacklist updates, and printer/load/calibration/cover assets. - Updates dual-nozzle handling to use configured toolhead labels and match Bambu X2D hotend placeholders. - Adds X2D-specific wipe tower cooling placeholder support and 3MF filament/nozzle change sequence metadata import/export plumbing. # Note I own a P2S and an X2D. That's all. I frankly have no idea if my changes cause regression on other printers, and have no capability to test. I know that for my X2D, which runs an AMS, .2mm nozzles, SuperTack, and in LAN mode, this has been working without issue. # Screenshots/Recordings/Graphs <img width="606" height="380" alt="Dual nozzle control" src="https://github.com/user-attachments/assets/0d1c1063-4621-4097-b97c-d739557bf18c" /> *Dual nozzle control* <img width="726" height="260" alt="image" src="https://github.com/user-attachments/assets/270355b7-ca67-4ca3-ad19-582b8f11411b" /> *Multi nozzle filament override* <img width="416" height="202" alt="X2D Machine config and dual nozzle support" src="https://github.com/user-attachments/assets/6a5c07b2-0d20-4819-8f42-d60731313249" /> *X2D Machine config and dual nozzle support* <img width="397" height="142" alt="Filament for Supports test prints" src="https://github.com/user-attachments/assets/3c7546bd-0e27-4d56-89b7-d9ca18c976f9" /> *Filament for Supports has been used in over 20 hours of test prints* <img width="210" height="263" alt="Left vs Right filament distinction" src="https://github.com/user-attachments/assets/03322268-b669-4f14-8d77-c4d96843d219" /> *Left vs Right filament distinction* <img width="557" height="327" alt="Custom filament mapping" src="https://github.com/user-attachments/assets/c1c4396f-7359-474e-80bd-78fec22f9c82" /> *Custom filament mapping* <img width="556" height="314" alt="Auto map" src="https://github.com/user-attachments/assets/d83e3217-edce-4340-886e-043962003a30" /> *Auto map* <img width="689" height="664" alt="LAN mode send print with X2D preview and no errors" src="https://github.com/user-attachments/assets/76009bbf-31d3-4a6c-979c-8643b487c824" /> *LAN mode send print with X2D preview and no errors, dual nozzle selection* ## Tests - 20 hours of dual-nozzle printing. - 100% CTest tests passed - Validated 208 changed JSON files. <!-- > A guide for users on how to download the artifacts from this PR. --> [How to Download Pull Requests Artifacts for Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts) Fix #13294
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
#include "DevConfigUtil.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
#include <wx/dir.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
@@ -246,4 +249,49 @@ std::map<std::string, std::vector<std::string>> DevPrinterConfigUtil::get_all_su
|
||||
return subseries;
|
||||
}
|
||||
|
||||
};
|
||||
std::string DevPrinterConfigUtil::get_toolhead_display_name(
|
||||
const std::string& type_str,
|
||||
int ext_id,
|
||||
ToolHeadComponent component,
|
||||
ToolHeadNameCase name_case,
|
||||
bool short_name)
|
||||
{
|
||||
static const std::map<ToolHeadComponent, std::string> comp_keys = {
|
||||
{ ToolHeadComponent::Extruder, "extruder" },
|
||||
{ ToolHeadComponent::Nozzle, "nozzle" },
|
||||
{ ToolHeadComponent::Hotend, "hotend" }
|
||||
};
|
||||
|
||||
const int case_index = static_cast<int>(name_case);
|
||||
const std::string role_key = std::to_string(ext_id);
|
||||
const std::string& comp_key = comp_keys.at(component);
|
||||
|
||||
std::string result;
|
||||
auto names_json = get_value_from_config<json>(type_str, "tool_head_display_names");
|
||||
if (!names_json.is_null() && names_json.contains(role_key) && names_json[role_key].contains(comp_key)) {
|
||||
auto& arr = names_json[role_key][comp_key];
|
||||
if (arr.is_array() && case_index < static_cast<int>(arr.size()))
|
||||
result = arr[case_index].get<std::string>();
|
||||
}
|
||||
|
||||
if (result.empty()) {
|
||||
const std::string side = ext_id == DEPUTY_EXTRUDER_ID ? "Left" : "Right";
|
||||
const std::string component_name = component == ToolHeadComponent::Extruder ? "Extruder" :
|
||||
component == ToolHeadComponent::Hotend ? "Hotend" : "Nozzle";
|
||||
result = side + " " + component_name;
|
||||
if (name_case == ToolHeadNameCase::SentenceCase && result.size() > side.size() + 1)
|
||||
result[side.size() + 1] = static_cast<char>(std::tolower(static_cast<unsigned char>(result[side.size() + 1])));
|
||||
else if (name_case == ToolHeadNameCase::LowerCase)
|
||||
std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
}
|
||||
|
||||
if (short_name) {
|
||||
auto sp = result.find(' ');
|
||||
if (sp != std::string::npos)
|
||||
result = result.substr(0, sp);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -30,6 +30,17 @@ public:
|
||||
~dePrinterConfigFactory() = default;
|
||||
};
|
||||
|
||||
enum class ToolHeadComponent {
|
||||
Extruder,
|
||||
Nozzle,
|
||||
Hotend
|
||||
};
|
||||
|
||||
enum class ToolHeadNameCase {
|
||||
TitleCase = 0,
|
||||
SentenceCase = 1,
|
||||
LowerCase = 2
|
||||
};
|
||||
|
||||
class DevPrinterConfigUtil
|
||||
{
|
||||
@@ -70,6 +81,12 @@ public:
|
||||
|
||||
/*extruder*/
|
||||
static bool get_printer_can_set_nozzle(std::string type_str) { return get_value_from_config<bool>(type_str, "enable_set_nozzle_info"); }// can set nozzle from studio
|
||||
static std::string get_toolhead_display_name(
|
||||
const std::string& type_str,
|
||||
int ext_id,
|
||||
ToolHeadComponent component,
|
||||
ToolHeadNameCase name_case = ToolHeadNameCase::TitleCase,
|
||||
bool short_name = false);
|
||||
|
||||
/*print job*/
|
||||
static bool support_ams_ext_mix_print(std::string type_str) { return get_value_from_config<bool>(type_str, "print", "support_ams_ext_mix_print"); }
|
||||
@@ -200,4 +217,4 @@ static std::string _parse_printer_type(const std::string& type_str)
|
||||
return type_str;
|
||||
}
|
||||
|
||||
};// namespace Slic3r
|
||||
};// namespace Slic3r
|
||||
|
||||
@@ -6103,6 +6103,9 @@ int PartPlateList::store_to_3mf_structure(PlateDataPtrs& plate_data_list, bool w
|
||||
plate_data_item->is_label_object_enabled = m_plate_list[i]->m_gcode_result->label_object_enabled;
|
||||
plate_data_item->limit_filament_maps = m_plate_list[i]->m_gcode_result->limit_filament_maps;
|
||||
plate_data_item->layer_filaments = m_plate_list[i]->m_gcode_result->layer_filaments;
|
||||
plate_data_item->filament_change_sequence = m_plate_list[i]->m_gcode_result->filament_change_sequence;
|
||||
plate_data_item->nozzle_change_sequence = m_plate_list[i]->m_gcode_result->nozzle_change_sequence;
|
||||
plate_data_item->optimal_assignment = m_plate_list[i]->m_gcode_result->optimal_assignment;
|
||||
plate_data_item->first_layer_time = std::to_string(m_plate_list[i]->cali_bboxes_data.first_layer_time);
|
||||
Print *print = nullptr;
|
||||
m_plate_list[i]->get_print((PrintBase **) &print, nullptr, nullptr);
|
||||
@@ -6178,6 +6181,9 @@ int PartPlateList::load_from_3mf_structure(PlateDataPtrs& plate_data_list, int f
|
||||
gcode_result->label_object_enabled = plate_data_list[i]->is_label_object_enabled;
|
||||
gcode_result->timelapse_warning_code = plate_data_list[i]->timelapse_warning_code;
|
||||
m_plate_list[index]->set_timelapse_warning_code(plate_data_list[i]->timelapse_warning_code);
|
||||
gcode_result->filament_change_sequence = plate_data_list[i]->filament_change_sequence;
|
||||
gcode_result->nozzle_change_sequence = plate_data_list[i]->nozzle_change_sequence;
|
||||
gcode_result->optimal_assignment = plate_data_list[i]->optimal_assignment;
|
||||
m_plate_list[index]->slice_filaments_info = plate_data_list[i]->slice_filaments_info;
|
||||
gcode_result->warnings = plate_data_list[i]->warnings;
|
||||
gcode_result->filament_maps = plate_data_list[i]->filament_maps;
|
||||
|
||||
@@ -169,6 +169,8 @@
|
||||
|
||||
#include "DeviceCore/DevFilaSystem.h"
|
||||
#include "DeviceCore/DevManager.h"
|
||||
#include "DeviceCore/DevConfigUtil.h"
|
||||
#include "DeviceCore/DevDefs.h"
|
||||
|
||||
using boost::optional;
|
||||
namespace fs = boost::filesystem;
|
||||
@@ -443,6 +445,7 @@ struct ExtruderGroup : StaticGroup
|
||||
}
|
||||
|
||||
void update_ams();
|
||||
void SetTitle(const wxString& title);
|
||||
|
||||
void sync_ams(MachineObject const *obj, std::vector<DevAms *> const &ams4, std::vector<DevAms *> const &ams1);
|
||||
|
||||
@@ -1263,6 +1266,16 @@ void ExtruderGroup::sync_ams(MachineObject const *obj, std::vector<DevAms *> con
|
||||
update_ams();
|
||||
}
|
||||
|
||||
void ExtruderGroup::SetTitle(const wxString& title)
|
||||
{
|
||||
m_label = title;
|
||||
int tW, tH, descent, externalLeading;
|
||||
GetTextExtent(m_label.IsEmpty() ? "Orca" : m_label, &tW, &tH, &descent, &externalLeading, &m_font);
|
||||
m_label_height = tH - externalLeading;
|
||||
m_label_width = tW;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
bool Sidebar::priv::switch_diameter(bool single)
|
||||
{
|
||||
wxString diameter;
|
||||
@@ -1272,13 +1285,16 @@ bool Sidebar::priv::switch_diameter(bool single)
|
||||
auto diameter_left = left_extruder->combo_diameter->GetValue();
|
||||
auto diameter_right = right_extruder->combo_diameter->GetValue();
|
||||
if (diameter_left != diameter_right) {
|
||||
std::string printer_type = wxGetApp().preset_bundle->printers.get_edited_preset().get_printer_type(wxGetApp().preset_bundle);
|
||||
auto left_name = _L(DevPrinterConfigUtil::get_toolhead_display_name(printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::SentenceCase));
|
||||
auto right_name = _L(DevPrinterConfigUtil::get_toolhead_display_name(printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::SentenceCase));
|
||||
MessageDialog dlg(this->plater,
|
||||
_L("The software does not support using different diameter of nozzles for one print. "
|
||||
"If the left and right nozzles are inconsistent, we can only proceed with single-head printing. "
|
||||
"Please confirm which nozzle you would like to use for this project."),
|
||||
_L("Switch diameter"), wxYES_NO | wxNO_DEFAULT);
|
||||
dlg.SetButtonLabel(wxID_YES, wxString::Format(_L("Left nozzle: %smm"), diameter_left));
|
||||
dlg.SetButtonLabel(wxID_NO, wxString::Format(_L("Right nozzle: %smm"), diameter_right));
|
||||
dlg.SetButtonLabel(wxID_YES, wxString::Format("%s: %smm", left_name, diameter_left));
|
||||
dlg.SetButtonLabel(wxID_NO, wxString::Format("%s: %smm", right_name, diameter_right));
|
||||
int result = dlg.ShowModal();
|
||||
if (result == wxID_YES)
|
||||
diameter = diameter_left;
|
||||
@@ -2661,6 +2677,9 @@ void Sidebar::update_presets(Preset::Type preset_type)
|
||||
};
|
||||
auto image_path = get_cur_select_bed_image();
|
||||
if (is_dual_extruder) {
|
||||
std::string printer_type = printer_preset.get_printer_type(wxGetApp().preset_bundle);
|
||||
p->left_extruder->SetTitle(_L(DevPrinterConfigUtil::get_toolhead_display_name(printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase)));
|
||||
p->right_extruder->SetTitle(_L(DevPrinterConfigUtil::get_toolhead_display_name(printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::TitleCase)));
|
||||
AMSCountPopupWindow::UpdateAMSCount(0, p->left_extruder);
|
||||
AMSCountPopupWindow::UpdateAMSCount(1, p->right_extruder);
|
||||
//if (!p->is_switching_diameter) {
|
||||
|
||||
@@ -5153,7 +5153,7 @@ void PrinterInfoBox::UpdatePlate(const std::string& plate_name)
|
||||
name = _L("Textured PEI Plate");
|
||||
m_bed_image->SetBitmap(create_scaled_bitmap("bed_pei", this, 40));
|
||||
}
|
||||
else if (plate_name == "SuperTack Plate") {
|
||||
else if (plate_name == "Supertack Plate" || plate_name == "SuperTack Plate") {
|
||||
name = _L("Cool Plate (SuperTack)");
|
||||
m_bed_image->SetBitmap(create_scaled_bitmap("bed_cool_supertack", this, 40));
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "PrePrintChecker.hpp"
|
||||
|
||||
#include "DeviceCore/DevConfig.h"
|
||||
#include "DeviceCore/DevConfigUtil.h"
|
||||
#include "DeviceCore/DevFilaSystem.h"
|
||||
#include "DeviceCore/DevManager.h"
|
||||
#include "DeviceCore/DevMapping.h"
|
||||
@@ -1572,10 +1573,11 @@ bool SyncAmsInfoDialog::is_nozzle_type_match(DevExtderSystem data, wxString &err
|
||||
if (target_machine_nozzle_id < flow_type_of_machine.size()) {
|
||||
if (flow_type_of_machine[target_machine_nozzle_id] != used_extruders_flow[it->first]) {
|
||||
wxString pos;
|
||||
auto sai_nz_pt = wxGetApp().preset_bundle->printers.get_edited_preset().get_printer_type(wxGetApp().preset_bundle);
|
||||
if (target_machine_nozzle_id == DEPUTY_EXTRUDER_ID) {
|
||||
pos = _L("left nozzle");
|
||||
pos = _L(DevPrinterConfigUtil::get_toolhead_display_name(sai_nz_pt, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::LowerCase));
|
||||
} else if ((target_machine_nozzle_id == MAIN_EXTRUDER_ID)) {
|
||||
pos = _L("right nozzle");
|
||||
pos = _L(DevPrinterConfigUtil::get_toolhead_display_name(sai_nz_pt, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::LowerCase));
|
||||
}
|
||||
|
||||
error_message = wxString::Format(_L("The nozzle flow setting of %s(%s) doesn't match with the slicing file(%s). "
|
||||
|
||||
+93
-6
@@ -61,6 +61,7 @@
|
||||
#endif // WIN32
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -499,6 +500,22 @@ void Tab::create_preset_tab()
|
||||
}
|
||||
#endif
|
||||
|
||||
if (dynamic_cast<TabFilament *>(this)) {
|
||||
m_variant_combo = new MultiSwitchButton(panel);
|
||||
m_variant_combo->Bind(wxCUSTOMEVT_MULTISWITCH_SELECTION, [this](auto &evt) {
|
||||
evt.Skip();
|
||||
switch_excluder(evt.GetInt());
|
||||
reload_config();
|
||||
update_changed_ui();
|
||||
toggle_options();
|
||||
if (m_active_page)
|
||||
m_active_page->update_visibility(m_mode, true);
|
||||
m_page_view->GetParent()->Layout();
|
||||
});
|
||||
m_variant_combo->Hide();
|
||||
m_main_sizer->Add(m_variant_combo, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, m_em_unit);
|
||||
}
|
||||
|
||||
this->SetSizer(m_main_sizer);
|
||||
//this->Layout();
|
||||
m_page_view = m_parent->get_paged_view();
|
||||
@@ -1295,6 +1312,8 @@ void Tab::msw_rescale()
|
||||
{
|
||||
m_mode_view->Rescale();
|
||||
}
|
||||
if (m_variant_combo)
|
||||
m_variant_combo->Rescale();
|
||||
|
||||
if (m_detach_preset_btn)
|
||||
m_detach_preset_btn->msw_rescale();
|
||||
@@ -1360,6 +1379,8 @@ void Tab::sys_color_changed()
|
||||
m_active_page->sys_color_changed();
|
||||
if (m_extruder_switch)
|
||||
m_extruder_switch->Rescale();
|
||||
if (m_variant_combo)
|
||||
m_variant_combo->Rescale();
|
||||
|
||||
//BBS: GUI refactor
|
||||
//Layout();
|
||||
@@ -3747,7 +3768,7 @@ void TabFilament::update_filament_overrides_page(const DynamicPrintConfig* print
|
||||
// "filament_seam_gap"
|
||||
};
|
||||
|
||||
const int selection = 0; //m_variant_combo->GetSelection(); // TODO: Orca hack
|
||||
const int selection = m_variant_combo ? m_variant_combo->GetSelection() : 0;
|
||||
auto opt = dynamic_cast<ConfigOptionVectorBase *>(m_config->option("filament_retraction_length"));
|
||||
const int extruder_idx = selection < 0 || selection >= static_cast<int>(opt->size()) ? 0 : selection;
|
||||
|
||||
@@ -4277,9 +4298,11 @@ void TabFilament::toggle_options()
|
||||
|
||||
toggle_line("activate_chamber_temp_control", printer_cfg.opt_bool("support_chamber_temp_control"));
|
||||
|
||||
std::string volumetric_speed_cos = m_config->opt_string("volumetric_speed_coefficients", 0u);
|
||||
const int selection = m_variant_combo ? m_variant_combo->GetSelection() : 0;
|
||||
const unsigned int variant_idx = (unsigned int) std::max(selection, 0);
|
||||
std::string volumetric_speed_cos = m_config->opt_string("volumetric_speed_coefficients", variant_idx);
|
||||
bool enable_fit = volumetric_speed_cos != "0 0 0 0 0 0";
|
||||
toggle_option("filament_adaptive_volumetric_speed", enable_fit, 256 + 0u);
|
||||
toggle_option("filament_adaptive_volumetric_speed", enable_fit, 256 + variant_idx);
|
||||
}
|
||||
|
||||
if (m_active_page->title() == L("Setting Overrides"))
|
||||
@@ -4297,7 +4320,8 @@ void TabFilament::toggle_options()
|
||||
toggle_option("filament_multitool_ramming_flow", multitool_ramming);
|
||||
|
||||
bool is_BBL_multi_extruder = is_BBL_printer && printer_cfg.option<ConfigOptionFloats>("nozzle_diameter")->size() > 1;
|
||||
const int extruder_idx = 0; // m_variant_combo->GetSelection(); // TODO: Orca hack
|
||||
const int selection = m_variant_combo ? m_variant_combo->GetSelection() : 0;
|
||||
const int extruder_idx = std::max(selection, 0);
|
||||
toggle_line("long_retractions_when_ec", is_BBL_multi_extruder, 256 + extruder_idx);
|
||||
toggle_line("retraction_distances_when_ec", is_BBL_multi_extruder && m_config->opt_bool("long_retractions_when_ec", extruder_idx), 256 + extruder_idx);
|
||||
}
|
||||
@@ -6306,6 +6330,9 @@ bool Tab::tree_sel_change_delayed(wxCommandEvent& event)
|
||||
if (m_extruder_switch) {
|
||||
m_main_sizer->Show(m_extruder_switch, !m_active_page->m_opt_id_map.empty());
|
||||
GetParent()->Layout();
|
||||
} else if (m_variant_combo) {
|
||||
m_main_sizer->Show(m_variant_combo, m_variant_combo->IsEnabled() && !m_active_page->m_opt_id_map.empty());
|
||||
GetParent()->Layout();
|
||||
}
|
||||
|
||||
auto throw_if_canceled = std::function<void()>([this](){
|
||||
@@ -6976,6 +7003,41 @@ void Tab::set_just_edit(bool just_edit)
|
||||
/// </summary>
|
||||
/// <param name="extruder_id"></param>
|
||||
|
||||
std::vector<wxString> Tab::generate_extruder_options()
|
||||
{
|
||||
std::vector<wxString> options;
|
||||
if (m_type != Preset::TYPE_FILAMENT)
|
||||
return options;
|
||||
|
||||
auto *variants = m_config->option<ConfigOptionStrings>("filament_extruder_variant");
|
||||
if (!variants)
|
||||
return options;
|
||||
|
||||
const std::vector<std::string> known_nozzle_types = {
|
||||
get_nozzle_volume_type_string(NozzleVolumeType::nvtHighFlow),
|
||||
get_nozzle_volume_type_string(NozzleVolumeType::nvtStandard),
|
||||
};
|
||||
|
||||
for (const std::string &variant : variants->values) {
|
||||
std::string drive;
|
||||
std::string nozzle;
|
||||
|
||||
for (const std::string &nozzle_type : known_nozzle_types) {
|
||||
if (variant.size() > nozzle_type.size() &&
|
||||
variant.substr(variant.size() - nozzle_type.size()) == nozzle_type &&
|
||||
variant[variant.size() - nozzle_type.size() - 1] == ' ') {
|
||||
drive = variant.substr(0, variant.size() - nozzle_type.size() - 1);
|
||||
nozzle = nozzle_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
options.push_back(nozzle.empty() ? from_u8(variant) : wxString::Format(wxT("%s: %s"), from_u8(drive), from_u8(nozzle)));
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
void Tab::update_extruder_variants(int extruder_id)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << extruder_id;
|
||||
@@ -7000,11 +7062,26 @@ void Tab::update_extruder_variants(int extruder_id)
|
||||
GetParent()->Layout();
|
||||
return;
|
||||
}
|
||||
} else if (m_variant_combo) {
|
||||
if (extruder_id >= 0)
|
||||
return;
|
||||
|
||||
const int selection = m_variant_combo->GetSelection();
|
||||
auto options = generate_extruder_options();
|
||||
m_variant_combo->SetOptions(options);
|
||||
|
||||
if (!options.empty())
|
||||
m_variant_combo->SetSelection(selection < 0 || selection >= (int) options.size() ? 0 : selection);
|
||||
|
||||
m_variant_combo->Enable(options.size() > 1);
|
||||
}
|
||||
switch_excluder(extruder_id);
|
||||
if (m_extruder_switch) {
|
||||
m_main_sizer->Show(m_extruder_switch, m_active_page && !m_active_page->m_opt_id_map.empty());
|
||||
GetParent()->Layout();
|
||||
} else if (m_variant_combo) {
|
||||
m_main_sizer->Show(m_variant_combo, m_variant_combo->IsEnabled() && m_active_page && !m_active_page->m_opt_id_map.empty());
|
||||
GetParent()->Layout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7018,7 +7095,7 @@ void Tab::switch_excluder(int extruder_id)
|
||||
{}, {"", "filament_extruder_variant"}, // Preset::TYPE_FILAMENT filament don't use id anymore
|
||||
{}, {"printer_extruder_id", "printer_extruder_variant"}, // Preset::TYPE_PRINTER
|
||||
};
|
||||
if (extruder_id >= nozzle_volumes->size() || extruder_id >= extruders->size())
|
||||
if (!m_variant_combo && (extruder_id >= nozzle_volumes->size() || extruder_id >= extruders->size()))
|
||||
extruder_id = 0;
|
||||
if (m_extruder_switch && m_type != Preset::TYPE_PRINTER) {
|
||||
int current_extruder = m_extruder_switch->GetValue() ? 1 : 0;
|
||||
@@ -7026,15 +7103,25 @@ void Tab::switch_excluder(int extruder_id)
|
||||
extruder_id = current_extruder;
|
||||
else if (extruder_id != current_extruder)
|
||||
return;
|
||||
} else if (m_variant_combo) {
|
||||
int current_variant = m_variant_combo->GetSelection();
|
||||
if (current_variant < 0)
|
||||
current_variant = 0;
|
||||
if (extruder_id == -1)
|
||||
extruder_id = current_variant;
|
||||
else if (extruder_id != current_variant)
|
||||
return;
|
||||
}
|
||||
auto get_index_for_extruder =
|
||||
[this, &extruders, &nozzle_volumes, variant_keys = variant_keys[m_type >= Preset::TYPE_COUNT ? Preset::TYPE_PRINT : m_type]](int extruder_id, int stride = 1) {
|
||||
return m_config->get_index_for_extruder(extruder_id + 1, variant_keys.first,
|
||||
ExtruderType(extruders->values[extruder_id]), NozzleVolumeType(nozzle_volumes->values[extruder_id]), variant_keys.second, stride);
|
||||
};
|
||||
auto index = get_index_for_extruder(extruder_id == -1 ? 0 : extruder_id);
|
||||
auto index = m_variant_combo ? extruder_id : get_index_for_extruder(extruder_id == -1 ? 0 : extruder_id);
|
||||
if (index < 0)
|
||||
return;
|
||||
if (m_variant_combo)
|
||||
m_variant_combo->SetClientData(reinterpret_cast<void *>(static_cast<std::uintptr_t>(index)));
|
||||
for (auto page : m_pages) {
|
||||
bool is_extruder = false;
|
||||
if (m_type == Preset::TYPE_PRINTER) {
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
class TabCtrl;
|
||||
class ModeSwitchButton;
|
||||
class SwitchButton;
|
||||
class MultiSwitchButton;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -305,6 +306,7 @@ public:
|
||||
|
||||
ModeSwitchButton *m_mode_view = nullptr;
|
||||
SwitchButton *m_extruder_switch = nullptr;
|
||||
MultiSwitchButton *m_variant_combo = nullptr;
|
||||
|
||||
public:
|
||||
// BBS
|
||||
@@ -426,6 +428,7 @@ public:
|
||||
|
||||
void update_extruder_variants(int extruder_id = -1);
|
||||
void switch_excluder(int extruder_id = -1);
|
||||
std::vector<wxString> generate_extruder_options();
|
||||
|
||||
protected:
|
||||
void create_line_with_widget(ConfigOptionsGroup* optgroup, const std::string& opt_key, const std::string& path, widget_t widget);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "SwitchButton.hpp"
|
||||
#include "Button.hpp"
|
||||
#include "Label.hpp"
|
||||
#include "StaticBox.hpp"
|
||||
|
||||
@@ -19,7 +20,10 @@
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/dcgraph.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
wxDEFINE_EVENT(wxCUSTOMEVT_SWITCH_POS, wxCommandEvent);
|
||||
wxDEFINE_EVENT(wxCUSTOMEVT_MULTISWITCH_SELECTION, wxCommandEvent);
|
||||
|
||||
SwitchButton::SwitchButton(wxWindow* parent, wxWindowID id)
|
||||
: wxBitmapToggleButton(parent, id, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE | wxBU_EXACTFIT)
|
||||
@@ -391,6 +395,192 @@ void ModeSwitchButton::update_tooltip()
|
||||
SetToolTip(m_tooltips[m_selection]);
|
||||
}
|
||||
|
||||
MultiSwitchButton::MultiSwitchButton(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style)
|
||||
: StaticBox(parent, id, pos, size, style)
|
||||
, m_bg_color(StateColor(
|
||||
std::make_pair(0xE8E8E8, (int) StateColor::NotChecked),
|
||||
std::make_pair(0x009688, (int) StateColor::Normal)))
|
||||
, m_text_color(StateColor(
|
||||
std::make_pair(0x6B6B6B, (int) StateColor::NotChecked),
|
||||
std::make_pair(0xFFFFFE, (int) StateColor::Normal)))
|
||||
, m_button_radius(10.0)
|
||||
, m_button_padding(10, 6)
|
||||
{
|
||||
SetCornerRadius(m_button_radius);
|
||||
SetBorderWidth(0);
|
||||
|
||||
sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto *hsizer = new wxBoxSizer(wxVERTICAL);
|
||||
hsizer->Add(sizer, 1, wxEXPAND);
|
||||
SetSizer(hsizer);
|
||||
SetMinSize(wxSize(-1, 20));
|
||||
|
||||
Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MultiSwitchButton::button_clicked, this);
|
||||
SetFont(Label::Body_12);
|
||||
}
|
||||
|
||||
MultiSwitchButton::~MultiSwitchButton()
|
||||
{
|
||||
DeleteAllOptions();
|
||||
}
|
||||
|
||||
int MultiSwitchButton::AppendOption(const wxString &option, void *clientData)
|
||||
{
|
||||
Button *btn = new Button();
|
||||
btn->Create(this, option, "", wxBORDER_NONE);
|
||||
btn->SetFont(GetFont());
|
||||
btn->SetBackgroundColor(m_bg_color);
|
||||
btn->SetTextColor(m_text_color);
|
||||
btn->SetCornerRadius(m_button_radius);
|
||||
btn->SetPaddingSize(m_button_padding);
|
||||
btn->SetClientData(clientData);
|
||||
|
||||
btns.push_back(btn);
|
||||
sizer->Add(btn, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
wxSize text_size = btn->GetTextExtent(option);
|
||||
btn->SetMinSize(wxSize(text_size.x + m_button_padding.x * 2 + 6, -1));
|
||||
|
||||
return int(btns.size()) - 1;
|
||||
}
|
||||
|
||||
void MultiSwitchButton::SetOptions(const std::vector<wxString> &options)
|
||||
{
|
||||
DeleteAllOptions();
|
||||
for (const auto &option : options)
|
||||
AppendOption(option);
|
||||
|
||||
Layout();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void MultiSwitchButton::DeleteAllOptions()
|
||||
{
|
||||
sel = -1;
|
||||
for (auto *btn : btns) {
|
||||
if (btn)
|
||||
btn->Destroy();
|
||||
}
|
||||
btns.clear();
|
||||
if (sizer)
|
||||
sizer->Clear();
|
||||
}
|
||||
|
||||
unsigned int MultiSwitchButton::GetCount() const
|
||||
{
|
||||
return (unsigned int) btns.size();
|
||||
}
|
||||
|
||||
int MultiSwitchButton::GetSelection() const
|
||||
{
|
||||
return sel;
|
||||
}
|
||||
|
||||
void MultiSwitchButton::SetSelection(int index)
|
||||
{
|
||||
if (index < 0 || index >= (int) btns.size() || index == sel)
|
||||
return;
|
||||
|
||||
sel = index;
|
||||
update_button_styles();
|
||||
send_selection_event();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
wxString MultiSwitchButton::GetSelectedText() const
|
||||
{
|
||||
return sel >= 0 && sel < (int) btns.size() ? btns[sel]->GetLabel() : wxString();
|
||||
}
|
||||
|
||||
wxString MultiSwitchButton::GetOptionText(unsigned int index) const
|
||||
{
|
||||
return index < btns.size() ? btns[index]->GetLabel() : wxString();
|
||||
}
|
||||
|
||||
void MultiSwitchButton::SetOptionText(unsigned int index, const wxString &text)
|
||||
{
|
||||
if (index >= btns.size())
|
||||
return;
|
||||
btns[index]->SetLabel(text);
|
||||
}
|
||||
|
||||
void *MultiSwitchButton::GetOptionData(unsigned int index) const
|
||||
{
|
||||
return index < btns.size() ? btns[index]->GetClientData() : nullptr;
|
||||
}
|
||||
|
||||
void MultiSwitchButton::SetOptionData(unsigned int index, void *clientData)
|
||||
{
|
||||
if (index >= btns.size())
|
||||
return;
|
||||
btns[index]->SetClientData(clientData);
|
||||
}
|
||||
|
||||
void MultiSwitchButton::update_button_styles()
|
||||
{
|
||||
for (int i = 0; i < (int) btns.size(); ++i) {
|
||||
btns[i]->SetValue(i == sel);
|
||||
btns[i]->SetBackgroundColor(m_bg_color);
|
||||
btns[i]->SetTextColor(m_text_color);
|
||||
btns[i]->Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void MultiSwitchButton::SetBackgroundColor(const StateColor &color)
|
||||
{
|
||||
m_bg_color = color;
|
||||
update_button_styles();
|
||||
}
|
||||
|
||||
void MultiSwitchButton::SetTextColor(const StateColor &color)
|
||||
{
|
||||
m_text_color = color;
|
||||
update_button_styles();
|
||||
}
|
||||
|
||||
void MultiSwitchButton::SetButtonCornerRadius(double radius)
|
||||
{
|
||||
m_button_radius = radius;
|
||||
SetCornerRadius(radius);
|
||||
for (auto *btn : btns)
|
||||
btn->SetCornerRadius(radius);
|
||||
Layout();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void MultiSwitchButton::SetButtonPadding(const wxSize &padding)
|
||||
{
|
||||
m_button_padding = padding;
|
||||
for (auto *btn : btns)
|
||||
btn->SetPaddingSize(padding);
|
||||
Layout();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void MultiSwitchButton::Rescale()
|
||||
{
|
||||
for (auto *btn : btns)
|
||||
btn->Rescale();
|
||||
}
|
||||
|
||||
void MultiSwitchButton::button_clicked(wxCommandEvent &event)
|
||||
{
|
||||
SetFocus();
|
||||
auto *btn = event.GetEventObject();
|
||||
auto iter = std::find(btns.begin(), btns.end(), btn);
|
||||
SetSelection(iter == btns.end() ? -1 : int(iter - btns.begin()));
|
||||
}
|
||||
|
||||
bool MultiSwitchButton::send_selection_event()
|
||||
{
|
||||
wxCommandEvent evt(wxCUSTOMEVT_MULTISWITCH_SELECTION, GetId());
|
||||
evt.SetEventObject(this);
|
||||
evt.SetInt(sel);
|
||||
evt.SetString(GetSelectedText());
|
||||
GetEventHandler()->ProcessEvent(evt);
|
||||
return true;
|
||||
}
|
||||
|
||||
SwitchBoard::SwitchBoard(wxWindow *parent, wxString leftL, wxString right, wxSize size)
|
||||
: wxWindow(parent, wxID_ANY, wxDefaultPosition, size)
|
||||
{
|
||||
@@ -554,4 +744,4 @@ bool SwitchBoard::Enable(bool enable /* = true */)
|
||||
is_enable = enable;
|
||||
Refresh();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,14 @@
|
||||
#include "StateColor.hpp"
|
||||
#include "StaticBox.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/tglbtn.h>
|
||||
|
||||
wxDECLARE_EVENT(wxCUSTOMEVT_SWITCH_POS, wxCommandEvent);
|
||||
wxDECLARE_EVENT(wxCUSTOMEVT_MULTISWITCH_SELECTION, wxCommandEvent);
|
||||
|
||||
class Button;
|
||||
|
||||
class SwitchButton : public wxBitmapToggleButton
|
||||
{
|
||||
@@ -76,6 +81,53 @@ private:
|
||||
wxString m_tooltips[3];
|
||||
};
|
||||
|
||||
class MultiSwitchButton : public StaticBox
|
||||
{
|
||||
public:
|
||||
MultiSwitchButton(wxWindow *parent = nullptr, wxWindowID id = wxID_ANY, const wxPoint &pos = wxDefaultPosition,
|
||||
const wxSize &size = wxDefaultSize, long style = 0);
|
||||
~MultiSwitchButton();
|
||||
|
||||
int AppendOption(const wxString &option, void *clientData = nullptr);
|
||||
void SetOptions(const std::vector<wxString> &options);
|
||||
void DeleteAllOptions();
|
||||
|
||||
unsigned int GetCount() const;
|
||||
|
||||
int GetSelection() const;
|
||||
void SetSelection(int index);
|
||||
wxString GetSelectedText() const;
|
||||
|
||||
wxString GetOptionText(unsigned int index) const;
|
||||
void SetOptionText(unsigned int index, const wxString &text);
|
||||
|
||||
void *GetOptionData(unsigned int index) const;
|
||||
void SetOptionData(unsigned int index, void *clientData);
|
||||
|
||||
void SetBackgroundColor(const StateColor &color);
|
||||
void SetTextColor(const StateColor &color);
|
||||
void SetButtonCornerRadius(double radius);
|
||||
void SetButtonPadding(const wxSize &padding);
|
||||
|
||||
void Rescale();
|
||||
|
||||
protected:
|
||||
void button_clicked(wxCommandEvent &event);
|
||||
void update_button_styles();
|
||||
|
||||
bool send_selection_event();
|
||||
|
||||
private:
|
||||
std::vector<Button *> btns;
|
||||
wxBoxSizer *sizer = nullptr;
|
||||
int sel = -1;
|
||||
|
||||
StateColor m_bg_color;
|
||||
StateColor m_text_color;
|
||||
double m_button_radius;
|
||||
wxSize m_button_padding;
|
||||
};
|
||||
|
||||
class SwitchBoard : public wxWindow
|
||||
{
|
||||
public:
|
||||
|
||||
Reference in New Issue
Block a user