Support for forward compatibility of configurations, user and system

config bundles, project files (3MFs, AMFs). When loading these files,
the caller may decide whether to substitute some of the configuration
values the current PrusaSlicer version does not understand with
some reasonable default value, and whether to report it. If substitution
is disabled, an exception is being thrown as before this commit.
If substitution is enabled, list of substitutions is returned by the
API to be presented to the user. This allows us to introduce for example
new firmware flavor key in PrusaSlicer 2.4 while letting PrusaSlicer
2.3.2 to fall back to some default and to report it to the user.

As a preparation for PrusaSlicer 2.4.0, the new firmware_flavor
"marlinfirmware" (signifying Marlin 2.0 and newer) that is not
supported by 2.3.2 yet will default to "marlin" (signifying legacy
Marlin).

When slicing from command line, substutions are performed by default
and reported into the console, however substitutions may be either
disabled or made silent with the new "config-compatibility" command
line option.

Substitute enums and bools only.  Allow booleans to be parsed as
    true: "1", "enabled", "on" case insensitive
    false: "0", "disabled", "off" case insensitive
This will allow us in the future for example to switch the draft_shield
boolean to an enum with the following values: "disabled" / "enabled" / "limited".

Added "enum_bitmask.hpp" - support for type safe sets of options.
See for example PresetBundle::load_configbundle(...
LoadConfigBundleAttributes flags) for an example of intended usage.

WIP: GUI for reporting the list of config substitutions needs to be
implemented by @YuSanka.
This commit is contained in:
Vojtech Bubnik 2021-06-27 16:04:23 +02:00
parent a53174a2fd
commit 84b28a25e8
47 changed files with 644 additions and 311 deletions

View File

@ -111,6 +111,7 @@ int CLI::run(int argc, char **argv)
#endif // _WIN32 #endif // _WIN32
const std::vector<std::string> &load_configs = m_config.option<ConfigOptionStrings>("load", true)->values; const std::vector<std::string> &load_configs = m_config.option<ConfigOptionStrings>("load", true)->values;
const ForwardCompatibilitySubstitutionRule config_substitution_rule = m_config.option<ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>>("config_compatibility", true)->value;
// load config files supplied via --load // load config files supplied via --load
for (auto const &file : load_configs) { for (auto const &file : load_configs) {
@ -123,12 +124,18 @@ int CLI::run(int argc, char **argv)
} }
} }
DynamicPrintConfig config; DynamicPrintConfig config;
ConfigSubstitutions config_substitutions;
try { try {
config.load(file); config_substitutions = config.load(file, config_substitution_rule);
} catch (std::exception &ex) { } catch (std::exception &ex) {
boost::nowide::cerr << "Error while reading config file: " << ex.what() << std::endl; boost::nowide::cerr << "Error while reading config file \"" << file << "\": " << ex.what() << std::endl;
return 1; return 1;
} }
if (! config_substitutions.empty()) {
boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n";
for (const ConfigSubstitution &subst : config_substitutions)
boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n";
}
config.normalize_fdm(); config.normalize_fdm();
PrinterTechnology other_printer_technology = Slic3r::printer_technology(config); PrinterTechnology other_printer_technology = Slic3r::printer_technology(config);
if (printer_technology == ptUnknown) { if (printer_technology == ptUnknown) {
@ -166,7 +173,9 @@ int CLI::run(int argc, char **argv)
try { try {
// When loading an AMF or 3MF, config is imported as well, including the printer technology. // When loading an AMF or 3MF, config is imported as well, including the printer technology.
DynamicPrintConfig config; DynamicPrintConfig config;
model = Model::read_from_file(file, &config, true); ConfigSubstitutionContext config_substitutions(config_substitution_rule);
//FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ?
model = Model::read_from_file(file, &config, &config_substitutions, Model::LoadAttribute::AddDefaultInstances);
PrinterTechnology other_printer_technology = Slic3r::printer_technology(config); PrinterTechnology other_printer_technology = Slic3r::printer_technology(config);
if (printer_technology == ptUnknown) { if (printer_technology == ptUnknown) {
printer_technology = other_printer_technology; printer_technology = other_printer_technology;
@ -175,6 +184,11 @@ int CLI::run(int argc, char **argv)
boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl;
return 1; return 1;
} }
if (! config_substitutions.substitutions.empty()) {
boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n";
for (const ConfigSubstitution& subst : config_substitutions.substitutions)
boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n";
}
// config is applied to m_print_config before the current m_config values. // config is applied to m_print_config before the current m_config values.
config += std::move(m_print_config); config += std::move(m_print_config);
m_print_config = std::move(config); m_print_config = std::move(config);

View File

@ -29,6 +29,7 @@ add_library(libslic3r STATIC
EdgeGrid.hpp EdgeGrid.hpp
ElephantFootCompensation.cpp ElephantFootCompensation.cpp
ElephantFootCompensation.hpp ElephantFootCompensation.hpp
enum_bitmask.hpp
ExPolygon.cpp ExPolygon.cpp
ExPolygon.hpp ExPolygon.hpp
ExPolygonCollection.cpp ExPolygonCollection.cpp

View File

@ -21,6 +21,10 @@
#include <boost/format.hpp> #include <boost/format.hpp>
#include <string.h> #include <string.h>
//FIXME for GCodeFlavor and gcfMarlin (for forward-compatibility conversion)
// This is not nice, likely it would be better to pass the ConfigSubstitutionContext to handle_legacy().
#include "PrintConfig.hpp"
namespace Slic3r { namespace Slic3r {
// Escape \n, \r and backslash // Escape \n, \r and backslash
@ -209,6 +213,10 @@ std::string escape_ampersand(const std::string& str)
return std::string(out.data(), outptr - out.data()); return std::string(out.data(), outptr - out.data());
} }
void ConfigOptionDeleter::operator()(ConfigOption* p) {
delete p;
}
std::vector<std::string> ConfigOptionDef::cli_args(const std::string &key) const std::vector<std::string> ConfigOptionDef::cli_args(const std::string &key) const
{ {
std::vector<std::string> args; std::vector<std::string> args;
@ -359,7 +367,8 @@ std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, s
// right: option description // right: option description
std::string descr = def.tooltip; std::string descr = def.tooltip;
if (show_defaults && def.default_value && def.type != coBool bool show_defaults_this = show_defaults || def.opt_key == "config_compatibility";
if (show_defaults_this && def.default_value && def.type != coBool
&& (def.type != coString || !def.default_value->serialize().empty())) { && (def.type != coString || !def.default_value->serialize().empty())) {
descr += " ("; descr += " (";
if (!def.sidetext.empty()) { if (!def.sidetext.empty()) {
@ -467,7 +476,7 @@ void ConfigBase::set(const std::string &opt_key, double value, bool create)
} }
} }
bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append) bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions_ctxt, bool append)
{ {
t_config_option_key opt_key = opt_key_src; t_config_option_key opt_key = opt_key_src;
std::string value = value_src; std::string value = value_src;
@ -477,22 +486,22 @@ bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src,
if (opt_key.empty()) if (opt_key.empty())
// Ignore the option. // Ignore the option.
return true; return true;
return this->set_deserialize_raw(opt_key, value, append); return this->set_deserialize_raw(opt_key, value, substitutions_ctxt, append);
} }
void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, bool append) void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions_ctxt, bool append)
{ {
if (! this->set_deserialize_nothrow(opt_key_src, value_src, append)) if (! this->set_deserialize_nothrow(opt_key_src, value_src, substitutions_ctxt, append))
throw BadOptionTypeException(format("ConfigBase::set_deserialize() failed for parameter \"%1%\", value \"%2%\"", opt_key_src, value_src)); throw BadOptionTypeException(format("ConfigBase::set_deserialize() failed for parameter \"%1%\", value \"%2%\"", opt_key_src, value_src));
} }
void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items) void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions_ctxt)
{ {
for (const SetDeserializeItem &item : items) for (const SetDeserializeItem &item : items)
this->set_deserialize(item.opt_key, item.opt_value, item.append); this->set_deserialize(item.opt_key, item.opt_value, substitutions_ctxt, item.append);
} }
bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, bool append) bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, ConfigSubstitutionContext& substitutions_ctxt, bool append)
{ {
t_config_option_key opt_key = opt_key_src; t_config_option_key opt_key = opt_key_src;
// Try to deserialize the option by its name. // Try to deserialize the option by its name.
@ -521,14 +530,38 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con
// Aliasing for example "solid_layers" to "top_solid_layers" and "bottom_solid_layers". // Aliasing for example "solid_layers" to "top_solid_layers" and "bottom_solid_layers".
for (const t_config_option_key &shortcut : optdef->shortcut) for (const t_config_option_key &shortcut : optdef->shortcut)
// Recursive call. // Recursive call.
if (! this->set_deserialize_raw(shortcut, value, append)) if (! this->set_deserialize_raw(shortcut, value, substitutions_ctxt, append))
return false; return false;
return true; return true;
} }
ConfigOption *opt = this->option(opt_key, true); ConfigOption *opt = this->option(opt_key, true);
assert(opt != nullptr); assert(opt != nullptr);
return opt->deserialize(value, append); bool success = opt->deserialize(value, append);
if (! success && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable &&
// Only allow substitutions of an enum value by another enum value or a boolean value with an enum value.
// That means, we expect enum values being added in the future and possibly booleans being converted to enums.
(optdef->type == coEnum || optdef->type == coBool))
{
// Deserialize failed, try to substitute with a default value.
assert(substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSilent);
if (opt_key == "gcode_flavor" && value == "marlinfirmware")
static_cast<ConfigOptionEnum<GCodeFlavor>*>(opt)->value = gcfMarlin;
else
opt->set(optdef->default_value.get());
if (substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable) {
// Log the substitution.
ConfigSubstitution config_substitution;
config_substitution.opt_def = optdef;
config_substitution.old_value = value;//std::unique_ptr<ConfigOption>(opt);
config_substitution.new_value = ConfigOptionUniquePtr(this->option(opt_key, true)->clone());
substitutions_ctxt.substitutions.emplace_back(std::move(config_substitution));
}
return true;
}
return success;
} }
// Return an absolute value of a possibly relative config variable. // Return an absolute value of a possibly relative config variable.
@ -587,36 +620,37 @@ void ConfigBase::setenv_() const
} }
} }
void ConfigBase::load(const std::string &file) ConfigSubstitutions ConfigBase::load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
{ {
if (is_gcode_file(file)) return is_gcode_file(file) ?
this->load_from_gcode_file(file); this->load_from_gcode_file(file, compatibility_rule) :
else this->load_from_ini(file, compatibility_rule);
this->load_from_ini(file);
} }
void ConfigBase::load_from_ini(const std::string &file) ConfigSubstitutions ConfigBase::load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
{ {
boost::property_tree::ptree tree; boost::property_tree::ptree tree;
boost::nowide::ifstream ifs(file); boost::nowide::ifstream ifs(file);
boost::property_tree::read_ini(ifs, tree); boost::property_tree::read_ini(ifs, tree);
this->load(tree); return this->load(tree, compatibility_rule);
} }
void ConfigBase::load(const boost::property_tree::ptree &tree) ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule)
{ {
ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
for (const boost::property_tree::ptree::value_type &v : tree) { for (const boost::property_tree::ptree::value_type &v : tree) {
try { try {
t_config_option_key opt_key = v.first; t_config_option_key opt_key = v.first;
this->set_deserialize(opt_key, v.second.get_value<std::string>()); this->set_deserialize(opt_key, v.second.get_value<std::string>(), substitutions_ctxt);
} catch (UnknownOptionException & /* e */) { } catch (UnknownOptionException & /* e */) {
// ignore // ignore
} }
} }
return std::move(substitutions_ctxt.substitutions);
} }
// Load the config keys from the tail of a G-code file. // Load the config keys from the tail of a G-code file.
void ConfigBase::load_from_gcode_file(const std::string &file) ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
{ {
// Read a 64k block from the end of the G-code. // Read a 64k block from the end of the G-code.
boost::nowide::ifstream ifs(file); boost::nowide::ifstream ifs(file);
@ -637,13 +671,15 @@ void ConfigBase::load_from_gcode_file(const std::string &file)
ifs.read(data.data(), data_length); ifs.read(data.data(), data_length);
ifs.close(); ifs.close();
size_t key_value_pairs = load_from_gcode_string(data.data()); ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
size_t key_value_pairs = load_from_gcode_string(data.data(), substitutions_ctxt);
if (key_value_pairs < 80) if (key_value_pairs < 80)
throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs)); throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs));
return std::move(substitutions_ctxt.substitutions);
} }
// Load the config keys from the given string. // Load the config keys from the given string.
size_t ConfigBase::load_from_gcode_string(const char* str) size_t ConfigBase::load_from_gcode_string(const char* str, ConfigSubstitutionContext& substitutions)
{ {
if (str == nullptr) if (str == nullptr)
return 0; return 0;
@ -688,7 +724,7 @@ size_t ConfigBase::load_from_gcode_string(const char* str)
if (key == nullptr) if (key == nullptr)
break; break;
try { try {
this->set_deserialize(std::string(key, key_end), std::string(value, end)); this->set_deserialize(std::string(key, key_end), std::string(value, end), substitutions);
++num_key_value_pairs; ++num_key_value_pairs;
} }
catch (UnknownOptionException & /* e */) { catch (UnknownOptionException & /* e */) {
@ -717,7 +753,7 @@ void ConfigBase::null_nullables()
ConfigOption *opt = this->optptr(opt_key, false); ConfigOption *opt = this->optptr(opt_key, false);
assert(opt != nullptr); assert(opt != nullptr);
if (opt->nullable()) if (opt->nullable())
opt->deserialize("nil"); opt->deserialize("nil", ForwardCompatibilitySubstitutionRule::Disable);
} }
} }
@ -881,8 +917,10 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
// Do not unescape single string values, the unescaping is left to the calling shell. // Do not unescape single string values, the unescaping is left to the calling shell.
static_cast<ConfigOptionString*>(opt_base)->value = value; static_cast<ConfigOptionString*>(opt_base)->value = value;
} else { } else {
// Just bail out if the configuration value is not understood.
ConfigSubstitutionContext context(ForwardCompatibilitySubstitutionRule::Disable);
// Any scalar value of a type different from Bool and String. // Any scalar value of a type different from Bool and String.
if (! this->set_deserialize_nothrow(opt_key, value, false)) { if (! this->set_deserialize_nothrow(opt_key, value, context, false)) {
boost::nowide::cerr << "Invalid value supplied for --" << token.c_str() << std::endl; boost::nowide::cerr << "Invalid value supplied for --" << token.c_str() << std::endl;
return false; return false;
} }

View File

@ -16,6 +16,7 @@
#include "Exception.hpp" #include "Exception.hpp"
#include "Point.hpp" #include "Point.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/trim.hpp> #include <boost/algorithm/string/trim.hpp>
#include <boost/format/format_fwd.hpp> #include <boost/format/format_fwd.hpp>
#include <boost/property_tree/ptree_fwd.hpp> #include <boost/property_tree/ptree_fwd.hpp>
@ -120,6 +121,41 @@ enum PrinterTechnology : unsigned char
ptAny ptAny
}; };
enum ForwardCompatibilitySubstitutionRule
{
Disable,
Enable,
EnableSilent,
};
class ConfigOption;
class ConfigOptionDef;
// For forward definition of ConfigOption in ConfigOptionUniquePtr, we have to define a custom deleter.
struct ConfigOptionDeleter { void operator()(ConfigOption* p); };
using ConfigOptionUniquePtr = std::unique_ptr<ConfigOption, ConfigOptionDeleter>;
// When parsing a configuration value, if the old_value is not understood by this PrusaSlicer version,
// it is being substituted with some default value that this PrusaSlicer could work with.
// This structure serves to inform the user about the substitutions having been done during file import.
struct ConfigSubstitution {
const ConfigOptionDef *opt_def { nullptr };
std::string old_value;
ConfigOptionUniquePtr new_value;
};
using ConfigSubstitutions = std::vector<ConfigSubstitution>;
// Filled in by ConfigBase::set_deserialize_raw(), which based on "rule" either bails out
// or performs substitutions when encountering an unknown configuration value.
struct ConfigSubstitutionContext
{
ConfigSubstitutionContext(ForwardCompatibilitySubstitutionRule rl) : rule(rl) {}
bool empty() const throw() { return substitutions.empty(); }
ForwardCompatibilitySubstitutionRule rule;
ConfigSubstitutions substitutions;
};
// A generic value of a configuration option. // A generic value of a configuration option.
class ConfigOption { class ConfigOption {
public: public:
@ -1193,9 +1229,16 @@ public:
bool deserialize(const std::string &str, bool append = false) override bool deserialize(const std::string &str, bool append = false) override
{ {
UNUSED(append); UNUSED(append);
this->value = (str.compare("1") == 0); if (str == "1" || boost::iequals(str, "enabled") || boost::iequals(str, "on")) {
this->value = true;
return true; return true;
} }
if (str == "0" || boost::iequals(str, "disabled") || boost::iequals(str, "off")) {
this->value = false;
return true;
}
return false;
}
private: private:
friend class cereal::access; friend class cereal::access;
@ -1601,6 +1644,14 @@ public:
static const constexpr char *nocli = "~~~noCLI"; static const constexpr char *nocli = "~~~noCLI";
}; };
inline bool operator<(const ConfigSubstitution &lhs, const ConfigSubstitution &rhs) throw() {
return lhs.opt_def->opt_key < rhs.opt_def->opt_key ||
(lhs.opt_def->opt_key == rhs.opt_def->opt_key && lhs.old_value < rhs.old_value);
}
inline bool operator==(const ConfigSubstitution &lhs, const ConfigSubstitution &rhs) throw() {
return lhs.opt_def == rhs.opt_def && lhs.old_value == rhs.old_value;
}
// Map from a config option name to its definition. // Map from a config option name to its definition.
// The definition does not carry an actual value of the config option, only its constant default value. // The definition does not carry an actual value of the config option, only its constant default value.
// t_config_option_key is std::string // t_config_option_key is std::string
@ -1679,6 +1730,8 @@ public:
} }
}; };
// An abstract configuration store. // An abstract configuration store.
class ConfigBase : public ConfigOptionResolver class ConfigBase : public ConfigOptionResolver
{ {
@ -1766,9 +1819,11 @@ public:
// Set a configuration value from a string, it will call an overridable handle_legacy() // Set a configuration value from a string, it will call an overridable handle_legacy()
// to resolve renamed and removed configuration keys. // to resolve renamed and removed configuration keys.
bool set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append = false); bool set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions, bool append = false);
// May throw BadOptionTypeException() if the operation fails. // May throw BadOptionTypeException() if the operation fails.
void set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false); void set_deserialize(const t_config_option_key &opt_key, const std::string &str, ConfigSubstitutionContext& config_substitutions, bool append = false);
void set_deserialize_strict(const t_config_option_key &opt_key, const std::string &str, bool append = false)
{ ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; this->set_deserialize(opt_key, str, ctxt, append); }
struct SetDeserializeItem { struct SetDeserializeItem {
SetDeserializeItem(const char *opt_key, const char *opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {} SetDeserializeItem(const char *opt_key, const char *opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {}
SetDeserializeItem(const std::string &opt_key, const std::string &opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {} SetDeserializeItem(const std::string &opt_key, const std::string &opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {}
@ -1783,17 +1838,19 @@ public:
std::string opt_key; std::string opt_value; bool append = false; std::string opt_key; std::string opt_value; bool append = false;
}; };
// May throw BadOptionTypeException() if the operation fails. // May throw BadOptionTypeException() if the operation fails.
void set_deserialize(std::initializer_list<SetDeserializeItem> items); void set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions);
void set_deserialize_strict(std::initializer_list<SetDeserializeItem> items)
{ ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; this->set_deserialize(items, ctxt); }
double get_abs_value(const t_config_option_key &opt_key) const; double get_abs_value(const t_config_option_key &opt_key) const;
double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const;
void setenv_() const; void setenv_() const;
void load(const std::string &file); ConfigSubstitutions load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
void load_from_ini(const std::string &file); ConfigSubstitutions load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
void load_from_gcode_file(const std::string &file); ConfigSubstitutions load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
// Returns number of key/value pairs extracted. // Returns number of key/value pairs extracted.
size_t load_from_gcode_string(const char* str); size_t load_from_gcode_string(const char* str, ConfigSubstitutionContext& substitutions);
void load(const boost::property_tree::ptree &tree); ConfigSubstitutions load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);
void save(const std::string &file) const; void save(const std::string &file) const;
// Set all the nullable values to nils. // Set all the nullable values to nils.
@ -1801,7 +1858,7 @@ public:
private: private:
// Set a configuration value from a string. // Set a configuration value from a string.
bool set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &str, bool append); bool set_deserialize_raw(const t_config_option_key& opt_key_src, const std::string& value, ConfigSubstitutionContext& substitutions, bool append);
}; };
// Configuration store with dynamic number of configuration values. // Configuration store with dynamic number of configuration values.

View File

@ -423,7 +423,7 @@ namespace Slic3r {
_3MF_Importer(); _3MF_Importer();
~_3MF_Importer(); ~_3MF_Importer();
bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, bool check_version); bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version);
private: private:
void _destroy_xml_parser(); void _destroy_xml_parser();
@ -438,16 +438,16 @@ namespace Slic3r {
XML_ErrorString(XML_GetErrorCode(m_xml_parser)); XML_ErrorString(XML_GetErrorCode(m_xml_parser));
} }
bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config); bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions);
bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename); void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename);
bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
// handlers to parse the .model file // handlers to parse the .model file
@ -514,7 +514,7 @@ namespace Slic3r {
bool _handle_start_config_metadata(const char** attributes, unsigned int num_attributes); bool _handle_start_config_metadata(const char** attributes, unsigned int num_attributes);
bool _handle_end_config_metadata(); bool _handle_end_config_metadata();
bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes); bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions);
// callbacks to parse the .model file // callbacks to parse the .model file
static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes); static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes);
@ -543,7 +543,7 @@ namespace Slic3r {
_destroy_xml_parser(); _destroy_xml_parser();
} }
bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, bool check_version) bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version)
{ {
m_version = 0; m_version = 0;
m_check_version = check_version; m_check_version = check_version;
@ -564,7 +564,7 @@ namespace Slic3r {
m_curr_characters.clear(); m_curr_characters.clear();
clear_errors(); clear_errors();
return _load_model_from_file(filename, model, config); return _load_model_from_file(filename, model, config, config_substitutions);
} }
void _3MF_Importer::_destroy_xml_parser() void _3MF_Importer::_destroy_xml_parser()
@ -586,7 +586,7 @@ namespace Slic3r {
XML_StopParser(m_xml_parser, false); XML_StopParser(m_xml_parser, false);
} }
bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config) bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions)
{ {
mz_zip_archive archive; mz_zip_archive archive;
mz_zip_zero_struct(&archive); mz_zip_zero_struct(&archive);
@ -648,7 +648,7 @@ namespace Slic3r {
if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE))
{ {
// extract slic3r layer config ranges file // extract slic3r layer config ranges file
_extract_layer_config_ranges_from_archive(archive, stat); _extract_layer_config_ranges_from_archive(archive, stat, config_substitutions);
} }
else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE))
{ {
@ -663,7 +663,7 @@ namespace Slic3r {
else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE))
{ {
// extract slic3r print config file // extract slic3r print config file
_extract_print_config_from_archive(archive, stat, config, filename); _extract_print_config_from_archive(archive, stat, config, config_substitutions, filename);
} }
if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE)) if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE))
{ {
@ -734,7 +734,7 @@ namespace Slic3r {
if (metadata.key == "name") if (metadata.key == "name")
model_object->name = metadata.value; model_object->name = metadata.value;
else else
model_object->config.set_deserialize(metadata.key, metadata.value); model_object->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
} }
// select object's detected volumes // select object's detected volumes
@ -751,7 +751,7 @@ namespace Slic3r {
volumes_ptr = &volumes; volumes_ptr = &volumes;
} }
if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr)) if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr, config_substitutions))
return false; return false;
} }
@ -828,7 +828,10 @@ namespace Slic3r {
return true; return true;
} }
void _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename) void _3MF_Importer::_extract_print_config_from_archive(
mz_zip_archive& archive, const mz_zip_archive_file_stat& stat,
DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions,
const std::string& archive_filename)
{ {
if (stat.m_uncomp_size > 0) if (stat.m_uncomp_size > 0)
{ {
@ -839,7 +842,7 @@ namespace Slic3r {
add_error("Error while reading config data to buffer"); add_error("Error while reading config data to buffer");
return; return;
} }
config.load_from_gcode_string(buffer.data()); config.load_from_gcode_string(buffer.data(), config_substitutions);
} }
} }
@ -914,7 +917,7 @@ namespace Slic3r {
} }
} }
void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions)
{ {
if (stat.m_uncomp_size > 0) if (stat.m_uncomp_size > 0)
{ {
@ -963,8 +966,7 @@ namespace Slic3r {
continue; continue;
std::string opt_key = option.second.get<std::string>("<xmlattr>.opt_key"); std::string opt_key = option.second.get<std::string>("<xmlattr>.opt_key");
std::string value = option.second.data(); std::string value = option.second.data();
config.set_deserialize(opt_key, value, config_substitutions);
config.set_deserialize(opt_key, value);
} }
config_ranges[{ min_z, max_z }].assign_config(std::move(config)); config_ranges[{ min_z, max_z }].assign_config(std::move(config));
@ -1855,7 +1857,7 @@ namespace Slic3r {
return true; return true;
} }
bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes) bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions)
{ {
if (!object.volumes.empty()) if (!object.volumes.empty())
{ {
@ -1957,7 +1959,7 @@ namespace Slic3r {
else if (metadata.key == SOURCE_IN_INCHES) else if (metadata.key == SOURCE_IN_INCHES)
volume->source.is_converted_from_inches = metadata.value == "1"; volume->source.is_converted_from_inches = metadata.value == "1";
else else
volume->config.set_deserialize(metadata.key, metadata.value); volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
} }
} }
@ -2880,13 +2882,13 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv
return true; return true;
} }
bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version)
{ {
if ((path == nullptr) || (config == nullptr) || (model == nullptr)) if (path == nullptr || model == nullptr)
return false; return false;
_3MF_Importer importer; _3MF_Importer importer;
bool res = importer.load_model_from_file(path, *model, *config, check_version); bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version);
importer.log_errors(); importer.log_errors();
return res; return res;
} }

View File

@ -25,11 +25,12 @@ namespace Slic3r {
}; };
class Model; class Model;
struct ConfigSubstitutionContext;
class DynamicPrintConfig; class DynamicPrintConfig;
struct ThumbnailData; struct ThumbnailData;
// Load the content of a 3mf file into the given model and preset bundle. // Load the content of a 3mf file into the given model and preset bundle.
extern bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version); extern bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version);
// Save the given model and the config data contained in the given Print into a 3mf file. // Save the given model and the config data contained in the given Print into a 3mf file.
// The model could be modified during the export process if meshes are not repaired or have no shared vertices // The model could be modified during the export process if meshes are not repaired or have no shared vertices

View File

@ -62,10 +62,11 @@ namespace Slic3r
struct AMFParserContext struct AMFParserContext
{ {
AMFParserContext(XML_Parser parser, DynamicPrintConfig* config, Model* model) : AMFParserContext(XML_Parser parser, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model) :
m_parser(parser), m_parser(parser),
m_model(*model), m_model(*model),
m_config(config) m_config(config),
m_config_substitutions(config_substitutions)
{ {
m_path.reserve(12); m_path.reserve(12);
} }
@ -256,6 +257,8 @@ struct AMFParserContext
std::string m_value[5]; std::string m_value[5];
// Pointer to config to update if config data are stored inside the amf file // Pointer to config to update if config data are stored inside the amf file
DynamicPrintConfig *m_config { nullptr }; DynamicPrintConfig *m_config { nullptr };
// Config substitution rules and collected config substitution log.
ConfigSubstitutionContext *m_config_substitutions { nullptr };
private: private:
AMFParserContext& operator=(AMFParserContext&); AMFParserContext& operator=(AMFParserContext&);
@ -699,8 +702,9 @@ void AMFParserContext::endElement(const char * /* name */)
} }
case NODE_TYPE_METADATA: case NODE_TYPE_METADATA:
if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0) if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0) {
m_config->load_from_gcode_string(m_value[1].c_str()); m_config->load_from_gcode_string(m_value[1].c_str(), *m_config_substitutions);
}
else if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) { else if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) {
const char *opt_key = m_value[0].c_str() + 7; const char *opt_key = m_value[0].c_str() + 7;
if (print_config_def.options.find(opt_key) != print_config_def.options.end()) { if (print_config_def.options.find(opt_key) != print_config_def.options.end()) {
@ -718,7 +722,7 @@ void AMFParserContext::endElement(const char * /* name */)
config = &it->second; config = &it->second;
} }
if (config) if (config)
config->set_deserialize(opt_key, m_value[1]); config->set_deserialize(opt_key, m_value[1], *m_config_substitutions);
} else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) { } else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) {
// Parse object's layer height profile, a semicolon separated list of floats. // Parse object's layer height profile, a semicolon separated list of floats.
char *p = m_value[1].data(); char *p = m_value[1].data();
@ -843,7 +847,7 @@ void AMFParserContext::endDocument()
} }
// Load an AMF file into a provided model. // Load an AMF file into a provided model.
bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model) bool load_amf_file(const char *path, DynamicPrintConfig *config, ConfigSubstitutionContext *config_substitutions, Model *model)
{ {
if ((path == nullptr) || (model == nullptr)) if ((path == nullptr) || (model == nullptr))
return false; return false;
@ -860,7 +864,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model)
return false; return false;
} }
AMFParserContext ctx(parser, config, model); AMFParserContext ctx(parser, config, config_substitutions, model);
XML_SetUserData(parser, (void*)&ctx); XML_SetUserData(parser, (void*)&ctx);
XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement); XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement);
XML_SetCharacterDataHandler(parser, AMFParserContext::characters); XML_SetCharacterDataHandler(parser, AMFParserContext::characters);
@ -904,7 +908,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model)
return result; return result;
} }
bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, Model* model, bool check_version) bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version)
{ {
if (stat.m_uncomp_size == 0) if (stat.m_uncomp_size == 0)
{ {
@ -920,7 +924,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi
return false; return false;
} }
AMFParserContext ctx(parser, config, model); AMFParserContext ctx(parser, config, config_substitutions, model);
XML_SetUserData(parser, (void*)&ctx); XML_SetUserData(parser, (void*)&ctx);
XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement); XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement);
XML_SetCharacterDataHandler(parser, AMFParserContext::characters); XML_SetCharacterDataHandler(parser, AMFParserContext::characters);
@ -980,7 +984,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi
} }
// Load an AMF archive into a provided model. // Load an AMF archive into a provided model.
bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) bool load_amf_archive(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version)
{ {
if ((path == nullptr) || (model == nullptr)) if ((path == nullptr) || (model == nullptr))
return false; return false;
@ -1006,7 +1010,7 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model
{ {
try try
{ {
if (!extract_model_from_archive(archive, stat, config, model, check_version)) if (!extract_model_from_archive(archive, stat, config, config_substitutions, model, check_version))
{ {
close_zip_reader(&archive); close_zip_reader(&archive);
printf("Archive does not contain a valid model"); printf("Archive does not contain a valid model");
@ -1048,11 +1052,11 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model
// Load an AMF file into a provided model. // Load an AMF file into a provided model.
// If config is not a null pointer, updates it if the amf file/archive contains config data // If config is not a null pointer, updates it if the amf file/archive contains config data
bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) bool load_amf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version)
{ {
if (boost::iends_with(path, ".amf.xml")) if (boost::iends_with(path, ".amf.xml"))
// backward compatibility with older slic3r output // backward compatibility with older slic3r output
return load_amf_file(path, config, model); return load_amf_file(path, config, config_substitutions, model);
else if (boost::iends_with(path, ".amf")) else if (boost::iends_with(path, ".amf"))
{ {
boost::nowide::ifstream file(path, boost::nowide::ifstream::binary); boost::nowide::ifstream file(path, boost::nowide::ifstream::binary);
@ -1063,7 +1067,7 @@ bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool c
file.read(zip_mask.data(), 2); file.read(zip_mask.data(), 2);
file.close(); file.close();
return (zip_mask == "PK") ? load_amf_archive(path, config, model, check_version) : load_amf_file(path, config, model); return (zip_mask == "PK") ? load_amf_archive(path, config, config_substitutions, model, check_version) : load_amf_file(path, config, config_substitutions, model);
} }
else else
return false; return false;

View File

@ -7,7 +7,7 @@ class Model;
class DynamicPrintConfig; class DynamicPrintConfig;
// Load the content of an amf file into the given model and configuration. // Load the content of an amf file into the given model and configuration.
extern bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version); extern bool load_amf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version);
// Save the given model and the config data into an amf file. // Save the given model and the config data into an amf file.
// The model could be modified during the export process if meshes are not repaired or have no shared vertices // The model could be modified during the export process if meshes are not repaired or have no shared vertices

View File

@ -286,11 +286,8 @@ static void extract_model_from_archive(
volume->name = name; volume->name = name;
} }
// Set the extruder to the volume. // Set the extruder to the volume.
if (extruder_id != (unsigned int)-1) { if (extruder_id != (unsigned int)-1)
char str_extruder[64]; volume->config.set("extruder", int(extruder_id));
sprintf(str_extruder, "%ud", extruder_id);
volume->config.set_deserialize("extruder", str_extruder);
}
} }
// Load a PrusaControl project file into a provided model. // Load a PrusaControl project file into a provided model.

View File

@ -287,13 +287,13 @@ std::vector<ExPolygons> extract_slices_from_sla_archive(
} // namespace } // namespace
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out) ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
{ {
ArchiveData arch = extract_sla_archive(zipfname, "png"); ArchiveData arch = extract_sla_archive(zipfname, "png");
out.load(arch.profile); return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
} }
void import_sla_archive( ConfigSubstitutions import_sla_archive(
const std::string & zipfname, const std::string & zipfname,
Vec2i windowsize, Vec2i windowsize,
TriangleMesh & out, TriangleMesh & out,
@ -305,7 +305,7 @@ void import_sla_archive(
windowsize.y() = std::max(2, windowsize.y()); windowsize.y() = std::max(2, windowsize.y());
ArchiveData arch = extract_sla_archive(zipfname, "thumbnail"); ArchiveData arch = extract_sla_archive(zipfname, "thumbnail");
profile.load(arch.profile); ConfigSubstitutions config_substitutions = profile.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
RasterParams rstp = get_raster_params(profile); RasterParams rstp = get_raster_params(profile);
rstp.win = {windowsize.y(), windowsize.x()}; rstp.win = {windowsize.y(), windowsize.x()};
@ -317,6 +317,8 @@ void import_sla_archive(
if (!slices.empty()) if (!slices.empty())
out = slices_to_triangle_mesh(slices, 0, slicp.layerh, slicp.initial_layerh); out = slices_to_triangle_mesh(slices, 0, slicp.layerh, slicp.initial_layerh);
return config_substitutions;
} }
using ConfMap = std::map<std::string, std::string>; using ConfMap = std::map<std::string, std::string>;

View File

@ -38,23 +38,23 @@ public:
} }
}; };
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out); ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
void import_sla_archive( ConfigSubstitutions import_sla_archive(
const std::string & zipfname, const std::string & zipfname,
Vec2i windowsize, Vec2i windowsize,
TriangleMesh & out, TriangleMesh & out,
DynamicPrintConfig & profile, DynamicPrintConfig & profile,
std::function<bool(int)> progr = [](int) { return true; }); std::function<bool(int)> progr = [](int) { return true; });
inline void import_sla_archive( inline ConfigSubstitutions import_sla_archive(
const std::string & zipfname, const std::string & zipfname,
Vec2i windowsize, Vec2i windowsize,
TriangleMesh & out, TriangleMesh & out,
std::function<bool(int)> progr = [](int) { return true; }) std::function<bool(int)> progr = [](int) { return true; })
{ {
DynamicPrintConfig profile; DynamicPrintConfig profile;
import_sla_archive(zipfname, windowsize, out, profile, progr); return import_sla_archive(zipfname, windowsize, out, profile, progr);
} }
} // namespace Slic3r::sla } // namespace Slic3r::sla

View File

@ -822,7 +822,10 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr
if (m_producer == EProducer::PrusaSlicer || m_producer == EProducer::Slic3rPE || m_producer == EProducer::Slic3r) { if (m_producer == EProducer::PrusaSlicer || m_producer == EProducer::Slic3rPE || m_producer == EProducer::Slic3r) {
DynamicPrintConfig config; DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults()); config.apply(FullPrintConfig::defaults());
config.load_from_gcode_file(filename); // Silently substitute unknown values by new ones for loading configurations from PrusaSlicer's own G-code.
// Showing substitution log or errors may make sense, but we are not really reading many values from the G-code config,
// thus a probability of incorrect substitution is low and the G-code viewer is a consumer-only anyways.
config.load_from_gcode_file(filename, ForwardCompatibilitySubstitutionRule::EnableSilent);
apply_config(config); apply_config(config);
} }
} }

View File

@ -94,13 +94,17 @@ void Model::update_links_bottom_up_recursive()
} }
} }
Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances, bool check_version) // Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well.
Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options)
{ {
Model model; Model model;
DynamicPrintConfig temp_config; DynamicPrintConfig temp_config;
ConfigSubstitutionContext temp_config_substitutions_context(ForwardCompatibilitySubstitutionRule::EnableSilent);
if (config == nullptr) if (config == nullptr)
config = &temp_config; config = &temp_config;
if (config_substitutions == nullptr)
config_substitutions = &temp_config_substitutions_context;
bool result = false; bool result = false;
if (boost::algorithm::iends_with(input_file, ".stl")) if (boost::algorithm::iends_with(input_file, ".stl"))
@ -108,9 +112,10 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
else if (boost::algorithm::iends_with(input_file, ".obj")) else if (boost::algorithm::iends_with(input_file, ".obj"))
result = load_obj(input_file.c_str(), &model); result = load_obj(input_file.c_str(), &model);
else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml")) else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml"))
result = load_amf(input_file.c_str(), config, &model, check_version); result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion);
else if (boost::algorithm::iends_with(input_file, ".3mf")) else if (boost::algorithm::iends_with(input_file, ".3mf"))
result = load_3mf(input_file.c_str(), config, &model, false); //FIXME options & LoadAttribute::CheckVersion ?
result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false);
else if (boost::algorithm::iends_with(input_file, ".prusa")) else if (boost::algorithm::iends_with(input_file, ".prusa"))
result = load_prus(input_file.c_str(), &model); result = load_prus(input_file.c_str(), &model);
else else
@ -125,24 +130,29 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
for (ModelObject *o : model.objects) for (ModelObject *o : model.objects)
o->input_file = input_file; o->input_file = input_file;
if (add_default_instances) if (options & LoadAttribute::AddDefaultInstances)
model.add_default_instances(); model.add_default_instances();
CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config); CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config);
CustomGCode::check_mode_for_custom_gcode_per_print_z(model.custom_gcode_per_print_z); CustomGCode::check_mode_for_custom_gcode_per_print_z(model.custom_gcode_per_print_z);
sort_remove_duplicates(config_substitutions->substitutions);
return model; return model;
} }
Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances, bool check_version) // Loading model from a file (3MF or AMF), not from a simple geometry file (STL or OBJ).
Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options)
{ {
assert(config != nullptr);
assert(config_substitutions != nullptr);
Model model; Model model;
bool result = false; bool result = false;
if (boost::algorithm::iends_with(input_file, ".3mf")) if (boost::algorithm::iends_with(input_file, ".3mf"))
result = load_3mf(input_file.c_str(), config, &model, check_version); result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, options & LoadAttribute::CheckVersion);
else if (boost::algorithm::iends_with(input_file, ".zip.amf")) else if (boost::algorithm::iends_with(input_file, ".zip.amf"))
result = load_amf(input_file.c_str(), config, &model, check_version); result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion);
else else
throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension."); throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension.");
@ -163,7 +173,7 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
o->input_file = input_file; o->input_file = input_file;
} }
if (add_default_instances) if (options & LoadAttribute::AddDefaultInstances)
model.add_default_instances(); model.add_default_instances();
CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config); CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config);
@ -396,13 +406,12 @@ bool Model::looks_like_multipart_object() const
} }
// Generate next extruder ID string, in the range of (1, max_extruders). // Generate next extruder ID string, in the range of (1, max_extruders).
static inline std::string auto_extruder_id(unsigned int max_extruders, unsigned int &cntr) static inline int auto_extruder_id(unsigned int max_extruders, unsigned int &cntr)
{ {
char str_extruder[64]; int out = ++ cntr;
sprintf(str_extruder, "%ud", cntr + 1); if (cntr == max_extruders)
if (++ cntr == max_extruders)
cntr = 0; cntr = 0;
return str_extruder; return out;
} }
void Model::convert_multipart_object(unsigned int max_extruders) void Model::convert_multipart_object(unsigned int max_extruders)
@ -429,7 +438,7 @@ void Model::convert_multipart_object(unsigned int max_extruders)
auto copy_volume = [o, max_extruders, &counter, &extruder_counter](ModelVolume *new_v) { auto copy_volume = [o, max_extruders, &counter, &extruder_counter](ModelVolume *new_v) {
assert(new_v != nullptr); assert(new_v != nullptr);
new_v->name = o->name + "_" + std::to_string(counter++); new_v->name = o->name + "_" + std::to_string(counter++);
new_v->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter)); new_v->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
return new_v; return new_v;
}; };
if (o->instances.empty()) { if (o->instances.empty()) {
@ -1706,7 +1715,7 @@ size_t ModelVolume::split(unsigned int max_extruders)
this->object->volumes[ivolume]->center_geometry_after_creation(); this->object->volumes[ivolume]->center_geometry_after_creation();
this->object->volumes[ivolume]->translate(offset); this->object->volumes[ivolume]->translate(offset);
this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1); this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1);
this->object->volumes[ivolume]->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter)); this->object->volumes[ivolume]->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
delete mesh; delete mesh;
++ idx; ++ idx;
} }

View File

@ -12,6 +12,7 @@
#include "TriangleMesh.hpp" #include "TriangleMesh.hpp"
#include "Arrange.hpp" #include "Arrange.hpp"
#include "CustomGCode.hpp" #include "CustomGCode.hpp"
#include "enum_bitmask.hpp"
#include <map> #include <map>
#include <memory> #include <memory>
@ -979,8 +980,20 @@ public:
OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model) OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model)
static Model read_from_file(const std::string& input_file, DynamicPrintConfig* config = nullptr, bool add_default_instances = true, bool check_version = false); enum class LoadAttribute : int {
static Model read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances = true, bool check_version = false); AddDefaultInstances,
CheckVersion
};
using LoadAttributes = enum_bitmask<LoadAttribute>;
static Model read_from_file(
const std::string& input_file,
DynamicPrintConfig* config = nullptr, ConfigSubstitutionContext* config_substitutions = nullptr,
LoadAttributes options = LoadAttribute::AddDefaultInstances);
static Model read_from_archive(
const std::string& input_file,
DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions,
LoadAttributes options = LoadAttribute::AddDefaultInstances);
// Add a new ModelObject to this Model, generate a new ID for this ModelObject. // Add a new ModelObject to this Model, generate a new ID for this ModelObject.
ModelObject* add_object(); ModelObject* add_object();
@ -1043,6 +1056,8 @@ private:
} }
}; };
ENABLE_ENUM_BITMASK_OPERATORS(Model::LoadAttribute)
#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE #undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE #undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE

View File

@ -647,7 +647,9 @@ void PresetCollection::add_default_preset(const std::vector<std::string> &keys,
// Load all presets found in dir_path. // Load all presets found in dir_path.
// Throws an exception on error. // Throws an exception on error.
void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir) void PresetCollection::load_presets(
const std::string &dir_path, const std::string &subdir,
PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule)
{ {
// Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points, // Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points,
// see https://github.com/prusa3d/PrusaSlicer/issues/732 // see https://github.com/prusa3d/PrusaSlicer/issues/732
@ -674,7 +676,9 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri
// Load the preset file, apply preset values on top of defaults. // Load the preset file, apply preset values on top of defaults.
try { try {
DynamicPrintConfig config; DynamicPrintConfig config;
config.load_from_ini(preset.file); ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule);
if (! config_substitutions.empty())
substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions) });
// Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field. // Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field.
const Preset &default_preset = this->default_preset_for(config); const Preset &default_preset = this->default_preset_for(config);
preset.config = default_preset.config; preset.config = default_preset.config;
@ -1546,7 +1550,9 @@ PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vector<std::str
// Load all printers found in dir_path. // Load all printers found in dir_path.
// Throws an exception on error. // Throws an exception on error.
void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const std::string& subdir) void PhysicalPrinterCollection::load_printers(
const std::string& dir_path, const std::string& subdir,
PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule)
{ {
// Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points, // Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points,
// see https://github.com/prusa3d/PrusaSlicer/issues/732 // see https://github.com/prusa3d/PrusaSlicer/issues/732
@ -1572,7 +1578,9 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const
// Load the preset file, apply preset values on top of defaults. // Load the preset file, apply preset values on top of defaults.
try { try {
DynamicPrintConfig config; DynamicPrintConfig config;
config.load_from_ini(printer.file); ConfigSubstitutions config_substitutions = config.load_from_ini(printer.file, substitution_rule);
if (! config_substitutions.empty())
substitutions.push_back({ name, Preset::TYPE_PHYSICAL_PRINTER, PresetConfigSubstitutions::Source::UserFile, printer.file, std::move(config_substitutions) });
printer.update_from_config(config); printer.update_from_config(config);
printer.loaded = true; printer.loaded = true;
} }

View File

@ -112,7 +112,9 @@ public:
TYPE_FILAMENT, TYPE_FILAMENT,
TYPE_SLA_MATERIAL, TYPE_SLA_MATERIAL,
TYPE_PRINTER, TYPE_PRINTER,
TYPE_COUNT, // This type is here to support PresetConfigSubstitutions for physical printers, however it does not belong to the Preset class,
// PhysicalPrinter class is used instead.
TYPE_PHYSICAL_PRINTER,
}; };
Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {} Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {}
@ -250,6 +252,27 @@ enum class PresetSelectCompatibleType {
Always Always
}; };
// Substitutions having been performed during parsing a single configuration file.
struct PresetConfigSubstitutions {
// User readable preset name.
std::string preset_name;
// Type of the preset (Print / Filament / Printer ...)
Preset::Type preset_type;
enum class Source {
UserFile,
ConfigBundle,
};
Source preset_source;
// Source of the preset. It may be empty in case of a ConfigBundle being loaded.
std::string preset_file;
// What config value has been substituted with what.
ConfigSubstitutions substitutions;
};
// Substitutions having been performed during parsing a set of configuration files, for example when starting up
// PrusaSlicer and reading the user Print / Filament / Printer profiles.
using PresetsConfigSubstitutions = std::vector<PresetConfigSubstitutions>;
// Collections of presets of the same type (one of the Print, Filament or Printer type). // Collections of presets of the same type (one of the Print, Filament or Printer type).
class PresetCollection class PresetCollection
{ {
@ -280,7 +303,7 @@ public:
void add_default_preset(const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name); void add_default_preset(const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name);
// Load ini files of the particular type from the provided directory path. // Load ini files of the particular type from the provided directory path.
void load_presets(const std::string &dir_path, const std::string &subdir); void load_presets(const std::string &dir_path, const std::string &subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule);
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets // Load a preset from an already parsed config file, insert it into the sorted sequence of presets
// and select it, losing previous modifications. // and select it, losing previous modifications.
@ -652,7 +675,7 @@ public:
const std::deque<PhysicalPrinter>& operator()() const { return m_printers; } const std::deque<PhysicalPrinter>& operator()() const { return m_printers; }
// Load ini files of the particular type from the provided directory path. // Load ini files of the particular type from the provided directory path.
void load_printers(const std::string& dir_path, const std::string& subdir); void load_printers(const std::string& dir_path, const std::string& subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule);
void load_printers_from_presets(PrinterPresetCollection &printer_presets); void load_printers_from_presets(PrinterPresetCollection &printer_presets);
// Load printer from the loaded configuration // Load printer from the loaded configuration
void load_printer(const std::string& path, const std::string& name, DynamicPrintConfig&& config, bool select, bool save=false); void load_printer(const std::string& path, const std::string& name, DynamicPrintConfig&& config, bool select, bool save=false);

View File

@ -162,7 +162,7 @@ void PresetBundle::setup_directories()
} }
} }
void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_model_id) PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule, const std::string &preferred_model_id)
{ {
// First load the vendor specific system presets. // First load the vendor specific system presets.
std::string errors_cummulative = this->load_system_presets(); std::string errors_cummulative = this->load_system_presets();
@ -175,33 +175,35 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_
// Store the print/filament/printer presets at the same location as the upstream Slic3r. // Store the print/filament/printer presets at the same location as the upstream Slic3r.
#endif #endif
; ;
PresetsConfigSubstitutions substitutions;
try { try {
this->prints.load_presets(dir_user_presets, "print"); this->prints.load_presets(dir_user_presets, "print", substitutions, substitution_rule);
} catch (const std::runtime_error &err) { } catch (const std::runtime_error &err) {
errors_cummulative += err.what(); errors_cummulative += err.what();
} }
try { try {
this->sla_prints.load_presets(dir_user_presets, "sla_print"); this->sla_prints.load_presets(dir_user_presets, "sla_print", substitutions, substitution_rule);
} catch (const std::runtime_error &err) { } catch (const std::runtime_error &err) {
errors_cummulative += err.what(); errors_cummulative += err.what();
} }
try { try {
this->filaments.load_presets(dir_user_presets, "filament"); this->filaments.load_presets(dir_user_presets, "filament", substitutions, substitution_rule);
} catch (const std::runtime_error &err) { } catch (const std::runtime_error &err) {
errors_cummulative += err.what(); errors_cummulative += err.what();
} }
try { try {
this->sla_materials.load_presets(dir_user_presets, "sla_material"); this->sla_materials.load_presets(dir_user_presets, "sla_material", substitutions, substitution_rule);
} catch (const std::runtime_error &err) { } catch (const std::runtime_error &err) {
errors_cummulative += err.what(); errors_cummulative += err.what();
} }
try { try {
this->printers.load_presets(dir_user_presets, "printer"); this->printers.load_presets(dir_user_presets, "printer", substitutions, substitution_rule);
} catch (const std::runtime_error &err) { } catch (const std::runtime_error &err) {
errors_cummulative += err.what(); errors_cummulative += err.what();
} }
try { try {
this->physical_printers.load_printers(dir_user_presets, "physical_printer"); this->physical_printers.load_printers(dir_user_presets, "physical_printer", substitutions, substitution_rule);
} catch (const std::runtime_error &err) { } catch (const std::runtime_error &err) {
errors_cummulative += err.what(); errors_cummulative += err.what();
} }
@ -211,6 +213,8 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_
throw Slic3r::RuntimeError(errors_cummulative); throw Slic3r::RuntimeError(errors_cummulative);
this->load_selections(config, preferred_model_id); this->load_selections(config, preferred_model_id);
return substitutions;
} }
// Load system presets into this PresetBundle. // Load system presets into this PresetBundle.
@ -230,13 +234,13 @@ std::string PresetBundle::load_system_presets()
// Load the config bundle, flatten it. // Load the config bundle, flatten it.
if (first) { if (first) {
// Reset this PresetBundle and load the first vendor config. // Reset this PresetBundle and load the first vendor config.
this->load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM); this->load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem);
first = false; first = false;
} else { } else {
// Load the other vendor configs, merge them with this PresetBundle. // Load the other vendor configs, merge them with this PresetBundle.
// Report duplicate profiles. // Report duplicate profiles.
PresetBundle other; PresetBundle other;
other.load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM); other.load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem);
std::vector<std::string> duplicates = this->merge_presets(std::move(other)); std::vector<std::string> duplicates = this->merge_presets(std::move(other));
if (! duplicates.empty()) { if (! duplicates.empty()) {
errors_cummulative += "Vendor configuration file " + name + " contains the following presets with names used by other vendors: "; errors_cummulative += "Vendor configuration file " + name + " contains the following presets with names used by other vendors: ";
@ -665,15 +669,15 @@ DynamicPrintConfig PresetBundle::full_sla_config() const
// Instead of a config file, a G-code may be loaded containing the full set of parameters. // Instead of a config file, a G-code may be loaded containing the full set of parameters.
// In the future the configuration will likely be read from an AMF file as well. // In the future the configuration will likely be read from an AMF file as well.
// If the file is loaded successfully, its print / filament / printer profiles will be activated. // If the file is loaded successfully, its print / filament / printer profiles will be activated.
void PresetBundle::load_config_file(const std::string &path) ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule)
{ {
if (is_gcode_file(path)) { if (is_gcode_file(path)) {
DynamicPrintConfig config; DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults()); config.apply(FullPrintConfig::defaults());
config.load_from_gcode_file(path); ConfigSubstitutions config_substitutions = config.load_from_gcode_file(path, compatibility_rule);
Preset::normalize(config); Preset::normalize(config);
load_config_file_config(path, true, std::move(config)); load_config_file_config(path, true, std::move(config));
return; return config_substitutions;
} }
// 1) Try to load the config file into a boost property tree. // 1) Try to load the config file into a boost property tree.
@ -692,6 +696,7 @@ void PresetBundle::load_config_file(const std::string &path)
// 2) Continue based on the type of the configuration file. // 2) Continue based on the type of the configuration file.
ConfigFileType config_file_type = guess_config_file_type(tree); ConfigFileType config_file_type = guess_config_file_type(tree);
ConfigSubstitutions config_substitutions;
switch (config_file_type) { switch (config_file_type) {
case CONFIG_FILE_TYPE_UNKNOWN: case CONFIG_FILE_TYPE_UNKNOWN:
throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path); throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path);
@ -702,15 +707,18 @@ void PresetBundle::load_config_file(const std::string &path)
// Initialize a config from full defaults. // Initialize a config from full defaults.
DynamicPrintConfig config; DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults()); config.apply(FullPrintConfig::defaults());
config.load(tree); config_substitutions = config.load(tree, compatibility_rule);
Preset::normalize(config); Preset::normalize(config);
load_config_file_config(path, true, std::move(config)); load_config_file_config(path, true, std::move(config));
break; return config_substitutions;
} }
case CONFIG_FILE_TYPE_CONFIG_BUNDLE: case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
load_config_file_config_bundle(path, tree); return load_config_file_config_bundle(path, tree);
break;
} }
// This shall never happen. Suppres compiler warnings.
assert(false);
return ConfigSubstitutions{};
} }
// Load a config file from a boost property_tree. This is a private method called from load_config_file. // Load a config file from a boost property_tree. This is a private method called from load_config_file.
@ -882,16 +890,23 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
} }
// Load the active configuration of a config bundle from a boost property_tree. This is a private method called from load_config_file. // Load the active configuration of a config bundle from a boost property_tree. This is a private method called from load_config_file.
void PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree) ConfigSubstitutions PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree)
{ {
// 1) Load the config bundle into a temp data. // 1) Load the config bundle into a temp data.
PresetBundle tmp_bundle; PresetBundle tmp_bundle;
// Load the config bundle, don't save the loaded presets to user profile directory. // Load the config bundle, but don't save the loaded presets to user profile directory, as only the presets marked as active in the loaded preset bundle
tmp_bundle.load_configbundle(path, 0); // will be loaded into the master PresetBundle and activated.
auto [presets_substitutions, presets_imported] = tmp_bundle.load_configbundle(path, {});
std::string bundle_name = std::string(" - ") + boost::filesystem::path(path).filename().string(); std::string bundle_name = std::string(" - ") + boost::filesystem::path(path).filename().string();
// 2) Extract active configs from the config bundle, copy them and activate them in this bundle. // 2) Extract active configs from the config bundle, copy them and activate them in this bundle.
auto load_one = [this, &path, &bundle_name](PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string { ConfigSubstitutions config_substitutions;
auto load_one = [this, &path, &bundle_name, &presets_substitutions = presets_substitutions, &config_substitutions](PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string {
// If there are substitutions reported for this preset, move them to config_substitutions.
if (auto it = std::find_if(presets_substitutions.begin(), presets_substitutions.end(), [&preset_name_src](const PresetConfigSubstitutions& subs){ return subs.preset_name == preset_name_src; });
it != presets_substitutions.end() && ! it->substitutions.empty())
append(config_substitutions, std::move(it->substitutions));
Preset *preset_src = collection_src.find_preset(preset_name_src, false); Preset *preset_src = collection_src.find_preset(preset_name_src, false);
Preset *preset_dst = collection_dst.find_preset(preset_name_src, false); Preset *preset_dst = collection_dst.find_preset(preset_name_src, false);
assert(preset_src != nullptr); assert(preset_src != nullptr);
@ -941,6 +956,9 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const
this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false); this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false);
this->update_compatible(PresetSelectCompatibleType::Never); this->update_compatible(PresetSelectCompatibleType::Never);
sort_remove_duplicates(config_substitutions);
return std::move(config_substitutions);
} }
// Process the Config Bundle loaded as a Boost property tree. // Process the Config Bundle loaded as a Boost property tree.
@ -1085,11 +1103,20 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree, co
// Load a config bundle file, into presets and store the loaded presets into separate files // Load a config bundle file, into presets and store the loaded presets into separate files
// of the local configuration directory. // of the local configuration directory.
size_t PresetBundle::load_configbundle(const std::string &path, unsigned int flags) std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(const std::string &path, LoadConfigBundleAttributes flags)
{ {
if (flags & (LOAD_CFGBNDLE_RESET_USER_PROFILE | LOAD_CFGBNDLE_SYSTEM)) // Enable substitutions for user config bundle, throw an exception when loading a system profile.
// Reset this bundle, delete user profile files if LOAD_CFGBNDLE_SAVE. ConfigSubstitutionContext substitution_context {
this->reset(flags & LOAD_CFGBNDLE_SAVE); flags.has(LoadConfigBundleAttribute::LoadSystem) ?
ForwardCompatibilitySubstitutionRule::Disable :
ForwardCompatibilitySubstitutionRule::Enable
};
PresetsConfigSubstitutions substitutions;
if (flags.has(LoadConfigBundleAttribute::ResetUserProfile) || flags.has(LoadConfigBundleAttribute::LoadSystem))
// Reset this bundle, delete user profile files if SaveImported.
this->reset(flags.has(LoadConfigBundleAttribute::SaveImported));
// 1) Read the complete config file into a boost::property_tree. // 1) Read the complete config file into a boost::property_tree.
namespace pt = boost::property_tree; namespace pt = boost::property_tree;
@ -1102,25 +1129,24 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
} }
const VendorProfile *vendor_profile = nullptr; const VendorProfile *vendor_profile = nullptr;
if (flags & (LOAD_CFGBNDLE_SYSTEM | LOAD_CFGBUNDLE_VENDOR_ONLY)) { if (flags.has(LoadConfigBundleAttribute::LoadSystem) || flags.has(LoadConfigBundleAttribute::LoadVendorOnly)) {
auto vp = VendorProfile::from_ini(tree, path); auto vp = VendorProfile::from_ini(tree, path);
if (vp.models.size() == 0) { if (vp.models.size() == 0) {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer model defined.") % path; BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer model defined.") % path;
return 0; return std::make_pair(PresetsConfigSubstitutions{}, 0);
} else if (vp.num_variants() == 0) { } else if (vp.num_variants() == 0) {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer variant defined") % path; BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer variant defined") % path;
return 0; return std::make_pair(PresetsConfigSubstitutions{}, 0);
} }
vendor_profile = &this->vendors.insert({vp.id, vp}).first->second; vendor_profile = &this->vendors.insert({vp.id, vp}).first->second;
} }
if (flags & LOAD_CFGBUNDLE_VENDOR_ONLY) { if (flags.has(LoadConfigBundleAttribute::LoadVendorOnly))
return 0; return std::make_pair(PresetsConfigSubstitutions{}, 0);
}
// 1.5) Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed. // 1.5) Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed.
// If loading a user config bundle, do not flatten with the system profiles, but keep the "inherits" flag intact. // If loading a user config bundle, do not flatten with the system profiles, but keep the "inherits" flag intact.
flatten_configbundle_hierarchy(tree, ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) ? this : nullptr); flatten_configbundle_hierarchy(tree, flags.has(LoadConfigBundleAttribute::LoadSystem) ? nullptr : this);
// 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files. // 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files.
// Parse the obsolete preset names, to be deleted when upgrading from the old configuration structure. // Parse the obsolete preset names, to be deleted when upgrading from the old configuration structure.
@ -1224,7 +1250,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
DynamicPrintConfig config; DynamicPrintConfig config;
std::string alias_name; std::string alias_name;
std::vector<std::string> renamed_from; std::vector<std::string> renamed_from;
auto parse_config_section = [&section, &alias_name, &renamed_from, &path](DynamicPrintConfig &config) { auto parse_config_section = [&section, &alias_name, &renamed_from, &substitution_context, &path](DynamicPrintConfig &config) {
substitution_context.substitutions.clear();
for (auto &kvp : section.second) { for (auto &kvp : section.second) {
if (kvp.first == "alias") if (kvp.first == "alias")
alias_name = kvp.second.data(); alias_name = kvp.second.data();
@ -1234,7 +1261,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
section.first << "\" contains invalid \"renamed_from\" key, which is being ignored."; section.first << "\" contains invalid \"renamed_from\" key, which is being ignored.";
} }
} }
config.set_deserialize(kvp.first, kvp.second.data()); // Throws on parsing error. For system presets, no substituion is being done, but an exception is thrown.
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
} }
}; };
if (presets == &this->printers) { if (presets == &this->printers) {
@ -1255,7 +1283,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
if (! incorrect_keys.empty()) if (! incorrect_keys.empty())
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" <<
section.first << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; section.first << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed";
if ((flags & LOAD_CFGBNDLE_SYSTEM) && presets == &printers) { if (flags.has(LoadConfigBundleAttribute::LoadSystem) && presets == &printers) {
// Filter out printer presets, which are not mentioned in the vendor profile. // Filter out printer presets, which are not mentioned in the vendor profile.
// These presets are considered not installed. // These presets are considered not installed.
auto printer_model = config.opt_string("printer_model"); auto printer_model = config.opt_string("printer_model");
@ -1290,7 +1318,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
section.first << "\" has already been loaded from another Confing Bundle."; section.first << "\" has already been loaded from another Confing Bundle.";
continue; continue;
} }
} else if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) { } else if (! flags.has(LoadConfigBundleAttribute::LoadSystem)) {
// This is a user config bundle. // This is a user config bundle.
const Preset *existing = presets->find_preset(preset_name, false); const Preset *existing = presets->find_preset(preset_name, false);
if (existing != nullptr) { if (existing != nullptr) {
@ -1319,9 +1347,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
/ presets->section_name() / file_name).make_preferred(); / presets->section_name() / file_name).make_preferred();
// Load the preset into the list of presets, save it to disk. // Load the preset into the list of presets, save it to disk.
Preset &loaded = presets->load_preset(file_path.string(), preset_name, std::move(config), false); Preset &loaded = presets->load_preset(file_path.string(), preset_name, std::move(config), false);
if (flags & LOAD_CFGBNDLE_SAVE) if (flags.has(LoadConfigBundleAttribute::SaveImported))
loaded.save(); loaded.save();
if (flags & LOAD_CFGBNDLE_SYSTEM) { if (flags.has(LoadConfigBundleAttribute::LoadSystem)) {
loaded.is_system = true; loaded.is_system = true;
loaded.vendor = vendor_profile; loaded.vendor = vendor_profile;
} }
@ -1342,7 +1370,10 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
else else
loaded.alias = std::move(alias_name); loaded.alias = std::move(alias_name);
loaded.renamed_from = std::move(renamed_from); loaded.renamed_from = std::move(renamed_from);
if (! substitution_context.empty())
substitutions.push_back({
preset_name, presets->type(), PresetConfigSubstitutions::Source::ConfigBundle,
std::string(), std::move(substitution_context.substitutions) });
++ presets_loaded; ++ presets_loaded;
} }
@ -1351,8 +1382,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
const DynamicPrintConfig& default_config = ph_printers->default_config(); const DynamicPrintConfig& default_config = ph_printers->default_config();
DynamicPrintConfig config = default_config; DynamicPrintConfig config = default_config;
substitution_context.substitutions.clear();
for (auto& kvp : section.second) for (auto& kvp : section.second)
config.set_deserialize(kvp.first, kvp.second.data()); config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
// Report configuration fields, which are misplaced into a wrong group. // Report configuration fields, which are misplaced into a wrong group.
std::string incorrect_keys = Preset::remove_invalid_keys(config, default_config); std::string incorrect_keys = Preset::remove_invalid_keys(config, default_config);
@ -1378,14 +1410,17 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
#endif #endif
/ "physical_printer" / file_name).make_preferred(); / "physical_printer" / file_name).make_preferred();
// Load the preset into the list of presets, save it to disk. // Load the preset into the list of presets, save it to disk.
ph_printers->load_printer(file_path.string(), ph_printer_name, std::move(config), false, flags & LOAD_CFGBNDLE_SAVE); ph_printers->load_printer(file_path.string(), ph_printer_name, std::move(config), false, flags.has(LoadConfigBundleAttribute::SaveImported));
if (! substitution_context.empty())
++ph_printers_loaded; substitutions.push_back({
ph_printer_name, Preset::TYPE_PHYSICAL_PRINTER, PresetConfigSubstitutions::Source::ConfigBundle,
std::string(), std::move(substitution_context.substitutions) });
++ ph_printers_loaded;
} }
} }
// 3) Activate the presets and physical printer if any exists. // 3) Activate the presets and physical printer if any exists.
if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) { if (! flags.has(LoadConfigBundleAttribute::LoadSystem)) {
if (! active_print.empty()) if (! active_print.empty())
prints.select_preset_by_name(active_print, true); prints.select_preset_by_name(active_print, true);
if (! active_sla_print.empty()) if (! active_sla_print.empty())
@ -1405,7 +1440,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
this->update_compatible(PresetSelectCompatibleType::Never); this->update_compatible(PresetSelectCompatibleType::Never);
} }
return presets_loaded + ph_printers_loaded; return std::make_pair(std::move(substitutions), presets_loaded + ph_printers_loaded);
} }
void PresetBundle::update_multi_material_filament_presets() void PresetBundle::update_multi_material_filament_presets()

View File

@ -3,6 +3,7 @@
#include "Preset.hpp" #include "Preset.hpp"
#include "AppConfig.hpp" #include "AppConfig.hpp"
#include "enum_bitmask.hpp"
#include <memory> #include <memory>
#include <unordered_map> #include <unordered_map>
@ -26,7 +27,7 @@ public:
// Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets. // Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets.
// Load selections (current print, current filaments, current printer) from config.ini // Load selections (current print, current filaments, current printer) from config.ini
// This is done just once on application start up. // This is done just once on application start up.
void load_presets(AppConfig &config, const std::string &preferred_model_id = ""); PresetsConfigSubstitutions load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule rule, const std::string &preferred_model_id = "");
// Export selections (current print, current filaments, current printer) into config.ini // Export selections (current print, current filaments, current printer) into config.ini
void export_selections(AppConfig &config); void export_selections(AppConfig &config);
@ -82,24 +83,26 @@ public:
// Instead of a config file, a G-code may be loaded containing the full set of parameters. // Instead of a config file, a G-code may be loaded containing the full set of parameters.
// In the future the configuration will likely be read from an AMF file as well. // In the future the configuration will likely be read from an AMF file as well.
// If the file is loaded successfully, its print / filament / printer profiles will be activated. // If the file is loaded successfully, its print / filament / printer profiles will be activated.
void load_config_file(const std::string &path); ConfigSubstitutions load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule);
// Load a config bundle file, into presets and store the loaded presets into separate files // Load a config bundle file, into presets and store the loaded presets into separate files
// of the local configuration directory. // of the local configuration directory.
// Load settings into the provided settings instance. // Load settings into the provided settings instance.
// Activate the presets stored in the config bundle. // Activate the presets stored in the config bundle.
// Returns the number of presets loaded successfully. // Returns the number of presets loaded successfully.
enum { enum LoadConfigBundleAttribute {
// Save the profiles, which have been loaded. // Save the profiles, which have been loaded.
LOAD_CFGBNDLE_SAVE = 1, SaveImported,
// Delete all old config profiles before loading. // Delete all old config profiles before loading.
LOAD_CFGBNDLE_RESET_USER_PROFILE = 2, ResetUserProfile,
// Load a system config bundle. // Load a system config bundle.
LOAD_CFGBNDLE_SYSTEM = 4, LoadSystem,
LOAD_CFGBUNDLE_VENDOR_ONLY = 8, LoadVendorOnly,
}; };
// Load the config bundle, store it to the user profile directory by default. using LoadConfigBundleAttributes = enum_bitmask<LoadConfigBundleAttribute>;
size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE); // Load the config bundle based on the flags.
// Don't do any config substitutions when loading a system profile, perform and report substitutions otherwise.
std::pair<PresetsConfigSubstitutions, size_t> load_configbundle(const std::string &path, LoadConfigBundleAttributes flags);
// Export a config bundle file containing all the presets and the names of the active presets. // Export a config bundle file containing all the presets and the names of the active presets.
void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false); void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false);
@ -155,12 +158,14 @@ private:
// and the external config is just referenced, not stored into user profile directory. // and the external config is just referenced, not stored into user profile directory.
// If it is not an external config, then the config will be stored into the user profile directory. // If it is not an external config, then the config will be stored into the user profile directory.
void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config); void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config);
void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree); ConfigSubstitutions load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
DynamicPrintConfig full_fff_config() const; DynamicPrintConfig full_fff_config() const;
DynamicPrintConfig full_sla_config() const; DynamicPrintConfig full_sla_config() const;
}; };
ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute)
} // namespace Slic3r } // namespace Slic3r
#endif /* slic3r_PresetBundle_hpp_ */ #endif /* slic3r_PresetBundle_hpp_ */

View File

@ -3225,7 +3225,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
} else if (opt_key == "bed_size" && !value.empty()) { } else if (opt_key == "bed_size" && !value.empty()) {
opt_key = "bed_shape"; opt_key = "bed_shape";
ConfigOptionPoint p; ConfigOptionPoint p;
p.deserialize(value); p.deserialize(value, ForwardCompatibilitySubstitutionRule::Disable);
std::ostringstream oss; std::ostringstream oss;
oss << "0x0," << p.value(0) << "x0," << p.value(0) << "x" << p.value(1) << ",0x" << p.value(1); oss << "0x0," << p.value(0) << "x0," << p.value(0) << "x" << p.value(1) << ",0x" << p.value(1);
value = oss.str(); value = oss.str();
@ -3777,6 +3777,20 @@ CLIMiscConfigDef::CLIMiscConfigDef()
def->label = L("Ignore non-existent config files"); def->label = L("Ignore non-existent config files");
def->tooltip = L("Do not fail if a file supplied to --load does not exist."); def->tooltip = L("Do not fail if a file supplied to --load does not exist.");
def = this->add("config_compatibility", coEnum);
def->label = L("Forward-compatibility rule when loading configurations from config files and project files (3MF, AMF).");
def->tooltip = L("This version of PrusaSlicer may not understand configurations produced by newest PrusaSlicer versions. "
"For example, newer PrusaSlicer may extend the list of supported firmware flavors. One may decide to "
"bail out or to substitute an unknown value with a default silently or verbosely.");
def->enum_keys_map = &ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>::get_enum_values();
def->enum_values.push_back("disable");
def->enum_values.push_back("enable");
def->enum_values.push_back("enable_silent");
def->enum_labels.push_back(L("Bail out on unknown configuration values"));
def->enum_labels.push_back(L("Enable reading unknown configuration values by verbosely substituting them with defaults."));
def->enum_labels.push_back(L("Enable reading unknown configuration values by silently substituting them with defaults."));
def->set_default_value(new ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>(ForwardCompatibilitySubstitutionRule::Enable));
def = this->add("load", coStrings); def = this->add("load", coStrings);
def->label = L("Load config file"); def->label = L("Load config file");
def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files."); def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files.");

View File

@ -216,6 +216,16 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<SLAPillarConnecti
return keys_map; return keys_map;
} }
template<> inline const t_config_enum_values& ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>::get_enum_values() {
static const t_config_enum_values keys_map = {
{ "disable", ForwardCompatibilitySubstitutionRule::Disable },
{ "enable", ForwardCompatibilitySubstitutionRule::Enable },
{ "enable_silent", ForwardCompatibilitySubstitutionRule::EnableSilent }
};
return keys_map;
}
// Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs. // Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs.
// Does not store the actual values, but defines default values. // Does not store the actual values, but defines default values.
class PrintConfigDef : public ConfigDef class PrintConfigDef : public ConfigDef
@ -1420,8 +1430,8 @@ public:
bool set_key_value(const std::string &opt_key, ConfigOption *opt) { bool out = m_data.set_key_value(opt_key, opt); this->touch(); return out; } bool set_key_value(const std::string &opt_key, ConfigOption *opt) { bool out = m_data.set_key_value(opt_key, opt); this->touch(); return out; }
template<typename T> template<typename T>
void set(const std::string &opt_key, T value) { m_data.set(opt_key, value, true); this->touch(); } void set(const std::string &opt_key, T value) { m_data.set(opt_key, value, true); this->touch(); }
void set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false) void set_deserialize(const t_config_option_key &opt_key, const std::string &str, ConfigSubstitutionContext &substitution_context, bool append = false)
{ m_data.set_deserialize(opt_key, str, append); this->touch(); } { m_data.set_deserialize(opt_key, str, substitution_context, append); this->touch(); }
bool erase(const t_config_option_key &opt_key) { bool out = m_data.erase(opt_key); if (out) this->touch(); return out; } bool erase(const t_config_option_key &opt_key) { bool out = m_data.erase(opt_key); if (out) this->touch(); return out; }
// Getters are thread safe. // Getters are thread safe.

View File

@ -0,0 +1,80 @@
#ifndef slic3r_enum_bitmask_hpp_
#define slic3r_enum_bitmask_hpp_
// enum_bitmask for passing a set of attributes to a function in a type safe way.
// Adapted from https://gpfault.net/posts/typesafe-bitmasks.txt.html
// with hints from https://www.strikerx3.dev/cpp/2019/02/27/typesafe-enum-class-bitmasks-in-cpp.html
#include <type_traits>
namespace Slic3r {
// enum_bitmasks can only be used with enums.
template<class option_type, typename = typename std::enable_if<std::is_enum<option_type>::value>::type>
class enum_bitmask {
// The type we'll use for storing the value of our bitmask should be the same as the enum's underlying type.
using underlying_type = typename std::underlying_type<option_type>::type;
// This method helps us avoid having to explicitly set enum values to powers of two.
static constexpr underlying_type mask_value(option_type o) { return 1 << static_cast<underlying_type>(o); }
// Private ctor to be used internally.
explicit constexpr enum_bitmask(underlying_type o) : m_bits(o) {}
public:
// Default ctor creates a bitmask with no options selected.
constexpr enum_bitmask() : m_bits(0) {}
// Creates a enum_bitmask with just one bit set.
// This ctor is intentionally non-explicit, to allow passing an options to a function:
// FunctionExpectingBitmask(Options::Opt1)
constexpr enum_bitmask(option_type o) : m_bits(mask_value(o)) {}
// Set the bit corresponding to the given option.
constexpr enum_bitmask operator|(option_type t) { return enum_bitmask(m_bits | mask_value(t)); }
// Combine with another enum_bitmask of the same type.
constexpr enum_bitmask operator|(enum_bitmask<option_type> t) { return enum_bitmask(m_bits | t.m_bits); }
// Get the value of the bit corresponding to the given option.
constexpr bool operator&(option_type t) { return m_bits & mask_value(t); }
constexpr bool has(option_type t) { return m_bits & mask_value(t); }
private:
underlying_type m_bits = 0;
};
// For enabling free functions producing enum_bitmask<> type from bit operations on enums.
template<typename Enum> struct is_enum_bitmask_type { static const bool enable = false; };
#define ENABLE_ENUM_BITMASK_OPERATORS(x) template<> struct is_enum_bitmask_type<x> { static const bool enable = true; };
template<class Enum> inline constexpr bool is_enum_bitmask_type_v = is_enum_bitmask_type<Enum>::enable;
// Creates an enum_bitmask from two options, convenient for passing of options to a function:
// FunctionExpectingBitmask(Options::Opt1 | Options::Opt2 | Options::Opt3)
template <class option_type>
constexpr std::enable_if_t<is_enum_bitmask_type_v<option_type>, enum_bitmask<option_type>> operator|(option_type lhs, option_type rhs) {
static_assert(std::is_enum_v<option_type>);
return enum_bitmask<option_type>{lhs} | rhs;
}
template <class option_type>
constexpr std::enable_if_t<is_enum_bitmask_type_v<option_type>, enum_bitmask<option_type>> operator|(option_type lhs, enum_bitmask<option_type> rhs) {
static_assert(std::is_enum_v<option_type>);
return enum_bitmask<option_type>{lhs} | rhs;
}
template <class option_type>
constexpr std::enable_if_t<is_enum_bitmask_type_v<option_type>, enum_bitmask<option_type>> only_if(bool condition, option_type opt) {
static_assert(std::is_enum_v<option_type>);
return condition ? enum_bitmask<option_type>{opt} : enum_bitmask<option_type>{};
}
template <class option_type>
constexpr std::enable_if_t<is_enum_bitmask_type_v<option_type>, enum_bitmask<option_type>> only_if(bool condition, enum_bitmask<option_type> opt) {
static_assert(std::is_enum_v<option_type>);
return condition ? opt : enum_bitmask<option_type>{};
}
} // namespace Slic3r
#endif // slic3r_enum_bitmask_hpp_

View File

@ -111,6 +111,7 @@
#include "BoundingBox.hpp" #include "BoundingBox.hpp"
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "Config.hpp" #include "Config.hpp"
#include "enum_bitmask.hpp"
#include "format.hpp" #include "format.hpp"
#include "I18N.hpp" #include "I18N.hpp"
#include "MultiPoint.hpp" #include "MultiPoint.hpp"

View File

@ -413,7 +413,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
++ it; ++ it;
// Read the active config bundle, parse the config version. // Read the active config bundle, parse the config version.
PresetBundle bundle; PresetBundle bundle;
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY); bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly);
for (const auto &vp : bundle.vendors) for (const auto &vp : bundle.vendors)
if (vp.second.id == cfg.name) if (vp.second.id == cfg.name)
cfg.version.config_version = vp.second.config_version; cfg.version.config_version = vp.second.config_version;

View File

@ -58,7 +58,9 @@ bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bu
this->is_prusa_bundle = ais_prusa_bundle; this->is_prusa_bundle = ais_prusa_bundle;
std::string path_string = source_path.string(); std::string path_string = source_path.string();
size_t presets_loaded = preset_bundle->load_configbundle(path_string, PresetBundle::LOAD_CFGBNDLE_SYSTEM); auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle(path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem);
// No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
assert(config_substitutions.empty());
auto first_vendor = preset_bundle->vendors.begin(); auto first_vendor = preset_bundle->vendors.begin();
if (first_vendor == preset_bundle->vendors.end()) { if (first_vendor == preset_bundle->vendors.end()) {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string; BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string;
@ -2458,7 +2460,10 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
} }
} }
preset_bundle->load_presets(*app_config, preferred_model); // Reloading the configs after some modifications were done to PrusaSlicer.ini.
// Just perform the substitutions silently, as the substitutions were already presented to the user on application start-up
// and the Wizard shall not create any new values that would require substitution.
PresetsConfigSubstitutions substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent, preferred_model);
if (page_custom->custom_wanted()) { if (page_custom->custom_wanted()) {
page_firmware->apply_custom_config(*custom_config); page_firmware->apply_custom_config(*custom_config);

View File

@ -618,6 +618,13 @@ void GUI_App::post_init()
this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str())); this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str()));
} }
else { else {
if (! this->init_params->preset_substitutions.empty()) {
// TODO: Add list of changes from all_substitutions
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
" To recover these files, incompatible values were changed to default values."
" But data in files won't be changed until you save them in PrusaSlicer.")));
}
#if 0 #if 0
// Load the cummulative config over the currently active profiles. // Load the cummulative config over the currently active profiles.
//FIXME if multiple configs are loaded, only the last one will have an effect. //FIXME if multiple configs are loaded, only the last one will have an effect.
@ -637,6 +644,24 @@ void GUI_App::post_init()
if (! this->init_params->extra_config.empty()) if (! this->init_params->extra_config.empty())
this->mainframe->load_config(this->init_params->extra_config); this->mainframe->load_config(this->init_params->extra_config);
} }
// The extra CallAfter() is needed because of Mac, where this is the only way
// to popup a modal dialog on start without screwing combo boxes.
// This is ugly but I honestly found no better way to do it.
// Neither wxShowEvent nor wxWindowCreateEvent work reliably.
if (this->preset_updater) {
this->check_updates(false);
CallAfter([this] {
this->config_wizard_startup();
this->preset_updater->slic3r_update_notify();
this->preset_updater->sync(preset_bundle);
});
}
#ifdef _WIN32
// Sets window property to mainframe so other instances can indentify it.
OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int);
#endif //WIN32
} }
IMPLEMENT_APP(GUI_App) IMPLEMENT_APP(GUI_App)
@ -871,7 +896,7 @@ bool GUI_App::on_init_inner()
// Suppress the '- default -' presets. // Suppress the '- default -' presets.
preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1"); preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1");
try { try {
preset_bundle->load_presets(*app_config); init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable);
} catch (const std::exception &ex) { } catch (const std::exception &ex) {
show_error(nullptr, ex.what()); show_error(nullptr, ex.what());
} }
@ -924,7 +949,6 @@ bool GUI_App::on_init_inner()
if (! plater_) if (! plater_)
return; return;
if (app_config->dirty() && app_config->get("autosave") == "1") if (app_config->dirty() && app_config->get("autosave") == "1")
app_config->save(); app_config->save();
@ -945,33 +969,6 @@ bool GUI_App::on_init_inner()
#endif #endif
this->post_init(); this->post_init();
} }
// Preset updating & Configwizard are done after the above initializations,
// and after MainFrame is created & shown.
// The extra CallAfter() is needed because of Mac, where this is the only way
// to popup a modal dialog on start without screwing combo boxes.
// This is ugly but I honestly found no better way to do it.
// Neither wxShowEvent nor wxWindowCreateEvent work reliably.
static bool once = true;
if (once) {
once = false;
if (preset_updater != nullptr) {
check_updates(false);
CallAfter([this] {
config_wizard_startup();
preset_updater->slic3r_update_notify();
preset_updater->sync(preset_bundle);
});
}
#ifdef _WIN32
//sets window property to mainframe so other instances can indentify it
OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int);
#endif //WIN32
}
}); });
m_initialized = true; m_initialized = true;
@ -1683,7 +1680,13 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK); Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK);
try { try {
app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id); app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id);
preset_bundle->load_presets(*app_config); if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable);
! all_substitutions.empty()) {
// TODO:
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
" To recover these files, incompatible values were changed to default values."
" But data in files won't be changed until you save them in PrusaSlicer.")));
}
// Load the currently selected preset into the GUI, update the preset selection box. // Load the currently selected preset into the GUI, update the preset selection box.
load_current_presets(); load_current_presets();
} catch (std::exception &ex) { } catch (std::exception &ex) {

View File

@ -235,7 +235,7 @@ public:
// Parameters extracted from the command line to be passed to GUI after initialization. // Parameters extracted from the command line to be passed to GUI after initialization.
const GUI_InitParams* init_params { nullptr }; GUI_InitParams* init_params { nullptr };
AppConfig* app_config{ nullptr }; AppConfig* app_config{ nullptr };
PresetBundle* preset_bundle{ nullptr }; PresetBundle* preset_bundle{ nullptr };

View File

@ -50,39 +50,8 @@ int GUI_Run(GUI_InitParams &params)
// gui->autosave = m_config.opt_string("autosave"); // gui->autosave = m_config.opt_string("autosave");
GUI::GUI_App::SetInstance(gui); GUI::GUI_App::SetInstance(gui);
gui->init_params = &params; gui->init_params = &params;
/*
gui->CallAfter([gui, this, &load_configs, params.start_as_gcodeviewer] {
if (!gui->initialized()) {
return;
}
if (params.start_as_gcodeviewer) { return wxEntry(params.argc, params.argv);
if (!m_input_files.empty())
gui->plater()->load_gcode(wxString::FromUTF8(m_input_files[0].c_str()));
} else {
#if 0
// Load the cummulative config over the currently active profiles.
//FIXME if multiple configs are loaded, only the last one will have an effect.
// We need to decide what to do about loading of separate presets (just print preset, just filament preset etc).
// As of now only the full configs are supported here.
if (!m_print_config.empty())
gui->mainframe->load_config(m_print_config);
#endif
if (!load_configs.empty())
// Load the last config to give it a name at the UI. The name of the preset may be later
// changed by loading an AMF or 3MF.
//FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config.
gui->mainframe->load_config_file(load_configs.back());
// If loading a 3MF file, the config is loaded from the last one.
if (!m_input_files.empty())
gui->plater()->load_files(m_input_files, true, true);
if (!m_extra_config.empty())
gui->mainframe->load_config(m_extra_config);
}
});
*/
int result = wxEntry(params.argc, params.argv);
return result;
} catch (const Slic3r::Exception &ex) { } catch (const Slic3r::Exception &ex) {
boost::nowide::cerr << ex.what() << std::endl; boost::nowide::cerr << ex.what() << std::endl;
wxMessageBox(boost::nowide::widen(ex.what()), _L("PrusaSlicer GUI initialization failed"), wxICON_STOP); wxMessageBox(boost::nowide::widen(ex.what()), _L("PrusaSlicer GUI initialization failed"), wxICON_STOP);

View File

@ -1,6 +1,7 @@
#ifndef slic3r_GUI_Init_hpp_ #ifndef slic3r_GUI_Init_hpp_
#define slic3r_GUI_Init_hpp_ #define slic3r_GUI_Init_hpp_
#include <libslic3r/Preset.hpp>
#include <libslic3r/PrintConfig.hpp> #include <libslic3r/PrintConfig.hpp>
namespace Slic3r { namespace Slic3r {
@ -12,6 +13,9 @@ struct GUI_InitParams
int argc; int argc;
char **argv; char **argv;
// Substitutions of unknown configuration values done during loading of user presets.
PresetsConfigSubstitutions preset_substitutions;
std::vector<std::string> load_configs; std::vector<std::string> load_configs;
DynamicPrintConfig extra_config; DynamicPrintConfig extra_config;
std::vector<std::string> input_files; std::vector<std::string> input_files;

View File

@ -139,16 +139,17 @@ void SLAImportJob::process()
if (p->path.empty()) return; if (p->path.empty()) return;
std::string path = p->path.ToUTF8().data(); std::string path = p->path.ToUTF8().data();
ConfigSubstitutions config_substitutions;
try { try {
switch (p->sel) { switch (p->sel) {
case Sel::modelAndProfile: case Sel::modelAndProfile:
import_sla_archive(path, p->win, p->mesh, p->profile, progr); config_substitutions = import_sla_archive(path, p->win, p->mesh, p->profile, progr);
break; break;
case Sel::modelOnly: case Sel::modelOnly:
import_sla_archive(path, p->win, p->mesh, progr); config_substitutions = import_sla_archive(path, p->win, p->mesh, progr);
break; break;
case Sel::profileOnly: case Sel::profileOnly:
import_sla_archive(path, p->profile); config_substitutions = import_sla_archive(path, p->profile);
break; break;
} }
@ -156,6 +157,10 @@ void SLAImportJob::process()
p->err = ex.what(); p->err = ex.what();
} }
if (! config_substitutions.empty()) {
//FIXME Add reporting here "Loading profiles found following incompatibilities."
}
update_status(100, was_canceled() ? _(L("Importing canceled.")) : update_status(100, was_canceled() ? _(L("Importing canceled.")) :
_(L("Importing done."))); _(L("Importing done.")));
} }

View File

@ -1311,6 +1311,7 @@ void MainFrame::update_menubar()
m_changeable_menu_items[miPrinterTab] ->SetBitmap(create_scaled_bitmap(is_fff ? "printer" : "sla_printer")); m_changeable_menu_items[miPrinterTab] ->SetBitmap(create_scaled_bitmap(is_fff ? "printer" : "sla_printer"));
} }
#if 0
// To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG". // To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG".
void MainFrame::quick_slice(const int qs) void MainFrame::quick_slice(const int qs)
{ {
@ -1433,6 +1434,7 @@ void MainFrame::quick_slice(const int qs)
// }; // };
// Slic3r::GUI::catch_error(this, []() { if (m_progress_dialog) m_progress_dialog->Destroy(); }); // Slic3r::GUI::catch_error(this, []() { if (m_progress_dialog) m_progress_dialog->Destroy(); });
} }
#endif
void MainFrame::reslice_now() void MainFrame::reslice_now()
{ {
@ -1515,7 +1517,13 @@ void MainFrame::load_config_file()
bool MainFrame::load_config_file(const std::string &path) bool MainFrame::load_config_file(const std::string &path)
{ {
try { try {
wxGetApp().preset_bundle->load_config_file(path); ConfigSubstitutions config_substitutions = wxGetApp().preset_bundle->load_config_file(path, ForwardCompatibilitySubstitutionRule::Enable);
if (! config_substitutions.empty()) {
// TODO: Add list of changes from all_substitutions
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
" To recover these files, incompatible values were changed to default values."
" But data in files won't be changed until you save them in PrusaSlicer.")));
}
} catch (const std::exception &ex) { } catch (const std::exception &ex) {
show_error(this, ex.what()); show_error(this, ex.what());
return false; return false;
@ -1571,14 +1579,22 @@ void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool re
wxGetApp().app_config->update_config_dir(get_dir_name(file)); wxGetApp().app_config->update_config_dir(get_dir_name(file));
auto presets_imported = 0; size_t presets_imported = 0;
PresetsConfigSubstitutions config_substitutions;
try { try {
presets_imported = wxGetApp().preset_bundle->load_configbundle(file.ToUTF8().data()); std::tie(config_substitutions, presets_imported) = wxGetApp().preset_bundle->load_configbundle(file.ToUTF8().data(), PresetBundle::LoadConfigBundleAttribute::SaveImported);
} catch (const std::exception &ex) { } catch (const std::exception &ex) {
show_error(this, ex.what()); show_error(this, ex.what());
return; return;
} }
if (! config_substitutions.empty()) {
// TODO: Add list of changes from all_substitutions
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
" To recover these files, incompatible values were changed to default values."
" But data in files won't be changed until you save them in PrusaSlicer.")));
}
// Load the currently selected preset into the GUI, update the preset selection box. // Load the currently selected preset into the GUI, update the preset selection box.
wxGetApp().load_current_presets(); wxGetApp().load_current_presets();

View File

@ -164,7 +164,7 @@ public:
bool is_last_input_file() const { return !m_qs_last_input_file.IsEmpty(); } bool is_last_input_file() const { return !m_qs_last_input_file.IsEmpty(); }
bool is_dlg_layout() const { return m_layout == ESettingsLayout::Dlg; } bool is_dlg_layout() const { return m_layout == ESettingsLayout::Dlg; }
void quick_slice(const int qs = qsUndef); // void quick_slice(const int qs = qsUndef);
void reslice_now(); void reslice_now();
void repair_stl(); void repair_stl();
void export_config(); void export_config();

View File

@ -2342,7 +2342,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
DynamicPrintConfig config; DynamicPrintConfig config;
{ {
DynamicPrintConfig config_loaded; DynamicPrintConfig config_loaded;
model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, false, load_config); ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::Enable };
model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, &config_substitutions, only_if(load_config, Model::LoadAttribute::CheckVersion));
if (load_config && !config_loaded.empty()) { if (load_config && !config_loaded.empty()) {
// Based on the printer technology field found in the loaded config, select the base for the config, // Based on the printer technology field found in the loaded config, select the base for the config,
PrinterTechnology printer_technology = Preset::printer_technology(config_loaded); PrinterTechnology printer_technology = Preset::printer_technology(config_loaded);
@ -2368,6 +2369,12 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
// and place the loaded config over the base. // and place the loaded config over the base.
config += std::move(config_loaded); config += std::move(config_loaded);
} }
if (! config_substitutions.empty()) {
// TODO:
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
" To recover these files, incompatible values were changed to default values."
" But data in files won't be changed until you save them in PrusaSlicer.")));
}
this->model.custom_gcode_per_print_z = model.custom_gcode_per_print_z; this->model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
} }
@ -2387,7 +2394,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
} }
} }
else { else {
model = Slic3r::Model::read_from_file(path.string(), nullptr, false, load_config); model = Slic3r::Model::read_from_file(path.string(), nullptr, nullptr, only_if(load_config, Model::LoadAttribute::CheckVersion));
for (auto obj : model.objects) for (auto obj : model.objects)
if (obj->name.empty()) if (obj->name.empty())
obj->name = fs::path(obj->input_file).filename().string(); obj->name = fs::path(obj->input_file).filename().string();
@ -3233,7 +3240,7 @@ void Plater::priv::reload_from_disk()
Model new_model; Model new_model;
try try
{ {
new_model = Model::read_from_file(path, nullptr, true, false); new_model = Model::read_from_file(path, nullptr, nullptr, Model::LoadAttribute::AddDefaultInstances);
for (ModelObject* model_object : new_model.objects) for (ModelObject* model_object : new_model.objects)
{ {
model_object->center_around_origin(); model_object->center_around_origin();
@ -4623,7 +4630,9 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator
// Switch to the other printer technology. Switch to the last printer active for that particular technology. // Switch to the other printer technology. Switch to the last printer active for that particular technology.
AppConfig *app_config = wxGetApp().app_config; AppConfig *app_config = wxGetApp().app_config;
app_config->set("presets", "printer", (new_printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name); app_config->set("presets", "printer", (new_printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name);
wxGetApp().preset_bundle->load_presets(*app_config); //FIXME Why are we reloading the whole preset bundle here? Please document. This is fishy and it is unnecessarily expensive.
// Anyways, don't report any config value substitutions, they have been already reported to the user at application start up.
wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent);
// load_current_presets() calls Tab::load_current_preset() -> TabPrint::update() -> Object_list::update_and_show_object_settings_item(), // load_current_presets() calls Tab::load_current_preset() -> TabPrint::update() -> Object_list::update_and_show_object_settings_item(),
// but the Object list still keeps pointer to the old Model. Avoid a crash by removing selection first. // but the Object list still keeps pointer to the old Model. Avoid a crash by removing selection first.
this->sidebar->obj_list()->unselect_objects(); this->sidebar->obj_list()->unselect_objects();

View File

@ -377,7 +377,8 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx)
// PresetBundle bundle; // PresetBundle bundle;
on_progress(L("Loading repaired model"), 80); on_progress(L("Loading repaired model"), 80);
DynamicPrintConfig config; DynamicPrintConfig config;
bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &model, false); ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::EnableSilent };
bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), config, config_substitutions, &model, false);
boost::filesystem::remove(path_dst); boost::filesystem::remove(path_dst);
if (! loaded) if (! loaded)
throw Slic3r::RuntimeError(L("Import of the repaired 3mf file failed")); throw Slic3r::RuntimeError(L("Import of the repaired 3mf file failed"));

View File

@ -611,7 +611,7 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
update.install(); update.install();
PresetBundle bundle; PresetBundle bundle;
bundle.load_configbundle(update.source.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM); bundle.load_configbundle(update.source.string(), PresetBundle::LoadConfigBundleAttribute::LoadSystem);
BOOST_LOG_TRIVIAL(info) << format("Deleting %1% conflicting presets", bundle.prints.size() + bundle.filaments.size() + bundle.printers.size()); BOOST_LOG_TRIVIAL(info) << format("Deleting %1% conflicting presets", bundle.prints.size() + bundle.filaments.size() + bundle.printers.size());
@ -709,6 +709,17 @@ void PresetUpdater::slic3r_update_notify()
} }
} }
static void reload_configs_update_gui()
{
// Reload global configuration
auto* app_config = GUI::wxGetApp().app_config;
// System profiles should not trigger any substitutions, user profiles may trigger substitutions, but these substitutions
// were already presented to the user on application start up. Just do substitutions now and keep quiet about it.
GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent);
GUI::wxGetApp().load_current_presets();
GUI::wxGetApp().plater()->set_bed_shape();
}
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, bool no_notification) const PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, bool no_notification) const
{ {
if (! p->enabled_config_update) { return R_NOOP; } if (! p->enabled_config_update) { return R_NOOP; }
@ -766,7 +777,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
} }
//forced update //forced update
if(incompatible_version) if (incompatible_version)
{ {
BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. At least one requires higher version of Slicer.", updates.updates.size()); BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. At least one requires higher version of Slicer.", updates.updates.size());
@ -781,14 +792,8 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
const auto res = dlg.ShowModal(); const auto res = dlg.ShowModal();
if (res == wxID_OK) { if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(info) << "User wants to update..."; BOOST_LOG_TRIVIAL(info) << "User wants to update...";
p->perform_updates(std::move(updates)); p->perform_updates(std::move(updates));
reload_configs_update_gui();
// Reload global configuration
auto* app_config = GUI::wxGetApp().app_config;
GUI::wxGetApp().preset_bundle->load_presets(*app_config);
GUI::wxGetApp().load_current_presets();
GUI::wxGetApp().plater()->set_bed_shape();
return R_UPDATE_INSTALLED; return R_UPDATE_INSTALLED;
} }
else { else {
@ -813,11 +818,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
if (res == wxID_OK) { if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
p->perform_updates(std::move(updates)); p->perform_updates(std::move(updates));
reload_configs_update_gui();
// Reload global configuration
auto* app_config = GUI::wxGetApp().app_config;
GUI::wxGetApp().preset_bundle->load_presets(*app_config);
GUI::wxGetApp().load_current_presets();
return R_UPDATE_INSTALLED; return R_UPDATE_INSTALLED;
} }
else { else {
@ -870,11 +871,7 @@ void PresetUpdater::on_update_notification_confirm()
if (res == wxID_OK) { if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
p->perform_updates(std::move(p->waiting_updates)); p->perform_updates(std::move(p->waiting_updates));
reload_configs_update_gui();
// Reload global configuration
auto* app_config = GUI::wxGetApp().app_config;
GUI::wxGetApp().preset_bundle->load_presets(*app_config);
GUI::wxGetApp().load_current_presets();
p->has_waiting_updates = false; p->has_waiting_updates = false;
//return R_UPDATE_INSTALLED; //return R_UPDATE_INSTALLED;
} }

View File

@ -200,14 +200,14 @@ void init_print(std::initializer_list<TriangleMesh> input_meshes, Slic3r::Print
void init_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, Slic3r::Model &model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments) void init_print(std::initializer_list<TestMesh> meshes, Slic3r::Print &print, Slic3r::Model &model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments)
{ {
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize(config_items); config.set_deserialize_strict(config_items);
init_print(meshes, print, model, config, comments); init_print(meshes, print, model, config, comments);
} }
void init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, Slic3r::Model &model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments) void init_print(std::initializer_list<TriangleMesh> meshes, Slic3r::Print &print, Slic3r::Model &model, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> config_items, bool comments)
{ {
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize(config_items); config.set_deserialize_strict(config_items);
init_print(meshes, print, model, config, comments); init_print(meshes, print, model, config, comments);
} }

View File

@ -19,7 +19,7 @@ SCENARIO("Extrusion width specifics", "[Flow]") {
GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer and a 20mm cube mesh") { GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer and a 20mm cube mesh") {
// this is a sharedptr // this is a sharedptr
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize({ config.set_deserialize_strict({
{ "brim_width", 2 }, { "brim_width", 2 },
{ "skirts", 1 }, { "skirts", 1 },
{ "perimeters", 3 }, { "perimeters", 3 },

View File

@ -10,7 +10,7 @@ SCENARIO("lift() is not ignored after unlift() at normal values of Z", "[GCodeWr
GIVEN("A config from a file and a single extruder.") { GIVEN("A config from a file and a single extruder.") {
GCodeWriter writer; GCodeWriter writer;
GCodeConfig &config = writer.config; GCodeConfig &config = writer.config;
config.load(std::string(TEST_DATA_DIR) + "/fff_print_tests/test_gcodewriter/config_lift_unlift.ini"); config.load(std::string(TEST_DATA_DIR) + "/fff_print_tests/test_gcodewriter/config_lift_unlift.ini", ForwardCompatibilitySubstitutionRule::Disable);
std::vector<unsigned int> extruder_ids {0}; std::vector<unsigned int> extruder_ids {0};
writer.set_extruders(extruder_ids); writer.set_extruders(extruder_ids);

View File

@ -50,7 +50,7 @@ SCENARIO("Print: Skirt generation", "[Print]") {
SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces to become internal.", "[Print]") { SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces to become internal.", "[Print]") {
GIVEN("sliced 20mm cube and config with top_solid_surfaces = 2 and bottom_solid_surfaces = 1") { GIVEN("sliced 20mm cube and config with top_solid_surfaces = 2 and bottom_solid_surfaces = 1") {
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize({ config.set_deserialize_strict({
{ "top_solid_layers", 2 }, { "top_solid_layers", 2 },
{ "bottom_solid_layers", 1 }, { "bottom_solid_layers", 1 },
{ "layer_height", 0.5 }, // get a known number of layers { "layer_height", 0.5 }, // get a known number of layers

View File

@ -224,7 +224,7 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode]") {
{ {
DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_num_extruders(4); config.set_num_extruders(4);
config.set_deserialize({ config.set_deserialize_strict({
{ "start_gcode", "; Extruder [current_extruder]" }, { "start_gcode", "; Extruder [current_extruder]" },
{ "infill_extruder", 2 }, { "infill_extruder", 2 },
{ "solid_infill_extruder", 2 }, { "solid_infill_extruder", 2 },

View File

@ -31,7 +31,7 @@ static int get_brim_tool(const std::string &gcode)
TEST_CASE("Skirt height is honored", "[Skirt]") { TEST_CASE("Skirt height is honored", "[Skirt]") {
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize({ config.set_deserialize_strict({
{ "skirts", 1 }, { "skirts", 1 },
{ "skirt_height", 5 }, { "skirt_height", 5 },
{ "perimeters", 0 }, { "perimeters", 0 },
@ -64,7 +64,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
GIVEN("A default configuration") { GIVEN("A default configuration") {
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_num_extruders(4); config.set_num_extruders(4);
config.set_deserialize({ config.set_deserialize_strict({
{ "support_material_speed", 99 }, { "support_material_speed", 99 },
{ "first_layer_height", 0.3 }, { "first_layer_height", 0.3 },
{ "gcode_comments", true }, { "gcode_comments", true },
@ -78,7 +78,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
}); });
WHEN("Brim width is set to 5") { WHEN("Brim width is set to 5") {
config.set_deserialize({ config.set_deserialize_strict({
{ "perimeters", 0 }, { "perimeters", 0 },
{ "skirts", 0 }, { "skirts", 0 },
{ "brim_width", 5 } { "brim_width", 5 }
@ -100,7 +100,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
} }
WHEN("Skirt area is smaller than the brim") { WHEN("Skirt area is smaller than the brim") {
config.set_deserialize({ config.set_deserialize_strict({
{ "skirts", 1 }, { "skirts", 1 },
{ "brim_width", 10} { "brim_width", 10}
}); });
@ -110,7 +110,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
} }
WHEN("Skirt height is 0 and skirts > 0") { WHEN("Skirt height is 0 and skirts > 0") {
config.set_deserialize({ config.set_deserialize_strict({
{ "skirts", 2 }, { "skirts", 2 },
{ "skirt_height", 0 } { "skirt_height", 0 }
}); });
@ -123,7 +123,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
// This is a real error! One shall print the brim with the external perimeter extruder! // This is a real error! One shall print the brim with the external perimeter extruder!
WHEN("Perimeter extruder = 2 and support extruders = 3") { WHEN("Perimeter extruder = 2 and support extruders = 3") {
THEN("Brim is printed with the extruder used for the perimeters of first object") { THEN("Brim is printed with the extruder used for the perimeters of first object") {
config.set_deserialize({ config.set_deserialize_strict({
{ "skirts", 0 }, { "skirts", 0 },
{ "brim_width", 5 }, { "brim_width", 5 },
{ "perimeter_extruder", 2 }, { "perimeter_extruder", 2 },
@ -137,7 +137,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
} }
WHEN("Perimeter extruder = 2, support extruders = 3, raft is enabled") { WHEN("Perimeter extruder = 2, support extruders = 3, raft is enabled") {
THEN("brim is printed with same extruder as skirt") { THEN("brim is printed with same extruder as skirt") {
config.set_deserialize({ config.set_deserialize_strict({
{ "skirts", 0 }, { "skirts", 0 },
{ "brim_width", 5 }, { "brim_width", 5 },
{ "perimeter_extruder", 2 }, { "perimeter_extruder", 2 },
@ -153,7 +153,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
#endif #endif
WHEN("brim width to 1 with layer_width of 0.5") { WHEN("brim width to 1 with layer_width of 0.5") {
config.set_deserialize({ config.set_deserialize_strict({
{ "skirts", 0 }, { "skirts", 0 },
{ "first_layer_extrusion_width", 0.5 }, { "first_layer_extrusion_width", 0.5 },
{ "brim_width", 1 } { "brim_width", 1 }
@ -167,7 +167,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
#if 0 #if 0
WHEN("brim ears on a square") { WHEN("brim ears on a square") {
config.set_deserialize({ config.set_deserialize_strict({
{ "skirts", 0 }, { "skirts", 0 },
{ "first_layer_extrusion_width", 0.5 }, { "first_layer_extrusion_width", 0.5 },
{ "brim_width", 1 }, { "brim_width", 1 },
@ -182,7 +182,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
} }
WHEN("brim ears on a square but with a too small max angle") { WHEN("brim ears on a square but with a too small max angle") {
config.set_deserialize({ config.set_deserialize_strict({
{ "skirts", 0 }, { "skirts", 0 },
{ "first_layer_extrusion_width", 0.5 }, { "first_layer_extrusion_width", 0.5 },
{ "brim_width", 1 }, { "brim_width", 1 },
@ -198,7 +198,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
#endif #endif
WHEN("Object is plated with overhang support and a brim") { WHEN("Object is plated with overhang support and a brim") {
config.set_deserialize({ config.set_deserialize_strict({
{ "layer_height", 0.4 }, { "layer_height", 0.4 },
{ "first_layer_height", 0.4 }, { "first_layer_height", 0.4 },
{ "skirts", 1 }, { "skirts", 1 },

View File

@ -14,7 +14,8 @@ SCENARIO("Reading 3mf file", "[3mf]") {
WHEN("3mf model is read") { WHEN("3mf model is read") {
std::string path = std::string(TEST_DATA_DIR) + "/test_3mf/Geräte/Büchse.3mf"; std::string path = std::string(TEST_DATA_DIR) + "/test_3mf/Geräte/Büchse.3mf";
DynamicPrintConfig config; DynamicPrintConfig config;
bool ret = load_3mf(path.c_str(), &config, &model, false); ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable };
bool ret = load_3mf(path.c_str(), config, ctxt, &model, false);
THEN("load should succeed") { THEN("load should succeed") {
REQUIRE(ret); REQUIRE(ret);
} }
@ -56,7 +57,10 @@ SCENARIO("Export+Import geometry to/from 3mf file cycle", "[3mf]") {
// load back the model from the 3mf file // load back the model from the 3mf file
Model dst_model; Model dst_model;
DynamicPrintConfig dst_config; DynamicPrintConfig dst_config;
load_3mf(test_file.c_str(), &dst_config, &dst_model, false); {
ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable };
load_3mf(test_file.c_str(), dst_config, ctxt, &dst_model, false);
}
boost::filesystem::remove(test_file); boost::filesystem::remove(test_file);
// compare meshes // compare meshes

View File

@ -8,7 +8,7 @@ SCENARIO("Generic config validation performs as expected.", "[Config]") {
GIVEN("A config generated from default options") { GIVEN("A config generated from default options") {
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
WHEN( "perimeter_extrusion_width is set to 250%, a valid value") { WHEN( "perimeter_extrusion_width is set to 250%, a valid value") {
config.set_deserialize("perimeter_extrusion_width", "250%"); config.set_deserialize_strict("perimeter_extrusion_width", "250%");
THEN( "The config is read as valid.") { THEN( "The config is read as valid.") {
REQUIRE(config.validate().empty()); REQUIRE(config.validate().empty());
} }
@ -39,7 +39,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
} }
} }
WHEN("A boolean option is set to a string value representing a 0 or 1") { WHEN("A boolean option is set to a string value representing a 0 or 1") {
CHECK_NOTHROW(config.set_deserialize("gcode_comments", "1")); CHECK_NOTHROW(config.set_deserialize_strict("gcode_comments", "1"));
THEN("The underlying value is set correctly.") { THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionBool>("gcode_comments")->getBool() == true); REQUIRE(config.opt<ConfigOptionBool>("gcode_comments")->getBool() == true);
} }
@ -58,7 +58,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
} }
} }
WHEN("A numeric option is set from serialized string") { WHEN("A numeric option is set from serialized string") {
config.set_deserialize("bed_temperature", "100"); config.set_deserialize_strict("bed_temperature", "100");
THEN("The underlying value is set correctly.") { THEN("The underlying value is set correctly.") {
REQUIRE(config.opt<ConfigOptionInts>("bed_temperature")->get_at(0) == 100); REQUIRE(config.opt<ConfigOptionInts>("bed_temperature")->get_at(0) == 100);
} }
@ -91,7 +91,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
} }
WHEN("A numeric option is set to a non-numeric value.") { WHEN("A numeric option is set to a non-numeric value.") {
THEN("A BadOptionTypeException exception is thown.") { THEN("A BadOptionTypeException exception is thown.") {
REQUIRE_THROWS_AS(config.set_deserialize("perimeter_speed", "zzzz"), BadOptionTypeException); REQUIRE_THROWS_AS(config.set_deserialize_strict("perimeter_speed", "zzzz"), BadOptionTypeException);
} }
THEN("The value does not change.") { THEN("The value does not change.") {
REQUIRE(config.opt<ConfigOptionFloat>("perimeter_speed")->getFloat() == 60.0); REQUIRE(config.opt<ConfigOptionFloat>("perimeter_speed")->getFloat() == 60.0);
@ -116,7 +116,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
} }
} }
WHEN("A float or percent is set as a percent through the string interface.") { WHEN("A float or percent is set as a percent through the string interface.") {
config.set_deserialize("first_layer_extrusion_width", "100%"); config.set_deserialize_strict("first_layer_extrusion_width", "100%");
THEN("Value and percent flag are 100/true") { THEN("Value and percent flag are 100/true") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width"); auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
REQUIRE(tmp->percent == true); REQUIRE(tmp->percent == true);
@ -124,7 +124,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
} }
} }
WHEN("A float or percent is set as a float through the string interface.") { WHEN("A float or percent is set as a float through the string interface.") {
config.set_deserialize("first_layer_extrusion_width", "100"); config.set_deserialize_strict("first_layer_extrusion_width", "100");
THEN("Value and percent flag are 100/false") { THEN("Value and percent flag are 100/false") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width"); auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
REQUIRE(tmp->percent == false); REQUIRE(tmp->percent == false);
@ -194,7 +194,7 @@ SCENARIO("Config ini load/save interface", "[Config]") {
WHEN("new_from_ini is called") { WHEN("new_from_ini is called") {
Slic3r::DynamicPrintConfig config; Slic3r::DynamicPrintConfig config;
std::string path = std::string(TEST_DATA_DIR) + "/test_config/new_from_ini.ini"; std::string path = std::string(TEST_DATA_DIR) + "/test_config/new_from_ini.ini";
config.load_from_ini(path); config.load_from_ini(path, ForwardCompatibilitySubstitutionRule::Disable);
THEN("Config object contains ini file options.") { THEN("Config object contains ini file options.") {
REQUIRE(config.option_throw<ConfigOptionStrings>("filament_colour", false)->values.size() == 1); REQUIRE(config.option_throw<ConfigOptionStrings>("filament_colour", false)->values.size() == 1);
REQUIRE(config.option_throw<ConfigOptionStrings>("filament_colour", false)->values.front() == "#ABCD"); REQUIRE(config.option_throw<ConfigOptionStrings>("filament_colour", false)->values.front() == "#ABCD");

View File

@ -9,7 +9,7 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
PlaceholderParser parser; PlaceholderParser parser;
auto config = DynamicPrintConfig::full_print_config(); auto config = DynamicPrintConfig::full_print_config();
config.set_deserialize( { config.set_deserialize_strict( {
{ "printer_notes", " PRINTER_VENDOR_PRUSA3D PRINTER_MODEL_MK2 " }, { "printer_notes", " PRINTER_VENDOR_PRUSA3D PRINTER_MODEL_MK2 " },
{ "nozzle_diameter", "0.6;0.6;0.6;0.6" }, { "nozzle_diameter", "0.6;0.6;0.6;0.6" },
{ "temperature", "357;359;363;378" } { "temperature", "357;359;363;378" }

View File

@ -282,7 +282,7 @@ bool ConfigBase__set(ConfigBase* THIS, const t_config_option_key &opt_key, SV* v
break; break;
} }
default: default:
if (! opt->deserialize(std::string(SvPV_nolen(value)))) if (! opt->deserialize(std::string(SvPV_nolen(value)), ForwardCompatibilitySubstitutionRule::Disable))
return false; return false;
} }
return true; return true;
@ -295,7 +295,8 @@ bool ConfigBase__set_deserialize(ConfigBase* THIS, const t_config_option_key &op
size_t len; size_t len;
const char * c = SvPV(str, len); const char * c = SvPV(str, len);
std::string value(c, len); std::string value(c, len);
return THIS->set_deserialize_nothrow(opt_key, value); ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable };
return THIS->set_deserialize_nothrow(opt_key, value, ctxt);
} }
void ConfigBase__set_ifndef(ConfigBase* THIS, const t_config_option_key &opt_key, SV* value, bool deserialize) void ConfigBase__set_ifndef(ConfigBase* THIS, const t_config_option_key &opt_key, SV* value, bool deserialize)

View File

@ -55,7 +55,7 @@
%code%{ %code%{
auto config = new DynamicPrintConfig(); auto config = new DynamicPrintConfig();
try { try {
config->load(path); config->load(path, ForwardCompatibilitySubstitutionRule::Disable);
RETVAL = config; RETVAL = config;
} catch (std::exception& e) { } catch (std::exception& e) {
delete config; delete config;
@ -119,7 +119,7 @@
%code%{ %code%{
auto config = new FullPrintConfig(); auto config = new FullPrintConfig();
try { try {
config->load(path); config->load(path, ForwardCompatibilitySubstitutionRule::Disable);
RETVAL = static_cast<GCodeConfig*>(config); RETVAL = static_cast<GCodeConfig*>(config);
} catch (std::exception& e) { } catch (std::exception& e) {
delete config; delete config;

View File

@ -41,7 +41,7 @@ _new_from_width(CLASS, role, width, nozzle_diameter, height, bridge_flow_ratio)
float bridge_flow_ratio; float bridge_flow_ratio;
CODE: CODE:
ConfigOptionFloatOrPercent optwidth; ConfigOptionFloatOrPercent optwidth;
optwidth.deserialize(width); optwidth.deserialize(width, ForwardCompatibilitySubstitutionRule::Disable);
RETVAL = new Flow(Flow::new_from_config_width(role, optwidth, nozzle_diameter, height, bridge_flow_ratio)); RETVAL = new Flow(Flow::new_from_config_width(role, optwidth, nozzle_diameter, height, bridge_flow_ratio));
OUTPUT: OUTPUT:
RETVAL RETVAL

View File

@ -22,7 +22,7 @@
%name{read_from_file} Model(std::string input_file, bool add_default_instances = true) %name{read_from_file} Model(std::string input_file, bool add_default_instances = true)
%code%{ %code%{
try { try {
RETVAL = new Model(Model::read_from_file(input_file, nullptr, add_default_instances)); RETVAL = new Model(Model::read_from_file(input_file, nullptr, nullptr, only_if(add_default_instances, Model::LoadAttribute::AddDefaultInstances)));
} catch (std::exception& e) { } catch (std::exception& e) {
croak("Error while opening %s: %s\n", input_file.c_str(), e.what()); croak("Error while opening %s: %s\n", input_file.c_str(), e.what());
} }