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.
This commit is contained in:
fa1k3
2026-10-13 19:36:47 -05:00
committed by GitHub
parent db9917e722
commit 4f7a796546
2 changed files with 65 additions and 3 deletions
+9 -3
View File
@@ -2235,9 +2235,11 @@ StringObjectException Print::validate(std::vector<StringObjectException> *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<StringObjectException> *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),
+56
View File
@@ -26,6 +26,7 @@
#include <catch2/catch_all.hpp>
#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<double, std::string>({
{ 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<double, bool>({ { 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);
}