Apply filament shrinkage compensation on the first slice (#16312)

Print::apply() decided the shrinkage compensation from the existing PrintObjects and their regions. On the first apply() they do not exist yet, so the first slice was not scaled. The CLI calls apply() only once, so filament_shrink and filament_shrinkage_compensation_z had no effect there.

The compensation is now decided before the PrintObjects are built, from the filaments the model uses, and stored in Print. When the filaments have different shrinkage values, every filament assigned to a feature, height range, modifier, support, tool change or the wipe tower counts, even if it does not end up printing. If they disagree, compensation stays off with the existing warning.
This commit is contained in:
fa1k3
2026-10-14 09:39:08 -05:00
committed by GitHub
parent 888012c30e
commit f1658855c8
5 changed files with 368 additions and 92 deletions
+71 -87
View File
@@ -149,6 +149,8 @@ void Print::clear()
m_print_regions.clear();
m_model.clear_objects();
m_statistics_by_extruder_count.clear();
m_same_shrinkage_compensations = false;
m_shrinkage_compensation = Vec3d::Ones();
}
bool Print::has_tpu_filament() const
@@ -584,32 +586,36 @@ std::vector<unsigned int> Print::object_extruders() const
for (const PrintRegion &region : object->all_regions())
region.collect_object_printing_extruders(*this, extruders);
for (const PrintObject* object : m_objects) {
const ModelObject* mo = object->model_object();
for (const ModelVolume* mv : mo->volumes) {
std::vector<int> volume_extruders = mv->get_extruders();
for (int extruder : volume_extruders) {
assert(extruder > 0);
extruders.push_back(extruder - 1);
}
}
// layer range
for (auto layer_range : mo->layer_config_ranges) {
if (layer_range.second.has("extruder")) {
//BBS: actually when user doesn't change filament by height range(value is default 0), height range should not save key "extruder".
//Don't know why height range always save key "extruder" because of no change(should only save difference)...
//Add protection here to avoid overflow
auto value = layer_range.second.option("extruder")->getInt();
if (value > 0)
extruders.push_back(value - 1);
}
}
}
for (const PrintObject* object : m_objects)
append_model_object_extruders(*object->model_object(), extruders);
sort_remove_duplicates(extruders);
return extruders;
}
// Appends 0-based indices of the filaments assigned to the volumes of model_object (painting included) and to its height ranges.
void Print::append_model_object_extruders(const ModelObject &mo, std::vector<unsigned int> &extruders)
{
for (const ModelVolume* mv : mo.volumes) {
std::vector<int> volume_extruders = mv->get_extruders();
for (int extruder : volume_extruders) {
assert(extruder > 0);
extruders.push_back(extruder - 1);
}
}
// layer range
for (auto layer_range : mo.layer_config_ranges) {
if (layer_range.second.has("extruder")) {
//BBS: actually when user doesn't change filament by height range(value is default 0), height range should not save key "extruder".
//Don't know why height range always save key "extruder" because of no change(should only save difference)...
//Add protection here to avoid overflow
auto value = layer_range.second.option("extruder")->getInt();
if (value > 0)
extruders.push_back(value - 1);
}
}
}
// returns 0-based indices of used extruders
std::vector<unsigned int> Print::support_material_extruders() const
{
@@ -619,22 +625,8 @@ std::vector<unsigned int> Print::support_material_extruders() const
auto num_extruders = (unsigned int)m_config.filament_diameter.size();
for (PrintObject *object : m_objects) {
if (object->has_support_material()) {
assert(object->config().support_filament >= 0);
if (object->config().support_filament == 0)
support_uses_current_extruder = true;
else {
unsigned int i = (unsigned int)object->config().support_filament - 1;
extruders.emplace_back((i >= num_extruders) ? 0 : i);
}
assert(object->config().support_interface_filament >= 0);
if (object->config().support_interface_filament == 0)
support_uses_current_extruder = true;
else {
unsigned int i = (unsigned int)object->config().support_interface_filament - 1;
extruders.emplace_back((i >= num_extruders) ? 0 : i);
}
}
if (object->has_support_material() && append_support_extruders(object->config(), num_extruders, extruders))
support_uses_current_extruder = true;
}
if (support_uses_current_extruder)
@@ -645,31 +637,61 @@ std::vector<unsigned int> Print::support_material_extruders() const
return extruders;
}
// Appends 0-based indices of the support and support interface filaments of an object printing support material.
// Returns true if support or support interface is printed with the filament of the object.
bool Print::append_support_extruders(const PrintObjectConfig &config, unsigned int num_extruders, std::vector<unsigned int> &extruders)
{
bool uses_current_extruder = false;
assert(config.support_filament >= 0);
if (config.support_filament == 0)
uses_current_extruder = true;
else {
unsigned int i = (unsigned int)config.support_filament - 1;
extruders.emplace_back((i >= num_extruders) ? 0 : i);
}
assert(config.support_interface_filament >= 0);
if (config.support_interface_filament == 0)
uses_current_extruder = true;
else {
unsigned int i = (unsigned int)config.support_interface_filament - 1;
extruders.emplace_back((i >= num_extruders) ? 0 : i);
}
return uses_current_extruder;
}
// returns 0-based indices of used extruders
std::vector<unsigned int> Print::extruders(bool conside_custom_gcode) const
{
std::vector<unsigned int> extruders = this->object_extruders();
append(extruders, this->support_material_extruders());
if (conside_custom_gcode) {
//BBS
int num_extruders = m_config.filament_colour.size();
if (m_model.plates_custom_gcodes.find(m_model.curr_plate_index) != m_model.plates_custom_gcodes.end()) {
for (auto item : m_model.plates_custom_gcodes.at(m_model.curr_plate_index).gcodes) {
if (item.type == CustomGCode::Type::ToolChange && item.extruder <= num_extruders)
extruders.push_back((unsigned int)(item.extruder - 1));
}
if (conside_custom_gcode)
this->append_tool_change_extruders(extruders);
this->append_wipe_tower_extruder(extruders);
sort_remove_duplicates(extruders);
return extruders;
}
// Appends 0-based indices of the filaments the tool changes of the current plate switch to.
void Print::append_tool_change_extruders(std::vector<unsigned int> &extruders) const
{
//BBS
int num_extruders = m_config.filament_colour.size();
if (m_model.plates_custom_gcodes.find(m_model.curr_plate_index) != m_model.plates_custom_gcodes.end()) {
for (auto item : m_model.plates_custom_gcodes.at(m_model.curr_plate_index).gcodes) {
if (item.type == CustomGCode::Type::ToolChange && item.extruder <= num_extruders)
extruders.push_back((unsigned int)(item.extruder - 1));
}
}
}
void Print::append_wipe_tower_extruder(std::vector<unsigned int> &extruders) const
{
// If a wipe tower filament is explicitly set, ensure it participates in tool ordering.
if (has_wipe_tower() && config().wipe_tower_filament != 0 && extruders.size() > 1) {
assert(config().wipe_tower_filament > 0 && config().wipe_tower_filament <= int(config().filament_diameter.size()));
extruders.emplace_back(config().wipe_tower_filament - 1); // config value is 1-based
}
sort_remove_duplicates(extruders);
return extruders;
}
unsigned int Print::num_object_instances() const
@@ -5652,44 +5674,6 @@ std::string PrintStatistics::finalize_output_path(const std::string &path_in) co
return final_path;
}
// Orca: Implement prusa's filament shrink compensation approach
// Returns if all used filaments have same shrinkage compensations.
bool Print::has_same_shrinkage_compensations() const {
const std::vector<unsigned int> extruders = this->extruders();
if (extruders.empty())
return false;
const double filament_shrinkage_compensation_xy = m_config.filament_shrink.get_at(extruders.front());
const double filament_shrinkage_compensation_z = m_config.filament_shrinkage_compensation_z.get_at(extruders.front());
for (unsigned int extruder : extruders) {
if (filament_shrinkage_compensation_xy != m_config.filament_shrink.get_at(extruder) ||
filament_shrinkage_compensation_z != m_config.filament_shrinkage_compensation_z.get_at(extruder)) {
return false;
}
}
return true;
}
// Orca: Implement prusa's filament shrink compensation approach, but amended so 100% from the user is the equivalent to 0 in orca.
// Returns scaling for each axis representing shrinkage compensations in each axis.
Vec3d Print::shrinkage_compensation() const
{
if (!this->has_same_shrinkage_compensations())
return Vec3d::Ones();
const unsigned int first_extruder = this->extruders().front();
const double xy_shrinkage_percent = m_config.filament_shrink.get_at(first_extruder);
const double z_shrinkage_percent = m_config.filament_shrinkage_compensation_z.get_at(first_extruder);
const double xy_compensation = 100.0 / xy_shrinkage_percent;
const double z_compensation = 100.0 / z_shrinkage_percent;
return { xy_compensation, xy_compensation, z_compensation };
}
const std::string PrintStatistics::FilamentUsedG = "filament used [g]";
const std::string PrintStatistics::FilamentUsedGMask = "; filament used [g] =";
+17 -5
View File
@@ -503,9 +503,12 @@ public:
std::vector<std::reference_wrapper<const PrintRegion>> all_regions() const;
const PrintObjectRegions* shared_regions() const throw() { return m_shared_regions; }
bool has_support() const { return m_config.enable_support || m_config.enforce_support_layers > 0; }
bool has_raft() const { return m_config.raft_layers > 0; }
bool has_support_material() const { return this->has_support() || this->has_raft(); }
static bool has_support(const PrintObjectConfig &config) { return config.enable_support || config.enforce_support_layers > 0; }
static bool has_raft(const PrintObjectConfig &config) { return config.raft_layers > 0; }
static bool has_support_material(const PrintObjectConfig &config) { return has_support(config) || has_raft(config); }
bool has_support() const { return has_support(m_config); }
bool has_raft() const { return has_raft(m_config); }
bool has_support_material() const { return has_support_material(m_config); }
// Checks if the model object is painted using the multi-material painting gizmo.
bool is_mm_painted() const { return this->model_object()->is_mm_painted(); }
// Checks if the model object is painted using the fuzzy skin painting gizmo.
@@ -1385,10 +1388,11 @@ public:
size_t config_index_generation() const { return m_config_index_generation; }
// Orca: Implement prusa's filament shrink compensation approach
// Both are decided by apply() from the filaments the model uses, before the PrintObject transformations are built.
// Returns if all used filaments have same shrinkage compensations.
bool has_same_shrinkage_compensations() const;
bool has_same_shrinkage_compensations() const { return m_same_shrinkage_compensations; }
// Returns scaling for each axis representing shrinkage compensations in each axis.
Vec3d shrinkage_compensation() const;
Vec3d shrinkage_compensation() const { return m_shrinkage_compensation; }
std::tuple<float, float> object_skirt_offset(double margin_height = 0) const;
@@ -1484,11 +1488,19 @@ private:
s_slicing_pipeline_hook_fn(*this, object, step);
}
static void append_model_object_extruders(const ModelObject &mo, std::vector<unsigned int> &extruders);
static bool append_support_extruders(const PrintObjectConfig &config, unsigned int num_extruders, std::vector<unsigned int> &extruders);
void append_tool_change_extruders(std::vector<unsigned int> &extruders) const;
void append_wipe_tower_extruder(std::vector<unsigned int> &extruders) const;
void update_shrinkage_compensation(size_t num_extruders, std::vector<int> &variant_index);
PrintConfig m_config;
PrintObjectConfig m_default_object_config;
PrintRegionConfig m_default_region_config;
PrintObjectPtrs m_objects;
PrintRegionPtrs m_print_regions;
bool m_same_shrinkage_compensations { false };
Vec3d m_shrinkage_compensation { Vec3d::Ones() };
//SoftFever
bool m_isBBLPrinter = false;
+76
View File
@@ -25,6 +25,7 @@
#include <vector>
#include <cstddef>
#include <initializer_list>
#include <iterator>
#include <set>
#include <cstdlib>
#include <functional>
@@ -1257,6 +1258,80 @@ static PrintObjectRegions* generate_print_object_regions(
return out.release();
}
// Orca: The shrinkage compensation is baked into the PrintObject transformations, so it has to be known before apply()
// creates them. Derive it from the filaments the new model prints with instead of from the existing PrintObjects and
// their regions, which do not exist yet on the first apply(). Every feature filament of every part, height range and
// modifier counts, whether the feature ends up printing or not, so filaments that disagree switch the compensation
// off rather than letting a wrong compensation through.
void Print::update_shrinkage_compensation(size_t num_extruders, std::vector<int> &variant_index)
{
const auto set_compensation = [this](unsigned int extruder) {
// 100% from the user is the equivalent to 0 in orca.
const double xy_compensation = 100.0 / m_config.filament_shrink.get_at(extruder);
const double z_compensation = 100.0 / m_config.filament_shrinkage_compensation_z.get_at(extruder);
m_shrinkage_compensation = { xy_compensation, xy_compensation, z_compensation };
};
const auto same_shrinkage = [this](unsigned int a, unsigned int b) {
return m_config.filament_shrink.get_at(a) == m_config.filament_shrink.get_at(b) &&
m_config.filament_shrinkage_compensation_z.get_at(a) == m_config.filament_shrinkage_compensation_z.get_at(b);
};
// Usually all filaments share the same shrinkage, then it does not matter which of them print.
bool uniform = true;
for (unsigned int extruder = 1; extruder < num_extruders && uniform; ++ extruder)
uniform = same_shrinkage(0, extruder);
if (uniform) {
m_same_shrinkage_compensations = true;
set_compensation(0);
return;
}
std::vector<unsigned int> extruders;
const auto append_feature_extruders = [num_extruders, &extruders](const PrintRegionConfig &config) {
for (int id : { config.outer_wall_filament_id.value, config.inner_wall_filament_id.value, config.sparse_infill_filament_id.value,
config.internal_solid_filament_id.value, config.top_surface_filament_id.value, config.bottom_surface_filament_id.value }) {
const unsigned int i = (unsigned int)std::max(0, id - 1);
extruders.emplace_back(i >= num_extruders ? 0 : i);
}
};
for (const ModelObject *model_object : m_model.objects) {
if (std::none_of(model_object->instances.begin(), model_object->instances.end(), [](const ModelInstance *mi) { return mi->is_printable(); }))
continue;
std::vector<const ModelVolume*> modifiers;
std::copy_if(model_object->volumes.begin(), model_object->volumes.end(), std::back_inserter(modifiers), [](const ModelVolume *mv) { return mv->is_modifier(); });
for (const ModelVolume *part : model_object->volumes) {
if (! part->is_model_part())
continue;
std::vector<PrintRegionConfig> parents { region_config_from_model_volume(m_default_region_config, nullptr, *part, num_extruders, variant_index) };
for (const auto &[range, range_config] : model_object->layer_config_ranges)
parents.emplace_back(region_config_from_model_volume(m_default_region_config, &range_config.get(), *part, num_extruders, variant_index));
for (const PrintRegionConfig &parent : parents) {
append_feature_extruders(parent);
for (const ModelVolume *modifier : modifiers)
append_feature_extruders(region_config_from_model_volume(parent, nullptr, *modifier, num_extruders, variant_index));
}
}
append_model_object_extruders(*model_object, extruders);
const PrintObjectConfig object_config = PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders, variant_index);
if (PrintObject::has_support_material(object_config))
append_support_extruders(object_config, (unsigned int)num_extruders, extruders);
}
this->append_tool_change_extruders(extruders);
// A mixed slot prints with its physical components.
const auto expand_and_sort = [this, &extruders]() {
extruders = expand_mixed_filaments(extruders, m_config.filament_is_mixed.values, m_config.filament_mixed_components.values);
};
expand_and_sort();
// The wipe tower filament may also be flushed into the objects.
this->append_wipe_tower_extruder(extruders);
expand_and_sort();
m_same_shrinkage_compensations = ! extruders.empty() &&
std::all_of(extruders.begin(), extruders.end(), [&](unsigned int extruder) { return same_shrinkage(extruders.front(), extruder); });
m_shrinkage_compensation = Vec3d::Ones();
if (m_same_shrinkage_compensations)
set_compensation(extruders.front());
}
Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config, bool extruder_applied)
{
#ifdef _DEBUG
@@ -1864,6 +1939,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// 4) Generate PrintObjects from ModelObjects and their instances.
{
this->update_shrinkage_compensation(num_extruders, print_variant_index);
PrintObjectPtrs print_objects_new;
print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size()));
bool new_objects = false;
+75
View File
@@ -11,9 +11,15 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_message.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include "libslic3r/GCode/ToolOrdering.hpp"
#include "libslic3r/MultiNozzleUtils.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/Layer.hpp"
#include "libslic3r/libslic3r.h"
#include "test_helpers.hpp"
@@ -333,3 +339,72 @@ TEST_CASE("Print::validate warns when a gradient mixed filament is used without
CHECK(count_opt(print, "enable_mixed_color_sublayer") == 0);
}
}
TEST_CASE("Filament shrinkage compensation compares the physical filaments of a mixed slot", "[MixedFilament][Print][Regression]")
{
// The mixed slot 3 blends filament 1 (90 %) and filament 2 (100 %); its own 90 % does not count.
DynamicPrintConfig config = mixed_config(false);
config.set_deserialize_strict({
{ "filament_shrink", "90%,100%,90%" },
{ "xy_contour_compensation", 0. },
{ "layer_change_gcode", "G92 E0\n" }, // validate() relative-E reset
});
Print print;
init_and_process_print({ cube(20.) }, print, config);
const PrintObject &object = *print.objects().front();
const BoundingBox extents = get_extents(object.get_layer(int(object.layer_count() / 2))->lslices);
CHECK_THAT(unscale<double>(extents.size().x()), Catch::Matchers::WithinAbs(20., 0.01));
std::vector<StringObjectException> warnings;
CHECK(print.validate(&warnings).string.empty());
CHECK(std::any_of(warnings.begin(), warnings.end(), [](const StringObjectException &w) {
return w.string.find("Filament shrinkage will not be used") != std::string::npos;
}));
}
TEST_CASE("Filament shrinkage compensation counts the wipe tower filament next to a mixed slot", "[MixedFilament][Print][Regression]")
{
// The object prints only with the mixed slot 4 of filaments 1 and 2 (both 90 %). The wipe tower filament 3 (100 %)
// may be flushed into the object, so the compensation is off.
DynamicPrintConfig config = multifilament_config(4);
config.set_deserialize_strict({
{"filament_is_mixed", "0,0,0,1"},
{"filament_mixed_components", ";;;1,2"},
{"filament_mixed_sublayer_ratios", ";;;0.5,0.5"},
{"filament_mixed_gradient", "0,0,0,0"},
{"filament_mixed_gradient_range", ";;;"},
{"filament_mixed_gradient_curve", ";;;"},
{"filament_mixed_gradient_per_part","0,0,0,0"},
{"outer_wall_filament_id", "4"},
{"inner_wall_filament_id", "4"},
{"sparse_infill_filament_id", "4"},
{"internal_solid_filament_id", "4"},
{"top_surface_filament_id", "4"},
{"bottom_surface_filament_id", "4"},
{"enable_prime_tower", "1"},
{"wipe_tower_filament", "3"},
{"prime_tower_width", "35"},
{"wipe_tower_x", "50"}, // inside the 200x200 test bed
{"wipe_tower_y", "50"},
{"filament_shrink", "90%,90%,100%,90%"},
{"xy_contour_compensation", "0"},
{"layer_change_gcode", "G92 E0\n"}, // validate() relative-E reset
});
// The object itself is on the mixed slot too, so no other filament is assigned.
const std::vector<std::vector<ConfigBase::SetDeserializeItem>> object_overrides { { { "extruder", 4 } } };
Print print;
Model model;
init_print({ cube(20.) }, print, model, config, &object_overrides);
print.process();
const PrintObject &object = *print.objects().front();
const BoundingBox extents = get_extents(object.get_layer(int(object.layer_count() / 2))->lslices);
CHECK_THAT(unscale<double>(extents.size().x()), Catch::Matchers::WithinAbs(20., 0.01));
std::vector<StringObjectException> warnings;
CHECK(print.validate(&warnings).string.empty());
CHECK(std::any_of(warnings.begin(), warnings.end(), [](const StringObjectException &w) {
return w.string.find("Filament shrinkage will not be used") != std::string::npos;
}));
}
+129
View File
@@ -63,6 +63,8 @@
#include "libslic3r/ObjectID.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/SurfaceCollection.hpp"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/ExPolygon.hpp"
using namespace Slic3r;
using namespace Slic3r::Test;
@@ -1652,3 +1654,130 @@ TEST_CASE("The build height check counts the raft", "[Print][validate][Regressio
else
CHECK(error.find("maximum build volume height") != std::string::npos);
}
TEST_CASE("Filament shrinkage compensation scales the sliced object", "[Print][Regression]")
{
// A 20 mm cube printed with a filament that shrinks to xy % / z % is sliced 20 * 100 / xy wide and,
// up to one layer, 20 * 100 / z tall.
const auto [xy, z] = GENERATE(table<double, double>({ { 100., 100. }, { 90., 100. }, { 100., 95. } }));
const double layer_height = 0.2;
Print print;
init_and_process_print({ cube(20.) }, print, {
{ "filament_shrink", xy },
{ "filament_shrinkage_compensation_z", z },
{ "layer_height", layer_height },
{ "initial_layer_print_height", layer_height },
{ "precise_z_height", false },
{ "raft_layers", 0 },
{ "xy_contour_compensation", 0. },
});
const PrintObject &object = *print.objects().front();
const Layer &middle = *object.get_layer(int(object.layer_count() / 2));
const BoundingBox extents = get_extents(middle.lslices);
CHECK_THAT(unscale<double>(extents.size().x()), Catch::Matchers::WithinAbs(20. * 100. / xy, 0.01));
CHECK_THAT(unscale<double>(extents.size().y()), Catch::Matchers::WithinAbs(20. * 100. / xy, 0.01));
CHECK_THAT(object.layers().back()->print_z, Catch::Matchers::WithinAbs(20. * 100. / z, layer_height));
}
TEST_CASE("Filament shrinkage compensation follows the filaments the object prints with", "[Print][Regression]")
{
// Filament 1 shrinks to 90 %, filament 2 not at all. An unused filament does not matter; two used filaments
// that disagree switch the compensation off and validate() warns about it.
const auto [wall_filament, expected_width] = GENERATE(table<int, double>({ { 1, 20. * 100. / 90. }, { 2, 20. } }));
Print print;
init_and_process_print({ cube(20.) }, print, multifilament_config(2, {
{ "filament_shrink", "90%,100%" },
{ "outer_wall_filament_id", wall_filament },
{ "inner_wall_filament_id", wall_filament },
{ "xy_contour_compensation", 0. },
{ "layer_change_gcode", "G92 E0\n" }, // validate() relative-E reset
}));
const PrintObject &object = *print.objects().front();
const BoundingBox extents = get_extents(object.get_layer(int(object.layer_count() / 2))->lslices);
CHECK_THAT(unscale<double>(extents.size().x()), Catch::Matchers::WithinAbs(expected_width, 0.01));
std::vector<StringObjectException> warnings;
CHECK(print.validate(&warnings).string.empty());
const bool mismatch_warning = std::any_of(warnings.begin(), warnings.end(), [](const StringObjectException &w) {
return w.string.find("Filament shrinkage will not be used") != std::string::npos;
});
CHECK(mismatch_warning == (wall_filament == 2));
}
TEST_CASE("Applying the same model again keeps the filament shrinkage compensation", "[Print][Regression]")
{
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({ { "filament_shrink", "90%" }, { "xy_contour_compensation", 0. } });
Print print;
Model model;
init_print({ cube(20.) }, print, model, config);
const auto width = [&print]() {
print.process();
const PrintObject &object = *print.objects().front();
return unscale<double>(get_extents(object.get_layer(int(object.layer_count() / 2))->lslices).size().x());
};
CHECK_THAT(width(), Catch::Matchers::WithinAbs(20. * 100. / 90., 0.01));
print.apply(model, config);
CHECK_THAT(width(), Catch::Matchers::WithinAbs(20. * 100. / 90., 0.01));
}
TEST_CASE("Height ranges and modifiers count for the filament shrinkage compensation", "[Print][Regression]")
{
// The model is complete before the first apply(), so no PrintRegion tells yet which filaments print.
enum class Setup { RangeWall, ModifierInfill, ModifierInheritingObjectFilament };
const Setup setup = GENERATE(Setup::RangeWall, Setup::ModifierInfill, Setup::ModifierInheritingObjectFilament);
// The object prints with the filament that shrinks to 90 %, the other one does not shrink.
const bool object_on_filament_2 = setup == Setup::ModifierInheritingObjectFilament;
DynamicPrintConfig config = multifilament_config(2, {
{ "filament_shrink", object_on_filament_2 ? "100%,90%" : "90%,100%" },
{ "xy_contour_compensation", 0. },
{ "layer_change_gcode", "G92 E0\n" }, // validate() relative-E reset
});
Model model;
ModelObject *object = model.add_object();
object->add_volume(cube(20.));
DynamicPrintConfig override;
switch (setup) {
case Setup::RangeWall:
// The walls of a height range print with the other filament.
override.set_key_value("outer_wall_filament_id", new ConfigOptionInt(2));
// Every layer range must carry a layer_height (see layer_height_profile_from_ranges).
override.set_key_value("layer_height", new ConfigOptionFloat(0.2));
object->layer_config_ranges[{ 5., 10. }].assign_config(std::move(override));
break;
case Setup::ModifierInfill:
// The infill inside a modifier prints with the other filament.
override.set_key_value("sparse_infill_filament_id", new ConfigOptionInt(2));
object->add_volume(cube(10.), ModelVolumeType::PARAMETER_MODIFIER, false)->config.apply(override);
break;
case Setup::ModifierInheritingObjectFilament:
// The modifier only changes the infill density and keeps printing with the filament of the object.
object->config.set_key_value("extruder", new ConfigOptionInt(2));
override.set_key_value("sparse_infill_density", new ConfigOptionPercent(30));
object->add_volume(cube(10.), ModelVolumeType::PARAMETER_MODIFIER, false)->config.apply(override);
break;
}
object->add_instance()->set_offset(Vec3d(90., 90., 0.));
object->ensure_on_bed();
Print print;
print.apply(model, config);
print.process();
const PrintObject &print_object = *print.objects().front();
const BoundingBox extents = get_extents(print_object.get_layer(int(print_object.layer_count() / 2))->lslices);
const double expected_width = object_on_filament_2 ? 20. * 100. / 90. : 20.;
CHECK_THAT(unscale<double>(extents.size().x()), Catch::Matchers::WithinAbs(expected_width, 0.01));
std::vector<StringObjectException> warnings;
CHECK(print.validate(&warnings).string.empty());
const bool mismatch_warning = std::any_of(warnings.begin(), warnings.end(), [](const StringObjectException &w) {
return w.string.find("Filament shrinkage will not be used") != std::string::npos;
});
CHECK(mismatch_warning == ! object_on_filament_2);
}