diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 8e7f43d9f8..1e17f8395d 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -874,8 +874,15 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail // Unit tests or command line slicing may not define "thumbnails" or "thumbnails_format". // If "thumbnails_format" is not defined, export to PNG. - if (std::vector> thumbnails = GCodeThumbnails::make_thumbnail_list(print.full_print_config()); - ! thumbnails.empty()) + auto [thumbnails, errors] = GCodeThumbnails::make_and_check_thumbnail_list(print.full_print_config()); + + if (errors != enum_bitmask()) { + std::string error_str = format("Invalid thumbnails value:"); + error_str += GCodeThumbnails::get_error_string(errors); + throw Slic3r::ExportError(error_str); + } + + if (!thumbnails.empty()) GCodeThumbnails::generate_binary_thumbnails( thumbnail_cb, binary_data.thumbnails, thumbnails, [&print]() { print.throw_if_canceled(); }); @@ -1009,8 +1016,15 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail if (! export_to_binary_gcode) { // if exporting gcode in ascii format, generate the thumbnails here - if (std::vector> thumbnails = GCodeThumbnails::make_thumbnail_list(print.full_print_config()); - ! thumbnails.empty()) + auto [thumbnails, errors] = GCodeThumbnails::make_and_check_thumbnail_list(print.full_print_config()); + + if (errors != enum_bitmask()) { + std::string error_str = format("Invalid thumbnails value:"); + error_str += GCodeThumbnails::get_error_string(errors); + throw Slic3r::ExportError(error_str); + } + + if (!thumbnails.empty()) GCodeThumbnails::export_thumbnails_to_file(thumbnail_cb, thumbnails, [&file](const char* sz) { file.write(sz); }, [&print]() { print.throw_if_canceled(); }); diff --git a/src/libslic3r/GCode/Thumbnails.cpp b/src/libslic3r/GCode/Thumbnails.cpp index 97f06eb059..edafccbea8 100644 --- a/src/libslic3r/GCode/Thumbnails.cpp +++ b/src/libslic3r/GCode/Thumbnails.cpp @@ -9,6 +9,9 @@ #include #include +#include +#include + namespace Slic3r::GCodeThumbnails { using namespace std::literals; @@ -120,38 +123,81 @@ std::unique_ptr compress_thumbnail(const ThumbnailData &d } } -std::vector> make_thumbnail_list(const DynamicPrintConfig &config) +std::pair make_and_check_thumbnail_list(const std::string& thumbnails_string, const std::string_view def_ext /*= "PNG"sv*/) +{ + if (thumbnails_string.empty()) + return {}; + + std::istringstream is(thumbnails_string); + std::string point_str; + + ThumbnailErrors errors; + + // generate thumbnails data to process it + + GCodeThumbnailDefinitionsList thumbnails_list; + 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') && !coord_str.empty()) { + std::istringstream(coord_str) >> point(0); + if (std::getline(iss, coord_str, '/') && !coord_str.empty()) { + std::istringstream(coord_str) >> point(1); + + if (0 < point(0) && point(0) < 1000 && 0 < point(1) && point(1) < 1000) { + std::string ext_str; + std::getline(iss, ext_str, '/'); + + if (ext_str.empty()) + ext_str = def_ext.empty() ? "PNG"sv : def_ext; + + // check validity of extention + boost::to_upper(ext_str); + if (!ConfigOptionEnum::from_string(ext_str, format)) { + format = GCodeThumbnailsFormat::PNG; + errors = enum_bitmask(errors | ThumbnailError::InvalidExt); + } + + thumbnails_list.emplace_back(std::make_pair(format, point)); + } + else + errors = enum_bitmask(errors | ThumbnailError::OutOfRange); + continue; + } + } + errors = enum_bitmask(errors | ThumbnailError::InvalidVal); + } + + return std::make_pair(std::move(thumbnails_list), errors); +} + +std::pair make_and_check_thumbnail_list(const ConfigBase& config) { // ??? 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 = 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 (const auto thumbnails_value = config.option("thumbnails")) + return make_and_check_thumbnail_list(thumbnails_value->value); - return thumbnails_list; + return {}; +} + +std::string get_error_string(const ThumbnailErrors& errors) +{ + std::string error_str; + + if (errors.has(ThumbnailError::InvalidVal)) + error_str += "\n - " + format("Invalid input format. Expected vector of dimensions in the following format: \"%1%\"", "XxYxEXT, XxYxEXT, ..."); + if (errors.has(ThumbnailError::OutOfRange)) + error_str += "\n - Input value is out of range"; + if (errors.has(ThumbnailError::InvalidExt)) + error_str += "\n - Some input extention is invalid"; + + return error_str; } } // namespace Slic3r::GCodeThumbnails diff --git a/src/libslic3r/GCode/Thumbnails.hpp b/src/libslic3r/GCode/Thumbnails.hpp index 4a22e84d13..a5f9803363 100644 --- a/src/libslic3r/GCode/Thumbnails.hpp +++ b/src/libslic3r/GCode/Thumbnails.hpp @@ -17,6 +17,14 @@ #include +#include "../libslic3r/enum_bitmask.hpp" + +namespace Slic3r { + enum class ThumbnailError : int { InvalidVal, OutOfRange, InvalidExt }; + using ThumbnailErrors = enum_bitmask; + ENABLE_ENUM_BITMASK_OPERATORS(ThumbnailError); +} + namespace Slic3r::GCodeThumbnails { struct CompressedImageBuffer @@ -29,7 +37,13 @@ struct CompressedImageBuffer std::unique_ptr compress_thumbnail(const ThumbnailData &data, GCodeThumbnailsFormat format); -std::vector> make_thumbnail_list(const DynamicPrintConfig &config); +typedef std::vector> GCodeThumbnailDefinitionsList; + +using namespace std::literals; +std::pair make_and_check_thumbnail_list(const std::string& thumbnails_string, const std::string_view def_ext = "PNG"sv); +std::pair make_and_check_thumbnail_list(const ConfigBase &config); + +std::string get_error_string(const ThumbnailErrors& errors); template inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector>& thumbnails_list, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 1e9764f815..0ff2f103c3 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -44,6 +44,7 @@ #include "libslic3r.h" #include "Utils.hpp" #include "PlaceholderParser.hpp" +#include "GCode/Thumbnails.hpp" using boost::property_tree::ptree; @@ -1299,7 +1300,6 @@ static const std::set independent_from_extruder_number_options = { "filament_ramming_parameters", "gcode_substitutions", "post_process", - "thumbnails", }; bool PresetCollection::is_independent_from_extruder_number_option(const std::string& opt_key) @@ -1323,6 +1323,15 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi } else if (opt_key == "default_filament_profile") { // Ignore this field, it is not presented to the user, therefore showing a "modified" flag for this parameter does not help. // Also the length of this field may differ, which may lead to a crash if the block below is used. + } else if (opt_key == "thumbnails") { + // "thumbnails" can not containes 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 thumnails instead of exactly config values + auto [thumbnails, er] = GCodeThumbnails::make_and_check_thumbnail_list(config_this); + auto [thumbnails_new, er_new] = GCodeThumbnails::make_and_check_thumbnail_list(config_other); + if (thumbnails != thumbnails_new || er != er_new) + // if those strings are actually the same, erase them from the list of dirty oprions + diff.emplace_back(opt_key); } else { switch (other_opt->type()) { case coInts: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; @@ -1346,7 +1355,14 @@ bool PresetCollection::is_dirty(const Preset *edited, const Preset *reference) { if (edited != nullptr && reference != nullptr) { // Only compares options existing in both configs. - if (! reference->config.equals(edited->config)) + bool is_dirty = !reference->config.equals(edited->config); + if (is_dirty && edited->type != Preset::TYPE_FILAMENT) { + // for non-filaments preset check deep difference for compared configs + // there can be cases (as for thumbnails), when configs can logically equal + // even when their values are not equal. + is_dirty = !deep_diff(edited->config, reference->config).empty(); + } + if (is_dirty) return true; // The "compatible_printers" option key is handled differently from the others: // It is not mandatory. If the key is missing, it means it is compatible with any printer. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 15b7639227..23e17e0018 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -24,6 +24,7 @@ #include "I18N.hpp" #include "SLA/SupportTree.hpp" +#include "GCode/Thumbnails.hpp" #include #include @@ -4371,6 +4372,35 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va // Don't convert single options here, implement such conversion in PrintConfigDef::handle_legacy() instead. void PrintConfigDef::handle_legacy_composite(DynamicPrintConfig &config) { + if (config.has("thumbnails")) { + std::string extention; + if (config.has("thumbnails_format")) { + if (const ConfigOptionDef* opt = config.def()->get("thumbnails_format")) { + auto label = opt->enum_def->enum_to_label(config.option("thumbnails_format")->getInt()); + if (label.has_value()) + extention = *label; + } + } + + std::string thumbnails_str = config.opt_string("thumbnails"); + auto [thumbnails_list, errors] = GCodeThumbnails::make_and_check_thumbnail_list(thumbnails_str, extention); + + if (errors != enum_bitmask()) { + std::string error_str = "\n" + format("Invalid value provided for parameter %1%: %2%", "thumbnails", thumbnails_str); + error_str += GCodeThumbnails::get_error_string(errors); + throw BadOptionValueException(error_str); + } + + if (!thumbnails_list.empty()) { + const auto& extentions = ConfigOptionEnum::get_enum_names(); + thumbnails_str.clear(); + for (const auto& [ext, size] : thumbnails_list) + thumbnails_str += format("%1%x%2%/%3%, ", size.x(), size.y(), extentions[int(ext)]); + thumbnails_str.resize(thumbnails_str.length() - 2); + + config.set_key_value("thumbnails", new ConfigOptionString(thumbnails_str)); + } + } } const PrintConfigDef print_config_def; diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index d682d2faf3..e82f835587 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -15,6 +15,7 @@ #include "libslic3r/PrintConfig.hpp" #include "libslic3r/enum_bitmask.hpp" +#include "libslic3r/GCode/Thumbnails.hpp" #include #include @@ -33,13 +34,7 @@ #define wxOSX false #endif -namespace Slic3r { - -enum class ThumbnailError : int { InvalidVal, OutOfRange, InvalidExt }; -using ThumbnailErrors = enum_bitmask; -ENABLE_ENUM_BITMASK_OPERATORS(ThumbnailError); - -namespace GUI { +namespace Slic3r :: GUI { wxString double_to_string(double const value, const int max_precision /*= 4*/) { @@ -71,82 +66,23 @@ wxString double_to_string(double const value, const int max_precision /*= 4*/) return s; } -bool is_valid_thumbnails_extention(wxString& ext) -{ - 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); + std::string input_string = into_u8(str); - if (!str.IsEmpty()) { + str.Clear(); - 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); + auto [thumbnails_list, errors] = GCodeThumbnails::make_and_check_thumbnail_list(input_string); + if (!thumbnails_list.empty()) { + const auto& extentions = ConfigOptionEnum::get_enum_names(); + for (const auto& [format, size] : thumbnails_list) + str += format_wxstr("%1%x%2%/%3%, ", size.x(), size.y(), extentions[int(format)]); + 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; -} - - Field::~Field() { if (m_on_kill_focus) @@ -1765,5 +1701,5 @@ boost::any& SliderCtrl::get_value() } -} // GUI -} // Slic3r +} // Slic3r :: GUI + diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 7e6437742e..bdc8199136 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -41,7 +41,6 @@ using t_change = std::function; wxString double_to_string(double const value, const int max_precision = 4); -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 8e80dfcd8b..1e1d3c08bd 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -925,10 +925,7 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config } break; case coString: { - if (opt_key == "thumbnails") - ret = get_valid_thumbnails_string(config); - else - ret = from_u8(config.opt_string(opt_key)); + ret = from_u8(config.opt_string(opt_key)); break; } case coStrings: diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 2d42c02cb8..6ded1c4a2f 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -43,8 +43,6 @@ namespace Slic3r { class BuildVolume; class Model; class ModelObject; -enum class ModelObjectCutAttribute : int; -using ModelObjectCutAttributes = enum_bitmask; class ModelInstance; class Print; class SLAPrint; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index f318f84959..cc92730b74 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -27,6 +27,7 @@ #include "libslic3r/Model.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" #include "libslic3r/GCode/GCodeWriter.hpp" +#include "libslic3r/GCode/Thumbnails.hpp" #include "slic3r/Utils/Http.hpp" #include "slic3r/Utils/PrintHost.hpp" @@ -662,21 +663,6 @@ 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) @@ -2673,6 +2659,32 @@ void TabPrinter::build_fff() optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { wxTheApp->CallAfter([this, opt_key, value]() { + if (opt_key == "thumbnails" && m_config->has("thumbnails_format")) { + // to backward compatibility we need to update "thumbnails_format" from new "thumbnails" + if (const std::string val = boost::any_cast(value); !value.empty()) { + auto [thumbnails_list, errors] = GCodeThumbnails::make_and_check_thumbnail_list(val); + + if (errors != enum_bitmask()) { + std::string error_str = format(_u8L("Invalid value provided for parameter %1%: %2%"), "thumbnails", val); + error_str += GCodeThumbnails::get_error_string(errors); + InfoDialog(parent(), _L("G-code flavor is switched"), from_u8(error_str)).ShowModal(); + } + + if (!thumbnails_list.empty()) { + GCodeThumbnailsFormat old_format = GCodeThumbnailsFormat(m_config->option("thumbnails_format")->getInt()); + GCodeThumbnailsFormat new_format = thumbnails_list.begin()->first; + if (old_format != new_format) { + DynamicPrintConfig new_conf = *m_config; + + auto* opt = m_config->option("thumbnails_format")->clone(); + opt->setInt(int(new_format)); + new_conf.set_key_value("thumbnails_format", opt); + + load_config(new_conf); + } + } + } + } if (opt_key == "silent_mode") { bool val = boost::any_cast(value); if (m_use_silent_mode != val) { diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 956e13e4be..5ae26ea9e8 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -1160,11 +1160,8 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& } return _L("Undef"); } - case coString: { - if (opt_key == "thumbnails") - return get_valid_thumbnails_string(config); + case coString: return from_u8(config.opt_string(opt_key)); - } case coStrings: { const ConfigOptionStrings* strings = config.opt(opt_key); if (strings) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d6d16db1d2..af2a4e033d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -28,6 +28,7 @@ endif() set_property(GLOBAL PROPERTY USE_FOLDERS ON) add_subdirectory(arrange) +add_subdirectory(thumbnails) add_subdirectory(libslic3r) add_subdirectory(slic3rutils) add_subdirectory(fff_print) diff --git a/tests/thumbnails/CMakeLists.txt b/tests/thumbnails/CMakeLists.txt new file mode 100644 index 0000000000..e07ef0421f --- /dev/null +++ b/tests/thumbnails/CMakeLists.txt @@ -0,0 +1,13 @@ +get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) + +add_executable(${_TEST_NAME}_tests + ${_TEST_NAME}_tests_main.cpp + test_thumbnails_input_string.cpp + test_thumbnails_ini_string.cpp +) + +target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) +set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") + +# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ") +add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${CATCH_EXTRA_ARGS}) \ No newline at end of file diff --git a/tests/thumbnails/test_thumbnails_ini_string.cpp b/tests/thumbnails/test_thumbnails_ini_string.cpp new file mode 100644 index 0000000000..32ec5cbd13 --- /dev/null +++ b/tests/thumbnails/test_thumbnails_ini_string.cpp @@ -0,0 +1,235 @@ +#include + +#include "libslic3r/Config.hpp" +#include "libslic3r/PrintConfig.hpp" + +#include + +using namespace Slic3r; +using namespace GCodeThumbnails; + + +static std::string empty_thumbnails() +{ + return "thumbnails = \n" + "thumbnails_format = "; +} + +static std::string valid_thumbnails() +{ + return "thumbnails = 160x120/JPG, 23x78/QOI, 230x780/JPG\n" + "thumbnails_format = JPG"; +} + +static std::string valid_thumbnails2() +{ + return "thumbnails = 160x120/PNG, 23x78/QOi, 320x240/PNg, 230x780/JPG\n" + "thumbnails_format = pnG"; +} + +static std::string valid_thumbnails3() +{ + return "thumbnails = 160x120/JPG, 23x78/QOI, 230x780/JPG"; +} + +static std::string old_valid_thumbnails() +{ + return "thumbnails = 160x120\n" + "thumbnails_format = JPG"; +} + +static std::string old_valid_thumbnails2() +{ + return "thumbnails = 160x120, 23x78, 320x240\n" + "thumbnails_format = PNG"; +} + +static std::string old_invalid_thumbnails() +{ + return "thumbnails = 160x\n" + "thumbnails_format = JPG"; +} + +static std::string old_invalid_thumbnails2() +{ + return "thumbnails = 160x120, 23*78, 320x240\n" + "thumbnails_format = PNG"; +} + +static std::string out_of_range_thumbnails() +{ + return "thumbnails = 1160x1200/PNG, 23x78/QOI, 320x240/PNG, 230x780/JPG\n" + "thumbnails_format = PNG"; +} + +static std::string out_of_range_thumbnails2() +{ + return "thumbnails = 1160x120/PNG, 23x78/QOI, -320x240/PNG, 230x780/JPG\n" + "thumbnails_format = PNG"; +} + +static std::string invalid_ext_thumbnails() +{ + return "thumbnails = 1160x120/PNk, 23x78/QOI, 320x240/PNG, 230x780/JPG\n" + "thumbnails_format = QOI"; +} + +static std::string invalid_ext_thumbnails2() +{ + return "thumbnails = 1160x120/PNG, 23x78/QO, 320x240/PNG, 230x780/JPG\n" + "thumbnails_format = PNG"; +} + +static std::string invalid_val_thumbnails() +{ + return "thumbnails = 1160x/PNg, 23x78/QOI, 320x240/PNG, 230x780/JPG\n" + "thumbnails_format = JPG"; +} + +static std::string invalid_val_thumbnails2() +{ + return "thumbnails = x120/PNg, 23x78/QOI, 320x240/PNG, 230x780/JPG\n" + "thumbnails_format = PNG"; +} + +static std::string invalid_val_thumbnails3() +{ + return "thumbnails = 1x/PNg, 23x78/QOI, 320x240/PNG, 230x780/JPG\n" + "thumbnails_format = qoi"; +} + +static std::string invalid_val_thumbnails4() +{ + return "thumbnails = 123*78/QOI, 320x240/PNG, 230x780/JPG\n" + "thumbnails_format = jpG"; +} + +static DynamicPrintConfig thumbnails_config() +{ + DynamicPrintConfig config; + config.apply_only(FullPrintConfig::defaults() , { "thumbnails", "thumbnails_format" }); + + return config; +} + +TEST_CASE("Validate Empty Thumbnails", "[Thumbnails in Config]") { + DynamicPrintConfig config = thumbnails_config(); + + auto test_loaded_config = [](DynamicPrintConfig& config) { + REQUIRE(config.opt("thumbnails")->empty()); + REQUIRE(config.option("thumbnails_format")->getInt() == (int)GCodeThumbnailsFormat::PNG); + }; + + SECTION("Load empty init_data") { + REQUIRE_NOTHROW(config.load_from_ini_string("", Enable)); + test_loaded_config(config); + } + + SECTION("Load empty format and empty thumbnails") { + REQUIRE_THROWS_AS(config.load_from_ini_string(empty_thumbnails(), Enable), BadOptionValueException); + test_loaded_config(config); + } +} + +TEST_CASE("Validate New Thumbnails", "[Thumbnails in Config]") { + + DynamicPrintConfig config = thumbnails_config(); + + auto test_loaded_config = [](DynamicPrintConfig& config, GCodeThumbnailsFormat format) { + REQUIRE(!config.opt("thumbnails")->empty()); + REQUIRE(config.option("thumbnails_format")->getInt() == (int)format); + }; + + SECTION("Test 1 (valid)") { + REQUIRE_NOTHROW(config.load_from_ini_string(valid_thumbnails(), Enable)); + test_loaded_config(config, GCodeThumbnailsFormat::JPG); + } + + SECTION("Test 2 (valid)") { + REQUIRE_NOTHROW(config.load_from_ini_string(valid_thumbnails2(), Enable)); + test_loaded_config(config, GCodeThumbnailsFormat::PNG); + } + + SECTION("Test 3 (valid)") { + REQUIRE_NOTHROW(config.load_from_ini_string(valid_thumbnails3(), Enable)); + test_loaded_config(config, GCodeThumbnailsFormat::PNG); + } + + + SECTION("Test 1 (out_of_range)") { + REQUIRE_THROWS_AS(config.load_from_ini_string(out_of_range_thumbnails(), Enable), BadOptionValueException); + test_loaded_config(config, GCodeThumbnailsFormat::PNG); + } + + SECTION("Test 2 (out_of_range)") { + REQUIRE_THROWS_AS(config.load_from_ini_string(out_of_range_thumbnails2(), Enable), BadOptionValueException); + test_loaded_config(config, GCodeThumbnailsFormat::PNG); + } + + + SECTION("Test 1 (invalid_ext)") { + REQUIRE_THROWS_AS(config.load_from_ini_string(invalid_ext_thumbnails(), Enable), BadOptionValueException); + test_loaded_config(config, GCodeThumbnailsFormat::QOI); + } + + SECTION("Test 2 (invalid_ext)") { + REQUIRE_THROWS_AS(config.load_from_ini_string(invalid_ext_thumbnails2(), Enable), BadOptionValueException); + test_loaded_config(config, GCodeThumbnailsFormat::PNG); + } + + + SECTION("Test 1 (invalid_val)") { + REQUIRE_THROWS_AS(config.load_from_ini_string(invalid_val_thumbnails(), Enable), BadOptionValueException); + test_loaded_config(config, GCodeThumbnailsFormat::JPG); + } + + SECTION("Test 2 (invalid_val)") { + REQUIRE_THROWS_AS(config.load_from_ini_string(invalid_val_thumbnails2(), Enable), BadOptionValueException); + test_loaded_config(config, GCodeThumbnailsFormat::PNG); + } + + SECTION("Test 3 (invalid_val)") { + REQUIRE_THROWS_AS(config.load_from_ini_string(invalid_val_thumbnails3(), Enable), BadOptionValueException); + test_loaded_config(config, GCodeThumbnailsFormat::PNG); + } + + SECTION("Test 4 (invalid_val)") { + REQUIRE_THROWS_AS(config.load_from_ini_string(invalid_val_thumbnails4(), Enable), BadOptionValueException); + test_loaded_config(config, GCodeThumbnailsFormat::PNG); + } +} + +TEST_CASE("Validate Old Thumbnails", "[Thumbnails in Config]") { + + DynamicPrintConfig config = thumbnails_config(); + + auto test_loaded_config = [](DynamicPrintConfig& config, GCodeThumbnailsFormat format) { + REQUIRE(!config.opt("thumbnails")->empty()); + REQUIRE(config.option("thumbnails_format")->getInt() == (int)format); + }; + + SECTION("Test 1 (valid)") { + REQUIRE_NOTHROW(config.load_from_ini_string(old_valid_thumbnails(), Enable)); + test_loaded_config(config, GCodeThumbnailsFormat::JPG); + } + + SECTION("Test 2 (valid)") { + REQUIRE_NOTHROW(config.load_from_ini_string(old_valid_thumbnails2(), Enable)); + test_loaded_config(config, GCodeThumbnailsFormat::PNG); + } + + SECTION("Test 1 (invalid)") { + REQUIRE_THROWS_AS(config.load_from_ini_string(old_invalid_thumbnails(), Enable), BadOptionValueException); + test_loaded_config(config, GCodeThumbnailsFormat::JPG); + } + + SECTION("Test 2 (invalid)") { + REQUIRE_THROWS_AS(config.load_from_ini_string(old_invalid_thumbnails2(), Enable), BadOptionValueException); + test_loaded_config(config, GCodeThumbnailsFormat::PNG); + } +} + + + + + diff --git a/tests/thumbnails/test_thumbnails_input_string.cpp b/tests/thumbnails/test_thumbnails_input_string.cpp new file mode 100644 index 0000000000..4c623b2736 --- /dev/null +++ b/tests/thumbnails/test_thumbnails_input_string.cpp @@ -0,0 +1,152 @@ +#include +#include + +#include + +using namespace Slic3r; +using namespace GCodeThumbnails; + + +// Test Thumbnails lines + +static std::string empty_thumbnails() +{ + return ""; +} + +static std::string valid_thumbnails() +{ + return "160x120/PNG, 23x78/QOI, 230x780/JPG"; +} + +static std::string valid_thumbnails2() +{ + return "160x120/PNG, 23x78/QOi, 320x240/PNg, 230x780/JPG"; +} + +static std::string out_of_range_thumbnail() +{ + return "160x1200/PNG, 23x78/QOI, 320x240/PNG, 230x780/JPG"; +} + +static std::string out_of_range_thumbnail2() +{ + return "160x120/PNG, 23x78/QOI, -320x240/PNG, 230x780/JPG"; +} + +static std::string invalid_ext_thumbnail() +{ + return "160x120/PNk, 23x78/QOI, 320x240/PNG, 230x780/JPG"; +} + +static std::string invalid_ext_thumbnail2() +{ + return "160x120/PNG, 23x78/QO, 320x240/PNG, 230x780/JPG"; +} + +static std::string invalid_val_thumbnail() +{ + return "160x/PNg, 23x78/QOI, 320x240/PNG, 230x780/JPG"; +} + +static std::string invalid_val_thumbnail2() +{ + return "x120/PNg, 23x78/QOI, 320x240/PNG, 230x780/JPG"; +} + +static std::string invalid_val_thumbnail3() +{ + return "x/PNg, 23x78/QOI, 320x240/PNG, 230x780/JPG"; +} + +static std::string invalid_val_thumbnail4() +{ + return "23*78/QOI, 320x240/PNG, 230x780/JPG"; +} + + +TEST_CASE("Empty Thumbnails", "[Thumbnails]") { + auto [thumbnails, errors] = make_and_check_thumbnail_list(empty_thumbnails()); + REQUIRE(errors == enum_bitmask()); + REQUIRE(thumbnails.empty()); +} + +TEST_CASE("Valid Thumbnails", "[Thumbnails]") { + + SECTION("Test 1") { + auto [thumbnails, errors] = make_and_check_thumbnail_list(valid_thumbnails()); + REQUIRE(errors == enum_bitmask()); + REQUIRE(thumbnails.size() == 3); + } + + SECTION("Test 2") { + auto [thumbnails, errors] = make_and_check_thumbnail_list(valid_thumbnails2()); + REQUIRE(errors == enum_bitmask()); + REQUIRE(thumbnails.size() == 4); + } +} + +TEST_CASE("Out of range Thumbnails", "[Thumbnails]") { + + SECTION("Test 1") { + auto [thumbnails, errors] = make_and_check_thumbnail_list(out_of_range_thumbnail()); + REQUIRE(errors != enum_bitmask()); + REQUIRE(errors.has(ThumbnailError::OutOfRange)); + REQUIRE(thumbnails.size() == 3); + } + + SECTION("Test 2") { + auto [thumbnails, errors] = make_and_check_thumbnail_list(out_of_range_thumbnail2()); + REQUIRE(errors != enum_bitmask()); + REQUIRE(errors.has(ThumbnailError::OutOfRange)); + REQUIRE(thumbnails.size() == 3); + } +} + +TEST_CASE("Invalid extention Thumbnails", "[Thumbnails]") { + + SECTION("Test 1") { + auto [thumbnails, errors] = make_and_check_thumbnail_list(invalid_ext_thumbnail()); + REQUIRE(errors != enum_bitmask()); + REQUIRE(errors.has(ThumbnailError::InvalidExt)); + REQUIRE(thumbnails.size() == 4); + } + + SECTION("Test 2") { + auto [thumbnails, errors] = make_and_check_thumbnail_list(invalid_ext_thumbnail2()); + REQUIRE(errors != enum_bitmask()); + REQUIRE(errors.has(ThumbnailError::InvalidExt)); + REQUIRE(thumbnails.size() == 4); + } +} + +TEST_CASE("Invalid value Thumbnails", "[Thumbnails]") { + + SECTION("Test 1") { + auto [thumbnails, errors] = make_and_check_thumbnail_list(invalid_val_thumbnail()); + REQUIRE(errors != enum_bitmask()); + REQUIRE(errors.has(ThumbnailError::InvalidVal)); + REQUIRE(thumbnails.size() == 3); + } + + SECTION("Test 2") { + auto [thumbnails, errors] = make_and_check_thumbnail_list(invalid_val_thumbnail2()); + REQUIRE(errors != enum_bitmask()); + REQUIRE(errors.has(ThumbnailError::InvalidVal)); + REQUIRE(thumbnails.size() == 3); + } + + SECTION("Test 3") { + auto [thumbnails, errors] = make_and_check_thumbnail_list(invalid_val_thumbnail3()); + REQUIRE(errors != enum_bitmask()); + REQUIRE(errors.has(ThumbnailError::InvalidVal)); + REQUIRE(thumbnails.size() == 3); + } + + SECTION("Test 4") { + auto [thumbnails, errors] = make_and_check_thumbnail_list(invalid_val_thumbnail4()); + REQUIRE(errors != enum_bitmask()); + REQUIRE(errors.has(ThumbnailError::InvalidVal)); + REQUIRE(thumbnails.size() == 2); + } +} \ No newline at end of file diff --git a/tests/thumbnails/thumbnails_tests_main.cpp b/tests/thumbnails/thumbnails_tests_main.cpp new file mode 100644 index 0000000000..b2aa80259d --- /dev/null +++ b/tests/thumbnails/thumbnails_tests_main.cpp @@ -0,0 +1 @@ +#include