mirror of
https://github.com/QIDITECH/QIDISlicer.git
synced 2026-01-30 15:38:43 +03:00
update test
This commit is contained in:
@@ -4,10 +4,10 @@
|
||||
find_package(Catch2 2.9 REQUIRED)
|
||||
|
||||
include(Catch)
|
||||
|
||||
set(TEST_DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data)
|
||||
file(TO_NATIVE_PATH "${TEST_DATA_DIR}" TEST_DATA_DIR)
|
||||
|
||||
|
||||
set(CATCH_EXTRA_ARGS "" CACHE STRING "Extra arguments for catch2 test suites.")
|
||||
|
||||
add_library(test_common INTERFACE)
|
||||
@@ -27,7 +27,10 @@ add_subdirectory(libslic3r)
|
||||
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)
|
||||
|
||||
@@ -1066,57 +1066,3 @@ TEMPLATE_TEST_CASE("Test if allowed item rotations are considered", "[arrange2]"
|
||||
REQUIRE(get_rotation(itm) == Approx(PI));
|
||||
}
|
||||
|
||||
//TEST_CASE("NFP optimizing test", "[arrange2]") {
|
||||
// using namespace Slic3r;
|
||||
|
||||
// auto itemshape = arr2::to_rectangle(BoundingBox{{scaled(-25.), scaled(-25.)}, {scaled(25.), scaled(25.)}});
|
||||
|
||||
// arr2::ArrangeItem item{arr2::DecomposedShape{itemshape}};
|
||||
|
||||
// ExPolygons nfp = { ExPolygon {{scaled(-2000.), scaled(25.)}, {scaled(2000.), scaled(25.)}} };
|
||||
|
||||
// struct K : public arr2::GravityKernel {
|
||||
// size_t &fncnt;
|
||||
// K(size_t &counter, Vec2crd gpos): arr2::GravityKernel{gpos}, fncnt{counter} {}
|
||||
// double placement_fitness(const arr2::ArrangeItem &itm, const Vec2crd &tr) const
|
||||
// {
|
||||
// ++fncnt;
|
||||
// return arr2::GravityKernel::placement_fitness(itm, tr);
|
||||
// }
|
||||
// };
|
||||
|
||||
// size_t counter = 0;
|
||||
// K k{counter, Vec2crd{0, 0}};
|
||||
// opt::Optimizer<opt::AlgBruteForce> solver_ref({}, 1000);
|
||||
// opt::Optimizer<opt::AlgNLoptSubplex> solver (opt::StopCriteria{}
|
||||
// .max_iterations(1000)
|
||||
// /*.rel_score_diff(1e-20)*/);
|
||||
|
||||
// double accuracy = 1.;
|
||||
// arr2::PackStrategyNFP strategy_ref(solver_ref, k, ex_seq, accuracy);
|
||||
// arr2::PackStrategyNFP strategy(solver, k, ex_seq, accuracy);
|
||||
|
||||
// SVG svg("nfp_optimizing.svg");
|
||||
// svg.flipY = true;
|
||||
// svg.draw_outline(nfp, "green");
|
||||
|
||||
// svg.draw_outline(item.shape().transformed_outline(), "yellow");
|
||||
|
||||
// double score = pick_best_spot_on_nfp(item, nfp, arr2::InfiniteBed{}, strategy);
|
||||
// svg.draw_outline(item.shape().transformed_outline(), "red");
|
||||
|
||||
// counter = 0;
|
||||
// double score_ref = pick_best_spot_on_nfp(item, nfp, arr2::InfiniteBed{}, strategy_ref);
|
||||
// svg.draw_outline(item.shape().transformed_outline(), "blue");
|
||||
|
||||
//#ifndef NDEBUG
|
||||
// std::cout << "fitness called: " << k.fncnt << " times" << std::endl;
|
||||
// std::cout << "score = " << score << " score_ref = " << score_ref << std::endl;
|
||||
//#endif
|
||||
|
||||
// REQUIRE(!std::isnan(score));
|
||||
// REQUIRE(!std::isnan(score_ref));
|
||||
// REQUIRE(score >= score_ref);
|
||||
//}
|
||||
|
||||
|
||||
|
||||
@@ -1078,41 +1078,3 @@ TEST_CASE("Testing duplicate function to really duplicate the whole Model",
|
||||
REQUIRE(is_collision_free(range(task->selected)));
|
||||
}
|
||||
|
||||
// TODO:
|
||||
//TEST_CASE("Testing fit-into-bed rotation search", "[arrange2][integration]")
|
||||
//{
|
||||
// using namespace Slic3r;
|
||||
|
||||
// Model model;
|
||||
|
||||
// ModelObject* new_object = model.add_object();
|
||||
// new_object->name = "big_cube";
|
||||
// new_object->add_instance();
|
||||
// TriangleMesh mesh = make_cube(205., 220., 10.);
|
||||
// mesh.rotate_z(15 * PI / 180);
|
||||
|
||||
// ModelVolume* new_volume = new_object->add_volume(mesh);
|
||||
// new_volume->name = new_object->name;
|
||||
|
||||
// store_3mf("rotfail.3mf", &model, nullptr, false);
|
||||
|
||||
// arr2::RectangleBed bed{scaled(250.), scaled(210.)};
|
||||
|
||||
// arr2::Scene scene{
|
||||
// arr2::SceneBuilder{}
|
||||
// .set_bed(bed)
|
||||
// .set_model(model)
|
||||
// .set_arrange_settings(arr2::ArrangeSettings{}
|
||||
// .set_distance_from_objects(0.)
|
||||
// .set_rotation_enabled(true)
|
||||
// )
|
||||
// };
|
||||
|
||||
// auto task = arr2::ArrangeTask<arr2::ArrangeItem>::create(scene);
|
||||
// auto result = task->process_native(arr2::DummyCtl{});
|
||||
|
||||
// REQUIRE(result->items.size() == 1);
|
||||
// REQUIRE(arr2::get_rotation(result->items.front()) > 0.);
|
||||
// REQUIRE(arr2::is_arranged(result->items.front()));
|
||||
//}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +0,0 @@
|
||||
#ifndef PRUSAPARTS_H
|
||||
#define PRUSAPARTS_H
|
||||
|
||||
#include <vector>
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
|
||||
using TestData = std::vector<Slic3r::Polygon>;
|
||||
using TestDataEx = std::vector<Slic3r::ExPolygons>;
|
||||
|
||||
extern const TestData QIDI_PART_POLYGONS;
|
||||
extern const TestData QIDI_STEGOSAUR_POLYGONS;
|
||||
extern const TestDataEx QIDI_PART_POLYGONS_EX;
|
||||
|
||||
#endif // PRUSAPARTS_H
|
||||
BIN
tests/data/seam_test_object.3mf
Normal file
BIN
tests/data/seam_test_object.3mf
Normal file
Binary file not shown.
@@ -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);
|
||||
}
|
||||
@@ -43,8 +43,8 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_jump_point_search.cpp
|
||||
test_support_spots_generator.cpp
|
||||
test_layer_region.cpp
|
||||
../data/prusaparts.cpp
|
||||
../data/prusaparts.hpp
|
||||
../data/qidiparts.cpp
|
||||
../data/qidiparts.hpp
|
||||
test_static_map.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -744,6 +744,7 @@ TEST_CASE("Arachne - #10034 - Degenerated Voronoi diagram - That wasn't fixed by
|
||||
export_perimeters_to_svg(debug_out_path("arachne-degenerated-diagram-10034-rotation-not-works.svg"), polygons, perimeters, union_ex(wall_tool_paths.getInnerContour()));
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("Arachne - SPE-1837 - No perimeters generated", "[ArachneNoPerimetersGeneratedSPE1837]") {
|
||||
Polygon poly_0 = {
|
||||
Point( 10000000, 10000000),
|
||||
@@ -762,4 +763,87 @@ TEST_CASE("Arachne - SPE-1837 - No perimeters generated", "[ArachneNoPerimetersG
|
||||
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
|
||||
|
||||
REQUIRE(!perimeters.empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Arachne - SPE-2298 - Missing twin edge", "[ArachneMissingTwinEdgeSPE2298]") {
|
||||
Polygon poly_0 = {
|
||||
Point(45275325, -26003582),
|
||||
Point(46698318, -24091837),
|
||||
Point(45534079, - 7648226),
|
||||
Point(44427730, 6913138),
|
||||
Point(42406709, 31931594),
|
||||
Point(42041617, 31895427),
|
||||
Point(42556409, 25628802),
|
||||
Point(43129149, 18571997),
|
||||
Point(44061956, 6884616),
|
||||
Point(44482729, 1466404),
|
||||
Point(45172290, - 7674740),
|
||||
Point(46329004, -23890062),
|
||||
Point(46303776, -23895512),
|
||||
Point(45146815, - 7676652),
|
||||
Point(44457276, 1464203),
|
||||
Point(44036504, 6882422),
|
||||
Point(43103702, 18569730),
|
||||
Point(42015592, 31899494),
|
||||
Point(41650258, 31866937),
|
||||
Point(44100538, 1436619)
|
||||
};
|
||||
|
||||
Polygons polygons = {poly_0};
|
||||
coord_t ext_perimeter_spacing = 407079;
|
||||
coord_t perimeter_spacing = 407079;
|
||||
coord_t inset_count = 1;
|
||||
|
||||
Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.2, PrintObjectConfig::defaults(), PrintConfig::defaults());
|
||||
wall_tool_paths.generate();
|
||||
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
|
||||
|
||||
REQUIRE(!perimeters.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Arachne - SPE-2298 - Missing twin edge - 2", "[ArachneMissingTwinEdge2SPE2298]") {
|
||||
Polygon poly_0 = {
|
||||
Point(-8908308, -51405945),
|
||||
Point(-12709229, -51250796),
|
||||
Point(-12746335, -51233657),
|
||||
Point(-12830242, -51142897),
|
||||
Point(-12826443, -51134671),
|
||||
Point(-13181213, -51120650),
|
||||
Point(-13184646, -51206854),
|
||||
Point(-19253324, -50972142),
|
||||
Point(-19253413, -50972139),
|
||||
Point(-20427346, -50924668),
|
||||
Point(-20427431, -50924664),
|
||||
Point(-25802429, -50698485),
|
||||
Point(-25802568, -50698481),
|
||||
Point(-28983179, -50556020),
|
||||
Point(-28984425, -50555950),
|
||||
Point(-29799753, -50499586),
|
||||
Point(-29801136, -50499472),
|
||||
Point(-29856539, -50494137),
|
||||
Point(-29857834, -50493996),
|
||||
Point(-30921022, -50364409),
|
||||
Point(-30922312, -50364235),
|
||||
Point(-31012584, -50350908),
|
||||
Point(-31022222, -50358055),
|
||||
Point(-31060596, -50368155),
|
||||
Point(-31429495, -50322406),
|
||||
Point(-31460950, -50531962),
|
||||
Point(-31194587, -50578945),
|
||||
Point(-30054463, -50718244),
|
||||
Point(-28903516, -50799260),
|
||||
Point(-14217296, -51420133),
|
||||
Point(-8916965, -51624212)
|
||||
};
|
||||
|
||||
Polygons polygons = {poly_0};
|
||||
coord_t ext_perimeter_spacing = 407079;
|
||||
coord_t perimeter_spacing = 407079;
|
||||
coord_t inset_count = 1;
|
||||
|
||||
Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.2, PrintObjectConfig::defaults(), PrintConfig::defaults());
|
||||
wall_tool_paths.generate();
|
||||
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
|
||||
|
||||
REQUIRE(!perimeters.empty());
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/LocalesUtils.hpp"
|
||||
|
||||
#include <LocalesUtils.hpp>
|
||||
#include <cereal/types/polymorphic.hpp>
|
||||
#include <cereal/types/string.hpp>
|
||||
#include <cereal/types/vector.hpp>
|
||||
@@ -247,6 +247,7 @@ TEST_CASE("Config serialization of multiple values", "[Config]"){
|
||||
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();
|
||||
|
||||
@@ -101,7 +101,8 @@ std::string get_font_filepath() {
|
||||
return resource_dir + "fonts/NotoSans-Regular.ttf";
|
||||
}
|
||||
|
||||
#include "imgui/imstb_truetype.h"
|
||||
// Explicit horror include (used to be implicit) - libslic3r "officialy" does not depend on imgui.
|
||||
#include "../../bundled_deps/imgui/imgui/imstb_truetype.h" // stbtt_fontinfo
|
||||
TEST_CASE("Read glyph C shape from font, stb library calls ONLY", "[Emboss]") {
|
||||
std::string font_path = get_font_filepath();
|
||||
char letter = 'C';
|
||||
@@ -262,6 +263,7 @@ TEST_CASE("Heal of 'i' in ALIENATO.TTF", "[Emboss]")
|
||||
auto a = heal_and_check(polygons);
|
||||
|
||||
Polygons scaled_shape = polygons; // copy
|
||||
|
||||
double text_shape_scale = 0.001; // Emboss.cpp --> SHAPE_SCALE
|
||||
scale(scaled_shape, 1 / text_shape_scale);
|
||||
auto b = heal_and_check(scaled_shape);
|
||||
@@ -310,6 +312,7 @@ TEST_CASE("Heal of overlaping contour", "[Emboss]"){
|
||||
ExPolygons shapes = {ExPolygon{contour}};
|
||||
CHECK(Emboss::heal_expolygons(shapes));
|
||||
}
|
||||
|
||||
TEST_CASE("Heal of points close to line", "[Emboss]")
|
||||
{
|
||||
std::string file_name = "points_close_to_line.svg";
|
||||
@@ -530,10 +533,10 @@ TEST_CASE("UndoRedo TextConfiguration serialization", "[Emboss]")
|
||||
TextConfiguration tc;
|
||||
tc.text = "Dovede-li se člověk zasmát sám sobě, nevyjde ze smíchu po celý život.";
|
||||
EmbossStyle& es = tc.style;
|
||||
es.name = "Seneca";
|
||||
es.path = "Simply the best";
|
||||
es.type = EmbossStyle::Type::file_path;
|
||||
FontProp &fp = es.prop;
|
||||
es.name = "Seneca";
|
||||
es.path = "Simply the best";
|
||||
es.type = EmbossStyle::Type::file_path;
|
||||
FontProp &fp = es.prop;
|
||||
fp.char_gap = 3;
|
||||
fp.line_gap = 7;
|
||||
fp.boldness = 2.3f;
|
||||
@@ -543,8 +546,7 @@ TEST_CASE("UndoRedo TextConfiguration serialization", "[Emboss]")
|
||||
|
||||
std::stringstream ss; // any stream can be used
|
||||
{
|
||||
cereal::BinaryOutputArchive oarchive(ss); // Create an output archive
|
||||
|
||||
cereal::BinaryOutputArchive oarchive(ss); // Create an output archive
|
||||
oarchive(tc);
|
||||
} // archive goes out of scope, ensuring all contents are flushed
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "libslic3r/ShortestPath.hpp"
|
||||
|
||||
//#include <random>
|
||||
//#include "libnest2d/tools/benchmark.h"
|
||||
#include "libslic3r/SVG.hpp"
|
||||
|
||||
#include "../data/qidiparts.hpp"
|
||||
@@ -230,6 +229,7 @@ SCENARIO("Circle Fit, 3 points", "[Geometry]") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") {
|
||||
GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
|
||||
Vec2d expected_center(-6, 0);
|
||||
@@ -369,6 +369,7 @@ SCENARIO("Circle Fit, least squares by decomposition or by solving normal equati
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("smallest_enclosing_circle_welzl", "[Geometry]") {
|
||||
// Some random points in plane.
|
||||
Points pts {
|
||||
|
||||
@@ -75,26 +75,13 @@ struct LayerRegionFixture {
|
||||
TEST_CASE_METHOD(LayerRegionFixture, "test the surface expansion", "[LayerRegion]") {
|
||||
const double custom_angle{1.234f};
|
||||
|
||||
// w36
|
||||
/* const Surfaces result{expand_merge_surfaces(
|
||||
const Surfaces result{expand_merge_surfaces(
|
||||
surfaces, stBottomBridge,
|
||||
expansion_zones,
|
||||
closing_radius,
|
||||
custom_angle
|
||||
)};*/
|
||||
|
||||
// w36
|
||||
const Surfaces result{expand_merge_surfaces(
|
||||
surfaces, stBottomBridge,
|
||||
shells,
|
||||
expansion_params_into_solid_infill,
|
||||
sparse,
|
||||
expansion_params_into_sparse_infill,
|
||||
closing_radius
|
||||
)};
|
||||
|
||||
|
||||
|
||||
if constexpr (export_svgs) {
|
||||
SVG svg("general_expansion.svg", BoundingBox{
|
||||
Point{scaled(-3.0), scaled(-1.0)},
|
||||
@@ -125,13 +112,9 @@ TEST_CASE_METHOD(LayerRegionFixture, "test the surface expansion", "[LayerRegion
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(LayerRegionFixture, "test the bridge expansion with the bridge angle detection", "[LayerRegion]") {
|
||||
// w36
|
||||
Surfaces result{expand_bridges_detect_orientations(
|
||||
surfaces,
|
||||
shells,
|
||||
expansion_params_into_solid_infill,
|
||||
sparse,
|
||||
expansion_params_into_sparse_infill,
|
||||
expansion_zones,
|
||||
closing_radius
|
||||
)};
|
||||
|
||||
@@ -148,7 +131,7 @@ TEST_CASE_METHOD(LayerRegionFixture, "test the bridge expansion with the bridge
|
||||
}
|
||||
|
||||
REQUIRE(result.size() == 2);
|
||||
CHECK(result.at(0).bridge_angle == Approx(1.5707963268));
|
||||
CHECK(std::fmod(result.at(1).bridge_angle, M_PI) == Approx(0.0));
|
||||
CHECK(std::fmod(result.at(1).bridge_angle, M_PI) == Approx(0.0));
|
||||
CHECK(result.at(0).expolygon.contour.size() == 22);
|
||||
CHECK(result.at(1).expolygon.contour.size() == 14);
|
||||
|
||||
@@ -72,6 +72,7 @@ TEST_CASE_METHOD(PolylineTestCase, "Split at first point", "[Polyline]") {
|
||||
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} };
|
||||
@@ -90,6 +91,7 @@ SCENARIO("Simplify polyne, template", "[Polyline]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("Simplify polyline", "[Polyline]")
|
||||
{
|
||||
GIVEN("polyline 1") {
|
||||
@@ -110,6 +112,7 @@ SCENARIO("Simplify polyline", "[Polyline]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("polyline 3") {
|
||||
auto polyline = Polyline{ {0,0}, {100,0}, {50,10} };
|
||||
WHEN("simplified with Douglas-Peucker") {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <igl/qslim.h>
|
||||
#include <test_utils.hpp>
|
||||
|
||||
#include <libslic3r/QuadricEdgeCollapse.hpp>
|
||||
@@ -239,7 +240,6 @@ TEST_CASE("Simplify frog_legs.obj to 5% by Quadric edge collapse", "[its][quadri
|
||||
Private::is_better_similarity(mesh.its, its, Private::frog_leg_5);
|
||||
}
|
||||
|
||||
#include <libigl/igl/qslim.h>
|
||||
TEST_CASE("Simplify frog_legs.obj to 5% by IGL/qslim", "[]")
|
||||
{
|
||||
std::string obj_filename = "frog_legs.obj";
|
||||
|
||||
@@ -5,16 +5,15 @@
|
||||
using namespace Slic3r;
|
||||
using namespace SupportSpotsGenerator;
|
||||
|
||||
|
||||
namespace Rectangle {
|
||||
const float width = 10;
|
||||
const float height = 20;
|
||||
const Polygon polygon = {
|
||||
scaled(Vec2f{-width / 2, -height / 2}),
|
||||
scaled(Vec2f{width / 2, -height / 2}),
|
||||
scaled(Vec2f{width / 2, height / 2}),
|
||||
scaled(Vec2f{-width / 2, height / 2})
|
||||
};
|
||||
const float width = 10;
|
||||
const float height = 20;
|
||||
const Polygon polygon = {
|
||||
scaled(Vec2f{-width / 2, -height / 2}),
|
||||
scaled(Vec2f{width / 2, -height / 2}),
|
||||
scaled(Vec2f{width / 2, height / 2}),
|
||||
scaled(Vec2f{-width / 2, height / 2})
|
||||
};
|
||||
}
|
||||
|
||||
TEST_CASE("Numerical integral over polygon calculation compared with exact solution.", "[SupportSpotsGenerator]") {
|
||||
@@ -77,7 +76,6 @@ TEST_CASE("Moment values and ratio check.", "[SupportSpotsGenerator]") {
|
||||
}
|
||||
|
||||
TEST_CASE("Moments calculation for rotated axis.", "[SupportSpotsGenerator]") {
|
||||
|
||||
Polygon polygon = {
|
||||
scaled(Vec2f{6.362284076172198, 138.9674202217155}),
|
||||
scaled(Vec2f{97.48779843751677, 106.08136606617076}),
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <test_utils.hpp>
|
||||
|
||||
#include <libslic3r/Polygon.hpp>
|
||||
#include <libslic3r/Polyline.hpp>
|
||||
#include <libslic3r/EdgeGrid.hpp>
|
||||
#include <libslic3r/Geometry.hpp>
|
||||
#include "libslic3r/Geometry/VoronoiUtilsCgal.hpp"
|
||||
|
||||
#include <libslic3r/Geometry/VoronoiOffset.hpp>
|
||||
#include <libslic3r/Geometry/VoronoiVisualUtils.hpp>
|
||||
|
||||
#include <numeric>
|
||||
|
||||
// #define VORONOI_DEBUG_OUT
|
||||
//#define VORONOI_DEBUG_OUT
|
||||
|
||||
#ifdef VORONOI_DEBUG_OUT
|
||||
#include <libslic3r/VoronoiVisualUtils.hpp>
|
||||
#include <libslic3r/Geometry/VoronoiVisualUtils.hpp>
|
||||
#include <libslic3r/Utils.hpp>
|
||||
#endif
|
||||
|
||||
using boost::polygon::voronoi_builder;
|
||||
@@ -340,7 +336,7 @@ TEST_CASE("Voronoi division by zero 12903", "[Voronoi]")
|
||||
// Funny sample from a dental industry?
|
||||
// Vojtech confirms this test fails and rightly so, because the input data contain self intersections.
|
||||
// This test is suppressed.
|
||||
TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi][!hide][!mayfail]")
|
||||
TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi]")
|
||||
{
|
||||
Lines lines = {
|
||||
{ { 260500,1564400 }, { 261040,1562960 } },
|
||||
@@ -1924,23 +1920,6 @@ TEST_CASE("Voronoi skeleton", "[VoronoiSkeleton]")
|
||||
REQUIRE(! skeleton_edges.empty());
|
||||
}
|
||||
|
||||
// Simple detection with complexity N^2 if there is any point in the input polygons that doesn't have Voronoi vertex.
|
||||
[[maybe_unused]] static bool has_missing_voronoi_vertices(const Polygons &polygons, const VD &vd)
|
||||
{
|
||||
auto are_equal = [](const VD::vertex_type v, const Point &p) { return (Vec2d(v.x(), v.y()) - p.cast<double>()).norm() <= SCALED_EPSILON; };
|
||||
|
||||
Points poly_points = to_points(polygons);
|
||||
std::vector<bool> found_vertices(poly_points.size());
|
||||
for (const Point &point : poly_points)
|
||||
for (const auto &vertex : vd.vertices())
|
||||
if (are_equal(vertex, point)) {
|
||||
found_vertices[&point - &poly_points.front()] = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return std::find(found_vertices.begin(), found_vertices.end(), false) != found_vertices.end();
|
||||
}
|
||||
|
||||
// This case is composed of one square polygon, and one of the edges is divided into two parts by a point that lies on this edge.
|
||||
// In some applications, this point is unnecessary and can be removed (merge two parts to one edge). But for the case of
|
||||
// multi-material segmentation, these points are necessary. In this case, Voronoi vertex for the point, which divides the edge
|
||||
@@ -1955,12 +1934,9 @@ TEST_CASE("Voronoi missing vertex 1", "[VoronoiMissingVertex1]")
|
||||
{-25000000, 25000000},
|
||||
{-25000000, -25000000},
|
||||
{-12412500, -25000000},
|
||||
// {- 1650000, -25000000},
|
||||
{ 25000000, -25000000}
|
||||
};
|
||||
|
||||
// poly.rotate(PI / 6);
|
||||
|
||||
REQUIRE(poly.area() > 0.);
|
||||
REQUIRE(intersecting_edges({poly}).empty());
|
||||
|
||||
@@ -1971,7 +1947,7 @@ TEST_CASE("Voronoi missing vertex 1", "[VoronoiMissingVertex1]")
|
||||
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex1-out.svg").c_str(), vd, Points(), lines);
|
||||
#endif
|
||||
|
||||
// REQUIRE(!has_missing_voronoi_vertices({poly}, vd));
|
||||
REQUIRE(vd.is_valid());
|
||||
}
|
||||
|
||||
// This case is composed of two square polygons (contour and hole), and again one of the edges is divided into two parts by a
|
||||
@@ -1998,8 +1974,6 @@ TEST_CASE("Voronoi missing vertex 2", "[VoronoiMissingVertex2]")
|
||||
}
|
||||
};
|
||||
|
||||
// polygons_rotate(poly, PI / 6);
|
||||
|
||||
double area = std::accumulate(poly.begin(), poly.end(), 0., [](double a, auto &poly) { return a + poly.area(); });
|
||||
REQUIRE(area > 0.);
|
||||
REQUIRE(intersecting_edges(poly).empty());
|
||||
@@ -2011,7 +1985,7 @@ TEST_CASE("Voronoi missing vertex 2", "[VoronoiMissingVertex2]")
|
||||
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex2-out.svg").c_str(), vd, Points(), lines);
|
||||
#endif
|
||||
|
||||
// REQUIRE(!has_missing_voronoi_vertices(poly, vd));
|
||||
REQUIRE(vd.is_valid());
|
||||
}
|
||||
|
||||
// This case is composed of two polygons, and again one of the edges is divided into two parts by a point that lies on this edge,
|
||||
@@ -2042,9 +2016,6 @@ TEST_CASE("Voronoi missing vertex 3", "[VoronoiMissingVertex3]")
|
||||
REQUIRE(area > 0.);
|
||||
REQUIRE(intersecting_edges(poly).empty());
|
||||
|
||||
// polygons_rotate(poly, PI/180);
|
||||
// polygons_rotate(poly, PI/6);
|
||||
|
||||
VD vd;
|
||||
Lines lines = to_lines(poly);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
@@ -2052,7 +2023,7 @@ TEST_CASE("Voronoi missing vertex 3", "[VoronoiMissingVertex3]")
|
||||
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex3-out.svg").c_str(), vd, Points(), lines);
|
||||
#endif
|
||||
|
||||
// REQUIRE(!has_missing_voronoi_vertices(poly, vd));
|
||||
REQUIRE(vd.is_valid());
|
||||
}
|
||||
|
||||
TEST_CASE("Voronoi missing vertex 4", "[VoronoiMissingVertex4]")
|
||||
@@ -2097,6 +2068,9 @@ TEST_CASE("Voronoi missing vertex 4", "[VoronoiMissingVertex4]")
|
||||
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);
|
||||
#endif
|
||||
|
||||
REQUIRE(vd_1.is_valid());
|
||||
REQUIRE(vd_2.is_valid());
|
||||
}
|
||||
|
||||
// In this case, the Voronoi vertex (146873, -146873) is included twice.
|
||||
@@ -2117,8 +2091,6 @@ TEST_CASE("Duplicate Voronoi vertices", "[Voronoi]")
|
||||
{ 25000000, 10790627},
|
||||
};
|
||||
|
||||
// poly.rotate(PI / 6);
|
||||
|
||||
REQUIRE(poly.area() > 0.);
|
||||
REQUIRE(intersecting_edges({poly}).empty());
|
||||
|
||||
@@ -2129,16 +2101,7 @@ TEST_CASE("Duplicate Voronoi vertices", "[Voronoi]")
|
||||
dump_voronoi_to_svg(debug_out_path("voronoi-duplicate-vertices-out.svg").c_str(), vd, Points(), lines);
|
||||
#endif
|
||||
|
||||
[[maybe_unused]] auto has_duplicate_vertices = [](const VD &vd) -> bool {
|
||||
std::vector<Vec2d> vertices;
|
||||
for (const auto &vertex : vd.vertices())
|
||||
vertices.emplace_back(Vec2d(vertex.x(), vertex.y()));
|
||||
|
||||
std::sort(vertices.begin(), vertices.end(), [](const Vec2d &l, const Vec2d &r) { return l.x() < r.x() || (l.x() == r.x() && l.y() < r.y()); });
|
||||
return std::unique(vertices.begin(), vertices.end()) != vertices.end();
|
||||
};
|
||||
|
||||
// REQUIRE(!has_duplicate_vertices(vd));
|
||||
REQUIRE(vd.is_valid());
|
||||
}
|
||||
|
||||
// In this case, there are three very close Voronoi vertices like in the previous test case after rotation. There is also one
|
||||
@@ -2157,8 +2120,6 @@ TEST_CASE("Intersecting Voronoi edges", "[Voronoi]")
|
||||
{ 25000000, - 146873},
|
||||
};
|
||||
|
||||
// poly.rotate(PI / 6);
|
||||
|
||||
REQUIRE(poly.area() > 0.);
|
||||
REQUIRE(intersecting_edges({poly}).empty());
|
||||
|
||||
@@ -2169,38 +2130,7 @@ TEST_CASE("Intersecting Voronoi edges", "[Voronoi]")
|
||||
dump_voronoi_to_svg(debug_out_path("voronoi-intersecting-edges-out.svg").c_str(), vd, Points(), lines);
|
||||
#endif
|
||||
|
||||
[[maybe_unused]] auto has_intersecting_edges = [](const Polygon &poly, const VD &vd) -> bool {
|
||||
BoundingBox bbox = get_extents(poly);
|
||||
const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y()));
|
||||
|
||||
std::vector<Voronoi::Internal::segment_type> segments;
|
||||
for (const Line &line : to_lines(poly))
|
||||
segments.emplace_back(Voronoi::Internal::point_type(double(line.a.x()), double(line.a.y())),
|
||||
Voronoi::Internal::point_type(double(line.b.x()), double(line.b.y())));
|
||||
|
||||
Lines edges;
|
||||
for (const auto &edge : vd.edges())
|
||||
if (edge.cell()->source_index() < edge.twin()->cell()->source_index()) {
|
||||
if (edge.is_finite()) {
|
||||
edges.emplace_back(Point(coord_t(edge.vertex0()->x()), coord_t(edge.vertex0()->y())),
|
||||
Point(coord_t(edge.vertex1()->x()), coord_t(edge.vertex1()->y())));
|
||||
} else if (edge.is_infinite()) {
|
||||
std::vector<Voronoi::Internal::point_type> samples;
|
||||
Voronoi::Internal::clip_infinite_edge(poly.points, segments, edge, bbox_dim_max, &samples);
|
||||
if (!samples.empty())
|
||||
edges.emplace_back(Point(coord_t(samples[0].x()), coord_t(samples[0].y())), Point(coord_t(samples[1].x()), coord_t(samples[1].y())));
|
||||
}
|
||||
}
|
||||
|
||||
Point intersect_point;
|
||||
for (auto first_it = edges.begin(); first_it != edges.end(); ++first_it)
|
||||
for (auto second_it = first_it + 1; second_it != edges.end(); ++second_it)
|
||||
if (first_it->intersection(*second_it, &intersect_point) && first_it->a != intersect_point && first_it->b != intersect_point)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
// REQUIRE(!has_intersecting_edges(poly, vd));
|
||||
REQUIRE(vd.is_valid());
|
||||
}
|
||||
|
||||
// In this case resulting Voronoi diagram is not planar. This case was distilled from GH issue #8474.
|
||||
@@ -2219,8 +2149,6 @@ TEST_CASE("Non-planar voronoi diagram", "[VoronoiNonPlanar]")
|
||||
{ 5500000, 40000000},
|
||||
};
|
||||
|
||||
// poly.rotate(PI / 6);
|
||||
|
||||
REQUIRE(poly.area() > 0.);
|
||||
REQUIRE(intersecting_edges({poly}).empty());
|
||||
|
||||
@@ -2231,7 +2159,7 @@ TEST_CASE("Non-planar voronoi diagram", "[VoronoiNonPlanar]")
|
||||
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));
|
||||
REQUIRE(vd.is_valid());
|
||||
}
|
||||
|
||||
// This case is extracted from SPE-1729, where several ExPolygon with very thin lines
|
||||
@@ -2288,4 +2216,69 @@ TEST_CASE("Invalid Voronoi diagram - Thin lines - SPE-1729", "[InvalidVoronoiDia
|
||||
#endif
|
||||
|
||||
// REQUIRE(vd.is_valid());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Voronoi cell doesn't contain a source point - SPE-2298", "[VoronoiCellSourcePointSPE2298]")
|
||||
{
|
||||
Polygon polygon = {
|
||||
{ 9854534, -39739718}, {- 4154002, -34864557}, {-13073118, -31802214},
|
||||
{-21265508, -29026626}, {-31388055, -25645073}, {-32409943, -25279942},
|
||||
{-33418087, -24864987}, {-34400568, -24404312}, {-35354754, -23899358},
|
||||
{-36278795, -23351325}, {-37170015, -22762146}, {-38025776, -22134628},
|
||||
{-38845825, -21468175}, {-39627905, -20764801}, {-40370549, -20026061},
|
||||
{-41072075, -19253859}, {-41731000, -18450032}, {-42345940, -17616466},
|
||||
{-42915530, -16755283}, {-43438684, -15868338}, {-43914245, -14957822},
|
||||
{-44341235, -14025879}, {-44718686, -13074712}, {-45045890, -12106566},
|
||||
{-45322386, -11123499}, {-45547674, -10127656}, {-45721021, - 9121581},
|
||||
{-45842174, - 8107658}, {-45910990, - 7088089}, {-45927405, - 6065217},
|
||||
{-45891432, - 5041288}, {-45803222, - 4018728}, {-45663042, - 2999801},
|
||||
{-45471245, - 1986784}, {-45230690, - 991761}, {-38655400, 23513180},
|
||||
{-38366034, 24494742}, {-38025334, 25467666}, {-37636844, 26420074},
|
||||
{-37200678, 27349843}, {-36718169, 28254419}, {-36191104, 29131704},
|
||||
{-35620587, 29979748}, {-35007621, 30796895}, {-34353751, 31580950},
|
||||
{-33660293, 32330182}, {-32928806, 33042775}, {-32160862, 33717057},
|
||||
{-31358104, 34351432}, {-30522331, 34944323}, {-29655434, 35494277},
|
||||
{-28759338, 35999922}, {-27835963, 36460011}, {-26921721, 36858494},
|
||||
{-25914008, 37239556}, {-24919466, 37557049}, {-24204878, 37746930},
|
||||
{-22880526, 38041931}, {-21833362, 38209050}, {-21449204, 38252031},
|
||||
{-20775657, 38324377}, {-19711119, 38387480}, {-18638667, 38398812},
|
||||
{-17762260, 38366962}, {-16480321, 38266321}, {-15396213, 38120856},
|
||||
{-14327987, 37925343}, {- 5801522, 36175494}, { 7791637, 33457589},
|
||||
{ 15887399, 31878986}, { 28428609, 29478881}, { 28438392, 29512722},
|
||||
{ 27850323, 29743358}, { 27058729, 29970066}, { 14135560, 32452875},
|
||||
{ 6101685, 34019760}, {- 5352362, 36305237}, {-14423391, 38160442},
|
||||
{-15528705, 38361745}, {-16625379, 38507834}, {-17721787, 38600631},
|
||||
{-18812787, 38641330}, {-19563804, 38633844}, {-20975692, 38563412},
|
||||
{-22036069, 38446419}, {-23087710, 38277136}, {-24123993, 38056689},
|
||||
{-25141240, 37786307}, {-26138324, 37466465}, {-26851801, 37197652},
|
||||
{-28067514, 36680229}, {-28988984, 36219404}, {-29886302, 35711371},
|
||||
{-30754551, 35158840}, {-31124518, 34896643}, {-31589528, 34564743},
|
||||
{-32392776, 33928220}, {-33161225, 33251721}, {-33454722, 32966117},
|
||||
{-33891684, 32538320}, {-34585318, 31787066}, {-35239508, 31000793},
|
||||
{-35527715, 30616853}, {-35851756, 30182731}, {-36422833, 29331982},
|
||||
{-36950377, 28452000}, {-37265788, 27860633}, {-37432874, 27545549},
|
||||
{-37870512, 26612217}, {-38261423, 25655915}, {-38581885, 24744387},
|
||||
{-38902507, 23671594}, {-45523689, - 1006672}, {-45770290, - 2026713},
|
||||
{-45963584, - 3043930}, {-46104330, - 4066310}, {-46192377, - 5092635},
|
||||
{-46228310, - 6120019}, {-46211987, - 7145807}, {-46143426, - 8167812},
|
||||
{-46022719, - 9183674}, {-45850055, -10191399}, {-45625772, -11188531},
|
||||
{-45350245, -12172975}, {-45023965, -13142600}, {-44647538, -14095222},
|
||||
{-44221691, -15028602}, {-43747176, -15940794}, {-43224933, -16829570},
|
||||
{-42655872, -17693052}, {-42041183, -18529065}, {-41381752, -19335983},
|
||||
{-40677899, -20112975}, {-39932077, -20856972}, {-39145730, -21566171},
|
||||
{-38320552, -22238686}, {-37458030, -22872953}, {-36560036, -23467217},
|
||||
{-35627745, -24020614}, {-34662272, -24532977}, {-33667551, -25000722},
|
||||
{-32645434, -25422669}, {-31588226, -25801077}, {-24380013, -28208306},
|
||||
{-24380013, -28208306}, {-13354262, -31942517}, {-13354261, -31942515},
|
||||
{-2032305, -35842454}, { 8025116, -39348505}, { 8820397, -39587703},
|
||||
{ 9636283, -39751794}, { 9847092, -39773278}};
|
||||
|
||||
VD vd;
|
||||
Lines lines = to_lines(polygon);
|
||||
vd.construct_voronoi(lines.begin(), lines.end());
|
||||
#ifdef VORONOI_DEBUG_OUT
|
||||
// dump_voronoi_to_svg(debug_out_path("voronoi-cell-source-point-spe2298.svg").c_str(), vd, Points(), lines);
|
||||
#endif
|
||||
|
||||
REQUIRE(vd.is_valid());
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp
|
||||
sla_supptgen_tests.cpp
|
||||
sla_raycast_tests.cpp
|
||||
sla_supptreeutils_tests.cpp
|
||||
sla_archive_readwrite_tests.cpp)
|
||||
sla_archive_readwrite_tests.cpp
|
||||
sla_zcorrection_tests.cpp)
|
||||
|
||||
# mold linker for successful linking needs also to link TBB library and link it before libslic3r.
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common TBB::tbb TBB::tbbmalloc libslic3r)
|
||||
|
||||
123
tests/sla_print/sla_zcorrection_tests.cpp
Normal file
123
tests/sla_print/sla_zcorrection_tests.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <test_utils.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "libslic3r/TriangleMeshSlicer.hpp"
|
||||
#include "libslic3r/SLA/ZCorrection.hpp"
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
#include "libslic3r/SVG.hpp"
|
||||
|
||||
void print_depthmap(std::string_view prefix,
|
||||
const Slic3r::BoundingBox &bb,
|
||||
const Slic3r::sla::zcorr_detail::DepthMap &dm)
|
||||
{
|
||||
using namespace Slic3r;
|
||||
|
||||
size_t cnt = 0;
|
||||
for (const sla::zcorr_detail::DepthMapLayer &layer : dm) {
|
||||
SVG svg(std::string(prefix) + std::to_string(cnt++) + ".svg", bb);
|
||||
for (const auto &[depth, dpolys] : layer) {
|
||||
svg.draw_outline(dpolys);
|
||||
svg.draw(dpolys, "green", 1. + depth / 10.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Number of layers should be equal after z correction", "[ZCorr]")
|
||||
{
|
||||
using namespace Slic3r;
|
||||
|
||||
const size_t Layers = random_value(size_t{1}, size_t{100});
|
||||
INFO("Layers = " << Layers);
|
||||
|
||||
float zcorr_depth = GENERATE(0.f, random_value(0.01f, 10.f));
|
||||
|
||||
std::vector<ExPolygons> slices(Layers);
|
||||
std::vector<float> hgrid = grid(0.f, Layers * 1.f, 1.f);
|
||||
|
||||
std::vector<ExPolygons> output = sla::apply_zcorrection(slices, hgrid, zcorr_depth);
|
||||
|
||||
REQUIRE(slices.size() == output.size());
|
||||
}
|
||||
|
||||
TEST_CASE("Testing DepthMap for a cube", "[ZCorr]")
|
||||
{
|
||||
using namespace Slic3r;
|
||||
|
||||
TriangleMesh mesh = load_model("20mm_cube.obj");
|
||||
auto bb = bounding_box(mesh);
|
||||
bb.offset(-0.1);
|
||||
|
||||
std::vector<float> hgrid = grid<float>(bb.min.z(), bb.max.z(), 1.f);
|
||||
|
||||
std::vector<ExPolygons> slices = slice_mesh_ex(mesh.its, hgrid, {});
|
||||
|
||||
sla::zcorr_detail::DepthMap dmap = sla::zcorr_detail::create_depthmap(slices, hgrid);
|
||||
|
||||
REQUIRE(dmap.size() == slices.size());
|
||||
|
||||
for (size_t i = 0; i < slices.size(); ++i) {
|
||||
const auto &dlayer = dmap[i];
|
||||
const ExPolygons &slayer = slices[i];
|
||||
REQUIRE(dlayer.size() == 1);
|
||||
REQUIRE(dlayer.begin()->first == i);
|
||||
double ad = area(dlayer.begin()->second);
|
||||
double as = area(slayer);
|
||||
REQUIRE(ad == Approx(as).margin(EPSILON));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Testing DepthMap for arbitrary shapes", "[ZCorr]")
|
||||
{
|
||||
using namespace Slic3r;
|
||||
|
||||
auto modelname = GENERATE("V_standing.obj", "A_upsidedown.obj");
|
||||
|
||||
TriangleMesh mesh = load_model(modelname);
|
||||
auto bb = bounding_box(mesh);
|
||||
bb.offset(-0.1);
|
||||
|
||||
std::vector<float> hgrid = grid<float>(bb.min.z(), bb.max.z(), 0.5f);
|
||||
|
||||
std::vector<ExPolygons> slices = slice_mesh_ex(mesh.its, hgrid, {});
|
||||
|
||||
size_t zcorr_layers = GENERATE(size_t{0}, random_value(size_t{1}, size_t{10}));
|
||||
|
||||
sla::zcorr_detail::DepthMap dmap =
|
||||
sla::zcorr_detail::create_depthmap(slices, hgrid, zcorr_layers);
|
||||
|
||||
#ifndef NDEBUG
|
||||
print_depthmap("debug_dmap", scaled(to_2d(bb)), dmap);
|
||||
#endif
|
||||
|
||||
REQUIRE(dmap.size() == slices.size());
|
||||
|
||||
auto corrslices_fast = sla::apply_zcorrection(slices, zcorr_layers);
|
||||
sla::zcorr_detail::apply_zcorrection(dmap, zcorr_layers);
|
||||
|
||||
for (size_t i = 0; i < corrslices_fast.size(); ++i) {
|
||||
ExPolygons dlayer = sla::zcorr_detail::merged_layer(dmap[i]);
|
||||
const ExPolygons &slayer = corrslices_fast[i];
|
||||
double ad = area(dlayer);
|
||||
double as = area(slayer);
|
||||
REQUIRE(ad == Approx(as).margin(EPSILON));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test depth to layers calculation", "[ZCorr]") {
|
||||
using namespace Slic3r;
|
||||
|
||||
float layer_h = 0.5f;
|
||||
std::vector<float> hgrid = grid(0.f, 100.f, layer_h);
|
||||
|
||||
float depth = GENERATE(0.f,
|
||||
random_value(0.01f, 0.499f),
|
||||
0.5f,
|
||||
random_value(0.501f, 10.f));
|
||||
|
||||
for (size_t i = 0; i < hgrid.size(); ++i) {
|
||||
auto expected_lyrs = std::min(i, static_cast<size_t>(std::ceil(depth/layer_h)));
|
||||
REQUIRE(sla::zcorr_detail::depth_to_layers(hgrid, i, depth) == expected_lyrs);
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,12 @@ add_executable(${_TEST_NAME}_tests
|
||||
slic3r_jobs_tests.cpp
|
||||
slic3r_version_tests.cpp
|
||||
slic3r_arrangejob_tests.cpp
|
||||
secretstore_tests.cpp
|
||||
)
|
||||
|
||||
# mold linker for successful linking needs also to link TBB library and link it before libslic3r.
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common TBB::tbb TBB::tbbmalloc libslic3r_gui libslic3r)
|
||||
|
||||
if (MSVC)
|
||||
target_link_libraries(${_TEST_NAME}_tests Setupapi.lib)
|
||||
endif ()
|
||||
|
||||
8
tests/slic3rutils/secretstore_tests.cpp
Normal file
8
tests/slic3rutils/secretstore_tests.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "slic3r/Utils/Secrets.hpp"
|
||||
|
||||
/*TEST_CASE("Secret store test", "[secretstore]") {
|
||||
|
||||
REQUIRE(Slic3r::check_secrets());
|
||||
}*/
|
||||
@@ -27,6 +27,7 @@ TEMPLATE_LIST_TEST_CASE("Empty worker should not block when queried for idle", "
|
||||
|
||||
REQUIRE(worker.is_idle());
|
||||
}
|
||||
|
||||
TEMPLATE_LIST_TEST_CASE("Empty worker should not do anything", "[Jobs]", TestClasses) {
|
||||
TestType worker{std::make_unique<Progress>()};
|
||||
|
||||
@@ -58,6 +59,7 @@ TEMPLATE_LIST_TEST_CASE("State should not be idle while running a job", "[Jobs]"
|
||||
|
||||
// make sure that the job starts BEFORE the worker.wait_for_idle() is called
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
worker.wait_for_idle();
|
||||
|
||||
REQUIRE(worker.is_idle());
|
||||
|
||||
Reference in New Issue
Block a user