diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp index f64605a9c..24f979267 100644 --- a/src/libslic3r/GCodeReader.hpp +++ b/src/libslic3r/GCodeReader.hpp @@ -29,6 +29,8 @@ public: float value(Axis axis) const { return m_axis[axis]; } bool has(char axis) const; bool has_value(char axis, float &value) const; + float new_X(const GCodeReader &reader) const { return this->has(X) ? this->x() : reader.x(); } + float new_Y(const GCodeReader &reader) const { return this->has(Y) ? this->y() : reader.y(); } float new_Z(const GCodeReader &reader) const { return this->has(Z) ? this->z() : reader.z(); } float new_E(const GCodeReader &reader) const { return this->has(E) ? this->e() : reader.e(); } float new_F(const GCodeReader &reader) const { return this->has(F) ? this->f() : reader.f(); } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index b3957218d..c5fd80b55 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -29,7 +29,7 @@ PrintConfigDef::PrintConfigDef() this->init_common_params(); assign_printer_technology_to_unknown(this->options, ptAny); this->init_fff_params(); - this->init_extruder_retract_keys(); + this->init_extruder_option_keys(); assign_printer_technology_to_unknown(this->options, ptFFF); this->init_sla_params(); assign_printer_technology_to_unknown(this->options, ptSLA); @@ -2270,8 +2270,17 @@ void PrintConfigDef::init_fff_params() } } -void PrintConfigDef::init_extruder_retract_keys() +void PrintConfigDef::init_extruder_option_keys() { + // ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings + m_extruder_option_keys = { + "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset", + "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", + "retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe", + "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", + "default_filament_profile" + }; + m_extruder_retract_keys = { "deretract_speed", "retract_before_travel", @@ -2938,6 +2947,20 @@ void DynamicPrintConfig::normalize() } } +void DynamicPrintConfig::set_num_extruders(unsigned int num_extruders) +{ + const auto &defaults = FullPrintConfig::defaults(); + for (const std::string &key : print_config_def.extruder_option_keys()) { + if (key == "default_filament_profile") + continue; + auto *opt = this->option(key, false); + assert(opt != nullptr); + assert(opt->is_vector()); + if (opt != nullptr && opt->is_vector()) + static_cast(opt)->resize(num_extruders, defaults.option(key)); + } +} + std::string DynamicPrintConfig::validate() { // Full print config is initialized from the defaults. diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 372e70e34..ce051b05b 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -193,6 +193,8 @@ public: static void handle_legacy(t_config_option_key &opt_key, std::string &value); + // Array options growing with the number of extruders + const std::vector& extruder_option_keys() const { return m_extruder_option_keys; } // Options defining the extruder retract properties. These keys are sorted lexicographically. // The extruder retract keys could be overidden by the same values defined at the Filament level // (then the key is further prefixed with the "filament_" prefix). @@ -201,9 +203,10 @@ public: private: void init_common_params(); void init_fff_params(); - void init_extruder_retract_keys(); + void init_extruder_option_keys(); void init_sla_params(); + std::vector m_extruder_option_keys; std::vector m_extruder_retract_keys; }; @@ -231,6 +234,8 @@ public: void normalize(); + void set_num_extruders(unsigned int num_extruders); + // Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned. std::string validate(); diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index ae995aa44..a360aaf1a 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -245,27 +245,13 @@ std::string Preset::remove_suffix_modified(const std::string &name) name; } -void Preset::set_num_extruders(DynamicPrintConfig &config, unsigned int num_extruders) -{ - const auto &defaults = FullPrintConfig::defaults(); - for (const std::string &key : Preset::nozzle_options()) { - if (key == "default_filament_profile") - continue; - auto *opt = config.option(key, false); - assert(opt != nullptr); - assert(opt->is_vector()); - if (opt != nullptr && opt->is_vector()) - static_cast(opt)->resize(num_extruders, defaults.option(key)); - } -} - // Update new extruder fields at the printer profile. void Preset::normalize(DynamicPrintConfig &config) { auto *nozzle_diameter = dynamic_cast(config.option("nozzle_diameter")); if (nozzle_diameter != nullptr) // Loaded the FFF Printer settings. Verify, that all extruder dependent values have enough values. - set_num_extruders(config, (unsigned int)nozzle_diameter->values.size()); + config.set_num_extruders((unsigned int)nozzle_diameter->values.size()); if (config.option("filament_diameter") != nullptr) { // This config contains single or multiple filament presets. // Ensure that the filament preset vector options contain the correct number of values. @@ -469,15 +455,7 @@ const std::vector& Preset::printer_options() // of the nozzle_diameter vector. const std::vector& Preset::nozzle_options() { - // ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings - static std::vector s_opts { - "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset", - "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", - "retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe", - "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", - "default_filament_profile" - }; - return s_opts; + return print_config_def.extruder_option_keys(); } const std::vector& Preset::sla_print_options() diff --git a/src/slic3r/GUI/Preset.hpp b/src/slic3r/GUI/Preset.hpp index 6ed818719..e2e4baa88 100644 --- a/src/slic3r/GUI/Preset.hpp +++ b/src/slic3r/GUI/Preset.hpp @@ -202,7 +202,7 @@ public: void set_visible_from_appconfig(const AppConfig &app_config); // Resize the extruder specific fields, initialize them with the content of the 1st extruder. - void set_num_extruders(unsigned int n) { set_num_extruders(this->config, n); } + void set_num_extruders(unsigned int n) { this->config.set_num_extruders(n); } // Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection. bool operator<(const Preset &other) const { return this->name < other.name; } @@ -227,8 +227,6 @@ public: protected: friend class PresetCollection; friend class PresetBundle; - // Resize the extruder specific vectors () - static void set_num_extruders(DynamicPrintConfig &config, unsigned int n); static std::string remove_suffix_modified(const std::string &name); }; diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index bd5623a22..3c47d637e 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -4,6 +4,7 @@ add_executable(${_TEST_NAME}_tests test_data.cpp test_data.hpp test_flow.cpp + test_skirt_brim.cpp test_trianglemesh.cpp ) target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index a8d3d0d03..a07360af7 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -226,11 +226,14 @@ std::shared_ptr init_print(std::initializer_list meshes, Slic3r model.arrange_objects(PrintConfig::min_object_distance(config.get())); model.center_instances_around_point(Slic3r::Vec2d(100,100)); - for (ModelObject *mo : model.objects) + for (ModelObject *mo : model.objects) { + mo->ensure_on_bed(); print->auto_assign_extruders(mo); + } print->apply(model, *config); print->validate(); + print->set_status_silent(); return print; } @@ -255,11 +258,14 @@ std::shared_ptr init_print(std::initializer_list meshes, Sl } model.arrange_objects(PrintConfig::min_object_distance(config.get())); model.center_instances_around_point(Slic3r::Vec2d(100, 100)); - for (ModelObject *mo : model.objects) + for (ModelObject *mo : model.objects) { + mo->ensure_on_bed(); print->auto_assign_extruders(mo); + } print->apply(model, *config); print->validate(); + print->set_status_silent(); return print; } diff --git a/tests/fff_print/test_skirt_brim.cpp b/tests/fff_print/test_skirt_brim.cpp new file mode 100644 index 000000000..0bef2b627 --- /dev/null +++ b/tests/fff_print/test_skirt_brim.cpp @@ -0,0 +1,263 @@ +#include + +#include "libslic3r/GCodeReader.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/Geometry.hpp" + +#include + +#include "test_data.hpp" // get access to init_print, etc + +using namespace Slic3r::Test; +using namespace Slic3r; + +/// Helper method to find the tool used for the brim (always the first extrusion) +int get_brim_tool(std::string &gcode, Slic3r::GCodeReader& parser) { + int brim_tool = -1; + int tool = -1; + + parser.parse_buffer(gcode, [&tool, &brim_tool] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line) + { + // if the command is a T command, set the the current tool + if (boost::starts_with(line.cmd(), "T")) { + tool = atoi(line.cmd().data() + 1); + } else if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0 && brim_tool < 0) { + brim_tool = tool; + } + }); + + return brim_tool; +} + +TEST_CASE("Skirt height is honored") { + std::shared_ptr config(Slic3r::DynamicPrintConfig::new_from_defaults()); + config->opt_int("skirts") = 1; + config->opt_int("skirt_height") = 5; + config->opt_int("perimeters") = 0; + config->opt_float("support_material_speed") = 99; + + // avoid altering speeds unexpectedly + config->set_deserialize("cooling", "0"); + config->set_deserialize("first_layer_speed", "100%"); + auto support_speed = config->opt("support_material_speed")->value * MM_PER_MIN; + + std::map layers_with_skirt; + std::string gcode; + GCodeReader parser; + Slic3r::Model model; + + SECTION("printing a single object") { + auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)}; + gcode = Slic3r::Test::gcode(print); + } + + SECTION("printing multiple objects") { + auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20, TestMesh::cube_20x20x20}, model, config)}; + gcode = Slic3r::Test::gcode(print); + } + parser.parse_buffer(gcode, [&layers_with_skirt, &support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line) + { + if (line.extruding(self) && self.f() == Approx(support_speed)) { + layers_with_skirt[self.z()] = 1; + } + }); + + REQUIRE(layers_with_skirt.size() == (size_t)config->opt_int("skirt_height")); +} + +SCENARIO("Original Slic3r Skirt/Brim tests", "[!mayfail]") { + auto parser {Slic3r::GCodeReader()}; + Slic3r::Model model; + std::string gcode; + GIVEN("A default configuration") { + std::shared_ptr config(Slic3r::DynamicPrintConfig::new_from_defaults()); + config->set_num_extruders(4); + config->opt_float("support_material_speed") = 99; + config->set_deserialize("first_layer_height", "0.3"); + config->set_deserialize("gcode_comments", "1"); + + // avoid altering speeds unexpectedly + config->set_deserialize("cooling", "0"); + config->set_deserialize("first_layer_speed", "100%"); + // remove noise from top/solid layers + config->opt_int("top_solid_layers") = 0; + config->opt_int("bottom_solid_layers") = 1; + + WHEN("Brim width is set to 5") { + config->opt_int("perimeters") = 0; + config->opt_int("skirts") = 0; + config->opt_float("brim_width") = 5; + THEN("Brim is generated") { + auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)}; + gcode = Slic3r::Test::gcode(print); + bool brim_generated = false; + auto support_speed = config->opt("support_material_speed")->value * MM_PER_MIN; + parser.parse_buffer(gcode, [&brim_generated, support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line) + { + if (self.z() == Approx(0.3) || line.new_Z(self) == Approx(0.3)) { + if (line.extruding(self) && self.f() == Approx(support_speed)) { + brim_generated = true; + } + } + }); + REQUIRE(brim_generated); + } + } + + WHEN("Skirt area is smaller than the brim") { + config->opt_int("skirts") = 1; + config->opt_float("brim_width") = 10; + auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)}; + THEN("Gcode generates") { + REQUIRE(! Slic3r::Test::gcode(print).empty()); + } + } + + WHEN("Skirt height is 0 and skirts > 0") { + config->opt_int("skirts") = 2; + config->opt_int("skirt_height") = 0; + + auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)}; + THEN("Gcode generates") { + REQUIRE(! Slic3r::Test::gcode(print).empty()); + } + } + + WHEN("Perimeter extruder = 2 and support extruders = 3") { + config->opt_int("skirts") = 0; + config->opt_float("brim_width") = 5; + config->opt_int("perimeter_extruder") = 2; + config->opt_int("support_material_extruder") = 3; + THEN("Brim is printed with the extruder used for the perimeters of first object") { + auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)}; + gcode = Slic3r::Test::gcode(print); + int tool = get_brim_tool(gcode, parser); + REQUIRE(tool == config->opt_int("perimeter_extruder") - 1); + } + } + WHEN("Perimeter extruder = 2, support extruders = 3, raft is enabled") { + config->opt_int("skirts") = 0; + config->opt_float("brim_width") = 5; + config->opt_int("perimeter_extruder") = 2; + config->opt_int("support_material_extruder") = 3; + config->opt_int("raft_layers") = 1; + THEN("brim is printed with same extruder as skirt") { + auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)}; + gcode = Slic3r::Test::gcode(print); + int tool = get_brim_tool(gcode, parser); + REQUIRE(tool == config->opt_int("support_material_extruder") - 1); + } + } + WHEN("brim width to 1 with layer_width of 0.5") { + config->opt_int("skirts") = 0; + config->set_deserialize("first_layer_extrusion_width", "0.5"); + config->opt_float("brim_width") = 1; + + THEN("2 brim lines") { + Slic3r::Model model; + auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)}; + print->process(); + REQUIRE(print->brim().entities.size() == 2); + } + } + +#if 0 + WHEN("brim ears on a square") { + config->opt_int("skirts") = 0); + config->set_deserialize("first_layer_extrusion_width", "0.5"); + config->opt_float("brim_width") = 1; + config->set("brim_ears", true); + config->set("brim_ears_max_angle", 91); + + Slic3r::Model model; + auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)}; + print->process(); + + THEN("Four brim ears") { + REQUIRE(print->brim.size() == 4); + } + } + + WHEN("brim ears on a square but with a too small max angle") { + config->set("skirts", 0); + config->set("first_layer_extrusion_width", 0.5); + config->set("brim_width", 1); + config->set("brim_ears", true); + config->set("brim_ears_max_angle", 89); + + THEN("no brim") { + Slic3r::Model model; + auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)}; + print->process(); + REQUIRE(print->brim.size() == 0); + } + } +#endif + + WHEN("Object is plated with overhang support and a brim") { + config->opt_float("layer_height") = 0.4; + config->set_deserialize("first_layer_height", "0.4"); + config->opt_int("skirts") = 1; + config->opt_float("skirt_distance") = 0; + config->opt_float("support_material_speed") = 99; + config->opt_int("perimeter_extruder") = 1; + config->opt_int("support_material_extruder") = 2; + config->opt_int("infill_extruder") = 3; // ensure that a tool command gets emitted. + config->set_deserialize("cooling", "0"); // to prevent speeds to be altered + config->set_deserialize("first_layer_speed", "100%"); // to prevent speeds to be altered + + Slic3r::Model model; + auto print {Slic3r::Test::init_print({TestMesh::overhang}, model, config)}; + print->process(); + + // config->set("support_material", true); // to prevent speeds to be altered + + THEN("skirt length is large enough to contain object with support") { + CHECK(config->opt_bool("support_material")); // test is not valid if support material is off + double skirt_length = 0.0; + Points extrusion_points; + int tool = -1; + + auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config); + std::string gcode = Slic3r::Test::gcode(print); + + auto support_speed = config->opt("support_material_speed")->value * MM_PER_MIN; + parser.parse_buffer(gcode, [config, &extrusion_points, &tool, &skirt_length, support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line) + { + // std::cerr << line.cmd() << "\n"; + if (boost::starts_with(line.cmd(), "T")) { + tool = atoi(line.cmd().data() + 1); + } else if (self.z() == Approx(config->opt("first_layer_height")->value)) { + // on first layer + if (line.extruding(self) && line.dist_XY(self) > 0) { + auto speed = ( self.f() > 0 ? self.f() : line.new_F(self)); + // std::cerr << "Tool " << tool << "\n"; + if (speed == Approx(support_speed) && tool == config->opt_int("perimeter_extruder") - 1) { + // Skirt uses first material extruder, support material speed. + skirt_length += line.dist_XY(self); + } else { + extrusion_points.push_back(Slic3r::Point::new_scale(line.new_X(self), line.new_Y(self))); + } + } + } + + if (self.z() == Approx(0.3) || line.new_Z(self) == Approx(0.3)) { + if (line.extruding(self) && self.f() == Approx(support_speed)) { + } + } + }); + Slic3r::Polygon convex_hull = Slic3r::Geometry::convex_hull(extrusion_points); + double hull_perimeter = unscale(convex_hull.split_at_first_point().length()); + REQUIRE(skirt_length > hull_perimeter); + } + } + WHEN("Large minimum skirt length is used.") { + config->opt_float("min_skirt_length") = 20; + Slic3r::Model model; + auto print = Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config); + THEN("Gcode generation doesn't crash") { + REQUIRE(! Slic3r::Test::gcode(print).empty()); + } + } + } +}