From 337d987ec836611853c73bf8962f2cf84700a4a0 Mon Sep 17 00:00:00 2001 From: "zhimin.zeng" Date: Wed, 23 Apr 2025 14:16:34 +0800 Subject: [PATCH] ENH: support new auto cali method jira: STUDIO-10798 Change-Id: I9490b050e93cd556e1d34b1e69e0508eaecec2cd (cherry picked from commit 7a8b34525ef77d49b6549ecb290e2b1f89c69419) --- .../pressure_advance/auto_pa_line_dual.3mf | Bin 0 -> 29649 bytes .../pressure_advance/auto_pa_line_single.3mf | Bin 0 -> 28096 bytes src/libslic3r/PresetBundle.cpp | 177 ++++++++ src/libslic3r/PresetBundle.hpp | 6 + src/libslic3r/calib.hpp | 1 + src/slic3r/GUI/CalibrationWizard.cpp | 76 +++- src/slic3r/GUI/CalibrationWizardCaliPage.cpp | 8 +- src/slic3r/GUI/CalibrationWizardPage.cpp | 10 +- src/slic3r/GUI/CalibrationWizardPage.hpp | 5 +- .../GUI/CalibrationWizardPresetPage.cpp | 19 +- .../GUI/CalibrationWizardPresetPage.hpp | 4 + src/slic3r/GUI/CalibrationWizardSavePage.cpp | 12 +- src/slic3r/Utils/CalibUtils.cpp | 399 +++++++++++++++++- src/slic3r/Utils/CalibUtils.hpp | 8 + 14 files changed, 705 insertions(+), 20 deletions(-) create mode 100644 resources/calib/pressure_advance/auto_pa_line_dual.3mf create mode 100644 resources/calib/pressure_advance/auto_pa_line_single.3mf diff --git a/resources/calib/pressure_advance/auto_pa_line_dual.3mf b/resources/calib/pressure_advance/auto_pa_line_dual.3mf new file mode 100644 index 0000000000000000000000000000000000000000..e10604c96359dda690eb407d60a8d65838b463f8 GIT binary patch literal 29649 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 in_filament_presets, + bool apply_extruder, + std::optional> filament_maps_new) +{ + DynamicPrintConfig &printer_config = in_printer_preset.config; + DynamicPrintConfig &print_config = in_print_preset.config; + + DynamicPrintConfig out; + out.apply(FullPrintConfig::defaults()); + out.apply(printer_config); + out.apply(print_config); + out.apply(project_config); + out.apply(in_filament_presets[0].config); + + size_t num_filaments = in_filament_presets.size(); + + std::vector filament_maps = out.option("filament_map")->values; + if (filament_maps_new.has_value()) + filament_maps = *filament_maps_new; + // in some middle state, they may be different + if (filament_maps.size() != num_filaments) { + filament_maps.resize(num_filaments, 1); + } + + auto *extruder_diameter = dynamic_cast(out.option("nozzle_diameter")); + // Collect the "compatible_printers_condition" and "inherits" values over all presets (print, filaments, printers) into a single vector. + std::vector compatible_printers_condition; + std::vector compatible_prints_condition; + std::vector inherits; + std::vector filament_ids; + std::vector print_compatible_printers; + // BBS: add logic for settings check between different system presets + std::vector different_settings; + std::string different_print_settings, different_printer_settings; + compatible_printers_condition.emplace_back(in_print_preset.compatible_printers_condition()); + + const ConfigOptionStrings *compatible_printers = print_config.option("compatible_printers", false); + if (compatible_printers) print_compatible_printers = compatible_printers->values; + // BBS: add logic for settings check between different system presets + std::string print_inherits = in_print_preset.inherits(); + inherits.emplace_back(print_inherits); + + // BBS: update printer config related with variants + if (apply_extruder) { + out.update_values_to_printer_extruders(out, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant"); + out.update_values_to_printer_extruders(out, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2); + // update print config related with variants + out.update_values_to_printer_extruders(out, print_options_with_variant, "print_extruder_id", "print_extruder_variant"); + } + + if (num_filaments <= 1) { + // BBS: update filament config related with variants + DynamicPrintConfig filament_config = in_filament_presets[0].config; + if (apply_extruder) filament_config.update_values_to_printer_extruders(out, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0]); + out.apply(filament_config); + compatible_printers_condition.emplace_back(in_filament_presets[0].compatible_printers_condition()); + compatible_prints_condition.emplace_back(in_filament_presets[0].compatible_prints_condition()); + std::string filament_inherits = in_filament_presets[0].inherits(); + inherits.emplace_back(filament_inherits); + filament_ids.emplace_back(in_filament_presets[0].filament_id); + + std::vector &filament_self_indice = out.option("filament_self_index", true)->values; + int index_size = out.option("filament_extruder_variant")->size(); + filament_self_indice.resize(index_size, 1); + } else { + std::vector filament_configs; + std::vector filament_presets; + for (const Preset & preset : in_filament_presets) { + filament_presets.emplace_back(&preset); + filament_configs.emplace_back(&(preset.config)); + } + + std::vector filament_temp_configs; + filament_temp_configs.resize(num_filaments); + for (size_t i = 0; i < num_filaments; ++i) { + filament_temp_configs[i] = *(filament_configs[i]); + if (apply_extruder) + filament_temp_configs[i].update_values_to_printer_extruders(out, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i]); + } + + // loop through options and apply them to the resulting config. + std::vector filament_variant_count(num_filaments, 1); + for (const t_config_option_key &key : in_filament_presets[0].config.keys()) { + if (key == "compatible_prints" || key == "compatible_printers") continue; + // Get a destination option. + ConfigOption *opt_dst = out.option(key, false); + if (opt_dst->is_scalar()) { + // Get an option, do not create if it does not exist. + const ConfigOption *opt_src = filament_temp_configs.front().option(key); + if (opt_src != nullptr) opt_dst->set(opt_src); + } else { + // BBS + ConfigOptionVectorBase *opt_vec_dst = static_cast(opt_dst); + { + if (apply_extruder) { + std::vector filament_opts(num_filaments, nullptr); + // Setting a vector value from all filament_configs. + for (size_t i = 0; i < filament_opts.size(); ++i) filament_opts[i] = filament_temp_configs[i].option(key); + opt_vec_dst->set(filament_opts); + } else { + for (size_t i = 0; i < num_filaments; ++i) { + const ConfigOptionVectorBase *filament_option = static_cast(filament_temp_configs[i].option(key)); + if (i == 0) + opt_vec_dst->set(filament_option); + else + opt_vec_dst->append(filament_option); + + if (key == "filament_extruder_variant") filament_variant_count[i] = filament_option->size(); + } + } + } + } + } + + if (!apply_extruder) { + // append filament_self_index + std::vector &filament_self_indice = out.option("filament_self_index", true)->values; + int index_size = out.option("filament_extruder_variant")->size(); + filament_self_indice.resize(index_size, 1); + int k = 0; + for (size_t i = 0; i < num_filaments; i++) { + for (size_t j = 0; j < filament_variant_count[i]; j++) { filament_self_indice[k++] = i + 1; } + } + } + } + + // These value types clash between the print and filament profiles. They should be renamed. + out.erase("compatible_prints"); + out.erase("compatible_prints_condition"); + out.erase("compatible_printers"); + out.erase("compatible_printers_condition"); + out.erase("inherits"); + // BBS: add logic for settings check between different system presets + out.erase("different_settings_to_system"); + + static const char *keys[] = {"support_filament", "support_interface_filament"}; + for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) { + std::string key = std::string(keys[i]); + auto *opt = dynamic_cast(out.option(key, false)); + assert(opt != nullptr); + opt->value = boost::algorithm::clamp(opt->value, 0, int(num_filaments)); + } + + std::vector filamnet_preset_names; + for (auto preset : in_filament_presets) { + filamnet_preset_names.emplace_back(preset.name); + } + out.option("print_settings_id", true)->value = in_print_preset.name; + out.option("filament_settings_id", true)->values = filamnet_preset_names; + out.option("printer_settings_id", true)->value = in_printer_preset.name; + out.option("filament_ids", true)->values = filament_ids; + out.option("filament_map", true)->values = filament_maps; + + auto add_if_some_non_empty = [&out](std::vector &&values, const std::string &key) { + bool nonempty = false; + for (const std::string &v : values) + if (!v.empty()) { + nonempty = true; + break; + } + if (nonempty) out.set_key_value(key, new ConfigOptionStrings(std::move(values))); + }; + add_if_some_non_empty(std::move(compatible_printers_condition), "compatible_machine_expression_group"); + add_if_some_non_empty(std::move(compatible_prints_condition), "compatible_process_expression_group"); + add_if_some_non_empty(std::move(inherits), "inherits_group"); + // BBS: add logic for settings check between different system presets + //add_if_some_non_empty(std::move(different_settings), "different_settings_to_system"); + add_if_some_non_empty(std::move(print_compatible_printers), "print_compatible_printers"); + + out.option("printer_technology", true)->value = ptFFF; + return out; +} + PresetBundle::PresetBundle() : prints(Preset::TYPE_PRINT, Preset::print_options(), static_cast(FullPrintConfig::defaults())) , filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast(FullPrintConfig::defaults()), "Default Filament") diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 00651b4cd6..09b34e7f58 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -75,6 +75,12 @@ struct FilamentBaseInfo class PresetBundle { public: + static DynamicPrintConfig construct_full_config(Preset &in_printer_preset, + Preset &in_print_preset, + const DynamicPrintConfig &project_config, + std::vector &in_filament_presets, + bool apply_extruder, + std::optional> filament_maps_new); PresetBundle(); PresetBundle(const PresetBundle &rhs); PresetBundle& operator=(const PresetBundle &rhs); diff --git a/src/libslic3r/calib.hpp b/src/libslic3r/calib.hpp index 445162ded0..1f60314a71 100644 --- a/src/libslic3r/calib.hpp +++ b/src/libslic3r/calib.hpp @@ -18,6 +18,7 @@ enum class CalibMode : int { Calib_PA_Line, Calib_PA_Pattern, Calib_PA_Tower, + Calib_Auto_PA_Line, Calib_Flow_Rate, Calib_Temp_Tower, Calib_Vol_speed_Tower, diff --git a/src/slic3r/GUI/CalibrationWizard.cpp b/src/slic3r/GUI/CalibrationWizard.cpp index ee0060a7d5..7200479f9c 100644 --- a/src/slic3r/GUI/CalibrationWizard.cpp +++ b/src/slic3r/GUI/CalibrationWizard.cpp @@ -563,9 +563,14 @@ void PressureAdvanceWizard::on_cali_action(wxCommandEvent& evt) show_step(m_curr_step->next); } else if (action == CaliPageActionType::CALI_ACTION_AUTO_CALI) { + if (curr_obj && curr_obj->is_support_new_auto_cali_method) { + set_cali_method(CalibrationMethod::CALI_METHOD_NEW_AUTO); + } + else { + set_cali_method(CalibrationMethod::CALI_METHOD_AUTO); + } CalibrationFilamentMode fila_mode = get_cali_filament_mode(curr_obj, m_mode); preset_step->page->set_cali_filament_mode(fila_mode); - set_cali_method(CalibrationMethod::CALI_METHOD_AUTO); preset_step->page->on_device_connected(curr_obj); show_step(m_curr_step->next); } @@ -756,7 +761,7 @@ void PressureAdvanceWizard::on_cali_start() calib_info.extruder_id = preset_page->get_extruder_id(calib_info.ams_id); calib_info.extruder_type = preset_page->get_extruder_type(calib_info.extruder_id); calib_info.nozzle_volume_type = preset_page->get_nozzle_volume_type(calib_info.extruder_id); - calib_info.select_ams = "[" + std::to_string(selected_tray_id) + "]"; + calib_info.select_ams = std::to_string(selected_tray_id); Preset *preset = selected_filaments.begin()->second; Preset * temp_filament_preset = new Preset(preset->type, preset->name + "_temp"); temp_filament_preset->config = preset->config; @@ -769,6 +774,9 @@ void PressureAdvanceWizard::on_cali_start() calib_info.print_prest = preset_page->get_print_preset(); calib_info.filament_prest = temp_filament_preset; + std::map filament_list = preset_page->get_filament_ams_list(); + calib_info.filament_color = filament_list[selected_tray_id].opt_string("filament_colour", 0u); + wxArrayString values = preset_page->get_custom_range_values(); if (values.size() != 3) { MessageDialog msg_dlg(nullptr, _L("The input value size must be 3."), wxEmptyString, wxICON_WARNING | wxOK); @@ -815,6 +823,57 @@ void PressureAdvanceWizard::on_cali_start() preset_page->on_cali_start_job(); } + } else if (m_cali_method == CalibrationMethod::CALI_METHOD_NEW_AUTO) { + if (selected_filaments.empty()) { + BOOST_LOG_TRIVIAL(warning) << "CaliPreset: selected filaments is empty"; + return; + } + + std::vector calib_infos; + for (auto &item : selected_filaments) { + int nozzle_temp = -1; + int bed_temp = -1; + float max_volumetric_speed = -1; + + if (!get_preset_info(item.second->config, plate_type, nozzle_temp, bed_temp, max_volumetric_speed)) { + BOOST_LOG_TRIVIAL(error) << "CaliPreset: get preset info error"; + continue; + } + + int selected_tray_id = 0; + CalibInfo calib_info; + calib_info.dev_id = curr_obj->dev_id; + get_tray_ams_and_slot_id(curr_obj, item.first, calib_info.ams_id, calib_info.slot_id, selected_tray_id); + calib_info.index = preset_page->get_index_by_tray_id(selected_tray_id); + calib_info.extruder_id = preset_page->get_extruder_id(calib_info.ams_id); + calib_info.nozzle_diameter = preset_page->get_nozzle_diameter(calib_info.extruder_id); + calib_info.extruder_type = preset_page->get_extruder_type(calib_info.extruder_id); + calib_info.nozzle_volume_type = preset_page->get_nozzle_volume_type(calib_info.extruder_id); + calib_info.select_ams = std::to_string(selected_tray_id); + Preset *preset = item.second; + Preset *temp_filament_preset = new Preset(preset->type, preset->name + "_temp"); + temp_filament_preset->config = preset->config; + + calib_info.bed_type = plate_type; + calib_info.process_bar = preset_page->get_sending_progress_bar(); + calib_info.printer_prest = preset_page->get_printer_preset(curr_obj, preset_page->get_nozzle_diameter(calib_info.extruder_id)); + calib_info.print_prest = preset_page->get_print_preset(); + calib_info.filament_prest = temp_filament_preset; + std::map filament_list = preset_page->get_filament_ams_list(); + calib_info.filament_color = filament_list[selected_tray_id].opt_string("filament_colour", 0u); + calib_info.params.mode = CalibMode::Calib_Auto_PA_Line; + calib_infos.emplace_back(calib_info); + } + + if (!CalibUtils::calib_generic_auto_pa_cali(calib_infos, wx_err_string)) { + if (!wx_err_string.empty()) { + MessageDialog msg_dlg(nullptr, wx_err_string, wxEmptyString, wxICON_WARNING | wxOK); + msg_dlg.ShowModal(); + } + return; + } + + preset_page->on_cali_start_job(); } else { assert(false); BOOST_LOG_TRIVIAL(error) << "CaliPreset: unsupported printer type or cali method"; @@ -902,7 +961,7 @@ void PressureAdvanceWizard::on_cali_save() } if (curr_obj->get_printer_series() == PrinterSeries::SERIES_X1) { - if (m_cali_method == CalibrationMethod::CALI_METHOD_AUTO) { + if (m_cali_method == CalibrationMethod::CALI_METHOD_AUTO || m_cali_method == CalibrationMethod::CALI_METHOD_NEW_AUTO) { std::vector new_pa_cali_results; auto save_page = static_cast(save_step->page); if (!save_page->get_auto_result(new_pa_cali_results)) { @@ -1228,7 +1287,7 @@ void FlowRateWizard::on_cali_start(CaliPresetStage stage, float cali_value, Flow if (!selected_filaments.empty()) { int selected_tray_id = 0; get_tray_ams_and_slot_id(curr_obj, selected_filaments.begin()->first, calib_info.ams_id, calib_info.slot_id, selected_tray_id); - calib_info.select_ams = "[" + std::to_string(selected_tray_id) + "]"; + calib_info.select_ams = std::to_string(selected_tray_id); calib_info.extruder_id = preset_page->get_extruder_id(calib_info.ams_id); calib_info.extruder_type = preset_page->get_extruder_type(calib_info.extruder_id); calib_info.nozzle_volume_type = preset_page->get_nozzle_volume_type(calib_info.extruder_id); @@ -1265,6 +1324,9 @@ void FlowRateWizard::on_cali_start(CaliPresetStage stage, float cali_value, Flow } calib_info.filament_prest = temp_filament_preset; + std::map filament_list = preset_page->get_filament_ams_list(); + calib_info.filament_color = filament_list[selected_tray_id].opt_string("filament_colour", 0u); + if (cali_stage > 0) { if (!CalibUtils::calib_flowrate(cali_stage, calib_info, wx_err_string)) { if (!wx_err_string.empty()) { @@ -1464,7 +1526,7 @@ std::map FlowRateWizard::generate_index_key_value void FlowRateWizard::set_cali_method(CalibrationMethod method) { m_cali_method = method; - if (method == CalibrationMethod::CALI_METHOD_AUTO) { + if (method == CalibrationMethod::CALI_METHOD_AUTO || method == CalibrationMethod::CALI_METHOD_NEW_AUTO) { m_page_steps.clear(); m_page_steps.push_back(start_step); m_page_steps.push_back(preset_step); @@ -1639,11 +1701,13 @@ void MaxVolumetricSpeedWizard::on_cali_start() if (!selected_filaments.empty()) { int selected_tray_id = 0; get_tray_ams_and_slot_id(curr_obj, selected_filaments.begin()->first, calib_info.ams_id, calib_info.slot_id, selected_tray_id); - calib_info.select_ams = "[" + std::to_string(selected_tray_id) + "]"; + calib_info.select_ams = std::to_string(selected_tray_id); calib_info.extruder_id = preset_page->get_extruder_id(calib_info.ams_id); calib_info.extruder_type = preset_page->get_extruder_type(calib_info.extruder_id); calib_info.nozzle_volume_type = preset_page->get_nozzle_volume_type(calib_info.extruder_id); calib_info.filament_prest = selected_filaments.begin()->second; + std::map filament_list = preset_page->get_filament_ams_list(); + calib_info.filament_color = filament_list[selected_tray_id].opt_string("filament_colour", 0u); } calib_info.bed_type = plate_type; diff --git a/src/slic3r/GUI/CalibrationWizardCaliPage.cpp b/src/slic3r/GUI/CalibrationWizardCaliPage.cpp index 39f8267832..722268162f 100644 --- a/src/slic3r/GUI/CalibrationWizardCaliPage.cpp +++ b/src/slic3r/GUI/CalibrationWizardCaliPage.cpp @@ -107,7 +107,7 @@ void CalibrationCaliPage::set_cali_img() CalibMode obj_cali_mode = get_obj_calibration_mode(curr_obj, method, cali_stage); set_pa_cali_image(cali_stage); } - else if (m_cali_method == CalibrationMethod::CALI_METHOD_AUTO) { + else if (m_cali_method == CalibrationMethod::CALI_METHOD_AUTO || m_cali_method == CalibrationMethod::CALI_METHOD_NEW_AUTO) { if (curr_obj) { if (curr_obj->is_multi_extruders()) { if (m_cur_extruder_id == 0) { @@ -213,8 +213,8 @@ void CalibrationCaliPage::update(MachineObject* obj) return; } - if (m_cali_mode == CalibMode::Calib_PA_Line) { - if (m_cali_method == CalibrationMethod::CALI_METHOD_AUTO) { + if (m_cali_mode == CalibMode::Calib_PA_Line || m_cali_mode == CalibMode::Calib_Auto_PA_Line) { + if (m_cali_method == CalibrationMethod::CALI_METHOD_AUTO || m_cali_method == CalibrationMethod::CALI_METHOD_NEW_AUTO) { if (get_obj_calibration_mode(obj) == m_cali_mode) { if (obj->is_printing_finished()) { if (obj->print_status == "FINISH") { @@ -479,7 +479,7 @@ void CalibrationCaliPage::set_cali_method(CalibrationMethod method) manual_steps.Add(_L("Calibration2")); manual_steps.Add(_L("Record Factor")); - if (method == CalibrationMethod::CALI_METHOD_AUTO) { + if (method == CalibrationMethod::CALI_METHOD_AUTO || method == CalibrationMethod::CALI_METHOD_NEW_AUTO) { m_step_panel->set_steps_string(auto_steps); m_step_panel->set_steps(1); } diff --git a/src/slic3r/GUI/CalibrationWizardPage.cpp b/src/slic3r/GUI/CalibrationWizardPage.cpp index fc5b8699b3..3da543e79d 100644 --- a/src/slic3r/GUI/CalibrationWizardPage.cpp +++ b/src/slic3r/GUI/CalibrationWizardPage.cpp @@ -137,7 +137,10 @@ CalibMode get_obj_calibration_mode(const MachineObject* obj, CalibrationMethod& } CalibMode cali_mode = CalibUtils::get_calib_mode_by_name(obj->subtask_name, cali_stage); - if (cali_mode != CalibMode::Calib_None) { + if (cali_mode == CalibMode::Calib_PA_Line && cali_stage == 2) { + method = CalibrationMethod::CALI_METHOD_NEW_AUTO; + } + else if (cali_mode != CalibMode::Calib_None) { method = CalibrationMethod::CALI_METHOD_MANUAL; } return cali_mode; @@ -264,8 +267,9 @@ void CaliPageButton::msw_rescale() } -FilamentComboBox::FilamentComboBox(wxWindow* parent, const wxPoint& pos, const wxSize& size) +FilamentComboBox::FilamentComboBox(wxWindow* parent, int index, const wxPoint& pos, const wxSize& size) : wxPanel(parent, wxID_ANY, pos, size, wxTAB_TRAVERSAL) + , m_index(index) { SetBackgroundColour(*wxWHITE); @@ -571,7 +575,7 @@ void CaliPageStepGuide::set_steps_string(wxArrayString steps) CaliPagePicture::CaliPagePicture(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style) : wxPanel(parent, id, pos, size, style) { - SetBackgroundColour(wxColour(0xCECECE)); + SetBackgroundColour(wxColour("#CECECE")); auto top_sizer = new wxBoxSizer(wxHORIZONTAL); top_sizer->AddStretchSpacer(); m_img = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap); diff --git a/src/slic3r/GUI/CalibrationWizardPage.hpp b/src/slic3r/GUI/CalibrationWizardPage.hpp index 1272537d22..d03979cd55 100644 --- a/src/slic3r/GUI/CalibrationWizardPage.hpp +++ b/src/slic3r/GUI/CalibrationWizardPage.hpp @@ -49,6 +49,7 @@ enum CalibrationFilamentMode { enum CalibrationMethod { CALI_METHOD_MANUAL = 0, CALI_METHOD_AUTO, + CALI_METHOD_NEW_AUTO, CALI_METHOD_NONE, }; @@ -78,13 +79,14 @@ enum class CaliPageType { class FilamentComboBox : public wxPanel { public: - FilamentComboBox(wxWindow* parent, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize); + FilamentComboBox(wxWindow* parent, int index, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize); ~FilamentComboBox() {}; void set_select_mode(CalibrationFilamentMode mode); CalibrationFilamentMode get_select_mode() { return m_mode; } void load_tray_from_ams(int id, DynamicPrintConfig& tray); void update_from_preset(); + int get_index() { return m_index; } int get_tray_id() { return m_tray_id; } bool is_bbl_filament() { return m_is_bbl_filamnet; } std::string get_tray_name() { return m_tray_name; } @@ -102,6 +104,7 @@ public: void HidePanel(); protected: + int m_index{0}; int m_tray_id { -1 }; std::string m_tray_name; bool m_is_bbl_filamnet{ false }; diff --git a/src/slic3r/GUI/CalibrationWizardPresetPage.cpp b/src/slic3r/GUI/CalibrationWizardPresetPage.cpp index caa7a85048..40b483ef89 100644 --- a/src/slic3r/GUI/CalibrationWizardPresetPage.cpp +++ b/src/slic3r/GUI/CalibrationWizardPresetPage.cpp @@ -889,7 +889,7 @@ void CalibrationPresetPage::create_filament_list_panel(wxWindow* parent) wxRadioButton* radio_btn = new wxRadioButton(m_filament_list_panel, wxID_ANY, ""); CheckBox* check_box = new CheckBox(m_filament_list_panel); check_box->SetBackgroundColour(*wxWHITE); - FilamentComboBox* fcb = new FilamentComboBox(m_filament_list_panel); + FilamentComboBox* fcb = new FilamentComboBox(m_filament_list_panel, i); fcb->SetRadioBox(radio_btn); fcb->SetCheckBox(check_box); fcb->set_select_mode(CalibrationFilamentMode::CALI_MODEL_SINGLE); @@ -1022,7 +1022,7 @@ void CalibrationPresetPage::create_multi_extruder_filament_list_panel(wxWindow * wxRadioButton *radio_btn = new wxRadioButton(m_multi_exutrder_filament_list_panel, wxID_ANY, ""); CheckBox * check_box = new CheckBox(m_multi_exutrder_filament_list_panel); check_box->SetBackgroundColour(*wxWHITE); - FilamentComboBox *fcb = new FilamentComboBox(m_multi_exutrder_filament_list_panel); + FilamentComboBox *fcb = new FilamentComboBox(m_multi_exutrder_filament_list_panel, i + 4); fcb->SetRadioBox(radio_btn); fcb->SetCheckBox(check_box); fcb->set_select_mode(CalibrationFilamentMode::CALI_MODEL_SINGLE); @@ -1066,7 +1066,7 @@ void CalibrationPresetPage::create_multi_extruder_filament_list_panel(wxWindow * wxRadioButton *radio_btn = new wxRadioButton(m_multi_exutrder_filament_list_panel, wxID_ANY, ""); CheckBox * check_box = new CheckBox(m_multi_exutrder_filament_list_panel); check_box->SetBackgroundColour(*wxWHITE); - FilamentComboBox *fcb = new FilamentComboBox(m_multi_exutrder_filament_list_panel); + FilamentComboBox *fcb = new FilamentComboBox(m_multi_exutrder_filament_list_panel, i); fcb->SetRadioBox(radio_btn); fcb->SetCheckBox(check_box); fcb->set_select_mode(CalibrationFilamentMode::CALI_MODEL_SINGLE); @@ -1448,7 +1448,7 @@ bool CalibrationPresetPage::is_filament_in_blacklist(int tray_id, Preset* preset } } if (devPrinterUtil::IsVirtualSlot(ams_id)) { - if (m_cali_mode == CalibMode::Calib_PA_Line && m_cali_method == CalibrationMethod::CALI_METHOD_AUTO) { + if (m_cali_mode == CalibMode::Calib_PA_Line && (m_cali_method == CalibrationMethod::CALI_METHOD_AUTO || m_cali_method == CalibrationMethod::CALI_METHOD_NEW_AUTO)) { std::string filamnt_type; preset->get_filament_type(filamnt_type); if (filamnt_type == "TPU") { @@ -2362,6 +2362,17 @@ void CalibrationPresetPage::select_default_compatible_filament() check_filament_compatible(); } +int CalibrationPresetPage::get_index_by_tray_id(int tray_id) +{ + std::vector fcb_list = get_selected_filament_combobox(); + for (auto fcb : fcb_list) { + if (fcb->get_tray_id() == tray_id) { + return fcb->get_index(); + } + } + return -1; +} + std::vector CalibrationPresetPage::get_selected_filament_combobox() { std::vector fcb_list; diff --git a/src/slic3r/GUI/CalibrationWizardPresetPage.hpp b/src/slic3r/GUI/CalibrationWizardPresetPage.hpp index fa3024f94d..fa23f9aee0 100644 --- a/src/slic3r/GUI/CalibrationWizardPresetPage.hpp +++ b/src/slic3r/GUI/CalibrationWizardPresetPage.hpp @@ -195,11 +195,15 @@ public: void select_default_compatible_filament(); + int get_index_by_tray_id(int tray_id); + std::vector get_selected_filament_combobox(); // key is tray_id std::map get_selected_filaments(); + std::map get_filament_ams_list() const { return filament_ams_list; } + void get_preset_info( float& nozzle_dia, BedType& plate_type); diff --git a/src/slic3r/GUI/CalibrationWizardSavePage.cpp b/src/slic3r/GUI/CalibrationWizardSavePage.cpp index cb00e9ab26..2133a7b63a 100644 --- a/src/slic3r/GUI/CalibrationWizardSavePage.cpp +++ b/src/slic3r/GUI/CalibrationWizardSavePage.cpp @@ -609,6 +609,16 @@ void CaliPASaveAutoPanel::sync_cali_result_for_multi_extruder(const std::vector< bool left_first_add_item = true; bool right_first_add_item = true; std::vector sorted_cali_result = cali_result; + if (m_obj && m_obj->is_support_new_auto_cali_method) { + for (auto &res : sorted_cali_result) { + if (res.ams_id == VIRTUAL_TRAY_MAIN_ID || res.ams_id == VIRTUAL_TRAY_DEPUTY_ID) { + res.tray_id = res.ams_id; + } else { + res.tray_id = res.ams_id * 4 + res.slot_id; + } + } + } + std::sort(sorted_cali_result.begin(), sorted_cali_result.end(), [](const PACalibResult &left, const PACalibResult &right) { return left.tray_id < right.tray_id; }); @@ -1206,7 +1216,7 @@ void CalibrationPASavePage::create_page(wxWindow* parent) void CalibrationPASavePage::sync_cali_result(MachineObject* obj) { // only auto need sync cali_result - if (obj && m_cali_method == CalibrationMethod::CALI_METHOD_AUTO) { + if (obj && (m_cali_method == CalibrationMethod::CALI_METHOD_AUTO || m_cali_method == CalibrationMethod::CALI_METHOD_NEW_AUTO)) { m_auto_panel->sync_cali_result(obj->pa_calib_results, obj->pa_calib_tab); } else { std::vector empty_result; diff --git a/src/slic3r/Utils/CalibUtils.cpp b/src/slic3r/Utils/CalibUtils.cpp index 83a7769722..4081d7b3a2 100644 --- a/src/slic3r/Utils/CalibUtils.cpp +++ b/src/slic3r/Utils/CalibUtils.cpp @@ -11,6 +11,7 @@ #include "slic3r/GUI/Jobs/BoostThreadWorker.hpp" #include "slic3r/GUI/Jobs/PlaterWorker.hpp" #include "../GUI/MsgDialog.hpp" +#include "libslic3r/FlushVolCalc.hpp" #include "../GUI/DeviceCore/DevConfig.h" #include "../GUI/DeviceCore/DevExtruderSystem.h" @@ -118,6 +119,8 @@ std::string get_calib_mode_name(CalibMode cali_mode, int stage) switch(cali_mode) { case CalibMode::Calib_PA_Line: return "pa_line_calib_mode"; + case CalibMode::Calib_Auto_PA_Line: + return "auto_pa_line_calib_mode"; case CalibMode::Calib_PA_Pattern: return "pa_pattern_calib_mode"; case CalibMode::Calib_Flow_Rate: @@ -283,6 +286,7 @@ static void init_multi_extruder_params_for_cali(DynamicPrintConfig& config, cons for (size_t index = 0; index < extruder_count; ++index) { if (physical_extruder_maps[index] == extruder_id) { + nozzle_volume_types[index] = (int) calib_info.nozzle_volume_type; extruder_id = index + 1; } } @@ -311,6 +315,10 @@ CalibMode CalibUtils::get_calib_mode_by_name(const std::string name, int& cali_s cali_stage = 1; return CalibMode::Calib_PA_Line; } + else if (name == "auto_pa_line_calib_mode") { + cali_stage = 2; + return CalibMode::Calib_PA_Line; + } else if (name == "flow_rate_coarse_calib_mode") { cali_stage = 1; return CalibMode::Calib_Flow_Rate; @@ -788,6 +796,211 @@ void CalibUtils::calib_pa_pattern(const CalibInfo &calib_info, Model& model) model.calib_pa_pattern = std::make_unique(pa_pattern); } +void CalibUtils::set_for_auto_pa_model_and_config(const std::vector &calib_infos, DynamicPrintConfig &full_config, Model &model) +{ + DynamicPrintConfig print_config = calib_infos[0].print_prest->config; + + float nozzle_diameter = full_config.option("nozzle_diameter")->get_at(0); + int extruder_count = full_config.option("nozzle_diameter")->values.size(); + + for (const auto opt : SuggestedConfigCalibPAPattern().float_pairs) { print_config.set_key_value(opt.first, new ConfigOptionFloat(opt.second)); } + + std::vector sorted_calib_infos = calib_infos; + std::sort(sorted_calib_infos.begin(), sorted_calib_infos.end(), [](const CalibInfo &left_item, const CalibInfo &right_item) { + return left_item.index < right_item.index; + }); + + for (const CalibInfo &calib_info : calib_infos) { + int index = get_index_for_extruder_parameter(print_config, "outer_wall_speed", calib_info.extruder_id, calib_info.extruder_type, calib_info.nozzle_volume_type); + float wall_speed = CalibPressureAdvance::find_optimal_PA_speed(full_config, print_config.get_abs_value("line_width"), print_config.get_abs_value("layer_height"), + calib_info.extruder_id, 0); + + ConfigOptionFloatsNullable *wall_speed_speed_opt = print_config.option("outer_wall_speed"); + std::vector new_speeds = wall_speed_speed_opt->values; + new_speeds[index] = wall_speed; + ModelObject* object = model.objects[calib_info.index]; + object->config.set_key_value("outer_wall_speed", new ConfigOptionFloatsNullable(new_speeds)); + } + + for (const auto opt : SuggestedConfigCalibPAPattern().nozzle_ratio_pairs) { + print_config.set_key_value(opt.first, new ConfigOptionFloat(nozzle_diameter * opt.second / 100)); + } + + for (const auto opt : SuggestedConfigCalibPAPattern().int_pairs) { print_config.set_key_value(opt.first, new ConfigOptionInt(opt.second)); } + + print_config.set_key_value(SuggestedConfigCalibPAPattern().brim_pair.first, new ConfigOptionEnum(SuggestedConfigCalibPAPattern().brim_pair.second)); + + auto* _wall_generator = print_config.option>("wall_generator"); + _wall_generator->value = PerimeterGeneratorType::Arachne; + + print_config.option("enable_prime_tower")->value = false; + + auto get_new_filament_id = [&sorted_calib_infos](int index) -> int { + for (size_t i = 0; i < sorted_calib_infos.size(); ++i) { + if (index == sorted_calib_infos[i].index) { + return (int) (i + 1); // 1 base filament_id + } + } + return 0; + }; + + // set printable and reset filament_id + for (size_t i = 0; i < model.objects.size(); ++i) { + auto iter = std::find_if(calib_infos.begin(), calib_infos.end(), [i](const CalibInfo &item) { return item.index == i; }); + + if (iter == calib_infos.end()) { + model.objects[i]->printable = false; + } else { + ModelObject *object = model.objects[i]; + object->config.set_key_value("extruder", new ConfigOptionInt(get_new_filament_id(iter->index))); + for (auto *volume : object->volumes) { + if (volume->config.has("extruder")) volume->config.erase("extruder"); + } + } + } + + // DynamicPrintConfig full_config; + full_config.apply(print_config); + full_config.option>("filament_map_mode", true)->value = FilamentMapMode::fmmManual; + + // nozzle volume type + std::vector& nozzle_volume_types = full_config.option("nozzle_volume_type", true)->values; + nozzle_volume_types.resize(extruder_count, NozzleVolumeType::nvtStandard); + int filament_nums = calib_infos.size(); + std::vector physical_extruder_maps = dynamic_cast(full_config.option("physical_extruder_map", true))->values; + for (size_t filament_index = 0; filament_index < calib_infos.size(); ++filament_index) { + CalibInfo calib_info = calib_infos[filament_index]; + int extruder_id = calib_info.extruder_id; + for (size_t index = 0; index < extruder_count; ++index) { + if (physical_extruder_maps[index] == extruder_id) { + extruder_id = index; + } + } + nozzle_volume_types[extruder_id] = (int)calib_info.nozzle_volume_type; + } + + // filament map transform to 1 base + std::vector &filament_maps = full_config.option("filament_map", true)->values; + std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value + 1; }); + + std::vector &filament_colors = full_config.option("filament_colour")->values; + filament_colors.resize(sorted_calib_infos.size(), "#000000"); + for (size_t i = 0; i < sorted_calib_infos.size(); ++i) { + filament_colors[i] = sorted_calib_infos[i].filament_color; + } + + // Add flush volume matrix + std::vector flush_matrix_vec; + for (int e_idx = 0; e_idx < extruder_count; ++e_idx) { + const std::vector &min_flush_volumes = get_min_flush_volumes(full_config, e_idx); + for (size_t from_idx = 0; from_idx < filament_nums; ++from_idx) { + for (size_t to_idx = 0; to_idx < filament_nums; ++to_idx) { + if (from_idx == to_idx) { + flush_matrix_vec.emplace_back(0); + } + else { + Slic3r::FlushVolCalculator calculator(min_flush_volumes[from_idx], Slic3r::g_max_flush_volume, extruder_count > 1, NozzleVolumeType(nozzle_volume_types[e_idx])); + wxColour from = wxColour(filament_colors[from_idx]); + wxColour to = wxColour(filament_colors[to_idx]); + int volume = calculator.calc_flush_vol(from.Alpha(), from.Red(), from.Green(), from.Blue(), to.Alpha(), to.Red(), to.Green(), to.Blue()); + flush_matrix_vec.emplace_back(double(volume)); + } + } + } + + } + std::vector &config_matrix = full_config.option("flush_volumes_matrix")->values; + set_flush_volumes_matrix(config_matrix, flush_matrix_vec, -1, extruder_count); +} + +bool CalibUtils::calib_generic_auto_pa_cali(const std::vector &calib_infos, wxString &error_message) +{ + DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager(); + if (!dev) { + error_message = _L("Need select printer"); + return false; + } + + MachineObject *obj_ = dev->get_selected_machine(); + if (obj_ == nullptr) { + error_message = _L("Need select printer"); + return false; + } + + if (!check_printable_status_before_cali(obj_, calib_infos, error_message)) + return false; + + const Calib_Params ¶ms = calib_infos[0].params; + if (params.mode != CalibMode::Calib_Auto_PA_Line) + return false; + + Model model; + std::string input_file; + if (obj_->is_multi_extruders()) + input_file = Slic3r::resources_dir() + "/calib/pressure_advance/auto_pa_line_dual.3mf"; + else + input_file = Slic3r::resources_dir() + "/calib/pressure_advance/auto_pa_line_single.3mf"; + + read_model_from_file(input_file, model); + + DynamicPrintConfig print_config = calib_infos[0].print_prest->config; + DynamicPrintConfig filament_config = calib_infos[0].filament_prest->config; + DynamicPrintConfig printer_config = calib_infos[0].printer_prest->config; + + Preset printer_preset = *calib_infos[0].printer_prest; + Preset print_preset = *calib_infos[0].print_prest; + std::vector filament_presets; + std::vector filament_map; + filament_map.resize(calib_infos.size()); + std::vector physical_extruder_maps = dynamic_cast(printer_config.option("physical_extruder_map", true))->values; + for (size_t i = 0; i < calib_infos.size(); ++i) { + CalibInfo calib_info = calib_infos[i]; + calib_info.filament_prest->config.set_key_value("curr_bed_type", new ConfigOptionEnum(calib_info.bed_type)); + filament_presets.emplace_back(*calib_info.filament_prest); + for (size_t index = 0; index < physical_extruder_maps.size(); ++index) { + if (physical_extruder_maps[index] == calib_info.extruder_id) { + filament_map[i] = index; + } + } + } + + PresetBundle *preset_bundle = wxGetApp().preset_bundle; + DynamicPrintConfig full_config = PresetBundle::construct_full_config(printer_preset, print_preset, preset_bundle->project_config, filament_presets, false, filament_map); + + set_for_auto_pa_model_and_config(calib_infos, full_config, model); + if (!process_and_store_3mf(&model, full_config, params, error_message)) + return false; + + try { + json js; + if (params.mode == CalibMode::Calib_PA_Line) + js["cali_type"] = "cali_pa_line"; + else if (params.mode == CalibMode::Calib_PA_Pattern) + js["cali_type"] = "cali_pa_pattern"; + else if (params.mode == CalibMode::Calib_Auto_PA_Line) + js["cali_type"] = "cali_auto_pa_line"; + + const ConfigOptionFloatsNullable *nozzle_diameter_config = printer_config.option("nozzle_diameter"); + assert(nozzle_diameter_config->values.size() > 0); + float nozzle_diameter = nozzle_diameter_config->values[0]; + + js["nozzle_diameter"] = nozzle_diameter; + std::string filament_ids; + for (const auto calib_info : calib_infos) { + filament_ids += calib_info.filament_prest->filament_id; + filament_ids += " "; + } + js["filament_id"] = filament_ids; + js["printer_type"] = obj_->printer_type; + NetworkAgent *agent = GUI::wxGetApp().getAgent(); + if (agent) + agent->track_event("cali", js.dump()); + } catch (...) {} + + send_to_print(calib_infos, error_message); + return true; +} + bool CalibUtils::calib_generic_PA(const CalibInfo &calib_info, wxString &error_message) { DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager(); @@ -1193,6 +1406,74 @@ bool CalibUtils::check_printable_status_before_cali(const MachineObject *obj, co return true; } +bool CalibUtils::check_printable_status_before_cali(const MachineObject *obj, const std::vector &cali_infos, wxString &error_message) +{ + if (!obj) { + error_message = _L("Need select printer"); + return false; + } + + if (cali_infos.empty()) + return true; + + float cali_diameter = cali_infos[0].nozzle_diameter; + int extruder_id = cali_infos[0].extruder_id; + for (const auto &cali_info : cali_infos) { + if (cali_infos[0].params.mode == CalibMode::Calib_Auto_PA_Line && !is_support_auto_pa_cali(cali_info.filament_prest->filament_id)) { + error_message = _L("TPU 90A/TPU 85A is too soft and does not support automatic Flow Dynamics calibration."); + return false; + } + + if (!is_approx(cali_diameter, cali_info.nozzle_diameter)) { + error_message = _L("Automatic calibration only supports cases where the left and right nozzle diameters are identical."); + return false; + } + } + + if (extruder_id >= obj->m_extder_data.extders.size()) { + error_message = _L("The number of printer extruders and the printer selected for calibration does not match."); + return false; + } + + float diameter = obj->m_extder_data.extders[extruder_id].current_nozzle_diameter; + bool is_multi_extruder = obj->is_multi_extruders(); + std::vector nozzle_volume_types; + if (is_multi_extruder) { + for (const Extder &extruder : obj->m_extder_data.extders) { nozzle_volume_types.emplace_back(extruder.current_nozzle_flow_type); } + } + + for (const auto &cali_info : cali_infos) { + wxString name = _L("left"); + if (cali_info.extruder_id == 0) { name = _L("right"); } + + if (!is_approx(cali_info.nozzle_diameter, diameter)) { + if (is_multi_extruder) + error_message = wxString::Format(_L("The currently selected nozzle diameter of %s extruder does not match the actual nozzle diameter.\n" + "Please click the Sync button above and restart the calibration."), + name); + else + error_message = _L("The nozzle diameter does not match the actual printer nozzle diameter.\n" + "Please click the Sync button above and restart the calibration."); + return false; + } + + if (is_multi_extruder) { + if (nozzle_volume_types[cali_info.extruder_id] == NozzleFlowType::NONE_FLOWTYPE) { + error_message = wxString::Format(_L("Printer %s nozzle information has not been set. Please configure it before proceeding with the calibration."), name); + return false; + } + + if (NozzleVolumeType(nozzle_volume_types[cali_info.extruder_id] - 1) != cali_info.nozzle_volume_type) { + error_message = wxString::Format(_L("The currently selected nozzle type of %s extruder does not match the actual printer nozzle type.\n" + "Please click the Sync button above and restart the calibration."), + name); + return false; + } + } + } + return true; +} + bool CalibUtils::check_printable_status_before_cali(const MachineObject* obj, const CalibInfo& cali_info, wxString& error_message) { if (!obj) { @@ -1296,7 +1577,7 @@ bool CalibUtils::process_and_store_3mf(Model *model, const DynamicPrintConfig &f BuildVolume build_volume(bedfs, print_height, extruder_areas, extruder_heights); unsigned int count = model->update_print_volume_state(build_volume); - if (count == 0) { + if (count == 0 && params.mode != CalibMode::Calib_Auto_PA_Line) { error_message = _L("Unable to calibrate: maybe because the set calibration value range is too large, or the step is too small"); return false; } @@ -1546,5 +1827,121 @@ void CalibUtils::send_to_print(const CalibInfo &calib_info, wxString &error_mess replace_job(*print_worker, std::move(print_job)); } +void CalibUtils::send_to_print(const std::vector &calib_infos, wxString &error_message, int flow_ratio_mode) +{ + std::string dev_id = calib_infos[0].dev_id; + std::shared_ptr process_bar = calib_infos[0].process_bar; + BedType bed_type = calib_infos[0].bed_type; + + DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager(); + if (!dev) { + error_message = _L("Need select printer"); + return; + } + + MachineObject *obj_ = dev->get_selected_machine(); + if (obj_ == nullptr) { + error_message = _L("Need select printer"); + return; + } + + if (obj_->is_in_upgrading()) { + error_message = _L("Cannot send the print job when the printer is updating firmware"); + return; + } else if (obj_->is_system_printing()) { + error_message = _L("The printer is executing instructions. Please restart printing after it ends"); + return; + } else if (obj_->is_in_printing()) { + error_message = _L("The printer is busy on other print job"); + return; + } + + else if (!obj_->is_support_print_without_sd && (obj_->get_sdcard_state() == MachineObject::SdcardState::NO_SDCARD)) { + error_message = _L("Storage needs to be inserted before printing."); + return; + } + if (obj_->is_lan_mode_printer()) { + if (obj_->get_sdcard_state() == MachineObject::SdcardState::NO_SDCARD) { + error_message = _L("Storage needs to be inserted before printing via LAN."); + return; + } + } + + auto print_job = std::make_shared(dev_id); + print_job->m_dev_ip = obj_->dev_ip; + print_job->m_ftp_folder = obj_->get_ftp_folder(); + print_job->m_access_code = obj_->get_access_code(); + +#if !BBL_RELEASE_TO_PUBLIC + print_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; + print_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; +#else + print_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp; + print_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt; +#endif + + print_job->connection_type = obj_->connection_type(); + print_job->cloud_print_only = obj_->is_support_cloud_print_only; + + PrintPrepareData job_data; + job_data.is_from_plater = false; + job_data.plate_idx = 0; + job_data._3mf_config_path = config_3mf_path; + job_data._3mf_path = path; + job_data._temp_path = temp_dir; + + PlateListData plate_data; + plate_data.is_valid = true; + plate_data.plate_count = 1; + plate_data.cur_plate_index = 0; + plate_data.bed_type = bed_type; + + print_job->job_data = job_data; + print_job->plate_data = plate_data; + print_job->m_print_type = "from_normal"; + + // set AMS mapping + std::string select_ams = "["; + std::string new_select_ams = "["; + for (size_t i = 0; i < calib_infos.size(); ++i) { + select_ams += calib_infos[i].select_ams; + new_select_ams += "{\"ams_id\":" + std::to_string(calib_infos[i].ams_id) + ", \"slot_id\":" + std::to_string(calib_infos[i].slot_id) + "}"; + if (i != calib_infos.size() - 1) { + select_ams += ","; + new_select_ams += ","; + } + } + select_ams += "]"; + new_select_ams += "]"; + print_job->task_ams_mapping = select_ams; + print_job->task_ams_mapping_info = ""; + print_job->task_ams_mapping2 = new_select_ams; + + if (calib_infos.size() == 1 && (calib_infos[0].select_ams == VIRTUAL_AMS_MAIN_ID_STR || calib_infos[0].select_ams == VIRTUAL_AMS_DEPUTY_ID_STR)) { + print_job->task_use_ams = false; + } + else { + print_job->task_use_ams = true; + } + + CalibMode cali_mode = calib_infos[0].params.mode; + print_job->m_project_name = get_calib_mode_name(cali_mode, flow_ratio_mode); + print_job->set_calibration_task(true); + + print_job->has_sdcard = obj_->get_sdcard_state() == MachineObject::SdcardState::HAS_SDCARD_NORMAL; + print_job->set_print_config(MachineBedTypeString[bed_type], true, true, false, false, true, 0, 1, 0); + print_job->set_print_job_finished_event(wxGetApp().plater()->get_send_calibration_finished_event(), print_job->m_project_name); + + { // after send: record the print job + json j; + j["print"]["ams_mapping"] = print_job->task_ams_mapping; + j["print"]["ams_mapping_2"] = print_job->task_ams_mapping2; + j["print"]["project_name"] = print_job->m_project_name; + j["print"]["is_cali_task"] = print_job->m_is_calibration_task; + BOOST_LOG_TRIVIAL(info) << "send_cali_job - after send: " << j.dump(); + } + + replace_job(*print_worker, std::move(print_job)); +} } } diff --git a/src/slic3r/Utils/CalibUtils.hpp b/src/slic3r/Utils/CalibUtils.hpp index 67fd69f0fe..118894dc7f 100644 --- a/src/slic3r/Utils/CalibUtils.hpp +++ b/src/slic3r/Utils/CalibUtils.hpp @@ -16,9 +16,11 @@ extern const float MAX_PA_K_VALUE; class CalibInfo { public: + int index = -1; int extruder_id = 0; int ams_id = 0; int slot_id = 0; + float nozzle_diameter; ExtruderType extruder_type{ExtruderType::etDirectDrive}; NozzleVolumeType nozzle_volume_type; Calib_Params params; @@ -26,6 +28,7 @@ public: Preset* filament_prest; Preset* print_prest; BedType bed_type; + std::string filament_color; std::string dev_id; std::string select_ams; std::shared_ptr process_bar; @@ -58,6 +61,9 @@ public: static void calib_pa_pattern(const CalibInfo &calib_info, Model &model); + static void set_for_auto_pa_model_and_config(const std::vector &calib_info, DynamicPrintConfig &full_config, Model &model); + + static bool calib_generic_auto_pa_cali(const std::vector &calib_info, wxString & error_message); static bool calib_generic_PA(const CalibInfo &calib_info, wxString &error_message); static void calib_temptue(const CalibInfo &calib_info, wxString &error_message); static void calib_max_vol_speed(const CalibInfo &calib_info, wxString &error_message); @@ -76,10 +82,12 @@ public: static bool check_printable_status_before_cali(const MachineObject *obj, const X1CCalibInfos &cali_infos, wxString &error_message); static bool check_printable_status_before_cali(const MachineObject *obj, const CalibInfo &cali_info, wxString &error_message); + static bool check_printable_status_before_cali(const MachineObject *obj, const std::vector &cali_infos, wxString &error_message); private: static bool process_and_store_3mf(Model* model, const DynamicPrintConfig& full_config, const Calib_Params& params, wxString& error_message); static void send_to_print(const CalibInfo &calib_info, wxString& error_message, int flow_ratio_mode = 0); // 0: none 1: coarse 2: fine + static void send_to_print(const std::vector &calib_infos, wxString &error_message, int flow_ratio_mode = 0); // 0: none 1: coarse 2: fine }; extern void get_tray_ams_and_slot_id(MachineObject* obj, int in_tray_id, int &ams_id, int &slot_id, int &tray_id);