mirror of
https://github.com/QIDITECH/QIDISlicer.git
synced 2026-01-30 23:48:44 +03:00
update test
This commit is contained in:
@@ -14,6 +14,13 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_gaps.cpp
|
||||
test_gcode.cpp
|
||||
test_gcode_travels.cpp
|
||||
test_seam_perimeters.cpp
|
||||
test_seam_shells.cpp
|
||||
test_seam_geometry.cpp
|
||||
test_seam_aligned.cpp
|
||||
test_seam_rear.cpp
|
||||
test_seam_random.cpp
|
||||
benchmark_seams.cpp
|
||||
test_gcodefindreplace.cpp
|
||||
test_gcodewriter.cpp
|
||||
test_cancel_object.cpp
|
||||
@@ -33,6 +40,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
)
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
|
||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||
target_compile_definitions(${_TEST_NAME}_tests PUBLIC CATCH_CONFIG_ENABLE_BENCHMARKING)
|
||||
|
||||
if (WIN32)
|
||||
qidislicer_copy_dlls(${_TEST_NAME}_tests)
|
||||
|
||||
138
tests/fff_print/benchmark_seams.cpp
Normal file
138
tests/fff_print/benchmark_seams.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include "test_data.hpp"
|
||||
|
||||
#include "libslic3r/GCode/SeamGeometry.hpp"
|
||||
#include "libslic3r/GCode/SeamAligned.hpp"
|
||||
#include "libslic3r/GCode/SeamRear.hpp"
|
||||
#include "libslic3r/GCode/SeamRandom.hpp"
|
||||
|
||||
TEST_CASE_METHOD(Slic3r::Test::SeamsFixture, "Seam benchmarks", "[Seams][.Benchmarks]") {
|
||||
BENCHMARK_ADVANCED("Create extrusions benchy")(Catch::Benchmark::Chronometer meter) {
|
||||
meter.measure([&] { return Slic3r::Seams::Geometry::get_extrusions(print_object->layers()); });
|
||||
};
|
||||
|
||||
using namespace Slic3r::Seams;
|
||||
|
||||
BENCHMARK_ADVANCED("Create shells benchy")(Catch::Benchmark::Chronometer meter) {
|
||||
std::vector<Perimeters::LayerPerimeters> inputs;
|
||||
inputs.reserve(meter.runs());
|
||||
std::generate_n(std::back_inserter(inputs), meter.runs(), [&]() {
|
||||
return Slic3r::Seams::Perimeters::create_perimeters(
|
||||
projected, layer_infos, painting, params.perimeter
|
||||
);
|
||||
});
|
||||
meter.measure([&](const int i) {
|
||||
return Shells::create_shells(std::move(inputs[i]), params.max_distance);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
BENCHMARK_ADVANCED("Get layer infos benchy")(Catch::Benchmark::Chronometer meter) {
|
||||
meter.measure([&] {
|
||||
return Perimeters::get_layer_infos(
|
||||
print_object->layers(), params.perimeter.elephant_foot_compensation
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
BENCHMARK_ADVANCED("Create perimeters benchy")(Catch::Benchmark::Chronometer meter) {
|
||||
meter.measure([&] {
|
||||
return Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter);
|
||||
});
|
||||
};
|
||||
|
||||
BENCHMARK_ADVANCED("Generate aligned seam benchy")(Catch::Benchmark::Chronometer meter) {
|
||||
std::vector<Shells::Shells<>> inputs;
|
||||
inputs.reserve(meter.runs());
|
||||
std::generate_n(std::back_inserter(inputs), meter.runs(), [&]() {
|
||||
Slic3r::Seams::Perimeters::LayerPerimeters perimeters{
|
||||
Slic3r::Seams::Perimeters::create_perimeters(
|
||||
projected, layer_infos, painting, params.perimeter
|
||||
)};
|
||||
return Shells::create_shells(
|
||||
std::move(perimeters), params.max_distance
|
||||
);
|
||||
});
|
||||
meter.measure([&](const int i) {
|
||||
return Aligned::get_object_seams(
|
||||
std::move(inputs[i]), visibility_calculator, params.aligned
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
BENCHMARK_ADVANCED("Visibility constructor")(Catch::Benchmark::Chronometer meter) {
|
||||
using Visibility = Slic3r::ModelInfo::Visibility;
|
||||
std::vector<Catch::Benchmark::storage_for<Visibility>> storage(meter.runs());
|
||||
meter.measure([&](const int i) {
|
||||
storage[i].construct(transformation, volumes, params.visibility, []() {});
|
||||
});
|
||||
};
|
||||
|
||||
BENCHMARK_ADVANCED("Generate rear seam benchy")(Catch::Benchmark::Chronometer meter) {
|
||||
std::vector<Perimeters::LayerPerimeters> inputs;
|
||||
inputs.reserve(meter.runs());
|
||||
std::generate_n(std::back_inserter(inputs), meter.runs(), [&]() {
|
||||
return Slic3r::Seams::Perimeters::create_perimeters(
|
||||
projected, layer_infos, painting, params.perimeter
|
||||
);
|
||||
});
|
||||
meter.measure([&](const int i) {
|
||||
return Rear::get_object_seams(std::move(inputs[i]), params.rear_tolerance, params.rear_y_offset);
|
||||
});
|
||||
};
|
||||
|
||||
BENCHMARK_ADVANCED("Generate random seam benchy")(Catch::Benchmark::Chronometer meter) {
|
||||
std::vector<Perimeters::LayerPerimeters> inputs;
|
||||
inputs.reserve(meter.runs());
|
||||
std::generate_n(std::back_inserter(inputs), meter.runs(), [&]() {
|
||||
return Slic3r::Seams::Perimeters::create_perimeters(
|
||||
projected, layer_infos, painting, params.perimeter
|
||||
);
|
||||
});
|
||||
meter.measure([&](const int i) {
|
||||
return Random::get_object_seams(std::move(inputs[i]), params.random_seed);
|
||||
});
|
||||
};
|
||||
|
||||
Placer placer;
|
||||
BENCHMARK_ADVANCED("Init seam placer aligned")(Catch::Benchmark::Chronometer meter) {
|
||||
meter.measure([&] {
|
||||
return placer.init(print->objects(), params, [](){});
|
||||
});
|
||||
};
|
||||
|
||||
SECTION("Place seam"){
|
||||
using namespace Slic3r;
|
||||
Placer placer;
|
||||
placer.init(print->objects(), params, [](){});
|
||||
std::vector<std::pair<const Layer*, const ExtrusionLoop*>> loops;
|
||||
|
||||
const PrintObject* object{print->objects().front()};
|
||||
for (const Layer* layer :object->layers()) {
|
||||
for (const LayerSlice& lslice : layer->lslices_ex) {
|
||||
for (const LayerIsland &island : lslice.islands) {
|
||||
const LayerRegion &layer_region = *layer->get_region(island.perimeters.region());
|
||||
for (uint32_t perimeter_id : island.perimeters) {
|
||||
const auto *entity_collection{static_cast<const ExtrusionEntityCollection*>(layer_region.perimeters().entities[perimeter_id])};
|
||||
if (entity_collection != nullptr) {
|
||||
for (const ExtrusionEntity *entity : *entity_collection) {
|
||||
const auto loop{static_cast<const ExtrusionLoop*>(entity)};
|
||||
if (loop == nullptr) {
|
||||
continue;
|
||||
}
|
||||
loops.emplace_back(layer, loop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BENCHMARK_ADVANCED("Place seam benchy")(Catch::Benchmark::Chronometer meter) {
|
||||
meter.measure([&] {
|
||||
for (const auto &[layer, loop] : loops) {
|
||||
placer.place_seam(layer, *loop, {0, 0});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -251,8 +251,8 @@ SCENARIO("Cooling integration tests", "[Cooling]") {
|
||||
l = line.dist_Z(self);
|
||||
if (l > 0.) {
|
||||
if (!layer_times.empty()) { // Ignore anything before first z move.
|
||||
layer_times.back() += 60. * std::abs(l) / line.new_F(self);
|
||||
}
|
||||
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())];
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,12 +2,19 @@
|
||||
#define SLIC3R_TEST_DATA_HPP
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Format/3mf.hpp"
|
||||
#include "libslic3r/GCode/ModelVisibility.hpp"
|
||||
#include "libslic3r/GCode/SeamGeometry.hpp"
|
||||
#include "libslic3r/GCode/SeamPerimeters.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/GCode/SeamPlacer.hpp"
|
||||
#include "libslic3r/GCode/SeamAligned.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Slic3r { namespace Test {
|
||||
@@ -39,14 +46,13 @@ enum class TestMesh {
|
||||
};
|
||||
|
||||
// Neccessary for <c++17
|
||||
struct TestMeshHash {
|
||||
std::size_t operator()(TestMesh tm) const {
|
||||
return static_cast<std::size_t>(tm);
|
||||
}
|
||||
struct TestMeshHash
|
||||
{
|
||||
std::size_t operator()(TestMesh tm) const { return static_cast<std::size_t>(tm); }
|
||||
};
|
||||
|
||||
/// Mesh enumeration to name mapping
|
||||
extern const std::unordered_map<TestMesh, const char*, TestMeshHash> mesh_names;
|
||||
extern const std::unordered_map<TestMesh, const char *, TestMeshHash> mesh_names;
|
||||
|
||||
/// Port of Slic3r::Test::mesh
|
||||
/// Basic cubes/boxes should call TriangleMesh::make_cube() directly and rescale/translate it
|
||||
@@ -56,35 +62,171 @@ TriangleMesh mesh(TestMesh m, Vec3d translate, Vec3d scale = Vec3d(1.0, 1.0, 1.0
|
||||
TriangleMesh mesh(TestMesh m, Vec3d translate, double scale = 1.0);
|
||||
|
||||
/// Templated function to see if two values are equivalent (+/- epsilon)
|
||||
template <typename T>
|
||||
bool _equiv(const T& a, const T& b) { return std::abs(a - b) < EPSILON; }
|
||||
template<typename T> bool _equiv(const T &a, const T &b) { return std::abs(a - b) < EPSILON; }
|
||||
|
||||
template <typename T>
|
||||
bool _equiv(const T& a, const T& b, double epsilon) { return abs(a - b) < epsilon; }
|
||||
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, 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);
|
||||
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,
|
||||
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);
|
||||
void init_and_process_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
|
||||
void init_and_process_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
|
||||
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
|
||||
);
|
||||
void init_and_process_print(
|
||||
std::initializer_list<TestMesh> meshes,
|
||||
Slic3r::Print &print,
|
||||
std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items,
|
||||
bool comments = false
|
||||
);
|
||||
void init_and_process_print(
|
||||
std::initializer_list<TriangleMesh> meshes,
|
||||
Slic3r::Print &print,
|
||||
std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items,
|
||||
bool comments = false
|
||||
);
|
||||
|
||||
std::string gcode(Print& print);
|
||||
std::string gcode(Print &print);
|
||||
|
||||
std::string slice(std::initializer_list<TestMesh> meshes, const DynamicPrintConfig &config, bool comments = false);
|
||||
std::string slice(std::initializer_list<TriangleMesh> meshes, const DynamicPrintConfig &config, bool comments = false);
|
||||
std::string slice(std::initializer_list<TestMesh> meshes, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
|
||||
std::string slice(std::initializer_list<TriangleMesh> meshes, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
|
||||
std::string slice(
|
||||
std::initializer_list<TestMesh> meshes, const DynamicPrintConfig &config, bool comments = false
|
||||
);
|
||||
std::string slice(
|
||||
std::initializer_list<TriangleMesh> meshes,
|
||||
const DynamicPrintConfig &config,
|
||||
bool comments = false
|
||||
);
|
||||
std::string slice(
|
||||
std::initializer_list<TestMesh> meshes,
|
||||
std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items,
|
||||
bool comments = false
|
||||
);
|
||||
std::string slice(
|
||||
std::initializer_list<TriangleMesh> meshes,
|
||||
std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items,
|
||||
bool comments = false
|
||||
);
|
||||
|
||||
bool contains(const std::string &data, const std::string &pattern);
|
||||
bool contains_regex(const std::string &data, const std::string &pattern);
|
||||
|
||||
} } // namespace Slic3r::Test
|
||||
inline std::unique_ptr<Print> process_3mf(const std::filesystem::path &path) {
|
||||
DynamicPrintConfig config;
|
||||
auto print{std::make_unique<Print>()};
|
||||
Model model;
|
||||
|
||||
ConfigSubstitutionContext context{ForwardCompatibilitySubstitutionRule::Disable};
|
||||
load_3mf(path.string().c_str(), config, context, &model, false);
|
||||
|
||||
Slic3r::Test::init_print(std::vector<TriangleMesh>{}, *print, model, config);
|
||||
print->process();
|
||||
|
||||
return print;
|
||||
}
|
||||
|
||||
static std::map<std::string, std::unique_ptr<Print>> prints_3mfs;
|
||||
// Lazy getter, to avoid processing the 3mf multiple times, it already takes ages.
|
||||
inline Print *get_print(const std::filesystem::path &file_path) {
|
||||
if (!prints_3mfs.count(file_path.string())) {
|
||||
prints_3mfs[file_path.string()] = process_3mf(file_path.string());
|
||||
}
|
||||
return prints_3mfs[file_path.string()].get();
|
||||
}
|
||||
|
||||
inline void serialize_seam(std::ostream &output, const std::vector<std::vector<Seams::SeamPerimeterChoice>> &seam) {
|
||||
output << "x,y,z,layer_index" << std::endl;
|
||||
|
||||
for (const std::vector<Seams::SeamPerimeterChoice> &layer : seam) {
|
||||
if (layer.empty()) {
|
||||
continue;
|
||||
}
|
||||
const Seams::SeamPerimeterChoice &choice{layer.front()};
|
||||
|
||||
// clang-format off
|
||||
output
|
||||
<< choice.choice.position.x() << ","
|
||||
<< choice.choice.position.y() << ","
|
||||
<< choice.perimeter.slice_z << ","
|
||||
<< choice.perimeter.layer_index << std::endl;
|
||||
// clang-format on
|
||||
}
|
||||
}
|
||||
|
||||
struct SeamsFixture
|
||||
{
|
||||
const std::filesystem::path file_3mf{
|
||||
std::filesystem::path{TEST_DATA_DIR} / std::filesystem::path{"seam_test_object.3mf"}};
|
||||
const Print *print{Test::get_print(file_3mf)};
|
||||
const PrintObject *print_object{print->objects()[0]};
|
||||
|
||||
Seams::Params params{Seams::Placer::get_params(print->full_print_config())};
|
||||
|
||||
const Transform3d transformation{print_object->trafo_centered()};
|
||||
const ModelVolumePtrs &volumes{print_object->model_object()->volumes};
|
||||
Seams::ModelInfo::Painting painting{transformation, volumes};
|
||||
|
||||
const std::vector<Seams::Geometry::Extrusions> extrusions{
|
||||
Seams::Geometry::get_extrusions(print_object->layers())};
|
||||
const Seams::Perimeters::LayerInfos layer_infos{Seams::Perimeters::get_layer_infos(
|
||||
print_object->layers(), params.perimeter.elephant_foot_compensation
|
||||
)};
|
||||
const std::vector<Seams::Geometry::BoundedPolygons> projected{
|
||||
Seams::Geometry::project_to_geometry(extrusions, params.max_distance)};
|
||||
|
||||
const ModelInfo::Visibility visibility{transformation, volumes, params.visibility, [](){}};
|
||||
Seams::Aligned::VisibilityCalculator
|
||||
visibility_calculator{visibility, params.convex_visibility_modifier, params.concave_visibility_modifier};
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::Test
|
||||
|
||||
#endif // SLIC3R_TEST_DATA_HPP
|
||||
|
||||
@@ -29,6 +29,7 @@ SCENARIO("Origin manipulation", "[GCode]") {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Wiping speeds", "[GCode]") {
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
@@ -60,10 +61,11 @@ TEST_CASE("Wiping speeds", "[GCode]") {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
bool has_moves_below_z_offset(const DynamicPrintConfig& config) {
|
||||
GCodeReader parser;
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
||||
@@ -73,10 +75,11 @@ bool has_moves_below_z_offset(const DynamicPrintConfig& config) {
|
||||
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++;
|
||||
}
|
||||
}
|
||||
});
|
||||
return moves_below_z_offset > 0;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Z moves with offset", "[GCode]") {
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
@@ -103,7 +106,7 @@ std::optional<double> parse_axis(const std::string& line, const std::string& axi
|
||||
return std::stod(matchedValue);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This tests the following behavior:
|
||||
@@ -138,10 +141,12 @@ TEST_CASE("Extrusion, travels, temeperatures", "[GCode]") {
|
||||
Model model;
|
||||
Test::init_print({TestMesh::cube_20x20x20}, print, model, config, false, 2);
|
||||
std::string gcode = Test::gcode(print);
|
||||
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream gcode_file{"sequential_print.gcode"};
|
||||
gcode_file << gcode;
|
||||
}
|
||||
|
||||
parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
INFO("Unexpected E argument");
|
||||
CHECK(!line.has_e());
|
||||
@@ -159,11 +164,11 @@ TEST_CASE("Extrusion, travels, temeperatures", "[GCode]") {
|
||||
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;
|
||||
@@ -177,7 +182,7 @@ TEST_CASE("Extrusion, travels, temeperatures", "[GCode]") {
|
||||
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});
|
||||
}
|
||||
@@ -237,12 +242,13 @@ TEST_CASE("M73s have correct percent values", "[GCode]") {
|
||||
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({
|
||||
@@ -258,7 +264,7 @@ TEST_CASE("M73s have correct percent values", "[GCode]") {
|
||||
std::ofstream gcode_file{"M73_2_copies.gcode"};
|
||||
gcode_file << Test::gcode(print);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Two objects") {
|
||||
config.set_deserialize_strict({
|
||||
@@ -307,8 +313,7 @@ TEST_CASE("M201 for acceleation reset", "[GCode]") {
|
||||
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;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
|
||||
SCENARIO("set_speed emits values with fixed-point output.", "[GCodeWriter]") {
|
||||
|
||||
GIVEN("GCodeWriter instance") {
|
||||
|
||||
@@ -71,10 +71,9 @@ SCENARIO("Perimeter nesting", "[Perimeters]")
|
||||
nullptr,
|
||||
nullptr,
|
||||
// cache:
|
||||
upper_layer_polygons_cache,
|
||||
lower_layer_polygons_cache,
|
||||
// output:
|
||||
loops, gap_fill, fill_expolygons,fill_expolygons_no_overlap,0);
|
||||
loops, gap_fill, fill_expolygons,fill_expolygons_no_overlap);
|
||||
|
||||
THEN("expected number of collections") {
|
||||
REQUIRE(loops.entities.size() == data.expolygons.size());
|
||||
|
||||
164
tests/fff_print/test_seam_aligned.cpp
Normal file
164
tests/fff_print/test_seam_aligned.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <libslic3r/GCode/SeamAligned.hpp>
|
||||
#include "test_data.hpp"
|
||||
#include <fstream>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Seams;
|
||||
|
||||
constexpr bool debug_files{false};
|
||||
|
||||
namespace AlignedTest {
|
||||
Perimeters::Perimeter get_perimeter() {
|
||||
const double slice_z{1.0};
|
||||
const std::size_t layer_index{};
|
||||
std::vector<Vec2d> positions{{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}, {0.0, 1.0}, {0.0, 0.5}};
|
||||
std::vector<double> angles(positions.size(), -M_PI / 2.0);
|
||||
angles[4] = 0.0;
|
||||
std::vector<Perimeters::PointType> point_types(positions.size(), Perimeters::PointType::common);
|
||||
std::vector<Perimeters::PointClassification>
|
||||
point_classifications{positions.size(), Perimeters::PointClassification::common};
|
||||
std::vector<Perimeters::AngleType> angle_type(positions.size(), Perimeters::AngleType::concave);
|
||||
angle_type[4] = Perimeters::AngleType::smooth;
|
||||
|
||||
return {
|
||||
slice_z,
|
||||
layer_index,
|
||||
false,
|
||||
std::move(positions),
|
||||
std::move(angles),
|
||||
std::move(point_types),
|
||||
std::move(point_classifications),
|
||||
std::move(angle_type)};
|
||||
}
|
||||
} // namespace AlignedTest
|
||||
|
||||
TEST_CASE("Snap to angle", "[Seams][SeamAligned]") {
|
||||
const Vec2d point{0.0, 0.4};
|
||||
const std::size_t search_start{4};
|
||||
const Perimeters::Perimeter perimeter{AlignedTest::get_perimeter()};
|
||||
|
||||
std::optional<std::size_t> snapped_to{
|
||||
Aligned::Impl::snap_to_angle(point, search_start, perimeter, 0.5)};
|
||||
|
||||
REQUIRE(snapped_to);
|
||||
CHECK(*snapped_to == 0);
|
||||
|
||||
snapped_to = Aligned::Impl::snap_to_angle(point, search_start, perimeter, 0.3);
|
||||
REQUIRE(!snapped_to);
|
||||
}
|
||||
|
||||
TEST_CASE("Get seam options", "[Seams][SeamAligned]") {
|
||||
Perimeters::Perimeter perimeter{AlignedTest::get_perimeter()};
|
||||
const Vec2d prefered_position{0.0, 0.3};
|
||||
|
||||
Aligned::Impl::SeamOptions options{Aligned::Impl::get_seam_options(
|
||||
perimeter, prefered_position, *perimeter.common_points.common_points, 0.4
|
||||
)};
|
||||
|
||||
CHECK(options.closest == 4);
|
||||
CHECK(options.adjacent == 0);
|
||||
CHECK((options.on_edge - Vec2d{0.0, 0.3}).norm() == Approx(0.0));
|
||||
REQUIRE(options.snapped);
|
||||
CHECK(options.snapped == 0);
|
||||
}
|
||||
|
||||
struct PickSeamOptionFixture
|
||||
{
|
||||
Perimeters::Perimeter perimeter{AlignedTest::get_perimeter()};
|
||||
|
||||
Aligned::Impl::SeamOptions options{
|
||||
4, // closest
|
||||
0, // adjacent
|
||||
true, // forward
|
||||
false, // snapped
|
||||
Vec2d{0.0, 0.3}, // on_edge
|
||||
};
|
||||
};
|
||||
|
||||
TEST_CASE_METHOD(PickSeamOptionFixture, "Pick seam option", "[Seams][SeamAligned]") {
|
||||
auto [previous_index, next_index, position]{pick_seam_option(perimeter, options)};
|
||||
CHECK(previous_index == next_index);
|
||||
CHECK((position - Vec2d{0.0, 0.0}).norm() == Approx(0.0));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(PickSeamOptionFixture, "Pick seam option picks enforcer", "[Seams][SeamAligned]") {
|
||||
perimeter.point_types[4] = Perimeters::PointType::enforcer;
|
||||
|
||||
auto [previous_index, next_index, position]{pick_seam_option(perimeter, options)};
|
||||
CHECK(previous_index == next_index);
|
||||
CHECK((position - Vec2d{0.0, 0.5}).norm() == Approx(0.0));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(PickSeamOptionFixture, "Nearest point", "[Seams][SeamAligned]") {
|
||||
const std::optional<SeamChoice> result{Aligned::Impl::Nearest{Vec2d{0.4, -0.1}, 0.2}(
|
||||
perimeter, Perimeters::PointType::common, Perimeters::PointClassification::common
|
||||
)};
|
||||
CHECK(result->previous_index == 0);
|
||||
CHECK(result->next_index == 1);
|
||||
CHECK((result->position - Vec2d{0.4, 0.0}).norm() == Approx(0.0));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(PickSeamOptionFixture, "Least visible point", "[Seams][SeamAligned]") {
|
||||
std::vector<double> precalculated_visibility{};
|
||||
for (std::size_t i{0}; i < perimeter.positions.size(); ++i) {
|
||||
precalculated_visibility.push_back(-static_cast<double>(i));
|
||||
}
|
||||
Aligned::Impl::LeastVisible least_visible{precalculated_visibility};
|
||||
const std::optional<SeamChoice> result{least_visible(
|
||||
perimeter, Perimeters::PointType::common, Perimeters::PointClassification::common
|
||||
)};
|
||||
CHECK(result->previous_index == 4);
|
||||
CHECK(result->next_index == 4);
|
||||
CHECK((result->position - Vec2d{0.0, 0.5}).norm() == Approx(0.0));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(Test::SeamsFixture, "Generate aligned seam", "[Seams][SeamAligned][Integration]") {
|
||||
Seams::Perimeters::LayerPerimeters perimeters{
|
||||
Seams::Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter)};
|
||||
Seams::Shells::Shells<> shells{
|
||||
Seams::Shells::create_shells(std::move(perimeters), params.max_distance)};
|
||||
|
||||
const std::vector<std::vector<SeamPerimeterChoice>> seam{
|
||||
Aligned::get_object_seams(std::move(shells), visibility_calculator, params.aligned)};
|
||||
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream csv{"aligned_seam.csv"};
|
||||
Test::serialize_seam(csv, seam);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(Test::SeamsFixture, "Calculate visibility", "[Seams][SeamAligned][Integration]") {
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream csv{"visibility.csv"};
|
||||
csv << "x,y,z,visibility,total_visibility" << std::endl;
|
||||
|
||||
Seams::Perimeters::LayerPerimeters perimeters{
|
||||
Seams::Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter)};
|
||||
|
||||
Seams::Shells::Shells<> shells{
|
||||
Seams::Shells::create_shells(std::move(perimeters), params.max_distance)};
|
||||
for (const Shells::Shell<> &shell : shells) {
|
||||
for (const Shells::Slice<> &slice : shell) {
|
||||
for (std::size_t index{0}; index < slice.boundary.positions.size(); ++index) {
|
||||
const Vec2d &position{slice.boundary.positions[index]};
|
||||
const double point_visibility{visibility.calculate_point_visibility(
|
||||
to_3d(position.cast<float>(), slice.boundary.slice_z)
|
||||
)};
|
||||
const double total_visibility{
|
||||
visibility_calculator(SeamChoice{index, index, position}, slice.boundary)};
|
||||
|
||||
// clang-format off
|
||||
csv <<
|
||||
position.x() << "," <<
|
||||
position.y() << "," <<
|
||||
slice.boundary.slice_z << "," <<
|
||||
point_visibility << "," <<
|
||||
total_visibility << std::endl;
|
||||
// clang-format on
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
145
tests/fff_print/test_seam_geometry.cpp
Normal file
145
tests/fff_print/test_seam_geometry.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <libslic3r/GCode/SeamGeometry.hpp>
|
||||
#include <libslic3r/Geometry.hpp>
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("Lists mapping", "[Seams][SeamGeometry]") {
|
||||
// clang-format off
|
||||
std::vector<std::vector<int>> list_of_lists{
|
||||
{},
|
||||
{7, 2, 3},
|
||||
{9, 6, 3, 6, 7},
|
||||
{1, 1, 3},
|
||||
{1},
|
||||
{3},
|
||||
{1},
|
||||
{},
|
||||
{3}
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
std::vector<std::size_t> sizes;
|
||||
sizes.reserve(list_of_lists.size());
|
||||
for (const std::vector<int> &list : list_of_lists) {
|
||||
sizes.push_back(list.size());
|
||||
}
|
||||
|
||||
const auto [mapping, bucket_cout]{Seams::Geometry::get_mapping(
|
||||
sizes,
|
||||
[&](const std::size_t layer_index,
|
||||
const std::size_t item_index) -> Seams::Geometry::MappingOperatorResult {
|
||||
unsigned max_diff{0};
|
||||
std::optional<std::size_t> index;
|
||||
const std::vector<int> &layer{list_of_lists[layer_index]};
|
||||
const std::vector<int> &next_layer{list_of_lists[layer_index + 1]};
|
||||
for (std::size_t i{0}; i < next_layer.size(); ++i) {
|
||||
const long diff{std::abs(next_layer[i] - layer[item_index])};
|
||||
if (diff > max_diff) {
|
||||
max_diff = diff;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
if (!index) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::pair{*index, static_cast<double>(max_diff)};
|
||||
}
|
||||
)};
|
||||
|
||||
// clang-format off
|
||||
CHECK(mapping == std::vector<std::vector<std::size_t>>{
|
||||
{},
|
||||
{0, 1, 2},
|
||||
{1, 3, 0, 4, 5},
|
||||
{1, 6, 7},
|
||||
{7},
|
||||
{7},
|
||||
{7},
|
||||
{},
|
||||
{8}
|
||||
});
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
TEST_CASE("Vertex angle calculation counterclockwise", "[Seams][SeamGeometry]") {
|
||||
std::vector<Vec2d> points{Vec2d{0, 0}, Vec2d{1, 0}, Vec2d{1, 1}, Vec2d{0, 1}};
|
||||
std::vector<double> angles{Seams::Geometry::get_vertex_angles(points, 0.1)};
|
||||
|
||||
CHECK(angles.size() == 4);
|
||||
for (const double angle : angles) {
|
||||
CHECK(angle == Approx(-M_PI / 2));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Vertex angle calculation clockwise", "[Seams][SeamGeometry]") {
|
||||
std::vector<Vec2d> points = {Vec2d{0, 0}, Vec2d{0, 1}, Vec2d{1, 1}, Vec2d{1, 0}};
|
||||
std::vector<double> angles = Seams::Geometry::get_vertex_angles(points, 0.1);
|
||||
|
||||
CHECK(angles.size() == 4);
|
||||
for (const double angle : angles) {
|
||||
CHECK(angle == Approx(M_PI / 2));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Vertex angle calculation small convex", "[Seams][SeamGeometry]") {
|
||||
std::vector<Vec2d> points = {Vec2d{0, 0}, Vec2d{-0.01, 1}, Vec2d{0, 2}, Vec2d{-2, 1}};
|
||||
std::vector<double> angles = Seams::Geometry::get_vertex_angles(points, 0.1);
|
||||
|
||||
CHECK(angles.size() == 4);
|
||||
CHECK(angles[1] > 0);
|
||||
CHECK(angles[1] < 0.02);
|
||||
}
|
||||
|
||||
TEST_CASE("Vertex angle calculation small concave", "[Seams][SeamGeometry]") {
|
||||
std::vector<Vec2d> points = {Vec2d{0, 0}, Vec2d{0.01, 1}, Vec2d{0, 2}, Vec2d{-2, 1}};
|
||||
std::vector<double> angles = Seams::Geometry::get_vertex_angles(points, 0.1);
|
||||
|
||||
CHECK(angles.size() == 4);
|
||||
CHECK(angles[1] < 0);
|
||||
CHECK(angles[1] > -0.02);
|
||||
}
|
||||
|
||||
TEST_CASE("Vertex angle is rotation agnostic", "[Seams][SeamGeometry]") {
|
||||
std::vector<Vec2d> points = {Vec2d{0, 0}, Vec2d{0.01, 1}, Vec2d{0, 2}, Vec2d{-2, 1}};
|
||||
std::vector<double> angles = Seams::Geometry::get_vertex_angles(points, 0.1);
|
||||
|
||||
Points polygon_points;
|
||||
using std::transform, std::back_inserter;
|
||||
transform(points.begin(), points.end(), back_inserter(polygon_points), [](const Vec2d &point) {
|
||||
return scaled(point);
|
||||
});
|
||||
Polygon polygon{polygon_points};
|
||||
polygon.rotate(M_PI - Slic3r::Geometry::deg2rad(10.0));
|
||||
|
||||
std::vector<Vec2d> rotated_points;
|
||||
using std::transform, std::back_inserter;
|
||||
transform(
|
||||
polygon.points.begin(), polygon.points.end(), back_inserter(rotated_points),
|
||||
[](const Point &point) { return unscaled(point); }
|
||||
);
|
||||
|
||||
std::vector<double> rotated_angles = Seams::Geometry::get_vertex_angles(points, 0.1);
|
||||
CHECK(rotated_angles[1] == Approx(angles[1]));
|
||||
}
|
||||
|
||||
TEST_CASE("Calculate overhangs", "[Seams][SeamGeometry]") {
|
||||
const ExPolygon square{
|
||||
scaled(Vec2d{0.0, 0.0}),
|
||||
scaled(Vec2d{1.0, 0.0}),
|
||||
scaled(Vec2d{1.0, 1.0}),
|
||||
scaled(Vec2d{0.0, 1.0})
|
||||
};
|
||||
const std::vector<Vec2d> points{Seams::Geometry::unscaled(square.contour.points)};
|
||||
ExPolygon previous_layer{square};
|
||||
previous_layer.translate(scaled(Vec2d{-0.5, 0}));
|
||||
AABBTreeLines::LinesDistancer<Linef> previous_layer_distancer{
|
||||
to_unscaled_linesf({previous_layer})};
|
||||
const std::vector<double> overhangs{
|
||||
Seams::Geometry::get_overhangs(points, previous_layer_distancer, 0.5)};
|
||||
REQUIRE(overhangs.size() == points.size());
|
||||
CHECK_THAT(overhangs, Catch::Matchers::Approx(std::vector<double>{
|
||||
0.0, M_PI / 4.0, M_PI / 4.0, 0.0
|
||||
}));
|
||||
}
|
||||
180
tests/fff_print/test_seam_perimeters.cpp
Normal file
180
tests/fff_print/test_seam_perimeters.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/GCode/SeamPerimeters.hpp"
|
||||
#include "libslic3r/Layer.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
#include <libslic3r/GCode/SeamGeometry.hpp>
|
||||
#include <libslic3r/Geometry.hpp>
|
||||
#include <fstream>
|
||||
|
||||
#include "test_data.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Seams;
|
||||
|
||||
constexpr bool debug_files{false};
|
||||
|
||||
const ExPolygon square{
|
||||
scaled(Vec2d{0.0, 0.0}), scaled(Vec2d{1.0, 0.0}), scaled(Vec2d{1.0, 1.0}),
|
||||
scaled(Vec2d{0.0, 1.0})};
|
||||
|
||||
TEST_CASE("Oversample painted", "[Seams][SeamPerimeters]") {
|
||||
auto is_painted{[](const Vec3f &position, float radius) {
|
||||
return (position - Vec3f{0.5, 0.0, 1.0}).norm() < radius;
|
||||
}};
|
||||
std::vector<Vec2d> points{Perimeters::Impl::oversample_painted(
|
||||
Seams::Geometry::unscaled(square.contour.points), is_painted, 1.0, 0.2
|
||||
)};
|
||||
|
||||
REQUIRE(points.size() == 8);
|
||||
CHECK((points[1] - Vec2d{0.2, 0.0}).norm() == Approx(0.0));
|
||||
|
||||
points = Perimeters::Impl::oversample_painted(
|
||||
Seams::Geometry::unscaled(square.contour.points), is_painted, 1.0, 0.199
|
||||
);
|
||||
CHECK(points.size() == 9);
|
||||
}
|
||||
|
||||
TEST_CASE("Remove redundant points", "[Seams][SeamPerimeters]") {
|
||||
using Perimeters::PointType;
|
||||
using Perimeters::PointClassification;
|
||||
|
||||
std::vector<Vec2d> points{{0.0, 0.0}, {1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0},
|
||||
{3.0, 1.0}, {3.0, 2.0}, {0.0, 2.0}};
|
||||
std::vector<PointType> point_types{PointType::common,
|
||||
PointType::enforcer, // Should keep this.
|
||||
PointType::enforcer, // Should keep this.
|
||||
PointType::blocker,
|
||||
PointType::blocker, // Should remove this.
|
||||
PointType::blocker, PointType::common};
|
||||
|
||||
const auto [resulting_points, resulting_point_types]{
|
||||
Perimeters::Impl::remove_redundant_points(points, point_types, 0.1)};
|
||||
|
||||
REQUIRE(resulting_points.size() == 6);
|
||||
REQUIRE(resulting_point_types.size() == 6);
|
||||
CHECK((resulting_points[3] - Vec2d{3.0, 0.0}).norm() == Approx(0.0));
|
||||
CHECK((resulting_points[4] - Vec2d{3.0, 2.0}).norm() == Approx(0.0));
|
||||
CHECK(resulting_point_types[3] == PointType::blocker);
|
||||
CHECK(resulting_point_types[4] == PointType::blocker);
|
||||
}
|
||||
|
||||
TEST_CASE("Perimeter constructs KD trees", "[Seams][SeamPerimeters]") {
|
||||
using Perimeters::PointType;
|
||||
using Perimeters::PointClassification;
|
||||
using Perimeters::AngleType;
|
||||
|
||||
std::vector<Vec2d> positions{Vec2d{0.0, 0.0}, Vec2d{1.0, 0.0}, Vec2d{1.0, 1.0}, Vec2d{0.0, 1.0}};
|
||||
std::vector<double> angles(4, -M_PI / 2.0);
|
||||
std::vector<PointType>
|
||||
point_types{PointType::enforcer, PointType::blocker, PointType::common, PointType::common};
|
||||
std::vector<PointClassification> point_classifications{
|
||||
PointClassification::overhang, PointClassification::embedded, PointClassification::embedded,
|
||||
PointClassification::common};
|
||||
std::vector<AngleType>
|
||||
angle_types{AngleType::convex, AngleType::concave, AngleType::smooth, AngleType::smooth};
|
||||
Perimeters::Perimeter perimeter{
|
||||
3.0,
|
||||
2,
|
||||
false,
|
||||
std::move(positions),
|
||||
std::move(angles),
|
||||
std::move(point_types),
|
||||
std::move(point_classifications),
|
||||
std::move(angle_types)};
|
||||
|
||||
CHECK(perimeter.enforced_points.overhanging_points);
|
||||
CHECK(perimeter.blocked_points.embedded_points);
|
||||
CHECK(perimeter.common_points.common_points);
|
||||
CHECK(perimeter.common_points.embedded_points);
|
||||
}
|
||||
|
||||
using std::filesystem::path;
|
||||
|
||||
constexpr const char *to_string(Perimeters::PointType point_type) {
|
||||
using Perimeters::PointType;
|
||||
|
||||
switch (point_type) {
|
||||
case PointType::enforcer: return "enforcer";
|
||||
case PointType::blocker: return "blocker";
|
||||
case PointType::common: return "common";
|
||||
}
|
||||
throw std::runtime_error("Unreachable");
|
||||
}
|
||||
|
||||
constexpr const char *to_string(Perimeters::PointClassification point_classification) {
|
||||
using Perimeters::PointClassification;
|
||||
|
||||
switch (point_classification) {
|
||||
case PointClassification::embedded: return "embedded";
|
||||
case PointClassification::overhang: return "overhang";
|
||||
case PointClassification::common: return "common";
|
||||
}
|
||||
throw std::runtime_error("Unreachable");
|
||||
}
|
||||
|
||||
constexpr const char *to_string(Perimeters::AngleType angle_type) {
|
||||
using Perimeters::AngleType;
|
||||
|
||||
switch (angle_type) {
|
||||
case AngleType::convex: return "convex";
|
||||
case AngleType::concave: return "concave";
|
||||
case AngleType::smooth: return "smooth";
|
||||
}
|
||||
throw std::runtime_error("Unreachable");
|
||||
}
|
||||
|
||||
void serialize_shell(std::ostream &output, const Shells::Shell<Perimeters::Perimeter> &shell) {
|
||||
output << "x,y,z,point_type,point_classification,angle_type,layer_index,"
|
||||
"point_index,distance,distance_to_previous,is_degenerate"
|
||||
<< std::endl;
|
||||
|
||||
for (std::size_t perimeter_index{0}; perimeter_index < shell.size(); ++perimeter_index) {
|
||||
const Shells::Slice<> &slice{shell[perimeter_index]};
|
||||
const Perimeters::Perimeter &perimeter{slice.boundary};
|
||||
const std::vector<Vec2d> &points{perimeter.positions};
|
||||
|
||||
double total_distance{0.0};
|
||||
for (std::size_t point_index{0}; point_index < perimeter.point_types.size(); ++point_index) {
|
||||
const Vec3d point{to_3d(points[point_index], perimeter.slice_z)};
|
||||
const Perimeters::PointType point_type{perimeter.point_types[point_index]};
|
||||
const Perimeters::PointClassification point_classification{
|
||||
perimeter.point_classifications[point_index]};
|
||||
const Perimeters::AngleType angle_type{perimeter.angle_types[point_index]};
|
||||
const std::size_t layer_index{slice.layer_index};
|
||||
const std::size_t previous_index{point_index == 0 ? points.size() - 1 : point_index - 1};
|
||||
const double distance_to_previous{(points[point_index] - points[previous_index]).norm()};
|
||||
total_distance += point_index == 0 ? 0.0 : distance_to_previous;
|
||||
const double distance{total_distance};
|
||||
const bool is_degenerate{perimeter.is_degenerate};
|
||||
|
||||
// clang-format off
|
||||
output
|
||||
<< point.x() << ","
|
||||
<< point.y() << ","
|
||||
<< point.z() << ","
|
||||
<< to_string(point_type) << ","
|
||||
<< to_string(point_classification) << ","
|
||||
<< to_string(angle_type) << ","
|
||||
<< layer_index << ","
|
||||
<< point_index << ","
|
||||
<< distance << ","
|
||||
<< distance_to_previous << ","
|
||||
<< is_degenerate << std::endl;
|
||||
// clang-format on
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(Test::SeamsFixture, "Create perimeters", "[Seams][SeamPerimeters][Integration]") {
|
||||
Seams::Perimeters::LayerPerimeters perimeters{
|
||||
Seams::Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter)};
|
||||
|
||||
Seams::Shells::Shells<> shells{
|
||||
Seams::Shells::create_shells(std::move(perimeters), params.max_distance)};
|
||||
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream csv{"perimeters.csv"};
|
||||
serialize_shell(csv, shells[0]);
|
||||
}
|
||||
}
|
||||
96
tests/fff_print/test_seam_random.cpp
Normal file
96
tests/fff_print/test_seam_random.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <libslic3r/GCode/SeamRandom.hpp>
|
||||
#include "test_data.hpp"
|
||||
#include <fstream>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Seams;
|
||||
|
||||
constexpr bool debug_files{false};
|
||||
|
||||
namespace RandomTest {
|
||||
Perimeters::Perimeter get_perimeter() {
|
||||
const double slice_z{1.0};
|
||||
const std::size_t layer_index{};
|
||||
std::vector<Vec2d> positions{{0.0, 0.0}, {0.5, 0.0}, {1.0, 0.0}};
|
||||
std::vector<double> angles(positions.size(), -M_PI / 2.0);
|
||||
std::vector<Perimeters::PointType> point_types(positions.size(), Perimeters::PointType::common);
|
||||
std::vector<Perimeters::PointClassification>
|
||||
point_classifications{positions.size(), Perimeters::PointClassification::common};
|
||||
std::vector<Perimeters::AngleType> angle_type(positions.size(), Perimeters::AngleType::concave);
|
||||
|
||||
return {
|
||||
slice_z,
|
||||
layer_index,
|
||||
false,
|
||||
std::move(positions),
|
||||
std::move(angles),
|
||||
std::move(point_types),
|
||||
std::move(point_classifications),
|
||||
std::move(angle_type)};
|
||||
}
|
||||
} // namespace RandomTest
|
||||
|
||||
double get_chi2_uniform(const std::vector<double> &data, double min, double max, const std::size_t bin_count) {
|
||||
std::vector<std::size_t> bins(bin_count);
|
||||
const double bin_size{(max - min) / bin_count};
|
||||
const double expected_frequncy{static_cast<double>(data.size()) / bin_count};
|
||||
|
||||
for (const double value : data) {
|
||||
auto bin{static_cast<int>(std::floor((value - min) / bin_size))};
|
||||
bins[bin]++;
|
||||
}
|
||||
|
||||
return std::accumulate(bins.begin(), bins.end(), 0.0, [&](const double total, const std::size_t count_in_bin){
|
||||
return total + std::pow(static_cast<double>(count_in_bin - expected_frequncy), 2.0) / expected_frequncy;
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("Random is uniform", "[Seams][SeamRandom]") {
|
||||
const int seed{42};
|
||||
std::mt19937 random_engine{seed};
|
||||
const Random::Impl::Random random{random_engine};
|
||||
Perimeters::Perimeter perimeter{RandomTest::get_perimeter()};
|
||||
|
||||
std::vector<double> x_positions;
|
||||
const std::size_t count{1001};
|
||||
x_positions.reserve(count);
|
||||
std::generate_n(std::back_inserter(x_positions), count, [&]() {
|
||||
std::optional<SeamChoice> choice{
|
||||
random(perimeter, Perimeters::PointType::common, Perimeters::PointClassification::common)};
|
||||
return choice->position.x();
|
||||
});
|
||||
const std::size_t degrees_of_freedom{10};
|
||||
const double critical{18.307}; // dof 10, significance 0.05
|
||||
|
||||
CHECK(get_chi2_uniform(x_positions, 0.0, 1.0, degrees_of_freedom + 1) < critical);
|
||||
}
|
||||
|
||||
TEST_CASE("Random respects point type", "[Seams][SeamRandom]") {
|
||||
const int seed{42};
|
||||
std::mt19937 random_engine{seed};
|
||||
const Random::Impl::Random random{random_engine};
|
||||
Perimeters::Perimeter perimeter{RandomTest::get_perimeter()};
|
||||
std::optional<SeamChoice> choice{
|
||||
random(perimeter, Perimeters::PointType::common, Perimeters::PointClassification::common)};
|
||||
|
||||
REQUIRE(choice);
|
||||
const std::size_t picked_index{choice->previous_index};
|
||||
perimeter.point_types[picked_index] = Perimeters::PointType::blocker;
|
||||
choice = random(perimeter, Perimeters::PointType::common, Perimeters::PointClassification::common);
|
||||
REQUIRE(choice);
|
||||
CHECK(choice->previous_index != picked_index);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(Test::SeamsFixture, "Generate random seam", "[Seams][SeamRandom][Integration]") {
|
||||
Seams::Perimeters::LayerPerimeters perimeters{
|
||||
Seams::Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter)};
|
||||
const std::vector<std::vector<SeamPerimeterChoice>> seams{
|
||||
Random::get_object_seams(std::move(perimeters), params.random_seed)};
|
||||
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream csv{"random_seam.csv"};
|
||||
Test::serialize_seam(csv, seams);
|
||||
}
|
||||
}
|
||||
47
tests/fff_print/test_seam_rear.cpp
Normal file
47
tests/fff_print/test_seam_rear.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <catch2/catch.hpp>
|
||||
#include <libslic3r/GCode/SeamRear.hpp>
|
||||
#include "test_data.hpp"
|
||||
#include <fstream>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Seams;
|
||||
|
||||
constexpr bool debug_files{false};
|
||||
|
||||
namespace RearTest {
|
||||
Perimeters::Perimeter get_perimeter() {
|
||||
const double slice_z{1.0};
|
||||
const std::size_t layer_index{};
|
||||
std::vector<Vec2d> positions{{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}, {0.5, 1.0}, {0.0, 1.0}};
|
||||
std::vector<double> angles(positions.size(), -M_PI / 2.0);
|
||||
angles[3] = 0.0;
|
||||
std::vector<Perimeters::PointType> point_types(positions.size(), Perimeters::PointType::common);
|
||||
std::vector<Perimeters::PointClassification>
|
||||
point_classifications{positions.size(), Perimeters::PointClassification::common};
|
||||
std::vector<Perimeters::AngleType> angle_type(positions.size(), Perimeters::AngleType::concave);
|
||||
angle_type[3] = Perimeters::AngleType::smooth;
|
||||
|
||||
return {
|
||||
slice_z,
|
||||
layer_index,
|
||||
false,
|
||||
std::move(positions),
|
||||
std::move(angles),
|
||||
std::move(point_types),
|
||||
std::move(point_classifications),
|
||||
std::move(angle_type)};
|
||||
}
|
||||
} // namespace RearTest
|
||||
|
||||
TEST_CASE_METHOD(Test::SeamsFixture, "Generate rear seam", "[Seams][SeamRear][Integration]") {
|
||||
Seams::Perimeters::LayerPerimeters perimeters{
|
||||
Seams::Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter)};
|
||||
const std::vector<std::vector<SeamPerimeterChoice>> seams{
|
||||
Rear::get_object_seams(std::move(perimeters), params.rear_tolerance, params.rear_y_offset)};
|
||||
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream csv{"rear_seam.csv"};
|
||||
Test::serialize_seam(csv, seams);
|
||||
}
|
||||
}
|
||||
59
tests/fff_print/test_seam_shells.cpp
Normal file
59
tests/fff_print/test_seam_shells.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/GCode/SeamPainting.hpp"
|
||||
#include "test_data.hpp"
|
||||
|
||||
#include "libslic3r/GCode/SeamShells.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Seams;
|
||||
|
||||
constexpr bool debug_files{false};
|
||||
|
||||
struct ProjectionFixture
|
||||
{
|
||||
Polygon extrusion_path{
|
||||
Point{scaled(Vec2d{-1.0, -1.0})}, Point{scaled(Vec2d{1.0, -1.0})},
|
||||
Point{scaled(Vec2d{1.0, 1.0})}, Point{scaled(Vec2d{-1.0, 1.0})}};
|
||||
|
||||
ExPolygon island_boundary;
|
||||
Seams::Geometry::Extrusions extrusions;
|
||||
double extrusion_width{0.2};
|
||||
|
||||
ProjectionFixture() {
|
||||
extrusions.emplace_back(Polygon{extrusion_path}, extrusion_path.bounding_box(), extrusion_width, island_boundary);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE_METHOD(ProjectionFixture, "Project to geometry matches", "[Seams][SeamShells]") {
|
||||
Polygon boundary_polygon{extrusion_path};
|
||||
// Add + 0.1 to check that boundary polygon has been picked.
|
||||
boundary_polygon.scale(1.0 + extrusion_width / 2.0 + 0.1);
|
||||
island_boundary.contour = boundary_polygon;
|
||||
|
||||
Seams::Geometry::BoundedPolygons result{Seams::Geometry::project_to_geometry(extrusions, 5.0)};
|
||||
REQUIRE(result.size() == 1);
|
||||
REQUIRE(result[0].polygon.size() == 4);
|
||||
// Boundary polygon is picked.
|
||||
CHECK(result[0].polygon[0].x() == Approx(scaled(-(1.0 + extrusion_width / 2.0 + 0.1))));
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(ProjectionFixture, "Project to geometry does not match", "[Seams][SeamShells]") {
|
||||
Polygon boundary_polygon{extrusion_path};
|
||||
|
||||
// Island boundary is far from the extrusion.
|
||||
boundary_polygon.scale(5.0);
|
||||
|
||||
island_boundary.contour = boundary_polygon;
|
||||
|
||||
Seams::Geometry::BoundedPolygons result{Seams::Geometry::project_to_geometry(extrusions, 1.0)};
|
||||
REQUIRE(result.size() == 1);
|
||||
REQUIRE(result[0].polygon.size() == 4);
|
||||
|
||||
const Polygon expanded{expand(extrusions.front().polygon, scaled(extrusion_width / 2.0)).front()};
|
||||
|
||||
// The extrusion is expanded and returned.
|
||||
CHECK(result[0].polygon == expanded);
|
||||
}
|
||||
Reference in New Issue
Block a user