mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-20 14:08:20 +00:00
Recompute the v4 recipe comparison once instead of per feature (148 s to 6 s) and compare bounding boxes as well as volumes; drop the incidental NaN loop from the unrepairable Voronoi case; prove the renamed preset is the one selected instead of checking a dirty flag that selecting clears; tell sparse from empty infill in the solid-shell test; plus smaller cleanups.
195 lines
8.4 KiB
C++
195 lines
8.4 KiB
C++
#include <catch2/catch_all.hpp>
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <catch2/catch_message.hpp>
|
|
#include "test_helpers.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include "libslic3r/PrintConfig.hpp"
|
|
#include "libslic3r/Config.hpp"
|
|
#include "libslic3r/TriangleMesh.hpp"
|
|
#include "libslic3r/libslic3r.h"
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
using namespace Slic3r;
|
|
using namespace Slic3r::Test;
|
|
|
|
// The fan stays off while printing the first close_fan_the_first_x_layers layers and runs afterwards.
|
|
TEST_CASE("Fan is held off for the initial layers", "[Cooling]")
|
|
{
|
|
const int fan_off_layers = 5;
|
|
const std::string gcode = slice({ cube(20) }, {
|
|
{ "close_fan_the_first_x_layers", fan_off_layers },
|
|
{ "full_fan_speed_layer", 0 },
|
|
{ "fan_min_speed", 50 },
|
|
{ "fan_max_speed", 100 },
|
|
{ "fan_cooling_layer_time", 1000 }, // every layer is short enough to run the fan
|
|
{ "layer_height", 0.2 },
|
|
{ "initial_layer_print_height", 0.2 },
|
|
});
|
|
|
|
// Record, for each layer, whether any extrusion was printed with the part fan running.
|
|
std::vector<bool> extruded_with_fan;
|
|
int layer = -1;
|
|
bool fan_on = false;
|
|
std::istringstream input(gcode);
|
|
std::string line;
|
|
while (std::getline(input, line)) {
|
|
if (line.rfind(";LAYER_CHANGE", 0) == 0) {
|
|
++layer;
|
|
extruded_with_fan.push_back(false);
|
|
} else if (line.rfind("M107", 0) == 0) {
|
|
fan_on = false;
|
|
} else if (line.rfind("M106 ", 0) == 0 && line.find(" P") == std::string::npos) {
|
|
const size_t s = line.find(" S");
|
|
fan_on = s != std::string::npos && std::stod(line.substr(s + 2)) > 0.;
|
|
} else if (layer >= 0 && fan_on && line.rfind("G1 ", 0) == 0 && line.find(" E") != std::string::npos &&
|
|
(line.find(" X") != std::string::npos || line.find(" Y") != std::string::npos)) {
|
|
extruded_with_fan[layer] = true;
|
|
}
|
|
}
|
|
|
|
// A 20 mm cube at 0.2 mm layers has 100 layers.
|
|
REQUIRE(extruded_with_fan.size() == 100);
|
|
for (int i = 0; i < fan_off_layers; ++i) {
|
|
CAPTURE(i);
|
|
CHECK_FALSE(extruded_with_fan[i]);
|
|
}
|
|
CHECK(extruded_with_fan[fan_off_layers]);
|
|
}
|
|
|
|
// The cooling pass resolves and strips its internal speed placeholders; none leak into
|
|
// the final G-code.
|
|
TEST_CASE("Cooling consumes its internal speed markers", "[Cooling]")
|
|
{
|
|
const std::string gcode = slice({ cube(20) }, { { "layer_height", 0.2 } });
|
|
CHECK(gcode.find(";_EXTRUDE_SET_SPEED") == std::string::npos);
|
|
}
|
|
|
|
TEST_CASE("Arc fitting remains active with overhang fan control", "[Cooling][Regression]")
|
|
{
|
|
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
|
config.set_deserialize_strict({
|
|
{ "enable_arc_fitting", true },
|
|
{ "enable_overhang_bridge_fan", true },
|
|
{ "enable_overhang_speed", true },
|
|
{ "initial_layer_print_height", 0.2 },
|
|
{ "layer_height", 0.2 },
|
|
{ "slow_down_for_layer_cooling", false },
|
|
});
|
|
config.set_key_value("fan_max_speed", new ConfigOptionFloats{20.0});
|
|
config.set_key_value("fan_min_speed", new ConfigOptionFloats{20.0});
|
|
config.set_key_value("overhang_fan_speed", new ConfigOptionInts{100});
|
|
config.set_key_value("overhang_fan_threshold", new ConfigOptionEnumsGeneric{Overhang_threshold_2_4});
|
|
config.set_key_value("layer_change_gcode", new ConfigOptionString{";TEST_LAYER=[layer_num]"});
|
|
|
|
const auto has_arc_after_first_layer = [](const std::string &gcode) {
|
|
std::istringstream input(gcode);
|
|
std::string line;
|
|
size_t layers_seen = 0;
|
|
while (std::getline(input, line)) {
|
|
if (line.rfind(";TEST_LAYER=", 0) == 0)
|
|
++layers_seen;
|
|
else if (layers_seen > 1 && (line.rfind("G2 ", 0) == 0 || line.rfind("G3 ", 0) == 0))
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
const auto has_arc_with_overhang_fan = [](const std::string &gcode) {
|
|
std::istringstream input(gcode);
|
|
std::string line;
|
|
bool overhang_fan_active = false;
|
|
bool outer_wall = false;
|
|
while (std::getline(input, line)) {
|
|
if (line.rfind("M106 S255", 0) == 0)
|
|
overhang_fan_active = true;
|
|
else if (line.rfind("M106 S0", 0) == 0 || line.rfind("M107", 0) == 0)
|
|
overhang_fan_active = false;
|
|
else if (line.rfind(";TYPE:", 0) == 0)
|
|
outer_wall = line == ";TYPE:Outer wall";
|
|
else if (overhang_fan_active && outer_wall && (line.rfind("G2 ", 0) == 0 || line.rfind("G3 ", 0) == 0))
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
const std::string with_overhang_speed = slice({ make_cylinder(25.0, 10.0, 2.0 * PI / 72.0) }, config);
|
|
CHECK(has_arc_after_first_layer(with_overhang_speed));
|
|
|
|
config.set_deserialize_strict({ { "enable_overhang_speed", false } });
|
|
const std::string without_overhang_speed = slice({ make_cylinder(25.0, 10.0, 2.0 * PI / 72.0) }, config);
|
|
CHECK(has_arc_after_first_layer(without_overhang_speed));
|
|
|
|
const std::string uniform_overhang_gcode = slice({ make_sphere(50.0, 2.0 * PI / 72.0) }, config);
|
|
CHECK(has_arc_with_overhang_fan(uniform_overhang_gcode));
|
|
}
|
|
|
|
TEST_CASE("Overhang fan transitions do not depend on overhang speed", "[Cooling][Regression]")
|
|
{
|
|
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
|
config.set_deserialize_strict({
|
|
{ "bridge_speed", 2.0 },
|
|
{ "enable_arc_fitting", false },
|
|
{ "enable_overhang_bridge_fan", true },
|
|
{ "enable_overhang_speed", false },
|
|
{ "initial_layer_print_height", 0.3 },
|
|
{ "inner_wall_speed", 30.0 },
|
|
{ "layer_height", 0.3 },
|
|
{ "outer_wall_speed", 30.0 },
|
|
{ "overhang_1_4_speed", "30" },
|
|
{ "overhang_2_4_speed", "29" },
|
|
{ "overhang_3_4_speed", "6" },
|
|
{ "overhang_4_4_speed", "3" },
|
|
{ "slow_down_for_layer_cooling", false },
|
|
{ "slowdown_for_curled_perimeters", false },
|
|
});
|
|
config.set_key_value("fan_max_speed", new ConfigOptionFloats{20.0});
|
|
config.set_key_value("fan_min_speed", new ConfigOptionFloats{20.0});
|
|
config.set_key_value("overhang_fan_speed", new ConfigOptionInts{100});
|
|
config.set_key_value("overhang_fan_threshold", new ConfigOptionEnumsGeneric{Overhang_threshold_2_4});
|
|
config.set_key_value("layer_change_gcode", new ConfigOptionString{";TEST_LAYER_Z=[layer_z]"});
|
|
|
|
const auto fan_commands = [](const std::string &gcode) {
|
|
std::vector<std::pair<std::string, std::string>> commands;
|
|
std::istringstream input(gcode);
|
|
std::string layer;
|
|
std::string line;
|
|
while (std::getline(input, line)) {
|
|
if (line.rfind(";TEST_LAYER_Z=", 0) == 0)
|
|
layer = line;
|
|
else if (!layer.empty() && (line.rfind("M106", 0) == 0 || line.rfind("M107", 0) == 0))
|
|
commands.emplace_back(layer, line);
|
|
}
|
|
return commands;
|
|
};
|
|
const auto feedrates = [](const std::string &gcode) {
|
|
std::vector<std::string> values;
|
|
std::istringstream input(gcode);
|
|
std::string word;
|
|
while (input >> word)
|
|
if (!word.empty() && word.front() == 'F')
|
|
values.push_back(word);
|
|
return values;
|
|
};
|
|
|
|
constexpr double sphere_radius = 50.0; // 100 mm diameter.
|
|
const std::string without_speed_gcode = slice({make_sphere(sphere_radius, PI / 24.0)}, config);
|
|
config.set_deserialize_strict({{"enable_overhang_speed", true}});
|
|
const std::string with_speed_gcode = slice({make_sphere(sphere_radius, PI / 24.0)}, config);
|
|
|
|
const auto without_speed_fan = fan_commands(without_speed_gcode);
|
|
const auto with_speed_fan = fan_commands(with_speed_gcode);
|
|
const auto without_speed_feedrates = feedrates(without_speed_gcode);
|
|
const auto with_speed_feedrates = feedrates(with_speed_gcode);
|
|
|
|
REQUIRE_FALSE(without_speed_fan.empty());
|
|
REQUIRE(std::any_of(without_speed_fan.begin(), without_speed_fan.end(),
|
|
[](const auto &command) { return command.second.find("S255") != std::string::npos; }));
|
|
REQUIRE(with_speed_feedrates != without_speed_feedrates);
|
|
CHECK(with_speed_fan == without_speed_fan);
|
|
}
|