Merge branch 'master' into fs_QuadricEdgeCollapse
This commit is contained in:
commit
ed9152d004
107 changed files with 2066 additions and 808 deletions
|
@ -118,7 +118,8 @@ int CLI::run(int argc, char **argv)
|
|||
boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer");
|
||||
#endif // _WIN32
|
||||
|
||||
const std::vector<std::string> &load_configs = m_config.option<ConfigOptionStrings>("load", true)->values;
|
||||
const std::vector<std::string> &load_configs = m_config.option<ConfigOptionStrings>("load", true)->values;
|
||||
const ForwardCompatibilitySubstitutionRule config_substitution_rule = m_config.option<ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>>("config_compatibility", true)->value;
|
||||
|
||||
// load config files supplied via --load
|
||||
for (auto const &file : load_configs) {
|
||||
|
@ -130,13 +131,19 @@ int CLI::run(int argc, char **argv)
|
|||
return 1;
|
||||
}
|
||||
}
|
||||
DynamicPrintConfig config;
|
||||
DynamicPrintConfig config;
|
||||
ConfigSubstitutions config_substitutions;
|
||||
try {
|
||||
config.load(file);
|
||||
config_substitutions = config.load(file, config_substitution_rule);
|
||||
} catch (std::exception &ex) {
|
||||
boost::nowide::cerr << "Error while reading config file: " << ex.what() << std::endl;
|
||||
boost::nowide::cerr << "Error while reading config file \"" << file << "\": " << ex.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (! config_substitutions.empty()) {
|
||||
boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n";
|
||||
for (const ConfigSubstitution &subst : config_substitutions)
|
||||
boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n";
|
||||
}
|
||||
config.normalize_fdm();
|
||||
PrinterTechnology other_printer_technology = get_printer_technology(config);
|
||||
if (printer_technology == ptUnknown) {
|
||||
|
@ -174,7 +181,9 @@ int CLI::run(int argc, char **argv)
|
|||
try {
|
||||
// When loading an AMF or 3MF, config is imported as well, including the printer technology.
|
||||
DynamicPrintConfig config;
|
||||
model = Model::read_from_file(file, &config, true);
|
||||
ConfigSubstitutionContext config_substitutions(config_substitution_rule);
|
||||
//FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ?
|
||||
model = Model::read_from_file(file, &config, &config_substitutions, Model::LoadAttribute::AddDefaultInstances);
|
||||
PrinterTechnology other_printer_technology = get_printer_technology(config);
|
||||
if (printer_technology == ptUnknown) {
|
||||
printer_technology = other_printer_technology;
|
||||
|
@ -183,6 +192,11 @@ int CLI::run(int argc, char **argv)
|
|||
boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (! config_substitutions.substitutions.empty()) {
|
||||
boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n";
|
||||
for (const ConfigSubstitution& subst : config_substitutions.substitutions)
|
||||
boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n";
|
||||
}
|
||||
// config is applied to m_print_config before the current m_config values.
|
||||
config += std::move(m_print_config);
|
||||
m_print_config = std::move(config);
|
||||
|
@ -362,7 +376,7 @@ int CLI::run(int argc, char **argv)
|
|||
o->cut(Z, m_config.opt_float("cut"), &out);
|
||||
}
|
||||
#else
|
||||
model.objects.front()->cut(0, m_config.opt_float("cut"), true, true, true);
|
||||
model.objects.front()->cut(0, m_config.opt_float("cut"), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::FlipLower);
|
||||
#endif
|
||||
model.delete_object(size_t(0));
|
||||
}
|
||||
|
|
|
@ -501,8 +501,9 @@ inline P _Box<P>::center() const BP2D_NOEXCEPT {
|
|||
using Coord = TCoord<P>;
|
||||
|
||||
P ret = { // No rounding here, we dont know if these are int coords
|
||||
Coord( (getX(minc) + getX(maxc)) / Coord(2) ),
|
||||
Coord( (getY(minc) + getY(maxc)) / Coord(2) )
|
||||
// Doing the division like this increases the max range of x and y coord
|
||||
getX(minc) / Coord(2) + getX(maxc) / Coord(2),
|
||||
getY(minc) / Coord(2) + getY(maxc) / Coord(2)
|
||||
};
|
||||
|
||||
return ret;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "Exception.hpp"
|
||||
#include "LocalesUtils.hpp"
|
||||
#include "Thread.hpp"
|
||||
|
||||
#include "format.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
@ -18,15 +18,24 @@
|
|||
#include <boost/property_tree/ptree_fwd.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/format/format_fwd.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
//#include <wx/string.h>
|
||||
//#include "I18N.hpp"
|
||||
#ifdef WIN32
|
||||
//FIXME replace the two following includes with <boost/md5.hpp> after it becomes mainstream.
|
||||
#include <boost/uuid/detail/md5.hpp>
|
||||
#include <boost/algorithm/hex.hpp>
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
static const std::string VENDOR_PREFIX = "vendor:";
|
||||
static const std::string MODEL_PREFIX = "model:";
|
||||
static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version";
|
||||
// Because of a crash in PrusaSlicer 2.3.0/2.3.1 when showing an update notification with some locales, we don't want PrusaSlicer 2.3.0/2.3.1
|
||||
// to show this notification. On the other hand, we would like PrusaSlicer 2.3.2 to show an update notification of the upcoming PrusaSlicer 2.4.0.
|
||||
// Thus we will let PrusaSlicer 2.3.2 and couple of follow-up versions to download the version number from an alternate file until the PrusaSlicer 2.3.0/2.3.1
|
||||
// are phased out, then we will revert to the original name.
|
||||
//static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version";
|
||||
static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version2";
|
||||
|
||||
const std::string AppConfig::SECTION_FILAMENTS = "filaments";
|
||||
const std::string AppConfig::SECTION_MATERIALS = "sla_materials";
|
||||
|
@ -177,25 +186,114 @@ void AppConfig::set_defaults()
|
|||
erase("", "object_settings_size");
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
static std::string appconfig_md5_hash_line(const std::string_view data)
|
||||
{
|
||||
//FIXME replace the two following includes with <boost/md5.hpp> after it becomes mainstream.
|
||||
// return boost::md5(data).hex_str_value();
|
||||
// boost::uuids::detail::md5 is an internal namespace thus it may change in the future.
|
||||
// Also this implementation is not the fastest, it was designed for short blocks of text.
|
||||
using boost::uuids::detail::md5;
|
||||
md5 md5_hash;
|
||||
// unsigned int[4], 128 bits
|
||||
md5::digest_type md5_digest{};
|
||||
std::string md5_digest_str;
|
||||
md5_hash.process_bytes(data.data(), data.size());
|
||||
md5_hash.get_digest(md5_digest);
|
||||
boost::algorithm::hex(md5_digest, md5_digest + std::size(md5_digest), std::back_inserter(md5_digest_str));
|
||||
// MD5 hash is 32 HEX digits long.
|
||||
assert(md5_digest_str.size() == 32);
|
||||
// This line will be emited at the end of the file.
|
||||
return "# MD5 checksum " + md5_digest_str + "\n";
|
||||
};
|
||||
|
||||
// Assume that the last line with the comment inside the config file contains a checksum and that the user didn't modify the config file.
|
||||
static bool verify_config_file_checksum(boost::nowide::ifstream &ifs)
|
||||
{
|
||||
auto read_whole_config_file = [&ifs]() -> std::string {
|
||||
std::stringstream ss;
|
||||
ss << ifs.rdbuf();
|
||||
return ss.str();
|
||||
};
|
||||
|
||||
ifs.seekg(0, boost::nowide::ifstream::beg);
|
||||
std::string whole_config = read_whole_config_file();
|
||||
|
||||
// The checksum should be on the last line in the config file.
|
||||
if (size_t last_comment_pos = whole_config.find_last_of('#'); last_comment_pos != std::string::npos) {
|
||||
// Split read config into two parts, one with checksum, and the second part is part with configuration from the checksum was computed.
|
||||
// Verify existence and validity of the MD5 checksum line at the end of the file.
|
||||
// When the checksum isn't found, the checksum was not saved correctly, it was removed or it is an older config file without the checksum.
|
||||
// If the checksum is incorrect, then the file was either not saved correctly or modified.
|
||||
if (std::string_view(whole_config.c_str() + last_comment_pos, whole_config.size() - last_comment_pos) == appconfig_md5_hash_line({ whole_config.data(), last_comment_pos }))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string AppConfig::load()
|
||||
{
|
||||
// 1) Read the complete config file into a boost::property_tree.
|
||||
namespace pt = boost::property_tree;
|
||||
pt::ptree tree;
|
||||
boost::nowide::ifstream ifs(AppConfig::config_path());
|
||||
boost::nowide::ifstream ifs;
|
||||
bool recovered = false;
|
||||
|
||||
try {
|
||||
ifs.open(AppConfig::config_path());
|
||||
#ifdef WIN32
|
||||
// Verify the checksum of the config file without taking just for debugging purpose.
|
||||
if (!verify_config_file_checksum(ifs))
|
||||
BOOST_LOG_TRIVIAL(info) << "The configuration file " << AppConfig::config_path() <<
|
||||
" has a wrong MD5 checksum or the checksum is missing. This may indicate a file corruption or a harmless user edit.";
|
||||
|
||||
ifs.seekg(0, boost::nowide::ifstream::beg);
|
||||
#endif
|
||||
pt::read_ini(ifs, tree);
|
||||
} catch (pt::ptree_error& ex) {
|
||||
// Error while parsing config file. We'll customize the error message and rethrow to be displayed.
|
||||
// ! But to avoid the use of _utf8 (related to use of wxWidgets)
|
||||
// we will rethrow this exception from the place of load() call, if returned value wouldn't be empty
|
||||
/*
|
||||
throw Slic3r::RuntimeError(
|
||||
_utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. "
|
||||
"Try to manually delete the file to recover from the error. Your user profiles will not be affected.")) +
|
||||
"\n\n" + AppConfig::config_path() + "\n\n" + ex.what());
|
||||
*/
|
||||
return ex.what();
|
||||
#ifdef WIN32
|
||||
// The configuration file is corrupted, try replacing it with the backup configuration.
|
||||
ifs.close();
|
||||
std::string backup_path = (boost::format("%1%.bak") % AppConfig::config_path()).str();
|
||||
if (boost::filesystem::exists(backup_path)) {
|
||||
// Compute checksum of the configuration backup file and try to load configuration from it when the checksum is correct.
|
||||
boost::nowide::ifstream backup_ifs(backup_path);
|
||||
if (!verify_config_file_checksum(backup_ifs)) {
|
||||
BOOST_LOG_TRIVIAL(error) << format("Both \"%1%\" and \"%2%\" are corrupted. It isn't possible to restore configuration from the backup.", AppConfig::config_path(), backup_path);
|
||||
backup_ifs.close();
|
||||
boost::filesystem::remove(backup_path);
|
||||
} else if (std::string error_message; copy_file(backup_path, AppConfig::config_path(), error_message, false) != SUCCESS) {
|
||||
BOOST_LOG_TRIVIAL(error) << format("Configuration file \"%1%\" is corrupted. Failed to restore from backup \"%2%\": %3%", AppConfig::config_path(), backup_path, error_message);
|
||||
backup_ifs.close();
|
||||
boost::filesystem::remove(backup_path);
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << format("Configuration file \"%1%\" was corrupted. It has been succesfully restored from the backup \"%2%\".", AppConfig::config_path(), backup_path);
|
||||
// Try parse configuration file after restore from backup.
|
||||
try {
|
||||
ifs.open(AppConfig::config_path());
|
||||
pt::read_ini(ifs, tree);
|
||||
recovered = true;
|
||||
} catch (pt::ptree_error& ex) {
|
||||
BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\" after it has been restored from backup: %2%", AppConfig::config_path(), ex.what());
|
||||
}
|
||||
}
|
||||
} else
|
||||
#endif // WIN32
|
||||
BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\": %2%", AppConfig::config_path(), ex.what());
|
||||
if (! recovered) {
|
||||
// Report the initial error of parsing PrusaSlicer.ini.
|
||||
// Error while parsing config file. We'll customize the error message and rethrow to be displayed.
|
||||
// ! But to avoid the use of _utf8 (related to use of wxWidgets)
|
||||
// we will rethrow this exception from the place of load() call, if returned value wouldn't be empty
|
||||
/*
|
||||
throw Slic3r::RuntimeError(
|
||||
_utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. "
|
||||
"Try to manually delete the file to recover from the error. Your user profiles will not be affected.")) +
|
||||
"\n\n" + AppConfig::config_path() + "\n\n" + ex.what());
|
||||
*/
|
||||
return ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Parse the property_tree, extract the sections and key / value pairs.
|
||||
|
@ -272,22 +370,21 @@ void AppConfig::save()
|
|||
const auto path = config_path();
|
||||
std::string path_pid = (boost::format("%1%.%2%") % path % get_current_pid()).str();
|
||||
|
||||
boost::nowide::ofstream c;
|
||||
c.open(path_pid, std::ios::out | std::ios::trunc);
|
||||
std::stringstream config_ss;
|
||||
if (m_mode == EAppMode::Editor)
|
||||
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
|
||||
config_ss << "# " << Slic3r::header_slic3r_generated() << std::endl;
|
||||
else
|
||||
c << "# " << Slic3r::header_gcodeviewer_generated() << std::endl;
|
||||
config_ss << "# " << Slic3r::header_gcodeviewer_generated() << std::endl;
|
||||
// Make sure the "no" category is written first.
|
||||
for (const auto& kvp : m_storage[""])
|
||||
c << kvp.first << " = " << kvp.second << std::endl;
|
||||
config_ss << kvp.first << " = " << kvp.second << std::endl;
|
||||
// Write the other categories.
|
||||
for (const auto& category : m_storage) {
|
||||
if (category.first.empty())
|
||||
continue;
|
||||
c << std::endl << "[" << category.first << "]" << std::endl;
|
||||
config_ss << std::endl << "[" << category.first << "]" << std::endl;
|
||||
for (const auto& kvp : category.second)
|
||||
c << kvp.first << " = " << kvp.second << std::endl;
|
||||
config_ss << kvp.first << " = " << kvp.second << std::endl;
|
||||
}
|
||||
// Write vendor sections
|
||||
for (const auto &vendor : m_vendors) {
|
||||
|
@ -295,17 +392,42 @@ void AppConfig::save()
|
|||
for (const auto &model : vendor.second) { size_sum += model.second.size(); }
|
||||
if (size_sum == 0) { continue; }
|
||||
|
||||
c << std::endl << "[" << VENDOR_PREFIX << vendor.first << "]" << std::endl;
|
||||
config_ss << std::endl << "[" << VENDOR_PREFIX << vendor.first << "]" << std::endl;
|
||||
|
||||
for (const auto &model : vendor.second) {
|
||||
if (model.second.size() == 0) { continue; }
|
||||
if (model.second.empty()) { continue; }
|
||||
const std::vector<std::string> variants(model.second.begin(), model.second.end());
|
||||
const auto escaped = escape_strings_cstyle(variants);
|
||||
c << MODEL_PREFIX << model.first << " = " << escaped << std::endl;
|
||||
config_ss << MODEL_PREFIX << model.first << " = " << escaped << std::endl;
|
||||
}
|
||||
}
|
||||
c.close();
|
||||
// One empty line before the MD5 sum.
|
||||
config_ss << std::endl;
|
||||
|
||||
std::string config_str = config_ss.str();
|
||||
boost::nowide::ofstream c;
|
||||
c.open(path_pid, std::ios::out | std::ios::trunc);
|
||||
c << config_str;
|
||||
#ifdef WIN32
|
||||
// WIN32 specific: The final "rename_file()" call is not safe in case of an application crash, there is no atomic "rename file" API
|
||||
// provided by Windows (sic!). Therefore we save a MD5 checksum to be able to verify file corruption. In addition,
|
||||
// we save the config file into a backup first before moving it to the final destination.
|
||||
c << appconfig_md5_hash_line(config_str);
|
||||
#endif
|
||||
c.close();
|
||||
|
||||
#ifdef WIN32
|
||||
// Make a backup of the configuration file before copying it to the final destination.
|
||||
std::string error_message;
|
||||
std::string backup_path = (boost::format("%1%.bak") % path).str();
|
||||
// Copy configuration file with PID suffix into the configuration file with "bak" suffix.
|
||||
if (copy_file(path_pid, backup_path, error_message, false) != SUCCESS)
|
||||
BOOST_LOG_TRIVIAL(error) << "Copying from " << path_pid << " to " << backup_path << " failed. Failed to create a backup configuration.";
|
||||
#endif
|
||||
|
||||
// Rename the config atomically.
|
||||
// On Windows, the rename is likely NOT atomic, thus it may fail if PrusaSlicer crashes on another thread in the meanwhile.
|
||||
// To cope with that, we already made a backup of the config on Windows.
|
||||
rename_file(path_pid, path);
|
||||
m_dirty = false;
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ public:
|
|||
|
||||
// Load the slic3r.ini from a user profile directory (or a datadir, if configured).
|
||||
// return error string or empty strinf
|
||||
std::string load();
|
||||
std::string load();
|
||||
// Store the slic3r.ini into a user profile directory (or a datadir, if configured).
|
||||
void save();
|
||||
|
||||
|
@ -63,12 +63,12 @@ public:
|
|||
{ std::string value; this->get("", key, value); return value; }
|
||||
void set(const std::string §ion, const std::string &key, const std::string &value)
|
||||
{
|
||||
#ifndef _NDEBUG
|
||||
#ifndef NDEBUG
|
||||
std::string key_trimmed = key;
|
||||
boost::trim_all(key_trimmed);
|
||||
assert(key_trimmed == key);
|
||||
assert(! key_trimmed.empty());
|
||||
#endif // _NDEBUG
|
||||
#endif // NDEBUG
|
||||
std::string &old = m_storage[section][key];
|
||||
if (old != value) {
|
||||
old = value;
|
||||
|
|
|
@ -33,6 +33,7 @@ add_library(libslic3r STATIC
|
|||
EdgeGrid.hpp
|
||||
ElephantFootCompensation.cpp
|
||||
ElephantFootCompensation.hpp
|
||||
enum_bitmask.hpp
|
||||
ExPolygon.cpp
|
||||
ExPolygon.hpp
|
||||
ExPolygonCollection.cpp
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
#include <boost/format.hpp>
|
||||
#include <string.h>
|
||||
|
||||
//FIXME for GCodeFlavor and gcfMarlin (for forward-compatibility conversion)
|
||||
// This is not nice, likely it would be better to pass the ConfigSubstitutionContext to handle_legacy().
|
||||
#include "PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Escape \n, \r and backslash
|
||||
|
@ -211,6 +215,10 @@ std::string escape_ampersand(const std::string& str)
|
|||
return std::string(out.data(), outptr - out.data());
|
||||
}
|
||||
|
||||
void ConfigOptionDeleter::operator()(ConfigOption* p) {
|
||||
delete p;
|
||||
}
|
||||
|
||||
std::vector<std::string> ConfigOptionDef::cli_args(const std::string &key) const
|
||||
{
|
||||
std::vector<std::string> args;
|
||||
|
@ -361,7 +369,8 @@ std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, s
|
|||
|
||||
// right: option description
|
||||
std::string descr = def.tooltip;
|
||||
if (show_defaults && def.default_value && def.type != coBool
|
||||
bool show_defaults_this = show_defaults || def.opt_key == "config_compatibility";
|
||||
if (show_defaults_this && def.default_value && def.type != coBool
|
||||
&& (def.type != coString || !def.default_value->serialize().empty())) {
|
||||
descr += " (";
|
||||
if (!def.sidetext.empty()) {
|
||||
|
@ -469,7 +478,7 @@ void ConfigBase::set(const std::string &opt_key, double value, bool create)
|
|||
}
|
||||
}
|
||||
|
||||
bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append)
|
||||
bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions_ctxt, bool append)
|
||||
{
|
||||
t_config_option_key opt_key = opt_key_src;
|
||||
std::string value = value_src;
|
||||
|
@ -479,29 +488,29 @@ bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src,
|
|||
if (opt_key.empty())
|
||||
// Ignore the option.
|
||||
return true;
|
||||
return this->set_deserialize_raw(opt_key, value, append);
|
||||
return this->set_deserialize_raw(opt_key, value, substitutions_ctxt, append);
|
||||
}
|
||||
|
||||
void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, bool append)
|
||||
void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions_ctxt, bool append)
|
||||
{
|
||||
if (! this->set_deserialize_nothrow(opt_key_src, value_src, append))
|
||||
if (! this->set_deserialize_nothrow(opt_key_src, value_src, substitutions_ctxt, append))
|
||||
throw BadOptionTypeException(format("ConfigBase::set_deserialize() failed for parameter \"%1%\", value \"%2%\"", opt_key_src, value_src));
|
||||
}
|
||||
|
||||
void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items)
|
||||
void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions_ctxt)
|
||||
{
|
||||
for (const SetDeserializeItem &item : items)
|
||||
this->set_deserialize(item.opt_key, item.opt_value, item.append);
|
||||
this->set_deserialize(item.opt_key, item.opt_value, substitutions_ctxt, item.append);
|
||||
}
|
||||
|
||||
bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, bool append)
|
||||
bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, ConfigSubstitutionContext& substitutions_ctxt, bool append)
|
||||
{
|
||||
t_config_option_key opt_key = opt_key_src;
|
||||
t_config_option_key opt_key = opt_key_src;
|
||||
// Try to deserialize the option by its name.
|
||||
const ConfigDef *def = this->def();
|
||||
const ConfigDef *def = this->def();
|
||||
if (def == nullptr)
|
||||
throw NoDefinitionException(opt_key);
|
||||
const ConfigOptionDef *optdef = def->get(opt_key);
|
||||
const ConfigOptionDef *optdef = def->get(opt_key);
|
||||
if (optdef == nullptr) {
|
||||
// If we didn't find an option, look for any other option having this as an alias.
|
||||
for (const auto &opt : def->options) {
|
||||
|
@ -523,14 +532,35 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con
|
|||
// Aliasing for example "solid_layers" to "top_solid_layers" and "bottom_solid_layers".
|
||||
for (const t_config_option_key &shortcut : optdef->shortcut)
|
||||
// Recursive call.
|
||||
if (! this->set_deserialize_raw(shortcut, value, append))
|
||||
if (! this->set_deserialize_raw(shortcut, value, substitutions_ctxt, append))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
ConfigOption *opt = this->option(opt_key, true);
|
||||
assert(opt != nullptr);
|
||||
return opt->deserialize(value, append);
|
||||
bool success = opt->deserialize(value, append);
|
||||
if (! success && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable &&
|
||||
// Only allow substitutions of an enum value by another enum value or a boolean value with an enum value.
|
||||
// That means, we expect enum values being added in the future and possibly booleans being converted to enums.
|
||||
(optdef->type == coEnum || optdef->type == coBool))
|
||||
{
|
||||
// Deserialize failed, try to substitute with a default value.
|
||||
assert(substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
|
||||
opt->set(optdef->default_value.get());
|
||||
|
||||
if (substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable) {
|
||||
// Log the substitution.
|
||||
ConfigSubstitution config_substitution;
|
||||
config_substitution.opt_def = optdef;
|
||||
config_substitution.old_value = value;//std::unique_ptr<ConfigOption>(opt);
|
||||
config_substitution.new_value = ConfigOptionUniquePtr(this->option(opt_key, true)->clone());
|
||||
substitutions_ctxt.substitutions.emplace_back(std::move(config_substitution));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
// Return an absolute value of a possibly relative config variable.
|
||||
|
@ -589,36 +619,37 @@ void ConfigBase::setenv_() const
|
|||
}
|
||||
}
|
||||
|
||||
void ConfigBase::load(const std::string &file)
|
||||
ConfigSubstitutions ConfigBase::load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
if (is_gcode_file(file))
|
||||
this->load_from_gcode_file(file);
|
||||
else
|
||||
this->load_from_ini(file);
|
||||
return is_gcode_file(file) ?
|
||||
this->load_from_gcode_file(file, true /* check header */, compatibility_rule) :
|
||||
this->load_from_ini(file, compatibility_rule);
|
||||
}
|
||||
|
||||
void ConfigBase::load_from_ini(const std::string &file)
|
||||
ConfigSubstitutions ConfigBase::load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
boost::property_tree::ptree tree;
|
||||
boost::nowide::ifstream ifs(file);
|
||||
boost::property_tree::read_ini(ifs, tree);
|
||||
this->load(tree);
|
||||
return this->load(tree, compatibility_rule);
|
||||
}
|
||||
|
||||
void ConfigBase::load(const boost::property_tree::ptree &tree)
|
||||
ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
|
||||
for (const boost::property_tree::ptree::value_type &v : tree) {
|
||||
try {
|
||||
t_config_option_key opt_key = v.first;
|
||||
this->set_deserialize(opt_key, v.second.get_value<std::string>());
|
||||
this->set_deserialize(opt_key, v.second.get_value<std::string>(), substitutions_ctxt);
|
||||
} catch (UnknownOptionException & /* e */) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return std::move(substitutions_ctxt.substitutions);
|
||||
}
|
||||
|
||||
// Load the config keys from the tail of a G-code file.
|
||||
void ConfigBase::load_from_gcode_file(const std::string& file, bool check_header)
|
||||
ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, bool check_header, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
// Read a 64k block from the end of the G-code.
|
||||
boost::nowide::ifstream ifs(file);
|
||||
|
@ -639,13 +670,15 @@ void ConfigBase::load_from_gcode_file(const std::string& file, bool check_header
|
|||
ifs.read(data.data(), data_length);
|
||||
ifs.close();
|
||||
|
||||
size_t key_value_pairs = load_from_gcode_string(data.data());
|
||||
ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
|
||||
size_t key_value_pairs = load_from_gcode_string(data.data(), substitutions_ctxt);
|
||||
if (key_value_pairs < 80)
|
||||
throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs));
|
||||
return std::move(substitutions_ctxt.substitutions);
|
||||
}
|
||||
|
||||
// Load the config keys from the given string.
|
||||
size_t ConfigBase::load_from_gcode_string(const char* str)
|
||||
size_t ConfigBase::load_from_gcode_string(const char* str, ConfigSubstitutionContext& substitutions)
|
||||
{
|
||||
if (str == nullptr)
|
||||
return 0;
|
||||
|
@ -690,7 +723,7 @@ size_t ConfigBase::load_from_gcode_string(const char* str)
|
|||
if (key == nullptr)
|
||||
break;
|
||||
try {
|
||||
this->set_deserialize(std::string(key, key_end), std::string(value, end));
|
||||
this->set_deserialize(std::string(key, key_end), std::string(value, end), substitutions);
|
||||
++num_key_value_pairs;
|
||||
}
|
||||
catch (UnknownOptionException & /* e */) {
|
||||
|
@ -719,7 +752,7 @@ void ConfigBase::null_nullables()
|
|||
ConfigOption *opt = this->optptr(opt_key, false);
|
||||
assert(opt != nullptr);
|
||||
if (opt->nullable())
|
||||
opt->deserialize("nil");
|
||||
opt->deserialize("nil", ForwardCompatibilitySubstitutionRule::Disable);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -883,8 +916,10 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
|
|||
// Do not unescape single string values, the unescaping is left to the calling shell.
|
||||
static_cast<ConfigOptionString*>(opt_base)->value = value;
|
||||
} else {
|
||||
// Just bail out if the configuration value is not understood.
|
||||
ConfigSubstitutionContext context(ForwardCompatibilitySubstitutionRule::Disable);
|
||||
// Any scalar value of a type different from Bool and String.
|
||||
if (! this->set_deserialize_nothrow(opt_key, value, false)) {
|
||||
if (! this->set_deserialize_nothrow(opt_key, value, context, false)) {
|
||||
boost::nowide::cerr << "Invalid value supplied for --" << token.c_str() << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "Exception.hpp"
|
||||
#include "Point.hpp"
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <boost/format/format_fwd.hpp>
|
||||
#include <boost/functional/hash.hpp>
|
||||
|
@ -163,6 +164,41 @@ enum PrinterTechnology : unsigned char
|
|||
ptAny
|
||||
};
|
||||
|
||||
enum ForwardCompatibilitySubstitutionRule
|
||||
{
|
||||
Disable,
|
||||
Enable,
|
||||
EnableSilent,
|
||||
};
|
||||
|
||||
class ConfigOption;
|
||||
class ConfigOptionDef;
|
||||
// For forward definition of ConfigOption in ConfigOptionUniquePtr, we have to define a custom deleter.
|
||||
struct ConfigOptionDeleter { void operator()(ConfigOption* p); };
|
||||
using ConfigOptionUniquePtr = std::unique_ptr<ConfigOption, ConfigOptionDeleter>;
|
||||
|
||||
// When parsing a configuration value, if the old_value is not understood by this PrusaSlicer version,
|
||||
// it is being substituted with some default value that this PrusaSlicer could work with.
|
||||
// This structure serves to inform the user about the substitutions having been done during file import.
|
||||
struct ConfigSubstitution {
|
||||
const ConfigOptionDef *opt_def { nullptr };
|
||||
std::string old_value;
|
||||
ConfigOptionUniquePtr new_value;
|
||||
};
|
||||
|
||||
using ConfigSubstitutions = std::vector<ConfigSubstitution>;
|
||||
|
||||
// Filled in by ConfigBase::set_deserialize_raw(), which based on "rule" either bails out
|
||||
// or performs substitutions when encountering an unknown configuration value.
|
||||
struct ConfigSubstitutionContext
|
||||
{
|
||||
ConfigSubstitutionContext(ForwardCompatibilitySubstitutionRule rl) : rule(rl) {}
|
||||
bool empty() const throw() { return substitutions.empty(); }
|
||||
|
||||
ForwardCompatibilitySubstitutionRule rule;
|
||||
ConfigSubstitutions substitutions;
|
||||
};
|
||||
|
||||
// A generic value of a configuration option.
|
||||
class ConfigOption {
|
||||
public:
|
||||
|
@ -768,7 +804,7 @@ public:
|
|||
return escape_string_cstyle(this->value);
|
||||
}
|
||||
|
||||
bool deserialize(const std::string &str, bool append = false) override
|
||||
bool deserialize(const std::string &str, bool append = false) override
|
||||
{
|
||||
UNUSED(append);
|
||||
return unescape_string_cstyle(str, this->value);
|
||||
|
@ -1272,8 +1308,15 @@ public:
|
|||
bool deserialize(const std::string &str, bool append = false) override
|
||||
{
|
||||
UNUSED(append);
|
||||
this->value = (str.compare("1") == 0);
|
||||
return true;
|
||||
if (str == "1" || boost::iequals(str, "enabled") || boost::iequals(str, "on")) {
|
||||
this->value = true;
|
||||
return true;
|
||||
}
|
||||
if (str == "0" || boost::iequals(str, "disabled") || boost::iequals(str, "off")) {
|
||||
this->value = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -1687,6 +1730,14 @@ public:
|
|||
static const constexpr char *nocli = "~~~noCLI";
|
||||
};
|
||||
|
||||
inline bool operator<(const ConfigSubstitution &lhs, const ConfigSubstitution &rhs) throw() {
|
||||
return lhs.opt_def->opt_key < rhs.opt_def->opt_key ||
|
||||
(lhs.opt_def->opt_key == rhs.opt_def->opt_key && lhs.old_value < rhs.old_value);
|
||||
}
|
||||
inline bool operator==(const ConfigSubstitution &lhs, const ConfigSubstitution &rhs) throw() {
|
||||
return lhs.opt_def == rhs.opt_def && lhs.old_value == rhs.old_value;
|
||||
}
|
||||
|
||||
// Map from a config option name to its definition.
|
||||
// The definition does not carry an actual value of the config option, only its constant default value.
|
||||
// t_config_option_key is std::string
|
||||
|
@ -1765,6 +1816,8 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// An abstract configuration store.
|
||||
class ConfigBase : public ConfigOptionResolver
|
||||
{
|
||||
|
@ -1853,9 +1906,11 @@ public:
|
|||
|
||||
// Set a configuration value from a string, it will call an overridable handle_legacy()
|
||||
// to resolve renamed and removed configuration keys.
|
||||
bool set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append = false);
|
||||
bool set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions, bool append = false);
|
||||
// May throw BadOptionTypeException() if the operation fails.
|
||||
void set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false);
|
||||
void set_deserialize(const t_config_option_key &opt_key, const std::string &str, ConfigSubstitutionContext& config_substitutions, bool append = false);
|
||||
void set_deserialize_strict(const t_config_option_key &opt_key, const std::string &str, bool append = false)
|
||||
{ ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; this->set_deserialize(opt_key, str, ctxt, append); }
|
||||
struct SetDeserializeItem {
|
||||
SetDeserializeItem(const char *opt_key, const char *opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {}
|
||||
SetDeserializeItem(const std::string &opt_key, const std::string &opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {}
|
||||
|
@ -1870,17 +1925,19 @@ public:
|
|||
std::string opt_key; std::string opt_value; bool append = false;
|
||||
};
|
||||
// May throw BadOptionTypeException() if the operation fails.
|
||||
void set_deserialize(std::initializer_list<SetDeserializeItem> items);
|
||||
void set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions);
|
||||
void set_deserialize_strict(std::initializer_list<SetDeserializeItem> items)
|
||||
{ ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; this->set_deserialize(items, ctxt); }
|
||||
|
||||
double get_abs_value(const t_config_option_key &opt_key) const;
|
||||
double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const;
|
||||
void setenv_() const;
|
||||
void load(const std::string &file);
|
||||
void load_from_ini(const std::string &file);
|
||||
void load_from_gcode_file(const std::string& file, bool check_header = true);
|
||||
ConfigSubstitutions load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
ConfigSubstitutions load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
ConfigSubstitutions load_from_gcode_file(const std::string &file, bool check_header /* = true */, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
// Returns number of key/value pairs extracted.
|
||||
size_t load_from_gcode_string(const char* str);
|
||||
void load(const boost::property_tree::ptree &tree);
|
||||
size_t load_from_gcode_string(const char* str, ConfigSubstitutionContext& substitutions);
|
||||
ConfigSubstitutions load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
void save(const std::string &file) const;
|
||||
|
||||
// Set all the nullable values to nils.
|
||||
|
@ -1888,7 +1945,7 @@ public:
|
|||
|
||||
private:
|
||||
// Set a configuration value from a string.
|
||||
bool set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &str, bool append);
|
||||
bool set_deserialize_raw(const t_config_option_key& opt_key_src, const std::string& value, ConfigSubstitutionContext& substitutions, bool append);
|
||||
};
|
||||
|
||||
// Configuration store with dynamic number of configuration values.
|
||||
|
|
|
@ -419,7 +419,7 @@ namespace Slic3r {
|
|||
_3MF_Importer();
|
||||
~_3MF_Importer();
|
||||
|
||||
bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, bool check_version);
|
||||
bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version);
|
||||
|
||||
private:
|
||||
void _destroy_xml_parser();
|
||||
|
@ -434,16 +434,16 @@ namespace Slic3r {
|
|||
XML_ErrorString(XML_GetErrorCode(m_xml_parser));
|
||||
}
|
||||
|
||||
bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config);
|
||||
bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions);
|
||||
bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
|
||||
void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
|
||||
void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
|
||||
void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename);
|
||||
void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename);
|
||||
bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
|
||||
|
||||
// handlers to parse the .model file
|
||||
|
@ -510,7 +510,7 @@ namespace Slic3r {
|
|||
bool _handle_start_config_metadata(const char** attributes, unsigned int num_attributes);
|
||||
bool _handle_end_config_metadata();
|
||||
|
||||
bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes);
|
||||
bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions);
|
||||
|
||||
// callbacks to parse the .model file
|
||||
static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes);
|
||||
|
@ -539,7 +539,7 @@ namespace Slic3r {
|
|||
_destroy_xml_parser();
|
||||
}
|
||||
|
||||
bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, bool check_version)
|
||||
bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version)
|
||||
{
|
||||
m_version = 0;
|
||||
m_check_version = check_version;
|
||||
|
@ -560,7 +560,7 @@ namespace Slic3r {
|
|||
m_curr_characters.clear();
|
||||
clear_errors();
|
||||
|
||||
return _load_model_from_file(filename, model, config);
|
||||
return _load_model_from_file(filename, model, config, config_substitutions);
|
||||
}
|
||||
|
||||
void _3MF_Importer::_destroy_xml_parser()
|
||||
|
@ -581,7 +581,7 @@ namespace Slic3r {
|
|||
XML_StopParser(m_xml_parser, false);
|
||||
}
|
||||
|
||||
bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config)
|
||||
bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions)
|
||||
{
|
||||
mz_zip_archive archive;
|
||||
mz_zip_zero_struct(&archive);
|
||||
|
@ -635,7 +635,7 @@ namespace Slic3r {
|
|||
}
|
||||
else if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) {
|
||||
// extract slic3r layer config ranges file
|
||||
_extract_layer_config_ranges_from_archive(archive, stat);
|
||||
_extract_layer_config_ranges_from_archive(archive, stat, config_substitutions);
|
||||
}
|
||||
else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) {
|
||||
// extract sla support points file
|
||||
|
@ -647,7 +647,7 @@ namespace Slic3r {
|
|||
}
|
||||
else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) {
|
||||
// extract slic3r print config file
|
||||
_extract_print_config_from_archive(archive, stat, config, filename);
|
||||
_extract_print_config_from_archive(archive, stat, config, config_substitutions, filename);
|
||||
}
|
||||
else if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE)) {
|
||||
// extract slic3r layer config ranges file
|
||||
|
@ -704,7 +704,7 @@ namespace Slic3r {
|
|||
new_model_object->clear_instances();
|
||||
new_model_object->add_instance(*model_object->instances.back());
|
||||
model_object->delete_last_instance();
|
||||
if (!_generate_volumes(*new_model_object, *geometry, volumes))
|
||||
if (!_generate_volumes(*new_model_object, *geometry, volumes, config_substitutions))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -759,7 +759,7 @@ namespace Slic3r {
|
|||
if (metadata.key == "name")
|
||||
model_object->name = metadata.value;
|
||||
else
|
||||
model_object->config.set_deserialize(metadata.key, metadata.value);
|
||||
model_object->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
|
||||
}
|
||||
|
||||
// select object's detected volumes
|
||||
|
@ -775,7 +775,7 @@ namespace Slic3r {
|
|||
volumes_ptr = &volumes;
|
||||
}
|
||||
|
||||
if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr))
|
||||
if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr, config_substitutions))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -867,7 +867,10 @@ namespace Slic3r {
|
|||
return true;
|
||||
}
|
||||
|
||||
void _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename)
|
||||
void _3MF_Importer::_extract_print_config_from_archive(
|
||||
mz_zip_archive& archive, const mz_zip_archive_file_stat& stat,
|
||||
DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions,
|
||||
const std::string& archive_filename)
|
||||
{
|
||||
if (stat.m_uncomp_size > 0) {
|
||||
std::string buffer((size_t)stat.m_uncomp_size, 0);
|
||||
|
@ -876,7 +879,7 @@ namespace Slic3r {
|
|||
add_error("Error while reading config data to buffer");
|
||||
return;
|
||||
}
|
||||
config.load_from_gcode_string(buffer.data());
|
||||
config.load_from_gcode_string(buffer.data(), config_substitutions);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -942,7 +945,7 @@ namespace Slic3r {
|
|||
}
|
||||
}
|
||||
|
||||
void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
|
||||
void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions)
|
||||
{
|
||||
if (stat.m_uncomp_size > 0) {
|
||||
std::string buffer((size_t)stat.m_uncomp_size, 0);
|
||||
|
@ -987,8 +990,7 @@ namespace Slic3r {
|
|||
continue;
|
||||
std::string opt_key = option.second.get<std::string>("<xmlattr>.opt_key");
|
||||
std::string value = option.second.data();
|
||||
|
||||
config.set_deserialize(opt_key, value);
|
||||
config.set_deserialize(opt_key, value, config_substitutions);
|
||||
}
|
||||
|
||||
config_ranges[{ min_z, max_z }].assign_config(std::move(config));
|
||||
|
@ -1827,7 +1829,7 @@ namespace Slic3r {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes)
|
||||
bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions)
|
||||
{
|
||||
if (!object.volumes.empty()) {
|
||||
add_error("Found invalid volumes count");
|
||||
|
@ -1943,7 +1945,7 @@ namespace Slic3r {
|
|||
else if (metadata.key == SOURCE_IN_METERS)
|
||||
volume->source.is_converted_from_meters = metadata.value == "1";
|
||||
else
|
||||
volume->config.set_deserialize(metadata.key, metadata.value);
|
||||
volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2953,16 +2955,15 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv
|
|||
return true;
|
||||
}
|
||||
|
||||
bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version)
|
||||
bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version)
|
||||
{
|
||||
if (path == nullptr || config == nullptr || model == nullptr)
|
||||
if (path == nullptr || model == nullptr)
|
||||
return false;
|
||||
|
||||
// All import should use "C" locales for number formatting.
|
||||
CNumericLocalesSetter locales_setter;
|
||||
|
||||
_3MF_Importer importer;
|
||||
bool res = importer.load_model_from_file(path, *model, *config, check_version);
|
||||
_3MF_Importer importer;
|
||||
bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version);
|
||||
importer.log_errors();
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -64,10 +64,11 @@ namespace Slic3r
|
|||
|
||||
struct AMFParserContext
|
||||
{
|
||||
AMFParserContext(XML_Parser parser, DynamicPrintConfig* config, Model* model) :
|
||||
AMFParserContext(XML_Parser parser, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model) :
|
||||
m_parser(parser),
|
||||
m_model(*model),
|
||||
m_config(config)
|
||||
m_config(config),
|
||||
m_config_substitutions(config_substitutions)
|
||||
{
|
||||
m_path.reserve(12);
|
||||
}
|
||||
|
@ -258,6 +259,8 @@ struct AMFParserContext
|
|||
std::string m_value[5];
|
||||
// Pointer to config to update if config data are stored inside the amf file
|
||||
DynamicPrintConfig *m_config { nullptr };
|
||||
// Config substitution rules and collected config substitution log.
|
||||
ConfigSubstitutionContext *m_config_substitutions { nullptr };
|
||||
|
||||
private:
|
||||
AMFParserContext& operator=(AMFParserContext&);
|
||||
|
@ -702,8 +705,9 @@ void AMFParserContext::endElement(const char * /* name */)
|
|||
}
|
||||
|
||||
case NODE_TYPE_METADATA:
|
||||
if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0)
|
||||
m_config->load_from_gcode_string(m_value[1].c_str());
|
||||
if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0) {
|
||||
m_config->load_from_gcode_string(m_value[1].c_str(), *m_config_substitutions);
|
||||
}
|
||||
else if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) {
|
||||
const char *opt_key = m_value[0].c_str() + 7;
|
||||
if (print_config_def.options.find(opt_key) != print_config_def.options.end()) {
|
||||
|
@ -721,7 +725,7 @@ void AMFParserContext::endElement(const char * /* name */)
|
|||
config = &it->second;
|
||||
}
|
||||
if (config)
|
||||
config->set_deserialize(opt_key, m_value[1]);
|
||||
config->set_deserialize(opt_key, m_value[1], *m_config_substitutions);
|
||||
} else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) {
|
||||
// Parse object's layer height profile, a semicolon separated list of floats.
|
||||
char *p = m_value[1].data();
|
||||
|
@ -849,7 +853,7 @@ void AMFParserContext::endDocument()
|
|||
}
|
||||
|
||||
// Load an AMF file into a provided model.
|
||||
bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model)
|
||||
bool load_amf_file(const char *path, DynamicPrintConfig *config, ConfigSubstitutionContext *config_substitutions, Model *model)
|
||||
{
|
||||
if ((path == nullptr) || (model == nullptr))
|
||||
return false;
|
||||
|
@ -866,7 +870,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model)
|
|||
return false;
|
||||
}
|
||||
|
||||
AMFParserContext ctx(parser, config, model);
|
||||
AMFParserContext ctx(parser, config, config_substitutions, model);
|
||||
XML_SetUserData(parser, (void*)&ctx);
|
||||
XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement);
|
||||
XML_SetCharacterDataHandler(parser, AMFParserContext::characters);
|
||||
|
@ -908,7 +912,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model)
|
|||
return result;
|
||||
}
|
||||
|
||||
bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, Model* model, bool check_version)
|
||||
bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version)
|
||||
{
|
||||
if (stat.m_uncomp_size == 0)
|
||||
{
|
||||
|
@ -924,7 +928,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi
|
|||
return false;
|
||||
}
|
||||
|
||||
AMFParserContext ctx(parser, config, model);
|
||||
AMFParserContext ctx(parser, config, config_substitutions, model);
|
||||
XML_SetUserData(parser, (void*)&ctx);
|
||||
XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement);
|
||||
XML_SetCharacterDataHandler(parser, AMFParserContext::characters);
|
||||
|
@ -984,7 +988,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi
|
|||
}
|
||||
|
||||
// Load an AMF archive into a provided model.
|
||||
bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model, bool check_version)
|
||||
bool load_amf_archive(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version)
|
||||
{
|
||||
if ((path == nullptr) || (model == nullptr))
|
||||
return false;
|
||||
|
@ -1010,7 +1014,7 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model
|
|||
{
|
||||
try
|
||||
{
|
||||
if (!extract_model_from_archive(archive, stat, config, model, check_version))
|
||||
if (!extract_model_from_archive(archive, stat, config, config_substitutions, model, check_version))
|
||||
{
|
||||
close_zip_reader(&archive);
|
||||
BOOST_LOG_TRIVIAL(error) << "Archive does not contain a valid model";
|
||||
|
@ -1052,13 +1056,13 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model
|
|||
|
||||
// Load an AMF file into a provided model.
|
||||
// If config is not a null pointer, updates it if the amf file/archive contains config data
|
||||
bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version)
|
||||
bool load_amf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version)
|
||||
{
|
||||
CNumericLocalesSetter locales_setter; // use "C" locales and point as a decimal separator
|
||||
|
||||
if (boost::iends_with(path, ".amf.xml"))
|
||||
// backward compatibility with older slic3r output
|
||||
return load_amf_file(path, config, model);
|
||||
return load_amf_file(path, config, config_substitutions, model);
|
||||
else if (boost::iends_with(path, ".amf"))
|
||||
{
|
||||
boost::nowide::ifstream file(path, boost::nowide::ifstream::binary);
|
||||
|
@ -1069,7 +1073,7 @@ bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool c
|
|||
file.read(zip_mask.data(), 2);
|
||||
file.close();
|
||||
|
||||
return (zip_mask == "PK") ? load_amf_archive(path, config, model, check_version) : load_amf_file(path, config, model);
|
||||
return (zip_mask == "PK") ? load_amf_archive(path, config, config_substitutions, model, check_version) : load_amf_file(path, config, config_substitutions, model);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -284,11 +284,8 @@ static void extract_model_from_archive(
|
|||
volume->name = name;
|
||||
}
|
||||
// Set the extruder to the volume.
|
||||
if (extruder_id != (unsigned int)-1) {
|
||||
char str_extruder[64];
|
||||
sprintf(str_extruder, "%ud", extruder_id);
|
||||
volume->config.set_deserialize("extruder", str_extruder);
|
||||
}
|
||||
if (extruder_id != (unsigned int)-1)
|
||||
volume->config.set("extruder", int(extruder_id));
|
||||
}
|
||||
|
||||
// Load a PrusaControl project file into a provided model.
|
||||
|
|
|
@ -287,13 +287,13 @@ std::vector<ExPolygons> extract_slices_from_sla_archive(
|
|||
|
||||
} // namespace
|
||||
|
||||
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
|
||||
ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
|
||||
{
|
||||
ArchiveData arch = extract_sla_archive(zipfname, "png");
|
||||
out.load(arch.profile);
|
||||
return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
}
|
||||
|
||||
void import_sla_archive(
|
||||
ConfigSubstitutions import_sla_archive(
|
||||
const std::string & zipfname,
|
||||
Vec2i windowsize,
|
||||
indexed_triangle_set & out,
|
||||
|
@ -305,7 +305,7 @@ void import_sla_archive(
|
|||
windowsize.y() = std::max(2, windowsize.y());
|
||||
|
||||
ArchiveData arch = extract_sla_archive(zipfname, "thumbnail");
|
||||
profile.load(arch.profile);
|
||||
ConfigSubstitutions config_substitutions = profile.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
|
||||
RasterParams rstp = get_raster_params(profile);
|
||||
rstp.win = {windowsize.y(), windowsize.x()};
|
||||
|
@ -317,6 +317,8 @@ void import_sla_archive(
|
|||
|
||||
if (!slices.empty())
|
||||
out = slices_to_mesh(slices, 0, slicp.layerh, slicp.initial_layerh);
|
||||
|
||||
return config_substitutions;
|
||||
}
|
||||
|
||||
using ConfMap = std::map<std::string, std::string>;
|
||||
|
|
|
@ -38,23 +38,23 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
|
||||
ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
|
||||
|
||||
void import_sla_archive(
|
||||
ConfigSubstitutions import_sla_archive(
|
||||
const std::string & zipfname,
|
||||
Vec2i windowsize,
|
||||
indexed_triangle_set & out,
|
||||
DynamicPrintConfig & profile,
|
||||
std::function<bool(int)> progr = [](int) { return true; });
|
||||
|
||||
inline void import_sla_archive(
|
||||
inline ConfigSubstitutions import_sla_archive(
|
||||
const std::string & zipfname,
|
||||
Vec2i windowsize,
|
||||
indexed_triangle_set & out,
|
||||
std::function<bool(int)> progr = [](int) { return true; })
|
||||
{
|
||||
DynamicPrintConfig profile;
|
||||
import_sla_archive(zipfname, windowsize, out, profile, progr);
|
||||
return import_sla_archive(zipfname, windowsize, out, profile, progr);
|
||||
}
|
||||
|
||||
} // namespace Slic3r::sla
|
||||
|
|
|
@ -523,7 +523,8 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
|
|||
// Check that there are extrusions on the very first layer.
|
||||
if (layers_to_print.size() == 1u) {
|
||||
if (!has_extrusions)
|
||||
throw Slic3r::SlicingError(_(L("There is an object with no extrusions on the first layer.")));
|
||||
throw Slic3r::SlicingError(_(L("There is an object with no extrusions in the first layer.")) + "\n" +
|
||||
_(L("Object name")) + ": " + object.model_object()->name);
|
||||
}
|
||||
|
||||
// In case there are extrusions on this layer, check there is a layer to lay it on.
|
||||
|
@ -541,7 +542,7 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
|
|||
|
||||
if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) {
|
||||
const_cast<Print*>(object.print())->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
|
||||
_(L("Empty layers detected. Make sure the object is printable.")) + "\n\n" +
|
||||
_(L("Empty layers detected. Make sure the object is printable.")) + "\n" +
|
||||
_(L("Object name")) + ": " + object.model_object()->name + "\n" + _(L("Print z")) + ": " +
|
||||
std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is "
|
||||
"usually caused by negligibly small extrusions or by a faulty model. Try to repair "
|
||||
|
|
|
@ -1285,7 +1285,10 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr
|
|||
if (m_producer == EProducer::PrusaSlicer || m_producer == EProducer::Slic3rPE || m_producer == EProducer::Slic3r) {
|
||||
DynamicPrintConfig config;
|
||||
config.apply(FullPrintConfig::defaults());
|
||||
config.load_from_gcode_file(filename, false);
|
||||
// Silently substitute unknown values by new ones for loading configurations from PrusaSlicer's own G-code.
|
||||
// Showing substitution log or errors may make sense, but we are not really reading many values from the G-code config,
|
||||
// thus a probability of incorrect substitution is low and the G-code viewer is a consumer-only anyways.
|
||||
config.load_from_gcode_file(filename, false, ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
apply_config(config);
|
||||
}
|
||||
else if (m_producer == EProducer::Simplify3D)
|
||||
|
|
|
@ -19,6 +19,7 @@ CNumericLocalesSetter::CNumericLocalesSetter()
|
|||
#else // APPLE
|
||||
m_original_locale = uselocale((locale_t)0);
|
||||
m_new_locale = newlocale(LC_NUMERIC_MASK, "C", m_original_locale);
|
||||
uselocale(m_new_locale);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -96,13 +96,17 @@ void Model::update_links_bottom_up_recursive()
|
|||
}
|
||||
}
|
||||
|
||||
Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances, bool check_version)
|
||||
// Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well.
|
||||
Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options)
|
||||
{
|
||||
Model model;
|
||||
|
||||
DynamicPrintConfig temp_config;
|
||||
ConfigSubstitutionContext temp_config_substitutions_context(ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
if (config == nullptr)
|
||||
config = &temp_config;
|
||||
if (config_substitutions == nullptr)
|
||||
config_substitutions = &temp_config_substitutions_context;
|
||||
|
||||
bool result = false;
|
||||
if (boost::algorithm::iends_with(input_file, ".stl"))
|
||||
|
@ -110,9 +114,10 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
|
|||
else if (boost::algorithm::iends_with(input_file, ".obj"))
|
||||
result = load_obj(input_file.c_str(), &model);
|
||||
else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml"))
|
||||
result = load_amf(input_file.c_str(), config, &model, check_version);
|
||||
result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion);
|
||||
else if (boost::algorithm::iends_with(input_file, ".3mf"))
|
||||
result = load_3mf(input_file.c_str(), config, &model, false);
|
||||
//FIXME options & LoadAttribute::CheckVersion ?
|
||||
result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false);
|
||||
else if (boost::algorithm::iends_with(input_file, ".prusa"))
|
||||
result = load_prus(input_file.c_str(), &model);
|
||||
else
|
||||
|
@ -127,24 +132,29 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
|
|||
for (ModelObject *o : model.objects)
|
||||
o->input_file = input_file;
|
||||
|
||||
if (add_default_instances)
|
||||
if (options & LoadAttribute::AddDefaultInstances)
|
||||
model.add_default_instances();
|
||||
|
||||
CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config);
|
||||
CustomGCode::check_mode_for_custom_gcode_per_print_z(model.custom_gcode_per_print_z);
|
||||
|
||||
sort_remove_duplicates(config_substitutions->substitutions);
|
||||
return model;
|
||||
}
|
||||
|
||||
Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances, bool check_version)
|
||||
// Loading model from a file (3MF or AMF), not from a simple geometry file (STL or OBJ).
|
||||
Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options)
|
||||
{
|
||||
assert(config != nullptr);
|
||||
assert(config_substitutions != nullptr);
|
||||
|
||||
Model model;
|
||||
|
||||
bool result = false;
|
||||
if (boost::algorithm::iends_with(input_file, ".3mf"))
|
||||
result = load_3mf(input_file.c_str(), config, &model, check_version);
|
||||
result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, options & LoadAttribute::CheckVersion);
|
||||
else if (boost::algorithm::iends_with(input_file, ".zip.amf"))
|
||||
result = load_amf(input_file.c_str(), config, &model, check_version);
|
||||
result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion);
|
||||
else
|
||||
throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension.");
|
||||
|
||||
|
@ -165,7 +175,7 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
|
|||
o->input_file = input_file;
|
||||
}
|
||||
|
||||
if (add_default_instances)
|
||||
if (options & LoadAttribute::AddDefaultInstances)
|
||||
model.add_default_instances();
|
||||
|
||||
CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config);
|
||||
|
@ -398,13 +408,12 @@ bool Model::looks_like_multipart_object() const
|
|||
}
|
||||
|
||||
// Generate next extruder ID string, in the range of (1, max_extruders).
|
||||
static inline std::string auto_extruder_id(unsigned int max_extruders, unsigned int &cntr)
|
||||
static inline int auto_extruder_id(unsigned int max_extruders, unsigned int &cntr)
|
||||
{
|
||||
char str_extruder[64];
|
||||
sprintf(str_extruder, "%ud", cntr + 1);
|
||||
if (++ cntr == max_extruders)
|
||||
int out = ++ cntr;
|
||||
if (cntr == max_extruders)
|
||||
cntr = 0;
|
||||
return str_extruder;
|
||||
return out;
|
||||
}
|
||||
|
||||
void Model::convert_multipart_object(unsigned int max_extruders)
|
||||
|
@ -431,7 +440,7 @@ void Model::convert_multipart_object(unsigned int max_extruders)
|
|||
auto copy_volume = [o, max_extruders, &counter, &extruder_counter](ModelVolume *new_v) {
|
||||
assert(new_v != nullptr);
|
||||
new_v->name = o->name + "_" + std::to_string(counter++);
|
||||
new_v->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter));
|
||||
new_v->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
|
||||
return new_v;
|
||||
};
|
||||
if (o->instances.empty()) {
|
||||
|
@ -1134,17 +1143,18 @@ bool ModelObject::needed_repair() const
|
|||
return false;
|
||||
}
|
||||
|
||||
ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower)
|
||||
ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes)
|
||||
{
|
||||
if (!keep_upper && !keep_lower) { return {}; }
|
||||
if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
return {};
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
|
||||
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
ModelObject* upper = keep_upper ? ModelObject::new_clone(*this) : nullptr;
|
||||
ModelObject* lower = keep_lower ? ModelObject::new_clone(*this) : nullptr;
|
||||
ModelObject* upper = attributes.has(ModelObjectCutAttribute::KeepUpper) ? ModelObject::new_clone(*this) : nullptr;
|
||||
ModelObject* lower = attributes.has(ModelObjectCutAttribute::KeepLower) ? ModelObject::new_clone(*this) : nullptr;
|
||||
|
||||
if (keep_upper) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
upper->set_model(nullptr);
|
||||
upper->sla_support_points.clear();
|
||||
upper->sla_drain_holes.clear();
|
||||
|
@ -1153,7 +1163,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
upper->input_file.clear();
|
||||
}
|
||||
|
||||
if (keep_lower) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
|
||||
lower->set_model(nullptr);
|
||||
lower->sla_support_points.clear();
|
||||
lower->sla_drain_holes.clear();
|
||||
|
@ -1193,8 +1203,10 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
|
||||
volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix));
|
||||
|
||||
if (keep_upper) { upper->add_volume(*volume); }
|
||||
if (keep_lower) { lower->add_volume(*volume); }
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
upper->add_volume(*volume);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
lower->add_volume(*volume);
|
||||
}
|
||||
else if (! volume->mesh().empty()) {
|
||||
|
||||
|
@ -1214,19 +1226,19 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
indexed_triangle_set upper_its, lower_its;
|
||||
mesh.require_shared_vertices();
|
||||
cut_mesh(mesh.its, float(z), &upper_its, &lower_its);
|
||||
if (keep_upper) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
upper_mesh = TriangleMesh(upper_its);
|
||||
upper_mesh.repair();
|
||||
upper_mesh.reset_repair_stats();
|
||||
}
|
||||
if (keep_lower) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
|
||||
lower_mesh = TriangleMesh(lower_its);
|
||||
lower_mesh.repair();
|
||||
lower_mesh.reset_repair_stats();
|
||||
}
|
||||
}
|
||||
|
||||
if (keep_upper && upper_mesh.facets_count() > 0) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper_mesh.facets_count() > 0) {
|
||||
ModelVolume* vol = upper->add_volume(upper_mesh);
|
||||
vol->name = volume->name;
|
||||
// Don't copy the config's ID.
|
||||
|
@ -1235,7 +1247,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
assert(vol->config.id() != volume->config.id());
|
||||
vol->set_material(volume->material_id(), *volume->material());
|
||||
}
|
||||
if (keep_lower && lower_mesh.facets_count() > 0) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower_mesh.facets_count() > 0) {
|
||||
ModelVolume* vol = lower->add_volume(lower_mesh);
|
||||
vol->name = volume->name;
|
||||
// Don't copy the config's ID.
|
||||
|
@ -1246,7 +1258,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
|
||||
// Compute the lower part instances' bounding boxes to figure out where to place
|
||||
// the upper part
|
||||
if (keep_upper) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
for (size_t i = 0; i < instances.size(); i++) {
|
||||
lower_bboxes[i].merge(instances[i]->transform_mesh_bounding_box(lower_mesh, true));
|
||||
}
|
||||
|
@ -1257,7 +1269,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
|
||||
ModelObjectPtrs res;
|
||||
|
||||
if (keep_upper && upper->volumes.size() > 0) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) {
|
||||
upper->invalidate_bounding_box();
|
||||
upper->center_around_origin();
|
||||
|
||||
|
@ -1277,7 +1289,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
|
||||
res.push_back(upper);
|
||||
}
|
||||
if (keep_lower && lower->volumes.size() > 0) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) {
|
||||
lower->invalidate_bounding_box();
|
||||
lower->center_around_origin();
|
||||
|
||||
|
@ -1288,7 +1300,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
|
||||
instance->set_transformation(Geometry::Transformation());
|
||||
instance->set_offset(offset);
|
||||
instance->set_rotation(Vec3d(rotate_lower ? Geometry::deg2rad(180.0) : 0.0, 0.0, rot_z));
|
||||
instance->set_rotation(Vec3d(attributes.has(ModelObjectCutAttribute::FlipLower) ? Geometry::deg2rad(180.0) : 0.0, 0.0, rot_z));
|
||||
}
|
||||
|
||||
res.push_back(lower);
|
||||
|
@ -1628,7 +1640,7 @@ bool ModelVolume::is_splittable() const
|
|||
{
|
||||
// the call mesh.is_splittable() is expensive, so cache the value to calculate it only once
|
||||
if (m_is_splittable == -1)
|
||||
m_is_splittable = (int)this->mesh().is_splittable();
|
||||
m_is_splittable = its_is_splittable(this->mesh().its);
|
||||
|
||||
return m_is_splittable == 1;
|
||||
}
|
||||
|
@ -1738,7 +1750,7 @@ size_t ModelVolume::split(unsigned int max_extruders)
|
|||
this->object->volumes[ivolume]->center_geometry_after_creation();
|
||||
this->object->volumes[ivolume]->translate(offset);
|
||||
this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1);
|
||||
this->object->volumes[ivolume]->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter));
|
||||
this->object->volumes[ivolume]->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
|
||||
this->object->volumes[ivolume]->m_is_splittable = 0;
|
||||
delete mesh;
|
||||
++ idx;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define slic3r_Model_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "enum_bitmask.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "ObjectID.hpp"
|
||||
#include "Point.hpp"
|
||||
|
@ -12,6 +13,7 @@
|
|||
#include "TriangleMesh.hpp"
|
||||
#include "Arrange.hpp"
|
||||
#include "CustomGCode.hpp"
|
||||
#include "enum_bitmask.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
@ -226,6 +228,10 @@ enum class ModelVolumeType : int {
|
|||
SUPPORT_ENFORCER,
|
||||
};
|
||||
|
||||
enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower };
|
||||
using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
|
||||
ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
|
||||
|
||||
// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials),
|
||||
// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials.
|
||||
// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed,
|
||||
|
@ -344,7 +350,7 @@ public:
|
|||
size_t materials_count() const;
|
||||
size_t facets_count() const;
|
||||
bool needed_repair() const;
|
||||
ModelObjectPtrs cut(size_t instance, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); // Note: z is in world coordinates
|
||||
ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes);
|
||||
void split(ModelObjectPtrs* new_objects);
|
||||
void merge();
|
||||
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
|
||||
|
@ -1031,8 +1037,20 @@ public:
|
|||
|
||||
OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model)
|
||||
|
||||
static Model read_from_file(const std::string& input_file, DynamicPrintConfig* config = nullptr, bool add_default_instances = true, bool check_version = false);
|
||||
static Model read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances = true, bool check_version = false);
|
||||
enum class LoadAttribute : int {
|
||||
AddDefaultInstances,
|
||||
CheckVersion
|
||||
};
|
||||
using LoadAttributes = enum_bitmask<LoadAttribute>;
|
||||
|
||||
static Model read_from_file(
|
||||
const std::string& input_file,
|
||||
DynamicPrintConfig* config = nullptr, ConfigSubstitutionContext* config_substitutions = nullptr,
|
||||
LoadAttributes options = LoadAttribute::AddDefaultInstances);
|
||||
static Model read_from_archive(
|
||||
const std::string& input_file,
|
||||
DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions,
|
||||
LoadAttributes options = LoadAttribute::AddDefaultInstances);
|
||||
|
||||
// Add a new ModelObject to this Model, generate a new ID for this ModelObject.
|
||||
ModelObject* add_object();
|
||||
|
@ -1097,6 +1115,8 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
ENABLE_ENUM_BITMASK_OPERATORS(Model::LoadAttribute)
|
||||
|
||||
#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
|
||||
#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE
|
||||
|
||||
|
|
|
@ -4,20 +4,15 @@
|
|||
#include "Layer.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "VoronoiVisualUtils.hpp"
|
||||
#include "MutablePolygon.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <cfloat>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#include <boost/geometry.hpp>
|
||||
#include <boost/geometry/geometries/point.hpp>
|
||||
#include <boost/geometry/geometries/segment.hpp>
|
||||
#include <boost/geometry/index/rtree.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
struct ColoredLine {
|
||||
Line line;
|
||||
|
@ -89,28 +84,37 @@ struct PaintedLineVisitor
|
|||
bool operator()(coord_t iy, coord_t ix)
|
||||
{
|
||||
// Called with a row and column of the grid cell, which is intersected by a line.
|
||||
auto cell_data_range = grid.cell_data_range(iy, ix);
|
||||
const Vec2d v1 = line_to_test.vector().cast<double>();
|
||||
auto cell_data_range = grid.cell_data_range(iy, ix);
|
||||
const Vec2d v1 = line_to_test.vector().cast<double>();
|
||||
const double v1_sqr_norm = v1.squaredNorm();
|
||||
const double heuristic_thr_part = line_to_test.length() + append_threshold;
|
||||
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
|
||||
Line grid_line = grid.line(*it_contour_and_segment);
|
||||
const Vec2d v2 = grid_line.vector().cast<double>();
|
||||
Line grid_line = grid.line(*it_contour_and_segment);
|
||||
const Vec2d v2 = grid_line.vector().cast<double>();
|
||||
double heuristic_thr_sqr = Slic3r::sqr(heuristic_thr_part + grid_line.length());
|
||||
|
||||
// An inexpensive heuristic to test whether line_to_test and grid_line can be somewhere close enough to each other.
|
||||
// This helps filter out cases when the following expensive calculations are useless.
|
||||
if ((grid_line.a - line_to_test.a).cast<double>().squaredNorm() > heuristic_thr_sqr ||
|
||||
(grid_line.b - line_to_test.a).cast<double>().squaredNorm() > heuristic_thr_sqr ||
|
||||
(grid_line.a - line_to_test.b).cast<double>().squaredNorm() > heuristic_thr_sqr ||
|
||||
(grid_line.b - line_to_test.b).cast<double>().squaredNorm() > heuristic_thr_sqr)
|
||||
continue;
|
||||
|
||||
// When lines have too different length, it is necessary to normalize them
|
||||
if (Slic3r::sqr(v1.dot(v2)) > cos_threshold2 * v1.squaredNorm() * v2.squaredNorm()) {
|
||||
if (Slic3r::sqr(v1.dot(v2)) > cos_threshold2 * v1_sqr_norm * v2.squaredNorm()) {
|
||||
// The two vectors are nearly collinear (their mutual angle is lower than 30 degrees)
|
||||
if (painted_lines_set.find(*it_contour_and_segment) == painted_lines_set.end()) {
|
||||
double dist_1 = grid_line.distance_to(line_to_test.a);
|
||||
double dist_2 = grid_line.distance_to(line_to_test.b);
|
||||
double dist_3 = line_to_test.distance_to(grid_line.a);
|
||||
double dist_4 = line_to_test.distance_to(grid_line.b);
|
||||
double total_dist = std::min(std::min(dist_1, dist_2), std::min(dist_3, dist_4));
|
||||
|
||||
if (total_dist < 50 * SCALED_EPSILON) {
|
||||
if (grid_line.distance_to_squared(line_to_test.a) < append_threshold2 ||
|
||||
grid_line.distance_to_squared(line_to_test.b) < append_threshold2 ||
|
||||
line_to_test.distance_to_squared(grid_line.a) < append_threshold2 ||
|
||||
line_to_test.distance_to_squared(grid_line.b) < append_threshold2) {
|
||||
Line line_to_test_projected;
|
||||
project_line_on_line(grid_line, line_to_test, &line_to_test_projected);
|
||||
|
||||
if (Line(grid_line.a, line_to_test_projected.a).length() > Line(grid_line.a, line_to_test_projected.b).length()) {
|
||||
if ((line_to_test_projected.a - grid_line.a).cast<double>().squaredNorm() > (line_to_test_projected.b - grid_line.a).cast<double>().squaredNorm())
|
||||
line_to_test_projected.reverse();
|
||||
}
|
||||
|
||||
painted_lines.push_back({it_contour_and_segment->first, it_contour_and_segment->second, line_to_test_projected, this->color});
|
||||
painted_lines_set.insert(*it_contour_and_segment);
|
||||
}
|
||||
|
@ -125,9 +129,11 @@ struct PaintedLineVisitor
|
|||
std::vector<PaintedLine> &painted_lines;
|
||||
Line line_to_test;
|
||||
std::unordered_set<std::pair<size_t, size_t>, boost::hash<std::pair<size_t, size_t>>> painted_lines_set;
|
||||
int color = -1;
|
||||
int color = -1;
|
||||
|
||||
static inline const double cos_threshold2 = Slic3r::sqr(cos(M_PI * 30. / 180.));
|
||||
static inline const double cos_threshold2 = Slic3r::sqr(cos(M_PI * 30. / 180.));
|
||||
static inline const double append_threshold = 50 * SCALED_EPSILON;
|
||||
static inline const double append_threshold2 = Slic3r::sqr(append_threshold);
|
||||
};
|
||||
|
||||
static std::vector<ColoredLine> to_colored_lines(const Polygon &polygon, int color)
|
||||
|
@ -154,6 +160,7 @@ static Polygon colored_points_to_polygon(const std::vector<ColoredLine> &lines)
|
|||
static Polygons colored_points_to_polygon(const std::vector<std::vector<ColoredLine>> &lines)
|
||||
{
|
||||
Polygons out;
|
||||
out.reserve(lines.size());
|
||||
for (const std::vector<ColoredLine> &l : lines)
|
||||
out.emplace_back(colored_points_to_polygon(l));
|
||||
return out;
|
||||
|
@ -484,6 +491,12 @@ static std::vector<std::vector<ColoredLine>> colorize_polygons(const Polygons &p
|
|||
|
||||
using boost::polygon::voronoi_diagram;
|
||||
|
||||
static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return Point(coord_t(point->x()), coord_t(point->y())); }
|
||||
|
||||
static inline Point mk_point(const Voronoi::Internal::point_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); }
|
||||
|
||||
static inline Point mk_point(const voronoi_diagram<double>::vertex_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); }
|
||||
|
||||
struct MMU_Graph
|
||||
{
|
||||
enum class ARC_TYPE { BORDER, NON_BORDER };
|
||||
|
@ -616,24 +629,63 @@ struct MMU_Graph
|
|||
{
|
||||
return this->is_vertex_on_contour(edge_iterator->vertex0()) && this->is_vertex_on_contour(edge_iterator->vertex1());
|
||||
}
|
||||
|
||||
// All Voronoi vertices are post-processes to merge very close vertices to single. Witch eliminates issues with intersection edges.
|
||||
// Also, Voronoi vertices outside of the bounding of input polygons are throw away by marking them.
|
||||
void append_voronoi_vertices(const Geometry::VoronoiDiagram &vd, const Polygons &color_poly_tmp, BoundingBox bbox) {
|
||||
bbox.offset(SCALED_EPSILON);
|
||||
|
||||
struct CPoint
|
||||
{
|
||||
CPoint() = delete;
|
||||
CPoint(const Point &point, size_t contour_idx, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(contour_idx) {}
|
||||
CPoint(const Point &point, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(0) {}
|
||||
const Point m_point;
|
||||
size_t m_point_idx;
|
||||
size_t m_contour_idx;
|
||||
|
||||
[[nodiscard]] const Point &point() const { return m_point; }
|
||||
bool operator==(const CPoint &rhs) const { return this->m_point == rhs.m_point && this->m_contour_idx == rhs.m_contour_idx && this->m_point_idx == rhs.m_point_idx; }
|
||||
};
|
||||
struct CPointAccessor { const Point* operator()(const CPoint &pt) const { return &pt.point(); }};
|
||||
typedef ClosestPointInRadiusLookup<CPoint, CPointAccessor> CPointLookupType;
|
||||
|
||||
CPointLookupType closest_voronoi_point(3 * coord_t(SCALED_EPSILON));
|
||||
CPointLookupType closest_contour_point(3 * coord_t(SCALED_EPSILON));
|
||||
for (const Polygon &polygon : color_poly_tmp)
|
||||
for (const Point &pt : polygon.points)
|
||||
closest_contour_point.insert(CPoint(pt, &polygon - &color_poly_tmp.front(), &pt - &polygon.points.front()));
|
||||
|
||||
for (const voronoi_diagram<double>::vertex_type &vertex : vd.vertices()) {
|
||||
vertex.color(-1);
|
||||
Point vertex_point = mk_point(vertex);
|
||||
|
||||
const Point &first_point = this->nodes[this->get_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point;
|
||||
const Point &second_point = this->nodes[this->get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point;
|
||||
|
||||
if (vertex_equal_to_point(&vertex, first_point)) {
|
||||
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
|
||||
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
|
||||
vertex.color(this->get_arc(vertex.incident_edge()->cell()->source_index()).from_idx);
|
||||
} else if (vertex_equal_to_point(&vertex, second_point)) {
|
||||
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
|
||||
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
|
||||
vertex.color(this->get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx);
|
||||
} else if (bbox.contains(vertex_point)) {
|
||||
if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < 3 * SCALED_EPSILON) {
|
||||
vertex.color(this->get_global_index(contour_pt->m_contour_idx, contour_pt->m_point_idx));
|
||||
} else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= 3 * SCALED_EPSILON) {
|
||||
closest_voronoi_point.insert(CPoint(vertex_point, this->nodes_count()));
|
||||
vertex.color(this->nodes_count());
|
||||
this->nodes.push_back({vertex_point});
|
||||
} else {
|
||||
vertex.color(voronoi_pt->m_point_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace bg = boost::geometry;
|
||||
namespace bgm = boost::geometry::model;
|
||||
namespace bgi = boost::geometry::index;
|
||||
|
||||
// float is needed because for coord_t bgi::intersects throws "bad numeric conversion: positive overflow"
|
||||
using rtree_point_t = bgm::point<float, 2, boost::geometry::cs::cartesian>;
|
||||
using rtree_t = bgi::rtree<std::pair<rtree_point_t, size_t>, bgi::rstar<16, 4>>;
|
||||
|
||||
static inline rtree_point_t mk_rtree_point(const Point &pt) { return rtree_point_t(float(pt.x()), float(pt.y())); }
|
||||
|
||||
static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return Point(coord_t(point->x()), coord_t(point->y())); }
|
||||
|
||||
static inline Point mk_point(const Voronoi::Internal::point_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); }
|
||||
|
||||
static inline Point mk_point(const voronoi_diagram<double>::vertex_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); }
|
||||
|
||||
static inline void mark_processed(const voronoi_diagram<double>::const_edge_iterator &edge_iterator)
|
||||
{
|
||||
edge_iterator->color(true);
|
||||
|
@ -695,7 +747,7 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
|
|||
{
|
||||
Geometry::VoronoiDiagram vd;
|
||||
std::vector<ColoredLine> lines_colored = to_lines(color_poly);
|
||||
Polygons color_poly_tmp = colored_points_to_polygon(color_poly);
|
||||
const Polygons color_poly_tmp = colored_points_to_polygon(color_poly);
|
||||
const Points points = to_points(color_poly_tmp);
|
||||
const Lines lines = to_lines(color_poly_tmp);
|
||||
|
||||
|
@ -719,6 +771,7 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
|
|||
|
||||
boost::polygon::construct_voronoi(lines_colored.begin(), lines_colored.end(), &vd);
|
||||
MMU_Graph graph;
|
||||
graph.nodes.reserve(points.size() + vd.vertices().size());
|
||||
for (const Point &point : points)
|
||||
graph.nodes.push_back({point});
|
||||
|
||||
|
@ -726,66 +779,8 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
|
|||
init_polygon_indices(graph, color_poly, lines_colored);
|
||||
|
||||
assert(graph.nodes.size() == lines_colored.size());
|
||||
|
||||
// All Voronoi vertices are post-processes to merge very close vertices to single. Witch Eliminates issues with intersection edges.
|
||||
// Also, Voronoi vertices outside of the bounding of input polygons are throw away by marking them.
|
||||
auto append_voronoi_vertices_to_graph = [&graph, &color_poly_tmp, &vd]() -> void {
|
||||
auto is_equal_points = [](const Point &p1, const Point &p2) { return p1 == p2 || (p1 - p2).cast<double>().norm() <= 3 * SCALED_EPSILON; };
|
||||
|
||||
BoundingBox bbox = get_extents(color_poly_tmp);
|
||||
bbox.offset(SCALED_EPSILON);
|
||||
// EdgeGrid is used for vertices near to contour and rtree for other vertices
|
||||
// FIXME Lukas H.: Get rid of EdgeGrid and rtree. Use only one structure for both cases.
|
||||
EdgeGrid::Grid grid;
|
||||
grid.set_bbox(bbox);
|
||||
grid.create(color_poly_tmp, coord_t(scale_(10.)));
|
||||
rtree_t rtree;
|
||||
for (const voronoi_diagram<double>::vertex_type &vertex : vd.vertices()) {
|
||||
vertex.color(-1);
|
||||
Point vertex_point = mk_point(vertex);
|
||||
|
||||
const Point &first_point = graph.nodes[graph.get_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point;
|
||||
const Point &second_point = graph.nodes[graph.get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point;
|
||||
|
||||
if (vertex_equal_to_point(&vertex, first_point)) {
|
||||
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
|
||||
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
|
||||
vertex.color(graph.get_arc(vertex.incident_edge()->cell()->source_index()).from_idx);
|
||||
} else if (vertex_equal_to_point(&vertex, second_point)) {
|
||||
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
|
||||
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
|
||||
vertex.color(graph.get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx);
|
||||
} else if (bbox.contains(vertex_point)) {
|
||||
EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point_signed_distance(vertex_point, coord_t(3 * SCALED_EPSILON));
|
||||
if (cp.valid()) {
|
||||
size_t global_idx = graph.get_global_index(cp.contour_idx, cp.start_point_idx);
|
||||
size_t global_idx_next = graph.get_global_index(cp.contour_idx, (cp.start_point_idx + 1) % color_poly_tmp[cp.contour_idx].points.size());
|
||||
vertex.color(is_equal_points(vertex_point, graph.nodes[global_idx].point) ? global_idx : global_idx_next);
|
||||
} else {
|
||||
if (rtree.empty()) {
|
||||
rtree.insert(std::make_pair(mk_rtree_point(vertex_point), graph.nodes_count()));
|
||||
vertex.color(graph.nodes_count());
|
||||
graph.nodes.push_back({vertex_point});
|
||||
} else {
|
||||
std::vector<std::pair<rtree_point_t, size_t>> closest;
|
||||
rtree.query(bgi::nearest(mk_rtree_point(vertex_point), 1), std::back_inserter(closest));
|
||||
assert(!closest.empty());
|
||||
rtree_point_t r_point = closest.front().first;
|
||||
Point closest_p(bg::get<0>(r_point), bg::get<1>(r_point));
|
||||
if (Line(vertex_point, closest_p).length() > 3 * SCALED_EPSILON) {
|
||||
rtree.insert(std::make_pair(mk_rtree_point(vertex_point), graph.nodes_count()));
|
||||
vertex.color(graph.nodes_count());
|
||||
graph.nodes.push_back({vertex_point});
|
||||
} else {
|
||||
vertex.color(closest.front().second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
append_voronoi_vertices_to_graph();
|
||||
BoundingBox bbox = get_extents(color_poly_tmp);
|
||||
graph.append_voronoi_vertices(vd, color_poly_tmp, bbox);
|
||||
|
||||
auto get_prev_contour_line = [&lines_colored, &color_poly, &graph](const voronoi_diagram<double>::const_edge_iterator &edge_it) -> ColoredLine {
|
||||
size_t contour_line_local_idx = lines_colored[edge_it->cell()->source_index()].local_line_idx;
|
||||
|
@ -803,7 +798,6 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<Col
|
|||
return lines_colored[contour_next_idx];
|
||||
};
|
||||
|
||||
BoundingBox bbox = get_extents(color_poly_tmp);
|
||||
bbox.offset(scale_(10.));
|
||||
const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y()));
|
||||
|
||||
|
@ -1428,7 +1422,7 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
|
|||
// All expolygons are expanded by SCALED_EPSILON, merged, and then shrunk again by SCALED_EPSILON
|
||||
// to ensure that very close polygons will be merged.
|
||||
ex_polygons = union_ex(ex_polygons);
|
||||
// Remove all expolygons and holes with an area less than 0.01mm^2
|
||||
// Remove all expolygons and holes with an area less than 0.1mm^2
|
||||
remove_small_and_small_holes(ex_polygons, Slic3r::sqr(scale_(0.1f)));
|
||||
// Occasionally, some input polygons contained self-intersections that caused problems with Voronoi diagrams
|
||||
// and consequently with the extraction of colored segments by function extract_colored_segments.
|
||||
|
@ -1437,19 +1431,19 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
|
|||
// Such close points sometimes caused that the Voronoi diagram has self-intersecting edges around these vertices.
|
||||
// This consequently leads to issues with the extraction of colored segments by function extract_colored_segments.
|
||||
// Calling expolygons_simplify fixed these issues.
|
||||
input_expolygons[layer_idx] = simplify_polygons_ex(to_polygons(expolygons_simplify(offset_ex(ex_polygons, float(-10 * SCALED_EPSILON)), 5 * SCALED_EPSILON)));
|
||||
input_polygons[layer_idx] = to_polygons(input_expolygons[layer_idx]);
|
||||
input_expolygons[layer_idx] = smooth_outward(expolygons_simplify(offset_ex(ex_polygons, -10.f * float(SCALED_EPSILON)), 5 * SCALED_EPSILON), 10 * coord_t(SCALED_EPSILON));
|
||||
input_polygons[layer_idx] = to_polygons(input_expolygons[layer_idx]);
|
||||
}
|
||||
}); // end of parallel_for
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - slices preparation in parallel - end";
|
||||
|
||||
for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) {
|
||||
throw_on_cancel_callback();
|
||||
BoundingBox bbox(get_extents(input_expolygons[layer_idx]));
|
||||
BoundingBox bbox(get_extents(input_polygons[layer_idx]));
|
||||
// Projected triangles may slightly exceed the input polygons.
|
||||
bbox.offset(20 * SCALED_EPSILON);
|
||||
edge_grids[layer_idx].set_bbox(bbox);
|
||||
edge_grids[layer_idx].create(input_expolygons[layer_idx], coord_t(scale_(10.)));
|
||||
edge_grids[layer_idx].create(input_polygons[layer_idx], coord_t(scale_(10.)));
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - projection of painted triangles - begin";
|
||||
|
@ -1498,7 +1492,7 @@ std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentati
|
|||
// [P0, P2] a [P0, P1]
|
||||
float t1 = (float(layer->slice_z) - facet[0].z()) / (facet[1].z() - facet[0].z());
|
||||
line_end_f = facet[0] + t1 * (facet[1] - facet[0]);
|
||||
} else if (facet[1].z() <= layer->slice_z) {
|
||||
} else {
|
||||
// [P0, P2] a [P1, P2]
|
||||
float t2 = (float(layer->slice_z) - facet[1].z()) / (facet[2].z() - facet[1].z());
|
||||
line_end_f = facet[1] + t2 * (facet[2] - facet[1]);
|
||||
|
|
|
@ -166,7 +166,7 @@ static bool clip_narrow_corner(
|
|||
assert(orient1 > 0 == blocked);
|
||||
assert(orient2 > 0 == blocked);
|
||||
}
|
||||
#endif // _NDEBUG
|
||||
#endif // NDEBUG
|
||||
if (polygon.size() < 3 || (forward == Far && backward == Far)) {
|
||||
polygon.clear();
|
||||
} else {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "Point.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -330,6 +331,24 @@ inline Polygons smooth_outward(Polygons polygons, coord_t clip_dist_scaled)
|
|||
return polygons;
|
||||
}
|
||||
|
||||
inline ExPolygons smooth_outward(ExPolygons expolygons, coord_t clip_dist_scaled)
|
||||
{
|
||||
MutablePolygon mp;
|
||||
for (ExPolygon &expolygon : expolygons) {
|
||||
mp.assign(expolygon.contour, expolygon.contour.size() * 2);
|
||||
smooth_outward(mp, clip_dist_scaled);
|
||||
mp.polygon(expolygon.contour);
|
||||
for (Polygon &hole : expolygon.holes) {
|
||||
mp.assign(hole, hole.size() * 2);
|
||||
smooth_outward(mp, clip_dist_scaled);
|
||||
mp.polygon(hole);
|
||||
}
|
||||
expolygon.holes.erase(std::remove_if(expolygon.holes.begin(), expolygon.holes.end(), [](const auto &p) { return p.empty(); }), expolygon.holes.end());
|
||||
}
|
||||
expolygons.erase(std::remove_if(expolygons.begin(), expolygons.end(), [](const auto &p) { return p.empty(); }), expolygons.end());
|
||||
return expolygons;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // slic3r_MutablePolygon_hpp_
|
||||
|
|
|
@ -666,7 +666,9 @@ void PresetCollection::add_default_preset(const std::vector<std::string> &keys,
|
|||
|
||||
// Load all presets found in dir_path.
|
||||
// Throws an exception on error.
|
||||
void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir)
|
||||
void PresetCollection::load_presets(
|
||||
const std::string &dir_path, const std::string &subdir,
|
||||
PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule)
|
||||
{
|
||||
// Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points,
|
||||
// see https://github.com/prusa3d/PrusaSlicer/issues/732
|
||||
|
@ -693,7 +695,9 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri
|
|||
// Load the preset file, apply preset values on top of defaults.
|
||||
try {
|
||||
DynamicPrintConfig config;
|
||||
config.load_from_ini(preset.file);
|
||||
ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule);
|
||||
if (! config_substitutions.empty())
|
||||
substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions) });
|
||||
// Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field.
|
||||
const Preset &default_preset = this->default_preset_for(config);
|
||||
preset.config = default_preset.config;
|
||||
|
@ -1580,7 +1584,9 @@ PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vector<std::str
|
|||
|
||||
// Load all printers found in dir_path.
|
||||
// Throws an exception on error.
|
||||
void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const std::string& subdir)
|
||||
void PhysicalPrinterCollection::load_printers(
|
||||
const std::string& dir_path, const std::string& subdir,
|
||||
PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule)
|
||||
{
|
||||
// Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points,
|
||||
// see https://github.com/prusa3d/PrusaSlicer/issues/732
|
||||
|
@ -1606,7 +1612,9 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const
|
|||
// Load the preset file, apply preset values on top of defaults.
|
||||
try {
|
||||
DynamicPrintConfig config;
|
||||
config.load_from_ini(printer.file);
|
||||
ConfigSubstitutions config_substitutions = config.load_from_ini(printer.file, substitution_rule);
|
||||
if (! config_substitutions.empty())
|
||||
substitutions.push_back({ name, Preset::TYPE_PHYSICAL_PRINTER, PresetConfigSubstitutions::Source::UserFile, printer.file, std::move(config_substitutions) });
|
||||
printer.update_from_config(config);
|
||||
printer.loaded = true;
|
||||
}
|
||||
|
|
|
@ -113,6 +113,9 @@ public:
|
|||
TYPE_SLA_MATERIAL,
|
||||
TYPE_PRINTER,
|
||||
TYPE_COUNT,
|
||||
// This type is here to support PresetConfigSubstitutions for physical printers, however it does not belong to the Preset class,
|
||||
// PhysicalPrinter class is used instead.
|
||||
TYPE_PHYSICAL_PRINTER,
|
||||
};
|
||||
|
||||
Type type = TYPE_INVALID;
|
||||
|
@ -251,6 +254,27 @@ enum class PresetSelectCompatibleType {
|
|||
Always
|
||||
};
|
||||
|
||||
// Substitutions having been performed during parsing a single configuration file.
|
||||
struct PresetConfigSubstitutions {
|
||||
// User readable preset name.
|
||||
std::string preset_name;
|
||||
// Type of the preset (Print / Filament / Printer ...)
|
||||
Preset::Type preset_type;
|
||||
enum class Source {
|
||||
UserFile,
|
||||
ConfigBundle,
|
||||
};
|
||||
Source preset_source;
|
||||
// Source of the preset. It may be empty in case of a ConfigBundle being loaded.
|
||||
std::string preset_file;
|
||||
// What config value has been substituted with what.
|
||||
ConfigSubstitutions substitutions;
|
||||
};
|
||||
|
||||
// Substitutions having been performed during parsing a set of configuration files, for example when starting up
|
||||
// PrusaSlicer and reading the user Print / Filament / Printer profiles.
|
||||
using PresetsConfigSubstitutions = std::vector<PresetConfigSubstitutions>;
|
||||
|
||||
// Collections of presets of the same type (one of the Print, Filament or Printer type).
|
||||
class PresetCollection
|
||||
{
|
||||
|
@ -280,7 +304,7 @@ public:
|
|||
void add_default_preset(const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name);
|
||||
|
||||
// Load ini files of the particular type from the provided directory path.
|
||||
void load_presets(const std::string &dir_path, const std::string &subdir);
|
||||
void load_presets(const std::string &dir_path, const std::string &subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule);
|
||||
|
||||
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets
|
||||
// and select it, losing previous modifications.
|
||||
|
@ -692,7 +716,7 @@ public:
|
|||
const std::deque<PhysicalPrinter>& operator()() const { return m_printers; }
|
||||
|
||||
// Load ini files of the particular type from the provided directory path.
|
||||
void load_printers(const std::string& dir_path, const std::string& subdir);
|
||||
void load_printers(const std::string& dir_path, const std::string& subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule);
|
||||
void load_printers_from_presets(PrinterPresetCollection &printer_presets);
|
||||
// Load printer from the loaded configuration
|
||||
void load_printer(const std::string& path, const std::string& name, DynamicPrintConfig&& config, bool select, bool save=false);
|
||||
|
|
|
@ -187,7 +187,7 @@ void PresetBundle::setup_directories()
|
|||
}
|
||||
}
|
||||
|
||||
void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_model_id)
|
||||
PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule, const std::string &preferred_model_id)
|
||||
{
|
||||
// First load the vendor specific system presets.
|
||||
std::string errors_cummulative = this->load_system_presets();
|
||||
|
@ -200,33 +200,35 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_
|
|||
// Store the print/filament/printer presets at the same location as the upstream Slic3r.
|
||||
#endif
|
||||
;
|
||||
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
try {
|
||||
this->prints.load_presets(dir_user_presets, "print");
|
||||
this->prints.load_presets(dir_user_presets, "print", substitutions, substitution_rule);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
try {
|
||||
this->sla_prints.load_presets(dir_user_presets, "sla_print");
|
||||
this->sla_prints.load_presets(dir_user_presets, "sla_print", substitutions, substitution_rule);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
try {
|
||||
this->filaments.load_presets(dir_user_presets, "filament");
|
||||
this->filaments.load_presets(dir_user_presets, "filament", substitutions, substitution_rule);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
try {
|
||||
this->sla_materials.load_presets(dir_user_presets, "sla_material");
|
||||
this->sla_materials.load_presets(dir_user_presets, "sla_material", substitutions, substitution_rule);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
try {
|
||||
this->printers.load_presets(dir_user_presets, "printer");
|
||||
this->printers.load_presets(dir_user_presets, "printer", substitutions, substitution_rule);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
try {
|
||||
this->physical_printers.load_printers(dir_user_presets, "physical_printer");
|
||||
this->physical_printers.load_printers(dir_user_presets, "physical_printer", substitutions, substitution_rule);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
|
@ -236,6 +238,8 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_
|
|||
throw Slic3r::RuntimeError(errors_cummulative);
|
||||
|
||||
this->load_selections(config, preferred_model_id);
|
||||
|
||||
return substitutions;
|
||||
}
|
||||
|
||||
// Load system presets into this PresetBundle.
|
||||
|
@ -255,13 +259,13 @@ std::string PresetBundle::load_system_presets()
|
|||
// Load the config bundle, flatten it.
|
||||
if (first) {
|
||||
// Reset this PresetBundle and load the first vendor config.
|
||||
this->load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM);
|
||||
this->load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem);
|
||||
first = false;
|
||||
} else {
|
||||
// Load the other vendor configs, merge them with this PresetBundle.
|
||||
// Report duplicate profiles.
|
||||
PresetBundle other;
|
||||
other.load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM);
|
||||
other.load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem);
|
||||
std::vector<std::string> duplicates = this->merge_presets(std::move(other));
|
||||
if (! duplicates.empty()) {
|
||||
errors_cummulative += "Vendor configuration file " + name + " contains the following presets with names used by other vendors: ";
|
||||
|
@ -690,15 +694,15 @@ DynamicPrintConfig PresetBundle::full_sla_config() const
|
|||
// Instead of a config file, a G-code may be loaded containing the full set of parameters.
|
||||
// In the future the configuration will likely be read from an AMF file as well.
|
||||
// If the file is loaded successfully, its print / filament / printer profiles will be activated.
|
||||
void PresetBundle::load_config_file(const std::string &path)
|
||||
ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
{
|
||||
if (is_gcode_file(path)) {
|
||||
DynamicPrintConfig config;
|
||||
config.apply(FullPrintConfig::defaults());
|
||||
config.load_from_gcode_file(path);
|
||||
ConfigSubstitutions config_substitutions = config.load_from_gcode_file(path, true /* check_header */, compatibility_rule);
|
||||
Preset::normalize(config);
|
||||
load_config_file_config(path, true, std::move(config));
|
||||
return;
|
||||
return config_substitutions;
|
||||
}
|
||||
|
||||
// 1) Try to load the config file into a boost property tree.
|
||||
|
@ -717,6 +721,7 @@ void PresetBundle::load_config_file(const std::string &path)
|
|||
|
||||
// 2) Continue based on the type of the configuration file.
|
||||
ConfigFileType config_file_type = guess_config_file_type(tree);
|
||||
ConfigSubstitutions config_substitutions;
|
||||
switch (config_file_type) {
|
||||
case CONFIG_FILE_TYPE_UNKNOWN:
|
||||
throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path);
|
||||
|
@ -727,15 +732,18 @@ void PresetBundle::load_config_file(const std::string &path)
|
|||
// Initialize a config from full defaults.
|
||||
DynamicPrintConfig config;
|
||||
config.apply(FullPrintConfig::defaults());
|
||||
config.load(tree);
|
||||
config_substitutions = config.load(tree, compatibility_rule);
|
||||
Preset::normalize(config);
|
||||
load_config_file_config(path, true, std::move(config));
|
||||
break;
|
||||
}
|
||||
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
|
||||
load_config_file_config_bundle(path, tree);
|
||||
break;
|
||||
return config_substitutions;
|
||||
}
|
||||
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
|
||||
return load_config_file_config_bundle(path, tree);
|
||||
}
|
||||
|
||||
// This shall never happen. Suppres compiler warnings.
|
||||
assert(false);
|
||||
return ConfigSubstitutions{};
|
||||
}
|
||||
|
||||
// Load a config file from a boost property_tree. This is a private method called from load_config_file.
|
||||
|
@ -907,16 +915,25 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||
}
|
||||
|
||||
// Load the active configuration of a config bundle from a boost property_tree. This is a private method called from load_config_file.
|
||||
void PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree)
|
||||
ConfigSubstitutions PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree)
|
||||
{
|
||||
// 1) Load the config bundle into a temp data.
|
||||
PresetBundle tmp_bundle;
|
||||
// Load the config bundle, don't save the loaded presets to user profile directory.
|
||||
tmp_bundle.load_configbundle(path, 0);
|
||||
// Load the config bundle, but don't save the loaded presets to user profile directory, as only the presets marked as active in the loaded preset bundle
|
||||
// will be loaded into the master PresetBundle and activated.
|
||||
auto [presets_substitutions, presets_imported] = tmp_bundle.load_configbundle(path, {});
|
||||
UNUSED(presets_imported);
|
||||
|
||||
std::string bundle_name = std::string(" - ") + boost::filesystem::path(path).filename().string();
|
||||
|
||||
// 2) Extract active configs from the config bundle, copy them and activate them in this bundle.
|
||||
auto load_one = [&path, &bundle_name](PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string {
|
||||
ConfigSubstitutions config_substitutions;
|
||||
auto load_one = [&path, &bundle_name, &presets_substitutions = presets_substitutions, &config_substitutions](
|
||||
PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string {
|
||||
// If there are substitutions reported for this preset, move them to config_substitutions.
|
||||
if (auto it = std::find_if(presets_substitutions.begin(), presets_substitutions.end(), [&preset_name_src](const PresetConfigSubstitutions& subs){ return subs.preset_name == preset_name_src; });
|
||||
it != presets_substitutions.end() && ! it->substitutions.empty())
|
||||
append(config_substitutions, std::move(it->substitutions));
|
||||
Preset *preset_src = collection_src.find_preset(preset_name_src, false);
|
||||
Preset *preset_dst = collection_dst.find_preset(preset_name_src, false);
|
||||
assert(preset_src != nullptr);
|
||||
|
@ -970,6 +987,9 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const
|
|||
this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false);
|
||||
|
||||
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||
|
||||
sort_remove_duplicates(config_substitutions);
|
||||
return config_substitutions;
|
||||
}
|
||||
|
||||
// Process the Config Bundle loaded as a Boost property tree.
|
||||
|
@ -1114,11 +1134,20 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree, co
|
|||
|
||||
// Load a config bundle file, into presets and store the loaded presets into separate files
|
||||
// of the local configuration directory.
|
||||
size_t PresetBundle::load_configbundle(const std::string &path, unsigned int flags)
|
||||
std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(const std::string &path, LoadConfigBundleAttributes flags)
|
||||
{
|
||||
if (flags & (LOAD_CFGBNDLE_RESET_USER_PROFILE | LOAD_CFGBNDLE_SYSTEM))
|
||||
// Reset this bundle, delete user profile files if LOAD_CFGBNDLE_SAVE.
|
||||
this->reset(flags & LOAD_CFGBNDLE_SAVE);
|
||||
// Enable substitutions for user config bundle, throw an exception when loading a system profile.
|
||||
ConfigSubstitutionContext substitution_context {
|
||||
flags.has(LoadConfigBundleAttribute::LoadSystem) ?
|
||||
ForwardCompatibilitySubstitutionRule::Disable :
|
||||
ForwardCompatibilitySubstitutionRule::Enable
|
||||
};
|
||||
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
|
||||
if (flags.has(LoadConfigBundleAttribute::ResetUserProfile) || flags.has(LoadConfigBundleAttribute::LoadSystem))
|
||||
// Reset this bundle, delete user profile files if SaveImported.
|
||||
this->reset(flags.has(LoadConfigBundleAttribute::SaveImported));
|
||||
|
||||
// 1) Read the complete config file into a boost::property_tree.
|
||||
namespace pt = boost::property_tree;
|
||||
|
@ -1131,25 +1160,24 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
}
|
||||
|
||||
const VendorProfile *vendor_profile = nullptr;
|
||||
if (flags & (LOAD_CFGBNDLE_SYSTEM | LOAD_CFGBUNDLE_VENDOR_ONLY)) {
|
||||
if (flags.has(LoadConfigBundleAttribute::LoadSystem) || flags.has(LoadConfigBundleAttribute::LoadVendorOnly)) {
|
||||
auto vp = VendorProfile::from_ini(tree, path);
|
||||
if (vp.models.size() == 0) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer model defined.") % path;
|
||||
return 0;
|
||||
return std::make_pair(PresetsConfigSubstitutions{}, 0);
|
||||
} else if (vp.num_variants() == 0) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer variant defined") % path;
|
||||
return 0;
|
||||
return std::make_pair(PresetsConfigSubstitutions{}, 0);
|
||||
}
|
||||
vendor_profile = &this->vendors.insert({vp.id, vp}).first->second;
|
||||
}
|
||||
|
||||
if (flags & LOAD_CFGBUNDLE_VENDOR_ONLY) {
|
||||
return 0;
|
||||
}
|
||||
if (flags.has(LoadConfigBundleAttribute::LoadVendorOnly))
|
||||
return std::make_pair(PresetsConfigSubstitutions{}, 0);
|
||||
|
||||
// 1.5) Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed.
|
||||
// If loading a user config bundle, do not flatten with the system profiles, but keep the "inherits" flag intact.
|
||||
flatten_configbundle_hierarchy(tree, ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) ? this : nullptr);
|
||||
flatten_configbundle_hierarchy(tree, flags.has(LoadConfigBundleAttribute::LoadSystem) ? nullptr : this);
|
||||
|
||||
// 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files.
|
||||
// Parse the obsolete preset names, to be deleted when upgrading from the old configuration structure.
|
||||
|
@ -1246,7 +1274,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
DynamicPrintConfig config;
|
||||
std::string alias_name;
|
||||
std::vector<std::string> renamed_from;
|
||||
auto parse_config_section = [§ion, &alias_name, &renamed_from, &path](DynamicPrintConfig &config) {
|
||||
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();
|
||||
|
@ -1256,7 +1285,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
section.first << "\" contains invalid \"renamed_from\" key, which is being ignored.";
|
||||
}
|
||||
}
|
||||
config.set_deserialize(kvp.first, kvp.second.data());
|
||||
// Throws on parsing error. For system presets, no substituion is being done, but an exception is thrown.
|
||||
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
|
||||
}
|
||||
};
|
||||
if (presets == &this->printers) {
|
||||
|
@ -1277,7 +1307,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
if (! incorrect_keys.empty())
|
||||
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" <<
|
||||
section.first << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed";
|
||||
if ((flags & LOAD_CFGBNDLE_SYSTEM) && presets == &printers) {
|
||||
if (flags.has(LoadConfigBundleAttribute::LoadSystem) && presets == &printers) {
|
||||
// Filter out printer presets, which are not mentioned in the vendor profile.
|
||||
// These presets are considered not installed.
|
||||
auto printer_model = config.opt_string("printer_model");
|
||||
|
@ -1312,7 +1342,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
section.first << "\" has already been loaded from another Confing Bundle.";
|
||||
continue;
|
||||
}
|
||||
} else if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) {
|
||||
} else if (! flags.has(LoadConfigBundleAttribute::LoadSystem)) {
|
||||
// This is a user config bundle.
|
||||
const Preset *existing = presets->find_preset(preset_name, false);
|
||||
if (existing != nullptr) {
|
||||
|
@ -1341,9 +1371,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
/ presets->section_name() / file_name).make_preferred();
|
||||
// Load the preset into the list of presets, save it to disk.
|
||||
Preset &loaded = presets->load_preset(file_path.string(), preset_name, std::move(config), false);
|
||||
if (flags & LOAD_CFGBNDLE_SAVE)
|
||||
if (flags.has(LoadConfigBundleAttribute::SaveImported))
|
||||
loaded.save();
|
||||
if (flags & LOAD_CFGBNDLE_SYSTEM) {
|
||||
if (flags.has(LoadConfigBundleAttribute::LoadSystem)) {
|
||||
loaded.is_system = true;
|
||||
loaded.vendor = vendor_profile;
|
||||
}
|
||||
|
@ -1364,7 +1394,10 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
else
|
||||
loaded.alias = std::move(alias_name);
|
||||
loaded.renamed_from = std::move(renamed_from);
|
||||
|
||||
if (! substitution_context.empty())
|
||||
substitutions.push_back({
|
||||
preset_name, presets->type(), PresetConfigSubstitutions::Source::ConfigBundle,
|
||||
std::string(), std::move(substitution_context.substitutions) });
|
||||
++ presets_loaded;
|
||||
}
|
||||
|
||||
|
@ -1373,8 +1406,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
const DynamicPrintConfig& default_config = ph_printers->default_config();
|
||||
DynamicPrintConfig config = default_config;
|
||||
|
||||
substitution_context.substitutions.clear();
|
||||
for (auto& kvp : section.second)
|
||||
config.set_deserialize(kvp.first, kvp.second.data());
|
||||
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
|
||||
|
||||
// Report configuration fields, which are misplaced into a wrong group.
|
||||
std::string incorrect_keys = Preset::remove_invalid_keys(config, default_config);
|
||||
|
@ -1400,14 +1434,17 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
#endif
|
||||
/ "physical_printer" / file_name).make_preferred();
|
||||
// Load the preset into the list of presets, save it to disk.
|
||||
ph_printers->load_printer(file_path.string(), ph_printer_name, std::move(config), false, flags & LOAD_CFGBNDLE_SAVE);
|
||||
|
||||
++ph_printers_loaded;
|
||||
ph_printers->load_printer(file_path.string(), ph_printer_name, std::move(config), false, flags.has(LoadConfigBundleAttribute::SaveImported));
|
||||
if (! substitution_context.empty())
|
||||
substitutions.push_back({
|
||||
ph_printer_name, Preset::TYPE_PHYSICAL_PRINTER, PresetConfigSubstitutions::Source::ConfigBundle,
|
||||
std::string(), std::move(substitution_context.substitutions) });
|
||||
++ ph_printers_loaded;
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Activate the presets and physical printer if any exists.
|
||||
if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) {
|
||||
if (! flags.has(LoadConfigBundleAttribute::LoadSystem)) {
|
||||
if (! active_print.empty())
|
||||
prints.select_preset_by_name(active_print, true);
|
||||
if (! active_sla_print.empty())
|
||||
|
@ -1427,7 +1464,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
|
|||
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||
}
|
||||
|
||||
return presets_loaded + ph_printers_loaded;
|
||||
return std::make_pair(std::move(substitutions), presets_loaded + ph_printers_loaded);
|
||||
}
|
||||
|
||||
void PresetBundle::update_multi_material_filament_presets()
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "Preset.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
#include "enum_bitmask.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
@ -26,7 +27,7 @@ public:
|
|||
|
||||
// Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets.
|
||||
// Load selections (current print, current filaments, current printer) from config.ini
|
||||
void load_presets(AppConfig &config, const std::string &preferred_model_id = std::string());
|
||||
PresetsConfigSubstitutions load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule rule, const std::string &preferred_model_id = std::string());
|
||||
|
||||
// Export selections (current print, current filaments, current printer) into config.ini
|
||||
void export_selections(AppConfig &config);
|
||||
|
@ -82,24 +83,26 @@ public:
|
|||
// Instead of a config file, a G-code may be loaded containing the full set of parameters.
|
||||
// In the future the configuration will likely be read from an AMF file as well.
|
||||
// If the file is loaded successfully, its print / filament / printer profiles will be activated.
|
||||
void load_config_file(const std::string &path);
|
||||
ConfigSubstitutions load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
|
||||
// Load a config bundle file, into presets and store the loaded presets into separate files
|
||||
// of the local configuration directory.
|
||||
// Load settings into the provided settings instance.
|
||||
// Activate the presets stored in the config bundle.
|
||||
// Returns the number of presets loaded successfully.
|
||||
enum {
|
||||
enum LoadConfigBundleAttribute {
|
||||
// Save the profiles, which have been loaded.
|
||||
LOAD_CFGBNDLE_SAVE = 1,
|
||||
SaveImported,
|
||||
// Delete all old config profiles before loading.
|
||||
LOAD_CFGBNDLE_RESET_USER_PROFILE = 2,
|
||||
ResetUserProfile,
|
||||
// Load a system config bundle.
|
||||
LOAD_CFGBNDLE_SYSTEM = 4,
|
||||
LOAD_CFGBUNDLE_VENDOR_ONLY = 8,
|
||||
LoadSystem,
|
||||
LoadVendorOnly,
|
||||
};
|
||||
// Load the config bundle, store it to the user profile directory by default.
|
||||
size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE);
|
||||
using LoadConfigBundleAttributes = enum_bitmask<LoadConfigBundleAttribute>;
|
||||
// Load the config bundle based on the flags.
|
||||
// Don't do any config substitutions when loading a system profile, perform and report substitutions otherwise.
|
||||
std::pair<PresetsConfigSubstitutions, size_t> load_configbundle(const std::string &path, LoadConfigBundleAttributes flags);
|
||||
|
||||
// Export a config bundle file containing all the presets and the names of the active presets.
|
||||
void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false);
|
||||
|
@ -155,12 +158,14 @@ private:
|
|||
// and the external config is just referenced, not stored into user profile directory.
|
||||
// If it is not an external config, then the config will be stored into the user profile directory.
|
||||
void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config);
|
||||
void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
|
||||
ConfigSubstitutions load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
|
||||
|
||||
DynamicPrintConfig full_fff_config() const;
|
||||
DynamicPrintConfig full_sla_config() const;
|
||||
};
|
||||
|
||||
ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute)
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_PresetBundle_hpp_ */
|
||||
|
|
|
@ -448,9 +448,9 @@ public:
|
|||
// Canceled internally from Print::apply() through the Print/PrintObject::invalidate_step() or ::invalidate_all_steps().
|
||||
CANCELED_INTERNAL = 2
|
||||
};
|
||||
CancelStatus cancel_status() const { return m_cancel_status; }
|
||||
CancelStatus cancel_status() const { return m_cancel_status.load(std::memory_order_acquire); }
|
||||
// Has the calculation been canceled?
|
||||
bool canceled() const { return m_cancel_status != NOT_CANCELED; }
|
||||
bool canceled() const { return m_cancel_status.load(std::memory_order_acquire) != NOT_CANCELED; }
|
||||
// Cancel the running computation. Stop execution of all the background threads.
|
||||
void cancel() { m_cancel_status = CANCELED_BY_USER; }
|
||||
void cancel_internal() { m_cancel_status = CANCELED_INTERNAL; }
|
||||
|
@ -481,7 +481,7 @@ protected:
|
|||
|
||||
// If the background processing stop was requested, throw CanceledException.
|
||||
// To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly.
|
||||
void throw_if_canceled() const { if (m_cancel_status) throw CanceledException(); }
|
||||
void throw_if_canceled() const { if (m_cancel_status.load(std::memory_order_acquire)) throw CanceledException(); }
|
||||
// Wrapper around this->throw_if_canceled(), so that throw_if_canceled() may be passed to a function without making throw_if_canceled() public.
|
||||
PrintTryCancel make_try_cancel() const { return PrintTryCancel(this); }
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ static t_config_enum_values s_keys_map_GCodeFlavor {
|
|||
{ "teacup", gcfTeacup },
|
||||
{ "makerware", gcfMakerWare },
|
||||
{ "marlin", gcfMarlinLegacy },
|
||||
{ "marlinfirmware", gcfMarlinFirmware },
|
||||
{ "marlin2", gcfMarlinFirmware },
|
||||
{ "sailfish", gcfSailfish },
|
||||
{ "smoothie", gcfSmoothie },
|
||||
{ "mach3", gcfMach3 },
|
||||
|
@ -67,6 +67,7 @@ static t_config_enum_values s_keys_map_MachineLimitsUsage {
|
|||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(MachineLimitsUsage)
|
||||
|
||||
static t_config_enum_values s_keys_map_PrintHostType {
|
||||
{ "prusalink", htPrusaLink },
|
||||
{ "octoprint", htOctoPrint },
|
||||
{ "duet", htDuet },
|
||||
{ "flashair", htFlashAir },
|
||||
|
@ -172,6 +173,13 @@ static const t_config_enum_values s_keys_map_BrimType = {
|
|||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BrimType)
|
||||
|
||||
static const t_config_enum_values s_keys_map_ForwardCompatibilitySubstitutionRule = {
|
||||
{ "disable", ForwardCompatibilitySubstitutionRule::Disable },
|
||||
{ "enable", ForwardCompatibilitySubstitutionRule::Enable },
|
||||
{ "enable_silent", ForwardCompatibilitySubstitutionRule::EnableSilent }
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
|
||||
|
||||
static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology)
|
||||
{
|
||||
for (std::pair<const t_config_option_key, ConfigOptionDef> &kvp : options)
|
||||
|
@ -1245,7 +1253,7 @@ void PrintConfigDef::init_fff_params()
|
|||
def->enum_values.push_back("teacup");
|
||||
def->enum_values.push_back("makerware");
|
||||
def->enum_values.push_back("marlin");
|
||||
def->enum_values.push_back("marlinfirmware");
|
||||
def->enum_values.push_back("marlin2");
|
||||
def->enum_values.push_back("sailfish");
|
||||
def->enum_values.push_back("mach3");
|
||||
def->enum_values.push_back("machinekit");
|
||||
|
@ -1772,11 +1780,13 @@ void PrintConfigDef::init_fff_params()
|
|||
def->tooltip = L("Slic3r can upload G-code files to a printer host. This field must contain "
|
||||
"the kind of the host.");
|
||||
def->enum_keys_map = &ConfigOptionEnum<PrintHostType>::get_enum_values();
|
||||
def->enum_values.push_back("prusalink");
|
||||
def->enum_values.push_back("octoprint");
|
||||
def->enum_values.push_back("duet");
|
||||
def->enum_values.push_back("flashair");
|
||||
def->enum_values.push_back("astrobox");
|
||||
def->enum_values.push_back("repetier");
|
||||
def->enum_labels.push_back("PrusaLink");
|
||||
def->enum_labels.push_back("OctoPrint");
|
||||
def->enum_labels.push_back("Duet");
|
||||
def->enum_labels.push_back("FlashAir");
|
||||
|
@ -3608,8 +3618,12 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
|
|||
} catch (boost::bad_lexical_cast &) {
|
||||
value = "0";
|
||||
}
|
||||
} else if (opt_key == "gcode_flavor" && value == "makerbot") {
|
||||
value = "makerware";
|
||||
} else if (opt_key == "gcode_flavor") {
|
||||
if (value == "makerbot")
|
||||
value = "makerware";
|
||||
else if (value == "marlinfirmware")
|
||||
// the "new" marlin firmware flavor used to be called "marlinfirmware" for some time during PrusaSlicer 2.4.0-alpha development.
|
||||
value = "marlin2";
|
||||
} else if (opt_key == "fill_density" && value.find("%") == std::string::npos) {
|
||||
try {
|
||||
// fill_density was turned into a percent value
|
||||
|
@ -3622,7 +3636,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
|
|||
} else if (opt_key == "bed_size" && !value.empty()) {
|
||||
opt_key = "bed_shape";
|
||||
ConfigOptionPoint p;
|
||||
p.deserialize(value);
|
||||
p.deserialize(value, ForwardCompatibilitySubstitutionRule::Disable);
|
||||
std::ostringstream oss;
|
||||
oss << "0x0," << p.value(0) << "x0," << p.value(0) << "x" << p.value(1) << ",0x" << p.value(1);
|
||||
value = oss.str();
|
||||
|
@ -4171,6 +4185,20 @@ CLIMiscConfigDef::CLIMiscConfigDef()
|
|||
def->label = L("Ignore non-existent config files");
|
||||
def->tooltip = L("Do not fail if a file supplied to --load does not exist.");
|
||||
|
||||
def = this->add("config_compatibility", coEnum);
|
||||
def->label = L("Forward-compatibility rule when loading configurations from config files and project files (3MF, AMF).");
|
||||
def->tooltip = L("This version of PrusaSlicer may not understand configurations produced by newest PrusaSlicer versions. "
|
||||
"For example, newer PrusaSlicer may extend the list of supported firmware flavors. One may decide to "
|
||||
"bail out or to substitute an unknown value with a default silently or verbosely.");
|
||||
def->enum_keys_map = &ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>::get_enum_values();
|
||||
def->enum_values.push_back("disable");
|
||||
def->enum_values.push_back("enable");
|
||||
def->enum_values.push_back("enable_silent");
|
||||
def->enum_labels.push_back(L("Bail out on unknown configuration values"));
|
||||
def->enum_labels.push_back(L("Enable reading unknown configuration values by verbosely substituting them with defaults."));
|
||||
def->enum_labels.push_back(L("Enable reading unknown configuration values by silently substituting them with defaults."));
|
||||
def->set_default_value(new ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>(ForwardCompatibilitySubstitutionRule::Enable));
|
||||
|
||||
def = this->add("load", coStrings);
|
||||
def->label = L("Load config file");
|
||||
def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files.");
|
||||
|
|
|
@ -44,7 +44,7 @@ enum class MachineLimitsUsage {
|
|||
};
|
||||
|
||||
enum PrintHostType {
|
||||
htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier
|
||||
htPrusaLink, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier
|
||||
};
|
||||
|
||||
enum AuthorizationType {
|
||||
|
@ -141,6 +141,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SeamPosition)
|
|||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLADisplayOrientation)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
|
||||
|
||||
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
|
||||
|
||||
|
@ -1086,8 +1087,8 @@ public:
|
|||
bool set_key_value(const std::string &opt_key, ConfigOption *opt) { bool out = m_data.set_key_value(opt_key, opt); this->touch(); return out; }
|
||||
template<typename T>
|
||||
void set(const std::string &opt_key, T value) { m_data.set(opt_key, value, true); this->touch(); }
|
||||
void set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false)
|
||||
{ m_data.set_deserialize(opt_key, str, append); this->touch(); }
|
||||
void set_deserialize(const t_config_option_key &opt_key, const std::string &str, ConfigSubstitutionContext &substitution_context, bool append = false)
|
||||
{ m_data.set_deserialize(opt_key, str, substitution_context, append); this->touch(); }
|
||||
bool erase(const t_config_option_key &opt_key) { bool out = m_data.erase(opt_key); if (out) this->touch(); return out; }
|
||||
|
||||
// Getters are thread safe.
|
||||
|
|
|
@ -10,11 +10,11 @@
|
|||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <tbb/global_control.h>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/task_arena.h>
|
||||
|
||||
#include "Thread.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -199,16 +199,14 @@ void name_tbb_thread_pool_threads()
|
|||
// TBB will respect the task affinity mask on Linux and spawn less threads than std::thread::hardware_concurrency().
|
||||
// const size_t nthreads_hw = std::thread::hardware_concurrency();
|
||||
const size_t nthreads_hw = tbb::this_task_arena::max_concurrency();
|
||||
size_t nthreads = nthreads_hw;
|
||||
size_t nthreads = nthreads_hw;
|
||||
|
||||
#ifdef SLIC3R_PROFILE
|
||||
// Shiny profiler is not thread safe, thus disable parallelization.
|
||||
disable_multi_threading();
|
||||
nthreads = 1;
|
||||
#endif
|
||||
|
||||
if (nthreads != nthreads_hw)
|
||||
tbb::global_control(tbb::global_control::max_allowed_parallelism, nthreads);
|
||||
|
||||
std::atomic<size_t> nthreads_running(0);
|
||||
std::condition_variable cv;
|
||||
std::mutex cv_m;
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#ifndef _NDEBUG
|
||||
#ifndef NDEBUG
|
||||
#define EXPENSIVE_DEBUG_CHECKS
|
||||
#endif // _NDEBUG
|
||||
#endif // NDEBUG
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -19,7 +19,7 @@ static inline Vec3i root_neighbors(const TriangleMesh &mesh, int triangle_id)
|
|||
return neighbors;
|
||||
}
|
||||
|
||||
#ifndef _NDEBUG
|
||||
#ifndef NDEBUG
|
||||
bool TriangleSelector::verify_triangle_midpoints(const Triangle &tr) const
|
||||
{
|
||||
for (int i = 0; i < 3; ++ i) {
|
||||
|
@ -57,7 +57,7 @@ bool TriangleSelector::verify_triangle_neighbors(const Triangle &tr, const Vec3i
|
|||
}
|
||||
return true;
|
||||
}
|
||||
#endif // _NDEBUG
|
||||
#endif // NDEBUG
|
||||
|
||||
// sides_to_split==-1 : just restore previous split
|
||||
void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx)
|
||||
|
@ -308,12 +308,12 @@ int TriangleSelector::triangle_midpoint_or_allocate(int itriangle, int vertexi,
|
|||
}
|
||||
assert(m_vertices[midpoint].ref_cnt == 0);
|
||||
} else {
|
||||
#ifndef _NDEBUG
|
||||
#ifndef NDEBUG
|
||||
Vec3f c1 = 0.5f * (m_vertices[vertexi].v + m_vertices[vertexj].v);
|
||||
Vec3f c2 = m_vertices[midpoint].v;
|
||||
float d = (c2 - c1).norm();
|
||||
assert(std::abs(d) < EPSILON);
|
||||
#endif // _NDEBUG
|
||||
#endif // NDEBUG
|
||||
assert(m_vertices[midpoint].ref_cnt > 0);
|
||||
}
|
||||
return midpoint;
|
||||
|
@ -816,13 +816,13 @@ void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, Enfo
|
|||
assert(tr.is_split());
|
||||
|
||||
// indices of triangle vertices
|
||||
#ifdef _NDEBUG
|
||||
#ifdef NDEBUG
|
||||
boost::container::small_vector<int, 6> verts_idxs;
|
||||
#else // _NDEBUG
|
||||
#else // NDEBUG
|
||||
// For easier debugging.
|
||||
std::vector<int> verts_idxs;
|
||||
verts_idxs.reserve(6);
|
||||
#endif // _NDEBUG
|
||||
#endif // NDEBUG
|
||||
for (int j=0, idx = tr.special_side(); j<3; ++j, idx = next_idx_modulo(idx, 3))
|
||||
verts_idxs.push_back(tr.verts_idxs[idx]);
|
||||
|
||||
|
@ -861,13 +861,13 @@ void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, Enfo
|
|||
break;
|
||||
}
|
||||
|
||||
#ifndef _NDEBUG
|
||||
#ifndef NDEBUG
|
||||
assert(this->verify_triangle_neighbors(tr, neighbors));
|
||||
for (int i = 0; i <= tr.number_of_split_sides(); ++i) {
|
||||
Vec3i n = this->child_neighbors(tr, neighbors, i);
|
||||
assert(this->verify_triangle_neighbors(m_triangles[tr.children[i]], n));
|
||||
}
|
||||
#endif // _NDEBUG
|
||||
#endif // NDEBUG
|
||||
}
|
||||
|
||||
bool TriangleSelector::has_facets(EnforcerBlockerType state) const
|
||||
|
|
|
@ -204,10 +204,10 @@ private:
|
|||
int triangle_midpoint(int itriangle, int vertexi, int vertexj) const;
|
||||
int triangle_midpoint_or_allocate(int itriangle, int vertexi, int vertexj);
|
||||
|
||||
#ifndef _NDEBUG
|
||||
#ifndef NDEBUG
|
||||
bool verify_triangle_neighbors(const Triangle& tr, const Vec3i& neighbors) const;
|
||||
bool verify_triangle_midpoints(const Triangle& tr) const;
|
||||
#endif // _NDEBUG
|
||||
#endif // NDEBUG
|
||||
|
||||
void get_facets_strict_recursive(
|
||||
const Triangle &tr,
|
||||
|
|
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_
|
|
@ -115,6 +115,7 @@
|
|||
#include "BoundingBox.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Config.hpp"
|
||||
#include "enum_bitmask.hpp"
|
||||
#include "format.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "MultiPoint.hpp"
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "Platform.hpp"
|
||||
#include "Time.hpp"
|
||||
#include "libslic3r.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
|
@ -43,7 +44,13 @@
|
|||
#include <boost/nowide/convert.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
|
||||
#include <tbb/global_control.h>
|
||||
// We are using quite an old TBB 2017 U7, which does not support global control API officially.
|
||||
// Before we update our build servers, let's use the old API, which is deprecated in up to date TBB.
|
||||
#ifdef TBB_HAS_GLOBAL_CONTROL
|
||||
#include <tbb/global_control.h>
|
||||
#else
|
||||
#include <tbb/task_scheduler_init.h>
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) || defined(__GNUC__ )
|
||||
#include <strings.h>
|
||||
|
@ -118,7 +125,12 @@ void trace(unsigned int level, const char *message)
|
|||
void disable_multi_threading()
|
||||
{
|
||||
// Disable parallelization so the Shiny profiler works
|
||||
#ifdef TBB_HAS_GLOBAL_CONTROL
|
||||
tbb::global_control(tbb::global_control::max_allowed_parallelism, 1);
|
||||
#else // TBB_HAS_GLOBAL_CONTROL
|
||||
static tbb::task_scheduler_init *tbb_init = new tbb::task_scheduler_init(1);
|
||||
UNUSED(tbb_init);
|
||||
#endif // TBB_HAS_GLOBAL_CONTROL
|
||||
}
|
||||
|
||||
static std::string g_var_dir;
|
||||
|
|
|
@ -181,6 +181,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/Mouse3DController.hpp
|
||||
GUI/DoubleSlider.cpp
|
||||
GUI/DoubleSlider.hpp
|
||||
GUI/Notebook.cpp
|
||||
GUI/Notebook.hpp
|
||||
GUI/ObjectDataViewModel.cpp
|
||||
GUI/ObjectDataViewModel.hpp
|
||||
GUI/InstanceCheck.cpp
|
||||
|
|
|
@ -413,7 +413,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
|
|||
++ it;
|
||||
// Read the active config bundle, parse the config version.
|
||||
PresetBundle bundle;
|
||||
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY);
|
||||
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly);
|
||||
for (const auto &vp : bundle.vendors)
|
||||
if (vp.second.id == cfg.name)
|
||||
cfg.version.config_version = vp.second.config_version;
|
||||
|
|
|
@ -255,7 +255,6 @@ private:
|
|||
std::shared_ptr<UITask> m_ui_task;
|
||||
|
||||
PrintState<BackgroundSlicingProcessStep, bspsCount> m_step_state;
|
||||
mutable tbb::mutex m_step_state_mutex;
|
||||
bool set_step_started(BackgroundSlicingProcessStep step);
|
||||
void set_step_done(BackgroundSlicingProcessStep step);
|
||||
bool is_step_done(BackgroundSlicingProcessStep step) const;
|
||||
|
|
|
@ -64,7 +64,9 @@ bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bu
|
|||
this->is_prusa_bundle = ais_prusa_bundle;
|
||||
|
||||
std::string path_string = source_path.string();
|
||||
size_t presets_loaded = preset_bundle->load_configbundle(path_string, PresetBundle::LOAD_CFGBNDLE_SYSTEM);
|
||||
auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle(path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem);
|
||||
// No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
|
||||
assert(config_substitutions.empty());
|
||||
auto first_vendor = preset_bundle->vendors.begin();
|
||||
if (first_vendor == preset_bundle->vendors.end()) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string;
|
||||
|
@ -2592,7 +2594,10 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
|||
}
|
||||
}
|
||||
|
||||
preset_bundle->load_presets(*app_config, preferred_model);
|
||||
// Reloading the configs after some modifications were done to PrusaSlicer.ini.
|
||||
// Just perform the substitutions silently, as the substitutions were already presented to the user on application start-up
|
||||
// and the Wizard shall not create any new values that would require substitution.
|
||||
PresetsConfigSubstitutions substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent, preferred_model);
|
||||
|
||||
if (page_custom->custom_wanted()) {
|
||||
page_firmware->apply_custom_config(*custom_config);
|
||||
|
|
|
@ -2010,11 +2010,11 @@ void Control::show_cog_icon_context_menu()
|
|||
[]() { return true; }, [this]() { return m_extra_style == 0; }, GUI::wxGetApp().plater());
|
||||
|
||||
append_menu_check_item(ruler_mode_menu, wxID_ANY, _L("Show object height"), _L("Show object height on the ruler"),
|
||||
[this](wxCommandEvent&) { m_extra_style & wxSL_AUTOTICKS ? m_extra_style &= wxSL_AUTOTICKS : m_extra_style |= wxSL_AUTOTICKS; }, ruler_mode_menu,
|
||||
[this](wxCommandEvent&) { m_extra_style & wxSL_AUTOTICKS ? m_extra_style ^= wxSL_AUTOTICKS : m_extra_style |= wxSL_AUTOTICKS; }, ruler_mode_menu,
|
||||
[]() { return true; }, [this]() { return m_extra_style & wxSL_AUTOTICKS; }, GUI::wxGetApp().plater());
|
||||
|
||||
append_menu_check_item(ruler_mode_menu, wxID_ANY, _L("Show estimated print time"), _L("Show estimated print time on the ruler"),
|
||||
[this](wxCommandEvent&) { m_extra_style & wxSL_VALUE_LABEL ? m_extra_style &= wxSL_VALUE_LABEL : m_extra_style |= wxSL_VALUE_LABEL; }, ruler_mode_menu,
|
||||
[this](wxCommandEvent&) { m_extra_style & wxSL_VALUE_LABEL ? m_extra_style ^= wxSL_VALUE_LABEL : m_extra_style |= wxSL_VALUE_LABEL; }, ruler_mode_menu,
|
||||
[]() { return true; }, [this]() { return m_extra_style & wxSL_VALUE_LABEL; }, GUI::wxGetApp().plater());
|
||||
|
||||
append_submenu(&menu, ruler_mode_menu, wxID_ANY, _L("Ruler mode"), _L("Set ruler mode"), "",
|
||||
|
|
|
@ -1141,6 +1141,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 == "fill_pattern")
|
||||
{
|
||||
std::string key;
|
||||
|
@ -1197,7 +1201,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);
|
||||
|
@ -1219,7 +1223,10 @@ boost::any& Choice::get_value()
|
|||
|
||||
if (m_opt.type == coEnum)
|
||||
{
|
||||
if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern") {
|
||||
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
|
||||
m_value = field->GetSelection()+1;
|
||||
} else if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern") {
|
||||
const std::string& key = m_opt.enum_values[field->GetSelection()];
|
||||
m_value = int(ConfigOptionEnum<InfillPattern>::get_enum_values().at(key));
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ namespace boost::filesystem { class path; }
|
|||
|
||||
class wxWindow;
|
||||
class wxMenuBar;
|
||||
class wxNotebook;
|
||||
class wxComboCtrl;
|
||||
class wxFileDialog;
|
||||
class wxTopLevelWindow;
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
#include "DesktopIntegrationDialog.hpp"
|
||||
|
||||
#include "BitmapCache.hpp"
|
||||
#include "Notebook.hpp"
|
||||
|
||||
#ifdef __WXMSW__
|
||||
#include <dbt.h>
|
||||
|
@ -624,6 +625,13 @@ void GUI_App::post_init()
|
|||
this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str()));
|
||||
}
|
||||
else {
|
||||
if (! this->init_params->preset_substitutions.empty()) {
|
||||
// TODO: Add list of changes from all_substitutions
|
||||
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
|
||||
" To recover these files, incompatible values were changed to default values."
|
||||
" But data in files won't be changed until you save them in PrusaSlicer.")));
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Load the cummulative config over the currently active profiles.
|
||||
//FIXME if multiple configs are loaded, only the last one will have an effect.
|
||||
|
@ -652,6 +660,24 @@ void GUI_App::post_init()
|
|||
if (! this->init_params->extra_config.empty())
|
||||
this->mainframe->load_config(this->init_params->extra_config);
|
||||
}
|
||||
|
||||
// The extra CallAfter() is needed because of Mac, where this is the only way
|
||||
// to popup a modal dialog on start without screwing combo boxes.
|
||||
// This is ugly but I honestly found no better way to do it.
|
||||
// Neither wxShowEvent nor wxWindowCreateEvent work reliably.
|
||||
if (this->preset_updater) {
|
||||
this->check_updates(false);
|
||||
CallAfter([this] {
|
||||
this->config_wizard_startup();
|
||||
this->preset_updater->slic3r_update_notify();
|
||||
this->preset_updater->sync(preset_bundle);
|
||||
});
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// Sets window property to mainframe so other instances can indentify it.
|
||||
OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int);
|
||||
#endif //WIN32
|
||||
}
|
||||
|
||||
IMPLEMENT_APP(GUI_App)
|
||||
|
@ -885,7 +911,7 @@ bool GUI_App::on_init_inner()
|
|||
// Suppress the '- default -' presets.
|
||||
preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1");
|
||||
try {
|
||||
preset_bundle->load_presets(*app_config);
|
||||
init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
} catch (const std::exception &ex) {
|
||||
show_error(nullptr, ex.what());
|
||||
}
|
||||
|
@ -904,8 +930,6 @@ bool GUI_App::on_init_inner()
|
|||
if (scrn && is_editor())
|
||||
scrn->SetText(_L("Preparing settings tabs") + dots);
|
||||
|
||||
m_tabs_as_menu = dark_mode() || app_config->get("tabs_as_menu") == "1";
|
||||
|
||||
mainframe = new MainFrame();
|
||||
// hide settings tabs after first Layout
|
||||
if (is_editor())
|
||||
|
@ -948,7 +972,6 @@ bool GUI_App::on_init_inner()
|
|||
if (! plater_)
|
||||
return;
|
||||
|
||||
|
||||
if (app_config->dirty() && app_config->get("autosave") == "1")
|
||||
app_config->save();
|
||||
|
||||
|
@ -969,33 +992,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;
|
||||
|
@ -1046,6 +1042,7 @@ void GUI_App::init_label_colours()
|
|||
m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT);
|
||||
m_color_highlight_default = is_dark_mode ? wxColour(78, 78, 78) : wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT);
|
||||
m_color_hovered_btn_label = is_dark_mode ? wxColour(253, 111, 40) : wxColour(252, 77, 1);
|
||||
m_color_selected_btn_bg = is_dark_mode ? wxColour(95, 73, 62) : wxColour(228, 220, 216);
|
||||
#else
|
||||
m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
#endif
|
||||
|
@ -1093,24 +1090,22 @@ void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool ju
|
|||
btn->Bind(wxEVT_LEAVE_WINDOW, [focus_button](wxMouseEvent& event) { focus_button(false); event.Skip(); });
|
||||
}
|
||||
}
|
||||
else if (dark_mode()) {
|
||||
if (wxTextCtrl* text = dynamic_cast<wxTextCtrl*>(window)) {
|
||||
if (text->GetBorder() != wxBORDER_SIMPLE)
|
||||
text->SetWindowStyle(text->GetWindowStyle() | wxBORDER_SIMPLE);
|
||||
}
|
||||
else if (wxCheckListBox* list = dynamic_cast<wxCheckListBox*>(window)) {
|
||||
list->SetWindowStyle(list->GetWindowStyle() | wxBORDER_SIMPLE);
|
||||
list->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default);
|
||||
for (size_t i = 0; i < list->GetCount(); i++)
|
||||
if (wxOwnerDrawn* item = list->GetItem(i)) {
|
||||
item->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default);
|
||||
item->SetTextColour(m_color_label_default);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (dynamic_cast<wxListBox*>(window))
|
||||
window->SetWindowStyle(window->GetWindowStyle() | wxBORDER_SIMPLE);
|
||||
else if (wxTextCtrl* text = dynamic_cast<wxTextCtrl*>(window)) {
|
||||
if (text->GetBorder() != wxBORDER_SIMPLE)
|
||||
text->SetWindowStyle(text->GetWindowStyle() | wxBORDER_SIMPLE);
|
||||
}
|
||||
else if (wxCheckListBox* list = dynamic_cast<wxCheckListBox*>(window)) {
|
||||
list->SetWindowStyle(list->GetWindowStyle() | wxBORDER_SIMPLE);
|
||||
list->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default);
|
||||
for (size_t i = 0; i < list->GetCount(); i++)
|
||||
if (wxOwnerDrawn* item = list->GetItem(i)) {
|
||||
item->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default);
|
||||
item->SetTextColour(m_color_label_default);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (dynamic_cast<wxListBox*>(window))
|
||||
window->SetWindowStyle(window->GetWindowStyle() | wxBORDER_SIMPLE);
|
||||
|
||||
if (!just_font)
|
||||
window->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default);
|
||||
|
@ -1141,8 +1136,6 @@ void GUI_App::UpdateDlgDarkUI(wxDialog* dlg)
|
|||
void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!dark_mode())
|
||||
return;
|
||||
UpdateDarkUI(dvc, highlited);
|
||||
wxItemAttr attr(dark_mode() ? m_color_highlight_default : m_color_label_default,
|
||||
m_color_window_default,
|
||||
|
@ -1158,8 +1151,6 @@ void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/)
|
|||
void GUI_App::UpdateAllStaticTextDarkUI(wxWindow* parent)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!dark_mode())
|
||||
return;
|
||||
wxGetApp().UpdateDarkUI(parent);
|
||||
|
||||
auto children = parent->GetChildren();
|
||||
|
@ -1225,6 +1216,11 @@ void GUI_App::set_label_clr_sys(const wxColour& clr)
|
|||
app_config->save();
|
||||
}
|
||||
|
||||
bool GUI_App::tabs_as_menu() const
|
||||
{
|
||||
return app_config->get("tabs_as_menu") == "1"; // || dark_mode();
|
||||
}
|
||||
|
||||
wxSize GUI_App::get_min_size() const
|
||||
{
|
||||
return wxSize(76*m_em_unit, 49 * m_em_unit);
|
||||
|
@ -1369,6 +1365,14 @@ void fatal_error(wxWindow* parent)
|
|||
// exit 1; // #ys_FIXME
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
void GUI_App::force_colors_update()
|
||||
{
|
||||
NppDarkMode::SetDarkMode(app_config->get("dark_color_mode") == "1");
|
||||
m_force_colors_update = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Called after the Preferences dialog is closed and the program settings are saved.
|
||||
// Update the UI based on the current preferences.
|
||||
void GUI_App::update_ui_from_settings()
|
||||
|
@ -1376,13 +1380,13 @@ void GUI_App::update_ui_from_settings()
|
|||
update_label_colours();
|
||||
mainframe->update_ui_from_settings();
|
||||
|
||||
#if 0 //#ifdef _WIN32 // #ysDarkMSW - Use to force dark colors for SystemLightMode
|
||||
if (m_force_sys_colors_update) {
|
||||
m_force_sys_colors_update = false;
|
||||
mainframe->force_sys_color_changed();
|
||||
mainframe->diff_dialog.force_sys_color_changed();
|
||||
#ifdef _WIN32
|
||||
if (m_force_colors_update) {
|
||||
m_force_colors_update = false;
|
||||
mainframe->force_color_changed();
|
||||
mainframe->diff_dialog.force_color_changed();
|
||||
if (m_wizard)
|
||||
m_wizard->force_sys_color_changed();
|
||||
m_wizard->force_color_changed();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -1766,6 +1770,11 @@ void GUI_App::update_mode()
|
|||
{
|
||||
sidebar().update_mode();
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (!wxGetApp().tabs_as_menu())
|
||||
dynamic_cast<Notebook*>(mainframe->m_tabpanel)->UpdateMode();
|
||||
#endif
|
||||
|
||||
for (auto tab : tabs_list)
|
||||
tab->update_mode();
|
||||
|
||||
|
@ -1872,7 +1881,13 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
|
|||
Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK);
|
||||
try {
|
||||
app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id);
|
||||
preset_bundle->load_presets(*app_config);
|
||||
if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
! all_substitutions.empty()) {
|
||||
// TODO:
|
||||
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
|
||||
" To recover these files, incompatible values were changed to default values."
|
||||
" But data in files won't be changed until you save them in PrusaSlicer.")));
|
||||
}
|
||||
// Load the currently selected preset into the GUI, update the preset selection box.
|
||||
load_current_presets();
|
||||
} catch (std::exception &ex) {
|
||||
|
@ -1899,13 +1914,6 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
|
|||
this->plater_->refresh_print();
|
||||
|
||||
if (dlg.recreate_GUI()) {
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (dlg.color_mode_changed()) {
|
||||
NppDarkMode::SetDarkMode(app_config->get("dark_color_mode") == "1");
|
||||
init_label_colours();
|
||||
}
|
||||
#endif
|
||||
m_tabs_as_menu = dark_mode() || app_config->get("tabs_as_menu") == "1";
|
||||
recreate_GUI(_L("Restart application") + dots);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -124,11 +124,10 @@ private:
|
|||
wxColour m_color_highlight_label_default;
|
||||
wxColour m_color_hovered_btn_label;
|
||||
wxColour m_color_highlight_default;
|
||||
//bool m_force_sys_colors_update { false }; // #ysDarkMSW - Use to force dark colors for SystemLightMode
|
||||
wxColour m_color_selected_btn_bg;
|
||||
bool m_force_colors_update { false };
|
||||
#endif
|
||||
|
||||
bool m_tabs_as_menu{ false };
|
||||
|
||||
wxFont m_small_font;
|
||||
wxFont m_bold_font;
|
||||
wxFont m_normal_font;
|
||||
|
@ -202,7 +201,9 @@ public:
|
|||
#ifdef _WIN32
|
||||
const wxColour& get_label_highlight_clr() { return m_color_highlight_label_default; }
|
||||
const wxColour& get_highlight_default_clr() { return m_color_highlight_default; }
|
||||
// void force_sys_colors_update() { m_force_sys_colors_update = true; } // #ysDarkMSW - Use to force dark colors for SystemLightMode
|
||||
const wxColour& get_color_hovered_btn_label() { return m_color_hovered_btn_label; }
|
||||
const wxColour& get_color_selected_btn_bg() { return m_color_selected_btn_bg; }
|
||||
void force_colors_update();
|
||||
#endif
|
||||
|
||||
const wxFont& small_font() { return m_small_font; }
|
||||
|
@ -210,7 +211,7 @@ public:
|
|||
const wxFont& normal_font() { return m_normal_font; }
|
||||
const wxFont& code_font() { return m_code_font; }
|
||||
int em_unit() const { return m_em_unit; }
|
||||
bool tabs_as_menu() const { return m_tabs_as_menu;}
|
||||
bool tabs_as_menu() const;
|
||||
wxSize get_min_size() const;
|
||||
float toolbar_icon_scale(const bool is_limited = false) const;
|
||||
void set_auto_toolbar_icon_scale(float scale) const;
|
||||
|
@ -273,7 +274,7 @@ public:
|
|||
NotificationManager* notification_manager();
|
||||
|
||||
// Parameters extracted from the command line to be passed to GUI after initialization.
|
||||
const GUI_InitParams* init_params { nullptr };
|
||||
GUI_InitParams* init_params { nullptr };
|
||||
|
||||
AppConfig* app_config{ nullptr };
|
||||
PresetBundle* preset_bundle{ nullptr };
|
||||
|
@ -281,7 +282,7 @@ public:
|
|||
MainFrame* mainframe{ nullptr };
|
||||
Plater* plater_{ nullptr };
|
||||
|
||||
PresetUpdater* get_preset_updater() { return preset_updater; }
|
||||
PresetUpdater* get_preset_updater() { return preset_updater; }
|
||||
|
||||
wxBookCtrlBase* tab_panel() const ;
|
||||
int extruders_cnt() const;
|
||||
|
|
|
@ -1115,5 +1115,20 @@ void MenuFactory::sys_color_changed()
|
|||
}
|
||||
}
|
||||
|
||||
void MenuFactory::sys_color_changed(wxMenuBar* menubar)
|
||||
{
|
||||
for (size_t id = 0; id < menubar->GetMenuCount(); id++) {
|
||||
wxMenu* menu = menubar->GetMenu(id);
|
||||
msw_rescale_menu(menu);
|
||||
#ifdef _WIN32
|
||||
// but under MSW we have to update item's bachground color
|
||||
for (wxMenuItem* item : menu->GetMenuItems())
|
||||
update_menu_item_def_colors(item);
|
||||
#endif
|
||||
}
|
||||
menubar->Refresh();
|
||||
}
|
||||
|
||||
|
||||
} //namespace GUI
|
||||
} //namespace Slic3r
|
||||
|
|
|
@ -44,6 +44,8 @@ public:
|
|||
void msw_rescale();
|
||||
void sys_color_changed();
|
||||
|
||||
static void sys_color_changed(wxMenuBar* menu_bar);
|
||||
|
||||
wxMenu* default_menu();
|
||||
wxMenu* object_menu();
|
||||
wxMenu* sla_object_menu();
|
||||
|
|
|
@ -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("PrusaSlicer 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;
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include <string>
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
|
||||
class wxNotebook;
|
||||
class wxGLCanvas;
|
||||
class wxBoxSizer;
|
||||
class wxStaticText;
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
#include "GUI_Utils.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#include "GUI_App.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include <wx/msw/registry.h>
|
||||
#endif
|
||||
#include <Windows.h>
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include <wx/msw/registry.h>
|
||||
#endif // _WIN32
|
||||
|
||||
#include <wx/toplevel.h>
|
||||
#include <wx/sizer.h>
|
||||
|
@ -174,7 +174,7 @@ bool check_dark_mode() {
|
|||
#ifdef _WIN32
|
||||
void update_dark_ui(wxWindow* window)
|
||||
{
|
||||
bool is_dark = wxGetApp().app_config->get("dark_color_mode") == "1" ? true : check_dark_mode();
|
||||
bool is_dark = wxGetApp().app_config->get("dark_color_mode") == "1";// ? true : check_dark_mode();// #ysDarkMSW - Allow it when we deside to support the sustem colors for application
|
||||
window->SetBackgroundColour(is_dark ? wxColour(43, 43, 43) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
window->SetForegroundColour(is_dark ? wxColour(250, 250, 250) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
|
||||
}
|
||||
|
|
|
@ -175,8 +175,8 @@ public:
|
|||
const wxFont& normal_font() const { return m_normal_font; }
|
||||
void enable_force_rescale() { m_force_rescale = true; }
|
||||
|
||||
#if 0 //#ifdef _WIN32 // #ysDarkMSW - Use to force dark colors for SystemLightMode
|
||||
void force_sys_color_changed()
|
||||
#ifdef _WIN32
|
||||
void force_color_changed()
|
||||
{
|
||||
update_dark_ui(this);
|
||||
on_sys_color_changed();
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -231,7 +232,10 @@ void GLGizmoCut::perform_cut(const Selection& selection)
|
|||
coordf_t object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z();
|
||||
|
||||
if (object_cut_z > 0.)
|
||||
wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower);
|
||||
wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z,
|
||||
only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) |
|
||||
only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) |
|
||||
only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower));
|
||||
else {
|
||||
// the object is SLA-elevated and the plane is under it.
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ void GLGizmoFdmSupports::on_shutdown()
|
|||
{
|
||||
m_angle_threshold_deg = 0.f;
|
||||
m_parent.use_slope(false);
|
||||
m_parent.toggle_model_objects_visibility(true);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "slic3r/GUI/BitmapCache.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/NotificationManager.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
|
@ -16,9 +17,26 @@
|
|||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
static inline void show_notification_extruders_limit_exceeded()
|
||||
{
|
||||
wxGetApp()
|
||||
.plater()
|
||||
->get_notification_manager()
|
||||
->push_notification(NotificationType::MmSegmentationExceededExtrudersLimit, NotificationManager::NotificationLevel::RegularNotification,
|
||||
GUI::format(_L("Your printer has more extruders than the multi-material painting gizmo supports. For this reason, only the "
|
||||
"first %1% extruders will be able to be used for painting."), GLGizmoMmuSegmentation::EXTRUDERS_LIMIT));
|
||||
}
|
||||
|
||||
void GLGizmoMmuSegmentation::on_opening()
|
||||
{
|
||||
if (wxGetApp().extruders_edited_cnt() > int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT))
|
||||
show_notification_extruders_limit_exceeded();
|
||||
}
|
||||
|
||||
void GLGizmoMmuSegmentation::on_shutdown()
|
||||
{
|
||||
m_parent.use_slope(false);
|
||||
m_parent.toggle_model_objects_visibility(true);
|
||||
}
|
||||
|
||||
std::string GLGizmoMmuSegmentation::on_get_name() const
|
||||
|
@ -131,6 +149,9 @@ void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection)
|
|||
ModelObject *model_object = m_c->selection_info()->model_object();
|
||||
int prev_extruders_count = int(m_original_extruders_colors.size());
|
||||
if (prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors) {
|
||||
if (wxGetApp().extruders_edited_cnt() > int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT))
|
||||
show_notification_extruders_limit_exceeded();
|
||||
|
||||
this->init_extruders_data();
|
||||
// Reinitialize triangle selectors because of change of extruder count need also change the size of GLIndexedVertexArray
|
||||
if (prev_extruders_count != wxGetApp().extruders_edited_cnt())
|
||||
|
@ -157,7 +178,7 @@ static void render_extruders_combo(const std::string &labe
|
|||
ImGui::BeginGroup();
|
||||
ImVec2 combo_pos = ImGui::GetCursorScreenPos();
|
||||
if (ImGui::BeginCombo(label.c_str(), "")) {
|
||||
for (size_t extruder_idx = 0; extruder_idx < extruders.size(); ++extruder_idx) {
|
||||
for (size_t extruder_idx = 0; extruder_idx < std::min(extruders.size(), GLGizmoMmuSegmentation::EXTRUDERS_LIMIT); ++extruder_idx) {
|
||||
ImGui::PushID(int(extruder_idx));
|
||||
ImVec2 start_position = ImGui::GetCursorScreenPos();
|
||||
|
||||
|
|
|
@ -36,6 +36,12 @@ public:
|
|||
|
||||
void set_painter_gizmo_data(const Selection& selection) override;
|
||||
|
||||
// TriangleSelector::serialization/deserialization has a limit to store 19 different states.
|
||||
// EXTRUDER_LIMIT + 1 states are used to storing the painting because also uncolored triangles are stored.
|
||||
// When increasing EXTRUDER_LIMIT, it needs to ensure that TriangleSelector::serialization/deserialization
|
||||
// will be also extended to support additional states, requiring at least one state to remain free out of 19 states.
|
||||
static const constexpr size_t EXTRUDERS_LIMIT = 16;
|
||||
|
||||
protected:
|
||||
std::array<float, 4> get_cursor_sphere_left_button_color() const override;
|
||||
std::array<float, 4> get_cursor_sphere_right_button_color() const override;
|
||||
|
@ -63,7 +69,7 @@ private:
|
|||
void update_model_object() const override;
|
||||
void update_from_model_object() override;
|
||||
|
||||
void on_opening() override {}
|
||||
void on_opening() override;
|
||||
void on_shutdown() override;
|
||||
PainterGizmoType get_painter_type() const override;
|
||||
|
||||
|
|
|
@ -16,6 +16,13 @@ namespace Slic3r::GUI {
|
|||
|
||||
|
||||
|
||||
void GLGizmoSeam::on_shutdown()
|
||||
{
|
||||
m_parent.toggle_model_objects_visibility(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool GLGizmoSeam::on_init()
|
||||
{
|
||||
m_shortcut_key = WXK_CONTROL_P;
|
||||
|
|
|
@ -27,7 +27,7 @@ private:
|
|||
void update_from_model_object() override;
|
||||
|
||||
void on_opening() override {}
|
||||
void on_shutdown() override {}
|
||||
void on_shutdown() override;
|
||||
|
||||
// This map holds all translated description texts, so they can be easily referenced during layout calculations
|
||||
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
|
||||
|
|
|
@ -140,22 +140,27 @@ void SLAImportJob::process()
|
|||
if (p->path.empty()) return;
|
||||
|
||||
std::string path = p->path.ToUTF8().data();
|
||||
ConfigSubstitutions config_substitutions;
|
||||
try {
|
||||
switch (p->sel) {
|
||||
case Sel::modelAndProfile:
|
||||
import_sla_archive(path, p->win, p->mesh, p->profile, progr);
|
||||
config_substitutions = import_sla_archive(path, p->win, p->mesh, p->profile, progr);
|
||||
break;
|
||||
case Sel::modelOnly:
|
||||
import_sla_archive(path, p->win, p->mesh, progr);
|
||||
config_substitutions = import_sla_archive(path, p->win, p->mesh, progr);
|
||||
break;
|
||||
case Sel::profileOnly:
|
||||
import_sla_archive(path, p->profile);
|
||||
config_substitutions = import_sla_archive(path, p->profile);
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (std::exception &ex) {
|
||||
p->err = ex.what();
|
||||
}
|
||||
|
||||
if (! config_substitutions.empty()) {
|
||||
//FIXME Add reporting here "Loading profiles found following incompatibilities."
|
||||
}
|
||||
|
||||
update_status(100, was_canceled() ? _(L("Importing canceled.")) :
|
||||
_(L("Importing done.")));
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
#include "I18N.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "Notebook.hpp"
|
||||
#include <wx/scrolwin.h>
|
||||
#include <wx/display.h>
|
||||
#include "GUI_App.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/listbook.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
@ -18,8 +18,6 @@ KBShortcutsDialog::KBShortcutsDialog()
|
|||
: DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, wxString(wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME) + " - " + _L("Keyboard Shortcuts"),
|
||||
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||
{
|
||||
// SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
|
||||
// fonts
|
||||
const wxFont& font = wxGetApp().normal_font();
|
||||
const wxFont& bold_font = wxGetApp().bold_font();
|
||||
|
@ -31,13 +29,10 @@ KBShortcutsDialog::KBShortcutsDialog()
|
|||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
wxBookCtrlBase* book;
|
||||
if (wxGetApp().dark_mode()) {
|
||||
book = new wxListbook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP);
|
||||
wxGetApp().UpdateDarkUI(book);
|
||||
wxGetApp().UpdateDarkUI(dynamic_cast<wxListbook*>(book)->GetListView());
|
||||
}
|
||||
else
|
||||
book = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP);
|
||||
// if (wxGetApp().dark_mode())
|
||||
book = new Notebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP);
|
||||
/* else
|
||||
book = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP);*/
|
||||
#else
|
||||
wxNotebook* book = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP);
|
||||
#endif
|
||||
|
|
|
@ -41,6 +41,8 @@
|
|||
#include "GUI_App.hpp"
|
||||
#include "UnsavedChangesDialog.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "Notebook.hpp"
|
||||
#include "GUI_Factories.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <dbt.h>
|
||||
|
@ -427,8 +429,13 @@ void MainFrame::update_layout()
|
|||
case ESettingsLayout::Old:
|
||||
{
|
||||
m_plater->Reparent(m_tabpanel);
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (!wxGetApp().tabs_as_menu())
|
||||
dynamic_cast<Notebook*>(m_tabpanel)->InsertPage(0, m_plater, _L("Plater"), std::string("plater"));
|
||||
else
|
||||
#endif
|
||||
m_tabpanel->InsertPage(0, m_plater, _L("Plater"));
|
||||
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND);
|
||||
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND | wxTOP, 1);
|
||||
m_plater->Show();
|
||||
m_tabpanel->Show();
|
||||
// update Tabs
|
||||
|
@ -447,6 +454,11 @@ void MainFrame::update_layout()
|
|||
m_tabpanel->Hide();
|
||||
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND);
|
||||
m_plater_page = new wxPanel(m_tabpanel);
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (!wxGetApp().tabs_as_menu())
|
||||
dynamic_cast<Notebook*>(m_tabpanel)->InsertPage(0, m_plater_page, _L("Plater"), std::string("plater"));
|
||||
else
|
||||
#endif
|
||||
m_tabpanel->InsertPage(0, m_plater_page, _L("Plater")); // empty panel just for Plater tab */
|
||||
m_plater->Show();
|
||||
break;
|
||||
|
@ -455,7 +467,7 @@ void MainFrame::update_layout()
|
|||
{
|
||||
m_main_sizer->Add(m_plater, 1, wxEXPAND);
|
||||
m_tabpanel->Reparent(&m_settings_dialog);
|
||||
m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND);
|
||||
m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND | wxTOP, 2);
|
||||
m_tabpanel->Show();
|
||||
m_plater->Show();
|
||||
|
||||
|
@ -476,6 +488,11 @@ void MainFrame::update_layout()
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
// Sizer with buttons for mode changing
|
||||
m_plater->sidebar().show_mode_sizer(wxGetApp().tabs_as_menu() || m_layout != ESettingsLayout::Old);
|
||||
#endif
|
||||
|
||||
#ifdef __WXMSW__
|
||||
if (update_scaling_state != State::noUpdate)
|
||||
{
|
||||
|
@ -640,7 +657,7 @@ void MainFrame::init_tabpanel()
|
|||
wxGetApp().UpdateDarkUI(m_tabpanel);
|
||||
}
|
||||
else
|
||||
m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME);
|
||||
m_tabpanel = new Notebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME, true);
|
||||
#else
|
||||
m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME);
|
||||
#endif
|
||||
|
@ -652,7 +669,7 @@ void MainFrame::init_tabpanel()
|
|||
m_settings_dialog.set_tabpanel(m_tabpanel);
|
||||
|
||||
#ifdef __WXMSW__
|
||||
m_tabpanel->Bind(/*wxEVT_LISTBOOK_PAGE_CHANGED*/wxEVT_BOOKCTRL_PAGE_CHANGED, [this](wxBookCtrlEvent& e) {
|
||||
m_tabpanel->Bind(wxEVT_BOOKCTRL_PAGE_CHANGED, [this](wxBookCtrlEvent& e) {
|
||||
#else
|
||||
m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxBookCtrlEvent& e) {
|
||||
#endif
|
||||
|
@ -763,20 +780,25 @@ void MainFrame::register_win32_callbacks()
|
|||
void MainFrame::create_preset_tabs()
|
||||
{
|
||||
wxGetApp().update_label_colours_from_appconfig();
|
||||
add_created_tab(new TabPrint(m_tabpanel));
|
||||
add_created_tab(new TabFilament(m_tabpanel));
|
||||
add_created_tab(new TabSLAPrint(m_tabpanel));
|
||||
add_created_tab(new TabSLAMaterial(m_tabpanel));
|
||||
add_created_tab(new TabPrinter(m_tabpanel));
|
||||
add_created_tab(new TabPrint(m_tabpanel), "cog");
|
||||
add_created_tab(new TabFilament(m_tabpanel), "spool");
|
||||
add_created_tab(new TabSLAPrint(m_tabpanel), "cog");
|
||||
add_created_tab(new TabSLAMaterial(m_tabpanel), "resin");
|
||||
add_created_tab(new TabPrinter(m_tabpanel), wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF ? "printer" : "sla_printer");
|
||||
}
|
||||
|
||||
void MainFrame::add_created_tab(Tab* panel)
|
||||
void MainFrame::add_created_tab(Tab* panel, const std::string& bmp_name /*= ""*/)
|
||||
{
|
||||
panel->create_preset_tab();
|
||||
|
||||
const auto printer_tech = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
|
||||
|
||||
if (panel->supports_printer_technology(printer_tech))
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (!wxGetApp().tabs_as_menu())
|
||||
dynamic_cast<Notebook*>(m_tabpanel)->AddPage(panel, panel->title(), bmp_name);
|
||||
else
|
||||
#endif
|
||||
m_tabpanel->AddPage(panel, panel->title());
|
||||
}
|
||||
|
||||
|
@ -960,6 +982,12 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect)
|
|||
wxGetApp().update_fonts(this);
|
||||
this->SetFont(this->normal_font());
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
// update common mode sizer
|
||||
if (!wxGetApp().tabs_as_menu())
|
||||
dynamic_cast<Notebook*>(m_tabpanel)->Rescale();
|
||||
#endif
|
||||
|
||||
// update Plater
|
||||
wxGetApp().plater()->msw_rescale();
|
||||
|
||||
|
@ -968,9 +996,8 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect)
|
|||
for (auto tab : wxGetApp().tabs_list)
|
||||
tab->msw_rescale();
|
||||
|
||||
wxMenuBar* menu_bar = this->GetMenuBar();
|
||||
for (size_t id = 0; id < menu_bar->GetMenuCount(); id++)
|
||||
msw_rescale_menu(menu_bar->GetMenu(id));
|
||||
for (size_t id = 0; id < m_menubar->GetMenuCount(); id++)
|
||||
msw_rescale_menu(m_menubar->GetMenu(id));
|
||||
|
||||
// Workarounds for correct Window rendering after rescale
|
||||
|
||||
|
@ -1003,6 +1030,11 @@ void MainFrame::on_sys_color_changed()
|
|||
#ifdef __WXMSW__
|
||||
wxGetApp().UpdateDarkUI(m_tabpanel);
|
||||
m_statusbar->update_dark_ui();
|
||||
#ifdef _MSW_DARK_MODE
|
||||
// update common mode sizer
|
||||
if (!wxGetApp().tabs_as_menu())
|
||||
dynamic_cast<Notebook*>(m_tabpanel)->Rescale();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// update Plater
|
||||
|
@ -1012,10 +1044,7 @@ void MainFrame::on_sys_color_changed()
|
|||
for (auto tab : wxGetApp().tabs_list)
|
||||
tab->sys_color_changed();
|
||||
|
||||
// msw_rescale_menu updates just icons, so use it
|
||||
wxMenuBar* menu_bar = this->GetMenuBar();
|
||||
for (size_t id = 0; id < menu_bar->GetMenuCount(); id++)
|
||||
msw_rescale_menu(menu_bar->GetMenu(id));
|
||||
MenuFactory::sys_color_changed(m_menubar);
|
||||
|
||||
this->Refresh();
|
||||
}
|
||||
|
@ -1518,6 +1547,7 @@ void MainFrame::update_menubar()
|
|||
m_changeable_menu_items[miPrinterTab] ->SetBitmap(create_menu_bitmap(is_fff ? "printer" : "sla_printer"));
|
||||
}
|
||||
|
||||
#if 0
|
||||
// To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG".
|
||||
void MainFrame::quick_slice(const int qs)
|
||||
{
|
||||
|
@ -1643,6 +1673,7 @@ void MainFrame::quick_slice(const int qs)
|
|||
// };
|
||||
// Slic3r::GUI::catch_error(this, []() { if (m_progress_dialog) m_progress_dialog->Destroy(); });
|
||||
}
|
||||
#endif
|
||||
|
||||
void MainFrame::reslice_now()
|
||||
{
|
||||
|
@ -1729,7 +1760,13 @@ void MainFrame::load_config_file()
|
|||
bool MainFrame::load_config_file(const std::string &path)
|
||||
{
|
||||
try {
|
||||
wxGetApp().preset_bundle->load_config_file(path);
|
||||
ConfigSubstitutions config_substitutions = wxGetApp().preset_bundle->load_config_file(path, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
if (! config_substitutions.empty()) {
|
||||
// TODO: Add list of changes from all_substitutions
|
||||
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
|
||||
" To recover these files, incompatible values were changed to default values."
|
||||
" But data in files won't be changed until you save them in PrusaSlicer.")));
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
show_error(this, ex.what());
|
||||
return false;
|
||||
|
@ -1793,14 +1830,22 @@ void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool re
|
|||
|
||||
wxGetApp().app_config->update_config_dir(get_dir_name(file));
|
||||
|
||||
auto presets_imported = 0;
|
||||
size_t presets_imported = 0;
|
||||
PresetsConfigSubstitutions config_substitutions;
|
||||
try {
|
||||
presets_imported = wxGetApp().preset_bundle->load_configbundle(file.ToUTF8().data());
|
||||
std::tie(config_substitutions, presets_imported) = wxGetApp().preset_bundle->load_configbundle(file.ToUTF8().data(), PresetBundle::LoadConfigBundleAttribute::SaveImported);
|
||||
} catch (const std::exception &ex) {
|
||||
show_error(this, ex.what());
|
||||
return;
|
||||
}
|
||||
|
||||
if (! config_substitutions.empty()) {
|
||||
// TODO: Add list of changes from all_substitutions
|
||||
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
|
||||
" To recover these files, incompatible values were changed to default values."
|
||||
" But data in files won't be changed until you save them in PrusaSlicer.")));
|
||||
}
|
||||
|
||||
// Load the currently selected preset into the GUI, update the preset selection box.
|
||||
wxGetApp().load_current_presets();
|
||||
|
||||
|
@ -2087,8 +2132,8 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe)
|
|||
this->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
|
||||
#else
|
||||
this->SetFont(wxGetApp().normal_font());
|
||||
#endif // __WXMSW__
|
||||
this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
#endif // __WXMSW__
|
||||
|
||||
// Load the icon either from the exe, or from the ico file.
|
||||
#if _WIN32
|
||||
|
@ -2169,6 +2214,12 @@ void SettingsDialog::on_dpi_changed(const wxRect& suggested_rect)
|
|||
const int& em = em_unit();
|
||||
const wxSize& size = wxSize(85 * em, 50 * em);
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
// update common mode sizer
|
||||
if (!wxGetApp().tabs_as_menu())
|
||||
dynamic_cast<Notebook*>(m_tabpanel)->Rescale();
|
||||
#endif
|
||||
|
||||
// update Tabs
|
||||
for (auto tab : wxGetApp().tabs_list)
|
||||
tab->msw_rescale();
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
#include "Event.hpp"
|
||||
#include "UnsavedChangesDialog.hpp"
|
||||
|
||||
class wxNotebook;
|
||||
class wxBookCtrlBase;
|
||||
class wxProgressDialog;
|
||||
|
||||
|
@ -154,7 +153,7 @@ public:
|
|||
|
||||
void init_tabpanel();
|
||||
void create_preset_tabs();
|
||||
void add_created_tab(Tab* panel);
|
||||
void add_created_tab(Tab* panel, const std::string& bmp_name = "");
|
||||
bool is_active_and_shown_tab(Tab* tab);
|
||||
// Register Win32 RawInput callbacks (3DConnexion) and removable media insert / remove callbacks.
|
||||
// Called from wxEVT_ACTIVATE, as wxEVT_CREATE was not reliable (bug in wxWidgets?).
|
||||
|
@ -170,7 +169,7 @@ public:
|
|||
bool is_last_input_file() const { return !m_qs_last_input_file.IsEmpty(); }
|
||||
bool is_dlg_layout() const { return m_layout == ESettingsLayout::Dlg; }
|
||||
|
||||
void quick_slice(const int qs = qsUndef);
|
||||
// void quick_slice(const int qs = qsUndef);
|
||||
void reslice_now();
|
||||
void repair_stl();
|
||||
void export_config();
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "I18N.hpp"
|
||||
#include "ConfigWizard.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "slic3r/GUI/MainFrame.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -24,7 +25,7 @@ namespace GUI {
|
|||
|
||||
|
||||
MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id, wxBitmap bitmap)
|
||||
: wxDialog(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||
: wxDialog(parent ? parent : dynamic_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||
, boldfont(wxGetApp().normal_font()/*wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)*/)
|
||||
, content_sizer(new wxBoxSizer(wxVERTICAL))
|
||||
, btn_sizer(new wxBoxSizer(wxHORIZONTAL))
|
||||
|
@ -32,6 +33,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
|
|||
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
|
||||
|
||||
this->SetFont(wxGetApp().normal_font());
|
||||
this->CenterOnParent();
|
||||
|
||||
auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto *rightsizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
@ -135,6 +137,7 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, bool monospaced_
|
|||
|
||||
SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT*wxGetApp().em_unit()));
|
||||
Fit();
|
||||
this->CenterOnParent();
|
||||
}
|
||||
|
||||
// WarningDialog
|
||||
|
@ -156,9 +159,10 @@ WarningDialog::WarningDialog(wxWindow *parent,
|
|||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
Fit();
|
||||
this->CenterOnParent();
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
// MessageDialog
|
||||
|
||||
MessageDialog::MessageDialog(wxWindow* parent,
|
||||
|
@ -180,7 +184,9 @@ MessageDialog::MessageDialog(wxWindow* parent,
|
|||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
Fit();
|
||||
this->CenterOnParent();
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <wx/dialog.h>
|
||||
#include <wx/font.h>
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
class wxBoxSizer;
|
||||
class wxCheckBox;
|
||||
|
@ -83,7 +84,7 @@ public:
|
|||
virtual ~WarningDialog() = default;
|
||||
};
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
// Generic message dialog, used intead of wxMessageDialog
|
||||
class MessageDialog : public MsgDialog
|
||||
{
|
||||
|
@ -98,6 +99,19 @@ public:
|
|||
MessageDialog &operator=(const MessageDialog&) = delete;
|
||||
virtual ~MessageDialog() = default;
|
||||
};
|
||||
#else
|
||||
// just a wrapper to wxMessageBox to use the same code on all platforms
|
||||
class MessageDialog : public wxMessageDialog
|
||||
{
|
||||
public:
|
||||
MessageDialog(wxWindow* parent,
|
||||
const wxString& message,
|
||||
const wxString& caption = wxEmptyString,
|
||||
long style = wxOK)
|
||||
: wxMessageDialog(parent, message, caption, style) {}
|
||||
~MessageDialog() {}
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
}
|
||||
|
|
134
src/slic3r/GUI/Notebook.cpp
Normal file
134
src/slic3r/GUI/Notebook.cpp
Normal file
|
@ -0,0 +1,134 @@
|
|||
#include "Notebook.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "GUI_App.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
#include <wx/button.h>
|
||||
#include <wx/sizer.h>
|
||||
|
||||
wxDEFINE_EVENT(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, wxCommandEvent);
|
||||
|
||||
ButtonsListCtrl::ButtonsListCtrl(wxWindow *parent, bool add_mode_buttons/* = false*/) :
|
||||
wxControl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE | wxTAB_TRAVERSAL)
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
SetDoubleBuffered(true);
|
||||
#endif //__WINDOWS__
|
||||
|
||||
m_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
this->SetSizer(m_sizer);
|
||||
|
||||
if (add_mode_buttons) {
|
||||
m_mode_sizer = new ModeSizer(this, int(0.5 * em_unit(this)));
|
||||
m_sizer->AddStretchSpacer(20);
|
||||
m_sizer->Add(m_mode_sizer, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
|
||||
}
|
||||
|
||||
this->Bind(wxEVT_PAINT, &ButtonsListCtrl::OnPaint, this);
|
||||
}
|
||||
|
||||
void ButtonsListCtrl::OnPaint(wxPaintEvent&)
|
||||
{
|
||||
Slic3r::GUI::wxGetApp().UpdateDarkUI(this);
|
||||
const wxSize sz = GetSize();
|
||||
wxPaintDC dc(this);
|
||||
|
||||
if (m_selection < 0 || m_selection >= (int)m_pageButtons.size())
|
||||
return;
|
||||
|
||||
// highlight selected button
|
||||
|
||||
const wxColour& selected_btn_bg = Slic3r::GUI::wxGetApp().get_color_selected_btn_bg();
|
||||
const wxColour& default_btn_bg = Slic3r::GUI::wxGetApp().get_highlight_default_clr();
|
||||
const wxColour& btn_marker_color = Slic3r::GUI::wxGetApp().get_color_hovered_btn_label();
|
||||
for (int idx = 0; idx < m_pageButtons.size(); idx++) {
|
||||
wxButton* btn = m_pageButtons[idx];
|
||||
|
||||
btn->SetBackgroundColour(idx == m_selection ? selected_btn_bg : default_btn_bg);
|
||||
|
||||
wxPoint pos = btn->GetPosition();
|
||||
wxSize size = btn->GetSize();
|
||||
const wxColour& clr = idx == m_selection ? btn_marker_color : default_btn_bg;
|
||||
dc.SetPen(clr);
|
||||
dc.SetBrush(clr);
|
||||
dc.DrawRectangle(pos.x, sz.y - 3, size.x, 3);
|
||||
}
|
||||
|
||||
dc.SetPen(btn_marker_color);
|
||||
dc.SetBrush(btn_marker_color);
|
||||
dc.DrawRectangle(1, sz.y - 1, sz.x, 1);
|
||||
}
|
||||
|
||||
void ButtonsListCtrl::UpdateMode()
|
||||
{
|
||||
m_mode_sizer->SetMode(Slic3r::GUI::wxGetApp().get_mode());
|
||||
}
|
||||
|
||||
void ButtonsListCtrl::Rescale()
|
||||
{
|
||||
m_mode_sizer->msw_rescale();
|
||||
for (ScalableButton* btn : m_pageButtons)
|
||||
btn->msw_rescale();
|
||||
}
|
||||
|
||||
void ButtonsListCtrl::SetSelection(int sel)
|
||||
{
|
||||
if (m_selection == sel)
|
||||
return;
|
||||
m_selection = sel;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
bool ButtonsListCtrl::InsertPage(size_t n, const wxString& text, bool bSelect/* = false*/, const std::string& bmp_name/* = ""*/)
|
||||
{
|
||||
ScalableButton* btn = new ScalableButton(this, wxID_ANY, bmp_name, text, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | (bmp_name.empty() ? 0 : wxBU_LEFT));
|
||||
btn->Bind(wxEVT_BUTTON, [this, btn](wxCommandEvent& event) {
|
||||
if (auto it = std::find(m_pageButtons.begin(), m_pageButtons.end(), btn); it != m_pageButtons.end()) {
|
||||
m_selection = it - m_pageButtons.begin();
|
||||
wxCommandEvent evt = wxCommandEvent(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED);
|
||||
evt.SetId(m_selection);
|
||||
wxPostEvent(this->GetParent(), evt);
|
||||
Refresh();
|
||||
}
|
||||
});
|
||||
Slic3r::GUI::wxGetApp().UpdateDarkUI(btn);
|
||||
m_pageButtons.insert(m_pageButtons.begin() + n, btn);
|
||||
m_sizer->Insert(n, new wxSizerItem(btn, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3));
|
||||
m_sizer->Layout();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ButtonsListCtrl::RemovePage(size_t n)
|
||||
{
|
||||
ScalableButton* btn = m_pageButtons[n];
|
||||
m_pageButtons.erase(m_pageButtons.begin() + n);
|
||||
m_sizer->Remove(n);
|
||||
btn->Reparent(nullptr);
|
||||
btn->Destroy();
|
||||
m_sizer->Layout();
|
||||
}
|
||||
|
||||
bool ButtonsListCtrl::SetPageImage(size_t n, const std::string& bmp_name) const
|
||||
{
|
||||
if (n >= m_pageButtons.size())
|
||||
return false;
|
||||
return m_pageButtons[n]->SetBitmap_(bmp_name);
|
||||
}
|
||||
|
||||
void ButtonsListCtrl::SetPageText(size_t n, const wxString& strText)
|
||||
{
|
||||
ScalableButton* btn = m_pageButtons[n];
|
||||
btn->SetLabel(strText);
|
||||
}
|
||||
|
||||
wxString ButtonsListCtrl::GetPageText(size_t n) const
|
||||
{
|
||||
ScalableButton* btn = m_pageButtons[n];
|
||||
return btn->GetLabel();
|
||||
}
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
|
305
src/slic3r/GUI/Notebook.hpp
Normal file
305
src/slic3r/GUI/Notebook.hpp
Normal file
|
@ -0,0 +1,305 @@
|
|||
#ifndef slic3r_Notebook_hpp_
|
||||
#define slic3r_Notebook_hpp_
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <wx/bookctrl.h>
|
||||
|
||||
class ModeSizer;
|
||||
class ScalableButton;
|
||||
|
||||
// custom message the ButtonsListCtrl sends to its parent (Notebook) to notify a selection change:
|
||||
wxDECLARE_EVENT(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, wxCommandEvent);
|
||||
|
||||
class ButtonsListCtrl : public wxControl
|
||||
{
|
||||
public:
|
||||
ButtonsListCtrl(wxWindow* parent, bool add_mode_buttons = false);
|
||||
~ButtonsListCtrl() {}
|
||||
|
||||
void OnPaint(wxPaintEvent&);
|
||||
void SetSelection(int sel);
|
||||
void UpdateMode();
|
||||
void Rescale();
|
||||
bool InsertPage(size_t n, const wxString& text, bool bSelect = false, const std::string& bmp_name = "");
|
||||
void RemovePage(size_t n);
|
||||
bool SetPageImage(size_t n, const std::string& bmp_name) const;
|
||||
void SetPageText(size_t n, const wxString& strText);
|
||||
wxString GetPageText(size_t n) const;
|
||||
|
||||
private:
|
||||
wxWindow* m_parent;
|
||||
wxBoxSizer* m_sizer;
|
||||
std::vector<ScalableButton*> m_pageButtons;
|
||||
int m_selection {-1};
|
||||
ModeSizer* m_mode_sizer {nullptr};
|
||||
};
|
||||
|
||||
class Notebook: public wxBookCtrlBase
|
||||
{
|
||||
public:
|
||||
Notebook(wxWindow * parent,
|
||||
wxWindowID winid = wxID_ANY,
|
||||
const wxPoint & pos = wxDefaultPosition,
|
||||
const wxSize & size = wxDefaultSize,
|
||||
long style = 0,
|
||||
bool add_mode_buttons = false)
|
||||
{
|
||||
Init();
|
||||
Create(parent, winid, pos, size, style, add_mode_buttons);
|
||||
}
|
||||
|
||||
bool Create(wxWindow * parent,
|
||||
wxWindowID winid = wxID_ANY,
|
||||
const wxPoint & pos = wxDefaultPosition,
|
||||
const wxSize & size = wxDefaultSize,
|
||||
long style = 0,
|
||||
bool add_mode_buttons = false)
|
||||
{
|
||||
if (!wxBookCtrlBase::Create(parent, winid, pos, size, style | wxBK_TOP))
|
||||
return false;
|
||||
|
||||
m_bookctrl = new ButtonsListCtrl(this, add_mode_buttons);
|
||||
|
||||
wxSizer* mainSizer = new wxBoxSizer(IsVertical() ? wxVERTICAL : wxHORIZONTAL);
|
||||
|
||||
if (style & wxBK_RIGHT || style & wxBK_BOTTOM)
|
||||
mainSizer->Add(0, 0, 1, wxEXPAND, 0);
|
||||
|
||||
m_controlSizer = new wxBoxSizer(IsVertical() ? wxHORIZONTAL : wxVERTICAL);
|
||||
m_controlSizer->Add(m_bookctrl, wxSizerFlags(1).Expand());
|
||||
wxSizerFlags flags;
|
||||
if (IsVertical())
|
||||
flags.Expand();
|
||||
else
|
||||
flags.CentreVertical();
|
||||
mainSizer->Add(m_controlSizer, flags.Border(wxALL, m_controlMargin));
|
||||
SetSizer(mainSizer);
|
||||
|
||||
this->Bind(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, [this](wxCommandEvent& evt)
|
||||
{
|
||||
if (int page_idx = evt.GetId(); page_idx >= 0)
|
||||
SetSelection(page_idx);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Methods specific to this class.
|
||||
|
||||
// A method allowing to add a new page without any label (which is unused
|
||||
// by this control) and show it immediately.
|
||||
bool ShowNewPage(wxWindow * page)
|
||||
{
|
||||
return AddPage(page, wxString(), ""/*true *//* select it */);
|
||||
}
|
||||
|
||||
|
||||
// Set effect to use for showing/hiding pages.
|
||||
void SetEffects(wxShowEffect showEffect, wxShowEffect hideEffect)
|
||||
{
|
||||
m_showEffect = showEffect;
|
||||
m_hideEffect = hideEffect;
|
||||
}
|
||||
|
||||
// Or the same effect for both of them.
|
||||
void SetEffect(wxShowEffect effect)
|
||||
{
|
||||
SetEffects(effect, effect);
|
||||
}
|
||||
|
||||
// And the same for time outs.
|
||||
void SetEffectsTimeouts(unsigned showTimeout, unsigned hideTimeout)
|
||||
{
|
||||
m_showTimeout = showTimeout;
|
||||
m_hideTimeout = hideTimeout;
|
||||
}
|
||||
|
||||
void SetEffectTimeout(unsigned timeout)
|
||||
{
|
||||
SetEffectsTimeouts(timeout, timeout);
|
||||
}
|
||||
|
||||
|
||||
// Implement base class pure virtual methods.
|
||||
|
||||
// adds a new page to the control
|
||||
bool AddPage(wxWindow* page,
|
||||
const wxString& text,
|
||||
const std::string& bmp_name,
|
||||
bool bSelect = false)
|
||||
{
|
||||
DoInvalidateBestSize();
|
||||
return InsertPage(GetPageCount(), page, text, bmp_name, bSelect);
|
||||
}
|
||||
|
||||
// Page management
|
||||
virtual bool InsertPage(size_t n,
|
||||
wxWindow * page,
|
||||
const wxString & text,
|
||||
bool bSelect = false,
|
||||
int imageId = NO_IMAGE) override
|
||||
{
|
||||
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect, imageId))
|
||||
return false;
|
||||
|
||||
GetBtnsListCtrl()->InsertPage(n, text, bSelect);
|
||||
|
||||
if (!DoSetSelectionAfterInsertion(n, bSelect))
|
||||
page->Hide();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InsertPage(size_t n,
|
||||
wxWindow * page,
|
||||
const wxString & text,
|
||||
const std::string& bmp_name = "",
|
||||
bool bSelect = false)
|
||||
{
|
||||
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect))
|
||||
return false;
|
||||
|
||||
GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name);
|
||||
|
||||
if (!DoSetSelectionAfterInsertion(n, bSelect))
|
||||
page->Hide();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual int SetSelection(size_t n) override
|
||||
{
|
||||
GetBtnsListCtrl()->SetSelection(n);
|
||||
return DoSetSelection(n, SetSelection_SendEvent);
|
||||
}
|
||||
|
||||
virtual int ChangeSelection(size_t n) override
|
||||
{
|
||||
GetBtnsListCtrl()->SetSelection(n);
|
||||
return DoSetSelection(n);
|
||||
}
|
||||
|
||||
// Neither labels nor images are supported but we still store the labels
|
||||
// just in case the user code attaches some importance to them.
|
||||
virtual bool SetPageText(size_t n, const wxString & strText) override
|
||||
{
|
||||
wxCHECK_MSG(n < GetPageCount(), false, wxS("Invalid page"));
|
||||
|
||||
GetBtnsListCtrl()->SetPageText(n, strText);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual wxString GetPageText(size_t n) const override
|
||||
{
|
||||
wxCHECK_MSG(n < GetPageCount(), wxString(), wxS("Invalid page"));
|
||||
return GetBtnsListCtrl()->GetPageText(n);
|
||||
}
|
||||
|
||||
virtual bool SetPageImage(size_t WXUNUSED(n), int WXUNUSED(imageId)) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual int GetPageImage(size_t WXUNUSED(n)) const override
|
||||
{
|
||||
return NO_IMAGE;
|
||||
}
|
||||
|
||||
bool SetPageImage(size_t n, const std::string& bmp_name)
|
||||
{
|
||||
return GetBtnsListCtrl()->SetPageImage(n, bmp_name);
|
||||
}
|
||||
|
||||
// Override some wxWindow methods too.
|
||||
virtual void SetFocus() override
|
||||
{
|
||||
wxWindow* const page = GetCurrentPage();
|
||||
if (page)
|
||||
page->SetFocus();
|
||||
}
|
||||
|
||||
ButtonsListCtrl* GetBtnsListCtrl() const { return static_cast<ButtonsListCtrl*>(m_bookctrl); }
|
||||
|
||||
void UpdateMode()
|
||||
{
|
||||
GetBtnsListCtrl()->UpdateMode();
|
||||
}
|
||||
|
||||
void Rescale()
|
||||
{
|
||||
GetBtnsListCtrl()->Rescale();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void UpdateSelectedPage(size_t WXUNUSED(newsel)) override
|
||||
{
|
||||
// Nothing to do here, but must be overridden to avoid the assert in
|
||||
// the base class version.
|
||||
}
|
||||
|
||||
virtual wxBookCtrlEvent * CreatePageChangingEvent() const override
|
||||
{
|
||||
return new wxBookCtrlEvent(wxEVT_BOOKCTRL_PAGE_CHANGING,
|
||||
GetId());
|
||||
}
|
||||
|
||||
virtual void MakeChangedEvent(wxBookCtrlEvent & event) override
|
||||
{
|
||||
event.SetEventType(wxEVT_BOOKCTRL_PAGE_CHANGED);
|
||||
}
|
||||
|
||||
virtual wxWindow * DoRemovePage(size_t page) override
|
||||
{
|
||||
wxWindow* const win = wxBookCtrlBase::DoRemovePage(page);
|
||||
if (win)
|
||||
{
|
||||
GetBtnsListCtrl()->RemovePage(page);
|
||||
DoSetSelectionAfterRemoval(page);
|
||||
}
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
virtual void DoSize() override
|
||||
{
|
||||
wxWindow* const page = GetCurrentPage();
|
||||
if (page)
|
||||
page->SetSize(GetPageRect());
|
||||
}
|
||||
|
||||
virtual void DoShowPage(wxWindow * page, bool show) override
|
||||
{
|
||||
if (show)
|
||||
page->ShowWithEffect(m_showEffect, m_showTimeout);
|
||||
else
|
||||
page->HideWithEffect(m_hideEffect, m_hideTimeout);
|
||||
}
|
||||
|
||||
private:
|
||||
void Init()
|
||||
{
|
||||
// We don't need any border as we don't have anything to separate the
|
||||
// page contents from.
|
||||
SetInternalBorder(0);
|
||||
|
||||
// No effects by default.
|
||||
m_showEffect =
|
||||
m_hideEffect = wxSHOW_EFFECT_NONE;
|
||||
|
||||
m_showTimeout =
|
||||
m_hideTimeout = 0;
|
||||
}
|
||||
|
||||
wxShowEffect m_showEffect,
|
||||
m_hideEffect;
|
||||
|
||||
unsigned m_showTimeout,
|
||||
m_hideTimeout;
|
||||
|
||||
ButtonsListCtrl* m_ctrl{ nullptr };
|
||||
|
||||
};
|
||||
#endif // _WIN32
|
||||
#endif // slic3r_Notebook_hpp_
|
|
@ -272,27 +272,28 @@ void NotificationManager::PopNotification::count_lines()
|
|||
// find next suitable endline
|
||||
if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - m_window_width_offset) {
|
||||
// more than one line till end
|
||||
size_t next_space = text.find_first_of(' ', last_end);
|
||||
if (next_space > 0) {
|
||||
size_t next_space_candidate = text.find_first_of(' ', next_space + 1);
|
||||
int next_space = text.find_first_of(' ', last_end);
|
||||
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;
|
||||
next_space_candidate = text.find_first_of(' ', next_space + 1);
|
||||
}
|
||||
// when one word longer than line.
|
||||
if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset) {
|
||||
float width_of_a = ImGui::CalcTextSize("a").x;
|
||||
int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a);
|
||||
while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset) {
|
||||
letter_count++;
|
||||
}
|
||||
m_endlines.push_back(last_end + letter_count);
|
||||
last_end += letter_count;
|
||||
}
|
||||
else {
|
||||
m_endlines.push_back(next_space);
|
||||
last_end = next_space + 1;
|
||||
} else {
|
||||
next_space = text.length();
|
||||
}
|
||||
// when one word longer than line.
|
||||
if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset) {
|
||||
float width_of_a = ImGui::CalcTextSize("a").x;
|
||||
int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a);
|
||||
while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset) {
|
||||
letter_count++;
|
||||
}
|
||||
m_endlines.push_back(last_end + letter_count);
|
||||
last_end += letter_count;
|
||||
} else {
|
||||
m_endlines.push_back(next_space);
|
||||
last_end = next_space + 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -87,7 +87,9 @@ enum class NotificationType
|
|||
DesktopIntegrationSuccess,
|
||||
DesktopIntegrationFail,
|
||||
UndoDesktopIntegrationSuccess,
|
||||
UndoDesktopIntegrationFail
|
||||
UndoDesktopIntegrationFail,
|
||||
// Notification that a printer has more extruders than are supported by MM Gizmo/segmentation.
|
||||
MmSegmentationExceededExtrudersLimit
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -68,7 +68,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
|
||||
|
@ -154,7 +155,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());
|
||||
#ifndef _WIN32
|
||||
|
@ -455,7 +457,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();
|
||||
|
||||
|
@ -463,13 +465,24 @@ 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->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;
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false);
|
||||
|
@ -493,6 +506,57 @@ 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()
|
||||
{
|
||||
|
@ -628,8 +692,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)
|
||||
|
@ -657,7 +722,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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -583,7 +583,7 @@ struct Sidebar::priv
|
|||
wxScrolledWindow *scrolled;
|
||||
wxPanel* presets_panel; // Used for MSW better layouts
|
||||
|
||||
ModeSizer *mode_sizer;
|
||||
ModeSizer *mode_sizer {nullptr};
|
||||
wxFlexGridSizer *sizer_presets;
|
||||
PlaterPresetComboBox *combo_print;
|
||||
std::vector<PlaterPresetComboBox*> combos_filament;
|
||||
|
@ -754,7 +754,8 @@ Sidebar::Sidebar(Plater *parent)
|
|||
p->sliced_info = new SlicedInfo(p->scrolled);
|
||||
|
||||
// Sizer in the scrolled area
|
||||
scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL/*RIGHT | wxBOTTOM | wxRIGHT, 5*/);
|
||||
if (p->mode_sizer)
|
||||
scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL);
|
||||
is_msw ?
|
||||
scrolled_sizer->Add(p->presets_panel, 0, wxEXPAND | wxLEFT, margin_5) :
|
||||
scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, margin_5);
|
||||
|
@ -944,13 +945,16 @@ void Sidebar::update_presets(Preset::Type preset_type)
|
|||
|
||||
void Sidebar::update_mode_sizer() const
|
||||
{
|
||||
p->mode_sizer->SetMode(m_mode);
|
||||
if (p->mode_sizer)
|
||||
p->mode_sizer->SetMode(m_mode);
|
||||
}
|
||||
|
||||
void Sidebar::change_top_border_for_mode_sizer(bool increase_border)
|
||||
{
|
||||
p->mode_sizer->set_items_flag(increase_border ? wxTOP : 0);
|
||||
p->mode_sizer->set_items_border(increase_border ? int(0.5 * wxGetApp().em_unit()) : 0);
|
||||
if (p->mode_sizer) {
|
||||
p->mode_sizer->set_items_flag(increase_border ? wxTOP : 0);
|
||||
p->mode_sizer->set_items_border(increase_border ? int(0.5 * wxGetApp().em_unit()) : 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Sidebar::update_reslice_btn_tooltip() const
|
||||
|
@ -965,7 +969,8 @@ void Sidebar::msw_rescale()
|
|||
{
|
||||
SetMinSize(wxSize(40 * wxGetApp().em_unit(), -1));
|
||||
|
||||
p->mode_sizer->msw_rescale();
|
||||
if (p->mode_sizer)
|
||||
p->mode_sizer->msw_rescale();
|
||||
|
||||
for (PlaterPresetComboBox* combo : std::vector<PlaterPresetComboBox*> { p->combo_print,
|
||||
p->combo_sla_print,
|
||||
|
@ -1009,7 +1014,8 @@ void Sidebar::sys_color_changed()
|
|||
for (wxWindow* btn : std::vector<wxWindow*>{ p->btn_reslice, p->btn_export_gcode })
|
||||
wxGetApp().UpdateDarkUI(btn, true);
|
||||
|
||||
p->mode_sizer->msw_rescale();
|
||||
if (p->mode_sizer)
|
||||
p->mode_sizer->msw_rescale();
|
||||
p->frequently_changed_parameters->sys_color_changed();
|
||||
p->object_settings->sys_color_changed();
|
||||
#endif
|
||||
|
@ -1394,6 +1400,12 @@ void Sidebar::collapse(bool collapse)
|
|||
wxGetApp().app_config->set("collapsed_sidebar", collapse ? "1" : "0");
|
||||
}
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
void Sidebar::show_mode_sizer(bool show)
|
||||
{
|
||||
p->mode_sizer->Show(show);
|
||||
}
|
||||
#endif
|
||||
|
||||
void Sidebar::update_ui_from_settings()
|
||||
{
|
||||
|
@ -2237,7 +2249,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
DynamicPrintConfig config;
|
||||
{
|
||||
DynamicPrintConfig config_loaded;
|
||||
model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, false, load_config);
|
||||
ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::Enable };
|
||||
model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, &config_substitutions, only_if(load_config, Model::LoadAttribute::CheckVersion));
|
||||
if (load_config && !config_loaded.empty()) {
|
||||
// Based on the printer technology field found in the loaded config, select the base for the config,
|
||||
PrinterTechnology printer_technology = Preset::printer_technology(config_loaded);
|
||||
|
@ -2261,6 +2274,12 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
// and place the loaded config over the base.
|
||||
config += std::move(config_loaded);
|
||||
}
|
||||
if (! config_substitutions.empty()) {
|
||||
// TODO:
|
||||
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
|
||||
" To recover these files, incompatible values were changed to default values."
|
||||
" But data in files won't be changed until you save them in PrusaSlicer.")));
|
||||
}
|
||||
|
||||
this->model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
|
||||
}
|
||||
|
@ -2330,7 +2349,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
}
|
||||
}
|
||||
else {
|
||||
model = Slic3r::Model::read_from_file(path.string(), nullptr, false, load_config);
|
||||
model = Slic3r::Model::read_from_file(path.string(), nullptr, nullptr, only_if(load_config, Model::LoadAttribute::CheckVersion));
|
||||
for (auto obj : model.objects)
|
||||
if (obj->name.empty())
|
||||
obj->name = fs::path(obj->input_file).filename().string();
|
||||
|
@ -3215,7 +3234,7 @@ void Plater::priv::replace_with_stl()
|
|||
|
||||
Model new_model;
|
||||
try {
|
||||
new_model = Model::read_from_file(path, nullptr, true, false);
|
||||
new_model = Model::read_from_file(path, nullptr, nullptr, Model::LoadAttribute::AddDefaultInstances);
|
||||
for (ModelObject* model_object : new_model.objects) {
|
||||
model_object->center_around_origin();
|
||||
model_object->ensure_on_bed();
|
||||
|
@ -3388,7 +3407,7 @@ void Plater::priv::reload_from_disk()
|
|||
Model new_model;
|
||||
try
|
||||
{
|
||||
new_model = Model::read_from_file(path, nullptr, true, false);
|
||||
new_model = Model::read_from_file(path, nullptr, nullptr, Model::LoadAttribute::AddDefaultInstances);
|
||||
for (ModelObject* model_object : new_model.objects) {
|
||||
model_object->center_around_origin();
|
||||
model_object->ensure_on_bed();
|
||||
|
@ -4599,7 +4618,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();
|
||||
|
@ -5306,21 +5327,20 @@ void Plater::toggle_layers_editing(bool enable)
|
|||
wxPostEvent(canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING));
|
||||
}
|
||||
|
||||
void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower)
|
||||
void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCutAttributes attributes)
|
||||
{
|
||||
wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds");
|
||||
auto *object = p->model.objects[obj_idx];
|
||||
|
||||
wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds");
|
||||
|
||||
if (!keep_upper && !keep_lower) {
|
||||
if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
return;
|
||||
}
|
||||
|
||||
Plater::TakeSnapshot snapshot(this, _L("Cut by Plane"));
|
||||
|
||||
wxBusyCursor wait;
|
||||
const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower);
|
||||
const auto new_objects = object->cut(instance_idx, z, attributes);
|
||||
|
||||
remove(obj_idx);
|
||||
p->load_model_objects(new_objects);
|
||||
|
@ -5328,9 +5348,7 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe
|
|||
Selection& selection = p->get_selection();
|
||||
size_t last_id = p->model.objects.size() - 1;
|
||||
for (size_t i = 0; i < new_objects.size(); ++i)
|
||||
{
|
||||
selection.add_object((unsigned int)(last_id - i), i == 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Plater::export_gcode(bool prefer_removable)
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "Selection.hpp"
|
||||
|
||||
#include "libslic3r/enum_bitmask.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
|
@ -24,6 +25,8 @@ namespace Slic3r {
|
|||
|
||||
class Model;
|
||||
class ModelObject;
|
||||
enum class ModelObjectCutAttribute : int;
|
||||
using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
|
||||
class ModelInstance;
|
||||
class Print;
|
||||
class SLAPrint;
|
||||
|
@ -109,6 +112,10 @@ public:
|
|||
void update_searcher();
|
||||
void update_ui_from_settings();
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
void show_mode_sizer(bool show);
|
||||
#endif
|
||||
|
||||
std::vector<PlaterPresetComboBox*>& combos_filament();
|
||||
Search::OptionsSearcher& get_searcher();
|
||||
std::string& get_search_line();
|
||||
|
@ -204,7 +211,7 @@ public:
|
|||
void convert_unit(ConversionType conv_type);
|
||||
void toggle_layers_editing(bool enable);
|
||||
|
||||
void cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false);
|
||||
void cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCutAttributes attributes);
|
||||
|
||||
void export_gcode(bool prefer_removable);
|
||||
void export_stl(bool extended = false, bool selection_only = false);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include "I18N.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/listbook.h>
|
||||
#include "Notebook.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
@ -58,15 +58,12 @@ void PreferencesDialog::build()
|
|||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
wxBookCtrlBase* tabs;
|
||||
if (wxGetApp().dark_mode()) {
|
||||
tabs = new wxListbook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME | wxNO_BORDER);
|
||||
wxGetApp().UpdateDarkUI(tabs);
|
||||
wxGetApp().UpdateDarkUI(dynamic_cast<wxListbook*>(tabs)->GetListView());
|
||||
}
|
||||
else {
|
||||
// if (wxGetApp().dark_mode())
|
||||
tabs = new Notebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME | wxNB_DEFAULT);
|
||||
/* else {
|
||||
tabs = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME | wxNB_DEFAULT);
|
||||
tabs->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
}
|
||||
}*/
|
||||
#else
|
||||
wxNotebook* tabs = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL |wxNB_NOPAGETHEME | wxNB_DEFAULT );
|
||||
tabs->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
|
@ -345,7 +342,6 @@ void PreferencesDialog::build()
|
|||
def.set_default_value(new ConfigOptionBool{ app_config->get("dark_color_mode") == "1" });
|
||||
option = Option(def, "dark_color_mode");
|
||||
m_optgroup_gui->append_single_option_line(option);
|
||||
#endif
|
||||
|
||||
def.label = L("Set settings tabs as menu items (experimental)");
|
||||
def.type = coBool;
|
||||
|
@ -354,6 +350,7 @@ void PreferencesDialog::build()
|
|||
def.set_default_value(new ConfigOptionBool{ app_config->get("tabs_as_menu") == "1" });
|
||||
option = Option(def, "tabs_as_menu");
|
||||
m_optgroup_gui->append_single_option_line(option);
|
||||
#endif
|
||||
|
||||
def.label = L("Use custom size for toolbar icons");
|
||||
def.type = coBool;
|
||||
|
@ -414,11 +411,7 @@ void PreferencesDialog::accept()
|
|||
// if (m_values.find("no_defaults") != m_values.end()
|
||||
// warning_catcher(this, wxString::Format(_L("You need to restart %s to make the changes effective."), SLIC3R_APP_NAME));
|
||||
|
||||
std::vector<std::string> options_to_recreate_GUI = { "no_defaults", "tabs_as_menu"
|
||||
#ifdef _MSW_DARK_MODE
|
||||
,"dark_color_mode"
|
||||
#endif
|
||||
};
|
||||
std::vector<std::string> options_to_recreate_GUI = { "no_defaults", "tabs_as_menu" };
|
||||
|
||||
for (const std::string& option : options_to_recreate_GUI) {
|
||||
if (m_values.find(option) != m_values.end()) {
|
||||
|
@ -432,9 +425,6 @@ void PreferencesDialog::accept()
|
|||
wxICON_QUESTION | wxYES | wxNO);
|
||||
if (dialog.ShowModal() == wxID_YES) {
|
||||
m_recreate_GUI = true;
|
||||
#ifdef _MSW_DARK_MODE
|
||||
m_color_mode_changed = m_values.find("dark_color_mode") != m_values.end();
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
for (const std::string& option : options_to_recreate_GUI)
|
||||
|
@ -495,6 +485,11 @@ void PreferencesDialog::accept()
|
|||
|
||||
EndModal(wxID_OK);
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (m_values.find("dark_color_mode") != m_values.end())
|
||||
wxGetApp().force_colors_update();
|
||||
#endif
|
||||
|
||||
if (m_settings_layout_changed)
|
||||
;// application will be recreated after Preference dialog will be destroyed
|
||||
else
|
||||
|
@ -585,7 +580,9 @@ void PreferencesDialog::create_icon_size_slider()
|
|||
|
||||
void PreferencesDialog::create_settings_mode_widget()
|
||||
{
|
||||
bool dark_mode = wxGetApp().dark_mode();
|
||||
#ifdef _MSW_DARK_MODE
|
||||
bool disable_new_layout = wxGetApp().tabs_as_menu();
|
||||
#endif
|
||||
std::vector<wxString> choices = { _L("Old regular layout with the tab bar"),
|
||||
_L("New layout, access via settings button in the top menu"),
|
||||
_L("Settings in non-modal window") };
|
||||
|
@ -596,7 +593,7 @@ void PreferencesDialog::create_settings_mode_widget()
|
|||
app_config->get("dlg_settings_layout_mode") == "1" ? 2 : 0;
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (dark_mode) {
|
||||
if (disable_new_layout) {
|
||||
choices = { _L("Old regular layout with the tab bar"),
|
||||
_L("Settings in non-modal window") };
|
||||
selection = app_config->get("dlg_settings_layout_mode") == "1" ? 1 : 0;
|
||||
|
@ -621,14 +618,18 @@ void PreferencesDialog::create_settings_mode_widget()
|
|||
|
||||
int dlg_id = 2;
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (dark_mode)
|
||||
if (disable_new_layout)
|
||||
dlg_id = 1;
|
||||
#endif
|
||||
|
||||
btn->Bind(wxEVT_RADIOBUTTON, [this, id, dlg_id, dark_mode](wxCommandEvent& ) {
|
||||
btn->Bind(wxEVT_RADIOBUTTON, [this, id, dlg_id
|
||||
#ifdef _MSW_DARK_MODE
|
||||
, disable_new_layout
|
||||
#endif
|
||||
](wxCommandEvent& ) {
|
||||
m_values["old_settings_layout_mode"] = (id == 0) ? "1" : "0";
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (!dark_mode)
|
||||
if (!disable_new_layout)
|
||||
m_values["new_settings_layout_mode"] = (id == 1) ? "1" : "0";
|
||||
#endif
|
||||
m_values["dlg_settings_layout_mode"] = (id == dlg_id) ? "1" : "0";
|
||||
|
@ -637,7 +638,7 @@ void PreferencesDialog::create_settings_mode_widget()
|
|||
}
|
||||
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(/*m_layout_mode_box*/stb_sizer, 1, wxALIGN_CENTER_VERTICAL);
|
||||
sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL);
|
||||
m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit());
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ class PreferencesDialog : public DPIDialog
|
|||
std::shared_ptr<ConfigOptionsGroup> m_optgroup_render;
|
||||
#endif // ENABLE_ENVIRONMENT_MAP
|
||||
wxSizer* m_icon_size_sizer;
|
||||
wxRadioBox* m_layout_mode_box;
|
||||
wxColourPickerCtrl* m_sys_colour {nullptr};
|
||||
wxColourPickerCtrl* m_mod_colour {nullptr};
|
||||
bool isOSX {false};
|
||||
|
@ -35,9 +34,7 @@ class PreferencesDialog : public DPIDialog
|
|||
bool m_seq_top_gcode_indices_changed{ false };
|
||||
#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
|
||||
bool m_recreate_GUI{false};
|
||||
#ifdef _MSW_DARK_MODE
|
||||
bool m_color_mode_changed {false};
|
||||
#endif
|
||||
|
||||
public:
|
||||
explicit PreferencesDialog(wxWindow* parent);
|
||||
~PreferencesDialog() = default;
|
||||
|
@ -48,9 +45,6 @@ public:
|
|||
bool seq_seq_top_gcode_indices_changed() const { return m_seq_top_gcode_indices_changed; }
|
||||
#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
|
||||
bool recreate_GUI() const { return m_recreate_GUI; }
|
||||
#ifdef _MSW_DARK_MODE
|
||||
bool color_mode_changed() const { return m_color_mode_changed; }
|
||||
#endif
|
||||
void build();
|
||||
void accept();
|
||||
|
||||
|
|
|
@ -208,12 +208,13 @@ SavePresetDialog::~SavePresetDialog()
|
|||
|
||||
void SavePresetDialog::build(std::vector<Preset::Type> types, std::string suffix)
|
||||
{
|
||||
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
#if defined(__WXMSW__)
|
||||
// ys_FIXME! temporary workaround for correct font scaling
|
||||
// Because of from wxWidgets 3.1.3 auto rescaling is implemented for the Fonts,
|
||||
// From the very beginning set dialog font to the wxSYS_DEFAULT_GUI_FONT
|
||||
this->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
|
||||
#else
|
||||
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
#endif // __WXMSW__
|
||||
|
||||
if (suffix.empty())
|
||||
|
@ -240,6 +241,10 @@ void SavePresetDialog::build(std::vector<Preset::Type> types, std::string suffix
|
|||
topSizer->SetSizeHints(this);
|
||||
|
||||
this->CenterOnScreen();
|
||||
|
||||
#ifdef _WIN32
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SavePresetDialog::AddItem(Preset::Type type, const std::string& suffix)
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
#include "UnsavedChangesDialog.hpp"
|
||||
#include "SavePresetDialog.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "Notebook.hpp"
|
||||
|
||||
#ifdef WIN32
|
||||
#include <commctrl.h>
|
||||
|
@ -255,8 +256,11 @@ void Tab::create_preset_tab()
|
|||
m_modified_label_clr = wxGetApp().get_label_clr_modified();
|
||||
m_default_text_clr = wxGetApp().get_label_clr_default();
|
||||
|
||||
#ifdef _MSW_DARK_MODE
|
||||
// Sizer with buttons for mode changing
|
||||
m_mode_sizer = new ModeSizer(panel, int (0.5*em_unit(this)));
|
||||
if (wxGetApp().tabs_as_menu())
|
||||
#endif
|
||||
m_mode_sizer = new ModeSizer(panel, int (0.5*em_unit(this)));
|
||||
|
||||
const float scale_factor = /*wxGetApp().*/em_unit(this)*0.1;// GetContentScaleFactor();
|
||||
m_hsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
@ -285,9 +289,11 @@ void Tab::create_preset_tab()
|
|||
// m_hsizer->AddStretchSpacer(32);
|
||||
// StretchSpacer has a strange behavior under OSX, so
|
||||
// There is used just additional sizer for m_mode_sizer with right alignment
|
||||
auto mode_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
mode_sizer->Add(m_mode_sizer, 1, wxALIGN_RIGHT);
|
||||
m_hsizer->Add(mode_sizer, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, wxOSX ? 15 : 10);
|
||||
if (m_mode_sizer) {
|
||||
auto mode_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
mode_sizer->Add(m_mode_sizer, 1, wxALIGN_RIGHT);
|
||||
m_hsizer->Add(mode_sizer, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, wxOSX ? 15 : 10);
|
||||
}
|
||||
|
||||
//Horizontal sizer to hold the tree and the selected page.
|
||||
m_hsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
@ -490,7 +496,8 @@ void Tab::OnActivate()
|
|||
|
||||
// Workaroud for Menu instead of NoteBook
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (wxGetApp().tabs_as_menu()) {
|
||||
// if (wxGetApp().tabs_as_menu())
|
||||
{
|
||||
wxSize sz = m_presets_choice->GetSize();
|
||||
wxSize ok_sz = wxSize(35 * m_em_unit, m_presets_choice->GetBestSize().y+1);
|
||||
if (sz != ok_sz) {
|
||||
|
@ -943,7 +950,8 @@ void Tab::update_mode()
|
|||
m_mode = wxGetApp().get_mode();
|
||||
|
||||
// update mode for ModeSizer
|
||||
m_mode_sizer->SetMode(m_mode);
|
||||
if (m_mode_sizer)
|
||||
m_mode_sizer->SetMode(m_mode);
|
||||
|
||||
update_visibility();
|
||||
|
||||
|
@ -969,7 +977,8 @@ void Tab::msw_rescale()
|
|||
{
|
||||
m_em_unit = em_unit(m_parent);
|
||||
|
||||
m_mode_sizer->msw_rescale();
|
||||
if (m_mode_sizer)
|
||||
m_mode_sizer->msw_rescale();
|
||||
m_presets_choice->msw_rescale();
|
||||
|
||||
m_treectrl->SetMinSize(wxSize(20 * m_em_unit, -1));
|
||||
|
@ -1026,7 +1035,8 @@ void Tab::sys_color_changed()
|
|||
update_label_colours();
|
||||
#ifdef _WIN32
|
||||
wxWindowUpdateLocker noUpdates(this);
|
||||
m_mode_sizer->msw_rescale();
|
||||
if (m_mode_sizer)
|
||||
m_mode_sizer->msw_rescale();
|
||||
wxGetApp().UpdateDarkUI(this);
|
||||
wxGetApp().UpdateDarkUI(m_treectrl);
|
||||
#endif
|
||||
|
@ -3085,7 +3095,15 @@ void Tab::load_current_preset()
|
|||
}
|
||||
if (tab->supports_printer_technology(printer_technology))
|
||||
{
|
||||
wxGetApp().tab_panel()->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title());
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (!wxGetApp().tabs_as_menu()) {
|
||||
std::string bmp_name = tab->type() == Slic3r::Preset::TYPE_FILAMENT ? "spool" :
|
||||
tab->type() == Slic3r::Preset::TYPE_SLA_MATERIAL ? "resin" : "cog";
|
||||
dynamic_cast<Notebook*>(wxGetApp().tab_panel())->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title(), bmp_name);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
wxGetApp().tab_panel()->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title());
|
||||
#ifdef __linux__ // the tabs apparently need to be explicitly shown on Linux (pull request #1563)
|
||||
int page_id = wxGetApp().tab_panel()->FindPage(tab);
|
||||
wxGetApp().tab_panel()->GetPage(page_id)->Show(true);
|
||||
|
@ -3099,6 +3117,10 @@ void Tab::load_current_preset()
|
|||
}
|
||||
static_cast<TabPrinter*>(this)->m_printer_technology = printer_technology;
|
||||
m_active_page = tmp_page;
|
||||
#ifdef _MSW_DARK_MODE
|
||||
if (!wxGetApp().tabs_as_menu())
|
||||
dynamic_cast<Notebook*>(wxGetApp().tab_panel())->SetPageImage(wxGetApp().tab_panel()->FindPage(this), printer_technology == ptFFF ? "printer" : "sla_printer");
|
||||
#endif
|
||||
}
|
||||
on_presets_changed();
|
||||
if (printer_technology == ptFFF) {
|
||||
|
|
|
@ -129,7 +129,7 @@ protected:
|
|||
wxScrolledWindow* m_page_view {nullptr};
|
||||
wxBoxSizer* m_page_sizer {nullptr};
|
||||
|
||||
ModeSizer* m_mode_sizer;
|
||||
ModeSizer* m_mode_sizer {nullptr};
|
||||
|
||||
struct PresetDependencies {
|
||||
Preset::Type type = Preset::TYPE_INVALID;
|
||||
|
|
|
@ -838,9 +838,13 @@ ScalableButton::ScalableButton( wxWindow * parent,
|
|||
Create(parent, id, label, pos, size, style);
|
||||
Slic3r::GUI::wxGetApp().UpdateDarkUI(this);
|
||||
|
||||
SetBitmap(create_scaled_bitmap(icon_name, parent, m_px_cnt));
|
||||
if (m_use_default_disabled_bitmap)
|
||||
SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true));
|
||||
if (!icon_name.empty()) {
|
||||
SetBitmap(create_scaled_bitmap(icon_name, parent, m_px_cnt));
|
||||
if (m_use_default_disabled_bitmap)
|
||||
SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true));
|
||||
if (!label.empty())
|
||||
SetBitmapMargins(int(0.5* em_unit(parent)), 0);
|
||||
}
|
||||
|
||||
if (size != wxDefaultSize)
|
||||
{
|
||||
|
@ -873,6 +877,20 @@ void ScalableButton::SetBitmap_(const ScalableBitmap& bmp)
|
|||
m_current_icon_name = bmp.name();
|
||||
}
|
||||
|
||||
bool ScalableButton::SetBitmap_(const std::string& bmp_name)
|
||||
{
|
||||
m_current_icon_name = bmp_name;
|
||||
if (m_current_icon_name.empty())
|
||||
return false;
|
||||
|
||||
wxBitmap bmp = create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt);
|
||||
SetBitmap(bmp);
|
||||
SetBitmapCurrent(bmp);
|
||||
if (m_use_default_disabled_bitmap)
|
||||
SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScalableButton::SetBitmapDisabled_(const ScalableBitmap& bmp)
|
||||
{
|
||||
SetBitmapDisabled(bmp.bmp());
|
||||
|
@ -898,11 +916,13 @@ void ScalableButton::msw_rescale()
|
|||
{
|
||||
Slic3r::GUI::wxGetApp().UpdateDarkUI(this, m_has_border);
|
||||
|
||||
SetBitmap(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt));
|
||||
if (!m_disabled_icon_name.empty())
|
||||
SetBitmapDisabled(create_scaled_bitmap(m_disabled_icon_name, m_parent, m_px_cnt));
|
||||
else if (m_use_default_disabled_bitmap)
|
||||
SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true));
|
||||
if (!m_current_icon_name.empty()) {
|
||||
SetBitmap(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt));
|
||||
if (!m_disabled_icon_name.empty())
|
||||
SetBitmapDisabled(create_scaled_bitmap(m_disabled_icon_name, m_parent, m_px_cnt));
|
||||
else if (m_use_default_disabled_bitmap)
|
||||
SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true));
|
||||
}
|
||||
|
||||
if (m_width > 0 || m_height>0)
|
||||
{
|
||||
|
|
|
@ -231,6 +231,7 @@ public:
|
|||
~ScalableButton() {}
|
||||
|
||||
void SetBitmap_(const ScalableBitmap& bmp);
|
||||
bool SetBitmap_(const std::string& bmp_name);
|
||||
void SetBitmapDisabled_(const ScalableBitmap &bmp);
|
||||
int GetBitmapHeight();
|
||||
void UseDefaultBitmapDisabled();
|
||||
|
|
|
@ -379,7 +379,8 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx)
|
|||
// PresetBundle bundle;
|
||||
on_progress(L("Loading repaired model"), 80);
|
||||
DynamicPrintConfig config;
|
||||
bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &model, false);
|
||||
ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::EnableSilent };
|
||||
bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), config, config_substitutions, &model, false);
|
||||
boost::filesystem::remove(path_dst);
|
||||
if (! loaded)
|
||||
throw Slic3r::RuntimeError(L("Import of the repaired 3mf file failed"));
|
||||
|
|
|
@ -213,4 +213,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,16 +56,15 @@ 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);
|
||||
fs::permissions(target, perms);
|
||||
}
|
||||
|
||||
struct Update
|
||||
|
@ -612,7 +611,7 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
|
|||
update.install();
|
||||
|
||||
PresetBundle bundle;
|
||||
bundle.load_configbundle(update.source.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM);
|
||||
bundle.load_configbundle(update.source.string(), PresetBundle::LoadConfigBundleAttribute::LoadSystem);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << format("Deleting %1% conflicting presets", bundle.prints.size() + bundle.filaments.size() + bundle.printers.size());
|
||||
|
||||
|
@ -710,6 +709,17 @@ void PresetUpdater::slic3r_update_notify()
|
|||
}
|
||||
}
|
||||
|
||||
static void reload_configs_update_gui()
|
||||
{
|
||||
// Reload global configuration
|
||||
auto* app_config = GUI::wxGetApp().app_config;
|
||||
// System profiles should not trigger any substitutions, user profiles may trigger substitutions, but these substitutions
|
||||
// were already presented to the user on application start up. Just do substitutions now and keep quiet about it.
|
||||
GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
GUI::wxGetApp().load_current_presets();
|
||||
GUI::wxGetApp().plater()->set_bed_shape();
|
||||
}
|
||||
|
||||
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, bool no_notification) const
|
||||
{
|
||||
if (! p->enabled_config_update) { return R_NOOP; }
|
||||
|
@ -767,7 +777,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
|
|||
}
|
||||
|
||||
//forced update
|
||||
if(incompatible_version)
|
||||
if (incompatible_version)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. At least one requires higher version of Slicer.", updates.updates.size());
|
||||
|
||||
|
@ -782,14 +792,8 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
|
|||
const auto res = dlg.ShowModal();
|
||||
if (res == wxID_OK) {
|
||||
BOOST_LOG_TRIVIAL(info) << "User wants to update...";
|
||||
|
||||
p->perform_updates(std::move(updates));
|
||||
|
||||
// Reload global configuration
|
||||
auto* app_config = GUI::wxGetApp().app_config;
|
||||
GUI::wxGetApp().preset_bundle->load_presets(*app_config);
|
||||
GUI::wxGetApp().load_current_presets();
|
||||
GUI::wxGetApp().plater()->set_bed_shape();
|
||||
reload_configs_update_gui();
|
||||
return R_UPDATE_INSTALLED;
|
||||
}
|
||||
else {
|
||||
|
@ -814,11 +818,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
|
|||
if (res == wxID_OK) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
|
||||
p->perform_updates(std::move(updates));
|
||||
|
||||
// Reload global configuration
|
||||
auto* app_config = GUI::wxGetApp().app_config;
|
||||
GUI::wxGetApp().preset_bundle->load_presets(*app_config);
|
||||
GUI::wxGetApp().load_current_presets();
|
||||
reload_configs_update_gui();
|
||||
return R_UPDATE_INSTALLED;
|
||||
}
|
||||
else {
|
||||
|
@ -871,11 +871,7 @@ void PresetUpdater::on_update_notification_confirm()
|
|||
if (res == wxID_OK) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
|
||||
p->perform_updates(std::move(p->waiting_updates));
|
||||
|
||||
// Reload global configuration
|
||||
auto* app_config = GUI::wxGetApp().app_config;
|
||||
GUI::wxGetApp().preset_bundle->load_presets(*app_config);
|
||||
GUI::wxGetApp().load_current_presets();
|
||||
reload_configs_update_gui();
|
||||
p->has_waiting_updates = false;
|
||||
//return R_UPDATE_INSTALLED;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
|
|||
case htFlashAir: return new FlashAir(config);
|
||||
case htAstroBox: return new AstroBox(config);
|
||||
case htRepetier: return new Repetier(config);
|
||||
case htPrusaLink: return new PrusaLink(config);
|
||||
default: return nullptr;
|
||||
}
|
||||
} else {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue