From f32591ed64cc71d118d15f60db784d1400cfe60d Mon Sep 17 00:00:00 2001 From: fa1k3 <268878418+fa1k3@users.noreply.github.com> Date: Wed, 14 Oct 2026 02:36:47 +0200 Subject: [PATCH] Check the real print height: count the Z shrinkage compensation once and include the raft (#16313) The build height check in Print::validate() compared object layers that already include the Z shrinkage compensation against printable_height divided by that compensation. That counted the compensation twice, so objects that fit were rejected. The check also left out the raft, so an object whose top plus the raft is above printable_height passed validation. The check now adds object_print_z_min to the top layer and applies the compensation once. The shrinkage message is shown only when the object fits without the compensation. Belt printers apply the compensation to the raw model height, since their height does not come from the layers. --- src/libslic3r/Print.cpp | 12 ++++++-- tests/fff_print/test_print.cpp | 56 ++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 1ac7448cee..645d8f416d 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -2235,9 +2235,11 @@ StringObjectException Print::validate(std::vector *warnin //FIXME It is quite expensive to generate object layers just to get the print height! auto layers = generate_object_layers(print_object.slicing_parameters(), layer_height_profile(print_object_idx), print_object.config().precise_z_height.value); if (!layers.empty()) { - effective_max_z = layers.back(); + // The object layers start above the raft. + const double raft_z = print_object.slicing_parameters().object_print_z_min; + effective_max_z = raft_z + layers.back(); last_layer_below_max = layers.size() >= 2 && - 0.5 * (layers[layers.size() - 2] + layers.back()) <= this->config().printable_height + EPSILON; + raft_z + 0.5 * (layers[layers.size() - 2] + layers.back()) <= this->config().printable_height + EPSILON; have_height = true; } } @@ -2245,7 +2247,11 @@ StringObjectException Print::validate(std::vector *warnin if (!have_height) continue; - if (shrinkage_compensation_z != 1. && effective_max_z > (this->config().printable_height / shrinkage_compensation_z + EPSILON)) { + // The layers of a regular printer already carry the Z shrinkage compensation, the belt height does not. + const double compensated_max_z = belt_printer ? effective_max_z * shrinkage_compensation_z : effective_max_z; + const double uncompensated_max_z = belt_printer ? effective_max_z : print_object.slicing_parameters().object_print_z_uncompensated_max; + if (shrinkage_compensation_z != 1. && compensated_max_z > this->config().printable_height + EPSILON && + uncompensated_max_z <= this->config().printable_height + EPSILON) { // The object exceeds the maximum build volume height because of shrinkage compensation. return StringObjectException{ Slic3r::format(_u8L("While the object %1% itself fits the build volume, it exceeds the maximum build volume height because of material shrinkage compensation."), print_object.model_object()->name), diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index 4b76de4b55..1b81eea0ea 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -26,6 +26,7 @@ #include #include "libslic3r/Print.hpp" +#include "libslic3r/Slicing.hpp" #include "libslic3r/Layer.hpp" #include "libslic3r/BuildVolume.hpp" #include "libslic3r/Support/TreeModelVolumes.hpp" @@ -1596,3 +1597,58 @@ TEST_CASE("Belt supports reach the belt under a leading overhang", "[Print][belt CHECK(lowest->print_z - floor_under_lowest > -0.2 - EPSILON); } } + +TEST_CASE("The build height check counts the Z shrinkage compensation once", "[Print][validate][Regression]") +{ + // printable_height is 100 mm and Z shrinks to 95 %, so the object is sliced 100 / 95 times taller. + // 92 mm become about 96.8 mm and fit; 97 mm become about 102.1 mm and only the compensation makes them too tall; 101 mm are too tall on their own. + const auto [height, expected] = GENERATE(table({ + { 92., "" }, + { 97., "because of material shrinkage compensation" }, + { 101., "exceeds the maximum build volume height." }, + })); + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "printable_height", 100. }, + { "filament_shrinkage_compensation_z", "95%" }, + { "layer_height", 0.2 }, + { "initial_layer_print_height", 0.2 }, + { "layer_change_gcode", "G92 E0\n" }, // validate() relative-E reset + }); + + Print print; + Model model; + init_print({ make_cube(20., 20., height) }, print, model, config); + const std::string error = print.validate().string; + INFO(error); + if (expected.empty()) + CHECK(error.empty()); + else + CHECK(error.find(expected) != std::string::npos); +} + +TEST_CASE("The build height check counts the raft", "[Print][validate][Regression]") +{ + // printable_height is 100 mm; three raft layers lift the object by 0.9 mm. + const auto [height, fits] = GENERATE(table({ { 98.8, true }, { 99.8, false } })); + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "printable_height", 100. }, + { "raft_layers", 3 }, + { "layer_height", 0.2 }, + { "initial_layer_print_height", 0.2 }, + { "layer_change_gcode", "G92 E0\n" }, // validate() relative-E reset + }); + + Print print; + Model model; + init_print({ make_cube(20., 20., height) }, print, model, config); + const SlicingParameters &slicing_params = print.objects().front()->slicing_parameters(); + REQUIRE_THAT(slicing_params.object_print_z_min, Catch::Matchers::WithinAbs(0.9, EPSILON)); + const std::string error = print.validate().string; + INFO(error); + if (fits) + CHECK(error.empty()); + else + CHECK(error.find("maximum build volume height") != std::string::npos); +}