Merge branch 'master' into fs_QuadricEdgeCollapse

This commit is contained in:
Filip Sykala 2021-07-07 16:52:10 +02:00
commit ed9152d004
107 changed files with 2066 additions and 808 deletions

View file

@ -4,7 +4,7 @@
#include "Exception.hpp"
#include "LocalesUtils.hpp"
#include "Thread.hpp"
#include "format.hpp"
#include <utility>
#include <vector>
@ -18,15 +18,24 @@
#include <boost/property_tree/ptree_fwd.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/format/format_fwd.hpp>
#include <boost/log/trivial.hpp>
//#include <wx/string.h>
//#include "I18N.hpp"
#ifdef WIN32
//FIXME replace the two following includes with <boost/md5.hpp> after it becomes mainstream.
#include <boost/uuid/detail/md5.hpp>
#include <boost/algorithm/hex.hpp>
#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 <boost/md5.hpp> 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<std::string> 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;
}

View file

@ -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 &section, 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;

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

@ -523,7 +523,8 @@ std::vector<GCode::LayerToPrint> 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::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) {
const_cast<Print*>(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 "

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

@ -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
}

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()) {
@ -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;

View file

@ -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 <map>
#include <memory>
@ -226,6 +228,10 @@ enum class ModelVolumeType : int {
SUPPORT_ENFORCER,
};
enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower };
using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials),
// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials.
// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed,
@ -344,7 +350,7 @@ public:
size_t materials_count() const;
size_t facets_count() const;
bool needed_repair() const;
ModelObjectPtrs cut(size_t instance, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); // Note: z is in world coordinates
ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes);
void split(ModelObjectPtrs* new_objects);
void merge();
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
@ -1031,8 +1037,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 +1115,8 @@ private:
}
};
ENABLE_ENUM_BITMASK_OPERATORS(Model::LoadAttribute)
#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE

View file

@ -4,20 +4,15 @@
#include "Layer.hpp"
#include "Print.hpp"
#include "VoronoiVisualUtils.hpp"
#include "MutablePolygon.hpp"
#include <utility>
#include <cfloat>
#include <unordered_set>
#include <boost/log/trivial.hpp>
#include <tbb/parallel_for.h>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point.hpp>
#include <boost/geometry/geometries/segment.hpp>
#include <boost/geometry/index/rtree.hpp>
namespace Slic3r {
struct ColoredLine {
Line line;
@ -89,28 +84,37 @@ struct PaintedLineVisitor
bool operator()(coord_t iy, coord_t ix)
{
// Called with a row and column of the grid cell, which is intersected by a line.
auto cell_data_range = grid.cell_data_range(iy, ix);
const Vec2d v1 = line_to_test.vector().cast<double>();
auto cell_data_range = grid.cell_data_range(iy, ix);
const Vec2d v1 = line_to_test.vector().cast<double>();
const double v1_sqr_norm = v1.squaredNorm();
const double heuristic_thr_part = line_to_test.length() + append_threshold;
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
Line grid_line = grid.line(*it_contour_and_segment);
const Vec2d v2 = grid_line.vector().cast<double>();
Line grid_line = grid.line(*it_contour_and_segment);
const Vec2d v2 = grid_line.vector().cast<double>();
double heuristic_thr_sqr = Slic3r::sqr(heuristic_thr_part + grid_line.length());
// An inexpensive heuristic to test whether line_to_test and grid_line can be somewhere close enough to each other.
// This helps filter out cases when the following expensive calculations are useless.
if ((grid_line.a - line_to_test.a).cast<double>().squaredNorm() > heuristic_thr_sqr ||
(grid_line.b - line_to_test.a).cast<double>().squaredNorm() > heuristic_thr_sqr ||
(grid_line.a - line_to_test.b).cast<double>().squaredNorm() > heuristic_thr_sqr ||
(grid_line.b - line_to_test.b).cast<double>().squaredNorm() > heuristic_thr_sqr)
continue;
// When lines have too different length, it is necessary to normalize them
if (Slic3r::sqr(v1.dot(v2)) > cos_threshold2 * v1.squaredNorm() * v2.squaredNorm()) {
if (Slic3r::sqr(v1.dot(v2)) > cos_threshold2 * v1_sqr_norm * v2.squaredNorm()) {
// The two vectors are nearly collinear (their mutual angle is lower than 30 degrees)
if (painted_lines_set.find(*it_contour_and_segment) == painted_lines_set.end()) {
double dist_1 = grid_line.distance_to(line_to_test.a);
double dist_2 = grid_line.distance_to(line_to_test.b);
double dist_3 = line_to_test.distance_to(grid_line.a);
double dist_4 = line_to_test.distance_to(grid_line.b);
double total_dist = std::min(std::min(dist_1, dist_2), std::min(dist_3, dist_4));
if (total_dist < 50 * SCALED_EPSILON) {
if (grid_line.distance_to_squared(line_to_test.a) < append_threshold2 ||
grid_line.distance_to_squared(line_to_test.b) < append_threshold2 ||
line_to_test.distance_to_squared(grid_line.a) < append_threshold2 ||
line_to_test.distance_to_squared(grid_line.b) < append_threshold2) {
Line line_to_test_projected;
project_line_on_line(grid_line, line_to_test, &line_to_test_projected);
if (Line(grid_line.a, line_to_test_projected.a).length() > Line(grid_line.a, line_to_test_projected.b).length()) {
if ((line_to_test_projected.a - grid_line.a).cast<double>().squaredNorm() > (line_to_test_projected.b - grid_line.a).cast<double>().squaredNorm())
line_to_test_projected.reverse();
}
painted_lines.push_back({it_contour_and_segment->first, it_contour_and_segment->second, line_to_test_projected, this->color});
painted_lines_set.insert(*it_contour_and_segment);
}
@ -125,9 +129,11 @@ struct PaintedLineVisitor
std::vector<PaintedLine> &painted_lines;
Line line_to_test;
std::unordered_set<std::pair<size_t, size_t>, boost::hash<std::pair<size_t, size_t>>> painted_lines_set;
int color = -1;
int color = -1;
static inline const double cos_threshold2 = Slic3r::sqr(cos(M_PI * 30. / 180.));
static inline const double cos_threshold2 = Slic3r::sqr(cos(M_PI * 30. / 180.));
static inline const double append_threshold = 50 * SCALED_EPSILON;
static inline const double append_threshold2 = Slic3r::sqr(append_threshold);
};
static std::vector<ColoredLine> to_colored_lines(const Polygon &polygon, int color)
@ -154,6 +160,7 @@ static Polygon colored_points_to_polygon(const std::vector<ColoredLine> &lines)
static Polygons colored_points_to_polygon(const std::vector<std::vector<ColoredLine>> &lines)
{
Polygons out;
out.reserve(lines.size());
for (const std::vector<ColoredLine> &l : lines)
out.emplace_back(colored_points_to_polygon(l));
return out;
@ -484,6 +491,12 @@ static std::vector<std::vector<ColoredLine>> colorize_polygons(const Polygons &p
using boost::polygon::voronoi_diagram;
static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return Point(coord_t(point->x()), coord_t(point->y())); }
static inline Point mk_point(const Voronoi::Internal::point_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); }
static inline Point mk_point(const voronoi_diagram<double>::vertex_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); }
struct MMU_Graph
{
enum class ARC_TYPE { BORDER, NON_BORDER };
@ -616,24 +629,63 @@ struct MMU_Graph
{
return this->is_vertex_on_contour(edge_iterator->vertex0()) && this->is_vertex_on_contour(edge_iterator->vertex1());
}
// All Voronoi vertices are post-processes to merge very close vertices to single. Witch eliminates issues with intersection edges.
// Also, Voronoi vertices outside of the bounding of input polygons are throw away by marking them.
void append_voronoi_vertices(const Geometry::VoronoiDiagram &vd, const Polygons &color_poly_tmp, BoundingBox bbox) {
bbox.offset(SCALED_EPSILON);
struct CPoint
{
CPoint() = delete;
CPoint(const Point &point, size_t contour_idx, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(contour_idx) {}
CPoint(const Point &point, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(0) {}
const Point m_point;
size_t m_point_idx;
size_t m_contour_idx;
[[nodiscard]] const Point &point() const { return m_point; }
bool operator==(const CPoint &rhs) const { return this->m_point == rhs.m_point && this->m_contour_idx == rhs.m_contour_idx && this->m_point_idx == rhs.m_point_idx; }
};
struct CPointAccessor { const Point* operator()(const CPoint &pt) const { return &pt.point(); }};
typedef ClosestPointInRadiusLookup<CPoint, CPointAccessor> CPointLookupType;
CPointLookupType closest_voronoi_point(3 * coord_t(SCALED_EPSILON));
CPointLookupType closest_contour_point(3 * coord_t(SCALED_EPSILON));
for (const Polygon &polygon : color_poly_tmp)
for (const Point &pt : polygon.points)
closest_contour_point.insert(CPoint(pt, &polygon - &color_poly_tmp.front(), &pt - &polygon.points.front()));
for (const voronoi_diagram<double>::vertex_type &vertex : vd.vertices()) {
vertex.color(-1);
Point vertex_point = mk_point(vertex);
const Point &first_point = this->nodes[this->get_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point;
const Point &second_point = this->nodes[this->get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point;
if (vertex_equal_to_point(&vertex, first_point)) {
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
vertex.color(this->get_arc(vertex.incident_edge()->cell()->source_index()).from_idx);
} else if (vertex_equal_to_point(&vertex, second_point)) {
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
vertex.color(this->get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx);
} else if (bbox.contains(vertex_point)) {
if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < 3 * SCALED_EPSILON) {
vertex.color(this->get_global_index(contour_pt->m_contour_idx, contour_pt->m_point_idx));
} else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= 3 * SCALED_EPSILON) {
closest_voronoi_point.insert(CPoint(vertex_point, this->nodes_count()));
vertex.color(this->nodes_count());
this->nodes.push_back({vertex_point});
} else {
vertex.color(voronoi_pt->m_point_idx);
}
}
}
}
};
namespace bg = boost::geometry;
namespace bgm = boost::geometry::model;
namespace bgi = boost::geometry::index;
// float is needed because for coord_t bgi::intersects throws "bad numeric conversion: positive overflow"
using rtree_point_t = bgm::point<float, 2, boost::geometry::cs::cartesian>;
using rtree_t = bgi::rtree<std::pair<rtree_point_t, size_t>, bgi::rstar<16, 4>>;
static inline rtree_point_t mk_rtree_point(const Point &pt) { return rtree_point_t(float(pt.x()), float(pt.y())); }
static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return Point(coord_t(point->x()), coord_t(point->y())); }
static inline Point mk_point(const Voronoi::Internal::point_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); }
static inline Point mk_point(const voronoi_diagram<double>::vertex_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); }
static inline void mark_processed(const voronoi_diagram<double>::const_edge_iterator &edge_iterator)
{
edge_iterator->color(true);
@ -695,7 +747,7 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
{
Geometry::VoronoiDiagram vd;
std::vector<ColoredLine> lines_colored = to_lines(color_poly);
Polygons color_poly_tmp = colored_points_to_polygon(color_poly);
const Polygons color_poly_tmp = colored_points_to_polygon(color_poly);
const Points points = to_points(color_poly_tmp);
const Lines lines = to_lines(color_poly_tmp);
@ -719,6 +771,7 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
boost::polygon::construct_voronoi(lines_colored.begin(), lines_colored.end(), &vd);
MMU_Graph graph;
graph.nodes.reserve(points.size() + vd.vertices().size());
for (const Point &point : points)
graph.nodes.push_back({point});
@ -726,66 +779,8 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
init_polygon_indices(graph, color_poly, lines_colored);
assert(graph.nodes.size() == lines_colored.size());
// All Voronoi vertices are post-processes to merge very close vertices to single. Witch Eliminates issues with intersection edges.
// Also, Voronoi vertices outside of the bounding of input polygons are throw away by marking them.
auto append_voronoi_vertices_to_graph = [&graph, &color_poly_tmp, &vd]() -> void {
auto is_equal_points = [](const Point &p1, const Point &p2) { return p1 == p2 || (p1 - p2).cast<double>().norm() <= 3 * SCALED_EPSILON; };
BoundingBox bbox = get_extents(color_poly_tmp);
bbox.offset(SCALED_EPSILON);
// EdgeGrid is used for vertices near to contour and rtree for other vertices
// FIXME Lukas H.: Get rid of EdgeGrid and rtree. Use only one structure for both cases.
EdgeGrid::Grid grid;
grid.set_bbox(bbox);
grid.create(color_poly_tmp, coord_t(scale_(10.)));
rtree_t rtree;
for (const voronoi_diagram<double>::vertex_type &vertex : vd.vertices()) {
vertex.color(-1);
Point vertex_point = mk_point(vertex);
const Point &first_point = graph.nodes[graph.get_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point;
const Point &second_point = graph.nodes[graph.get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point;
if (vertex_equal_to_point(&vertex, first_point)) {
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
vertex.color(graph.get_arc(vertex.incident_edge()->cell()->source_index()).from_idx);
} else if (vertex_equal_to_point(&vertex, second_point)) {
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
vertex.color(graph.get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx);
} else if (bbox.contains(vertex_point)) {
EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point_signed_distance(vertex_point, coord_t(3 * SCALED_EPSILON));
if (cp.valid()) {
size_t global_idx = graph.get_global_index(cp.contour_idx, cp.start_point_idx);
size_t global_idx_next = graph.get_global_index(cp.contour_idx, (cp.start_point_idx + 1) % color_poly_tmp[cp.contour_idx].points.size());
vertex.color(is_equal_points(vertex_point, graph.nodes[global_idx].point) ? global_idx : global_idx_next);
} else {
if (rtree.empty()) {
rtree.insert(std::make_pair(mk_rtree_point(vertex_point), graph.nodes_count()));
vertex.color(graph.nodes_count());
graph.nodes.push_back({vertex_point});
} else {
std::vector<std::pair<rtree_point_t, size_t>> closest;
rtree.query(bgi::nearest(mk_rtree_point(vertex_point), 1), std::back_inserter(closest));
assert(!closest.empty());
rtree_point_t r_point = closest.front().first;
Point closest_p(bg::get<0>(r_point), bg::get<1>(r_point));
if (Line(vertex_point, closest_p).length() > 3 * SCALED_EPSILON) {
rtree.insert(std::make_pair(mk_rtree_point(vertex_point), graph.nodes_count()));
vertex.color(graph.nodes_count());
graph.nodes.push_back({vertex_point});
} else {
vertex.color(closest.front().second);
}
}
}
}
}
};
append_voronoi_vertices_to_graph();
BoundingBox bbox = get_extents(color_poly_tmp);
graph.append_voronoi_vertices(vd, color_poly_tmp, bbox);
auto get_prev_contour_line = [&lines_colored, &color_poly, &graph](const voronoi_diagram<double>::const_edge_iterator &edge_it) -> ColoredLine {
size_t contour_line_local_idx = lines_colored[edge_it->cell()->source_index()].local_line_idx;
@ -803,7 +798,6 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
return lines_colored[contour_next_idx];
};
BoundingBox bbox = get_extents(color_poly_tmp);
bbox.offset(scale_(10.));
const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y()));
@ -1428,7 +1422,7 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
// All expolygons are expanded by SCALED_EPSILON, merged, and then shrunk again by SCALED_EPSILON
// to ensure that very close polygons will be merged.
ex_polygons = union_ex(ex_polygons);
// Remove all expolygons and holes with an area less than 0.01mm^2
// Remove all expolygons and holes with an area less than 0.1mm^2
remove_small_and_small_holes(ex_polygons, Slic3r::sqr(scale_(0.1f)));
// Occasionally, some input polygons contained self-intersections that caused problems with Voronoi diagrams
// and consequently with the extraction of colored segments by function extract_colored_segments.
@ -1437,19 +1431,19 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
// Such close points sometimes caused that the Voronoi diagram has self-intersecting edges around these vertices.
// This consequently leads to issues with the extraction of colored segments by function extract_colored_segments.
// Calling expolygons_simplify fixed these issues.
input_expolygons[layer_idx] = simplify_polygons_ex(to_polygons(expolygons_simplify(offset_ex(ex_polygons, float(-10 * SCALED_EPSILON)), 5 * SCALED_EPSILON)));
input_polygons[layer_idx] = to_polygons(input_expolygons[layer_idx]);
input_expolygons[layer_idx] = smooth_outward(expolygons_simplify(offset_ex(ex_polygons, -10.f * float(SCALED_EPSILON)), 5 * SCALED_EPSILON), 10 * coord_t(SCALED_EPSILON));
input_polygons[layer_idx] = to_polygons(input_expolygons[layer_idx]);
}
}); // end of parallel_for
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - slices preparation in parallel - end";
for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) {
throw_on_cancel_callback();
BoundingBox bbox(get_extents(input_expolygons[layer_idx]));
BoundingBox bbox(get_extents(input_polygons[layer_idx]));
// Projected triangles may slightly exceed the input polygons.
bbox.offset(20 * SCALED_EPSILON);
edge_grids[layer_idx].set_bbox(bbox);
edge_grids[layer_idx].create(input_expolygons[layer_idx], coord_t(scale_(10.)));
edge_grids[layer_idx].create(input_polygons[layer_idx], coord_t(scale_(10.)));
}
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - projection of painted triangles - begin";
@ -1498,7 +1492,7 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
// [P0, P2] a [P0, P1]
float t1 = (float(layer->slice_z) - facet[0].z()) / (facet[1].z() - facet[0].z());
line_end_f = facet[0] + t1 * (facet[1] - facet[0]);
} else if (facet[1].z() <= layer->slice_z) {
} else {
// [P0, P2] a [P1, P2]
float t2 = (float(layer->slice_z) - facet[1].z()) / (facet[2].z() - facet[1].z());
line_end_f = facet[1] + t2 * (facet[2] - facet[1]);

View file

@ -166,7 +166,7 @@ static bool clip_narrow_corner(
assert(orient1 > 0 == blocked);
assert(orient2 > 0 == blocked);
}
#endif // _NDEBUG
#endif // NDEBUG
if (polygon.size() < 3 || (forward == Far && backward == Far)) {
polygon.clear();
} else {

View file

@ -3,6 +3,7 @@
#include "Point.hpp"
#include "Polygon.hpp"
#include "ExPolygon.hpp"
namespace Slic3r {
@ -330,6 +331,24 @@ inline Polygons smooth_outward(Polygons polygons, coord_t clip_dist_scaled)
return polygons;
}
inline ExPolygons smooth_outward(ExPolygons expolygons, coord_t clip_dist_scaled)
{
MutablePolygon mp;
for (ExPolygon &expolygon : expolygons) {
mp.assign(expolygon.contour, expolygon.contour.size() * 2);
smooth_outward(mp, clip_dist_scaled);
mp.polygon(expolygon.contour);
for (Polygon &hole : expolygon.holes) {
mp.assign(hole, hole.size() * 2);
smooth_outward(mp, clip_dist_scaled);
mp.polygon(hole);
}
expolygon.holes.erase(std::remove_if(expolygon.holes.begin(), expolygon.holes.end(), [](const auto &p) { return p.empty(); }), expolygon.holes.end());
}
expolygons.erase(std::remove_if(expolygons.begin(), expolygons.end(), [](const auto &p) { return p.empty(); }), expolygons.end());
return expolygons;
}
}
#endif // slic3r_MutablePolygon_hpp_

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,25 @@ 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, {});
UNUSED(presets_imported);
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 = [&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 +987,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 config_substitutions;
}
// Process the Config Bundle loaded as a Boost property tree.
@ -1114,11 +1134,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 +1160,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 +1274,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 +1285,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 +1307,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 +1342,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 +1371,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 +1394,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 +1406,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 +1434,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 +1464,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

@ -448,9 +448,9 @@ public:
// Canceled internally from Print::apply() through the Print/PrintObject::invalidate_step() or ::invalidate_all_steps().
CANCELED_INTERNAL = 2
};
CancelStatus cancel_status() const { return m_cancel_status; }
CancelStatus cancel_status() const { return m_cancel_status.load(std::memory_order_acquire); }
// Has the calculation been canceled?
bool canceled() const { return m_cancel_status != NOT_CANCELED; }
bool canceled() const { return m_cancel_status.load(std::memory_order_acquire) != NOT_CANCELED; }
// Cancel the running computation. Stop execution of all the background threads.
void cancel() { m_cancel_status = CANCELED_BY_USER; }
void cancel_internal() { m_cancel_status = CANCELED_INTERNAL; }
@ -481,7 +481,7 @@ protected:
// If the background processing stop was requested, throw CanceledException.
// To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly.
void throw_if_canceled() const { if (m_cancel_status) throw CanceledException(); }
void throw_if_canceled() const { if (m_cancel_status.load(std::memory_order_acquire)) throw CanceledException(); }
// Wrapper around this->throw_if_canceled(), so that throw_if_canceled() may be passed to a function without making throw_if_canceled() public.
PrintTryCancel make_try_cancel() const { return PrintTryCancel(this); }

View file

@ -50,7 +50,7 @@ static t_config_enum_values s_keys_map_GCodeFlavor {
{ "teacup", gcfTeacup },
{ "makerware", gcfMakerWare },
{ "marlin", gcfMarlinLegacy },
{ "marlinfirmware", gcfMarlinFirmware },
{ "marlin2", gcfMarlinFirmware },
{ "sailfish", gcfSailfish },
{ "smoothie", gcfSmoothie },
{ "mach3", gcfMach3 },
@ -67,6 +67,7 @@ static t_config_enum_values s_keys_map_MachineLimitsUsage {
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(MachineLimitsUsage)
static t_config_enum_values s_keys_map_PrintHostType {
{ "prusalink", htPrusaLink },
{ "octoprint", htOctoPrint },
{ "duet", htDuet },
{ "flashair", htFlashAir },
@ -172,6 +173,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)
@ -1245,7 +1253,7 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back("teacup");
def->enum_values.push_back("makerware");
def->enum_values.push_back("marlin");
def->enum_values.push_back("marlinfirmware");
def->enum_values.push_back("marlin2");
def->enum_values.push_back("sailfish");
def->enum_values.push_back("mach3");
def->enum_values.push_back("machinekit");
@ -1772,11 +1780,13 @@ void PrintConfigDef::init_fff_params()
def->tooltip = L("Slic3r can upload G-code files to a printer host. This field must contain "
"the kind of the host.");
def->enum_keys_map = &ConfigOptionEnum<PrintHostType>::get_enum_values();
def->enum_values.push_back("prusalink");
def->enum_values.push_back("octoprint");
def->enum_values.push_back("duet");
def->enum_values.push_back("flashair");
def->enum_values.push_back("astrobox");
def->enum_values.push_back("repetier");
def->enum_labels.push_back("PrusaLink");
def->enum_labels.push_back("OctoPrint");
def->enum_labels.push_back("Duet");
def->enum_labels.push_back("FlashAir");
@ -3608,8 +3618,12 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
} catch (boost::bad_lexical_cast &) {
value = "0";
}
} else if (opt_key == "gcode_flavor" && value == "makerbot") {
value = "makerware";
} else if (opt_key == "gcode_flavor") {
if (value == "makerbot")
value = "makerware";
else if (value == "marlinfirmware")
// the "new" marlin firmware flavor used to be called "marlinfirmware" for some time during PrusaSlicer 2.4.0-alpha development.
value = "marlin2";
} else if (opt_key == "fill_density" && value.find("%") == std::string::npos) {
try {
// fill_density was turned into a percent value
@ -3622,7 +3636,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 +4185,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

@ -44,7 +44,7 @@ enum class MachineLimitsUsage {
};
enum PrintHostType {
htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier
htPrusaLink, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier
};
enum AuthorizationType {
@ -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

@ -10,11 +10,11 @@
#include <condition_variable>
#include <mutex>
#include <thread>
#include <tbb/global_control.h>
#include <tbb/parallel_for.h>
#include <tbb/task_arena.h>
#include "Thread.hpp"
#include "Utils.hpp"
namespace Slic3r {
@ -199,16 +199,14 @@ void name_tbb_thread_pool_threads()
// TBB will respect the task affinity mask on Linux and spawn less threads than std::thread::hardware_concurrency().
// const size_t nthreads_hw = std::thread::hardware_concurrency();
const size_t nthreads_hw = tbb::this_task_arena::max_concurrency();
size_t nthreads = nthreads_hw;
size_t nthreads = nthreads_hw;
#ifdef SLIC3R_PROFILE
// Shiny profiler is not thread safe, thus disable parallelization.
disable_multi_threading();
nthreads = 1;
#endif
if (nthreads != nthreads_hw)
tbb::global_control(tbb::global_control::max_allowed_parallelism, nthreads);
std::atomic<size_t> nthreads_running(0);
std::condition_variable cv;
std::mutex cv_m;

View file

@ -3,9 +3,9 @@
#include <boost/container/small_vector.hpp>
#ifndef _NDEBUG
#ifndef NDEBUG
#define EXPENSIVE_DEBUG_CHECKS
#endif // _NDEBUG
#endif // NDEBUG
namespace Slic3r {
@ -19,7 +19,7 @@ static inline Vec3i root_neighbors(const TriangleMesh &mesh, int triangle_id)
return neighbors;
}
#ifndef _NDEBUG
#ifndef NDEBUG
bool TriangleSelector::verify_triangle_midpoints(const Triangle &tr) const
{
for (int i = 0; i < 3; ++ i) {
@ -57,7 +57,7 @@ bool TriangleSelector::verify_triangle_neighbors(const Triangle &tr, const Vec3i
}
return true;
}
#endif // _NDEBUG
#endif // NDEBUG
// sides_to_split==-1 : just restore previous split
void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx)
@ -308,12 +308,12 @@ int TriangleSelector::triangle_midpoint_or_allocate(int itriangle, int vertexi,
}
assert(m_vertices[midpoint].ref_cnt == 0);
} else {
#ifndef _NDEBUG
#ifndef NDEBUG
Vec3f c1 = 0.5f * (m_vertices[vertexi].v + m_vertices[vertexj].v);
Vec3f c2 = m_vertices[midpoint].v;
float d = (c2 - c1).norm();
assert(std::abs(d) < EPSILON);
#endif // _NDEBUG
#endif // NDEBUG
assert(m_vertices[midpoint].ref_cnt > 0);
}
return midpoint;
@ -816,13 +816,13 @@ void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, Enfo
assert(tr.is_split());
// indices of triangle vertices
#ifdef _NDEBUG
#ifdef NDEBUG
boost::container::small_vector<int, 6> verts_idxs;
#else // _NDEBUG
#else // NDEBUG
// For easier debugging.
std::vector<int> verts_idxs;
verts_idxs.reserve(6);
#endif // _NDEBUG
#endif // NDEBUG
for (int j=0, idx = tr.special_side(); j<3; ++j, idx = next_idx_modulo(idx, 3))
verts_idxs.push_back(tr.verts_idxs[idx]);
@ -861,13 +861,13 @@ void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, Enfo
break;
}
#ifndef _NDEBUG
#ifndef NDEBUG
assert(this->verify_triangle_neighbors(tr, neighbors));
for (int i = 0; i <= tr.number_of_split_sides(); ++i) {
Vec3i n = this->child_neighbors(tr, neighbors, i);
assert(this->verify_triangle_neighbors(m_triangles[tr.children[i]], n));
}
#endif // _NDEBUG
#endif // NDEBUG
}
bool TriangleSelector::has_facets(EnforcerBlockerType state) const

View file

@ -204,10 +204,10 @@ private:
int triangle_midpoint(int itriangle, int vertexi, int vertexj) const;
int triangle_midpoint_or_allocate(int itriangle, int vertexi, int vertexj);
#ifndef _NDEBUG
#ifndef NDEBUG
bool verify_triangle_neighbors(const Triangle& tr, const Vec3i& neighbors) const;
bool verify_triangle_midpoints(const Triangle& tr) const;
#endif // _NDEBUG
#endif // NDEBUG
void get_facets_strict_recursive(
const Triangle &tr,

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

@ -8,6 +8,7 @@
#include "Platform.hpp"
#include "Time.hpp"
#include "libslic3r.h"
#ifdef WIN32
#include <windows.h>
@ -43,7 +44,13 @@
#include <boost/nowide/convert.hpp>
#include <boost/nowide/cstdio.hpp>
#include <tbb/global_control.h>
// We are using quite an old TBB 2017 U7, which does not support global control API officially.
// Before we update our build servers, let's use the old API, which is deprecated in up to date TBB.
#ifdef TBB_HAS_GLOBAL_CONTROL
#include <tbb/global_control.h>
#else
#include <tbb/task_scheduler_init.h>
#endif
#if defined(__linux__) || defined(__GNUC__ )
#include <strings.h>
@ -118,7 +125,12 @@ void trace(unsigned int level, const char *message)
void disable_multi_threading()
{
// Disable parallelization so the Shiny profiler works
#ifdef TBB_HAS_GLOBAL_CONTROL
tbb::global_control(tbb::global_control::max_allowed_parallelism, 1);
#else // TBB_HAS_GLOBAL_CONTROL
static tbb::task_scheduler_init *tbb_init = new tbb::task_scheduler_init(1);
UNUSED(tbb_init);
#endif // TBB_HAS_GLOBAL_CONTROL
}
static std::string g_var_dir;