Merge remote-tracking branch 'remotes/prusa/stable' into dev

need to merge profiles
need to test & iron out the PresetConfigSubstitutions thingy
This commit is contained in:
remi durand 2021-07-10 15:31:19 +02:00
commit f87b5fccde
82 changed files with 1925 additions and 657 deletions

71
resources/icons/info.svg Normal file
View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.0"
id="error"
x="0px"
y="0px"
viewBox="0 0 200 200"
enable-background="new 0 0 100 100"
xml:space="preserve"
sodipodi:docname="notification_error.svg"
width="200"
height="200"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"><metadata
id="metadata19"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs17" /><sodipodi:namedview
inkscape:document-rotation="0"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1377"
id="namedview15"
showgrid="false"
inkscape:zoom="5.04"
inkscape:cx="117.17146"
inkscape:cy="98.609664"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="error" />
<g
id="g4"
transform="matrix(2.52,0,0,2.52,-26,-26)">
<path
fill="#808080"
d="m 50,54.25 c -2.35,0 -4.25,-1.9 -4.25,-4.25 V 35 c 0,-2.35 1.9,-4.25 4.25,-4.25 2.35,0 4.25,1.9 4.25,4.25 v 15 c 0,2.35 -1.9,4.25 -4.25,4.25 z"
id="path2" />
</g>
<g
id="g8"
transform="matrix(2.52,0,0,2.52,-26,-26)">
<circle
fill="#808080"
cx="50"
cy="65"
r="5"
id="circle6" />
</g>
<g
id="g12"
transform="matrix(2.52,0,0,2.52,-26,-26)">
<path
fill="#808080"
d="M 50,89.25 C 28.36,89.25 10.75,71.64 10.75,50 10.75,28.36 28.36,10.75 50,10.75 71.64,10.75 89.25,28.36 89.25,50 89.25,71.64 71.64,89.25 50,89.25 Z m 0,-70 C 33.05,19.25 19.25,33.04 19.25,50 19.25,66.95 33.04,80.75 50,80.75 66.95,80.75 80.75,66.96 80.75,50 80.75,33.05 66.95,19.25 50,19.25 Z"
id="path10" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.0"
id="error"
x="0px"
y="0px"
viewBox="0 0 200 200"
enable-background="new 0 0 100 100"
xml:space="preserve"
sodipodi:docname="notification_error.svg"
width="200"
height="200"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"><metadata
id="metadata19"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs17" /><sodipodi:namedview
inkscape:document-rotation="0"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1377"
id="namedview15"
showgrid="false"
inkscape:zoom="5.04"
inkscape:cx="117.17146"
inkscape:cy="98.609664"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="error" />
<g
id="g4"
transform="matrix(2.52,0,0,2.52,-26,-26)">
<path
fill="#FFFFFF"
d="m 50,54.25 c -2.35,0 -4.25,-1.9 -4.25,-4.25 V 35 c 0,-2.35 1.9,-4.25 4.25,-4.25 2.35,0 4.25,1.9 4.25,4.25 v 15 c 0,2.35 -1.9,4.25 -4.25,4.25 z"
id="path2" />
</g>
<g
id="g8"
transform="matrix(2.52,0,0,2.52,-26,-26)">
<circle
fill="#FFFFFF"
cx="50"
cy="65"
r="5"
id="circle6" />
</g>
<g
id="g12"
transform="matrix(2.52,0,0,2.52,-26,-26)">
<path
fill="#FFFFFF"
d="M 50,89.25 C 28.36,89.25 10.75,71.64 10.75,50 10.75,28.36 28.36,10.75 50,10.75 71.64,10.75 89.25,28.36 89.25,50 89.25,71.64 71.64,89.25 50,89.25 Z m 0,-70 C 33.05,19.25 19.25,33.04 19.25,50 19.25,66.95 33.04,80.75 50,80.75 66.95,80.75 80.75,66.96 80.75,50 80.75,33.05 66.95,19.25 50,19.25 Z"
id="path10" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

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

View File

@ -3,6 +3,7 @@
#include "AppConfig.hpp"
#include "Exception.hpp"
#include "Thread.hpp"
#include "format.hpp"
#include <utility>
#include <vector>
@ -16,9 +17,13 @@
#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 {
@ -263,15 +268,103 @@ 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) {
#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
@ -283,6 +376,7 @@ std::string AppConfig::load()
*/
return ex.what();
}
}
// 2) Parse the property_tree, extract the sections and key / value pairs.
for (const auto &section : tree) {
@ -358,22 +452,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 std::pair<std::string, std::string> &kvp : m_storage[""])
c << kvp.first << " = " << kvp.second << std::endl;
for (const auto& kvp : m_storage[""])
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;
for (const std::pair<std::string, std::string> &kvp : category.second)
c << kvp.first << " = " << kvp.second << std::endl;
config_ss << std::endl << "[" << category.first << "]" << std::endl;
for (const auto& kvp : category.second)
config_ss << kvp.first << " = " << kvp.second << std::endl;
}
// Write vendor sections
for (const auto &vendor : m_vendors) {
@ -381,17 +474,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;
}
}
// 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();

View File

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

View File

@ -21,6 +21,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 {
@ -244,6 +248,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;
@ -270,7 +278,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
case coPercents: return new ConfigOptionPercentsNullable();
case coFloatsOrPercents: return new ConfigOptionFloatsOrPercentsNullable();
case coBools: return new ConfigOptionBoolsNullable();
default: throw Slic3r::RuntimeError(std::string("Unknown option type for nullable option ") + this->label);
default: throw ConfigurationError(std::string("Unknown option type for nullable option ") + this->label);
}
} else {
switch (this->type) {
@ -291,7 +299,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
case coBool: return new ConfigOptionBool();
case coBools: return new ConfigOptionBools();
case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map);
default: throw Slic3r::RuntimeError(std::string("Unknown option type for option ") + this->label);
default: throw ConfigurationError(std::string("Unknown option type for option ") + this->label);
}
}
}
@ -394,7 +402,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()) {
@ -525,7 +534,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;
@ -535,23 +544,22 @@ 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)) {
throw BadOptionTypeException(format("ConfigBase::set_deserialize() failed for parameter \"%1%\", value \"%2%\"", opt_key_src, value_src));
}
if (! this->set_deserialize_nothrow(opt_key_src, value_src, substitutions_ctxt, append))
throw BadOptionValueException(format("Invalid value provided for parameter %1%: %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);
for (const SetDeserializeItem &item : items)
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;
// Try to deserialize the option by its name.
@ -580,7 +588,7 @@ 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;
}
@ -588,10 +596,56 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con
ConfigOption *opt = this->option(opt_key, true);
if (opt == nullptr)
throw new UnknownOptionException(opt_key);
bool success = true;
if (!optdef->can_phony || !value.empty()) {
success = true;
bool substituted = false;
if (optdef->type == coBools && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable) {
//FIXME Special handling of vectors of bools, quick and not so dirty solution before PrusaSlicer 2.3.2 release.
bool nullable = opt->nullable();
ConfigHelpers::DeserializationSubstitution default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToFalse;
if (optdef->default_value) {
// Default value for vectors of booleans used in a "per extruder" context, thus the default contains just a single value.
assert(dynamic_cast<const ConfigOptionVector<unsigned char>*>(optdef->default_value.get()));
auto &values = static_cast<const ConfigOptionVector<unsigned char>*>(optdef->default_value.get())->values;
if (values.size() == 1 && values.front() == 1)
default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToTrue;
}
auto result = nullable ?
static_cast<ConfigOptionBoolsNullable*>(opt)->deserialize_with_substitutions(value, append, default_value) :
static_cast<ConfigOptionBools*>(opt)->deserialize_with_substitutions(value, append, default_value);
success = result != ConfigHelpers::DeserializationResult::Failed;
substituted = result == ConfigHelpers::DeserializationResult::Substituted;
} else {
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) && ConfigHelpers::looks_like_enum_value(value)) {
// Deserialize failed, try to substitute with a default value.
assert(substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSilent);
if (optdef->type == coEnum && opt_key == "gcode_flavor" && (value == "marlin2" || value == "marlinfirmware"))
static_cast<ConfigOptionEnum<GCodeFlavor>*>(opt)->value = gcfMarlin;
else if (optdef->type == coBool)
static_cast<ConfigOptionBool*>(opt)->value = ConfigHelpers::enum_looks_like_true_value(value);
else
// Just use the default of the option.
opt->set(optdef->default_value.get());
success = true;
substituted = true;
}
}
bool ok = true;
if (!optdef->can_phony || !value.empty())
ok = opt->deserialize(value, append);
if (substituted && (substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable ||
substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent)) {
// Log the substitution.
ConfigSubstitution config_substitution;
config_substitution.opt_def = optdef;
config_substitution.old_value = value;
config_substitution.new_value = ConfigOptionUniquePtr(opt->clone());
substitutions_ctxt.substitutions.emplace_back(std::move(config_substitution));
}
}
//set phony status
if (optdef->can_phony)
if(value.empty())
@ -600,8 +654,7 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con
opt->set_phony(false);
else
opt->set_phony(false);
return ok;
return success;
}
// Return an absolute value of a possibly relative config variable.
@ -662,7 +715,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const
cast_opt->get_abs_value(this->get_abs_value(opt_def->ratio_over));
}
std::stringstream ss; ss << "ConfigBase::get_abs_value(): "<< opt_key<<" has not a valid option type for get_abs_value()";
throw Slic3r::RuntimeError(ss.str());
throw ConfigurationError(ss.str());
}
// Return an absolute value of a possibly relative config variable.
@ -673,7 +726,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati
const ConfigOption *raw_opt = this->option(opt_key);
assert(raw_opt != nullptr);
if (raw_opt->type() != coFloatOrPercent)
throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
throw ConfigurationError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
// Compute absolute value.
return static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(ratio_over);
}
@ -696,67 +749,78 @@ 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, 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)
{
try {
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);
} catch (const ConfigurationError &e) {
throw ConfigurationError(format("Failed loading configuration file \"%1%\": %2%", file, e.what()));
}
}
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)
ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
try {
// Read a 64k block from the end of the G-code.
boost::nowide::ifstream ifs(file);
{
const char slic3r_gcode_header[] = "; generated by Slic3r ";
const char slic3rpp_gcode_header[] = "; generated by Slic3r++ ";
const char superslicer_gcode_header[] = "; generated by SuperSlicer ";
const char prusaslicer_gcode_header[] = "; generated by PrusaSlicer ";
std::string firstline;
std::getline(ifs, firstline);
if (strncmp(slic3r_gcode_header, firstline.c_str(), strlen(slic3r_gcode_header)) != 0 &&
boost::nowide::ifstream ifs(file);
{
const char slic3r_gcode_header[] = "; generated by Slic3r ";
const char slic3rpp_gcode_header[] = "; generated by Slic3r++ ";
const char superslicer_gcode_header[] = "; generated by SuperSlicer ";
const char prusaslicer_gcode_header[] = "; generated by PrusaSlicer ";
std::string firstline;
std::getline(ifs, firstline);
if (strncmp(slic3r_gcode_header, firstline.c_str(), strlen(slic3r_gcode_header)) != 0 &&
strncmp(slic3rpp_gcode_header, firstline.c_str(), strlen(slic3rpp_gcode_header)) != 0 &&
strncmp(superslicer_gcode_header, firstline.c_str(), strlen(superslicer_gcode_header)) != 0 &&
strncmp(prusaslicer_gcode_header, firstline.c_str(), strlen(prusaslicer_gcode_header)) != 0)
throw Slic3r::RuntimeError("Not a g-code recognized for configuration import.");
}
throw ConfigurationError("Not a g-code recognized for configuration import.");
}
ifs.seekg(0, ifs.end);
auto file_length = ifs.tellg();
auto data_length = std::min<std::fstream::pos_type>(65535, file_length);
ifs.seekg(file_length - data_length, ifs.beg);
auto file_length = ifs.tellg();
auto data_length = std::min<std::fstream::pos_type>(65535, file_length);
ifs.seekg(file_length - data_length, ifs.beg);
std::vector<char> data(size_t(data_length) + 1, 0);
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));
throw ConfigurationError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs));
return std::move(substitutions_ctxt.substitutions);
} catch (const ConfigurationError &e) {
throw ConfigurationError(format("Failed loading configuration from G-code \"%1%\": %2%", file, e.what()));
}
}
// 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;
@ -801,8 +865,7 @@ size_t ConfigBase::load_from_gcode_string(const char* str)
if (key == nullptr)
break;
try {
//change it from set_deserialize to set_deserialize_nothrow to allow bad/old config to swtch to default value.
if(this->set_deserialize_nothrow(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 */) {
@ -839,7 +902,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);
}
}
@ -890,7 +953,7 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre
throw NoDefinitionException(opt_key);
const ConfigOptionDef *optdef = def->get(opt_key);
if (optdef == nullptr)
// throw Slic3r::RuntimeError(std::string("Invalid option name: ") + opt_key);
// throw ConfigurationError(std::string("Invalid option name: ") + opt_key);
// Let the parent decide what to do if the opt_key is not defined by this->def().
return nullptr;
ConfigOption *opt = optdef->create_default_option();
@ -1010,8 +1073,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/property_tree/ptree_fwd.hpp>
@ -75,23 +76,59 @@ enum OptionCategory : int
};
std::string toString(OptionCategory opt);
/// Specialization of std::exception to indicate that an unknown config option has been encountered.
class UnknownOptionException : public Slic3r::RuntimeError {
public:
UnknownOptionException() :
Slic3r::RuntimeError("Unknown option exception") {}
UnknownOptionException(const std::string &opt_key) :
Slic3r::RuntimeError(std::string("Unknown option exception: ") + opt_key) {}
namespace ConfigHelpers {
inline bool looks_like_enum_value(std::string value)
{
boost::trim(value);
if (value.empty() || value.size() > 64 || ! isalpha(value.front()))
return false;
for (const char c : value)
if (! (isalnum(c) || c == '_' || c == '-'))
return false;
return true;
}
inline bool enum_looks_like_true_value(std::string value) {
boost::trim(value);
return boost::iequals(value, "enabled") || boost::iequals(value, "on");
}
enum class DeserializationSubstitution {
Disabled,
DefaultsToFalse,
DefaultsToTrue
};
enum class DeserializationResult {
Loaded,
Substituted,
Failed,
};
};
/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
class NoDefinitionException : public Slic3r::RuntimeError
// Base for all exceptions thrown by the configuration layer.
class ConfigurationError : public Slic3r::RuntimeError {
public:
using RuntimeError::RuntimeError;
};
// Specialization of std::exception to indicate that an unknown config option has been encountered.
class UnknownOptionException : public ConfigurationError {
public:
UnknownOptionException() :
ConfigurationError("Unknown option exception") {}
UnknownOptionException(const std::string &opt_key) :
ConfigurationError(std::string("Unknown option exception: ") + opt_key) {}
};
// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
class NoDefinitionException : public ConfigurationError
{
public:
NoDefinitionException() :
Slic3r::RuntimeError("No definition exception") {}
ConfigurationError("No definition exception") {}
NoDefinitionException(const std::string &opt_key) :
Slic3r::RuntimeError(std::string("No definition exception: ") + opt_key) {}
ConfigurationError(std::string("No definition exception: ") + opt_key) {}
};
// a bit more specific than a runtime_error
class ConfigurationException : public std::runtime_error
@ -103,13 +140,22 @@ public:
std::runtime_error(std::string("Configuration exception: ") + opt_key) {}
};
/// Indicate that an unsupported accessor was called on a config option.
class BadOptionTypeException : public Slic3r::RuntimeError
// Indicate that an unsupported accessor was called on a config option.
class BadOptionTypeException : public ConfigurationError
{
public:
BadOptionTypeException() : Slic3r::RuntimeError("Bad option type exception") {}
BadOptionTypeException(const std::string &message) : Slic3r::RuntimeError(message) {}
BadOptionTypeException(const char* message) : Slic3r::RuntimeError(message) {}
BadOptionTypeException() : ConfigurationError("Bad option type exception") {}
BadOptionTypeException(const std::string &message) : ConfigurationError(message) {}
BadOptionTypeException(const char* message) : ConfigurationError(message) {}
};
// Indicate that an option has been deserialized from an invalid value.
class BadOptionValueException : public ConfigurationError
{
public:
BadOptionValueException() : ConfigurationError("Bad option value exception") {}
BadOptionValueException(const std::string &message) : ConfigurationError(message) {}
BadOptionValueException(const char* message) : ConfigurationError(message) {}
};
// Type of a configuration value.
@ -208,8 +254,47 @@ inline OutputFormat operator&=(OutputFormat& a, OutputFormat b) {
a = a & b; return a;
}
enum ForwardCompatibilitySubstitutionRule
{
// Disable susbtitution, throw exception if an option value is not recognized.
Disable,
// Enable substitution of an unknown option value with default. Log the substitution.
Enable,
// Enable substitution of an unknown option value with default. Don't log the substitution.
EnableSilent,
// Enable substitution of an unknown option value with default. Log substitutions in user profiles, don't log substitutions in system profiles.
EnableSystemSilent,
// Enable silent substitution of an unknown option value with default when loading user profiles. Throw on an unknown option value in a system profile.
EnableSilentDisableSystem,
};
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 {
@ -277,7 +362,7 @@ public:
void set(const ConfigOption *rhs) override
{
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionSingle: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionSingle: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionSingle<T>*>(rhs));
this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
this->phony = rhs->phony;
@ -286,7 +371,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionSingle: Comparing incompatible types");
throw ConfigurationError("ConfigOptionSingle: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionSingle<T>*>(&rhs));
return this->value == static_cast<const ConfigOptionSingle<T>*>(&rhs)->value;
}
@ -352,7 +437,7 @@ public:
void set(const ConfigOption *rhs) override
{
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionVector: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionVector: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionVector<T>*>(rhs));
this->values = static_cast<const ConfigOptionVector<T>*>(rhs)->values;
this->phony = rhs->phony;
@ -370,12 +455,12 @@ public:
if (opt->type() == this->type()) {
auto other = static_cast<const ConfigOptionVector<T>*>(opt);
if (other->values.empty())
throw Slic3r::RuntimeError("ConfigOptionVector::set(): Assigning from an empty vector");
throw ConfigurationError("ConfigOptionVector::set(): Assigning from an empty vector");
this->values.emplace_back(other->values.front());
} else if (opt->type() == this->scalar_type())
this->values.emplace_back(static_cast<const ConfigOptionSingle<T>*>(opt)->value);
else
throw Slic3r::RuntimeError("ConfigOptionVector::set():: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionVector::set():: Assigning an incompatible type");
}
}
@ -394,12 +479,12 @@ public:
// Assign the first value of the rhs vector.
auto other = static_cast<const ConfigOptionVector<T>*>(rhs);
if (other->values.empty())
throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning from an empty vector");
throw ConfigurationError("ConfigOptionVector::set_at(): Assigning from an empty vector");
this->values[i] = other->get_at(j);
} else if (rhs->type() == this->scalar_type())
this->values[i] = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
else
throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning an incompatible type");
throw ConfigurationError("ConfigOptionVector::set_at(): Assigning an incompatible type");
}
const T& get_at(size_t i) const
@ -426,7 +511,7 @@ public:
if (opt_default == nullptr)
this->values.resize(n, this->default_value);
if (opt_default->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionVector::resize(): Extending with an incompatible type.");
throw ConfigurationError("ConfigOptionVector::resize(): Extending with an incompatible type.");
if(static_cast<const ConfigOptionVector<T>*>(opt_default)->values.empty())
this->values.resize(n, this->default_value);
else
@ -446,7 +531,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionVector: Comparing incompatible types");
throw ConfigurationError("ConfigOptionVector: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionVector<T>*>(&rhs));
return this->values == static_cast<const ConfigOptionVector<T>*>(&rhs)->values;
}
@ -458,9 +543,9 @@ public:
// An option overrides another option if it is not nil and not equal.
bool overriden_by(const ConfigOption *rhs) const override {
if (this->nullable())
throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
throw ConfigurationError("Cannot override a nullable ConfigOption.");
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionVector.overriden_by() applied to different types.");
throw ConfigurationError("ConfigOptionVector.overriden_by() applied to different types.");
auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs);
if (! rhs->nullable())
// Overridding a non-nullable object with another non-nullable object.
@ -478,9 +563,9 @@ public:
// Apply an override option, possibly a nullable one.
bool apply_override(const ConfigOption *rhs) override {
if (this->nullable())
throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
throw ConfigurationError("Cannot override a nullable ConfigOption.");
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionVector.apply_override() applied to different types.");
throw ConfigurationError("ConfigOptionVector.apply_override() applied to different types.");
auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs);
if (! rhs->nullable()) {
// Overridding a non-nullable object with another non-nullable object.
@ -571,7 +656,7 @@ public:
bool operator==(const ConfigOptionFloatsTempl &rhs) const { return vectors_equal(this->values, rhs.values); }
bool operator==(const ConfigOption &rhs) const override {
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionFloatsTempl: Comparing incompatible types");
throw ConfigurationError("ConfigOptionFloatsTempl: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionVector<double>*>(&rhs));
return vectors_equal(this->values, static_cast<const ConfigOptionVector<double>*>(&rhs)->values);
}
@ -618,7 +703,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
throw ConfigurationError("Deserializing nil into a non-nullable object");
} else {
std::istringstream iss(item_str);
double value;
@ -643,9 +728,9 @@ protected:
if (NULLABLE)
ss << "nil";
else
throw Slic3r::RuntimeError("Serializing NaN");
throw ConfigurationError("Serializing NaN");
} else
throw Slic3r::RuntimeError("Serializing invalid number");
throw ConfigurationError("Serializing invalid number");
}
static bool vectors_equal(const std::vector<double> &v1, const std::vector<double> &v2) {
if (NULLABLE) {
@ -764,7 +849,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
throw ConfigurationError("Deserializing nil into a non-nullable object");
} else {
std::istringstream iss(item_str);
int32_t value;
@ -781,7 +866,7 @@ private:
if (NULLABLE)
ss << "nil";
else
throw Slic3r::RuntimeError("Serializing NaN");
throw ConfigurationError("Serializing NaN");
} else
ss << v;
}
@ -811,7 +896,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);
@ -970,7 +1055,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Comparing incompatible types");
throw ConfigurationError("ConfigOptionFloatOrPercent: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(&rhs));
return *this == *static_cast<const ConfigOptionFloatOrPercent*>(&rhs);
}
@ -981,7 +1066,7 @@ public:
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
throw ConfigurationException("ConfigOptionFloatOrPercent: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionFloatOrPercent: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(rhs));
*this = *static_cast<const ConfigOptionFloatOrPercent*>(rhs);
}
@ -1046,7 +1131,7 @@ public:
bool operator==(const ConfigOptionFloatsOrPercentsTempl &rhs) const { return vectors_equal(this->values, rhs.values); }
bool operator==(const ConfigOption &rhs) const override {
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionFloatsOrPercentsTempl: Comparing incompatible types");
throw ConfigurationError("ConfigOptionFloatsOrPercentsTempl: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs));
return vectors_equal(this->values, static_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs)->values);
}
@ -1093,7 +1178,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
throw ConfigurationError("Deserializing nil into a non-nullable object");
} else {
bool percent = item_str.find_first_of("%") != std::string::npos;
std::istringstream iss(item_str);
@ -1121,9 +1206,9 @@ protected:
if (NULLABLE)
ss << "nil";
else
throw Slic3r::RuntimeError("Serializing NaN");
throw ConfigurationError("Serializing NaN");
} else
throw Slic3r::RuntimeError("Serializing invalid number");
throw ConfigurationError("Serializing invalid number");
}
static bool vectors_equal(const std::vector<FloatOrPercent> &v1, const std::vector<FloatOrPercent> &v2) {
if (NULLABLE) {
@ -1312,8 +1397,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") {
this->value = true;
return true;
}
if (str == "0") {
this->value = false;
return true;
}
return false;
}
private:
@ -1374,24 +1466,39 @@ public:
}
return vv;
}
bool deserialize(const std::string &str, bool append = false) override
ConfigHelpers::DeserializationResult deserialize_with_substitutions(const std::string &str, bool append, ConfigHelpers::DeserializationSubstitution substitution)
{
if (! append)
this->values.clear();
std::istringstream is(str);
std::string item_str;
bool substituted = false;
while (std::getline(is, item_str, ',')) {
boost::trim(item_str);
unsigned char new_value = 0;
if (item_str == "nil") {
if (NULLABLE)
this->values.push_back(nil_value());
else
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
throw ConfigurationError("Deserializing nil into a non-nullable object");
} else if (item_str == "1") {
new_value = true;
} else if (item_str == "0") {
new_value = false;
} else if (substitution != ConfigHelpers::DeserializationSubstitution::Disabled && ConfigHelpers::looks_like_enum_value(item_str)) {
new_value = ConfigHelpers::enum_looks_like_true_value(item_str) || substitution == ConfigHelpers::DeserializationSubstitution::DefaultsToTrue;
substituted = true;
} else
this->values.push_back(item_str.compare("1") == 0);
return ConfigHelpers::DeserializationResult::Failed;
this->values.push_back(new_value);
}
return true;
return substituted ? ConfigHelpers::DeserializationResult::Substituted : ConfigHelpers::DeserializationResult::Loaded;
}
bool deserialize(const std::string &str, bool append = false) override
{
return this->deserialize_with_substitutions(str, append, ConfigHelpers::DeserializationSubstitution::Disabled) == ConfigHelpers::DeserializationResult::Loaded;
}
protected:
@ -1400,7 +1507,7 @@ protected:
if (NULLABLE)
ss << "nil";
else
throw Slic3r::RuntimeError("Serializing NaN");
throw ConfigurationError("Serializing NaN");
} else
ss << (v ? "1" : "0");
}
@ -1436,14 +1543,14 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Comparing incompatible types");
throw ConfigurationError("ConfigOptionEnum<T>: Comparing incompatible types");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
return this->value == (T)rhs.getInt();
}
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionEnum<T>: Assigning an incompatible type");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
this->value = (T)rhs->getInt();
this->phony = rhs->phony;
@ -1522,14 +1629,14 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Comparing incompatible types");
throw ConfigurationError("ConfigOptionEnumGeneric: Comparing incompatible types");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
return this->value == rhs.getInt();
}
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
this->value = rhs->getInt();
this->phony = rhs->phony;
@ -1585,7 +1692,7 @@ public:
case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; }
case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; }
case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; }
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
}
} else {
switch (this->type) {
@ -1604,7 +1711,7 @@ public:
case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; }
case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; }
case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; }
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
}
}
}
@ -1616,7 +1723,7 @@ public:
case coInts: archive(*static_cast<const ConfigOptionIntsNullable*>(opt)); break;
case coPercents: archive(*static_cast<const ConfigOptionPercentsNullable*>(opt));break;
case coBools: archive(*static_cast<const ConfigOptionBoolsNullable*>(opt)); break;
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
}
} else {
switch (this->type) {
@ -1635,7 +1742,7 @@ public:
case coBool: archive(*static_cast<const ConfigOptionBool*>(opt)); break;
case coBools: archive(*static_cast<const ConfigOptionBools*>(opt)); break;
case coEnum: archive(*static_cast<const ConfigOptionEnumGeneric*>(opt)); break;
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
}
}
// Make the compiler happy, shut up the warnings.
@ -1731,6 +1838,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
@ -1758,7 +1873,7 @@ public:
return out;
}
/// Iterate through all of the CLI options and write them to a stream.
// Iterate through all of the CLI options and write them to a stream.
std::ostream& print_cli_help(
std::ostream& out, bool show_defaults,
std::function<bool(const ConfigOptionDef &)> filter = [](const ConfigOptionDef &){ return true; }) const;
@ -1809,6 +1924,8 @@ public:
}
};
// An abstract configuration store.
class ConfigBase : public ConfigOptionResolver
{
@ -1905,9 +2022,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) {}
@ -1922,17 +2041,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);
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, 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, bool to_prusa = false) const;
// Set all the nullable values to nils.
@ -1940,7 +2061,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.
@ -2110,9 +2231,9 @@ private:
template<class Archive> void serialize(Archive &ar) { ar(options); }
};
/// Configuration store with a static definition of configuration values.
/// In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons,
/// because the configuration values could be accessed directly.
// Configuration store with a static definition of configuration values.
// In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons,
// because the configuration values could be accessed directly.
class StaticConfig : public virtual ConfigBase
{
public:

View File

@ -427,7 +427,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();
@ -442,16 +442,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
@ -518,7 +518,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);
@ -547,7 +547,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;
@ -568,7 +568,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()
@ -590,7 +590,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);
@ -653,7 +653,7 @@ namespace Slic3r {
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
@ -665,7 +665,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);
print_config_parsed = true;
}
if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE))
@ -687,7 +687,8 @@ namespace Slic3r {
}
//parsed superslicer/prusa files if slic3r not found
//note that is we successfully read one of the config file, then the other ones should also have the same name
auto read_from_other_storage = [this, &print_config_parsed, num_entries, &archive, &stat, &config, &model, &filename](const std::string &print_config_name, const std::string& model_config_name) -> bool {
auto read_from_other_storage = [this, &print_config_parsed, num_entries, &archive, &stat, &config, &model, &filename, &config_substitutions]
(const std::string &print_config_name, const std::string& model_config_name) -> bool {
for (mz_uint i = 0; i < num_entries; ++i)
{
if (mz_zip_reader_file_stat(&archive, i, &stat))
@ -699,7 +700,7 @@ namespace Slic3r {
if (boost::algorithm::iequals(name, print_config_name))
{
// 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);
print_config_parsed = true;
} else if (boost::algorithm::iequals(name, model_config_name))
{
@ -774,7 +775,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
@ -791,7 +792,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;
}
@ -868,7 +869,12 @@ 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)
{
@ -879,7 +885,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);
}
}
@ -954,7 +960,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)
{
@ -1003,8 +1009,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));
@ -1895,7 +1900,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())
{
@ -1997,7 +2002,7 @@ namespace Slic3r {
else if (metadata.key == SOURCE_IN_INCHES)
volume->source.is_converted_from_inches = metadata.value == "1";
else
volume->config.set_deserialize(metadata.key, metadata.value);
volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
}
}
@ -2962,13 +2967,13 @@ 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;
_3MF_Importer importer;
bool res = importer.load_model_from_file(path, *model, *config, check_version);
bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version);
importer.log_errors();
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

@ -62,10 +62,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);
}
@ -256,6 +257,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&);
@ -704,8 +707,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()) {
@ -723,7 +727,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();
@ -848,7 +852,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;
@ -865,7 +869,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);
@ -909,7 +913,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)
{
@ -925,7 +929,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);
@ -985,7 +989,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;
@ -1011,7 +1015,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);
printf("Archive does not contain a valid model");
@ -1053,11 +1057,11 @@ 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)
{
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);
@ -1068,7 +1072,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

@ -286,11 +286,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

@ -203,7 +203,7 @@ RasterParams get_raster_params(const DynamicPrintConfig &cfg)
if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h ||
!opt_mirror_x || !opt_mirror_y || !opt_orient)
throw Slic3r::FileIOError("Invalid SL1 file");
throw Slic3r::FileIOError("Invalid SL1 / SL1S file");
RasterParams rstp;
@ -229,7 +229,7 @@ SliceParams get_slice_params(const DynamicPrintConfig &cfg)
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
if (!opt_layerh || !opt_init_layerh)
throw Slic3r::FileIOError("Invalid SL1 file");
throw Slic3r::FileIOError("Invalid SL1 / SL1S file");
return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
}
@ -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,
Vec2i32 windowsize,
TriangleMesh & 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_triangle_mesh(slices, 0, slicp.layerh, slicp.initial_layerh);
return config_substitutions;
}
using ConfMap = std::map<std::string, std::string>;

View File

@ -25,23 +25,23 @@ public:
void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = "") override;
};
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,
Vec2i32 windowsize,
TriangleMesh & 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,
Vec2i32 windowsize,
TriangleMesh & 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

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

View File

@ -49,7 +49,7 @@ public:
// collection of expolygons representing the bridged areas (thus not
// needing support material)
Polygons bridged;
// Polygons bridged;
// collection of polylines representing the unsupported bridge edges
Polylines unsupported_bridge_edges;

View File

@ -327,7 +327,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
}else if (bd.detect_angle(custom_angle)) {
bridges[idx_last].bridge_angle = bd.angle;
if (this->layer()->object()->config().support_material) {
polygons_append(this->bridged, intersection(bd.coverage(), to_polygons(initial)));
//polygons_append(this->bridged, intersection(bd.coverage(), to_polygons(initial)));
append(this->unsupported_bridge_edges, bd.unsupported_edges());
}
} else {

View File

@ -121,13 +121,17 @@ bool Model::equals(const Model& rhs) const {
return true;
}
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"))
@ -135,9 +139,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
@ -152,24 +157,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.");
@ -191,7 +201,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);
@ -424,13 +434,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)
@ -457,7 +466,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()) {
@ -1766,7 +1775,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));
delete mesh;
++ idx;
}

View File

@ -12,6 +12,7 @@
#include "TriangleMesh.hpp"
#include "Arrange.hpp"
#include "CustomGCode.hpp"
#include "enum_bitmask.hpp"
#include <map>
#include <memory>
@ -996,8 +997,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);
bool equals(const Model& rhs) const;
@ -1062,6 +1075,8 @@ private:
}
};
ENABLE_ENUM_BITMASK_OPERATORS(Model::LoadAttribute)
#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE

View File

@ -919,7 +919,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
@ -946,7 +948,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;
@ -1829,7 +1833,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
@ -1855,7 +1861,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::TYPE_PHYSICAL_PRINTER, PresetConfigSubstitutions::Source::UserFile, printer.file, std::move(config_substitutions) });
printer.update_from_config(config);
printer.loaded = true;
}

View File

@ -124,6 +124,10 @@ public:
TYPE_SLA_PRINT = TYPE_SLA | TYPE_PRINT1,
TYPE_SLA_MATERIAL = TYPE_SLA | TYPE_MATERIAL,
TYPE_TECHNOLOGY = TYPE_FFF | TYPE_SLA,
// 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 = 1 << 5,
};
Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {}
@ -278,6 +282,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
{
@ -308,7 +333,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.
@ -680,7 +705,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

@ -162,10 +162,12 @@ 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();
PresetsConfigSubstitutions substitutions;
std::string errors_cummulative;
std::tie(substitutions, errors_cummulative) = this->load_system_presets(substitution_rule);
const std::string dir_user_presets = data_dir()
#ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR
@ -175,33 +177,34 @@ 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
;
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();
}
@ -211,16 +214,26 @@ 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.
// For each vendor, there will be a single PresetBundle loaded.
std::string PresetBundle::load_system_presets()
std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule)
{
if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent)
// Loading system presets, don't log substitutions.
compatibility_rule = ForwardCompatibilitySubstitutionRule::EnableSilent;
else if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem)
// Loading system presets, throw on unknown option value.
compatibility_rule = ForwardCompatibilitySubstitutionRule::Disable;
// Here the vendor specific read only Config Bundles are stored.
boost::filesystem::path dir = (boost::filesystem::path(data_dir()) / "vendor").make_preferred();
std::string errors_cummulative;
bool first = true;
boost::filesystem::path dir = (boost::filesystem::path(data_dir()) / "vendor").make_preferred();
PresetsConfigSubstitutions substitutions;
std::string errors_cummulative;
bool first = true;
for (auto &dir_entry : boost::filesystem::directory_iterator(dir))
if (Slic3r::is_ini_file(dir_entry)) {
std::string name = dir_entry.path().filename().string();
@ -230,13 +243,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);
append(substitutions, this->load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem, compatibility_rule).first);
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);
append(substitutions, other.load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem, compatibility_rule).first);
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: ";
@ -258,7 +271,7 @@ std::string PresetBundle::load_system_presets()
}
this->update_system_maps();
return errors_cummulative;
return std::make_pair(std::move(substitutions), errors_cummulative);
}
// Merge one vendor's presets with the other vendor's presets, report duplicates.
@ -666,15 +679,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, 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.
@ -685,33 +698,41 @@ void PresetBundle::load_config_file(const std::string &path)
} catch (const std::ifstream::failure &err) {
throw Slic3r::RuntimeError(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what());
} catch (const boost::property_tree::file_parser_error &err) {
throw Slic3r::RuntimeError((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%")
% err.filename() % err.message() % err.line()).str());
throw Slic3r::RuntimeError(format("Failed loading the Config Bundle \"%1%\": %2% at line %3%",
err.filename(), err.message(), err.line()));
} catch (const std::runtime_error &err) {
throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what());
}
// 2) Continue based on the type of the configuration file.
ConfigFileType config_file_type = guess_config_file_type(tree);
switch (config_file_type) {
case CONFIG_FILE_TYPE_UNKNOWN:
throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path);
case CONFIG_FILE_TYPE_APP_CONFIG:
throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file.");
case CONFIG_FILE_TYPE_CONFIG:
{
// Initialize a config from full defaults.
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
config.load(tree);
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;
ConfigSubstitutions config_substitutions;
try {
switch (config_file_type) {
case CONFIG_FILE_TYPE_UNKNOWN:
throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path);
case CONFIG_FILE_TYPE_APP_CONFIG:
throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file.");
case CONFIG_FILE_TYPE_CONFIG:
{
// Initialize a config from full defaults.
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
config_substitutions = config.load(tree, compatibility_rule);
Preset::normalize(config);
load_config_file_config(path, true, std::move(config));
return config_substitutions;
}
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
return load_config_file_config_bundle(path, tree, compatibility_rule);
}
} catch (const ConfigurationError &e) {
throw Slic3r::RuntimeError(format("Invalid configuration file %1%: %2%", path, e.what()));
}
// 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.
@ -883,16 +904,24 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
}
// Load the active configuration of a config bundle from a boost property_tree. This is a private method called from load_config_file.
void PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree)
ConfigSubstitutions PresetBundle::load_config_file_config_bundle(
const std::string &path, const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
// 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, {}, compatibility_rule);
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 = [this, &path, &bundle_name](PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string {
ConfigSubstitutions config_substitutions;
auto load_one = [this, &path, &bundle_name, &presets_substitutions = presets_substitutions, &config_substitutions](PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string {
// If there are substitutions reported for this preset, move them to config_substitutions.
if (auto it = std::find_if(presets_substitutions.begin(), presets_substitutions.end(), [&preset_name_src](const PresetConfigSubstitutions& subs){ return subs.preset_name == preset_name_src; });
it != presets_substitutions.end() && ! it->substitutions.empty())
append(config_substitutions, std::move(it->substitutions));
Preset *preset_src = collection_src.find_preset(preset_name_src, false);
Preset *preset_dst = collection_dst.find_preset(preset_name_src, false);
assert(preset_src != nullptr);
@ -942,6 +971,9 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const
this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false);
this->update_compatible(PresetSelectCompatibleType::Never);
sort_remove_duplicates(config_substitutions);
return std::move(config_substitutions);
}
// Process the Config Bundle loaded as a Boost property tree.
@ -1086,11 +1118,16 @@ 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, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
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 { compatibility_rule };
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;
@ -1103,25 +1140,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.
@ -1188,7 +1224,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
active_sla_material = kvp.second.data();
} else if (kvp.first == "printer") {
active_printer = kvp.second.data();
}else if (kvp.first == "physical_printer") {
} else if (kvp.first == "physical_printer") {
active_physical_printer = kvp.second.data();
}
}
@ -1225,30 +1261,36 @@ 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) {
for (auto &kvp : section.second) {
if (kvp.first == "alias")
alias_name = kvp.second.data();
else if (kvp.first == "renamed_from") {
if (! unescape_strings_cstyle(kvp.second.data(), renamed_from)) {
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The preset \"" <<
section.first << "\" contains invalid \"renamed_from\" key, which is being ignored.";
}
}
config.set_deserialize(kvp.first, kvp.second.data());
try {
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();
else if (kvp.first == "renamed_from") {
if (! unescape_strings_cstyle(kvp.second.data(), renamed_from)) {
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The preset \"" <<
section.first << "\" contains invalid \"renamed_from\" key, which is being ignored.";
}
}
// 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) {
// Select the default config based on the printer_technology field extracted from kvp.
DynamicPrintConfig config_src;
parse_config_section(config_src);
default_config = &presets->default_preset_for(config_src).config;
config = *default_config;
config.apply(config_src);
} else {
default_config = &presets->default_preset().config;
config = *default_config;
parse_config_section(config);
}
};
if (presets == &this->printers) {
// Select the default config based on the printer_technology field extracted from kvp.
DynamicPrintConfig config_src;
parse_config_section(config_src);
default_config = &presets->default_preset_for(config_src).config;
config = *default_config;
config.apply(config_src);
} else {
default_config = &presets->default_preset().config;
config = *default_config;
parse_config_section(config);
} catch (const ConfigurationError &e) {
throw ConfigurationError(format("Invalid configuration bundle \"%1%\", section [%2%]: ", path, section.first) + e.what());
}
Preset::normalize(config);
// Report configuration fields, which are misplaced into a wrong group.
@ -1256,7 +1298,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");
@ -1291,7 +1333,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) {
@ -1320,9 +1362,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;
}
@ -1343,7 +1385,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;
}
@ -1352,8 +1397,13 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
const DynamicPrintConfig& default_config = ph_printers->default_config();
DynamicPrintConfig config = default_config;
for (auto& kvp : section.second)
config.set_deserialize(kvp.first, kvp.second.data());
substitution_context.substitutions.clear();
try {
for (auto& kvp : section.second)
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
} catch (const ConfigurationError &e) {
throw ConfigurationError(format("Invalid configuration bundle \"%1%\", section [%2%]: ", path, section.first) + e.what());
}
// Report configuration fields, which are misplaced into a wrong group.
std::string incorrect_keys = Preset::remove_invalid_keys(config, default_config);
@ -1379,14 +1429,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())
@ -1406,7 +1459,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
// This is done just once on application start up.
void load_presets(AppConfig &config, const std::string &preferred_model_id = "");
PresetsConfigSubstitutions load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule rule, const std::string &preferred_model_id = "");
// Export selections (current print, current filaments, current printer) into config.ini
void export_selections(AppConfig &config);
@ -82,24 +83,27 @@ 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, ForwardCompatibilitySubstitutionRule compatibility_rule);
// 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);
@ -136,7 +140,7 @@ public:
static const char *PRUSA_BUNDLE;
private:
std::string load_system_presets();
std::pair<PresetsConfigSubstitutions, std::string> load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule);
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> merge_presets(PresetBundle &&other);
// Update renamed_from and alias maps of system profiles.
@ -155,12 +159,15 @@ 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, ForwardCompatibilitySubstitutionRule compatibility_rule);
DynamicPrintConfig full_fff_config() const;
DynamicPrintConfig full_sla_config() const;
};
ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute)
} // namespace Slic3r
#endif /* slic3r_PresetBundle_hpp_ */

View File

@ -74,7 +74,7 @@ void PrintConfigDef::init_common_params()
def = this->add("thumbnails", coPoints);
def->label = L("Thumbnails size");
def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 files, in the following format: \"XxY, XxY, ...\"");
def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 / .sl1s files, in the following format: \"XxY, XxY, ...\"");
def->mode = comExpert;
def->min = 0;
def->max = 2048;
@ -2723,12 +2723,14 @@ 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_values.push_back("klipper");
def->enum_labels.push_back("PrusaLink");
def->enum_labels.push_back("OctoPrint");
def->enum_labels.push_back("Duet");
def->enum_labels.push_back("FlashAir");
@ -5283,7 +5285,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();
@ -6528,6 +6530,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 Slic3r may not understand configurations produced by newest Slic3r versions. "
"For example, newer Slic3r 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("Bail out on unknown configuration values");
def->enum_labels.push_back("Enable reading unknown configuration values by verbosely substituting them with defaults.");
def->enum_labels.push_back("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

@ -72,6 +72,7 @@ enum class MachineLimitsUsage : uint8_t {
};
enum PrintHostType {
htPrusaLink,
htOctoPrint,
htDuet,
htFlashAir,
@ -222,6 +223,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<MachineLimitsUsag
template<> inline const t_config_enum_values& ConfigOptionEnum<PrintHostType>::get_enum_values() {
static t_config_enum_values keys_map = {
{"prusalink", htPrusaLink},
{"octoprint", htOctoPrint},
{"duet", htDuet},
{"flashair", htFlashAir},
@ -370,6 +372,15 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<ZLiftTop>::get_en
return keys_map;
}
template<> inline const t_config_enum_values& ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>::get_enum_values() {
static const t_config_enum_values keys_map = {
{ "disable", ForwardCompatibilitySubstitutionRule::Disable },
{ "enable", ForwardCompatibilitySubstitutionRule::Enable },
{ "enable_silent", ForwardCompatibilitySubstitutionRule::EnableSilent }
};
return keys_map;
}
// Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs.
// Does not store the actual values, but defines default values.
class PrintConfigDef : public ConfigDef
@ -1901,8 +1912,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

@ -2925,6 +2925,16 @@ namespace Slic3r {
return this->slice_volumes(zs, SlicingMode::Regular, volumes);
}
//FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it.
static void fix_mesh_connectivity(TriangleMesh &mesh)
{
auto nr_degenerated = mesh.stl.stats.degenerate_facets;
stl_check_facets_exact(&mesh.stl);
if (nr_degenerated != mesh.stl.stats.degenerate_facets)
// stl_check_facets_exact() removed some newly degenerated faces. Some faces could become degenerate after some mesh transformation.
stl_generate_shared_vertices(&mesh.stl, mesh.its);
}
std::vector<ExPolygons> PrintObject::slice_volumes(
const std::vector<float>& z,
SlicingMode mode, size_t slicing_mode_normal_below_layer, SlicingMode mode_below,
@ -2937,10 +2947,8 @@ namespace Slic3r {
TriangleMesh mesh(volumes.front()->mesh());
mesh.transform(volumes.front()->get_matrix(), true);
assert(mesh.repaired);
if (volumes.size() == 1 && mesh.repaired) {
//FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it.
stl_check_facets_exact(&mesh.stl);
}
if (volumes.size() == 1 && mesh.repaired)
fix_mesh_connectivity(mesh);
for (size_t idx_volume = 1; idx_volume < volumes.size(); ++idx_volume) {
const ModelVolume& model_volume = *volumes[idx_volume];
TriangleMesh vol_mesh(model_volume.mesh());
@ -2973,10 +2981,8 @@ namespace Slic3r {
//FIXME better to split the mesh into separate shells, perform slicing over each shell separately and then to use a Boolean operation to merge them.
TriangleMesh mesh(volume.mesh());
mesh.transform(volume.get_matrix(), true);
if (mesh.repaired) {
//FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it.
stl_check_facets_exact(&mesh.stl);
}
if (mesh.repaired)
fix_mesh_connectivity(mesh);
if (mesh.stl.stats.number_of_facets > 0) {
mesh.transform(m_trafo, true);
// apply XY shift

View File

@ -1547,6 +1547,11 @@ static inline void do_crossover(const std::vector<FlipEdge> &edges_in, std::vect
assert(edges_in.size() == edges_out.size());
}
// Worst time complexity: O(min(n, 100) * (n * log n + n^2)
// Expected time complexity: O(min(n, 100) * (n * log n + k * n)
// where n is the number of edges and k is the number of connection_lengths candidates after the first one
// is found that improves the total cost.
//FIXME there are likley better heuristics to lower the time complexity.
static inline void reorder_by_two_exchanges_with_segment_flipping(std::vector<FlipEdge> &edges)
{
if (edges.size() < 2)
@ -1556,7 +1561,8 @@ static inline void reorder_by_two_exchanges_with_segment_flipping(std::vector<Fl
std::vector<FlipEdge> edges_tmp(edges);
std::vector<std::pair<double, size_t>> connection_lengths(edges.size() - 1, std::pair<double, size_t>(0., 0));
std::vector<char> connection_tried(edges.size(), false);
for (size_t iter = 0; iter < edges.size(); ++ iter) {
const size_t max_iterations = std::min(edges.size(), size_t(100));
for (size_t iter = 0; iter < max_iterations; ++ iter) {
// Initialize connection costs and connection lengths.
for (size_t i = 1; i < edges.size(); ++ i) {
const FlipEdge &e1 = edges[i - 1];
@ -1621,6 +1627,8 @@ static inline void reorder_by_two_exchanges_with_segment_flipping(std::vector<Fl
}
}
#if 0
// Currently not used, too slow.
static inline void reorder_by_three_exchanges_with_segment_flipping(std::vector<FlipEdge> &edges)
{
if (edges.size() < 3) {
@ -1703,6 +1711,7 @@ static inline void reorder_by_three_exchanges_with_segment_flipping(std::vector<
}
}
}
#endif
typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::DontAlign> Matrixd;
@ -1774,6 +1783,8 @@ static inline std::pair<double, size_t> minimum_crossover_cost(
return std::make_pair(cost_min, flip_min);
}
#if 0
// Currently not used, too slow.
static inline void reorder_by_three_exchanges_with_segment_flipping2(std::vector<FlipEdge> &edges)
{
if (edges.size() < 3) {
@ -1870,8 +1881,11 @@ static inline void reorder_by_three_exchanges_with_segment_flipping2(std::vector
}
}
}
#endif
// Flip the sequences of polylines to lower the total length of connecting lines.
// Used by the infill generator if the infill is not connected with perimeter lines
// and to order the brim lines.
static inline void improve_ordering_by_two_exchanges_with_segment_flipping(Polylines &polylines, bool fixed_start)
{
#ifndef NDEBUG
@ -1923,6 +1937,7 @@ static inline void improve_ordering_by_two_exchanges_with_segment_flipping(Polyl
#endif /* NDEBUG */
}
// Used to optimize order of infill lines and brim lines.
Polylines chain_polylines(Polylines &&polylines, const Point *start_near)
{
#ifdef DEBUG_SVG_OUTPUT

View File

@ -226,7 +226,7 @@ inline typename CONTAINER_TYPE::value_type& next_value_modulo(typename CONTAINER
return container[next_idx_modulo(idx, container.size())];
}
extern std::string xml_escape(std::string text);
extern std::string xml_escape(std::string text, bool is_marked = false);
#if defined __GNUC__ && __GNUC__ < 5 && !defined __clang__

View File

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

View File

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

View File

@ -424,6 +424,53 @@ std::error_code rename_file(const std::string &from, const std::string &to)
}
#ifdef __linux__
// Copied from boost::filesystem.
// Called by copy_file_linux() in case linux sendfile() API is not supported.
int copy_file_linux_read_write(int infile, int outfile, uintmax_t file_size)
{
std::vector<char> buf(
// Prefer the buffer to be larger than the file size so that we don't have
// to perform an extra read if the file fits in the buffer exactly.
std::clamp<size_t>(file_size + (file_size < ~static_cast<uintmax_t >(0u)),
// Min and max buffer sizes are selected to minimize the overhead from system calls.
// The values are picked based on coreutils cp(1) benchmarking data described here:
// https://github.com/coreutils/coreutils/blob/d1b0257077c0b0f0ee25087efd46270345d1dd1f/src/ioblksize.h#L23-L72
8u * 1024u, 256u * 1024u),
0);
#if defined(POSIX_FADV_SEQUENTIAL)
::posix_fadvise(infile, 0, 0, POSIX_FADV_SEQUENTIAL);
#endif
// Don't use file size to limit the amount of data to copy since some filesystems, like procfs or sysfs,
// provide files with generated content and indicate that their size is zero or 4096. Just copy as much data
// as we can read from the input file.
while (true) {
ssize_t sz_read = ::read(infile, buf.data(), buf.size());
if (sz_read == 0)
break;
if (sz_read < 0) {
int err = errno;
if (err == EINTR)
continue;
return err;
}
// Allow for partial writes - see Advanced Unix Programming (2nd Ed.),
// Marc Rochkind, Addison-Wesley, 2004, page 94
for (ssize_t sz_wrote = 0; sz_wrote < sz_read;) {
ssize_t sz = ::write(outfile, buf.data() + sz_wrote, static_cast<std::size_t>(sz_read - sz_wrote));
if (sz < 0) {
int err = errno;
if (err == EINTR)
continue;
return err;
}
sz_wrote += sz;
}
}
return 0;
}
// Copied from boost::filesystem, to support copying a file to a weird filesystem, which does not support changing file attributes,
// for example ChromeOS Linux integration or FlashAIR WebDAV.
// Copied and simplified from boost::filesystem::detail::copy_file() with option = overwrite_if_exists and with just the Linux path kept,
@ -465,7 +512,7 @@ bool copy_file_linux(const boost::filesystem::path &from, const boost::filesyste
err = errno;
goto fail;
}
const mode_t from_mode = from_stat.st_mode;
if (!S_ISREG(from_mode)) {
err = ENOSYS;
@ -520,6 +567,19 @@ bool copy_file_linux(const boost::filesystem::path &from, const boost::filesyste
ssize_t sz = ::sendfile(outfile.fd, infile.fd, nullptr, size_to_copy);
if (sz < 0) {
err = errno;
if (offset == 0u) {
// sendfile may fail with EINVAL if the underlying filesystem does not support it.
// See https://patchwork.kernel.org/project/linux-nfs/patch/20190411183418.4510-1-olga.kornievskaia@gmail.com/
// https://bugzilla.redhat.com/show_bug.cgi?id=1783554.
// https://github.com/boostorg/filesystem/commit/4b9052f1e0b2acf625e8247582f44acdcc78a4ce
if (err == EINVAL || err == EOPNOTSUPP) {
err = copy_file_linux_read_write(infile.fd, outfile.fd, from_stat.st_size);
if (err < 0)
goto fail;
// Succeeded.
break;
}
}
if (err == EINTR)
continue;
if (err == 0)
@ -529,7 +589,6 @@ bool copy_file_linux(const boost::filesystem::path &from, const boost::filesyste
offset += sz;
}
}
// If we created a new file with an explicitly added S_IWUSR permission,
// we may need to update its mode bits to match the source file.
if (to_mode != from_mode && ::fchmod(outfile.fd, from_mode) != 0) {
@ -781,7 +840,7 @@ unsigned get_current_pid()
#endif
}
std::string xml_escape(std::string text)
std::string xml_escape(std::string text, bool is_marked/* = false*/)
{
std::string::size_type pos = 0;
for (;;)
@ -796,8 +855,8 @@ std::string xml_escape(std::string text)
case '\"': replacement = "&quot;"; break;
case '\'': replacement = "&apos;"; break;
case '&': replacement = "&amp;"; break;
case '<': replacement = "&lt;"; break;
case '>': replacement = "&gt;"; break;
case '<': replacement = is_marked ? "<" :"&lt;"; break;
case '>': replacement = is_marked ? ">" :"&gt;"; break;
default: break;
}

View File

@ -8,14 +8,23 @@
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree_fwd.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/log/trivial.hpp>
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/format.hpp"
#include "libslic3r/libslic3r.h"
#include "libslic3r/Time.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/FileParserError.hpp"
#include "libslic3r/Utils.hpp"
#include "../GUI/GUI.hpp"
#include "../GUI/GUI_App.hpp"
#include "../GUI/I18N.hpp"
#include "../GUI/MainFrame.hpp"
#include <wx/richmsgdlg.h>
#define SLIC3R_SNAPSHOTS_DIR "snapshots"
#define SLIC3R_SNAPSHOT_FILE "snapshot.ini"
@ -362,7 +371,8 @@ static void copy_config_dir_single_level(const boost::filesystem::path &path_src
for (auto &dir_entry : boost::filesystem::directory_iterator(path_src))
if (Slic3r::is_ini_file(dir_entry))
boost::filesystem::copy_file(dir_entry.path(), path_dst / dir_entry.path().filename(), boost::filesystem::copy_option::overwrite_if_exists);
if (std::string error_message; copy_file(dir_entry.path().string(), (path_dst / dir_entry.path().filename()).string(), error_message, false) != SUCCESS)
throw Slic3r::RuntimeError(format("Failed copying \"%1%\" to \"%2%\": %3%", path_src.string(), path_dst.string(), error_message));
}
static void delete_existing_ini_files(const boost::filesystem::path &path)
@ -413,7 +423,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
++ it;
// Read the active config bundle, parse the config version.
PresetBundle bundle;
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY);
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly, ForwardCompatibilitySubstitutionRule::EnableSilent);
for (const auto &vp : bundle.vendors)
if (vp.second.id == cfg.name)
cfg.version.config_version = vp.second.config_version;
@ -433,14 +443,27 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
}
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
boost::filesystem::create_directory(snapshot_dir);
// Backup the presets.
for (const char *subdir : snapshot_subdirs)
copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir);
snapshot.save_ini((snapshot_dir / "snapshot.ini").string());
assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured);
m_snapshots.emplace_back(std::move(snapshot));
try {
boost::filesystem::create_directory(snapshot_dir);
// Backup the presets.
for (const char *subdir : snapshot_subdirs)
copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir);
snapshot.save_ini((snapshot_dir / "snapshot.ini").string());
assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured);
m_snapshots.emplace_back(std::move(snapshot));
} catch (...) {
if (boost::filesystem::is_directory(snapshot_dir)) {
try {
// Clean up partially copied snapshot.
boost::filesystem::remove_all(snapshot_dir);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "Failed taking snapshot and failed removing the snapshot directory " << snapshot_dir;
}
}
throw;
}
return m_snapshots.back();
}
@ -551,6 +574,32 @@ SnapshotDB& SnapshotDB::singleton()
return instance;
}
const Snapshot* take_config_snapshot_report_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment)
{
try {
return &SnapshotDB::singleton().take_snapshot(app_config, reason, comment);
} catch (std::exception &err) {
show_error(static_cast<wxWindow*>(wxGetApp().mainframe),
_L("Taking a configuration snapshot failed.") + "\n\n" + from_u8(err.what()));
return nullptr;
}
}
bool take_config_snapshot_cancel_on_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment, const std::string &message)
{
try {
SnapshotDB::singleton().take_snapshot(app_config, reason, comment);
return true;
} catch (std::exception &err) {
wxRichMessageDialog dlg(static_cast<wxWindow*>(wxGetApp().mainframe),
_L("PrusaSlicer has encountered an error while taking a configuration snapshot.") + "\n\n" + from_u8(err.what()) + "\n\n" + from_u8(message),
_L("PrusaSlicer error"),
wxYES_NO);
dlg.SetYesNoLabels(_L("Continue"), _L("Abort"));
return dlg.ShowModal() == wxID_YES;
}
}
} // namespace Config
} // namespace GUI
} // namespace Slic3r

View File

@ -127,6 +127,13 @@ private:
std::vector<Snapshot> m_snapshots;
};
// Take snapshot on SnapshotDB::singleton(). If taking snapshot fails, report an error and return nullptr.
const Snapshot* take_config_snapshot_report_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment);
// Take snapshot on SnapshotDB::singleton(). If taking snapshot fails, report "message", and present a "Continue" or "Abort" buttons to respond.
// Return true on success and on "Continue" to continue with the process (for example installation of presets).
bool take_config_snapshot_cancel_on_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment, const std::string &message);
} // namespace Config
} // namespace GUI
} // namespace Slic3r

View File

@ -59,7 +59,11 @@ bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bu
this->is_prusa_bundle = ais_prusa_bundle;
std::string path_string = source_path.string();
size_t presets_loaded = preset_bundle->load_configbundle(path_string, PresetBundle::LOAD_CFGBNDLE_SYSTEM);
// Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle(
path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
// No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
assert(config_substitutions.empty());
auto first_vendor = preset_bundle->vendors.begin();
if (first_vendor == preset_bundle->vendors.end()) {
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string;
@ -2358,7 +2362,7 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo
return true;
}
void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
{
const auto enabled_vendors = appconfig_new.vendors();
@ -2407,14 +2411,14 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
break;
}
if (snapshot) {
SnapshotDB::singleton().take_snapshot(*app_config, snapshot_reason);
}
if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Continue with applying configuration changes?")))
return false;
if (install_bundles.size() > 0) {
// Install bundles from resources.
// Don't create snapshot - we've already done that above if applicable.
updater->install_bundles_rsrc(std::move(install_bundles), false);
if (! updater->install_bundles_rsrc(std::move(install_bundles), false))
return false;
} else {
BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources";
}
@ -2490,7 +2494,11 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
}
}
preset_bundle->load_presets(*app_config, preferred_model);
// Reloading the configs after some modifications were done to PrusaSlicer.ini.
// Just perform the substitutions silently, as the substitutions were already presented to the user on application start-up
// and the Wizard shall not create any new values that would require substitution.
// Throw on substitutions in system profiles, as the system profiles provided over the air should be compatible with this PrusaSlicer version.
preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem, preferred_model);
if (page_custom->custom_wanted()) {
page_firmware->apply_custom_config(*custom_config);
@ -2504,6 +2512,8 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
// Update the selections from the compatibilty.
preset_bundle->export_selections(*app_config);
return true;
}
@ -2762,7 +2772,8 @@ bool ConfigWizard::run(RunReason reason, StartPage start_page)
p->set_start_page(start_page);
if (ShowModal() == wxID_OK) {
p->apply_config(app.app_config, app.preset_bundle, app.preset_updater);
if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater))
return false;
app.app_config->set_legacy_datadir(false);
app.update_mode();
app.obj_manipul()->update_ui_from_settings();

View File

@ -622,7 +622,7 @@ struct ConfigWizard::priv
bool on_bnt_finish();
bool check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id = std::string());
void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
bool apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
// #ys_FIXME_alise
void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add);

View File

@ -306,9 +306,11 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR
// to avoid event propagation to other sidebar items
c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) {
evt.StopPropagation();
#ifdef __linux__
// FinishEditing grabs new selection and triggers config update. We better call
// it explicitly, automatic update on KILL_FOCUS didn't work on Linux.
this->FinishEditing();
#endif
});
return c_editor;

View File

@ -1185,6 +1185,10 @@ void Choice::set_value(const boost::any& value, bool change_event)
}
case coEnum: {
int val = boost::any_cast<int>(value);
if (m_opt_id.compare("host_type") == 0 && val != 0 &&
m_opt.enum_values.size() > field->GetCount()) // for case, when PrusaLink isn't used as a HostType
val--;
if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "solid_fill_pattern"
|| m_opt_id == "fill_pattern" || m_opt_id == "support_material_interface_pattern" || m_opt_id == "brim_ears_pattern")
val = idx_from_enum_value<InfillPattern>(val);
@ -1223,6 +1227,8 @@ void Choice::set_value(const boost::any& value, bool change_event)
val = idx_from_enum_value<WipeAlgo>(val);
else if (m_opt_id.compare("output_format") == 0)
val = idx_from_enum_value<OutputFormat>(val);
else if (m_opt_id.compare("config_compatibility") == 0)
val = idx_from_enum_value<ForwardCompatibilitySubstitutionRule>(val);
field->SetSelection(val);
break;
}
@ -1279,7 +1285,7 @@ void Choice::set_values(const wxArrayString &values)
auto ww = dynamic_cast<choice_ctrl*>(window);
auto value = ww->GetValue();
ww->Clear();
ww->Append("");
// ww->Append("");
for (const auto &el : values)
ww->Append(el);
ww->SetValue(value);
@ -1301,7 +1307,10 @@ boost::any& Choice::get_value()
if (m_opt.type == coEnum)
{
int ret_enum = field->GetSelection();
int ret_enum = field->GetSelection();
if (m_opt_id.compare("host_type") == 0 &&
m_opt.enum_values.size() > field->GetCount()) // for case, when PrusaLink isn't used as a HostType
ret_enum++;
if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "solid_fill_pattern"
|| m_opt_id == "support_material_interface_pattern" || m_opt_id == "fill_pattern" || m_opt_id == "brim_ears_pattern")
convert_to_enum_value<InfillPattern>(ret_enum);
@ -1340,6 +1349,8 @@ boost::any& Choice::get_value()
convert_to_enum_value<WipeAlgo>(ret_enum);
else if (m_opt_id.compare("output_format") == 0)
convert_to_enum_value<OutputFormat>(ret_enum);
else if(m_opt_id.compare("config_compatibility") == 0)
convert_to_enum_value<ForwardCompatibilitySubstitutionRule>(ret_enum);
}
else if (m_opt.gui_type == "f_enum_open") {
const int ret_enum = field->GetSelection();

View File

@ -65,6 +65,8 @@ enum {
USB_PID_MMU_APP = 4,
USB_PID_CW1_BOOT = 7,
USB_PID_CW1_APP = 8,
USB_PID_CW1S_BOOT = 14,
USB_PID_CW1S_APP = 15,
};
// This enum discriminates the kind of information in EVT_AVRDUDE,
@ -308,7 +310,7 @@ void FirmwareDialog::priv::update_flash_enabled()
void FirmwareDialog::priv::load_hex_file(const wxString &path)
{
hex_file = HexFile(path.wx_str());
const bool autodetect = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1;
const bool autodetect = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1 || hex_file.device == HexFile::DEV_CW1S;
set_autodetect(autodetect);
}
@ -636,6 +638,10 @@ void FirmwareDialog::priv::perform_upload()
this->prepare_avr109(Avr109Pid(USB_PID_CW1_BOOT, USB_PID_CW1_APP));
break;
case HexFile::DEV_CW1S:
this->prepare_avr109(Avr109Pid(USB_PID_CW1S_BOOT, USB_PID_CW1S_APP));
break;
default:
this->prepare_mk2();
break;
@ -761,11 +767,10 @@ const char* FirmwareDialog::priv::avr109_dev_name(Avr109Pid usb_pid) {
switch (usb_pid.boot) {
case USB_PID_MMU_BOOT:
return "Original Prusa MMU 2.0 Control";
break;
case USB_PID_CW1_BOOT:
return "Original Prusa CW1";
break;
case USB_PID_CW1S_BOOT:
return "Original Prusa CW1S";
default: throw Slic3r::RuntimeError((boost::format("Invalid avr109 device USB PID: %1%") % usb_pid.boot).str());
}
}

View File

@ -5,6 +5,7 @@
#include <string>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/any.hpp>
#if __APPLE__
@ -18,6 +19,7 @@
#include "AboutDialog.hpp"
#include "MsgDialog.hpp"
#include "format.hpp"
#include "libslic3r/Print.hpp"
@ -273,6 +275,127 @@ void warning_catcher(wxWindow* parent, const wxString& message)
msg.ShowModal();
}
static wxString bold(const wxString& str)
{
return wxString::Format("<b>%s</b>", str);
};
static wxString bold_string(const wxString& str)
{
return wxString::Format("<b>\"%s\"</b>", str);
};
static void add_config_substitutions(const ConfigSubstitutions& conf_substitutions, wxString& changes)
{
changes += "<table>";
for (const ConfigSubstitution& conf_substitution : conf_substitutions) {
wxString new_val;
const ConfigOptionDef* def = conf_substitution.opt_def;
if (!def)
continue;
switch (def->type) {
case coEnum:
{
const std::vector<std::string>& labels = def->enum_labels;
const std::vector<std::string>& values = def->enum_values;
int val = conf_substitution.new_value->getInt();
bool is_infill = def->opt_key == "top_fill_pattern" ||
def->opt_key == "bottom_fill_pattern" ||
def->opt_key == "fill_pattern";
// Each infill doesn't use all list of infill declared in PrintConfig.hpp.
// So we should "convert" val to the correct one
if (is_infill) {
for (const auto& key_val : *def->enum_keys_map)
if ((int)key_val.second == val) {
auto it = std::find(values.begin(), values.end(), key_val.first);
if (it == values.end())
break;
auto idx = it - values.begin();
new_val = wxString("\"") + values[idx] + "\"" + " (" + from_u8(_utf8(labels[idx])) + ")";
break;
}
if (new_val.IsEmpty()) {
assert(false);
new_val = _L("Undefined");
}
}
else
new_val = wxString("\"") + values[val] + "\"" + " (" + from_u8(_utf8(labels[val])) + ")";
break;
}
case coBool:
new_val = conf_substitution.new_value->getBool() ? "true" : "false";
break;
case coBools:
if (conf_substitution.new_value->nullable())
for (const char v : static_cast<const ConfigOptionBoolsNullable*>(conf_substitution.new_value.get())->values)
new_val += std::string(v == ConfigOptionBoolsNullable::nil_value() ? "nil" : v ? "true" : "false") + ", ";
else
for (const char v : static_cast<const ConfigOptionBools*>(conf_substitution.new_value.get())->values)
new_val += std::string(v ? "true" : "false") + ", ";
if (! new_val.empty())
new_val.erase(new_val.begin() + new_val.size() - 2, new_val.end());
break;
default:
assert(false);
}
changes += format_wxstr("<tr><td><b>\"%1%\" (%2%)</b></td><td>: ", def->opt_key, _(def->label)) +
format_wxstr(_L("%1% was substituted with %2%"), bold_string(conf_substitution.old_value), bold(new_val)) +
"</td></tr>";
}
changes += "</table>";
}
static wxString substitution_message(const wxString& changes)
{
return
_L("Most likely the configuration was produced by a newer version of PrusaSlicer or by some PrusaSlicer fork.") + " " +
_L("The following values were substituted:") + "\n" + changes + "\n\n" +
_L("Review the substitutions and adjust them if needed.");
}
void show_substitutions_info(const PresetsConfigSubstitutions& presets_config_substitutions)
{
wxString changes;
auto preset_type_name = [](Preset::Type type) {
switch (type) {
case Preset::TYPE_FFF_PRINT: return _L("Print settings");
case Preset::TYPE_SLA_PRINT: return _L("SLA print settings");
case Preset::TYPE_FFF_FILAMENT: return _L("Filament");
case Preset::TYPE_SLA_MATERIAL: return _L("SLA material");
case Preset::TYPE_PRINTER: return _L("Printer");
case Preset::TYPE_PHYSICAL_PRINTER: return _L("Physical Printer");
default: assert(false); return wxString();
}
};
for (const PresetConfigSubstitutions& substitution : presets_config_substitutions) {
changes += "\n\n" + format_wxstr("%1% : %2%", preset_type_name(substitution.preset_type), bold_string(substitution.preset_name));
if (!substitution.preset_file.empty())
changes += format_wxstr(" (%1%)", substitution.preset_file);
add_config_substitutions(substitution.substitutions, changes);
}
InfoDialog msg(nullptr, _L("Configuration bundle was loaded, however some configuration values were not recognized."), substitution_message(changes));
msg.ShowModal();
}
void show_substitutions_info(const ConfigSubstitutions& config_substitutions, const std::string& filename)
{
wxString changes = "\n";
add_config_substitutions(config_substitutions, changes);
InfoDialog msg(nullptr,
format_wxstr(_L("Configuration file \"%1%\" was loaded, however some configuration values were not recognized."), from_u8(filename)),
substitution_message(changes));
msg.ShowModal();
}
void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items)
{
if (comboCtrl == nullptr)

View File

@ -7,6 +7,7 @@ namespace boost::filesystem { class path; }
#include <wx/string.h>
#include "libslic3r/Config.hpp"
#include "libslic3r/Preset.hpp"
class wxWindow;
class wxMenuBar;
@ -49,6 +50,8 @@ void show_info(wxWindow* parent, const wxString& message, const wxString& title
void show_info(wxWindow* parent, const char* message, const char* title = nullptr);
inline void show_info(wxWindow* parent, const std::string& message,const std::string& title = std::string()) { show_info(parent, message.c_str(), title.c_str()); }
void warning_catcher(wxWindow* parent, const wxString& message);
void show_substitutions_info(const PresetsConfigSubstitutions& presets_config_substitutions);
void show_substitutions_info(const ConfigSubstitutions& config_substitutions, const std::string& filename);
// Creates a wxCheckListBoxComboPopup inside the given wxComboCtrl, filled with the given text and items.
// Items data must be separated by '|', and contain the item name to be shown followed by its initial value (0 for false, 1 for true).

View File

@ -443,7 +443,9 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension)
/* FT_TEX */ "Texture (*.png, *.svg)|*.png;*.PNG;*.svg;*.SVG",
/* FT_PNGZIP */ "Masked SLA files (*.sl1)|*.sl1;*.SL1",
/* FT_SL1 */ "Masked SLA files (*.sl1, *.sl1s)|*.sl1;*.SL1;*.sl1s;*.SL1S",
// Workaround for OSX file picker, for some reason it always saves with the 1st extension.
/* FT_SL1S */ "Masked SLA files (*.sl1s, *.sl1)|*.sl1s;*.SL1S;*.sl1;*.SL1",
};
std::string out = defaults[file_type];
@ -634,6 +636,9 @@ void GUI_App::post_init()
this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str()));
}
else {
if (! this->init_params->preset_substitutions.empty())
show_substitutions_info(this->init_params->preset_substitutions);
#if 0
// Load the cummulative config over the currently active profiles.
//FIXME if multiple configs are loaded, only the last one will have an effect.
@ -653,6 +658,24 @@ void GUI_App::post_init()
if (! this->init_params->extra_config.empty())
this->mainframe->load_config(this->init_params->extra_config);
}
// The extra CallAfter() is needed because of Mac, where this is the only way
// to popup a modal dialog on start without screwing combo boxes.
// This is ugly but I honestly found no better way to do it.
// Neither wxShowEvent nor wxWindowCreateEvent work reliably.
if (this->preset_updater) {
this->check_updates(false);
CallAfter([this] {
this->config_wizard_startup();
this->preset_updater->slic3r_update_notify();
this->preset_updater->sync(preset_bundle);
});
}
#ifdef _WIN32
// Sets window property to mainframe so other instances can indentify it.
OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int);
#endif //WIN32
}
IMPLEMENT_APP(GUI_App)
@ -923,7 +946,10 @@ bool GUI_App::on_init_inner()
// Suppress the '- default -' presets.
preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1");
try {
preset_bundle->load_presets(*app_config);
// Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only.
// If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force
// installation of a compatible system preset, thus nullifying the system preset substitutions.
init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent);
} catch (const std::exception &ex) {
show_error(nullptr, ex.what());
}
@ -976,7 +1002,6 @@ bool GUI_App::on_init_inner()
if (! plater_)
return;
if (app_config->dirty() && app_config->get("autosave") == "1")
app_config->save();
@ -997,33 +1022,6 @@ bool GUI_App::on_init_inner()
#endif
this->post_init();
}
// Preset updating & Configwizard are done after the above initializations,
// and after MainFrame is created & shown.
// The extra CallAfter() is needed because of Mac, where this is the only way
// to popup a modal dialog on start without screwing combo boxes.
// This is ugly but I honestly found no better way to do it.
// Neither wxShowEvent nor wxWindowCreateEvent work reliably.
static bool once = true;
if (once) {
once = false;
if (preset_updater != nullptr) {
check_updates(false);
CallAfter([this] {
config_wizard_startup();
preset_updater->slic3r_update_notify();
preset_updater->sync(preset_bundle);
});
}
#ifdef _WIN32
//sets window property to mainframe so other instances can indentify it
OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int);
#endif //WIN32
}
});
m_initialized = true;
@ -1819,9 +1817,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
child->SetFont(normal_font());
if (dlg.ShowModal() == wxID_OK)
app_config->set("on_snapshot",
Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot(
*app_config, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()).id);
if (const Config::Snapshot *snapshot = Config::take_config_snapshot_report_error(
*app_config, Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data());
snapshot != nullptr)
app_config->set("on_snapshot", snapshot->id);
}
break;
case ConfigMenuSnapshots:
@ -1832,13 +1831,24 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot);
dlg.ShowModal();
if (!dlg.snapshot_to_activate().empty()) {
if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config))
Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK);
if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config) &&
! Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK, "",
GUI::format(_L("Continue to activate a configuration snapshot %1%?"), dlg.snapshot_to_activate())))
break;
try {
app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id);
preset_bundle->load_presets(*app_config);
// Enable substitutions, log both user and system substitutions. There should not be any substitutions performed when loading system
// presets because compatibility of profiles shall be verified using the min_slic3r_version keys in config index, but users
// are known to be creative and mess with the config files in various ways.
if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable);
! all_substitutions.empty())
show_substitutions_info(all_substitutions);
// Load the currently selected preset into the GUI, update the preset selection box.
load_current_presets();
// update config wizard in respect to the new config
update_wizard_from_config();
} catch (std::exception &ex) {
GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what()));
}
@ -2015,6 +2025,17 @@ void GUI_App::load_current_presets(bool check_printer_presets_ /*= true*/)
}
}
void GUI_App::update_wizard_from_config()
{
if (!m_wizard)
return;
// If ConfigWizard was created before changing of the configuration,
// we have to destroy it to have possibility to create it again in respect to the new config's parameters
m_wizard->Reparent(nullptr);
m_wizard->Destroy();
m_wizard = nullptr;
}
bool GUI_App::OnExceptionInMainLoop()
{
generic_exception_handle();
@ -2175,7 +2196,13 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage
{
wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
if (reason == ConfigWizard::RR_USER)
if (PresetUpdater::UpdateResult result = preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD);
result == PresetUpdater::R_ALL_CANCELED)
return false;
if (! m_wizard) {
wxBusyCursor wait;
m_wizard = new ConfigWizard(mainframe);
}
@ -2334,7 +2361,7 @@ void GUI_App::check_updates(const bool verbose)
{
PresetUpdater::UpdateResult updater_result;
try {
updater_result = preset_updater->config_update(app_config->orig_version(), verbose);
updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION);
if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
mainframe->Close();
}

View File

@ -63,7 +63,9 @@ enum FileType
FT_TEX,
FT_PNGZIP,
FT_SL1,
// Workaround for OSX file picker, for some reason it always saves with the 1st extension.
FT_SL1S,
FT_SIZE,
};
@ -226,6 +228,7 @@ public:
bool check_print_host_queue();
bool checked_tab(Tab* tab);
void load_current_presets(bool check_printer_presets = true);
void update_wizard_from_config();
wxString current_language_code() const { return m_wxLocale->GetCanonicalName(); }
// Translate the language code to a code, for which Prusa Research maintains translations. Defaults to "en_US".
@ -250,7 +253,7 @@ public:
// Parameters extracted from the command line to be passed to GUI after initialization.
const GUI_InitParams* init_params { nullptr };
GUI_InitParams* init_params { nullptr };
AppConfig* app_config{ nullptr };
PresetBundle* preset_bundle{ nullptr };
@ -260,7 +263,7 @@ public:
std::mutex not_modal_dialog_mutex;
wxDialog* not_modal_dialog = nullptr;
PresetUpdater* get_preset_updater() { return preset_updater; }
PresetUpdater* get_preset_updater() { return preset_updater; }
wxNotebook* tab_panel() const ;
int extruders_cnt() const;

View File

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

View File

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

View File

@ -33,7 +33,7 @@ public:
m_filepicker = new wxFilePickerCtrl(this, wxID_ANY,
from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")),
"SL1 archive files (*.sl1, *.zip)|*.sl1;*.SL1;*.zip;*.ZIP",
"SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP",
wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST);
szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER);
@ -113,13 +113,14 @@ public:
Plater *plater;
Sel sel = Sel::modelAndProfile;
TriangleMesh mesh;
DynamicPrintConfig profile;
wxString path;
Vec2i32 win = {2, 2};
std::string err;
TriangleMesh mesh;
DynamicPrintConfig profile;
wxString path;
Vec2i32 win = {2, 2};
std::string err;
ConfigSubstitutions config_substitutions;
priv(Plater *plt): plater{plt} {}
};
@ -142,13 +143,13 @@ void SLAImportJob::process()
try {
switch (p->sel) {
case Sel::modelAndProfile:
import_sla_archive(path, p->win, p->mesh, p->profile, progr);
p->config_substitutions = import_sla_archive(path, p->win, p->mesh, p->profile, progr);
break;
case Sel::modelOnly:
import_sla_archive(path, p->win, p->mesh, progr);
p->config_substitutions = import_sla_archive(path, p->win, p->mesh, progr);
break;
case Sel::profileOnly:
import_sla_archive(path, p->profile);
p->config_substitutions = import_sla_archive(path, p->profile);
break;
}
@ -181,6 +182,7 @@ void SLAImportJob::prepare()
p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : path.ToUTF8();
p->sel = dlg.get_selection();
p->win = dlg.get_marchsq_windowsize();
p->config_substitutions.clear();
} else {
p->path = "";
}
@ -223,8 +225,11 @@ void SLAImportJob::finalize()
bool is_centered = false;
p->plater->sidebar().obj_list()->load_mesh_object(p->mesh, name, is_centered);
}
if (! p->config_substitutions.empty())
show_substitutions_info(p->config_substitutions, p->path.ToUTF8().data());
reset();
}
}}
}} // namespace Slic3r::GUI

View File

@ -1268,7 +1268,7 @@ void MainFrame::init_menubar_as_editor()
[this](wxCommandEvent&) { if (m_plater) m_plater->add_model(true); }, "import_plater", nullptr,
[this](){return m_plater != nullptr; }, this);
append_menu_item(import_menu, wxID_ANY, _L("Import SL1 archive") + dots, _L("Load an SL1 archive"),
append_menu_item(import_menu, wxID_ANY, _L("Import SL1 / SL1S archive") + dots, _L("Load an SL1 / Sl1S archive"),
[this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr,
[this](){return m_plater != nullptr; }, this);
@ -1625,6 +1625,7 @@ void MainFrame::update_menubar()
m_changeable_menu_items[miPrinterTab] ->SetBitmap(create_scaled_bitmap(is_fff ? "printer" : "sla_printer"));
}
#if 0
// To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG".
void MainFrame::quick_slice(const int qs)
{
@ -1747,6 +1748,7 @@ void MainFrame::quick_slice(const int qs)
// };
// Slic3r::GUI::catch_error(this, []() { if (m_progress_dialog) m_progress_dialog->Destroy(); });
}
#endif
void MainFrame::reslice_now()
{
@ -1829,7 +1831,9 @@ void MainFrame::load_config_file()
bool MainFrame::load_config_file(const std::string &path)
{
try {
wxGetApp().preset_bundle->load_config_file(path);
ConfigSubstitutions config_substitutions = wxGetApp().preset_bundle->load_config_file(path, ForwardCompatibilitySubstitutionRule::Enable);
if (!config_substitutions.empty())
show_substitutions_info(config_substitutions, path);
} catch (const std::exception& ex) {
show_error(this, ex.what());
return false;
@ -1885,14 +1889,20 @@ void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool re
wxGetApp().app_config->update_config_dir(get_dir_name(file));
auto presets_imported = 0;
size_t presets_imported = 0;
PresetsConfigSubstitutions config_substitutions;
try {
presets_imported = wxGetApp().preset_bundle->load_configbundle(file.ToUTF8().data());
// Report all substitutions.
std::tie(config_substitutions, presets_imported) = wxGetApp().preset_bundle->load_configbundle(
file.ToUTF8().data(), PresetBundle::LoadConfigBundleAttribute::SaveImported, ForwardCompatibilitySubstitutionRule::Enable);
} catch (const std::exception &ex) {
show_error(this, ex.what());
return;
}
if (! config_substitutions.empty())
show_substitutions_info(config_substitutions);
// Load the currently selected preset into the GUI, update the preset selection box.
wxGetApp().load_current_presets();

View File

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

View File

@ -58,7 +58,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
logo = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap);
topsizer->Add(logo, 0, wxALL, BORDER);
topsizer->Add(logo, 0, /*wxALL*/wxTOP | wxBOTTOM | wxLEFT, BORDER);
topsizer->Add(rightsizer, 1, wxALL | wxEXPAND, BORDER);
SetSizerAndFit(topsizer);
@ -107,5 +107,57 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, bool monospaced_
Fit();
}
// InfoDialog
InfoDialog::InfoDialog(wxWindow* parent, const wxString &title, const wxString& msg)
: MsgDialog(parent, wxString::Format(_L("%s information"), SLIC3R_APP_NAME), title)
, msg(msg)
{
this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
// Text shown as HTML, so that mouse selection and Ctrl-V to copy will work.
wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO);
{
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
wxFont monospace = wxGetApp().code_font();
wxColour text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
const int font_size = font.GetPointSize() - 1;
int size[] = { font_size, font_size, font_size, font_size, font_size, font_size, font_size };
html->SetFonts(font.GetFaceName(), monospace.GetFaceName(), size);
html->SetBorders(2);
// calculate html page size from text
int lines = msg.Freq('\n');
if (msg.Contains("<tr>")) {
int pos = 0;
while (pos < (int)msg.Len() && pos != wxNOT_FOUND) {
pos = msg.find("<tr>", pos + 1);
lines+=2;
}
}
int page_height = std::min((font.GetPixelSize().y + 1) * lines, 68 * wxGetApp().em_unit());
wxSize page_size(68 * wxGetApp().em_unit(), page_height);
html->SetMinSize(page_size);
std::string msg_escaped = xml_escape(msg.ToUTF8().data(), true);
boost::replace_all(msg_escaped, "\r\n", "<br>");
boost::replace_all(msg_escaped, "\n", "<br>");
html->SetPage("<html><body bgcolor=\"" + bgr_clr_str + "\"><font color=\"" + text_clr_str + "\">" + wxString::FromUTF8(msg_escaped.data()) + "</font></body></html>");
content_sizer->Add(html, 1, wxEXPAND);
}
// Set info bitmap
logo->SetBitmap(create_scaled_bitmap("info", this, 84));
Fit();
}
}
}

View File

@ -66,6 +66,22 @@ private:
};
// Generic info dialog, used for displaying exceptions
class InfoDialog : public MsgDialog
{
public:
InfoDialog(wxWindow *parent, const wxString &title, const wxString &msg);
InfoDialog(InfoDialog&&) = delete;
InfoDialog(const InfoDialog&) = delete;
InfoDialog&operator=(InfoDialog&&) = delete;
InfoDialog&operator=(const InfoDialog&) = delete;
virtual ~InfoDialog() = default;
private:
wxString msg;
};
}
}

View File

@ -9,6 +9,7 @@
#include "libslic3r/AppConfig.hpp"
#include "wxExtensions.hpp"
#include "libslic3r/Config.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
@ -36,7 +37,25 @@ wxDEFINE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClicke
wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent);
wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, PresetUpdateAvailableClickedEvent);
namespace Notifications_Internal{
const NotificationManager::NotificationData NotificationManager::basic_notifications[] = {
// {NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10, _u8L("Slicing is not possible.")},
// {NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0, _u8L("Exporting finished."), _u8L("Eject drive.") },
{NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") },
// {NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5, _u8L("3D Mouse connected.") },
// {NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20, _u8L("New Presets are available."), _u8L("See here.") },
{NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more."), [](wxEvtHandler* evnthndlr){
if (evnthndlr != nullptr) wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED)); return true; }},
{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr){
wxLaunchDefaultBrowser("https://github.com/" SLIC3R_GITHUB "/releases"); return true; }},
{NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotification, 10,
_u8L("You have just added a G-code for color change, but its value is empty.\n"
"To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") },
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") },
//{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20, _u8L("Loading of model has Failed") },
//{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
};
namespace {
ImFont* add_default_font(float pixel_size)
{
ImGuiIO& io = ImGui::GetIO();
@ -48,7 +67,7 @@ namespace Notifications_Internal{
return font;
}
static inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity)
inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity)
{
if (fading_out)
ImGui::PushStyleColor(idx, ImVec4(col.x, col.y, col.z, col.w * current_fade_opacity));
@ -138,8 +157,8 @@ NotificationManager::PopNotification::PopNotification(const NotificationData &n,
, m_last_remaining_time (n.duration)
, m_counting_down (n.duration != 0)
, m_text1 (n.text1)
, m_hypertext (n.hypertext)
, m_text2 (n.text2)
, m_hypertext (n.hypertext)
, m_text2 (n.text2)
, m_evt_handler (evt_handler)
, m_notification_start (GLCanvas3D::timestamp_now())
{
@ -187,26 +206,26 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init
// color change based on fading out
bool fading_pop = false;
if (m_fading_out) {
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity);
fading_pop = true;
}
// background color
if (m_is_gray) {
ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f);
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
}
else if (m_data.level == NotificationLevel::ErrorNotification) {
ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
backcolor.x += 0.3f;
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
}
else if (m_data.level == NotificationLevel::WarningNotification) {
ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
backcolor.x += 0.3f;
backcolor.y += 0.15f;
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
}
// name of window - probably indentifies window and is shown so last_end add whitespaces according to id
@ -304,23 +323,23 @@ NotificationManager::PopNotification::RenderResult NotificationManager::PopNotif
if (m_fading_out) {
if (!m_paused)
m_current_fade_opacity -= 1.f / ((m_fading_time + 1.f) * 60.f);
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity);
fading_pop = true;
}
// background color
if (m_is_gray) {
ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f);
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
} else if (m_data.level == NotificationLevel::ErrorNotification) {
ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
backcolor.x += 0.3f;
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
} else if (m_data.level == NotificationLevel::WarningNotification) {
ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
backcolor.x += 0.3f;
backcolor.y += 0.15f;
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
}
//name of window - probably indentifies window and is shown so last_end add whitespaces according to id
@ -414,7 +433,7 @@ void NotificationManager::PopNotification::init()
if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - m_window_width_offset) {
// more than one line till end
int next_space = text.find_first_of(' ', last_end);
if (next_space > 0) {
if (next_space > 0 && next_space < text.length()) {
int next_space_candidate = text.find_first_of(' ', next_space + 1);
while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) {
next_space = next_space_candidate;
@ -524,7 +543,7 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui,
orange_color.y += 0.2f;
//text
Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity);
ImGui::SetCursorPosX(text_x);
ImGui::SetCursorPosY(text_y);
imgui.text(text.c_str());
@ -547,8 +566,8 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img
orange_color.w = 0.8f;
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
@ -647,9 +666,9 @@ void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper&
{
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
//button - if part if treggered
@ -852,8 +871,9 @@ void NotificationManager::ExportFinishedNotification::render_text(ImGuiWrapper&
for (size_t i = 0; i < m_lines_count; i++) {
if (m_text1.size() >= m_endlines[i]) {
std::string line = m_text1.substr(last_end, m_endlines[i] - last_end);
if (i < m_lines_count - 1)
last_end = m_endlines[i] + (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0);
last_end = m_endlines[i];
if (m_text1.size() > m_endlines[i])
last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0);
ImGui::SetCursorPosX(x_offset);
ImGui::SetCursorPosY(starting_y + i * shift_y);
imgui.text(line.c_str());
@ -881,8 +901,8 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW
orange_color.w = 0.8f;
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
std::string button_text;
@ -937,7 +957,7 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW
}
bool NotificationManager::ExportFinishedNotification::on_text_click()
{
Notifications_Internal::open_folder(m_export_dir_path);
open_folder(m_export_dir_path);
return false;
}
//------ProgressBar----------------
@ -980,10 +1000,10 @@ NotificationManager::NotificationManager(wxEvtHandler* evt_handler) :
}
void NotificationManager::push_notification(const NotificationType type, int timestamp)
{
auto it = std::find_if(basic_notifications.begin(), basic_notifications.end(),
auto it = std::find_if(std::begin(basic_notifications), std::end(basic_notifications),
boost::bind(&NotificationData::type, boost::placeholders::_1) == type);
assert(it != basic_notifications.end());
if (it != basic_notifications.end())
assert(it != std::end(basic_notifications));
if (it != std::end(basic_notifications))
push_notification_data(*it, timestamp);
}
void NotificationManager::push_notification(const std::string& text, int timestamp)

View File

@ -167,7 +167,7 @@ private:
// Callback for hypertext - returns true if notification should close after triggering
// Usually sends event to UI thread thru wxEvtHandler.
// Examples in basic_notifications.
std::function<bool(wxEvtHandler*)> callback { nullptr };
std::function<bool(wxEvtHandler*)> callback;
const std::string text2;
};
@ -231,7 +231,7 @@ private:
//returns top in actual frame
float get_current_top() const { return m_top_y; }
const NotificationType get_type() const { return m_data.type; }
const NotificationData get_data() const { return m_data; }
const NotificationData& get_data() const { return m_data; }
const bool is_gray() const { return m_is_gray; }
// Call equals one second down
void substract_remaining_time(int seconds) { m_remaining_time -= seconds; }
@ -383,10 +383,10 @@ private:
ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage) : PopNotification(n, id_provider, evt_handler) { set_percentage(percentage); }
void set_percentage(float percent) { m_percentage = percent; if (percent >= 1.0f) m_progress_complete = true; else m_progress_complete = false; }
protected:
virtual void init();
virtual void render_text(ImGuiWrapper& imgui,
void init();
void render_text(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y);
const float win_pos_x, const float win_pos_y) override;
void render_bar(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y);
@ -410,20 +410,20 @@ private:
std::string m_export_dir_path;
protected:
// Reserves space on right for more buttons
virtual void count_spaces() override;
virtual void render_text(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y) override;
void count_spaces() override;
void render_text(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y) override;
// Renders also button to open directory with exported path and eject removable media
virtual void render_close_button(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y) override;
void render_close_button(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y) override;
void render_eject_button(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y);
virtual void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) override
void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) override
{ m_minimize_b_visible = false; }
virtual bool on_text_click() override;
bool on_text_click() override;
// local time of last hover for showing tooltip
long m_hover_time { 0 };
};
@ -465,23 +465,7 @@ private:
#endif // ENABLE_NEW_NOTIFICATIONS_FADE_OUT
*/
//prepared (basic) notifications
const std::vector<NotificationData> basic_notifications = {
// {NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10, _u8L("Slicing is not possible.")},
// {NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0, _u8L("Exporting finished."), _u8L("Eject drive.") },
{NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") },
// {NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5, _u8L("3D Mouse connected.") },
// {NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20, _u8L("New Presets are available."), _u8L("See here.") },
{NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more."), [](wxEvtHandler* evnthndlr){
if (evnthndlr != nullptr) wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED)); return true; }},
{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr){
wxLaunchDefaultBrowser("https://github.com/" SLIC3R_GITHUB "/releases"); return true; }},
{NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotification, 10,
_u8L("You have just added a G-code for color change, but its value is empty.\n"
"To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") },
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") },
//{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20, _u8L("Loading of model has Failed") },
//{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
};
static const NotificationData basic_notifications[];
};
}//namespace GUI

View File

@ -977,6 +977,8 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
ret = static_cast<int>(config.option<ConfigOptionEnum<WipeAlgo>>(opt_key)->value);
} else if (opt_key == "output_format") {
ret = static_cast<int>(config.option<ConfigOptionEnum<OutputFormat>>(opt_key)->value);
} else if (opt_key == "config_compatibility") {
ret = static_cast<int>(config.option<ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>>(opt_key)->value);
}
}
break;

View File

@ -67,7 +67,8 @@ PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, const std::str
// update Print Host upload from the selected preset
m_parent->get_printer()->update_from_preset(*preset);
// update values in parent (PhysicalPrinterDialog)
m_parent->update();
m_parent->update(true);
}
// update PrinterTechnology if it was changed
@ -153,7 +154,8 @@ void PresetForPrinter::msw_rescale()
PhysicalPrinterDialog::PhysicalPrinterDialog(wxWindow* parent, wxString printer_name) :
DPIDialog(parent, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
m_printer("", wxGetApp().preset_bundle->physical_printers.default_config())
m_printer("", wxGetApp().preset_bundle->physical_printers.default_config()),
had_all_mk3(!printer_name.empty())
{
SetFont(wxGetApp().normal_font());
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
@ -484,7 +486,7 @@ void PhysicalPrinterDialog::update_printhost_buttons()
m_printhost_browse_btn->Enable(host->has_auto_discovery());
}
void PhysicalPrinterDialog::update()
void PhysicalPrinterDialog::update(bool printer_change)
{
m_optgroup->reload_config();
@ -492,19 +494,30 @@ void PhysicalPrinterDialog::update()
// Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment)
bool supports_multiple_printers = false;
if (tech == ptFFF) {
m_optgroup->show_field("host_type");
m_optgroup->hide_field("printhost_authorization_type");
m_optgroup->show_field("printhost_apikey", true);
for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" })
m_optgroup->hide_field(opt_key);
update_host_type(printer_change);
const auto opt = m_config->option<ConfigOptionEnum<PrintHostType>>("host_type");
supports_multiple_printers = opt && opt->value == htRepetier;
m_optgroup->show_field("host_type");
if (opt && opt->value == htPrusaLink)
{
m_optgroup->show_field("printhost_authorization_type");
AuthorizationType auth_type = m_config->option<ConfigOptionEnum<AuthorizationType>>("printhost_authorization_type")->value;
m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword);
for (const char* opt_key : { "printhost_user", "printhost_password" })
m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword);
} else {
m_optgroup->hide_field("printhost_authorization_type");
m_optgroup->show_field("printhost_apikey", true);
for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" })
m_optgroup->hide_field(opt_key);
supports_multiple_printers = opt && opt->value == htRepetier;
}
// hide api key for klipper
if (opt && opt->value == htKlipper) {
m_optgroup->hide_field("printhost_apikey");
}
}
}
else {
m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false);
m_optgroup->hide_field("host_type");
@ -534,6 +547,58 @@ void PhysicalPrinterDialog::update()
this->Layout();
}
void PhysicalPrinterDialog::update_host_type(bool printer_change)
{
if (m_presets.empty())
return;
bool all_presets_are_from_mk3_family = true;
for (PresetForPrinter* prstft : m_presets) {
std::string preset_name = prstft->get_preset_name();
if (Preset* preset = wxGetApp().preset_bundle->printers.find_preset(preset_name)) {
std::string model_id = preset->config.opt_string("printer_model");
if (preset->vendor && preset->vendor->name == "Prusa Research") {
const std::vector<VendorProfile::PrinterModel>& models = preset->vendor->models;
auto it = std::find_if(models.begin(), models.end(),
[model_id](const VendorProfile::PrinterModel& model) { return model.id == model_id; });
if (it != models.end() && it->family == "MK3")
continue;
} else if (!preset->vendor && model_id.rfind("MK3", 0) == 0) {
continue;
}
}
all_presets_are_from_mk3_family = false;
break;
}
Field* ht = m_optgroup->get_field("host_type");
wxArrayString types;
// Append localized enum_labels
assert(ht->m_opt.enum_labels.size() == ht->m_opt.enum_values.size());
for (size_t i = 0; i < ht->m_opt.enum_labels.size(); i++) {
if (ht->m_opt.enum_values[i] == "prusalink" && !all_presets_are_from_mk3_family)
continue;
types.Add(_(ht->m_opt.enum_labels[i]));
}
Choice* choice = dynamic_cast<Choice*>(ht);
choice->set_values(types);
auto set_to_choice_and_config = [this, choice](PrintHostType type) {
choice->set_value(static_cast<int>(type));
m_config->set_key_value("host_type", new ConfigOptionEnum<PrintHostType>(type));
};
if ((printer_change && all_presets_are_from_mk3_family) || (!had_all_mk3 && all_presets_are_from_mk3_family))
set_to_choice_and_config(htPrusaLink);
else if ((printer_change && !all_presets_are_from_mk3_family) || (!all_presets_are_from_mk3_family && m_config->option<ConfigOptionEnum<PrintHostType>>("host_type")->value == htPrusaLink))
set_to_choice_and_config(htOctoPrint);
else
choice->set_value(m_config->option("host_type")->getInt());
had_all_mk3 = all_presets_are_from_mk3_family;
}
wxString PhysicalPrinterDialog::get_printer_name()
{
return m_printer_name->GetValue();
@ -661,8 +726,9 @@ void PhysicalPrinterDialog::AddPreset(wxEvent& event)
m_presets_sizer->Add(m_presets.back()->sizer(), 1, wxEXPAND | wxTOP, BORDER_W);
update_full_printer_names();
this->Fit();
update_host_type(true);
}
void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer)
@ -689,7 +755,8 @@ void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer)
this->Layout();
this->Fit();
update_host_type(true);
}
}} // namespace Slic3r::GUI

View File

@ -85,7 +85,8 @@ public:
PhysicalPrinterDialog(wxWindow* parent, wxString printer_name);
~PhysicalPrinterDialog();
void update();
void update(bool printer_change = false);
void update_host_type(bool printer_change);
void update_printhost_buttons();
void update_printers();
wxString get_printer_name();
@ -95,10 +96,11 @@ public:
PrinterTechnology get_printer_technology();
void DeletePreset(PresetForPrinter* preset_for_printer);
protected:
void on_dpi_changed(const wxRect& suggested_rect) override;
void on_sys_color_changed() override {};
bool had_all_mk3;
};

View File

@ -2399,7 +2399,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
DynamicPrintConfig config;
{
DynamicPrintConfig config_loaded;
model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, false, load_config);
ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::Enable };
model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, &config_substitutions, only_if(load_config, Model::LoadAttribute::CheckVersion));
if (load_config && !config_loaded.empty()) {
// Based on the printer technology field found in the loaded config, select the base for the config,
PrinterTechnology printer_technology = Preset::printer_technology(config_loaded);
@ -2425,6 +2426,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
// and place the loaded config over the base.
config += std::move(config_loaded);
}
if (! config_substitutions.empty())
show_substitutions_info(config_substitutions.substitutions, filename.string());
this->model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
}
@ -2445,11 +2448,15 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
}
}
else {
model = Slic3r::Model::read_from_file(path.string(), nullptr, false, load_config);
model = Slic3r::Model::read_from_file(path.string(), nullptr, nullptr, only_if(load_config, Model::LoadAttribute::CheckVersion));
for (auto obj : model.objects)
if (obj->name.empty())
obj->name = fs::path(obj->input_file).filename().string();
}
} catch (const ConfigurationError &e) {
std::string message = GUI::format(_L("Failed loading file \"%1%\" due to an invalid configuration."), filename.string()) + "\n\n" + e.what();
GUI::show_error(q, message);
continue;
} catch (const std::exception &e) {
GUI::show_error(q, e.what());
continue;
@ -3316,7 +3323,7 @@ void Plater::priv::reload_from_disk()
Model new_model;
try
{
new_model = Model::read_from_file(path, nullptr, true, false);
new_model = Model::read_from_file(path, nullptr, nullptr, Model::LoadAttribute::AddDefaultInstances);
for (ModelObject* model_object : new_model.objects)
{
model_object->center_around_origin();
@ -4706,7 +4713,9 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator
// Switch to the other printer technology. Switch to the last printer active for that particular technology.
AppConfig *app_config = wxGetApp().app_config;
app_config->set("presets", "printer", (new_printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name);
wxGetApp().preset_bundle->load_presets(*app_config);
//FIXME Why are we reloading the whole preset bundle here? Please document. This is fishy and it is unnecessarily expensive.
// Anyways, don't report any config value substitutions, they have been already reported to the user at application start up.
wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent);
// load_current_presets() calls Tab::load_current_preset() -> TabPrint::update() -> Object_list::update_and_show_object_settings_item(),
// but the Object list still keeps pointer to the old Model. Avoid a crash by removing selection first.
this->sidebar->obj_list()->unselect_objects();
@ -5477,13 +5486,14 @@ void Plater::export_gcode(bool prefer_removable)
fs::path output_path;
{
wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _L("Save G-code file as:") : _L("Save SL1 file as:"),
std::string ext = default_output_file.extension().string();
wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _L("Save G-code file as:") : _L("Save SL1 / SL1S file as:"),
start_dir,
from_path(default_output_file.filename()),
GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()),
GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : boost::iequals(ext, ".sl1s") ? FT_SL1S : FT_SL1, ext),
wxFD_SAVE | wxFD_OVERWRITE_PROMPT
);
if (dlg.ShowModal() == wxID_OK)
if (dlg.ShowModal() == wxID_OK)
output_path = into_path(dlg.GetPath());
}

View File

@ -887,9 +887,13 @@ void PlaterPresetComboBox::update()
if (!tooltip.IsEmpty())
SetToolTip(tooltip);
#ifdef __WXMSW__
// Use this part of code just on Windows to avoid of some layout issues on Linux
// see https://github.com/prusa3d/PrusaSlicer/issues/5163 and https://github.com/prusa3d/PrusaSlicer/issues/5505
// Update control min size after rescale (changed Display DPI under MSW)
if (GetMinWidth() != 20 * m_em_unit)
SetMinSize(wxSize(20 * m_em_unit, GetSize().GetHeight()));
#endif //__WXMSW__
}
void PlaterPresetComboBox::msw_rescale()

View File

@ -952,6 +952,8 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig&
return get_string_from_enum<InfillPattern>(opt_key, config);
if (opt_key == "complete_objects_sort")
return get_string_from_enum<CompleteObjectSort>(opt_key, config);
if (opt_key == "config_compatibility")
return get_string_from_enum<ForwardCompatibilitySubstitutionRule>(opt_key, config);
if (opt_key == "display_orientation")
return get_string_from_enum<SLADisplayOrientation>(opt_key, config);
if (opt_key == "output_format")

View File

@ -85,8 +85,11 @@ bool MsgUpdateSlic3r::disable_version_check() const
// MsgUpdateConfig
MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates) :
MsgDialog(nullptr, _(L("Configuration update")), _(L("Configuration update is available")), wxID_NONE)
MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_before_wizard/* = false*/) :
MsgDialog(nullptr, force_before_wizard ? _L("Opening Configuration Wizard") : _L("Configuration update"),
force_before_wizard ? _L("PrusaSlicer is not using the newest configuration available.\n"
"Configuration Wizard may not offer the latest printers, filaments and SLA materials to be installed. ") :
_L("Configuration update is available"), wxID_NONE)
{
auto *text = new wxStaticText(this, wxID_ANY, _(L(
"Would you like to install it?\n\n"
@ -130,11 +133,17 @@ MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates) :
content_sizer->Add(versions);
content_sizer->AddSpacer(2*VERT_SPACING);
auto *btn_cancel = new wxButton(this, wxID_CANCEL);
btn_sizer->Add(btn_cancel);
btn_sizer->AddSpacer(HORIZ_SPACING);
auto *btn_ok = new wxButton(this, wxID_OK);
auto* btn_ok = new wxButton(this, wxID_OK, force_before_wizard ? _L("Install") : "OK");
btn_sizer->Add(btn_ok);
btn_sizer->AddSpacer(HORIZ_SPACING);
if (force_before_wizard) {
auto* btn_no_install = new wxButton(this, wxID_ANY, _L("Don't install"));
btn_no_install->Bind(wxEVT_BUTTON, [this](wxEvent&) { this->EndModal(wxID_CLOSE); });
btn_sizer->Add(btn_no_install);
btn_sizer->AddSpacer(HORIZ_SPACING);
}
auto* btn_cancel = new wxButton(this, wxID_CANCEL);
btn_sizer->Add(btn_cancel);
btn_ok->SetFocus();
Fit();

View File

@ -54,7 +54,8 @@ public:
{}
};
MsgUpdateConfig(const std::vector<Update> &updates);
// force_before_wizard - indicates that check of updated is forced before ConfigWizard opening
MsgUpdateConfig(const std::vector<Update> &updates, bool force_before_wizard = false);
MsgUpdateConfig(MsgUpdateConfig &&) = delete;
MsgUpdateConfig(const MsgUpdateConfig &) = delete;
MsgUpdateConfig &operator=(MsgUpdateConfig &&) = delete;

View File

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

View File

@ -19,6 +19,7 @@ static HexFile::DeviceKind parse_device_kind(const std::string &str)
else if (str == "mk3") { return HexFile::DEV_MK3; }
else if (str == "mm-control") { return HexFile::DEV_MM_CONTROL; }
else if (str == "cw1") { return HexFile::DEV_CW1; }
else if (str == "cw1s") { return HexFile::DEV_CW1S; }
else { return HexFile::DEV_GENERIC; }
}

View File

@ -17,6 +17,7 @@ struct HexFile
DEV_MK3,
DEV_MM_CONTROL,
DEV_CW1,
DEV_CW1S,
};
boost::filesystem::path path;

View File

@ -178,7 +178,7 @@ const char* SL1Host::get_name() const { return "SL1Host"; }
wxString SL1Host::get_test_ok_msg () const
{
return wxString::Format(_L("Connection to %s works correctly."), "Prusa SL1");
return wxString::Format(_L("Connection to %s works correctly."), "Prusa SL1 / SL1S");
}
wxString SL1Host::get_test_failed_msg (wxString &msg) const
@ -209,4 +209,48 @@ void SL1Host::set_auth(Http &http) const
}
}
// PrusaLink
PrusaLink::PrusaLink(DynamicPrintConfig* config) :
OctoPrint(config),
authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
username(config->opt_string("printhost_user")),
password(config->opt_string("printhost_password"))
{
}
const char* PrusaLink::get_name() const { return "PrusaLink"; }
wxString PrusaLink::get_test_ok_msg() const
{
return _(L("Connection to PrusaLink works correctly."));
}
wxString PrusaLink::get_test_failed_msg(wxString& msg) const
{
return GUI::from_u8((boost::format("%s: %s")
% _utf8(L("Could not connect to PrusaLink"))
% std::string(msg.ToUTF8())).str());
}
bool PrusaLink::validate_version_text(const boost::optional<std::string>& version_text) const
{
return version_text ? (boost::starts_with(*version_text, "PrusaLink") || boost::starts_with(*version_text, "OctoPrint")) : false;
}
void PrusaLink::set_auth(Http& http) const
{
switch (authorization_type) {
case atKeyPassword:
http.header("X-Api-Key", get_apikey());
break;
case atUserPassword:
http.auth_digest(username, password);
break;
}
if (!get_cafile().empty()) {
http.ca_file(get_cafile());
}
}
}

View File

@ -70,6 +70,31 @@ private:
std::string password;
};
class PrusaLink : public OctoPrint
{
public:
PrusaLink(DynamicPrintConfig* config);
~PrusaLink() override = default;
const char* get_name() const override;
wxString get_test_ok_msg() const override;
wxString get_test_failed_msg(wxString& msg) const override;
bool can_start_print() const override { return true; }
protected:
bool validate_version_text(const boost::optional<std::string>& version_text) const override;
private:
void set_auth(Http& http) const override;
// Host authorization type.
AuthorizationType authorization_type;
// username and password for HTTP Digest Authentization (RFC RFC2617)
std::string username;
std::string password;
};
}
#endif

View File

@ -56,15 +56,18 @@ static const char *TMP_EXTENSION = ".download";
void copy_file_fix(const fs::path &source, const fs::path &target)
{
static const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; // aka 644
BOOST_LOG_TRIVIAL(debug) << format("PresetUpdater: Copying %1% -> %2%", source, target);
// Make sure the file has correct permission both before and after we copy over it
if (fs::exists(target)) {
fs::permissions(target, perms);
std::string error_message;
CopyFileResult cfr = copy_file(source.string(), target.string(), error_message, false);
if (cfr != CopyFileResult::SUCCESS) {
BOOST_LOG_TRIVIAL(error) << "Copying failed(" << cfr << "): " << error_message;
throw Slic3r::CriticalException(GUI::format(
_L("Copying of file %1% to %2% failed: %3%"),
source, target, error_message));
}
fs::copy_file(source, target, fs::copy_option::overwrite_if_exists);
// Permissions should be copied from the source file by copy_file(). We are not sure about the source
// permissions, let's rewrite them with 644.
static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;
fs::permissions(target, perms);
}
@ -168,7 +171,7 @@ struct PresetUpdater::priv
void check_install_indices() const;
Updates get_config_updates(const Semver& old_slic3r_version) const;
void perform_updates(Updates &&updates, bool snapshot = true) const;
bool perform_updates(Updates &&updates, bool snapshot = true) const;
void set_waiting_updates(Updates u);
};
@ -474,7 +477,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
}
if (recommended->config_version < vp.config_version) {
BOOST_LOG_TRIVIAL(warning) << format("Recommended config version for the currently running " SLIC3R_APP_NAME " is older than the currently installed config for vendor %1%. This should not happen.", idx.vendor());
BOOST_LOG_TRIVIAL(warning) << format("Recommended config version for the currently running %1% is older than the currently installed config for vendor %2%. This should not happen.", SLIC3R_APP_NAME, idx.vendor());
continue;
}
@ -545,7 +548,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
found = true;
} else {
BOOST_LOG_TRIVIAL(warning) << format("The recommended config version for vendor `%1%` in resources does not match the recommended\n"
" config version for this version of " SLIC3R_APP_NAME ". Corrupted installation?", idx.vendor());
" config version for this version of `%2%`. Corrupted installation?", idx.vendor(), SLIC3R_APP_NAME);
}
}
}
@ -598,12 +601,14 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
return updates;
}
void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const
bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const
{
if (updates.incompats.size() > 0) {
if (snapshot) {
BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
SnapshotDB::singleton().take_snapshot(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_DOWNGRADE);
if (! GUI::Config::take_config_snapshot_cancel_on_error(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_DOWNGRADE, "",
_u8L("Continue and install configuration updates?")))
return false;
}
BOOST_LOG_TRIVIAL(info) << format("Deleting %1% incompatible bundles", updates.incompats.size());
@ -618,7 +623,9 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
if (snapshot) {
BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
SnapshotDB::singleton().take_snapshot(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_UPGRADE);
if (! GUI::Config::take_config_snapshot_cancel_on_error(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_UPGRADE, "",
_u8L("Continue and install configuration updates?")))
return false;
}
BOOST_LOG_TRIVIAL(info) << format("Performing %1% updates", updates.updates.size());
@ -629,7 +636,8 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
update.install();
PresetBundle bundle;
bundle.load_configbundle(update.source.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM);
// Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
bundle.load_configbundle(update.source.string(), PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
BOOST_LOG_TRIVIAL(info) << format("Deleting %1% conflicting presets", bundle.prints.size() + bundle.filaments.size() + bundle.printers.size());
@ -661,6 +669,8 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); }
}
}
return true;
}
void PresetUpdater::priv::set_waiting_updates(Updates u)
@ -727,7 +737,20 @@ void PresetUpdater::slic3r_update_notify()
}
}
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, bool no_notification) const
static void reload_configs_update_gui()
{
// Reload global configuration
auto* app_config = GUI::wxGetApp().app_config;
// System profiles should not trigger any substitutions, user profiles may trigger substitutions, but these substitutions
// were already presented to the user on application start up. Just do substitutions now and keep quiet about it.
// However throw on substitutions in system profiles, those shall never happen with system profiles installed over the air.
GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem);
GUI::wxGetApp().load_current_presets();
GUI::wxGetApp().plater()->set_bed_shape();
GUI::wxGetApp().update_wizard_from_config();
}
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, UpdateParams params) const
{
if (! p->enabled_config_update) { return R_NOOP; }
@ -761,11 +784,9 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
// This effectively removes the incompatible bundles:
// (snapshot is taken beforehand)
p->perform_updates(std::move(updates));
if (!GUI::wxGetApp().run_wizard(GUI::ConfigWizard::RR_DATA_INCOMPAT)) {
if (! p->perform_updates(std::move(updates)) ||
! GUI::wxGetApp().run_wizard(GUI::ConfigWizard::RR_DATA_INCOMPAT))
return R_INCOMPAT_EXIT;
}
return R_INCOMPAT_CONFIGURED;
}
@ -784,7 +805,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
}
//forced update
if(incompatible_version)
if (incompatible_version)
{
BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. At least one requires higher version of Slicer.", updates.updates.size());
@ -799,14 +820,9 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
const auto res = dlg.ShowModal();
if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(info) << "User wants to update...";
p->perform_updates(std::move(updates));
// Reload global configuration
auto* app_config = GUI::wxGetApp().app_config;
GUI::wxGetApp().preset_bundle->load_presets(*app_config);
GUI::wxGetApp().load_current_presets();
GUI::wxGetApp().plater()->set_bed_shape();
if (! p->perform_updates(std::move(updates)))
return R_INCOMPAT_EXIT;
reload_configs_update_gui();
return R_UPDATE_INSTALLED;
}
else {
@ -816,36 +832,36 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
}
// regular update
if (no_notification) {
BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size());
std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
for (const auto& update : updates.updates) {
std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string();
updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url));
}
GUI::MsgUpdateConfig dlg(updates_msg);
const auto res = dlg.ShowModal();
if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
p->perform_updates(std::move(updates));
// Reload global configuration
auto* app_config = GUI::wxGetApp().app_config;
GUI::wxGetApp().preset_bundle->load_presets(*app_config);
GUI::wxGetApp().load_current_presets();
return R_UPDATE_INSTALLED;
}
else {
BOOST_LOG_TRIVIAL(info) << "User refused the update";
return R_UPDATE_REJECT;
}
} else {
if (params == UpdateParams::SHOW_NOTIFICATION) {
p->set_waiting_updates(updates);
GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAvailable);
}
else {
BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size());
std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
for (const auto& update : updates.updates) {
std::string changelog_url = update.version.config_version.prerelease() == nullptr ? update.changelog_url : std::string();
updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url));
}
GUI::MsgUpdateConfig dlg(updates_msg, params == UpdateParams::FORCED_BEFORE_WIZARD);
const auto res = dlg.ShowModal();
if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
if (! p->perform_updates(std::move(updates)))
return R_ALL_CANCELED;
reload_configs_update_gui();
return R_UPDATE_INSTALLED;
}
else {
BOOST_LOG_TRIVIAL(info) << "User refused the update";
if (params == UpdateParams::FORCED_BEFORE_WIZARD && res == wxID_CANCEL)
return R_ALL_CANCELED;
return R_UPDATE_REJECT;
}
}
// MsgUpdateConfig will show after the notificaation is clicked
} else {
@ -855,7 +871,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
return R_NOOP;
}
void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot) const
bool PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot) const
{
Updates updates;
@ -867,7 +883,7 @@ void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool
updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", "");
}
p->perform_updates(std::move(updates), snapshot);
return p->perform_updates(std::move(updates), snapshot);
}
void PresetUpdater::on_update_notification_confirm()
@ -887,20 +903,14 @@ void PresetUpdater::on_update_notification_confirm()
const auto res = dlg.ShowModal();
if (res == wxID_OK) {
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
p->perform_updates(std::move(p->waiting_updates));
// Reload global configuration
auto* app_config = GUI::wxGetApp().app_config;
GUI::wxGetApp().preset_bundle->load_presets(*app_config);
GUI::wxGetApp().load_current_presets();
p->has_waiting_updates = false;
//return R_UPDATE_INSTALLED;
if (p->perform_updates(std::move(p->waiting_updates))) {
reload_configs_update_gui();
p->has_waiting_updates = false;
}
}
else {
BOOST_LOG_TRIVIAL(info) << "User refused the update";
//return R_UPDATE_REJECT;
}
}
}
}

View File

@ -35,18 +35,24 @@ public:
R_INCOMPAT_CONFIGURED,
R_UPDATE_INSTALLED,
R_UPDATE_REJECT,
R_UPDATE_NOTIFICATION
R_UPDATE_NOTIFICATION,
R_ALL_CANCELED
};
enum class UpdateParams {
SHOW_TEXT_BOX, // force modal textbox
SHOW_NOTIFICATION, // only shows notification
FORCED_BEFORE_WIZARD // indicates that check of updated is forced before ConfigWizard opening
};
// If updating is enabled, check if updates are available in cache, if so, ask about installation.
// A false return value implies Slic3r should exit due to incompatibility of configuration.
// Providing old slic3r version upgrade profiles on upgrade of an application even in case
// that the config index installed from the Internet is equal to the index contained in the installation package.
// no_notification = force modal textbox, otherwise some cases only shows notification
UpdateResult config_update(const Semver &old_slic3r_version, bool no_notification) const;
UpdateResult config_update(const Semver &old_slic3r_version, UpdateParams params) const;
// "Update" a list of bundles from resources (behaves like an online update).
void install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
bool install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
void on_update_notification_confirm();
private:

View File

@ -60,6 +60,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
const auto host_type = opt != nullptr ? opt->value : htOctoPrint;
switch (host_type) {
case htPrusaLink: return new PrusaLink(config);
case htOctoPrint: return new OctoPrint(config);
case htDuet: return new Duet(config);
case htFlashAir: return new FlashAir(config);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,8 +18,6 @@
%code%{ RETVAL = &THIS->thin_fills; %};
Ref<SurfaceCollection> fill_surfaces()
%code%{ RETVAL = &THIS->fill_surfaces; %};
Polygons bridged()
%code%{ RETVAL = THIS->bridged; %};
Ref<ExtrusionEntityCollection> perimeters()
%code%{ RETVAL = &THIS->perimeters; %};
Ref<ExtrusionEntityCollection> fills()

View File

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