2019-10-15 14:31:20 +00:00
|
|
|
#include <catch2/catch.hpp>
|
|
|
|
|
|
|
|
#include "libslic3r/GCodeReader.hpp"
|
|
|
|
#include "libslic3r/Config.hpp"
|
|
|
|
#include "libslic3r/Geometry.hpp"
|
|
|
|
|
|
|
|
#include <boost/algorithm/string.hpp>
|
|
|
|
|
|
|
|
#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)
|
2019-10-17 15:09:15 +00:00
|
|
|
static int get_brim_tool(const std::string &gcode)
|
|
|
|
{
|
2019-10-25 11:34:37 +00:00
|
|
|
int brim_tool = -1;
|
|
|
|
int tool = -1;
|
2019-10-17 15:09:15 +00:00
|
|
|
GCodeReader parser;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
});
|
2019-10-15 14:31:20 +00:00
|
|
|
return brim_tool;
|
|
|
|
}
|
|
|
|
|
2019-10-25 11:34:37 +00:00
|
|
|
TEST_CASE("Skirt height is honored", "[Skirt]") {
|
2019-10-16 09:16:50 +00:00
|
|
|
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
2019-10-17 15:09:15 +00:00
|
|
|
config.set_deserialize({
|
2019-10-17 17:09:24 +00:00
|
|
|
{ "skirts", 1 },
|
|
|
|
{ "skirt_height", 5 },
|
|
|
|
{ "perimeters", 0 },
|
|
|
|
{ "support_material_speed", 99 },
|
2019-10-17 15:09:15 +00:00
|
|
|
// avoid altering speeds unexpectedly
|
2019-10-17 17:09:24 +00:00
|
|
|
{ "cooling", false },
|
2019-10-17 15:09:15 +00:00
|
|
|
{ "first_layer_speed", "100%" }
|
|
|
|
});
|
2019-10-15 14:31:20 +00:00
|
|
|
|
|
|
|
std::string gcode;
|
|
|
|
SECTION("printing a single object") {
|
2019-10-17 15:09:15 +00:00
|
|
|
gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
2019-10-15 14:31:20 +00:00
|
|
|
}
|
|
|
|
SECTION("printing multiple objects") {
|
2019-10-17 15:09:15 +00:00
|
|
|
gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20, TestMesh::cube_20x20x20}, config);
|
2019-10-15 14:31:20 +00:00
|
|
|
}
|
2019-10-17 15:09:15 +00:00
|
|
|
|
|
|
|
std::map<double, bool> layers_with_skirt;
|
|
|
|
double support_speed = config.opt<Slic3r::ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
|
|
|
|
GCodeReader parser;
|
|
|
|
parser.parse_buffer(gcode, [&layers_with_skirt, &support_speed] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
2019-10-15 14:31:20 +00:00
|
|
|
if (line.extruding(self) && self.f() == Approx(support_speed)) {
|
|
|
|
layers_with_skirt[self.z()] = 1;
|
|
|
|
}
|
|
|
|
});
|
2019-10-16 09:16:50 +00:00
|
|
|
REQUIRE(layers_with_skirt.size() == (size_t)config.opt_int("skirt_height"));
|
2019-10-15 14:31:20 +00:00
|
|
|
}
|
|
|
|
|
2019-10-25 11:34:37 +00:00
|
|
|
SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
|
2019-10-15 14:31:20 +00:00
|
|
|
GIVEN("A default configuration") {
|
2019-10-16 09:16:50 +00:00
|
|
|
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
|
|
|
config.set_num_extruders(4);
|
2019-10-17 15:09:15 +00:00
|
|
|
config.set_deserialize({
|
2019-10-17 17:09:24 +00:00
|
|
|
{ "support_material_speed", 99 },
|
|
|
|
{ "first_layer_height", 0.3 },
|
|
|
|
{ "gcode_comments", true },
|
2019-10-17 15:09:15 +00:00
|
|
|
// avoid altering speeds unexpectedly
|
2019-10-17 17:09:24 +00:00
|
|
|
{ "cooling", false },
|
2019-10-17 15:09:15 +00:00
|
|
|
{ "first_layer_speed", "100%" },
|
|
|
|
// remove noise from top/solid layers
|
2019-10-17 17:09:24 +00:00
|
|
|
{ "top_solid_layers", 0 },
|
2019-10-25 11:34:37 +00:00
|
|
|
{ "bottom_solid_layers", 1 },
|
|
|
|
{ "start_gcode", "T[initial_tool]\n" }
|
2019-10-17 15:09:15 +00:00
|
|
|
});
|
2019-10-15 14:31:20 +00:00
|
|
|
|
|
|
|
WHEN("Brim width is set to 5") {
|
2019-10-17 15:09:15 +00:00
|
|
|
config.set_deserialize({
|
2019-10-17 17:09:24 +00:00
|
|
|
{ "perimeters", 0 },
|
|
|
|
{ "skirts", 0 },
|
|
|
|
{ "brim_width", 5 }
|
2019-10-17 15:09:15 +00:00
|
|
|
});
|
|
|
|
THEN("Brim is generated") {
|
|
|
|
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
2019-10-15 14:31:20 +00:00
|
|
|
bool brim_generated = false;
|
2019-10-16 09:16:50 +00:00
|
|
|
double support_speed = config.opt<Slic3r::ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
|
2019-10-17 15:09:15 +00:00
|
|
|
Slic3r::GCodeReader parser;
|
|
|
|
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;
|
2019-10-15 14:31:20 +00:00
|
|
|
}
|
2019-10-17 15:09:15 +00:00
|
|
|
}
|
|
|
|
});
|
2019-10-15 14:31:20 +00:00
|
|
|
REQUIRE(brim_generated);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
WHEN("Skirt area is smaller than the brim") {
|
2019-10-17 15:09:15 +00:00
|
|
|
config.set_deserialize({
|
2019-10-17 17:09:24 +00:00
|
|
|
{ "skirts", 1 },
|
|
|
|
{ "brim_width", 10}
|
2019-10-17 15:09:15 +00:00
|
|
|
});
|
2019-10-15 14:31:20 +00:00
|
|
|
THEN("Gcode generates") {
|
2019-10-17 15:09:15 +00:00
|
|
|
REQUIRE(! Slic3r::Test::slice({TestMesh::cube_20x20x20}, config).empty());
|
2019-10-15 14:31:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
WHEN("Skirt height is 0 and skirts > 0") {
|
2019-10-17 15:09:15 +00:00
|
|
|
config.set_deserialize({
|
2019-10-17 17:09:24 +00:00
|
|
|
{ "skirts", 2 },
|
|
|
|
{ "skirt_height", 0 }
|
2019-10-17 15:09:15 +00:00
|
|
|
});
|
2019-10-15 14:31:20 +00:00
|
|
|
THEN("Gcode generates") {
|
2019-10-17 15:09:15 +00:00
|
|
|
REQUIRE(! Slic3r::Test::slice({TestMesh::cube_20x20x20}, config).empty());
|
2019-10-15 14:31:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
WHEN("Perimeter extruder = 2 and support extruders = 3") {
|
|
|
|
THEN("Brim is printed with the extruder used for the perimeters of first object") {
|
2019-10-25 11:34:37 +00:00
|
|
|
config.set_deserialize({
|
2019-10-17 17:09:24 +00:00
|
|
|
{ "skirts", 0 },
|
|
|
|
{ "brim_width", 5 },
|
|
|
|
{ "perimeter_extruder", 2 },
|
2019-10-25 11:34:37 +00:00
|
|
|
{ "support_material_extruder", 3 },
|
|
|
|
{ "infill_extruder", 4 }
|
|
|
|
});
|
|
|
|
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
2019-10-17 15:09:15 +00:00
|
|
|
int tool = get_brim_tool(gcode);
|
2019-10-16 09:16:50 +00:00
|
|
|
REQUIRE(tool == config.opt_int("perimeter_extruder") - 1);
|
2019-10-15 14:31:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
WHEN("Perimeter extruder = 2, support extruders = 3, raft is enabled") {
|
|
|
|
THEN("brim is printed with same extruder as skirt") {
|
2019-10-25 11:34:37 +00:00
|
|
|
config.set_deserialize({
|
|
|
|
{ "skirts", 0 },
|
|
|
|
{ "brim_width", 5 },
|
|
|
|
{ "perimeter_extruder", 2 },
|
|
|
|
{ "support_material_extruder", 3 },
|
|
|
|
{ "infill_extruder", 4 },
|
|
|
|
{ "raft_layers", 1 }
|
|
|
|
});
|
|
|
|
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
2019-10-17 15:09:15 +00:00
|
|
|
int tool = get_brim_tool(gcode);
|
2019-10-16 09:16:50 +00:00
|
|
|
REQUIRE(tool == config.opt_int("support_material_extruder") - 1);
|
2019-10-15 14:31:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
WHEN("brim width to 1 with layer_width of 0.5") {
|
2019-10-17 15:09:15 +00:00
|
|
|
config.set_deserialize({
|
2019-10-17 17:09:24 +00:00
|
|
|
{ "skirts", 0 },
|
|
|
|
{ "first_layer_extrusion_width", 0.5 },
|
|
|
|
{ "brim_width", 1 }
|
2019-10-17 15:09:15 +00:00
|
|
|
});
|
2019-10-15 14:31:20 +00:00
|
|
|
THEN("2 brim lines") {
|
2019-10-17 15:09:15 +00:00
|
|
|
Slic3r::Print print;
|
|
|
|
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, config);
|
|
|
|
REQUIRE(print.brim().entities.size() == 2);
|
2019-10-15 14:31:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
WHEN("brim ears on a square") {
|
2019-10-17 15:09:15 +00:00
|
|
|
config.set_deserialize({
|
2019-10-17 17:09:24 +00:00
|
|
|
{ "skirts", 0 },
|
|
|
|
{ "first_layer_extrusion_width", 0.5 },
|
|
|
|
{ "brim_width", 1 },
|
|
|
|
{ "brim_ears", 1 },
|
|
|
|
{ "brim_ears_max_angle", 91 }
|
2019-10-17 15:09:15 +00:00
|
|
|
});
|
|
|
|
Slic3r::Print print;
|
|
|
|
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, config);
|
2019-10-15 14:31:20 +00:00
|
|
|
THEN("Four brim ears") {
|
2019-10-17 15:09:15 +00:00
|
|
|
REQUIRE(print.brim().entities.size() == 4);
|
2019-10-15 14:31:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
WHEN("brim ears on a square but with a too small max angle") {
|
2019-10-17 15:09:15 +00:00
|
|
|
config.set_deserialize({
|
2019-10-17 17:09:24 +00:00
|
|
|
{ "skirts", 0 },
|
|
|
|
{ "first_layer_extrusion_width", 0.5 },
|
|
|
|
{ "brim_width", 1 },
|
|
|
|
{ "brim_ears", 1 },
|
|
|
|
{ "brim_ears_max_angle", 89 }
|
2019-10-17 15:09:15 +00:00
|
|
|
});
|
2019-10-15 14:31:20 +00:00
|
|
|
THEN("no brim") {
|
2019-10-17 15:09:15 +00:00
|
|
|
Slic3r::Print print;
|
|
|
|
Slic3r::Test::init_and_process_print({ TestMesh::cube_20x20x20 }, print, config);
|
|
|
|
REQUIRE(print.brim().entities.size() == 0);
|
2019-10-15 14:31:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
WHEN("Object is plated with overhang support and a brim") {
|
2019-10-17 15:09:15 +00:00
|
|
|
config.set_deserialize({
|
2019-10-17 17:09:24 +00:00
|
|
|
{ "layer_height", 0.4 },
|
|
|
|
{ "first_layer_height", 0.4 },
|
|
|
|
{ "skirts", 1 },
|
|
|
|
{ "skirt_distance", 0 },
|
|
|
|
{ "support_material_speed", 99 },
|
|
|
|
{ "perimeter_extruder", 1 },
|
|
|
|
{ "support_material_extruder", 2 },
|
|
|
|
{ "infill_extruder", 3 }, // ensure that a tool command gets emitted.
|
|
|
|
{ "cooling", false }, // to prevent speeds to be altered
|
2019-10-17 15:09:15 +00:00
|
|
|
{ "first_layer_speed", "100%" }, // to prevent speeds to be altered
|
2019-10-25 11:34:37 +00:00
|
|
|
{ "start_gcode", "T[initial_tool]\n" }
|
2019-10-17 15:09:15 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
THEN("overhang generates?") {
|
|
|
|
//FIXME does it make sense?
|
|
|
|
REQUIRE(! Slic3r::Test::slice({TestMesh::overhang}, config).empty());
|
|
|
|
}
|
2019-10-15 14:31:20 +00:00
|
|
|
|
2019-10-16 09:16:50 +00:00
|
|
|
// config.set("support_material", true); // to prevent speeds to be altered
|
2019-10-15 14:31:20 +00:00
|
|
|
|
|
|
|
THEN("skirt length is large enough to contain object with support") {
|
2019-10-16 09:16:50 +00:00
|
|
|
CHECK(config.opt_bool("support_material")); // test is not valid if support material is off
|
2019-10-17 15:09:15 +00:00
|
|
|
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
2019-10-16 09:16:50 +00:00
|
|
|
double support_speed = config.opt<ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
|
2019-10-17 15:09:15 +00:00
|
|
|
double skirt_length = 0.0;
|
|
|
|
Points extrusion_points;
|
|
|
|
int tool = -1;
|
|
|
|
GCodeReader parser;
|
|
|
|
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<ConfigOptionFloat>("first_layer_height")->value)) {
|
|
|
|
// on first layer
|
|
|
|
if (line.extruding(self) && line.dist_XY(self) > 0) {
|
|
|
|
float 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)));
|
2019-10-15 14:31:20 +00:00
|
|
|
}
|
2019-10-17 15:09:15 +00:00
|
|
|
}
|
|
|
|
if (self.z() == Approx(0.3) || line.new_Z(self) == Approx(0.3)) {
|
|
|
|
if (line.extruding(self) && self.f() == Approx(support_speed)) {
|
2019-10-15 14:31:20 +00:00
|
|
|
}
|
2019-10-17 15:09:15 +00:00
|
|
|
}
|
|
|
|
});
|
2019-10-15 14:31:20 +00:00
|
|
|
Slic3r::Polygon convex_hull = Slic3r::Geometry::convex_hull(extrusion_points);
|
|
|
|
double hull_perimeter = unscale<double>(convex_hull.split_at_first_point().length());
|
|
|
|
REQUIRE(skirt_length > hull_perimeter);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
WHEN("Large minimum skirt length is used.") {
|
2019-10-18 10:05:37 +00:00
|
|
|
config.set("min_skirt_length", 20);
|
2019-10-15 14:31:20 +00:00
|
|
|
THEN("Gcode generation doesn't crash") {
|
2019-10-17 15:09:15 +00:00
|
|
|
REQUIRE(! Slic3r::Test::slice({TestMesh::cube_20x20x20}, config).empty());
|
2019-10-15 14:31:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|