From 29d32aa1ed2797b46d25368e76d72eebd9ac869c Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 20 Mar 2024 05:15:25 -0400 Subject: [PATCH 001/100] Start Spoolman Implementation -Spoolman utility class that fetches the spools, creates filament presets, and updates filament presets -Add config entries for all needed values --- src/libslic3r/Preset.cpp | 7 +- src/libslic3r/PrintConfig.cpp | 63 ++++++ src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/CreatePresetsDialog.cpp | 4 +- src/slic3r/GUI/CreatePresetsDialog.hpp | 3 + src/slic3r/GUI/PhysicalPrinterDialog.cpp | 19 +- src/slic3r/GUI/Tab.cpp | 11 + src/slic3r/Utils/Spoolman.cpp | 262 +++++++++++++++++++++++ src/slic3r/Utils/Spoolman.hpp | 166 ++++++++++++++ 9 files changed, 530 insertions(+), 7 deletions(-) create mode 100644 src/slic3r/Utils/Spoolman.cpp create mode 100644 src/slic3r/Utils/Spoolman.hpp diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 48c52d9686..75e397eef6 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -849,7 +849,8 @@ static std::vector s_Preset_filament_options { "filament_loading_speed", "filament_loading_speed_start", "filament_load_time", "filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", - "filament_multitool_ramming", "filament_multitool_ramming_volume", "filament_multitool_ramming_flow", "activate_chamber_temp_control" + "filament_multitool_ramming", "filament_multitool_ramming_volume", "filament_multitool_ramming_flow", "activate_chamber_temp_control", + "spoolman_spool_id", "spoolman_remaining_weight", "spoolman_used_weight", "spoolman_remaining_length", "spoolman_used_length", "spoolman_archived" }; static std::vector s_Preset_machine_limits_options { @@ -874,7 +875,7 @@ static std::vector s_Preset_printer_options { "best_object_pos","head_wrap_detect_zone", //SoftFever "host_type", "print_host", "printhost_apikey", - "print_host_webui", + "print_host_webui", "spoolman_enabled", "spoolman_port", "printhost_cafile","printhost_port","printhost_authorization_type", "printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", "thumbnails", "thumbnails_format", "use_firmware_retraction", "use_relative_e_distances", "printer_notes", @@ -2184,7 +2185,7 @@ bool PresetCollection::clone_presets_for_filament(Preset const *const & pres { std::vector const presets = {preset}; return clone_presets(presets, failures, [&filament_name, &filament_id, &dynamic_config, &compatible_printers](Preset &preset, Preset::Type &type) { - preset.name = filament_name + " @" + compatible_printers; + preset.name = filament_name + (compatible_printers.empty() ? "" : (" @" + compatible_printers)); if (type == Preset::TYPE_FILAMENT) { preset.config.apply_only(dynamic_config, {"filament_vendor", "compatible_printers", "filament_type"},true); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f07b0de3ae..3f8cbec4de 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -534,6 +534,20 @@ void PrintConfigDef::init_common_params() def->cli = ConfigOptionDef::nocli; def->set_default_value(new ConfigOptionString("")); + def = this->add("spoolman_enabled", coBool); + def->label = L("Spoolman Support"); + def->tooltip = L("Enables spool management features powered by a Spoolman server instance"); + def->mode = comAdvanced; + def->cli = ConfigOptionDef::nocli; + def->set_default_value(new ConfigOptionBool()); + + def = this->add("spoolman_port", coString); + def->label = L("Spoolman Port"); + def->tooltip = L("Indicates the port your Spoolman instance is hosted on"); + def->mode = comAdvanced; + def->cli = ConfigOptionDef::nocli; + def->set_default_value(new ConfigOptionString("8000")); + def = this->add("print_host_webui", coString); def->label = L("Device UI"); def->tooltip = L("Specify the URL of your device user interface if it's not same as print_host"); @@ -1948,6 +1962,55 @@ def = this->add("filament_loading_speed", coFloats); def->set_default_value(new ConfigOptionStrings{L("(Undefined)")}); def->cli = ConfigOptionDef::nocli; + def = this->add("spoolman_spool_id", coInt); + 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 = comAdvanced; + def->set_default_value(new ConfigOptionInt()); + def->cli = ConfigOptionDef::nocli; + + def = this->add("spoolman_remaining_weight", coFloat); + def->label = L("Remaining Weight"); + def->tooltip = L("Remaining weight of the spool"); + def->mode = comAdvanced; + def->readonly = true; + def->set_default_value(new ConfigOptionFloat()); + def->cli = ConfigOptionDef::nocli; + + def = this->add("spoolman_used_weight", coFloat); + def->label = L("Used Weight"); + def->tooltip = L("Used weight of the spool"); + def->mode = comAdvanced; + def->readonly = true; + def->set_default_value(new ConfigOptionFloat()); + def->cli = ConfigOptionDef::nocli; + + def = this->add("spoolman_remaining_length", coFloat); + def->label = L("Remaining Length"); + def->tooltip = L("Remaining length of the filament on the spool"); + def->mode = comAdvanced; + def->readonly = true; + def->set_default_value(new ConfigOptionFloat()); + def->cli = ConfigOptionDef::nocli; + + def = this->add("spoolman_used_length", coFloat); + def->label = L("Used Length"); + def->tooltip = L("Used length of the filament from the spool"); + def->mode = comAdvanced; + def->readonly = true; + def->set_default_value(new ConfigOptionFloat()); + def->cli = ConfigOptionDef::nocli; + + def = this->add("spoolman_archived", coBool); + def->label = L("Archived"); + def->tooltip = L("Indicates if the spool is archived"); + def->mode = comAdvanced; + def->readonly = true; + def->set_default_value(new ConfigOptionBool()); + def->cli = ConfigOptionDef::nocli; + def = this->add("infill_direction", coFloat); def->label = L("Infill direction"); def->category = L("Strength"); diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 0b1526f1ee..150fd14a3e 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -528,6 +528,8 @@ set(SLIC3R_GUI_SOURCES GUI/PrinterCloudAuthDialog.hpp Utils/Obico.cpp Utils/Obico.hpp + Utils/Spoolman.cpp + Utils/Spoolman.hpp ) if (WIN32) diff --git a/src/slic3r/GUI/CreatePresetsDialog.cpp b/src/slic3r/GUI/CreatePresetsDialog.cpp index 91a0a62108..8d798deea6 100644 --- a/src/slic3r/GUI/CreatePresetsDialog.cpp +++ b/src/slic3r/GUI/CreatePresetsDialog.cpp @@ -113,7 +113,7 @@ static std::set cannot_input_key = {9, 10, 13, 33, 35, 36, 37, 38, 40, 41, static std::set special_key = {'\n', '\t', '\r', '\v', '@', ';'}; -static std::string remove_special_key(const std::string &str) +std::string remove_special_key(const std::string &str) { std::string res_str; for (char c : str) { @@ -399,7 +399,7 @@ static std::string calculate_md5(const std::string &input) return md5; } -static std::string get_filament_id(std::string vendor_typr_serial) +std::string get_filament_id(std::string vendor_typr_serial) { std::unordered_map> filament_id_to_filament_name; diff --git a/src/slic3r/GUI/CreatePresetsDialog.hpp b/src/slic3r/GUI/CreatePresetsDialog.hpp index 9c79bc71f3..2e7afca5b7 100644 --- a/src/slic3r/GUI/CreatePresetsDialog.hpp +++ b/src/slic3r/GUI/CreatePresetsDialog.hpp @@ -16,6 +16,9 @@ namespace Slic3r { namespace GUI { +std::string remove_special_key(const std::string &str); +std::string get_filament_id(std::string vendor_typr_serial); + class CreateFilamentPresetDialog : public DPIDialog { public: diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 7b75464496..b5e26315e7 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -122,7 +122,7 @@ PhysicalPrinterDialog::~PhysicalPrinterDialog() void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup) { m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (opt_key == "host_type" || opt_key == "printhost_authorization_type") + if (opt_key == "host_type" || opt_key == "printhost_authorization_type" || opt_key == "spoolman_enabled") this->update(); if (opt_key == "print_host") this->update_printhost_buttons(); @@ -132,6 +132,12 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr m_optgroup->append_single_option_line("host_type"); + m_optgroup->append_single_option_line("spoolman_enabled"); + + Option option = m_optgroup->get_option("spoolman_port"); + option.opt.width = Field::def_width_wider(); + m_optgroup->append_single_option_line(option); + auto create_sizer_with_btn = [](wxWindow* parent, ScalableButton** btn, const std::string& icon_name, const wxString& label) { *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); (*btn)->SetFont(wxGetApp().normal_font()); @@ -210,7 +216,7 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr }; // Set a wider width for a better alignment - Option option = m_optgroup->get_option("print_host"); + option = m_optgroup->get_option("print_host"); option.opt.width = Field::def_width_wider(); Line host_line = m_optgroup->create_single_option_line(option); host_line.append_widget(printhost_browse); @@ -512,6 +518,15 @@ void PhysicalPrinterDialog::update(bool printer_change) } } } + } else if (opt->value == htOctoPrint) { + m_optgroup->show_field("spoolman_enabled"); + m_optgroup->show_field("spoolman_port", m_config->opt_bool("spoolman_enabled")); + } else { + m_config->set("spoolman_enabled", false); + m_optgroup->hide_field("spoolman_enabled"); + + m_config->set("spoolman_port", m_optgroup->get_option("spoolman_port").opt.get_default_value()->value); + m_optgroup->hide_field("spoolman_port"); } } else { diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index b1f4dbbf0f..c0958e7669 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3273,6 +3273,17 @@ void TabFilament::build() option.opt.height = gcode_field_height;// 150; optgroup->append_single_option_line(option); + page = add_options_page(L("Spoolman"), "advanced"); + optgroup = page->new_optgroup("Basic information"); + optgroup->append_single_option_line("spoolman_spool_id"); + + optgroup = page->new_optgroup("Spool Statistics"); + optgroup->append_single_option_line("spoolman_remaining_weight"); + optgroup->append_single_option_line("spoolman_used_weight"); + optgroup->append_single_option_line("spoolman_remaining_length"); + optgroup->append_single_option_line("spoolman_used_length"); + optgroup->append_single_option_line("spoolman_archived"); + page = add_options_page(L("Multimaterial"), "advanced"); optgroup = page->new_optgroup(L("Wipe tower parameters")); optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower"); diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp new file mode 100644 index 0000000000..3ebe1fb6a9 --- /dev/null +++ b/src/slic3r/Utils/Spoolman.cpp @@ -0,0 +1,262 @@ +#include +#include +#include +#include +#include +#include "Spoolman.hpp" +#include "Http.hpp" + +namespace Slic3r { + +namespace { +template Type get_opt(pt::ptree& data, string path) { return data.get_optional(path).value_or(Type()); } +} // namespace + +//--------------------------------- +// Spoolman +//--------------------------------- + +static vector statistics_keys = {"spoolman_remaining_weight", "spoolman_used_weight", "spoolman_remaining_length", + "spoolman_used_length", "spoolman_archived"}; + +pt::ptree Spoolman::get_spoolman_json(const string& api_endpoint) +{ + DynamicPrintConfig& config = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config; + string host = config.opt_string("print_host"); + string spoolman_host = config.opt_string("spoolman_port"); + string spoolman_port; + + if (auto idx = spoolman_host.find_last_of(':'); idx != string::npos) { + boost::regex pattern("(?[a-zA-Z0-9.]+):(?[0-9]+)"); + boost::smatch result; + boost::regex_search(spoolman_host, result, pattern); + spoolman_port = result["port"]; // get port value first since it is overwritten when setting the host value in the next line + spoolman_host = result["host"]; + } else if (regex_match(spoolman_host, regex("^[0-9]+"))) { + spoolman_port = spoolman_host; + spoolman_host.clear(); + } else if (auto idx = host.find_last_of(':'); idx != string::npos) + host = host.erase(idx); + + if (spoolman_host.empty()) + spoolman_host = host; + + auto url = spoolman_host + ":" + spoolman_port + "/api/v1/" + api_endpoint; + auto http = Http::get(url); + + bool res; + std::string res_body; + + http.on_error([&](const std::string& body, std::string error, unsigned status) { + string msg = "Failed to get data from the Spoolman server. Make sure that the port is correct and the server is running."; + BOOST_LOG_TRIVIAL(error) << msg << boost::format(" HTTP Error: %1%, HTTP status code: %2%") % error % status; + show_error(nullptr, msg); + res = false; + }) + .on_complete([&](std::string body, unsigned) { + res_body = std::move(body); + res = true; + }) + .perform_sync(); + + if (!res) + return {}; + + if (res_body.empty()) { + BOOST_LOG_TRIVIAL(info) << "Spoolman request returned an empty string"; + return {}; + } + + pt::ptree tree; + try { + stringstream ss(res_body); + pt::read_json(ss, tree); + } catch (std::exception& exception) { + BOOST_LOG_TRIVIAL(error) << "Failed to read json into property tree. Exception: " << exception.what(); + return {}; + } + + return tree; +} + +bool Spoolman::pull_spoolman_spools() +{ + pt::ptree tree; + + m_vendors.clear(); + m_filaments.clear(); + m_spools.clear(); + + // Vendor + tree = get_spoolman_json("vendor"); + if (tree.empty()) + return false; + for (const auto& item : tree) + m_vendors.emplace(item.second.get("id"), make_shared(SpoolmanVendor(item.second))); + + // Filament + tree = get_spoolman_json("filament"); + if (tree.empty()) + return false; + for (const auto& item : tree) + m_filaments.emplace(item.second.get("id"), make_shared(SpoolmanFilament(item.second))); + + // Spool + tree = get_spoolman_json("spool"); + if (tree.empty()) + return false; + for (const auto& item : tree) + m_spools.emplace(item.second.get("id"), make_shared(SpoolmanSpool(item.second))); + + return true; +} + +bool Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile) +{ + PresetBundle* preset_bundle = wxGetApp().preset_bundle; + PresetCollection& filaments = preset_bundle->filaments; + string filament_preset_name = remove_special_key(spool->getVendor()->name + " " + spool->m_filament_ptr->name + " " + + spool->m_filament_ptr->material); + Preset* preset = filaments.find_preset(filament_preset_name); + if (preset) { + BOOST_LOG_TRIVIAL(error) << "Preset already exists with the name " << filament_preset_name; + return false; + } + string user_filament_id = get_filament_id(filament_preset_name); + vector failures; + DynamicConfig config; + config.set_key_value("filament_vendor", new ConfigOptionStrings({spool->getVendor()->name})); + config.set_key_value("compatible_printers", base_profile->config.option("compatible_printers")->clone()); + config.set_key_value("filament_type", new ConfigOptionStrings({spool->m_filament_ptr->material})); + //TODO: replace this clone function with a manual implementation that leaves inheritance intact + filaments.clone_presets_for_filament(base_profile, failures, filament_preset_name, user_filament_id, config, ""); + if (!failures.empty()) { + BOOST_LOG_TRIVIAL(error) << "Failed to clone filament preset: " << failures.at(0); + return false; + } + preset = filaments.find_preset(filament_preset_name); + if (!preset) { + BOOST_LOG_TRIVIAL(error) << "Spoolman: The returned preset was a nullptr. Attempted to find the preset " + filament_preset_name; + return false; + } + config.clear(); + spool->apply_to_config(config); + preset->config.apply(config); + preset->save(nullptr); + return true; +} + +bool Spoolman::update_filament_preset_from_spool(Preset* filament_preset, bool only_update_statistics) +{ + DynamicConfig config; + const int& spool_id = filament_preset->config.opt_int("spoolman_spool_id"); + if (spool_id < 1) + return false; // IDs below 1 are not used by spoolman and should be ignored + get_instance()->m_spools[spool_id]->apply_to_config(config); + filament_preset->config.apply_only(config, only_update_statistics ? statistics_keys : config.keys()); + return true; +} + +//--------------------------------- +// SpoolmanVendor +//--------------------------------- + +void SpoolmanVendor::update_from_server() { update_from_json(Spoolman::get_spoolman_json("vendor/" + std::to_string(id))); } + +void SpoolmanVendor::update_from_json(pt::ptree json_data) +{ + id = json_data.get("id"); + name = get_opt(json_data, "name"); +} + +void SpoolmanVendor::apply_to_config(Slic3r::DynamicConfig& config) const +{ + config.set_key_value("filament_vendor", new ConfigOptionStrings({name})); +} + +//--------------------------------- +// SpoolmanFilament +//--------------------------------- + +void SpoolmanFilament::update_from_server(bool recursive) +{ + const boost::property_tree::ptree& json_data = Spoolman::get_spoolman_json("filament/" + std::to_string(id)); + update_from_json(json_data); + if (recursive) + m_vendor_ptr->update_from_json(json_data.get_child("vendor")); +} + +void SpoolmanFilament::update_from_json(pt::ptree json_data) +{ + if (int vendor_id = json_data.get("vendor.id"); m_vendor_ptr && m_vendor_ptr->id != vendor_id) { + if (!m_spoolman->m_vendors.count(vendor_id)) + m_spoolman->m_vendors.emplace(vendor_id, make_shared(SpoolmanVendor(json_data.get_child("vendor")))); + m_vendor_ptr = m_spoolman->m_vendors[vendor_id]; + } + id = json_data.get("id"); + name = get_opt(json_data, "name"); + material = get_opt(json_data, "material"); + price = get_opt(json_data, "price"); + density = get_opt(json_data, "density"); + diameter = get_opt(json_data, "diameter"); + article_number = get_opt(json_data, "article_number"); + extruder_temp = get_opt(json_data, "settings_extruder_temp"); + bed_temp = get_opt(json_data, "settings_bed_temp"); + color = "#" + get_opt(json_data, "color_hex"); +} + +void SpoolmanFilament::apply_to_config(Slic3r::DynamicConfig& config) const +{ + config.set_key_value("filament_type", new ConfigOptionStrings({material})); + config.set_key_value("filament_cost", new ConfigOptionFloats({price})); + config.set_key_value("filament_density", new ConfigOptionFloats({density})); + config.set_key_value("filament_diameter", new ConfigOptionFloats({diameter})); + config.set_key_value("nozzle_temperature_initial_layer", new ConfigOptionInts({extruder_temp + 5})); + config.set_key_value("nozzle_temperature", new ConfigOptionInts({extruder_temp})); + config.set_key_value("hot_plate_temp_initial_layer", new ConfigOptionInts({bed_temp + 5})); + config.set_key_value("hot_plate_temp", new ConfigOptionInts({bed_temp})); + config.set_key_value("default_filament_colour", new ConfigOptionStrings{color}); + m_vendor_ptr->apply_to_config(config); +} + +//--------------------------------- +// SpoolmanSpool +//--------------------------------- + +void SpoolmanSpool::update_from_server(bool recursive) +{ + const boost::property_tree::ptree& json_data = Spoolman::get_spoolman_json("spool/" + std::to_string(id)); + update_from_json(json_data); + if (recursive) { + m_filament_ptr->update_from_json(json_data.get_child("filament")); + getVendor()->update_from_json(json_data.get_child("filament.vendor")); + } +} + +void SpoolmanSpool::apply_to_config(Slic3r::DynamicConfig& config) const +{ + config.set_key_value("spoolman_spool_id", new ConfigOptionInt(id)); + config.set_key_value("spoolman_remaining_weight", new ConfigOptionFloat(remaining_weight)); + config.set_key_value("spoolman_used_weight", new ConfigOptionFloat(used_weight)); + config.set_key_value("spoolman_remaining_length", new ConfigOptionFloat(remaining_length)); + config.set_key_value("spoolman_used_length", new ConfigOptionFloat(used_length)); + config.set_key_value("spoolman_archived", new ConfigOptionBool(archived)); + m_filament_ptr->apply_to_config(config); +} + +void SpoolmanSpool::update_from_json(pt::ptree json_data) +{ + if (int filament_id = json_data.get("filament.id"); m_filament_ptr && m_filament_ptr->id != filament_id) { + if (!m_spoolman->m_filaments.count(filament_id)) + m_spoolman->m_filaments.emplace(filament_id, make_shared(SpoolmanFilament(json_data.get_child("filament")))); + m_filament_ptr = m_spoolman->m_filaments.at(filament_id); + } + id = json_data.get("id"); + remaining_weight = get_opt(json_data, "remaining_weight"); + used_weight = get_opt(json_data, "used_weight"); + remaining_length = get_opt(json_data, "remaining_length"); + used_length = get_opt(json_data, "used_length"); + archived = get_opt(json_data, "archived"); +} + +} // namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp new file mode 100644 index 0000000000..fbd4b5d156 --- /dev/null +++ b/src/slic3r/Utils/Spoolman.hpp @@ -0,0 +1,166 @@ +#ifndef SLIC3R_SPOOLMAN_HPP +#define SLIC3R_SPOOLMAN_HPP + +#include + +namespace pt = boost::property_tree; + +namespace Slic3r { + +class SpoolmanVendor; +class SpoolmanFilament; +class SpoolmanSpool; + +typedef shared_ptr SpoolmanVendorShrPtr; +typedef shared_ptr SpoolmanFilamentShrPtr; +typedef shared_ptr SpoolmanSpoolShrPtr; + +/// Contains routines to get the data from the Spoolman server, save as Spoolman data containers, and create presets from them. +/// The Spoolman data classes can only be accessed/instantiated by this class. +/// An instance of this class can only be accessed via the get_instance() function. +class Spoolman +{ + inline static Spoolman* m_instance{nullptr}; + + bool m_initialized{false}; + + std::map m_vendors{}; + std::map m_filaments{}; + std::map m_spools{}; + + Spoolman() + { + m_instance = this; + m_initialized = pull_spoolman_spools(); + }; + + static pt::ptree get_spoolman_json(const string& api_endpoint); + /// get all the spools from the api and store them + bool pull_spoolman_spools(); + +public: + // returns true if the operation was successful and false for any errors/issues + static bool create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile); + // returns true if the operation was successful and false for any errors/issues + static bool update_filament_preset_from_spool(Preset* filament_preset, bool only_update_statistics = false); + + const std::map& get_spoolman_spools(bool update = false) + { + if (update || !m_initialized) + m_initialized = pull_spoolman_spools(); + return m_spools; + }; + + SpoolmanSpoolShrPtr get_spoolman_spool_by_id(int spool_id, bool update = false) + { + if (update || !m_initialized) + m_initialized = pull_spoolman_spools(); + return m_spools[spool_id]; + }; + + static Spoolman* get_instance() + { + if (!m_instance) + new Spoolman(); + return m_instance; + }; + + friend class SpoolmanVendor; + friend class SpoolmanFilament; + friend class SpoolmanSpool; +}; + +/// Vendor: The vendor name +class SpoolmanVendor +{ +public: + int id; + string name; + + void update_from_server(); + +private: + Spoolman* m_spoolman; + + explicit SpoolmanVendor(const pt::ptree& json_data) : m_spoolman(Spoolman::m_instance) { update_from_json(json_data); }; + + void update_from_json(pt::ptree json_data); + void apply_to_config(Slic3r::DynamicConfig& config) const; + + friend class Spoolman; + friend class SpoolmanFilament; + friend class SpoolmanSpool; +}; + +/// Filament: Contains data about a type of filament, including the material, weight, price, +/// etc. You can have multiple spools of one type of filament +class SpoolmanFilament +{ +public: + int id; + string name; + string material; + float price; + float density; + float diameter; + string article_number; + int extruder_temp; + int bed_temp; + string color; + + SpoolmanVendorShrPtr m_vendor_ptr; + + void update_from_server(bool recursive = false); + +private: + Spoolman* m_spoolman; + + explicit SpoolmanFilament(const pt::ptree& json_data) : m_spoolman(Spoolman::m_instance) + { + m_vendor_ptr = m_spoolman->m_vendors[json_data.get("vendor.id")]; + update_from_json(json_data); + }; + + void update_from_json(pt::ptree json_data); + void apply_to_config(Slic3r::DynamicConfig& config) const; + + friend class Spoolman; + friend class SpoolmanVendor; + friend class SpoolmanSpool; +}; + +/// Spool: Contains data on the used and remaining amounts of filament +class SpoolmanSpool +{ +public: + int id; + float remaining_weight; + float used_weight; + float remaining_length; + float used_length; + bool archived; + + SpoolmanFilamentShrPtr m_filament_ptr; + + SpoolmanVendorShrPtr& getVendor() { return m_filament_ptr->m_vendor_ptr; }; + + void update_from_server(bool recursive = false); + + void apply_to_config(DynamicConfig& config) const; + +private: + Spoolman* m_spoolman; + + explicit SpoolmanSpool(const pt::ptree& json_data) : m_spoolman(Spoolman::m_instance) + { + m_filament_ptr = m_spoolman->m_filaments[json_data.get("filament.id")]; + update_from_json(json_data); + } + + void update_from_json(pt::ptree json_data); + + friend class Spoolman; +}; + +} // namespace Slic3r +#endif // SLIC3R_SPOOLMAN_HPP From 1166e4ee7ab00c22b58099c40e866e36bac5495b Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 20 Mar 2024 05:47:26 -0400 Subject: [PATCH 002/100] Hide Spoolman tab when Spoolman is not enabled --- src/slic3r/GUI/Tab.cpp | 6 +++++- src/slic3r/GUI/Tab.hpp | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index c0958e7669..57f5c72fea 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3284,6 +3284,10 @@ void TabFilament::build() optgroup->append_single_option_line("spoolman_used_length"); optgroup->append_single_option_line("spoolman_archived"); + page->m_should_show_fn = [&](bool current_value) { + return m_preset_bundle->printers.get_edited_preset().config.opt_bool("spoolman_enabled"); + }; + page = add_options_page(L("Multimaterial"), "advanced"); optgroup = page->new_optgroup(L("Wipe tower parameters")); optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower"); @@ -5739,7 +5743,7 @@ void Page::update_visibility(ConfigOptionMode mode, bool update_contolls_visibil #endif } - m_show = ret_val; + m_show = m_should_show_fn ? m_should_show_fn(ret_val) : ret_val; #ifdef __WXMSW__ if (!m_show) return; // BBS: fix field control position diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index f04f303360..48f058376c 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -92,6 +92,10 @@ public: bool m_split_multi_line = false; bool m_option_label_at_right = false; + // Orca: allow the show value to be overridden by a callback function + // The function provides a bool parameter that is set to the current decision on whether it should be shown + std::function m_should_show_fn = 0; + public: std::vector m_optgroups; DynamicPrintConfig* m_config; From 5895e557a697aad01f6823d498d1f905a5d0afc2 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 20 Mar 2024 22:46:14 -0400 Subject: [PATCH 003/100] Update the create filament function --- src/libslic3r/Preset.hpp | 3 +++ src/slic3r/Utils/Spoolman.cpp | 43 ++++++++++++++++------------------- src/slic3r/Utils/Spoolman.hpp | 2 +- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index b03ce605e3..b418e8f7e9 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -773,6 +773,9 @@ private: // Orca: used for validation only int m_errors = 0; + + // Orca: to access m_presets to manually insert a newly created preset + friend class Spoolman; }; // Printer supports the FFF and SLA technologies, with different set of configuration values, diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 3ebe1fb6a9..2715a6b43c 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -111,38 +111,35 @@ bool Spoolman::pull_spoolman_spools() return true; } -bool Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile) +bool Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, Preset* base_profile) { PresetBundle* preset_bundle = wxGetApp().preset_bundle; PresetCollection& filaments = preset_bundle->filaments; string filament_preset_name = remove_special_key(spool->getVendor()->name + " " + spool->m_filament_ptr->name + " " + spool->m_filament_ptr->material); - Preset* preset = filaments.find_preset(filament_preset_name); + string user_filament_id = get_filament_id(filament_preset_name); + + // Check if the preset already exists + Preset* preset = filaments.find_preset(filament_preset_name); if (preset) { BOOST_LOG_TRIVIAL(error) << "Preset already exists with the name " << filament_preset_name; return false; } - string user_filament_id = get_filament_id(filament_preset_name); - vector failures; - DynamicConfig config; - config.set_key_value("filament_vendor", new ConfigOptionStrings({spool->getVendor()->name})); - config.set_key_value("compatible_printers", base_profile->config.option("compatible_printers")->clone()); - config.set_key_value("filament_type", new ConfigOptionStrings({spool->m_filament_ptr->material})); - //TODO: replace this clone function with a manual implementation that leaves inheritance intact - filaments.clone_presets_for_filament(base_profile, failures, filament_preset_name, user_filament_id, config, ""); - if (!failures.empty()) { - BOOST_LOG_TRIVIAL(error) << "Failed to clone filament preset: " << failures.at(0); - return false; - } - preset = filaments.find_preset(filament_preset_name); - if (!preset) { - BOOST_LOG_TRIVIAL(error) << "Spoolman: The returned preset was a nullptr. Attempted to find the preset " + filament_preset_name; - return false; - } - config.clear(); - spool->apply_to_config(config); - preset->config.apply(config); - preset->save(nullptr); + + // Insert a new preset in the sorted location + auto it = filaments.find_preset_internal(filament_preset_name); + preset = &*filaments.m_presets.emplace(it, Preset::TYPE_FILAMENT, filament_preset_name); + + // Apply config values from base profile and spool then save + preset->config.apply(base_profile->config); + preset->config.set_key_value("filament_settings_id", new ConfigOptionStrings({filament_preset_name})); + preset->config.set("inherits", base_profile->name, true); + spool->apply_to_config(preset->config); + preset->filament_id = user_filament_id; + preset->version = base_profile->version; + preset->file = filaments.path_for_preset(*preset); + preset->save(&base_profile->config); + return true; } diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index fbd4b5d156..1ea8415a2d 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -40,7 +40,7 @@ class Spoolman public: // returns true if the operation was successful and false for any errors/issues - static bool create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile); + static bool create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, Preset *base_profile); // returns true if the operation was successful and false for any errors/issues static bool update_filament_preset_from_spool(Preset* filament_preset, bool only_update_statistics = false); From 9648e8f5d94a81805a29e957629a78abedb5a5ed Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 20 Mar 2024 23:18:25 -0400 Subject: [PATCH 004/100] Add update from server functionality to preset update fn --- src/slic3r/Utils/Spoolman.cpp | 8 ++++++-- src/slic3r/Utils/Spoolman.hpp | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 2715a6b43c..0a2b9abb1d 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -143,13 +143,17 @@ bool Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spoo return true; } -bool Spoolman::update_filament_preset_from_spool(Preset* filament_preset, bool only_update_statistics) +bool Spoolman::update_filament_preset_from_spool(Preset* filament_preset, bool update_from_server, bool only_update_statistics) { DynamicConfig config; const int& spool_id = filament_preset->config.opt_int("spoolman_spool_id"); if (spool_id < 1) return false; // IDs below 1 are not used by spoolman and should be ignored - get_instance()->m_spools[spool_id]->apply_to_config(config); + + SpoolmanSpoolShrPtr& spool = get_instance()->m_spools[spool_id]; + if (update_from_server) + spool->update_from_server(!only_update_statistics); + spool->apply_to_config(config); filament_preset->config.apply_only(config, only_update_statistics ? statistics_keys : config.keys()); return true; } diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index 1ea8415a2d..d588c188aa 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -42,7 +42,7 @@ public: // returns true if the operation was successful and false for any errors/issues static bool create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, Preset *base_profile); // returns true if the operation was successful and false for any errors/issues - static bool update_filament_preset_from_spool(Preset* filament_preset, bool only_update_statistics = false); + static bool update_filament_preset_from_spool(Preset* filament_preset, bool update_from_server, bool only_update_statistics); const std::map& get_spoolman_spools(bool update = false) { From d697567571d14dfac7b1f75d99c2a609e61c0bdf Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Thu, 21 Mar 2024 07:42:17 -0400 Subject: [PATCH 005/100] Add Buttons to Update Spools --- src/slic3r/GUI/Tab.cpp | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6119401184..79e6b40930 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -67,7 +67,7 @@ #include "Search.hpp" #include "BedShapeDialog.hpp" -#include "BedShapeDialog.hpp" +#include "Spoolman.hpp" // #include "BonjourDialog.hpp" #ifdef WIN32 #include @@ -3286,6 +3286,36 @@ void TabFilament::build() optgroup = page->new_optgroup("Basic information"); optgroup->append_single_option_line("spoolman_spool_id"); + line = {"Spoolman Update", ""}; + line.append_option(Option(ConfigOptionDef(), "")); + line.widget = [&](wxWindow* parent){ + auto sizer = new wxBoxSizer(wxHORIZONTAL); + + auto on_click = [&](bool stats_only) { + if (m_presets->current_is_dirty() && m_active_page->get_field("spoolman_spool_id")->m_is_modified_value) { + show_error(this, "This profile cannot be updated with an unsaved Spool ID value. Please save the profile, then try updating again."); + return; + } + Spoolman::update_filament_preset_from_spool(&m_presets->get_edited_preset(), true, stats_only); + Spoolman::update_filament_preset_from_spool(&m_presets->get_selected_preset(), false, stats_only); + const Preset* preset = m_presets->find_preset(m_presets->get_edited_preset().inherits()); + m_presets->get_selected_preset().save(preset ? &preset->config : nullptr); + update_dirty(); + }; + + auto refresh_all_btn = new wxButton(parent, wxID_ANY, _L("Update Filament")); + refresh_all_btn->Bind(wxEVT_BUTTON, [on_click](wxCommandEvent& evt) { on_click(false); }); + wxGetApp().UpdateDarkUI(refresh_all_btn); + sizer->Add(refresh_all_btn); + + auto refresh_stats_btn = new wxButton(parent, wxID_ANY, _L("Update Stats")); + refresh_stats_btn->Bind(wxEVT_BUTTON, [on_click](wxCommandEvent& evt) { on_click(true); }); + wxGetApp().UpdateDarkUI(refresh_stats_btn); + sizer->Add(refresh_stats_btn); + return sizer; + }; + optgroup->append_line(line); + optgroup = page->new_optgroup("Spool Statistics"); optgroup->append_single_option_line("spoolman_remaining_weight"); optgroup->append_single_option_line("spoolman_used_weight"); From 4698488a0d4d6f4a9811dee096de29b0b0c09296 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Thu, 21 Mar 2024 07:59:37 -0400 Subject: [PATCH 006/100] Modify the Preset::save function make the config argument const. Allows more flexibility when calling it and in its current implementation, non-const access is not needed. --- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Preset.hpp | 2 +- src/slic3r/GUI/Tab.cpp | 2 +- src/slic3r/Utils/Spoolman.cpp | 2 +- src/slic3r/Utils/Spoolman.hpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 2525de552f..8f72dec9ee 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -502,7 +502,7 @@ void Preset::remove_files() } //BBS: add logic for only difference save -void Preset::save(DynamicPrintConfig* parent_config) +void Preset::save(const DynamicPrintConfig* parent_config) { //BBS: add project embedded preset logic if (this->is_project_embedded) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index b418e8f7e9..9b631cfce0 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -253,7 +253,7 @@ public: //BBS: add logic for only difference save //if parent_config is null, save all keys, otherwise, only save difference - void save(DynamicPrintConfig* parent_config); + void save(const DynamicPrintConfig* parent_config); void reload(Preset const & parent); // Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty. diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 79e6b40930..0c17f7e64e 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3298,7 +3298,7 @@ void TabFilament::build() } Spoolman::update_filament_preset_from_spool(&m_presets->get_edited_preset(), true, stats_only); Spoolman::update_filament_preset_from_spool(&m_presets->get_selected_preset(), false, stats_only); - const Preset* preset = m_presets->find_preset(m_presets->get_edited_preset().inherits()); + const Preset* preset = m_presets->get_selected_preset_parent(); m_presets->get_selected_preset().save(preset ? &preset->config : nullptr); update_dirty(); }; diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 0a2b9abb1d..4272eb3f2c 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -111,7 +111,7 @@ bool Spoolman::pull_spoolman_spools() return true; } -bool Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, Preset* base_profile) +bool Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile) { PresetBundle* preset_bundle = wxGetApp().preset_bundle; PresetCollection& filaments = preset_bundle->filaments; diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index d588c188aa..b88794dcd4 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -40,7 +40,7 @@ class Spoolman public: // returns true if the operation was successful and false for any errors/issues - static bool create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, Preset *base_profile); + static bool create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile); // returns true if the operation was successful and false for any errors/issues static bool update_filament_preset_from_spool(Preset* filament_preset, bool update_from_server, bool only_update_statistics); From 74c156713ec7091f8688e1ca4f8b5b6b7c415444 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Thu, 21 Mar 2024 08:29:15 -0400 Subject: [PATCH 007/100] Use a different method of saving new preset --- src/libslic3r/Preset.hpp | 3 --- src/slic3r/Utils/Spoolman.cpp | 8 ++------ 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 9b631cfce0..e927abc008 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -773,9 +773,6 @@ private: // Orca: used for validation only int m_errors = 0; - - // Orca: to access m_presets to manually insert a newly created preset - friend class Spoolman; }; // Printer supports the FFF and SLA technologies, with different set of configuration values, diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 4272eb3f2c..7f716678fe 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -126,11 +126,7 @@ bool Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spoo return false; } - // Insert a new preset in the sorted location - auto it = filaments.find_preset_internal(filament_preset_name); - preset = &*filaments.m_presets.emplace(it, Preset::TYPE_FILAMENT, filament_preset_name); - - // Apply config values from base profile and spool then save + preset = new Preset( Preset::TYPE_FILAMENT, filament_preset_name); preset->config.apply(base_profile->config); preset->config.set_key_value("filament_settings_id", new ConfigOptionStrings({filament_preset_name})); preset->config.set("inherits", base_profile->name, true); @@ -138,7 +134,7 @@ bool Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spoo preset->filament_id = user_filament_id; preset->version = base_profile->version; preset->file = filaments.path_for_preset(*preset); - preset->save(&base_profile->config); + filaments.save_current_preset(filament_preset_name, false, false, preset); return true; } From 38f59113b295d75aa8472197c2c7016dea182e83 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Thu, 21 Mar 2024 09:12:37 -0400 Subject: [PATCH 008/100] Add label to filament preset combobox --- src/slic3r/GUI/PresetComboBoxes.cpp | 9 +++++++-- src/slic3r/GUI/PresetComboBoxes.hpp | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index d037a62394..080981c1ea 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -756,6 +756,9 @@ void PlaterPresetComboBox::OnSelect(wxCommandEvent &evt) evt.StopPropagation(); if (marker == LABEL_ITEM_MARKER) return; + if (marker == LABEL_ITEM_IMPORT_SPOOLMAN) { + return; + } //if (marker == LABEL_ITEM_WIZARD_PRINTERS) // show_add_menu(); //else { @@ -1081,9 +1084,11 @@ void PlaterPresetComboBox::update() wxBitmap* bmp = get_bmp("edit_preset_list", wide_icons, "edit_uni"); assert(bmp); - if (m_type == Preset::TYPE_FILAMENT) + if (m_type == Preset::TYPE_FILAMENT) { set_label_marker(Append(separator(L("Add/Remove filaments")), *bmp), LABEL_ITEM_WIZARD_FILAMENTS); - else if (m_type == Preset::TYPE_SLA_MATERIAL) + if (m_preset_bundle->printers.get_edited_preset().config.opt_bool("spoolman_enabled")) + set_label_marker(Append(separator(L("Import filament from Spoolman")), *bmp), LABEL_ITEM_IMPORT_SPOOLMAN); + } else if (m_type == Preset::TYPE_SLA_MATERIAL) set_label_marker(Append(separator(L("Add/Remove materials")), *bmp), LABEL_ITEM_WIZARD_MATERIALS); else { set_label_marker(Append(separator(L("Select/Remove printers(system presets)")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 954c06862f..77cab3bf13 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -47,6 +47,7 @@ public: LABEL_ITEM_WIZARD_FILAMENTS, LABEL_ITEM_WIZARD_MATERIALS, LABEL_ITEM_WIZARD_ADD_PRINTERS, + LABEL_ITEM_IMPORT_SPOOLMAN, LABEL_ITEM_MAX, }; From 5b793f5e1a0ced3d1d5aec34d51974cf41e416b8 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Thu, 21 Mar 2024 11:00:16 -0400 Subject: [PATCH 009/100] Use std:: prefix in header --- src/slic3r/Utils/Spoolman.hpp | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index b88794dcd4..3c1b8611fa 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -6,14 +6,15 @@ namespace pt = boost::property_tree; namespace Slic3r { +class Preset; class SpoolmanVendor; class SpoolmanFilament; class SpoolmanSpool; -typedef shared_ptr SpoolmanVendorShrPtr; -typedef shared_ptr SpoolmanFilamentShrPtr; -typedef shared_ptr SpoolmanSpoolShrPtr; +typedef std::shared_ptr SpoolmanVendorShrPtr; +typedef std::shared_ptr SpoolmanFilamentShrPtr; +typedef std::shared_ptr SpoolmanSpoolShrPtr; /// Contains routines to get the data from the Spoolman server, save as Spoolman data containers, and create presets from them. /// The Spoolman data classes can only be accessed/instantiated by this class. @@ -30,11 +31,11 @@ class Spoolman Spoolman() { - m_instance = this; + m_instance = this; m_initialized = pull_spoolman_spools(); }; - static pt::ptree get_spoolman_json(const string& api_endpoint); + static pt::ptree get_spoolman_json(const std::string& api_endpoint); /// get all the spools from the api and store them bool pull_spoolman_spools(); @@ -74,8 +75,8 @@ public: class SpoolmanVendor { public: - int id; - string name; + int id; + std::string name; void update_from_server(); @@ -97,16 +98,16 @@ private: class SpoolmanFilament { public: - int id; - string name; - string material; - float price; - float density; - float diameter; - string article_number; - int extruder_temp; - int bed_temp; - string color; + int id; + std::string name; + std::string material; + float price; + float density; + float diameter; + std::string article_number; + int extruder_temp; + int bed_temp; + std::string color; SpoolmanVendorShrPtr m_vendor_ptr; From ee50f2877d6424331352a977258d1d338c9eff28 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Mon, 25 Mar 2024 00:28:43 -0400 Subject: [PATCH 010/100] Change to SpoolmanResult return for Spoolman functions --- src/slic3r/Utils/Spoolman.cpp | 58 +++++++++++++++++++++++++++-------- src/slic3r/Utils/Spoolman.hpp | 11 +++++-- 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 7f716678fe..c83a55d54f 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -111,22 +111,53 @@ bool Spoolman::pull_spoolman_spools() return true; } -bool Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile) +SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile) { PresetBundle* preset_bundle = wxGetApp().preset_bundle; PresetCollection& filaments = preset_bundle->filaments; string filament_preset_name = remove_special_key(spool->getVendor()->name + " " + spool->m_filament_ptr->name + " " + spool->m_filament_ptr->material); string user_filament_id = get_filament_id(filament_preset_name); + SpoolmanResult result; // Check if the preset already exists Preset* preset = filaments.find_preset(filament_preset_name); if (preset) { - BOOST_LOG_TRIVIAL(error) << "Preset already exists with the name " << filament_preset_name; - return false; + std::string msg("Preset already exists with the name"); + BOOST_LOG_TRIVIAL(error) << msg << filament_preset_name; + result.messages.emplace_back(msg); } - preset = new Preset( Preset::TYPE_FILAMENT, filament_preset_name); + // Check for presets with the same spool ID + int visible(0), invisible(0); + for (const auto& item : filaments.get_presets()) { // count num of visible and invisible + if (item.config.opt_int("spoolman_spool_id") == spool->id) { + if (item.is_visible) + visible++; + else + invisible++; + } + if (visible > 1 && invisible > 1) + break; + } + // if there were any, build the message + if (visible) { + if (visible > 1) + result.messages.emplace_back("Multiple visible presets share the same spool ID"); + else + result.messages.emplace_back("A visible preset shares the same spool ID"); + } + if (invisible) { + if (invisible > 1) + result.messages.emplace_back("Multiple invisible presets share the same spool ID"); + else + result.messages.emplace_back("An invisible preset shares the same spool ID"); + } + + if (result.failure()) + return result; + + preset = new Preset(Preset::TYPE_FILAMENT, filament_preset_name); preset->config.apply(base_profile->config); preset->config.set_key_value("filament_settings_id", new ConfigOptionStrings({filament_preset_name})); preset->config.set("inherits", base_profile->name, true); @@ -136,22 +167,25 @@ bool Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spoo preset->file = filaments.path_for_preset(*preset); filaments.save_current_preset(filament_preset_name, false, false, preset); - return true; + return {}; } -bool Spoolman::update_filament_preset_from_spool(Preset* filament_preset, bool update_from_server, bool only_update_statistics) +SpoolmanResult Spoolman::update_filament_preset_from_spool(Preset* filament_preset, bool update_from_server, bool only_update_statistics) { - DynamicConfig config; - const int& spool_id = filament_preset->config.opt_int("spoolman_spool_id"); - if (spool_id < 1) - return false; // IDs below 1 are not used by spoolman and should be ignored - + DynamicConfig config; + SpoolmanResult result; + const int& spool_id = filament_preset->config.opt_int("spoolman_spool_id"); + 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 + return result; + } SpoolmanSpoolShrPtr& spool = get_instance()->m_spools[spool_id]; if (update_from_server) spool->update_from_server(!only_update_statistics); spool->apply_to_config(config); filament_preset->config.apply_only(config, only_update_statistics ? statistics_keys : config.keys()); - return true; + return result; } //--------------------------------- diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index 3c1b8611fa..a9830f5d3d 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -16,6 +16,13 @@ typedef std::shared_ptr SpoolmanVendorShrPtr; typedef std::shared_ptr SpoolmanFilamentShrPtr; typedef std::shared_ptr SpoolmanSpoolShrPtr; +struct SpoolmanResult +{ + SpoolmanResult() = default; + bool failure() { return !messages.empty(); } + std::vector messages{}; +}; + /// Contains routines to get the data from the Spoolman server, save as Spoolman data containers, and create presets from them. /// The Spoolman data classes can only be accessed/instantiated by this class. /// An instance of this class can only be accessed via the get_instance() function. @@ -41,9 +48,9 @@ class Spoolman public: // returns true if the operation was successful and false for any errors/issues - static bool create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile); + static SpoolmanResult create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile); // returns true if the operation was successful and false for any errors/issues - static bool update_filament_preset_from_spool(Preset* filament_preset, bool update_from_server, bool only_update_statistics); + static SpoolmanResult update_filament_preset_from_spool(Preset* filament_preset, bool update_from_server, bool only_update_statistics); const std::map& get_spoolman_spools(bool update = false) { From f5768e20bf57e25174d8c93da0491dc49d035d89 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Mon, 25 Mar 2024 08:38:39 -0400 Subject: [PATCH 011/100] Updates to Spoolman util class add force and detach options to create function move call to get_filament_id past failure checks in the create function. It takes a few seconds to run and it is better to not run it if you don't have to create get_name_from_spool function --- src/slic3r/Utils/Spoolman.cpp | 80 ++++++++++++++++++++--------------- src/slic3r/Utils/Spoolman.hpp | 13 ++++-- 2 files changed, 55 insertions(+), 38 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index c83a55d54f..9a5ef6da33 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -111,67 +111,74 @@ bool Spoolman::pull_spoolman_spools() return true; } -SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile) +SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, + const Preset* base_profile, + bool detach, + bool force) { - PresetBundle* preset_bundle = wxGetApp().preset_bundle; - PresetCollection& filaments = preset_bundle->filaments; - string filament_preset_name = remove_special_key(spool->getVendor()->name + " " + spool->m_filament_ptr->name + " " + - spool->m_filament_ptr->material); - string user_filament_id = get_filament_id(filament_preset_name); + PresetCollection& filaments = wxGetApp().preset_bundle->filaments; + string filament_preset_name = get_name_from_spool(spool); SpoolmanResult result; // Check if the preset already exists Preset* preset = filaments.find_preset(filament_preset_name); if (preset) { + if (force) { + update_filament_preset_from_spool(preset, true, false); + filaments.save_current_preset(preset->name, detach, false, preset); + return result; + } std::string msg("Preset already exists with the name"); BOOST_LOG_TRIVIAL(error) << msg << filament_preset_name; result.messages.emplace_back(msg); } - // Check for presets with the same spool ID - int visible(0), invisible(0); - for (const auto& item : filaments.get_presets()) { // count num of visible and invisible - if (item.config.opt_int("spoolman_spool_id") == spool->id) { - if (item.is_visible) - visible++; - else - invisible++; + if (!force) { + // 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.is_visible) + visible++; + else + invisible++; + } + if (visible > 1 && invisible > 1) + break; } - if (visible > 1 && invisible > 1) - break; + // if there were any, build the message + if (visible) { + if (visible > 1) + result.messages.emplace_back("Multiple visible presets share the same spool ID"); + else + result.messages.emplace_back("A visible preset shares the same spool ID"); + } + if (invisible) { + if (invisible > 1) + result.messages.emplace_back("Multiple invisible presets share the same spool ID"); + else + result.messages.emplace_back("An invisible preset shares the same spool ID"); + } + if (result.failure()) + return result; } - // if there were any, build the message - if (visible) { - if (visible > 1) - result.messages.emplace_back("Multiple visible presets share the same spool ID"); - else - result.messages.emplace_back("A visible preset shares the same spool ID"); - } - if (invisible) { - if (invisible > 1) - result.messages.emplace_back("Multiple invisible presets share the same spool ID"); - else - result.messages.emplace_back("An invisible preset shares the same spool ID"); - } - - if (result.failure()) - return result; preset = new Preset(Preset::TYPE_FILAMENT, filament_preset_name); preset->config.apply(base_profile->config); preset->config.set_key_value("filament_settings_id", new ConfigOptionStrings({filament_preset_name})); preset->config.set("inherits", base_profile->name, true); spool->apply_to_config(preset->config); - preset->filament_id = user_filament_id; + preset->filament_id = get_filament_id(filament_preset_name); preset->version = base_profile->version; preset->file = filaments.path_for_preset(*preset); - filaments.save_current_preset(filament_preset_name, false, false, preset); + filaments.save_current_preset(filament_preset_name, detach, false, preset); return {}; } SpoolmanResult Spoolman::update_filament_preset_from_spool(Preset* filament_preset, bool update_from_server, bool only_update_statistics) { + DynamicConfig config; SpoolmanResult result; const int& spool_id = filament_preset->config.opt_int("spoolman_spool_id"); @@ -188,6 +195,11 @@ SpoolmanResult Spoolman::update_filament_preset_from_spool(Preset* filament_pres return result; } +std::string Spoolman::get_name_from_spool(const SpoolmanSpoolShrPtr& spool) +{ + return remove_special_key(spool->getVendor()->name + " " + spool->m_filament_ptr->name + " " + spool->m_filament_ptr->material); +} + //--------------------------------- // SpoolmanVendor //--------------------------------- diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index a9830f5d3d..b3aec55ec9 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -47,10 +47,15 @@ class Spoolman bool pull_spoolman_spools(); public: - // returns true if the operation was successful and false for any errors/issues - static SpoolmanResult create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile); - // returns true if the operation was successful and false for any errors/issues - static SpoolmanResult update_filament_preset_from_spool(Preset* filament_preset, bool update_from_server, bool only_update_statistics); + static SpoolmanResult create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, + const Preset* base_profile, + bool detach = false, + bool force = false); + static SpoolmanResult update_filament_preset_from_spool(Preset* filament_preset, + bool update_from_server = true, + bool only_update_statistics = false); + + static std::string get_name_from_spool(const SpoolmanSpoolShrPtr& spool); const std::map& get_spoolman_spools(bool update = false) { From 76343ef5b69882f426df57bdcf71b52759174816 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Mon, 25 Mar 2024 10:20:31 -0400 Subject: [PATCH 012/100] Fix how "inherits" key is set Wouldn't load the filament if the "inherits" preset is not a base preset. The check is now done and it gets the base preset's parent if it needs to. --- src/slic3r/Utils/Spoolman.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 9a5ef6da33..eb14f9e1f9 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -163,17 +163,19 @@ SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolSh return result; } + std::string inherits = filaments.is_base_preset(*base_profile) ? base_profile->name : base_profile->inherits(); + preset = new Preset(Preset::TYPE_FILAMENT, filament_preset_name); preset->config.apply(base_profile->config); preset->config.set_key_value("filament_settings_id", new ConfigOptionStrings({filament_preset_name})); - preset->config.set("inherits", base_profile->name, true); + preset->config.set("inherits", inherits, true); spool->apply_to_config(preset->config); preset->filament_id = get_filament_id(filament_preset_name); preset->version = base_profile->version; - preset->file = filaments.path_for_preset(*preset); + preset->loaded = true; filaments.save_current_preset(filament_preset_name, detach, false, preset); - return {}; + return result; } SpoolmanResult Spoolman::update_filament_preset_from_spool(Preset* filament_preset, bool update_from_server, bool only_update_statistics) From cd98cba849ff20c68c90bd24308c1d090a3e6200 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Mon, 25 Mar 2024 10:42:05 -0400 Subject: [PATCH 013/100] Add SpoolmanImportDialog Added dialog itself Added as it an item on the PlaterPresetCombobox New extra renderer that renders a color in a dataview cell --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/ExtraRenderers.cpp | 14 ++ src/slic3r/GUI/ExtraRenderers.hpp | 19 ++ src/slic3r/GUI/PresetComboBoxes.cpp | 5 + src/slic3r/GUI/SpoolmanImportDialog.cpp | 315 ++++++++++++++++++++++++ src/slic3r/GUI/SpoolmanImportDialog.hpp | 144 +++++++++++ 6 files changed, 499 insertions(+) create mode 100644 src/slic3r/GUI/SpoolmanImportDialog.cpp create mode 100644 src/slic3r/GUI/SpoolmanImportDialog.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 150fd14a3e..ec50e48f17 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -530,6 +530,8 @@ set(SLIC3R_GUI_SOURCES Utils/Obico.hpp Utils/Spoolman.cpp Utils/Spoolman.hpp + GUI/SpoolmanImportDialog.cpp + GUI/SpoolmanImportDialog.hpp ) if (WIN32) diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index 068a463246..1252667fd5 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -392,4 +392,18 @@ wxSize TextRenderer::GetSize() const return GetTextExtent(m_value); } +// ---------------------------------------------------------------------------- +// ColorRenderer +// ---------------------------------------------------------------------------- +bool ColorRenderer::SetValue(const wxVariant& value) { + color << value; + return true; +} + +bool ColorRenderer::Render(wxRect cell, wxDC* dc, int state) { + cell.Deflate(4); + dc->SetBrush(wxBrush(color)); + dc->DrawRectangle(cell); + return true; +} \ No newline at end of file diff --git a/src/slic3r/GUI/ExtraRenderers.hpp b/src/slic3r/GUI/ExtraRenderers.hpp index b778df4c49..64987f8679 100644 --- a/src/slic3r/GUI/ExtraRenderers.hpp +++ b/src/slic3r/GUI/ExtraRenderers.hpp @@ -188,4 +188,23 @@ private: }; + +// ---------------------------------------------------------------------------- +// ColorRenderer +// ---------------------------------------------------------------------------- + +class ColorRenderer : public wxDataViewCustomRenderer +{ + wxColour color; + +public: + ColorRenderer() : wxDataViewCustomRenderer(wxT("wxColour")) {} + + bool SetValue(const wxVariant& value) override; + bool GetValue(wxVariant& value) const override { return false; }; + + bool Render(wxRect cell, wxDC* dc, int state) override; + wxSize GetSize() const override { return wxDefaultSize; }; +}; + #endif // slic3r_GUI_ExtraRenderers_hpp_ diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 080981c1ea..3010481feb 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -39,6 +39,7 @@ #include "BitmapCache.hpp" #include "MsgDialog.hpp" #include "ParamsDialog.hpp" +#include "SpoolmanImportDialog.hpp" // A workaround for a set of issues related to text fitting into gtk widgets: #if defined(__WXGTK20__) || defined(__WXGTK3__) @@ -757,6 +758,10 @@ void PlaterPresetComboBox::OnSelect(wxCommandEvent &evt) if (marker == LABEL_ITEM_MARKER) return; if (marker == LABEL_ITEM_IMPORT_SPOOLMAN) { + SpoolmanImportDialog dlg(wxGetApp().mainframe); + dlg.ShowModal(); + // update to show any new presets + this->update(); return; } //if (marker == LABEL_ITEM_WIZARD_PRINTERS) diff --git a/src/slic3r/GUI/SpoolmanImportDialog.cpp b/src/slic3r/GUI/SpoolmanImportDialog.cpp new file mode 100644 index 0000000000..6c1bb69cef --- /dev/null +++ b/src/slic3r/GUI/SpoolmanImportDialog.cpp @@ -0,0 +1,315 @@ +#include "SpoolmanImportDialog.hpp" +#include "I18N.hpp" +#include "GUI_App.hpp" +#include "ExtraRenderers.hpp" +#include "MsgDialog.hpp" + +#define BTN_GAP FromDIP(10) +#define BTN_SIZE wxSize(FromDIP(58), FromDIP(24)) + +namespace Slic3r { namespace GUI { + +//----------------------------------------- +// SpoolmanViewModel +//----------------------------------------- + +wxDataViewItem SpoolmanViewModel::AddSpool(SpoolmanSpoolShrPtr spool) +{ + m_top_children.emplace_back(std::make_unique(spool)); + wxDataViewItem item(m_top_children.back().get()); + ItemAdded(wxDataViewItem(nullptr), item); + return item; +} + +void SpoolmanViewModel::SetAllToggles(bool value) +{ + for (auto& item : m_top_children) + if (item->set_checked(value)) + ItemChanged(wxDataViewItem(item.get())); +} + +std::vector SpoolmanViewModel::GetSelectedSpools() +{ + std::vector spools; + for (auto& item : m_top_children) + if (item->get_checked()) + spools.emplace_back(item->get_spool()); + return spools; +} + +wxString SpoolmanViewModel::GetColumnType(unsigned int col) const +{ + if (col == COL_CHECK) + return "bool"; + else if (col == COL_COLOR) + return "wxColour"; + return "string"; +} + +unsigned int SpoolmanViewModel::GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const +{ + if (parent.IsOk()) + return 0; + + for (auto child : m_top_children) + array.push_back(wxDataViewItem(child.get())); + return m_top_children.size(); +} + +void SpoolmanViewModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const +{ + SpoolmanNode* node = get_node(item); + if (!node) + return; + switch (col) { + case COL_CHECK: variant = node->get_checked(); break; + case COL_ID: variant = std::to_string(node->get_id()); break; + case COL_COLOR: variant << wxColour(node->get_color()); break; + case COL_VENDOR: variant = node->get_vendor_name(); break; + case COL_NAME: variant = node->get_filament_name(); break; + case COL_MATERIAL: variant = node->get_material(); break; + default: wxLogError("Out of bounds column call to SpoolmanViewModel::GetValue. col = %d", col); + } +} + +bool SpoolmanViewModel::SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) +{ + if (col == COL_CHECK) { + get_node(item)->set_checked(variant.GetBool()); + return true; + } + wxLogError("Out of bounds column call to SpoolmanViewModel::SetValue. Only column 0 should be set to a value. col = %d", col); + return false; +} + +bool SpoolmanViewModel::IsEnabled(const wxDataViewItem& item, unsigned int col) const { return !get_node(item)->is_archived(); } + +//----------------------------------------- +// SpoolmanViewCtrl +//----------------------------------------- + +SpoolmanViewCtrl::SpoolmanViewCtrl(wxWindow* parent) : wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_ROW_LINES) +{ + wxGetApp().UpdateDVCDarkUI(this); + ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_DEFAULT); + + m_model = new SpoolmanViewModel(); + this->AssociateModel(m_model); + m_model->SetAssociatedControl(this); + + this->AppendToggleColumn(L"\u2714", COL_CHECK, wxDATAVIEW_CELL_ACTIVATABLE, 4 * EM, wxALIGN_CENTER, 0); + this->AppendTextColumn("ID", COL_ID, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, 0); + this->AppendColumn(new wxDataViewColumn("Color", new ColorRenderer(), COL_COLOR, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, 0)); + this->AppendTextColumn("Vendor", COL_VENDOR)->SetWidth(wxCOL_WIDTH_AUTOSIZE); + this->AppendTextColumn("Name", COL_NAME)->SetWidth(wxCOL_WIDTH_AUTOSIZE); + this->AppendTextColumn("Material", COL_MATERIAL)->SetWidth(wxCOL_WIDTH_AUTOSIZE); + + for (int i = COL_ID; i <= COL_MATERIAL; i++) + if (i != COL_COLOR) + this->GetColumn(i)->SetFlag(wxCOL_SORTABLE); +} + +//----------------------------------------- +// SpoolmanImportDialog +//----------------------------------------- + +SpoolmanImportDialog::SpoolmanImportDialog(wxWindow* parent) + : DPIDialog(parent, wxID_ANY, _L("Import from Spoolman"), wxDefaultPosition, {-1, 45 * EM}, wxDEFAULT_DIALOG_STYLE) +{ +#ifdef _WIN32 + this->SetBackgroundColour(*wxWHITE); + wxGetApp().UpdateDarkUI(this); + wxGetApp().UpdateDlgDarkUI(this); +#else + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif + + auto main_sizer = new wxBoxSizer(wxVERTICAL); + + // SpoolmanViewCtrl + m_svc = new SpoolmanViewCtrl(this); + main_sizer->Add(m_svc, 1, wxCENTER | wxEXPAND | wxALL, 10); + + // Base Preset Label + auto* label = new Label(this, _L("Base Preset:")); + wxGetApp().UpdateDarkUI(label); + main_sizer->Add(label, 0, wxLEFT, 10); + + auto preset_sizer = new wxBoxSizer(wxHORIZONTAL); + + // PresetCombobox + m_preset_combobox = new TabPresetComboBox(this, Preset::TYPE_FILAMENT); + preset_sizer->Add(m_preset_combobox, 1, wxEXPAND | wxRIGHT, 10); + m_preset_combobox->update(); + + // Detach Checkbox + m_detach_checkbox = new wxCheckBox(this, wxID_ANY, _L("Save as Detached")); + m_detach_checkbox->SetToolTip(_L("Save as a standalone preset")); + preset_sizer->Add(m_detach_checkbox, 0, wxALIGN_CENTER_VERTICAL); + + main_sizer->Add(preset_sizer, 0, wxEXPAND | wxALL, 10); + + // Buttons + main_sizer->Add(create_btn_sizer(), 0, wxCENTER | wxEXPAND | wxALL, 10); + + this->SetSizer(main_sizer); + + // Load data into SVC + for (const auto& spoolman_spool : m_spoolman->get_spoolman_spools()) + m_svc->get_model()->AddSpool(spoolman_spool.second); +} + +void SpoolmanImportDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + for (auto btn : m_button_list) { + btn->SetMinSize(BTN_SIZE); + btn->SetCornerRadius(FromDIP(12)); + } + + Fit(); + Refresh(); +} + +void SpoolmanImportDialog::on_import() +{ + const Preset* current_preset = wxGetApp().preset_bundle->filaments.find_preset(m_preset_combobox->GetStringSelection().ToUTF8().data()); + const vector& spools = m_svc->get_model()->GetSelectedSpools(); + if (spools.empty()) { + show_error(this, "No spools are selected"); + return; + } + + for (const auto& spool : spools) + if (Spoolman::get_name_from_spool(spool) == current_preset->name) { + show_error(this, "One of the selected spools is the same as the current base preset.\n" + "Please deselect that spool or select a different base preset."); + return; + } + + bool detach = m_detach_checkbox->GetValue(); + std::vector> failed_spools; + + auto create_presets = [&](const vector& spools, bool force = false) { + // Attempt to create the presets + // Calculating the hash for the internal filament id takes a bit, so using multithreading to speed it up + std::vector threads; + for (const auto& spool : spools) { + threads.emplace_back(Slic3r::create_thread([&spool, &failed_spools, ¤t_preset, &force, &detach]() { + auto res = Spoolman::create_filament_preset_from_spool(spool, current_preset, detach, force); + if (res.failure()) + failed_spools.emplace_back(spool, res); + })); + } + + // Join/wait for threads to finish before continuing + for (auto& thread : threads) + if (thread.joinable()) + thread.join(); + }; + + create_presets(spools); + + // Show message with any errors + if (!failed_spools.empty()) { + // message spools with same message + std::map> sorted_error_messages; + + for (const std::pair& failed_spool : failed_spools) + for (const auto& msg : failed_spool.second.messages) + sorted_error_messages[msg].emplace_back(failed_spool.first); + + std::stringstream error_message; + for (const auto& msg_pair : sorted_error_messages) { + for (const auto& errored_spool : msg_pair.second) + error_message << Spoolman::get_name_from_spool(errored_spool) << ",\n"; + error_message.seekp(-2, ios_base::end); + error_message << ":\n"; + error_message << "\t" << msg_pair.first << std::endl << std::endl; + } + + error_message << "Would you like to ignore these issues and continue?\n" + "Presets with the same name will be updated and presets with conflicting IDs will be forcibly created."; + + WarningDialog dlg = WarningDialog(this, error_message.str(), wxEmptyString, wxYES | wxCANCEL); + if (dlg.ShowModal() == wxID_YES) { + std::vector retry_spools; + for (const auto& item : failed_spools) + retry_spools.emplace_back(item.first); + create_presets(retry_spools, true); + this->EndModal(wxID_OK); + } + + // Update the combobox to display any successfully added presets + m_preset_combobox->update(); + return; + } + this->EndModal(wxID_OK); +} + +// Orca: Apply buttons style +wxBoxSizer* SpoolmanImportDialog::create_btn_sizer() +{ + auto btn_sizer = new wxBoxSizer(wxHORIZONTAL); + + auto apply_highlighted_btn_colors = [](Button* btn) { + btn->SetBackgroundColor(StateColor(std::pair(wxColour(0, 137, 123), StateColor::Pressed), + std::pair(wxColour(38, 166, 154), StateColor::Hovered), + std::pair(wxColour(0, 150, 136), StateColor::Normal))); + + btn->SetBorderColor(StateColor(std::pair(wxColour(0, 150, 136), StateColor::Normal))); + + btn->SetTextColor(StateColor(std::pair(wxColour(255, 255, 254), StateColor::Normal))); + }; + + auto apply_std_btn_colors = [](Button* btn) { + btn->SetBackgroundColor(StateColor(std::pair(wxColour(206, 206, 206), StateColor::Pressed), + std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(wxColour(255, 255, 255), StateColor::Normal))); + + btn->SetBorderColor(StateColor(std::pair(wxColour(38, 46, 48), StateColor::Normal))); + + btn->SetTextColor(StateColor(std::pair(wxColour(38, 46, 48), StateColor::Normal))); + }; + + auto style_btn = [this, apply_highlighted_btn_colors, apply_std_btn_colors](Button* btn, bool highlight) { + btn->SetMinSize(BTN_SIZE); + btn->SetCornerRadius(FromDIP(12)); + if (highlight) + apply_highlighted_btn_colors(btn); + else + apply_std_btn_colors(btn); + }; + + Button* all_btn = new Button(this, _L("All")); + style_btn(all_btn, false); + all_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { m_svc->get_model()->SetAllToggles(true); }); + btn_sizer->Add(all_btn, 0, wxALIGN_CENTER_VERTICAL); + m_button_list.push_back(all_btn); + + Button* none_btn = new Button(this, _L("None")); + style_btn(none_btn, false); + none_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { m_svc->get_model()->SetAllToggles(false); }); + btn_sizer->Add(none_btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, BTN_GAP); + m_button_list.push_back(none_btn); + + btn_sizer->AddStretchSpacer(); + + Button* import_btn = new Button(this, _L("Import")); + style_btn(import_btn, true); + import_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_import(); }); + import_btn->SetFocus(); + import_btn->SetId(wxID_OK); + btn_sizer->Add(import_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, BTN_GAP); + m_button_list.push_back(import_btn); + + Button* cancel_btn = new Button(this, _L("Cancel")); + style_btn(cancel_btn, false); + cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { this->EndModal(wxID_CANCEL); }); + cancel_btn->SetId(wxID_CANCEL); + btn_sizer->Add(cancel_btn, 0, wxALIGN_CENTER_VERTICAL); + m_button_list.push_back(cancel_btn); + + return btn_sizer; +} + +}} // namespace Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/SpoolmanImportDialog.hpp b/src/slic3r/GUI/SpoolmanImportDialog.hpp new file mode 100644 index 0000000000..bf95fa4a3f --- /dev/null +++ b/src/slic3r/GUI/SpoolmanImportDialog.hpp @@ -0,0 +1,144 @@ +#ifndef ORCASLICER_SPOOLMANIMPORTDIALOG_HPP +#define ORCASLICER_SPOOLMANIMPORTDIALOG_HPP + +#include +#include +#include "GUI_Utils.hpp" +#include "PresetComboBoxes.hpp" +#define EM wxGetApp().em_unit() + +namespace Slic3r { namespace GUI { +class SpoolmanViewCtrl; + +enum Column { COL_CHECK = 0, COL_ID, COL_COLOR, COL_VENDOR, COL_NAME, COL_MATERIAL }; + +//----------------------------------------- +// SpoolmanNode +//----------------------------------------- + +class SpoolmanNode +{ +public: + explicit SpoolmanNode(const SpoolmanSpoolShrPtr& spool) : m_spool(spool) {} + + int get_id() const { return m_spool->id; }; + std::string get_color() const { return m_spool->m_filament_ptr->color; } + std::string get_vendor_name() const { return m_spool->getVendor()->name; }; + std::string get_filament_name() const { return m_spool->m_filament_ptr->name; }; + std::string get_material() const { return m_spool->m_filament_ptr->material; }; + bool is_archived() const { return m_spool->archived; }; + + bool get_checked() { return m_checked; }; + // return if value has changed + bool set_checked(bool value) + { + if (m_checked == value) + return false; + m_checked = value; + return true; + }; + + SpoolmanSpoolShrPtr get_spool() { return m_spool; } + +protected: + SpoolmanSpoolShrPtr m_spool; + bool m_checked{false}; +}; + +typedef std::shared_ptr SpoolmanNodeShrPtr; + +// Static helper method +namespace { +SpoolmanNode* get_node(const wxDataViewItem& item) +{ + if (!item.IsOk()) + return nullptr; + return static_cast(item.GetID()); +} +} // namespace + +//----------------------------------------- +// SpoolmanViewItem +//----------------------------------------- + +class SpoolmanViewModel : public wxDataViewModel +{ +public: + SpoolmanViewModel() {} + + wxDataViewItem AddSpool(SpoolmanSpoolShrPtr spool); + + void SetAllToggles(bool value); + + std::vector GetSelectedSpools(); + + wxString GetColumnType(unsigned int col) const override; + unsigned int GetColumnCount() const override { return 5; } + + // returns a nullptr item. this control only has a single tier + wxDataViewItem GetParent(const wxDataViewItem& item) const override { return wxDataViewItem(nullptr); }; + unsigned int GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const override; + + void SetAssociatedControl(SpoolmanViewCtrl* ctrl) { m_ctrl = ctrl; } + + void GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const override; + bool SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) override; + + bool IsEnabled(const wxDataViewItem& item, unsigned int col) const override; + // Not using container functionality + bool IsContainer(const wxDataViewItem& item) const override { return false; }; + + // Is the container just a header or an item with all columns + // In our case it is an item with all columns + bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } + +protected: + wxWindow* m_parent{nullptr}; + SpoolmanViewCtrl* m_ctrl{nullptr}; + std::vector m_top_children; +}; + +//----------------------------------------- +// SpoolmanViewCtrl +//----------------------------------------- + +class SpoolmanViewCtrl : public wxDataViewCtrl +{ +public: + SpoolmanViewCtrl(wxWindow* parent); + ~SpoolmanViewCtrl() { + if (m_model) + m_model->DecRef(); + } + + SpoolmanViewModel* get_model() const { return m_model; } + +protected: + SpoolmanViewModel* m_model; +}; + +//----------------------------------------- +// SpoolmanImportDialog +//----------------------------------------- + +class SpoolmanImportDialog : public DPIDialog +{ +public: + SpoolmanImportDialog(wxWindow* parent); + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; + + void on_import(); + + wxBoxSizer* create_btn_sizer(); + + std::vector m_button_list; + Spoolman* m_spoolman{Spoolman::get_instance()}; + SpoolmanViewCtrl* m_svc; + TabPresetComboBox* m_preset_combobox; + wxCheckBox* m_detach_checkbox; +}; +}} // namespace Slic3r::GUI + +#endif // ORCASLICER_SPOOLMANIMPORTDIALOG_HPP \ No newline at end of file From 4ec7c1227d0e81b57a8da0ae6e3ffbf36c4b6bb0 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 27 Mar 2024 06:58:44 -0400 Subject: [PATCH 014/100] Refactor PhysicalPrinterDialog.cpp for readability and fix conflicts Merge in previous commit had a conflict that caused compile error. Refactor code from merge to make it more readable Add Refresh statement in the update to fix weird artifacting after selecting certain host types --- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 82 +++++++++++++----------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 1e24b20985..f1c41d8dba 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -500,6 +500,7 @@ void PhysicalPrinterDialog::update_preset_input() { void PhysicalPrinterDialog::update(bool printer_change) { m_optgroup->reload_config(); + this->Freeze(); const PrinterTechnology tech = Preset::printer_technology(*m_config); // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) @@ -535,6 +536,7 @@ void PhysicalPrinterDialog::update(bool printer_change) } } } + if (opt->value == htPrusaLink) { // PrusaConnect does NOT allow http digest m_optgroup->show_field("printhost_authorization_type"); AuthorizationType auth_type = m_config->option>("printhost_authorization_type")->value; @@ -547,43 +549,9 @@ void PhysicalPrinterDialog::update(bool printer_change) for (const std::string& opt_key : std::vector{ "printhost_user", "printhost_password" }) m_optgroup->hide_field(opt_key); supports_multiple_printers = opt->value == htRepetier || opt->value == htObico; + } - if (opt->value == htPrusaConnect) { // automatically show default prusaconnect address - if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) { - if (wxTextCtrl* temp = dynamic_cast(printhost_field)->text_ctrl(); temp && temp->GetValue().IsEmpty()) { - temp->SetValue(L"https://connect.prusa3d.com"); - } - } - } else if (opt->value == htObico) { // automatically show default obico address - if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) { - if (wxTextCtrl* temp = dynamic_cast(printhost_field)->text_ctrl(); temp && temp->GetValue().IsEmpty()) { - temp->SetValue(L"https://app.obico.io"); - m_config->opt_string("print_host") = "https://app.obico.io"; - } - } - } else if (opt->value == htSimplyPrint) { - // Set the host url - if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) { - printhost_field->disable(); - if (wxTextCtrl* temp = dynamic_cast(printhost_field)->text_ctrl(); temp && temp->GetValue().IsEmpty()) { - temp->SetValue("https://simplyprint.io"); - } - m_config->opt_string("print_host") = "https://simplyprint.io"; - } - if (Field* printhost_webui_field = m_optgroup->get_field("print_host_webui"); printhost_webui_field) { - printhost_webui_field->disable(); - if (wxTextCtrl* temp = dynamic_cast(printhost_webui_field)->text_ctrl(); temp && temp->GetValue().IsEmpty()) { - temp->SetValue("https://simplyprint.io/panel"); - } - m_config->opt_string("print_host_webui") = "https://simplyprint.io/panel"; - } - m_optgroup->hide_field("printhost_apikey"); - m_optgroup->disable_field("printhost_cafile"); - m_optgroup->disable_field("printhost_ssl_ignore_revoke"); - if (m_printhost_cafile_browse_btn) - m_printhost_cafile_browse_btn->Disable(); - } - } else if (opt->value == htOctoPrint) { + if (opt->value == htOctoPrint) { m_optgroup->show_field("spoolman_enabled"); m_optgroup->show_field("spoolman_port", m_config->opt_bool("spoolman_enabled")); } else { @@ -593,11 +561,45 @@ void PhysicalPrinterDialog::update(bool printer_change) m_config->set("spoolman_port", m_optgroup->get_option("spoolman_port").opt.get_default_value()->value); m_optgroup->hide_field("spoolman_port"); } - + if (opt->value == htFlashforge) { - m_optgroup->hide_field("printhost_apikey"); - m_optgroup->hide_field("printhost_authorization_type"); + m_optgroup->hide_field("printhost_apikey"); + m_optgroup->hide_field("printhost_authorization_type"); + } else if (opt->value == htPrusaConnect) { // automatically show default prusaconnect address + if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) { + if (wxTextCtrl* temp = dynamic_cast(printhost_field)->text_ctrl(); temp && temp->GetValue().IsEmpty()) { + temp->SetValue(L"https://connect.prusa3d.com"); + } } + } else if (opt->value == htObico) { // automatically show default obico address + if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) { + if (wxTextCtrl* temp = dynamic_cast(printhost_field)->text_ctrl(); temp && temp->GetValue().IsEmpty()) { + temp->SetValue(L"https://app.obico.io"); + m_config->opt_string("print_host") = "https://app.obico.io"; + } + } + } else if (opt->value == htSimplyPrint) { // automatically show default simplyprint address + // Set the host url + if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) { + printhost_field->disable(); + if (wxTextCtrl* temp = dynamic_cast(printhost_field)->text_ctrl(); temp && temp->GetValue().IsEmpty()) { + temp->SetValue("https://simplyprint.io"); + } + m_config->opt_string("print_host") = "https://simplyprint.io"; + } + if (Field* printhost_webui_field = m_optgroup->get_field("print_host_webui"); printhost_webui_field) { + printhost_webui_field->disable(); + if (wxTextCtrl* temp = dynamic_cast(printhost_webui_field)->text_ctrl(); temp && temp->GetValue().IsEmpty()) { + temp->SetValue("https://simplyprint.io/panel"); + } + m_config->opt_string("print_host_webui") = "https://simplyprint.io/panel"; + } + m_optgroup->hide_field("printhost_apikey"); + m_optgroup->disable_field("printhost_cafile"); + m_optgroup->disable_field("printhost_ssl_ignore_revoke"); + if (m_printhost_cafile_browse_btn) + m_printhost_cafile_browse_btn->Disable(); + } } else { m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false); @@ -621,6 +623,8 @@ void PhysicalPrinterDialog::update(bool printer_change) this->SetSize(this->GetBestSize()); this->Layout(); + this->Refresh(); + this->Thaw(); } void PhysicalPrinterDialog::update_host_type(bool printer_change) From c2f791d05f25f82244da6c540e405f5f5f02b1c5 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 27 Mar 2024 07:47:48 -0400 Subject: [PATCH 015/100] Change spoolman_port to spoolman_host --- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 8 +++++--- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 8 ++++---- src/slic3r/Utils/Spoolman.cpp | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 39cd8b3252..8815da1dfd 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -876,7 +876,7 @@ static std::vector s_Preset_printer_options { "best_object_pos","head_wrap_detect_zone", //SoftFever "host_type", "print_host", "printhost_apikey", "bbl_use_printhost", - "print_host_webui", "spoolman_enabled", "spoolman_port", + "print_host_webui", "spoolman_enabled", "spoolman_host", "printhost_cafile","printhost_port","printhost_authorization_type", "printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", "thumbnails", "thumbnails_format", "use_firmware_retraction", "use_relative_e_distances", "printer_notes", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 730e097e9b..b65cb56da8 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -567,9 +567,11 @@ void PrintConfigDef::init_common_params() def->cli = ConfigOptionDef::nocli; def->set_default_value(new ConfigOptionBool()); - def = this->add("spoolman_port", coString); - def->label = L("Spoolman Port"); - def->tooltip = L("Indicates the port your Spoolman instance is hosted on"); + def = this->add("spoolman_host", coString); + def->label = L("Spoolman Host"); + def->tooltip = L("Points to where you Spoolman instance is hosted. " + "You can either provide just the port to use the same URL as your printer " + "or provide the full address in the format of :."); def->mode = comAdvanced; def->cli = ConfigOptionDef::nocli; def->set_default_value(new ConfigOptionString("8000")); diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index f1c41d8dba..91a680b69f 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -136,7 +136,7 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr m_optgroup->append_single_option_line("spoolman_enabled"); - Option option = m_optgroup->get_option("spoolman_port"); + Option option = m_optgroup->get_option("spoolman_host"); option.opt.width = Field::def_width_wider(); m_optgroup->append_single_option_line(option); @@ -553,13 +553,13 @@ void PhysicalPrinterDialog::update(bool printer_change) if (opt->value == htOctoPrint) { m_optgroup->show_field("spoolman_enabled"); - m_optgroup->show_field("spoolman_port", m_config->opt_bool("spoolman_enabled")); + m_optgroup->show_field("spoolman_host", m_config->opt_bool("spoolman_enabled")); } else { m_config->set("spoolman_enabled", false); m_optgroup->hide_field("spoolman_enabled"); - m_config->set("spoolman_port", m_optgroup->get_option("spoolman_port").opt.get_default_value()->value); - m_optgroup->hide_field("spoolman_port"); + m_config->set("spoolman_host", m_optgroup->get_option("spoolman_host").opt.get_default_value()->value); + m_optgroup->hide_field("spoolman_host"); } if (opt->value == htFlashforge) { diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index eb14f9e1f9..895a5a09a4 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -23,7 +23,7 @@ pt::ptree Spoolman::get_spoolman_json(const string& api_endpoint) { DynamicPrintConfig& config = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config; string host = config.opt_string("print_host"); - string spoolman_host = config.opt_string("spoolman_port"); + string spoolman_host = config.opt_string("spoolman_host"); string spoolman_port; if (auto idx = spoolman_host.find_last_of(':'); idx != string::npos) { From 9d1b6773295676cd5b964a6d5ef6d5d78b089b26 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Fri, 29 Mar 2024 09:38:45 -0400 Subject: [PATCH 016/100] Improve Spoolman Server Error Handling Add Spoolman::is_server_valid() Check validity of server in SpoolmanImportDialog and in Tab when clicking the update buttons Hide the update buttons if Spoolman ID is 0 Check results from spool update when updating spools --- src/slic3r/GUI/SpoolmanImportDialog.cpp | 9 +++++++-- src/slic3r/GUI/Tab.cpp | 18 +++++++++++++++--- src/slic3r/Utils/Spoolman.cpp | 11 +++++++---- src/slic3r/Utils/Spoolman.hpp | 9 +++++---- 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/SpoolmanImportDialog.cpp b/src/slic3r/GUI/SpoolmanImportDialog.cpp index 6c1bb69cef..dd3ed141b8 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.cpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.cpp @@ -124,6 +124,11 @@ SpoolmanImportDialog::SpoolmanImportDialog(wxWindow* parent) SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); #endif + if (!Spoolman::is_server_valid()) { + show_error(parent, "Failed to get data from the Spoolman server. Make sure that the port is correct and the server is running."); + return; + } + auto main_sizer = new wxBoxSizer(wxVERTICAL); // SpoolmanViewCtrl @@ -155,7 +160,7 @@ SpoolmanImportDialog::SpoolmanImportDialog(wxWindow* parent) this->SetSizer(main_sizer); // Load data into SVC - for (const auto& spoolman_spool : m_spoolman->get_spoolman_spools()) + for (const auto& spoolman_spool : m_spoolman->get_spoolman_spools(true)) m_svc->get_model()->AddSpool(spoolman_spool.second); } @@ -196,7 +201,7 @@ void SpoolmanImportDialog::on_import() for (const auto& spool : spools) { threads.emplace_back(Slic3r::create_thread([&spool, &failed_spools, ¤t_preset, &force, &detach]() { auto res = Spoolman::create_filament_preset_from_spool(spool, current_preset, detach, force); - if (res.failure()) + if (res.has_failed()) failed_spools.emplace_back(spool, res); })); } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 33f6a6544a..f2837e5f02 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3287,7 +3287,7 @@ void TabFilament::build() optgroup->append_single_option_line("spoolman_spool_id"); line = {"Spoolman Update", ""}; - line.append_option(Option(ConfigOptionDef(), "")); + line.append_option(Option(ConfigOptionDef(), "spoolman_update")); line.widget = [&](wxWindow* parent){ auto sizer = new wxBoxSizer(wxHORIZONTAL); @@ -3296,8 +3296,16 @@ void TabFilament::build() show_error(this, "This profile cannot be updated with an unsaved Spool ID value. Please save the profile, then try updating again."); return; } - Spoolman::update_filament_preset_from_spool(&m_presets->get_edited_preset(), true, stats_only); - Spoolman::update_filament_preset_from_spool(&m_presets->get_selected_preset(), false, stats_only); + if (!Spoolman::is_server_valid()) { + show_error(parent, "Failed to get data from the Spoolman server. Make sure that the port is correct and the server is running."); + return; + } + auto res1 = Spoolman::update_filament_preset_from_spool(&m_presets->get_edited_preset(), true, stats_only); + auto res2 = Spoolman::update_filament_preset_from_spool(&m_presets->get_selected_preset(), false, stats_only); + + if (res1.has_failed() || res2.has_failed()) + return; + const Preset* preset = m_presets->get_selected_preset_parent(); m_presets->get_selected_preset().save(preset ? &preset->config : nullptr); update_dirty(); @@ -3471,6 +3479,10 @@ void TabFilament::toggle_options() "filament_toolchange_delay", "filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed"}) toggle_option(el, !is_BBL_printer); } + + if (m_active_page->title() == L("Spoolman")) { + toggle_line("spoolman_update", m_config->opt_int("spoolman_spool_id") > 0); + } } void TabFilament::update() diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 895a5a09a4..f8f96c9afe 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -48,9 +48,7 @@ pt::ptree Spoolman::get_spoolman_json(const string& api_endpoint) std::string res_body; http.on_error([&](const std::string& body, std::string error, unsigned status) { - string msg = "Failed to get data from the Spoolman server. Make sure that the port is correct and the server is running."; - BOOST_LOG_TRIVIAL(error) << msg << boost::format(" HTTP Error: %1%, HTTP status code: %2%") % error % status; - show_error(nullptr, msg); + BOOST_LOG_TRIVIAL(error) << "Failed to get data from the Spoolman server. Make sure that the port is correct and the server is running." << boost::format(" HTTP Error: %1%, HTTP status code: %2%") % error % status; res = false; }) .on_complete([&](std::string body, unsigned) { @@ -159,7 +157,7 @@ SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolSh else result.messages.emplace_back("An invisible preset shares the same spool ID"); } - if (result.failure()) + if (result.has_failed()) return result; } @@ -202,6 +200,11 @@ std::string Spoolman::get_name_from_spool(const SpoolmanSpoolShrPtr& spool) return remove_special_key(spool->getVendor()->name + " " + spool->m_filament_ptr->name + " " + spool->m_filament_ptr->material); } +bool Spoolman::is_server_valid() +{ + return !get_spoolman_json("info").empty(); +} + //--------------------------------- // SpoolmanVendor //--------------------------------- diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index b3aec55ec9..e9cfe2e4b9 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -1,8 +1,6 @@ #ifndef SLIC3R_SPOOLMAN_HPP #define SLIC3R_SPOOLMAN_HPP -#include - namespace pt = boost::property_tree; namespace Slic3r { @@ -19,7 +17,7 @@ typedef std::shared_ptr SpoolmanSpoolShrPtr; struct SpoolmanResult { SpoolmanResult() = default; - bool failure() { return !messages.empty(); } + bool has_failed() { return !messages.empty(); } std::vector messages{}; }; @@ -39,7 +37,8 @@ class Spoolman Spoolman() { m_instance = this; - m_initialized = pull_spoolman_spools(); + if (is_server_valid()) + m_initialized = pull_spoolman_spools(); }; static pt::ptree get_spoolman_json(const std::string& api_endpoint); @@ -57,6 +56,8 @@ public: static std::string get_name_from_spool(const SpoolmanSpoolShrPtr& spool); + static bool is_server_valid(); + const std::map& get_spoolman_spools(bool update = false) { if (update || !m_initialized) From 913b0036eaff5f5f8b76b353569b0fabde22cd5c Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Fri, 29 Mar 2024 13:48:42 -0400 Subject: [PATCH 017/100] Fix Dialog Sizing Also minor fix regarding hiding scrollbars --- src/slic3r/GUI/PresetComboBoxes.cpp | 1 - src/slic3r/GUI/SpoolmanImportDialog.cpp | 37 +++++++++++++++++-------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 3010481feb..9ef53b1749 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -759,7 +759,6 @@ void PlaterPresetComboBox::OnSelect(wxCommandEvent &evt) return; if (marker == LABEL_ITEM_IMPORT_SPOOLMAN) { SpoolmanImportDialog dlg(wxGetApp().mainframe); - dlg.ShowModal(); // update to show any new presets this->update(); return; diff --git a/src/slic3r/GUI/SpoolmanImportDialog.cpp b/src/slic3r/GUI/SpoolmanImportDialog.cpp index dd3ed141b8..70cced223e 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.cpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.cpp @@ -91,22 +91,27 @@ bool SpoolmanViewModel::IsEnabled(const wxDataViewItem& item, unsigned int col) SpoolmanViewCtrl::SpoolmanViewCtrl(wxWindow* parent) : wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_ROW_LINES) { wxGetApp().UpdateDVCDarkUI(this); +#if _WIN32 ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_DEFAULT); +#elif + SetScrollbar(wxHORIZONTAL, 0, 0, 0) +#endif m_model = new SpoolmanViewModel(); this->AssociateModel(m_model); m_model->SetAssociatedControl(this); this->AppendToggleColumn(L"\u2714", COL_CHECK, wxDATAVIEW_CELL_ACTIVATABLE, 4 * EM, wxALIGN_CENTER, 0); - this->AppendTextColumn("ID", COL_ID, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, 0); + this->AppendTextColumn("ID", COL_ID, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, wxCOL_SORTABLE); this->AppendColumn(new wxDataViewColumn("Color", new ColorRenderer(), COL_COLOR, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, 0)); - this->AppendTextColumn("Vendor", COL_VENDOR)->SetWidth(wxCOL_WIDTH_AUTOSIZE); - this->AppendTextColumn("Name", COL_NAME)->SetWidth(wxCOL_WIDTH_AUTOSIZE); - this->AppendTextColumn("Material", COL_MATERIAL)->SetWidth(wxCOL_WIDTH_AUTOSIZE); + this->AppendTextColumn("Vendor", COL_VENDOR, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_NOT, wxCOL_SORTABLE); + this->AppendTextColumn("Name", COL_NAME, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_NOT, wxCOL_SORTABLE); + this->AppendTextColumn("Material", COL_MATERIAL, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_NOT, wxCOL_SORTABLE); - for (int i = COL_ID; i <= COL_MATERIAL; i++) - if (i != COL_COLOR) - this->GetColumn(i)->SetFlag(wxCOL_SORTABLE); + // fake column to put the expander in + auto temp_col = this->AppendTextColumn("", 100); + temp_col->SetHidden(true); + this->SetExpanderColumn(temp_col); } //----------------------------------------- @@ -133,18 +138,18 @@ SpoolmanImportDialog::SpoolmanImportDialog(wxWindow* parent) // SpoolmanViewCtrl m_svc = new SpoolmanViewCtrl(this); - main_sizer->Add(m_svc, 1, wxCENTER | wxEXPAND | wxALL, 10); + main_sizer->Add(m_svc, 1, wxCENTER | wxEXPAND | wxALL, EM); // Base Preset Label auto* label = new Label(this, _L("Base Preset:")); wxGetApp().UpdateDarkUI(label); - main_sizer->Add(label, 0, wxLEFT, 10); + main_sizer->Add(label, 0, wxLEFT, EM); auto preset_sizer = new wxBoxSizer(wxHORIZONTAL); // PresetCombobox m_preset_combobox = new TabPresetComboBox(this, Preset::TYPE_FILAMENT); - preset_sizer->Add(m_preset_combobox, 1, wxEXPAND | wxRIGHT, 10); + preset_sizer->Add(m_preset_combobox, 1, wxEXPAND | wxRIGHT, EM); m_preset_combobox->update(); // Detach Checkbox @@ -152,16 +157,24 @@ SpoolmanImportDialog::SpoolmanImportDialog(wxWindow* parent) m_detach_checkbox->SetToolTip(_L("Save as a standalone preset")); preset_sizer->Add(m_detach_checkbox, 0, wxALIGN_CENTER_VERTICAL); - main_sizer->Add(preset_sizer, 0, wxEXPAND | wxALL, 10); + main_sizer->Add(preset_sizer, 0, wxEXPAND | wxALL, EM); // Buttons - main_sizer->Add(create_btn_sizer(), 0, wxCENTER | wxEXPAND | wxALL, 10); + main_sizer->Add(create_btn_sizer(), 0, wxCENTER | wxEXPAND | wxALL, EM); this->SetSizer(main_sizer); // Load data into SVC for (const auto& spoolman_spool : m_spoolman->get_spoolman_spools(true)) m_svc->get_model()->AddSpool(spoolman_spool.second); + + int colWidth = 8 * EM; // 4 EM for checkbox (width isn't calculated right), 4 EM for border + for (int i = COL_ID; i <= COL_MATERIAL; ++i) { + colWidth +=m_svc->GetColumnAt(i)->GetWidth(); + } + this->SetSize(wxDefaultCoord, wxDefaultCoord, colWidth, wxDefaultCoord, wxSIZE_SET_CURRENT); + + this->ShowModal(); } void SpoolmanImportDialog::on_dpi_changed(const wxRect& suggested_rect) From 55b8483b36baa5ec1785a9b2fd63485f89e06658 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 29 May 2024 01:51:31 -0400 Subject: [PATCH 018/100] Fix missing comma from conflict resolution --- src/libslic3r/Preset.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 41493f95f6..89c3b1944e 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -851,7 +851,7 @@ static std::vector s_Preset_filament_options { "filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_multitool_ramming", "filament_multitool_ramming_volume", "filament_multitool_ramming_flow", "activate_chamber_temp_control", - "filament_long_retractions_when_cut","filament_retraction_distances_when_cut" + "filament_long_retractions_when_cut","filament_retraction_distances_when_cut", "spoolman_spool_id", "spoolman_remaining_weight", "spoolman_used_weight", "spoolman_remaining_length", "spoolman_used_length", "spoolman_archived" }; From 93f6559e77048bcec9165e0ea8dafb90e14e118d Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Sat, 1 Jun 2024 01:34:23 -0400 Subject: [PATCH 019/100] Add spoolman_host to the secure full config --- src/libslic3r/PresetBundle.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index e1503604bb..c28e32847f 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -2079,7 +2079,8 @@ DynamicPrintConfig PresetBundle::full_config_secure() const config.erase("printhost_cafile"); config.erase("printhost_user"); config.erase("printhost_password"); - config.erase("printhost_port"); + config.erase("printhost_port"); + config.erase("spoolman_host"); return config; } From b19e373a0012340675f59ce09dcd95af55f8fd32 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Sat, 1 Jun 2024 07:09:01 -0400 Subject: [PATCH 020/100] Add sidetext to ConfigDef for stats --- src/libslic3r/PrintConfig.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 1d3edf6d96..df739739ac 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2036,6 +2036,7 @@ def = this->add("filament_loading_speed", coFloats); def->label = L("Remaining Weight"); def->tooltip = L("Remaining weight of the spool"); def->mode = comAdvanced; + def->sidetext = L("g"); def->readonly = true; def->set_default_value(new ConfigOptionFloat()); def->cli = ConfigOptionDef::nocli; @@ -2044,6 +2045,7 @@ def = this->add("filament_loading_speed", coFloats); def->label = L("Used Weight"); def->tooltip = L("Used weight of the spool"); def->mode = comAdvanced; + def->sidetext = L("g"); def->readonly = true; def->set_default_value(new ConfigOptionFloat()); def->cli = ConfigOptionDef::nocli; @@ -2052,6 +2054,7 @@ def = this->add("filament_loading_speed", coFloats); def->label = L("Remaining Length"); def->tooltip = L("Remaining length of the filament on the spool"); def->mode = comAdvanced; + def->sidetext = L("mm"); def->readonly = true; def->set_default_value(new ConfigOptionFloat()); def->cli = ConfigOptionDef::nocli; @@ -2060,6 +2063,7 @@ def = this->add("filament_loading_speed", coFloats); def->label = L("Used Length"); def->tooltip = L("Used length of the filament from the spool"); def->mode = comAdvanced; + def->sidetext = L("mm"); def->readonly = true; def->set_default_value(new ConfigOptionFloat()); def->cli = ConfigOptionDef::nocli; From 61c06a4a4ba33d42f435f2d435c2debc21ec449a Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 5 Jun 2024 01:29:40 -0400 Subject: [PATCH 021/100] Change spoolman_spool_id mode to simple --- src/libslic3r/PrintConfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index df739739ac..12a2dfe514 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2028,7 +2028,7 @@ def = this->add("filament_loading_speed", coFloats); 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 = comAdvanced; + def->mode = comSimple; def->set_default_value(new ConfigOptionInt()); def->cli = ConfigOptionDef::nocli; From b395da52f6dcd42bb27d1b64f75dc032fed357e3 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 5 Jun 2024 01:47:31 -0400 Subject: [PATCH 022/100] Spoolman Statistics Moved to Preset Spoolman statistics are now stored in the filament preset and are not saved to a file --- src/libslic3r/GCode.cpp | 29 ++++++++++++++++++ src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Preset.hpp | 10 ++++++ src/libslic3r/PresetBundle.cpp | 13 ++++++++ src/libslic3r/PrintConfig.cpp | 56 ++++++++-------------------------- src/libslic3r/PrintConfig.hpp | 4 +++ src/slic3r/GUI/Tab.cpp | 48 +++++++++++++++++++++++------ src/slic3r/GUI/Tab.hpp | 1 + src/slic3r/Utils/Spoolman.cpp | 26 ++++++++-------- src/slic3r/Utils/Spoolman.hpp | 1 + 10 files changed, 123 insertions(+), 67 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 467ec4bbeb..23afbf5a34 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1666,6 +1666,35 @@ 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); + } + + 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 % remaining_weight); + print->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, msg); + } + } + } + //BBS: add some log for error output BOOST_LOG_TRIVIAL(debug) << boost::format("Finished processing gcode to %1% ") % path_tmp; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 89c3b1944e..13875d15f8 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -852,7 +852,7 @@ static std::vector s_Preset_filament_options { "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_multitool_ramming", "filament_multitool_ramming_volume", "filament_multitool_ramming_flow", "activate_chamber_temp_control", "filament_long_retractions_when_cut","filament_retraction_distances_when_cut", - "spoolman_spool_id", "spoolman_remaining_weight", "spoolman_used_weight", "spoolman_remaining_length", "spoolman_used_length", "spoolman_archived" + "spoolman_spool_id" }; static std::vector s_Preset_machine_limits_options { diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index ef4e97b721..5e274663b9 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -244,6 +244,16 @@ public: long long updated_time{0}; //last updated time std::map key_values; + // indicate if spoolman is enabled for this preset + bool spoolman_enabled() const { return config.opt_int("spoolman_spool_id") > 0; } + + // Orca: spoolman statistics. these are not stored in the preset file + double spoolman_remaining_length = 0; + double spoolman_remaining_weight = 0; + double spoolman_used_length = 0; + double spoolman_used_weight = 0; + bool spoolman_archived = false; + static std::string get_type_string(Preset::Type type); // get string type for iot static std::string get_iot_type_string(Preset::Type type); diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index c28e32847f..746ddb2f1f 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -2110,6 +2110,9 @@ 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; compatible_printers_condition.emplace_back(this->prints.get_edited_preset().compatible_printers_condition()); @@ -2132,6 +2135,9 @@ 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_remaining_weight); + filament_remaining_length.emplace_back(this->filaments.get_edited_preset().spoolman_remaining_weight); //BBS: add logic for settings check between different system presets //std::string filament_inherits = this->filaments.get_edited_preset().inherits(); std::string current_preset_name = this->filament_presets[0]; @@ -2173,6 +2179,9 @@ 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_remaining_weight); + filament_remaining_length.emplace_back(preset->spoolman_remaining_weight); //BBS: add logic for settings check between different system presets std::string filament_inherits = Preset::inherits(cfg_rw); @@ -2272,6 +2281,10 @@ 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; + // Serialize the collected "compatible_printers_condition" and "inherits" fields. // There will be 1 + num_exturders fields for "inherits" and 2 + num_extruders for "compatible_printers_condition" stored. // The vector will not be stored if all fields are empty strings. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 12a2dfe514..48b3776dc0 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2016,6 +2016,18 @@ def = this->add("filament_loading_speed", coFloats); 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; + + def = this->add("filament_remaining_length", coFloats); + def->set_default_value(new ConfigOptionFloats()); + def->cli = ConfigOptionDef::nocli; + def = this->add("filament_vendor", coStrings); def->label = L("Vendor"); def->tooltip = L("Vendor of filament. For show only"); @@ -2032,50 +2044,6 @@ def = this->add("filament_loading_speed", coFloats); def->set_default_value(new ConfigOptionInt()); def->cli = ConfigOptionDef::nocli; - def = this->add("spoolman_remaining_weight", coFloat); - def->label = L("Remaining Weight"); - def->tooltip = L("Remaining weight of the spool"); - def->mode = comAdvanced; - def->sidetext = L("g"); - def->readonly = true; - def->set_default_value(new ConfigOptionFloat()); - def->cli = ConfigOptionDef::nocli; - - def = this->add("spoolman_used_weight", coFloat); - def->label = L("Used Weight"); - def->tooltip = L("Used weight of the spool"); - def->mode = comAdvanced; - def->sidetext = L("g"); - def->readonly = true; - def->set_default_value(new ConfigOptionFloat()); - def->cli = ConfigOptionDef::nocli; - - def = this->add("spoolman_remaining_length", coFloat); - def->label = L("Remaining Length"); - def->tooltip = L("Remaining length of the filament on the spool"); - def->mode = comAdvanced; - def->sidetext = L("mm"); - def->readonly = true; - def->set_default_value(new ConfigOptionFloat()); - def->cli = ConfigOptionDef::nocli; - - def = this->add("spoolman_used_length", coFloat); - def->label = L("Used Length"); - def->tooltip = L("Used length of the filament from the spool"); - def->mode = comAdvanced; - def->sidetext = L("mm"); - def->readonly = true; - def->set_default_value(new ConfigOptionFloat()); - def->cli = ConfigOptionDef::nocli; - - def = this->add("spoolman_archived", coBool); - def->label = L("Archived"); - def->tooltip = L("Indicates if the spool is archived"); - def->mode = comAdvanced; - def->readonly = true; - def->set_default_value(new ConfigOptionBool()); - def->cli = ConfigOptionDef::nocli; - def = this->add("infill_direction", coFloat); def->label = L("Infill direction"); def->category = L("Strength"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index be3edef9a3..ea408fcd92 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1031,6 +1031,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, fan_kickstart)) ((ConfigOptionBool, fan_speedup_overhangs)) ((ConfigOptionFloat, fan_speedup_time)) + ((ConfigOptionStrings, filament_settings_id)) ((ConfigOptionFloats, filament_diameter)) ((ConfigOptionFloats, filament_density)) ((ConfigOptionStrings, filament_type)) @@ -1134,6 +1135,9 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionStrings, small_area_infill_flow_compensation_model)) ((ConfigOptionBool, has_scarf_joint_seam)) + ((ConfigOptionBools, filament_spoolman_enabled)) + ((ConfigOptionFloats, filament_remaining_weight)) + ((ConfigOptionFloats, filament_remaining_length)) ) // This object is mapped to Perl as Slic3r::Config::Print. diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index f4a8b7da60..1eccfe5f5a 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3191,6 +3191,19 @@ void TabFilament::update_filament_overrides_page() } } +void TabFilament::update_spoolman_statistics() { + Preset* selected_preset = &m_presets->get_selected_preset(); + + if (!selected_preset->spoolman_enabled()) + return; + + m_active_page->get_field("spoolman_remaining_weight")->set_value(double_to_string(selected_preset->spoolman_remaining_weight, 2), false); + m_active_page->get_field("spoolman_used_weight")->set_value(double_to_string(selected_preset->spoolman_used_weight, 2), false); + m_active_page->get_field("spoolman_remaining_length")->set_value(double_to_string(selected_preset->spoolman_remaining_length * 0.001, 2), false); + m_active_page->get_field("spoolman_used_length")->set_value(double_to_string(selected_preset->spoolman_used_length * 0.001, 2), false); + m_active_page->get_field("spoolman_archived")->set_value(selected_preset->spoolman_archived, false); +} + void TabFilament::build() { m_presets = &m_preset_bundle->filaments; @@ -3404,7 +3417,7 @@ void TabFilament::build() return; } if (!Spoolman::is_server_valid()) { - show_error(parent, "Failed to get data from the Spoolman server. Make sure that the port is correct and the server is running."); + show_error(this, "Failed to get data from the Spoolman server. Make sure that the port is correct and the server is running."); return; } auto res1 = Spoolman::update_filament_preset_from_spool(&m_presets->get_edited_preset(), true, stats_only); @@ -3413,9 +3426,7 @@ void TabFilament::build() if (res1.has_failed() || res2.has_failed()) return; - const Preset* preset = m_presets->get_selected_preset_parent(); - m_presets->get_selected_preset().save(preset ? &preset->config : nullptr); - update_dirty(); + update_spoolman_statistics(); }; auto refresh_all_btn = new wxButton(parent, wxID_ANY, _L("Update Filament")); @@ -3431,12 +3442,28 @@ void TabFilament::build() }; optgroup->append_line(line); - optgroup = page->new_optgroup("Spool Statistics"); - optgroup->append_single_option_line("spoolman_remaining_weight"); - optgroup->append_single_option_line("spoolman_used_weight"); - optgroup->append_single_option_line("spoolman_remaining_length"); - optgroup->append_single_option_line("spoolman_used_length"); - optgroup->append_single_option_line("spoolman_archived"); + optgroup = page->new_optgroup(_L("Spool Statistics")); + + auto build_statistics_line = [&](const std::string& key, const std::string& label, + const std::string& sidetext, const ConfigOptionType& type = coFloat) { + auto def = ConfigOptionDef(); + def.opt_key = key; + def.label = label; + def.type = type; + def.sidetext = sidetext; + def.readonly = true; + if (type == coFloat) + def.set_default_value(new ConfigOptionFloat()); + else if (type == coBool) + def.set_default_value(new ConfigOptionBool()); + return optgroup->append_single_option_line(Option(def, key)); + }; + + build_statistics_line("spoolman_remaining_weight", "Remaining Weight", "g"); + build_statistics_line("spoolman_used_weight", "Used Weight", "g"); + build_statistics_line("spoolman_remaining_length", "Remaining Length", "m"); + build_statistics_line("spoolman_used_length", "Used Length", "m"); + build_statistics_line("spoolman_archived", "Archived", "", coBool); page->m_should_show_fn = [&](bool current_value) { return m_preset_bundle->printers.get_edited_preset().config.opt_bool("spoolman_enabled"); @@ -3589,6 +3616,7 @@ void TabFilament::toggle_options() if (m_active_page->title() == L("Spoolman")) { toggle_line("spoolman_update", m_config->opt_int("spoolman_spool_id") > 0); + update_spoolman_statistics(); } } diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index df33bb4905..7ad32a9398 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -574,6 +574,7 @@ private: void add_filament_overrides_page(); void update_filament_overrides_page(); void update_volumetric_flow_preset_hints(); + void update_spoolman_statistics(); std::map m_overrides_options; diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index f8f96c9afe..6123718f05 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -16,9 +16,6 @@ template Type get_opt(pt::ptree& data, string path) { return data.ge // Spoolman //--------------------------------- -static vector statistics_keys = {"spoolman_remaining_weight", "spoolman_used_weight", "spoolman_remaining_length", - "spoolman_used_length", "spoolman_archived"}; - pt::ptree Spoolman::get_spoolman_json(const string& api_endpoint) { DynamicPrintConfig& config = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config; @@ -167,7 +164,7 @@ SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolSh preset->config.apply(base_profile->config); preset->config.set_key_value("filament_settings_id", new ConfigOptionStrings({filament_preset_name})); preset->config.set("inherits", inherits, true); - spool->apply_to_config(preset->config); + spool->apply_to_preset(preset); preset->filament_id = get_filament_id(filament_preset_name); preset->version = base_profile->version; preset->loaded = true; @@ -178,7 +175,6 @@ SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolSh SpoolmanResult Spoolman::update_filament_preset_from_spool(Preset* filament_preset, bool update_from_server, bool only_update_statistics) { - DynamicConfig config; SpoolmanResult result; const int& spool_id = filament_preset->config.opt_int("spoolman_spool_id"); @@ -190,8 +186,7 @@ SpoolmanResult Spoolman::update_filament_preset_from_spool(Preset* filament_pres SpoolmanSpoolShrPtr& spool = get_instance()->m_spools[spool_id]; if (update_from_server) spool->update_from_server(!only_update_statistics); - spool->apply_to_config(config); - filament_preset->config.apply_only(config, only_update_statistics ? statistics_keys : config.keys()); + spool->apply_to_preset(filament_preset, only_update_statistics); return result; } @@ -284,14 +279,21 @@ void SpoolmanSpool::update_from_server(bool recursive) void SpoolmanSpool::apply_to_config(Slic3r::DynamicConfig& config) const { config.set_key_value("spoolman_spool_id", new ConfigOptionInt(id)); - config.set_key_value("spoolman_remaining_weight", new ConfigOptionFloat(remaining_weight)); - config.set_key_value("spoolman_used_weight", new ConfigOptionFloat(used_weight)); - config.set_key_value("spoolman_remaining_length", new ConfigOptionFloat(remaining_length)); - config.set_key_value("spoolman_used_length", new ConfigOptionFloat(used_length)); - config.set_key_value("spoolman_archived", new ConfigOptionBool(archived)); m_filament_ptr->apply_to_config(config); } +void SpoolmanSpool::apply_to_preset(Preset* preset, bool only_update_statistics) const +{ + preset->spoolman_remaining_weight = remaining_weight; + preset->spoolman_used_weight = used_weight; + preset->spoolman_remaining_length = remaining_length; + preset->spoolman_used_length = used_length; + preset->spoolman_archived = archived; + if (only_update_statistics) + return; + this->apply_to_config(preset->config); +} + void SpoolmanSpool::update_from_json(pt::ptree json_data) { if (int filament_id = json_data.get("filament.id"); m_filament_ptr && m_filament_ptr->id != filament_id) { diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index e9cfe2e4b9..224c9d9d1d 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -161,6 +161,7 @@ public: void update_from_server(bool recursive = false); void apply_to_config(DynamicConfig& config) const; + void apply_to_preset(Preset* preset, bool only_update_statistics = false) const; private: Spoolman* m_spoolman; From 5511dbddbfe3c7e67f0d61d1a157ba846b8ab62c Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 5 Jun 2024 02:10:36 -0400 Subject: [PATCH 023/100] Auto Update Spoolman Preset Statistics On GUI Init and printer preset change, the spoolman statistics are updated on all visible user filament profiles with spoolman feature enabled. --- src/libslic3r/Preset.hpp | 8 ++++++++ src/libslic3r/PresetBundle.cpp | 12 +++++++++++- src/libslic3r/PresetBundle.hpp | 3 +++ src/slic3r/GUI/GUI_App.cpp | 2 ++ src/slic3r/GUI/Plater.cpp | 3 +++ 5 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 5e274663b9..2d82c764dc 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -654,6 +654,14 @@ public: size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); } + std::vector get_visible() { + std::vector ret; + for (auto& item : m_presets) + if (item.is_visible) + ret.emplace_back(&item); + return ret; + } + // Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ. bool current_is_dirty() const { return is_dirty(&this->get_edited_preset(), &this->get_selected_preset()); } diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 746ddb2f1f..77e624c4ec 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -23,7 +23,7 @@ #include #include #include - +#include // Store the print/filament/printer presets into a "presets" subdirectory of the Slic3rPE config dir. // This breaks compatibility with the upstream Slic3r if the --datadir is used to switch between the two versions. @@ -4157,4 +4157,14 @@ void PresetBundle::set_default_suppressed(bool default_suppressed) printers.set_default_suppressed(default_suppressed); } +void PresetBundle::update_spoolman_statistics() { + if (printers.get_edited_preset().config.opt_bool("spoolman_enabled") && Spoolman::is_server_valid()) { + for (auto& item : filaments.get_visible()) { + if (item->is_user() && item->spoolman_enabled()) { + Spoolman::update_filament_preset_from_spool(item, true, true); + } + } + } +} + } // namespace Slic3r diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 4cc62a3ed9..908223b62d 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -245,6 +245,9 @@ public: void update_compatible(PresetSelectCompatibleType select_other_print_if_incompatible, PresetSelectCompatibleType select_other_filament_if_incompatible); void update_compatible(PresetSelectCompatibleType select_other_if_incompatible) { this->update_compatible(select_other_if_incompatible, select_other_if_incompatible); } + // Update the statistics values for the visible filament profiles with spoolman enabled + void update_spoolman_statistics(); + // Set the is_visible flag for printer vendors, printer models and printer variants // based on the user configuration. // If the "vendor" section is missing, enable all models and variants of the particular vendor. diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index e10fb62c1c..56d8d06bef 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2437,6 +2437,8 @@ bool GUI_App::on_init_inner() } //} + preset_bundle->update_spoolman_statistics(); + #ifdef WIN32 #if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) register_win32_dpi_event(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 384c6f0b74..2a93f8bb91 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6388,6 +6388,9 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) view3D->deselect_all(); } + + wxGetApp().preset_bundle->update_spoolman_statistics(); + #if 0 // do not toggle auto calc when change printer // update flush matrix size_t filament_size = wxGetApp().plater()->get_extruder_colors_from_plater_config().size(); From 98e769b6a870a05ee322b5cfabdcb2f4164bd340 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Thu, 6 Jun 2024 08:17:43 -0400 Subject: [PATCH 024/100] Fix build on non-windows systems --- src/slic3r/GUI/SpoolmanImportDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/SpoolmanImportDialog.cpp b/src/slic3r/GUI/SpoolmanImportDialog.cpp index 70cced223e..e686b819ca 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.cpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.cpp @@ -93,7 +93,7 @@ SpoolmanViewCtrl::SpoolmanViewCtrl(wxWindow* parent) : wxDataViewCtrl(parent, wx wxGetApp().UpdateDVCDarkUI(this); #if _WIN32 ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_DEFAULT); -#elif +#else SetScrollbar(wxHORIZONTAL, 0, 0, 0) #endif From 30d3af1044fcfb5c4eecc4a71c05e15b0d8d062d Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Sat, 8 Jun 2024 03:41:48 -0400 Subject: [PATCH 025/100] Fix build on non-windows systems, the sequel --- src/slic3r/GUI/SpoolmanImportDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/SpoolmanImportDialog.cpp b/src/slic3r/GUI/SpoolmanImportDialog.cpp index e686b819ca..6ea93968af 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.cpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.cpp @@ -94,7 +94,7 @@ SpoolmanViewCtrl::SpoolmanViewCtrl(wxWindow* parent) : wxDataViewCtrl(parent, wx #if _WIN32 ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_DEFAULT); #else - SetScrollbar(wxHORIZONTAL, 0, 0, 0) + SetScrollbar(wxHORIZONTAL, 0, 0, 0); #endif m_model = new SpoolmanViewModel(); From 4cc9dc272d80083a58b62013938e5a2211aa6fc2 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Mon, 17 Jun 2024 23:04:24 -0400 Subject: [PATCH 026/100] Improvements to update_spoolman_statistics and related functions - Clears spoolman instance if changing/updating the printer profile - Add debug messages upon update failure - Spoolman.cpp: Check if preset is a filament preset and check if the spool exists before attempting to update it - Preset.hpp: update spoolman_enabled function to work with filament and printer profiles --- src/libslic3r/Preset.hpp | 9 ++++++++- src/libslic3r/PresetBundle.cpp | 11 ++++++++--- src/libslic3r/PresetBundle.hpp | 3 ++- src/slic3r/GUI/Plater.cpp | 2 +- src/slic3r/Utils/Spoolman.cpp | 12 +++++++++--- src/slic3r/Utils/Spoolman.hpp | 8 ++++++++ 6 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 824233b7ac..b1b30464a3 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -246,7 +246,14 @@ public: std::map key_values; // indicate if spoolman is enabled for this preset - bool spoolman_enabled() const { return config.opt_int("spoolman_spool_id") > 0; } + // 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; + if (type == TYPE_PRINTER) + return config.opt_bool("spoolman_enabled"); + return false; + } // Orca: spoolman statistics. these are not stored in the preset file double spoolman_remaining_length = 0; diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index e3daf3bb41..029009d866 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -4161,11 +4161,16 @@ void PresetBundle::set_default_suppressed(bool default_suppressed) printers.set_default_suppressed(default_suppressed); } -void PresetBundle::update_spoolman_statistics() { - if (printers.get_edited_preset().config.opt_bool("spoolman_enabled") && Spoolman::is_server_valid()) { +void PresetBundle::update_spoolman_statistics(bool updating_printer) { + // If the printer profile is being switched, clear the spoolman instance. + // This is done, so it can be populated by the correct spoolman instance for the new printer profile + if (updating_printer) Spoolman::get_instance()->clear(); + if (printers.get_edited_preset().spoolman_enabled() && Spoolman::is_server_valid()) { for (auto& item : filaments.get_visible()) { if (item->is_user() && item->spoolman_enabled()) { - Spoolman::update_filament_preset_from_spool(item, true, true); + if (auto res = Spoolman::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.messages[0] << ". Spool ID: " << item->config.opt_int("spoolman_spool_id"); } } } diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 908223b62d..8d3f9e1bdc 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -246,7 +246,8 @@ public: void update_compatible(PresetSelectCompatibleType select_other_if_incompatible) { this->update_compatible(select_other_if_incompatible, select_other_if_incompatible); } // Update the statistics values for the visible filament profiles with spoolman enabled - void update_spoolman_statistics(); + // updating_printer should be set true if the update is due to a change in printer profile + void update_spoolman_statistics(bool updating_printer = false); // Set the is_visible flag for printer vendors, printer models and printer variants // based on the user configuration. diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 589104ec6e..514b76faac 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6522,7 +6522,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) view3D->deselect_all(); } - wxGetApp().preset_bundle->update_spoolman_statistics(); + wxGetApp().preset_bundle->update_spoolman_statistics(true); #if 0 // do not toggle auto calc when change printer // update flush matrix diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 6123718f05..76066bc19c 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -78,9 +78,7 @@ bool Spoolman::pull_spoolman_spools() { pt::ptree tree; - m_vendors.clear(); - m_filaments.clear(); - m_spools.clear(); + this->clear(); // Vendor tree = get_spoolman_json("vendor"); @@ -177,6 +175,10 @@ SpoolmanResult Spoolman::update_filament_preset_from_spool(Preset* filament_pres { DynamicConfig config; SpoolmanResult result; + if (filament_preset->type != Preset::TYPE_FILAMENT) { + result.messages.emplace_back("Preset is not a filament preset"); + return result; + } const int& spool_id = filament_preset->config.opt_int("spoolman_spool_id"); if (spool_id < 1) { result.messages.emplace_back( @@ -184,6 +186,10 @@ SpoolmanResult Spoolman::update_filament_preset_from_spool(Preset* filament_pres return result; } SpoolmanSpoolShrPtr& spool = get_instance()->m_spools[spool_id]; + if (!spool) { + result.messages.emplace_back("The spool ID does not exist in the local spool cache"); + return result; + } if (update_from_server) spool->update_from_server(!only_update_statistics); spool->apply_to_preset(filament_preset, only_update_statistics); diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index 224c9d9d1d..bba102b88d 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -72,6 +72,14 @@ public: return m_spools[spool_id]; }; + void clear() + { + m_spools.clear(); + m_filaments.clear(); + m_vendors.clear(); + m_initialized = false; + } + static Spoolman* get_instance() { if (!m_instance) From ac4d48106348b82f47a5818ad0cd8db4a8a3cd4c Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Tue, 18 Jun 2024 00:52:24 -0400 Subject: [PATCH 027/100] Update Spoolman Stats via PhysicalPrinterDialog If any spoolman-related keys are updated in the PhysicalPrinterDialog, call update_spoolman_statistics --- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 5d0e6b8022..6ee9b5643d 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -702,7 +702,20 @@ void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) void PhysicalPrinterDialog::OnOK(wxEvent& event) { + // determine if any spoolman related keys have been updated + bool update_spool_stats = false; + const vector& current_dirty_options = m_presets->current_dirty_options(); + if (!current_dirty_options.empty()) { + for (const auto& option_key : {"spoolman_enabled", "spoolman_host", "print_host"}) { + if (std::find(current_dirty_options.begin(), current_dirty_options.end(), option_key) != current_dirty_options.end()) { + update_spool_stats = true; + break; + } + } + } wxGetApp().get_tab(Preset::TYPE_PRINTER)->save_preset("", false, false, true, m_preset_name ); + if (update_spool_stats) // only update spoolman if spoolman related keys were changed + wxGetApp().preset_bundle->update_spoolman_statistics(true); event.Skip(); } From 35475fd8c8ad4f70e6a3ffb6a97c24ab7cf50f90 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Tue, 25 Jun 2024 05:55:25 +0000 Subject: [PATCH 028/100] Actually fix Linux and macOS builds --- src/slic3r/GUI/SpoolmanImportDialog.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/SpoolmanImportDialog.cpp b/src/slic3r/GUI/SpoolmanImportDialog.cpp index 6ea93968af..e355a5c152 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.cpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.cpp @@ -170,7 +170,11 @@ SpoolmanImportDialog::SpoolmanImportDialog(wxWindow* parent) int colWidth = 8 * EM; // 4 EM for checkbox (width isn't calculated right), 4 EM for border for (int i = COL_ID; i <= COL_MATERIAL; ++i) { - colWidth +=m_svc->GetColumnAt(i)->GetWidth(); +#ifdef _WIN32 + colWidth += m_svc->GetColumnAt(i)->GetWidth(); +#else + colWidth += m_svc->GetColumn(i)->GetWidth(); +#endif // _WIN32 } this->SetSize(wxDefaultCoord, wxDefaultCoord, colWidth, wxDefaultCoord, wxSIZE_SET_CURRENT); From d253098850c1af69ace335f7bcd7ac14d31d304e Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Mon, 24 Jun 2024 00:43:16 -0400 Subject: [PATCH 029/100] Fix typos --- src/libslic3r/GCode.cpp | 2 +- src/libslic3r/PresetBundle.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index a4f2d8eb45..b502a1bc4c 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1647,7 +1647,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu 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 % remaining_weight); + 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); } } diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 859df030b1..cdc21859db 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -2153,7 +2153,7 @@ DynamicPrintConfig PresetBundle::full_fff_config() const 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_remaining_weight); - filament_remaining_length.emplace_back(this->filaments.get_edited_preset().spoolman_remaining_weight); + filament_remaining_length.emplace_back(this->filaments.get_edited_preset().spoolman_remaining_length); //BBS: add logic for settings check between different system presets //std::string filament_inherits = this->filaments.get_edited_preset().inherits(); std::string current_preset_name = this->filament_presets[0]; @@ -2197,7 +2197,7 @@ DynamicPrintConfig PresetBundle::full_fff_config() const 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_remaining_weight); - filament_remaining_length.emplace_back(preset->spoolman_remaining_weight); + filament_remaining_length.emplace_back(preset->spoolman_remaining_length); //BBS: add logic for settings check between different system presets std::string filament_inherits = Preset::inherits(cfg_rw); @@ -4180,7 +4180,7 @@ void PresetBundle::update_spoolman_statistics(bool updating_printer) { // This is done, so it can be populated by the correct spoolman instance for the new printer profile if (updating_printer) Spoolman::get_instance()->clear(); if (printers.get_edited_preset().spoolman_enabled() && Spoolman::is_server_valid()) { - for (auto& item : filaments.get_visible()) { + for (auto item : filaments.get_visible()) { if (item->is_user() && item->spoolman_enabled()) { if (auto res = Spoolman::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: " From 8a51eb4b1e93013ce1288d978f5754642fb95edc Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Mon, 24 Jun 2024 03:44:29 -0400 Subject: [PATCH 030/100] Move spoolman statistics to a struct The struct is stored in a shared ptr. This is done so that if the statistics are updated on the edited preset, the changes will be reflected on the saved preset and vice versa --- src/libslic3r/Preset.hpp | 17 +++++++++++------ src/libslic3r/PresetBundle.cpp | 8 ++++---- src/slic3r/GUI/Tab.cpp | 17 +++++++++-------- src/slic3r/Utils/Spoolman.cpp | 11 ++++++----- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index b1b30464a3..64f1e5a27f 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -255,12 +255,17 @@ public: return false; } - // Orca: spoolman statistics. these are not stored in the preset file - double spoolman_remaining_length = 0; - double spoolman_remaining_weight = 0; - double spoolman_used_length = 0; - double spoolman_used_weight = 0; - bool spoolman_archived = false; + struct SpoolmanStatistics { + // Orca: spoolman statistics. these are not stored in the preset file + double remaining_length = 0; + double remaining_weight = 0; + double used_length = 0; + double used_weight = 0; + bool archived = false; + }; + + // the statistics a ptr so that they are a shared value for both the saved and edited preset + std::shared_ptr spoolman_statistics = std::make_shared(); static std::string get_type_string(Preset::Type type); // get string type for iot diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index cdc21859db..a700aedf90 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -2152,8 +2152,8 @@ DynamicPrintConfig PresetBundle::full_fff_config() const 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_remaining_weight); - filament_remaining_length.emplace_back(this->filaments.get_edited_preset().spoolman_remaining_length); + 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 //std::string filament_inherits = this->filaments.get_edited_preset().inherits(); std::string current_preset_name = this->filament_presets[0]; @@ -2196,8 +2196,8 @@ DynamicPrintConfig PresetBundle::full_fff_config() const 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_remaining_weight); - filament_remaining_length.emplace_back(preset->spoolman_remaining_length); + filament_remaining_weight.emplace_back(preset->spoolman_statistics->remaining_weight); + filament_remaining_length.emplace_back(preset->spoolman_statistics->remaining_length); //BBS: add logic for settings check between different system presets std::string filament_inherits = Preset::inherits(cfg_rw); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 16a6e08b61..23c0fd1c3e 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3190,11 +3190,13 @@ void TabFilament::update_spoolman_statistics() { if (!selected_preset->spoolman_enabled()) return; - m_active_page->get_field("spoolman_remaining_weight")->set_value(double_to_string(selected_preset->spoolman_remaining_weight, 2), false); - m_active_page->get_field("spoolman_used_weight")->set_value(double_to_string(selected_preset->spoolman_used_weight, 2), false); - m_active_page->get_field("spoolman_remaining_length")->set_value(double_to_string(selected_preset->spoolman_remaining_length * 0.001, 2), false); - m_active_page->get_field("spoolman_used_length")->set_value(double_to_string(selected_preset->spoolman_used_length * 0.001, 2), false); - m_active_page->get_field("spoolman_archived")->set_value(selected_preset->spoolman_archived, false); + auto spoolman_stats = selected_preset->spoolman_statistics; + + m_active_page->get_field("spoolman_remaining_weight")->set_value(double_to_string(spoolman_stats->remaining_weight, 2), false); + m_active_page->get_field("spoolman_used_weight")->set_value(double_to_string(spoolman_stats->used_weight, 2), false); + m_active_page->get_field("spoolman_remaining_length")->set_value(double_to_string(spoolman_stats->remaining_length * 0.001, 2), false); + m_active_page->get_field("spoolman_used_length")->set_value(double_to_string(spoolman_stats->used_length * 0.001, 2), false); + m_active_page->get_field("spoolman_archived")->set_value(spoolman_stats->archived, false); } void TabFilament::build() @@ -3414,10 +3416,9 @@ void TabFilament::build() show_error(this, "Failed to get data from the Spoolman server. Make sure that the port is correct and the server is running."); return; } - auto res1 = Spoolman::update_filament_preset_from_spool(&m_presets->get_edited_preset(), true, stats_only); - auto res2 = Spoolman::update_filament_preset_from_spool(&m_presets->get_selected_preset(), false, stats_only); + auto res = Spoolman::update_filament_preset_from_spool(&m_presets->get_selected_preset(), false, stats_only); - if (res1.has_failed() || res2.has_failed()) + if (res.has_failed()) return; update_spoolman_statistics(); diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 76066bc19c..c06c49e635 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -290,11 +290,12 @@ void SpoolmanSpool::apply_to_config(Slic3r::DynamicConfig& config) const void SpoolmanSpool::apply_to_preset(Preset* preset, bool only_update_statistics) const { - preset->spoolman_remaining_weight = remaining_weight; - preset->spoolman_used_weight = used_weight; - preset->spoolman_remaining_length = remaining_length; - preset->spoolman_used_length = used_length; - preset->spoolman_archived = archived; + auto spoolman_stats = preset->spoolman_statistics; + spoolman_stats->remaining_weight = remaining_weight; + spoolman_stats->used_weight = used_weight; + spoolman_stats->remaining_length = remaining_length; + spoolman_stats->used_length = used_length; + spoolman_stats->archived = archived; if (only_update_statistics) return; this->apply_to_config(preset->config); From bf5f28d39e8e4c98684b6cf8a758ca385fb2329e Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Tue, 25 Jun 2024 06:46:29 -0400 Subject: [PATCH 031/100] Change to unsigned int for spool id --- src/slic3r/Utils/Spoolman.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index bba102b88d..21945614e9 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -30,9 +30,9 @@ class Spoolman bool m_initialized{false}; - std::map m_vendors{}; - std::map m_filaments{}; - std::map m_spools{}; + std::map m_vendors{}; + std::map m_filaments{}; + std::map m_spools{}; Spoolman() { @@ -58,14 +58,14 @@ public: static bool is_server_valid(); - const std::map& get_spoolman_spools(bool update = false) + const std::map& get_spoolman_spools(bool update = false) { if (update || !m_initialized) m_initialized = pull_spoolman_spools(); return m_spools; }; - SpoolmanSpoolShrPtr get_spoolman_spool_by_id(int spool_id, bool update = false) + SpoolmanSpoolShrPtr get_spoolman_spool_by_id(unsigned int spool_id, bool update = false) { if (update || !m_initialized) m_initialized = pull_spoolman_spools(); From 0557851e5e0c5cce937effbc413a62cbb15fa93b Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Tue, 25 Jun 2024 06:48:20 -0400 Subject: [PATCH 032/100] Implement backend for using filament --- src/slic3r/Utils/Spoolman.cpp | 66 +++++++++++++++++++++++++++++++++-- src/slic3r/Utils/Spoolman.hpp | 6 +++- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index c06c49e635..88440be203 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -16,7 +16,7 @@ template Type get_opt(pt::ptree& data, string path) { return data.ge // Spoolman //--------------------------------- -pt::ptree Spoolman::get_spoolman_json(const string& api_endpoint) +static std::string get_spoolman_server_url() { DynamicPrintConfig& config = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config; string host = config.opt_string("print_host"); @@ -38,7 +38,12 @@ pt::ptree Spoolman::get_spoolman_json(const string& api_endpoint) if (spoolman_host.empty()) spoolman_host = host; - auto url = spoolman_host + ":" + spoolman_port + "/api/v1/" + api_endpoint; + return spoolman_host + ":" + spoolman_port; +} + +pt::ptree Spoolman::get_spoolman_json(const string& api_endpoint) +{ + auto url = get_spoolman_server_url() + "/api/v1/" + api_endpoint; auto http = Http::get(url); bool res; @@ -74,6 +79,49 @@ pt::ptree Spoolman::get_spoolman_json(const string& api_endpoint) return tree; } +pt::ptree Spoolman::put_spoolman_json(const string& api_endpoint, const pt::ptree& data) +{ + auto url = get_spoolman_server_url() + "/api/v1/" + api_endpoint; + auto http = Http::put2(url); + + bool res; + std::string res_body; + + stringstream ss; + pt::write_json(ss, data); + + http.header("Content-Type", "application/json") + .set_post_body(ss.str()) + .on_error([&](const std::string& body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << "Failed to put data to the Spoolman server. Make sure that the port is correct and the server is running." << boost::format(" HTTP Error: %1%, HTTP status code: %2%, Response body: %3%") % error % status % body; + res = false; + }) + .on_complete([&](std::string body, unsigned) { + res_body = std::move(body); + res = true; + }) + .perform_sync(); + + if (!res) + return {}; + + if (res_body.empty()) { + BOOST_LOG_TRIVIAL(info) << "Spoolman request returned an empty string"; + return {}; + } + + pt::ptree tree; + try { + ss = stringstream(res_body); + pt::read_json(ss, tree); + } catch (std::exception& exception) { + BOOST_LOG_TRIVIAL(error) << "Failed to read json into property tree. Exception: " << exception.what(); + return {}; + } + + return tree; +} + bool Spoolman::pull_spoolman_spools() { pt::ptree tree; @@ -104,6 +152,20 @@ bool Spoolman::pull_spoolman_spools() return true; } +bool Spoolman::use_spoolman_spool(const unsigned int& spool_id, const double& weight_used) +{ + pt::ptree tree; + tree.put("use_weight", weight_used); + + std::string endpoint = (boost::format("spool/%1%/use") % spool_id).str(); + tree = put_spoolman_json(endpoint, tree); + if (tree.empty()) + return false; + + get_spoolman_spool_by_id(spool_id)->update_from_json(tree); + return true; +} + SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile, bool detach, diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index 21945614e9..12dbbe38f4 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -41,11 +41,15 @@ class Spoolman m_initialized = pull_spoolman_spools(); }; + /// gets the json response from the specified API endpoint static pt::ptree get_spoolman_json(const std::string& api_endpoint); + /// puts the provided data to the specified API endpoint and returns the response + static pt::ptree put_spoolman_json(const std::string& api_endpoint, const pt::ptree& data); /// get all the spools from the api and store them bool pull_spoolman_spools(); - public: + /// uses/consumes filament from the specified spool + bool use_spoolman_spool(const unsigned int& spool_id, const double& weight_used); static SpoolmanResult create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile, bool detach = false, From 5c4080a00d0f3897025b4cd53a40120515e2863c Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Tue, 25 Jun 2024 07:39:52 -0400 Subject: [PATCH 033/100] Refactor get_spoolman_api_url --- src/slic3r/Utils/Spoolman.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 88440be203..e75be03663 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -16,7 +16,7 @@ template Type get_opt(pt::ptree& data, string path) { return data.ge // Spoolman //--------------------------------- -static std::string get_spoolman_server_url() +static std::string get_spoolman_api_url() { DynamicPrintConfig& config = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config; string host = config.opt_string("print_host"); @@ -38,12 +38,12 @@ static std::string get_spoolman_server_url() if (spoolman_host.empty()) spoolman_host = host; - return spoolman_host + ":" + spoolman_port; + return spoolman_host + ":" + spoolman_port + "/api/v1/"; } pt::ptree Spoolman::get_spoolman_json(const string& api_endpoint) { - auto url = get_spoolman_server_url() + "/api/v1/" + api_endpoint; + auto url = get_spoolman_api_url() + api_endpoint; auto http = Http::get(url); bool res; @@ -81,7 +81,7 @@ pt::ptree Spoolman::get_spoolman_json(const string& api_endpoint) pt::ptree Spoolman::put_spoolman_json(const string& api_endpoint, const pt::ptree& data) { - auto url = get_spoolman_server_url() + "/api/v1/" + api_endpoint; + auto url = get_spoolman_api_url() + api_endpoint; auto http = Http::put2(url); bool res; From b652e9861bb4289531331d44a84535d2af19d82b Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Tue, 25 Jun 2024 08:17:38 -0400 Subject: [PATCH 034/100] Add comments and refactor --- src/slic3r/GUI/SpoolmanImportDialog.cpp | 4 ++-- src/slic3r/Utils/Spoolman.cpp | 10 +++++----- src/slic3r/Utils/Spoolman.hpp | 14 ++++++++++---- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/SpoolmanImportDialog.cpp b/src/slic3r/GUI/SpoolmanImportDialog.cpp index e355a5c152..b7bb560aa9 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.cpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.cpp @@ -202,7 +202,7 @@ void SpoolmanImportDialog::on_import() } for (const auto& spool : spools) - if (Spoolman::get_name_from_spool(spool) == current_preset->name) { + if (spool->get_preset_name() == current_preset->name) { show_error(this, "One of the selected spools is the same as the current base preset.\n" "Please deselect that spool or select a different base preset."); return; @@ -243,7 +243,7 @@ void SpoolmanImportDialog::on_import() std::stringstream error_message; for (const auto& msg_pair : sorted_error_messages) { for (const auto& errored_spool : msg_pair.second) - error_message << Spoolman::get_name_from_spool(errored_spool) << ",\n"; + error_message << errored_spool->get_preset_name() << ",\n"; error_message.seekp(-2, ios_base::end); error_message << ":\n"; error_message << "\t" << msg_pair.first << std::endl << std::endl; diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index e75be03663..1c0bb8fe26 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -258,11 +258,6 @@ SpoolmanResult Spoolman::update_filament_preset_from_spool(Preset* filament_pres return result; } -std::string Spoolman::get_name_from_spool(const SpoolmanSpoolShrPtr& spool) -{ - return remove_special_key(spool->getVendor()->name + " " + spool->m_filament_ptr->name + " " + spool->m_filament_ptr->material); -} - bool Spoolman::is_server_valid() { return !get_spoolman_json("info").empty(); @@ -344,6 +339,11 @@ void SpoolmanSpool::update_from_server(bool recursive) } } +std::string SpoolmanSpool::get_preset_name() +{ + return remove_special_key(getVendor()->name + " " + m_filament_ptr->name + " " + m_filament_ptr->material); +} + void SpoolmanSpool::apply_to_config(Slic3r::DynamicConfig& config) const { config.set_key_value("spoolman_spool_id", new ConfigOptionInt(id)); diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index 12dbbe38f4..b2f5818d53 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -43,12 +43,17 @@ class Spoolman /// gets the json response from the specified API endpoint static pt::ptree get_spoolman_json(const std::string& api_endpoint); - /// puts the provided data to the specified API endpoint and returns the response + + /// puts the provided data to the specified API endpoint + /// \returns the json response static pt::ptree put_spoolman_json(const std::string& api_endpoint, const pt::ptree& data); + /// 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 + /// uses/consumes filament from the specified spool then updates the spool + /// \returns if succeeded bool use_spoolman_spool(const unsigned int& spool_id, const double& weight_used); static SpoolmanResult create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile, @@ -58,8 +63,6 @@ public: bool update_from_server = true, bool only_update_statistics = false); - static std::string get_name_from_spool(const SpoolmanSpoolShrPtr& spool); - static bool is_server_valid(); const std::map& get_spoolman_spools(bool update = false) @@ -172,6 +175,9 @@ public: void update_from_server(bool recursive = false); + /// builds a preset name based on spool data + std::string get_preset_name(); + void apply_to_config(DynamicConfig& config) const; void apply_to_preset(Preset* preset, bool only_update_statistics = false) const; From 393fc70dd85f68151664bdb6829c0e363cae2f44 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Tue, 25 Jun 2024 19:07:31 -0400 Subject: [PATCH 035/100] Add functions to build error messages to SpoolmanResult --- src/libslic3r/PresetBundle.cpp | 2 +- src/slic3r/Utils/Spoolman.hpp | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index a700aedf90..d8196faeea 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -4184,7 +4184,7 @@ void PresetBundle::update_spoolman_statistics(bool updating_printer) { if (item->is_user() && item->spoolman_enabled()) { if (auto res = Spoolman::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.messages[0] << ". Spool ID: " << item->config.opt_int("spoolman_spool_id"); + << res.build_single_line_message() << "Spool ID: " << item->config.opt_int("spoolman_spool_id"); } } } diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index b2f5818d53..44caf2ef25 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -18,6 +18,26 @@ struct SpoolmanResult { SpoolmanResult() = default; bool has_failed() { return !messages.empty(); } + std::string build_error_dialog_message() { + if (!has_failed()) return {}; + std::string message = messages.size() > 1 ? "Multiple errors:\n" : "Error:\n"; + + for (const auto& error : messages) { + message += error + "\n"; + } + + return message; + } + std::string build_single_line_message() { + if (!has_failed()) return {}; + std::string message = messages.size() > 1 ? "Multiple errors: " : "Error: "; + + for (const auto& error : messages) { + message += error + ". "; + } + + return message; + } std::vector messages{}; }; From 51bcb8120a622eb4689c3ea43b806e0ebe2d5980 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Tue, 25 Jun 2024 19:29:41 -0400 Subject: [PATCH 036/100] Rename updating_printer to clear_cache to be more descriptive --- src/libslic3r/PresetBundle.cpp | 7 +++---- src/libslic3r/PresetBundle.hpp | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index d8196faeea..016a426230 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -4175,10 +4175,9 @@ void PresetBundle::set_default_suppressed(bool default_suppressed) printers.set_default_suppressed(default_suppressed); } -void PresetBundle::update_spoolman_statistics(bool updating_printer) { - // If the printer profile is being switched, clear the spoolman instance. - // This is done, so it can be populated by the correct spoolman instance for the new printer profile - if (updating_printer) Spoolman::get_instance()->clear(); +void PresetBundle::update_spoolman_statistics(bool clear_cache) { + // Clear the cache so that it can be repopulated with the correct info + if (clear_cache) Spoolman::get_instance()->clear(); if (printers.get_edited_preset().spoolman_enabled() && Spoolman::is_server_valid()) { for (auto item : filaments.get_visible()) { if (item->is_user() && item->spoolman_enabled()) { diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 1bf8b4465a..4bc80b4531 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -244,8 +244,8 @@ public: void update_compatible(PresetSelectCompatibleType select_other_if_incompatible) { this->update_compatible(select_other_if_incompatible, select_other_if_incompatible); } // Update the statistics values for the visible filament profiles with spoolman enabled - // updating_printer should be set true if the update is due to a change in printer profile - void update_spoolman_statistics(bool updating_printer = false); + // clear_cache should be set true if the update is due to a change in printer profile or other change that requires it + void update_spoolman_statistics(bool clear_cache = false); // Set the is_visible flag for printer vendors, printer models and printer variants // based on the user configuration. From ebba337d66d8b6d914608f5c93559bb41c086ad5 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Tue, 25 Jun 2024 19:43:27 -0400 Subject: [PATCH 037/100] Fix missed name change --- src/slic3r/Utils/Spoolman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 1c0bb8fe26..219cd031ce 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -172,7 +172,7 @@ SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolSh bool force) { PresetCollection& filaments = wxGetApp().preset_bundle->filaments; - string filament_preset_name = get_name_from_spool(spool); + string filament_preset_name = spool->get_preset_name(); SpoolmanResult result; // Check if the preset already exists From c317e59a613a3396054dd6399f2d405a6d4a12fd Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 26 Jun 2024 20:45:24 -0400 Subject: [PATCH 038/100] Move spoolman stats update function Move the function to fix the issue with linking during the build of the orca profile validator --- src/libslic3r/PresetBundle.cpp | 14 -------------- src/libslic3r/PresetBundle.hpp | 4 ---- src/slic3r/GUI/GUI_App.cpp | 3 ++- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 3 ++- src/slic3r/GUI/Plater.cpp | 3 ++- src/slic3r/Utils/Spoolman.cpp | 19 +++++++++++++++++++ src/slic3r/Utils/Spoolman.hpp | 5 +++++ 7 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 016a426230..1ff2866969 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -4175,18 +4175,4 @@ void PresetBundle::set_default_suppressed(bool default_suppressed) printers.set_default_suppressed(default_suppressed); } -void PresetBundle::update_spoolman_statistics(bool clear_cache) { - // Clear the cache so that it can be repopulated with the correct info - if (clear_cache) Spoolman::get_instance()->clear(); - if (printers.get_edited_preset().spoolman_enabled() && Spoolman::is_server_valid()) { - for (auto item : filaments.get_visible()) { - if (item->is_user() && item->spoolman_enabled()) { - if (auto res = Spoolman::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"); - } - } - } -} - } // namespace Slic3r diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 4bc80b4531..976e654074 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -243,10 +243,6 @@ public: void update_compatible(PresetSelectCompatibleType select_other_print_if_incompatible, PresetSelectCompatibleType select_other_filament_if_incompatible); void update_compatible(PresetSelectCompatibleType select_other_if_incompatible) { this->update_compatible(select_other_if_incompatible, select_other_if_incompatible); } - // Update the statistics values for the visible filament profiles with spoolman enabled - // clear_cache should be set true if the update is due to a change in printer profile or other change that requires it - void update_spoolman_statistics(bool clear_cache = false); - // Set the is_visible flag for printer vendors, printer models and printer variants // based on the user configuration. // If the "vendor" section is missing, enable all models and variants of the particular vendor. diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index f9eb043ee7..f5631ce0c2 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -128,6 +128,7 @@ #endif #ifdef _WIN32 #include +#include #endif #ifdef WIN32 @@ -2486,7 +2487,7 @@ bool GUI_App::on_init_inner() } //} - preset_bundle->update_spoolman_statistics(); + Spoolman::update_visible_spool_statistics(); #ifdef WIN32 #if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index bf57714ffa..3d7cc4602e 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include "libslic3r/libslic3r.h" #include "libslic3r/PrintConfig.hpp" @@ -761,7 +762,7 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event) } wxGetApp().get_tab(Preset::TYPE_PRINTER)->save_preset("", false, false, true, m_preset_name ); if (update_spool_stats) // only update spoolman if spoolman related keys were changed - wxGetApp().preset_bundle->update_spoolman_statistics(true); + Spoolman::update_visible_spool_statistics(true); event.Skip(); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ec76af21be..6d0f5bbf43 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -138,6 +138,7 @@ #include #include // Needs to be last because reasons :-/ #include +#include #include "WipeTowerDialog.hpp" #include "ObjColorDialog.hpp" @@ -6509,7 +6510,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) view3D->deselect_all(); } - wxGetApp().preset_bundle->update_spoolman_statistics(true); + Spoolman::update_visible_spool_statistics(true); #if 0 // do not toggle auto calc when change printer // update flush matrix diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 219cd031ce..38eacb57df 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -258,6 +258,25 @@ SpoolmanResult Spoolman::update_filament_preset_from_spool(Preset* filament_pres return result; } +void Spoolman::update_visible_spool_statistics(bool clear_cache) +{ + PresetBundle* preset_bundle = GUI::wxGetApp().preset_bundle; + PresetCollection& printers = preset_bundle->printers; + PresetCollection& filaments = preset_bundle->filaments; + + // Clear the cache so that it can be repopulated with the correct info + if (clear_cache) Spoolman::get_instance()->clear(); + if (printers.get_edited_preset().spoolman_enabled() && Spoolman::is_server_valid()) { + for (auto item : filaments.get_visible()) { + if (item->is_user() && item->spoolman_enabled()) { + if (auto res = Spoolman::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"); + } + } + } +} + bool Spoolman::is_server_valid() { return !get_spoolman_json("info").empty(); diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index 44caf2ef25..e912622d1d 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -75,6 +75,7 @@ public: /// uses/consumes filament from the specified spool then updates the spool /// \returns if succeeded bool use_spoolman_spool(const unsigned int& spool_id, const double& weight_used); + static SpoolmanResult create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_profile, bool detach = false, @@ -83,6 +84,10 @@ public: bool update_from_server = true, bool only_update_statistics = false); + /// Update the statistics values for the visible filament profiles with spoolman enabled + /// 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); + static bool is_server_valid(); const std::map& get_spoolman_spools(bool update = false) From b5c3a8f54675c08eb5e9ebb5fbfaeb8fe1a18253 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 26 Jun 2024 23:17:09 -0400 Subject: [PATCH 039/100] Refactoring --- src/slic3r/Utils/Spoolman.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 38eacb57df..09613e56e1 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -265,11 +265,11 @@ void Spoolman::update_visible_spool_statistics(bool clear_cache) PresetCollection& filaments = preset_bundle->filaments; // Clear the cache so that it can be repopulated with the correct info - if (clear_cache) Spoolman::get_instance()->clear(); - if (printers.get_edited_preset().spoolman_enabled() && Spoolman::is_server_valid()) { + if (clear_cache) get_instance()->clear(); + if (printers.get_edited_preset().spoolman_enabled() && is_server_valid()) { for (auto item : filaments.get_visible()) { if (item->is_user() && item->spoolman_enabled()) { - if (auto res = Spoolman::update_filament_preset_from_spool(item, true, true); res.has_failed()) + 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"); } From d2bfeca3d5bf0d0f8aec5742407099c6cca19a74 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 26 Jun 2024 23:20:26 -0400 Subject: [PATCH 040/100] fix non windows builds --- src/slic3r/GUI/GUI_App.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index f5631ce0c2..16169e3edc 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -99,6 +99,7 @@ #include "Notebook.hpp" #include "Widgets/Label.hpp" #include "Widgets/ProgressDialog.hpp" +#include //BBS: DailyTip and UserGuide Dialog #include "WebDownPluginDlg.hpp" @@ -128,7 +129,6 @@ #endif #ifdef _WIN32 #include -#include #endif #ifdef WIN32 From 61e46dcf23c898e188ee0e31d4dd5b7817e9fe51 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 26 Jun 2024 23:50:04 -0400 Subject: [PATCH 041/100] update include statement --- src/slic3r/GUI/GUI_App.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 16169e3edc..87ace8e3ea 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -99,7 +99,7 @@ #include "Notebook.hpp" #include "Widgets/Label.hpp" #include "Widgets/ProgressDialog.hpp" -#include +#include "Spoolman.hpp" //BBS: DailyTip and UserGuide Dialog #include "WebDownPluginDlg.hpp" From 205002e27aea4c809a8648e25b1529efe5ed0e31 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Tue, 24 Sep 2024 04:59:39 -0400 Subject: [PATCH 042/100] Fix manual update buttons It wasn't pulling from the server, so it wasn't truly updating the spool --- src/slic3r/GUI/Tab.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 66cea3885d..09f42d387d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3482,7 +3482,7 @@ void TabFilament::build() show_error(this, "Failed to get data from the Spoolman server. Make sure that the port is correct and the server is running."); return; } - auto res = Spoolman::update_filament_preset_from_spool(&m_presets->get_selected_preset(), false, stats_only); + auto res = Spoolman::update_filament_preset_from_spool(&m_presets->get_selected_preset(), true, stats_only); if (res.has_failed()) return; From 2df8d288836886051a2dbf1a1548e0b736cb5c79 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Tue, 24 Sep 2024 06:06:49 -0400 Subject: [PATCH 043/100] Update how low filament warning is displayed --- src/libslic3r/GCode.cpp | 2 +- src/libslic3r/PrintBase.hpp | 3 ++- src/slic3r/GUI/Plater.cpp | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 12d9916fd4..d2ad0c1160 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1606,7 +1606,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu 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); + print->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, msg, PrintStateBase::SlicingNotificationType::SlicingNotEnoughFilament); } } } diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index b680ac274e..b332359004 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -61,7 +61,8 @@ public: SlicingReplaceInitEmptyLayers, SlicingNeedSupportOn, SlicingEmptyGcodeLayers, - SlicingGcodeOverlap + SlicingGcodeOverlap, + SlicingNotEnoughFilament }; typedef size_t TimeStamp; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 9485aacf2d..f2a12e9256 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6705,7 +6705,9 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) for (auto const& warning : state.warnings) { if (warning.current) { NotificationManager::NotificationLevel notif_level = NotificationManager::NotificationLevel::WarningNotificationLevel; - if (evt.status.message_type == PrintStateBase::SlicingNotificationType::SlicingReplaceInitEmptyLayers || evt.status.message_type == PrintStateBase::SlicingNotificationType::SlicingEmptyGcodeLayers) { + if (evt.status.message_type == PrintStateBase::SlicingNotificationType::SlicingReplaceInitEmptyLayers || + evt.status.message_type == PrintStateBase::SlicingNotificationType::SlicingEmptyGcodeLayers || + evt.status.message_type == PrintStateBase::SlicingNotificationType::SlicingNotEnoughFilament) { notif_level = NotificationManager::NotificationLevel::SeriousWarningNotificationLevel; } notification_manager->push_slicing_warning_notification(warning.message, false, model_object, object_id, warning_step, warning.message_id, notif_level); From 69446ca6ece413a5968d6f545dcd531ccb5a4b28 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 23 Oct 2024 18:05:50 -0400 Subject: [PATCH 044/100] Modify slicing warning dialog to support multiple plates The slicing warnings are now stored on a per-plate basis, so the warnings for the previously sliced plate does not get overwritten by slicing a new plate. The warning dialog now shows the warnings only for the currently exported plate, not just the most recently sliced and will show all print warnings when all plates are being exported. --- src/slic3r/GUI/Plater.cpp | 61 ++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 983202ea54..3fdfb0fc02 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2254,6 +2254,7 @@ struct Plater::priv int m_cur_slice_plate; //BBS: m_slice_all in .gcode.3mf file case, set true when slice all bool m_slice_all_only_has_gcode{ false }; + bool m_export_all{ false }; bool m_need_update{false}; //BBS: add popup object table logic @@ -2549,7 +2550,7 @@ struct Plater::priv void on_export_finished(wxCommandEvent&); void on_slicing_began(); - void clear_warnings(); + void clear_warnings(const bool& clear_all_plates = false); void add_warning(const Slic3r::PrintStateBase::Warning &warning, size_t oid); // Update notification manager with the current state of warnings produced by the background process (slicing). void actualize_slicing_warnings(const PrintBase &print); @@ -2723,7 +2724,7 @@ private: std::string m_last_sla_printer_profile_name; // vector of all warnings generated by last slicing - std::vector> current_warnings; + std::map>> current_warnings; bool show_warning_dialog { false }; //record print preset @@ -4972,7 +4973,7 @@ void Plater::priv::reset(bool apply_presets_change) { Plater::TakeSnapshot snapshot(q, "Reset Project", UndoRedo::SnapshotType::ProjectSeparator); - clear_warnings(); + clear_warnings(true); set_project_filename(""); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " call set_project_filename: empty"; @@ -6776,7 +6777,9 @@ void Plater::priv::on_slicing_began() } void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid) { - for (auto& it : current_warnings) { + const auto& cur_plate_idx = m_slice_all ? m_cur_slice_plate : this->partplate_list.get_curr_plate_index(); + auto& cur_plate_warnings = current_warnings[cur_plate_idx]; + for (auto& it : cur_plate_warnings) { if (warning.message_id == it.first.message_id) { if (warning.message_id != 0 || (warning.message_id == 0 && warning.message == it.first.message)) { @@ -6786,7 +6789,7 @@ void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, s } } } - current_warnings.emplace_back(std::pair(warning, oid)); + cur_plate_warnings.emplace_back(warning, oid); } void Plater::priv::actualize_slicing_warnings(const PrintBase &print) { @@ -6810,29 +6813,49 @@ void Plater::priv::actualize_object_warnings(const PrintBase& print) std::sort(ids.begin(), ids.end()); notification_manager->remove_simplify_suggestion_of_released_objects(ids); } -void Plater::priv::clear_warnings() +void Plater::priv::clear_warnings(const bool& clear_all_plates) { notification_manager->close_slicing_errors_and_warnings(); - this->current_warnings.clear(); + if (clear_all_plates) + this->current_warnings.clear(); + else + this->current_warnings.erase(this->partplate_list.get_curr_plate_index()); } bool Plater::priv::warnings_dialog() { - if (current_warnings.empty()) + const auto& cur_plate_idx = this->partplate_list.get_curr_plate_index(); + if (current_warnings.empty() || + (!m_export_all && (current_warnings.count(cur_plate_idx) == 0 || current_warnings[cur_plate_idx].empty()))) return true; - std::string text = _u8L("There are warnings after slicing models:") + "\n"; - for (auto const& it : current_warnings) { - size_t next_n = it.first.message.find_first_of('\n', 0); - text += "\n"; - if (next_n != std::string::npos) - text += it.first.message.substr(0, next_n); + std::string text = _u8L("There are warnings after slicing models:"); + + auto get_text_from_warnings = [&](const vector>& warnings, const unsigned int& plate_idx = -1) { + if (m_export_all) + text += "\n\n" + (boost::format(_u8L("Plate") + " %1%:") % (plate_idx+1)).str(); else - text += it.first.message; + text += "\n"; + for (const auto& [warning, object_id] : warnings) { + size_t next_n = warning.message.find_first_of('\n', 0); + text += "\n"; + if (next_n != std::string::npos) + text += warning.message.substr(0, next_n); + else + text += warning.message; + } + }; + + if (m_export_all) { + for (auto& [plate_idx, warnings] : current_warnings) { + if (warnings.empty()) continue; + get_text_from_warnings(warnings, plate_idx); + } + } else { + get_text_from_warnings(current_warnings[cur_plate_idx]); } //text += "\n\nDo you still wish to export?"; - MessageDialog msg_window(this->q, from_u8(text), _L("warnings"), wxOK); + MessageDialog msg_window(this->q, from_u8(text), _L("Slicing Warnings"), wxOK); const auto res = msg_window.ShowModal(); return res == wxID_OK; - } //BBS: add project slice logic @@ -7010,6 +7033,7 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":finished, reload print soon"); m_is_slicing = false; + m_export_all = false; this->preview->reload_print(false); /* BBS if in publishing progress */ if (m_is_publishing) { @@ -7035,6 +7059,7 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) if (ret) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":slicing all, plate %1% can not be sliced, will stop")%m_cur_slice_plate; m_is_slicing = false; + m_export_all = false; } //not the last plate update_fff_scene_only_shells(); @@ -7309,6 +7334,7 @@ void Plater::priv::on_action_export_all_sliced_file(SimpleEvent &) { if (q != nullptr) { BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ":received export all sliced file event\n"; + m_export_all = true; q->export_gcode_3mf(true); } } @@ -7325,6 +7351,7 @@ void Plater::priv::on_action_export_to_sdcard_all(SimpleEvent&) { if (q != nullptr) { BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ":received export sliced file event\n"; + m_export_all = true; q->send_to_printer(true); } } From 5f26edc48bc1185d58a4a49cd56fccb493a8d028 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 23 Oct 2024 20:24:58 -0400 Subject: [PATCH 045/100] Restore Yes/No dialog style to slicer warning dialog --- src/slic3r/GUI/Plater.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3fdfb0fc02..4a5d86c155 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6852,10 +6852,10 @@ bool Plater::priv::warnings_dialog() } else { get_text_from_warnings(current_warnings[cur_plate_idx]); } - //text += "\n\nDo you still wish to export?"; - MessageDialog msg_window(this->q, from_u8(text), _L("Slicing Warnings"), wxOK); + text += "\n\nDo you still wish to export?"; + MessageDialog msg_window(this->q, from_u8(text), _L("Slicing Warnings"), wxYES_NO); const auto res = msg_window.ShowModal(); - return res == wxID_OK; + return res == wxID_YES; } //BBS: add project slice logic From 12f24afdf19c6e7a9ba6b21f6d71d95fc06837bd Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Fri, 25 Oct 2024 17:30:48 -0400 Subject: [PATCH 046/100] Modify how/when slicer warning dialog is shown The slicing dialog is now handled by the export functions within Plater.cpp rather than in the BackgroundSlicingProcess.cpp via an event to the plater. This allows the continue yes/no dialog to properly interrupt the export and shows the dialog during all types of export rather than just the legacy gcode export and legacy print. --- src/slic3r/GUI/Plater.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 4a5d86c155..2ae7d85864 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6747,8 +6747,9 @@ void Plater::priv::on_slicing_completed(wxCommandEvent & evt) void Plater::priv::on_export_began(wxCommandEvent& evt) { - if (show_warning_dialog) - warnings_dialog(); + // Orca: warning dialog calls moved + // if (show_warning_dialog) + // warnings_dialog(); } void Plater::priv::on_export_finished(wxCommandEvent& evt) @@ -7266,6 +7267,10 @@ int Plater::priv::update_print_required_data(Slic3r::DynamicPrintConfig config, void Plater::priv::on_action_send_to_printer(bool isall) { + // Orca: Prompt the user with the current slicing warnings (if any) and continue if they wish to + if (!warnings_dialog()) + return; + if (!m_send_to_sdcard_dlg) m_send_to_sdcard_dlg = new SendToPrinterDialog(q); if (isall) { m_send_to_sdcard_dlg->prepare(PLATE_ALL_IDX); @@ -11435,6 +11440,10 @@ void Plater::export_gcode(bool prefer_removable) if (p->process_completed_with_error == p->partplate_list.get_curr_plate_index()) return; + // Orca: Prompt the user with the current slicing warnings (if any) and continue if they wish to + if (!p->warnings_dialog()) + return; + // If possible, remove accents from accented latin characters. // This function is useful for generating file names to be processed by legacy firmwares. fs::path default_output_file; @@ -11537,6 +11546,10 @@ void Plater::export_gcode_3mf(bool export_all) if (p->process_completed_with_error == p->partplate_list.get_curr_plate_index()) return; + // Orca: Prompt the user with the current slicing warnings (if any) and continue if they wish to + if (!p->warnings_dialog()) + return; + //calc default_output_file, get default output file from background process fs::path default_output_file; AppConfig& appconfig = *wxGetApp().app_config; @@ -12579,6 +12592,10 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us if (! physical_printer_config || p->model.objects.empty()) return; + // Orca: Prompt the user with the current slicing warnings (if any) and continue if they wish to + if (!p->warnings_dialog()) + return; + PrintHostJob upload_job(physical_printer_config); if (upload_job.empty()) return; From 704dbdef88b0713a9517ba15085bab41058b0bfc Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Fri, 8 Nov 2024 08:21:11 -0500 Subject: [PATCH 047/100] Tie slicing warning/error notifications to the plate that generated them This allows notification actions to be performed on a per-plate basis and allows the same notification to exist between multiple plates --- src/slic3r/GUI/NotificationManager.cpp | 70 ++++++++++++++++++-------- src/slic3r/GUI/NotificationManager.hpp | 15 +++--- src/slic3r/GUI/Plater.cpp | 40 +++++++++------ src/slic3r/GUI/Plater.hpp | 4 ++ 4 files changed, 87 insertions(+), 42 deletions(-) diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 08ef8c7493..f8928c0474 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1870,8 +1870,14 @@ void NotificationManager::push_slicing_error_notification(const std::string &tex } link += "] "; } - set_all_slicing_errors_gray(false); - push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotificationLevel, 0, _u8L("Error:") + "\n" + text, link, callback }, 0); + + NotificationData data { NotificationType::SlicingError, NotificationLevel::ErrorNotificationLevel, 0, _u8L("Error:") + "\n" + text, link, callback }; + auto notification = std::make_unique(data, m_id_provider, m_evt_handler); + notification->plate_id = wxGetApp().plater()->get_current_slicing_plate_index(); + + set_all_slicing_errors_gray(false, notification->plate_id); + + push_notification_data(std::move(notification), 0); set_slicing_progress_hidden(); } void NotificationManager::push_slicing_warning_notification(const std::string& text, bool gray, ModelObject const * obj, ObjectID oid, int warning_step, int warning_msg_id, NotificationLevel level/* = NotificationLevel::WarningNotificationLevel*/) @@ -1898,6 +1904,8 @@ void NotificationManager::push_slicing_warning_notification(const std::string& t auto notification = std::make_unique(data, m_id_provider, m_evt_handler); notification->object_id = oid; notification->warning_step = warning_step; + notification->plate_id = wxGetApp().plater()->get_current_slicing_plate_index(); + if (push_notification_data(std::move(notification), 0)) { m_pop_notifications.back()->set_gray(gray); } @@ -1944,21 +1952,25 @@ void NotificationManager::close_plater_warning_notification(const std::string& t } } } -void NotificationManager::set_all_slicing_errors_gray(bool g) +void NotificationManager::set_all_slicing_errors_gray(bool g, int plate_id) { - for (std::unique_ptr ¬ification : m_pop_notifications) { - if (notification->get_type() == NotificationType::SlicingError) { - notification->set_gray(g); - } - } + for (std::unique_ptr ¬ification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingError) { + if (auto obj_notif = dynamic_cast(notification.get()); obj_notif->plate_id == plate_id) { + notification->set_gray(g); + } + } + } } -void NotificationManager::set_all_slicing_warnings_gray(bool g) +void NotificationManager::set_all_slicing_warnings_gray(bool g, int plate_id) { - for (std::unique_ptr ¬ification : m_pop_notifications) { - if (notification->get_type() == NotificationType::SlicingWarning) { - notification->set_gray(g); - } - } + for (std::unique_ptr ¬ification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingWarning) { + if (auto obj_notif = dynamic_cast(notification.get()); obj_notif->plate_id == plate_id) { + notification->set_gray(g); + } + } + } } /* void NotificationManager::set_slicing_warning_gray(const std::string& text, bool g) @@ -1978,6 +1990,15 @@ void NotificationManager::close_slicing_errors_and_warnings() } } } +void NotificationManager::close_slicing_errors_and_warnings(int plate_idx) { + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingError || notification->get_type() == NotificationType::SlicingWarning) { + if (auto oid_notif = dynamic_cast(notification.get()); oid_notif->plate_id == plate_idx) { + oid_notif->close(); + } + } + } +} void NotificationManager::close_slicing_error_notification(const std::string& text) { for (std::unique_ptr& notification : m_pop_notifications) { @@ -2001,12 +2022,12 @@ void NotificationManager::close_notification_of_type(const NotificationType type } } } -void NotificationManager::remove_slicing_warnings_of_released_objects(const std::vector& living_oids) +void NotificationManager::remove_slicing_warnings_of_released_objects(const std::vector& living_oids, int plate_id) { for (std::unique_ptr ¬ification : m_pop_notifications) if (notification->get_type() == NotificationType::SlicingWarning) { - if (! std::binary_search(living_oids.begin(), living_oids.end(), - static_cast(notification.get())->object_id)) + if (auto oid_notif = static_cast(notification.get()); + !std::binary_search(living_oids.begin(), living_oids.end(), oid_notif->object_id) && oid_notif->plate_id == plate_id) notification->close(); } } @@ -2216,10 +2237,14 @@ void NotificationManager::push_slicing_serious_warning_notification(const std::s } link += "] "; } - set_all_slicing_warnings_gray(false); - push_notification_data({NotificationType::SlicingSeriousWarning, NotificationLevel::SeriousWarningNotificationLevel, 0, _u8L("Serious warning:") + "\n" + text, link, - callback}, - 0); + + NotificationData data {NotificationType::SlicingSeriousWarning, NotificationLevel::SeriousWarningNotificationLevel, 0, _u8L("Serious warning:") + "\n" + text, link, callback}; + auto notification = std::make_unique(data, m_id_provider, m_evt_handler); + notification->plate_id = wxGetApp().plater()->get_current_slicing_plate_index(); + + set_all_slicing_warnings_gray(false, notification->plate_id); + + push_notification_data(std::move(notification), 0); set_slicing_progress_hidden(); } @@ -2660,6 +2685,9 @@ bool NotificationManager::activate_existing(const NotificationManager::PopNotifi const NotificationData& data2 = w2->get_data(); if (data1.sub_msg_id != data2.sub_msg_id) continue; + // multiple notifications with the same msg id are allowed if they are for different plates + if (w1->plate_id != w2->plate_id) + continue;; //if (!(*it)->compare_text(new_text) || w1->object_id != w2->object_id) { // continue; //} diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index dce17f6080..6032d7665b 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -210,17 +210,19 @@ public: void push_slicing_error_notification(const std::string &text, std::vector objs); // Creates Slicing Warning notification with a custom text and no fade out. void push_slicing_warning_notification(const std::string &text, bool gray, ModelObject const *obj, ObjectID oid, int warning_step, int warning_msg_id, NotificationLevel level = NotificationLevel::WarningNotificationLevel); - // marks slicing errors as gray - void set_all_slicing_errors_gray(bool g); - // marks slicing warings as gray - void set_all_slicing_warnings_gray(bool g); + // marks slicing errors as gray for the specified plate + void set_all_slicing_errors_gray(bool g, int plate_id); + // marks slicing warings as gray for the specified plate + void set_all_slicing_warnings_gray(bool g, int plate_id); // void set_slicing_warning_gray(const std::string& text, bool g); // immediately stops showing slicing errors void close_slicing_errors_and_warnings(); + // close slicing errors for a specific plate index + void close_slicing_errors_and_warnings(int plate_idx); void close_slicing_error_notification(const std::string& text); // Release those slicing warnings, which refer to an ObjectID, which is not in the list. // living_oids is expected to be sorted. - void remove_slicing_warnings_of_released_objects(const std::vector& living_oids); + void remove_slicing_warnings_of_released_objects(const std::vector& living_oids, int plate_id); // Object partially outside of the printer working space, cannot print. No fade out. void push_plater_error_notification(const std::string& text); // Object fully out of the printer working space and such. No fade out. @@ -233,7 +235,7 @@ public: std::function callback = std::function()); void set_simplify_suggestion_multiline(const ObjectID oid, bool bMulti); // Close object warnings, whose ObjectID is not in the list. - // living_oids is expected to be sorted. + // living_oids is expected to be sorted and contain all ObjectIDs for the current project (all plates). void remove_simplify_suggestion_of_released_objects(const std::vector& living_oids); void remove_simplify_suggestion_with_id(const ObjectID oid); // Called when the side bar changes its visibility, as the "slicing complete" notification supplements @@ -582,6 +584,7 @@ private: {} ObjectID object_id; int warning_step { 0 }; + int plate_id { 0 }; }; class PlaterWarningNotification : public PopNotification diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 2ae7d85864..78b1db3ac9 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2313,6 +2313,10 @@ struct Plater::priv priv(Plater *q, MainFrame *main_frame); ~priv(); + int get_current_slicing_plate_index() const + { + return (m_is_slicing && m_slice_all) ? m_cur_slice_plate : partplate_list.get_curr_plate_index(); + } bool need_update() const { return m_need_update; } void set_need_update(bool need_update) { m_need_update = need_update; } @@ -2554,7 +2558,7 @@ struct Plater::priv void add_warning(const Slic3r::PrintStateBase::Warning &warning, size_t oid); // Update notification manager with the current state of warnings produced by the background process (slicing). void actualize_slicing_warnings(const PrintBase &print); - void actualize_object_warnings(const PrintBase& print); + void actualize_object_warnings(); // Displays dialog window with list of warnings. // Returns true if user clicks OK. // Returns true if current_warnings vector is empty without showning the dialog @@ -5293,7 +5297,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool if (err.string.empty()) { this->partplate_list.get_curr_plate()->update_apply_result_invalid(false); - notification_manager->set_all_slicing_errors_gray(true); + notification_manager->set_all_slicing_errors_gray(true, partplate_list.get_curr_plate_index()); notification_manager->close_notification_of_type(NotificationType::ValidateError); if (invalidated != Print::APPLY_STATUS_UNCHANGED && background_processing_enabled()) return_state |= UPDATE_BACKGROUND_PROCESS_RESTART; @@ -5336,7 +5340,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool if (background_process.empty()) process_validation_warning({}); actualize_slicing_warnings(*this->background_process.current_print()); - actualize_object_warnings(*this->background_process.current_print()); + actualize_object_warnings(); show_warning_dialog = false; process_completed_with_error = -1; } @@ -6778,8 +6782,7 @@ void Plater::priv::on_slicing_began() } void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid) { - const auto& cur_plate_idx = m_slice_all ? m_cur_slice_plate : this->partplate_list.get_curr_plate_index(); - auto& cur_plate_warnings = current_warnings[cur_plate_idx]; + auto& cur_plate_warnings = current_warnings[get_current_slicing_plate_index()]; for (auto& it : cur_plate_warnings) { if (warning.message_id == it.first.message_id) { if (warning.message_id != 0 || (warning.message_id == 0 && warning.message == it.first.message)) @@ -6801,13 +6804,13 @@ void Plater::priv::actualize_slicing_warnings(const PrintBase &print) } ids.emplace_back(print.id()); std::sort(ids.begin(), ids.end()); - notification_manager->remove_slicing_warnings_of_released_objects(ids); - notification_manager->set_all_slicing_warnings_gray(true); + notification_manager->remove_slicing_warnings_of_released_objects(ids, print.get_plate_index()); + notification_manager->set_all_slicing_warnings_gray(true, print.get_plate_index()); } -void Plater::priv::actualize_object_warnings(const PrintBase& print) +void Plater::priv::actualize_object_warnings() { std::vector ids; - for (const ModelObject* object : print.model().objects ) + for (const ModelObject* object : model.objects ) { ids.push_back(object->id()); } @@ -6816,11 +6819,13 @@ void Plater::priv::actualize_object_warnings(const PrintBase& print) } void Plater::priv::clear_warnings(const bool& clear_all_plates) { - notification_manager->close_slicing_errors_and_warnings(); - if (clear_all_plates) - this->current_warnings.clear(); - else - this->current_warnings.erase(this->partplate_list.get_curr_plate_index()); + if (clear_all_plates) { + notification_manager->close_slicing_errors_and_warnings(); + current_warnings.clear(); + } else { + notification_manager->close_slicing_errors_and_warnings(get_current_slicing_plate_index()); + current_warnings.erase(get_current_slicing_plate_index()); + } } bool Plater::priv::warnings_dialog() { @@ -13791,7 +13796,7 @@ void Plater::validate_current_plate(bool& model_fits, bool& validate_error) if (err.string.empty()) { p->partplate_list.get_curr_plate()->update_apply_result_invalid(false); - p->notification_manager->set_all_slicing_errors_gray(true); + p->notification_manager->set_all_slicing_errors_gray(true, p->partplate_list.get_curr_plate_index()); p->notification_manager->close_notification_of_type(NotificationType::ValidateError); // Pass a warning from validation and either show a notification, @@ -14303,6 +14308,11 @@ void Plater::post_process_string_object_exception(StringObjectException &err) return; } +int Plater::get_current_slicing_plate_index() const +{ + return p->get_current_slicing_plate_index(); +} + #if ENABLE_ENVIRONMENT_MAP void Plater::init_environment_texture() { diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 4086a15771..5574a2a222 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -614,6 +614,10 @@ public: bool show_publish_dialog(bool show = true); //BBS: post process string object exception strings by warning types void post_process_string_object_exception(StringObjectException &err); + // Return the plate that is currently being sliced + // If all plates are being sliced, Plater::priv::m_cur_slice_plate is returned + // If no plate is being sliced or a singluar plate is being sliced, partplate_list.get_curr_plate_index() is returned + int get_current_slicing_plate_index() const; #if ENABLE_ENVIRONMENT_MAP void init_environment_texture(); From 941c4ce3a09ae78adec327c62f1dc4823738b7ac Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Fri, 8 Nov 2024 08:39:13 -0500 Subject: [PATCH 048/100] Only shows slicing warnings/errors for the currently selected plate --- src/slic3r/GUI/GLCanvas3D.cpp | 5 +++++ src/slic3r/GUI/GLToolbar.cpp | 1 + src/slic3r/GUI/GLToolbar.hpp | 1 + src/slic3r/GUI/NotificationManager.cpp | 12 ++++++++++++ src/slic3r/GUI/NotificationManager.hpp | 1 + src/slic3r/GUI/Plater.cpp | 15 ++++++++++++++- 6 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 9fc70c6245..9fbcd3f080 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -6462,6 +6462,11 @@ void GLCanvas3D::_update_select_plate_toolbar_stats_item(bool force_selected) { if (force_selected && m_sel_plate_toolbar.show_stats_item) m_sel_plate_toolbar.m_all_plates_stats_item->selected = true; + + if (m_sel_plate_toolbar.m_all_plates_stats_item->selected) { + auto evt = SimpleEvent(EVT_GLTOOLBAR_SELECT_ALL_PLATES_STATS); + wxPostEvent(wxGetApp().plater(), evt); + } } bool GLCanvas3D::_update_imgui_select_plate_toolbar() diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index be97e52e84..298453aeac 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -33,6 +33,7 @@ wxDEFINE_EVENT(EVT_GLTOOLBAR_SEND_TO_PRINTER, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_SEND_TO_PRINTER_ALL, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_PRINT_MULTI_MACHINE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLTOOLBAR_SELECT_ALL_PLATES_STATS, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent); wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent); diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 85413edc66..97b7d4e662 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -33,6 +33,7 @@ wxDECLARE_EVENT(EVT_GLTOOLBAR_SEND_TO_PRINTER, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_SEND_TO_PRINTER_ALL, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_PRINT_MULTI_MACHINE, SimpleEvent); +wxDECLARE_EVENT(EVT_GLTOOLBAR_SELECT_ALL_PLATES_STATS, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent); wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent); diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index f8928c0474..b1713ffb30 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -2007,6 +2007,18 @@ void NotificationManager::close_slicing_error_notification(const std::string& te } } } +void NotificationManager::hide_slicing_notifications_from_other_plates(int current_plate_id) +{ + for (std::unique_ptr ¬ification : m_pop_notifications) { + if (notification->get_type() == NotificationType::SlicingWarning || + notification->get_type() == NotificationType::SlicingSeriousWarning || + notification->get_type() == NotificationType::SlicingError) { + if (auto oid_notif = dynamic_cast(notification.get())) { + oid_notif->hide(oid_notif->plate_id != current_plate_id); + } + } + } +} void NotificationManager::push_simplify_suggestion_notification(const std::string& text, ObjectID object_id, const std::string& hypertext/* = ""*/, std::function callback/* = std::function()*/) { NotificationData data{ NotificationType::SimplifySuggestion, NotificationLevel::PrintInfoNotificationLevel, 0, text, hypertext, callback }; diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 6032d7665b..1c633c6341 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -220,6 +220,7 @@ public: // close slicing errors for a specific plate index void close_slicing_errors_and_warnings(int plate_idx); void close_slicing_error_notification(const std::string& text); + void hide_slicing_notifications_from_other_plates(int current_plate_id); // Release those slicing warnings, which refer to an ObjectID, which is not in the list. // living_oids is expected to be sorted. void remove_slicing_warnings_of_released_objects(const std::vector& living_oids, int plate_id); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 78b1db3ac9..62ca81b440 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2588,6 +2588,7 @@ struct Plater::priv //BBS: add part plate related logic void on_plate_right_click(RBtnPlateEvent&); void on_plate_selected(SimpleEvent&); + void on_all_plates_stats_selected(SimpleEvent& evt); void on_action_request_model_id(wxCommandEvent& evt); void on_action_download_project(wxCommandEvent& evt); void on_slice_button_status(bool enable); @@ -3123,6 +3124,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) q->Bind(EVT_GLTOOLBAR_SEND_TO_PRINTER_ALL, &priv::on_action_export_to_sdcard_all, this); q->Bind(EVT_GLTOOLBAR_PRINT_MULTI_MACHINE, &priv::on_action_send_to_multi_machine, this); q->Bind(EVT_GLCANVAS_PLATE_SELECT, &priv::on_plate_selected, this); + q->Bind(EVT_GLTOOLBAR_SELECT_ALL_PLATES_STATS, &priv::on_all_plates_stats_selected, this); q->Bind(EVT_DOWNLOAD_PROJECT, &priv::on_action_download_project, this); q->Bind(EVT_IMPORT_MODEL_ID, &priv::on_action_request_model_id, this); q->Bind(EVT_PRINT_FINISHED, [q](wxCommandEvent& evt) { q->print_job_finished(evt); }); @@ -7037,6 +7039,9 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) if (is_finished) { + if (m_slice_all) + preview->get_canvas3d()->_update_select_plate_toolbar_stats_item(true); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":finished, reload print soon"); m_is_slicing = false; m_export_all = false; @@ -7165,7 +7170,8 @@ void Plater::priv::on_action_slice_all(SimpleEvent&) if (!m_is_publishing) q->select_view_3D("Preview"); //BBS: wish to select all plates stats item - preview->get_canvas3d()->_update_select_plate_toolbar_stats_item(true); + // Orca: This call has been moved to the process complete function + // preview->get_canvas3d()->_update_select_plate_toolbar_stats_item(true); } } @@ -7373,6 +7379,12 @@ void Plater::priv::on_plate_selected(SimpleEvent&) sidebar->obj_list()->on_plate_selected(partplate_list.get_curr_plate_index()); } +void Plater::priv::on_all_plates_stats_selected(SimpleEvent& evt) +{ + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": received all plates stats selected event\n"; + notification_manager->hide_slicing_notifications_from_other_plates(-1); +} + void Plater::priv::on_action_request_model_id(wxCommandEvent& evt) { BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ":received import model id event\n" ; @@ -13626,6 +13638,7 @@ int Plater::select_plate(int plate_index, bool need_slice) if ((!ret) && (p->background_process.can_switch_print())) { + get_notification_manager()->hide_slicing_notifications_from_other_plates(plate_index); //select successfully p->partplate_list.update_slice_context_to_current_plate(p->background_process); p->preview->update_gcode_result(p->partplate_list.get_current_slice_result()); From 1005067526a13e8084f752e866c91e32d3454fdb Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Tue, 19 Nov 2024 02:54:38 -0500 Subject: [PATCH 049/100] Update Spoolman::is_server_valid --- src/slic3r/Utils/Spoolman.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 09613e56e1..65733db2fe 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -279,7 +279,12 @@ void Spoolman::update_visible_spool_statistics(bool clear_cache) bool Spoolman::is_server_valid() { - return !get_spoolman_json("info").empty(); + bool res = false; + Http::get(get_spoolman_api_url() + "info").on_complete([&res](std::string, unsigned http_status) { + if (http_status == 200) + res = true; + }).perform_sync(); + return res; } //--------------------------------- From bf17e8e99836a1342014bf40eebef748b77bb343 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 27 Nov 2024 12:31:49 -0500 Subject: [PATCH 050/100] 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) From 0298b99077ab25d03d6127539598d87df475447c Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Fri, 20 Dec 2024 09:36:48 -0500 Subject: [PATCH 051/100] Add DSA checkbox to consumption dialog --- src/libslic3r/AppConfig.cpp | 3 +++ src/slic3r/GUI/Plater.cpp | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index fbd72d565f..ce71d4d74a 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("show_spoolman_consumption_dialog").empty()) + set_bool("show_spoolman_consumption_dialog", true); + if (get("spoolman", "consumption_type").empty()) set_str("spoolman", "consumption_type", "weight"); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index d63d42bd8c..ea17cb8630 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6876,6 +6876,9 @@ bool Plater::priv::warnings_dialog() void Plater::priv::spoolman_consumption_dialog(const bool& all_plates) { + static constexpr auto show_dlg_key = "show_spoolman_consumption_dialog"; + if (!wxGetApp().app_config->get_bool(show_dlg_key)) + return; if (!wxGetApp().preset_bundle->printers.get_edited_preset().spoolman_enabled()) return; if (!Spoolman::is_server_valid()) @@ -6918,6 +6921,7 @@ void Plater::priv::spoolman_consumption_dialog(const bool& all_plates) msg += message + "\n"; auto dlg = MessageDialog(nullptr, msg, _L("Spoolman Filament Consumption"), wxYES_NO); + dlg.show_dsa_button(); if (dlg.ShowModal() == wxID_YES) { if (spoolman->use_spoolman_spools(estimates, consumption_type)) { notification_manager->push_spoolman_consumption_finished_notification(); @@ -6926,6 +6930,8 @@ void Plater::priv::spoolman_consumption_dialog(const bool& all_plates) show_error(nullptr, _L("Failed to consume filament from Spoolman")); } } + if (dlg.get_checkbox_state()) + wxGetApp().app_config->set(show_dlg_key, false); } //BBS: add project slice logic From a4e00750de73755687109f5a384e9df32457aa58 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Thu, 26 Dec 2024 14:56:03 -0500 Subject: [PATCH 052/100] Update app config set call --- src/slic3r/GUI/Plater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ea17cb8630..bc173ea8c1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6931,7 +6931,7 @@ void Plater::priv::spoolman_consumption_dialog(const bool& all_plates) } } if (dlg.get_checkbox_state()) - wxGetApp().app_config->set(show_dlg_key, false); + wxGetApp().app_config->set_bool(show_dlg_key, false); } //BBS: add project slice logic From 5c168ce5ff2687e906476301f485c2c8632d3b2f Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Thu, 26 Dec 2024 15:58:04 -0500 Subject: [PATCH 053/100] Correct lambda signature --- src/slic3r/GUI/Plater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index bc173ea8c1..44eef2cc3e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6845,7 +6845,7 @@ bool Plater::priv::warnings_dialog() return true; std::string text = _u8L("There are warnings after slicing models:"); - auto get_text_from_warnings = [&](const vector>& warnings, const unsigned int& plate_idx = -1) { + auto get_text_from_warnings = [&](const vector>& warnings, const unsigned int& plate_idx = -1) { if (m_export_all) text += "\n\n" + (boost::format(_u8L("Plate") + " %1%:") % (plate_idx+1)).str(); else From 16429569ea3442bad008f528ca9e9c5de627a915 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Fri, 27 Dec 2024 18:28:43 -0500 Subject: [PATCH 054/100] Handle unicode characters in SpoolmanImportDialog --- src/slic3r/GUI/SpoolmanImportDialog.cpp | 4 ++-- src/slic3r/GUI/SpoolmanImportDialog.hpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/SpoolmanImportDialog.cpp b/src/slic3r/GUI/SpoolmanImportDialog.cpp index b7bb560aa9..201aaf585e 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.cpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.cpp @@ -41,7 +41,7 @@ wxString SpoolmanViewModel::GetColumnType(unsigned int col) const { if (col == COL_CHECK) return "bool"; - else if (col == COL_COLOR) + if (col == COL_COLOR) return "wxColour"; return "string"; } @@ -64,7 +64,7 @@ void SpoolmanViewModel::GetValue(wxVariant& variant, const wxDataViewItem& item, switch (col) { case COL_CHECK: variant = node->get_checked(); break; case COL_ID: variant = std::to_string(node->get_id()); break; - case COL_COLOR: variant << wxColour(node->get_color()); break; + case COL_COLOR: variant << node->get_color(); break; case COL_VENDOR: variant = node->get_vendor_name(); break; case COL_NAME: variant = node->get_filament_name(); break; case COL_MATERIAL: variant = node->get_material(); break; diff --git a/src/slic3r/GUI/SpoolmanImportDialog.hpp b/src/slic3r/GUI/SpoolmanImportDialog.hpp index bf95fa4a3f..e69d8414d6 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.hpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.hpp @@ -22,10 +22,10 @@ public: explicit SpoolmanNode(const SpoolmanSpoolShrPtr& spool) : m_spool(spool) {} int get_id() const { return m_spool->id; }; - std::string get_color() const { return m_spool->m_filament_ptr->color; } - std::string get_vendor_name() const { return m_spool->getVendor()->name; }; - std::string get_filament_name() const { return m_spool->m_filament_ptr->name; }; - std::string get_material() const { return m_spool->m_filament_ptr->material; }; + wxColour get_color() const { return wxColour(m_spool->m_filament_ptr->color); } + wxString get_vendor_name() const { return wxString::FromUTF8(m_spool->getVendor()->name); }; + wxString get_filament_name() const { return wxString::FromUTF8(m_spool->m_filament_ptr->name); }; + wxString get_material() const { return wxString::FromUTF8(m_spool->m_filament_ptr->material); }; bool is_archived() const { return m_spool->archived; }; bool get_checked() { return m_checked; }; From 8256b626529eab68072949d34f1717d0096600a5 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Fri, 27 Dec 2024 19:56:18 -0500 Subject: [PATCH 055/100] Update presets that are visible and compatible rather than all visible presets --- src/libslic3r/Preset.hpp | 5 +++-- src/slic3r/Utils/Spoolman.cpp | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index ba4e5880b6..8ddba128f0 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -676,10 +676,11 @@ public: size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); } - std::vector get_visible() { + // gets all presets that are visible and compatible + std::vector get_compatible() { std::vector ret; for (auto& item : m_presets) - if (item.is_visible) + if (item.is_visible && item.is_compatible) ret.emplace_back(&item); return ret; } diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 9f5612daeb..c38724e767 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -307,7 +307,7 @@ void Spoolman::update_visible_spool_statistics(bool clear_cache) // Clear the cache so that it can be repopulated with the correct info if (clear_cache) get_instance()->clear(); if (printers.get_edited_preset().spoolman_enabled() && is_server_valid()) { - for (auto item : filaments.get_visible()) { + for (auto item : filaments.get_compatible()) { 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: " @@ -328,7 +328,7 @@ void Spoolman::update_specific_spool_statistics(const std::vector& spool_ids_set.erase(0); if (printers.get_edited_preset().spoolman_enabled() && is_server_valid()) { - for (auto item : filaments.get_visible()) { + for (auto item : filaments.get_compatible()) { 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: " From 77454ba5bdd9e5e715ac140617f4860f91d81b2f Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Sun, 29 Dec 2024 23:53:36 -0500 Subject: [PATCH 056/100] Fix use of the incorrect type for "spoolman_spool_id" --- src/slic3r/Utils/Spoolman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index c38724e767..fe1e175b80 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -432,7 +432,7 @@ std::string SpoolmanSpool::get_preset_name() void SpoolmanSpool::apply_to_config(Slic3r::DynamicConfig& config) const { - config.set_key_value("spoolman_spool_id", new ConfigOptionInt(id)); + config.set_key_value("spoolman_spool_id", new ConfigOptionInts({id})); m_filament_ptr->apply_to_config(config); } From 0b0ac65e087c68f60aa3f4c7e2d02581dc97bca4 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Sun, 5 Jan 2025 07:39:13 -0500 Subject: [PATCH 057/100] Update Spoolman::create_filament_preset_from_spool -check if the base_preset is valid. If it isn't, the current edited preset is used -if there is an existing preset, check if it is a system preset. If it is, do not allow overwriting -update the generation of the preset name to include the printer name -only check for presets with the same spool ID within the list of compatible filament presets. users should be allowed to have different presets with the spool ID for different printers -check if the materials are the same between the spool and base_preset -when setting the "inherits" config value, get the first preset that is a system preset or base user preset in the inheritance hierarchy -add translation to messages -update get_preset_name() to prevent extra spaces --- src/slic3r/Utils/Spoolman.cpp | 104 +++++++++++++++++++++------------- src/slic3r/Utils/Spoolman.hpp | 2 +- 2 files changed, 65 insertions(+), 41 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index fe1e175b80..54c5e5761d 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -207,66 +207,83 @@ bool Spoolman::undo_use_spoolman_spools() } SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, - const Preset* base_profile, + const Preset* base_preset, bool detach, bool force) { - PresetCollection& filaments = wxGetApp().preset_bundle->filaments; - string filament_preset_name = spool->get_preset_name(); + PresetCollection& filaments = wxGetApp().preset_bundle->filaments; SpoolmanResult result; - // Check if the preset already exists - Preset* preset = filaments.find_preset(filament_preset_name); - if (preset) { - if (force) { - update_filament_preset_from_spool(preset, true, false); - filaments.save_current_preset(preset->name, detach, false, preset); - return result; - } - std::string msg("Preset already exists with the name"); - BOOST_LOG_TRIVIAL(error) << msg << filament_preset_name; - result.messages.emplace_back(msg); - } + if (!base_preset) + base_preset = &filaments.get_edited_preset(); + + std::string filament_preset_name = spool->get_preset_name(); + + // Bring over the printer name from the base preset or add one for the current printer + if (const auto idx = base_preset->name.rfind(" @"); idx != std::string::npos) + filament_preset_name += base_preset->name.substr(idx); + else + filament_preset_name += " @" + wxGetApp().preset_bundle->printers.get_selected_preset_name(); + + if (const auto idx = filament_preset_name.rfind(" - Copy"); idx != std::string::npos) + filament_preset_name.erase(idx); + + Preset* preset = filaments.find_preset(filament_preset_name); + + if (force) { + if (preset && !preset->is_user()) + result.messages.emplace_back(_u8L("A system preset exists with the same name and cannot be overwritten")); + } else { + // Check if a preset with the same name already exists + if (preset) { + if (preset->is_user()) + result.messages.emplace_back(_u8L("Preset already exists with the same name")); + else + result.messages.emplace_back(_u8L("A system preset exists with the same name and cannot be overwritten")); + } - if (!force) { // 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", 0) == spool->id) { - if (item.is_visible) - visible++; - else - invisible++; + int compatible(0); + for (const auto item : filaments.get_compatible()) { // count num of visible and invisible + if (item->is_user() && item->config.opt_int("spoolman_spool_id", 0) == spool->id) { + compatible++; + if (compatible > 1) + break; } - if (visible > 1 && invisible > 1) - break; } // if there were any, build the message - if (visible) { - if (visible > 1) - result.messages.emplace_back("Multiple visible presets share the same spool ID"); + if (compatible) { + if (compatible > 1) + result.messages.emplace_back(_u8L("Multiple compatible presets share the same spool ID")); else - result.messages.emplace_back("A visible preset shares the same spool ID"); + result.messages.emplace_back(_u8L("A compatible preset shares the same spool ID")); } - if (invisible) { - if (invisible > 1) - result.messages.emplace_back("Multiple invisible presets share the same spool ID"); - else - result.messages.emplace_back("An invisible preset shares the same spool ID"); + + // Check if the material types match between the base preset and the spool + if (base_preset->config.opt_string("filament_type", 0) != spool->m_filament_ptr->material) { + result.messages.emplace_back(_u8L("The materials of the base preset and the Spoolman spool do not match")); } - if (result.has_failed()) - return result; } - std::string inherits = filaments.is_base_preset(*base_profile) ? base_profile->name : base_profile->inherits(); + if (result.has_failed()) + return result; + + // get the first preset that is a system preset or base user preset in the inheritance hierarchy + std::string inherits; + if (!detach) { + if (const auto base = filaments.get_preset_base(*base_preset)) + inherits = base->name; + else // fallback if the above operation fails + inherits = base_preset->name; + } preset = new Preset(Preset::TYPE_FILAMENT, filament_preset_name); - preset->config.apply(base_profile->config); + preset->config.apply(base_preset->config); preset->config.set_key_value("filament_settings_id", new ConfigOptionStrings({filament_preset_name})); preset->config.set("inherits", inherits, true); spool->apply_to_preset(preset); preset->filament_id = get_filament_id(filament_preset_name); - preset->version = base_profile->version; + preset->version = base_preset->version; preset->loaded = true; filaments.save_current_preset(filament_preset_name, detach, false, preset); @@ -427,7 +444,14 @@ void SpoolmanSpool::update_from_server(bool recursive) std::string SpoolmanSpool::get_preset_name() { - return remove_special_key(getVendor()->name + " " + m_filament_ptr->name + " " + m_filament_ptr->material); + auto name = getVendor()->name; + + if (!m_filament_ptr->name.empty()) + name += " " + m_filament_ptr->name; + if (!m_filament_ptr->material.empty()) + name += " " + m_filament_ptr->material; + + return remove_special_key(name); } void SpoolmanSpool::apply_to_config(Slic3r::DynamicConfig& config) const diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index 4f925c4016..58d6f23826 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -91,7 +91,7 @@ public: bool undo_use_spoolman_spools(); static SpoolmanResult create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, - const Preset* base_profile, + const Preset* base_preset, bool detach = false, bool force = false); static SpoolmanResult update_filament_preset_from_spool(Preset* filament_preset, From cbc5e984852ff876c33fea2464c8f69661f35534 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Mon, 6 Jan 2025 06:34:19 -0500 Subject: [PATCH 058/100] Update SpoolmanImportDialog::on_import() -general cleanup -remove the check to see if the spool's preset name is the same as the current preset's name. this is handled in the Spoolman create function -update how the error message is generated -properly handle wide unicode chars in the preset names -check for errors when retrying spools -add translation to messages --- src/slic3r/GUI/SpoolmanImportDialog.cpp | 65 ++++++++++++------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/src/slic3r/GUI/SpoolmanImportDialog.cpp b/src/slic3r/GUI/SpoolmanImportDialog.cpp index 201aaf585e..e2657e50a4 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.cpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.cpp @@ -194,29 +194,23 @@ void SpoolmanImportDialog::on_dpi_changed(const wxRect& suggested_rect) void SpoolmanImportDialog::on_import() { - const Preset* current_preset = wxGetApp().preset_bundle->filaments.find_preset(m_preset_combobox->GetStringSelection().ToUTF8().data()); - const vector& spools = m_svc->get_model()->GetSelectedSpools(); - if (spools.empty()) { - show_error(this, "No spools are selected"); + const auto current_preset = wxGetApp().preset_bundle->filaments.find_preset(m_preset_combobox->GetStringSelection().ToUTF8().data()); + const auto& selected_spools = m_svc->get_model()->GetSelectedSpools(); + if (selected_spools.empty()) { + show_error(this, _L("No spools are selected")); return; } - for (const auto& spool : spools) - if (spool->get_preset_name() == current_preset->name) { - show_error(this, "One of the selected spools is the same as the current base preset.\n" - "Please deselect that spool or select a different base preset."); - return; - } - - bool detach = m_detach_checkbox->GetValue(); + const bool detach = m_detach_checkbox->GetValue(); std::vector> failed_spools; auto create_presets = [&](const vector& spools, bool force = false) { + failed_spools.clear(); // Attempt to create the presets // Calculating the hash for the internal filament id takes a bit, so using multithreading to speed it up std::vector threads; for (const auto& spool : spools) { - threads.emplace_back(Slic3r::create_thread([&spool, &failed_spools, ¤t_preset, &force, &detach]() { + threads.emplace_back(Slic3r::create_thread([&] { auto res = Spoolman::create_filament_preset_from_spool(spool, current_preset, detach, force); if (res.has_failed()) failed_spools.emplace_back(spool, res); @@ -229,40 +223,43 @@ void SpoolmanImportDialog::on_import() thread.join(); }; - create_presets(spools); + create_presets(selected_spools); // Show message with any errors if (!failed_spools.empty()) { - // message spools with same message - std::map> sorted_error_messages; + auto build_error_msg = [&](const wxString& prefix, const wxString& postfix = "") { + wxString error_message = prefix + ":\n\n"; + for (const auto& [spool_ptr, result] : failed_spools) { + error_message += wxString::FromUTF8(spool_ptr->get_preset_name()) + ":\n"; + for (const auto& msg : result.messages) { + error_message += " - " + msg + "\n"; + } + error_message += "\n"; + } + if (postfix.empty()) + error_message.erase(error_message.size() - 2); + else + error_message += postfix; + return error_message; + }; - for (const std::pair& failed_spool : failed_spools) - for (const auto& msg : failed_spool.second.messages) - sorted_error_messages[msg].emplace_back(failed_spool.first); + const auto error_message = build_error_msg(_L("Errors were generated while trying to import the selected spools"), + _L("Would you like to ignore these issues and continue?")); - std::stringstream error_message; - for (const auto& msg_pair : sorted_error_messages) { - for (const auto& errored_spool : msg_pair.second) - error_message << errored_spool->get_preset_name() << ",\n"; - error_message.seekp(-2, ios_base::end); - error_message << ":\n"; - error_message << "\t" << msg_pair.first << std::endl << std::endl; - } - - error_message << "Would you like to ignore these issues and continue?\n" - "Presets with the same name will be updated and presets with conflicting IDs will be forcibly created."; - - WarningDialog dlg = WarningDialog(this, error_message.str(), wxEmptyString, wxYES | wxCANCEL); + auto dlg = WarningDialog(this, error_message, wxEmptyString, wxYES | wxCANCEL); if (dlg.ShowModal() == wxID_YES) { std::vector retry_spools; - for (const auto& item : failed_spools) - retry_spools.emplace_back(item.first); + for (const auto& [spool_ptr, res] : failed_spools) + retry_spools.emplace_back(spool_ptr); create_presets(retry_spools, true); + if (!failed_spools.empty()) + show_error(this, build_error_msg(_L("Errors were still generated during force import"))); this->EndModal(wxID_OK); } // Update the combobox to display any successfully added presets m_preset_combobox->update(); + // Don't close the dialog so that the user may update their selections and try again return; } this->EndModal(wxID_OK); From bc90fd73a9511f2d1d2ce5828b5e32036aac500b Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Mon, 6 Jan 2025 06:41:31 -0500 Subject: [PATCH 059/100] Add max timeout for Spoolman HTTP requests --- src/slic3r/Utils/Spoolman.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 54c5e5761d..b57a61be90 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -12,6 +12,9 @@ namespace { template Type get_opt(pt::ptree& data, string path) { return data.get_optional(path).value_or(Type()); } } // namespace +// Max timout in seconds for Spoolman HTTP requests +static constexpr long MAX_TIMEOUT = 5; + //--------------------------------- // Spoolman //--------------------------------- @@ -57,6 +60,7 @@ pt::ptree Spoolman::get_spoolman_json(const string& api_endpoint) res_body = std::move(body); res = true; }) + .timeout_max(MAX_TIMEOUT) .perform_sync(); if (!res) @@ -100,6 +104,7 @@ pt::ptree Spoolman::put_spoolman_json(const string& api_endpoint, const pt::ptre res_body = std::move(body); res = true; }) + .timeout_max(MAX_TIMEOUT) .perform_sync(); if (!res) @@ -362,7 +367,9 @@ bool Spoolman::is_server_valid() Http::get(get_spoolman_api_url() + "info").on_complete([&res](std::string, unsigned http_status) { if (http_status == 200) res = true; - }).perform_sync(); + }) + .timeout_max(MAX_TIMEOUT) + .perform_sync(); return res; } From c4d75ed0849679ae56e50345b80ebf651121a2d9 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Tue, 21 Jan 2025 19:11:16 -0500 Subject: [PATCH 060/100] Move "spoolman_enabled" and "spoolman_host" to the app config This allows these options to be saved across printer presets. This commit removes the options from PhysicalPrinterDialog.cpp and does not include a way to set the values in the program. This will be implemented in a future commit. --- src/libslic3r/AppConfig.cpp | 6 ++++ src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Preset.hpp | 4 +-- src/libslic3r/PresetBundle.cpp | 1 - src/libslic3r/PrintConfig.cpp | 16 ----------- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 34 ++--------------------- src/slic3r/GUI/Plater.cpp | 4 +-- src/slic3r/GUI/PresetComboBoxes.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 4 +-- src/slic3r/Utils/Spoolman.cpp | 35 ++++++++++-------------- src/slic3r/Utils/Spoolman.hpp | 12 ++++++-- 11 files changed, 38 insertions(+), 82 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index ce71d4d74a..a3b0782825 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -410,6 +410,12 @@ void AppConfig::set_defaults() if (get("show_spoolman_consumption_dialog").empty()) set_bool("show_spoolman_consumption_dialog", true); + if (get("spoolman", "enabled").empty()) + set("spoolman", "enabled", false); + + if (get("spoolman", "host").empty()) + set_str("spoolman", "host", ""); + if (get("spoolman", "consumption_type").empty()) set_str("spoolman", "consumption_type", "weight"); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 63d2a8c7e4..059d8ce112 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -870,7 +870,7 @@ static std::vector s_Preset_printer_options { "nozzle_type", "nozzle_hrc","auxiliary_fan", "nozzle_volume","upward_compatible_machine", "z_hop_types", "travel_slope", "retract_lift_enforce","support_chamber_temp_control","support_air_filtration","printer_structure", "best_object_pos","head_wrap_detect_zone", "host_type", "print_host", "printhost_apikey", "bbl_use_printhost", - "print_host_webui", "spoolman_enabled", "spoolman_host", + "print_host_webui", "printhost_cafile","printhost_port","printhost_authorization_type", "printhost_user", "printhost_password", "printhost_ssl_ignore_revoke", "thumbnails", "thumbnails_format", "use_firmware_retraction", "use_relative_e_distances", "printer_notes", diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 8ddba128f0..c0a898a7b4 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -246,12 +246,10 @@ public: std::map key_values; // indicate if spoolman is enabled for this preset - // works for filament and printer profiles. All other profiles return false + // works for filament presets only. All other profiles return false bool spoolman_enabled() const { if (type == TYPE_FILAMENT) 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 68b9e5a689..5ed3d00f7f 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -2117,7 +2117,6 @@ DynamicPrintConfig PresetBundle::full_config_secure() const config.erase("printhost_user"); config.erase("printhost_password"); config.erase("printhost_port"); - config.erase("spoolman_host"); return config; } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 42e31db3bd..78ce0be744 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -550,22 +550,6 @@ void PrintConfigDef::init_common_params() def->cli = ConfigOptionDef::nocli; def->set_default_value(new ConfigOptionString("")); - def = this->add("spoolman_enabled", coBool); - def->label = L("Spoolman Support"); - def->tooltip = L("Enables spool management features powered by a Spoolman server instance"); - def->mode = comAdvanced; - def->cli = ConfigOptionDef::nocli; - def->set_default_value(new ConfigOptionBool()); - - def = this->add("spoolman_host", coString); - def->label = L("Spoolman Host"); - def->tooltip = L("Points to where you Spoolman instance is hosted. " - "You can either provide just the port to use the same URL as your printer " - "or provide the full address in the format of :."); - def->mode = comAdvanced; - def->cli = ConfigOptionDef::nocli; - def->set_default_value(new ConfigOptionString("8000")); - def = this->add("print_host_webui", coString); def->label = L("Device UI"); def->tooltip = L("Specify the URL of your device user interface if it's not same as print_host"); diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 3d7cc4602e..3207bff534 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -125,7 +125,7 @@ PhysicalPrinterDialog::~PhysicalPrinterDialog() void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup) { m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (opt_key == "host_type" || opt_key == "printhost_authorization_type" || opt_key == "spoolman_enabled") + if (opt_key == "host_type" || opt_key == "printhost_authorization_type") this->update(); if (opt_key == "print_host") this->update_printhost_buttons(); @@ -137,12 +137,6 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr m_optgroup->append_single_option_line("host_type"); - m_optgroup->append_single_option_line("spoolman_enabled"); - - Option option = m_optgroup->get_option("spoolman_host"); - option.opt.width = Field::def_width_wider(); - m_optgroup->append_single_option_line(option); - auto create_sizer_with_btn = [](wxWindow* parent, ScalableButton** btn, const std::string& icon_name, const wxString& label) { *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); (*btn)->SetFont(wxGetApp().normal_font()); @@ -250,7 +244,7 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr }; // Set a wider width for a better alignment - option = m_optgroup->get_option("print_host"); + Option option = m_optgroup->get_option("print_host"); option.opt.width = Field::def_width_wider(); Line host_line = m_optgroup->create_single_option_line(option); host_line.append_widget(printhost_browse); @@ -584,17 +578,6 @@ void PhysicalPrinterDialog::update(bool printer_change) supports_multiple_printers = opt->value == htRepetier || opt->value == htObico; } - if (opt->value == htOctoPrint) { - m_optgroup->show_field("spoolman_enabled"); - m_optgroup->show_field("spoolman_host", m_config->opt_bool("spoolman_enabled")); - } else { - m_config->set("spoolman_enabled", false); - m_optgroup->hide_field("spoolman_enabled"); - - m_config->set("spoolman_host", m_optgroup->get_option("spoolman_host").opt.get_default_value()->value); - m_optgroup->hide_field("spoolman_host"); - } - if (opt->value == htFlashforge) { m_optgroup->hide_field("printhost_apikey"); m_optgroup->hide_field("printhost_authorization_type"); @@ -749,20 +732,7 @@ void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) void PhysicalPrinterDialog::OnOK(wxEvent& event) { - // determine if any spoolman related keys have been updated - bool update_spool_stats = false; - const vector& current_dirty_options = m_presets->current_dirty_options(); - if (!current_dirty_options.empty()) { - for (const auto& option_key : {"spoolman_enabled", "spoolman_host", "print_host"}) { - if (std::find(current_dirty_options.begin(), current_dirty_options.end(), option_key) != current_dirty_options.end()) { - update_spool_stats = true; - break; - } - } - } wxGetApp().get_tab(Preset::TYPE_PRINTER)->save_preset("", false, false, true, m_preset_name ); - if (update_spool_stats) // only update spoolman if spoolman related keys were changed - Spoolman::update_visible_spool_statistics(true); event.Skip(); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 44eef2cc3e..047731523a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6617,7 +6617,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) view3D->deselect_all(); } - Spoolman::update_visible_spool_statistics(true); + Spoolman::update_visible_spool_statistics(); #if 0 // do not toggle auto calc when change printer // update flush matrix @@ -6879,8 +6879,6 @@ void Plater::priv::spoolman_consumption_dialog(const bool& all_plates) static constexpr auto show_dlg_key = "show_spoolman_consumption_dialog"; if (!wxGetApp().app_config->get_bool(show_dlg_key)) return; - if (!wxGetApp().preset_bundle->printers.get_edited_preset().spoolman_enabled()) - return; if (!Spoolman::is_server_valid()) return; diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 1ffdfb364b..4131e18947 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1107,7 +1107,7 @@ void PlaterPresetComboBox::update() if (m_type == Preset::TYPE_FILAMENT) { set_label_marker(Append(separator(L("Add/Remove filaments")), *bmp), LABEL_ITEM_WIZARD_FILAMENTS); - if (m_preset_bundle->printers.get_edited_preset().config.opt_bool("spoolman_enabled")) + if (Spoolman::is_enabled()) set_label_marker(Append(separator(L("Import filament from Spoolman")), *bmp), LABEL_ITEM_IMPORT_SPOOLMAN); } else if (m_type == Preset::TYPE_SLA_MATERIAL) set_label_marker(Append(separator(L("Add/Remove materials")), *bmp), LABEL_ITEM_WIZARD_MATERIALS); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 1d0e28f5e4..ae77155245 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3561,8 +3561,8 @@ void TabFilament::build() build_statistics_line("spoolman_used_length", "Used Length", "m"); build_statistics_line("spoolman_archived", "Archived", "", coBool); - page->m_should_show_fn = [&](bool current_value) { - return m_preset_bundle->printers.get_edited_preset().config.opt_bool("spoolman_enabled"); + page->m_should_show_fn = [&](bool) { + return Spoolman::is_enabled(); }; page = add_options_page(L("Multimaterial"), "custom-gcode_multi_material"); // ORCA: icon only visible on placeholders diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index b57a61be90..983945ce03 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -21,25 +21,17 @@ static constexpr long MAX_TIMEOUT = 5; static std::string get_spoolman_api_url() { - DynamicPrintConfig& config = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config; - string host = config.opt_string("print_host"); - string spoolman_host = config.opt_string("spoolman_host"); - string spoolman_port; + std::string spoolman_host = wxGetApp().app_config->get("spoolman", "host"); + std::string spoolman_port = Spoolman::DEFAULT_PORT; - if (auto idx = spoolman_host.find_last_of(':'); idx != string::npos) { - boost::regex pattern("(?[a-zA-Z0-9.]+):(?[0-9]+)"); + // If the host contains a port, use that rather than the default + if (const auto idx = spoolman_host.find_last_of(':'); idx != string::npos) { + static const boost::regex pattern("(?[a-zA-Z0-9.]+):(?[0-9]+)"); boost::smatch result; boost::regex_search(spoolman_host, result, pattern); spoolman_port = result["port"]; // get port value first since it is overwritten when setting the host value in the next line spoolman_host = result["host"]; - } else if (regex_match(spoolman_host, regex("^[0-9]+"))) { - spoolman_port = spoolman_host; - spoolman_host.clear(); - } else if (auto idx = host.find_last_of(':'); idx != string::npos) - host = host.erase(idx); - - if (spoolman_host.empty()) - spoolman_host = host; + } return spoolman_host + ":" + spoolman_port + "/api/v1/"; } @@ -323,13 +315,12 @@ SpoolmanResult Spoolman::update_filament_preset_from_spool(Preset* filament_pres void Spoolman::update_visible_spool_statistics(bool clear_cache) { PresetBundle* preset_bundle = GUI::wxGetApp().preset_bundle; - PresetCollection& printers = preset_bundle->printers; PresetCollection& filaments = preset_bundle->filaments; // Clear the cache so that it can be repopulated with the correct info if (clear_cache) get_instance()->clear(); - if (printers.get_edited_preset().spoolman_enabled() && is_server_valid()) { - for (auto item : filaments.get_compatible()) { + if (is_server_valid()) { + for (const auto item : filaments.get_compatible()) { 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: " @@ -342,15 +333,14 @@ void Spoolman::update_visible_spool_statistics(bool clear_cache) 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_compatible()) { + if (is_server_valid()) { + for (const auto item : filaments.get_compatible()) { 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: " @@ -364,6 +354,9 @@ void Spoolman::update_specific_spool_statistics(const std::vector& bool Spoolman::is_server_valid() { bool res = false; + if (!is_enabled()) + return res; + Http::get(get_spoolman_api_url() + "info").on_complete([&res](std::string, unsigned http_status) { if (http_status == 200) res = true; @@ -373,6 +366,8 @@ bool Spoolman::is_server_valid() return res; } +bool Spoolman::is_enabled() { return GUI::wxGetApp().app_config->get_bool("spoolman", "enabled"); } + //--------------------------------- // SpoolmanVendor //--------------------------------- diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index 58d6f23826..1dc5e6fa25 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -80,6 +80,8 @@ class Spoolman /// \returns if succeeded bool use_spoolman_spool(const unsigned int& spool_id, const double& usage, const std::string& usage_type); public: + static constexpr auto DEFAULT_PORT = "7912"; + /// 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. @@ -105,21 +107,25 @@ public: /// 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); + /// Check if Spoolman is enabled and the provided host is valid static bool is_server_valid(); + /// Check if Spoolman is enabled + static bool is_enabled(); + const std::map& get_spoolman_spools(bool update = false) { if (update || !m_initialized) m_initialized = pull_spoolman_spools(); return m_spools; - }; + } SpoolmanSpoolShrPtr get_spoolman_spool_by_id(unsigned int spool_id, bool update = false) { if (update || !m_initialized) m_initialized = pull_spoolman_spools(); return m_spools[spool_id]; - }; + } void clear() { @@ -134,7 +140,7 @@ public: if (!m_instance) new Spoolman(); return m_instance; - }; + } friend class SpoolmanVendor; friend class SpoolmanFilament; From aad0b2f01ab2a569eec7dfeb2ec1f9adc48974bf Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Tue, 21 Jan 2025 19:31:00 -0500 Subject: [PATCH 061/100] TEMP: Add spoolman options back to PhysicalPrinterDialog.cpp as fake options Adds fake options that save to the app config. These options are available regardless of what host type is selected --- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 35 +++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 3207bff534..1c3c4a4d95 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -125,7 +125,7 @@ PhysicalPrinterDialog::~PhysicalPrinterDialog() void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup) { m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (opt_key == "host_type" || opt_key == "printhost_authorization_type") + if (opt_key == "host_type" || opt_key == "printhost_authorization_type" || opt_key == "spoolman_enabled") this->update(); if (opt_key == "print_host") this->update_printhost_buttons(); @@ -137,6 +137,22 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr m_optgroup->append_single_option_line("host_type"); + ConfigOptionDef def; + def.type = coBool; + def.label = _u8L("Spoolman Enabled"); + def.tooltip = _u8L("Enables spool management features powered by a Spoolman server instance"); + def.set_default_value(new ConfigOptionBool()); + m_optgroup->append_single_option_line((Option(def, "spoolman_enabled"))); + + def = ConfigOptionDef(); + def.type = coString; + def.label = _u8L("Spoolman Host"); + def.tooltip = _u8L("Points to where you Spoolman instance is hosted. Use the format of :. You may also just specify the " + "host and it will use the default Spoolman port of ") + Spoolman::DEFAULT_PORT; + def.set_default_value(new ConfigOptionString()); + m_optgroup->append_single_option_line(Option(def, "spoolman_host")); + + auto create_sizer_with_btn = [](wxWindow* parent, ScalableButton** btn, const std::string& icon_name, const wxString& label) { *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); (*btn)->SetFont(wxGetApp().normal_font()); @@ -349,6 +365,9 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr m_optgroup->activate(); + m_optgroup->get_field("spoolman_enabled")->set_value(wxGetApp().app_config->get_bool("spoolman", "enabled"), false); + m_optgroup->get_field("spoolman_host")->set_value(wxString::FromUTF8(wxGetApp().app_config->get("spoolman", "host")), false); + Field* printhost_field = m_optgroup->get_field("print_host"); if (printhost_field) { @@ -552,6 +571,8 @@ void PhysicalPrinterDialog::update(bool printer_change) if (m_printhost_cafile_browse_btn) m_printhost_cafile_browse_btn->Enable(); + m_optgroup->show_field("spoolman_host", any_cast(m_optgroup->get_field("spoolman_enabled")->get_value())); + // hide pre-configured address, in case user switched to a different host type if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) { if (wxTextCtrl* temp = dynamic_cast(printhost_field)->text_ctrl(); temp) { @@ -732,6 +753,18 @@ void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) void PhysicalPrinterDialog::OnOK(wxEvent& event) { + const auto host = any_cast(m_optgroup->get_field("spoolman_host")->get_value()); + const auto enabled = any_cast(m_optgroup->get_field("spoolman_enabled")->get_value()); + const auto& appconfig = wxGetApp().app_config; + + // clear the Spoolman cache and reload if either of the Spoolman settings change + // clear the Spoolman cache and reload if either of the Spoolman settings change + if (enabled != appconfig->get_bool("spoolman", "enabled") || host != appconfig->get("spoolman", "host")) { + appconfig->set("spoolman", "enabled", enabled); + appconfig->set("spoolman", "host", host); + Spoolman::update_visible_spool_statistics(true); + } + wxGetApp().get_tab(Preset::TYPE_PRINTER)->save_preset("", false, false, true, m_preset_name ); event.Skip(); } From 3b1d61922ca70a6d8ebf5e48407a12d278c33f22 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Thu, 23 Jan 2025 03:34:35 -0500 Subject: [PATCH 062/100] Fix spools not being pulled when calling Spoolman::update_filament_preset_from_spool When starting with an empty spoolman instance or after clearing the spoolman instance, spools should be pulled from the server the next time you try to do a spoolman action. This would not happen when calling Spoolman::update_filament_preset_from_spool because it was directly accessing the vector of spools rather than accessing them through the helper function --- src/slic3r/Utils/Spoolman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 983945ce03..22e3b463b4 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -301,7 +301,7 @@ SpoolmanResult Spoolman::update_filament_preset_from_spool(Preset* filament_pres "Preset provided does not have a valid Spoolman spool ID"); // IDs below 1 are not used by spoolman and should be ignored return result; } - SpoolmanSpoolShrPtr& spool = get_instance()->m_spools[spool_id]; + SpoolmanSpoolShrPtr spool = get_instance()->get_spoolman_spool_by_id(spool_id); if (!spool) { result.messages.emplace_back("The spool ID does not exist in the local spool cache"); return result; From 426f97841b3e43f4b57e3ec2d23e385b95526cae Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Sat, 22 Feb 2025 18:03:45 -0500 Subject: [PATCH 063/100] Fix crash upon invalid input --- src/slic3r/Utils/Spoolman.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 22e3b463b4..9e28c0baab 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -24,13 +24,19 @@ static std::string get_spoolman_api_url() std::string spoolman_host = wxGetApp().app_config->get("spoolman", "host"); std::string spoolman_port = Spoolman::DEFAULT_PORT; + // Remove http(s) designator from the string as it interferes with the next step + spoolman_host = boost::regex_replace(spoolman_host, boost::regex("https?://"), ""); + // If the host contains a port, use that rather than the default - if (const auto idx = spoolman_host.find_last_of(':'); idx != string::npos) { - static const boost::regex pattern("(?[a-zA-Z0-9.]+):(?[0-9]+)"); + if (spoolman_host.find_last_of(':') != string::npos) { + static const boost::regex pattern(R"((?[a-z0-9.\-_]+):(?[0-9]+))", boost::regex_constants::icase); boost::smatch result; - boost::regex_search(spoolman_host, result, pattern); - spoolman_port = result["port"]; // get port value first since it is overwritten when setting the host value in the next line - spoolman_host = result["host"]; + if (boost::regex_match(spoolman_host, result, pattern)) { + spoolman_port = result["port"]; // get port value first since it is overwritten when setting the host value in the next line + spoolman_host = result["host"]; + } else { + BOOST_LOG_TRIVIAL(error) << "Failed to parse host string. Host: " << spoolman_host << ", Port: " << spoolman_port; + } } return spoolman_host + ":" + spoolman_port + "/api/v1/"; From 4efd36ad2979981fdf08ba8ac27b0c575ddfcdb1 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Thu, 21 Aug 2025 10:38:49 +0000 Subject: [PATCH 064/100] Fix flatpak build --- src/slic3r/Utils/Spoolman.cpp | 1 + src/slic3r/Utils/Spoolman.hpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 9e28c0baab..9f157e7df7 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "Spoolman.hpp" #include "Http.hpp" diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index 1dc5e6fa25..8693dca062 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -1,6 +1,10 @@ #ifndef SLIC3R_SPOOLMAN_HPP #define SLIC3R_SPOOLMAN_HPP +#include +#include +#include + namespace pt = boost::property_tree; namespace Slic3r { From 4db1a3311c62dc4ecda02d4195c84c27239044c9 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Mon, 8 Sep 2025 11:20:38 +0000 Subject: [PATCH 065/100] Improve SpoolmanImportDialog sizing --- src/slic3r/GUI/SpoolmanImportDialog.cpp | 65 +++++++++++++++++++++---- src/slic3r/GUI/SpoolmanImportDialog.hpp | 2 +- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/SpoolmanImportDialog.cpp b/src/slic3r/GUI/SpoolmanImportDialog.cpp index e2657e50a4..bf4bbbfeb3 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.cpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.cpp @@ -7,6 +7,13 @@ #define BTN_GAP FromDIP(10) #define BTN_SIZE wxSize(FromDIP(58), FromDIP(24)) +#ifdef _WIN32 +#define GET_COLUMN(dvc, idx) dvc->GetColumnAt(idx) +#else +#define GET_COLUMN(dvc, idx) dvc->GetColumn(idx) +#endif + + namespace Slic3r { namespace GUI { //----------------------------------------- @@ -162,21 +169,52 @@ SpoolmanImportDialog::SpoolmanImportDialog(wxWindow* parent) // Buttons main_sizer->Add(create_btn_sizer(), 0, wxCENTER | wxEXPAND | wxALL, EM); - this->SetSizer(main_sizer); - // Load data into SVC for (const auto& spoolman_spool : m_spoolman->get_spoolman_spools(true)) m_svc->get_model()->AddSpool(spoolman_spool.second); - int colWidth = 8 * EM; // 4 EM for checkbox (width isn't calculated right), 4 EM for border - for (int i = COL_ID; i <= COL_MATERIAL; ++i) { -#ifdef _WIN32 - colWidth += m_svc->GetColumnAt(i)->GetWidth(); +#ifdef __LINUX__ + // Column width is not updated until shown in wxGTK + bool adjusting_width = false; + + m_svc->Bind(wxEVT_SIZE, [&](wxSizeEvent&) { + // A column width of 0 means the view has not fully initialized yet. Ignore events while the view is uninitialized. + // Ignore any events caused by the adjusting the width + if (GET_COLUMN(m_svc, 1)->GetWidth() == 0 || adjusting_width) return; + + int colWidth = 4 * EM; // 4 EM for checkbox (width isn't calculated right) + for (int i = COL_ID; i < COL_COUNT; ++i) + colWidth += GET_COLUMN(m_svc, i)->GetWidth(); + // Add buffer to ensure the scrollbars hide + colWidth += EM / 2; + + int old_width = m_svc->GetSize().GetWidth(); + if (old_width == colWidth) return; + + // Start adjusting the width of the view. Ignore any size events caused by this + adjusting_width = true; + m_svc->SetMinSize({colWidth, -1}); + + // Re-center the window + auto window_pos = this->GetPosition(); + window_pos.x -= (colWidth - old_width) / 2; + this->SetPosition(window_pos); + + this->Fit(); + this->CallAfter([&] { + this->Layout(); + adjusting_width = false; + }); + }); #else - colWidth += m_svc->GetColumn(i)->GetWidth(); -#endif // _WIN32 - } - this->SetSize(wxDefaultCoord, wxDefaultCoord, colWidth, wxDefaultCoord, wxSIZE_SET_CURRENT); + int colWidth = 4 * EM; // 4 EM for checkbox (width isn't calculated right) + for (int i = COL_ID; i < COL_COUNT; ++i) + colWidth += GET_COLUMN(m_svc, i)->GetWidth(); + m_svc->SetMinSize({colWidth, -1}); +#endif + + main_sizer->SetMinSize({-1, 45 * EM}); + this->SetSizerAndFit(main_sizer); this->ShowModal(); } @@ -188,6 +226,13 @@ void SpoolmanImportDialog::on_dpi_changed(const wxRect& suggested_rect) btn->SetCornerRadius(FromDIP(12)); } +#ifndef __LINUX__ + int colWidth = 4 * EM; // 4 EM for checkbox (width isn't calculated right) + for (int i = COL_ID; i < COL_COUNT; ++i) + colWidth += GET_COLUMN(m_svc, i)->GetWidth(); + m_svc->SetMinSize({colWidth, -1}); +#endif + Fit(); Refresh(); } diff --git a/src/slic3r/GUI/SpoolmanImportDialog.hpp b/src/slic3r/GUI/SpoolmanImportDialog.hpp index e69d8414d6..4ee7b0dea6 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.hpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.hpp @@ -10,7 +10,7 @@ namespace Slic3r { namespace GUI { class SpoolmanViewCtrl; -enum Column { COL_CHECK = 0, COL_ID, COL_COLOR, COL_VENDOR, COL_NAME, COL_MATERIAL }; +enum Column { COL_CHECK = 0, COL_ID, COL_COLOR, COL_VENDOR, COL_NAME, COL_MATERIAL, COL_COUNT }; //----------------------------------------- // SpoolmanNode From 76d39f383830111da563aa99c7a63e2c232847df Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Mon, 8 Sep 2025 14:30:40 +0000 Subject: [PATCH 066/100] Use new standardized dialog buttons --- src/slic3r/GUI/SpoolmanImportDialog.cpp | 84 ++++-------------------- src/slic3r/GUI/SpoolmanImportDialog.hpp | 3 - src/slic3r/GUI/Widgets/DialogButtons.cpp | 24 +++++-- src/slic3r/GUI/Widgets/DialogButtons.hpp | 5 ++ 4 files changed, 33 insertions(+), 83 deletions(-) diff --git a/src/slic3r/GUI/SpoolmanImportDialog.cpp b/src/slic3r/GUI/SpoolmanImportDialog.cpp index bf4bbbfeb3..628df1d32b 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.cpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.cpp @@ -3,6 +3,7 @@ #include "GUI_App.hpp" #include "ExtraRenderers.hpp" #include "MsgDialog.hpp" +#include "Widgets/DialogButtons.hpp" #define BTN_GAP FromDIP(10) #define BTN_SIZE wxSize(FromDIP(58), FromDIP(24)) @@ -166,8 +167,16 @@ SpoolmanImportDialog::SpoolmanImportDialog(wxWindow* parent) main_sizer->Add(preset_sizer, 0, wxEXPAND | wxALL, EM); - // Buttons - main_sizer->Add(create_btn_sizer(), 0, wxCENTER | wxEXPAND | wxALL, EM); + auto buttons = new DialogButtons(this, {"All", "None", "Import", "Cancel"}, _L("Import")); + buttons->SetLeftAlignLabels({_L("All"), _L("None")}); + buttons->UpdateButtons(); + + buttons->GetButtonFromLabel(_L("All"))->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { m_svc->get_model()->SetAllToggles(true); }); + buttons->GetButtonFromLabel(_L("None"))->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { m_svc->get_model()->SetAllToggles(false); }); + buttons->GetButtonFromLabel(_L("Import"))->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { on_import(); }); + buttons->GetButtonFromLabel(_L("Cancel"))->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { this->EndModal(wxID_CANCEL); }); + + main_sizer->Add(buttons, 0, wxCENTER | wxEXPAND | wxALL, EM); // Load data into SVC for (const auto& spoolman_spool : m_spoolman->get_spoolman_spools(true)) @@ -221,11 +230,6 @@ SpoolmanImportDialog::SpoolmanImportDialog(wxWindow* parent) void SpoolmanImportDialog::on_dpi_changed(const wxRect& suggested_rect) { - for (auto btn : m_button_list) { - btn->SetMinSize(BTN_SIZE); - btn->SetCornerRadius(FromDIP(12)); - } - #ifndef __LINUX__ int colWidth = 4 * EM; // 4 EM for checkbox (width isn't calculated right) for (int i = COL_ID; i < COL_COUNT; ++i) @@ -310,70 +314,4 @@ void SpoolmanImportDialog::on_import() this->EndModal(wxID_OK); } -// Orca: Apply buttons style -wxBoxSizer* SpoolmanImportDialog::create_btn_sizer() -{ - auto btn_sizer = new wxBoxSizer(wxHORIZONTAL); - - auto apply_highlighted_btn_colors = [](Button* btn) { - btn->SetBackgroundColor(StateColor(std::pair(wxColour(0, 137, 123), StateColor::Pressed), - std::pair(wxColour(38, 166, 154), StateColor::Hovered), - std::pair(wxColour(0, 150, 136), StateColor::Normal))); - - btn->SetBorderColor(StateColor(std::pair(wxColour(0, 150, 136), StateColor::Normal))); - - btn->SetTextColor(StateColor(std::pair(wxColour(255, 255, 254), StateColor::Normal))); - }; - - auto apply_std_btn_colors = [](Button* btn) { - btn->SetBackgroundColor(StateColor(std::pair(wxColour(206, 206, 206), StateColor::Pressed), - std::pair(wxColour(238, 238, 238), StateColor::Hovered), - std::pair(wxColour(255, 255, 255), StateColor::Normal))); - - btn->SetBorderColor(StateColor(std::pair(wxColour(38, 46, 48), StateColor::Normal))); - - btn->SetTextColor(StateColor(std::pair(wxColour(38, 46, 48), StateColor::Normal))); - }; - - auto style_btn = [this, apply_highlighted_btn_colors, apply_std_btn_colors](Button* btn, bool highlight) { - btn->SetMinSize(BTN_SIZE); - btn->SetCornerRadius(FromDIP(12)); - if (highlight) - apply_highlighted_btn_colors(btn); - else - apply_std_btn_colors(btn); - }; - - Button* all_btn = new Button(this, _L("All")); - style_btn(all_btn, false); - all_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { m_svc->get_model()->SetAllToggles(true); }); - btn_sizer->Add(all_btn, 0, wxALIGN_CENTER_VERTICAL); - m_button_list.push_back(all_btn); - - Button* none_btn = new Button(this, _L("None")); - style_btn(none_btn, false); - none_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { m_svc->get_model()->SetAllToggles(false); }); - btn_sizer->Add(none_btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, BTN_GAP); - m_button_list.push_back(none_btn); - - btn_sizer->AddStretchSpacer(); - - Button* import_btn = new Button(this, _L("Import")); - style_btn(import_btn, true); - import_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_import(); }); - import_btn->SetFocus(); - import_btn->SetId(wxID_OK); - btn_sizer->Add(import_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, BTN_GAP); - m_button_list.push_back(import_btn); - - Button* cancel_btn = new Button(this, _L("Cancel")); - style_btn(cancel_btn, false); - cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { this->EndModal(wxID_CANCEL); }); - cancel_btn->SetId(wxID_CANCEL); - btn_sizer->Add(cancel_btn, 0, wxALIGN_CENTER_VERTICAL); - m_button_list.push_back(cancel_btn); - - return btn_sizer; -} - }} // namespace Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/SpoolmanImportDialog.hpp b/src/slic3r/GUI/SpoolmanImportDialog.hpp index 4ee7b0dea6..4f3b152012 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.hpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.hpp @@ -131,9 +131,6 @@ protected: void on_import(); - wxBoxSizer* create_btn_sizer(); - - std::vector m_button_list; Spoolman* m_spoolman{Spoolman::get_instance()}; SpoolmanViewCtrl* m_svc; TabPresetComboBox* m_preset_combobox; diff --git a/src/slic3r/GUI/Widgets/DialogButtons.cpp b/src/slic3r/GUI/Widgets/DialogButtons.cpp index 4db205eca9..1a2ccfe619 100644 --- a/src/slic3r/GUI/Widgets/DialogButtons.cpp +++ b/src/slic3r/GUI/Widgets/DialogButtons.cpp @@ -117,6 +117,16 @@ void DialogButtons::SetAlertButton(wxString translated_label) { btn->SetStyle(ButtonStyle::Alert, ButtonType::Choice); } +void DialogButtons::SetLeftAlignIDs(std::set ids) +{ + m_left_align_IDs = ids; +} + +void DialogButtons::SetLeftAlignLabels(std::set translated_labels) +{ + m_left_align_labels = translated_labels; +} + void DialogButtons::UpdateButtons() { m_sizer->Clear(); SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#FFFFFF"))); @@ -129,22 +139,22 @@ void DialogButtons::UpdateButtons() { int btn_gap = FromDIP(10); - auto list = m_left_align_IDs; - auto on_left = [list](int id){ - return list.find(wxStandardID(id)) != list.end(); + auto& id_list = m_left_align_IDs; + auto& label_list = m_left_align_labels; + auto on_left = [&id_list, &label_list](const Button* button){ + return id_list.find(wxStandardID(button->GetId())) != id_list.end() || label_list.find(button->GetLabel()) != label_list.end(); }; for (Button* btn : m_buttons) // Left aligned - if(on_left(btn->GetId())) + if(on_left(btn)) m_sizer->Add(btn, 0, wxLEFT | wxTOP | wxBOTTOM | wxALIGN_CENTER_VERTICAL, btn_gap); m_sizer->AddStretchSpacer(); - if(m_sizer->IsEmpty()) // add left margin if no button on left. fixes no gap on small windows - m_sizer->AddSpacer(btn_gap); + m_sizer->AddSpacer(btn_gap); for (Button* btn : m_buttons) // Right aligned - if(!on_left(btn->GetId())) + if(!on_left(btn)) m_sizer->Add(btn, 0, wxRIGHT | wxTOP | wxBOTTOM | wxALIGN_CENTER_VERTICAL, btn_gap); SetPrimaryButton(m_primary); diff --git a/src/slic3r/GUI/Widgets/DialogButtons.hpp b/src/slic3r/GUI/Widgets/DialogButtons.hpp index df80e8aee3..de504a87db 100644 --- a/src/slic3r/GUI/Widgets/DialogButtons.hpp +++ b/src/slic3r/GUI/Widgets/DialogButtons.hpp @@ -37,6 +37,9 @@ public: void SetAlertButton(wxString label); + void SetLeftAlignIDs(std::set ids); + void SetLeftAlignLabels(std::set translated_labels); + void UpdateButtons(); ~DialogButtons(); @@ -112,6 +115,8 @@ private: wxID_FORWARD }; + std::set m_left_align_labels; + Button* PickFromList(std::set ID_list); int FromDIP(int d); From e873612b15391c3c179c28dc65f715b8ab47e513 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Thu, 30 Oct 2025 03:53:04 -0400 Subject: [PATCH 067/100] Fix compile errors --- src/libslic3r/Preset.cpp | 2 +- src/slic3r/GUI/NotificationManager.cpp | 4 ++-- src/slic3r/GUI/NotificationManager.hpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 9167b11762..545266d240 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -595,7 +595,7 @@ void Preset::save(const DynamicPrintConfig* parent_config) else { ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src); ConfigOptionVectorBase* opt_vec_dst = static_cast(opt_dst); - ConfigOptionVectorBase* opt_vec_inherit = static_cast(parent_config->option(option)); + const ConfigOptionVectorBase* opt_vec_inherit = static_cast(parent_config->option(option)); if (opt_vec_src->size() == 1) opt_dst->set(opt_src); else if (key_set1->find(option) != key_set1->end()) { diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index b5e5373bdc..ddad79da9c 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -2017,7 +2017,7 @@ void NotificationManager::set_all_slicing_errors_gray(bool g, int plate_id) { for (std::unique_ptr ¬ification : m_pop_notifications) { if (notification->get_type() == NotificationType::SlicingError) { - if (auto obj_notif = dynamic_cast(notification.get()); obj_notif->plate_id == plate_id) { + if (auto obj_notif = dynamic_cast(notification.get()); plate_id == -1 || obj_notif->plate_id == plate_id) { notification->set_gray(g); } } @@ -2027,7 +2027,7 @@ void NotificationManager::set_all_slicing_warnings_gray(bool g, int plate_id) { for (std::unique_ptr ¬ification : m_pop_notifications) { if (notification->get_type() == NotificationType::SlicingWarning) { - if (auto obj_notif = dynamic_cast(notification.get()); obj_notif->plate_id == plate_id) { + if (auto obj_notif = dynamic_cast(notification.get()); plate_id == -1 || obj_notif->plate_id == plate_id) { notification->set_gray(g); } } diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 2f8dc3e010..143245c588 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -225,9 +225,9 @@ public: // Creates Slicing Warning notification with a custom text and no fade out. void push_slicing_warning_notification(const std::string &text, bool gray, ModelObject const *obj, ObjectID oid, int warning_step, int warning_msg_id, NotificationLevel level = NotificationLevel::WarningNotificationLevel); // marks slicing errors as gray for the specified plate - void set_all_slicing_errors_gray(bool g, int plate_id); + void set_all_slicing_errors_gray(bool g, int plate_id = -1); // marks slicing warings as gray for the specified plate - void set_all_slicing_warnings_gray(bool g, int plate_id); + void set_all_slicing_warnings_gray(bool g, int plate_id = -1); // void set_slicing_warning_gray(const std::string& text, bool g); // immediately stops showing slicing errors void close_slicing_errors_and_warnings(); From fe543c302602226976c6863ce43e565767ae21b5 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Mon, 10 Nov 2025 15:35:19 -0500 Subject: [PATCH 068/100] Add Spoolman info/settings dialog --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/BitmapCache.cpp | 22 ++- src/slic3r/GUI/BitmapCache.hpp | 1 + src/slic3r/GUI/OptionsGroup.cpp | 92 +++++----- src/slic3r/GUI/OptionsGroup.hpp | 3 +- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 34 +--- src/slic3r/GUI/Plater.cpp | 10 ++ src/slic3r/GUI/SpoolmanDialog.cpp | 218 +++++++++++++++++++++++ src/slic3r/GUI/SpoolmanDialog.hpp | 45 +++++ src/slic3r/Utils/Spoolman.cpp | 1 + src/slic3r/Utils/Spoolman.hpp | 1 + 11 files changed, 345 insertions(+), 84 deletions(-) create mode 100644 src/slic3r/GUI/SpoolmanDialog.cpp create mode 100644 src/slic3r/GUI/SpoolmanDialog.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index e26fa0ac4f..157ad49877 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -619,6 +619,8 @@ set(SLIC3R_GUI_SOURCES Utils/Spoolman.hpp GUI/SpoolmanImportDialog.cpp GUI/SpoolmanImportDialog.hpp + GUI/SpoolmanDialog.cpp + GUI/SpoolmanDialog.hpp Utils/WxFontUtils.cpp Utils/WxFontUtils.hpp ) diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index 10a67f784d..eded7827db 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -307,16 +307,30 @@ error: return NULL; } -wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_width, unsigned target_height, +wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_width, unsigned target_height, const bool grayscale/* = false*/, const bool dark_mode/* = false*/, const std::string& new_color /*= ""*/, const float scale_in_center/* = 0*/) { + std::map replaces; + if (!new_color.empty()) + replaces["\"#009688\""] = "\"" + new_color + "\""; + return load_svg(bitmap_name, target_width, target_height, grayscale, dark_mode, replaces, scale_in_center); +} + +wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_width, unsigned target_height, + const bool grayscale, const bool dark_mode, const std::map& replacement_colors, const float scale_in_center/* = 0*/) +{ + std::string color_key; + if (!replacement_colors.empty()) + for (const auto& [key, val] : replacement_colors) + color_key.append(key).append(",").append(val); + std::string bitmap_key = bitmap_name + ( target_height !=0 ? "-h" + std::to_string(target_height) : "-w" + std::to_string(target_width)) + (m_scale != 1.0f ? "-s" + float_to_string_decimal_point(m_scale) : "") + (dark_mode ? "-dm" : "") + (grayscale ? "-gs" : "") - + new_color; + + color_key; auto it = m_map.find(bitmap_key); if (it != m_map.end()) @@ -347,8 +361,8 @@ wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_ if (strstr(bitmap_name.c_str(), "toggle_on") != NULL && dark_mode) // ORCA only replace color of toggle button replaces["#009688"] = "#00675b"; - if (!new_color.empty()) - replaces["\"#009688\""] = "\"" + new_color + "\""; + if (!replacement_colors.empty()) + replaces.insert(replacement_colors.begin(), replacement_colors.end()); NSVGimage *image = nullptr; if (strstr(bitmap_name.c_str(), "printer_thumbnail") == NULL) { diff --git a/src/slic3r/GUI/BitmapCache.hpp b/src/slic3r/GUI/BitmapCache.hpp index e2ebe75314..120a274240 100644 --- a/src/slic3r/GUI/BitmapCache.hpp +++ b/src/slic3r/GUI/BitmapCache.hpp @@ -44,6 +44,7 @@ public: static NSVGimage* nsvgParseFromFileWithReplace(const char* filename, const char* units, float dpi, const std::map& replaces); // Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height/width. wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false, const bool dark_mode = false, const std::string& new_color = "", const float scale_in_center = 0.f); + wxBitmap* load_svg(const std::string &bitmap_key, unsigned width, unsigned height, const bool grayscale, const bool dark_mode, const std::map& replacement_colors, const float scale_in_center = 0.f); //Load background image of semi transparent material with color, wxBitmap* load_svg2(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false, const bool dark_mode = false, const std::vector& array_new_color = std::vector(), const float scale_in_center = 0.0f); diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index a319cafab5..0c145c684d 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -170,6 +170,52 @@ void OptionsGroup::remove_option_if(std::function con // TODO: remove items from g->m_options; } +void OptionsGroup::msw_rescale() +{ + // update bitmaps for extra column items (like "mode markers" or buttons on settings panel) + if (rescale_extra_column_item) + for (auto extra_col : m_extra_column_item_ptrs) + rescale_extra_column_item(extra_col); + + // update undo buttons : rescale bitmaps + for (const auto& field : m_fields) + field.second->msw_rescale(); + + auto rescale = [](wxSizer* sizer) { + for (wxSizerItem* item : sizer->GetChildren()) + if (item->IsWindow()) { + wxWindow* win = item->GetWindow(); + // check if window is ScalableButton + ScalableButton* sc_btn = dynamic_cast(win); + if (sc_btn) { + sc_btn->msw_rescale(); + sc_btn->SetSize(sc_btn->GetBestSize()); + return; + } + // check if window is wxButton + wxButton* btn = dynamic_cast(win); + if (btn) { + btn->SetSize(btn->GetBestSize()); + return; + } + } + }; + + // scale widgets and extra widgets if any exists + for (const Line& line : m_lines) { + if (line.widget_sizer) + rescale(line.widget_sizer); + if (line.extra_widget_sizer) + rescale(line.extra_widget_sizer); + } + + if (custom_ctrl) + custom_ctrl->msw_rescale(); + + if (auto line = dynamic_cast<::StaticLine*>(stb)) + line->Rescale(); +} + void OptionsGroup::show_field(const t_config_option_key& opt_key, bool show/* = true*/) { Field* field = get_field(opt_key); @@ -825,52 +871,6 @@ bool ConfigOptionsGroup::update_visibility(ConfigOptionMode mode) return true; } -void ConfigOptionsGroup::msw_rescale() -{ - // update bitmaps for extra column items (like "mode markers" or buttons on settings panel) - if (rescale_extra_column_item) - for (auto extra_col : m_extra_column_item_ptrs) - rescale_extra_column_item(extra_col); - - // update undo buttons : rescale bitmaps - for (const auto& field : m_fields) - field.second->msw_rescale(); - - auto rescale = [](wxSizer* sizer) { - for (wxSizerItem* item : sizer->GetChildren()) - if (item->IsWindow()) { - wxWindow* win = item->GetWindow(); - // check if window is ScalableButton - ScalableButton* sc_btn = dynamic_cast(win); - if (sc_btn) { - sc_btn->msw_rescale(); - sc_btn->SetSize(sc_btn->GetBestSize()); - return; - } - // check if window is wxButton - wxButton* btn = dynamic_cast(win); - if (btn) { - btn->SetSize(btn->GetBestSize()); - return; - } - } - }; - - // scale widgets and extra widgets if any exists - for (const Line& line : m_lines) { - if (line.widget_sizer) - rescale(line.widget_sizer); - if (line.extra_widget_sizer) - rescale(line.extra_widget_sizer); - } - - if (custom_ctrl) - custom_ctrl->msw_rescale(); - - if (auto line = dynamic_cast<::StaticLine*>(stb)) - line->Rescale(); -} - void ConfigOptionsGroup::sys_color_changed() { #ifdef _WIN32 diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index ce523cbedc..6062138e12 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -200,6 +200,7 @@ public: bool is_activated() { return sizer != nullptr; } void remove_option_if(std::function const & comp); + void msw_rescale(); protected: std::map m_options; wxWindow* m_parent {nullptr}; @@ -293,7 +294,7 @@ public: void Show(const bool show); bool is_visible(ConfigOptionMode mode); bool update_visibility(ConfigOptionMode mode); - void msw_rescale(); + // void msw_rescale(); void sys_color_changed(); void refresh(); boost::any config_value(const std::string& opt_key, int opt_index, bool deserialize); diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 141d0bb2ea..1ec105e47b 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -125,7 +125,7 @@ PhysicalPrinterDialog::~PhysicalPrinterDialog() void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup) { m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (opt_key == "host_type" || opt_key == "printhost_authorization_type" || opt_key == "spoolman_enabled") + if (opt_key == "host_type" || opt_key == "printhost_authorization_type") this->update(); if (opt_key == "print_host") this->update_printhost_buttons(); @@ -137,22 +137,6 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr m_optgroup->append_single_option_line("host_type"); - ConfigOptionDef def; - def.type = coBool; - def.label = _u8L("Spoolman Enabled"); - def.tooltip = _u8L("Enables spool management features powered by a Spoolman server instance"); - def.set_default_value(new ConfigOptionBool()); - m_optgroup->append_single_option_line((Option(def, "spoolman_enabled"))); - - def = ConfigOptionDef(); - def.type = coString; - def.label = _u8L("Spoolman Host"); - def.tooltip = _u8L("Points to where you Spoolman instance is hosted. Use the format of :. You may also just specify the " - "host and it will use the default Spoolman port of ") + Spoolman::DEFAULT_PORT; - def.set_default_value(new ConfigOptionString()); - m_optgroup->append_single_option_line(Option(def, "spoolman_host")); - - auto create_sizer_with_btn = [](wxWindow* parent, Button** btn, const std::string& icon_name, const wxString& label) { *btn = new Button(parent, label); (*btn)->SetStyle(ButtonStyle::Regular, ButtonType::Parameter); @@ -365,9 +349,6 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr m_optgroup->activate(); - m_optgroup->get_field("spoolman_enabled")->set_value(wxGetApp().app_config->get_bool("spoolman", "enabled"), false); - m_optgroup->get_field("spoolman_host")->set_value(wxString::FromUTF8(wxGetApp().app_config->get("spoolman", "host")), false); - Field* printhost_field = m_optgroup->get_field("print_host"); if (printhost_field) { @@ -571,7 +552,6 @@ void PhysicalPrinterDialog::update(bool printer_change) if (m_printhost_cafile_browse_btn) m_printhost_cafile_browse_btn->Enable(); - m_optgroup->show_field("spoolman_host", any_cast(m_optgroup->get_field("spoolman_enabled")->get_value())); // hide pre-configured address, in case user switched to a different host type if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) { @@ -761,18 +741,6 @@ void PhysicalPrinterDialog::check_host_key_valid() void PhysicalPrinterDialog::OnOK(wxEvent& event) { - const auto host = any_cast(m_optgroup->get_field("spoolman_host")->get_value()); - const auto enabled = any_cast(m_optgroup->get_field("spoolman_enabled")->get_value()); - const auto& appconfig = wxGetApp().app_config; - - // clear the Spoolman cache and reload if either of the Spoolman settings change - // clear the Spoolman cache and reload if either of the Spoolman settings change - if (enabled != appconfig->get_bool("spoolman", "enabled") || host != appconfig->get("spoolman", "host")) { - appconfig->set("spoolman", "enabled", enabled); - appconfig->set("spoolman", "host", host); - Spoolman::update_visible_spool_statistics(true); - } - wxGetApp().get_tab(Preset::TYPE_PRINTER)->save_preset("", false, false, true, m_preset_name ); event.Skip(); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 758d41a241..7583de3676 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -37,6 +37,7 @@ #include #include #include +#include "SpoolmanDialog.hpp" #ifdef _WIN32 #include #include @@ -471,6 +472,7 @@ struct Sidebar::priv ScalableButton * m_bpButton_del_filament; ScalableButton * m_bpButton_ams_filament; ScalableButton * m_bpButton_set_filament; + ScalableButton * m_bpButton_spoolman; int m_menu_filament_id = -1; wxScrolledWindow* m_panel_filament_content; wxScrolledWindow* m_scrolledWindow_filament_content; @@ -1943,6 +1945,14 @@ Sidebar::Sidebar(Plater *parent) bSizer39->Add(ams_btn, 0, wxALIGN_CENTER | wxLEFT, FromDIP(SidebarProps::IconSpacing())); //bSizer39->Add(FromDIP(10), 0, 0, 0, 0 ); + ScalableButton* spoolman_btn = new ScalableButton(p->m_panel_filament_title, wxID_ANY, "spool"); + spoolman_btn->SetToolTip(_L("View Spoolman info")); + spoolman_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { + SpoolmanDialog dialog(wxGetApp().mainframe); + }); + p->m_bpButton_spoolman = spoolman_btn; + bSizer39->Add(spoolman_btn, 0, wxALIGN_CENTER | wxLEFT, FromDIP(SidebarProps::IconSpacing())); + ScalableButton* set_btn = new ScalableButton(p->m_panel_filament_title, wxID_ANY, "settings"); set_btn->SetToolTip(_L("Set filaments to use")); set_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { diff --git a/src/slic3r/GUI/SpoolmanDialog.cpp b/src/slic3r/GUI/SpoolmanDialog.cpp new file mode 100644 index 0000000000..49e9ad08c9 --- /dev/null +++ b/src/slic3r/GUI/SpoolmanDialog.cpp @@ -0,0 +1,218 @@ +#include "SpoolmanDialog.hpp" + +#include "GUI_App.hpp" +#include "I18N.hpp" +#include "OptionsGroup.hpp" +#include "Plater.hpp" +#include "Spoolman.hpp" +#include "Widgets/DialogButtons.hpp" +#include "Widgets/LabeledStaticBox.hpp" +#include "Widgets/LoadingSpinner.hpp" +#include "wx/sizer.h" +#define EM wxGetApp().em_unit() + +namespace Slic3r::GUI { + +static BitmapCache cache; +const static std::regex spoolman_regex("^spoolman_"); + +SpoolInfoWidget::SpoolInfoWidget(wxWindow* parent, const Preset* preset) : wxPanel(parent, wxID_ANY), m_preset(preset) +{ + auto main_sizer = new wxStaticBoxSizer(new LabeledStaticBox(this), wxVERTICAL); + + auto bitmap = cache.load_svg("spool", EM * 10, EM * 10, false, false, + {{"#009688", m_preset->config.opt_string("default_filament_colour", 0)}}); + + m_spool_bitmap = new wxStaticBitmap(this, wxID_ANY, *bitmap); + m_spool_bitmap->SetMinSize({EM * 10, EM * 10}); + main_sizer->Add(m_spool_bitmap, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, EM); + + m_preset_name_label = new Label(this, wxString::FromUTF8(preset->name)); + m_preset_name_label->SetFont(Label::Body_12); + m_preset_name_label->SetBackgroundColour(*wxWHITE); + wxGetApp().UpdateDarkUI(m_preset_name_label); + main_sizer->Add(m_preset_name_label, 0, wxALIGN_CENTER_HORIZONTAL | wxDOWN | wxLEFT | wxRIGHT, EM); + + m_remaining_weight_label = new Label(this); + if (preset->spoolman_enabled()) { + auto spool = Spoolman::get_instance()->get_spoolman_spool_by_id(preset->config.opt_int("spoolman_spool_id", 0)); + m_remaining_weight_label->SetLabelText( + format("%1% g / %2% g", double_to_string(spool->remaining_weight, 2), double_to_string(spool->m_filament_ptr->weight, 2))); + } else { + m_remaining_weight_label->SetLabelText(_L("Not Spoolman enabled")); + m_remaining_weight_label->SetForegroundColour(*wxRED); + } + m_remaining_weight_label->SetFont(Label::Body_12); + m_remaining_weight_label->SetBackgroundColour(*wxWHITE); + wxGetApp().UpdateDarkUI(m_remaining_weight_label); + main_sizer->Add(m_remaining_weight_label, 0, wxALIGN_CENTER_HORIZONTAL | wxDOWN | wxLEFT | wxRIGHT, EM); + + this->SetSizer(main_sizer); +} + +void SpoolInfoWidget::rescale() +{ + auto bitmap = cache.load_svg("spool", EM * 10, EM * 10, false, false, + {{"#009688", m_preset->config.opt_string("default_filament_colour", 0)}}); + m_spool_bitmap->SetBitmap(*bitmap); + m_spool_bitmap->SetMinSize({EM * 10, EM * 10}); +} + +SpoolmanDialog::SpoolmanDialog(wxWindow* parent) + : DPIDialog(parent, wxID_ANY, _L("Spoolman"), wxDefaultPosition, {-1, 45 * EM}, wxDEFAULT_DIALOG_STYLE) +{ +#ifdef _WIN32 + this->SetBackgroundColour(*wxWHITE); + wxGetApp().UpdateDarkUI(this); + wxGetApp().UpdateDlgDarkUI(this); +#else + wxWindow::SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif + + auto main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->SetMinSize({-1, 45 * EM}); + + m_optgroup = new OptionsGroup(this, _L("Spoolman Options"), wxEmptyString); + build_options_group(); + m_optgroup->m_on_change = [&](const std::string& key, const boost::any& value) { m_dirty_settings = true; }; + main_sizer->Add(m_optgroup->sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, EM); + + m_spoolman_info_sizer = new wxBoxSizer(wxVERTICAL); + + m_spoolman_error_label = new Label(this); + m_spoolman_error_label->SetFont(Label::Body_16); + m_spoolman_error_label->SetForegroundColour(*wxRED); + + m_spoolman_info_sizer->AddStretchSpacer(1); + m_spoolman_info_sizer->Add(m_spoolman_error_label, 0, wxALIGN_CENTER); + m_spoolman_info_sizer->AddStretchSpacer(1); + main_sizer->Add(m_spoolman_info_sizer, 1, wxALL | wxALIGN_CENTER | wxEXPAND, EM); + + m_info_widgets_sizer = new wxGridSizer(2, EM, EM); + build_spool_info(); + main_sizer->Add(m_info_widgets_sizer, 1, wxALL | wxALIGN_CENTER, EM); + + m_buttons = new DialogButtons(this, {"Refresh", "OK"}); + m_buttons->UpdateButtons(); + m_buttons->GetButtonFromLabel(_L("Refresh"))->Bind(wxEVT_BUTTON, &SpoolmanDialog::OnRefresh, this); + m_buttons->GetOK()->Bind(wxEVT_BUTTON, &SpoolmanDialog::OnOK, this); + main_sizer->Add(m_buttons, 0, wxALIGN_BOTTOM | wxEXPAND | wxLEFT | wxRIGHT, EM); + + this->SetSizerAndFit(main_sizer); + this->SetMinSize(wxDefaultSize); + + this->ShowModal(); +} +void SpoolmanDialog::build_options_group() const +{ + ConfigOptionDef def; + def.type = coBool; + def.label = _u8L("Spoolman Enabled"); + def.tooltip = _u8L("Enables spool management features powered by a Spoolman server instance"); + def.set_default_value(new ConfigOptionBool()); + m_optgroup->append_single_option_line((Option(def, "spoolman_enabled"))); + + def = ConfigOptionDef(); + def.type = coString; + def.label = _u8L("Spoolman Host"); + def.tooltip = _u8L("Points to where you Spoolman instance is hosted. Use the format of :. You may also just specify the " + "host and it will use the default Spoolman port of ") + + Spoolman::DEFAULT_PORT; + def.set_default_value(new ConfigOptionString()); + m_optgroup->append_single_option_line(Option(def, "spoolman_host")); + + m_optgroup->activate(); + + // Load config values from the app config + const auto app_config = wxGetApp().app_config; + for (auto& line : m_optgroup->get_lines()) { + for (auto& option : line.get_options()) { + auto app_config_key = regex_replace(option.opt_id, spoolman_regex, ""); + if (option.opt.type == coBool) + m_optgroup->set_value(option.opt_id, app_config->get_bool("spoolman", app_config_key)); + else if (option.opt.type == coString) + m_optgroup->set_value(option.opt_id, wxString::FromUTF8(app_config->get("spoolman", app_config_key))); + else + BOOST_LOG_TRIVIAL(error) << "SpoolmanDialog load: Unknown option type " << option.opt.type; + } + } +} + +void SpoolmanDialog::build_spool_info() +{ + wxBusyCursor cursor; + m_info_widgets_sizer->Show(false); + m_spoolman_info_sizer->Show(false); + m_info_widgets_sizer->Clear(); + if (!Spoolman::is_enabled()) { + m_spoolman_error_label->SetLabelText(_L("Spoolman is not enabled")); + m_spoolman_info_sizer->Show(true); + return; + } + if (!Spoolman::is_server_valid()) { + m_spoolman_error_label->SetLabelText(_L("Spoolman server is invalid")); + m_spoolman_info_sizer->Show(true); + return; + } + m_info_widgets_sizer->Show(true); + auto preset_bundle = wxGetApp().preset_bundle; + for (auto& filament_preset_name : preset_bundle->filament_presets) { + auto spool_info_widget = new SpoolInfoWidget(this, preset_bundle->filaments.find_preset(filament_preset_name)); + m_info_widgets_sizer->Add(spool_info_widget, 0, wxEXPAND); + } +} + +void SpoolmanDialog::save_spoolman_settings() +{ + // clear the Spoolman cache and reload if any of the Spoolman settings change + if (!m_dirty_settings) + return; + + // Save config values to the app config + const auto app_config = wxGetApp().app_config; + for (auto& line : m_optgroup->get_lines()) { + for (auto& option : line.get_options()) { + auto app_config_key = regex_replace(option.opt_id, spoolman_regex, ""); + auto val = m_optgroup->get_value(option.opt_id); + if (option.opt.type == coBool) + app_config->set("spoolman", app_config_key, any_cast(val)); + else if (option.opt.type == coString) + app_config->set("spoolman", app_config_key, any_cast(val)); + else + BOOST_LOG_TRIVIAL(error) << "SpoolmanDialog save: Unknown option type " << option.opt.type; + } + } + + Spoolman::update_visible_spool_statistics(true); + m_dirty_settings = false; +} + +void SpoolmanDialog::OnRefresh(wxCommandEvent& e) +{ + Freeze(); + save_spoolman_settings(); + build_spool_info(); + Thaw(); + Fit(); + Layout(); + Refresh(); +} + +void SpoolmanDialog::OnOK(wxCommandEvent& e) +{ + save_spoolman_settings(); + this->EndModal(wxID_OK); +} + +void SpoolmanDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + GetSizer()->SetMinSize(wxDefaultCoord, 45 * EM); + m_optgroup->msw_rescale(); + for (auto item : m_info_widgets_sizer->GetChildren()) + if (auto info_widget = dynamic_cast(item)) + info_widget->rescale(); + Fit(); + Refresh(); +} + +} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/SpoolmanDialog.hpp b/src/slic3r/GUI/SpoolmanDialog.hpp new file mode 100644 index 0000000000..d021acdcdd --- /dev/null +++ b/src/slic3r/GUI/SpoolmanDialog.hpp @@ -0,0 +1,45 @@ +#ifndef ORCASLICER_SPOOLMANDIALOG_HPP +#define ORCASLICER_SPOOLMANDIALOG_HPP +#include "GUI_Utils.hpp" +#include "OptionsGroup.hpp" +#include "Widgets/DialogButtons.hpp" +#include "Widgets/Label.hpp" + +namespace Slic3r::GUI { +class SpoolInfoWidget : public wxPanel +{ +public: + SpoolInfoWidget(wxWindow* parent, const Preset* preset); + + void rescale(); + +private: + wxStaticBitmap* m_spool_bitmap; + Label* m_preset_name_label; + Label* m_remaining_weight_label; + const Preset* m_preset; +}; + +class SpoolmanDialog : DPIDialog +{ +public: + SpoolmanDialog(wxWindow* parent); + void build_options_group() const; + void build_spool_info(); + void save_spoolman_settings(); + void OnRefresh(wxCommandEvent& e); + void OnOK(wxCommandEvent& e); + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; + + bool m_dirty_settings{false}; + OptionsGroup* m_optgroup; + wxBoxSizer* m_spoolman_info_sizer; + Label* m_spoolman_error_label; + wxGridSizer* m_info_widgets_sizer; + DialogButtons* m_buttons; +}; +} // namespace Slic3r::GUI + +#endif // ORCASLICER_SPOOLMANDIALOG_HPP \ No newline at end of file diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 9f157e7df7..4e24b65945 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -417,6 +417,7 @@ void SpoolmanFilament::update_from_json(pt::ptree json_data) price = get_opt(json_data, "price"); density = get_opt(json_data, "density"); diameter = get_opt(json_data, "diameter"); + weight = get_opt(json_data, "weight"); article_number = get_opt(json_data, "article_number"); extruder_temp = get_opt(json_data, "settings_extruder_temp"); bed_temp = get_opt(json_data, "settings_bed_temp"); diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index 8693dca062..52158f55a4 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -184,6 +184,7 @@ public: float price; float density; float diameter; + float weight; std::string article_number; int extruder_temp; int bed_temp; From c1e414f9cdafba019b2c9f4345d617f3f9c3e26f Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Fri, 14 Nov 2025 17:14:50 -0500 Subject: [PATCH 069/100] Add loading screen to SpoolmanDialog --- .../loading_spinner.svg} | 0 resources/web/guide/0/index.html | 2 +- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/SpoolmanDialog.cpp | 129 ++++++++++++------ src/slic3r/GUI/SpoolmanDialog.hpp | 18 ++- src/slic3r/GUI/Widgets/LoadingSpinner.cpp | 87 ++++++++++++ src/slic3r/GUI/Widgets/LoadingSpinner.hpp | 36 +++++ 7 files changed, 227 insertions(+), 47 deletions(-) rename resources/{web/guide/0/loading.svg => images/loading_spinner.svg} (100%) create mode 100644 src/slic3r/GUI/Widgets/LoadingSpinner.cpp create mode 100644 src/slic3r/GUI/Widgets/LoadingSpinner.hpp diff --git a/resources/web/guide/0/loading.svg b/resources/images/loading_spinner.svg similarity index 100% rename from resources/web/guide/0/loading.svg rename to resources/images/loading_spinner.svg diff --git a/resources/web/guide/0/index.html b/resources/web/guide/0/index.html index efa703fd36..c34e2fc7d8 100644 --- a/resources/web/guide/0/index.html +++ b/resources/web/guide/0/index.html @@ -16,7 +16,7 @@
- +
Loading……
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 43691c59ea..05925c0570 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -493,6 +493,8 @@ set(SLIC3R_GUI_SOURCES GUI/Widgets/Label.cpp GUI/Widgets/LabeledStaticBox.cpp GUI/Widgets/LabeledStaticBox.hpp + GUI/Widgets/LoadingSpinner.cpp + GUI/Widgets/LoadingSpinner.hpp GUI/Widgets/Label.hpp GUI/Widgets/PopupWindow.cpp GUI/Widgets/PopupWindow.hpp diff --git a/src/slic3r/GUI/SpoolmanDialog.cpp b/src/slic3r/GUI/SpoolmanDialog.cpp index 49e9ad08c9..276f21bd69 100644 --- a/src/slic3r/GUI/SpoolmanDialog.cpp +++ b/src/slic3r/GUI/SpoolmanDialog.cpp @@ -7,12 +7,12 @@ #include "Spoolman.hpp" #include "Widgets/DialogButtons.hpp" #include "Widgets/LabeledStaticBox.hpp" -#include "Widgets/LoadingSpinner.hpp" #include "wx/sizer.h" #define EM wxGetApp().em_unit() namespace Slic3r::GUI { +wxDEFINE_EVENT(EVT_FINISH_LOADING, wxCommandEvent); static BitmapCache cache; const static std::regex spoolman_regex("^spoolman_"); @@ -69,40 +69,71 @@ SpoolmanDialog::SpoolmanDialog(wxWindow* parent) wxWindow::SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); #endif - auto main_sizer = new wxBoxSizer(wxVERTICAL); - main_sizer->SetMinSize({-1, 45 * EM}); + auto window_sizer = new wxBoxSizer(wxVERTICAL); + window_sizer->SetMinSize({wxDefaultCoord, 45 * EM}); - m_optgroup = new OptionsGroup(this, _L("Spoolman Options"), wxEmptyString); + // Main panel + m_main_panel = new wxPanel(this); + auto main_panel_sizer = new wxBoxSizer(wxVERTICAL); + m_main_panel->SetBackgroundColour(*wxWHITE); + wxGetApp().UpdateDarkUI(m_main_panel); + m_main_panel->SetSizer(main_panel_sizer); + + m_optgroup = new OptionsGroup(m_main_panel, _L("Spoolman Options"), wxEmptyString); build_options_group(); m_optgroup->m_on_change = [&](const std::string& key, const boost::any& value) { m_dirty_settings = true; }; - main_sizer->Add(m_optgroup->sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, EM); + main_panel_sizer->Add(m_optgroup->sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, EM); - m_spoolman_info_sizer = new wxBoxSizer(wxVERTICAL); + m_spoolman_error_label_sizer = new wxBoxSizer(wxVERTICAL); - m_spoolman_error_label = new Label(this); + m_spoolman_error_label = new Label(m_main_panel); m_spoolman_error_label->SetFont(Label::Body_16); m_spoolman_error_label->SetForegroundColour(*wxRED); - m_spoolman_info_sizer->AddStretchSpacer(1); - m_spoolman_info_sizer->Add(m_spoolman_error_label, 0, wxALIGN_CENTER); - m_spoolman_info_sizer->AddStretchSpacer(1); - main_sizer->Add(m_spoolman_info_sizer, 1, wxALL | wxALIGN_CENTER | wxEXPAND, EM); + m_spoolman_error_label_sizer->AddStretchSpacer(1); + m_spoolman_error_label_sizer->Add(m_spoolman_error_label, 0, wxALIGN_CENTER); + m_spoolman_error_label_sizer->AddStretchSpacer(1); + main_panel_sizer->Add(m_spoolman_error_label_sizer, 1, wxALL | wxALIGN_CENTER | wxEXPAND, EM); m_info_widgets_sizer = new wxGridSizer(2, EM, EM); - build_spool_info(); - main_sizer->Add(m_info_widgets_sizer, 1, wxALL | wxALIGN_CENTER, EM); + main_panel_sizer->Add(m_info_widgets_sizer, 1, wxALL | wxALIGN_CENTER, EM); - m_buttons = new DialogButtons(this, {"Refresh", "OK"}); + m_buttons = new DialogButtons(m_main_panel, {"Refresh", "OK"}); m_buttons->UpdateButtons(); m_buttons->GetButtonFromLabel(_L("Refresh"))->Bind(wxEVT_BUTTON, &SpoolmanDialog::OnRefresh, this); m_buttons->GetOK()->Bind(wxEVT_BUTTON, &SpoolmanDialog::OnOK, this); - main_sizer->Add(m_buttons, 0, wxALIGN_BOTTOM | wxEXPAND | wxLEFT | wxRIGHT, EM); + main_panel_sizer->Add(m_buttons, 0, wxALIGN_BOTTOM | wxEXPAND | wxLEFT | wxRIGHT, EM); + window_sizer->Add(m_main_panel, 1, wxEXPAND); - this->SetSizerAndFit(main_sizer); + // Loading Panel + m_loading_panel = new wxPanel(this); + auto loading_panel_sizer = new wxBoxSizer(wxHORIZONTAL); + m_loading_panel->SetBackgroundColour(*wxWHITE); + wxGetApp().UpdateDarkUI(m_loading_panel); + m_loading_panel->SetSizer(loading_panel_sizer); + m_loading_panel->SetMinSize({main_panel_sizer->CalcMin().GetWidth(), wxDefaultCoord}); + loading_panel_sizer->AddStretchSpacer(1); + + m_loading_spinner = new LoadingSpinner(m_loading_panel, {5 * EM, 5 * EM}); + loading_panel_sizer->Add(m_loading_spinner, 0, wxALIGN_CENTER | wxRIGHT, EM); + + auto loading_label = new Label(m_loading_panel, Label::Body_16, _L("Loading...")); + loading_label->SetForegroundColour(*wxWHITE); + loading_panel_sizer->Add(loading_label, 0, wxALIGN_CENTER); + + loading_panel_sizer->AddStretchSpacer(1); + window_sizer->Add(m_loading_panel, 1, wxEXPAND); + + build_spool_info(); + + this->SetSizerAndFit(window_sizer); this->SetMinSize(wxDefaultSize); + this->Bind(EVT_FINISH_LOADING, &SpoolmanDialog::OnFinishLoading, this); + this->ShowModal(); } + void SpoolmanDialog::build_options_group() const { ConfigOptionDef def; @@ -140,26 +171,37 @@ void SpoolmanDialog::build_options_group() const void SpoolmanDialog::build_spool_info() { - wxBusyCursor cursor; + show_loading(); m_info_widgets_sizer->Show(false); - m_spoolman_info_sizer->Show(false); - m_info_widgets_sizer->Clear(); - if (!Spoolman::is_enabled()) { - m_spoolman_error_label->SetLabelText(_L("Spoolman is not enabled")); - m_spoolman_info_sizer->Show(true); - return; - } - if (!Spoolman::is_server_valid()) { - m_spoolman_error_label->SetLabelText(_L("Spoolman server is invalid")); - m_spoolman_info_sizer->Show(true); - return; - } - m_info_widgets_sizer->Show(true); - auto preset_bundle = wxGetApp().preset_bundle; - for (auto& filament_preset_name : preset_bundle->filament_presets) { - auto spool_info_widget = new SpoolInfoWidget(this, preset_bundle->filaments.find_preset(filament_preset_name)); - m_info_widgets_sizer->Add(spool_info_widget, 0, wxEXPAND); - } + m_spoolman_error_label_sizer->Show(false); + create_thread([&] { + m_info_widgets_sizer->Clear(); + bool show_widgets = false; + if (!Spoolman::is_enabled()) { + m_spoolman_error_label->SetLabelText(_L("Spoolman is not enabled")); + m_spoolman_error_label_sizer->Show(true); + } else if (!Spoolman::is_server_valid()) { + m_spoolman_error_label->SetLabelText(_L("Spoolman server is invalid")); + m_spoolman_error_label_sizer->Show(true); + } else { + show_widgets = true; + } + + // Finish loading on the main thread + auto evt = new wxCommandEvent(EVT_FINISH_LOADING); + evt->SetInt(show_widgets); + wxQueueEvent(this, evt); + }); +} + +void SpoolmanDialog::show_loading(bool show) +{ + m_main_panel->Show(!show); + m_loading_panel->Show(show); + Layout(); + if (!show) + Fit(); + Refresh(); } void SpoolmanDialog::save_spoolman_settings() @@ -187,15 +229,22 @@ void SpoolmanDialog::save_spoolman_settings() m_dirty_settings = false; } +void SpoolmanDialog::OnFinishLoading(wxCommandEvent& event) +{ + if (event.GetInt()) { + m_info_widgets_sizer->Show(true); + auto preset_bundle = wxGetApp().preset_bundle; + for (auto& filament_preset_name : preset_bundle->filament_presets) { + m_info_widgets_sizer->Add(new SpoolInfoWidget(m_main_panel, preset_bundle->filaments.find_preset(filament_preset_name)), 0, wxEXPAND); + } + } + show_loading(false); +} + void SpoolmanDialog::OnRefresh(wxCommandEvent& e) { - Freeze(); save_spoolman_settings(); build_spool_info(); - Thaw(); - Fit(); - Layout(); - Refresh(); } void SpoolmanDialog::OnOK(wxCommandEvent& e) diff --git a/src/slic3r/GUI/SpoolmanDialog.hpp b/src/slic3r/GUI/SpoolmanDialog.hpp index d021acdcdd..310bcc9602 100644 --- a/src/slic3r/GUI/SpoolmanDialog.hpp +++ b/src/slic3r/GUI/SpoolmanDialog.hpp @@ -4,6 +4,7 @@ #include "OptionsGroup.hpp" #include "Widgets/DialogButtons.hpp" #include "Widgets/Label.hpp" +#include "Widgets/LoadingSpinner.hpp" namespace Slic3r::GUI { class SpoolInfoWidget : public wxPanel @@ -26,19 +27,24 @@ public: SpoolmanDialog(wxWindow* parent); void build_options_group() const; void build_spool_info(); + void show_loading(bool show = true); void save_spoolman_settings(); + void OnFinishLoading(wxCommandEvent& event); void OnRefresh(wxCommandEvent& e); void OnOK(wxCommandEvent& e); protected: void on_dpi_changed(const wxRect& suggested_rect) override; - bool m_dirty_settings{false}; - OptionsGroup* m_optgroup; - wxBoxSizer* m_spoolman_info_sizer; - Label* m_spoolman_error_label; - wxGridSizer* m_info_widgets_sizer; - DialogButtons* m_buttons; + bool m_dirty_settings{false}; + OptionsGroup* m_optgroup; + wxPanel* m_main_panel; + wxPanel* m_loading_panel; + wxGridSizer* m_info_widgets_sizer; + wxBoxSizer* m_spoolman_error_label_sizer; + Label* m_spoolman_error_label; + LoadingSpinner* m_loading_spinner; + DialogButtons* m_buttons; }; } // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Widgets/LoadingSpinner.cpp b/src/slic3r/GUI/Widgets/LoadingSpinner.cpp new file mode 100644 index 0000000000..3aab31258c --- /dev/null +++ b/src/slic3r/GUI/Widgets/LoadingSpinner.cpp @@ -0,0 +1,87 @@ +#include "LoadingSpinner.hpp" + +#include "slic3r/GUI/GUI_App.hpp" + +namespace Slic3r::GUI { +LoadingSpinner::LoadingSpinner( + wxWindow* parent, const wxSize& size) + : m_timer_interval(20), m_animation_duration(1200) +{ + Create(parent, wxID_ANY, wxDefaultPosition, size, wxNO_BORDER); + + wxWindowBase::SetBackgroundColour(*wxWHITE); + wxGetApp().UpdateDarkUI(this); + + // An odd size causes jittering + const auto bmp_size = size / 2 * 2; + m_base_image = m_bitmap_cache.load_svg("loading_spinner", bmp_size.GetWidth(), bmp_size.GetHeight())->ConvertToImage(); + m_rendered_image = m_base_image; + m_timer.SetOwner(this); + + this->Bind(wxEVT_PAINT, &LoadingSpinner::OnPaint, this); + this->Bind(wxEVT_TIMER, &LoadingSpinner::OnTimer, this); + this->Bind(wxEVT_SIZE, &LoadingSpinner::OnSizeChanged, this); + + this->Start(); +} + +void LoadingSpinner::Start() +{ + m_timer_start = std::chrono::steady_clock::now(); + m_timer.Start(m_timer_interval); +} + +void LoadingSpinner::Stop() +{ + m_timer.Stop(); +} + +void LoadingSpinner::SetUpdateInterval(const int update_interval_ms) +{ + m_timer_interval = update_interval_ms; +} + +void LoadingSpinner::SetAnimationDuration(const int duration_ms) +{ + m_animation_duration = duration_ms; +} + +void LoadingSpinner::OnPaint(wxPaintEvent& event) +{ + wxPaintDC dc(this); + dc.Clear(); + + wxRealPoint point(m_rendered_image.GetWidth() / 2., m_rendered_image.GetHeight() / 2.); + point -= wxRealPoint(this->GetSize().GetWidth() / 2., this->GetSize().GetHeight() / 2.); + point = wxRealPoint(point.x * -1, point.y * -1); + const wxBitmap bitmap(m_rendered_image); + dc.DrawBitmap(bitmap, point); +} + +void LoadingSpinner::OnTimer(wxTimerEvent& event) +{ + const auto start_time_diff = std::chrono::duration_cast(std::chrono::steady_clock::now() - m_timer_start).count(); + const auto current_animation_progress = static_cast(start_time_diff % m_animation_duration) / m_animation_duration; + + static constexpr double RAD = 2 * M_PI; + m_rendered_image = m_base_image.Rotate(RAD * (1 - current_animation_progress), m_center_of_rotation); + Refresh(); +} + +void LoadingSpinner::OnSizeChanged(wxSizeEvent& event) +{ + const auto size = event.GetSize() / 2 * 2; + m_base_image = m_bitmap_cache.load_svg("loading_spinner", size.GetWidth(), size.GetHeight())->ConvertToImage(); + m_center_of_rotation = wxPoint(size.GetWidth() / 2, size.GetHeight() / 2); +} + +bool LoadingSpinner::Show(bool show) +{ + if (show) + this->Start(); + else + this->Stop(); + + return wxWindow::Show(show); +} +} // namespace Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/Widgets/LoadingSpinner.hpp b/src/slic3r/GUI/Widgets/LoadingSpinner.hpp new file mode 100644 index 0000000000..2580c7a469 --- /dev/null +++ b/src/slic3r/GUI/Widgets/LoadingSpinner.hpp @@ -0,0 +1,36 @@ +#ifndef ORCASLICER_LOADINGSPINNER_HPP +#define ORCASLICER_LOADINGSPINNER_HPP +#include "slic3r/GUI/BitmapCache.hpp" +#include "wx/control.h" + +namespace Slic3r::GUI { +class LoadingSpinner : public wxControl +{ +public: + LoadingSpinner(wxWindow* parent, const wxSize& size); + + void Start(); + void Stop(); + void SetUpdateInterval(int update_interval_ms); + void SetAnimationDuration(int duration_ms); + + bool Show(bool show) override; + +protected: + void OnPaint(wxPaintEvent& event); + void OnTimer(wxTimerEvent& event); + void OnSizeChanged(wxSizeEvent& event); + + wxTimer m_timer; + int m_timer_interval; + int m_animation_duration; + std::chrono::steady_clock::time_point m_timer_start; + BitmapCache m_bitmap_cache; + wxImage m_base_image; + wxImage m_rendered_image; + wxPoint m_center_of_rotation; +}; + +} // namespace Slic3r::GUI + +#endif // ORCASLICER_LOADINGSPINNER_HPP From 60587062f65f3ef7a96e034f511b3f7e07971aea Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Fri, 14 Nov 2025 19:33:11 -0500 Subject: [PATCH 070/100] Save server valid response for 5 seconds --- src/slic3r/Utils/Spoolman.cpp | 15 ++++++++++++++- src/slic3r/Utils/Spoolman.hpp | 3 ++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 4e24b65945..c63f1c53b7 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -358,18 +358,31 @@ void Spoolman::update_specific_spool_statistics(const std::vector& } -bool Spoolman::is_server_valid() +bool Spoolman::is_server_valid(bool force_check) { + using namespace std::chrono; + static time_point last_validity_check; + static bool last_res; + bool res = false; if (!is_enabled()) return res; + if (!force_check) { + if (duration_cast(steady_clock::now() - last_validity_check).count() < 5) + return last_res; + } + Http::get(get_spoolman_api_url() + "info").on_complete([&res](std::string, unsigned http_status) { if (http_status == 200) res = true; }) .timeout_max(MAX_TIMEOUT) .perform_sync(); + + last_validity_check = steady_clock::now(); + last_res = res; + return res; } diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index 52158f55a4..36f3db36a6 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -52,6 +52,7 @@ class Spoolman { inline static Spoolman* m_instance{nullptr}; + bool m_initialized{false}; std::map m_use_undo_buffer{}; @@ -112,7 +113,7 @@ public: static void update_specific_spool_statistics(const std::vector& spool_ids); /// Check if Spoolman is enabled and the provided host is valid - static bool is_server_valid(); + static bool is_server_valid(bool force_check = false); /// Check if Spoolman is enabled static bool is_enabled(); From a28650449894e9076dc2fa90d7de1c8e524b1344 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Fri, 14 Nov 2025 22:23:03 -0500 Subject: [PATCH 071/100] Consolidate api call functions --- src/slic3r/Utils/Spoolman.cpp | 71 ++++++++++++----------------------- src/slic3r/Utils/Spoolman.hpp | 21 ++++++++++- 2 files changed, 43 insertions(+), 49 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index c63f1c53b7..2f56ab8bfd 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -5,7 +5,6 @@ #include #include #include "Spoolman.hpp" -#include "Http.hpp" namespace Slic3r { @@ -43,58 +42,28 @@ static std::string get_spoolman_api_url() return spoolman_host + ":" + spoolman_port + "/api/v1/"; } -pt::ptree Spoolman::get_spoolman_json(const string& api_endpoint) + +Http Spoolman::get_http_instance(const HTTPAction action, const std::string& url) { - auto url = get_spoolman_api_url() + api_endpoint; - auto http = Http::get(url); - - bool res; - std::string res_body; - - http.on_error([&](const std::string& body, std::string error, unsigned status) { - BOOST_LOG_TRIVIAL(error) << "Failed to get data from the Spoolman server. Make sure that the port is correct and the server is running." << boost::format(" HTTP Error: %1%, HTTP status code: %2%") % error % status; - res = false; - }) - .on_complete([&](std::string body, unsigned) { - res_body = std::move(body); - res = true; - }) - .timeout_max(MAX_TIMEOUT) - .perform_sync(); - - if (!res) - return {}; - - if (res_body.empty()) { - BOOST_LOG_TRIVIAL(info) << "Spoolman request returned an empty string"; - return {}; - } - - pt::ptree tree; - try { - stringstream ss(res_body); - pt::read_json(ss, tree); - } catch (std::exception& exception) { - BOOST_LOG_TRIVIAL(error) << "Failed to read json into property tree. Exception: " << exception.what(); - return {}; - } - - return tree; + if (action == GET) + return Http::get(url); + if (action == PUT) + return Http::put2(url); + if (action == POST) + return Http::post(url); + throw RuntimeError("Invalid HTTP action"); } -pt::ptree Spoolman::put_spoolman_json(const string& api_endpoint, const pt::ptree& data) + +pt::ptree Spoolman::spoolman_api_call(const HTTPAction http_action, const std::string& api_endpoint, const pt::ptree& data) { - auto url = get_spoolman_api_url() + api_endpoint; - auto http = Http::put2(url); + const auto url = get_spoolman_api_url() + api_endpoint; + auto http = get_http_instance(http_action, url); bool res; std::string res_body; - stringstream ss; - pt::write_json(ss, data); - http.header("Content-Type", "application/json") - .set_post_body(ss.str()) .on_error([&](const std::string& body, std::string error, unsigned status) { BOOST_LOG_TRIVIAL(error) << "Failed to put data to the Spoolman server. Make sure that the port is correct and the server is running." << boost::format(" HTTP Error: %1%, HTTP status code: %2%, Response body: %3%") % error % status % body; res = false; @@ -103,8 +72,15 @@ pt::ptree Spoolman::put_spoolman_json(const string& api_endpoint, const pt::ptre res_body = std::move(body); res = true; }) - .timeout_max(MAX_TIMEOUT) - .perform_sync(); + .timeout_max(MAX_TIMEOUT); + + if (!data.empty()) { + stringstream ss; + pt::write_json(ss, data); + http.set_post_body(ss.str()); + } + + http.perform_sync(); if (!res) return {}; @@ -116,7 +92,7 @@ pt::ptree Spoolman::put_spoolman_json(const string& api_endpoint, const pt::ptre pt::ptree tree; try { - ss = stringstream(res_body); + stringstream ss = stringstream(res_body); pt::read_json(ss, tree); } catch (std::exception& exception) { BOOST_LOG_TRIVIAL(error) << "Failed to read json into property tree. Exception: " << exception.what(); @@ -126,6 +102,7 @@ pt::ptree Spoolman::put_spoolman_json(const string& api_endpoint, const pt::ptre return tree; } + bool Spoolman::pull_spoolman_spools() { pt::ptree tree; diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index 36f3db36a6..c0921b375b 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -1,6 +1,7 @@ #ifndef SLIC3R_SPOOLMAN_HPP #define SLIC3R_SPOOLMAN_HPP +#include "Http.hpp" #include #include #include @@ -69,12 +70,28 @@ class Spoolman m_initialized = pull_spoolman_spools(); }; + enum HTTPAction + { + GET, PUT, POST + }; + + /// get an Http instance for the specified HTTPAction + static Http get_http_instance(HTTPAction action, const std::string& url); + + /// uses the specified HTTPAction to make an API call to the spoolman server + static pt::ptree spoolman_api_call(HTTPAction http_action, const std::string& api_endpoint, const pt::ptree& data = {}); + /// gets the json response from the specified API endpoint - static pt::ptree get_spoolman_json(const std::string& api_endpoint); + /// \returns the json response + static pt::ptree get_spoolman_json(const std::string& api_endpoint) { return spoolman_api_call(GET, api_endpoint); } /// puts the provided data to the specified API endpoint /// \returns the json response - static pt::ptree put_spoolman_json(const std::string& api_endpoint, const pt::ptree& data); + static pt::ptree put_spoolman_json(const std::string& api_endpoint, const pt::ptree& data) { return spoolman_api_call(PUT, api_endpoint, data); } + + /// posts the provided data to the specified API endpoint + /// \returns the json response + static pt::ptree post_spoolman_json(const std::string& api_endpoint, const pt::ptree& data) { return spoolman_api_call(POST, api_endpoint, data); } /// get all the spools from the api and store them /// \returns if succeeded From 463101612a4394c73592b04c2177641e37ea6167 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Sat, 15 Nov 2025 18:17:07 -0500 Subject: [PATCH 072/100] Skip applying invalid temperature values --- src/slic3r/Utils/Spoolman.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 2f56ab8bfd..271ad9dd91 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -420,10 +420,14 @@ void SpoolmanFilament::apply_to_config(Slic3r::DynamicConfig& config) const config.set_key_value("filament_cost", new ConfigOptionFloats({price})); config.set_key_value("filament_density", new ConfigOptionFloats({density})); config.set_key_value("filament_diameter", new ConfigOptionFloats({diameter})); - config.set_key_value("nozzle_temperature_initial_layer", new ConfigOptionInts({extruder_temp + 5})); - config.set_key_value("nozzle_temperature", new ConfigOptionInts({extruder_temp})); - config.set_key_value("hot_plate_temp_initial_layer", new ConfigOptionInts({bed_temp + 5})); - config.set_key_value("hot_plate_temp", new ConfigOptionInts({bed_temp})); + if (extruder_temp > 0) { + config.set_key_value("nozzle_temperature_initial_layer", new ConfigOptionInts({extruder_temp + 5})); + config.set_key_value("nozzle_temperature", new ConfigOptionInts({extruder_temp})); + } + if (bed_temp > 0) { + config.set_key_value("hot_plate_temp_initial_layer", new ConfigOptionInts({bed_temp + 5})); + config.set_key_value("hot_plate_temp", new ConfigOptionInts({bed_temp})); + } config.set_key_value("default_filament_colour", new ConfigOptionStrings{color}); m_vendor_ptr->apply_to_config(config); } From 900ad7daca15697a6056fb044949727e8ab25f8f Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Sat, 15 Nov 2025 18:25:20 -0500 Subject: [PATCH 073/100] Fix unix build --- src/slic3r/GUI/SpoolmanDialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/SpoolmanDialog.cpp b/src/slic3r/GUI/SpoolmanDialog.cpp index 276f21bd69..58ced5af86 100644 --- a/src/slic3r/GUI/SpoolmanDialog.cpp +++ b/src/slic3r/GUI/SpoolmanDialog.cpp @@ -8,6 +8,7 @@ #include "Widgets/DialogButtons.hpp" #include "Widgets/LabeledStaticBox.hpp" #include "wx/sizer.h" +#include "format.hpp" #define EM wxGetApp().em_unit() namespace Slic3r::GUI { From 258343d511af83f3452834cd7e65e14ae60798c6 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Sun, 16 Nov 2025 07:27:51 +0000 Subject: [PATCH 074/100] Fix flatpak build --- src/slic3r/GUI/Widgets/LoadingSpinner.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/Widgets/LoadingSpinner.hpp b/src/slic3r/GUI/Widgets/LoadingSpinner.hpp index 2580c7a469..9620c927bc 100644 --- a/src/slic3r/GUI/Widgets/LoadingSpinner.hpp +++ b/src/slic3r/GUI/Widgets/LoadingSpinner.hpp @@ -1,5 +1,6 @@ #ifndef ORCASLICER_LOADINGSPINNER_HPP #define ORCASLICER_LOADINGSPINNER_HPP +#include #include "slic3r/GUI/BitmapCache.hpp" #include "wx/control.h" From 421610435ffef13616725be312feda0a89285571 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Fri, 21 Nov 2025 06:15:32 -0500 Subject: [PATCH 075/100] Save preset data to extra field --- src/libslic3r/Config.cpp | 40 ++++++++------ src/libslic3r/Config.hpp | 2 + src/libslic3r/Preset.cpp | 18 +++++++ src/libslic3r/Preset.hpp | 3 ++ src/slic3r/GUI/SpoolmanDialog.cpp | 11 +++- src/slic3r/GUI/SpoolmanDialog.hpp | 1 + src/slic3r/GUI/Tab.cpp | 60 ++++++++++++++++++++- src/slic3r/Utils/Spoolman.cpp | 87 +++++++++++++++++++++++++++++++ src/slic3r/Utils/Spoolman.hpp | 16 +++++- 9 files changed, 217 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 75935b2177..f6dc07fa7d 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -801,7 +801,28 @@ ConfigSubstitutions ConfigBase::load_from_json(const std::string &file, ForwardC int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContext& substitution_context, bool load_inherits_to_config, std::map& key_values, std::string& reason) { - json j; + try { + json j; + boost::nowide::ifstream ifs(file); + ifs >> j; + ifs.close(); + return load_from_json(j, substitution_context, load_inherits_to_config, key_values, reason, file); + } + catch (const std::ifstream::failure &err) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": parse "<& key_values, std::string& reason, const std::string& file) +{ std::list different_settings_append; std::string new_support_style; std::string is_infill_first; @@ -855,17 +876,13 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex }; try { - boost::nowide::ifstream ifs(file); - ifs >> j; - ifs.close(); - const ConfigDef* config_def = this->def(); if (config_def == nullptr) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": no config defs!"; return -1; } //parse the json elements - for (auto it = j.begin(); it != j.end(); it++) { + for (auto it = data.begin(); it != data.end(); it++) { if (boost::iequals(it.key(),BBL_JSON_KEY_VERSION)) { key_values.emplace(BBL_JSON_KEY_VERSION, it.value()); } @@ -1076,16 +1093,7 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex this->handle_legacy_composite(); return 0; } - catch (const std::ifstream::failure &err) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": parse "< #include @@ -2592,6 +2593,7 @@ public: ConfigSubstitutions load_string_map(std::map &key_values, ForwardCompatibilitySubstitutionRule compatibility_rule); //BBS: add json support int load_from_json(const std::string &file, ConfigSubstitutionContext& substitutions, bool load_inherits_in_config, std::map& key_values, std::string& reason); + int load_from_json(const nlohmann::json &data, ConfigSubstitutionContext& substitutions, bool load_inherits_in_config, std::map& key_values, std::string& reason, const std::string& file = ""); ConfigSubstitutions load_from_json(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule, std::map& key_values, std::string& reason); ConfigSubstitutions load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 745faccc59..44b678521b 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -2290,6 +2290,24 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string return preset; } +bool PresetCollection::load_full_config(DynamicPrintConfig& config) +{ + const auto& inherits = Preset::inherits(config); + if (inherits.empty()) + return true; + + const auto inherits_preset = this->find_preset2(inherits); + if (!inherits_preset) + return false; + + const auto& inherits_config = inherits_preset->config; + const auto input_copy = config; + config.clear(); + config.apply(inherits_config); + config.apply(input_copy); + return true; +} + bool PresetCollection::clone_presets(std::vector const &presets, std::vector &failures, std::function modifier, bool force_rewritten) { std::vector new_presets; diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 9d2587a2e5..cc349c87ce 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -512,6 +512,9 @@ public: Preset& load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select = true, Semver file_version = Semver()); Preset& load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select = true, Semver file_version = Semver()); + // Loads the config options from the inherits preset into the provided partial config + bool load_full_config(DynamicPrintConfig& config); + bool clone_presets(std::vector const &presets, std::vector &failures, std::function modifier, bool force_rewritten = false); bool clone_presets_for_printer( std::vector const &templates, std::vector &failures, std::string const &printer, std::function create_filament_id, bool force_rewritten = false); diff --git a/src/slic3r/GUI/SpoolmanDialog.cpp b/src/slic3r/GUI/SpoolmanDialog.cpp index 58ced5af86..a15349ed49 100644 --- a/src/slic3r/GUI/SpoolmanDialog.cpp +++ b/src/slic3r/GUI/SpoolmanDialog.cpp @@ -82,7 +82,11 @@ SpoolmanDialog::SpoolmanDialog(wxWindow* parent) m_optgroup = new OptionsGroup(m_main_panel, _L("Spoolman Options"), wxEmptyString); build_options_group(); - m_optgroup->m_on_change = [&](const std::string& key, const boost::any& value) { m_dirty_settings = true; }; + m_optgroup->m_on_change = [&](const std::string& key, const boost::any& value) { + m_dirty_settings = true; + if (key == "spoolman_host") + m_dirty_host = true; + }; main_panel_sizer->Add(m_optgroup->sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, EM); m_spoolman_error_label_sizer = new wxBoxSizer(wxVERTICAL); @@ -226,8 +230,11 @@ void SpoolmanDialog::save_spoolman_settings() } } - Spoolman::update_visible_spool_statistics(true); + if (m_dirty_host) + Spoolman::on_server_changed(); + Spoolman::update_visible_spool_statistics(m_dirty_host); m_dirty_settings = false; + m_dirty_host = false; } void SpoolmanDialog::OnFinishLoading(wxCommandEvent& event) diff --git a/src/slic3r/GUI/SpoolmanDialog.hpp b/src/slic3r/GUI/SpoolmanDialog.hpp index 310bcc9602..f4ddc0ec4f 100644 --- a/src/slic3r/GUI/SpoolmanDialog.hpp +++ b/src/slic3r/GUI/SpoolmanDialog.hpp @@ -37,6 +37,7 @@ protected: void on_dpi_changed(const wxRect& suggested_rect) override; bool m_dirty_settings{false}; + bool m_dirty_host{false}; OptionsGroup* m_optgroup; wxPanel* m_main_panel; wxPanel* m_loading_panel; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 78ca708730..0d2e769dae 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3958,8 +3958,10 @@ void TabFilament::build() } auto res = Spoolman::update_filament_preset_from_spool(&m_presets->get_selected_preset(), true, stats_only); - if (res.has_failed()) + if (res.has_failed()) { + show_error(this, res.build_error_dialog_message()); return; + } update_spoolman_statistics(); }; @@ -3977,6 +3979,62 @@ void TabFilament::build() }; optgroup->append_line(line); + line = {"Preset Sync", ""}; + line.append_option(Option(ConfigOptionDef(), "spoolman_preset_sync")); + line.widget = [&](wxWindow* parent){ + auto sizer = new wxBoxSizer(wxHORIZONTAL); + + auto sync_to_spoolman_btn = new wxButton(parent, wxID_ANY, _L("Save Preset to Spoolman")); + sync_to_spoolman_btn->Bind(wxEVT_BUTTON, [&](wxCommandEvent& evt) { + auto res = Spoolman::save_preset_to_spoolman(&m_presets->get_selected_preset()); + if (res.has_failed()) + show_error(this, res.build_error_dialog_message()); + }); + wxGetApp().UpdateDarkUI(sync_to_spoolman_btn); + sizer->Add(sync_to_spoolman_btn); + + auto load_from_spoolman_btn = new wxButton(parent, wxID_ANY, _L("Load Preset from Spoolman")); + load_from_spoolman_btn->Bind(wxEVT_BUTTON, [&](wxCommandEvent& evt) { + if (m_presets->current_is_dirty() && m_active_page->get_field("spoolman_spool_id")->m_is_modified_value) { + show_error(this, "This profile cannot be updated with an unsaved Spool ID value. Please save the profile, then try updating again."); + return; + } + + if (!Spoolman::is_server_valid()) { + show_error(this, "Failed to get data from the Spoolman server. Make sure that the port is correct and the server is running."); + return; + } + + auto spool = Spoolman::get_instance()->get_spoolman_spool_by_id( + m_presets->get_selected_preset().config.opt_int("spoolman_spool_id", 0)); + if (spool->m_filament_ptr->preset_data.empty()) { + show_error(this, "The Spoolman filament does not contain any preset data."); + return; + } + + auto config = spool->m_filament_ptr->get_config_from_preset_data(); + if (config.empty()) { + show_error(this, "The stored preset data is invalid."); + return; + } + + // Do not change preset name in this operation + auto current_preset_name = m_presets->get_selected_preset().config.opt_string("filament_settings_id", 0u); + config.set_key_value("filament_settings_id", new ConfigOptionStrings({current_preset_name})); + + // Apply spool configuration changes + spool->apply_to_config(config); + + // Load config changes into the tab + this->load_config(config); + }); + wxGetApp().UpdateDarkUI(load_from_spoolman_btn); + sizer->Add(load_from_spoolman_btn); + + return sizer; + }; + optgroup->append_line(line); + optgroup = page->new_optgroup(_L("Spool Statistics")); auto build_statistics_line = [&](const std::string& key, const std::string& label, diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 271ad9dd91..f23aaec738 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -51,6 +51,8 @@ Http Spoolman::get_http_instance(const HTTPAction action, const std::string& url return Http::put2(url); if (action == POST) return Http::post(url); + if (action == PATCH) + return Http::patch(url); throw RuntimeError("Invalid HTTP action"); } @@ -296,6 +298,54 @@ SpoolmanResult Spoolman::update_filament_preset_from_spool(Preset* filament_pres return result; } +SpoolmanResult Spoolman::save_preset_to_spoolman(const Preset* filament_preset) +{ + SpoolmanResult result; + if (filament_preset->type != Preset::TYPE_FILAMENT) { + result.messages.emplace_back("Preset is not a filament preset"); + return result; + } + 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 + return result; + } + SpoolmanSpoolShrPtr spool = get_instance()->get_spoolman_spool_by_id(spool_id); + if (!spool) { + result.messages.emplace_back("The spool ID does not exist in the local spool cache"); + return result; + } + if (filament_preset->is_dirty) { + result.messages.emplace_back("Please save the current changes to the preset"); + return result; + } + + boost::nowide::ifstream fs(filament_preset->file); + + std::string preset_data; + std::string line; + while (std::getline(fs, line)) { + preset_data += line; + } + // Spoolman extra fields are a string read as json + // To save a string to an extra field, the data must be surrounded by double quotes + // and literal quotes must be escaped twice + std::string formated_preset_data = boost::replace_all_copy(preset_data, "\"", "\\\""); + formated_preset_data = "\"" + formated_preset_data + "\""; + + pt::ptree pt; + pt.add("extra.orcaslicer_preset_data", formated_preset_data); + auto res = patch_spoolman_json("filament/" + std::to_string(spool_id), pt); + + if (res.empty()) + result.messages.emplace_back("Failed to save the data"); + else + spool->m_filament_ptr->preset_data = std::move(preset_data); + return result; +} + + void Spoolman::update_visible_spool_statistics(bool clear_cache) { PresetBundle* preset_bundle = GUI::wxGetApp().preset_bundle; @@ -335,6 +385,17 @@ void Spoolman::update_specific_spool_statistics(const std::vector& } +void Spoolman::on_server_changed() +{ + if (!is_server_valid()) + return; + pt::ptree pt; + pt.add("name", "OrcaSlicer Preset Data"); + pt.add("field_type", "text"); + post_spoolman_json("field/filament/orcaslicer_preset_data", pt); +} + + bool Spoolman::is_server_valid(bool force_check) { using namespace std::chrono; @@ -412,6 +473,10 @@ void SpoolmanFilament::update_from_json(pt::ptree json_data) extruder_temp = get_opt(json_data, "settings_extruder_temp"); bed_temp = get_opt(json_data, "settings_bed_temp"); color = "#" + get_opt(json_data, "color_hex"); + preset_data = get_opt(json_data, "extra.orcaslicer_preset_data"); + if (preset_data.front() == '"' && preset_data.back() == '"') + preset_data = preset_data.substr(1, preset_data.length() - 2); + boost::replace_all(preset_data, "\\\"", "\""); } void SpoolmanFilament::apply_to_config(Slic3r::DynamicConfig& config) const @@ -432,6 +497,28 @@ void SpoolmanFilament::apply_to_config(Slic3r::DynamicConfig& config) const m_vendor_ptr->apply_to_config(config); } +DynamicPrintConfig SpoolmanFilament::get_config_from_preset_data() const +{ + if (preset_data.empty()) + return {}; + json j = json::parse(preset_data); + ConfigSubstitutionContext context(Enable); + std::map key_values; + std::string reason; + DynamicPrintConfig config; + config.load_from_json(j, context, true, key_values, reason); + if (!reason.empty()) + return {}; + auto& presets = wxGetApp().preset_bundle->filaments; + if (!presets.load_full_config(config)) + return {}; + const auto invalid_keys = Preset::remove_invalid_keys(config, presets.default_preset().config); + if (!invalid_keys.empty()) + return {}; + return config; +} + + //--------------------------------- // SpoolmanSpool //--------------------------------- diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index c0921b375b..47811adf77 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -66,13 +66,15 @@ class Spoolman Spoolman() { m_instance = this; - if (is_server_valid()) + if (is_server_valid()) { + on_server_changed(); m_initialized = pull_spoolman_spools(); + } }; enum HTTPAction { - GET, PUT, POST + GET, PUT, POST, PATCH }; /// get an Http instance for the specified HTTPAction @@ -93,6 +95,10 @@ class Spoolman /// \returns the json response static pt::ptree post_spoolman_json(const std::string& api_endpoint, const pt::ptree& data) { return spoolman_api_call(POST, api_endpoint, data); } + /// patches the provided data to the specified API endpoint + /// \returns the json response + static pt::ptree patch_spoolman_json(const std::string& api_endpoint, const pt::ptree& data) { return spoolman_api_call(PATCH, api_endpoint, data); } + /// get all the spools from the api and store them /// \returns if succeeded bool pull_spoolman_spools(); @@ -122,6 +128,8 @@ public: bool update_from_server = true, bool only_update_statistics = false); + static SpoolmanResult save_preset_to_spoolman(const Preset* filament_preset); + /// Update the statistics values for the visible filament profiles with spoolman enabled /// 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); @@ -129,6 +137,8 @@ public: /// 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 void on_server_changed(); + /// Check if Spoolman is enabled and the provided host is valid static bool is_server_valid(bool force_check = false); @@ -207,10 +217,12 @@ public: int extruder_temp; int bed_temp; std::string color; + std::string preset_data; SpoolmanVendorShrPtr m_vendor_ptr; void update_from_server(bool recursive = false); + DynamicPrintConfig get_config_from_preset_data() const; private: Spoolman* m_spoolman; From c1c99917df1775a92a4c20fde99b22bb1395f5b5 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Fri, 21 Nov 2025 18:49:47 -0500 Subject: [PATCH 076/100] Update Spoolman buttons --- src/slic3r/GUI/Tab.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 0d2e769dae..3fe7c89f05 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3966,14 +3966,14 @@ void TabFilament::build() update_spoolman_statistics(); }; - auto refresh_all_btn = new wxButton(parent, wxID_ANY, _L("Update Filament")); + auto refresh_all_btn = new Button(parent, _L("Update Filament")); refresh_all_btn->Bind(wxEVT_BUTTON, [on_click](wxCommandEvent& evt) { on_click(false); }); - wxGetApp().UpdateDarkUI(refresh_all_btn); + refresh_all_btn->SetStyle(ButtonStyle::Regular, ButtonType::Parameter); sizer->Add(refresh_all_btn); - auto refresh_stats_btn = new wxButton(parent, wxID_ANY, _L("Update Stats")); + auto refresh_stats_btn = new Button(parent, _L("Update Stats")); refresh_stats_btn->Bind(wxEVT_BUTTON, [on_click](wxCommandEvent& evt) { on_click(true); }); - wxGetApp().UpdateDarkUI(refresh_stats_btn); + refresh_stats_btn->SetStyle(ButtonStyle::Regular, ButtonType::Parameter); sizer->Add(refresh_stats_btn); return sizer; }; @@ -3984,16 +3984,16 @@ void TabFilament::build() line.widget = [&](wxWindow* parent){ auto sizer = new wxBoxSizer(wxHORIZONTAL); - auto sync_to_spoolman_btn = new wxButton(parent, wxID_ANY, _L("Save Preset to Spoolman")); + auto sync_to_spoolman_btn = new Button(parent, _L("Save Preset")); sync_to_spoolman_btn->Bind(wxEVT_BUTTON, [&](wxCommandEvent& evt) { auto res = Spoolman::save_preset_to_spoolman(&m_presets->get_selected_preset()); if (res.has_failed()) show_error(this, res.build_error_dialog_message()); }); - wxGetApp().UpdateDarkUI(sync_to_spoolman_btn); + sync_to_spoolman_btn->SetStyle(ButtonStyle::Regular, ButtonType::Parameter); sizer->Add(sync_to_spoolman_btn); - auto load_from_spoolman_btn = new wxButton(parent, wxID_ANY, _L("Load Preset from Spoolman")); + auto load_from_spoolman_btn = new Button(parent, _L("Load Preset")); load_from_spoolman_btn->Bind(wxEVT_BUTTON, [&](wxCommandEvent& evt) { if (m_presets->current_is_dirty() && m_active_page->get_field("spoolman_spool_id")->m_is_modified_value) { show_error(this, "This profile cannot be updated with an unsaved Spool ID value. Please save the profile, then try updating again."); @@ -4028,7 +4028,7 @@ void TabFilament::build() // Load config changes into the tab this->load_config(config); }); - wxGetApp().UpdateDarkUI(load_from_spoolman_btn); + load_from_spoolman_btn->SetStyle(ButtonStyle::Regular, ButtonType::Parameter); sizer->Add(load_from_spoolman_btn); return sizer; From a90c301de3c8f6ed3bfc960397032a31844aeaf8 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Fri, 21 Nov 2025 19:01:20 -0500 Subject: [PATCH 077/100] Use double in Spoolman to match ConfigOptionFloat(s) --- src/slic3r/Utils/Spoolman.cpp | 16 ++++++++-------- src/slic3r/Utils/Spoolman.hpp | 20 ++++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index f23aaec738..4893fd2e24 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -465,10 +465,10 @@ void SpoolmanFilament::update_from_json(pt::ptree json_data) id = json_data.get("id"); name = get_opt(json_data, "name"); material = get_opt(json_data, "material"); - price = get_opt(json_data, "price"); - density = get_opt(json_data, "density"); - diameter = get_opt(json_data, "diameter"); - weight = get_opt(json_data, "weight"); + price = get_opt(json_data, "price"); + density = get_opt(json_data, "density"); + diameter = get_opt(json_data, "diameter"); + weight = get_opt(json_data, "weight"); article_number = get_opt(json_data, "article_number"); extruder_temp = get_opt(json_data, "settings_extruder_temp"); bed_temp = get_opt(json_data, "settings_bed_temp"); @@ -572,10 +572,10 @@ void SpoolmanSpool::update_from_json(pt::ptree json_data) m_filament_ptr = m_spoolman->m_filaments.at(filament_id); } id = json_data.get("id"); - remaining_weight = get_opt(json_data, "remaining_weight"); - used_weight = get_opt(json_data, "used_weight"); - remaining_length = get_opt(json_data, "remaining_length"); - used_length = get_opt(json_data, "used_length"); + remaining_weight = get_opt(json_data, "remaining_weight"); + used_weight = get_opt(json_data, "used_weight"); + remaining_length = get_opt(json_data, "remaining_length"); + used_length = get_opt(json_data, "used_length"); archived = get_opt(json_data, "archived"); } diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index 47811adf77..be850f6b46 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -209,10 +209,10 @@ public: int id; std::string name; std::string material; - float price; - float density; - float diameter; - float weight; + double price; + double density; + double diameter; + double weight; std::string article_number; int extruder_temp; int bed_temp; @@ -245,12 +245,12 @@ private: class SpoolmanSpool { public: - int id; - float remaining_weight; - float used_weight; - float remaining_length; - float used_length; - bool archived; + int id; + double remaining_weight; + double used_weight; + double remaining_length; + double used_length; + bool archived; SpoolmanFilamentShrPtr m_filament_ptr; From 4a036534ddde5f1864fb4dd2bb28bc4ece56d98d Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Sat, 22 Nov 2025 09:26:49 +0000 Subject: [PATCH 078/100] Fix flatpak build --- src/slic3r/Utils/Spoolman.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index be850f6b46..9324f938d4 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -4,12 +4,13 @@ #include "Http.hpp" #include #include -#include namespace pt = boost::property_tree; namespace Slic3r { class Preset; +class DynamicConfig; +class DynamicPrintConfig; class SpoolmanVendor; class SpoolmanFilament; @@ -234,7 +235,7 @@ private: }; void update_from_json(pt::ptree json_data); - void apply_to_config(Slic3r::DynamicConfig& config) const; + void apply_to_config(DynamicConfig& config) const; friend class Spoolman; friend class SpoolmanVendor; From 5456eca7badcd2623f4405ebba13b36920fda3cf Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 26 Nov 2025 14:56:32 -0500 Subject: [PATCH 079/100] Fix AppConfig get_bool --- src/libslic3r/AppConfig.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 9307877552..594cf06873 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -104,7 +104,7 @@ public: std::string get(const std::string &key) const { std::string value; this->get("app", key, value); return value; } bool get_bool(const std::string §ion, const std::string &key) const - { return this->get(section, key) == "true" || this->get(key) == "1"; } + { return this->get(section, key) == "true" || this->get(section, key) == "1"; } bool get_bool(const std::string &key) const { return this->get_bool("app", key); } void set(const std::string §ion, const std::string &key, const std::string &value) From 937aa4d72bd9fddf03d7613f1de3a1e4d52dbf13 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Thu, 27 Nov 2025 04:52:03 -0500 Subject: [PATCH 080/100] Make ConfigOptionsGroup more generic - Add abstract class DynamicConfigWithDef - Use DynamicConfigWithDef in place of DynamicPrintConfig throughout ConfigOptionsGroup --- src/libslic3r/Config.hpp | 8 ++++++++ src/libslic3r/PrintConfig.cpp | 2 +- src/libslic3r/PrintConfig.hpp | 8 ++++---- src/slic3r/GUI/GUI.cpp | 2 +- src/slic3r/GUI/GUI.hpp | 2 +- src/slic3r/GUI/OptionsGroup.cpp | 8 ++++---- src/slic3r/GUI/OptionsGroup.hpp | 16 ++++++++-------- 7 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 5406859a16..876eb3dd01 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -2803,6 +2803,14 @@ private: template void serialize(Archive &ar) { ar(options); } }; +// An abstract class for a dynamic config that is expected to always return a valid def when calling this->def() +class DynamicConfigWithDef : public DynamicConfig +{ + using DynamicConfig::DynamicConfig; +public: + const ConfigDef* def() const override = 0; +}; + // Configuration store with a static definition of configuration values. // In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons, // because the configuration values could be accessed directly. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 145831c820..9570c75e57 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -7742,7 +7742,7 @@ DynamicPrintConfig DynamicPrintConfig::full_print_config() return DynamicPrintConfig((const PrintRegionConfig&)FullPrintConfig::defaults()); } -DynamicPrintConfig::DynamicPrintConfig(const StaticPrintConfig& rhs) : DynamicConfig(rhs, rhs.keys_ref()) +DynamicPrintConfig::DynamicPrintConfig(const StaticPrintConfig& rhs) : DynamicConfigWithDef(rhs, rhs.keys_ref()) { } diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 6c069df067..0d31d6ccd3 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -564,14 +564,14 @@ double min_object_distance(const ConfigBase &cfg); // so the modified configuration values may be diffed against the active configuration // to invalidate the proper slicing resp. g-code generation processing steps. // This object is mapped to Perl as Slic3r::Config. -class DynamicPrintConfig : public DynamicConfig +class DynamicPrintConfig : public DynamicConfigWithDef { public: DynamicPrintConfig() {} - DynamicPrintConfig(const DynamicPrintConfig &rhs) : DynamicConfig(rhs) {} - DynamicPrintConfig(DynamicPrintConfig &&rhs) noexcept : DynamicConfig(std::move(rhs)) {} + DynamicPrintConfig(const DynamicPrintConfig &rhs) : DynamicConfigWithDef(rhs) {} + DynamicPrintConfig(DynamicPrintConfig &&rhs) noexcept : DynamicConfigWithDef(std::move(rhs)) {} explicit DynamicPrintConfig(const StaticPrintConfig &rhs); - explicit DynamicPrintConfig(const ConfigBase &rhs) : DynamicConfig(rhs) {} + explicit DynamicPrintConfig(const ConfigBase &rhs) : DynamicConfigWithDef(rhs) {} DynamicPrintConfig& operator=(const DynamicPrintConfig &rhs) { DynamicConfig::operator=(rhs); return *this; } DynamicPrintConfig& operator=(DynamicPrintConfig &&rhs) noexcept { DynamicConfig::operator=(std::move(rhs)); return *this; } diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index ce0c24d08a..30495fb2ce 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -103,7 +103,7 @@ const std::string& shortkey_alt_prefix() } // opt_index = 0, by the reason of zero-index in ConfigOptionVector by default (in case only one element) -void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/) +void change_opt_value(DynamicConfigWithDef& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/) { try{ diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp index db8cf06a61..ff337d6b43 100644 --- a/src/slic3r/GUI/GUI.hpp +++ b/src/slic3r/GUI/GUI.hpp @@ -37,7 +37,7 @@ extern AppConfig* get_app_config(); extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change); // Change option value in config -void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0); +void change_opt_value(DynamicConfigWithDef& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0); // If monospaced_font is true, the error message is displayed using html
tags, // so that the code formatting will be preserved. This is useful for reporting errors from the placeholder parser. diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 70d7f4e72d..999ca4d7b4 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -726,7 +726,7 @@ void ConfigOptionsGroup::back_to_sys_value(const std::string& opt_key) back_to_config_value(m_get_sys_config(), opt_key); } -void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, const std::string& opt_key) +void ConfigOptionsGroup::back_to_config_value(const DynamicConfigWithDef& config, const std::string& opt_key) { boost::any value; if (opt_key == "extruders_count") { @@ -937,7 +937,7 @@ boost::any ConfigOptionsGroup::config_value(const std::string& opt_key, int opt_ } } -boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config, const std::string& opt_key, int opt_index /*= -1*/) +boost::any ConfigOptionsGroup::get_config_value(const DynamicConfigWithDef& config, const std::string& opt_key, int opt_index /*= -1*/) { size_t idx = opt_index == -1 ? 0 : opt_index; @@ -1118,7 +1118,7 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config } // BBS: restore all pages in preset -boost::any ConfigOptionsGroup::get_config_value2(const DynamicPrintConfig& config, const std::string& opt_key, int opt_index /*= -1*/) +boost::any ConfigOptionsGroup::get_config_value2(const DynamicConfigWithDef& config, const std::string& opt_key, int opt_index /*= -1*/) { size_t idx = opt_index == -1 ? 0 : opt_index; @@ -1273,7 +1273,7 @@ std::pair ConfigOptionsGroup::get_custom_ctrl_with_blinki void ConfigOptionsGroup::change_opt_value(const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/) { - Slic3r::GUI::change_opt_value(const_cast(*m_config), opt_key, value, opt_index); + Slic3r::GUI::change_opt_value(const_cast(*m_config), opt_key, value, opt_index); if (m_modelconfig) m_modelconfig->touch(); } diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 6062138e12..dafbbea5bb 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -247,10 +247,10 @@ public: class ConfigOptionsGroup: public OptionsGroup { public: - ConfigOptionsGroup( wxWindow* parent, const wxString& title, const wxString& icon, DynamicPrintConfig* config = nullptr, + ConfigOptionsGroup( wxWindow* parent, const wxString& title, const wxString& icon, DynamicConfigWithDef* config = nullptr, bool is_tab_opt = false, column_t extra_clmn = nullptr) : OptionsGroup(parent, title, icon, is_tab_opt, extra_clmn), m_config(config) {} - ConfigOptionsGroup( wxWindow* parent, const wxString& title, DynamicPrintConfig* config = nullptr, + ConfigOptionsGroup( wxWindow* parent, const wxString& title, DynamicConfigWithDef* config = nullptr, bool is_tab_opt = false, column_t extra_clmn = nullptr) : ConfigOptionsGroup(parent, title, wxEmptyString, config, is_tab_opt, extra_clmn) {} ConfigOptionsGroup( wxWindow* parent, const wxString& title, ModelConfig* config, @@ -264,7 +264,7 @@ public: const t_opt_map& opt_map() const throw() { return m_opt_map; } void set_config_category_and_type(const wxString &category, int type) { m_config_category = category; m_config_type = type; } - void set_config(DynamicPrintConfig* config) { + void set_config(DynamicConfigWithDef* config) { m_config = config; m_modelconfig = nullptr; } Option get_option(const std::string& opt_key, int opt_index = -1); Line create_single_option_line(const std::string& title, const std::string& path = std::string(), int idx = -1) /*const*/{ @@ -286,7 +286,7 @@ public: void on_change_OG(const t_config_option_key& opt_id, const boost::any& value) override; void back_to_initial_value(const std::string& opt_key) override; void back_to_sys_value(const std::string& opt_key) override; - void back_to_config_value(const DynamicPrintConfig& config, const std::string& opt_key); + void back_to_config_value(const DynamicConfigWithDef& config, const std::string& opt_key); void on_kill_focus(const std::string& opt_key) override; void reload_config(); // return value shows visibility : false => all options are hidden @@ -299,9 +299,9 @@ public: void refresh(); boost::any config_value(const std::string& opt_key, int opt_index, bool deserialize); // return option value from config - boost::any get_config_value(const DynamicPrintConfig& config, const std::string& opt_key, int opt_index = -1); + boost::any get_config_value(const DynamicConfigWithDef& config, const std::string& opt_key, int opt_index = -1); // BBS: restore all pages in preset - boost::any get_config_value2(const DynamicPrintConfig& config, const std::string& opt_key, int opt_index = -1); + boost::any get_config_value2(const DynamicConfigWithDef& config, const std::string& opt_key, int opt_index = -1); Field* get_fieldc(const t_config_option_key& opt_key, int opt_index); std::pair get_custom_ctrl_with_blinking_ptr(const t_config_option_key& opt_key, int opt_index/* = -1*/); @@ -311,7 +311,7 @@ protected: // Reference to libslic3r config or ModelConfig::get(), non-owning pointer. // The reference is const, so that the spots which modify m_config are clearly // demarcated by const_cast and m_config_changed_callback is called afterwards. - const DynamicPrintConfig* m_config {nullptr}; + const DynamicConfigWithDef* m_config {nullptr}; // If the config is modelconfig, then ModelConfig::touch() has to be called after value change. ModelConfig* m_modelconfig { nullptr }; t_opt_map m_opt_map; @@ -326,7 +326,7 @@ protected: // It is designed for single extruder multiple material machine. class ExtruderOptionsGroup : public ConfigOptionsGroup { public: - ExtruderOptionsGroup(wxWindow* parent, const wxString& title, const wxString& icon, DynamicPrintConfig* config = nullptr, // ORCA: add support for icons + ExtruderOptionsGroup(wxWindow* parent, const wxString& title, const wxString& icon, DynamicConfigWithDef* config = nullptr, // ORCA: add support for icons bool is_tab_opt = false, column_t extra_clmn = nullptr) : ConfigOptionsGroup(parent, title, icon, config, is_tab_opt, extra_clmn) {} From d2a497e8a422ba47eaa9972fb20648821e419d24 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Thu, 27 Nov 2025 04:55:08 -0500 Subject: [PATCH 081/100] Use ConfigOptionsGroup for SpoolmanDialog --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/SpoolmanDialog.cpp | 57 +++++------------------- src/slic3r/GUI/SpoolmanDialog.hpp | 28 +++++++----- src/slic3r/Utils/SpoolmanConfig.cpp | 69 +++++++++++++++++++++++++++++ src/slic3r/Utils/SpoolmanConfig.hpp | 37 ++++++++++++++++ 5 files changed, 136 insertions(+), 57 deletions(-) create mode 100644 src/slic3r/Utils/SpoolmanConfig.cpp create mode 100644 src/slic3r/Utils/SpoolmanConfig.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index da3fcf3aa8..821d421ffa 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -631,6 +631,8 @@ set(SLIC3R_GUI_SOURCES Utils/WxFontUtils.hpp Utils/FileTransferUtils.cpp Utils/FileTransferUtils.hpp + Utils/SpoolmanConfig.cpp + Utils/SpoolmanConfig.hpp ) add_subdirectory(GUI/DeviceCore) diff --git a/src/slic3r/GUI/SpoolmanDialog.cpp b/src/slic3r/GUI/SpoolmanDialog.cpp index a15349ed49..355fd4f6c7 100644 --- a/src/slic3r/GUI/SpoolmanDialog.cpp +++ b/src/slic3r/GUI/SpoolmanDialog.cpp @@ -5,6 +5,7 @@ #include "OptionsGroup.hpp" #include "Plater.hpp" #include "Spoolman.hpp" +#include "SpoolmanConfig.hpp" #include "Widgets/DialogButtons.hpp" #include "Widgets/LabeledStaticBox.hpp" #include "wx/sizer.h" @@ -15,7 +16,6 @@ namespace Slic3r::GUI { wxDEFINE_EVENT(EVT_FINISH_LOADING, wxCommandEvent); static BitmapCache cache; -const static std::regex spoolman_regex("^spoolman_"); SpoolInfoWidget::SpoolInfoWidget(wxWindow* parent, const Preset* preset) : wxPanel(parent, wxID_ANY), m_preset(preset) { @@ -80,7 +80,8 @@ SpoolmanDialog::SpoolmanDialog(wxWindow* parent) wxGetApp().UpdateDarkUI(m_main_panel); m_main_panel->SetSizer(main_panel_sizer); - m_optgroup = new OptionsGroup(m_main_panel, _L("Spoolman Options"), wxEmptyString); + m_config = new SpoolmanDynamicConfig(wxGetApp().app_config); + m_optgroup = new ConfigOptionsGroup(m_main_panel, _L("Spoolman Options"), wxEmptyString, m_config); build_options_group(); m_optgroup->m_on_change = [&](const std::string& key, const boost::any& value) { m_dirty_settings = true; @@ -139,39 +140,18 @@ SpoolmanDialog::SpoolmanDialog(wxWindow* parent) this->ShowModal(); } +SpoolmanDialog::~SpoolmanDialog() +{ + delete m_config; +} + void SpoolmanDialog::build_options_group() const { - ConfigOptionDef def; - def.type = coBool; - def.label = _u8L("Spoolman Enabled"); - def.tooltip = _u8L("Enables spool management features powered by a Spoolman server instance"); - def.set_default_value(new ConfigOptionBool()); - m_optgroup->append_single_option_line((Option(def, "spoolman_enabled"))); - - def = ConfigOptionDef(); - def.type = coString; - def.label = _u8L("Spoolman Host"); - def.tooltip = _u8L("Points to where you Spoolman instance is hosted. Use the format of :. You may also just specify the " - "host and it will use the default Spoolman port of ") + - Spoolman::DEFAULT_PORT; - def.set_default_value(new ConfigOptionString()); - m_optgroup->append_single_option_line(Option(def, "spoolman_host")); + m_optgroup->append_single_option_line("spoolman_enabled"); + m_optgroup->append_single_option_line("spoolman_host"); m_optgroup->activate(); - - // Load config values from the app config - const auto app_config = wxGetApp().app_config; - for (auto& line : m_optgroup->get_lines()) { - for (auto& option : line.get_options()) { - auto app_config_key = regex_replace(option.opt_id, spoolman_regex, ""); - if (option.opt.type == coBool) - m_optgroup->set_value(option.opt_id, app_config->get_bool("spoolman", app_config_key)); - else if (option.opt.type == coString) - m_optgroup->set_value(option.opt_id, wxString::FromUTF8(app_config->get("spoolman", app_config_key))); - else - BOOST_LOG_TRIVIAL(error) << "SpoolmanDialog load: Unknown option type " << option.opt.type; - } - } + m_optgroup->reload_config(); } void SpoolmanDialog::build_spool_info() @@ -216,19 +196,7 @@ void SpoolmanDialog::save_spoolman_settings() return; // Save config values to the app config - const auto app_config = wxGetApp().app_config; - for (auto& line : m_optgroup->get_lines()) { - for (auto& option : line.get_options()) { - auto app_config_key = regex_replace(option.opt_id, spoolman_regex, ""); - auto val = m_optgroup->get_value(option.opt_id); - if (option.opt.type == coBool) - app_config->set("spoolman", app_config_key, any_cast(val)); - else if (option.opt.type == coString) - app_config->set("spoolman", app_config_key, any_cast(val)); - else - BOOST_LOG_TRIVIAL(error) << "SpoolmanDialog save: Unknown option type " << option.opt.type; - } - } + m_config->save_to_appconfig(wxGetApp().app_config); if (m_dirty_host) Spoolman::on_server_changed(); @@ -271,5 +239,4 @@ void SpoolmanDialog::on_dpi_changed(const wxRect& suggested_rect) Fit(); Refresh(); } - } // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/SpoolmanDialog.hpp b/src/slic3r/GUI/SpoolmanDialog.hpp index f4ddc0ec4f..11cd484ddf 100644 --- a/src/slic3r/GUI/SpoolmanDialog.hpp +++ b/src/slic3r/GUI/SpoolmanDialog.hpp @@ -6,7 +6,9 @@ #include "Widgets/Label.hpp" #include "Widgets/LoadingSpinner.hpp" -namespace Slic3r::GUI { +namespace Slic3r { +class SpoolmanDynamicConfig; +namespace GUI { class SpoolInfoWidget : public wxPanel { public: @@ -25,6 +27,7 @@ class SpoolmanDialog : DPIDialog { public: SpoolmanDialog(wxWindow* parent); + ~SpoolmanDialog() override; void build_options_group() const; void build_spool_info(); void show_loading(bool show = true); @@ -36,17 +39,18 @@ public: protected: void on_dpi_changed(const wxRect& suggested_rect) override; - bool m_dirty_settings{false}; - bool m_dirty_host{false}; - OptionsGroup* m_optgroup; - wxPanel* m_main_panel; - wxPanel* m_loading_panel; - wxGridSizer* m_info_widgets_sizer; - wxBoxSizer* m_spoolman_error_label_sizer; - Label* m_spoolman_error_label; - LoadingSpinner* m_loading_spinner; - DialogButtons* m_buttons; + bool m_dirty_settings{false}; + bool m_dirty_host{false}; + ConfigOptionsGroup* m_optgroup; + SpoolmanDynamicConfig* m_config; + wxPanel* m_main_panel; + wxPanel* m_loading_panel; + wxGridSizer* m_info_widgets_sizer; + wxBoxSizer* m_spoolman_error_label_sizer; + Label* m_spoolman_error_label; + LoadingSpinner* m_loading_spinner; + DialogButtons* m_buttons; }; -} // namespace Slic3r::GUI +}} // namespace Slic3r::GUI #endif // ORCASLICER_SPOOLMANDIALOG_HPP \ No newline at end of file diff --git a/src/slic3r/Utils/SpoolmanConfig.cpp b/src/slic3r/Utils/SpoolmanConfig.cpp new file mode 100644 index 0000000000..f0ea8448ee --- /dev/null +++ b/src/slic3r/Utils/SpoolmanConfig.cpp @@ -0,0 +1,69 @@ +#include "SpoolmanConfig.hpp" + +#include "Spoolman.hpp" +#include "libslic3r/AppConfig.hpp" +#include "slic3r/GUI/I18N.hpp" + +namespace Slic3r { +const static std::regex spoolman_regex("^spoolman_"); + +SpoolmanConfigDef::SpoolmanConfigDef() +{ + ConfigOptionDef* def; + + def = this->add("spoolman_enabled", coBool); + def->label = _u8L("Spoolman Enabled"); + def->tooltip = _u8L("Enables spool management features powered by a Spoolman server instance"); + def->set_default_value(new ConfigOptionBool()); + + def = this->add("spoolman_host", coString); + def->label = _u8L("Spoolman Host"); + def->tooltip = _u8L("Points to where you Spoolman instance is hosted. Use the format of :. You may also just specify the " + "host and it will use the default Spoolman port of ") + + Spoolman::DEFAULT_PORT; + def->set_default_value(new ConfigOptionString()); +} + +const SpoolmanConfigDef spoolman_config_def; + +void SpoolmanDynamicConfig::load_defaults() +{ + this->clear(); + this->apply(default_spoolman_config); +} + +void SpoolmanDynamicConfig::load_from_appconfig(const AppConfig* app_config) +{ + this->load_defaults(); + for (const auto& [opt_key, opt_def] : this->def()->options) { + auto app_config_key = regex_replace(opt_key, spoolman_regex, ""); + auto val = app_config->get("spoolman", app_config_key); + if (val.empty()) + continue; + auto opt = this->option(opt_key, true); + opt->deserialize(val); + } +} + +void SpoolmanDynamicConfig::save_to_appconfig(AppConfig* app_config) const +{ + for (const auto& key : this->keys()) { + const auto opt = this->option(key); + if (opt == this->def()->get(key)->default_value.get()) + continue; + auto val = opt->serialize(); + auto app_config_key = regex_replace(key, spoolman_regex, ""); + app_config->set("spoolman", app_config_key, val); + } +} + +SpoolmanDynamicConfig create_spoolman_default_config() +{ + SpoolmanDynamicConfig config; + for (const auto& key : config.def()->keys()) + config.option(key, true); + return config; +} + +const SpoolmanDynamicConfig default_spoolman_config = create_spoolman_default_config(); +} // namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/Utils/SpoolmanConfig.hpp b/src/slic3r/Utils/SpoolmanConfig.hpp new file mode 100644 index 0000000000..29f447af5f --- /dev/null +++ b/src/slic3r/Utils/SpoolmanConfig.hpp @@ -0,0 +1,37 @@ +#ifndef ORCASLICER_SPOOLMANCONFIG_HPP +#define ORCASLICER_SPOOLMANCONFIG_HPP + +#include "libslic3r/Config.hpp" + +namespace Slic3r { +class AppConfig; + +class SpoolmanConfigDef : public ConfigDef +{ +public: + SpoolmanConfigDef(); +}; + +extern const SpoolmanConfigDef spoolman_config_def; + +class SpoolmanDynamicConfig : public DynamicConfigWithDef +{ +public: + SpoolmanDynamicConfig() = default; + explicit SpoolmanDynamicConfig(const AppConfig* app_config) + { + load_from_appconfig(app_config); + } + const ConfigDef* def() const override { return &spoolman_config_def; } + // Clear config and load defaults + void load_defaults(); + // Load config from appconfig on top of defaults + void load_from_appconfig(const AppConfig* app_config); + // Save config to appconfig with default values stripped + void save_to_appconfig(AppConfig* app_config) const; +}; + +extern const SpoolmanDynamicConfig default_spoolman_config; +} // namespace Slic3r + +#endif // ORCASLICER_SPOOLMANCONFIG_HPP From 2c91504d86ac143dbc02f28a4ab5c98a2630240c Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Fri, 28 Nov 2025 04:25:01 -0500 Subject: [PATCH 082/100] Add consumption type to SpoolmanDialog --- src/slic3r/GUI/SpoolmanDialog.cpp | 1 + src/slic3r/Utils/SpoolmanConfig.cpp | 19 +++++++++++++++++++ src/slic3r/Utils/SpoolmanConfig.hpp | 6 ++++++ 3 files changed, 26 insertions(+) diff --git a/src/slic3r/GUI/SpoolmanDialog.cpp b/src/slic3r/GUI/SpoolmanDialog.cpp index 355fd4f6c7..ed1b88bbd4 100644 --- a/src/slic3r/GUI/SpoolmanDialog.cpp +++ b/src/slic3r/GUI/SpoolmanDialog.cpp @@ -149,6 +149,7 @@ void SpoolmanDialog::build_options_group() const { m_optgroup->append_single_option_line("spoolman_enabled"); m_optgroup->append_single_option_line("spoolman_host"); + m_optgroup->append_single_option_line("spoolman_consumption_type"); m_optgroup->activate(); m_optgroup->reload_config(); diff --git a/src/slic3r/Utils/SpoolmanConfig.cpp b/src/slic3r/Utils/SpoolmanConfig.cpp index f0ea8448ee..a4f1d7366d 100644 --- a/src/slic3r/Utils/SpoolmanConfig.cpp +++ b/src/slic3r/Utils/SpoolmanConfig.cpp @@ -7,6 +7,11 @@ namespace Slic3r { const static std::regex spoolman_regex("^spoolman_"); +static const t_config_enum_values s_keys_map_ConsumptionType = { + {"weight", ctWEIGHT}, + {"length", ctLENGTH} +}; + SpoolmanConfigDef::SpoolmanConfigDef() { ConfigOptionDef* def; @@ -22,6 +27,20 @@ SpoolmanConfigDef::SpoolmanConfigDef() "host and it will use the default Spoolman port of ") + Spoolman::DEFAULT_PORT; def->set_default_value(new ConfigOptionString()); + + def = this->add("spoolman_consumption_type", coEnum); + def->label = _u8L("Consumption Type"); + def->tooltip = _u8L("The unit of measurement that sent to Spoolman for consumption"); + def->set_default_value(new ConfigOptionEnumGeneric(&s_keys_map_ConsumptionType, ctWEIGHT)); + def->enum_keys_map = &s_keys_map_ConsumptionType; + def->enum_labels = { + _u8L("Weight"), + _u8L("Length") + }; + def->enum_values = { + "weight", + "length" + }; } const SpoolmanConfigDef spoolman_config_def; diff --git a/src/slic3r/Utils/SpoolmanConfig.hpp b/src/slic3r/Utils/SpoolmanConfig.hpp index 29f447af5f..788c656a38 100644 --- a/src/slic3r/Utils/SpoolmanConfig.hpp +++ b/src/slic3r/Utils/SpoolmanConfig.hpp @@ -6,6 +6,12 @@ namespace Slic3r { class AppConfig; +enum ConsumptionType +{ + ctWEIGHT, + ctLENGTH +}; + class SpoolmanConfigDef : public ConfigDef { public: From 8f600df4204b32070e8e841c80e5bd008bf27aef Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Sun, 30 Nov 2025 11:06:57 +0000 Subject: [PATCH 083/100] Fix flatpak build --- src/slic3r/Utils/SpoolmanConfig.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/Utils/SpoolmanConfig.cpp b/src/slic3r/Utils/SpoolmanConfig.cpp index a4f1d7366d..70ccd1c59c 100644 --- a/src/slic3r/Utils/SpoolmanConfig.cpp +++ b/src/slic3r/Utils/SpoolmanConfig.cpp @@ -3,6 +3,7 @@ #include "Spoolman.hpp" #include "libslic3r/AppConfig.hpp" #include "slic3r/GUI/I18N.hpp" +#include namespace Slic3r { const static std::regex spoolman_regex("^spoolman_"); From 633fd252b088df34141b264b6b6f78c7077d38e0 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Mon, 1 Dec 2025 11:13:12 -0500 Subject: [PATCH 084/100] Implement consumption dialog for printing/sending to BBL printers --- src/slic3r/GUI/Jobs/PrintJob.cpp | 1 + src/slic3r/GUI/Jobs/SendJob.cpp | 1 + src/slic3r/GUI/Plater.cpp | 25 ++++++++++++++++++------- src/slic3r/GUI/SendToPrinter.cpp | 1 + 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/Jobs/PrintJob.cpp b/src/slic3r/GUI/Jobs/PrintJob.cpp index f07e89aad7..51d535d660 100644 --- a/src/slic3r/GUI/Jobs/PrintJob.cpp +++ b/src/slic3r/GUI/Jobs/PrintJob.cpp @@ -652,6 +652,7 @@ void PrintJob::process(Ctl &ctl) evt->SetInt(sel); } } + evt->SetExtraLong(job_data.plate_idx == PLATE_ALL_IDX ? PLATE_ALL_IDX : curr_plate_idx - 1); wxQueueEvent(m_plater, evt); m_job_finished = true; } diff --git a/src/slic3r/GUI/Jobs/SendJob.cpp b/src/slic3r/GUI/Jobs/SendJob.cpp index 4d14ab2dcc..d1b70971d8 100644 --- a/src/slic3r/GUI/Jobs/SendJob.cpp +++ b/src/slic3r/GUI/Jobs/SendJob.cpp @@ -363,6 +363,7 @@ void SendJob::process(Ctl &ctl) BOOST_LOG_TRIVIAL(error) << "send_job: send ok."; wxCommandEvent* evt = new wxCommandEvent(m_print_job_completed_id); evt->SetString(from_u8(params.project_name)); + evt->SetInt(params.plate_index == PLATE_ALL_IDX ? PLATE_ALL_IDX : params.plate_index - 1); wxQueueEvent(m_plater, evt); m_job_finished = true; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b4db2ae689..e494316451 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4389,7 +4389,7 @@ struct Plater::priv } } void export_gcode(fs::path output_path, bool output_path_on_removable_media); - void export_gcode(fs::path output_path, bool output_path_on_removable_media, PrintHostJob upload_job); + void export_gcode(fs::path output_path, bool output_path_on_removable_media, PrintHostJob upload_job, bool export_all = false); void reload_from_disk(); bool replace_volume_with_stl(int object_idx, int volume_idx, const fs::path& new_path, const std::string& snapshot = ""); @@ -4423,6 +4423,7 @@ struct Plater::priv // 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 spoolman_consumption_dialog(int plate_idx); void on_action_add(SimpleEvent&); void on_action_add_plate(SimpleEvent&); @@ -7661,7 +7662,7 @@ void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_remova this->background_process.set_task(PrintBase::TaskParams()); this->restart_background_process(priv::UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT); } -void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_removable_media, PrintHostJob upload_job) +void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_removable_media, PrintHostJob upload_job, bool export_all) { wxCHECK_RET(!(output_path.empty() && upload_job.empty()), "export_gcode: output_path and upload_job empty"); @@ -7681,6 +7682,7 @@ void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_remova if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0) return; + m_export_all = export_all; show_warning_dialog = true; if (! output_path.empty()) { background_process.schedule_export(output_path.string(), output_path_on_removable_media); @@ -9245,6 +9247,11 @@ bool Plater::priv::warnings_dialog() } void Plater::priv::spoolman_consumption_dialog(const bool& all_plates) +{ + spoolman_consumption_dialog(all_plates ? PLATE_ALL_IDX : PLATE_CURRENT_IDX); +} + +void Plater::priv::spoolman_consumption_dialog(int plate_idx) { static constexpr auto show_dlg_key = "show_spoolman_consumption_dialog"; if (!wxGetApp().app_config->get_bool(show_dlg_key)) @@ -9276,11 +9283,13 @@ void Plater::priv::spoolman_consumption_dialog(const bool& all_plates) } }; - if (all_plates) + if (plate_idx == PLATE_ALL_IDX) for (const auto& plate : partplate_list.get_plate_list()) apply_estimates_from_plate(plate); - else + else if (plate_idx == PLATE_CURRENT_IDX) apply_estimates_from_plate(partplate_list.get_curr_plate()); + else + apply_estimates_from_plate(partplate_list.get_plate(plate_idx)); if (estimates.empty()) return; @@ -9790,7 +9799,6 @@ void Plater::priv::on_action_export_all_sliced_file(SimpleEvent &) { if (q != nullptr) { BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ":received export all sliced file event\n"; - m_export_all = true; q->export_gcode_3mf(true); } } @@ -9807,7 +9815,6 @@ void Plater::priv::on_action_export_to_sdcard_all(SimpleEvent&) { if (q != nullptr) { BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ":received export sliced file event\n"; - m_export_all = true; q->send_to_printer(true); } } @@ -14438,6 +14445,7 @@ void Plater::export_gcode_3mf(bool export_all) // update last output dir appconfig.update_last_output_dir(output_path.parent_path().string(), false); p->notification_manager->push_exporting_finished_notification(output_path.string(), p->last_output_dir_path, on_removable); + p->spoolman_consumption_dialog(plate_idx); } } @@ -15589,7 +15597,7 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us upload_job.upload_data.source_path = p->m_print_job_data._3mf_path; } - p->export_gcode(fs::path(), false, std::move(upload_job)); + p->export_gcode(fs::path(), false, std::move(upload_job), plate_idx == PLATE_ALL_IDX); } int Plater::send_gcode(int plate_idx, Export3mfProgressFn proFn) { @@ -15653,6 +15661,7 @@ void Plater::send_calibration_job_finished(wxCommandEvent & evt) event.SetEventObject(curr_wizard); wxPostEvent(curr_wizard, event); } + p->spoolman_consumption_dialog(static_cast(evt.GetExtraLong())); evt.Skip(); } @@ -15681,6 +15690,7 @@ void Plater::print_job_finished(wxCommandEvent &evt) MonitorPanel* curr_monitor = p->main_frame->m_monitor; if(curr_monitor) curr_monitor->get_tabpanel()->ChangeSelection(MonitorPanel::PrinterTab::PT_STATUS); + p->spoolman_consumption_dialog(static_cast(evt.GetExtraLong())); } void Plater::send_job_finished(wxCommandEvent& evt) @@ -15691,6 +15701,7 @@ void Plater::send_job_finished(wxCommandEvent& evt) send_gcode_finish(evt.GetString()); p->hide_send_to_printer_dlg(); + p->spoolman_consumption_dialog(evt.GetInt()); //p->main_frame->request_select_tab(MainFrame::TabPosition::tpMonitor); ////jump to monitor and select device status panel //MonitorPanel* curr_monitor = p->main_frame->m_monitor; diff --git a/src/slic3r/GUI/SendToPrinter.cpp b/src/slic3r/GUI/SendToPrinter.cpp index aac83ac1bf..0ffe75eb63 100644 --- a/src/slic3r/GUI/SendToPrinter.cpp +++ b/src/slic3r/GUI/SendToPrinter.cpp @@ -1995,6 +1995,7 @@ void SendToPrinterDialog::UploadFileRessultCallback(int res, int resp_ec, std::s show_status(PrintDialogStatus::PrintStatusReadingFinished); wxCommandEvent *evt = new wxCommandEvent(m_plater->get_send_finished_event()); evt->SetString(from_u8(m_current_project_name.utf8_string())); + evt->SetInt(m_print_plate_idx); wxQueueEvent(m_plater, evt); } else { From 17c4540e3658f15e61ed5dfad11ed55b31cf373c Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Mon, 1 Dec 2025 17:02:09 -0500 Subject: [PATCH 085/100] Improve the look of spool info widgets with only 1 row --- src/slic3r/GUI/SpoolmanDialog.cpp | 17 ++++++++++------- src/slic3r/GUI/SpoolmanDialog.hpp | 3 ++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/SpoolmanDialog.cpp b/src/slic3r/GUI/SpoolmanDialog.cpp index ed1b88bbd4..c11f90f165 100644 --- a/src/slic3r/GUI/SpoolmanDialog.cpp +++ b/src/slic3r/GUI/SpoolmanDialog.cpp @@ -101,8 +101,11 @@ SpoolmanDialog::SpoolmanDialog(wxWindow* parent) m_spoolman_error_label_sizer->AddStretchSpacer(1); main_panel_sizer->Add(m_spoolman_error_label_sizer, 1, wxALL | wxALIGN_CENTER | wxEXPAND, EM); - m_info_widgets_sizer = new wxGridSizer(2, EM, EM); - main_panel_sizer->Add(m_info_widgets_sizer, 1, wxALL | wxALIGN_CENTER, EM); + m_info_widgets_parent_sizer = new wxBoxSizer(wxVERTICAL); + m_info_widgets_grid_sizer = new wxGridSizer(2, EM, EM); + m_info_widgets_parent_sizer->Add(m_info_widgets_grid_sizer, 0, wxALIGN_CENTER); + m_info_widgets_parent_sizer->AddStretchSpacer(); + main_panel_sizer->Add(m_info_widgets_parent_sizer, 1, wxALL | wxALIGN_CENTER, EM); m_buttons = new DialogButtons(m_main_panel, {"Refresh", "OK"}); m_buttons->UpdateButtons(); @@ -158,10 +161,10 @@ void SpoolmanDialog::build_options_group() const void SpoolmanDialog::build_spool_info() { show_loading(); - m_info_widgets_sizer->Show(false); + m_info_widgets_parent_sizer->Show(false); m_spoolman_error_label_sizer->Show(false); create_thread([&] { - m_info_widgets_sizer->Clear(); + m_info_widgets_grid_sizer->Clear(); bool show_widgets = false; if (!Spoolman::is_enabled()) { m_spoolman_error_label->SetLabelText(_L("Spoolman is not enabled")); @@ -209,10 +212,10 @@ void SpoolmanDialog::save_spoolman_settings() void SpoolmanDialog::OnFinishLoading(wxCommandEvent& event) { if (event.GetInt()) { - m_info_widgets_sizer->Show(true); + m_info_widgets_parent_sizer->Show(true); auto preset_bundle = wxGetApp().preset_bundle; for (auto& filament_preset_name : preset_bundle->filament_presets) { - m_info_widgets_sizer->Add(new SpoolInfoWidget(m_main_panel, preset_bundle->filaments.find_preset(filament_preset_name)), 0, wxEXPAND); + m_info_widgets_grid_sizer->Add(new SpoolInfoWidget(m_main_panel, preset_bundle->filaments.find_preset(filament_preset_name)), 0, wxEXPAND); } } show_loading(false); @@ -234,7 +237,7 @@ void SpoolmanDialog::on_dpi_changed(const wxRect& suggested_rect) { GetSizer()->SetMinSize(wxDefaultCoord, 45 * EM); m_optgroup->msw_rescale(); - for (auto item : m_info_widgets_sizer->GetChildren()) + for (auto item : m_info_widgets_grid_sizer->GetChildren()) if (auto info_widget = dynamic_cast(item)) info_widget->rescale(); Fit(); diff --git a/src/slic3r/GUI/SpoolmanDialog.hpp b/src/slic3r/GUI/SpoolmanDialog.hpp index 11cd484ddf..c662b1c130 100644 --- a/src/slic3r/GUI/SpoolmanDialog.hpp +++ b/src/slic3r/GUI/SpoolmanDialog.hpp @@ -45,7 +45,8 @@ protected: SpoolmanDynamicConfig* m_config; wxPanel* m_main_panel; wxPanel* m_loading_panel; - wxGridSizer* m_info_widgets_sizer; + wxBoxSizer* m_info_widgets_parent_sizer; + wxGridSizer* m_info_widgets_grid_sizer; wxBoxSizer* m_spoolman_error_label_sizer; Label* m_spoolman_error_label; LoadingSpinner* m_loading_spinner; From f2f18130a85c228a54b9b1b0af43990ef860ed26 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Mon, 1 Dec 2025 17:17:10 -0500 Subject: [PATCH 086/100] Remove unneeded defaults from AppConfig.cpp --- src/libslic3r/AppConfig.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 7967c0fbca..0235418ff7 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -462,15 +462,6 @@ void AppConfig::set_defaults() if (get("show_spoolman_consumption_dialog").empty()) set_bool("show_spoolman_consumption_dialog", true); - if (get("spoolman", "enabled").empty()) - set("spoolman", "enabled", false); - - if (get("spoolman", "host").empty()) - set_str("spoolman", "host", ""); - - 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"); From 63b03860e4ada7af471a132ea289ce203bf72d05 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 3 Dec 2025 15:22:14 +0000 Subject: [PATCH 087/100] Handle filaments without vendor info --- src/slic3r/GUI/SpoolmanImportDialog.hpp | 8 +++---- src/slic3r/Utils/Spoolman.cpp | 28 ++++++++++++++++--------- src/slic3r/Utils/Spoolman.hpp | 7 +++++-- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/SpoolmanImportDialog.hpp b/src/slic3r/GUI/SpoolmanImportDialog.hpp index 4f3b152012..300bd0866a 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.hpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.hpp @@ -23,10 +23,10 @@ public: int get_id() const { return m_spool->id; }; wxColour get_color() const { return wxColour(m_spool->m_filament_ptr->color); } - wxString get_vendor_name() const { return wxString::FromUTF8(m_spool->getVendor()->name); }; - wxString get_filament_name() const { return wxString::FromUTF8(m_spool->m_filament_ptr->name); }; - wxString get_material() const { return wxString::FromUTF8(m_spool->m_filament_ptr->material); }; - bool is_archived() const { return m_spool->archived; }; + wxString get_vendor_name() const { return m_spool->getVendor() ? wxString::FromUTF8(m_spool->getVendor()->name) : wxString(); } + wxString get_filament_name() const { return wxString::FromUTF8(m_spool->m_filament_ptr->name); } + wxString get_material() const { return wxString::FromUTF8(m_spool->m_filament_ptr->material); } + bool is_archived() const { return m_spool->archived; } bool get_checked() { return m_checked; }; // return if value has changed diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 4893fd2e24..cd2750f7ac 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -9,7 +9,7 @@ namespace Slic3r { namespace { -template Type get_opt(pt::ptree& data, string path) { return data.get_optional(path).value_or(Type()); } +template Type get_opt(pt::ptree& data, const string& path, Type default_val = {}) { return data.get_optional(path).value_or(default_val); } } // namespace // Max timout in seconds for Spoolman HTTP requests @@ -451,16 +451,20 @@ void SpoolmanFilament::update_from_server(bool recursive) { const boost::property_tree::ptree& json_data = Spoolman::get_spoolman_json("filament/" + std::to_string(id)); update_from_json(json_data); - if (recursive) + if (recursive && m_vendor_ptr) m_vendor_ptr->update_from_json(json_data.get_child("vendor")); } void SpoolmanFilament::update_from_json(pt::ptree json_data) { - if (int vendor_id = json_data.get("vendor.id"); m_vendor_ptr && m_vendor_ptr->id != vendor_id) { - if (!m_spoolman->m_vendors.count(vendor_id)) - m_spoolman->m_vendors.emplace(vendor_id, make_shared(SpoolmanVendor(json_data.get_child("vendor")))); - m_vendor_ptr = m_spoolman->m_vendors[vendor_id]; + auto vendor_id = json_data.get_optional("vendor.id"); + if (m_vendor_ptr && !vendor_id.has_value()) { + m_vendor_ptr = nullptr; + } else if (vendor_id.has_value() && (!m_vendor_ptr || m_vendor_ptr->id != vendor_id.get())) { + auto val = vendor_id.get(); + if (!m_spoolman->m_vendors.count(val)) + m_spoolman->m_vendors.emplace(val, make_shared(SpoolmanVendor(json_data.get_child("vendor")))); + m_vendor_ptr = m_spoolman->m_vendors[val]; } id = json_data.get("id"); name = get_opt(json_data, "name"); @@ -494,7 +498,8 @@ void SpoolmanFilament::apply_to_config(Slic3r::DynamicConfig& config) const config.set_key_value("hot_plate_temp", new ConfigOptionInts({bed_temp})); } config.set_key_value("default_filament_colour", new ConfigOptionStrings{color}); - m_vendor_ptr->apply_to_config(config); + if (m_vendor_ptr) + m_vendor_ptr->apply_to_config(config); } DynamicPrintConfig SpoolmanFilament::get_config_from_preset_data() const @@ -529,18 +534,21 @@ void SpoolmanSpool::update_from_server(bool recursive) update_from_json(json_data); if (recursive) { m_filament_ptr->update_from_json(json_data.get_child("filament")); - getVendor()->update_from_json(json_data.get_child("filament.vendor")); + if (getVendor()) + getVendor()->update_from_json(json_data.get_child("filament.vendor")); } } std::string SpoolmanSpool::get_preset_name() { - auto name = getVendor()->name; - + string name; + if (getVendor()) + name += getVendor()->name; if (!m_filament_ptr->name.empty()) name += " " + m_filament_ptr->name; if (!m_filament_ptr->material.empty()) name += " " + m_filament_ptr->material; + boost::trim(name); return remove_special_key(name); } diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index 9324f938d4..4c7f310d94 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -220,6 +220,7 @@ public: std::string color; std::string preset_data; + // Can be nullptr SpoolmanVendorShrPtr m_vendor_ptr; void update_from_server(bool recursive = false); @@ -230,7 +231,8 @@ private: explicit SpoolmanFilament(const pt::ptree& json_data) : m_spoolman(Spoolman::m_instance) { - m_vendor_ptr = m_spoolman->m_vendors[json_data.get("vendor.id")]; + if (const auto vendor_id = json_data.get_optional("vendor.id"); vendor_id.has_value()) + m_vendor_ptr = m_spoolman->m_vendors[vendor_id.value()]; update_from_json(json_data); }; @@ -255,7 +257,8 @@ public: SpoolmanFilamentShrPtr m_filament_ptr; - SpoolmanVendorShrPtr& getVendor() { return m_filament_ptr->m_vendor_ptr; }; + // Can be nullptr + SpoolmanVendorShrPtr& getVendor() { return m_filament_ptr->m_vendor_ptr; } void update_from_server(bool recursive = false); From d7229356414bf719734c95d9b6286ec0c822e23d Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 3 Dec 2025 11:28:26 -0500 Subject: [PATCH 088/100] Update parsing of preset_data --- src/slic3r/Utils/Spoolman.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index cd2750f7ac..dcd1e46c54 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -478,9 +478,10 @@ void SpoolmanFilament::update_from_json(pt::ptree json_data) bed_temp = get_opt(json_data, "settings_bed_temp"); color = "#" + get_opt(json_data, "color_hex"); preset_data = get_opt(json_data, "extra.orcaslicer_preset_data"); - if (preset_data.front() == '"' && preset_data.back() == '"') - preset_data = preset_data.substr(1, preset_data.length() - 2); - boost::replace_all(preset_data, "\\\"", "\""); + if (!preset_data.empty()) { + boost::trim_if(preset_data, [](char c) { return c == '"'; }); + boost::replace_all(preset_data, "\\\"", "\""); + } } void SpoolmanFilament::apply_to_config(Slic3r::DynamicConfig& config) const From 7975838189cecdbedf227765d52ee2a33f42c533 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 3 Dec 2025 19:03:38 -0500 Subject: [PATCH 089/100] Rename getVendor --- src/slic3r/GUI/SpoolmanImportDialog.hpp | 2 +- src/slic3r/Utils/Spoolman.cpp | 8 ++++---- src/slic3r/Utils/Spoolman.hpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/SpoolmanImportDialog.hpp b/src/slic3r/GUI/SpoolmanImportDialog.hpp index 300bd0866a..81b2eee9f8 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.hpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.hpp @@ -23,7 +23,7 @@ public: int get_id() const { return m_spool->id; }; wxColour get_color() const { return wxColour(m_spool->m_filament_ptr->color); } - wxString get_vendor_name() const { return m_spool->getVendor() ? wxString::FromUTF8(m_spool->getVendor()->name) : wxString(); } + wxString get_vendor_name() const { return m_spool->get_vendor() ? wxString::FromUTF8(m_spool->get_vendor()->name) : wxString(); } wxString get_filament_name() const { return wxString::FromUTF8(m_spool->m_filament_ptr->name); } wxString get_material() const { return wxString::FromUTF8(m_spool->m_filament_ptr->material); } bool is_archived() const { return m_spool->archived; } diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index dcd1e46c54..62307f3054 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -535,16 +535,16 @@ void SpoolmanSpool::update_from_server(bool recursive) update_from_json(json_data); if (recursive) { m_filament_ptr->update_from_json(json_data.get_child("filament")); - if (getVendor()) - getVendor()->update_from_json(json_data.get_child("filament.vendor")); + if (get_vendor()) + get_vendor()->update_from_json(json_data.get_child("filament.vendor")); } } std::string SpoolmanSpool::get_preset_name() { string name; - if (getVendor()) - name += getVendor()->name; + if (get_vendor()) + name += get_vendor()->name; if (!m_filament_ptr->name.empty()) name += " " + m_filament_ptr->name; if (!m_filament_ptr->material.empty()) diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index 4c7f310d94..a051e0eb4e 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -258,7 +258,7 @@ public: SpoolmanFilamentShrPtr m_filament_ptr; // Can be nullptr - SpoolmanVendorShrPtr& getVendor() { return m_filament_ptr->m_vendor_ptr; } + SpoolmanVendorShrPtr& get_vendor() { return m_filament_ptr->m_vendor_ptr; } void update_from_server(bool recursive = false); From 908f4de8a79b9937dc7a28d46b39f8a95a7933d4 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 3 Dec 2025 19:04:43 -0500 Subject: [PATCH 090/100] Rename vendor and filament pointers --- src/slic3r/GUI/SpoolmanDialog.cpp | 2 +- src/slic3r/GUI/SpoolmanImportDialog.hpp | 6 ++--- src/slic3r/GUI/Tab.cpp | 4 +-- src/slic3r/Utils/Spoolman.cpp | 36 ++++++++++++------------- src/slic3r/Utils/Spoolman.hpp | 10 +++---- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/slic3r/GUI/SpoolmanDialog.cpp b/src/slic3r/GUI/SpoolmanDialog.cpp index c11f90f165..4501269b0e 100644 --- a/src/slic3r/GUI/SpoolmanDialog.cpp +++ b/src/slic3r/GUI/SpoolmanDialog.cpp @@ -38,7 +38,7 @@ SpoolInfoWidget::SpoolInfoWidget(wxWindow* parent, const Preset* preset) : wxPan if (preset->spoolman_enabled()) { auto spool = Spoolman::get_instance()->get_spoolman_spool_by_id(preset->config.opt_int("spoolman_spool_id", 0)); m_remaining_weight_label->SetLabelText( - format("%1% g / %2% g", double_to_string(spool->remaining_weight, 2), double_to_string(spool->m_filament_ptr->weight, 2))); + format("%1% g / %2% g", double_to_string(spool->remaining_weight, 2), double_to_string(spool->filament->weight, 2))); } else { m_remaining_weight_label->SetLabelText(_L("Not Spoolman enabled")); m_remaining_weight_label->SetForegroundColour(*wxRED); diff --git a/src/slic3r/GUI/SpoolmanImportDialog.hpp b/src/slic3r/GUI/SpoolmanImportDialog.hpp index 81b2eee9f8..1cc102d930 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.hpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.hpp @@ -22,10 +22,10 @@ public: explicit SpoolmanNode(const SpoolmanSpoolShrPtr& spool) : m_spool(spool) {} int get_id() const { return m_spool->id; }; - wxColour get_color() const { return wxColour(m_spool->m_filament_ptr->color); } + wxColour get_color() const { return wxColour(m_spool->filament->color); } wxString get_vendor_name() const { return m_spool->get_vendor() ? wxString::FromUTF8(m_spool->get_vendor()->name) : wxString(); } - wxString get_filament_name() const { return wxString::FromUTF8(m_spool->m_filament_ptr->name); } - wxString get_material() const { return wxString::FromUTF8(m_spool->m_filament_ptr->material); } + wxString get_filament_name() const { return wxString::FromUTF8(m_spool->filament->name); } + wxString get_material() const { return wxString::FromUTF8(m_spool->filament->material); } bool is_archived() const { return m_spool->archived; } bool get_checked() { return m_checked; }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 2e2bb4f2e5..6e30982477 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4130,12 +4130,12 @@ void TabFilament::build() auto spool = Spoolman::get_instance()->get_spoolman_spool_by_id( m_presets->get_selected_preset().config.opt_int("spoolman_spool_id", 0)); - if (spool->m_filament_ptr->preset_data.empty()) { + if (spool->filament->preset_data.empty()) { show_error(this, "The Spoolman filament does not contain any preset data."); return; } - auto config = spool->m_filament_ptr->get_config_from_preset_data(); + auto config = spool->filament->get_config_from_preset_data(); if (config.empty()) { show_error(this, "The stored preset data is invalid."); return; diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 62307f3054..2df0bf400f 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -243,7 +243,7 @@ SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolSh } // Check if the material types match between the base preset and the spool - if (base_preset->config.opt_string("filament_type", 0) != spool->m_filament_ptr->material) { + if (base_preset->config.opt_string("filament_type", 0) != spool->filament->material) { result.messages.emplace_back(_u8L("The materials of the base preset and the Spoolman spool do not match")); } } @@ -341,7 +341,7 @@ SpoolmanResult Spoolman::save_preset_to_spoolman(const Preset* filament_preset) if (res.empty()) result.messages.emplace_back("Failed to save the data"); else - spool->m_filament_ptr->preset_data = std::move(preset_data); + spool->filament->preset_data = std::move(preset_data); return result; } @@ -451,20 +451,20 @@ void SpoolmanFilament::update_from_server(bool recursive) { const boost::property_tree::ptree& json_data = Spoolman::get_spoolman_json("filament/" + std::to_string(id)); update_from_json(json_data); - if (recursive && m_vendor_ptr) - m_vendor_ptr->update_from_json(json_data.get_child("vendor")); + if (recursive && vendor) + vendor->update_from_json(json_data.get_child("vendor")); } void SpoolmanFilament::update_from_json(pt::ptree json_data) { auto vendor_id = json_data.get_optional("vendor.id"); - if (m_vendor_ptr && !vendor_id.has_value()) { - m_vendor_ptr = nullptr; - } else if (vendor_id.has_value() && (!m_vendor_ptr || m_vendor_ptr->id != vendor_id.get())) { + if (vendor && !vendor_id.has_value()) { + vendor = nullptr; + } else if (vendor_id.has_value() && (!vendor || vendor->id != vendor_id.get())) { auto val = vendor_id.get(); if (!m_spoolman->m_vendors.count(val)) m_spoolman->m_vendors.emplace(val, make_shared(SpoolmanVendor(json_data.get_child("vendor")))); - m_vendor_ptr = m_spoolman->m_vendors[val]; + vendor = m_spoolman->m_vendors[val]; } id = json_data.get("id"); name = get_opt(json_data, "name"); @@ -499,8 +499,8 @@ void SpoolmanFilament::apply_to_config(Slic3r::DynamicConfig& config) const config.set_key_value("hot_plate_temp", new ConfigOptionInts({bed_temp})); } config.set_key_value("default_filament_colour", new ConfigOptionStrings{color}); - if (m_vendor_ptr) - m_vendor_ptr->apply_to_config(config); + if (vendor) + vendor->apply_to_config(config); } DynamicPrintConfig SpoolmanFilament::get_config_from_preset_data() const @@ -534,7 +534,7 @@ void SpoolmanSpool::update_from_server(bool recursive) const boost::property_tree::ptree& json_data = Spoolman::get_spoolman_json("spool/" + std::to_string(id)); update_from_json(json_data); if (recursive) { - m_filament_ptr->update_from_json(json_data.get_child("filament")); + filament->update_from_json(json_data.get_child("filament")); if (get_vendor()) get_vendor()->update_from_json(json_data.get_child("filament.vendor")); } @@ -545,10 +545,10 @@ std::string SpoolmanSpool::get_preset_name() string name; if (get_vendor()) name += get_vendor()->name; - if (!m_filament_ptr->name.empty()) - name += " " + m_filament_ptr->name; - if (!m_filament_ptr->material.empty()) - name += " " + m_filament_ptr->material; + if (!filament->name.empty()) + name += " " + filament->name; + if (!filament->material.empty()) + name += " " + filament->material; boost::trim(name); return remove_special_key(name); @@ -557,7 +557,7 @@ std::string SpoolmanSpool::get_preset_name() void SpoolmanSpool::apply_to_config(Slic3r::DynamicConfig& config) const { config.set_key_value("spoolman_spool_id", new ConfigOptionInts({id})); - m_filament_ptr->apply_to_config(config); + filament->apply_to_config(config); } void SpoolmanSpool::apply_to_preset(Preset* preset, bool only_update_statistics) const @@ -575,10 +575,10 @@ void SpoolmanSpool::apply_to_preset(Preset* preset, bool only_update_statistics) void SpoolmanSpool::update_from_json(pt::ptree json_data) { - if (int filament_id = json_data.get("filament.id"); m_filament_ptr && m_filament_ptr->id != filament_id) { + if (int filament_id = json_data.get("filament.id"); filament && filament->id != filament_id) { if (!m_spoolman->m_filaments.count(filament_id)) m_spoolman->m_filaments.emplace(filament_id, make_shared(SpoolmanFilament(json_data.get_child("filament")))); - m_filament_ptr = m_spoolman->m_filaments.at(filament_id); + filament = m_spoolman->m_filaments.at(filament_id); } id = json_data.get("id"); remaining_weight = get_opt(json_data, "remaining_weight"); diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index a051e0eb4e..02717ec7bc 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -221,7 +221,7 @@ public: std::string preset_data; // Can be nullptr - SpoolmanVendorShrPtr m_vendor_ptr; + SpoolmanVendorShrPtr vendor; void update_from_server(bool recursive = false); DynamicPrintConfig get_config_from_preset_data() const; @@ -232,7 +232,7 @@ private: explicit SpoolmanFilament(const pt::ptree& json_data) : m_spoolman(Spoolman::m_instance) { if (const auto vendor_id = json_data.get_optional("vendor.id"); vendor_id.has_value()) - m_vendor_ptr = m_spoolman->m_vendors[vendor_id.value()]; + vendor = m_spoolman->m_vendors[vendor_id.value()]; update_from_json(json_data); }; @@ -255,10 +255,10 @@ public: double used_length; bool archived; - SpoolmanFilamentShrPtr m_filament_ptr; + SpoolmanFilamentShrPtr filament; // Can be nullptr - SpoolmanVendorShrPtr& get_vendor() { return m_filament_ptr->m_vendor_ptr; } + SpoolmanVendorShrPtr& get_vendor() { return filament->vendor; } void update_from_server(bool recursive = false); @@ -273,7 +273,7 @@ private: explicit SpoolmanSpool(const pt::ptree& json_data) : m_spoolman(Spoolman::m_instance) { - m_filament_ptr = m_spoolman->m_filaments[json_data.get("filament.id")]; + filament = m_spoolman->m_filaments[json_data.get("filament.id")]; update_from_json(json_data); } From f0db919dd028952c9bd357347b84526644db65fb Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 3 Dec 2025 20:08:43 -0500 Subject: [PATCH 091/100] Improve dark mode code --- src/slic3r/GUI/SpoolmanDialog.cpp | 17 +++-------------- src/slic3r/GUI/SpoolmanImportDialog.cpp | 7 +------ 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/src/slic3r/GUI/SpoolmanDialog.cpp b/src/slic3r/GUI/SpoolmanDialog.cpp index 4501269b0e..33c94f6f34 100644 --- a/src/slic3r/GUI/SpoolmanDialog.cpp +++ b/src/slic3r/GUI/SpoolmanDialog.cpp @@ -19,6 +19,7 @@ static BitmapCache cache; SpoolInfoWidget::SpoolInfoWidget(wxWindow* parent, const Preset* preset) : wxPanel(parent, wxID_ANY), m_preset(preset) { + this->SetBackgroundColour(*wxWHITE); auto main_sizer = new wxStaticBoxSizer(new LabeledStaticBox(this), wxVERTICAL); auto bitmap = cache.load_svg("spool", EM * 10, EM * 10, false, false, @@ -30,8 +31,6 @@ SpoolInfoWidget::SpoolInfoWidget(wxWindow* parent, const Preset* preset) : wxPan m_preset_name_label = new Label(this, wxString::FromUTF8(preset->name)); m_preset_name_label->SetFont(Label::Body_12); - m_preset_name_label->SetBackgroundColour(*wxWHITE); - wxGetApp().UpdateDarkUI(m_preset_name_label); main_sizer->Add(m_preset_name_label, 0, wxALIGN_CENTER_HORIZONTAL | wxDOWN | wxLEFT | wxRIGHT, EM); m_remaining_weight_label = new Label(this); @@ -44,10 +43,9 @@ SpoolInfoWidget::SpoolInfoWidget(wxWindow* parent, const Preset* preset) : wxPan m_remaining_weight_label->SetForegroundColour(*wxRED); } m_remaining_weight_label->SetFont(Label::Body_12); - m_remaining_weight_label->SetBackgroundColour(*wxWHITE); - wxGetApp().UpdateDarkUI(m_remaining_weight_label); main_sizer->Add(m_remaining_weight_label, 0, wxALIGN_CENTER_HORIZONTAL | wxDOWN | wxLEFT | wxRIGHT, EM); + wxGetApp().UpdateDarkUIWin(this); this->SetSizer(main_sizer); } @@ -62,13 +60,7 @@ void SpoolInfoWidget::rescale() SpoolmanDialog::SpoolmanDialog(wxWindow* parent) : DPIDialog(parent, wxID_ANY, _L("Spoolman"), wxDefaultPosition, {-1, 45 * EM}, wxDEFAULT_DIALOG_STYLE) { -#ifdef _WIN32 this->SetBackgroundColour(*wxWHITE); - wxGetApp().UpdateDarkUI(this); - wxGetApp().UpdateDlgDarkUI(this); -#else - wxWindow::SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); -#endif auto window_sizer = new wxBoxSizer(wxVERTICAL); window_sizer->SetMinSize({wxDefaultCoord, 45 * EM}); @@ -76,8 +68,6 @@ SpoolmanDialog::SpoolmanDialog(wxWindow* parent) // Main panel m_main_panel = new wxPanel(this); auto main_panel_sizer = new wxBoxSizer(wxVERTICAL); - m_main_panel->SetBackgroundColour(*wxWHITE); - wxGetApp().UpdateDarkUI(m_main_panel); m_main_panel->SetSizer(main_panel_sizer); m_config = new SpoolmanDynamicConfig(wxGetApp().app_config); @@ -117,8 +107,6 @@ SpoolmanDialog::SpoolmanDialog(wxWindow* parent) // Loading Panel m_loading_panel = new wxPanel(this); auto loading_panel_sizer = new wxBoxSizer(wxHORIZONTAL); - m_loading_panel->SetBackgroundColour(*wxWHITE); - wxGetApp().UpdateDarkUI(m_loading_panel); m_loading_panel->SetSizer(loading_panel_sizer); m_loading_panel->SetMinSize({main_panel_sizer->CalcMin().GetWidth(), wxDefaultCoord}); loading_panel_sizer->AddStretchSpacer(1); @@ -140,6 +128,7 @@ SpoolmanDialog::SpoolmanDialog(wxWindow* parent) this->Bind(EVT_FINISH_LOADING, &SpoolmanDialog::OnFinishLoading, this); + wxGetApp().UpdateDlgDarkUI(this); this->ShowModal(); } diff --git a/src/slic3r/GUI/SpoolmanImportDialog.cpp b/src/slic3r/GUI/SpoolmanImportDialog.cpp index 628df1d32b..5a67c846f8 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.cpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.cpp @@ -129,13 +129,7 @@ SpoolmanViewCtrl::SpoolmanViewCtrl(wxWindow* parent) : wxDataViewCtrl(parent, wx SpoolmanImportDialog::SpoolmanImportDialog(wxWindow* parent) : DPIDialog(parent, wxID_ANY, _L("Import from Spoolman"), wxDefaultPosition, {-1, 45 * EM}, wxDEFAULT_DIALOG_STYLE) { -#ifdef _WIN32 this->SetBackgroundColour(*wxWHITE); - wxGetApp().UpdateDarkUI(this); - wxGetApp().UpdateDlgDarkUI(this); -#else - SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); -#endif if (!Spoolman::is_server_valid()) { show_error(parent, "Failed to get data from the Spoolman server. Make sure that the port is correct and the server is running."); @@ -225,6 +219,7 @@ SpoolmanImportDialog::SpoolmanImportDialog(wxWindow* parent) main_sizer->SetMinSize({-1, 45 * EM}); this->SetSizerAndFit(main_sizer); + wxGetApp().UpdateDlgDarkUI(this); this->ShowModal(); } From 0d2b10ad2dcc03c7ba16ce8c67af049a711046a3 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 3 Dec 2025 20:47:57 -0500 Subject: [PATCH 092/100] Add opt "handles_spoolman_consumption" Allows you to specify that the printer handles Spoolman consumption on its own. This will bypass the consumption dialog for that printer when enabled. --- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 6 ++++++ src/slic3r/GUI/Plater.cpp | 4 +++- src/slic3r/GUI/Tab.cpp | 3 +++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 281247ca84..e3884a11dc 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1020,7 +1020,7 @@ static std::vector s_Preset_printer_options { "cooling_tube_retraction", "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "purge_in_prime_tower", "enable_filament_ramming", "z_offset", - "disable_m73", "preferred_orientation", "emit_machine_limits_to_gcode", "pellet_modded_printer", "support_multi_bed_types", "default_bed_type", "bed_mesh_min","bed_mesh_max","bed_mesh_probe_distance", "adaptive_bed_mesh_margin", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut", + "disable_m73", "preferred_orientation", "emit_machine_limits_to_gcode", "pellet_modded_printer", "support_multi_bed_types", "handles_spoolman_consumption", "default_bed_type", "bed_mesh_min","bed_mesh_max","bed_mesh_probe_distance", "adaptive_bed_mesh_margin", "enable_long_retraction_when_cut","long_retractions_when_cut","retraction_distances_when_cut", "bed_temperature_formula", "nozzle_flush_dataset" }; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 9570c75e57..98793e4e2e 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3545,6 +3545,12 @@ void PrintConfigDef::init_fff_params() def->mode = comSimple; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("handles_spoolman_consumption", coBool); + def->label = L("Handles Spoolman consumption"); + def->tooltip = L("Indicates that the printer will handle sending consumption requests to Spoolman"); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("gcode_label_objects", coBool); def->label = L("Label objects"); def->tooltip = L("Enable this to add comments into the G-code labeling print moves with what object they belong to," diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e494316451..d1c60839ab 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4420,7 +4420,7 @@ 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, + // If Spoolman is active, the printer does not handle its own consumption, 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 spoolman_consumption_dialog(int plate_idx); @@ -9256,6 +9256,8 @@ void Plater::priv::spoolman_consumption_dialog(int plate_idx) static constexpr auto show_dlg_key = "show_spoolman_consumption_dialog"; if (!wxGetApp().app_config->get_bool(show_dlg_key)) return; + if (wxGetApp().preset_bundle->printers.get_edited_preset().config.opt_bool("handles_spoolman_consumption")) + return; if (!Spoolman::is_server_valid()) return; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6e30982477..a92a1100b5 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4498,6 +4498,7 @@ void TabPrinter::build_fff() optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); optgroup->append_single_option_line("printer_structure"); optgroup->append_single_option_line("gcode_flavor"); + optgroup->append_single_option_line("handles_spoolman_consumption"); optgroup->append_single_option_line("pellet_modded_printer", "pellet-flow-coefficient"); optgroup->append_single_option_line("bbl_use_printhost"); optgroup->append_single_option_line("scan_first_layer"); @@ -5332,6 +5333,8 @@ void TabPrinter::toggle_options() // SoftFever: hide non-BBL settings for (auto el : {"use_firmware_retraction", "use_relative_e_distances", "support_multi_bed_types", "pellet_modded_printer", "bed_mesh_max", "bed_mesh_min", "bed_mesh_probe_distance", "adaptive_bed_mesh_margin", "thumbnails"}) toggle_line(el, !is_BBL_printer); + + toggle_line("handles_spoolman_consumption", Spoolman::is_enabled()); } if (m_active_page->title() == L("Machine G-code")) { From c7f7dd4aa3230d15f8a4f146f1af814d5ff83f58 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 3 Dec 2025 20:54:59 -0500 Subject: [PATCH 093/100] Add reset option for "Do not show again" on Spoolman consumption dialog --- src/slic3r/GUI/Preferences.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 7d36d30f8b..3fa6a427dc 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1297,6 +1297,11 @@ void PreferencesDialog::create_items() }); g_sizer->Add(item_restore_hide_pop_ups); + auto item_restore_spoolman_consumption_dialog = create_item_button(_L("Spoolman consumption"), _L("Clear"), L"", _L("Clear my choice for hiding the Spoolman consumption dialog."), []() { + wxGetApp().app_config->set_bool("show_spoolman_consumption_dialog", true); + }); + g_sizer->Add(item_restore_spoolman_consumption_dialog); + g_sizer->AddSpacer(FromDIP(10)); sizer_page->Add(g_sizer, 0, wxEXPAND); From ffcbabd6e6d07a0d557ef63412e9f1aadddd9180 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Thu, 4 Dec 2025 14:05:30 -0500 Subject: [PATCH 094/100] Don't include unused spools in consumption dialog --- src/libslic3r/Print.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 2a7d748458..1415b01574 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -3553,18 +3553,19 @@ std::vector Print::get_spoolman_filament_co } // 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) { + auto get_used_filament_from_volume = [&](const int& extruder_id) -> std::optional> { // confirm the item exists in the stats map if (m_print_statistics.filament_stats.count(extruder_id) <= 0) - return std::pair {0., 0.}; + return {}; 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 }; + return std::make_optional(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)); + if (auto usage_opt = get_used_filament_from_volume(idx); usage_opt.has_value()) + spoolman_filament_consumption.emplace_back(idx, m_config, usage_opt.value()); return spoolman_filament_consumption; } From bc050dd1c704bbab8ea0f0347b1234515a513399 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Mon, 8 Dec 2025 15:24:37 -0500 Subject: [PATCH 095/100] Update saving preset to Spoolman Base the saved preset on a system preset --- src/libslic3r/Config.cpp | 16 ++++++++++------ src/libslic3r/Config.hpp | 2 +- src/libslic3r/Preset.cpp | 33 ++++++++++++++++++++++----------- src/libslic3r/Preset.hpp | 6 ++++-- src/slic3r/Utils/Spoolman.cpp | 19 +++++++++++++------ 5 files changed, 50 insertions(+), 26 deletions(-) diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index f6dc07fa7d..cddbe698d4 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -1451,7 +1451,7 @@ ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, Fo } //BBS: add json support -void ConfigBase::save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const +json ConfigBase::save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const { json j; //record the headers @@ -1486,12 +1486,16 @@ void ConfigBase::save_to_json(const std::string &file, const std::string &name, } } - boost::nowide::ofstream c; - c.open(file, std::ios::out | std::ios::trunc); - c << std::setw(4) << j << std::endl; - c.close(); + if (!file.empty()) { + boost::nowide::ofstream c; + c.open(file, std::ios::out | std::ios::trunc); + c << std::setw(4) << j << std::endl; + c.close(); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", saved config to %1%\n")%file; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", saved config to %1%\n")%file; + } + + return j; } void ConfigBase::save(const std::string &file) const diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 876eb3dd01..94f0ce5386 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -2617,7 +2617,7 @@ public: void save(const std::string &file) const; //BBS: add json support - void save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const; + nlohmann::json save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const; // Set all the nullable values to nils. void null_nullables(); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index e3884a11dc..7386bbf075 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -512,7 +512,7 @@ void Preset::load_info(const std::string& file) } } -void Preset::save_info(std::string file) +void Preset::save_info(std::string file) const { //BBS: add project embedded preset logic if (this->is_project_embedded) @@ -551,7 +551,7 @@ void Preset::remove_files() } //BBS: add logic for only difference save -void Preset::save(const DynamicPrintConfig* parent_config) +void Preset::save(const DynamicPrintConfig* parent_config, json* output /*= nullptr*/) const { //BBS: add project embedded preset logic if (this->is_project_embedded) @@ -568,7 +568,14 @@ void Preset::save(const DynamicPrintConfig* parent_config) else from_str = std::string("Default"); - boost::filesystem::create_directories(fs::path(this->file).parent_path()); + json j; + + // If an empty string is passed as the file path, the preset is not outputted to a file + std::string file_str; + if (!output) { + file_str = this->file; + boost::filesystem::create_directories(fs::path(file_str).parent_path()); + } //BBS: only save difference if it has parent if (parent_config) { @@ -588,12 +595,12 @@ void Preset::save(const DynamicPrintConfig* parent_config) for (auto option: dirty_options) { - ConfigOption *opt_src = config.option(option); + const ConfigOption *opt_src = config.option(option); ConfigOption *opt_dst = temp_config.option(option, true); if (opt_dst->is_scalar() || !(opt_dst->nullable())) opt_dst->set(opt_src); else { - ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src); + const ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src); ConfigOptionVectorBase* opt_vec_dst = static_cast(opt_dst); const ConfigOptionVectorBase* opt_vec_inherit = static_cast(parent_config->option(option)); if (opt_vec_src->size() == 1) @@ -608,19 +615,23 @@ void Preset::save(const DynamicPrintConfig* parent_config) opt_dst->set(opt_src); } } - temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string()); + j = temp_config.save_to_json(file_str, this->name, from_str, this->version.to_string()); } else if (!filament_id.empty() && inherits().empty()) { DynamicPrintConfig temp_config = config; temp_config.set_key_value(BBL_JSON_KEY_FILAMENT_ID, new ConfigOptionString(filament_id)); - temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string()); + j = temp_config.save_to_json(file_str, this->name, from_str, this->version.to_string()); } else { - this->config.save_to_json(this->file, this->name, from_str, this->version.to_string()); + j = this->config.save_to_json(file_str, this->name, from_str, this->version.to_string()); } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " save config for: " << this->name << " and filament_id: " << filament_id << " and base_id: " << this->base_id; - fs::path idx_file(this->file); - idx_file.replace_extension(".info"); - this->save_info(idx_file.string()); + if (output) { + *output = std::move(j); + } else { + fs::path idx_file(this->file); + idx_file.replace_extension(".info"); + this->save_info(idx_file.string()); + } } void Preset::reload(Preset const &parent) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 273313bb47..22349f1a29 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -14,6 +14,7 @@ #include "PrintConfig.hpp" #include "Semver.hpp" #include "ProjectTask.hpp" +#include "nlohmann/json.hpp" //BBS: change system directories #define PRESET_SYSTEM_DIR "system" @@ -293,12 +294,13 @@ public: static std::string get_iot_type_string(Preset::Type type); static Preset::Type get_type_from_string(std::string type_str); void load_info(const std::string& file); - void save_info(std::string file = ""); + void save_info(std::string file = "") const; void remove_files(); //BBS: add logic for only difference save //if parent_config is null, save all keys, otherwise, only save difference - void save(const DynamicPrintConfig* parent_config); + //if output is null, save the data to file, otherwise, save the data to output + void save(const DynamicPrintConfig* parent_config, nlohmann::json* output = nullptr) const; void reload(Preset const & parent); // Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty. diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 2df0bf400f..e27e829694 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -321,13 +321,20 @@ SpoolmanResult Spoolman::save_preset_to_spoolman(const Preset* filament_preset) return result; } - boost::nowide::ifstream fs(filament_preset->file); + auto& filaments = wxGetApp().preset_bundle->filaments; + // get the first preset that is a system preset or base user preset in the inheritance hierarchy + const DynamicPrintConfig* base_config = nullptr; + if (!filament_preset->inherits().empty()) + if (auto base = filaments.get_preset_base(*filament_preset)) + base_config = &base->config; + + json j; + filament_preset->save(base_config, &j); + + std::stringstream ss; + ss << j; + std::string preset_data = ss.str(); - std::string preset_data; - std::string line; - while (std::getline(fs, line)) { - preset_data += line; - } // Spoolman extra fields are a string read as json // To save a string to an extra field, the data must be surrounded by double quotes // and literal quotes must be escaped twice From 1e6fc856210420df70b74103db3554a1ac377e76 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Mon, 8 Dec 2025 16:29:22 -0500 Subject: [PATCH 096/100] Use preset data for SpoolmanImportDialog --- src/slic3r/GUI/SpoolmanImportDialog.cpp | 13 ++- src/slic3r/GUI/SpoolmanImportDialog.hpp | 4 +- src/slic3r/GUI/Tab.cpp | 17 ++-- src/slic3r/Utils/Spoolman.cpp | 108 +++++++++++++++++------- src/slic3r/Utils/Spoolman.hpp | 9 +- 5 files changed, 110 insertions(+), 41 deletions(-) diff --git a/src/slic3r/GUI/SpoolmanImportDialog.cpp b/src/slic3r/GUI/SpoolmanImportDialog.cpp index 5a67c846f8..4873e56eaa 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.cpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.cpp @@ -76,6 +76,7 @@ void SpoolmanViewModel::GetValue(wxVariant& variant, const wxDataViewItem& item, case COL_VENDOR: variant = node->get_vendor_name(); break; case COL_NAME: variant = node->get_filament_name(); break; case COL_MATERIAL: variant = node->get_material(); break; + case COL_PRESET_DATA: variant = wxString(node->get_has_preset_data() ? L"\u2713" : L"\u2715"); break; default: wxLogError("Out of bounds column call to SpoolmanViewModel::GetValue. col = %d", col); } } @@ -115,6 +116,7 @@ SpoolmanViewCtrl::SpoolmanViewCtrl(wxWindow* parent) : wxDataViewCtrl(parent, wx this->AppendTextColumn("Vendor", COL_VENDOR, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_NOT, wxCOL_SORTABLE); this->AppendTextColumn("Name", COL_NAME, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_NOT, wxCOL_SORTABLE); this->AppendTextColumn("Material", COL_MATERIAL, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_NOT, wxCOL_SORTABLE); + this->AppendTextColumn("Preset Data", COL_PRESET_DATA, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE, wxALIGN_CENTER, wxCOL_SORTABLE); // fake column to put the expander in auto temp_col = this->AppendTextColumn("", 100); @@ -155,10 +157,16 @@ SpoolmanImportDialog::SpoolmanImportDialog(wxWindow* parent) m_preset_combobox->update(); // Detach Checkbox + auto checkbox_sizer = new wxBoxSizer(wxVERTICAL); m_detach_checkbox = new wxCheckBox(this, wxID_ANY, _L("Save as Detached")); m_detach_checkbox->SetToolTip(_L("Save as a standalone preset")); - preset_sizer->Add(m_detach_checkbox, 0, wxALIGN_CENTER_VERTICAL); + checkbox_sizer->Add(m_detach_checkbox, 0, wxALIGN_CENTER_HORIZONTAL); + m_ignore_preset_data_checkbox = new wxCheckBox(this, wxID_ANY, _L("Ignore Included Preset")); + m_ignore_preset_data_checkbox->SetToolTip(_L("Ignore the preset data stored in Spoolman and use the selected base preset instead")); + checkbox_sizer->Add(m_ignore_preset_data_checkbox, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, EM); + + preset_sizer->Add(checkbox_sizer, 0, wxALIGN_CENTER_VERTICAL); main_sizer->Add(preset_sizer, 0, wxEXPAND | wxALL, EM); auto buttons = new DialogButtons(this, {"All", "None", "Import", "Cancel"}, _L("Import")); @@ -246,6 +254,7 @@ void SpoolmanImportDialog::on_import() } const bool detach = m_detach_checkbox->GetValue(); + const bool ignore_preset_data = m_ignore_preset_data_checkbox->GetValue(); std::vector> failed_spools; auto create_presets = [&](const vector& spools, bool force = false) { @@ -255,7 +264,7 @@ void SpoolmanImportDialog::on_import() std::vector threads; for (const auto& spool : spools) { threads.emplace_back(Slic3r::create_thread([&] { - auto res = Spoolman::create_filament_preset_from_spool(spool, current_preset, detach, force); + auto res = Spoolman::create_filament_preset_from_spool(spool, current_preset, !ignore_preset_data, detach, force); if (res.has_failed()) failed_spools.emplace_back(spool, res); })); diff --git a/src/slic3r/GUI/SpoolmanImportDialog.hpp b/src/slic3r/GUI/SpoolmanImportDialog.hpp index 1cc102d930..cc46d3f70b 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.hpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.hpp @@ -10,7 +10,7 @@ namespace Slic3r { namespace GUI { class SpoolmanViewCtrl; -enum Column { COL_CHECK = 0, COL_ID, COL_COLOR, COL_VENDOR, COL_NAME, COL_MATERIAL, COL_COUNT }; +enum Column { COL_CHECK = 0, COL_ID, COL_COLOR, COL_VENDOR, COL_NAME, COL_MATERIAL, COL_PRESET_DATA, COL_COUNT }; //----------------------------------------- // SpoolmanNode @@ -26,6 +26,7 @@ public: wxString get_vendor_name() const { return m_spool->get_vendor() ? wxString::FromUTF8(m_spool->get_vendor()->name) : wxString(); } wxString get_filament_name() const { return wxString::FromUTF8(m_spool->filament->name); } wxString get_material() const { return wxString::FromUTF8(m_spool->filament->material); } + bool get_has_preset_data() const { return !m_spool->filament->preset_data.empty(); } bool is_archived() const { return m_spool->archived; } bool get_checked() { return m_checked; }; @@ -135,6 +136,7 @@ protected: SpoolmanViewCtrl* m_svc; TabPresetComboBox* m_preset_combobox; wxCheckBox* m_detach_checkbox; + wxCheckBox* m_ignore_preset_data_checkbox; }; }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a92a1100b5..56fb00e6ab 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4128,28 +4128,31 @@ void TabFilament::build() return; } + auto selected_preset = m_presets->get_selected_preset(); + auto edited_preset = m_presets->get_edited_preset(); auto spool = Spoolman::get_instance()->get_spoolman_spool_by_id( - m_presets->get_selected_preset().config.opt_int("spoolman_spool_id", 0)); + selected_preset.config.opt_int("spoolman_spool_id", 0)); + if (spool->filament->preset_data.empty()) { show_error(this, "The Spoolman filament does not contain any preset data."); return; } - auto config = spool->filament->get_config_from_preset_data(); - if (config.empty()) { + const auto success = spool->filament->get_config_from_preset_data(edited_preset.config); + if (!success) { show_error(this, "The stored preset data is invalid."); return; } // Do not change preset name in this operation - auto current_preset_name = m_presets->get_selected_preset().config.opt_string("filament_settings_id", 0u); - config.set_key_value("filament_settings_id", new ConfigOptionStrings({current_preset_name})); + auto current_preset_name = selected_preset.config.opt_string("filament_settings_id", 0u); + edited_preset.config.set_key_value("filament_settings_id", new ConfigOptionStrings({current_preset_name})); // Apply spool configuration changes - spool->apply_to_config(config); + spool->apply_to_config(edited_preset.config); // Load config changes into the tab - this->load_config(config); + this->load_config(edited_preset.config); }); load_from_spoolman_btn->SetStyle(ButtonStyle::Regular, ButtonType::Parameter); sizer->Add(load_from_spoolman_btn); diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index e27e829694..a74e6dff9a 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -191,12 +191,42 @@ bool Spoolman::undo_use_spoolman_spools() SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_preset, + bool use_preset_data, bool detach, bool force) { PresetCollection& filaments = wxGetApp().preset_bundle->filaments; SpoolmanResult result; + DynamicPrintConfig preset_data_config; + std::map additional_preset_data; + if (use_preset_data) { + // If the preset data is empty, use the base_preset + if (spool->filament->preset_data.empty()) { + use_preset_data = false; + } else { + if (spool->filament->get_config_from_preset_data(preset_data_config, &additional_preset_data)) { + if (const auto& inherits = preset_data_config.opt_string("inherits"); inherits.empty()) { + // If inherits is empty, the profile data is detached. When use_preset_data, + // the base_preset is not important, so just provide the default preset + base_preset = &filaments.default_preset(); + } else { + if (const auto preset_data_base_preset = filaments.find_preset(inherits)) { + base_preset = preset_data_base_preset; + } else { + if (!force) + result.messages.emplace_back(_u8L("The provided preset data inherits from a non-existing preset.")); + use_preset_data = false; + } + } + } else { + if (!force) + result.messages.emplace_back(_u8L("There was an error parsing the preset data for the spool")); + use_preset_data = false; + } + } + } + if (!base_preset) base_preset = &filaments.get_edited_preset(); @@ -213,17 +243,12 @@ SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolSh Preset* preset = filaments.find_preset(filament_preset_name); - if (force) { - if (preset && !preset->is_user()) - result.messages.emplace_back(_u8L("A system preset exists with the same name and cannot be overwritten")); - } else { + if (preset && !preset->is_user()) + result.messages.emplace_back(_u8L("A system preset exists with the same name and cannot be overwritten")); + if (!force) { // Check if a preset with the same name already exists - if (preset) { - if (preset->is_user()) - result.messages.emplace_back(_u8L("Preset already exists with the same name")); - else - result.messages.emplace_back(_u8L("A system preset exists with the same name and cannot be overwritten")); - } + if (preset && preset->is_user()) + result.messages.emplace_back(_u8L("Preset already exists with the same name")); // Check for presets with the same spool ID int compatible(0); @@ -243,7 +268,7 @@ SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolSh } // Check if the material types match between the base preset and the spool - if (base_preset->config.opt_string("filament_type", 0) != spool->filament->material) { + if (!use_preset_data && base_preset->config.opt_string("filament_type", 0) != spool->filament->material) { result.messages.emplace_back(_u8L("The materials of the base preset and the Spoolman spool do not match")); } } @@ -251,23 +276,41 @@ SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolSh if (result.has_failed()) return result; - // get the first preset that is a system preset or base user preset in the inheritance hierarchy - std::string inherits; - if (!detach) { + // Begin building the new preset + preset = new Preset(Preset::TYPE_FILAMENT, filament_preset_name); + + // Apply the base config + if (use_preset_data) { + preset->config.apply(preset_data_config); + } else { + preset->config.apply(base_preset->config); + } + + // Get the first preset that is a system preset or base user preset in the inheritance hierarchy + // preset data should already be based on a system preset + if (!use_preset_data && !detach) { + std::string inherits; if (const auto base = filaments.get_preset_base(*base_preset)) inherits = base->name; else // fallback if the above operation fails inherits = base_preset->name; + preset->config.set("inherits", inherits, true); } - preset = new Preset(Preset::TYPE_FILAMENT, filament_preset_name); - preset->config.apply(base_preset->config); preset->config.set_key_value("filament_settings_id", new ConfigOptionStrings({filament_preset_name})); - preset->config.set("inherits", inherits, true); - spool->apply_to_preset(preset); preset->filament_id = get_filament_id(filament_preset_name); - preset->version = base_preset->version; - preset->loaded = true; + + // Apply settings from the spool on top of the base preset + spool->apply_to_preset(preset); + + // Finalize and save + if (use_preset_data) { + if (auto version = Semver::parse(additional_preset_data[BBL_JSON_KEY_VERSION])) + preset->version = *version; + } else { + preset->version = base_preset->version; + } + preset->loaded = true; filaments.save_current_preset(filament_preset_name, detach, false, preset); return result; @@ -510,25 +553,30 @@ void SpoolmanFilament::apply_to_config(Slic3r::DynamicConfig& config) const vendor->apply_to_config(config); } -DynamicPrintConfig SpoolmanFilament::get_config_from_preset_data() const +bool SpoolmanFilament::get_config_from_preset_data(DynamicPrintConfig& config, std::map* additional_values) const { if (preset_data.empty()) - return {}; + return false; json j = json::parse(preset_data); ConfigSubstitutionContext context(Enable); std::map key_values; std::string reason; - DynamicPrintConfig config; - config.load_from_json(j, context, true, key_values, reason); + DynamicPrintConfig new_config; + new_config.load_from_json(j, context, true, key_values, reason); if (!reason.empty()) - return {}; + return false; auto& presets = wxGetApp().preset_bundle->filaments; - if (!presets.load_full_config(config)) - return {}; - const auto invalid_keys = Preset::remove_invalid_keys(config, presets.default_preset().config); + if (!presets.load_full_config(new_config)) + return false; + const auto invalid_keys = Preset::remove_invalid_keys(new_config, presets.default_preset().config); if (!invalid_keys.empty()) - return {}; - return config; + return false; + + // Checks passed - apply to preset + config = std::move(new_config); + if (additional_values) + *additional_values = std::move(key_values); + return true; } diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index 02717ec7bc..20d7bc1c7f 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -121,8 +121,15 @@ public: /// \returns if succeeded bool undo_use_spoolman_spools(); + /// Create a filament preset from a Spoolman spool + /// \param spool Spoolman spool + /// \param base_preset preset to inherit settings from + /// \param use_preset_data if the spool has preset data, the SpoolmanFilament's preset_data will be used instead of using the base preset + /// \param detach create profile without depending + /// \param force attempt to force past errors static SpoolmanResult create_filament_preset_from_spool(const SpoolmanSpoolShrPtr& spool, const Preset* base_preset, + bool use_preset_data = false, bool detach = false, bool force = false); static SpoolmanResult update_filament_preset_from_spool(Preset* filament_preset, @@ -224,7 +231,7 @@ public: SpoolmanVendorShrPtr vendor; void update_from_server(bool recursive = false); - DynamicPrintConfig get_config_from_preset_data() const; + bool get_config_from_preset_data(DynamicPrintConfig& config, std::map* additional_values = nullptr) const; private: Spoolman* m_spoolman; From 014b102e78e8ef04f39580ebfb0d37b985e905c9 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Wed, 10 Dec 2025 05:36:37 -0500 Subject: [PATCH 097/100] Improve multithread work in SpoolmanImportDialog --- src/slic3r/GUI/SpoolmanImportDialog.cpp | 40 +++++++++++++++---------- src/slic3r/Utils/Spoolman.cpp | 7 +++++ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/SpoolmanImportDialog.cpp b/src/slic3r/GUI/SpoolmanImportDialog.cpp index 4873e56eaa..fb3d8ca2d4 100644 --- a/src/slic3r/GUI/SpoolmanImportDialog.cpp +++ b/src/slic3r/GUI/SpoolmanImportDialog.cpp @@ -4,6 +4,7 @@ #include "ExtraRenderers.hpp" #include "MsgDialog.hpp" #include "Widgets/DialogButtons.hpp" +#include "oneapi/tbb/parallel_for_each.h" #define BTN_GAP FromDIP(10) #define BTN_SIZE wxSize(FromDIP(58), FromDIP(24)) @@ -246,7 +247,8 @@ void SpoolmanImportDialog::on_dpi_changed(const wxRect& suggested_rect) void SpoolmanImportDialog::on_import() { - const auto current_preset = wxGetApp().preset_bundle->filaments.find_preset(m_preset_combobox->GetStringSelection().ToUTF8().data()); + auto& filaments = wxGetApp().preset_bundle->filaments; + const auto current_preset = filaments.find_preset(m_preset_combobox->GetStringSelection().ToUTF8().data()); const auto& selected_spools = m_svc->get_model()->GetSelectedSpools(); if (selected_spools.empty()) { show_error(this, _L("No spools are selected")); @@ -259,21 +261,29 @@ void SpoolmanImportDialog::on_import() auto create_presets = [&](const vector& spools, bool force = false) { failed_spools.clear(); - // Attempt to create the presets - // Calculating the hash for the internal filament id takes a bit, so using multithreading to speed it up - std::vector threads; - for (const auto& spool : spools) { - threads.emplace_back(Slic3r::create_thread([&] { - auto res = Spoolman::create_filament_preset_from_spool(spool, current_preset, !ignore_preset_data, detach, force); - if (res.has_failed()) - failed_spools.emplace_back(spool, res); - })); - } + // Save selected preset + const auto selected_preset_name = filaments.get_selected_preset_name(); + auto edited_preset = filaments.get_edited_preset(); - // Join/wait for threads to finish before continuing - for (auto& thread : threads) - if (thread.joinable()) - thread.join(); + std::mutex failed_spools_mutex; + auto create_preset = [&](const SpoolmanSpoolShrPtr& spool) { + auto res = Spoolman::create_filament_preset_from_spool(spool, current_preset, !ignore_preset_data, detach, force); + if (res.has_failed()) { + std::lock_guard lock(failed_spools_mutex); + failed_spools.emplace_back(spool, res); + } + }; + + // Calculating the hash for the internal filament id takes a bit, so using multithreading to speed it up + wxBusyCursor busy_cursor; + if (spools.size() > 1) + tbb::parallel_for_each(spools.begin(), spools.end(), create_preset); + else + create_preset(spools[0]); + + // Restore selected preset + filaments.select_preset_by_name(selected_preset_name, true); + filaments.get_edited_preset() = std::move(edited_preset); }; create_presets(selected_spools); diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index a74e6dff9a..b5f43578a9 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -311,7 +311,14 @@ SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolSh preset->version = base_preset->version; } preset->loaded = true; + + static std::mutex save_mutex; + // The save_current_preset function selects and modifies the current preset + // The operation is not thread safe and will cause an exception if multiple + // threads try to change the selected preset at the same time + save_mutex.lock(); filaments.save_current_preset(filament_preset_name, detach, false, preset); + save_mutex.unlock(); return result; } From 5a45364dd85a96da6d447e3eed65a2ee3fe740e0 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Sat, 13 Dec 2025 20:22:22 -0500 Subject: [PATCH 098/100] Update the edited preset instead of selected When updating the filament from Spoolman, load into the edited preset so the user is required to save the changes --- src/slic3r/GUI/Tab.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 1c957076a5..4a47d2964a 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4088,7 +4088,7 @@ void TabFilament::build() show_error(this, "Failed to get data from the Spoolman server. Make sure that the port is correct and the server is running."); return; } - auto res = Spoolman::update_filament_preset_from_spool(&m_presets->get_selected_preset(), true, stats_only); + auto res = Spoolman::update_filament_preset_from_spool(&m_presets->get_edited_preset(), true, stats_only); if (res.has_failed()) { show_error(this, res.build_error_dialog_message()); @@ -4096,6 +4096,7 @@ void TabFilament::build() } update_spoolman_statistics(); + this->update_dirty(); }; auto refresh_all_btn = new Button(parent, _L("Update Filament")); @@ -4160,8 +4161,7 @@ void TabFilament::build() // Apply spool configuration changes spool->apply_to_config(edited_preset.config); - // Load config changes into the tab - this->load_config(edited_preset.config); + this->update_dirty(); }); load_from_spoolman_btn->SetStyle(ButtonStyle::Regular, ButtonType::Parameter); sizer->Add(load_from_spoolman_btn); From d6144891bfb91faa2ecb8960a3d50a92bfeb8c57 Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Thu, 18 Dec 2025 07:03:14 -0500 Subject: [PATCH 099/100] Update printer/vendor identifier --- src/slic3r/Utils/Spoolman.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index b5f43578a9..1728ccfb68 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -232,11 +232,19 @@ SpoolmanResult Spoolman::create_filament_preset_from_spool(const SpoolmanSpoolSh std::string filament_preset_name = spool->get_preset_name(); - // Bring over the printer name from the base preset or add one for the current printer - if (const auto idx = base_preset->name.rfind(" @"); idx != std::string::npos) + // Add a printer/vendor identifier + if (auto idx = base_preset->name.rfind(" @"); idx != std::string::npos && base_preset->name.substr(idx) != " @System") { filament_preset_name += base_preset->name.substr(idx); - else + } else if (base_preset->vendor) { + // Presets from the filament library are compatible with all printers by default + // Skip adding an identifier + if (!base_preset->m_from_orca_filament_lib) + filament_preset_name += " @" + base_preset->vendor->name; + } else if (use_preset_data && (idx = additional_preset_data[BBL_JSON_KEY_NAME].find(" @")) != std::string::npos) { + filament_preset_name += additional_preset_data[BBL_JSON_KEY_NAME].substr(idx); + } else { filament_preset_name += " @" + wxGetApp().preset_bundle->printers.get_selected_preset_name(); + } if (const auto idx = filament_preset_name.rfind(" - Copy"); idx != std::string::npos) filament_preset_name.erase(idx); From 1d7d853daf83e3864bd2770442819f3692f37eec Mon Sep 17 00:00:00 2001 From: Ocraftyone Date: Fri, 19 Dec 2025 16:44:02 -0500 Subject: [PATCH 100/100] Add Spoolman comments to filament notes --- src/slic3r/Utils/Spoolman.cpp | 38 +++++++++++++++++++++++++++++++++-- src/slic3r/Utils/Spoolman.hpp | 15 ++++++++------ 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/slic3r/Utils/Spoolman.cpp b/src/slic3r/Utils/Spoolman.cpp index 1728ccfb68..c92fad88e6 100644 --- a/src/slic3r/Utils/Spoolman.cpp +++ b/src/slic3r/Utils/Spoolman.cpp @@ -10,6 +10,31 @@ namespace Slic3r { namespace { template Type get_opt(pt::ptree& data, const string& path, Type default_val = {}) { return data.get_optional(path).value_or(default_val); } + +void update_note(std::string& config_note, const std::string& type, const std::string& value) +{ + const auto header = ";spoolman_comment_" + type + "\n"; + if (size_t header_idx; !config_note.empty() && (header_idx = config_note.find(header)) != std::string::npos) { + const size_t comment_begin_idx = header_idx + header.size(); + if (const size_t comment_end_idx = config_note.find("\n;spoolman_comment_", header_idx); comment_end_idx != std::string::npos) { + const size_t comment_len = comment_end_idx - comment_begin_idx; + config_note.replace(comment_begin_idx, comment_len, value + "\n"); + return; + } + } + + if (value.empty()) + return; + + if (const size_t end_comment_idx = config_note.find("\n;spoolman_comment_end"); end_comment_idx != std::string::npos) { + config_note.insert(end_comment_idx, "\n" + header + value + "\n"); + return; + } + + if (!config_note.empty()) + config_note.append("\n"); + config_note.append(header).append(value).append("\n\n;spoolman_comment_end"); +} } // namespace // Max timout in seconds for Spoolman HTTP requests @@ -499,13 +524,16 @@ void SpoolmanVendor::update_from_server() { update_from_json(Spoolman::get_spool void SpoolmanVendor::update_from_json(pt::ptree json_data) { - id = json_data.get("id"); - name = get_opt(json_data, "name"); + id = json_data.get("id"); + name = get_opt(json_data, "name"); + comment = get_opt(json_data, "comment"); } void SpoolmanVendor::apply_to_config(Slic3r::DynamicConfig& config) const { config.set_key_value("filament_vendor", new ConfigOptionStrings({name})); + auto& config_note = config.opt("filament_notes", true)->get_at(0); + update_note(config_note, "vendor", comment); } //--------------------------------- @@ -543,6 +571,7 @@ void SpoolmanFilament::update_from_json(pt::ptree json_data) bed_temp = get_opt(json_data, "settings_bed_temp"); color = "#" + get_opt(json_data, "color_hex"); preset_data = get_opt(json_data, "extra.orcaslicer_preset_data"); + comment = get_opt(json_data, "comment"); if (!preset_data.empty()) { boost::trim_if(preset_data, [](char c) { return c == '"'; }); boost::replace_all(preset_data, "\\\"", "\""); @@ -564,6 +593,8 @@ void SpoolmanFilament::apply_to_config(Slic3r::DynamicConfig& config) const config.set_key_value("hot_plate_temp", new ConfigOptionInts({bed_temp})); } config.set_key_value("default_filament_colour", new ConfigOptionStrings{color}); + auto& config_note = config.opt("filament_notes", true)->get_at(0); + update_note(config_note, "filament", comment); if (vendor) vendor->apply_to_config(config); } @@ -627,6 +658,8 @@ std::string SpoolmanSpool::get_preset_name() void SpoolmanSpool::apply_to_config(Slic3r::DynamicConfig& config) const { config.set_key_value("spoolman_spool_id", new ConfigOptionInts({id})); + auto& config_note = config.opt("filament_notes", true)->get_at(0); + update_note(config_note, "spool", comment); filament->apply_to_config(config); } @@ -651,6 +684,7 @@ void SpoolmanSpool::update_from_json(pt::ptree json_data) filament = m_spoolman->m_filaments.at(filament_id); } id = json_data.get("id"); + comment = get_opt(json_data, "comment"); remaining_weight = get_opt(json_data, "remaining_weight"); used_weight = get_opt(json_data, "used_weight"); remaining_length = get_opt(json_data, "remaining_length"); diff --git a/src/slic3r/Utils/Spoolman.hpp b/src/slic3r/Utils/Spoolman.hpp index 20d7bc1c7f..835d67aeed 100644 --- a/src/slic3r/Utils/Spoolman.hpp +++ b/src/slic3r/Utils/Spoolman.hpp @@ -193,6 +193,7 @@ class SpoolmanVendor public: int id; std::string name; + std::string comment; void update_from_server(); @@ -226,6 +227,7 @@ public: int bed_temp; std::string color; std::string preset_data; + std::string comment; // Can be nullptr SpoolmanVendorShrPtr vendor; @@ -255,12 +257,13 @@ private: class SpoolmanSpool { public: - int id; - double remaining_weight; - double used_weight; - double remaining_length; - double used_length; - bool archived; + int id; + std::string comment; + double remaining_weight; + double used_weight; + double remaining_length; + double used_length; + bool archived; SpoolmanFilamentShrPtr filament;