diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 52b16f9458..9818887c1d 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -2702,6 +2702,9 @@ public: virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) = 0; // Collect names of all configuration values maintained by this configuration store. virtual t_config_option_keys keys() const = 0; + // Set this config's options on target member by member, when target is of this config's static type or + // derives from it, and return true. apply() prefers this to looking every key up by name. + virtual bool apply_to(ConfigBase &/*target*/) const { return false; } protected: // Verify whether the opt_key has not been obsoleted or renamed. @@ -2753,7 +2756,8 @@ public: // Apply all keys of other ConfigBase defined by this->def() to this ConfigBase. // An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(), // or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set. - void apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->apply_only(other, other.keys(), ignore_nonexistent); } + void apply(const ConfigBase &other, bool ignore_nonexistent = false) + { if (! other.apply_to(*this)) this->apply_only(other, other.keys(), ignore_nonexistent); } // Apply explicitely enumerated keys of other ConfigBase defined by this->def() to this ConfigBase. // An UnknownOptionException is thrown in case some option keys are not defined by this->def(), // or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 94220e31fd..5043c317c6 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -11958,6 +11958,21 @@ PRINT_CONFIG_CACHE_INITIALIZE(( SLAMaterialConfig, SLAPrintConfig, SLAPrintObjectConfig, SLAPrinterConfig, SLAFullPrintConfig)) static int print_config_static_initialized = print_config_static_initializer(); +// The same set() calls ConfigBase::apply_only() makes, without looking every key up by name. Out of line so the +// option list is expanded for this once, not in every file that includes PrintConfig.hpp. +#define PRINT_CONFIG_APPLY_TO_DEFINITION(r, data, CLASS_NAME) \ + bool CLASS_NAME::apply_to(ConfigBase &target) const \ + { \ + auto *dst = dynamic_cast(&target); \ + if (dst == nullptr) \ + return false; \ + visit_option_pairs(*dst, *this, [](const char*, ConfigOption &a, const ConfigOption &b) { a.set(&b); return true; }); \ + return true; \ + } +BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_APPLY_TO_DEFINITION, _, (PrintObjectConfig)(PrintRegionConfig)(MachineEnvelopeConfig)(GCodeConfig) + (SLAMaterialConfig)(SLAPrintConfig)(SLAPrintObjectConfig)(SLAPrinterConfig)) +#undef PRINT_CONFIG_APPLY_TO_DEFINITION + //BBS: remove unused command currently CLIActionsConfigDef::CLIActionsConfigDef() { diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 1e91f2e451..bbd92079c0 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1033,9 +1033,10 @@ public: \ #define PRINT_CONFIG_CLASS_ELEMENT_DEFINITION(r, data, elem) BOOST_PP_TUPLE_ELEM(0, elem) BOOST_PP_TUPLE_ELEM(1, elem); #define PRINT_CONFIG_CLASS_ELEMENT_VISIT(r, data, elem) if (! f(BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(1, elem)), this->BOOST_PP_TUPLE_ELEM(1, elem), rhs.BOOST_PP_TUPLE_ELEM(1, elem))) return; +#define PRINT_CONFIG_CLASS_ELEMENT_VISIT_SELF(r, data, elem) if (! f(BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(1, elem)), self.BOOST_PP_TUPLE_ELEM(1, elem), rhs.BOOST_PP_TUPLE_ELEM(1, elem))) return; // Each option list is expanded into the members and again into for_each_option_pair(), which calls // f(key, this->option, rhs.option) in declaration order and stops when f returns false. hash(), -// operator==, operator< and initialize() iterate the options through that visitor. +// operator==, operator<, initialize() and apply_to() iterate the options through that visitor. #define PRINT_CONFIG_CLASS_COMMON_BODY(CLASS_NAME) \ size_t hash() const throw() \ { \ @@ -1067,11 +1068,16 @@ class CLASS_NAME : public StaticPrintConfig { \ STATIC_PRINT_CONFIG_CACHE(CLASS_NAME) \ public: \ BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_DEFINITION, _, PARAMETER_DEFINITION_SEQ) \ - template void for_each_option_pair(const CLASS_NAME &rhs, F &&f) const \ - { \ - BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_VISIT, _, PARAMETER_DEFINITION_SEQ) \ - } \ + template void for_each_option_pair(const CLASS_NAME &rhs, F &&f) const { visit_option_pairs(*this, rhs, f); } \ + /* Defined in PrintConfig.cpp. */ \ + bool apply_to(ConfigBase &target) const override; \ PRINT_CONFIG_CLASS_COMMON_BODY(CLASS_NAME) \ +private: \ + /* The one expansion of the option list, for a const self and for apply_to()'s mutable target. */ \ + template static void visit_option_pairs(Self &self, const CLASS_NAME &rhs, F &&f) \ + { \ + BOOST_PP_SEQ_FOR_EACH(PRINT_CONFIG_CLASS_ELEMENT_VISIT_SELF, _, PARAMETER_DEFINITION_SEQ) \ + } \ }; #define PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST_ITEM(r, data, i, elem) BOOST_PP_COMMA_IF(i) public elem @@ -1092,6 +1098,8 @@ class CLASS_NAME : PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST(CLASSES_PARENTS_TUPLE) public: \ PARAMETER_DEFINITION \ template void for_each_option_pair(const CLASS_NAME &rhs, F &&f) const { PARAMETER_VISIT } \ + /* Its parents each apply themselves to a target member by member, so this one keeps the lookup by name. */ \ + bool apply_to(ConfigBase &/*target*/) const override { return false; } \ size_t hash() const throw() \ { \ size_t seed = 0; \ @@ -2182,6 +2190,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE0( #undef STATIC_PRINT_CONFIG_CACHE_DERIVED #undef PRINT_CONFIG_CLASS_ELEMENT_DEFINITION #undef PRINT_CONFIG_CLASS_ELEMENT_VISIT +#undef PRINT_CONFIG_CLASS_ELEMENT_VISIT_SELF #undef PRINT_CONFIG_CLASS_COMMON_BODY #undef PRINT_CONFIG_CLASS_DEFINE #undef PRINT_CONFIG_CLASS_DERIVED_CLASS_LIST diff --git a/tests/libslic3r/test_config.cpp b/tests/libslic3r/test_config.cpp index 0da7b7bafe..00c4d6d170 100644 --- a/tests/libslic3r/test_config.cpp +++ b/tests/libslic3r/test_config.cpp @@ -1289,3 +1289,73 @@ TEST_CASE("Static print configs compare, order and hash by their option values", REQUIRE(c.optptr("gcode_flavor") == &c.gcode_flavor); } } + +namespace { + +// Keys whose values differ between two full configs, compared as text so enum names count too. +std::vector differing_keys(const FullPrintConfig &a, const FullPrintConfig &b) +{ + std::vector keys; + for (const std::string &key : a.keys()) + if (a.opt_serialize(key) != b.opt_serialize(key)) + keys.push_back(key); + return keys; +} + +// Applies source to one full config member by member and to another key by key, as apply() did before +// static configs could apply themselves. +template void check_member_apply_matches_key_apply(const Source &source) +{ + FullPrintConfig by_member; + FullPrintConfig by_key; + by_member.apply(source); + by_key.apply_only(source, source.keys()); + CHECK(differing_keys(by_member, by_key).empty()); + CHECK_FALSE(differing_keys(by_member, FullPrintConfig()).empty()); +} + +} // namespace + +TEST_CASE("A static config applies itself onto a config of its type as a lookup by name would", "[Config]") +{ + SECTION("region config") + { + PrintRegionConfig region; + region.sparse_infill_pattern.value = ipGyroid; + region.outer_wall_speed.values = {37.}; + region.sparse_infill_density.value = 35.; + FullPrintConfig full; + REQUIRE(region.apply_to(full)); + check_member_apply_matches_key_apply(region); + } + SECTION("object config") + { + PrintObjectConfig object; + object.seam_position.value = spRear; + object.wall_generator.value = PerimeterGeneratorType::Arachne; + object.support_speed.values = {33.}; + object.enable_support.value = true; + FullPrintConfig full; + REQUIRE(object.apply_to(full)); + check_member_apply_matches_key_apply(object); + } + SECTION("G-code config, whose enum lists carry their names through a keys map") + { + GCodeConfig gcode; + gcode.z_hop_types.values = {int(zhtSpiral)}; + gcode.retraction_length.values = {1.5}; + FullPrintConfig full; + REQUIRE(gcode.apply_to(full)); + check_member_apply_matches_key_apply(gcode); + } +} + +TEST_CASE("A static config applied onto a config of another type falls back to a lookup by name", "[Config]") +{ + PrintRegionConfig region; + region.sparse_infill_pattern.value = ipGyroid; + DynamicPrintConfig dynamic; + REQUIRE_FALSE(region.apply_to(dynamic)); + dynamic.apply(region); + CHECK(dynamic.opt_serialize("sparse_infill_pattern") == "gyroid"); +}