mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-10 02:37:04 +00:00
Fix post-slice self-invalidation on custom multi-extruder printers
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
#define slic3r_Config_hpp_
|
#define slic3r_Config_hpp_
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <algorithm>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
@@ -780,10 +781,14 @@ public:
|
|||||||
this->values[i] = rhs_vec->values[i];
|
this->values[i] = rhs_vec->values[i];
|
||||||
modified = true;
|
modified = true;
|
||||||
} else {
|
} else {
|
||||||
if ((i < default_index.size()) && (default_index[i] < default_value.size()))
|
// Orca: a negative slot (failed variant lookup) must not silently collapse the
|
||||||
|
// whole array to the first slot's value — the int-vs-size_t comparison used to
|
||||||
|
// promote -1 past the bounds check. Keep the slot's own value (get_at-style
|
||||||
|
// clamp) when no valid index is available.
|
||||||
|
if ((i < default_index.size()) && (default_index[i] >= 0) && (size_t(default_index[i]) < default_value.size()))
|
||||||
this->values[i] = default_value[default_index[i]];
|
this->values[i] = default_value[default_index[i]];
|
||||||
else
|
else
|
||||||
this->values[i] = default_value[0];
|
this->values[i] = default_value[std::min(i, default_value.size() - 1)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return modified;
|
return modified;
|
||||||
@@ -2106,6 +2111,11 @@ public:
|
|||||||
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
||||||
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
|
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
|
||||||
this->value = rhs->getInt();
|
this->value = rhs->getInt();
|
||||||
|
// Orca: options embedded in a StaticPrintConfig are constructed without a keys_map;
|
||||||
|
// adopt the source's so a later serialize() can emit names.
|
||||||
|
if (this->keys_map == nullptr)
|
||||||
|
if (auto rhs_generic = dynamic_cast<const ConfigOptionEnumGeneric *>(rhs))
|
||||||
|
this->keys_map = rhs_generic->keys_map;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string serialize() const override
|
std::string serialize() const override
|
||||||
@@ -2162,7 +2172,12 @@ public:
|
|||||||
if (rhs->type() != this->type())
|
if (rhs->type() != this->type())
|
||||||
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
|
||||||
// rhs could be of the following type: ConfigOptionEnumsGeneric
|
// rhs could be of the following type: ConfigOptionEnumsGeneric
|
||||||
this->values = dynamic_cast<const ConfigOptionEnumsGenericTempl *>(rhs)->values;
|
auto rhs_enums = dynamic_cast<const ConfigOptionEnumsGenericTempl *>(rhs);
|
||||||
|
this->values = rhs_enums->values;
|
||||||
|
// Orca: options embedded in a StaticPrintConfig are constructed without a keys_map;
|
||||||
|
// adopt the source's so a later serialize() emits names instead of empty tokens.
|
||||||
|
if (this->keys_map == nullptr)
|
||||||
|
this->keys_map = rhs_enums->keys_map;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string serialize() const override
|
std::string serialize() const override
|
||||||
|
|||||||
@@ -3409,7 +3409,11 @@ void Print::update_filament_maps_to_config(std::vector<int> f_maps, std::vector<
|
|||||||
}
|
}
|
||||||
else if ((extruder_volume_type_count > extruder_count) && (m_config.filament_volume_map.values.size() > index))
|
else if ((extruder_volume_type_count > extruder_count) && (m_config.filament_volume_map.values.size() > index))
|
||||||
nozzle_volume_type = (NozzleVolumeType)(m_config.filament_volume_map.values[index]);
|
nozzle_volume_type = (NozzleVolumeType)(m_config.filament_volume_map.values[index]);
|
||||||
m_config.filament_map_2.values[index] = m_ori_full_print_config.get_index_for_extruder(f_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
// Orca: when the process variant columns cannot be matched (degenerate
|
||||||
|
// print_extruder_id), key the override by plain extruder index like the seeding
|
||||||
|
// above instead of poisoning the map with -1.
|
||||||
|
int slot_index = m_ori_full_print_config.get_index_for_extruder(f_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||||
|
m_config.filament_map_2.values[index] = slot_index >= 0 ? slot_index : f_maps[index] - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_full_print_config = m_ori_full_print_config;
|
m_full_print_config = m_ori_full_print_config;
|
||||||
|
|||||||
@@ -1355,7 +1355,11 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
if ((extruder_volume_type_count > extruder_count) && opt_filament_volume_maps
|
if ((extruder_volume_type_count > extruder_count) && opt_filament_volume_maps
|
||||||
&& opt_filament_volume_maps->values.size() == filament_maps.size())
|
&& opt_filament_volume_maps->values.size() == filament_maps.size())
|
||||||
nozzle_volume_type = (NozzleVolumeType)(opt_filament_volume_maps->values[index]);
|
nozzle_volume_type = (NozzleVolumeType)(opt_filament_volume_maps->values[index]);
|
||||||
m_config.filament_map_2.values[index] = new_full_config.get_index_for_extruder(filament_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
// Orca: when the process variant columns cannot be matched (degenerate
|
||||||
|
// print_extruder_id), key the override by plain extruder index like the seeding
|
||||||
|
// above instead of poisoning the map with -1.
|
||||||
|
int slot_index = new_full_config.get_index_for_extruder(filament_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
|
||||||
|
m_config.filament_map_2.values[index] = slot_index >= 0 ? slot_index : filament_maps[index] - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not use the ApplyStatus as we will use the max function when updating apply_status.
|
// Do not use the ApplyStatus as we will use the max function when updating apply_status.
|
||||||
@@ -1411,6 +1415,16 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
|||||||
num_extruders_changed = true;
|
num_extruders_changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (! print_diff.empty()) {
|
||||||
|
// Orca: m_config can diverge from an unchanged full config (e.g. the in-slice retract
|
||||||
|
// override recompute writing different values than the apply-time computation). The
|
||||||
|
// invalidation above already fired for print_diff, so repair m_config here as well;
|
||||||
|
// otherwise the divergence is never corrected and every subsequent apply of the same
|
||||||
|
// config invalidates the result again, forever.
|
||||||
|
m_placeholder_parser.apply_config(filament_overrides);
|
||||||
|
m_config.apply_only(new_full_config, print_diff, true);
|
||||||
|
m_config.apply(filament_overrides);
|
||||||
|
}
|
||||||
|
|
||||||
ModelObjectStatusDB model_object_status_db;
|
ModelObjectStatusDB model_object_status_db;
|
||||||
|
|
||||||
|
|||||||
@@ -10495,6 +10495,44 @@ int DynamicPrintConfig::get_extruder_nozzle_volume_count(int extruder_count, std
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Orca: BBL system profiles ship full-width print_extruder_id/print_extruder_variant columns, but
|
||||||
|
// custom multi-extruder printers only ever get the machine-scope columns synthesized for them (see
|
||||||
|
// extend_extruder_variant); the process scope keeps the length-1 defaults, both in presets and in
|
||||||
|
// 3mf project configs. Expanding with that degenerate map makes every per-extruder lookup fail, and
|
||||||
|
// because both keys are themselves in print_options_with_variant, the expansion then latches a
|
||||||
|
// full-width-but-wrong [1,1,...] map that also defeats the generated_extruder_id fallback in
|
||||||
|
// get_index_for_extruder. Synthesize the process columns from the printer's extruder_variant_list
|
||||||
|
// (same token walk as extend_extruder_variant) before expanding.
|
||||||
|
static void ensure_process_variant_columns(DynamicPrintConfig &config, const DynamicPrintConfig &printer_config)
|
||||||
|
{
|
||||||
|
auto id_opt = dynamic_cast<ConfigOptionInts *>(config.option("print_extruder_id"));
|
||||||
|
auto variant_opt = dynamic_cast<ConfigOptionStrings *>(config.option("print_extruder_variant"));
|
||||||
|
auto list_opt = dynamic_cast<const ConfigOptionStrings *>(printer_config.option("extruder_variant_list"));
|
||||||
|
if (!id_opt || !variant_opt || !list_opt)
|
||||||
|
return;
|
||||||
|
if (id_opt->values.size() != 1 || variant_opt->values.size() != 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::vector<int> ids;
|
||||||
|
std::vector<std::string> variants;
|
||||||
|
for (int i = 0; i < int(list_opt->values.size()); ++i) {
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
boost::split(tokens, list_opt->get_at(i), boost::is_any_of(","), boost::token_compress_on);
|
||||||
|
for (std::string &token : tokens) {
|
||||||
|
boost::trim(token);
|
||||||
|
if (token.empty())
|
||||||
|
continue;
|
||||||
|
ids.push_back(i + 1);
|
||||||
|
variants.push_back(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A single column is the legitimate single-extruder layout, not a degenerate one.
|
||||||
|
if (ids.size() <= 1)
|
||||||
|
return;
|
||||||
|
id_opt->values = std::move(ids);
|
||||||
|
variant_opt->values = std::move(variants);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<int> DynamicPrintConfig::update_values_to_printer_extruders(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector<std::vector<NozzleVolumeType>>& nv_types,
|
std::vector<int> DynamicPrintConfig::update_values_to_printer_extruders(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector<std::vector<NozzleVolumeType>>& nv_types,
|
||||||
std::set<std::string>& key_set, std::string id_name, std::string variant_name, unsigned int stride, unsigned int extruder_id, NozzleVolumeType filament_nvt)
|
std::set<std::string>& key_set, std::string id_name, std::string variant_name, unsigned int stride, unsigned int extruder_id, NozzleVolumeType filament_nvt)
|
||||||
{
|
{
|
||||||
@@ -10536,6 +10574,8 @@ std::vector<int> DynamicPrintConfig::update_values_to_printer_extruders(DynamicP
|
|||||||
variant_count = 1;
|
variant_count = 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if (id_name == "print_extruder_id")
|
||||||
|
ensure_process_variant_columns(*this, printer_config);
|
||||||
// Orca: emit the slots first, then size variant_count from what was actually
|
// Orca: emit the slots first, then size variant_count from what was actually
|
||||||
// emitted. extruder_nozzle_volume_count only equals the emitted total when every
|
// emitted. extruder_nozzle_volume_count only equals the emitted total when every
|
||||||
// extruder carries per-type stats; an extruder with an empty stats entry combined
|
// extruder carries per-type stats; an extruder with an empty stats entry combined
|
||||||
|
|||||||
@@ -43,18 +43,33 @@ TEST_CASE("apply_override fills nil entries from the 0-based default index", "[C
|
|||||||
REQUIRE(resolved.values == std::vector<double>({30., 42.}));
|
REQUIRE(resolved.values == std::vector<double>({30., 42.}));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("an index past the machine slots falls back to the first slot") {
|
SECTION("an index past the machine slots keeps the slot's own value") {
|
||||||
std::vector<int> slot_index{5, 0};
|
std::vector<int> slot_index{5, 0};
|
||||||
ConfigOptionFloats resolved(machine);
|
ConfigOptionFloats resolved(machine);
|
||||||
REQUIRE(resolved.apply_override(&filament, slot_index));
|
REQUIRE(resolved.apply_override(&filament, slot_index));
|
||||||
REQUIRE(resolved.values == std::vector<double>({10., 42.}));
|
REQUIRE(resolved.values == std::vector<double>({10., 42.}));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("a negative index (unresolved slot) falls back to the first slot") {
|
SECTION("a negative index (unresolved slot) keeps the slot's own value") {
|
||||||
std::vector<int> slot_index{-1, 0};
|
ConfigOptionFloatsNullable all_nil;
|
||||||
|
all_nil.values = {ConfigOptionFloatsNullable::nil_value(), ConfigOptionFloatsNullable::nil_value(),
|
||||||
|
ConfigOptionFloatsNullable::nil_value()};
|
||||||
|
std::vector<int> slot_index{2, -1, 0};
|
||||||
ConfigOptionFloats resolved(machine);
|
ConfigOptionFloats resolved(machine);
|
||||||
REQUIRE(resolved.apply_override(&filament, slot_index));
|
REQUIRE(!resolved.apply_override(&all_nil, slot_index));
|
||||||
REQUIRE(resolved.values == std::vector<double>({10., 42.}));
|
REQUIRE(resolved.values == std::vector<double>({30., 20., 10.}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("all-nil overrides keyed by unresolved slots leave the machine values intact") {
|
||||||
|
// The failed-lookup map a degenerate print_extruder_id used to produce; the negative
|
||||||
|
// slots must not collapse the machine array to its first value.
|
||||||
|
ConfigOptionFloats per_extruder({100., 70., 70., 70., 100.});
|
||||||
|
ConfigOptionFloatsNullable all_nil;
|
||||||
|
all_nil.values.assign(5, ConfigOptionFloatsNullable::nil_value());
|
||||||
|
std::vector<int> slot_index{0, -1, -1, -1, 0};
|
||||||
|
ConfigOptionFloats resolved(per_extruder);
|
||||||
|
REQUIRE(!resolved.apply_override(&all_nil, slot_index));
|
||||||
|
REQUIRE(resolved.values == std::vector<double>({100., 70., 70., 70., 100.}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,6 +253,102 @@ TEST_CASE("update_values_to_printer_extruders expands one slot per (extruder x v
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("update_values_to_printer_extruders synthesizes degenerate process variant columns", "[Config]")
|
||||||
|
{
|
||||||
|
// Non-BBL process presets and 3mf project configs keep the length-1 defaults for
|
||||||
|
// print_extruder_id/print_extruder_variant; only BBL system presets ship full-width columns.
|
||||||
|
auto add_degenerate_print_columns = [](DynamicPrintConfig &config) {
|
||||||
|
config.option<ConfigOptionInts>("print_extruder_id", true)->values = {1};
|
||||||
|
config.option<ConfigOptionStrings>("print_extruder_variant", true)->values = {"Direct Drive Standard"};
|
||||||
|
config.option<ConfigOptionFloats>("outer_wall_speed", true)->values = {30.};
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("a single-column pair on a multi-extruder machine expands to one column per extruder") {
|
||||||
|
DynamicPrintConfig config;
|
||||||
|
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive, etDirectDrive};
|
||||||
|
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard, nvtStandard};
|
||||||
|
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard", "Direct Drive Standard"};
|
||||||
|
add_degenerate_print_columns(config);
|
||||||
|
|
||||||
|
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||||
|
int extruder_count = 2;
|
||||||
|
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||||
|
|
||||||
|
std::vector<int> variant_index = config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||||
|
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||||
|
|
||||||
|
REQUIRE(variant_index == std::vector<int>({0, 1}));
|
||||||
|
REQUIRE(config.option<ConfigOptionInts>("print_extruder_id")->values == std::vector<int>({1, 2}));
|
||||||
|
REQUIRE(config.option<ConfigOptionStrings>("print_extruder_variant")->values ==
|
||||||
|
std::vector<std::string>({"Direct Drive Standard", "Direct Drive Standard"}));
|
||||||
|
// width-1 data arrays replicate their only column into every slot
|
||||||
|
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values == std::vector<double>({30., 30.}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a multi-variant list synthesizes one column per (extruder x variant)") {
|
||||||
|
DynamicPrintConfig config = make_hybrid_printer_config();
|
||||||
|
add_degenerate_print_columns(config);
|
||||||
|
|
||||||
|
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||||
|
int extruder_count = 2;
|
||||||
|
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||||
|
REQUIRE(count == 3);
|
||||||
|
|
||||||
|
std::vector<int> variant_index = config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||||
|
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||||
|
|
||||||
|
// same slot resolution as the explicit BBL-style 4-column layout
|
||||||
|
REQUIRE(variant_index == std::vector<int>({0, 2, 3}));
|
||||||
|
REQUIRE(config.option<ConfigOptionInts>("print_extruder_id")->values == std::vector<int>({1, 2, 2}));
|
||||||
|
REQUIRE(config.option<ConfigOptionStrings>("print_extruder_variant")->values ==
|
||||||
|
std::vector<std::string>({"Direct Drive Standard", "Direct Drive Standard", "Direct Drive High Flow"}));
|
||||||
|
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values == std::vector<double>({30., 30., 30.}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a single-extruder single-column layout is not treated as degenerate") {
|
||||||
|
DynamicPrintConfig config;
|
||||||
|
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive};
|
||||||
|
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard};
|
||||||
|
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard"};
|
||||||
|
add_degenerate_print_columns(config);
|
||||||
|
|
||||||
|
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||||
|
int extruder_count = 1;
|
||||||
|
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||||
|
|
||||||
|
config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||||
|
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||||
|
|
||||||
|
REQUIRE(config.option<ConfigOptionInts>("print_extruder_id")->values == std::vector<int>({1}));
|
||||||
|
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values == std::vector<double>({30.}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a second expansion leaves the synthesized layout unchanged") {
|
||||||
|
DynamicPrintConfig config;
|
||||||
|
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive, etDirectDrive};
|
||||||
|
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard, nvtStandard};
|
||||||
|
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard", "Direct Drive Standard"};
|
||||||
|
add_degenerate_print_columns(config);
|
||||||
|
|
||||||
|
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||||
|
int extruder_count = 2;
|
||||||
|
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||||
|
|
||||||
|
config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||||
|
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||||
|
DynamicPrintConfig once = config;
|
||||||
|
config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||||
|
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||||
|
|
||||||
|
REQUIRE(config.option<ConfigOptionInts>("print_extruder_id")->values ==
|
||||||
|
once.option<ConfigOptionInts>("print_extruder_id")->values);
|
||||||
|
REQUIRE(config.option<ConfigOptionStrings>("print_extruder_variant")->values ==
|
||||||
|
once.option<ConfigOptionStrings>("print_extruder_variant")->values);
|
||||||
|
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values ==
|
||||||
|
once.option<ConfigOptionFloats>("outer_wall_speed")->values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("update_values_to_printer_extruders_for_multiple_filaments resolves per-filament slots", "[Config]")
|
TEST_CASE("update_values_to_printer_extruders_for_multiple_filaments resolves per-filament slots", "[Config]")
|
||||||
{
|
{
|
||||||
auto make_filament_arrays = [](DynamicPrintConfig &config) {
|
auto make_filament_arrays = [](DynamicPrintConfig &config) {
|
||||||
|
|||||||
@@ -500,6 +500,52 @@ TEST_CASE("Re-applying an unchanged config after slicing keeps the result valid"
|
|||||||
REQUIRE(print.is_step_done(psSlicingFinished));
|
REQUIRE(print.is_step_done(psSlicingFinished));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A degenerate process variant map on a custom multi-extruder printer slices to a stable result", "[Print][Regression]")
|
||||||
|
{
|
||||||
|
// Non-BBL multi-extruder printers get machine-scope variant columns synthesized on preset
|
||||||
|
// load (extend_extruder_variant), but nothing ships process-scope print_extruder_id /
|
||||||
|
// print_extruder_variant: presets and 3mf project configs carry the length-1 defaults. The
|
||||||
|
// apply-time expansion must synthesize the process columns from extruder_variant_list;
|
||||||
|
// otherwise the failed per-extruder lookups collapse the per-extruder retract overrides
|
||||||
|
// during slicing and the post-slice re-apply invalidates every fresh result, forever.
|
||||||
|
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||||
|
config.option<ConfigOptionFloats>("nozzle_diameter", true)->values = {0.4, 0.4, 0.4, 0.4, 0.4};
|
||||||
|
config.set_num_extruders(5);
|
||||||
|
// per-extruder machine values that a first-slot collapse would destroy
|
||||||
|
config.option<ConfigOptionPercents>("retract_before_wipe", true)->values = {100., 70., 70., 70., 100.};
|
||||||
|
config.option<ConfigOptionEnumsGeneric>("z_hop_types", true)->values = {zhtSlope, zhtNormal, zhtNormal, zhtNormal, zhtSlope};
|
||||||
|
// filament presets carry the nullable override twins (all-nil = "no override"); they are what
|
||||||
|
// routes the machine values through apply_override in the in-slice override recompute
|
||||||
|
config.option<ConfigOptionPercentsNullable>("filament_retract_before_wipe", true)->values =
|
||||||
|
std::vector<double>(5, ConfigOptionPercentsNullable::nil_value());
|
||||||
|
config.option<ConfigOptionEnumsGenericNullable>("filament_z_hop_types", true)->values =
|
||||||
|
std::vector<int>(5, ConfigOptionEnumsGenericNullable::nil_value());
|
||||||
|
config.option<ConfigOptionFloats>("filament_diameter", true)->values = std::vector<double>(5, 1.75);
|
||||||
|
config.option<ConfigOptionStrings>("filament_colour", true)->values = {"#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#00FFFF"};
|
||||||
|
config.option<ConfigOptionInts>("filament_map", true)->values = {1, 2, 3, 4, 1};
|
||||||
|
|
||||||
|
Model model;
|
||||||
|
model.add_object("cube", "", make_cube(20, 20, 20))->add_instance()->set_offset(Vec3d(100., 100., 0.));
|
||||||
|
|
||||||
|
Print print;
|
||||||
|
print.apply(model, config);
|
||||||
|
print.process();
|
||||||
|
REQUIRE(print.is_step_done(psSlicingFinished));
|
||||||
|
|
||||||
|
// BackgroundSlicingProcess reads the engine-computed maps back into the plate config after
|
||||||
|
// slicing; the next apply overlays that written-back state.
|
||||||
|
config.option<ConfigOptionInts>("filament_map", true)->values = print.get_filament_maps();
|
||||||
|
config.option<ConfigOptionInts>("filament_volume_map", true)->values = print.get_filament_volume_maps();
|
||||||
|
config.option<ConfigOptionInts>("filament_nozzle_map", true)->values = print.get_filament_nozzle_maps();
|
||||||
|
|
||||||
|
auto status = print.apply(model, config);
|
||||||
|
REQUIRE(status == PrintBase::APPLY_STATUS_UNCHANGED);
|
||||||
|
REQUIRE(print.is_step_done(psSlicingFinished));
|
||||||
|
// the per-extruder machine values must survive the in-slice override recompute
|
||||||
|
REQUIRE(print.config().retract_before_wipe.values == std::vector<double>({100., 70., 70., 70., 100.}));
|
||||||
|
REQUIRE(print.config().z_hop_types.values == std::vector<int>({zhtSlope, zhtNormal, zhtNormal, zhtNormal, zhtSlope}));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("normalize_nozzle_map_per_layer makes per-filament assignments gap-free", "[MultiNozzle][H2C][Dynamic]")
|
TEST_CASE("normalize_nozzle_map_per_layer makes per-filament assignments gap-free", "[MultiNozzle][H2C][Dynamic]")
|
||||||
{
|
{
|
||||||
SECTION("gaps inherit the last used nozzle, entries on used layers stay untouched") {
|
SECTION("gaps inherit the last used nozzle, entries on used layers stay untouched") {
|
||||||
|
|||||||
Reference in New Issue
Block a user