mirror of
https://github.com/QIDITECH/QIDIStudio.git
synced 2026-01-31 00:48:41 +03:00
update
This commit is contained in:
27
tests/fff_print/CMakeLists.txt
Normal file
27
tests/fff_print/CMakeLists.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
add_executable(${_TEST_NAME}_tests
|
||||
${_TEST_NAME}_tests.cpp
|
||||
test_data.cpp
|
||||
test_data.hpp
|
||||
test_extrusion_entity.cpp
|
||||
test_fill.cpp
|
||||
test_flow.cpp
|
||||
test_gcode.cpp
|
||||
test_gcodewriter.cpp
|
||||
test_model.cpp
|
||||
test_print.cpp
|
||||
test_printgcode.cpp
|
||||
test_printobject.cpp
|
||||
test_skirt_brim.cpp
|
||||
test_support_material.cpp
|
||||
test_trianglemesh.cpp
|
||||
)
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
|
||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||
|
||||
if (WIN32)
|
||||
qidislicer_copy_dlls(${_TEST_NAME}_tests)
|
||||
endif()
|
||||
|
||||
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
||||
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${CATCH_EXTRA_ARGS})
|
||||
3
tests/fff_print/fff_print_tests.cpp
Normal file
3
tests/fff_print/fff_print_tests.cpp
Normal file
@@ -0,0 +1,3 @@
|
||||
#include <catch_main.hpp>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
370
tests/fff_print/test_data.cpp
Normal file
370
tests/fff_print/test_data.cpp
Normal file
File diff suppressed because one or more lines are too long
86
tests/fff_print/test_data.hpp
Normal file
86
tests/fff_print/test_data.hpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#ifndef SLIC3R_TEST_DATA_HPP
|
||||
#define SLIC3R_TEST_DATA_HPP
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Slic3r { namespace Test {
|
||||
|
||||
constexpr double MM_PER_MIN = 60.0;
|
||||
|
||||
/// Enumeration of test meshes
|
||||
enum class TestMesh {
|
||||
A,
|
||||
L,
|
||||
V,
|
||||
_40x10,
|
||||
cube_20x20x20,
|
||||
sphere_50mm,
|
||||
bridge,
|
||||
bridge_with_hole,
|
||||
cube_with_concave_hole,
|
||||
cube_with_hole,
|
||||
gt2_teeth,
|
||||
ipadstand,
|
||||
overhang,
|
||||
pyramid,
|
||||
sloping_hole,
|
||||
slopy_cube,
|
||||
small_dorito,
|
||||
step,
|
||||
two_hollow_squares
|
||||
};
|
||||
|
||||
// Neccessary for <c++17
|
||||
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;
|
||||
|
||||
/// Port of Slic3r::Test::mesh
|
||||
/// Basic cubes/boxes should call TriangleMesh::make_cube() directly and rescale/translate it
|
||||
TriangleMesh mesh(TestMesh m);
|
||||
|
||||
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, double epsilon) { return abs(a - b) < epsilon; }
|
||||
|
||||
Slic3r::Model model(const std::string& model_name, TriangleMesh&& _mesh);
|
||||
void init_print(std::vector<TriangleMesh> &&meshes, Slic3r::Print &print, Slic3r::Model& model, const DynamicPrintConfig &config_in, bool comments = false);
|
||||
void init_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(), bool comments = false);
|
||||
void init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, const Slic3r::DynamicPrintConfig &config_in = Slic3r::DynamicPrintConfig::full_print_config(), bool comments = false);
|
||||
void init_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
|
||||
void init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, Slic3r::Model& model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments = false);
|
||||
|
||||
void init_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 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);
|
||||
|
||||
} } // namespace Slic3r::Test
|
||||
|
||||
|
||||
#endif // SLIC3R_TEST_DATA_HPP
|
||||
85
tests/fff_print/test_extrusion_entity.cpp
Normal file
85
tests/fff_print/test_extrusion_entity.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "libslic3r/ExtrusionEntityCollection.hpp"
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include "test_data.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
static inline Slic3r::Point random_point(float LO=-50, float HI=50)
|
||||
{
|
||||
Vec2f pt = Vec2f(LO, LO) + (Vec2d(rand(), rand()) * (HI-LO) / RAND_MAX).cast<float>();
|
||||
return pt.cast<coord_t>();
|
||||
}
|
||||
|
||||
// build a sample extrusion entity collection with random start and end points.
|
||||
static Slic3r::ExtrusionPath random_path(size_t length = 20, float LO = -50, float HI = 50)
|
||||
{
|
||||
ExtrusionPath t {erPerimeter, 1.0, 1.0, 1.0};
|
||||
for (size_t j = 0; j < length; ++ j)
|
||||
t.polyline.append(random_point(LO, HI));
|
||||
return t;
|
||||
}
|
||||
|
||||
static Slic3r::ExtrusionPaths random_paths(size_t count = 10, size_t length = 20, float LO = -50, float HI = 50)
|
||||
{
|
||||
Slic3r::ExtrusionPaths p;
|
||||
for (size_t i = 0; i < count; ++ i)
|
||||
p.push_back(random_path(length, LO, HI));
|
||||
return p;
|
||||
}
|
||||
|
||||
SCENARIO("ExtrusionEntityCollection: Polygon flattening", "[ExtrusionEntity]") {
|
||||
srand(0xDEADBEEF); // consistent seed for test reproducibility.
|
||||
|
||||
// Generate one specific random path set and save it for later comparison
|
||||
Slic3r::ExtrusionPaths nosort_path_set = random_paths();
|
||||
|
||||
Slic3r::ExtrusionEntityCollection sub_nosort;
|
||||
sub_nosort.append(nosort_path_set);
|
||||
sub_nosort.no_sort = true;
|
||||
|
||||
Slic3r::ExtrusionEntityCollection sub_sort;
|
||||
sub_sort.no_sort = false;
|
||||
sub_sort.append(random_paths());
|
||||
|
||||
GIVEN("A Extrusion Entity Collection with a child that has one child that is marked as no-sort") {
|
||||
Slic3r::ExtrusionEntityCollection sample;
|
||||
Slic3r::ExtrusionEntityCollection output;
|
||||
|
||||
sample.append(sub_sort);
|
||||
sample.append(sub_nosort);
|
||||
sample.append(sub_sort);
|
||||
|
||||
WHEN("The EEC is flattened with default options (preserve_order=false)") {
|
||||
output = sample.flatten();
|
||||
THEN("The output EEC contains no Extrusion Entity Collections") {
|
||||
CHECK(std::count_if(output.entities.cbegin(), output.entities.cend(), [=](const ExtrusionEntity* e) {return e->is_collection();}) == 0);
|
||||
}
|
||||
}
|
||||
WHEN("The EEC is flattened with preservation (preserve_order=true)") {
|
||||
output = sample.flatten(true);
|
||||
THEN("The output EECs contains one EEC.") {
|
||||
CHECK(std::count_if(output.entities.cbegin(), output.entities.cend(), [=](const ExtrusionEntity* e) {return e->is_collection();}) == 1);
|
||||
}
|
||||
AND_THEN("The ordered EEC contains the same order of elements than the original") {
|
||||
// find the entity in the collection
|
||||
for (auto e : output.entities)
|
||||
if (e->is_collection()) {
|
||||
ExtrusionEntityCollection *temp = dynamic_cast<ExtrusionEntityCollection*>(e);
|
||||
// check each Extrusion path against nosort_path_set to see if the first and last match the same
|
||||
CHECK(nosort_path_set.size() == temp->entities.size());
|
||||
for (size_t i = 0; i < nosort_path_set.size(); ++ i) {
|
||||
CHECK(temp->entities[i]->first_point() == nosort_path_set[i].first_point());
|
||||
CHECK(temp->entities[i]->last_point() == nosort_path_set[i].last_point());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
478
tests/fff_print/test_fill.cpp
Normal file
478
tests/fff_print/test_fill.cpp
Normal file
@@ -0,0 +1,478 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/Fill/Fill.hpp"
|
||||
#include "libslic3r/Flow.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/SVG.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include "test_data.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacing, double angle = 0, double density = 1.0);
|
||||
|
||||
#if 0
|
||||
TEST_CASE("Fill: adjusted solid distance") {
|
||||
int surface_width = 250;
|
||||
int distance = Slic3r::Flow::solid_spacing(surface_width, 47);
|
||||
REQUIRE(distance == Approx(50));
|
||||
REQUIRE(surface_width % distance == 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("Fill: Pattern Path Length", "[Fill]") {
|
||||
std::unique_ptr<Slic3r::Fill> filler(Slic3r::Fill::new_from_type("rectilinear"));
|
||||
filler->angle = float(-(PI)/2.0);
|
||||
FillParams fill_params;
|
||||
filler->spacing = 5;
|
||||
fill_params.dont_adjust = true;
|
||||
//fill_params.endpoints_overlap = false;
|
||||
fill_params.density = float(filler->spacing / 50.0);
|
||||
|
||||
auto test = [&filler, &fill_params] (const ExPolygon& poly) -> Slic3r::Polylines {
|
||||
Slic3r::Surface surface(stTop, poly);
|
||||
return filler->fill_surface(&surface, fill_params);
|
||||
};
|
||||
|
||||
SECTION("Square") {
|
||||
Slic3r::Points test_set;
|
||||
test_set.reserve(4);
|
||||
std::vector<Vec2d> points {Vec2d(0,0), Vec2d(100,0), Vec2d(100,100), Vec2d(0,100)};
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
std::transform(points.cbegin()+i, points.cend(), std::back_inserter(test_set), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } );
|
||||
std::transform(points.cbegin(), points.cbegin()+i, std::back_inserter(test_set), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } );
|
||||
Slic3r::Polylines paths = test(Slic3r::ExPolygon(test_set));
|
||||
REQUIRE(paths.size() == 1); // one continuous path
|
||||
|
||||
// TODO: determine what the "Expected length" should be for rectilinear fill of a 100x100 polygon.
|
||||
// This check only checks that it's above scale(3*100 + 2*50) + scaled_epsilon.
|
||||
// ok abs($paths->[0]->length - scale(3*100 + 2*50)) - scaled_epsilon, 'path has expected length';
|
||||
REQUIRE(std::abs(paths[0].length() - static_cast<double>(scale_(3*100 + 2*50))) - SCALED_EPSILON > 0); // path has expected length
|
||||
|
||||
test_set.clear();
|
||||
}
|
||||
}
|
||||
SECTION("Diamond with endpoints on grid") {
|
||||
std::vector<Vec2d> points {Vec2d(0,0), Vec2d(100,0), Vec2d(150,50), Vec2d(100,100), Vec2d(0,100), Vec2d(-50,50)};
|
||||
Slic3r::Points test_set;
|
||||
test_set.reserve(6);
|
||||
std::transform(points.cbegin(), points.cend(), std::back_inserter(test_set), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } );
|
||||
Slic3r::Polylines paths = test(Slic3r::ExPolygon(test_set));
|
||||
REQUIRE(paths.size() == 1); // one continuous path
|
||||
}
|
||||
|
||||
SECTION("Square with hole") {
|
||||
std::vector<Vec2d> square {Vec2d(0,0), Vec2d(100,0), Vec2d(100,100), Vec2d(0,100)};
|
||||
std::vector<Vec2d> hole {Vec2d(25,25), Vec2d(75,25), Vec2d(75,75), Vec2d(25,75) };
|
||||
std::reverse(hole.begin(), hole.end());
|
||||
|
||||
Slic3r::Points test_hole;
|
||||
Slic3r::Points test_square;
|
||||
|
||||
std::transform(square.cbegin(), square.cend(), std::back_inserter(test_square), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } );
|
||||
std::transform(hole.cbegin(), hole.cend(), std::back_inserter(test_hole), [] (const Vec2d& a) -> Point { return Point::new_scale(a.x(), a.y()); } );
|
||||
|
||||
for (double angle : {-(PI/2.0), -(PI/4.0), -(PI), PI/2.0, PI}) {
|
||||
for (double spacing : {25.0, 5.0, 7.5, 8.5}) {
|
||||
fill_params.density = float(filler->spacing / spacing);
|
||||
filler->angle = float(angle);
|
||||
ExPolygon e(test_square, test_hole);
|
||||
Slic3r::Polylines paths = test(e);
|
||||
#if 0
|
||||
{
|
||||
BoundingBox bbox = get_extents(e);
|
||||
SVG svg("c:\\data\\temp\\square_with_holes.svg", bbox);
|
||||
svg.draw(e);
|
||||
svg.draw(paths);
|
||||
svg.Close();
|
||||
}
|
||||
#endif
|
||||
REQUIRE((paths.size() >= 1 && paths.size() <= 3));
|
||||
// paths don't cross hole
|
||||
REQUIRE(diff_pl(paths, offset(e, float(SCALED_EPSILON*10))).size() == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
SECTION("Regression: Missing infill segments in some rare circumstances") {
|
||||
filler->angle = float(PI/4.0);
|
||||
fill_params.dont_adjust = false;
|
||||
filler->spacing = 0.654498;
|
||||
//filler->endpoints_overlap = unscale(359974);
|
||||
fill_params.density = 1;
|
||||
filler->layer_id = 66;
|
||||
filler->z = 20.15;
|
||||
|
||||
Slic3r::Points points {Point(25771516,14142125),Point(14142138,25771515),Point(2512749,14142131),Point(14142125,2512749)};
|
||||
Slic3r::Polylines paths = test(Slic3r::ExPolygon(points));
|
||||
REQUIRE(paths.size() == 1); // one continuous path
|
||||
|
||||
// TODO: determine what the "Expected length" should be for rectilinear fill of a 100x100 polygon.
|
||||
// This check only checks that it's above scale(3*100 + 2*50) + scaled_epsilon.
|
||||
// ok abs($paths->[0]->length - scale(3*100 + 2*50)) - scaled_epsilon, 'path has expected length';
|
||||
REQUIRE(std::abs(paths[0].length() - static_cast<double>(scale_(3*100 + 2*50))) - SCALED_EPSILON > 0); // path has expected length
|
||||
}
|
||||
|
||||
SECTION("Rotated Square") {
|
||||
Slic3r::Points square { Point::new_scale(0,0), Point::new_scale(50,0), Point::new_scale(50,50), Point::new_scale(0,50)};
|
||||
Slic3r::ExPolygon expolygon(square);
|
||||
std::unique_ptr<Slic3r::Fill> filler(Slic3r::Fill::new_from_type("rectilinear"));
|
||||
filler->bounding_box = get_extents(expolygon.contour);
|
||||
filler->angle = 0;
|
||||
|
||||
Surface surface(stTop, expolygon);
|
||||
auto flow = Slic3r::Flow(0.69f, 0.4f, 0.50f);
|
||||
|
||||
FillParams fill_params;
|
||||
fill_params.density = 1.0;
|
||||
filler->spacing = flow.spacing();
|
||||
|
||||
for (auto angle : { 0.0, 45.0}) {
|
||||
surface.expolygon.rotate(angle, Point(0,0));
|
||||
Polylines paths = filler->fill_surface(&surface, fill_params);
|
||||
REQUIRE(paths.size() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
#if 0 // Disabled temporarily due to precission issues on the Mac VM
|
||||
SECTION("Solid surface fill") {
|
||||
Slic3r::Points points {
|
||||
Point::new_scale(6883102, 9598327.01296997),
|
||||
Point::new_scale(6883102, 20327272.01297),
|
||||
Point::new_scale(3116896, 20327272.01297),
|
||||
Point::new_scale(3116896, 9598327.01296997)
|
||||
};
|
||||
Slic3r::ExPolygon expolygon(points);
|
||||
|
||||
REQUIRE(test_if_solid_surface_filled(expolygon, 0.55) == true);
|
||||
for (size_t i = 0; i <= 20; ++i)
|
||||
{
|
||||
expolygon.scale(1.05);
|
||||
REQUIRE(test_if_solid_surface_filled(expolygon, 0.55) == true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("Solid surface fill") {
|
||||
Slic3r::Points points {
|
||||
Slic3r::Point(59515297,5422499),Slic3r::Point(59531249,5578697),Slic3r::Point(59695801,6123186),
|
||||
Slic3r::Point(59965713,6630228),Slic3r::Point(60328214,7070685),Slic3r::Point(60773285,7434379),
|
||||
Slic3r::Point(61274561,7702115),Slic3r::Point(61819378,7866770),Slic3r::Point(62390306,7924789),
|
||||
Slic3r::Point(62958700,7866744),Slic3r::Point(63503012,7702244),Slic3r::Point(64007365,7434357),
|
||||
Slic3r::Point(64449960,7070398),Slic3r::Point(64809327,6634999),Slic3r::Point(65082143,6123325),
|
||||
Slic3r::Point(65245005,5584454),Slic3r::Point(65266967,5422499),Slic3r::Point(66267307,5422499),
|
||||
Slic3r::Point(66269190,8310081),Slic3r::Point(66275379,17810072),Slic3r::Point(66277259,20697500),
|
||||
Slic3r::Point(65267237,20697500),Slic3r::Point(65245004,20533538),Slic3r::Point(65082082,19994444),
|
||||
Slic3r::Point(64811462,19488579),Slic3r::Point(64450624,19048208),Slic3r::Point(64012101,18686514),
|
||||
Slic3r::Point(63503122,18415781),Slic3r::Point(62959151,18251378),Slic3r::Point(62453416,18198442),
|
||||
Slic3r::Point(62390147,18197355),Slic3r::Point(62200087,18200576),Slic3r::Point(61813519,18252990),
|
||||
Slic3r::Point(61274433,18415918),Slic3r::Point(60768598,18686517),Slic3r::Point(60327567,19047892),
|
||||
Slic3r::Point(59963609,19493297),Slic3r::Point(59695865,19994587),Slic3r::Point(59531222,20539379),
|
||||
Slic3r::Point(59515153,20697500),Slic3r::Point(58502480,20697500),Slic3r::Point(58502480,5422499)
|
||||
};
|
||||
Slic3r::ExPolygon expolygon(points);
|
||||
|
||||
REQUIRE(test_if_solid_surface_filled(expolygon, 0.55) == true);
|
||||
REQUIRE(test_if_solid_surface_filled(expolygon, 0.55, PI/2.0) == true);
|
||||
}
|
||||
SECTION("Solid surface fill") {
|
||||
Slic3r::Points points {
|
||||
Point::new_scale(0,0),Point::new_scale(98,0),Point::new_scale(98,10), Point::new_scale(0,10)
|
||||
};
|
||||
Slic3r::ExPolygon expolygon(points);
|
||||
|
||||
REQUIRE(test_if_solid_surface_filled(expolygon, 0.5, 45.0, 0.99) == true);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
my $collection = Slic3r::Polyline::Collection->new(
|
||||
Slic3r::Polyline->new([0,15], [0,18], [0,20]),
|
||||
Slic3r::Polyline->new([0,10], [0,8], [0,5]),
|
||||
);
|
||||
is_deeply
|
||||
[ map $_->[Y], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ],
|
||||
[20, 18, 15, 10, 8, 5],
|
||||
'chained path';
|
||||
}
|
||||
|
||||
{
|
||||
my $collection = Slic3r::Polyline::Collection->new(
|
||||
Slic3r::Polyline->new([4,0], [10,0], [15,0]),
|
||||
Slic3r::Polyline->new([10,5], [15,5], [20,5]),
|
||||
);
|
||||
is_deeply
|
||||
[ map $_->[X], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ],
|
||||
[reverse 4, 10, 15, 10, 15, 20],
|
||||
'chained path';
|
||||
}
|
||||
|
||||
{
|
||||
my $collection = Slic3r::ExtrusionPath::Collection->new(
|
||||
map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1),
|
||||
Slic3r::Polyline->new([0,15], [0,18], [0,20]),
|
||||
Slic3r::Polyline->new([0,10], [0,8], [0,5]),
|
||||
);
|
||||
is_deeply
|
||||
[ map $_->[Y], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ],
|
||||
[20, 18, 15, 10, 8, 5],
|
||||
'chained path';
|
||||
}
|
||||
|
||||
{
|
||||
my $collection = Slic3r::ExtrusionPath::Collection->new(
|
||||
map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1),
|
||||
Slic3r::Polyline->new([15,0], [10,0], [4,0]),
|
||||
Slic3r::Polyline->new([10,5], [15,5], [20,5]),
|
||||
);
|
||||
is_deeply
|
||||
[ map $_->[X], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ],
|
||||
[reverse 4, 10, 15, 10, 15, 20],
|
||||
'chained path';
|
||||
}
|
||||
|
||||
for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) {
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('fill_pattern', $pattern);
|
||||
$config->set('external_fill_pattern', $pattern);
|
||||
$config->set('perimeters', 1);
|
||||
$config->set('skirts', 0);
|
||||
$config->set('fill_density', 20);
|
||||
$config->set('layer_height', 0.05);
|
||||
$config->set('perimeter_extruder', 1);
|
||||
$config->set('infill_extruder', 2);
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2);
|
||||
ok my $gcode = Slic3r::Test::gcode($print), "successful $pattern infill generation";
|
||||
my $tool = undef;
|
||||
my @perimeter_points = my @infill_points = ();
|
||||
Slic3r::GCode::Reader->new->parse($gcode, sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd =~ /^T(\d+)/) {
|
||||
$tool = $1;
|
||||
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
|
||||
if ($tool == $config->perimeter_extruder-1) {
|
||||
push @perimeter_points, Slic3r::Point->new_scale($args->{X}, $args->{Y});
|
||||
} elsif ($tool == $config->infill_extruder-1) {
|
||||
push @infill_points, Slic3r::Point->new_scale($args->{X}, $args->{Y});
|
||||
}
|
||||
}
|
||||
});
|
||||
my $convex_hull = convex_hull(\@perimeter_points);
|
||||
ok !(defined first { !$convex_hull->contains_point($_) } @infill_points), "infill does not exceed perimeters ($pattern)";
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('infill_only_where_needed', 1);
|
||||
$config->set('bottom_solid_layers', 0);
|
||||
$config->set('infill_extruder', 2);
|
||||
$config->set('infill_extrusion_width', 0.5);
|
||||
$config->set('fill_density', 40);
|
||||
$config->set('cooling', 0); # for preventing speeds from being altered
|
||||
$config->set('first_layer_speed', '100%'); # for preventing speeds from being altered
|
||||
|
||||
my $test = sub {
|
||||
my $print = Slic3r::Test::init_print('pyramid', config => $config);
|
||||
|
||||
my $tool = undef;
|
||||
my @infill_extrusions = (); # array of polylines
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd =~ /^T(\d+)/) {
|
||||
$tool = $1;
|
||||
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
|
||||
if ($tool == $config->infill_extruder-1) {
|
||||
push @infill_extrusions, Slic3r::Line->new_scale(
|
||||
[ $self->X, $self->Y ],
|
||||
[ $info->{new_X}, $info->{new_Y} ],
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
return 0 if !@infill_extrusions; # prevent calling convex_hull() with no points
|
||||
|
||||
my $convex_hull = convex_hull([ map $_->pp, map @$_, @infill_extrusions ]);
|
||||
return unscale unscale sum(map $_->area, @{offset([$convex_hull], scale(+$config->infill_extrusion_width/2))});
|
||||
};
|
||||
|
||||
my $tolerance = 5; # mm^2
|
||||
|
||||
$config->set('solid_infill_below_area', 0);
|
||||
ok $test->() < $tolerance,
|
||||
'no infill is generated when using infill_only_where_needed on a pyramid';
|
||||
|
||||
$config->set('solid_infill_below_area', 70);
|
||||
ok abs($test->() - $config->solid_infill_below_area) < $tolerance,
|
||||
'infill is only generated under the forced solid shells';
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('skirts', 0);
|
||||
$config->set('perimeters', 1);
|
||||
$config->set('fill_density', 0);
|
||||
$config->set('top_solid_layers', 0);
|
||||
$config->set('bottom_solid_layers', 0);
|
||||
$config->set('solid_infill_below_area', 20000000);
|
||||
$config->set('solid_infill_every_layers', 2);
|
||||
$config->set('perimeter_speed', 99);
|
||||
$config->set('external_perimeter_speed', 99);
|
||||
$config->set('cooling', 0);
|
||||
$config->set('first_layer_speed', '100%');
|
||||
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my %layers_with_extrusion = ();
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd eq 'G1' && $info->{dist_XY} > 0 && $info->{extruding}) {
|
||||
if (($args->{F} // $self->F) != $config->perimeter_speed*60) {
|
||||
$layers_with_extrusion{$self->Z} = ($args->{F} // $self->F);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ok !%layers_with_extrusion,
|
||||
"solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0";
|
||||
}
|
||||
|
||||
{
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('skirts', 0);
|
||||
$config->set('perimeters', 3);
|
||||
$config->set('fill_density', 0);
|
||||
$config->set('layer_height', 0.2);
|
||||
$config->set('first_layer_height', 0.2);
|
||||
$config->set('nozzle_diameter', [0.35]);
|
||||
$config->set('infill_extruder', 2);
|
||||
$config->set('solid_infill_extruder', 2);
|
||||
$config->set('infill_extrusion_width', 0.52);
|
||||
$config->set('solid_infill_extrusion_width', 0.52);
|
||||
$config->set('first_layer_extrusion_width', 0);
|
||||
|
||||
my $print = Slic3r::Test::init_print('A', config => $config);
|
||||
my %infill = (); # Z => [ Line, Line ... ]
|
||||
my $tool = undef;
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd =~ /^T(\d+)/) {
|
||||
$tool = $1;
|
||||
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
|
||||
if ($tool == $config->infill_extruder-1) {
|
||||
my $z = 1 * $self->Z;
|
||||
$infill{$z} ||= [];
|
||||
push @{$infill{$z}}, Slic3r::Line->new_scale(
|
||||
[ $self->X, $self->Y ],
|
||||
[ $info->{new_X}, $info->{new_Y} ],
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
my $grow_d = scale($config->infill_extrusion_width)/2;
|
||||
my $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]);
|
||||
my $layer1_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.4} } ]);
|
||||
my $diff = diff($layer0_infill, $layer1_infill);
|
||||
$diff = offset2_ex($diff, -$grow_d, +$grow_d);
|
||||
$diff = [ grep { $_->area > 2*(($grow_d*2)**2) } @$diff ];
|
||||
is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0';
|
||||
}
|
||||
|
||||
{
|
||||
# GH: #2697
|
||||
my $config = Slic3r::Config->new_from_defaults;
|
||||
$config->set('perimeter_extrusion_width', 0.72);
|
||||
$config->set('top_infill_extrusion_width', 0.1);
|
||||
$config->set('infill_extruder', 2); # in order to distinguish infill
|
||||
$config->set('solid_infill_extruder', 2); # in order to distinguish infill
|
||||
|
||||
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
|
||||
my %infill = (); # Z => [ Line, Line ... ]
|
||||
my %other = (); # Z => [ Line, Line ... ]
|
||||
my $tool = undef;
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd =~ /^T(\d+)/) {
|
||||
$tool = $1;
|
||||
} elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
|
||||
my $z = 1 * $self->Z;
|
||||
my $line = Slic3r::Line->new_scale(
|
||||
[ $self->X, $self->Y ],
|
||||
[ $info->{new_X}, $info->{new_Y} ],
|
||||
);
|
||||
if ($tool == $config->infill_extruder-1) {
|
||||
$infill{$z} //= [];
|
||||
push @{$infill{$z}}, $line;
|
||||
} else {
|
||||
$other{$z} //= [];
|
||||
push @{$other{$z}}, $line;
|
||||
}
|
||||
}
|
||||
});
|
||||
my $top_z = max(keys %infill);
|
||||
my $top_infill_grow_d = scale($config->top_infill_extrusion_width)/2;
|
||||
my $top_infill = union([ map @{$_->grow($top_infill_grow_d)}, @{ $infill{$top_z} } ]);
|
||||
my $perimeters_grow_d = scale($config->perimeter_extrusion_width)/2;
|
||||
my $perimeters = union([ map @{$_->grow($perimeters_grow_d)}, @{ $other{$top_z} } ]);
|
||||
my $covered = union_ex([ @$top_infill, @$perimeters ]);
|
||||
my @holes = map @{$_->holes}, @$covered;
|
||||
ok sum(map unscale unscale $_->area*-1, @holes) < 1, 'no gaps between top solid infill and perimeters';
|
||||
}
|
||||
*/
|
||||
|
||||
bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacing, double angle, double density)
|
||||
{
|
||||
std::unique_ptr<Slic3r::Fill> filler(Slic3r::Fill::new_from_type("rectilinear"));
|
||||
filler->bounding_box = get_extents(expolygon.contour);
|
||||
filler->angle = float(angle);
|
||||
|
||||
Flow flow(float(flow_spacing), 0.4f, float(flow_spacing));
|
||||
filler->spacing = flow.spacing();
|
||||
|
||||
FillParams fill_params;
|
||||
fill_params.density = float(density);
|
||||
fill_params.dont_adjust = false;
|
||||
|
||||
Surface surface(stBottom, expolygon);
|
||||
Slic3r::Polylines paths = filler->fill_surface(&surface, fill_params);
|
||||
|
||||
// check whether any part was left uncovered
|
||||
Polygons grown_paths;
|
||||
grown_paths.reserve(paths.size());
|
||||
|
||||
// figure out what is actually going on here re: data types
|
||||
float line_offset = float(scale_(filler->spacing / 2.0 + EPSILON));
|
||||
std::for_each(paths.begin(), paths.end(), [line_offset, &grown_paths] (const Slic3r::Polyline& p) {
|
||||
polygons_append(grown_paths, offset(p, line_offset));
|
||||
});
|
||||
|
||||
// Shrink the initial expolygon a bit, this simulates the infill / perimeter overlap that we usually apply.
|
||||
ExPolygons uncovered = diff_ex(offset(expolygon, - float(0.2 * scale_(flow_spacing))), grown_paths, ApplySafetyOffset::Yes);
|
||||
|
||||
// ignore very small dots
|
||||
const double scaled_flow_spacing = std::pow(scale_(flow_spacing), 2);
|
||||
uncovered.erase(std::remove_if(uncovered.begin(), uncovered.end(), [scaled_flow_spacing](const ExPolygon& poly) { return poly.area() < scaled_flow_spacing; }), uncovered.end());
|
||||
|
||||
#if 0
|
||||
if (! uncovered.empty()) {
|
||||
BoundingBox bbox = get_extents(expolygon.contour);
|
||||
bbox.merge(get_extents(uncovered));
|
||||
bbox.merge(get_extents(grown_paths));
|
||||
SVG svg("c:\\data\\temp\\test_if_solid_surface_filled.svg", bbox);
|
||||
svg.draw(expolygon);
|
||||
svg.draw(uncovered, "red");
|
||||
svg.Close();
|
||||
}
|
||||
#endif
|
||||
|
||||
return uncovered.empty(); // solid surface is fully filled
|
||||
}
|
||||
171
tests/fff_print/test_flow.cpp
Normal file
171
tests/fff_print/test_flow.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
|
||||
#include "test_data.hpp" // get access to init_print, etc
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/GCodeReader.hpp"
|
||||
#include "libslic3r/Flow.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
using namespace Slic3r::Test;
|
||||
using namespace Slic3r;
|
||||
|
||||
SCENARIO("Extrusion width specifics", "[Flow]") {
|
||||
GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer and a 20mm cube mesh") {
|
||||
// this is a sharedptr
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "brim_width", 2 },
|
||||
{ "skirts", 1 },
|
||||
{ "perimeters", 3 },
|
||||
{ "fill_density", "40%" },
|
||||
{ "first_layer_height", 0.3 }
|
||||
});
|
||||
|
||||
WHEN("first layer width set to 2mm") {
|
||||
Slic3r::Model model;
|
||||
config.set("first_layer_extrusion_width", 2);
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_print({TestMesh::cube_20x20x20}, print, model, config);
|
||||
|
||||
std::vector<double> E_per_mm_bottom;
|
||||
std::string gcode = Test::gcode(print);
|
||||
Slic3r::GCodeReader parser;
|
||||
const double layer_height = config.opt_float("layer_height");
|
||||
parser.parse_buffer(gcode, [&E_per_mm_bottom, layer_height] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
|
||||
{
|
||||
if (self.z() == Approx(layer_height).margin(0.01)) { // only consider first layer
|
||||
if (line.extruding(self) && line.dist_XY(self) > 0) {
|
||||
E_per_mm_bottom.emplace_back(line.dist_E(self) / line.dist_XY(self));
|
||||
}
|
||||
}
|
||||
});
|
||||
THEN(" First layer width applies to everything on first layer.") {
|
||||
bool pass = false;
|
||||
double avg_E = std::accumulate(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), 0.0) / static_cast<double>(E_per_mm_bottom.size());
|
||||
|
||||
pass = (std::count_if(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), [avg_E] (const double& v) { return v == Approx(avg_E); }) == 0);
|
||||
REQUIRE(pass == true);
|
||||
REQUIRE(E_per_mm_bottom.size() > 0); // make sure it actually passed because of extrusion
|
||||
}
|
||||
THEN(" First layer width does not apply to upper layer.") {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// needs gcode export
|
||||
SCENARIO(" Bridge flow specifics.", "[Flow]") {
|
||||
GIVEN("A default config with no cooling and a fixed bridge speed, flow ratio and an overhang mesh.") {
|
||||
WHEN("bridge_flow_ratio is set to 1.0") {
|
||||
THEN("Output flow is as expected.") {
|
||||
}
|
||||
}
|
||||
WHEN("bridge_flow_ratio is set to 0.5") {
|
||||
THEN("Output flow is as expected.") {
|
||||
}
|
||||
}
|
||||
WHEN("bridge_flow_ratio is set to 2.0") {
|
||||
THEN("Output flow is as expected.") {
|
||||
}
|
||||
}
|
||||
}
|
||||
GIVEN("A default config with no cooling and a fixed bridge speed, flow ratio, fixed extrusion width of 0.4mm and an overhang mesh.") {
|
||||
WHEN("bridge_flow_ratio is set to 1.0") {
|
||||
THEN("Output flow is as expected.") {
|
||||
}
|
||||
}
|
||||
WHEN("bridge_flow_ratio is set to 0.5") {
|
||||
THEN("Output flow is as expected.") {
|
||||
}
|
||||
}
|
||||
WHEN("bridge_flow_ratio is set to 2.0") {
|
||||
THEN("Output flow is as expected.") {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test the expected behavior for auto-width,
|
||||
/// spacing, etc
|
||||
SCENARIO("Flow: Flow math for non-bridges", "[Flow]") {
|
||||
GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") {
|
||||
ConfigOptionFloatOrPercent width(1.0, false);
|
||||
float nozzle_diameter = 0.4f;
|
||||
float layer_height = 0.4f;
|
||||
|
||||
// Spacing for non-bridges is has some overlap
|
||||
THEN("External perimeter flow has spacing fixed to 1.125 * nozzle_diameter") {
|
||||
auto flow = Flow::new_from_config_width(frExternalPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height);
|
||||
REQUIRE(flow.spacing() == Approx(1.125 * nozzle_diameter - layer_height * (1.0 - PI / 4.0)));
|
||||
}
|
||||
|
||||
THEN("Internal perimeter flow has spacing fixed to 1.125 * nozzle_diameter") {
|
||||
auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height);
|
||||
REQUIRE(flow.spacing() == Approx(1.125 *nozzle_diameter - layer_height * (1.0 - PI / 4.0)));
|
||||
}
|
||||
THEN("Spacing for supplied width is 0.8927f") {
|
||||
auto flow = Flow::new_from_config_width(frExternalPerimeter, width, nozzle_diameter, layer_height);
|
||||
REQUIRE(flow.spacing() == Approx(width.value - layer_height * (1.0 - PI / 4.0)));
|
||||
flow = Flow::new_from_config_width(frPerimeter, width, nozzle_diameter, layer_height);
|
||||
REQUIRE(flow.spacing() == Approx(width.value - layer_height * (1.0 - PI / 4.0)));
|
||||
}
|
||||
}
|
||||
/// Check the min/max
|
||||
GIVEN("Nozzle Diameter of 0.25") {
|
||||
float nozzle_diameter = 0.25f;
|
||||
float layer_height = 0.5f;
|
||||
WHEN("layer height is set to 0.2") {
|
||||
layer_height = 0.15f;
|
||||
THEN("Max width is set.") {
|
||||
auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height);
|
||||
REQUIRE(flow.width() == Approx(1.125 * nozzle_diameter));
|
||||
}
|
||||
}
|
||||
WHEN("Layer height is set to 0.25") {
|
||||
layer_height = 0.25f;
|
||||
THEN("Min width is set.") {
|
||||
auto flow = Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent(0, false), nozzle_diameter, layer_height);
|
||||
REQUIRE(flow.width() == Approx(1.125 * nozzle_diameter));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
/// Check for an edge case in the maths where the spacing could be 0; original
|
||||
/// math is 0.99. Slic3r issue #4654
|
||||
GIVEN("Input spacing of 0.414159 and a total width of 2") {
|
||||
double in_spacing = 0.414159;
|
||||
double total_width = 2.0;
|
||||
auto flow = Flow::new_from_spacing(1.0, 0.4, 0.3);
|
||||
WHEN("solid_spacing() is called") {
|
||||
double result = flow.solid_spacing(total_width, in_spacing);
|
||||
THEN("Yielded spacing is greater than 0") {
|
||||
REQUIRE(result > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
/// Spacing, width calculation for bridge extrusions
|
||||
SCENARIO("Flow: Flow math for bridges", "[Flow]") {
|
||||
GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") {
|
||||
float nozzle_diameter = 0.4f;
|
||||
float bridge_flow = 1.0f;
|
||||
WHEN("Flow role is frExternalPerimeter") {
|
||||
auto flow = Flow::bridging_flow(nozzle_diameter * sqrt(bridge_flow), nozzle_diameter);
|
||||
THEN("Bridge width is same as nozzle diameter") {
|
||||
REQUIRE(flow.width() == Approx(nozzle_diameter));
|
||||
}
|
||||
THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING") {
|
||||
REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
tests/fff_print/test_gcode.cpp
Normal file
22
tests/fff_print/test_gcode.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "libslic3r/GCode.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
SCENARIO("Origin manipulation", "[GCode]") {
|
||||
Slic3r::GCode gcodegen;
|
||||
WHEN("set_origin to (10,0)") {
|
||||
gcodegen.set_origin(Vec2d(10,0));
|
||||
REQUIRE(gcodegen.origin() == Vec2d(10, 0));
|
||||
}
|
||||
WHEN("set_origin to (10,0) and translate by (5, 5)") {
|
||||
gcodegen.set_origin(Vec2d(10,0));
|
||||
gcodegen.set_origin(gcodegen.origin() + Vec2d(5, 5));
|
||||
THEN("origin returns reference to point") {
|
||||
REQUIRE(gcodegen.origin() == Vec2d(15,5));
|
||||
}
|
||||
}
|
||||
}
|
||||
96
tests/fff_print/test_gcodewriter.cpp
Normal file
96
tests/fff_print/test_gcodewriter.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "libslic3r/GCodeWriter.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
SCENARIO("lift() is not ignored after unlift() at normal values of Z", "[GCodeWriter]") {
|
||||
GIVEN("A config from a file and a single extruder.") {
|
||||
GCodeWriter writer;
|
||||
GCodeConfig &config = writer.config;
|
||||
config.load(std::string(TEST_DATA_DIR) + "/fff_print_tests/test_gcodewriter/config_lift_unlift.ini", ForwardCompatibilitySubstitutionRule::Disable);
|
||||
|
||||
std::vector<unsigned int> extruder_ids {0};
|
||||
writer.set_extruders(extruder_ids);
|
||||
writer.set_extruder(0);
|
||||
|
||||
WHEN("Z is set to 203") {
|
||||
double trouble_Z = 203;
|
||||
writer.travel_to_z(trouble_Z);
|
||||
AND_WHEN("GcodeWriter::Lift() is called") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
|
||||
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
|
||||
AND_WHEN("GCodeWriter::Unlift() is called") {
|
||||
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
|
||||
THEN("GCodeWriter::Lift() emits gcode.") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WHEN("Z is set to 500003") {
|
||||
double trouble_Z = 500003;
|
||||
writer.travel_to_z(trouble_Z);
|
||||
AND_WHEN("GcodeWriter::Lift() is called") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
|
||||
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
|
||||
AND_WHEN("GCodeWriter::Unlift() is called") {
|
||||
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
|
||||
THEN("GCodeWriter::Lift() emits gcode.") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WHEN("Z is set to 10.3") {
|
||||
double trouble_Z = 10.3;
|
||||
writer.travel_to_z(trouble_Z);
|
||||
AND_WHEN("GcodeWriter::Lift() is called") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
|
||||
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
|
||||
AND_WHEN("GCodeWriter::Unlift() is called") {
|
||||
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
|
||||
THEN("GCodeWriter::Lift() emits gcode.") {
|
||||
REQUIRE(writer.lift().size() > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// The test above will fail for trouble_Z == 9007199254740992, where trouble_Z + 1.5 will be rounded to trouble_Z + 2.0 due to double mantisa overflow.
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("set_speed emits values with fixed-point output.", "[GCodeWriter]") {
|
||||
|
||||
GIVEN("GCodeWriter instance") {
|
||||
GCodeWriter writer;
|
||||
WHEN("set_speed is called to set speed to 99999.123") {
|
||||
THEN("Output string is G1 F99999.123") {
|
||||
REQUIRE_THAT(writer.set_speed(99999.123), Catch::Equals("G1 F99999.123\n"));
|
||||
}
|
||||
}
|
||||
WHEN("set_speed is called to set speed to 1") {
|
||||
THEN("Output string is G1 F1") {
|
||||
REQUIRE_THAT(writer.set_speed(1.0), Catch::Equals("G1 F1\n"));
|
||||
}
|
||||
}
|
||||
WHEN("set_speed is called to set speed to 203.200022") {
|
||||
THEN("Output string is G1 F203.2") {
|
||||
REQUIRE_THAT(writer.set_speed(203.200022), Catch::Equals("G1 F203.2\n"));
|
||||
}
|
||||
}
|
||||
WHEN("set_speed is called to set speed to 203.200522") {
|
||||
THEN("Output string is G1 F203.201") {
|
||||
REQUIRE_THAT(writer.set_speed(203.200522), Catch::Equals("G1 F203.201\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
tests/fff_print/test_model.cpp
Normal file
61
tests/fff_print/test_model.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/ModelArrange.hpp"
|
||||
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "test_data.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Test;
|
||||
|
||||
SCENARIO("Model construction", "[Model]") {
|
||||
GIVEN("A Slic3r Model") {
|
||||
Slic3r::Model model;
|
||||
Slic3r::TriangleMesh sample_mesh = Slic3r::make_cube(20,20,20);
|
||||
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
Slic3r::Print print;
|
||||
|
||||
WHEN("Model object is added") {
|
||||
Slic3r::ModelObject *model_object = model.add_object();
|
||||
THEN("Model object list == 1") {
|
||||
REQUIRE(model.objects.size() == 1);
|
||||
}
|
||||
model_object->add_volume(sample_mesh);
|
||||
THEN("Model volume list == 1") {
|
||||
REQUIRE(model_object->volumes.size() == 1);
|
||||
}
|
||||
THEN("Model volume is a part") {
|
||||
REQUIRE(model_object->volumes.front()->is_model_part());
|
||||
}
|
||||
THEN("Mesh is equivalent to input mesh.") {
|
||||
REQUIRE(! sample_mesh.its.vertices.empty());
|
||||
const std::vector<Vec3f>& mesh_vertices = model_object->volumes.front()->mesh().its.vertices;
|
||||
Vec3f mesh_offset = model_object->volumes.front()->source.mesh_offset.cast<float>();
|
||||
for (size_t i = 0; i < sample_mesh.its.vertices.size(); ++ i) {
|
||||
const Vec3f &p1 = sample_mesh.its.vertices[i];
|
||||
const Vec3f p2 = mesh_vertices[i] + mesh_offset;
|
||||
REQUIRE((p2 - p1).norm() < EPSILON);
|
||||
}
|
||||
}
|
||||
model_object->add_instance();
|
||||
arrange_objects(model, InfiniteBed{scaled(Vec2d(100, 100))}, ArrangeParams{scaled(min_object_distance(config))});
|
||||
model_object->ensure_on_bed();
|
||||
print.auto_assign_extruders(model_object);
|
||||
THEN("Print works?") {
|
||||
print.set_status_silent();
|
||||
print.apply(model, config);
|
||||
print.process();
|
||||
boost::filesystem::path temp = boost::filesystem::unique_path();
|
||||
print.export_gcode(temp.string(), nullptr, nullptr);
|
||||
REQUIRE(boost::filesystem::exists(temp));
|
||||
REQUIRE(boost::filesystem::is_regular_file(temp));
|
||||
REQUIRE(boost::filesystem::file_size(temp) > 0);
|
||||
boost::nowide::remove(temp.string().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
128
tests/fff_print/test_print.cpp
Normal file
128
tests/fff_print/test_print.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/Layer.hpp"
|
||||
|
||||
#include "test_data.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Test;
|
||||
|
||||
SCENARIO("PrintObject: Perimeter generation", "[PrintObject]") {
|
||||
GIVEN("20mm cube and default config") {
|
||||
WHEN("make_perimeters() is called") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, { { "fill_density", 0 } });
|
||||
const PrintObject &object = *print.objects().front();
|
||||
THEN("67 layers exist in the model") {
|
||||
REQUIRE(object.layers().size() == 66);
|
||||
}
|
||||
THEN("Every layer in region 0 has 1 island of perimeters") {
|
||||
for (const Layer *layer : object.layers())
|
||||
REQUIRE(layer->regions().front()->perimeters.entities.size() == 1);
|
||||
}
|
||||
THEN("Every layer in region 0 has 3 paths in its perimeters list.") {
|
||||
for (const Layer *layer : object.layers())
|
||||
REQUIRE(layer->regions().front()->perimeters.items_count() == 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("Print: Skirt generation", "[Print]") {
|
||||
GIVEN("20mm cube and default config") {
|
||||
WHEN("Skirts is set to 2 loops") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
|
||||
{ "skirt_height", 1 },
|
||||
{ "skirt_distance", 1 },
|
||||
{ "skirts", 2 }
|
||||
});
|
||||
THEN("Skirt Extrusion collection has 2 loops in it") {
|
||||
REQUIRE(print.skirt().items_count() == 2);
|
||||
REQUIRE(print.skirt().flatten().entities.size() == 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces to become internal.", "[Print]") {
|
||||
GIVEN("sliced 20mm cube and config with top_solid_surfaces = 2 and bottom_solid_surfaces = 1") {
|
||||
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "top_solid_layers", 2 },
|
||||
{ "bottom_solid_layers", 1 },
|
||||
{ "layer_height", 0.25 }, // get a known number of layers
|
||||
{ "first_layer_height", 0.25 }
|
||||
});
|
||||
Slic3r::Print print;
|
||||
Slic3r::Model model;
|
||||
Slic3r::Test::init_print({TestMesh::cube_20x20x20}, print, model, config);
|
||||
// Precondition: Ensure that the model has 2 solid top layers (39, 38)
|
||||
// and one solid bottom layer (0).
|
||||
auto test_is_solid_infill = [&print](size_t obj_id, size_t layer_id) {
|
||||
const Layer &layer = *(print.objects().at(obj_id)->get_layer((int)layer_id));
|
||||
// iterate over all of the regions in the layer
|
||||
for (const LayerRegion *region : layer.regions()) {
|
||||
// for each region, iterate over the fill surfaces
|
||||
for (const Surface &surface : region->fill_surfaces.surfaces)
|
||||
CHECK(surface.is_solid());
|
||||
}
|
||||
};
|
||||
print.process();
|
||||
test_is_solid_infill(0, 0); // should be solid
|
||||
test_is_solid_infill(0, 79); // should be solid
|
||||
test_is_solid_infill(0, 78); // should be solid
|
||||
WHEN("Model is re-sliced with top_solid_layers == 3") {
|
||||
config.set("top_solid_layers", 3);
|
||||
print.apply(model, config);
|
||||
print.process();
|
||||
THEN("Print object does not have 0 solid bottom layers.") {
|
||||
test_is_solid_infill(0, 0);
|
||||
}
|
||||
AND_THEN("Print object has 3 top solid layers") {
|
||||
test_is_solid_infill(0, 79);
|
||||
test_is_solid_infill(0, 78);
|
||||
test_is_solid_infill(0, 77);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("Print: Brim generation", "[Print]") {
|
||||
GIVEN("20mm cube and default config, 1mm first layer width") {
|
||||
WHEN("Brim is set to 3mm") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
|
||||
{ "first_layer_extrusion_width", 1 },
|
||||
{ "brim_width", 3 }
|
||||
});
|
||||
THEN("Brim Extrusion collection has 3 loops in it") {
|
||||
REQUIRE(print.brim().items_count() == 3);
|
||||
}
|
||||
}
|
||||
WHEN("Brim is set to 6mm") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
|
||||
{ "first_layer_extrusion_width", 1 },
|
||||
{ "brim_width", 6 }
|
||||
});
|
||||
THEN("Brim Extrusion collection has 6 loops in it") {
|
||||
REQUIRE(print.brim().items_count() == 6);
|
||||
}
|
||||
}
|
||||
WHEN("Brim is set to 6mm, extrusion width 0.5mm") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
|
||||
{ "first_layer_extrusion_width", 1 },
|
||||
{ "brim_width", 6 },
|
||||
{ "first_layer_extrusion_width", 0.5 }
|
||||
});
|
||||
print.process();
|
||||
THEN("Brim Extrusion collection has 12 loops in it") {
|
||||
REQUIRE(print.brim().items_count() == 14);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
271
tests/fff_print/test_printgcode.cpp
Normal file
271
tests/fff_print/test_printgcode.cpp
Normal file
@@ -0,0 +1,271 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/GCodeReader.hpp"
|
||||
|
||||
#include "test_data.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/regex.hpp>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Test;
|
||||
|
||||
boost::regex perimeters_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; perimeter");
|
||||
boost::regex infill_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; infill");
|
||||
boost::regex skirt_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; skirt");
|
||||
|
||||
SCENARIO( "PrintGCode basic functionality", "[PrintGCode]") {
|
||||
GIVEN("A default configuration and a print test object") {
|
||||
WHEN("the output is executed with no support material") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Model model;
|
||||
Slic3r::Test::init_print({TestMesh::cube_20x20x20}, print, model, {
|
||||
{ "layer_height", 0.2 },
|
||||
{ "first_layer_height", 0.2 },
|
||||
{ "first_layer_extrusion_width", 0 },
|
||||
{ "gcode_comments", true },
|
||||
{ "start_gcode", "" }
|
||||
});
|
||||
std::string gcode = Slic3r::Test::gcode(print);
|
||||
THEN("Some text output is generated.") {
|
||||
REQUIRE(gcode.size() > 0);
|
||||
}
|
||||
THEN("Exported text contains slic3r version") {
|
||||
REQUIRE(gcode.find(SLIC3R_VERSION) != std::string::npos);
|
||||
}
|
||||
//THEN("Exported text contains git commit id") {
|
||||
// REQUIRE(gcode.find("; Git Commit") != std::string::npos);
|
||||
// REQUIRE(gcode.find(SLIC3R_BUILD_ID) != std::string::npos);
|
||||
//}
|
||||
THEN("Exported text contains extrusion statistics.") {
|
||||
REQUIRE(gcode.find("; external perimeters extrusion width") != std::string::npos);
|
||||
REQUIRE(gcode.find("; perimeters extrusion width") != std::string::npos);
|
||||
REQUIRE(gcode.find("; infill extrusion width") != std::string::npos);
|
||||
REQUIRE(gcode.find("; solid infill extrusion width") != std::string::npos);
|
||||
REQUIRE(gcode.find("; top infill extrusion width") != std::string::npos);
|
||||
REQUIRE(gcode.find("; support material extrusion width") == std::string::npos);
|
||||
REQUIRE(gcode.find("; first layer extrusion width") == std::string::npos);
|
||||
}
|
||||
THEN("Exported text does not contain cooling markers (they were consumed)") {
|
||||
REQUIRE(gcode.find(";_EXTRUDE_SET_SPEED") == std::string::npos);
|
||||
}
|
||||
|
||||
THEN("GCode preamble is emitted.") {
|
||||
REQUIRE(gcode.find("G21 ; set units to millimeters") != std::string::npos);
|
||||
}
|
||||
|
||||
THEN("Config options emitted for print config, default region config, default object config") {
|
||||
REQUIRE(gcode.find("; first_layer_temperature") != std::string::npos);
|
||||
REQUIRE(gcode.find("; layer_height") != std::string::npos);
|
||||
REQUIRE(gcode.find("; fill_density") != std::string::npos);
|
||||
}
|
||||
THEN("Infill is emitted.") {
|
||||
boost::smatch has_match;
|
||||
REQUIRE(boost::regex_search(gcode, has_match, infill_regex));
|
||||
}
|
||||
THEN("Perimeters are emitted.") {
|
||||
boost::smatch has_match;
|
||||
REQUIRE(boost::regex_search(gcode, has_match, perimeters_regex));
|
||||
}
|
||||
THEN("Skirt is emitted.") {
|
||||
boost::smatch has_match;
|
||||
REQUIRE(boost::regex_search(gcode, has_match, skirt_regex));
|
||||
}
|
||||
THEN("final Z height is 20mm") {
|
||||
double final_z = 0.0;
|
||||
GCodeReader reader;
|
||||
reader.apply_config(print.config());
|
||||
reader.parse_buffer(gcode, [&final_z] (GCodeReader& self, const GCodeReader::GCodeLine& line) {
|
||||
final_z = std::max<double>(final_z, static_cast<double>(self.z())); // record the highest Z point we reach
|
||||
});
|
||||
REQUIRE(final_z == Approx(20.));
|
||||
}
|
||||
}
|
||||
WHEN("output is executed with complete objects and two differently-sized meshes") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Model model;
|
||||
Slic3r::Test::init_print({TestMesh::cube_20x20x20,TestMesh::cube_20x20x20}, print, model, {
|
||||
{ "first_layer_extrusion_width", 0 },
|
||||
{ "first_layer_height", 0.3 },
|
||||
{ "layer_height", 0.2 },
|
||||
{ "support_material", false },
|
||||
{ "raft_layers", 0 },
|
||||
{ "complete_objects", true },
|
||||
{ "gcode_comments", true },
|
||||
{ "between_objects_gcode", "; between-object-gcode" }
|
||||
});
|
||||
std::string gcode = Slic3r::Test::gcode(print);
|
||||
THEN("Some text output is generated.") {
|
||||
REQUIRE(gcode.size() > 0);
|
||||
}
|
||||
THEN("Infill is emitted.") {
|
||||
boost::smatch has_match;
|
||||
REQUIRE(boost::regex_search(gcode, has_match, infill_regex));
|
||||
}
|
||||
THEN("Perimeters are emitted.") {
|
||||
boost::smatch has_match;
|
||||
REQUIRE(boost::regex_search(gcode, has_match, perimeters_regex));
|
||||
}
|
||||
THEN("Skirt is emitted.") {
|
||||
boost::smatch has_match;
|
||||
REQUIRE(boost::regex_search(gcode, has_match, skirt_regex));
|
||||
}
|
||||
THEN("Between-object-gcode is emitted.") {
|
||||
REQUIRE(gcode.find("; between-object-gcode") != std::string::npos);
|
||||
}
|
||||
THEN("final Z height is 20.1mm") {
|
||||
double final_z = 0.0;
|
||||
GCodeReader reader;
|
||||
reader.apply_config(print.config());
|
||||
reader.parse_buffer(gcode, [&final_z] (GCodeReader& self, const GCodeReader::GCodeLine& line) {
|
||||
final_z = std::max(final_z, static_cast<double>(self.z())); // record the highest Z point we reach
|
||||
});
|
||||
REQUIRE(final_z == Approx(20.1));
|
||||
}
|
||||
THEN("Z height resets on object change") {
|
||||
double final_z = 0.0;
|
||||
bool reset = false;
|
||||
GCodeReader reader;
|
||||
reader.apply_config(print.config());
|
||||
reader.parse_buffer(gcode, [&final_z, &reset] (GCodeReader& self, const GCodeReader::GCodeLine& line) {
|
||||
if (final_z > 0 && std::abs(self.z() - 0.3) < 0.01 ) { // saw higher Z before this, now it's lower
|
||||
reset = true;
|
||||
} else {
|
||||
final_z = std::max(final_z, static_cast<double>(self.z())); // record the highest Z point we reach
|
||||
}
|
||||
});
|
||||
REQUIRE(reset == true);
|
||||
}
|
||||
THEN("Shorter object is printed before taller object.") {
|
||||
double final_z = 0.0;
|
||||
bool reset = false;
|
||||
GCodeReader reader;
|
||||
reader.apply_config(print.config());
|
||||
reader.parse_buffer(gcode, [&final_z, &reset] (GCodeReader& self, const GCodeReader::GCodeLine& line) {
|
||||
if (final_z > 0 && std::abs(self.z() - 0.3) < 0.01 ) {
|
||||
reset = (final_z > 20.0);
|
||||
} else {
|
||||
final_z = std::max(final_z, static_cast<double>(self.z())); // record the highest Z point we reach
|
||||
}
|
||||
});
|
||||
REQUIRE(reset == true);
|
||||
}
|
||||
}
|
||||
WHEN("the output is executed with support material") {
|
||||
std::string gcode = ::Test::slice({TestMesh::cube_20x20x20}, {
|
||||
{ "first_layer_extrusion_width", 0 },
|
||||
{ "support_material", true },
|
||||
{ "raft_layers", 3 },
|
||||
{ "gcode_comments", true }
|
||||
});
|
||||
THEN("Some text output is generated.") {
|
||||
REQUIRE(gcode.size() > 0);
|
||||
}
|
||||
THEN("Exported text contains extrusion statistics.") {
|
||||
REQUIRE(gcode.find("; external perimeters extrusion width") != std::string::npos);
|
||||
REQUIRE(gcode.find("; perimeters extrusion width") != std::string::npos);
|
||||
REQUIRE(gcode.find("; infill extrusion width") != std::string::npos);
|
||||
REQUIRE(gcode.find("; solid infill extrusion width") != std::string::npos);
|
||||
REQUIRE(gcode.find("; top infill extrusion width") != std::string::npos);
|
||||
REQUIRE(gcode.find("; support material extrusion width") != std::string::npos);
|
||||
REQUIRE(gcode.find("; first layer extrusion width") == std::string::npos);
|
||||
}
|
||||
THEN("Raft is emitted.") {
|
||||
REQUIRE(gcode.find("; raft") != std::string::npos);
|
||||
}
|
||||
}
|
||||
WHEN("the output is executed with a separate first layer extrusion width") {
|
||||
std::string gcode = ::Test::slice({ TestMesh::cube_20x20x20 }, {
|
||||
{ "first_layer_extrusion_width", "0.5" }
|
||||
});
|
||||
THEN("Some text output is generated.") {
|
||||
REQUIRE(gcode.size() > 0);
|
||||
}
|
||||
THEN("Exported text contains extrusion statistics.") {
|
||||
REQUIRE(gcode.find("; external perimeters extrusion width") != std::string::npos);
|
||||
REQUIRE(gcode.find("; perimeters extrusion width") != std::string::npos);
|
||||
REQUIRE(gcode.find("; infill extrusion width") != std::string::npos);
|
||||
REQUIRE(gcode.find("; solid infill extrusion width") != std::string::npos);
|
||||
REQUIRE(gcode.find("; top infill extrusion width") != std::string::npos);
|
||||
REQUIRE(gcode.find("; support material extrusion width") == std::string::npos);
|
||||
REQUIRE(gcode.find("; first layer extrusion width") != std::string::npos);
|
||||
}
|
||||
}
|
||||
WHEN("Cooling is enabled and the fan is disabled.") {
|
||||
std::string gcode = ::Test::slice({ TestMesh::cube_20x20x20 }, {
|
||||
{ "cooling", true },
|
||||
{ "disable_fan_first_layers", 5 }
|
||||
});
|
||||
THEN("GCode to disable fan is emitted."){
|
||||
REQUIRE(gcode.find("M107") != std::string::npos);
|
||||
}
|
||||
}
|
||||
WHEN("end_gcode exists with layer_num and layer_z") {
|
||||
std::string gcode = ::Test::slice({ TestMesh::cube_20x20x20 }, {
|
||||
{ "end_gcode", "; Layer_num [layer_num]\n; Layer_z [layer_z]" },
|
||||
{ "layer_height", 0.1 },
|
||||
{ "first_layer_height", 0.1 }
|
||||
});
|
||||
THEN("layer_num and layer_z are processed in the end gcode") {
|
||||
REQUIRE(gcode.find("; Layer_num 199") != std::string::npos);
|
||||
REQUIRE(gcode.find("; Layer_z 20") != std::string::npos);
|
||||
}
|
||||
}
|
||||
WHEN("current_extruder exists in start_gcode") {
|
||||
{
|
||||
std::string gcode = ::Test::slice({ TestMesh::cube_20x20x20 }, {
|
||||
{ "start_gcode", "; Extruder [current_extruder]" }
|
||||
});
|
||||
THEN("current_extruder is processed in the start gcode and set for first extruder") {
|
||||
REQUIRE(gcode.find("; Extruder 0") != std::string::npos);
|
||||
}
|
||||
}
|
||||
{
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_num_extruders(4);
|
||||
config.set_deserialize_strict({
|
||||
{ "start_gcode", "; Extruder [current_extruder]" },
|
||||
{ "infill_extruder", 2 },
|
||||
{ "solid_infill_extruder", 2 },
|
||||
{ "perimeter_extruder", 2 },
|
||||
{ "support_material_extruder", 2 },
|
||||
{ "support_material_interface_extruder", 2 }
|
||||
});
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
||||
THEN("current_extruder is processed in the start gcode and set for second extruder") {
|
||||
REQUIRE(gcode.find("; Extruder 1") != std::string::npos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("layer_num represents the layer's index from z=0") {
|
||||
std::string gcode = ::Test::slice({ TestMesh::cube_20x20x20, TestMesh::cube_20x20x20 }, {
|
||||
{ "complete_objects", true },
|
||||
{ "gcode_comments", true },
|
||||
{ "layer_gcode", ";Layer:[layer_num] ([layer_z] mm)" },
|
||||
{ "layer_height", 0.1 },
|
||||
{ "first_layer_height", 0.1 }
|
||||
});
|
||||
// End of the 1st object.
|
||||
std::string token = ";Layer:199 ";
|
||||
size_t pos = gcode.find(token);
|
||||
THEN("First and second object last layer is emitted") {
|
||||
// First object
|
||||
REQUIRE(pos != std::string::npos);
|
||||
pos += token.size();
|
||||
REQUIRE(pos < gcode.size());
|
||||
double z = 0;
|
||||
REQUIRE((sscanf(gcode.data() + pos, "(%lf mm)", &z) == 1));
|
||||
REQUIRE(z == Approx(20.));
|
||||
// Second object
|
||||
pos = gcode.find(";Layer:399 ", pos);
|
||||
REQUIRE(pos != std::string::npos);
|
||||
pos += token.size();
|
||||
REQUIRE(pos < gcode.size());
|
||||
REQUIRE((sscanf(gcode.data() + pos, "(%lf mm)", &z) == 1));
|
||||
REQUIRE(z == Approx(20.));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
tests/fff_print/test_printobject.cpp
Normal file
89
tests/fff_print/test_printobject.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/Layer.hpp"
|
||||
|
||||
#include "test_data.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Test;
|
||||
|
||||
SCENARIO("PrintObject: object layer heights", "[PrintObject]") {
|
||||
GIVEN("20mm cube and default initial config, initial layer height of 2mm") {
|
||||
WHEN("generate_object_layers() is called for 2mm layer heights and nozzle diameter of 3mm") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
|
||||
{ "first_layer_height", 2 },
|
||||
{ "layer_height", 2 },
|
||||
{ "nozzle_diameter", 3 }
|
||||
});
|
||||
ConstLayerPtrsAdaptor layers = print.objects().front()->layers();
|
||||
THEN("The output vector has 10 entries") {
|
||||
REQUIRE(layers.size() == 10);
|
||||
}
|
||||
AND_THEN("Each layer is approximately 2mm above the previous Z") {
|
||||
coordf_t last = 0.0;
|
||||
for (size_t i = 0; i < layers.size(); ++ i) {
|
||||
REQUIRE((layers[i]->print_z - last) == Approx(2.0));
|
||||
last = layers[i]->print_z;
|
||||
}
|
||||
}
|
||||
}
|
||||
WHEN("generate_object_layers() is called for 10mm layer heights and nozzle diameter of 11mm") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
|
||||
{ "first_layer_height", 2 },
|
||||
{ "layer_height", 10 },
|
||||
{ "nozzle_diameter", 11 }
|
||||
});
|
||||
ConstLayerPtrsAdaptor layers = print.objects().front()->layers();
|
||||
THEN("The output vector has 3 entries") {
|
||||
REQUIRE(layers.size() == 3);
|
||||
}
|
||||
AND_THEN("Layer 0 is at 2mm") {
|
||||
REQUIRE(layers.front()->print_z == Approx(2.0));
|
||||
}
|
||||
AND_THEN("Layer 1 is at 12mm") {
|
||||
REQUIRE(layers[1]->print_z == Approx(12.0));
|
||||
}
|
||||
}
|
||||
WHEN("generate_object_layers() is called for 15mm layer heights and nozzle diameter of 16mm") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
|
||||
{ "first_layer_height", 2 },
|
||||
{ "layer_height", 15 },
|
||||
{ "nozzle_diameter", 16 }
|
||||
});
|
||||
ConstLayerPtrsAdaptor layers = print.objects().front()->layers();
|
||||
THEN("The output vector has 2 entries") {
|
||||
REQUIRE(layers.size() == 2);
|
||||
}
|
||||
AND_THEN("Layer 0 is at 2mm") {
|
||||
REQUIRE(layers[0]->print_z == Approx(2.0));
|
||||
}
|
||||
AND_THEN("Layer 1 is at 17mm") {
|
||||
REQUIRE(layers[1]->print_z == Approx(17.0));
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
WHEN("generate_object_layers() is called for 15mm layer heights and nozzle diameter of 5mm") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
|
||||
{ "first_layer_height", 2 },
|
||||
{ "layer_height", 15 },
|
||||
{ "nozzle_diameter", 5 }
|
||||
});
|
||||
const std::vector<Slic3r::Layer*> &layers = print.objects().front()->layers();
|
||||
THEN("The layer height is limited to 5mm.") {
|
||||
CHECK(layers.size() == 5);
|
||||
coordf_t last = 2.0;
|
||||
for (size_t i = 1; i < layers.size(); i++) {
|
||||
REQUIRE((layers[i]->print_z - last) == Approx(5.0));
|
||||
last = layers[i]->print_z;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
267
tests/fff_print/test_skirt_brim.cpp
Normal file
267
tests/fff_print/test_skirt_brim.cpp
Normal file
@@ -0,0 +1,267 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "libslic3r/GCodeReader.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "test_data.hpp" // get access to init_print, etc
|
||||
|
||||
using namespace Slic3r::Test;
|
||||
using namespace Slic3r;
|
||||
|
||||
/// Helper method to find the tool used for the brim (always the first extrusion)
|
||||
static int get_brim_tool(const std::string &gcode)
|
||||
{
|
||||
int brim_tool = -1;
|
||||
int tool = -1;
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(gcode, [&tool, &brim_tool] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
|
||||
{
|
||||
// if the command is a T command, set the the current tool
|
||||
if (boost::starts_with(line.cmd(), "T")) {
|
||||
tool = atoi(line.cmd().data() + 1);
|
||||
} else if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0 && brim_tool < 0) {
|
||||
brim_tool = tool;
|
||||
}
|
||||
});
|
||||
return brim_tool;
|
||||
}
|
||||
|
||||
TEST_CASE("Skirt height is honored", "[Skirt]") {
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "skirts", 1 },
|
||||
{ "skirt_height", 5 },
|
||||
{ "perimeters", 0 },
|
||||
{ "support_material_speed", 99 },
|
||||
// avoid altering speeds unexpectedly
|
||||
{ "cooling", false },
|
||||
{ "first_layer_speed", "100%" }
|
||||
});
|
||||
|
||||
std::string gcode;
|
||||
SECTION("printing a single object") {
|
||||
gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
||||
}
|
||||
SECTION("printing multiple objects") {
|
||||
gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20, TestMesh::cube_20x20x20}, config);
|
||||
}
|
||||
|
||||
std::map<double, bool> layers_with_skirt;
|
||||
double support_speed = config.opt<Slic3r::ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(gcode, [&layers_with_skirt, &support_speed] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
if (line.extruding(self) && self.f() == Approx(support_speed)) {
|
||||
layers_with_skirt[self.z()] = 1;
|
||||
}
|
||||
});
|
||||
REQUIRE(layers_with_skirt.size() == (size_t)config.opt_int("skirt_height"));
|
||||
}
|
||||
|
||||
SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
|
||||
GIVEN("A default configuration") {
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
config.set_num_extruders(4);
|
||||
config.set_deserialize_strict({
|
||||
{ "support_material_speed", 99 },
|
||||
{ "first_layer_height", 0.3 },
|
||||
{ "gcode_comments", true },
|
||||
// avoid altering speeds unexpectedly
|
||||
{ "cooling", false },
|
||||
{ "first_layer_speed", "100%" },
|
||||
// remove noise from top/solid layers
|
||||
{ "top_solid_layers", 0 },
|
||||
{ "bottom_solid_layers", 1 },
|
||||
{ "start_gcode", "T[initial_tool]\n" }
|
||||
});
|
||||
|
||||
WHEN("Brim width is set to 5") {
|
||||
config.set_deserialize_strict({
|
||||
{ "perimeters", 0 },
|
||||
{ "skirts", 0 },
|
||||
{ "brim_width", 5 }
|
||||
});
|
||||
THEN("Brim is generated") {
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
||||
bool brim_generated = false;
|
||||
double support_speed = config.opt<Slic3r::ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
|
||||
Slic3r::GCodeReader parser;
|
||||
parser.parse_buffer(gcode, [&brim_generated, support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line) {
|
||||
if (self.z() == Approx(0.3) || line.new_Z(self) == Approx(0.3)) {
|
||||
if (line.extruding(self) && self.f() == Approx(support_speed)) {
|
||||
brim_generated = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
REQUIRE(brim_generated);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("Skirt area is smaller than the brim") {
|
||||
config.set_deserialize_strict({
|
||||
{ "skirts", 1 },
|
||||
{ "brim_width", 10}
|
||||
});
|
||||
THEN("Gcode generates") {
|
||||
REQUIRE(! Slic3r::Test::slice({TestMesh::cube_20x20x20}, config).empty());
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("Skirt height is 0 and skirts > 0") {
|
||||
config.set_deserialize_strict({
|
||||
{ "skirts", 2 },
|
||||
{ "skirt_height", 0 }
|
||||
});
|
||||
THEN("Gcode generates") {
|
||||
REQUIRE(! Slic3r::Test::slice({TestMesh::cube_20x20x20}, config).empty());
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
// This is a real error! One shall print the brim with the external perimeter extruder!
|
||||
WHEN("Perimeter extruder = 2 and support extruders = 3") {
|
||||
THEN("Brim is printed with the extruder used for the perimeters of first object") {
|
||||
config.set_deserialize_strict({
|
||||
{ "skirts", 0 },
|
||||
{ "brim_width", 5 },
|
||||
{ "perimeter_extruder", 2 },
|
||||
{ "support_material_extruder", 3 },
|
||||
{ "infill_extruder", 4 }
|
||||
});
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
||||
int tool = get_brim_tool(gcode);
|
||||
REQUIRE(tool == config.opt_int("perimeter_extruder") - 1);
|
||||
}
|
||||
}
|
||||
WHEN("Perimeter extruder = 2, support extruders = 3, raft is enabled") {
|
||||
THEN("brim is printed with same extruder as skirt") {
|
||||
config.set_deserialize_strict({
|
||||
{ "skirts", 0 },
|
||||
{ "brim_width", 5 },
|
||||
{ "perimeter_extruder", 2 },
|
||||
{ "support_material_extruder", 3 },
|
||||
{ "infill_extruder", 4 },
|
||||
{ "raft_layers", 1 }
|
||||
});
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
||||
int tool = get_brim_tool(gcode);
|
||||
REQUIRE(tool == config.opt_int("support_material_extruder") - 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
WHEN("brim width to 1 with layer_width of 0.5") {
|
||||
config.set_deserialize_strict({
|
||||
{ "skirts", 0 },
|
||||
{ "first_layer_extrusion_width", 0.5 },
|
||||
{ "brim_width", 1 }
|
||||
});
|
||||
THEN("2 brim lines") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, config);
|
||||
REQUIRE(print.brim().entities.size() == 2);
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
WHEN("brim ears on a square") {
|
||||
config.set_deserialize_strict({
|
||||
{ "skirts", 0 },
|
||||
{ "first_layer_extrusion_width", 0.5 },
|
||||
{ "brim_width", 1 },
|
||||
{ "brim_ears", 1 },
|
||||
{ "brim_ears_max_angle", 91 }
|
||||
});
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, config);
|
||||
THEN("Four brim ears") {
|
||||
REQUIRE(print.brim().entities.size() == 4);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("brim ears on a square but with a too small max angle") {
|
||||
config.set_deserialize_strict({
|
||||
{ "skirts", 0 },
|
||||
{ "first_layer_extrusion_width", 0.5 },
|
||||
{ "brim_width", 1 },
|
||||
{ "brim_ears", 1 },
|
||||
{ "brim_ears_max_angle", 89 }
|
||||
});
|
||||
THEN("no brim") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({ TestMesh::cube_20x20x20 }, print, config);
|
||||
REQUIRE(print.brim().entities.size() == 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
WHEN("Object is plated with overhang support and a brim") {
|
||||
config.set_deserialize_strict({
|
||||
{ "layer_height", 0.4 },
|
||||
{ "first_layer_height", 0.4 },
|
||||
{ "skirts", 1 },
|
||||
{ "skirt_distance", 0 },
|
||||
{ "support_material_speed", 99 },
|
||||
{ "perimeter_extruder", 1 },
|
||||
{ "support_material_extruder", 2 },
|
||||
{ "infill_extruder", 3 }, // ensure that a tool command gets emitted.
|
||||
{ "cooling", false }, // to prevent speeds to be altered
|
||||
{ "first_layer_speed", "100%" }, // to prevent speeds to be altered
|
||||
{ "start_gcode", "T[initial_tool]\n" }
|
||||
});
|
||||
|
||||
THEN("overhang generates?") {
|
||||
//FIXME does it make sense?
|
||||
REQUIRE(! Slic3r::Test::slice({TestMesh::overhang}, config).empty());
|
||||
}
|
||||
|
||||
// config.set("support_material", true); // to prevent speeds to be altered
|
||||
|
||||
#if 0
|
||||
// This test is not finished.
|
||||
THEN("skirt length is large enough to contain object with support") {
|
||||
CHECK(config.opt_bool("support_material")); // test is not valid if support material is off
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
||||
double support_speed = config.opt<ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
|
||||
double skirt_length = 0.0;
|
||||
Points extrusion_points;
|
||||
int tool = -1;
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(gcode, [config, &extrusion_points, &tool, &skirt_length, support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line) {
|
||||
// std::cerr << line.cmd() << "\n";
|
||||
if (boost::starts_with(line.cmd(), "T")) {
|
||||
tool = atoi(line.cmd().data() + 1);
|
||||
} else if (self.z() == Approx(config.opt<ConfigOptionFloat>("first_layer_height")->value)) {
|
||||
// on first layer
|
||||
if (line.extruding(self) && line.dist_XY(self) > 0) {
|
||||
float speed = ( self.f() > 0 ? self.f() : line.new_F(self));
|
||||
// std::cerr << "Tool " << tool << "\n";
|
||||
if (speed == Approx(support_speed) && tool == config.opt_int("perimeter_extruder") - 1) {
|
||||
// Skirt uses first material extruder, support material speed.
|
||||
skirt_length += line.dist_XY(self);
|
||||
} else
|
||||
extrusion_points.push_back(Slic3r::Point::new_scale(line.new_X(self), line.new_Y(self)));
|
||||
}
|
||||
}
|
||||
if (self.z() == Approx(0.3) || line.new_Z(self) == Approx(0.3)) {
|
||||
if (line.extruding(self) && self.f() == Approx(support_speed)) {
|
||||
}
|
||||
}
|
||||
});
|
||||
Slic3r::Polygon convex_hull = Slic3r::Geometry::convex_hull(extrusion_points);
|
||||
double hull_perimeter = unscale<double>(convex_hull.split_at_first_point().length());
|
||||
REQUIRE(skirt_length > hull_perimeter);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
WHEN("Large minimum skirt length is used.") {
|
||||
config.set("min_skirt_length", 20);
|
||||
THEN("Gcode generation doesn't crash") {
|
||||
REQUIRE(! Slic3r::Test::slice({TestMesh::cube_20x20x20}, config).empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
238
tests/fff_print/test_support_material.cpp
Normal file
238
tests/fff_print/test_support_material.cpp
Normal file
@@ -0,0 +1,238 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "libslic3r/GCodeReader.hpp"
|
||||
#include "libslic3r/Layer.hpp"
|
||||
|
||||
#include "test_data.hpp" // get access to init_print, etc
|
||||
|
||||
using namespace Slic3r::Test;
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("SupportMaterial: Three raft layers created", "[SupportMaterial]")
|
||||
{
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({ TestMesh::cube_20x20x20 }, print, {
|
||||
{ "support_material", 1 },
|
||||
{ "raft_layers", 3 }
|
||||
});
|
||||
REQUIRE(print.objects().front()->support_layers().size() == 3);
|
||||
}
|
||||
|
||||
SCENARIO("SupportMaterial: support_layers_z and contact_distance", "[SupportMaterial]")
|
||||
{
|
||||
// Box h = 20mm, hole bottom at 5mm, hole height 10mm (top edge at 15mm).
|
||||
TriangleMesh mesh = Slic3r::Test::mesh(Slic3r::Test::TestMesh::cube_with_hole);
|
||||
mesh.rotate_x(float(M_PI / 2));
|
||||
// mesh.write_binary("d:\\temp\\cube_with_hole.stl");
|
||||
|
||||
auto check = [](Slic3r::Print &print, bool &first_support_layer_height_ok, bool &layer_height_minimum_ok, bool &layer_height_maximum_ok, bool &top_spacing_ok)
|
||||
{
|
||||
ConstSupportLayerPtrsAdaptor support_layers = print.objects().front()->support_layers();
|
||||
|
||||
first_support_layer_height_ok = support_layers.front()->print_z == print.config().first_layer_height.value;
|
||||
|
||||
layer_height_minimum_ok = true;
|
||||
layer_height_maximum_ok = true;
|
||||
double min_layer_height = print.config().min_layer_height.values.front();
|
||||
double max_layer_height = print.config().nozzle_diameter.values.front();
|
||||
if (print.config().max_layer_height.values.front() > EPSILON)
|
||||
max_layer_height = std::min(max_layer_height, print.config().max_layer_height.values.front());
|
||||
for (size_t i = 1; i < support_layers.size(); ++ i) {
|
||||
if (support_layers[i]->print_z - support_layers[i - 1]->print_z < min_layer_height - EPSILON)
|
||||
layer_height_minimum_ok = false;
|
||||
if (support_layers[i]->print_z - support_layers[i - 1]->print_z > max_layer_height + EPSILON)
|
||||
layer_height_maximum_ok = false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
double expected_top_spacing = print.default_object_config().layer_height + print.config().nozzle_diameter.get_at(0);
|
||||
bool wrong_top_spacing = 0;
|
||||
std::vector<coordf_t> top_z { 1.1 };
|
||||
for (coordf_t top_z_el : top_z) {
|
||||
// find layer index of this top surface.
|
||||
size_t layer_id = -1;
|
||||
for (size_t i = 0; i < support_z.size(); ++ i) {
|
||||
if (abs(support_z[i] - top_z_el) < EPSILON) {
|
||||
layer_id = i;
|
||||
i = static_cast<int>(support_z.size());
|
||||
}
|
||||
}
|
||||
|
||||
// check that first support layer above this top surface (or the next one) is spaced with nozzle diameter
|
||||
if (abs(support_z[layer_id + 1] - support_z[layer_id] - expected_top_spacing) > EPSILON &&
|
||||
abs(support_z[layer_id + 2] - support_z[layer_id] - expected_top_spacing) > EPSILON) {
|
||||
wrong_top_spacing = 1;
|
||||
}
|
||||
}
|
||||
d = ! wrong_top_spacing;
|
||||
#else
|
||||
top_spacing_ok = true;
|
||||
#endif
|
||||
};
|
||||
|
||||
GIVEN("A print object having one modelObject") {
|
||||
WHEN("First layer height = 0.4") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({ mesh }, print, {
|
||||
{ "support_material", 1 },
|
||||
{ "layer_height", 0.2 },
|
||||
{ "first_layer_height", 0.4 },
|
||||
{ "dont_support_bridges", false },
|
||||
});
|
||||
bool a, b, c, d;
|
||||
check(print, a, b, c, d);
|
||||
THEN("First layer height is honored") { REQUIRE(a == true); }
|
||||
THEN("No null or negative support layers") { REQUIRE(b == true); }
|
||||
THEN("No layers thicker than nozzle diameter") { REQUIRE(c == true); }
|
||||
// THEN("Layers above top surfaces are spaced correctly") { REQUIRE(d == true); }
|
||||
}
|
||||
WHEN("Layer height = 0.2 and, first layer height = 0.3") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({ mesh }, print, {
|
||||
{ "support_material", 1 },
|
||||
{ "layer_height", 0.2 },
|
||||
{ "first_layer_height", 0.3 },
|
||||
{ "dont_support_bridges", false },
|
||||
});
|
||||
bool a, b, c, d;
|
||||
check(print, a, b, c, d);
|
||||
THEN("First layer height is honored") { REQUIRE(a == true); }
|
||||
THEN("No null or negative support layers") { REQUIRE(b == true); }
|
||||
THEN("No layers thicker than nozzle diameter") { REQUIRE(c == true); }
|
||||
// THEN("Layers above top surfaces are spaced correctly") { REQUIRE(d == true); }
|
||||
}
|
||||
WHEN("Layer height = nozzle_diameter[0]") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({ mesh }, print, {
|
||||
{ "support_material", 1 },
|
||||
{ "layer_height", 0.2 },
|
||||
{ "first_layer_height", 0.3 },
|
||||
{ "dont_support_bridges", false },
|
||||
});
|
||||
bool a, b, c, d;
|
||||
check(print, a, b, c, d);
|
||||
THEN("First layer height is honored") { REQUIRE(a == true); }
|
||||
THEN("No null or negative support layers") { REQUIRE(b == true); }
|
||||
THEN("No layers thicker than nozzle diameter") { REQUIRE(c == true); }
|
||||
// THEN("Layers above top surfaces are spaced correctly") { REQUIRE(d == true); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Test 8.
|
||||
TEST_CASE("SupportMaterial: forced support is generated", "[SupportMaterial]")
|
||||
{
|
||||
// Create a mesh & modelObject.
|
||||
TriangleMesh mesh = TriangleMesh::make_cube(20, 20, 20);
|
||||
|
||||
Model model = Model();
|
||||
ModelObject *object = model.add_object();
|
||||
object->add_volume(mesh);
|
||||
model.add_default_instances();
|
||||
model.align_instances_to_origin();
|
||||
|
||||
Print print = Print();
|
||||
|
||||
std::vector<coordf_t> contact_z = {1.9};
|
||||
std::vector<coordf_t> top_z = {1.1};
|
||||
print.default_object_config.support_material_enforce_layers = 100;
|
||||
print.default_object_config.support_material = 0;
|
||||
print.default_object_config.layer_height = 0.2;
|
||||
print.default_object_config.set_deserialize("first_layer_height", "0.3");
|
||||
|
||||
print.add_model_object(model.objects[0]);
|
||||
print.objects.front()->_slice();
|
||||
|
||||
SupportMaterial *support = print.objects.front()->_support_material();
|
||||
auto support_z = support->support_layers_z(contact_z, top_z, print.default_object_config.layer_height);
|
||||
|
||||
bool check = true;
|
||||
for (size_t i = 1; i < support_z.size(); i++) {
|
||||
if (support_z[i] - support_z[i - 1] <= 0)
|
||||
check = false;
|
||||
}
|
||||
|
||||
REQUIRE(check == true);
|
||||
}
|
||||
|
||||
// TODO
|
||||
bool test_6_checks(Print& print)
|
||||
{
|
||||
bool has_bridge_speed = true;
|
||||
|
||||
// Pre-Processing.
|
||||
PrintObject* print_object = print.objects.front();
|
||||
print_object->infill();
|
||||
SupportMaterial* support_material = print.objects.front()->_support_material();
|
||||
support_material->generate(print_object);
|
||||
// TODO but not needed in test 6 (make brims and make skirts).
|
||||
|
||||
// Exporting gcode.
|
||||
// TODO validation found in Simple.pm
|
||||
|
||||
|
||||
return has_bridge_speed;
|
||||
}
|
||||
|
||||
// Test 6.
|
||||
SCENARIO("SupportMaterial: Checking bridge speed", "[SupportMaterial]")
|
||||
{
|
||||
GIVEN("Print object") {
|
||||
// Create a mesh & modelObject.
|
||||
TriangleMesh mesh = TriangleMesh::make_cube(20, 20, 20);
|
||||
|
||||
Model model = Model();
|
||||
ModelObject *object = model.add_object();
|
||||
object->add_volume(mesh);
|
||||
model.add_default_instances();
|
||||
model.align_instances_to_origin();
|
||||
|
||||
Print print = Print();
|
||||
print.config.brim_width = 0;
|
||||
print.config.skirts = 0;
|
||||
print.config.skirts = 0;
|
||||
print.default_object_config.support_material = 1;
|
||||
print.default_region_config.top_solid_layers = 0; // so that we don't have the internal bridge over infill.
|
||||
print.default_region_config.bridge_speed = 99;
|
||||
print.config.cooling = 0;
|
||||
print.config.set_deserialize("first_layer_speed", "100%");
|
||||
|
||||
WHEN("support_material_contact_distance = 0.2") {
|
||||
print.default_object_config.support_material_contact_distance = 0.2;
|
||||
print.add_model_object(model.objects[0]);
|
||||
|
||||
bool check = test_6_checks(print);
|
||||
REQUIRE(check == true); // bridge speed is used.
|
||||
}
|
||||
|
||||
WHEN("support_material_contact_distance = 0") {
|
||||
print.default_object_config.support_material_contact_distance = 0;
|
||||
print.add_model_object(model.objects[0]);
|
||||
|
||||
bool check = test_6_checks(print);
|
||||
REQUIRE(check == true); // bridge speed is not used.
|
||||
}
|
||||
|
||||
WHEN("support_material_contact_distance = 0.2 & raft_layers = 5") {
|
||||
print.default_object_config.support_material_contact_distance = 0.2;
|
||||
print.default_object_config.raft_layers = 5;
|
||||
print.add_model_object(model.objects[0]);
|
||||
|
||||
bool check = test_6_checks(print);
|
||||
REQUIRE(check == true); // bridge speed is used.
|
||||
}
|
||||
|
||||
WHEN("support_material_contact_distance = 0 & raft_layers = 5") {
|
||||
print.default_object_config.support_material_contact_distance = 0;
|
||||
print.default_object_config.raft_layers = 5;
|
||||
print.add_model_object(model.objects[0]);
|
||||
|
||||
bool check = test_6_checks(print);
|
||||
|
||||
REQUIRE(check == true); // bridge speed is not used.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
353
tests/fff_print/test_trianglemesh.cpp
Normal file
353
tests/fff_print/test_trianglemesh.cpp
Normal file
@@ -0,0 +1,353 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/TriangleMeshSlicer.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <future>
|
||||
#include <chrono>
|
||||
|
||||
//#include "test_options.hpp"
|
||||
#include "test_data.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace std;
|
||||
|
||||
static inline TriangleMesh make_cube() { return make_cube(20., 20, 20); }
|
||||
|
||||
SCENARIO( "TriangleMesh: Basic mesh statistics") {
|
||||
GIVEN( "A 20mm cube, built from constexpr std::array" ) {
|
||||
std::vector<Vec3f> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
|
||||
std::vector<Vec3i> facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} };
|
||||
TriangleMesh cube(vertices, facets);
|
||||
|
||||
THEN( "Volume is appropriate for 20mm square cube.") {
|
||||
REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2);
|
||||
}
|
||||
|
||||
THEN( "Vertices array matches input.") {
|
||||
for (size_t i = 0U; i < cube.its.vertices.size(); i++) {
|
||||
REQUIRE(cube.its.vertices.at(i) == vertices.at(i).cast<float>());
|
||||
}
|
||||
for (size_t i = 0U; i < vertices.size(); i++) {
|
||||
REQUIRE(vertices.at(i).cast<float>() == cube.its.vertices.at(i));
|
||||
}
|
||||
}
|
||||
THEN( "Vertex count matches vertex array size.") {
|
||||
REQUIRE(cube.facets_count() == facets.size());
|
||||
}
|
||||
|
||||
THEN( "Facet array matches input.") {
|
||||
for (size_t i = 0U; i < cube.its.indices.size(); i++) {
|
||||
REQUIRE(cube.its.indices.at(i) == facets.at(i));
|
||||
}
|
||||
|
||||
for (size_t i = 0U; i < facets.size(); i++) {
|
||||
REQUIRE(facets.at(i) == cube.its.indices.at(i));
|
||||
}
|
||||
}
|
||||
THEN( "Facet count matches facet array size.") {
|
||||
REQUIRE(cube.facets_count() == facets.size());
|
||||
}
|
||||
|
||||
#if 0
|
||||
THEN( "Number of normals is equal to the number of facets.") {
|
||||
REQUIRE(cube.normals().size() == facets.size());
|
||||
}
|
||||
#endif
|
||||
|
||||
THEN( "center() returns the center of the object.") {
|
||||
REQUIRE(cube.center() == Vec3d(10.0,10.0,10.0));
|
||||
}
|
||||
|
||||
THEN( "Size of cube is (20,20,20)") {
|
||||
REQUIRE(cube.size() == Vec3d(20,20,20));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO( "TriangleMesh: Transformation functions affect mesh as expected.") {
|
||||
GIVEN( "A 20mm cube with one corner on the origin") {
|
||||
auto cube = make_cube();
|
||||
|
||||
WHEN( "The cube is scaled 200% uniformly") {
|
||||
cube.scale(2.0);
|
||||
THEN( "The volume is equivalent to 40x40x40 (all dimensions increased by 200%") {
|
||||
REQUIRE(abs(cube.volume() - 40.0*40.0*40.0) < 1e-2);
|
||||
}
|
||||
}
|
||||
WHEN( "The resulting cube is scaled 200% in the X direction") {
|
||||
cube.scale(Vec3f(2.0, 1, 1));
|
||||
THEN( "The volume is doubled.") {
|
||||
REQUIRE(abs(cube.volume() - 2*20.0*20.0*20.0) < 1e-2);
|
||||
}
|
||||
THEN( "The X coordinate size is 200%.") {
|
||||
REQUIRE(cube.its.vertices.at(0).x() == 40.0);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN( "The cube is scaled 25% in the X direction") {
|
||||
cube.scale(Vec3f(0.25, 1, 1));
|
||||
THEN( "The volume is 25% of the previous volume.") {
|
||||
REQUIRE(abs(cube.volume() - 0.25*20.0*20.0*20.0) < 1e-2);
|
||||
}
|
||||
THEN( "The X coordinate size is 25% from previous.") {
|
||||
REQUIRE(cube.its.vertices.at(0).x() == 5.0);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN( "The cube is rotated 45 degrees.") {
|
||||
cube.rotate_z(float(M_PI / 4.));
|
||||
THEN( "The X component of the size is sqrt(2)*20") {
|
||||
REQUIRE(abs(cube.size().x() - sqrt(2.0)*20) < 1e-2);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN( "The cube is translated (5, 10, 0) units with a Vec3f ") {
|
||||
cube.translate(Vec3f(5.0, 10.0, 0.0));
|
||||
THEN( "The first vertex is located at 25, 30, 0") {
|
||||
REQUIRE(cube.its.vertices.at(0) == Vec3f(25.0, 30.0, 0.0));
|
||||
}
|
||||
}
|
||||
|
||||
WHEN( "The cube is translated (5, 10, 0) units with 3 doubles") {
|
||||
cube.translate(5.0, 10.0, 0.0);
|
||||
THEN( "The first vertex is located at 25, 30, 0") {
|
||||
REQUIRE(cube.its.vertices.at(0) == Vec3f(25.0, 30.0, 0.0));
|
||||
}
|
||||
}
|
||||
WHEN( "The cube is translated (5, 10, 0) units and then aligned to origin") {
|
||||
cube.translate(5.0, 10.0, 0.0);
|
||||
cube.align_to_origin();
|
||||
THEN( "The third vertex is located at 0,0,0") {
|
||||
REQUIRE(cube.its.vertices.at(2) == Vec3f::Zero());
|
||||
}
|
||||
THEN( "Size is OK") {
|
||||
REQUIRE(cube.stats().size == Vec3f(20.f, 20.f, 20.f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO( "TriangleMesh: slice behavior.") {
|
||||
GIVEN( "A 20mm cube with one corner on the origin") {
|
||||
auto cube = make_cube();
|
||||
|
||||
WHEN("Cube is sliced with z = [0+EPSILON,2,4,8,6,8,10,12,14,16,18,20]") {
|
||||
std::vector<double> z { 0+EPSILON,2,4,8,6,8,10,12,14,16,18,20 };
|
||||
std::vector<ExPolygons> result = cube.slice(z);
|
||||
THEN( "The correct number of polygons are returned per layer.") {
|
||||
for (size_t i = 0U; i < z.size(); i++) {
|
||||
REQUIRE(result.at(i).size() == 1);
|
||||
}
|
||||
}
|
||||
THEN( "The area of the returned polygons is correct.") {
|
||||
for (size_t i = 0U; i < z.size(); i++) {
|
||||
REQUIRE(result.at(i).at(0).area() == 20.0*20/(std::pow(SCALING_FACTOR,2)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
GIVEN( "A STL with an irregular shape.") {
|
||||
const std::vector<Vec3f> vertices {{0,0,0},{0,0,20},{0,5,0},{0,5,20},{50,0,0},{50,0,20},{15,5,0},{35,5,0},{15,20,0},{50,5,0},{35,20,0},{15,5,10},{50,5,20},{35,5,10},{35,20,10},{15,20,10}};
|
||||
const std::vector<Vec3i> facets {{0,1,2},{2,1,3},{1,0,4},{5,1,4},{0,2,4},{4,2,6},{7,6,8},{4,6,7},{9,4,7},{7,8,10},{2,3,6},{11,3,12},{7,12,9},{13,12,7},{6,3,11},{11,12,13},{3,1,5},{12,3,5},{5,4,9},{12,5,9},{13,7,10},{14,13,10},{8,15,10},{10,15,14},{6,11,8},{8,11,15},{15,11,13},{14,15,13}};
|
||||
|
||||
auto cube = make_cube();
|
||||
WHEN(" a top tangent plane is sliced") {
|
||||
// At Z = 10 we have a top horizontal surface.
|
||||
std::vector<ExPolygons> slices = cube.slice({5.0, 10.0});
|
||||
THEN( "its area is included") {
|
||||
REQUIRE(slices.at(0).at(0).area() > 0);
|
||||
REQUIRE(slices.at(1).at(0).area() > 0);
|
||||
}
|
||||
}
|
||||
WHEN(" a model that has been transformed is sliced") {
|
||||
cube.mirror_z();
|
||||
std::vector<ExPolygons> slices = cube.slice({-5.0, -10.0});
|
||||
THEN( "it is sliced properly (mirrored bottom plane area is included)") {
|
||||
REQUIRE(slices.at(0).at(0).area() > 0);
|
||||
REQUIRE(slices.at(1).at(0).area() > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO( "make_xxx functions produce meshes.") {
|
||||
GIVEN("make_cube() function") {
|
||||
WHEN("make_cube() is called with arguments 20,20,20") {
|
||||
TriangleMesh cube = make_cube(20,20,20);
|
||||
THEN("The resulting mesh has one and only one vertex at 0,0,0") {
|
||||
const std::vector<Vec3f> &verts = cube.its.vertices;
|
||||
REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return t.x() == 0 && t.y() == 0 && t.z() == 0; } ) == 1);
|
||||
}
|
||||
THEN("The mesh volume is 20*20*20") {
|
||||
REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2);
|
||||
}
|
||||
THEN("There are 12 facets.") {
|
||||
REQUIRE(cube.its.indices.size() == 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
GIVEN("make_cylinder() function") {
|
||||
WHEN("make_cylinder() is called with arguments 10,10, PI / 3") {
|
||||
TriangleMesh cyl = make_cylinder(10, 10, PI / 243.0);
|
||||
double angle = (2*PI / floor(2*PI / (PI / 243.0)));
|
||||
THEN("The resulting mesh has one and only one vertex at 0,0,0") {
|
||||
const std::vector<Vec3f> &verts = cyl.its.vertices;
|
||||
REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return t.x() == 0 && t.y() == 0 && t.z() == 0; } ) == 1);
|
||||
}
|
||||
THEN("The resulting mesh has one and only one vertex at 0,0,10") {
|
||||
const std::vector<Vec3f> &verts = cyl.its.vertices;
|
||||
REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return t.x() == 0 && t.y() == 0 && t.z() == 10; } ) == 1);
|
||||
}
|
||||
THEN("Resulting mesh has 2 + (2*PI/angle * 2) vertices.") {
|
||||
REQUIRE(cyl.its.vertices.size() == (2 + ((2*PI/angle)*2)));
|
||||
}
|
||||
THEN("Resulting mesh has 2*PI/angle * 4 facets") {
|
||||
REQUIRE(cyl.its.indices.size() == (2*PI/angle)*4);
|
||||
}
|
||||
THEN( "The mesh volume is approximately 10pi * 10^2") {
|
||||
REQUIRE(abs(cyl.volume() - (10.0 * M_PI * std::pow(10,2))) < 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIVEN("make_sphere() function") {
|
||||
WHEN("make_sphere() is called with arguments 10, PI / 3") {
|
||||
TriangleMesh sph = make_sphere(10, PI / 243.0);
|
||||
THEN("Resulting mesh has one point at 0,0,-10 and one at 0,0,10") {
|
||||
const std::vector<stl_vertex> &verts = sph.its.vertices;
|
||||
REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return is_approx(t, Vec3f(0.f, 0.f, 10.f)); } ) == 1);
|
||||
REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return is_approx(t, Vec3f(0.f, 0.f, -10.f)); } ) == 1);
|
||||
}
|
||||
THEN( "The mesh volume is approximately 4/3 * pi * 10^3") {
|
||||
REQUIRE(abs(sph.volume() - (4.0/3.0 * M_PI * std::pow(10,3))) < 1); // 1% tolerance?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO( "TriangleMesh: split functionality.") {
|
||||
GIVEN( "A 20mm cube with one corner on the origin") {
|
||||
auto cube = make_cube();
|
||||
WHEN( "The mesh is split into its component parts.") {
|
||||
std::vector<TriangleMesh> meshes = cube.split();
|
||||
THEN(" The bounding box statistics are propagated to the split copies") {
|
||||
REQUIRE(meshes.size() == 1);
|
||||
REQUIRE((meshes.front().bounding_box() == cube.bounding_box()));
|
||||
}
|
||||
}
|
||||
}
|
||||
GIVEN( "Two 20mm cubes, each with one corner on the origin, merged into a single TriangleMesh") {
|
||||
auto cube = make_cube();
|
||||
TriangleMesh cube2(cube);
|
||||
|
||||
cube.merge(cube2);
|
||||
WHEN( "The combined mesh is split") {
|
||||
THEN( "Number of faces is 2x the source.") {
|
||||
REQUIRE(cube.facets_count() == 2 * cube2.facets_count());
|
||||
}
|
||||
std::vector<TriangleMesh> meshes = cube.split();
|
||||
THEN( "Two meshes are in the output vector.") {
|
||||
REQUIRE(meshes.size() == 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO( "TriangleMesh: Mesh merge functions") {
|
||||
GIVEN( "Two 20mm cubes, each with one corner on the origin") {
|
||||
auto cube = make_cube();
|
||||
TriangleMesh cube2(cube);
|
||||
|
||||
WHEN( "The two meshes are merged") {
|
||||
cube.merge(cube2);
|
||||
THEN( "There are twice as many facets in the merged mesh as the original.") {
|
||||
REQUIRE(cube.facets_count() == 2 * cube2.facets_count());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO( "TriangleMeshSlicer: Cut behavior.") {
|
||||
GIVEN( "A 20mm cube with one corner on the origin") {
|
||||
auto cube = make_cube();
|
||||
WHEN( "Object is cut at the bottom") {
|
||||
indexed_triangle_set upper {};
|
||||
indexed_triangle_set lower {};
|
||||
cut_mesh(cube.its, 0, &upper, &lower);
|
||||
THEN("Upper mesh has all facets except those belonging to the slicing plane.") {
|
||||
REQUIRE(upper.indices.size() == 12);
|
||||
}
|
||||
THEN("Lower mesh has no facets.") {
|
||||
REQUIRE(lower.indices.size() == 0);
|
||||
}
|
||||
}
|
||||
WHEN( "Object is cut at the center") {
|
||||
indexed_triangle_set upper {};
|
||||
indexed_triangle_set lower {};
|
||||
cut_mesh(cube.its, 10, &upper, &lower);
|
||||
THEN("Upper mesh has 2 external horizontal facets, 3 facets on each side, and 6 facets on the triangulated side (2 + 12 + 6).") {
|
||||
REQUIRE(upper.indices.size() == 2+12+6);
|
||||
}
|
||||
THEN("Lower mesh has 2 external horizontal facets, 3 facets on each side, and 6 facets on the triangulated side (2 + 12 + 6).") {
|
||||
REQUIRE(lower.indices.size() == 2+12+6);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef TEST_PERFORMANCE
|
||||
TEST_CASE("Regression test for issue #4486 - files take forever to slice") {
|
||||
TriangleMesh mesh;
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
mesh.ReadSTLFile(std::string(testfile_dir) + "test_trianglemesh/4486/100_000.stl");
|
||||
|
||||
config.set("layer_height", 500);
|
||||
config.set("first_layer_height", 250);
|
||||
config.set("nozzle_diameter", 500);
|
||||
|
||||
Slic3r::Print print;
|
||||
Slic3r::Model model;
|
||||
Slic3r::Test::init_print({mesh}, print, model, config);
|
||||
|
||||
print.status_cb = [] (int ln, const std::string& msg) { Slic3r::Log::info("Print") << ln << " " << msg << "\n";};
|
||||
|
||||
std::future<void> fut = std::async([&print] () { print.process(); });
|
||||
std::chrono::milliseconds span {120000};
|
||||
bool timedout {false};
|
||||
if(fut.wait_for(span) == std::future_status::timeout) {
|
||||
timedout = true;
|
||||
}
|
||||
REQUIRE(timedout == false);
|
||||
|
||||
}
|
||||
#endif // TEST_PERFORMANCE
|
||||
|
||||
#ifdef BUILD_PROFILE
|
||||
TEST_CASE("Profile test for issue #4486 - files take forever to slice") {
|
||||
TriangleMesh mesh;
|
||||
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
mesh.ReadSTLFile(std::string(testfile_dir) + "test_trianglemesh/4486/10_000.stl");
|
||||
|
||||
config.set("layer_height", 500);
|
||||
config.set("first_layer_height", 250);
|
||||
config.set("nozzle_diameter", 500);
|
||||
config.set("fill_density", "5%");
|
||||
|
||||
Slic3r::Print print;
|
||||
Slic3r::Model model;
|
||||
Slic3r::Test::init_print({mesh}, print, model, config);
|
||||
|
||||
print.status_cb = [] (int ln, const std::string& msg) { Slic3r::Log::info("Print") << ln << " " << msg << "\n";};
|
||||
|
||||
print.process();
|
||||
|
||||
REQUIRE(true);
|
||||
|
||||
}
|
||||
#endif //BUILD_PROFILE
|
||||
Reference in New Issue
Block a user