mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-06-15 08:23:00 +00:00
Revive the disabled fff_print test suite (#14196)
* Fix null-deref and arranger bugs that gate headless slicing tests export_gcode dereferenced a null result out-param, enum serialization dereferenced a null keys_map, and get_arrange_polys left bed_idx unseeded so the arranger dropped items. All only affect the headless test/CLI path. * Fix the headless test harness and add G-code test helpers Use the real arranger, fix temp-file handling with an RAII guard, and add layers_with_role / max_z for inspecting sliced G-code. * Re-enable the Model construction test * Re-enable SupportMaterial tests and add an enforced-support test * Re-enable and extend PrintObject layer-height and perimeter tests * Re-enable Print skirt, brim, and solid-surface tests * Re-enable and extend PrintGCode tests Un-hide the basic scenario (dead-key fixes, reframes, trimmed trivia) and add initial-layer-height, sequential-order, and null-result export tests. * Re-enable and reframe the skirt/brim tests Detect skirt/brim by G-code role comment instead of a sentinel speed, and resolve the previously-unfinished skirt-enclosure test. * Replace the stale lift()/unlift() test with a z_hop test * Delete the stub and broken Flow tests
This commit is contained in:
@@ -2186,7 +2186,7 @@ private:
|
||||
else
|
||||
throw ConfigurationError("Serializing NaN");
|
||||
}
|
||||
else {
|
||||
else if (this->keys_map != nullptr) {
|
||||
for (const auto& kvp : *this->keys_map)
|
||||
if (kvp.second == v)
|
||||
ss << kvp.first;
|
||||
|
||||
@@ -2194,7 +2194,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu
|
||||
BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info();
|
||||
print->set_done(psGCodeExport);
|
||||
|
||||
if(is_BBL_Printer())
|
||||
if(is_BBL_Printer() && result != nullptr)
|
||||
result->label_object_enabled = m_enable_exclude_object;
|
||||
// Write the profiler measurements to file
|
||||
PROFILE_UPDATE();
|
||||
|
||||
@@ -19,6 +19,10 @@ arrangement::ArrangePolygons get_arrange_polys(const Model &model, ModelInstance
|
||||
for (ModelObject *mo : model.objects)
|
||||
for (ModelInstance *minst : mo->instances) {
|
||||
minst->get_arrange_polygon(&ap);
|
||||
// ModelInstance::get_arrange_polygon leaves bed_idx at its UNARRANGED
|
||||
// default; seed it to bed 0 (as get_instance_arrange_poly does) so the
|
||||
// nester treats the item as placeable instead of returning it unplaced.
|
||||
ap.bed_idx = 0;
|
||||
input.emplace_back(ap);
|
||||
instances.emplace_back(minst);
|
||||
}
|
||||
|
||||
@@ -2632,7 +2632,8 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
|
||||
gcode.do_export(this, path.c_str(), result, thumbnail_cb);
|
||||
gcode.export_layer_filaments(result);
|
||||
//BBS
|
||||
result->conflict_result = m_conflict_result;
|
||||
if (result != nullptr)
|
||||
result->conflict_result = m_conflict_result;
|
||||
return path.c_str();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
before_layer_gcode =
|
||||
between_objects_gcode =
|
||||
end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n"
|
||||
end_gcode = M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n
|
||||
extrusion_axis = E
|
||||
extrusion_multiplier = 1
|
||||
filament_cost = 0
|
||||
filament_density = 0
|
||||
filament_diameter = 3
|
||||
filament_max_volumetric_speed = 0
|
||||
gcode_comments = 0
|
||||
gcode_flavor = reprap
|
||||
layer_gcode =
|
||||
max_print_speed = 80
|
||||
max_volumetric_speed = 0
|
||||
retract_length = 2
|
||||
retract_length_toolchange = 10
|
||||
retract_lift = 1.5
|
||||
retract_lift_above = 0
|
||||
retract_lift_below = 0
|
||||
retract_restart_extra = 0
|
||||
retract_restart_extra_toolchange = 0
|
||||
retract_speed = 40
|
||||
start_filament_gcode = "; Filament gcode\n"
|
||||
start_gcode = G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n
|
||||
toolchange_gcode =
|
||||
travel_speed = 130
|
||||
use_firmware_retraction = 0
|
||||
use_relative_e_distances = 0
|
||||
use_volumetric_e = 0
|
||||
@@ -10,10 +10,11 @@
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <libslic3r/ModelArrange.hpp>
|
||||
|
||||
#include "test_utils.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Slic3r { namespace Test {
|
||||
@@ -282,16 +283,36 @@ void init_and_process_print(std::initializer_list<TriangleMesh> meshes, Slic3r::
|
||||
|
||||
std::string gcode(Print & print)
|
||||
{
|
||||
boost::filesystem::path temp = boost::filesystem::unique_path();
|
||||
ScopedTemporaryFile temp(".gcode");
|
||||
print.set_status_silent();
|
||||
print.process();
|
||||
print.export_gcode(temp.string(), nullptr, nullptr);
|
||||
std::ifstream t(temp.string());
|
||||
std::string str((std::istreambuf_iterator<char>(t)), std::istreambuf_iterator<char>());
|
||||
boost::nowide::remove(temp.string().c_str());
|
||||
return str;
|
||||
}
|
||||
|
||||
std::set<double> layers_with_role(const std::string &gcode, const std::string &role)
|
||||
{
|
||||
std::set<double> layers;
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(gcode, [&layers, &role](GCodeReader &self, const GCodeReader::GCodeLine &line) {
|
||||
if (line.extruding(self) && line.comment().find(role) != std::string_view::npos)
|
||||
layers.insert(self.z());
|
||||
});
|
||||
return layers;
|
||||
}
|
||||
|
||||
double max_z(const std::string &gcode)
|
||||
{
|
||||
double z = 0.0;
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(gcode, [&z](GCodeReader &self, const GCodeReader::GCodeLine &) {
|
||||
z = std::max(z, static_cast<double>(self.z()));
|
||||
});
|
||||
return z;
|
||||
}
|
||||
|
||||
Slic3r::Model model(const std::string &model_name, TriangleMesh &&_mesh)
|
||||
{
|
||||
Slic3r::Model result;
|
||||
@@ -338,7 +359,7 @@ std::string slice(std::initializer_list<TriangleMesh> meshes, std::initializer_l
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
SCENARIO("init_print functionality", "[test_data][.]") {
|
||||
SCENARIO("init_print functionality", "[test_data]") {
|
||||
GIVEN("A default config") {
|
||||
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||
WHEN("init_print is called with a single mesh.") {
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Slic3r { namespace Test {
|
||||
@@ -80,6 +82,13 @@ std::string slice(std::initializer_list<TriangleMesh> meshes, const DynamicPrint
|
||||
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);
|
||||
|
||||
// Distinct layer Z heights that carry an extrusion tagged with the given role
|
||||
// comment (requires gcode_comments), e.g. "skirt", "brim", "support".
|
||||
std::set<double> layers_with_role(const std::string &gcode, const std::string &role);
|
||||
|
||||
// Highest Z reached by any move in the gcode.
|
||||
double max_z(const std::string &gcode);
|
||||
|
||||
} } // namespace Slic3r::Test
|
||||
|
||||
|
||||
|
||||
@@ -15,82 +15,7 @@
|
||||
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() == Catch::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 == Catch::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,
|
||||
/// 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") {
|
||||
|
||||
@@ -6,68 +6,6 @@
|
||||
|
||||
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.lazy_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.z_hop.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.lazy_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.lazy_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.z_hop.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.lazy_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.lazy_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.z_hop.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.lazy_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") {
|
||||
@@ -94,3 +32,28 @@ SCENARIO("set_speed emits values with fixed-point output.", "[GCodeWriter]") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("z_hop lifts the nozzle when a lift is requested", "[GCodeWriter]") {
|
||||
GIVEN("A writer with the nozzle parked at Z = 10") {
|
||||
GCodeWriter writer;
|
||||
std::vector<unsigned int> extruder_ids { 0 };
|
||||
writer.set_extruders(extruder_ids);
|
||||
writer.set_extruder(0);
|
||||
writer.travel_to_z(10.0);
|
||||
|
||||
WHEN("z_hop is 1 and an eager lift is requested") {
|
||||
writer.config.z_hop.values = { 1.0 };
|
||||
std::string gcode = writer.eager_lift(LiftType::NormalLift);
|
||||
THEN("a Z move up by z_hop is emitted") {
|
||||
REQUIRE_THAT(gcode, Catch::Matchers::ContainsSubstring("Z11"));
|
||||
}
|
||||
}
|
||||
WHEN("z_hop is 0") {
|
||||
writer.config.z_hop.values = { 0.0 };
|
||||
std::string gcode = writer.eager_lift(LiftType::NormalLift);
|
||||
THEN("no lift is emitted") {
|
||||
REQUIRE(gcode.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/ModelArrange.hpp"
|
||||
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "test_data.hpp"
|
||||
#include "test_utils.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Test;
|
||||
|
||||
SCENARIO("Model construction", "[Model][.]") {
|
||||
SCENARIO("Model construction", "[Model]") {
|
||||
GIVEN("A Slic3r Model") {
|
||||
Slic3r::Model model;
|
||||
Slic3r::TriangleMesh sample_mesh = Slic3r::make_cube(20,20,20);
|
||||
@@ -49,12 +49,11 @@ SCENARIO("Model construction", "[Model][.]") {
|
||||
print.set_status_silent();
|
||||
print.apply(model, config);
|
||||
print.process();
|
||||
boost::filesystem::path temp = boost::filesystem::unique_path();
|
||||
ScopedTemporaryFile temp(".gcode");
|
||||
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());
|
||||
REQUIRE(boost::filesystem::exists(temp.path()));
|
||||
REQUIRE(boost::filesystem::is_regular_file(temp.path()));
|
||||
REQUIRE(boost::filesystem::file_size(temp.path()) > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,35 +9,14 @@
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Test;
|
||||
|
||||
SCENARIO("PrintObject: Perimeter generation", "[PrintObject][.]") {
|
||||
SCENARIO("Print: Skirt generation", "[Print]") {
|
||||
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") {
|
||||
WHEN("skirt_loops is set to 2") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
|
||||
{ "skirt_height", 1 },
|
||||
{ "skirt_distance", 1 },
|
||||
{ "skirts", 2 }
|
||||
{ "skirt_height", 1 },
|
||||
{ "skirt_distance", 1 },
|
||||
{ "skirt_loops", 2 }
|
||||
});
|
||||
THEN("Skirt Extrusion collection has 2 loops in it") {
|
||||
REQUIRE(print.skirt().items_count() == 2);
|
||||
@@ -47,19 +26,19 @@ SCENARIO("Print: Skirt generation", "[Print][.]") {
|
||||
}
|
||||
}
|
||||
|
||||
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") {
|
||||
SCENARIO("Print: Changing number of solid shell layers does not cause all surfaces to become internal.", "[Print]") {
|
||||
GIVEN("sliced 20mm cube and config with top_shell_layers = 2 and bottom_shell_layers = 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 }
|
||||
{ "top_shell_layers", 2 },
|
||||
{ "bottom_shell_layers", 1 },
|
||||
{ "layer_height", 0.25 }, // get a known number of layers
|
||||
{ "initial_layer_print_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)
|
||||
// Precondition: Ensure that the model has 2 solid top layers (79, 78)
|
||||
// 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));
|
||||
@@ -74,8 +53,8 @@ SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces t
|
||||
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);
|
||||
WHEN("Model is re-sliced with top_shell_layers == 3") {
|
||||
config.set("top_shell_layers", 3);
|
||||
print.apply(model, config);
|
||||
print.process();
|
||||
THEN("Print object does not have 0 solid bottom layers.") {
|
||||
@@ -90,27 +69,14 @@ SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces t
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("Print: Brim generation", "[Print][.]") {
|
||||
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") {
|
||||
size_t total_items = 0;
|
||||
for (const auto& pair : print.get_brimMap()) {
|
||||
total_items += pair.second.items_count();
|
||||
}
|
||||
REQUIRE(total_items == 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 }
|
||||
{ "brim_type", "outer_only" },
|
||||
{ "initial_layer_line_width", 1 },
|
||||
{ "brim_width", 6 }
|
||||
});
|
||||
THEN("Brim Extrusion collection has 6 loops in it") {
|
||||
size_t total_items = 0;
|
||||
@@ -123,17 +89,16 @@ SCENARIO("Print: Brim generation", "[Print][.]") {
|
||||
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 }
|
||||
{ "brim_type", "outer_only" },
|
||||
{ "brim_width", 6 },
|
||||
{ "initial_layer_line_width", 0.5 }
|
||||
});
|
||||
print.process();
|
||||
THEN("Brim Extrusion collection has 12 loops in it") {
|
||||
size_t total_items = 0;
|
||||
for (const auto& pair : print.get_brimMap()) {
|
||||
total_items += pair.second.items_count();
|
||||
}
|
||||
REQUIRE(total_items == 14);
|
||||
REQUIRE(total_items == 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,14 @@
|
||||
#include "libslic3r/GCodeReader.hpp"
|
||||
|
||||
#include "test_data.hpp"
|
||||
#include "test_utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/regex.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <set>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Test;
|
||||
@@ -25,25 +30,23 @@ 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][.]") {
|
||||
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", "" }
|
||||
{ "layer_height", 0.2 },
|
||||
{ "initial_layer_print_height", 0.2 },
|
||||
{ "initial_layer_line_width", 0 },
|
||||
{ "gcode_comments", true },
|
||||
{ "machine_start_gcode", "" },
|
||||
{ "z_hop", 0 }
|
||||
});
|
||||
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);
|
||||
@@ -61,14 +64,9 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode][.]") {
|
||||
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);
|
||||
THEN("The config trailer includes print and region settings") {
|
||||
REQUIRE(gcode.find("; layer_height") != std::string::npos);
|
||||
REQUIRE(gcode.find("; fill_density") != std::string::npos);
|
||||
REQUIRE(gcode.find("; sparse_infill_density") != std::string::npos);
|
||||
}
|
||||
THEN("Infill is emitted.") {
|
||||
boost::smatch has_match;
|
||||
@@ -83,27 +81,22 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode][.]") {
|
||||
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 == Catch::Approx(20.));
|
||||
REQUIRE_THAT(max_z(gcode), Catch::Matchers::WithinAbs(20., 1e-4));
|
||||
}
|
||||
}
|
||||
WHEN("output is executed with complete objects and two differently-sized meshes") {
|
||||
WHEN("output is executed with two objects printed sequentially") {
|
||||
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" }
|
||||
{ "initial_layer_line_width", 0 },
|
||||
{ "initial_layer_print_height", 0.3 },
|
||||
{ "layer_height", 0.2 },
|
||||
{ "enable_support", false },
|
||||
{ "raft_layers", 0 },
|
||||
{ "print_sequence", "by object" },
|
||||
{ "gcode_comments", true },
|
||||
{ "printing_by_object_gcode", "; between-object-gcode" },
|
||||
{ "z_hop", 0 }
|
||||
});
|
||||
std::string gcode = Slic3r::Test::gcode(print);
|
||||
THEN("Some text output is generated.") {
|
||||
@@ -125,13 +118,7 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode][.]") {
|
||||
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 == Catch::Approx(20.1));
|
||||
REQUIRE_THAT(max_z(gcode), Catch::Matchers::WithinAbs(20.1, 1e-4));
|
||||
}
|
||||
THEN("Z height resets on object change") {
|
||||
double final_z = 0.0;
|
||||
@@ -147,27 +134,13 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode][.]") {
|
||||
});
|
||||
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 }
|
||||
{ "initial_layer_line_width", 0 },
|
||||
{ "enable_support", true },
|
||||
{ "raft_layers", 3 },
|
||||
{ "gcode_comments", true }
|
||||
});
|
||||
THEN("Some text output is generated.") {
|
||||
REQUIRE(gcode.size() > 0);
|
||||
@@ -187,7 +160,7 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode][.]") {
|
||||
}
|
||||
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" }
|
||||
{ "initial_layer_line_width", "0.5" }
|
||||
});
|
||||
THEN("Some text output is generated.") {
|
||||
REQUIRE(gcode.size() > 0);
|
||||
@@ -204,18 +177,18 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode][.]") {
|
||||
}
|
||||
WHEN("Cooling is enabled and the fan is disabled.") {
|
||||
std::string gcode = ::Test::slice({ TestMesh::cube_20x20x20 }, {
|
||||
{ "cooling", true },
|
||||
{ "disable_fan_first_layers", 5 }
|
||||
{ "cooling", true },
|
||||
{ "close_fan_the_first_x_layers", 5 }
|
||||
});
|
||||
THEN("GCode to disable fan is emitted."){
|
||||
REQUIRE(gcode.find("M107") != std::string::npos);
|
||||
REQUIRE(gcode.find("M106 S0") != 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 }
|
||||
{ "machine_end_gcode", "; Layer_num [layer_num]\n; Layer_z [layer_z]" },
|
||||
{ "layer_height", 0.1 },
|
||||
{ "initial_layer_print_height", 0.1 }
|
||||
});
|
||||
THEN("layer_num and layer_z are processed in the end gcode") {
|
||||
REQUIRE(gcode.find("; Layer_num 199") != std::string::npos);
|
||||
@@ -223,39 +196,21 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode][.]") {
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
std::string gcode = ::Test::slice({ TestMesh::cube_20x20x20 }, {
|
||||
{ "machine_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);
|
||||
}
|
||||
}
|
||||
|
||||
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 }
|
||||
{ "print_sequence", "by object" },
|
||||
{ "gcode_comments", true },
|
||||
{ "layer_change_gcode", ";Layer:[layer_num] ([layer_z] mm)" },
|
||||
{ "layer_height", 0.1 },
|
||||
{ "initial_layer_print_height", 0.1 }
|
||||
});
|
||||
// End of the 1st object.
|
||||
std::string token = ";Layer:199 ";
|
||||
@@ -267,15 +222,82 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode][.]") {
|
||||
REQUIRE(pos < gcode.size());
|
||||
double z = 0;
|
||||
REQUIRE((sscanf(gcode.data() + pos, "(%lf mm)", &z) == 1));
|
||||
REQUIRE(z == Catch::Approx(20.));
|
||||
REQUIRE_THAT(z, Catch::Matchers::WithinAbs(20., 1e-4));
|
||||
// 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 == Catch::Approx(20.));
|
||||
REQUIRE_THAT(z, Catch::Matchers::WithinAbs(20., 1e-4));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("export_gcode writes G-code without a result pointer", "[PrintGCode][export_gcode]")
|
||||
{
|
||||
Print print;
|
||||
Model model;
|
||||
Slic3r::Test::init_print({TestMesh::cube_20x20x20}, print, model);
|
||||
print.process();
|
||||
|
||||
SECTION("non-BBL printer") {}
|
||||
SECTION("BBL printer") { print.is_BBL_printer() = true; }
|
||||
|
||||
ScopedTemporaryFile temp(".gcode");
|
||||
REQUIRE_NOTHROW(print.export_gcode(temp.string(), nullptr, nullptr));
|
||||
|
||||
std::ifstream in(temp.string());
|
||||
const std::string gcode((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
|
||||
|
||||
REQUIRE_FALSE(gcode.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Initial layer height is honored", "[PrintGCode]")
|
||||
{
|
||||
const std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, {
|
||||
{ "initial_layer_print_height", 0.3 },
|
||||
{ "layer_height", 0.2 },
|
||||
{ "z_hop", 0 } // keep recorded Z equal to the printed layer height
|
||||
});
|
||||
|
||||
std::set<double> layer_zs;
|
||||
GCodeReader reader;
|
||||
reader.parse_buffer(gcode, [&layer_zs] (GCodeReader& self, const GCodeReader::GCodeLine& line) {
|
||||
if (line.extruding(self) && line.dist_XY(self) > 0)
|
||||
layer_zs.insert(self.z());
|
||||
});
|
||||
|
||||
REQUIRE(layer_zs.size() > 1);
|
||||
REQUIRE_THAT(*layer_zs.begin(), Catch::Matchers::WithinAbs(0.3, 1e-4));
|
||||
REQUIRE_THAT(*std::next(layer_zs.begin()), Catch::Matchers::WithinAbs(0.5, 1e-4));
|
||||
}
|
||||
|
||||
TEST_CASE("Sequential printing follows model order", "[PrintGCode]")
|
||||
{
|
||||
// Two objects of different heights, taller one added first. Orca prints
|
||||
// sequential objects in model order, so the taller one is printed first.
|
||||
const std::string gcode = Slic3r::Test::slice({ Slic3r::make_cube(20, 20, 20), Slic3r::make_cube(20, 20, 10) }, {
|
||||
{ "print_sequence", "by object" },
|
||||
{ "layer_height", 0.2 },
|
||||
{ "initial_layer_print_height", 0.2 },
|
||||
{ "z_hop", 0 }
|
||||
});
|
||||
|
||||
// The first object's height is the peak Z reached before Z drops back to the
|
||||
// first layer (the object change). With by-object printing only an object
|
||||
// change returns Z to the bottom.
|
||||
double first_object_peak_z = 0.0;
|
||||
double running_peak = 0.0;
|
||||
GCodeReader reader;
|
||||
reader.parse_buffer(gcode, [&] (GCodeReader& self, const GCodeReader::GCodeLine& line) {
|
||||
if (first_object_peak_z != 0.0 || !line.extruding(self)) return; // ignore travels (e.g. start-gcode Z lift)
|
||||
if (running_peak > 1.0 && self.z() < 1.0)
|
||||
first_object_peak_z = running_peak;
|
||||
else
|
||||
running_peak = std::max(running_peak, static_cast<double>(self.z()));
|
||||
});
|
||||
|
||||
REQUIRE_THAT(first_object_peak_z, Catch::Matchers::WithinAbs(20.0, 0.3));
|
||||
}
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
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") {
|
||||
SCENARIO("PrintObject: object layer heights", "[PrintObject]") {
|
||||
GIVEN("A 20mm cube") {
|
||||
WHEN("sliced with a 2mm layer height and a 3mm nozzle") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
|
||||
{ "first_layer_height", 2 },
|
||||
{ "layer_height", 2 },
|
||||
{ "nozzle_diameter", 3 }
|
||||
{ "initial_layer_print_height", 2 },
|
||||
{ "layer_height", 2 },
|
||||
{ "nozzle_diameter", 3 }
|
||||
});
|
||||
ConstLayerPtrsAdaptor layers = print.objects().front()->layers();
|
||||
THEN("The output vector has 10 entries") {
|
||||
@@ -25,65 +25,84 @@ SCENARIO("PrintObject: object layer heights", "[PrintObject][.]") {
|
||||
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) == Catch::Approx(2.0));
|
||||
REQUIRE_THAT(layers[i]->print_z - last, Catch::Matchers::WithinAbs(2.0, 1e-4));
|
||||
last = layers[i]->print_z;
|
||||
}
|
||||
}
|
||||
}
|
||||
WHEN("generate_object_layers() is called for 10mm layer heights and nozzle diameter of 11mm") {
|
||||
WHEN("sliced with a 10mm layer height and an 11mm nozzle") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
|
||||
{ "first_layer_height", 2 },
|
||||
{ "layer_height", 10 },
|
||||
{ "nozzle_diameter", 11 }
|
||||
{ "initial_layer_print_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 == Catch::Approx(2.0));
|
||||
REQUIRE_THAT(layers.front()->print_z, Catch::Matchers::WithinAbs(2.0, 1e-4));
|
||||
}
|
||||
AND_THEN("Layer 1 is at 12mm") {
|
||||
REQUIRE(layers[1]->print_z == Catch::Approx(12.0));
|
||||
REQUIRE_THAT(layers[1]->print_z, Catch::Matchers::WithinAbs(12.0, 1e-4));
|
||||
}
|
||||
}
|
||||
WHEN("generate_object_layers() is called for 15mm layer heights and nozzle diameter of 16mm") {
|
||||
WHEN("sliced with a 15mm layer height and a 16mm nozzle") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
|
||||
{ "first_layer_height", 2 },
|
||||
{ "layer_height", 15 },
|
||||
{ "nozzle_diameter", 16 }
|
||||
{ "initial_layer_print_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 == Catch::Approx(2.0));
|
||||
REQUIRE_THAT(layers[0]->print_z, Catch::Matchers::WithinAbs(2.0, 1e-4));
|
||||
}
|
||||
AND_THEN("Layer 1 is at 17mm") {
|
||||
REQUIRE(layers[1]->print_z == Catch::Approx(17.0));
|
||||
REQUIRE_THAT(layers[1]->print_z, Catch::Matchers::WithinAbs(17.0, 1e-4));
|
||||
}
|
||||
}
|
||||
#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) == Catch::Approx(5.0));
|
||||
last = layers[i]->print_z;
|
||||
}
|
||||
WHEN("layer height exceeds the nozzle diameter") {
|
||||
// Orca does not clamp an over-large layer height to the nozzle; it
|
||||
// rejects the slice during flow computation. Pin that behavior.
|
||||
THEN("Slicing is rejected") {
|
||||
Slic3r::Print print;
|
||||
REQUIRE_THROWS(Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
|
||||
{ "initial_layer_print_height", 0.3 },
|
||||
{ "layer_height", 0.5 },
|
||||
{ "nozzle_diameter", 0.4 }
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, { { "sparse_infill_density", 0 } });
|
||||
const PrintObject &object = *print.objects().front();
|
||||
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);
|
||||
}
|
||||
}
|
||||
WHEN("wall_loops is set to 3") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, {
|
||||
{ "sparse_infill_density", 0 },
|
||||
{ "wall_loops", 3 }
|
||||
});
|
||||
const PrintObject &object = *print.objects().front();
|
||||
THEN("Every layer in region 0 has 3 perimeter loops") {
|
||||
for (const Layer *layer : object.layers())
|
||||
REQUIRE(layer->regions().front()->perimeters.items_count() == 3);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,19 @@
|
||||
#include "libslic3r/GCodeReader.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/Geometry/ConvexHull.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#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)
|
||||
/// Helper method to find the tool used for the brim (always the first extrusion).
|
||||
[[maybe_unused]] static int get_brim_tool(const std::string &gcode)
|
||||
{
|
||||
int brim_tool = -1;
|
||||
int tool = -1;
|
||||
@@ -29,16 +32,13 @@ static int get_brim_tool(const std::string &gcode)
|
||||
return brim_tool;
|
||||
}
|
||||
|
||||
TEST_CASE("Skirt height is honored", "[Skirt][.]") {
|
||||
TEST_CASE("Skirt height is honored", "[SkirtBrim]") {
|
||||
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%" }
|
||||
{ "skirt_loops", 1 },
|
||||
{ "skirt_height", 5 },
|
||||
{ "wall_loops", 0 },
|
||||
{ "gcode_comments", true }
|
||||
});
|
||||
|
||||
std::string gcode;
|
||||
@@ -49,75 +49,38 @@ TEST_CASE("Skirt height is honored", "[Skirt][.]") {
|
||||
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() == Catch::Approx(support_speed)) {
|
||||
layers_with_skirt[self.z()] = 1;
|
||||
}
|
||||
});
|
||||
REQUIRE(layers_with_skirt.size() == (size_t)config.opt_int("skirt_height"));
|
||||
REQUIRE(layers_with_role(gcode, "skirt").size() == (size_t)config.opt_int("skirt_height"));
|
||||
}
|
||||
|
||||
SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim][.]") {
|
||||
SCENARIO("Skirt and brim generation", "[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 },
|
||||
{ "initial_layer_print_height", 0.3 },
|
||||
{ "gcode_comments", true },
|
||||
// avoid altering speeds unexpectedly
|
||||
{ "cooling", false },
|
||||
{ "first_layer_speed", "100%" },
|
||||
{ "slow_down_for_layer_cooling", false },
|
||||
{ "initial_layer_speed", "100%" },
|
||||
// remove noise from top/solid layers
|
||||
{ "top_solid_layers", 0 },
|
||||
{ "bottom_solid_layers", 1 },
|
||||
{ "start_gcode", "T[initial_tool]\n" }
|
||||
{ "top_shell_layers", 0 },
|
||||
{ "bottom_shell_layers", 1 },
|
||||
{ "machine_start_gcode", "T[initial_tool]\n" }
|
||||
});
|
||||
|
||||
WHEN("Brim width is set to 5") {
|
||||
config.set_deserialize_strict({
|
||||
{ "perimeters", 0 },
|
||||
{ "skirts", 0 },
|
||||
{ "brim_width", 5 }
|
||||
{ "wall_loops", 0 },
|
||||
{ "skirt_loops", 0 },
|
||||
{ "brim_type", "outer_only" },
|
||||
{ "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() == Catch::Approx(0.3) || line.new_Z(self) == Catch::Approx(0.3)) {
|
||||
if (line.extruding(self) && self.f() == Catch::Approx(support_speed)) {
|
||||
brim_generated = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
REQUIRE(brim_generated);
|
||||
REQUIRE(! layers_with_role(gcode, "brim").empty());
|
||||
}
|
||||
}
|
||||
|
||||
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!
|
||||
@@ -154,10 +117,11 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim][.]") {
|
||||
|
||||
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 }
|
||||
});
|
||||
{ "skirt_loops", 0 },
|
||||
{ "initial_layer_line_width", 0.5 },
|
||||
{ "brim_type", "outer_only" },
|
||||
{ "brim_width", 1 }
|
||||
});
|
||||
THEN("2 brim lines") {
|
||||
Slic3r::Print print;
|
||||
Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, config);
|
||||
@@ -203,68 +167,68 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim][.]") {
|
||||
|
||||
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" }
|
||||
{ "layer_height", 0.4 },
|
||||
{ "initial_layer_print_height", 0.4 },
|
||||
{ "skirt_loops", 1 },
|
||||
{ "skirt_distance", 0 },
|
||||
{ "enable_support", 1 },
|
||||
{ "brim_type", "outer_only" },
|
||||
{ "brim_width", 5 }
|
||||
});
|
||||
|
||||
THEN("overhang generates?") {
|
||||
//FIXME does it make sense?
|
||||
REQUIRE(! Slic3r::Test::slice({TestMesh::overhang}, config).empty());
|
||||
THEN("Support and brim are both emitted") {
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::overhang}, config);
|
||||
REQUIRE(! layers_with_role(gcode, "support").empty());
|
||||
REQUIRE(! layers_with_role(gcode, "brim").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() == Catch::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 == Catch::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() == Catch::Approx(0.3) || line.new_Z(self) == Catch::Approx(0.3)) {
|
||||
if (line.extruding(self) && self.f() == Catch::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("an object with support is surrounded by a skirt") {
|
||||
config.set_deserialize_strict({
|
||||
{ "enable_support", 1 },
|
||||
{ "skirt_loops", 1 },
|
||||
{ "skirt_distance", 2 },
|
||||
{ "brim_type", "no_brim" },
|
||||
{ "z_hop", 0 }
|
||||
});
|
||||
THEN("the skirt is long enough to enclose the object and its support") {
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::overhang}, config);
|
||||
const double first_layer_z = config.opt_float("initial_layer_print_height");
|
||||
|
||||
// On the first layer, accumulate the skirt loop length and collect the
|
||||
// object + support extrusion points; the skirt must enclose them.
|
||||
double skirt_length = 0.0;
|
||||
Points footprint;
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(gcode, [&] (GCodeReader& self, const GCodeReader::GCodeLine& line) {
|
||||
if (! line.extruding(self) || line.dist_XY(self) <= 0 || std::abs(self.z() - first_layer_z) > 0.01)
|
||||
return;
|
||||
if (line.comment().find("skirt") != std::string_view::npos)
|
||||
skirt_length += line.dist_XY(self);
|
||||
else
|
||||
footprint.push_back(Point::new_scale(line.new_X(self), line.new_Y(self)));
|
||||
});
|
||||
|
||||
const double hull_perimeter = unscale<double>(Geometry::convex_hull(footprint).split_at_first_point().length());
|
||||
REQUIRE(hull_perimeter > 0.0); // guard against an empty footprint passing trivially
|
||||
REQUIRE(skirt_length > hull_perimeter);
|
||||
}
|
||||
}
|
||||
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());
|
||||
// One skirt loop around a 20mm cube is ~88mm, so 500mm forces extra loops.
|
||||
config.set_deserialize_strict({
|
||||
{ "skirt_loops", 1 },
|
||||
{ "min_skirt_length", 500 }
|
||||
});
|
||||
THEN("The skirt is extended to at least the minimum length") {
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
|
||||
double skirt_length = 0.0;
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(gcode, [&skirt_length] (GCodeReader& self, const GCodeReader::GCodeLine& line) {
|
||||
if (line.extruding(self) && line.comment().find("skirt") != std::string_view::npos)
|
||||
skirt_length += line.dist_XY(self);
|
||||
});
|
||||
REQUIRE(skirt_length >= 500.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,24 +8,41 @@
|
||||
using namespace Slic3r::Test;
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("SupportMaterial: Three raft layers created", "[SupportMaterial][.]")
|
||||
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 }
|
||||
{ "enable_support", 1 },
|
||||
{ "raft_layers", 3 }
|
||||
});
|
||||
REQUIRE(print.objects().front()->support_layers().size() == 3);
|
||||
}
|
||||
|
||||
SCENARIO("SupportMaterial: support_layers_z and contact_distance", "[SupportMaterial][.]")
|
||||
TEST_CASE("SupportMaterial: enforced support layers are generated", "[SupportMaterial]")
|
||||
{
|
||||
// enforce_support_layers forces support on the first N layers even with support off.
|
||||
Slic3r::Print baseline;
|
||||
Slic3r::Test::init_and_process_print({ TestMesh::overhang }, baseline, {
|
||||
{ "enable_support", 0 },
|
||||
{ "enforce_support_layers", 0 }
|
||||
});
|
||||
REQUIRE(baseline.objects().front()->support_layers().empty());
|
||||
|
||||
Slic3r::Print enforced;
|
||||
Slic3r::Test::init_and_process_print({ TestMesh::overhang }, enforced, {
|
||||
{ "enable_support", 0 },
|
||||
{ "enforce_support_layers", 100 }
|
||||
});
|
||||
REQUIRE(enforced.objects().front()->support_layers().size() > 0);
|
||||
}
|
||||
|
||||
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)
|
||||
auto check = [](Slic3r::Print &print, bool &first_support_layer_height_ok, bool &layer_height_minimum_ok, bool &layer_height_maximum_ok)
|
||||
{
|
||||
ConstSupportLayerPtrsAdaptor support_layers = print.objects().front()->support_layers();
|
||||
|
||||
@@ -43,196 +60,36 @@ SCENARIO("SupportMaterial: support_layers_z and contact_distance", "[SupportMate
|
||||
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") {
|
||||
WHEN("Layer height = 0.2 and 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 },
|
||||
{ "enable_support", 1 },
|
||||
{ "layer_height", 0.2 },
|
||||
{ "initial_layer_print_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); }
|
||||
bool first_layer_ok, layer_min_ok, layer_max_ok;
|
||||
check(print, first_layer_ok, layer_min_ok, layer_max_ok);
|
||||
THEN("First layer height is honored") { REQUIRE(first_layer_ok == true); }
|
||||
THEN("No null or negative support layers") { REQUIRE(layer_min_ok == true); }
|
||||
THEN("No layers thicker than nozzle diameter") { REQUIRE(layer_max_ok == true); }
|
||||
}
|
||||
WHEN("Layer height = 0.2 and, first layer height = 0.3") {
|
||||
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 },
|
||||
{ "enable_support", 1 },
|
||||
{ "layer_height", 0.2 },
|
||||
{ "initial_layer_print_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); }
|
||||
bool first_layer_ok, layer_min_ok, layer_max_ok;
|
||||
check(print, first_layer_ok, layer_min_ok, layer_max_ok);
|
||||
THEN("First layer height is honored") { REQUIRE(first_layer_ok == true); }
|
||||
THEN("No null or negative support layers") { REQUIRE(layer_min_ok == true); }
|
||||
THEN("No layers thicker than nozzle diameter") { REQUIRE(layer_max_ok == 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
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/Format/OBJ.hpp>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#if defined(WIN32) || defined(_WIN32)
|
||||
#define PATH_SEPARATOR R"(\)"
|
||||
#else
|
||||
@@ -20,4 +22,26 @@ inline Slic3r::TriangleMesh load_model(const std::string &obj_filename)
|
||||
return mesh;
|
||||
}
|
||||
|
||||
// RAII holder for a unique temporary file path, removed when the guard goes out
|
||||
// of scope so a failing assertion never leaks it. Uses the system temp dir with
|
||||
// a unique name (parallel-safe, cross-platform). The file itself is created by
|
||||
// whoever writes to path()/string(); this only reserves the name and cleans up.
|
||||
class ScopedTemporaryFile
|
||||
{
|
||||
public:
|
||||
explicit ScopedTemporaryFile(const std::string &extension = ".tmp")
|
||||
: m_path(boost::filesystem::temp_directory_path()
|
||||
/ boost::filesystem::unique_path("orca-%%%%-%%%%-%%%%" + extension))
|
||||
{}
|
||||
~ScopedTemporaryFile() { boost::system::error_code ec; boost::filesystem::remove(m_path, ec); }
|
||||
ScopedTemporaryFile(const ScopedTemporaryFile &) = delete;
|
||||
ScopedTemporaryFile &operator=(const ScopedTemporaryFile &) = delete;
|
||||
|
||||
const boost::filesystem::path &path() const { return m_path; }
|
||||
std::string string() const { return m_path.string(); }
|
||||
|
||||
private:
|
||||
boost::filesystem::path m_path;
|
||||
};
|
||||
|
||||
#endif // SLIC3R_TEST_UTILS
|
||||
|
||||
Reference in New Issue
Block a user