>("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);
@@ -362,7 +376,7 @@ int CLI::run(int argc, char **argv)
o->cut(Z, m_config.opt_float("cut"), &out);
}
#else
- model.objects.front()->cut(0, m_config.opt_float("cut"), true, true, true);
+ model.objects.front()->cut(0, m_config.opt_float("cut"), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::FlipLower);
#endif
model.delete_object(size_t(0));
}
diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp
index 7ea437339..f388e37b1 100644
--- a/src/libnest2d/include/libnest2d/geometry_traits.hpp
+++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp
@@ -501,8 +501,9 @@ inline P _Box::center() const BP2D_NOEXCEPT {
using Coord = TCoord
;
P ret = { // No rounding here, we dont know if these are int coords
- Coord( (getX(minc) + getX(maxc)) / Coord(2) ),
- Coord( (getY(minc) + getY(maxc)) / Coord(2) )
+ // Doing the division like this increases the max range of x and y coord
+ getX(minc) / Coord(2) + getX(maxc) / Coord(2),
+ getY(minc) / Coord(2) + getY(maxc) / Coord(2)
};
return ret;
diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp
index 81e4db2ba..cee9eafdc 100644
--- a/src/libslic3r/AppConfig.cpp
+++ b/src/libslic3r/AppConfig.cpp
@@ -4,7 +4,7 @@
#include "Exception.hpp"
#include "LocalesUtils.hpp"
#include "Thread.hpp"
-
+#include "format.hpp"
#include
#include
@@ -18,15 +18,24 @@
#include
#include
#include
+#include
-//#include
-//#include "I18N.hpp"
+#ifdef WIN32
+//FIXME replace the two following includes with after it becomes mainstream.
+#include
+#include
+#endif
namespace Slic3r {
static const std::string VENDOR_PREFIX = "vendor:";
static const std::string MODEL_PREFIX = "model:";
-static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version";
+// Because of a crash in PrusaSlicer 2.3.0/2.3.1 when showing an update notification with some locales, we don't want PrusaSlicer 2.3.0/2.3.1
+// to show this notification. On the other hand, we would like PrusaSlicer 2.3.2 to show an update notification of the upcoming PrusaSlicer 2.4.0.
+// Thus we will let PrusaSlicer 2.3.2 and couple of follow-up versions to download the version number from an alternate file until the PrusaSlicer 2.3.0/2.3.1
+// are phased out, then we will revert to the original name.
+//static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version";
+static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version2";
const std::string AppConfig::SECTION_FILAMENTS = "filaments";
const std::string AppConfig::SECTION_MATERIALS = "sla_materials";
@@ -177,25 +186,114 @@ void AppConfig::set_defaults()
erase("", "object_settings_size");
}
+#ifdef WIN32
+static std::string appconfig_md5_hash_line(const std::string_view data)
+{
+ //FIXME replace the two following includes with after it becomes mainstream.
+ // return boost::md5(data).hex_str_value();
+ // boost::uuids::detail::md5 is an internal namespace thus it may change in the future.
+ // Also this implementation is not the fastest, it was designed for short blocks of text.
+ using boost::uuids::detail::md5;
+ md5 md5_hash;
+ // unsigned int[4], 128 bits
+ md5::digest_type md5_digest{};
+ std::string md5_digest_str;
+ md5_hash.process_bytes(data.data(), data.size());
+ md5_hash.get_digest(md5_digest);
+ boost::algorithm::hex(md5_digest, md5_digest + std::size(md5_digest), std::back_inserter(md5_digest_str));
+ // MD5 hash is 32 HEX digits long.
+ assert(md5_digest_str.size() == 32);
+ // This line will be emited at the end of the file.
+ return "# MD5 checksum " + md5_digest_str + "\n";
+};
+
+// Assume that the last line with the comment inside the config file contains a checksum and that the user didn't modify the config file.
+static bool verify_config_file_checksum(boost::nowide::ifstream &ifs)
+{
+ auto read_whole_config_file = [&ifs]() -> std::string {
+ std::stringstream ss;
+ ss << ifs.rdbuf();
+ return ss.str();
+ };
+
+ ifs.seekg(0, boost::nowide::ifstream::beg);
+ std::string whole_config = read_whole_config_file();
+
+ // The checksum should be on the last line in the config file.
+ if (size_t last_comment_pos = whole_config.find_last_of('#'); last_comment_pos != std::string::npos) {
+ // Split read config into two parts, one with checksum, and the second part is part with configuration from the checksum was computed.
+ // Verify existence and validity of the MD5 checksum line at the end of the file.
+ // When the checksum isn't found, the checksum was not saved correctly, it was removed or it is an older config file without the checksum.
+ // If the checksum is incorrect, then the file was either not saved correctly or modified.
+ if (std::string_view(whole_config.c_str() + last_comment_pos, whole_config.size() - last_comment_pos) == appconfig_md5_hash_line({ whole_config.data(), last_comment_pos }))
+ return true;
+ }
+ return false;
+}
+#endif
+
std::string AppConfig::load()
{
// 1) Read the complete config file into a boost::property_tree.
namespace pt = boost::property_tree;
pt::ptree tree;
- boost::nowide::ifstream ifs(AppConfig::config_path());
+ boost::nowide::ifstream ifs;
+ bool recovered = false;
+
try {
+ ifs.open(AppConfig::config_path());
+#ifdef WIN32
+ // Verify the checksum of the config file without taking just for debugging purpose.
+ if (!verify_config_file_checksum(ifs))
+ BOOST_LOG_TRIVIAL(info) << "The configuration file " << AppConfig::config_path() <<
+ " has a wrong MD5 checksum or the checksum is missing. This may indicate a file corruption or a harmless user edit.";
+
+ ifs.seekg(0, boost::nowide::ifstream::beg);
+#endif
pt::read_ini(ifs, tree);
} catch (pt::ptree_error& ex) {
- // Error while parsing config file. We'll customize the error message and rethrow to be displayed.
- // ! But to avoid the use of _utf8 (related to use of wxWidgets)
- // we will rethrow this exception from the place of load() call, if returned value wouldn't be empty
- /*
- throw Slic3r::RuntimeError(
- _utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. "
- "Try to manually delete the file to recover from the error. Your user profiles will not be affected.")) +
- "\n\n" + AppConfig::config_path() + "\n\n" + ex.what());
- */
- return ex.what();
+#ifdef WIN32
+ // The configuration file is corrupted, try replacing it with the backup configuration.
+ ifs.close();
+ std::string backup_path = (boost::format("%1%.bak") % AppConfig::config_path()).str();
+ if (boost::filesystem::exists(backup_path)) {
+ // Compute checksum of the configuration backup file and try to load configuration from it when the checksum is correct.
+ boost::nowide::ifstream backup_ifs(backup_path);
+ if (!verify_config_file_checksum(backup_ifs)) {
+ BOOST_LOG_TRIVIAL(error) << format("Both \"%1%\" and \"%2%\" are corrupted. It isn't possible to restore configuration from the backup.", AppConfig::config_path(), backup_path);
+ backup_ifs.close();
+ boost::filesystem::remove(backup_path);
+ } else if (std::string error_message; copy_file(backup_path, AppConfig::config_path(), error_message, false) != SUCCESS) {
+ BOOST_LOG_TRIVIAL(error) << format("Configuration file \"%1%\" is corrupted. Failed to restore from backup \"%2%\": %3%", AppConfig::config_path(), backup_path, error_message);
+ backup_ifs.close();
+ boost::filesystem::remove(backup_path);
+ } else {
+ BOOST_LOG_TRIVIAL(info) << format("Configuration file \"%1%\" was corrupted. It has been succesfully restored from the backup \"%2%\".", AppConfig::config_path(), backup_path);
+ // Try parse configuration file after restore from backup.
+ try {
+ ifs.open(AppConfig::config_path());
+ pt::read_ini(ifs, tree);
+ recovered = true;
+ } catch (pt::ptree_error& ex) {
+ BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\" after it has been restored from backup: %2%", AppConfig::config_path(), ex.what());
+ }
+ }
+ } else
+#endif // WIN32
+ BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\": %2%", AppConfig::config_path(), ex.what());
+ if (! recovered) {
+ // Report the initial error of parsing PrusaSlicer.ini.
+ // Error while parsing config file. We'll customize the error message and rethrow to be displayed.
+ // ! But to avoid the use of _utf8 (related to use of wxWidgets)
+ // we will rethrow this exception from the place of load() call, if returned value wouldn't be empty
+ /*
+ throw Slic3r::RuntimeError(
+ _utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. "
+ "Try to manually delete the file to recover from the error. Your user profiles will not be affected.")) +
+ "\n\n" + AppConfig::config_path() + "\n\n" + ex.what());
+ */
+ return ex.what();
+ }
}
// 2) Parse the property_tree, extract the sections and key / value pairs.
@@ -272,22 +370,21 @@ void AppConfig::save()
const auto path = config_path();
std::string path_pid = (boost::format("%1%.%2%") % path % get_current_pid()).str();
- boost::nowide::ofstream c;
- c.open(path_pid, std::ios::out | std::ios::trunc);
+ std::stringstream config_ss;
if (m_mode == EAppMode::Editor)
- c << "# " << Slic3r::header_slic3r_generated() << std::endl;
+ config_ss << "# " << Slic3r::header_slic3r_generated() << std::endl;
else
- c << "# " << Slic3r::header_gcodeviewer_generated() << std::endl;
+ config_ss << "# " << Slic3r::header_gcodeviewer_generated() << std::endl;
// Make sure the "no" category is written first.
for (const auto& kvp : m_storage[""])
- c << kvp.first << " = " << kvp.second << std::endl;
+ config_ss << kvp.first << " = " << kvp.second << std::endl;
// Write the other categories.
for (const auto& category : m_storage) {
if (category.first.empty())
continue;
- c << std::endl << "[" << category.first << "]" << std::endl;
+ config_ss << std::endl << "[" << category.first << "]" << std::endl;
for (const auto& kvp : category.second)
- c << kvp.first << " = " << kvp.second << std::endl;
+ config_ss << kvp.first << " = " << kvp.second << std::endl;
}
// Write vendor sections
for (const auto &vendor : m_vendors) {
@@ -295,17 +392,42 @@ void AppConfig::save()
for (const auto &model : vendor.second) { size_sum += model.second.size(); }
if (size_sum == 0) { continue; }
- c << std::endl << "[" << VENDOR_PREFIX << vendor.first << "]" << std::endl;
+ config_ss << std::endl << "[" << VENDOR_PREFIX << vendor.first << "]" << std::endl;
for (const auto &model : vendor.second) {
- if (model.second.size() == 0) { continue; }
+ if (model.second.empty()) { continue; }
const std::vector variants(model.second.begin(), model.second.end());
const auto escaped = escape_strings_cstyle(variants);
- c << MODEL_PREFIX << model.first << " = " << escaped << std::endl;
+ config_ss << MODEL_PREFIX << model.first << " = " << escaped << std::endl;
}
}
- c.close();
+ // One empty line before the MD5 sum.
+ config_ss << std::endl;
+ std::string config_str = config_ss.str();
+ boost::nowide::ofstream c;
+ c.open(path_pid, std::ios::out | std::ios::trunc);
+ c << config_str;
+#ifdef WIN32
+ // WIN32 specific: The final "rename_file()" call is not safe in case of an application crash, there is no atomic "rename file" API
+ // provided by Windows (sic!). Therefore we save a MD5 checksum to be able to verify file corruption. In addition,
+ // we save the config file into a backup first before moving it to the final destination.
+ c << appconfig_md5_hash_line(config_str);
+#endif
+ c.close();
+
+#ifdef WIN32
+ // Make a backup of the configuration file before copying it to the final destination.
+ std::string error_message;
+ std::string backup_path = (boost::format("%1%.bak") % path).str();
+ // Copy configuration file with PID suffix into the configuration file with "bak" suffix.
+ if (copy_file(path_pid, backup_path, error_message, false) != SUCCESS)
+ BOOST_LOG_TRIVIAL(error) << "Copying from " << path_pid << " to " << backup_path << " failed. Failed to create a backup configuration.";
+#endif
+
+ // Rename the config atomically.
+ // On Windows, the rename is likely NOT atomic, thus it may fail if PrusaSlicer crashes on another thread in the meanwhile.
+ // To cope with that, we already made a backup of the config on Windows.
rename_file(path_pid, path);
m_dirty = false;
}
diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp
index c1bc0837c..07b09c5c8 100644
--- a/src/libslic3r/AppConfig.hpp
+++ b/src/libslic3r/AppConfig.hpp
@@ -37,7 +37,7 @@ public:
// Load the slic3r.ini from a user profile directory (or a datadir, if configured).
// return error string or empty strinf
- std::string load();
+ std::string load();
// Store the slic3r.ini into a user profile directory (or a datadir, if configured).
void save();
@@ -63,12 +63,12 @@ public:
{ std::string value; this->get("", key, value); return value; }
void set(const std::string §ion, const std::string &key, const std::string &value)
{
-#ifndef _NDEBUG
+#ifndef NDEBUG
std::string key_trimmed = key;
boost::trim_all(key_trimmed);
assert(key_trimmed == key);
assert(! key_trimmed.empty());
-#endif // _NDEBUG
+#endif // NDEBUG
std::string &old = m_storage[section][key];
if (old != value) {
old = value;
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index d04c08272..4d4dece27 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -33,6 +33,7 @@ add_library(libslic3r STATIC
EdgeGrid.hpp
ElephantFootCompensation.cpp
ElephantFootCompensation.hpp
+ enum_bitmask.hpp
ExPolygon.cpp
ExPolygon.hpp
ExPolygonCollection.cpp
diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp
index bd396243c..b9f9b266d 100644
--- a/src/libslic3r/Config.cpp
+++ b/src/libslic3r/Config.cpp
@@ -23,6 +23,10 @@
#include
#include
+//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 ConfigOptionDef::cli_args(const std::string &key) const
{
std::vector 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 items)
+void ConfigBase::set_deserialize(std::initializer_list 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(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());
+ this->set_deserialize(opt_key, v.second.get_value(), 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(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;
}
diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp
index 14b4b7c2e..bf356dfe6 100644
--- a/src/libslic3r/Config.hpp
+++ b/src/libslic3r/Config.hpp
@@ -16,6 +16,7 @@
#include "Exception.hpp"
#include "Point.hpp"
+#include
#include
#include
#include
@@ -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;
+
+// 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;
+
+// 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 items);
+ void set_deserialize(std::initializer_list items, ConfigSubstitutionContext& substitutions);
+ void set_deserialize_strict(std::initializer_list 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.
diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp
index fbf27c548..c2ba011a8 100644
--- a/src/libslic3r/Format/3mf.cpp
+++ b/src/libslic3r/Format/3mf.cpp
@@ -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(".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;
}
diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp
index a09a1b834..b91e90da7 100644
--- a/src/libslic3r/Format/3mf.hpp
+++ b/src/libslic3r/Format/3mf.hpp
@@ -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
diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp
index 94318a930..d03cfd4fa 100644
--- a/src/libslic3r/Format/AMF.cpp
+++ b/src/libslic3r/Format/AMF.cpp
@@ -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;
diff --git a/src/libslic3r/Format/AMF.hpp b/src/libslic3r/Format/AMF.hpp
index b4e2c54ab..a073071fc 100644
--- a/src/libslic3r/Format/AMF.hpp
+++ b/src/libslic3r/Format/AMF.hpp
@@ -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
diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp
index abf30a53b..586fbafb5 100644
--- a/src/libslic3r/Format/PRUS.cpp
+++ b/src/libslic3r/Format/PRUS.cpp
@@ -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.
diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp
index f556b0ead..6d779b94e 100644
--- a/src/libslic3r/Format/SL1.cpp
+++ b/src/libslic3r/Format/SL1.cpp
@@ -287,13 +287,13 @@ std::vector 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;
diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp
index a3e6ce26c..2c7e1edc1 100644
--- a/src/libslic3r/Format/SL1.hpp
+++ b/src/libslic3r/Format/SL1.hpp
@@ -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 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 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
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index ca76a2320..24bd4939f 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -523,7 +523,8 @@ std::vector GCode::collect_layers_to_print(const PrintObjec
// Check that there are extrusions on the very first layer.
if (layers_to_print.size() == 1u) {
if (!has_extrusions)
- throw Slic3r::SlicingError(_(L("There is an object with no extrusions on the first layer.")));
+ throw Slic3r::SlicingError(_(L("There is an object with no extrusions in the first layer.")) + "\n" +
+ _(L("Object name")) + ": " + object.model_object()->name);
}
// In case there are extrusions on this layer, check there is a layer to lay it on.
@@ -541,7 +542,7 @@ std::vector GCode::collect_layers_to_print(const PrintObjec
if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) {
const_cast(object.print())->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
- _(L("Empty layers detected. Make sure the object is printable.")) + "\n\n" +
+ _(L("Empty layers detected. Make sure the object is printable.")) + "\n" +
_(L("Object name")) + ": " + object.model_object()->name + "\n" + _(L("Print z")) + ": " +
std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is "
"usually caused by negligibly small extrusions or by a faulty model. Try to repair "
diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp
index a3f2f2219..65679f120 100644
--- a/src/libslic3r/GCode/GCodeProcessor.cpp
+++ b/src/libslic3r/GCode/GCodeProcessor.cpp
@@ -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)
diff --git a/src/libslic3r/LocalesUtils.cpp b/src/libslic3r/LocalesUtils.cpp
index 8f4d26642..64ab700ed 100644
--- a/src/libslic3r/LocalesUtils.cpp
+++ b/src/libslic3r/LocalesUtils.cpp
@@ -19,6 +19,7 @@ CNumericLocalesSetter::CNumericLocalesSetter()
#else // APPLE
m_original_locale = uselocale((locale_t)0);
m_new_locale = newlocale(LC_NUMERIC_MASK, "C", m_original_locale);
+ uselocale(m_new_locale);
#endif
}
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index 84566f4b1..a382a4258 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -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()) {
@@ -1134,17 +1143,18 @@ bool ModelObject::needed_repair() const
return false;
}
-ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower)
+ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes)
{
- if (!keep_upper && !keep_lower) { return {}; }
+ if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower))
+ return {};
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
// Clone the object to duplicate instances, materials etc.
- ModelObject* upper = keep_upper ? ModelObject::new_clone(*this) : nullptr;
- ModelObject* lower = keep_lower ? ModelObject::new_clone(*this) : nullptr;
+ ModelObject* upper = attributes.has(ModelObjectCutAttribute::KeepUpper) ? ModelObject::new_clone(*this) : nullptr;
+ ModelObject* lower = attributes.has(ModelObjectCutAttribute::KeepLower) ? ModelObject::new_clone(*this) : nullptr;
- if (keep_upper) {
+ if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
upper->set_model(nullptr);
upper->sla_support_points.clear();
upper->sla_drain_holes.clear();
@@ -1153,7 +1163,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
upper->input_file.clear();
}
- if (keep_lower) {
+ if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
lower->set_model(nullptr);
lower->sla_support_points.clear();
lower->sla_drain_holes.clear();
@@ -1193,8 +1203,10 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix));
- if (keep_upper) { upper->add_volume(*volume); }
- if (keep_lower) { lower->add_volume(*volume); }
+ if (attributes.has(ModelObjectCutAttribute::KeepUpper))
+ upper->add_volume(*volume);
+ if (attributes.has(ModelObjectCutAttribute::KeepLower))
+ lower->add_volume(*volume);
}
else if (! volume->mesh().empty()) {
@@ -1214,19 +1226,19 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
indexed_triangle_set upper_its, lower_its;
mesh.require_shared_vertices();
cut_mesh(mesh.its, float(z), &upper_its, &lower_its);
- if (keep_upper) {
+ if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
upper_mesh = TriangleMesh(upper_its);
upper_mesh.repair();
upper_mesh.reset_repair_stats();
}
- if (keep_lower) {
+ if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
lower_mesh = TriangleMesh(lower_its);
lower_mesh.repair();
lower_mesh.reset_repair_stats();
}
}
- if (keep_upper && upper_mesh.facets_count() > 0) {
+ if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper_mesh.facets_count() > 0) {
ModelVolume* vol = upper->add_volume(upper_mesh);
vol->name = volume->name;
// Don't copy the config's ID.
@@ -1235,7 +1247,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
assert(vol->config.id() != volume->config.id());
vol->set_material(volume->material_id(), *volume->material());
}
- if (keep_lower && lower_mesh.facets_count() > 0) {
+ if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower_mesh.facets_count() > 0) {
ModelVolume* vol = lower->add_volume(lower_mesh);
vol->name = volume->name;
// Don't copy the config's ID.
@@ -1246,7 +1258,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
// Compute the lower part instances' bounding boxes to figure out where to place
// the upper part
- if (keep_upper) {
+ if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
for (size_t i = 0; i < instances.size(); i++) {
lower_bboxes[i].merge(instances[i]->transform_mesh_bounding_box(lower_mesh, true));
}
@@ -1257,7 +1269,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
ModelObjectPtrs res;
- if (keep_upper && upper->volumes.size() > 0) {
+ if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) {
upper->invalidate_bounding_box();
upper->center_around_origin();
@@ -1277,7 +1289,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
res.push_back(upper);
}
- if (keep_lower && lower->volumes.size() > 0) {
+ if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) {
lower->invalidate_bounding_box();
lower->center_around_origin();
@@ -1288,7 +1300,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
instance->set_transformation(Geometry::Transformation());
instance->set_offset(offset);
- instance->set_rotation(Vec3d(rotate_lower ? Geometry::deg2rad(180.0) : 0.0, 0.0, rot_z));
+ instance->set_rotation(Vec3d(attributes.has(ModelObjectCutAttribute::FlipLower) ? Geometry::deg2rad(180.0) : 0.0, 0.0, rot_z));
}
res.push_back(lower);
@@ -1628,7 +1640,7 @@ bool ModelVolume::is_splittable() const
{
// the call mesh.is_splittable() is expensive, so cache the value to calculate it only once
if (m_is_splittable == -1)
- m_is_splittable = (int)this->mesh().is_splittable();
+ m_is_splittable = its_is_splittable(this->mesh().its);
return m_is_splittable == 1;
}
@@ -1738,7 +1750,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;
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
index c6a54d5c6..59ab14b4c 100644
--- a/src/libslic3r/Model.hpp
+++ b/src/libslic3r/Model.hpp
@@ -2,6 +2,7 @@
#define slic3r_Model_hpp_
#include "libslic3r.h"
+#include "enum_bitmask.hpp"
#include "Geometry.hpp"
#include "ObjectID.hpp"
#include "Point.hpp"
@@ -12,6 +13,7 @@
#include "TriangleMesh.hpp"
#include "Arrange.hpp"
#include "CustomGCode.hpp"
+#include "enum_bitmask.hpp"
#include