Fix skirt/Brim (#4669)

* Start implementing skirt/brim tests.

* Move skirt_height_z (representing the highest skirt height in actual Z coordinates) to Print and prime it with make_brim so it can be used in PrintGCode.

Updated test to correctly verify skirt is generated for one and multiple objects.

* Fallout from the config refactor; use correct accessors.

* Fix bug where brim was not generated (inverted logic)

* Finish porting tests, left one intentional failure as support material is not generated yet (segfaults).
This commit is contained in:
Joseph Lenox 2019-01-06 16:58:08 -06:00 committed by Alessandro Ranellucci
parent b3cac9d0fb
commit dabb5f3823
5 changed files with 162 additions and 50 deletions

View File

@ -1,68 +1,118 @@
#include <catch.hpp> #include <catch.hpp>
#include "test_data.hpp" // get access to init_print, etc #include "test_data.hpp" // get access to init_print, etc
#include "GCodeReader.hpp" #include "GCodeReader.hpp"
#include "Config.hpp"
#include "Geometry.hpp"
#include <regex>
using namespace Slic3r::Test; using namespace Slic3r::Test;
using namespace Slic3r; using namespace Slic3r;
SCENARIO("Original Slic3r Skirt/Brim tests", "[!mayfail]") { /// Helper method to find the tool used for the brim (always the first extrusion)
GIVEN("Configuration with a skirt height of 2") { int get_brim_tool(std::stringstream& gcode, Slic3r::GCodeReader& parser) {
auto config {Config::new_from_defaults()}; int brim_tool = -1;
config->set("skirts", 1); std::regex tool_regex("^T(\\d+)");
config->set("skirt_height", 2); std::smatch m;
config->set("perimeters", 0); int tool = -1;
config->set("support_material_speed", 99);
// avoid altering speeds unexpectedly parser.parse_stream(gcode, [&tool, &brim_tool, &m, tool_regex] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
config->set("cooling", 0); {
config->set("first_layer_speed", "100%"); // if the command is a T command, set the the current tool
if (std::regex_match(line.cmd, m, tool_regex)) {
WHEN("multiple objects are printed") { tool = std::stoi(m[1].str());
auto gcode {std::stringstream("")}; } else if (line.cmd == "G1" && line.extruding() && line.dist_XY() > 0 && brim_tool < 0) {
Slic3r::Model model; brim_tool = tool;
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20, TestMesh::cube_20x20x20}, model, config)};
std::map<double, bool> layers_with_skirt;
Slic3r::Test::gcode(gcode, print);
auto parser {Slic3r::GCodeReader()};
parser.parse_stream(gcode, [&layers_with_skirt, &config] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
{
if (self.Z > 0) {
if (line.extruding() && line.new_F() == config->getFloat("support_material_speed") * 60.0) {
layers_with_skirt[self.Z] = 1;
}
} }
}); });
THEN("skirt_height is honored") { return brim_tool;
REQUIRE(layers_with_skirt.size() == (size_t)config->getInt("skirt_height")); }
}
}
TEST_CASE("Skirt height is honored") {
auto config {Config::new_from_defaults()};
config->set("skirts", 1);
config->set("skirt_height", 5);
config->set("perimeters", 0);
config->set("support_material_speed", 99);
// avoid altering speeds unexpectedly
config->set("cooling", false);
config->set("first_layer_speed", "100%");
auto support_speed = config->get<ConfigOptionFloat>("support_material_speed") * MM_PER_MIN;
std::map<double, bool> layers_with_skirt;
auto gcode {std::stringstream("")};
gcode.clear();
auto parser {Slic3r::GCodeReader()};
Slic3r::Model model;
SECTION("printing a single object") {
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
Slic3r::Test::gcode(gcode, print);
} }
SECTION("printing multiple objects") {
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20, TestMesh::cube_20x20x20}, model, config)};
Slic3r::Test::gcode(gcode, print);
}
parser.parse_stream(gcode, [&layers_with_skirt, &support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
{
if (line.extruding() && self.F == Approx(support_speed)) {
layers_with_skirt[self.Z] = 1;
}
});
REQUIRE(layers_with_skirt.size() == (size_t)config->getInt("skirt_height"));
}
SCENARIO("Original Slic3r Skirt/Brim tests", "[!mayfail]") {
auto parser {Slic3r::GCodeReader()};
Slic3r::Model model;
auto gcode {std::stringstream("")};
gcode.clear();
GIVEN("A default configuration") { GIVEN("A default configuration") {
auto config {Config::new_from_defaults()}; auto config {Config::new_from_defaults()};
config->set("support_material_speed", 99); config->set("support_material_speed", 99);
config->set("first_layer_height", 0.3);
config->set("gcode_comments", true);
// avoid altering speeds unexpectedly // avoid altering speeds unexpectedly
config->set("cooling", 0); config->set("cooling", false);
config->set("first_layer_speed", "100%"); config->set("first_layer_speed", "100%");
// remove noise from top/solid layers // remove noise from top/solid layers
config->set("top_solid_layers", 0); config->set("top_solid_layers", 0);
config->set("bottom_solid_layers", 0); config->set("bottom_solid_layers", 1);
WHEN("Brim width is set to 5") { WHEN("Brim width is set to 5") {
config->set("perimeters", 0); config->set("perimeters", 0);
config->set("skirts", 0); config->set("skirts", 0);
config->set("brim_width", 5); config->set("brim_width", 5);
THEN("Brim is generated") { THEN("Brim is generated") {
REQUIRE(false); auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
Slic3r::Test::gcode(gcode, print);
bool brim_generated = false;
auto support_speed = config->get<ConfigOptionFloat>("support_material_speed") * MM_PER_MIN;
parser.parse_stream(gcode, [&brim_generated, support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
{
if (self.Z == Approx(0.3) || line.new_Z() == Approx(0.3)) {
if (line.extruding() && self.F == Approx(support_speed)) {
brim_generated = true;
}
}
});
REQUIRE(brim_generated);
} }
} }
WHEN("Skirt area is smaller than the brim") { WHEN("Skirt area is smaller than the brim") {
config->set("skirts", 1); config->set("skirts", 1);
config->set("brim_width", 10); config->set("brim_width", 10);
THEN("GCode generates successfully.") { auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
REQUIRE(false); THEN("Gcode generates") {
Slic3r::Test::gcode(gcode, print);
auto exported {gcode.str()};
REQUIRE(exported.size() > 0);
} }
} }
@ -70,19 +120,37 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[!mayfail]") {
config->set("skirts", 2); config->set("skirts", 2);
config->set("skirt_height", 0); config->set("skirt_height", 0);
THEN("GCode generates successfully.") { auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
REQUIRE(false); THEN("Gcode generates") {
Slic3r::Test::gcode(gcode, print);
auto exported {gcode.str()};
REQUIRE(exported.size() > 0);
} }
} }
WHEN("Perimeter extruder = 2 and support extruders = 3") { WHEN("Perimeter extruder = 2 and support extruders = 3") {
config->set("skirts", 0);
config->set("brim_width", 5);
config->set("perimeter_extruder", 2);
config->set("support_material_extruder", 3);
THEN("Brim is printed with the extruder used for the perimeters of first object") { THEN("Brim is printed with the extruder used for the perimeters of first object") {
REQUIRE(false); auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
Slic3r::Test::gcode(gcode, print);
int tool = get_brim_tool(gcode, parser);
REQUIRE(tool == config->getInt("perimeter_extruder") - 1);
} }
} }
WHEN("Perimeter extruder = 2, support extruders = 3, raft is enabled") { WHEN("Perimeter extruder = 2, support extruders = 3, raft is enabled") {
config->set("skirts", 0);
config->set("brim_width", 5);
config->set("perimeter_extruder", 2);
config->set("support_material_extruder", 3);
config->set("raft_layers", 1);
THEN("brim is printed with same extruder as skirt") { THEN("brim is printed with same extruder as skirt") {
REQUIRE(false); auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
Slic3r::Test::gcode(gcode, print);
int tool = get_brim_tool(gcode, parser);
REQUIRE(tool == config->getInt("support_material_extruder") - 1);
} }
} }
@ -94,17 +162,55 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[!mayfail]") {
config->set("support_material_speed", 99); config->set("support_material_speed", 99);
config->set("perimeter_extruder", 1); config->set("perimeter_extruder", 1);
config->set("support_material_extruder", 2); config->set("support_material_extruder", 2);
config->set("cooling", 0); // to prevent speeds to be altered config->set("infill_extruder", 3); // ensure that a tool command gets emitted.
config->set("cooling", false); // to prevent speeds to be altered
config->set("first_layer_speed", "100%"); // to prevent speeds to be altered config->set("first_layer_speed", "100%"); // to prevent speeds to be altered
Slic3r::Model model; Slic3r::Model model;
auto print {Slic3r::Test::init_print({TestMesh::overhang}, model, config)}; auto print {Slic3r::Test::init_print({TestMesh::overhang}, model, config)};
print->process(); print->process();
config->set("support_material", true); // to prevent speeds to be altered // config->set("support_material", true); // to prevent speeds to be altered
THEN("skirt length is large enough to contain object with support") { THEN("skirt length is large enough to contain object with support") {
REQUIRE(false); CHECK(config->getBool("support_material")); // test is not valid if support material is off
double skirt_length = 0.0;
Points extrusion_points;
int tool = -1;
std::regex tool_regex("^T(\\d+)");
std::smatch m;
auto print {Slic3r::Test::init_print({TestMesh::cube_20x20x20}, model, config)};
Slic3r::Test::gcode(gcode, print);
auto support_speed = config->get<ConfigOptionFloat>("support_material_speed") * MM_PER_MIN;
parser.parse_stream(gcode, [tool_regex, &m, config, &extrusion_points, &tool, &skirt_length, support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
{
std::cerr << line.cmd << "\n";
if (std::regex_match(line.cmd, m, tool_regex)) {
tool = std::stoi(m[1].str());
} else if (self.Z == Approx(config->get<ConfigOptionFloat>("first_layer_height").value)) {
// on first layer
if (line.extruding() && line.dist_XY() > 0) {
auto speed = ( self.F > 0 ? self.F : line.new_F());
std::cerr << "Tool " << tool << "\n";
if (speed == Approx(support_speed) && tool == config->getInt("perimeter_extruder") - 1) {
// Skirt uses first material extruder, support material speed.
skirt_length += line.dist_XY();
} else {
extrusion_points.push_back(Slic3r::Point::new_scale(line.new_X(), line.new_Y()));
}
}
}
if (self.Z == Approx(0.3) || line.new_Z() == Approx(0.3)) {
if (line.extruding() && self.F == Approx(support_speed)) {
}
}
});
auto convex_hull = Slic3r::Geometry::convex_hull(extrusion_points);
auto hull_perimeter = unscale(convex_hull.split_at_first_point().length());
REQUIRE(skirt_length > hull_perimeter);
} }
} }
WHEN("Large minimum skirt length is used.") { WHEN("Large minimum skirt length is used.") {

View File

@ -11,6 +11,8 @@
namespace Slic3r { namespace Test { namespace Slic3r { namespace Test {
constexpr double MM_PER_MIN = 60.0;
/// Enumeration of test meshes /// Enumeration of test meshes
enum class TestMesh { enum class TestMesh {
A, A,

View File

@ -172,7 +172,7 @@ Print::make_skirt()
// include the thickest object first. It is just guaranteed that a skirt is // include the thickest object first. It is just guaranteed that a skirt is
// prepended to the first 'n' layers (with 'n' = skirt_height). // prepended to the first 'n' layers (with 'n' = skirt_height).
// $skirt_height_z in this case is the highest possible skirt height for safety. // $skirt_height_z in this case is the highest possible skirt height for safety.
double skirt_height_z {-1.0}; this->skirt_height_z = -1.0;
for (const auto* object : this->objects) { for (const auto* object : this->objects) {
const size_t skirt_height { const size_t skirt_height {
this->has_infinite_skirt() this->has_infinite_skirt()
@ -180,7 +180,7 @@ Print::make_skirt()
: std::min(size_t(this->config.skirt_height()), object->layer_count()) : std::min(size_t(this->config.skirt_height()), object->layer_count())
}; };
const Layer* highest_layer { object->get_layer(skirt_height - 1) }; const Layer* highest_layer { object->get_layer(skirt_height - 1) };
skirt_height_z = std::max(skirt_height_z, highest_layer->print_z); this->skirt_height_z = std::max(skirt_height_z, highest_layer->print_z);
} }
// collect points from all layers contained in skirt height // collect points from all layers contained in skirt height
@ -188,16 +188,16 @@ Print::make_skirt()
for (auto* object : this->objects) { for (auto* object : this->objects) {
Points object_points; Points object_points;
// get object layers up to skirt_height_z // get object layers up to this->skirt_height_z
for (const auto* layer : object->layers) { for (const auto* layer : object->layers) {
if (layer->print_z > skirt_height_z) break; if (layer->print_z > this->skirt_height_z) break;
for (const ExPolygon ex : layer->slices) for (const ExPolygon ex : layer->slices)
append_to(object_points, static_cast<Points>(ex)); append_to(object_points, static_cast<Points>(ex));
} }
// get support layers up to skirt_height_z // get support layers up to this->skirt_height_z
for (const auto* layer : object->support_layers) { for (const auto* layer : object->support_layers) {
if (layer->print_z > skirt_height_z) break; if (layer->print_z > this->skirt_height_z) break;
for (auto* ee : layer->support_fills) for (auto* ee : layer->support_fills)
append_to(object_points, ee->as_polyline().points); append_to(object_points, ee->as_polyline().points);
for (auto* ee : layer->support_interface_fills) for (auto* ee : layer->support_interface_fills)

View File

@ -243,6 +243,7 @@ class Print
// ordered collections of extrusion paths to build skirt loops and brim // ordered collections of extrusion paths to build skirt loops and brim
ExtrusionEntityCollection skirt, brim; ExtrusionEntityCollection skirt, brim;
double skirt_height_z {-1.0};
Print(); Print();
~Print(); ~Print();

View File

@ -491,8 +491,11 @@ PrintGCode::process_layer(size_t idx, const Layer* layer, const Points& copies)
// extrude skirt along raft layers and normal obj layers // extrude skirt along raft layers and normal obj layers
// (not along interlaced support material layers) // (not along interlaced support material layers)
if (layer->id() < static_cast<size_t>(obj.config.raft_layers) if (layer->id() < static_cast<size_t>(obj.config.raft_layers)
|| ((_print.has_infinite_skirt() || _skirt_done.size() == 0 || (_skirt_done.rbegin())->first < _print.config.skirt_height) || ((_print.has_infinite_skirt()
|| _skirt_done.size() == 0
|| (_skirt_done.rbegin())->first < scale_(_print.skirt_height_z))
&& _skirt_done.count(scale_(layer->print_z)) == 0 && _skirt_done.count(scale_(layer->print_z)) == 0
&& typeid(layer) != typeid(SupportLayer*)) ) { && typeid(layer) != typeid(SupportLayer*)) ) {
@ -547,7 +550,7 @@ PrintGCode::process_layer(size_t idx, const Layer* layer, const Points& copies)
} }
// extrude brim // extrude brim
if (this->_brim_done) { if (!this->_brim_done) {
gcode += _gcodegen.set_extruder(_print.brim_extruder() - 1); gcode += _gcodegen.set_extruder(_print.brim_extruder() - 1);
_gcodegen.set_origin(Pointf(0,0)); _gcodegen.set_origin(Pointf(0,0));
_gcodegen.avoid_crossing_perimeters.use_external_mp = true; _gcodegen.avoid_crossing_perimeters.use_external_mp = true;