diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 35d1386ac1..debe3e63b9 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -886,14 +886,37 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail // Write information on the generator. file.write_format("; %s\n\n", Slic3r::header_slic3r_generated().c_str()); - // Unit tests or command line slicing may not define "thumbnails" or "thumbnails_format". - // If "thumbnails_format" is not defined, export to PNG. - if (const auto [thumbnails, thumbnails_format] = std::make_pair( - print.full_print_config().option("thumbnails"), - print.full_print_config().option>("thumbnails_format")); - thumbnails) - GCodeThumbnails::export_thumbnails_to_file( - thumbnail_cb, thumbnails->values, thumbnails_format ? thumbnails_format->value : GCodeThumbnailsFormat::PNG, + // ??? Unit tests or command line slicing may not define "thumbnails" or "thumbnails_format". + // ??? If "thumbnails_format" is not defined, export to PNG. + + // generate thumbnails data to process it + + std::vector> thumbnails_list; + if (const auto thumbnails_value = print.full_print_config().option("thumbnails")) { + std::string str = thumbnails_value->value; + std::istringstream is(str); + std::string point_str; + while (std::getline(is, point_str, ',')) { + Vec2d point(Vec2d::Zero()); + GCodeThumbnailsFormat format; + std::istringstream iss(point_str); + std::string coord_str; + if (std::getline(iss, coord_str, 'x')) { + std::istringstream(coord_str) >> point(0); + if (std::getline(iss, coord_str, '/')) { + std::istringstream(coord_str) >> point(1); + std::string ext_str; + if (std::getline(iss, ext_str, '/')) + format = ext_str == "JPG" ? GCodeThumbnailsFormat::JPG : + ext_str == "QOI" ? GCodeThumbnailsFormat::QOI :GCodeThumbnailsFormat::PNG; + } + } + thumbnails_list.emplace_back(std::make_pair(format, point)); + } + } + + if (!thumbnails_list.empty()) + GCodeThumbnails::export_thumbnails_to_file(thumbnail_cb, thumbnails_list, [&file](const char* sz) { file.write(sz); }, [&print]() { print.throw_if_canceled(); }); diff --git a/src/libslic3r/GCode/Thumbnails.hpp b/src/libslic3r/GCode/Thumbnails.hpp index 30bb6b653b..68b11d15fb 100644 --- a/src/libslic3r/GCode/Thumbnails.hpp +++ b/src/libslic3r/GCode/Thumbnails.hpp @@ -24,34 +24,36 @@ struct CompressedImageBuffer std::unique_ptr compress_thumbnail(const ThumbnailData &data, GCodeThumbnailsFormat format); template -inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector &sizes, GCodeThumbnailsFormat format, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled) +inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector>& thumbnails_list, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled) { // Write thumbnails using base64 encoding if (thumbnail_cb != nullptr) { - static constexpr const size_t max_row_length = 78; - ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ sizes, true, true, true, true }); - for (const ThumbnailData& data : thumbnails) - if (data.is_valid()) { - auto compressed = compress_thumbnail(data, format); - if (compressed->data && compressed->size) { - std::string encoded; - encoded.resize(boost::beast::detail::base64::encoded_size(compressed->size)); - encoded.resize(boost::beast::detail::base64::encode((void*)encoded.data(), (const void*)compressed->data, compressed->size)); + for (const auto& [format, size] : thumbnails_list) { + static constexpr const size_t max_row_length = 78; + ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ {size}, true, true, true, true }); + for (const ThumbnailData& data : thumbnails) + if (data.is_valid()) { + auto compressed = compress_thumbnail(data, format); + if (compressed->data && compressed->size) { + std::string encoded; + encoded.resize(boost::beast::detail::base64::encoded_size(compressed->size)); + encoded.resize(boost::beast::detail::base64::encode((void*)encoded.data(), (const void*)compressed->data, compressed->size)); - output((boost::format("\n;\n; %s begin %dx%d %d\n") % compressed->tag() % data.width % data.height % encoded.size()).str().c_str()); + output((boost::format("\n;\n; %s begin %dx%d %d\n") % compressed->tag() % data.width % data.height % encoded.size()).str().c_str()); - while (encoded.size() > max_row_length) { - output((boost::format("; %s\n") % encoded.substr(0, max_row_length)).str().c_str()); - encoded = encoded.substr(max_row_length); + while (encoded.size() > max_row_length) { + output((boost::format("; %s\n") % encoded.substr(0, max_row_length)).str().c_str()); + encoded = encoded.substr(max_row_length); + } + + if (encoded.size() > 0) + output((boost::format("; %s\n") % encoded).str().c_str()); + + output((boost::format("; %s end\n;\n") % compressed->tag()).str().c_str()); } - - if (encoded.size() > 0) - output((boost::format("; %s\n") % encoded).str().c_str()); - - output((boost::format("; %s end\n;\n") % compressed->tag()).str().c_str()); + throw_if_canceled(); } - throw_if_canceled(); - } + } } } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 8f6057f904..334eb9bb82 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -285,12 +285,12 @@ void PrintConfigDef::init_common_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.)); - def = this->add("thumbnails", coPoints); + def = this->add("thumbnails", coString); def->label = L("G-code thumbnails"); - def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 / .sl1s files, in the following format: \"XxY, XxY, ...\""); + def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 / .sl1s files, in the following format: \"XxYxEXT, XxYxEXT, ...\""); def->mode = comExpert; def->gui_type = ConfigOptionDef::GUIType::one_string; - def->set_default_value(new ConfigOptionPoints()); + def->set_default_value(new ConfigOptionString()); def = this->add("thumbnails_format", coEnum); def->label = L("Format of G-code thumbnails"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 59964c0b8a..a2a4466045 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -835,7 +835,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionInt, standby_temperature_delta)) ((ConfigOptionInts, temperature)) ((ConfigOptionInt, threads)) - ((ConfigOptionPoints, thumbnails)) + ((ConfigOptionString, thumbnails)) ((ConfigOptionEnum, thumbnails_format)) ((ConfigOptionFloat, top_solid_infill_acceleration)) ((ConfigOptionFloat, travel_acceleration)) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 121493b68b..bbc2b04dd2 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -8,6 +8,7 @@ #include "format.hpp" #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/enum_bitmask.hpp" #include #include @@ -26,7 +27,13 @@ #define wxOSX false #endif -namespace Slic3r { namespace GUI { +namespace Slic3r { + +enum class ThumbnailError : int { InvalidVal, OutOfRange, InvalidExt }; +using ThumbnailErrors = enum_bitmask; +ENABLE_ENUM_BITMASK_OPERATORS(ThumbnailError); + +namespace GUI { wxString double_to_string(double const value, const int max_precision /*= 4*/) { @@ -58,14 +65,79 @@ wxString double_to_string(double const value, const int max_precision /*= 4*/) return s; } -wxString get_thumbnails_string(const std::vector& values) +bool is_valid_thumbnails_extention(wxString& ext) { - wxString ret_str; - for (size_t i = 0; i < values.size(); ++ i) { - const Vec2d& el = values[i]; - ret_str += wxString::Format((i == 0) ? "%ix%i" : ", %ix%i", int(el[0]), int(el[1])); - } - return ret_str; + ext.UpperCase(); + static const std::vector extentions = { "PNG", "JPG", "QOI" }; + + return std::find(extentions.begin(), extentions.end(), ext) != extentions.end(); +} + +ThumbnailErrors validate_thumbnails_string(wxString& str, const wxString& def_ext = "PNG") +{ + bool invalid_val, out_of_range_val, invalid_ext; + invalid_val = out_of_range_val = invalid_ext = false; + str.Replace(" ", wxEmptyString, true); + + if (!str.IsEmpty()) { + + std::vector> out_thumbnails; + + wxStringTokenizer thumbnails(str, ","); + while (thumbnails.HasMoreTokens()) { + wxString token = thumbnails.GetNextToken(); + double x, y; + wxStringTokenizer thumbnail(token, "x"); + if (thumbnail.HasMoreTokens()) { + wxString x_str = thumbnail.GetNextToken(); + if (x_str.ToDouble(&x) && thumbnail.HasMoreTokens()) { + wxStringTokenizer y_and_ext(thumbnail.GetNextToken(), "/"); + + wxString y_str = y_and_ext.GetNextToken(); + if (y_str.ToDouble(&y)) { + // thumbnail has no extension + if (0 < x && x < 1000 && 0 < y && y < 1000) { + wxString ext = y_and_ext.HasMoreTokens() ? y_and_ext.GetNextToken() : def_ext; + bool is_valid_ext = is_valid_thumbnails_extention(ext); + invalid_ext |= !is_valid_ext; + out_thumbnails.push_back({ Vec2d(x, y), into_u8(is_valid_ext ? ext : def_ext) }); + continue; + } + out_of_range_val |= true; + continue; + } + } + } + invalid_val |= true; + } + + str.Clear(); + for (const auto& [size, ext] : out_thumbnails) + str += format_wxstr("%1%x%2%/%3%, ", size.x(), size.y(), ext); + str.resize(str.Len()-2); + } + + ThumbnailErrors errors = only_if(invalid_val, ThumbnailError::InvalidVal) | + only_if(invalid_ext, ThumbnailError::InvalidExt) | + only_if(out_of_range_val, ThumbnailError::OutOfRange); + + return errors; +} + +wxString get_valid_thumbnails_string(const DynamicPrintConfig& config) +{ + // >>> ysFIXME - temporary code, till "thumbnails_format" options exists in config + wxString format = "PNG"; + if (const ConfigOptionDef* opt = config.def()->get("thumbnails_format")) + if (auto label = opt->enum_def->enum_to_label(config.option("thumbnails_format")->getInt()); + label.has_value()) + format = from_u8(*label); + // <<< + + wxString str = from_u8(config.opt_string("thumbnails")); + validate_thumbnails_string(str, format); + + return str; } @@ -355,56 +427,35 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true } } - m_value = into_u8(str); - break; } - - case coPoints: { - std::vector out_values; - str.Replace(" ", wxEmptyString, true); - if (!str.IsEmpty()) { - bool invalid_val = false; - bool out_of_range_val = false; - wxStringTokenizer thumbnails(str, ","); - while (thumbnails.HasMoreTokens()) { - wxString token = thumbnails.GetNextToken(); - double x, y; - wxStringTokenizer thumbnail(token, "x"); - if (thumbnail.HasMoreTokens()) { - wxString x_str = thumbnail.GetNextToken(); - if (x_str.ToDouble(&x) && thumbnail.HasMoreTokens()) { - wxString y_str = thumbnail.GetNextToken(); - if (y_str.ToDouble(&y) && !thumbnail.HasMoreTokens()) { - if (0 < x && x < 1000 && 0 < y && y < 1000) { - out_values.push_back(Vec2d(x, y)); - continue; - } - out_of_range_val = true; - break; - } - } + if (m_opt.opt_key == "thumbnails") { + wxString str_out = str; + ThumbnailErrors errors = validate_thumbnails_string(str_out); + if (errors != enum_bitmask()) { + set_value(str_out, true); + wxString error_str; + if (errors.has(ThumbnailError::InvalidVal)) + error_str += format_wxstr(_L("Invalid input format. Expected vector of dimensions in the following format: \"%1%\""), "XxYxEXT, XxYxEXT, ..."); + if (errors.has(ThumbnailError::OutOfRange)) { + if (!error_str.empty()) + error_str += "\n\n"; + error_str += _L("Input value is out of range"); } - invalid_val = true; - break; + if (errors.has(ThumbnailError::InvalidExt)) { + if (!error_str.empty()) + error_str += "\n\n"; + error_str += _L("Some input extention is invalid"); + } + show_error(m_parent, error_str); } - - if (out_of_range_val) { - wxString text_value; - if (!m_value.empty()) - text_value = get_thumbnails_string(boost::any_cast>(m_value)); - set_value(text_value, true); - show_error(m_parent, _L("Input value is out of range")); - } - else if (invalid_val) { - wxString text_value; - if (!m_value.empty()) - text_value = get_thumbnails_string(boost::any_cast>(m_value)); - set_value(text_value, true); - show_error(m_parent, format_wxstr(_L("Invalid input format. Expected vector of dimensions in the following format: \"%1%\""),"XxY, XxY, ..." )); + else if (str_out != str) { + str = str_out; + set_value(str, true); } } - m_value = out_values; - break; } + m_value = into_u8(str); + break; + } default: break; @@ -484,9 +535,6 @@ void TextCtrl::BUILD() { text_value = vec->get_at(m_opt_idx); break; } - case coPoints: - text_value = get_thumbnails_string(m_opt.get_default_value()->values); - break; default: break; } diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 73af0ef53d..704e0900b3 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -37,7 +37,7 @@ using t_change = std::function; wxString double_to_string(double const value, const int max_precision = 4); -wxString get_thumbnails_string(const std::vector& values); +wxString get_valid_thumbnails_string(const DynamicPrintConfig& config); class UndoValueUIManager { diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 614fda9130..3fda23323b 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -904,9 +904,13 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config ret = double_to_string(val); } break; - case coString: - ret = from_u8(config.opt_string(opt_key)); - break; + case coString: { + if (opt_key == "thumbnails") + ret = get_valid_thumbnails_string(config); + else + ret = from_u8(config.opt_string(opt_key)); + break; + } case coStrings: if (opt_key == "compatible_printers" || opt_key == "compatible_prints" || opt_key == "gcode_substitutions") { ret = config.option(opt_key)->values; @@ -946,8 +950,6 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config case coPoints: if (opt_key == "bed_shape") ret = config.option(opt_key)->values; - else if (opt_key == "thumbnails") - ret = get_thumbnails_string(config.option(opt_key)->values); else ret = config.option(opt_key)->get_at(idx); break; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 5bbc2fa5d8..94a2960679 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -636,6 +636,21 @@ void Tab::update_changed_ui() if (tab->m_sys_extruders_count != tab->m_extruders_count) nonsys_options.emplace_back("extruders_count"); } + + // "thumbnails" can not containe a extentions in old config but are valid and use PNG extention by default + // So, check if "thumbnails" is really changed + // We will compare full strings for thumnails instead of exactly config values + { + auto check_thumbnails_option = [](std::vector& keys, const DynamicPrintConfig& config, const DynamicPrintConfig& config_new) { + if (auto it = std::find(keys.begin(), keys.end(), "thumbnails"); it != keys.end()) + if (get_valid_thumbnails_string(config) == get_valid_thumbnails_string(config_new)) + // if those strings are actually the same, erase them from the list of dirty oprions + keys.erase(it); + }; + check_thumbnails_option(dirty_options, m_presets->get_edited_preset().config, m_presets->get_selected_preset().config); + if (const Preset* parent_preset = m_presets->get_selected_preset_parent()) + check_thumbnails_option(nonsys_options, m_presets->get_edited_preset().config, parent_preset->config); + } } for (auto& it : m_options_list) @@ -2526,7 +2541,6 @@ void TabPrinter::build_fff() option = optgroup->get_option("thumbnails"); option.opt.full_width = true; optgroup->append_single_option_line(option); - optgroup->append_single_option_line("thumbnails_format"); optgroup->append_single_option_line("silent_mode"); optgroup->append_single_option_line("remaining_times"); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 5fe036d467..18eacaa153 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -1154,8 +1154,11 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& } return _L("Undef"); } - case coString: + case coString: { + if (opt_key == "thumbnails") + return get_valid_thumbnails_string(config); return from_u8(config.opt_string(opt_key)); + } case coStrings: { const ConfigOptionStrings* strings = config.opt(opt_key); if (strings) { @@ -1204,8 +1207,6 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& BedShape shape(*config.option(opt_key)); return shape.get_full_name_with_params(); } - if (opt_key == "thumbnails") - return get_thumbnails_string(config.option(opt_key)->values); Vec2d val = config.opt(opt_key)->get_at(opt_idx); return from_u8((boost::format("[%1%]") % ConfigOptionPoint(val).serialize()).str());