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.

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 ad336e2cc0
commit 0f3cabb5d9
47 changed files with 643 additions and 314 deletions

View File

@ -118,7 +118,8 @@ int CLI::run(int argc, char **argv)
boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer");
#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
for (auto const &file : load_configs) {
@ -130,13 +131,19 @@ int CLI::run(int argc, char **argv)
return 1;
}
}
DynamicPrintConfig config;
DynamicPrintConfig config;
ConfigSubstitutions config_substitutions;
try {
config.load(file);
config_substitutions = config.load(file, config_substitution_rule);
} 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;
}
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();
PrinterTechnology other_printer_technology = get_printer_technology(config);
if (printer_technology == ptUnknown) {
@ -174,7 +181,9 @@ int CLI::run(int argc, char **argv)
try {
// When loading an AMF or 3MF, config is imported as well, including the printer technology.
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 = get_printer_technology(config);
if (printer_technology == ptUnknown) {
printer_technology = other_printer_technology;
@ -183,6 +192,11 @@ int CLI::run(int argc, char **argv)
boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl;
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 += std::move(m_print_config);
m_print_config = std::move(config);

View File

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

View File

@ -23,6 +23,10 @@
#include <boost/format.hpp>
#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 {
// Escape \n, \r and backslash
@ -211,6 +215,10 @@ std::string escape_ampersand(const std::string& str)
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> args;
@ -361,7 +369,8 @@ std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, s
// right: option description
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())) {
descr += " (";
if (!def.sidetext.empty()) {
@ -469,7 +478,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;
std::string value = value_src;
@ -479,29 +488,29 @@ bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src,
if (opt_key.empty())
// Ignore the option.
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));
}
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)
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.
const ConfigDef *def = this->def();
const ConfigDef *def = this->def();
if (def == nullptr)
throw NoDefinitionException(opt_key);
const ConfigOptionDef *optdef = def->get(opt_key);
const ConfigOptionDef *optdef = def->get(opt_key);
if (optdef == nullptr) {
// If we didn't find an option, look for any other option having this as an alias.
for (const auto &opt : def->options) {
@ -523,14 +532,35 @@ 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".
for (const t_config_option_key &shortcut : optdef->shortcut)
// Recursive call.
if (! this->set_deserialize_raw(shortcut, value, append))
if (! this->set_deserialize_raw(shortcut, value, substitutions_ctxt, append))
return false;
return true;
}
ConfigOption *opt = this->option(opt_key, true);
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);
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.
@ -589,36 +619,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))
this->load_from_gcode_file(file);
else
this->load_from_ini(file);
return is_gcode_file(file) ?
this->load_from_gcode_file(file, true /* check header */, compatibility_rule) :
this->load_from_ini(file, compatibility_rule);
}
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::nowide::ifstream ifs(file);
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) {
try {
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 */) {
// ignore
}
}
return std::move(substitutions_ctxt.substitutions);
}
// Load the config keys from the tail of a G-code file.
void ConfigBase::load_from_gcode_file(const std::string& file, bool check_header)
ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, bool check_header, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
// Read a 64k block from the end of the G-code.
boost::nowide::ifstream ifs(file);
@ -639,13 +670,15 @@ void ConfigBase::load_from_gcode_file(const std::string& file, bool check_header
ifs.read(data.data(), data_length);
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)
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.
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)
return 0;
@ -690,7 +723,7 @@ size_t ConfigBase::load_from_gcode_string(const char* str)
if (key == nullptr)
break;
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;
}
catch (UnknownOptionException & /* e */) {
@ -719,7 +752,7 @@ void ConfigBase::null_nullables()
ConfigOption *opt = this->optptr(opt_key, false);
assert(opt != nullptr);
if (opt->nullable())
opt->deserialize("nil");
opt->deserialize("nil", ForwardCompatibilitySubstitutionRule::Disable);
}
}
@ -883,8 +916,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.
static_cast<ConfigOptionString*>(opt_base)->value = value;
} 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.
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;
return false;
}

View File

@ -16,6 +16,7 @@
#include "Exception.hpp"
#include "Point.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/format/format_fwd.hpp>
#include <boost/functional/hash.hpp>
@ -163,6 +164,41 @@ enum PrinterTechnology : unsigned char
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.
class ConfigOption {
public:
@ -768,7 +804,7 @@ public:
return escape_string_cstyle(this->value);
}
bool deserialize(const std::string &str, bool append = false) override
bool deserialize(const std::string &str, bool append = false) override
{
UNUSED(append);
return unescape_string_cstyle(str, this->value);
@ -1272,8 +1308,15 @@ public:
bool deserialize(const std::string &str, bool append = false) override
{
UNUSED(append);
this->value = (str.compare("1") == 0);
return true;
if (str == "1" || boost::iequals(str, "enabled") || boost::iequals(str, "on")) {
this->value = true;
return true;
}
if (str == "0" || boost::iequals(str, "disabled") || boost::iequals(str, "off")) {
this->value = false;
return true;
}
return false;
}
private:
@ -1687,6 +1730,14 @@ public:
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.
// The definition does not carry an actual value of the config option, only its constant default value.
// t_config_option_key is std::string
@ -1765,6 +1816,8 @@ public:
}
};
// An abstract configuration store.
class ConfigBase : public ConfigOptionResolver
{
@ -1853,9 +1906,11 @@ public:
// Set a configuration value from a string, it will call an overridable handle_legacy()
// 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.
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 {
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) {}
@ -1870,17 +1925,19 @@ public:
std::string opt_key; std::string opt_value; bool append = false;
};
// 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, double ratio_over) const;
void setenv_() const;
void load(const std::string &file);
void load_from_ini(const std::string &file);
void load_from_gcode_file(const std::string& file, bool check_header = true);
ConfigSubstitutions load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
ConfigSubstitutions load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
ConfigSubstitutions load_from_gcode_file(const std::string &file, bool check_header /* = true */, ForwardCompatibilitySubstitutionRule compatibility_rule);
// Returns number of key/value pairs extracted.
size_t load_from_gcode_string(const char* str);
void load(const boost::property_tree::ptree &tree);
size_t load_from_gcode_string(const char* str, ConfigSubstitutionContext& substitutions);
ConfigSubstitutions load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);
void save(const std::string &file) const;
// Set all the nullable values to nils.
@ -1888,7 +1945,7 @@ public:
private:
// 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.

View File

@ -419,7 +419,7 @@ namespace Slic3r {
_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:
void _destroy_xml_parser();
@ -434,16 +434,16 @@ namespace Slic3r {
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);
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_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_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);
// handlers to parse the .model file
@ -510,7 +510,7 @@ namespace Slic3r {
bool _handle_start_config_metadata(const char** attributes, unsigned int num_attributes);
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
static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes);
@ -539,7 +539,7 @@ namespace Slic3r {
_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_check_version = check_version;
@ -560,7 +560,7 @@ namespace Slic3r {
m_curr_characters.clear();
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()
@ -581,7 +581,7 @@ namespace Slic3r {
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_zero_struct(&archive);
@ -635,7 +635,7 @@ namespace Slic3r {
}
else if (boost::algorithm::iequals(name, 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)) {
// extract sla support points file
@ -647,7 +647,7 @@ namespace Slic3r {
}
else if (boost::algorithm::iequals(name, 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);
}
else if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE)) {
// extract slic3r layer config ranges file
@ -704,7 +704,7 @@ namespace Slic3r {
new_model_object->clear_instances();
new_model_object->add_instance(*model_object->instances.back());
model_object->delete_last_instance();
if (!_generate_volumes(*new_model_object, *geometry, volumes))
if (!_generate_volumes(*new_model_object, *geometry, volumes, config_substitutions))
return false;
}
}
@ -759,7 +759,7 @@ namespace Slic3r {
if (metadata.key == "name")
model_object->name = metadata.value;
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
@ -775,7 +775,7 @@ namespace Slic3r {
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;
}
@ -867,7 +867,10 @@ namespace Slic3r {
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) {
std::string buffer((size_t)stat.m_uncomp_size, 0);
@ -876,7 +879,7 @@ namespace Slic3r {
add_error("Error while reading config data to buffer");
return;
}
config.load_from_gcode_string(buffer.data());
config.load_from_gcode_string(buffer.data(), config_substitutions);
}
}
@ -942,7 +945,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) {
std::string buffer((size_t)stat.m_uncomp_size, 0);
@ -987,8 +990,7 @@ namespace Slic3r {
continue;
std::string opt_key = option.second.get<std::string>("<xmlattr>.opt_key");
std::string value = option.second.data();
config.set_deserialize(opt_key, value);
config.set_deserialize(opt_key, value, config_substitutions);
}
config_ranges[{ min_z, max_z }].assign_config(std::move(config));
@ -1827,7 +1829,7 @@ namespace Slic3r {
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()) {
add_error("Found invalid volumes count");
@ -1943,7 +1945,7 @@ namespace Slic3r {
else if (metadata.key == SOURCE_IN_METERS)
volume->source.is_converted_from_meters = metadata.value == "1";
else
volume->config.set_deserialize(metadata.key, metadata.value);
volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
}
}
@ -2953,16 +2955,15 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv
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;
// All import should use "C" locales for number formatting.
CNumericLocalesSetter locales_setter;
_3MF_Importer importer;
bool res = importer.load_model_from_file(path, *model, *config, check_version);
_3MF_Importer importer;
bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version);
importer.log_errors();
return res;
}

View File

@ -25,11 +25,12 @@ namespace Slic3r {
};
class Model;
struct ConfigSubstitutionContext;
class DynamicPrintConfig;
struct ThumbnailData;
// 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.
// The model could be modified during the export process if meshes are not repaired or have no shared vertices

View File

@ -64,10 +64,11 @@ namespace Slic3r
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_model(*model),
m_config(config)
m_config(config),
m_config_substitutions(config_substitutions)
{
m_path.reserve(12);
}
@ -258,6 +259,8 @@ struct AMFParserContext
std::string m_value[5];
// Pointer to config to update if config data are stored inside the amf file
DynamicPrintConfig *m_config { nullptr };
// Config substitution rules and collected config substitution log.
ConfigSubstitutionContext *m_config_substitutions { nullptr };
private:
AMFParserContext& operator=(AMFParserContext&);
@ -702,8 +705,9 @@ void AMFParserContext::endElement(const char * /* name */)
}
case NODE_TYPE_METADATA:
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());
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_substitutions);
}
else if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) {
const char *opt_key = m_value[0].c_str() + 7;
if (print_config_def.options.find(opt_key) != print_config_def.options.end()) {
@ -721,7 +725,7 @@ void AMFParserContext::endElement(const char * /* name */)
config = &it->second;
}
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) {
// Parse object's layer height profile, a semicolon separated list of floats.
char *p = m_value[1].data();
@ -849,7 +853,7 @@ void AMFParserContext::endDocument()
}
// 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))
return false;
@ -866,7 +870,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model)
return false;
}
AMFParserContext ctx(parser, config, model);
AMFParserContext ctx(parser, config, config_substitutions, model);
XML_SetUserData(parser, (void*)&ctx);
XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement);
XML_SetCharacterDataHandler(parser, AMFParserContext::characters);
@ -908,7 +912,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model)
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)
{
@ -924,7 +928,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi
return false;
}
AMFParserContext ctx(parser, config, model);
AMFParserContext ctx(parser, config, config_substitutions, model);
XML_SetUserData(parser, (void*)&ctx);
XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement);
XML_SetCharacterDataHandler(parser, AMFParserContext::characters);
@ -984,7 +988,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi
}
// 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))
return false;
@ -1010,7 +1014,7 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model
{
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);
BOOST_LOG_TRIVIAL(error) << "Archive does not contain a valid model";
@ -1052,13 +1056,13 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* 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
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)
{
CNumericLocalesSetter locales_setter; // use "C" locales and point as a decimal separator
if (boost::iends_with(path, ".amf.xml"))
// 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"))
{
boost::nowide::ifstream file(path, boost::nowide::ifstream::binary);
@ -1069,7 +1073,7 @@ bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool c
file.read(zip_mask.data(), 2);
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
return false;

View File

@ -7,7 +7,7 @@ class Model;
class DynamicPrintConfig;
// 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.
// The model could be modified during the export process if meshes are not repaired or have no shared vertices

View File

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

View File

@ -287,13 +287,13 @@ std::vector<ExPolygons> extract_slices_from_sla_archive(
} // 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");
out.load(arch.profile);
return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
}
void import_sla_archive(
ConfigSubstitutions import_sla_archive(
const std::string & zipfname,
Vec2i windowsize,
indexed_triangle_set & out,
@ -305,7 +305,7 @@ void import_sla_archive(
windowsize.y() = std::max(2, windowsize.y());
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);
rstp.win = {windowsize.y(), windowsize.x()};
@ -317,6 +317,8 @@ void import_sla_archive(
if (!slices.empty())
out = slices_to_mesh(slices, 0, slicp.layerh, slicp.initial_layerh);
return config_substitutions;
}
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,
Vec2i windowsize,
indexed_triangle_set & out,
DynamicPrintConfig & profile,
std::function<bool(int)> progr = [](int) { return true; });
inline void import_sla_archive(
inline ConfigSubstitutions import_sla_archive(
const std::string & zipfname,
Vec2i windowsize,
indexed_triangle_set & out,
std::function<bool(int)> progr = [](int) { return true; })
{
DynamicPrintConfig profile;
import_sla_archive(zipfname, windowsize, out, profile, progr);
return import_sla_archive(zipfname, windowsize, out, profile, progr);
}
} // namespace Slic3r::sla

View File

@ -1285,7 +1285,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) {
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
config.load_from_gcode_file(filename, false);
// 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, false, ForwardCompatibilitySubstitutionRule::EnableSilent);
apply_config(config);
}
else if (m_producer == EProducer::Simplify3D)

View File

@ -96,13 +96,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;
DynamicPrintConfig temp_config;
ConfigSubstitutionContext temp_config_substitutions_context(ForwardCompatibilitySubstitutionRule::EnableSilent);
if (config == nullptr)
config = &temp_config;
if (config_substitutions == nullptr)
config_substitutions = &temp_config_substitutions_context;
bool result = false;
if (boost::algorithm::iends_with(input_file, ".stl"))
@ -110,9 +114,10 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
else if (boost::algorithm::iends_with(input_file, ".obj"))
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"))
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"))
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"))
result = load_prus(input_file.c_str(), &model);
else
@ -127,24 +132,29 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
for (ModelObject *o : model.objects)
o->input_file = input_file;
if (add_default_instances)
if (options & LoadAttribute::AddDefaultInstances)
model.add_default_instances();
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);
sort_remove_duplicates(config_substitutions->substitutions);
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;
bool result = false;
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"))
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
throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension.");
@ -165,7 +175,7 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
o->input_file = input_file;
}
if (add_default_instances)
if (options & LoadAttribute::AddDefaultInstances)
model.add_default_instances();
CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config);
@ -398,13 +408,12 @@ bool Model::looks_like_multipart_object() const
}
// 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];
sprintf(str_extruder, "%ud", cntr + 1);
if (++ cntr == max_extruders)
int out = ++ cntr;
if (cntr == max_extruders)
cntr = 0;
return str_extruder;
return out;
}
void Model::convert_multipart_object(unsigned int max_extruders)
@ -431,7 +440,7 @@ void Model::convert_multipart_object(unsigned int max_extruders)
auto copy_volume = [o, max_extruders, &counter, &extruder_counter](ModelVolume *new_v) {
assert(new_v != nullptr);
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;
};
if (o->instances.empty()) {
@ -1738,7 +1747,7 @@ size_t ModelVolume::split(unsigned int max_extruders)
this->object->volumes[ivolume]->center_geometry_after_creation();
this->object->volumes[ivolume]->translate(offset);
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));
this->object->volumes[ivolume]->m_is_splittable = 0;
delete mesh;
++ idx;

View File

@ -12,6 +12,7 @@
#include "TriangleMesh.hpp"
#include "Arrange.hpp"
#include "CustomGCode.hpp"
#include "enum_bitmask.hpp"
#include <map>
#include <memory>
@ -1031,8 +1032,20 @@ public:
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);
static Model read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances = true, bool check_version = false);
enum class LoadAttribute : int {
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.
ModelObject* add_object();
@ -1097,6 +1110,8 @@ private:
}
};
ENABLE_ENUM_BITMASK_OPERATORS(Model::LoadAttribute)
#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE

View File

@ -666,7 +666,9 @@ void PresetCollection::add_default_preset(const std::vector<std::string> &keys,
// Load all presets found in dir_path.
// 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,
// see https://github.com/prusa3d/PrusaSlicer/issues/732
@ -693,7 +695,9 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri
// Load the preset file, apply preset values on top of defaults.
try {
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.
const Preset &default_preset = this->default_preset_for(config);
preset.config = default_preset.config;
@ -1580,7 +1584,9 @@ PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vector<std::str
// Load all printers found in dir_path.
// 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,
// see https://github.com/prusa3d/PrusaSlicer/issues/732
@ -1606,7 +1612,9 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const
// Load the preset file, apply preset values on top of defaults.
try {
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.loaded = true;
}

View File

@ -113,6 +113,9 @@ public:
TYPE_SLA_MATERIAL,
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,
};
Type type = TYPE_INVALID;
@ -251,6 +254,27 @@ enum class PresetSelectCompatibleType {
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).
class PresetCollection
{
@ -280,7 +304,7 @@ public:
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.
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
// and select it, losing previous modifications.
@ -692,7 +716,7 @@ public:
const std::deque<PhysicalPrinter>& operator()() const { return m_printers; }
// 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);
// Load printer from the loaded configuration
void load_printer(const std::string& path, const std::string& name, DynamicPrintConfig&& config, bool select, bool save=false);

View File

@ -187,7 +187,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.
std::string errors_cummulative = this->load_system_presets();
@ -200,33 +200,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.
#endif
;
PresetsConfigSubstitutions substitutions;
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) {
errors_cummulative += err.what();
}
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) {
errors_cummulative += err.what();
}
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) {
errors_cummulative += err.what();
}
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) {
errors_cummulative += err.what();
}
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) {
errors_cummulative += err.what();
}
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) {
errors_cummulative += err.what();
}
@ -236,6 +238,8 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_
throw Slic3r::RuntimeError(errors_cummulative);
this->load_selections(config, preferred_model_id);
return substitutions;
}
// Load system presets into this PresetBundle.
@ -255,13 +259,13 @@ std::string PresetBundle::load_system_presets()
// Load the config bundle, flatten it.
if (first) {
// 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;
} else {
// Load the other vendor configs, merge them with this PresetBundle.
// Report duplicate profiles.
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));
if (! duplicates.empty()) {
errors_cummulative += "Vendor configuration file " + name + " contains the following presets with names used by other vendors: ";
@ -690,15 +694,15 @@ DynamicPrintConfig PresetBundle::full_sla_config() const
// 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.
// 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)) {
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
config.load_from_gcode_file(path);
ConfigSubstitutions config_substitutions = config.load_from_gcode_file(path, true /* check_header */, compatibility_rule);
Preset::normalize(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.
@ -717,6 +721,7 @@ void PresetBundle::load_config_file(const std::string &path)
// 2) Continue based on the type of the configuration file.
ConfigFileType config_file_type = guess_config_file_type(tree);
ConfigSubstitutions config_substitutions;
switch (config_file_type) {
case CONFIG_FILE_TYPE_UNKNOWN:
throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path);
@ -727,15 +732,18 @@ void PresetBundle::load_config_file(const std::string &path)
// Initialize a config from full defaults.
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
config.load(tree);
config_substitutions = config.load(tree, compatibility_rule);
Preset::normalize(config);
load_config_file_config(path, true, std::move(config));
break;
}
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
load_config_file_config_bundle(path, tree);
break;
return config_substitutions;
}
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
return load_config_file_config_bundle(path, tree);
}
// 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.
@ -907,16 +915,24 @@ 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.
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.
PresetBundle tmp_bundle;
// Load the config bundle, don't save the loaded presets to user profile directory.
tmp_bundle.load_configbundle(path, 0);
// 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
// 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();
// 2) Extract active configs from the config bundle, copy them and activate them in this bundle.
auto load_one = [&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_dst = collection_dst.find_preset(preset_name_src, false);
assert(preset_src != nullptr);
@ -970,6 +986,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->update_compatible(PresetSelectCompatibleType::Never);
sort_remove_duplicates(config_substitutions);
return std::move(config_substitutions);
}
// Process the Config Bundle loaded as a Boost property tree.
@ -1114,11 +1133,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
// 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))
// Reset this bundle, delete user profile files if LOAD_CFGBNDLE_SAVE.
this->reset(flags & LOAD_CFGBNDLE_SAVE);
// Enable substitutions for user config bundle, throw an exception when loading a system profile.
ConfigSubstitutionContext substitution_context {
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.
namespace pt = boost::property_tree;
@ -1131,25 +1159,24 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
}
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);
if (vp.models.size() == 0) {
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) {
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;
}
if (flags & LOAD_CFGBUNDLE_VENDOR_ONLY) {
return 0;
}
if (flags.has(LoadConfigBundleAttribute::LoadVendorOnly))
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.
// 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.
// Parse the obsolete preset names, to be deleted when upgrading from the old configuration structure.
@ -1246,7 +1273,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
DynamicPrintConfig config;
std::string alias_name;
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) {
if (kvp.first == "alias")
alias_name = kvp.second.data();
@ -1256,7 +1284,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
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) {
@ -1277,7 +1306,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
if (! incorrect_keys.empty())
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";
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.
// These presets are considered not installed.
auto printer_model = config.opt_string("printer_model");
@ -1312,7 +1341,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
section.first << "\" has already been loaded from another Confing Bundle.";
continue;
}
} else if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) {
} else if (! flags.has(LoadConfigBundleAttribute::LoadSystem)) {
// This is a user config bundle.
const Preset *existing = presets->find_preset(preset_name, false);
if (existing != nullptr) {
@ -1341,9 +1370,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
/ presets->section_name() / file_name).make_preferred();
// 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);
if (flags & LOAD_CFGBNDLE_SAVE)
if (flags.has(LoadConfigBundleAttribute::SaveImported))
loaded.save();
if (flags & LOAD_CFGBNDLE_SYSTEM) {
if (flags.has(LoadConfigBundleAttribute::LoadSystem)) {
loaded.is_system = true;
loaded.vendor = vendor_profile;
}
@ -1364,7 +1393,10 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
else
loaded.alias = std::move(alias_name);
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;
}
@ -1373,8 +1405,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
const DynamicPrintConfig& default_config = ph_printers->default_config();
DynamicPrintConfig config = default_config;
substitution_context.substitutions.clear();
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.
std::string incorrect_keys = Preset::remove_invalid_keys(config, default_config);
@ -1400,14 +1433,17 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
#endif
/ "physical_printer" / file_name).make_preferred();
// 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_loaded;
ph_printers->load_printer(file_path.string(), ph_printer_name, std::move(config), false, flags.has(LoadConfigBundleAttribute::SaveImported));
if (! substitution_context.empty())
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.
if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) {
if (! flags.has(LoadConfigBundleAttribute::LoadSystem)) {
if (! active_print.empty())
prints.select_preset_by_name(active_print, true);
if (! active_sla_print.empty())
@ -1427,7 +1463,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
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()

View File

@ -3,6 +3,7 @@
#include "Preset.hpp"
#include "AppConfig.hpp"
#include "enum_bitmask.hpp"
#include <memory>
#include <unordered_map>
@ -26,7 +27,7 @@ public:
// 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
void load_presets(AppConfig &config, const std::string &preferred_model_id = std::string());
PresetsConfigSubstitutions load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule rule, const std::string &preferred_model_id = std::string());
// Export selections (current print, current filaments, current printer) into config.ini
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.
// 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.
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
// of the local configuration directory.
// Load settings into the provided settings instance.
// Activate the presets stored in the config bundle.
// Returns the number of presets loaded successfully.
enum {
enum LoadConfigBundleAttribute {
// Save the profiles, which have been loaded.
LOAD_CFGBNDLE_SAVE = 1,
SaveImported,
// Delete all old config profiles before loading.
LOAD_CFGBNDLE_RESET_USER_PROFILE = 2,
ResetUserProfile,
// Load a system config bundle.
LOAD_CFGBNDLE_SYSTEM = 4,
LOAD_CFGBUNDLE_VENDOR_ONLY = 8,
LoadSystem,
LoadVendorOnly,
};
// Load the config bundle, store it to the user profile directory by default.
size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE);
using LoadConfigBundleAttributes = enum_bitmask<LoadConfigBundleAttribute>;
// 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.
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.
// 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_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_sla_config() const;
};
ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute)
} // namespace Slic3r
#endif /* slic3r_PresetBundle_hpp_ */

View File

@ -172,6 +172,13 @@ static const t_config_enum_values s_keys_map_BrimType = {
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BrimType)
static const t_config_enum_values s_keys_map_ForwardCompatibilitySubstitutionRule = {
{ "disable", ForwardCompatibilitySubstitutionRule::Disable },
{ "enable", ForwardCompatibilitySubstitutionRule::Enable },
{ "enable_silent", ForwardCompatibilitySubstitutionRule::EnableSilent }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology)
{
for (std::pair<const t_config_option_key, ConfigOptionDef> &kvp : options)
@ -3622,7 +3629,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
} else if (opt_key == "bed_size" && !value.empty()) {
opt_key = "bed_shape";
ConfigOptionPoint p;
p.deserialize(value);
p.deserialize(value, ForwardCompatibilitySubstitutionRule::Disable);
std::ostringstream oss;
oss << "0x0," << p.value(0) << "x0," << p.value(0) << "x" << p.value(1) << ",0x" << p.value(1);
value = oss.str();
@ -4171,6 +4178,20 @@ CLIMiscConfigDef::CLIMiscConfigDef()
def->label = L("Ignore non-existent config files");
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->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.");

View File

@ -141,6 +141,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SeamPosition)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLADisplayOrientation)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
@ -1086,8 +1087,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; }
template<typename T>
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)
{ m_data.set_deserialize(opt_key, str, append); this->touch(); }
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, 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; }
// 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

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

View File

@ -413,7 +413,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
++ it;
// Read the active config bundle, parse the config version.
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)
if (vp.second.id == cfg.name)
cfg.version.config_version = vp.second.config_version;

View File

@ -64,7 +64,9 @@ bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bu
this->is_prusa_bundle = ais_prusa_bundle;
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();
if (first_vendor == preset_bundle->vendors.end()) {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string;
@ -2592,7 +2594,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()) {
page_firmware->apply_custom_config(*custom_config);

View File

@ -624,6 +624,13 @@ void GUI_App::post_init()
this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str()));
}
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
// Load the cummulative config over the currently active profiles.
//FIXME if multiple configs are loaded, only the last one will have an effect.
@ -652,6 +659,24 @@ void GUI_App::post_init()
if (! this->init_params->extra_config.empty())
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)
@ -885,7 +910,7 @@ bool GUI_App::on_init_inner()
// Suppress the '- default -' presets.
preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1");
try {
preset_bundle->load_presets(*app_config);
init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable);
} catch (const std::exception &ex) {
show_error(nullptr, ex.what());
}
@ -948,7 +973,6 @@ bool GUI_App::on_init_inner()
if (! plater_)
return;
if (app_config->dirty() && app_config->get("autosave") == "1")
app_config->save();
@ -969,33 +993,6 @@ bool GUI_App::on_init_inner()
#endif
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;
@ -1872,7 +1869,13 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK);
try {
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_current_presets();
} catch (std::exception &ex) {

View File

@ -273,7 +273,7 @@ public:
NotificationManager* notification_manager();
// 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 };
PresetBundle* preset_bundle{ nullptr };
@ -281,7 +281,7 @@ public:
MainFrame* mainframe{ nullptr };
Plater* plater_{ nullptr };
PresetUpdater* get_preset_updater() { return preset_updater; }
PresetUpdater* get_preset_updater() { return preset_updater; }
wxBookCtrlBase* tab_panel() const ;
int extruders_cnt() const;

View File

@ -50,39 +50,8 @@ int GUI_Run(GUI_InitParams &params)
// gui->autosave = m_config.opt_string("autosave");
GUI::GUI_App::SetInstance(gui);
gui->init_params = &params;
/*
gui->CallAfter([gui, this, &load_configs, params.start_as_gcodeviewer] {
if (!gui->initialized()) {
return;
}
if (params.start_as_gcodeviewer) {
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;
return wxEntry(params.argc, params.argv);
} catch (const Slic3r::Exception &ex) {
boost::nowide::cerr << ex.what() << std::endl;
wxMessageBox(boost::nowide::widen(ex.what()), _L("PrusaSlicer GUI initialization failed"), wxICON_STOP);

View File

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

View File

@ -140,22 +140,27 @@ void SLAImportJob::process()
if (p->path.empty()) return;
std::string path = p->path.ToUTF8().data();
ConfigSubstitutions config_substitutions;
try {
switch (p->sel) {
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;
case Sel::modelOnly:
import_sla_archive(path, p->win, p->mesh, progr);
config_substitutions = import_sla_archive(path, p->win, p->mesh, progr);
break;
case Sel::profileOnly:
import_sla_archive(path, p->profile);
config_substitutions = import_sla_archive(path, p->profile);
break;
}
} catch (std::exception &ex) {
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.")) :
_(L("Importing done.")));

View File

@ -1518,6 +1518,7 @@ void MainFrame::update_menubar()
m_changeable_menu_items[miPrinterTab] ->SetBitmap(create_menu_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".
void MainFrame::quick_slice(const int qs)
{
@ -1643,6 +1644,7 @@ void MainFrame::quick_slice(const int qs)
// };
// Slic3r::GUI::catch_error(this, []() { if (m_progress_dialog) m_progress_dialog->Destroy(); });
}
#endif
void MainFrame::reslice_now()
{
@ -1729,7 +1731,13 @@ void MainFrame::load_config_file()
bool MainFrame::load_config_file(const std::string &path)
{
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) {
show_error(this, ex.what());
return false;
@ -1793,14 +1801,22 @@ void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool re
wxGetApp().app_config->update_config_dir(get_dir_name(file));
auto presets_imported = 0;
size_t presets_imported = 0;
PresetsConfigSubstitutions config_substitutions;
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) {
show_error(this, ex.what());
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.
wxGetApp().load_current_presets();

View File

@ -170,7 +170,7 @@ public:
bool is_last_input_file() const { return !m_qs_last_input_file.IsEmpty(); }
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 repair_stl();
void export_config();

View File

@ -2237,7 +2237,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
DynamicPrintConfig config;
{
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()) {
// 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);
@ -2261,6 +2262,12 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
// and place the loaded config over the base.
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;
}
@ -2330,7 +2337,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
}
}
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)
if (obj->name.empty())
obj->name = fs::path(obj->input_file).filename().string();
@ -3215,7 +3222,7 @@ void Plater::priv::replace_with_stl()
Model new_model;
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) {
model_object->center_around_origin();
model_object->ensure_on_bed();
@ -3388,7 +3395,7 @@ void Plater::priv::reload_from_disk()
Model new_model;
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) {
model_object->center_around_origin();
model_object->ensure_on_bed();
@ -4599,7 +4606,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.
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);
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(),
// but the Object list still keeps pointer to the old Model. Avoid a crash by removing selection first.
this->sidebar->obj_list()->unselect_objects();

View File

@ -379,7 +379,8 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx)
// PresetBundle bundle;
on_progress(L("Loading repaired model"), 80);
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);
if (! loaded)
throw Slic3r::RuntimeError(L("Import of the repaired 3mf file failed"));

View File

@ -612,7 +612,7 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
update.install();
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());
@ -710,6 +710,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
{
if (! p->enabled_config_update) { return R_NOOP; }
@ -767,7 +778,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
}
//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());
@ -782,14 +793,8 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
const auto res = dlg.ShowModal();
if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(info) << "User wants to update...";
p->perform_updates(std::move(updates));
// 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();
reload_configs_update_gui();
return R_UPDATE_INSTALLED;
}
else {
@ -814,11 +819,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
p->perform_updates(std::move(updates));
// Reload global configuration
auto* app_config = GUI::wxGetApp().app_config;
GUI::wxGetApp().preset_bundle->load_presets(*app_config);
GUI::wxGetApp().load_current_presets();
reload_configs_update_gui();
return R_UPDATE_INSTALLED;
}
else {
@ -871,11 +872,7 @@ void PresetUpdater::on_update_notification_confirm()
if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
p->perform_updates(std::move(p->waiting_updates));
// Reload global configuration
auto* app_config = GUI::wxGetApp().app_config;
GUI::wxGetApp().preset_bundle->load_presets(*app_config);
GUI::wxGetApp().load_current_presets();
reload_configs_update_gui();
p->has_waiting_updates = false;
//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)
{
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);
}
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();
config.set_deserialize(config_items);
config.set_deserialize_strict(config_items);
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") {
// this is a sharedptr
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize({
config.set_deserialize_strict({
{ "brim_width", 2 },
{ "skirts", 1 },
{ "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.") {
GCodeWriter writer;
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};
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]") {
GIVEN("sliced 20mm cube and config with top_solid_surfaces = 2 and bottom_solid_surfaces = 1") {
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize({
config.set_deserialize_strict({
{ "top_solid_layers", 2 },
{ "bottom_solid_layers", 1 },
{ "layer_height", 0.25 }, // get a known number of layers

View File

@ -224,7 +224,7 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode]") {
{
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_num_extruders(4);
config.set_deserialize({
config.set_deserialize_strict({
{ "start_gcode", "; Extruder [current_extruder]" },
{ "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]") {
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_deserialize({
config.set_deserialize_strict({
{ "skirts", 1 },
{ "skirt_height", 5 },
{ "perimeters", 0 },
@ -64,7 +64,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
GIVEN("A default configuration") {
DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
config.set_num_extruders(4);
config.set_deserialize({
config.set_deserialize_strict({
{ "support_material_speed", 99 },
{ "first_layer_height", 0.3 },
{ "gcode_comments", true },
@ -78,7 +78,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
});
WHEN("Brim width is set to 5") {
config.set_deserialize({
config.set_deserialize_strict({
{ "perimeters", 0 },
{ "skirts", 0 },
{ "brim_width", 5 }
@ -100,7 +100,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
}
WHEN("Skirt area is smaller than the brim") {
config.set_deserialize({
config.set_deserialize_strict({
{ "skirts", 1 },
{ "brim_width", 10}
});
@ -110,7 +110,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
}
WHEN("Skirt height is 0 and skirts > 0") {
config.set_deserialize({
config.set_deserialize_strict({
{ "skirts", 2 },
{ "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!
WHEN("Perimeter extruder = 2 and support extruders = 3") {
THEN("Brim is printed with the extruder used for the perimeters of first object") {
config.set_deserialize({
config.set_deserialize_strict({
{ "skirts", 0 },
{ "brim_width", 5 },
{ "perimeter_extruder", 2 },
@ -137,7 +137,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
}
WHEN("Perimeter extruder = 2, support extruders = 3, raft is enabled") {
THEN("brim is printed with same extruder as skirt") {
config.set_deserialize({
config.set_deserialize_strict({
{ "skirts", 0 },
{ "brim_width", 5 },
{ "perimeter_extruder", 2 },
@ -153,7 +153,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
#endif
WHEN("brim width to 1 with layer_width of 0.5") {
config.set_deserialize({
config.set_deserialize_strict({
{ "skirts", 0 },
{ "first_layer_extrusion_width", 0.5 },
{ "brim_width", 1 }
@ -167,7 +167,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
#if 0
WHEN("brim ears on a square") {
config.set_deserialize({
config.set_deserialize_strict({
{ "skirts", 0 },
{ "first_layer_extrusion_width", 0.5 },
{ "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") {
config.set_deserialize({
config.set_deserialize_strict({
{ "skirts", 0 },
{ "first_layer_extrusion_width", 0.5 },
{ "brim_width", 1 },
@ -198,7 +198,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
#endif
WHEN("Object is plated with overhang support and a brim") {
config.set_deserialize({
config.set_deserialize_strict({
{ "layer_height", 0.4 },
{ "first_layer_height", 0.4 },
{ "skirts", 1 },

View File

@ -14,7 +14,8 @@ SCENARIO("Reading 3mf file", "[3mf]") {
WHEN("3mf model is read") {
std::string path = std::string(TEST_DATA_DIR) + "/test_3mf/Geräte/Büchse.3mf";
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") {
REQUIRE(ret);
}
@ -56,7 +57,10 @@ SCENARIO("Export+Import geometry to/from 3mf file cycle", "[3mf]") {
// load back the model from the 3mf file
Model dst_model;
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);
// compare meshes

View File

@ -9,7 +9,7 @@ SCENARIO("Generic config validation performs as expected.", "[Config]") {
GIVEN("A config generated from default options") {
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
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.") {
REQUIRE(config.validate().empty());
}
@ -40,7 +40,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
}
}
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.") {
REQUIRE(config.opt<ConfigOptionBool>("gcode_comments")->getBool() == true);
}
@ -59,7 +59,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
}
}
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.") {
REQUIRE(config.opt<ConfigOptionInts>("bed_temperature")->get_at(0) == 100);
}
@ -92,7 +92,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
}
WHEN("A numeric option is set to a non-numeric value.") {
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.") {
REQUIRE(config.opt<ConfigOptionFloat>("perimeter_speed")->getFloat() == 60.0);
@ -117,7 +117,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
}
}
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") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
REQUIRE(tmp->percent == true);
@ -125,7 +125,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") {
}
}
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") {
auto tmp = config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
REQUIRE(tmp->percent == false);
@ -195,7 +195,7 @@ SCENARIO("Config ini load/save interface", "[Config]") {
WHEN("new_from_ini is called") {
Slic3r::DynamicPrintConfig config;
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.") {
REQUIRE(config.option_throw<ConfigOptionStrings>("filament_colour", false)->values.size() == 1);
REQUIRE(config.option_throw<ConfigOptionStrings>("filament_colour", false)->values.front() == "#ABCD");

View File

@ -9,7 +9,7 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
PlaceholderParser parser;
auto config = DynamicPrintConfig::full_print_config();
config.set_deserialize( {
config.set_deserialize_strict( {
{ "printer_notes", " PRINTER_VENDOR_PRUSA3D PRINTER_MODEL_MK2 " },
{ "nozzle_diameter", "0.6;0.6;0.6;0.6" },
{ "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;
}
default:
if (! opt->deserialize(std::string(SvPV_nolen(value))))
if (! opt->deserialize(std::string(SvPV_nolen(value)), ForwardCompatibilitySubstitutionRule::Disable))
return false;
}
return true;
@ -295,7 +295,8 @@ bool ConfigBase__set_deserialize(ConfigBase* THIS, const t_config_option_key &op
size_t len;
const char * c = SvPV(str, 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)

View File

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

View File

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

View File

@ -22,7 +22,7 @@
%name{read_from_file} Model(std::string input_file, bool add_default_instances = true)
%code%{
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) {
croak("Error while opening %s: %s\n", input_file.c_str(), e.what());
}