diff options
122 files changed, 3298 insertions, 1336 deletions
diff --git a/create_release.py b/create_release.py index 8145b3ee3..118ee3495 100644 --- a/create_release.py +++ b/create_release.py @@ -17,7 +17,7 @@ import subprocess repo = "supermerill/SuperSlicer" program_name = "SuperSlicer" path_7zip = r"C:\Program Files\7-Zip\7z.exe" -github_auth_token = "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" +github_auth_token = "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" def get_version(): settings_stream = open("./version.inc", mode="r", encoding="utf-8"); diff --git a/resources/icons/info.svg b/resources/icons/info.svg new file mode 100644 index 000000000..276b26061 --- /dev/null +++ b/resources/icons/info.svg @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + id="error" + x="0px" + y="0px" + viewBox="0 0 200 200" + enable-background="new 0 0 100 100" + xml:space="preserve" + sodipodi:docname="notification_error.svg" + width="200" + height="200" + inkscape:version="1.0 (4035a4fb49, 2020-05-01)"><metadata + id="metadata19"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs17" /><sodipodi:namedview + inkscape:document-rotation="0" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="2560" + inkscape:window-height="1377" + id="namedview15" + showgrid="false" + inkscape:zoom="5.04" + inkscape:cx="117.17146" + inkscape:cy="98.609664" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" + inkscape:current-layer="error" /> +<g + id="g4" + transform="matrix(2.52,0,0,2.52,-26,-26)"> + <path + fill="#808080" + d="m 50,54.25 c -2.35,0 -4.25,-1.9 -4.25,-4.25 V 35 c 0,-2.35 1.9,-4.25 4.25,-4.25 2.35,0 4.25,1.9 4.25,4.25 v 15 c 0,2.35 -1.9,4.25 -4.25,4.25 z" + id="path2" /> +</g> +<g + id="g8" + transform="matrix(2.52,0,0,2.52,-26,-26)"> + <circle + fill="#808080" + cx="50" + cy="65" + r="5" + id="circle6" /> +</g> +<g + id="g12" + transform="matrix(2.52,0,0,2.52,-26,-26)"> + <path + fill="#808080" + d="M 50,89.25 C 28.36,89.25 10.75,71.64 10.75,50 10.75,28.36 28.36,10.75 50,10.75 71.64,10.75 89.25,28.36 89.25,50 89.25,71.64 71.64,89.25 50,89.25 Z m 0,-70 C 33.05,19.25 19.25,33.04 19.25,50 19.25,66.95 33.04,80.75 50,80.75 66.95,80.75 80.75,66.96 80.75,50 80.75,33.05 66.95,19.25 50,19.25 Z" + id="path10" /> +</g> +</svg> diff --git a/resources/icons/resin_cog.svg b/resources/icons/resin_cog.svg new file mode 100644 index 000000000..fbe4d9c74 --- /dev/null +++ b/resources/icons/resin_cog.svg @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve"> +<g id="resin" transform="translate(2)"> + <rect x="4" y="7" fill="#2172eb" width="8" height="8"/> + <path fill="none" stroke="#808080" stroke-linecap="round" stroke-miterlimit="10" d="M4.5,15h6.99c0.28,0,0.5-0.23,0.5-0.5V6 + c0-1-2-1-2-2s0-1,0-1h1V1.5C11,1.23,10.77,1,10.5,1H5.5C5.23,1,5,1.23,5,1.5V3h1v1c0,1-2,1-2,2v8.5C4,14.77,4.23,15,4.5,15z"/> +</g> +<g + id="machine_x2B_cog" + transform="matrix(0.83360586,0,0,0.83360586,-0.53099627,-0.47101916)"><path + style="fill:#808080;stroke:none;stroke-opacity:1" + inkscape:connector-curvature="0" + d="M 13.77,6.39 C 13.64,5.92 13.45,5.47 13.22,5.06 l 0.43,-1.3 -1.41,-1.41 -1.3,0.43 C 10.52,2.55 10.08,2.36 9.61,2.23 L 9,1 H 7 L 6.39,2.23 C 5.92,2.36 5.47,2.54 5.06,2.78 L 3.76,2.35 2.34,3.76 2.77,5.06 C 2.54,5.47 2.36,5.92 2.23,6.39 L 1,7 v 2 l 1.23,0.61 c 0.13,0.47 0.32,0.92 0.55,1.33 l -0.43,1.3 1.41,1.41 1.3,-0.43 c 0.42,0.23 0.86,0.42 1.33,0.55 L 7,15 h 2 l 0.61,-1.23 c 0.47,-0.13 0.92,-0.32 1.33,-0.55 l 1.3,0.43 1.41,-1.41 -0.43,-1.3 c 0.23,-0.42 0.42,-0.86 0.55,-1.33 L 15,9 V 7 Z M 8,13 C 5.24,13 3,10.76 3,8 3,5.24 5.24,3 8,3 c 2.76,0 5,2.24 5,5 0,2.76 -2.24,5 -5,5 z" + id="path2" /><path + style="fill:#2172eb" + inkscape:connector-curvature="0" + d="M 11.3,7.08 C 11.23,6.81 11.12,6.56 10.99,6.32 L 11.24,5.58 10.43,4.77 9.68,5.01 C 9.45,4.88 9.19,4.78 8.92,4.7 L 8.57,4 H 7.43 L 7.08,4.7 C 6.81,4.78 6.55,4.88 6.32,5.01 L 5.58,4.77 4.77,5.58 5.02,6.32 C 4.88,6.55 4.78,6.81 4.7,7.08 L 4,7.43 v 1.14 l 0.7,0.35 c 0.07,0.27 0.18,0.52 0.31,0.76 l -0.25,0.74 0.81,0.81 0.74,-0.25 c 0.24,0.13 0.49,0.24 0.76,0.31 L 7.43,12 h 1.14 l 0.35,-0.7 c 0.27,-0.07 0.52,-0.18 0.76,-0.31 l 0.74,0.25 0.81,-0.81 -0.25,-0.74 C 11.11,9.45 11.22,9.2 11.29,8.93 L 12,8.57 V 7.43 Z M 8,10.86 C 6.42,10.86 5.14,9.58 5.14,8 5.14,6.42 6.42,5.14 8,5.14 c 1.58,0 2.86,1.28 2.86,2.86 0,1.58 -1.28,2.86 -2.86,2.86 z" + id="path4" /></g> +</svg> diff --git a/resources/icons/sla_printer_cog.svg b/resources/icons/sla_printer_cog.svg new file mode 100644 index 000000000..1fb5e5a12 --- /dev/null +++ b/resources/icons/sla_printer_cog.svg @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve"> +<g id="sla" transform="matrix(0.90508475,0,0,0.90508475,2.4542373,1.4237288)"> + <rect x="3" y="11" fill="#808080" width="1" height="4"/> + <rect x="12" y="11" fill="#808080" width="1" height="4"/> + <rect x="7.5" y="6.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 19.5 3.5)" fill="#808080" width="1" height="10"/> + <rect x="7.5" y="9.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 22.5 6.5)" fill="#808080" width="1" height="10"/> + <rect x="10.5" y="11.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 24.5 1.5)" fill="#808080" width="2" height="3"/> + <rect x="3.5" y="11.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 17.5 8.5)" fill="#808080" width="2" height="3"/> + <rect x="3" y="1" fill="#2172eb" width="10" height="10"/> +</g> +<g + id="machine_x2B_cog" + transform="matrix(0.83360586,0,0,0.83360586,-0.53099627,-0.47101916)"><path + style="fill:#808080;stroke:none;stroke-opacity:1" + inkscape:connector-curvature="0" + d="M 13.77,6.39 C 13.64,5.92 13.45,5.47 13.22,5.06 l 0.43,-1.3 -1.41,-1.41 -1.3,0.43 C 10.52,2.55 10.08,2.36 9.61,2.23 L 9,1 H 7 L 6.39,2.23 C 5.92,2.36 5.47,2.54 5.06,2.78 L 3.76,2.35 2.34,3.76 2.77,5.06 C 2.54,5.47 2.36,5.92 2.23,6.39 L 1,7 v 2 l 1.23,0.61 c 0.13,0.47 0.32,0.92 0.55,1.33 l -0.43,1.3 1.41,1.41 1.3,-0.43 c 0.42,0.23 0.86,0.42 1.33,0.55 L 7,15 h 2 l 0.61,-1.23 c 0.47,-0.13 0.92,-0.32 1.33,-0.55 l 1.3,0.43 1.41,-1.41 -0.43,-1.3 c 0.23,-0.42 0.42,-0.86 0.55,-1.33 L 15,9 V 7 Z M 8,13 C 5.24,13 3,10.76 3,8 3,5.24 5.24,3 8,3 c 2.76,0 5,2.24 5,5 0,2.76 -2.24,5 -5,5 z" + id="path2" /><path + style="fill:#808080" + inkscape:connector-curvature="0" + d="M 11.3,7.08 C 11.23,6.81 11.12,6.56 10.99,6.32 L 11.24,5.58 10.43,4.77 9.68,5.01 C 9.45,4.88 9.19,4.78 8.92,4.7 L 8.57,4 H 7.43 L 7.08,4.7 C 6.81,4.78 6.55,4.88 6.32,5.01 L 5.58,4.77 4.77,5.58 5.02,6.32 C 4.88,6.55 4.78,6.81 4.7,7.08 L 4,7.43 v 1.14 l 0.7,0.35 c 0.07,0.27 0.18,0.52 0.31,0.76 l -0.25,0.74 0.81,0.81 0.74,-0.25 c 0.24,0.13 0.49,0.24 0.76,0.31 L 7.43,12 h 1.14 l 0.35,-0.7 c 0.27,-0.07 0.52,-0.18 0.76,-0.31 l 0.74,0.25 0.81,-0.81 -0.25,-0.74 C 11.11,9.45 11.22,9.2 11.29,8.93 L 12,8.57 V 7.43 Z M 8,10.86 C 6.42,10.86 5.14,9.58 5.14,8 5.14,6.42 6.42,5.14 8,5.14 c 1.58,0 2.86,1.28 2.86,2.86 0,1.58 -1.28,2.86 -2.86,2.86 z" + id="path4" /></g> +</svg> diff --git a/resources/icons/white/info.svg b/resources/icons/white/info.svg new file mode 100644 index 000000000..db227aa32 --- /dev/null +++ b/resources/icons/white/info.svg @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + id="error" + x="0px" + y="0px" + viewBox="0 0 200 200" + enable-background="new 0 0 100 100" + xml:space="preserve" + sodipodi:docname="notification_error.svg" + width="200" + height="200" + inkscape:version="1.0 (4035a4fb49, 2020-05-01)"><metadata + id="metadata19"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs17" /><sodipodi:namedview + inkscape:document-rotation="0" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="2560" + inkscape:window-height="1377" + id="namedview15" + showgrid="false" + inkscape:zoom="5.04" + inkscape:cx="117.17146" + inkscape:cy="98.609664" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" + inkscape:current-layer="error" /> +<g + id="g4" + transform="matrix(2.52,0,0,2.52,-26,-26)"> + <path + fill="#FFFFFF" + d="m 50,54.25 c -2.35,0 -4.25,-1.9 -4.25,-4.25 V 35 c 0,-2.35 1.9,-4.25 4.25,-4.25 2.35,0 4.25,1.9 4.25,4.25 v 15 c 0,2.35 -1.9,4.25 -4.25,4.25 z" + id="path2" /> +</g> +<g + id="g8" + transform="matrix(2.52,0,0,2.52,-26,-26)"> + <circle + fill="#FFFFFF" + cx="50" + cy="65" + r="5" + id="circle6" /> +</g> +<g + id="g12" + transform="matrix(2.52,0,0,2.52,-26,-26)"> + <path + fill="#FFFFFF" + d="M 50,89.25 C 28.36,89.25 10.75,71.64 10.75,50 10.75,28.36 28.36,10.75 50,10.75 71.64,10.75 89.25,28.36 89.25,50 89.25,71.64 71.64,89.25 50,89.25 Z m 0,-70 C 33.05,19.25 19.25,33.04 19.25,50 19.25,66.95 33.04,80.75 50,80.75 66.95,80.75 80.75,66.96 80.75,50 80.75,33.05 66.95,19.25 50,19.25 Z" + id="path10" /> +</g> +</svg> diff --git a/resources/profiles b/resources/profiles -Subproject 118aa919c16837eb2ff6ba97e2934fa4144ef80 +Subproject b2309c4a532bd2bbe26c5d9f1bff8bb2bee707f diff --git a/resources/ui_layout/print.ui b/resources/ui_layout/print.ui index 991ac342a..f5e1bd710 100644 --- a/resources/ui_layout/print.ui +++ b/resources/ui_layout/print.ui @@ -14,6 +14,7 @@ group:Horizontal shells setting:bottom_solid_min_thickness end_line top_bottom_shell_thickness_explanation + setting:solid_over_perimeters setting:enforce_full_fill_volume group:Quality line:Only one perimeter on Top surfaces @@ -92,7 +93,10 @@ group:Modifying slices line:XY compensation setting:width$6:xy_size_compensation setting:width$6:xy_inner_size_compensation + end_line + line:XY First layer compensation setting:width$6:first_layer_size_compensation + setting:width$6:first_layer_size_compensation_layers end_line line:Vertical Hole shrinking compensation setting:width$6:hole_size_compensation @@ -225,6 +229,9 @@ group:label_width$8:sidetext_width$7:Speed for print moves setting:width$4:solid_infill_speed setting:width$4:top_solid_infill_speed end_line + line:Ironing speed + setting:label$_:width$4:ironing_speed + end_line line:Support speed setting:width$4:support_material_speed setting:width$4:support_material_interface_speed @@ -238,7 +245,6 @@ group:label_width$8:sidetext_width$7:Speed for print moves setting:width$4:gap_fill_speed setting:width$4:thin_walls_speed end_line - setting:label$Ironing post-process speed:label_width$30:width$4:ironing_speed group:Speed for non-print moves line:Travel speed setting:label$xy:travel_speed @@ -254,11 +260,12 @@ group:sidetext_width$7:Modifiers setting:label_width$8:width$4:small_perimeter_max_length setting:label_width$8:width$4:small_perimeter_speed group:Acceleration control (advanced) + setting:default_acceleration setting:perimeter_acceleration setting:infill_acceleration setting:bridge_acceleration setting:first_layer_acceleration - setting:default_acceleration + setting:travel_acceleration group:Autospeed (advanced) setting:label$Volumetric speed for Autospeed:max_volumetric_speed setting:max_print_speed @@ -348,6 +355,7 @@ group:Plater group:Sequential printing setting:complete_objects setting:complete_objects_one_skirt + setting:complete_objects_one_brim setting:complete_objects_sort line:Extruder clearance (mm) setting:width$6:extruder_clearance_radius diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 17811f3c9..ff951e6b4 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -110,7 +110,8 @@ int CLI::run(int argc, char **argv) boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), GCODEVIEWER_APP_CMD); #endif // _WIN32 - const std::vector<std::string> &load_configs = m_config.option<ConfigOptionStrings>("load", true)->values; + const std::vector<std::string> &load_configs = m_config.option<ConfigOptionStrings>("load", true)->values; + const ForwardCompatibilitySubstitutionRule config_substitution_rule = m_config.option<ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>>("config_compatibility", true)->value; // load config files supplied via --load for (auto const &file : load_configs) { @@ -122,13 +123,19 @@ int CLI::run(int argc, char **argv) return 1; } } - DynamicPrintConfig config; + DynamicPrintConfig config; + ConfigSubstitutions config_substitutions; try { - config.load(file); + config_substitutions = config.load(file, config_substitution_rule); } catch (std::exception &ex) { - boost::nowide::cerr << "Error while reading config file: " << ex.what() << std::endl; + boost::nowide::cerr << "Error while reading config file \"" << file << "\": " << ex.what() << std::endl; return 1; } + if (! config_substitutions.empty()) { + boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n"; + for (const ConfigSubstitution &subst : config_substitutions) + boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n"; + } config.normalize_fdm(); PrinterTechnology other_printer_technology = Slic3r::printer_technology(config); if (printer_technology == ptUnknown) { @@ -166,7 +173,9 @@ int CLI::run(int argc, char **argv) try { // When loading an AMF or 3MF, config is imported as well, including the printer technology. DynamicPrintConfig config; - model = Model::read_from_file(file, &config, true); + ConfigSubstitutionContext config_substitutions(config_substitution_rule); + //FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ? + model = Model::read_from_file(file, &config, &config_substitutions, Model::LoadAttribute::AddDefaultInstances); PrinterTechnology other_printer_technology = Slic3r::printer_technology(config); if (printer_technology == ptUnknown) { printer_technology = other_printer_technology; @@ -175,6 +184,11 @@ int CLI::run(int argc, char **argv) boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; return 1; } + if (! config_substitutions.substitutions.empty()) { + boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n"; + for (const ConfigSubstitution& subst : config_substitutions.substitutions) + boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n"; + } // config is applied to m_print_config before the current m_config values. config += std::move(m_print_config); m_print_config = std::move(config); diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 9504b13bb..3824fb749 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -3,6 +3,7 @@ #include "AppConfig.hpp" #include "Exception.hpp" #include "Thread.hpp" +#include "format.hpp" #include <utility> #include <vector> @@ -16,9 +17,13 @@ #include <boost/property_tree/ptree_fwd.hpp> #include <boost/algorithm/string/predicate.hpp> #include <boost/format/format_fwd.hpp> +#include <boost/log/trivial.hpp> -//#include <wx/string.h> -//#include "I18N.hpp" +#ifdef WIN32 +//FIXME replace the two following includes with <boost/md5.hpp> after it becomes mainstream. +#include <boost/uuid/detail/md5.hpp> +#include <boost/algorithm/hex.hpp> +#endif namespace Slic3r { @@ -263,15 +268,103 @@ void AppConfig::set_defaults() erase("", "object_settings_size"); } +#ifdef WIN32 +static std::string appconfig_md5_hash_line(const std::string_view data) +{ + //FIXME replace the two following includes with <boost/md5.hpp> after it becomes mainstream. + // return boost::md5(data).hex_str_value(); + // boost::uuids::detail::md5 is an internal namespace thus it may change in the future. + // Also this implementation is not the fastest, it was designed for short blocks of text. + using boost::uuids::detail::md5; + md5 md5_hash; + // unsigned int[4], 128 bits + md5::digest_type md5_digest{}; + std::string md5_digest_str; + md5_hash.process_bytes(data.data(), data.size()); + md5_hash.get_digest(md5_digest); + boost::algorithm::hex(md5_digest, md5_digest + std::size(md5_digest), std::back_inserter(md5_digest_str)); + // MD5 hash is 32 HEX digits long. + assert(md5_digest_str.size() == 32); + // This line will be emited at the end of the file. + return "# MD5 checksum " + md5_digest_str + "\n"; +}; + +// Assume that the last line with the comment inside the config file contains a checksum and that the user didn't modify the config file. +static bool verify_config_file_checksum(boost::nowide::ifstream &ifs) +{ + auto read_whole_config_file = [&ifs]() -> std::string { + std::stringstream ss; + ss << ifs.rdbuf(); + return ss.str(); + }; + + ifs.seekg(0, boost::nowide::ifstream::beg); + std::string whole_config = read_whole_config_file(); + + // The checksum should be on the last line in the config file. + if (size_t last_comment_pos = whole_config.find_last_of('#'); last_comment_pos != std::string::npos) { + // Split read config into two parts, one with checksum, and the second part is part with configuration from the checksum was computed. + // Verify existence and validity of the MD5 checksum line at the end of the file. + // When the checksum isn't found, the checksum was not saved correctly, it was removed or it is an older config file without the checksum. + // If the checksum is incorrect, then the file was either not saved correctly or modified. + if (std::string_view(whole_config.c_str() + last_comment_pos, whole_config.size() - last_comment_pos) == appconfig_md5_hash_line({ whole_config.data(), last_comment_pos })) + return true; + } + return false; +} +#endif + std::string AppConfig::load() { // 1) Read the complete config file into a boost::property_tree. namespace pt = boost::property_tree; pt::ptree tree; - boost::nowide::ifstream ifs(AppConfig::config_path()); + boost::nowide::ifstream ifs; + bool recovered = false; + try { + ifs.open(AppConfig::config_path()); +#ifdef WIN32 + // Verify the checksum of the config file without taking just for debugging purpose. + if (!verify_config_file_checksum(ifs)) + BOOST_LOG_TRIVIAL(info) << "The configuration file " << AppConfig::config_path() << + " has a wrong MD5 checksum or the checksum is missing. This may indicate a file corruption or a harmless user edit."; + + ifs.seekg(0, boost::nowide::ifstream::beg); +#endif pt::read_ini(ifs, tree); } catch (pt::ptree_error& ex) { +#ifdef WIN32 + // The configuration file is corrupted, try replacing it with the backup configuration. + ifs.close(); + std::string backup_path = (boost::format("%1%.bak") % AppConfig::config_path()).str(); + if (boost::filesystem::exists(backup_path)) { + // Compute checksum of the configuration backup file and try to load configuration from it when the checksum is correct. + boost::nowide::ifstream backup_ifs(backup_path); + if (!verify_config_file_checksum(backup_ifs)) { + BOOST_LOG_TRIVIAL(error) << format("Both \"%1%\" and \"%2%\" are corrupted. It isn't possible to restore configuration from the backup.", AppConfig::config_path(), backup_path); + backup_ifs.close(); + boost::filesystem::remove(backup_path); + } else if (std::string error_message; copy_file(backup_path, AppConfig::config_path(), error_message, false) != SUCCESS) { + BOOST_LOG_TRIVIAL(error) << format("Configuration file \"%1%\" is corrupted. Failed to restore from backup \"%2%\": %3%", AppConfig::config_path(), backup_path, error_message); + backup_ifs.close(); + boost::filesystem::remove(backup_path); + } else { + BOOST_LOG_TRIVIAL(info) << format("Configuration file \"%1%\" was corrupted. It has been succesfully restored from the backup \"%2%\".", AppConfig::config_path(), backup_path); + // Try parse configuration file after restore from backup. + try { + ifs.open(AppConfig::config_path()); + pt::read_ini(ifs, tree); + recovered = true; + } catch (pt::ptree_error& ex) { + BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\" after it has been restored from backup: %2%", AppConfig::config_path(), ex.what()); + } + } + } else +#endif // WIN32 + BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\": %2%", AppConfig::config_path(), ex.what()); + if (! recovered) { + // Report the initial error of parsing PrusaSlicer.ini. // Error while parsing config file. We'll customize the error message and rethrow to be displayed. // ! But to avoid the use of _utf8 (related to use of wxWidgets) // we will rethrow this exception from the place of load() call, if returned value wouldn't be empty @@ -283,6 +376,7 @@ std::string AppConfig::load() */ return ex.what(); } + } // 2) Parse the property_tree, extract the sections and key / value pairs. for (const auto §ion : tree) { @@ -358,22 +452,21 @@ void AppConfig::save() const auto path = config_path(); std::string path_pid = (boost::format("%1%.%2%") % path % get_current_pid()).str(); - boost::nowide::ofstream c; - c.open(path_pid, std::ios::out | std::ios::trunc); + std::stringstream config_ss; if (m_mode == EAppMode::Editor) - c << "# " << Slic3r::header_slic3r_generated() << std::endl; + config_ss << "# " << Slic3r::header_slic3r_generated() << std::endl; else - c << "# " << Slic3r::header_gcodeviewer_generated() << std::endl; + config_ss << "# " << Slic3r::header_gcodeviewer_generated() << std::endl; // Make sure the "no" category is written first. - for (const std::pair<std::string, std::string> &kvp : m_storage[""]) - c << kvp.first << " = " << kvp.second << std::endl; + for (const auto& kvp : m_storage[""]) + config_ss << kvp.first << " = " << kvp.second << std::endl; // Write the other categories. for (const auto category : m_storage) { if (category.first.empty()) continue; - c << std::endl << "[" << category.first << "]" << std::endl; - for (const std::pair<std::string, std::string> &kvp : category.second) - c << kvp.first << " = " << kvp.second << std::endl; + config_ss << std::endl << "[" << category.first << "]" << std::endl; + for (const auto& kvp : category.second) + config_ss << kvp.first << " = " << kvp.second << std::endl; } // Write vendor sections for (const auto &vendor : m_vendors) { @@ -381,17 +474,42 @@ void AppConfig::save() for (const auto &model : vendor.second) { size_sum += model.second.size(); } if (size_sum == 0) { continue; } - c << std::endl << "[" << VENDOR_PREFIX << vendor.first << "]" << std::endl; + config_ss << std::endl << "[" << VENDOR_PREFIX << vendor.first << "]" << std::endl; for (const auto &model : vendor.second) { - if (model.second.size() == 0) { continue; } + if (model.second.empty()) { continue; } const std::vector<std::string> variants(model.second.begin(), model.second.end()); const auto escaped = escape_strings_cstyle(variants); - c << MODEL_PREFIX << model.first << " = " << escaped << std::endl; + config_ss << MODEL_PREFIX << model.first << " = " << escaped << std::endl; } } + // One empty line before the MD5 sum. + config_ss << std::endl; + + std::string config_str = config_ss.str(); + boost::nowide::ofstream c; + c.open(path_pid, std::ios::out | std::ios::trunc); + c << config_str; +#ifdef WIN32 + // WIN32 specific: The final "rename_file()" call is not safe in case of an application crash, there is no atomic "rename file" API + // provided by Windows (sic!). Therefore we save a MD5 checksum to be able to verify file corruption. In addition, + // we save the config file into a backup first before moving it to the final destination. + c << appconfig_md5_hash_line(config_str); +#endif c.close(); +#ifdef WIN32 + // Make a backup of the configuration file before copying it to the final destination. + std::string error_message; + std::string backup_path = (boost::format("%1%.bak") % path).str(); + // Copy configuration file with PID suffix into the configuration file with "bak" suffix. + if (copy_file(path_pid, backup_path, error_message, false) != SUCCESS) + BOOST_LOG_TRIVIAL(error) << "Copying from " << path_pid << " to " << backup_path << " failed. Failed to create a backup configuration."; +#endif + + // Rename the config atomically. + // On Windows, the rename is likely NOT atomic, thus it may fail if PrusaSlicer crashes on another thread in the meanwhile. + // To cope with that, we already made a backup of the config on Windows. rename_file(path_pid, path); m_dirty = false; } diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index c8ccd18cd..0a53a5330 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -37,7 +37,7 @@ public: // Load the slic3r.ini from a user profile directory (or a datadir, if configured). // return error string or empty strinf - std::string load(); + std::string load(); // Store the slic3r.ini into a user profile directory (or a datadir, if configured). void save(); diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 33e79aa88..1678d2e2f 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -34,6 +34,7 @@ add_library(libslic3r STATIC EdgeGrid.hpp ElephantFootCompensation.cpp ElephantFootCompensation.hpp + enum_bitmask.hpp ExPolygon.cpp ExPolygon.hpp ExPolygonCollection.cpp diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index db5dd463c..0528c137a 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -1228,8 +1228,9 @@ ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector<s holes.reserve(expoly.holes.size()); for (const Polygon& hole : expoly.holes) append(holes, fix_after_outer_offset(mittered_offset_path_scaled(hole.points, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftNegative, false)); + //tiny holes can be reduced to giberish, get rid of them. for (auto it = holes.begin(); it != holes.end();) - if (ClipperLib::Area(*it) < 0.) { + if (ClipperLib::Area(*it) < CLIPPER_OFFSET_SCALE*CLIPPER_OFFSET_SCALE) { it = holes.erase(it); } else ++it; diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index b4e9b4281..24f2a44af 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -21,6 +21,10 @@ #include <boost/format.hpp> #include <string.h> +//FIXME for GCodeFlavor and gcfMarlin (for forward-compatibility conversion) +// This is not nice, likely it would be better to pass the ConfigSubstitutionContext to handle_legacy(). +#include "PrintConfig.hpp" + #define L(s) (s) namespace Slic3r { @@ -246,6 +250,10 @@ std::string escape_ampersand(const std::string& str) return std::string(out.data(), outptr - out.data()); } +void ConfigOptionDeleter::operator()(ConfigOption* p) { + delete p; +} + std::vector<std::string> ConfigOptionDef::cli_args(const std::string &key) const { std::vector<std::string> args; @@ -272,7 +280,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const case coPercents: return new ConfigOptionPercentsNullable(); case coFloatsOrPercents: return new ConfigOptionFloatsOrPercentsNullable(); case coBools: return new ConfigOptionBoolsNullable(); - default: throw Slic3r::RuntimeError(std::string("Unknown option type for nullable option ") + this->label); + default: throw ConfigurationError(std::string("Unknown option type for nullable option ") + this->label); } } else { switch (this->type) { @@ -293,7 +301,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const case coBool: return new ConfigOptionBool(); case coBools: return new ConfigOptionBools(); case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map); - default: throw Slic3r::RuntimeError(std::string("Unknown option type for option ") + this->label); + default: throw ConfigurationError(std::string("Unknown option type for option ") + this->label); } } } @@ -396,7 +404,8 @@ std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, s // right: option description std::string descr = def.tooltip; - if (show_defaults && def.default_value && def.type != coBool + bool show_defaults_this = show_defaults || def.opt_key == "config_compatibility"; + if (show_defaults_this && def.default_value && def.type != coBool && (def.type != coString || !def.default_value->serialize().empty())) { descr += " ("; if (!def.sidetext.empty()) { @@ -528,7 +537,7 @@ void ConfigBase::set(const std::string &opt_key, double value, bool create) } } -bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append) +bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions_ctxt, bool append) { t_config_option_key opt_key = opt_key_src; std::string value = value_src; @@ -538,23 +547,22 @@ bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, if (opt_key.empty()) // Ignore the option. return true; - return this->set_deserialize_raw(opt_key, value, append); + return this->set_deserialize_raw(opt_key, value, substitutions_ctxt, append); } -void ConfigBase::set_deserialize(const t_config_option_key& opt_key_src, const std::string& value_src, bool append) +void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions_ctxt, bool append) { - if (!this->set_deserialize_nothrow(opt_key_src, value_src, append)) { - throw BadOptionTypeException(format("ConfigBase::set_deserialize() failed for parameter \"%1%\", value \"%2%\"", opt_key_src, value_src)); + if (! this->set_deserialize_nothrow(opt_key_src, value_src, substitutions_ctxt, append)) + throw BadOptionValueException(format("Invalid value provided for parameter %1%: %2%", opt_key_src, value_src)); } -} -void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items) +void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions_ctxt) { for (const SetDeserializeItem &item : items) - this->set_deserialize(item.opt_key, item.opt_value, item.append); + this->set_deserialize(item.opt_key, item.opt_value, substitutions_ctxt, item.append); } -bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, bool append) +bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, ConfigSubstitutionContext& substitutions_ctxt, bool append) { t_config_option_key opt_key = opt_key_src; // Try to deserialize the option by its name. @@ -583,7 +591,7 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con // Aliasing for example "solid_layers" to "top_solid_layers" and "bottom_solid_layers". for (const t_config_option_key &shortcut : optdef->shortcut) // Recursive call. - if (! this->set_deserialize_raw(shortcut, value, append)) + if (! this->set_deserialize_raw(shortcut, value, substitutions_ctxt, append)) return false; return true; } @@ -591,10 +599,56 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con ConfigOption *opt = this->option(opt_key, true); if (opt == nullptr) throw new UnknownOptionException(opt_key); + bool success = true; + if (!optdef->can_phony || !value.empty()) { + success = true; + bool substituted = false; + if (optdef->type == coBools && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable) { + //FIXME Special handling of vectors of bools, quick and not so dirty solution before PrusaSlicer 2.3.2 release. + bool nullable = opt->nullable(); + ConfigHelpers::DeserializationSubstitution default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToFalse; + if (optdef->default_value) { + // Default value for vectors of booleans used in a "per extruder" context, thus the default contains just a single value. + assert(dynamic_cast<const ConfigOptionVector<unsigned char>*>(optdef->default_value.get())); + auto &values = static_cast<const ConfigOptionVector<unsigned char>*>(optdef->default_value.get())->values; + if (values.size() == 1 && values.front() == 1) + default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToTrue; + } + auto result = nullable ? + static_cast<ConfigOptionBoolsNullable*>(opt)->deserialize_with_substitutions(value, append, default_value) : + static_cast<ConfigOptionBools*>(opt)->deserialize_with_substitutions(value, append, default_value); + success = result != ConfigHelpers::DeserializationResult::Failed; + substituted = result == ConfigHelpers::DeserializationResult::Substituted; + } else { + success = opt->deserialize(value, append); + if (! success && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable && + // Only allow substitutions of an enum value by another enum value or a boolean value with an enum value. + // That means, we expect enum values being added in the future and possibly booleans being converted to enums. + (optdef->type == coEnum || optdef->type == coBool) && ConfigHelpers::looks_like_enum_value(value)) { + // Deserialize failed, try to substitute with a default value. + //assert(substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSilent); + if (optdef->type == coEnum && opt_key == "gcode_flavor" && (value == "marlin2" || value == "marlinfirmware")) + static_cast<ConfigOptionEnum<GCodeFlavor>*>(opt)->value = gcfMarlin; + else if (optdef->type == coBool) + static_cast<ConfigOptionBool*>(opt)->value = ConfigHelpers::enum_looks_like_true_value(value); + else + // Just use the default of the option. + opt->set(optdef->default_value.get()); + success = true; + substituted = true; + } + } - bool ok = true; - if (!optdef->can_phony || !value.empty()) - ok = opt->deserialize(value, append); + if (substituted && (substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || + substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent)) { + // Log the substitution. + ConfigSubstitution config_substitution; + config_substitution.opt_def = optdef; + config_substitution.old_value = value; + config_substitution.new_value = ConfigOptionUniquePtr(opt->clone()); + substitutions_ctxt.substitutions.emplace_back(std::move(config_substitution)); + } + } //set phony status if (optdef->can_phony) if(value.empty()) @@ -603,8 +657,7 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con opt->set_phony(false); else opt->set_phony(false); - - return ok; + return success; } // Return an absolute value of a possibly relative config variable. @@ -665,7 +718,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const cast_opt->get_abs_value(this->get_abs_value(opt_def->ratio_over)); } std::stringstream ss; ss << "ConfigBase::get_abs_value(): "<< opt_key<<" has not a valid option type for get_abs_value()"; - throw Slic3r::RuntimeError(ss.str()); + throw ConfigurationError(ss.str()); } // Return an absolute value of a possibly relative config variable. @@ -676,7 +729,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati const ConfigOption *raw_opt = this->option(opt_key); assert(raw_opt != nullptr); if (raw_opt->type() != coFloatOrPercent) - throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent"); + throw ConfigurationError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent"); // Compute absolute value. return static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(ratio_over); } @@ -699,37 +752,43 @@ void ConfigBase::setenv_() const } } -void ConfigBase::load(const std::string &file) +ConfigSubstitutions ConfigBase::load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule) { - if (is_gcode_file(file)) - this->load_from_gcode_file(file); - else - this->load_from_ini(file); + return is_gcode_file(file) ? + this->load_from_gcode_file(file, compatibility_rule) : + this->load_from_ini(file, compatibility_rule); } -void ConfigBase::load_from_ini(const std::string &file) +ConfigSubstitutions ConfigBase::load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule) { + try { boost::property_tree::ptree tree; boost::nowide::ifstream ifs(file); boost::property_tree::read_ini(ifs, tree); - this->load(tree); + return this->load(tree, compatibility_rule); + } catch (const ConfigurationError &e) { + throw ConfigurationError(format("Failed loading configuration file \"%1%\": %2%", file, e.what())); +} } -void ConfigBase::load(const boost::property_tree::ptree &tree) +ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule) { + ConfigSubstitutionContext substitutions_ctxt(compatibility_rule); for (const boost::property_tree::ptree::value_type &v : tree) { try { t_config_option_key opt_key = v.first; - this->set_deserialize(opt_key, v.second.get_value<std::string>()); + this->set_deserialize(opt_key, v.second.get_value<std::string>(), substitutions_ctxt); } catch (UnknownOptionException & /* e */) { // ignore } } + return std::move(substitutions_ctxt.substitutions); } // Load the config keys from the tail of a G-code file. -void ConfigBase::load_from_gcode_file(const std::string &file) +ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule) { + try { // Read a 64k block from the end of the G-code. boost::nowide::ifstream ifs(file); { @@ -743,7 +802,7 @@ void ConfigBase::load_from_gcode_file(const std::string &file) strncmp(slic3rpp_gcode_header, firstline.c_str(), strlen(slic3rpp_gcode_header)) != 0 && strncmp(superslicer_gcode_header, firstline.c_str(), strlen(superslicer_gcode_header)) != 0 && strncmp(prusaslicer_gcode_header, firstline.c_str(), strlen(prusaslicer_gcode_header)) != 0) - throw Slic3r::RuntimeError("Not a g-code recognized for configuration import."); + throw ConfigurationError("Not a g-code recognized for configuration import."); } ifs.seekg(0, ifs.end); auto file_length = ifs.tellg(); @@ -753,13 +812,18 @@ void ConfigBase::load_from_gcode_file(const std::string &file) ifs.read(data.data(), data_length); ifs.close(); - size_t key_value_pairs = load_from_gcode_string(data.data()); + ConfigSubstitutionContext substitutions_ctxt(compatibility_rule); + size_t key_value_pairs = load_from_gcode_string(data.data(), substitutions_ctxt); if (key_value_pairs < 80) - throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs)); + throw ConfigurationError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs)); + return std::move(substitutions_ctxt.substitutions); + } catch (const ConfigurationError &e) { + throw ConfigurationError(format("Failed loading configuration from G-code \"%1%\": %2%", file, e.what())); +} } // Load the config keys from the given string. -size_t ConfigBase::load_from_gcode_string(const char* str) +size_t ConfigBase::load_from_gcode_string(const char* str, ConfigSubstitutionContext& substitutions) { if (str == nullptr) return 0; @@ -804,8 +868,7 @@ size_t ConfigBase::load_from_gcode_string(const char* str) if (key == nullptr) break; try { - //change it from set_deserialize to set_deserialize_nothrow to allow bad/old config to swtch to default value. - if(this->set_deserialize_nothrow(std::string(key, key_end), std::string(value, end))) + this->set_deserialize(std::string(key, key_end), std::string(value, end), substitutions); ++num_key_value_pairs; } catch (UnknownOptionException & /* e */) { @@ -842,7 +905,7 @@ void ConfigBase::null_nullables() ConfigOption *opt = this->optptr(opt_key, false); assert(opt != nullptr); if (opt->nullable()) - opt->deserialize("nil"); + opt->deserialize("nil", ForwardCompatibilitySubstitutionRule::Disable); } } @@ -893,7 +956,7 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre throw NoDefinitionException(opt_key); const ConfigOptionDef *optdef = def->get(opt_key); if (optdef == nullptr) -// throw Slic3r::RuntimeError(std::string("Invalid option name: ") + opt_key); +// throw ConfigurationError(std::string("Invalid option name: ") + opt_key); // Let the parent decide what to do if the opt_key is not defined by this->def(). return nullptr; ConfigOption *opt = optdef->create_default_option(); @@ -1013,8 +1076,10 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option // Do not unescape single string values, the unescaping is left to the calling shell. static_cast<ConfigOptionString*>(opt_base)->value = value; } else { + // Just bail out if the configuration value is not understood. + ConfigSubstitutionContext context(ForwardCompatibilitySubstitutionRule::Disable); // Any scalar value of a type different from Bool and String. - if (! this->set_deserialize_nothrow(opt_key, value, false)) { + if (! this->set_deserialize_nothrow(opt_key, value, context, false)) { boost::nowide::cerr << "Invalid value supplied for --" << token.c_str() << std::endl; return false; } diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index f7cb5536c..736b24400 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -16,6 +16,7 @@ #include "Exception.hpp" #include "Point.hpp" +#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/trim.hpp> #include <boost/format/format_fwd.hpp> #include <boost/property_tree/ptree_fwd.hpp> @@ -75,23 +76,59 @@ enum OptionCategory : int }; std::string toString(OptionCategory opt); -/// Specialization of std::exception to indicate that an unknown config option has been encountered. -class UnknownOptionException : public Slic3r::RuntimeError { +namespace ConfigHelpers { + inline bool looks_like_enum_value(std::string value) + { + boost::trim(value); + if (value.empty() || value.size() > 64 || ! isalpha(value.front())) + return false; + for (const char c : value) + if (! (isalnum(c) || c == '_' || c == '-')) + return false; + return true; + } + + inline bool enum_looks_like_true_value(std::string value) { + boost::trim(value); + return boost::iequals(value, "enabled") || boost::iequals(value, "on"); + } + + enum class DeserializationSubstitution { + Disabled, + DefaultsToFalse, + DefaultsToTrue + }; + + enum class DeserializationResult { + Loaded, + Substituted, + Failed, + }; +}; + +// Base for all exceptions thrown by the configuration layer. +class ConfigurationError : public Slic3r::RuntimeError { +public: + using RuntimeError::RuntimeError; +}; + +// Specialization of std::exception to indicate that an unknown config option has been encountered. +class UnknownOptionException : public ConfigurationError { public: UnknownOptionException() : - Slic3r::RuntimeError("Unknown option exception") {} + ConfigurationError("Unknown option exception") {} UnknownOptionException(const std::string &opt_key) : - Slic3r::RuntimeError(std::string("Unknown option exception: ") + opt_key) {} + ConfigurationError(std::string("Unknown option exception: ") + opt_key) {} }; -/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null). -class NoDefinitionException : public Slic3r::RuntimeError +// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null). +class NoDefinitionException : public ConfigurationError { public: NoDefinitionException() : - Slic3r::RuntimeError("No definition exception") {} + ConfigurationError("No definition exception") {} NoDefinitionException(const std::string &opt_key) : - Slic3r::RuntimeError(std::string("No definition exception: ") + opt_key) {} + ConfigurationError(std::string("No definition exception: ") + opt_key) {} }; // a bit more specific than a runtime_error class ConfigurationException : public std::runtime_error @@ -103,13 +140,22 @@ public: std::runtime_error(std::string("Configuration exception: ") + opt_key) {} }; -/// Indicate that an unsupported accessor was called on a config option. -class BadOptionTypeException : public Slic3r::RuntimeError +// Indicate that an unsupported accessor was called on a config option. +class BadOptionTypeException : public ConfigurationError { public: - BadOptionTypeException() : Slic3r::RuntimeError("Bad option type exception") {} - BadOptionTypeException(const std::string &message) : Slic3r::RuntimeError(message) {} - BadOptionTypeException(const char* message) : Slic3r::RuntimeError(message) {} + BadOptionTypeException() : ConfigurationError("Bad option type exception") {} + BadOptionTypeException(const std::string &message) : ConfigurationError(message) {} + BadOptionTypeException(const char* message) : ConfigurationError(message) {} +}; + +// Indicate that an option has been deserialized from an invalid value. +class BadOptionValueException : public ConfigurationError +{ +public: + BadOptionValueException() : ConfigurationError("Bad option value exception") {} + BadOptionValueException(const std::string &message) : ConfigurationError(message) {} + BadOptionValueException(const char* message) : ConfigurationError(message) {} }; // Type of a configuration value. @@ -208,8 +254,47 @@ inline OutputFormat operator&=(OutputFormat& a, OutputFormat b) { a = a & b; return a; } +enum ForwardCompatibilitySubstitutionRule +{ + // Disable susbtitution, throw exception if an option value is not recognized. + Disable, + // Enable substitution of an unknown option value with default. Log the substitution. + Enable, + // Enable substitution of an unknown option value with default. Don't log the substitution. + EnableSilent, + // Enable substitution of an unknown option value with default. Log substitutions in user profiles, don't log substitutions in system profiles. + EnableSystemSilent, + // Enable silent substitution of an unknown option value with default when loading user profiles. Throw on an unknown option value in a system profile. + EnableSilentDisableSystem, +}; +class ConfigOption; +class ConfigOptionDef; +// For forward definition of ConfigOption in ConfigOptionUniquePtr, we have to define a custom deleter. +struct ConfigOptionDeleter { void operator()(ConfigOption* p); }; +using ConfigOptionUniquePtr = std::unique_ptr<ConfigOption, ConfigOptionDeleter>; + +// When parsing a configuration value, if the old_value is not understood by this PrusaSlicer version, +// it is being substituted with some default value that this PrusaSlicer could work with. +// This structure serves to inform the user about the substitutions having been done during file import. +struct ConfigSubstitution { + const ConfigOptionDef *opt_def { nullptr }; + std::string old_value; + ConfigOptionUniquePtr new_value; +}; + +using ConfigSubstitutions = std::vector<ConfigSubstitution>; +// Filled in by ConfigBase::set_deserialize_raw(), which based on "rule" either bails out +// or performs substitutions when encountering an unknown configuration value. +struct ConfigSubstitutionContext +{ + ConfigSubstitutionContext(ForwardCompatibilitySubstitutionRule rl) : rule(rl) {} + bool empty() const throw() { return substitutions.empty(); } + + ForwardCompatibilitySubstitutionRule rule; + ConfigSubstitutions substitutions; +}; // A generic value of a configuration option. class ConfigOption { @@ -277,7 +362,7 @@ public: void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw Slic3r::RuntimeError("ConfigOptionSingle: Assigning an incompatible type"); + throw ConfigurationError("ConfigOptionSingle: Assigning an incompatible type"); assert(dynamic_cast<const ConfigOptionSingle<T>*>(rhs)); this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value; this->phony = rhs->phony; @@ -286,7 +371,7 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw Slic3r::RuntimeError("ConfigOptionSingle: Comparing incompatible types"); + throw ConfigurationError("ConfigOptionSingle: Comparing incompatible types"); assert(dynamic_cast<const ConfigOptionSingle<T>*>(&rhs)); return this->value == static_cast<const ConfigOptionSingle<T>*>(&rhs)->value; } @@ -352,7 +437,7 @@ public: void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw Slic3r::RuntimeError("ConfigOptionVector: Assigning an incompatible type"); + throw ConfigurationError("ConfigOptionVector: Assigning an incompatible type"); assert(dynamic_cast<const ConfigOptionVector<T>*>(rhs)); this->values = static_cast<const ConfigOptionVector<T>*>(rhs)->values; this->phony = rhs->phony; @@ -370,12 +455,12 @@ public: if (opt->type() == this->type()) { auto other = static_cast<const ConfigOptionVector<T>*>(opt); if (other->values.empty()) - throw Slic3r::RuntimeError("ConfigOptionVector::set(): Assigning from an empty vector"); + throw ConfigurationError("ConfigOptionVector::set(): Assigning from an empty vector"); this->values.emplace_back(other->values.front()); } else if (opt->type() == this->scalar_type()) this->values.emplace_back(static_cast<const ConfigOptionSingle<T>*>(opt)->value); else - throw Slic3r::RuntimeError("ConfigOptionVector::set():: Assigning an incompatible type"); + throw ConfigurationError("ConfigOptionVector::set():: Assigning an incompatible type"); } } @@ -394,12 +479,12 @@ public: // Assign the first value of the rhs vector. auto other = static_cast<const ConfigOptionVector<T>*>(rhs); if (other->values.empty()) - throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning from an empty vector"); + throw ConfigurationError("ConfigOptionVector::set_at(): Assigning from an empty vector"); this->values[i] = other->get_at(j); } else if (rhs->type() == this->scalar_type()) this->values[i] = static_cast<const ConfigOptionSingle<T>*>(rhs)->value; else - throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning an incompatible type"); + throw ConfigurationError("ConfigOptionVector::set_at(): Assigning an incompatible type"); } const T& get_at(size_t i) const @@ -426,7 +511,7 @@ public: if (opt_default == nullptr) this->values.resize(n, this->default_value); if (opt_default->type() != this->type()) - throw Slic3r::RuntimeError("ConfigOptionVector::resize(): Extending with an incompatible type."); + throw ConfigurationError("ConfigOptionVector::resize(): Extending with an incompatible type."); if(static_cast<const ConfigOptionVector<T>*>(opt_default)->values.empty()) this->values.resize(n, this->default_value); else @@ -446,7 +531,7 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw Slic3r::RuntimeError("ConfigOptionVector: Comparing incompatible types"); + throw ConfigurationError("ConfigOptionVector: Comparing incompatible types"); assert(dynamic_cast<const ConfigOptionVector<T>*>(&rhs)); return this->values == static_cast<const ConfigOptionVector<T>*>(&rhs)->values; } @@ -458,9 +543,9 @@ public: // An option overrides another option if it is not nil and not equal. bool overriden_by(const ConfigOption *rhs) const override { if (this->nullable()) - throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption."); + throw ConfigurationError("Cannot override a nullable ConfigOption."); if (rhs->type() != this->type()) - throw Slic3r::RuntimeError("ConfigOptionVector.overriden_by() applied to different types."); + throw ConfigurationError("ConfigOptionVector.overriden_by() applied to different types."); auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs); if (! rhs->nullable()) // Overridding a non-nullable object with another non-nullable object. @@ -478,9 +563,9 @@ public: // Apply an override option, possibly a nullable one. bool apply_override(const ConfigOption *rhs) override { if (this->nullable()) - throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption."); + throw ConfigurationError("Cannot override a nullable ConfigOption."); if (rhs->type() != this->type()) - throw Slic3r::RuntimeError("ConfigOptionVector.apply_override() applied to different types."); + throw ConfigurationError("ConfigOptionVector.apply_override() applied to different types."); auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs); if (! rhs->nullable()) { // Overridding a non-nullable object with another non-nullable object. @@ -571,7 +656,7 @@ public: bool operator==(const ConfigOptionFloatsTempl &rhs) const { return vectors_equal(this->values, rhs.values); } bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw Slic3r::RuntimeError("ConfigOptionFloatsTempl: Comparing incompatible types"); + throw ConfigurationError("ConfigOptionFloatsTempl: Comparing incompatible types"); assert(dynamic_cast<const ConfigOptionVector<double>*>(&rhs)); return vectors_equal(this->values, static_cast<const ConfigOptionVector<double>*>(&rhs)->values); } @@ -618,7 +703,7 @@ public: if (NULLABLE) this->values.push_back(nil_value()); else - throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object"); + throw ConfigurationError("Deserializing nil into a non-nullable object"); } else { std::istringstream iss(item_str); double value; @@ -643,9 +728,9 @@ protected: if (NULLABLE) ss << "nil"; else - throw Slic3r::RuntimeError("Serializing NaN"); + throw ConfigurationError("Serializing NaN"); } else - throw Slic3r::RuntimeError("Serializing invalid number"); + throw ConfigurationError("Serializing invalid number"); } static bool vectors_equal(const std::vector<double> &v1, const std::vector<double> &v2) { if (NULLABLE) { @@ -764,7 +849,7 @@ public: if (NULLABLE) this->values.push_back(nil_value()); else - throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object"); + throw ConfigurationError("Deserializing nil into a non-nullable object"); } else { std::istringstream iss(item_str); int32_t value; @@ -781,7 +866,7 @@ private: if (NULLABLE) ss << "nil"; else - throw Slic3r::RuntimeError("Serializing NaN"); + throw ConfigurationError("Serializing NaN"); } else ss << v; } @@ -811,7 +896,7 @@ public: return escape_string_cstyle(this->value); } - bool deserialize(const std::string &str, bool append = false) override + bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); return unescape_string_cstyle(str, this->value); @@ -970,7 +1055,7 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Comparing incompatible types"); + throw ConfigurationError("ConfigOptionFloatOrPercent: Comparing incompatible types"); assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(&rhs)); return *this == *static_cast<const ConfigOptionFloatOrPercent*>(&rhs); } @@ -981,7 +1066,7 @@ public: void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw ConfigurationException("ConfigOptionFloatOrPercent: Assigning an incompatible type"); + throw ConfigurationError("ConfigOptionFloatOrPercent: Assigning an incompatible type"); assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(rhs)); *this = *static_cast<const ConfigOptionFloatOrPercent*>(rhs); } @@ -1046,7 +1131,7 @@ public: bool operator==(const ConfigOptionFloatsOrPercentsTempl &rhs) const { return vectors_equal(this->values, rhs.values); } bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw Slic3r::RuntimeError("ConfigOptionFloatsOrPercentsTempl: Comparing incompatible types"); + throw ConfigurationError("ConfigOptionFloatsOrPercentsTempl: Comparing incompatible types"); assert(dynamic_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs)); return vectors_equal(this->values, static_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs)->values); } @@ -1093,7 +1178,7 @@ public: if (NULLABLE) this->values.push_back(nil_value()); else - throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object"); + throw ConfigurationError("Deserializing nil into a non-nullable object"); } else { bool percent = item_str.find_first_of("%") != std::string::npos; std::istringstream iss(item_str); @@ -1121,9 +1206,9 @@ protected: if (NULLABLE) ss << "nil"; else - throw Slic3r::RuntimeError("Serializing NaN"); + throw ConfigurationError("Serializing NaN"); } else - throw Slic3r::RuntimeError("Serializing invalid number"); + throw ConfigurationError("Serializing invalid number"); } static bool vectors_equal(const std::vector<FloatOrPercent> &v1, const std::vector<FloatOrPercent> &v2) { if (NULLABLE) { @@ -1312,8 +1397,15 @@ public: bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); - this->value = (str.compare("1") == 0); - return true; + if (str == "1") { + this->value = true; + return true; + } + if (str == "0") { + this->value = false; + return true; + } + return false; } private: @@ -1374,24 +1466,39 @@ public: } return vv; } - - bool deserialize(const std::string &str, bool append = false) override + + ConfigHelpers::DeserializationResult deserialize_with_substitutions(const std::string &str, bool append, ConfigHelpers::DeserializationSubstitution substitution) { if (! append) this->values.clear(); std::istringstream is(str); std::string item_str; + bool substituted = false; while (std::getline(is, item_str, ',')) { boost::trim(item_str); + unsigned char new_value = 0; if (item_str == "nil") { if (NULLABLE) - this->values.push_back(nil_value()); + new_value = nil_value(); else - throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object"); + throw ConfigurationError("Deserializing nil into a non-nullable object"); + } else if (item_str == "1") { + new_value = true; + } else if (item_str == "0") { + new_value = false; + } else if (substitution != ConfigHelpers::DeserializationSubstitution::Disabled && ConfigHelpers::looks_like_enum_value(item_str)) { + new_value = ConfigHelpers::enum_looks_like_true_value(item_str) || substitution == ConfigHelpers::DeserializationSubstitution::DefaultsToTrue; + substituted = true; } else - this->values.push_back(item_str.compare("1") == 0); + return ConfigHelpers::DeserializationResult::Failed; + this->values.push_back(new_value); } - return true; + return substituted ? ConfigHelpers::DeserializationResult::Substituted : ConfigHelpers::DeserializationResult::Loaded; + } + + bool deserialize(const std::string &str, bool append = false) override + { + return this->deserialize_with_substitutions(str, append, ConfigHelpers::DeserializationSubstitution::Disabled) == ConfigHelpers::DeserializationResult::Loaded; } protected: @@ -1400,7 +1507,7 @@ protected: if (NULLABLE) ss << "nil"; else - throw Slic3r::RuntimeError("Serializing NaN"); + throw ConfigurationError("Serializing NaN"); } else ss << (v ? "1" : "0"); } @@ -1436,14 +1543,14 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Comparing incompatible types"); + throw ConfigurationError("ConfigOptionEnum<T>: Comparing incompatible types"); // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T> return this->value == (T)rhs.getInt(); } void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Assigning an incompatible type"); + throw ConfigurationError("ConfigOptionEnum<T>: Assigning an incompatible type"); // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T> this->value = (T)rhs->getInt(); this->phony = rhs->phony; @@ -1522,14 +1629,14 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Comparing incompatible types"); + throw ConfigurationError("ConfigOptionEnumGeneric: Comparing incompatible types"); // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T> return this->value == rhs.getInt(); } void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Assigning an incompatible type"); + throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type"); // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T> this->value = rhs->getInt(); this->phony = rhs->phony; @@ -1585,7 +1692,7 @@ public: case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; } case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; } case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; } - default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key); + default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key); } } else { switch (this->type) { @@ -1604,7 +1711,7 @@ public: case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; } case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; } case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; } - default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key); + default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key); } } } @@ -1616,7 +1723,7 @@ public: case coInts: archive(*static_cast<const ConfigOptionIntsNullable*>(opt)); break; case coPercents: archive(*static_cast<const ConfigOptionPercentsNullable*>(opt));break; case coBools: archive(*static_cast<const ConfigOptionBoolsNullable*>(opt)); break; - default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key); + default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key); } } else { switch (this->type) { @@ -1635,7 +1742,7 @@ public: case coBool: archive(*static_cast<const ConfigOptionBool*>(opt)); break; case coBools: archive(*static_cast<const ConfigOptionBools*>(opt)); break; case coEnum: archive(*static_cast<const ConfigOptionEnumGeneric*>(opt)); break; - default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key); + default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key); } } // Make the compiler happy, shut up the warnings. @@ -1731,6 +1838,14 @@ public: static const constexpr char *nocli = "~~~noCLI"; }; +inline bool operator<(const ConfigSubstitution &lhs, const ConfigSubstitution &rhs) throw() { + return lhs.opt_def->opt_key < rhs.opt_def->opt_key || + (lhs.opt_def->opt_key == rhs.opt_def->opt_key && lhs.old_value < rhs.old_value); +} +inline bool operator==(const ConfigSubstitution &lhs, const ConfigSubstitution &rhs) throw() { + return lhs.opt_def == rhs.opt_def && lhs.old_value == rhs.old_value; +} + // Map from a config option name to its definition. // The definition does not carry an actual value of the config option, only its constant default value. // t_config_option_key is std::string @@ -1758,7 +1873,7 @@ public: return out; } - /// Iterate through all of the CLI options and write them to a stream. + // Iterate through all of the CLI options and write them to a stream. std::ostream& print_cli_help( std::ostream& out, bool show_defaults, std::function<bool(const ConfigOptionDef &)> filter = [](const ConfigOptionDef &){ return true; }) const; @@ -1809,6 +1924,8 @@ public: } }; + + // An abstract configuration store. class ConfigBase : public ConfigOptionResolver { @@ -1905,9 +2022,11 @@ public: // Set a configuration value from a string, it will call an overridable handle_legacy() // to resolve renamed and removed configuration keys. - bool set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append = false); + bool set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions, bool append = false); // May throw BadOptionTypeException() if the operation fails. - void set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false); + void set_deserialize(const t_config_option_key &opt_key, const std::string &str, ConfigSubstitutionContext& config_substitutions, bool append = false); + void set_deserialize_strict(const t_config_option_key &opt_key, const std::string &str, bool append = false) + { ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; this->set_deserialize(opt_key, str, ctxt, append); } struct SetDeserializeItem { SetDeserializeItem(const char *opt_key, const char *opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {} SetDeserializeItem(const std::string &opt_key, const std::string &opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {} @@ -1922,17 +2041,19 @@ public: std::string opt_key; std::string opt_value; bool append = false; }; // May throw BadOptionTypeException() if the operation fails. - void set_deserialize(std::initializer_list<SetDeserializeItem> items); + void set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions); + void set_deserialize_strict(std::initializer_list<SetDeserializeItem> items) + { ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; this->set_deserialize(items, ctxt); } double get_abs_value(const t_config_option_key &opt_key) const; double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; void setenv_() const; - void load(const std::string &file); - void load_from_ini(const std::string &file); - void load_from_gcode_file(const std::string &file); + ConfigSubstitutions load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule); + ConfigSubstitutions load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule); + ConfigSubstitutions load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule); // Returns number of key/value pairs extracted. - size_t load_from_gcode_string(const char* str); - void load(const boost::property_tree::ptree &tree); + size_t load_from_gcode_string(const char* str, ConfigSubstitutionContext& substitutions); + ConfigSubstitutions load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule); void save(const std::string &file, bool to_prusa = false) const; // Set all the nullable values to nils. @@ -1940,7 +2061,7 @@ public: private: // Set a configuration value from a string. - bool set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &str, bool append); + bool set_deserialize_raw(const t_config_option_key& opt_key_src, const std::string& value, ConfigSubstitutionContext& substitutions, bool append); }; // Configuration store with dynamic number of configuration values. @@ -2110,9 +2231,9 @@ private: template<class Archive> void serialize(Archive &ar) { ar(options); } }; -/// 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. +// 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. class StaticConfig : public virtual ConfigBase { public: diff --git a/src/libslic3r/Extruder.cpp b/src/libslic3r/Extruder.cpp index 2f37a62f2..e9783a75d 100644 --- a/src/libslic3r/Extruder.cpp +++ b/src/libslic3r/Extruder.cpp @@ -147,12 +147,12 @@ double Tool::retract_restart_extra_toolchange() const return 0; } -int Tool::temp_offset() const +int16_t Tool::temp_offset() const { return 0; } -int Tool::fan_offset() const +int8_t Tool::fan_offset() const { return 0; } @@ -219,12 +219,12 @@ double Extruder::retract_restart_extra_toolchange() const return m_config->retract_restart_extra_toolchange.get_at(m_id); } -int Extruder::temp_offset() const +int16_t Extruder::temp_offset() const { return m_config->extruder_temperature_offset.get_at(m_id); } -int Extruder::fan_offset() const +int8_t Extruder::fan_offset() const { return m_config->extruder_fan_offset.get_at(m_id); } diff --git a/src/libslic3r/Extruder.hpp b/src/libslic3r/Extruder.hpp index c9c38138a..1d8917090 100644 --- a/src/libslic3r/Extruder.hpp +++ b/src/libslic3r/Extruder.hpp @@ -48,8 +48,8 @@ public: virtual double retract_restart_extra() const; virtual double retract_length_toolchange() const; virtual double retract_restart_extra_toolchange() const; - virtual int temp_offset() const; - virtual int fan_offset() const; + virtual int16_t temp_offset() const; + virtual int8_t fan_offset() const; protected: // Private constructor to create a key for a search in std::set. @@ -104,8 +104,8 @@ public: double retract_restart_extra() const override; double retract_length_toolchange() const override; double retract_restart_extra_toolchange() const override; - int temp_offset() const override; - int fan_offset() const override; + int16_t temp_offset() const override; + int8_t fan_offset() const override; protected: // Private constructor to create a key for a search in std::set. diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index 1cbb9e00f..5c825f5c7 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -255,15 +255,6 @@ void ExtrusionLoop::polygons_covered_by_spacing(Polygons &out, const float scale path.polygons_covered_by_spacing(out, scaled_epsilon); } -double ExtrusionLoop::min_mm3_per_mm() const -{ - double min_mm3_per_mm = std::numeric_limits<double>::max(); - for (const ExtrusionPath &path : this->paths) - if (path.role() != erGapFill && path.role() != erThinWall && path.role() != erMilling) - min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm); - return min_mm3_per_mm; -} - std::string ExtrusionEntity::role_to_string(ExtrusionRole role) { switch (role) { diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 0d9069050..c062456de 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -201,8 +201,6 @@ public: { Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; } virtual Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; } - // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. - virtual double min_mm3_per_mm() const = 0; virtual Polyline as_polyline() const = 0; virtual void collect_polylines(Polylines &dst) const = 0; virtual Polylines as_polylines() const { Polylines dst; this->collect_polylines(dst); return dst; } @@ -269,8 +267,6 @@ public: { Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; } virtual Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; } - // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. - double min_mm3_per_mm() const override { return this->mm3_per_mm; } Polyline as_polyline() const override { return this->polyline; } void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); } double total_volume() const override { return mm3_per_mm * unscale<double>(length()); } @@ -362,15 +358,6 @@ public: entity.polygons_covered_by_spacing(out, scaled_epsilon); } - // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. - double min_mm3_per_mm() const override { - double min_mm3_per_mm = std::numeric_limits<double>::max(); - for (const THING &entity : this->paths) - if (entity.role() != erGapFill && entity.role() != erThinWall && entity.role() != erMilling) - min_mm3_per_mm = std::min(min_mm3_per_mm, entity.min_mm3_per_mm()); - return min_mm3_per_mm; - } - Polyline as_polyline() const override { Polyline out; if (!paths.empty()) { @@ -444,8 +431,8 @@ public: ExtrusionPaths paths; ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : m_loop_role(role) {} - ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) {} - ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role) {} + ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) { assert(this->first_point() == this->paths.back().polyline.points.back()); } + ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role) { assert(this->first_point() == this->paths.back().polyline.points.back()); } ExtrusionLoop(const ExtrusionPath &path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role) { this->paths.push_back(path); } ExtrusionLoop(const ExtrusionPath &&path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role) @@ -481,8 +468,6 @@ public: { Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; } Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; } - // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. - double min_mm3_per_mm() const override; Polyline as_polyline() const override { return this->polygon().split_at_first_point(); } void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); } double total_volume() const override { double volume = 0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp index a7c338e02..c80028780 100644 --- a/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/src/libslic3r/ExtrusionEntityCollection.cpp @@ -170,15 +170,4 @@ FlatenEntities::flatten(const ExtrusionEntityCollection &to_flatten) && { return std::move(to_fill); } - -double -ExtrusionEntityCollection::min_mm3_per_mm() const -{ - double min_mm3_per_mm = std::numeric_limits<double>::max(); - for (const ExtrusionEntity *entity : this->entities) - if(entity->role() != erGapFill && entity->role() != erThinWall && entity->role() != erMilling) - min_mm3_per_mm = std::min(min_mm3_per_mm, entity->min_mm3_per_mm()); - return min_mm3_per_mm; -} - } diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index 7c8920cb4..439dddab2 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -115,7 +115,6 @@ public: /// You should be iterating over flatten().entities if you are interested in the underlying ExtrusionEntities (and don't care about hierarchy). /// \param preserve_ordering Flag to method that will flatten if and only if the underlying collection is sortable when True (default: False). ExtrusionEntityCollection flatten(bool preserve_ordering = false) const; - double min_mm3_per_mm() const override; double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; } // Following methods shall never be called on an ExtrusionEntityCollection. diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 313bf9692..26952ae46 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -112,7 +112,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer) FlowRole extrusion_role = surface.has_pos_top() ? frTopSolidInfill : (surface.has_fill_solid() ? frSolidInfill : frInfill); bool is_bridge = layer.id() > 0 && surface.has_mod_bridge(); bool is_denser = false; - params.extruder = layerm.region()->extruder(extrusion_role); + params.extruder = layerm.region()->extruder(extrusion_role, *layer.object()); params.pattern = region_config.fill_pattern.value; params.density = float(region_config.fill_density) / 100.f; params.dont_adjust = false; @@ -324,7 +324,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer) } if (internal_solid_fill == nullptr) { // Produce another solid fill. - params.extruder = layerm.region()->extruder(frSolidInfill); + params.extruder = layerm.region()->extruder(frSolidInfill, *layer.object()); params.pattern = layerm.region()->config().solid_fill_pattern.value; params.density = 100.f; params.role = erInternalInfill; @@ -465,9 +465,12 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: } //init the surface with the current polygon - surface_fill.surface.expolygon = std::move(expoly); - //make fill - f->fill_surface_extrusion(&surface_fill.surface, surface_fill.params, m_regions[surface_fill.region_id]->fills.entities); + if (!expoly.contour.empty()) { + surface_fill.surface.expolygon = std::move(expoly); + + //make fill + f->fill_surface_extrusion(&surface_fill.surface, surface_fill.params, m_regions[surface_fill.region_id]->fills.entities); + } } } diff --git a/src/libslic3r/Fill/FillSmooth.cpp b/src/libslic3r/Fill/FillSmooth.cpp index 1591081b9..8c4e89bdc 100644 --- a/src/libslic3r/Fill/FillSmooth.cpp +++ b/src/libslic3r/Fill/FillSmooth.cpp @@ -36,6 +36,9 @@ namespace Slic3r { if (params.config != NULL && idx > 0) params_modifided.flow_mult *= (float)params.config->fill_smooth_distribution.get_abs_value(1); else if (params.config != NULL && idx == 0) params_modifided.flow_mult *= (1.f - (float)params.config->fill_smooth_distribution.get_abs_value(1)); else params_modifided.flow_mult *= (float)percentFlow[idx]; + //set role + if (rolePass[idx] != erNone) + params_modifided.role = rolePass[idx]; //choose if we are going to extrude with or without overlap if ((params.flow.bridge && idx == 0) || has_overlap[idx] || this->no_overlap_expolygons.empty()){ @@ -115,8 +118,8 @@ namespace Slic3r { // first infill FillParams first_pass_params = params; - if(first_pass_params.role != ExtrusionRole::erSupportMaterial && first_pass_params.role != ExtrusionRole::erSupportMaterialInterface) - first_pass_params.role = ExtrusionRole::erSolidInfill; + //if(first_pass_params.role != ExtrusionRole::erSupportMaterial && first_pass_params.role != ExtrusionRole::erSupportMaterialInterface) + //s first_pass_params.role = ExtrusionRole::erSolidInfill; perform_single_fill(0, *eecroot, *surface, first_pass_params, volume_to_occupy); //use monotonic for ironing pass diff --git a/src/libslic3r/Fill/FillSmooth.hpp b/src/libslic3r/Fill/FillSmooth.hpp index 789978002..fa7a0b4b6 100644 --- a/src/libslic3r/Fill/FillSmooth.hpp +++ b/src/libslic3r/Fill/FillSmooth.hpp @@ -18,9 +18,9 @@ public: fillPattern[0] = InfillPattern::ipRectilinearWGapFill; fillPattern[1] = InfillPattern::ipRectilinear; fillPattern[2] = InfillPattern::ipRectilinear; - rolePass[0] = erSolidInfill; - rolePass[1] = erTopSolidInfill; - rolePass[2] = erTopSolidInfill; + rolePass[0] = erNone;// erTopSolidInfill; + rolePass[1] = erIroning; + rolePass[2] = erIroning; percentWidth[0] = 1; percentWidth[1] = 2; percentWidth[2] = 1.0; @@ -73,9 +73,9 @@ public: fillPattern[0] = InfillPattern::ipHilbertCurve; //ipRectilinear fillPattern[1] = InfillPattern::ipConcentric; fillPattern[2] = InfillPattern::ipRectilinear; - rolePass[0] = erTopSolidInfill;//erSolidInfill - rolePass[1] = erSolidInfill; - rolePass[2] = erTopSolidInfill; + rolePass[0] = erSolidInfill;//erSolidInfill + rolePass[1] = erTopSolidInfill; + rolePass[2] = erIroning; percentWidth[0] = 1; //0.8 percentWidth[1] = 1.5; percentWidth[2] = 2.8; @@ -106,9 +106,9 @@ public: fillPattern[0] = InfillPattern::ipHilbertCurve; //ipHilbertCurve fillPattern[1] = InfillPattern::ipHilbertCurve; fillPattern[2] = InfillPattern::ipRectilinear; - rolePass[0] = erSolidInfill; - rolePass[1] = erTopSolidInfill; - rolePass[2] = erTopSolidInfill; + rolePass[0] = erTopSolidInfill; + rolePass[1] = erIroning; + rolePass[2] = erIroning; percentWidth[0] = 1; percentWidth[1] = 1.5; percentWidth[2] = 1.0; diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index f8b4f0970..fe98c0518 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -427,7 +427,7 @@ namespace Slic3r { _3MF_Importer(); ~_3MF_Importer(); - bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, bool check_version); + bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version); private: void _destroy_xml_parser(); @@ -442,16 +442,16 @@ namespace Slic3r { XML_ErrorString(XML_GetErrorCode(m_xml_parser)); } - bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config); + bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions); bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions); void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename); + void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename); bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); // handlers to parse the .model file @@ -518,7 +518,7 @@ namespace Slic3r { bool _handle_start_config_metadata(const char** attributes, unsigned int num_attributes); bool _handle_end_config_metadata(); - bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes); + bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions); // callbacks to parse the .model file static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes); @@ -547,7 +547,7 @@ namespace Slic3r { _destroy_xml_parser(); } - bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, bool check_version) + bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version) { m_version = 0; m_check_version = check_version; @@ -568,7 +568,7 @@ namespace Slic3r { m_curr_characters.clear(); clear_errors(); - return _load_model_from_file(filename, model, config); + return _load_model_from_file(filename, model, config, config_substitutions); } void _3MF_Importer::_destroy_xml_parser() @@ -590,7 +590,7 @@ namespace Slic3r { XML_StopParser(m_xml_parser, false); } - bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config) + bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions) { mz_zip_archive archive; mz_zip_zero_struct(&archive); @@ -653,7 +653,7 @@ namespace Slic3r { if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) { // extract slic3r layer config ranges file - _extract_layer_config_ranges_from_archive(archive, stat); + _extract_layer_config_ranges_from_archive(archive, stat, config_substitutions); } else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) { // extract sla support points file @@ -665,7 +665,7 @@ namespace Slic3r { } else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { // extract slic3r print config file - _extract_print_config_from_archive(archive, stat, config, filename); + _extract_print_config_from_archive(archive, stat, config, config_substitutions, filename); print_config_parsed = true; } if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE)) @@ -687,7 +687,8 @@ namespace Slic3r { } //parsed superslicer/prusa files if slic3r not found //note that is we successfully read one of the config file, then the other ones should also have the same name - auto read_from_other_storage = [this, &print_config_parsed, num_entries, &archive, &stat, &config, &model, &filename](const std::string &print_config_name, const std::string& model_config_name) -> bool { + auto read_from_other_storage = [this, &print_config_parsed, num_entries, &archive, &stat, &config, &model, &filename, &config_substitutions] + (const std::string &print_config_name, const std::string& model_config_name) -> bool { for (mz_uint i = 0; i < num_entries; ++i) { if (mz_zip_reader_file_stat(&archive, i, &stat)) @@ -699,7 +700,7 @@ namespace Slic3r { if (boost::algorithm::iequals(name, print_config_name)) { // extract slic3r print config file - _extract_print_config_from_archive(archive, stat, config, filename); + _extract_print_config_from_archive(archive, stat, config, config_substitutions, filename); print_config_parsed = true; } else if (boost::algorithm::iequals(name, model_config_name)) { @@ -774,7 +775,7 @@ namespace Slic3r { if (metadata.key == "name") model_object->name = metadata.value; else - model_object->config.set_deserialize(metadata.key, metadata.value); + model_object->config.set_deserialize(metadata.key, metadata.value, config_substitutions); } // select object's detected volumes @@ -791,7 +792,7 @@ namespace Slic3r { volumes_ptr = &volumes; } - if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr)) + if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr, config_substitutions)) return false; } @@ -868,7 +869,12 @@ namespace Slic3r { return true; } - void _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename) + void _3MF_Importer::_extract_print_config_from_archive( + mz_zip_archive& archive, + const mz_zip_archive_file_stat& stat, + DynamicPrintConfig& config, + ConfigSubstitutionContext& config_substitutions, + const std::string& archive_filename) { if (stat.m_uncomp_size > 0) { @@ -879,7 +885,7 @@ namespace Slic3r { add_error("Error while reading config data to buffer"); return; } - config.load_from_gcode_string(buffer.data()); + config.load_from_gcode_string(buffer.data(), config_substitutions); } } @@ -954,7 +960,7 @@ namespace Slic3r { } } - void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions) { if (stat.m_uncomp_size > 0) { @@ -1003,8 +1009,7 @@ namespace Slic3r { continue; std::string opt_key = option.second.get<std::string>("<xmlattr>.opt_key"); std::string value = option.second.data(); - - config.set_deserialize(opt_key, value); + config.set_deserialize(opt_key, value, config_substitutions); } config_ranges[{ min_z, max_z }].assign_config(std::move(config)); @@ -1895,7 +1900,7 @@ namespace Slic3r { return true; } - bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes) + bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions) { if (!object.volumes.empty()) { @@ -1997,7 +2002,7 @@ namespace Slic3r { else if (metadata.key == SOURCE_IN_INCHES) volume->source.is_converted_from_inches = metadata.value == "1"; else - volume->config.set_deserialize(metadata.key, metadata.value); + volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions); } } @@ -2971,13 +2976,13 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv return true; } -bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) +bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version) { - if ((path == nullptr) || (config == nullptr) || (model == nullptr)) + if (path == nullptr || model == nullptr) return false; _3MF_Importer importer; - bool res = importer.load_model_from_file(path, *model, *config, check_version); + bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version); importer.log_errors(); return res; } diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index ccfd9356d..553b31898 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -25,11 +25,12 @@ namespace Slic3r { }; class Model; + struct ConfigSubstitutionContext; class DynamicPrintConfig; struct ThumbnailData; // Load the content of a 3mf file into the given model and preset bundle. - extern bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version); + extern bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version); // Save the given model and the config data contained in the given Print into a 3mf file. // The model could be modified during the export process if meshes are not repaired or have no shared vertices diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 72c6af310..3fe538b36 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -62,10 +62,11 @@ namespace Slic3r struct AMFParserContext { - AMFParserContext(XML_Parser parser, DynamicPrintConfig* config, Model* model) : + AMFParserContext(XML_Parser parser, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model) : m_parser(parser), m_model(*model), - m_config(config) + m_config(config), + m_config_substitutions(config_substitutions) { m_path.reserve(12); } @@ -256,6 +257,8 @@ struct AMFParserContext std::string m_value[5]; // Pointer to config to update if config data are stored inside the amf file DynamicPrintConfig *m_config { nullptr }; + // Config substitution rules and collected config substitution log. + ConfigSubstitutionContext *m_config_substitutions { nullptr }; private: AMFParserContext& operator=(AMFParserContext&); @@ -704,8 +707,9 @@ void AMFParserContext::endElement(const char * /* name */) } case NODE_TYPE_METADATA: - if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0) - m_config->load_from_gcode_string(m_value[1].c_str()); + if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0) { + m_config->load_from_gcode_string(m_value[1].c_str(), *m_config_substitutions); + } else if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) { const char *opt_key = m_value[0].c_str() + 7; if (print_config_def.options.find(opt_key) != print_config_def.options.end()) { @@ -723,7 +727,7 @@ void AMFParserContext::endElement(const char * /* name */) config = &it->second; } if (config) - config->set_deserialize(opt_key, m_value[1]); + config->set_deserialize(opt_key, m_value[1], *m_config_substitutions); } else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) { // Parse object's layer height profile, a semicolon separated list of floats. char *p = m_value[1].data(); @@ -848,7 +852,7 @@ void AMFParserContext::endDocument() } // Load an AMF file into a provided model. -bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model) +bool load_amf_file(const char *path, DynamicPrintConfig *config, ConfigSubstitutionContext *config_substitutions, Model *model) { if ((path == nullptr) || (model == nullptr)) return false; @@ -865,7 +869,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model) return false; } - AMFParserContext ctx(parser, config, model); + AMFParserContext ctx(parser, config, config_substitutions, model); XML_SetUserData(parser, (void*)&ctx); XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement); XML_SetCharacterDataHandler(parser, AMFParserContext::characters); @@ -909,7 +913,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model) return result; } -bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, Model* model, bool check_version) +bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version) { if (stat.m_uncomp_size == 0) { @@ -925,7 +929,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi return false; } - AMFParserContext ctx(parser, config, model); + AMFParserContext ctx(parser, config, config_substitutions, model); XML_SetUserData(parser, (void*)&ctx); XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement); XML_SetCharacterDataHandler(parser, AMFParserContext::characters); @@ -985,7 +989,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi } // Load an AMF archive into a provided model. -bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) +bool load_amf_archive(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version) { if ((path == nullptr) || (model == nullptr)) return false; @@ -1011,7 +1015,7 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model { try { - if (!extract_model_from_archive(archive, stat, config, model, check_version)) + if (!extract_model_from_archive(archive, stat, config, config_substitutions, model, check_version)) { close_zip_reader(&archive); printf("Archive does not contain a valid model"); @@ -1053,11 +1057,11 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model // Load an AMF file into a provided model. // If config is not a null pointer, updates it if the amf file/archive contains config data -bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) +bool load_amf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version) { if (boost::iends_with(path, ".amf.xml")) // backward compatibility with older slic3r output - return load_amf_file(path, config, model); + return load_amf_file(path, config, config_substitutions, model); else if (boost::iends_with(path, ".amf")) { boost::nowide::ifstream file(path, boost::nowide::ifstream::binary); @@ -1068,7 +1072,7 @@ bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool c file.read(zip_mask.data(), 2); file.close(); - return (zip_mask == "PK") ? load_amf_archive(path, config, model, check_version) : load_amf_file(path, config, model); + return (zip_mask == "PK") ? load_amf_archive(path, config, config_substitutions, model, check_version) : load_amf_file(path, config, config_substitutions, model); } else return false; diff --git a/src/libslic3r/Format/AMF.hpp b/src/libslic3r/Format/AMF.hpp index 138195cd6..e834104e7 100644 --- a/src/libslic3r/Format/AMF.hpp +++ b/src/libslic3r/Format/AMF.hpp @@ -7,7 +7,7 @@ class Model; class DynamicPrintConfig; // Load the content of an amf file into the given model and configuration. -extern bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version); +extern bool load_amf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version); // Save the given model and the config data into an amf file. // The model could be modified during the export process if meshes are not repaired or have no shared vertices diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp index e2c38d957..6f86a203f 100644 --- a/src/libslic3r/Format/PRUS.cpp +++ b/src/libslic3r/Format/PRUS.cpp @@ -286,11 +286,8 @@ static void extract_model_from_archive( volume->name = name; } // Set the extruder to the volume. - if (extruder_id != (unsigned int)-1) { - char str_extruder[64]; - sprintf(str_extruder, "%ud", extruder_id); - volume->config.set_deserialize("extruder", str_extruder); - } + if (extruder_id != (unsigned int)-1) + volume->config.set("extruder", int(extruder_id)); } // Load a PrusaControl project file into a provided model. diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index eedf19a0d..5f26c463b 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -203,7 +203,7 @@ RasterParams get_raster_params(const DynamicPrintConfig &cfg) if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h || !opt_mirror_x || !opt_mirror_y || !opt_orient) - throw Slic3r::FileIOError("Invalid SL1 file"); + throw Slic3r::FileIOError("Invalid SL1 / SL1S file"); RasterParams rstp; @@ -229,7 +229,7 @@ SliceParams get_slice_params(const DynamicPrintConfig &cfg) auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height"); if (!opt_layerh || !opt_init_layerh) - throw Slic3r::FileIOError("Invalid SL1 file"); + throw Slic3r::FileIOError("Invalid SL1 / SL1S file"); return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()}; } @@ -287,13 +287,13 @@ std::vector<ExPolygons> extract_slices_from_sla_archive( } // namespace -void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out) +ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out) { ArchiveData arch = extract_sla_archive(zipfname, "png"); - out.load(arch.profile); + return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable); } -void import_sla_archive( +ConfigSubstitutions import_sla_archive( const std::string & zipfname, Vec2i32 windowsize, TriangleMesh & out, @@ -305,7 +305,7 @@ void import_sla_archive( windowsize.y() = std::max(2, windowsize.y()); ArchiveData arch = extract_sla_archive(zipfname, "thumbnail"); - profile.load(arch.profile); + ConfigSubstitutions config_substitutions = profile.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable); RasterParams rstp = get_raster_params(profile); rstp.win = {windowsize.y(), windowsize.x()}; @@ -317,6 +317,8 @@ void import_sla_archive( if (!slices.empty()) out = slices_to_triangle_mesh(slices, 0, slicp.layerh, slicp.initial_layerh); + + return config_substitutions; } using ConfMap = std::map<std::string, std::string>; diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp index 71c9959b6..f214e7c07 100644 --- a/src/libslic3r/Format/SL1.hpp +++ b/src/libslic3r/Format/SL1.hpp @@ -25,23 +25,23 @@ public: void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = "") override; }; -void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out); +ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out); -void import_sla_archive( +ConfigSubstitutions import_sla_archive( const std::string & zipfname, Vec2i32 windowsize, TriangleMesh & out, DynamicPrintConfig & profile, std::function<bool(int)> progr = [](int) { return true; }); -inline void import_sla_archive( +inline ConfigSubstitutions import_sla_archive( const std::string & zipfname, Vec2i32 windowsize, TriangleMesh & out, std::function<bool(int)> progr = [](int) { return true; }) { DynamicPrintConfig profile; - import_sla_archive(zipfname, windowsize, out, profile, progr); + return import_sla_archive(zipfname, windowsize, out, profile, progr); } } // namespace Slic3r::sla diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index d4e1502ea..48a60af38 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -273,10 +273,11 @@ static inline void set_extra_lift(const Layer& layer, const Print& print, GCodeW // Retract for a tool change, using the toolchange retract value and setting the priming extra length. gcode += gcodegen.retract(true); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); - gcode += gcodegen.travel_to( + Polyline polyline = gcodegen.travel_to( + gcode, wipe_tower_point_to_object_point(gcodegen, start_pos), - erMixed, - "Travel to a Wipe Tower"); + erMixed); + gcodegen.write_travel_to(gcode, polyline, "Travel to a Wipe Tower"); gcode += gcodegen.unretract(); } @@ -727,6 +728,81 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re // free functions called by GCode::_do_export() namespace DoExport { + + class ExtrusionMinMM : public ExtrusionVisitorConst { + double min = std::numeric_limits<double>::max(); + std::unordered_set<ExtrusionRole> excluded; + public: + ExtrusionMinMM(const ConfigBase* config) { + excluded.insert(erIroning); + excluded.insert(erMilling); + excluded.insert(erCustom); + excluded.insert(erMixed); + excluded.insert(erNone); + excluded.insert(erWipeTower); + if (config->get_abs_value("perimeter_speed") != 0 && config->get_abs_value("small_perimeter_speed") != 0) { + excluded.insert(erPerimeter); + excluded.insert(erSkirt); + } + if (config->get_abs_value("external_perimeter_speed") != 0 && config->get_abs_value("small_perimeter_speed") != 0) + excluded.insert(erExternalPerimeter); + if (config->get_abs_value("overhangs_speed") != 0 && config->get_abs_value("small_perimeter_speed") != 0) + excluded.insert(erOverhangPerimeter); + if (config->get_abs_value("gap_fill_speed") != 0) + excluded.insert(erGapFill); + if (config->get_abs_value("thin_walls_speed") != 0) + excluded.insert(erThinWall); + if (config->get_abs_value("infill_speed") != 0) + excluded.insert(erInternalInfill); + if (config->get_abs_value("solid_infill_speed") != 0) + excluded.insert(erSolidInfill); + if (config->get_abs_value("top_solid_infill_speed") != 0) + excluded.insert(erTopSolidInfill); + if (config->get_abs_value("bridge_speed") != 0) + excluded.insert(erBridgeInfill); + if (config->get_abs_value("bridge_speed_internal") != 0) + excluded.insert(erInternalBridgeInfill); + if (config->get_abs_value("support_material_speed") != 0) + excluded.insert(erSupportMaterial); + if (config->get_abs_value("support_material_interface_speed") != 0) + excluded.insert(erSupportMaterialInterface); + } + virtual void use(const ExtrusionPath& path) override { + if (excluded.find(path.role()) == excluded.end()) + min = std::min(min, path.mm3_per_mm); + } + virtual void use(const ExtrusionPath3D& path3D) override { + if (excluded.find(path3D.role()) == excluded.end()) + min = std::min(min, path3D.mm3_per_mm); + } + virtual void use(const ExtrusionMultiPath& multipath) override { + for (const ExtrusionPath& path : multipath.paths) + use(path); + } + virtual void use(const ExtrusionMultiPath3D& multipath) override { + for (const ExtrusionPath& path : multipath.paths) + use(path); + } + virtual void use(const ExtrusionLoop& loop) override { + for (const ExtrusionPath& path : loop.paths) + use(path); + } + virtual void use(const ExtrusionEntityCollection& collection) override { + for (const ExtrusionEntity* entity : collection.entities) + entity->visit(*this); + } + double reset_use_get(const ExtrusionEntityCollection entity) { reset(); use(entity); return get(); } + double get() { return min; } + void reset() { min = std::numeric_limits<double>::max(); } + //test if at least a ExtrusionRole from tests is used for min computation + bool is_compatible(std::initializer_list<ExtrusionRole> tests) { + for (ExtrusionRole test : tests) + if (excluded.find(test) == excluded.end()) + return true; + return false; + } + }; + static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled) { silent_time_estimator_enabled = (config.gcode_flavor.value == gcfMarlin) && config.silent_mode; @@ -737,6 +813,7 @@ namespace DoExport { static double autospeed_volumetric_limit(const Print &print) { + ExtrusionMinMM compute_min_mm3_per_mm{ &print.full_print_config() }; // get the minimum cross-section used in the print std::vector<double> mm3_per_mm; for (auto object : print.objects()) { @@ -744,37 +821,20 @@ namespace DoExport { const PrintRegion* region = print.regions()[region_id]; for (auto layer : object->layers()) { const LayerRegion* layerm = layer->regions()[region_id]; - if (region->config().get_abs_value("perimeter_speed") == 0 || - region->config().get_abs_value("small_perimeter_speed") == 0 || - region->config().get_abs_value("external_perimeter_speed") == 0 || - region->config().get_abs_value("overhangs_speed") == 0) - mm3_per_mm.push_back(layerm->perimeters.min_mm3_per_mm()); - if (region->config().get_abs_value("infill_speed") == 0 || - region->config().get_abs_value("solid_infill_speed") == 0 || - region->config().get_abs_value("top_solid_infill_speed") == 0 || - region->config().get_abs_value("bridge_speed") == 0 || - region->config().get_abs_value("bridge_speed_internal") == 0) - { - // Minimal volumetric flow should not be calculated over ironing extrusions. - // Use following lambda instead of the built-it method. - // https://github.com/prusa3d/PrusaSlicer/issues/5082 - auto min_mm3_per_mm_no_ironing = [](const ExtrusionEntityCollection& eec) -> double { - double min = std::numeric_limits<double>::max(); - for (const ExtrusionEntity* ee : eec.entities) - if (ee->role() != erIroning) - min = std::min(min, ee->min_mm3_per_mm()); - return min; - }; - - mm3_per_mm.push_back(min_mm3_per_mm_no_ironing(layerm->fills)); - } + if (compute_min_mm3_per_mm.is_compatible({ erPerimeter, erExternalPerimeter, erOverhangPerimeter })) + mm3_per_mm.push_back(compute_min_mm3_per_mm.reset_use_get(layerm->perimeters)); + if (compute_min_mm3_per_mm.is_compatible({ erInternalInfill, erSolidInfill, erTopSolidInfill,erBridgeInfill,erInternalBridgeInfill })) + mm3_per_mm.push_back(compute_min_mm3_per_mm.reset_use_get(layerm->fills)); } } - if (object->config().get_abs_value("support_material_speed") == 0 || - object->config().get_abs_value("support_material_interface_speed") == 0) + if (compute_min_mm3_per_mm.is_compatible({ erSupportMaterial, erSupportMaterialInterface })) for (auto layer : object->support_layers()) - mm3_per_mm.push_back(layer->support_fills.min_mm3_per_mm()); + mm3_per_mm.push_back(compute_min_mm3_per_mm.reset_use_get(layer->support_fills)); } + if (compute_min_mm3_per_mm.is_compatible({ erSkirt })) { + mm3_per_mm.push_back(compute_min_mm3_per_mm.reset_use_get(print.skirt())); + mm3_per_mm.push_back(compute_min_mm3_per_mm.reset_use_get(print.brim())); + } // filter out 0-width segments mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [](double v) { return v < 0.000001; }), mm3_per_mm.end()); double volumetric_speed = 0.; @@ -1333,7 +1393,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Disable fan. if ((initial_extruder_id != (uint16_t)-1) && !this->config().start_gcode_manual && print.config().disable_fan_first_layers.get_at(initial_extruder_id)) - _write(file, m_writer.set_fan(0, true, initial_extruder_id)); + _write(file, m_writer.set_fan(uint8_t(0), true, initial_extruder_id)); //ensure fan is at the right speed print.throw_if_canceled(); @@ -1427,7 +1487,10 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer m_avoid_crossing_perimeters.use_external_mp_once(); _write(file, this->retract()); - _write(file, this->travel_to(Point(0, 0), erNone, "move to origin position for next object")); + std::string gcode; + Polyline polyline = this->travel_to(gcode, Point(0, 0), erNone); + this->write_travel_to(gcode, polyline, "move to origin position for next object"); + _write(file, gcode); m_enable_cooling_markers = true; // Disable motion planner when traveling to first object point. m_avoid_crossing_perimeters.disable_once(); @@ -1535,7 +1598,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu _add_object_change_labels(gcode); _write(file, gcode); } - _write(file, m_writer.set_fan(false)); + _write(file, m_writer.set_fan(uint8_t(0))); // adds tag for processor _write_format(file, ";%s%s\n", GCodeProcessor::Extrusion_Role_Tag.c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); @@ -3107,9 +3170,10 @@ std::string GCode::extrude_loop(const ExtrusionLoop &original_loop, const std::s double min_length = scale_(this->m_config.small_perimeter_min_length.get_abs_value(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0))); double max_length = scale_(this->m_config.small_perimeter_max_length.get_abs_value(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0))); if (loop.length() <= min_length) { - speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed); + speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed); } else { - speed = -(loop.length() - min_length) / (max_length - min_length); + //set speed between -1 and 0 you have to multiply the real peed by the opposite of that, and add the other part as small_perimeter_speed + speed = (min_length - loop.length()) / (max_length - min_length); } } @@ -3662,39 +3726,77 @@ std::string GCode::_before_extrude(const ExtrusionPath &path, const std::string std::string gcode; std::string description{ description_in }; - // go to first point of extrusion path - if (!m_last_pos_defined || m_last_pos != path.first_point()) { - gcode += this->travel_to( - path.first_point(), - path.role(), - "move to first " + description + " point" - ); - } - - //if needed, write the gcode_label_objects_end then gcode_label_objects_start - //should be already done by travel_to, but just in case - _add_object_change_labels(gcode); - - // compensate retraction - gcode += this->unretract(); - // adjust acceleration + // adjust acceleration, inside the travel to set the deceleration + double acceleration = get_default_acceleration(m_config); + double travel_acceleration = m_writer.get_acceleration(); { - double acceleration = get_default_acceleration(m_config); if (this->on_first_layer() && m_config.first_layer_acceleration.value > 0) { acceleration = m_config.first_layer_acceleration.get_abs_value(acceleration); } else if (m_config.perimeter_acceleration.value > 0 && is_perimeter(path.role())) { acceleration = m_config.perimeter_acceleration.get_abs_value(acceleration); - } else if (m_config.bridge_acceleration.value > 0 && is_bridge(path.role()) - && path.role() != erOverhangPerimeter ) { + } else if (m_config.bridge_acceleration.value > 0 && is_bridge(path.role()) + && path.role() != erOverhangPerimeter) { acceleration = m_config.bridge_acceleration.get_abs_value(acceleration); } else if (m_config.infill_acceleration.value > 0 && is_infill(path.role())) { acceleration = m_config.infill_acceleration.get_abs_value(acceleration); } - //travel acceleration should be already set at startup via special gcode, and so it's automatically used by G0. - m_writer.set_acceleration((uint16_t)floor(acceleration + 0.5)); + if (m_config.travel_acceleration.value > 0) + travel_acceleration = m_config.travel_acceleration.get_abs_value(acceleration); } + if (travel_acceleration == acceleration) { + m_writer.set_acceleration((uint32_t)floor(acceleration + 0.5)); + // go to first point of extrusion path (stop at midpoint to let us set the decel speed) + if (!m_last_pos_defined || m_last_pos != path.first_point()) { + Polyline polyline = this->travel_to(gcode, path.first_point(), path.role()); + this->write_travel_to(gcode, polyline, "move to first " + description + " point (" + std::to_string(acceleration) +" == "+ std::to_string(travel_acceleration)+")"); + } + } else { + // go to midpoint to let us set the decel speed) + if (!m_last_pos_defined || m_last_pos != path.first_point()) { + Polyline poly_start = this->travel_to(gcode, path.first_point(), path.role()); + coordf_t length = poly_start.length(); + if (length > SCALED_EPSILON) { + Polyline poly_end; + coordf_t min_length = scale_(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0.5)) * 20; + if (poly_start.size() > 2 && length > min_length * 3) { + //if complex travel, try to deccelerate only at the end, unless it's less than ~ 20 nozzle + if (poly_start.lines().back().length() < min_length) { + poly_end = poly_start; + poly_start.clip_end(min_length); + poly_end.clip_start(length - min_length); + } else { + poly_end.points.push_back(poly_start.points.back()); + poly_start.points.pop_back(); + poly_end.points.push_back(poly_start.points.back()); + poly_end.reverse(); + } + } else { + poly_end = poly_start; + poly_start.clip_end(length / 2); + poly_end.clip_start(length / 2); + } + m_writer.set_acceleration((uint32_t)floor(travel_acceleration + 0.5)); + this->write_travel_to(gcode, poly_start, "move to first " + description + " point (acceleration)"); + //travel acceleration should be already set at startup via special gcode, and so it's automatically used by G0. + m_writer.set_acceleration((uint32_t)floor(acceleration + 0.5)); + this->write_travel_to(gcode, poly_end, "move to first " + description + " point (deceleration)"); + } else { + m_writer.set_acceleration((uint32_t)floor(travel_acceleration + 0.5)); + this->write_travel_to(gcode, poly_start, "move to first " + description + " point (acceleration)"); + } + } else { + m_writer.set_acceleration((uint32_t)floor(acceleration + 0.5)); + } + } + + //if needed, write the gcode_label_objects_end then gcode_label_objects_start + //should be already done by travel_to, but just in case + _add_object_change_labels(gcode); + + // compensate retraction + gcode += this->unretract(); // set speed if (speed < 0) { @@ -3734,40 +3836,53 @@ std::string GCode::_before_extrude(const ExtrusionPath &path, const std::string if (factor < 1 && !(is_bridge(path.role()))) { float small_speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed); //apply factor between feature speed and small speed - speed = speed * factor + (1.f - factor) * small_speed; - } + speed = (speed * factor) + double((1.f - factor) * small_speed); + } } if (m_volumetric_speed != 0. && speed == 0) { //if m_volumetric_speed, use the max size for thinwall & gapfill, to avoid variations - speed = m_volumetric_speed / path.mm3_per_mm; - if (speed > m_config.max_print_speed.value) - speed = m_config.max_print_speed.value; + double vol_speed = m_volumetric_speed / path.mm3_per_mm; + if (vol_speed > m_config.max_print_speed.value) + vol_speed = m_config.max_print_speed.value; + // if using a % of an auto speed, use the % over the volumetric speed. + if (path.role() == erExternalPerimeter) { + speed = m_config.get_abs_value("external_perimeter_speed", vol_speed); + } else if (path.role() == erInternalBridgeInfill) { + speed = m_config.get_abs_value("bridge_speed_internal", vol_speed); + } else if (path.role() == erOverhangPerimeter) { + speed = m_config.get_abs_value("overhangs_speed", vol_speed); + } else if (path.role() == erSolidInfill) { + speed = m_config.get_abs_value("solid_infill_speed", vol_speed); + } else if (path.role() == erTopSolidInfill) { + speed = m_config.get_abs_value("top_solid_infill_speed", vol_speed); + } + if(speed == 0){ + speed = vol_speed; + } } if (speed == 0) // this code shouldn't trigger as if it's 0, you have to get a m_volumetric_speed speed = m_config.max_print_speed.value; if (this->on_first_layer()) - if (path.role() == erInternalInfill || path.role() == erSolidInfill) - speed = std::min(m_config.get_abs_value("first_layer_infill_speed", speed), speed); - else - speed = std::min(m_config.get_abs_value("first_layer_speed", speed), speed); - if (m_config.max_volumetric_speed.value > 0) { - // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) - speed = std::min( - speed, - m_config.max_volumetric_speed.value / path.mm3_per_mm - ); - } - if (EXTRUDER_CONFIG_WITH_DEFAULT(filament_max_volumetric_speed, 0) > 0) { - // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) - speed = std::min( - speed, - EXTRUDER_CONFIG_WITH_DEFAULT(filament_max_volumetric_speed, speed) / path.mm3_per_mm - ); + if (path.role() == erInternalInfill || path.role() == erSolidInfill) { + double first_layer_infill_speed = m_config.get_abs_value("first_layer_infill_speed", speed); + if(first_layer_infill_speed > 0) + speed = std::min(first_layer_infill_speed, speed); + } else { + double first_layer_speed = m_config.get_abs_value("first_layer_speed", speed); + if (first_layer_speed > 0) + speed = std::min(first_layer_speed, speed); + } + // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) + if (m_config.max_volumetric_speed.value > 0 && path.mm3_per_mm > 0) { + speed = std::min(m_config.max_volumetric_speed.value / path.mm3_per_mm, speed); + } + double filament_max_volumetric_speed = EXTRUDER_CONFIG_WITH_DEFAULT(filament_max_volumetric_speed, 0); + if (filament_max_volumetric_speed > 0) { + speed = std::min(filament_max_volumetric_speed / path.mm3_per_mm, speed); } - if (EXTRUDER_CONFIG_WITH_DEFAULT(filament_max_speed, 0) > 0) { - speed = std::min( - speed, - EXTRUDER_CONFIG_WITH_DEFAULT(filament_max_speed, speed)); + double filament_max_speed = EXTRUDER_CONFIG_WITH_DEFAULT(filament_max_speed, 0); + if (filament_max_speed > 0) { + speed = std::min(filament_max_speed, speed); } double F = speed * 60; // convert mm/sec to mm/min @@ -3881,7 +3996,7 @@ void GCode::_add_object_change_labels(std::string& gcode) { } // This method accepts &point in print coordinates. -std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string comment) +Polyline GCode::travel_to(std::string &gcode, const Point &point, ExtrusionRole role) { /* Define the travel move as a line between current position and the taget point. This is expressed in print coordinates, so it will need to be translated by @@ -3910,7 +4025,6 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string m_avoid_crossing_perimeters.reset_once_modifiers(); // generate G-code for the travel move - std::string gcode; if (needs_retraction) { if (m_config.avoid_crossing_perimeters && could_be_wipe_disabled) m_wipe.reset_path(); @@ -3931,15 +4045,19 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string //if needed, write the gcode_label_objects_end then gcode_label_objects_start _add_object_change_labels(gcode); + return travel; +} + + +void GCode::write_travel_to(std::string &gcode, const Polyline& travel, std::string comment) +{ // use G1 because we rely on paths being straight (G0 may make round paths) if (travel.size() >= 2) { - for (size_t i = 1; i < travel.size(); ++ i) + for (size_t i = 1; i < travel.size(); ++i) gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment); this->set_last_pos(travel.points.back()); } - return gcode; } - bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) { if (travel.length() < scale_(EXTRUDER_CONFIG_WITH_DEFAULT(retract_before_travel, 0))) { diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 9559a8a6b..a116d5dfe 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -315,7 +315,8 @@ private: std::string extrude_ironing(const Print& print, const std::vector<ObjectByExtruder::Island::Region>& by_region); std::string extrude_support(const ExtrusionEntityCollection &support_fills); - std::string travel_to(const Point &point, ExtrusionRole role, std::string comment); + Polyline travel_to(std::string& gcode, const Point &point, ExtrusionRole role); + void write_travel_to(std::string& gcode, const Polyline& travel, std::string comment); bool needs_retraction(const Polyline &travel, ExtrusionRole role = erNone); std::string retract(bool toolchange = false); std::string unretract() { return m_writer.unlift() + m_writer.unretract(); } diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index e84380de9..51e6fbb7a 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -878,7 +878,10 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr if (m_producer == EProducer::PrusaSlicer || m_producer == EProducer::SuperSlicer || m_producer == EProducer::Slic3rPE || m_producer == EProducer::Slic3r) { DynamicPrintConfig config; config.apply(FullPrintConfig::defaults()); - config.load_from_gcode_file(filename); + // Silently substitute unknown values by new ones for loading configurations from PrusaSlicer's own G-code. + // Showing substitution log or errors may make sense, but we are not really reading many values from the G-code config, + // thus a probability of incorrect substitution is low and the G-code viewer is a consumer-only anyways. + config.load_from_gcode_file(filename, ForwardCompatibilitySubstitutionRule::EnableSilent); apply_config(config); } } diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 3c0f743df..e9ababa33 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -9,7 +9,7 @@ #define FLAVOR_IS(val) this->config.gcode_flavor.value == val #define FLAVOR_IS_NOT(val) this->config.gcode_flavor.value != val #define COMMENT(comment) if (this->config.gcode_comments.value && !comment.empty()) gcode << " ; " << comment; -#define PRECISION(val, precision) std::fixed << std::setprecision(precision) << (val) +#define PRECISION(val, precision) to_string_nozero(val, precision) #define XYZ_NUM(val) PRECISION(val, this->config.gcode_precision_xyz.value) #define FLOAT_PRECISION(val, precision) std::defaultfloat << std::setprecision(precision) << (val) #define F_NUM(val) FLOAT_PRECISION(val, 8) @@ -17,6 +17,34 @@ namespace Slic3r { +std::string to_string_nozero(double value, int32_t max_precision) { + double intpart; + if (modf(value, &intpart) == 0.0) { + //shortcut for int + return boost::lexical_cast<std::string>(intpart); + } else { + std::stringstream ss; + //first, get the int part, to see how many digit it takes + int long10 = 0; + if (intpart > 9) + long10 = std::floor(std::log10(std::abs(intpart))); + //set the usable precision: there is only 15-16 decimal digit in a double + ss << std::fixed << std::setprecision(int(std::min(15 - long10, int(max_precision)))) << value; + std::string ret = ss.str(); + uint8_t nb_del = 0; + for (uint8_t i = uint8_t(ss.tellp()) - 1; i > 0; i--) { + if (ret[i] == '0') + nb_del++; + else + break; + } + if (nb_del > 0) + return ret.substr(0, ret.size() - nb_del); + else + return ret; + } +} + std::string GCodeWriter::PausePrintCode = "M601"; void GCodeWriter::apply_print_config(const PrintConfig &print_config) @@ -129,19 +157,19 @@ std::string GCodeWriter::postamble() const return gcode.str(); } -std::string GCodeWriter::set_temperature(const unsigned int temperature, bool wait, int tool) +std::string GCodeWriter::set_temperature(const int16_t temperature, bool wait, int tool) { //use m_tool if tool isn't set if (tool < 0 && m_tool != nullptr) tool = m_tool->id(); //add offset - int16_t temp_w_offset = int16_t(temperature); + int16_t temp_w_offset = temperature; temp_w_offset += int16_t(get_tool(tool)->temp_offset()); temp_w_offset = std::max(int16_t(0), std::min(int16_t(2000), temp_w_offset)); // temp_w_offset has an effective minimum value of 0, so this cast is safe. - if (m_last_temperature_with_offset == static_cast<uint16_t>(temp_w_offset) && !wait) + if (m_last_temperature_with_offset == temp_w_offset && !wait) return ""; if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish))) return ""; @@ -184,7 +212,7 @@ std::string GCodeWriter::set_temperature(const unsigned int temperature, bool wa return gcode.str(); } -std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait) +std::string GCodeWriter::set_bed_temperature(uint32_t temperature, bool wait) { if (temperature == m_last_bed_temperature && (! wait || m_last_bed_temperature_reached)) return std::string(); @@ -220,25 +248,25 @@ std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait return gcode.str(); } -std::string GCodeWriter::set_fan(const unsigned int speed, bool dont_save, uint16_t default_tool) +std::string GCodeWriter::set_fan(const uint8_t speed, bool dont_save, uint16_t default_tool) { std::ostringstream gcode; const Tool *tool = m_tool == nullptr ? get_tool(default_tool) : m_tool; //add fan_offset - int16_t fan_speed = int16_t(speed); + int8_t fan_speed = int8_t(std::min(uint8_t(100), speed)); if (tool != nullptr) - fan_speed += int8_t(tool->fan_offset()); - fan_speed = std::max(int16_t(0), std::min(int16_t(100), fan_speed)); + fan_speed += tool->fan_offset(); + fan_speed = std::max(int8_t(0), std::min(int8_t(100), fan_speed)); const auto fan_baseline = (this->config.fan_percentage.value ? 100.0 : 255.0); // fan_speed has an effective minimum value of 0, so this cast is safe. //test if it's useful to write it - if (m_last_fan_speed_with_offset != static_cast<uint16_t>(fan_speed) || dont_save) { + if (m_last_fan_speed_with_offset != fan_speed || dont_save) { //save new current value if (!dont_save) { m_last_fan_speed = speed; - m_last_fan_speed_with_offset = fan_speed; + m_last_fan_speed_with_offset = uint8_t(fan_speed); } // write it @@ -271,7 +299,7 @@ std::string GCodeWriter::set_fan(const unsigned int speed, bool dont_save, uint1 return gcode.str(); } -void GCodeWriter::set_acceleration(unsigned int acceleration) +void GCodeWriter::set_acceleration(uint32_t acceleration) { // Clamp the acceleration to the allowed maximum. if (m_max_acceleration > 0 && acceleration > m_max_acceleration) @@ -283,6 +311,11 @@ void GCodeWriter::set_acceleration(unsigned int acceleration) m_current_acceleration = acceleration; } +uint32_t GCodeWriter::get_acceleration() const +{ + return m_current_acceleration; +} + std::string GCodeWriter::write_acceleration(){ if (m_current_acceleration == m_last_acceleration || m_current_acceleration == 0) return ""; @@ -294,9 +327,12 @@ std::string GCodeWriter::write_acceleration(){ if (FLAVOR_IS(gcfRepetier)) { // M201: Set max printing acceleration gcode << "M201 X" << m_current_acceleration << " Y" << m_current_acceleration; - } else if(FLAVOR_IS(gcfMarlin) || FLAVOR_IS(gcfLerdge) || FLAVOR_IS(gcfRepRap) || FLAVOR_IS(gcfSprinter)){ + } else if(FLAVOR_IS(gcfMarlin) || FLAVOR_IS(gcfLerdge) || FLAVOR_IS(gcfSprinter)){ // M204: Set printing acceleration gcode << "M204 P" << m_current_acceleration; + } else if (FLAVOR_IS(gcfRepRap)) { + // M204: Set printing & travel acceleration + gcode << "M204 P" << m_current_acceleration <<" T" << m_current_acceleration; } else { // M204: Set default acceleration gcode << "M204 S" << m_current_acceleration; @@ -331,16 +367,16 @@ std::string GCodeWriter::reset_e(bool force) } } -std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, bool allow_100) const +std::string GCodeWriter::update_progress(uint32_t num, uint32_t tot, bool allow_100) const { if (FLAVOR_IS_NOT(gcfMakerWare) && FLAVOR_IS_NOT(gcfSailfish)) return ""; - unsigned int percent = (unsigned int)floor(100.0 * num / tot + 0.5); - if (!allow_100) percent = std::min(percent, (unsigned int)99); + uint8_t percent = (uint32_t)floor(100.0 * num / tot + 0.5); + if (!allow_100) percent = std::min(percent, (uint8_t)99); std::ostringstream gcode; - gcode << "M73 P" << percent; + gcode << "M73 P" << int(percent); if (this->config.gcode_comments) gcode << " ; update progress"; gcode << "\n"; return gcode.str(); @@ -354,7 +390,7 @@ std::string GCodeWriter::toolchange_prefix() const "T"; } -std::string GCodeWriter::toolchange(unsigned int tool_id) +std::string GCodeWriter::toolchange(uint16_t tool_id) { // set the new extruder /*auto it_extruder = Slic3r::lower_bound_by_predicate(m_extruders.begin(), m_extruders.end(), [tool_id](const Extruder &e) { return e.id() < tool_id; }); diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index 0d8c7b42c..084b8e4e1 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -44,24 +44,25 @@ public: const Tool* get_tool(uint16_t id) const; std::string preamble(); std::string postamble() const; - std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1); - std::string set_bed_temperature(unsigned int temperature, bool wait = false); - unsigned int get_fan() { return m_last_fan_speed; } + std::string set_temperature(int16_t temperature, bool wait = false, int tool = -1); + std::string set_bed_temperature(uint32_t temperature, bool wait = false); + uint8_t get_fan() { return m_last_fan_speed; } /// set fan at speed. Save it as current fan speed if !dont_save, and use tool default_tool if the internal m_tool is null (no toolchange done yet). - std::string set_fan(unsigned int speed, bool dont_save = false, uint16_t default_tool = 0); - void set_acceleration(unsigned int acceleration); + std::string set_fan(uint8_t speed, bool dont_save = false, uint16_t default_tool = 0); + void set_acceleration(uint32_t acceleration); + uint32_t get_acceleration() const; std::string write_acceleration(); std::string reset_e(bool force = false); - std::string update_progress(unsigned int num, unsigned int tot, bool allow_100 = false) const; + std::string update_progress(uint32_t num, uint32_t tot, bool allow_100 = false) const; // return false if this extruder was already selected - bool need_toolchange(unsigned int tool_id) const + bool need_toolchange(uint16_t tool_id) const { return m_tool == nullptr || m_tool->id() != tool_id; } - std::string set_tool(unsigned int tool_id) + std::string set_tool(uint16_t tool_id) { return this->need_toolchange(tool_id) ? this->toolchange(tool_id) : ""; } // Prefix of the toolchange G-code line, to be used by the CoolingBuffer to separate sections of the G-code // printed with the same extruder. std::string toolchange_prefix() const; - std::string toolchange(unsigned int tool_id); + std::string toolchange(uint16_t tool_id); std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()) const; std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string()); std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string()); @@ -84,16 +85,16 @@ private: std::string m_extrusion_axis; bool m_single_extruder_multi_material; Tool* m_tool; - unsigned int m_last_acceleration; - unsigned int m_current_acceleration; + uint32_t m_last_acceleration; + uint32_t m_current_acceleration; // Limit for setting the acceleration, to respect the machine limits set for the Marlin firmware. // If set to zero, the limit is not in action. - unsigned int m_max_acceleration; - unsigned int m_last_fan_speed; - unsigned int m_last_fan_speed_with_offset; - unsigned int m_last_temperature; - unsigned int m_last_temperature_with_offset; - unsigned int m_last_bed_temperature; + uint32_t m_max_acceleration; + uint8_t m_last_fan_speed; + uint8_t m_last_fan_speed_with_offset; + int16_t m_last_temperature; + int16_t m_last_temperature_with_offset; + int16_t m_last_bed_temperature; bool m_last_bed_temperature_reached; double m_lifted; Vec3d m_pos = Vec3d::Zero(); diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 49d75e5e0..ed0c70071 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -49,7 +49,7 @@ public: // collection of expolygons representing the bridged areas (thus not // needing support material) - Polygons bridged; +// Polygons bridged; // collection of polylines representing the unsupported bridge edges Polylines unsupported_bridge_edges; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 214bd6e87..3a5bcfd5b 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -327,7 +327,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly }else if (bd.detect_angle(custom_angle)) { bridges[idx_last].bridge_angle = bd.angle; if (this->layer()->object()->config().support_material) { - polygons_append(this->bridged, intersection(bd.coverage(), to_polygons(initial))); + //polygons_append(this->bridged, intersection(bd.coverage(), to_polygons(initial))); append(this->unsupported_bridge_edges, bd.unsupported_edges()); } } else { diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 7246dde30..11098c972 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -121,13 +121,17 @@ bool Model::equals(const Model& rhs) const { return true; } -Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances, bool check_version) +// Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well. +Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options) { Model model; DynamicPrintConfig temp_config; + ConfigSubstitutionContext temp_config_substitutions_context(ForwardCompatibilitySubstitutionRule::EnableSilent); if (config == nullptr) config = &temp_config; + if (config_substitutions == nullptr) + config_substitutions = &temp_config_substitutions_context; bool result = false; if (boost::algorithm::iends_with(input_file, ".stl")) @@ -135,9 +139,10 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c else if (boost::algorithm::iends_with(input_file, ".obj")) result = load_obj(input_file.c_str(), &model); else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml")) - result = load_amf(input_file.c_str(), config, &model, check_version); + result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion); else if (boost::algorithm::iends_with(input_file, ".3mf")) - result = load_3mf(input_file.c_str(), config, &model, false); + //FIXME options & LoadAttribute::CheckVersion ? + result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false); else if (boost::algorithm::iends_with(input_file, ".prusa")) result = load_prus(input_file.c_str(), &model); else @@ -152,24 +157,29 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c for (ModelObject *o : model.objects) o->input_file = input_file; - if (add_default_instances) + if (options & LoadAttribute::AddDefaultInstances) model.add_default_instances(); CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config); CustomGCode::check_mode_for_custom_gcode_per_print_z(model.custom_gcode_per_print_z); + sort_remove_duplicates(config_substitutions->substitutions); return model; } -Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances, bool check_version) +// Loading model from a file (3MF or AMF), not from a simple geometry file (STL or OBJ). +Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options) { + assert(config != nullptr); + assert(config_substitutions != nullptr); + Model model; bool result = false; if (boost::algorithm::iends_with(input_file, ".3mf")) - result = load_3mf(input_file.c_str(), config, &model, check_version); + result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, options & LoadAttribute::CheckVersion); else if (boost::algorithm::iends_with(input_file, ".zip.amf")) - result = load_amf(input_file.c_str(), config, &model, check_version); + result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion); else throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension."); @@ -191,7 +201,7 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig o->input_file = input_file; } - if (add_default_instances) + if (options & LoadAttribute::AddDefaultInstances) model.add_default_instances(); CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config); @@ -424,13 +434,12 @@ bool Model::looks_like_multipart_object() const } // Generate next extruder ID string, in the range of (1, max_extruders). -static inline std::string auto_extruder_id(unsigned int max_extruders, unsigned int &cntr) +static inline int auto_extruder_id(unsigned int max_extruders, unsigned int &cntr) { - char str_extruder[64]; - sprintf(str_extruder, "%ud", cntr + 1); - if (++ cntr == max_extruders) + int out = ++ cntr; + if (cntr == max_extruders) cntr = 0; - return str_extruder; + return out; } void Model::convert_multipart_object(unsigned int max_extruders) @@ -457,7 +466,7 @@ void Model::convert_multipart_object(unsigned int max_extruders) auto copy_volume = [o, max_extruders, &counter, &extruder_counter](ModelVolume *new_v) { assert(new_v != nullptr); new_v->name = o->name + "_" + std::to_string(counter++); - new_v->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter)); + new_v->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter)); return new_v; }; if (o->instances.empty()) { @@ -1766,7 +1775,7 @@ size_t ModelVolume::split(unsigned int max_extruders) this->object->volumes[ivolume]->center_geometry_after_creation(); this->object->volumes[ivolume]->translate(offset); this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1); - this->object->volumes[ivolume]->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter)); + this->object->volumes[ivolume]->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter)); delete mesh; ++ idx; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index b1b86f4a6..2190080c8 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -12,6 +12,7 @@ #include "TriangleMesh.hpp" #include "Arrange.hpp" #include "CustomGCode.hpp" +#include "enum_bitmask.hpp" #include <map> #include <memory> @@ -996,8 +997,20 @@ public: OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model) - static Model read_from_file(const std::string& input_file, DynamicPrintConfig* config = nullptr, bool add_default_instances = true, bool check_version = false); - static Model read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances = true, bool check_version = false); + enum class LoadAttribute : int { + AddDefaultInstances, + CheckVersion + }; + using LoadAttributes = enum_bitmask<LoadAttribute>; + + static Model read_from_file( + const std::string& input_file, + DynamicPrintConfig* config = nullptr, ConfigSubstitutionContext* config_substitutions = nullptr, + LoadAttributes options = LoadAttribute::AddDefaultInstances); + static Model read_from_archive( + const std::string& input_file, + DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, + LoadAttributes options = LoadAttribute::AddDefaultInstances); bool equals(const Model& rhs) const; @@ -1062,6 +1075,8 @@ private: } }; +ENABLE_ENUM_BITMASK_OPERATORS(Model::LoadAttribute) + #undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE #undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE diff --git a/src/libslic3r/Optimize/Optimizer.hpp b/src/libslic3r/Optimize/Optimizer.hpp index 05191eba2..8ae55c61c 100644 --- a/src/libslic3r/Optimize/Optimizer.hpp +++ b/src/libslic3r/Optimize/Optimizer.hpp @@ -8,6 +8,7 @@ #include <functional> #include <limits> #include <cassert> +#include <optional> namespace Slic3r { namespace opt { diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 33e675082..ece14427c 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -99,16 +99,54 @@ void PerimeterGenerator::process() coord_t ext_min_spacing = (coord_t)( ext_perimeter_spacing2 * (1 - 0.05/*INSET_OVERLAP_TOLERANCE*/) ); // prepare grown lower layer slices for overhang detection - if (this->lower_slices != NULL && this->config->overhangs_width.value > 0) { + if (this->lower_slices != NULL && (this->config->overhangs_width.value > 0 || this->config->overhangs_width_speed.value > 0)) { // We consider overhang any part where the entire nozzle diameter is not supported by the // lower layer, so we take lower slices and offset them by overhangs_width of the nozzle diameter used // in the current layer - double offset_val = double(scale_(config->overhangs_width.get_abs_value(nozzle_diameter))) - (float)(ext_perimeter_width / 2); - this->_lower_slices_bridge_flow = offset(*this->lower_slices, offset_val); - } - if (this->lower_slices != NULL && this->config->overhangs_width_speed.value > 0) { - double offset_val = double(scale_(config->overhangs_width_speed.get_abs_value(nozzle_diameter))) - (float)(ext_perimeter_width / 2); - this->_lower_slices_bridge_speed = offset(*this->lower_slices, offset_val); + + //we use a range to avoid threshold issues. + coord_t overhangs_width_flow = scale_(config->overhangs_width.get_abs_value(nozzle_diameter)); + coord_t overhangs_width_speed = scale_(config->overhangs_width_speed.get_abs_value(nozzle_diameter)); + coord_t min_feature = std::min(overhangs_width_flow, overhangs_width_speed) / 2; + coord_t overhangs_width_flow_90 = coord_t(overhangs_width_flow * 0.99); + coord_t overhangs_width_flow_110 = coord_t(overhangs_width_flow * 1.15); + coord_t overhangs_width_speed_90 = coord_t(overhangs_width_speed * 0.99); + coord_t overhangs_width_speed_110 = coord_t(overhangs_width_speed * 1.15); + + //flow offset should be greater than speed offset because the flow apply also the speed. + //check if overhangs_width_speed is low enough to be relevant (if flow is activated) + if (overhangs_width_flow > 0 && overhangs_width_speed + nozzle_diameter * 0.01 > overhangs_width_flow) { + overhangs_width_speed = 0; + overhangs_width_speed_90 = 0; + overhangs_width_speed_110 = 0; + } + if (overhangs_width_flow > 0) { + if (overhangs_width_flow_90 < overhangs_width_speed_110) { + overhangs_width_speed_110 = overhangs_width_flow_90 = (overhangs_width_flow + overhangs_width_speed) / 2; + } + } + + if (overhangs_width_speed > 0 || overhangs_width_flow > 0) { + ExPolygons simplified; + //simplify the lower slices if too high (means low number) resolution (we can be very aggressive here) + if (this->print_config->resolution < min_feature / 2) { + for (const ExPolygon& expoly : *lower_slices) { + expoly.simplify(min_feature, &simplified); + } + } + + if (overhangs_width_speed > 0) { + this->_lower_slices_bridge_speed_small = offset((simplified.empty() ? *this->lower_slices : simplified), (coordf_t)overhangs_width_speed_90 - (coordf_t)(ext_perimeter_width / 2)); + this->_lower_slices_bridge_speed_big = offset((simplified.empty() ? *this->lower_slices : simplified), (coordf_t)overhangs_width_speed_110 - (coordf_t)(ext_perimeter_width / 2)); + } + if (overhangs_width_flow > 0) { + if (overhangs_width_speed_110 == overhangs_width_flow_90) + this->_lower_slices_bridge_flow_small = this->_lower_slices_bridge_speed_big; + else + this->_lower_slices_bridge_flow_small = offset((simplified.empty() ? *this->lower_slices : simplified), (coordf_t)overhangs_width_flow_90 - (coordf_t)(ext_perimeter_width / 2)); + this->_lower_slices_bridge_flow_big = offset((simplified.empty() ? *this->lower_slices : simplified), (coordf_t)overhangs_width_flow_110 - (coordf_t)(ext_perimeter_width / 2)); + } + } } // have to grown the perimeters if mill post-process @@ -337,6 +375,7 @@ void PerimeterGenerator::process() // simplification already done at slicing //ExPolygons last = union_ex(surface.expolygon.simplify_p(SCALED_RESOLUTION)); ExPolygons last = union_ex(surface.expolygon); + double last_area = -1; if (loop_number >= 0) { @@ -525,6 +564,8 @@ void PerimeterGenerator::process() (float)(ext_min_spacing / 4), (round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter), (round_peri ? min_round_spacing : 3))); + + next_onion = intersection_ex(next_onion, last); } } if (m_spiral_vase && next_onion.size() > 1) { @@ -547,11 +588,23 @@ void PerimeterGenerator::process() +(float)(min_spacing / 2 - 1), (round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter), (round_peri ? min_round_spacing : 3)); - + // now try with different min spacing if we fear some hysteresis + //TODO, do that for each polygon from last, instead to do for all of them in one go. ExPolygons no_thin_onion = offset_ex(last, double(-good_spacing)); + if (last_area < 0) { + last_area = 0; + for (const ExPolygon& expoly : last) { + last_area += expoly.area(); + } + } + double new_area = 0; + for (const ExPolygon& expoly : next_onion) { + new_area += expoly.area(); + } + std::vector<float> divs { 1.8f, 1.6f }; //don't over-extrude, so don't use divider >2 size_t idx_div = 0; - while (next_onion.size() > no_thin_onion.size() && idx_div < divs.size()) { + while ((next_onion.size() > no_thin_onion.size() || (new_area != 0 && last_area > new_area * 100)) && idx_div < divs.size()) { float div = divs[idx_div]; //use a sightly bigger spacing to try to drastically improve the split, that can lead to very thick gapfill ExPolygons next_onion_secondTry = offset2_ex( @@ -559,10 +612,21 @@ void PerimeterGenerator::process() -(float)(good_spacing + (min_spacing / div) - 1), +(float)((min_spacing / div) - 1)); if (next_onion.size() > next_onion_secondTry.size() * 1.2 && next_onion.size() > next_onion_secondTry.size() + 2) { + // don't get it if it creates too many next_onion = next_onion_secondTry; + } else if (next_onion.size() > next_onion_secondTry.size() || last_area > new_area * 100) { + // don't get it if it's too small + double area_new = 0; + for (const ExPolygon& expoly : next_onion_secondTry) { + area_new += expoly.area(); + } + if (last_area > area_new * 100 || new_area == 0) { + next_onion = next_onion_secondTry; + } } idx_div++; } + last_area = new_area; } else { // If "overlapping_perimeters" is enabled, this paths will be entered, which @@ -1073,92 +1137,238 @@ void PerimeterGenerator::process() } // for each island } -template<typename LINE> -ExtrusionPaths PerimeterGenerator::create_overhangs(LINE loop_polygons, ExtrusionRole role, bool is_external) const { + +ExtrusionPaths PerimeterGenerator::create_overhangs(const Polyline& loop_polygons, ExtrusionRole role, bool is_external) const { ExtrusionPaths paths; double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder - 1); if (this->config->overhangs_width.get_abs_value(nozzle_diameter) == 0 && 0 == this->config->overhangs_width_speed.get_abs_value(nozzle_diameter)) { //error + ExtrusionPath path(role); + path.polyline = loop_polygons; + path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm; + path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width; + path.height = (float)this->layer->height; + assert(path.mm3_per_mm == path.mm3_per_mm); + assert(path.width == path.width); + assert(path.height == path.height); + paths.push_back(path); return paths; - } else if (this->config->overhangs_width.get_abs_value(nozzle_diameter) > this->config->overhangs_width_speed.get_abs_value(nozzle_diameter)) { - //set the fan & speed before the flow + } + //set the fan & speed before the flow + Polylines ok_polylines = { loop_polygons }; + + Polylines small_speed; + Polylines big_speed; + bool no_small_flow = _lower_slices_bridge_speed_big == _lower_slices_bridge_flow_small; + Polylines small_flow; + Polylines big_flow; + + Polylines* previous = &ok_polylines; + if (this->config->overhangs_width_speed.value > 0) { + if (!this->_lower_slices_bridge_speed_small.empty()) { + small_speed = diff_pl(*previous, this->_lower_slices_bridge_speed_small); + if (!small_speed.empty()) { + *previous = intersection_pl(*previous, this->_lower_slices_bridge_speed_small); + previous = &small_speed; + } + } + if (!this->_lower_slices_bridge_speed_big.empty()) { + big_speed = diff_pl(*previous, this->_lower_slices_bridge_speed_big); + if (!big_speed.empty()) { + *previous = intersection_pl(*previous, this->_lower_slices_bridge_speed_big); + previous = &big_speed; + } + } + } + if (this->config->overhangs_width.value > 0) { + if (!this->_lower_slices_bridge_flow_small.empty()) { + small_flow = diff_pl(*previous, this->_lower_slices_bridge_flow_small); + if (!small_flow.empty()) { + *previous = intersection_pl(*previous, this->_lower_slices_bridge_flow_small); + previous = &small_flow; + } + } + if (!this->_lower_slices_bridge_flow_big.empty()) { + big_flow = diff_pl(*previous, this->_lower_slices_bridge_flow_big); + if (!big_flow.empty()) { + *previous = intersection_pl(*previous, this->_lower_slices_bridge_flow_big); + previous = &big_flow; + } + } + } - // get non-overhang paths by intersecting this loop with the grown lower slices + //note: layer height is used to identify the path type + if (!ok_polylines.empty()) { extrusion_paths_append( paths, - (this->config->overhangs_width_speed.value > 0 ? - intersection_pl(std::vector<LINE>{ loop_polygons }, this->_lower_slices_bridge_speed) : - intersection_pl(std::vector<LINE>{ loop_polygons }, this->_lower_slices_bridge_flow)), + ok_polylines, role, is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm, is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width, - (float)this->layer->height); - - // get overhang paths by checking what parts of this loop fall - // outside the grown lower slices - Polylines poly_speed = - (this->config->overhangs_width_speed.value > 0 ? - diff_pl(std::vector<LINE>{ loop_polygons }, this->_lower_slices_bridge_speed) : - Polylines{}); - if(this->config->overhangs_width_speed.value > 0) - extrusion_paths_append( - paths, - intersection_pl(poly_speed, this->_lower_slices_bridge_flow), - erOverhangPerimeter, - is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm, - is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width, - (float)this->layer->height); - + 0); + } + if (!small_speed.empty()) { extrusion_paths_append( paths, - (this->config->overhangs_width_speed.value > 0 ? - diff_pl(poly_speed, this->_lower_slices_bridge_flow): - diff_pl(std::vector<LINE>{ loop_polygons }, this->_lower_slices_bridge_flow)), + small_speed, erOverhangPerimeter, - this->_mm3_per_mm_overhang, - this->overhang_flow.width, - this->overhang_flow.height); - - } else { - //can't set flow without fan & speed - - // get non-overhang paths by intersecting this loop with the grown lower slices + is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm, + is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width, + no_small_flow ? 2 : 1); + } + if (!big_speed.empty()) { extrusion_paths_append( paths, - (this->config->overhangs_width.value > 0 ? - intersection_pl(std::vector<LINE>{ loop_polygons }, this->_lower_slices_bridge_flow) : - intersection_pl(std::vector<LINE>{ loop_polygons }, this->_lower_slices_bridge_speed)), - role, + big_speed, + erOverhangPerimeter, is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm, is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width, - (float)this->layer->height); - - // get overhang paths by checking what parts of this loop fall - // outside the grown lower slices - if (this->config->overhangs_width.value > 0) { - extrusion_paths_append( - paths, - diff_pl(std::vector<LINE>{ loop_polygons }, this->_lower_slices_bridge_flow), - erOverhangPerimeter, - this->_mm3_per_mm_overhang, - this->overhang_flow.width, - this->overhang_flow.height); - } else { - extrusion_paths_append( - paths, - diff_pl(std::vector<LINE>{ loop_polygons }, this->_lower_slices_bridge_speed), - erOverhangPerimeter, - is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm, - is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width, - (float)this->layer->height); - } + no_small_flow ? 3 : 2); + } + if (!small_flow.empty()) { + extrusion_paths_append( + paths, + small_flow, + erOverhangPerimeter, + this->_mm3_per_mm_overhang, + this->overhang_flow.width, + 3); + } + if (!big_flow.empty()) { + extrusion_paths_append( + paths, + big_flow, + erOverhangPerimeter, + this->_mm3_per_mm_overhang, + this->overhang_flow.width, + 4); } // reapply the nearest point search for starting point // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. if(!paths.empty()) chain_and_reorder_extrusion_paths(paths, &paths.front().first_point()); + + bool has_normal = !ok_polylines.empty(); + bool has_speed = !small_speed.empty() || !big_speed.empty(); + bool has_flow = !small_flow.empty() || !big_flow.empty(); + + std::function<void(ExtrusionPaths&, const std::function<bool(ExtrusionPath&, ExtrusionPath&, ExtrusionPath&)>&)> foreach = [](ExtrusionPaths &paths, const std::function<bool(ExtrusionPath&, ExtrusionPath&, ExtrusionPath&)>& doforeach) { + if (paths.size() > 2) + for (int i = 1; i < paths.size() - 1; i++) { + if (doforeach(paths[i - 1], paths[i], paths[i + 1])) { + paths.erase(paths.begin() + i); + i--; + if (paths[i].height == paths[i + 1].height) { + paths[i].polyline.points.insert(paths[i].polyline.points.end(), paths[i + 1].polyline.points.begin() + 1, paths[i + 1].polyline.points.end()); + paths.erase(paths.begin() + i + 1); + } + } + } + if (paths.size() > 2) + if (doforeach(paths[paths.size() - 2], paths.back(), paths.front())) { + paths.erase(paths.end() - 1); + if (paths.back().height == paths.front().height) { + paths.front().polyline.points.insert(paths.front().polyline.points.begin(), paths.back().polyline.points.begin(), paths.back().polyline.points.end() - 1); + paths.erase(paths.end() - 1); + } + } + if (paths.size() > 2) + if (doforeach(paths.back(), paths.front(), paths[1])) { + paths.erase(paths.begin()); + if (paths.back().height == paths.front().height) { + paths.front().polyline.points.insert(paths.front().polyline.points.begin(), paths.back().polyline.points.begin(), paths.back().polyline.points.end() - 1); + paths.erase(paths.end() - 1); + } + } + }; + + if (paths.size() > 2) { + double min_length = this->perimeter_flow.scaled_width() * 2; + double ok_length = this->perimeter_flow.scaled_width() * 20; + + foreach(paths, [min_length, ok_length](ExtrusionPath& prev, ExtrusionPath& curr, ExtrusionPath& next) { + if (curr.length() < min_length) { + float diff_height = std::abs(prev.height - curr.height) - std::abs(next.height - curr.height); + //have to choose the rigth path + if (diff_height < 0 || (diff_height == 0 && prev.length() > next.length())) { + //merge to previous + assert(prev.last_point() == curr.first_point()); + assert(curr.polyline.points.size() > 1); + prev.polyline.points.insert(prev.polyline.points.end(), curr.polyline.points.begin() + 1, curr.polyline.points.end()); + } else { + //merge to next + assert(curr.last_point() == next.first_point()); + assert(curr.polyline.points.size() > 1); + next.polyline.points.insert(next.polyline.points.begin(), curr.polyline.points.begin(), curr.polyline.points.end() - 1); + } + return true; + } else if(((int)curr.height) % 2 == 1 && curr.length() > ok_length){ + curr.height++; + if (prev.height == curr.height) { + prev.polyline.points.insert(prev.polyline.points.end(), curr.polyline.points.begin() + 1, curr.polyline.points.end()); + return true; + } else if (next.height == curr.height) { + next.polyline.points.insert(next.polyline.points.begin(), curr.polyline.points.begin(), curr.polyline.points.end() - 1); + return true; + } + } + return false; + }); + + foreach(paths, [](ExtrusionPath& prev, ExtrusionPath& curr, ExtrusionPath& next) { + if (curr.height == 3) { + //have to choose the rigth path + if (prev.height == 4 || (prev.height == 2 && next.height < 2)) { + //merge to previous + assert(prev.last_point() == curr.first_point()); + assert(curr.polyline.points.size() > 1); + prev.polyline.points.insert(prev.polyline.points.end(), curr.polyline.points.begin() + 1, curr.polyline.points.end()); + } else { + //merge to next + assert(curr.last_point() == next.first_point()); + assert(curr.polyline.points.size() > 1); + next.polyline.points.insert(next.polyline.points.begin(), curr.polyline.points.begin(), curr.polyline.points.end() - 1); + } + return true; + } + return false; + }); + foreach(paths, [](ExtrusionPath& prev, ExtrusionPath& curr, ExtrusionPath& next) { + if (curr.height == 1) { + //have to choose the rigth path + if (prev.height == 2 || (prev.height == 0 && next.height > 2)) { + //merge to previous + assert(prev.last_point() == curr.first_point()); + assert(curr.polyline.points.size() > 1); + prev.polyline.points.insert(prev.polyline.points.end(), curr.polyline.points.begin() + 1, curr.polyline.points.end()); + } else { + //merge to next + assert(curr.last_point() == next.first_point()); + assert(curr.polyline.points.size() > 1); + next.polyline.points.insert(next.polyline.points.begin(), curr.polyline.points.begin(), curr.polyline.points.end() - 1); + } + return true; + } + return false; + }); + } + if(paths.size() == 2){ + double min_length = this->perimeter_flow.scaled_width() * 2; + if (paths.front().length() < min_length) { + paths.back().polyline.points.insert(paths.back().polyline.points.begin(), paths.front().polyline.points.begin(), paths.front().polyline.points.end() - 1); + paths.erase(paths.begin()); + }else if (paths.back().length() < min_length) { + paths.front().polyline.points.insert(paths.front().polyline.points.end(), paths.back().polyline.points.begin() + 1, paths.back().polyline.points.end()); + paths.erase(paths.begin() + 1); + } + } + //set correct height + for (ExtrusionPath& path : paths) { + path.height = path.height < 3 ? (float)this->layer->height : this->overhang_flow.height; + } + return paths; } @@ -1196,7 +1406,7 @@ ExtrusionEntityCollection PerimeterGenerator::_traverse_loops( ExtrusionPaths paths; if ( this->config->overhangs_width_speed.value > 0 && this->layer->id() > 0 && !(this->object_config->support_material && this->object_config->support_material_contact_distance_type.value == zdNone)) { - paths = this->create_overhangs(loop.polygon, role, is_external); + paths = this->create_overhangs(loop.polygon.split_at_first_point(), role, is_external); } else { ExtrusionPath path(role); path.polyline = loop.polygon.split_at_first_point(); diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index 7d6916df3..8a8f976f9 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -99,11 +99,12 @@ private: double _ext_mm3_per_mm; double _mm3_per_mm; double _mm3_per_mm_overhang; - Polygons _lower_slices_bridge_flow; - Polygons _lower_slices_bridge_speed; + Polygons _lower_slices_bridge_flow_small; + Polygons _lower_slices_bridge_flow_big; + Polygons _lower_slices_bridge_speed_small; + Polygons _lower_slices_bridge_speed_big; - template<typename LINE> - ExtrusionPaths create_overhangs(LINE loop_polygons, ExtrusionRole role, bool is_external) const; + ExtrusionPaths create_overhangs(const Polyline& loop_polygons, ExtrusionRole role, bool is_external) const; // transform loops into ExtrusionEntityCollection, adding also thin walls into it. ExtrusionEntityCollection _traverse_loops(const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) const; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 00d48462f..be21b8b89 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -420,8 +420,8 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config) if (model.empty() || variant.empty()) return; is_visible = app_config.get_variant(vendor->id, model, variant); - } else if (type == TYPE_FILAMENT || type == TYPE_SLA_MATERIAL) { - const std::string §ion_name = (type == TYPE_FILAMENT) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; + } else if (type == TYPE_FFF_FILAMENT || type == TYPE_SLA_MATERIAL) { + const std::string §ion_name = (type == TYPE_FFF_FILAMENT) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; if (app_config.has_section(section_name)) { // Check whether this profile is marked as "installed" in PrusaSlicer.ini, // or whether a profile is marked as "installed", which this profile may have been renamed from. @@ -446,6 +446,8 @@ const std::vector<std::string>& Preset::print_options() "top_solid_min_thickness", "bottom_solid_layers", "bottom_solid_min_thickness", + "solid_over_perimeters", + "duplicate_distance", "extra_perimeters", "extra_perimeters_odd_layers", "extra_perimeters_overhangs", @@ -456,26 +458,28 @@ const std::vector<std::string>& Preset::print_options() "avoid_crossing_perimeters", "avoid_crossing_not_first_layer", "thin_perimeters", "thin_perimeters_all", - "thin_walls", "overhangs_speed", "overhangs_width", "overhangs_width_speed", "overhangs_reverse", "overhangs_reverse_threshold", - "seam_position", + "seam_position", + // external_perimeters "external_perimeters_first", "external_perimeters_vase", "external_perimeters_nothole", "external_perimeters_hole", - "fill_density" - , "fill_pattern" - , "fill_top_flow_ratio" - , "fill_smooth_width" - , "fill_smooth_distribution" - , "top_fill_pattern" - , "bottom_fill_pattern" - , "solid_fill_pattern", + // fill pattern + "fill_density", + "fill_pattern", + "fill_top_flow_ratio", + "fill_smooth_width", + "fill_smooth_distribution", + "top_fill_pattern", + "bottom_fill_pattern", + "solid_fill_pattern", "infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", + // ironing "ironing", "ironing_type", "ironing_flowrate", @@ -493,25 +497,40 @@ const std::vector<std::string>& Preset::print_options() "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", #endif /* HAS_PRESSURE_EQUALIZER */ "min_width_top_surface", + "bridge_speed", + "bridge_speed_internal", + // speeds + "external_perimeter_speed", + "first_layer_speed", + "infill_speed", "perimeter_speed", "small_perimeter_speed", - "small_perimeter_min_length", "small_perimeter_max_length", - "external_perimeter_speed", "infill_speed", "solid_infill_speed", - "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed", - "bridge_speed", - "bridge_speed_internal", + "small_perimeter_min_length", + "solid_infill_speed", + "support_material_interface_speed", + "support_material_speed", + "support_material_xy_spacing", + "top_solid_infill_speed", + "travel_speed", "travel_speed_z", + // gapfill "gap_fill", "gap_fill_min_area", "gap_fill_overlap", "gap_fill_speed", - "travel_speed", "travel_speed_z", "first_layer_speed", "perimeter_acceleration", "infill_acceleration", - "bridge_acceleration", "first_layer_acceleration", "default_acceleration", - "duplicate_distance", + // acceleration + "bridge_acceleration", + "default_acceleration", + "first_layer_acceleration", + "infill_acceleration", + "perimeter_acceleration", + "travel_acceleration", + // skirt "skirts", "skirt_distance", "skirt_height", "skirt_extrusion_width", "min_skirt_length", "draft_shield", + // brim "brim_inside_holes", "brim_width", "brim_width_interior", @@ -520,39 +539,43 @@ const std::vector<std::string>& Preset::print_options() "brim_ears_max_angle", "brim_ears_pattern", "brim_offset", - "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", - "raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", + // support + "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", + "raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", "support_material_interface_pattern", - "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", - "support_material_interface_spacing", "support_material_interface_contact_loops" - , "support_material_contact_distance_type" - , "support_material_contact_distance_top" - , "support_material_contact_distance_bottom" - , "support_material_buildplate_only", "dont_support_bridges", "notes", + "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", + "support_material_interface_spacing", "support_material_interface_contact_loops", + "support_material_contact_distance_type", + "support_material_contact_distance_top", + "support_material_contact_distance_bottom", + "support_material_buildplate_only", "dont_support_bridges", "notes", "complete_objects", "complete_objects_one_skirt", + "complete_objects_one_brim", "complete_objects_sort", "extruder_clearance_radius", "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder", "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", "ooze_prevention", "standby_temperature_delta", "interface_shells", + // width & spacing "extrusion_spacing", "extrusion_width", "first_layer_extrusion_spacing", "first_layer_extrusion_width", "perimeter_round_corners", - "perimeter_extrusion_spacing", - "perimeter_extrusion_width", - "external_perimeter_extrusion_spacing", - "external_perimeter_extrusion_width", - "infill_extrusion_spacing", - "infill_extrusion_width", - "solid_infill_extrusion_spacing", - "solid_infill_extrusion_width", - "top_infill_extrusion_spacing", - "top_infill_extrusion_width", - "support_material_extrusion_width", - "infill_overlap", "bridge_flow_ratio", + "perimeter_extrusion_spacing", + "perimeter_extrusion_width", + "external_perimeter_extrusion_spacing", + "external_perimeter_extrusion_width", + "infill_extrusion_spacing", + "infill_extrusion_width", + "solid_infill_extrusion_spacing", + "solid_infill_extrusion_width", + "top_infill_extrusion_spacing", + "top_infill_extrusion_width", + "support_material_extrusion_width", + // overlap, ratios + "infill_overlap", "bridge_flow_ratio", "infill_anchor", "infill_anchor_max", "clip_multipart_objects", @@ -560,14 +583,17 @@ const std::vector<std::string>& Preset::print_options() "bridge_overlap", "first_layer_flow_ratio", "clip_multipart_objects", "enforce_full_fill_volume", "external_infill_margin", "bridged_infill_margin", + // compensation "first_layer_size_compensation", + "first_layer_size_compensation_layers", "xy_size_compensation", "xy_inner_size_compensation", "hole_size_compensation", "hole_size_threshold", "hole_to_polyhole", "hole_to_polyhole_threshold", - "threads", "resolution", + "threads", + // wipe tower "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "wipe_tower_brim", "single_extruder_multi_material_priming", @@ -583,14 +609,19 @@ const std::vector<std::string>& Preset::print_options() "seam_travel_cost", "infill_connection", "infill_connection_solid", "infill_connection_top", "infill_connection_bottom", "first_layer_infill_speed", + // thin wall + "thin_walls", "thin_walls_min_width", "thin_walls_overlap", - "thin_walls_speed" - , "model_precision" - , "curve_smoothing_precision" - , "curve_smoothing_cutoff_dist" - , "curve_smoothing_angle_convex" - , "curve_smoothing_angle_concave", + "thin_walls_speed", + "thin_walls_merge", + //precision, spoothign + "model_precision", + "resolution", + "curve_smoothing_precision", + "curve_smoothing_cutoff_dist", + "curve_smoothing_angle_convex", + "curve_smoothing_angle_concave", "print_extrusion_multiplier", "print_retract_length", "print_temperature", @@ -599,11 +630,11 @@ const std::vector<std::string>& Preset::print_options() "external_perimeter_overlap", "perimeter_bonding", "perimeter_overlap", + //milling "milling_after_z", "milling_post_process", "milling_extra_size", "milling_speed", - "thin_walls_merge", }; return s_opts; } @@ -889,7 +920,9 @@ void PresetCollection::add_default_preset(const std::vector<std::string> &keys, // Load all presets found in dir_path. // Throws an exception on error. -void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir) +void PresetCollection::load_presets( + const std::string &dir_path, const std::string &subdir, + PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule) { // Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points, // see https://github.com/prusa3d/PrusaSlicer/issues/732 @@ -916,7 +949,9 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri // Load the preset file, apply preset values on top of defaults. try { DynamicPrintConfig config; - config.load_from_ini(preset.file); + ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule); + if (! config_substitutions.empty()) + substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions) }); // Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field. const Preset &default_preset = this->default_preset_for(config); preset.config = default_preset.config; @@ -1551,8 +1586,8 @@ void PresetCollection::update_map_system_profile_renamed() std::string PresetCollection::name() const { switch (this->type()) { - case Preset::TYPE_PRINT: return L("print"); - case Preset::TYPE_FILAMENT: return L("filament"); + case Preset::TYPE_FFF_PRINT: return L("print"); + case Preset::TYPE_FFF_FILAMENT: return L("filament"); case Preset::TYPE_SLA_PRINT: return L("SLA print"); case Preset::TYPE_SLA_MATERIAL: return L("SLA material"); case Preset::TYPE_PRINTER: return L("printer"); @@ -1563,8 +1598,8 @@ std::string PresetCollection::name() const std::string PresetCollection::section_name() const { switch (this->type()) { - case Preset::TYPE_PRINT: return "print"; - case Preset::TYPE_FILAMENT: return "filament"; + case Preset::TYPE_FFF_PRINT: return "print"; + case Preset::TYPE_FFF_FILAMENT: return "filament"; case Preset::TYPE_SLA_PRINT: return "sla_print"; case Preset::TYPE_SLA_MATERIAL: return "sla_material"; case Preset::TYPE_PRINTER: return "printer"; @@ -1800,7 +1835,9 @@ PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vector<std::str // Load all printers found in dir_path. // Throws an exception on error. -void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const std::string& subdir) +void PhysicalPrinterCollection::load_printers( + const std::string& dir_path, const std::string& subdir, + PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule) { // Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points, // see https://github.com/prusa3d/PrusaSlicer/issues/732 @@ -1826,7 +1863,9 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const // Load the preset file, apply preset values on top of defaults. try { DynamicPrintConfig config; - config.load_from_ini(printer.file); + ConfigSubstitutions config_substitutions = config.load_from_ini(printer.file, substitution_rule); + if (! config_substitutions.empty()) + substitutions.push_back({ name, Preset::Type::TYPE_PHYSICAL_PRINTER, PresetConfigSubstitutions::Source::UserFile, printer.file, std::move(config_substitutions) }); printer.update_from_config(config); printer.loaded = true; } diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 415831a5a..28b64bbbd 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -110,15 +110,24 @@ typedef std::map<std::string, VendorProfile> VendorMap; class Preset { public: - enum Type + enum Type : uint8_t { - TYPE_INVALID, - TYPE_PRINT, - TYPE_SLA_PRINT, - TYPE_FILAMENT, - TYPE_SLA_MATERIAL, - TYPE_PRINTER, - TYPE_COUNT, + TYPE_INVALID = 0, + TYPE_PRINT1 = 1 << 0, + TYPE_MATERIAL = 1 << 1, + TYPE_PRINTER = 1 << 2, + TYPE_TAB = TYPE_PRINT1 | TYPE_MATERIAL | TYPE_PRINTER, + TYPE_FFF = 1 << 3, + TYPE_FFF_PRINT = TYPE_FFF | TYPE_PRINT1, + TYPE_FFF_FILAMENT = TYPE_FFF | TYPE_MATERIAL, + TYPE_SLA = 1 << 4, + TYPE_SLA_PRINT = TYPE_SLA | TYPE_PRINT1, + TYPE_SLA_MATERIAL = TYPE_SLA | TYPE_MATERIAL, + TYPE_TECHNOLOGY = TYPE_FFF | TYPE_SLA, + + // This type is here to support PresetConfigSubstitutions for physical printers, however it does not belong to the Preset class, + // PhysicalPrinter class is used instead. + TYPE_PHYSICAL_PRINTER = 1 << 5, }; Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {} @@ -184,7 +193,7 @@ public: // Returns the "compatible_prints_condition". static std::string& compatible_prints_condition(DynamicPrintConfig &cfg) { return cfg.option<ConfigOptionString>("compatible_prints_condition", true)->value; } std::string& compatible_prints_condition() { - assert(this->type == TYPE_FILAMENT || this->type == TYPE_SLA_MATERIAL); + assert(this->type == TYPE_FFF_FILAMENT || this->type == TYPE_SLA_MATERIAL); return Preset::compatible_prints_condition(this->config); } const std::string& compatible_prints_condition() const { return const_cast<Preset*>(this)->compatible_prints_condition(); } @@ -192,7 +201,7 @@ public: // Returns the "compatible_printers_condition". static std::string& compatible_printers_condition(DynamicPrintConfig &cfg) { return cfg.option<ConfigOptionString>("compatible_printers_condition", true)->value; } std::string& compatible_printers_condition() { - assert(this->type == TYPE_PRINT || this->type == TYPE_SLA_PRINT || this->type == TYPE_FILAMENT || this->type == TYPE_SLA_MATERIAL); + assert(this->type == TYPE_FFF_PRINT || this->type == TYPE_SLA_PRINT || this->type == TYPE_FFF_FILAMENT || this->type == TYPE_SLA_MATERIAL); return Preset::compatible_printers_condition(this->config); } const std::string& compatible_printers_condition() const { return const_cast<Preset*>(this)->compatible_printers_condition(); } @@ -251,6 +260,19 @@ bool is_compatible_with_print (const PresetWithVendorProfile &preset, const Pre bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config); bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer); +inline Preset::Type operator|(Preset::Type a, Preset::Type b) { + return static_cast<Preset::Type>(static_cast<uint16_t>(a) | static_cast<uint16_t>(b)); +} +inline Preset::Type operator&(Preset::Type a, Preset::Type b) { + return static_cast<Preset::Type>(static_cast<uint16_t>(a) & static_cast<uint16_t>(b)); +} +inline Preset::Type operator|=(Preset::Type& a, Preset::Type b) { + a = a | b; return a; +} +inline Preset::Type operator&=(Preset::Type& a, Preset::Type b) { + a = a & b; return a; +} + enum class PresetSelectCompatibleType { // Never select a compatible preset if the newly selected profile is not compatible. Never, @@ -260,6 +282,27 @@ enum class PresetSelectCompatibleType { Always }; +// Substitutions having been performed during parsing a single configuration file. +struct PresetConfigSubstitutions { + // User readable preset name. + std::string preset_name; + // Type of the preset (Print / Filament / Printer ...) + Preset::Type preset_type; + enum class Source { + UserFile, + ConfigBundle, + }; + Source preset_source; + // Source of the preset. It may be empty in case of a ConfigBundle being loaded. + std::string preset_file; + // What config value has been substituted with what. + ConfigSubstitutions substitutions; +}; + +// Substitutions having been performed during parsing a set of configuration files, for example when starting up +// PrusaSlicer and reading the user Print / Filament / Printer profiles. +using PresetsConfigSubstitutions = std::vector<PresetConfigSubstitutions>; + // Collections of presets of the same type (one of the Print, Filament or Printer type). class PresetCollection { @@ -290,7 +333,7 @@ public: void add_default_preset(const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name); // Load ini files of the particular type from the provided directory path. - void load_presets(const std::string &dir_path, const std::string &subdir); + void load_presets(const std::string &dir_path, const std::string &subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule); // Load a preset from an already parsed config file, insert it into the sorted sequence of presets // and select it, losing previous modifications. @@ -663,7 +706,7 @@ public: const std::deque<PhysicalPrinter>& operator()() const { return m_printers; } // Load ini files of the particular type from the provided directory path. - void load_printers(const std::string& dir_path, const std::string& subdir); + void load_printers(const std::string& dir_path, const std::string& subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule); void load_printers_from_presets(PrinterPresetCollection &printer_presets); // Load printer from the loaded configuration void load_printer(const std::string& path, const std::string& name, DynamicPrintConfig&& config, bool select, bool save=false); diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index f709ae926..82f44251a 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -38,8 +38,8 @@ static std::vector<std::string> s_project_options { const char *PresetBundle::PRUSA_BUNDLE = "PrusaResearch"; PresetBundle::PresetBundle() : - prints(Preset::TYPE_PRINT, Preset::print_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults())), - filaments(Preset::TYPE_FILAMENT, Preset::filament_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults())), + prints(Preset::TYPE_FFF_PRINT, Preset::print_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults())), + filaments(Preset::TYPE_FFF_FILAMENT, Preset::filament_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults())), sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options(), static_cast<const SLAMaterialConfig&>(SLAFullPrintConfig::defaults())), sla_prints(Preset::TYPE_SLA_PRINT, Preset::sla_print_options(), static_cast<const SLAPrintObjectConfig&>(SLAFullPrintConfig::defaults())), printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast<const PrintRegionConfig&>(FullPrintConfig::defaults()), "- default FFF -"), @@ -162,10 +162,12 @@ void PresetBundle::setup_directories() } } -void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_model_id) +PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule, const std::string &preferred_model_id) { // First load the vendor specific system presets. - std::string errors_cummulative = this->load_system_presets(); + PresetsConfigSubstitutions substitutions; + std::string errors_cummulative; + std::tie(substitutions, errors_cummulative) = this->load_system_presets(substitution_rule); const std::string dir_user_presets = data_dir() #ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR @@ -175,33 +177,34 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_ // Store the print/filament/printer presets at the same location as the upstream Slic3r. #endif ; + try { - this->prints.load_presets(dir_user_presets, "print"); + this->prints.load_presets(dir_user_presets, "print", substitutions, substitution_rule); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } try { - this->sla_prints.load_presets(dir_user_presets, "sla_print"); + this->sla_prints.load_presets(dir_user_presets, "sla_print", substitutions, substitution_rule); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } try { - this->filaments.load_presets(dir_user_presets, "filament"); + this->filaments.load_presets(dir_user_presets, "filament", substitutions, substitution_rule); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } try { - this->sla_materials.load_presets(dir_user_presets, "sla_material"); + this->sla_materials.load_presets(dir_user_presets, "sla_material", substitutions, substitution_rule); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } try { - this->printers.load_presets(dir_user_presets, "printer"); + this->printers.load_presets(dir_user_presets, "printer", substitutions, substitution_rule); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } try { - this->physical_printers.load_printers(dir_user_presets, "physical_printer"); + this->physical_printers.load_printers(dir_user_presets, "physical_printer", substitutions, substitution_rule); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } @@ -211,16 +214,26 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_ throw Slic3r::RuntimeError(errors_cummulative); this->load_selections(config, preferred_model_id); + + return substitutions; } // Load system presets into this PresetBundle. // For each vendor, there will be a single PresetBundle loaded. -std::string PresetBundle::load_system_presets() +std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule) { + if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent) + // Loading system presets, don't log substitutions. + compatibility_rule = ForwardCompatibilitySubstitutionRule::EnableSilent; + else if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem) + // Loading system presets, throw on unknown option value. + compatibility_rule = ForwardCompatibilitySubstitutionRule::Disable; + // Here the vendor specific read only Config Bundles are stored. - boost::filesystem::path dir = (boost::filesystem::path(data_dir()) / "vendor").make_preferred(); - std::string errors_cummulative; - bool first = true; + boost::filesystem::path dir = (boost::filesystem::path(data_dir()) / "vendor").make_preferred(); + PresetsConfigSubstitutions substitutions; + std::string errors_cummulative; + bool first = true; for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) if (Slic3r::is_ini_file(dir_entry)) { std::string name = dir_entry.path().filename().string(); @@ -230,13 +243,13 @@ std::string PresetBundle::load_system_presets() // Load the config bundle, flatten it. if (first) { // Reset this PresetBundle and load the first vendor config. - this->load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM); + append(substitutions, this->load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem, compatibility_rule).first); first = false; } else { // Load the other vendor configs, merge them with this PresetBundle. // Report duplicate profiles. PresetBundle other; - other.load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM); + append(substitutions, other.load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem, compatibility_rule).first); std::vector<std::string> duplicates = this->merge_presets(std::move(other)); if (! duplicates.empty()) { errors_cummulative += "Vendor configuration file " + name + " contains the following presets with names used by other vendors: "; @@ -258,7 +271,7 @@ std::string PresetBundle::load_system_presets() } this->update_system_maps(); - return errors_cummulative; + return std::make_pair(std::move(substitutions), errors_cummulative); } // Merge one vendor's presets with the other vendor's presets, report duplicates. @@ -320,9 +333,9 @@ const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& p if (preset_type == Preset::TYPE_PRINTER || preset_type == Preset::TYPE_INVALID) return alias; - const PresetCollection& presets = preset_type == Preset::TYPE_PRINT ? prints : + const PresetCollection& presets = preset_type == Preset::TYPE_FFF_PRINT ? prints : preset_type == Preset::TYPE_SLA_PRINT ? sla_prints : - preset_type == Preset::TYPE_FILAMENT ? filaments : + preset_type == Preset::TYPE_FFF_FILAMENT ? filaments : sla_materials; return presets.get_preset_name_by_alias(alias); @@ -331,9 +344,9 @@ const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& p void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::Type type, const std::vector<std::string>& unselected_options) { - PresetCollection& presets = type == Preset::TYPE_PRINT ? prints : + PresetCollection& presets = type == Preset::TYPE_FFF_PRINT ? prints : type == Preset::TYPE_SLA_PRINT ? sla_prints : - type == Preset::TYPE_FILAMENT ? filaments : + type == Preset::TYPE_FFF_FILAMENT ? filaments : type == Preset::TYPE_SLA_MATERIAL ? sla_materials : printers; // if we want to save just some from selected options @@ -348,7 +361,7 @@ void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset:: // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. update_compatible(PresetSelectCompatibleType::Never); - if (type == Preset::TYPE_FILAMENT) { + if (type == Preset::TYPE_FFF_FILAMENT) { // synchronize the first filament presets. set_filament_preset(0, filaments.get_selected_preset_name()); } @@ -666,15 +679,15 @@ DynamicPrintConfig PresetBundle::full_sla_config() const // Instead of a config file, a G-code may be loaded containing the full set of parameters. // In the future the configuration will likely be read from an AMF file as well. // If the file is loaded successfully, its print / filament / printer profiles will be activated. -void PresetBundle::load_config_file(const std::string &path) +ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule) { if (is_gcode_file(path)) { DynamicPrintConfig config; config.apply(FullPrintConfig::defaults()); - config.load_from_gcode_file(path); + ConfigSubstitutions config_substitutions = config.load_from_gcode_file(path, compatibility_rule); Preset::normalize(config); load_config_file_config(path, true, std::move(config)); - return; + return config_substitutions; } // 1) Try to load the config file into a boost property tree. @@ -685,33 +698,41 @@ void PresetBundle::load_config_file(const std::string &path) } catch (const std::ifstream::failure &err) { throw Slic3r::RuntimeError(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what()); } catch (const boost::property_tree::file_parser_error &err) { - throw Slic3r::RuntimeError((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%") - % err.filename() % err.message() % err.line()).str()); + throw Slic3r::RuntimeError(format("Failed loading the Config Bundle \"%1%\": %2% at line %3%", + err.filename(), err.message(), err.line())); } catch (const std::runtime_error &err) { throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what()); } // 2) Continue based on the type of the configuration file. ConfigFileType config_file_type = guess_config_file_type(tree); - switch (config_file_type) { - case CONFIG_FILE_TYPE_UNKNOWN: - throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path); - case CONFIG_FILE_TYPE_APP_CONFIG: - throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file."); - case CONFIG_FILE_TYPE_CONFIG: - { - // Initialize a config from full defaults. - DynamicPrintConfig config; - config.apply(FullPrintConfig::defaults()); - config.load(tree); - Preset::normalize(config); - load_config_file_config(path, true, std::move(config)); - break; - } - case CONFIG_FILE_TYPE_CONFIG_BUNDLE: - load_config_file_config_bundle(path, tree); - break; + ConfigSubstitutions config_substitutions; + try { + switch (config_file_type) { + case CONFIG_FILE_TYPE_UNKNOWN: + throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path); + case CONFIG_FILE_TYPE_APP_CONFIG: + throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file."); + case CONFIG_FILE_TYPE_CONFIG: + { + // Initialize a config from full defaults. + DynamicPrintConfig config; + config.apply(FullPrintConfig::defaults()); + config_substitutions = config.load(tree, compatibility_rule); + Preset::normalize(config); + load_config_file_config(path, true, std::move(config)); + return config_substitutions; + } + case CONFIG_FILE_TYPE_CONFIG_BUNDLE: + return load_config_file_config_bundle(path, tree, compatibility_rule); + } + } catch (const ConfigurationError &e) { + throw Slic3r::RuntimeError(format("Invalid configuration file %1%: %2%", path, e.what())); } + + // This shall never happen. Suppres compiler warnings. + assert(false); + return ConfigSubstitutions{}; } // Load a config file from a boost property_tree. This is a private method called from load_config_file. @@ -883,65 +904,19 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool } // Load the active configuration of a config bundle from a boost property_tree. This is a private method called from load_config_file. -void PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree) +// Note: only called when using --load from cli. Will load the bundle like with the menu but wihtout saving it. +ConfigSubstitutions PresetBundle::load_config_file_config_bundle( + const std::string &path, const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule) { - // 1) Load the config bundle into a temp data. - PresetBundle tmp_bundle; - // Load the config bundle, don't save the loaded presets to user profile directory. - tmp_bundle.load_configbundle(path, 0); - std::string bundle_name = std::string(" - ") + boost::filesystem::path(path).filename().string(); - - // 2) Extract active configs from the config bundle, copy them and activate them in this bundle. - auto load_one = [this, &path, &bundle_name](PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string { - Preset *preset_src = collection_src.find_preset(preset_name_src, false); - Preset *preset_dst = collection_dst.find_preset(preset_name_src, false); - assert(preset_src != nullptr); - std::string preset_name_dst; - if (preset_dst != nullptr && preset_dst->is_default) { - // No need to copy a default preset, it always exists in collection_dst. - if (activate) - collection_dst.select_preset(0); - return preset_name_src; - } else if (preset_dst != nullptr && preset_src->config == preset_dst->config) { - // Don't save as the config exists in the current bundle and its content is the same. - return preset_name_src; - } else { - // Generate a new unique name. - preset_name_dst = preset_name_src + bundle_name; - Preset *preset_dup = nullptr; - for (size_t i = 1; (preset_dup = collection_dst.find_preset(preset_name_dst, false)) != nullptr; ++ i) { - if (preset_src->config == preset_dup->config) - // The preset has been already copied into collection_dst. - return preset_name_dst; - // Try to generate another name. - char buf[64]; - sprintf(buf, " (%d)", (int)i); - preset_name_dst = preset_name_src + buf + bundle_name; - } - } - assert(! preset_name_dst.empty()); - // Save preset_src->config into collection_dst under preset_name_dst. - // The "compatible_printers" field should not have been exported into a config.ini or a G-code anyway, - // but some of the alpha versions of Slic3r did. - ConfigOption *opt_compatible = preset_src->config.optptr("compatible_printers"); - if (opt_compatible != nullptr) { - assert(opt_compatible->type() == coStrings); - if (opt_compatible->type() == coStrings) - static_cast<ConfigOptionStrings*>(opt_compatible)->values.clear(); - } - collection_dst.load_preset(path, preset_name_dst, std::move(preset_src->config), activate).is_external = true; - return preset_name_dst; - }; - load_one(this->prints, tmp_bundle.prints, tmp_bundle.prints .get_selected_preset_name(), true); - load_one(this->sla_prints, tmp_bundle.sla_prints, tmp_bundle.sla_prints .get_selected_preset_name(), true); - load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filaments .get_selected_preset_name(), true); - load_one(this->sla_materials, tmp_bundle.sla_materials, tmp_bundle.sla_materials.get_selected_preset_name(), true); - load_one(this->printers, tmp_bundle.printers, tmp_bundle.printers .get_selected_preset_name(), true); - this->update_multi_material_filament_presets(); - for (size_t i = 1; i < std::min(tmp_bundle.filament_presets.size(), this->filament_presets.size()); ++ i) - this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false); - + // Load the config bundle, but don't save the loaded presets to user profile directory + // [PresetsConfigSubstitutions, size_t] + auto [presets_substitutions, presets_imported] = this->load_configbundle(path, { }, compatibility_rule); + ConfigSubstitutions config_substitutions; this->update_compatible(PresetSelectCompatibleType::Never); + for (PresetConfigSubstitutions &sub : presets_substitutions) + append(config_substitutions, std::move(sub.substitutions)); + sort_remove_duplicates(config_substitutions); + return std::move(config_substitutions); } // Process the Config Bundle loaded as a Boost property tree. @@ -1086,11 +1061,16 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree, co // Load a config bundle file, into presets and store the loaded presets into separate files // of the local configuration directory. -size_t PresetBundle::load_configbundle(const std::string &path, unsigned int flags) +std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle( + const std::string &path, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule) { - if (flags & (LOAD_CFGBNDLE_RESET_USER_PROFILE | LOAD_CFGBNDLE_SYSTEM)) - // Reset this bundle, delete user profile files if LOAD_CFGBNDLE_SAVE. - this->reset(flags & LOAD_CFGBNDLE_SAVE); + // Enable substitutions for user config bundle, throw an exception when loading a system profile. + ConfigSubstitutionContext substitution_context { compatibility_rule }; + PresetsConfigSubstitutions substitutions; + + if (flags.has(LoadConfigBundleAttribute::ResetUserProfile) || flags.has(LoadConfigBundleAttribute::LoadSystem)) + // Reset this bundle, delete user profile files if SaveImported. + this->reset(flags.has(LoadConfigBundleAttribute::SaveImported)); // 1) Read the complete config file into a boost::property_tree. namespace pt = boost::property_tree; @@ -1103,25 +1083,24 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla } const VendorProfile *vendor_profile = nullptr; - if (flags & (LOAD_CFGBNDLE_SYSTEM | LOAD_CFGBUNDLE_VENDOR_ONLY)) { + if (flags.has(LoadConfigBundleAttribute::LoadSystem) || flags.has(LoadConfigBundleAttribute::LoadVendorOnly)) { auto vp = VendorProfile::from_ini(tree, path); if (vp.models.size() == 0) { BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer model defined.") % path; - return 0; + return std::make_pair(PresetsConfigSubstitutions{}, 0); } else if (vp.num_variants() == 0) { BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer variant defined") % path; - return 0; + return std::make_pair(PresetsConfigSubstitutions{}, 0); } vendor_profile = &this->vendors.insert({vp.id, vp}).first->second; } - if (flags & LOAD_CFGBUNDLE_VENDOR_ONLY) { - return 0; - } + if (flags.has(LoadConfigBundleAttribute::LoadVendorOnly)) + return std::make_pair(PresetsConfigSubstitutions{}, 0); // 1.5) Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed. // If loading a user config bundle, do not flatten with the system profiles, but keep the "inherits" flag intact. - flatten_configbundle_hierarchy(tree, ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) ? this : nullptr); + flatten_configbundle_hierarchy(tree, flags.has(LoadConfigBundleAttribute::LoadSystem) ? nullptr : this); // 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files. // Parse the obsolete preset names, to be deleted when upgrading from the old configuration structure. @@ -1188,7 +1167,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla active_sla_material = kvp.second.data(); } else if (kvp.first == "printer") { active_printer = kvp.second.data(); - }else if (kvp.first == "physical_printer") { + } else if (kvp.first == "physical_printer") { active_physical_printer = kvp.second.data(); } } @@ -1225,30 +1204,36 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla DynamicPrintConfig config; std::string alias_name; std::vector<std::string> renamed_from; - auto parse_config_section = [§ion, &alias_name, &renamed_from, &path](DynamicPrintConfig &config) { - for (auto &kvp : section.second) { - if (kvp.first == "alias") - alias_name = kvp.second.data(); - else if (kvp.first == "renamed_from") { - if (! unescape_strings_cstyle(kvp.second.data(), renamed_from)) { - BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The preset \"" << - section.first << "\" contains invalid \"renamed_from\" key, which is being ignored."; - } - } - config.set_deserialize(kvp.first, kvp.second.data()); + try { + auto parse_config_section = [§ion, &alias_name, &renamed_from, &substitution_context, &path](DynamicPrintConfig &config) { + substitution_context.substitutions.clear(); + for (auto &kvp : section.second) { + if (kvp.first == "alias") + alias_name = kvp.second.data(); + else if (kvp.first == "renamed_from") { + if (! unescape_strings_cstyle(kvp.second.data(), renamed_from)) { + BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The preset \"" << + section.first << "\" contains invalid \"renamed_from\" key, which is being ignored."; + } + } + // Throws on parsing error. For system presets, no substituion is being done, but an exception is thrown. + config.set_deserialize(kvp.first, kvp.second.data(), substitution_context); + } + }; + if (presets == &this->printers) { + // Select the default config based on the printer_technology field extracted from kvp. + DynamicPrintConfig config_src; + parse_config_section(config_src); + default_config = &presets->default_preset_for(config_src).config; + config = *default_config; + config.apply(config_src); + } else { + default_config = &presets->default_preset().config; + config = *default_config; + parse_config_section(config); } - }; - if (presets == &this->printers) { - // Select the default config based on the printer_technology field extracted from kvp. - DynamicPrintConfig config_src; - parse_config_section(config_src); - default_config = &presets->default_preset_for(config_src).config; - config = *default_config; - config.apply(config_src); - } else { - default_config = &presets->default_preset().config; - config = *default_config; - parse_config_section(config); + } catch (const ConfigurationError &e) { + throw ConfigurationError(format("Invalid configuration bundle \"%1%\", section [%2%]: ", path, section.first) + e.what()); } Preset::normalize(config); // Report configuration fields, which are misplaced into a wrong group. @@ -1256,7 +1241,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla if (! incorrect_keys.empty()) BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << section.first << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; - if ((flags & LOAD_CFGBNDLE_SYSTEM) && presets == &printers) { + if (flags.has(LoadConfigBundleAttribute::LoadSystem) && presets == &printers) { // Filter out printer presets, which are not mentioned in the vendor profile. // These presets are considered not installed. auto printer_model = config.opt_string("printer_model"); @@ -1291,7 +1276,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla section.first << "\" has already been loaded from another Confing Bundle."; continue; } - } else if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) { + } else if (! flags.has(LoadConfigBundleAttribute::LoadSystem)) { // This is a user config bundle. const Preset *existing = presets->find_preset(preset_name, false); if (existing != nullptr) { @@ -1320,9 +1305,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla / presets->section_name() / file_name).make_preferred(); // Load the preset into the list of presets, save it to disk. Preset &loaded = presets->load_preset(file_path.string(), preset_name, std::move(config), false); - if (flags & LOAD_CFGBNDLE_SAVE) + if (flags.has(LoadConfigBundleAttribute::SaveImported)) loaded.save(); - if (flags & LOAD_CFGBNDLE_SYSTEM) { + if (flags.has(LoadConfigBundleAttribute::LoadSystem)) { loaded.is_system = true; loaded.vendor = vendor_profile; } @@ -1343,7 +1328,10 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla else loaded.alias = std::move(alias_name); loaded.renamed_from = std::move(renamed_from); - + if (! substitution_context.empty()) + substitutions.push_back({ + preset_name, presets->type(), PresetConfigSubstitutions::Source::ConfigBundle, + std::string(), std::move(substitution_context.substitutions) }); ++ presets_loaded; } @@ -1352,8 +1340,13 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla const DynamicPrintConfig& default_config = ph_printers->default_config(); DynamicPrintConfig config = default_config; - for (auto& kvp : section.second) - config.set_deserialize(kvp.first, kvp.second.data()); + substitution_context.substitutions.clear(); + try { + for (auto& kvp : section.second) + config.set_deserialize(kvp.first, kvp.second.data(), substitution_context); + } catch (const ConfigurationError &e) { + throw ConfigurationError(format("Invalid configuration bundle \"%1%\", section [%2%]: ", path, section.first) + e.what()); + } // Report configuration fields, which are misplaced into a wrong group. std::string incorrect_keys = Preset::remove_invalid_keys(config, default_config); @@ -1379,18 +1372,21 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla #endif / "physical_printer" / file_name).make_preferred(); // Load the preset into the list of presets, save it to disk. - ph_printers->load_printer(file_path.string(), ph_printer_name, std::move(config), false, flags & LOAD_CFGBNDLE_SAVE); - - ++ph_printers_loaded; + ph_printers->load_printer(file_path.string(), ph_printer_name, std::move(config), false, flags.has(LoadConfigBundleAttribute::SaveImported)); + if (! substitution_context.empty()) + substitutions.push_back({ + ph_printer_name, Preset::TYPE_PHYSICAL_PRINTER, PresetConfigSubstitutions::Source::ConfigBundle, + std::string(), std::move(substitution_context.substitutions) }); + ++ ph_printers_loaded; } } // 3) Activate the presets and physical printer if any exists. - if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) { + if (! flags.has(LoadConfigBundleAttribute::LoadSystem)) { if (! active_print.empty()) prints.select_preset_by_name(active_print, true); if (! active_sla_print.empty()) - sla_materials.select_preset_by_name(active_sla_print, true); + sla_prints.select_preset_by_name(active_sla_print, true); if (! active_sla_material.empty()) sla_materials.select_preset_by_name(active_sla_material, true); if (! active_printer.empty()) @@ -1406,7 +1402,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla this->update_compatible(PresetSelectCompatibleType::Never); } - return presets_loaded + ph_printers_loaded; + return std::make_pair(std::move(substitutions), presets_loaded + ph_printers_loaded); } void PresetBundle::update_multi_material_filament_presets() diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 5d7cc84ba..cd26a0968 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -3,6 +3,7 @@ #include "Preset.hpp" #include "AppConfig.hpp" +#include "enum_bitmask.hpp" #include <memory> #include <unordered_map> @@ -26,7 +27,7 @@ public: // Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets. // Load selections (current print, current filaments, current printer) from config.ini // This is done just once on application start up. - void load_presets(AppConfig &config, const std::string &preferred_model_id = ""); + PresetsConfigSubstitutions load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule rule, const std::string &preferred_model_id = ""); // Export selections (current print, current filaments, current printer) into config.ini void export_selections(AppConfig &config); @@ -82,24 +83,27 @@ public: // Instead of a config file, a G-code may be loaded containing the full set of parameters. // In the future the configuration will likely be read from an AMF file as well. // If the file is loaded successfully, its print / filament / printer profiles will be activated. - void load_config_file(const std::string &path); + ConfigSubstitutions load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule); // Load a config bundle file, into presets and store the loaded presets into separate files // of the local configuration directory. // Load settings into the provided settings instance. // Activate the presets stored in the config bundle. // Returns the number of presets loaded successfully. - enum { + enum LoadConfigBundleAttribute { // Save the profiles, which have been loaded. - LOAD_CFGBNDLE_SAVE = 1, + SaveImported, // Delete all old config profiles before loading. - LOAD_CFGBNDLE_RESET_USER_PROFILE = 2, + ResetUserProfile, // Load a system config bundle. - LOAD_CFGBNDLE_SYSTEM = 4, - LOAD_CFGBUNDLE_VENDOR_ONLY = 8, + LoadSystem, + LoadVendorOnly, }; - // Load the config bundle, store it to the user profile directory by default. - size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE); + using LoadConfigBundleAttributes = enum_bitmask<LoadConfigBundleAttribute>; + // Load the config bundle based on the flags. + // Don't do any config substitutions when loading a system profile, perform and report substitutions otherwise. + std::pair<PresetsConfigSubstitutions, size_t> load_configbundle( + const std::string &path, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule); // Export a config bundle file containing all the presets and the names of the active presets. void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false); @@ -136,7 +140,7 @@ public: static const char *PRUSA_BUNDLE; private: - std::string load_system_presets(); + std::pair<PresetsConfigSubstitutions, std::string> load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule); // Merge one vendor's presets with the other vendor's presets, report duplicates. std::vector<std::string> merge_presets(PresetBundle &&other); // Update renamed_from and alias maps of system profiles. @@ -155,12 +159,15 @@ private: // and the external config is just referenced, not stored into user profile directory. // If it is not an external config, then the config will be stored into the user profile directory. void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config); - void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree); + ConfigSubstitutions load_config_file_config_bundle( + const std::string &path, const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule); DynamicPrintConfig full_fff_config() const; DynamicPrintConfig full_sla_config() const; }; +ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute) + } // namespace Slic3r #endif /* slic3r_PresetBundle_hpp_ */ diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 900dae6e7..38883733d 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -172,6 +172,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option "toolchange_gcode", "top_fan_speed", "threads", + "travel_acceleration", "travel_speed", "travel_speed_z", "use_firmware_retraction", @@ -202,6 +203,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option || opt_key == "skirt_distance" || opt_key == "min_skirt_length" || opt_key == "complete_objects_one_skirt" + || opt_key == "complete_objects_one_brim" || opt_key == "ooze_prevention" || opt_key == "wipe_tower_x" || opt_key == "wipe_tower_y" @@ -1275,8 +1277,9 @@ static inline bool sequential_print_horizontal_clearance_valid(const Print &prin { Polygons convex_hulls_other; std::map<ObjectID, Polygon> map_model_object_to_convex_hull; + const double dist_grow = PrintConfig::min_object_distance(&print.default_region_config()) * 2; for (const PrintObject *print_object : print.objects()) { - double dist_grow = PrintConfig::min_object_distance(&print.full_print_config()) * 2 ;// &print_object->config()); + const double object_grow = print.config().complete_objects_one_brim ? dist_grow : std::max(dist_grow, print_object->config().brim_width.value); assert(! print_object->model_object()->instances.empty()); assert(! print_object->instances().empty()); ObjectID model_object_id = print_object->model_object()->id(); @@ -1294,7 +1297,7 @@ static inline bool sequential_print_horizontal_clearance_valid(const Print &prin Geometry::assemble_transform(Vec3d::Zero(), model_instance0->get_rotation(), model_instance0->get_scaling_factor(), model_instance0->get_mirror())), // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. - float(scale_(0.5 * dist_grow - EPSILON)), + float(scale_(0.5 * object_grow - EPSILON)), jtRound, float(scale_(0.1))).front()); } // Make a copy, so it may be rotated for instances. @@ -1515,20 +1518,6 @@ std::pair<PrintBase::PrintValidationError, std::string> Print::validate() const return L("One or more object were assigned an extruder that the printer does not have."); #endif - auto validate_extrusion_width = [min_nozzle_diameter, max_nozzle_diameter](const ConfigBase &config, const char *opt_key, double layer_height, std::string &err_msg) -> bool { - double extrusion_width_min = config.get_abs_value(opt_key, min_nozzle_diameter); - double extrusion_width_max = config.get_abs_value(opt_key, max_nozzle_diameter); - if (extrusion_width_min == 0) { - // Default "auto-generated" extrusion width is always valid. - } else if (extrusion_width_min <= layer_height) { - err_msg = (boost::format(L("%1%=%2% mm is too low to be printable at a layer height %3% mm")) % opt_key % extrusion_width_min % layer_height).str(); - return false; - } else if (extrusion_width_max >= max_nozzle_diameter * 4.) { - err_msg = (boost::format(L("Excessive %1%=%2% mm to be printable with a nozzle diameter %3% mm")) % opt_key % extrusion_width_max % max_nozzle_diameter).str(); - return false; - } - return true; - }; for (PrintObject *object : m_objects) { if (object->config().raft_layers > 0 || object->config().support_material.value) { if ((object->config().support_material_extruder == 0 || object->config().support_material_interface_extruder == 0) && max_nozzle_diameter - min_nozzle_diameter > EPSILON) { @@ -1552,40 +1541,62 @@ std::pair<PrintBase::PrintValidationError, std::string> Print::validate() const } } } - - // validate first_layer_height - double first_layer_height = object->config().get_abs_value("first_layer_height", this->m_config.nozzle_diameter.get_at(0)); - double first_layer_min_nozzle_diameter; - if (object->config().raft_layers > 0) { - // if we have raft layers, only support material extruder is used on first layer - size_t first_layer_extruder = object->config().raft_layers == 1 - ? object->config().support_material_interface_extruder-1 - : object->config().support_material_extruder-1; - first_layer_min_nozzle_diameter = (first_layer_extruder == size_t(-1)) ? - min_nozzle_diameter : - m_config.nozzle_diameter.get_at(first_layer_extruder); - } else { - // if we don't have raft layers, any nozzle diameter is potentially used in first layer - first_layer_min_nozzle_diameter = min_nozzle_diameter; + + // validate layer_height for each region + for (size_t region_id = 0; region_id < object->region_volumes.size(); ++region_id) { + if (object->region_volumes[region_id].empty()) continue; + const PrintRegion* region = this->regions()[region_id]; + std::vector<uint16_t> object_extruders; + PrintRegion::collect_object_printing_extruders(config(), object->config(), region->config(), object_extruders); + //object->region_volumes[region_id].front().first.second < object->layers() + double layer_height = object->config().layer_height.value; + for (uint16_t extruder_id : object_extruders) { + double min_layer_height = config().min_layer_height.values[extruder_id]; + double max_layer_height = config().max_layer_height.values[extruder_id]; + double nozzle_diameter = config().nozzle_diameter.values[extruder_id]; + double first_layer_height = object->config().first_layer_height.get_abs_value(nozzle_diameter); + if (max_layer_height < EPSILON) max_layer_height = nozzle_diameter * 0.75; + + //check first layer + if (object->region_volumes[region_id].front().first.first < first_layer_height) { + if (first_layer_height + EPSILON < min_layer_height) + return { PrintBase::PrintValidationError::pveWrongSettings, (boost::format(L("First layer height can't be greater than %s")) % "min layer height").str() }; + for (auto tuple : std::vector<std::pair<double, const char*>>{ + {nozzle_diameter, "nozzle diameter"}, + {max_layer_height, "max layer height"}, + {skirt_flow(extruder_id).width, "skirt extrusion width"}, + {region->width(FlowRole::frSupportMaterial, true, *object), "support material extrusion width"}, + {region->width(FlowRole::frPerimeter, true, *object), "perimeter extrusion width"}, + {region->width(FlowRole::frExternalPerimeter, true, *object), "perimeter extrusion width"}, + {region->width(FlowRole::frInfill, true, *object), "infill extrusion width"}, + {region->width(FlowRole::frSolidInfill, true, *object), "solid infill extrusion width"}, + {region->width(FlowRole::frTopSolidInfill, true, *object), "top solid infill extrusion width"}, + }) + if (first_layer_height > tuple.first + EPSILON) + return { PrintBase::PrintValidationError::pveWrongSettings, (boost::format(L("First layer height can't be greater than %s")) % tuple.second).str() }; + + } + //check not-first layer + if (object->region_volumes[region_id].front().first.second > layer_height) { + if (layer_height + EPSILON < min_layer_height) + return { PrintBase::PrintValidationError::pveWrongSettings, (boost::format(L("First layer height can't be greater than %s")) % "min layer height").str() }; + for (auto tuple : std::vector<std::pair<double, const char*>>{ + {nozzle_diameter, "nozzle diameter"}, + {max_layer_height, "max layer height"}, + {skirt_flow(extruder_id).width, "skirt extrusion width"}, + {region->width(FlowRole::frSupportMaterial, false, *object), "support material extrusion width"}, + {region->width(FlowRole::frPerimeter, false, *object), "perimeter extrusion width"}, + {region->width(FlowRole::frExternalPerimeter, false, *object), "perimeter extrusion width"}, + {region->width(FlowRole::frInfill, false, *object), "infill extrusion width"}, + {region->width(FlowRole::frSolidInfill, false, *object), "solid infill extrusion width"}, + {region->width(FlowRole::frTopSolidInfill, false, *object), "top solid infill extrusion width"}, + }) + if (layer_height > tuple.first + EPSILON) + return { PrintBase::PrintValidationError::pveWrongSettings, (boost::format(L("Layer height can't be greater than %s")) % tuple.second).str() }; + } + } } - if (first_layer_height > first_layer_min_nozzle_diameter) - return { PrintBase::PrintValidationError::pveWrongSettings,L("First layer height can't be greater than nozzle diameter") }; - - // validate layer_height - double layer_height = object->config().layer_height.value; - if (layer_height > min_nozzle_diameter) - return { PrintBase::PrintValidationError::pveWrongSettings,L("Layer height can't be greater than nozzle diameter") }; - - // Validate extrusion widths. - std::string err_msg; - if (! validate_extrusion_width(object->config(), "extrusion_width", layer_height, err_msg)) - return { PrintBase::PrintValidationError::pveWrongSettings,err_msg }; - if ((object->config().support_material || object->config().raft_layers > 0) && ! validate_extrusion_width(object->config(), "support_material_extrusion_width", layer_height, err_msg)) - return { PrintBase::PrintValidationError::pveWrongSettings,err_msg }; - for (const char *opt_key : { "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "top_infill_extrusion_width" }) - for (size_t i = 0; i < object->region_volumes.size(); ++ i) - if (! object->region_volumes[i].empty() && ! validate_extrusion_width(this->get_region(i)->config(), opt_key, layer_height, err_msg)) - return { PrintBase::PrintValidationError::pveWrongSettings, err_msg }; + } } @@ -1803,7 +1814,7 @@ void Print::process() const PrintObjectConfig &brim_config = obj_group.front()->config(); if (brim_config.brim_width > 0 || brim_config.brim_width_interior > 0) { this->set_status(88, L("Generating brim")); - if (config().complete_objects) { + if (config().complete_objects && !config().complete_objects_one_brim) { for (PrintObject *obj : obj_group) { //get flow std::vector<uint16_t> set_extruders = this->object_extruders({ obj }); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 840b87f8f..ba79a4db5 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -65,8 +65,9 @@ public: const Print* print() const { return m_print; } const PrintRegionConfig& config() const { return m_config; } // 1-based extruder identifier for this region and role. - uint16_t extruder(FlowRole role) const; - Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const; + uint16_t extruder(FlowRole role, const PrintObject& object) const; + Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject& object) const; + float width(FlowRole role, bool first_layer, const PrintObject& object) const; // Average diameter of nozzles participating on extruding this region. coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const; // Average diameter of nozzles participating on extruding this region. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 7716b881e..7fc8f40b5 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -74,7 +74,7 @@ void PrintConfigDef::init_common_params() def = this->add("thumbnails", coPoints); def->label = L("Thumbnails size"); - def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 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: \"XxY, XxY, ...\""); def->mode = comExpert; def->min = 0; def->max = 2048; @@ -310,6 +310,7 @@ void PrintConfigDef::init_fff_params() "\nSet zero to disable acceleration control for bridges." "\nNote that it won't be applied to overhangs, they still use the perimeter acceleration."); def->sidetext = L("mm/s² or %"); + def->ratio_over = "default_acceleration"; def->min = 0; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(0,false)); @@ -574,6 +575,14 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("complete_objects_one_brim", coBool); + def->label = L("Print all brim at startup"); + def->category = OptionCategory::output; + def->tooltip = L("When using 'Complete individual objects', the default behavior is to draw the brim at the beginning of each object." + " if you prefer to have more place for you objects, you can print all the brims at the beginning, so ther is less problem with collision."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("complete_objects_sort", coEnum); def->label = L("Object sort"); def->category = OptionCategory::output; @@ -627,6 +636,7 @@ void PrintConfigDef::init_fff_params() "\nYou can set it as a % of the max of the X/Y machine acceleration limit." "\nSet zero to prevent resetting acceleration at all."); def->sidetext = L("mm/s² or %"); + def->ratio_over = "machine_max_acceleration_X"; def->min = 0; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(0,false)); @@ -1695,6 +1705,19 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0)); + def = this->add("first_layer_size_compensation_layers", coInt); + def->label = L("height in layers"); + def->full_label = L("XY First layer compensation height in layers"); + def->category = OptionCategory::slicing; + def->tooltip = L("The number of layers on which the elephant foot compensation will be active. " + "The first layer will be shrunk by the elephant foot compensation value, then " + "the next layers will be gradually shrunk less, up to the layer indicated by this value."); + def->sidetext = L("layers"); + def->min = 1; + def->max = 30; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(1)); + def = this->add("fill_smooth_width", coFloatOrPercent); def->label = L("Width"); def->full_label = L("Ironing width"); @@ -1730,6 +1753,7 @@ void PrintConfigDef::init_fff_params() "\nCan be a % of the default acceleration" "\nSet zero to disable acceleration control for first layer."); def->sidetext = L("mm/s² or %"); + def->ratio_over = "default_acceleration"; def->min = 0; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); @@ -1880,7 +1904,8 @@ void PrintConfigDef::init_fff_params() def->full_label = L("Gap fill speed"); def->category = OptionCategory::speed; def->tooltip = L("Speed for filling small gaps using short zigzag moves. Keep this reasonably low " - "to avoid too much shaking and resonance issues."); + "to avoid too much shaking and resonance issues." + "\nGap fill extrusions are ignored from the automatic volumetric speed computation, unless you set it to 0."); def->sidetext = L("mm/s"); def->min = 0; def->mode = comAdvanced; @@ -1972,6 +1997,7 @@ void PrintConfigDef::init_fff_params() "\nCan be a % of the default acceleration" "\nSet zero to disable acceleration control for infill."); def->sidetext = L("mm/s² or %"); + def->ratio_over = "default_acceleration"; def->min = 0; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(0,false)); @@ -2286,14 +2312,17 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(0.1)); - def = this->add("ironing_speed", coFloat); + def = this->add("ironing_speed", coFloatOrPercent); def->label = L("Ironing"); def->category = OptionCategory::ironing; - def->tooltip = L("Ironing"); + def->tooltip = L("Ironing speed. Used for the ironing pass of the ironing infill pattern, and the post-process infill." + " Can be defined as mm.s, or a % of the top solid infill speed." + "\nIroning extrusions are ignored from the automatic volumetric speed computation."); def->sidetext = L("mm/s"); - def->min = 0; + def->ratio_over = "top_solid_infill_speed"; + def->min = 0.1; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(15)); + def->set_default_value(new ConfigOptionFloatOrPercent(15, false)); def = this->add("layer_gcode", coString); def->label = L("After layer change G-code"); @@ -2693,12 +2722,14 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Slic3r can upload G-code files to a printer host. This field must contain " "the kind of the host."); def->enum_keys_map = &ConfigOptionEnum<PrintHostType>::get_enum_values(); + def->enum_values.push_back("prusalink"); def->enum_values.push_back("octoprint"); def->enum_values.push_back("duet"); def->enum_values.push_back("flashair"); def->enum_values.push_back("astrobox"); def->enum_values.push_back("repetier"); def->enum_values.push_back("klipper"); + def->enum_labels.push_back("PrusaLink"); def->enum_labels.push_back("OctoPrint"); def->enum_labels.push_back("Duet"); def->enum_labels.push_back("FlashAir"); @@ -2863,6 +2894,8 @@ void PrintConfigDef::init_fff_params() "\nCan be a % of the default acceleration" "\nSet zero to disable acceleration control for perimeters."); def->sidetext = L("mm/s² or %"); + def->ratio_over = "default_acceleration"; + def->min = 0; def->mode = comExpert; def->set_default_value(new ConfigOptionFloatOrPercent(0,false)); @@ -3607,6 +3640,20 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("solid_over_perimeters", coInt); + def->label = L("Max perimeters layer for solid infill"); + def->category = OptionCategory::perimeter; + def->tooltip = L("When you have a medium/hight number of top/bottom solid layers, and a low/medium of perimeters," + " then it have to put some solid infill inside the part to have enough solid layers." + "\nBy setting this to somethign higher than 0, you can remove this 'inside filling'." + " This number allow to keep some if there is a low number of perimeter over the void." + "\nIf this setting is equal or higher than the top/bottom solid layer count, it won't evict anything." + "\nIf this setting is set to 1, it will evict all solid fill are are only over perimeters." + "\nSet it to 0 to disable."); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(2)); + def = this->add("support_material", coBool); def->label = L("Generate support material"); def->category = OptionCategory::support; @@ -4070,6 +4117,18 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("travel_acceleration", coFloatOrPercent); + def->label = L("Travel"); + def->full_label = L("Travel acceleration"); + def->category = OptionCategory::speed; + def->tooltip = L("Acceleration for travel moves (jumps between distant extrusion points)." + "\nNote that the deceleration of a travel will use the acceleration value of the extrusion that will be printed after it (if any)"); + def->sidetext = L("mm/s² or %"); + def->ratio_over = "default_acceleration"; + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloatOrPercent(1500, false)); + def = this->add("travel_speed", coFloat); def->label = L("Travel"); def->full_label = L("Travel speed"); @@ -5241,7 +5300,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va } else if (opt_key == "bed_size" && !value.empty()) { opt_key = "bed_shape"; ConfigOptionPoint p; - p.deserialize(value); + p.deserialize(value, ForwardCompatibilitySubstitutionRule::Disable); std::ostringstream oss; oss << "0x0," << p.value(0) << "x0," << p.value(0) << "x" << p.value(1) << ",0x" << p.value(1); value = oss.str(); @@ -5388,6 +5447,7 @@ void PrintConfigDef::to_prusa(t_config_option_key& opt_key, std::string& value, "brim_offset", "chamber_temperature", "complete_objects_one_skirt", +"complete_objects_one_brim", "complete_objects_sort", "solid_fill_pattern", "enforce_full_fill_volume", @@ -5516,6 +5576,8 @@ void PrintConfigDef::to_prusa(t_config_option_key& opt_key, std::string& value, "start_gcode_manual", "perimeter_round_corners", "travel_speed_z", +"first_layer_size_compensation_layers", +"travel_acceleration", }; //looks if it's to be removed, or have to be transformed if (to_remove_keys.find(opt_key) != to_remove_keys.end()) { @@ -5542,9 +5604,12 @@ void PrintConfigDef::to_prusa(t_config_option_key& opt_key, std::string& value, } } else if ("elephant_foot_min_width" == opt_key) { opt_key = "elefant_foot_min_width"; - } else if("first_layer_acceleration" == opt_key || "infill_acceleration" == opt_key || "bridge_acceleration" == opt_key || "default_acceleration" == opt_key || "overhangs_speed" == opt_key || "perimeter_acceleration" == opt_key){ - if (value.find("%") != std::string::npos) - value = "0"; + } else if("first_layer_acceleration" == opt_key || "infill_acceleration" == opt_key || "bridge_acceleration" == opt_key || "default_acceleration" == opt_key || "perimeter_acceleration" == opt_key + || "overhangs_speed" == opt_key || "ironing_speed" == opt_key){ + // remove '%' + if (value.find("%") != std::string::npos) { + value = std::to_string(all_conf.get_abs_value(opt_key)); + } } else if ("gap_fill_speed" == opt_key && all_conf.has("gap_fill") && !all_conf.option<ConfigOptionBool>("gap_fill")->value) { value = "0"; } else if ("bridge_flow_ratio" == opt_key && all_conf.has("bridge_flow_ratio")) { @@ -5664,7 +5729,6 @@ double PrintConfig::min_object_distance(const ConfigBase *config, double ref_hei //std::cout << "START min_object_distance =>" << base_dist << "\n"; const ConfigOptionBool* co_opt = config->option<ConfigOptionBool>("complete_objects"); if (co_opt && co_opt->value) { - double brim_dist = 0; double skirt_dist = 0; try { std::vector<double> vals = dynamic_cast<const ConfigOptionFloats*>(config->option("nozzle_diameter"))->values; @@ -5679,18 +5743,7 @@ double PrintConfig::min_object_distance(const ConfigBase *config, double ref_hei base_dist = extruder_clearance_radius; } - //add brim width - // note: now brim can be per-object, so you have to get a different min_object_distance per object - // You should increase/reduce the size of the polygons that have a model-wide setting. const double first_layer_height = config->get_abs_value("first_layer_height"); - if (ref_height <= first_layer_height && ref_height != 0) { - if (config->option("brim_width")->getFloat() > 0) { - brim_dist += config->option("brim_width")->getFloat(); - } - } - else //if (config->option("brim_width")->getFloat() + 1 > base_dist) { - base_dist += config->option("brim_width")->getFloat(); - //} //add the skirt if (config->option("skirts")->getInt() > 0 && config->option("skirt_height")->getInt() >= 1 && !config->option("complete_objects_one_skirt")->getBool()) { if (ref_height == 0) { @@ -5723,7 +5776,7 @@ double PrintConfig::min_object_distance(const ConfigBase *config, double ref_hei catch (const std::exception & ex) { boost::nowide::cerr << ex.what() << std::endl; } - return base_dist + std::max(skirt_dist, brim_dist); + return base_dist + skirt_dist; } return base_dist; } @@ -5892,7 +5945,13 @@ bool DynamicPrintConfig::value_changed(const t_config_option_key& opt_key, const double max_nozzle_diameter = 0; for (double dmr : nozzle_diameter_option->values) max_nozzle_diameter = std::max(max_nozzle_diameter, dmr); - Flow flow = Flow::new_from_spacing(spacing_option->get_abs_value(max_nozzle_diameter), max_nozzle_diameter, layer_height_option->value, false); + double spacing_value = spacing_option->get_abs_value(max_nozzle_diameter); + Flow flow = Flow::new_from_spacing(spacing_value, max_nozzle_diameter,layer_height_option->value, false); + //test for valid height. If too high, revert to round shape + if (flow.height > spacing_value / (1 - (1. - 0.25 * PI) * flow.spacing_ratio)) { + flow.width = spacing_value / (1 - (1. - 0.25 * PI) * flow.spacing_ratio); + flow.height = flow.width; + } if (opt_key == "extrusion_spacing") { ConfigOptionFloatOrPercent* width_option = this->option<ConfigOptionFloatOrPercent>("extrusion_width"); if (width_option) { @@ -6002,6 +6061,7 @@ bool DynamicPrintConfig::value_changed(const t_config_option_key& opt_key, const width_option->set_phony(false); spacing_option->set_phony(true); Flow flow = Flow::new_from_config_width(FlowRole::frPerimeter, *width_option, max_nozzle_diameter, layer_height_option->value, 0); + if (flow.width < flow.height) flow.height = flow.width; spacing_option->value = (width_option->percent) ? std::round(100 * flow.spacing() / max_nozzle_diameter) : (std::round(flow.spacing() * 10000) / 10000); spacing_option->percent = width_option->percent; something_changed = true; @@ -6013,6 +6073,7 @@ bool DynamicPrintConfig::value_changed(const t_config_option_key& opt_key, const width_option->set_phony(false); spacing_option->set_phony(true); Flow flow = Flow::new_from_config_width(FlowRole::frPerimeter, *width_option, max_nozzle_diameter, layer_height_option->value, 0); + if (flow.width < flow.height) flow.height = flow.width; spacing_option->value = (width_option->percent) ? std::round(100 * flow.spacing() / max_nozzle_diameter) : (std::round(flow.spacing() * 10000) / 10000); spacing_option->percent = width_option->percent; something_changed = true; @@ -6025,6 +6086,7 @@ bool DynamicPrintConfig::value_changed(const t_config_option_key& opt_key, const width_option->set_phony(false); spacing_option->set_phony(true); Flow flow = Flow::new_from_config_width(FlowRole::frExternalPerimeter, *width_option, max_nozzle_diameter, layer_height_option->value, 0); + if (flow.width < flow.height) flow.height = flow.width; flow.spacing_ratio = perimeter_overlap_option->get_abs_value(1); spacing_option->value = (width_option->percent) ? std::round(100 * flow.spacing() / max_nozzle_diameter) : (std::round(flow.spacing() * 10000) / 10000); spacing_option->percent = width_option->percent; @@ -6038,6 +6100,7 @@ bool DynamicPrintConfig::value_changed(const t_config_option_key& opt_key, const width_option->set_phony(false); spacing_option->set_phony(true); Flow ext_perimeter_flow = Flow::new_from_config_width(FlowRole::frPerimeter, *width_option, max_nozzle_diameter, layer_height_option->value, 0); + if (ext_perimeter_flow.width < ext_perimeter_flow.height) ext_perimeter_flow.height = ext_perimeter_flow.width; ext_perimeter_flow.spacing_ratio = external_perimeter_overlap_option->get_abs_value(0.5); spacing_option->value = (width_option->percent) ? std::round(100 * ext_perimeter_flow.spacing() / max_nozzle_diameter) : (std::round(ext_perimeter_flow.spacing() * 10000) / 10000); spacing_option->percent = width_option->percent; @@ -6050,6 +6113,7 @@ bool DynamicPrintConfig::value_changed(const t_config_option_key& opt_key, const width_option->set_phony(false); spacing_option->set_phony(true); Flow flow = Flow::new_from_config_width(FlowRole::frInfill, *width_option, max_nozzle_diameter, layer_height_option->value, 0); + if (flow.width < flow.height) flow.height = flow.width; spacing_option->value = (width_option->percent) ? std::round(100 * flow.spacing() / max_nozzle_diameter) : (std::round(flow.spacing() * 10000) / 10000); spacing_option->percent = width_option->percent; something_changed = true; @@ -6061,6 +6125,7 @@ bool DynamicPrintConfig::value_changed(const t_config_option_key& opt_key, const width_option->set_phony(false); spacing_option->set_phony(true); Flow flow = Flow::new_from_config_width(FlowRole::frSolidInfill, *width_option, max_nozzle_diameter, layer_height_option->value, 0); + if (flow.width < flow.height) flow.height = flow.width; spacing_option->value = (width_option->percent) ? std::round(100 * flow.spacing() / max_nozzle_diameter) : (std::round(flow.spacing() * 10000) / 10000); spacing_option->percent = width_option->percent; something_changed = true; @@ -6072,6 +6137,7 @@ bool DynamicPrintConfig::value_changed(const t_config_option_key& opt_key, const width_option->set_phony(false); spacing_option->set_phony(true); Flow flow = Flow::new_from_config_width(FlowRole::frTopSolidInfill, *width_option, max_nozzle_diameter, layer_height_option->value, 0); + if (flow.width < flow.height) flow.height = flow.width; spacing_option->value = (width_option->percent) ? std::round(100 * flow.spacing() / max_nozzle_diameter) : (std::round(flow.spacing() * 10000) / 10000); spacing_option->percent = width_option->percent; something_changed = true; @@ -6495,6 +6561,20 @@ CLIMiscConfigDef::CLIMiscConfigDef() def->label = L("Ignore non-existent config files"); def->tooltip = L("Do not fail if a file supplied to --load does not exist."); + def = this->add("config_compatibility", coEnum); + def->label = L("Forward-compatibility rule when loading configurations from config files and project files (3MF, AMF)."); + def->tooltip = L("This version of Slic3r may not understand configurations produced by newest Slic3r versions. " + "For example, newer Slic3r may extend the list of supported firmware flavors. One may decide to " + "bail out or to substitute an unknown value with a default silently or verbosely."); + def->enum_keys_map = &ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>::get_enum_values(); + def->enum_values.push_back("disable"); + def->enum_values.push_back("enable"); + def->enum_values.push_back("enable_silent"); + def->enum_labels.push_back("Bail out on unknown configuration values"); + def->enum_labels.push_back("Enable reading unknown configuration values by verbosely substituting them with defaults."); + def->enum_labels.push_back("Enable reading unknown configuration values by silently substituting them with defaults."); + def->set_default_value(new ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>(ForwardCompatibilitySubstitutionRule::Enable)); + def = this->add("load", coStrings); def->label = L("Load config file"); def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files."); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 0bccab020..d6b165be9 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -72,6 +72,7 @@ enum class MachineLimitsUsage : uint8_t { }; enum PrintHostType { + htPrusaLink, htOctoPrint, htDuet, htFlashAir, @@ -222,6 +223,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<MachineLimitsUsag template<> inline const t_config_enum_values& ConfigOptionEnum<PrintHostType>::get_enum_values() { static t_config_enum_values keys_map = { + {"prusalink", htPrusaLink}, {"octoprint", htOctoPrint}, {"duet", htDuet}, {"flashair", htFlashAir}, @@ -369,6 +371,15 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<ZLiftTop>::get_en return keys_map; } +template<> inline const t_config_enum_values& ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>::get_enum_values() { + static const t_config_enum_values keys_map = { + { "disable", ForwardCompatibilitySubstitutionRule::Disable }, + { "enable", ForwardCompatibilitySubstitutionRule::Enable }, + { "enable_silent", ForwardCompatibilitySubstitutionRule::EnableSilent } + }; + + return keys_map; +} // Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs. // Does not store the actual values, but defines default values. class PrintConfigDef : public ConfigDef @@ -622,6 +633,7 @@ public: ConfigOptionFloatOrPercent first_layer_height; ConfigOptionFloatOrPercent first_layer_extrusion_width; ConfigOptionFloat first_layer_size_compensation; + ConfigOptionInt first_layer_size_compensation_layers; ConfigOptionFloat hole_size_compensation; ConfigOptionFloat hole_size_threshold; ConfigOptionBool infill_only_where_needed; @@ -692,6 +704,7 @@ protected: OPT_PTR(first_layer_height); OPT_PTR(first_layer_extrusion_width); OPT_PTR(first_layer_size_compensation); + OPT_PTR(first_layer_size_compensation_layers); OPT_PTR(infill_only_where_needed); OPT_PTR(interface_shells); OPT_PTR(layer_height); @@ -802,7 +815,7 @@ public: ConfigOptionEnum<IroningType> ironing_type; ConfigOptionPercent ironing_flowrate; ConfigOptionFloat ironing_spacing; - ConfigOptionFloat ironing_speed; + ConfigOptionFloatOrPercent ironing_speed; // milling options ConfigOptionFloatOrPercent milling_after_z; ConfigOptionFloatOrPercent milling_extra_size; @@ -837,6 +850,7 @@ public: ConfigOptionFloatOrPercent solid_infill_extrusion_width; ConfigOptionInt solid_infill_every_layers; ConfigOptionFloatOrPercent solid_infill_speed; + ConfigOptionInt solid_over_perimeters; ConfigOptionInt print_temperature; ConfigOptionBool thin_perimeters; ConfigOptionBool thin_perimeters_all; @@ -947,6 +961,7 @@ protected: OPT_PTR(solid_infill_extrusion_width); OPT_PTR(solid_infill_every_layers); OPT_PTR(solid_infill_speed); + OPT_PTR(solid_over_perimeters); OPT_PTR(print_temperature); OPT_PTR(thin_perimeters); OPT_PTR(thin_perimeters_all); @@ -1262,6 +1277,7 @@ public: ConfigOptionInts chamber_temperature; ConfigOptionBool complete_objects; ConfigOptionBool complete_objects_one_skirt; + ConfigOptionBool complete_objects_one_brim; ConfigOptionEnum<CompleteObjectSort> complete_objects_sort; ConfigOptionFloats colorprint_heights; ConfigOptionBools cooling; @@ -1327,6 +1343,7 @@ public: ConfigOptionBool thumbnails_with_bed; ConfigOptionPercent time_estimation_compensation; ConfigOptionInts top_fan_speed; + ConfigOptionFloatOrPercent travel_acceleration; ConfigOptionBools wipe; ConfigOptionBool wipe_tower; ConfigOptionFloatOrPercent wipe_tower_brim; @@ -1357,6 +1374,7 @@ protected: OPT_PTR(chamber_temperature); OPT_PTR(complete_objects); OPT_PTR(complete_objects_one_skirt); + OPT_PTR(complete_objects_one_brim); OPT_PTR(complete_objects_sort); OPT_PTR(colorprint_heights); OPT_PTR(cooling); @@ -1422,6 +1440,7 @@ protected: OPT_PTR(thumbnails_with_bed); OPT_PTR(time_estimation_compensation); OPT_PTR(top_fan_speed); + OPT_PTR(travel_acceleration); OPT_PTR(wipe); OPT_PTR(wipe_tower); OPT_PTR(wipe_tower_brim); @@ -1894,8 +1913,8 @@ public: bool set_key_value(const std::string &opt_key, ConfigOption *opt) { bool out = m_data.set_key_value(opt_key, opt); this->touch(); return out; } template<typename T> void set(const std::string &opt_key, T value) { m_data.set(opt_key, value, true); this->touch(); } - void set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false) - { m_data.set_deserialize(opt_key, str, append); this->touch(); } + void set_deserialize(const t_config_option_key &opt_key, const std::string &str, ConfigSubstitutionContext &substitution_context, bool append = false) + { m_data.set_deserialize(opt_key, str, substitution_context, append); this->touch(); } bool erase(const t_config_option_key &opt_key) { bool out = m_data.erase(opt_key); if (out) this->touch(); return out; } // Getters are thread safe. diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 100366ba1..02c3d5057 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -710,6 +710,7 @@ namespace Slic3r { || opt_key == "slice_closing_radius" || opt_key == "clip_multipart_objects" || opt_key == "first_layer_size_compensation" + || opt_key == "first_layer_size_compensation_layers" || opt_key == "elephant_foot_min_width" || opt_key == "dont_support_bridges" || opt_key == "support_material_contact_distance_type" @@ -777,6 +778,7 @@ namespace Slic3r { || opt_key == "solid_infill_below_area" || opt_key == "solid_infill_extruder" || opt_key == "solid_infill_every_layers" + || opt_key == "solid_over_perimeters" || opt_key == "top_solid_layers" || opt_key == "top_solid_min_thickness") { steps.emplace_back(posPrepareInfill); @@ -1478,7 +1480,11 @@ namespace Slic3r { { // Collected polygons, offsetted Polygons top_surfaces; + Polygons top_fill_surfaces; + Polygons top_perimeter_surfaces; Polygons bottom_surfaces; + Polygons bottom_fill_surfaces; + Polygons bottom_perimeter_surfaces; Polygons holes; }; bool spiral_vase = this->print()->config().spiral_vase.value; @@ -1537,10 +1543,14 @@ namespace Slic3r { // Top surfaces. append(cache.top_surfaces, offset(to_expolygons(layerm.slices().filter_by_type(stPosTop | stDensSolid)), min_perimeter_infill_spacing)); append(cache.top_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_type(stPosTop | stDensSolid)), min_perimeter_infill_spacing)); + append(cache.top_fill_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_type(stPosTop | stDensSolid)), min_perimeter_infill_spacing)); + append(cache.top_perimeter_surfaces, to_polygons(layerm.slices().filter_by_type(stPosTop | stDensSolid))); // Bottom surfaces. const SurfaceType surfaces_bottom[2] = { stPosBottom | stDensSolid, stPosBottom | stDensSolid | stModBridge }; append(cache.bottom_surfaces, offset(to_expolygons(layerm.slices().filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing)); append(cache.bottom_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing)); + append(cache.bottom_fill_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing)); + append(cache.bottom_perimeter_surfaces, to_polygons(layerm.slices().filter_by_type(stPosTop | stDensSolid))); // Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only. // First find the maxium number of perimeters per region slice. unsigned int perimeters = 0; @@ -1595,13 +1605,16 @@ namespace Slic3r { //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(num_layers / 16, size_t(1)); + //solid_over_perimeters value, to remove solid fill where there's only perimeters on multiple layers + int nb_perimeter_layers_for_solid_fill = region.config().solid_over_perimeters.value; + if (!top_bottom_surfaces_all_regions) { // This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness // is calculated over a single material. BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - start : cache top / bottom"; tbb::parallel_for( tbb::blocked_range<size_t>(0, num_layers, grain_size), - [this, idx_region, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) { + [this, idx_region, &cache_top_botom_regions, nb_perimeter_layers_for_solid_fill](const tbb::blocked_range<size_t>& range) { for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { m_print->throw_if_canceled(); Layer& layer = *m_layers[idx_layer]; @@ -1611,10 +1624,18 @@ namespace Slic3r { auto& cache = cache_top_botom_regions[idx_layer]; cache.top_surfaces = offset(to_expolygons(layerm.slices().filter_by_type(stPosTop | stDensSolid)), min_perimeter_infill_spacing); append(cache.top_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_type(stPosTop | stDensSolid)), min_perimeter_infill_spacing)); + if (nb_perimeter_layers_for_solid_fill != 0) { + cache.top_fill_surfaces = offset(to_expolygons(layerm.fill_surfaces.filter_by_type(stPosTop | stDensSolid)), min_perimeter_infill_spacing); + cache.top_perimeter_surfaces = to_polygons(layerm.slices().filter_by_type(stPosTop | stDensSolid)); + } // Bottom surfaces. const SurfaceType surfaces_bottom[2] = { stPosBottom | stDensSolid, stPosBottom | stDensSolid | stModBridge }; cache.bottom_surfaces = offset(to_expolygons(layerm.slices().filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing); append(cache.bottom_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing)); + if (nb_perimeter_layers_for_solid_fill != 0) { + cache.bottom_fill_surfaces = offset(to_expolygons(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing); + cache.bottom_perimeter_surfaces = to_polygons(layerm.slices().filter_by_types(surfaces_bottom, 2)); + } // Holes over all regions. Only collect them once, they are valid for all idx_region iterations. if (cache.holes.empty()) { for (size_t idx_region = 0; idx_region < layer.regions().size(); ++idx_region) @@ -1629,7 +1650,7 @@ namespace Slic3r { BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - start : ensure vertical wall thickness"; tbb::parallel_for( tbb::blocked_range<size_t>(0, num_layers, grain_size), - [this, idx_region, &cache_top_botom_regions] + [this, idx_region, &cache_top_botom_regions, nb_perimeter_layers_for_solid_fill] (const tbb::blocked_range<size_t>& range) { // printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end()); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { @@ -1653,6 +1674,8 @@ namespace Slic3r { coord_t infill_line_spacing = solid_infill_flow.scaled_spacing(); // Find a union of perimeters below / above this surface to guarantee a minimum shell thickness. Polygons shell; + Polygons fill_shell; + Polygons max_perimeter_shell; // for nb_perimeter_layers_for_solid_fill Polygons holes; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING ExPolygons shell_ex; @@ -1700,6 +1723,16 @@ namespace Slic3r { // than running the union_ all at once. shell = union_(shell, false); } + if (nb_perimeter_layers_for_solid_fill != 0) { + if (!cache.top_fill_surfaces.empty()) { + polygons_append(fill_shell, cache.top_fill_surfaces); + fill_shell = union_(fill_shell, false); + } + if (nb_perimeter_layers_for_solid_fill > 1 && i - idx_layer < nb_perimeter_layers_for_solid_fill) { + polygons_append(max_perimeter_shell, cache.top_perimeter_surfaces); + max_perimeter_shell = union_(max_perimeter_shell, false); + } + } } } if (int n_bottom_layers = region_config.bottom_solid_layers.value; n_bottom_layers > 0) { @@ -1719,6 +1752,16 @@ namespace Slic3r { // than running the union_ all at once. shell = union_(shell, false); } + if (nb_perimeter_layers_for_solid_fill != 0) { + if (!cache.bottom_fill_surfaces.empty()) { + polygons_append(fill_shell, cache.bottom_fill_surfaces); + fill_shell = union_(fill_shell, false); + } + if (nb_perimeter_layers_for_solid_fill > 1 && idx_layer - i < nb_perimeter_layers_for_solid_fill) { + polygons_append(max_perimeter_shell, cache.bottom_perimeter_surfaces); + max_perimeter_shell = union_(max_perimeter_shell, false); + } + } } } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING @@ -1779,11 +1822,27 @@ namespace Slic3r { svg.Close(); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - // Trim the shells region by the internal & internal void surfaces. const SurfaceType surfaceTypesInternal[] = { stPosInternal | stDensSparse, stPosInternal | stDensVoid, stPosInternal | stDensSolid }; const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces.filter_by_types(surfaceTypesInternal, 3)); - shell = intersection(shell, polygonsInternal, true); + { + Polygons shell_internal = intersection(shell, polygonsInternal, true); + //check if a polygon is only over perimeter, in this case evict it (depends from nb_perimeter_layers_for_solid_fill value) + if (nb_perimeter_layers_for_solid_fill != 0) { + for (int i = 0; i < shell_internal.size(); i++) { + if (intersection({ shell_internal[i] }, fill_shell, false).empty()) { + if (nb_perimeter_layers_for_solid_fill < 2 || intersection({ shell_internal[i] }, max_perimeter_shell, false).empty()) { + shell_internal.erase(shell_internal.begin() + i); + i--; + } + } + } + if (shell_internal.empty()) + continue; + } + + shell = std::move(shell_internal); + } polygons_append(shell, diff(polygonsInternal, holes)); if (shell.empty()) continue; @@ -2112,7 +2171,7 @@ namespace Slic3r { static void apply_to_print_region_config(PrintRegionConfig& out, const DynamicPrintConfig& in) { - // 1) Copy the "extruder key to infill_extruder and perimeter_extruder. + // 1) Copy the "extruder" key to infill_extruder and perimeter_extruder. std::string sextruder = "extruder"; auto* opt_extruder = in.opt<ConfigOptionInt>(sextruder); if (opt_extruder) { @@ -2322,7 +2381,7 @@ namespace Slic3r { } std::vector<ExPolygons> expolygons_by_layer = this->slice_region(region_id, slice_zs, slicing_mode, slicing_mode_normal_below_layer, SlicingMode::Regular); //scale for shrinkage - const size_t extruder_id = this->print()->regions()[region_id]->extruder(FlowRole::frPerimeter) - 1; + const size_t extruder_id = this->print()->regions()[region_id]->extruder(FlowRole::frPerimeter, *this) - 1; double scale = print()->config().filament_shrink.get_abs_value(extruder_id, 1); if (scale != 1) { scale = 1 / scale; @@ -2375,7 +2434,7 @@ namespace Slic3r { } //scale for shrinkage for (SlicedVolume& sv : sliced_volumes) { - double scale = print()->config().filament_shrink.get_abs_value(this->print()->regions()[sv.region_id]->extruder(FlowRole::frPerimeter) - 1, 1); + double scale = print()->config().filament_shrink.get_abs_value(this->print()->regions()[sv.region_id]->extruder(FlowRole::frPerimeter, *this) - 1, 1); if (scale != 1) { scale = 1 / scale; for (ExPolygons& polys : sv.expolygons_by_layer) @@ -2500,9 +2559,13 @@ namespace Slic3r { float hole_delta = inner_delta + float(scale_(m_config.hole_size_compensation.value)); //FIXME only apply the compensation if no raft is enabled. float first_layer_compensation = 0.f; - if (layer_id == 0 && m_config.raft_layers == 0 && m_config.first_layer_size_compensation.value != 0) { + int first_layers = m_config.first_layer_size_compensation_layers.value; + if (layer_id < first_layers && m_config.raft_layers == 0 && m_config.first_layer_size_compensation.value != 0) { // Only enable Elephant foot compensation if printing directly on the print bed. first_layer_compensation = float(scale_(m_config.first_layer_size_compensation.value)); + // reduce first_layer_compensation for every layer over the first one. + first_layer_compensation = (first_layers - layer_id + 1) * first_layer_compensation / float(first_layers); + // simplify compensations if possible if (first_layer_compensation > 0) { outter_delta += first_layer_compensation; inner_delta += first_layer_compensation; @@ -2537,7 +2600,7 @@ namespace Slic3r { expolygons = _shrink_contour_holes(std::max(0.f, outter_delta), std::max(0.f, inner_delta), std::max(0.f, hole_delta), expolygons); } // Apply the elephant foot compensation. - if (layer_id == 0 && first_layer_compensation != 0.f) { + if (layer_id < first_layers && first_layer_compensation != 0.f) { expolygons = union_ex(Slic3r::elephant_foot_compensation(expolygons, layerm->flow(frExternalPerimeter), unscale<double>(-first_layer_compensation))); } @@ -2591,7 +2654,7 @@ namespace Slic3r { // Apply the negative XY compensation. (the ones that is <0) ExPolygons trimming; static const float eps = float(scale_(m_config.slice_closing_radius.value) * 1.5); - if (layer_id == 0 && first_layer_compensation < 0.f) { + if (layer_id < first_layers && first_layer_compensation < 0.f) { ExPolygons expolygons_first_layer = offset_ex(layer->merged(eps), -eps); trimming = Slic3r::elephant_foot_compensation(expolygons_first_layer, layer->regions().front()->flow(frExternalPerimeter), unscale<double>(-first_layer_compensation)); @@ -2929,6 +2992,16 @@ namespace Slic3r { return this->slice_volumes(zs, SlicingMode::Regular, volumes); } +//FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. +static void fix_mesh_connectivity(TriangleMesh &mesh) +{ + auto nr_degenerated = mesh.stl.stats.degenerate_facets; + stl_check_facets_exact(&mesh.stl); + if (nr_degenerated != mesh.stl.stats.degenerate_facets) + // stl_check_facets_exact() removed some newly degenerated faces. Some faces could become degenerate after some mesh transformation. + stl_generate_shared_vertices(&mesh.stl, mesh.its); +} + std::vector<ExPolygons> PrintObject::slice_volumes( const std::vector<float>& z, SlicingMode mode, size_t slicing_mode_normal_below_layer, SlicingMode mode_below, @@ -2941,10 +3014,8 @@ namespace Slic3r { TriangleMesh mesh(volumes.front()->mesh()); mesh.transform(volumes.front()->get_matrix(), true); assert(mesh.repaired); - if (volumes.size() == 1 && mesh.repaired) { - //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. - stl_check_facets_exact(&mesh.stl); - } + if (volumes.size() == 1 && mesh.repaired) + fix_mesh_connectivity(mesh); for (size_t idx_volume = 1; idx_volume < volumes.size(); ++idx_volume) { const ModelVolume& model_volume = *volumes[idx_volume]; TriangleMesh vol_mesh(model_volume.mesh()); @@ -2977,10 +3048,8 @@ namespace Slic3r { //FIXME better to split the mesh into separate shells, perform slicing over each shell separately and then to use a Boolean operation to merge them. TriangleMesh mesh(volume.mesh()); mesh.transform(volume.get_matrix(), true); - if (mesh.repaired) { - //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. - stl_check_facets_exact(&mesh.stl); - } + if (mesh.repaired) + fix_mesh_connectivity(mesh); if (mesh.stl.stats.number_of_facets > 0) { mesh.transform(m_trafo, true); // apply XY shift @@ -3253,10 +3322,12 @@ namespace Slic3r { coordf_t print_z = layer->print_z; coordf_t bottom_z = layer->bottom_z(); + // 0: topSolid, 1: botSolid, 2: boSolidBridged for (size_t idx_surface_type = 0; idx_surface_type < 3; ++idx_surface_type) { m_print->throw_if_canceled(); SurfaceType type = (idx_surface_type == 0) ? (stPosTop | stDensSolid) : - ((idx_surface_type == 1) ? (stPosBottom | stDensSolid) : (stPosBottom | stDensSolid | stModBridge)); + ((idx_surface_type == 1) ? (stPosBottom | stDensSolid) : + (stPosBottom | stDensSolid | stModBridge)); int num_solid_layers = ((type & stPosTop) == stPosTop) ? region_config.top_solid_layers.value : region_config.bottom_solid_layers.value; if (num_solid_layers == 0) continue; @@ -3273,16 +3344,17 @@ namespace Slic3r { // Surfaces including the area of perimeters. Everything, that is visible from the top / bottom // (not covered by a layer above / below). // This does not contain the areas covered by perimeters! - Polygons solid; + ExPolygons solid; for (const Surface& surface : layerm->slices().surfaces) if (surface.surface_type == type) - polygons_append(solid, to_polygons(surface.expolygon)); + solid.push_back(surface.expolygon); // Infill areas (slices without the perimeters). for (const Surface& surface : layerm->fill_surfaces.surfaces) if (surface.surface_type == type) - polygons_append(solid, to_polygons(surface.expolygon)); + solid.push_back(surface.expolygon); if (solid.empty()) continue; + solid = union_ex(solid); // Slic3r::debugf "Layer %d has %s surfaces\n", $i, (($type & stTop) != 0) ? 'top' : 'bottom'; // Scatter top / bottom regions to other layers. Scattering process is inherently serial, it is difficult to parallelize without locking. @@ -3308,13 +3380,14 @@ namespace Slic3r { // narrow bottom surfaces): reassigning $solid will consider the 'shadow' of the // upper perimeter as an obstacle and shell will not be propagated to more upper layers //FIXME How does it work for stInternalBRIDGE? This is set for sparse infill. Likely this does not work. - Polygons new_internal_solid; + ExPolygons new_internal_solid; { - Polygons internal; + ExPolygons internal; for (const Surface& surface : neighbor_layerm->fill_surfaces.surfaces) if (surface.has_pos_internal() && (surface.has_fill_sparse() || surface.has_fill_solid())) - polygons_append(internal, to_polygons(surface.expolygon)); - new_internal_solid = intersection(solid, internal, true); + internal.push_back(surface.expolygon); + internal = union_ex(internal); + new_internal_solid = intersection_ex(solid, internal, true); } if (new_internal_solid.empty()) { // No internal solid needed on this layer. In order to decide whether to continue @@ -3339,18 +3412,23 @@ namespace Slic3r { // and it's not wanted in a hollow print even if it would make sense when // obeying the solid shell count option strictly (DWIM!) float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width()); - Polygons too_narrow = diff( + ExPolygons too_narrow = diff_ex( new_internal_solid, - offset2(new_internal_solid, -margin, +margin, jtMiter, 5), + offset2_ex(new_internal_solid, -margin, +margin, jtMiter, 5), true); // Trim the regularized region by the original region. if (!too_narrow.empty()) - new_internal_solid = solid = diff(new_internal_solid, too_narrow); + if (!too_narrow.empty()) { + solid = new_internal_solid = diff_ex(new_internal_solid, too_narrow); + } } + + //merill: this is creating artifacts, and i can't recreate the issue it wants to fix. + // make sure the new internal solid is wide enough, as it might get collapsed // when spacing is added in Fill.pm - { + if(false){ //FIXME Vojtech: Disable this and you will be sorry. // https://github.com/prusa3d/PrusaSlicer/issues/26 bottom float margin = 3.f * layerm->flow(frSolidInfill).scaled_width(); // require at least this size @@ -3359,28 +3437,28 @@ namespace Slic3r { // get a triangle in $too_narrow; if we grow it below then the shell // would have a different shape from the external surface and we'd still // have the same angle, so the next shell would be grown even more and so on. - Polygons too_narrow = diff( + ExPolygons too_narrow = diff_ex( new_internal_solid, - offset2(new_internal_solid, -margin, +margin, ClipperLib::jtMiter, 5), + offset2_ex(new_internal_solid, -margin, +margin, ClipperLib::jtMiter, 5), true); if (!too_narrow.empty()) { // grow the collapsing parts and add the extra area to the neighbor layer // as well as to our original surfaces so that we support this // additional area in the next shell too // make sure our grown surfaces don't exceed the fill area - Polygons internal; + ExPolygons internal; for (const Surface& surface : neighbor_layerm->fill_surfaces.surfaces) if (surface.has_pos_internal() && !surface.has_mod_bridge()) - polygons_append(internal, to_polygons(surface.expolygon)); - polygons_append(new_internal_solid, - intersection( - offset(too_narrow, +margin), + internal.push_back(surface.expolygon); + expolygons_append(new_internal_solid, + intersection_ex( + offset_ex(too_narrow, +margin), // Discard bridges as they are grown for anchoring and we can't // remove such anchors. (This may happen when a bridge is being // anchored onto a wall where little space remains after the bridge // is grown, and that little space is an internal solid shell so // it triggers this too_narrow logic.) - internal)); + union_ex(internal))); // see https://github.com/prusa3d/PrusaSlicer/pull/3426 // solid = new_internal_solid; } @@ -3389,30 +3467,31 @@ namespace Slic3r { // internal-solid are the union of the existing internal-solid surfaces // and new ones SurfaceCollection backup = std::move(neighbor_layerm->fill_surfaces); - polygons_append(new_internal_solid, to_polygons(backup.filter_by_type(stPosInternal | stDensSolid))); + expolygons_append(new_internal_solid, to_expolygons(backup.filter_by_type(stPosInternal | stDensSolid))); ExPolygons internal_solid = union_ex(new_internal_solid, false); // assign new internal-solid surfaces to layer neighbor_layerm->fill_surfaces.set(internal_solid, stPosInternal | stDensSolid); // subtract intersections from layer surfaces to get resulting internal surfaces - Polygons polygons_internal = to_polygons(std::move(internal_solid)); + //ExPolygons polygons_internal = to_polygons(std::move(internal_solid)); ExPolygons internal = diff_ex( - to_polygons(backup.filter_by_type(stPosInternal | stDensSparse)), - polygons_internal, + to_expolygons(backup.filter_by_type(stPosInternal | stDensSparse)), + internal_solid, true); // assign resulting internal surfaces to layer neighbor_layerm->fill_surfaces.append(internal, stPosInternal | stDensSparse); - polygons_append(polygons_internal, to_polygons(std::move(internal))); + expolygons_append(internal_solid, internal); // assign top and bottom surfaces to layer SurfaceType surface_types_solid[] = { stPosTop | stDensSolid, stPosBottom | stDensSolid, stPosBottom | stDensSolid | stModBridge }; backup.keep_types(surface_types_solid, 3); //backup.keep_types_flag(stPosTop | stPosBottom); std::vector<SurfacesPtr> top_bottom_groups; backup.group(&top_bottom_groups); - for (SurfacesPtr& group : top_bottom_groups) + for (SurfacesPtr& group : top_bottom_groups) { neighbor_layerm->fill_surfaces.append( - diff_ex(to_polygons(group), polygons_internal), + diff_ex(to_expolygons(group), union_ex(internal_solid)), // Use an existing surface as a template, it carries the bridge angle etc. *group.front()); + } } EXTERNAL:; } // foreach type (stTop, stBottom, stBottomBridge) diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index 21f5d5586..e62b34ea6 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -4,7 +4,7 @@ namespace Slic3r { // 1-based extruder identifier for this region and role. -uint16_t PrintRegion::extruder(FlowRole role) const +uint16_t PrintRegion::extruder(FlowRole role, const PrintObject& object) const { size_t extruder = 0; if (role == frPerimeter || role == frExternalPerimeter) @@ -13,6 +13,10 @@ uint16_t PrintRegion::extruder(FlowRole role) const extruder = m_config.infill_extruder; else if (role == frSolidInfill || role == frTopSolidInfill) extruder = m_config.solid_infill_extruder; + else if (role == frSupportMaterial) + extruder = object.config().support_material_extruder; + else if (role == frSupportMaterialInterface) + extruder = object.config().support_material_interface_extruder; else throw Slic3r::InvalidArgument("Unknown role"); return extruder; @@ -50,10 +54,48 @@ Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool fir // Get the configured nozzle_diameter for the extruder associated to the flow role requested. // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. - double nozzle_diameter = m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1); + double nozzle_diameter = m_print->config().nozzle_diameter.get_at(this->extruder(role, object) - 1); return Flow::new_from_config_width(role, config_width, (float)nozzle_diameter, (float)layer_height, bridge ? (float)m_config.bridge_flow_ratio.get_abs_value(1) : 0.0f); } +float PrintRegion::width(FlowRole role, bool first_layer, const PrintObject& object) const +{ + const ConfigOptionFloatOrPercent* config_width = nullptr; + // otherwise, get extrusion width from configuration + // (might be an absolute value, or a percent value, or zero for auto) + if (first_layer && object.config().first_layer_extrusion_width.value > 0) { + config_width = &object.config().first_layer_extrusion_width; + } else if (role == frExternalPerimeter) { + config_width = &m_config.external_perimeter_extrusion_width; + } else if (role == frPerimeter) { + config_width = &m_config.perimeter_extrusion_width; + } else if (role == frInfill) { + config_width = &m_config.infill_extrusion_width; + } else if (role == frSolidInfill) { + config_width = &m_config.solid_infill_extrusion_width; + } else if (role == frTopSolidInfill) { + config_width = &m_config.top_infill_extrusion_width; + } else if (role == frSupportMaterial || role == frSupportMaterialInterface) { + config_width = &object.config().support_material_extrusion_width; + } else { + throw Slic3r::InvalidArgument("Unknown role"); + } + + if (!config_width || config_width->value == 0) + config_width = &object.config().extrusion_width; + + // Get the configured nozzle_diameter for the extruder associated to the flow role requested. + // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. + double nozzle_diameter = m_print->config().nozzle_diameter.get_at(this->extruder(role, object) - 1); + if (config_width->value <= 0.) { + // If user left option to 0, calculate a sane default width. + return Flow::auto_extrusion_width(role, nozzle_diameter); + } else { + // If user set a manual value, use it. + return float(config_width->get_abs_value(nozzle_diameter)); + } +} + coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const { return (print_config.nozzle_diameter.get_at(m_config.perimeter_extruder.value - 1) + diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp index 6eff2ada0..f7e7d5bca 100644 --- a/src/libslic3r/ShortestPath.cpp +++ b/src/libslic3r/ShortestPath.cpp @@ -1547,6 +1547,11 @@ static inline void do_crossover(const std::vector<FlipEdge> &edges_in, std::vect assert(edges_in.size() == edges_out.size()); } +// Worst time complexity: O(min(n, 100) * (n * log n + n^2) +// Expected time complexity: O(min(n, 100) * (n * log n + k * n) +// where n is the number of edges and k is the number of connection_lengths candidates after the first one +// is found that improves the total cost. +//FIXME there are likley better heuristics to lower the time complexity. static inline void reorder_by_two_exchanges_with_segment_flipping(std::vector<FlipEdge> &edges) { if (edges.size() < 2) @@ -1556,7 +1561,8 @@ static inline void reorder_by_two_exchanges_with_segment_flipping(std::vector<Fl std::vector<FlipEdge> edges_tmp(edges); std::vector<std::pair<double, size_t>> connection_lengths(edges.size() - 1, std::pair<double, size_t>(0., 0)); std::vector<char> connection_tried(edges.size(), false); - for (size_t iter = 0; iter < edges.size(); ++ iter) { + const size_t max_iterations = std::min(edges.size(), size_t(100)); + for (size_t iter = 0; iter < max_iterations; ++ iter) { // Initialize connection costs and connection lengths. for (size_t i = 1; i < edges.size(); ++ i) { const FlipEdge &e1 = edges[i - 1]; @@ -1621,6 +1627,8 @@ static inline void reorder_by_two_exchanges_with_segment_flipping(std::vector<Fl } } +#if 0 +// Currently not used, too slow. static inline void reorder_by_three_exchanges_with_segment_flipping(std::vector<FlipEdge> &edges) { if (edges.size() < 3) { @@ -1703,6 +1711,7 @@ static inline void reorder_by_three_exchanges_with_segment_flipping(std::vector< } } } +#endif typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::DontAlign> Matrixd; @@ -1774,6 +1783,8 @@ static inline std::pair<double, size_t> minimum_crossover_cost( return std::make_pair(cost_min, flip_min); } +#if 0 +// Currently not used, too slow. static inline void reorder_by_three_exchanges_with_segment_flipping2(std::vector<FlipEdge> &edges) { if (edges.size() < 3) { @@ -1870,8 +1881,11 @@ static inline void reorder_by_three_exchanges_with_segment_flipping2(std::vector } } } +#endif // Flip the sequences of polylines to lower the total length of connecting lines. +// Used by the infill generator if the infill is not connected with perimeter lines +// and to order the brim lines. static inline void improve_ordering_by_two_exchanges_with_segment_flipping(Polylines &polylines, bool fixed_start) { #ifndef NDEBUG @@ -1923,6 +1937,7 @@ static inline void improve_ordering_by_two_exchanges_with_segment_flipping(Polyl #endif /* NDEBUG */ } +// Used to optimize order of infill lines and brim lines. Polylines chain_polylines(Polylines &&polylines, const Point *start_near) { #ifdef DEBUG_SVG_OUTPUT diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index f9be1f494..665b930ba 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -226,7 +226,7 @@ inline typename CONTAINER_TYPE::value_type& next_value_modulo(typename CONTAINER return container[next_idx_modulo(idx, container.size())]; } -extern std::string xml_escape(std::string text); +extern std::string xml_escape(std::string text, bool is_marked = false); #if defined __GNUC__ && __GNUC__ < 5 && !defined __clang__ diff --git a/src/libslic3r/enum_bitmask.hpp b/src/libslic3r/enum_bitmask.hpp new file mode 100644 index 000000000..4c2076313 --- /dev/null +++ b/src/libslic3r/enum_bitmask.hpp @@ -0,0 +1,80 @@ +#ifndef slic3r_enum_bitmask_hpp_ +#define slic3r_enum_bitmask_hpp_ + +// enum_bitmask for passing a set of attributes to a function in a type safe way. +// Adapted from https://gpfault.net/posts/typesafe-bitmasks.txt.html +// with hints from https://www.strikerx3.dev/cpp/2019/02/27/typesafe-enum-class-bitmasks-in-cpp.html + +#include <type_traits> + +namespace Slic3r { + +// enum_bitmasks can only be used with enums. +template<class option_type, typename = typename std::enable_if<std::is_enum<option_type>::value>::type> +class enum_bitmask { + // The type we'll use for storing the value of our bitmask should be the same as the enum's underlying type. + using underlying_type = typename std::underlying_type<option_type>::type; + + // This method helps us avoid having to explicitly set enum values to powers of two. + static constexpr underlying_type mask_value(option_type o) { return 1 << static_cast<underlying_type>(o); } + + // Private ctor to be used internally. + explicit constexpr enum_bitmask(underlying_type o) : m_bits(o) {} + +public: + // Default ctor creates a bitmask with no options selected. + constexpr enum_bitmask() : m_bits(0) {} + + // Creates a enum_bitmask with just one bit set. + // This ctor is intentionally non-explicit, to allow passing an options to a function: + // FunctionExpectingBitmask(Options::Opt1) + constexpr enum_bitmask(option_type o) : m_bits(mask_value(o)) {} + + // Set the bit corresponding to the given option. + constexpr enum_bitmask operator|(option_type t) { return enum_bitmask(m_bits | mask_value(t)); } + + // Combine with another enum_bitmask of the same type. + constexpr enum_bitmask operator|(enum_bitmask<option_type> t) { return enum_bitmask(m_bits | t.m_bits); } + + // Get the value of the bit corresponding to the given option. + constexpr bool operator&(option_type t) { return m_bits & mask_value(t); } + constexpr bool has(option_type t) { return m_bits & mask_value(t); } + +private: + underlying_type m_bits = 0; +}; + +// For enabling free functions producing enum_bitmask<> type from bit operations on enums. +template<typename Enum> struct is_enum_bitmask_type { static const bool enable = false; }; +#define ENABLE_ENUM_BITMASK_OPERATORS(x) template<> struct is_enum_bitmask_type<x> { static const bool enable = true; }; +template<class Enum> inline constexpr bool is_enum_bitmask_type_v = is_enum_bitmask_type<Enum>::enable; + +// Creates an enum_bitmask from two options, convenient for passing of options to a function: +// FunctionExpectingBitmask(Options::Opt1 | Options::Opt2 | Options::Opt3) +template <class option_type> +constexpr std::enable_if_t<is_enum_bitmask_type_v<option_type>, enum_bitmask<option_type>> operator|(option_type lhs, option_type rhs) { + static_assert(std::is_enum_v<option_type>); + return enum_bitmask<option_type>{lhs} | rhs; +} + +template <class option_type> +constexpr std::enable_if_t<is_enum_bitmask_type_v<option_type>, enum_bitmask<option_type>> operator|(option_type lhs, enum_bitmask<option_type> rhs) { + static_assert(std::is_enum_v<option_type>); + return enum_bitmask<option_type>{lhs} | rhs; +} + +template <class option_type> +constexpr std::enable_if_t<is_enum_bitmask_type_v<option_type>, enum_bitmask<option_type>> only_if(bool condition, option_type opt) { + static_assert(std::is_enum_v<option_type>); + return condition ? enum_bitmask<option_type>{opt} : enum_bitmask<option_type>{}; +} + +template <class option_type> +constexpr std::enable_if_t<is_enum_bitmask_type_v<option_type>, enum_bitmask<option_type>> only_if(bool condition, enum_bitmask<option_type> opt) { + static_assert(std::is_enum_v<option_type>); + return condition ? opt : enum_bitmask<option_type>{}; +} + +} // namespace Slic3r + +#endif // slic3r_enum_bitmask_hpp_ diff --git a/src/libslic3r/pchheader.hpp b/src/libslic3r/pchheader.hpp index a1d6da5fe..0135fd712 100644 --- a/src/libslic3r/pchheader.hpp +++ b/src/libslic3r/pchheader.hpp @@ -111,6 +111,7 @@ #include "BoundingBox.hpp" #include "ClipperUtils.hpp" #include "Config.hpp" +#include "enum_bitmask.hpp" #include "format.hpp" #include "I18N.hpp" #include "MultiPoint.hpp" diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 0d03de17b..ef77f2263 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -424,6 +424,53 @@ std::error_code rename_file(const std::string &from, const std::string &to) } #ifdef __linux__ +// Copied from boost::filesystem. +// Called by copy_file_linux() in case linux sendfile() API is not supported. +int copy_file_linux_read_write(int infile, int outfile, uintmax_t file_size) +{ + std::vector<char> buf( + // Prefer the buffer to be larger than the file size so that we don't have + // to perform an extra read if the file fits in the buffer exactly. + std::clamp<size_t>(file_size + (file_size < ~static_cast<uintmax_t >(0u)), + // Min and max buffer sizes are selected to minimize the overhead from system calls. + // The values are picked based on coreutils cp(1) benchmarking data described here: + // https://github.com/coreutils/coreutils/blob/d1b0257077c0b0f0ee25087efd46270345d1dd1f/src/ioblksize.h#L23-L72 + 8u * 1024u, 256u * 1024u), + 0); + +#if defined(POSIX_FADV_SEQUENTIAL) + ::posix_fadvise(infile, 0, 0, POSIX_FADV_SEQUENTIAL); +#endif + + // Don't use file size to limit the amount of data to copy since some filesystems, like procfs or sysfs, + // provide files with generated content and indicate that their size is zero or 4096. Just copy as much data + // as we can read from the input file. + while (true) { + ssize_t sz_read = ::read(infile, buf.data(), buf.size()); + if (sz_read == 0) + break; + if (sz_read < 0) { + int err = errno; + if (err == EINTR) + continue; + return err; + } + // Allow for partial writes - see Advanced Unix Programming (2nd Ed.), + // Marc Rochkind, Addison-Wesley, 2004, page 94 + for (ssize_t sz_wrote = 0; sz_wrote < sz_read;) { + ssize_t sz = ::write(outfile, buf.data() + sz_wrote, static_cast<std::size_t>(sz_read - sz_wrote)); + if (sz < 0) { + int err = errno; + if (err == EINTR) + continue; + return err; + } + sz_wrote += sz; + } + } + return 0; +} + // Copied from boost::filesystem, to support copying a file to a weird filesystem, which does not support changing file attributes, // for example ChromeOS Linux integration or FlashAIR WebDAV. // Copied and simplified from boost::filesystem::detail::copy_file() with option = overwrite_if_exists and with just the Linux path kept, @@ -465,7 +512,7 @@ bool copy_file_linux(const boost::filesystem::path &from, const boost::filesyste err = errno; goto fail; } - + const mode_t from_mode = from_stat.st_mode; if (!S_ISREG(from_mode)) { err = ENOSYS; @@ -520,6 +567,19 @@ bool copy_file_linux(const boost::filesystem::path &from, const boost::filesyste ssize_t sz = ::sendfile(outfile.fd, infile.fd, nullptr, size_to_copy); if (sz < 0) { err = errno; + if (offset == 0u) { + // sendfile may fail with EINVAL if the underlying filesystem does not support it. + // See https://patchwork.kernel.org/project/linux-nfs/patch/20190411183418.4510-1-olga.kornievskaia@gmail.com/ + // https://bugzilla.redhat.com/show_bug.cgi?id=1783554. + // https://github.com/boostorg/filesystem/commit/4b9052f1e0b2acf625e8247582f44acdcc78a4ce + if (err == EINVAL || err == EOPNOTSUPP) { + err = copy_file_linux_read_write(infile.fd, outfile.fd, from_stat.st_size); + if (err < 0) + goto fail; + // Succeeded. + break; + } + } if (err == EINTR) continue; if (err == 0) @@ -529,7 +589,6 @@ bool copy_file_linux(const boost::filesystem::path &from, const boost::filesyste offset += sz; } } - // If we created a new file with an explicitly added S_IWUSR permission, // we may need to update its mode bits to match the source file. if (to_mode != from_mode && ::fchmod(outfile.fd, from_mode) != 0) { @@ -781,7 +840,7 @@ unsigned get_current_pid() #endif } -std::string xml_escape(std::string text) +std::string xml_escape(std::string text, bool is_marked/* = false*/) { std::string::size_type pos = 0; for (;;) @@ -796,8 +855,8 @@ std::string xml_escape(std::string text) case '\"': replacement = """; break; case '\'': replacement = "'"; break; case '&': replacement = "&"; break; - case '<': replacement = "<"; break; - case '>': replacement = ">"; break; + case '<': replacement = is_marked ? "<" :"<"; break; + case '>': replacement = is_marked ? ">" :">"; break; default: break; } diff --git a/src/slic3r/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp index 47b8384ec..0ded575c8 100644 --- a/src/slic3r/Config/Snapshot.cpp +++ b/src/slic3r/Config/Snapshot.cpp @@ -8,14 +8,23 @@ #include <boost/property_tree/ini_parser.hpp> #include <boost/property_tree/ptree_fwd.hpp> #include <boost/filesystem/operations.hpp> +#include <boost/log/trivial.hpp> #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/format.hpp" #include "libslic3r/libslic3r.h" #include "libslic3r/Time.hpp" #include "libslic3r/Config.hpp" #include "libslic3r/FileParserError.hpp" #include "libslic3r/Utils.hpp" +#include "../GUI/GUI.hpp" +#include "../GUI/GUI_App.hpp" +#include "../GUI/I18N.hpp" +#include "../GUI/MainFrame.hpp" + +#include <wx/richmsgdlg.h> + #define SLIC3R_SNAPSHOTS_DIR "snapshots" #define SLIC3R_SNAPSHOT_FILE "snapshot.ini" @@ -362,7 +371,8 @@ static void copy_config_dir_single_level(const boost::filesystem::path &path_src for (auto &dir_entry : boost::filesystem::directory_iterator(path_src)) if (Slic3r::is_ini_file(dir_entry)) - boost::filesystem::copy_file(dir_entry.path(), path_dst / dir_entry.path().filename(), boost::filesystem::copy_option::overwrite_if_exists); + if (std::string error_message; copy_file(dir_entry.path().string(), (path_dst / dir_entry.path().filename()).string(), error_message, false) != SUCCESS) + throw Slic3r::RuntimeError(format("Failed copying \"%1%\" to \"%2%\": %3%", path_src.string(), path_dst.string(), error_message)); } static void delete_existing_ini_files(const boost::filesystem::path &path) @@ -413,7 +423,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot: ++ it; // Read the active config bundle, parse the config version. PresetBundle bundle; - bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY); + bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly, ForwardCompatibilitySubstitutionRule::EnableSilent); for (const auto &vp : bundle.vendors) if (vp.second.id == cfg.name) cfg.version.config_version = vp.second.config_version; @@ -433,14 +443,27 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot: } boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id; - boost::filesystem::create_directory(snapshot_dir); - - // Backup the presets. - for (const char *subdir : snapshot_subdirs) - copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir); - snapshot.save_ini((snapshot_dir / "snapshot.ini").string()); - assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured); - m_snapshots.emplace_back(std::move(snapshot)); + + try { + boost::filesystem::create_directory(snapshot_dir); + + // Backup the presets. + for (const char *subdir : snapshot_subdirs) + copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir); + snapshot.save_ini((snapshot_dir / "snapshot.ini").string()); + assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured); + m_snapshots.emplace_back(std::move(snapshot)); + } catch (...) { + if (boost::filesystem::is_directory(snapshot_dir)) { + try { + // Clean up partially copied snapshot. + boost::filesystem::remove_all(snapshot_dir); + } catch (...) { + BOOST_LOG_TRIVIAL(error) << "Failed taking snapshot and failed removing the snapshot directory " << snapshot_dir; + } + } + throw; + } return m_snapshots.back(); } @@ -551,6 +574,32 @@ SnapshotDB& SnapshotDB::singleton() return instance; } +const Snapshot* take_config_snapshot_report_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment) +{ + try { + return &SnapshotDB::singleton().take_snapshot(app_config, reason, comment); + } catch (std::exception &err) { + show_error(static_cast<wxWindow*>(wxGetApp().mainframe), + _L("Taking a configuration snapshot failed.") + "\n\n" + from_u8(err.what())); + return nullptr; + } +} + +bool take_config_snapshot_cancel_on_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment, const std::string &message) +{ + try { + SnapshotDB::singleton().take_snapshot(app_config, reason, comment); + return true; + } catch (std::exception &err) { + wxRichMessageDialog dlg(static_cast<wxWindow*>(wxGetApp().mainframe), + _L("SuperSlicer has encountered an error while taking a configuration snapshot.") + "\n\n" + from_u8(err.what()) + "\n\n" + from_u8(message), + _L("SuperSlicer error"), + wxYES_NO); + dlg.SetYesNoLabels(_L("Continue"), _L("Abort")); + return dlg.ShowModal() == wxID_YES; + } +} + } // namespace Config } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/Config/Snapshot.hpp b/src/slic3r/Config/Snapshot.hpp index 48add8a1a..f45300633 100644 --- a/src/slic3r/Config/Snapshot.hpp +++ b/src/slic3r/Config/Snapshot.hpp @@ -127,6 +127,13 @@ private: std::vector<Snapshot> m_snapshots; }; +// Take snapshot on SnapshotDB::singleton(). If taking snapshot fails, report an error and return nullptr. +const Snapshot* take_config_snapshot_report_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment); + +// Take snapshot on SnapshotDB::singleton(). If taking snapshot fails, report "message", and present a "Continue" or "Abort" buttons to respond. +// Return true on success and on "Continue" to continue with the process (for example installation of presets). +bool take_config_snapshot_cancel_on_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment, const std::string &message); + } // namespace Config } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/CalibrationBedDialog.cpp b/src/slic3r/GUI/CalibrationBedDialog.cpp index 781ad2bb6..9d3a2efe8 100644 --- a/src/slic3r/GUI/CalibrationBedDialog.cpp +++ b/src/slic3r/GUI/CalibrationBedDialog.cpp @@ -51,7 +51,7 @@ void CalibrationBedDialog::create_geometry(wxCommandEvent& event_args) { Slic3r::resources_dir()+"/calibration/bed_leveling/patch.amf"}, true, false, false); assert(objs_idx.size() == 5); - const DynamicPrintConfig* printConfig = this->gui_app->get_tab(Preset::TYPE_PRINT)->get_config(); + const DynamicPrintConfig* printConfig = this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->get_config(); const DynamicPrintConfig* printerConfig = this->gui_app->get_tab(Preset::TYPE_PRINTER)->get_config(); /// --- scale --- @@ -136,10 +136,10 @@ void CalibrationBedDialog::create_geometry(wxCommandEvent& event_args) { //update plater GLCanvas3D::set_warning_freeze(false); - this->gui_app->get_tab(Preset::TYPE_PRINT)->load_config(new_print_config); + this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->load_config(new_print_config); plat->on_config_change(new_print_config); plat->changed_objects(objs_idx); - this->gui_app->get_tab(Preset::TYPE_PRINT)->update_dirty(); + this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->update_dirty(); //update everything, easier to code. this->gui_app->obj_list()->update_after_undo_redo(); //if(!plat->is_background_process_update_scheduled()) diff --git a/src/slic3r/GUI/CalibrationBridgeDialog.cpp b/src/slic3r/GUI/CalibrationBridgeDialog.cpp index 0689c8bb3..97e13a0a2 100644 --- a/src/slic3r/GUI/CalibrationBridgeDialog.cpp +++ b/src/slic3r/GUI/CalibrationBridgeDialog.cpp @@ -79,7 +79,7 @@ void CalibrationBridgeDialog::create_geometry(std::string setting_to_test, bool std::vector<size_t> objs_idx = plat->load_files(items, true, false, false); assert(objs_idx.size() == nb_items); - const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_PRINT)->get_config(); + const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->get_config(); const DynamicPrintConfig* printer_config = this->gui_app->get_tab(Preset::TYPE_PRINTER)->get_config(); /// --- scale --- @@ -146,10 +146,10 @@ void CalibrationBridgeDialog::create_geometry(std::string setting_to_test, bool //update plater GLCanvas3D::set_warning_freeze(false); - this->gui_app->get_tab(Preset::TYPE_PRINT)->load_config(new_print_config); + this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->load_config(new_print_config); plat->on_config_change(new_print_config); plat->changed_objects(objs_idx); - //this->gui_app->get_tab(Preset::TYPE_PRINT)->update_dirty(); + //this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->update_dirty(); //update everything, easier to code. ObjectList* obj = this->gui_app->obj_list(); obj->update_after_undo_redo(); diff --git a/src/slic3r/GUI/CalibrationCubeDialog.cpp b/src/slic3r/GUI/CalibrationCubeDialog.cpp index b5b737527..24c3243cf 100644 --- a/src/slic3r/GUI/CalibrationCubeDialog.cpp +++ b/src/slic3r/GUI/CalibrationCubeDialog.cpp @@ -66,8 +66,8 @@ void CalibrationCubeDialog::create_geometry(std::string calibration_path) { Slic3r::resources_dir()+"/calibration/cube/"+ calibration_path}, true, false, false); assert(objs_idx.size() == 1); - const DynamicPrintConfig* printConfig = this->gui_app->get_tab(Preset::TYPE_PRINT)->get_config(); - const DynamicPrintConfig* filamentConfig = this->gui_app->get_tab(Preset::TYPE_FILAMENT)->get_config(); + const DynamicPrintConfig* printConfig = this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->get_config(); + const DynamicPrintConfig* filamentConfig = this->gui_app->get_tab(Preset::TYPE_FFF_FILAMENT)->get_config(); const DynamicPrintConfig* printerConfig = this->gui_app->get_tab(Preset::TYPE_PRINTER)->get_config(); /// --- scale --- diff --git a/src/slic3r/GUI/CalibrationFlowDialog.cpp b/src/slic3r/GUI/CalibrationFlowDialog.cpp index 9cfe37de9..74858ac0d 100644 --- a/src/slic3r/GUI/CalibrationFlowDialog.cpp +++ b/src/slic3r/GUI/CalibrationFlowDialog.cpp @@ -58,7 +58,7 @@ void CalibrationFlowDialog::create_geometry(float start, float delta) { assert(objs_idx.size() == 5); - const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_PRINT)->get_config(); + const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->get_config(); const DynamicPrintConfig* printerConfig = this->gui_app->get_tab(Preset::TYPE_PRINTER)->get_config(); /// --- scale --- @@ -162,10 +162,10 @@ void CalibrationFlowDialog::create_geometry(float start, float delta) { //update plater GLCanvas3D::set_warning_freeze(false); - this->gui_app->get_tab(Preset::TYPE_PRINT)->load_config(new_print_config); + this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->load_config(new_print_config); plat->on_config_change(new_print_config); plat->changed_objects(objs_idx); - this->gui_app->get_tab(Preset::TYPE_PRINT)->update_dirty(); + this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->update_dirty(); //update everything, easier to code. ObjectList* obj = this->gui_app->obj_list(); obj->update_after_undo_redo(); diff --git a/src/slic3r/GUI/CalibrationOverBridgeDialog.cpp b/src/slic3r/GUI/CalibrationOverBridgeDialog.cpp index 7f2be5240..e0a12e612 100644 --- a/src/slic3r/GUI/CalibrationOverBridgeDialog.cpp +++ b/src/slic3r/GUI/CalibrationOverBridgeDialog.cpp @@ -63,7 +63,7 @@ void CalibrationOverBridgeDialog::create_geometry(bool over_bridge) { Slic3r::resources_dir()+"/calibration/over-bridge_tuning/over-bridge_flow_ratio_test.amf"}, true, false, false); assert(objs_idx.size() == 6); - const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_PRINT)->get_config(); + const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->get_config(); const DynamicPrintConfig* printer_config = this->gui_app->get_tab(Preset::TYPE_PRINTER)->get_config(); /// --- scale --- @@ -138,7 +138,7 @@ void CalibrationOverBridgeDialog::create_geometry(bool over_bridge) { //update plater GLCanvas3D::set_warning_freeze(false); - this->gui_app->get_tab(Preset::TYPE_PRINT)->load_config(new_print_config); + this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->load_config(new_print_config); plat->on_config_change(new_print_config); plat->changed_objects(objs_idx); //update everything, easier to code. diff --git a/src/slic3r/GUI/CalibrationRetractionDialog.cpp b/src/slic3r/GUI/CalibrationRetractionDialog.cpp index 71e111a75..99c45e3a3 100644 --- a/src/slic3r/GUI/CalibrationRetractionDialog.cpp +++ b/src/slic3r/GUI/CalibrationRetractionDialog.cpp @@ -39,7 +39,7 @@ void CalibrationRetractionDialog::create_buttons(wxStdDialogButtonSizer* buttons //start_step = new wxComboBox(this, wxID_ANY, wxString{ "current" }, wxDefaultPosition, wxDefaultSize, 7, choices_start); //start_step->SetToolTip(_(L("Select the highest temperature to test for."))); //start_step->SetSelection(0); - const DynamicPrintConfig* filament_config = this->gui_app->get_tab(Preset::TYPE_FILAMENT)->get_config(); + const DynamicPrintConfig* filament_config = this->gui_app->get_tab(Preset::TYPE_FFF_FILAMENT)->get_config(); int temp = int((2 + filament_config->option<ConfigOptionInts>("temperature")->get_at(0)) / 5) * 5; auto size = wxSize(4 * em_unit(), wxDefaultCoord); temp_start = new wxTextCtrl(this, wxID_ANY, std::to_string(temp), wxDefaultPosition, size); @@ -77,7 +77,7 @@ void CalibrationRetractionDialog::create_buttons(wxStdDialogButtonSizer* buttons void CalibrationRetractionDialog::remove_slowdown(wxCommandEvent& event_args) { - const DynamicPrintConfig* filament_config = this->gui_app->get_tab(Preset::TYPE_FILAMENT)->get_config(); + const DynamicPrintConfig* filament_config = this->gui_app->get_tab(Preset::TYPE_FFF_FILAMENT)->get_config(); DynamicPrintConfig new_filament_config = *filament_config; //make a copy const ConfigOptionInts *fil_conf = filament_config->option<ConfigOptionInts>("slowdown_below_layer_time"); @@ -93,9 +93,9 @@ void CalibrationRetractionDialog::remove_slowdown(wxCommandEvent& event_args) { new_fil_conf->values[0] = 0; new_filament_config.set_key_value("fan_below_layer_time", new_fil_conf); - this->gui_app->get_tab(Preset::TYPE_FILAMENT)->load_config(new_filament_config); + this->gui_app->get_tab(Preset::TYPE_FFF_FILAMENT)->load_config(new_filament_config); this->main_frame->plater()->on_config_change(new_filament_config); - this->gui_app->get_tab(Preset::TYPE_FILAMENT)->update_dirty(); + this->gui_app->get_tab(Preset::TYPE_FFF_FILAMENT)->update_dirty(); } @@ -136,9 +136,9 @@ void CalibrationRetractionDialog::create_geometry(wxCommandEvent& event_args) { assert(objs_idx.size() == nb_items); - const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_PRINT)->get_config(); + const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->get_config(); const DynamicPrintConfig* printer_config = this->gui_app->get_tab(Preset::TYPE_PRINTER)->get_config(); - const DynamicPrintConfig* filament_config = this->gui_app->get_tab(Preset::TYPE_FILAMENT)->get_config(); + const DynamicPrintConfig* filament_config = this->gui_app->get_tab(Preset::TYPE_FFF_FILAMENT)->get_config(); double retraction_start = 0; std::string str = temp_start->GetValue().ToStdString(); @@ -250,8 +250,8 @@ void CalibrationRetractionDialog::create_geometry(wxCommandEvent& event_args) { if (print_config->option<ConfigOptionInt>("skirts")->getInt() > 0 && print_config->option<ConfigOptionInt>("skirt_height")->getInt() > 0) { new_print_config.set_key_value("complete_objects_one_skirt", new ConfigOptionBool(true)); } - this->gui_app->get_tab(Preset::TYPE_PRINT)->load_config(new_print_config); - this->gui_app->get_tab(Preset::TYPE_PRINT)->update_dirty(); + this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->load_config(new_print_config); + this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->update_dirty(); plat->on_config_change(new_print_config); } diff --git a/src/slic3r/GUI/CalibrationTempDialog.cpp b/src/slic3r/GUI/CalibrationTempDialog.cpp index 3ff38e3eb..42b38f02e 100644 --- a/src/slic3r/GUI/CalibrationTempDialog.cpp +++ b/src/slic3r/GUI/CalibrationTempDialog.cpp @@ -65,8 +65,8 @@ void CalibrationTempDialog::create_geometry(wxCommandEvent& event_args) { Slic3r::resources_dir()+"/calibration/filament_temp/Smart_compact_temperature_calibration_item.amf"}, true, false, false); assert(objs_idx.size() == 1); - const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_PRINT)->get_config(); - const DynamicPrintConfig* filament_config = this->gui_app->get_tab(Preset::TYPE_FILAMENT)->get_config(); + const DynamicPrintConfig* print_config = this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->get_config(); + const DynamicPrintConfig* filament_config = this->gui_app->get_tab(Preset::TYPE_FFF_FILAMENT)->get_config(); const DynamicPrintConfig* printer_config = this->gui_app->get_tab(Preset::TYPE_PRINTER)->get_config(); // -- get temps @@ -168,12 +168,12 @@ void CalibrationTempDialog::create_geometry(wxCommandEvent& event_args) { //update plater GLCanvas3D::set_warning_freeze(false); - this->gui_app->get_tab(Preset::TYPE_PRINT)->load_config(new_print_config); + this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->load_config(new_print_config); plat->on_config_change(new_print_config); //this->gui_app->get_tab(Preset::TYPE_PRINTER)->load_config(new_printer_config); //plat->on_config_change(new_printer_config); plat->changed_objects(objs_idx); - this->gui_app->get_tab(Preset::TYPE_PRINT)->update_dirty(); + this->gui_app->get_tab(Preset::TYPE_FFF_PRINT)->update_dirty(); //this->gui_app->get_tab(Preset::TYPE_PRINTER)->update_dirty(); plat->is_preview_shown(); //update everything, easier to code. diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index ef6df2ada..bbfb310aa 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -433,7 +433,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) bool have_default_acceleration = config->option<ConfigOptionFloatOrPercent>("default_acceleration")->value > 0; for (auto el : { "perimeter_acceleration", "infill_acceleration", - "bridge_acceleration", "first_layer_acceleration" }) + "bridge_acceleration", "first_layer_acceleration", "travel_acceleration" }) toggle_field(el, have_default_acceleration); bool have_skirt = config->opt_int("skirts") > 0; @@ -483,7 +483,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) bool have_sequential_printing = config->opt_bool("complete_objects"); for (auto el : { /*"extruder_clearance_radius", "extruder_clearance_height",*/ "complete_objects_one_skirt", - "complete_objects_sort"}) + "complete_objects_sort", "complete_objects_one_brim"}) toggle_field(el, have_sequential_printing); bool have_ooze_prevention = config->opt_bool("ooze_prevention"); diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index dbc6fa725..1fab1b727 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -59,7 +59,11 @@ bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bu this->is_prusa_bundle = ais_prusa_bundle; std::string path_string = source_path.string(); - size_t presets_loaded = preset_bundle->load_configbundle(path_string, PresetBundle::LOAD_CFGBNDLE_SYSTEM); + // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air. + auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle( + path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable); + // No substitutions shall be reported when loading a system config bundle, no substitutions are allowed. + assert(config_substitutions.empty()); auto first_vendor = preset_bundle->vendors.begin(); if (first_vendor == preset_bundle->vendors.end()) { BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string; @@ -2358,7 +2362,7 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo return true; } -void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater) +bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater) { const auto enabled_vendors = appconfig_new.vendors(); @@ -2407,14 +2411,14 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese break; } - if (snapshot) { - SnapshotDB::singleton().take_snapshot(*app_config, snapshot_reason); - } + if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Continue with applying configuration changes?"))) + return false; if (install_bundles.size() > 0) { // Install bundles from resources. // Don't create snapshot - we've already done that above if applicable. - updater->install_bundles_rsrc(std::move(install_bundles), false); + if (! updater->install_bundles_rsrc(std::move(install_bundles), false)) + return false; } else { BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources"; } @@ -2490,7 +2494,11 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese } } - preset_bundle->load_presets(*app_config, preferred_model); + // Reloading the configs after some modifications were done to PrusaSlicer.ini. + // Just perform the substitutions silently, as the substitutions were already presented to the user on application start-up + // and the Wizard shall not create any new values that would require substitution. + // Throw on substitutions in system profiles, as the system profiles provided over the air should be compatible with this PrusaSlicer version. + preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem, preferred_model); if (page_custom->custom_wanted()) { page_firmware->apply_custom_config(*custom_config); @@ -2504,6 +2512,8 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese // Update the selections from the compatibilty. preset_bundle->export_selections(*app_config); + + return true; } @@ -2762,7 +2772,8 @@ bool ConfigWizard::run(RunReason reason, StartPage start_page) p->set_start_page(start_page); if (ShowModal() == wxID_OK) { - p->apply_config(app.app_config, app.preset_bundle, app.preset_updater); + if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater)) + return false; app.app_config->set_legacy_datadir(false); app.update_mode(); app.obj_manipul()->update_ui_from_settings(); diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index 3aba9af8c..79577264a 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -622,7 +622,7 @@ struct ConfigWizard::priv bool on_bnt_finish(); bool check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id = std::string()); - void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater); + bool apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater); // #ys_FIXME_alise void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add); diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index 27e2c1224..ad7fe5d0f 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -306,9 +306,11 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR // to avoid event propagation to other sidebar items c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { evt.StopPropagation(); +#ifdef __linux__ // FinishEditing grabs new selection and triggers config update. We better call // it explicitly, automatic update on KILL_FOCUS didn't work on Linux. this->FinishEditing(); +#endif }); return c_editor; diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index b0177cd0d..edb81c843 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -1185,6 +1185,10 @@ void Choice::set_value(const boost::any& value, bool change_event) } case coEnum: { int val = boost::any_cast<int>(value); + if (m_opt_id.compare("host_type") == 0 && val != 0 && + m_opt.enum_values.size() > field->GetCount()) // for case, when PrusaLink isn't used as a HostType + val--; + if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "solid_fill_pattern" || m_opt_id == "fill_pattern" || m_opt_id == "support_material_interface_pattern" || m_opt_id == "brim_ears_pattern") val = idx_from_enum_value<InfillPattern>(val); @@ -1223,6 +1227,8 @@ void Choice::set_value(const boost::any& value, bool change_event) val = idx_from_enum_value<WipeAlgo>(val); else if (m_opt_id.compare("output_format") == 0) val = idx_from_enum_value<OutputFormat>(val); + else if (m_opt_id.compare("config_compatibility") == 0) + val = idx_from_enum_value<ForwardCompatibilitySubstitutionRule>(val); field->SetSelection(val); break; } @@ -1279,7 +1285,7 @@ void Choice::set_values(const wxArrayString &values) auto ww = dynamic_cast<choice_ctrl*>(window); auto value = ww->GetValue(); ww->Clear(); - ww->Append(""); +// ww->Append(""); for (const auto &el : values) ww->Append(el); ww->SetValue(value); @@ -1301,7 +1307,10 @@ boost::any& Choice::get_value() if (m_opt.type == coEnum) { - int ret_enum = field->GetSelection(); + int ret_enum = field->GetSelection(); + if (m_opt_id.compare("host_type") == 0 && + m_opt.enum_values.size() > field->GetCount()) // for case, when PrusaLink isn't used as a HostType + ret_enum++; if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "solid_fill_pattern" || m_opt_id == "support_material_interface_pattern" || m_opt_id == "fill_pattern" || m_opt_id == "brim_ears_pattern") convert_to_enum_value<InfillPattern>(ret_enum); @@ -1340,6 +1349,8 @@ boost::any& Choice::get_value() convert_to_enum_value<WipeAlgo>(ret_enum); else if (m_opt_id.compare("output_format") == 0) convert_to_enum_value<OutputFormat>(ret_enum); + else if(m_opt_id.compare("config_compatibility") == 0) + convert_to_enum_value<ForwardCompatibilitySubstitutionRule>(ret_enum); } else if (m_opt.gui_type == "f_enum_open") { const int ret_enum = field->GetSelection(); diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp index 879e7fe34..1b4fafeac 100644 --- a/src/slic3r/GUI/FirmwareDialog.cpp +++ b/src/slic3r/GUI/FirmwareDialog.cpp @@ -65,6 +65,8 @@ enum { USB_PID_MMU_APP = 4, USB_PID_CW1_BOOT = 7, USB_PID_CW1_APP = 8, + USB_PID_CW1S_BOOT = 14, + USB_PID_CW1S_APP = 15, }; // This enum discriminates the kind of information in EVT_AVRDUDE, @@ -308,7 +310,7 @@ void FirmwareDialog::priv::update_flash_enabled() void FirmwareDialog::priv::load_hex_file(const wxString &path) { hex_file = HexFile(path.wx_str()); - const bool autodetect = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1; + const bool autodetect = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1 || hex_file.device == HexFile::DEV_CW1S; set_autodetect(autodetect); } @@ -636,6 +638,10 @@ void FirmwareDialog::priv::perform_upload() this->prepare_avr109(Avr109Pid(USB_PID_CW1_BOOT, USB_PID_CW1_APP)); break; + case HexFile::DEV_CW1S: + this->prepare_avr109(Avr109Pid(USB_PID_CW1S_BOOT, USB_PID_CW1S_APP)); + break; + default: this->prepare_mk2(); break; @@ -761,11 +767,10 @@ const char* FirmwareDialog::priv::avr109_dev_name(Avr109Pid usb_pid) { switch (usb_pid.boot) { case USB_PID_MMU_BOOT: return "Original Prusa MMU 2.0 Control"; - break; case USB_PID_CW1_BOOT: return "Original Prusa CW1"; - break; - + case USB_PID_CW1S_BOOT: + return "Original Prusa CW1S"; default: throw Slic3r::RuntimeError((boost::format("Invalid avr109 device USB PID: %1%") % usb_pid.boot).str()); } } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 495e0ddf7..3edd7a578 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -6571,7 +6571,7 @@ void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = m_pos(X); cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = m_pos(Y); cfg.opt<ConfigOptionFloat>("wipe_tower_rotation_angle", true)->value = (180./M_PI) * m_rotation; - wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); + wxGetApp().get_tab(Preset::TYPE_FFF_PRINT)->load_config(cfg); } diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 3ed8a7021..f810f3e2e 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -5,6 +5,7 @@ #include <string> #include <boost/algorithm/string.hpp> +#include <boost/algorithm/string/predicate.hpp> #include <boost/any.hpp> #if __APPLE__ @@ -18,6 +19,7 @@ #include "AboutDialog.hpp" #include "MsgDialog.hpp" +#include "format.hpp" #include "libslic3r/Print.hpp" @@ -273,6 +275,127 @@ void warning_catcher(wxWindow* parent, const wxString& message) msg.ShowModal(); } +static wxString bold(const wxString& str) +{ + return wxString::Format("<b>%s</b>", str); +}; + +static wxString bold_string(const wxString& str) +{ + return wxString::Format("<b>\"%s\"</b>", str); +}; + +static void add_config_substitutions(const ConfigSubstitutions& conf_substitutions, wxString& changes) +{ + changes += "<table>"; + for (const ConfigSubstitution& conf_substitution : conf_substitutions) { + wxString new_val; + const ConfigOptionDef* def = conf_substitution.opt_def; + if (!def) + continue; + switch (def->type) { + case coEnum: + { + const std::vector<std::string>& labels = def->enum_labels; + const std::vector<std::string>& values = def->enum_values; + int val = conf_substitution.new_value->getInt(); + + bool is_infill = def->opt_key == "top_fill_pattern" || + def->opt_key == "bottom_fill_pattern" || + def->opt_key == "fill_pattern"; + + // Each infill doesn't use all list of infill declared in PrintConfig.hpp. + // So we should "convert" val to the correct one + if (is_infill) { + for (const auto& key_val : *def->enum_keys_map) + if ((int)key_val.second == val) { + auto it = std::find(values.begin(), values.end(), key_val.first); + if (it == values.end()) + break; + auto idx = it - values.begin(); + new_val = wxString("\"") + values[idx] + "\"" + " (" + from_u8(_utf8(labels[idx])) + ")"; + break; + } + if (new_val.IsEmpty()) { + assert(false); + new_val = _L("Undefined"); + } + } + else + new_val = wxString("\"") + values[val] + "\"" + " (" + from_u8(_utf8(labels[val])) + ")"; + break; + } + case coBool: + new_val = conf_substitution.new_value->getBool() ? "true" : "false"; + break; + case coBools: + if (conf_substitution.new_value->nullable()) + for (const char v : static_cast<const ConfigOptionBoolsNullable*>(conf_substitution.new_value.get())->values) + new_val += std::string(v == ConfigOptionBoolsNullable::nil_value() ? "nil" : v ? "true" : "false") + ", "; + else + for (const char v : static_cast<const ConfigOptionBools*>(conf_substitution.new_value.get())->values) + new_val += std::string(v ? "true" : "false") + ", "; + if (! new_val.empty()) + new_val.erase(new_val.begin() + new_val.size() - 2, new_val.end()); + break; + default: + assert(false); + } + + changes += format_wxstr("<tr><td><b>\"%1%\" (%2%)</b></td><td>: ", def->opt_key, _(def->label)) + + format_wxstr(_L("%1% was substituted with %2%"), bold_string(conf_substitution.old_value), bold(new_val)) + + "</td></tr>"; + } + changes += "</table>"; +} + +static wxString substitution_message(const wxString& changes) +{ + return + _L("Most likely the configuration was produced by a newer version of " SLIC3R_APP_NAME " or PrusaSlicer.") + " " + + _L("The following values were substituted:") + "\n" + changes + "\n\n" + + _L("Review the substitutions and adjust them if needed."); +} + +void show_substitutions_info(const PresetsConfigSubstitutions& presets_config_substitutions) +{ + wxString changes; + + auto preset_type_name = [](Preset::Type type) { + switch (type) { + case Preset::TYPE_FFF_PRINT: return _L("Print settings"); + case Preset::TYPE_SLA_PRINT: return _L("SLA print settings"); + case Preset::TYPE_FFF_FILAMENT: return _L("Filament"); + case Preset::TYPE_SLA_MATERIAL: return _L("SLA material"); + case Preset::TYPE_PRINTER: return _L("Printer"); + case Preset::TYPE_PHYSICAL_PRINTER: return _L("Physical Printer"); + default: assert(false); return wxString(); + } + }; + + for (const PresetConfigSubstitutions& substitution : presets_config_substitutions) { + changes += "\n\n" + format_wxstr("%1% : %2%", preset_type_name(substitution.preset_type), bold_string(substitution.preset_name)); + if (!substitution.preset_file.empty()) + changes += format_wxstr(" (%1%)", substitution.preset_file); + + add_config_substitutions(substitution.substitutions, changes); + } + + InfoDialog msg(nullptr, _L("Configuration bundle was loaded, however some configuration values were not recognized."), substitution_message(changes)); + msg.ShowModal(); +} + +void show_substitutions_info(const ConfigSubstitutions& config_substitutions, const std::string& filename) +{ + wxString changes = "\n"; + add_config_substitutions(config_substitutions, changes); + + InfoDialog msg(nullptr, + format_wxstr(_L("Configuration file \"%1%\" was loaded, however some configuration values were not recognized."), from_u8(filename)), + substitution_message(changes)); + msg.ShowModal(); +} + void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items) { if (comboCtrl == nullptr) diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp index a90115933..f80d18344 100644 --- a/src/slic3r/GUI/GUI.hpp +++ b/src/slic3r/GUI/GUI.hpp @@ -7,6 +7,7 @@ namespace boost::filesystem { class path; } #include <wx/string.h> #include "libslic3r/Config.hpp" +#include "libslic3r/Preset.hpp" class wxWindow; class wxMenuBar; @@ -49,6 +50,8 @@ void show_info(wxWindow* parent, const wxString& message, const wxString& title void show_info(wxWindow* parent, const char* message, const char* title = nullptr); inline void show_info(wxWindow* parent, const std::string& message,const std::string& title = std::string()) { show_info(parent, message.c_str(), title.c_str()); } void warning_catcher(wxWindow* parent, const wxString& message); +void show_substitutions_info(const PresetsConfigSubstitutions& presets_config_substitutions); +void show_substitutions_info(const ConfigSubstitutions& config_substitutions, const std::string& filename); // Creates a wxCheckListBoxComboPopup inside the given wxComboCtrl, filled with the given text and items. // Items data must be separated by '|', and contain the item name to be shown followed by its initial value (0 for false, 1 for true). diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 625fb5197..63a062b5e 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -443,7 +443,9 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension) /* FT_TEX */ "Texture (*.png, *.svg)|*.png;*.PNG;*.svg;*.SVG", - /* FT_PNGZIP */ "Masked SLA files (*.sl1)|*.sl1;*.SL1", + /* FT_SL1 */ "Masked SLA files (*.sl1, *.sl1s)|*.sl1;*.SL1;*.sl1s;*.SL1S", + // Workaround for OSX file picker, for some reason it always saves with the 1st extension. + /* FT_SL1S */ "Masked SLA files (*.sl1s, *.sl1)|*.sl1s;*.SL1S;*.sl1;*.SL1", }; std::string out = defaults[file_type]; @@ -634,6 +636,9 @@ void GUI_App::post_init() this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str())); } else { + if (! this->init_params->preset_substitutions.empty()) + show_substitutions_info(this->init_params->preset_substitutions); + #if 0 // Load the cummulative config over the currently active profiles. //FIXME if multiple configs are loaded, only the last one will have an effect. @@ -653,6 +658,24 @@ void GUI_App::post_init() if (! this->init_params->extra_config.empty()) this->mainframe->load_config(this->init_params->extra_config); } + + // The extra CallAfter() is needed because of Mac, where this is the only way + // to popup a modal dialog on start without screwing combo boxes. + // This is ugly but I honestly found no better way to do it. + // Neither wxShowEvent nor wxWindowCreateEvent work reliably. + if (this->preset_updater) { + this->check_updates(false); + CallAfter([this] { + this->config_wizard_startup(); + this->preset_updater->slic3r_update_notify(); + this->preset_updater->sync(preset_bundle); + }); + } + +#ifdef _WIN32 + // Sets window property to mainframe so other instances can indentify it. + OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); +#endif //WIN32 } IMPLEMENT_APP(GUI_App) @@ -721,16 +744,21 @@ void GUI_App::init_app_config() // Mac : "~/Library/Application Support/Slic3r" if (data_dir().empty()) { - #ifndef __linux__ + //check if there is a "configuration" directory next to the resources + if (boost::filesystem::exists(boost::filesystem::path{ resources_dir() } / ".." / "configuration")) { + set_data_dir((boost::filesystem::path{ resources_dir() } / ".." / "configuration").string()); + } else { +#ifndef __linux__ set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data()); - #else +#else // Since version 2.3, config dir on Linux is in ${XDG_CONFIG_HOME}. // https://github.com/prusa3d/PrusaSlicer/issues/2911 wxString dir; - if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) + if (!wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty()) dir = wxFileName::GetHomeDir() + wxS("/.config"); set_data_dir((dir + "/" + GetAppName()).ToUTF8().data()); - #endif +#endif + } } if (!app_config) @@ -923,7 +951,10 @@ bool GUI_App::on_init_inner() // Suppress the '- default -' presets. preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1"); try { - preset_bundle->load_presets(*app_config); + // Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only. + // If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force + // installation of a compatible system preset, thus nullifying the system preset substitutions. + init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent); } catch (const std::exception &ex) { show_error(nullptr, ex.what()); } @@ -976,7 +1007,6 @@ bool GUI_App::on_init_inner() if (! plater_) return; - if (app_config->dirty() && app_config->get("autosave") == "1") app_config->save(); @@ -997,33 +1027,6 @@ bool GUI_App::on_init_inner() #endif this->post_init(); } - - // Preset updating & Configwizard are done after the above initializations, - // and after MainFrame is created & shown. - // The extra CallAfter() is needed because of Mac, where this is the only way - // to popup a modal dialog on start without screwing combo boxes. - // This is ugly but I honestly found no better way to do it. - // Neither wxShowEvent nor wxWindowCreateEvent work reliably. - - static bool once = true; - if (once) { - once = false; - - if (preset_updater != nullptr) { - check_updates(false); - - CallAfter([this] { - config_wizard_startup(); - preset_updater->slic3r_update_notify(); - preset_updater->sync(preset_bundle); - }); - } - -#ifdef _WIN32 - //sets window property to mainframe so other instances can indentify it - OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); -#endif //WIN32 - } }); m_initialized = true; @@ -1819,9 +1822,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu) child->SetFont(normal_font()); if (dlg.ShowModal() == wxID_OK) - app_config->set("on_snapshot", - Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot( - *app_config, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()).id); + if (const Config::Snapshot *snapshot = Config::take_config_snapshot_report_error( + *app_config, Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()); + snapshot != nullptr) + app_config->set("on_snapshot", snapshot->id); } break; case ConfigMenuSnapshots: @@ -1832,13 +1836,24 @@ void GUI_App::add_config_menu(wxMenuBar *menu) ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot); dlg.ShowModal(); if (!dlg.snapshot_to_activate().empty()) { - if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config)) - Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK); + if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config) && + ! Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK, "", + GUI::format(_L("Continue to activate a configuration snapshot %1%?"), dlg.snapshot_to_activate()))) + break; try { app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id); - preset_bundle->load_presets(*app_config); + // Enable substitutions, log both user and system substitutions. There should not be any substitutions performed when loading system + // presets because compatibility of profiles shall be verified using the min_slic3r_version keys in config index, but users + // are known to be creative and mess with the config files in various ways. + if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable); + ! all_substitutions.empty()) + show_substitutions_info(all_substitutions); + // Load the currently selected preset into the GUI, update the preset selection box. load_current_presets(); + + // update config wizard in respect to the new config + update_wizard_from_config(); } catch (std::exception &ex) { GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what())); } @@ -2015,6 +2030,17 @@ void GUI_App::load_current_presets(bool check_printer_presets_ /*= true*/) } } +void GUI_App::update_wizard_from_config() +{ + if (!m_wizard) + return; + // If ConfigWizard was created before changing of the configuration, + // we have to destroy it to have possibility to create it again in respect to the new config's parameters + m_wizard->Reparent(nullptr); + m_wizard->Destroy(); + m_wizard = nullptr; +} + bool GUI_App::OnExceptionInMainLoop() { generic_exception_handle(); @@ -2175,7 +2201,13 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage { wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); + if (reason == ConfigWizard::RR_USER) + if (PresetUpdater::UpdateResult result = preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD); + result == PresetUpdater::R_ALL_CANCELED) + return false; + if (! m_wizard) { + wxBusyCursor wait; m_wizard = new ConfigWizard(mainframe); } @@ -2334,7 +2366,7 @@ void GUI_App::check_updates(const bool verbose) { PresetUpdater::UpdateResult updater_result; try { - updater_result = preset_updater->config_update(app_config->orig_version(), verbose); + updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION); if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) { mainframe->Close(); } diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index ba764badb..3dfb8cc38 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -63,7 +63,9 @@ enum FileType FT_TEX, - FT_PNGZIP, + FT_SL1, + // Workaround for OSX file picker, for some reason it always saves with the 1st extension. + FT_SL1S, FT_SIZE, }; @@ -226,6 +228,7 @@ public: bool check_print_host_queue(); bool checked_tab(Tab* tab); void load_current_presets(bool check_printer_presets = true); + void update_wizard_from_config(); wxString current_language_code() const { return m_wxLocale->GetCanonicalName(); } // Translate the language code to a code, for which Prusa Research maintains translations. Defaults to "en_US". @@ -250,7 +253,7 @@ public: // Parameters extracted from the command line to be passed to GUI after initialization. - const GUI_InitParams* init_params { nullptr }; + GUI_InitParams* init_params { nullptr }; AppConfig* app_config{ nullptr }; PresetBundle* preset_bundle{ nullptr }; @@ -260,7 +263,7 @@ public: std::mutex not_modal_dialog_mutex; wxDialog* not_modal_dialog = nullptr; - PresetUpdater* get_preset_updater() { return preset_updater; } + PresetUpdater* get_preset_updater() { return preset_updater; } wxNotebook* tab_panel() const ; int extruders_cnt() const; diff --git a/src/slic3r/GUI/GUI_Init.cpp b/src/slic3r/GUI/GUI_Init.cpp index 413bc632d..313dbe201 100644 --- a/src/slic3r/GUI/GUI_Init.cpp +++ b/src/slic3r/GUI/GUI_Init.cpp @@ -50,39 +50,8 @@ int GUI_Run(GUI_InitParams ¶ms) // gui->autosave = m_config.opt_string("autosave"); GUI::GUI_App::SetInstance(gui); gui->init_params = ¶ms; -/* - gui->CallAfter([gui, this, &load_configs, params.start_as_gcodeviewer] { - if (!gui->initialized()) { - return; - } - if (params.start_as_gcodeviewer) { - if (!m_input_files.empty()) - gui->plater()->load_gcode(wxString::FromUTF8(m_input_files[0].c_str())); - } else { -#if 0 - // Load the cummulative config over the currently active profiles. - //FIXME if multiple configs are loaded, only the last one will have an effect. - // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). - // As of now only the full configs are supported here. - if (!m_print_config.empty()) - gui->mainframe->load_config(m_print_config); -#endif - if (!load_configs.empty()) - // Load the last config to give it a name at the UI. The name of the preset may be later - // changed by loading an AMF or 3MF. - //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. - gui->mainframe->load_config_file(load_configs.back()); - // If loading a 3MF file, the config is loaded from the last one. - if (!m_input_files.empty()) - gui->plater()->load_files(m_input_files, true, true); - if (!m_extra_config.empty()) - gui->mainframe->load_config(m_extra_config); - } - }); -*/ - int result = wxEntry(params.argc, params.argv); - return result; + return wxEntry(params.argc, params.argv); } catch (const Slic3r::Exception &ex) { boost::nowide::cerr << ex.what() << std::endl; wxMessageBox(boost::nowide::widen(ex.what()), wxString::Format(_L("%s GUI initialization failed"), SLIC3R_APP_NAME), wxICON_STOP); diff --git a/src/slic3r/GUI/GUI_Init.hpp b/src/slic3r/GUI/GUI_Init.hpp index c420c9554..2adf618a4 100644 --- a/src/slic3r/GUI/GUI_Init.hpp +++ b/src/slic3r/GUI/GUI_Init.hpp @@ -1,6 +1,7 @@ #ifndef slic3r_GUI_Init_hpp_ #define slic3r_GUI_Init_hpp_ +#include <libslic3r/Preset.hpp> #include <libslic3r/PrintConfig.hpp> namespace Slic3r { @@ -12,6 +13,9 @@ struct GUI_InitParams int argc; char **argv; + // Substitutions of unknown configuration values done during loading of user presets. + PresetsConfigSubstitutions preset_substitutions; + std::vector<std::string> load_configs; DynamicPrintConfig extra_config; std::vector<std::string> input_files; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 2559e6f87..670d88661 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -4176,9 +4176,9 @@ void ObjectList::change_part_type() } } - const wxString names[] = { _(L("Part")), _(L("Modifier")), _(L("Support Enforcer")), _(L("Support Blocker")) }; + const wxString names[] = { _L("Part"), _L("Modifier"), _L("Support Enforcer"), _L("Support Blocker"), _L("Seam Position") }; - auto new_type = ModelVolumeType(wxGetSingleChoiceIndex(_(L("Type:")), _(L("Select type of part")), wxArrayString(4, names), int(type))); + auto new_type = ModelVolumeType(wxGetSingleChoiceIndex(_(L("Type:")), _(L("Select type of part")), wxArrayString(5, names), int(type))); if (new_type == type || new_type == ModelVolumeType::INVALID) return; diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index d8f563da1..6b1a67908 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -69,17 +69,36 @@ void ArrangeJob::clear_input() m_unprintable.reserve(cunprint /* for optional wti */); } +void add_brim(arrangement::ArrangePolygon &ap, const ModelConfigObject &config, const Plater* plater) +{ + if (!plater->config()->option("complete_objects_one_brim")->getBool()) { + // object-brim increase the size of the object + // Should be using the "inflation" field but it's non-functional right now. + coord_t diff = scale_(plater->config()->option("brim_width")->getFloat() - plater->config()->option("extruder_clearance_radius")->getFloat() / 2); + if (config.option("brim_width")) + diff = scale_(config.option("brim_width")->getFloat() - plater->config()->option("extruder_clearance_radius")->getFloat() / 2); + if (diff > 0) { + ExPolygons brimmed = offset_ex(ap.poly, diff); + assert(brimmed.size() == 1); + ap.poly = brimmed[0]; + } + } +} + void ArrangeJob::prepare_all() { clear_input(); for (ModelObject *obj: m_plater->model().objects) for (ModelInstance *mi : obj->instances) { ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable; - cont.emplace_back(get_arrange_poly(PtrWrapper{mi}, m_plater)); + arrangement::ArrangePolygon &&ap = get_arrange_poly(PtrWrapper{ mi }, m_plater); + add_brim(ap, obj->config, m_plater); + cont.emplace_back(std::move(ap)); } - if (auto wti = get_wipe_tower_arrangepoly(*m_plater)) + if (auto wti = get_wipe_tower_arrangepoly(*m_plater)) { m_selected.emplace_back(std::move(*wti)); + } } void ArrangeJob::prepare_selected() { @@ -114,7 +133,8 @@ void ArrangeJob::prepare_selected() { (inst_sel[i] ? m_selected : m_unselected) : m_unprintable; - + + add_brim(ap, model.objects[oidx]->config, m_plater); cont.emplace_back(std::move(ap)); } } @@ -124,6 +144,7 @@ void ArrangeJob::prepare_selected() { auto &cont = m_plater->get_selection().is_wipe_tower() ? m_selected : m_unselected; + cont.emplace_back(std::move(ap)); } diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index f5e4c976f..a309bd0d3 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -33,7 +33,7 @@ public: m_filepicker = new wxFilePickerCtrl(this, wxID_ANY, from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")), - "SL1 archive files (*.sl1, *.zip)|*.sl1;*.SL1;*.zip;*.ZIP", + "SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP", wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST); szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER); @@ -113,13 +113,14 @@ public: Plater *plater; Sel sel = Sel::modelAndProfile; - - TriangleMesh mesh; - DynamicPrintConfig profile; - wxString path; - Vec2i32 win = {2, 2}; - std::string err; - + + TriangleMesh mesh; + DynamicPrintConfig profile; + wxString path; + Vec2i32 win = {2, 2}; + std::string err; + ConfigSubstitutions config_substitutions; + priv(Plater *plt): plater{plt} {} }; @@ -142,13 +143,13 @@ void SLAImportJob::process() try { switch (p->sel) { case Sel::modelAndProfile: - import_sla_archive(path, p->win, p->mesh, p->profile, progr); + p->config_substitutions = import_sla_archive(path, p->win, p->mesh, p->profile, progr); break; case Sel::modelOnly: - import_sla_archive(path, p->win, p->mesh, progr); + p->config_substitutions = import_sla_archive(path, p->win, p->mesh, progr); break; case Sel::profileOnly: - import_sla_archive(path, p->profile); + p->config_substitutions = import_sla_archive(path, p->profile); break; } @@ -181,6 +182,7 @@ void SLAImportJob::prepare() p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : path.ToUTF8(); p->sel = dlg.get_selection(); p->win = dlg.get_marchsq_windowsize(); + p->config_substitutions.clear(); } else { p->path = ""; } @@ -223,8 +225,11 @@ void SLAImportJob::finalize() bool is_centered = false; p->plater->sidebar().obj_list()->load_mesh_object(p->mesh, name, is_centered); } - + + if (! p->config_substitutions.empty()) + show_substitutions_info(p->config_substitutions, p->path.ToUTF8().data()); + reset(); } -}} +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 9ef9c43ce..1dac5e694 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -253,6 +253,61 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S } } +void MainFrame::update_icon() { + + // icons for ESettingsLayout::Hidden + wxImageList* img_list = nullptr; + int icon_size = 0; + try { + icon_size = atoi(wxGetApp().app_config->get("tab_icon_size").c_str()); + } + catch (std::exception e) {} + switch (m_layout) + { + case ESettingsLayout::Unknown: + { + break; + } case ESettingsLayout::Old: + case ESettingsLayout::Hidden: + { + if (m_tabpanel->GetPageCount() == 4 && icon_size >= 8) { + m_tabpanel->SetPageImage(0, 0); + m_tabpanel->SetPageImage(1, 3); + m_tabpanel->SetPageImage(2, m_plater->printer_technology() == PrinterTechnology::ptSLA ? 6 : 4); + m_tabpanel->SetPageImage(3, m_plater->printer_technology() == PrinterTechnology::ptSLA ? 7 : 5); + } + break; + } + case ESettingsLayout::Tabs: + { + if (icon_size >= 8) + { + m_tabpanel->SetPageImage(0, 0); + m_tabpanel->SetPageImage(1, 1); + m_tabpanel->SetPageImage(2, 2); + m_tabpanel->SetPageImage(3, 3); + m_tabpanel->SetPageImage(4, m_plater->printer_technology() == PrinterTechnology::ptSLA ? 6 : 4); + m_tabpanel->SetPageImage(5, m_plater->printer_technology() == PrinterTechnology::ptSLA ? 7 : 5); + } + break; + } + case ESettingsLayout::Dlg: + { + if (m_tabpanel->GetPageCount() == 4 && icon_size >= 8) { + m_tabpanel->SetPageImage(0, 3); + m_tabpanel->SetPageImage(1, m_plater->printer_technology() == PrinterTechnology::ptSLA ? 6 : 4); + m_tabpanel->SetPageImage(2, m_plater->printer_technology() == PrinterTechnology::ptSLA ? 7 : 5); + } + break; + } + case ESettingsLayout::GCodeViewer: + { + break; + } + } + +} + void MainFrame::update_layout() { auto restore_to_creation = [this]() { @@ -262,6 +317,8 @@ void MainFrame::update_layout() } }; + std::cout << "update_layout: " << m_tabpanel->GetPageCount() << "\n"; + // On Linux m_plater needs to be removed from m_tabpanel before to reparent it //clear if previous was old m_tabpanel_stop_event = true; @@ -360,6 +417,7 @@ void MainFrame::update_layout() // From the very beginning the Print settings should be selected m_last_selected_setting_tab = 0; m_last_selected_plater_tab = 999; + std::cout << "update_layout1: " << m_tabpanel->GetPageCount() << "\n"; // Set new settings switch (m_layout) @@ -373,29 +431,7 @@ void MainFrame::update_layout() m_plater->Reparent(m_tabpanel); m_tabpanel->InsertPage(0, m_plater, _L("Plater")); m_main_sizer->Add(m_tabpanel, 1, wxEXPAND); - // icons for ESettingsLayout::Old - wxImageList* img_list = nullptr; - int icon_size = 0; - try { - icon_size = atoi(wxGetApp().app_config->get("tab_icon_size").c_str()); - } - catch (std::exception e) {} - if (m_tabpanel->GetPageCount() == 4 && icon_size >= 8) { - std::initializer_list<std::string> icon_list = { "plater", "cog", "spool_cog", "printer_cog" }; - if (icon_size < 16) - icon_list = { "plater", "cog", "spool", "printer" }; - for (std::string icon_name : icon_list) { - const wxBitmap& bmp = create_scaled_bitmap(icon_name, this, icon_size); - if (img_list == nullptr) - img_list = new wxImageList(bmp.GetWidth(), bmp.GetHeight()); - img_list->Add(bmp); - } - m_tabpanel->AssignImageList(img_list); - m_tabpanel->SetPageImage(0, 0); - m_tabpanel->SetPageImage(1, 1); - m_tabpanel->SetPageImage(2, 2); - m_tabpanel->SetPageImage(3, 3); - } + update_icon(); // show m_plater->Show(); m_tabpanel->Show(); @@ -407,42 +443,15 @@ void MainFrame::update_layout() m_plater->enable_view_toolbar(false); bool need_freeze = !this->IsFrozen(); if(need_freeze) this->Freeze(); - // icons for ESettingsLayout::Tabs - wxImageList* img_list = nullptr; - int icon_size = 0; - try { - icon_size = atoi(wxGetApp().app_config->get("tab_icon_size").c_str()); - } - catch (std::exception e) {} - if (icon_size >= 8) { - std::initializer_list<std::string> icon_list = { "editor_menu", "layers", "preview_menu", "cog", "spool_cog", "printer_cog" }; - if (icon_size < 16) - icon_list = { "editor_menu", "layers", "preview_menu", "cog", "spool", "printer" }; - for (std::string icon_name : icon_list) { - const wxBitmap& bmp = create_scaled_bitmap(icon_name, this, icon_size); - if (img_list == nullptr) - img_list = new wxImageList(bmp.GetWidth(), bmp.GetHeight()); - img_list->Add(bmp); - } - } wxPanel* first_panel = new wxPanel(m_tabpanel); m_tabpanel->InsertPage(0, first_panel, _L("3D view")); m_tabpanel->InsertPage(1, new wxPanel(m_tabpanel), _L("Sliced preview")); m_tabpanel->InsertPage(2, new wxPanel(m_tabpanel), _L("Gcode preview")); if (m_tabpanel->GetPageCount() == 6) { - m_tabpanel->AssignImageList(img_list); m_tabpanel->GetPage(0)->SetSizer(new wxBoxSizer(wxVERTICAL)); m_tabpanel->GetPage(1)->SetSizer(new wxBoxSizer(wxVERTICAL)); m_tabpanel->GetPage(2)->SetSizer(new wxBoxSizer(wxVERTICAL)); - if (icon_size >= 8) - { - m_tabpanel->SetPageImage(0, 0); - m_tabpanel->SetPageImage(1, 1); - m_tabpanel->SetPageImage(2, 2); - m_tabpanel->SetPageImage(3, 3); - m_tabpanel->SetPageImage(4, 4); - m_tabpanel->SetPageImage(5, 5); - } + update_icon(); } m_plater->Reparent(first_panel); first_panel->GetSizer()->Add(m_plater, 1, wxEXPAND); @@ -460,6 +469,7 @@ void MainFrame::update_layout() m_main_sizer->Add(m_tabpanel, 1, wxEXPAND); m_plater_page = new wxPanel(m_tabpanel); m_tabpanel->InsertPage(0, m_plater_page, _L("Plater")); // empty panel just for Plater tab */ + update_icon(); m_plater->Show(); break; } @@ -468,6 +478,7 @@ void MainFrame::update_layout() m_main_sizer->Add(m_plater, 1, wxEXPAND); m_tabpanel->Reparent(&m_settings_dialog); m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND); + update_icon(); m_tabpanel->Show(); m_plater->Show(); break; @@ -602,6 +613,20 @@ void MainFrame::shutdown() wxGetApp().plater_ = nullptr; } +void MainFrame::change_tab(Tab* old_tab, Tab* new_tab) +{ + int page_id = m_tabpanel->FindPage(old_tab); + if (page_id >= 0 && page_id < m_tabpanel->GetPageCount()) { + m_tabpanel->GetPage(page_id)->Show(false); + m_tabpanel->RemovePage(page_id); + } + m_tabpanel->InsertPage(page_id, new_tab, new_tab->title()); + #ifdef __linux__ // the tabs apparently need to be explicitly shown on Linux (pull request #1563) + m_tabpanel->GetPage(page_id)->Show(true); + #endif // __linux__ + MainFrame::update_icon(); +} + void MainFrame::update_title() { wxString title = wxEmptyString; @@ -653,6 +678,25 @@ void MainFrame::init_tabpanel() m_tabpanel->Hide(); m_settings_dialog.set_tabpanel(m_tabpanel); + // icons for m_tabpanel tabs + wxImageList* img_list = nullptr; + int icon_size = 0; + try { + icon_size = atoi(wxGetApp().app_config->get("tab_icon_size").c_str()); + } + catch (std::exception e) {} + if (icon_size >= 8) { + std::initializer_list<std::string> icon_list = { "editor_menu", "layers", "preview_menu", "cog", "spool_cog", "printer_cog", "resin_cog", "sla_printer_cog" }; + if (icon_size < 16) + icon_list = { "editor_menu", "layers", "preview_menu", "cog", "spool", "printer", "resin", "sla_printer" }; + for (std::string icon_name : icon_list) { + const wxBitmap& bmp = create_scaled_bitmap(icon_name, this, icon_size); + if (img_list == nullptr) + img_list = new wxImageList(bmp.GetWidth(), bmp.GetHeight()); + img_list->Add(bmp); + } + } + m_tabpanel->AssignImageList(img_list); m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&) { if (m_tabpanel_stop_event) @@ -771,6 +815,7 @@ void MainFrame::init_tabpanel() if (wxGetApp().is_editor()) create_preset_tabs(); + std::cout << "create_preset_tabs: " << m_tabpanel->GetPageCount() << "\n"; if (m_plater) { // load initial config @@ -1224,7 +1269,7 @@ void MainFrame::init_menubar_as_editor() [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(true); }, "import_plater", nullptr, [this](){return m_plater != nullptr; }, this); - append_menu_item(import_menu, wxID_ANY, _L("Import SL1 archive") + dots, _L("Load an SL1 archive"), + append_menu_item(import_menu, wxID_ANY, _L("Import SL1 / SL1S archive") + dots, _L("Load an SL1 / Sl1S archive"), [this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr, [this](){return m_plater != nullptr; }, this); @@ -1581,6 +1626,7 @@ void MainFrame::update_menubar() m_changeable_menu_items[miPrinterTab] ->SetBitmap(create_scaled_bitmap(is_fff ? "printer" : "sla_printer")); } +#if 0 // To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG". void MainFrame::quick_slice(const int qs) { @@ -1703,6 +1749,7 @@ void MainFrame::quick_slice(const int qs) // }; // Slic3r::GUI::catch_error(this, []() { if (m_progress_dialog) m_progress_dialog->Destroy(); }); } +#endif void MainFrame::reslice_now() { @@ -1785,7 +1832,9 @@ void MainFrame::load_config_file() bool MainFrame::load_config_file(const std::string &path) { try { - wxGetApp().preset_bundle->load_config_file(path); + ConfigSubstitutions config_substitutions = wxGetApp().preset_bundle->load_config_file(path, ForwardCompatibilitySubstitutionRule::Enable); + if (!config_substitutions.empty()) + show_substitutions_info(config_substitutions, path); } catch (const std::exception& ex) { show_error(this, ex.what()); return false; @@ -1841,14 +1890,20 @@ void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool re wxGetApp().app_config->update_config_dir(get_dir_name(file)); - auto presets_imported = 0; + size_t presets_imported = 0; + PresetsConfigSubstitutions config_substitutions; try { - presets_imported = wxGetApp().preset_bundle->load_configbundle(file.ToUTF8().data()); + // Report all substitutions. + std::tie(config_substitutions, presets_imported) = wxGetApp().preset_bundle->load_configbundle( + file.ToUTF8().data(), PresetBundle::LoadConfigBundleAttribute::SaveImported, ForwardCompatibilitySubstitutionRule::Enable); } catch (const std::exception &ex) { show_error(this, ex.what()); return; } + if (! config_substitutions.empty()) + show_substitutions_info(config_substitutions); + // Load the currently selected preset into the GUI, update the preset selection box. wxGetApp().load_current_presets(); @@ -1900,11 +1955,11 @@ void MainFrame::select_tab(Tab* tab) return; ETabType tab_type = ETabType::LastSettings; switch (tab->type()) { - case Preset::Type::TYPE_FILAMENT: + case Preset::Type::TYPE_FFF_FILAMENT: case Preset::Type::TYPE_SLA_MATERIAL: tab_type = ETabType::FilamentSettings; break; - case Preset::Type::TYPE_PRINT: + case Preset::Type::TYPE_FFF_PRINT: case Preset::Type::TYPE_SLA_PRINT: tab_type = ETabType::PrintSettings; break; @@ -1964,7 +2019,7 @@ MainFrame::ETabType MainFrame::selected_tab() const return ETabType((uint8_t)ETabType::PrintSettings + m_tabpanel->GetSelection() - 1); } } else if (m_layout == ESettingsLayout::Dlg) { - if (!m_main_sizer->IsShown(m_tabpanel)) { + if (!m_settings_dialog.GetSizer()->IsShown(m_tabpanel)) { if (m_plater->is_view3D_shown()) { return ETabType::Plater3D; } else { diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 584f02d5e..167f86d72 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -41,11 +41,11 @@ enum QuickSlice qsExportPNG = 8 }; -struct PresetTab { - std::string name; - Tab* panel; - PrinterTechnology technology; -}; +//struct PresetTab { +// std::string name; +// Tab* panel; +// PrinterTechnology technology; +//}; // ---------------------------------------------------------------------------- // SettingsDialog @@ -89,6 +89,7 @@ class MainFrame : public DPIFrame void on_presets_changed(SimpleEvent&); void on_value_changed(wxCommandEvent&); + void update_icon(); bool can_start_new_project() const; bool can_save() const; @@ -173,6 +174,7 @@ public: void create_preset_tabs(); void add_created_tab(Tab* panel); bool is_active_and_shown_tab(Tab* tab); + void change_tab(Tab* old_tab, Tab* new_tab); // Register Win32 RawInput callbacks (3DConnexion) and removable media insert / remove callbacks. // Called from wxEVT_ACTIVATE, as wxEVT_CREATE was not reliable (bug in wxWidgets?). void register_win32_callbacks(); @@ -185,7 +187,7 @@ public: bool is_last_input_file() const { return !m_qs_last_input_file.IsEmpty(); } ESettingsLayout get_layout() const { return m_layout; } - void quick_slice(const int qs = qsUndef); +// void quick_slice(const int qs = qsUndef); void reslice_now(); void repair_stl(); void export_config(bool to_prusa = false); diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index 36abe4616..992e60f5d 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -58,7 +58,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he logo = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap); - topsizer->Add(logo, 0, wxALL, BORDER); + topsizer->Add(logo, 0, /*wxALL*/wxTOP | wxBOTTOM | wxLEFT, BORDER); topsizer->Add(rightsizer, 1, wxALL | wxEXPAND, BORDER); SetSizerAndFit(topsizer); @@ -107,5 +107,57 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, bool monospaced_ Fit(); } + +// InfoDialog + +InfoDialog::InfoDialog(wxWindow* parent, const wxString &title, const wxString& msg) + : MsgDialog(parent, wxString::Format(_L("%s information"), SLIC3R_APP_NAME), title) + , msg(msg) +{ + this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + + // Text shown as HTML, so that mouse selection and Ctrl-V to copy will work. + wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO); + { + wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + wxFont monospace = wxGetApp().code_font(); + wxColour text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); + wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); + auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); + const int font_size = font.GetPointSize() - 1; + int size[] = { font_size, font_size, font_size, font_size, font_size, font_size, font_size }; + html->SetFonts(font.GetFaceName(), monospace.GetFaceName(), size); + html->SetBorders(2); + + // calculate html page size from text + int lines = msg.Freq('\n'); + + if (msg.Contains("<tr>")) { + int pos = 0; + while (pos < (int)msg.Len() && pos != wxNOT_FOUND) { + pos = msg.find("<tr>", pos + 1); + lines+=2; + } + } + int page_height = std::min((font.GetPixelSize().y + 1) * lines, 68 * wxGetApp().em_unit()); + wxSize page_size(68 * wxGetApp().em_unit(), page_height); + + html->SetMinSize(page_size); + + std::string msg_escaped = xml_escape(msg.ToUTF8().data(), true); + boost::replace_all(msg_escaped, "\r\n", "<br>"); + boost::replace_all(msg_escaped, "\n", "<br>"); + html->SetPage("<html><body bgcolor=\"" + bgr_clr_str + "\"><font color=\"" + text_clr_str + "\">" + wxString::FromUTF8(msg_escaped.data()) + "</font></body></html>"); + content_sizer->Add(html, 1, wxEXPAND); + } + + // Set info bitmap + logo->SetBitmap(create_scaled_bitmap("info", this, 84)); + + Fit(); +} + + } } diff --git a/src/slic3r/GUI/MsgDialog.hpp b/src/slic3r/GUI/MsgDialog.hpp index 70032089b..85d524a44 100644 --- a/src/slic3r/GUI/MsgDialog.hpp +++ b/src/slic3r/GUI/MsgDialog.hpp @@ -66,6 +66,22 @@ private: }; +// Generic info dialog, used for displaying exceptions +class InfoDialog : public MsgDialog +{ +public: + InfoDialog(wxWindow *parent, const wxString &title, const wxString &msg); + InfoDialog(InfoDialog&&) = delete; + InfoDialog(const InfoDialog&) = delete; + InfoDialog&operator=(InfoDialog&&) = delete; + InfoDialog&operator=(const InfoDialog&) = delete; + virtual ~InfoDialog() = default; + +private: + wxString msg; +}; + + } } diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 20d5e4ea7..dfca32b7a 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -9,6 +9,7 @@ #include "libslic3r/AppConfig.hpp" #include "wxExtensions.hpp" +#include "libslic3r/Config.hpp" #include <boost/algorithm/string.hpp> #include <boost/log/trivial.hpp> @@ -36,7 +37,25 @@ wxDEFINE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClicke wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent); wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, PresetUpdateAvailableClickedEvent); -namespace Notifications_Internal{ +const NotificationManager::NotificationData NotificationManager::basic_notifications[] = { + // {NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10, _u8L("Slicing is not possible.")}, + // {NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0, _u8L("Exporting finished."), _u8L("Eject drive.") }, + {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") }, + // {NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5, _u8L("3D Mouse connected.") }, + // {NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20, _u8L("New Presets are available."), _u8L("See here.") }, + {NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more."), [](wxEvtHandler* evnthndlr){ + if (evnthndlr != nullptr) wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED)); return true; }}, + {NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr){ + wxLaunchDefaultBrowser("https://github.com/" SLIC3R_GITHUB "/releases"); return true; }}, + {NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotification, 10, + _u8L("You have just added a G-code for color change, but its value is empty.\n" + "To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") }, + //{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") }, + //{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20, _u8L("Loading of model has Failed") }, + //{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification +}; + +namespace { ImFont* add_default_font(float pixel_size) { ImGuiIO& io = ImGui::GetIO(); @@ -48,7 +67,7 @@ namespace Notifications_Internal{ return font; } - static inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity) + inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity) { if (fading_out) ImGui::PushStyleColor(idx, ImVec4(col.x, col.y, col.z, col.w * current_fade_opacity)); @@ -138,8 +157,8 @@ NotificationManager::PopNotification::PopNotification(const NotificationData &n, , m_last_remaining_time (n.duration) , m_counting_down (n.duration != 0) , m_text1 (n.text1) - , m_hypertext (n.hypertext) - , m_text2 (n.text2) + , m_hypertext (n.hypertext) + , m_text2 (n.text2) , m_evt_handler (evt_handler) , m_notification_start (GLCanvas3D::timestamp_now()) { @@ -187,26 +206,26 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init // color change based on fading out bool fading_pop = false; if (m_fading_out) { - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity); fading_pop = true; } // background color if (m_is_gray) { ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f); - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); } else if (m_data.level == NotificationLevel::ErrorNotification) { ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); backcolor.x += 0.3f; - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); } else if (m_data.level == NotificationLevel::WarningNotification) { ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); backcolor.x += 0.3f; backcolor.y += 0.15f; - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); } // name of window - probably indentifies window and is shown so last_end add whitespaces according to id @@ -304,23 +323,23 @@ NotificationManager::PopNotification::RenderResult NotificationManager::PopNotif if (m_fading_out) { if (!m_paused) m_current_fade_opacity -= 1.f / ((m_fading_time + 1.f) * 60.f); - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity); fading_pop = true; } // background color if (m_is_gray) { ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f); - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); } else if (m_data.level == NotificationLevel::ErrorNotification) { ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); backcolor.x += 0.3f; - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); } else if (m_data.level == NotificationLevel::WarningNotification) { ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); backcolor.x += 0.3f; backcolor.y += 0.15f; - Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity); } //name of window - probably indentifies window and is shown so last_end add whitespaces according to id @@ -414,7 +433,7 @@ void NotificationManager::PopNotification::init() if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - m_window_width_offset) { // more than one line till end int next_space = text.find_first_of(' ', last_end); - if (next_space > 0) { + if (next_space > 0 && next_space < text.length()) { int next_space_candidate = text.find_first_of(' ', next_space + 1); while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) { next_space = next_space_candidate; @@ -524,7 +543,7 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, orange_color.y += 0.2f; //text - Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity); ImGui::SetCursorPosX(text_x); ImGui::SetCursorPosY(text_y); imgui.text(text.c_str()); @@ -547,8 +566,8 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img orange_color.w = 0.8f; ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); @@ -647,9 +666,9 @@ void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); //button - if part if treggered @@ -852,8 +871,9 @@ void NotificationManager::ExportFinishedNotification::render_text(ImGuiWrapper& for (size_t i = 0; i < m_lines_count; i++) { if (m_text1.size() >= m_endlines[i]) { std::string line = m_text1.substr(last_end, m_endlines[i] - last_end); - if (i < m_lines_count - 1) - last_end = m_endlines[i] + (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); + last_end = m_endlines[i]; + if (m_text1.size() > m_endlines[i]) + last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); ImGui::SetCursorPosX(x_offset); ImGui::SetCursorPosY(starting_y + i * shift_y); imgui.text(line.c_str()); @@ -881,8 +901,8 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW orange_color.w = 0.8f; ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); - Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); std::string button_text; @@ -937,7 +957,7 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW } bool NotificationManager::ExportFinishedNotification::on_text_click() { - Notifications_Internal::open_folder(m_export_dir_path); + open_folder(m_export_dir_path); return false; } //------ProgressBar---------------- @@ -980,10 +1000,10 @@ NotificationManager::NotificationManager(wxEvtHandler* evt_handler) : } void NotificationManager::push_notification(const NotificationType type, int timestamp) { - auto it = std::find_if(basic_notifications.begin(), basic_notifications.end(), + auto it = std::find_if(std::begin(basic_notifications), std::end(basic_notifications), boost::bind(&NotificationData::type, boost::placeholders::_1) == type); - assert(it != basic_notifications.end()); - if (it != basic_notifications.end()) + assert(it != std::end(basic_notifications)); + if (it != std::end(basic_notifications)) push_notification_data(*it, timestamp); } void NotificationManager::push_notification(const std::string& text, int timestamp) diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index de7204038..ee13ae8da 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -167,7 +167,7 @@ private: // Callback for hypertext - returns true if notification should close after triggering // Usually sends event to UI thread thru wxEvtHandler. // Examples in basic_notifications. - std::function<bool(wxEvtHandler*)> callback { nullptr }; + std::function<bool(wxEvtHandler*)> callback; const std::string text2; }; @@ -231,7 +231,7 @@ private: //returns top in actual frame float get_current_top() const { return m_top_y; } const NotificationType get_type() const { return m_data.type; } - const NotificationData get_data() const { return m_data; } + const NotificationData& get_data() const { return m_data; } const bool is_gray() const { return m_is_gray; } // Call equals one second down void substract_remaining_time(int seconds) { m_remaining_time -= seconds; } @@ -383,10 +383,10 @@ private: ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage) : PopNotification(n, id_provider, evt_handler) { set_percentage(percentage); } void set_percentage(float percent) { m_percentage = percent; if (percent >= 1.0f) m_progress_complete = true; else m_progress_complete = false; } protected: - virtual void init(); - virtual void render_text(ImGuiWrapper& imgui, + void init(); + void render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y); + const float win_pos_x, const float win_pos_y) override; void render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); @@ -410,20 +410,20 @@ private: std::string m_export_dir_path; protected: // Reserves space on right for more buttons - virtual void count_spaces() override; - virtual void render_text(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y) override; + void count_spaces() override; + void render_text(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; // Renders also button to open directory with exported path and eject removable media - virtual void render_close_button(ImGuiWrapper& imgui, - const float win_size_x, const float win_size_y, - const float win_pos_x, const float win_pos_y) override; + void render_close_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; void render_eject_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); - virtual void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) override + void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) override { m_minimize_b_visible = false; } - virtual bool on_text_click() override; + bool on_text_click() override; // local time of last hover for showing tooltip long m_hover_time { 0 }; }; @@ -465,23 +465,7 @@ private: #endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT */ //prepared (basic) notifications - const std::vector<NotificationData> basic_notifications = { -// {NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10, _u8L("Slicing is not possible.")}, -// {NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0, _u8L("Exporting finished."), _u8L("Eject drive.") }, - {NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") }, -// {NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5, _u8L("3D Mouse connected.") }, -// {NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20, _u8L("New Presets are available."), _u8L("See here.") }, - {NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more."), [](wxEvtHandler* evnthndlr){ - if (evnthndlr != nullptr) wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED)); return true; }}, - {NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr){ - wxLaunchDefaultBrowser("https://github.com/" SLIC3R_GITHUB "/releases"); return true; }}, - {NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotification, 10, - _u8L("You have just added a G-code for color change, but its value is empty.\n" - "To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") }, - //{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") }, - //{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20, _u8L("Loading of model has Failed") }, - //{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification - }; + static const NotificationData basic_notifications[]; }; }//namespace GUI diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 260126cd1..7035f8f5c 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -977,6 +977,8 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config ret = static_cast<int>(config.option<ConfigOptionEnum<WipeAlgo>>(opt_key)->value); } else if (opt_key == "output_format") { ret = static_cast<int>(config.option<ConfigOptionEnum<OutputFormat>>(opt_key)->value); + } else if (opt_key == "config_compatibility") { + ret = static_cast<int>(config.option<ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>>(opt_key)->value); } } break; diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 57a8f7700..280b0a045 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -67,7 +67,8 @@ PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, const std::str // update Print Host upload from the selected preset m_parent->get_printer()->update_from_preset(*preset); // update values in parent (PhysicalPrinterDialog) - m_parent->update(); + m_parent->update(true); + } // update PrinterTechnology if it was changed @@ -153,7 +154,8 @@ void PresetForPrinter::msw_rescale() PhysicalPrinterDialog::PhysicalPrinterDialog(wxWindow* parent, wxString printer_name) : DPIDialog(parent, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), - m_printer("", wxGetApp().preset_bundle->physical_printers.default_config()) + m_printer("", wxGetApp().preset_bundle->physical_printers.default_config()), + had_all_mk3(!printer_name.empty()) { SetFont(wxGetApp().normal_font()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -488,7 +490,7 @@ void PhysicalPrinterDialog::update_printhost_buttons() m_printhost_browse_btn->Enable(host->has_auto_discovery()); } -void PhysicalPrinterDialog::update() +void PhysicalPrinterDialog::update(bool printer_change) { m_optgroup->reload_config(); @@ -496,19 +498,30 @@ void PhysicalPrinterDialog::update() // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) bool supports_multiple_printers = false; if (tech == ptFFF) { - m_optgroup->show_field("host_type"); - m_optgroup->hide_field("printhost_authorization_type"); - m_optgroup->show_field("printhost_apikey", true); - for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" }) - m_optgroup->hide_field(opt_key); + update_host_type(printer_change); const auto opt = m_config->option<ConfigOptionEnum<PrintHostType>>("host_type"); - supports_multiple_printers = opt && opt->value == htRepetier; + m_optgroup->show_field("host_type"); + if (opt && opt->value == htPrusaLink) + { + m_optgroup->show_field("printhost_authorization_type"); + AuthorizationType auth_type = m_config->option<ConfigOptionEnum<AuthorizationType>>("printhost_authorization_type")->value; + m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword); + for (const char* opt_key : { "printhost_user", "printhost_password" }) + m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword); + } else { + m_optgroup->hide_field("printhost_authorization_type"); + m_optgroup->show_field("printhost_apikey", true); + for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" }) + m_optgroup->hide_field(opt_key); + supports_multiple_printers = opt && opt->value == htRepetier; + } + // hide api key for klipper if (opt && opt->value == htKlipper) { m_optgroup->hide_field("printhost_apikey"); } - } + } else { m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false); m_optgroup->hide_field("host_type"); @@ -538,6 +551,58 @@ void PhysicalPrinterDialog::update() this->Layout(); } +void PhysicalPrinterDialog::update_host_type(bool printer_change) +{ + if (m_presets.empty()) + return; + bool all_presets_are_from_mk3_family = true; + + for (PresetForPrinter* prstft : m_presets) { + std::string preset_name = prstft->get_preset_name(); + if (Preset* preset = wxGetApp().preset_bundle->printers.find_preset(preset_name)) { + std::string model_id = preset->config.opt_string("printer_model"); + if (preset->vendor && preset->vendor->name == "Prusa Research") { + const std::vector<VendorProfile::PrinterModel>& models = preset->vendor->models; + auto it = std::find_if(models.begin(), models.end(), + [model_id](const VendorProfile::PrinterModel& model) { return model.id == model_id; }); + if (it != models.end() && it->family == "MK3") + continue; + } else if (!preset->vendor && model_id.rfind("MK3", 0) == 0) { + continue; + } + + } + all_presets_are_from_mk3_family = false; + break; + } + + Field* ht = m_optgroup->get_field("host_type"); + + wxArrayString types; + // Append localized enum_labels + assert(ht->m_opt.enum_labels.size() == ht->m_opt.enum_values.size()); + for (size_t i = 0; i < ht->m_opt.enum_labels.size(); i++) { + if (ht->m_opt.enum_values[i] == "prusalink" && !all_presets_are_from_mk3_family) + continue; + types.Add(_(ht->m_opt.enum_labels[i])); + } + + Choice* choice = dynamic_cast<Choice*>(ht); + choice->set_values(types); + auto set_to_choice_and_config = [this, choice](PrintHostType type) { + choice->set_value(static_cast<int>(type)); + m_config->set_key_value("host_type", new ConfigOptionEnum<PrintHostType>(type)); + }; + if ((printer_change && all_presets_are_from_mk3_family) || (!had_all_mk3 && all_presets_are_from_mk3_family)) + set_to_choice_and_config(htPrusaLink); + else if ((printer_change && !all_presets_are_from_mk3_family) || (!all_presets_are_from_mk3_family && m_config->option<ConfigOptionEnum<PrintHostType>>("host_type")->value == htPrusaLink)) + set_to_choice_and_config(htOctoPrint); + else + choice->set_value(m_config->option("host_type")->getInt()); + had_all_mk3 = all_presets_are_from_mk3_family; +} + + wxString PhysicalPrinterDialog::get_printer_name() { return m_printer_name->GetValue(); @@ -665,8 +730,9 @@ void PhysicalPrinterDialog::AddPreset(wxEvent& event) m_presets_sizer->Add(m_presets.back()->sizer(), 1, wxEXPAND | wxTOP, BORDER_W); update_full_printer_names(); - this->Fit(); + + update_host_type(true); } void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer) @@ -693,7 +759,8 @@ void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer) this->Layout(); this->Fit(); -} + update_host_type(true); +} }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.hpp b/src/slic3r/GUI/PhysicalPrinterDialog.hpp index 7ee1f7d92..cb9a48b3e 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.hpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.hpp @@ -85,7 +85,8 @@ public: PhysicalPrinterDialog(wxWindow* parent, wxString printer_name); ~PhysicalPrinterDialog(); - void update(); + void update(bool printer_change = false); + void update_host_type(bool printer_change); void update_printhost_buttons(); void update_printers(); wxString get_printer_name(); @@ -95,10 +96,11 @@ public: PrinterTechnology get_printer_technology(); void DeletePreset(PresetForPrinter* preset_for_printer); - protected: void on_dpi_changed(const wxRect& suggested_rect) override; void on_sys_color_changed() override {}; + + bool had_all_mk3; }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 26f930990..f51a4edc5 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -299,7 +299,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : m_og->hide_labels(); m_og->m_on_change = [config, this](t_config_option_key opt_key, boost::any value) { - Tab* tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT); + Tab* tab_print = wxGetApp().get_tab(Preset::TYPE_FFF_PRINT); if (!tab_print) return; if (opt_key == "fill_density") { @@ -692,8 +692,8 @@ Sidebar::Sidebar(Plater *parent) }; p->combos_filament.push_back(nullptr); - init_combo(&p->combo_print, _L("Print settings"), Preset::TYPE_PRINT, false); - init_combo(&p->combos_filament[0], _L("Filament"), Preset::TYPE_FILAMENT, true); + init_combo(&p->combo_print, _L("Print settings"), Preset::TYPE_FFF_PRINT, false); + init_combo(&p->combos_filament[0], _L("Filament"), Preset::TYPE_FFF_FILAMENT, true); init_combo(&p->combo_sla_print, _L("SLA print settings"), Preset::TYPE_SLA_PRINT, false); init_combo(&p->combo_sla_material, _L("SLA material"), Preset::TYPE_SLA_MATERIAL, false); init_combo(&p->combo_printer, _L("Printer"), Preset::TYPE_PRINTER, false); @@ -809,7 +809,7 @@ Sidebar::Sidebar(Plater *parent) Sidebar::~Sidebar() {} void Sidebar::init_filament_combo(PlaterPresetComboBox **combo, const int extr_idx) { - *combo = new PlaterPresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT); + *combo = new PlaterPresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FFF_FILAMENT); // # copy icons from first choice // $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets; @@ -865,7 +865,7 @@ void Sidebar::update_presets(Preset::Type preset_type) const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology(); switch (preset_type) { - case Preset::TYPE_FILAMENT: + case Preset::TYPE_FFF_FILAMENT: { const size_t extruder_cnt = print_tech != ptFFF ? 1 : dynamic_cast<ConfigOptionFloats*>(preset_bundle.printers.get_edited_preset().config.option("nozzle_diameter"))->values.size(); @@ -883,7 +883,7 @@ void Sidebar::update_presets(Preset::Type preset_type) break; } - case Preset::TYPE_PRINT: + case Preset::TYPE_FFF_PRINT: p->combo_print->update(); break; @@ -1978,6 +1978,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) "complete_objects", "complete_objects_sort", "complete_objects_one_skirt", + "complete_objects_one_brim", "duplicate_distance", "extruder_clearance_radius", "skirt_extrusion_width", "first_layer_extrusion_width", @@ -2398,7 +2399,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ DynamicPrintConfig config; { DynamicPrintConfig config_loaded; - model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, false, load_config); + ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::Enable }; + model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, &config_substitutions, only_if(load_config, Model::LoadAttribute::CheckVersion)); if (load_config && !config_loaded.empty()) { // Based on the printer technology field found in the loaded config, select the base for the config, PrinterTechnology printer_technology = Preset::printer_technology(config_loaded); @@ -2424,6 +2426,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ // and place the loaded config over the base. config += std::move(config_loaded); } + if (! config_substitutions.empty()) + show_substitutions_info(config_substitutions.substitutions, filename.string()); this->model.custom_gcode_per_print_z = model.custom_gcode_per_print_z; } @@ -2444,11 +2448,15 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ } } else { - model = Slic3r::Model::read_from_file(path.string(), nullptr, false, load_config); + model = Slic3r::Model::read_from_file(path.string(), nullptr, nullptr, only_if(load_config, Model::LoadAttribute::CheckVersion)); for (auto obj : model.objects) if (obj->name.empty()) obj->name = fs::path(obj->input_file).filename().string(); } + } catch (const ConfigurationError &e) { + std::string message = GUI::format(_L("Failed loading file \"%1%\" due to an invalid configuration."), filename.string()) + "\n\n" + e.what(); + GUI::show_error(q, message); + continue; } catch (const std::exception &e) { GUI::show_error(q, e.what()); continue; @@ -3324,7 +3332,7 @@ void Plater::priv::reload_from_disk() Model new_model; try { - new_model = Model::read_from_file(path, nullptr, true, false); + new_model = Model::read_from_file(path, nullptr, nullptr, Model::LoadAttribute::AddDefaultInstances); for (ModelObject* model_object : new_model.objects) { model_object->center_around_origin(); @@ -3610,13 +3618,13 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type, Preset::remove_suffix_modified(combo->GetString(selection).ToUTF8().data())); - if (preset_type == Preset::TYPE_FILAMENT) { + if (preset_type == Preset::TYPE_FFF_FILAMENT) { wxGetApp().preset_bundle->set_filament_preset(idx, preset_name); } bool select_preset = !combo->selection_is_changed_according_to_physical_printers(); // TODO: ? - if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) { + if (preset_type == Preset::TYPE_FFF_FILAMENT && sidebar->is_multifilament()) { // Only update the plater UI for the 2nd and other filaments. combo->update(); } @@ -4006,7 +4014,7 @@ void Plater::priv::on_wipetower_moved(Vec3dEvent &evt) DynamicPrintConfig cfg; cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = evt.data(0); cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = evt.data(1); - wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); + wxGetApp().get_tab(Preset::TYPE_FFF_PRINT)->load_config(cfg); } void Plater::priv::on_wipetower_rotated(Vec3dEvent& evt) @@ -4015,7 +4023,7 @@ void Plater::priv::on_wipetower_rotated(Vec3dEvent& evt) cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = evt.data(0); cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = evt.data(1); cfg.opt<ConfigOptionFloat>("wipe_tower_rotation_angle", true)->value = Geometry::rad2deg(evt.data(2)); - wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); + wxGetApp().get_tab(Preset::TYPE_FFF_PRINT)->load_config(cfg); } void Plater::priv::on_update_geometry(Vec3dsEvent<2>&) @@ -4725,7 +4733,9 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator // Switch to the other printer technology. Switch to the last printer active for that particular technology. AppConfig *app_config = wxGetApp().app_config; app_config->set("presets", "printer", (new_printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name); - wxGetApp().preset_bundle->load_presets(*app_config); + //FIXME Why are we reloading the whole preset bundle here? Please document. This is fishy and it is unnecessarily expensive. + // Anyways, don't report any config value substitutions, they have been already reported to the user at application start up. + wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent); // load_current_presets() calls Tab::load_current_preset() -> TabPrint::update() -> Object_list::update_and_show_object_settings_item(), // but the Object list still keeps pointer to the old Model. Avoid a crash by removing selection first. this->sidebar->obj_list()->unselect_objects(); @@ -4744,7 +4754,7 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator new_config.set_key_value("wipe_tower_x", new ConfigOptionFloat(model.wipe_tower.position.x())); new_config.set_key_value("wipe_tower_y", new ConfigOptionFloat(model.wipe_tower.position.y())); new_config.set_key_value("wipe_tower_rotation_angle", new ConfigOptionFloat(model.wipe_tower.rotation)); - Tab *tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT); + Tab *tab_print = wxGetApp().get_tab(Preset::TYPE_FFF_PRINT); tab_print->load_config(new_config); tab_print->update_dirty(); } @@ -5496,13 +5506,14 @@ void Plater::export_gcode(bool prefer_removable) fs::path output_path; { - wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _L("Save G-code file as:") : _L("Save SL1 file as:"), + std::string ext = default_output_file.extension().string(); + wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _L("Save G-code file as:") : _L("Save SL1 / SL1S file as:"), start_dir, from_path(default_output_file.filename()), - GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()), + GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : boost::iequals(ext, ".sl1s") ? FT_SL1S : FT_SL1, ext), wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); - if (dlg.ShowModal() == wxID_OK) + if (dlg.ShowModal() == wxID_OK) output_path = into_path(dlg.GetPath()); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 852121e80..d5ac676a9 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -78,12 +78,12 @@ PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const switch (m_type) { - case Preset::TYPE_PRINT: { + case Preset::TYPE_FFF_PRINT: { m_collection = &m_preset_bundle->prints; m_main_bitmap_name = "cog"; break; } - case Preset::TYPE_FILAMENT: { + case Preset::TYPE_FFF_FILAMENT: { m_collection = &m_preset_bundle->filaments; m_main_bitmap_name = "spool"; break; @@ -399,7 +399,7 @@ wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, con // Paint a red flag for incompatible presets. bmps.emplace_back(is_compatible ? bitmap_cache().mkclear(norm_icon_width, icon_height) : m_bitmapIncompatible.bmp()); - if (m_type == Preset::TYPE_FILAMENT && !filament_rgb.empty()) + if (m_type == Preset::TYPE_FFF_FILAMENT && !filament_rgb.empty()) { unsigned char rgb[3]; // Paint the color bars. @@ -604,7 +604,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset } }); - if (m_type == Preset::TYPE_FILAMENT) + if (m_type == Preset::TYPE_FFF_FILAMENT) { Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) { const Preset* selected_preset = m_collection->find_preset(m_preset_bundle->filament_presets[m_extruder_idx]); @@ -671,7 +671,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset /* In a case of a multi-material printing, for editing another Filament Preset * it's needed to select this preset for the "Filament settings" Tab */ - if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) + if (m_type == Preset::TYPE_FFF_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) { const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data(); @@ -703,9 +703,9 @@ bool PlaterPresetComboBox::switch_to_tab() wxGetApp().tab_panel()->SetSelection(page_id); // Switch to Settings NotePad - if(m_type == Preset::Type::TYPE_PRINT || m_type == Preset::Type::TYPE_SLA_PRINT) + if(m_type == Preset::Type::TYPE_FFF_PRINT || m_type == Preset::Type::TYPE_SLA_PRINT) wxGetApp().mainframe->select_tab(MainFrame::ETabType::PrintSettings); - else if (m_type == Preset::Type::TYPE_FILAMENT || m_type == Preset::Type::TYPE_SLA_MATERIAL) + else if (m_type == Preset::Type::TYPE_FFF_FILAMENT || m_type == Preset::Type::TYPE_SLA_MATERIAL) wxGetApp().mainframe->select_tab(MainFrame::ETabType::FilamentSettings); else if (m_type == Preset::Type::TYPE_PRINTER) wxGetApp().mainframe->select_tab(MainFrame::ETabType::PrinterSettings); @@ -763,7 +763,7 @@ void PlaterPresetComboBox::show_edit_menu() // If an incompatible preset is selected, it is shown as well. void PlaterPresetComboBox::update() { - if (m_type == Preset::TYPE_FILAMENT && + if (m_type == Preset::TYPE_FFF_FILAMENT && (m_preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA || m_preset_bundle->filament_presets.size() <= (size_t)m_extruder_idx) ) return; @@ -775,7 +775,7 @@ void PlaterPresetComboBox::update() const Preset* selected_filament_preset = nullptr; std::string extruder_color; - if (m_type == Preset::TYPE_FILAMENT) + if (m_type == Preset::TYPE_FFF_FILAMENT) { unsigned char rgb[3]; extruder_color = m_preset_bundle->printers.get_edited_preset().config.opt_string("extruder_colour", (unsigned int)m_extruder_idx); @@ -787,7 +787,7 @@ void PlaterPresetComboBox::update() } bool has_selection = m_collection->get_selected_idx() != size_t(-1); - const Preset* selected_preset = m_type == Preset::TYPE_FILAMENT ? selected_filament_preset : has_selection ? &m_collection->get_selected_preset() : nullptr; + const Preset* selected_preset = m_type == Preset::TYPE_FFF_FILAMENT ? selected_filament_preset : has_selection ? &m_collection->get_selected_preset() : nullptr; // Show wide icons if the currently selected preset is not compatible with the current printer, // and draw a red flag in front of the selected preset. bool wide_icons = selected_preset && !selected_preset->is_compatible; @@ -804,7 +804,7 @@ void PlaterPresetComboBox::update() for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) { const Preset& preset = presets[i]; - bool is_selected = m_type == Preset::TYPE_FILAMENT ? + bool is_selected = m_type == Preset::TYPE_FFF_FILAMENT ? m_preset_bundle->filament_presets[m_extruder_idx] == preset.name : // The case, when some physical printer is selected m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection() ? false : @@ -817,7 +817,7 @@ void PlaterPresetComboBox::update() std::string bitmap_type_name = bitmap_key = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; bool single_bar = false; - if (m_type == Preset::TYPE_FILAMENT) + if (m_type == Preset::TYPE_FFF_FILAMENT) { // Assign an extruder color to the selected item if the extruder color is defined. filament_rgb = is_selected ? selected_filament_preset->config.opt_string("filament_colour", 0) : @@ -883,11 +883,11 @@ void PlaterPresetComboBox::update() } } - if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_FILAMENT || m_type == Preset::TYPE_SLA_MATERIAL) { + if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_FFF_FILAMENT || m_type == Preset::TYPE_SLA_MATERIAL) { wxBitmap* bmp = get_bmp("edit_preset_list", wide_icons, "edit_uni"); assert(bmp); - if (m_type == Preset::TYPE_FILAMENT) + if (m_type == Preset::TYPE_FFF_FILAMENT) set_label_marker(Append(separator(L("Add/Remove filaments")), *bmp), LABEL_ITEM_WIZARD_FILAMENTS); else if (m_type == Preset::TYPE_SLA_MATERIAL) set_label_marker(Append(separator(L("Add/Remove materials")), *bmp), LABEL_ITEM_WIZARD_MATERIALS); @@ -901,9 +901,13 @@ void PlaterPresetComboBox::update() if (!tooltip.IsEmpty()) SetToolTip(tooltip); +#ifdef __WXMSW__ + // Use this part of code just on Windows to avoid of some layout issues on Linux + // see https://github.com/prusa3d/PrusaSlicer/issues/5163 and https://github.com/prusa3d/PrusaSlicer/issues/5505 // Update control min size after rescale (changed Display DPI under MSW) if (GetMinWidth() != 20 * m_em_unit) SetMinSize(wxSize(20 * m_em_unit, GetSize().GetHeight())); +#endif //__WXMSW__ } void PlaterPresetComboBox::msw_rescale() diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index fed47725d..459df570a 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -227,18 +227,26 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle return (speed_normal > 0.) ? speed_normal : speed_max; }; if (perimeter_extruder_active) { - double external_perimeter_rate = Flow::new_from_config_width(frExternalPerimeter, - first_positive(first_layer_extrusion_width_ptr, external_perimeter_extrusion_width, extrusion_width), - nozzle_diameter, lh, bfr).mm3_per_mm() * + Flow external_flow = Flow::new_from_config_width(frExternalPerimeter, + first_positive(first_layer_extrusion_width_ptr, external_perimeter_extrusion_width, extrusion_width), + nozzle_diameter, lh, bfr); + if (external_flow.height > external_flow.width) + external_flow.height = external_flow.width; + external_flow.spacing_ratio = print_config.opt<ConfigOptionPercent>("external_perimeter_overlap")->get_abs_value(1); + double external_perimeter_rate = external_flow.mm3_per_mm() * (bridging ? bridge_speed : limit_by_first_layer_speed(std::max(external_perimeter_speed, small_perimeter_speed), max_print_speed)); if (max_flow < external_perimeter_rate) { max_flow = external_perimeter_rate; max_flow_extrusion_type = _utf8(L("external perimeters")); } - double perimeter_rate = Flow::new_from_config_width(frPerimeter, - first_positive(first_layer_extrusion_width_ptr, perimeter_extrusion_width, extrusion_width), - nozzle_diameter, lh, bfr).mm3_per_mm() * + Flow perimeter_flow = Flow::new_from_config_width(frPerimeter, + first_positive(first_layer_extrusion_width_ptr, perimeter_extrusion_width, extrusion_width), + nozzle_diameter, lh, bfr); + if (perimeter_flow.height > perimeter_flow.width) + perimeter_flow.height = perimeter_flow.width; + perimeter_flow.spacing_ratio = print_config.opt<ConfigOptionPercent>("perimeter_overlap")->get_abs_value(1); + double perimeter_rate = perimeter_flow.mm3_per_mm() * (bridging ? bridge_speed : limit_by_first_layer_speed(std::max(perimeter_speed, small_perimeter_speed), max_print_speed)); if (max_flow < perimeter_rate) { @@ -247,27 +255,36 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle } } if (! bridging && infill_extruder_active) { - double infill_rate = Flow::new_from_config_width(frInfill, - first_positive(first_layer_extrusion_width_ptr, infill_extrusion_width, extrusion_width), - nozzle_diameter, lh, bfr).mm3_per_mm() * limit_infill_by_first_layer_speed(infill_speed, max_print_speed); + Flow infill_flow = Flow::new_from_config_width(frInfill, + first_positive(first_layer_extrusion_width_ptr, infill_extrusion_width, extrusion_width), + nozzle_diameter, lh, bfr); + if (infill_flow.height > infill_flow.width) + infill_flow.height = infill_flow.width; + double infill_rate = infill_flow.mm3_per_mm() * limit_infill_by_first_layer_speed(infill_speed, max_print_speed); if (max_flow < infill_rate) { max_flow = infill_rate; max_flow_extrusion_type = _utf8(L("infill")); } } if (solid_infill_extruder_active) { - double solid_infill_rate = Flow::new_from_config_width(frInfill, - first_positive(first_layer_extrusion_width_ptr, solid_infill_extrusion_width, extrusion_width), - nozzle_diameter, lh, 0).mm3_per_mm() * + Flow solid_infill_flow = Flow::new_from_config_width(frInfill, + first_positive(first_layer_extrusion_width_ptr, solid_infill_extrusion_width, extrusion_width), + nozzle_diameter, lh, 0); + if (solid_infill_flow.height > solid_infill_flow.width) + solid_infill_flow.height = solid_infill_flow.width; + double solid_infill_rate = solid_infill_flow.mm3_per_mm() * (bridging ? bridge_speed : limit_infill_by_first_layer_speed(solid_infill_speed, max_print_speed)); if (max_flow < solid_infill_rate) { max_flow = solid_infill_rate; max_flow_extrusion_type = _utf8(L("solid infill")); } if (! bridging) { - double top_solid_infill_rate = Flow::new_from_config_width(frInfill, - first_positive(first_layer_extrusion_width_ptr, top_infill_extrusion_width, extrusion_width), - nozzle_diameter, lh, bfr).mm3_per_mm() * limit_infill_by_first_layer_speed(top_solid_infill_speed, max_print_speed); + Flow top_solid_infill_flow = Flow::new_from_config_width(frInfill, + first_positive(first_layer_extrusion_width_ptr, top_infill_extrusion_width, extrusion_width), + nozzle_diameter, lh, bfr); + if (top_solid_infill_flow.height > top_solid_infill_flow.width) + top_solid_infill_flow.height = top_solid_infill_flow.width; + double top_solid_infill_rate = top_solid_infill_flow.mm3_per_mm() * limit_infill_by_first_layer_speed(top_solid_infill_speed, max_print_speed); if (max_flow < top_solid_infill_rate) { max_flow = top_solid_infill_rate; max_flow_extrusion_type = _utf8(L("top solid infill")); @@ -275,9 +292,12 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle } } if (support_material_extruder_active) { - double support_material_rate = Flow::new_from_config_width(frSupportMaterial, - first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width), - nozzle_diameter, lh, bfr).mm3_per_mm() * + Flow support_material_flow = Flow::new_from_config_width(frSupportMaterial, + first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width), + nozzle_diameter, lh, bfr); + if (support_material_flow.height > support_material_flow.width) + support_material_flow.height = support_material_flow.width; + double support_material_rate = support_material_flow.mm3_per_mm() * (bridging ? bridge_speed : limit_by_first_layer_speed(support_material_speed, max_print_speed)); if (max_flow < support_material_rate) { max_flow = support_material_rate; @@ -285,9 +305,12 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle } } if (support_material_interface_extruder_active) { - double support_material_interface_rate = Flow::new_from_config_width(frSupportMaterialInterface, + Flow support_material_interface_flow = Flow::new_from_config_width(frSupportMaterialInterface, first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width), - nozzle_diameter, lh, bfr).mm3_per_mm() * + nozzle_diameter, lh, bfr); + if (support_material_interface_flow.height > support_material_interface_flow.width) + support_material_interface_flow.height = support_material_interface_flow.width; + double support_material_interface_rate = support_material_interface_flow.mm3_per_mm() * (bridging ? bridge_speed : limit_by_first_layer_speed(support_material_interface_speed, max_print_speed)); if (max_flow < support_material_interface_rate) { max_flow = support_material_interface_rate; @@ -297,17 +320,19 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle //FIXME handle gap_fill_speed if (! out.empty()) out += "\n"; - out += (first_layer ? _utf8(L("First layer volumetric")) : (bridging ? _utf8(L("Bridging volumetric")) : _utf8(L("Volumetric")))); - out += " " + _utf8(L("flow rate is maximized")) + " "; bool limited_by_max_volumetric_speed = max_volumetric_speed > 0 && max_volumetric_speed < max_flow; - out += (limited_by_max_volumetric_speed ? - _utf8(L("by the print profile maximum")) : - (_utf8(L("when printing"))+ " " + max_flow_extrusion_type)) - + " " + _utf8(L("with a volumetric rate"))+ " "; + + std::string pattern = (boost::format(_u8L("%s flow rate is maximized ")) + % (first_layer ? _u8L("First layer volumetric") : (bridging ? _u8L("Bridging volumetric") : _u8L("Volumetric")))).str(); + std::string pattern2; if (limited_by_max_volumetric_speed) - max_flow = max_volumetric_speed; + pattern2 = (boost::format(_u8L("by the print profile maximum volumetric rate of %3.2f mm³/s at filament speed %3.2f mm/s.")) + % max_volumetric_speed % (max_volumetric_speed / filament_crossection)).str(); + else + pattern2 = (boost::format(_u8L("when printing %s with a volumetric rate of %3.2f mm³/s at filament speed %3.2f mm/s.")) + % max_flow_extrusion_type % max_flow % (max_flow / filament_crossection)).str(); - out += (boost::format(_utf8(L("%3.2f mm³/s at filament speed %3.2f mm/s."))) % max_flow % (max_flow / filament_crossection)).str(); + out += pattern + " " + pattern2; } return out; @@ -338,7 +363,17 @@ std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle& pre *print_config.opt<ConfigOptionFloatOrPercent>("perimeter_extrusion_width"), nozzle_diameter, layer_height, false); - //set spacing + // failsafe for too big height + if (external_perimeter_flow.height > external_perimeter_flow.width) + external_perimeter_flow.height = external_perimeter_flow.width; + if (perimeter_flow.height > perimeter_flow.width) + perimeter_flow.height = perimeter_flow.width; + if (external_perimeter_flow.height != perimeter_flow.height) { + perimeter_flow.height = std::min(perimeter_flow.height, external_perimeter_flow.height); + external_perimeter_flow.height = perimeter_flow.height; + } + + // set spacing external_perimeter_flow.spacing_ratio = print_config.opt<ConfigOptionPercent>("external_perimeter_overlap")->get_abs_value(1); perimeter_flow.spacing_ratio = print_config.opt<ConfigOptionPercent>("perimeter_overlap")->get_abs_value(1); diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index a3eadca15..e9ad959ae 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -31,10 +31,10 @@ namespace Search { static char marker_by_type(Preset::Type type, PrinterTechnology pt) { switch(type) { - case Preset::TYPE_PRINT: + case Preset::TYPE_FFF_PRINT: case Preset::TYPE_SLA_PRINT: return ImGui::PrintIconMarker; - case Preset::TYPE_FILAMENT: + case Preset::TYPE_FFF_FILAMENT: return ImGui::FilamentIconMarker; case Preset::TYPE_SLA_MATERIAL: return ImGui::MaterialIconMarker; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 32ac2a30c..6f1171977 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -119,7 +119,7 @@ Tab::Tab(wxNotebook* parent, const wxString& title, Preset::Type type) : m_compatible_printers.dialog_title = _L("Compatible printers"); m_compatible_printers.dialog_label = _L("Select the printers this profile is compatible with."); - m_compatible_prints.type = Preset::TYPE_PRINT; + m_compatible_prints.type = Preset::TYPE_FFF_PRINT; m_compatible_prints.key_list = "compatible_prints"; m_compatible_prints.key_condition = "compatible_prints_condition"; m_compatible_prints.dialog_title = _L("Compatible print profiles"); @@ -147,9 +147,9 @@ Tab::Tab(wxNotebook* parent, const wxString& title, Preset::Type type) : void Tab::set_type() { - if (m_name == "print") { m_type = Slic3r::Preset::TYPE_PRINT; } + if (m_name == "print") { m_type = Slic3r::Preset::TYPE_FFF_PRINT; } else if (m_name == "sla_print") { m_type = Slic3r::Preset::TYPE_SLA_PRINT; } - else if (m_name == "filament") { m_type = Slic3r::Preset::TYPE_FILAMENT; } + else if (m_name == "filament") { m_type = Slic3r::Preset::TYPE_FFF_FILAMENT; } else if (m_name == "sla_material") { m_type = Slic3r::Preset::TYPE_SLA_MATERIAL; } else if (m_name == "printer") { m_type = Slic3r::Preset::TYPE_PRINTER; } else { m_type = Slic3r::Preset::TYPE_INVALID; assert(false); } @@ -817,7 +817,7 @@ void Tab::update_changed_tree_ui() get_sys_and_mod_flags(opt_key, sys_page, modified_page); } } - if (m_type == Preset::TYPE_FILAMENT && page->title() == "Advanced") { + if (m_type == Preset::TYPE_FFF_FILAMENT && page->title() == "Advanced") { get_sys_and_mod_flags("filament_ramming_parameters", sys_page, modified_page); } if (page->title() == "Dependencies") { @@ -825,7 +825,7 @@ void Tab::update_changed_tree_ui() sys_page = m_presets->get_selected_preset_parent() != nullptr; modified_page = false; } else { - if (m_type == Slic3r::Preset::TYPE_FILAMENT || m_type == Slic3r::Preset::TYPE_SLA_MATERIAL) + if (m_type == Slic3r::Preset::TYPE_FFF_FILAMENT || m_type == Slic3r::Preset::TYPE_SLA_MATERIAL) get_sys_and_mod_flags("compatible_prints", sys_page, modified_page); get_sys_and_mod_flags("compatible_printers", sys_page, modified_page); } @@ -913,7 +913,7 @@ void Tab::on_roll_back_value(const bool to_sys /*= true*/) is_empty ? m_compatible_printers.btn->Disable() : m_compatible_printers.btn->Enable(); } // "compatible_prints" option exists only in Filament Settimgs and Materials Tabs - if ((m_type == Preset::TYPE_FILAMENT || m_type == Preset::TYPE_SLA_MATERIAL) && (m_options_list["compatible_prints"] & os) == 0) { + if ((m_type == Preset::TYPE_FFF_FILAMENT || m_type == Preset::TYPE_SLA_MATERIAL) && (m_options_list["compatible_prints"] & os) == 0) { to_sys ? group->back_to_sys_value("compatible_prints") : group->back_to_initial_value("compatible_prints"); load_key_value("compatible_prints", true/*some value*/, true); @@ -1186,6 +1186,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) } //wxGetApp().preset_bundle->value_changed(opt_key); + // update phony fields if (m_config->value_changed(opt_key, { wxGetApp().plater()->config() })) { update_dirty(); //# Initialize UI components with the config values. @@ -2979,24 +2980,30 @@ void TabPrinter::toggle_options() //z step checks { double z_step = m_config->opt_float("z_step"); + int64_t z_step_Mlong = (int64_t)(z_step * 1000000.); DynamicPrintConfig new_conf; bool has_changed = false; const std::vector<double>& min_layer_height = m_config->option<ConfigOptionFloats>("min_layer_height")->values; - for (int i = 0; i < min_layer_height.size(); i++) - if (min_layer_height[i] / z_step != 0) { - if(!has_changed ) + for (int i = 0; i < min_layer_height.size(); i++) { + if (min_layer_height[i] != 0 && (int64_t)(min_layer_height[i] * 1000000.) % z_step_Mlong != 0) { + if (!has_changed) new_conf = *m_config; new_conf.option<ConfigOptionFloats>("min_layer_height")->values[i] = std::max(z_step, Slic3r::check_z_step(new_conf.option<ConfigOptionFloats>("min_layer_height")->values[i], z_step)); has_changed = true; } - const std::vector<double>& max_layer_height = m_config->option<ConfigOptionFloats>("max_layer_height")->values; - for (int i = 0; i < max_layer_height.size(); i++) - if (max_layer_height[i] / z_step != 0) { + } + const std::vector<double>& nozzle_diameters = m_config->option<ConfigOptionFloats>("nozzle_diameter")->values; + std::vector<double> max_layer_height = m_config->option<ConfigOptionFloats>("max_layer_height")->values; + for (int i = 0; i < max_layer_height.size(); i++) { + if (max_layer_height[i] == 0) + max_layer_height[i] = nozzle_diameters[i] * 0.75; + if ((int64_t)(max_layer_height[i] * 1000000.) % z_step_Mlong != 0) { if (!has_changed) new_conf = *m_config; new_conf.option<ConfigOptionFloats>("max_layer_height")->values[i] = std::max(z_step, Slic3r::check_z_step(new_conf.option<ConfigOptionFloats>("max_layer_height")->values[i], z_step)); has_changed = true; } + } if (has_changed) { load_config(new_conf); } @@ -3071,7 +3078,8 @@ void Tab::load_current_preset() //merill note: this is a bit of anti-inheritance pattern if (m_type == Slic3r::Preset::TYPE_PRINTER) { const PrinterTechnology printer_technology = m_presets->get_edited_preset().printer_technology(); - if (printer_technology != static_cast<TabPrinter*>(this)->m_printer_technology) + const PrinterTechnology old_printer_technology = static_cast<TabPrinter*>(this)->m_printer_technology; + if (printer_technology != old_printer_technology) { // The change of the technology requires to remove some of unrelated Tabs // During this action, wxNoteBook::RemovePage invoke wxEVT_NOTEBOOK_PAGE_CHANGED @@ -3081,22 +3089,15 @@ void Tab::load_current_preset() Page* tmp_page = m_active_page; m_active_page = nullptr; for (auto tab : wxGetApp().tabs_list) { - if (tab->type() == Preset::TYPE_PRINTER) // Printer tab is shown every time + if (tab->type() == Preset::TYPE_PRINTER) // Printer tab shouln't be swapped continue; if (tab->supports_printer_technology(printer_technology)) { - wxGetApp().tab_panel()->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title()); - #ifdef __linux__ // the tabs apparently need to be explicitly shown on Linux (pull request #1563) - int page_id = wxGetApp().tab_panel()->FindPage(tab); - wxGetApp().tab_panel()->GetPage(page_id)->Show(true); - #endif // __linux__ - } - else { - int page_id = wxGetApp().tab_panel()->FindPage(tab); - //TODO shouldn't happen, emit an error here. - if (page_id >= 0 && page_id < wxGetApp().tab_panel()->GetPageCount()) { - wxGetApp().tab_panel()->GetPage(page_id)->Show(false); - wxGetApp().tab_panel()->RemovePage(page_id); + //search the other one to be replaced + for (auto tab_old : wxGetApp().tabs_list) { + if ((tab->type() & Preset::TYPE_TAB) == (tab_old->type() & Preset::TYPE_TAB) && tab_old->supports_printer_technology(old_printer_technology) ) { + wxGetApp().mainframe->change_tab(tab_old, tab); + } } } } @@ -3116,11 +3117,11 @@ void Tab::load_current_preset() } else { on_presets_changed(); - if (m_type == Preset::TYPE_SLA_PRINT || m_type == Preset::TYPE_PRINT) + if (m_type == Preset::TYPE_SLA_PRINT || m_type == Preset::TYPE_FFF_PRINT) update_frequently_changed_parameters(); //update width/spacing links - if (m_type == Preset::TYPE_PRINT) { + if (m_type == Preset::TYPE_FFF_PRINT) { //verify that spacings are set if (m_config && m_config->update_phony({ wxGetApp().plater()->config() })) { update_dirty(); @@ -3218,7 +3219,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, } assert(! delete_current || (m_presets->get_edited_preset().name != preset_name && m_presets->get_edited_preset().is_user())); bool current_dirty = ! delete_current && m_presets->current_is_dirty(); - bool print_tab = m_presets->type() == Preset::TYPE_PRINT || m_presets->type() == Preset::TYPE_SLA_PRINT; + bool print_tab = m_presets->type() == Preset::TYPE_FFF_PRINT || m_presets->type() == Preset::TYPE_SLA_PRINT; bool printer_tab = m_presets->type() == Preset::TYPE_PRINTER; bool canceled = false; bool technology_changed = false; @@ -3240,7 +3241,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, canceled = old_preset_dirty && ! new_preset_compatible && ! may_discard_current_dirty_preset(&dependent, preset_name); if (! canceled) { // The preset will be switched to a different, compatible preset, or the '-- default --'. - m_dependent_tabs.emplace_back((printer_technology == ptFFF) ? Preset::Type::TYPE_FILAMENT : Preset::Type::TYPE_SLA_MATERIAL); + m_dependent_tabs.emplace_back((printer_technology == ptFFF) ? Preset::Type::TYPE_FFF_FILAMENT : Preset::Type::TYPE_SLA_MATERIAL); if (old_preset_dirty && ! new_preset_compatible) dependent.discard_current_changes(); } @@ -3267,9 +3268,9 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, bool new_preset_compatible; }; std::vector<PresetUpdate> updates = { - { Preset::Type::TYPE_PRINT, &m_preset_bundle->prints, ptFFF }, + { Preset::Type::TYPE_FFF_PRINT, &m_preset_bundle->prints, ptFFF }, { Preset::Type::TYPE_SLA_PRINT, &m_preset_bundle->sla_prints, ptSLA }, - { Preset::Type::TYPE_FILAMENT, &m_preset_bundle->filaments, ptFFF }, + { Preset::Type::TYPE_FFF_FILAMENT, &m_preset_bundle->filaments, ptFFF }, { Preset::Type::TYPE_SLA_MATERIAL, &m_preset_bundle->sla_materials,ptSLA } }; for (PresetUpdate &pu : updates) { @@ -3339,8 +3340,8 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, }; if (current_dirty || delete_current || print_tab || printer_tab) m_preset_bundle->update_compatible( - update_compatible_type(technology_changed, print_tab, (print_tab ? this : wxGetApp().get_tab(Preset::TYPE_PRINT))->m_show_incompatible_presets), - update_compatible_type(technology_changed, false, wxGetApp().get_tab(Preset::TYPE_FILAMENT)->m_show_incompatible_presets)); + update_compatible_type(technology_changed, print_tab, (print_tab ? this : wxGetApp().get_tab(Preset::TYPE_FFF_PRINT))->m_show_incompatible_presets), + update_compatible_type(technology_changed, false, wxGetApp().get_tab(Preset::TYPE_FFF_FILAMENT)->m_show_incompatible_presets)); // Initialize the UI from the current preset. if (printer_tab) static_cast<TabPrinter*>(this)->update_pages(); @@ -3356,8 +3357,8 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, * to the corresponding printer_technology */ const PrinterTechnology printer_technology = m_presets->get_edited_preset().printer_technology(); - if (printer_technology == ptFFF && m_dependent_tabs.front() != Preset::Type::TYPE_PRINT) - m_dependent_tabs = { Preset::Type::TYPE_PRINT, Preset::Type::TYPE_FILAMENT }; + if (printer_technology == ptFFF && m_dependent_tabs.front() != Preset::Type::TYPE_FFF_PRINT) + m_dependent_tabs = { Preset::Type::TYPE_FFF_PRINT, Preset::Type::TYPE_FFF_FILAMENT }; else if (printer_technology == ptSLA && m_dependent_tabs.front() != Preset::Type::TYPE_SLA_PRINT) m_dependent_tabs = { Preset::Type::TYPE_SLA_PRINT, Preset::Type::TYPE_SLA_MATERIAL }; } @@ -3372,7 +3373,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, load_current_preset(); // apply duplicate_distance for print preset - if (m_type == Preset::TYPE_PRINT) { + if (m_type == Preset::TYPE_FFF_PRINT || m_type == Preset::TYPE_SLA_PRINT) { wxGetApp().mainframe->plater()->canvas3D()->set_arrange_settings(m_presets->get_edited_preset().config, m_presets->get_edited_preset().printer_technology()); } if (m_type == Preset::TYPE_PRINTER) { @@ -3411,7 +3412,7 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr // If filament preset is saved for multi-material printer preset, // there are cases when filament comboboxs are updated for old (non-modified) colors, // but in full_config a filament_colors option aren't. - if (presets->type() == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) + if (presets->type() == Preset::TYPE_FFF_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) wxGetApp().plater()->force_filament_colors_update(); } } @@ -3615,7 +3616,7 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) /* If filament preset is saved for multi-material printer preset, * there are cases when filament comboboxs are updated for old (non-modified) colors, * but in full_config a filament_colors option aren't.*/ - if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) + if (m_type == Preset::TYPE_FFF_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) wxGetApp().plater()->force_filament_colors_update(); { @@ -3623,15 +3624,15 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) // Update profile selection combo boxes at the depending tabs to reflect modifications in profile compatibility. std::vector<Preset::Type> dependent; switch (m_type) { - case Preset::TYPE_PRINT: - dependent = { Preset::TYPE_FILAMENT }; + case Preset::TYPE_FFF_PRINT: + dependent = { Preset::TYPE_FFF_FILAMENT }; break; case Preset::TYPE_SLA_PRINT: dependent = { Preset::TYPE_SLA_MATERIAL }; break; case Preset::TYPE_PRINTER: if (static_cast<const TabPrinter*>(this)->m_printer_technology == ptFFF) - dependent = { Preset::TYPE_PRINT, Preset::TYPE_FILAMENT }; + dependent = { Preset::TYPE_FFF_PRINT, Preset::TYPE_FFF_FILAMENT }; else dependent = { Preset::TYPE_SLA_PRINT, Preset::TYPE_SLA_MATERIAL }; break; diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 29d4699c3..fd541ead2 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -379,7 +379,7 @@ class TabPrint : public Tab public: TabPrint(wxNotebook* parent) : // Tab(parent, _(L("Print Settings")), L("print")) {} - Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_PRINT) {} + Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_FFF_PRINT) {} ~TabPrint() {} void build() override; @@ -411,7 +411,7 @@ protected: public: TabFilament(wxNotebook* parent) : // Tab(parent, _(L("Filament Settings")), L("filament")) {} - Tab(parent, _(L("Filament Settings")), Slic3r::Preset::TYPE_FILAMENT) {} + Tab(parent, _(L("Filament Settings")), Slic3r::Preset::TYPE_FFF_FILAMENT) {} ~TabFilament() {} void build() override; diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index fc1395545..f04d422f9 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -40,9 +40,9 @@ namespace GUI { // ---------------------------------------------------------------------------- static const std::map<Preset::Type, std::string> type_icon_names = { - {Preset::TYPE_PRINT, "cog" }, + {Preset::TYPE_FFF_PRINT, "cog" }, {Preset::TYPE_SLA_PRINT, "cog" }, - {Preset::TYPE_FILAMENT, "spool" }, + {Preset::TYPE_FFF_FILAMENT, "spool" }, {Preset::TYPE_SLA_MATERIAL, "resin" }, {Preset::TYPE_PRINTER, "printer" }, }; @@ -952,6 +952,8 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& return get_string_from_enum<InfillPattern>(opt_key, config); if (opt_key == "complete_objects_sort") return get_string_from_enum<CompleteObjectSort>(opt_key, config); + if (opt_key == "config_compatibility") + return get_string_from_enum<ForwardCompatibilitySubstitutionRule>(opt_key, config); if (opt_key == "display_orientation") return get_string_from_enum<SLADisplayOrientation>(opt_key, config); if (opt_key == "output_format") diff --git a/src/slic3r/GUI/UpdateDialogs.cpp b/src/slic3r/GUI/UpdateDialogs.cpp index 44e39990b..ad92e5ed1 100644 --- a/src/slic3r/GUI/UpdateDialogs.cpp +++ b/src/slic3r/GUI/UpdateDialogs.cpp @@ -85,8 +85,11 @@ bool MsgUpdateSlic3r::disable_version_check() const // MsgUpdateConfig -MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates) : - MsgDialog(nullptr, _(L("Configuration update")), _(L("Configuration update is available")), wxID_NONE) +MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_before_wizard/* = false*/) : + MsgDialog(nullptr, force_before_wizard ? _L("Opening Configuration Wizard") : _L("Configuration update"), + force_before_wizard ? wxString::Format(_L("%s is not using the newest configuration available.\n" + "Configuration Wizard may not offer the latest printers, filaments and SLA materials to be installed. "), SLIC3R_APP_NAME) : + _L("Configuration update is available"), wxID_NONE) { auto *text = new wxStaticText(this, wxID_ANY, _(L( "Would you like to install it?\n\n" @@ -130,11 +133,17 @@ MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates) : content_sizer->Add(versions); content_sizer->AddSpacer(2*VERT_SPACING); - auto *btn_cancel = new wxButton(this, wxID_CANCEL); - btn_sizer->Add(btn_cancel); - btn_sizer->AddSpacer(HORIZ_SPACING); - auto *btn_ok = new wxButton(this, wxID_OK); + auto* btn_ok = new wxButton(this, wxID_OK, force_before_wizard ? _L("Install") : "OK"); btn_sizer->Add(btn_ok); + btn_sizer->AddSpacer(HORIZ_SPACING); + if (force_before_wizard) { + auto* btn_no_install = new wxButton(this, wxID_ANY, _L("Don't install")); + btn_no_install->Bind(wxEVT_BUTTON, [this](wxEvent&) { this->EndModal(wxID_CLOSE); }); + btn_sizer->Add(btn_no_install); + btn_sizer->AddSpacer(HORIZ_SPACING); + } + auto* btn_cancel = new wxButton(this, wxID_CANCEL); + btn_sizer->Add(btn_cancel); btn_ok->SetFocus(); Fit(); diff --git a/src/slic3r/GUI/UpdateDialogs.hpp b/src/slic3r/GUI/UpdateDialogs.hpp index 6d355065a..aa3a10677 100644 --- a/src/slic3r/GUI/UpdateDialogs.hpp +++ b/src/slic3r/GUI/UpdateDialogs.hpp @@ -54,7 +54,8 @@ public: {} }; - MsgUpdateConfig(const std::vector<Update> &updates); + // force_before_wizard - indicates that check of updated is forced before ConfigWizard opening + MsgUpdateConfig(const std::vector<Update> &updates, bool force_before_wizard = false); MsgUpdateConfig(MsgUpdateConfig &&) = delete; MsgUpdateConfig(const MsgUpdateConfig &) = delete; MsgUpdateConfig &operator=(MsgUpdateConfig &&) = delete; diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index bcab6daaf..9df656701 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -377,7 +377,8 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) // PresetBundle bundle; on_progress(L("Loading repaired model"), 80); DynamicPrintConfig config; - bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &model, false); + ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::EnableSilent }; + bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), config, config_substitutions, &model, false); boost::filesystem::remove(path_dst); if (! loaded) throw Slic3r::RuntimeError(L("Import of the repaired 3mf file failed")); diff --git a/src/slic3r/Utils/HexFile.cpp b/src/slic3r/Utils/HexFile.cpp index 26596f629..a13fcab02 100644 --- a/src/slic3r/Utils/HexFile.cpp +++ b/src/slic3r/Utils/HexFile.cpp @@ -19,6 +19,7 @@ static HexFile::DeviceKind parse_device_kind(const std::string &str) else if (str == "mk3") { return HexFile::DEV_MK3; } else if (str == "mm-control") { return HexFile::DEV_MM_CONTROL; } else if (str == "cw1") { return HexFile::DEV_CW1; } + else if (str == "cw1s") { return HexFile::DEV_CW1S; } else { return HexFile::DEV_GENERIC; } } diff --git a/src/slic3r/Utils/HexFile.hpp b/src/slic3r/Utils/HexFile.hpp index 742ae00e6..b32d110ed 100644 --- a/src/slic3r/Utils/HexFile.hpp +++ b/src/slic3r/Utils/HexFile.hpp @@ -17,6 +17,7 @@ struct HexFile DEV_MK3, DEV_MM_CONTROL, DEV_CW1, + DEV_CW1S, }; boost::filesystem::path path; diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp index bb864c98d..f0d9982e4 100644 --- a/src/slic3r/Utils/OctoPrint.cpp +++ b/src/slic3r/Utils/OctoPrint.cpp @@ -178,7 +178,7 @@ const char* SL1Host::get_name() const { return "SL1Host"; } wxString SL1Host::get_test_ok_msg () const { - return wxString::Format(_L("Connection to %s works correctly."), "Prusa SL1"); + return wxString::Format(_L("Connection to %s works correctly."), "Prusa SL1 / SL1S"); } wxString SL1Host::get_test_failed_msg (wxString &msg) const @@ -209,4 +209,48 @@ void SL1Host::set_auth(Http &http) const } } +// PrusaLink +PrusaLink::PrusaLink(DynamicPrintConfig* config) : + OctoPrint(config), + authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value), + username(config->opt_string("printhost_user")), + password(config->opt_string("printhost_password")) +{ +} + +const char* PrusaLink::get_name() const { return "PrusaLink"; } + +wxString PrusaLink::get_test_ok_msg() const +{ + return _(L("Connection to PrusaLink works correctly.")); +} + +wxString PrusaLink::get_test_failed_msg(wxString& msg) const +{ + return GUI::from_u8((boost::format("%s: %s") + % _utf8(L("Could not connect to PrusaLink")) + % std::string(msg.ToUTF8())).str()); +} + +bool PrusaLink::validate_version_text(const boost::optional<std::string>& version_text) const +{ + return version_text ? (boost::starts_with(*version_text, "PrusaLink") || boost::starts_with(*version_text, "OctoPrint")) : false; +} + +void PrusaLink::set_auth(Http& http) const +{ + switch (authorization_type) { + case atKeyPassword: + http.header("X-Api-Key", get_apikey()); + break; + case atUserPassword: + http.auth_digest(username, password); + break; + } + + if (!get_cafile().empty()) { + http.ca_file(get_cafile()); + } +} + } diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp index c91dc5834..7c17f3793 100644 --- a/src/slic3r/Utils/OctoPrint.hpp +++ b/src/slic3r/Utils/OctoPrint.hpp @@ -70,6 +70,31 @@ private: std::string password; }; +class PrusaLink : public OctoPrint +{ +public: + PrusaLink(DynamicPrintConfig* config); + ~PrusaLink() override = default; + + const char* get_name() const override; + + wxString get_test_ok_msg() const override; + wxString get_test_failed_msg(wxString& msg) const override; + bool can_start_print() const override { return true; } + +protected: + bool validate_version_text(const boost::optional<std::string>& version_text) const override; + +private: + void set_auth(Http& http) const override; + + // Host authorization type. + AuthorizationType authorization_type; + // username and password for HTTP Digest Authentization (RFC RFC2617) + std::string username; + std::string password; +}; + } #endif diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 37c32da16..076504243 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -56,15 +56,18 @@ static const char *TMP_EXTENSION = ".download"; void copy_file_fix(const fs::path &source, const fs::path &target) { - static const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; // aka 644 - BOOST_LOG_TRIVIAL(debug) << format("PresetUpdater: Copying %1% -> %2%", source, target); - - // Make sure the file has correct permission both before and after we copy over it - if (fs::exists(target)) { - fs::permissions(target, perms); + std::string error_message; + CopyFileResult cfr = copy_file(source.string(), target.string(), error_message, false); + if (cfr != CopyFileResult::SUCCESS) { + BOOST_LOG_TRIVIAL(error) << "Copying failed(" << cfr << "): " << error_message; + throw Slic3r::CriticalException(GUI::format( + _L("Copying of file %1% to %2% failed: %3%"), + source, target, error_message)); } - fs::copy_file(source, target, fs::copy_option::overwrite_if_exists); + // Permissions should be copied from the source file by copy_file(). We are not sure about the source + // permissions, let's rewrite them with 644. + static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; fs::permissions(target, perms); } @@ -168,7 +171,7 @@ struct PresetUpdater::priv void check_install_indices() const; Updates get_config_updates(const Semver& old_slic3r_version) const; - void perform_updates(Updates &&updates, bool snapshot = true) const; + bool perform_updates(Updates &&updates, bool snapshot = true) const; void set_waiting_updates(Updates u); }; @@ -512,7 +515,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version } if (recommended->config_version < vp.config_version) { - BOOST_LOG_TRIVIAL(warning) << format("Recommended config version for the currently running " SLIC3R_APP_NAME " is older than the currently installed config for vendor %1%. This should not happen.", idx.vendor()); + BOOST_LOG_TRIVIAL(warning) << format("Recommended config version for the currently running %1% is older than the currently installed config for vendor %2%. This should not happen.", SLIC3R_APP_NAME, idx.vendor()); continue; } @@ -583,7 +586,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version found = true; } else { BOOST_LOG_TRIVIAL(warning) << format("The recommended config version for vendor `%1%` in resources does not match the recommended\n" - " config version for this version of " SLIC3R_APP_NAME ". Corrupted installation?", idx.vendor()); + " config version for this version of `%2%`. Corrupted installation?", idx.vendor(), SLIC3R_APP_NAME); } } } @@ -636,12 +639,14 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version return updates; } -void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const +bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const { if (updates.incompats.size() > 0) { if (snapshot) { BOOST_LOG_TRIVIAL(info) << "Taking a snapshot..."; - SnapshotDB::singleton().take_snapshot(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_DOWNGRADE); + if (! GUI::Config::take_config_snapshot_cancel_on_error(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_DOWNGRADE, "", + _u8L("Continue and install configuration updates?"))) + return false; } BOOST_LOG_TRIVIAL(info) << format("Deleting %1% incompatible bundles", updates.incompats.size()); @@ -656,7 +661,9 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons if (snapshot) { BOOST_LOG_TRIVIAL(info) << "Taking a snapshot..."; - SnapshotDB::singleton().take_snapshot(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_UPGRADE); + if (! GUI::Config::take_config_snapshot_cancel_on_error(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_UPGRADE, "", + _u8L("Continue and install configuration updates?"))) + return false; } BOOST_LOG_TRIVIAL(info) << format("Performing %1% updates", updates.updates.size()); @@ -667,7 +674,8 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons update.install(); PresetBundle bundle; - bundle.load_configbundle(update.source.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM); + // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air. + bundle.load_configbundle(update.source.string(), PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable); BOOST_LOG_TRIVIAL(info) << format("Deleting %1% conflicting presets", bundle.prints.size() + bundle.filaments.size() + bundle.printers.size()); @@ -699,6 +707,8 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); } } } + + return true; } void PresetUpdater::priv::set_waiting_updates(Updates u) @@ -765,7 +775,20 @@ void PresetUpdater::slic3r_update_notify() } } -PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, bool no_notification) const +static void reload_configs_update_gui() +{ + // Reload global configuration + auto* app_config = GUI::wxGetApp().app_config; + // System profiles should not trigger any substitutions, user profiles may trigger substitutions, but these substitutions + // were already presented to the user on application start up. Just do substitutions now and keep quiet about it. + // However throw on substitutions in system profiles, those shall never happen with system profiles installed over the air. + GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem); + GUI::wxGetApp().load_current_presets(); + GUI::wxGetApp().plater()->set_bed_shape(); + GUI::wxGetApp().update_wizard_from_config(); +} + +PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, UpdateParams params) const { if (! p->enabled_config_update) { return R_NOOP; } @@ -799,11 +822,9 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3 // This effectively removes the incompatible bundles: // (snapshot is taken beforehand) - p->perform_updates(std::move(updates)); - - if (!GUI::wxGetApp().run_wizard(GUI::ConfigWizard::RR_DATA_INCOMPAT)) { + if (! p->perform_updates(std::move(updates)) || + ! GUI::wxGetApp().run_wizard(GUI::ConfigWizard::RR_DATA_INCOMPAT)) return R_INCOMPAT_EXIT; - } return R_INCOMPAT_CONFIGURED; } @@ -822,7 +843,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3 } //forced update - if(incompatible_version) + if (incompatible_version) { BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. At least one requires higher version of Slicer.", updates.updates.size()); @@ -837,14 +858,9 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3 const auto res = dlg.ShowModal(); if (res == wxID_OK) { BOOST_LOG_TRIVIAL(info) << "User wants to update..."; - - p->perform_updates(std::move(updates)); - - // Reload global configuration - auto* app_config = GUI::wxGetApp().app_config; - GUI::wxGetApp().preset_bundle->load_presets(*app_config); - GUI::wxGetApp().load_current_presets(); - GUI::wxGetApp().plater()->set_bed_shape(); + if (! p->perform_updates(std::move(updates))) + return R_INCOMPAT_EXIT; + reload_configs_update_gui(); return R_UPDATE_INSTALLED; } else { @@ -854,35 +870,35 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3 } // regular update - if (no_notification) { + if (params == UpdateParams::SHOW_NOTIFICATION) { + p->set_waiting_updates(updates); + GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAvailable); + } + else { BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size()); - std::vector<GUI::MsgUpdateConfig::Update> updates_msg; + std::vector<GUI::MsgUpdateConfig::Update> updates_msg; for (const auto& update : updates.updates) { - std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string(); - updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url)); - } + std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string(); + updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url)); + } - GUI::MsgUpdateConfig dlg(updates_msg); + GUI::MsgUpdateConfig dlg(updates_msg, params == UpdateParams::FORCED_BEFORE_WIZARD); - const auto res = dlg.ShowModal(); - if (res == wxID_OK) { - BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; - p->perform_updates(std::move(updates)); - - // Reload global configuration - auto* app_config = GUI::wxGetApp().app_config; - GUI::wxGetApp().preset_bundle->load_presets(*app_config); - GUI::wxGetApp().load_current_presets(); - return R_UPDATE_INSTALLED; + const auto res = dlg.ShowModal(); + if (res == wxID_OK) { + BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; + if (! p->perform_updates(std::move(updates))) + return R_ALL_CANCELED; + reload_configs_update_gui(); + return R_UPDATE_INSTALLED; } else { - BOOST_LOG_TRIVIAL(info) << "User refused the update"; - return R_UPDATE_REJECT; - } - } else { - p->set_waiting_updates(updates); - GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAvailable); + BOOST_LOG_TRIVIAL(info) << "User refused the update"; + if (params == UpdateParams::FORCED_BEFORE_WIZARD && res == wxID_CANCEL) + return R_ALL_CANCELED; + return R_UPDATE_REJECT; + } } // MsgUpdateConfig will show after the notificaation is clicked @@ -893,7 +909,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3 return R_NOOP; } -void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot) const +bool PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot) const { Updates updates; @@ -905,7 +921,7 @@ void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); } - p->perform_updates(std::move(updates), snapshot); + return p->perform_updates(std::move(updates), snapshot); } void PresetUpdater::on_update_notification_confirm() @@ -925,20 +941,14 @@ void PresetUpdater::on_update_notification_confirm() const auto res = dlg.ShowModal(); if (res == wxID_OK) { BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; - p->perform_updates(std::move(p->waiting_updates)); - - // Reload global configuration - auto* app_config = GUI::wxGetApp().app_config; - GUI::wxGetApp().preset_bundle->load_presets(*app_config); - GUI::wxGetApp().load_current_presets(); - p->has_waiting_updates = false; - //return R_UPDATE_INSTALLED; + if (p->perform_updates(std::move(p->waiting_updates))) { + reload_configs_update_gui(); + p->has_waiting_updates = false; + } } else { BOOST_LOG_TRIVIAL(info) << "User refused the update"; - //return R_UPDATE_REJECT; - } - + } } } diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp index 0ca363c61..d7eeb5604 100644 --- a/src/slic3r/Utils/PresetUpdater.hpp +++ b/src/slic3r/Utils/PresetUpdater.hpp @@ -35,18 +35,24 @@ public: R_INCOMPAT_CONFIGURED, R_UPDATE_INSTALLED, R_UPDATE_REJECT, - R_UPDATE_NOTIFICATION + R_UPDATE_NOTIFICATION, + R_ALL_CANCELED + }; + + enum class UpdateParams { + SHOW_TEXT_BOX, // force modal textbox + SHOW_NOTIFICATION, // only shows notification + FORCED_BEFORE_WIZARD // indicates that check of updated is forced before ConfigWizard opening }; // If updating is enabled, check if updates are available in cache, if so, ask about installation. // A false return value implies Slic3r should exit due to incompatibility of configuration. // Providing old slic3r version upgrade profiles on upgrade of an application even in case // that the config index installed from the Internet is equal to the index contained in the installation package. - // no_notification = force modal textbox, otherwise some cases only shows notification - UpdateResult config_update(const Semver &old_slic3r_version, bool no_notification) const; + UpdateResult config_update(const Semver &old_slic3r_version, UpdateParams params) const; // "Update" a list of bundles from resources (behaves like an online update). - void install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const; + bool install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const; void on_update_notification_confirm(); private: diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index 455494277..52ec8bbfe 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -60,6 +60,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) const auto host_type = opt != nullptr ? opt->value : htOctoPrint; switch (host_type) { + case htPrusaLink: return new PrusaLink(config); case htOctoPrint: return new OctoPrint(config); case htDuet: return new Duet(config); case htFlashAir: return new FlashAir(config); diff --git a/tests/data/cpp/test_data.cpp b/tests/data/cpp/test_data.cpp index e09976571..830aa1a5f 100644 --- a/tests/data/cpp/test_data.cpp +++ b/tests/data/cpp/test_data.cpp @@ -203,14 +203,14 @@ void init_print(std::initializer_list<TriangleMesh> input_meshes, Slic3r::Print void init_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, Slic3r::Model &model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments) { Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); - config.set_deserialize(config_items); + config.set_deserialize_strict(config_items); init_print(meshes, print, model, config, comments); } void init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, Slic3r::Model &model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments) { Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); - config.set_deserialize(config_items); + config.set_deserialize_strict(config_items); init_print(meshes, print, model, config, comments); } diff --git a/tests/fff_print/test_flow.cpp b/tests/fff_print/test_flow.cpp index c9052a668..2ba1c7de5 100644 --- a/tests/fff_print/test_flow.cpp +++ b/tests/fff_print/test_flow.cpp @@ -19,7 +19,7 @@ SCENARIO("Extrusion width specifics", "[Flow]") { GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer and a 20mm cube mesh") { // this is a sharedptr DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); - config.set_deserialize({ + config.set_deserialize_strict({ { "brim_width", 2 }, { "skirts", 1 }, { "perimeters", 3 }, diff --git a/tests/fff_print/test_gcodewriter.cpp b/tests/fff_print/test_gcodewriter.cpp index 438a0283b..1594058f2 100644 --- a/tests/fff_print/test_gcodewriter.cpp +++ b/tests/fff_print/test_gcodewriter.cpp @@ -10,7 +10,7 @@ SCENARIO("lift() is not ignored after unlift() at normal values of Z", "[GCodeWr GIVEN("A config from a file and a single extruder.") { GCodeWriter writer; GCodeConfig &config = writer.config; - config.load(std::string(TEST_DATA_DIR) + "/fff_print_tests/test_gcodewriter/config_lift_unlift.ini"); + config.load(std::string(TEST_DATA_DIR) + "/fff_print_tests/test_gcodewriter/config_lift_unlift.ini", ForwardCompatibilitySubstitutionRule::Disable); std::vector<uint16_t> extruder_ids {0}; writer.set_extruders(extruder_ids); diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index e2891efaa..9009cd97c 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -62,7 +62,7 @@ SCENARIO("Print: Skirt generation", "[Print]") { SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces to become internal.", "[Print]") { GIVEN("sliced 20mm cube and config with top_solid_surfaces = 2 and bottom_solid_surfaces = 1") { Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); - config.set_deserialize({ + config.set_deserialize_strict({ { "top_solid_layers", 2 }, { "bottom_solid_layers", 1 }, { "layer_height", 0.5 }, // get a known number of layers diff --git a/tests/fff_print/test_printgcode.cpp b/tests/fff_print/test_printgcode.cpp index 10d3af9a3..87bbea88d 100644 --- a/tests/fff_print/test_printgcode.cpp +++ b/tests/fff_print/test_printgcode.cpp @@ -224,7 +224,7 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode]") { { DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); config.set_num_extruders(4); - config.set_deserialize({ + config.set_deserialize_strict({ { "start_gcode", "; Extruder [current_extruder]" }, { "infill_extruder", 2 }, { "solid_infill_extruder", 2 }, diff --git a/tests/fff_print/test_skirt_brim.cpp b/tests/fff_print/test_skirt_brim.cpp index 097f72dcc..8f508f323 100644 --- a/tests/fff_print/test_skirt_brim.cpp +++ b/tests/fff_print/test_skirt_brim.cpp @@ -31,7 +31,7 @@ static int get_brim_tool(const std::string &gcode) TEST_CASE("Skirt height is honored", "[Skirt]") { DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); - config.set_deserialize({ + config.set_deserialize_strict({ { "skirts", 1 }, { "skirt_height", 5 }, { "perimeters", 0 }, @@ -64,7 +64,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { GIVEN("A default configuration") { DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); config.set_num_extruders(4); - config.set_deserialize({ + config.set_deserialize_strict({ { "support_material_speed", 99 }, { "first_layer_height", 0.3 }, { "gcode_comments", true }, @@ -78,7 +78,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { }); WHEN("Brim width is set to 5") { - config.set_deserialize({ + config.set_deserialize_strict({ { "perimeters", 0 }, { "skirts", 0 }, { "brim_width", 5 } @@ -100,7 +100,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { } WHEN("Skirt area is smaller than the brim") { - config.set_deserialize({ + config.set_deserialize_strict({ { "skirts", 1 }, { "brim_width", 10} }); @@ -110,7 +110,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { } WHEN("Skirt height is 0 and skirts > 0") { - config.set_deserialize({ + config.set_deserialize_strict({ { "skirts", 2 }, { "skirt_height", 0 } }); @@ -123,7 +123,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { // This is a real error! One shall print the brim with the external perimeter extruder! WHEN("Perimeter extruder = 2 and support extruders = 3") { THEN("Brim is printed with the extruder used for the perimeters of first object") { - config.set_deserialize({ + config.set_deserialize_strict({ { "skirts", 0 }, { "brim_width", 5 }, { "perimeter_extruder", 2 }, @@ -137,7 +137,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { } WHEN("Perimeter extruder = 2, support extruders = 3, raft is enabled") { THEN("brim is printed with same extruder as skirt") { - config.set_deserialize({ + config.set_deserialize_strict({ { "skirts", 0 }, { "brim_width", 5 }, { "perimeter_extruder", 2 }, @@ -153,7 +153,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { #endif WHEN("brim width to 1 with layer_width of 0.5") { - config.set_deserialize({ + config.set_deserialize_strict({ { "skirts", 0 }, { "first_layer_extrusion_width", 0.5 }, { "brim_width", 1 } @@ -167,7 +167,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { #if 0 WHEN("brim ears on a square") { - config.set_deserialize({ + config.set_deserialize_strict({ { "skirts", 0 }, { "first_layer_extrusion_width", 0.5 }, { "brim_width", 1 }, @@ -182,7 +182,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { } WHEN("brim ears on a square but with a too small max angle") { - config.set_deserialize({ + config.set_deserialize_strict({ { "skirts", 0 }, { "first_layer_extrusion_width", 0.5 }, { "brim_width", 1 }, @@ -198,7 +198,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { #endif WHEN("Object is plated with overhang support and a brim") { - config.set_deserialize({ + config.set_deserialize_strict({ { "layer_height", 0.4 }, { "first_layer_height", 0.4 }, { "skirts", 1 }, diff --git a/tests/libslic3r/test_3mf.cpp b/tests/libslic3r/test_3mf.cpp index fb41ef93b..035d47b11 100644 --- a/tests/libslic3r/test_3mf.cpp +++ b/tests/libslic3r/test_3mf.cpp @@ -14,7 +14,8 @@ SCENARIO("Reading 3mf file", "[3mf]") { WHEN("3mf model is read") { std::string path = std::string(TEST_DATA_DIR) + "/test_3mf/Geräte/Büchse.3mf"; DynamicPrintConfig config; - bool ret = load_3mf(path.c_str(), &config, &model, false); + ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; + bool ret = load_3mf(path.c_str(), config, ctxt, &model, false); THEN("load should succeed") { REQUIRE(ret); } @@ -56,7 +57,10 @@ SCENARIO("Export+Import geometry to/from 3mf file cycle", "[3mf]") { // load back the model from the 3mf file Model dst_model; DynamicPrintConfig dst_config; - load_3mf(test_file.c_str(), &dst_config, &dst_model, false); + { + ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; + load_3mf(test_file.c_str(), dst_config, ctxt, &dst_model, false); + } boost::filesystem::remove(test_file); // compare meshes diff --git a/tests/libslic3r/test_config.cpp b/tests/libslic3r/test_config.cpp index 9ffc45b61..ddfeecd80 100644 --- a/tests/libslic3r/test_config.cpp +++ b/tests/libslic3r/test_config.cpp @@ -12,7 +12,7 @@ SCENARIO("Generic config validation performs as expected.", "[Config]") { GIVEN("A config generated from default options") { Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); WHEN( "perimeter_extrusion_width is set to 250%, a valid value") { - config.set_deserialize("perimeter_extrusion_width", "250%"); + config.set_deserialize_strict("perimeter_extrusion_width", "250%"); THEN( "The config is read as valid.") { REQUIRE(config.validate().empty()); } @@ -43,7 +43,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") { } } WHEN("A boolean option is set to a string value representing a 0 or 1") { - CHECK_NOTHROW(config.set_deserialize("gcode_comments", "1")); + CHECK_NOTHROW(config.set_deserialize_strict("gcode_comments", "1")); THEN("The underlying value is set correctly.") { REQUIRE(config.opt<ConfigOptionBool>("gcode_comments")->getBool() == true); } @@ -62,7 +62,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") { } } WHEN("A numeric option is set from serialized string") { - config.set_deserialize("bed_temperature", "100"); + config.set_deserialize_strict("bed_temperature", "100"); THEN("The underlying value is set correctly.") { REQUIRE(config.opt<ConfigOptionInts>("bed_temperature")->get_at(0) == 100); } @@ -95,7 +95,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") { } WHEN("A numeric option is set to a non-numeric value.") { THEN("A BadOptionTypeException exception is thown.") { - REQUIRE_THROWS_AS(config.set_deserialize("perimeter_speed", "zzzz"), BadOptionTypeException); + REQUIRE_THROWS_AS(config.set_deserialize_strict("perimeter_speed", "zzzz"), BadOptionValueException); } THEN("The value does not change.") { REQUIRE(config.opt<ConfigOptionFloat>("perimeter_speed")->getFloat() == 60.0); @@ -120,7 +120,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") { } } WHEN("A float or percent is set as a percent through the string interface.") { - config.set_deserialize("first_layer_extrusion_width", "100%"); + config.set_deserialize_strict("first_layer_extrusion_width", "100%"); THEN("Value and percent flag are 100/true") { auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width"); REQUIRE(tmp->percent == true); @@ -128,7 +128,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") { } } WHEN("A float or percent is set as a float through the string interface.") { - config.set_deserialize("first_layer_extrusion_width", "100"); + config.set_deserialize_strict("first_layer_extrusion_width", "100"); THEN("Value and percent flag are 100/false") { auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width"); REQUIRE(tmp->percent == false); @@ -198,7 +198,7 @@ SCENARIO("Config ini load/save interface", "[Config]") { WHEN("new_from_ini is called") { Slic3r::DynamicPrintConfig config; std::string path = std::string(TEST_DATA_DIR) + "/test_config/new_from_ini.ini"; - config.load_from_ini(path); + config.load_from_ini(path, ForwardCompatibilitySubstitutionRule::Disable); THEN("Config object contains ini file options.") { REQUIRE(config.option_throw<ConfigOptionStrings>("filament_colour", false)->values.size() == 1); REQUIRE(config.option_throw<ConfigOptionStrings>("filament_colour", false)->values.front() == "#ABCD"); diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp index 3dd8acda7..3035ddb75 100644 --- a/tests/libslic3r/test_placeholder_parser.cpp +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -10,7 +10,7 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { auto config = DynamicPrintConfig::full_print_config(); // To test the "first_layer_extrusion_width" over "nozzle_diameter" chain. - config.set_deserialize( { + config.set_deserialize_strict( { { "printer_notes", " PRINTER_VENDOR_PRUSA3D PRINTER_MODEL_MK2 " }, { "nozzle_diameter", "0.6;0.6;0.6;0.6" }, { "temperature", "357;359;363;378" } diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index aec6ceb6a..20288243e 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -282,7 +282,7 @@ bool ConfigBase__set(ConfigBase* THIS, const t_config_option_key &opt_key, SV* v break; } default: - if (! opt->deserialize(std::string(SvPV_nolen(value)))) + if (! opt->deserialize(std::string(SvPV_nolen(value)), ForwardCompatibilitySubstitutionRule::Disable)) return false; } return true; @@ -295,7 +295,8 @@ bool ConfigBase__set_deserialize(ConfigBase* THIS, const t_config_option_key &op size_t len; const char * c = SvPV(str, len); std::string value(c, len); - return THIS->set_deserialize_nothrow(opt_key, value); + ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; + return THIS->set_deserialize_nothrow(opt_key, value, ctxt); } void ConfigBase__set_ifndef(ConfigBase* THIS, const t_config_option_key &opt_key, SV* value, bool deserialize) diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index b8f996797..d1c9bfa0c 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -55,7 +55,7 @@ %code%{ auto config = new DynamicPrintConfig(); try { - config->load(path); + config->load(path, ForwardCompatibilitySubstitutionRule::Disable); RETVAL = config; } catch (std::exception& e) { delete config; @@ -119,7 +119,7 @@ %code%{ auto config = new FullPrintConfig(); try { - config->load(path); + config->load(path, ForwardCompatibilitySubstitutionRule::Disable); RETVAL = static_cast<GCodeConfig*>(config); } catch (std::exception& e) { delete config; diff --git a/xs/xsp/Flow.xsp b/xs/xsp/Flow.xsp index b57df5e37..2f8a8f2f8 100644 --- a/xs/xsp/Flow.xsp +++ b/xs/xsp/Flow.xsp @@ -41,7 +41,7 @@ _new_from_width(CLASS, role, width, nozzle_diameter, height, bridge_flow_ratio) float bridge_flow_ratio; CODE: ConfigOptionFloatOrPercent optwidth; - optwidth.deserialize(width); + optwidth.deserialize(width, ForwardCompatibilitySubstitutionRule::Disable); RETVAL = new Flow(Flow::new_from_config_width(role, optwidth, nozzle_diameter, height, bridge_flow_ratio)); OUTPUT: RETVAL diff --git a/xs/xsp/Layer.xsp b/xs/xsp/Layer.xsp index fdcc26eb6..5d006e676 100644 --- a/xs/xsp/Layer.xsp +++ b/xs/xsp/Layer.xsp @@ -18,8 +18,6 @@ %code%{ RETVAL = &THIS->thin_fills; %}; Ref<SurfaceCollection> fill_surfaces() %code%{ RETVAL = &THIS->fill_surfaces; %}; - Polygons bridged() - %code%{ RETVAL = THIS->bridged; %}; Ref<ExtrusionEntityCollection> perimeters() %code%{ RETVAL = &THIS->perimeters; %}; Ref<ExtrusionEntityCollection> fills() diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 93067ebe3..e7a171efd 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -22,7 +22,7 @@ %name{read_from_file} Model(std::string input_file, bool add_default_instances = true) %code%{ try { - RETVAL = new Model(Model::read_from_file(input_file, nullptr, add_default_instances)); + RETVAL = new Model(Model::read_from_file(input_file, nullptr, nullptr, only_if(add_default_instances, Model::LoadAttribute::AddDefaultInstances))); } catch (std::exception& e) { croak("Error while opening %s: %s\n", input_file.c_str(), e.what()); } |