mirror of
https://github.com/QIDITECH/QIDISlicer.git
synced 2026-01-30 23:48:44 +03:00
Prusa 2.7.2
This commit is contained in:
@@ -24,8 +24,10 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
add_subdirectory(arrange)
|
||||
add_subdirectory(thumbnails)
|
||||
add_subdirectory(libslic3r)
|
||||
add_subdirectory(slic3rutils)
|
||||
add_subdirectory(fff_print)
|
||||
add_subdirectory(sla_print)
|
||||
add_subdirectory(cpp17 EXCLUDE_FROM_ALL) # does not have to be built all the time
|
||||
if (SLIC3R_GUI)
|
||||
add_subdirectory(slic3rutils)
|
||||
endif()
|
||||
# add_subdirectory(example)
|
||||
|
||||
@@ -207,7 +207,7 @@ static void check_nfp(const std::string & outfile_prefix,
|
||||
auto orb_ex_offs_pos_r_ch = offset_ex(orb_ex_r_ch, scaled<float>(EPSILON));
|
||||
auto orb_ex_offs_neg_r_ch = offset_ex(orb_ex_r_ch, -scaled<float>(EPSILON));
|
||||
|
||||
auto bedpoly_offs = offset_ex(bedpoly, SCALED_EPSILON);
|
||||
auto bedpoly_offs = offset_ex(bedpoly, static_cast<float>(SCALED_EPSILON));
|
||||
|
||||
auto check_at_nfppos = [&](const Point &pos) {
|
||||
ExPolygons orb_ex = orb_ex_r;
|
||||
|
||||
@@ -13,14 +13,17 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_flow.cpp
|
||||
test_gaps.cpp
|
||||
test_gcode.cpp
|
||||
test_gcode_travels.cpp
|
||||
test_gcodefindreplace.cpp
|
||||
test_gcodewriter.cpp
|
||||
test_layers.cpp
|
||||
test_model.cpp
|
||||
test_multi.cpp
|
||||
test_perimeters.cpp
|
||||
test_print.cpp
|
||||
test_printgcode.cpp
|
||||
test_printobject.cpp
|
||||
test_retraction.cpp
|
||||
test_shells.cpp
|
||||
test_skirt_brim.cpp
|
||||
test_support_material.cpp
|
||||
|
||||
@@ -250,10 +250,10 @@ SCENARIO("Cooling integration tests", "[Cooling]") {
|
||||
if (l == 0)
|
||||
l = line.dist_Z(self);
|
||||
if (l > 0.) {
|
||||
if (layer_times.empty())
|
||||
layer_times.emplace_back(0.);
|
||||
if (!layer_times.empty()) { // Ignore anything before first z move.
|
||||
layer_times.back() += 60. * std::abs(l) / line.new_F(self);
|
||||
}
|
||||
}
|
||||
if (line.has('F') && line.f() == external_perimeter_speed)
|
||||
++ layer_external[scaled<coord_t>(self.z())];
|
||||
}
|
||||
|
||||
@@ -45,13 +45,21 @@ SCENARIO("Custom G-code", "[CustomGCode]")
|
||||
});
|
||||
GCodeReader parser;
|
||||
bool last_move_was_z_change = false;
|
||||
bool first_z_move = true; // First z move is not a layer change.
|
||||
int num_layer_changes_not_applied = 0;
|
||||
parser.parse_buffer(Slic3r::Test::slice({ Test::TestMesh::cube_2x20x10 }, config),
|
||||
[&last_move_was_z_change, &num_layer_changes_not_applied](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
|
||||
[&](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
|
||||
{
|
||||
if (last_move_was_z_change != line.cmd_is("_MY_CUSTOM_LAYER_GCODE_"))
|
||||
if (last_move_was_z_change != line.cmd_is("_MY_CUSTOM_LAYER_GCODE_")) {
|
||||
++ num_layer_changes_not_applied;
|
||||
last_move_was_z_change = line.dist_Z(self) > 0;
|
||||
}
|
||||
if (line.dist_Z(self) > 0 && first_z_move) {
|
||||
first_z_move = false;
|
||||
} else if (line.dist_Z(self) > 0){
|
||||
last_move_was_z_change = true;
|
||||
} else {
|
||||
last_move_was_z_change = false;
|
||||
}
|
||||
});
|
||||
THEN("custom layer G-code is applied after Z move and before other moves") {
|
||||
REQUIRE(num_layer_changes_not_applied == 0);
|
||||
|
||||
@@ -236,7 +236,7 @@ static bool verbose_gcode()
|
||||
return s == "1" || s == "on" || s == "yes";
|
||||
}
|
||||
|
||||
void init_print(std::vector<TriangleMesh> &&meshes, Slic3r::Print &print, Slic3r::Model &model, const DynamicPrintConfig &config_in, bool comments)
|
||||
void init_print(std::vector<TriangleMesh> &&meshes, Slic3r::Print &print, Slic3r::Model &model, const DynamicPrintConfig &config_in, bool comments, unsigned duplicate_count)
|
||||
{
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.apply(config_in);
|
||||
@@ -247,10 +247,18 @@ void init_print(std::vector<TriangleMesh> &&meshes, Slic3r::Print &print, Slic3r
|
||||
for (const TriangleMesh &t : meshes) {
|
||||
ModelObject *object = model.add_object();
|
||||
object->name += "object.stl";
|
||||
object->add_volume(std::move(t));
|
||||
object->add_volume(t);
|
||||
object->add_instance();
|
||||
}
|
||||
arrange_objects(model, arr2::to_arrange_bed(get_bed_shape(config)), arr2::ArrangeSettings{}.set_distance_from_objects(min_object_distance(config)));
|
||||
double distance = min_object_distance(config);
|
||||
arr2::ArrangeSettings arrange_settings{};
|
||||
arrange_settings.set_distance_from_objects(distance);
|
||||
arr2::ArrangeBed bed{arr2::to_arrange_bed(get_bed_shape(config))};
|
||||
if (duplicate_count > 1) {
|
||||
duplicate(model, duplicate_count, bed, arrange_settings);
|
||||
}
|
||||
|
||||
arrange_objects(model, bed, arrange_settings);
|
||||
model.center_instances_around_point({100, 100});
|
||||
for (ModelObject *mo : model.objects) {
|
||||
mo->ensure_on_bed();
|
||||
@@ -262,36 +270,36 @@ void init_print(std::vector<TriangleMesh> &&meshes, Slic3r::Print &print, Slic3r
|
||||
print.set_status_silent();
|
||||
}
|
||||
|
||||
void init_print(std::initializer_list<TestMesh> test_meshes, Slic3r::Print &print, Slic3r::Model &model, const Slic3r::DynamicPrintConfig &config_in, bool comments)
|
||||
void init_print(std::initializer_list<TestMesh> test_meshes, Slic3r::Print &print, Slic3r::Model &model, const Slic3r::DynamicPrintConfig &config_in, bool comments, unsigned duplicate_count)
|
||||
{
|
||||
std::vector<TriangleMesh> triangle_meshes;
|
||||
triangle_meshes.reserve(test_meshes.size());
|
||||
for (const TestMesh test_mesh : test_meshes)
|
||||
triangle_meshes.emplace_back(mesh(test_mesh));
|
||||
init_print(std::move(triangle_meshes), print, model, config_in, comments);
|
||||
init_print(std::move(triangle_meshes), print, model, config_in, comments, duplicate_count);
|
||||
}
|
||||
|
||||
void init_print(std::initializer_list<TriangleMesh> input_meshes, Slic3r::Print &print, Slic3r::Model &model, const DynamicPrintConfig &config_in, bool comments)
|
||||
void init_print(std::initializer_list<TriangleMesh> input_meshes, Slic3r::Print &print, Slic3r::Model &model, const DynamicPrintConfig &config_in, bool comments, unsigned duplicate_count)
|
||||
{
|
||||
std::vector<TriangleMesh> triangle_meshes;
|
||||
triangle_meshes.reserve(input_meshes.size());
|
||||
for (const TriangleMesh &input_mesh : input_meshes)
|
||||
triangle_meshes.emplace_back(input_mesh);
|
||||
init_print(std::move(triangle_meshes), print, model, config_in, comments);
|
||||
init_print(std::move(triangle_meshes), print, model, config_in, comments, duplicate_count);
|
||||
}
|
||||
|
||||
void init_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, Slic3r::Model &model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments)
|
||||
void init_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, Slic3r::Model &model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments, unsigned duplicate_count)
|
||||
{
|
||||
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict(config_items);
|
||||
init_print(meshes, print, model, config, comments);
|
||||
init_print(meshes, print, model, config, comments, duplicate_count);
|
||||
}
|
||||
|
||||
void init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, Slic3r::Model &model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool 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, unsigned duplicate_count)
|
||||
{
|
||||
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict(config_items);
|
||||
init_print(meshes, print, model, config, comments);
|
||||
init_print(meshes, print, model, config, comments, duplicate_count);
|
||||
}
|
||||
|
||||
void init_and_process_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, const DynamicPrintConfig &config, bool comments)
|
||||
|
||||
@@ -63,11 +63,11 @@ template <typename T>
|
||||
bool _equiv(const T& a, const T& b, double epsilon) { return abs(a - b) < epsilon; }
|
||||
|
||||
Slic3r::Model model(const std::string& model_name, TriangleMesh&& _mesh);
|
||||
void init_print(std::vector<TriangleMesh> &&meshes, Slic3r::Print &print, Slic3r::Model& model, const DynamicPrintConfig &config_in, bool comments = false);
|
||||
void init_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(), bool comments = false);
|
||||
void init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(), bool comments = false);
|
||||
void init_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
|
||||
void init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
|
||||
void init_print(std::vector<TriangleMesh> &&meshes, Slic3r::Print &print, Slic3r::Model& model, const DynamicPrintConfig &config_in, bool comments = false, unsigned duplicate_count = 1);
|
||||
void init_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(), bool comments = false, unsigned duplicate_count = 1);
|
||||
void init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(), bool comments = false, unsigned duplicate = 1);
|
||||
void init_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false, unsigned duplicate = 1);
|
||||
void init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false, unsigned duplicate = 1);
|
||||
|
||||
void init_and_process_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, const DynamicPrintConfig& config, bool comments = false);
|
||||
void init_and_process_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, const DynamicPrintConfig& config, bool comments = false);
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/Fill/Fill.hpp"
|
||||
#include "libslic3r/Flow.hpp"
|
||||
#include "libslic3r/Layer.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
#include <fstream>
|
||||
|
||||
#include "libslic3r/GCode.hpp"
|
||||
#include "libslic3r/Geometry/ConvexHull.hpp"
|
||||
#include "libslic3r/ModelArrange.hpp"
|
||||
#include "test_data.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GCode::Impl;
|
||||
using namespace Test;
|
||||
|
||||
constexpr bool debug_files = false;
|
||||
|
||||
SCENARIO("Origin manipulation", "[GCode]") {
|
||||
Slic3r::GCodeGenerator gcodegen;
|
||||
@@ -22,219 +29,290 @@ SCENARIO("Origin manipulation", "[GCode]") {
|
||||
}
|
||||
}
|
||||
|
||||
struct ApproxEqualsPoints : public Catch::MatcherBase<Points> {
|
||||
ApproxEqualsPoints(const Points& expected, unsigned tolerance): expected(expected), tolerance(tolerance) {}
|
||||
bool match(const Points& points) const override {
|
||||
if (points.size() != expected.size()) {
|
||||
return false;
|
||||
TEST_CASE("Wiping speeds", "[GCode]") {
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "wipe", "1" },
|
||||
{ "retract_layer_change", "0" },
|
||||
});
|
||||
bool have_wipe = false;
|
||||
std::vector<double> retract_speeds;
|
||||
bool extruded_on_this_layer = false;
|
||||
bool wiping_on_new_layer = false;
|
||||
|
||||
GCodeReader parser;
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
||||
parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
if (line.travel() && line.dist_Z(self) != 0) {
|
||||
extruded_on_this_layer = false;
|
||||
} else if (line.extruding(self) && line.dist_XY(self) > 0) {
|
||||
extruded_on_this_layer = true;
|
||||
} else if (line.retracting(self) && line.dist_XY(self) > 0) {
|
||||
have_wipe = true;
|
||||
wiping_on_new_layer = !extruded_on_this_layer;
|
||||
const double f = line.has_f() ? line.f() : self.f();
|
||||
double move_time = line.dist_XY(self) / f;
|
||||
retract_speeds.emplace_back(std::abs(line.dist_E(self)) / move_time);
|
||||
}
|
||||
for (auto i = 0u; i < points.size(); ++i) {
|
||||
const Point& point = points[i];
|
||||
const Point& expected_point = this->expected[i];
|
||||
if (
|
||||
std::abs(point.x() - expected_point.x()) > this->tolerance
|
||||
|| std::abs(point.y() - expected_point.y()) > this->tolerance
|
||||
) {
|
||||
return false;
|
||||
});
|
||||
CHECK(have_wipe);
|
||||
double expected_retract_speed = config.option<ConfigOptionFloats>("retract_speed")->get_at(0) * 60;
|
||||
for (const double retract_speed : retract_speeds) {
|
||||
INFO("Wipe moves don\'t retract faster than configured speed");
|
||||
CHECK(retract_speed < expected_retract_speed);
|
||||
}
|
||||
INFO("No wiping after layer change")
|
||||
CHECK(!wiping_on_new_layer);
|
||||
}
|
||||
return true;
|
||||
bool has_moves_below_z_offset(const DynamicPrintConfig& config) {
|
||||
GCodeReader parser;
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
||||
|
||||
unsigned moves_below_z_offset{};
|
||||
double configured_offset = config.opt_float("z_offset");
|
||||
parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
if (line.travel() && line.has_z() && line.z() < configured_offset) {
|
||||
moves_below_z_offset++;
|
||||
}
|
||||
std::string describe() const override {
|
||||
std::stringstream ss;
|
||||
ss << std::endl;
|
||||
for (const Point& point : expected) {
|
||||
ss << "(" << point.x() << ", " << point.y() << ")" << std::endl;
|
||||
}
|
||||
ss << "With tolerance: " << this->tolerance;
|
||||
|
||||
return "Equals " + ss.str();
|
||||
}
|
||||
|
||||
private:
|
||||
Points expected;
|
||||
unsigned tolerance;
|
||||
};
|
||||
|
||||
Points get_points(const std::vector<DistancedPoint>& result) {
|
||||
Points result_points;
|
||||
std::transform(
|
||||
result.begin(),
|
||||
result.end(),
|
||||
std::back_inserter(result_points),
|
||||
[](const DistancedPoint& point){
|
||||
return point.point;
|
||||
}
|
||||
);
|
||||
return result_points;
|
||||
}
|
||||
|
||||
std::vector<double> get_distances(const std::vector<DistancedPoint>& result) {
|
||||
std::vector<double> result_distances;
|
||||
std::transform(
|
||||
result.begin(),
|
||||
result.end(),
|
||||
std::back_inserter(result_distances),
|
||||
[](const DistancedPoint& point){
|
||||
return point.distance_from_start;
|
||||
}
|
||||
);
|
||||
return result_distances;
|
||||
}
|
||||
|
||||
TEST_CASE("Place points at distances - expected use", "[GCode]") {
|
||||
std::vector<Point> line{
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{1, 0}),
|
||||
scaled(Vec2f{2, 1}),
|
||||
scaled(Vec2f{2, 2})
|
||||
};
|
||||
std::vector<double> distances{0, 0.2, 0.5, 1 + std::sqrt(2)/2, 1 + std::sqrt(2) + 0.5, 100.0};
|
||||
std::vector<DistancedPoint> result = slice_xy_path(line, distances);
|
||||
|
||||
REQUIRE_THAT(get_points(result), ApproxEqualsPoints(Points{
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{0.2, 0}),
|
||||
scaled(Vec2f{0.5, 0}),
|
||||
scaled(Vec2f{1, 0}),
|
||||
scaled(Vec2f{1.5, 0.5}),
|
||||
scaled(Vec2f{2, 1}),
|
||||
scaled(Vec2f{2, 1.5}),
|
||||
scaled(Vec2f{2, 2})
|
||||
}, 5));
|
||||
|
||||
REQUIRE_THAT(get_distances(result), Catch::Matchers::Approx(std::vector<double>{
|
||||
distances[0], distances[1], distances[2], 1, distances[3], 1 + std::sqrt(2), distances[4], 2 + std::sqrt(2)
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_CASE("Place points at distances - edge case", "[GCode]") {
|
||||
std::vector<Point> line{
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{1, 0}),
|
||||
scaled(Vec2f{2, 0})
|
||||
};
|
||||
std::vector<double> distances{0, 1, 1.5, 2};
|
||||
Points result{get_points(slice_xy_path(line, distances))};
|
||||
CHECK(result == Points{
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{1, 0}),
|
||||
scaled(Vec2f{1.5, 0}),
|
||||
scaled(Vec2f{2, 0})
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("Generate elevated travel", "[GCode]") {
|
||||
std::vector<Point> xy_path{
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{1, 0}),
|
||||
};
|
||||
std::vector<double> ensure_points_at_distances{0.2, 0.5};
|
||||
Points3 result{generate_elevated_travel(xy_path, ensure_points_at_distances, 2.0, [](double x){return 1 + x;})};
|
||||
|
||||
CHECK(result == Points3{
|
||||
scaled(Vec3f{0, 0, 3.0}),
|
||||
scaled(Vec3f{0.2, 0, 3.2}),
|
||||
scaled(Vec3f{0.5, 0, 3.5}),
|
||||
scaled(Vec3f{1, 0, 4.0})
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("Get first crossed line distance", "[GCode]") {
|
||||
// A 2x2 square at 0, 0, with 1x1 square hole in its center.
|
||||
ExPolygon square_with_hole{
|
||||
{
|
||||
scaled(Vec2f{-1, -1}),
|
||||
scaled(Vec2f{1, -1}),
|
||||
scaled(Vec2f{1, 1}),
|
||||
scaled(Vec2f{-1, 1})
|
||||
},
|
||||
{
|
||||
scaled(Vec2f{-0.5, -0.5}),
|
||||
scaled(Vec2f{0.5, -0.5}),
|
||||
scaled(Vec2f{0.5, 0.5}),
|
||||
scaled(Vec2f{-0.5, 0.5})
|
||||
return moves_below_z_offset > 0;
|
||||
}
|
||||
};
|
||||
// A 2x2 square above the previous square at (0, 3).
|
||||
ExPolygon square_above{
|
||||
{
|
||||
scaled(Vec2f{-1, 2}),
|
||||
scaled(Vec2f{1, 2}),
|
||||
scaled(Vec2f{1, 4}),
|
||||
scaled(Vec2f{-1, 4})
|
||||
}
|
||||
};
|
||||
|
||||
// Bottom-up travel intersecting the squares.
|
||||
Lines travel{Polyline{
|
||||
scaled(Vec2f{0, -2}),
|
||||
scaled(Vec2f{0, -0.7}),
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{0, 1}),
|
||||
scaled(Vec2f{0, 1.3}),
|
||||
scaled(Vec2f{0, 2.4}),
|
||||
scaled(Vec2f{0, 4.5}),
|
||||
scaled(Vec2f{0, 5}),
|
||||
}.lines()};
|
||||
|
||||
// Try different cases by skipping lines in the travel.
|
||||
AABBTreeLines::LinesDistancer<Linef> distancer = get_expolygons_distancer({square_with_hole, square_above});
|
||||
CHECK(*get_first_crossed_line_distance(travel, distancer) == Approx(1));
|
||||
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(1), distancer) == Approx(0.2));
|
||||
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(2), distancer) == Approx(0.5));
|
||||
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(3), distancer) == Approx(1.0)); //Edge case
|
||||
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(4), distancer) == Approx(0.7));
|
||||
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(5), distancer) == Approx(1.6));
|
||||
CHECK_FALSE(get_first_crossed_line_distance(tcb::span{travel}.subspan(6), distancer));
|
||||
}
|
||||
|
||||
TEST_CASE("Generate regular polygon", "[GCode]") {
|
||||
const unsigned points_count{32};
|
||||
const Point centroid{scaled(Vec2d{5, -2})};
|
||||
const Polygon result{generate_regular_polygon(centroid, scaled(Vec2d{0, 0}), points_count)};
|
||||
const Point oposite_point{centroid * 2};
|
||||
|
||||
REQUIRE(result.size() == 32);
|
||||
CHECK(result[16].x() == Approx(oposite_point.x()));
|
||||
CHECK(result[16].y() == Approx(oposite_point.y()));
|
||||
|
||||
std::vector<double> angles;
|
||||
angles.reserve(points_count);
|
||||
for (unsigned index = 0; index < points_count; index++) {
|
||||
const unsigned previous_index{index == 0 ? points_count - 1 : index - 1};
|
||||
const unsigned next_index{index == points_count - 1 ? 0 : index + 1};
|
||||
|
||||
const Point previous_point = result.points[previous_index];
|
||||
const Point current_point = result.points[index];
|
||||
const Point next_point = result.points[next_index];
|
||||
|
||||
angles.emplace_back(angle(Vec2crd{previous_point - current_point}, Vec2crd{next_point - current_point}));
|
||||
}
|
||||
|
||||
std::vector<double> expected;
|
||||
angles.reserve(points_count);
|
||||
std::generate_n(std::back_inserter(expected), points_count, [&](){
|
||||
return angles.front();
|
||||
TEST_CASE("Z moves with offset", "[GCode]") {
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "z_offset", 5 },
|
||||
{ "start_gcode", "" },
|
||||
});
|
||||
|
||||
CHECK_THAT(angles, Catch::Matchers::Approx(expected));
|
||||
INFO("No lift");
|
||||
CHECK(!has_moves_below_z_offset(config));
|
||||
|
||||
config.set_deserialize_strict({{ "retract_lift", "3" }});
|
||||
INFO("Lift < z offset");
|
||||
CHECK(!has_moves_below_z_offset(config));
|
||||
|
||||
config.set_deserialize_strict({{ "retract_lift", "6" }});
|
||||
INFO("Lift > z offset");
|
||||
CHECK(!has_moves_below_z_offset(config));
|
||||
}
|
||||
|
||||
TEST_CASE("Square bed with padding", "[GCode]") {
|
||||
const Bed bed{
|
||||
{
|
||||
Vec2d{0, 0},
|
||||
Vec2d{100, 0},
|
||||
Vec2d{100, 100},
|
||||
Vec2d{0, 100}
|
||||
},
|
||||
10.0
|
||||
};
|
||||
std::optional<double> parse_axis(const std::string& line, const std::string& axis) {
|
||||
std::smatch matches;
|
||||
if (std::regex_search(line, matches, std::regex{axis + "(\\d+)"})) {
|
||||
std::string matchedValue = matches[1].str();
|
||||
return std::stod(matchedValue);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
CHECK(bed.centroid.x() == 50);
|
||||
CHECK(bed.centroid.y() == 50);
|
||||
CHECK(bed.contains_within_padding(Vec2d{10, 10}));
|
||||
CHECK_FALSE(bed.contains_within_padding(Vec2d{9, 10}));
|
||||
/**
|
||||
* This tests the following behavior:
|
||||
* - complete objects does not crash
|
||||
* - no hard-coded "E" are generated
|
||||
* - Z moves are correctly generated for both objects
|
||||
* - no travel moves go outside skirt
|
||||
* - temperatures are set correctly
|
||||
*/
|
||||
TEST_CASE("Extrusion, travels, temeperatures", "[GCode]") {
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "gcode_comments", 1 },
|
||||
{ "complete_objects", 1 },
|
||||
{ "extrusion_axis", 'A' },
|
||||
{ "start_gcode", "" }, // prevent any default extra Z move
|
||||
{ "layer_height", 0.4 },
|
||||
{ "first_layer_height", 0.4 },
|
||||
{ "temperature", "200" },
|
||||
{ "first_layer_temperature", "210" },
|
||||
{ "retract_length", "0" }
|
||||
});
|
||||
|
||||
}
|
||||
std::vector<double> z_moves;
|
||||
Points travel_moves;
|
||||
Points extrusions;
|
||||
std::vector<double> temps;
|
||||
|
||||
GCodeReader parser;
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
Test::init_print({TestMesh::cube_20x20x20}, print, model, config, false, 2);
|
||||
std::string gcode = Test::gcode(print);
|
||||
parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
INFO("Unexpected E argument");
|
||||
CHECK(!line.has_e());
|
||||
|
||||
if (line.has_z()) {
|
||||
z_moves.emplace_back(line.z());
|
||||
}
|
||||
if (line.has_x() || line.has_y()) {
|
||||
if (line.extruding(self) || line.has_unknown_axis()) {
|
||||
extrusions.emplace_back(scaled(line.x()), scaled(line.y()));
|
||||
} else if (!extrusions.empty()){ // skip initial travel move to first skirt point
|
||||
travel_moves.emplace_back(scaled(line.x()), scaled(line.y()));
|
||||
}
|
||||
} else if (line.cmd_is("M104") || line.cmd_is("M109")) {
|
||||
const std::optional<double> parsed_temperature = parse_axis(line.raw(), "S");
|
||||
if (!parsed_temperature) {
|
||||
FAIL("Failed to parse temperature!");
|
||||
}
|
||||
if (temps.empty() || temps.back() != parsed_temperature) {
|
||||
temps.emplace_back(*parsed_temperature);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const unsigned layer_count = 20 / 0.4;
|
||||
INFO("Complete_objects generates the correct number of Z moves.");
|
||||
CHECK(z_moves.size() == layer_count * 2);
|
||||
auto first_moves = tcb::span{z_moves}.subspan(0, layer_count);
|
||||
auto second_moves = tcb::span{z_moves}.subspan(layer_count);
|
||||
|
||||
CHECK( std::vector(first_moves.begin(), first_moves.end()) == std::vector(second_moves.begin(), second_moves.end()));
|
||||
const Polygon convex_hull{Geometry::convex_hull(extrusions)};
|
||||
INFO("All travel moves happen within skirt.");
|
||||
for (const Point& travel_move : travel_moves) {
|
||||
CHECK(convex_hull.contains(travel_move));
|
||||
}
|
||||
INFO("Expected temperature changes");
|
||||
CHECK(temps == std::vector<double>{210, 200, 210, 200, 0});
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Used filament", "[GCode]") {
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "retract_length", "1000000" },
|
||||
{ "use_relative_e_distances", 1 },
|
||||
{ "layer_gcode", "G92 E0\n" },
|
||||
});
|
||||
GCodeReader parser;
|
||||
Print print;
|
||||
Model model;
|
||||
Test::init_print({TestMesh::cube_20x20x20}, print, model, config);
|
||||
Test::gcode(print);
|
||||
|
||||
INFO("Final retraction is not considered in total used filament");
|
||||
CHECK(print.print_statistics().total_used_filament > 0);
|
||||
}
|
||||
|
||||
void check_m73s(Print& print){
|
||||
std::vector<double> percent{};
|
||||
bool got_100 = false;
|
||||
bool extruding_after_100 = 0;
|
||||
|
||||
GCodeReader parser;
|
||||
std::string gcode = Slic3r::Test::gcode(print);
|
||||
parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
|
||||
if (line.cmd_is("M73")) {
|
||||
std::optional<double> p = parse_axis(line.raw(), "P");
|
||||
if (!p) {
|
||||
FAIL("Failed to parse percent");
|
||||
}
|
||||
percent.emplace_back(*p);
|
||||
got_100 = p == Approx(100);
|
||||
}
|
||||
if (line.extruding(self) && got_100) {
|
||||
extruding_after_100 = true;
|
||||
}
|
||||
});
|
||||
INFO("M73 is never given more than 100%");
|
||||
for (const double value : percent) {
|
||||
CHECK(value <= 100);
|
||||
}
|
||||
INFO("No extrusions after M73 P100.");
|
||||
CHECK(!extruding_after_100);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("M73s have correct percent values", "[GCode]") {
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
|
||||
SECTION("Single object") {
|
||||
config.set_deserialize_strict({
|
||||
{" gcode_flavor", "sailfish" },
|
||||
{" raft_layers", 3 },
|
||||
});
|
||||
Print print;
|
||||
Model model;
|
||||
Test::init_print({TestMesh::cube_20x20x20}, print, model, config);
|
||||
check_m73s(print);
|
||||
}
|
||||
|
||||
SECTION("Two copies of single object") {
|
||||
config.set_deserialize_strict({
|
||||
{" gcode_flavor", "sailfish" },
|
||||
});
|
||||
Print print;
|
||||
Model model;
|
||||
|
||||
Test::init_print({TestMesh::cube_20x20x20}, print, model, config, false, 2);
|
||||
check_m73s(print);
|
||||
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream gcode_file{"M73_2_copies.gcode"};
|
||||
gcode_file << Test::gcode(print);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Two objects") {
|
||||
config.set_deserialize_strict({
|
||||
{" gcode_flavor", "sailfish" },
|
||||
});
|
||||
Print print;
|
||||
Model model;
|
||||
Test::init_print({TestMesh::cube_20x20x20, TestMesh::cube_20x20x20}, print, model, config);
|
||||
check_m73s(print);
|
||||
}
|
||||
|
||||
SECTION("One layer object") {
|
||||
config.set_deserialize_strict({
|
||||
{" gcode_flavor", "sailfish" },
|
||||
});
|
||||
Print print;
|
||||
Model model;
|
||||
TriangleMesh test_mesh{mesh(TestMesh::cube_20x20x20)};
|
||||
const auto layer_height = static_cast<float>(config.opt_float("layer_height"));
|
||||
test_mesh.scale(Vec3f{1.0F, 1.0F, layer_height/20.0F});
|
||||
Test::init_print({test_mesh}, print, model, config);
|
||||
check_m73s(print);
|
||||
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream gcode_file{"M73_one_layer.gcode"};
|
||||
gcode_file << Test::gcode(print);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("M201 for acceleation reset", "[GCode]") {
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "gcode_flavor", "repetier" },
|
||||
{ "default_acceleration", 1337 },
|
||||
});
|
||||
|
||||
GCodeReader parser;
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::cube_with_hole}, config);
|
||||
|
||||
bool has_accel = false;
|
||||
bool has_m204 = false;
|
||||
|
||||
parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
if (line.cmd_is("M201") && line.has_x() && line.has_y()) {
|
||||
if (line.x() == 1337 && line.y() == 1337) {
|
||||
has_accel = true;
|
||||
}
|
||||
|
||||
}
|
||||
if (line.cmd_is("M204") && line.raw().find('S') != std::string::npos) {
|
||||
has_m204 = true;
|
||||
}
|
||||
});
|
||||
|
||||
INFO("M201 is generated for repetier firmware.");
|
||||
CHECK(has_accel);
|
||||
INFO("M204 is not generated for repetier firmware");
|
||||
CHECK(!has_m204);
|
||||
}
|
||||
|
||||
201
tests/fff_print/test_gcode_travels.cpp
Normal file
201
tests/fff_print/test_gcode_travels.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <libslic3r/GCode/Travels.hpp>
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
#include <libslic3r/GCode.hpp>
|
||||
#include <boost/math/special_functions/pow.hpp>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::GCode::Impl::Travels;
|
||||
|
||||
struct ApproxEqualsPoints : public Catch::MatcherBase<Points> {
|
||||
ApproxEqualsPoints(const Points& expected, unsigned tolerance): expected(expected), tolerance(tolerance) {}
|
||||
bool match(const Points& points) const override {
|
||||
if (points.size() != expected.size()) {
|
||||
return false;
|
||||
}
|
||||
for (auto i = 0u; i < points.size(); ++i) {
|
||||
const Point& point = points[i];
|
||||
const Point& expected_point = this->expected[i];
|
||||
if (
|
||||
std::abs(point.x() - expected_point.x()) > int(this->tolerance)
|
||||
|| std::abs(point.y() - expected_point.y()) > int(this->tolerance)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
std::string describe() const override {
|
||||
std::stringstream ss;
|
||||
ss << std::endl;
|
||||
for (const Point& point : expected) {
|
||||
ss << "(" << point.x() << ", " << point.y() << ")" << std::endl;
|
||||
}
|
||||
ss << "With tolerance: " << this->tolerance;
|
||||
|
||||
return "Equals " + ss.str();
|
||||
}
|
||||
|
||||
private:
|
||||
Points expected;
|
||||
unsigned tolerance;
|
||||
};
|
||||
|
||||
Points get_points(const std::vector<DistancedPoint>& result) {
|
||||
Points result_points;
|
||||
std::transform(
|
||||
result.begin(),
|
||||
result.end(),
|
||||
std::back_inserter(result_points),
|
||||
[](const DistancedPoint& point){
|
||||
return point.point;
|
||||
}
|
||||
);
|
||||
return result_points;
|
||||
}
|
||||
|
||||
std::vector<double> get_distances(const std::vector<DistancedPoint>& result) {
|
||||
std::vector<double> result_distances;
|
||||
std::transform(
|
||||
result.begin(),
|
||||
result.end(),
|
||||
std::back_inserter(result_distances),
|
||||
[](const DistancedPoint& point){
|
||||
return point.distance_from_start;
|
||||
}
|
||||
);
|
||||
return result_distances;
|
||||
}
|
||||
|
||||
TEST_CASE("Place points at distances - expected use", "[GCode]") {
|
||||
std::vector<Point> line{
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{1, 0}),
|
||||
scaled(Vec2f{2, 1}),
|
||||
scaled(Vec2f{2, 2})
|
||||
};
|
||||
std::vector<double> distances{0, 0.2, 0.5, 1 + std::sqrt(2)/2, 1 + std::sqrt(2) + 0.5, 100.0};
|
||||
std::vector<DistancedPoint> result = slice_xy_path(line, distances);
|
||||
|
||||
REQUIRE_THAT(get_points(result), ApproxEqualsPoints(Points{
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{0.2, 0}),
|
||||
scaled(Vec2f{0.5, 0}),
|
||||
scaled(Vec2f{1, 0}),
|
||||
scaled(Vec2f{1.5, 0.5}),
|
||||
scaled(Vec2f{2, 1}),
|
||||
scaled(Vec2f{2, 1.5}),
|
||||
scaled(Vec2f{2, 2})
|
||||
}, 5));
|
||||
|
||||
REQUIRE_THAT(get_distances(result), Catch::Matchers::Approx(std::vector<double>{
|
||||
distances[0], distances[1], distances[2], 1, distances[3], 1 + std::sqrt(2), distances[4], 2 + std::sqrt(2)
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_CASE("Place points at distances - edge case", "[GCode]") {
|
||||
std::vector<Point> line{
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{1, 0}),
|
||||
scaled(Vec2f{2, 0})
|
||||
};
|
||||
std::vector<double> distances{0, 1, 1.5, 2};
|
||||
Points result{get_points(slice_xy_path(line, distances))};
|
||||
CHECK(result == Points{
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{1, 0}),
|
||||
scaled(Vec2f{1.5, 0}),
|
||||
scaled(Vec2f{2, 0})
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("Generate elevated travel", "[GCode]") {
|
||||
std::vector<Point> xy_path{
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{1, 0}),
|
||||
};
|
||||
std::vector<double> ensure_points_at_distances{0.2, 0.5};
|
||||
Points3 result{generate_elevated_travel(xy_path, ensure_points_at_distances, 2.0, [](double x){return 1 + x;})};
|
||||
|
||||
CHECK(result == Points3{
|
||||
scaled(Vec3f{ 0.f, 0.f, 3.f}),
|
||||
scaled(Vec3f{0.2f, 0.f, 3.2f}),
|
||||
scaled(Vec3f{0.5f, 0.f, 3.5f}),
|
||||
scaled(Vec3f{ 1.f, 0.f, 4.f})
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("Get first crossed line distance", "[GCode]") {
|
||||
// A 2x2 square at 0, 0, with 1x1 square hole in its center.
|
||||
ExPolygon square_with_hole{
|
||||
{
|
||||
scaled(Vec2f{-1, -1}),
|
||||
scaled(Vec2f{1, -1}),
|
||||
scaled(Vec2f{1, 1}),
|
||||
scaled(Vec2f{-1, 1})
|
||||
},
|
||||
{
|
||||
scaled(Vec2f{-0.5, -0.5}),
|
||||
scaled(Vec2f{0.5, -0.5}),
|
||||
scaled(Vec2f{0.5, 0.5}),
|
||||
scaled(Vec2f{-0.5, 0.5})
|
||||
}
|
||||
};
|
||||
// A 2x2 square above the previous square at (0, 3).
|
||||
ExPolygon square_above{
|
||||
{
|
||||
scaled(Vec2f{-1, 2}),
|
||||
scaled(Vec2f{1, 2}),
|
||||
scaled(Vec2f{1, 4}),
|
||||
scaled(Vec2f{-1, 4})
|
||||
}
|
||||
};
|
||||
|
||||
// Bottom-up travel intersecting the squares.
|
||||
Lines travel{Polyline{
|
||||
scaled(Vec2f{0, -2}),
|
||||
scaled(Vec2f{0, -0.7}),
|
||||
scaled(Vec2f{0, 0}),
|
||||
scaled(Vec2f{0, 1}),
|
||||
scaled(Vec2f{0, 1.3}),
|
||||
scaled(Vec2f{0, 2.4}),
|
||||
scaled(Vec2f{0, 4.5}),
|
||||
scaled(Vec2f{0, 5}),
|
||||
}.lines()};
|
||||
|
||||
std::vector<GCode::ObjectOrExtrusionLinef> lines;
|
||||
for (const ExPolygon& polygon : {square_with_hole, square_above}) {
|
||||
for (const Line& line : polygon.lines()) {
|
||||
lines.emplace_back(unscale(line.a), unscale(line.b));
|
||||
}
|
||||
}
|
||||
// Try different cases by skipping lines in the travel.
|
||||
AABBTreeLines::LinesDistancer<GCode::ObjectOrExtrusionLinef> distancer{std::move(lines)};
|
||||
|
||||
CHECK(get_first_crossed_line_distance(travel, distancer) == Approx(1));
|
||||
CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(1), distancer) == Approx(0.2));
|
||||
CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(2), distancer) == Approx(0.5));
|
||||
CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(3), distancer) == Approx(1.0)); //Edge case
|
||||
CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(4), distancer) == Approx(0.7));
|
||||
CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(5), distancer) == Approx(1.6));
|
||||
CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(6), distancer) == std::numeric_limits<double>::max());
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Elevated travel formula", "[GCode]") {
|
||||
const double lift_height{10};
|
||||
const double slope_end{10};
|
||||
const double blend_width{10};
|
||||
const ElevatedTravelParams params{lift_height, slope_end, blend_width};
|
||||
|
||||
ElevatedTravelFormula f{params};
|
||||
|
||||
const double distance = slope_end - blend_width / 2;
|
||||
const double slope = (f(distance) - f(0)) / distance;
|
||||
// At the begining it has given slope.
|
||||
CHECK(slope == lift_height / slope_end);
|
||||
// At the end it is flat.
|
||||
CHECK(f(slope_end + blend_width / 2) == f(slope_end + blend_width));
|
||||
// Should be smoothed.
|
||||
CHECK(f(slope_end) < lift_height);
|
||||
}
|
||||
104
tests/fff_print/test_layers.cpp
Normal file
104
tests/fff_print/test_layers.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Ported from t/layers.t
|
||||
*/
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
#include "test_data.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Test;
|
||||
|
||||
void check_layers(const DynamicPrintConfig& config) {
|
||||
GCodeReader parser;
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
||||
|
||||
std::vector<double> z;
|
||||
std::vector<double> increments;
|
||||
|
||||
parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
if (line.has_z()) {
|
||||
z.emplace_back(line.z());
|
||||
increments.emplace_back(line.dist_Z(self));
|
||||
}
|
||||
});
|
||||
|
||||
const double first_layer_height = config.opt_float("first_layer_height");
|
||||
const double z_offset = config.opt_float("z_offset");
|
||||
const double layer_height = config.opt_float("layer_height");
|
||||
INFO("Correct first layer height.");
|
||||
CHECK(z.at(0) == Approx(first_layer_height + z_offset));
|
||||
INFO("Correct second layer height")
|
||||
CHECK(z.at(1) == Approx(first_layer_height + layer_height + z_offset));
|
||||
|
||||
INFO("Correct layer height")
|
||||
for (const double increment : tcb::span{increments}.subspan(1)) {
|
||||
CHECK(increment == Approx(layer_height));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Layer heights are correct", "[Layers]") {
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "start_gcode", "" },
|
||||
{ "layer_height", 0.3 },
|
||||
{ "first_layer_height", 0.2 },
|
||||
{ "retract_length", "0" }
|
||||
});
|
||||
|
||||
SECTION("Absolute first layer height") {
|
||||
check_layers(config);
|
||||
}
|
||||
|
||||
SECTION("Relative layer height") {
|
||||
const double layer_height = config.opt_float("layer_height");
|
||||
config.set_deserialize_strict({
|
||||
{ "first_layer_height", 0.6 * layer_height },
|
||||
});
|
||||
|
||||
check_layers(config);
|
||||
}
|
||||
|
||||
SECTION("Positive z offset") {
|
||||
config.set_deserialize_strict({
|
||||
{ "z_offset", 0.9 },
|
||||
});
|
||||
|
||||
check_layers(config);
|
||||
}
|
||||
|
||||
SECTION("Negative z offset") {
|
||||
config.set_deserialize_strict({
|
||||
{ "z_offset", -0.8 },
|
||||
});
|
||||
|
||||
check_layers(config);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("GCode has reasonable height", "[Layers]") {
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "fill_density", 0 },
|
||||
{ "gcode_binary", 0 },
|
||||
});
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
TriangleMesh test_mesh{mesh(TestMesh::cube_20x20x20)};
|
||||
test_mesh.scale(2);
|
||||
Test::init_print({test_mesh}, print, model, config);
|
||||
const std::string gcode{Test::gcode(print)};
|
||||
|
||||
std::vector<double> z;
|
||||
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
if (line.dist_Z(self) != Approx(0)) {
|
||||
z.emplace_back(line.z());
|
||||
}
|
||||
});
|
||||
|
||||
REQUIRE(!z.empty());
|
||||
INFO("Last Z is: " + std::to_string(z.back()));
|
||||
CHECK((z.back() > 20*1.8 && z.back() < 20*2.2));
|
||||
}
|
||||
359
tests/fff_print/test_retraction.cpp
Normal file
359
tests/fff_print/test_retraction.cpp
Normal file
@@ -0,0 +1,359 @@
|
||||
/**
|
||||
* Ported from t/retraction.t
|
||||
*/
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <libslic3r/GCodeReader.hpp>
|
||||
#include <libslic3r/Config.hpp>
|
||||
|
||||
#include "test_data.hpp"
|
||||
#include <regex>
|
||||
#include <fstream>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Test;
|
||||
|
||||
constexpr bool debug_files {false};
|
||||
|
||||
void check_gcode(std::initializer_list<TestMesh> meshes, const DynamicPrintConfig& config, const unsigned duplicate) {
|
||||
constexpr std::size_t tools_count = 4;
|
||||
std::size_t tool = 0;
|
||||
std::array<unsigned, tools_count> toolchange_count{0}; // Track first usages so that we don't expect retract_length_toolchange when extruders are used for the first time
|
||||
std::array<bool, tools_count> retracted{false};
|
||||
std::array<double, tools_count> retracted_length{0};
|
||||
bool lifted = false;
|
||||
double lift_dist = 0; // Track lifted distance for toolchanges and extruders with different retract_lift values
|
||||
bool changed_tool = false;
|
||||
bool wait_for_toolchange = false;
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
Test::init_print({TestMesh::cube_20x20x20}, print, model, config, false, duplicate);
|
||||
std::string gcode = Test::gcode(print);
|
||||
|
||||
if constexpr(debug_files) {
|
||||
static int count{0};
|
||||
std::ofstream file{"check_gcode_" + std::to_string(count++) + ".gcode"};
|
||||
file << gcode;
|
||||
}
|
||||
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
std::regex regex{"^T(\\d+)"};
|
||||
std::smatch matches;
|
||||
std::string cmd{line.cmd()};
|
||||
if (std::regex_match(cmd, matches, regex)) {
|
||||
tool = std::stoul(matches[1].str());
|
||||
changed_tool = true;
|
||||
wait_for_toolchange = false;
|
||||
toolchange_count[tool]++;
|
||||
} else if (std::regex_match(cmd, std::regex{"^G[01]$"}) && !line.has(Z)) { // ignore lift taking place after retraction
|
||||
INFO("Toolchange must not happen right after retraction.");
|
||||
CHECK(!wait_for_toolchange);
|
||||
}
|
||||
|
||||
const double retract_length = config.option<ConfigOptionFloats>("retract_length")->get_at(tool);
|
||||
const double retract_before_travel = config.option<ConfigOptionFloats>("retract_before_travel")->get_at(tool);
|
||||
const double retract_length_toolchange = config.option<ConfigOptionFloats>("retract_length_toolchange")->get_at(tool);
|
||||
const double retract_restart_extra = config.option<ConfigOptionFloats>("retract_restart_extra")->get_at(tool);
|
||||
const double retract_restart_extra_toolchange = config.option<ConfigOptionFloats>("retract_restart_extra_toolchange")->get_at(tool);
|
||||
|
||||
if (line.dist_Z(self) != 0) {
|
||||
// lift move or lift + change layer
|
||||
const double retract_lift = config.option<ConfigOptionFloats>("retract_lift")->get_at(tool);
|
||||
if (
|
||||
line.dist_Z(self) == Approx(retract_lift)
|
||||
|| (
|
||||
line.dist_Z(self) == Approx(config.opt_float("layer_height") + retract_lift)
|
||||
&& retract_lift > 0
|
||||
)
|
||||
) {
|
||||
INFO("Only lift while retracted");
|
||||
CHECK(retracted[tool]);
|
||||
INFO("No double lift");
|
||||
CHECK(!lifted);
|
||||
lifted = true;
|
||||
lift_dist = line.dist_Z(self);
|
||||
}
|
||||
if (line.dist_Z(self) < 0) {
|
||||
INFO("Must be lifted before going down.")
|
||||
CHECK(lifted);
|
||||
INFO("Going down by the same amount of the lift or by the amount needed to get to next layer");
|
||||
CHECK((
|
||||
line.dist_Z(self) == Approx(-lift_dist)
|
||||
|| line.dist_Z(self) == Approx(-lift_dist + config.opt_float("layer_height"))
|
||||
));
|
||||
lift_dist = 0;
|
||||
lifted = false;
|
||||
}
|
||||
const double feedrate = line.has_f() ? line.f() : self.f();
|
||||
INFO("move Z at travel speed");
|
||||
CHECK(feedrate == Approx(config.opt_float("travel_speed") * 60));
|
||||
}
|
||||
if (line.retracting(self)) {
|
||||
retracted[tool] = true;
|
||||
retracted_length[tool] += -line.dist_E(self);
|
||||
if (retracted_length[tool] == Approx(retract_length)) {
|
||||
// okay
|
||||
} else if (retracted_length[tool] == Approx(retract_length_toolchange)) {
|
||||
wait_for_toolchange = true;
|
||||
} else {
|
||||
INFO("Not retracted by the correct amount.");
|
||||
CHECK(false);
|
||||
}
|
||||
}
|
||||
if (line.extruding(self)) {
|
||||
INFO("Only extruding while not lifted");
|
||||
CHECK(!lifted);
|
||||
if (retracted[tool]) {
|
||||
double expected_amount = retracted_length[tool] + retract_restart_extra;
|
||||
if (changed_tool && toolchange_count[tool] > 1) {
|
||||
expected_amount = retract_length_toolchange + retract_restart_extra_toolchange;
|
||||
changed_tool = false;
|
||||
}
|
||||
INFO("Unretracted by the correct amount");
|
||||
REQUIRE(line.dist_E(self) == Approx(expected_amount));
|
||||
retracted[tool] = false;
|
||||
retracted_length[tool] = 0;
|
||||
}
|
||||
}
|
||||
if (line.travel() && line.dist_XY(self) >= retract_before_travel) {
|
||||
INFO("Retracted before long travel move");
|
||||
CHECK(retracted[tool]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void test_slicing(std::initializer_list<TestMesh> meshes, DynamicPrintConfig& config, const unsigned duplicate = 1) {
|
||||
SECTION("Retraction") {
|
||||
check_gcode(meshes, config, duplicate);
|
||||
}
|
||||
|
||||
SECTION("Restart extra length") {
|
||||
config.set_deserialize_strict({{ "retract_restart_extra", "1" }});
|
||||
check_gcode(meshes, config, duplicate);
|
||||
}
|
||||
|
||||
SECTION("Negative restart extra length") {
|
||||
config.set_deserialize_strict({{ "retract_restart_extra", "-1" }});
|
||||
check_gcode(meshes, config, duplicate);
|
||||
}
|
||||
|
||||
SECTION("Retract_lift") {
|
||||
config.set_deserialize_strict({{ "retract_lift", "1,2" }});
|
||||
check_gcode(meshes, config, duplicate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("Slicing with retraction and lifing", "[retraction]") {
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "nozzle_diameter", "0.6,0.6,0.6,0.6" },
|
||||
{ "first_layer_height", config.opt_float("layer_height") },
|
||||
{ "first_layer_speed", "100%" },
|
||||
{ "start_gcode", "" }, // To avoid dealing with the nozzle lift in start G-code
|
||||
{ "retract_length", "1.5" },
|
||||
{ "retract_before_travel", "3" },
|
||||
{ "retract_layer_change", "1" },
|
||||
{ "only_retract_when_crossing_perimeters", 0 },
|
||||
});
|
||||
|
||||
SECTION("Standard run") {
|
||||
test_slicing({TestMesh::cube_20x20x20}, config);
|
||||
}
|
||||
SECTION("With duplicate cube") {
|
||||
test_slicing({TestMesh::cube_20x20x20}, config, 2);
|
||||
}
|
||||
SECTION("Dual extruder with multiple skirt layers") {
|
||||
config.set_deserialize_strict({
|
||||
{"infill_extruder", 2},
|
||||
{"skirts", 4},
|
||||
{"skirt_height", 3},
|
||||
});
|
||||
test_slicing({TestMesh::cube_20x20x20}, config);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Z moves", "[retraction]") {
|
||||
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "start_gcode", "" }, // To avoid dealing with the nozzle lift in start G-code
|
||||
{ "retract_length", "0" },
|
||||
{ "retract_layer_change", "0" },
|
||||
{ "retract_lift", "0.2" }
|
||||
});
|
||||
|
||||
bool retracted = false;
|
||||
unsigned layer_changes_with_retraction = 0;
|
||||
unsigned retractions = 0;
|
||||
unsigned z_restores = 0;
|
||||
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
||||
|
||||
if constexpr(debug_files) {
|
||||
std::ofstream file{"zmoves.gcode"};
|
||||
file << gcode;
|
||||
}
|
||||
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
if (line.retracting(self)) {
|
||||
retracted = true;
|
||||
retractions++;
|
||||
} else if (line.extruding(self) && retracted) {
|
||||
retracted = 0;
|
||||
}
|
||||
|
||||
if (line.dist_Z(self) != 0 && retracted) {
|
||||
layer_changes_with_retraction++;
|
||||
}
|
||||
|
||||
if (line.dist_Z(self) < 0) {
|
||||
z_restores++;
|
||||
}
|
||||
});
|
||||
|
||||
INFO("no retraction on layer change");
|
||||
CHECK(layer_changes_with_retraction == 0);
|
||||
INFO("no retractions");
|
||||
CHECK(retractions == 0);
|
||||
INFO("no lift");
|
||||
CHECK(z_restores == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Firmware retraction handling", "[retraction]") {
|
||||
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "use_firmware_retraction", 1 },
|
||||
});
|
||||
|
||||
bool retracted = false;
|
||||
unsigned double_retractions = 0;
|
||||
unsigned double_unretractions = 0;
|
||||
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
if (line.cmd_is("G10")) {
|
||||
if (retracted)
|
||||
double_retractions++;
|
||||
retracted = true;
|
||||
} else if (line.cmd_is("G11")) {
|
||||
if (!retracted)
|
||||
double_unretractions++;
|
||||
retracted = 0;
|
||||
}
|
||||
});
|
||||
INFO("No double retractions");
|
||||
CHECK(double_retractions == 0);
|
||||
INFO("No double unretractions");
|
||||
CHECK(double_unretractions == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Firmware retraction when length is 0", "[retraction]") {
|
||||
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "use_firmware_retraction", 1 },
|
||||
{ "retract_length", "0" },
|
||||
});
|
||||
|
||||
bool retracted = false;
|
||||
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
if (line.cmd_is("G10")) {
|
||||
retracted = true;
|
||||
}
|
||||
});
|
||||
INFO("Retracting also when --retract-length is 0 but --use-firmware-retraction is enabled");
|
||||
CHECK(retracted);
|
||||
}
|
||||
|
||||
std::vector<double> get_lift_layers(const DynamicPrintConfig& config) {
|
||||
Print print;
|
||||
Model model;
|
||||
Test::init_print({TestMesh::cube_20x20x20}, print, model, config, false, 2);
|
||||
std::string gcode = Test::gcode(print);
|
||||
|
||||
std::vector<double> result;
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
if (line.cmd_is("G1") && line.dist_Z(self) < 0) {
|
||||
result.push_back(line.new_Z(self));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
bool values_are_in_range(const std::vector<double>& values, double from, double to) {
|
||||
for (const double& value : values) {
|
||||
if (value < from || value > to) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_CASE("Lift above/bellow layers", "[retraction]") {
|
||||
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "nozzle_diameter", "0.6,0.6,0.6,0.6" },
|
||||
{ "start_gcode", "" },
|
||||
{ "retract_lift", "3,4" },
|
||||
});
|
||||
|
||||
config.set_deserialize_strict({
|
||||
{ "retract_lift_above", "0, 0" },
|
||||
{ "retract_lift_below", "0, 0" },
|
||||
});
|
||||
std::vector<double> lift_layers = get_lift_layers(config);
|
||||
INFO("lift takes place when above/below == 0");
|
||||
CHECK(!lift_layers.empty());
|
||||
|
||||
config.set_deserialize_strict({
|
||||
{ "retract_lift_above", "5, 6" },
|
||||
{ "retract_lift_below", "15, 13" },
|
||||
});
|
||||
lift_layers = get_lift_layers(config);
|
||||
INFO("lift takes place when above/below != 0");
|
||||
CHECK(!lift_layers.empty());
|
||||
|
||||
double retract_lift_above = config.option<ConfigOptionFloats>("retract_lift_above")->get_at(0);
|
||||
double retract_lift_below = config.option<ConfigOptionFloats>("retract_lift_below")->get_at(0);
|
||||
|
||||
INFO("Z is not lifted above/below the configured value");
|
||||
CHECK(values_are_in_range(lift_layers, retract_lift_above, retract_lift_below));
|
||||
|
||||
// check lifting with different values for 2. extruder
|
||||
config.set_deserialize_strict({
|
||||
{"perimeter_extruder", 2},
|
||||
{"infill_extruder", 2},
|
||||
{"retract_lift_above", "0, 0"},
|
||||
{"retract_lift_below", "0, 0"}
|
||||
});
|
||||
|
||||
lift_layers = get_lift_layers(config);
|
||||
INFO("lift takes place when above/below == 0 for 2. extruder");
|
||||
CHECK(!lift_layers.empty());
|
||||
|
||||
config.set_deserialize_strict({
|
||||
{ "retract_lift_above", "5, 6" },
|
||||
{ "retract_lift_below", "15, 13" },
|
||||
});
|
||||
lift_layers = get_lift_layers(config);
|
||||
INFO("lift takes place when above/below != 0 for 2. extruder");
|
||||
CHECK(!lift_layers.empty());
|
||||
|
||||
retract_lift_above = config.option<ConfigOptionFloats>("retract_lift_above")->get_at(1);
|
||||
retract_lift_below = config.option<ConfigOptionFloats>("retract_lift_below")->get_at(1);
|
||||
|
||||
INFO("Z is not lifted above/below the configured value for 2. extruder");
|
||||
CHECK(values_are_in_range(lift_layers, retract_lift_above, retract_lift_below));
|
||||
}
|
||||
@@ -2,6 +2,8 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
|
||||
add_executable(${_TEST_NAME}_tests
|
||||
${_TEST_NAME}_tests.cpp
|
||||
test_line.cpp
|
||||
test_point.cpp
|
||||
test_3mf.cpp
|
||||
test_aabbindirect.cpp
|
||||
test_kdtreeindirect.cpp
|
||||
|
||||
@@ -478,7 +478,7 @@ TEST_CASE("Arachne - #8849 - Missing part of model", "[ArachneMissingPart8849]")
|
||||
export_perimeters_to_svg(debug_out_path("arachne-missing-part-8849.svg"), polygons, perimeters, union_ex(wall_tool_paths.getInnerContour()));
|
||||
#endif
|
||||
|
||||
int64_t total_extrusion_length = 0;
|
||||
[[maybe_unused]] int64_t total_extrusion_length = 0;
|
||||
for (Arachne::VariableWidthLines &perimeter : perimeters)
|
||||
for (Arachne::ExtrusionLine &extrusion_line : perimeter)
|
||||
total_extrusion_length += extrusion_line.getLength();
|
||||
|
||||
@@ -280,7 +280,6 @@ TEST_CASE("least squares arc fitting, interpolating end points", "[ArcWelder]")
|
||||
REQUIRE(new_center_opt);
|
||||
if (new_center_opt) {
|
||||
Vec2d new_center = *new_center_opt;
|
||||
double new_radius = (new_center - start_pos).norm();
|
||||
double total_deviation = 0;
|
||||
double new_total_deviation = 0;
|
||||
for (const Vec2d &s : samples) {
|
||||
|
||||
@@ -11,6 +11,242 @@
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("Dynamic config serialization - tests ConfigBase", "[Config]"){
|
||||
DynamicPrintConfig config;
|
||||
INFO("Serialize float");
|
||||
config.set_key_value("layer_height", new ConfigOptionFloat(0.3));
|
||||
CHECK(config.opt_serialize("layer_height") == "0.3");
|
||||
|
||||
INFO("Serialize int");
|
||||
config.set_key_value("perimeters", new ConfigOptionInt(2));
|
||||
CHECK(config.opt_serialize("perimeters") == "2");
|
||||
|
||||
INFO("Serialize float or percent");
|
||||
config.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(30, true));
|
||||
CHECK(config.opt_serialize("first_layer_height") == "30%");
|
||||
|
||||
INFO("Serialize bool");
|
||||
config.set_key_value("use_relative_e_distances", new ConfigOptionBool(true));
|
||||
CHECK(config.opt_serialize("use_relative_e_distances") == "1");
|
||||
|
||||
INFO("Serialize enum");
|
||||
config.set_key_value("gcode_flavor", new ConfigOptionEnum<GCodeFlavor>(gcfTeacup));
|
||||
CHECK(config.opt_serialize("gcode_flavor") == "teacup");
|
||||
|
||||
INFO("Serialize string");
|
||||
config.set_key_value("extrusion_axis", new ConfigOptionString("A"));
|
||||
CHECK(config.opt_serialize("extrusion_axis") == "A");
|
||||
|
||||
INFO("Serialize string with newline");
|
||||
config.set_key_value("notes", new ConfigOptionString("foo\nbar"));
|
||||
CHECK(config.opt_serialize("notes") == "foo\\nbar");
|
||||
config.set_deserialize_strict("notes", "bar\\nbaz");
|
||||
INFO("Deserialize string with newline");
|
||||
CHECK(config.opt_string("notes") == "bar\nbaz");
|
||||
|
||||
INFO("Serialize points");
|
||||
config.set_key_value("extruder_offset", new ConfigOptionPoints({{10, 20}, {30, 45}}));
|
||||
CHECK(config.opt_serialize("extruder_offset") == "10x20,30x45");
|
||||
INFO("Deserialize points");
|
||||
config.set_deserialize_strict("extruder_offset", "20x10");
|
||||
CHECK(config.option<ConfigOptionPoints>("extruder_offset")->values == std::vector{Vec2d{20, 10}});
|
||||
|
||||
INFO("Serialize floats");
|
||||
config.set_key_value("nozzle_diameter", new ConfigOptionFloats({0.2, 3}));
|
||||
CHECK(config.opt_serialize("nozzle_diameter") == "0.2,3");
|
||||
INFO("Deserialize floats");
|
||||
config.set_deserialize_strict("nozzle_diameter", "0.1,0.4");
|
||||
CHECK_THAT(config.option<ConfigOptionFloats>("nozzle_diameter")->values, Catch::Matchers::Approx(std::vector{0.1, 0.4}));
|
||||
INFO("Deserialize floats from one value");
|
||||
config.set_deserialize_strict("nozzle_diameter", "3");
|
||||
CHECK_THAT(config.option<ConfigOptionFloats>("nozzle_diameter")->values, Catch::Matchers::Approx(std::vector{3.0}));
|
||||
|
||||
INFO("Serialize ints");
|
||||
config.set_key_value("temperature", new ConfigOptionInts({180, 210}));
|
||||
CHECK(config.opt_serialize("temperature") == "180,210");
|
||||
INFO("Deserialize ints");
|
||||
config.set_deserialize_strict("temperature", "195,220");
|
||||
CHECK(config.option<ConfigOptionInts>("temperature")->values == std::vector{195,220});
|
||||
|
||||
INFO("Serialize bools");
|
||||
config.set_key_value("wipe", new ConfigOptionBools({true, false}));
|
||||
CHECK(config.opt_serialize("wipe") == "1,0");
|
||||
INFO("Deserialize bools");
|
||||
config.set_deserialize_strict("wipe", "0,1,1");
|
||||
CHECK(config.option<ConfigOptionBools>("wipe")->values == std::vector<unsigned char>{false, true, true});
|
||||
|
||||
INFO("Deserialize bools from empty stirng");
|
||||
config.set_deserialize_strict("wipe", "");
|
||||
CHECK(config.option<ConfigOptionBools>("wipe")->values == std::vector<unsigned char>{});
|
||||
|
||||
INFO("Deserialize bools from value");
|
||||
config.set_deserialize_strict({{"wipe", 1}});
|
||||
CHECK(config.option<ConfigOptionBools>("wipe")->values == std::vector<unsigned char>{true});
|
||||
|
||||
INFO("Serialize strings");
|
||||
config.set_key_value("post_process", new ConfigOptionStrings({"foo", "bar"}));
|
||||
CHECK(config.opt_serialize("post_process") == "foo;bar");
|
||||
INFO("Deserialize strings");
|
||||
config.set_deserialize_strict("post_process", "bar;baz");
|
||||
CHECK(config.option<ConfigOptionStrings>("post_process")->values == std::vector<std::string>{"bar", "baz"});
|
||||
}
|
||||
|
||||
TEST_CASE("Get keys", "[Config]"){
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
CHECK(!config.keys().empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Set not already set option", "[Config]") {
|
||||
DynamicPrintConfig config;
|
||||
config.set_deserialize_strict("filament_diameter", "3");
|
||||
}
|
||||
|
||||
TEST_CASE("Config apply dynamic to static", "[Config]") {
|
||||
DynamicPrintConfig config;
|
||||
config.set_deserialize_strict("perimeters", "2");
|
||||
|
||||
// This trick is taken directly from perl.
|
||||
StaticPrintConfig* config2 = static_cast<GCodeConfig*>(new FullPrintConfig());
|
||||
config2->apply(config, true);
|
||||
|
||||
CHECK(config2->opt_int("perimeters") == 2);
|
||||
delete config2;
|
||||
}
|
||||
|
||||
TEST_CASE("Config apply static to dynamic", "[Config]") {
|
||||
// This trick is taken directly from perl.
|
||||
StaticPrintConfig* config = static_cast<GCodeConfig*>(new FullPrintConfig());
|
||||
|
||||
DynamicPrintConfig config2;
|
||||
config2.apply(*config, true);
|
||||
delete config;
|
||||
|
||||
CHECK(
|
||||
config2.opt_int("perimeters") ==
|
||||
DynamicPrintConfig::full_print_config().opt_int("perimeters")
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("Config apply dynamic to dynamic", "[Config]") {
|
||||
|
||||
DynamicPrintConfig config;
|
||||
config.set_key_value("extruder_offset", new ConfigOptionPoints({{0, 0}, {20, 0}, {0, 20}}));
|
||||
DynamicPrintConfig config2;
|
||||
config2.apply(config, true);
|
||||
|
||||
CHECK(
|
||||
config2.option<ConfigOptionPoints>("extruder_offset")->values ==
|
||||
std::vector<Vec2d>{{0, 0}, {20, 0}, {0, 20}}
|
||||
);
|
||||
}
|
||||
|
||||
TEST_CASE("Get abs value on percent", "[Config]") {
|
||||
StaticPrintConfig* config = static_cast<GCodeConfig*>(new FullPrintConfig());
|
||||
|
||||
config->set_deserialize_strict("solid_infill_speed", "60");
|
||||
config->set_deserialize_strict("top_solid_infill_speed", "10%");
|
||||
CHECK(config->get_abs_value("top_solid_infill_speed") == 6);
|
||||
delete config;
|
||||
}
|
||||
|
||||
TEST_CASE("No interference between DynamicConfig objects", "[Config]") {
|
||||
DynamicPrintConfig config;
|
||||
config.set_key_value("fill_pattern", new ConfigOptionString("line"));
|
||||
DynamicPrintConfig config2;
|
||||
config2.set_key_value("fill_pattern", new ConfigOptionString("hilbertcurve"));
|
||||
CHECK(config.opt_string("fill_pattern") == "line");
|
||||
}
|
||||
|
||||
TEST_CASE("Normalize fdm extruder", "[Config]") {
|
||||
DynamicPrintConfig config;
|
||||
config.set("extruder", 2, true);
|
||||
config.set("perimeter_extruder", 3, true);
|
||||
config.normalize_fdm();
|
||||
INFO("Extruder option is removed after normalize().");
|
||||
CHECK(!config.has("extruder"));
|
||||
INFO("Undefined extruder is populated with default extruder.");
|
||||
CHECK(config.opt_int("infill_extruder") == 2);
|
||||
INFO("Defined extruder is not overwritten by default extruder.");
|
||||
CHECK(config.opt_int("perimeter_extruder") == 3);
|
||||
}
|
||||
|
||||
TEST_CASE("Normalize fdm infill extruder", "[Config]") {
|
||||
DynamicPrintConfig config;
|
||||
config.set("infill_extruder", 2, true);
|
||||
config.normalize_fdm();
|
||||
INFO("Undefined solid infill extruder is populated with infill extruder.");
|
||||
CHECK(config.opt_int("solid_infill_extruder") == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Normalize fdm retract layer change", "[Config]") {
|
||||
DynamicPrintConfig config;
|
||||
config.set("spiral_vase", true, true);
|
||||
config.set_key_value("retract_layer_change", new ConfigOptionBools({true, false}));
|
||||
config.normalize_fdm();
|
||||
CHECK(config.option<ConfigOptionBools>("retract_layer_change")->values == std::vector<unsigned char>{0, 0});
|
||||
}
|
||||
|
||||
TEST_CASE("Can read ini with invalid items", "[Config]") {
|
||||
std::string path = std::string(TEST_DATA_DIR) + "/test_config/bad_config_options.ini";
|
||||
|
||||
DynamicPrintConfig config;
|
||||
config.load(path, ForwardCompatibilitySubstitutionRule::Disable);
|
||||
//Did not crash.
|
||||
}
|
||||
|
||||
struct SerializationTestData {
|
||||
std::string name;
|
||||
std::vector<std::string> values;
|
||||
std::string serialized;
|
||||
};
|
||||
|
||||
TEST_CASE("Config serialization of multiple values", "[Config]"){
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
std::vector<SerializationTestData> test_data{
|
||||
{
|
||||
"empty",
|
||||
{},
|
||||
""
|
||||
},
|
||||
{
|
||||
"single empty",
|
||||
{""},
|
||||
"\"\""
|
||||
},
|
||||
{
|
||||
"single noempty, simple",
|
||||
{"RGB"},
|
||||
"RGB"
|
||||
},
|
||||
{
|
||||
"multiple noempty, simple",
|
||||
{"ABC", "DEF", "09182745@!#$*(&"},
|
||||
"ABC;DEF;09182745@!#$*(&"
|
||||
},
|
||||
{
|
||||
"multiple, simple, some empty",
|
||||
{"ABC", "DEF", "", "09182745@!#$*(&", ""},
|
||||
"ABC;DEF;;09182745@!#$*(&;"
|
||||
},
|
||||
{
|
||||
"complex",
|
||||
{"some \"quoted\" notes", "yet\n some notes", "whatever \n notes", ""},
|
||||
"\"some \\\"quoted\\\" notes\";\"yet\\n some notes\";\"whatever \\n notes\";"
|
||||
}
|
||||
};
|
||||
|
||||
for (const SerializationTestData& data : test_data) {
|
||||
config.set_key_value("filament_notes", new ConfigOptionStrings(data.values));
|
||||
CHECK(config.opt_serialize("filament_notes") == data.serialized);
|
||||
|
||||
config.set_deserialize_strict("filament_notes", "");
|
||||
CHECK(config.option<ConfigOptionStrings>("filament_notes")->values == std::vector<std::string>{});
|
||||
|
||||
config.set_deserialize_strict("filament_notes", data.serialized);
|
||||
CHECK(config.option<ConfigOptionStrings>("filament_notes")->values == data.values);
|
||||
}
|
||||
}
|
||||
SCENARIO("Generic config validation performs as expected.", "[Config]") {
|
||||
GIVEN("A config generated from default options") {
|
||||
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
|
||||
59
tests/libslic3r/test_line.cpp
Normal file
59
tests/libslic3r/test_line.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Ported from xs/t/10_line.t
|
||||
*/
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
#include <libslic3r/Line.hpp>
|
||||
#include "test_utils.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("Line can be translated", "[Line]") {
|
||||
Line line{{100, 100}, {200, 100}};
|
||||
|
||||
line.translate(10, -5);
|
||||
CHECK(Points{line.a, line.b} == Points{{110, 95}, {210, 95}});
|
||||
}
|
||||
|
||||
TEST_CASE("Check if lines are parallel", "[Line]") {
|
||||
CHECK(Line{{0, 0}, {100, 0}}.parallel_to(Line{{200, 200}, {0, 200}}));
|
||||
}
|
||||
|
||||
TEST_CASE("Parallel lines under angles", "[Line]") {
|
||||
auto base_angle = GENERATE(0, M_PI/3, M_PI/2, M_PI);
|
||||
|
||||
Line line{{0, 0}, {100, 0}};
|
||||
line.rotate(base_angle, {0, 0});
|
||||
Line clone{line};
|
||||
|
||||
INFO("Line is parallel to self");
|
||||
CHECK(line.parallel_to(clone));
|
||||
|
||||
clone.reverse();
|
||||
INFO("Line is parallel to self + PI");
|
||||
CHECK(line.parallel_to(clone));
|
||||
|
||||
INFO("Line is parallel to its direction");
|
||||
CHECK(line.parallel_to(line.direction()));
|
||||
INFO("Line is parallel to its direction + PI");
|
||||
line.parallel_to(line.direction() + M_PI);
|
||||
INFO("line is parallel to its direction - PI")
|
||||
line.parallel_to(line.direction() - M_PI);
|
||||
|
||||
SECTION("Line is parallel within epsilon") {
|
||||
clone = line;
|
||||
clone.rotate(EPSILON/2, {0, 0});
|
||||
CHECK(line.parallel_to(clone));
|
||||
clone = line;
|
||||
clone.rotate(-EPSILON/2, {0, 0});
|
||||
CHECK(line.parallel_to(clone));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Intersection infinite", "[Line]") {
|
||||
const Line a{{100, 0}, {200, 0}};
|
||||
const Line b{{300, 300}, {300, 100}};
|
||||
Point r;
|
||||
a.intersection_infinite(b, &r);
|
||||
CHECK(r == Point{300, 0});
|
||||
}
|
||||
46
tests/libslic3r/test_point.cpp
Normal file
46
tests/libslic3r/test_point.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
/**
|
||||
* Ported from xs/t/03_point.t
|
||||
* - it used to check ccw() but it does not exist anymore
|
||||
* and cross product uses doubles
|
||||
*/
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include "test_utils.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("Nearest point", "[Point]") {
|
||||
const Point point{10, 15};
|
||||
const Point point2{30, 15};
|
||||
|
||||
const Point nearest{nearest_point({point2, Point{100, 200}}, point).first};
|
||||
CHECK(nearest == point2);
|
||||
}
|
||||
|
||||
TEST_CASE("Distance to line", "[Point]") {
|
||||
const Line line{{0, 0}, {100, 0}};
|
||||
CHECK(line.distance_to(Point{0, 0}) == Approx(0));
|
||||
CHECK(line.distance_to(Point{100, 0}) == Approx(0));
|
||||
CHECK(line.distance_to(Point{50, 0}) == Approx(0));
|
||||
CHECK(line.distance_to(Point{150, 0}) == Approx(50));
|
||||
CHECK(line.distance_to(Point{0, 50}) == Approx(50));
|
||||
CHECK(line.distance_to(Point{50, 50}) == Approx(50));
|
||||
CHECK(line.perp_distance_to(Point{50, 50}) == Approx(50));
|
||||
CHECK(line.perp_distance_to(Point{150, 50}) == Approx(50));
|
||||
}
|
||||
|
||||
TEST_CASE("Distance to diagonal line", "[Point]") {
|
||||
const Line line{{50, 50}, {125, -25}};
|
||||
CHECK(std::abs(line.distance_to(Point{100, 0})) == Approx(0));
|
||||
}
|
||||
|
||||
TEST_CASE("Perp distance to line does not overflow", "[Point]") {
|
||||
const Line line{
|
||||
{18335846, 18335845},
|
||||
{18335846, 1664160},
|
||||
};
|
||||
|
||||
CHECK(line.distance_to(Point{1664161, 18335848}) == Approx(16671685));
|
||||
}
|
||||
@@ -5,6 +5,73 @@
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
struct PolylineTestCase {
|
||||
Polyline polyline{
|
||||
{100, 100},
|
||||
{200, 100},
|
||||
{200, 200}
|
||||
};
|
||||
};
|
||||
|
||||
TEST_CASE_METHOD(PolylineTestCase, "Lines can be retrieved", "[Polyline]") {
|
||||
|
||||
CHECK(polyline.lines() == Lines{
|
||||
{{100, 100}, {200, 100}},
|
||||
{{200, 100}, {200, 200}},
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(PolylineTestCase, "Clip", "[Polyline]") {
|
||||
const double len = polyline.length();
|
||||
polyline.clip_end(len/3);
|
||||
CHECK(std::abs(polyline.length() - 2.0/3.0*len) < 1);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(PolylineTestCase, "Append", "[Polyline]") {
|
||||
Polyline tested_polyline{polyline};
|
||||
tested_polyline.append(tested_polyline);
|
||||
Points expected{polyline.points};
|
||||
expected.insert(expected.end(), polyline.points.begin(), polyline.points.end());
|
||||
|
||||
CHECK(tested_polyline.points == expected);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(PolylineTestCase, "Extend end", "[Polyline]") {
|
||||
CHECK(polyline.length() == 100*2);
|
||||
polyline.extend_end(50);
|
||||
CHECK(polyline.length() == 100*2 + 50);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(PolylineTestCase, "Extend start", "[Polyline]") {
|
||||
CHECK(polyline.length() == 100*2);
|
||||
polyline.extend_start(50);
|
||||
CHECK(polyline.length() == 100*2 + 50);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(PolylineTestCase, "Split", "[Polyline]") {
|
||||
Polyline p1;
|
||||
Polyline p2;
|
||||
const Point point{150, 100};
|
||||
polyline.split_at(point, &p1, &p2);
|
||||
CHECK(p1.size() == 2);
|
||||
CHECK(p2.size() == 3);
|
||||
CHECK(p1.last_point() == point);
|
||||
CHECK(p2.first_point() == point);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(PolylineTestCase, "Split at first point", "[Polyline]") {
|
||||
Polyline to_split{
|
||||
polyline.points[0],
|
||||
polyline.points[1],
|
||||
polyline.points[2],
|
||||
polyline.points[0]
|
||||
};
|
||||
Polyline p1;
|
||||
Polyline p2;
|
||||
to_split.split_at(to_split.first_point(), &p1, &p2);
|
||||
CHECK(p1.size() == 1);
|
||||
CHECK(p2.size() == 4);
|
||||
}
|
||||
SCENARIO("Simplify polyne, template", "[Polyline]")
|
||||
{
|
||||
Points polyline{ {0,0}, {1000,0}, {2000,0}, {2000,1000}, {2000,2000}, {1000,2000}, {0,2000}, {0,1000}, {0,0} };
|
||||
@@ -43,4 +110,23 @@ SCENARIO("Simplify polyline", "[Polyline]")
|
||||
}
|
||||
}
|
||||
}
|
||||
GIVEN("polyline 3") {
|
||||
auto polyline = Polyline{ {0,0}, {100,0}, {50,10} };
|
||||
WHEN("simplified with Douglas-Peucker") {
|
||||
polyline.simplify(25.);
|
||||
THEN("not simplified") {
|
||||
REQUIRE(polyline == Polyline{ {0,0}, {100, 0}, {50,10} });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("polyline 4") {
|
||||
auto polyline = Polyline{ {0,0}, {20,0}, {50,0}, {80,0}, {100,0} };
|
||||
WHEN("simplified with Douglas-Peucker") {
|
||||
polyline.simplify(2.);
|
||||
THEN("not simplified") {
|
||||
REQUIRE(polyline == Polyline{ {0,0}, {100,0} });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using namespace Slic3r;
|
||||
using namespace SupportSpotsGenerator;
|
||||
|
||||
|
||||
TEST_CASE("Numerical integral calculation compared with exact solution.", "[SupportSpotsGenerator]") {
|
||||
namespace Rectangle {
|
||||
const float width = 10;
|
||||
const float height = 20;
|
||||
const Polygon polygon = {
|
||||
@@ -15,13 +15,35 @@ TEST_CASE("Numerical integral calculation compared with exact solution.", "[Supp
|
||||
scaled(Vec2f{width / 2, height / 2}),
|
||||
scaled(Vec2f{-width / 2, height / 2})
|
||||
};
|
||||
}
|
||||
|
||||
const Integrals integrals{{polygon}};
|
||||
CHECK(integrals.area == Approx(width * height));
|
||||
TEST_CASE("Numerical integral over polygon calculation compared with exact solution.", "[SupportSpotsGenerator]") {
|
||||
const Integrals integrals{Rectangle::polygon};
|
||||
|
||||
CHECK(integrals.area == Approx(Rectangle::width * Rectangle::height));
|
||||
CHECK(integrals.x_i.x() == Approx(0));
|
||||
CHECK(integrals.x_i.y() == Approx(0));
|
||||
CHECK(integrals.x_i_squared.x() == Approx(std::pow(width, 3) * height / 12));
|
||||
CHECK(integrals.x_i_squared.y() == Approx(width * std::pow(height, 3) / 12));
|
||||
CHECK(integrals.x_i_squared.x() == Approx(std::pow(Rectangle::width, 3) * Rectangle::height / 12));
|
||||
CHECK(integrals.x_i_squared.y() == Approx(Rectangle::width * std::pow(Rectangle::height, 3) / 12));
|
||||
}
|
||||
|
||||
TEST_CASE("Integrals over multiple polygons", "[SupportSpotsGenerator]") {
|
||||
const Integrals integrals{{Rectangle::polygon, Rectangle::polygon}};
|
||||
|
||||
CHECK(integrals.area == Approx(2 * Rectangle::width * Rectangle::height));
|
||||
}
|
||||
|
||||
TEST_CASE("Numerical integral over line calculation compared with exact solution.", "[SupportSpotsGenerator]") {
|
||||
const float length = 10;
|
||||
const float width = 20;
|
||||
const Polyline polyline{scaled(Vec2f{-length/2.0f, 0.0f}), scaled(Vec2f{length/2.0f, 0.0f})};
|
||||
|
||||
const Integrals integrals{{polyline}, {width}};
|
||||
CHECK(integrals.area == Approx(length * width));
|
||||
CHECK(integrals.x_i.x() == Approx(0));
|
||||
CHECK(integrals.x_i.y() == Approx(0));
|
||||
CHECK(integrals.x_i_squared.x() == Approx(std::pow(length, 3) * width / 12));
|
||||
CHECK(integrals.x_i_squared.y() == Approx(length * std::pow(width, 3) / 12));
|
||||
}
|
||||
|
||||
TEST_CASE("Moment values and ratio check.", "[SupportSpotsGenerator]") {
|
||||
@@ -37,7 +59,7 @@ TEST_CASE("Moment values and ratio check.", "[SupportSpotsGenerator]") {
|
||||
scaled(Vec2f{0, height})
|
||||
};
|
||||
|
||||
const Integrals integrals{{polygon}};
|
||||
const Integrals integrals{polygon};
|
||||
|
||||
const Vec2f x_axis{1, 0};
|
||||
const float x_axis_moment = compute_second_moment(integrals, x_axis);
|
||||
@@ -69,7 +91,7 @@ TEST_CASE("Moments calculation for rotated axis.", "[SupportSpotsGenerator]") {
|
||||
scaled(Vec2f{77.56229640885199, 189.33057746591336})
|
||||
};
|
||||
|
||||
Integrals integrals{{polygon}};
|
||||
Integrals integrals{polygon};
|
||||
|
||||
// Meassured counterclockwise from (1, 0)
|
||||
const float angle = 1.432f;
|
||||
@@ -130,7 +152,7 @@ TEST_CASE_METHOD(ObjectPartFixture, "Constructing ObjectPart using extrusion col
|
||||
std::nullopt
|
||||
};
|
||||
|
||||
Integrals expected{{expected_polygon}};
|
||||
Integrals expected{expected_polygon};
|
||||
|
||||
CHECK(part.connected_to_bed == true);
|
||||
Vec3f volume_centroid{part.volume_centroid_accumulator / part.volume};
|
||||
|
||||
@@ -62,7 +62,7 @@ TEST_CASE("Voronoi missing edges - points 12067", "[Voronoi]")
|
||||
|
||||
// Construction of the Voronoi Diagram.
|
||||
VD vd;
|
||||
construct_voronoi(pts.begin(), pts.end(), &vd);
|
||||
vd.construct_voronoi(pts.begin(), pts.end());
|
||||
|
||||
#ifdef VORONOI_DEBUG_OUT
|
||||
dump_voronoi_to_svg(debug_out_path("voronoi-pts.svg").c_str(),
|
||||
@@ -190,7 +190,7 @@ TEST_CASE("Voronoi missing edges - Alessandro gapfill 12707", "[Voronoi]")
|
||||
|
||||
Lines lines = to_lines(poly);
|
||||
VD vd;
|
||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
|
||||
#ifdef VORONOI_DEBUG_OUT
|
||||
dump_voronoi_to_svg(debug_out_path("voronoi-lines.svg").c_str(),
|
||||
@@ -298,7 +298,7 @@ TEST_CASE("Voronoi weirdness", "[Voronoi]")
|
||||
|
||||
VD vd;
|
||||
Lines lines = to_lines(poly);
|
||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
|
||||
#ifdef VORONOI_DEBUG_OUT
|
||||
dump_voronoi_to_svg(debug_out_path("voronoi-weirdness.svg").c_str(),
|
||||
@@ -322,7 +322,7 @@ TEST_CASE("Voronoi division by zero 12903", "[Voronoi]")
|
||||
}
|
||||
|
||||
VD vd;
|
||||
construct_voronoi(pts.begin(), pts.end(), &vd);
|
||||
vd.construct_voronoi(pts.begin(), pts.end());
|
||||
|
||||
#ifdef VORONOI_DEBUG_OUT
|
||||
// Scale the voronoi vertices and input points, so that the dump_voronoi_to_svg will display them correctly.
|
||||
@@ -1319,7 +1319,7 @@ TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi][!hide][!mayfail]")
|
||||
#endif
|
||||
|
||||
VD vd;
|
||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
|
||||
for (const auto& edge : vd.edges())
|
||||
if (edge.is_finite()) {
|
||||
@@ -1360,7 +1360,7 @@ TEST_CASE("Voronoi offset", "[VoronoiOffset]")
|
||||
|
||||
VD vd;
|
||||
Lines lines = to_lines(poly_with_hole);
|
||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
|
||||
for (const OffsetTest &ot : {
|
||||
OffsetTest { scale_(0.2), 1, 1 },
|
||||
@@ -1426,7 +1426,7 @@ TEST_CASE("Voronoi offset 2", "[VoronoiOffset]")
|
||||
|
||||
VD vd;
|
||||
Lines lines = to_lines(poly);
|
||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
|
||||
for (const OffsetTest &ot : {
|
||||
OffsetTest { scale_(0.2), 2, 2 },
|
||||
@@ -1496,7 +1496,7 @@ TEST_CASE("Voronoi offset 3", "[VoronoiOffset]")
|
||||
|
||||
VD vd;
|
||||
Lines lines = to_lines(poly);
|
||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
|
||||
for (const OffsetTest &ot : {
|
||||
OffsetTest { scale_(0.2), 2, 2 },
|
||||
@@ -1747,7 +1747,7 @@ TEST_CASE("Voronoi offset with edge collapse", "[VoronoiOffset4]")
|
||||
|
||||
VD vd;
|
||||
Lines lines = to_lines(poly);
|
||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
|
||||
for (const OffsetTest &ot : {
|
||||
OffsetTest { scale_(0.2), 2, 2 },
|
||||
@@ -1858,7 +1858,7 @@ TEST_CASE("Voronoi offset 5", "[VoronoiOffset5]")
|
||||
|
||||
VD vd;
|
||||
Lines lines = to_lines(poly);
|
||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
|
||||
for (const OffsetTest &ot : {
|
||||
OffsetTest { scale_(2.8), 1, 1 },
|
||||
@@ -1916,7 +1916,7 @@ TEST_CASE("Voronoi skeleton", "[VoronoiSkeleton]")
|
||||
|
||||
VD vd;
|
||||
Lines lines = to_lines(poly);
|
||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
Slic3r::Voronoi::annotate_inside_outside(vd, lines);
|
||||
static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
|
||||
std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha);
|
||||
@@ -1966,7 +1966,7 @@ TEST_CASE("Voronoi missing vertex 1", "[VoronoiMissingVertex1]")
|
||||
|
||||
VD vd;
|
||||
Lines lines = to_lines(poly);
|
||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
#ifdef VORONOI_DEBUG_OUT
|
||||
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex1-out.svg").c_str(), vd, Points(), lines);
|
||||
#endif
|
||||
@@ -2006,7 +2006,7 @@ TEST_CASE("Voronoi missing vertex 2", "[VoronoiMissingVertex2]")
|
||||
|
||||
VD vd;
|
||||
Lines lines = to_lines(poly);
|
||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
#ifdef VORONOI_DEBUG_OUT
|
||||
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex2-out.svg").c_str(), vd, Points(), lines);
|
||||
#endif
|
||||
@@ -2047,7 +2047,7 @@ TEST_CASE("Voronoi missing vertex 3", "[VoronoiMissingVertex3]")
|
||||
|
||||
VD vd;
|
||||
Lines lines = to_lines(poly);
|
||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
#ifdef VORONOI_DEBUG_OUT
|
||||
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex3-out.svg").c_str(), vd, Points(), lines);
|
||||
#endif
|
||||
@@ -2091,8 +2091,8 @@ TEST_CASE("Voronoi missing vertex 4", "[VoronoiMissingVertex4]")
|
||||
Geometry::VoronoiDiagram vd_2;
|
||||
Lines lines_1 = to_lines(polygon_1);
|
||||
Lines lines_2 = to_lines(polygon_2);
|
||||
construct_voronoi(lines_1.begin(), lines_1.end(), &vd_1);
|
||||
construct_voronoi(lines_2.begin(), lines_2.end(), &vd_2);
|
||||
vd_1.construct_voronoi(lines_1.begin(), lines_1.end());
|
||||
vd_2.construct_voronoi(lines_2.begin(), lines_2.end());
|
||||
#ifdef VORONOI_DEBUG_OUT
|
||||
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex4-1-out.svg").c_str(), vd_1, Points(), lines_1);
|
||||
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex4-2-out.svg").c_str(), vd_2, Points(), lines_2);
|
||||
@@ -2124,7 +2124,7 @@ TEST_CASE("Duplicate Voronoi vertices", "[Voronoi]")
|
||||
|
||||
VD vd;
|
||||
Lines lines = to_lines(poly);
|
||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
#ifdef VORONOI_DEBUG_OUT
|
||||
dump_voronoi_to_svg(debug_out_path("voronoi-duplicate-vertices-out.svg").c_str(), vd, Points(), lines);
|
||||
#endif
|
||||
@@ -2164,7 +2164,7 @@ TEST_CASE("Intersecting Voronoi edges", "[Voronoi]")
|
||||
|
||||
VD vd;
|
||||
Lines lines = to_lines(poly);
|
||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
#ifdef VORONOI_DEBUG_OUT
|
||||
dump_voronoi_to_svg(debug_out_path("voronoi-intersecting-edges-out.svg").c_str(), vd, Points(), lines);
|
||||
#endif
|
||||
@@ -2226,10 +2226,66 @@ TEST_CASE("Non-planar voronoi diagram", "[VoronoiNonPlanar]")
|
||||
|
||||
VD vd;
|
||||
Lines lines = to_lines(poly);
|
||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
#ifdef VORONOI_DEBUG_OUT
|
||||
dump_voronoi_to_svg(debug_out_path("voronoi-non-planar-out.svg").c_str(), vd, Points(), lines);
|
||||
#endif
|
||||
|
||||
// REQUIRE(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(vd));
|
||||
}
|
||||
|
||||
// This case is extracted from SPE-1729, where several ExPolygon with very thin lines
|
||||
// and holes formed by very close (1-5nm) vertices that are on the edge of our resolution.
|
||||
// Those thin lines and holes are both unprintable and cause the Voronoi diagram to be invalid.
|
||||
TEST_CASE("Invalid Voronoi diagram - Thin lines - SPE-1729", "[InvalidVoronoiDiagramThinLinesSPE1729]")
|
||||
{
|
||||
Polygon contour = {
|
||||
{32247689, -2405501},
|
||||
{32247733, -2308514},
|
||||
{32247692, -2405496},
|
||||
{50484384, 332941},
|
||||
{50374839, 1052546},
|
||||
{32938040, -1637993},
|
||||
{32938024, -1673788},
|
||||
{32942107, 7220481},
|
||||
{32252205, 7447599},
|
||||
{32252476, 8037808},
|
||||
{32555965, 8277599},
|
||||
{17729260, 8904718},
|
||||
{17729236, 8853233},
|
||||
{17729259, 8904722},
|
||||
{17039259, 8935481},
|
||||
{17033440, -3880421},
|
||||
{17204385, -3852156},
|
||||
{17723645, -3441873},
|
||||
{17723762, -3187210},
|
||||
{17728957, 8240730},
|
||||
{17728945, 8213866},
|
||||
{31716233, 7614090},
|
||||
{20801623, -1009882},
|
||||
{21253963, -1580792},
|
||||
{32252082, 7157187},
|
||||
{32248022, -1673787},
|
||||
{24245653, -2925506},
|
||||
{18449246, -3809095},
|
||||
{18728385, -4449246}
|
||||
};
|
||||
|
||||
Polygon hole = {
|
||||
{32247789, -2181284},
|
||||
{32247870, -2003865},
|
||||
{32247872, -2003866},
|
||||
{32247752, -2267007}
|
||||
};
|
||||
|
||||
Polygons polygons = {contour, hole};
|
||||
|
||||
VD vd;
|
||||
Lines lines = to_lines(polygons);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
#ifdef VORONOI_DEBUG_OUT
|
||||
dump_voronoi_to_svg(debug_out_path("invalid-voronoi-diagram-thin-lines.svg").c_str(), vd, Points(), lines);
|
||||
#endif
|
||||
|
||||
// REQUIRE(vd.is_valid());
|
||||
}
|
||||
Reference in New Issue
Block a user