mirror of
https://github.com/QIDITECH/QIDISlicer.git
synced 2026-01-31 07:58:43 +03:00
Prusa 2.7.3
This commit is contained in:
@@ -16,6 +16,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_gcode_travels.cpp
|
||||
test_gcodefindreplace.cpp
|
||||
test_gcodewriter.cpp
|
||||
test_cancel_object.cpp
|
||||
test_layers.cpp
|
||||
test_model.cpp
|
||||
test_multi.cpp
|
||||
|
||||
200
tests/fff_print/test_cancel_object.cpp
Normal file
200
tests/fff_print/test_cancel_object.cpp
Normal file
@@ -0,0 +1,200 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
|
||||
#include "libslic3r/GCode.hpp"
|
||||
#include "test_data.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Test;
|
||||
|
||||
constexpr bool debug_files{false};
|
||||
|
||||
std::string remove_object(const std::string &gcode, const int id) {
|
||||
std::string result{gcode};
|
||||
std::string start_token{"M486 S" + std::to_string(id) + "\n"};
|
||||
std::string end_token{"M486 S-1\n"};
|
||||
|
||||
std::size_t start{result.find(start_token)};
|
||||
|
||||
while (start != std::string::npos) {
|
||||
std::size_t end_token_start{result.find(end_token, start)};
|
||||
std::size_t end{end_token_start + end_token.size()};
|
||||
result.replace(start, end - start, "");
|
||||
start = result.find(start_token);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST_CASE("Remove object sanity check", "[CancelObject]") {
|
||||
// clang-format off
|
||||
const std::string gcode{
|
||||
"the\n"
|
||||
"M486 S2\n"
|
||||
"to delete\n"
|
||||
"M486 S-1\n"
|
||||
"kept\n"
|
||||
"M486 S2\n"
|
||||
"to also delete\n"
|
||||
"M486 S-1\n"
|
||||
"lines\n"
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
const std::string result{remove_object(gcode, 2)};
|
||||
|
||||
// clang-format off
|
||||
CHECK(result == std::string{
|
||||
"the\n"
|
||||
"kept\n"
|
||||
"lines\n"
|
||||
});
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
void check_retraction(const std::string &gcode, double offset = 0.0) {
|
||||
GCodeReader parser;
|
||||
std::map<int, double> retracted;
|
||||
unsigned count{0};
|
||||
std::set<int> there_is_unretract;
|
||||
int extruder_id{0};
|
||||
|
||||
parser.parse_buffer(
|
||||
gcode,
|
||||
[&](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
INFO("Line number: " + std::to_string(++count));
|
||||
INFO("Extruder id: " + std::to_string(extruder_id));
|
||||
if (!line.raw().empty() && line.raw().front() == 'T') {
|
||||
extruder_id = std::stoi(std::string{line.raw().back()});
|
||||
}
|
||||
if (line.dist_XY(self) < std::numeric_limits<double>::epsilon()) {
|
||||
if (line.has_e() && line.e() < 0) {
|
||||
retracted[extruder_id] += line.e();
|
||||
}
|
||||
if (line.has_e() && line.e() > 0) {
|
||||
INFO("Line: " + line.raw());
|
||||
if (there_is_unretract.count(extruder_id) == 0) {
|
||||
there_is_unretract.insert(extruder_id);
|
||||
REQUIRE(retracted[extruder_id] + offset + line.e() == Approx(0.0));
|
||||
} else {
|
||||
REQUIRE(retracted[extruder_id] + line.e() == Approx(0.0));
|
||||
}
|
||||
retracted[extruder_id] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void add_object(
|
||||
Model &model, const std::string &name, const int extruder, const Vec3d &offset = Vec3d::Zero()
|
||||
) {
|
||||
std::string extruder_id{std::to_string(extruder)};
|
||||
ModelObject *object = model.add_object();
|
||||
object->name = name;
|
||||
ModelVolume *volume = object->add_volume(Test::mesh(Test::TestMesh::cube_20x20x20));
|
||||
volume->set_material_id("material" + extruder_id);
|
||||
volume->translate(offset);
|
||||
DynamicPrintConfig config;
|
||||
config.set_deserialize_strict({
|
||||
{"extruder", extruder_id},
|
||||
});
|
||||
volume->config.assign_config(config);
|
||||
object->add_instance();
|
||||
object->ensure_on_bed();
|
||||
}
|
||||
|
||||
class CancelObjectFixture
|
||||
{
|
||||
public:
|
||||
CancelObjectFixture() {
|
||||
config.set_deserialize_strict({
|
||||
{"gcode_flavor", "marlin2"},
|
||||
{"gcode_label_objects", "firmware"},
|
||||
{"gcode_comments", "1"},
|
||||
{"use_relative_e_distances", "1"},
|
||||
{"wipe", "0"},
|
||||
{"skirts", "0"},
|
||||
});
|
||||
|
||||
add_object(two_cubes, "no_offset_cube", 0);
|
||||
add_object(two_cubes, "offset_cube", 0, {30.0, 0.0, 0.0});
|
||||
|
||||
add_object(multimaterial_cubes, "no_offset_cube", 1);
|
||||
add_object(multimaterial_cubes, "offset_cube", 2, {30.0, 0.0, 0.0});
|
||||
|
||||
retract_length = config.option<ConfigOptionFloats>("retract_length")->get_at(0);
|
||||
retract_length_toolchange = config.option<ConfigOptionFloats>("retract_length_toolchange")
|
||||
->get_at(0);
|
||||
}
|
||||
|
||||
DynamicPrintConfig config{Slic3r::DynamicPrintConfig::full_print_config()};
|
||||
|
||||
Model two_cubes;
|
||||
Model multimaterial_cubes;
|
||||
|
||||
double retract_length{};
|
||||
double retract_length_toolchange{};
|
||||
};
|
||||
|
||||
TEST_CASE_METHOD(CancelObjectFixture, "Single extruder", "[CancelObject]") {
|
||||
Print print;
|
||||
print.apply(two_cubes, config);
|
||||
print.validate();
|
||||
const std::string gcode{Test::gcode(print)};
|
||||
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream output{"single_extruder_two.gcode"};
|
||||
output << gcode;
|
||||
}
|
||||
|
||||
SECTION("One remaining") {
|
||||
const std::string removed_object_gcode{remove_object(gcode, 0)};
|
||||
REQUIRE(removed_object_gcode.find("M486 S1\n") != std::string::npos);
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream output{"single_extruder_one.gcode"};
|
||||
output << removed_object_gcode;
|
||||
}
|
||||
|
||||
check_retraction(removed_object_gcode);
|
||||
}
|
||||
|
||||
SECTION("All cancelled") {
|
||||
const std::string removed_all_gcode{remove_object(remove_object(gcode, 0), 1)};
|
||||
|
||||
// First retraction is not compensated - set offset.
|
||||
check_retraction(removed_all_gcode, retract_length);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(CancelObjectFixture, "Sequential print", "[CancelObject]") {
|
||||
config.set_deserialize_strict({{"complete_objects", 1}});
|
||||
|
||||
Print print;
|
||||
print.apply(two_cubes, config);
|
||||
print.validate();
|
||||
const std::string gcode{Test::gcode(print)};
|
||||
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream output{"sequential_print_two.gcode"};
|
||||
output << gcode;
|
||||
}
|
||||
|
||||
SECTION("One remaining") {
|
||||
const std::string removed_object_gcode{remove_object(gcode, 0)};
|
||||
REQUIRE(removed_object_gcode.find("M486 S1\n") != std::string::npos);
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream output{"sequential_print_one.gcode"};
|
||||
output << removed_object_gcode;
|
||||
}
|
||||
|
||||
check_retraction(removed_object_gcode);
|
||||
}
|
||||
|
||||
SECTION("All cancelled") {
|
||||
const std::string removed_all_gcode{remove_object(remove_object(gcode, 0), 1)};
|
||||
|
||||
// First retraction is not compensated - set offset.
|
||||
check_retraction(removed_all_gcode, retract_length);
|
||||
}
|
||||
}
|
||||
@@ -138,11 +138,15 @@ TEST_CASE("Extrusion, travels, temeperatures", "[GCode]") {
|
||||
Model model;
|
||||
Test::init_print({TestMesh::cube_20x20x20}, print, model, config, false, 2);
|
||||
std::string gcode = Test::gcode(print);
|
||||
if constexpr (debug_files) {
|
||||
std::ofstream gcode_file{"sequential_print.gcode"};
|
||||
gcode_file << gcode;
|
||||
}
|
||||
parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
INFO("Unexpected E argument");
|
||||
CHECK(!line.has_e());
|
||||
|
||||
if (line.has_z()) {
|
||||
if (line.has_z() && std::abs(line.dist_Z(self)) > 0) {
|
||||
z_moves.emplace_back(line.z());
|
||||
}
|
||||
if (line.has_x() || line.has_y()) {
|
||||
|
||||
@@ -42,6 +42,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_anyptr.cpp
|
||||
test_jump_point_search.cpp
|
||||
test_support_spots_generator.cpp
|
||||
test_layer_region.cpp
|
||||
../data/prusaparts.cpp
|
||||
../data/prusaparts.hpp
|
||||
test_static_map.cpp
|
||||
|
||||
148
tests/libslic3r/test_layer_region.cpp
Normal file
148
tests/libslic3r/test_layer_region.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/SVG.hpp"
|
||||
#include <catch2/catch.hpp>
|
||||
#include <libslic3r/LayerRegion.hpp>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Algorithm;
|
||||
|
||||
constexpr bool export_svgs = false;
|
||||
|
||||
ExPolygon rectangle(const Point& origin, const int width, const int height) {
|
||||
return {
|
||||
origin,
|
||||
origin + Point{width, 0},
|
||||
origin + Point{width, height},
|
||||
origin + Point{0, height},
|
||||
};
|
||||
}
|
||||
|
||||
struct LayerRegionFixture {
|
||||
Surfaces surfaces{
|
||||
Surface{
|
||||
stBottomBridge,
|
||||
rectangle({scaled(-1.0), scaled(0.0)}, scaled(1.0), scaled(1.0))
|
||||
},
|
||||
Surface{
|
||||
stBottomBridge,
|
||||
rectangle({scaled(0.0), scaled(0.0)}, scaled(1.0), scaled(1.0))
|
||||
},
|
||||
Surface{
|
||||
stBottomBridge,
|
||||
rectangle({scaled(-3.0), scaled(0.0)}, scaled(1.0), scaled(1.0))
|
||||
}
|
||||
};
|
||||
|
||||
ExPolygons shells{{
|
||||
rectangle({scaled(-1.0), scaled(1.0)}, scaled(3.0), scaled(1.0))
|
||||
}};
|
||||
ExPolygons sparse {{
|
||||
rectangle({scaled(-2.0), scaled(-1.0)}, scaled(1.0), scaled(3.0))
|
||||
}};
|
||||
|
||||
const float scaled_spacing{scaled(0.3)};
|
||||
|
||||
static constexpr const float expansion_step = scaled<float>(0.1);
|
||||
static constexpr const size_t max_nr_expansion_steps = 5;
|
||||
const float closing_radius = 0.55f * 0.65f * 1.05f * scaled_spacing;
|
||||
const int shells_expansion_depth = scaled(0.6);
|
||||
const RegionExpansionParameters expansion_params_into_solid_infill = RegionExpansionParameters::build(
|
||||
shells_expansion_depth,
|
||||
expansion_step,
|
||||
max_nr_expansion_steps
|
||||
);
|
||||
const int sparse_expansion_depth = scaled(0.3);
|
||||
const RegionExpansionParameters expansion_params_into_sparse_infill = RegionExpansionParameters::build(
|
||||
sparse_expansion_depth,
|
||||
expansion_step,
|
||||
max_nr_expansion_steps
|
||||
);
|
||||
|
||||
std::vector<ExpansionZone> expansion_zones{
|
||||
ExpansionZone{
|
||||
std::move(shells),
|
||||
expansion_params_into_solid_infill,
|
||||
},
|
||||
ExpansionZone{
|
||||
std::move(sparse),
|
||||
expansion_params_into_sparse_infill,
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
TEST_CASE_METHOD(LayerRegionFixture, "test the surface expansion", "[LayerRegion]") {
|
||||
const double custom_angle{1.234f};
|
||||
|
||||
const Surfaces result{expand_merge_surfaces(
|
||||
surfaces, stBottomBridge,
|
||||
expansion_zones,
|
||||
closing_radius,
|
||||
custom_angle
|
||||
)};
|
||||
|
||||
if constexpr (export_svgs) {
|
||||
SVG svg("general_expansion.svg", BoundingBox{
|
||||
Point{scaled(-3.0), scaled(-1.0)},
|
||||
Point{scaled(2.0), scaled(2.0)}
|
||||
});
|
||||
|
||||
svg.draw(surfaces, "blue");
|
||||
svg.draw(expansion_zones[0].expolygons, "green");
|
||||
svg.draw(expansion_zones[1].expolygons, "red");
|
||||
svg.draw_outline(result, "black", "", scale_(0.01));
|
||||
}
|
||||
|
||||
REQUIRE(result.size() == 2);
|
||||
CHECK(result.at(0).bridge_angle == Approx(custom_angle));
|
||||
CHECK(result.at(1).bridge_angle == Approx(custom_angle));
|
||||
CHECK(result.at(0).expolygon.contour.size() == 22);
|
||||
CHECK(result.at(1).expolygon.contour.size() == 14);
|
||||
|
||||
// These lines in the polygons should correspond to the expansion depth.
|
||||
CHECK(result.at(0).expolygon.contour.lines().at(2).length() == shells_expansion_depth);
|
||||
CHECK(result.at(1).expolygon.contour.lines().at(7).length() == sparse_expansion_depth);
|
||||
CHECK(result.at(1).expolygon.contour.lines().at(11).length() == sparse_expansion_depth);
|
||||
|
||||
CHECK(intersection_ex({result.at(0).expolygon}, expansion_zones[0].expolygons).size() == 0);
|
||||
CHECK(intersection_ex({result.at(0).expolygon}, expansion_zones[1].expolygons).size() == 0);
|
||||
CHECK(intersection_ex({result.at(1).expolygon}, expansion_zones[0].expolygons).size() == 0);
|
||||
CHECK(intersection_ex({result.at(1).expolygon}, expansion_zones[1].expolygons).size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(LayerRegionFixture, "test the bridge expansion with the bridge angle detection", "[LayerRegion]") {
|
||||
Surfaces result{expand_bridges_detect_orientations(
|
||||
surfaces,
|
||||
expansion_zones,
|
||||
closing_radius
|
||||
)};
|
||||
|
||||
if constexpr (export_svgs) {
|
||||
SVG svg("bridge_expansion.svg", BoundingBox{
|
||||
Point{scaled(-3.0), scaled(-1.0)},
|
||||
Point{scaled(2.0), scaled(2.0)}
|
||||
});
|
||||
|
||||
svg.draw(surfaces, "blue");
|
||||
svg.draw(expansion_zones[0].expolygons, "green");
|
||||
svg.draw(expansion_zones[1].expolygons, "red");
|
||||
svg.draw_outline(result, "black", "", scale_(0.01));
|
||||
}
|
||||
|
||||
REQUIRE(result.size() == 2);
|
||||
CHECK(result.at(0).bridge_angle == Approx(1.5707963268));
|
||||
CHECK(std::fmod(result.at(1).bridge_angle, M_PI) == Approx(0.0));
|
||||
CHECK(result.at(0).expolygon.contour.size() == 22);
|
||||
CHECK(result.at(1).expolygon.contour.size() == 14);
|
||||
|
||||
// These lines in the polygons should correspond to the expansion depth.
|
||||
CHECK(result.at(0).expolygon.contour.lines().at(2).length() == shells_expansion_depth);
|
||||
CHECK(result.at(1).expolygon.contour.lines().at(7).length() == sparse_expansion_depth);
|
||||
CHECK(result.at(1).expolygon.contour.lines().at(11).length() == sparse_expansion_depth);
|
||||
|
||||
CHECK(intersection_ex({result.at(0).expolygon}, expansion_zones[0].expolygons).size() == 0);
|
||||
CHECK(intersection_ex({result.at(0).expolygon}, expansion_zones[1].expolygons).size() == 0);
|
||||
CHECK(intersection_ex({result.at(1).expolygon}, expansion_zones[0].expolygons).size() == 0);
|
||||
CHECK(intersection_ex({result.at(1).expolygon}, expansion_zones[1].expolygons).size() == 0);
|
||||
}
|
||||
Reference in New Issue
Block a user