From bf17e8e99836a1342014bf40eebef748b77bb343 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 27 Nov 2024 12:31:49 -0500 Subject: [PATCH] Implement filament consumption dialog and undo functionality --- src/libslic3r/AppConfig.cpp | 3 + src/libslic3r/GCode.cpp | 33 +++------- src/libslic3r/Preset.hpp | 2 +- src/libslic3r/PresetBundle.cpp | 4 -- src/libslic3r/Print.cpp | 28 ++++++++ src/libslic3r/Print.hpp | 24 +++++++ src/libslic3r/PrintConfig.cpp | 8 +-- src/libslic3r/PrintConfig.hpp | 2 +- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 10 +-- src/slic3r/GUI/NotificationManager.cpp | 15 +++++ src/slic3r/GUI/NotificationManager.hpp | 4 ++ src/slic3r/GUI/Plater.cpp | 62 ++++++++++++++++++ src/slic3r/GUI/Tab.cpp | 2 +- src/slic3r/Utils/Spoolman.cpp | 72 +++++++++++++++++++-- src/slic3r/Utils/Spoolman.hpp | 21 +++++- 15 files changed, 241 insertions(+), 49 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 0decfaac12..fbd72d565f 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -407,6 +407,9 @@ void AppConfig::set_defaults() set_str("print", "timelapse", "1"); } + if (get("spoolman", "consumption_type").empty()) + set_str("spoolman", "consumption_type", "weight"); + // Remove legacy window positions/sizes erase("app", "main_frame_maximized"); erase("app", "main_frame_pos"); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 6ae57e8e9e..e61afd66b7 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1566,32 +1566,15 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu result->filename = path; } - std::vector filaments_with_spoolman_idxs; - for (int l = 0; l < print->config().filament_density.size(); ++l) { - if (print->config().filament_spoolman_enabled.get_at(l)) - filaments_with_spoolman_idxs.push_back(l); - } + // Check the consumption of filament against the remaining filament as reported by Spoolman + for (const auto& est : print->get_spoolman_filament_consumption_estimates()) { + double remaining_length = print->config().filament_remaining_length.get_at(est.print_config_idx); + double remaining_weight = print->config().filament_remaining_weight.get_at(est.print_config_idx); - if (!filaments_with_spoolman_idxs.empty()) { - // get used filament (meters and grams) from used volume in respect to the active extruder - auto get_used_filament_from_volume = [&](int extruder_id) { - double volume = print->m_print_statistics.filament_stats[extruder_id]; - std::pair ret = { volume / (PI * sqr(0.5 * print->config().filament_diameter.get_at(extruder_id))), - volume * print->config().filament_density.get_at(extruder_id) * 0.001 }; - return ret; - }; - - for (const auto& item : filaments_with_spoolman_idxs) { - auto [est_used_length, est_used_weight] = get_used_filament_from_volume(item); - double remaining_length = print->config().filament_remaining_length.get_at(item); - double remaining_weight = print->config().filament_remaining_weight.get_at(item); - - if (est_used_length > remaining_length || est_used_weight > remaining_weight) { - std::string filament_name = print->config().filament_settings_id.get_at(item); - std::string msg = boost::str(boost::format(_("Filament %1% does not have enough material for the print. Used: %2$.2f m, %3$.2f g, Remaining: %4$.2f m, %5$.2f g")) % - filament_name % (est_used_length * 0.001) % est_used_weight % (remaining_length * 0.001) % remaining_weight); - print->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, msg, PrintStateBase::SlicingNotificationType::SlicingNotEnoughFilament); - } + if (est.est_used_length > remaining_length || est.est_used_weight > remaining_weight) { + std::string msg = boost::str(boost::format(_("Filament %1% does not have enough material for the print. Used: %2$.2f m, %3$.2f g, Remaining: %4$.2f m, %5$.2f g")) % + est.filament_name % (est.est_used_length * 0.001) % est.est_used_weight % (remaining_length * 0.001) % remaining_weight); + print->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, msg, PrintStateBase::SlicingNotificationType::SlicingNotEnoughFilament); } } diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 81d41d9d0b..ba4e5880b6 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -249,7 +249,7 @@ public: // works for filament and printer profiles. All other profiles return false bool spoolman_enabled() const { if (type == TYPE_FILAMENT) - return config.opt_int("spoolman_spool_id") > 0; + return config.opt_int("spoolman_spool_id", 0) > 0; if (type == TYPE_PRINTER) return config.opt_bool("spoolman_enabled"); return false; diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 8946a8fea3..1c7d0761bc 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -2127,7 +2127,6 @@ DynamicPrintConfig PresetBundle::full_fff_config() const std::vector print_compatible_printers; //BBS: add logic for settings check between different system presets std::vector different_settings; - std::vector filament_spoolman_enabled; std::vector filament_remaining_weight; std::vector filament_remaining_length; std::string different_print_settings, different_printer_settings; @@ -2152,7 +2151,6 @@ DynamicPrintConfig PresetBundle::full_fff_config() const out.apply(this->filaments.get_edited_preset().config); compatible_printers_condition.emplace_back(this->filaments.get_edited_preset().compatible_printers_condition()); compatible_prints_condition .emplace_back(this->filaments.get_edited_preset().compatible_prints_condition()); - filament_spoolman_enabled.emplace_back(this->filaments.get_edited_preset().spoolman_enabled()); filament_remaining_weight.emplace_back(this->filaments.get_edited_preset().spoolman_statistics->remaining_weight); filament_remaining_length.emplace_back(this->filaments.get_edited_preset().spoolman_statistics->remaining_length); //BBS: add logic for settings check between different system presets @@ -2196,7 +2194,6 @@ DynamicPrintConfig PresetBundle::full_fff_config() const DynamicPrintConfig &cfg_rw = *const_cast(cfg); compatible_printers_condition.emplace_back(Preset::compatible_printers_condition(cfg_rw)); compatible_prints_condition .emplace_back(Preset::compatible_prints_condition(cfg_rw)); - filament_spoolman_enabled.emplace_back(preset->spoolman_enabled()); filament_remaining_weight.emplace_back(preset->spoolman_statistics->remaining_weight); filament_remaining_length.emplace_back(preset->spoolman_statistics->remaining_length); @@ -2306,7 +2303,6 @@ DynamicPrintConfig PresetBundle::full_fff_config() const out.option("filament_settings_id", true)->values = this->filament_presets; out.option("printer_settings_id", true)->value = this->printers.get_selected_preset_name(); out.option("filament_ids", true)->values = filament_ids; - out.option("filament_spoolman_enabled", true)->values = filament_spoolman_enabled; out.option("filament_remaining_weight", true)->values = filament_remaining_weight; out.option("filament_remaining_length", true)->values = filament_remaining_length; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index f76029d2ee..5b6eb5d882 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -36,6 +36,7 @@ #include "nlohmann/json.hpp" #include "GCode/ConflictChecker.hpp" +#include "slic3r/Utils/Spoolman.hpp" #include @@ -3058,6 +3059,33 @@ Vec3d Print::shrinkage_compensation() const return { xy_compensation, xy_compensation, z_compensation }; } +std::vector Print::get_spoolman_filament_consumption_estimates() const +{ + std::vector spoolman_filament_consumption; + + std::vector filaments_with_spoolman_idxs; + for (int l = 0; l < m_config.spoolman_spool_id.size(); ++l) { + if (m_config.spoolman_spool_id.get_at(l) > 0) + filaments_with_spoolman_idxs.push_back(l); + } + + // get used filament (meters and grams) from used volume in respect to the active extruder + auto get_used_filament_from_volume = [&](const int& extruder_id) { + // confirm the item exists in the stats map + if (m_print_statistics.filament_stats.count(extruder_id) <= 0) + return std::pair {0., 0.}; + + const double& volume = m_print_statistics.filament_stats.at(extruder_id); + return std::pair { volume / (PI * sqr(0.5 * m_config.filament_diameter.get_at(extruder_id))), + volume * m_config.filament_density.get_at(extruder_id) * 0.001 }; + }; + + for (const auto& idx : filaments_with_spoolman_idxs) + spoolman_filament_consumption.emplace_back(idx, m_config, get_used_filament_from_volume(idx)); + + return spoolman_filament_consumption; +} + const std::string PrintStatistics::FilamentUsedG = "filament used [g]"; const std::string PrintStatistics::FilamentUsedGMask = "; filament used [g] ="; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 7b7d13e709..c1128c62f3 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -812,6 +812,28 @@ enum FilamentTempType { HighLowCompatible, Undefine }; + +struct SpoolmanFilamentConsumptionEstimate +{ + const unsigned int print_config_idx; + const unsigned int spoolman_spool_id; + const std::string filament_name; + const double est_used_length; + const double est_used_weight; + + SpoolmanFilamentConsumptionEstimate(const unsigned int& print_config_idx, const PrintConfig& config, const double& est_used_length, const double& est_used_weight) + : print_config_idx(print_config_idx) + , spoolman_spool_id(config.spoolman_spool_id.get_at(print_config_idx)) + , filament_name(config.filament_settings_id.get_at(print_config_idx)) + , est_used_length(est_used_length) + , est_used_weight(est_used_weight) + {} + + // Alternate that allows the estimates to be provided as a std::pair [est_used_length, est_used_weight] + SpoolmanFilamentConsumptionEstimate(const unsigned int& print_config_idx, const PrintConfig& config, const std::pair& estimates) : + SpoolmanFilamentConsumptionEstimate(print_config_idx, config, estimates.first, estimates.second) {} +}; + // The complete print tray with possibly multiple objects. class Print : public PrintBaseWithState { @@ -990,6 +1012,8 @@ public: std::tuple object_skirt_offset(double margin_height = 0) const; + std::vector get_spoolman_filament_consumption_estimates() const; + protected: // Invalidates the step, and its depending steps in Print. bool invalidate_step(PrintStep step); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 80255a1382..4f816d8ca4 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2198,10 +2198,6 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionStrings()); def->cli = ConfigOptionDef::nocli; - def = this->add("filament_spoolman_enabled", coBools); - def->set_default_value(new ConfigOptionBools()); - def->cli = ConfigOptionDef::nocli; - def = this->add("filament_remaining_weight", coFloats); def->set_default_value(new ConfigOptionFloats()); def->cli = ConfigOptionDef::nocli; @@ -2217,13 +2213,13 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionStrings{L("(Undefined)")}); def->cli = ConfigOptionDef::nocli; - def = this->add("spoolman_spool_id", coInt); + def = this->add("spoolman_spool_id", coInts); def->label = L("Spoolman ID"); def->tooltip = L("The spool ID of this filament profile within your Spoolman instance. This will allow automatic spool switching when " "using moonraker to track spool usage and one touch updating of this filament profile from the Spoolman properties. " "Setting this to a value of 0 disables its functionality."); def->mode = comSimple; - def->set_default_value(new ConfigOptionInt()); + def->set_default_value(new ConfigOptionInts({ 0 })); def->cli = ConfigOptionDef::nocli; def = this->add("infill_direction", coFloat); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 331449c72b..a6476c3817 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1163,7 +1163,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionStrings, small_area_infill_flow_compensation_model)) ((ConfigOptionBool, has_scarf_joint_seam)) - ((ConfigOptionBools, filament_spoolman_enabled)) + ((ConfigOptionInts, spoolman_spool_id)) ((ConfigOptionFloats, filament_remaining_weight)) ((ConfigOptionFloats, filament_remaining_length)) ) diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index a71e7cca0d..b1ff847a55 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -250,9 +250,11 @@ void BackgroundSlicingProcess::process_fff() finalize_gcode(); else export_gcode(); + wxQueueEvent(wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_finished_id)); } else if (! m_upload_job.empty()) { wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id)); prepare_upload(); + wxQueueEvent(wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_finished_id)); } else { m_print->set_status(100, _utf8(L("Slicing complete"))); } @@ -883,10 +885,10 @@ void BackgroundSlicingProcess::export_gcode() } // BBS - auto evt = new wxCommandEvent(m_event_export_finished_id, GUI::wxGetApp().mainframe->m_plater->GetId()); - wxString output_gcode_str = wxString::FromUTF8(export_path.c_str(), export_path.length()); - evt->SetString(output_gcode_str); - wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt); + // auto evt = new wxCommandEvent(m_event_export_finished_id, GUI::wxGetApp().mainframe->m_plater->GetId()); + // wxString output_gcode_str = wxString::FromUTF8(export_path.c_str(), export_path.length()); + // evt->SetString(output_gcode_str); + // wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt); // BBS: to be checked. Whether use export_path or output_path. gcode_add_line_number(export_path, m_fff_print->full_print_config()); diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index b1713ffb30..d12b53d4eb 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -26,6 +26,8 @@ #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif +#include "Spoolman.hpp" + #include static constexpr float GAP_WIDTH = 10.0f; @@ -2087,6 +2089,19 @@ void NotificationManager::push_import_finished_notification(const std::string& p set_slicing_progress_hidden(); } +void NotificationManager::push_spoolman_consumption_finished_notification() +{ + close_notification_of_type(NotificationType::SpoolmanConsumptionFinished); + auto callback = [](wxEvtHandler*) { + if (Spoolman::get_instance()->undo_use_spoolman_spools()) + return true; + show_error(nullptr, _L("Failed to undo Spoolman filament consumption")); + return false; + }; + NotificationData data {NotificationType::SpoolmanConsumptionFinished, NotificationLevel::RegularNotificationLevel, 0, _u8L("Spoolman consumption finished successfully.") + " ", _u8L("Undo"), callback }; + push_notification_data(data, 0); +} + void NotificationManager::push_download_URL_progress_notification(size_t id, const std::string& text, std::function user_action_callback) { // If already exists diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 1c633c6341..bc1d2aa075 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -126,6 +126,8 @@ enum class NotificationType NetfabbFinished, // Short meesage to fill space between start and finish of export ExportOngoing, + // A message showing that Spoolman filament consumption finished and allow a rollback of the action + SpoolmanConsumptionFinished, // Progressbar of download from prusaslicer://url URLDownload, // BBS: Short meesage to fill space between start and finish of arranging @@ -250,6 +252,8 @@ public: void push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable); void push_import_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable); + void push_spoolman_consumption_finished_notification(); + // Download URL progress notif void push_download_URL_progress_notification(size_t id, const std::string& text, std::function user_action_callback); void set_download_URL_progress(size_t id, float percentage); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 62ca81b440..d63d42bd8c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2564,6 +2564,10 @@ struct Plater::priv // Returns true if current_warnings vector is empty without showning the dialog bool warnings_dialog(); + // If Spoolman is active, the server is valid, and at least one Spoolman spool is used, + // a dialog will be show asking if the user would like to consume the estimated filament usage + void spoolman_consumption_dialog(const bool& all_plates); + void on_action_add(SimpleEvent&); void on_action_add_plate(SimpleEvent&); void on_action_del_plate(SimpleEvent&); @@ -6760,8 +6764,12 @@ void Plater::priv::on_export_began(wxCommandEvent& evt) void Plater::priv::on_export_finished(wxCommandEvent& evt) { + if (!m_export_all || (m_cur_slice_plate == (partplate_list.get_plate_count() - 1))) + spoolman_consumption_dialog(m_export_all); #if 0 //BBS: also export 3mf to the same directory for debugging + if (evt.GetString().empty()) + return; std::string gcode_path_str(evt.GetString().ToUTF8().data()); fs::path gcode_path(gcode_path_str); @@ -6866,6 +6874,60 @@ bool Plater::priv::warnings_dialog() return res == wxID_YES; } +void Plater::priv::spoolman_consumption_dialog(const bool& all_plates) +{ + if (!wxGetApp().preset_bundle->printers.get_edited_preset().spoolman_enabled()) + return; + if (!Spoolman::is_server_valid()) + return; + + auto spoolman = Spoolman::get_instance(); + const auto& consumption_type = wxGetApp().app_config->get("spoolman", "consumption_type"); + std::string unit = consumption_type == "weight" ? "g" : consumption_type == "length" ? "mm" : ""; + + if (unit.empty()) { + BOOST_LOG_TRIVIAL(error) << "The specified consumption type is not valid"; + return; + } + + std::map estimates; + std::map messages; + + auto apply_estimates_from_plate = [&] (PartPlate* plate) { + for (const auto& est : plate->fff_print()->get_spoolman_filament_consumption_estimates()) { + auto& id = est.spoolman_spool_id; + if (consumption_type == "weight") { + estimates[id] += est.est_used_weight; + } else if (consumption_type == "length") { + estimates[id] += est.est_used_length; + } else return; + messages[id] = wxString::FromUTF8((boost::format("%1%: %2% %3%") % est.filament_name % double_to_string(estimates[id], 2) % unit).str()); + } + }; + + if (all_plates) + for (const auto& plate : partplate_list.get_plate_list()) + apply_estimates_from_plate(plate); + else + apply_estimates_from_plate(partplate_list.get_curr_plate()); + + if (estimates.empty()) return; + + auto msg = _L("Would you like to consume the used filaments registered in Spoolman?") + "\n\n"; + for (const auto& [id, message] : messages) + msg += message + "\n"; + + auto dlg = MessageDialog(nullptr, msg, _L("Spoolman Filament Consumption"), wxYES_NO); + if (dlg.ShowModal() == wxID_YES) { + if (spoolman->use_spoolman_spools(estimates, consumption_type)) { + notification_manager->push_spoolman_consumption_finished_notification(); + } else { + BOOST_LOG_TRIVIAL(error) << "Failed to consume filament from Spoolman"; + show_error(nullptr, _L("Failed to consume filament from Spoolman")); + } + } +} + //BBS: add project slice logic void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) { diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index c1e52886da..08916dbfa6 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3712,7 +3712,7 @@ void TabFilament::toggle_options() } if (m_active_page->title() == L("Spoolman")) { - toggle_line("spoolman_update", m_config->opt_int("spoolman_spool_id") > 0); + toggle_line("spoolman_update", m_config->opt_int("spoolman_spool_id", 0) > 0); update_spoolman_statistics(); } } diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 65733db2fe..9f5612daeb 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -152,10 +152,10 @@ bool Spoolman::pull_spoolman_spools() return true; } -bool Spoolman::use_spoolman_spool(const unsigned int& spool_id, const double& weight_used) +bool Spoolman::use_spoolman_spool(const unsigned int& spool_id, const double& usage, const std::string& usage_type) { pt::ptree tree; - tree.put("use_weight", weight_used); + tree.put("use_" + usage_type, usage); std::string endpoint = (boost::format("spool/%1%/use") % spool_id).str(); tree = put_spoolman_json(endpoint, tree); @@ -166,6 +166,46 @@ bool Spoolman::use_spoolman_spool(const unsigned int& spool_id, const double& we return true; } +bool Spoolman::use_spoolman_spools(const std::map& data, const std::string& usage_type) +{ + if (!(usage_type == "length" || usage_type == "weight")) + return false; + + std::vector spool_ids; + + for (auto& [spool_id, usage] : data) { + if (!use_spoolman_spool(spool_id, usage, usage_type)) + return false; + spool_ids.emplace_back(spool_id); + } + + update_specific_spool_statistics(spool_ids); + + m_use_undo_buffer = data; + m_last_usage_type = usage_type; + return true; +} + +bool Spoolman::undo_use_spoolman_spools() +{ + if (m_use_undo_buffer.empty() || m_last_usage_type.empty()) + return false; + + std::vector spool_ids; + + for (auto& [spool_id, usage] : m_use_undo_buffer) { + if (!use_spoolman_spool(spool_id, usage * -1, m_last_usage_type)) + return false; + spool_ids.emplace_back(spool_id); + } + + update_specific_spool_statistics(spool_ids); + + m_use_undo_buffer.clear(); + m_last_usage_type.clear(); + return true; +} + SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile, bool detach, @@ -192,7 +232,7 @@ SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolSh // Check for presets with the same spool ID int visible(0), invisible(0); for (const auto& item : filaments()) { // count num of visible and invisible - if (item.config.opt_int("spoolman_spool_id") == spool->id) { + if (item.config.opt_int("spoolman_spool_id", 0) == spool->id) { if (item.is_visible) visible++; else @@ -241,7 +281,7 @@ SpoolmanResult Spoolman::update_filament_preset_from_spool(Preset* filament_pres result.messages.emplace_back("Preset is not a filament preset"); return result; } - const int& spool_id = filament_preset->config.opt_int("spoolman_spool_id"); + const int& spool_id = filament_preset->config.opt_int("spoolman_spool_id", 0); if (spool_id < 1) { result.messages.emplace_back( "Preset provided does not have a valid Spoolman spool ID"); // IDs below 1 are not used by spoolman and should be ignored @@ -271,12 +311,34 @@ void Spoolman::update_visible_spool_statistics(bool clear_cache) if (item->is_user() && item->spoolman_enabled()) { if (auto res = update_filament_preset_from_spool(item, true, true); res.has_failed()) BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Failed to update spoolman statistics with the following error: " - << res.build_single_line_message() << "Spool ID: " << item->config.opt_int("spoolman_spool_id"); + << res.build_single_line_message() << "Spool ID: " << item->config.opt_int("spoolman_spool_id", 0); } } } } +void Spoolman::update_specific_spool_statistics(const std::vector& spool_ids) +{ + PresetBundle* preset_bundle = GUI::wxGetApp().preset_bundle; + PresetCollection& printers = preset_bundle->printers; + PresetCollection& filaments = preset_bundle->filaments; + + std::set spool_ids_set(spool_ids.begin(), spool_ids.end()); + // make sure '0' is not a value + spool_ids_set.erase(0); + + if (printers.get_edited_preset().spoolman_enabled() && is_server_valid()) { + for (auto item : filaments.get_visible()) { + if (item->is_user() && spool_ids_set.count(item->config.opt_int("spoolman_spool_id", 0)) > 0) { + if (auto res = update_filament_preset_from_spool(item, true, true); res.has_failed()) + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": Failed to update spoolman statistics with the following error: " + << res.build_single_line_message() << "Spool ID: " << item->config.opt_int("spoolman_spool_id", 0); + } + } + } +} + + bool Spoolman::is_server_valid() { bool res = false; diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index e912622d1d..4f925c4016 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -50,6 +50,9 @@ class Spoolman bool m_initialized{false}; + std::map m_use_undo_buffer{}; + std::string m_last_usage_type{}; + std::map m_vendors{}; std::map m_filaments{}; std::map m_spools{}; @@ -71,10 +74,21 @@ class Spoolman /// get all the spools from the api and store them /// \returns if succeeded bool pull_spoolman_spools(); -public: + /// uses/consumes filament from the specified spool then updates the spool + /// \param usage_type The consumption metric to be used. Should be "length" or "weight". This will NOT be checked. /// \returns if succeeded - bool use_spoolman_spool(const unsigned int& spool_id, const double& weight_used); + bool use_spoolman_spool(const unsigned int& spool_id, const double& usage, const std::string& usage_type); +public: + /// uses/consumes filament from multiple specified spools then updates them + /// \param data a map with the spool ID as the key and the amount to be consumed as the value + /// \param usage_type The consumption metric to be used. Should be "length" or "weight". This will be checked. + /// \returns if succeeded + bool use_spoolman_spools(const std::map& data, const std::string& usage_type); + + /// undo the previous use/consumption + /// \returns if succeeded + bool undo_use_spoolman_spools(); static SpoolmanResult create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile, @@ -88,6 +102,9 @@ public: /// clear_cache should be set true if the update is due to a change in printer profile or other change that requires it static void update_visible_spool_statistics(bool clear_cache = false); + /// Update the statistics values for the filament profiles tied to the specified spool IDs + static void update_specific_spool_statistics(const std::vector& spool_ids); + static bool is_server_valid(); const std::map& get_spoolman_spools(bool update = false)