Files
OrcaSlicer/tests/fff_print/test_printobject.cpp
raistlin7447 29f31b9b38 fff_print: a maintainable testing framework (proposal + coverage) (#14426)
* fix: initialize Print::m_isBBLPrinter

Built outside the GUI/CLI (headless tests, embedded use) the member was read
uninitialized: is_BBL_printer()/wipe_tower_type() feed it into ToolOrdering,
which then non-deterministically dropped per-feature filament assignments.
Default it to false, the value the GUI and CLI already assign for non-Bambu
printers.

* docs(test): add the fff_print testing contract

tests/fff_print/README.md codifies how the suite is organized: one file per
subsystem (each owning both in-memory and emitted-G-code assertions), flat
behavioral test names with a single [Subsystem] tag, a robust-tests guide,
the shared helpers, and an add-a-test checklist. Linked from tests/CLAUDE.md.

* test(fff_print): reorganize the suite to the contract and add coverage

Bring every subsystem into one file per the README: rename the test_data
harness to test_helpers; consolidate skirt/brim; split multi-filament and
cooling into their own files; disperse the test_printgcode grab-bag and the
end-to-end smoke scenario into focused tests; fold test_gcode into
test_gcodewriter. Standardize names and tags, align cube tests on the cube()
helper, and de-qualify the flagship files.

New coverage: multi-filament per-feature and per-object routing; a skirt/brim
behavior matrix (the #14333 rework, including brim ears, with regression
coverage for #14319 and #14366); resolved extrusion-width and config
comments; custom-G-code placeholders; fan control and speed-marker
consumption.

Re-enable three slice tests previously tagged [NotWorking]: the clipper
"Coordinate outside allowed range" error that disabled them was specific to a
past CI runner environment and no longer reproduces.

* test(fff_print): tag arm64-flaky skirt/brim tests NotWorking

Four skirt/brim slice tests intermittently throw ClipperLib's "Coordinate
outside allowed range" on the macOS and Windows arm64 CI toolchains (an FP
divergence, not a slicing bug; see PR #14207). Linux x86_64 and aarch64 are
unaffected. Tag them [NotWorking] so ctest -LE NotWorking skips them.

* test(fff_print): re-enable the arm64 skirt/brim tests

These were tagged [NotWorking] as a stopgap when myfork's daily-driver build
combined them with the cross-platform CI on a base that predated upstream's
m_origin fix (99dea01cc3). With upstream merged in, Print::m_origin is
initialized and the "Coordinate outside allowed range" throw is gone, so the
tests pass on macOS/Windows arm64. Drop the tags.
2026-07-06 22:24:24 +08:00

133 lines
5.4 KiB
C++

#include <catch2/catch_all.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Print.hpp"
#include "libslic3r/Layer.hpp"
#include "libslic3r/GCodeReader.hpp"
#include "test_helpers.hpp"
#include <iterator>
#include <set>
using namespace Slic3r;
using namespace Slic3r::Test;
SCENARIO("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({cube(20)}, print, {
{ "initial_layer_print_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_THAT(layers[i]->print_z - last, Catch::Matchers::WithinAbs(2.0, 1e-4));
last = layers[i]->print_z;
}
}
}
WHEN("sliced with a 10mm layer height and an 11mm nozzle") {
Slic3r::Print print;
Slic3r::Test::init_and_process_print({cube(20)}, print, {
{ "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_THAT(layers.front()->print_z, Catch::Matchers::WithinAbs(2.0, 1e-4));
}
AND_THEN("Layer 1 is at 12mm") {
REQUIRE_THAT(layers[1]->print_z, Catch::Matchers::WithinAbs(12.0, 1e-4));
}
}
WHEN("sliced with a 15mm layer height and a 16mm nozzle") {
Slic3r::Print print;
Slic3r::Test::init_and_process_print({cube(20)}, print, {
{ "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_THAT(layers[0]->print_z, Catch::Matchers::WithinAbs(2.0, 1e-4));
}
AND_THEN("Layer 1 is at 17mm") {
REQUIRE_THAT(layers[1]->print_z, Catch::Matchers::WithinAbs(17.0, 1e-4));
}
}
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({cube(20)}, print, {
{ "initial_layer_print_height", 0.3 },
{ "layer_height", 0.5 },
{ "nozzle_diameter", 0.4 }
}));
}
}
}
}
SCENARIO("Perimeter generation", "[PrintObject]") {
GIVEN("20mm cube and default config") {
WHEN("make_perimeters() is called") {
Slic3r::Print print;
Slic3r::Test::init_and_process_print({cube(20)}, 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({cube(20)}, 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);
}
}
}
}
TEST_CASE("Initial layer height is honored", "[PrintObject]")
{
const std::string gcode = Slic3r::Test::slice({cube(20)}, {
{ "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));
}