mirror of
https://git.mirrors.martin98.com/https://github.com/slic3r/Slic3r.git
synced 2025-08-12 02:48:59 +08:00
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:
commit
f87b5fccde
71
resources/icons/info.svg
Normal file
71
resources/icons/info.svg
Normal 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 |
71
resources/icons/white/info.svg
Normal file
71
resources/icons/white/info.svg
Normal 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 |
@ -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);
|
||||
|
@ -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 §ion : 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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -34,6 +34,7 @@ add_library(libslic3r STATIC
|
||||
EdgeGrid.hpp
|
||||
ElephantFootCompensation.cpp
|
||||
ElephantFootCompensation.hpp
|
||||
enum_bitmask.hpp
|
||||
ExPolygon.cpp
|
||||
ExPolygon.hpp
|
||||
ExPolygonCollection.cpp
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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>;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 = [§ion, &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 = [§ion, &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()
|
||||
|
@ -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_ */
|
||||
|
@ -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.");
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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__
|
||||
|
80
src/libslic3r/enum_bitmask.hpp
Normal file
80
src/libslic3r/enum_bitmask.hpp
Normal 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_
|
@ -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"
|
||||
|
@ -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 = """; break;
|
||||
case '\'': replacement = "'"; break;
|
||||
case '&': replacement = "&"; break;
|
||||
case '<': replacement = "<"; break;
|
||||
case '>': replacement = ">"; break;
|
||||
case '<': replacement = is_marked ? "<" :"<"; break;
|
||||
case '>': replacement = is_marked ? ">" :">"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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).
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -50,39 +50,8 @@ int GUI_Run(GUI_InitParams ¶ms)
|
||||
// gui->autosave = m_config.opt_string("autosave");
|
||||
GUI::GUI_App::SetInstance(gui);
|
||||
gui->init_params = ¶ms;
|
||||
/*
|
||||
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);
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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")
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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"));
|
||||
|
@ -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; }
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ struct HexFile
|
||||
DEV_MK3,
|
||||
DEV_MM_CONTROL,
|
||||
DEV_CW1,
|
||||
DEV_CW1S,
|
||||
};
|
||||
|
||||
boost::filesystem::path path;
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 },
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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 },
|
||||
|
@ -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 },
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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" }
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user